diff options
author | Manuel Kaufmann <humitos@gmail.com> | 2013-01-14 15:42:35 (GMT) |
---|---|---|
committer | Gonzalo Odiard <godiard@gmail.com> | 2013-01-25 12:38:40 (GMT) |
commit | 0bf46d8bf132609bab93a93360e78e09f01d7b88 (patch) | |
tree | 45cf4f0892622a61b3a06caf8365920dadc7f0b1 | |
parent | f4ac3ea1126a571bd0b8e60bcced451066853b9c (diff) |
Modularization of classes
- Put each class in a different .py file
- Rename jukeboxactivity.py to activity.py to make this more standard
Signed-off-by: Manuel Kaufmann <humitos@gmail.com>
Reviewed-by: Gonzalo Odiard <gonzalo@laptop.org>
-rw-r--r-- | activity.py (renamed from jukeboxactivity.py) | 187 | ||||
-rw-r--r-- | activity/activity.info | 2 | ||||
-rw-r--r-- | controls.py (renamed from ControlToolbar.py) | 73 | ||||
-rw-r--r-- | player.py | 182 | ||||
-rw-r--r-- | playlist.py (renamed from widgets.py) | 2 | ||||
-rw-r--r-- | viewtoolbar.py | 63 |
6 files changed, 278 insertions, 231 deletions
diff --git a/jukeboxactivity.py b/activity.py index 1bc4d4a..14fcbe9 100644 --- a/jukeboxactivity.py +++ b/activity.py @@ -1,11 +1,3 @@ -""" - jukeboxactivity.py - Activity that plays media. - Copyright (C) 2007 Andy Wingo <wingo@pobox.com> - Copyright (C) 2007 Red Hat, Inc. - Copyright (C) 2008-2010 Kushal Das <kushal@fedoraproject.org> -""" - # 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 @@ -21,6 +13,12 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA +# Activity that plays media. +# 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) 2013 Manuel Kaufmann <humitos@gmail.com> + import sys import logging import tempfile @@ -49,23 +47,12 @@ from gi.repository import Gtk from gi.repository import Gst from gi.repository import Gio -# Needed for window.get_xid(), xvimagesink.set_window_handle(), -# respectively: -from gi.repository import GdkX11, GstVideo - -# Avoid "Fatal Python error: GC object already tracked" -# http://stackoverflow.com/questions/7496629/gstreamer-appsrc-causes-random-crashes -GObject.threads_init() - -# Initialize GStreamer -Gst.init(None) - import urllib -from ControlToolbar import Control, ViewToolbar -from ConfigParser import ConfigParser -cf = ConfigParser() +from viewtoolbar import ViewToolbar +from controls import Controls +from player import GstPlayer -from widgets import PlayListWidget +from playlist import PlayList PLAYLIST_WIDTH_PROP = 1.0 / 3 @@ -106,7 +93,7 @@ class JukeboxActivity(activity.Activity): toolbar_box.toolbar.insert(view_toolbar_button, -1) view_toolbar_button.show() - self.control = Control(toolbar_box.toolbar, self) + self.control = Controls(toolbar_box.toolbar, self) toolbar_box.toolbar.insert(StopButton(self), -1) @@ -155,7 +142,7 @@ class JukeboxActivity(activity.Activity): self.canvas = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) self._alert = None - self.playlist_widget = PlayListWidget(self.play) + self.playlist_widget = PlayList(self.play) self.playlist_widget.update(self.playlist) self.playlist_widget.show() self.canvas.pack_start(self.playlist_widget, False, True, 0) @@ -657,156 +644,6 @@ class JukeboxActivity(activity.Activity): self.canvas.queue_draw() -class GstPlayer(GObject.GObject): - - __gsignals__ = { - 'error': (GObject.SignalFlags.RUN_FIRST, None, [str, str]), - 'eos': (GObject.SignalFlags.RUN_FIRST, None, []), - } - - def __init__(self, videowidget): - GObject.GObject.__init__(self) - - self.playing = False - self.error = False - - # Create GStreamer pipeline - self.pipeline = Gst.Pipeline() - # Create bus to get events from GStreamer pipeline - self.bus = self.pipeline.get_bus() - self.bus.add_signal_watch() - - self.bus.connect('message::eos', self.__on_eos_message) - self.bus.connect('message::error', self.__on_error_message) - - # This is needed to make the video output in our DrawingArea - self.bus.enable_sync_message_emission() - self.bus.connect('sync-message::element', self.__on_sync_message) - - # Create GStreamer elements - self.player = Gst.ElementFactory.make('playbin', None) - self.pipeline.add(self.player) - - # Set the proper flags to render the vis-plugin - GST_PLAY_FLAG_VIS = 1 << 3 - GST_PLAY_FLAG_TEXT = 1 << 2 - self.player.props.flags |= GST_PLAY_FLAG_VIS - self.player.props.flags |= GST_PLAY_FLAG_TEXT - - r = Gst.Registry.get() - l = [x for x in r.get_feature_list(Gst.ElementFactory) - if (x.get_metadata('klass') == "Visualization")] - if len(l): - e = l.pop() # take latest plugin in the list - vis_plug = Gst.ElementFactory.make(e.get_name(), e.get_name()) - self.player.set_property('vis-plugin', vis_plug) - - self.overlay = None - videowidget.realize() - self.videowidget = videowidget - self.videowidget_xid = videowidget.get_window().get_xid() - self._init_video_sink() - - def __on_error_message(self, bus, msg): - self.stop() - self.playing = False - self.error = True - err, debug = msg.parse_error() - self.emit('error', err, debug) - - def __on_eos_message(self, bus, msg): - logging.debug('SIGNAL: eos') - self.playing = False - self.emit('eos') - - def __on_sync_message(self, bus, msg): - if msg.get_structure().get_name() == 'prepare-window-handle': - msg.src.set_window_handle(self.videowidget_xid) - - def set_uri(self, uri): - self.pipeline.set_state(Gst.State.READY) - logging.debug('### Setting URI: %s', uri) - self.player.set_property('uri', uri) - - def _init_video_sink(self): - self.bin = Gst.Bin() - videoscale = Gst.ElementFactory.make('videoscale', 'videoscale') - self.bin.add(videoscale) - pad = videoscale.get_static_pad("sink") - ghostpad = Gst.GhostPad.new("sink", pad) - self.bin.add_pad(ghostpad) - videoscale.set_property("method", 0) - - textoverlay = Gst.ElementFactory.make('textoverlay', 'textoverlay') - self.overlay = textoverlay - self.bin.add(textoverlay) - conv = Gst.ElementFactory.make("videoconvert", "conv") - self.bin.add(conv) - videosink = Gst.ElementFactory.make('autovideosink', 'autovideosink') - self.bin.add(videosink) - - videoscale.link(textoverlay) - textoverlay.link(conv) - conv.link(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" - - p_success, position = self.player.query_position(Gst.Format.TIME) - d_success, duration = self.player.query_duration(Gst.Format.TIME) - - return (p_success and d_success, position, duration) - - def seek(self, location): - """ - @param location: time to seek to, in nanoseconds - """ - - logging.debug('Seek: %s ns', location) - - self.pipeline.seek_simple(Gst.Format.TIME, - Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, - location) - - def pause(self): - logging.debug("pausing player") - self.pipeline.set_state(Gst.State.PAUSED) - self.playing = False - - def play(self): - logging.debug("playing player") - self.pipeline.set_state(Gst.State.PLAYING) - self.playing = True - self.error = False - - def stop(self): - self.playing = False - self.pipeline.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): GObject.GObject.__init__(self) diff --git a/activity/activity.info b/activity/activity.info index 76cfd1b..f66c422 100644 --- a/activity/activity.info +++ b/activity/activity.info @@ -3,7 +3,7 @@ name = Jukebox bundle_id = org.laptop.sugar.Jukebox license = GPLv2+ icon = activity-jukebox -exec = sugar-activity jukeboxactivity.JukeboxActivity +exec = sugar-activity activity.JukeboxActivity show_launcher = yes activity_version = 29 mime_types = video/x-theora;audio/x-vorbis;audio/x-flac;audio/x-speex;application/x-ogm-video;application/x-ogm-audio;video/x-mng;audio/x-aiff;audio/x-wav;audio/x-m4a;video/mpeg4;video/mpeg-stream;video/mpeg;application/ogg;video/mpegts;video/mpeg2;video/mpeg1;audio/mpeg;audio/x-ac3;video/x-cdxa;audio/x-au;audio/mpegurl;audio/x-mpegurl;audio/x-vorbis+ogg;audio/x-scpls;audio/ogg;video/ogg;audio/x-flac+ogg;audio/x-speex+ogg;video/x-theora+ogg;video/x-ogm+ogg;video/x-flv;video/mp4;video/x-matroska;video/x-msvideo;video/quicktime, video/x-quicktime, image/mov, audio/aiff, audio/x-midi, video/avi diff --git a/ControlToolbar.py b/controls.py index 2205bde..b84a55a 100644 --- a/ControlToolbar.py +++ b/controls.py @@ -1,68 +1,33 @@ -# Copyright (C) 2007 Andy Wingo <wingo@pobox.com> -# Copyright (C) 2007 Red Hat, Inc. -# Copyright (C) 2008 Kushal Das <kushal@fedoraproject.org> -# 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 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 program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 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 +# 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 +# Copyright (C) 2013 Manuel Kaufmann <humitos@gmail.com> -from gettext import gettext as _ +import logging -from gi.repository import GObject from gi.repository import Gtk +from gi.repository import GObject -from sugar3.graphics.toolbutton import ToolButton -from sugar3.graphics.toggletoolbutton import ToggleToolButton - - -class ViewToolbar(Gtk.Toolbar): - __gtype_name__ = 'ViewToolbar' - - __gsignals__ = { - 'go-fullscreen': (GObject.SignalFlags.RUN_FIRST, - None, - ([])), - 'toggle-playlist': (GObject.SignalFlags.RUN_FIRST, - None, - ([])) - } - - def __init__(self): - GObject.GObject.__init__(self) - - self._show_playlist = ToggleToolButton('view-list') - self._show_playlist.set_active(True) - self._show_playlist.set_tooltip(_('Show Playlist')) - self._show_playlist.connect('toggled', self._playlist_toggled_cb) - self.insert(self._show_playlist, -1) - self._show_playlist.show() - - self._fullscreen = ToolButton('view-fullscreen') - self._fullscreen.set_tooltip(_('Fullscreen')) - self._fullscreen.connect('clicked', self._fullscreen_cb) - self.insert(self._fullscreen, -1) - self._fullscreen.show() - - def _fullscreen_cb(self, button): - self.emit('go-fullscreen') +from gettext import gettext as _ - def _playlist_toggled_cb(self, button): - self.emit('toggle-playlist') +from sugar3.graphics.toolbutton import ToolButton -class Control(GObject.GObject): - """Class to create the Control (play) toolbar""" +class Controls(GObject.GObject): + """Class to create the Control (play, back, forward, + add, remove, etc) toolbar""" def __init__(self, toolbar, jukebox): GObject.GObject.__init__(self) diff --git a/player.py b/player.py new file mode 100644 index 0000000..b5b03da --- /dev/null +++ b/player.py @@ -0,0 +1,182 @@ +# 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 + +# Copyright (C) 2013 Manuel Kaufmann <humitos@gmail.com> + +import logging + +from gi.repository import Gst +from gi.repository import GObject + +# Needed for window.get_xid(), xvimagesink.set_window_handle(), +# respectively: +from gi.repository import GdkX11, GstVideo + +# Avoid "Fatal Python error: GC object already tracked" +# http://stackoverflow.com/questions/7496629/gstreamer-appsrc-causes-random-crashes +# GObject.threads_init() + +# Initialize GStreamer +Gst.init(None) + + +class GstPlayer(GObject.GObject): + + __gsignals__ = { + 'error': (GObject.SignalFlags.RUN_FIRST, None, [str, str]), + 'eos': (GObject.SignalFlags.RUN_FIRST, None, []), + } + + def __init__(self, videowidget): + GObject.GObject.__init__(self) + + self.playing = False + self.error = False + + # Create GStreamer pipeline + self.pipeline = Gst.Pipeline() + # Create bus to get events from GStreamer pipeline + self.bus = self.pipeline.get_bus() + self.bus.add_signal_watch() + + self.bus.connect('message::eos', self.__on_eos_message) + self.bus.connect('message::error', self.__on_error_message) + + # This is needed to make the video output in our DrawingArea + self.bus.enable_sync_message_emission() + self.bus.connect('sync-message::element', self.__on_sync_message) + + # Create GStreamer elements + self.player = Gst.ElementFactory.make('playbin', None) + self.pipeline.add(self.player) + + # Set the proper flags to render the vis-plugin + GST_PLAY_FLAG_VIS = 1 << 3 + GST_PLAY_FLAG_TEXT = 1 << 2 + self.player.props.flags |= GST_PLAY_FLAG_VIS + self.player.props.flags |= GST_PLAY_FLAG_TEXT + + r = Gst.Registry.get() + l = [x for x in r.get_feature_list(Gst.ElementFactory) + if (x.get_metadata('klass') == "Visualization")] + if len(l): + e = l.pop() # take latest plugin in the list + vis_plug = Gst.ElementFactory.make(e.get_name(), e.get_name()) + self.player.set_property('vis-plugin', vis_plug) + + self.overlay = None + videowidget.realize() + self.videowidget = videowidget + self.videowidget_xid = videowidget.get_window().get_xid() + self._init_video_sink() + + def __on_error_message(self, bus, msg): + self.stop() + self.playing = False + self.error = True + err, debug = msg.parse_error() + self.emit('error', err, debug) + + def __on_eos_message(self, bus, msg): + logging.debug('SIGNAL: eos') + self.playing = False + self.emit('eos') + + def __on_sync_message(self, bus, msg): + if msg.get_structure().get_name() == 'prepare-window-handle': + msg.src.set_window_handle(self.videowidget_xid) + + def set_uri(self, uri): + self.pipeline.set_state(Gst.State.READY) + logging.debug('### Setting URI: %s', uri) + self.player.set_property('uri', uri) + + def _init_video_sink(self): + self.bin = Gst.Bin() + videoscale = Gst.ElementFactory.make('videoscale', 'videoscale') + self.bin.add(videoscale) + pad = videoscale.get_static_pad("sink") + ghostpad = Gst.GhostPad.new("sink", pad) + self.bin.add_pad(ghostpad) + videoscale.set_property("method", 0) + + textoverlay = Gst.ElementFactory.make('textoverlay', 'textoverlay') + self.overlay = textoverlay + self.bin.add(textoverlay) + conv = Gst.ElementFactory.make("videoconvert", "conv") + self.bin.add(conv) + videosink = Gst.ElementFactory.make('autovideosink', 'autovideosink') + self.bin.add(videosink) + + videoscale.link(textoverlay) + textoverlay.link(conv) + conv.link(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" + + p_success, position = self.player.query_position(Gst.Format.TIME) + d_success, duration = self.player.query_duration(Gst.Format.TIME) + + return (p_success and d_success, position, duration) + + def seek(self, location): + """ + @param location: time to seek to, in nanoseconds + """ + + logging.debug('Seek: %s ns', location) + + self.pipeline.seek_simple(Gst.Format.TIME, + Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, + location) + + def pause(self): + logging.debug("pausing player") + self.pipeline.set_state(Gst.State.PAUSED) + self.playing = False + + def play(self): + logging.debug("playing player") + self.pipeline.set_state(Gst.State.PLAYING) + self.playing = True + self.error = False + + def stop(self): + self.playing = False + self.pipeline.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 @@ -31,7 +31,7 @@ COLUMNS_NAME = ('index', 'media', 'available') COLUMNS = dict((name, i) for i, name in enumerate(COLUMNS_NAME)) -class PlayListWidget(Gtk.ScrolledWindow): +class PlayList(Gtk.ScrolledWindow): def __init__(self, play_callback): self._playlist = None self._play_callback = play_callback diff --git a/viewtoolbar.py b/viewtoolbar.py new file mode 100644 index 0000000..2f15bbe --- /dev/null +++ b/viewtoolbar.py @@ -0,0 +1,63 @@ +# 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 + +# Copyright (C) 2007 Andy Wingo <wingo@pobox.com> +# Copyright (C) 2007 Red Hat, Inc. +# Copyright (C) 2008 Kushal Das <kushal@fedoraproject.org> +# Copyright (C) 2013 Manuel Kaufmann <humitos@gmail.com> + +import logging + +from gettext import gettext as _ + +from gi.repository import GObject +from gi.repository import Gtk + +from sugar3.graphics.toolbutton import ToolButton +from sugar3.graphics.toggletoolbutton import ToggleToolButton + + +class ViewToolbar(Gtk.Toolbar): + __gtype_name__ = 'ViewToolbar' + + __gsignals__ = { + 'go-fullscreen': (GObject.SignalFlags.RUN_FIRST, + None, + ([])), + 'toggle-playlist': (GObject.SignalFlags.RUN_FIRST, + None, + ([])) + } + + def __init__(self): + GObject.GObject.__init__(self) + + self._show_playlist = ToggleToolButton('view-list') + self._show_playlist.set_active(True) + self._show_playlist.set_tooltip(_('Show Playlist')) + self._show_playlist.connect('toggled', self._playlist_toggled_cb) + self.insert(self._show_playlist, -1) + self._show_playlist.show() + + self._fullscreen = ToolButton('view-fullscreen') + self._fullscreen.set_tooltip(_('Fullscreen')) + self._fullscreen.connect('clicked', self._fullscreen_cb) + self.insert(self._fullscreen, -1) + self._fullscreen.show() + + def _fullscreen_cb(self, button): + self.emit('go-fullscreen') + + def _playlist_toggled_cb(self, button): + self.emit('toggle-playlist') |