[pitivi] previewer: Allow generating thumbnails for assets



commit 7473ce51f93a4d145eb8c97993ba3f3e7b123b34
Author: Swayamjeet <swayam1998 gmail com>
Date:   Mon Jul 15 19:34:35 2019 +0530

    previewer: Allow generating thumbnails for assets
    
    Fixes #2332

 pitivi/medialibrary.py        |  58 +++++++--
 pitivi/project.py             |  40 ++++---
 pitivi/timeline/previewers.py | 267 ++++++++++++++++++++++++------------------
 pitivi/timeline/timeline.py   |  38 +++---
 4 files changed, 238 insertions(+), 165 deletions(-)
---
diff --git a/pitivi/medialibrary.py b/pitivi/medialibrary.py
index dffe0983..60172e5d 100644
--- a/pitivi/medialibrary.py
+++ b/pitivi/medialibrary.py
@@ -42,7 +42,7 @@ from pitivi.dialogs.clipmediaprops import ClipMediaPropsDialog
 from pitivi.dialogs.filelisterrordialog import FileListErrorDialog
 from pitivi.mediafilespreviewer import PreviewWidget
 from pitivi.settings import GlobalSettings
-from pitivi.timeline.previewers import ThumbnailCache
+from pitivi.timeline.previewers import AssetPreviewer
 from pitivi.utils.loggable import Loggable
 from pitivi.utils.misc import disconnectAllByFunc
 from pitivi.utils.misc import path_from_uri
@@ -168,9 +168,13 @@ class FileChooserExtraWidget(Gtk.Grid, Loggable):
             self.app.settings.proxyingStrategy = ProxyingStrategy.AUTOMATIC
 
 
-class AssetThumbnail(Loggable):
+class AssetThumbnail(GObject.Object, Loggable):
     """Provider of decorated thumbnails for an asset."""
 
+    __gsignals__ = {
+        "thumb-updated": (GObject.SignalFlags.RUN_LAST, None, ()),
+    }
+
     EMBLEMS = {}
     PROXIED = "asset-proxied"
     NO_PROXY = "no-proxy"
@@ -187,10 +191,15 @@ class AssetThumbnail(Loggable):
             os.path.join(get_pixmap_dir(), "%s.svg" % status), 64, 64)
 
     def __init__(self, asset, proxy_manager):
+        GObject.Object.__init__(self)
         Loggable.__init__(self)
         self.__asset = asset
-        self.src_small, self.src_large = self.__get_thumbnails()
         self.proxy_manager = proxy_manager
+        self.previewer = None
+        self.refresh()
+
+    def refresh(self):
+        self.src_small, self.src_large = self.__get_thumbnails()
         self.decorate()
 
     def __get_thumbnails(self):
@@ -228,10 +237,14 @@ class AssetThumbnail(Loggable):
                         small_thumb, large_thumb = self.__get_icons("image-x-generic")
                 else:
                     # Build or reuse a ThumbnailCache.
-                    thumb_cache = ThumbnailCache.get(self.__asset)
-                    small_thumb = thumb_cache.get_preview_thumbnail()
+                    previewer = AssetPreviewer(self.__asset, 90)
+                    small_thumb = previewer.thumb_cache.get_preview_thumbnail()
                     if not small_thumb:
                         small_thumb, large_thumb = self.__get_icons("video-x-generic")
+                        # Only try once to generate the thumbnail.
+                        if not self.previewer:
+                            self.previewer = previewer
+                            previewer.connect("done", self.__done_cb)
                     else:
                         width = small_thumb.props.width
                         height = small_thumb.props.height
@@ -248,6 +261,11 @@ class AssetThumbnail(Loggable):
             small_thumb, large_thumb = self.__get_icons("audio-x-generic")
         return small_thumb, large_thumb
 
+    def __done_cb(self, unused_asset_previewer):
+        """Handles the done signal of our AssetPreviewer."""
+        self.refresh()
+        self.emit("thumb-updated")
+
     @staticmethod
     def get_asset_thumbnails_path(real_uri):
         """Gets normal & large thumbnail path for the asset in the XDG cache.
@@ -794,8 +812,6 @@ class MediaLibraryWidget(Gtk.Box, Loggable):
         dialog.show()
 
     def _addAsset(self, asset):
-        info = asset.get_info()
-
         if self.app.proxy_manager.is_proxy_asset(asset) and \
                 not asset.props.proxy_target:
             self.info("%s is a proxy asset but has no target, "
@@ -823,8 +839,28 @@ class MediaLibraryWidget(Gtk.Box, Loggable):
                                     name,
                                     thumbs_decorator))
 
+            thumbs_decorator.connect("thumb-updated", self.__thumb_updated_cb, asset)
+
         del self._pending_assets[:]
 
+    def __thumb_updated_cb(self, asset_thumbnail, asset):
+        """Handles the thumb-updated signal of the AssetThumbnails in the model."""
+        tree_iter = None
+        for row in self.storemodel:
+            if asset == row[COL_ASSET]:
+                tree_iter = row.iter
+                break
+
+        if not tree_iter:
+            return
+
+        self.storemodel.set_value(tree_iter,
+                                  COL_ICON_64,
+                                  asset_thumbnail.small_thumb)
+        self.storemodel.set_value(tree_iter,
+                                  COL_ICON_128,
+                                  asset_thumbnail.large_thumb)
+
     # medialibrary callbacks
 
     def _assetLoadingProgressCb(self, project, progress, estimated_time):
@@ -838,10 +874,10 @@ class MediaLibraryWidget(Gtk.Box, Loggable):
             if not asset.ready:
                 proxying_files.append(asset)
                 if row[COL_THUMB_DECORATOR].state != AssetThumbnail.IN_PROGRESS:
-                    thumbs_decorator = AssetThumbnail(asset, self.app.proxy_manager)
-                    row[COL_ICON_64] = thumbs_decorator.small_thumb
-                    row[COL_ICON_128] = thumbs_decorator.large_thumb
-                    row[COL_THUMB_DECORATOR] = thumbs_decorator
+                    asset_previewer = row[COL_THUMB_DECORATOR]
+                    asset_previewer.refresh()
+                    row[COL_ICON_64] = asset_previewer.small_thumb
+                    row[COL_ICON_128] = asset_previewer.large_thumb
 
         if progress == 0:
             self._startImporting(project)
diff --git a/pitivi/project.py b/pitivi/project.py
index 177b95f4..6cceefd7 100644
--- a/pitivi/project.py
+++ b/pitivi/project.py
@@ -45,6 +45,7 @@ from pitivi.preset import VideoPresetManager
 from pitivi.render import Encoders
 from pitivi.settings import get_dir
 from pitivi.settings import xdg_cache_home
+from pitivi.timeline.previewers import Previewer
 from pitivi.timeline.previewers import ThumbnailCache
 from pitivi.undo.project import AssetAddedIntention
 from pitivi.undo.project import AssetProxiedIntention
@@ -489,26 +490,27 @@ class ProjectManager(GObject.Object, Loggable):
                 "Could not close project - this could be because there were unsaved changes and the user 
cancelled when prompted about them")
             return False
 
-        self.current_project.finalize()
+        with Previewer.manager.paused(interrupt=True):
+            self.current_project.finalize()
 
-        project = self.current_project
-        self.current_project = None
-        project.create_thumb()
-        self.emit("project-closed", project)
-        # We should never choke on silly stuff like disconnecting signals
-        # that were already disconnected. It blocks the UI for nothing.
-        # This can easily happen when a project load/creation failed.
-        try:
-            project.disconnect_by_function(self._projectChangedCb)
-        except Exception:
-            self.debug(
-                "Tried disconnecting signals, but they were not connected")
-        try:
-            project.pipeline.disconnect_by_function(self._projectPipelineDiedCb)
-        except Exception:
-            self.fixme("Handle better the errors and not get to this point")
-        self._cleanBackup(project.uri)
-        self.exitcode = project.release()
+            project = self.current_project
+            self.current_project = None
+            project.create_thumb()
+            self.emit("project-closed", project)
+            # We should never choke on silly stuff like disconnecting signals
+            # that were already disconnected. It blocks the UI for nothing.
+            # This can easily happen when a project load/creation failed.
+            try:
+                project.disconnect_by_function(self._projectChangedCb)
+            except Exception:
+                self.debug(
+                    "Tried disconnecting signals, but they were not connected")
+            try:
+                project.pipeline.disconnect_by_function(self._projectPipelineDiedCb)
+            except Exception:
+                self.fixme("Handle better the errors and not get to this point")
+            self._cleanBackup(project.uri)
+            self.exitcode = project.release()
 
         return True
 
diff --git a/pitivi/timeline/previewers.py b/pitivi/timeline/previewers.py
index e85dc7bb..1b87069c 100644
--- a/pitivi/timeline/previewers.py
+++ b/pitivi/timeline/previewers.py
@@ -378,7 +378,7 @@ class PreviewGeneratorManager(Loggable):
             self._start_previewer(self._previewers[track_type].pop())
 
 
-class Previewer(Gtk.Layout):
+class Previewer(GObject.Object):
     """Base class for previewers.
 
     Attributes:
@@ -389,8 +389,7 @@ class Previewer(Gtk.Layout):
     manager = PreviewGeneratorManager()
 
     def __init__(self, track_type, max_cpu_usage):
-        Gtk.Layout.__init__(self)
-
+        GObject.Object.__init__(self)
         self.track_type = track_type
         self._max_cpu_usage = max_cpu_usage
 
@@ -430,13 +429,14 @@ class Previewer(Gtk.Layout):
         return max(THUMB_PERIOD, quantized)
 
 
-class ImagePreviewer(Previewer, Zoomable, Loggable):
+class ImagePreviewer(Gtk.Layout, Previewer, Zoomable, Loggable):
     """An image previewer widget, drawing thumbnails."""
 
     # We could define them in Previewer, but for some reason they are ignored.
     __gsignals__ = PREVIEW_GENERATOR_SIGNALS
 
     def __init__(self, ges_elem, max_cpu_usage):
+        Gtk.Layout.__init__(self)
         Previewer.__init__(self, GES.TrackType.VIDEO, max_cpu_usage)
         Zoomable.__init__(self)
         Loggable.__init__(self)
@@ -546,31 +546,27 @@ class ImagePreviewer(Previewer, Zoomable, Loggable):
         Zoomable.__del__(self)
 
 
-class VideoPreviewer(Previewer, Zoomable, Loggable):
-    """A video previewer widget, drawing thumbnails.
+class AssetPreviewer(Previewer, Loggable):
+    """Previewer for creating thumbnails for a video asset.
 
     Attributes:
-        ges_elem (GES.TrackElement): The previewed element.
-        thumbs (dict): Maps (quantized) times to Thumbnail widgets.
         thumb_cache (ThumbnailCache): The pixmaps persistent cache.
     """
 
     # We could define them in Previewer, but for some reason they are ignored.
     __gsignals__ = PREVIEW_GENERATOR_SIGNALS
 
-    def __init__(self, ges_elem, max_cpu_usage):
+    def __init__(self, asset, max_cpu_usage):
         Previewer.__init__(self, GES.TrackType.VIDEO, max_cpu_usage)
-        Zoomable.__init__(self)
         Loggable.__init__(self)
 
-        self.ges_elem = ges_elem
-
         # Guard against malformed URIs
-        self.uri = quote_uri(get_proxy_target(ges_elem).props.id)
+        self.asset = asset
+        self.uri = quote_uri(asset.props.id)
 
         self.__start_id = 0
         self.__preroll_timeout_id = 0
-        self._thumb_cb_id = 0
+        self.__thumb_cb_id = 0
 
         # The thumbs to be generated.
         self.queue = []
@@ -578,14 +574,12 @@ class VideoPreviewer(Previewer, Zoomable, Loggable):
         self.position = -1
         # The positions for which we failed to get a pixbuf.
         self.failures = set()
-        self._thumb_cb_id = None
 
-        self.thumbs = {}
         self.thumb_height = THUMB_HEIGHT
         self.thumb_width = 0
 
         self.thumb_cache = ThumbnailCache.get(self.uri)
-        self._ensure_proxy_thumbnails_cache()
+
         self.thumb_width, unused_height = self.thumb_cache.image_size
         self.pipeline = None
         self.gdkpixbufsink = None
@@ -594,17 +588,22 @@ class VideoPreviewer(Previewer, Zoomable, Loggable):
         # Initial delay before generating the next thumbnail, in millis.
         self.interval = 500
 
-        # Connect signals and fire things up
-        self.ges_elem.connect("notify::in-point", self._inpoint_changed_cb)
-        self.ges_elem.connect("notify::duration", self._duration_changed_cb)
-
         self.become_controlled()
 
-        self.connect("notify::height-request", self._height_changed_cb)
+    def _update_thumbnails(self):
+        """Updates the queue of thumbnails to be produced.
 
-    def pause_generation(self):
-        if self.pipeline:
-            self.pipeline.set_state(Gst.State.READY)
+        Subclasses can also update the managed UI, if any.
+
+        The contract is that if the method sets a queue,
+        it also calls become_controlled().
+        """
+        position = int(self.asset.get_duration() / 2)
+        if position in self.thumb_cache:
+            return
+        if position not in self.failures and position != self.position:
+            self.queue = [position]
+            self.become_controlled()
 
     def _setup_pipeline(self):
         """Creates the pipeline.
@@ -617,7 +616,6 @@ class VideoPreviewer(Previewer, Zoomable, Loggable):
             # bringing the pipeline back to PAUSED.
             self.pipeline.set_state(Gst.State.PAUSED)
             return
-
         pipeline = Gst.parse_launch(
             "uridecodebin uri={uri} name=decode ! "
             "videoconvert ! "
@@ -650,7 +648,7 @@ class VideoPreviewer(Previewer, Zoomable, Loggable):
         thumbnail will be generated +/- 10%. Even then, it will only
         happen when the gobject loop is idle to avoid blocking the UI.
         """
-        if self._thumb_cb_id is not None:
+        if self.__thumb_cb_id:
             # A thumb has already been scheduled.
             return
 
@@ -670,9 +668,9 @@ class VideoPreviewer(Previewer, Zoomable, Loggable):
             self.log("Thumbnailing slowed down to a %.1f ms interval for `%s`",
                      self.interval, path_from_uri(self.uri))
         self.cpu_usage_tracker.reset()
-        self._thumb_cb_id = GLib.timeout_add(self.interval,
-                                             self._create_next_thumb_cb,
-                                             priority=GLib.PRIORITY_LOW)
+        self.__thumb_cb_id = GLib.timeout_add(self.interval,
+                                              self._create_next_thumb_cb,
+                                              priority=GLib.PRIORITY_LOW)
 
     def _start_thumbnailing_cb(self):
         if not self.__start_id:
@@ -703,7 +701,7 @@ class VideoPreviewer(Previewer, Zoomable, Loggable):
 
     def _create_next_thumb_cb(self):
         """Creates a missing thumbnail."""
-        self._thumb_cb_id = None
+        self.__thumb_cb_id = 0
 
         try:
             self.position = self.queue.pop(0)
@@ -725,59 +723,14 @@ class VideoPreviewer(Previewer, Zoomable, Loggable):
         # and then the next thumbnail generation operation will be scheduled.
         return False
 
-    def _update_thumbnails(self):
-        """Updates the thumbnail widgets for the clip at the current zoom."""
-        if not self.thumb_width:
-            # The thumb_width will be available when pipeline has been started
-            return
-
-        thumbs = {}
-        queue = []
-        interval = self.thumb_interval(self.thumb_width)
-        element_left = quantize(self.ges_elem.props.in_point, interval)
-        element_right = self.ges_elem.props.in_point + self.ges_elem.props.duration
-        y = (self.props.height_request - self.thumb_height) / 2
-        for position in range(element_left, element_right, interval):
-            x = Zoomable.nsToPixel(position) - self.nsToPixel(self.ges_elem.props.in_point)
-            try:
-                thumb = self.thumbs.pop(position)
-                self.move(thumb, x, y)
-            except KeyError:
-                thumb = Thumbnail(self.thumb_width, self.thumb_height)
-                self.put(thumb, x, y)
-
-            thumbs[position] = thumb
-            if position in self.thumb_cache:
-                pixbuf = self.thumb_cache[position]
-                thumb.set_from_pixbuf(pixbuf)
-                thumb.set_visible(True)
-            else:
-                if position not in self.failures and position != self.position:
-                    queue.append(position)
-        for thumb in self.thumbs.values():
-            self.remove(thumb)
-        self.thumbs = thumbs
-        self.queue = queue
-        if queue:
-            self.become_controlled()
-
-    def _set_pixbuf(self, pixbuf):
-        """Sets the pixbuf for the thumbnail at the expected position."""
-        position = self.position
-        self.position = -1
-
-        try:
-            thumb = self.thumbs[position]
-        except KeyError:
-            # Can happen because we don't stop the pipeline before
-            # updating the thumbnails in _update_thumbnails.
-            return
-        thumb.set_from_pixbuf(pixbuf)
-        self.thumb_cache[position] = pixbuf
-        self.queue_draw()
+    def _set_pixbuf(self, pixbuf, position):
+        """Updates the managed UI when a new pixbuf becomes available.
 
-    def zoomChanged(self):
-        self._update_thumbnails()
+        Args:
+            pixbuf (GdkPixbuf.Pixbuf): The pixbuf produced by self.pipeline.
+            position (int): The position for which the thumb has been created,
+                in nanoseconds.
+        """
 
     def __bus_message_cb(self, unused_bus, message):
         if message.src == self.pipeline and \
@@ -800,7 +753,9 @@ class VideoPreviewer(Previewer, Zoomable, Loggable):
             struct_name = struct.get_name()
             if struct_name == "preroll-pixbuf":
                 pixbuf = struct.get_value("pixbuf")
-                self._set_pixbuf(pixbuf)
+                self.thumb_cache[self.position] = pixbuf
+                self._set_pixbuf(pixbuf, self.position)
+                self.position = -1
         elif message.src == self.pipeline and \
                 message.type == Gst.MessageType.ASYNC_DONE:
             if self.position >= 0:
@@ -820,38 +775,12 @@ class VideoPreviewer(Previewer, Zoomable, Loggable):
             return True
         return False
 
-    def _height_changed_cb(self, unused_widget, unused_param_spec):
-        self._update_thumbnails()
-
-    def _inpoint_changed_cb(self, unused_ges_timeline_element, unused_param_spec):
-        """Handles the changing of the in-point of the clip."""
-        self._update_thumbnails()
-
-    def _duration_changed_cb(self, unused_ges_timeline_element, unused_param_spec):
-        """Handles the changing of the duration of the clip."""
-        self._update_thumbnails()
-
-    def set_selected(self, selected):
-        if selected:
-            opacity = 0.5
-        else:
-            opacity = 1.0
-
-        for thumb in self.get_children():
-            thumb.props.opacity = opacity
-
     def start_generation(self):
         self.debug("Waiting for UI to become idle for: %s",
                    path_from_uri(self.uri))
         self.__start_id = GLib.idle_add(self._start_thumbnailing_cb,
                                         priority=GLib.PRIORITY_LOW)
 
-    def _ensure_proxy_thumbnails_cache(self):
-        """Ensures that both the target asset and the proxy assets have caches."""
-        uri = quote_uri(self.ges_elem.props.uri)
-        if self.uri != uri:
-            self.thumb_cache.copy(uri)
-
     def stop_generation(self):
         if self.__start_id:
             # Cancel the starting.
@@ -863,10 +792,10 @@ class VideoPreviewer(Previewer, Zoomable, Loggable):
             GLib.source_remove(self.__preroll_timeout_id)
             self.__preroll_timeout_id = None
 
-        if self._thumb_cb_id:
+        if self.__thumb_cb_id:
             # Cancel the thumbnailing.
-            GLib.source_remove(self._thumb_cb_id)
-            self._thumb_cb_id = None
+            GLib.source_remove(self.__thumb_cb_id)
+            self.__thumb_cb_id = 0
 
         if self.pipeline:
             self.pipeline.get_bus().remove_signal_watch()
@@ -874,14 +803,121 @@ class VideoPreviewer(Previewer, Zoomable, Loggable):
             self.pipeline.get_state(Gst.CLOCK_TIME_NONE)
             self.pipeline = None
 
-        self._ensure_proxy_thumbnails_cache()
         self.emit("done")
 
+    def pause_generation(self):
+        if self.pipeline:
+            self.pipeline.set_state(Gst.State.READY)
+
+
+# pylint: disable=too-many-ancestors
+class VideoPreviewer(Gtk.Layout, AssetPreviewer, Zoomable):
+    """A video previewer widget, drawing thumbnails.
+
+    Attributes:
+        ges_elem (GES.VideoSource): The previewed element.
+        thumbs (dict): Maps (quantized) times to the managed Thumbnail widgets.
+    """
+
+    # We could define them in Previewer, but for some reason they are ignored.
+    __gsignals__ = PREVIEW_GENERATOR_SIGNALS
+
+    def __init__(self, ges_elem, max_cpu_usage):
+        Gtk.Layout.__init__(self)
+        Zoomable.__init__(self)
+        AssetPreviewer.__init__(self, get_proxy_target(ges_elem), max_cpu_usage)
+
+        self.ges_elem = ges_elem
+        self.thumbs = {}
+
+        self._ensure_proxy_thumbnails_cache()
+
+        # Connect signals and fire things up
+        self.ges_elem.connect("notify::in-point", self._inpoint_changed_cb)
+        self.ges_elem.connect("notify::duration", self._duration_changed_cb)
+
+        self.connect("notify::height-request", self._height_changed_cb)
+
+    def set_selected(self, selected):
+        if selected:
+            opacity = 0.5
+        else:
+            opacity = 1.0
+
+        for thumb in self.get_children():
+            thumb.props.opacity = opacity
+
+    def _update_thumbnails(self):
+        """Updates the thumbnail widgets for the clip at the current zoom."""
+        if not self.thumb_width:
+            # The thumb_width will be available when pipeline has been started
+            return
+
+        thumbs = {}
+        queue = []
+        interval = self.thumb_interval(self.thumb_width)
+        element_left = quantize(self.ges_elem.props.in_point, interval)
+        element_right = self.ges_elem.props.in_point + self.ges_elem.props.duration
+        y = (self.props.height_request - self.thumb_height) / 2
+        for position in range(element_left, element_right, interval):
+            x = Zoomable.nsToPixel(position) - self.nsToPixel(self.ges_elem.props.in_point)
+            try:
+                thumb = self.thumbs.pop(position)
+                self.move(thumb, x, y)
+            except KeyError:
+                thumb = Thumbnail(self.thumb_width, self.thumb_height)
+                self.put(thumb, x, y)
+
+            thumbs[position] = thumb
+            if position in self.thumb_cache:
+                pixbuf = self.thumb_cache[position]
+                thumb.set_from_pixbuf(pixbuf)
+                thumb.set_visible(True)
+            else:
+                if position not in self.failures and position != self.position:
+                    queue.append(position)
+        for thumb in self.thumbs.values():
+            self.remove(thumb)
+        self.thumbs = thumbs
+        self.queue = queue
+        if queue:
+            self.become_controlled()
+
+    def _ensure_proxy_thumbnails_cache(self):
+        """Ensures that both the target asset and the proxy assets have caches."""
+        uri = quote_uri(self.ges_elem.props.uri)
+        if self.uri != uri:
+            self.thumb_cache.copy(uri)
+
+    def _set_pixbuf(self, pixbuf, position):
+        """Sets the pixbuf for the thumbnail at the expected position."""
+        try:
+            thumb = self.thumbs[position]
+        except KeyError:
+            # Can happen because we don't stop the pipeline before
+            # updating the thumbnails in _update_thumbnails.
+            return
+        thumb.set_from_pixbuf(pixbuf)
+
     def release(self):
         """Stops preview generation and cleans the object."""
         self.stop_generation()
         Zoomable.__del__(self)
 
+    def _height_changed_cb(self, unused_widget, unused_param_spec):
+        self._update_thumbnails()
+
+    def _inpoint_changed_cb(self, unused_ges_timeline_element, unused_param_spec):
+        """Handles the changing of the in-point of the clip."""
+        self._update_thumbnails()
+
+    def _duration_changed_cb(self, unused_ges_timeline_element, unused_param_spec):
+        """Handles the changing of the duration of the clip."""
+        self._update_thumbnails()
+
+    def zoomChanged(self):
+        self._update_thumbnails()
+
 
 class Thumbnail(Gtk.Image):
     """Simple widget representing a Thumbnail."""
@@ -1053,12 +1089,13 @@ def get_wavefile_location_for_uri(uri):
     return os.path.join(cache_dir, filename)
 
 
-class AudioPreviewer(Previewer, Zoomable, Loggable):
+class AudioPreviewer(Gtk.Layout, Previewer, Zoomable, Loggable):
     """Audio previewer using the results from the "level" GStreamer element."""
 
     __gsignals__ = PREVIEW_GENERATOR_SIGNALS
 
     def __init__(self, ges_elem, max_cpu_usage):
+        Gtk.Layout.__init__(self)
         Previewer.__init__(self, GES.TrackType.AUDIO, max_cpu_usage)
         Zoomable.__init__(self)
         Loggable.__init__(self)
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index 477ccd14..1f37fe07 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -446,26 +446,24 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
 
     def setProject(self, project):
         """Connects to the GES.Timeline holding the project."""
-        # Avoid starting/closing preview generation like crazy while tearing down project
-        with Previewer.manager.paused(interrupt=True):
-            if self.ges_timeline is not None:
-                self.disconnect_by_func(self._button_press_event_cb)
-                self.disconnect_by_func(self._button_release_event_cb)
-                self.disconnect_by_func(self._motion_notify_event_cb)
-
-                self.ges_timeline.disconnect_by_func(self._durationChangedCb)
-                self.ges_timeline.disconnect_by_func(self._layer_added_cb)
-                self.ges_timeline.disconnect_by_func(self._layer_removed_cb)
-                self.ges_timeline.disconnect_by_func(self.__snapping_started_cb)
-                self.ges_timeline.disconnect_by_func(self.__snapping_ended_cb)
-                for ges_layer in self.ges_timeline.get_layers():
-                    self._remove_layer(ges_layer)
-
-                self.ges_timeline.ui = None
-                self.ges_timeline = None
-
-            if self._project:
-                self._project.pipeline.disconnect_by_func(self._positionCb)
+        if self.ges_timeline is not None:
+            self.disconnect_by_func(self._button_press_event_cb)
+            self.disconnect_by_func(self._button_release_event_cb)
+            self.disconnect_by_func(self._motion_notify_event_cb)
+
+            self.ges_timeline.disconnect_by_func(self._durationChangedCb)
+            self.ges_timeline.disconnect_by_func(self._layer_added_cb)
+            self.ges_timeline.disconnect_by_func(self._layer_removed_cb)
+            self.ges_timeline.disconnect_by_func(self.__snapping_started_cb)
+            self.ges_timeline.disconnect_by_func(self.__snapping_ended_cb)
+            for ges_layer in self.ges_timeline.get_layers():
+                self._remove_layer(ges_layer)
+
+            self.ges_timeline.ui = None
+            self.ges_timeline = None
+
+        if self._project:
+            self._project.pipeline.disconnect_by_func(self._positionCb)
 
         self._project = project
         if self._project:


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