timeline-tree: also trim non-core track elements
authorHenry Wilkes <hwilkes@igalia.com>
Mon, 27 Apr 2020 15:05:54 +0000 (16:05 +0100)
committerHenry Wilkes <hwilkes@igalia.com>
Thu, 7 May 2020 08:37:15 +0000 (09:37 +0100)
Also trim the in-point of non-core children of clips to ensure that
their content will appear in the timeline at the same position.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>

ges/ges-timeline-tree.c
tests/check/python/test_timeline.py

index 851e25e..163f468 100644 (file)
@@ -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;
   }
index fab0bf0..04d2aef 100644 (file)
@@ -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)