track-element: use out-point for updating control bindings
authorHenry Wilkes <hwilkes@igalia.com>
Wed, 20 May 2020 20:20:10 +0000 (21:20 +0100)
committerHenry Wilkes <hwilkes@igalia.com>
Mon, 25 May 2020 10:20:38 +0000 (11:20 +0100)
The out-point, which is an internal time, is used instead of the
duration for determining the control binding value at the end of the
element.

Also, allow the user to switch off the auto-clamping of control sources
if they are not desired. And allow them to clamp specific control sources
individually.

Also, fix a lot of memory leaks related to control sources. In
particular, releasing the extra ref gained by source in
g_object_get (binding, "control-source", &source, NULL);

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

ges/ges-base-xml-formatter.c
ges/ges-clip.c
ges/ges-internal.h
ges/ges-timeline-tree.c
ges/ges-track-element.c
ges/ges-track-element.h
ges/ges-xml-formatter.c
tests/check/ges/clip.c
tests/check/ges/project.c

index 7b7338bd89674f9289171ac5c1936dc75a0e3f43..30d02c2e3bc3c5f80cd3870e431fb13d3433fcc9 100644 (file)
@@ -1081,8 +1081,7 @@ ges_base_xml_formatter_add_control_binding (GESBaseXmlFormatter * self,
   if (priv->state != STATE_LOADING_CLIPS) {
     GST_DEBUG_OBJECT (self, "Not loading control bindings in %s state.",
         loading_state_name (priv->state));
-    g_slist_free_full (timed_values, g_free);
-    return;
+    goto done;
   }
 
   if (track_id[0] != '-' && priv->current_clip)
@@ -1092,23 +1091,28 @@ ges_base_xml_formatter_add_control_binding (GESBaseXmlFormatter * self,
 
   if (element == NULL) {
     GST_WARNING ("No current track element to which we can append a binding");
-    return;
+    goto done;
   }
 
   if (!g_strcmp0 (source_type, "interpolation")) {
     GstControlSource *source;
 
     source = gst_interpolation_control_source_new ();
+
+    /* add first before setting values to avoid clamping */
     ges_track_element_set_control_source (element, source,
         property_name, binding_type);
 
     g_object_set (source, "mode", mode, NULL);
-
     gst_timed_value_control_source_set_from_list (GST_TIMED_VALUE_CONTROL_SOURCE
         (source), timed_values);
-    g_slist_free_full (timed_values, g_free);
+
+    gst_object_unref (source);
   } else
     GST_WARNING ("This interpolation type is not supported\n");
+
+done:
+  g_slist_free_full (timed_values, g_free);
 }
 
 void
index 33f7c5fa6df25c5fc05f92e073bc73ee5f4a1d6a..a29790da046c55c79085e49fa16404153c95ab23 100644 (file)
@@ -167,6 +167,7 @@ struct _GESClipPrivate
 
   GstClockTime duration_limit;
   gboolean prevent_duration_limit_update;
+  gboolean prevent_children_outpoint_update;
 
   gboolean allow_any_remove;
 
@@ -446,6 +447,19 @@ _calculate_duration_limit (GESClip * self, GList * child_data)
   return limit;
 }
 
+static void
+_update_children_outpoints (GESClip * self)
+{
+  GList *tmp;
+
+  if (self->priv->prevent_children_outpoint_update)
+    return;
+
+  for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) {
+    ges_track_element_update_outpoint (tmp->data);
+  }
+}
+
 static void
 _update_duration_limit (GESClip * self)
 {
@@ -864,6 +878,7 @@ _child_active_changed (GESClip * self, GESTrackElement * child)
   gboolean active = ges_track_element_is_active (child);
   gboolean is_core = _IS_CORE_CHILD (child);
   gboolean prev_prevent = self->priv->prevent_duration_limit_update;
+  gboolean prev_prevent_outpoint = self->priv->prevent_children_outpoint_update;
 
   /* We want to ensure that each active non-core element has a
    * corresponding active core element in the same track */
@@ -872,6 +887,7 @@ _child_active_changed (GESClip * self, GESTrackElement * child)
 
   self->priv->setting_active = TRUE;
   self->priv->prevent_duration_limit_update = TRUE;
+  self->priv->prevent_children_outpoint_update = TRUE;
 
   /* If we are core, make all the non-core elements in-active
    * If we are non-core, make the core element active (should only be one) */
@@ -895,6 +911,7 @@ _child_active_changed (GESClip * self, GESTrackElement * child)
 
   self->priv->setting_active = FALSE;
   self->priv->prevent_duration_limit_update = prev_prevent;
+  self->priv->prevent_children_outpoint_update = prev_prevent_outpoint;
 }
 
 /****************************************************
@@ -1025,6 +1042,7 @@ _update_active_for_track (GESClip * self, GESTrackElement * child)
   GESTrackElement *core;
   gboolean active;
   gboolean prev_prevent = self->priv->prevent_duration_limit_update;
+  gboolean prev_prevent_outpoint = self->priv->prevent_children_outpoint_update;
 
   if (self->priv->allow_any_track || _IS_CORE_CHILD (child) || !track)
     return;
@@ -1050,6 +1068,7 @@ _update_active_for_track (GESClip * self, GESTrackElement * child)
 
     self->priv->setting_active = TRUE;
     self->priv->prevent_duration_limit_update = TRUE;
+    self->priv->prevent_children_outpoint_update = TRUE;
 
     if (!ges_track_element_set_active (child, FALSE))
       GST_ERROR_OBJECT (self, "Failed to de-activate child %" GES_FORMAT,
@@ -1057,6 +1076,7 @@ _update_active_for_track (GESClip * self, GESTrackElement * child)
 
     self->priv->setting_active = FALSE;
     self->priv->prevent_duration_limit_update = prev_prevent;
+    self->priv->prevent_children_outpoint_update = prev_prevent_outpoint;
   }
 }
 
@@ -1066,29 +1086,36 @@ static void
 _child_property_changed_cb (GESTimelineElement * child, GParamSpec * pspec,
     GESClip * self)
 {
-  gboolean update = FALSE;
+  gboolean update_limit = FALSE;
+  gboolean update_outpoint = FALSE;
   const gchar *name = pspec->name;
 
   if (_IS_PROP ("track")) {
-    update = TRUE;
+    update_limit = TRUE;
+    update_outpoint = TRUE;
     _update_active_for_track (self, GES_TRACK_ELEMENT (child));
   } else if (_IS_PROP ("active")) {
-    update = TRUE;
+    update_limit = TRUE;
+    update_outpoint = TRUE;
     _child_active_changed (self, GES_TRACK_ELEMENT (child));
   } else if (_IS_PROP ("priority")) {
-    update = TRUE;
+    update_limit = TRUE;
+    update_outpoint = TRUE;
     _child_priority_changed (GES_CONTAINER (self), child);
   } else if (_IS_PROP ("in-point")) {
-    update = _child_inpoint_changed (self, child);
+    /* update outpoint already handled by the track element */
+    update_limit = _child_inpoint_changed (self, child);
   } else if (_IS_PROP ("max-duration")) {
-    update = TRUE;
+    update_limit = TRUE;
     _child_max_duration_changed (GES_CONTAINER (self), child);
   } else if (_IS_PROP ("has-internal-source")) {
     _child_has_internal_source_changed (self, child);
   }
 
-  if (update)
+  if (update_limit)
     _update_duration_limit (self);
+  if (update_outpoint)
+    _update_children_outpoints (self);
 }
 
 /****************************************************
@@ -1140,6 +1167,7 @@ _child_time_property_changed_cb (GESTimelineElement * child,
   if (time_prop) {
     g_free (time_prop);
     _update_duration_limit (self);
+    _update_children_outpoints (self);
   }
 }
 
@@ -1346,6 +1374,7 @@ _set_priority (GESTimelineElement * element, guint32 priority)
   GList *tmp;
   guint32 min_prio, max_prio, min_child_prio = G_MAXUINT32;
   gboolean prev_prevent = priv->prevent_duration_limit_update;
+  gboolean prev_prevent_outpoint = priv->prevent_children_outpoint_update;
   GESContainer *container = GES_CONTAINER (element);
 
   for (tmp = container->children; tmp; tmp = g_list_next (tmp))
@@ -1358,6 +1387,7 @@ _set_priority (GESTimelineElement * element, guint32 priority)
   /* offsets will remain constant for the children */
   priv->prevent_resort = TRUE;
   priv->prevent_duration_limit_update = TRUE;
+  priv->prevent_children_outpoint_update = TRUE;
   priv->setting_priority = TRUE;
   for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
     GESTimelineElement *child = tmp->data;
@@ -1378,6 +1408,7 @@ _set_priority (GESTimelineElement * element, guint32 priority)
   priv->prevent_resort = FALSE;
   priv->setting_priority = FALSE;
   priv->prevent_duration_limit_update = prev_prevent;
+  priv->prevent_children_outpoint_update = prev_prevent_outpoint;
 
   return TRUE;
 }
@@ -1480,6 +1511,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
   GESClipPrivate *priv = self->priv;
   GESAsset *asset, *creator_asset;
   gboolean prev_prevent = priv->prevent_duration_limit_update;
+  gboolean prev_prevent_outpoint = priv->prevent_children_outpoint_update;
   GList *tmp;
   GError *error = NULL;
 
@@ -1664,6 +1696,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
     priv->prevent_resort = TRUE;
     priv->setting_priority = TRUE;
     priv->prevent_duration_limit_update = TRUE;
+    priv->prevent_children_outpoint_update = TRUE;
 
     /* increase the priority of anything with a lower priority */
     for (tmp = container->children; tmp; tmp = tmp->next) {
@@ -1676,6 +1709,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
     priv->prevent_resort = FALSE;
     priv->setting_priority = FALSE;
     priv->prevent_duration_limit_update = prev_prevent;
+    priv->prevent_children_outpoint_update = prev_prevent_outpoint;
     /* no need to call _ges_container_sort_children (container) since
      * there is no change to the ordering yet (this happens after the
      * child is actually added) */
@@ -1776,12 +1810,14 @@ _remove_child (GESContainer * container, GESTimelineElement * element)
   if (_IS_TOP_EFFECT (element)) {
     GList *tmp;
     gboolean prev_prevent = priv->prevent_duration_limit_update;
+    gboolean prev_prevent_outpoint = priv->prevent_children_outpoint_update;
     GST_DEBUG_OBJECT (container, "Resyncing effects priority.");
 
     /* changing priorities, so preventing a re-sort */
     priv->prevent_resort = TRUE;
     priv->setting_priority = TRUE;
     priv->prevent_duration_limit_update = TRUE;
+    priv->prevent_children_outpoint_update = TRUE;
     for (tmp = container->children; tmp; tmp = tmp->next) {
       guint32 sibling_prio = GES_TIMELINE_ELEMENT_PRIORITY (tmp->data);
       if (sibling_prio > element->priority)
@@ -1792,6 +1828,7 @@ _remove_child (GESContainer * container, GESTimelineElement * element)
     priv->prevent_resort = FALSE;
     priv->setting_priority = FALSE;
     priv->prevent_duration_limit_update = prev_prevent;
+    priv->prevent_children_outpoint_update = prev_prevent_outpoint;
     /* no need to re-sort the children since the rest keep the same
      * relative priorities */
     /* height may have changed */
@@ -1817,6 +1854,7 @@ _child_added (GESContainer * container, GESTimelineElement * element)
     _update_max_duration (container);
 
   _update_duration_limit (self);
+  _update_children_outpoints (self);
 }
 
 static void
@@ -1835,6 +1873,8 @@ _child_removed (GESContainer * container, GESTimelineElement * element)
     _update_max_duration (container);
 
   _update_duration_limit (self);
+  _update_children_outpoints (self);
+  ges_track_element_update_outpoint (GES_TRACK_ELEMENT (element));
 }
 
 static void
@@ -1862,6 +1902,10 @@ _transfer_child (GESClip * from_clip, GESClip * to_clip,
   GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (to_clip);
   gboolean prev_prevent_from = from_clip->priv->prevent_duration_limit_update;
   gboolean prev_prevent_to = to_clip->priv->prevent_duration_limit_update;
+  gboolean prev_prevent_outpoint_from =
+      from_clip->priv->prevent_children_outpoint_update;
+  gboolean prev_prevent_outpoint_to =
+      to_clip->priv->prevent_children_outpoint_update;
 
   /* We need to bump the refcount to avoid the object to be destroyed */
   gst_object_ref (child);
@@ -1871,6 +1915,8 @@ _transfer_child (GESClip * from_clip, GESClip * to_clip,
 
   from_clip->priv->prevent_duration_limit_update = TRUE;
   to_clip->priv->prevent_duration_limit_update = TRUE;
+  from_clip->priv->prevent_children_outpoint_update = TRUE;
+  to_clip->priv->prevent_children_outpoint_update = TRUE;
 
   from_clip->priv->allow_any_remove = TRUE;
   ges_container_remove (GES_CONTAINER (from_clip),
@@ -1887,6 +1933,9 @@ _transfer_child (GESClip * from_clip, GESClip * to_clip,
 
   from_clip->priv->prevent_duration_limit_update = prev_prevent_from;
   to_clip->priv->prevent_duration_limit_update = prev_prevent_to;
+  from_clip->priv->prevent_children_outpoint_update =
+      prev_prevent_outpoint_from;
+  to_clip->priv->prevent_children_outpoint_update = prev_prevent_outpoint_to;
 
   gst_object_unref (child);
 }
@@ -2115,12 +2164,14 @@ ges_clip_empty_from_track (GESClip * clip, GESTrack * track)
 {
   GList *tmp;
   gboolean prev_prevent = clip->priv->prevent_duration_limit_update;
+  gboolean prev_prevent_outpoint = clip->priv->prevent_children_outpoint_update;
 
   if (track == NULL)
     return;
   /* allow us to remove in any order */
   clip->priv->allow_any_track = TRUE;
   clip->priv->prevent_duration_limit_update = TRUE;
+  clip->priv->prevent_children_outpoint_update = TRUE;
 
   for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
     GESTrackElement *child = tmp->data;
@@ -2132,7 +2183,9 @@ ges_clip_empty_from_track (GESClip * clip, GESTrack * track)
   }
   clip->priv->allow_any_track = FALSE;
   clip->priv->prevent_duration_limit_update = prev_prevent;
+  clip->priv->prevent_children_outpoint_update = prev_prevent_outpoint;
   _update_duration_limit (clip);
+  _update_children_outpoints (clip);
 }
 
 static GESTrackElement *
@@ -3193,8 +3246,7 @@ ges_clip_split_full (GESClip * clip, guint64 position, GError ** error)
   GList *tmp, *transitions = NULL;
   GESClip *new_object;
   gboolean no_core = FALSE;
-  GstClockTime start, inpoint, duration, old_duration, new_duration,
-      new_inpoint;
+  GstClockTime start, duration, old_duration, new_duration, new_inpoint;
   GESTimelineElement *element;
   GESTimeline *timeline;
   GHashTable *track_for_copy;
@@ -3210,7 +3262,6 @@ ges_clip_split_full (GESClip * clip, guint64 position, GError ** error)
 
   duration = element->duration;
   start = element->start;
-  inpoint = element->inpoint;
 
   if (position >= start + duration || position <= start) {
     GST_WARNING_OBJECT (clip, "Can not split %" GST_TIME_FORMAT
@@ -3259,6 +3310,7 @@ ges_clip_split_full (GESClip * clip, guint64 position, GError ** error)
   /* Create the new Clip */
   new_object = GES_CLIP (ges_timeline_element_copy (element, FALSE));
   new_object->priv->prevent_duration_limit_update = TRUE;
+  new_object->priv->prevent_children_outpoint_update = TRUE;
 
   GST_DEBUG_OBJECT (new_object, "New 'splitted' clip");
   /* Set new timing properties on the Clip */
@@ -3293,11 +3345,7 @@ ges_clip_split_full (GESClip * clip, guint64 position, GError ** error)
     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);
+    copy = ges_clip_copy_track_element_into (new_object, orig, new_inpoint);
 
     if (!copy)
       continue;
@@ -3347,7 +3395,9 @@ ges_clip_split_full (GESClip * clip, guint64 position, GError ** error)
   g_list_free_full (transitions, gst_object_unref);
 
   new_object->priv->prevent_duration_limit_update = FALSE;
+  new_object->priv->prevent_children_outpoint_update = FALSE;
   _update_duration_limit (new_object);
+  _update_children_outpoints (new_object);
 
   return new_object;
 }
index 3e8d95040c9666de275e1592b9627c4390318669..0241877685eeaed3cf2ef051c3d66fd3958fd248 100644 (file)
@@ -454,6 +454,9 @@ G_GNUC_INTERNAL void ges_track_element_set_layer_active         (GESTrackElement
 G_GNUC_INTERNAL void ges_track_element_copy_bindings (GESTrackElement *element,
                                                       GESTrackElement *new_element,
                                                       guint64 position);
+G_GNUC_INTERNAL void ges_track_element_freeze_control_sources   (GESTrackElement * object,
+                                                                 gboolean freeze);
+G_GNUC_INTERNAL void ges_track_element_update_outpoint          (GESTrackElement * self);
 
 G_GNUC_INTERNAL void
 ges_track_element_set_creator_asset                    (GESTrackElement * self,
index 67a99d70a95bd165ff29e596c0368e114996bef7..09f405a52f123d4393395bd9bc3786ade64074f6 100644 (file)
@@ -1861,6 +1861,12 @@ timeline_tree_perform_edits (GNode * root, GHashTable * edits)
   /* freeze the auto-transitions whilst we edit */
   ges_timeline_freeze_auto_transitions (root->data, TRUE);
 
+  g_hash_table_iter_init (&iter, edits);
+  while (g_hash_table_iter_next (&iter, &key, &value)) {
+    if (GES_IS_TRACK_ELEMENT (key))
+      ges_track_element_freeze_control_sources (GES_TRACK_ELEMENT (key), TRUE);
+  }
+
   g_hash_table_iter_init (&iter, edits);
   while (g_hash_table_iter_next (&iter, &key, &value)) {
     GESTimelineElement *element = key;
@@ -1868,6 +1874,13 @@ timeline_tree_perform_edits (GNode * root, GHashTable * edits)
     if (!perform_element_edit (element, edit_data))
       no_errors = FALSE;
   }
+
+  g_hash_table_iter_init (&iter, edits);
+  while (g_hash_table_iter_next (&iter, &key, &value)) {
+    if (GES_IS_TRACK_ELEMENT (key))
+      ges_track_element_freeze_control_sources (GES_TRACK_ELEMENT (key), FALSE);
+  }
+
   /* allow the transitions to update if they can */
   ges_timeline_freeze_auto_transitions (root->data, FALSE);
 
index ac4f2386d65a35ad4e8d4919daef4725f27304ec..691a829bc70049452ad144b5e92212baf03d453d 100644 (file)
  * tracks and take responsibility for updating them. The only track
  * elements that are not automatically created by clips, but a user is
  * likely to want to create, are #GESEffect-s.
+ *
+ * ## Control Bindings for Children Properties
+ *
+ * You can set up control bindings for a track element child property
+ * using ges_track_element_set_control_source(). A
+ * #GstTimedValueControlSource should specify the timed values using the
+ * internal source coordinates (see #GESTimelineElement). By default,
+ * these will be updated to lie between the #GESTimelineElement:in-point
+ * and out-point of the element. This can be switched off by setting
+ * #GESTrackElement:auto-clamp-control-sources to %FALSE.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -62,13 +72,15 @@ struct _GESTrackElementPrivate
   gboolean has_internal_source_forbidden;
   gboolean has_internal_source;
 
-  gboolean locked;              /* If TRUE, then moves in sync with its controlling
-                                 * GESClip */
   gboolean layer_active;
 
   GHashTable *bindings_hashtable;       /* We need this if we want to be able to serialize
                                            and deserialize keyframes */
   GESAsset *creator_asset;
+
+  GstClockTime outpoint;
+  gboolean freeze_control_sources;
+  gboolean auto_clamp_control_sources;
 };
 
 enum
@@ -78,6 +90,7 @@ enum
   PROP_TRACK_TYPE,
   PROP_TRACK,
   PROP_HAS_INTERNAL_SOURCE,
+  PROP_AUTO_CLAMP_CONTROL_SOURCES,
   PROP_LAST
 };
 
@@ -120,10 +133,6 @@ static gboolean _set_max_duration (GESTimelineElement * element,
 static gboolean _set_priority (GESTimelineElement * element, guint32 priority);
 GESTrackType _get_track_types (GESTimelineElement * object);
 
-static void
-_update_control_bindings (GESTimelineElement * element, GstClockTime inpoint,
-    GstClockTime duration);
-
 static gboolean
 _lookup_child (GESTrackElement * object,
     const gchar * prop_name, GstElement ** element, GParamSpec ** pspec)
@@ -197,6 +206,10 @@ ges_track_element_get_property (GObject * object, guint property_id,
       g_value_set_boolean (value,
           ges_track_element_has_internal_source (track_element));
       break;
+    case PROP_AUTO_CLAMP_CONTROL_SOURCES:
+      g_value_set_boolean (value,
+          ges_track_element_get_auto_clamp_control_sources (track_element));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
   }
@@ -220,6 +233,10 @@ ges_track_element_set_property (GObject * object, guint property_id,
       ges_track_element_set_has_internal_source (track_element,
           g_value_get_boolean (value));
       break;
+    case PROP_AUTO_CLAMP_CONTROL_SOURCES:
+      ges_track_element_set_auto_clamp_control_sources (track_element,
+          g_value_get_boolean (value));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
   }
@@ -412,6 +429,28 @@ ges_track_element_class_init (GESTrackElementClass * klass)
   g_object_class_install_property (object_class, PROP_HAS_INTERNAL_SOURCE,
       properties[PROP_HAS_INTERNAL_SOURCE]);
 
+  /**
+   * GESTrackElement:auto-clamp-control-sources:
+   *
+   * Whether the control sources on the element (see
+   * ges_track_element_set_control_source()) will be automatically
+   * updated whenever the #GESTimelineElement:in-point or out-point of the
+   * element change in value.
+   *
+   * See ges_track_element_clamp_control_source() for how this is done
+   * per control source.
+   *
+   * Default value: %TRUE
+   */
+  properties[PROP_AUTO_CLAMP_CONTROL_SOURCES] =
+      g_param_spec_boolean ("auto-clamp-control-sources",
+      "Auto-Clamp Control Sources", "Whether to automatically update the "
+      "control sources with a change in in-point or out-point", TRUE,
+      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+  g_object_class_install_property (object_class,
+      PROP_AUTO_CLAMP_CONTROL_SOURCES,
+      properties[PROP_AUTO_CLAMP_CONTROL_SOURCES]);
+
   /**
    * GESTrackElement::control-binding-added:
    * @track_element: A #GESTrackElement
@@ -483,6 +522,9 @@ ges_track_element_init (GESTrackElement * self)
    * because it calls g_object_new_with_properties.
    */
   self->priv->has_internal_source = TRUE;
+
+  self->priv->outpoint = GST_CLOCK_TIME_NONE;
+  self->priv->auto_clamp_control_sources = TRUE;
 }
 
 static gfloat
@@ -504,6 +546,7 @@ interpolate_values_for_position (GstTimedValue * first_value,
   diff = second_value->value - first_value->value;
   interval = second_value->timestamp - first_value->timestamp;
 
+  /* FIXME: properly support non-linear timed control sources */
   if (position > first_value->timestamp)
     value_at_pos =
         first_value->value + ((float) (position -
@@ -520,98 +563,116 @@ interpolate_values_for_position (GstTimedValue * first_value,
 }
 
 static void
-_update_control_bindings (GESTimelineElement * element, GstClockTime inpoint,
-    GstClockTime duration)
+_update_control_source (GstTimedValueControlSource * source, gboolean absolute,
+    GstClockTime inpoint, GstClockTime outpoint)
 {
-  GParamSpec **specs;
-  guint n, n_specs;
-  GstControlBinding *binding;
-  GstTimedValueControlSource *source;
-  GESTrackElement *self = GES_TRACK_ELEMENT (element);
-
-  specs = ges_track_element_list_children_properties (self, &n_specs);
-
-  for (n = 0; n < n_specs; ++n) {
-    GList *values, *tmp;
-    gboolean absolute;
-    GstTimedValue *last, *first, *prev = NULL, *next = NULL;
-    gfloat value_at_pos;
-
-    binding = ges_track_element_get_control_binding (self, specs[n]->name);
-
-    if (!binding)
-      continue;
-
-    g_object_get (binding, "control_source", &source, NULL);
+  GList *values, *tmp;
+  GstTimedValue *last, *first, *prev = NULL, *next = NULL;
+  gfloat value_at_pos;
 
-    g_object_get (binding, "absolute", &absolute, NULL);
-    if (duration == 0) {
-      gst_timed_value_control_source_unset_all (GST_TIMED_VALUE_CONTROL_SOURCE
-          (source));
-      continue;
-    }
+  if (inpoint == outpoint) {
+    gst_timed_value_control_source_unset_all (source);
+    return;
+  }
 
-    values =
-        gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE
-        (source));
+  values = gst_timed_value_control_source_get_all (source);
 
-    if (g_list_length (values) == 0)
-      continue;
+  if (g_list_length (values) == 0)
+    return;
 
-    first = values->data;
+  first = values->data;
 
-    for (tmp = values->next; tmp; tmp = tmp->next) {
-      next = tmp->data;
+  for (tmp = values->next; tmp; tmp = tmp->next) {
+    next = tmp->data;
 
-      if (next->timestamp > inpoint)
-        break;
+    if (next->timestamp == inpoint) {
+      /* just leave this value in place */
+      first = NULL;
+      break;
     }
-    g_list_free (values);
+    if (next->timestamp > inpoint)
+      break;
+  }
+  g_list_free (values);
 
+  if (first) {
     value_at_pos =
         interpolate_values_for_position (first, next, inpoint, absolute);
     gst_timed_value_control_source_unset (source, first->timestamp);
     gst_timed_value_control_source_set (source, inpoint, value_at_pos);
+  }
 
-    values =
-        gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE
-        (source));
+  if (GST_CLOCK_TIME_IS_VALID (outpoint)) {
+    values = gst_timed_value_control_source_get_all (source);
 
-    if (duration != GST_CLOCK_TIME_NONE) {
-      last = g_list_last (values)->data;
+    last = g_list_last (values)->data;
 
-      for (tmp = g_list_last (values)->prev; tmp; tmp = tmp->prev) {
-        prev = tmp->data;
+    for (tmp = g_list_last (values)->prev; tmp; tmp = tmp->prev) {
+      prev = tmp->data;
 
-        if (prev->timestamp < duration + inpoint)
-          break;
+      if (prev->timestamp == outpoint) {
+        /* leave this value in place */
+        last = NULL;
+        break;
       }
-      g_list_free (values);
+      if (prev->timestamp < outpoint)
+        break;
+    }
+    g_list_free (values);
 
+    if (last) {
       value_at_pos =
-          interpolate_values_for_position (prev, last, duration + inpoint,
-          absolute);
+          interpolate_values_for_position (prev, last, outpoint, absolute);
 
       gst_timed_value_control_source_unset (source, last->timestamp);
-      gst_timed_value_control_source_set (source, duration + inpoint,
-          value_at_pos);
-      values =
-          gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE
-          (source));
+      gst_timed_value_control_source_set (source, outpoint, value_at_pos);
     }
+  }
 
-    for (tmp = values; tmp; tmp = tmp->next) {
-      GstTimedValue *value = tmp->data;
-      if (value->timestamp < inpoint)
-        gst_timed_value_control_source_unset (source, value->timestamp);
-      else if (duration != GST_CLOCK_TIME_NONE
-          && value->timestamp > duration + inpoint)
-        gst_timed_value_control_source_unset (source, value->timestamp);
-    }
-    g_list_free (values);
+  values = gst_timed_value_control_source_get_all (source);
+
+  for (tmp = values; tmp; tmp = tmp->next) {
+    GstTimedValue *value = tmp->data;
+    if (value->timestamp < inpoint)
+      gst_timed_value_control_source_unset (source, value->timestamp);
+    else if (GST_CLOCK_TIME_IS_VALID (outpoint) && value->timestamp > outpoint)
+      gst_timed_value_control_source_unset (source, value->timestamp);
   }
+  g_list_free (values);
+}
 
-  g_free (specs);
+static void
+_update_control_bindings (GESTrackElement * self, GstClockTime inpoint,
+    GstClockTime outpoint)
+{
+  gchar *name;
+  GstControlBinding *binding;
+  GstControlSource *source;
+  gboolean absolute;
+  gpointer value, key;
+  GHashTableIter iter;
+
+  if (self->priv->freeze_control_sources)
+    return;
+
+  g_hash_table_iter_init (&iter, self->priv->bindings_hashtable);
+  while (g_hash_table_iter_next (&iter, &key, &value)) {
+    binding = value;
+    name = key;
+    g_object_get (binding, "control-source", &source, "absolute", &absolute,
+        NULL);
+
+    if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) {
+      GST_INFO_OBJECT (self, "Not updating %s because it does not have a"
+          " timed value control source", name);
+      gst_object_unref (source);
+      continue;
+    }
+
+    _update_control_source (GST_TIMED_VALUE_CONTROL_SOURCE (source), absolute,
+        inpoint, outpoint);
+    gst_object_unref (source);
+  }
 }
 
 static gboolean
@@ -626,6 +687,45 @@ _set_start (GESTimelineElement * element, GstClockTime start)
   return TRUE;
 }
 
+static void
+ges_track_element_update_outpoint_full (GESTrackElement * self,
+    GstClockTime inpoint, GstClockTime duration)
+{
+  GstClockTime current_inpoint = _INPOINT (self);
+  gboolean increase = (inpoint > current_inpoint);
+  GstClockTime outpoint = GST_CLOCK_TIME_NONE;
+  GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (self);
+  GESTrackElementPrivate *priv = self->priv;
+
+  if (GES_IS_CLIP (parent) && ges_track_element_get_track (self)
+      && ges_track_element_is_active (self)
+      && GST_CLOCK_TIME_IS_VALID (duration)) {
+    outpoint =
+        ges_clip_get_internal_time_from_timeline_time (GES_CLIP (parent), self,
+        _START (self) + duration, NULL);
+
+    if (!GST_CLOCK_TIME_IS_VALID (outpoint))
+      GST_ERROR_OBJECT (self, "Got an invalid out-point");
+    else if (increase)
+      outpoint += (inpoint - current_inpoint);
+    else
+      outpoint -= (current_inpoint - inpoint);
+  }
+
+  if ((priv->outpoint != outpoint || inpoint != current_inpoint)
+      && self->priv->auto_clamp_control_sources)
+    _update_control_bindings (self, inpoint, outpoint);
+
+  priv->outpoint = outpoint;
+}
+
+void
+ges_track_element_update_outpoint (GESTrackElement * self)
+{
+  GESTimelineElement *el = GES_TIMELINE_ELEMENT (self);
+  ges_track_element_update_outpoint_full (self, el->inpoint, el->duration);
+}
+
 static gboolean
 _set_inpoint (GESTimelineElement * element, GstClockTime inpoint)
 {
@@ -652,7 +752,8 @@ _set_inpoint (GESTimelineElement * element, GstClockTime inpoint)
   }
 
   g_object_set (object->priv->nleobject, "inpoint", inpoint, NULL);
-  _update_control_bindings (element, inpoint, GST_CLOCK_TIME_NONE);
+
+  ges_track_element_update_outpoint_full (object, inpoint, element->duration);
 
   return TRUE;
 }
@@ -663,16 +764,11 @@ _set_duration (GESTimelineElement * element, GstClockTime duration)
   GESTrackElement *object = GES_TRACK_ELEMENT (element);
   GESTrackElementPrivate *priv = object->priv;
 
-  g_return_val_if_fail (object->priv->nleobject, FALSE);
-
-  if (GST_CLOCK_TIME_IS_VALID (_MAXDURATION (element)) &&
-      duration > _INPOINT (object) + _MAXDURATION (element))
-    duration = _MAXDURATION (element) - _INPOINT (object);
+  g_return_val_if_fail (priv->nleobject, FALSE);
 
   g_object_set (priv->nleobject, "duration", duration, NULL);
 
-  _update_control_bindings (element, ges_timeline_element_get_inpoint (element),
-      duration);
+  ges_track_element_update_outpoint_full (object, element->inpoint, duration);
 
   return TRUE;
 }
@@ -1626,15 +1722,16 @@ ges_track_element_copy_bindings (GESTrackElement * element,
     if (!binding)
       continue;
 
-    g_object_get (binding, "control_source", &source, NULL);
+    g_object_get (binding, "control-source", &source, "absolute", &absolute,
+        NULL);
     if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) {
       GST_FIXME_OBJECT (element,
           "Implement support for control source type: %s",
           G_OBJECT_TYPE_NAME (source));
+      gst_object_unref (source);
       continue;
     }
 
-    g_object_get (binding, "absolute", &absolute, NULL);
     g_object_get (source, "mode", &mode, NULL);
 
     new_source =
@@ -1656,6 +1753,9 @@ ges_track_element_copy_bindings (GESTrackElement * element,
     else
       ges_track_element_set_control_source (new_element,
           GST_CONTROL_SOURCE (new_source), specs[n]->name, "direct");
+
+    gst_object_unref (source);
+    gst_object_unref (new_source);
   }
 
   g_free (specs);
@@ -1765,9 +1865,9 @@ ges_track_element_set_control_source (GESTrackElement * object,
     GstControlSource * source,
     const gchar * property_name, const gchar * binding_type)
 {
+  gboolean ret = FALSE;
   GESTrackElementPrivate *priv;
   GstElement *element;
-  GParamSpec *pspec;
   GstControlBinding *binding;
   gboolean direct, direct_absolute;
 
@@ -1780,7 +1880,7 @@ ges_track_element_set_control_source (GESTrackElement * object,
     return FALSE;
   }
 
-  if (!ges_track_element_lookup_child (object, property_name, &element, &pspec)) {
+  if (!ges_track_element_lookup_child (object, property_name, &element, NULL)) {
     GST_WARNING ("You need to provide a valid and controllable property name");
     return FALSE;
   }
@@ -1789,39 +1889,55 @@ ges_track_element_set_control_source (GESTrackElement * object,
   direct = !g_strcmp0 (binding_type, "direct");
   direct_absolute = !g_strcmp0 (binding_type, "direct-absolute");
 
-  if (direct || direct_absolute) {
-    /* First remove existing binding */
-    if (ges_track_element_remove_control_binding (object, property_name)) {
-      GST_LOG ("Removed old binding for property %s", property_name);
-    }
+  if (!direct && !direct_absolute) {
+    GST_WARNING_OBJECT (object, "Binding type must be in "
+        "[direct, direct-absolute]");
+    goto done;
+  }
 
-    if (direct_absolute)
-      binding =
-          gst_direct_control_binding_new_absolute (GST_OBJECT (element),
-          property_name, source);
-    else
-      binding =
-          gst_direct_control_binding_new (GST_OBJECT (element), property_name,
-          source);
-
-    gst_object_add_control_binding (GST_OBJECT (element), binding);
-    /* FIXME: maybe we should force the
-     * "ChildTypeName:property-name"
-     * format convention for child property names in bindings_hashtable.
-     * Currently the table may also contain
-     * "property-name"
-     * as keys.
-     */
-    g_hash_table_insert (priv->bindings_hashtable, g_strdup (property_name),
-        binding);
-    g_signal_emit (object, ges_track_element_signals[CONTROL_BINDING_ADDED],
-        0, binding);
-    return TRUE;
+  /* First remove existing binding */
+  if (ges_track_element_remove_control_binding (object, property_name))
+    GST_LOG_OBJECT (object, "Removed old binding for property %s",
+        property_name);
+
+  if (direct_absolute)
+    binding = gst_direct_control_binding_new_absolute (GST_OBJECT (element),
+        property_name, source);
+  else
+    binding = gst_direct_control_binding_new (GST_OBJECT (element),
+        property_name, source);
+
+  gst_object_add_control_binding (GST_OBJECT (element), binding);
+  /* FIXME: maybe we should force the
+   * "ChildTypeName:property-name"
+   * format convention for child property names in bindings_hashtable.
+   * Currently the table may also contain
+   * "property-name"
+   * as keys.
+   */
+  g_hash_table_insert (priv->bindings_hashtable, g_strdup (property_name),
+      binding);
+
+  if (GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)
+      && priv->auto_clamp_control_sources) {
+    /* Make sure we have the control source used by the binding */
+    g_object_get (binding, "control-source", &source, NULL);
+
+    _update_control_source (GST_TIMED_VALUE_CONTROL_SOURCE (source),
+        direct_absolute, _INPOINT (object), priv->outpoint);
+
+    gst_object_unref (source);
   }
 
-  GST_WARNING ("Binding type must be in [direct]");
+  g_signal_emit (object, ges_track_element_signals[CONTROL_BINDING_ADDED],
+      0, binding);
 
-  return FALSE;
+  ret = TRUE;
+
+done:
+  gst_object_unref (element);
+
+  return ret;
 }
 
 /**
@@ -1857,6 +1973,105 @@ ges_track_element_get_control_binding (GESTrackElement * object,
   return binding;
 }
 
+/**
+ * ges_track_element_clamp_control_source:
+ * @object: A #GESTrackElement
+ * @property_name: The name of the child property to clamp the control
+ * source of
+ *
+ * Clamp the #GstTimedValueControlSource for the specified child property
+ * to lie between the #GESTimelineElement:in-point and out-point of the
+ * element. The out-point is the #GES_TIMELINE_ELEMENT_END of the element
+ * translated from the timeline coordinates to the internal source
+ * coordinates of the element.
+ *
+ * If the property does not have a #GstTimedValueControlSource set by
+ * ges_track_element_set_control_source(), nothing happens. Otherwise, if
+ * a timed value for the control source lies before the in-point of the
+ * element, or after its out-point, then it will be removed. At the
+ * in-point and out-point times, a new interpolated value will be placed.
+ */
+void
+ges_track_element_clamp_control_source (GESTrackElement * object,
+    const gchar * property_name)
+{
+  GstControlBinding *binding;
+  GstControlSource *source;
+  gboolean absolute;
+
+  g_return_if_fail (GES_IS_TRACK_ELEMENT (object));
+
+  binding = ges_track_element_get_control_binding (object, property_name);
+
+  if (!binding)
+    return;
+
+  g_object_get (binding, "control-source", &source, "absolute", &absolute,
+      NULL);
+
+  if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) {
+    gst_object_unref (source);
+    return;
+  }
+
+  _update_control_source (GST_TIMED_VALUE_CONTROL_SOURCE (source), absolute,
+      _INPOINT (object), object->priv->outpoint);
+  gst_object_unref (source);
+}
+
+/**
+ * ges_track_element_set_auto_clamp_control_sources:
+ * @object: A #GESTrackElement
+ * @auto_clamp: Whether to automatically clamp the control sources for the
+ * child properties of @object
+ *
+ * Sets #GESTrackElement:auto-clamp-control-sources. If set to %TRUE, this
+ * will immediately clamp all the control sources.
+ */
+void
+ges_track_element_set_auto_clamp_control_sources (GESTrackElement * object,
+    gboolean auto_clamp)
+{
+  g_return_if_fail (GES_IS_TRACK_ELEMENT (object));
+
+  if (auto_clamp == object->priv->auto_clamp_control_sources)
+    return;
+
+  object->priv->auto_clamp_control_sources = auto_clamp;
+  if (auto_clamp)
+    _update_control_bindings (object, _INPOINT (object),
+        object->priv->outpoint);
+
+  g_object_notify_by_pspec (G_OBJECT (object),
+      properties[PROP_AUTO_CLAMP_CONTROL_SOURCES]);
+}
+
+/**
+ * ges_track_element_get_auto_clamp_control_sources:
+ * @object: A #GESTrackElement
+ *
+ * Gets #GESTrackElement:auto-clamp-control-sources.
+ *
+ * Returns: Whether the control sources for the child properties of
+ * @object are automatically clamped.
+ */
+gboolean
+ges_track_element_get_auto_clamp_control_sources (GESTrackElement * object)
+{
+  g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
+
+  return object->priv->auto_clamp_control_sources;
+}
+
+void
+ges_track_element_freeze_control_sources (GESTrackElement * object,
+    gboolean freeze)
+{
+  object->priv->freeze_control_sources = freeze;
+  if (!freeze && object->priv->auto_clamp_control_sources)
+    _update_control_bindings (object, _INPOINT (object),
+        object->priv->outpoint);
+}
 
 /**
  * ges_track_element_is_core:
index ccf797ebf959d0370a01470dbde2d39791815bbc..4e3e2d8e947a706c43c3dc5ad4ab8bd7e8b4fc63 100644 (file)
@@ -161,6 +161,16 @@ ges_track_element_set_control_source          (GESTrackElement *object,
                                                const gchar *property_name,
                                                const gchar *binding_type);
 
+GES_API void
+ges_track_element_clamp_control_source        (GESTrackElement * object,
+                                               const gchar * property_name);
+
+GES_API void
+ges_track_element_set_auto_clamp_control_sources (GESTrackElement * object,
+                                                  gboolean auto_clamp);
+GES_API gboolean
+ges_track_element_get_auto_clamp_control_sources (GESTrackElement * object);
+
 GES_API GstControlBinding *
 ges_track_element_get_control_binding         (GESTrackElement *object,
                                                const gchar *property_name);
index b44a176ef081bc9f64393e97f5fb0793398658e3..6082640d25bf0e89a44208152965a42f55dd1cf4 100644 (file)
@@ -1511,6 +1511,8 @@ _save_keyframes (GString * str, GESTrackElement * trackelement, gint index,
         append_escaped (str, g_markup_printf_escaped ("'/>\n"), depth);
       } else
         GST_DEBUG ("control source not in [interpolation]");
+
+      gst_object_unref (source);
     } else
       GST_DEBUG ("Binding type not in [direct, direct-absolute]");
   }
index 156e1f9ff68f8e0ad4e9e3b3fbc98f0b02e8fad8..9e034b164a47381285059700c0c979159669acfc 100644 (file)
@@ -203,7 +203,10 @@ GST_START_TEST (test_split_direct_bindings)
 
   CHECK_OBJECT_PROPS (clip, 0 * GST_SECOND, 10 * GST_SECOND, 5 * GST_SECOND);
   check_layer (clip, 0);
+
   gst_object_unref (timeline);
+  gst_object_unref (source);
+  gst_object_unref (splitsource);
 
   ges_deinit ();
 }
@@ -303,6 +306,9 @@ GST_START_TEST (test_split_direct_absolute_bindings)
   check_layer (clip, 0);
 
   gst_object_unref (timeline);
+  gst_object_unref (source);
+  gst_object_unref (splitsource);
+
   ges_deinit ();
 }
 
@@ -4573,11 +4579,12 @@ _new_timed_value (GstClockTime time, gdouble val)
   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); \
+      prop_name, GES_TIMELINE_ELEMENT_NAME (element)); \
   g_object_get (G_OBJECT (binding), "control-source", &source, \
       "object", &found_object, NULL); \
   \
-  fail_unless (found_object == child); \
+  if (child) \
+    fail_unless (found_object == child); \
   g_object_unref (found_object); \
   \
   fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source)); \
@@ -4587,17 +4594,19 @@ _new_timed_value (GstClockTime time, gdouble val)
   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); \
+    gdouble diff = (val1->value > val2->value) ? \
+        val1->value - val2->value : val2->value - val1->value; \
+    fail_unless (val1->timestamp == val2->timestamp && diff < 0.0001, \
+        "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); \
+  fail_unless (found_mode == mode); \
   g_object_unref (source); \
 }
 
@@ -4641,6 +4650,8 @@ GST_START_TEST (test_copy_paste_children_properties)
   /* 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");
+  ges_track_element_set_auto_clamp_control_sources (GES_TRACK_ELEMENT
+      (track_el), FALSE);
 
   /* set a control binding */
   timed_vals = g_slist_prepend (NULL, _new_timed_value (200, 5));
@@ -4709,6 +4720,279 @@ GST_START_TEST (test_copy_paste_children_properties)
 
 GST_END_TEST;
 
+#define _THREE_TIMED_VALS(timed_vals, tm1, val1, tm2, val2, tm3, val3) \
+  if (timed_vals) \
+    g_slist_free_full (timed_vals, g_free); \
+  timed_vals = g_slist_prepend (NULL, _new_timed_value (tm3, val3)); \
+  timed_vals = g_slist_prepend (timed_vals, _new_timed_value (tm2, val2)); \
+  timed_vals = g_slist_prepend (timed_vals, _new_timed_value (tm1, val1));
+
+#define _TWO_TIMED_VALS(timed_vals, tm1, val1, tm2, val2) \
+  if (timed_vals) \
+    g_slist_free_full (timed_vals, g_free); \
+  timed_vals = g_slist_prepend (NULL, _new_timed_value (tm2, val2)); \
+  timed_vals = g_slist_prepend (timed_vals, _new_timed_value (tm1, val1));
+
+#define _assert_control_source(obj, prop, vals) \
+  _assert_binding (obj, prop, NULL, vals, GST_INTERPOLATION_MODE_LINEAR);
+
+GST_START_TEST (test_children_property_bindings_with_rate_effects)
+{
+  GESTimeline *timeline;
+  GESTrack *track;
+  GESLayer *layer;
+  GESClip *clip;
+  GESTrackElement *video_source, *rate0, *rate1, *overlay;
+  GstControlSource *ctrl_source;
+  GSList *video_source_vals = NULL, *overlay_vals = NULL;
+  GValue value = G_VALUE_INIT;
+  GstControlBinding *binding;
+
+  ges_init ();
+
+  g_value_init (&value, G_TYPE_DOUBLE);
+
+  timeline = ges_timeline_new ();
+  track = GES_TRACK (ges_video_track_new ());
+  fail_unless (ges_timeline_add_track (timeline, track));
+
+  layer = ges_timeline_append_layer (timeline);
+
+  clip = GES_CLIP (ges_test_clip_new ());
+  assert_set_duration (clip, 4);
+  assert_set_start (clip, 20);
+  assert_set_inpoint (clip, 3);
+
+  fail_unless (ges_layer_add_clip (layer, clip));
+
+  video_source = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE);
+  fail_unless (video_source);
+  gst_object_unref (video_source);
+
+  rate0 = GES_TRACK_ELEMENT (ges_effect_new ("videorate rate=0.5"));
+  rate1 = GES_TRACK_ELEMENT (ges_effect_new ("videorate rate=4.0"));
+  overlay = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay"));
+  ges_track_element_set_has_internal_source (overlay, TRUE);
+  assert_set_inpoint (overlay, 9);
+
+  fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (rate0), -1,
+          NULL));
+  fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (overlay), 0,
+          NULL));
+  fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (rate1), 0,
+          NULL));
+
+  fail_unless (ges_track_element_get_auto_clamp_control_sources (video_source));
+  fail_unless (ges_track_element_get_auto_clamp_control_sources (overlay));
+
+  /* source's alpha property */
+  _THREE_TIMED_VALS (video_source_vals, 1, 0.7, 7, 1.0, 15, 0.2);
+
+  ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ());
+  g_object_set (G_OBJECT (ctrl_source), "mode",
+      GST_INTERPOLATION_MODE_LINEAR, NULL);
+  fail_unless (gst_timed_value_control_source_set_from_list
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), video_source_vals));
+
+  fail_unless (ges_track_element_set_control_source (video_source, ctrl_source,
+          "alpha", "direct"));
+  gst_object_unref (ctrl_source);
+
+  /* values have been clamped between its in-point:3 and its
+   * out-point:11 (4ns in timeline is 8ns in source) */
+  _THREE_TIMED_VALS (video_source_vals, 3, 0.8, 7, 1.0, 11, 0.6);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* overlay's xpos property */
+  _THREE_TIMED_VALS (overlay_vals, 9, 12, 17, 16, 25, 8);
+
+  ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ());
+  g_object_set (G_OBJECT (ctrl_source), "mode",
+      GST_INTERPOLATION_MODE_LINEAR, NULL);
+  fail_unless (gst_timed_value_control_source_set_from_list
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), overlay_vals));
+
+  fail_unless (ges_track_element_set_control_source (overlay, ctrl_source,
+          "xpos", "direct-absolute"));
+  gst_object_unref (ctrl_source);
+
+  /* unchanged since values are at the edges already
+   * in-point:9 out-point:25 (4ns in timeline is 16ns in source) */
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* setting the in-point changes the in-point and out-point */
+  /* increase in-point */
+  assert_set_inpoint (video_source, 5);
+
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 13, 0.4);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* decrease in-point */
+  assert_set_inpoint (overlay, 7);
+
+  _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 23, 10);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* when trimming start, out-point should stay the same */
+  fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip),
+          -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, 19, NULL));
+
+  /* in-point of video_source now 3 */
+  _THREE_TIMED_VALS (video_source_vals, 3, 0.8, 7, 1.0, 13, 0.4);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* in-point of video_source now 3 */
+  _THREE_TIMED_VALS (overlay_vals, 3, 9, 17, 16, 23, 10);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* trim forwards */
+  fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip),
+          -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, 20, NULL));
+
+  /* in-point of video_source now 5 again */
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 13, 0.4);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* in-point of overlay now 7 again */
+  _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 23, 10);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* trim end */
+  fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip),
+          -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 25, NULL));
+
+  /* out-point of video_source now 15 */
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 15, 0.2);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* out-point of overlay now 27 */
+  _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 27, 6);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* trim backwards */
+  fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip),
+          -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 23, NULL));
+
+  /* out-point of video_source now 11 */
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* in-point of overlay now 19 */
+  _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 19, 14);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* changing the rate changes the out-point */
+  _assert_set_rate (rate0, "rate", 1.0, value);
+
+  /* out-point of video_source now 17 */
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 17, 0.0);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* unchanged for overlay, which is after rate0 */
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* change back */
+  _assert_set_rate (rate0, "rate", 0.5, value);
+
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* unchanged for overlay, which is after rate0 */
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* make inactive */
+  fail_unless (ges_track_element_set_active (rate0, FALSE));
+
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 17, 0.0);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* unchanged for overlay, which is after rate0 */
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* make active again */
+  fail_unless (ges_track_element_set_active (rate0, TRUE));
+
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* unchanged for overlay, which is after rate0 */
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* change order */
+  fail_unless (ges_clip_set_top_effect_index (clip, GES_BASE_EFFECT (overlay),
+          2));
+
+  /* video source unchanged */
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* new out-point is 13
+   * new value is interpolated between the previous value
+   * (at time 7, value 11) and the *final* value (at time 19, value 14)
+   * Not the middle value at time 17, value 16! */
+  _TWO_TIMED_VALS (overlay_vals, 7, 11, 13, 12.5);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* removing time effect changes out-point */
+  gst_object_ref (rate0);
+  fail_unless (ges_clip_remove_top_effect (clip, GES_BASE_EFFECT (rate0),
+          NULL));
+
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 17, 0.0);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  _TWO_TIMED_VALS (overlay_vals, 7, 11, 19, 14);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* adding also changes it */
+  fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (rate0), 2,
+          NULL));
+
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* unchanged for overlay */
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* new value will use the value already set at in-point if possible */
+
+  assert_set_inpoint (video_source, 7);
+
+  _TWO_TIMED_VALS (video_source_vals, 7, 1.0, 13, 0.4);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* same with out-point for overlay */
+  binding = ges_track_element_get_control_binding (overlay, "xpos");
+  fail_unless (binding);
+  g_object_get (binding, "control-source", &ctrl_source, NULL);
+
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 11, 5));
+  gst_object_unref (ctrl_source);
+  _THREE_TIMED_VALS (overlay_vals, 7, 11, 11, 5, 19, 14);
+
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip),
+          -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 21, NULL));
+
+  _TWO_TIMED_VALS (video_source_vals, 7, 1.0, 9, 0.8);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* overlay uses existing value, rather than an interpolation */
+  _TWO_TIMED_VALS (overlay_vals, 7, 11, 11, 5);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  g_slist_free_full (video_source_vals, g_free);
+  g_slist_free_full (overlay_vals, g_free);
+
+  gst_object_unref (timeline);
+  g_value_unset (&value);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
 GST_START_TEST (test_unchanged_after_layer_add_failure)
 {
   GList *found;
@@ -5257,6 +5541,7 @@ ges_suite (void)
   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);
+  tcase_add_test (tc_chain, test_children_property_bindings_with_rate_effects);
   tcase_add_test (tc_chain, test_unchanged_after_layer_add_failure);
   tcase_add_test (tc_chain, test_convert_time);
 
index 3c544df158e8d0a3170b8735f7b0c40d3b650a6b..d5d7c73234d3dd2f8a815954174813261e5822c5 100644 (file)
@@ -342,6 +342,8 @@ _add_properties (GESTimeline * timeline)
                 (source), 5 * GST_SECOND, 0.);
             gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE
                 (source), 10 * GST_SECOND, 1.);
+
+            gst_object_unref (source);
           } else if (GES_IS_VIDEO_SOURCE (element)) {
             /* Adding children properties */
             gint64 posx = 42;
@@ -412,6 +414,7 @@ _check_properties (GESTimeline * timeline)
             fail_unless (value->value == 1.);
             fail_unless (value->timestamp == 10 * GST_SECOND);
             g_list_free (timed_values);
+            gst_object_unref (source);
           }
           /* Checking children properties */
           else if (GES_IS_VIDEO_SOURCE (element)) {