ges: Make setting start/duration move or trim generic

We were implementing the logic for moving/trimming elements specific
to SourceClip but this was not correct ass the new timeline tree allows
us to handle that for all element types in a generic and nice way.

This make us need to have groups trimming properly implemented in the
timeline tree, leading to some fixes in the group tests.

This adds tests for the various cases known to not be handled properly
by the previous code.

Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/92
This commit is contained in:
Thibault Saunier 2020-03-05 15:56:28 -03:00
parent b66290d1be
commit 6b7c658b6a
9 changed files with 338 additions and 92 deletions

View file

@ -91,9 +91,11 @@ neighbour_changed_cb (GESClip * clip, GParamSpec * arg G_GNUC_UNUSED,
}
self->positioning = TRUE;
ELEMENT_SET_FLAG (self->transition_clip, GES_TIMELINE_ELEMENT_SET_SIMPLE);
_set_start0 (GES_TIMELINE_ELEMENT (self->transition_clip),
_START (self->next_source));
_set_duration0 (GES_TIMELINE_ELEMENT (self->transition_clip), new_duration);
ELEMENT_SET_FLAG (self->transition_clip, GES_TIMELINE_ELEMENT_SET_SIMPLE);
self->positioning = FALSE;
}

View file

@ -218,8 +218,11 @@ _set_duration (GESTimelineElement * element, GstClockTime duration)
for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
GESTimelineElement *child = (GESTimelineElement *) tmp->data;
if (child != container->initiated_move)
if (child != container->initiated_move) {
ELEMENT_SET_FLAG (child, GES_TIMELINE_ELEMENT_SET_SIMPLE);
_set_duration0 (GES_TIMELINE_ELEMENT (child), duration);
ELEMENT_UNSET_FLAG (child, GES_TIMELINE_ELEMENT_SET_SIMPLE);
}
}
container->children_control_mode = GES_CHILDREN_UPDATE;

View file

@ -46,49 +46,6 @@ enum
G_DEFINE_TYPE_WITH_PRIVATE (GESSourceClip, ges_source_clip, GES_TYPE_CLIP);
static gboolean
_set_start (GESTimelineElement * element, GstClockTime start)
{
GESTimelineElement *toplevel =
ges_timeline_element_get_toplevel_parent (element);
gst_object_unref (toplevel);
if (element->timeline
&& !ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE)
&& !ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
if (!ges_timeline_move_object_simple (element->timeline, element, NULL,
GES_EDGE_NONE, start))
return FALSE;
return -1;
}
return
GES_TIMELINE_ELEMENT_CLASS (ges_source_clip_parent_class)->set_start
(element, start);
}
static gboolean
_set_duration (GESTimelineElement * element, GstClockTime duration)
{
GESTimelineElement *toplevel =
ges_timeline_element_get_toplevel_parent (element);
gst_object_unref (toplevel);
if (element->timeline
&& !ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE)
&& !ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
if (!timeline_trim_object (element->timeline, element,
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element), NULL, GES_EDGE_END,
element->start + duration))
return FALSE;
return -1;
}
return
GES_TIMELINE_ELEMENT_CLASS (ges_source_clip_parent_class)->set_duration
(element, duration);
}
static void
ges_source_clip_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
@ -119,14 +76,10 @@ static void
ges_source_clip_class_init (GESSourceClipClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass);
object_class->get_property = ges_source_clip_get_property;
object_class->set_property = ges_source_clip_set_property;
object_class->finalize = ges_source_clip_finalize;
element_class->set_start = _set_start;
element_class->set_duration = _set_duration;
}
static void

View file

@ -972,6 +972,7 @@ ges_timeline_element_get_timeline (GESTimelineElement * self)
gboolean
ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start)
{
gboolean emit_notify = TRUE;
GESTimelineElementClass *klass;
GESTimelineElement *toplevel_container, *parent;
@ -980,13 +981,24 @@ ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start)
if (self->start == start)
return TRUE;
klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
GST_DEBUG_OBJECT (self, "current start: %" GST_TIME_FORMAT
" new start: %" GST_TIME_FORMAT,
GST_TIME_ARGS (GES_TIMELINE_ELEMENT_START (self)), GST_TIME_ARGS (start));
toplevel_container = ges_timeline_element_get_toplevel_parent (self);
if (self->timeline
&& !ELEMENT_FLAG_IS_SET (self, GES_TIMELINE_ELEMENT_SET_SIMPLE)
&& !ELEMENT_FLAG_IS_SET (toplevel_container,
GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
if (!ges_timeline_move_object_simple (self->timeline, self, NULL,
GES_EDGE_NONE, start)) {
gst_object_unref (toplevel_container);
return FALSE;
}
emit_notify = FALSE;
}
parent = self->parent;
/* FIXME This should not belong to GESTimelineElement */
@ -1003,9 +1015,10 @@ ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start)
}
gst_object_unref (toplevel_container);
klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
if (klass->set_start) {
gint res = klass->set_start (self, start);
if (res == TRUE) {
if (res == TRUE && emit_notify) {
self->start = start;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_START]);
}
@ -1126,19 +1139,40 @@ ges_timeline_element_set_duration (GESTimelineElement * self,
GstClockTime duration)
{
GESTimelineElementClass *klass;
gboolean emit_notify = TRUE;
GESTimelineElement *toplevel;
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
toplevel = ges_timeline_element_get_toplevel_parent (self);
if (self->timeline &&
!ELEMENT_FLAG_IS_SET (self, GES_TIMELINE_ELEMENT_SET_SIMPLE) &&
!ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
gboolean res;
res = timeline_trim_object (self->timeline, self,
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self), NULL,
GES_EDGE_END, self->start + duration);
if (!res) {
gst_object_unref (toplevel);
return FALSE;
}
emit_notify = res == -1;
}
gst_object_unref (toplevel);
GST_DEBUG_OBJECT (self, "current duration: %" GST_TIME_FORMAT
" new duration: %" GST_TIME_FORMAT,
GST_TIME_ARGS (GES_TIMELINE_ELEMENT_DURATION (self)),
GST_TIME_ARGS (duration));
klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
if (klass->set_duration) {
gint res = klass->set_duration (self, duration);
if (res == TRUE) {
if (res == TRUE && emit_notify) {
self->duration = duration;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]);
}

View file

@ -75,6 +75,10 @@ struct _TreeIterationData
GHashTable *moved_clips;
GList *neighbours;
/* Data related to trimming groups */
GstClockTime trim_group_start;
GstClockTime trim_group_end;
} tree_iteration_data_init = {
.root = NULL,
.res = TRUE,
@ -91,6 +95,8 @@ struct _TreeIterationData
.edge = GES_EDGE_NONE,
.moved_clips = NULL,
.neighbours = NULL,
.trim_group_start = GST_CLOCK_TIME_NONE,
.trim_group_end = GST_CLOCK_TIME_NONE,
};
/* *INDENT-ON* */
@ -783,6 +789,47 @@ error:
goto done;
}
static gboolean
trim_group_get_vals (TreeIterationData * data, GESTimelineElement * e,
GstClockTimeDiff * n_start, GstClockTimeDiff * n_inpoint,
GstClockTimeDiff * n_duration)
{
GstClockTimeDiff offset;
GstClockTimeDiff group_nstart =
GST_CLOCK_DIFF (data->start_diff, data->trim_group_start);
GstClockTimeDiff group_nend =
GST_CLOCK_DIFF (data->duration_diff, data->trim_group_end);
if (data->edge == GES_EDGE_START &&
((group_nstart >= e->start) || (e->start == data->trim_group_start))) {
offset = GST_CLOCK_DIFF (group_nstart, e->start);
*n_start = group_nstart;
*n_inpoint = GST_CLOCK_DIFF (offset, e->inpoint);
*n_duration =
GST_CLOCK_DIFF (*n_start, (GstClockTimeDiff) e->start + e->duration);
GST_DEBUG_OBJECT (data->element, "Trimming %" GES_FORMAT " start",
GES_ARGS (e));
return TRUE;
} else if (data->edge == GES_EDGE_END &&
((group_nend <= _END (e)) || (_END (e) == data->trim_group_end))) {
offset = GST_CLOCK_DIFF (group_nend, _END (e));
*n_start = e->start;
*n_inpoint = e->inpoint;
*n_duration = GST_CLOCK_DIFF (offset, e->duration);
GST_DEBUG_OBJECT (data->element, "Trimming %" GES_FORMAT " end",
GES_ARGS (e));
return TRUE;
}
/* Ignoring child */
return FALSE;
}
static gboolean
check_trim_child (GNode * node, TreeIterationData * data)
{
@ -793,6 +840,12 @@ check_trim_child (GNode * node, TreeIterationData * data)
GST_CLOCK_DIFF (data->duration_diff, e->duration) :
GST_CLOCK_DIFF (n_start, (GstClockTimeDiff) e->start + e->duration);
if (GST_CLOCK_TIME_IS_VALID (data->trim_group_start)
&& !trim_group_get_vals (data, e, &n_start, &n_inpoint, &n_duration)) {
GST_DEBUG_OBJECT (data->element, "Not trimming");
return FALSE;
}
if (!timeline_tree_can_move_element_internal (data->root, e,
(gint64) ges_timeline_element_get_layer_priority (e) -
data->priority_diff, n_start, n_inpoint, n_duration, NULL,
@ -823,21 +876,33 @@ timeline_tree_can_trim_element_internal (GNode * root, TreeIterationData * data)
static void
trim_simple (GESTimelineElement * element, GstClockTimeDiff offset,
GESEdge edge)
GESEdge edge, TreeIterationData * data)
{
GESTimelineElement *toplevel =
ges_timeline_element_get_toplevel_parent (element);
GstClockTimeDiff n_start = GST_CLOCK_DIFF (offset, element->start);
GstClockTimeDiff n_inpoint = GST_CLOCK_DIFF (offset, element->inpoint);
GstClockTimeDiff n_duration = edge == GES_EDGE_END
? GST_CLOCK_DIFF (offset, element->duration)
: element->duration + offset;
if (data && GST_CLOCK_TIME_IS_VALID (data->trim_group_start))
g_assert (trim_group_get_vals (data, element, &n_start, &n_inpoint,
&n_duration));
ELEMENT_SET_FLAG (element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
if (edge == GES_EDGE_END) {
ges_timeline_element_set_duration (element, GST_CLOCK_DIFF (offset,
element->duration));
} else {
ges_timeline_element_set_start (element, GST_CLOCK_DIFF (offset,
element->start));
ges_timeline_element_set_inpoint (element, GST_CLOCK_DIFF (offset,
element->inpoint));
ges_timeline_element_set_duration (element, element->duration + offset);
ELEMENT_SET_FLAG (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE);
if (edge != GES_EDGE_END) {
ges_timeline_element_set_start (element, n_start);
ges_timeline_element_set_inpoint (element, n_inpoint);
}
ges_timeline_element_set_duration (element, n_duration);
GST_LOG ("Trimmed %" GES_FORMAT, GES_ARGS (element));
ELEMENT_UNSET_FLAG (element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
ELEMENT_UNSET_FLAG (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE);
gst_object_unref (toplevel);
}
#define SET_TRIMMING_DATA(data, _edge, offset) G_STMT_START { \
@ -845,6 +910,10 @@ trim_simple (GESTimelineElement * element, GstClockTimeDiff offset,
data.start_diff = (_edge) == GES_EDGE_END ? 0 : (offset); \
data.inpoint_diff = (_edge) == GES_EDGE_END ? 0 : (offset); \
data.duration_diff = (_edge) == GES_EDGE_END ? (offset) : -(offset); \
if (GES_IS_GROUP (data.element)) {\
data.trim_group_start = data.element->start;\
data.trim_group_end = _END (data.element); \
} \
} G_STMT_END
@ -869,6 +938,10 @@ timeline_tree_trim (GNode * root, GESTimelineElement * element,
};
TreeIterationData data = tree_iteration_data_init;
/* Make sure to check all children of clips */
if (GES_IS_TRACK_ELEMENT (element) && element->parent)
element = element->parent;
data.root = root;
data.element = element;
data.priority_diff =
@ -876,13 +949,12 @@ timeline_tree_trim (GNode * root, GESTimelineElement * element,
new_layer_priority;
data.snapping = snapping_distance ? &snapping : NULL;
data.moved_clips = g_hash_table_new (g_direct_hash, g_direct_equal);
SET_TRIMMING_DATA (data, edge, offset);
GST_INFO ("%" GES_FORMAT " trimming %s with offset %" G_GINT64_FORMAT "",
GES_ARGS (element), edge == GES_EDGE_END ? "end" : "start", offset);
g_node_traverse (find_node (root, element), G_IN_ORDER,
G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list,
&data.movings);
g_node_traverse (find_node (root, get_toplevel_container (element)),
G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
(GNodeTraverseFunc) add_element_to_list, &data.movings);
if (!timeline_tree_can_trim_element_internal (root, &data)) {
GST_INFO ("Can not trim object.");
@ -909,7 +981,7 @@ timeline_tree_trim (GNode * root, GESTimelineElement * element,
g_hash_table_iter_init (&iter, data.moved_clips);
while (g_hash_table_iter_next (&iter, (gpointer *) & elem, NULL))
trim_simple (elem, offset, edge);
trim_simple (elem, offset, edge, &data);
timeline_tree_create_transitions (root, ges_timeline_find_auto_transition);
timeline_update_transition (root->data);
@ -1155,9 +1227,9 @@ timeline_tree_roll (GNode * root, GESTimelineElement * element,
}
}
trim_simple (element, offset, edge);
trim_simple (element, offset, edge, NULL);
for (tmp = data.neighbours; tmp; tmp = tmp->next)
trim_simple (tmp->data, offset, data.edge);
trim_simple (tmp->data, offset, data.edge, NULL);
done:
timeline_update_duration (root->data);

View file

@ -1096,8 +1096,10 @@ _trim_transition (GESTimeline * timeline, GESTimelineElement * element,
GESLayer *layer = ges_timeline_get_layer (timeline,
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element));
if (!ges_layer_get_auto_transition (layer))
goto fail;
if (!ges_layer_get_auto_transition (layer)) {
gst_object_unref (layer);
return -1;
}
gst_object_unref (layer);
for (tmp = timeline->priv->auto_transitions; tmp; tmp = tmp->next) {
@ -1123,10 +1125,6 @@ _trim_transition (GESTimeline * timeline, GESTimelineElement * element,
}
return FALSE;
fail:
gst_object_unref (layer);
return FALSE;
}

View file

@ -195,40 +195,67 @@ GST_START_TEST (test_move_group)
* |----------------------------------|
* | 7--------- 2----------|
* layer1: | | clip1 | | clip2 |
* | 22--------30 62----------|
* | 20--------30 60----------|
* |----------------------------------|
*/
ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 12);
CHECK_OBJECT_PROPS (clip, 12, 2, 3);
CHECK_OBJECT_PROPS (clip1, 22, 7, 8);
CHECK_OBJECT_PROPS (clip2, 62, 2, 48);
CHECK_OBJECT_PROPS (clip1, 20, 5, 10);
CHECK_OBJECT_PROPS (clip2, 60, 0, 50);
CHECK_OBJECT_PROPS (group, 12, 0, 98);
ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2);
/* Setting the duration would lead to overlaps */
ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (group), 10);
fail_if (ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (group),
10));
CHECK_OBJECT_PROPS (clip, 12, 2, 3);
CHECK_OBJECT_PROPS (clip1, 22, 7, 8);
CHECK_OBJECT_PROPS (clip2, 62, 2, 48);
CHECK_OBJECT_PROPS (clip1, 20, 5, 10);
CHECK_OBJECT_PROPS (clip2, 60, 0, 50);
CHECK_OBJECT_PROPS (group, 12, 0, 98);
ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (group), 100);
CHECK_OBJECT_PROPS (clip, 12, 2, 3);
CHECK_OBJECT_PROPS (clip1, 22, 7, 8);
CHECK_OBJECT_PROPS (clip2, 62, 2, 50);
CHECK_OBJECT_PROPS (clip1, 20, 5, 10);
CHECK_OBJECT_PROPS (clip2, 60, 0, 52);
CHECK_OBJECT_PROPS (group, 12, 0, 100);
ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (group), 20);
CHECK_OBJECT_PROPS (clip, 20, 2, 3);
CHECK_OBJECT_PROPS (clip1, 30, 7, 8);
CHECK_OBJECT_PROPS (clip2, 70, 2, 50);
CHECK_OBJECT_PROPS (clip1, 28, 5, 10);
CHECK_OBJECT_PROPS (clip2, 68, 0, 52);
CHECK_OBJECT_PROPS (group, 20, 0, 100);
/* Trim fails because clip inpoint would become negative */
fail_if (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 10));
CHECK_OBJECT_PROPS (clip, 20, 2, 3);
CHECK_OBJECT_PROPS (clip1, 30, 7, 8);
CHECK_OBJECT_PROPS (clip2, 70, 2, 50);
CHECK_OBJECT_PROPS (clip1, 28, 5, 10);
CHECK_OBJECT_PROPS (clip2, 68, 0, 52);
CHECK_OBJECT_PROPS (group, 20, 0, 100);
fail_unless (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 18));
CHECK_OBJECT_PROPS (clip, 18, 0, 5);
CHECK_OBJECT_PROPS (clip1, 28, 5, 10);
CHECK_OBJECT_PROPS (clip2, 68, 0, 52);
CHECK_OBJECT_PROPS (group, 18, 0, 102);
fail_unless (ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip),
17));
CHECK_OBJECT_PROPS (clip, 18, 0, 17);
CHECK_OBJECT_PROPS (clip1, 28, 5, 10);
CHECK_OBJECT_PROPS (clip2, 68, 0, 52);
CHECK_OBJECT_PROPS (group, 18, 0, 102);
fail_unless (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 30));
CHECK_OBJECT_PROPS (clip, 30, 12, 5);
CHECK_OBJECT_PROPS (clip1, 30, 7, 8);
CHECK_OBJECT_PROPS (clip2, 68, 0, 52);
CHECK_OBJECT_PROPS (group, 30, 0, 90);
fail_unless (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 25));
CHECK_OBJECT_PROPS (clip, 25, 7, 10);
CHECK_OBJECT_PROPS (clip1, 25, 2, 13);
CHECK_OBJECT_PROPS (clip2, 68, 0, 52);
CHECK_OBJECT_PROPS (group, 25, 0, 95);
ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2);
check_destroyed (G_OBJECT (timeline), G_OBJECT (group), NULL);
gst_object_unref (asset);

View file

@ -188,8 +188,8 @@ class GESSimpleTimelineTest(GESTest):
len(self.track_types))
self.layer = self.timeline.append_layer()
def add_clip(self, start, in_point, duration):
clip = GES.TestClip()
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
@ -197,11 +197,11 @@ class GESSimpleTimelineTest(GESTest):
return clip
def append_clip(self, layer=0):
def append_clip(self, layer=0, asset_type=GES.TestClip):
while len(self.timeline.get_layers()) < layer + 1:
self.timeline.append_layer()
layer = self.timeline.get_layers()[layer]
clip = GES.TestClip()
clip = GES.Asset.request(asset_type, None).extract()
clip.props.start = layer.get_duration()
clip.props.duration = 10
self.assertTrue(layer.add_clip(clip))
@ -215,6 +215,9 @@ class GESSimpleTimelineTest(GESTest):
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:

View file

@ -505,6 +505,160 @@ class TestEditing(common.GESSimpleTimelineTest):
]
])
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):