clip: add the duration-limit property
authorHenry Wilkes <hwilkes@igalia.com>
Mon, 13 Apr 2020 16:42:22 +0000 (17:42 +0100)
committerHenry Wilkes <hwilkes@igalia.com>
Thu, 7 May 2020 08:37:15 +0000 (09:37 +0100)
The duration-limit is the maximum duration that can be set for the clip
given its current children and their properties. If a change in the
children properties causes this to drop below the current duration, it
is automatically capped by this limit.

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

ges/ges-clip.c
ges/ges-clip.h
tests/check/ges/clip.c
tests/check/ges/test-utils.h

index a97286e389932784af0358d3c0ca61b2d552a969..1b36a2e21ddd32653fc3f0f03b73be137ec49bfe 100644 (file)
@@ -118,6 +118,9 @@ struct _GESClipPrivate
 
   /* The formats supported by this Clip */
   GESTrackType supportedformats;
+
+  GstClockTime duration_limit;
+  gboolean prevent_duration_limit_update;
 };
 
 enum
@@ -125,6 +128,7 @@ enum
   PROP_0,
   PROP_LAYER,
   PROP_SUPPORTED_FORMATS,
+  PROP_DURATION_LIMIT,
   PROP_LAST
 };
 
@@ -148,11 +152,176 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESClip, ges_clip,
 #define _IS_CORE_CHILD(child) GES_TRACK_ELEMENT_IS_CORE(child)
 
 #define _IS_TOP_EFFECT(child) \
-    (!_IS_CORE_CHILD (child) && GES_IS_BASE_EFFECT (child))
+  (!_IS_CORE_CHILD (child) && GES_IS_BASE_EFFECT (child))
 
 #define _IS_CORE_INTERNAL_SOURCE_CHILD(child) \
   (_IS_CORE_CHILD (child) \
-   && ges_track_element_has_internal_source (GES_TRACK_ELEMENT (child)))
+  && ges_track_element_has_internal_source (GES_TRACK_ELEMENT (child)))
+
+#define _MIN_CLOCK_TIME(a, b) \
+  (GST_CLOCK_TIME_IS_VALID (a) ? \
+  (GST_CLOCK_TIME_IS_VALID (b) ? MIN (a, b) : a) : b) \
+
+#define _CLOCK_TIME_IS_LESS(first, second) \
+  (GST_CLOCK_TIME_IS_VALID (first) && (!GST_CLOCK_TIME_IS_VALID (second) \
+  || first < second))
+
+
+typedef struct _DurationLimitData
+{
+  GESTrackElement *child;
+  GESTrack *track;
+  guint32 priority;
+  GstClockTime max_duration;
+  GstClockTime inpoint;
+  gboolean active;
+} DurationLimitData;
+
+static DurationLimitData *
+_duration_limit_data_new (GESTrackElement * child)
+{
+  GESTrack *track = ges_track_element_get_track (child);
+  DurationLimitData *data = g_new0 (DurationLimitData, 1);
+
+  data->child = gst_object_ref (child);
+  data->track = track ? gst_object_ref (track) : NULL;
+  data->inpoint = _INPOINT (child);
+  data->max_duration = _MAXDURATION (child);
+  data->priority = _PRIORITY (child);
+  data->active = ges_track_element_is_active (child);
+
+  return data;
+}
+
+static void
+_duration_limit_data_free (gpointer data_p)
+{
+  DurationLimitData *data = data_p;
+  gst_clear_object (&data->track);
+  gst_clear_object (&data->child);
+  g_free (data);
+}
+
+static GList *
+_duration_limit_data_list (GESClip * clip)
+{
+  GList *tmp, *list = NULL;
+
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next)
+    list = g_list_prepend (list, _duration_limit_data_new (tmp->data));
+
+  return list;
+}
+
+static gint
+_cmp_by_track_then_priority (gconstpointer a_p, gconstpointer b_p)
+{
+  const DurationLimitData *a = a_p, *b = b_p;
+  if (a->track < b->track)
+    return -1;
+  else if (a->track > b->track)
+    return 1;
+  /* if higher priority (numerically lower) place later */
+  if (a->priority < b->priority)
+    return -1;
+  else if (a->priority > b->priority)
+    return 1;
+  return 0;
+}
+
+#define _INTERNAL_LIMIT(data) \
+  ((data->active && GST_CLOCK_TIME_IS_VALID (data->max_duration)) ? \
+    data->max_duration - data->inpoint : GST_CLOCK_TIME_NONE)
+
+static GstClockTime
+_calculate_duration_limit (GESClip * self, GList * child_data)
+{
+  GstClockTime limit = GST_CLOCK_TIME_NONE;
+  GList *tmp;
+
+  child_data = g_list_sort (child_data, _cmp_by_track_then_priority);
+
+  tmp = child_data;
+
+  while (tmp) {
+    /* 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);
+
+      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));
+      }
+
+      GST_LOG_OBJECT (self, "duration-limit for track %" GST_PTR_FORMAT
+          " is %" GST_TIME_FORMAT, track, GST_TIME_ARGS (track_limit));
+      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;
+      }
+    }
+  }
+  GST_LOG_OBJECT (self, "calculated duration-limit for the clip is %"
+      GST_TIME_FORMAT, GST_TIME_ARGS (limit));
+
+  g_list_free_full (child_data, _duration_limit_data_free);
+
+  return limit;
+}
+
+static void
+_update_duration_limit (GESClip * self)
+{
+  GstClockTime duration_limit;
+
+  if (self->priv->prevent_duration_limit_update)
+    return;
+
+  duration_limit = _calculate_duration_limit (self,
+      _duration_limit_data_list (self));
+
+  if (duration_limit != self->priv->duration_limit) {
+    GESTimelineElement *element = GES_TIMELINE_ELEMENT (self);
+
+    self->priv->duration_limit = duration_limit;
+    GST_INFO_OBJECT (self, "duration-limit for the clip is %"
+        GST_TIME_FORMAT, GST_TIME_ARGS (duration_limit));
+
+    if (_CLOCK_TIME_IS_LESS (duration_limit, element->duration)) {
+      gboolean res;
+
+      GST_INFO_OBJECT (self, "Automatically reducing duration to %"
+          GST_TIME_FORMAT " to match the new duration-limit because "
+          "the current duration %" GST_TIME_FORMAT " exceeds it",
+          GST_TIME_ARGS (duration_limit), GST_TIME_ARGS (element->duration));
+
+      /* trim end with no snapping */
+      if (element->timeline)
+        res = timeline_tree_trim (timeline_get_tree (element->timeline),
+            element, 0, GST_CLOCK_DIFF (duration_limit, element->duration),
+            GES_EDGE_END, 0);
+      else
+        res = ges_timeline_element_set_duration (element, duration_limit);
+
+      if (!res)
+        GST_ERROR_OBJECT (self, "Could not reduce the duration of the "
+            "clip to below its duration-limit of %" GST_TIME_FORMAT,
+            GST_TIME_ARGS (duration_limit));
+    }
+    /* notify after the auto-change in duration to allow the user to set
+     * the duration in response to the change in their callbacks */
+    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION_LIMIT]);
+  }
+}
 
 /* @min_priority: The absolute minimum priority a child of @container should have
  * @max_priority: The absolute maximum priority a child of @container should have
@@ -181,8 +350,7 @@ _get_priority_range (GESContainer * container, guint32 * min_priority,
 }
 
 static void
-_child_priority_changed_cb (GESTimelineElement * child,
-    GParamSpec * arg G_GNUC_UNUSED, GESContainer * container)
+_child_priority_changed (GESContainer * container, GESTimelineElement * child)
 {
   /* we do not change the rest of the clip in response to a change in
    * the child priority */
@@ -206,24 +374,29 @@ _child_priority_changed_cb (GESTimelineElement * child,
   }
 }
 
-static void
-_child_inpoint_changed_cb (GESTimelineElement * child, GParamSpec * pspec,
-    GESContainer * container)
+/* returns TRUE if duration-limit needs to be updated */
+static gboolean
+_child_inpoint_changed (GESClip * self, GESTimelineElement * child)
 {
-  if (GES_CLIP (container)->priv->setting_inpoint)
-    return;
+  if (self->priv->setting_inpoint)
+    return FALSE;
 
-  /* ignore non-core */
-  /* if the track element has no internal content, then this means its
-   * in-point has been set (back) to 0, we can ignore this update */
+  /* if we have a non-core child, then we do not need the in-point of the
+   * clip to change. Similarly, if the track element is core but has no
+   * internal content, then this means its in-point has been set (back) to
+   * 0, which means we do not need to update the in-point of the clip. */
   if (!_IS_CORE_INTERNAL_SOURCE_CHILD (child))
-    return;
+    return TRUE;
+
+  /* if setting the in-point of the clip, this will handle the change in
+   * the duration-limit */
 
   /* If the child->inpoint is the same as our own, set_inpoint will do
    * nothing. For example, when we set them in add_child (the notifies for
    * this are released after child_added is called because
    * ges_container_add freezes them) */
-  _set_inpoint0 (GES_TIMELINE_ELEMENT (container), child->inpoint);
+  _set_inpoint0 (GES_TIMELINE_ELEMENT (self), child->inpoint);
+  return FALSE;
 }
 
 /* called when a child is added, removed or their max-duration changes */
@@ -239,10 +412,8 @@ _update_max_duration (GESContainer * container)
 
   for (tmp = container->children; tmp; tmp = tmp->next) {
     GESTimelineElement *child = tmp->data;
-    if (_IS_CORE_CHILD (child)
-        && GST_CLOCK_TIME_IS_VALID (child->maxduration))
-      min = GST_CLOCK_TIME_IS_VALID (min) ? MIN (min, child->maxduration) :
-          child->maxduration;
+    if (_IS_CORE_CHILD (child))
+      min = _MIN_CLOCK_TIME (min, child->maxduration);
   }
   priv->updating_max_duration = TRUE;
   ges_timeline_element_set_max_duration (GES_TIMELINE_ELEMENT (container), min);
@@ -250,8 +421,8 @@ _update_max_duration (GESContainer * container)
 }
 
 static void
-_child_max_duration_changed_cb (GESTimelineElement * child,
-    GParamSpec * pspec, GESContainer * container)
+_child_max_duration_changed (GESContainer * container,
+    GESTimelineElement * child)
 {
   /* ignore non-core */
   if (!_IS_CORE_CHILD (child))
@@ -261,8 +432,7 @@ _child_max_duration_changed_cb (GESTimelineElement * child,
 }
 
 static void
-_child_has_internal_source_changed_cb (GESTimelineElement * child,
-    GParamSpec * pspec, GESContainer * container)
+_child_has_internal_source_changed (GESClip * self, GESTimelineElement * child)
 {
   /* ignore non-core */
   /* if the track element is now registered to have no internal content,
@@ -271,7 +441,34 @@ _child_has_internal_source_changed_cb (GESTimelineElement * child,
     return;
 
   /* otherwise, we need to make its in-point match ours */
-  _set_inpoint0 (child, _INPOINT (container));
+  _set_inpoint0 (child, _INPOINT (self));
+}
+
+#define _IS_PROP(prop) (g_strcmp0 (name, prop) == 0)
+
+static void
+_child_property_changed_cb (GESTimelineElement * child, GParamSpec * pspec,
+    GESClip * self)
+{
+  gboolean update = FALSE;
+  const gchar *name = pspec->name;
+
+  if (_IS_PROP ("track") || _IS_PROP ("active")) {
+    update = TRUE;
+  } else if (_IS_PROP ("priority")) {
+    update = TRUE;
+    _child_priority_changed (GES_CONTAINER (self), child);
+  } else if (_IS_PROP ("in-point")) {
+    update = _child_inpoint_changed (self, child);
+  } else if (_IS_PROP ("max-duration")) {
+    update = 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)
+    _update_duration_limit (self);
 }
 
 /****************************************************
@@ -416,10 +613,13 @@ static gboolean
 _set_childrens_inpoint (GESTimelineElement * element, GstClockTime inpoint,
     gboolean break_on_failure)
 {
+  GESClip *self = GES_CLIP (element);
   GList *tmp;
-  GESClipPrivate *priv = GES_CLIP (element)->priv;
+  GESClipPrivate *priv = self->priv;
+  gboolean prev_prevent = priv->prevent_duration_limit_update;
 
   priv->setting_inpoint = TRUE;
+  priv->prevent_duration_limit_update = TRUE;
   for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
     GESTimelineElement *child = tmp->data;
 
@@ -428,12 +628,18 @@ _set_childrens_inpoint (GESTimelineElement * element, GstClockTime inpoint,
         GST_ERROR_OBJECT ("Could not set the in-point of child %"
             GES_FORMAT " to %" GST_TIME_FORMAT, GES_ARGS (child),
             GST_TIME_ARGS (inpoint));
-        if (break_on_failure)
+        if (break_on_failure) {
+          priv->setting_inpoint = FALSE;
+          priv->prevent_duration_limit_update = prev_prevent;
           return FALSE;
+        }
       }
     }
   }
   priv->setting_inpoint = FALSE;
+  priv->prevent_duration_limit_update = prev_prevent;
+
+  _update_duration_limit (self);
 
   return TRUE;
 }
@@ -452,7 +658,6 @@ static gboolean
 _set_duration (GESTimelineElement * element, GstClockTime duration)
 {
   GList *tmp, *children;
-
   GESContainer *container = GES_CONTAINER (element);
 
   /* get copy of children, since GESContainer may resort the clip */
@@ -477,9 +682,12 @@ static gboolean
 _set_max_duration (GESTimelineElement * element, GstClockTime maxduration)
 {
   GList *tmp;
-  GESClipPrivate *priv = GES_CLIP (element)->priv;
+  GESClip *self = GES_CLIP (element);
+  GESClipPrivate *priv = self->priv;
   GstClockTime new_min = GST_CLOCK_TIME_NONE;
   gboolean has_core = FALSE;
+  gboolean res = FALSE;
+  gboolean prev_prevent = priv->prevent_duration_limit_update;
 
   /* if we are setting based on a change in the minimum */
   if (priv->updating_max_duration)
@@ -487,6 +695,7 @@ _set_max_duration (GESTimelineElement * element, GstClockTime maxduration)
 
   /* else, we set every core child to have the same max duration */
 
+  priv->prevent_duration_limit_update = TRUE;
   priv->prevent_max_duration_update = TRUE;
   for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
     GESTimelineElement *child = tmp->data;
@@ -498,14 +707,12 @@ _set_max_duration (GESTimelineElement * element, GstClockTime maxduration)
           GST_ERROR_OBJECT ("Could not set the max-duration of child %"
               GES_FORMAT " to %" GST_TIME_FORMAT, GES_ARGS (child),
               GST_TIME_ARGS (maxduration));
-
-        if (GST_CLOCK_TIME_IS_VALID (child->maxduration))
-          new_min = GST_CLOCK_TIME_IS_VALID (new_min) ?
-              MIN (new_min, child->maxduration) : child->maxduration;
+        new_min = _MIN_CLOCK_TIME (new_min, child->maxduration);
       }
     }
   }
   priv->prevent_max_duration_update = FALSE;
+  priv->prevent_duration_limit_update = prev_prevent;
 
   if (!has_core) {
     /* allow max-duration to be set arbitrarily when we have no
@@ -515,7 +722,8 @@ _set_max_duration (GESTimelineElement * element, GstClockTime maxduration)
       GST_INFO_OBJECT (element,
           "Allowing max-duration of the clip to be set to %" GST_TIME_FORMAT
           " because it has no core children", GST_TIME_ARGS (maxduration));
-    return TRUE;
+    res = TRUE;
+    goto done;
   }
 
   if (new_min != maxduration) {
@@ -532,10 +740,15 @@ _set_max_duration (GESTimelineElement * element, GstClockTime maxduration)
     priv->updating_max_duration = TRUE;
     ges_timeline_element_set_max_duration (element, new_min);
     priv->updating_max_duration = FALSE;
-    return FALSE;
+    goto done;
   }
 
-  return TRUE;
+  res = TRUE;
+
+done:
+  _update_duration_limit (self);
+
+  return res;
 }
 
 static gboolean
@@ -544,7 +757,7 @@ _set_priority (GESTimelineElement * element, guint32 priority)
   GESClipPrivate *priv = GES_CLIP (element)->priv;
   GList *tmp;
   guint32 min_prio, max_prio;
-
+  gboolean prev_prevent = priv->prevent_duration_limit_update;
   GESContainer *container = GES_CONTAINER (element);
 
   /* send the new 'priority' to determine what the new 'min_prio' should
@@ -554,6 +767,7 @@ _set_priority (GESTimelineElement * element, guint32 priority)
   /* offsets will remain constant for the children */
   priv->prevent_resort = TRUE;
   priv->prevent_priority_offset_update = TRUE;
+  priv->prevent_duration_limit_update = TRUE;
   for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
     guint32 track_element_prio;
     GESTimelineElement *child = (GESTimelineElement *) tmp->data;
@@ -582,9 +796,11 @@ _set_priority (GESTimelineElement * element, guint32 priority)
     _set_priority0 (child, track_element_prio);
   }
   /* no need to re-sort the container since we maintained the relative
-   * offsets. As such, the height remains the same as well. */
+   * offsets. As such, the height and duration-limit remains the same as
+   * well. */
   priv->prevent_resort = FALSE;
   priv->prevent_priority_offset_update = FALSE;
+  priv->prevent_duration_limit_update = prev_prevent;
 
   return TRUE;
 }
@@ -650,28 +866,30 @@ _compute_height (GESContainer * container)
 static gboolean
 _add_child (GESContainer * container, GESTimelineElement * element)
 {
+  GESClip *self = GES_CLIP (container);
   GESClipClass *klass = GES_CLIP_GET_CLASS (GES_CLIP (container));
   guint max_prio, min_prio;
   GESTrack *track;
   GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (container);
-  GESClipPrivate *priv = GES_CLIP (container)->priv;
+  GESClipPrivate *priv = self->priv;
   GESAsset *asset, *creator_asset;
+  gboolean prev_prevent = priv->prevent_duration_limit_update;
 
   g_return_val_if_fail (GES_IS_TRACK_ELEMENT (element), FALSE);
 
   if (element->timeline && element->timeline != timeline) {
-    GST_WARNING_OBJECT (container, "Cannot add %" GES_FORMAT " as a child "
+    GST_WARNING_OBJECT (self, "Cannot add %" GES_FORMAT " as a child "
         "because its timeline is %" GST_PTR_FORMAT " rather than the "
         "clip's timeline %" GST_PTR_FORMAT, GES_ARGS (element),
         element->timeline, timeline);
     return FALSE;
   }
 
-  asset = ges_extractable_get_asset (GES_EXTRACTABLE (container));
+  asset = ges_extractable_get_asset (GES_EXTRACTABLE (self));
   creator_asset =
       ges_track_element_get_creator_asset (GES_TRACK_ELEMENT (element));
   if (creator_asset && asset != creator_asset) {
-    GST_WARNING_OBJECT (container,
+    GST_WARNING_OBJECT (self,
         "Cannot add the track element %" GES_FORMAT " as a child "
         "because it is a core element created by another clip with a "
         "different asset to the current clip's asset", GES_ARGS (element));
@@ -685,7 +903,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
      * the track, so we would have checked this with the
      * element->timeline check. But technically a user could get around
      * this, so we double check here. */
-    GST_WARNING_OBJECT (container, "Cannot add %" GES_FORMAT " as a child "
+    GST_WARNING_OBJECT (self, "Cannot add %" GES_FORMAT " as a child "
         "because its track %" GST_PTR_FORMAT " is part of the timeline %"
         GST_PTR_FORMAT " rather than the clip's timeline %" GST_PTR_FORMAT,
         GES_ARGS (element), track, ges_track_get_timeline (track), timeline);
@@ -700,8 +918,8 @@ _add_child (GESContainer * container, GESTimelineElement * element)
      * list of added effects, so we do not increase nb_effects. */
 
     if (track && !priv->allow_any_track
-        && _track_contains_core (GES_CLIP (container), track, TRUE)) {
-      GST_WARNING_OBJECT (container, "Cannot add the core child %" GES_FORMAT
+        && _track_contains_core (self, track, TRUE)) {
+      GST_WARNING_OBJECT (self, "Cannot add the core child %" GES_FORMAT
           " because it is in the same track %" GST_PTR_FORMAT " as an "
           "existing core child", GES_ARGS (element), track);
       return FALSE;
@@ -712,11 +930,10 @@ _add_child (GESContainer * container, GESTimelineElement * element)
     if (ges_track_element_has_internal_source (GES_TRACK_ELEMENT (element))) {
       /* adding can fail if the max-duration of the element is smaller
        * than the current in-point of the clip */
-      if (!_set_inpoint0 (element, _INPOINT (container))) {
+      if (!_set_inpoint0 (element, _INPOINT (self))) {
         GST_ERROR_OBJECT (element, "Could not set the in-point of the "
             "element %" GES_FORMAT " to %" GST_TIME_FORMAT ". Not adding "
-            "as a child", GES_ARGS (element),
-            GST_TIME_ARGS (_INPOINT (container)));
+            "as a child", GES_ARGS (element), GST_TIME_ARGS (_INPOINT (self)));
         return FALSE;
       }
     }
@@ -730,20 +947,21 @@ _add_child (GESContainer * container, GESTimelineElement * element)
      * to make room. */
 
     if (track && !priv->allow_any_track
-        && !_track_contains_core (GES_CLIP (container), track, TRUE)) {
-      GST_WARNING_OBJECT (container, "Cannot add the effect %" GES_FORMAT
+        && !_track_contains_core (GES_CLIP (self), track, TRUE)) {
+      GST_WARNING_OBJECT (self, "Cannot add the effect %" GES_FORMAT
           " because its track %" GST_PTR_FORMAT " does not contain one "
           "of the clip's core children", GES_ARGS (element), track);
       return FALSE;
     }
 
-    GST_DEBUG_OBJECT (container, "Adding %ith effect: %" GES_FORMAT
+    GST_DEBUG_OBJECT (self, "Adding %ith effect: %" GES_FORMAT
         " Priority %i", priv->nb_effects + 1, GES_ARGS (element),
         min_prio + priv->nb_effects);
 
     /* changing priorities, and updating their offset */
     priv->prevent_resort = TRUE;
-    tmp = g_list_nth (GES_CONTAINER_CHILDREN (container), priv->nb_effects);
+    priv->prevent_duration_limit_update = TRUE;
+    tmp = g_list_nth (container->children, priv->nb_effects);
     for (; tmp; tmp = tmp->next)
       ges_timeline_element_set_priority (GES_TIMELINE_ELEMENT (tmp->data),
           GES_TIMELINE_ELEMENT_PRIORITY (tmp->data) + 1);
@@ -751,30 +969,32 @@ _add_child (GESContainer * container, GESTimelineElement * element)
     _set_priority0 (element, min_prio + priv->nb_effects);
     priv->nb_effects++;
     priv->prevent_resort = FALSE;
+    priv->prevent_duration_limit_update = prev_prevent;
     /* 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) */
     /* The height has already changed (increased by 1) */
     _compute_height (container);
+    /* update duration limit in _child_added */
   } else {
     if (_IS_TOP_EFFECT (element))
-      GST_WARNING_OBJECT (container, "Cannot add the effect %" GES_FORMAT
+      GST_WARNING_OBJECT (self, "Cannot add the effect %" GES_FORMAT
           " because it is not a core element created by the clip itself "
           "and the %s class does not allow for adding extra effects",
           GES_ARGS (element), G_OBJECT_CLASS_NAME (klass));
     else if (GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass))
-      GST_WARNING_OBJECT (container, "Cannot add the track element %"
+      GST_WARNING_OBJECT (self, "Cannot add the track element %"
           GES_FORMAT " because it is neither a core element created by "
           "the clip itself, nor a GESBaseEffect", GES_ARGS (element));
     else
-      GST_WARNING_OBJECT (container, "Cannot add the track element %"
+      GST_WARNING_OBJECT (self, "Cannot add the track element %"
           GES_FORMAT " because it is not a core element created by the "
           "clip itself", GES_ARGS (element));
     return FALSE;
   }
 
-  _set_start0 (element, GES_TIMELINE_ELEMENT_START (container));
-  _set_duration0 (element, GES_TIMELINE_ELEMENT_DURATION (container));
+  _set_start0 (element, GES_TIMELINE_ELEMENT_START (self));
+  _set_duration0 (element, GES_TIMELINE_ELEMENT_DURATION (self));
 
   return TRUE;
 }
@@ -787,10 +1007,12 @@ _remove_child (GESContainer * container, GESTimelineElement * element)
   /* NOTE: notifies are currently frozen by ges_container_add */
   if (_IS_TOP_EFFECT (element)) {
     GList *tmp;
+    gboolean prev_prevent = priv->prevent_duration_limit_update;
     GST_DEBUG_OBJECT (container, "Resyncing effects priority.");
 
     /* changing priorities, so preventing a re-sort */
     priv->prevent_resort = TRUE;
+    priv->prevent_duration_limit_update = TRUE;
     for (tmp = GES_CONTAINER_CHILDREN (container); tmp; tmp = tmp->next) {
       guint32 sibling_prio = GES_TIMELINE_ELEMENT_PRIORITY (tmp->data);
       if (sibling_prio > element->priority)
@@ -799,46 +1021,44 @@ _remove_child (GESContainer * container, GESTimelineElement * element)
     }
     priv->nb_effects--;
     priv->prevent_resort = FALSE;
+    priv->prevent_duration_limit_update = prev_prevent;
     /* no need to re-sort the children since the rest keep the same
      * relative priorities */
     /* height may have changed */
     _compute_height (container);
   }
+  /* duration-limit updated in _child_removed */
   return TRUE;
 }
 
 static void
 _child_added (GESContainer * container, GESTimelineElement * element)
 {
-  g_signal_connect (element, "notify::priority",
-      G_CALLBACK (_child_priority_changed_cb), container);
-  g_signal_connect (element, "notify::in-point",
-      G_CALLBACK (_child_inpoint_changed_cb), container);
-  g_signal_connect (element, "notify::max-duration",
-      G_CALLBACK (_child_max_duration_changed_cb), container);
-  g_signal_connect (element, "notify::has-internal-source",
-      G_CALLBACK (_child_has_internal_source_changed_cb), container);
+  GESClip *self = GES_CLIP (container);
+
+  g_signal_connect (element, "notify", G_CALLBACK (_child_property_changed_cb),
+      self);
 
-  _child_priority_changed_cb (element, NULL, container);
+  _child_priority_changed (container, element);
 
   if (_IS_CORE_CHILD (element))
     _update_max_duration (container);
+
+  _update_duration_limit (self);
 }
 
 static void
 _child_removed (GESContainer * container, GESTimelineElement * element)
 {
-  g_signal_handlers_disconnect_by_func (element, _child_priority_changed_cb,
-      container);
-  g_signal_handlers_disconnect_by_func (element, _child_inpoint_changed_cb,
-      container);
-  g_signal_handlers_disconnect_by_func (element,
-      _child_max_duration_changed_cb, container);
-  g_signal_handlers_disconnect_by_func (element,
-      _child_has_internal_source_changed_cb, container);
+  GESClip *self = GES_CLIP (container);
+
+  g_signal_handlers_disconnect_by_func (element, _child_property_changed_cb,
+      self);
 
   if (_IS_CORE_CHILD (element))
     _update_max_duration (container);
+
+  _update_duration_limit (self);
 }
 
 static void
@@ -855,12 +1075,17 @@ add_clip_to_list (gpointer key, gpointer clip, GList ** list)
  * NOTE: Since this does not change the creator asset of the child, this
  * should only be called for transferring children between clips with the
  * same asset.
+ * NOTE: This also prevents the update of the duration-limit, so you
+ * should ensure that you call _update_duration_limit on both clips when
+ * transferring has completed.
  */
 static void
 _transfer_child (GESClip * from_clip, GESClip * to_clip,
     GESTrackElement * child)
 {
   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;
 
   /* We need to bump the refcount to avoid the object to be destroyed */
   gst_object_ref (child);
@@ -868,6 +1093,9 @@ _transfer_child (GESClip * from_clip, GESClip * to_clip,
   /* don't want to change tracks */
   ges_timeline_set_moving_track_elements (timeline, TRUE);
 
+  from_clip->priv->prevent_duration_limit_update = TRUE;
+  to_clip->priv->prevent_duration_limit_update = TRUE;
+
   ges_container_remove (GES_CONTAINER (from_clip),
       GES_TIMELINE_ELEMENT (child));
 
@@ -879,6 +1107,9 @@ _transfer_child (GESClip * from_clip, GESClip * to_clip,
   to_clip->priv->allow_any_track = FALSE;
   ges_timeline_set_moving_track_elements (timeline, FALSE);
 
+  from_clip->priv->prevent_duration_limit_update = prev_prevent_from;
+  to_clip->priv->prevent_duration_limit_update = prev_prevent_to;
+
   gst_object_unref (child);
 }
 
@@ -942,6 +1173,14 @@ _ungroup (GESContainer * container, gboolean recursive)
   g_hash_table_foreach (_tracktype_clip, (GHFunc) add_clip_to_list, &ret);
   g_hash_table_unref (_tracktype_clip);
 
+  /* Need to update the duration limit.
+   * Since we have divided the clip by its tracks, the duration-limit,
+   * which is a minimum value calculated per track, can only increase in
+   * value, which means the duration of the clip should not change, which
+   * means updating should always be possible */
+  for (tmp = ret; tmp; tmp = tmp->next)
+    _update_duration_limit (tmp->data);
+
   return ret;
 }
 
@@ -1062,10 +1301,31 @@ _group (GList * containers)
       supported_formats |= ges_track_element_get_track_type (celement);
     }
     g_list_free_full (children, gst_object_unref);
+    /* duration-limit should be GST_CLOCK_TIME_NONE now that we have no
+     * children */
+    _update_duration_limit (cclip);
 
     ges_layer_remove_clip (layer, cclip);
   }
 
+  /* Need to update the duration limit.
+   * Each received clip C_i that has been grouped may have had a different
+   * duration-limit L_i. In each case the duration must be less than
+   * this limit, and since each clip shares the same duration, we have
+   * for each clip C_i:
+   *   duration <= L_i
+   * Thus:
+   *   duration <= min_i (L_i)
+   *
+   * Now, upon grouping each clip C_i into C, we have not changed the
+   * children properties that affect the duration-limit. And since the
+   * duration-limit is calculated as the minimum amongst the tracks of C,
+   * this means that the duration-limit for C should be
+   *   L = min_i (L_i) >= duration
+   * Therefore, we can safely set the duration-limit of C to L without
+   * changing the duration of C. */
+  _update_duration_limit (GES_CLIP (ret));
+
   ges_clip_set_supported_formats (GES_CLIP (ret), supported_formats);
 
   return ret;
@@ -1075,10 +1335,13 @@ void
 ges_clip_empty_from_track (GESClip * clip, GESTrack * track)
 {
   GList *tmp;
+  gboolean prev_prevent = clip->priv->prevent_duration_limit_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;
 
   for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
     GESTrackElement *child = tmp->data;
@@ -1089,6 +1352,8 @@ ges_clip_empty_from_track (GESClip * clip, GESTrack * track)
     }
   }
   clip->priv->allow_any_track = FALSE;
+  clip->priv->prevent_duration_limit_update = prev_prevent;
+  _update_duration_limit (clip);
 }
 
 static GESTrackElement *
@@ -1261,6 +1526,8 @@ ges_clip_get_property (GObject * object, guint property_id,
     case PROP_SUPPORTED_FORMATS:
       g_value_set_flags (value, clip->priv->supportedformats);
       break;
+    case PROP_DURATION_LIMIT:
+      g_value_set_uint64 (value, clip->priv->duration_limit);
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
   }
@@ -1338,6 +1605,30 @@ ges_clip_class_init (GESClipClass * klass)
   g_object_class_install_property (object_class, PROP_LAYER,
       properties[PROP_LAYER]);
 
+  /**
+   * GESClip:duration-limit:
+   *
+   * 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.
+   *
+   * Note that whilst a clip has no children in any tracks, the limit will
+   * be unknown, and similarly set to #GST_CLOCK_TIME_NONE.
+   *
+   * If the duration-limit would ever go below the current
+   * #GESTimelineElement:duration of the clip due to a change in the above
+   * variables, its #GESTimelineElement:duration will be set to the new
+   * limit.
+   */
+  properties[PROP_DURATION_LIMIT] =
+      g_param_spec_uint64 ("duration-limit", "Duration Limit",
+      "A limit on the duration of the clip", 0, G_MAXUINT64,
+      GST_CLOCK_TIME_NONE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
+  g_object_class_install_property (object_class, PROP_DURATION_LIMIT,
+      properties[PROP_DURATION_LIMIT]);
+
   element_class->ripple = _ripple;
   element_class->ripple_end = _ripple_end;
   element_class->roll_start = _roll_start;
@@ -1367,6 +1658,7 @@ static void
 ges_clip_init (GESClip * self)
 {
   self->priv = ges_clip_get_instance_private (self);
+  self->priv->duration_limit = GST_CLOCK_TIME_NONE;
 }
 
 /**
@@ -1697,6 +1989,22 @@ ges_clip_get_layer (GESClip * clip)
   return clip->priv->layer;
 }
 
+/**
+ * ges_clip_get_duration_limit:
+ * @clip: A #GESClip
+ *
+ * Gets the #GESClip:duration-limit of the clip.
+ *
+ * Returns: The duration-limit of @clip.
+ */
+GstClockTime
+ges_clip_get_duration_limit (GESClip * clip)
+{
+  g_return_val_if_fail (GES_IS_CLIP (clip), GST_CLOCK_TIME_NONE);
+
+  return clip->priv->duration_limit;
+}
+
 /**
  * ges_clip_get_top_effects:
  * @clip: A #GESClip
index 628d756fa90cbfc765cf882f44760b88693a06db..1e2c5d9a07c1b6669597b272295f7c7d38410b78 100644 (file)
@@ -193,4 +193,7 @@ GstClockTime ges_clip_get_timeline_time_from_source_frame (GESClip * clip,
                                                            GESFrameNumber frame_number,
                                                            GError ** err);
 
+GES_API
+GstClockTime ges_clip_get_duration_limit (GESClip * clip);
+
 G_END_DECLS
index 2c26ea58e90185cf18172d0be9f1624c90acd5a8..08e78bac0264776354eefcf00a5bdea2980d62e7 100644 (file)
 #include <ges/ges.h>
 #include <gst/check/gstcheck.h>
 
+#define _assert_add(clip, child) \
+  fail_unless (ges_container_add (GES_CONTAINER (clip), \
+        GES_TIMELINE_ELEMENT (child)))
+
+#define _assert_remove(clip, child) \
+  fail_unless (ges_container_remove (GES_CONTAINER (clip), \
+        GES_TIMELINE_ELEMENT (child)))
+
 GST_START_TEST (test_object_properties)
 {
   GESClip *clip;
@@ -95,8 +103,7 @@ GST_START_TEST (test_object_properties)
   nle_object_check (ges_track_element_get_nleobject (trackelement), 400, 510,
       120, 510, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE);
 
-  ges_container_remove (GES_CONTAINER (clip),
-      GES_TIMELINE_ELEMENT (trackelement));
+  _assert_remove (clip, trackelement);
 
   gst_object_unref (timeline);
 
@@ -365,10 +372,10 @@ GST_START_TEST (test_split_object)
       GES_TIMELINE_ELEMENT (clip));
 
   effect1 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
-  ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (effect1));
+  _assert_add (clip, effect1);
 
   effect2 = GES_TRACK_ELEMENT (ges_effect_new ("vertigotv"));
-  ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (effect2));
+  _assert_add (clip, effect2);
 
   /* Check that trackelement has the same properties */
   CHECK_OBJECT_PROPS (trackelement1, 42, 12, 50);
@@ -578,18 +585,15 @@ GST_START_TEST (test_clip_group_ungroup)
 
   el = GES_TRACK_ELEMENT (ges_effect_new ("audioecho"));
   ges_track_element_set_track_type (el, GES_TRACK_TYPE_AUDIO);
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (el)));
+  _assert_add (clip, el);
 
   el = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
   ges_track_element_set_track_type (el, GES_TRACK_TYPE_VIDEO);
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (el)));
+  _assert_add (clip, el);
 
   el = GES_TRACK_ELEMENT (ges_effect_new ("videobalance"));
   ges_track_element_set_track_type (el, GES_TRACK_TYPE_VIDEO);
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (el)));
+  _assert_add (clip, el);
 
   assert_num_children (clip, 5);
   CHECK_OBJECT_PROPS (clip, 0, 0, 10);
@@ -696,27 +700,27 @@ GST_START_TEST (test_clip_group_ungroup)
   assert_num_in_track (audio_track, 2);
   assert_num_in_track (video_track, 3);
 
-  ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (video_clip), 10);
+  assert_set_start (video_clip, 10);
   CHECK_OBJECT_PROPS (video_clip, 10, 0, 10);
   CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10);
 
   _assert_regroup_fails (containers);
 
-  ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (video_clip), 0);
-  ges_timeline_element_set_inpoint (GES_TIMELINE_ELEMENT (video_clip), 10);
+  assert_set_start (video_clip, 0);
+  assert_set_inpoint (video_clip, 10);
   CHECK_OBJECT_PROPS (video_clip, 0, 10, 10);
   CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10);
 
   _assert_regroup_fails (containers);
 
-  ges_timeline_element_set_inpoint (GES_TIMELINE_ELEMENT (video_clip), 0);
-  ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (video_clip), 15);
+  assert_set_inpoint (video_clip, 0);
+  assert_set_duration (video_clip, 15);
   CHECK_OBJECT_PROPS (video_clip, 0, 0, 15);
   CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10);
 
   _assert_regroup_fails (containers);
 
-  ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (video_clip), 10);
+  assert_set_duration (video_clip, 10);
   CHECK_OBJECT_PROPS (video_clip, 0, 0, 10);
   CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10);
 
@@ -897,8 +901,7 @@ GST_START_TEST (test_clip_can_group)
   /* can group if same asset but different tracks */
   clip1 = ges_layer_add_asset (layer1, asset2, 0, 0, 10, GES_TRACK_TYPE_VIDEO);
   fail_unless (clip1);
-  fail_unless (ges_container_add (GES_CONTAINER (clip1),
-          GES_TIMELINE_ELEMENT (ges_effect_new ("agingtv"))));
+  _assert_add (clip1, ges_effect_new ("agingtv"));
   assert_num_children (clip1, 2);
 
   clip2 = ges_layer_add_asset (layer1, asset2, 0, 0, 10, GES_TRACK_TYPE_AUDIO);
@@ -997,11 +1000,9 @@ GST_START_TEST (test_adding_children_to_track)
   fail_unless (ges_track_element_get_track (source) == track1);
 
   effect = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect)));
+  _assert_add (clip, effect);
   effect2 = GES_TRACK_ELEMENT (ges_effect_new ("vertigotv"));
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect2)));
+  _assert_add (clip, effect2);
   assert_num_children (clip, 3);
   assert_num_in_track (track1, 3);
   assert_num_in_track (track2, 0);
@@ -1154,8 +1155,7 @@ GST_START_TEST (test_adding_children_to_track)
   /* removing core from the container, empties the non-core from their
    * tracks */
   gst_object_ref (added);
-  fail_unless (ges_container_remove (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (added)));
+  _assert_remove (clip, added);
   assert_num_children (clip, 5);
   fail_unless (ges_track_element_get_track (source) == track1);
   fail_if (ges_track_element_get_track (added));
@@ -1167,10 +1167,8 @@ GST_START_TEST (test_adding_children_to_track)
   assert_num_in_track (track2, 0);
   gst_object_unref (added);
 
-  fail_unless (ges_container_remove (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (added2)));
-  fail_unless (ges_container_remove (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (added3)));
+  _assert_remove (clip, added2);
+  _assert_remove (clip, added3);
   assert_num_children (clip, 3);
   assert_num_in_track (track1, 3);
   assert_num_in_track (track2, 0);
@@ -1234,14 +1232,13 @@ GST_START_TEST (test_adding_children_to_track)
 
   /* can not add source at time 23 because it would result in three
    * overlapping sources in the track */
-  fail_unless (ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip),
-          23));
+  assert_set_start (clip, 23);
   fail_if (ges_clip_add_child_to_track (clip, source, track1, NULL));
   assert_num_children (clip, 3);
   assert_num_in_track (track1, 4);
 
   /* can add at 5, with overlap */
-  fail_unless (ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip), 5));
+  assert_set_start (clip, 5);
   added = ges_clip_add_child_to_track (clip, source, track1, NULL);
   /* added is the source since it was not already in a track */
   fail_unless (added == source);
@@ -1314,8 +1311,7 @@ GST_START_TEST (test_clip_refcount_remove_child)
   assert_num_in_track (track, 2);
   ASSERT_OBJECT_REFCOUNT (effect, "1 for the track + 1 timeline", 2);
 
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect)));
+  _assert_add (clip, effect);
   assert_num_children (clip, 2);
   ASSERT_OBJECT_REFCOUNT (effect, "1 for the container + 1 for the track"
       " + 1 timeline", 3);
@@ -1326,8 +1322,7 @@ GST_START_TEST (test_clip_refcount_remove_child)
   g_signal_connect (clip, "child-removed", G_CALLBACK (child_removed_cb),
       &called);
   gst_object_ref (effect);
-  fail_unless (ges_container_remove (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect)));
+  _assert_remove (clip, effect);
   fail_unless (called == TRUE);
   ASSERT_OBJECT_REFCOUNT (effect, "1 test ref", 1);
   gst_object_unref (effect);
@@ -1377,18 +1372,15 @@ GST_START_TEST (test_clip_find_track_element)
 
   effect = GES_TRACK_ELEMENT (ges_effect_new ("identity"));
   fail_unless (ges_track_add_element (track, effect));
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect)));
+  _assert_add (clip, effect);
 
   effect1 = GES_TRACK_ELEMENT (ges_effect_new ("identity"));
   fail_unless (ges_track_add_element (track1, effect1));
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect1)));
+  _assert_add (clip, effect1);
 
   effect2 = GES_TRACK_ELEMENT (ges_effect_new ("identity"));
   fail_unless (ges_track_add_element (track2, effect2));
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect2)));
+  _assert_add (clip, effect2);
 
   fail_if (selection_called);
   assert_num_children (clip, 6);
@@ -1529,16 +1521,13 @@ GST_START_TEST (test_effects_priorities)
   ges_layer_add_clip (layer, clip);
 
   effect = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect)));
+  _assert_add (clip, effect);
 
   effect1 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect1)));
+  _assert_add (clip, effect1);
 
   effect2 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect2)));
+  _assert_add (clip, effect2);
 
   fail_unless_equals_int (MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0,
       _PRIORITY (effect));
@@ -1712,10 +1701,10 @@ GST_START_TEST (test_children_time_setters)
     ges_track_element_set_has_internal_source (child, TRUE);
     _test_children_time_setting_on_clip (clip, child);
     /* clip in a group */
-    fail_unless (ges_container_add (group, GES_TIMELINE_ELEMENT (clip)));
+    _assert_add (group, clip);
     _test_children_time_setting_on_clip (clip, child);
     /* group is removed from the timeline and destroyed when empty */
-    ges_container_remove (group, GES_TIMELINE_ELEMENT (clip));
+    _assert_remove (group, clip);
     /* child not in timeline */
     gst_object_ref (clip);
     fail_unless (ges_layer_remove_clip (layer, clip));
@@ -1803,9 +1792,9 @@ GST_START_TEST (test_children_inpoint)
 
   clip = GES_TIMELINE_ELEMENT (ges_test_clip_new ());
 
-  fail_unless (ges_timeline_element_set_start (clip, 5));
-  fail_unless (ges_timeline_element_set_duration (clip, 20));
-  fail_unless (ges_timeline_element_set_inpoint (clip, 30));
+  assert_set_start (clip, 5);
+  assert_set_duration (clip, 20);
+  assert_set_inpoint (clip, 30);
 
   CHECK_OBJECT_PROPS (clip, 5, 30, 20);
 
@@ -1833,13 +1822,13 @@ GST_START_TEST (test_children_inpoint)
   fail_if (ges_track_element_has_internal_source (GES_TRACK_ELEMENT (effect)));
   /* allow us to choose our own in-point */
   ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (effect), TRUE);
-  fail_unless (ges_timeline_element_set_start (effect, 104));
-  fail_unless (ges_timeline_element_set_duration (effect, 53));
-  fail_unless (ges_timeline_element_set_inpoint (effect, 67));
+  assert_set_start (effect, 104);
+  assert_set_duration (effect, 53);
+  assert_set_inpoint (effect, 67);
 
   /* adding the effect will change its start and duration, but not its
    * in-point */
-  fail_unless (ges_container_add (GES_CONTAINER (clip), effect));
+  _assert_add (clip, effect);
 
   CHECK_OBJECT_PROPS (clip, 5, 30, 20);
   CHECK_OBJECT_PROPS (child0, 5, 30, 20);
@@ -1865,7 +1854,7 @@ GST_START_TEST (test_children_inpoint)
 
   /* when we set the in-point on a core-child with an internal source we
    * also set the clip and siblings with the same features */
-  fail_unless (ges_timeline_element_set_inpoint (child1, 50));
+  assert_set_inpoint (child1, 50);
 
   CHECK_OBJECT_PROPS (clip, 5, 50, 20);
   /* child with no internal source not changed */
@@ -1883,7 +1872,7 @@ GST_START_TEST (test_children_inpoint)
   CHECK_OBJECT_PROPS (child1, 5, 50, 20);
   CHECK_OBJECT_PROPS (effect, 5, 67, 20);
 
-  fail_unless (ges_timeline_element_set_inpoint (child0, 40));
+  assert_set_inpoint (child0, 40);
 
   CHECK_OBJECT_PROPS (clip, 5, 40, 20);
   CHECK_OBJECT_PROPS (child0, 5, 40, 20);
@@ -1891,7 +1880,7 @@ GST_START_TEST (test_children_inpoint)
   CHECK_OBJECT_PROPS (effect, 5, 67, 20);
 
   /* setting in-point on effect shouldn't change any other siblings */
-  fail_unless (ges_timeline_element_set_inpoint (effect, 77));
+  assert_set_inpoint (effect, 77);
 
   CHECK_OBJECT_PROPS (clip, 5, 40, 20);
   CHECK_OBJECT_PROPS (child0, 5, 40, 20);
@@ -1943,13 +1932,13 @@ GST_START_TEST (test_children_max_duration)
 
     max_duration = clips[i].max_duration;
     fail_unless_equals_uint64 (_MAX_DURATION (clip), max_duration);
-    fail_unless (ges_timeline_element_set_start (clip, 5));
-    fail_unless (ges_timeline_element_set_duration (clip, 20));
-    fail_unless (ges_timeline_element_set_inpoint (clip, 30));
+    assert_set_start (clip, 5);
+    assert_set_duration (clip, 20);
+    assert_set_inpoint (clip, 30);
 
     /* can set the max duration the clip to anything whilst it has
      * no core child */
-    fail_unless (ges_timeline_element_set_max_duration (clip, 150));
+    assert_set_max_duration (clip, 150);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 150);
 
@@ -1960,31 +1949,31 @@ GST_START_TEST (test_children_max_duration)
     /* allow us to choose our own max-duration */
     ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (effect),
         TRUE);
-    fail_unless (ges_timeline_element_set_start (effect, 104));
-    fail_unless (ges_timeline_element_set_duration (effect, 53));
-    fail_unless (ges_timeline_element_set_max_duration (effect, 400));
+    assert_set_start (effect, 104);
+    assert_set_duration (effect, 53);
+    assert_set_max_duration (effect, 400);
 
     /* adding the effect will change its start and duration, but not its
      * max-duration (or in-point) */
-    fail_unless (ges_container_add (GES_CONTAINER (clip), effect));
+    _assert_add (clip, effect);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 150);
     CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
 
     /* only non-core, so can still set the max-duration */
-    fail_unless (ges_timeline_element_set_max_duration (clip, 200));
+    assert_set_max_duration (clip, 200);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 200);
     CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
 
     /* removing should not change the max-duration we set on the clip */
     gst_object_ref (effect);
-    fail_unless (ges_container_remove (GES_CONTAINER (clip), effect));
+    _assert_remove (clip, effect);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 200);
     CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
 
-    fail_unless (ges_container_add (GES_CONTAINER (clip), effect));
+    _assert_add (clip, effect);
     gst_object_unref (effect);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 200);
@@ -2022,21 +2011,21 @@ GST_START_TEST (test_children_max_duration)
 
     /* when setting max_duration of core children, clip will take the
      * minimum value */
-    fail_unless (ges_timeline_element_set_max_duration (child0, new_max - 1));
+    assert_set_max_duration (child0, new_max - 1);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 1);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max - 1);
     CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, max_duration);
     CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
 
-    fail_unless (ges_timeline_element_set_max_duration (child1, new_max - 2));
+    assert_set_max_duration (child1, new_max - 2);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max - 1);
     CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
     CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
 
-    fail_unless (ges_timeline_element_set_max_duration (child0, new_max + 1));
+    assert_set_max_duration (child0, new_max + 1);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
@@ -2092,21 +2081,21 @@ GST_START_TEST (test_children_max_duration)
     CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
 
     /* setting below new_max is ok */
-    fail_unless (ges_timeline_element_set_inpoint (child0, 15));
+    assert_set_inpoint (child0, 15);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 15, 20, new_max - 2);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 15, 20, new_max + 1);
     CHECK_OBJECT_PROPS_MAX (child1, 5, 15, 20, new_max - 2);
     CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
 
-    fail_unless (ges_timeline_element_set_inpoint (child1, 25));
+    assert_set_inpoint (child1, 25);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 25, 20, new_max - 2);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 25, 20, new_max + 1);
     CHECK_OBJECT_PROPS_MAX (child1, 5, 25, 20, new_max - 2);
     CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
 
-    fail_unless (ges_timeline_element_set_inpoint (clip, 30));
+    assert_set_inpoint (clip, 30);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
@@ -2114,7 +2103,7 @@ GST_START_TEST (test_children_max_duration)
     CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
 
     /* non-core has no effect */
-    fail_unless (ges_timeline_element_set_max_duration (effect, new_max + 500));
+    assert_set_max_duration (effect, new_max + 500);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
@@ -2123,7 +2112,7 @@ GST_START_TEST (test_children_max_duration)
 
     /* can set the in-point of non-core to be higher than the max_duration
      * of the clip */
-    fail_unless (ges_timeline_element_set_inpoint (effect, new_max + 2));
+    assert_set_inpoint (effect, new_max + 2);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
@@ -2145,14 +2134,14 @@ GST_START_TEST (test_children_max_duration)
     CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
     CHECK_OBJECT_PROPS_MAX (effect, 5, new_max + 2, 20, new_max + 500);
 
-    fail_unless (ges_timeline_element_set_inpoint (effect, 0));
+    assert_set_inpoint (effect, 0);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
     CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
     CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, new_max + 500);
 
-    fail_unless (ges_timeline_element_set_max_duration (effect, 400));
+    assert_set_max_duration (effect, 400);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
@@ -2161,7 +2150,7 @@ GST_START_TEST (test_children_max_duration)
 
     /* setting on the clip will set all the core children to the same
      * value */
-    fail_unless (ges_timeline_element_set_max_duration (clip, 180));
+    assert_set_max_duration (clip, 180);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 180);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 180);
@@ -2217,7 +2206,7 @@ GST_START_TEST (test_children_max_duration)
     CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
 
     /* can now set the max-duration, which will effect the clip */
-    fail_unless (ges_timeline_element_set_max_duration (child0, 140));
+    assert_set_max_duration (child0, 140);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 140);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140);
@@ -2232,7 +2221,7 @@ GST_START_TEST (test_children_max_duration)
     CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, GST_CLOCK_TIME_NONE);
     CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
 
-    fail_unless (ges_timeline_element_set_max_duration (child1, 130));
+    assert_set_max_duration (child1, 130);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 130);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140);
@@ -2245,7 +2234,7 @@ GST_START_TEST (test_children_max_duration)
     gst_object_ref (effect);
 
     /* removing non-core does nothing */
-    fail_unless (ges_container_remove (GES_CONTAINER (clip), effect));
+    _assert_remove (clip, effect);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 130);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140);
@@ -2253,7 +2242,7 @@ GST_START_TEST (test_children_max_duration)
     CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
 
     /* new minimum max-duration for the clip when we remove child1 */
-    fail_unless (ges_container_remove (GES_CONTAINER (clip), child1));
+    _assert_remove (clip, child1);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 140);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140);
@@ -2262,7 +2251,7 @@ GST_START_TEST (test_children_max_duration)
 
     /* with no core-children, the max-duration of the clip is set to
      * GST_CLOCK_TIME_NONE */
-    fail_unless (ges_container_remove (GES_CONTAINER (clip), child0));
+    _assert_remove (clip, child0);
 
     CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, GST_CLOCK_TIME_NONE);
     CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140);
@@ -2283,6 +2272,323 @@ GST_START_TEST (test_children_max_duration)
 
 GST_END_TEST;
 
+#define _assert_duration_limit(clip, expect) \
+  assert_equals_uint64 (ges_clip_get_duration_limit (GES_CLIP (clip)), expect)
+
+GST_START_TEST (test_duration_limit)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESClip *clip;
+  GESTrackElement *video_source, *audio_source;
+  GESTrackElement *effect1, *effect2, *effect3;
+  GESTrack *track1, *track2;
+  gint limit_notify_count = 0;
+  gint duration_notify_count = 0;
+
+  ges_init ();
+
+  timeline = ges_timeline_new ();
+  track1 = GES_TRACK (ges_video_track_new ());
+  track2 = GES_TRACK (ges_audio_track_new ());
+
+  fail_unless (ges_timeline_add_track (timeline, track1));
+  fail_unless (ges_timeline_add_track (timeline, track2));
+  /* add track3 later */
+
+  layer = ges_timeline_append_layer (timeline);
+
+  clip = GES_CLIP (ges_test_clip_new ());
+  g_signal_connect (clip, "notify::duration-limit", G_CALLBACK (_count_cb),
+      &limit_notify_count);
+  g_signal_connect (clip, "notify::duration", G_CALLBACK (_count_cb),
+      &duration_notify_count);
+
+  /* no limit to begin with */
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+
+  /* add effects */
+  effect1 = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay"));
+  ges_track_element_set_has_internal_source (effect1, TRUE);
+
+  effect2 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
+  ges_track_element_set_has_internal_source (effect2, TRUE);
+
+  effect3 = GES_TRACK_ELEMENT (ges_effect_new ("audioecho"));
+  ges_track_element_set_has_internal_source (effect3, TRUE);
+
+  _assert_add (clip, effect1);
+  _assert_add (clip, effect2);
+  _assert_add (clip, effect3);
+  assert_num_children (clip, 3);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  assert_equals_int (limit_notify_count, 0);
+  assert_equals_int (duration_notify_count, 0);
+
+  /* no change in duration limit whilst children are not in any track */
+  assert_set_max_duration (effect1, 20);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  assert_equals_int (limit_notify_count, 0);
+  assert_equals_int (duration_notify_count, 0);
+
+  assert_set_inpoint (effect1, 5);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  assert_equals_int (limit_notify_count, 0);
+  assert_equals_int (duration_notify_count, 0);
+
+  /* set a duration that will be above the duration-limit */
+  assert_set_duration (clip, 20);
+  assert_equals_int (duration_notify_count, 1);
+
+  /* add to layer to create sources */
+  fail_unless (ges_layer_add_clip (layer, clip));
+
+  /* duration-limit changes once because of effect1 */
+  _assert_duration_limit (clip, 15);
+  assert_equals_int (limit_notify_count, 1);
+  assert_equals_int (duration_notify_count, 2);
+  /* duration has automatically been set to the duration-limit */
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 0, 15, GST_CLOCK_TIME_NONE);
+
+  assert_num_children (clip, 5);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 2);
+
+  video_source = ges_clip_find_track_element (clip, track1, GES_TYPE_SOURCE);
+  fail_unless (video_source);
+  gst_object_unref (video_source);
+
+  audio_source = ges_clip_find_track_element (clip, track2, GES_TYPE_SOURCE);
+  fail_unless (audio_source);
+  gst_object_unref (audio_source);
+
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  fail_unless (ges_track_element_get_track (video_source) == track1);
+  fail_unless (ges_track_element_get_track_type (video_source) ==
+      GES_TRACK_TYPE_VIDEO);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  fail_unless (ges_track_element_get_track (audio_source) == track2);
+  fail_unless (ges_track_element_get_track_type (audio_source) ==
+      GES_TRACK_TYPE_AUDIO);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  fail_unless (ges_track_element_get_track (effect1) == track1);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  fail_unless (ges_track_element_get_track (effect2) == track1);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  fail_unless (ges_track_element_get_track (effect3) == track2);
+
+  /* Make effect1 inactive, which will remove the duration-limit */
+  fail_unless (ges_track_element_set_active (effect1, FALSE));
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  assert_equals_int (limit_notify_count, 2);
+  /* duration is unchanged */
+  assert_equals_int (duration_notify_count, 2);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE);
+
+  /* changing the in-point does not change the duration limit whilst
+   * there is no max-duration */
+  assert_set_inpoint (clip, 10);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  assert_equals_int (limit_notify_count, 2);
+  assert_equals_int (duration_notify_count, 2);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 10, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 10, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 10, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE);
+
+  assert_set_max_duration (video_source, 40);
+  _assert_duration_limit (clip, 30);
+  assert_equals_int (limit_notify_count, 3);
+  assert_equals_int (duration_notify_count, 2);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 10, 15, 40);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 10, 15, 40);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 10, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE);
+
+  /* set in-point using child */
+  assert_set_inpoint (audio_source, 15);
+  _assert_duration_limit (clip, 25);
+  assert_equals_int (limit_notify_count, 4);
+  assert_equals_int (duration_notify_count, 2);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 40);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 40);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE);
+
+  /* set max-duration of core children */
+  assert_set_max_duration (clip, 60);
+  _assert_duration_limit (clip, 45);
+  assert_equals_int (limit_notify_count, 5);
+  assert_equals_int (duration_notify_count, 2);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE);
+
+  /* can set duration up to the limit */
+  assert_set_duration (clip, 45);
+  _assert_duration_limit (clip, 45);
+  assert_equals_int (limit_notify_count, 5);
+  assert_equals_int (duration_notify_count, 3);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 45, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 45, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 45, 60);
+  /* effect1 has a duration that exceeds max-duration - in-point
+   * ok since it is currently inactive */
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 45, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 45, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 45, GST_CLOCK_TIME_NONE);
+
+  /* limit the effects */
+  assert_set_max_duration (effect2, 70);
+  _assert_duration_limit (clip, 45);
+  assert_equals_int (limit_notify_count, 5);
+  assert_equals_int (duration_notify_count, 3);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 45, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 45, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 45, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 45, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 45, 70);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 45, GST_CLOCK_TIME_NONE);
+
+  assert_set_inpoint (effect2, 40);
+  _assert_duration_limit (clip, 30);
+  assert_equals_int (limit_notify_count, 6);
+  assert_equals_int (duration_notify_count, 4);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 30, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 30, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 30, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 30, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 30, 70);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 30, GST_CLOCK_TIME_NONE);
+
+  /* no change */
+  assert_set_max_duration (effect3, 35);
+  _assert_duration_limit (clip, 30);
+  assert_equals_int (limit_notify_count, 6);
+  assert_equals_int (duration_notify_count, 4);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 30, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 30, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 30, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 30, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 30, 70);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 30, 35);
+
+  /* make effect1 active again */
+  fail_unless (ges_track_element_set_active (effect1, TRUE));
+  _assert_duration_limit (clip, 15);
+  assert_equals_int (limit_notify_count, 7);
+  assert_equals_int (duration_notify_count, 5);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35);
+
+  /* removing effect2 from track does not change limit */
+  fail_unless (ges_track_remove_element (track1, effect2));
+  _assert_duration_limit (clip, 15);
+  assert_equals_int (limit_notify_count, 7);
+  assert_equals_int (duration_notify_count, 5);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35);
+  /* no track */
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70);
+
+  /* removing effect1 does */
+  fail_unless (ges_track_remove_element (track1, effect1));
+  _assert_duration_limit (clip, 35);
+  assert_equals_int (limit_notify_count, 8);
+  assert_equals_int (duration_notify_count, 5);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35);
+  /* no track */
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70);
+
+  /* add back */
+  fail_unless (ges_track_add_element (track1, effect2));
+  _assert_duration_limit (clip, 30);
+  assert_equals_int (limit_notify_count, 9);
+  assert_equals_int (duration_notify_count, 5);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70);
+  /* no track */
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+
+  assert_set_duration (clip, 20);
+  _assert_duration_limit (clip, 30);
+  assert_equals_int (limit_notify_count, 9);
+  assert_equals_int (duration_notify_count, 6);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 20, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 20, 60);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 20, 35);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 20, 60);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 20, 70);
+  /* no track */
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 20, 20);
+
+  fail_unless (ges_track_add_element (track1, effect1));
+  _assert_duration_limit (clip, 15);
+  assert_equals_int (limit_notify_count, 10);
+  assert_equals_int (duration_notify_count, 7);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70);
+
+  gst_object_ref (clip);
+  fail_unless (ges_layer_remove_clip (layer, clip));
+
+  assert_num_in_track (track1, 0);
+  assert_num_in_track (track2, 0);
+  assert_num_children (clip, 5);
+
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  /* may have several changes in duration limit as the children are
+   * emptied from their tracks */
+  fail_unless (limit_notify_count > 10);
+  assert_equals_int (duration_notify_count, 7);
+  /* none in any track */
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70);
+
+  gst_object_unref (timeline);
+  gst_object_unref (clip);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
 GST_START_TEST (test_children_properties_contain)
 {
   GESTimeline *timeline;
@@ -2297,7 +2603,7 @@ GST_START_TEST (test_children_properties_contain)
   timeline = ges_timeline_new_audio_video ();
   layer = ges_timeline_append_layer (timeline);
   clip = GES_CLIP (ges_test_clip_new ());
-  ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip), 50);
+  assert_set_duration (clip, 50);
 
   fail_unless (ges_layer_add_clip (layer, clip));
 
@@ -2404,7 +2710,7 @@ GST_START_TEST (test_children_properties_change)
   timeline = ges_timeline_new_audio_video ();
   layer = ges_timeline_append_layer (timeline);
   clip = GES_TIMELINE_ELEMENT (ges_test_clip_new ());
-  ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip), 50);
+  assert_set_duration (clip, 50);
 
   fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip)));
   fail_unless (GES_CONTAINER_CHILDREN (clip));
@@ -2678,7 +2984,7 @@ GST_START_TEST (test_copy_paste_children_properties)
   timeline = ges_timeline_new_audio_video ();
   layer = ges_timeline_append_layer (timeline);
   clip = GES_TIMELINE_ELEMENT (ges_test_clip_new ());
-  ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip), 50);
+  assert_set_duration (clip, 50);
 
   fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip)));
 
@@ -2792,6 +3098,7 @@ ges_suite (void)
   tcase_add_test (tc_chain, test_can_add_effect);
   tcase_add_test (tc_chain, test_children_inpoint);
   tcase_add_test (tc_chain, test_children_max_duration);
+  tcase_add_test (tc_chain, test_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);
index 8208361e28d5c586b33512fc7344bc88fe8da0f2..27885b69c7bf2eaff9846678924e52e8158d514a 100644 (file)
@@ -106,6 +106,26 @@ G_STMT_START {                                          \
       GST_TIME_ARGS (_MAX_DURATION(obj)), GST_TIME_ARGS (max_duration)); \
 }
 
+#define assert_set_start(obj, start) \
+  fail_unless (ges_timeline_element_set_start (\
+        GES_TIMELINE_ELEMENT (obj), start), \
+        "Could not set the start of " #obj " to " #start)
+
+#define assert_set_duration(obj, duration) \
+  fail_unless (ges_timeline_element_set_duration (\
+        GES_TIMELINE_ELEMENT (obj), duration), \
+        "Could not set the duration of " #obj " to " #duration)
+
+#define assert_set_inpoint(obj, inpoint) \
+  fail_unless (ges_timeline_element_set_inpoint (\
+        GES_TIMELINE_ELEMENT (obj), inpoint), \
+        "Could not set the in-point of " #obj " to " #inpoint)
+
+#define assert_set_max_duration(obj, max_duration) \
+  fail_unless (ges_timeline_element_set_max_duration (\
+        GES_TIMELINE_ELEMENT (obj), max_duration), \
+        "Could not set the max-duration of " #obj " to " #max_duration)
+
 #define assert_num_in_track(track, val) \
 { \
   GList *tmp = ges_track_get_elements (track); \