gstreamer/tests/check/python/test_timeline.py
Henry Wilkes 142456d8ba errors: added edit errors
Added more errors to GES_ERROR for when edits fail (other than
programming or usage errors). Also promoted some GST messages if they
related to a usage error.

Also added explanation of timeline overlap rules in user docs.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
2020-05-22 19:15:57 +01:00

3670 lines
156 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),
]
])
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)
Gst.error('%s' % 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)
Gst.error('%s' % position)
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([], -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([], -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_ripple_end(self):
clip = self.append_clip()
clip.set_max_duration(20)
self.append_clip().set_max_duration(10)
self.append_clip().set_max_duration(10)
self.print_timeline()
self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 20))
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 0, 20),
(GES.TestClip, 20, 10),
(GES.TestClip, 30, 10),
]
])
self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 15))
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 0, 15),
(GES.TestClip, 15, 10),
(GES.TestClip, 25, 10),
]
])
def test_move_group_full_overlap(self):
self.track_types = [GES.TrackType.AUDIO]
super().setUp()
for _ in range(4):
self.append_clip()
clips = self.layer.get_clips()
self.assertTrue(clips[0].ripple(20))
self.assertTimelineTopology([
[
(GES.TestClip, 20, 10),
(GES.TestClip, 30, 10),
(GES.TestClip, 40, 10),
(GES.TestClip, 50, 10),
]
])
group = GES.Container.group(clips[1:])
self.print_timeline()
self.assertFalse(group.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0))
self.print_timeline()
self.assertTimelineTopology([
[
(GES.TestClip, 20, 10),
(GES.TestClip, 30, 10),
(GES.TestClip, 40, 10),
(GES.TestClip, 50, 10),
]
])
self.assertFalse(clips[1].edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0))
self.print_timeline()
self.assertTimelineTopology([
[
(GES.TestClip, 20, 10),
(GES.TestClip, 30, 10),
(GES.TestClip, 40, 10),
(GES.TestClip, 50, 10),
]
])
def test_trim_inside_group(self):
self.track_types = [GES.TrackType.AUDIO]
super().setUp()
for _ in range(2):
self.append_clip()
clips = self.layer.get_clips()
group = GES.Container.group(clips)
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
]
])
self.assertEqual(group.props.start, 0)
self.assertEqual(group.props.duration, 20)
clips[0].trim(5)
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 5, 5),
(GES.TestClip, 10, 10),
]
])
self.assertEqual(group.props.start, 5)
self.assertEqual(group.props.duration, 15)
group1 = GES.Group.new ()
group1.add(group)
clips[0].trim(0)
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
]
])
self.assertEqual(group.props.start, 0)
self.assertEqual(group.props.duration, 20)
self.assertEqual(group1.props.start, 0)
self.assertEqual(group1.props.duration, 20)
self.assertTrue(clips[1].edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15))
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 5),
]
])
self.assertEqual(group.props.start, 0)
self.assertEqual(group.props.duration, 15)
self.assertEqual(group1.props.start, 0)
self.assertEqual(group1.props.duration, 15)
def test_trim_end_past_max_duration(self):
clip = self.append_clip()
max_duration = clip.props.duration
clip.set_max_duration(max_duration)
self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5))
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 5, 5),
]
])
self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15))
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 5, 5),
]
])
def test_illegal_effect_move(self):
c0 = self.append_clip()
self.append_clip()
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
]
])
effect = GES.Effect.new("agingtv")
c0.add(effect)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
]
])
self.assertFalse(effect.set_start(10))
self.assertEqual(effect.props.start, 0)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
]
])
self.assertFalse(effect.set_duration(20))
self.assertEqual(effect.props.duration, 10)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
]
])
def test_moving_overlay_clip_in_group(self):
c0 = self.append_clip()
overlay = self.append_clip(asset_type=GES.TextOverlayClip)
group = GES.Group.new()
group.add(c0)
group.add(overlay)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TextOverlayClip, 10, 10),
]
], groups=[(c0, overlay)])
self.assertTrue(overlay.set_start(20))
self.assertTimelineTopology([
[
(GES.TestClip, 10, 10),
(GES.TextOverlayClip, 20, 10),
]
], groups=[(c0, overlay)])
def test_moving_group_in_group(self):
c0 = self.append_clip()
overlay = self.append_clip(asset_type=GES.TextOverlayClip)
group0 = GES.Group.new()
group0.add(c0)
group0.add(overlay)
c1 = self.append_clip()
group1 = GES.Group.new()
group1.add(group0)
group1.add(c1)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TextOverlayClip, 10, 10),
(GES.TestClip, 20, 10),
]
], groups=[(c1, group0), (c0, overlay)])
self.assertTrue(group0.set_start(10))
self.assertTimelineTopology([
[
(GES.TestClip, 10, 10),
(GES.TextOverlayClip, 20, 10),
(GES.TestClip, 30, 10),
]
], groups=[(c1, group0), (c0, overlay)])
self.check_element_values(group0, 10, 0, 20)
self.check_element_values(group1, 10, 0, 30)
def test_illegal_group_child_move(self):
clip0 = self.append_clip()
_clip1 = self.add_clip(20, 0, 10)
overlay = self.add_clip(20, 0, 10, asset_type=GES.TextOverlayClip)
group = GES.Group.new()
group.add(clip0)
group.add(overlay)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TextOverlayClip, 20, 10),
(GES.TestClip, 20, 10),
]
], groups=[(clip0, overlay),])
# Can't move as clip0 and clip1 would fully overlap
self.assertFalse(overlay.set_start(40))
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TextOverlayClip, 20, 10),
(GES.TestClip, 20, 10),
]
], groups=[(clip0, overlay)])
def test_child_duration_change(self):
c0 = self.append_clip()
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
]
])
self.assertTrue(c0.set_duration(40))
self.assertTimelineTopology([
[
(GES.TestClip, 0, 40),
]
])
c0.children[0].set_duration(10)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
]
])
self.assertTrue(c0.set_start(40))
self.assertTimelineTopology([
[
(GES.TestClip, 40, 10),
]
])
c0.children[0].set_start(10)
self.assertTimelineTopology([
[
(GES.TestClip, 10, 10),
]
])
class TestInvalidOverlaps(common.GESSimpleTimelineTest):
def test_adding_or_moving(self):
clip1 = self.add_clip(start=10, in_point=0, duration=3)
self.assertIsNotNone(clip1)
def check_add_move_clip(start, duration):
self.timeline.props.auto_transition = True
self.layer.props.auto_transition = True
clip2 = GES.TestClip()
clip2.props.start = start
clip2.props.duration = duration
self.assertFalse(self.layer.add_clip(clip2))
self.assertEqual(len(self.layer.get_clips()), 1)
# Add the clip at a different position.
clip2.props.start = 25
self.assertTrue(self.layer.add_clip(clip2))
self.assertEqual(clip2.props.start, 25)
# Try to move the second clip by editing it.
self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, start))
self.assertEqual(clip2.props.start, 25)
# Try to put it in a group and move the group.
clip3 = GES.TestClip()
clip3.props.start = 20
clip3.props.duration = 1
self.assertTrue(self.layer.add_clip(clip3))
group = GES.Container.group([clip3, clip2])
self.assertTrue(group.props.start, 20)
self.assertFalse(group.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, start - 5))
self.assertEqual(group.props.start, 20)
self.assertEqual(clip3.props.start, 20)
self.assertEqual(clip2.props.start, 25)
for clip in group.ungroup(False):
self.assertTrue(self.layer.remove_clip(clip))
# clip1 contains...
check_add_move_clip(start=10, duration=1)
check_add_move_clip(start=11, duration=1)
check_add_move_clip(start=12, duration=1)
def test_splitting(self):
clip1 = self.add_clip(start=9, in_point=0, duration=3)
clip2 = self.add_clip(start=10, in_point=0, duration=4)
clip3 = self.add_clip(start=12, in_point=0, duration=3)
self.assertIsNone(clip1.split_full(13))
self.assertIsNone(clip1.split_full(8))
self.assertIsNone(clip3.split_full(12))
self.assertIsNone(clip3.split_full(15))
def _fail_split(self, clip, position):
split = None
error = None
try:
split = clip.split_full(position)
except GLib.Error as err:
error = err
self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK)
self.assertIsNone(split)
def test_split_with_transition(self):
self.track_types = [GES.TrackType.AUDIO]
super().setUp()
self.timeline.set_auto_transition(True)
clip0 = self.add_clip(start=0, in_point=0, duration=50)
clip1 = self.add_clip(start=20, in_point=0, duration=50)
clip2 = self.add_clip(start=60, in_point=0, duration=20)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 50),
(GES.TransitionClip, 20, 30),
(GES.TestClip, 20, 50),
(GES.TransitionClip, 60, 10),
(GES.TestClip, 60, 20),
]
])
# Split should fail as the first part of the split
# would be fully overlapping clip0
self._fail_split(clip1, 40)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 50),
(GES.TransitionClip, 20, 30),
(GES.TestClip, 20, 50),
(GES.TransitionClip, 60, 10),
(GES.TestClip, 60, 20),
]
])
# same with end of the clip
self._fail_split(clip1, 65)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 50),
(GES.TransitionClip, 20, 30),
(GES.TestClip, 20, 50),
(GES.TransitionClip, 60, 10),
(GES.TestClip, 60, 20),
]
])
def test_changing_duration(self):
clip1 = self.add_clip(start=9, in_point=0, duration=2)
clip2 = self.add_clip(start=10, in_point=0, duration=2)
self.assertFalse(clip1.set_start(10))
self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, clip2.props.start + clip2.props.duration))
self.assertFalse(clip1.ripple_end(clip2.props.start + clip2.props.duration))
self.assertFalse(clip1.roll_end(clip2.props.start + clip2.props.duration))
# clip2's end edge to the left, to decrease its duration.
self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, clip1.props.start + clip1.props.duration))
self.assertFalse(clip2.ripple_end(clip1.props.start + clip1.props.duration))
self.assertFalse(clip2.roll_end(clip1.props.start + clip1.props.duration))
# clip2's start edge to the left, to increase its duration.
self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, clip1.props.start))
self.assertFalse(clip2.trim(clip1.props.start))
# clip1's start edge to the right, to decrease its duration.
self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, clip2.props.start))
self.assertFalse(clip1.trim(clip2.props.start))
def test_rippling_backward(self):
self.track_types = [GES.TrackType.AUDIO]
super().setUp()
self.maxDiff = None
for i in range(4):
self.append_clip()
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
(GES.TestClip, 20, 10),
(GES.TestClip, 30, 10),
]
])
clip = self.layer.get_clips()[2]
self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip.props.start - 20))
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
(GES.TestClip, 20, 10),
(GES.TestClip, 30, 10),
]
])
self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip.props.start + 10))
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
(GES.TestClip, 30, 10),
(GES.TestClip, 40, 10),
]
])
self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip.props.start -20))
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
(GES.TestClip, 30, 10),
(GES.TestClip, 40, 10),
]
])
def test_rolling(self):
clip1 = self.add_clip(start=9, in_point=0, duration=2)
clip2 = self.add_clip(start=10, in_point=0, duration=2)
clip3 = self.add_clip(start=11, in_point=0, duration=2)
# Rolling clip1's end -1 would lead to clip3 to overlap 100% with clip2.
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 9, 2),
(GES.TestClip, 10, 2),
(GES.TestClip, 11, 2)
]
])
self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, clip1.props.start + clip1.props.duration - 1))
self.assertFalse(clip1.roll_end(13))
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 9, 2),
(GES.TestClip, 10, 2),
(GES.TestClip, 11, 2)
]
])
# Rolling clip3's start +1 would lead to clip1 to overlap 100% with clip2.
self.assertFalse(clip3.edit([], -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 12))
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 9, 2),
(GES.TestClip, 10, 2),
(GES.TestClip, 11, 2)
]
])
def test_layers(self):
self.track_types = [GES.TrackType.AUDIO]
super().setUp()
self.maxDiff = None
self.timeline.append_layer()
for i in range(2):
self.append_clip()
self.append_clip(1)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
],
[
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
]
])
clip = self.layer.get_clips()[0]
self.assertFalse(clip.edit([], 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0))
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
],
[
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
]
])
def test_rippling(self):
self.timeline.remove_track(self.timeline.get_tracks()[0])
clip1 = self.add_clip(start=9, in_point=0, duration=2)
clip2 = self.add_clip(start=10, in_point=0, duration=2)
clip3 = self.add_clip(start=11, in_point=0, duration=2)
# Rippling clip2's start -2 would bring clip3 exactly on top of clip1.
self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 8))
self.assertFalse(clip2.ripple(8))
# Rippling clip1's end -1 would bring clip3 exactly on top of clip2.
self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 8))
self.assertFalse(clip1.ripple_end(8))
def test_move_group_to_layer(self):
self.track_types = [GES.TrackType.AUDIO]
super().setUp()
self.append_clip()
self.append_clip()
self.append_clip()
clips = self.layer.get_clips()
clips[1].props.start += 2
group = GES.Container.group(clips[1:])
self.assertTrue(clips[1].edit([], 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE,
group.props.start))
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
],
[
(GES.TestClip, 12, 10),
(GES.TestClip, 20, 10),
]
])
clips[0].props.start = 15
self.assertTimelineTopology([
[
(GES.TestClip, 15, 10),
],
[
(GES.TestClip, 12, 10),
(GES.TestClip, 20, 10),
]
])
self.assertFalse(clips[1].edit([], 0, GES.EditMode.EDIT_NORMAL,
GES.Edge.EDGE_NONE, group.props.start))
self.assertTimelineTopology([
[
(GES.TestClip, 15, 10),
],
[
(GES.TestClip, 12, 10),
(GES.TestClip, 20, 10),
]
])
def test_copy_paste_overlapping(self):
self.track_types = [GES.TrackType.AUDIO]
super().setUp()
clip = self.append_clip()
copy = clip.copy(True)
self.assertIsNone(copy.paste(copy.props.start))
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
]
])
copy = clip.copy(True)
pasted = copy.paste(copy.props.start + 1)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TestClip, 1, 10),
]
])
pasted.move_to_layer(self.timeline.append_layer())
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
],
[
(GES.TestClip, 1, 10),
]
])
copy = pasted.copy(True)
self.assertIsNotNone(copy.paste(pasted.props.start - 1))
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
],
[
(GES.TestClip, 0, 10),
(GES.TestClip, 1, 10),
],
])
group = GES.Group.new()
group.add(clip)
copied_group = group.copy(True)
self.assertFalse(copied_group.paste(group.props.start))
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
],
[
(GES.TestClip, 0, 10),
(GES.TestClip, 1, 10),
],
])
def test_move_group_with_overlaping_clips(self):
self.track_types = [GES.TrackType.AUDIO]
super().setUp()
self.append_clip()
self.append_clip()
self.append_clip()
self.timeline.props.auto_transition = True
clips = self.layer.get_clips()
clips[1].props.start += 5
group = GES.Container.group(clips[1:])
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TestClip, 15, 10),
(GES.TransitionClip, 20, 5),
(GES.TestClip, 20, 10),
]
])
clips[0].props.start = 30
self.assertTimelineTopology([
[
(GES.TestClip, 15, 10),
(GES.TransitionClip, 20, 5),
(GES.TestClip, 20, 10),
(GES.TestClip, 30, 10),
]
])
# the 3 clips would overlap
self.assertFalse(clips[1].edit([], 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 25))
self.assertTimelineTopology([
[
(GES.TestClip, 15, 10),
(GES.TransitionClip, 20, 5),
(GES.TestClip, 20, 10),
(GES.TestClip, 30, 10),
]
])
class TestConfigurationRules(common.GESSimpleTimelineTest):
def _try_add_clip(self, start, duration, layer=None, error=None):
if layer is None:
layer = self.layer
asset = GES.Asset.request(GES.TestClip, None)
found_err = None
clip = None
# large inpoint to allow trims
try:
clip = layer.add_asset_full(
asset, start, 1000, duration, GES.TrackType.UNKNOWN)
except GLib.Error as err:
found_err = err
if error is None:
self.assertIsNotNone(clip)
else:
self.assertIsNone(clip)
self.assertGESError(found_err, error)
return clip
def test_full_overlap_add(self):
clip1 = self._try_add_clip(50, 50)
self._try_add_clip(50, 50, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
self._try_add_clip(49, 51, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
self._try_add_clip(51, 49, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
def test_triple_overlap_add(self):
clip1 = self._try_add_clip(0, 50)
clip2 = self._try_add_clip(40, 50)
self._try_add_clip(39, 12, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
self._try_add_clip(30, 30, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
self._try_add_clip(1, 88, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
def test_full_overlap_move(self):
clip1 = self._try_add_clip(0, 50)
clip2 = self._try_add_clip(50, 50)
self.assertFalse(clip2.set_start(0))
def test_triple_overlap_move(self):
clip1 = self._try_add_clip(0, 50)
clip2 = self._try_add_clip(40, 50)
clip3 = self._try_add_clip(100, 60)
self.assertFalse(clip3.set_start(30))
def test_full_overlap_move_into_layer(self):
clip1 = self._try_add_clip(0, 50)
layer2 = self.timeline.append_layer()
clip2 = self._try_add_clip(0, 50, layer2)
res = None
try:
res = clip2.move_to_layer_full(self.layer)
except GLib.Error as error:
self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK)
self.assertIsNone(res)
def test_triple_overlap_move_into_layer(self):
clip1 = self._try_add_clip(0, 50)
clip2 = self._try_add_clip(40, 50)
layer2 = self.timeline.append_layer()
clip3 = self._try_add_clip(30, 30, layer2)
res = None
try:
res = clip3.move_to_layer_full(self.layer)
except GLib.Error as error:
self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK)
self.assertIsNone(res)
def test_full_overlap_trim(self):
clip1 = self._try_add_clip(0, 50)
clip2 = self._try_add_clip(50, 50)
self.assertFalse(clip2.trim(0))
self.assertFalse(clip1.set_duration(100))
def test_triple_overlap_trim(self):
clip1 = self._try_add_clip(0, 20)
clip2 = self._try_add_clip(10, 30)
clip3 = self._try_add_clip(30, 20)
self.assertFalse(clip3.trim(19))
self.assertFalse(clip1.set_duration(31))
class TestSnapping(common.GESSimpleTimelineTest):
def test_snapping(self):
self.timeline.props.auto_transition = True
self.timeline.set_snapping_distance(1)
clip1 = self.add_clip(0, 0, 100)
# Split clip1.
split_position = 50
clip2 = clip1.split(split_position)
self.assertEqual(len(self.layer.get_clips()), 2)
self.assertEqual(clip1.props.duration, split_position)
self.assertEqual(clip2.props.start, split_position)
# Make sure snapping prevents clip2 to be moved to the left.
clip2.edit([], self.layer.get_priority(), GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE,
clip2.props.start - 1)
self.assertEqual(clip2.props.start, split_position)
def test_trim_snapps_inside_group(self):
self.track_types = [GES.TrackType.AUDIO]
super().setUp()
self.timeline.props.auto_transition = True
self.timeline.set_snapping_distance(5)
snaps = []
def snapping_started_cb(timeline, element1, element2, dist, self):
snaps.append(set([element1, element2]))
self.timeline.connect('snapping-started', snapping_started_cb, self)
clip = self.append_clip()
clip1 = self.append_clip()
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
]
])
clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 15)
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
]
])
self.assertEqual(snaps[0], set([clip.get_children(False)[0], clip1.get_children(False)[0]]))
clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 16)
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 0, 10),
(GES.TestClip, 16, 4),
]
])
def test_trim_no_snapping_on_same_clip(self):
self.timeline.props.auto_transition = True
self.timeline.set_snapping_distance(1)
not_called = []
def snapping_started_cb(timeline, element1, element2, dist, self):
not_called.append("No snapping should happen")
self.timeline.connect('snapping-started', snapping_started_cb, self)
clip = self.append_clip()
clip.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5)
self.assertEqual(not_called, [])
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 5, 5),
]
])
clip.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 4)
self.assertEqual(not_called, [])
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 4, 6),
]
])
def test_no_snapping_on_split(self):
self.timeline.props.auto_transition = True
self.timeline.set_snapping_distance(1)
not_called = []
def snapping_started_cb(timeline, element1, element2, dist, self):
not_called.append("No snapping should happen")
self.timeline.connect('snapping-started', snapping_started_cb, self)
clip1 = self.add_clip(0, 0, 100)
# Split clip1.
split_position = 50
clip2 = clip1.split(split_position)
self.assertEqual(not_called, [])
self.assertEqual(len(self.layer.get_clips()), 2)
self.assertEqual(clip1.props.duration, split_position)
self.assertEqual(clip2.props.start, split_position)
class TestComplexEditing(common.GESTimelineConfigTest):
def test_normal_move(self):
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
.................g1..................
: :
: *=========*
: | c0 |
: *=========*
____________________:___________________________________:____
layer1______________:___________________________________:____
: :
..............g0............... :
: : :
*=======================* : :
| c1 | : :
*=========*=============*=====* :
: | c2 | :
: *===================* :
:.............................: :
: :
:...................................:
_____________________________________________________________
layer2_______________________________________________________
*=============================*
| c3 |
*=============================*
"""
track = self.add_video_track()
c0 = self.add_clip("c0", 0, [track], 23, 5)
c1 = self.add_clip("c1", 1, [track], 10, 12)
c2 = self.add_clip("c2", 1, [track], 15, 10)
self.register_auto_transition(c1, c2, track)
c3 = self.add_clip("c3", 2, [track], 2, 15)
g0 = self.add_group("g0", [c1, c2])
g1 = self.add_group("g1", [c0, g0])
self.assertTimelineConfig()
# test invalid edits
# cannot move c0 up one layer because it would cause a triple
# overlap between c1, c2 and c3 when g0 moves
self.assertFailEdit(
c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 23,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot move c0, without moving g1, to 21 layer 1 because it
# would be completely overlapped by c2
self.assertFailEdit(
c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 20,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot move c1, without moving g1, with end 25 because it
# would be completely overlapped by c2
self.assertFailEdit(
c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 25,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot move g0 to layer 0 because it would make c0 go to a
# negative layer
self.assertFailEdit(
g0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10,
GES.Error.NEGATIVE_LAYER)
# cannot move c1 for same reason
error = None
try:
c1.move_to_layer_full(self.timeline.get_layer(0))
except GLib.Error as err:
error = err
self.assertGESError(error, GES.Error.NEGATIVE_LAYER)
self.assertTimelineConfig({}, [])
# failure with snapping
self.timeline.set_snapping_distance(1)
# cannot move to 0 because end edge of c0 would snap with end of
# c3, making the new start become negative
self.assertFailEdit(
g0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0,
GES.Error.NEGATIVE_TIME)
# cannot move start of c1 to 14 because snapping causes a full
# overlap with c0
self.assertFailEdit(
c1, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 14,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot move end of c2 to 21 because snapping causes a full
# overlap with c0
self.assertFailEdit(
c2, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 21,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# successes
self.timeline.set_snapping_distance(3)
# moving c0 also moves g1, along with g0
# with no snapping, this would result in a triple overlap between
# c1, c2 and c3, but c2's start edge will snap to the end of c3
# at a distance of 3 allowing the edit to succeed
#
# c1 and c3 have a new transition
# transition between c1 and c2 is not lost
#
# NOTE: there is no snapping between c0, c1 or c2 even though
# their edges are within distance 2 of each other because they are
# all moving
self.assertEdit(
c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 22, 17,
[c2], [c3],
{
c0 : {"start": 25, "layer": 1},
c1 : {"start": 12, "layer": 2},
c2 : {"start": 17, "layer": 2},
g0 : {"start": 12, "layer": 2},
g1 : {"start": 12, "layer": 1}
}, [(c3, c1, track)], [])
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
_____________________________________________________________
layer1_______________________________________________________
.................g1..................
: :
: *=========*
: | c0 |
: *=========*
________________________:___________________________________:
layer2__________________:___________________________________:
: :
..............g0............... :
: : :
*=======================* : :
| c1 | : :
*===================*=========*=============*=====* :
| c3 | c2 | :
*=============================*===================* :
:.............................: :
: :
:...................................:
"""
# using EDGE_START we can move without moving parent
# snap at same position 17 but with c1's end edge to c3's end
# edge
# loose transition between c1 and c3
self.assertEdit(
g0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 5, 17,
[c1], [c3],
{
c1 : {"start": 5, "layer": 0},
c2 : {"start": 10, "layer": 0},
g0 : {"start": 5, "layer": 0},
g1 : {"start": 5, "duration": 25, "layer": 0},
}, [], [(c3, c1, track)])
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
.........................g1........................
: :
...............g0.............. :
: : :
*=======================* : :
| c1 | : :
*=========*=============*=====* :
: | c2 | :
: *===================* :
:.............................: :
__________:_________________________________________________:
layer1____:_________________________________________________:
: :
: *=========*
: | c0 |
: *=========*
:.................................................:
_____________________________________________________________
layer2_______________________________________________________
*=============================*
| c3 |
*=============================*
"""
# using EDGE_END we can move without moving parent
# no snap
# loose transition between c1 and c2
self.assertEdit(
c2, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 21, None,
[], [],
{
c2 : {"duration": 11, "layer": 1},
g0 : {"duration": 16},
}, [], [(c1, c2, track)])
# no snapping when we use move layer
self.timeline.set_snapping_distance(10)
self.assertTrue(
c3.move_to_layer(self.timeline.get_layer(1)))
self.assertTimelineConfig(
new_props={c3 : {"layer": 1}}, new_transitions=[(c3, c2, track)])
def test_ripple(self):
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
......................g0.................
: :
*=========* :
| c0 | :
*=========* :
______________:_______________________________________:______
layer1________:_______________________________________:______
: :
: *=============*
: | c3 |
: *=============*
:.......................................:
*===================*
| c2 |
*=========*=========*=========*
| c1 |
*===================*
"""
track = self.add_video_track()
c0 = self.add_clip("c0", 0, [track], 7, 5)
c1 = self.add_clip("c1", 1, [track], 5, 10)
c2 = self.add_clip("c2", 1, [track], 10, 10)
c3 = self.add_clip("c3", 1, [track], 20, 7)
self.register_auto_transition(c1, c2, track)
g0 = self.add_group("g0", [c0, c3])
self.assertTimelineConfig()
# test failures
self.timeline.set_snapping_distance(2)
# would cause negative layer priority for c0
self.assertFailEdit(
c1, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 5,
GES.Error.NEGATIVE_LAYER)
# would lead to c2 fully overlapping c3 since c2 does ripple
# but c3 does not(c3 shares a toplevel with c0, and
# GES_EDGE_START, same as NORMAL mode, does not move the
# toplevel
self.assertFailEdit(
c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 25,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# would lead to c2 fully overlapping c3 since c2 does not
# ripple but c3 does
self.assertFailEdit(
c0, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 13,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# add two more clips
c4 = self.add_clip("c4", 2, [track], 17, 8)
c5 = self.add_clip("c5", 2, [track], 21, 8)
self.register_auto_transition(c4, c5, track)
self.assertTimelineConfig()
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
......................g0.................
: :
*=========* :
| c0 | :
*=========* :
______________:_______________________________________:______
layer1________:_______________________________________:______
: :
: *=============*
: | c3 |
: *=============*
:.......................................:
*===================*
| c2 |
*=========*=========*=========*
| c1 |
*===================*
_____________________________________________________________
layer2_______________________________________________________
*===============*
| c4 |
*=======*=======*=======*
| c5 |
*===============*
"""
# rippling start of c2 only moves c4 and c5 because c3 is part
# of a toplevel with an earlier start
# NOTE: snapping only occurs for the edges of c2, in particular
# start of c4 does not snap to end of c1
self.assertEdit(
c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 8, 7,
[c2], [c0],
{
c2 : {"start": 7},
c4 : {"start": 14},
c5 : {"start": 18},
}, [], [])
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
......................g0.................
: :
*=========* :
| c0 | :
*=========* :
______________:_______________________________________:______
layer1________:_______________________________________:______
: :
: *=============*
: | c3 |
: *=============*
:.......................................:
*===================*
| c2 |
*===*===============*===*
| c1 |
*===================*
_____________________________________________________________
layer2_______________________________________________________
*===============*
| c4 |
*=======*=======*=======*
| c5 |
*===============*
"""
# rippling end of c2, only c5 moves
# NOTE: start edge of c2 does not snap!
self.assertEdit(
c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 19, 20,
[c2], [c3],
{
c2 : {"duration": 13},
c5 : {"start": 21},
}, [], [])
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
......................g0.................
: :
*=========* :
| c0 | :
*=========* :
______________:_______________________________________:______
layer1________:_______________________________________:______
: :
: *=============*
: | c3 |
: *=============*
:.......................................:
*=========================*
| c2 |
*===*===============*=========*
| c1 |
*===================*
_____________________________________________________________
layer2_______________________________________________________
*===============*
| c4 |
*=============*=*=============*
| c5 |
*===============*
"""
# everything except c1 moves, and to the next layer
# end edge of c2 snaps to end of c1
# NOTE: does not snap to edges of rippled clips
# NOTE: c4 and c5 do not loose their transition when moving
# to the new layer
self.assertEdit(
c2, 2, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 0, 15,
[c2], [c1],
{
c0 : {"start": 2, "layer": 1},
c2 : {"start": 2, "layer": 2},
c3 : {"start": 15, "layer": 2},
c4 : {"start": 9, "layer": 3},
c5 : {"start": 16, "layer": 3},
g0 : {"start": 2, "layer": 1},
}, [(c0, c1, track)], [(c1, c2, track)])
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
_____________________________________________________________
layer1_______________________________________________________
*===================*
| c1 |
*===================*
...................g0....................
*=========* :
| c0 | :
*=========* :
____:_______________________________________:________________
layer2______________________________________:________________
: :
: *=============*
: | c3 |
: *=============*
:.......................................:
*=========================*
| c2 |
*=========================*
_____________________________________________________________
layer3_______________________________________________________
*===============*
| c4 |
*=============*=*=============*
| c5 |
*===============*
"""
# group c1 and c5, and g0 and c2
g1 = self.add_group("g1", [c1, c5])
g2 = self.add_group("g2", [g0, c2])
self.assertTimelineConfig()
# moving end edge of c0 does not move anything else in the same
# toplevel g2
# c5 does not move because it is grouped with c1, which starts
# earlier than the end edge of c0
# only c4 moves
# c0 does not snap to c4's start edge
self.assertEdit(
c0, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 10, None,
[], [],
{
c0 : {"duration": 8},
c4 : {"start": 12},
}, [], [])
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
_____________________________________________________________
layer1_______________________________________________________
..................g1.....................
*===================* :
| c1 | :
*===================* :
:...................................... :
...................g2.................... : :
:..................g0...................: : :
*===============* : : :
| c0 | : : :
*===============* : : :
____:_______________________________________:___:_:__________
layer2______________________________________:___:_:__________
: : : :
: *=============* : :
: | c3 | : :
: *=============* : :
:.......................................: : :
*=========================* : : :
| c2 | : : :
*=========================* : : :
:.......................................: : :
________________________________________________:_:__________
layer3__________________________________________:_:__________
................: :
*===============* :
| c5 | :
*===============* :
:.................:
*===============*
| c4 |
*===============*
"""
# rippling start of c5 does not move anything else
# end edge snaps to start of c4
self.timeline.set_snapping_distance(1)
self.assertEdit(
c5, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 18, None,
[], [],
{
c5 : {"start": 18, "layer": 0},
g1 : {"layer": 0, "duration": 21},
}, [], [(c4, c5, track)])
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
....................g1.....................
: *===============*
: | c5 |
: *===============*
__________:_________________________________________:________
layer1____:_________________________________________:________
: :
*===================* :
| c1 | :
*===================* :
:.........................................:
...................g2....................
:..................g0...................:
*===============* :
| c0 | :
*===============* :
____:_______________________________________:________________
layer2______________________________________:________________
: :
: *=============*
: | c3 |
: *=============*
:.......................................:
*=========================* :
| c2 | :
*=========================* :
:.......................................:
_____________________________________________________________
layer3_______________________________________________________
*===============*
| c4 |
*===============*
"""
# rippling g1 using c5
# initial position would make c1 go negative, but end edge of c1
# will snap to end of c0, allowing the edit to succeed
# c4 also moves because it is after the start of g1
self.timeline.set_snapping_distance(3)
self.assertEdit(
c5, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 12, 10,
[c1], [c0],
{
c5 : {"start": 13, "layer": 1},
c1 : {"start": 0, "layer": 2},
g1 : {"start": 0, "layer": 1},
c4 : {"start": 7, "layer": 4},
}, [(c1, c2, track)], [(c0, c1, track)])
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
_____________________________________________________________
layer1_______________________________________________________
....................g1.....................
: *===============*
: | c5 |
: *===============*
: ...................g2.................:..
: :..................g0.................:.:
: *===============* : :
: | c0 | : :
: *===============* : :
:___:_____________________________________:_:________________
layer2____________________________________:_:________________
: : : :
*===================* : :
| c1 | : :
*===================* : :
:...:.....................................: :
: *=============*
: | c3 |
: *=============*
:.......................................:
*=========================* :
| c2 | :
*=========================* :
:.......................................:
_____________________________________________________________
layer3_______________________________________________________
_____________________________________________________________
layer4_______________________________________________________
*===============*
| c4 |
*===============*
"""
# moving start of c1 will move everything expect c5 because they
# can snap to c5 since it is not moving
# c1 and c2 keep transition
self.assertEdit(
c1, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 20, 21,
[c1], [c5],
{
c1 : {"start": 21, "layer": 1},
g1 : {"start": 13, "duration": 18},
c0 : {"start": 23, "layer": 0},
c2 : {"start": 23, "layer": 1},
c3 : {"start": 36, "layer": 1},
g0 : {"start": 23, "layer": 0},
g2 : {"start": 23, "layer": 0},
c4 : {"start": 28, "layer": 3},
}, [], [])
def test_trim(self):
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
..................g2...................
: :
:...............g0............. :
: *===============* :
: | a0 | :
: *===*=====*=========* :
: | a1 | : :
: *=========* : :
*===================* : :
| v0 | : :
*===================* : :
:.............................: :
__________:_____________________________________:____________
layer1____:_____________________________________:____________
: :
: ............g1..............:
: : *=================*
: : | a2 |
: : *=================*
: *===========================*
: | v1 |
: *===========================*
__________:_________:___________________________:____________
layer2____:_________:___________________________:____________
*=============* *=================*
| a3 | | a4 |
*=============* *=================*
: :...........................:
*=========================* :
| v2 | :
*=============*===========*===========*
: | v3 |
: *=======================*
:.....................................:
_____________________________________________________________
layer3_______________________________________________________
*===========================* *===============*
| v4 | | v5 |
*===========================* *===============*
"""
audio_track = self.add_audio_track()
video_track = self.add_video_track()
a0 = self.add_clip("a0", 0, [audio_track], 12, 8, 5, 15)
a1 = self.add_clip("a1", 0, [audio_track], 10, 5)
self.register_auto_transition(a1, a0, audio_track)
a2 = self.add_clip("a2", 1, [audio_track], 15, 9, 7, 19)
a3 = self.add_clip("a3", 2, [audio_track], 5, 7, 10)
a4 = self.add_clip("a4", 2, [audio_track], 15, 9)
v0 = self.add_clip("v0", 0, [video_track], 5, 10, 5)
v1 = self.add_clip("v1", 1, [video_track], 10, 14)
v2 = self.add_clip("v2", 2, [video_track], 5, 13, 4)
v3 = self.add_clip("v3", 2, [video_track], 12, 12)
self.register_auto_transition(v2, v3, video_track)
v4 = self.add_clip("v4", 3, [video_track], 0, 13)
v5 = self.add_clip("v5", 3, [video_track], 18, 8)
g0 = self.add_group("g0", [a0, a1, v0])
g1 = self.add_group("g1", [v1, a2, a4])
g2 = self.add_group("g2", [a3, v2, v3, g0, g1])
self.assertTimelineConfig()
# edit failures
# cannot trim end of g0 to 16 because a0 and a1 would fully
# overlap
self.assertFailEdit(
g0, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot edit to new layer because there would be triple overlaps
# between v2, v3, v4 and v5
self.assertFailEdit(
g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 20,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot trim g1 end to 14 because it would result in a negative
# duration for a2 and a4
self.assertFailEdit(
g1, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 14,
GES.Error.NEGATIVE_TIME)
# cannot trim end of v2 below its start
self.assertFailEdit(
v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 2,
GES.Error.NEGATIVE_TIME)
# cannot trim end of g0 because a0's duration-limit would be
# exceeded
self.assertFailEdit(
g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 23,
GES.Error.NOT_ENOUGH_INTERNAL_CONTENT)
# cannot trim g0 to 12 because a0 and a1 would fully overlap
self.assertFailEdit(
g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 12,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot trim start of v2 beyond its end point
self.assertFailEdit(
v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 20,
GES.Error.NEGATIVE_TIME)
# with snapping
self.timeline.set_snapping_distance(4)
# cannot trim end of g2 to 19 because v1 and v2 would fully
# overlap after snapping to v5 start edge(18)
self.assertFailEdit(
g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 19,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot trim g2 to 3 because it would snap to start edge of
# v4(0), causing v2's in-point to be negative
self.assertFailEdit(
g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 3,
GES.Error.NEGATIVE_TIME)
# success
self.timeline.set_snapping_distance(2)
# first trim v4 start
self.assertEdit(
v4, 3, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 1, None, [], [],
{
v4 : {"start": 1, "in-point": 1, "duration": 12},
}, [], [])
# and trim v5 end
self.assertEdit(
v5, 3, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 25, 24,
[v5], [a2, a4, v1, v3],
{
v5 : {"duration": 6},
}, [], [])
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
..................g2...................
: :
:...............g0............. :
: *===============* :
: | a0 | :
: *===*=====*=========* :
: | a1 | : :
: *=========* : :
*===================* : :
| v0 | : :
*===================* : :
:.............................: :
__________:_____________________________________:____________
layer1____:_____________________________________:____________
: :
: ............g1..............:
: : *=================*
: : | a2 |
: : *=================*
: *===========================*
: | v1 |
: *===========================*
__________:_________:___________________________:____________
layer2____:_________:___________________________:____________
*=============* *=================*
| a3 | | a4 |
*=============* *=================*
: :...........................:
*=========================* :
| v2 | :
*=============*===========*===========*
: | v3 |
: *=======================*
:.....................................:
_____________________________________________________________
layer3_______________________________________________________
*=========================* *===========*
| v4 | | v5 |
*=========================* *===========*
"""
# can trim g2 to 0 even though in-point of v2 is 4 because it will
# snap to 1. Note, there is only snapping on the start edge
# everything at the start edge is stretched back
self.assertEdit(
g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0, 1,
[v0, v2, a3], [v4],
{
v0 : {"start": 1, "in-point": 1, "duration": 14},
a3 : {"start": 1, "in-point": 6, "duration": 11},
v2 : {"start": 1, "in-point": 0, "duration": 17},
g0 : {"start": 1, "duration": 19},
g2 : {"start": 1, "duration": 23},
}, [], [])
self.timeline.set_snapping_distance(0)
# trim end to use as a snapping point
self.assertEdit(
v4, 3, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 11, None, [], [],
{
v4 : {"duration": 10},
}, [], [])
self.timeline.set_snapping_distance(2)
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
......................g2.......................
: :
:................g0.................... :
: *===============* :
: | a0 | :
: *===*=====*=========* :
: | a1 | : :
: *=========* : :
*===========================* : :
| v0 | : :
*===========================* : :
:.....................................: :
__:_____________________________________________:____________
layer1__________________________________________:____________
: :
: ............g1..............:
: : *=================*
: : | a2 |
: : *=================*
: *===========================*
: | v1 |
: *===========================*
__:_________________:___________________________:____________
layer2______________:___________________________:____________
*=====================* *=================*
| a3 | | a4 |
*=====================* *=================*
: :...........................:
*=================================* :
| v2 | :
*=====================*===========*===========*
: | v3 |
: *=======================*
:.............................................:
_____________________________________________________________
layer3_______________________________________________________
*===================* *===========*
| v4 | | v5 |
*===================* *===========*
"""
# can trim g2 to 12 even though it would cause a0 and a1 to fully
# overlap because the snapping allows it to succeed
self.assertEdit(
g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 12, 11,
[a3, v0, v2], [v4],
{
v0 : {"start": 11, "in-point": 11, "duration": 4},
a1 : {"start": 11, "in-point": 1, "duration": 4},
v1 : {"start": 11, "in-point": 1, "duration": 13},
a3 : {"start": 11, "in-point": 16, "duration": 1},
v2 : {"start": 11, "in-point": 10, "duration": 7},
g0 : {"start": 11, "duration": 9},
g1 : {"start": 11, "duration": 13},
g2 : {"start": 11, "duration": 13},
}, [], [])
# trim end to use as a snapping point
self.assertEdit(
v5, 4, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 27, None, [], [],
{
v5 : {"duration": 9, "layer": 4},
}, [], [])
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
.............g2............
: :
:.......g0......... :
: *===============* :
: | a0 | :
*=*=====*=========* :
| a1 | : :
*=======* : :
*=======* : :
| v0 | : :
*=======* : :
:.................: :
______________________:_________________________:____________
layer1________________:_________________________:____________
: :
:.........g1..............:
: *=================*
: | a2 |
: *=================*
*=========================*
| v1 |
*=========================*
______________________:_________________________:____________
layer2________________:_________________________:____________
*=* *=================*
a3| | a4 |
*=* *=================*
:.........................:
*=============* :
| v2 | :
*=*===========*===========*
: | v3 |
: *=======================*
:.........................:
_____________________________________________________________
layer3_______________________________________________________
*===================*
| v4 |
*===================*
_____________________________________________________________
layer4_______________________________________________________
*=================*
| v5 |
*=================*
"""
# trim end of g2 and move layer. Without the snap, would fail since
# a2's duration-limit is 12.
# Even elements not being trimmed will still move layer
# a0 and a1 keep transition
# v2 and v3 keep transition
self.assertEdit(
g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 29, 27,
[a2, a4, v1, v3], [v5],
{
a2 : {"duration": 12, "layer": 2},
v1 : {"duration": 16, "layer": 2},
a4 : {"duration": 12, "layer": 3},
v3 : {"duration": 15, "layer": 3},
g1 : {"duration": 16, "layer": 2},
g2 : {"duration": 16, "layer": 1},
a0 : {"layer": 1},
a1 : {"layer": 1},
v0 : {"layer": 1},
a3 : {"layer": 3},
v2 : {"layer": 3},
g0 : {"layer": 1},
}, [], [])
# trim start to use as a snapping point
self.timeline.set_snapping_distance(0)
self.assertEdit(
v5, 4, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 19, None,
[], [],
{
v5 : {"start": 19, "in-point": 1, "duration": 8},
}, [], [])
self.timeline.set_snapping_distance(2)
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
_____________________________________________________________
layer1_______________________________________________________
.............g2..................
: :
:.......g0......... :
: *===============* :
: | a0 | :
*=*=====*=========* :
| a1 | : :
*=======* : :
*=======* : :
| v0 | : :
*=======* : :
:.................: :
______________________:_______________________________:______
layer2________________:_______________________________:______
: :
:.........g1....................:
: *=======================*
: | a2 |
: *=======================*
*===============================*
| v1 |
*===============================*
______________________:_______________________________:______
layer3________________:_______________________________:______
*=* *=======================*
a3| | a4 |
*=* *=======================*
:...............................:
*===================*=============* :
| v4 | v2 | :
*===================*=*===========*=================*
: | v3 |
: *=============================*
:...............................:
_____________________________________________________________
layer4_______________________________________________________
*===============*
| v5 |
*===============*
"""
# trim end of g2 and move layer. Trim at 17 would lead to
# v3 being fully overlapped by v2, but snap to 19 makes it work
self.assertEdit(
g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 17, 19,
[a2, a4, v1, v3], [v5],
{
a0 : {"duration": 7},
a2 : {"duration": 4},
v1 : {"duration": 8},
a4 : {"duration": 4},
v3 : {"duration": 7},
g0 : {"duration": 8},
g1 : {"duration": 8},
g2 : {"duration": 8},
}, [], [])
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
_____________________________________________________________
layer1_______________________________________________________
........g2.......
: :
:.......g0......:
: *=============*
: | a0 |
*=*=====*=======*
| a1 | :
*=======* :
*=======* :
| v0 | :
*=======* :
:...............:
______________________:_______________:______________________
layer2________________:_______________:______________________
: :
:.......g1......:
: *=======*
: | a2 |
: *=======*
*===============*
| v1 |
*===============*
______________________:_______________:______________________
layer3________________:_______________:______________________
*=* *=======*
a3| | a4 |
*=* *=======*
:...............:
*===================*=============* :
| v4 | v2 | :
*===================*=*===========*=*
: | v3 |
: *=============*
:...............:
_____________________________________________________________
layer4_______________________________________________________
*===============*
| v5 |
*===============*
"""
# can trim without trimming parent
self.assertEdit(
v0, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5, None, [], [],
{
v0 : {"start": 5, "in-point": 5, "duration": 10},
g0 : {"start": 5, "duration": 14},
g2 : {"start": 5, "duration": 14},
}, [], [])
self.assertEdit(
a2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 23, None, [], [],
{
a2 : {"duration": 8},
g1 : {"duration": 12},
g2 : {"duration": 18},
}, [], [])
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
_____________________________________________________________
layer1_______________________________________________________
...................g2................
: :
:...........g0............... :
: *=============* :
: | a0 | :
: *=*=====*=======* :
: | a1 | : :
: *=======* : :
*===================* : :
| v0 | : :
*===================* : :
:...........................: :
__________:___________________________________:______________
layer2____:___________________________________:______________
: :
: ............g1..........:
: : *===============*
: : | a2 |
: : *===============*
: *===============* :
: | v1 | :
: *===============* :
__________:___________:_______________________:______________
layer3____:___________:_______________________:______________
: *=* *=======* :
: a3| | a4 | :
: *=* *=======* :
: :.......................:
*===================*=============* :
| v4 | v2 | :
*===================*=*===========*=* :
: | v3 | :
: *=============* :
:...................................:
_____________________________________________________________
layer4_______________________________________________________
*===============*
| v5 |
*===============*
"""
# same with group within a group
self.assertEdit(
g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 9, 11,
[v0], [v1, v2, v4, a3],
{
v0 : {"start": 11, "in-point": 11, "duration": 4, "layer": 0},
a0 : {"layer": 0},
a1 : {"layer": 0},
g0 : {"start": 11, "duration": 8, "layer": 0},
g2 : {"start": 11, "duration": 12, "layer": 0},
}, [], [])
self.assertEdit(
g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 17, 18,
[a0], [v2],
{
a0 : {"duration": 6},
g0 : {"duration": 7},
}, [], [])
def test_roll(self):
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
*===============================* ]
| c0 | ]
*=============*=====*===================*=====*=============* ]>video
| c1 | | c2 | ]
*===================* *===================* ]
..........g0.........
*===========* : ]
| c3 | : ]>audio0
*===*=======*===* : ]
: | c4 | : ] ]
: *===*=======*===* ]
: | c5 | ]>audio1
: *===========* ]
____________________:___________________:____________________
layer1______________:___________________:____________________
: :
.............g3.....:.... :
: *=================* : ]
: | c12 | ....:...g4............... ]
: *=================* : : : ]
:........g1.........: : : :.........g2........: ]>audio0
*=============* : : : *=============* : ]
| c8 | : : : | c10 | : ]
*=============* : : *=========*=============* : ]
: : : | c7 | : ] ]
: *=========*=========* : ]>video
: | c6 | : : : ] ]
: *=============*=========* : : *=============* ]
: | c9 |...:...........:...: | c11 | ]
: *=============* : : : *=============* ]
:...................: : : :...................: ]>audio1
:.......................: *=================* : ]
| c13 | : ]
*=================* : ]
:.......................:
"""
video = self.add_video_track()
audio0 = self.add_audio_track()
audio1 = self.add_audio_track()
c0 = self.add_clip("c0", 0, [video], 7, 16)
c1 = self.add_clip("c1", 0, [video], 0, 10)
c2 = self.add_clip("c2", 0, [video], 20, 10, 20)
self.register_auto_transition(c1, c0, video)
self.register_auto_transition(c0, c2, video)
c3 = self.add_clip("c3", 0, [audio0], 10, 6, 2, 38)
c4 = self.add_clip("c4", 0, [audio0, audio1], 12, 6, 15)
self.register_auto_transition(c3, c4, audio0)
c5 = self.add_clip("c5", 0, [audio1], 14, 6, 30, 38)
self.register_auto_transition(c4, c5, audio1)
c6 = self.add_clip("c6", 1, [audio1, video], 10, 5, 7)
c7 = self.add_clip("c7", 1, [audio0, video], 15, 5, 1, 15)
g0 = self.add_group("g0", [c3, c4, c5, c6, c7])
c8 = self.add_clip("c8", 1, [audio0], 0, 7, 3, 13)
c9 = self.add_clip("c9", 1, [audio1], 3, 7)
g1 = self.add_group("g1", [c8, c9])
c10 = self.add_clip("c10", 1, [audio0], 20, 7, 1)
c11 = self.add_clip("c11", 1, [audio1], 23, 7, 3, 10)
g2 = self.add_group("g2", [c10, c11])
c12 = self.add_clip("c12", 1, [audio0], 3, 9)
self.register_auto_transition(c8, c12, audio0)
g3 = self.add_group("g3", [g1, c12])
c13 = self.add_clip("c13", 1, [audio1], 18, 9)
self.register_auto_transition(c13, c11, audio1)
g4 = self.add_group("g4", [g2, c13])
self.assertTimelineConfig()
# edit failures
self.timeline.set_snapping_distance(2)
# cannot roll c10 to 22, which snaps to 23, because it will
# extend c5 beyond its duration limit of 8
self.assertFailEdit(
c10, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22,
GES.Error.NOT_ENOUGH_INTERNAL_CONTENT)
# same with g2
self.assertFailEdit(
g2, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22,
GES.Error.NOT_ENOUGH_INTERNAL_CONTENT)
# cannot roll end c9 to 8, which snaps to 7, because it would
# cause c3's in-point to become negative
self.assertFailEdit(
c9, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8,
GES.Error.NEGATIVE_TIME)
# same with g1
self.assertFailEdit(
g1, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8,
GES.Error.NEGATIVE_TIME)
# cannot roll c13 to 19, snap to 20, because it would cause
# c4 to fully overlap c5
self.assertFailEdit(
c13, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 19,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot roll c12 to 11, snap to 10, because it would cause
# c3 to fully overlap c4
self.assertFailEdit(
c12, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 11,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# give c6 a bit more allowed duration so we can focus on c9
self.assertTrue(c6.set_inpoint(10))
self.assertTimelineConfig({ c6 : {"in-point": 10}})
# cannot roll c6 to 0 because it would cause c9 to be trimmed
# below its start
self.assertFailEdit(
c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 0,
GES.Error.NEGATIVE_TIME)
# set back
self.assertTrue(c6.set_inpoint(7))
self.assertTimelineConfig({ c6 : {"in-point": 7}})
# give c7 a bit more allowed duration so we can focus on c10
self.assertTrue(c7.set_inpoint(0))
self.assertTimelineConfig({ c7 : {"in-point": 0}})
# cannot roll end c7 to 30 because it would cause c10 to be
# trimmed beyond its end
self.assertFailEdit(
c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 30,
GES.Error.NEGATIVE_TIME)
# set back
self.assertTrue(c7.set_inpoint(1))
self.assertTimelineConfig({ c7 : {"in-point": 1}})
# moving layer is not supported
self.assertFailEdit(
c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 7, None)
self.assertFailEdit(
c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 23, None)
# successes
self.timeline.set_snapping_distance(0)
# c1 and g1 are trimmed at their end
# NOTE: c12 is not trimmed even though it shares a group g3
# with g1 because g3 does not share the same edge
# trim forward
self.assertEdit(
c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 11, None,
[], [],
{
c6 : {"start": 11, "in-point": 8, "duration": 4},
c1 : {"duration": 11},
c9 : {"duration": 8},
g1 : {"duration": 11},
}, [], [])
# and reset
self.assertEdit(
c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 10, None,
[], [],
{
c6 : {"start": 10, "in-point": 7, "duration": 5},
c1 : {"duration": 10},
c9 : {"duration": 7},
g1 : {"duration": 10},
}, [], [])
# same with g0
self.assertEdit(
g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 11, None,
[], [],
{
c6 : {"start": 11, "in-point": 8, "duration": 4},
c3 : {"start": 11, "in-point": 3, "duration": 5},
g0 : {"start": 11, "duration": 9},
c1 : {"duration": 11},
c9 : {"duration": 8},
g1 : {"duration": 11},
}, [], [])
self.assertEdit(
g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 10, None,
[], [],
{
c6 : {"start": 10, "in-point": 7, "duration": 5},
c3 : {"start": 10, "in-point": 2, "duration": 6},
g0 : {"start": 10, "duration": 10},
c1 : {"duration": 10},
c9 : {"duration": 7},
g1 : {"duration": 10},
}, [], [])
self.timeline.set_snapping_distance(1)
# trim backward
# NOTE: c9 has zero width, not considered overlapping with c6
# snapping allows the edit to succeed (in-point of c6 no longer
# negative)
# NOTE: c12 does not move, but c8 does because it is in the same
# group as g1
# loose transitions
self.assertEdit(
c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 2, 3,
[c6], [c12],
{
c6 : {"start": 3, "in-point": 0, "duration": 12},
g0 : {"start": 3, "duration": 17},
c1 : {"duration": 3},
c8 : {"duration": 3},
c9 : {"duration": 0},
g1 : {"duration": 3},
}, [], [(c1, c0, video), (c8, c12, audio0)])
# bring back
# NOTE: no snapping to c3 start edge because it is part of the
# element being edited, g0, even though it doesn't end up changing
# gain back new transitions
self.assertEdit(
g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 10, None,
[], [],
{
c6 : {"start": 10, "in-point": 7, "duration": 5},
g0 : {"start": 10, "duration": 10},
c1 : {"duration": 10},
c8 : {"duration": 10},
c9 : {"duration": 7},
g1 : {"duration": 10},
}, [(c1, c0, video), (c8, c12, audio0)], [])
# same but with the end edge of g0
self.timeline.set_snapping_distance(0)
self.assertEdit(
c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 19, None, [], [],
{
c7 : {"duration": 4},
c2 : {"start": 19, "in-point": 19, "duration": 11},
c10 : {"start": 19, "in-point": 0, "duration": 8},
g2 : {"start": 19, "duration": 11},
}, [], [])
self.assertEdit(
c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 20, None, [], [],
{
c7 : {"duration": 5},
c2 : {"start": 20, "in-point": 20, "duration": 10},
c10 : {"start": 20, "in-point": 1, "duration": 7},
g2 : {"start": 20, "duration": 10},
}, [], [])
# do same with g0
self.assertEdit(
g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 19, None, [], [],
{
c7 : {"duration": 4},
c5 : {"duration": 5},
g0 : {"duration": 9},
c2 : {"start": 19, "in-point": 19, "duration": 11},
c10 : {"start": 19, "in-point": 0, "duration": 8},
g2 : {"start": 19, "duration": 11},
}, [], [])
self.assertEdit(
g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 20, None, [], [],
{
c7 : {"duration": 5},
c5 : {"duration": 6},
g0 : {"duration": 10},
c2 : {"start": 20, "in-point": 20, "duration": 10},
c10 : {"start": 20, "in-point": 1, "duration": 7},
g2 : {"start": 20, "duration": 10},
}, [], [])
self.timeline.set_snapping_distance(1)
# trim forwards
# NOTE: c10 has zero width, not considered overlapping with c7
# snapping allows the edit to succeed (duration of c7 no longer
# above its limit)
# NOTE: c12 does not move, but c11 does because it is in the same
# group as g2
self.assertEdit(
c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 28, 27,
[c7], [c13],
{
c7 : {"duration": 12},
g0 : {"duration": 17},
c2 : {"start": 27, "in-point": 27, "duration": 3},
c10 : {"start": 27, "in-point": 8, "duration": 0},
c11 : {"start": 27, "in-point": 7, "duration": 3},
g2 : {"start": 27, "duration": 3},
}, [], [(c0, c2, video), (c13, c11, audio1)])
# bring back using g0
# NOTE: no snapping to c5 end edge because it is part of the
# element being edited, g0, even though it doesn't end up changing
self.assertEdit(
g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 20, None, [], [],
{
c7 : {"duration": 5},
g0 : {"duration": 10},
c2 : {"start": 20, "in-point": 20, "duration": 10},
c10 : {"start": 20, "in-point": 1, "duration": 7},
c11 : {"start": 20, "in-point": 0, "duration": 10},
g2 : {"start": 20, "duration": 10},
}, [(c0, c2, video), (c13, c11, audio1)], [])
# adjust c0 for snapping
# doesn't move anything else
self.assertEdit(
c0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 8, None,
[], [],
{
c0 : {"start": 8, "in-point": 1, "duration": 15},
}, [], [])
self.assertEdit(
c0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 22, None, [], [],
{
c0 : {"duration": 14},
}, [], [])
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
*===========================* ]
| c0 | ]
*===============*===*===================*===*===============* ]>video
| c1 | | c2 | ]
*===================* *===================* ]
..........g0.........
*===========* : ]
| c3 | : ]>audio0
*===*=======*===* : ]
: | c4 | : ] ]
: *===*=======*===* ]
: | c5 | ]>audio1
: *===========* ]
____________________:___________________:____________________
layer1______________:___________________:____________________
: :
.............g3.....:.... :
: *=================* : ]
: | c12 | ....:...g4............... ]
: *=================* : : : ]
:........g1.........: : : :.........g2........: ]>audio0
*===================* : : *=============* : ]
| c8 | : : | c10 | : ]
*===================* : *=========*=============* : ]
: : : | c7 | : ] ]
: *=========*=========* : ]>video
: | c6 | : : : ] ]
: *=============*=========* : *===================* ]
: | c9 |...:...........:...| c11 | ]
: *=============* : : *===================* ]
:...................: : : :...................: ]>audio1
:.......................: *=================* : ]
| c13 | : ]
*=================* : ]
:.......................:
"""
# rolling only moves an element if it contains a source that
# touches the rolling edge. For a group, any source below it
# at the corresponding edge counts, we also prefer trimming the
# whole group over just one of its childrens.
# As such, when rolling the end of c5, c11 shares the audio1
# track and starts when c5 ends, so is set to be trimmed. But it
# is also at the start edge of its parent g2, so g2 is set to be
# trimmed. However, it is not at the start of g4, so g4 is not
# set to be trimmed. As such, c10 will also move, even though it
# does not share a track. c2, on the other hand, will not move
# NOTE: snapping helps keep c5's duration below its limit (8)
self.assertEdit(
c5, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 23, 22,
[c5], [c0],
{
c5 : {"duration": 8},
g0 : {"duration": 12},
c11 : {"start": 22, "in-point": 2, "duration": 8},
c10 : {"start": 22, "in-point": 3, "duration": 5},
g2 : {"start": 22, "duration": 8},
}, [], [])
# same with c3 at its start edge
self.assertEdit(
c3, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 7, 8,
[c3], [c0],
{
c3 : {"start": 8, "in-point": 0, "duration": 8},
g0 : {"start": 8, "duration": 14},
c8 : {"duration": 8},
c9 : {"duration": 5},
g1 : {"duration": 8},
}, [], [])
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
*===========================* ]
| c0 | ]
*===============*===*===================*===*===============* ]>video
| c1 | | c2 | ]
*===================* *===================* ]
..............g0.............
*===============* : ]
| c3 | : ]>audio0
*=======*=======*===* : ]
: | c4 | : ] ]
: *===*=======*=======* ]
: | c5 | ]>audio1
: *===============* ]
________________:___________________________:________________
layer1__________:___________________________:________________
: :
.............g3.:........ :
: *=================* : ]
: | c12 | ........:.g4............. ]
: *=================* : : : ]
:........g1.....: : : :.....g2........: ]>audio0
*===============* : : *=========* : ]
| c8 | : : | c10 | : ]
*===============* : *=========* *=========* : ]
: : : | c7 | : : ] ]
: : *=========*=========* : : ]>video
: : | c6 | : : : ] ]
: *=========* *=========* : *===============* ]
: | c9 |.......:...........:.......| c11 | ]
: *=========* : : *===============* ]
:...............: : : :...............: ]>audio1
:.......................: *=================* : ]
| c13 | : ]
*=================* : ]
:.......................:
"""
# rolling end of c1 only moves c6, similarly with c2 and c7
self.assertEdit(
c1, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8, 8,
[c1], [c9, c8, c3, c0],
{
c1 : {"duration": 8},
c6 : {"start": 8, "in-point": 5, "duration": 7},
}, [], [(c1, c0, video)])
self.assertEdit(
c2, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22, 22,
[c2], [c0, c5, c10, c11],
{
c2 : {"start": 22, "in-point": 22, "duration": 8},
c7 : {"duration": 7},
}, [], [(c0, c2, video)])
# move c3 end edge out the way
self.timeline.set_snapping_distance(0)
self.assertEdit(
c3, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 17, None, [], [],
{
c3: {"duration": 9},
}, [], [])
self.timeline.set_snapping_distance(2)
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
*===========================* ]
| c0 | ]
*===============*===========================*===============* ]>video
| c1 | | c2 | ]
*===============* *===============* ]
..............g0.............
*=================* : ]
| c3 | : ]>audio0
*=======*=========*=* : ]
: | c4 | : ] ]
: *===*=======*=======* ]
: | c5 | ]>audio1
: *===============* ]
________________:___________________________:________________
layer1__________:___________________________:________________
: :
.............g3.:........ :
: *=================* : ]
: | c12 | ........:.g4............. ]
: *=================* : : : ]
:........g1.....: : : :.....g2........: ]>audio0
*===============* : : *=========* : ]
| c8 | : : | c10 | : ]
*===============* : *=============*=========* : ]
: : : | c7 | : ] ]
: *=============*=============* : ]>video
: | c6 | : : : ] ]
: *=========*=============* : *===============* ]
: | c9 |.......:...........:.......| c11 | ]
: *=========* : : *===============* ]
:...............: : : :...............: ]>audio1
:.......................: *=================* : ]
| c13 | : ]
*=================* : ]
:.......................:
"""
# can safely roll within a group
# NOTE: we do not snap to an edge used in the edit
self.assertEdit(
c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 15, 14,
[c6], [c5],
{
c6: {"duration": 6},
c7: {"start": 14, "in-point": 0, "duration": 8},
}, [], [])
self.assertEdit(
c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 16, 17,
[c7], [c3],
{
c6: {"duration": 9},
c7: {"start": 17, "in-point": 3, "duration": 5},
}, [], [])
def test_snap_from_negative(self):
track = self.add_video_track()
c0 = self.add_clip("c0", 0, [track], 0, 20)
c1 = self.add_clip("c1", 0, [track], 100, 10)
g1 = self.add_group("g0", [c0, c1])
snap_to = self.add_clip("snap-to", 2, [track], 4, 50)
self.assertTimelineConfig()
self.timeline.set_snapping_distance(9)
# move without snap would make start edge of c0 go to -5, but this
# edge snaps to the start edge of snap_to, allowing the edit to
# succeed
self.assertEdit(
c1, 1, GES.EditMode.NORMAL, GES.Edge.NONE, 95, 4, [c0], [snap_to],
{
c0 : {"start": 4, "layer": 1},
c1 : {"start": 104, "layer": 1},
g1 : {"start": 4, "layer": 1},
}, [], [])
def test_move_layer(self):
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
*===================*
| c1 |
*===================*
..............g2...............
: :
:.............g0..............:
*=============================*
| c0 |
*=============================*
: :
: :
__________:_____________________________:____________________
layer1____:_____________________________:____________________
: :
*===================* :
| c2 | :
*===================* :
:.............................:
: ...............g1...:..........
: *=============================*
: | c3 |
: *=============================*
__________:_________:_____________________________:__________
layer2____:_________:_____________________________:__________
: : :
: : *===================*
: : | c5 |
: :.........*===================*
*===================* :
| c4 | :
*===================* :
:.............................:
*===================*
| c6 |
*===================*
"""
track = self.add_video_track()
c0 = self.add_clip("c0", 0, [track], 5, 15)
c1 = self.add_clip("c1", 0, [track], 15, 10)
self.register_auto_transition(c0, c1, track)
c2 = self.add_clip("c2", 1, [track], 5, 10)
c3 = self.add_clip("c3", 1, [track], 10, 15)
self.register_auto_transition(c2, c3, track)
c4 = self.add_clip("c4", 2, [track], 5, 10)
c5 = self.add_clip("c5", 2, [track], 15, 10)
c6 = self.add_clip("c6", 2, [track], 10, 10)
self.register_auto_transition(c4, c6, track)
self.register_auto_transition(c6, c5, track)
g0 = self.add_group("g0", [c0, c2])
g1 = self.add_group("g1", [c3, c5])
g2 = self.add_group("g2", [g0, c4])
self.assertTimelineConfig()
layer = self.timeline.get_layer(0)
self.assertIsNotNone(layer)
# don't loose auto-transitions
# clips stay in their layer (groups do not move them)
self.timeline.move_layer(layer, 2)
self.assertTimelineConfig(
{
c0 : {"layer": 2},
c1 : {"layer": 2},
c2 : {"layer": 0},
c3 : {"layer": 0},
c4 : {"layer": 1},
c5 : {"layer": 1},
c6 : {"layer": 1},
g1 : {"layer": 0},
})
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
..............g2...............
: :
:.............g0..............:
*===================* :
| c2 | :
*===================* :
: ..........................:
: : ...............g1..............
: : *=============================*
: : | c3 |
: : *=============================*
__________:___:_____:_____________________________:__________
layer1____:___:_____:_____________________________:__________
: : : :
: : : *===================*
: : : | c5 |
: : : *===================*
: : :.............................:
: :..........g2.....
: g0 :
: : :
*===================*:
| c4 |:
*===================*:
: :................:
: : *===================*
: : | c6 |
: : *===================*
__________:___:______________________________________________
layer2____:___:______________________________________________
: :..........................
*=============================*
| c0 |
*=============================*
:.............................:
:.............................:
*===================*
| c1 |
*===================*
"""
layer = self.timeline.get_layer(1)
self.assertIsNotNone(layer)
self.timeline.move_layer(layer, 0)
self.assertTimelineConfig(
{
c2 : {"layer": 1},
c3 : {"layer": 1},
c4 : {"layer": 0},
c5 : {"layer": 0},
c6 : {"layer": 0},
g0 : {"layer": 1},
})
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
*===================*
| c6 |
*===================*
..............g2...............
*===================* :
| c4 | :
*===================* :
: ...............g1...:..........
: : *===================*
: : | c5 |
: : *===================*
__________:_________:___________________:_________:__________
layer1____:_________:___________________:_________:__________
: : : :
: *=============================*
: | c3 |
: *=============================*
: :...................:.........:
:.............g0..............:
*===================* :
| c2 | :
*===================* :
:.............................:
__________:_____________________________:____________________
layer2____:_____________________________:____________________
: :
*=============================*
| c0 |
*=============================*
:.............................:
:.............................:
*===================*
| c1 |
*===================*
"""
self.timeline.append_layer()
layer = self.timeline.get_layer(3)
self.assertIsNotNone(layer)
self.timeline.move_layer(layer, 1)
self.assertTimelineConfig(
{
c0 : {"layer": 3},
c1 : {"layer": 3},
c2 : {"layer": 2},
c3 : {"layer": 2},
g0 : {"layer": 2},
})
layer = self.timeline.get_layer(3)
self.assertIsNotNone(layer)
self.timeline.move_layer(layer, 0)
self.assertTimelineConfig(
{
c0 : {"layer": 0},
c1 : {"layer": 0},
c2 : {"layer": 3},
c3 : {"layer": 3},
c4 : {"layer": 1},
c5 : {"layer": 1},
c6 : {"layer": 1},
g0 : {"layer": 0},
g1 : {"layer": 1},
})
"""
, . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
0 5 10 15 20 25 30
_____________________________________________________________
layer0_______________________________________________________
*===================*
| c1 |
*===================*
..............g2...............
:.............g0..............:
*=============================*
| c0 |
*=============================*
: ..........................:
__________:___:______________________________________________
layer1____:___:______________________________________________
: :
: :.......g2.......
: g0 :
: : :
*===================*
| c4 |
*===================*
: :...............:
: : *===================*
: : | c6 |
: : *===================*
: : ...............g1..............
: : : *===================*
: : : | c5 |
: : : *===================*
__________:___:_____:_____________________________:__________
layer2____:___:_____:_____________________________:__________
__________:___:_____:_____________________________:__________
layer3____:___:_____:_____________________________:__________
: : : :
: : *=============================*
: : | c3 |
: : *=============================*
: : :.............................:
: :..........................
*===================* :
| c2 | :
*===================* :
:.............................:
:.............................:
"""
layer = self.timeline.get_layer(1)
self.assertTrue(self.timeline.remove_layer(layer))
# TODO: add tests when removing layers:
# FIXME: groups should probably loose their children when they
# are removed from the timeline, which would change g1's
# priority, but currently c5 remains in the group with priority
# of the removed layer
def test_not_snappable(self):
track = self.add_video_track()
c0 = self.add_clip("c0", 0, [track], 0, 10)
no_source = self.add_clip(
"no-source", 0, [], 5, 10, effects=[GES.Effect.new("agingtv")])
effect_clip = self.add_clip(
"effect-clip", 0, [track], 5, 10, clip_type=GES.EffectClip,
asset_id="agingtv || audioecho")
text = self.add_clip(
"text-clip", 0, [track], 5, 10, clip_type=GES.TextOverlayClip)
self.assertTimelineConfig()
self.timeline.set_snapping_distance(20)
self.assertEdit(
c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 8, None,
[], [], {c0 : {"start": 8}}, [], [])
self.assertEdit(
c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 5, None,
[], [], {c0 : {"start": 5}}, [], [])
self.assertEdit(
c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 8, None,
[], [], {c0 : {"duration": 3}}, [], [])
c1 = self.add_clip("c1", 0, [track], 30, 3)
self.assertTimelineConfig()
# end edge snaps to start of c1
self.assertEdit(
c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10, 30,
[c0], [c1], {c0 : {"start": 27}}, [], [])
class TestTransitions(common.GESSimpleTimelineTest):
def test_emission_order_for_transition_clip_added_signal(self):
self.timeline.props.auto_transition = True
unused_clip1 = self.add_clip(0, 0, 100)
clip2 = self.add_clip(100, 0, 100)
# Connect to signals to track in which order they are emitted.
signals = []
def clip_added_cb(layer, clip):
self.assertIsInstance(clip, GES.TransitionClip)
signals.append("clip-added")
self.layer.connect("clip-added", clip_added_cb)
def property_changed_cb(clip, pspec):
self.assertEqual(clip, clip2)
self.assertEqual(pspec.name, "start")
signals.append("notify::start")
clip2.connect("notify::start", property_changed_cb)
# Move clip2 to create a transition with clip1.
clip2.edit([], self.layer.get_priority(),
GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 50)
# The clip-added signal is emitted twice, once for the video
# transition and once for the audio transition.
self.assertEqual(
signals, ["notify::start", "clip-added", "clip-added"])
def create_xges(self):
uri = common.get_asset_uri("png.png")
return """<ges version='0.4'>
<project properties='properties;' metadatas='metadatas, author=(string)&quot;&quot;, 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)&quot;video/x-raw\(ANY\)&quot;, restriction-caps=(string)&quot;video/x-raw\,\ width\=\(int\)1920\,\ height\=\(int\)1080\,\ framerate\=\(fraction\)30/1&quot;, 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)&quot;audio/x-raw\(ANY\)&quot;, restriction-caps=(string)&quot;audio/x-raw\,\ format\=\(string\)S32LE\,\ channels\=\(int\)2\,\ rate\=\(int\)44100\,\ layout\=\(string\)interleaved&quot;, 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))