Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/TurtleArt/tagplay.py
diff options
context:
space:
mode:
Diffstat (limited to 'TurtleArt/tagplay.py')
-rw-r--r--TurtleArt/tagplay.py495
1 files changed, 384 insertions, 111 deletions
diff --git a/TurtleArt/tagplay.py b/TurtleArt/tagplay.py
index 5c86f62..83f9df5 100644
--- a/TurtleArt/tagplay.py
+++ b/TurtleArt/tagplay.py
@@ -1,85 +1,363 @@
-#Copyright (c) 2009, Walter Bender (on behalf of Sugar Labs)
-
-#Permission is hereby granted, free of charge, to any person obtaining a copy
-#of this software and associated documentation files (the "Software"), to deal
-#in the Software without restriction, including without limitation the rights
-#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-#copies of the Software, and to permit persons to whom the Software is
-#furnished to do so, subject to the following conditions:
-
-#The above copyright notice and this permission notice shall be included in
-#all copies or substantial portions of the Software.
-
-#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-#THE SOFTWARE.
-
"""
-Video and audio playback
-
-Based on code snippets from
-http://wiki.sugarlabs.org/go/Development_Team/Almanac/GStreamer
+ tagplay.py
+ refactored based on Jukebox Activity
+ Copyright (C) 2007 Andy Wingo <wingo@pobox.com>
+ Copyright (C) 2007 Red Hat, Inc.
+ Copyright (C) 2008-2010 Kushal Das <kushal@fedoraproject.org>
+ Copyright (C) 2010 Walter Bender
"""
-import gtk
+
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+
+
+import logging
+from gettext import gettext as _
+import os
+
+from sugar.activity import activity
+from sugar.graphics.objectchooser import ObjectChooser
+from sugar import mime
+
import pygtk
pygtk.require('2.0')
+
+import sys
+
+import gobject
+
import pygst
pygst.require('0.10')
import gst
import gst.interfaces
-import gobject
-gobject.threads_init()
+import gtk
-try:
- from sugar.datastore import datastore
-except ImportError:
- pass
+import urllib
+from ConfigParser import ConfigParser
+cf = ConfigParser()
-class Gplay:
- def __init__(self):
- self.window = None
+def play_audio(lc, file_path):
+ """ Called from Show block of audio media """
+ if lc.gplay is not None and lc.gplay.player is not None:
+ if lc.gplay.player.playing:
+ lc.gplay.player.stop()
+ if lc.gplay.bin is not None:
+ lc.gplay.bin.destroy()
+
+ lc.gplay = Gplay(lc, lc.tw.canvas.width, lc.tw.canvas.height, 4, 3)
+ lc.gplay.start(file_path)
+
+
+def play_movie_from_file(lc, filepath, x, y, w, h):
+ """ Called from Show block of video media """
+ if lc.gplay is not None and lc.gplay.player is not None:
+ if lc.gplay.player.playing:
+ lc.gplay.player.stop()
+ if lc.gplay.bin is not None:
+ lc.gplay.bin.destroy()
+
+ lc.gplay = Gplay(lc, x, y, w, h)
+ lc.gplay.start(filepath)
+
+
+def stop_media(lc):
+ """ Called from Clean block and toolbar Stop button """
+ if lc.gplay == None:
+ return
+
+ if lc.gplay.player is not None:
+ lc.gplay.player.stop()
+ if lc.gplay.bin != None:
+ lc.gplay.bin.destroy()
+
+ lc.gplay = None
+
+
+def media_playing(lc):
+
+ if lc.gplay == None:
+ return False
+ return lc.gplay.player.playing
+
+
+class Gplay():
+ UPDATE_INTERVAL = 500
+
+ def __init__(self, lc, x, y, w, h):
+
+ self.update_id = -1
+ self.changed_id = -1
+ self.seek_timeout_id = -1
+ self.player = None
+ self.uri = None
+ self.playlist = []
+ self.jobjectlist = []
+ self.playpath = None
+ self.only_audio = False
+ self.got_stream_info = False
+ self.currentplaying = 0
+ self.p_position = gst.CLOCK_TIME_NONE
+ self.p_duration = gst.CLOCK_TIME_NONE
+
+ self.bin = gtk.Window()
+
+ self.videowidget = VideoWidget()
+ self.bin.add(self.videowidget)
+ self.bin.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_NORMAL)
+ self.bin.set_decorated(False)
+ if lc.tw.running_sugar:
+ self.bin.set_transient_for(lc.tw.activity)
+
+ self.bin.move(x, y+108)
+ self.bin.resize(w, h)
+ self.bin.show_all()
+
+ self._want_document = True
+
+ def _player_eos_cb(self, widget):
+ logging.debug("end of stream")
+ pass
+
+ def _player_error_cb(self, widget, message, detail):
+ self.player.stop()
+ self.player.set_uri(None)
+ logging.debug("Error: %s - %s" % (message, detail))
+
+ def _player_stream_info_cb(self, widget, stream_info):
+ if not len(stream_info) or self.got_stream_info:
+ return
+
+ GST_STREAM_TYPE_UNKNOWN = 0
+ GST_STREAM_TYPE_AUDIO = 1
+ GST_STREAM_TYPE_VIDEO = 2
+ GST_STREAM_TYPE_TEXT = 3
+
+ only_audio = True
+ for item in stream_info:
+ if item.props.type == GST_STREAM_TYPE_VIDEO:
+ only_audio = False
+ self.only_audio = only_audio
+ self.got_stream_info = True
+
+ def read_file(self, file_path):
+ self.uri = os.path.abspath(file_path)
+ if os.path.islink(self.uri):
+ self.uri = os.path.realpath(self.uri)
+ gobject.idle_add(self.start, self.uri)
+
+ def getplaylist(self, links):
+ result = []
+ for x in links:
+ if x.startswith('http://'):
+ result.append(x)
+ elif x.startswith('#'):
+ continue
+ else:
+ result.append('file://' + \
+ urllib.quote(os.path.join(self.playpath,x)))
+ return result
+
+ def start(self, uri=None):
+ self._want_document = False
+ self.playpath = os.path.dirname(uri)
+ if not uri:
+ return False
+ # FIXME: parse m3u files and extract actual URL
+ if uri.endswith(".m3u") or uri.endswith(".m3u8"):
+ self.playlist.extend(self.getplaylist([line.strip() \
+ for line in open(uri).readlines()]))
+ elif uri.endswith('.pls'):
+ try:
+ cf.readfp(open(uri))
+ x = 1
+ while True:
+ self.playlist.append(cf.get("playlist",'File'+str(x)))
+ x += 1
+ except:
+ #read complete
+ pass
+ else:
+ self.playlist.append("file://" + urllib.quote(os.path.abspath(uri)))
+ if not self.player:
+ # lazy init the player so that videowidget is realized
+ # and has a valid widget allocation
+ self.player = GstPlayer(self.videowidget)
+ self.player.connect("eos", self._player_eos_cb)
+ self.player.connect("error", self._player_error_cb)
+ self.player.connect("stream-info", self._player_stream_info_cb)
+
+ try:
+ if not self.currentplaying:
+ logging.info("Playing: " + self.playlist[0])
+ self.player.set_uri(self.playlist[0])
+ self.currentplaying = 0
+ self.play_toggled()
+ self.show_all()
+ else:
+ pass
+ except:
+ pass
+ return False
+
+ def play_toggled(self):
+ if self.player.is_playing():
+ self.player.pause()
+ else:
+ if self.player.error:
+ pass
+ else:
+ self.player.play()
+ if self.update_id == -1:
+ self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
+ self.update_scale_cb)
+
+ def volume_changed_cb(self, widget, value):
+ if self.player:
+ self.player.player.set_property('volume', value)
+
+ def scale_button_press_cb(self, widget, event):
+ self.was_playing = self.player.is_playing()
+ if self.was_playing:
+ self.player.pause()
+
+ # don't timeout-update position during seek
+ if self.update_id != -1:
+ gobject.source_remove(self.update_id)
+ self.update_id = -1
+
+ def scale_value_changed_cb(self, scale):
+ # see seek.c:seek_cb
+ real = long(scale.get_value() * self.p_duration / 100) # in ns
+ self.player.seek(real)
+ # allow for a preroll
+ self.player.get_state(timeout=50 * gst.MSECOND) # 50 ms
+
+ def scale_button_release_cb(self, widget, event):
+ # see seek.cstop_seek
+ widget.disconnect(self.changed_id)
+ self.changed_id = -1
+
+ if self.seek_timeout_id != -1:
+ gobject.source_remove(self.seek_timeout_id)
+ self.seek_timeout_id = -1
+ else:
+ if self.was_playing:
+ self.player.play()
+
+ if self.update_id != -1:
+ self.error('Had a previous update timeout id')
+ else:
+ self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
+ self.update_scale_cb)
+
+ def update_scale_cb(self):
+ self.p_position, self.p_duration = self.player.query_position()
+ if self.p_position != gst.CLOCK_TIME_NONE:
+ value = self.p_position * 100.0 / self.p_duration
+
+ return True
+
+
+class GstPlayer(gobject.GObject):
+ __gsignals__ = {
+ 'error': (gobject.SIGNAL_RUN_FIRST, None, [str, str]),
+ 'eos' : (gobject.SIGNAL_RUN_FIRST, None, []),
+ '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")
+
+ self.videowidget = videowidget
+ self._init_video_sink()
- self.player = gst.element_factory_make("playbin", "playbin")
- xis = gst.element_factory_make("xvimagesink", "xvimagesink")
- self.player.set_property("video-sink", xis)
bus = self.player.get_bus()
bus.enable_sync_message_emission()
bus.add_signal_watch()
- self.SYNC_ID = bus.connect('sync-message::element', \
- self._onSyncMessageCb)
+ bus.connect('sync-message::element', self.on_sync_message)
+ bus.connect('message', self.on_message)
- def _onSyncMessageCb(self, bus, 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 True
+ return
if message.structure.get_name() == 'prepare-xwindow-id':
- if self.window is None:
- return True
- self.window.set_sink(message.src)
+ self.videowidget.set_sink(message.src)
message.src.set_property('force-aspect-ratio', True)
- return 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_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 setFile(self, path):
- uri = "file://" + str(path)
- if (self.player.get_property('uri') == uri):
- self.seek(gst.SECOND*0)
- return
+ 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)
- self.player.set_state(gst.STATE_READY)
- self.player.set_property('uri', uri)
- ext = uri[len(uri)-3:]
- if (ext == "jpg"):
- self.pause()
+ 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:
- self.play()
+ 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)
+
+ 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, conv, videosink)
+ self.player.set_property("video-sink", self.bin)
- def queryPosition(self):
- #"Returns a (position, duration) tuple"
+ def query_position(self):
+ "Returns a (position, duration) tuple"
try:
position, format = self.player.query_position(gst.FORMAT_TIME)
except:
@@ -89,31 +367,38 @@ class Gplay:
duration, format = self.player.query_duration(gst.FORMAT_TIME)
except:
duration = gst.CLOCK_TIME_NONE
+
return (position, duration)
- def seek(self, time):
- event = gst.event_new_seek(1.0,\
- gst.FORMAT_TIME,\
- gst.SEEK_FLAG_FLUSH|gst.SEEK_FLAG_ACCURATE,\
- gst.SEEK_TYPE_SET,\
- time,\
- gst.SEEK_TYPE_NONE, 0)
+ 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):
- self.playing = False
+ logging.debug("pausing player")
self.player.set_state(gst.STATE_PAUSED)
+ self.playing = False
def play(self):
- self.playing = True
+ logging.debug("playing player")
self.player.set_state(gst.STATE_PLAYING)
-
+ self.playing = True
+ self.error = False
+
def stop(self):
- self.playing = False
self.player.set_state(gst.STATE_NULL)
- # self.nextMovie()
+ logging.debug("stopped player")
def get_state(self, timeout=1):
return self.player.get_state(timeout=timeout)
@@ -121,56 +406,44 @@ class Gplay:
def is_playing(self):
return self.playing
-class PlayVideoWindow(gtk.Window):
+
+class VideoWidget(gtk.DrawingArea):
def __init__(self):
- gtk.Window.__init__(self)
+ gtk.DrawingArea.__init__(self)
+ self.set_events(gtk.gdk.EXPOSURE_MASK)
self.imagesink = None
self.unset_flags(gtk.DOUBLE_BUFFERED)
self.set_flags(gtk.APP_PAINTABLE)
- def set_sink(self, sink):
- if (self.imagesink != None):
- assert self.window.xid
- self.imagesink = None
- del self.imagesink
+ 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
- if self.window is not None:
- self.imagesink.set_xwindow_id(self.window.xid)
+ self.imagesink.set_xwindow_id(self.window.xid)
-def play_audio(lc, filepath):
- print "loading audio id: " + filepath
- if lc.gplay == None:
- lc.gplay = Gplay()
- lc.gplay.setFile("file:///" + filepath)
-def play_movie_from_file(lc, filepath, x, y, w, h):
- if lc.gplay == None:
- lc.gplay = Gplay()
- # wait for current movie to stop playing
- if lc.gplay.is_playing:
- print "already playing..."
- lc.gplay.setFile("file:///" + filepath)
- if lc.gplay.window == None:
- gplayWin = PlayVideoWindow()
- lc.gplay.window = gplayWin
- gplayWin.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
- gplayWin.set_decorated(False)
- if lc.tw.running_sugar:
- gplayWin.set_transient_for(lc.tw.activity)
- # y position is too high for some reason (toolbox?) adding offset
- gplayWin.move(x, y+108)
- gplayWin.resize(w, h)
- gplayWin.show_all()
+if __name__ == '__main__':
+ window = gtk.Window()
+
+ view = VideoWidget()
+
+ player = GstPlayer(view)
+
+ window.add(view)
+
+ player.set_uri('http://78.46.73.237:8000/prog')
+ player.play()
+ window.show_all()
+
+
+
+ gtk.main()
-def stop_media(lc):
- if lc.gplay == None:
- return
- lc.gplay.stop()
- if lc.gplay.window != None:
- # We need to destroy the video window
- # print dir(lc.gplay.window)
- lc.gplay.window.destroy()
- lc.gplay = None