# Copyright 2008 Chris Ball. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Screencast Activity: An activity for producing XO tutorials.""" from gettext import gettext as _ from dbus.service import method, signal as dbus_signal import fcntl import gobject import gtk import logging import os import popen2 import re import shutil import signal import sys from sugar.activity import activity from sugar.activity.activity import ActivityToolbox, \ get_bundle_path, get_bundle_name from sugar.graphics.alert import NotifyAlert from sugar.graphics.combobox import ComboBox SERVICE = "org.laptop.Screencast" IFACE = SERVICE PATH = "/org/laptop/Screencast" OUTFILE = "/tmp/recordmydesktop.ogv" class ScreencastActivity(activity.Activity): """Screencast Activity as specified in activity.info""" def __init__(self, handle): """Set up the Screencast activity.""" super(ScreencastActivity, self).__init__(handle) self._logger = logging.getLogger('screencast-activity') self.timed_id2 = None from sugar.graphics.menuitem import MenuItem from sugar.graphics.icon import Icon # Main layout. Record button, stop button, label. hbox = gtk.HBox() vbox = gtk.VBox() # Toolbar. toolbox = ActivityToolbox(self) activity_toolbar = toolbox.get_activity_toolbar() activity_toolbar.remove(activity_toolbar.share) activity_toolbar.share = None activity_toolbar.remove(activity_toolbar.keep) activity_toolbar.keep = None self.set_toolbox(toolbox) toolbox.show() # Recording buttons. self.record = gtk.Button("Record") self.record.connect("clicked", self.record_cb) self.record.set_size_request(150, 150) recimage = gtk.Image() recimage.set_from_icon_name("media-record", -1) self.record.set_image(recimage) self.stop = gtk.Button("Stop") self.stop.connect("clicked", self.stop_cb) self.stop.set_size_request(150, 150) self.stop.set_sensitive(False) stopimage = gtk.Image() stopimage.set_from_icon_name("media-playback-stop", -1) self.stop.set_image(stopimage) # Record sound checkbox and quality selector hbox2 = gtk.HBox(spacing=50) self.audiocheckbox = gtk.CheckButton(label="record sound") self.audiocheckbox.set_active(True) hbox2.add(self.audiocheckbox) self.qualitycombo = ComboBox() self.qualitycombo.append_item("0", "high quality video") self.qualitycombo.append_item("1", "medium quality video") self.qualitycombo.append_item("2", "low quality video") self.qualitycombo.set_active(2) hbox2.add(self.qualitycombo) options = gtk.Alignment(0.5, 0, 0, 0) options.add(hbox2) # Status label. self.status = gtk.Label(_("Status: Stopped")) hbox.pack_start(self.record, expand=False, padding=40) hbox.pack_start(self.stop, expand=False, padding=40) # Encoding progress bar self.progressbar=gtk.ProgressBar(adjustment=None) self.progressbar.set_fraction(0) self.progressbar.set_text("0% complete") valign = gtk.Alignment(0.5, 0.4, 0, 0) valign.add(vbox) vbox.pack_end(self.progressbar, expand=True, padding=20) vbox.pack_end(self.status, expand=True, padding=40) vbox.pack_end(hbox, expand=True, fill=False) vbox.pack_end(options, expand=True, padding=40) self.set_canvas(valign) self.show_all() self.progressbar.hide() def write_file (self, file_path): print "Saving file to %s" % file_path self.metadata['mime_type'] = 'video/ogg' #try: # shutil.copy(OUTFILE, file_path) #except IOError, e: # print "unable to save to outfile: %s" % e # FIXME: This fails in /tmp. # that comment by probably cjb # I have no idea why it was saving to filepath #added copy-to-journal in check_status_cb #error msgs are OK probably just no video processed #Tony Forster #try: # os.remove(OUTFILE) #except OSError, e: # print "unable to remove outfile: %s" % e def can_close(self): if self.status.get_text().startswith("Status: Stopped"): return True else: self.alert("You need to finish operation before quitting.", self.status.get_text()) def alert(self, title, text=None): alert = NotifyAlert(timeout=10) alert.props.title = title alert.props.msg = text self.add_alert(alert) alert.connect('response', self.alert_cancel_cb) alert.show() def alert_cancel_cb(self, alert, response_id): self.remove_alert(alert) def record_cb(self, record): self.stop.set_sensitive(True) self.record.set_sensitive(False) self.audiocheckbox.set_sensitive(False) self.qualitycombo.set_sensitive(False) execargs = ["./recordmydesktop", "--no-frame", "--overwrite"] if not self.audiocheckbox.get_active(): execargs.append("--no-sound") if self.qualitycombo.get_active()==0: execargs.append("-v_quality") # in later versions seems to be --v_quality instead execargs.append("0") elif self.qualitycombo.get_active()==1: execargs.append("-v_quality") # in later versions seems to be --v_quality instead execargs.append("31") execargs.append("-o") execargs.append(OUTFILE) self.childp = popen2.Popen3(execargs, "t", 0) flags = fcntl.fcntl(self.childp.childerr, fcntl.F_GETFL) fcntl.fcntl(self.childp.childerr, fcntl.F_SETFL, flags|os.O_NONBLOCK) flags = fcntl.fcntl(self.childp.fromchild, fcntl.F_GETFL) fcntl.fcntl(self.childp.fromchild, fcntl.F_SETFL, flags|os.O_NONBLOCK) self.timed_id = gobject.timeout_add(1000, self.check_status_cb) self.status.set_text("Status: Recording") def stop_cb(self, stop): exitret = os.waitpid(self.childp.pid, os.WNOHANG) if exitret[0] == 0: os.kill(self.childp.pid, signal.SIGTERM) self.stop.set_sensitive(False) def update_counter(self): self.progressbar.show() while True: try: strstdout=self.childp.fromchild.read() self.counter_fraction=float(re.search("[0-9][0-9]?[0-9]?", strstdout).group()) percentage=self.counter_fraction/100.0 if percentage>1.0: percentage=1.0 #print "PORCENTAJE %s " % str(percentage) self.progressbar.set_fraction(percentage) self.progressbar.set_text("%d%%"%int(percentage*100)+' complete') except IOError: gtk.main_iteration(block=False) except AttributeError: break except: print "Unexpected error:", sys.exc_info()[0] print "Unexpected error:", sys.exc_info()[1] break def check_status_cb(self): if self.childp.pid: exitret = os.waitpid(self.childp.pid, os.WNOHANG) if exitret[0] != 0: # The recording process exited self.status.set_text("Status: Stopped") if self.timed_id2: gobject.source_remove(self.timed_id2) self.timed_id2 = None self.progressbar.hide() self.alert("Success:", "Saved recording to journal") self.progressbar.set_fraction(0) self.progressbar.set_text('0% complete') self.record.set_sensitive(True) self.audiocheckbox.set_sensitive(True) self.qualitycombo.set_sensitive(True) if self._jobject.metadata['title_set_by_user'] == '1': title = self.metadata['title'] else: title = "My Screencast" os.system("copy-to-journal /tmp/recordmydesktop.ogv -m video/ogg -t \"%s\""% title) return False else: # Maybe we have new stderr. while True: try: err_line = self.childp.childerr.readline() if err_line.startswith("STATE:ENCODING"): if not self.timed_id2: self.timed_id2=gobject.timeout_add(300,self.update_counter) self.status.set_text("Status: Encoding, please wait") except: break return True