clip: copy and paste control bindings
authorHenry Wilkes <hwilkes@igalia.com>
Tue, 3 Mar 2020 18:00:51 +0000 (18:00 +0000)
committerHenry Wilkes <hwilkes@igalia.com>
Mon, 16 Mar 2020 14:19:52 +0000 (14:19 +0000)
Previously the control bindings were not properly copied into the pasted
clip. Also changed the order so that elements are added to the clip
before the clip is added to the timeline.

ges/ges-clip.c
tests/check/ges/clip.c

index d57f3f5..533ab22 100644 (file)
@@ -727,7 +727,9 @@ _deep_copy (GESTimelineElement * element, GESTimelineElement * copy)
 
   for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
     el = GES_TRACK_ELEMENT (tmp->data);
+    /* copies the children properties */
     el_copy = ges_track_element_copy_core (el, TRUE);
+    ges_track_element_copy_bindings (el, el_copy, GST_CLOCK_TIME_NONE);
     ccopy->priv->copied_track_elements =
         g_list_append (ccopy->priv->copied_track_elements, el_copy);
   }
@@ -745,17 +747,7 @@ _paste (GESTimelineElement * element, GESTimelineElement * ref,
 
   if (self->priv->copied_layer)
     nclip->priv->copied_layer = g_object_ref (self->priv->copied_layer);
-
   ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (nclip), paste_position);
-  if (self->priv->copied_layer) {
-    if (!ges_layer_add_clip (self->priv->copied_layer, nclip)) {
-      GST_INFO ("%" GES_FORMAT " could not be pasted to %" GST_TIME_FORMAT,
-          GES_ARGS (element), GST_TIME_ARGS (paste_position));
-
-      return NULL;
-    }
-
-  }
 
   for (tmp = self->priv->copied_track_elements; tmp; tmp = tmp->next) {
     GESTrackElement *new_trackelement, *trackelement =
@@ -781,6 +773,18 @@ _paste (GESTimelineElement * element, GESTimelineElement * ref,
         GST_CLOCK_TIME_NONE);
   }
 
+  /* FIXME: should we bypass the select-tracks-for-object signal when
+   * copying and pasting? */
+  if (self->priv->copied_layer) {
+    if (!ges_layer_add_clip (self->priv->copied_layer, nclip)) {
+      GST_INFO ("%" GES_FORMAT " could not be pasted to %" GST_TIME_FORMAT,
+          GES_ARGS (element), GST_TIME_ARGS (paste_position));
+
+      return NULL;
+    }
+
+  }
+
   return GES_TIMELINE_ELEMENT (nclip);
 }
 
index 68b1462..3a4f1ee 100644 (file)
@@ -1230,6 +1230,201 @@ GST_START_TEST (test_children_properties_change)
 
 GST_END_TEST;
 
+static GESTimelineElement *
+_el_with_child_prop (GESTimelineElement * clip, GObject * prop_child,
+    GParamSpec * prop)
+{
+  GList *tmp;
+  GESTimelineElement *child = NULL;
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GObject *found_child;
+    GParamSpec *found_prop;
+    if (ges_timeline_element_lookup_child (tmp->data, prop->name,
+            &found_child, &found_prop)) {
+      if (found_child == prop_child && found_prop == prop) {
+        child = tmp->data;
+        /* break early, but still free */
+        tmp->next = NULL;
+      }
+      g_param_spec_unref (found_prop);
+      g_object_unref (found_child);
+    }
+  }
+  return child;
+}
+
+static GstTimedValue *
+_new_timed_value (GstClockTime time, gdouble val)
+{
+  GstTimedValue *tmval = g_new0 (GstTimedValue, 1);
+  tmval->value = val;
+  tmval->timestamp = time;
+  return tmval;
+}
+
+#define _assert_binding(element, prop_name, child, timed_vals, mode) \
+{ \
+  GstInterpolationMode found_mode; \
+  GSList *tmp1; \
+  GList *tmp2; \
+  guint i; \
+  GList *found_timed_vals; \
+  GObject *found_object = NULL; \
+  GstControlSource *source = NULL; \
+  GstControlBinding *binding = ges_track_element_get_control_binding ( \
+      GES_TRACK_ELEMENT (element), prop_name); \
+  fail_unless (binding, "No control binding found for %s on %s", \
+      prop_name, element->name); \
+  g_object_get (G_OBJECT (binding), "control-source", &source, \
+      "object", &found_object, NULL); \
+  \
+  fail_unless (found_object == child); \
+  g_object_unref (found_object); \
+  \
+  fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source)); \
+  found_timed_vals = gst_timed_value_control_source_get_all ( \
+      GST_TIMED_VALUE_CONTROL_SOURCE (source)); \
+  \
+  for (i = 0, tmp1 = timed_vals, tmp2 = found_timed_vals; tmp1 && tmp2; \
+      tmp1 = tmp1->next, tmp2 = tmp2->next, i++) { \
+    GstTimedValue *val1 = tmp1->data, *val2 = tmp2->data; \
+    fail_unless (val1->timestamp == val2->timestamp && \
+        val1->value == val2->value, "The %ith timed value (%lu: %g) " \
+        "does not match the found timed value (%lu: %g)", \
+        i, val1->timestamp, val1->value, val2->timestamp, val2->value); \
+  } \
+  fail_unless (tmp1 == NULL, "Found too few timed values"); \
+  fail_unless (tmp2 == NULL, "Found too many timed values"); \
+  \
+  g_list_free (found_timed_vals); \
+  g_object_get (G_OBJECT (source), "mode", &found_mode, NULL); \
+  fail_unless (found_mode == GST_INTERPOLATION_MODE_CUBIC); \
+  g_object_unref (source); \
+}
+
+GST_START_TEST (test_copy_paste_children_properties)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESTimelineElement *clip, *copy, *pasted, *track_el, *pasted_el;
+  GObject *sub_child, *pasted_sub_child;
+  GParamSpec **orig_props, **pasted_props, **track_el_props, **pasted_el_props;
+  guint num_orig_props, num_pasted_props, num_track_el_props,
+      num_pasted_el_props;
+  GParamSpec *prop, *found_prop;
+  GValue val = G_VALUE_INIT;
+  GSList *timed_vals;
+  GstControlSource *source;
+
+  ges_init ();
+
+  timeline = ges_timeline_new_audio_video ();
+  layer = ges_timeline_append_layer (timeline);
+  clip = GES_TIMELINE_ELEMENT (ges_test_clip_new ());
+  ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip), 50);
+
+  fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip)));
+
+  /* get children properties */
+  orig_props =
+      ges_timeline_element_list_children_properties (clip, &num_orig_props);
+  fail_unless (num_orig_props);
+
+  /* focus on one property */
+  fail_unless (ges_timeline_element_lookup_child (clip, "posx",
+          &sub_child, &prop));
+  g_value_init (&val, G_TYPE_INT);
+  g_value_set_int (&val, 30);
+  fail_unless (ges_timeline_element_set_child_property (clip, "posx", &val));
+  g_value_unset (&val);
+
+  _assert_int_val_child_prop (clip, val, 30, prop, "posx");
+
+  /* find the track element where the child property comes from */
+  fail_unless (track_el = _el_with_child_prop (clip, sub_child, prop));
+  _assert_int_val_child_prop (track_el, val, 30, prop, "posx");
+
+  track_el_props =
+      ges_timeline_element_list_children_properties (track_el,
+      &num_track_el_props);
+
+  /* set a control binding */
+  timed_vals = g_slist_prepend (NULL, _new_timed_value (200, 5));
+  timed_vals = g_slist_prepend (timed_vals, _new_timed_value (40, 50));
+  timed_vals = g_slist_prepend (timed_vals, _new_timed_value (20, 10));
+  timed_vals = g_slist_prepend (timed_vals, _new_timed_value (0, 20));
+
+  source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ());
+  g_object_set (G_OBJECT (source), "mode", GST_INTERPOLATION_MODE_CUBIC, NULL);
+  fail_unless (gst_timed_value_control_source_set_from_list
+      (GST_TIMED_VALUE_CONTROL_SOURCE (source), timed_vals));
+
+  fail_unless (ges_track_element_set_control_source (GES_TRACK_ELEMENT
+          (track_el), source, "posx", "direct-absolute"));
+  g_object_unref (source);
+
+  /* check the control binding */
+  _assert_binding (track_el, "posx", sub_child, timed_vals,
+      GST_INTERPOLATION_MODE_CUBIC);
+
+  /* copy and paste */
+  fail_unless (copy = ges_timeline_element_copy (clip, TRUE));
+  fail_unless (pasted = ges_timeline_element_paste (copy, 30));
+
+  gst_object_unref (copy);
+
+  /* test that the new clip has the same child properties */
+  pasted_props =
+      ges_timeline_element_list_children_properties (pasted, &num_pasted_props);
+
+  assert_property_list_match (pasted_props, num_pasted_props,
+      orig_props, num_orig_props);
+
+  /* get the details for the copied 'prop' property */
+  fail_unless (ges_timeline_element_lookup_child (pasted,
+          "posx", &pasted_sub_child, &found_prop));
+  fail_unless (found_prop == prop);
+  g_param_spec_unref (found_prop);
+  fail_unless (G_OBJECT_TYPE (pasted_sub_child) == G_OBJECT_TYPE (sub_child));
+
+  _assert_int_val_child_prop (pasted, val, 30, prop, "posx");
+
+  /* get the associated child */
+  fail_unless (pasted_el =
+      _el_with_child_prop (pasted, pasted_sub_child, prop));
+  _assert_int_val_child_prop (pasted_el, val, 30, prop, "posx");
+
+  pasted_el_props =
+      ges_timeline_element_list_children_properties (pasted_el,
+      &num_pasted_el_props);
+
+  assert_property_list_match (pasted_el_props, num_pasted_el_props,
+      track_el_props, num_track_el_props);
+
+  /* check the control binding on the pasted element */
+  _assert_binding (pasted_el, "posx", pasted_sub_child, timed_vals,
+      GST_INTERPOLATION_MODE_CUBIC);
+
+
+  /* free */
+  g_slist_free_full (timed_vals, g_free);
+
+  free_children_properties (pasted_props, num_pasted_props);
+  free_children_properties (orig_props, num_orig_props);
+  free_children_properties (pasted_el_props, num_pasted_el_props);
+  free_children_properties (track_el_props, num_track_el_props);
+
+  g_param_spec_unref (prop);
+  g_object_unref (pasted_sub_child);
+  g_object_unref (sub_child);
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+
 
 static Suite *
 ges_suite (void)
@@ -1251,6 +1446,7 @@ ges_suite (void)
   tcase_add_test (tc_chain, test_can_add_effect);
   tcase_add_test (tc_chain, test_children_properties_contain);
   tcase_add_test (tc_chain, test_children_properties_change);
+  tcase_add_test (tc_chain, test_copy_paste_children_properties);
 
   return s;
 }