gboolean prevent_max_duration_update;
gboolean setting_inpoint;
+ gboolean allow_any_track;
+
/* The formats supported by this Clip */
GESTrackType supportedformats;
};
* 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) \
_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;
+}
/*****************************************************
* *
{
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;
}
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 */
/* 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);
/* 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",
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.");
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);
*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));
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);
}
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)
{
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);
- ges_track_element_copy_bindings (el, el_copy, GST_CLOCK_TIME_NONE);
+ 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);
+
+ /* _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);
}
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);
-
- /* 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;
- }
+ /* 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);
- 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,
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);
}
{
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);
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);
}
/**
*
* 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.
* @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
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)) {
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;
- }
- }
- g_list_free_full (children, gst_object_unref);
-
- /* 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);
+ /* 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;
}
- 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);
+ if (already_created)
+ return NULL;
- 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;
}
/*
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));
}
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
}
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
}
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),
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;
}
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;
+}
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 *
****************************************************/
#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
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
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 *
{
GESLayer *current_layer;
GList *tmp;
+ GESTimeline *timeline = layer->timeline;
GST_DEBUG ("layer:%p, clip:%p", layer, 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);
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;
}
*/
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:
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;
/* 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;
* @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
* #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),
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 ();
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);
}
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
-add_object_to_tracks (GESTimeline * timeline, GESClip * clip, GESTrack * track)
+_set_track_selection_error (GESTimeline * timeline, gboolean error)
{
- gint i;
- GList *tmp, *list;
- GESTrackType types, visited_type = GES_TRACK_TYPE_UNKNOWN;
+ LOCK_DYN (timeline);
+ timeline->priv->track_selection_error = error;
+ UNLOCK_DYN (timeline);
+}
- GST_DEBUG_OBJECT (timeline, "Creating %" GST_PTR_FORMAT
- " trackelements and adding them to our tracks", clip);
+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;
+ }
- types = ges_clip_get_supported_formats (clip);
if (track) {
- if ((types & track->type) == 0)
- return;
- types = track->type;
+ /* 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);
}
+}
- 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)))
+/* 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)
+{
+ 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);
+
+ LOCK_DYN (timeline);
+ 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);
- g_list_free (list);
+ 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);
}
- UNLOCK_DYN (timeline);
+ 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
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,
"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)));
ges_extractable_get_asset (GES_EXTRACTABLE (clip)));
GST_DEBUG ("Done");
+
+ return ret;
}
static void
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"
return;
}
- GST_DEBUG_OBJECT (timeline, "Clip %" GES_FORMAT " removed from layer %p",
- GES_ARGS (clip), layer);
-
- /* Go over the clip's track element and figure out which one belongs to
- * the list of tracks we control */
-
- 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);
+ GST_DEBUG_OBJECT (timeline, "Clip %" GES_FORMAT " removed from layer",
+ GES_ARGS (clip));
- /* 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");
+ LOCK_DYN (timeline);
+ for (tmp = timeline->tracks; tmp; tmp = tmp->next)
+ ges_clip_empty_from_track (clip, tmp->data);
+ UNLOCK_DYN (timeline);
- 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");
}
*
* 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.
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",
/* 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;
}
/* 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,
* @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.
*/
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 */
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 */
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) {
}
g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_TRACK]);
- return ret;
+ return TRUE;
}
void
* accessing it
*/
static gboolean
-remove_object_internal (GESTrack * track, GESTrackElement * object)
+remove_object_internal (GESTrack * track, GESTrackElement * object,
+ gboolean emit)
{
GESTrackPrivate *priv;
GstElement *nleobject;
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);
static void
dispose_trackelements_foreach (GESTrackElement * trackelement, GESTrack * track)
{
- remove_object_internal (track, trackelement);
+ remove_object_internal (track, trackelement, TRUE);
}
/* GstElement virtual methods */
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);
}
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
* 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.
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);
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)),
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;
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));
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));
+ CHECK_THREAD (track);
- return FALSE;
+ return remove_element_internal (track, object, TRUE);
}
/**
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");
* 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");
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));
/* 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);
* 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;
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 */
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);
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 ();
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));
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));
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;
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");
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");
* 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);
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;
+
+ for (i = 0; i < 4; i++) {
+ if (clip == data->clips[i]) {
+ data->num_calls[i]++;
+ recognise_clip = TRUE;
+ }
+ }
- gst_object_ref (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;
+ }
- g_ptr_array_add (ret, track);
+ 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;
}
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 ();
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);
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);
+
+ 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)));
+
+ 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;
- 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);
+ 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 */
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);
- /* 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_equals_int (st_data.num_unrecognised, 0);
+ /* Make sure the associated TrackElements are in the Track */
+ 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);
+ /* 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) {
- /* 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);
+ 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;
+ }
+ }
}
- 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);
+ 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);
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) ==
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 ();
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 ();
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 ();
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);
+
+ 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_equals_uint64 (_START (splitclip), 67);
- assert_equals_uint64 (_DURATION (splitclip), 25);
- assert_equals_uint64 (_INPOINT (splitclip), 37);
+ 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);
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 ();
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);
-
- 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);
-
- 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);
+ 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);
- 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);
+ 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 (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);
- 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);
-
- 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);
+ assert_layer (regrouped_clip, layer);
+
+ 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);
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;
}
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 ();
}
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 ());
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));
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), 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);
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);
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 ();
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));
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 */
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,
_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);
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);
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) \
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) \
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);