[pitivi] previewer: Allow generating thumbnails for assets
- From: Alexandru Băluț <alexbalut src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] previewer: Allow generating thumbnails for assets
- Date: Sat, 27 Jul 2019 09:15:09 +0000 (UTC)
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]