effect: Add support for time effects
authorHenry Wilkes <hwilkes@igalia.com>
Fri, 15 May 2020 13:28:09 +0000 (14:28 +0100)
committerHenry Wilkes <hwilkes@igalia.com>
Fri, 22 May 2020 18:16:04 +0000 (19:16 +0100)
Allow the user to register a child property of a base effect as a time
property. This can be used by GES to correctly calculate the
duration-limit of a clip when it has time effects on it. The existing
ges_effect_class_register_rate_property is now used to automatically
register such time effects for rate effects.

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

ges/ges-base-effect-clip.c
ges/ges-base-effect.c
ges/ges-base-effect.h
ges/ges-clip.c
ges/ges-effect.c
ges/ges-internal.h
ges/ges-timeline-element.c
ges/ges-timeline-element.h
ges/ges-track-element.c
tests/check/ges/clip.c

index 118832c53ef0303a351b4328ba644ab8a54f5792..952abf5a195ec244dd066c7ae6695f1e87061325 100644 (file)
@@ -31,6 +31,9 @@
  * non-core elements. These additional effects are applied to the output
  * of the core effects of the clip that they share a #GESTrack with. See
  * #GESClip for how to add and move these effects from the clip.
+ *
+ * Note that you cannot add time effects to #GESBaseEffectClip, neither
+ * as core children, nor as additional effects.
  */
 
 /* FIXME: properly handle the priority of the children. How should we sort
@@ -51,15 +54,32 @@ struct _GESBaseEffectClipPrivate
 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESBaseEffectClip, ges_base_effect_clip,
     GES_TYPE_OPERATION_CLIP);
 
+static gboolean
+ges_base_effect_clip_add_child (GESContainer * container,
+    GESTimelineElement * element)
+{
+  if (GES_IS_TIME_EFFECT (element)) {
+    GST_WARNING_OBJECT (container, "Cannot add %" GES_FORMAT " as a child "
+        "because it is a time effect", GES_ARGS (element));
+    return FALSE;
+  }
+
+  return
+      GES_CONTAINER_CLASS (ges_base_effect_clip_parent_class)->add_child
+      (container, element);
+}
+
 static void
 ges_base_effect_clip_class_init (GESBaseEffectClipClass * klass)
 {
+  GESContainerClass *container_class = GES_CONTAINER_CLASS (klass);
+
   GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) = TRUE;
+  container_class->add_child = ges_base_effect_clip_add_child;
 }
 
 static void
 ges_base_effect_clip_init (GESBaseEffectClip * self)
 {
   self->priv = ges_base_effect_clip_get_instance_private (self);
-
 }
index b604833c816f35544715e787fe80cf2e7e7ab1d6..19c9e4bf358d96bacfdee182db11d89de72664e0 100644 (file)
  * @title: GESBaseEffect
  * @short_description: adds an effect to a stream in a GESSourceClip or a
  * GESLayer
+ *
+ * A #GESBaseEffect is some operation that applies an effect to the data
+ * it receives.
+ *
+ * ## Time Effects
+ *
+ * Some operations will change the timing of the stream data they receive
+ * in some way. In particular, the #GstElement that they wrap could alter
+ * the times of the segment they receive in a #GST_EVENT_SEGMENT event,
+ * or the times of a seek they receive in a #GST_EVENT_SEEK event. Such
+ * operations would be considered time effects since they translate the
+ * times they receive on their source to different times at their sink,
+ * and vis versa. This introduces two sets of time coordinates for the
+ * event: (internal) sink coordinates and (internal) source coordinates,
+ * where segment times are translated from the sink coordinates to the
+ * source coordinates, and seek times are translated from the source
+ * coordinates to the sink coordinates.
+ *
+ * If you use such an effect in GES, you will need to inform GES of the
+ * properties that control the timing with
+ * ges_base_effect_register_time_property(), and the effect's timing
+ * behaviour using ges_base_effect_set_time_translation_funcs().
+ *
+ * Note that a time effect should not have its
+ * #GESTrackElement:has-internal-source set to %TRUE.
+ *
+ * In addition, note that GES only *fully* supports time effects whose
+ * mapping from the source to sink coordinates (those applied to seeks)
+ * obeys:
+ *
+ * + Maps the time `0` to `0`. So initial time-shifting effects are
+ *   excluded.
+ * + Is monotonically increasing. So reversing effects, and effects that
+ *   jump backwards in the stream are excluded.
+ * + Can handle a reasonable #GstClockTime, relative to the project. So
+ *   this would exclude a time effect with an extremely large speed-up
+ *   that would cause the converted #GstClockTime seeks to overflow.
+ * + Is 'continuously reversible'. This essentially means that for every
+ *   time in the sink coordinates, we can, to 'good enough' accuracy,
+ *   calculate the corresponding time in the source coordinates. Moreover,
+ *   this should correspond to how segment times are translated from
+ *   sink to source.
+ * + Only depends on the registered time properties, rather than the
+ *   state of the #GstElement or the data it receives. This would exclude,
+ *   say, an effect that would speedup if there is more red in the image
+ *   it receives.
+ *
+ * Note that a constant-rate-change effect that is not extremely fast or
+ * slow would satisfy these conditions. For such effects, you may wish to
+ * use ges_effect_class_register_rate_property().
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #include "ges-track-element.h"
 #include "ges-base-effect.h"
 
+typedef struct _TimePropertyData
+{
+  gchar *property_name;
+  GObject *child;
+  GParamSpec *pspec;
+} TimePropertyData;
+
+static void
+_time_property_data_free (gpointer data_p)
+{
+  TimePropertyData *data = data_p;
+  g_free (data->property_name);
+  gst_object_unref (data->child);
+  g_param_spec_unref (data->pspec);
+  g_free (data);
+}
+
 struct _GESBaseEffectPrivate
 {
-  void *nothing;
+  GList *time_properties;
+  GESBaseEffectTimeTranslationFunc source_to_sink;
+  GESBaseEffectTimeTranslationFunc sink_to_source;
+  gpointer translation_data;
+  GDestroyNotify destroy_translation_data;
 };
 
 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESBaseEffect, ges_base_effect,
     GES_TYPE_OPERATION);
 
+static gboolean
+ges_base_effect_set_child_property_full (GESTimelineElement * element,
+    GObject * child, GParamSpec * pspec, const GValue * value, GError ** error)
+{
+  GESClip *parent = GES_IS_CLIP (element->parent) ?
+      GES_CLIP (element->parent) : NULL;
+
+  if (parent && !ges_clip_can_set_time_property_of_child (parent,
+          GES_TRACK_ELEMENT (element), child, pspec, value, error)) {
+    GST_INFO_OBJECT (element, "Cannot set time property '%s::%s' "
+        "because the parent clip %" GES_FORMAT " would not allow it",
+        G_OBJECT_TYPE_NAME (child), pspec->name, GES_ARGS (parent));
+    return FALSE;
+  }
+
+  return
+      GES_TIMELINE_ELEMENT_CLASS
+      (ges_base_effect_parent_class)->set_child_property_full (element, child,
+      pspec, value, error);
+}
+
+static void
+ges_base_effect_dispose (GObject * object)
+{
+  GESBaseEffectPrivate *priv = GES_BASE_EFFECT (object)->priv;
+
+  g_list_free_full (priv->time_properties, _time_property_data_free);
+  priv->time_properties = NULL;
+  if (priv->destroy_translation_data)
+    priv->destroy_translation_data (priv->translation_data);
+  priv->destroy_translation_data = NULL;
+  priv->source_to_sink = NULL;
+  priv->sink_to_source = NULL;
+
+  G_OBJECT_CLASS (ges_base_effect_parent_class)->dispose (object);
+}
+
 static void
 ges_base_effect_class_init (GESBaseEffectClass * klass)
 {
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass);
+
+  object_class->dispose = ges_base_effect_dispose;
+  element_class->set_child_property_full =
+      ges_base_effect_set_child_property_full;
 }
 
 static void
@@ -52,3 +166,261 @@ ges_base_effect_init (GESBaseEffect * self)
 {
   self->priv = ges_base_effect_get_instance_private (self);
 }
+
+static void
+_child_property_removed (GESTimelineElement * element, GObject * child,
+    GParamSpec * pspec, gpointer user_data)
+{
+  GList *tmp;
+  GESBaseEffectPrivate *priv = GES_BASE_EFFECT (element)->priv;
+
+  for (tmp = priv->time_properties; tmp; tmp = tmp->next) {
+    TimePropertyData *data = tmp->data;
+    if (data->child == child && data->pspec == pspec) {
+      priv->time_properties = g_list_remove (priv->time_properties, data);
+      _time_property_data_free (data);
+      return;
+    }
+  }
+}
+
+/**
+ * ges_base_effect_register_time_property:
+ * @effect: A #GESBaseEffect
+ * @child_property_name: The name of the child property to register as
+ * a time property
+ *
+ * Register a child property of the effect as a property that, when set,
+ * can change the timing of its input data. The child property should be
+ * specified as in ges_timeline_element_lookup_child().
+ *
+ * You should also set the corresponding time translation using
+ * ges_base_effect_set_time_translation_funcs().
+ *
+ * Note that @effect must not be part of a clip, nor can it have
+ * #GESTrackElement:has-internal-source set to %TRUE.
+ *
+ * Returns: %TRUE if the child property was found and newly registered.
+ */
+gboolean
+ges_base_effect_register_time_property (GESBaseEffect * effect,
+    const gchar * child_property_name)
+{
+  GESTimelineElement *element;
+  GESTrackElement *el;
+  GParamSpec *pspec;
+  GObject *child;
+  GList *tmp;
+  TimePropertyData *data;
+
+  g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE);
+  el = GES_TRACK_ELEMENT (effect);
+  element = GES_TIMELINE_ELEMENT (el);
+
+  g_return_val_if_fail (element->parent == NULL, FALSE);
+  g_return_val_if_fail (ges_track_element_has_internal_source (el) == FALSE,
+      FALSE);
+
+  if (!ges_timeline_element_lookup_child (element, child_property_name,
+          &child, &pspec))
+    return FALSE;
+
+  for (tmp = effect->priv->time_properties; tmp; tmp = tmp->next) {
+    data = tmp->data;
+    if (data->child == child && data->pspec == pspec) {
+      GST_WARNING_OBJECT (effect, "Already registered the time effect for %s",
+          child_property_name);
+      g_object_unref (child);
+      g_param_spec_unref (pspec);
+      return FALSE;
+    }
+  }
+
+  ges_track_element_set_has_internal_source_is_forbidden (el);
+
+  data = g_new0 (TimePropertyData, 1);
+  data->child = child;
+  data->pspec = pspec;
+  data->property_name = g_strdup (child_property_name);
+
+  effect->priv->time_properties =
+      g_list_prepend (effect->priv->time_properties, data);
+
+  g_signal_handlers_disconnect_by_func (effect, _child_property_removed, NULL);
+  g_signal_connect (effect, "child-property-removed",
+      G_CALLBACK (_child_property_removed), NULL);
+
+  return TRUE;
+}
+
+/**
+ * ges_base_effect_set_time_translation_funcs:
+ * @effect: A #GESBaseEffect
+ * @source_to_sink_func: (nullable) (scope notified): The function to use
+ * for querying how a time is translated from the source coordinates to
+ * the sink coordinates of @effect
+ * @sink_to_source_func: (nullable) (scope notified): The function to use
+ * for querying how a time is translated from the sink coordinates to the
+ * source coordinates of @effect
+ * @user_data: (closure): Data to pass to both @source_to_sink_func and
+ * @sink_to_source_func
+ * @destroy: (destroy user_data) (nullable): Method to call to destroy
+ * @user_data, or %NULL
+ *
+ * Set the time translation query functions for the time effect. If an
+ * effect is a time effect, it will have two sets of coordinates: one
+ * at its sink and one at its source. The given functions should be able
+ * to translate between these two sets of coordinates. More specifically,
+ * @source_to_sink_func should *emulate* how the corresponding #GstElement
+ * would translate the #GstSegment @time field, and @sink_to_source_func
+ * should emulate how the corresponding #GstElement would translate the
+ * seek query @start and @stop values, as used in gst_element_seek(). As
+ * such, @sink_to_source_func should act as an approximate reverse of
+ * @source_to_sink_func.
+ *
+ * Note, these functions will be passed a table of time properties, as
+ * registered in ges_base_effect_register_time_property(), and their
+ * values. The functions should emulate what the translation *would* be
+ * *if* the time properties were set to the given values. They should not
+ * use the currently set values.
+ *
+ * Note that @effect must not be part of a clip, nor can it have
+ * #GESTrackElement:has-internal-source set to %TRUE.
+ *
+ * Returns: %TRUE if the translation functions were set.
+ */
+gboolean
+ges_base_effect_set_time_translation_funcs (GESBaseEffect * effect,
+    GESBaseEffectTimeTranslationFunc source_to_sink_func,
+    GESBaseEffectTimeTranslationFunc sink_to_source_func,
+    gpointer user_data, GDestroyNotify destroy)
+{
+  GESTimelineElement *element;
+  GESTrackElement *el;
+  GESBaseEffectPrivate *priv;
+
+  g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE);
+
+  element = GES_TIMELINE_ELEMENT (effect);
+  el = GES_TRACK_ELEMENT (element);
+
+  g_return_val_if_fail (element->parent == NULL, FALSE);
+  g_return_val_if_fail (ges_track_element_has_internal_source (el) == FALSE,
+      FALSE);
+
+  ges_track_element_set_has_internal_source_is_forbidden (el);
+
+  priv = effect->priv;
+  if (priv->destroy_translation_data)
+    priv->destroy_translation_data (priv->translation_data);
+
+  priv->translation_data = user_data;
+  priv->destroy_translation_data = destroy;
+  priv->source_to_sink = source_to_sink_func;
+  priv->sink_to_source = sink_to_source_func;
+
+  return TRUE;
+}
+
+/**
+ * ges_base_effect_is_time_effect:
+ * @effect: A #GESBaseEffect
+ *
+ * Get whether the effect is considered a time effect or not. An effect
+ * with registered time properties or set translation functions is
+ * considered a time effect.
+ *
+ * Returns: %TRUE if @effect is considered a time effect.
+ */
+gboolean
+ges_base_effect_is_time_effect (GESBaseEffect * effect)
+{
+  GESBaseEffectPrivate *priv;
+  g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE);
+
+  priv = effect->priv;
+  if (priv->time_properties || priv->source_to_sink || priv->sink_to_source)
+    return TRUE;
+  return FALSE;
+}
+
+gchar *
+ges_base_effect_get_time_property_name (GESBaseEffect * effect,
+    GObject * child, GParamSpec * pspec)
+{
+  GList *tmp;
+  for (tmp = effect->priv->time_properties; tmp; tmp = tmp->next) {
+    TimePropertyData *data = tmp->data;
+    if (data->pspec == pspec && data->child == child)
+      return g_strdup (data->property_name);
+  }
+  return NULL;
+}
+
+static void
+_gvalue_free (gpointer data)
+{
+  GValue *val = data;
+  g_value_unset (val);
+  g_free (val);
+}
+
+GHashTable *
+ges_base_effect_get_time_property_values (GESBaseEffect * effect)
+{
+  GList *tmp;
+  GHashTable *ret =
+      g_hash_table_new_full (g_str_hash, g_str_equal, g_free, _gvalue_free);
+
+  for (tmp = effect->priv->time_properties; tmp; tmp = tmp->next) {
+    TimePropertyData *data = tmp->data;
+    GValue *value = g_new0 (GValue, 1);
+
+    /* FIXME: once we move to GLib 2.60, g_object_get_property() will
+     * automatically initialize the type */
+    g_value_init (value, data->pspec->value_type);
+    g_object_get_property (data->child, data->pspec->name, value);
+
+    g_hash_table_insert (ret, g_strdup (data->property_name), value);
+  }
+
+  return ret;
+}
+
+GstClockTime
+ges_base_effect_translate_source_to_sink_time (GESBaseEffect * effect,
+    GstClockTime time, GHashTable * time_property_values)
+{
+  GESBaseEffectPrivate *priv = effect->priv;
+
+  if (!GST_CLOCK_TIME_IS_VALID (time))
+    return GST_CLOCK_TIME_NONE;
+
+  if (priv->source_to_sink)
+    return priv->source_to_sink (effect, time, time_property_values,
+        priv->translation_data);
+
+  if (time_property_values && g_hash_table_size (time_property_values))
+    GST_ERROR_OBJECT (effect, "The time effect is missing its source to "
+        "sink translation function");
+  return time;
+}
+
+GstClockTime
+ges_base_effect_translate_sink_to_source_time (GESBaseEffect * effect,
+    GstClockTime time, GHashTable * time_property_values)
+{
+  GESBaseEffectPrivate *priv = effect->priv;
+
+  if (!GST_CLOCK_TIME_IS_VALID (time))
+    return GST_CLOCK_TIME_NONE;
+
+  if (priv->sink_to_source)
+    return effect->priv->sink_to_source (effect, time, time_property_values,
+        priv->translation_data);
+
+  if (time_property_values && g_hash_table_size (time_property_values))
+    GST_ERROR_OBJECT (effect, "The time effect is missing its sink to "
+        "source translation function");
+  return time;
+}
index 6f3d6389ea0ba08c3439f2ef9634680d768750c7..71cf5da4a332aedeb5592aae6acd129b0f64da45 100644 (file)
@@ -50,9 +50,44 @@ struct _GESBaseEffectClass
 {
   /*< private > */
   GESOperationClass parent_class;
+
   /* Padding for API extension */
   gpointer _ges_reserved[GES_PADDING];
 
 };
 
+/**
+ * GESBaseEffectTimeTranslationFunc:
+ * @effect: The #GESBaseEffect that is doing the time translation
+ * @time: The #GstClockTime to translation
+ * @time_property_values: (element-type gchar* GValue*): A table of child
+ * property name/value pairs
+ * @user_data: Data passed to ges_base_effect_set_time_translation_funcs()
+ *
+ * A function for querying how an effect would translate a time if it had
+ * the given child property values set. The keys for @time_properties will
+ * be the same string that was passed to
+ * ges_base_effect_register_time_property(), the values will be #GValue*
+ * values of the corresponding child properties. You should always use the
+ * values given in @time_properties before using the currently set values.
+ *
+ * Returns: The translated time.
+ */
+typedef GstClockTime (*GESBaseEffectTimeTranslationFunc) (GESBaseEffect * effect,
+                                                          GstClockTime time,
+                                                          GHashTable * time_property_values,
+                                                          gpointer user_data);
+
+GES_API gboolean
+ges_base_effect_register_time_property     (GESBaseEffect * effect,
+                                            const gchar * child_property_name);
+GES_API gboolean
+ges_base_effect_set_time_translation_funcs (GESBaseEffect * effect,
+                                            GESBaseEffectTimeTranslationFunc source_to_sink_func,
+                                            GESBaseEffectTimeTranslationFunc sink_to_source_func,
+                                            gpointer user_data,
+                                            GDestroyNotify destroy);
+GES_API gboolean
+ges_base_effect_is_time_effect             (GESBaseEffect * effect);
+
 G_END_DECLS
index 9cd79f59acdfb03a8c4cfe50109b8e7902e1ea02..baf42b4466eb998d2b3388060ac346465c00c842 100644 (file)
@@ -224,6 +224,7 @@ typedef struct _DurationLimitData
   GstClockTime max_duration;
   GstClockTime inpoint;
   gboolean active;
+  GHashTable *time_property_values;
 } DurationLimitData;
 
 static DurationLimitData *
@@ -239,6 +240,10 @@ _duration_limit_data_new (GESTrackElement * child)
   data->priority = _PRIORITY (child);
   data->active = ges_track_element_is_active (child);
 
+  if (GES_IS_TIME_EFFECT (child))
+    data->time_property_values =
+        ges_base_effect_get_time_property_values (GES_BASE_EFFECT (child));
+
   return data;
 }
 
@@ -248,6 +253,8 @@ _duration_limit_data_free (gpointer data_p)
   DurationLimitData *data = data_p;
   gst_clear_object (&data->track);
   gst_clear_object (&data->child);
+  if (data->time_property_values)
+    g_hash_table_unref (data->time_property_values);
   g_free (data);
 }
 
@@ -288,9 +295,9 @@ _cmp_by_track_then_priority (gconstpointer a_p, gconstpointer b_p)
     return 1;
   /* if higher priority (numerically lower) place later */
   if (a->priority < b->priority)
-    return -1;
-  else if (a->priority > b->priority)
     return 1;
+  else if (a->priority > b->priority)
+    return -1;
   return 0;
 }
 
@@ -298,43 +305,129 @@ _cmp_by_track_then_priority (gconstpointer a_p, gconstpointer b_p)
   ((data->active && GST_CLOCK_TIME_IS_VALID (data->max_duration)) ? \
     data->max_duration - data->inpoint : GST_CLOCK_TIME_NONE)
 
+static GstClockTime
+_calculate_track_duration_limit (GESClip * self, GList * start, GList * end)
+{
+  GList *tmp;
+  DurationLimitData *data = start->data;
+  GstClockTime track_limit;
+
+  /* convert source-duration to timeline-duration
+   * E.g. consider the following stack
+   *
+   *       *=============================*
+   *       |           source            |
+   *       |        in-point = 5         |
+   *       |      max-duration = 20      |
+   *       *=============================*
+   *       5         10        15        20   (internal coordinates)
+   *
+   *  duration-limit = 15 because max-duration - in-point = 15
+   *
+   *       0         5         10        15
+   *       *=============================*
+   *       |         time-effect         |    | sink_to_source
+   *       |         rate = 0.5          |    v    / 0.5
+   *       *=============================*
+   *       0         10        20        30
+   *
+   *  duration-limit = 30 because rate effect can make it last longer
+   *
+   *       13        23        33    (internal coordinates)
+   *       *===================*
+   *       |effect-with-source |
+   *       |   in-point = 13   |
+   *       | max-duration = 33 |
+   *       *===================*
+   *       13        23        33    (internal coordinates)
+   *
+   *  duration-limit = 20 because effect-with-source cannot cover 30
+   *
+   *       0         10        20
+   *       *===================*
+   *       |    time-effect    |    | sink_to_source
+   *       |    rate = 2.0     |    v     / 2.0
+   *       *===================*
+   *       0         5         10
+   *
+   *  duration-limit = 10 because rate effect uses up twice as much
+   *
+   * -----------------------------------------------timeline
+   */
+
+  while (!_IS_CORE_CHILD (data->child)) {
+    GST_WARNING_OBJECT (self, "Child %" GES_FORMAT " has a lower "
+        "priority than the core child in the same track. Ignoring.",
+        GES_ARGS (data->child));
+
+    start = start->next;
+    if (start == end) {
+      GST_ERROR_OBJECT (self, "Track %" GST_PTR_FORMAT " is missing a "
+          "core child", data->track);
+      return GST_CLOCK_TIME_NONE;
+    }
+    data = start->data;
+  }
+
+  track_limit = _INTERNAL_LIMIT (data);
+
+  for (tmp = start->next; tmp != end; tmp = tmp->next) {
+    data = tmp->data;
+
+    if (GES_IS_TIME_EFFECT (data->child)) {
+      GESBaseEffect *effect = GES_BASE_EFFECT (data->child);
+      if (data->inpoint)
+        GST_ERROR_OBJECT (self, "Did not expect an in-point to be set "
+            "for the time effect %" GES_FORMAT, GES_ARGS (effect));
+      if (GST_CLOCK_TIME_IS_VALID (data->max_duration))
+        GST_ERROR_OBJECT (self, "Did not expect a max-duration to be set "
+            "for the time effect %" GES_FORMAT, GES_ARGS (effect));
+
+      if (data->active) {
+        /* for the time effect, the minimum time it will receive is 0
+         * (it should map 0 -> 0), and the maximum time will be track_limit */
+        track_limit = ges_base_effect_translate_sink_to_source_time (effect,
+            track_limit, data->time_property_values);
+      }
+    } else {
+      GstClockTime el_limit = _INTERNAL_LIMIT (data);
+      track_limit = _MIN_CLOCK_TIME (track_limit, el_limit);
+    }
+  }
+
+  GST_LOG_OBJECT (self, "Track duration-limit for track %" GST_PTR_FORMAT
+      " is %" GST_TIME_FORMAT, data->track, GST_TIME_ARGS (track_limit));
+
+  return track_limit;
+}
+
 /* transfer-full of child_data */
 static GstClockTime
 _calculate_duration_limit (GESClip * self, GList * child_data)
 {
   GstClockTime limit = GST_CLOCK_TIME_NONE;
-  GList *tmp;
+  GList *start, *end;
 
   child_data = g_list_sort (child_data, _cmp_by_track_then_priority);
 
-  tmp = child_data;
+  start = child_data;
 
-  while (tmp) {
+  while (start) {
     /* we have the first element in the track, of the lowest priority, and
      * work our way up from here */
-    DurationLimitData *data = tmp->data;
-    GESTrack *track = data->track;
-    if (track) {
-      GstClockTime track_limit = _INTERNAL_LIMIT (data);
+    GESTrack *track = ((DurationLimitData *) (start->data))->track;
 
-      for (tmp = tmp->next; tmp; tmp = tmp->next) {
-        data = tmp->data;
-        if (data->track != track)
-          break;
-        track_limit = _MIN_CLOCK_TIME (track_limit, _INTERNAL_LIMIT (data));
-      }
+    end = start;
+    do {
+      end = end->next;
+    } while (end && ((DurationLimitData *) (end->data))->track == track);
 
-      GST_LOG_OBJECT (self, "duration-limit for track %" GST_PTR_FORMAT
-          " is %" GST_TIME_FORMAT, track, GST_TIME_ARGS (track_limit));
+    if (track) {
+      GstClockTime track_limit =
+          _calculate_track_duration_limit (self, start, end);
       limit = _MIN_CLOCK_TIME (limit, track_limit);
-    } else {
-      /* children not in a track do not affect the duration-limit */
-      for (tmp = tmp->next; tmp; tmp = tmp->next) {
-        data = tmp->data;
-        if (data->track)
-          break;
-      }
     }
+    start = end;
   }
   GST_LOG_OBJECT (self, "calculated duration-limit for the clip is %"
       GST_TIME_FORMAT, GST_TIME_ARGS (limit));
@@ -969,6 +1062,58 @@ _child_property_changed_cb (GESTimelineElement * child, GParamSpec * pspec,
     _update_duration_limit (self);
 }
 
+/****************************************************
+ *                time properties                   *
+ ****************************************************/
+
+gboolean
+ges_clip_can_set_time_property_of_child (GESClip * clip,
+    GESTrackElement * child, GObject * child_prop_object, GParamSpec * pspec,
+    const GValue * value, GError ** error)
+{
+  if (_IS_TOP_EFFECT (child)) {
+    gchar *prop_name =
+        ges_base_effect_get_time_property_name (GES_BASE_EFFECT (child),
+        child_prop_object, pspec);
+
+    if (prop_name) {
+      GList *child_data;
+      DurationLimitData *data = _duration_limit_data_new (child);
+      GValue *copy = g_new0 (GValue, 1);
+
+      g_value_init (copy, pspec->value_type);
+      g_value_copy (value, copy);
+
+      g_hash_table_insert (data->time_property_values, prop_name, copy);
+
+      child_data = _duration_limit_data_list_with_data (clip, data);
+
+      if (!_can_update_duration_limit (clip, child_data, error)) {
+        gchar *val_str = gst_value_serialize (value);
+        GST_INFO_OBJECT (clip, "Cannot set the child-property %s of "
+            "child %" GES_FORMAT " to %s because the duration-limit "
+            "cannot be adjusted", prop_name, GES_ARGS (child), val_str);
+        g_free (val_str);
+        return FALSE;
+      }
+    }
+  }
+  return TRUE;
+}
+
+static void
+_child_time_property_changed_cb (GESTimelineElement * child,
+    GObject * prop_object, GParamSpec * pspec, GESClip * self)
+{
+  gchar *time_prop =
+      ges_base_effect_get_time_property_name (GES_BASE_EFFECT (child),
+      prop_object, pspec);
+  if (time_prop) {
+    g_free (time_prop);
+    _update_duration_limit (self);
+  }
+}
+
 /*****************************************************
  *                                                   *
  * GESTimelineElement virtual methods implementation *
@@ -1551,6 +1696,10 @@ _child_added (GESContainer * container, GESTimelineElement * element)
   g_signal_connect (element, "notify", G_CALLBACK (_child_property_changed_cb),
       self);
 
+  if (GES_IS_TIME_EFFECT (element))
+    g_signal_connect (element, "deep-notify",
+        G_CALLBACK (_child_time_property_changed_cb), self);
+
   if (_IS_CORE_CHILD (element))
     _update_max_duration (container);
 
@@ -1564,6 +1713,10 @@ _child_removed (GESContainer * container, GESTimelineElement * element)
 
   g_signal_handlers_disconnect_by_func (element, _child_property_changed_cb,
       self);
+  /* NOTE: we do not test if the effect is a time effect since technically
+   * it can stop being a time effect, although this would be rare */
+  g_signal_handlers_disconnect_by_func (element,
+      _child_time_property_changed_cb, self);
 
   if (_IS_CORE_CHILD (element))
     _update_max_duration (container);
@@ -2124,9 +2277,10 @@ ges_clip_class_init (GESClipClass * klass)
    *
    * The maximum #GESTimelineElement:duration that can be *currently* set
    * for the clip, taking into account the #GESTimelineElement:in-point,
-   * #GESTimelineElement:max-duration, GESTrackElement:active, and
-   * #GESTrackElement:track properties of its children. If there is no
-   * limit, this will be set to #GST_CLOCK_TIME_NONE.
+   * #GESTimelineElement:max-duration, #GESTrackElement:active, and
+   * #GESTrackElement:track properties of its children, as well as any
+   * time effects. If there is no limit, this will be set to
+   * #GST_CLOCK_TIME_NONE.
    *
    * Note that whilst a clip has no children in any tracks, the limit will
    * be unknown, and similarly set to #GST_CLOCK_TIME_NONE.
index 4de3aaf10b1337b240efdcaca22cfe11abe27a1a..7bba621f3a1e4abd7f953f9d48609c26d78f8c50 100644 (file)
@@ -252,14 +252,83 @@ ghost_compatible_pads (GstElement * bin, GstElement * child,
   }
 }
 
+static gdouble
+_get_rate_factor (GESBaseEffect * effect, GHashTable * rate_values)
+{
+  GHashTableIter iter;
+  gpointer key, val;
+  gdouble factor = 1.0;
+
+  g_hash_table_iter_init (&iter, rate_values);
+  while (g_hash_table_iter_next (&iter, &key, &val)) {
+    GValue *value = val;
+    gchar *prop_name = key;
+    gdouble rate = 1.0;
+
+    switch (G_VALUE_TYPE (value)) {
+      case G_TYPE_DOUBLE:
+        rate = g_value_get_double (value);
+        break;
+      case G_TYPE_FLOAT:
+        rate = g_value_get_float (value);
+        break;
+      default:
+        GST_ERROR_OBJECT (effect, "Rate property %s has neither a gdouble "
+            "nor gfloat value", prop_name);
+        break;
+    }
+    factor *= rate;
+  }
+
+  return factor;
+}
+
+static GstClockTime
+_rate_source_to_sink (GESBaseEffect * effect, GstClockTime time,
+    GHashTable * rate_values, gpointer user_data)
+{
+  /* multiply by rate factor
+   * E.g. rate=2.0, then the time 30 at the source would become
+   * 60 at the sink because we are using up twice as much data in a given
+   * time */
+  gdouble rate_factor = _get_rate_factor (effect, rate_values);
+
+  if (time == 0)
+    return 0;
+  if (rate_factor == 0.0) {
+    GST_ERROR_OBJECT (effect, "The rate effect has a rate of 0");
+    return 0;
+  }
+  return (GstClockTime) (time * rate_factor);
+}
+
+static GstClockTime
+_rate_sink_to_source (GESBaseEffect * effect, GstClockTime time,
+    GHashTable * rate_values, gpointer user_data)
+{
+  /* divide by rate factor */
+  gdouble rate_factor = _get_rate_factor (effect, rate_values);
+
+  if (time == 0)
+    return 0;
+  if (rate_factor == 0.0) {
+    GST_ERROR_OBJECT (effect, "The rate effect has a rate of 0");
+    return GST_CLOCK_TIME_NONE;
+  }
+  return (GstClockTime) (time / rate_factor);
+}
+
 static GstElement *
 ges_effect_create_element (GESTrackElement * object)
 {
+  GESBaseEffect *base_effect = GES_BASE_EFFECT (object);
+  GESEffectClass *class;
   GList *tmp;
   GstElement *effect;
   gchar *bin_desc;
   GstCaps *valid_caps;
   gint n_src = 0, n_sink = 0;
+  gboolean is_rate_effect = FALSE;
 
   GError *error = NULL;
   GESEffect *self = GES_EFFECT (object);
@@ -308,6 +377,22 @@ ges_effect_create_element (GESTrackElement * object)
   ges_track_element_add_children_props (object, effect, NULL,
       blacklisted_factories, NULL);
 
+  class = GES_EFFECT_CLASS (g_type_class_peek (GES_TYPE_EFFECT));
+
+  for (tmp = class->rate_properties; tmp; tmp = tmp->next) {
+    gchar *prop = tmp->data;
+    if (ges_timeline_element_lookup_child (GES_TIMELINE_ELEMENT (object), prop,
+            NULL, NULL)) {
+      if (!ges_base_effect_register_time_property (base_effect, prop))
+        GST_ERROR_OBJECT (object, "Failed to register rate property %s", prop);
+      is_rate_effect = TRUE;
+    }
+  }
+  if (is_rate_effect
+      && !ges_base_effect_set_time_translation_funcs (base_effect,
+          _rate_source_to_sink, _rate_sink_to_source, NULL, NULL))
+    GST_ERROR_OBJECT (object, "Failed to set rate translation functions");
+
 done:
   gst_clear_caps (&valid_caps);
 
@@ -352,22 +437,38 @@ ges_effect_new (const gchar * bin_description)
 /**
  * ges_effect_class_register_rate_property:
  * @klass: Instance of the GESEffectClass
- * @element_name: Name of the GstElement that changes the rate
- * @property_name: Name of the property that changes the rate
+ * @element_name: The #GstElementFactory name of the element that changes
+ * the rate
+ * @property_name: The name of the property that changes the rate
+ *
+ * Register an element that can change the rate at which media is playing.
+ * The property type must be float or double, and must be a factor of the
+ * rate, i.e. a value of 2.0 must mean that the media plays twice as fast.
+ * Several properties may be registered for a single element type,
+ * provided they all contribute to the rate as independent factors. For
+ * example, this is true for the "GstPitch::rate" and "GstPitch::tempo"
+ * properties. These are already registered by default in GES, along with
+ * #videorate:rate for #videorate and #scaletempo:rate for #scaletempo.
+ *
+ * If such a rate property becomes a child property of a #GESEffect upon
+ * its creation (the element is part of its #GESEffect:bin-description),
+ * it will be automatically registered as a time property (see
+ * ges_base_effect_register_time_property()) and will have its time
+ * translation functions set (see
+ * ges_base_effect_set_time_translation_funcs()) to use the overall rate
+ * of the rate properties. Note that if an effect contains a rate
+ * property as well as a non-rate time property, you should ensure to set
+ * the time translation functions to some other methods using
+ * ges_base_effect_set_time_translation_funcs().
  *
- * Register an element that can change the rate at which media is playing. The
- * property type must be float or double, and must be a factor of the rate,
- * i.e. a value of 2.0 must mean that the media plays twice as fast. For
- * example, this is true for the properties 'rate' and 'tempo' of the element
- * 'pitch', which is already registered by default. By registering the element,
- * timeline duration can be correctly converted into media duration, allowing
- * the right segment seeks to be sent to the sources.
+ * Note, you can obtain a reference to the GESEffectClass using
  *
- * A reference to the GESEffectClass can be obtained as follows:
+ * ```
  *   GES_EFFECT_CLASS (g_type_class_ref (GES_TYPE_EFFECT));
+ * ```
  *
- * Returns: whether the rate property was succesfully registered. When this
- * method returns false, a warning is emitted with more information.
+ * Returns: %TRUE if the rate property was successfully registered. When
+ * this method returns %FALSE, a warning is emitted with more information.
  */
 gboolean
 ges_effect_class_register_rate_property (GESEffectClass * klass,
index 76eb2606455d02156290158fe71a0dbef2db970c..84911ff5d08d3b6a79ecb119448f3a4096d3dab9 100644 (file)
@@ -87,6 +87,10 @@ GstDebugCategory * _ges_debug (void);
 #define GES_FORMAT GES_TIMELINE_ELEMENT_FORMAT
 #define GES_ARGS GES_TIMELINE_ELEMENT_ARGS
 
+#define GES_IS_TIME_EFFECT(element) \
+  (GES_IS_BASE_EFFECT (element) \
+  && ges_base_effect_is_time_effect (GES_BASE_EFFECT (element)))
+
 #define GES_TIMELINE_ELEMENT_SET_BEING_EDITED(element) \
   ELEMENT_SET_FLAG ( \
       ges_timeline_element_peak_toplevel (GES_TIMELINE_ELEMENT (element)), \
@@ -412,6 +416,7 @@ G_GNUC_INTERNAL gboolean          ges_clip_can_set_max_duration_of_child (GESCli
 G_GNUC_INTERNAL gboolean          ges_clip_can_set_active_of_child (GESClip * clip, GESTrackElement * child, gboolean active, GError ** error);
 G_GNUC_INTERNAL gboolean          ges_clip_can_set_priority_of_child (GESClip * clip, GESTrackElement * child, guint32 priority, GError ** error);
 G_GNUC_INTERNAL gboolean          ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, GESTrack * tack, GError ** error);
+G_GNUC_INTERNAL gboolean          ges_clip_can_set_time_property_of_child (GESClip * clip, GESTrackElement * child, GObject * prop_object, GParamSpec * pspec, const GValue * value, GError ** error);
 G_GNUC_INTERNAL void              ges_clip_empty_from_track       (GESClip * clip, GESTrack * track);
 
 /****************************************************
@@ -439,6 +444,9 @@ ges_track_element_set_creator_asset                    (GESTrackElement * self,
 G_GNUC_INTERNAL GESAsset *
 ges_track_element_get_creator_asset                    (GESTrackElement * self);
 
+G_GNUC_INTERNAL void
+ges_track_element_set_has_internal_source_is_forbidden (GESTrackElement * element);
+
 G_GNUC_INTERNAL GstElement* ges_source_create_topbin(const gchar* bin_name, GstElement* sub_element, GPtrArray* elements);
 G_GNUC_INTERNAL void ges_track_set_caps(GESTrack* track,
     const GstCaps* caps);
@@ -455,6 +463,24 @@ G_GNUC_INTERNAL GESImageSource     * ges_image_source_new      (gchar *uri);
 G_GNUC_INTERNAL GESTitleSource     * ges_title_source_new      (void);
 G_GNUC_INTERNAL GESVideoTestSource * ges_video_test_source_new (void);
 
+/****************************************************
+ *                GESBaseEffect                     *
+ ****************************************************/
+G_GNUC_INTERNAL gchar *
+ges_base_effect_get_time_property_name        (GESBaseEffect * effect,
+                                               GObject * child,
+                                               GParamSpec * pspec);
+G_GNUC_INTERNAL GHashTable *
+ges_base_effect_get_time_property_values      (GESBaseEffect * effect);
+G_GNUC_INTERNAL GstClockTime
+ges_base_effect_translate_source_to_sink_time (GESBaseEffect * effect,
+                                               GstClockTime time,
+                                               GHashTable * time_property_values);
+G_GNUC_INTERNAL GstClockTime
+ges_base_effect_translate_sink_to_source_time (GESBaseEffect * effect,
+                                               GstClockTime time,
+                                               GHashTable * time_property_values);
+
 /****************************************************
  *              GESTimelineElement                  *
  ****************************************************/
index 491564f8faafe78ce610a8939c6e9948699dbc05..13053552c691df6503ddc3cdfcdf953232448d48 100644 (file)
@@ -174,6 +174,15 @@ _set_child_property (GESTimelineElement * self G_GNUC_UNUSED, GObject * child,
     g_object_set_property (child, pspec->name, value);
 }
 
+static gboolean
+_set_child_property_full (GESTimelineElement * self, GObject * child,
+    GParamSpec * pspec, const GValue * value, GError ** error)
+{
+  GES_TIMELINE_ELEMENT_GET_CLASS (self)->set_child_property (self, child,
+      pspec, (GValue *) value);
+  return TRUE;
+}
+
 static gboolean
 _lookup_child (GESTimelineElement * self, const gchar * prop_name,
     GObject ** child, GParamSpec ** pspec)
@@ -549,7 +558,7 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass)
 
   /**
    * GESTimelineElement::child-property-removed:
-   * @timeline_element: A #GESTtimelineElement
+   * @timeline_element: A #GESTimelineElement
    * @prop_object: The child whose property has been unregistered
    * @prop: The specification for the property that has been unregistered
    *
@@ -582,6 +591,7 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass)
       ges_timeline_element_get_children_properties;
   klass->lookup_child = _lookup_child;
   klass->set_child_property = _set_child_property;
+  klass->set_child_property_full = _set_child_property_full;
   klass->get_natural_framerate = _get_natural_framerate;
 }
 
@@ -762,8 +772,8 @@ emit_deep_notify_in_idle (EmitDeepNotifyInIdleData * data)
 }
 
 static void
-child_prop_changed_cb (GObject * child, GParamSpec * arg
-    G_GNUC_UNUSED, GESTimelineElement * self)
+child_prop_changed_cb (GObject * child, GParamSpec * arg,
+    GESTimelineElement * self)
 {
   EmitDeepNotifyInIdleData *data;
 
@@ -786,7 +796,7 @@ child_prop_changed_cb (GObject * child, GParamSpec * arg
 
 static gboolean
 set_child_property_by_pspec (GESTimelineElement * self,
-    GParamSpec * pspec, const GValue * value)
+    GParamSpec * pspec, const GValue * value, GError ** error)
 {
   GESTimelineElementClass *klass;
   GESTimelineElement *setter = self;
@@ -805,6 +815,10 @@ set_child_property_by_pspec (GESTimelineElement * self,
     klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
   }
 
+  if (klass->set_child_property_full)
+    return klass->set_child_property_full (setter, handler->child, pspec,
+        value, error);
+
   g_assert (klass->set_child_property);
   klass->set_child_property (setter, handler->child, pspec, (GValue *) value);
 
@@ -1816,14 +1830,15 @@ ges_timeline_element_set_child_property_by_pspec (GESTimelineElement * self,
   g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));
   g_return_if_fail (G_IS_PARAM_SPEC (pspec));
 
-  set_child_property_by_pspec (self, pspec, value);
+  set_child_property_by_pspec (self, pspec, value, NULL);
 }
 
 /**
- * ges_timeline_element_set_child_property:
+ * ges_timeline_element_set_child_property_full:
  * @self: A #GESTimelineElement
  * @property_name: The name of the child property to set
  * @value: The value to set the property to
+ * @error: (nullable): Return location for an error
  *
  * Sets the property of a child of the element.
  *
@@ -1840,24 +1855,22 @@ ges_timeline_element_set_child_property_by_pspec (GESTimelineElement * self,
  * property set to @value. Other children that may have also matched the
  * property name (and type name) are left unchanged!
  *
- * Note that ges_timeline_element_set_child_properties() may be more
- * convenient for C programming.
- *
  * Returns: %TRUE if the property was found and set.
  */
 gboolean
-ges_timeline_element_set_child_property (GESTimelineElement * self,
-    const gchar * property_name, const GValue * value)
+ges_timeline_element_set_child_property_full (GESTimelineElement * self,
+    const gchar * property_name, const GValue * value, GError ** error)
 {
   GParamSpec *pspec;
   GObject *child;
 
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
+  g_return_val_if_fail (!error || !*error, FALSE);
 
   if (!ges_timeline_element_lookup_child (self, property_name, &child, &pspec))
     goto not_found;
 
-  return set_child_property_by_pspec (self, pspec, value);
+  return set_child_property_by_pspec (self, pspec, value, error);
 
 not_found:
   {
@@ -1867,6 +1880,28 @@ not_found:
   }
 }
 
+/**
+ * ges_timeline_element_set_child_property:
+ * @self: A #GESTimelineElement
+ * @property_name: The name of the child property to set
+ * @value: The value to set the property to
+ *
+ * See ges_timeline_element_set_child_property_full(), which also gives an
+ * error.
+ *
+ * Note that ges_timeline_element_set_child_properties() may be more
+ * convenient for C programming.
+ *
+ * Returns: %TRUE if the property was found and set.
+ */
+gboolean
+ges_timeline_element_set_child_property (GESTimelineElement * self,
+    const gchar * property_name, const GValue * value)
+{
+  return ges_timeline_element_set_child_property_full (self, property_name,
+      value, NULL);
+}
+
 /**
  * ges_timeline_element_get_child_property:
  * @self: A #GESTimelineElement
@@ -2001,7 +2036,7 @@ ges_timeline_element_set_child_property_valist (GESTimelineElement * self,
     if (error)
       goto cant_copy;
 
-    set_child_property_by_pspec (self, pspec, &value);
+    set_child_property_by_pspec (self, pspec, &value, NULL);
 
     g_param_spec_unref (pspec);
     g_value_unset (&value);
index e2b9abb250d6b294d3da726ac00714e4d1f4d16f..a812abb079e2f9d361754a37faf567e64f8c2d87 100644 (file)
@@ -217,6 +217,9 @@ struct _GESTimelineElement
  * @set_child_property: Method for setting the child property given by
  * @pspec on @child to @value. Default implementation will use
  * g_object_set_property().
+ * @set_child_property_full: Similar to @set_child_property, except
+ * setting can fail, with the @error being optionally set. Default
+ * implementation will call @set_child_property and return %TRUE.
  * @get_layer_priority: Get the #GESLayer:priority of the layer that this
  * element is part of.
  * @list_children_properties: List the children properties that have been
@@ -265,15 +268,23 @@ struct _GESTimelineElementClass
   gboolean (*lookup_child)                 (GESTimelineElement *self, const gchar *prop_name,
                                             GObject **child, GParamSpec **pspec);
   GESTrackType (*get_track_types)          (GESTimelineElement * self);
-  void         (*set_child_property)       (GESTimelineElement * self, GObject *child,
-                                            GParamSpec *pspec, GValue *value);
+  void         (*set_child_property)       (GESTimelineElement * self,
+                                            GObject *child,
+                                            GParamSpec *pspec,
+                                            GValue *value);
 
   guint32      (*get_layer_priority)       (GESTimelineElement *self);
 
   /*< private > */
   gboolean (*get_natural_framerate) (GESTimelineElement * self, gint *framerate_n, gint *framerate_d);
+
+  gboolean     (*set_child_property_full)  (GESTimelineElement * self,
+                                            GObject *child,
+                                            GParamSpec *pspec,
+                                            const GValue *value,
+                                            GError ** error);
   /* Padding for API extension */
-  gpointer _ges_reserved[GES_PADDING_LARGE - 5];
+  gpointer _ges_reserved[GES_PADDING_LARGE - 6];
 };
 
 GES_API
@@ -371,6 +382,11 @@ gboolean             ges_timeline_element_set_child_property          (GESTimeli
                                                                        const gchar *property_name,
                                                                        const GValue * value);
 GES_API
+gboolean             ges_timeline_element_set_child_property_full     (GESTimelineElement *self,
+                                                                       const gchar *property_name,
+                                                                       const GValue * value,
+                                                                       GError ** error);
+GES_API
 gboolean             ges_timeline_element_get_child_property          (GESTimelineElement *self,
                                                                        const gchar *property_name,
                                                                        GValue * value);
index 5461432f03924d4eae72bc68cb84e233ff3d8ef6..ac4f2386d65a35ad4e8d4919daef4725f27304ec 100644 (file)
@@ -59,6 +59,7 @@ struct _GESTrackElementPrivate
   GstElement *element;          /* The element contained in the nleobject (can be NULL) */
 
   GESTrack *track;
+  gboolean has_internal_source_forbidden;
   gboolean has_internal_source;
 
   gboolean locked;              /* If TRUE, then moves in sync with its controlling
@@ -818,6 +819,12 @@ ges_track_element_set_has_internal_source (GESTrackElement * object,
   if (G_UNLIKELY (has_internal_source == object->priv->has_internal_source))
     return;
 
+  if (has_internal_source && object->priv->has_internal_source_forbidden) {
+    GST_WARNING_OBJECT (object, "Setting an internal source for this "
+        "element is forbidden");
+    return;
+  }
+
   object->priv->has_internal_source = has_internal_source;
 
   if (!has_internal_source) {
@@ -830,6 +837,13 @@ ges_track_element_set_has_internal_source (GESTrackElement * object,
       properties[PROP_HAS_INTERNAL_SOURCE]);
 }
 
+void
+ges_track_element_set_has_internal_source_is_forbidden (GESTrackElement *
+    element)
+{
+  element->priv->has_internal_source_forbidden = TRUE;
+}
+
 /**
  * ges_track_element_set_track_type:
  * @object: A #GESTrackElement
@@ -1124,7 +1138,7 @@ ges_track_element_set_layer_active (GESTrackElement * element, gboolean active)
  * ges_track_element_set_control_source(), and their values are the
  * corresponding created #GstControlBinding.
  *
- * Returns: (element-type gchar* GstControlBinding)(transfer none): A
+ * Returns: (element-type gchar* GstControlBinding*)(transfer none): A
  * hash table containing all child-property-name/control-binding pairs
  * for @trackelement.
  */
index e93f36d12d6841eeb2e988b9cb0426dbbf455d6f..f12906f76d18caa8edcb0fde789394b0623388b8 100644 (file)
@@ -3621,6 +3621,582 @@ GST_START_TEST (test_can_set_duration_limit)
 
 GST_END_TEST;
 
+#define _assert_set_rate(element, prop_name, rate, val) \
+{ \
+  GError *error = NULL; \
+  if (G_VALUE_TYPE (&val) == G_TYPE_DOUBLE) \
+    g_value_set_double (&val, rate); \
+  else if (G_VALUE_TYPE (&val) == G_TYPE_FLOAT) \
+    g_value_set_float (&val, rate); \
+  \
+  fail_unless (ges_timeline_element_set_child_property_full ( \
+        GES_TIMELINE_ELEMENT (element), prop_name, &val, &error)); \
+  fail_if (error); \
+  g_value_reset (&val); \
+}
+
+#define _assert_fail_set_rate(element, prop_name, rate, val, code) \
+{ \
+  GError * error = NULL; \
+  if (G_VALUE_TYPE (&val) == G_TYPE_DOUBLE) \
+    g_value_set_double (&val, rate); \
+  else if (G_VALUE_TYPE (&val) == G_TYPE_FLOAT) \
+    g_value_set_float (&val, rate); \
+  \
+  fail_if (ges_timeline_element_set_child_property_full ( \
+        GES_TIMELINE_ELEMENT (element), prop_name, &val, &error)); \
+  assert_GESError (error, code); \
+  g_value_reset (&val); \
+}
+
+#define _assert_rate_equal(element, prop_name, rate, val) \
+{ \
+  gdouble found = -1.0; \
+  fail_unless (ges_timeline_element_get_child_property ( \
+        GES_TIMELINE_ELEMENT (element), prop_name, &val)); \
+  \
+  if (G_VALUE_TYPE (&val) == G_TYPE_DOUBLE) \
+    found = g_value_get_double (&val); \
+  else if (G_VALUE_TYPE (&val) == G_TYPE_FLOAT) \
+    found = g_value_get_float (&val); \
+  \
+  fail_unless (found == rate, "found %s: %g != expected: %g", found, \
+      prop_name, rate); \
+  g_value_reset (&val); \
+}
+
+GST_START_TEST (test_rate_effects_duration_limit)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESClip *clip;
+  GESTrackElement *source0, *source1;
+  GESTrackElement *overlay0, *overlay1, *videorate, *pitch;
+  GESTrack *track0, *track1;
+  gint limit_notify_count = 0;
+  GValue fval = G_VALUE_INIT;
+  GValue dval = G_VALUE_INIT;
+
+  ges_init ();
+
+  g_value_init (&fval, G_TYPE_FLOAT);
+  g_value_init (&dval, G_TYPE_DOUBLE);
+
+  timeline = ges_timeline_new ();
+  track0 = GES_TRACK (ges_video_track_new ());
+  track1 = GES_TRACK (ges_audio_track_new ());
+
+  fail_unless (ges_timeline_add_track (timeline, track0));
+  fail_unless (ges_timeline_add_track (timeline, track1));
+
+  layer = ges_timeline_append_layer (timeline);
+
+  /* place a dummy clip at the start of the layer */
+  clip = GES_CLIP (ges_test_clip_new ());
+  assert_set_start (clip, 0);
+  assert_set_duration (clip, 26);
+
+  fail_unless (ges_layer_add_clip (layer, clip));
+
+  /* the clip we will be editing overlaps first clip by 16 at its start */
+  clip = GES_CLIP (ges_test_clip_new ());
+
+  g_signal_connect (clip, "notify::duration-limit", G_CALLBACK (_count_cb),
+      &limit_notify_count);
+
+  assert_set_start (clip, 10);
+  assert_set_duration (clip, 64);
+
+  fail_unless (ges_layer_add_clip (layer, clip));
+
+  source0 =
+      ges_clip_find_track_element (clip, track0, GES_TYPE_VIDEO_TEST_SOURCE);
+  source1 =
+      ges_clip_find_track_element (clip, track1, GES_TYPE_AUDIO_TEST_SOURCE);
+
+  fail_unless (source0);
+  fail_unless (source1);
+
+  gst_object_unref (source0);
+  gst_object_unref (source1);
+
+  assert_equals_int (limit_notify_count, 0);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 0, 64, GST_CLOCK_TIME_NONE);
+
+  assert_set_inpoint (clip, 13);
+
+  assert_equals_int (limit_notify_count, 0);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, GST_CLOCK_TIME_NONE);
+
+  assert_set_max_duration (clip, 77);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+
+  /* add effects */
+  overlay0 = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay"));
+  ges_track_element_set_has_internal_source (overlay0, TRUE);
+
+  videorate = GES_TRACK_ELEMENT (ges_effect_new ("videorate"));
+  fail_unless (ges_base_effect_is_time_effect (GES_BASE_EFFECT (videorate)));
+
+  overlay1 = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay"));
+  ges_track_element_set_has_internal_source (overlay1, TRUE);
+
+  pitch = GES_TRACK_ELEMENT (ges_effect_new ("pitch"));
+  fail_unless (ges_base_effect_is_time_effect (GES_BASE_EFFECT (pitch)));
+
+  /* add overlay1 at highest priority */
+  _assert_add (clip, overlay1);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE);
+
+  _assert_set_rate (videorate, "rate", 4.0, dval);
+  _assert_rate_equal (videorate, "rate", 4.0, dval);
+  fail_unless (ges_track_add_element (track0, videorate));
+
+  /* cannot add videorate as it would cause the duration-limit to drop
+   * to 16, causing a full overlap */
+  /* track keeps alive */
+  fail_if (ges_container_add (GES_CONTAINER (clip),
+          GES_TIMELINE_ELEMENT (videorate)));
+
+  /* setting to 1.0 makes it work again */
+  _assert_set_rate (videorate, "rate", 1.0, dval);
+  _assert_add (clip, videorate);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+
+  /* add second overlay at lower priority */
+  _assert_add (clip, overlay0);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+
+  /* also add a pitch element in another track */
+  _assert_add (clip, pitch);
+  _assert_set_rate (pitch, "rate", 1.0, fval);
+  _assert_set_rate (pitch, "tempo", 1.0, fval);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+  _assert_rate_equal (pitch, "rate", 1.0, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  fail_unless (ges_track_element_get_track (overlay0) == track0);
+  fail_unless (ges_track_element_get_track (videorate) == track0);
+  fail_unless (ges_track_element_get_track (overlay1) == track0);
+  fail_unless (ges_track_element_get_track (pitch) == track1);
+
+  /* flow in track0:
+   * source0 -> overlay0 -> videorate -> overlay1 -> timeline output
+   *
+   * flow in track1:
+   * source1 -> pitch -> timeline output
+   */
+
+  /* cannot set the rates to 4.0 since this would cause a full overlap */
+  _assert_fail_set_rate (videorate, "rate", 4.0, dval,
+      GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+  _assert_fail_set_rate (pitch, "rate", 4.0, fval,
+      GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+  _assert_fail_set_rate (pitch, "tempo", 4.0, fval,
+      GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+  _assert_rate_equal (pitch, "rate", 1.0, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* limit overlay0 */
+  assert_set_max_duration (overlay0, 91);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+  _assert_rate_equal (pitch, "rate", 1.0, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  assert_set_inpoint (overlay0, 59);
+
+  assert_equals_int (limit_notify_count, 2);
+  _assert_duration_limit (clip, 32);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+  _assert_rate_equal (pitch, "rate", 1.0, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* can set pitch rate to 2.0, but not videorate rate because videorate
+   * shares a track with overlay0 */
+
+  _assert_set_rate (pitch, "rate", 2.0, fval);
+  assert_equals_int (limit_notify_count, 2);
+  _assert_fail_set_rate (videorate, "rate", 2.0, dval,
+      GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+  assert_equals_int (limit_notify_count, 2);
+  /* can't set tempo to 2.0 since overall effect would bring duration
+   * limit too low */
+  _assert_fail_set_rate (pitch, "tempo", 2.0, fval,
+      GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+
+  assert_equals_int (limit_notify_count, 2);
+  _assert_duration_limit (clip, 32);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+  _assert_rate_equal (pitch, "rate", 2.0, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* cannot set in-point of clip because pitch would cause limit to go
+   * to 16 */
+  assert_fail_set_inpoint (clip, 45);
+  /* same for max-duration of source1 */
+  assert_fail_set_max_duration (source1, 45);
+
+  assert_equals_int (limit_notify_count, 2);
+  _assert_duration_limit (clip, 32);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+  _assert_rate_equal (pitch, "rate", 2.0, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* can set rate to 0.5 */
+  _assert_set_rate (videorate, "rate", 0.5, dval);
+
+  /* no change yet, since pitch rate is still 2.0 */
+  assert_equals_int (limit_notify_count, 2);
+  _assert_duration_limit (clip, 32);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 2.0, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  _assert_set_rate (pitch, "rate", 0.5, fval);
+
+  assert_equals_int (limit_notify_count, 3);
+  /* duration-limit is 64 because overlay0 only has 32 nanoseconds of
+   * content, stretched to 64 by videorate */
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* setting the max-duration of the sources does not change the limit
+   * since the limit on overlay0 is fine.
+   * Note that pitch handles the unlimited duration (GST_CLOCK_TIME_NONE)
+   * without any problems */
+  assert_set_max_duration (clip, GST_CLOCK_TIME_NONE);
+
+  assert_equals_int (limit_notify_count, 3);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  assert_set_max_duration (clip, 77);
+
+  assert_equals_int (limit_notify_count, 3);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* limit overlay1. It should not be changes by the videorate element
+   * since it acts at a lower priority
+   * first make it last longer, so no change in duration-limit */
+
+  assert_set_max_duration (overlay1, 81);
+
+  assert_equals_int (limit_notify_count, 3);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, 81);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* now make it shorter */
+  assert_set_inpoint (overlay1, 51);
+
+  assert_equals_int (limit_notify_count, 4);
+  _assert_duration_limit (clip, 30);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 30, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 30, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 30, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 30, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 30, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 30, 81);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 30, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* remove the overlay0 limit */
+  assert_set_max_duration (overlay0, GST_CLOCK_TIME_NONE);
+
+  /* no change because of overlay1 */
+  assert_equals_int (limit_notify_count, 4);
+  _assert_duration_limit (clip, 30);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 30, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 30, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 30, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 30, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 30, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 30, 81);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 30, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  assert_set_max_duration (overlay1, GST_CLOCK_TIME_NONE);
+
+  assert_equals_int (limit_notify_count, 5);
+  _assert_duration_limit (clip, 128);
+  /* can set up to the limit */
+  assert_set_duration (clip, 128);
+
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 128, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 128, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 128, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 128, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 128, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 128, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 128, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* tempo contributes the same factor as rate */
+
+  _assert_set_rate (pitch, "tempo", 2.0, fval);
+
+  assert_equals_int (limit_notify_count, 6);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 2.0, fval);
+
+  _assert_set_rate (videorate, "rate", 0.1, dval);
+  assert_equals_int (limit_notify_count, 6);
+  _assert_set_rate (pitch, "tempo", 0.5, dval);
+
+  assert_equals_int (limit_notify_count, 7);
+  _assert_duration_limit (clip, 256);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.1, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 0.5, fval);
+
+  _assert_set_rate (pitch, "tempo", 1.0, dval);
+  assert_equals_int (limit_notify_count, 8);
+  _assert_set_rate (videorate, "rate", 0.5, dval);
+
+  assert_equals_int (limit_notify_count, 8);
+  _assert_duration_limit (clip, 128);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* make videorate in-active */
+  fail_unless (ges_track_element_set_active (videorate, FALSE));
+
+  assert_equals_int (limit_notify_count, 9);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  fail_unless (ges_track_element_set_active (videorate, TRUE));
+
+  assert_equals_int (limit_notify_count, 10);
+  _assert_duration_limit (clip, 128);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* removing pitch, same effect as making inactive */
+  _assert_remove (clip, pitch);
+
+  assert_equals_int (limit_notify_count, 11);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+
+  /* no max-duration will give unlimited limit */
+  assert_set_max_duration (source1, GST_CLOCK_TIME_NONE);
+
+  assert_equals_int (limit_notify_count, 12);
+  _assert_duration_limit (clip, 128);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+
+  assert_set_max_duration (source0, GST_CLOCK_TIME_NONE);
+
+  assert_equals_int (limit_notify_count, 13);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+
+  gst_object_unref (timeline);
+
+  g_value_unset (&fval);
+  g_value_unset (&dval);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+
 GST_START_TEST (test_children_properties_contain)
 {
   GESTimeline *timeline;
@@ -4232,6 +4808,7 @@ ges_suite (void)
   tcase_add_test (tc_chain, test_children_max_duration);
   tcase_add_test (tc_chain, test_duration_limit);
   tcase_add_test (tc_chain, test_can_set_duration_limit);
+  tcase_add_test (tc_chain, test_rate_effects_duration_limit);
   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);