# -*- coding: utf-8 -*- # # Copyright (c) 2016 Alexandru Băluț # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this program; if not, write to the # Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, # Boston, MA 02110-1301, USA. from . import overrides_hack import tempfile # noqa import gi gi.require_version("Gst", "1.0") 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 unittest # noqa from unittest import mock from . import common # noqa Gst.init(None) GES.init() class TestTimeline(common.GESSimpleTimelineTest): def test_signals_not_emitted_when_loading(self): 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) loaded_called = False def loaded(unused_project, unused_timeline): nonlocal loaded_called loaded_called = True mainloop.quit() project.connect("loaded", loaded) timeline = project.extract() signals = ["layer-added", "group-added", "track-added"] handle = mock.Mock() for signal in signals: timeline.connect(signal, handle) mainloop.run() self.assertTrue(loaded_called) handle.assert_not_called() def test_deeply_nested_serialization(self): deep_timeline = common.create_project(with_group=True, saved="deep") deep_project = deep_timeline.get_asset() deep_asset = GES.UriClipAsset.request_sync(deep_project.props.id) nested_timeline = common.create_project(with_group=False, saved=False) nested_project = nested_timeline.get_asset() nested_project.add_asset(deep_project) nested_timeline.append_layer().add_asset(deep_asset, 0, 0, 5 * Gst.SECOND, GES.TrackType.UNKNOWN) uri = "file://%s" % tempfile.NamedTemporaryFile(suffix="-nested.xges").name nested_timeline.get_asset().save(nested_timeline, uri, None, overwrite=True) asset = GES.UriClipAsset.request_sync(nested_project.props.id) project = self.timeline.get_asset() project.add_asset(nested_project) refclip = self.layer.add_asset(asset, 0, 0, 5 * Gst.SECOND, GES.TrackType.VIDEO) uri = "file://%s" % tempfile.NamedTemporaryFile(suffix=".xges").name project.save(self.timeline, uri, None, overwrite=True) self.assertEqual(len(project.list_assets(GES.Extractable)), 2) mainloop = common.create_main_loop() def loaded_cb(unused_project, unused_timeline): mainloop.quit() project.connect("loaded", loaded_cb) # Extract again the timeline and compare with previous one. timeline = project.extract() mainloop.run() layer, = timeline.get_layers() clip, = layer.get_clips() self.assertEqual(clip.props.uri, refclip.props.uri) self.assertEqual(timeline.props.duration, self.timeline.props.duration) self.assertEqual(timeline.get_asset(), project) self.assertEqual(len(project.list_assets(GES.Extractable)), 2) def test_iter_timeline(self): all_clips = set() for l in range(5): self.timeline.append_layer() for _ in range(5): all_clips.add(self.append_clip(l)) self.assertEqual(set(self.timeline.iter_clips()), all_clips) def test_nested_serialization(self): nested_timeline = common.create_project(with_group=True, saved=True) nested_project = nested_timeline.get_asset() layer = nested_timeline.append_layer() asset = GES.UriClipAsset.request_sync(nested_project.props.id) refclip = self.layer.add_asset(asset, 0, 0, 110 * Gst.SECOND, GES.TrackType.UNKNOWN) nested_project.save(nested_timeline, nested_project.props.id, None, True) project = self.timeline.get_asset() project.add_asset(nested_project) uri = "file://%s" % tempfile.NamedTemporaryFile(suffix=".xges").name self.assertEqual(len(project.list_assets(GES.Extractable)), 2) project.save(self.timeline, uri, None, overwrite=True) self.assertEqual(len(project.list_assets(GES.Extractable)), 2) mainloop = common.create_main_loop() def loaded(unused_project, unused_timeline): mainloop.quit() project.connect("loaded", loaded) # Extract again the timeline and compare with previous one. timeline = project.extract() mainloop.run() layer, = timeline.get_layers() clip, = layer.get_clips() self.assertEqual(clip.props.uri, refclip.props.uri) self.assertEqual(timeline.props.duration, self.timeline.props.duration) self.assertEqual(timeline.get_asset(), project) self.assertEqual(len(project.list_assets(GES.Extractable)), 2) def test_timeline_duration(self): self.append_clip() self.append_clip() clips = self.layer.get_clips() self.assertEqual(self.timeline.props.duration, 20) self.layer.remove_clip(clips[1]) self.assertEqual(self.timeline.props.duration, 10) self.append_clip() self.append_clip() clips = self.layer.get_clips() self.assertEqual(self.timeline.props.duration, 30) group = GES.Container.group(clips[1:]) self.assertEqual(self.timeline.props.duration, 30) group1 = GES.Container.group([]) group1.add(group) self.assertEqual(self.timeline.props.duration, 30) def test_spliting_with_auto_transition_on_the_left(self): self.track_types = [GES.TrackType.AUDIO] super().setUp() self.timeline.props.auto_transition = True clip1 = self.add_clip(0, 0, 100) clip2 = self.add_clip(50, 0, 100) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 100), (GES.TransitionClip, 50, 50), (GES.TestClip, 50, 100) ] ]) clip1.split(25) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 25), (GES.TestClip, 25, 75), (GES.TransitionClip, 50, 50), (GES.TestClip, 50, 100), ] ]) clip2.split(125) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 25), (GES.TestClip, 25, 75), (GES.TransitionClip, 50, 50), (GES.TestClip, 50, 75), (GES.TestClip, 125, 25), ] ]) @unittest.skipUnless(*common.can_generate_assets()) def test_auto_transition_type_after_setting_proxy_asset(self): self.track_types = [GES.TrackType.VIDEO] super().setUp() self.timeline.props.auto_transition = True with common.created_video_asset() as uri: self.append_clip(asset_type=GES.UriClip, asset_id=uri) self.append_clip(asset_type=GES.UriClip, asset_id=uri).props.start = 5 clip1, transition, clip2 = self.layer.get_clips() video_transition, = transition.get_children(True) video_transition.set_transition_type(GES.VideoStandardTransitionType.BAR_WIPE_LR) self.assertEqual(video_transition.get_transition_type(), GES.VideoStandardTransitionType.BAR_WIPE_LR) with common.created_video_asset() as uri2: proxy_asset = GES.UriClipAsset.request_sync(uri2) clip1.set_asset(proxy_asset) clip1, transition1, clip2 = self.layer.get_clips() video_transition1, = transition1.get_children(True) self.assertEqual(video_transition, video_transition1) self.assertEqual(video_transition.get_transition_type(), GES.VideoStandardTransitionType.BAR_WIPE_LR) def test_frame_info(self): self.track_types = [GES.TrackType.VIDEO] super().setUp() vtrack, = self.timeline.get_tracks() vtrack.update_restriction_caps(Gst.Caps("video/x-raw,framerate=60/1")) self.assertEqual(self.timeline.get_frame_time(60), Gst.SECOND) layer = self.timeline.append_layer() asset = GES.Asset.request(GES.TestClip, "framerate=120/1,height=500,width=500,max-duration=f120") clip = layer.add_asset( asset, 0, 0, Gst.SECOND, GES.TrackType.UNKNOWN) self.assertEqual(clip.get_id(), "GESTestClip, framerate=(fraction)120/1, height=(int)500, width=(int)500, max-duration=(string)f120;") test_source, = clip.get_children(True) self.assertEqual(test_source.get_natural_size(), (True, 500, 500)) self.assertEqual(test_source.get_natural_framerate(), (True, 120, 1)) self.assertEqual(test_source.props.max_duration, Gst.SECOND) self.assertEqual(clip.get_natural_framerate(), (True, 120, 1)) self.assertEqual(self.timeline.get_frame_at(Gst.SECOND), 60) self.assertEqual(clip.props.max_duration, Gst.SECOND) def test_layer_active(self): def check_nle_object_activeness(clip, track_type, active=None, ref_clip=None): assert ref_clip is not None or active is not None if ref_clip: ref_elem, = ref_clip.find_track_elements(None, track_type, GES.Source) active = ref_elem.get_nleobject().props.active elem, = clip.find_track_elements(None, track_type, GES.Source) self.assertIsNotNone(elem) self.assertEqual(elem.get_nleobject().props.active, active) def get_tracks(timeline): for track in self.timeline.get_tracks(): if track.props.track_type == GES.TrackType.VIDEO: video_track = track else: audio_track = track return video_track, audio_track def check_set_active_for_tracks(layer, active, tracks, expected_changed_tracks): callback_called = [] def _check_active_changed_cb(layer, active, tracks, expected_tracks, expected_active): self.assertEqual(set(tracks), set(expected_tracks)) self.assertEqual(active, expected_active) callback_called.append(True) layer.connect("active-changed", _check_active_changed_cb, expected_changed_tracks, active) self.assertTrue(layer.set_active_for_tracks(active, tracks)) self.layer.disconnect_by_func(_check_active_changed_cb) self.assertEqual(callback_called, [True]) c0 = self.append_clip() check_nle_object_activeness(c0, GES.TrackType.VIDEO, True) check_nle_object_activeness(c0, GES.TrackType.AUDIO, True) elem, = c0.find_track_elements(None, GES.TrackType.AUDIO, GES.Source) elem.props.active = False check_nle_object_activeness(c0, GES.TrackType.VIDEO, True) check_nle_object_activeness(c0, GES.TrackType.AUDIO, False) self.check_reload_timeline() elem.props.active = True # Muting audio track video_track, audio_track = get_tracks(self.timeline) check_set_active_for_tracks(self.layer, False, [audio_track], [audio_track]) check_nle_object_activeness(c0, GES.TrackType.VIDEO, True) check_nle_object_activeness(c0, GES.TrackType.AUDIO, False) self.check_reload_timeline() c1 = self.append_clip() check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) check_nle_object_activeness(c1, GES.TrackType.AUDIO, False) l1 = self.timeline.append_layer() c1.move_to_layer(l1) check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) check_nle_object_activeness(c1, GES.TrackType.AUDIO, True) self.assertTrue(c1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, c1.props.start)) check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) check_nle_object_activeness(c1, GES.TrackType.AUDIO, False) self.check_reload_timeline() self.assertTrue(self.layer.remove_clip(c1)) check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) check_nle_object_activeness(c1, GES.TrackType.AUDIO, True) self.assertTrue(self.layer.add_clip(c1)) check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) check_nle_object_activeness(c1, GES.TrackType.AUDIO, False) check_set_active_for_tracks(self.layer, True, None, [audio_track]) check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) check_nle_object_activeness(c1, GES.TrackType.AUDIO, True) elem, = c1.find_track_elements(None, GES.TrackType.AUDIO, GES.Source) check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) check_nle_object_activeness(c1, GES.TrackType.AUDIO, True) # Force deactivating a specific TrackElement elem.props.active = False check_nle_object_activeness(c1, GES.TrackType.VIDEO, True) check_nle_object_activeness(c1, GES.TrackType.AUDIO, False) self.check_reload_timeline() # Try activating a specific TrackElement, that won't change the # underlying nleobject activness check_set_active_for_tracks(self.layer, False, None, [audio_track, video_track]) check_nle_object_activeness(c1, GES.TrackType.VIDEO, False) check_nle_object_activeness(c1, GES.TrackType.AUDIO, False) elem.props.active = True check_nle_object_activeness(c1, GES.TrackType.VIDEO, False) check_nle_object_activeness(c1, GES.TrackType.AUDIO, False) self.check_reload_timeline() class TestEditing(common.GESSimpleTimelineTest): def test_transition_disappears_when_moving_to_another_layer(self): self.timeline.props.auto_transition = True unused_clip1 = self.add_clip(0, 0, 100) clip2 = self.add_clip(50, 0, 100) self.assertEqual(len(self.layer.get_clips()), 4) layer2 = self.timeline.append_layer() clip2.edit([], layer2.get_priority(), GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, clip2.props.start) self.assertEqual(len(self.layer.get_clips()), 1) self.assertEqual(len(layer2.get_clips()), 1) def activate_snapping(self): self.timeline.set_snapping_distance(5) self.snapped_at = [] def _snapped_cb(timeline, elem1, elem2, position): self.snapped_at.append(position) def _snapped_end_cb(timeline, elem1, elem2, position): if self.snapped_at: # Ignoring first snap end. self.snapped_at.append(Gst.CLOCK_TIME_NONE) self.timeline.connect("snapping-started", _snapped_cb) self.timeline.connect("snapping-ended", _snapped_end_cb) def test_snap_start_snap_end(self): clip = self.append_clip() self.append_clip() self.activate_snapping() self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), ] ]) # snap to 20 clip.props.start = 18 self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 10, 10), (GES.TestClip, 20, 10), ] ]) self.assertEqual(self.snapped_at, [20]) # no snapping clip.props.start = 30 self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 10, 10), (GES.TestClip, 30, 10), ] ]) self.assertEqual(self.snapped_at, [20, Gst.CLOCK_TIME_NONE]) # snap to 20 clip.props.start = 18 self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 10, 10), (GES.TestClip, 20, 10), ] ]) self.assertEqual(self.snapped_at, [20, Gst.CLOCK_TIME_NONE, 20]) # snap to 20 again clip.props.start = 19 self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 10, 10), (GES.TestClip, 20, 10), ] ]) self.assertEqual( self.snapped_at, [20, Gst.CLOCK_TIME_NONE, 20, Gst.CLOCK_TIME_NONE, 20]) def test_rippling_snaps(self): self.timeline.props.auto_transition = True self.append_clip() clip = self.append_clip() self.activate_snapping() self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), ] ]) clip.edit([], 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 15) self.assertEqual(self.snapped_at, [10]) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), ] ]) clip.edit([], 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 20) self.assertEqual(self.snapped_at, [10, Gst.CLOCK_TIME_NONE]) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TestClip, 20, 10), ] ]) def test_transition_moves_when_rippling_to_another_layer(self): self.timeline.props.auto_transition = True clip1 = self.add_clip(0, 0, 100) clip2 = self.add_clip(50, 0, 100) all_clips = self.layer.get_clips() self.assertEqual(len(all_clips), 4) layer2 = self.timeline.append_layer() clip1.edit([], layer2.get_priority(), GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip1.props.start) self.assertEqual(self.layer.get_clips(), []) self.assertEqual(set(layer2.get_clips()), set(all_clips)) def test_transition_rippling_after_next_clip_stays(self): self.timeline.props.auto_transition = True clip1 = self.add_clip(0, 0, 100) clip2 = self.add_clip(50, 0, 100) all_clips = self.layer.get_clips() self.assertEqual(len(all_clips), 4) clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip2.props.start + 1) self.assertEqual(set(self.layer.get_clips()), set(all_clips)) def test_transition_rippling_over_does_not_create_another_transition(self): self.timeline.props.auto_transition = True clip1 = self.add_clip(0, 0, 17 * Gst.SECOND) clip2 = clip1.split(7.0 * Gst.SECOND) # Make a transition between the two clips clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 4.5 * Gst.SECOND) # Rippl clip1 and check that transitions ar always the sames all_clips = self.layer.get_clips() self.assertEqual(len(all_clips), 4) clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 41.5 * Gst.SECOND) self.assertEqual(len(self.layer.get_clips()), 4) clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 35 * Gst.SECOND) self.assertEqual(len(self.layer.get_clips()), 4) def test_trim_transition(self): self.track_types = [GES.TrackType.AUDIO] super().setUp() self.timeline.props.auto_transition = True self.add_clip(0, 0, 10) self.add_clip(5, 0, 10) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TransitionClip, 5, 5), (GES.TestClip, 5, 10), ] ]) transition = self.layer.get_clips()[1] self.assertTrue(transition.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 7)) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TransitionClip, 7, 3), (GES.TestClip, 7, 8), ] ]) def test_trim_start(self): clip = self.append_clip() self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 10)) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 10, 10), ] ]) self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0)) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 10, 10), ] ]) def test_trim_non_core(self): clip = self.append_clip() self.assertTrue(clip.set_inpoint(12)) self.assertTrue(clip.set_max_duration(30)) self.assertEqual(clip.get_duration_limit(), 18) for child in clip.get_children(False): self.assertEqual(child.get_inpoint(), 12) self.assertEqual(child.get_max_duration(), 30) effect0 = GES.Effect.new("textoverlay") effect0.set_has_internal_source(True) self.assertTrue(effect0.set_inpoint(5)) self.assertTrue(effect0.set_max_duration(20)) self.assertTrue(clip.add(effect0)) self.assertEqual(clip.get_duration_limit(), 15) effect1 = GES.Effect.new("agingtv") effect1.set_has_internal_source(False) self.assertTrue(clip.add(effect1)) effect2 = GES.Effect.new("textoverlay") effect2.set_has_internal_source(True) self.assertTrue(effect2.set_inpoint(8)) self.assertTrue(effect2.set_max_duration(18)) self.assertTrue(clip.add(effect2)) self.assertEqual(clip.get_duration_limit(), 10) effect3 = GES.Effect.new("textoverlay") effect3.set_has_internal_source(True) self.assertTrue(effect3.set_inpoint(20)) self.assertTrue(effect3.set_max_duration(22)) self.assertTrue(effect3.set_active(False)) self.assertTrue(clip.add(effect3)) self.assertEqual(clip.get_duration_limit(), 10) self.assertTrue(clip.set_start(10)) self.assertTrue(clip.set_duration(10)) # cannot trim to a 0 because effect0 would have a negative in-point error = None try: clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0) except GLib.Error as err: error = err self.assertGESError(error, GES.Error.NEGATIVE_TIME) self.assertEqual(clip.start, 10) self.assertEqual(clip.inpoint, 12) self.assertEqual(effect0.inpoint, 5) self.assertEqual(effect1.inpoint, 0) self.assertEqual(effect2.inpoint, 8) self.assertEqual(effect3.inpoint, 20) self.assertTrue( clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5)) self.assertEqual(clip.start, 5) self.assertEqual(clip.duration, 15) self.assertEqual(clip.get_duration_limit(), 15) for child in clip.get_children(False): self.assertEqual(child.start, 5) self.assertEqual(child.duration, 15) self.assertEqual(clip.inpoint, 7) self.assertEqual(effect0.inpoint, 0) self.assertEqual(effect1.inpoint, 0) self.assertEqual(effect2.inpoint, 3) self.assertEqual(effect3.inpoint, 20) self.assertTrue( clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 15)) self.assertEqual(clip.start, 15) self.assertEqual(clip.duration, 5) self.assertEqual(clip.get_duration_limit(), 5) for child in clip.get_children(False): self.assertEqual(child.start, 15) self.assertEqual(child.duration, 5) self.assertEqual(clip.inpoint, 17) self.assertEqual(effect0.inpoint, 10) self.assertEqual(effect1.inpoint, 0) self.assertEqual(effect2.inpoint, 13) self.assertEqual(effect3.inpoint, 20) def test_trim_time_effects(self): self.track_types = [GES.TrackType.VIDEO] super().setUp() clip = self.append_clip(asset_id="max-duration=30") self.assertTrue(clip.set_inpoint(12)) self.assertEqual(clip.get_duration_limit(), 18) children = clip.get_children(False) self.assertTrue(children) self.assertEqual(len(children), 1) source = children[0] self.assertEqual(source.get_inpoint(), 12) self.assertEqual(source.get_max_duration(), 30) rate0 = GES.Effect.new("videorate rate=0.25") overlay = GES.Effect.new("textoverlay") overlay.set_has_internal_source(True) self.assertTrue(overlay.set_inpoint(5)) self.assertTrue(overlay.set_max_duration(16)) rate1 = GES.Effect.new("videorate rate=2.0") self.assertTrue(clip.add(rate0)) self.assertTrue(clip.add(overlay)) self.assertTrue(clip.add(rate1)) # source -> rate1 -> overlay -> rate0 # in-point/max-dur 12-30 5-16 # internal # start/end 12-30 0-9 5-14 0-36 self.assertEqual(clip.get_duration_limit(), 36) self.assertTrue(clip.set_start(40)) self.assertTrue(clip.set_duration(10)) self.check_reload_timeline() # cannot trim to a 16 because overlay would have a negative in-point error = None try: clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 16) except GLib.Error as err: error = err self.assertGESError(error, GES.Error.NEGATIVE_TIME) self.assertEqual(clip.get_start(), 40) self.assertEqual(clip.get_duration(), 10) self.assertEqual(source.get_inpoint(), 12) self.assertEqual(source.get_max_duration(), 30) self.assertEqual(overlay.get_inpoint(), 5) self.assertEqual(overlay.get_max_duration(), 16) self.check_reload_timeline() # trim backwards to 20 self.assertTrue( clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 20)) self.assertEqual(clip.get_start(), 20) self.assertEqual(clip.get_duration(), 30) # reduced by 10 self.assertEqual(source.get_inpoint(), 2) self.assertEqual(source.get_max_duration(), 30) # reduced by 5 self.assertEqual(overlay.get_inpoint(), 0) self.assertEqual(overlay.get_max_duration(), 16) # trim forwards to 28 self.assertTrue( clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 28)) self.assertEqual(clip.get_start(), 28) self.assertEqual(clip.get_duration(), 22) # increased by 4 self.assertEqual(source.get_inpoint(), 6) self.assertEqual(source.get_max_duration(), 30) # increased by 2 self.assertEqual(overlay.get_inpoint(), 2) self.assertEqual(overlay.get_max_duration(), 16) self.check_reload_timeline() def test_ripple_end(self): clip = self.append_clip() clip.set_max_duration(20) self.append_clip().set_max_duration(10) self.append_clip().set_max_duration(10) self.print_timeline() self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 20)) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 20), (GES.TestClip, 20, 10), (GES.TestClip, 30, 10), ] ]) self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 15)) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 15), (GES.TestClip, 15, 10), (GES.TestClip, 25, 10), ] ]) def test_move_group_full_overlap(self): self.track_types = [GES.TrackType.AUDIO] super().setUp() for _ in range(4): self.append_clip() clips = self.layer.get_clips() self.assertTrue(clips[0].ripple(20)) self.assertTimelineTopology([ [ (GES.TestClip, 20, 10), (GES.TestClip, 30, 10), (GES.TestClip, 40, 10), (GES.TestClip, 50, 10), ] ]) group = GES.Container.group(clips[1:]) self.print_timeline() self.assertFalse(group.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0)) self.print_timeline() self.assertTimelineTopology([ [ (GES.TestClip, 20, 10), (GES.TestClip, 30, 10), (GES.TestClip, 40, 10), (GES.TestClip, 50, 10), ] ]) self.assertFalse(clips[1].edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0)) self.print_timeline() self.assertTimelineTopology([ [ (GES.TestClip, 20, 10), (GES.TestClip, 30, 10), (GES.TestClip, 40, 10), (GES.TestClip, 50, 10), ] ]) def test_trim_inside_group(self): self.track_types = [GES.TrackType.AUDIO] super().setUp() for _ in range(2): self.append_clip() clips = self.layer.get_clips() group = GES.Container.group(clips) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), ] ]) self.assertEqual(group.props.start, 0) self.assertEqual(group.props.duration, 20) clips[0].trim(5) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 5, 5), (GES.TestClip, 10, 10), ] ]) self.assertEqual(group.props.start, 5) self.assertEqual(group.props.duration, 15) group1 = GES.Group.new () group1.add(group) clips[0].trim(0) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), ] ]) self.assertEqual(group.props.start, 0) self.assertEqual(group.props.duration, 20) self.assertEqual(group1.props.start, 0) self.assertEqual(group1.props.duration, 20) self.assertTrue(clips[1].edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15)) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TestClip, 10, 5), ] ]) self.assertEqual(group.props.start, 0) self.assertEqual(group.props.duration, 15) self.assertEqual(group1.props.start, 0) self.assertEqual(group1.props.duration, 15) def test_trim_end_past_max_duration(self): clip = self.append_clip() max_duration = clip.props.duration clip.set_max_duration(max_duration) self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5)) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 5, 5), ] ]) self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15)) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 5, 5), ] ]) def test_illegal_effect_move(self): c0 = self.append_clip() self.append_clip() self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), ] ]) effect = GES.Effect.new("agingtv") c0.add(effect) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), ] ]) self.assertFalse(effect.set_start(10)) self.assertEqual(effect.props.start, 0) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), ] ]) self.assertFalse(effect.set_duration(20)) self.assertEqual(effect.props.duration, 10) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), ] ]) def test_moving_overlay_clip_in_group(self): c0 = self.append_clip() overlay = self.append_clip(asset_type=GES.TextOverlayClip) group = GES.Group.new() group.add(c0) group.add(overlay) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), (GES.TextOverlayClip, 10, 10), ] ], groups=[(c0, overlay)]) self.assertTrue(overlay.set_start(20)) self.assertTimelineTopology([ [ (GES.TestClip, 10, 10), (GES.TextOverlayClip, 20, 10), ] ], groups=[(c0, overlay)]) def test_moving_group_in_group(self): c0 = self.append_clip() overlay = self.append_clip(asset_type=GES.TextOverlayClip) group0 = GES.Group.new() group0.add(c0) group0.add(overlay) c1 = self.append_clip() group1 = GES.Group.new() group1.add(group0) group1.add(c1) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), (GES.TextOverlayClip, 10, 10), (GES.TestClip, 20, 10), ] ], groups=[(c1, group0), (c0, overlay)]) self.assertTrue(group0.set_start(10)) self.assertTimelineTopology([ [ (GES.TestClip, 10, 10), (GES.TextOverlayClip, 20, 10), (GES.TestClip, 30, 10), ] ], groups=[(c1, group0), (c0, overlay)]) self.check_element_values(group0, 10, 0, 20) self.check_element_values(group1, 10, 0, 30) def test_illegal_group_child_move(self): clip0 = self.append_clip() _clip1 = self.add_clip(20, 0, 10) overlay = self.add_clip(20, 0, 10, asset_type=GES.TextOverlayClip) group = GES.Group.new() group.add(clip0) group.add(overlay) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), (GES.TextOverlayClip, 20, 10), (GES.TestClip, 20, 10), ] ], groups=[(clip0, overlay),]) # Can't move as clip0 and clip1 would fully overlap self.assertFalse(overlay.set_start(40)) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), (GES.TextOverlayClip, 20, 10), (GES.TestClip, 20, 10), ] ], groups=[(clip0, overlay)]) def test_child_duration_change(self): c0 = self.append_clip() self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), ] ]) self.assertTrue(c0.set_duration(40)) self.assertTimelineTopology([ [ (GES.TestClip, 0, 40), ] ]) c0.children[0].set_duration(10) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), ] ]) self.assertTrue(c0.set_start(40)) self.assertTimelineTopology([ [ (GES.TestClip, 40, 10), ] ]) c0.children[0].set_start(10) self.assertTimelineTopology([ [ (GES.TestClip, 10, 10), ] ]) class TestInvalidOverlaps(common.GESSimpleTimelineTest): def test_adding_or_moving(self): clip1 = self.add_clip(start=10, in_point=0, duration=3) self.assertIsNotNone(clip1) def check_add_move_clip(start, duration): self.timeline.props.auto_transition = True self.layer.props.auto_transition = True clip2 = GES.TestClip() clip2.props.start = start clip2.props.duration = duration self.assertFalse(self.layer.add_clip(clip2)) self.assertEqual(len(self.layer.get_clips()), 1) # Add the clip at a different position. clip2.props.start = 25 self.assertTrue(self.layer.add_clip(clip2)) self.assertEqual(clip2.props.start, 25) # Try to move the second clip by editing it. self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, start)) self.assertEqual(clip2.props.start, 25) # Try to put it in a group and move the group. clip3 = GES.TestClip() clip3.props.start = 20 clip3.props.duration = 1 self.assertTrue(self.layer.add_clip(clip3)) group = GES.Container.group([clip3, clip2]) self.assertTrue(group.props.start, 20) self.assertFalse(group.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, start - 5)) self.assertEqual(group.props.start, 20) self.assertEqual(clip3.props.start, 20) self.assertEqual(clip2.props.start, 25) for clip in group.ungroup(False): self.assertTrue(self.layer.remove_clip(clip)) # clip1 contains... check_add_move_clip(start=10, duration=1) check_add_move_clip(start=11, duration=1) check_add_move_clip(start=12, duration=1) def test_splitting(self): clip1 = self.add_clip(start=9, in_point=0, duration=3) clip2 = self.add_clip(start=10, in_point=0, duration=4) clip3 = self.add_clip(start=12, in_point=0, duration=3) self.assertIsNone(clip1.split_full(13)) self.assertIsNone(clip1.split_full(8)) self.assertIsNone(clip3.split_full(12)) self.assertIsNone(clip3.split_full(15)) def _fail_split(self, clip, position): split = None error = None try: split = clip.split_full(position) except GLib.Error as err: error = err self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK) self.assertIsNone(split) def test_split_with_transition(self): self.track_types = [GES.TrackType.AUDIO] super().setUp() self.timeline.set_auto_transition(True) clip0 = self.add_clip(start=0, in_point=0, duration=50) clip1 = self.add_clip(start=20, in_point=0, duration=50) clip2 = self.add_clip(start=60, in_point=0, duration=20) self.assertTimelineTopology([ [ (GES.TestClip, 0, 50), (GES.TransitionClip, 20, 30), (GES.TestClip, 20, 50), (GES.TransitionClip, 60, 10), (GES.TestClip, 60, 20), ] ]) # Split should fail as the first part of the split # would be fully overlapping clip0 self._fail_split(clip1, 40) self.assertTimelineTopology([ [ (GES.TestClip, 0, 50), (GES.TransitionClip, 20, 30), (GES.TestClip, 20, 50), (GES.TransitionClip, 60, 10), (GES.TestClip, 60, 20), ] ]) # same with end of the clip self._fail_split(clip1, 65) self.assertTimelineTopology([ [ (GES.TestClip, 0, 50), (GES.TransitionClip, 20, 30), (GES.TestClip, 20, 50), (GES.TransitionClip, 60, 10), (GES.TestClip, 60, 20), ] ]) def test_changing_duration(self): clip1 = self.add_clip(start=9, in_point=0, duration=2) clip2 = self.add_clip(start=10, in_point=0, duration=2) self.assertFalse(clip1.set_start(10)) self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, clip2.props.start + clip2.props.duration)) self.assertFalse(clip1.ripple_end(clip2.props.start + clip2.props.duration)) self.assertFalse(clip1.roll_end(clip2.props.start + clip2.props.duration)) # clip2's end edge to the left, to decrease its duration. self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, clip1.props.start + clip1.props.duration)) self.assertFalse(clip2.ripple_end(clip1.props.start + clip1.props.duration)) self.assertFalse(clip2.roll_end(clip1.props.start + clip1.props.duration)) # clip2's start edge to the left, to increase its duration. self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, clip1.props.start)) self.assertFalse(clip2.trim(clip1.props.start)) # clip1's start edge to the right, to decrease its duration. self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, clip2.props.start)) self.assertFalse(clip1.trim(clip2.props.start)) def test_rippling_backward(self): self.track_types = [GES.TrackType.AUDIO] super().setUp() self.maxDiff = None for i in range(4): self.append_clip() self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), (GES.TestClip, 20, 10), (GES.TestClip, 30, 10), ] ]) clip = self.layer.get_clips()[2] self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip.props.start - 20)) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), (GES.TestClip, 20, 10), (GES.TestClip, 30, 10), ] ]) self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip.props.start + 10)) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), (GES.TestClip, 30, 10), (GES.TestClip, 40, 10), ] ]) self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip.props.start -20)) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), (GES.TestClip, 30, 10), (GES.TestClip, 40, 10), ] ]) def test_rolling(self): clip1 = self.add_clip(start=9, in_point=0, duration=2) clip2 = self.add_clip(start=10, in_point=0, duration=2) clip3 = self.add_clip(start=11, in_point=0, duration=2) # Rolling clip1's end -1 would lead to clip3 to overlap 100% with clip2. self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 9, 2), (GES.TestClip, 10, 2), (GES.TestClip, 11, 2) ] ]) self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, clip1.props.start + clip1.props.duration - 1)) self.assertFalse(clip1.roll_end(13)) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 9, 2), (GES.TestClip, 10, 2), (GES.TestClip, 11, 2) ] ]) # Rolling clip3's start +1 would lead to clip1 to overlap 100% with clip2. self.assertFalse(clip3.edit([], -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 12)) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 9, 2), (GES.TestClip, 10, 2), (GES.TestClip, 11, 2) ] ]) def test_layers(self): self.track_types = [GES.TrackType.AUDIO] super().setUp() self.maxDiff = None self.timeline.append_layer() for i in range(2): self.append_clip() self.append_clip(1) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), ], [ (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), ] ]) clip = self.layer.get_clips()[0] self.assertFalse(clip.edit([], 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0)) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), ], [ (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), ] ]) def test_rippling(self): self.timeline.remove_track(self.timeline.get_tracks()[0]) clip1 = self.add_clip(start=9, in_point=0, duration=2) clip2 = self.add_clip(start=10, in_point=0, duration=2) clip3 = self.add_clip(start=11, in_point=0, duration=2) # Rippling clip2's start -2 would bring clip3 exactly on top of clip1. self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 8)) self.assertFalse(clip2.ripple(8)) # Rippling clip1's end -1 would bring clip3 exactly on top of clip2. self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 8)) self.assertFalse(clip1.ripple_end(8)) def test_move_group_to_layer(self): self.track_types = [GES.TrackType.AUDIO] super().setUp() self.append_clip() self.append_clip() self.append_clip() clips = self.layer.get_clips() clips[1].props.start += 2 group = GES.Container.group(clips[1:]) self.assertTrue(clips[1].edit([], 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, group.props.start)) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), ], [ (GES.TestClip, 12, 10), (GES.TestClip, 20, 10), ] ]) clips[0].props.start = 15 self.assertTimelineTopology([ [ (GES.TestClip, 15, 10), ], [ (GES.TestClip, 12, 10), (GES.TestClip, 20, 10), ] ]) self.assertFalse(clips[1].edit([], 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, group.props.start)) self.assertTimelineTopology([ [ (GES.TestClip, 15, 10), ], [ (GES.TestClip, 12, 10), (GES.TestClip, 20, 10), ] ]) def test_copy_paste_overlapping(self): self.track_types = [GES.TrackType.AUDIO] super().setUp() clip = self.append_clip() copy = clip.copy(True) self.assertIsNone(copy.paste(copy.props.start)) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), ] ]) copy = clip.copy(True) pasted = copy.paste(copy.props.start + 1) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), (GES.TestClip, 1, 10), ] ]) pasted.move_to_layer(self.timeline.append_layer()) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), ], [ (GES.TestClip, 1, 10), ] ]) copy = pasted.copy(True) self.assertIsNotNone(copy.paste(pasted.props.start - 1)) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), ], [ (GES.TestClip, 0, 10), (GES.TestClip, 1, 10), ], ]) group = GES.Group.new() group.add(clip) copied_group = group.copy(True) self.assertFalse(copied_group.paste(group.props.start)) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), ], [ (GES.TestClip, 0, 10), (GES.TestClip, 1, 10), ], ]) def test_move_group_with_overlaping_clips(self): self.track_types = [GES.TrackType.AUDIO] super().setUp() self.append_clip() self.append_clip() self.append_clip() self.timeline.props.auto_transition = True clips = self.layer.get_clips() clips[1].props.start += 5 group = GES.Container.group(clips[1:]) self.assertTimelineTopology([ [ (GES.TestClip, 0, 10), (GES.TestClip, 15, 10), (GES.TransitionClip, 20, 5), (GES.TestClip, 20, 10), ] ]) clips[0].props.start = 30 self.assertTimelineTopology([ [ (GES.TestClip, 15, 10), (GES.TransitionClip, 20, 5), (GES.TestClip, 20, 10), (GES.TestClip, 30, 10), ] ]) # the 3 clips would overlap self.assertFalse(clips[1].edit([], 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 25)) self.assertTimelineTopology([ [ (GES.TestClip, 15, 10), (GES.TransitionClip, 20, 5), (GES.TestClip, 20, 10), (GES.TestClip, 30, 10), ] ]) class TestConfigurationRules(common.GESSimpleTimelineTest): def _try_add_clip(self, start, duration, layer=None, error=None): if layer is None: layer = self.layer asset = GES.Asset.request(GES.TestClip, None) found_err = None clip = None # large inpoint to allow trims try: clip = layer.add_asset_full( asset, start, 1000, duration, GES.TrackType.UNKNOWN) except GLib.Error as err: found_err = err if error is None: self.assertIsNotNone(clip) else: self.assertIsNone(clip) self.assertGESError(found_err, error) return clip def test_full_overlap_add(self): clip1 = self._try_add_clip(50, 50) self._try_add_clip(50, 50, error=GES.Error.INVALID_OVERLAP_IN_TRACK) self._try_add_clip(49, 51, error=GES.Error.INVALID_OVERLAP_IN_TRACK) self._try_add_clip(51, 49, error=GES.Error.INVALID_OVERLAP_IN_TRACK) def test_triple_overlap_add(self): clip1 = self._try_add_clip(0, 50) clip2 = self._try_add_clip(40, 50) self._try_add_clip(39, 12, error=GES.Error.INVALID_OVERLAP_IN_TRACK) self._try_add_clip(30, 30, error=GES.Error.INVALID_OVERLAP_IN_TRACK) self._try_add_clip(1, 88, error=GES.Error.INVALID_OVERLAP_IN_TRACK) def test_full_overlap_move(self): clip1 = self._try_add_clip(0, 50) clip2 = self._try_add_clip(50, 50) self.assertFalse(clip2.set_start(0)) def test_triple_overlap_move(self): clip1 = self._try_add_clip(0, 50) clip2 = self._try_add_clip(40, 50) clip3 = self._try_add_clip(100, 60) self.assertFalse(clip3.set_start(30)) def test_full_overlap_move_into_layer(self): clip1 = self._try_add_clip(0, 50) layer2 = self.timeline.append_layer() clip2 = self._try_add_clip(0, 50, layer2) res = None try: res = clip2.move_to_layer_full(self.layer) except GLib.Error as error: self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK) self.assertIsNone(res) def test_triple_overlap_move_into_layer(self): clip1 = self._try_add_clip(0, 50) clip2 = self._try_add_clip(40, 50) layer2 = self.timeline.append_layer() clip3 = self._try_add_clip(30, 30, layer2) res = None try: res = clip3.move_to_layer_full(self.layer) except GLib.Error as error: self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK) self.assertIsNone(res) def test_full_overlap_trim(self): clip1 = self._try_add_clip(0, 50) clip2 = self._try_add_clip(50, 50) self.assertFalse(clip2.trim(0)) self.assertFalse(clip1.set_duration(100)) def test_triple_overlap_trim(self): clip1 = self._try_add_clip(0, 20) clip2 = self._try_add_clip(10, 30) clip3 = self._try_add_clip(30, 20) self.assertFalse(clip3.trim(19)) self.assertFalse(clip1.set_duration(31)) class TestSnapping(common.GESSimpleTimelineTest): def test_snapping(self): self.timeline.props.auto_transition = True self.timeline.set_snapping_distance(1) clip1 = self.add_clip(0, 0, 100) # Split clip1. split_position = 50 clip2 = clip1.split(split_position) self.assertEqual(len(self.layer.get_clips()), 2) self.assertEqual(clip1.props.duration, split_position) self.assertEqual(clip2.props.start, split_position) # Make sure snapping prevents clip2 to be moved to the left. clip2.edit([], self.layer.get_priority(), GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, clip2.props.start - 1) self.assertEqual(clip2.props.start, split_position) def test_trim_snapps_inside_group(self): self.track_types = [GES.TrackType.AUDIO] super().setUp() self.timeline.props.auto_transition = True self.timeline.set_snapping_distance(5) snaps = [] def snapping_started_cb(timeline, element1, element2, dist, self): snaps.append(set([element1, element2])) self.timeline.connect('snapping-started', snapping_started_cb, self) clip = self.append_clip() clip1 = self.append_clip() self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), ] ]) clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 15) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TestClip, 10, 10), ] ]) self.assertEqual(snaps[0], set([clip.get_children(False)[0], clip1.get_children(False)[0]])) clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 16) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 0, 10), (GES.TestClip, 16, 4), ] ]) def test_trim_no_snapping_on_same_clip(self): self.timeline.props.auto_transition = True self.timeline.set_snapping_distance(1) not_called = [] def snapping_started_cb(timeline, element1, element2, dist, self): not_called.append("No snapping should happen") self.timeline.connect('snapping-started', snapping_started_cb, self) clip = self.append_clip() clip.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5) self.assertEqual(not_called, []) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 5, 5), ] ]) clip.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 4) self.assertEqual(not_called, []) self.assertTimelineTopology([ [ # Unique layer (GES.TestClip, 4, 6), ] ]) def test_no_snapping_on_split(self): self.timeline.props.auto_transition = True self.timeline.set_snapping_distance(1) not_called = [] def snapping_started_cb(timeline, element1, element2, dist, self): not_called.append("No snapping should happen") self.timeline.connect('snapping-started', snapping_started_cb, self) clip1 = self.add_clip(0, 0, 100) # Split clip1. split_position = 50 clip2 = clip1.split(split_position) self.assertEqual(not_called, []) self.assertEqual(len(self.layer.get_clips()), 2) self.assertEqual(clip1.props.duration, split_position) self.assertEqual(clip2.props.start, split_position) class TestComplexEditing(common.GESTimelineConfigTest): def test_normal_move(self): """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ .................g1.................. : : : *=========* : | c0 | : *=========* ____________________:___________________________________:____ layer1______________:___________________________________:____ : : ..............g0............... : : : : *=======================* : : | c1 | : : *=========*=============*=====* : : | c2 | : : *===================* : :.............................: : : : :...................................: _____________________________________________________________ layer2_______________________________________________________ *=============================* | c3 | *=============================* """ track = self.add_video_track() c0 = self.add_clip("c0", 0, [track], 23, 5) c1 = self.add_clip("c1", 1, [track], 10, 12) c2 = self.add_clip("c2", 1, [track], 15, 10) self.register_auto_transition(c1, c2, track) c3 = self.add_clip("c3", 2, [track], 2, 15) g0 = self.add_group("g0", [c1, c2]) g1 = self.add_group("g1", [c0, g0]) self.assertTimelineConfig() # test invalid edits # cannot move c0 up one layer because it would cause a triple # overlap between c1, c2 and c3 when g0 moves self.assertFailEdit( c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 23, GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot move c0, without moving g1, to 21 layer 1 because it # would be completely overlapped by c2 self.assertFailEdit( c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 20, GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot move c1, without moving g1, with end 25 because it # would be completely overlapped by c2 self.assertFailEdit( c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 25, GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot move g0 to layer 0 because it would make c0 go to a # negative layer self.assertFailEdit( g0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10, GES.Error.NEGATIVE_LAYER) # cannot move c1 for same reason error = None try: c1.move_to_layer_full(self.timeline.get_layer(0)) except GLib.Error as err: error = err self.assertGESError(error, GES.Error.NEGATIVE_LAYER) self.assertTimelineConfig({}, []) # failure with snapping self.timeline.set_snapping_distance(1) # cannot move to 0 because end edge of c0 would snap with end of # c3, making the new start become negative self.assertFailEdit( g0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0, GES.Error.NEGATIVE_TIME) # cannot move start of c1 to 14 because snapping causes a full # overlap with c0 self.assertFailEdit( c1, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 14, GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot move end of c2 to 21 because snapping causes a full # overlap with c0 self.assertFailEdit( c2, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 21, GES.Error.INVALID_OVERLAP_IN_TRACK) # successes self.timeline.set_snapping_distance(3) # moving c0 also moves g1, along with g0 # with no snapping, this would result in a triple overlap between # c1, c2 and c3, but c2's start edge will snap to the end of c3 # at a distance of 3 allowing the edit to succeed # # c1 and c3 have a new transition # transition between c1 and c2 is not lost # # NOTE: there is no snapping between c0, c1 or c2 even though # their edges are within distance 2 of each other because they are # all moving self.assertEdit( c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 22, 17, [c2], [c3], { c0 : {"start": 25, "layer": 1}, c1 : {"start": 12, "layer": 2}, c2 : {"start": 17, "layer": 2}, g0 : {"start": 12, "layer": 2}, g1 : {"start": 12, "layer": 1} }, [(c3, c1, track)], []) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ _____________________________________________________________ layer1_______________________________________________________ .................g1.................. : : : *=========* : | c0 | : *=========* ________________________:___________________________________: layer2__________________:___________________________________: : : ..............g0............... : : : : *=======================* : : | c1 | : : *===================*=========*=============*=====* : | c3 | c2 | : *=============================*===================* : :.............................: : : : :...................................: """ # using EDGE_START we can move without moving parent # snap at same position 17 but with c1's end edge to c3's end # edge # loose transition between c1 and c3 self.assertEdit( g0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 5, 17, [c1], [c3], { c1 : {"start": 5, "layer": 0}, c2 : {"start": 10, "layer": 0}, g0 : {"start": 5, "layer": 0}, g1 : {"start": 5, "duration": 25, "layer": 0}, }, [], [(c3, c1, track)]) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ .........................g1........................ : : ...............g0.............. : : : : *=======================* : : | c1 | : : *=========*=============*=====* : : | c2 | : : *===================* : :.............................: : __________:_________________________________________________: layer1____:_________________________________________________: : : : *=========* : | c0 | : *=========* :.................................................: _____________________________________________________________ layer2_______________________________________________________ *=============================* | c3 | *=============================* """ # using EDGE_END we can move without moving parent # no snap # loose transition between c1 and c2 self.assertEdit( c2, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 21, None, [], [], { c2 : {"duration": 11, "layer": 1}, g0 : {"duration": 16}, }, [], [(c1, c2, track)]) # no snapping when we use move layer self.timeline.set_snapping_distance(10) self.assertTrue( c3.move_to_layer(self.timeline.get_layer(1))) self.assertTimelineConfig( new_props={c3 : {"layer": 1}}, new_transitions=[(c3, c2, track)]) def test_ripple(self): """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ ......................g0................. : : *=========* : | c0 | : *=========* : ______________:_______________________________________:______ layer1________:_______________________________________:______ : : : *=============* : | c3 | : *=============* :.......................................: *===================* | c2 | *=========*=========*=========* | c1 | *===================* """ track = self.add_video_track() c0 = self.add_clip("c0", 0, [track], 7, 5) c1 = self.add_clip("c1", 1, [track], 5, 10) c2 = self.add_clip("c2", 1, [track], 10, 10) c3 = self.add_clip("c3", 1, [track], 20, 7) self.register_auto_transition(c1, c2, track) g0 = self.add_group("g0", [c0, c3]) self.assertTimelineConfig() # test failures self.timeline.set_snapping_distance(2) # would cause negative layer priority for c0 self.assertFailEdit( c1, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 5, GES.Error.NEGATIVE_LAYER) # would lead to c2 fully overlapping c3 since c2 does ripple # but c3 does not(c3 shares a toplevel with c0, and # GES_EDGE_START, same as NORMAL mode, does not move the # toplevel self.assertFailEdit( c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 25, GES.Error.INVALID_OVERLAP_IN_TRACK) # would lead to c2 fully overlapping c3 since c2 does not # ripple but c3 does self.assertFailEdit( c0, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 13, GES.Error.INVALID_OVERLAP_IN_TRACK) # add two more clips c4 = self.add_clip("c4", 2, [track], 17, 8) c5 = self.add_clip("c5", 2, [track], 21, 8) self.register_auto_transition(c4, c5, track) self.assertTimelineConfig() """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ ......................g0................. : : *=========* : | c0 | : *=========* : ______________:_______________________________________:______ layer1________:_______________________________________:______ : : : *=============* : | c3 | : *=============* :.......................................: *===================* | c2 | *=========*=========*=========* | c1 | *===================* _____________________________________________________________ layer2_______________________________________________________ *===============* | c4 | *=======*=======*=======* | c5 | *===============* """ # rippling start of c2 only moves c4 and c5 because c3 is part # of a toplevel with an earlier start # NOTE: snapping only occurs for the edges of c2, in particular # start of c4 does not snap to end of c1 self.assertEdit( c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 8, 7, [c2], [c0], { c2 : {"start": 7}, c4 : {"start": 14}, c5 : {"start": 18}, }, [], []) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ ......................g0................. : : *=========* : | c0 | : *=========* : ______________:_______________________________________:______ layer1________:_______________________________________:______ : : : *=============* : | c3 | : *=============* :.......................................: *===================* | c2 | *===*===============*===* | c1 | *===================* _____________________________________________________________ layer2_______________________________________________________ *===============* | c4 | *=======*=======*=======* | c5 | *===============* """ # rippling end of c2, only c5 moves # NOTE: start edge of c2 does not snap! self.assertEdit( c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 19, 20, [c2], [c3], { c2 : {"duration": 13}, c5 : {"start": 21}, }, [], []) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ ......................g0................. : : *=========* : | c0 | : *=========* : ______________:_______________________________________:______ layer1________:_______________________________________:______ : : : *=============* : | c3 | : *=============* :.......................................: *=========================* | c2 | *===*===============*=========* | c1 | *===================* _____________________________________________________________ layer2_______________________________________________________ *===============* | c4 | *=============*=*=============* | c5 | *===============* """ # everything except c1 moves, and to the next layer # end edge of c2 snaps to end of c1 # NOTE: does not snap to edges of rippled clips # NOTE: c4 and c5 do not loose their transition when moving # to the new layer self.assertEdit( c2, 2, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 0, 15, [c2], [c1], { c0 : {"start": 2, "layer": 1}, c2 : {"start": 2, "layer": 2}, c3 : {"start": 15, "layer": 2}, c4 : {"start": 9, "layer": 3}, c5 : {"start": 16, "layer": 3}, g0 : {"start": 2, "layer": 1}, }, [(c0, c1, track)], [(c1, c2, track)]) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ _____________________________________________________________ layer1_______________________________________________________ *===================* | c1 | *===================* ...................g0.................... *=========* : | c0 | : *=========* : ____:_______________________________________:________________ layer2______________________________________:________________ : : : *=============* : | c3 | : *=============* :.......................................: *=========================* | c2 | *=========================* _____________________________________________________________ layer3_______________________________________________________ *===============* | c4 | *=============*=*=============* | c5 | *===============* """ # group c1 and c5, and g0 and c2 g1 = self.add_group("g1", [c1, c5]) g2 = self.add_group("g2", [g0, c2]) self.assertTimelineConfig() # moving end edge of c0 does not move anything else in the same # toplevel g2 # c5 does not move because it is grouped with c1, which starts # earlier than the end edge of c0 # only c4 moves # c0 does not snap to c4's start edge self.assertEdit( c0, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 10, None, [], [], { c0 : {"duration": 8}, c4 : {"start": 12}, }, [], []) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ _____________________________________________________________ layer1_______________________________________________________ ..................g1..................... *===================* : | c1 | : *===================* : :...................................... : ...................g2.................... : : :..................g0...................: : : *===============* : : : | c0 | : : : *===============* : : : ____:_______________________________________:___:_:__________ layer2______________________________________:___:_:__________ : : : : : *=============* : : : | c3 | : : : *=============* : : :.......................................: : : *=========================* : : : | c2 | : : : *=========================* : : : :.......................................: : : ________________________________________________:_:__________ layer3__________________________________________:_:__________ ................: : *===============* : | c5 | : *===============* : :.................: *===============* | c4 | *===============* """ # rippling start of c5 does not move anything else # end edge snaps to start of c4 self.timeline.set_snapping_distance(1) self.assertEdit( c5, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 18, None, [], [], { c5 : {"start": 18, "layer": 0}, g1 : {"layer": 0, "duration": 21}, }, [], [(c4, c5, track)]) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ ....................g1..................... : *===============* : | c5 | : *===============* __________:_________________________________________:________ layer1____:_________________________________________:________ : : *===================* : | c1 | : *===================* : :.........................................: ...................g2.................... :..................g0...................: *===============* : | c0 | : *===============* : ____:_______________________________________:________________ layer2______________________________________:________________ : : : *=============* : | c3 | : *=============* :.......................................: *=========================* : | c2 | : *=========================* : :.......................................: _____________________________________________________________ layer3_______________________________________________________ *===============* | c4 | *===============* """ # rippling g1 using c5 # initial position would make c1 go negative, but end edge of c1 # will snap to end of c0, allowing the edit to succeed # c4 also moves because it is after the start of g1 self.timeline.set_snapping_distance(3) self.assertEdit( c5, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 12, 10, [c1], [c0], { c5 : {"start": 13, "layer": 1}, c1 : {"start": 0, "layer": 2}, g1 : {"start": 0, "layer": 1}, c4 : {"start": 7, "layer": 4}, }, [(c1, c2, track)], [(c0, c1, track)]) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ _____________________________________________________________ layer1_______________________________________________________ ....................g1..................... : *===============* : | c5 | : *===============* : ...................g2.................:.. : :..................g0.................:.: : *===============* : : : | c0 | : : : *===============* : : :___:_____________________________________:_:________________ layer2____________________________________:_:________________ : : : : *===================* : : | c1 | : : *===================* : : :...:.....................................: : : *=============* : | c3 | : *=============* :.......................................: *=========================* : | c2 | : *=========================* : :.......................................: _____________________________________________________________ layer3_______________________________________________________ _____________________________________________________________ layer4_______________________________________________________ *===============* | c4 | *===============* """ # moving start of c1 will move everything expect c5 because they # can snap to c5 since it is not moving # c1 and c2 keep transition self.assertEdit( c1, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 20, 21, [c1], [c5], { c1 : {"start": 21, "layer": 1}, g1 : {"start": 13, "duration": 18}, c0 : {"start": 23, "layer": 0}, c2 : {"start": 23, "layer": 1}, c3 : {"start": 36, "layer": 1}, g0 : {"start": 23, "layer": 0}, g2 : {"start": 23, "layer": 0}, c4 : {"start": 28, "layer": 3}, }, [], []) def test_trim(self): """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ ..................g2................... : : :...............g0............. : : *===============* : : | a0 | : : *===*=====*=========* : : | a1 | : : : *=========* : : *===================* : : | v0 | : : *===================* : : :.............................: : __________:_____________________________________:____________ layer1____:_____________________________________:____________ : : : ............g1..............: : : *=================* : : | a2 | : : *=================* : *===========================* : | v1 | : *===========================* __________:_________:___________________________:____________ layer2____:_________:___________________________:____________ *=============* *=================* | a3 | | a4 | *=============* *=================* : :...........................: *=========================* : | v2 | : *=============*===========*===========* : | v3 | : *=======================* :.....................................: _____________________________________________________________ layer3_______________________________________________________ *===========================* *===============* | v4 | | v5 | *===========================* *===============* """ audio_track = self.add_audio_track() video_track = self.add_video_track() a0 = self.add_clip("a0", 0, [audio_track], 12, 8, 5, 15) a1 = self.add_clip("a1", 0, [audio_track], 10, 5) self.register_auto_transition(a1, a0, audio_track) a2 = self.add_clip("a2", 1, [audio_track], 15, 9, 7, 19) a3 = self.add_clip("a3", 2, [audio_track], 5, 7, 10) a4 = self.add_clip("a4", 2, [audio_track], 15, 9) v0 = self.add_clip("v0", 0, [video_track], 5, 10, 5) v1 = self.add_clip("v1", 1, [video_track], 10, 14) v2 = self.add_clip("v2", 2, [video_track], 5, 13, 4) v3 = self.add_clip("v3", 2, [video_track], 12, 12) self.register_auto_transition(v2, v3, video_track) v4 = self.add_clip("v4", 3, [video_track], 0, 13) v5 = self.add_clip("v5", 3, [video_track], 18, 8) g0 = self.add_group("g0", [a0, a1, v0]) g1 = self.add_group("g1", [v1, a2, a4]) g2 = self.add_group("g2", [a3, v2, v3, g0, g1]) self.assertTimelineConfig() # edit failures # cannot trim end of g0 to 16 because a0 and a1 would fully # overlap self.assertFailEdit( g0, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15, GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot edit to new layer because there would be triple overlaps # between v2, v3, v4 and v5 self.assertFailEdit( g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 20, GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot trim g1 end to 14 because it would result in a negative # duration for a2 and a4 self.assertFailEdit( g1, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 14, GES.Error.NEGATIVE_TIME) # cannot trim end of v2 below its start self.assertFailEdit( v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 2, GES.Error.NEGATIVE_TIME) # cannot trim end of g0 because a0's duration-limit would be # exceeded self.assertFailEdit( g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 23, GES.Error.NOT_ENOUGH_INTERNAL_CONTENT) # cannot trim g0 to 12 because a0 and a1 would fully overlap self.assertFailEdit( g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 12, GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot trim start of v2 beyond its end point self.assertFailEdit( v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 20, GES.Error.NEGATIVE_TIME) # with snapping self.timeline.set_snapping_distance(4) # cannot trim end of g2 to 19 because v1 and v2 would fully # overlap after snapping to v5 start edge(18) self.assertFailEdit( g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 19, GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot trim g2 to 3 because it would snap to start edge of # v4(0), causing v2's in-point to be negative self.assertFailEdit( g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 3, GES.Error.NEGATIVE_TIME) # success self.timeline.set_snapping_distance(2) # first trim v4 start self.assertEdit( v4, 3, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 1, None, [], [], { v4 : {"start": 1, "in-point": 1, "duration": 12}, }, [], []) # and trim v5 end self.assertEdit( v5, 3, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 25, 24, [v5], [a2, a4, v1, v3], { v5 : {"duration": 6}, }, [], []) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ ..................g2................... : : :...............g0............. : : *===============* : : | a0 | : : *===*=====*=========* : : | a1 | : : : *=========* : : *===================* : : | v0 | : : *===================* : : :.............................: : __________:_____________________________________:____________ layer1____:_____________________________________:____________ : : : ............g1..............: : : *=================* : : | a2 | : : *=================* : *===========================* : | v1 | : *===========================* __________:_________:___________________________:____________ layer2____:_________:___________________________:____________ *=============* *=================* | a3 | | a4 | *=============* *=================* : :...........................: *=========================* : | v2 | : *=============*===========*===========* : | v3 | : *=======================* :.....................................: _____________________________________________________________ layer3_______________________________________________________ *=========================* *===========* | v4 | | v5 | *=========================* *===========* """ # can trim g2 to 0 even though in-point of v2 is 4 because it will # snap to 1. Note, there is only snapping on the start edge # everything at the start edge is stretched back self.assertEdit( g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0, 1, [v0, v2, a3], [v4], { v0 : {"start": 1, "in-point": 1, "duration": 14}, a3 : {"start": 1, "in-point": 6, "duration": 11}, v2 : {"start": 1, "in-point": 0, "duration": 17}, g0 : {"start": 1, "duration": 19}, g2 : {"start": 1, "duration": 23}, }, [], []) self.timeline.set_snapping_distance(0) # trim end to use as a snapping point self.assertEdit( v4, 3, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 11, None, [], [], { v4 : {"duration": 10}, }, [], []) self.timeline.set_snapping_distance(2) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ ......................g2....................... : : :................g0.................... : : *===============* : : | a0 | : : *===*=====*=========* : : | a1 | : : : *=========* : : *===========================* : : | v0 | : : *===========================* : : :.....................................: : __:_____________________________________________:____________ layer1__________________________________________:____________ : : : ............g1..............: : : *=================* : : | a2 | : : *=================* : *===========================* : | v1 | : *===========================* __:_________________:___________________________:____________ layer2______________:___________________________:____________ *=====================* *=================* | a3 | | a4 | *=====================* *=================* : :...........................: *=================================* : | v2 | : *=====================*===========*===========* : | v3 | : *=======================* :.............................................: _____________________________________________________________ layer3_______________________________________________________ *===================* *===========* | v4 | | v5 | *===================* *===========* """ # can trim g2 to 12 even though it would cause a0 and a1 to fully # overlap because the snapping allows it to succeed self.assertEdit( g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 12, 11, [a3, v0, v2], [v4], { v0 : {"start": 11, "in-point": 11, "duration": 4}, a1 : {"start": 11, "in-point": 1, "duration": 4}, v1 : {"start": 11, "in-point": 1, "duration": 13}, a3 : {"start": 11, "in-point": 16, "duration": 1}, v2 : {"start": 11, "in-point": 10, "duration": 7}, g0 : {"start": 11, "duration": 9}, g1 : {"start": 11, "duration": 13}, g2 : {"start": 11, "duration": 13}, }, [], []) # trim end to use as a snapping point self.assertEdit( v5, 4, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 27, None, [], [], { v5 : {"duration": 9, "layer": 4}, }, [], []) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ .............g2............ : : :.......g0......... : : *===============* : : | a0 | : *=*=====*=========* : | a1 | : : *=======* : : *=======* : : | v0 | : : *=======* : : :.................: : ______________________:_________________________:____________ layer1________________:_________________________:____________ : : :.........g1..............: : *=================* : | a2 | : *=================* *=========================* | v1 | *=========================* ______________________:_________________________:____________ layer2________________:_________________________:____________ *=* *=================* a3| | a4 | *=* *=================* :.........................: *=============* : | v2 | : *=*===========*===========* : | v3 | : *=======================* :.........................: _____________________________________________________________ layer3_______________________________________________________ *===================* | v4 | *===================* _____________________________________________________________ layer4_______________________________________________________ *=================* | v5 | *=================* """ # trim end of g2 and move layer. Without the snap, would fail since # a2's duration-limit is 12. # Even elements not being trimmed will still move layer # a0 and a1 keep transition # v2 and v3 keep transition self.assertEdit( g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 29, 27, [a2, a4, v1, v3], [v5], { a2 : {"duration": 12, "layer": 2}, v1 : {"duration": 16, "layer": 2}, a4 : {"duration": 12, "layer": 3}, v3 : {"duration": 15, "layer": 3}, g1 : {"duration": 16, "layer": 2}, g2 : {"duration": 16, "layer": 1}, a0 : {"layer": 1}, a1 : {"layer": 1}, v0 : {"layer": 1}, a3 : {"layer": 3}, v2 : {"layer": 3}, g0 : {"layer": 1}, }, [], []) # trim start to use as a snapping point self.timeline.set_snapping_distance(0) self.assertEdit( v5, 4, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 19, None, [], [], { v5 : {"start": 19, "in-point": 1, "duration": 8}, }, [], []) self.timeline.set_snapping_distance(2) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ _____________________________________________________________ layer1_______________________________________________________ .............g2.................. : : :.......g0......... : : *===============* : : | a0 | : *=*=====*=========* : | a1 | : : *=======* : : *=======* : : | v0 | : : *=======* : : :.................: : ______________________:_______________________________:______ layer2________________:_______________________________:______ : : :.........g1....................: : *=======================* : | a2 | : *=======================* *===============================* | v1 | *===============================* ______________________:_______________________________:______ layer3________________:_______________________________:______ *=* *=======================* a3| | a4 | *=* *=======================* :...............................: *===================*=============* : | v4 | v2 | : *===================*=*===========*=================* : | v3 | : *=============================* :...............................: _____________________________________________________________ layer4_______________________________________________________ *===============* | v5 | *===============* """ # trim end of g2 and move layer. Trim at 17 would lead to # v3 being fully overlapped by v2, but snap to 19 makes it work self.assertEdit( g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 17, 19, [a2, a4, v1, v3], [v5], { a0 : {"duration": 7}, a2 : {"duration": 4}, v1 : {"duration": 8}, a4 : {"duration": 4}, v3 : {"duration": 7}, g0 : {"duration": 8}, g1 : {"duration": 8}, g2 : {"duration": 8}, }, [], []) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ _____________________________________________________________ layer1_______________________________________________________ ........g2....... : : :.......g0......: : *=============* : | a0 | *=*=====*=======* | a1 | : *=======* : *=======* : | v0 | : *=======* : :...............: ______________________:_______________:______________________ layer2________________:_______________:______________________ : : :.......g1......: : *=======* : | a2 | : *=======* *===============* | v1 | *===============* ______________________:_______________:______________________ layer3________________:_______________:______________________ *=* *=======* a3| | a4 | *=* *=======* :...............: *===================*=============* : | v4 | v2 | : *===================*=*===========*=* : | v3 | : *=============* :...............: _____________________________________________________________ layer4_______________________________________________________ *===============* | v5 | *===============* """ # can trim without trimming parent self.assertEdit( v0, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5, None, [], [], { v0 : {"start": 5, "in-point": 5, "duration": 10}, g0 : {"start": 5, "duration": 14}, g2 : {"start": 5, "duration": 14}, }, [], []) self.assertEdit( a2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 23, None, [], [], { a2 : {"duration": 8}, g1 : {"duration": 12}, g2 : {"duration": 18}, }, [], []) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ _____________________________________________________________ layer1_______________________________________________________ ...................g2................ : : :...........g0............... : : *=============* : : | a0 | : : *=*=====*=======* : : | a1 | : : : *=======* : : *===================* : : | v0 | : : *===================* : : :...........................: : __________:___________________________________:______________ layer2____:___________________________________:______________ : : : ............g1..........: : : *===============* : : | a2 | : : *===============* : *===============* : : | v1 | : : *===============* : __________:___________:_______________________:______________ layer3____:___________:_______________________:______________ : *=* *=======* : : a3| | a4 | : : *=* *=======* : : :.......................: *===================*=============* : | v4 | v2 | : *===================*=*===========*=* : : | v3 | : : *=============* : :...................................: _____________________________________________________________ layer4_______________________________________________________ *===============* | v5 | *===============* """ # same with group within a group self.assertEdit( g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 9, 11, [v0], [v1, v2, v4, a3], { v0 : {"start": 11, "in-point": 11, "duration": 4, "layer": 0}, a0 : {"layer": 0}, a1 : {"layer": 0}, g0 : {"start": 11, "duration": 8, "layer": 0}, g2 : {"start": 11, "duration": 12, "layer": 0}, }, [], []) self.assertEdit( g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 17, 18, [a0], [v2], { a0 : {"duration": 6}, g0 : {"duration": 7}, }, [], []) def test_roll(self): """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ *===============================* ] | c0 | ] *=============*=====*===================*=====*=============* ]>video | c1 | | c2 | ] *===================* *===================* ] ..........g0......... *===========* : ] | c3 | : ]>audio0 *===*=======*===* : ] : | c4 | : ] ] : *===*=======*===* ] : | c5 | ]>audio1 : *===========* ] ____________________:___________________:____________________ layer1______________:___________________:____________________ : : .............g3.....:.... : : *=================* : ] : | c12 | ....:...g4............... ] : *=================* : : : ] :........g1.........: : : :.........g2........: ]>audio0 *=============* : : : *=============* : ] | c8 | : : : | c10 | : ] *=============* : : *=========*=============* : ] : : : | c7 | : ] ] : *=========*=========* : ]>video : | c6 | : : : ] ] : *=============*=========* : : *=============* ] : | c9 |...:...........:...: | c11 | ] : *=============* : : : *=============* ] :...................: : : :...................: ]>audio1 :.......................: *=================* : ] | c13 | : ] *=================* : ] :.......................: """ video = self.add_video_track() audio0 = self.add_audio_track() audio1 = self.add_audio_track() c0 = self.add_clip("c0", 0, [video], 7, 16) c1 = self.add_clip("c1", 0, [video], 0, 10) c2 = self.add_clip("c2", 0, [video], 20, 10, 20) self.register_auto_transition(c1, c0, video) self.register_auto_transition(c0, c2, video) c3 = self.add_clip("c3", 0, [audio0], 10, 6, 2, 38) c4 = self.add_clip("c4", 0, [audio0, audio1], 12, 6, 15) self.register_auto_transition(c3, c4, audio0) c5 = self.add_clip("c5", 0, [audio1], 14, 6, 30, 38) self.register_auto_transition(c4, c5, audio1) c6 = self.add_clip("c6", 1, [audio1, video], 10, 5, 7) c7 = self.add_clip("c7", 1, [audio0, video], 15, 5, 1, 15) g0 = self.add_group("g0", [c3, c4, c5, c6, c7]) c8 = self.add_clip("c8", 1, [audio0], 0, 7, 3, 13) c9 = self.add_clip("c9", 1, [audio1], 3, 7) g1 = self.add_group("g1", [c8, c9]) c10 = self.add_clip("c10", 1, [audio0], 20, 7, 1) c11 = self.add_clip("c11", 1, [audio1], 23, 7, 3, 10) g2 = self.add_group("g2", [c10, c11]) c12 = self.add_clip("c12", 1, [audio0], 3, 9) self.register_auto_transition(c8, c12, audio0) g3 = self.add_group("g3", [g1, c12]) c13 = self.add_clip("c13", 1, [audio1], 18, 9) self.register_auto_transition(c13, c11, audio1) g4 = self.add_group("g4", [g2, c13]) self.assertTimelineConfig() # edit failures self.timeline.set_snapping_distance(2) # cannot roll c10 to 22, which snaps to 23, because it will # extend c5 beyond its duration limit of 8 self.assertFailEdit( c10, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22, GES.Error.NOT_ENOUGH_INTERNAL_CONTENT) # same with g2 self.assertFailEdit( g2, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22, GES.Error.NOT_ENOUGH_INTERNAL_CONTENT) # cannot roll end c9 to 8, which snaps to 7, because it would # cause c3's in-point to become negative self.assertFailEdit( c9, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8, GES.Error.NEGATIVE_TIME) # same with g1 self.assertFailEdit( g1, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8, GES.Error.NEGATIVE_TIME) # cannot roll c13 to 19, snap to 20, because it would cause # c4 to fully overlap c5 self.assertFailEdit( c13, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 19, GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot roll c12 to 11, snap to 10, because it would cause # c3 to fully overlap c4 self.assertFailEdit( c12, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 11, GES.Error.INVALID_OVERLAP_IN_TRACK) # give c6 a bit more allowed duration so we can focus on c9 self.assertTrue(c6.set_inpoint(10)) self.assertTimelineConfig({ c6 : {"in-point": 10}}) # cannot roll c6 to 0 because it would cause c9 to be trimmed # below its start self.assertFailEdit( c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 0, GES.Error.NEGATIVE_TIME) # set back self.assertTrue(c6.set_inpoint(7)) self.assertTimelineConfig({ c6 : {"in-point": 7}}) # give c7 a bit more allowed duration so we can focus on c10 self.assertTrue(c7.set_inpoint(0)) self.assertTimelineConfig({ c7 : {"in-point": 0}}) # cannot roll end c7 to 30 because it would cause c10 to be # trimmed beyond its end self.assertFailEdit( c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 30, GES.Error.NEGATIVE_TIME) # set back self.assertTrue(c7.set_inpoint(1)) self.assertTimelineConfig({ c7 : {"in-point": 1}}) # moving layer is not supported self.assertFailEdit( c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 7, None) self.assertFailEdit( c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 23, None) # successes self.timeline.set_snapping_distance(0) # c1 and g1 are trimmed at their end # NOTE: c12 is not trimmed even though it shares a group g3 # with g1 because g3 does not share the same edge # trim forward self.assertEdit( c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 11, None, [], [], { c6 : {"start": 11, "in-point": 8, "duration": 4}, c1 : {"duration": 11}, c9 : {"duration": 8}, g1 : {"duration": 11}, }, [], []) # and reset self.assertEdit( c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 10, None, [], [], { c6 : {"start": 10, "in-point": 7, "duration": 5}, c1 : {"duration": 10}, c9 : {"duration": 7}, g1 : {"duration": 10}, }, [], []) # same with g0 self.assertEdit( g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 11, None, [], [], { c6 : {"start": 11, "in-point": 8, "duration": 4}, c3 : {"start": 11, "in-point": 3, "duration": 5}, g0 : {"start": 11, "duration": 9}, c1 : {"duration": 11}, c9 : {"duration": 8}, g1 : {"duration": 11}, }, [], []) self.assertEdit( g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 10, None, [], [], { c6 : {"start": 10, "in-point": 7, "duration": 5}, c3 : {"start": 10, "in-point": 2, "duration": 6}, g0 : {"start": 10, "duration": 10}, c1 : {"duration": 10}, c9 : {"duration": 7}, g1 : {"duration": 10}, }, [], []) self.timeline.set_snapping_distance(1) # trim backward # NOTE: c9 has zero width, not considered overlapping with c6 # snapping allows the edit to succeed (in-point of c6 no longer # negative) # NOTE: c12 does not move, but c8 does because it is in the same # group as g1 # loose transitions self.assertEdit( c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 2, 3, [c6], [c12], { c6 : {"start": 3, "in-point": 0, "duration": 12}, g0 : {"start": 3, "duration": 17}, c1 : {"duration": 3}, c8 : {"duration": 3}, c9 : {"duration": 0}, g1 : {"duration": 3}, }, [], [(c1, c0, video), (c8, c12, audio0)]) # bring back # NOTE: no snapping to c3 start edge because it is part of the # element being edited, g0, even though it doesn't end up changing # gain back new transitions self.assertEdit( g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 10, None, [], [], { c6 : {"start": 10, "in-point": 7, "duration": 5}, g0 : {"start": 10, "duration": 10}, c1 : {"duration": 10}, c8 : {"duration": 10}, c9 : {"duration": 7}, g1 : {"duration": 10}, }, [(c1, c0, video), (c8, c12, audio0)], []) # same but with the end edge of g0 self.timeline.set_snapping_distance(0) self.assertEdit( c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 19, None, [], [], { c7 : {"duration": 4}, c2 : {"start": 19, "in-point": 19, "duration": 11}, c10 : {"start": 19, "in-point": 0, "duration": 8}, g2 : {"start": 19, "duration": 11}, }, [], []) self.assertEdit( c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 20, None, [], [], { c7 : {"duration": 5}, c2 : {"start": 20, "in-point": 20, "duration": 10}, c10 : {"start": 20, "in-point": 1, "duration": 7}, g2 : {"start": 20, "duration": 10}, }, [], []) # do same with g0 self.assertEdit( g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 19, None, [], [], { c7 : {"duration": 4}, c5 : {"duration": 5}, g0 : {"duration": 9}, c2 : {"start": 19, "in-point": 19, "duration": 11}, c10 : {"start": 19, "in-point": 0, "duration": 8}, g2 : {"start": 19, "duration": 11}, }, [], []) self.assertEdit( g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 20, None, [], [], { c7 : {"duration": 5}, c5 : {"duration": 6}, g0 : {"duration": 10}, c2 : {"start": 20, "in-point": 20, "duration": 10}, c10 : {"start": 20, "in-point": 1, "duration": 7}, g2 : {"start": 20, "duration": 10}, }, [], []) self.timeline.set_snapping_distance(1) # trim forwards # NOTE: c10 has zero width, not considered overlapping with c7 # snapping allows the edit to succeed (duration of c7 no longer # above its limit) # NOTE: c12 does not move, but c11 does because it is in the same # group as g2 self.assertEdit( c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 28, 27, [c7], [c13], { c7 : {"duration": 12}, g0 : {"duration": 17}, c2 : {"start": 27, "in-point": 27, "duration": 3}, c10 : {"start": 27, "in-point": 8, "duration": 0}, c11 : {"start": 27, "in-point": 7, "duration": 3}, g2 : {"start": 27, "duration": 3}, }, [], [(c0, c2, video), (c13, c11, audio1)]) # bring back using g0 # NOTE: no snapping to c5 end edge because it is part of the # element being edited, g0, even though it doesn't end up changing self.assertEdit( g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 20, None, [], [], { c7 : {"duration": 5}, g0 : {"duration": 10}, c2 : {"start": 20, "in-point": 20, "duration": 10}, c10 : {"start": 20, "in-point": 1, "duration": 7}, c11 : {"start": 20, "in-point": 0, "duration": 10}, g2 : {"start": 20, "duration": 10}, }, [(c0, c2, video), (c13, c11, audio1)], []) # adjust c0 for snapping # doesn't move anything else self.assertEdit( c0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 8, None, [], [], { c0 : {"start": 8, "in-point": 1, "duration": 15}, }, [], []) self.assertEdit( c0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 22, None, [], [], { c0 : {"duration": 14}, }, [], []) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ *===========================* ] | c0 | ] *===============*===*===================*===*===============* ]>video | c1 | | c2 | ] *===================* *===================* ] ..........g0......... *===========* : ] | c3 | : ]>audio0 *===*=======*===* : ] : | c4 | : ] ] : *===*=======*===* ] : | c5 | ]>audio1 : *===========* ] ____________________:___________________:____________________ layer1______________:___________________:____________________ : : .............g3.....:.... : : *=================* : ] : | c12 | ....:...g4............... ] : *=================* : : : ] :........g1.........: : : :.........g2........: ]>audio0 *===================* : : *=============* : ] | c8 | : : | c10 | : ] *===================* : *=========*=============* : ] : : : | c7 | : ] ] : *=========*=========* : ]>video : | c6 | : : : ] ] : *=============*=========* : *===================* ] : | c9 |...:...........:...| c11 | ] : *=============* : : *===================* ] :...................: : : :...................: ]>audio1 :.......................: *=================* : ] | c13 | : ] *=================* : ] :.......................: """ # rolling only moves an element if it contains a source that # touches the rolling edge. For a group, any source below it # at the corresponding edge counts, we also prefer trimming the # whole group over just one of its childrens. # As such, when rolling the end of c5, c11 shares the audio1 # track and starts when c5 ends, so is set to be trimmed. But it # is also at the start edge of its parent g2, so g2 is set to be # trimmed. However, it is not at the start of g4, so g4 is not # set to be trimmed. As such, c10 will also move, even though it # does not share a track. c2, on the other hand, will not move # NOTE: snapping helps keep c5's duration below its limit (8) self.assertEdit( c5, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 23, 22, [c5], [c0], { c5 : {"duration": 8}, g0 : {"duration": 12}, c11 : {"start": 22, "in-point": 2, "duration": 8}, c10 : {"start": 22, "in-point": 3, "duration": 5}, g2 : {"start": 22, "duration": 8}, }, [], []) # same with c3 at its start edge self.assertEdit( c3, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 7, 8, [c3], [c0], { c3 : {"start": 8, "in-point": 0, "duration": 8}, g0 : {"start": 8, "duration": 14}, c8 : {"duration": 8}, c9 : {"duration": 5}, g1 : {"duration": 8}, }, [], []) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ *===========================* ] | c0 | ] *===============*===*===================*===*===============* ]>video | c1 | | c2 | ] *===================* *===================* ] ..............g0............. *===============* : ] | c3 | : ]>audio0 *=======*=======*===* : ] : | c4 | : ] ] : *===*=======*=======* ] : | c5 | ]>audio1 : *===============* ] ________________:___________________________:________________ layer1__________:___________________________:________________ : : .............g3.:........ : : *=================* : ] : | c12 | ........:.g4............. ] : *=================* : : : ] :........g1.....: : : :.....g2........: ]>audio0 *===============* : : *=========* : ] | c8 | : : | c10 | : ] *===============* : *=========* *=========* : ] : : : | c7 | : : ] ] : : *=========*=========* : : ]>video : : | c6 | : : : ] ] : *=========* *=========* : *===============* ] : | c9 |.......:...........:.......| c11 | ] : *=========* : : *===============* ] :...............: : : :...............: ]>audio1 :.......................: *=================* : ] | c13 | : ] *=================* : ] :.......................: """ # rolling end of c1 only moves c6, similarly with c2 and c7 self.assertEdit( c1, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8, 8, [c1], [c9, c8, c3, c0], { c1 : {"duration": 8}, c6 : {"start": 8, "in-point": 5, "duration": 7}, }, [], [(c1, c0, video)]) self.assertEdit( c2, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22, 22, [c2], [c0, c5, c10, c11], { c2 : {"start": 22, "in-point": 22, "duration": 8}, c7 : {"duration": 7}, }, [], [(c0, c2, video)]) # move c3 end edge out the way self.timeline.set_snapping_distance(0) self.assertEdit( c3, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 17, None, [], [], { c3: {"duration": 9}, }, [], []) self.timeline.set_snapping_distance(2) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ *===========================* ] | c0 | ] *===============*===========================*===============* ]>video | c1 | | c2 | ] *===============* *===============* ] ..............g0............. *=================* : ] | c3 | : ]>audio0 *=======*=========*=* : ] : | c4 | : ] ] : *===*=======*=======* ] : | c5 | ]>audio1 : *===============* ] ________________:___________________________:________________ layer1__________:___________________________:________________ : : .............g3.:........ : : *=================* : ] : | c12 | ........:.g4............. ] : *=================* : : : ] :........g1.....: : : :.....g2........: ]>audio0 *===============* : : *=========* : ] | c8 | : : | c10 | : ] *===============* : *=============*=========* : ] : : : | c7 | : ] ] : *=============*=============* : ]>video : | c6 | : : : ] ] : *=========*=============* : *===============* ] : | c9 |.......:...........:.......| c11 | ] : *=========* : : *===============* ] :...............: : : :...............: ]>audio1 :.......................: *=================* : ] | c13 | : ] *=================* : ] :.......................: """ # can safely roll within a group # NOTE: we do not snap to an edge used in the edit self.assertEdit( c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 15, 14, [c6], [c5], { c6: {"duration": 6}, c7: {"start": 14, "in-point": 0, "duration": 8}, }, [], []) self.assertEdit( c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 16, 17, [c7], [c3], { c6: {"duration": 9}, c7: {"start": 17, "in-point": 3, "duration": 5}, }, [], []) def test_snap_from_negative(self): track = self.add_video_track() c0 = self.add_clip("c0", 0, [track], 0, 20) c1 = self.add_clip("c1", 0, [track], 100, 10) g1 = self.add_group("g0", [c0, c1]) snap_to = self.add_clip("snap-to", 2, [track], 4, 50) self.assertTimelineConfig() self.timeline.set_snapping_distance(9) # move without snap would make start edge of c0 go to -5, but this # edge snaps to the start edge of snap_to, allowing the edit to # succeed self.assertEdit( c1, 1, GES.EditMode.NORMAL, GES.Edge.NONE, 95, 4, [c0], [snap_to], { c0 : {"start": 4, "layer": 1}, c1 : {"start": 104, "layer": 1}, g1 : {"start": 4, "layer": 1}, }, [], []) def test_move_layer(self): """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ *===================* | c1 | *===================* ..............g2............... : : :.............g0..............: *=============================* | c0 | *=============================* : : : : __________:_____________________________:____________________ layer1____:_____________________________:____________________ : : *===================* : | c2 | : *===================* : :.............................: : ...............g1...:.......... : *=============================* : | c3 | : *=============================* __________:_________:_____________________________:__________ layer2____:_________:_____________________________:__________ : : : : : *===================* : : | c5 | : :.........*===================* *===================* : | c4 | : *===================* : :.............................: *===================* | c6 | *===================* """ track = self.add_video_track() c0 = self.add_clip("c0", 0, [track], 5, 15) c1 = self.add_clip("c1", 0, [track], 15, 10) self.register_auto_transition(c0, c1, track) c2 = self.add_clip("c2", 1, [track], 5, 10) c3 = self.add_clip("c3", 1, [track], 10, 15) self.register_auto_transition(c2, c3, track) c4 = self.add_clip("c4", 2, [track], 5, 10) c5 = self.add_clip("c5", 2, [track], 15, 10) c6 = self.add_clip("c6", 2, [track], 10, 10) self.register_auto_transition(c4, c6, track) self.register_auto_transition(c6, c5, track) g0 = self.add_group("g0", [c0, c2]) g1 = self.add_group("g1", [c3, c5]) g2 = self.add_group("g2", [g0, c4]) self.assertTimelineConfig() layer = self.timeline.get_layer(0) self.assertIsNotNone(layer) # don't loose auto-transitions # clips stay in their layer (groups do not move them) self.timeline.move_layer(layer, 2) self.assertTimelineConfig( { c0 : {"layer": 2}, c1 : {"layer": 2}, c2 : {"layer": 0}, c3 : {"layer": 0}, c4 : {"layer": 1}, c5 : {"layer": 1}, c6 : {"layer": 1}, g1 : {"layer": 0}, }) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ ..............g2............... : : :.............g0..............: *===================* : | c2 | : *===================* : : ..........................: : : ...............g1.............. : : *=============================* : : | c3 | : : *=============================* __________:___:_____:_____________________________:__________ layer1____:___:_____:_____________________________:__________ : : : : : : : *===================* : : : | c5 | : : : *===================* : : :.............................: : :..........g2..... : g0 : : : : *===================*: | c4 |: *===================*: : :................: : : *===================* : : | c6 | : : *===================* __________:___:______________________________________________ layer2____:___:______________________________________________ : :.......................... *=============================* | c0 | *=============================* :.............................: :.............................: *===================* | c1 | *===================* """ layer = self.timeline.get_layer(1) self.assertIsNotNone(layer) self.timeline.move_layer(layer, 0) self.assertTimelineConfig( { c2 : {"layer": 1}, c3 : {"layer": 1}, c4 : {"layer": 0}, c5 : {"layer": 0}, c6 : {"layer": 0}, g0 : {"layer": 1}, }) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ *===================* | c6 | *===================* ..............g2............... *===================* : | c4 | : *===================* : : ...............g1...:.......... : : *===================* : : | c5 | : : *===================* __________:_________:___________________:_________:__________ layer1____:_________:___________________:_________:__________ : : : : : *=============================* : | c3 | : *=============================* : :...................:.........: :.............g0..............: *===================* : | c2 | : *===================* : :.............................: __________:_____________________________:____________________ layer2____:_____________________________:____________________ : : *=============================* | c0 | *=============================* :.............................: :.............................: *===================* | c1 | *===================* """ self.timeline.append_layer() layer = self.timeline.get_layer(3) self.assertIsNotNone(layer) self.timeline.move_layer(layer, 1) self.assertTimelineConfig( { c0 : {"layer": 3}, c1 : {"layer": 3}, c2 : {"layer": 2}, c3 : {"layer": 2}, g0 : {"layer": 2}, }) layer = self.timeline.get_layer(3) self.assertIsNotNone(layer) self.timeline.move_layer(layer, 0) self.assertTimelineConfig( { c0 : {"layer": 0}, c1 : {"layer": 0}, c2 : {"layer": 3}, c3 : {"layer": 3}, c4 : {"layer": 1}, c5 : {"layer": 1}, c6 : {"layer": 1}, g0 : {"layer": 0}, g1 : {"layer": 1}, }) """ , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . , 0 5 10 15 20 25 30 _____________________________________________________________ layer0_______________________________________________________ *===================* | c1 | *===================* ..............g2............... :.............g0..............: *=============================* | c0 | *=============================* : ..........................: __________:___:______________________________________________ layer1____:___:______________________________________________ : : : :.......g2....... : g0 : : : : *===================* | c4 | *===================* : :...............: : : *===================* : : | c6 | : : *===================* : : ...............g1.............. : : : *===================* : : : | c5 | : : : *===================* __________:___:_____:_____________________________:__________ layer2____:___:_____:_____________________________:__________ __________:___:_____:_____________________________:__________ layer3____:___:_____:_____________________________:__________ : : : : : : *=============================* : : | c3 | : : *=============================* : : :.............................: : :.......................... *===================* : | c2 | : *===================* : :.............................: :.............................: """ layer = self.timeline.get_layer(1) self.assertTrue(self.timeline.remove_layer(layer)) # TODO: add tests when removing layers: # FIXME: groups should probably loose their children when they # are removed from the timeline, which would change g1's # priority, but currently c5 remains in the group with priority # of the removed layer def test_not_snappable(self): track = self.add_video_track() c0 = self.add_clip("c0", 0, [track], 0, 10) no_source = self.add_clip( "no-source", 0, [], 5, 10, effects=[GES.Effect.new("agingtv")]) effect_clip = self.add_clip( "effect-clip", 0, [track], 5, 10, clip_type=GES.EffectClip, asset_id="agingtv || audioecho") text = self.add_clip( "text-clip", 0, [track], 5, 10, clip_type=GES.TextOverlayClip) self.assertTimelineConfig() self.timeline.set_snapping_distance(20) self.assertEdit( c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 8, None, [], [], {c0 : {"start": 8}}, [], []) self.assertEdit( c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 5, None, [], [], {c0 : {"start": 5}}, [], []) self.assertEdit( c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 8, None, [], [], {c0 : {"duration": 3}}, [], []) c1 = self.add_clip("c1", 0, [track], 30, 3) self.assertTimelineConfig() # end edge snaps to start of c1 self.assertEdit( c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10, 30, [c0], [c1], {c0 : {"start": 27}}, [], []) class TestTransitions(common.GESSimpleTimelineTest): def test_emission_order_for_transition_clip_added_signal(self): self.timeline.props.auto_transition = True unused_clip1 = self.add_clip(0, 0, 100) clip2 = self.add_clip(100, 0, 100) # Connect to signals to track in which order they are emitted. signals = [] def clip_added_cb(layer, clip): self.assertIsInstance(clip, GES.TransitionClip) signals.append("clip-added") self.layer.connect("clip-added", clip_added_cb) def property_changed_cb(clip, pspec): self.assertEqual(clip, clip2) self.assertEqual(pspec.name, "start") signals.append("notify::start") clip2.connect("notify::start", property_changed_cb) # Move clip2 to create a transition with clip1. clip2.edit([], self.layer.get_priority(), GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 50) # The clip-added signal is emitted twice, once for the video # transition and once for the audio transition. self.assertEqual( signals, ["notify::start", "clip-added", "clip-added"]) def create_xges(self): uri = common.get_asset_uri("png.png") return """ """ % {"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() def loaded_cb(unused_project, unused_timeline): mainloop.quit() project.connect("loaded", loaded_cb) mainloop.run() layers = timeline.get_layers() self.assertEqual(len(layers), 1) self.assertTrue(layers[0].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() def loaded_cb(unused_project, unused_timeline): mainloop.quit() project.connect("loaded", loaded_cb) mainloop.run() layers = timeline.get_layers() self.assertEqual(len(layers), 1) 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) class TestPriorities(common.GESSimpleTimelineTest): def test_clips_priorities(self): clip = self.add_clip(0, 0, 100) clip1 = self.add_clip(100, 0, 100) self.timeline.commit() self.assertLess(clip.props.priority, clip1.props.priority) clip.props.start = 101 self.timeline.commit() self.assertGreater(clip.props.priority, clip1.props.priority) class TestTimelineElement(common.GESSimpleTimelineTest): def test_set_child_property(self): clip = self.add_clip(0, 0, 100) source = clip.find_track_element(None, GES.VideoSource) self.assertTrue(source.set_child_property("height", 5)) self.assertEqual(clip.get_child_property("height"), (True, 5))