From e434d5d7ecde8db0e24fd7284769509edff835f2 Mon Sep 17 00:00:00 2001 From: Andrés Ambrois Date: Tue, 11 May 2010 16:45:18 +0000 Subject: Initial commit --- diff --git a/activity/activity-secuencia.svg b/activity/activity-secuencia.svg new file mode 100644 index 0000000..ec0bcdd --- /dev/null +++ b/activity/activity-secuencia.svg @@ -0,0 +1,103 @@ + + + + + + + ]> +image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/activity/activity.info b/activity/activity.info new file mode 100644 index 0000000..f39b4c4 --- /dev/null +++ b/activity/activity.info @@ -0,0 +1,9 @@ +[Activity] +name = Secuencia +license = GPLv2+ +bundle_id = org.ceibaljam.Secuencia +exec = sugar-activity secuencia.Secuencia +icon = activity-secuencia +activity_version = 1 +host_version = 1 +show_launcher = no diff --git a/multimedia.py b/multimedia.py new file mode 100755 index 0000000..34ab734 --- /dev/null +++ b/multimedia.py @@ -0,0 +1,252 @@ +#!/usr/bin/python +# Copyright (c) 2010, CeibalJam! +# 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 + +import gst +import gobject +import gtk + +import logging +logger = logging.getLogger('secuencia: ' + __file__) + +class AudioPlayer(object): + def __init__(self): + self.player = gst.element_factory_make('playbin') + fakesink = gst.element_factory_make('fakesink') + self.player.set_property("video-sink", fakesink) + + bus = self.player.get_bus() + bus.add_signal_watch() + bus.connect('message', self._gstmessage_cb) + + self.playing = False + + def play(self, file): + self.player.set_state(gst.STATE_NULL) + self.player.props.uri = 'file://' + file + self.player.set_state(gst.STATE_PLAYING) + self.playing = True + + def _gstmessage_cb(self, bus, message): + if message.type == gst.MESSAGE_EOS: + self.player.set_state(gst.STATE_NULL) + self.playing = False + elif message.type == gst.MESSAGE_ERROR: + err, debug = message.parse_error() + logger.error('play_pipe: %s %s' % (err, debug)) + self.player.set_state(gst.STATE_NULL) + self.playing = False + +class GstPlayer(gobject.GObject): + __gsignals__ = { + 'error': (gobject.SIGNAL_RUN_FIRST, None, [str, str]), + 'eos' : (gobject.SIGNAL_RUN_FIRST, None, []), + 'tag' : (gobject.SIGNAL_RUN_FIRST, None, [str, str]), + 'stream-info' : (gobject.SIGNAL_RUN_FIRST, None, [object]) + } + + def __init__(self, videowidget): + gobject.GObject.__init__(self) + + self.playing = False + self.error = False + + self.player = gst.element_factory_make("playbin", "player") + + r = gst.registry_get_default() + l = [x for x in r.get_feature_list(gst.ElementFactory) if (gst.ElementFactory.get_klass(x) == "Visualization")] + if len(l): + e = l.pop() # take latest plugin in the list + vis_plug = gst.element_factory_make(e.get_name()) + self.player.set_property('vis-plugin', vis_plug) + + self.overlay = None + self.videowidget = videowidget + self._init_video_sink() + + bus = self.player.get_bus() + bus.enable_sync_message_emission() + bus.add_signal_watch() + bus.connect('sync-message::element', self.on_sync_message) + bus.connect('message', self.on_message) + + def set_uri(self, uri): + self.player.set_property('uri', uri) + + def on_sync_message(self, bus, message): + if message.structure is None: + return + if message.structure.get_name() == 'prepare-xwindow-id': + self.videowidget.set_sink(message.src) + message.src.set_property('force-aspect-ratio', True) + + def on_message(self, bus, message): + t = message.type + if t == gst.MESSAGE_ERROR: + err, debug = message.parse_error() + logging.debug("Error: %s - %s" % (err, debug)) + self.error = True + self.emit("eos") + self.playing = False + self.emit("error", str(err), str(debug)) + elif t == gst.MESSAGE_EOS: + self.emit("eos") + self.playing = False + elif t == gst.MESSAGE_TAG: + tags = message.parse_tag() + for tag in tags.keys(): + self.emit('tag', str(tag), str(tags[tag])) + elif t == gst.MESSAGE_STATE_CHANGED: + old, new, pen = message.parse_state_changed() + if old == gst.STATE_READY and new == gst.STATE_PAUSED: + self.emit('stream-info', self.player.props.stream_info_value_array) + + def _init_video_sink(self): + self.bin = gst.Bin() + videoscale = gst.element_factory_make('videoscale') + self.bin.add(videoscale) + pad = videoscale.get_pad("sink") + ghostpad = gst.GhostPad("sink", pad) + self.bin.add_pad(ghostpad) + videoscale.set_property("method", 0) + + caps_string = "video/x-raw-yuv, " + r = self.videowidget.get_allocation() + if r.width > 500 and r.height > 500: + # Sigh... xvimagesink on the XOs will scale the video to fit + # but ximagesink in Xephyr does not. So we live with unscaled + # video in Xephyr so that the XO can work right. + w = 480 + h = float(w) / float(float(r.width) / float(r.height)) + caps_string += "width=%d, height=%d" % (w, h) + else: + caps_string += "width=480, height=360" + caps = gst.Caps(caps_string) + self.filter = gst.element_factory_make("capsfilter", "filter") + self.bin.add(self.filter) + self.filter.set_property("caps", caps) + + textoverlay = gst.element_factory_make('textoverlay') + self.overlay = textoverlay + self.bin.add(textoverlay) + conv = gst.element_factory_make ("ffmpegcolorspace", "conv"); + self.bin.add(conv) + videosink = gst.element_factory_make('autovideosink') + self.bin.add(videosink) + gst.element_link_many(videoscale, self.filter, textoverlay, conv, videosink) + self.player.set_property("video-sink", self.bin) + + def set_overlay(self, title, artist, album): + text = "%s\n%s" % (title, artist) + if album and len(album): + text += "\n%s" % album + self.overlay.set_property("text", text) + self.overlay.set_property("font-desc", "sans bold 14") + self.overlay.set_property("halignment", "right") + self.overlay.set_property("valignment", "bottom") + try: + # Only in OLPC versions of gstreamer-plugins-base for now + self.overlay.set_property("line-align", "left") + except: + pass + + def query_position(self): + "Returns a (position, duration) tuple" + try: + position, format = self.player.query_position(gst.FORMAT_TIME) + except: + position = gst.CLOCK_TIME_NONE + + try: + duration, format = self.player.query_duration(gst.FORMAT_TIME) + except: + duration = gst.CLOCK_TIME_NONE + + return (position, duration) + + def seek(self, location): + """ + @param location: time to seek to, in nanoseconds + """ + event = gst.event_new_seek(1.0, gst.FORMAT_TIME, + gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE, + gst.SEEK_TYPE_SET, location, + gst.SEEK_TYPE_NONE, 0) + + res = self.player.send_event(event) + if res: + self.player.set_new_stream_time(0L) + else: + logging.debug("seek to %r failed" % location) + + def pause(self): + logging.debug("pausing player") + self.player.set_state(gst.STATE_PAUSED) + self.playing = False + + def play(self): + logging.debug("playing player") + self.player.set_state(gst.STATE_PLAYING) + self.playing = True + self.error = False + + def stop(self): + self.player.set_state(gst.STATE_NULL) + logging.debug("stopped player") + + def get_state(self, timeout=1): + return self.player.get_state(timeout=timeout) + + def is_playing(self): + return self.playing + +class VideoWidget(gtk.DrawingArea): + def __init__(self): + gtk.DrawingArea.__init__(self) + self.set_events(gtk.gdk.POINTER_MOTION_MASK | + gtk.gdk.POINTER_MOTION_HINT_MASK | + gtk.gdk.EXPOSURE_MASK | + gtk.gdk.KEY_PRESS_MASK | + gtk.gdk.KEY_RELEASE_MASK) + self.imagesink = None + self.unset_flags(gtk.DOUBLE_BUFFERED) + self.set_flags(gtk.APP_PAINTABLE) + + def do_expose_event(self, event): + if self.imagesink: + self.imagesink.expose() + return False + else: + return True + + def set_sink(self, sink): + assert self.window.xid + self.imagesink = sink + self.imagesink.set_xwindow_id(self.window.xid) + +if __name__ == '__main__': + import sys + w = gtk.Window() + w.set_size_request(800,600) + view = VideoWidget() + + player = GstPlayer(view) + w.add(view) + w.connect('destroy', lambda w: gtk.main_quit()) + w.show_all() + print "Playing " + sys.argv[1] + player.set_uri(sys.argv[1]) + player.play() + gtk.main() diff --git a/secuencia.py b/secuencia.py new file mode 100644 index 0000000..04fdc7c --- /dev/null +++ b/secuencia.py @@ -0,0 +1,147 @@ +from sugar.activity import activity +from sugar.graphics.toolbarbox import ToolbarBox +from sugar.graphics.objectchooser import ObjectChooser +from sugar.datastore import datastore +from sugar import mime +from sugar.logger import logging +from gettext import gettext as _ +import gtk +import os +import statvfs +import zipfile +import gio + + +ACTION_ICONS = { + 'sound' : gtk.STOCK_MEDIA_PLAY, + 'video' : gtk.STOCK_MEDIA_PLAY, + 'next' : gtk.STOCK_GO_FORWARD, +} + +class Secuencia(activity.Activity): + def __init__(self, handle): + activity.Activity.__init__(self, handle) + + self.toolbox = activity.ActivityToolbox(self) + self.set_toolbox(self.toolbox) + self.toolbox.show() + + main_box = gtk.VBox() + + frame = gtk.Frame() + main_box.pack_start(frame) + frame.show() + + frame_vbox = gtk.VBox() + frame.add(frame_vbox) + frame_vbox.show() + + self.text = gtk.Label() + frame_vbox.pack_start(self.text) + self.text.show() + + self.image = gtk.Image() + frame_vbox.pack_start(self.image) + self.image.show() + + self.action_icon = gtk.Image() + main_box.pack_start(self.action_icon, False) + self.action_icon.show() + + self.set_canvas(main_box) + main_box.show() + + # A list of (filename, mimetype) tuples + self.list = [] + self.current_pos = -1 + self.executed_action = True + + if self.shared_activity: + pass + elif handle.object_id is not None: + self.list = self.metadata['files'] + else: + self._show_journal_object_picker() + + self.connect('button-press-event', lambda w, e: self._next()) + self.show_all() + self._next() + + def _next(self): + if not self.executed_action: + card = self.list[self.current_pos] + self._execute(card) + self.executed_action = True + else: + self.current_pos += 1 + try: + card = self.list[self.current_pos] + except IndexError: + self.current_pos = -1 + self._next() + else: + self._show_card(card) + self.executed_action = False + + def _execute(self, card): + if 'sound' in card: + # Play sound + pass + elif 'video' in card: + # Play video + pass + + def _show_card(self, card): + if 'image' in card: + self.image.set_from_file(os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'], 'tmp', card['image'])) + else: + self.image.set_from_stock(gtk.STOCK_MISSING_IMAGE) + + if 'action' in card: + self.action_icon.set_from_stock(ACTION_ICONS[card['action']], gtk.ICON_SIZE_LARGE_TOOLBAR) + else: + self.action_icon.set_from_stock(ACTION_ICONS['next'], gtk.ICON_SIZE_LARGE_TOOLBAR) + + self.text.set_text(card['title']) + + def _show_journal_object_picker(self): + """Show the journal object picker to load a document. + + This is for if Read is launched without a document. + """ + chooser = ObjectChooser(_('Choose document'), self, + gtk.DIALOG_MODAL | + gtk.DIALOG_DESTROY_WITH_PARENT, + what_filter=mime.GENERIC_TYPE_TEXT) #FIXME: Filter by this activity's type + try: + result = chooser.run() + if result == gtk.RESPONSE_ACCEPT: + logging.debug('ObjectChooser: %r' % + chooser.get_selected_object()) + jobject = chooser.get_selected_object() + if jobject and jobject.file_path: + self.list = jobject.metadata['files'] + self.read_file(jobject.file_path) + finally: + chooser.destroy() + del chooser + + def read_file(self, path): + zip = zipfile.ZipFile(path) + info = zip.infolist() + size = reduce(lambda s, i: s+i.file_size, info, 0) + stat = os.stavfs(os.environ['SUGAR_ACTIVITY_ROOT']) + if stat[statvfs.F_BSIZE]*stat[statvfs.F_BAVAIL] < size: + #TODO: Alert: Not enough free space + for member in zip.infolist(): + try: + data = member.read() + self.list.append((member.filename, gio.content_type_guess(None, data)) + f = open(os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'], 'tmp', member.filename), 'w') + f.write(data) + del data + finally: + f.close() + + def write_file(self, path): + pass diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..382f317 --- /dev/null +++ b/setup.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +# Copyright (C) 2006, Red Hat, Inc. +# +# 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 + +from sugar.activity import bundlebuilder + +if __name__ == "__main__": + bundlebuilder.start() -- cgit v0.9.1