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