mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-12 19:36:38 +00:00
712bda84db
There are cases where user might want to be in full control of the timeline and not be limited by the checks that are being done by GES to go from one timeline layout to another, this should be doable as it is a valid use case. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3501>
3787 lines
160 KiB
Python
3787 lines
160 KiB
Python
# -*- 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))
|