[pitivi] previewers: Split VideoPreviewer logic into ImagePreviewer
- From: Alexandru Băluț <alexbalut src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] previewers: Split VideoPreviewer logic into ImagePreviewer
- Date: Sun, 14 Jul 2019 21:15:13 +0000 (UTC)
commit abcdcc802f7e2ce52fbe367fa837a27ee837261d
Author: Swayamjeet <swayam1998 gmail com>
Date: Thu Jul 11 23:35:43 2019 +0530
previewers: Split VideoPreviewer logic into ImagePreviewer
pitivi/timeline/elements.py | 8 +-
pitivi/timeline/previewers.py | 205 +++++++++++++++++++++++++++++++-----------
tests/test_previewers.py | 16 ++--
3 files changed, 162 insertions(+), 67 deletions(-)
---
diff --git a/pitivi/timeline/elements.py b/pitivi/timeline/elements.py
index 53705972..073f63eb 100644
--- a/pitivi/timeline/elements.py
+++ b/pitivi/timeline/elements.py
@@ -33,9 +33,8 @@ from matplotlib.figure import Figure
from pitivi.configure import get_pixmap_dir
from pitivi.effects import ALLOWED_ONLY_ONCE_EFFECTS
-from pitivi.effects import AUDIO_EFFECT
-from pitivi.effects import VIDEO_EFFECT
from pitivi.timeline.previewers import AudioPreviewer
+from pitivi.timeline.previewers import ImagePreviewer
from pitivi.timeline.previewers import VideoPreviewer
from pitivi.undo.timeline import CommitTimelineFinalizingAction
from pitivi.utils import pipeline
@@ -973,7 +972,10 @@ class VideoUriSource(VideoSource):
self.get_style_context().add_class("VideoUriSource")
def _getPreviewer(self):
- previewer = VideoPreviewer(self._ges_elem, self.timeline.app.settings.previewers_max_cpu)
+ if isinstance(self._ges_elem, GES.ImageSource):
+ previewer = ImagePreviewer(self._ges_elem, self.timeline.app.settings.previewers_max_cpu)
+ else:
+ previewer = VideoPreviewer(self._ges_elem, self.timeline.app.settings.previewers_max_cpu)
previewer.get_style_context().add_class("VideoUriSource")
return previewer
diff --git a/pitivi/timeline/previewers.py b/pitivi/timeline/previewers.py
index 1d5f92f9..e85dc7bb 100644
--- a/pitivi/timeline/previewers.py
+++ b/pitivi/timeline/previewers.py
@@ -92,7 +92,6 @@ class PreviewerBin(Gst.Bin, Loggable):
def finalize(self, proxy=None):
"""Finalizes the previewer, saving data to the disk if needed."""
- pass
class ThumbnailBin(PreviewerBin):
@@ -409,11 +408,142 @@ class Previewer(Gtk.Layout):
def set_selected(self, selected):
"""Marks this instance as being selected."""
- pass
def pause_generation(self):
"""Pauses preview generation."""
- pass
+
+ @staticmethod
+ def thumb_interval(thumb_width):
+ """Gets the interval for which a thumbnail is displayed.
+
+ Returns:
+ int: a duration in nanos, multiple of THUMB_PERIOD.
+ """
+ interval = Zoomable.pixelToNs(thumb_width + THUMB_MARGIN_PX)
+ # Make sure the thumb interval is a multiple of THUMB_PERIOD.
+ quantized = quantize(interval, THUMB_PERIOD)
+ # Make sure the quantized thumb interval fits
+ # the thumb and the margin.
+ if quantized < interval:
+ quantized += THUMB_PERIOD
+ # Make sure we don't show thumbs more often than THUMB_PERIOD.
+ return max(THUMB_PERIOD, quantized)
+
+
+class ImagePreviewer(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):
+ 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.__start_id = 0
+
+ self.__image_pixbuf = None
+
+ self.thumbs = {}
+ self.thumb_height = THUMB_HEIGHT
+ self.thumb_width = 0
+
+ self.ges_elem.connect("notify::duration", self._duration_changed_cb)
+
+ self.become_controlled()
+
+ self.connect("notify::height-request", self._height_changed_cb)
+
+ def _start_thumbnailing_cb(self):
+ if not self.__start_id:
+ # Can happen if stopGeneration is called because the clip has been
+ # removed from the timeline after the PreviewGeneratorManager
+ # started this job.
+ return False
+
+ self.__start_id = None
+
+ self.debug("Generating thumbnail for image: %s", path_from_uri(self.uri))
+ self.__image_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
+ Gst.uri_get_location(self.uri), -1, self.thumb_height, True)
+ self.thumb_width = self.__image_pixbuf.props.width
+ self._update_thumbnails()
+ self.emit("done")
+
+ # Stop calling me, I started already.
+ return False
+
+ def _update_thumbnails(self):
+ """Updates the thumbnail widgets for the clip at the current zoom."""
+ if not self.thumb_width:
+ # The __image_pixbuf is not ready yet.
+ return
+
+ thumbs = {}
+ 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
+ thumb.set_from_pixbuf(self.__image_pixbuf)
+ thumb.set_visible(True)
+
+ for thumb in self.thumbs.values():
+ self.remove(thumb)
+ self.thumbs = thumbs
+
+ def zoomChanged(self):
+ self._update_thumbnails()
+
+ def _height_changed_cb(self, unused_widget, unused_param_spec):
+ 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 stop_generation(self):
+ if self.__start_id:
+ # Cancel the starting.
+ GLib.source_remove(self.__start_id)
+ self.__start_id = None
+
+ self.emit("done")
+
+ def release(self):
+ """Stops preview generation and cleans the object."""
+ self.stop_generation()
+ Zoomable.__del__(self)
class VideoPreviewer(Previewer, Zoomable, Loggable):
@@ -454,11 +584,9 @@ class VideoPreviewer(Previewer, Zoomable, Loggable):
self.thumb_height = THUMB_HEIGHT
self.thumb_width = 0
- self.__image_pixbuf = None
- if not isinstance(ges_elem, GES.ImageSource):
- self.thumb_cache = ThumbnailCache.get(self.uri)
- self._ensure_proxy_thumbnails_cache()
- self.thumb_width, unused_height = self.thumb_cache.image_size
+ 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
@@ -555,28 +683,20 @@ class VideoPreviewer(Previewer, Zoomable, Loggable):
self.__start_id = None
- if isinstance(self.ges_elem, GES.ImageSource):
- self.debug("Generating thumbnail for image: %s", path_from_uri(self.uri))
- self.__image_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
- Gst.uri_get_location(self.uri), -1, self.thumb_height, True)
- self.thumb_width = self.__image_pixbuf.props.width
- self._update_thumbnails()
- self.emit("done")
+ if not self.thumb_width:
+ self.debug("Finding thumb width")
+ self._setup_pipeline()
+ return False
+
+ # Update the thumbnails with what we already have, if anything.
+ self._update_thumbnails()
+ if self.queue:
+ self.debug("Generating thumbnails for video: %s, %s", path_from_uri(self.uri), self.queue)
+ # When the pipeline status is set to PAUSED,
+ # the first thumbnail generation will be scheduled.
+ self._setup_pipeline()
else:
- if not self.thumb_width:
- self.debug("Finding thumb width")
- self._setup_pipeline()
- return False
-
- # Update the thumbnails with what we already have, if anything.
- self._update_thumbnails()
- if self.queue:
- self.debug("Generating thumbnails for video: %s, %s", path_from_uri(self.uri), self.queue)
- # When the pipeline status is set to PAUSED,
- # the first thumbnail generation will be scheduled.
- self._setup_pipeline()
- else:
- self.emit("done")
+ self.emit("done")
# Stop calling me, I started already.
return False
@@ -605,33 +725,15 @@ class VideoPreviewer(Previewer, Zoomable, Loggable):
# and then the next thumbnail generation operation will be scheduled.
return False
- @property
- def thumb_interval(self):
- """Gets the interval for which a thumbnail is displayed.
-
- Returns:
- int: a duration in nanos, multiple of THUMB_PERIOD.
- """
- interval = Zoomable.pixelToNs(self.thumb_width + THUMB_MARGIN_PX)
- # Make sure the thumb interval is a multiple of THUMB_PERIOD.
- quantized = quantize(interval, THUMB_PERIOD)
- # Make sure the quantized thumb interval fits
- # the thumb and the margin.
- if quantized < interval:
- quantized += THUMB_PERIOD
- # Make sure we don't show thumbs more often than THUMB_PERIOD.
- return max(THUMB_PERIOD, quantized)
-
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
- # or the __image_pixbuf is ready.
return
thumbs = {}
queue = []
- interval = self.thumb_interval
+ 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
@@ -645,10 +747,7 @@ class VideoPreviewer(Previewer, Zoomable, Loggable):
self.put(thumb, x, y)
thumbs[position] = thumb
- if isinstance(self.ges_elem, GES.ImageSource):
- thumb.set_from_pixbuf(self.__image_pixbuf)
- thumb.set_visible(True)
- elif position in self.thumb_cache:
+ if position in self.thumb_cache:
pixbuf = self.thumb_cache[position]
thumb.set_from_pixbuf(pixbuf)
thumb.set_visible(True)
@@ -868,7 +967,7 @@ class ThumbnailCache(Loggable):
Returns:
List[int]: The width and height of the images in the cache.
"""
- if self._image_size[0] is 0:
+ if self._image_size[0] == 0:
self._cur.execute("SELECT * FROM Thumbs LIMIT 1")
row = self._cur.fetchone()
if row:
diff --git a/tests/test_previewers.py b/tests/test_previewers.py
index 0d3c9912..3fda9e0f 100644
--- a/tests/test_previewers.py
+++ b/tests/test_previewers.py
@@ -28,10 +28,10 @@ from gi.repository import GES
from gi.repository import Gst
from pitivi.timeline.previewers import get_wavefile_location_for_uri
+from pitivi.timeline.previewers import Previewer
from pitivi.timeline.previewers import THUMB_HEIGHT
from pitivi.timeline.previewers import THUMB_PERIOD
from pitivi.timeline.previewers import ThumbnailCache
-from pitivi.timeline.previewers import VideoPreviewer
from tests import common
from tests.test_media_library import BaseTestMediaLibrary
@@ -98,22 +98,16 @@ class TestAudioPreviewer(BaseTestMediaLibrary):
self.assertEqual(samples, SIMPSON_WAVFORM_VALUES)
-class TestVideoPreviewer(common.TestCase):
- """Tests for the `VideoPreviewer` class."""
+class TestPreviewer(common.TestCase):
+ """Tests for the `Previewer` class."""
def test_thumb_interval(self):
- """Checks the `thumb_interval` property."""
- ges_elem = mock.Mock()
- ges_elem.props.uri = common.get_sample_uri("1sec_simpsons_trailer.mp4")
- ges_elem.props.id = common.get_sample_uri("1sec_simpsons_trailer.mp4")
- previewer = VideoPreviewer(ges_elem, 94)
- previewer.thumb_width = 1 # Just so it's not None.
-
+ """Checks the `thumb_interval` method."""
def run_thumb_interval(interval):
"""Runs thumb_interval."""
with mock.patch("pitivi.utils.timeline.Zoomable.pixelToNs") as pixel_to_ns:
pixel_to_ns.return_value = interval
- return previewer.thumb_interval
+ return Previewer.thumb_interval(1)
self.assertEqual(run_thumb_interval(1), THUMB_PERIOD)
self.assertEqual(run_thumb_interval(THUMB_PERIOD - 1), THUMB_PERIOD)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]