[pitivi/ges] Move seek related code to pipeline.py



commit c7fc321279cd73dbfa1ae5253995fa69afa23cc6
Author: Thibault Saunier <thibault saunier collabora com>
Date:   Tue May 1 10:04:42 2012 -0400

    Move seek related code to pipeline.py
    
    Remove the playback file as it doesn't make sense to have these utils anymore
    
    Do the flushing happen right away, ie do it in the pipeline directly not
    through the seeker

 pitivi/clipproperties.py    |   12 +--
 pitivi/effects.py           |   10 ++--
 pitivi/mainwindow.py        |   48 +-------------
 pitivi/project.py           |    3 +-
 pitivi/render.py            |    9 +--
 pitivi/timeline/ruler.py    |    2 +-
 pitivi/timeline/timeline.py |   17 +++--
 pitivi/utils/Makefile.am    |    1 -
 pitivi/utils/pipeline.py    |  156 +++++++++++++++++++++++++++++++++++++++++--
 pitivi/utils/playback.py    |  145 ----------------------------------------
 pitivi/viewer.py            |    6 +-
 11 files changed, 181 insertions(+), 228 deletions(-)
---
diff --git a/pitivi/clipproperties.py b/pitivi/clipproperties.py
index ef8f42f..26b4b89 100644
--- a/pitivi/clipproperties.py
+++ b/pitivi/clipproperties.py
@@ -34,7 +34,6 @@ from pitivi.configure import get_ui_dir
 
 from pitivi.dialogs.depsmanager import DepsManager
 
-from pitivi.utils.playback import Seeker
 from pitivi.utils.ui import EFFECT_TUPLE
 from pitivi.utils.loggable import Loggable
 from pitivi.utils.ui import PADDING, SPACING
@@ -92,7 +91,7 @@ class ClipProperties(gtk.ScrolledWindow, Loggable):
         vbox.set_homogeneous(False)
         vp.add(vbox)
 
-        self.effect_properties_handling = EffectsPropertiesManager(instance.action_log)
+        self.effect_properties_handling = EffectsPropertiesManager(instance)
 
         self.effect_expander = EffectProperties(instance,
                 self.effect_properties_handling, self)
@@ -176,8 +175,6 @@ class EffectProperties(gtk.Expander, gtk.HBox):
         self._info_bar = None
         self._config_ui_h_pos = None
         self._timeline = None
-        # We use the seeker to flush the pipeline when needed
-        self._seeker = Seeker()
 
         self._vcontent = gtk.VPaned()
         self.add(self._vcontent)
@@ -370,7 +367,7 @@ class EffectProperties(gtk.Expander, gtk.HBox):
                     track.add_object(effect)
                     self._updateAll()
                     self.app.action_log.commit()
-                    self._seeker.flush()
+                    self.app.current.pipeline.flushSeek()
 
                     break
 
@@ -530,7 +527,6 @@ class TransformationProperties(gtk.Expander):
         self.default_values = {}
         self.set_label(_("Transformation"))
         self.set_sensitive(False)
-        self._seeker = Seeker()
 
         if not "Frei0r" in soft_deps:
             self.builder = gtk.Builder()
@@ -620,7 +616,7 @@ class TransformationProperties(gtk.Expander):
             box.update_from_effect(self.effect)
 
     def _flushPipeLineCb(self, widget):
-        self._seeker.flush()
+        self.app.current.pipeline.flushSeek()
 
     def _findEffect(self, name):
         for track_effect in self._current_tl_obj.get_track_objects():
@@ -664,7 +660,7 @@ class TransformationProperties(gtk.Expander):
             if self._current_tl_obj:
                 self._current_tl_obj = None
                 self.zoom_scale.set_value(1.0)
-                self._seeker.flush()
+                self.app.current.pipeline.flushSeek()
             self.effect = None
             self.set_sensitive(False)
         self._updateBoxVisibility()
diff --git a/pitivi/effects.py b/pitivi/effects.py
index d2f5cb0..57dfd7b 100644
--- a/pitivi/effects.py
+++ b/pitivi/effects.py
@@ -52,7 +52,6 @@ from pitivi.settings import GlobalSettings
 import pitivi.utils.ui as dnd
 from pitivi.utils.loggable import Loggable
 from pitivi.utils.ui import SPACING
-from pitivi.utils.playback import Seeker
 
 from pitivi.utils.widgets import GstElementSettingsWidget, FractionWidget
 
@@ -770,12 +769,12 @@ PROPS_TO_IGNORE = ['name', 'qos', 'silent', 'message']
 
 
 class EffectsPropertiesManager:
-    def __init__(self, action_log):
+    def __init__(self, instance):
         self.cache_dict = {}
         self._current_effect_setting_ui = None
         self._current_element_values = {}
-        self.action_log = action_log
-        self._seeker = Seeker()
+        self.action_log = instance.action_log
+        self.app = instance
 
     def getEffectConfigurationUI(self, effect):
         """
@@ -846,5 +845,6 @@ class EffectsPropertiesManager:
             self.action_log.begin("Effect property change")
             self._current_effect_setting_ui.element.set_child_property(prop.name, value)
             self.action_log.commit()
-            self._seeker.flush()
+
+            self.app.current.pipeline.flushSeek()
             self._current_element_values[prop.name] = value
diff --git a/pitivi/mainwindow.py b/pitivi/mainwindow.py
index af4b817..e138290 100644
--- a/pitivi/mainwindow.py
+++ b/pitivi/mainwindow.py
@@ -36,7 +36,7 @@ from gettext import gettext as _
 from gtk import RecentManager
 from hashlib import md5
 
-from pitivi.utils.pipeline import PipelineError
+from pitivi.utils.pipeline import Seeker
 from pitivi.utils.loggable import Loggable
 from pitivi.utils.misc import in_devel
 from pitivi.settings import GlobalSettings
@@ -752,10 +752,7 @@ class PitiviMainWindow(gtk.Window, Loggable):
         if self.app.current.timeline.props.duration != 0:
             self.render_button.set_sensitive(True)
 
-        self._seeker = self.app.current.seeker
-        self._seeker.connect("seek", self._timelineSeekCb)
-        self._seeker.connect("seek-relative", self._timelineSeekRelativeCb)
-        self._seeker.connect("flush", self._timelineSeekFlushCb)
+        self._seeker = Seeker()
 
         # preliminary seek to ensure the project pipeline is configured
         self._seeker.seek(0)
@@ -1303,47 +1300,6 @@ class PitiviMainWindow(gtk.Window, Loggable):
 
         previewer.play()
 
-    def _timelineSeekRelativeCb(self, unused_seeker, time):
-        try:
-            position = self.app.current.pipeline.getPosition()
-            position += time
-
-            self.app.current.pipeline.simple_seek(position)
-            self._seeker.setPosition(position)
-
-        except PipelineError:
-            self.error("seek failed %s %s %s", gst.TIME_ARGS(position), format, e)
-
-    def _timelineSeekFlushCb(self, unused_seeker):
-        try:
-            position = self.app.current.pipeline.getPosition()
-            self.app.current.pipeline.simple_seek(position)
-            self._seeker.setPosition(position)
-
-        except PipelineError:
-            self.error("seek failed %s %s %s", gst.TIME_ARGS(position), format, e)
-
-    def _timelineSeekCb(self, ruler, position, format):
-        """
-        The app's main seek method used when the user seeks manually.
-
-        We clamp the seeker position so that it cannot go past 0 or the
-        end of the timeline.
-        """
-        # FIXME: ideally gstreamer should allow seeking to the exact end...
-        # but since it doesn't, we seek one nanosecond before the end.
-        end = self.app.current.timeline.props.duration - 1
-        # CLAMP (0, position, self.app.current.timeline.props.duration)
-        position = sorted((0, position, end))[1]
-
-        try:
-            self.app.current.pipeline.simple_seek(position)
-            self._seeker.setPosition(position)
-        except PipelineError:
-            self.warning("Could not seek to %s", gst.TIME_ARGS(position))
-        # Ensure that the viewer UI is updated when seeking while paused
-        self.viewer.positionCheck()
-
     def updateTitle(self):
         name = touched = ""
         if self.app.current:
diff --git a/pitivi/project.py b/pitivi/project.py
index c32c7d9..883f6f2 100644
--- a/pitivi/project.py
+++ b/pitivi/project.py
@@ -42,7 +42,7 @@ from pitivi.undo.undo import UndoableAction
 from pitivi.configure import get_ui_dir
 
 from pitivi.utils.misc import quote_uri, path_from_uri
-from pitivi.utils.playback import Seeker
+from pitivi.utils.pipeline import Seeker
 from pitivi.utils.loggable import Loggable
 from pitivi.utils.signal import Signallable
 from pitivi.utils.pipeline import Pipeline
@@ -515,6 +515,7 @@ class Project(Signallable, Loggable):
     uri = property(getUri, setUri)
 
     def release(self):
+        self.pipeline.release()
         self.pipeline = None
         self.timeline = None
 
diff --git a/pitivi/render.py b/pitivi/render.py
index b9fa4db..43a4e55 100644
--- a/pitivi/render.py
+++ b/pitivi/render.py
@@ -29,14 +29,12 @@ import gtk
 import gst
 import ges
 import time
-import gst
 
 import pitivi.utils.loggable as log
 
 from gettext import gettext as _
 
 from pitivi import configure
-from pitivi.utils.playback import Seeker
 from pitivi.utils.signal import Signallable
 
 from pitivi.utils.loggable import Loggable
@@ -384,7 +382,6 @@ class RenderDialog(Loggable):
         self.app = app
         self.project = project
         self.system = app.system
-        self._seeker = Seeker()
         if pipeline != None:
             self._pipeline = pipeline
         else:
@@ -890,7 +887,7 @@ class RenderDialog(Loggable):
         for obj, id in self._gstSigId.iteritems():
             obj.disconnect(id)
         self._gstSigId = {}
-        self._seeker.disconnect_by_function(self._updatePositionCb)
+        self.app.current.pipeline.disconnect_by_function(self._updatePositionCb)
 
     def _updateProjectSettings(self):
         """Updates the settings of the project if the render settings changed.
@@ -960,7 +957,7 @@ class RenderDialog(Loggable):
         bus = self._pipeline.get_bus()
         bus.add_signal_watch()
         self._gstSigId[bus] = bus.connect('message', self._busMessageCb)
-        self._seeker.connect("position-changed", self._updatePositionCb)
+        self.app.current.connect("position", self._updatePositionCb)
 
     def _closeButtonClickedCb(self, unused_button):
         self.debug("Render dialog's Close button clicked")
@@ -988,7 +985,7 @@ class RenderDialog(Loggable):
                     else:
                         self.system.uninhibitSleep(RenderDialog.INHIBIT_REASON)
 
-    def _updatePositionCb(self, seeker, position):
+    def _updatePositionCb(self, pipeline, position):
         if self.progress:
             text = None
             timediff = time.time() - self.timestarted
diff --git a/pitivi/timeline/ruler.py b/pitivi/timeline/ruler.py
index 8ba2002..1c20033 100644
--- a/pitivi/timeline/ruler.py
+++ b/pitivi/timeline/ruler.py
@@ -28,7 +28,7 @@ import gtk
 import gst
 import cairo
 
-from pitivi.utils.playback import Seeker
+from pitivi.utils.pipeline import Seeker
 from pitivi.utils.timeline import Zoomable
 from pitivi.utils.loggable import Loggable
 
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index 458a4b4..5f2d145 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -329,13 +329,13 @@ class TimelineCanvas(goocanvas.Canvas, Zoomable, Loggable):
         return True
 
     def _selectionEnd(self, item, target, event):
-        seeker = self.app.current.seeker
         self.pointer_ungrab(self.get_root_item(), event.time)
         self._selecting = False
         self._marquee.props.visibility = goocanvas.ITEM_INVISIBLE
         if not self._got_motion_notify:
             self._timeline.selection.setSelection([], 0)
-            seeker.seek(Zoomable.pixelToNs(event.x))
+            #FIXME Do we need to seek here?
+            #self._pipeline.seek(Zoomable.pixelToNs(event.x))
         else:
             self._got_motion_notify = False
             mode = 0
@@ -799,7 +799,7 @@ class Timeline(gtk.Table, Loggable, Zoomable):
                 elif mod & gtk.gdk.CONTROL_MASK:
                     self._seeker.seek(rtime + 1)
                 else:
-                    self.seeker.seekRelative(long(self.rate * gst.SECOND))
+                    self._seeker.seekRelative(long(self.rate * gst.SECOND))
         finally:
             return True
 
@@ -880,7 +880,7 @@ class Timeline(gtk.Table, Loggable, Zoomable):
                         track.add_object(effect)
                         self.app.action_log.commit()
                         self._factories = None
-                        self.seeker.seek(self._position)
+                        self._seeker.seek(self._position)
                         context.drop_finish(True, timestamp)
 
                         self.timeline.selection.setSelection(timeline_objs, SELECT)
@@ -1184,7 +1184,8 @@ class Timeline(gtk.Table, Loggable, Zoomable):
         self.debug("Setting project %s", project)
         if self._project:
             self._project.disconnect_by_function(self._settingsChangedCb)
-            self.seeker.disconnect_by_function(self.positionChangedCb)
+            self._pipeline.disconnect_by_func(self.positionChangedCb)
+            self.pipeline = None
 
         self._project = project
         if self._project:
@@ -1192,8 +1193,10 @@ class Timeline(gtk.Table, Loggable, Zoomable):
             self.ruler.setProjectFrameRate(self._project.getSettings().videorate)
             self.ruler.zoomChanged()
             self._settingsChangedCb(self._project, None, self._project.getSettings())
-            self.seeker = self._project.seeker
-            self.seeker.connect("position-changed", self.positionChangedCb)
+
+            self._seeker = self._project.seeker
+            self._pipeline = self._project.pipeline
+            self._pipeline.connect("position", self.positionChangedCb)
             self._project.connect("settings-changed", self._settingsChangedCb)
 
     def _settingsChangedCb(self, project, old, new):
diff --git a/pitivi/utils/Makefile.am b/pitivi/utils/Makefile.am
index 32e7c8f..1bdb05c 100644
--- a/pitivi/utils/Makefile.am
+++ b/pitivi/utils/Makefile.am
@@ -5,7 +5,6 @@ utils_PYTHON = 	\
 	extract.py      \
 	timeline.py     \
 	loggable.py     \
-	playback.py     \
 	pipeline.py     \
 	signal.py       \
 	ui.py           \
diff --git a/pitivi/utils/pipeline.py b/pitivi/utils/pipeline.py
index d7c4922..8b3e7c2 100644
--- a/pitivi/utils/pipeline.py
+++ b/pitivi/utils/pipeline.py
@@ -27,6 +27,8 @@
 High-level pipelines
 """
 from pitivi.utils.loggable import Loggable
+from pitivi.utils.signal import Signallable
+
 import gobject
 import gst
 import ges
@@ -37,6 +39,118 @@ class PipelineError(Exception):
     pass
 
 
+class Seeker(Signallable, Loggable):
+    """
+    The Seeker is a singleton helper class to do various seeking
+    operations in the pipeline.
+    """
+    _instance = None
+    __signals__ = {
+        'seek': ['position', 'format'],
+        'flush': [],
+        'seek-relative': ['time'],
+    }
+
+    def __new__(cls, *args, **kwargs):
+        """
+        Override the new method to return the singleton instance if available.
+        Otherwise, create one.
+        """
+        if not cls._instance:
+            cls._instance = super(Seeker, cls).__new__(cls, *args, **kwargs)
+        return cls._instance
+
+    def __init__(self, timeout=80):
+        """
+        @param timeout (optional): the amount of miliseconds for a seek attempt
+        """
+        Signallable.__init__(self)
+        Loggable.__init__(self)
+
+        self.timeout = timeout
+        self.pending_seek_id = None
+        self.position = None
+        self.format = None
+        self._time = None
+
+    def seek(self, position, format=gst.FORMAT_TIME, on_idle=False):
+        self.format = format
+        self.position = position
+
+        if self.pending_seek_id is None:
+            if on_idle:
+                gobject.idle_add(self._seekTimeoutCb)
+            else:
+                self._seekTimeoutCb()
+            self.pending_seek_id = self._scheduleSeek(self.timeout,
+                    self._seekTimeoutCb)
+
+    def seekRelative(self, time, on_idle=False):
+        if self.pending_seek_id is None:
+            self._time = time
+            if on_idle:
+                gobject.idle_add(self._seekRelativeTimeoutCb)
+            else:
+                self._seekTimeoutCb()
+            self.pending_seek_id = self._scheduleSeek(self.timeout,
+                    self._seekTimeoutCb, True)
+
+    def flush(self):
+        try:
+            self.emit('flush')
+        except:
+            self.error("Error while flushing")
+
+    def _scheduleSeek(self, timeout, callback, relative=False):
+        return gobject.timeout_add(timeout, callback, relative)
+
+    def _seekTimeoutCb(self, relative=False):
+        self.pending_seek_id = None
+        if relative:
+            try:
+                self.emit('seek-relative', self._time)
+            except:
+                self.error("Error while seeking %s relative",
+                        self._time)
+                # if an exception happened while seeking, properly
+                # reset ourselves
+                return False
+
+            self._time = None
+        elif self.position != None and self.format != None:
+            position, self.position = self.position, None
+            format, self.format = self.format, None
+            try:
+                self.emit('seek', position, format)
+            except:
+                self.error("Error while seeking to position:%s format: %r",
+                          gst.TIME_ARGS(position), format)
+                # if an exception happened while seeking, properly
+                # reset ourselves
+                return False
+        return False
+
+    def setPosition(self, position):
+        self.emit("position-changed", position)
+
+
+#-----------------------------------------------------------------------------#
+#                   Pipeline utils                                            #
+def togglePlayback(pipeline):
+    if int(pipeline.get_state()[1]) == int(gst.STATE_PLAYING):
+        state = gst.STATE_PAUSED
+    else:
+        state = gst.STATE_PLAYING
+
+    res = pipeline.set_state(state)
+    if res == gst.STATE_CHANGE_FAILURE:
+        gst.error("Could no set state to %s")
+        state = gst.STATE_NULL
+        pipeline.set_state(state)
+
+    return state
+
+
 class Pipeline(ges.TimelinePipeline, Loggable):
     """
 
@@ -63,7 +177,7 @@ class Pipeline(ges.TimelinePipeline, Loggable):
         "eos": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                         ()),
         "error": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
-                        (gobject.TYPE_POINTER, gobject.TYPE_POINTER)),
+                        (gobject.TYPE_STRING, gobject.TYPE_STRING)),
         "element-message": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                         (gobject.TYPE_POINTER,))}
 
@@ -77,6 +191,12 @@ class Pipeline(ges.TimelinePipeline, Loggable):
         self._listening = False  # for the position handler
         self._listeningInterval = 300  # default 300ms
         self._listeningSigId = 0
+        self._seeker = Seeker()
+
+        self._duration = gst.CLOCK_TIME_NONE
+        self._seeker.connect("seek", self._seekCb)
+        self._seeker.connect("seek-relative", self._seekRelativeCb)
+        self._seeker.connect("flush", self._seekFlushCb)
 
     def release(self):
         """
@@ -92,10 +212,30 @@ class Pipeline(ges.TimelinePipeline, Loggable):
         self._bus.disconnect_by_func(self._busMessageCb)
         self._bus.remove_signal_watch()
         self._bus.set_sync_handler(None)
+
+        self._seeker.disconnect_by_func(self._seekRelativeCb)
+        self._seeker.disconnect_by_func(self._seekFlushCb)
+        self._seeker.disconnect_by_func(self._seekCb)
+
         self.setState(gst.STATE_NULL)
         self._bus = None
 
-    def flushSeekVideo(self):
+    def _seekRelativeCb(self, unused_seeker, time):
+        self.seekRelative(time)
+
+    def _seekFlushCb(self, unused_seeker):
+        self.flushSeek()
+
+    def _seekCb(self, ruler, position, format):
+        """
+        The app's main seek method used when the user seeks manually.
+
+        We clamp the seeker position so that it cannot go past 0 or the
+        end of the timeline.
+        """
+        self.simple_seek(position)
+
+    def flushSeek(self):
         self.pause()
         try:
             self.seekRelative(0)
@@ -194,11 +334,16 @@ class Pipeline(ges.TimelinePipeline, Loggable):
         try:
             dur, format = self.query_duration(format)
         except Exception, e:
+
             self.handleException(e)
             raise PipelineError("Couldn't get duration")
 
         self.log("Got duration %s" % gst.TIME_ARGS(dur))
-        self.emit("duration-changed", dur)
+        if self._duration != dur:
+            self.emit("duration-changed", dur)
+
+        self._duration = dur
+
         return dur
 
     def activatePositionListener(self, interval=300):
@@ -275,13 +420,14 @@ class Pipeline(ges.TimelinePipeline, Loggable):
         if not res:
             self.debug("seeking failed")
             raise PipelineError("seek failed")
+
         self.debug("seeking succesfull")
         self.emit('position', position)
 
     def seekRelative(self, time):
         seekvalue = max(0, min(self.getPosition() + time,
             self.getDuration()))
-        self.seek(seekvalue)
+        self.simple_seek(seekvalue)
 
     #}
     ## Private methods
@@ -332,7 +478,7 @@ class Pipeline(ges.TimelinePipeline, Loggable):
 
     def _handleErrorMessage(self, error, detail, source):
         self.error("error from %s: %s (%s)" % (source, error, detail))
-        self.emit('error', error, detail)
+        self.emit('error', error.message, detail)
 
     def _busSyncMessageHandler(self, unused_bus, message):
         if message.type == gst.MESSAGE_ELEMENT:
diff --git a/pitivi/viewer.py b/pitivi/viewer.py
index b73761b..f48f794 100644
--- a/pitivi/viewer.py
+++ b/pitivi/viewer.py
@@ -31,7 +31,7 @@ from math import pi
 
 from pitivi.utils.loggable import Loggable
 from pitivi.settings import GlobalSettings
-from pitivi.utils.playback import Seeker
+from pitivi.utils.pipeline import Seeker
 from pitivi.utils.ui import SPACING, hex_to_rgb
 from pitivi.utils.widgets import TimeWidget
 
@@ -148,7 +148,7 @@ class PitiviViewer(gtk.VBox, Loggable):
                 self.pipeline.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, position)
 
         self._setUiActive()
-        self.seeker = self.app.projectManager.current.seeker
+        self.seeker = Seeker()
 
     def _disconnectFromPipeline(self):
         self.debug("pipeline:%r", self.pipeline)
@@ -402,7 +402,7 @@ class PitiviViewer(gtk.VBox, Loggable):
 
     def _goToEndCb(self, unused_button):
         try:
-            end = self.app.current.timeline.props.duration
+            end = self.app.current.pipeline.getDuration()
         except:
             self.warning("Couldn't get timeline duration")
         try:



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]