This commit is contained in:
Akkariin Meiko
2022-03-12 03:16:09 +08:00
Unverified
parent 12b76e0c7a
commit 27c4ec74a1
10075 changed files with 5122287 additions and 1 deletions
@@ -0,0 +1,772 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016 Alexandru Băluț <alexandru.balut@gmail.com>
# Copyright (c) 2016, Thibault Saunier
#
# 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 urllib.parse import urlparse
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
from gi.repository import GObject # noqa
import contextlib # noqa
import os #noqa
import unittest # noqa
import tempfile # noqa
try:
gi.require_version("GstTranscoder", "1.0")
from gi.repository import GstTranscoder
except ValueError:
GstTranscoder = None
Gst.init(None)
GES.init()
def create_main_loop():
"""Creates a MainLoop with a timeout."""
mainloop = GLib.MainLoop()
timed_out = False
def timeout_cb(unused):
nonlocal timed_out
timed_out = True
mainloop.quit()
def run(timeout_seconds=5, until_empty=False):
source = GLib.timeout_source_new_seconds(timeout_seconds)
source.set_callback(timeout_cb)
source.attach()
if until_empty:
GLib.idle_add(mainloop.quit)
GLib.MainLoop.run(mainloop)
source.destroy()
if timed_out:
raise Exception("Timed out after %s seconds" % timeout_seconds)
mainloop.run = run
return mainloop
def create_project(with_group=False, saved=False):
"""Creates a project with two clips in a group."""
timeline = GES.Timeline.new_audio_video()
layer = timeline.append_layer()
if with_group:
clip1 = GES.TitleClip()
clip1.set_start(0)
clip1.set_duration(10*Gst.SECOND)
layer.add_clip(clip1)
clip2 = GES.TitleClip()
clip2.set_start(100 * Gst.SECOND)
clip2.set_duration(10*Gst.SECOND)
layer.add_clip(clip2)
group = GES.Container.group([clip1, clip2])
if saved:
if isinstance(saved, str):
suffix = "-%s.xges" % saved
else:
suffix = ".xges"
uri = "file://%s" % tempfile.NamedTemporaryFile(suffix=suffix).name
timeline.get_asset().save(timeline, uri, None, overwrite=True)
return timeline
@contextlib.contextmanager
def created_project_file(xges):
_, xges_path = tempfile.mkstemp(suffix=".xges")
with open(xges_path, "w") as f:
f.write(xges)
yield Gst.filename_to_uri(os.path.abspath(xges_path))
os.remove(xges_path)
def can_generate_assets():
if GstTranscoder is None:
return False, "GstTranscoder is not available"
if not Gst.ElementFactory.make("testsrcbin"):
return False, "testbinsrc is not available"
return True, None
@contextlib.contextmanager
def created_video_asset(uri=None, num_bufs=30, framerate="30/1"):
with tempfile.NamedTemporaryFile(suffix=".ogg") as f:
if not uri:
uri = Gst.filename_to_uri(f.name)
name = f.name
else:
name = urlparse(uri).path
pipe = Gst.parse_launch(f"videotestsrc num-buffers={num_bufs} ! video/x-raw,framerate={framerate} ! theoraenc ! oggmux ! filesink location={name}")
pipe.set_state(Gst.State.PLAYING)
assert pipe.get_bus().timed_pop_filtered(Gst.CLOCK_TIME_NONE, Gst.MessageType.EOS)
pipe.set_state(Gst.State.NULL)
yield uri
def get_asset_uri(name):
python_tests_dir = os.path.dirname(os.path.abspath(__file__))
assets_dir = os.path.join(python_tests_dir, "..", "assets")
return Gst.filename_to_uri(os.path.join(assets_dir, name))
class GESTest(unittest.TestCase):
def _log(self, func, format, *args):
string = format
if args:
string = string % args[0]
func(string)
def log(self, format, *args):
self._log(Gst.log, format, *args)
def debug(self, format, *args):
self._log(Gst.debug, format, *args)
def info(self, format, *args):
self._log(Gst.info, format, *args)
def fixme(self, format, *args):
self._log(Gst.fixme, format, *args)
def warning(self, format, *args):
self._log(Gst.warning, format, *args)
def error(self, format, *args):
self._log(Gst.error, format, *args)
def check_clip_values(self, clip, start, in_point, duration):
for elem in [clip] + clip.get_children(False):
self.check_element_values(elem, start, in_point, duration)
def check_element_values(self, element, start, in_point, duration):
self.assertEqual(element.props.start, start, element)
self.assertEqual(element.props.in_point, in_point, element)
self.assertEqual(element.props.duration, duration, element)
def assert_effects(self, clip, *effects):
# Make sure there are no other effects.
self.assertEqual(set(clip.get_top_effects()), set(effects))
# Make sure their order is correct.
indexes = [clip.get_top_effect_index(effect)
for effect in effects]
self.assertEqual(indexes, list(range(len(effects))))
def assertGESError(self, error, code, message=""):
if error is None:
raise AssertionError(
"{}{}Received no error".format(message, message and ": "))
if error.domain != "GES_ERROR":
raise AssertionError(
"{}{}Received error ({}) in domain {} rather than "
"GES_ERROR".format(
message, message and ": ", error.message, error.domain))
err_code = GES.Error(error.code)
if err_code != code:
raise AssertionError(
"{}{}Received {} error ({}) rather than {}".format(
message, message and ": ", err_code.value_name,
error.message, code.value_name))
class GESSimpleTimelineTest(GESTest):
def __init__(self, *args):
self.track_types = [GES.TrackType.AUDIO, GES.TrackType.VIDEO]
super(GESSimpleTimelineTest, self).__init__(*args)
def timeline_as_str(self):
res = "====== %s =======\n" % self.timeline
for layer in self.timeline.get_layers():
res += "Layer %04d: " % layer.get_priority()
for clip in layer.get_clips():
res += "{ %s }" % clip
res += '\n------------------------\n'
for group in self.timeline.get_groups():
res += "GROUP %s :" % group
for clip in group.get_children(False):
res += " { %s }" % clip.props.name
res += '\n'
res += "================================\n"
return res
def print_timeline(self):
print(self.timeline_as_str())
def setUp(self):
self.timeline = GES.Timeline.new()
for track_type in self.track_types:
self.assertIn(
track_type, [GES.TrackType.AUDIO, GES.TrackType.VIDEO])
if track_type == GES.TrackType.AUDIO:
self.assertTrue(self.timeline.add_track(GES.AudioTrack.new()))
else:
self.assertTrue(self.timeline.add_track(GES.VideoTrack.new()))
self.assertEqual(len(self.timeline.get_tracks()),
len(self.track_types))
self.layer = self.timeline.append_layer()
def add_clip(self, start, in_point, duration, asset_type=GES.TestClip):
clip = GES.Asset.request(asset_type, None).extract()
clip.props.start = start
clip.props.in_point = in_point
clip.props.duration = duration
self.assertTrue(self.layer.add_clip(clip))
return clip
def append_clip(self, layer=0, asset_type=GES.TestClip, asset_id=None):
while len(self.timeline.get_layers()) < layer + 1:
self.timeline.append_layer()
layer = self.timeline.get_layers()[layer]
if asset_type == GES.UriClip:
asset = GES.UriClipAsset.request_sync(asset_id)
else:
asset = GES.Asset.request(asset_type, asset_id)
clip = asset.extract()
clip.props.start = layer.get_duration()
clip.props.duration = 10
self.assertTrue(layer.add_clip(clip))
return clip
def assertElementAreEqual(self, ref, element):
self.assertTrue(isinstance(element, type(ref)), "%s and %s do not have the same type!" % (ref, element))
props = [p for p in ref.list_properties() if p.name not in ['name']
and not GObject.type_is_a(p.value_type, GObject.Object)]
for p in props:
pname = p.name
refval = GObject.Value()
refval.init(p.value_type)
refval.set_value(ref.get_property(pname))
value = GObject.Value()
value.init(p.value_type)
value.set_value(element.get_property(pname))
self.assertTrue(Gst.value_compare(refval, value) == Gst.VALUE_EQUAL,
"%s are not equal: %s != %s\n %s != %s" % (pname, value, refval, element, ref))
if isinstance(ref, GES.TrackElement):
self.assertElementAreEqual(ref.get_nleobject(), element.get_nleobject())
return
if not isinstance(ref, GES.Clip):
return
ttypes = [track.type for track in self.timeline.get_tracks()]
for ttype in ttypes:
if ttypes.count(ttype) > 1:
self.warning("Can't deeply check %s and %s "
"(only one track per type supported %s %s found)" % (ref,
element, ttypes.count(ttype), ttype))
return
children = element.get_children(False)
for ref_child in ref.get_children(False):
ref_track = ref_child.get_track()
if not ref_track:
self.warning("Can't check %s as not in a track" % (ref_child))
continue
child = None
for tmpchild in children:
if not isinstance(tmpchild, type(ref_child)):
continue
if ref_track.type != tmpchild.get_track().type:
continue
if not isinstance(ref_child, GES.Effect):
child = tmpchild
break
elif ref_child.props.bin_description == tmpchild.props.bin_description:
child = tmpchild
break
self.assertIsNotNone(child, "Could not find equivalent child %s in %s(%s)" % (ref_child,
element, children))
self.assertElementAreEqual(ref_child, child)
def check_reload_timeline(self):
tmpf = tempfile.NamedTemporaryFile(suffix='.xges')
uri = Gst.filename_to_uri(tmpf.name)
self.assertTrue(self.timeline.save_to_uri(uri, None, True))
project = GES.Project.new(uri)
mainloop = create_main_loop()
def loaded_cb(unused_project, unused_timeline):
mainloop.quit()
project.connect("loaded", loaded_cb)
reloaded_timeline = project.extract()
mainloop.run()
self.assertIsNotNone(reloaded_timeline)
layers = self.timeline.get_layers()
reloaded_layers = reloaded_timeline.get_layers()
self.assertEqual(len(layers), len(reloaded_layers))
for layer, reloaded_layer in zip(layers, reloaded_layers):
clips = layer.get_clips()
reloaded_clips = reloaded_layer.get_clips()
self.assertEqual(len(clips), len(reloaded_clips))
for clip, reloaded_clip in zip(clips, reloaded_clips):
self.assertElementAreEqual(clip, reloaded_clip)
return reloaded_timeline
def assertTimelineTopology(self, topology, groups=[]):
res = []
for layer in self.timeline.get_layers():
layer_timings = []
for clip in layer.get_clips():
layer_timings.append(
(type(clip), clip.props.start, clip.props.duration))
for child in clip.get_children(True):
self.assertEqual(child.props.start, clip.props.start)
self.assertEqual(child.props.duration, clip.props.duration)
res.append(layer_timings)
if topology != res:
Gst.error(self.timeline_as_str())
self.assertEqual(topology, res)
timeline_groups = self.timeline.get_groups()
if groups and timeline_groups:
for i, group in enumerate(groups):
self.assertEqual(set(group), set(timeline_groups[i].get_children(False)))
self.assertEqual(len(timeline_groups), i + 1)
return res
class GESTimelineConfigTest(GESTest):
"""
Tests where all the configuration changes, snapping positions and
auto-transitions are accounted for.
"""
def setUp(self):
timeline = GES.Timeline.new()
self.timeline = timeline
timeline.set_auto_transition(True)
self.snap_occured = False
self.snap = None
def snap_started(tl, el1, el2, pos):
if self.snap_occured:
raise AssertionError(
"Previous snap {} not accounted for".format(self.snap))
self.snap_occured = True
if self.snap is not None:
raise AssertionError(
"Previous snap {} not ended".format(self.snap))
self.snap = (el1.get_parent(), el2.get_parent(), pos)
def snap_ended(tl, el1, el2, pos):
self.assertEqual(
self.snap, (el1.get_parent(), el2.get_parent(), pos))
self.snap = None
timeline.connect("snapping-started", snap_started)
timeline.connect("snapping-ended", snap_ended)
self.lost_clips = []
def unrecord_lost_clip(layer, clip):
if clip in self.lost_clips:
self.lost_clips.remove(clip)
def record_lost_clip(layer, clip):
self.lost_clips.append(clip)
def layer_added(tl, layer):
layer.connect("clip-added", unrecord_lost_clip)
layer.connect("clip-removed", record_lost_clip)
timeline.connect("layer-added", layer_added)
self.clips = []
self.auto_transitions = {}
self.config = {}
@staticmethod
def new_config(start, duration, inpoint, maxduration, layer):
return {"start": start, "duration": duration, "in-point": inpoint,
"max-duration": maxduration, "layer": layer}
def add_clip(self, name, layer, tracks, start, duration, inpoint=0,
maxduration=Gst.CLOCK_TIME_NONE, clip_type=GES.TestClip,
asset_id=None, effects=None):
"""
Create a clip with the given @name and properties and add it to the
layer of priority @layer to the tracks in @tracks. Also registers
its expected configuration.
"""
if effects is None:
effects = []
lay = self.timeline.get_layer(layer)
while lay is None:
self.timeline.append_layer()
lay = self.timeline.get_layer(layer)
asset = GES.Asset.request(clip_type, asset_id)
clip = asset.extract()
self.assertTrue(clip.set_name(name))
# FIXME: would be better to use select-tracks-for-object
# hack around the fact that we cannot use select-tracks-for-object
# in python by setting start to large number to ensure no conflict
# when adding a clip
self.assertTrue(clip.set_start(10000))
self.assertTrue(clip.set_duration(duration))
self.assertTrue(clip.set_inpoint(inpoint))
for effect in effects:
self.assertTrue(clip.add(effect))
if lay.add_clip(clip) != True:
raise AssertionError(
"Failed to add clip {} to layer {}".format(name, layer))
# then remove the children not in the selected tracks, which may
# now allow some clips to fully/triple overlap because they do
# not share a track
for child in clip.get_children(False):
if child.get_track() not in tracks:
clip.remove(child)
# then move to the desired start
prev_snap = self.timeline.get_snapping_distance()
self.timeline.set_snapping_distance(0)
self.assertTrue(clip.set_start(start))
self.timeline.set_snapping_distance(prev_snap)
self.assertTrue(clip.set_max_duration(maxduration))
self.config[clip] = self.new_config(
start, duration, inpoint, maxduration, layer)
self.clips.append(clip)
return clip
def add_group(self, name, to_group):
"""
Create a group with the given @name and the elements in @to_group.
Also registers its expected configuration.
"""
group = GES.Group.new()
self.assertTrue(group.set_name(name))
start = None
end = None
layer = None
for element in to_group:
if start is None:
start = element.start
end = element.start + element.duration
layer = element.get_layer_priority()
else:
start = min(start, element.start)
end = max(end, element.start + element.duration)
layer = min(layer, element.get_layer_priority())
self.assertTrue(group.add(element))
self.config[group] = self.new_config(
start, end - start, 0, Gst.CLOCK_TIME_NONE, layer)
return group
def register_auto_transition(self, clip1, clip2, track):
"""
Register that we expect an auto-transition to exist between
@clip1 and @clip2 in @track.
"""
transition = self._find_transition(clip1, clip2, track)
if transition is None:
raise AssertionError(
"{} and {} have no auto-transition in track {}".format(
clip1, clip2, track))
if transition in self.auto_transitions.values():
raise AssertionError(
"Auto-transition between {} and {} in track {} already "
"registered".format(clip1, clip2, track))
key = (clip1, clip2, track)
if key in self.auto_transitions:
raise AssertionError(
"Auto-transition already registered for {}".format(key))
self.auto_transitions[key] = transition
def add_video_track(self):
track = GES.VideoTrack.new()
self.assertTrue(self.timeline.add_track(track))
return track
def add_audio_track(self):
track = GES.AudioTrack.new()
self.assertTrue(self.timeline.add_track(track))
return track
def assertElementConfig(self, element, config):
for prop in config:
if prop == "layer":
val = element.get_layer_priority()
else:
val = element.get_property(prop)
if val != config[prop]:
raise AssertionError("{} property {}: {} != {}".format(
element, prop, val, config[prop]))
@staticmethod
def _source_in_track(clip, track):
if clip.find_track_element(track, GES.Source):
return True
return False
def _find_transition(self, clip1, clip2, track):
"""find transition from earlier clip1 to later clip2"""
if not self._source_in_track(clip1, track) or \
not self._source_in_track(clip2, track):
return None
layer_prio = clip1.get_layer_priority()
if layer_prio != clip2.get_layer_priority():
return None
if clip1.start >= clip2.start:
return None
start = clip2.start
end = clip1.start + clip1.duration
if start >= end:
return None
duration = end - start
layer = self.timeline.get_layer(layer_prio)
self.assertIsNotNone(layer)
for clip in layer.get_clips():
children = clip.get_children(False)
if len(children) == 1:
child = children[0]
else:
continue
if isinstance(clip, GES.TransitionClip) and clip.start == start \
and clip.duration == duration and child.get_track() == track:
return clip
raise AssertionError(
"No auto-transition between {} and {} in track {}".format(
clip1, clip2, track))
def _transition_between(self, new, existing, clip1, clip2, track):
if clip1.start < clip2.start:
entry = (clip1, clip2, track)
else:
entry = (clip2, clip1, track)
trans = self._find_transition(*entry)
if trans is None:
return
if entry in new:
new.remove(entry)
self.auto_transitions[entry] = trans
elif entry in existing:
existing.remove(entry)
expect = self.auto_transitions[entry]
if trans != expect:
raise AssertionError(
"Auto-transition between {} and {} in track {} changed "
"from {} to {}".format(
clip1, clip2, track, expect, trans))
else:
raise AssertionError(
"Unexpected transition found between {} and {} in track {}"
"".format(clip1, clip2, track))
def assertTimelineConfig(
self, new_props=None, snap_position=None, snap_froms=None,
snap_tos=None, new_transitions=None, lost_transitions=None):
"""
Check that the timeline configuration has only changed by the
differences present in @new_props.
Check that a snap occurred at @snap_position between one of the
clips in @snap_froms and one of the clips in @snap_tos.
Check that all new transitions in the timeline are present in
@new_transitions.
Checl that all the transitions that were lost are in
@lost_transitions.
"""
if new_props is None:
new_props = {}
if snap_froms is None:
snap_froms = []
if snap_tos is None:
snap_tos = []
if new_transitions is None:
new_transitions = []
if lost_transitions is None:
lost_transitions = []
for element, config in new_props.items():
if element not in self.config:
self.config[element] = {}
for prop in config:
self.config[element][prop] = new_props[element][prop]
for element, config in self.config.items():
self.assertElementConfig(element, config)
# check that snapping occurred
snaps = []
for snap_from in snap_froms:
for snap_to in snap_tos:
snaps.append((snap_from, snap_to, snap_position))
if self.snap is None:
if snaps:
raise AssertionError(
"No snap occurred, but expected a snap in {}".format(snaps))
elif not snaps:
if self.snap_occured:
raise AssertionError(
"Snap {} occurred, but expected no snap".format(self.snap))
elif self.snap not in snaps:
raise AssertionError(
"Snap {} occurred, but expected a snap in {}".format(
self.snap, snaps))
self.snap_occured = False
# check that lost transitions are not part of the layer
for clip1, clip2, track in lost_transitions:
key = (clip1, clip2, track)
if key not in self.auto_transitions:
raise AssertionError(
"No such auto-transition between {} and {} in track {} "
"is registered".format(clip1, clip2, track))
# make sure original transition was removed from the layer
trans = self.auto_transitions[key]
if trans not in self.lost_clips:
raise AssertionError(
"The auto-transition {} between {} and {} track {} was "
"not removed from the layers, but expect it to be lost"
"".format(trans, clip1, clip2, track))
self.lost_clips.remove(trans)
# make sure a new one wasn't created
trans = self._find_transition(clip1, clip2, track)
if trans is not None:
raise AssertionError(
"Found auto-transition between {} and {} in track {} "
"is present, but expected it to be lost".format(
clip1, clip2, track))
# since it was lost, remove it
del self.auto_transitions[key]
# check that all lost clips are accounted for
if self.lost_clips:
raise AssertionError(
"Clips were lost that are not accounted for: {}".format(
self.lost_clips))
# check that all other transitions are either existing ones or
# new ones
new = set(new_transitions)
existing = set(self.auto_transitions.keys())
for i, clip1 in enumerate(self.clips):
for clip2 in self.clips[i+1:]:
for track in self.timeline.get_tracks():
self._transition_between(
new, existing, clip1, clip2, track)
# make sure we are not missing any expected transitions
if new:
raise AssertionError(
"Did not find new transitions for {}".format(new))
if existing:
raise AssertionError(
"Did not find existing transitions for {}".format(existing))
# make sure there aren't any clips we are unaware of
transitions = self.auto_transitions.values()
for layer in self.timeline.get_layers():
for clip in layer.get_clips():
if clip not in self.clips and clip not in transitions:
raise AssertionError("Unknown clip {}".format(clip))
def assertEdit(self, element, layer, mode, edge, position, snap,
snap_froms, snap_tos, new_props, new_transitions,
lost_transitions):
if not element.edit_full(layer, mode, edge, position):
raise AssertionError(
"Edit of {} to layer {}, mode {}, edge {}, at position {} "
"failed when a success was expected".format(
element, layer, mode, edge, position))
self.assertTimelineConfig(
new_props=new_props, snap_position=snap, snap_froms=snap_froms,
snap_tos=snap_tos, new_transitions=new_transitions,
lost_transitions=lost_transitions)
def assertFailEdit(self, element, layer, mode, edge, position, err_code):
res = None
error = None
try:
res = element.edit_full(layer, mode, edge, position)
except GLib.Error as exception:
error = exception
if err_code is None:
if res is not False:
raise AssertionError(
"Edit of {} to layer {}, mode {}, edge {}, at "
"position {} succeeded when a failure was expected"
"".format(
element, layer, mode, edge, position))
if error is not None:
raise AssertionError(
"Edit of {} to layer {}, mode {}, edge {}, at "
"position {} did produced an error when none was "
"expected".format(
element, layer, mode, edge, position))
else:
self.assertGESError(
error, err_code,
"Edit of {} to layer {}, mode {}, edge {}, at "
"position {}".format(element, layer, mode, edge, position))
# should be no change or snapping if edit fails
self.assertTimelineConfig()
@@ -0,0 +1,25 @@
import os
import gi.overrides
LOCAL_OVERRIDE_PATH = "gst-editing-services/bindings/python/gi/overrides/"
FILE = os.path.realpath(__file__)
if not gi.overrides.__path__[0].endswith(LOCAL_OVERRIDE_PATH):
local_overrides = None
# our overrides don't take precedence, let's fix it
for i, path in enumerate(gi.overrides.__path__):
if path.endswith(LOCAL_OVERRIDE_PATH):
local_overrides = path
if local_overrides:
gi.overrides.__path__.remove(local_overrides)
else:
local_overrides = os.path.abspath(os.path.join(FILE, "../../../../../", LOCAL_OVERRIDE_PATH))
gi.overrides.__path__.insert(0, local_overrides)
# Execute previously set sitecustomize.py script if it existed
if os.environ.get("GST_ENV"):
old_sitecustomize = os.path.join(os.path.dirname(__file__),
"old.sitecustomize.gstuninstalled.py")
if os.path.exists(old_sitecustomize):
exec(compile(open(old_sitecustomize).read(), old_sitecustomize, 'exec'))
@@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019 Thibault Saunier <tsaunier@igalia.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 os
import gi
import tempfile
gi.require_version("Gst", "1.0")
gi.require_version("GES", "1.0")
from gi.repository import Gst # noqa
from gi.repository import GLib # noqa
from gi.repository import GES # noqa
import unittest # noqa
from unittest import mock
from . import common
from .common import GESSimpleTimelineTest # noqa
Gst.init(None)
GES.init()
class TestTimeline(GESSimpleTimelineTest):
def test_request_relocated_assets_sync(self):
path = os.path.join(__file__, "../../../", "png.png")
with self.assertRaises(GLib.Error):
GES.UriClipAsset.request_sync(Gst.filename_to_uri(path))
GES.add_missing_uri_relocation_uri(Gst.filename_to_uri(os.path.join(__file__, "../../assets")), False)
path = os.path.join(__file__, "../../", "png.png")
self.assertEqual(GES.UriClipAsset.request_sync(Gst.filename_to_uri(path)).props.id,
Gst.filename_to_uri(os.path.join(__file__, "../../assets/png.png")))
def test_request_relocated_twice(self):
GES.add_missing_uri_relocation_uri(Gst.filename_to_uri(os.path.join(__file__, "../../")), True)
proj = GES.Project.new()
asset = proj.create_asset_sync("file:///png.png", GES.UriClip)
self.assertIsNotNone(asset)
asset = proj.create_asset_sync("file:///png.png", GES.UriClip)
self.assertIsNotNone(asset)
@unittest.skipUnless(*common.can_generate_assets())
def test_reload_asset(self):
with common.created_video_asset(num_bufs=2, framerate="2/1") as uri:
asset0 = GES.UriClipAsset.request_sync(uri)
self.assertEqual(asset0.props.duration, Gst.SECOND)
with common.created_video_asset(uri, 4, framerate="2/1") as uri:
GES.Asset.needs_reload(GES.UriClip, uri)
asset1 = GES.UriClipAsset.request_sync(uri)
self.assertEqual(asset1.props.duration, 2 * Gst.SECOND)
self.assertEqual(asset1, asset0)
with common.created_video_asset(uri, 6, framerate="2/1") as uri:
mainloop = common.create_main_loop()
def asset_loaded_cb(_, res, mainloop):
asset2 = GES.Asset.request_finish(res)
self.assertEqual(asset2.props.duration, 3 * Gst.SECOND)
self.assertEqual(asset2, asset0)
mainloop.quit()
GES.Asset.needs_reload(GES.UriClip, uri)
GES.Asset.request_async(GES.UriClip, uri, None, asset_loaded_cb, mainloop)
mainloop.run()
def test_asset_metadata_on_reload(self):
mainloop = GLib.MainLoop()
unused, xges_path = tempfile.mkstemp(suffix=".xges")
project_uri = Gst.filename_to_uri(os.path.abspath(xges_path))
asset_uri = Gst.filename_to_uri(os.path.join(__file__, "../../assets/audio_video.ogg"))
xges = """<ges version='0.3'>
<project properties='properties;' metadatas='metadatas;'>
<ressources>
<asset id='%(uri)s' extractable-type-name='GESUriClip' properties='properties, supported-formats=(int)6, duration=(guint64)2003000000;' metadatas='metadatas, container-format=(string)Matroska, language-code=(string)und, application-name=(string)Lavc56.60.100, encoder-version=(uint)0, audio-codec=(string)Vorbis, nominal-bitrate=(uint)80000, bitrate=(uint)80000, video-codec=(string)&quot;On2\ VP8&quot;, file-size=(guint64)223340, foo=(string)bar;' >
</asset>
</ressources>
</project>
</ges>"""% {"uri": asset_uri}
with open(xges_path, "w") as xges_file:
xges_file.write(xges)
def loaded_cb(project, timeline):
asset = project.list_assets(GES.Extractable)[0]
self.assertEqual(asset.get_meta("foo"), "bar")
mainloop.quit()
loaded_project = GES.Project(uri=project_uri, extractable_type=GES.Timeline)
loaded_project.connect("loaded", loaded_cb)
timeline = loaded_project.extract()
mainloop.run()
@@ -0,0 +1,378 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2015, Thibault Saunier
#
# 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 os
import tempfile
import gi
gi.require_version("Gst", "1.0")
gi.require_version("GES", "1.0")
from gi.repository import Gst # noqa
Gst.init(None) # noqa
from gi.repository import GES # noqa
GES.init()
from . import common # noqa
import unittest # noqa
class TestCopyPaste(unittest.TestCase):
def setUp(self):
self.timeline = GES.Timeline.new_audio_video()
self.assertEqual(len(self.timeline.get_tracks()), 2)
self.layer = self.timeline.append_layer()
def testCopyClipRemoveAndPaste(self):
clip1 = GES.TestClip.new()
clip1.props.duration = 10
self.layer.add_clip(clip1)
self.assertEqual(len(clip1.get_children(False)), 2)
copy = clip1.copy(True)
self.assertEqual(len(self.layer.get_clips()), 1)
self.layer.remove_clip(clip1)
copy.paste(10)
self.assertEqual(len(self.layer.get_clips()), 1)
def testCopyPasteTitleClip(self):
clip1 = GES.TitleClip.new()
clip1.props.duration = 10
self.layer.add_clip(clip1)
self.assertEqual(len(clip1.get_children(False)), 1)
copy = clip1.copy(True)
self.assertEqual(len(self.layer.get_clips()), 1)
copy.paste(10)
self.assertEqual(len(self.layer.get_clips()), 2)
class TestTransitionClip(unittest.TestCase):
def test_serialize_invert(self):
timeline = GES.Timeline.new()
timeline.add_track(GES.VideoTrack.new())
layer = timeline.append_layer()
clip1 = GES.TransitionClip.new_for_nick("crossfade")
clip1.props.duration = Gst.SECOND
self.assertTrue(layer.add_clip(clip1))
vtransition, = clip1.children
vtransition.set_inverted(True)
self.assertEqual(vtransition.props.invert, True)
with tempfile.NamedTemporaryFile() as tmpxges:
uri = Gst.filename_to_uri(tmpxges.name)
timeline.save_to_uri(uri, None, True)
timeline = GES.Timeline.new_from_uri(uri)
project = timeline.get_asset()
mainloop = common.create_main_loop()
project.connect("loaded", lambda _, __: mainloop.quit())
mainloop.run()
self.assertIsNotNone(timeline)
layer, = timeline.get_layers()
clip, = layer.get_clips()
vtransition, = clip.children
self.assertEqual(vtransition.props.invert, True)
class TestTitleClip(unittest.TestCase):
def testSetColor(self):
timeline = GES.Timeline.new_audio_video()
clip = GES.TitleClip.new()
timeline.append_layer().add_clip(clip )
self.assertTrue(clip.set_child_property('color', 1))
self.assertTrue(clip.set_child_property('color', 4294967295))
def testGetPropertyNotInTrack(self):
title_clip = GES.TitleClip.new()
self.assertEqual(title_clip.props.text, "")
self.assertEqual(title_clip.props.font_desc, "Serif 36")
def test_split_effect(self):
timeline = GES.Timeline.new()
timeline.add_track(GES.VideoTrack.new())
layer = timeline.append_layer()
clip1 = GES.TitleClip.new()
clip1.props.duration = Gst.SECOND
self.assertTrue(layer.add_clip(clip1))
effect = GES.Effect.new("agingtv")
self.assertTrue(clip1.add(effect))
children1 = clip1.get_children(True)
self.assertNotEqual(children1[0].props.priority,
children1[1].props.priority)
clip2 = clip1.split(Gst.SECOND / 2)
children1 = clip1.get_children(True)
self.assertNotEqual(children1[0].props.priority,
children1[1].props.priority)
children2 = clip2.get_children(True)
self.assertNotEqual(children2[0].props.priority,
children2[1].props.priority)
class TestUriClip(common.GESSimpleTimelineTest):
def test_max_duration_on_extract(self):
asset = GES.UriClipAsset.request_sync(common.get_asset_uri("audio_video.ogg"))
clip = asset.extract()
self.assertEqual(clip.props.max_duration, Gst.SECOND)
class TestTrackElements(common.GESSimpleTimelineTest):
def test_add_to_layer_with_effect_remove_add(self):
timeline = GES.Timeline.new_audio_video()
video_track, audio_track = timeline.get_tracks()
layer = timeline.append_layer()
test_clip = GES.TestClip()
self.assertEqual(test_clip.get_children(True), [])
self.assertTrue(layer.add_clip(test_clip))
audio_source = test_clip.find_track_element(None, GES.AudioSource)
video_source = test_clip.find_track_element(None, GES.VideoSource)
self.assertTrue(test_clip.set_child_property("volume", 0.0))
self.assertEqual(audio_source.get_child_property("volume")[1], 0.0)
effect = GES.Effect.new("agingtv")
test_clip.add(effect)
self.assertEqual(audio_source.props.track, audio_track)
self.assertEqual(video_source.props.track, video_track)
self.assertEqual(effect.props.track, video_track)
children = test_clip.get_children(True)
layer.remove_clip(test_clip)
self.assertEqual(test_clip.get_children(True), children)
self.assertEqual(audio_source.props.track, None)
self.assertEqual(video_source.props.track, None)
self.assertEqual(effect.props.track, None)
self.assertTrue(layer.add_clip(test_clip))
self.assertEqual(test_clip.get_children(True), children)
self.assertEqual(audio_source.props.track, audio_track)
self.assertEqual(video_source.props.track, video_track)
self.assertEqual(effect.props.track, video_track)
audio_source = test_clip.find_track_element(None, GES.AudioSource)
self.assertFalse(audio_source is None)
self.assertEqual(audio_source.get_child_property("volume")[1], 0.0)
self.assertEqual(audio_source.props.track, audio_track)
self.assertEqual(video_source.props.track, video_track)
self.assertEqual(effect.props.track, video_track)
def test_effects_priority(self):
timeline = GES.Timeline.new_audio_video()
layer = timeline.append_layer()
test_clip = GES.TestClip.new()
layer.add_clip(test_clip)
self.assert_effects(test_clip)
effect1 = GES.Effect.new("agingtv")
test_clip.add(effect1)
self.assert_effects(test_clip, effect1)
test_clip.set_top_effect_index(effect1, 1)
self.assert_effects(test_clip, effect1)
test_clip.set_top_effect_index(effect1, 10)
self.assert_effects(test_clip, effect1)
effect2 = GES.Effect.new("dicetv")
test_clip.add(effect2)
self.assert_effects(test_clip, effect1, effect2)
test_clip.remove(effect1)
self.assert_effects(test_clip, effect2)
def test_effects_index(self):
timeline = GES.Timeline.new_audio_video()
layer = timeline.append_layer()
test_clip = GES.TestClip.new()
layer.add_clip(test_clip)
self.assert_effects(test_clip)
ref_effects_list = []
def add_effect(effect):
test_clip.add(effect)
ref_effects_list.append(effect)
self.assert_effects(test_clip, *ref_effects_list)
def move_effect(old_index, new_index):
effect = ref_effects_list[old_index]
self.assertTrue(test_clip.set_top_effect_index(effect, new_index))
ref_effects_list.insert(new_index, ref_effects_list.pop(old_index))
self.assert_effects(test_clip, *ref_effects_list)
effects = ["agingtv", "dicetv", "burn", "gamma", "edgetv", "alpha",
"exclusion", "chromahold", "coloreffects", "videobalance"]
for effect in effects:
add_effect(GES.Effect.new(effect))
move_effect(3, 8)
move_effect(5, 6)
move_effect(0, 9)
self.assert_effects(test_clip, *ref_effects_list)
def test_signal_order_when_removing_effect(self):
timeline = GES.Timeline.new_audio_video()
layer = timeline.append_layer()
test_clip = GES.TestClip.new()
layer.add_clip(test_clip)
self.assert_effects(test_clip)
effect1 = GES.Effect.new("agingtv")
test_clip.add(effect1)
effect2 = GES.Effect.new("dicetv")
test_clip.add(effect2)
self.assert_effects(test_clip, effect1, effect2)
mainloop = common.create_main_loop()
signals = []
def handler_cb(*args):
signals.append(args[-1])
test_clip.connect("child-removed", handler_cb, "child-removed")
effect2.connect("notify::priority", handler_cb, "notify::priority")
test_clip.remove(effect1)
test_clip.disconnect_by_func(handler_cb)
effect2.disconnect_by_func(handler_cb)
self.assert_effects(test_clip, effect2)
mainloop.run(until_empty=True)
self.assertEqual(signals, ["child-removed", "notify::priority"])
def test_moving_core_track_elements(self):
clip = self.append_clip()
clip1 = self.append_clip()
title_clip = self.append_clip(asset_type=GES.TitleClip)
track_element = clip.find_track_element(None, GES.VideoSource)
self.assertTrue(clip.remove(track_element))
track_element1 = clip1.find_track_element(None, GES.VideoSource)
self.assertTrue(clip1.remove(track_element1))
self.assertTrue(clip1.add(track_element))
self.assertIsNotNone(track_element.get_track())
# We can add another TestSource to the clip as it has the same parent
# asset
self.assertTrue(clip1.add(track_element1))
# We already have a core TrackElement for the video track, not adding
# a second one.
self.assertIsNone(track_element1.get_track())
clip1.remove(track_element)
clip1.remove(track_element1)
title = title_clip.find_track_element(None, GES.VideoSource)
self.assertTrue(title_clip.remove(title))
# But we can't add an element that has been created by a TitleClip
self.assertFalse(clip.add(title))
self.assertFalse(title_clip.add(track_element))
self.assertTrue(clip.add(track_element))
self.assertTrue(clip1.add(track_element1))
def test_ungroup_regroup(self):
clip = self.append_clip()
children = clip.get_children(True)
clip1, clip2 = GES.Container.ungroup(clip, True)
self.assertEqual(clip, clip1)
clip1_child, = clip1.get_children(True)
clip2_child, = clip2.get_children(True)
self.assertCountEqual (children, [clip1_child, clip2_child])
# can freely move children between the ungrouped clips
self.assertTrue(clip1.remove(clip1_child))
self.assertTrue(clip2.add(clip1_child))
self.assertTrue(clip2.remove(clip2_child))
self.assertTrue(clip1.add(clip2_child))
grouped = GES.Container.group([clip1, clip2])
self.assertEqual(grouped, clip1)
self.assertCountEqual(clip1.get_children(True),
[clip1_child, clip2_child])
self.assertEqual(clip2.get_children(True), [])
# can freely move children between the grouped clips
self.assertTrue(clip1.remove(clip2_child))
self.assertTrue(clip2.add(clip2_child))
self.assertTrue(clip1.remove(clip1_child))
self.assertTrue(clip2.add(clip1_child))
self.assertTrue(clip2.remove(clip1_child))
self.assertTrue(clip1.add(clip1_child))
self.assertTrue(clip2.remove(clip2_child))
self.assertTrue(clip1.add(clip2_child))
# clip2 no longer part of the timeline
self.assertIsNone(clip2.props.layer)
self.assertEqual(clip1.props.layer, self.layer)
self.assertIsNone(clip2.props.timeline)
self.assertEqual(clip1.props.timeline, self.timeline)
def test_image_source_asset(self):
asset = GES.UriClipAsset.request_sync(common.get_asset_uri("png.png"))
clip = self.layer.add_asset(asset, 0, 0, Gst.SECOND, GES.TrackType.UNKNOWN)
image_src, = clip.get_children(True)
self.assertTrue(image_src.get_asset().is_image())
self.assertTrue(isinstance(image_src, GES.VideoUriSource))
imagefreeze, = [e for e in image_src.get_nleobject().iterate_recurse()
if e.get_factory().get_name() == "imagefreeze"]
asset = GES.UriClipAsset.request_sync(common.get_asset_uri("audio_video.ogg"))
clip = self.layer.add_asset(asset, Gst.SECOND, 0, Gst.SECOND, GES.TrackType.VIDEO)
video_src, = clip.get_children(True)
self.assertEqual([e for e in video_src.get_nleobject().iterate_recurse()
if e.get_factory().get_name() == "imagefreeze"], [])
@@ -0,0 +1,407 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2015, Thibault Saunier
#
# 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 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 . import common # noqa
import unittest # noqa
from unittest import mock
Gst.init(None)
GES.init()
class TestGroup(common.GESSimpleTimelineTest):
def testCopyGroup(self):
clip1 = GES.TestClip.new()
clip1.props.duration = 10
self.layer.add_clip(clip1)
self.assertEqual(len(clip1.get_children(False)), 2)
group = GES.Group.new()
self.assertTrue(group.add(clip1))
self.assertEqual(len(group.get_children(False)), 1)
group_copy = group.copy(True)
self.assertEqual(len(group_copy.get_children(False)), 0)
self.assertTrue(group_copy.paste(10))
clips = self.layer.get_clips()
self.assertEqual(len(clips), 2)
self.assertEqual(clips[1].props.start, 10)
clips[1].edit([], 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10)
clips = self.layer.get_clips()
self.assertEqual(len(clips), 1)
def testPasteChangedGroup(self):
clip1 = GES.TestClip.new()
clip1.props.duration = 10
clip2 = GES.TestClip.new()
clip2.props.start = 20
clip2.props.duration = 10
self.layer.add_clip(clip1)
self.layer.add_clip(clip2)
self.assertEqual(len(clip1.get_children(False)), 2)
group = GES.Group.new()
self.assertTrue(group.add(clip1))
self.assertEqual(len(group.get_children(False)), 1)
group_copy = group.copy(True)
self.assertEqual(len(group_copy.get_children(False)), 0)
self.assertTrue(group.add(clip2))
self.assertEqual(len(group.get_children(False)), 2)
self.assertEqual(len(group_copy.get_children(False)), 0)
self.assertTrue(group_copy.paste(10))
clips = self.layer.get_clips()
self.assertEqual(len(clips), 3)
self.assertEqual(clips[1].props.start, 10)
def testPasteChangedGroup(self):
clip1 = GES.TestClip.new()
clip1.props.duration = 10
clip2 = GES.TestClip.new()
clip2.props.start = 20
clip2.props.duration = 10
self.layer.add_clip(clip1)
self.layer.add_clip(clip2)
self.assertEqual(len(clip1.get_children(False)), 2)
group = GES.Group.new()
self.assertTrue(group.add(clip1))
self.assertEqual(len(group.get_children(False)), 1)
group_copy = group.copy(True)
self.assertEqual(len(group_copy.get_children(False)), 0)
self.assertTrue(group.add(clip2))
self.assertEqual(len(group.get_children(False)), 2)
self.assertEqual(len(group_copy.get_children(False)), 0)
self.assertTrue(group_copy.paste(10))
clips = self.layer.get_clips()
self.assertEqual(len(clips), 3)
self.assertEqual(clips[1].props.start, 10)
def test_move_clips_between_layers_with_auto_transition(self):
self.timeline.props.auto_transition = True
layer2 = self.timeline.append_layer()
clip1 = GES.TestClip.new()
clip1.props.start = 0
clip1.props.duration = 30
clip2 = GES.TestClip.new()
clip2.props.start = 20
clip2.props.duration = 20
self.layer.add_clip(clip1)
self.layer.add_clip(clip2)
clips = self.layer.get_clips()
self.assertEqual(len(clips), 4)
self.assertEqual(layer2.get_clips(), [])
group = GES.Container.group(clips)
self.assertIsNotNone(group)
self.assertTrue(clip1.edit(
self.timeline.get_layers(), 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0))
self.assertEqual(self.layer.get_clips(), [])
clips = layer2.get_clips()
self.assertEqual(len(clips), 4)
def test_remove_emits_signal(self):
clip1 = GES.TestClip.new()
self.layer.add_clip(clip1)
group = GES.Group.new()
child_removed_cb = mock.Mock()
group.connect("child-removed", child_removed_cb)
group.add(clip1)
group.remove(clip1)
child_removed_cb.assert_called_once_with(group, clip1)
group.add(clip1)
child_removed_cb.reset_mock()
group.ungroup(recursive=False)
child_removed_cb.assert_called_once_with(group, clip1)
def test_loaded_project_has_groups(self):
mainloop = common.create_main_loop()
timeline = common.create_project(with_group=True, saved=True)
layer, = timeline.get_layers()
group, = timeline.get_groups()
self.assertEqual(len(layer.get_clips()), 2)
for clip in layer.get_clips():
self.assertEqual(clip.get_parent(), group)
# 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()
mainloop.run()
self.assertTrue(loaded_called)
layer, = timeline.get_layers()
group, = timeline.get_groups()
self.assertEqual(len(layer.get_clips()), 2)
for clip in layer.get_clips():
self.assertEqual(clip.get_parent(), group)
def test_moving_group_with_transition(self):
self.timeline.props.auto_transition = True
clip1 = GES.TestClip.new()
clip1.props.start = 0
clip1.props.duration = 30
clip2 = GES.TestClip.new()
clip2.props.start = 20
clip2.props.duration = 20
self.layer.add_clip(clip1)
self.layer.add_clip(clip2)
clips = self.layer.get_clips()
self.assertEqual(len(clips), 4)
video_transition = None
audio_transition = None
for clip in clips:
if isinstance(clip, GES.TransitionClip):
if isinstance(clip.get_children(False)[0], GES.VideoTransition):
video_transition = clip
else:
audio_transition = clip
self.assertIsNotNone(audio_transition)
self.assertIsNotNone(video_transition)
self.assertEqual(video_transition.props.start, 20)
self.assertEqual(video_transition.props.duration, 10)
self.assertEqual(audio_transition.props.start, 20)
self.assertEqual(audio_transition.props.duration, 10)
group = GES.Container.group(clips)
self.assertIsNotNone(group)
self.assertTrue(clip2.edit(
self.timeline.get_layers(), 0,
GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 25))
clip2.props.start = 25
clips = self.layer.get_clips()
self.assertEqual(len(clips), 4)
self.assertEqual(clip1.props.start, 5)
self.assertEqual(clip1.props.duration, 30)
self.assertEqual(clip2.props.start, 25)
self.assertEqual(clip2.props.duration, 20)
self.assertEqual(video_transition.props.start, 25)
self.assertEqual(video_transition.props.duration, 10)
self.assertEqual(audio_transition.props.start, 25)
self.assertEqual(audio_transition.props.duration, 10)
def test_moving_group_snapping_from_the_middle(self):
self.track_types = [GES.TrackType.AUDIO]
super().setUp()
snapped_positions = []
def snapping_started_cb(timeline, first_element, second_element,
position, snapped_positions):
snapped_positions.append(position)
self.timeline.props.snapping_distance = 5
self.timeline.connect("snapping-started", snapping_started_cb,
snapped_positions)
for start in range(0, 20, 5):
clip = GES.TestClip.new()
clip.props.start = start
clip.props.duration = 5
self.layer.add_clip(clip)
clips = self.layer.get_clips()
self.assertEqual(len(clips), 4)
group = GES.Container.group(clips[1:3])
self.assertIsNotNone(group)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 5),
(GES.TestClip, 5, 5),
(GES.TestClip, 10, 5),
(GES.TestClip, 15, 5),
],
], groups=[clips[1:3]])
self.assertEqual(clips[1].props.start, 5)
self.assertEqual(clips[2].props.start, 10)
clips[2].edit([], 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 11)
self.assertEqual(snapped_positions[0], 5)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 5),
(GES.TestClip, 5, 5),
(GES.TestClip, 10, 5),
(GES.TestClip, 15, 5),
],
], groups=[clips[1:3]])
def test_rippling_with_group(self):
self.track_types = [GES.TrackType.AUDIO]
super().setUp()
for _ in range(4):
self.append_clip()
snapped_positions = []
def snapping_started_cb(timeline, first_element, second_element,
position, snapped_positions):
snapped_positions.append(position)
self.timeline.props.snapping_distance = 5
self.timeline.connect("snapping-started", snapping_started_cb,
snapped_positions)
clips = self.layer.get_clips()
self.assertEqual(len(clips), 4)
group_clips = clips[1:3]
GES.Container.group(group_clips)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
(GES.TestClip, 20, 10),
(GES.TestClip, 30, 10),
],
], groups=[group_clips])
self.assertFalse(clips[2].edit([], 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 5))
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
(GES.TestClip, 20, 10),
(GES.TestClip, 30, 10),
],
], groups=[group_clips])
# Negative start...
self.assertFalse(clips[2].edit([], 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 1))
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TestClip, 10, 10),
(GES.TestClip, 20, 10),
(GES.TestClip, 30, 10),
],
], groups=[group_clips])
self.assertTrue(clips[2].edit([], 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 20))
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
],
[
(GES.TestClip, 10, 10),
(GES.TestClip, 20, 10),
(GES.TestClip, 30, 10),
],
], groups=[group_clips])
def test_group_priority(self):
self.track_types = [GES.TrackType.AUDIO]
self.setUp()
clip0 = self.append_clip()
clip1 = self.append_clip(1)
clip1.props.start = 20
group = GES.Group.new()
group.add(clip0)
group.add(clip1)
self.assertEqual(group.get_layer_priority(), 0)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
],
[
(GES.TestClip, 20, 10),
]
], groups=[(clip0, clip1)])
group.remove(clip0)
self.assertEqual(group.get_layer_priority(), 1)
clip1.edit(self.timeline.get_layers(), 2, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, clip1.start)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
],
[ ],
[
(GES.TestClip, 20, 10),
]
], groups=[(clip1,)])
self.assertEqual(group.get_layer_priority(), 2)
self.assertTrue(clip1.edit(self.timeline.get_layers(), 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, clip1.start))
self.assertTimelineTopology([
[
(GES.TestClip, 0, 10),
(GES.TestClip, 20, 10),
],
[ ],
[ ]
], groups=[(clip1,)])
File diff suppressed because it is too large Load Diff