| # -*- coding: utf-8 -*- | |
| # | |
| # Copyright (c) 2016 Alexandru Băluț <alexandru.balut@gmail.com> | |
| # | |
| # 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}}, [], []) | |
| def test_disable_timeline_editing_apis(self): | |
| track = self.add_video_track() | |
| self.assertEqual(self.timeline.props.auto_transition, True) | |
| self.timeline.disable_edit_apis(True) | |
| self.assertEqual(self.timeline.props.auto_transition, False) | |
| c0 = self.add_clip("c0", 0, [track], 0, 10) | |
| # Without disabling edit API adding clip would fail | |
| c1 = self.add_clip("c1", 0, [track], 0, 10) | |
| self.assertTimelineConfig() | |
| c1.set_start(1) | |
| c1.set_duration(1) | |
| self.assertEqual(c1.get_start(), 1) | |
| self.assertEqual(c1.get_duration(), 1) | |
| 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 """<ges version='0.4'> | |
| <project properties='properties;' metadatas='metadatas, author=(string)"", render-scale=(double)100, format-version=(string)0.4;'> | |
| <ressources> | |
| <asset id='GESTitleClip' extractable-type-name='GESTitleClip' properties='properties;' metadatas='metadatas;' /> | |
| <asset id='bar-wipe-lr' extractable-type-name='GESTransitionClip' properties='properties;' metadatas='metadatas, description=(string)GES_VIDEO_STANDARD_TRANSITION_TYPE_BAR_WIPE_LR;' /> | |
| <asset id='%(uri)s' extractable-type-name='GESUriClip' properties='properties, supported-formats=(int)4, duration=(guint64)18446744073709551615;' metadatas='metadatas, video-codec=(string)PNG, file-size=(guint64)73294;' /> | |
| <asset id='crossfade' extractable-type-name='GESTransitionClip' properties='properties;' metadatas='metadatas, description=(string)GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE;' /> | |
| </ressources> | |
| <timeline properties='properties, auto-transition=(boolean)true, snapping-distance=(guint64)31710871;' metadatas='metadatas, duration=(guint64)13929667032;'> | |
| <track caps='video/x-raw(ANY)' track-type='4' track-id='0' properties='properties, async-handling=(boolean)false, message-forward=(boolean)true, caps=(string)"video/x-raw\(ANY\)", restriction-caps=(string)"video/x-raw\,\ width\=\(int\)1920\,\ height\=\(int\)1080\,\ framerate\=\(fraction\)30/1", mixing=(boolean)true;' metadatas='metadatas;'/> | |
| <track caps='audio/x-raw(ANY)' track-type='2' track-id='1' properties='properties, async-handling=(boolean)false, message-forward=(boolean)true, caps=(string)"audio/x-raw\(ANY\)", restriction-caps=(string)"audio/x-raw\,\ format\=\(string\)S32LE\,\ channels\=\(int\)2\,\ rate\=\(int\)44100\,\ layout\=\(string\)interleaved", mixing=(boolean)true;' metadatas='metadatas;'/> | |
| <layer priority='0' properties='properties, auto-transition=(boolean)true;' metadatas='metadatas, volume=(float)1;'> | |
| <clip id='0' asset-id='%(uri)s' type-name='GESUriClip' layer-priority='0' track-types='6' start='0' duration='4558919302' inpoint='0' rate='0' properties='properties, name=(string)uriclip25263, mute=(boolean)false, is-image=(boolean)false;' /> | |
| <clip id='1' asset-id='bar-wipe-lr' type-name='GESTransitionClip' layer-priority='0' track-types='4' start='3225722559' duration='1333196743' inpoint='0' rate='0' properties='properties, name=(string)transitionclip84;' children-properties='properties, GESVideoTransition::border=(uint)0, GESVideoTransition::invert=(boolean)false;'/> | |
| <clip id='2' asset-id='%(uri)s' type-name='GESUriClip' layer-priority='0' track-types='6' start='3225722559' duration='3479110239' inpoint='4558919302' rate='0' properties='properties, name=(string)uriclip25265, mute=(boolean)false, is-image=(boolean)false;' /> | |
| </layer> | |
| <groups> | |
| </groups> | |
| </timeline> | |
| </project> | |
| </ges>""" % {"uri": uri} | |
| def test_auto_transition(self): | |
| xges = self.create_xges() | |
| with common.created_project_file(xges) as proj_uri: | |
| project = GES.Project.new(proj_uri) | |
| timeline = project.extract() | |
| mainloop = common.create_main_loop() | |
| 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)) |