diff options
author | Thibault Saunier <tsaunier@igalia.com> | 2019-01-28 22:12:40 -0300 |
---|---|---|
committer | Thibault Saunier <tsaunier@igalia.com> | 2019-01-28 22:12:40 -0300 |
commit | e267006cfbc9e7fd58777faaa1dfd76a54cca669 (patch) | |
tree | 0a391ed8b38d327e7069d74ed2b6177ea6d6d812 | |
parent | e2bb6c13ae857bd1b3dd17b85dd9585f0293d5d3 (diff) |
timeline: Better handle loading inconsistent timelines
Auto transition when having 3 overlapping clips in a same point in the
timeline is not supported as we can't handle it in a nice way. Before we
to avoid creating 2 overlapping transitions (which is plain broken in
NLE) were completely disabling `auto-transition` and removing all
auto-transitions in the timeline but this is pretty weird for the end
user. This commit changes and now makes sure 2 transitions are not
created in the same place.
Also cleanup previous test case.
-rw-r--r-- | ges/ges-timeline.c | 34 | ||||
-rw-r--r-- | tests/check/python/common.py | 19 | ||||
-rw-r--r-- | tests/check/python/test_timeline.py | 87 |
3 files changed, 107 insertions, 33 deletions
diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index 8df5bafc..3177ceaf 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -943,8 +943,18 @@ _create_auto_transition_from_transitions (GESTimeline * timeline, _find_transition_from_auto_transitions (timeline, layer, track, prev, next, transition_duration); - if (auto_transition) + + if (auto_transition) { + if (timeline->priv->needs_rollback) { + GST_WARNING_OBJECT (timeline, + "Created an auto transition where we have 3 overlapping clips" + " removing it as this case is NOT allowed nor supported"); + g_signal_emit_by_name (auto_transition, "destroy-me"); + timeline->priv->needs_rollback = FALSE; + return NULL; + } return auto_transition; + } /* Try to find a transition that perfectly fits with the one that @@ -2327,28 +2337,6 @@ layer_auto_transition_changed_cb (GESLayer * layer, } } g_list_free_full (clips, gst_object_unref); - - if (timeline->priv->needs_rollback) { - GList *tmp, *trans; - - ges_layer_set_auto_transition (layer, FALSE); - GST_ERROR_OBJECT (layer, "Has overlapping transition, " - " we can't handle that, setting auto_transition" - " to FALSE, and removing all transitions"); - trans = g_list_copy (timeline->priv->auto_transitions); - for (tmp = trans; tmp; tmp = tmp->next) { - g_signal_emit_by_name (tmp->data, "destroy-me"); - } - - g_list_free (trans); - - trans = ges_layer_get_clips (layer); - for (tmp = trans; tmp; tmp = tmp->next) { - if (GES_IS_TRANSITION_CLIP (tmp->data)) - ges_layer_remove_clip (layer, tmp->data); - } - g_list_free_full (trans, gst_object_unref); - } } static void diff --git a/tests/check/python/common.py b/tests/check/python/common.py index 08cd3b48..a180841f 100644 --- a/tests/check/python/common.py +++ b/tests/check/python/common.py @@ -26,6 +26,8 @@ gi.require_version("GES", "1.0") from gi.repository import Gst # noqa from gi.repository import GES # noqa from gi.repository import GLib # noqa +import contextlib # noqa +import os #noqa import unittest # noqa import tempfile # noqa @@ -82,6 +84,23 @@ def create_project(with_group=False, saved=False): return timeline +@contextlib.contextmanager +def created_project_file(xges): + _, xges_path = tempfile.mkstemp(suffix=".xges") + with open(xges_path, "w") as f: + f.write(xges) + + yield Gst.filename_to_uri(os.path.abspath(xges_path)) + + os.remove(xges_path) + + +def get_asset_uri(name): + python_tests_dir = os.path.dirname(os.path.abspath(__file__)) + assets_dir = os.path.join(python_tests_dir, "..", "assets") + return Gst.filename_to_uri(os.path.join(assets_dir, name)) + + class GESTest(unittest.TestCase): def _log(self, func, format, *args): diff --git a/tests/check/python/test_timeline.py b/tests/check/python/test_timeline.py index 72a81b06..a79c1bed 100644 --- a/tests/check/python/test_timeline.py +++ b/tests/check/python/test_timeline.py @@ -29,9 +29,7 @@ from gi.repository import GES # noqa import unittest # noqa from unittest import mock -from .common import create_main_loop -from .common import create_project -from .common import GESSimpleTimelineTest # noqa +from . import common # noqa Gst.init(None) GES.init() @@ -40,8 +38,8 @@ GES.init() class TestTimeline(unittest.TestCase): def test_signals_not_emitted_when_loading(self): - mainloop = create_main_loop() - timeline = create_project(with_group=True, saved=True) + mainloop = common.create_main_loop() + timeline = common.create_project(with_group=True, saved=True) # Reload the project, check the group. project = GES.Project.new(uri=timeline.get_asset().props.uri) @@ -66,7 +64,7 @@ class TestTimeline(unittest.TestCase): handle.assert_not_called() -class TestSplitting(GESSimpleTimelineTest): +class TestSplitting(common.GESSimpleTimelineTest): def setUp(self): self.track_types = [GES.TrackType.AUDIO] super(TestSplitting, self).setUp() @@ -118,7 +116,7 @@ class TestSplitting(GESSimpleTimelineTest): ]) -class TestEditing(GESSimpleTimelineTest): +class TestEditing(common.GESSimpleTimelineTest): def test_transition_disappears_when_moving_to_another_layer(self): self.timeline.props.auto_transition = True @@ -176,7 +174,7 @@ class TestEditing(GESSimpleTimelineTest): self.assertEquals(len(self.layer.get_clips()), 4) -class TestSnapping(GESSimpleTimelineTest): +class TestSnapping(common.GESSimpleTimelineTest): def test_snapping(self): self.timeline.props.auto_transition = True @@ -216,7 +214,7 @@ class TestSnapping(GESSimpleTimelineTest): self.assertEqual(clip1.props.duration, split_position) self.assertEqual(clip2.props.start, split_position) -class TestTransitions(GESSimpleTimelineTest): +class TestTransitions(common.GESSimpleTimelineTest): def test_emission_order_for_transition_clip_added_signal(self): self.timeline.props.auto_transition = True @@ -245,6 +243,75 @@ class TestTransitions(GESSimpleTimelineTest): self.assertEqual( signals, ["notify::start", "clip-added", "clip-added"]) + def create_xges(self): + uri = common.get_asset_uri("png.png") + return """<ges version='0.4'> + <project properties='properties;' metadatas='metadatas, author=(string)"", render-scale=(double)100, format-version=(string)0.4;'> + <ressources> + <asset id='GESTitleClip' extractable-type-name='GESTitleClip' properties='properties;' metadatas='metadatas;' /> + <asset id='bar-wipe-lr' extractable-type-name='GESTransitionClip' properties='properties;' metadatas='metadatas, description=(string)GES_VIDEO_STANDARD_TRANSITION_TYPE_BAR_WIPE_LR;' /> + <asset id='%(uri)s' extractable-type-name='GESUriClip' properties='properties, supported-formats=(int)4, duration=(guint64)18446744073709551615;' metadatas='metadatas, video-codec=(string)PNG, file-size=(guint64)73294;' /> + <asset id='crossfade' extractable-type-name='GESTransitionClip' properties='properties;' metadatas='metadatas, description=(string)GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE;' /> + </ressources> + <timeline properties='properties, auto-transition=(boolean)true, snapping-distance=(guint64)31710871;' metadatas='metadatas, duration=(guint64)13929667032;'> + <track caps='video/x-raw(ANY)' track-type='4' track-id='0' properties='properties, async-handling=(boolean)false, message-forward=(boolean)true, caps=(string)"video/x-raw\(ANY\)", restriction-caps=(string)"video/x-raw\,\ width\=\(int\)1920\,\ height\=\(int\)1080\,\ framerate\=\(fraction\)30/1", mixing=(boolean)true;' metadatas='metadatas;'/> + <track caps='audio/x-raw(ANY)' track-type='2' track-id='1' properties='properties, async-handling=(boolean)false, message-forward=(boolean)true, caps=(string)"audio/x-raw\(ANY\)", restriction-caps=(string)"audio/x-raw\,\ format\=\(string\)S32LE\,\ channels\=\(int\)2\,\ rate\=\(int\)44100\,\ layout\=\(string\)interleaved", mixing=(boolean)true;' metadatas='metadatas;'/> + <layer priority='0' properties='properties, auto-transition=(boolean)true;' metadatas='metadatas, volume=(float)1;'> + <clip id='0' asset-id='%(uri)s' type-name='GESUriClip' layer-priority='0' track-types='6' start='0' duration='4558919302' inpoint='0' rate='0' properties='properties, name=(string)uriclip25263, mute=(boolean)false, is-image=(boolean)false;' /> + <clip id='1' asset-id='bar-wipe-lr' type-name='GESTransitionClip' layer-priority='0' track-types='4' start='3225722559' duration='1333196743' inpoint='0' rate='0' properties='properties, name=(string)transitionclip84;' children-properties='properties, GESVideoTransition::border=(uint)0, GESVideoTransition::invert=(boolean)false;'/> + <clip id='2' asset-id='%(uri)s' type-name='GESUriClip' layer-priority='0' track-types='6' start='3225722559' duration='3479110239' inpoint='4558919302' rate='0' properties='properties, name=(string)uriclip25265, mute=(boolean)false, is-image=(boolean)false;' /> + </layer> + <layer priority='1' properties='properties, auto-transition=(boolean)true;' metadatas='metadatas, volume=(float)1;'> + <clip id='3' asset-id='%(uri)s' type-name='GESUriClip' layer-priority='1' track-types='4' start='8566459322' duration='1684610449' inpoint='0' rate='0' properties='properties, name=(string)uriclip25266, mute=(boolean)false, is-image=(boolean)true;' /> + <clip id='4' asset-id='GESTitleClip' type-name='GESTitleClip' layer-priority='1' track-types='6' start='8566459322' duration='4500940746' inpoint='0' rate='0' properties='properties, name=(string)titleclip69;' /> + <clip id='5' asset-id='%(uri)s' type-name='GESUriClip' layer-priority='1' track-types='4' start='9566459322' duration='4363207710' inpoint='0' rate='0' properties='properties, name=(string)uriclip25275, mute=(boolean)false, is-image=(boolean)true;' /> + </layer> + <groups> + </groups> + </timeline> +</project> +</ges>""" % {"uri": uri} + + def test_auto_transition(self): + xges = self.create_xges() + with common.created_project_file(xges) as proj_uri: + project = GES.Project.new(proj_uri) + timeline = project.extract() + + mainloop = common.create_main_loop() + mainloop.run(until_empty=True) + + layers = timeline.get_layers() + self.assertEqual(len(layers), 2) + + self.assertTrue(layers[0].props.auto_transition) + self.assertTrue(layers[1].props.auto_transition) + + def test_transition_type(self): + xges = self.create_xges() + with common.created_project_file(xges) as proj_uri: + project = GES.Project.new(proj_uri) + timeline = project.extract() + + mainloop = common.create_main_loop() + mainloop.run(until_empty=True) + + layers = timeline.get_layers() + self.assertEqual(len(layers), 2) + + clips = layers[0].get_clips() + clip1 = clips[0] + clip2 = clips[-1] + # There should be a transition because clip1 intersects clip2 + self.assertLess(clip1.props.start, clip2.props.start) + self.assertLess(clip2.props.start, clip1.props.start + clip1.props.duration) + self.assertLess(clip1.props.start + clip1.props.duration, clip2.props.start + clip2.props.duration) + self.assertEqual(len(clips), 3) + + # Even though 3 clips overlap 1 transition will be created + clips = layers[1].get_clips() + self.assertEqual(len(clips), 4) + class TestPriorities(GESSimpleTimelineTest): @@ -257,4 +324,4 @@ class TestPriorities(GESSimpleTimelineTest): clip.props.start = 101 self.timeline.commit() - self.assertGreater(clip.props.priority, clip1.props.priority)
\ No newline at end of file + self.assertGreater(clip.props.priority, clip1.props.priority) |