[pitivi] Handle proxy duration not matching its original file duration



commit 13181372c12d0fe0c15898563e3e5eb7a9db81a3
Author: Thibault Saunier <tsaunier igalia com>
Date:   Tue Mar 3 15:27:07 2020 -0300

    Handle proxy duration not matching its original file duration
    
    Fixes https://gitlab.gnome.org/GNOME/pitivi/issues/2324

 pitivi/check.py             | 15 +++++++-------
 pitivi/timeline/timeline.py | 48 ++++++++++++++++++++++++---------------------
 pitivi/utils/misc.py        | 16 +++++++++++++++
 pitivi/utils/proxy.py       | 46 +++++++++++++++++++++++++++++++++----------
 tests/test_medialibrary.py  | 26 ++++++++++++++++++++++++
 5 files changed, 112 insertions(+), 39 deletions(-)
---
diff --git a/pitivi/check.py b/pitivi/check.py
index 253cb8b9..41daec7d 100644
--- a/pitivi/check.py
+++ b/pitivi/check.py
@@ -376,13 +376,6 @@ def initialize_modules():
 
     require_version("GstPbutils", GST_API_VERSION)
     from gi.repository import GstPbutils
-    from pitivi.utils.misc import video_info_get_natural_height, video_info_get_natural_width, 
video_info_get_rotation
-
-    # Monkey patch a helper method for retrieving the size of a video
-    # when using square pixels.
-    GstPbutils.DiscovererVideoInfo.get_natural_width = video_info_get_natural_width
-    GstPbutils.DiscovererVideoInfo.get_natural_height = video_info_get_natural_height
-    GstPbutils.DiscovererVideoInfo.get_rotation = video_info_get_rotation
 
     if not os.environ.get("GES_DISCOVERY_TIMEOUT"):
         os.environ["GES_DISCOVERY_TIMEOUT"] = "5"
@@ -394,6 +387,14 @@ def initialize_modules():
     # Monkey patch deprecated methods to use the new variant by default
     GES.TrackElement.list_children_properties = GES.TimelineElement.list_children_properties
 
+    from pitivi.utils.misc import video_info_get_natural_height, video_info_get_natural_width, 
video_info_get_rotation
+
+    # Monkey patch a helper method for retrieving the size of a video
+    # when using square pixels.
+    GstPbutils.DiscovererVideoInfo.get_natural_width = video_info_get_natural_width
+    GstPbutils.DiscovererVideoInfo.get_natural_height = video_info_get_natural_height
+    GstPbutils.DiscovererVideoInfo.get_rotation = video_info_get_rotation
+
     from pitivi.utils import validate
     if validate.init() and "--inspect-action-type" in sys.argv:
         try:
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index f561dca9..fc5455e3 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -42,6 +42,7 @@ from pitivi.timeline.previewers import Previewer
 from pitivi.timeline.ruler import ScaleRuler
 from pitivi.undo.timeline import CommitTimelineFinalizingAction
 from pitivi.utils.loggable import Loggable
+from pitivi.utils.misc import asset_get_duration
 from pitivi.utils.proxy import get_proxy_target
 from pitivi.utils.timeline import EditingContext
 from pitivi.utils.timeline import SELECT
@@ -915,6 +916,27 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         for ges_layer in self.ges_timeline.get_layers():
             ges_layer.ui.update_position()
 
+    def add_clip_to_layer(self, ges_layer, asset, start):
+        if asset.is_image():
+            clip_duration = self.app.settings.imageClipLength * \
+                Gst.SECOND / 1000.0
+            max_duration = 0
+        else:
+            clip_duration = asset_get_duration(asset)
+            max_duration = clip_duration
+
+        ges_clip = ges_layer.add_asset(asset, start, 0, clip_duration,
+                                        asset.get_supported_formats())
+        if not ges_clip:
+            return ges_clip
+
+        # Tell GES that the max duration is our newly compute max duration
+        # so it has the proper information when doing timeline editing.
+        if max_duration and ges_clip.props.max_duration > max_duration:
+            ges_clip.props.max_duration = max_duration
+        return ges_clip
+
+
     def __create_clips(self, x, y):
         """Creates the clips for an asset drag operation.
 
@@ -932,11 +954,6 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
 
         ges_clips = []
         for asset in assets:
-            if asset.is_image():
-                clip_duration = self.app.settings.imageClipLength * \
-                    Gst.SECOND / 1000.0
-            else:
-                clip_duration = asset.get_duration()
 
             ges_layer, unused_on_sep = self.get_layer_at(y)
             if not placement:
@@ -945,15 +962,11 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
 
             self.debug("Creating %s at %s", asset.props.id, Gst.TIME_ARGS(placement))
 
-            ges_clip = ges_layer.add_asset(asset,
-                                           placement,
-                                           0,
-                                           clip_duration,
-                                           asset.get_supported_formats())
+            ges_clip = self.add_clip_to_layer(ges_layer, asset, placement)
             if not ges_clip:
                 return False
 
-            placement += clip_duration
+            placement += ges_clip.props.duration
             ges_clip.first_placement = True
             self._project.pipeline.commit_timeline()
 
@@ -1486,17 +1499,8 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
                     layer.add_clip(obj)
                     duration = obj.get_duration()
                 elif isinstance(obj, GES.Asset):
-                    if obj.is_image():
-                        duration = self.app.settings.imageClipLength * \
-                            Gst.SECOND / 1000.0
-                    else:
-                        duration = obj.get_duration()
-
-                    layer.add_asset(obj,
-                                    start=clip_position,
-                                    inpoint=0,
-                                    duration=duration,
-                                    track_types=obj.get_supported_formats())
+                    ges_clip = self.timeline.add_clip_to_layer(layer, obj, clip_position)
+                    duration = ges_clip.props.duration
                 else:
                     raise TimelineError("Cannot insert: %s" % type(obj))
                 clip_position += duration
diff --git a/pitivi/utils/misc.py b/pitivi/utils/misc.py
index ba844e76..84e29a50 100644
--- a/pitivi/utils/misc.py
+++ b/pitivi/utils/misc.py
@@ -26,6 +26,7 @@ from urllib.parse import urlsplit
 
 from gi.repository import GdkPixbuf
 from gi.repository import GLib
+from gi.repository import GES
 from gi.repository import Gst
 from gi.repository import Gtk
 
@@ -35,6 +36,9 @@ from pitivi.configure import APPMANUALURL_ONLINE
 from pitivi.utils.threads import Thread
 
 
+ASSET_DURATION_META = "pitivi:asset-duration"
+
+
 def scale_pixbuf(pixbuf, width, height):
     """Scales the given pixbuf preserving the original aspect ratio."""
     pixbuf_width = pixbuf.props.width
@@ -455,3 +459,15 @@ def video_info_get_natural_height(video_info):
         return _get_square_width(video_info)
 
     return video_info.get_height()
+
+def asset_get_duration(asset):
+    assert isinstance(asset, GES.UriClipAsset)
+
+    # Use pitivi information before the information provided by GStreamer.
+    # We can't handle that kind of information in GES itself as it is
+    # related to Pitivi' own way of handling proxies.
+    res, duration = asset.get_uint64(ASSET_DURATION_META)
+    if res:
+        return duration
+
+    return asset.get_duration()
\ No newline at end of file
diff --git a/pitivi/utils/proxy.py b/pitivi/utils/proxy.py
index d80f9cf8..54dde5fd 100644
--- a/pitivi/utils/proxy.py
+++ b/pitivi/utils/proxy.py
@@ -29,6 +29,8 @@ from gi.repository import GstTranscoder
 
 from pitivi.configure import get_gstpresets_dir
 from pitivi.dialogs.prefs import PreferencesDialog
+from pitivi.utils.misc import ASSET_DURATION_META
+from pitivi.utils.misc import asset_get_duration
 from pitivi.settings import GlobalSettings
 from pitivi.utils.loggable import Loggable
 
@@ -319,7 +321,10 @@ class ProxyManager(GObject.Object, Loggable):
         if cls.is_scaled_proxy(uri):
             return ".".join(uri.split(".")[:-4])
 
-        return ".".join(uri.split(".")[:-3])
+        if cls.is_proxy_asset(uri):
+            return ".".join(uri.split(".")[:-3])
+
+        return uri
 
     def get_proxy_uri(self, asset, scaled=False):
         """Gets the URI of the corresponding proxy file for the specified asset.
@@ -457,15 +462,36 @@ class ProxyManager(GObject.Object, Loggable):
 
             del transcoder
 
-        if asset.get_info().get_duration() != proxy.get_info().get_duration():
-            self.error(
-                "Asset %s (duration=%s) and created proxy %s (duration=%s) do not"
-                " have the same duration this should *never* happen, please file"
-                " a bug with the media files." % (
-                    asset.get_id(), Gst.TIME_ARGS(asset.get_info().get_duration()),
-                    proxy.get_id(), Gst.TIME_ARGS(proxy.get_info().get_duration())
-                )
-            )
+        asset_duration = asset_get_duration(asset)
+        proxy_duration = asset_get_duration(proxy)
+        if asset_duration != proxy_duration:
+            duration = min(asset_duration, proxy_duration)
+
+            self.info("Reseting %s duration from %s to %s as"
+                " new proxy has a different duration",
+                asset.props.id, Gst.TIME_ARGS(asset_duration), Gst.TIME_ARGS(duration))
+            asset.set_uint64(ASSET_DURATION_META, duration)
+            proxy.set_uint64(ASSET_DURATION_META, duration)
+            target_uri = self.get_target_uri(asset)
+
+            for clip in self.app.project_manager.current_project.ges_timeline.iter_clips():
+                if self.get_target_uri(clip.props.uri) == target_uri:
+                    if clip.props.in_point + clip.props.duration > duration:
+                        new_duration = duration - clip.props.in_point
+                        if new_duration > 0:
+                            self.warning("%s reseting duration to %s as"
+                                " new proxy has a shorter duration",
+                                clip, Gst.TIME_ARGS(new_duration))
+                            clip.set_duration(new_duration)
+                        else:
+                            new_inpoint = new_duration - clip.props.in_point
+                            self.error("%s reseting duration to %s"
+                                " and inpoint to %s as the proxy"
+                                " is shorter",
+                                clip, Gst.TIME_ARGS(new_duration), Gst.TIME_ARGS(new_inpoint))
+                            clip.set_inpoint(new_inpoint)
+                            clip.set_duration(duration - new_inpoint)
+                        clip.set_max_duration(duration)
 
         if shadow:
             self.app.project_manager.current_project.finalize_proxy(proxy)
diff --git a/tests/test_medialibrary.py b/tests/test_medialibrary.py
index a05a38e0..b2a505d5 100644
--- a/tests/test_medialibrary.py
+++ b/tests/test_medialibrary.py
@@ -22,6 +22,7 @@ from unittest import mock
 
 from gi.repository import Gdk
 from gi.repository import GES
+from gi.repository import GObject
 from gi.repository import Gst
 
 from pitivi import medialibrary
@@ -29,6 +30,8 @@ from pitivi.project import ProjectManager
 from pitivi.utils.proxy import ProxyingStrategy
 from pitivi.utils.validate import create_event
 from tests import common
+from pitivi.utils.misc import asset_get_duration
+from pitivi.utils.misc import ASSET_DURATION_META
 
 
 class BaseTestMediaLibrary(common.TestCase):
@@ -359,6 +362,29 @@ class TestMediaLibrary(BaseTestMediaLibrary):
             proxy = self.check_add_proxy(asset, check_progress=False)
             self.check_disable_proxy(proxy, asset, delete=True)
 
+    def test_proxy_duration_mismatch(self):
+        sample_name = "30fps_numeroted_frames_red.mkv"
+        with common.cloned_sample(sample_name):
+            self.check_import([sample_name], proxying_strategy=ProxyingStrategy.NOTHING)
+            timeline = self.app.project_manager.current_project.ges_timeline
+
+            asset = self.medialibrary.storemodel[0][medialibrary.COL_ASSET]
+            clip = timeline.append_layer().add_asset(asset, 0, 0, Gst.CLOCK_TIME_NONE, GES.TrackType.VIDEO)
+
+            duration = 2.5 * Gst.SECOND
+            fake_duration = 3 * Gst.SECOND
+
+            asset.set_uint64(ASSET_DURATION_META, fake_duration)
+            clip.props.max_duration = fake_duration
+            clip.props.duration = fake_duration
+            self.assertEqual(clip.props.duration, fake_duration)
+            proxy = self.check_add_proxy(asset)
+
+            self.assertEqual(asset_get_duration(asset), duration)
+            self.assertEqual(asset_get_duration(proxy), duration)
+            self.assertEqual(clip.props.duration, duration)
+            self.assertEqual(clip.props.max_duration, duration)
+
     def test_regenerate_scaled_proxy(self):
         sample_name = "30fps_numeroted_frames_red.mkv"
         with common.cloned_sample(sample_name):


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