clip: only allow core elements as children
authorHenry Wilkes <hwilkes@igalia.com>
Mon, 2 Mar 2020 12:56:03 +0000 (12:56 +0000)
committerHenry Wilkes <hwilkes@igalia.com>
Mon, 16 Mar 2020 14:19:51 +0000 (14:19 +0000)
Only allow elements that were created by ges_clip_create_track_elements
(or copied from such an element) to be added to a clip. This prevents
users from adding arbitrary elements to a clip.

As an exception, a user can add GESBaseEffects to clips whose class
supports it, i.e. to a GESSourceClip and a GESBaseEffectClip.

This change also introduces a distinction between the core elements of a
clip (created by ges_clip_create_track_elements) and non-core elements
(currently, only GESBaseEffects, for some classes). In particular,
GESBaseEffectClip will now distinguish between its core elements and
effects added by the user. This means that the core elements will always
have the lowest priority, and will not be listed as top effects. This is
desirable because it brings the behaviour of GESBaseEffectClip in line
with other clip types.

12 files changed:
ges/ges-base-effect-clip.c
ges/ges-clip.c
ges/ges-clip.h
ges/ges-container.c
ges/ges-internal.h
ges/ges-source-clip.c
ges/ges-timeline-element.c
ges/ges-timeline.c
ges/ges-track-element.c
tests/check/ges/clip.c
tests/check/ges/effects.c
tests/check/ges/test-utils.h

index 4cf1e65..118832c 100644 (file)
 /**
  * SECTION: gesbaseeffectclip
  * @title: GESBaseEffectClip
- * @short_description: An effect in a GESLayer
+ * @short_description: An effect in a #GESLayer
  *
- * The effect will be applied on the sources that have lower priorities
- * (higher number) between the inpoint and the end of it.
+ * #GESBaseEffectClip-s are clips whose core elements are
+ * #GESBaseEffect-s.
+ *
+ * ## Effects
+ *
+ * #GESBaseEffectClip-s can have **additional** #GESBaseEffect-s added as
+ * 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.
  */
+
+/* FIXME: properly handle the priority of the children. How should we sort
+ * the priority of effects when two #GESBaseEffectClip's overlap? */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -44,6 +54,7 @@ G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESBaseEffectClip, ges_base_effect_clip,
 static void
 ges_base_effect_clip_class_init (GESBaseEffectClipClass * klass)
 {
+  GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) = TRUE;
 }
 
 static void
index faaf997..fab8f4b 100644 (file)
  *
  * ## Effects
  *
- * For #GESSourceClip-s, you may wish to add additional track elements to
- * the clip in the form of #GESEffect-s. These can be added to the source
- * clip using ges_container_add(), which will take care of adding the
- * effect to the timeline's tracks. The new effect will be placed between
- * the clip's core track elements and its other effects. As such, the
- * newly added effect will be applied to any source data **before** the
- * other existing effects. You can change the ordering of effects using
+ * Some subclasses (#GESSourceClip and #GESBaseEffect) may also allow
+ * their objects to have additional non-core #GESBaseEffect-s elements as
+ * children. These are additional effects that are applied to the output
+ * data of the core elements. They can be added to the clip using
+ * ges_container_add(), which will take care of adding the effect to the
+ * timeline's tracks. The new effect will be placed between the clip's
+ * core track elements and its other effects. As such, the newly added
+ * effect will be applied to any source data **before** the other existing
+ * effects. You can change the ordering of effects using
  * ges_clip_set_top_effect_index().
  */
 #ifdef HAVE_CONFIG_H
@@ -332,54 +334,76 @@ _compute_height (GESContainer * container)
 static gboolean
 _add_child (GESContainer * container, GESTimelineElement * element)
 {
-  GList *tmp;
+  GESClipClass *klass = GES_CLIP_GET_CLASS (GES_CLIP (container));
   guint max_prio, min_prio;
+  GESChildrenControlMode mode = container->children_control_mode;
   GESClipPrivate *priv = GES_CLIP (container)->priv;
 
   g_return_val_if_fail (GES_IS_TRACK_ELEMENT (element), FALSE);
 
   /* First make sure we work with a sorted list of GESTimelineElement-s */
   _ges_container_sort_children (container);
-
-  /* If the TrackElement is an effect:
-   *  - We add it on top of the list of TrackEffect
-   *  - We put all TrackElements present in the Clip
-   *    which are not BaseEffect on top of them
-   * FIXME: Let the full control over priorities to the user
-   */
   _get_priority_range (container, &min_prio, &max_prio);
-  if (GES_IS_BASE_EFFECT (element)) {
-    GESChildrenControlMode mode = container->children_control_mode;
+  if (ELEMENT_FLAG_IS_SET (element, GES_TRACK_ELEMENT_IS_CORE)) {
+    /* NOTE: we are assuming that the core element is **our** core element
+     * and not from another clip. */
+    /* NOTE: Core track elements that are base effects are added like any
+     * other core clip. In particular, they are *not* added to the list of
+     * added effects, so we don not increase nb_effects. */
+
+    /* Always add at the same priority, on top of existing effects */
+    _set_priority0 (element, min_prio + priv->nb_effects);
 
-    GST_DEBUG_OBJECT (container, "Adding %ith effect: %" GST_PTR_FORMAT
-        " Priority %i", priv->nb_effects + 1, element,
+    /* Set the core element to have the same in-point, which we don't
+     * apply to effects */
+    container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
+    _set_inpoint0 (element, GES_TIMELINE_ELEMENT_INPOINT (container));
+  } else if (GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass)
+      && GES_IS_BASE_EFFECT (element)) {
+    GList *tmp;
+    /* Add the effect at the lowest priority among effects (just after
+     * the core elements). Need to shift the core elements up by 1
+     * to make room. */
+    GST_DEBUG_OBJECT (container, "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 */
     tmp = g_list_nth (GES_CONTAINER_CHILDREN (container), priv->nb_effects);
-    container->children_control_mode = GES_CHILDREN_UPDATE_OFFSETS;
-    for (; tmp; tmp = tmp->next) {
+    for (; tmp; tmp = tmp->next)
       ges_timeline_element_set_priority (GES_TIMELINE_ELEMENT (tmp->data),
           GES_TIMELINE_ELEMENT_PRIORITY (tmp->data) + 1);
-    }
 
     _set_priority0 (element, min_prio + priv->nb_effects);
-    container->children_control_mode = mode;
     priv->nb_effects++;
+    /* 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);
   } else {
-    /* We add the track element on top of the effect list */
-    _set_priority0 (element, min_prio + priv->nb_effects);
+    if (GES_IS_BASE_EFFECT (element))
+      GST_WARNING_OBJECT (container, "Can not 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 the adding extra effects",
+          GES_ARGS (element), G_OBJECT_CLASS_NAME (klass));
+    else if (GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass))
+      GST_WARNING_OBJECT (container, "Can not 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, "Can not add the track element %"
+          GES_FORMAT " because it is not a core element created by the "
+          "clip itself", GES_ARGS (element));
+    return FALSE;
   }
 
   /* We set the timing value of the child to ours, we avoid infinite loop
    * making sure the container ignore notifies from the child */
   container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
   _set_start0 (element, GES_TIMELINE_ELEMENT_START (container));
-  /* FIXME: we should allow the inpoint to be different for children
-   * that are *not* the core track elements of the clip. E.g. if we
-   * add a GESEffect to a clip, we should not be editing its inpoint */
-  _set_inpoint0 (element, GES_TIMELINE_ELEMENT_INPOINT (container));
   _set_duration0 (element, GES_TIMELINE_ELEMENT_DURATION (container));
-  container->children_control_mode = GES_CHILDREN_UPDATE;
+  container->children_control_mode = mode;
 
   return TRUE;
 }
@@ -387,18 +411,26 @@ _add_child (GESContainer * container, GESTimelineElement * element)
 static gboolean
 _remove_child (GESContainer * container, GESTimelineElement * element)
 {
-  if (GES_IS_BASE_EFFECT (element)) {
+  if (!ELEMENT_FLAG_IS_SET (element, GES_TRACK_ELEMENT_IS_CORE)
+      && GES_IS_BASE_EFFECT (element)) {
     GES_CLIP (container)->priv->nb_effects--;
   }
 
   GST_FIXME_OBJECT (container, "We should set other children prios");
 
+  if (ELEMENT_FLAG_IS_SET (element, GES_TRACK_ELEMENT_IS_CORE)) {
+    /* When removed, the child is no longer considered a core element.
+     * This prevents the user from being able to add the removed track
+     * element to an arbitrary clip */
+    ELEMENT_UNSET_FLAG (element, GES_TRACK_ELEMENT_IS_CORE);
+  }
   return TRUE;
 }
 
 static void
 _child_added (GESContainer * container, GESTimelineElement * element)
 {
+  /* FIXME: adjust the max-duration according to the new child */
   g_signal_connect (G_OBJECT (element), "notify::priority",
       G_CALLBACK (_child_priority_changed_cb), container);
 
@@ -412,7 +444,8 @@ _child_removed (GESContainer * container, GESTimelineElement * element)
   g_signal_handlers_disconnect_by_func (element, _child_priority_changed_cb,
       container);
 
-  if (GES_IS_BASE_EFFECT (element)) {
+  if (!ELEMENT_FLAG_IS_SET (element, GES_TRACK_ELEMENT_IS_CORE)
+      && GES_IS_BASE_EFFECT (element)) {
     GList *tmp;
     guint32 priority;
     GESChildrenControlMode mode = container->children_control_mode;
@@ -443,6 +476,23 @@ add_clip_to_list (gpointer key, gpointer clip, GList ** list)
   *list = g_list_prepend (*list, gst_object_ref (clip));
 }
 
+static void
+_transfer_child (GESClip * from_clip, GESClip * to_clip,
+    GESTrackElement * child)
+{
+  gboolean is_core;
+  /* We need to bump the refcount to avoid the object to be destroyed */
+  gst_object_ref (child);
+  /* IS_CORE flag is removed when an element is removed from its clip */
+  is_core = ELEMENT_FLAG_IS_SET (child, GES_TRACK_ELEMENT_IS_CORE);
+  ges_container_remove (GES_CONTAINER (from_clip),
+      GES_TIMELINE_ELEMENT (child));
+  if (is_core)
+    ELEMENT_SET_FLAG (child, GES_TRACK_ELEMENT_IS_CORE);
+  ges_container_add (GES_CONTAINER (to_clip), GES_TIMELINE_ELEMENT (child));
+  gst_object_unref (child);
+}
+
 static GList *
 _ungroup (GESContainer * container, gboolean recursive)
 {
@@ -489,14 +539,8 @@ _ungroup (GESContainer * container, gboolean recursive)
     }
 
     /* Move trackelement to the container it is supposed to land into */
-    if (tmpclip != clip) {
-      /* We need to bump the refcount to avoid the object to be destroyed */
-      gst_object_ref (track_element);
-      ges_container_remove (container, GES_TIMELINE_ELEMENT (track_element));
-      ges_container_add (GES_CONTAINER (tmpclip),
-          GES_TIMELINE_ELEMENT (track_element));
-      gst_object_unref (track_element);
-    }
+    if (tmpclip != clip)
+      _transfer_child (clip, tmpclip, track_element);
   }
   g_list_free_full (children, gst_object_unref);
   g_hash_table_foreach (_tracktype_clip, (GHFunc) add_clip_to_list, &ret);
@@ -636,13 +680,9 @@ _group (GList * containers)
     GList *children = ges_container_get_children (GES_CONTAINER (cclip), FALSE);
 
     for (tmpelement = children; tmpelement; tmpelement = tmpelement->next) {
-      GESTimelineElement *celement = GES_TIMELINE_ELEMENT (tmpelement->data);
-
-      ges_container_remove (GES_CONTAINER (cclip), celement);
-      ges_container_add (ret, celement);
-
-      supported_formats = supported_formats |
-          ges_track_element_get_track_type (GES_TRACK_ELEMENT (celement));
+      GESTrackElement *celement = GES_TRACK_ELEMENT (tmpelement->data);
+      _transfer_child (cclip, GES_CLIP (ret), celement);
+      supported_formats |= ges_track_element_get_track_type (celement);
     }
     g_list_free_full (children, gst_object_unref);
 
@@ -665,14 +705,16 @@ _deep_copy (GESTimelineElement * element, GESTimelineElement * copy)
 {
   GList *tmp;
   GESClip *self = GES_CLIP (element), *ccopy = GES_CLIP (copy);
+  GESTrackElement *el, *el_copy;
 
   if (!self->priv->layer)
     return;
 
   for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
+    el = GES_TRACK_ELEMENT (tmp->data);
+    el_copy = ges_track_element_copy_core (el, TRUE);
     ccopy->priv->copied_track_elements =
-        g_list_append (ccopy->priv->copied_track_elements,
-        ges_timeline_element_copy (tmp->data, TRUE));
+        g_list_append (ccopy->priv->copied_track_elements, el_copy);
   }
 
   ccopy->priv->copied_layer = g_object_ref (self->priv->layer);
@@ -704,9 +746,11 @@ _paste (GESTimelineElement * element, GESTimelineElement * ref,
     GESTrackElement *new_trackelement, *trackelement =
         GES_TRACK_ELEMENT (tmp->data);
 
+    /* NOTE: we do not deep copy the track element, we instead call
+     * ges_track_element_copy_properties explicitly, which is the
+     * deep_copy for the GESTrackElementClass. */
     new_trackelement =
-        GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT
-            (trackelement), FALSE));
+        GES_TRACK_ELEMENT (ges_track_element_copy_core (trackelement, FALSE));
     if (new_trackelement == NULL) {
       GST_WARNING_OBJECT (trackelement, "Could not create a copy");
       continue;
@@ -930,7 +974,6 @@ ges_clip_create_track_elements (GESClip * clip, GESTrackType type)
 {
   GList *result = NULL, *tmp, *children;
   GESClipClass *klass;
-  guint max_prio, min_prio;
   gboolean readding_effects_only = TRUE;
 
   g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
@@ -950,12 +993,16 @@ ges_clip_create_track_elements (GESClip * clip, GESTrackType type)
 
     if (!ges_track_element_get_track (child)
         && ges_track_element_get_track_type (child) & type) {
+      gboolean is_core = ELEMENT_FLAG_IS_SET (child, GES_TRACK_ELEMENT_IS_CORE);
 
       GST_DEBUG_OBJECT (clip, "Removing for reusage: %" GST_PTR_FORMAT, child);
       result = g_list_append (result, g_object_ref (child));
       ges_container_remove (GES_CONTAINER (clip), tmp->data);
-      if (!GES_IS_BASE_EFFECT (child))
+      if (is_core) {
+        /* set on child since the flag is removed when removed from the clip */
+        ELEMENT_SET_FLAG (child, GES_TRACK_ELEMENT_IS_CORE);
         readding_effects_only = FALSE;
+      }
     }
   }
   g_list_free_full (children, gst_object_unref);
@@ -991,26 +1038,22 @@ ges_clip_create_track_elements (GESClip * clip, GESTrackType type)
    * recreated.
    */
   if (readding_effects_only) {
-    result = g_list_concat (result, klass->create_track_elements (clip, type));
+    GList *track_elements = klass->create_track_elements (clip, type);
+    for (tmp = track_elements; tmp; tmp = tmp->next) {
+      GESTimelineElement *elem = tmp->data;
+      ELEMENT_SET_FLAG (elem, GES_TRACK_ELEMENT_IS_CORE);
+
+      /* FIXME: we should set the max duration of the clip based on the
+       * max duration of the child. Not the other way around. */
+      if (GST_CLOCK_TIME_IS_VALID (GES_TIMELINE_ELEMENT_MAX_DURATION (clip)))
+        ges_timeline_element_set_max_duration (GES_TIMELINE_ELEMENT (elem),
+            GES_TIMELINE_ELEMENT_MAX_DURATION (clip));
+    }
+    result = g_list_concat (track_elements, result);
   }
 
-  _get_priority_range (GES_CONTAINER (clip), &min_prio, &max_prio);
   for (tmp = result; tmp; tmp = tmp->next) {
-    GESTimelineElement *elem = tmp->data;
-
-    _set_start0 (elem, GES_TIMELINE_ELEMENT_START (clip));
-    /* FIXME: only set the in-point and max-duration for non-core
-     * elements */
-    _set_inpoint0 (elem, GES_TIMELINE_ELEMENT_INPOINT (clip));
-    _set_duration0 (elem, GES_TIMELINE_ELEMENT_DURATION (clip));
-
-    if (GST_CLOCK_TIME_IS_VALID (GES_TIMELINE_ELEMENT_MAX_DURATION (clip)))
-      ges_timeline_element_set_max_duration (GES_TIMELINE_ELEMENT (elem),
-          GES_TIMELINE_ELEMENT_MAX_DURATION (clip));
-
-    _set_priority0 (elem, min_prio + clip->priv->nb_effects);
-
-    ges_container_add (GES_CONTAINER (clip), elem);
+    ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (tmp->data));
   }
 
   return result;
@@ -1241,21 +1284,40 @@ GList *
 ges_clip_get_top_effects (GESClip * clip)
 {
   GList *tmp, *ret;
-  guint i;
+  GESTimelineElement *child;
 
   g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
 
   GST_DEBUG_OBJECT (clip, "Getting the %i top effects", clip->priv->nb_effects);
   ret = NULL;
 
-  for (tmp = GES_CONTAINER_CHILDREN (clip), i = 0;
-      i < clip->priv->nb_effects; tmp = tmp->next, i++) {
-    ret = g_list_append (ret, gst_object_ref (tmp->data));
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    child = tmp->data;
+    if (GES_IS_BASE_EFFECT (child)
+        && !ELEMENT_FLAG_IS_SET (child, GES_TRACK_ELEMENT_IS_CORE))
+      ret = g_list_append (ret, gst_object_ref (child));
   }
 
   return g_list_sort (ret, (GCompareFunc) element_start_compare);
 }
 
+static gboolean
+_is_added_effect (GESClip * clip, GESBaseEffect * effect)
+{
+  if (GES_TIMELINE_ELEMENT_PARENT (effect) != GES_TIMELINE_ELEMENT (clip)) {
+    GST_WARNING_OBJECT (clip, "The effect %" GES_FORMAT
+        " doe not belong to this clip", GES_ARGS (effect));
+    return FALSE;
+  }
+  if (ELEMENT_FLAG_IS_SET (effect, GES_TRACK_ELEMENT_IS_CORE)) {
+    GST_WARNING_OBJECT (clip, "The effect %" GES_FORMAT " is not a top "
+        "effect of this clip (it is a core element of the clip)",
+        GES_ARGS (effect));
+    return FALSE;
+  }
+  return TRUE;
+}
+
 /**
  * ges_clip_get_top_effect_index:
  * @clip: A #GESClip
@@ -1277,6 +1339,8 @@ ges_clip_get_top_effect_index (GESClip * clip, GESBaseEffect * effect)
 
   g_return_val_if_fail (GES_IS_CLIP (clip), -1);
   g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), -1);
+  if (!_is_added_effect (clip, effect))
+    return -1;
 
   _get_priority_range (GES_CONTAINER (clip), &min_prio, &max_prio);
 
@@ -1322,12 +1386,12 @@ ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect,
   GESTrackElement *track_element;
 
   g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
+  g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE);
 
-  track_element = GES_TRACK_ELEMENT (effect);
-  if (G_UNLIKELY (GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (track_element)) !=
-          clip))
+  if (!_is_added_effect (clip, effect))
     return FALSE;
 
+  track_element = GES_TRACK_ELEMENT (effect);
   current_prio = _PRIORITY (track_element);
 
   _get_priority_range (GES_CONTAINER (clip), &min_prio, &max_prio);
@@ -1472,29 +1536,6 @@ ges_clip_split (GESClip * clip, guint64 position)
       inpoint + old_duration * media_duration_factor);
   _set_duration0 (GES_TIMELINE_ELEMENT (new_object), new_duration);
 
-  /* FIXME: this does not check that the new duration of the clip does
-   * not break the allowed configurations of a timeline!
-   * E.g., consider a layer with two clips:
-   *
-   * [  clip0            ]
-   *         [            clip1  ]
-   * 0   1   2   3   4   5   6   7  - timeline time
-   *
-   * If we split clip1 at time 4 then we would have
-   *
-   * [  clip0            ]
-   *         [ clip1 ]
-   *                 [   new-clip]
-   * 0   1   2   3   4   5   6   7  - timeline time
-   *
-   * This means that clip1 is entirely covered by clip0! This is allowed
-   * to occur using ges_clip_split()!
-   *
-   * Note this early setting of the duration does not perform such a
-   * check. Moreover, the later setting of the duration at the end of this
-   * function also does not perform this check because we set the
-   * GES_TIMELINE_ELEMENT_SET_SIMPLE flag!
-   */
   _DURATION (clip) = old_duration;
   g_object_notify (G_OBJECT (clip), "duration");
 
@@ -1507,25 +1548,12 @@ ges_clip_split (GESClip * clip, guint64 position)
     GESTrackElement *new_trackelement, *trackelement =
         GES_TRACK_ELEMENT (tmp->data);
 
-    new_trackelement =
-        GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT
-            (trackelement), FALSE));
+    new_trackelement = ges_track_element_copy_core (trackelement, FALSE);
     if (new_trackelement == NULL) {
       GST_WARNING_OBJECT (trackelement, "Could not create a copy");
       continue;
     }
 
-    /* Set 'new' track element timing propeties */
-    _set_start0 (GES_TIMELINE_ELEMENT (new_trackelement), position);
-    /* FIXME: not all track elements should have their inpoint set,
-     * e.g. transitions or most effects. But how can we determine
-     * whether a transition should *always* have a 0 inpoint, or
-     * whether a transition actually uses the inpoint, but it just so
-     * happens to be set to 0? */
-    _set_inpoint0 (GES_TIMELINE_ELEMENT (new_trackelement),
-        inpoint + old_duration * media_duration_factor);
-    _set_duration0 (GES_TIMELINE_ELEMENT (new_trackelement), new_duration);
-
     ges_container_add (GES_CONTAINER (new_object),
         GES_TIMELINE_ELEMENT (new_trackelement));
     ges_track_element_copy_properties (GES_TIMELINE_ELEMENT (trackelement),
index 700ece9..8051fb5 100644 (file)
@@ -40,6 +40,15 @@ G_BEGIN_DECLS
 typedef struct _GESClipPrivate GESClipPrivate;
 
 /**
+ * GES_CLIP_CLASS_CAN_ADD_EFFECTS:
+ * @klass: A #GESClipClass
+ *
+ * Whether the class allows for the user to add additional non-core
+ * #GESBaseEffect-s to clips from this class.
+ */
+#define GES_CLIP_CLASS_CAN_ADD_EFFECTS(klass) ((GES_CLIP_CLASS (klass))->ABI.abi.can_add_effects)
+
+/**
  * GESFillTrackElementFunc:
  * @clip: The #GESClip controlling the track elements
  * @track_element: The #GESTrackElement
@@ -114,6 +123,9 @@ struct _GESClip
  * @create_track_elements: Method to create the (multiple) core
  * #GESTrackElement-s of a clip of this class. If create_track_element()
  * is implemented, this should be kept as the default implementation
+ * @can_add_effects: Whether the user can add additional non-core
+ * #GESBaseEffect-s to clips from this class, to be applied to the output
+ * data of the core elements.
  */
 struct _GESClipClass
 {
@@ -126,7 +138,12 @@ struct _GESClipClass
 
   /*< private >*/
   /* Padding for API extension */
-  gpointer _ges_reserved[GES_PADDING_LARGE];
+  union {
+    gpointer _ges_reserved[GES_PADDING_LARGE];
+    struct {
+      gboolean can_add_effects;
+    } abi;
+  } ABI;
 };
 
 /****************************************************
index 0c42ce8..41c548d 100644 (file)
@@ -755,7 +755,7 @@ ges_container_add (GESContainer * container, GESTimelineElement * child)
   if (class->add_child) {
     if (class->add_child (container, child) == FALSE) {
       container->children_control_mode = GES_CHILDREN_UPDATE;
-      GST_WARNING_OBJECT (container, "Erreur adding child %p", child);
+      GST_WARNING_OBJECT (container, "Error adding child %p", child);
       return FALSE;
     }
   }
index c22ba81..e3b08ec 100644 (file)
@@ -410,8 +410,10 @@ G_GNUC_INTERNAL void ges_track_element_copy_properties          (GESTimelineElem
                                                                  GESTimelineElement * elementcopy);
 
 G_GNUC_INTERNAL void ges_track_element_copy_bindings (GESTrackElement *element,
-                                                      GESTrackElement *new_element,
-                                                      guint64 position);
+                                                      GESTrackElement *new_element,
+                                                      guint64 position);
+G_GNUC_INTERNAL GESTrackElement * ges_track_element_copy_core   (GESTrackElement * self,
+                                                                 gboolean deep);
 
 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,
@@ -436,6 +438,7 @@ typedef enum
 {
   GES_CLIP_IS_MOVING = (1 << 0),
   GES_TIMELINE_ELEMENT_SET_SIMPLE = (1 << 1),
+  GES_TRACK_ELEMENT_IS_CORE = (1 << 2),
 } GESTimelineElementFlags;
 
 G_GNUC_INTERNAL gdouble ges_timeline_element_get_media_duration_factor(GESTimelineElement *self);
index 21c9f95..322072f 100644 (file)
 /**
  * SECTION:gessourceclip
  * @title: GESSourceClip
- * @short_description: Base Class for sources of a GESLayer
+ * @short_description: Base Class for sources of a #GESLayer
+ *
+ * #GESSourceClip-s are clips whose core elements are #GESSource-s.
+ *
+ * ## Effects
+ *
+ * #GESSourceClip-s can also have #GESBaseEffect-s added as non-core
+ * elements. These effects are applied to the core sources of the clip
+ * that they share a #GESTrack with. See #GESClip for how to add and move
+ * these effects from the clip.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -80,6 +89,8 @@ ges_source_clip_class_init (GESSourceClipClass * klass)
   object_class->get_property = ges_source_clip_get_property;
   object_class->set_property = ges_source_clip_set_property;
   object_class->finalize = ges_source_clip_finalize;
+
+  GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) = TRUE;
 }
 
 static void
index b4c89d9..b1a3c37 100644 (file)
@@ -1543,7 +1543,7 @@ ges_timeline_element_copy (GESTimelineElement * self, gboolean deep)
 
   GESTimelineElement *ret = NULL;
 
-  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
+  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL);
 
   klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
 
index f8cb45c..6d07b09 100644 (file)
@@ -1433,9 +1433,7 @@ clip_track_element_added_cb (GESClip * clip,
     /* FIXME: in both cases track_element is removed from the clip! */
     g_clear_object (&existing_src);
 
-    track_element_copy =
-        GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT
-            (track_element), TRUE));
+    track_element_copy = ges_track_element_copy_core (track_element, TRUE);
 
     GST_LOG_OBJECT (timeline, "Trying to add %p to track %p",
         track_element_copy, track);
index dd62c5b..3d1d2ae 100644 (file)
@@ -1250,6 +1250,19 @@ ges_track_element_copy_properties (GESTimelineElement * element,
   g_free (specs);
 }
 
+/* copy an element, as well as its GES_TRACK_ELEMENT_IS_CORE flag */
+GESTrackElement *
+ges_track_element_copy_core (GESTrackElement * self, gboolean deep)
+{
+  GESTimelineElement *copy =
+      ges_timeline_element_copy (GES_TIMELINE_ELEMENT (self), deep);
+  if (!copy)
+    return NULL;
+  if (ELEMENT_FLAG_IS_SET (self, GES_TRACK_ELEMENT_IS_CORE))
+    ELEMENT_SET_FLAG (copy, GES_TRACK_ELEMENT_IS_CORE);
+  return GES_TRACK_ELEMENT (copy);
+}
+
 static void
 _split_binding (GESTrackElement * element, GESTrackElement * new_element,
     guint64 position, GstTimedValueControlSource * source,
index 238671d..d84ba63 100644 (file)
 #include <ges/ges.h>
 #include <gst/check/gstcheck.h>
 
-void count_cb (GObject * obj, GParamSpec * pspec, gint * count);
-void test_children_time_val (GESClip * clip, const gchar * prop,
-    GstClockTime val);
-void test_children_time_setter (GESClip * clip, GESTimelineElement * child,
-    const gchar * prop, gboolean (*setter) (GESTimelineElement *, GstClockTime),
-    GstClockTime val1, GstClockTime val2);
-void test_children_time_setting_on_clip (GESClip * clip,
-    GESTimelineElement * child);
-
 GST_START_TEST (test_object_properties)
 {
   GESClip *clip;
@@ -738,56 +729,70 @@ GST_START_TEST (test_effects_priorities)
 
 GST_END_TEST;
 
-void
-count_cb (GObject * obj, GParamSpec * pspec, gint * count)
+static void
+_count_cb (GObject * obj, GParamSpec * pspec, gint * count)
 {
   *count = *count + 1;
 }
 
-void
-test_children_time_val (GESClip * clip, const gchar * prop, GstClockTime val)
-{
-  GList *tmp;
-  GstClockTime read_val;
-
-  g_object_get (clip, prop, &read_val, NULL);
-  assert_equals_uint64 (read_val, val);
-  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp != NULL; tmp = tmp->next) {
-    g_object_get (tmp->data, prop, &read_val, NULL);
-    assert_equals_uint64 (read_val, val);
-  }
-}
-
-void
-test_children_time_setter (GESClip * clip, GESTimelineElement * child,
-    const gchar * prop, gboolean (*setter) (GESTimelineElement *, GstClockTime),
-    GstClockTime val1, GstClockTime val2)
-{
-  gint clip_count = 0;
-  gint child_count = 0;
-  gchar *notify_name = g_strconcat ("notify::", prop, NULL);
-
-  g_signal_connect (clip, notify_name, G_CALLBACK (count_cb), &clip_count);
-  g_signal_connect (child, notify_name, G_CALLBACK (count_cb), &child_count);
-  fail_unless (setter (GES_TIMELINE_ELEMENT (clip), val1));
-  test_children_time_val (clip, prop, val1);
-  assert_equals_int (clip_count, 1);
-  assert_equals_int (child_count, 1);
-  clip_count = 0;
-  child_count = 0;
-  fail_unless (setter (child, val2));
-  test_children_time_val (clip, prop, val2);
-  assert_equals_int (clip_count, 1);
-  assert_equals_int (child_count, 1);
-  assert_equals_int (g_signal_handlers_disconnect_by_func (clip,
-          G_CALLBACK (count_cb), &clip_count), 1);
-  assert_equals_int (g_signal_handlers_disconnect_by_func (child,
-          G_CALLBACK (count_cb), &child_count), 1);
-  g_free (notify_name);
+#define _assert_children_time_setter(clip, child, prop, setter, val1, val2) \
+{ \
+  gint clip_count = 0; \
+  gint child_count = 0; \
+  gchar *notify_name = g_strconcat ("notify::", prop, NULL); \
+  gchar *clip_name = GES_TIMELINE_ELEMENT_NAME (clip); \
+  gchar *child_name = NULL; \
+  g_signal_connect (clip, notify_name, G_CALLBACK (_count_cb), \
+      &clip_count); \
+  if (child) { \
+    child_name = GES_TIMELINE_ELEMENT_NAME (child); \
+    g_signal_connect (child, notify_name, G_CALLBACK (_count_cb), \
+        &child_count); \
+  } \
+  \
+  fail_unless (setter (GES_TIMELINE_ELEMENT (clip), val1), \
+      "Failed to set the %s property for clip %s", prop, clip_name); \
+  assert_clip_children_time_val (clip, prop, val1); \
+  \
+  fail_unless (clip_count == 1, "The callback for the %s property was " \
+      "called %i times for clip %s, rather than once", \
+      prop, clip_count, clip_name); \
+  if (child) { \
+    fail_unless (child_count == 1, "The callback for the %s property " \
+        "was called %i times for the child %s of clip %s, rather than " \
+        "once", prop, child_count, child_name, clip_name); \
+  } \
+  \
+  clip_count = 0; \
+  if (child) { \
+    child_count = 0; \
+    fail_unless (setter (GES_TIMELINE_ELEMENT (child), val2), \
+        "Failed to set the %s property for the child %s of clip %s", \
+        prop, child_name, clip_name); \
+    fail_unless (child_count == 1, "The callback for the %s property " \
+        "was called %i more times for the child %s of clip %s, rather " \
+        "than once more", prop, child_count, child_name, clip_name); \
+  } else { \
+    fail_unless (setter (GES_TIMELINE_ELEMENT (clip), val2), \
+      "Failed to set the %s property for clip %s", prop, clip_name); \
+  } \
+  assert_clip_children_time_val (clip, prop, val2); \
+  \
+  fail_unless (clip_count == 1, "The callback for the %s property " \
+      "was called %i more times for clip %s, rather than once more", \
+      prop, clip_count, clip_name); \
+  assert_equals_int (g_signal_handlers_disconnect_by_func (clip, \
+          G_CALLBACK (_count_cb), &clip_count), 1); \
+  if (child) { \
+    assert_equals_int (g_signal_handlers_disconnect_by_func (child, \
+            G_CALLBACK (_count_cb), &child_count), 1); \
+  } \
+  \
+  g_free (notify_name); \
 }
 
-void
-test_children_time_setting_on_clip (GESClip * clip, GESTimelineElement * child)
+static void
+_test_children_time_setting_on_clip (GESClip * clip, GESTimelineElement * child)
 {
   /* FIXME: Don't necessarily want to change the inpoint of all the
    * children if the clip inpoint changes. Really, we would only expect
@@ -800,50 +805,66 @@ test_children_time_setting_on_clip (GESClip * clip, GESTimelineElement * child)
    * test should be changed to only check that source elements have
    * their inpoint changed, and operation elements have their inpoint
    * unchanged */
-  test_children_time_setter (clip, child, "in-point",
-      ges_timeline_element_set_inpoint, 23, 52);
-  test_children_time_setter (clip, child, "start",
-      ges_timeline_element_set_start, 43, 72);
-  test_children_time_setter (clip, child, "duration",
-      ges_timeline_element_set_duration, 53, 12);
+  _assert_children_time_setter (clip, child, "in-point",
+      ges_timeline_element_set_inpoint, 11, 101);
+  _assert_children_time_setter (clip, child, "in-point",
+      ges_timeline_element_set_inpoint, 51, 1);
+  _assert_children_time_setter (clip, child, "start",
+      ges_timeline_element_set_start, 12, 102);
+  _assert_children_time_setter (clip, child, "start",
+      ges_timeline_element_set_start, 52, 2);
+  _assert_children_time_setter (clip, child, "duration",
+      ges_timeline_element_set_duration, 13, 103);
+  _assert_children_time_setter (clip, child, "duration",
+      ges_timeline_element_set_duration, 53, 3);
 }
 
 GST_START_TEST (test_children_time_setters)
 {
   GESTimeline *timeline;
-  GESTrack *audio_track, *video_track;
   GESLayer *layer;
-
-  GESTimelineElement *effect;
-  GESClip *clips[] = {
-    GES_CLIP (ges_transition_clip_new (GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE)),  /* operation clip */
-    GES_CLIP (ges_test_clip_new ()),    /* source clip */
-  };
+  GESClip *clips[] = { NULL, NULL };
   gint i;
 
   ges_init ();
 
-  audio_track = GES_TRACK (ges_audio_track_new ());
-  video_track = GES_TRACK (ges_video_track_new ());
-
-  timeline = ges_timeline_new ();
-  fail_unless (ges_timeline_add_track (timeline, audio_track));
-  fail_unless (ges_timeline_add_track (timeline, video_track));
+  timeline = ges_timeline_new_audio_video ();
+  fail_unless (timeline);
 
   layer = ges_timeline_append_layer (timeline);
 
-  effect = GES_TIMELINE_ELEMENT (ges_effect_new ("agingtv"));
+  clips[0] =
+      GES_CLIP (ges_transition_clip_new
+      (GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE));
+  clips[1] = GES_CLIP (ges_test_clip_new ());
 
   for (i = 0; i < G_N_ELEMENTS (clips); i++) {
     GESClip *clip = clips[i];
-    fail_unless (ges_container_add (GES_CONTAINER (clip), effect));
-    test_children_time_setting_on_clip (clip, effect);
+    GESContainer *group = GES_CONTAINER (ges_group_new ());
+    GList *children;
+    GESTimelineElement *child;
+    /* no children */
+    _test_children_time_setting_on_clip (clip, NULL);
+    /* child in timeline */
     fail_unless (ges_layer_add_clip (layer, clip));
-    test_children_time_setting_on_clip (clip, effect);
-    fail_unless (ges_container_remove (GES_CONTAINER (clip), effect));
+    children = GES_CONTAINER_CHILDREN (clip);
+    fail_unless (children);
+    child = GES_TIMELINE_ELEMENT (children->data);
+    _test_children_time_setting_on_clip (clip, child);
+    /* clip in a group */
+    ges_container_add (group, GES_TIMELINE_ELEMENT (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));
+    /* child not in timeline */
+    gst_object_ref (clip);
     fail_unless (ges_layer_remove_clip (layer, clip));
+    children = GES_CONTAINER_CHILDREN (clip);
+    fail_unless (children);
+    child = GES_TIMELINE_ELEMENT (children->data);
+    _test_children_time_setting_on_clip (clip, child);
+    gst_object_unref (clip);
   }
-  gst_object_unref (effect);
   gst_object_unref (timeline);
 
   ges_deinit ();
@@ -851,6 +872,59 @@ GST_START_TEST (test_children_time_setters)
 
 GST_END_TEST;
 
+GST_START_TEST (test_can_add_effect)
+{
+  struct CanAddEffectData
+  {
+    GESClip *clip;
+    gboolean can_add_effect;
+  } clips[6];
+  guint i;
+  gchar *uri;
+
+  ges_init ();
+
+  uri = ges_test_get_audio_video_uri ();
+
+  clips[0] = (struct CanAddEffectData) {
+  GES_CLIP (ges_test_clip_new ()), TRUE};
+  clips[1] = (struct CanAddEffectData) {
+  GES_CLIP (ges_uri_clip_new (uri)), TRUE};
+  clips[2] = (struct CanAddEffectData) {
+  GES_CLIP (ges_title_clip_new ()), TRUE};
+  clips[3] = (struct CanAddEffectData) {
+  GES_CLIP (ges_effect_clip_new ("agingtv", "audioecho")), TRUE};
+  clips[4] = (struct CanAddEffectData) {
+  GES_CLIP (ges_transition_clip_new
+        (GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE)), FALSE};
+  clips[5] = (struct CanAddEffectData) {
+  GES_CLIP (ges_text_overlay_clip_new ()), FALSE};
+
+  g_free (uri);
+
+  for (i = 0; i < G_N_ELEMENTS (clips); i++) {
+    GESClip *clip = clips[i].clip;
+    GESTimelineElement *effect =
+        GES_TIMELINE_ELEMENT (ges_effect_new ("agingtv"));
+    gst_object_ref_sink (effect);
+    gst_object_ref_sink (clip);
+    fail_unless (clip);
+    if (clips[i].can_add_effect)
+      fail_unless (ges_container_add (GES_CONTAINER (clip), effect),
+          "Could not add an effect to clip %s",
+          GES_TIMELINE_ELEMENT_NAME (clip));
+    else
+      fail_if (ges_container_add (GES_CONTAINER (clip), effect),
+          "Could add an effect to clip %s, but we expect this to fail",
+          GES_TIMELINE_ELEMENT_NAME (clip));
+    gst_object_unref (effect);
+    gst_object_unref (clip);
+  }
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
 
 static Suite *
 ges_suite (void)
@@ -869,6 +943,7 @@ ges_suite (void)
   tcase_add_test (tc_chain, test_clip_find_track_element);
   tcase_add_test (tc_chain, test_effects_priorities);
   tcase_add_test (tc_chain, test_children_time_setters);
+  tcase_add_test (tc_chain, test_can_add_effect);
 
   return s;
 }
index d7cb857..602c1f2 100644 (file)
@@ -182,15 +182,11 @@ GST_START_TEST (test_effect_clip)
   GESLayer *layer;
   GESTrack *track_audio, *track_video;
   GESEffectClip *effect_clip;
-  GESEffect *effect, *effect1;
-  GList *effects, *tmp;
-  gint i, clip_height;
-  gint effect_prio = -1;
-  /* FIXME the order of track type is not well defined */
-  guint track_type[4] = { GES_TRACK_TYPE_AUDIO,
-    GES_TRACK_TYPE_VIDEO, GES_TRACK_TYPE_VIDEO,
-    GES_TRACK_TYPE_AUDIO
-  };
+  GESEffect *effect, *effect1, *core_effect, *core_effect1;
+  GList *children, *top_effects, *tmp;
+  gint clip_height;
+  gint core_effect_prio;
+  gint effect_index, effect1_index;
 
   ges_init ();
 
@@ -204,45 +200,91 @@ GST_START_TEST (test_effect_clip)
   ges_timeline_add_layer (timeline, layer);
 
   GST_DEBUG ("Create effect");
-  effect_clip = ges_effect_clip_new ("agingtv", "audiopanorama");
+  /* these are the core video and audio effects for the clip */
+  effect_clip = ges_effect_clip_new ("videobalance", "audioecho");
 
   g_object_set (effect_clip, "duration", 25 * GST_SECOND, NULL);
 
   ges_layer_add_clip (layer, (GESClip *) effect_clip);
 
+  /* core elements should now be created */
+  fail_unless (children = GES_CONTAINER_CHILDREN (effect_clip));
+  core_effect = GES_EFFECT (children->data);
+  fail_unless (children = children->next);
+  core_effect1 = GES_EFFECT (children->data);
+  fail_unless (children->next == NULL);
+
+  /* both effects are placed at the same priority since they are core
+   * children of the clip, destined for different tracks */
+  core_effect_prio = _PRIORITY (core_effect);
+  assert_equals_int (core_effect_prio, _PRIORITY (core_effect1));
+  g_object_get (effect_clip, "height", &clip_height, NULL);
+  assert_equals_int (clip_height, 1);
+
+  /* add additional non-core effects */
   effect = ges_effect_new ("agingtv");
   fail_unless (ges_container_add (GES_CONTAINER (effect_clip),
           GES_TIMELINE_ELEMENT (effect)));
   fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect)) ==
       track_video);
 
+  /* placed at a higher priority than the effects */
+  core_effect_prio = _PRIORITY (core_effect);
+  assert_equals_int (core_effect_prio, _PRIORITY (core_effect1));
+  fail_unless (_PRIORITY (effect) < core_effect_prio);
   g_object_get (effect_clip, "height", &clip_height, NULL);
-  assert_equals_int (clip_height, 3);
+  assert_equals_int (clip_height, 2);
+
+  effect_index =
+      ges_clip_get_top_effect_index (GES_CLIP (effect_clip),
+      GES_BASE_EFFECT (effect));
+  assert_equals_int (effect_index, 0);
 
+  /* 'effect1' is placed in between the core children and 'effect' */
   effect1 = ges_effect_new ("audiopanorama");
   fail_unless (ges_container_add (GES_CONTAINER (effect_clip),
           GES_TIMELINE_ELEMENT (effect1)));
   fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect1)) ==
       track_audio);
 
+  /* 'effect' is still the highest priority effect, and the core
+   * elements are at the lowest priority */
+  core_effect_prio = _PRIORITY (core_effect);
+  assert_equals_int (core_effect_prio, _PRIORITY (core_effect1));
+  fail_unless (_PRIORITY (effect1) < core_effect_prio);
+  fail_unless (_PRIORITY (effect1) > _PRIORITY (effect));
   g_object_get (effect_clip, "height", &clip_height, NULL);
-  assert_equals_int (clip_height, 4);
-
-  effects = ges_clip_get_top_effects (GES_CLIP (effect_clip));
-  for (tmp = effects, i = 0; tmp; tmp = tmp->next, i++) {
-    gint priority = ges_clip_get_top_effect_position (GES_CLIP (effect_clip),
-        GES_BASE_EFFECT (tmp->data));
-    fail_unless (priority > effect_prio);
-    fail_unless (GES_IS_EFFECT (tmp->data));
-    fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (tmp->data))->
-        type == track_type[i]);
-    effect_prio = priority;
-
-    gst_object_unref (tmp->data);
-  }
-  g_list_free (effects);
+  assert_equals_int (clip_height, 3);
 
-  ges_layer_remove_clip (layer, (GESClip *) effect_clip);
+  effect_index =
+      ges_clip_get_top_effect_index (GES_CLIP (effect_clip),
+      GES_BASE_EFFECT (effect));
+  effect1_index =
+      ges_clip_get_top_effect_index (GES_CLIP (effect_clip),
+      GES_BASE_EFFECT (effect1));
+  assert_equals_int (effect_index, 0);
+  assert_equals_int (effect1_index, 1);
+
+  /* all effects are children of the effect_clip, ordered by priority */
+  fail_unless (children = GES_CONTAINER_CHILDREN (effect_clip));
+  fail_unless (children->data == effect);
+  fail_unless (children = children->next);
+  fail_unless (children->data == effect1);
+  fail_unless (children = children->next);
+  fail_unless (children->data == core_effect);
+  fail_unless (children = children->next);
+  fail_unless (children->data == core_effect1);
+  fail_unless (children->next == NULL);
+
+  /* but only the additional effects are part of the top effects */
+  top_effects = ges_clip_get_top_effects (GES_CLIP (effect_clip));
+  fail_unless (tmp = top_effects);
+  fail_unless (tmp->data == effect);
+  fail_unless (tmp = tmp->next);
+  fail_unless (tmp->data == effect1);
+  fail_unless (tmp->next == NULL);
+
+  g_list_free_full (top_effects, gst_object_unref);
 
   gst_object_unref (timeline);
 
@@ -253,14 +295,14 @@ GST_END_TEST;
 
 GST_START_TEST (test_priorities_clip)
 {
-  GList *effects, *tmp;
+  GList *top_effects, *tmp;
   GESTimeline *timeline;
   GESLayer *layer;
-  GESEffectClip *effect_clip;
-  GESTrack *track_audio, *track_video;
-  GESEffect *effect, *effect1, *audio_effect = NULL, *video_effect = NULL;
-
-  gint effect_prio = -1;
+  GESClip *effect_clip;
+  GESTrack *track_audio, *track_video, *track;
+  GESBaseEffect *effects[6], *audio_effect = NULL, *video_effect = NULL;
+  gint prev_index, i, num_effects = G_N_ELEMENTS (effects);
+  guint32 base_prio = MIN_NLE_PRIO + TRANSITIONS_HEIGHT;
 
   ges_init ();
 
@@ -274,7 +316,7 @@ GST_START_TEST (test_priorities_clip)
   ges_timeline_add_layer (timeline, layer);
 
   GST_DEBUG ("Create effect");
-  effect_clip = ges_effect_clip_new ("agingtv", "audiopanorama");
+  effect_clip = GES_CLIP (ges_effect_clip_new ("videobalance", "audioecho"));
 
   g_object_set (effect_clip, "duration", 25 * GST_SECOND, NULL);
 
@@ -291,73 +333,134 @@ GST_START_TEST (test_priorities_clip)
   }
   fail_unless (GES_IS_EFFECT (audio_effect));
   fail_unless (GES_IS_EFFECT (video_effect));
+  fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (audio_effect)) ==
+      track_audio);
+  fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (video_effect)) ==
+      track_video);
 
-  /* FIXME This is ridiculus, both effects should have the same priority */
-  assert_equals_int (_PRIORITY (audio_effect),
-      MIN_NLE_PRIO + TRANSITIONS_HEIGHT);
-  assert_equals_int (_PRIORITY (video_effect),
-      MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 1);
-  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 2);
+  /* both the core effects have the same priority */
+  assert_equals_int (_PRIORITY (audio_effect), base_prio);
+  assert_equals_int (_PRIORITY (video_effect), base_prio);
+  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 1);
+
+  /* can not change their priority using the top effect methods since
+   * they are not top effects */
+  fail_unless (ges_clip_set_top_effect_index (effect_clip, audio_effect, 1)
+      == FALSE);
+  fail_unless (ges_clip_set_top_effect_index (effect_clip, video_effect, 0)
+      == FALSE);
+
+  /* adding non-core effects */
+  GST_DEBUG ("Adding effects to the effect clip ");
+  for (i = 0; i < num_effects; i++) {
+    if (i % 2)
+      effects[i] = GES_BASE_EFFECT (ges_effect_new ("agingtv"));
+    else
+      effects[i] = GES_BASE_EFFECT (ges_effect_new ("audiopanorama"));
+    fail_unless (ges_container_add (GES_CONTAINER (effect_clip),
+            GES_TIMELINE_ELEMENT (effects[i])));
+    assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 2 + i);
+    track = ges_track_element_get_track (GES_TRACK_ELEMENT (effects[i]));
+    if (i % 2)
+      fail_unless (track == track_video);
+    else
+      fail_unless (track == track_audio);
+  }
 
-  effect = ges_effect_new ("agingtv");
-  GST_DEBUG ("Adding effect to the effect clip %" GST_PTR_FORMAT, effect);
-  fail_unless (ges_container_add (GES_CONTAINER (effect_clip),
-          GES_TIMELINE_ELEMENT (effect)));
-  fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect)) ==
-      track_video);
-  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 3);
+  /* change top effect index */
+  for (i = 0; i < num_effects; i++) {
+    assert_equals_int (ges_clip_get_top_effect_index (effect_clip, effects[i]),
+        i);
+    assert_equals_int (_PRIORITY (effects[i]), i + base_prio);
+  }
 
-  effect1 = ges_effect_new ("audiopanorama");
-  GST_DEBUG ("Adding effect1 to the effect clip %" GST_PTR_FORMAT, effect1);
-  fail_unless (ges_container_add (GES_CONTAINER (effect_clip),
-          GES_TIMELINE_ELEMENT (effect1)));
-  fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect1)) ==
-      track_audio);
+  assert_equals_int (_PRIORITY (video_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (audio_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (effect_clip), 1);
+  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), num_effects + 1);
+
+  /* moving 4th effect to index 1 should only change the priority of
+   * effects 1, 2, 3, and 4 because these lie between the new index (1)
+   * and the old index (4). */
+  fail_unless (ges_clip_set_top_effect_index (effect_clip, effects[4], 1));
+
+  assert_equals_int (_PRIORITY (effects[0]), 0 + base_prio);
+  assert_equals_int (_PRIORITY (effects[1]), 2 + base_prio);
+  assert_equals_int (_PRIORITY (effects[2]), 3 + base_prio);
+  assert_equals_int (_PRIORITY (effects[3]), 4 + base_prio);
+  assert_equals_int (_PRIORITY (effects[4]), 1 + base_prio);
+  assert_equals_int (_PRIORITY (effects[5]), 5 + base_prio);
+
+  /* everything else stays the same */
+  assert_equals_int (_PRIORITY (video_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (audio_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (effect_clip), 1);
+  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), num_effects + 1);
 
-  fail_unless (ges_clip_set_top_effect_priority (GES_CLIP (effect_clip),
-          GES_BASE_EFFECT (effect1), 0));
+  /* move back */
+  fail_unless (ges_clip_set_top_effect_index (effect_clip, effects[4], 4));
+
+  for (i = 0; i < num_effects; i++) {
+    assert_equals_int (ges_clip_get_top_effect_index (effect_clip, effects[i]),
+        i);
+    assert_equals_int (_PRIORITY (effects[i]), i + base_prio);
+  }
+
+  assert_equals_int (_PRIORITY (video_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (audio_effect), num_effects + base_prio);
   assert_equals_int (_PRIORITY (effect_clip), 1);
+  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), num_effects + 1);
 
-  assert_equals_int (_PRIORITY (effect), 3 + MIN_NLE_PRIO + TRANSITIONS_HEIGHT);
-  assert_equals_int (_PRIORITY (effect1),
-      0 + MIN_NLE_PRIO + TRANSITIONS_HEIGHT);
+  /* moving 2nd effect to index 4 should only change the priority of
+   * effects 2, 3 and 4 because these lie between the new index (4) and
+   * the old index (2). */
 
-  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 4);
+  fail_unless (ges_clip_set_top_effect_index (effect_clip, effects[2], 4));
 
-  fail_unless (ges_clip_set_top_effect_priority (GES_CLIP (effect_clip),
-          GES_BASE_EFFECT (effect1), 3));
-  assert_equals_int (_PRIORITY (effect), 2 + MIN_NLE_PRIO + TRANSITIONS_HEIGHT);
-  assert_equals_int (_PRIORITY (effect1),
-      3 + MIN_NLE_PRIO + TRANSITIONS_HEIGHT);
-  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 4);
+  assert_equals_int (_PRIORITY (effects[0]), 0 + base_prio);
+  assert_equals_int (_PRIORITY (effects[1]), 1 + base_prio);
+  assert_equals_int (_PRIORITY (effects[2]), 4 + base_prio);
+  assert_equals_int (_PRIORITY (effects[3]), 2 + base_prio);
+  assert_equals_int (_PRIORITY (effects[4]), 3 + base_prio);
+  assert_equals_int (_PRIORITY (effects[5]), 5 + base_prio);
 
-  effects = ges_clip_get_top_effects (GES_CLIP (effect_clip));
-  for (tmp = effects; tmp; tmp = tmp->next) {
-    gint priority = ges_clip_get_top_effect_position (GES_CLIP (effect_clip),
-        GES_BASE_EFFECT (tmp->data));
-    fail_unless (priority > effect_prio);
-    fail_unless (GES_IS_EFFECT (tmp->data));
-    effect_prio = priority;
+  /* everything else stays the same */
+  assert_equals_int (_PRIORITY (video_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (audio_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (effect_clip), 1);
+  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), num_effects + 1);
 
-    gst_object_unref (tmp->data);
-  }
-  g_list_free (effects);
+  /* move 4th effect to index 0 should only change the priority of
+   * effects 0, 1, 3 and 4 because these lie between the new index (0) and
+   * the old index (3) */
 
-  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 4);
-  effects = ges_clip_get_top_effects (GES_CLIP (effect_clip));
-  effect_prio = 0;
-  for (tmp = effects; tmp; tmp = tmp->next) {
-    gint priority = ges_clip_get_top_effect_position (GES_CLIP (effect_clip),
+  fail_unless (ges_clip_set_top_effect_index (effect_clip, effects[4], 0));
+
+  assert_equals_int (_PRIORITY (effects[0]), 1 + base_prio);
+  assert_equals_int (_PRIORITY (effects[1]), 2 + base_prio);
+  assert_equals_int (_PRIORITY (effects[2]), 4 + base_prio);
+  assert_equals_int (_PRIORITY (effects[3]), 3 + base_prio);
+  assert_equals_int (_PRIORITY (effects[4]), 0 + base_prio);
+  assert_equals_int (_PRIORITY (effects[5]), 5 + base_prio);
+
+  /* everything else stays the same */
+  assert_equals_int (_PRIORITY (video_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (audio_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (effect_clip), 1);
+  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), num_effects + 1);
+
+  /* make sure top effects are ordered by index */
+  top_effects = ges_clip_get_top_effects (effect_clip);
+  prev_index = -1;
+  for (tmp = top_effects; tmp; tmp = tmp->next) {
+    gint index = ges_clip_get_top_effect_index (effect_clip,
         GES_BASE_EFFECT (tmp->data));
-    fail_unless (priority >= effect_prio, "%d >= %d", priority, effect_prio);
+    fail_unless (index >= 0);
+    fail_unless (index > prev_index);
     fail_unless (GES_IS_EFFECT (tmp->data));
-    effect_prio = priority;
-
-    gst_object_unref (tmp->data);
+    prev_index = index;
   }
-  g_list_free (effects);
-
-  ges_layer_remove_clip (layer, (GESClip *) effect_clip);
+  g_list_free_full (top_effects, gst_object_unref);
 
   gst_object_unref (timeline);
 
@@ -526,7 +629,7 @@ GST_START_TEST (test_split_clip_effect_priorities)
   ges_init ();
 
   timeline = ges_timeline_new ();
-  layer = ges_layer_new ();
+  layer = ges_timeline_append_layer (timeline);
   track_video = GES_TRACK (ges_video_track_new ());
 
   g_object_set (timeline, "auto-transition", TRUE, NULL);
@@ -547,6 +650,7 @@ GST_START_TEST (test_split_clip_effect_priorities)
   assert_equals_uint64 (GES_TIMELINE_ELEMENT_PRIORITY (source), 4);
 
   nclip = ges_clip_split (clip, GST_SECOND);
+  fail_unless (nclip);
   neffect = ges_clip_find_track_element (nclip, NULL, GES_TYPE_EFFECT);
   nsource = ges_clip_find_track_element (nclip, NULL, GES_TYPE_VIDEO_SOURCE);
 
index 971079e..b225c76 100644 (file)
@@ -94,6 +94,29 @@ G_STMT_START {                                          \
   fail_unless (_DURATION (obj) == duration, "%s duration is %" GST_TIME_FORMAT " != %" GST_TIME_FORMAT, GES_TIMELINE_ELEMENT_NAME(obj), GST_TIME_ARGS (_DURATION(obj)), GST_TIME_ARGS (duration));\
 }
 
+/* assert that the time property (start, duration or in-point) is the
+ * same as @cmp for the clip and all its children */
+#define assert_clip_children_time_val(clip, property, cmp) \
+{ \
+  GList *tmp; \
+  GstClockTime read_val; \
+  gchar *name = GES_TIMELINE_ELEMENT (clip)->name; \
+  g_object_get (clip, property, &read_val, NULL); \
+  fail_unless (read_val == cmp, "The %s property for clip %s is %" \
+      GST_TIME_FORMAT ", rather than the expected value of %" \
+      GST_TIME_FORMAT, property, name, GST_TIME_ARGS (read_val), \
+      GST_TIME_ARGS (cmp)); \
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp != NULL; \
+      tmp = tmp->next) { \
+    GESTimelineElement *child = tmp->data; \
+    g_object_get (child, property, &read_val, NULL); \
+    fail_unless (read_val == cmp, "The %s property for the child %s " \
+        "of clip %s is %" GST_TIME_FORMAT ", rather than the expected " \
+        "value of %" GST_TIME_FORMAT, property, child->name, name, \
+        GST_TIME_ARGS (read_val), GST_TIME_ARGS (cmp)); \
+  } \
+}
+
 #define check_layer(clip, layer_prio) {                                      \
   fail_unless (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip) ==  (layer_prio),  \
     "%s in layer %d instead of %d", GES_TIMELINE_ELEMENT_NAME (clip),        \