diff --git a/ges/ges-clip.c b/ges/ges-clip.c index 7e6c295160..99767c1dd2 100644 --- a/ges/ges-clip.c +++ b/ges/ges-clip.c @@ -113,6 +113,8 @@ struct _GESClipPrivate gboolean prevent_max_duration_update; gboolean setting_inpoint; + gboolean allow_any_track; + /* The formats supported by this Clip */ GESTrackType supportedformats; }; @@ -148,8 +150,10 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESClip, ges_clip, * Listen to our children * ****************************************************/ -#define _IS_CORE_CHILD(child) \ - (ges_track_element_get_creators (GES_TRACK_ELEMENT (child)) != NULL) +#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)) #define _IS_CORE_INTERNAL_SOURCE_CHILD(child) \ (_IS_CORE_CHILD (child) \ @@ -275,6 +279,85 @@ _child_has_internal_source_changed_cb (GESTimelineElement * child, _set_inpoint0 (child, _INPOINT (container)); } +/**************************************************** + * Restrict our children * + ****************************************************/ + +static gboolean +_track_contains_core (GESClip * clip, GESTrack * track, gboolean core) +{ + GList *tmp; + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTrackElement *child = tmp->data; + if (_IS_CORE_CHILD (child) == core + && ges_track_element_get_track (child) == track) + return TRUE; + } + return FALSE; +} + +gboolean +ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, + GESTrack * track) +{ + GESTrack *current_track = ges_track_element_get_track (child); + + if (clip->priv->allow_any_track) + return TRUE; + + if (current_track == track) + return TRUE; + + if (current_track) { + /* can not remove a core element from a track if a non-core one sits + * above it */ + if (_IS_CORE_CHILD (child) + && _track_contains_core (clip, current_track, FALSE)) { + GST_INFO_OBJECT (clip, "Cannot move the core child %" GES_FORMAT + " to the track %" GST_PTR_FORMAT " because it has non-core " + "siblings above it in its current track %" GST_PTR_FORMAT, + GES_ARGS (child), track, current_track); + return FALSE; + } + /* otherwise can remove */ + } + if (track) { + GESTimeline *clip_timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip); + const GESTimeline *track_timeline = ges_track_get_timeline (track); + if (track_timeline == NULL) { + GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT + " to the track %" GST_PTR_FORMAT " because it is not part " + "of a timeline", GES_ARGS (child), track); + return FALSE; + } + if (track_timeline != clip_timeline) { + GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT + " to the track %" GST_PTR_FORMAT " because its timeline %" + GST_PTR_FORMAT " does not match the clip's timeline %" + GST_PTR_FORMAT, GES_ARGS (child), track, track_timeline, + clip_timeline); + return FALSE; + } + /* one core child per track, and other children (effects) can only be + * placed in a track that already has a core child */ + if (_IS_CORE_CHILD (child)) { + if (_track_contains_core (clip, track, TRUE)) { + GST_INFO_OBJECT (clip, "Cannot move the core child %" GES_FORMAT + " to the track %" GST_PTR_FORMAT " because it contains a " + "core sibling", GES_ARGS (child), track); + return FALSE; + } + } else { + if (!_track_contains_core (clip, track, TRUE)) { + GST_INFO_OBJECT (clip, "Cannot move the non-core child %" + GES_FORMAT " to the track %" GST_PTR_FORMAT " because it " + " does not contain a core sibling", GES_ARGS (child), track); + return FALSE; + } + } + } + return TRUE; +} /***************************************************** * * @@ -574,17 +657,18 @@ _add_child (GESContainer * container, GESTimelineElement * element) { GESClipClass *klass = GES_CLIP_GET_CLASS (GES_CLIP (container)); guint max_prio, min_prio; + GESTrack *track; GList *creators; + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (container); GESClipPrivate *priv = GES_CLIP (container)->priv; g_return_val_if_fail (GES_IS_TRACK_ELEMENT (element), FALSE); - if (element->timeline - && element->timeline != GES_TIMELINE_ELEMENT_TIMELINE (container)) { + if (element->timeline && element->timeline != timeline) { GST_WARNING_OBJECT (container, "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, GES_TIMELINE_ELEMENT_TIMELINE (container)); + element->timeline, timeline); return FALSE; } @@ -597,12 +681,34 @@ _add_child (GESContainer * container, GESTimelineElement * element) return FALSE; } + track = ges_track_element_get_track (GES_TRACK_ELEMENT (element)); + + if (track && ges_track_get_timeline (track) != timeline) { + /* really, an element in a track should have the same timeline as + * 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 " + "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); + return FALSE; + } + /* NOTE: notifies are currently frozen by ges_container_add */ _get_priority_range (container, &min_prio, &max_prio); if (creators) { /* 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. */ + * other core elements. In particular, they are *not* added to the + * 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 + " because it is in the same track %" GST_PTR_FORMAT " as an " + "existing core child", GES_ARGS (element), track); + return FALSE; + } /* Set the core element to have the same in-point, which we don't * apply to effects */ @@ -620,12 +726,20 @@ _add_child (GESContainer * container, GESTimelineElement * element) /* Always add at the same priority, on top of existing effects */ _set_priority0 (element, min_prio + priv->nb_effects); - } else if (GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) && - GES_IS_BASE_EFFECT (element)) { + } else if (GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) && _IS_TOP_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. */ + + if (track && !priv->allow_any_track + && !_track_contains_core (GES_CLIP (container), track, TRUE)) { + GST_WARNING_OBJECT (container, "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 " Priority %i", priv->nb_effects + 1, GES_ARGS (element), min_prio + priv->nb_effects); @@ -646,7 +760,7 @@ _add_child (GESContainer * container, GESTimelineElement * element) /* The height has already changed (increased by 1) */ _compute_height (container); } else { - if (GES_IS_BASE_EFFECT (element)) + if (_IS_TOP_EFFECT (element)) GST_WARNING_OBJECT (container, "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", @@ -674,7 +788,7 @@ _remove_child (GESContainer * container, GESTimelineElement * element) GESClipPrivate *priv = GES_CLIP (container)->priv; /* NOTE: notifies are currently frozen by ges_container_add */ - if (!_IS_CORE_CHILD (element) && GES_IS_BASE_EFFECT (element)) { + if (_IS_TOP_EFFECT (element)) { GList *tmp; GST_DEBUG_OBJECT (container, "Resyncing effects priority."); @@ -701,13 +815,13 @@ _remove_child (GESContainer * container, GESTimelineElement * element) static void _child_added (GESContainer * container, GESTimelineElement * element) { - g_signal_connect (G_OBJECT (element), "notify::priority", + g_signal_connect (element, "notify::priority", G_CALLBACK (_child_priority_changed_cb), container); - g_signal_connect (G_OBJECT (element), "notify::in-point", + g_signal_connect (element, "notify::in-point", G_CALLBACK (_child_inpoint_changed_cb), container); - g_signal_connect (G_OBJECT (element), "notify::max-duration", + g_signal_connect (element, "notify::max-duration", G_CALLBACK (_child_max_duration_changed_cb), container); - g_signal_connect (G_OBJECT (element), "notify::has-internal-source", + g_signal_connect (element, "notify::has-internal-source", G_CALLBACK (_child_has_internal_source_changed_cb), container); _child_priority_changed_cb (element, NULL, container); @@ -738,12 +852,24 @@ add_clip_to_list (gpointer key, gpointer clip, GList ** list) *list = g_list_prepend (*list, gst_object_ref (clip)); } +/* NOTE: Since this does not change the track of @child, this should + * only be called if it is guaranteed that neither @from_clip nor @to_clip + * will not break the track rules: + * + no more than one core child per track + * + every non-core child must be in the same track as a core child + */ static void _transfer_child (GESClip * from_clip, GESClip * to_clip, GESTrackElement * child) { + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (to_clip); + /* We need to bump the refcount to avoid the object to be destroyed */ gst_object_ref (child); + + /* don't want to change tracks */ + ges_timeline_set_moving_track_elements (timeline, TRUE); + ges_container_remove (GES_CONTAINER (from_clip), GES_TIMELINE_ELEMENT (child)); @@ -752,7 +878,11 @@ _transfer_child (GESClip * from_clip, GESClip * to_clip, ges_track_element_add_creator (child, to_clip); } + to_clip->priv->allow_any_track = TRUE; ges_container_add (GES_CONTAINER (to_clip), GES_TIMELINE_ELEMENT (child)); + to_clip->priv->allow_any_track = FALSE; + ges_timeline_set_moving_track_elements (timeline, FALSE); + gst_object_unref (child); } @@ -982,10 +1112,87 @@ _group (GList * containers) done: if (tracks) g_free (tracks); - return ret; } +void +ges_clip_empty_from_track (GESClip * clip, GESTrack * track) +{ + GList *tmp; + if (track == NULL) + return; + /* allow us to remove in any order */ + clip->priv->allow_any_track = TRUE; + + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + GESTrackElement *child = tmp->data; + if (ges_track_element_get_track (child) == track) { + if (!ges_track_remove_element (track, child)) + GST_ERROR_OBJECT (clip, "Failed to remove child %" GES_FORMAT + " from the track %" GST_PTR_FORMAT, GES_ARGS (child), track); + } + } + clip->priv->allow_any_track = FALSE; +} + +static GESTrackElement * +_copy_track_element_to (GESTrackElement * orig, GESClip * to_clip, + GstClockTime position) +{ + GESTrackElement *copy; + GESTimelineElement *el_copy, *el_orig; + + /* 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. */ + el_orig = GES_TIMELINE_ELEMENT (orig); + el_copy = ges_timeline_element_copy (el_orig, FALSE); + + if (el_copy == NULL) + return NULL; + + copy = GES_TRACK_ELEMENT (el_copy); + ges_track_element_copy_properties (el_orig, el_copy); + /* NOTE: control bindings that are not registered in GES are not + * handled */ + ges_track_element_copy_bindings (orig, copy, position); + + if (_IS_CORE_CHILD (orig)) + ges_track_element_add_creator (copy, to_clip); + + return copy; +} + +static GESTrackElement * +ges_clip_copy_track_element_into (GESClip * clip, GESTrackElement * orig, + GstClockTime position) +{ + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip); + GESTrackElement *copy; + + copy = _copy_track_element_to (orig, clip, position); + if (copy == NULL) { + GST_ERROR_OBJECT (clip, "Failed to create a copy of the " + "element %" GES_FORMAT " for the clip", GES_ARGS (orig)); + return NULL; + } + + gst_object_ref (copy); + ges_timeline_set_moving_track_elements (timeline, TRUE); + if (!ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (copy))) { + GST_ERROR_OBJECT (clip, "Failed to add the copied child track " + "element %" GES_FORMAT " to the clip", GES_ARGS (copy)); + ges_timeline_set_moving_track_elements (timeline, FALSE); + gst_object_unref (copy); + return NULL; + } + ges_timeline_set_moving_track_elements (timeline, FALSE); + /* now owned by the clip */ + gst_object_unref (copy); + + return copy; +} + static void _deep_copy (GESTimelineElement * element, GESTimelineElement * copy) { @@ -993,20 +1200,26 @@ _deep_copy (GESTimelineElement * element, GESTimelineElement * copy) GESClip *self = GES_CLIP (element), *ccopy = GES_CLIP (copy); GESTrackElement *el, *el_copy; - if (!self->priv->layer) - return; - + /* NOTE: this should only be called on a newly created @copy, so + * its copied_track_elements, and copied_layer, should be free to set + * without disposing of the previous values */ for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { el = GES_TRACK_ELEMENT (tmp->data); - /* copies the children properties */ - el_copy = GES_TRACK_ELEMENT (ges_timeline_element_copy (tmp->data, TRUE)); - /* any element created by self, will have its copy considered created - * by self's copy */ - if (_IS_CORE_CHILD (el)) - ges_track_element_add_creator (el_copy, ccopy); + el_copy = _copy_track_element_to (el, ccopy, GST_CLOCK_TIME_NONE); + if (!el_copy) { + GST_ERROR_OBJECT (element, "Failed to copy the track element %" + GES_FORMAT " for pasting", GES_ARGS (el)); + continue; + } + /* owned by copied_track_elements */ + gst_object_ref_sink (el_copy); - ges_track_element_copy_bindings (el, el_copy, GST_CLOCK_TIME_NONE); + /* _add_child will add core elements at the lowest priority and new + * non-core effects at the lowest effect priority, so we need to add + * the highest priority children first to preserve the effect order. + * The clip's children are already ordered by highest priority first. + * So we order copied_track_elements in the same way */ ccopy->priv->copied_track_elements = g_list_append (ccopy->priv->copied_track_elements, el_copy); } @@ -1022,48 +1235,12 @@ _paste (GESTimelineElement * element, GESTimelineElement * ref, GESClip *self = GES_CLIP (element); GESClip *nclip = GES_CLIP (ges_timeline_element_copy (element, FALSE)); - if (self->priv->copied_layer) - nclip->priv->copied_layer = g_object_ref (self->priv->copied_layer); ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (nclip), paste_position); - for (tmp = self->priv->copied_track_elements; tmp; tmp = tmp->next) { - GESTrackElement *new_trackelement, *trackelement = - GES_TRACK_ELEMENT (tmp->data); + /* paste in order of priority (highest first) */ + for (tmp = self->priv->copied_track_elements; tmp; tmp = tmp->next) + ges_clip_copy_track_element_into (nclip, tmp->data, GST_CLOCK_TIME_NONE); - /* 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)); - if (new_trackelement == NULL) { - GST_WARNING_OBJECT (trackelement, "Could not create a copy"); - continue; - } - - if (_IS_CORE_CHILD (trackelement)) - ges_track_element_add_creator (new_trackelement, nclip); - - gst_object_ref_sink (new_trackelement); - if (!ges_container_add (GES_CONTAINER (nclip), - GES_TIMELINE_ELEMENT (new_trackelement))) { - GST_ERROR_OBJECT (self, "Failed add the copied child track element %" - GES_FORMAT " to the copy %" GES_FORMAT, - GES_ARGS (new_trackelement), GES_ARGS (nclip)); - gst_object_unref (new_trackelement); - continue; - } - - ges_track_element_copy_properties (GES_TIMELINE_ELEMENT (trackelement), - GES_TIMELINE_ELEMENT (new_trackelement)); - - ges_track_element_copy_bindings (trackelement, new_trackelement, - GST_CLOCK_TIME_NONE); - gst_object_unref (new_trackelement); - } - - /* FIXME: should we bypass the select-tracks-for-object signal when - * copying and pasting? */ if (self->priv->copied_layer) { if (!ges_layer_add_clip (self->priv->copied_layer, nclip)) { GST_INFO ("%" GES_FORMAT " could not be pasted to %" GST_TIME_FORMAT, @@ -1071,9 +1248,12 @@ _paste (GESTimelineElement * element, GESTimelineElement * ref, return NULL; } - } + /* NOTE: self should not be used and be freed after this call, so we can + * leave the freeing of copied_layer and copied_track_elements to the + * dispose method */ + return GES_TIMELINE_ELEMENT (nclip); } @@ -1138,7 +1318,7 @@ ges_clip_dispose (GObject * object) { GESClip *self = GES_CLIP (object); - g_list_free_full (self->priv->copied_track_elements, g_object_unref); + g_list_free_full (self->priv->copied_track_elements, gst_object_unref); self->priv->copied_track_elements = NULL; g_clear_object (&self->priv->copied_layer); @@ -1218,15 +1398,7 @@ ges_clip_class_init (GESClipClass * klass) static void ges_clip_init (GESClip * self) { - GESClipPrivate *priv; - priv = self->priv = ges_clip_get_instance_private (self); - priv->layer = NULL; - priv->nb_effects = 0; - priv->prevent_priority_offset_update = FALSE; - priv->prevent_resort = FALSE; - priv->updating_max_duration = FALSE; - priv->prevent_max_duration_update = FALSE; - priv->setting_inpoint = FALSE; + self->priv = ges_clip_get_instance_private (self); } /** @@ -1236,9 +1408,6 @@ ges_clip_init (GESClip * self) * * Creates the core #GESTrackElement of the clip, of the given track type. * - * Note, unlike ges_clip_create_track_elements(), this does not add the - * created track element to the clip or set their timings. - * * Returns: (transfer floating) (nullable): The element created * by @clip, or %NULL if @clip can not provide a track element for the * given @type or an error occurred. @@ -1277,7 +1446,7 @@ ges_clip_create_track_element (GESClip * clip, GESTrackType type) * @type: The track-type to create elements for * * Creates the core #GESTrackElement-s of the clip, of the given track - * type, and adds them to the clip. + * type. * * Returns: (transfer container) (element-type GESTrackElement): A list of * the #GESTrackElement-s created by @clip for the given @type, or %NULL @@ -1287,14 +1456,15 @@ ges_clip_create_track_element (GESClip * clip, GESTrackType type) GList * ges_clip_create_track_elements (GESClip * clip, GESTrackType type) { - /* add_list holds a ref to its elements to keep them alive - * result does not */ - GList *result = NULL, *add_list = NULL, *tmp, *children; + GList *tmp, *ret; GESClipClass *klass; - gboolean readding_effects_only = TRUE; + gboolean already_created = FALSE; g_return_val_if_fail (GES_IS_CLIP (clip), NULL); + if ((clip->priv->supportedformats & type) == 0) + return NULL; + klass = GES_CLIP_GET_CLASS (clip); if (!(klass->create_track_elements)) { @@ -1304,71 +1474,24 @@ ges_clip_create_track_elements (GESClip * clip, GESTrackType type) GST_DEBUG_OBJECT (clip, "Creating TrackElements for type: %s", ges_track_type_name (type)); - children = ges_container_get_children (GES_CONTAINER (clip), TRUE); - for (tmp = children; tmp; tmp = tmp->next) { + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { GESTrackElement *child = GES_TRACK_ELEMENT (tmp->data); - if (!ges_track_element_get_track (child) + if (_IS_CORE_CHILD (child) && ges_track_element_get_track_type (child) & type) { - GST_DEBUG_OBJECT (clip, "Removing for reusage: %" GST_PTR_FORMAT, child); - add_list = g_list_append (add_list, gst_object_ref (child)); - ges_container_remove (GES_CONTAINER (clip), tmp->data); - if (_IS_CORE_CHILD (child)) - readding_effects_only = FALSE; + /* assume the core track elements have all been created if we find + * at least one core child with the same type */ + already_created = TRUE; + break; } } - g_list_free_full (children, gst_object_unref); + if (already_created) + return NULL; - /* FIXME: we need something smarter to determine whether we should - * create the track elements. - * Currently, if the clip contains at least one element with a matching - * track-type, not in a track and not a GESBaseEffect, we will not - * recreate the track elements! But this is not a reliable indicator. - * - * For example, consider a uri clip that creates two audio track - * elements: El_A and El_B. First, we add the clip to a timeline that - * only has a single track: Track_A, and we connect to the timeline's - * ::select-tracks-for-object signal to only allow El_A to end up in - * Track_A. As such, whilst both El_A and El_B are initially created, - * El_B will eventually be removed from the clip since it has no track - * (see clip_track_element_added_cb in ges-timeline.c). As such, we now - * have a clip that only contains El_A. - * Next, we remove Track_A from the timeline. El_A will remain a child - * of the clip, but now has its track unset. - * Next, we add Track_B to the timeline, and we connect to the - * timeline's ::select-tracks-for-object signal to only allow El_B to - * end up in Track_B. - * - * However, since the clip contains an audio track element, that is not - * an effect and has no track set: El_A. Therefore, the - * create_track_elements method below will not be called, so we will not - * have an El_B created for Track_B! - * - * Moreover, even if we did recreate the track elements, we would be - * creating El_A again! We could destroy and recreate El_A instead, or - * we would need a way to determine exactly which elements need to be - * recreated. - */ - if (readding_effects_only) { - GList *track_elements = klass->create_track_elements (clip, type); - for (tmp = track_elements; tmp; tmp = tmp->next) { - gst_object_ref_sink (tmp->data); - ges_track_element_add_creator (tmp->data, clip); - } - add_list = g_list_concat (track_elements, add_list); - } - - for (tmp = add_list; tmp; tmp = tmp->next) { - GESTimelineElement *el = GES_TIMELINE_ELEMENT (tmp->data); - if (ges_container_add (GES_CONTAINER (clip), el)) - result = g_list_append (result, el); - else - GST_ERROR_OBJECT (clip, "Failed add the track element %" - GES_FORMAT " to the clip", GES_ARGS (el)); - } - g_list_free_full (add_list, gst_object_unref); - - return result; + ret = klass->create_track_elements (clip, type); + for (tmp = ret; tmp; tmp = tmp->next) + ges_track_element_add_creator (tmp->data, clip); + return ret; } /* @@ -1605,7 +1728,7 @@ ges_clip_get_top_effects (GESClip * clip) for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { child = tmp->data; - if (GES_IS_BASE_EFFECT (child) && !_IS_CORE_CHILD (child)) + if (_IS_TOP_EFFECT (child)) ret = g_list_append (ret, gst_object_ref (child)); } @@ -1801,14 +1924,20 @@ ges_clip_split (GESClip * clip, guint64 position) GESClip *new_object; GstClockTime start, inpoint, duration, old_duration, new_duration; gdouble media_duration_factor; + GESTimelineElement *element; + GESTimeline *timeline; + GHashTable *track_for_copy; g_return_val_if_fail (GES_IS_CLIP (clip), NULL); g_return_val_if_fail (clip->priv->layer, NULL); g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), NULL); - duration = _DURATION (clip); - start = _START (clip); - inpoint = _INPOINT (clip); + element = GES_TIMELINE_ELEMENT (clip); + timeline = element->timeline; + + duration = element->duration; + start = element->start; + inpoint = element->inpoint; if (position >= start + duration || position <= start) { GST_WARNING_OBJECT (clip, "Can not split %" GST_TIME_FORMAT @@ -1817,9 +1946,9 @@ ges_clip_split (GESClip * clip, guint64 position) } old_duration = position - start; - if (!timeline_tree_can_move_element (timeline_get_tree - (GES_TIMELINE_ELEMENT_TIMELINE (clip)), GES_TIMELINE_ELEMENT (clip), - GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), + if (timeline && !timeline_tree_can_move_element (timeline_get_tree + (timeline), element, + ges_timeline_element_get_layer_priority (element), start, old_duration, NULL)) { GST_WARNING_OBJECT (clip, "Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT @@ -1829,10 +1958,10 @@ ges_clip_split (GESClip * clip, guint64 position) } new_duration = duration + start - position; - if (!timeline_tree_can_move_element (timeline_get_tree - (GES_TIMELINE_ELEMENT_TIMELINE (clip)), GES_TIMELINE_ELEMENT (clip), - GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), position, new_duration, - NULL)) { + if (timeline && !timeline_tree_can_move_element (timeline_get_tree + (timeline), element, + ges_timeline_element_get_layer_priority (element), + position, new_duration, NULL)) { GST_WARNING_OBJECT (clip, "Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT " as timeline would end up in an illegal" " state.", GES_ARGS (clip), @@ -1844,64 +1973,60 @@ ges_clip_split (GESClip * clip, guint64 position) GST_TIME_ARGS (position)); /* Create the new Clip */ - new_object = GES_CLIP (ges_timeline_element_copy (GES_TIMELINE_ELEMENT (clip), - FALSE)); + new_object = GES_CLIP (ges_timeline_element_copy (element, FALSE)); GST_DEBUG_OBJECT (new_object, "New 'splitted' clip"); /* Set new timing properties on the Clip */ media_duration_factor = - ges_timeline_element_get_media_duration_factor (GES_TIMELINE_ELEMENT - (clip)); + ges_timeline_element_get_media_duration_factor (element); _set_start0 (GES_TIMELINE_ELEMENT (new_object), position); _set_inpoint0 (GES_TIMELINE_ELEMENT (new_object), inpoint + old_duration * media_duration_factor); _set_duration0 (GES_TIMELINE_ELEMENT (new_object), new_duration); - _DURATION (clip) = old_duration; - g_object_notify (G_OBJECT (clip), "duration"); - /* We do not want the timeline to create again TrackElement-s */ ges_clip_set_moving_from_layer (new_object, TRUE); + /* adding to the same layer should not fail when moving */ ges_layer_add_clip (clip->priv->layer, new_object); ges_clip_set_moving_from_layer (new_object, FALSE); + /* split binding before duration changes */ + track_for_copy = g_hash_table_new_full (NULL, NULL, + gst_object_unref, gst_object_unref); + /* _add_child will add core elements at the lowest priority and new + * non-core effects at the lowest effect priority, so we need to add the + * highest priority children first to preserve the effect order. The + * clip's children are already ordered by highest priority first. */ for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { - GESTrackElement *new_trackelement, *trackelement = - GES_TRACK_ELEMENT (tmp->data); - - new_trackelement = - GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT - (trackelement), FALSE)); - if (new_trackelement == NULL) { - GST_WARNING_OBJECT (trackelement, "Could not create a copy"); - continue; - } - - if (_IS_CORE_CHILD (trackelement)) - ges_track_element_add_creator (new_trackelement, new_object); - - /* FIXME: in-point for non-core track elements should be shifted by - * the split (adding them to the new clip will not set their in-point) - * Handle this once generic time effects are supported in splitting */ - ges_container_add (GES_CONTAINER (new_object), - GES_TIMELINE_ELEMENT (new_trackelement)); - ges_track_element_copy_properties (GES_TIMELINE_ELEMENT (trackelement), - GES_TIMELINE_ELEMENT (new_trackelement)); - + GESTrackElement *copy, *orig = tmp->data; + GESTrack *track = ges_track_element_get_track (orig); /* FIXME: is position - start + inpoint always the correct splitting * point for control bindings? What coordinate system are control * bindings given in? */ - /* NOTE: control bindings that are not registered in GES are not - * handled */ - ges_track_element_copy_bindings (trackelement, new_trackelement, + copy = ges_clip_copy_track_element_into (new_object, orig, position - start + inpoint); + if (copy && track) + g_hash_table_insert (track_for_copy, gst_object_ref (copy), + gst_object_ref (track)); } - /* FIXME: The below leads to a *second* notify signal for duration */ ELEMENT_SET_FLAG (clip, GES_TIMELINE_ELEMENT_SET_SIMPLE); - _DURATION (clip) = duration; _set_duration0 (GES_TIMELINE_ELEMENT (clip), old_duration); ELEMENT_UNSET_FLAG (clip, GES_TIMELINE_ELEMENT_SET_SIMPLE); + g_object_notify (G_OBJECT (clip), "duration"); + + /* add to the track after the duration change so we don't overlap! */ + for (tmp = GES_CONTAINER_CHILDREN (new_object); tmp; tmp = tmp->next) { + GESTrackElement *copy = tmp->data; + GESTrack *track = g_hash_table_lookup (track_for_copy, copy); + if (track) { + new_object->priv->allow_any_track = TRUE; + ges_track_add_element (track, copy); + new_object->priv->allow_any_track = FALSE; + } + } + + g_hash_table_unref (track_for_copy); return new_object; } @@ -2133,3 +2258,117 @@ ges_clip_get_timeline_time_from_source_frame (GESClip * clip, return GST_CLOCK_DIFF (inpoint_diff, _START (clip)); } + +/** + * ges_clip_add_child_to_track: + * @clip: A #GESClip + * @child: A child of @clip + * @track: The track to add @child to + * @err: Return location for an error + * + * Adds the track element child of the clip to a specific track. + * + * If the given child is already in another track, this will create a copy + * of the child, add it to the clip, and add this copy to the track. + * + * You should only call this whilst a clip is part of a #GESTimeline, and + * for tracks that are in the same timeline. + * + * This method is an alternative to using the + * #GESTimeline::select-tracks-for-object signal, but can be used to + * complement it when, say, you wish to copy a clip's children from one + * track into a new one. + * + * When the child is a core child, it must be added to a track that does + * not already contain another core child of the same clip. If it is not a + * core child (an additional effect), then it must be added to a track + * that already contains one of the core children of the same clip. + * + * This method can also fail if the adding the track element to the track + * would break a configuration rule of the corresponding #GESTimeline, + * such as causing three sources to overlap at a single time, or causing + * a source to completely overlap another in the same track. + * + * Note that @err will not always be set upon failure. + * + * Returns: (transfer none): The element that was added to @track, either + * @child or a copy of child, or %NULL if the element could not be added. + */ +GESTrackElement * +ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child, + GESTrack * track, GError ** err) +{ + GESTimeline *timeline; + GESTrackElement *el; + GESTrack *current_track; + + g_return_val_if_fail (GES_IS_CLIP (clip), NULL); + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (child), NULL); + g_return_val_if_fail (GES_IS_TRACK (track), NULL); + g_return_val_if_fail (!err || !*err, NULL); + + timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip); + + if (!g_list_find (GES_CONTAINER_CHILDREN (clip), child)) { + GST_WARNING_OBJECT (clip, "The track element %" GES_FORMAT " is not " + "a child of the clip", GES_ARGS (child)); + return NULL; + } + + if (!timeline) { + GST_WARNING_OBJECT (clip, "Cannot add children to tracks unless " + "the clip is part of a timeline"); + return NULL; + } + + if (timeline != ges_track_get_timeline (track)) { + GST_WARNING_OBJECT (clip, "Cannot add the children to the track %" + GST_PTR_FORMAT " because its timeline is %" GST_PTR_FORMAT + " rather than that of the clip %" GST_PTR_FORMAT, + track, ges_track_get_timeline (track), timeline); + return NULL; + } + + current_track = ges_track_element_get_track (child); + + if (current_track == track) { + GST_WARNING_OBJECT (clip, "Child %s" GES_FORMAT " is already in the " + "track %" GST_PTR_FORMAT, GES_ARGS (child), track); + return NULL; + } + + /* copy if the element is already in a track */ + if (current_track) { + el = ges_clip_copy_track_element_into (clip, child, GST_CLOCK_TIME_NONE); + if (!el) { + GST_ERROR_OBJECT (clip, "Could not add a copy of the track element %" + GES_FORMAT " to the clip so cannot add it to the track %" + GST_PTR_FORMAT, GES_ARGS (child), track); + return NULL; + } + if (_IS_TOP_EFFECT (child)) { + /* add at next lowest priority */ + ges_clip_set_top_effect_index (clip, GES_BASE_EFFECT (el), + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (child)) + 1); + } + } else { + el = child; + } + + /* FIXME: set error if can not be added to track: + * Either breaks the track rules for the clip, or the timeline + * configuration rules */ + if (!ges_track_add_element (track, el)) { + GST_WARNING_OBJECT (clip, "Could not add the track element %" + GES_FORMAT " to the track %" GST_PTR_FORMAT, GES_ARGS (el), track); + if (el != child) + ges_container_remove (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (el)); + return NULL; + } + + if (GES_IS_SOURCE (el)) + timeline_tree_create_transitions (timeline_get_tree (timeline), + ges_timeline_find_auto_transition); + + return el; +} diff --git a/ges/ges-clip.h b/ges/ges-clip.h index d130bd8262..628d756fa9 100644 --- a/ges/ges-clip.h +++ b/ges/ges-clip.h @@ -155,6 +155,9 @@ GES_API GList * ges_clip_find_track_elements (GESClip * clip, GESTrack * track, GESTrackType track_type, GType type); +GES_API +GESTrackElement * ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child, GESTrack * track, GError **err); + /**************************************************** * Layer * ****************************************************/ diff --git a/ges/ges-internal.h b/ges/ges-internal.h index f33c940ace..1d51ea6fe0 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -83,6 +83,9 @@ GstDebugCategory * _ges_debug (void); #define GES_FORMAT GES_TIMELINE_ELEMENT_FORMAT #define GES_ARGS GES_TIMELINE_ELEMENT_ARGS +#define GES_TRACK_ELEMENT_IS_CORE(child) \ + (ges_track_element_get_creators (GES_TRACK_ELEMENT (child)) != NULL) + #define SUPRESS_UNUSED_WARNING(a) (void)a G_GNUC_INTERNAL gboolean @@ -156,6 +159,14 @@ timeline_create_transitions (GESTimeline * timeline, GESTrackElement * track_ele G_GNUC_INTERNAL void timeline_get_framerate(GESTimeline *self, gint *fps_n, gint *fps_d); +G_GNUC_INTERNAL void +ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving); + +G_GNUC_INTERNAL gboolean +ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip); + +G_GNUC_INTERNAL void +ges_timeline_remove_clip (GESTimeline * timeline, GESClip * clip); G_GNUC_INTERNAL void @@ -406,6 +417,8 @@ G_GNUC_INTERNAL void ges_clip_set_moving_from_layer (GESClip *clip G_GNUC_INTERNAL GESTrackElement* ges_clip_create_track_element (GESClip *clip, GESTrackType type); G_GNUC_INTERNAL GList* ges_clip_create_track_elements (GESClip *clip, GESTrackType type); G_GNUC_INTERNAL gboolean ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTimelineElement * child, GstClockTime inpoint); +G_GNUC_INTERNAL gboolean ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, GESTrack * tack); +G_GNUC_INTERNAL void ges_clip_empty_from_track (GESClip * clip, GESTrack * track); /**************************************************** * GESLayer * diff --git a/ges/ges-layer.c b/ges/ges-layer.c index b5a3a4460a..05e206578c 100644 --- a/ges/ges-layer.c +++ b/ges/ges-layer.c @@ -482,6 +482,7 @@ ges_layer_remove_clip_internal (GESLayer * layer, GESClip * clip, { GESLayer *current_layer; GList *tmp; + GESTimeline *timeline = layer->timeline; GST_DEBUG ("layer:%p, clip:%p", layer, clip); @@ -507,8 +508,10 @@ ges_layer_remove_clip_internal (GESLayer * layer, GESClip * clip, /* inform the clip it's no longer in a layer */ ges_clip_set_layer (clip, NULL); /* so neither in a timeline */ - if (layer->timeline) + if (timeline) { + ges_timeline_remove_clip (timeline, clip); ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip), NULL); + } for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) ges_track_element_set_layer_active (tmp->data, TRUE); @@ -766,25 +769,17 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip) ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip), layer->timeline); - /* emit 'clip-added' */ - /* FIXME: we are emitting the 'clip-added' signal even though we still - * might fail. This is because the timeline uses this signal to create - * the auto-transitions etc needed for timeline_tree_can_move_element - * below, which checks whether the added clip is in a legal position. - * However, we should have a way to check that adding a clip will be - * legal **before** we actually add it! - * A user connecting to 'clip-added' in such a case would receive a - * signal saying that the clip was added, but a return value that says - * something else! */ + /* FIXME: ideally we would only emit if we are going to return TRUE. + * However, for backward-compatibility, we ensure the "clip-added" + * signal is released before the clip's "child-added" signal, which is + * invoked by ges_timeline_add_clip */ g_signal_emit (layer, ges_layer_signals[OBJECT_ADDED], 0, clip); - if (!ELEMENT_FLAG_IS_SET (clip, GES_CLIP_IS_MOVING) && layer->timeline - && !timeline_tree_can_move_element (timeline_get_tree (layer->timeline), - GES_TIMELINE_ELEMENT (clip), - GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), - GES_TIMELINE_ELEMENT_START (clip), - GES_TIMELINE_ELEMENT_DURATION (clip), NULL)) { - GST_INFO_OBJECT (layer, "Clip %" GES_FORMAT, GES_ARGS (clip)); + if (layer->timeline && !ges_timeline_add_clip (layer->timeline, clip)) { + GST_WARNING_OBJECT (layer, "Could not add the clip %" GES_FORMAT + " to the timeline %" GST_PTR_FORMAT, GES_ARGS (clip), layer->timeline); + /* FIXME: change emit signal to FALSE once we are able to delay the + * "clip-added" signal until after ges_timeline_add_clip */ ges_layer_remove_clip_internal (layer, clip, TRUE); return FALSE; } diff --git a/ges/ges-timeline-element.c b/ges/ges-timeline-element.c index 81c9529e62..ade013423c 100644 --- a/ges/ges-timeline-element.c +++ b/ges/ges-timeline-element.c @@ -409,7 +409,8 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass) */ properties[PROP_TIMELINE] = g_param_spec_object ("timeline", "Timeline", - "The timeline the object is in", GES_TYPE_TIMELINE, G_PARAM_READWRITE); + "The timeline the object is in", GES_TYPE_TIMELINE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GESTimelineElement:start: @@ -980,6 +981,9 @@ ges_timeline_element_set_timeline (GESTimelineElement * self, GST_DEBUG_OBJECT (self, "set timeline to %" GST_PTR_FORMAT, timeline); + if (self->timeline == timeline) + return TRUE; + if (timeline != NULL && G_UNLIKELY (self->timeline != NULL)) goto had_timeline; diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index 9775bce90b..ef50a9b5c2 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -152,7 +152,8 @@ struct _GESTimelinePrivate /* While we are creating and adding the TrackElements for a clip, we need to * ignore the child-added signal */ - GESClip *ignore_track_element_added; + gboolean track_elements_moving; + gboolean track_selection_error; GList *groups; guint stream_start_group_id; @@ -702,8 +703,12 @@ ges_timeline_class_init (GESTimelineClass * klass) * @clip: The clip that @track_element is being added to * @track_element: The element being added * - * This will be emitted whenever a new track element is added to a - * clip within the timeline. + * This will be emitted whenever the timeline needs to determine which + * tracks a clip's children should be added to. The track element will + * be added to each of the tracks given in the return. If a track + * element is selected to go into multiple tracks, it will be copied + * into the additional tracks, under the same clip. Note that the copy + * will *not* keep its properties or state in sync with the original. * * Connect to this signal once if you wish to control which element * should be added to which track. Doing so will overwrite the default @@ -711,16 +716,48 @@ ges_timeline_class_init (GESTimelineClass * klass) * #GESTrack:track-type includes the @track_element's * #GESTrackElement:track-type. * - * If you wish to use this, you should add all necessary tracks to the - * timeline before adding any clips. In particular, this signal is - * **not** re-emitted for the existing clips when a new track is added - * to the timeline. + * Note that under the default track selection, if a clip would produce + * multiple core children of the same #GESTrackType, it will choose + * one of the core children arbitrarily to place in the corresponding + * tracks, with a warning for the other core children that are not + * placed in the track. For example, this would happen for a #GESUriClip + * that points to a file that contains multiple audio streams. If you + * wish to choose the stream, you could connect to this signal, and use, + * say, ges_uri_source_asset_get_stream_info() to choose which core + * source to add. + * + * When a clip is first added to a timeline, its core elements will + * be created for the current tracks in the timeline if they have not + * already been created. Then this will be emitted for each of these + * core children to select which tracks, if any, they should be added + * to. It will then be called for any non-core children in the clip. + * + * In addition, if a new track element is ever added to a clip in a + * timeline (and it is not already part of a track) this will be emitted + * to select which tracks the element should be added to. + * + * Finally, as a special case, if a track is added to the timeline + * *after* it already contains clips, then it will request the creation + * of the clips' core elements of the corresponding type, if they have + * not already been created, and this signal will be emitted for each of + * these newly created elements. In addition, this will also be released + * for all other track elements in the timeline's clips that have not + * yet been assigned a track. However, in this final case, the timeline + * will only check whether the newly added track appears in the track + * list. If it does appear, the track element will be added to the newly + * added track. All other tracks in the returned track list are ignored. + * + * In this latter case, track elements that are already part of a track + * will not be asked if they want to be copied into the new track. If + * you wish to do this, you can use ges_clip_add_child_to_track(). + * + * Note that the returned #GPtrArray should own a new reference to each + * of its contained #GESTrack. The timeline will set the #GDestroyNotify + * free function on the #GPtrArray to dereference the elements. * * Returns: (transfer full) (element-type GESTrack): An array of - * #GESTrack-s that @track_element should be added to. If this contains - * more than one track, a copy of @track_element will be added to the - * other tracks. If this is empty, @track_element will also be removed - * from @clip. + * #GESTrack-s that @track_element should be added to, or %NULL to + * not add the element to any track. */ ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT] = g_signal_new ("select-tracks-for-object", G_TYPE_FROM_CLASS (klass), @@ -1297,12 +1334,26 @@ timeline_remove_group (GESTimeline * timeline, GESGroup * group) gst_object_unref (group); } +static GESTrackElement * +_core_in_track (GESTrack * track, GESClip * clip) +{ + GList *tmp; + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + if (GES_TRACK_ELEMENT_IS_CORE (tmp->data) + && ges_track_element_get_track (tmp->data) == track) { + return tmp->data; + } + } + return NULL; +} + static GPtrArray * select_tracks_for_object_default (GESTimeline * timeline, GESClip * clip, GESTrackElement * tr_object, gpointer user_data) { GPtrArray *result; GList *tmp; + GESTrackElement *core; result = g_ptr_array_new (); @@ -1311,6 +1362,24 @@ select_tracks_for_object_default (GESTimeline * timeline, GESTrack *track = GES_TRACK (tmp->data); if ((track->type & ges_track_element_get_track_type (tr_object))) { + if (GES_TRACK_ELEMENT_IS_CORE (tr_object)) { + core = _core_in_track (track, clip); + if (core) { + GST_WARNING_OBJECT (timeline, "The clip '%s' contains multiple " + "core elements of the same %s track type. The core child " + "'%s' has already been chosen arbitrarily for the track %" + GST_PTR_FORMAT ", which means that the other core child " + "'%s' of the same type can not be added to the track. " + "Consider connecting to " + "GESTimeline::select-tracks-for-objects to be able to " + "specify which core element should land in the track", + GES_TIMELINE_ELEMENT_NAME (clip), + ges_track_type_name (track->type), + GES_TIMELINE_ELEMENT_NAME (core), track, + GES_TIMELINE_ELEMENT_NAME (tr_object)); + continue; + } + } gst_object_ref (track); g_ptr_array_add (result, track); } @@ -1320,34 +1389,273 @@ select_tracks_for_object_default (GESTimeline * timeline, return result; } +static GPtrArray * +_get_selected_tracks (GESTimeline * timeline, GESClip * clip, + GESTrackElement * track_element) +{ + guint i, j; + GPtrArray *tracks = NULL; + + g_signal_emit (G_OBJECT (timeline), + ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT], 0, clip, track_element, + &tracks); + + if (tracks == NULL) + tracks = g_ptr_array_new (); + + g_ptr_array_set_free_func (tracks, gst_object_unref); + + /* make sure unique */ + for (i = 0; i < tracks->len;) { + GESTrack *track = GES_TRACK (g_ptr_array_index (tracks, i)); + + for (j = i + 1; j < tracks->len;) { + if (track == g_ptr_array_index (tracks, j)) { + GST_WARNING_OBJECT (timeline, "Found the track %" GST_PTR_FORMAT + " more than once in the return for select-tracks-for-object " + "signal for track element %" GES_FORMAT " in clip %" + GES_FORMAT ". Ignoring the extra track", track, + GES_ARGS (track_element), GES_ARGS (clip)); + g_ptr_array_remove_index (tracks, j); + /* don't increase index since the next track is in its place */ + continue; + } + j++; + } + + if (ges_track_get_timeline (track) != timeline) { + GST_WARNING_OBJECT (timeline, "The track %" GST_PTR_FORMAT + " found in the return for select-tracks-for-object belongs " + "to a different timeline %" GST_PTR_FORMAT ". Ignoring this " + "track", track, ges_track_get_timeline (track)); + g_ptr_array_remove_index (tracks, i); + /* don't increase index since the next track is in its place */ + continue; + } + i++; + } + + return tracks; +} + +/* returns TRUE if track element was successfully added to all the + * selected tracks */ +static gboolean +_add_track_element_to_tracks (GESTimeline * timeline, GESClip * clip, + GESTrackElement * track_element) +{ + guint i; + gboolean ret = TRUE; + GPtrArray *tracks = _get_selected_tracks (timeline, clip, track_element); + + for (i = 0; i < tracks->len; i++) { + GESTrack *track = GES_TRACK (g_ptr_array_index (tracks, i)); + if (!ges_clip_add_child_to_track (clip, track_element, track, NULL)) + ret = FALSE; + } + + g_ptr_array_unref (tracks); + + return ret; +} + +static gboolean +_try_add_track_element_to_track (GESTimeline * timeline, GESClip * clip, + GESTrackElement * track_element, GESTrack * track) +{ + gboolean no_error = TRUE; + GPtrArray *tracks = _get_selected_tracks (timeline, clip, track_element); + + /* if we are trying to add the element to a newly added track, then + * we only check whether the track list contains the newly added track, + * if it does we add the track element to the track, or add a copy if + * the track element is already in a track */ + if (g_ptr_array_find (tracks, track, NULL)) { + if (!ges_clip_add_child_to_track (clip, track_element, track, NULL)) + no_error = FALSE; + } + + g_ptr_array_unref (tracks); + return no_error; +} + +/* accepts NULL */ +void +ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving) +{ + if (timeline) { + LOCK_DYN (timeline); + timeline->priv->track_elements_moving = moving; + UNLOCK_DYN (timeline); + } +} + static void +_set_track_selection_error (GESTimeline * timeline, gboolean error) +{ + LOCK_DYN (timeline); + timeline->priv->track_selection_error = error; + UNLOCK_DYN (timeline); +} + +static gboolean +_get_track_selection_error (GESTimeline * timeline) +{ + gboolean ret; + + LOCK_DYN (timeline); + ret = timeline->priv->track_selection_error; + timeline->priv->track_selection_error = FALSE; + UNLOCK_DYN (timeline); + + return ret; +} + +static void +clip_track_element_added_cb (GESClip * clip, + GESTrackElement * track_element, GESTimeline * timeline) +{ + gboolean error = FALSE; + + if (timeline->priv->track_elements_moving) { + GST_DEBUG_OBJECT (timeline, "Ignoring element added: %" GES_FORMAT + " in %" GES_FORMAT, GES_ARGS (track_element), GES_ARGS (clip)); + return; + } + + if (ges_track_element_get_track (track_element) != NULL) { + GST_DEBUG_OBJECT (timeline, "Not selecting tracks for %" GES_FORMAT + " in %" GES_FORMAT " because it already part of the track %" + GST_PTR_FORMAT, GES_ARGS (track_element), GES_ARGS (clip), + ges_track_element_get_track (track_element)); + return; + } + + if (!_add_track_element_to_tracks (timeline, clip, track_element)) + error = TRUE; + + if (error) + _set_track_selection_error (timeline, TRUE); +} + +static void +clip_track_element_removed_cb (GESClip * clip, + GESTrackElement * track_element, GESTimeline * timeline) +{ + GESTrack *track = ges_track_element_get_track (track_element); + + if (timeline->priv->track_elements_moving) { + GST_DEBUG_OBJECT (timeline, "Ignoring element removed (%" GST_PTR_FORMAT + " in %" GST_PTR_FORMAT, track_element, clip); + + return; + } + + if (track) { + /* if we have non-core elements in the same track, they should be + * removed from them to preserve the rule that a non-core can only be + * in the same track as a core element from the same clip */ + if (GES_TRACK_ELEMENT_IS_CORE (track_element)) + ges_clip_empty_from_track (clip, track); + ges_track_remove_element (track, track_element); + } +} + +/* returns TRUE if no errors in adding to tracks */ +static gboolean +_add_clip_children_to_tracks (GESTimeline * timeline, GESClip * clip, + gboolean add_core, GESTrack * new_track, GList * blacklist) +{ + GList *tmp, *children; + gboolean no_errors = TRUE; + + /* list of children may change if some are copied into tracks */ + children = ges_container_get_children (GES_CONTAINER (clip), FALSE); + for (tmp = children; tmp; tmp = tmp->next) { + GESTrackElement *el = tmp->data; + if (GES_TRACK_ELEMENT_IS_CORE (el) != add_core) + continue; + if (g_list_find (blacklist, el)) + continue; + if (ges_track_element_get_track (el) == NULL) { + gboolean res; + if (new_track) + res = _try_add_track_element_to_track (timeline, clip, el, new_track); + else + res = _add_track_element_to_tracks (timeline, clip, el); + if (!res) + no_errors = FALSE; + } + } + g_list_free_full (children, gst_object_unref); + + return no_errors; +} + +/* returns TRUE if no errors in adding to tracks */ +static gboolean add_object_to_tracks (GESTimeline * timeline, GESClip * clip, GESTrack * track) { - gint i; - GList *tmp, *list; - GESTrackType types, visited_type = GES_TRACK_TYPE_UNKNOWN; + GList *tracks, *tmp, *list, *created, *just_added = NULL; + gboolean no_errors = TRUE; + /* TODO: extend with GError ** argument, which is accepted by + * ges_clip_add_child_to_track */ GST_DEBUG_OBJECT (timeline, "Creating %" GST_PTR_FORMAT " trackelements and adding them to our tracks", clip); - types = ges_clip_get_supported_formats (clip); - if (track) { - if ((types & track->type) == 0) - return; - types = track->type; - } - LOCK_DYN (timeline); - for (i = 0, tmp = timeline->tracks; tmp; tmp = tmp->next, i++) { - GESTrack *track = GES_TRACK (tmp->data); - /* FIXME: visited_type is essentially unused */ - if (((track->type & types) == 0 || (track->type & visited_type))) - continue; - - list = ges_clip_create_track_elements (clip, track->type); - g_list_free (list); - } + tracks = + g_list_copy_deep (timeline->tracks, (GCopyFunc) gst_object_ref, NULL); UNLOCK_DYN (timeline); + /* create core elements */ + for (tmp = tracks; tmp; tmp = tmp->next) { + GESTrack *track = GES_TRACK (tmp->data); + list = ges_clip_create_track_elements (clip, track->type); + for (created = list; created; created = created->next) { + GESTimelineElement *el = created->data; + + gst_object_ref (el); + + /* make track selection be handled by clip_track_element_added_cb + * This is needed for backward-compatibility: when adding a clip to + * a layer, the track is set for the core elements of the clip + * during the child-added signal emission, just before the user's + * own connection. + * NOTE: for the children that have not just been created, they + * are already part of the clip and so child-added will not be + * released. And when a child is selected for multiple tracks, their + * copy will be added to the clip before the track is selected, so + * the track will not be set in the child-added signal */ + _set_track_selection_error (timeline, FALSE); + if (!ges_container_add (GES_CONTAINER (clip), el)) + GST_ERROR_OBJECT (clip, "Could not add the core element %s " + "to the clip", el->name); + if (_get_track_selection_error (timeline)) + no_errors = FALSE; + + gst_object_unref (el); + } + /* just_added only used for pointer comparison, so safe to include + * elements that are now destroyed because they failed to be added to + * the clip */ + just_added = g_list_concat (just_added, list); + } + g_list_free_full (tracks, gst_object_unref); + + /* set the tracks for the other children, with core elements first to + * make sure the non-core can be placed above them in the track (a + * non-core can not be in a track by itself) */ + /* include just_added as a blacklist to ensure we do not try the track + * selection a second time when track selection returns no tracks */ + if (!_add_clip_children_to_tracks (timeline, clip, TRUE, track, just_added)) + no_errors = FALSE; + if (!_add_clip_children_to_tracks (timeline, clip, FALSE, track, just_added)) + no_errors = FALSE; + + g_list_free (just_added); + + return no_errors; } static void @@ -1392,160 +1700,14 @@ layer_auto_transition_changed_cb (GESLayer * layer, g_list_free_full (clips, gst_object_unref); } -static void -clip_track_element_added_cb (GESClip * clip, - GESTrackElement * track_element, GESTimeline * timeline) -{ - guint i; - GESTrack *track; - gboolean is_source; - GPtrArray *tracks = NULL; - GESTrackElement *existing_src = NULL; - - if (timeline->priv->ignore_track_element_added == clip) { - GST_DEBUG_OBJECT (timeline, "Ignoring element added (%" GST_PTR_FORMAT - " in %" GST_PTR_FORMAT, track_element, clip); - - return; - } - - if (ges_track_element_get_track (track_element)) { - GST_WARNING_OBJECT (track_element, "Already in a track"); - - return; - } - - g_signal_emit (G_OBJECT (timeline), - ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT], 0, clip, track_element, - &tracks); - /* FIXME: make sure each track in the list is unique */ - /* FIXME: make sure each track is part of the same timeline as the clip, - * and warn and ignore the track if it isn't */ - - if (!tracks || tracks->len == 0) { - GST_WARNING_OBJECT (timeline, "Got no Track to add %p (type %s), removing" - " from clip (stopping 'child-added' signal emission).", - track_element, ges_track_type_name (ges_track_element_get_track_type - (track_element))); - - if (tracks) - g_ptr_array_unref (tracks); - - g_signal_stop_emission_by_name (clip, "child-added"); - ges_container_remove (GES_CONTAINER (clip), - GES_TIMELINE_ELEMENT (track_element)); - - return; - } - - /* We add the current element to the first track */ - track = g_ptr_array_index (tracks, 0); - - is_source = g_type_is_a (G_OBJECT_TYPE (track_element), GES_TYPE_SOURCE); - if (is_source) - existing_src = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE); - - if (existing_src == NULL) { - if (!ges_track_add_element (track, track_element)) { - GST_WARNING_OBJECT (clip, "Failed to add track element to track"); - ges_container_remove (GES_CONTAINER (clip), - GES_TIMELINE_ELEMENT (track_element)); - /* FIXME: unref all the individual track in tracks */ - g_ptr_array_unref (tracks); - return; - } - } else { - GST_INFO_OBJECT (clip, "Already had a Source Element in %" GST_PTR_FORMAT - " of type %s, removing new one. (stopping 'child-added' emission)", - track, G_OBJECT_TYPE_NAME (track_element)); - g_signal_stop_emission_by_name (clip, "child-added"); - ges_container_remove (GES_CONTAINER (clip), - GES_TIMELINE_ELEMENT (track_element)); - } - gst_object_unref (track); - g_clear_object (&existing_src); - - /* And create copies to add to other tracks */ - timeline->priv->ignore_track_element_added = clip; - for (i = 1; i < tracks->len; i++) { - GESTrack *track; - GESTrackElement *track_element_copy; - - track = g_ptr_array_index (tracks, i); - if (is_source) - existing_src = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE); - if (existing_src == NULL) { - ges_container_remove (GES_CONTAINER (clip), - GES_TIMELINE_ELEMENT (track_element)); - gst_object_unref (track); - /* FIXME: tracks is needed for the next loop after continue */ - g_ptr_array_unref (tracks); - continue; - } else { - GST_INFO_OBJECT (clip, "Already had a Source Element in %" GST_PTR_FORMAT - " of type %s, removing new one. (stopping 'child-added' emission)", - track, G_OBJECT_TYPE_NAME (track_element)); - g_signal_stop_emission_by_name (clip, "child-added"); - ges_container_remove (GES_CONTAINER (clip), - GES_TIMELINE_ELEMENT (track_element)); - } - /* 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)); - /* if a core child, mark the copy as core so it can be added */ - if (ges_track_element_get_creators (track_element)) - ges_track_element_add_creator (track_element_copy, clip); - - GST_LOG_OBJECT (timeline, "Trying to add %p to track %p", - track_element_copy, track); - - if (!ges_container_add (GES_CONTAINER (clip), - GES_TIMELINE_ELEMENT (track_element_copy))) { - GST_WARNING_OBJECT (clip, "Failed to add track element to clip"); - gst_object_unref (track_element_copy); - /* FIXME: unref **all** the individual track in tracks */ - g_ptr_array_unref (tracks); - return; - } - - if (!ges_track_add_element (track, track_element_copy)) { - GST_WARNING_OBJECT (clip, "Failed to add track element to track"); - ges_container_remove (GES_CONTAINER (clip), - GES_TIMELINE_ELEMENT (track_element_copy)); - /* FIXME: should we also stop the child-added and child-removed - * emissions? */ - gst_object_unref (track_element_copy); - /* FIXME: unref **all** the individual track in tracks */ - g_ptr_array_unref (tracks); - return; - } - - gst_object_unref (track); - } - timeline->priv->ignore_track_element_added = NULL; - g_ptr_array_unref (tracks); - if (GES_IS_SOURCE (track_element)) - timeline_tree_create_transitions (timeline->priv->tree, - ges_timeline_find_auto_transition); -} - -static void -clip_track_element_removed_cb (GESClip * clip, - GESTrackElement * track_element, GESTimeline * timeline) -{ - GESTrack *track = ges_track_element_get_track (track_element); - - if (track) - ges_track_remove_element (track, track_element); -} - -static void -layer_object_added_cb (GESLayer * layer, GESClip * clip, GESTimeline * timeline) +/* returns TRUE if selecting of tracks did not error */ +gboolean +ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip) { GESProject *project; + gboolean ret; + /* TODO: extend with GError ** argument, which is accepted by + * ges_clip_add_child_to_track */ /* We make sure not to be connected twice */ g_signal_handlers_disconnect_by_func (clip, clip_track_element_added_cb, @@ -1564,11 +1726,11 @@ layer_object_added_cb (GESLayer * layer, GESClip * clip, GESTimeline * timeline) "TrackElement", clip); timeline_tree_create_transitions (timeline->priv->tree, ges_timeline_find_auto_transition); - return; + ret = TRUE; + } else { + ret = add_object_to_tracks (timeline, clip, NULL); } - add_object_to_tracks (timeline, clip, NULL); - GST_DEBUG ("Making sure that the asset is in our project"); project = GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline))); @@ -1576,6 +1738,8 @@ layer_object_added_cb (GESLayer * layer, GESClip * clip, GESTimeline * timeline) ges_extractable_get_asset (GES_EXTRACTABLE (clip))); GST_DEBUG ("Done"); + + return ret; } static void @@ -1589,11 +1753,10 @@ layer_priority_changed_cb (GESLayer * layer, sort_layers); } -static void -layer_object_removed_cb (GESLayer * layer, GESClip * clip, - GESTimeline * timeline) +void +ges_timeline_remove_clip (GESTimeline * timeline, GESClip * clip) { - GList *trackelements, *tmp; + GList *tmp; if (ges_clip_is_moving_from_layer (clip)) { GST_DEBUG ("Clip %p is moving from a layer to another, not doing" @@ -1601,41 +1764,19 @@ layer_object_removed_cb (GESLayer * layer, GESClip * clip, return; } - GST_DEBUG_OBJECT (timeline, "Clip %" GES_FORMAT " removed from layer %p", - GES_ARGS (clip), layer); + GST_DEBUG_OBJECT (timeline, "Clip %" GES_FORMAT " removed from layer", + GES_ARGS (clip)); - /* Go over the clip's track element and figure out which one belongs to - * the list of tracks we control */ + LOCK_DYN (timeline); + for (tmp = timeline->tracks; tmp; tmp = tmp->next) + ges_clip_empty_from_track (clip, tmp->data); + UNLOCK_DYN (timeline); - trackelements = ges_container_get_children (GES_CONTAINER (clip), FALSE); - for (tmp = trackelements; tmp; tmp = tmp->next) { - GESTrackElement *track_element = (GESTrackElement *) tmp->data; - GESTrack *track = ges_track_element_get_track (track_element); - - if (!track) - continue; - - GST_DEBUG_OBJECT (timeline, "Trying to remove TrackElement %p", - track_element); - - /* FIXME Check if we should actually check that we control the - * track in the new management of TrackElement context */ - LOCK_DYN (timeline); - if (G_LIKELY (g_list_find_custom (timeline->priv->priv_tracks, track, - (GCompareFunc) custom_find_track) || track == NULL)) { - GST_DEBUG ("Belongs to one of the tracks we control"); - - ges_track_remove_element (track, track_element); - } - UNLOCK_DYN (timeline); - } g_signal_handlers_disconnect_by_func (clip, clip_track_element_added_cb, timeline); g_signal_handlers_disconnect_by_func (clip, clip_track_element_removed_cb, timeline); - g_list_free_full (trackelements, gst_object_unref); - GST_DEBUG ("Done"); } @@ -1971,6 +2112,17 @@ ges_timeline_append_layer (GESTimeline * timeline) * * Add a layer to the timeline. * + * If the layer contains #GESClip-s, then this may trigger the creation of + * their core track element children for the timeline's tracks, and the + * placement of the clip's children in the tracks of the timeline using + * #GESTimeline::select-tracks-for-object. Some errors may occur if this + * would break one of the configuration rules of the timeline in one of + * its tracks. In such cases, some track elements would fail to be added + * to their tracks, but this method would still return %TRUE. As such, it + * is advised that you only add clips to layers that already part of a + * timeline. In such situations, ges_layer_add_clip() is able to fail if + * adding the clip would cause such an error. + * * Deprecated: 1.18: This method requires you to ensure the layer's * #GESLayer:priority will be unique to the timeline. Use * ges_timeline_append_layer() and ges_timeline_move_layer() instead. @@ -2025,10 +2177,6 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer) ges_layer_set_timeline (layer, timeline); /* Connect to 'clip-added'/'clip-removed' signal from the new layer */ - g_signal_connect_after (layer, "clip-added", - G_CALLBACK (layer_object_added_cb), timeline); - g_signal_connect_after (layer, "clip-removed", - G_CALLBACK (layer_object_removed_cb), timeline); g_signal_connect (layer, "notify::priority", G_CALLBACK (layer_priority_changed_cb), timeline); g_signal_connect (layer, "notify::auto-transition", @@ -2041,12 +2189,9 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer) /* add any existing clips to the timeline */ objects = ges_layer_get_clips (layer); - for (tmp = objects; tmp; tmp = tmp->next) { - layer_object_added_cb (layer, tmp->data, timeline); - gst_object_unref (tmp->data); - tmp->data = NULL; - } - g_list_free (objects); + for (tmp = objects; tmp; tmp = tmp->next) + ges_timeline_add_clip (timeline, tmp->data); + g_list_free_full (objects, gst_object_unref); return TRUE; } @@ -2080,18 +2225,12 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESLayer * layer) /* remove objects from any private data structures */ layer_objects = ges_layer_get_clips (layer); - for (tmp = layer_objects; tmp; tmp = tmp->next) { - layer_object_removed_cb (layer, GES_CLIP (tmp->data), timeline); - gst_object_unref (G_OBJECT (tmp->data)); - tmp->data = NULL; - } - g_list_free (layer_objects); + for (tmp = layer_objects; tmp; tmp = tmp->next) + ges_timeline_remove_clip (timeline, tmp->data); + g_list_free_full (layer_objects, gst_object_unref); /* Disconnect signals */ GST_DEBUG ("Disconnecting signal callbacks"); - g_signal_handlers_disconnect_by_func (layer, layer_object_added_cb, timeline); - g_signal_handlers_disconnect_by_func (layer, layer_object_removed_cb, - timeline); g_signal_handlers_disconnect_by_func (layer, layer_priority_changed_cb, timeline); g_signal_handlers_disconnect_by_func (layer, @@ -2115,8 +2254,16 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESLayer * layer) * @timeline: The #GESTimeline * @track: (transfer full): The track to add * - * Add a track to the timeline. Existing #GESClip-s in the timeline will, - * where appropriate, add their controlled elements to the new track. + * Add a track to the timeline. + * + * If the timeline already contains clips, then this may trigger the + * creation of their core track element children for the track, and the + * placement of the clip's children in the track of the timeline using + * #GESTimeline::select-tracks-for-object. Some errors may occur if this + * would break one of the configuration rules for the timeline in the + * track. In such cases, some track elements would fail to be added to the + * track, but this method would still return %TRUE. As such, it is advised + * that you avoid adding tracks to timelines that already contain clips. * * Returns: %TRUE if @track was properly added. */ @@ -2184,13 +2331,10 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track) GList *objects, *obj; objects = ges_layer_get_clips (tmp->data); - for (obj = objects; obj; obj = obj->next) { - GESClip *clip = obj->data; + for (obj = objects; obj; obj = obj->next) + add_object_to_tracks (timeline, obj->data, track); - add_object_to_tracks (timeline, clip, track); - gst_object_unref (clip); - } - g_list_free (objects); + g_list_free_full (objects, gst_object_unref); } /* FIXME Check if we should rollback if we can't sync state */ @@ -2240,8 +2384,22 @@ ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track) gst_object_unref (tr_priv->pad); priv->priv_tracks = g_list_remove (priv->priv_tracks, tr_priv); UNLOCK_DYN (timeline); - timeline->tracks = g_list_remove (timeline->tracks, track); + /* empty track of all elements that belong to the timeline's clips */ + /* elements with no parent can stay in the track, but their timeline + * will be set to NULL when the track's timeline is set to NULL */ + + for (tmp = timeline->layers; tmp; tmp = tmp->next) { + GList *clips, *clip; + clips = ges_layer_get_clips (tmp->data); + + for (clip = clips; clip; clip = clip->next) + ges_clip_empty_from_track (clip->data, track); + + g_list_free_full (clips, gst_object_unref); + } + + timeline->tracks = g_list_remove (timeline->tracks, track); ges_track_set_timeline (track, NULL); /* Remove ghost pad */ diff --git a/ges/ges-track-element.c b/ges/ges-track-element.c index 646a7c0818..ec7d178442 100644 --- a/ges/ges-track-element.c +++ b/ges/ges-track-element.c @@ -1049,12 +1049,20 @@ ges_track_element_add_children_props (GESTrackElement * self, gboolean ges_track_element_set_track (GESTrackElement * object, GESTrack * track) { - gboolean ret = TRUE; + GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (object); g_return_val_if_fail (object->priv->nleobject, FALSE); GST_DEBUG_OBJECT (object, "new track: %" GST_PTR_FORMAT, track); + if (GES_IS_CLIP (parent) + && !ges_clip_can_set_track_of_child (GES_CLIP (parent), object, track)) { + GST_WARNING_OBJECT (object, "The parent clip %" GES_FORMAT " would " + "not allow the track to be set to %" GST_PTR_FORMAT, + GES_ARGS (parent), track); + return FALSE; + } + object->priv->track = track; if (object->priv->track) { @@ -1065,7 +1073,7 @@ ges_track_element_set_track (GESTrackElement * object, GESTrack * track) } g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_TRACK]); - return ret; + return TRUE; } void diff --git a/ges/ges-track.c b/ges/ges-track.c index af57dba018..d9613d961f 100644 --- a/ges/ges-track.c +++ b/ges/ges-track.c @@ -382,7 +382,8 @@ ges_track_get_composition (GESTrack * track) * accessing it */ static gboolean -remove_object_internal (GESTrack * track, GESTrackElement * object) +remove_object_internal (GESTrack * track, GESTrackElement * object, + gboolean emit) { GESTrackPrivate *priv; GstElement *nleobject; @@ -392,25 +393,30 @@ remove_object_internal (GESTrack * track, GESTrackElement * object) priv = track->priv; if (G_UNLIKELY (ges_track_element_get_track (object) != track)) { - GST_WARNING ("Object belongs to another track"); + GST_WARNING_OBJECT (track, "Object belongs to another track"); return FALSE; } + if (!ges_track_element_set_track (object, NULL)) { + GST_WARNING_OBJECT (track, "Failed to unset the track for %" GES_FORMAT, + GES_ARGS (object)); + return FALSE; + } + ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL); + if ((nleobject = ges_track_element_get_nleobject (object))) { GST_DEBUG ("Removing NleObject '%s' from composition '%s'", GST_ELEMENT_NAME (nleobject), GST_ELEMENT_NAME (priv->composition)); if (!ges_nle_composition_remove_object (priv->composition, nleobject)) { - GST_WARNING ("Failed to remove nleobject from composition"); + GST_WARNING_OBJECT (track, "Failed to remove nleobject from composition"); return FALSE; } } - ges_track_element_set_track (object, NULL); - ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL); - - g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_REMOVED], 0, - GES_TRACK_ELEMENT (object)); + if (emit) + g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_REMOVED], 0, + GES_TRACK_ELEMENT (object)); gst_object_unref (object); @@ -420,7 +426,7 @@ remove_object_internal (GESTrack * track, GESTrackElement * object) static void dispose_trackelements_foreach (GESTrackElement * trackelement, GESTrack * track) { - remove_object_internal (track, trackelement); + remove_object_internal (track, trackelement, TRUE); } /* GstElement virtual methods */ @@ -918,9 +924,19 @@ ges_track_new (GESTrackType type, GstCaps * caps) void ges_track_set_timeline (GESTrack * track, GESTimeline * timeline) { + GSequenceIter *it; + g_return_if_fail (GES_IS_TRACK (track)); + g_return_if_fail (timeline == NULL || GES_IS_TIMELINE (timeline)); GST_DEBUG ("track:%p, timeline:%p", track, timeline); track->priv->timeline = timeline; + + for (it = g_sequence_get_begin_iter (track->priv->trackelements_by_start); + g_sequence_iter_is_end (it) == FALSE; it = g_sequence_iter_next (it)) { + GESTimelineElement *trackelement = + GES_TIMELINE_ELEMENT (g_sequence_get (it)); + ges_timeline_element_set_timeline (trackelement, timeline); + } track_resort_and_fill_gaps (track); } @@ -1089,6 +1105,31 @@ notify: GST_DEBUG_OBJECT (track, "The track has been set to mixing = %d", mixing); } +static gboolean +remove_element_internal (GESTrack * track, GESTrackElement * object, + gboolean emit) +{ + GSequenceIter *it; + GESTrackPrivate *priv = track->priv; + + GST_DEBUG_OBJECT (track, "Removing %" GST_PTR_FORMAT, object); + + it = g_hash_table_lookup (priv->trackelements_iter, object); + g_sequence_remove (it); + + if (remove_object_internal (track, object, emit) == TRUE) { + ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL); + + return TRUE; + } + + g_hash_table_insert (track->priv->trackelements_iter, object, + g_sequence_insert_sorted (track->priv->trackelements_by_start, object, + (GCompareDataFunc) element_start_compare, NULL)); + + return FALSE; +} + /** * ges_track_add_element: * @track: A #GESTrack @@ -1097,6 +1138,9 @@ notify: * Adds the given track element to the track, which takes ownership of the * element. * + * Note that this can fail if it would break a configuration rule of the + * track's #GESTimeline. + * * Note that a #GESTrackElement can only be added to one track. * * Returns: %TRUE if @object was successfully added to @track. @@ -1104,8 +1148,14 @@ notify: gboolean ges_track_add_element (GESTrack * track, GESTrackElement * object) { + GESTimeline *timeline; + GESTimelineElement *el; + g_return_val_if_fail (GES_IS_TRACK (track), FALSE); g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + + el = GES_TIMELINE_ELEMENT (object); + CHECK_THREAD (track); GST_DEBUG ("track:%p, object:%p", track, object); @@ -1117,12 +1167,14 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object) return FALSE; } - if (G_UNLIKELY (!ges_track_element_set_track (object, track))) { - GST_ERROR ("Couldn't properly add the object to the Track"); + if (!ges_track_element_set_track (object, track)) { + GST_WARNING_OBJECT (track, "Failed to set the track for %" GES_FORMAT, + GES_ARGS (object)); gst_object_ref_sink (object); gst_object_unref (object); return FALSE; } + ges_timeline_element_set_timeline (el, NULL); GST_DEBUG ("Adding object %s to ourself %s", GST_OBJECT_NAME (ges_track_element_get_nleobject (object)), @@ -1131,6 +1183,7 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object) if (G_UNLIKELY (!ges_nle_composition_add_object (track->priv->composition, ges_track_element_get_nleobject (object)))) { GST_WARNING ("Couldn't add object to the NleComposition"); + ges_track_element_set_track (object, NULL); gst_object_ref_sink (object); gst_object_unref (object); return FALSE; @@ -1141,8 +1194,20 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object) g_sequence_insert_sorted (track->priv->trackelements_by_start, object, (GCompareDataFunc) element_start_compare, NULL)); - ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), - track->priv->timeline); + timeline = track->priv->timeline; + ges_timeline_element_set_timeline (el, timeline); + if (timeline + && !timeline_tree_can_move_element (timeline_get_tree (timeline), el, + GES_TIMELINE_ELEMENT_LAYER_PRIORITY (el), el->start, el->duration, + NULL)) { + GST_WARNING_OBJECT (track, + "Could not add the track element %" GES_FORMAT + " to the track because it breaks the timeline " "configuration rules", + GES_ARGS (el)); + remove_element_internal (track, object, FALSE); + return FALSE; + } + g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_ADDED], 0, GES_TRACK_ELEMENT (object)); @@ -1188,31 +1253,12 @@ ges_track_get_elements (GESTrack * track) gboolean ges_track_remove_element (GESTrack * track, GESTrackElement * object) { - GSequenceIter *it; - GESTrackPrivate *priv; - g_return_val_if_fail (GES_IS_TRACK (track), FALSE); g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + CHECK_THREAD (track); - priv = track->priv; - - GST_DEBUG_OBJECT (track, "Removing %" GST_PTR_FORMAT, object); - - it = g_hash_table_lookup (priv->trackelements_iter, object); - g_sequence_remove (it); - - if (remove_object_internal (track, object) == TRUE) { - ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL); - - return TRUE; - } - - g_hash_table_insert (track->priv->trackelements_iter, object, - g_sequence_insert_sorted (track->priv->trackelements_by_start, object, - (GCompareDataFunc) element_start_compare, NULL)); - - return FALSE; + return remove_element_internal (track, object, TRUE); } /** diff --git a/tests/check/ges/basic.c b/tests/check/ges/basic.c index d37d9c1a9c..b50f87bedd 100644 --- a/tests/check/ges/basic.c +++ b/tests/check/ges/basic.c @@ -54,8 +54,7 @@ GST_START_TEST (test_ges_scenario) layers = ges_timeline_get_layers (timeline); fail_unless (g_list_find (layers, layer) != NULL); - g_list_foreach (layers, (GFunc) gst_object_unref, NULL); - g_list_free (layers); + g_list_free_full (layers, gst_object_unref); /* Give the Timeline a Track */ GST_DEBUG ("Create a Track"); @@ -101,6 +100,7 @@ GST_START_TEST (test_ges_scenario) * 3 by the timeline * 1 by the track */ ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3); + fail_unless (ges_track_element_get_track (trackelement) == track); GST_DEBUG ("Remove the Clip from the layer"); @@ -108,6 +108,10 @@ GST_START_TEST (test_ges_scenario) gst_object_ref (source); ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); fail_unless (ges_layer_remove_clip (layer, GES_CLIP (source))); + /* track elements emptied from the track, but stay in clip */ + fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) == + GES_TIMELINE_ELEMENT (source)); + fail_unless (ges_track_element_get_track (trackelement) == NULL); ASSERT_OBJECT_REFCOUNT (source, "source", 1); ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); tmp_layer = ges_clip_get_layer (GES_CLIP (source)); @@ -118,7 +122,7 @@ GST_START_TEST (test_ges_scenario) /* Remove the track from the timeline */ gst_object_ref (track); fail_unless (ges_timeline_remove_track (timeline, track)); - fail_unless (ges_track_get_timeline (track) == NULL); + assert_num_in_track (track, 0); tracks = ges_timeline_get_tracks (timeline); fail_unless (tracks == NULL); @@ -149,12 +153,23 @@ GST_END_TEST; * and then add it to the timeline. */ +#define _CREATE_SOURCE(layer, clip, start, duration) \ +{ \ + GESAsset *asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); \ + GST_DEBUG ("Creating a source"); \ + fail_unless (clip = ges_layer_add_asset (layer, asset, start, 0, \ + duration, GES_TRACK_TYPE_UNKNOWN)); \ + assert_layer(clip, layer); \ + ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); \ + gst_object_unref (asset); \ +} + GST_START_TEST (test_ges_timeline_add_layer) { GESTimeline *timeline; - GESLayer *layer, *tmp_layer; + GESLayer *layer; GESTrack *track; - GESTestClip *s1, *s2, *s3; + GESClip *s1, *s2, *s3; GList *trackelements, *layers; GESTrackElement *trackelement; @@ -180,34 +195,6 @@ GST_START_TEST (test_ges_timeline_add_layer) fail_unless (ges_track_get_timeline (track) == timeline); fail_unless ((gpointer) GST_ELEMENT_PARENT (track) == (gpointer) timeline); - /* Create a source and add it to the Layer */ - GST_DEBUG ("Creating a source"); - s1 = ges_test_clip_new (); - fail_unless (s1 != NULL); - fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1))); - tmp_layer = ges_clip_get_layer (GES_CLIP (s1)); - fail_unless (tmp_layer == layer); - ASSERT_OBJECT_REFCOUNT (layer, "layer", 2); - gst_object_unref (tmp_layer); - - GST_DEBUG ("Creating a source"); - s2 = ges_test_clip_new (); - fail_unless (s2 != NULL); - fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2))); - tmp_layer = ges_clip_get_layer (GES_CLIP (s2)); - fail_unless (tmp_layer == layer); - ASSERT_OBJECT_REFCOUNT (layer, "layer", 2); - gst_object_unref (tmp_layer); - - GST_DEBUG ("Creating a source"); - s3 = ges_test_clip_new (); - fail_unless (s3 != NULL); - fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3))); - tmp_layer = ges_clip_get_layer (GES_CLIP (s3)); - fail_unless (tmp_layer == layer); - ASSERT_OBJECT_REFCOUNT (layer, "layer", 2); - gst_object_unref (tmp_layer); - GST_DEBUG ("Add the layer to the timeline"); fail_unless (ges_timeline_add_layer (timeline, layer)); /* The timeline steals our reference to the layer */ @@ -215,8 +202,14 @@ GST_START_TEST (test_ges_timeline_add_layer) fail_unless (layer->timeline == timeline); layers = ges_timeline_get_layers (timeline); fail_unless (g_list_find (layers, layer) != NULL); - g_list_foreach (layers, (GFunc) gst_object_unref, NULL); - g_list_free (layers); + g_list_free_full (layers, gst_object_unref); + + _CREATE_SOURCE (layer, s1, 0, 10); + ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); + _CREATE_SOURCE (layer, s2, 20, 10); + ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); + _CREATE_SOURCE (layer, s3, 40, 10); + ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); /* Make sure the associated TrackElements are in the Track */ trackelements = GES_CONTAINER_CHILDREN (s1); @@ -266,9 +259,9 @@ GST_END_TEST; GST_START_TEST (test_ges_timeline_add_layer_first) { GESTimeline *timeline; - GESLayer *layer, *tmp_layer; + GESLayer *layer; GESTrack *track; - GESTestClip *s1, *s2, *s3; + GESClip *s1, *s2, *s3; GList *trackelements, *tmp, *layers; ges_init (); @@ -286,30 +279,9 @@ GST_START_TEST (test_ges_timeline_add_layer_first) track = GES_TRACK (ges_video_track_new ()); fail_unless (track != NULL); - /* Create a source and add it to the Layer */ - GST_DEBUG ("Creating a source"); - s1 = ges_test_clip_new (); - fail_unless (s1 != NULL); - fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1))); - tmp_layer = ges_clip_get_layer (GES_CLIP (s1)); - fail_unless (tmp_layer == layer); - gst_object_unref (tmp_layer); - - GST_DEBUG ("Creating a source"); - s2 = ges_test_clip_new (); - fail_unless (s2 != NULL); - fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2))); - tmp_layer = ges_clip_get_layer (GES_CLIP (s2)); - fail_unless (tmp_layer == layer); - gst_object_unref (tmp_layer); - - GST_DEBUG ("Creating a source"); - s3 = ges_test_clip_new (); - fail_unless (s3 != NULL); - fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3))); - tmp_layer = ges_clip_get_layer (GES_CLIP (s3)); - fail_unless (tmp_layer == layer); - gst_object_unref (tmp_layer); + _CREATE_SOURCE (layer, s1, 0, 10); + _CREATE_SOURCE (layer, s2, 20, 10); + _CREATE_SOURCE (layer, s3, 40, 10); GST_DEBUG ("Add the layer to the timeline"); fail_unless (ges_timeline_add_layer (timeline, layer)); @@ -318,8 +290,7 @@ GST_START_TEST (test_ges_timeline_add_layer_first) fail_unless (layer->timeline == timeline); layers = ges_timeline_get_layers (timeline); fail_unless (g_list_find (layers, layer) != NULL); - g_list_foreach (layers, (GFunc) gst_object_unref, NULL); - g_list_free (layers); + g_list_free_full (layers, gst_object_unref); GST_DEBUG ("Add the track to the timeline"); fail_unless (ges_timeline_add_track (timeline, track)); @@ -369,9 +340,9 @@ GST_END_TEST; GST_START_TEST (test_ges_timeline_remove_track) { GESTimeline *timeline; - GESLayer *layer, *tmp_layer; + GESLayer *layer; GESTrack *track; - GESTestClip *s1, *s2, *s3; + GESClip *s1, *s2, *s3; GESTrackElement *t1, *t2, *t3; GList *trackelements, *tmp, *layers; @@ -390,32 +361,11 @@ GST_START_TEST (test_ges_timeline_remove_track) track = GES_TRACK (ges_video_track_new ()); fail_unless (track != NULL); - /* Create a source and add it to the Layer */ - GST_DEBUG ("Creating a source"); - s1 = ges_test_clip_new (); - fail_unless (s1 != NULL); - fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1))); - tmp_layer = ges_clip_get_layer (GES_CLIP (s1)); - fail_unless (tmp_layer == layer); - gst_object_unref (tmp_layer); + _CREATE_SOURCE (layer, s1, 0, 10); ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); - - GST_DEBUG ("Creating a source"); - s2 = ges_test_clip_new (); - fail_unless (s2 != NULL); - fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2))); - tmp_layer = ges_clip_get_layer (GES_CLIP (s2)); - fail_unless (tmp_layer == layer); - gst_object_unref (tmp_layer); + _CREATE_SOURCE (layer, s2, 20, 10); ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); - - GST_DEBUG ("Creating a source"); - s3 = ges_test_clip_new (); - fail_unless (s3 != NULL); - fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3))); - tmp_layer = ges_clip_get_layer (GES_CLIP (s3)); - fail_unless (tmp_layer == layer); - gst_object_unref (tmp_layer); + _CREATE_SOURCE (layer, s3, 40, 10); ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); GST_DEBUG ("Add the layer to the timeline"); @@ -426,8 +376,7 @@ GST_START_TEST (test_ges_timeline_remove_track) layers = ges_timeline_get_layers (timeline); fail_unless (g_list_find (layers, layer) != NULL); - g_list_foreach (layers, (GFunc) gst_object_unref, NULL); - g_list_free (layers); + g_list_free_full (layers, gst_object_unref); ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); GST_DEBUG ("Add the track to the timeline"); @@ -485,8 +434,18 @@ GST_START_TEST (test_ges_timeline_remove_track) * 1 by the timeline */ ASSERT_OBJECT_REFCOUNT (t3, "t3", 3); + fail_unless (ges_track_element_get_track (t1) == track); + fail_unless (ges_track_element_get_track (t2) == track); + fail_unless (ges_track_element_get_track (t3) == track); + /* remove the track and check that the track elements have been released */ + gst_object_ref (track); fail_unless (ges_timeline_remove_track (timeline, track)); + assert_num_in_track (track, 0); + gst_object_unref (track); + fail_unless (ges_track_element_get_track (t1) == NULL); + fail_unless (ges_track_element_get_track (t2) == NULL); + fail_unless (ges_track_element_get_track (t3) == NULL); ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 1); ASSERT_OBJECT_REFCOUNT (t2, "trackelement", 1); @@ -507,22 +466,67 @@ GST_END_TEST; typedef struct { - GESTestClip **o1, **o2, **o3; - GESTrack **tr1, **tr2; + GESClip *clips[4]; + guint num_calls[4]; + GESTrackElement *effects[3]; + GESTrack *tr1, *tr2; + guint num_unrecognised; } SelectTracksData; static GPtrArray * select_tracks_cb (GESTimeline * timeline, GESClip * clip, - GESTrackElement * track_element, SelectTracksData * st_data) + GESTrackElement * track_element, SelectTracksData * data) { - GESTrack *track; - GPtrArray *ret = g_ptr_array_new (); - track = (clip == (GESClip *) * st_data->o2) ? *st_data->tr2 : *st_data->tr1; + gboolean track1 = FALSE; + gboolean track2 = FALSE; + guint i; + gboolean recognise_clip = FALSE; - gst_object_ref (track); + for (i = 0; i < 4; i++) { + if (clip == data->clips[i]) { + data->num_calls[i]++; + recognise_clip = TRUE; + } + } - g_ptr_array_add (ret, track); + if (!recognise_clip) { + GST_DEBUG_OBJECT (timeline, "unrecognised clip %" GES_FORMAT " for " + "track element %" GES_FORMAT, GES_ARGS (clip), + GES_ARGS (track_element)); + data->num_unrecognised++; + return ret; + } + + if (GES_IS_BASE_EFFECT (track_element)) { + if (track_element == data->effects[0]) { + track1 = TRUE; + } else if (track_element == data->effects[1]) { + track1 = TRUE; + track2 = TRUE; + } else if (track_element == data->effects[2]) { + track2 = TRUE; + } else { + GST_DEBUG_OBJECT (timeline, "unrecognised effect %" GES_FORMAT, + GES_ARGS (track_element)); + data->num_unrecognised++; + } + } else if (GES_IS_SOURCE (track_element)) { + if (clip == data->clips[0] || clip == data->clips[1]) + track1 = TRUE; + if (clip == data->clips[1] || clip == data->clips[2]) + track2 = TRUE; + /* clips[3] has no tracks selected */ + } else { + GST_DEBUG_OBJECT (timeline, "unrecognised track element %" GES_FORMAT, + GES_ARGS (track_element)); + data->num_unrecognised++; + } + + if (track1) + g_ptr_array_add (ret, gst_object_ref (data->tr1)); + if (track2) + g_ptr_array_add (ret, gst_object_ref (data->tr2)); return ret; } @@ -530,12 +534,14 @@ select_tracks_cb (GESTimeline * timeline, GESClip * clip, GST_START_TEST (test_ges_timeline_multiple_tracks) { GESTimeline *timeline; - GESLayer *layer, *tmp_layer; + GESLayer *layer; GESTrack *track1, *track2; - GESTestClip *s1, *s2, *s3; - GESTrackElement *t1, *t2, *t3; + GESClip *s1, *s2, *s3, *s4; + GESTrackElement *e1, *e2, *e3, *el, *el2, *e_copy; + gboolean found_e1 = FALSE, found_e2 = FALSE, found_e3 = FALSE; GList *trackelements, *tmp, *layers; - SelectTracksData st_data = { &s1, &s2, &s3, &track1, &track2 }; + GstControlSource *ctrl_source; + SelectTracksData st_data; ges_init (); @@ -544,9 +550,6 @@ GST_START_TEST (test_ges_timeline_multiple_tracks) timeline = ges_timeline_new (); fail_unless (timeline != NULL); - g_signal_connect (timeline, "select-tracks-for-object", - G_CALLBACK (select_tracks_cb), &st_data); - GST_DEBUG ("Create a layer"); layer = ges_layer_new (); fail_unless (layer != NULL); @@ -570,31 +573,71 @@ GST_START_TEST (test_ges_timeline_multiple_tracks) fail_unless (ges_track_get_timeline (track2) == timeline); fail_unless ((gpointer) GST_ELEMENT_PARENT (track2) == (gpointer) timeline); - /* Create a source and add it to the Layer */ - GST_DEBUG ("Creating a source"); - s1 = ges_test_clip_new (); - fail_unless (s1 != NULL); - fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1))); - tmp_layer = ges_clip_get_layer (GES_CLIP (s1)); - fail_unless (tmp_layer == layer); - gst_object_unref (tmp_layer); + /* adding to the layer before it is part of the timeline does not + * trigger track selection */ + /* s1 and s3 can overlap since they are destined for different tracks */ + /* s2 will overlap both */ + /* s4 destined for no track */ + _CREATE_SOURCE (layer, s1, 0, 10); + _CREATE_SOURCE (layer, s2, 5, 10); + _CREATE_SOURCE (layer, s3, 0, 10); + _CREATE_SOURCE (layer, s4, 0, 20); - GST_DEBUG ("Creating a source"); - s2 = ges_test_clip_new (); - fail_unless (s2 != NULL); - fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2))); - tmp_layer = ges_clip_get_layer (GES_CLIP (s2)); - fail_unless (tmp_layer == layer); - gst_object_unref (tmp_layer); + e1 = GES_TRACK_ELEMENT (ges_effect_new ("videobalance")); + fail_unless (ges_container_add (GES_CONTAINER (s2), + GES_TIMELINE_ELEMENT (e1))); + e2 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv ! vertigotv")); + fail_unless (ges_container_add (GES_CONTAINER (s2), + GES_TIMELINE_ELEMENT (e2))); + e3 = GES_TRACK_ELEMENT (ges_effect_new ("alpha")); + fail_unless (ges_container_add (GES_CONTAINER (s2), + GES_TIMELINE_ELEMENT (e3))); + assert_equals_int (0, + ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e1))); + assert_equals_int (1, + ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e2))); + assert_equals_int (2, + ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e3))); - GST_DEBUG ("Creating a source"); - s3 = ges_test_clip_new (); - fail_unless (s3 != NULL); - fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3))); - tmp_layer = ges_clip_get_layer (GES_CLIP (s3)); - fail_unless (tmp_layer == layer); - gst_object_unref (tmp_layer); + assert_num_children (s1, 0); + assert_num_children (s2, 3); + assert_num_children (s3, 0); + ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (s2), + "scratch-lines", 2, "speed", 50.0, NULL); + + ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ()); + g_object_set (G_OBJECT (ctrl_source), "mode", + GST_INTERPOLATION_MODE_NONE, NULL); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 0, 1.0)); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 4, 7.0)); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 8, 3.0)); + fail_unless (ges_track_element_set_control_source (e2, ctrl_source, + "scratch-lines", "direct-absolute")); + gst_object_unref (ctrl_source); + + st_data.tr1 = track1; + st_data.tr2 = track2; + st_data.clips[0] = s1; + st_data.clips[1] = s2; + st_data.clips[2] = s3; + st_data.clips[3] = s4; + st_data.num_calls[0] = 0; + st_data.num_calls[1] = 0; + st_data.num_calls[2] = 0; + st_data.num_calls[3] = 0; + st_data.effects[0] = e1; + st_data.effects[1] = e2; + st_data.effects[2] = e3; + st_data.num_unrecognised = 0; + + g_signal_connect (timeline, "select-tracks-for-object", + G_CALLBACK (select_tracks_cb), &st_data); + + /* adding layer to the timeline will trigger track selection, this */ GST_DEBUG ("Add the layer to the timeline"); fail_unless (ges_timeline_add_layer (timeline, layer)); /* The timeline steals our reference to the layer */ @@ -603,70 +646,121 @@ GST_START_TEST (test_ges_timeline_multiple_tracks) layers = ges_timeline_get_layers (timeline); fail_unless (g_list_find (layers, layer) != NULL); - g_list_foreach (layers, (GFunc) gst_object_unref, NULL); - g_list_free (layers); + g_list_free_full (layers, gst_object_unref); + + assert_equals_int (st_data.num_unrecognised, 0); /* Make sure the associated TrackElements are in the Track */ - trackelements = GES_CONTAINER_CHILDREN (s1); - fail_unless (trackelements != NULL); - t1 = GES_TRACK_ELEMENT ((trackelements)->data); - for (tmp = trackelements; tmp; tmp = tmp->next) { - /* There are 3 references held: - * 1 by the clip - * 1 by the track - * 1 by the timeline */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3); - fail_unless (ges_track_element_get_track (tmp->data) == track1); - } - gst_object_ref (t1); - /* There are 3 references held: - * 1 by the container - * 1 by the track - * 1 by the timeline - * 1 added by ourselves above (gst_object_ref (t1)) */ - ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 4); + assert_num_children (s1, 1); + el = GES_CONTAINER_CHILDREN (s1)->data; + fail_unless (GES_IS_SOURCE (el)); + fail_unless (ges_track_element_get_track (el) == track1); + ASSERT_OBJECT_REFCOUNT (el, "1 timeline + 1 track + 1 clip", 3); + /* called once for source */ + assert_equals_int (st_data.num_calls[0], 1); + /* 2 sources + 4 effects */ + assert_num_children (s2, 6); trackelements = GES_CONTAINER_CHILDREN (s2); - fail_unless (trackelements != NULL); - t2 = GES_TRACK_ELEMENT (trackelements->data); - for (tmp = trackelements; tmp; tmp = tmp->next) { - /* There are 3 references held: - * 1 by the clip - * 1 by the track - * 1 by the timeline */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3); - fail_unless (ges_track_element_get_track (tmp->data) == track2); - } - gst_object_ref (t2); - /* There are 3 references held: - * 1 by the container - * 1 by the track - * 1 by the timeline - * 1 added by ourselves above (gst_object_ref (t2)) */ - ASSERT_OBJECT_REFCOUNT (t2, "t2", 4); + /* sources at the end */ + el = g_list_nth_data (trackelements, 5); + fail_unless (GES_IS_SOURCE (el)); + el2 = g_list_nth_data (trackelements, 4); + fail_unless (GES_IS_SOURCE (el2)); + + /* font-desc is originally "", but on setting switches to Normal, so we + * set it explicitly */ + ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (el), + "font-desc", "Normal", NULL); + assert_equal_children_properties (el, el2); + assert_equal_bindings (el, el2); + + assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (el), + GES_TIMELINE_ELEMENT_PRIORITY (el2)); + + /* check one in each track */ + fail_unless (ges_track_element_get_track (el) + != ges_track_element_get_track (el2)); + fail_unless (ges_track_element_get_track (el) == track1 + || ges_track_element_get_track (el2) == track1); + fail_unless (ges_track_element_get_track (el) == track2 + || ges_track_element_get_track (el2) == track2); + + /* effects */ + e_copy = NULL; + for (tmp = trackelements; tmp; tmp = tmp->next) { + el = tmp->data; + ASSERT_OBJECT_REFCOUNT (el, "1 timeline + 1 track + 1 clip", 3); + if (GES_IS_BASE_EFFECT (el)) { + if (el == e1) { + fail_if (found_e1); + found_e1 = TRUE; + } else if (el == e2) { + fail_if (found_e2); + found_e2 = TRUE; + } else if (el == e3) { + fail_if (found_e3); + found_e3 = TRUE; + } else { + fail_if (e_copy); + e_copy = el; + } + } + } + fail_unless (found_e1); + fail_unless (found_e2); + fail_unless (found_e3); + fail_unless (e_copy); + + fail_unless (ges_track_element_get_track (e1) == track1); + fail_unless (ges_track_element_get_track (e3) == track2); + + assert_equal_children_properties (e2, e_copy); + assert_equal_bindings (e2, e_copy); + + /* check one in each track */ + fail_unless (ges_track_element_get_track (e2) + != ges_track_element_get_track (e_copy)); + fail_unless (ges_track_element_get_track (e2) == track1 + || ges_track_element_get_track (e_copy) == track1); + fail_unless (ges_track_element_get_track (e2) == track2 + || ges_track_element_get_track (e_copy) == track2); + + /* e2 copy placed next to e2 in top effect list */ + assert_equals_int (0, + ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e1))); + assert_equals_int (1, + ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e2))); + assert_equals_int (2, + ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e_copy))); + assert_equals_int (3, + ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e3))); + + /* called 4 times: 1 for source, and 1 for each effect (3) */ + assert_equals_int (st_data.num_calls[1], 4); + + assert_num_children (s3, 1); + el = GES_CONTAINER_CHILDREN (s3)->data; + fail_unless (GES_IS_SOURCE (el)); + fail_unless (ges_track_element_get_track (el) == track2); + ASSERT_OBJECT_REFCOUNT (el, "1 timeline + 1 track + 1 clip", 3); + /* called once for source */ + assert_equals_int (st_data.num_calls[2], 1); + + /* one child but no track */ + assert_num_children (s4, 1); + el = GES_CONTAINER_CHILDREN (s4)->data; + fail_unless (GES_IS_SOURCE (el)); + fail_unless (ges_track_element_get_track (el) == NULL); + ASSERT_OBJECT_REFCOUNT (el, "1 clip", 1); + /* called once for source (where no track was selected) */ + assert_equals_int (st_data.num_calls[0], 1); + + /* 2 sources + 2 effects */ + assert_num_in_track (track1, 4); + assert_num_in_track (track2, 4); - trackelements = GES_CONTAINER_CHILDREN (s3); - fail_unless (trackelements != NULL); - t3 = GES_TRACK_ELEMENT (trackelements->data); - for (tmp = trackelements; tmp; tmp = tmp->next) { - /* There are 3 references held: - * 1 by the clip - * 1 by the track - * 1 by the timeline */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3); - fail_unless (ges_track_element_get_track (tmp->data) == track1); - } - gst_object_ref (t3); - /* There are 3 references held: - * 1 by the container - * 1 by the track - * 1 by the timeline - * 1 added by ourselves above (gst_object_ref (t3)) */ - ASSERT_OBJECT_REFCOUNT (t3, "t3", 4); - gst_object_unref (t1); - gst_object_unref (t2); - gst_object_unref (t3); gst_object_unref (timeline); diff --git a/tests/check/ges/clip.c b/tests/check/ges/clip.c index c231b11a4a..f07ac11e39 100644 --- a/tests/check/ges/clip.c +++ b/tests/check/ges/clip.c @@ -54,7 +54,7 @@ GST_START_TEST (test_object_properties) ges_layer_add_clip (layer, GES_CLIP (clip)); ges_timeline_commit (timeline); - assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1); + assert_num_children (clip, 1); trackelement = GES_CONTAINER_CHILDREN (clip)->data; fail_unless (trackelement != NULL); fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) == @@ -133,7 +133,7 @@ GST_START_TEST (test_split_direct_bindings) g_object_unref (asset); CHECK_OBJECT_PROPS (clip, 0 * GST_SECOND, 10 * GST_SECOND, 10 * GST_SECOND); - assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1); + assert_num_children (clip, 1); check_layer (clip, 0); source = gst_interpolation_control_source_new (); @@ -231,7 +231,7 @@ GST_START_TEST (test_split_direct_absolute_bindings) g_object_unref (asset); CHECK_OBJECT_PROPS (clip, 0 * GST_SECOND, 10 * GST_SECOND, 10 * GST_SECOND); - assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1); + assert_num_children (clip, 1); check_layer (clip, 0); source = gst_interpolation_control_source_new (); @@ -301,14 +301,37 @@ GST_START_TEST (test_split_direct_absolute_bindings) GST_END_TEST; +static GPtrArray * +_select_none (GESTimeline * timeline, GESClip * clip, + GESTrackElement * track_element, guint * called_p) +{ + (*called_p)++; + return NULL; +} + +static GPtrArray * +_select_track (GESTimeline * timeline, GESClip * clip, + GESTrackElement * track_element, GESTrack ** track_p) +{ + GPtrArray *tracks = g_ptr_array_new (); + fail_unless (track_p); + fail_unless (*track_p); + g_ptr_array_insert (tracks, -1, gst_object_ref (*track_p)); + *track_p = NULL; + return tracks; +} GST_START_TEST (test_split_object) { GESTimeline *timeline; + GESTrack *track1, *track2, *effect_track; GESLayer *layer; GESClip *clip, *splitclip; GList *splittrackelements; - GESTrackElement *trackelement, *splittrackelement; + GESTrackElement *trackelement1, *trackelement2, *effect1, *effect2, + *splittrackelement; + guint32 priority1, priority2, effect_priority1, effect_priority2; + guint selection_called = 0; ges_init (); @@ -327,58 +350,151 @@ GST_START_TEST (test_split_object) g_object_set (clip, "start", (guint64) 42, "duration", (guint64) 50, "in-point", (guint64) 12, NULL); ASSERT_OBJECT_REFCOUNT (timeline, "timeline", 1); - assert_equals_uint64 (_START (clip), 42); - assert_equals_uint64 (_DURATION (clip), 50); - assert_equals_uint64 (_INPOINT (clip), 12); + CHECK_OBJECT_PROPS (clip, 42, 12, 50); ges_layer_add_clip (layer, GES_CLIP (clip)); ges_timeline_commit (timeline); - assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 2); - trackelement = GES_CONTAINER_CHILDREN (clip)->data; - fail_unless (trackelement != NULL); - fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) == + assert_num_children (clip, 2); + trackelement1 = GES_CONTAINER_CHILDREN (clip)->data; + fail_unless (trackelement1 != NULL); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement1) == + GES_TIMELINE_ELEMENT (clip)); + trackelement2 = GES_CONTAINER_CHILDREN (clip)->next->data; + fail_unless (trackelement2 != NULL); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement2) == GES_TIMELINE_ELEMENT (clip)); + effect1 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv")); + ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (effect1)); + + effect2 = GES_TRACK_ELEMENT (ges_effect_new ("vertigotv")); + ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (effect2)); + /* Check that trackelement has the same properties */ - assert_equals_uint64 (_START (trackelement), 42); - assert_equals_uint64 (_DURATION (trackelement), 50); - assert_equals_uint64 (_INPOINT (trackelement), 12); + CHECK_OBJECT_PROPS (trackelement1, 42, 12, 50); + CHECK_OBJECT_PROPS (trackelement2, 42, 12, 50); + CHECK_OBJECT_PROPS (effect1, 42, 0, 50); + CHECK_OBJECT_PROPS (effect2, 42, 0, 50); /* And let's also check that it propagated correctly to GNonLin */ - nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 50, 12, - 50, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE); + nle_object_check (ges_track_element_get_nleobject (trackelement1), 42, 50, 12, + 50, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 2, TRUE); + nle_object_check (ges_track_element_get_nleobject (trackelement2), 42, 50, 12, + 50, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 2, TRUE); + + track1 = ges_track_element_get_track (trackelement1); + fail_unless (track1); + track2 = ges_track_element_get_track (trackelement2); + fail_unless (track2); + fail_unless (track1 != track2); + effect_track = ges_track_element_get_track (effect1); + fail_unless (effect_track); + fail_unless (ges_track_element_get_track (effect2) == effect_track); + + priority1 = GES_TIMELINE_ELEMENT_PRIORITY (trackelement1); + priority2 = GES_TIMELINE_ELEMENT_PRIORITY (trackelement2); + effect_priority1 = GES_TIMELINE_ELEMENT_PRIORITY (effect1); + effect_priority2 = GES_TIMELINE_ELEMENT_PRIORITY (effect2); + + fail_unless (priority1 == priority2); + fail_unless (priority1 > effect_priority2); + fail_unless (effect_priority2 > effect_priority1); + + ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (clip), + "font-desc", "Normal", "posx", 30, "posy", 50, "alpha", 0.1, + "freq", 449.0, "scratch-lines", 2, "zoom-speed", 1.05, NULL); + + /* splitting should avoid track selection */ + g_signal_connect (timeline, "select-tracks-for-object", + G_CALLBACK (_select_none), &selection_called); splitclip = ges_clip_split (clip, 67); fail_unless (GES_IS_CLIP (splitclip)); + fail_unless (splitclip != clip); - assert_equals_uint64 (_START (clip), 42); - assert_equals_uint64 (_DURATION (clip), 25); - assert_equals_uint64 (_INPOINT (clip), 12); + fail_if (selection_called); - assert_equals_uint64 (_START (splitclip), 67); - assert_equals_uint64 (_DURATION (splitclip), 25); - assert_equals_uint64 (_INPOINT (splitclip), 37); + CHECK_OBJECT_PROPS (clip, 42, 12, 25); + CHECK_OBJECT_PROPS (trackelement1, 42, 12, 25); + CHECK_OBJECT_PROPS (trackelement1, 42, 12, 25); + CHECK_OBJECT_PROPS (effect1, 42, 0, 25); + CHECK_OBJECT_PROPS (effect2, 42, 0, 25); + + CHECK_OBJECT_PROPS (splitclip, 67, 37, 25); + + assert_equal_children_properties (splitclip, clip); splittrackelements = GES_CONTAINER_CHILDREN (splitclip); - fail_unless_equals_int (g_list_length (splittrackelements), 2); + fail_unless_equals_int (g_list_length (splittrackelements), 4); + /* first is the effects */ splittrackelement = GES_TRACK_ELEMENT (splittrackelements->data); fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement)); - assert_equals_uint64 (_START (splittrackelement), 67); - assert_equals_uint64 (_DURATION (splittrackelement), 25); - assert_equals_uint64 (_INPOINT (splittrackelement), 37); + CHECK_OBJECT_PROPS (splittrackelement, 67, 0, 25); - fail_unless (splittrackelement != trackelement); - fail_unless (splitclip != clip); + assert_equal_children_properties (splittrackelement, effect1); + fail_unless (ges_track_element_get_track (splittrackelement) == effect_track); + fail_unless (ges_track_element_get_track (effect1) == effect_track); + /* +3 priority from layer */ + assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement), + effect_priority1 + 3); + fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (effect1) == effect_priority1); + + fail_unless (splittrackelement != trackelement1); + fail_unless (splittrackelement != trackelement2); + fail_unless (splittrackelement != effect1); + fail_unless (splittrackelement != effect2); splittrackelement = GES_TRACK_ELEMENT (splittrackelements->next->data); fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement)); - assert_equals_uint64 (_START (splittrackelement), 67); - assert_equals_uint64 (_DURATION (splittrackelement), 25); - assert_equals_uint64 (_INPOINT (splittrackelement), 37); + CHECK_OBJECT_PROPS (splittrackelement, 67, 0, 25); - fail_unless (splittrackelement != trackelement); - fail_unless (splitclip != clip); + assert_equal_children_properties (splittrackelement, effect2); + fail_unless (ges_track_element_get_track (splittrackelement) == effect_track); + fail_unless (ges_track_element_get_track (effect2) == effect_track); + assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement), + effect_priority2 + 3); + fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (effect2) == effect_priority2); + + fail_unless (splittrackelement != trackelement1); + fail_unless (splittrackelement != trackelement2); + fail_unless (splittrackelement != effect1); + fail_unless (splittrackelement != effect2); + + splittrackelement = GES_TRACK_ELEMENT (splittrackelements->next->next->data); + fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement)); + CHECK_OBJECT_PROPS (splittrackelement, 67, 37, 25); + + /* core elements have swapped order in the clip, this is ok since they + * share the same priority */ + assert_equal_children_properties (splittrackelement, trackelement2); + fail_unless (ges_track_element_get_track (splittrackelement) == track2); + fail_unless (ges_track_element_get_track (trackelement2) == track2); + assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement), + priority2 + 3); + fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (trackelement2) == priority2); + + fail_unless (splittrackelement != trackelement1); + fail_unless (splittrackelement != trackelement2); + fail_unless (splittrackelement != effect1); + fail_unless (splittrackelement != effect2); + + splittrackelement = + GES_TRACK_ELEMENT (splittrackelements->next->next->next->data); + fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement)); + CHECK_OBJECT_PROPS (splittrackelement, 67, 37, 25); + + assert_equal_children_properties (splittrackelement, trackelement1); + fail_unless (ges_track_element_get_track (splittrackelement) == track1); + fail_unless (ges_track_element_get_track (trackelement1) == track1); + assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement), + priority1 + 3); + fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (trackelement1) == priority2); + + fail_unless (splittrackelement != trackelement1); + fail_unless (splittrackelement != trackelement2); + fail_unless (splittrackelement != effect1); + fail_unless (splittrackelement != effect2); /* We own the only ref */ ASSERT_OBJECT_REFCOUNT (splitclip, "1 ref for us + 1 for the timeline", 2); @@ -395,15 +511,51 @@ GST_START_TEST (test_split_object) GST_END_TEST; +#define _assert_higher_priority(el, higher) \ +{ \ + if (higher) { \ + guint32 el_prio = GES_TIMELINE_ELEMENT_PRIORITY (el); \ + guint32 higher_prio = GES_TIMELINE_ELEMENT_PRIORITY (higher); \ + fail_unless (el_prio > higher_prio, "%s does not have a higher " \ + "priority than %s (%u vs %u)", GES_TIMELINE_ELEMENT_NAME (el), \ + GES_TIMELINE_ELEMENT_NAME (higher), el_prio, higher_prio); \ + } \ +} + +#define _assert_regroup_fails(clip_list) \ +{ \ + GESContainer *regrouped = ges_container_group (clip_list); \ + fail_unless (GES_IS_GROUP (regrouped)); \ + assert_equals_int (g_list_length (regrouped->children), \ + g_list_length (clip_list)); \ + g_list_free_full (ges_container_ungroup (regrouped, FALSE), \ + gst_object_unref); \ +} + GST_START_TEST (test_clip_group_ungroup) { GESAsset *asset; GESTimeline *timeline; - GESClip *clip, *clip2; + GESClip *clip, *video_clip, *audio_clip; + GESTrackElement *el; GList *containers, *tmp; GESLayer *layer; GESContainer *regrouped_clip; GESTrack *audio_track, *video_track; + guint selection_called = 0; + struct + { + GESTrackElement *element; + GESTrackElement *higher_priority; + } audio_els[2]; + struct + { + GESTrackElement *element; + GESTrackElement *higher_priority; + } video_els[3]; + guint i, j; + const gchar *name; + GESTrackType type; ges_init (); @@ -420,101 +572,192 @@ GST_START_TEST (test_clip_group_ungroup) assert_is_type (asset, GES_TYPE_ASSET); clip = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); - ASSERT_OBJECT_REFCOUNT (clip, "1 layer + 1 timeline.all_elements", 2); - assert_equals_uint64 (_START (clip), 0); - assert_equals_uint64 (_INPOINT (clip), 0); - assert_equals_uint64 (_DURATION (clip), 10); - assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 2); + ASSERT_OBJECT_REFCOUNT (clip, "1 layer + 1 timeline.all_els", 2); + assert_num_children (clip, 2); + CHECK_OBJECT_PROPS (clip, 0, 0, 10); + + 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))); + + 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))); + + 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_num_children (clip, 5); + CHECK_OBJECT_PROPS (clip, 0, 0, 10); + + i = j = 0; + for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { + el = tmp->data; + type = ges_track_element_get_track_type (el); + if (type == GES_TRACK_TYPE_AUDIO) { + fail_unless (i < G_N_ELEMENTS (audio_els)); + audio_els[i].element = el; + fail_unless (ges_track_element_get_track (el) == audio_track, + "%s not in audio track", GES_TIMELINE_ELEMENT_NAME (el)); + if (i == 0) + audio_els[i].higher_priority = NULL; + else + audio_els[i].higher_priority = audio_els[i - 1].element; + _assert_higher_priority (el, audio_els[i].higher_priority); + i++; + } + if (type == GES_TRACK_TYPE_VIDEO) { + fail_unless (j < G_N_ELEMENTS (video_els)); + video_els[j].element = el; + fail_unless (ges_track_element_get_track (el) == video_track, + "%s not in video track", GES_TIMELINE_ELEMENT_NAME (el)); + if (j == 0) + video_els[j].higher_priority = NULL; + else + video_els[j].higher_priority = video_els[j - 1].element; + _assert_higher_priority (el, video_els[j].higher_priority); + j++; + } + } + fail_unless (i == G_N_ELEMENTS (audio_els)); + fail_unless (j == G_N_ELEMENTS (video_els)); + assert_num_in_track (audio_track, 2); + assert_num_in_track (video_track, 3); + + /* group and ungroup should avoid track selection */ + g_signal_connect (timeline, "select-tracks-for-object", + G_CALLBACK (_select_none), &selection_called); containers = ges_container_ungroup (GES_CONTAINER (clip), FALSE); + + fail_if (selection_called); + + video_clip = NULL; + audio_clip = NULL; + assert_equals_int (g_list_length (containers), 2); - fail_unless (clip == containers->data); - assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1); - assert_equals_uint64 (_START (clip), 0); - assert_equals_uint64 (_INPOINT (clip), 0); - assert_equals_uint64 (_DURATION (clip), 10); - ASSERT_OBJECT_REFCOUNT (clip, "1 for the layer + 1 for the timeline + " + + type = ges_clip_get_supported_formats (containers->data); + if (type == GES_TRACK_TYPE_VIDEO) + video_clip = containers->data; + if (type == GES_TRACK_TYPE_AUDIO) + audio_clip = containers->data; + + type = ges_clip_get_supported_formats (containers->next->data); + if (type == GES_TRACK_TYPE_VIDEO) + video_clip = containers->next->data; + if (type == GES_TRACK_TYPE_AUDIO) + audio_clip = containers->next->data; + + fail_unless (video_clip); + fail_unless (audio_clip); + fail_unless (video_clip == clip || audio_clip == clip); + + assert_layer (video_clip, layer); + assert_num_children (video_clip, 3); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (video_clip) == timeline); + CHECK_OBJECT_PROPS (video_clip, 0, 0, 10); + ASSERT_OBJECT_REFCOUNT (video_clip, "1 for the layer + 1 for the timeline + " "1 in containers list", 3); - clip2 = containers->next->data; - fail_if (clip2 == clip); - fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (clip2) != NULL); - assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip2)), 1); - assert_equals_uint64 (_START (clip2), 0); - assert_equals_uint64 (_INPOINT (clip2), 0); - assert_equals_uint64 (_DURATION (clip2), 10); - ASSERT_OBJECT_REFCOUNT (clip2, "1 for the layer + 1 for the timeline +" - " 1 in containers list", 3); + assert_layer (audio_clip, layer); + assert_num_children (audio_clip, 2); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (audio_clip) == timeline); + CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10); + ASSERT_OBJECT_REFCOUNT (audio_clip, "1 for the layer + 1 for the timeline + " + "1 in containers list", 3); - tmp = ges_track_get_elements (audio_track); - assert_equals_int (g_list_length (tmp), 1); - ASSERT_OBJECT_REFCOUNT (tmp->data, "1 for the track + 1 for the container " - "+ 1 for the timeline + 1 in tmp list", 4); - assert_equals_int (ges_track_element_get_track_type (tmp->data), - GES_TRACK_TYPE_AUDIO); - assert_equals_int (ges_clip_get_supported_formats (GES_CLIP - (ges_timeline_element_get_parent (tmp->data))), GES_TRACK_TYPE_AUDIO); - g_list_free_full (tmp, gst_object_unref); - tmp = ges_track_get_elements (video_track); - assert_equals_int (g_list_length (tmp), 1); - ASSERT_OBJECT_REFCOUNT (tmp->data, "1 for the track + 1 for the container " - "+ 1 for the timeline + 1 in tmp list", 4); - assert_equals_int (ges_track_element_get_track_type (tmp->data), - GES_TRACK_TYPE_VIDEO); - assert_equals_int (ges_clip_get_supported_formats (GES_CLIP - (ges_timeline_element_get_parent (tmp->data))), GES_TRACK_TYPE_VIDEO); - g_list_free_full (tmp, gst_object_unref); + for (i = 0; i < G_N_ELEMENTS (audio_els); i++) { + el = audio_els[i].element; + name = GES_TIMELINE_ELEMENT_NAME (el); + fail_unless (ges_track_element_get_track (el) == audio_track, + "%s not in audio track", name); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) == + GES_TIMELINE_ELEMENT (audio_clip), "%s not in the audio clip", name); + ASSERT_OBJECT_REFCOUNT (el, + "1 for the track + 1 for the container " "+ 1 for the timeline", 3); + _assert_higher_priority (el, audio_els[i].higher_priority); + } + for (i = 0; i < G_N_ELEMENTS (video_els); i++) { + el = video_els[i].element; + name = GES_TIMELINE_ELEMENT_NAME (el); + fail_unless (ges_track_element_get_track (el) == video_track, + "%s not in video track", name); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) == + GES_TIMELINE_ELEMENT (video_clip), "%s not in the video clip", name); + ASSERT_OBJECT_REFCOUNT (el, + "1 for the track + 1 for the container " "+ 1 for the timeline", 3); + _assert_higher_priority (el, video_els[i].higher_priority); + } + assert_num_in_track (audio_track, 2); + assert_num_in_track (video_track, 3); - ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip), 10); - assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1); - assert_equals_uint64 (_START (clip), 10); - assert_equals_uint64 (_INPOINT (clip), 0); - assert_equals_uint64 (_DURATION (clip), 10); - assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip2)), 1); - assert_equals_uint64 (_START (clip2), 0); - assert_equals_uint64 (_INPOINT (clip2), 0); - assert_equals_uint64 (_DURATION (clip2), 10); + ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (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); + 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); + 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); + CHECK_OBJECT_PROPS (video_clip, 0, 0, 10); + CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10); regrouped_clip = ges_container_group (containers); - fail_unless (GES_IS_GROUP (regrouped_clip)); - assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (regrouped_clip)), - 2); - tmp = ges_container_ungroup (regrouped_clip, FALSE); - g_list_free_full (tmp, gst_object_unref); - ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip), 0); - regrouped_clip = ges_container_group (containers); + fail_if (selection_called); + assert_is_type (regrouped_clip, GES_TYPE_CLIP); - assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (regrouped_clip)), - 2); + assert_num_children (regrouped_clip, 5); assert_equals_int (ges_clip_get_supported_formats (GES_CLIP (regrouped_clip)), GES_TRACK_TYPE_VIDEO | GES_TRACK_TYPE_AUDIO); g_list_free_full (containers, gst_object_unref); - GST_DEBUG ("Check clips in the layer"); - tmp = ges_layer_get_clips (layer); - assert_equals_int (g_list_length (tmp), 1); - g_list_free_full (tmp, gst_object_unref); + assert_layer (regrouped_clip, layer); - GST_DEBUG ("Check TrackElement in audio track"); - tmp = ges_track_get_elements (audio_track); - assert_equals_int (g_list_length (tmp), 1); - assert_equals_int (ges_track_element_get_track_type (tmp->data), - GES_TRACK_TYPE_AUDIO); - fail_unless (GES_CONTAINER (ges_timeline_element_get_parent (tmp->data)) == - regrouped_clip); - g_list_free_full (tmp, gst_object_unref); - - GST_DEBUG ("Check TrackElement in video track"); - tmp = ges_track_get_elements (video_track); - assert_equals_int (g_list_length (tmp), 1); - ASSERT_OBJECT_REFCOUNT (tmp->data, "1 for the track + 1 for the container " - "+ 1 for the timeline + 1 in tmp list", 4); - assert_equals_int (ges_track_element_get_track_type (tmp->data), - GES_TRACK_TYPE_VIDEO); - fail_unless (GES_CONTAINER (ges_timeline_element_get_parent (tmp->data)) == - regrouped_clip); - g_list_free_full (tmp, gst_object_unref); + for (i = 0; i < G_N_ELEMENTS (audio_els); i++) { + el = audio_els[i].element; + name = GES_TIMELINE_ELEMENT_NAME (el); + fail_unless (ges_track_element_get_track (el) == audio_track, + "%s not in audio track", name); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) == + GES_TIMELINE_ELEMENT (regrouped_clip), "%s not in the regrouped clip", + name); + ASSERT_OBJECT_REFCOUNT (el, + "1 for the track + 1 for the container " "+ 1 for the timeline", 3); + _assert_higher_priority (el, audio_els[i].higher_priority); + } + for (i = 0; i < G_N_ELEMENTS (video_els); i++) { + el = video_els[i].element; + name = GES_TIMELINE_ELEMENT_NAME (el); + fail_unless (ges_track_element_get_track (el) == video_track, + "%s not in video track", name); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) == + GES_TIMELINE_ELEMENT (regrouped_clip), "%s not in the regrouped clip", + name); + ASSERT_OBJECT_REFCOUNT (el, + "1 for the track + 1 for the container " "+ 1 for the timeline", 3); + _assert_higher_priority (el, video_els[i].higher_priority); + } + assert_num_in_track (audio_track, 2); + assert_num_in_track (video_track, 3); gst_object_unref (timeline); @@ -523,12 +766,324 @@ GST_START_TEST (test_clip_group_ungroup) GST_END_TEST; +GST_START_TEST (test_adding_children_to_track) +{ + GESTimeline *timeline; + GESLayer *layer; + GESTrack *track1, *track2; + GESClip *clip, *clip2; + GESAsset *asset; + GESTrackElement *source, *effect, *effect2, *added, *added2, *added3; + GstControlSource *ctrl_source; + guint selection_called = 0; + + ges_init (); + + timeline = ges_timeline_new (); + ges_timeline_set_auto_transition (timeline, TRUE); + track1 = GES_TRACK (ges_video_track_new ()); + track2 = GES_TRACK (ges_video_track_new ()); + + + /* only add two for now */ + fail_unless (ges_timeline_add_track (timeline, track1)); + + layer = ges_timeline_append_layer (timeline); + + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + clip = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + fail_unless (clip); + assert_num_children (clip, 1); + assert_num_in_track (track1, 1); + assert_num_in_track (track2, 0); + source = GES_CONTAINER_CHILDREN (clip)->data; + 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))); + effect2 = GES_TRACK_ELEMENT (ges_effect_new ("vertigotv")); + fail_unless (ges_container_add (GES_CONTAINER (clip), + GES_TIMELINE_ELEMENT (effect2))); + assert_num_children (clip, 3); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + fail_unless (ges_track_element_get_track (effect) == track1); + fail_unless (ges_track_element_get_track (effect2) == track1); + + ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (clip), + "font-desc", "Normal", "posx", 30, "posy", 50, "alpha", 0.1, + "freq", 449.0, "scratch-lines", 2, NULL); + + ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ()); + g_object_set (G_OBJECT (ctrl_source), "mode", + GST_INTERPOLATION_MODE_CUBIC, NULL); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 0, 20.0)); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 5, 45.0)); + fail_unless (ges_track_element_set_control_source (source, ctrl_source, + "posx", "direct-absolute")); + gst_object_unref (ctrl_source); + + ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ()); + g_object_set (G_OBJECT (ctrl_source), "mode", + GST_INTERPOLATION_MODE_LINEAR, NULL); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 2, 0.1)); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 5, 0.7)); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 8, 0.3)); + fail_unless (ges_track_element_set_control_source (source, ctrl_source, + "alpha", "direct")); + gst_object_unref (ctrl_source); + + ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ()); + g_object_set (G_OBJECT (ctrl_source), "mode", + GST_INTERPOLATION_MODE_NONE, NULL); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 0, 1.0)); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 4, 7.0)); + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 8, 3.0)); + fail_unless (ges_track_element_set_control_source (effect, ctrl_source, + "scratch-lines", "direct-absolute")); + gst_object_unref (ctrl_source); + + /* can't add to a track that does not belong to the timeline */ + fail_if (ges_clip_add_child_to_track (clip, source, track2, NULL)); + assert_num_children (clip, 3); + fail_unless (ges_track_element_get_track (source) == track1); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + + /* can't add the clip to a track that already contains our source */ + fail_if (ges_clip_add_child_to_track (clip, source, track1, NULL)); + assert_num_children (clip, 3); + fail_unless (ges_track_element_get_track (source) == track1); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + + /* can't remove a core element from its track whilst a non-core sits + * above it */ + fail_if (ges_track_remove_element (track1, source)); + assert_num_children (clip, 3); + fail_unless (ges_track_element_get_track (source) == track1); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + + /* can not add to the same track as it is currently in */ + fail_if (ges_clip_add_child_to_track (clip, effect, track1, NULL)); + fail_unless (ges_track_element_get_track (effect) == track1); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + + /* adding another video track, select-tracks-for-object will do nothing + * since no each track element is already part of a track */ + fail_unless (ges_timeline_add_track (timeline, track2)); + assert_num_children (clip, 3); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + + /* can not add effect to a track that does not contain a core child */ + fail_if (ges_clip_add_child_to_track (clip, effect, track2, NULL)); + assert_num_children (clip, 3); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + + /* can add core */ + + added = ges_clip_add_child_to_track (clip, source, track2, NULL); + fail_unless (added); + assert_num_children (clip, 4); + fail_unless (added != source); + fail_unless (ges_track_element_get_track (source) == track1); + fail_unless (ges_track_element_get_track (added) == track2); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 1); + + assert_equal_children_properties (added, source); + assert_equal_bindings (added, source); + + /* can now add non-core */ + assert_equals_int (0, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect))); + assert_equals_int (1, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2))); + + added2 = ges_clip_add_child_to_track (clip, effect, track2, NULL); + fail_unless (added2); + assert_num_children (clip, 5); + fail_unless (added2 != effect); + fail_unless (ges_track_element_get_track (effect) == track1); + fail_unless (ges_track_element_get_track (added2) == track2); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 2); + + assert_equal_children_properties (added2, effect); + assert_equal_bindings (added2, effect); + + assert_equals_int (0, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect))); + assert_equals_int (1, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (added2))); + assert_equals_int (2, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2))); + + added3 = ges_clip_add_child_to_track (clip, effect2, track2, NULL); + fail_unless (added3); + assert_num_children (clip, 6); + fail_unless (added3 != effect2); + fail_unless (ges_track_element_get_track (effect2) == track1); + fail_unless (ges_track_element_get_track (added3) == track2); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 3); + + assert_equal_children_properties (added3, effect2); + assert_equal_bindings (added3, effect2); + + /* priorities within new track match that in previous track! */ + assert_equals_int (0, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect))); + assert_equals_int (1, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (added2))); + assert_equals_int (2, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2))); + assert_equals_int (3, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (added3))); + + /* 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_num_children (clip, 5); + fail_unless (ges_track_element_get_track (source) == track1); + fail_if (ges_track_element_get_track (added)); + fail_if (ges_track_element_get_track (added2)); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (added) == NULL); + fail_unless (GES_TIMELINE_ELEMENT_PARENT (added2) == + GES_TIMELINE_ELEMENT (clip)); + assert_num_in_track (track1, 3); + 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_num_children (clip, 3); + assert_num_in_track (track1, 3); + assert_num_in_track (track2, 0); + + /* remove from layer empties all children from the tracks */ + gst_object_ref (clip); + + fail_unless (ges_layer_remove_clip (layer, clip)); + assert_num_children (clip, 3); + fail_if (ges_track_element_get_track (source)); + fail_if (ges_track_element_get_track (effect)); + assert_num_in_track (track1, 0); + assert_num_in_track (track2, 0); + + /* add different sources to the layer */ + fail_unless (ges_layer_add_asset (layer, asset, 0, 0, 10, + GES_TRACK_TYPE_UNKNOWN)); + fail_unless (ges_layer_add_asset (layer, asset, 20, 0, 10, + GES_TRACK_TYPE_UNKNOWN)); + fail_unless (clip2 = ges_layer_add_asset (layer, asset, 25, 0, 10, + GES_TRACK_TYPE_UNKNOWN)); + assert_num_children (clip2, 2); + /* 3 sources + 1 transition */ + assert_num_in_track (track1, 4); + assert_num_in_track (track2, 4); + + /* removing the track from the timeline empties it of track elements */ + gst_object_ref (track2); + fail_unless (ges_timeline_remove_track (timeline, track2)); + /* but children remain in the clips */ + assert_num_children (clip2, 2); + assert_num_in_track (track1, 4); + assert_num_in_track (track2, 0); + gst_object_unref (track2); + + /* add clip back in, but don't select any tracks */ + g_signal_connect (timeline, "select-tracks-for-object", + G_CALLBACK (_select_none), &selection_called); + + /* can add the clip to the layer, despite a source existing between + * 0 and 10 because the clip will not fill any track */ + /* NOTE: normally this would be useless because it would not trigger + * the creation of any core children. But clip currently still has + * its core children */ + fail_unless (ges_layer_add_clip (layer, clip)); + gst_object_unref (clip); + + /* one call for each child */ + assert_equals_int (selection_called, 3); + + fail_if (ges_track_element_get_track (source)); + fail_if (ges_track_element_get_track (effect)); + assert_num_children (clip, 3); + assert_num_in_track (track1, 4); + + /* can not add the source to the track because it would overlap another + * source */ + fail_if (ges_clip_add_child_to_track (clip, source, track1, NULL)); + assert_num_children (clip, 3); + assert_num_in_track (track1, 4); + + /* 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)); + 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)); + 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); + assert_num_children (clip, 3); + /* 4 sources + 2 transitions */ + assert_num_in_track (track1, 6); + + /* also add effect */ + added = ges_clip_add_child_to_track (clip, effect, track1, NULL); + /* added is the source since it was not already in a track */ + fail_unless (added == effect); + assert_num_children (clip, 3); + assert_num_in_track (track1, 7); + + added = ges_clip_add_child_to_track (clip, effect2, track1, NULL); + /* added is the source since it was not already in a track */ + fail_unless (added == effect2); + assert_num_children (clip, 3); + assert_num_in_track (track1, 8); + + assert_equals_int (0, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect))); + assert_equals_int (1, + ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2))); + + gst_object_unref (timeline); + + ges_deinit (); +} + +GST_END_TEST; static void child_removed_cb (GESClip * clip, GESTimelineElement * effect, gboolean * called) { - ASSERT_OBJECT_REFCOUNT (effect, "1 keeping alive ref + emission ref", 2); + ASSERT_OBJECT_REFCOUNT (effect, "1 test ref + 1 keeping alive ref + " + "emission ref", 3); *called = TRUE; } @@ -537,30 +1092,52 @@ GST_START_TEST (test_clip_refcount_remove_child) GESClip *clip; GESTrack *track; gboolean called; - GESTrackElement *effect; + GESTrackElement *effect, *source; + GESTimeline *timeline; + GESLayer *layer; ges_init (); - clip = GES_CLIP (ges_test_clip_new ()); + timeline = ges_timeline_new (); track = GES_TRACK (ges_audio_track_new ()); - effect = GES_TRACK_ELEMENT (ges_effect_new ("identity")); + fail_unless (ges_timeline_add_track (timeline, track)); + layer = ges_timeline_append_layer (timeline); + clip = GES_CLIP (ges_test_clip_new ()); + fail_unless (ges_layer_add_clip (layer, clip)); + + assert_num_children (clip, 1); + assert_num_in_track (track, 1); + + source = GES_CONTAINER_CHILDREN (clip)->data; + ASSERT_OBJECT_REFCOUNT (source, "1 for the container + 1 for the track" + " + 1 timeline", 3); + + effect = GES_TRACK_ELEMENT (ges_effect_new ("identity")); fail_unless (ges_track_add_element (track, effect)); + 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_OBJECT_REFCOUNT (effect, "1 for the container + 1 for the track", 2); + assert_num_children (clip, 2); + ASSERT_OBJECT_REFCOUNT (effect, "1 for the container + 1 for the track" + " + 1 timeline", 3); fail_unless (ges_track_remove_element (track, effect)); ASSERT_OBJECT_REFCOUNT (effect, "1 for the container", 1); 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))); fail_unless (called == TRUE); + ASSERT_OBJECT_REFCOUNT (effect, "1 test ref", 1); + gst_object_unref (effect); - check_destroyed (G_OBJECT (track), NULL, NULL); - check_destroyed (G_OBJECT (clip), NULL, NULL); + check_destroyed (G_OBJECT (timeline), G_OBJECT (track), + G_OBJECT (layer), G_OBJECT (clip), G_OBJECT (source), NULL); ges_deinit (); } @@ -572,13 +1149,14 @@ GST_START_TEST (test_clip_find_track_element) GESClip *clip; GList *foundelements; GESTimeline *timeline; + GESLayer *layer; GESTrack *track, *track1, *track2; + guint selection_called = 0; - GESTrackElement *effect, *effect1, *effect2, *foundelem; + GESTrackElement *effect, *effect1, *effect2, *foundelem, *video_source; ges_init (); - clip = GES_CLIP (ges_test_clip_new ()); track = GES_TRACK (ges_audio_track_new ()); track1 = GES_TRACK (ges_audio_track_new ()); track2 = GES_TRACK (ges_video_track_new ()); @@ -588,11 +1166,18 @@ GST_START_TEST (test_clip_find_track_element) fail_unless (ges_timeline_add_track (timeline, track1)); fail_unless (ges_timeline_add_track (timeline, track2)); - /* need to register the clip with the timeline */ - /* FIXME: we should make the clip part of a layer, but the current - * default select-tracks-for-object signal is broken for multiple - * tracks. In fact, we should be using this signal in this test */ - ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip), timeline); + layer = ges_timeline_append_layer (timeline); + clip = GES_CLIP (ges_test_clip_new ()); + + /* should have a source in every track */ + fail_unless (ges_layer_add_clip (layer, clip)); + assert_num_children (clip, 3); + assert_num_in_track (track, 1); + assert_num_in_track (track1, 1); + assert_num_in_track (track2, 1); + + g_signal_connect (timeline, "select-tracks-for-object", + G_CALLBACK (_select_none), &selection_called); effect = GES_TRACK_ELEMENT (ges_effect_new ("identity")); fail_unless (ges_track_add_element (track, effect)); @@ -609,29 +1194,111 @@ GST_START_TEST (test_clip_find_track_element) fail_unless (ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (effect2))); - foundelem = ges_clip_find_track_element (clip, track, G_TYPE_NONE); + fail_if (selection_called); + assert_num_children (clip, 6); + assert_num_in_track (track, 2); + assert_num_in_track (track1, 2); + assert_num_in_track (track2, 2); + + foundelem = ges_clip_find_track_element (clip, track, GES_TYPE_EFFECT); fail_unless (foundelem == effect); gst_object_unref (foundelem); - foundelem = ges_clip_find_track_element (clip, NULL, GES_TYPE_SOURCE); + foundelem = ges_clip_find_track_element (clip, track1, GES_TYPE_EFFECT); + fail_unless (foundelem == effect1); + gst_object_unref (foundelem); + + foundelem = ges_clip_find_track_element (clip, track2, GES_TYPE_EFFECT); + fail_unless (foundelem == effect2); + gst_object_unref (foundelem); + + foundelem = ges_clip_find_track_element (clip, NULL, GES_TYPE_TRANSITION); fail_unless (foundelem == NULL); + foundelem = ges_clip_find_track_element (clip, track, GES_TYPE_TRANSITION); + fail_unless (foundelem == NULL); + + foundelem = ges_clip_find_track_element (clip, track1, GES_TYPE_TRANSITION); + fail_unless (foundelem == NULL); + + foundelem = ges_clip_find_track_element (clip, track2, GES_TYPE_TRANSITION); + fail_unless (foundelem == NULL); + + foundelem = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE); + fail_unless (GES_IS_AUDIO_TEST_SOURCE (foundelem)); + gst_object_unref (foundelem); + + foundelem = ges_clip_find_track_element (clip, track1, GES_TYPE_SOURCE); + fail_unless (GES_IS_AUDIO_TEST_SOURCE (foundelem)); + gst_object_unref (foundelem); + + foundelem = ges_clip_find_track_element (clip, track2, GES_TYPE_SOURCE); + fail_unless (GES_IS_VIDEO_TEST_SOURCE (foundelem)); + gst_object_unref (foundelem); + + video_source = ges_clip_find_track_element (clip, NULL, + GES_TYPE_VIDEO_TEST_SOURCE); + fail_unless (foundelem == video_source); + gst_object_unref (video_source); + + foundelements = ges_clip_find_track_elements (clip, NULL, GES_TRACK_TYPE_AUDIO, G_TYPE_NONE); - fail_unless_equals_int (g_list_length (foundelements), 2); + fail_unless_equals_int (g_list_length (foundelements), 4); g_list_free_full (foundelements, gst_object_unref); foundelements = ges_clip_find_track_elements (clip, NULL, GES_TRACK_TYPE_VIDEO, G_TYPE_NONE); - fail_unless_equals_int (g_list_length (foundelements), 1); + fail_unless_equals_int (g_list_length (foundelements), 2); g_list_free_full (foundelements, gst_object_unref); + foundelements = ges_clip_find_track_elements (clip, NULL, + GES_TRACK_TYPE_UNKNOWN, GES_TYPE_SOURCE); + fail_unless_equals_int (g_list_length (foundelements), 3); + fail_unless (g_list_find (foundelements, video_source)); + g_list_free_full (foundelements, gst_object_unref); + + foundelements = ges_clip_find_track_elements (clip, NULL, + GES_TRACK_TYPE_UNKNOWN, GES_TYPE_EFFECT); + fail_unless_equals_int (g_list_length (foundelements), 3); + fail_unless (g_list_find (foundelements, effect)); + fail_unless (g_list_find (foundelements, effect1)); + fail_unless (g_list_find (foundelements, effect2)); + g_list_free_full (foundelements, gst_object_unref); + + foundelements = ges_clip_find_track_elements (clip, NULL, + GES_TRACK_TYPE_VIDEO, GES_TYPE_SOURCE); + fail_unless_equals_int (g_list_length (foundelements), 1); + fail_unless (foundelements->data == video_source); + g_list_free_full (foundelements, gst_object_unref); + + foundelements = ges_clip_find_track_elements (clip, track2, + GES_TRACK_TYPE_UNKNOWN, GES_TYPE_SOURCE); + fail_unless_equals_int (g_list_length (foundelements), 1); + fail_unless (foundelements->data == video_source); + g_list_free_full (foundelements, gst_object_unref); + + foundelements = ges_clip_find_track_elements (clip, track2, + GES_TRACK_TYPE_UNKNOWN, G_TYPE_NONE); + fail_unless_equals_int (g_list_length (foundelements), 2); + fail_unless (g_list_find (foundelements, effect2)); + fail_unless (g_list_find (foundelements, video_source)); + g_list_free_full (foundelements, gst_object_unref); + + foundelements = ges_clip_find_track_elements (clip, track1, + GES_TRACK_TYPE_UNKNOWN, GES_TYPE_EFFECT); + fail_unless_equals_int (g_list_length (foundelements), 1); + fail_unless (foundelements->data == effect1); + g_list_free_full (foundelements, gst_object_unref); + + /* NOTE: search in *either* track or track type + * TODO 2.0: this should be an AND condition, rather than OR */ foundelements = ges_clip_find_track_elements (clip, track, GES_TRACK_TYPE_VIDEO, G_TYPE_NONE); - fail_unless_equals_int (g_list_length (foundelements), 2); - fail_unless (g_list_find (foundelements, effect2) != NULL, - "In the video track"); - fail_unless (g_list_find (foundelements, effect2) != NULL, "In 'track'"); + fail_unless_equals_int (g_list_length (foundelements), 4); + fail_unless (g_list_find (foundelements, effect)); + fail_unless (g_list_find (foundelements, effect2)); + fail_unless (g_list_find (foundelements, video_source)); g_list_free_full (foundelements, gst_object_unref); gst_object_unref (timeline); @@ -1736,9 +2403,10 @@ _el_with_child_prop (GESTimelineElement * clip, GObject * prop_child, if (ges_timeline_element_lookup_child (tmp->data, prop->name, &found_child, &found_prop)) { if (found_child == prop_child && found_prop == prop) { + g_param_spec_unref (found_prop); + g_object_unref (found_child); child = tmp->data; - /* break early, but still free */ - tmp->next = NULL; + break; } g_param_spec_unref (found_prop); g_object_unref (found_child); @@ -1802,13 +2470,12 @@ GST_START_TEST (test_copy_paste_children_properties) GESLayer *layer; GESTimelineElement *clip, *copy, *pasted, *track_el, *pasted_el; GObject *sub_child, *pasted_sub_child; - GParamSpec **orig_props, **pasted_props, **track_el_props, **pasted_el_props; - guint num_orig_props, num_pasted_props, num_track_el_props, - num_pasted_el_props; + GParamSpec **orig_props; + guint num_orig_props; GParamSpec *prop, *found_prop; GValue val = G_VALUE_INIT; - GSList *timed_vals; GstControlSource *source; + GSList *timed_vals; ges_init (); @@ -1824,24 +2491,20 @@ GST_START_TEST (test_copy_paste_children_properties) ges_timeline_element_list_children_properties (clip, &num_orig_props); fail_unless (num_orig_props); + /* font-desc is originally "", but on setting switches to Normal, so we + * set it explicitly */ + ges_timeline_element_set_child_properties (clip, "font-desc", "Normal", + "posx", 30, "posy", 50, "alpha", 0.1, "freq", 449.0, NULL); + /* focus on one property */ fail_unless (ges_timeline_element_lookup_child (clip, "posx", &sub_child, &prop)); - g_value_init (&val, G_TYPE_INT); - g_value_set_int (&val, 30); - fail_unless (ges_timeline_element_set_child_property (clip, "posx", &val)); - g_value_unset (&val); - _assert_int_val_child_prop (clip, val, 30, prop, "posx"); /* find the track element where the child property comes from */ fail_unless (track_el = _el_with_child_prop (clip, sub_child, prop)); _assert_int_val_child_prop (track_el, val, 30, prop, "posx"); - track_el_props = - ges_timeline_element_list_children_properties (track_el, - &num_track_el_props); - /* set a control binding */ timed_vals = g_slist_prepend (NULL, _new_timed_value (200, 5)); timed_vals = g_slist_prepend (timed_vals, _new_timed_value (40, 50)); @@ -1855,6 +2518,7 @@ GST_START_TEST (test_copy_paste_children_properties) fail_unless (ges_track_element_set_control_source (GES_TRACK_ELEMENT (track_el), source, "posx", "direct-absolute")); + g_object_unref (source); /* check the control binding */ @@ -1866,13 +2530,10 @@ GST_START_TEST (test_copy_paste_children_properties) fail_unless (pasted = ges_timeline_element_paste (copy, 30)); gst_object_unref (copy); + gst_object_unref (pasted); /* test that the new clip has the same child properties */ - pasted_props = - ges_timeline_element_list_children_properties (pasted, &num_pasted_props); - - assert_property_list_match (pasted_props, num_pasted_props, - orig_props, num_orig_props); + assert_equal_children_properties (clip, pasted); /* get the details for the copied 'prop' property */ fail_unless (ges_timeline_element_lookup_child (pasted, @@ -1888,25 +2549,18 @@ GST_START_TEST (test_copy_paste_children_properties) _el_with_child_prop (pasted, pasted_sub_child, prop)); _assert_int_val_child_prop (pasted_el, val, 30, prop, "posx"); - pasted_el_props = - ges_timeline_element_list_children_properties (pasted_el, - &num_pasted_el_props); - - assert_property_list_match (pasted_el_props, num_pasted_el_props, - track_el_props, num_track_el_props); + assert_equal_children_properties (track_el, pasted_el); /* check the control binding on the pasted element */ _assert_binding (pasted_el, "posx", pasted_sub_child, timed_vals, GST_INTERPOLATION_MODE_CUBIC); + assert_equal_bindings (pasted_el, track_el); /* free */ g_slist_free_full (timed_vals, g_free); - free_children_properties (pasted_props, num_pasted_props); free_children_properties (orig_props, num_orig_props); - free_children_properties (pasted_el_props, num_pasted_el_props); - free_children_properties (track_el_props, num_track_el_props); g_param_spec_unref (prop); g_object_unref (pasted_sub_child); @@ -1933,6 +2587,7 @@ ges_suite (void) tcase_add_test (tc_chain, test_split_direct_bindings); tcase_add_test (tc_chain, test_split_direct_absolute_bindings); tcase_add_test (tc_chain, test_clip_group_ungroup); + tcase_add_test (tc_chain, test_adding_children_to_track); tcase_add_test (tc_chain, test_clip_refcount_remove_child); tcase_add_test (tc_chain, test_clip_find_track_element); tcase_add_test (tc_chain, test_effects_priorities); diff --git a/tests/check/ges/test-utils.h b/tests/check/ges/test-utils.h index 1dc7057ff7..8208361e28 100644 --- a/tests/check/ges/test-utils.h +++ b/tests/check/ges/test-utils.h @@ -106,6 +106,23 @@ G_STMT_START { \ GST_TIME_ARGS (_MAX_DURATION(obj)), GST_TIME_ARGS (max_duration)); \ } +#define assert_num_in_track(track, val) \ +{ \ + GList *tmp = ges_track_get_elements (track); \ + guint length = g_list_length (tmp); \ + fail_unless (length == val, "Track %" GST_PTR_FORMAT \ + " contains %u track elements, rather than %u", track, length, val); \ + g_list_free_full (tmp, gst_object_unref); \ +} + +#define assert_num_children(clip, cmp) \ +{ \ + guint num_children = g_list_length (GES_CONTAINER_CHILDREN (clip)); \ + fail_unless (cmp == num_children, \ + "clip %s contains %u children rather than %u", \ + GES_TIMELINE_ELEMENT_NAME (clip), num_children, cmp); \ +} + /* 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) \ @@ -142,6 +159,27 @@ G_STMT_START { \ GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), layer_prio); \ } +#define assert_layer(clip, layer) \ +{ \ + GESLayer *tmp_layer = ges_clip_get_layer (GES_CLIP (clip)); \ + fail_unless (tmp_layer == GES_LAYER (layer), "clip %s belongs to " \ + "layer %u (timeline %" GST_PTR_FORMAT ") rather than layer %u " \ + "(timeline %" GST_PTR_FORMAT ")", \ + tmp_layer ? ges_layer_get_priority (tmp_layer) : 0, \ + tmp_layer ? tmp_layer->timeline : NULL, \ + layer ? ges_layer_get_priority (GES_LAYER (layer)) : 0, \ + layer ? GES_LAYER (layer)->timeline : NULL); \ + if (tmp_layer) \ + gst_object_unref (tmp_layer); \ + if (layer) { \ + GList *layer_clips = ges_layer_get_clips (GES_LAYER (layer)); \ + fail_unless (g_list_find (layer_clips, clip), "clip %s not found " \ + "in layer %u (timeline %" GST_PTR_FORMAT ")", \ + ges_layer_get_priority (GES_LAYER (layer)), layer->timeline); \ + g_list_free_full (layer_clips, gst_object_unref); \ + } \ +} + /* test that the two property lists contain the same properties the same * number of times */ #define assert_property_list_match(list1, len1, list2, len2) \ @@ -188,5 +226,144 @@ G_STMT_START { \ g_free (count_list1); \ } +#define assert_equal_children_properties(el1, el2) \ +{ \ + guint i, num1, num2; \ + const gchar *name1 = GES_TIMELINE_ELEMENT_NAME (el1); \ + const gchar *name2 = GES_TIMELINE_ELEMENT_NAME (el2); \ + GParamSpec **el_props1 = ges_timeline_element_list_children_properties ( \ + GES_TIMELINE_ELEMENT (el1), &num1); \ + GParamSpec **el_props2 = ges_timeline_element_list_children_properties ( \ + GES_TIMELINE_ELEMENT (el2), &num2); \ + assert_property_list_match (el_props1, num1, el_props2, num2); \ + \ + for (i = 0; i < num1; i++) { \ + gchar *ser1, *ser2; \ + GParamSpec *prop = el_props1[i]; \ + GValue val1 = G_VALUE_INIT, val2 = G_VALUE_INIT; \ + /* name property can be different */ \ + if (g_strcmp0 (prop->name, "name") == 0) \ + continue; \ + if (g_strcmp0 (prop->name, "parent") == 0) \ + continue; \ + g_value_init (&val1, prop->value_type); \ + g_value_init (&val2, prop->value_type); \ + ges_timeline_element_get_child_property_by_pspec ( \ + GES_TIMELINE_ELEMENT (el1), prop, &val1); \ + ges_timeline_element_get_child_property_by_pspec ( \ + GES_TIMELINE_ELEMENT (el2), prop, &val2); \ + ser1 = gst_value_serialize (&val1); \ + ser2 = gst_value_serialize (&val2); \ + fail_unless (gst_value_compare (&val1, &val2) == GST_VALUE_EQUAL, \ + "Child property '%s' for %s does not match that for %s (%s vs %s)", \ + prop->name, name1, name2, ser1, ser2); \ + g_free (ser1); \ + g_free (ser2); \ + g_value_unset (&val1); \ + g_value_unset (&val2); \ + } \ + free_children_properties (el_props1, num1); \ + free_children_properties (el_props2, num2); \ +} + +#define assert_equal_bindings(el1, el2) \ +{ \ + guint i, num1, num2; \ + const gchar *name1 = GES_TIMELINE_ELEMENT_NAME (el1); \ + const gchar *name2 = GES_TIMELINE_ELEMENT_NAME (el2); \ + GParamSpec **props1 = ges_timeline_element_list_children_properties ( \ + GES_TIMELINE_ELEMENT (el1), &num1); \ + GParamSpec **props2 = ges_timeline_element_list_children_properties ( \ + GES_TIMELINE_ELEMENT (el2), &num2); \ + assert_property_list_match (props1, num1, props2, num2); \ + \ + for (i = 0; i < num1; i++) { \ + const gchar *prop = props1[i]->name; \ + GList *tmp1, *tmp2; \ + GList *timed_vals1, *timed_vals2; \ + GObject *object1, *object2; \ + gboolean abs1, abs2; \ + GstControlSource *source1, *source2; \ + GstInterpolationMode mode1, mode2; \ + GstControlBinding *binding1, *binding2; \ + guint j; \ + \ + binding1 = ges_track_element_get_control_binding ( \ + GES_TRACK_ELEMENT (el1), prop); \ + binding2 = ges_track_element_get_control_binding ( \ + GES_TRACK_ELEMENT (el2), prop); \ + if (binding1 == NULL) { \ + fail_unless (binding2 == NULL, "%s has a binding for property " \ + " '%s', whilst %s does not", name2, prop, name1); \ + continue; \ + } \ + if (binding2 == NULL) { \ + fail_unless (binding1 == NULL, "%s has a binding for property " \ + "'%s', whilst %s does not", name1, prop, name2); \ + continue; \ + } \ + \ + fail_unless (G_OBJECT_TYPE (binding1) == GST_TYPE_DIRECT_CONTROL_BINDING, \ + "%s binding for property '%s' is not a direct control binding, " \ + "so cannot be handled", prop, name1); \ + fail_unless (G_OBJECT_TYPE (binding2) == GST_TYPE_DIRECT_CONTROL_BINDING, \ + "%s binding for property '%s' is not a direct control binding, " \ + "so cannot be handled", prop, name2); \ + \ + g_object_get (G_OBJECT (binding1), "control-source", &source1, \ + "absolute", &abs1, "object", &object1, NULL); \ + g_object_get (G_OBJECT (binding2), "control-source", &source2, \ + "absolute", &abs2, "object", &object2, NULL); \ + \ + fail_unless (G_OBJECT_TYPE (object1) == G_OBJECT_TYPE (object2), \ + "The child object for property '%s' for %s and %s correspond " \ + "to different object types (%s vs %s)", prop, name1, name2, \ + G_OBJECT_TYPE_NAME (object1), G_OBJECT_TYPE_NAME (object2)); \ + gst_object_unref (object1); \ + gst_object_unref (object2); \ + \ + fail_unless (abs1 == abs2, "control biding for property '%s' " \ + " is %s absolute for %s, but %s absolute for %s", prop, \ + abs1 ? "" : "not", name1, abs2 ? "" : "not", name2); \ + \ + fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source1), \ + "%s does not have an interpolation control source for " \ + "property '%s', so cannot be handled", name1, prop); \ + fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source2), \ + "%s does not have an interpolation control source for " \ + "property '%s', so cannot be handled", name2, prop); \ + g_object_get (G_OBJECT (source1), "mode", &mode1, NULL); \ + g_object_get (G_OBJECT (source2), "mode", &mode2, NULL); \ + fail_unless (mode1 == mode2, "control source for property '%s' " \ + "has different modes for %s and %s (%i vs %i)", prop, \ + name1, name2, mode1, mode2); \ + \ + timed_vals1 = gst_timed_value_control_source_get_all ( \ + GST_TIMED_VALUE_CONTROL_SOURCE (source1)); \ + timed_vals2 = gst_timed_value_control_source_get_all ( \ + GST_TIMED_VALUE_CONTROL_SOURCE (source2)); \ + \ + for (j = 0, tmp1 = timed_vals1, tmp2 = timed_vals2; tmp1 && tmp2; \ + j++, tmp1 = tmp1->next, tmp2 = tmp2->next) { \ + GstTimedValue *val1 = tmp1->data, *val2 = tmp2->data; \ + fail_unless (val1->timestamp == val2->timestamp && \ + val1->value == val2->value, "The %uth timed value for property " \ + "'%s' is different for %s and %s: (%" G_GUINT64_FORMAT ": %g) vs " \ + "(%" G_GUINT64_FORMAT ": %g)", j, prop, name1, name2, \ + val1->timestamp, val1->value, val2->timestamp, val2->value); \ + } \ + fail_unless (tmp1 == NULL, "Found too many timed values for " \ + "property '%s' for %s", prop, name1); \ + fail_unless (tmp2 == NULL, "Found too many timed values for " \ + "property '%s' for %s", prop, name2); \ + \ + g_list_free (timed_vals1); \ + g_list_free (timed_vals2); \ + gst_object_unref (source1); \ + gst_object_unref (source2); \ + } \ + free_children_properties (props1, num1); \ + free_children_properties (props2, num2); \ +} void print_timeline(GESTimeline *timeline);