clip: preserve auto-transition in split
authorHenry Wilkes <hwilkes@igalia.com>
Mon, 27 Apr 2020 18:11:16 +0000 (19:11 +0100)
committerHenry Wilkes <hwilkes@igalia.com>
Thu, 7 May 2020 08:37:15 +0000 (09:37 +0100)
When splitting a clip, keep the auto-transition at the end of the clip
alive and move its source to that of the corresponding split track
element.

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

ges/ges-auto-transition.c
ges/ges-auto-transition.h
ges/ges-clip.c
ges/ges-internal.h
ges/ges-timeline.c
tests/check/ges/clip.c

index dd89c4a..e36955b 100644 (file)
@@ -39,8 +39,8 @@ static guint auto_transition_signals[LAST_SIGNAL] = { 0 };
 G_DEFINE_TYPE (GESAutoTransition, ges_auto_transition, G_TYPE_OBJECT);
 
 static void
-neighbour_changed_cb (GESClip * clip, GParamSpec * arg G_GNUC_UNUSED,
-    GESAutoTransition * self)
+neighbour_changed_cb (G_GNUC_UNUSED GObject * object,
+    G_GNUC_UNUSED GParamSpec * arg, GESAutoTransition * self)
 {
   gint64 new_duration;
   guint32 layer_prio;
@@ -110,7 +110,36 @@ _track_changed_cb (GESTrackElement * track_element,
 
     g_signal_emit (self, auto_transition_signals[DESTROY_ME], 0);
   }
+}
+
+static void
+_connect_to_source (GESAutoTransition * self, GESTrackElement * source)
+{
+  g_signal_connect (source, "notify::start",
+      G_CALLBACK (neighbour_changed_cb), self);
+  g_signal_connect_after (source, "notify::priority",
+      G_CALLBACK (neighbour_changed_cb), self);
+  g_signal_connect (source, "notify::duration",
+      G_CALLBACK (neighbour_changed_cb), self);
+
+  g_signal_connect (source, "notify::track",
+      G_CALLBACK (_track_changed_cb), self);
+}
+
+static void
+_disconnect_from_source (GESAutoTransition * self, GESTrackElement * source)
+{
+  g_signal_handlers_disconnect_by_func (source, neighbour_changed_cb, self);
+  g_signal_handlers_disconnect_by_func (source, _track_changed_cb, self);
+}
 
+void
+ges_auto_transition_set_previous_source (GESAutoTransition * self,
+    GESTrackElement * source)
+{
+  _disconnect_from_source (self, self->previous_source);
+  _connect_to_source (self, source);
+  self->previous_source = source;
 }
 
 static void
@@ -123,16 +152,8 @@ ges_auto_transition_finalize (GObject * object)
 {
   GESAutoTransition *self = GES_AUTO_TRANSITION (object);
 
-  g_signal_handlers_disconnect_by_func (self->previous_source,
-      neighbour_changed_cb, self);
-  g_signal_handlers_disconnect_by_func (self->next_source, neighbour_changed_cb,
-      self);
-  g_signal_handlers_disconnect_by_func (self->next_source, _track_changed_cb,
-      self);
-  g_signal_handlers_disconnect_by_func (self->previous_source,
-      _track_changed_cb, self);
-
-  g_free (self->key);
+  _disconnect_from_source (self, self->previous_source);
+  _disconnect_from_source (self, self->next_source);
 
   G_OBJECT_CLASS (ges_auto_transition_parent_class)->finalize (object);
 }
@@ -149,7 +170,6 @@ ges_auto_transition_class_init (GESAutoTransitionClass * klass)
   object_class->finalize = ges_auto_transition_finalize;
 }
 
-
 GESAutoTransition *
 ges_auto_transition_new (GESTrackElement * transition,
     GESTrackElement * previous_source, GESTrackElement * next_source)
@@ -161,48 +181,26 @@ ges_auto_transition_new (GESTrackElement * transition,
   self->next_source = next_source;
   self->transition = transition;
 
-  self->previous_clip =
-      GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (previous_source));
-  self->next_clip = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (next_source));
   self->transition_clip = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (transition));
 
-  g_signal_connect (previous_source, "notify::start",
-      G_CALLBACK (neighbour_changed_cb), self);
-  g_signal_connect_after (previous_source, "notify::priority",
-      G_CALLBACK (neighbour_changed_cb), self);
-  g_signal_connect (next_source, "notify::start",
-      G_CALLBACK (neighbour_changed_cb), self);
-  g_signal_connect (next_source, "notify::priority",
-      G_CALLBACK (neighbour_changed_cb), self);
-  g_signal_connect (previous_source, "notify::duration",
-      G_CALLBACK (neighbour_changed_cb), self);
-  g_signal_connect (next_source, "notify::duration",
-      G_CALLBACK (neighbour_changed_cb), self);
-
-  g_signal_connect (next_source, "notify::track",
-      G_CALLBACK (_track_changed_cb), self);
-  g_signal_connect (previous_source, "notify::track",
-      G_CALLBACK (_track_changed_cb), self);
+  _connect_to_source (self, previous_source);
+  _connect_to_source (self, next_source);
 
   GST_DEBUG_OBJECT (self, "Created transition %" GST_PTR_FORMAT
       " between %" GST_PTR_FORMAT "[%" GST_TIME_FORMAT
       " - %" GST_TIME_FORMAT "] and: %" GST_PTR_FORMAT
       "[%" GST_TIME_FORMAT " - %" GST_TIME_FORMAT "]"
-      " in layer nb %i, start: %" GST_TIME_FORMAT " duration: %"
-      GST_TIME_FORMAT, transition, previous_source,
+      " in layer nb %" G_GUINT32_FORMAT ", start: %" GST_TIME_FORMAT
+      " duration: %" GST_TIME_FORMAT, transition, previous_source,
       GST_TIME_ARGS (_START (previous_source)),
       GST_TIME_ARGS (_END (previous_source)),
       next_source,
       GST_TIME_ARGS (_START (next_source)),
       GST_TIME_ARGS (_END (next_source)),
-      ges_layer_get_priority (ges_clip_get_layer
-          (self->previous_clip)),
+      GES_TIMELINE_ELEMENT_LAYER_PRIORITY (next_source),
       GST_TIME_ARGS (_START (transition)),
       GST_TIME_ARGS (_DURATION (transition)));
 
-  self->key = g_strdup_printf ("%p%p", self->previous_source,
-      self->next_source);
-
   return self;
 }
 
@@ -211,5 +209,5 @@ ges_auto_transition_update (GESAutoTransition * self)
 {
   GST_INFO ("Updating info %s",
       GES_TIMELINE_ELEMENT_NAME (self->transition_clip));
-  neighbour_changed_cb (self->previous_clip, NULL, self);
+  neighbour_changed_cb (NULL, NULL, self);
 }
index a6e2ebb..29f6238 100644 (file)
@@ -49,15 +49,9 @@ struct _GESAutoTransition
   GESTrackElement *next_source;
   GESTrackElement *transition;
 
-  GESLayer *layer;
-
-  GESClip *previous_clip;
-  GESClip *next_clip;
   GESClip *transition_clip;
   gboolean positioning;
 
-  gchar *key;
-
   gboolean frozen;
 
   /* Padding for API extension */
index 1fbb652..13126d9 100644 (file)
@@ -2204,7 +2204,7 @@ ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect,
 GESClip *
 ges_clip_split (GESClip * clip, guint64 position)
 {
-  GList *tmp;
+  GList *tmp, *transitions = NULL;
   GESClip *new_object;
   GstClockTime start, inpoint, duration, old_duration, new_duration;
   gdouble media_duration_factor;
@@ -2272,6 +2272,7 @@ ges_clip_split (GESClip * clip, guint64 position)
    * binding values */
   track_for_copy = g_hash_table_new_full (NULL, NULL,
       gst_object_unref, gst_object_unref);
+
   /* _add_child will add core elements at the lowest priority and new
    * non-core effects at the lowest effect priority, so we need to add the
    * highest priority children first to preserve the effect order. The
@@ -2279,14 +2280,29 @@ ges_clip_split (GESClip * clip, guint64 position)
   for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
     GESTrackElement *copy, *orig = tmp->data;
     GESTrack *track = ges_track_element_get_track (orig);
+    GESAutoTransition *trans;
+
     /* FIXME: is position - start + inpoint always the correct splitting
      * point for control bindings? What coordinate system are control
      * bindings given in? */
     copy = ges_clip_copy_track_element_into (new_object, orig,
         position - start + inpoint);
-    if (copy && track)
+
+    if (!copy)
+      continue;
+
+    if (track)
       g_hash_table_insert (track_for_copy, gst_object_ref (copy),
           gst_object_ref (track));
+
+    trans = timeline ?
+        ges_timeline_get_auto_transition_at_end (timeline, orig) : NULL;
+
+    if (trans) {
+      trans->frozen = TRUE;
+      ges_auto_transition_set_previous_source (trans, copy);
+      transitions = g_list_append (transitions, trans);
+    }
   }
 
   GES_TIMELINE_ELEMENT_SET_BEING_EDITED (clip);
@@ -2310,7 +2326,14 @@ ges_clip_split (GESClip * clip, guint64 position)
     }
   }
 
+  for (tmp = transitions; tmp; tmp = tmp->next) {
+    GESAutoTransition *trans = tmp->data;
+    trans->frozen = FALSE;
+    ges_auto_transition_update (trans);
+  }
+
   g_hash_table_unref (track_for_copy);
+  g_list_free_full (transitions, gst_object_unref);
 
   return new_object;
 }
index 5c41006..ae1f275 100644 (file)
@@ -106,6 +106,9 @@ GstDebugCategory * _ges_debug (void);
 G_GNUC_INTERNAL void
 ges_timeline_freeze_auto_transitions (GESTimeline * timeline, gboolean freeze);
 
+G_GNUC_INTERNAL GESAutoTransition *
+ges_timeline_get_auto_transition_at_end (GESTimeline * timeline, GESTrackElement * source);
+
 G_GNUC_INTERNAL gboolean
 ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element,
     GList * layers, gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
@@ -155,6 +158,11 @@ ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip);
 G_GNUC_INTERNAL void
 ges_timeline_remove_clip (GESTimeline * timeline, GESClip * clip);
 
+G_GNUC_INTERNAL void
+ges_auto_transition_set_previous_source (GESAutoTransition * self, GESTrackElement * source);
+
+
+
 G_GNUC_INTERNAL
 void
 track_resort_and_fill_gaps    (GESTrack *track);
index fc7a628..6ffadf8 100644 (file)
@@ -980,6 +980,34 @@ ges_timeline_find_auto_transition (GESTimeline * timeline,
   return NULL;
 }
 
+GESAutoTransition *
+ges_timeline_get_auto_transition_at_end (GESTimeline * timeline,
+    GESTrackElement * source)
+{
+  GList *tmp, *auto_transitions;
+  GESAutoTransition *ret = NULL;
+
+  LOCK_DYN (timeline);
+  auto_transitions = g_list_copy_deep (timeline->priv->auto_transitions,
+      (GCopyFunc) gst_object_ref, NULL);
+  UNLOCK_DYN (timeline);
+
+  for (tmp = auto_transitions; tmp; tmp = tmp->next) {
+    GESAutoTransition *auto_trans = (GESAutoTransition *) tmp->data;
+
+    /* We already have a transition linked to one of the elements we want to
+     * find a transition for */
+    if (auto_trans->previous_source == source) {
+      ret = gst_object_ref (auto_trans);
+      break;
+    }
+  }
+
+  g_list_free_full (auto_transitions, gst_object_unref);
+
+  return ret;
+}
+
 static GESAutoTransition *
 _create_auto_transition_from_transitions (GESTimeline * timeline,
     GESTrackElement * prev, GESTrackElement * next,
@@ -1168,12 +1196,12 @@ _edit_auto_transition (GESTimeline * timeline, GESTimelineElement * element,
       }
 
       if (edge == GES_EDGE_END)
-        replace = GES_TIMELINE_ELEMENT (auto_transition->previous_clip);
+        replace = GES_TIMELINE_ELEMENT (auto_transition->previous_source);
       else
-        replace = GES_TIMELINE_ELEMENT (auto_transition->next_clip);
+        replace = GES_TIMELINE_ELEMENT (auto_transition->next_source);
 
-      GST_INFO_OBJECT (element, "Trimming clip %" GES_FORMAT " in place "
-          "of trimming the corresponding auto-transition", GES_ARGS (replace));
+      GST_INFO_OBJECT (element, "Trimming %" GES_FORMAT " in place  of "
+          "trimming the corresponding auto-transition", GES_ARGS (replace));
       return ges_timeline_element_edit (replace, layers, -1, mode, edge,
           position);
     }
index 1e7b29b..405d078 100644 (file)
@@ -308,6 +308,136 @@ GST_START_TEST (test_split_direct_absolute_bindings)
 
 GST_END_TEST;
 
+static GESTimelineElement *
+_find_auto_transition (GESTrack * track, GESClip * from_clip, GESClip * to_clip)
+{
+  GstClockTime start, end;
+  GList *tmp, *track_els;
+  GESTimelineElement *ret = NULL;
+  GESLayer *layer0, *layer1;
+
+  layer0 = ges_clip_get_layer (from_clip);
+  layer1 = ges_clip_get_layer (to_clip);
+
+  fail_unless (layer0 == layer1, "%" GES_FORMAT " and %" GES_FORMAT " do not "
+      "share the same layer", GES_ARGS (from_clip), GES_ARGS (to_clip));
+  gst_object_unref (layer1);
+
+  start = GES_TIMELINE_ELEMENT_START (to_clip);
+  end = GES_TIMELINE_ELEMENT_START (from_clip)
+      + GES_TIMELINE_ELEMENT_DURATION (from_clip);
+
+  fail_if (end <= start, "%" GES_FORMAT " starts after %" GES_FORMAT " ends",
+      GES_ARGS (to_clip), GES_ARGS (from_clip));
+
+  track_els = ges_track_get_elements (track);
+
+  for (tmp = track_els; tmp; tmp = tmp->next) {
+    GESTimelineElement *el = tmp->data;
+    if (GES_IS_TRANSITION (el) && el->start == start
+        && (el->start + el->duration) == end) {
+      fail_if (ret, "Found two transitions %" GES_FORMAT " and %" GES_FORMAT
+          " between %" GES_FORMAT " and %" GES_FORMAT " in track %"
+          GST_PTR_FORMAT, GES_ARGS (el), GES_ARGS (ret), GES_ARGS (from_clip),
+          GES_ARGS (to_clip), track);
+      ret = el;
+    }
+  }
+  fail_unless (ret, "Found no transitions between %" GES_FORMAT " and %"
+      GES_FORMAT " in track %" GST_PTR_FORMAT, GES_ARGS (from_clip),
+      GES_ARGS (to_clip), track);
+
+  g_list_free_full (track_els, gst_object_unref);
+
+  fail_unless (GES_IS_CLIP (ret->parent), "Transition %" GES_FORMAT
+      " between %" GES_FORMAT " and %" GES_FORMAT " in track %"
+      GST_PTR_FORMAT " has no parent clip", GES_ARGS (ret),
+      GES_ARGS (from_clip), GES_ARGS (to_clip), track);
+
+  layer1 = ges_clip_get_layer (GES_CLIP (ret->parent));
+
+  fail_unless (layer0 == layer1, "Transition %" GES_FORMAT " between %"
+      GES_FORMAT " and %" GES_FORMAT " in track %" GST_PTR_FORMAT
+      " belongs to layer %" GST_PTR_FORMAT " rather than %" GST_PTR_FORMAT,
+      GES_ARGS (ret), GES_ARGS (from_clip), GES_ARGS (to_clip), track,
+      layer1, layer0);
+
+  gst_object_unref (layer0);
+  gst_object_unref (layer1);
+
+  return ret;
+}
+
+GST_START_TEST (test_split_with_auto_transitions)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESTrack *tracks[3];
+  GESTimelineElement *found;
+  GESTimelineElement *prev_trans[3];
+  GESTimelineElement *post_trans[3];
+  GESAsset *asset;
+  GESClip *clip, *split, *prev, *post;
+  guint i;
+
+  ges_init ();
+
+  timeline = ges_timeline_new ();
+
+  ges_timeline_set_auto_transition (timeline, TRUE);
+
+  tracks[0] = GES_TRACK (ges_audio_track_new ());
+  tracks[1] = GES_TRACK (ges_audio_track_new ());
+  tracks[2] = GES_TRACK (ges_video_track_new ());
+
+  for (i = 0; i < 3; i++)
+    fail_unless (ges_timeline_add_track (timeline, tracks[i]));
+
+  layer = ges_timeline_append_layer (timeline);
+  asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL);
+
+  prev = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN);
+  clip = ges_layer_add_asset (layer, asset, 5, 0, 20, GES_TRACK_TYPE_UNKNOWN);
+  post = ges_layer_add_asset (layer, asset, 20, 0, 10, GES_TRACK_TYPE_UNKNOWN);
+
+  fail_unless (prev);
+  fail_unless (clip);
+  fail_unless (post);
+
+  for (i = 0; i < 3; i++) {
+    prev_trans[i] = _find_auto_transition (tracks[i], prev, clip);
+    post_trans[i] = _find_auto_transition (tracks[i], clip, post);
+    /* 3 sources, 2 auto-transitions */
+    assert_num_in_track (tracks[i], 5);
+
+  }
+
+  /* cannot split within a transition */
+  fail_if (ges_clip_split (clip, 5));
+  fail_if (ges_clip_split (clip, 20));
+
+  /* we should keep the same auto-transitions during a split */
+  split = ges_clip_split (clip, 15);
+  fail_unless (split);
+
+  for (i = 0; i < 3; i++) {
+    found = _find_auto_transition (tracks[i], prev, clip);
+    fail_unless (found == prev_trans[i], "Transition between %" GES_FORMAT
+        " and %" GES_FORMAT " changed", GES_ARGS (prev), GES_ARGS (clip));
+
+    found = _find_auto_transition (tracks[i], split, post);
+    fail_unless (found == post_trans[i], "Transition between %" GES_FORMAT
+        " and %" GES_FORMAT " changed", GES_ARGS (clip), GES_ARGS (post));
+  }
+
+  gst_object_unref (timeline);
+  gst_object_unref (asset);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
 static GPtrArray *
 _select_none (GESTimeline * timeline, GESClip * clip,
     GESTrackElement * track_element, guint * called_p)
@@ -3329,6 +3459,7 @@ ges_suite (void)
   tcase_add_test (tc_chain, test_split_ordering);
   tcase_add_test (tc_chain, test_split_direct_bindings);
   tcase_add_test (tc_chain, test_split_direct_absolute_bindings);
+  tcase_add_test (tc_chain, test_split_with_auto_transitions);
   tcase_add_test (tc_chain, test_clip_group_ungroup);
   tcase_add_test (tc_chain, test_clip_can_group);
   tcase_add_test (tc_chain, test_adding_children_to_track);