diff --git a/ges/ges-timeline-tree.c b/ges/ges-timeline-tree.c index 851e25ea06..163f468bf4 100644 --- a/ges/ges-timeline-tree.c +++ b/ges/ges-timeline-tree.c @@ -869,9 +869,64 @@ set_edit_move_values (GESTimelineElement * element, EditData * data) return set_layer_priority (element, data); } +static gboolean +set_edit_trim_start_inpoint_value (GESTimelineElement * element, + EditData * data) +{ + GstClockTime new_inpoint = _clock_time_minus_diff (element->inpoint, + data->offset, NULL); + if (!GST_CLOCK_TIME_IS_VALID (new_inpoint)) { + GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT + " with offset %" G_GINT64_FORMAT " because it would result in an " + "invalid in-point", GES_ARGS (element), data->offset); + return FALSE; + } + data->inpoint = new_inpoint; + return TRUE; +} + +static gboolean +set_edit_trim_start_non_core_children (GESTimelineElement * clip, + GstClockTimeDiff offset, GHashTable * edit_table) +{ + GList *tmp; + GESTimelineElement *child; + GESTrackElement *el; + EditData *data; + + /* need to set in-point of active non-core children to keep their + * internal content at the same timeline position. This also ensures + * the duration-limit will not be broken */ + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + child = tmp->data; + el = tmp->data; + if (ges_track_element_has_internal_source (el) + && ges_track_element_is_active (el) + && !GES_TRACK_ELEMENT_IS_CORE (child)) { + + GST_INFO_OBJECT (child, "Setting track element %s to trim in-point " + "with offset %" G_GINT64_FORMAT " since the parent clip %" + GES_FORMAT " is being trimmed at its start", child->name, offset, + GES_ARGS (clip)); + + if (g_hash_table_contains (edit_table, child)) { + GST_ERROR_OBJECT (child, "Already set to be edited"); + return FALSE; + } + + data = new_edit_data (EDIT_TRIM_START, offset, 0); + g_hash_table_insert (edit_table, child, data); + if (!set_edit_trim_start_inpoint_value (child, data)) + return FALSE; + } + } + return TRUE; +} + /* trim the start of a clip or a track element */ static gboolean -set_edit_trim_start_values (GESTimelineElement * element, EditData * data) +set_edit_trim_start_values (GESTimelineElement * element, EditData * data, + GHashTable * edit_table) { GstClockTime new_start = _clock_time_minus_diff (element->start, data->offset, NULL); @@ -892,18 +947,16 @@ set_edit_trim_start_values (GESTimelineElement * element, EditData * data) } _CHECK_END (element, new_start, new_duration); - if (!GES_IS_TRACK_ELEMENT (element) - || ges_track_element_has_internal_source (GES_TRACK_ELEMENT (element))) { - GstClockTime new_inpoint = - _clock_time_minus_diff (element->inpoint, data->offset, NULL); - if (!GST_CLOCK_TIME_IS_VALID (new_inpoint)) { - GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT - " with offset %" G_GINT64_FORMAT " because it would result in an " - "invalid in-point", GES_ARGS (element), data->offset); + if (GES_IS_CLIP (element)) { + if (!set_edit_trim_start_inpoint_value (element, data)) + return FALSE; + if (!set_edit_trim_start_non_core_children (element, data->offset, + edit_table)) + return FALSE; + } else if (GES_IS_TRACK_ELEMENT (element) + && ges_track_element_has_internal_source (GES_TRACK_ELEMENT (element))) { + if (!set_edit_trim_start_inpoint_value (element, data)) return FALSE; - } - - data->inpoint = new_inpoint; } data->start = new_start; data->duration = new_duration; @@ -955,13 +1008,14 @@ set_edit_trim_end_values (GESTimelineElement * element, EditData * data) /* handles clips and track elements with no parents */ static gboolean -set_clip_edit_values (GESTimelineElement * element, EditData * data) +set_clip_edit_values (GESTimelineElement * element, EditData * data, + GHashTable * edit_table) { switch (data->mode) { case EDIT_MOVE: return set_edit_move_values (element, data); case EDIT_TRIM_START: - return set_edit_trim_start_values (element, data); + return set_edit_trim_start_values (element, data, edit_table); case EDIT_TRIM_END: return set_edit_trim_end_values (element, data); } @@ -1094,7 +1148,7 @@ replace_group_with_clip_edits (GNode * root, GESTimelineElement * group, } clip_data = new_edit_data (clip_mode, offset, layer_offset); g_hash_table_insert (edit_table, clip, clip_data); - if (!set_clip_edit_values (clip, clip_data)) + if (!set_clip_edit_values (clip, clip_data, edit_table)) goto error; } } @@ -1131,7 +1185,7 @@ timeline_tree_set_element_edit_values (GNode * root, GHashTable * edits) if (GES_IS_GROUP (element)) res = replace_group_with_clip_edits (root, element, edits); else - res = set_clip_edit_values (element, edit_data); + res = set_clip_edit_values (element, edit_data, edits); if (!res) goto error; } diff --git a/tests/check/python/test_timeline.py b/tests/check/python/test_timeline.py index fab0bf09cc..04d2aef53b 100644 --- a/tests/check/python/test_timeline.py +++ b/tests/check/python/test_timeline.py @@ -522,6 +522,89 @@ class TestEditing(common.GESSimpleTimelineTest): ] ]) + 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 + self.assertFalse( + clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0)) + + self.assertEqual(clip.start, 10) + self.assertEqual(clip.inpoint, 12) + self.assertEqual(effect0.inpoint, 5) + self.assertEqual(effect1.inpoint, 0) + self.assertEqual(effect2.inpoint, 8) + self.assertEqual(effect3.inpoint, 20) + + self.assertTrue( + clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5)) + + self.assertEqual(clip.start, 5) + self.assertEqual(clip.duration, 15) + self.assertEqual(clip.get_duration_limit(), 15) + + for child in clip.get_children(False): + self.assertEqual(child.start, 5) + self.assertEqual(child.duration, 15) + + self.assertEqual(clip.inpoint, 7) + self.assertEqual(effect0.inpoint, 0) + self.assertEqual(effect1.inpoint, 0) + self.assertEqual(effect2.inpoint, 3) + self.assertEqual(effect3.inpoint, 20) + + self.assertTrue( + clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 15)) + + self.assertEqual(clip.start, 15) + self.assertEqual(clip.duration, 5) + self.assertEqual(clip.get_duration_limit(), 5) + + for child in clip.get_children(False): + self.assertEqual(child.start, 15) + self.assertEqual(child.duration, 5) + + self.assertEqual(clip.inpoint, 17) + self.assertEqual(effect0.inpoint, 10) + self.assertEqual(effect1.inpoint, 0) + self.assertEqual(effect2.inpoint, 13) + self.assertEqual(effect3.inpoint, 20) + def test_ripple_end(self): clip = self.append_clip() clip.set_max_duration(20)