diff options
Diffstat (limited to 'multimedia.py')
-rwxr-xr-x | multimedia.py | 252 |
1 files changed, 252 insertions, 0 deletions
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() |