timeline: re-handle clip children track selection

The way a clip's track elements are added to tracks was re-handled. This
doesn't affect the normal usage of a simple audio-video timeline, where
the tracks are added before any clips, but usage for multi-track
timelines has improved. The main changes are:

+ We can now handle a track being selected for more than one track,
  including a full copy of their children properties and bindings.
  (Previously broken.)
+ When a clip is split, we copy the new elements directly into the same
  track, avoiding select-tracks-for-object.
+ When a clip is grouped or ungrouped, we avoid moving the elements to
  or from tracks.
+ Added API to allow users to copy the core elements of a clip directly
  into a track, complementing select-tracks-for-object.
+ Enforced the rule that a clip can only contain one core child in a
  track, and all the non-core children must be added to tracks that
  already contains a core child. This extends the previous condition
  that two sources from the same clip should not be added to the same
  track.
+ Made ges_track_add_element check that the newly added track element
  does not break the configuration rules of the timeline.
+ When adding a track to a timeline, we only use
  select-tracks-for-object to check whether track elements should be
  added to the new track, not existing ones.
+ When removing a track from a timeline, we empty it of all the track
  elements that are controlled by a clip. Thus, we ensure that a clip
  only contains elements that are in the tracks of the same timeline, or
  no track. Similarly, when removing a clip from a timeline.
+ We can now avoid unsupported timeline configurations when a layer is
  added to a timeline, and already contains clips.
+ We can now avoid unsupported timeline configurations when a track is
  added to a timeline, and the timeline already contains clips.

Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/84
This commit is contained in:
Henry Wilkes 2020-04-06 12:09:54 +01:00
parent f7a1bdb289
commit 269c2d1dc0
11 changed files with 2230 additions and 838 deletions

View file

@ -113,6 +113,8 @@ struct _GESClipPrivate
gboolean prevent_max_duration_update;
gboolean setting_inpoint;
gboolean allow_any_track;
/* The formats supported by this Clip */
GESTrackType supportedformats;
};
@ -148,8 +150,10 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESClip, ges_clip,
* Listen to our children *
****************************************************/
#define _IS_CORE_CHILD(child) \
(ges_track_element_get_creators (GES_TRACK_ELEMENT (child)) != NULL)
#define _IS_CORE_CHILD(child) GES_TRACK_ELEMENT_IS_CORE(child)
#define _IS_TOP_EFFECT(child) \
(!_IS_CORE_CHILD (child) && GES_IS_BASE_EFFECT (child))
#define _IS_CORE_INTERNAL_SOURCE_CHILD(child) \
(_IS_CORE_CHILD (child) \
@ -275,6 +279,85 @@ _child_has_internal_source_changed_cb (GESTimelineElement * child,
_set_inpoint0 (child, _INPOINT (container));
}
/****************************************************
* Restrict our children *
****************************************************/
static gboolean
_track_contains_core (GESClip * clip, GESTrack * track, gboolean core)
{
GList *tmp;
for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
GESTrackElement *child = tmp->data;
if (_IS_CORE_CHILD (child) == core
&& ges_track_element_get_track (child) == track)
return TRUE;
}
return FALSE;
}
gboolean
ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child,
GESTrack * track)
{
GESTrack *current_track = ges_track_element_get_track (child);
if (clip->priv->allow_any_track)
return TRUE;
if (current_track == track)
return TRUE;
if (current_track) {
/* can not remove a core element from a track if a non-core one sits
* above it */
if (_IS_CORE_CHILD (child)
&& _track_contains_core (clip, current_track, FALSE)) {
GST_INFO_OBJECT (clip, "Cannot move the core child %" GES_FORMAT
" to the track %" GST_PTR_FORMAT " because it has non-core "
"siblings above it in its current track %" GST_PTR_FORMAT,
GES_ARGS (child), track, current_track);
return FALSE;
}
/* otherwise can remove */
}
if (track) {
GESTimeline *clip_timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
const GESTimeline *track_timeline = ges_track_get_timeline (track);
if (track_timeline == NULL) {
GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT
" to the track %" GST_PTR_FORMAT " because it is not part "
"of a timeline", GES_ARGS (child), track);
return FALSE;
}
if (track_timeline != clip_timeline) {
GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT
" to the track %" GST_PTR_FORMAT " because its timeline %"
GST_PTR_FORMAT " does not match the clip's timeline %"
GST_PTR_FORMAT, GES_ARGS (child), track, track_timeline,
clip_timeline);
return FALSE;
}
/* one core child per track, and other children (effects) can only be
* placed in a track that already has a core child */
if (_IS_CORE_CHILD (child)) {
if (_track_contains_core (clip, track, TRUE)) {
GST_INFO_OBJECT (clip, "Cannot move the core child %" GES_FORMAT
" to the track %" GST_PTR_FORMAT " because it contains a "
"core sibling", GES_ARGS (child), track);
return FALSE;
}
} else {
if (!_track_contains_core (clip, track, TRUE)) {
GST_INFO_OBJECT (clip, "Cannot move the non-core child %"
GES_FORMAT " to the track %" GST_PTR_FORMAT " because it "
" does not contain a core sibling", GES_ARGS (child), track);
return FALSE;
}
}
}
return TRUE;
}
/*****************************************************
* *
@ -574,17 +657,18 @@ _add_child (GESContainer * container, GESTimelineElement * element)
{
GESClipClass *klass = GES_CLIP_GET_CLASS (GES_CLIP (container));
guint max_prio, min_prio;
GESTrack *track;
GList *creators;
GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (container);
GESClipPrivate *priv = GES_CLIP (container)->priv;
g_return_val_if_fail (GES_IS_TRACK_ELEMENT (element), FALSE);
if (element->timeline
&& element->timeline != GES_TIMELINE_ELEMENT_TIMELINE (container)) {
if (element->timeline && element->timeline != timeline) {
GST_WARNING_OBJECT (container, "Cannot add %" GES_FORMAT " as a child "
"because its timeline is %" GST_PTR_FORMAT " rather than the "
"clip's timeline %" GST_PTR_FORMAT, GES_ARGS (element),
element->timeline, GES_TIMELINE_ELEMENT_TIMELINE (container));
element->timeline, timeline);
return FALSE;
}
@ -597,12 +681,34 @@ _add_child (GESContainer * container, GESTimelineElement * element)
return FALSE;
}
track = ges_track_element_get_track (GES_TRACK_ELEMENT (element));
if (track && ges_track_get_timeline (track) != timeline) {
/* really, an element in a track should have the same timeline as
* the track, so we would have checked this with the
* element->timeline check. But technically a user could get around
* this, so we double check here. */
GST_WARNING_OBJECT (container, "Cannot add %" GES_FORMAT " as a child "
"because its track %" GST_PTR_FORMAT " is part of the timeline %"
GST_PTR_FORMAT " rather than the clip's timeline %" GST_PTR_FORMAT,
GES_ARGS (element), track, ges_track_get_timeline (track), timeline);
return FALSE;
}
/* NOTE: notifies are currently frozen by ges_container_add */
_get_priority_range (container, &min_prio, &max_prio);
if (creators) {
/* NOTE: Core track elements that are base effects are added like any
* other core clip. In particular, they are *not* added to the list of
* added effects, so we don not increase nb_effects. */
* other core elements. In particular, they are *not* added to the
* list of added effects, so we do not increase nb_effects. */
if (track && !priv->allow_any_track
&& _track_contains_core (GES_CLIP (container), track, TRUE)) {
GST_WARNING_OBJECT (container, "Cannot add the core child %" GES_FORMAT
" because it is in the same track %" GST_PTR_FORMAT " as an "
"existing core child", GES_ARGS (element), track);
return FALSE;
}
/* Set the core element to have the same in-point, which we don't
* apply to effects */
@ -620,12 +726,20 @@ _add_child (GESContainer * container, GESTimelineElement * element)
/* Always add at the same priority, on top of existing effects */
_set_priority0 (element, min_prio + priv->nb_effects);
} else if (GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) &&
GES_IS_BASE_EFFECT (element)) {
} else if (GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) && _IS_TOP_EFFECT (element)) {
GList *tmp;
/* Add the effect at the lowest priority among effects (just after
* the core elements). Need to shift the core elements up by 1
* to make room. */
if (track && !priv->allow_any_track
&& !_track_contains_core (GES_CLIP (container), track, TRUE)) {
GST_WARNING_OBJECT (container, "Cannot add the effect %" GES_FORMAT
" because its track %" GST_PTR_FORMAT " does not contain one "
"of the clip's core children", GES_ARGS (element), track);
return FALSE;
}
GST_DEBUG_OBJECT (container, "Adding %ith effect: %" GES_FORMAT
" Priority %i", priv->nb_effects + 1, GES_ARGS (element),
min_prio + priv->nb_effects);
@ -646,7 +760,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
/* The height has already changed (increased by 1) */
_compute_height (container);
} else {
if (GES_IS_BASE_EFFECT (element))
if (_IS_TOP_EFFECT (element))
GST_WARNING_OBJECT (container, "Cannot add the effect %" GES_FORMAT
" because it is not a core element created by the clip itself "
"and the %s class does not allow for adding extra effects",
@ -674,7 +788,7 @@ _remove_child (GESContainer * container, GESTimelineElement * element)
GESClipPrivate *priv = GES_CLIP (container)->priv;
/* NOTE: notifies are currently frozen by ges_container_add */
if (!_IS_CORE_CHILD (element) && GES_IS_BASE_EFFECT (element)) {
if (_IS_TOP_EFFECT (element)) {
GList *tmp;
GST_DEBUG_OBJECT (container, "Resyncing effects priority.");
@ -701,13 +815,13 @@ _remove_child (GESContainer * container, GESTimelineElement * element)
static void
_child_added (GESContainer * container, GESTimelineElement * element)
{
g_signal_connect (G_OBJECT (element), "notify::priority",
g_signal_connect (element, "notify::priority",
G_CALLBACK (_child_priority_changed_cb), container);
g_signal_connect (G_OBJECT (element), "notify::in-point",
g_signal_connect (element, "notify::in-point",
G_CALLBACK (_child_inpoint_changed_cb), container);
g_signal_connect (G_OBJECT (element), "notify::max-duration",
g_signal_connect (element, "notify::max-duration",
G_CALLBACK (_child_max_duration_changed_cb), container);
g_signal_connect (G_OBJECT (element), "notify::has-internal-source",
g_signal_connect (element, "notify::has-internal-source",
G_CALLBACK (_child_has_internal_source_changed_cb), container);
_child_priority_changed_cb (element, NULL, container);
@ -738,12 +852,24 @@ add_clip_to_list (gpointer key, gpointer clip, GList ** list)
*list = g_list_prepend (*list, gst_object_ref (clip));
}
/* NOTE: Since this does not change the track of @child, this should
* only be called if it is guaranteed that neither @from_clip nor @to_clip
* will not break the track rules:
* + no more than one core child per track
* + every non-core child must be in the same track as a core child
*/
static void
_transfer_child (GESClip * from_clip, GESClip * to_clip,
GESTrackElement * child)
{
GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (to_clip);
/* We need to bump the refcount to avoid the object to be destroyed */
gst_object_ref (child);
/* don't want to change tracks */
ges_timeline_set_moving_track_elements (timeline, TRUE);
ges_container_remove (GES_CONTAINER (from_clip),
GES_TIMELINE_ELEMENT (child));
@ -752,7 +878,11 @@ _transfer_child (GESClip * from_clip, GESClip * to_clip,
ges_track_element_add_creator (child, to_clip);
}
to_clip->priv->allow_any_track = TRUE;
ges_container_add (GES_CONTAINER (to_clip), GES_TIMELINE_ELEMENT (child));
to_clip->priv->allow_any_track = FALSE;
ges_timeline_set_moving_track_elements (timeline, FALSE);
gst_object_unref (child);
}
@ -982,10 +1112,87 @@ _group (GList * containers)
done:
if (tracks)
g_free (tracks);
return ret;
}
void
ges_clip_empty_from_track (GESClip * clip, GESTrack * track)
{
GList *tmp;
if (track == NULL)
return;
/* allow us to remove in any order */
clip->priv->allow_any_track = TRUE;
for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
GESTrackElement *child = tmp->data;
if (ges_track_element_get_track (child) == track) {
if (!ges_track_remove_element (track, child))
GST_ERROR_OBJECT (clip, "Failed to remove child %" GES_FORMAT
" from the track %" GST_PTR_FORMAT, GES_ARGS (child), track);
}
}
clip->priv->allow_any_track = FALSE;
}
static GESTrackElement *
_copy_track_element_to (GESTrackElement * orig, GESClip * to_clip,
GstClockTime position)
{
GESTrackElement *copy;
GESTimelineElement *el_copy, *el_orig;
/* NOTE: we do not deep copy the track element, we instead call
* ges_track_element_copy_properties explicitly, which is the
* deep_copy for the GESTrackElementClass. */
el_orig = GES_TIMELINE_ELEMENT (orig);
el_copy = ges_timeline_element_copy (el_orig, FALSE);
if (el_copy == NULL)
return NULL;
copy = GES_TRACK_ELEMENT (el_copy);
ges_track_element_copy_properties (el_orig, el_copy);
/* NOTE: control bindings that are not registered in GES are not
* handled */
ges_track_element_copy_bindings (orig, copy, position);
if (_IS_CORE_CHILD (orig))
ges_track_element_add_creator (copy, to_clip);
return copy;
}
static GESTrackElement *
ges_clip_copy_track_element_into (GESClip * clip, GESTrackElement * orig,
GstClockTime position)
{
GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
GESTrackElement *copy;
copy = _copy_track_element_to (orig, clip, position);
if (copy == NULL) {
GST_ERROR_OBJECT (clip, "Failed to create a copy of the "
"element %" GES_FORMAT " for the clip", GES_ARGS (orig));
return NULL;
}
gst_object_ref (copy);
ges_timeline_set_moving_track_elements (timeline, TRUE);
if (!ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (copy))) {
GST_ERROR_OBJECT (clip, "Failed to add the copied child track "
"element %" GES_FORMAT " to the clip", GES_ARGS (copy));
ges_timeline_set_moving_track_elements (timeline, FALSE);
gst_object_unref (copy);
return NULL;
}
ges_timeline_set_moving_track_elements (timeline, FALSE);
/* now owned by the clip */
gst_object_unref (copy);
return copy;
}
static void
_deep_copy (GESTimelineElement * element, GESTimelineElement * copy)
{
@ -993,20 +1200,26 @@ _deep_copy (GESTimelineElement * element, GESTimelineElement * copy)
GESClip *self = GES_CLIP (element), *ccopy = GES_CLIP (copy);
GESTrackElement *el, *el_copy;
if (!self->priv->layer)
return;
/* NOTE: this should only be called on a newly created @copy, so
* its copied_track_elements, and copied_layer, should be free to set
* without disposing of the previous values */
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
el = GES_TRACK_ELEMENT (tmp->data);
/* copies the children properties */
el_copy = GES_TRACK_ELEMENT (ges_timeline_element_copy (tmp->data, TRUE));
/* any element created by self, will have its copy considered created
* by self's copy */
if (_IS_CORE_CHILD (el))
ges_track_element_add_creator (el_copy, ccopy);
el_copy = _copy_track_element_to (el, ccopy, GST_CLOCK_TIME_NONE);
if (!el_copy) {
GST_ERROR_OBJECT (element, "Failed to copy the track element %"
GES_FORMAT " for pasting", GES_ARGS (el));
continue;
}
/* owned by copied_track_elements */
gst_object_ref_sink (el_copy);
ges_track_element_copy_bindings (el, el_copy, GST_CLOCK_TIME_NONE);
/* _add_child will add core elements at the lowest priority and new
* non-core effects at the lowest effect priority, so we need to add
* the highest priority children first to preserve the effect order.
* The clip's children are already ordered by highest priority first.
* So we order copied_track_elements in the same way */
ccopy->priv->copied_track_elements =
g_list_append (ccopy->priv->copied_track_elements, el_copy);
}
@ -1022,48 +1235,12 @@ _paste (GESTimelineElement * element, GESTimelineElement * ref,
GESClip *self = GES_CLIP (element);
GESClip *nclip = GES_CLIP (ges_timeline_element_copy (element, FALSE));
if (self->priv->copied_layer)
nclip->priv->copied_layer = g_object_ref (self->priv->copied_layer);
ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (nclip), paste_position);
for (tmp = self->priv->copied_track_elements; tmp; tmp = tmp->next) {
GESTrackElement *new_trackelement, *trackelement =
GES_TRACK_ELEMENT (tmp->data);
/* paste in order of priority (highest first) */
for (tmp = self->priv->copied_track_elements; tmp; tmp = tmp->next)
ges_clip_copy_track_element_into (nclip, tmp->data, GST_CLOCK_TIME_NONE);
/* NOTE: we do not deep copy the track element, we instead call
* ges_track_element_copy_properties explicitly, which is the
* deep_copy for the GESTrackElementClass. */
new_trackelement =
GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT
(trackelement), FALSE));
if (new_trackelement == NULL) {
GST_WARNING_OBJECT (trackelement, "Could not create a copy");
continue;
}
if (_IS_CORE_CHILD (trackelement))
ges_track_element_add_creator (new_trackelement, nclip);
gst_object_ref_sink (new_trackelement);
if (!ges_container_add (GES_CONTAINER (nclip),
GES_TIMELINE_ELEMENT (new_trackelement))) {
GST_ERROR_OBJECT (self, "Failed add the copied child track element %"
GES_FORMAT " to the copy %" GES_FORMAT,
GES_ARGS (new_trackelement), GES_ARGS (nclip));
gst_object_unref (new_trackelement);
continue;
}
ges_track_element_copy_properties (GES_TIMELINE_ELEMENT (trackelement),
GES_TIMELINE_ELEMENT (new_trackelement));
ges_track_element_copy_bindings (trackelement, new_trackelement,
GST_CLOCK_TIME_NONE);
gst_object_unref (new_trackelement);
}
/* FIXME: should we bypass the select-tracks-for-object signal when
* copying and pasting? */
if (self->priv->copied_layer) {
if (!ges_layer_add_clip (self->priv->copied_layer, nclip)) {
GST_INFO ("%" GES_FORMAT " could not be pasted to %" GST_TIME_FORMAT,
@ -1071,9 +1248,12 @@ _paste (GESTimelineElement * element, GESTimelineElement * ref,
return NULL;
}
}
/* NOTE: self should not be used and be freed after this call, so we can
* leave the freeing of copied_layer and copied_track_elements to the
* dispose method */
return GES_TIMELINE_ELEMENT (nclip);
}
@ -1138,7 +1318,7 @@ ges_clip_dispose (GObject * object)
{
GESClip *self = GES_CLIP (object);
g_list_free_full (self->priv->copied_track_elements, g_object_unref);
g_list_free_full (self->priv->copied_track_elements, gst_object_unref);
self->priv->copied_track_elements = NULL;
g_clear_object (&self->priv->copied_layer);
@ -1218,15 +1398,7 @@ ges_clip_class_init (GESClipClass * klass)
static void
ges_clip_init (GESClip * self)
{
GESClipPrivate *priv;
priv = self->priv = ges_clip_get_instance_private (self);
priv->layer = NULL;
priv->nb_effects = 0;
priv->prevent_priority_offset_update = FALSE;
priv->prevent_resort = FALSE;
priv->updating_max_duration = FALSE;
priv->prevent_max_duration_update = FALSE;
priv->setting_inpoint = FALSE;
self->priv = ges_clip_get_instance_private (self);
}
/**
@ -1236,9 +1408,6 @@ ges_clip_init (GESClip * self)
*
* Creates the core #GESTrackElement of the clip, of the given track type.
*
* Note, unlike ges_clip_create_track_elements(), this does not add the
* created track element to the clip or set their timings.
*
* Returns: (transfer floating) (nullable): The element created
* by @clip, or %NULL if @clip can not provide a track element for the
* given @type or an error occurred.
@ -1277,7 +1446,7 @@ ges_clip_create_track_element (GESClip * clip, GESTrackType type)
* @type: The track-type to create elements for
*
* Creates the core #GESTrackElement-s of the clip, of the given track
* type, and adds them to the clip.
* type.
*
* Returns: (transfer container) (element-type GESTrackElement): A list of
* the #GESTrackElement-s created by @clip for the given @type, or %NULL
@ -1287,14 +1456,15 @@ ges_clip_create_track_element (GESClip * clip, GESTrackType type)
GList *
ges_clip_create_track_elements (GESClip * clip, GESTrackType type)
{
/* add_list holds a ref to its elements to keep them alive
* result does not */
GList *result = NULL, *add_list = NULL, *tmp, *children;
GList *tmp, *ret;
GESClipClass *klass;
gboolean readding_effects_only = TRUE;
gboolean already_created = FALSE;
g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
if ((clip->priv->supportedformats & type) == 0)
return NULL;
klass = GES_CLIP_GET_CLASS (clip);
if (!(klass->create_track_elements)) {
@ -1304,71 +1474,24 @@ ges_clip_create_track_elements (GESClip * clip, GESTrackType type)
GST_DEBUG_OBJECT (clip, "Creating TrackElements for type: %s",
ges_track_type_name (type));
children = ges_container_get_children (GES_CONTAINER (clip), TRUE);
for (tmp = children; tmp; tmp = tmp->next) {
for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
GESTrackElement *child = GES_TRACK_ELEMENT (tmp->data);
if (!ges_track_element_get_track (child)
if (_IS_CORE_CHILD (child)
&& ges_track_element_get_track_type (child) & type) {
GST_DEBUG_OBJECT (clip, "Removing for reusage: %" GST_PTR_FORMAT, child);
add_list = g_list_append (add_list, gst_object_ref (child));
ges_container_remove (GES_CONTAINER (clip), tmp->data);
if (_IS_CORE_CHILD (child))
readding_effects_only = FALSE;
/* assume the core track elements have all been created if we find
* at least one core child with the same type */
already_created = TRUE;
break;
}
}
g_list_free_full (children, gst_object_unref);
if (already_created)
return NULL;
/* FIXME: we need something smarter to determine whether we should
* create the track elements.
* Currently, if the clip contains at least one element with a matching
* track-type, not in a track and not a GESBaseEffect, we will not
* recreate the track elements! But this is not a reliable indicator.
*
* For example, consider a uri clip that creates two audio track
* elements: El_A and El_B. First, we add the clip to a timeline that
* only has a single track: Track_A, and we connect to the timeline's
* ::select-tracks-for-object signal to only allow El_A to end up in
* Track_A. As such, whilst both El_A and El_B are initially created,
* El_B will eventually be removed from the clip since it has no track
* (see clip_track_element_added_cb in ges-timeline.c). As such, we now
* have a clip that only contains El_A.
* Next, we remove Track_A from the timeline. El_A will remain a child
* of the clip, but now has its track unset.
* Next, we add Track_B to the timeline, and we connect to the
* timeline's ::select-tracks-for-object signal to only allow El_B to
* end up in Track_B.
*
* However, since the clip contains an audio track element, that is not
* an effect and has no track set: El_A. Therefore, the
* create_track_elements method below will not be called, so we will not
* have an El_B created for Track_B!
*
* Moreover, even if we did recreate the track elements, we would be
* creating El_A again! We could destroy and recreate El_A instead, or
* we would need a way to determine exactly which elements need to be
* recreated.
*/
if (readding_effects_only) {
GList *track_elements = klass->create_track_elements (clip, type);
for (tmp = track_elements; tmp; tmp = tmp->next) {
gst_object_ref_sink (tmp->data);
ges_track_element_add_creator (tmp->data, clip);
}
add_list = g_list_concat (track_elements, add_list);
}
for (tmp = add_list; tmp; tmp = tmp->next) {
GESTimelineElement *el = GES_TIMELINE_ELEMENT (tmp->data);
if (ges_container_add (GES_CONTAINER (clip), el))
result = g_list_append (result, el);
else
GST_ERROR_OBJECT (clip, "Failed add the track element %"
GES_FORMAT " to the clip", GES_ARGS (el));
}
g_list_free_full (add_list, gst_object_unref);
return result;
ret = klass->create_track_elements (clip, type);
for (tmp = ret; tmp; tmp = tmp->next)
ges_track_element_add_creator (tmp->data, clip);
return ret;
}
/*
@ -1605,7 +1728,7 @@ ges_clip_get_top_effects (GESClip * clip)
for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
child = tmp->data;
if (GES_IS_BASE_EFFECT (child) && !_IS_CORE_CHILD (child))
if (_IS_TOP_EFFECT (child))
ret = g_list_append (ret, gst_object_ref (child));
}
@ -1801,14 +1924,20 @@ ges_clip_split (GESClip * clip, guint64 position)
GESClip *new_object;
GstClockTime start, inpoint, duration, old_duration, new_duration;
gdouble media_duration_factor;
GESTimelineElement *element;
GESTimeline *timeline;
GHashTable *track_for_copy;
g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
g_return_val_if_fail (clip->priv->layer, NULL);
g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), NULL);
duration = _DURATION (clip);
start = _START (clip);
inpoint = _INPOINT (clip);
element = GES_TIMELINE_ELEMENT (clip);
timeline = element->timeline;
duration = element->duration;
start = element->start;
inpoint = element->inpoint;
if (position >= start + duration || position <= start) {
GST_WARNING_OBJECT (clip, "Can not split %" GST_TIME_FORMAT
@ -1817,9 +1946,9 @@ ges_clip_split (GESClip * clip, guint64 position)
}
old_duration = position - start;
if (!timeline_tree_can_move_element (timeline_get_tree
(GES_TIMELINE_ELEMENT_TIMELINE (clip)), GES_TIMELINE_ELEMENT (clip),
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip),
if (timeline && !timeline_tree_can_move_element (timeline_get_tree
(timeline), element,
ges_timeline_element_get_layer_priority (element),
start, old_duration, NULL)) {
GST_WARNING_OBJECT (clip,
"Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT
@ -1829,10 +1958,10 @@ ges_clip_split (GESClip * clip, guint64 position)
}
new_duration = duration + start - position;
if (!timeline_tree_can_move_element (timeline_get_tree
(GES_TIMELINE_ELEMENT_TIMELINE (clip)), GES_TIMELINE_ELEMENT (clip),
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), position, new_duration,
NULL)) {
if (timeline && !timeline_tree_can_move_element (timeline_get_tree
(timeline), element,
ges_timeline_element_get_layer_priority (element),
position, new_duration, NULL)) {
GST_WARNING_OBJECT (clip,
"Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT
" as timeline would end up in an illegal" " state.", GES_ARGS (clip),
@ -1844,64 +1973,60 @@ ges_clip_split (GESClip * clip, guint64 position)
GST_TIME_ARGS (position));
/* Create the new Clip */
new_object = GES_CLIP (ges_timeline_element_copy (GES_TIMELINE_ELEMENT (clip),
FALSE));
new_object = GES_CLIP (ges_timeline_element_copy (element, FALSE));
GST_DEBUG_OBJECT (new_object, "New 'splitted' clip");
/* Set new timing properties on the Clip */
media_duration_factor =
ges_timeline_element_get_media_duration_factor (GES_TIMELINE_ELEMENT
(clip));
ges_timeline_element_get_media_duration_factor (element);
_set_start0 (GES_TIMELINE_ELEMENT (new_object), position);
_set_inpoint0 (GES_TIMELINE_ELEMENT (new_object),
inpoint + old_duration * media_duration_factor);
_set_duration0 (GES_TIMELINE_ELEMENT (new_object), new_duration);
_DURATION (clip) = old_duration;
g_object_notify (G_OBJECT (clip), "duration");
/* We do not want the timeline to create again TrackElement-s */
ges_clip_set_moving_from_layer (new_object, TRUE);
/* adding to the same layer should not fail when moving */
ges_layer_add_clip (clip->priv->layer, new_object);
ges_clip_set_moving_from_layer (new_object, FALSE);
/* split binding before duration changes */
track_for_copy = g_hash_table_new_full (NULL, NULL,
gst_object_unref, gst_object_unref);
/* _add_child will add core elements at the lowest priority and new
* non-core effects at the lowest effect priority, so we need to add the
* highest priority children first to preserve the effect order. The
* clip's children are already ordered by highest priority first. */
for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
GESTrackElement *new_trackelement, *trackelement =
GES_TRACK_ELEMENT (tmp->data);
new_trackelement =
GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT
(trackelement), FALSE));
if (new_trackelement == NULL) {
GST_WARNING_OBJECT (trackelement, "Could not create a copy");
continue;
}
if (_IS_CORE_CHILD (trackelement))
ges_track_element_add_creator (new_trackelement, new_object);
/* FIXME: in-point for non-core track elements should be shifted by
* the split (adding them to the new clip will not set their in-point)
* Handle this once generic time effects are supported in splitting */
ges_container_add (GES_CONTAINER (new_object),
GES_TIMELINE_ELEMENT (new_trackelement));
ges_track_element_copy_properties (GES_TIMELINE_ELEMENT (trackelement),
GES_TIMELINE_ELEMENT (new_trackelement));
GESTrackElement *copy, *orig = tmp->data;
GESTrack *track = ges_track_element_get_track (orig);
/* FIXME: is position - start + inpoint always the correct splitting
* point for control bindings? What coordinate system are control
* bindings given in? */
/* NOTE: control bindings that are not registered in GES are not
* handled */
ges_track_element_copy_bindings (trackelement, new_trackelement,
copy = ges_clip_copy_track_element_into (new_object, orig,
position - start + inpoint);
if (copy && track)
g_hash_table_insert (track_for_copy, gst_object_ref (copy),
gst_object_ref (track));
}
/* FIXME: The below leads to a *second* notify signal for duration */
ELEMENT_SET_FLAG (clip, GES_TIMELINE_ELEMENT_SET_SIMPLE);
_DURATION (clip) = duration;
_set_duration0 (GES_TIMELINE_ELEMENT (clip), old_duration);
ELEMENT_UNSET_FLAG (clip, GES_TIMELINE_ELEMENT_SET_SIMPLE);
g_object_notify (G_OBJECT (clip), "duration");
/* add to the track after the duration change so we don't overlap! */
for (tmp = GES_CONTAINER_CHILDREN (new_object); tmp; tmp = tmp->next) {
GESTrackElement *copy = tmp->data;
GESTrack *track = g_hash_table_lookup (track_for_copy, copy);
if (track) {
new_object->priv->allow_any_track = TRUE;
ges_track_add_element (track, copy);
new_object->priv->allow_any_track = FALSE;
}
}
g_hash_table_unref (track_for_copy);
return new_object;
}
@ -2133,3 +2258,117 @@ ges_clip_get_timeline_time_from_source_frame (GESClip * clip,
return GST_CLOCK_DIFF (inpoint_diff, _START (clip));
}
/**
* ges_clip_add_child_to_track:
* @clip: A #GESClip
* @child: A child of @clip
* @track: The track to add @child to
* @err: Return location for an error
*
* Adds the track element child of the clip to a specific track.
*
* If the given child is already in another track, this will create a copy
* of the child, add it to the clip, and add this copy to the track.
*
* You should only call this whilst a clip is part of a #GESTimeline, and
* for tracks that are in the same timeline.
*
* This method is an alternative to using the
* #GESTimeline::select-tracks-for-object signal, but can be used to
* complement it when, say, you wish to copy a clip's children from one
* track into a new one.
*
* When the child is a core child, it must be added to a track that does
* not already contain another core child of the same clip. If it is not a
* core child (an additional effect), then it must be added to a track
* that already contains one of the core children of the same clip.
*
* This method can also fail if the adding the track element to the track
* would break a configuration rule of the corresponding #GESTimeline,
* such as causing three sources to overlap at a single time, or causing
* a source to completely overlap another in the same track.
*
* Note that @err will not always be set upon failure.
*
* Returns: (transfer none): The element that was added to @track, either
* @child or a copy of child, or %NULL if the element could not be added.
*/
GESTrackElement *
ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child,
GESTrack * track, GError ** err)
{
GESTimeline *timeline;
GESTrackElement *el;
GESTrack *current_track;
g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
g_return_val_if_fail (GES_IS_TRACK_ELEMENT (child), NULL);
g_return_val_if_fail (GES_IS_TRACK (track), NULL);
g_return_val_if_fail (!err || !*err, NULL);
timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
if (!g_list_find (GES_CONTAINER_CHILDREN (clip), child)) {
GST_WARNING_OBJECT (clip, "The track element %" GES_FORMAT " is not "
"a child of the clip", GES_ARGS (child));
return NULL;
}
if (!timeline) {
GST_WARNING_OBJECT (clip, "Cannot add children to tracks unless "
"the clip is part of a timeline");
return NULL;
}
if (timeline != ges_track_get_timeline (track)) {
GST_WARNING_OBJECT (clip, "Cannot add the children to the track %"
GST_PTR_FORMAT " because its timeline is %" GST_PTR_FORMAT
" rather than that of the clip %" GST_PTR_FORMAT,
track, ges_track_get_timeline (track), timeline);
return NULL;
}
current_track = ges_track_element_get_track (child);
if (current_track == track) {
GST_WARNING_OBJECT (clip, "Child %s" GES_FORMAT " is already in the "
"track %" GST_PTR_FORMAT, GES_ARGS (child), track);
return NULL;
}
/* copy if the element is already in a track */
if (current_track) {
el = ges_clip_copy_track_element_into (clip, child, GST_CLOCK_TIME_NONE);
if (!el) {
GST_ERROR_OBJECT (clip, "Could not add a copy of the track element %"
GES_FORMAT " to the clip so cannot add it to the track %"
GST_PTR_FORMAT, GES_ARGS (child), track);
return NULL;
}
if (_IS_TOP_EFFECT (child)) {
/* add at next lowest priority */
ges_clip_set_top_effect_index (clip, GES_BASE_EFFECT (el),
ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (child)) + 1);
}
} else {
el = child;
}
/* FIXME: set error if can not be added to track:
* Either breaks the track rules for the clip, or the timeline
* configuration rules */
if (!ges_track_add_element (track, el)) {
GST_WARNING_OBJECT (clip, "Could not add the track element %"
GES_FORMAT " to the track %" GST_PTR_FORMAT, GES_ARGS (el), track);
if (el != child)
ges_container_remove (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (el));
return NULL;
}
if (GES_IS_SOURCE (el))
timeline_tree_create_transitions (timeline_get_tree (timeline),
ges_timeline_find_auto_transition);
return el;
}

View file

@ -155,6 +155,9 @@ GES_API
GList * ges_clip_find_track_elements (GESClip * clip, GESTrack * track,
GESTrackType track_type, GType type);
GES_API
GESTrackElement * ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child, GESTrack * track, GError **err);
/****************************************************
* Layer *
****************************************************/

View file

@ -83,6 +83,9 @@ GstDebugCategory * _ges_debug (void);
#define GES_FORMAT GES_TIMELINE_ELEMENT_FORMAT
#define GES_ARGS GES_TIMELINE_ELEMENT_ARGS
#define GES_TRACK_ELEMENT_IS_CORE(child) \
(ges_track_element_get_creators (GES_TRACK_ELEMENT (child)) != NULL)
#define SUPRESS_UNUSED_WARNING(a) (void)a
G_GNUC_INTERNAL gboolean
@ -156,6 +159,14 @@ timeline_create_transitions (GESTimeline * timeline, GESTrackElement * track_ele
G_GNUC_INTERNAL void timeline_get_framerate(GESTimeline *self, gint *fps_n,
gint *fps_d);
G_GNUC_INTERNAL void
ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving);
G_GNUC_INTERNAL gboolean
ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip);
G_GNUC_INTERNAL void
ges_timeline_remove_clip (GESTimeline * timeline, GESClip * clip);
G_GNUC_INTERNAL
void
@ -406,6 +417,8 @@ G_GNUC_INTERNAL void ges_clip_set_moving_from_layer (GESClip *clip
G_GNUC_INTERNAL GESTrackElement* ges_clip_create_track_element (GESClip *clip, GESTrackType type);
G_GNUC_INTERNAL GList* ges_clip_create_track_elements (GESClip *clip, GESTrackType type);
G_GNUC_INTERNAL gboolean ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTimelineElement * child, GstClockTime inpoint);
G_GNUC_INTERNAL gboolean ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, GESTrack * tack);
G_GNUC_INTERNAL void ges_clip_empty_from_track (GESClip * clip, GESTrack * track);
/****************************************************
* GESLayer *

View file

@ -482,6 +482,7 @@ ges_layer_remove_clip_internal (GESLayer * layer, GESClip * clip,
{
GESLayer *current_layer;
GList *tmp;
GESTimeline *timeline = layer->timeline;
GST_DEBUG ("layer:%p, clip:%p", layer, clip);
@ -507,8 +508,10 @@ ges_layer_remove_clip_internal (GESLayer * layer, GESClip * clip,
/* inform the clip it's no longer in a layer */
ges_clip_set_layer (clip, NULL);
/* so neither in a timeline */
if (layer->timeline)
if (timeline) {
ges_timeline_remove_clip (timeline, clip);
ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip), NULL);
}
for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next)
ges_track_element_set_layer_active (tmp->data, TRUE);
@ -766,25 +769,17 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip)
ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip),
layer->timeline);
/* emit 'clip-added' */
/* FIXME: we are emitting the 'clip-added' signal even though we still
* might fail. This is because the timeline uses this signal to create
* the auto-transitions etc needed for timeline_tree_can_move_element
* below, which checks whether the added clip is in a legal position.
* However, we should have a way to check that adding a clip will be
* legal **before** we actually add it!
* A user connecting to 'clip-added' in such a case would receive a
* signal saying that the clip was added, but a return value that says
* something else! */
/* FIXME: ideally we would only emit if we are going to return TRUE.
* However, for backward-compatibility, we ensure the "clip-added"
* signal is released before the clip's "child-added" signal, which is
* invoked by ges_timeline_add_clip */
g_signal_emit (layer, ges_layer_signals[OBJECT_ADDED], 0, clip);
if (!ELEMENT_FLAG_IS_SET (clip, GES_CLIP_IS_MOVING) && layer->timeline
&& !timeline_tree_can_move_element (timeline_get_tree (layer->timeline),
GES_TIMELINE_ELEMENT (clip),
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip),
GES_TIMELINE_ELEMENT_START (clip),
GES_TIMELINE_ELEMENT_DURATION (clip), NULL)) {
GST_INFO_OBJECT (layer, "Clip %" GES_FORMAT, GES_ARGS (clip));
if (layer->timeline && !ges_timeline_add_clip (layer->timeline, clip)) {
GST_WARNING_OBJECT (layer, "Could not add the clip %" GES_FORMAT
" to the timeline %" GST_PTR_FORMAT, GES_ARGS (clip), layer->timeline);
/* FIXME: change emit signal to FALSE once we are able to delay the
* "clip-added" signal until after ges_timeline_add_clip */
ges_layer_remove_clip_internal (layer, clip, TRUE);
return FALSE;
}

View file

@ -409,7 +409,8 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass)
*/
properties[PROP_TIMELINE] =
g_param_spec_object ("timeline", "Timeline",
"The timeline the object is in", GES_TYPE_TIMELINE, G_PARAM_READWRITE);
"The timeline the object is in", GES_TYPE_TIMELINE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GESTimelineElement:start:
@ -980,6 +981,9 @@ ges_timeline_element_set_timeline (GESTimelineElement * self,
GST_DEBUG_OBJECT (self, "set timeline to %" GST_PTR_FORMAT, timeline);
if (self->timeline == timeline)
return TRUE;
if (timeline != NULL && G_UNLIKELY (self->timeline != NULL))
goto had_timeline;

View file

@ -152,7 +152,8 @@ struct _GESTimelinePrivate
/* While we are creating and adding the TrackElements for a clip, we need to
* ignore the child-added signal */
GESClip *ignore_track_element_added;
gboolean track_elements_moving;
gboolean track_selection_error;
GList *groups;
guint stream_start_group_id;
@ -702,8 +703,12 @@ ges_timeline_class_init (GESTimelineClass * klass)
* @clip: The clip that @track_element is being added to
* @track_element: The element being added
*
* This will be emitted whenever a new track element is added to a
* clip within the timeline.
* This will be emitted whenever the timeline needs to determine which
* tracks a clip's children should be added to. The track element will
* be added to each of the tracks given in the return. If a track
* element is selected to go into multiple tracks, it will be copied
* into the additional tracks, under the same clip. Note that the copy
* will *not* keep its properties or state in sync with the original.
*
* Connect to this signal once if you wish to control which element
* should be added to which track. Doing so will overwrite the default
@ -711,16 +716,48 @@ ges_timeline_class_init (GESTimelineClass * klass)
* #GESTrack:track-type includes the @track_element's
* #GESTrackElement:track-type.
*
* If you wish to use this, you should add all necessary tracks to the
* timeline before adding any clips. In particular, this signal is
* **not** re-emitted for the existing clips when a new track is added
* to the timeline.
* Note that under the default track selection, if a clip would produce
* multiple core children of the same #GESTrackType, it will choose
* one of the core children arbitrarily to place in the corresponding
* tracks, with a warning for the other core children that are not
* placed in the track. For example, this would happen for a #GESUriClip
* that points to a file that contains multiple audio streams. If you
* wish to choose the stream, you could connect to this signal, and use,
* say, ges_uri_source_asset_get_stream_info() to choose which core
* source to add.
*
* When a clip is first added to a timeline, its core elements will
* be created for the current tracks in the timeline if they have not
* already been created. Then this will be emitted for each of these
* core children to select which tracks, if any, they should be added
* to. It will then be called for any non-core children in the clip.
*
* In addition, if a new track element is ever added to a clip in a
* timeline (and it is not already part of a track) this will be emitted
* to select which tracks the element should be added to.
*
* Finally, as a special case, if a track is added to the timeline
* *after* it already contains clips, then it will request the creation
* of the clips' core elements of the corresponding type, if they have
* not already been created, and this signal will be emitted for each of
* these newly created elements. In addition, this will also be released
* for all other track elements in the timeline's clips that have not
* yet been assigned a track. However, in this final case, the timeline
* will only check whether the newly added track appears in the track
* list. If it does appear, the track element will be added to the newly
* added track. All other tracks in the returned track list are ignored.
*
* In this latter case, track elements that are already part of a track
* will not be asked if they want to be copied into the new track. If
* you wish to do this, you can use ges_clip_add_child_to_track().
*
* Note that the returned #GPtrArray should own a new reference to each
* of its contained #GESTrack. The timeline will set the #GDestroyNotify
* free function on the #GPtrArray to dereference the elements.
*
* Returns: (transfer full) (element-type GESTrack): An array of
* #GESTrack-s that @track_element should be added to. If this contains
* more than one track, a copy of @track_element will be added to the
* other tracks. If this is empty, @track_element will also be removed
* from @clip.
* #GESTrack-s that @track_element should be added to, or %NULL to
* not add the element to any track.
*/
ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT] =
g_signal_new ("select-tracks-for-object", G_TYPE_FROM_CLASS (klass),
@ -1297,12 +1334,26 @@ timeline_remove_group (GESTimeline * timeline, GESGroup * group)
gst_object_unref (group);
}
static GESTrackElement *
_core_in_track (GESTrack * track, GESClip * clip)
{
GList *tmp;
for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
if (GES_TRACK_ELEMENT_IS_CORE (tmp->data)
&& ges_track_element_get_track (tmp->data) == track) {
return tmp->data;
}
}
return NULL;
}
static GPtrArray *
select_tracks_for_object_default (GESTimeline * timeline,
GESClip * clip, GESTrackElement * tr_object, gpointer user_data)
{
GPtrArray *result;
GList *tmp;
GESTrackElement *core;
result = g_ptr_array_new ();
@ -1311,6 +1362,24 @@ select_tracks_for_object_default (GESTimeline * timeline,
GESTrack *track = GES_TRACK (tmp->data);
if ((track->type & ges_track_element_get_track_type (tr_object))) {
if (GES_TRACK_ELEMENT_IS_CORE (tr_object)) {
core = _core_in_track (track, clip);
if (core) {
GST_WARNING_OBJECT (timeline, "The clip '%s' contains multiple "
"core elements of the same %s track type. The core child "
"'%s' has already been chosen arbitrarily for the track %"
GST_PTR_FORMAT ", which means that the other core child "
"'%s' of the same type can not be added to the track. "
"Consider connecting to "
"GESTimeline::select-tracks-for-objects to be able to "
"specify which core element should land in the track",
GES_TIMELINE_ELEMENT_NAME (clip),
ges_track_type_name (track->type),
GES_TIMELINE_ELEMENT_NAME (core), track,
GES_TIMELINE_ELEMENT_NAME (tr_object));
continue;
}
}
gst_object_ref (track);
g_ptr_array_add (result, track);
}
@ -1320,34 +1389,273 @@ select_tracks_for_object_default (GESTimeline * timeline,
return result;
}
static GPtrArray *
_get_selected_tracks (GESTimeline * timeline, GESClip * clip,
GESTrackElement * track_element)
{
guint i, j;
GPtrArray *tracks = NULL;
g_signal_emit (G_OBJECT (timeline),
ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT], 0, clip, track_element,
&tracks);
if (tracks == NULL)
tracks = g_ptr_array_new ();
g_ptr_array_set_free_func (tracks, gst_object_unref);
/* make sure unique */
for (i = 0; i < tracks->len;) {
GESTrack *track = GES_TRACK (g_ptr_array_index (tracks, i));
for (j = i + 1; j < tracks->len;) {
if (track == g_ptr_array_index (tracks, j)) {
GST_WARNING_OBJECT (timeline, "Found the track %" GST_PTR_FORMAT
" more than once in the return for select-tracks-for-object "
"signal for track element %" GES_FORMAT " in clip %"
GES_FORMAT ". Ignoring the extra track", track,
GES_ARGS (track_element), GES_ARGS (clip));
g_ptr_array_remove_index (tracks, j);
/* don't increase index since the next track is in its place */
continue;
}
j++;
}
if (ges_track_get_timeline (track) != timeline) {
GST_WARNING_OBJECT (timeline, "The track %" GST_PTR_FORMAT
" found in the return for select-tracks-for-object belongs "
"to a different timeline %" GST_PTR_FORMAT ". Ignoring this "
"track", track, ges_track_get_timeline (track));
g_ptr_array_remove_index (tracks, i);
/* don't increase index since the next track is in its place */
continue;
}
i++;
}
return tracks;
}
/* returns TRUE if track element was successfully added to all the
* selected tracks */
static gboolean
_add_track_element_to_tracks (GESTimeline * timeline, GESClip * clip,
GESTrackElement * track_element)
{
guint i;
gboolean ret = TRUE;
GPtrArray *tracks = _get_selected_tracks (timeline, clip, track_element);
for (i = 0; i < tracks->len; i++) {
GESTrack *track = GES_TRACK (g_ptr_array_index (tracks, i));
if (!ges_clip_add_child_to_track (clip, track_element, track, NULL))
ret = FALSE;
}
g_ptr_array_unref (tracks);
return ret;
}
static gboolean
_try_add_track_element_to_track (GESTimeline * timeline, GESClip * clip,
GESTrackElement * track_element, GESTrack * track)
{
gboolean no_error = TRUE;
GPtrArray *tracks = _get_selected_tracks (timeline, clip, track_element);
/* if we are trying to add the element to a newly added track, then
* we only check whether the track list contains the newly added track,
* if it does we add the track element to the track, or add a copy if
* the track element is already in a track */
if (g_ptr_array_find (tracks, track, NULL)) {
if (!ges_clip_add_child_to_track (clip, track_element, track, NULL))
no_error = FALSE;
}
g_ptr_array_unref (tracks);
return no_error;
}
/* accepts NULL */
void
ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving)
{
if (timeline) {
LOCK_DYN (timeline);
timeline->priv->track_elements_moving = moving;
UNLOCK_DYN (timeline);
}
}
static void
_set_track_selection_error (GESTimeline * timeline, gboolean error)
{
LOCK_DYN (timeline);
timeline->priv->track_selection_error = error;
UNLOCK_DYN (timeline);
}
static gboolean
_get_track_selection_error (GESTimeline * timeline)
{
gboolean ret;
LOCK_DYN (timeline);
ret = timeline->priv->track_selection_error;
timeline->priv->track_selection_error = FALSE;
UNLOCK_DYN (timeline);
return ret;
}
static void
clip_track_element_added_cb (GESClip * clip,
GESTrackElement * track_element, GESTimeline * timeline)
{
gboolean error = FALSE;
if (timeline->priv->track_elements_moving) {
GST_DEBUG_OBJECT (timeline, "Ignoring element added: %" GES_FORMAT
" in %" GES_FORMAT, GES_ARGS (track_element), GES_ARGS (clip));
return;
}
if (ges_track_element_get_track (track_element) != NULL) {
GST_DEBUG_OBJECT (timeline, "Not selecting tracks for %" GES_FORMAT
" in %" GES_FORMAT " because it already part of the track %"
GST_PTR_FORMAT, GES_ARGS (track_element), GES_ARGS (clip),
ges_track_element_get_track (track_element));
return;
}
if (!_add_track_element_to_tracks (timeline, clip, track_element))
error = TRUE;
if (error)
_set_track_selection_error (timeline, TRUE);
}
static void
clip_track_element_removed_cb (GESClip * clip,
GESTrackElement * track_element, GESTimeline * timeline)
{
GESTrack *track = ges_track_element_get_track (track_element);
if (timeline->priv->track_elements_moving) {
GST_DEBUG_OBJECT (timeline, "Ignoring element removed (%" GST_PTR_FORMAT
" in %" GST_PTR_FORMAT, track_element, clip);
return;
}
if (track) {
/* if we have non-core elements in the same track, they should be
* removed from them to preserve the rule that a non-core can only be
* in the same track as a core element from the same clip */
if (GES_TRACK_ELEMENT_IS_CORE (track_element))
ges_clip_empty_from_track (clip, track);
ges_track_remove_element (track, track_element);
}
}
/* returns TRUE if no errors in adding to tracks */
static gboolean
_add_clip_children_to_tracks (GESTimeline * timeline, GESClip * clip,
gboolean add_core, GESTrack * new_track, GList * blacklist)
{
GList *tmp, *children;
gboolean no_errors = TRUE;
/* list of children may change if some are copied into tracks */
children = ges_container_get_children (GES_CONTAINER (clip), FALSE);
for (tmp = children; tmp; tmp = tmp->next) {
GESTrackElement *el = tmp->data;
if (GES_TRACK_ELEMENT_IS_CORE (el) != add_core)
continue;
if (g_list_find (blacklist, el))
continue;
if (ges_track_element_get_track (el) == NULL) {
gboolean res;
if (new_track)
res = _try_add_track_element_to_track (timeline, clip, el, new_track);
else
res = _add_track_element_to_tracks (timeline, clip, el);
if (!res)
no_errors = FALSE;
}
}
g_list_free_full (children, gst_object_unref);
return no_errors;
}
/* returns TRUE if no errors in adding to tracks */
static gboolean
add_object_to_tracks (GESTimeline * timeline, GESClip * clip, GESTrack * track)
{
gint i;
GList *tmp, *list;
GESTrackType types, visited_type = GES_TRACK_TYPE_UNKNOWN;
GList *tracks, *tmp, *list, *created, *just_added = NULL;
gboolean no_errors = TRUE;
/* TODO: extend with GError ** argument, which is accepted by
* ges_clip_add_child_to_track */
GST_DEBUG_OBJECT (timeline, "Creating %" GST_PTR_FORMAT
" trackelements and adding them to our tracks", clip);
types = ges_clip_get_supported_formats (clip);
if (track) {
if ((types & track->type) == 0)
return;
types = track->type;
}
LOCK_DYN (timeline);
for (i = 0, tmp = timeline->tracks; tmp; tmp = tmp->next, i++) {
GESTrack *track = GES_TRACK (tmp->data);
/* FIXME: visited_type is essentially unused */
if (((track->type & types) == 0 || (track->type & visited_type)))
continue;
list = ges_clip_create_track_elements (clip, track->type);
g_list_free (list);
}
tracks =
g_list_copy_deep (timeline->tracks, (GCopyFunc) gst_object_ref, NULL);
UNLOCK_DYN (timeline);
/* create core elements */
for (tmp = tracks; tmp; tmp = tmp->next) {
GESTrack *track = GES_TRACK (tmp->data);
list = ges_clip_create_track_elements (clip, track->type);
for (created = list; created; created = created->next) {
GESTimelineElement *el = created->data;
gst_object_ref (el);
/* make track selection be handled by clip_track_element_added_cb
* This is needed for backward-compatibility: when adding a clip to
* a layer, the track is set for the core elements of the clip
* during the child-added signal emission, just before the user's
* own connection.
* NOTE: for the children that have not just been created, they
* are already part of the clip and so child-added will not be
* released. And when a child is selected for multiple tracks, their
* copy will be added to the clip before the track is selected, so
* the track will not be set in the child-added signal */
_set_track_selection_error (timeline, FALSE);
if (!ges_container_add (GES_CONTAINER (clip), el))
GST_ERROR_OBJECT (clip, "Could not add the core element %s "
"to the clip", el->name);
if (_get_track_selection_error (timeline))
no_errors = FALSE;
gst_object_unref (el);
}
/* just_added only used for pointer comparison, so safe to include
* elements that are now destroyed because they failed to be added to
* the clip */
just_added = g_list_concat (just_added, list);
}
g_list_free_full (tracks, gst_object_unref);
/* set the tracks for the other children, with core elements first to
* make sure the non-core can be placed above them in the track (a
* non-core can not be in a track by itself) */
/* include just_added as a blacklist to ensure we do not try the track
* selection a second time when track selection returns no tracks */
if (!_add_clip_children_to_tracks (timeline, clip, TRUE, track, just_added))
no_errors = FALSE;
if (!_add_clip_children_to_tracks (timeline, clip, FALSE, track, just_added))
no_errors = FALSE;
g_list_free (just_added);
return no_errors;
}
static void
@ -1392,160 +1700,14 @@ layer_auto_transition_changed_cb (GESLayer * layer,
g_list_free_full (clips, gst_object_unref);
}
static void
clip_track_element_added_cb (GESClip * clip,
GESTrackElement * track_element, GESTimeline * timeline)
{
guint i;
GESTrack *track;
gboolean is_source;
GPtrArray *tracks = NULL;
GESTrackElement *existing_src = NULL;
if (timeline->priv->ignore_track_element_added == clip) {
GST_DEBUG_OBJECT (timeline, "Ignoring element added (%" GST_PTR_FORMAT
" in %" GST_PTR_FORMAT, track_element, clip);
return;
}
if (ges_track_element_get_track (track_element)) {
GST_WARNING_OBJECT (track_element, "Already in a track");
return;
}
g_signal_emit (G_OBJECT (timeline),
ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT], 0, clip, track_element,
&tracks);
/* FIXME: make sure each track in the list is unique */
/* FIXME: make sure each track is part of the same timeline as the clip,
* and warn and ignore the track if it isn't */
if (!tracks || tracks->len == 0) {
GST_WARNING_OBJECT (timeline, "Got no Track to add %p (type %s), removing"
" from clip (stopping 'child-added' signal emission).",
track_element, ges_track_type_name (ges_track_element_get_track_type
(track_element)));
if (tracks)
g_ptr_array_unref (tracks);
g_signal_stop_emission_by_name (clip, "child-added");
ges_container_remove (GES_CONTAINER (clip),
GES_TIMELINE_ELEMENT (track_element));
return;
}
/* We add the current element to the first track */
track = g_ptr_array_index (tracks, 0);
is_source = g_type_is_a (G_OBJECT_TYPE (track_element), GES_TYPE_SOURCE);
if (is_source)
existing_src = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE);
if (existing_src == NULL) {
if (!ges_track_add_element (track, track_element)) {
GST_WARNING_OBJECT (clip, "Failed to add track element to track");
ges_container_remove (GES_CONTAINER (clip),
GES_TIMELINE_ELEMENT (track_element));
/* FIXME: unref all the individual track in tracks */
g_ptr_array_unref (tracks);
return;
}
} else {
GST_INFO_OBJECT (clip, "Already had a Source Element in %" GST_PTR_FORMAT
" of type %s, removing new one. (stopping 'child-added' emission)",
track, G_OBJECT_TYPE_NAME (track_element));
g_signal_stop_emission_by_name (clip, "child-added");
ges_container_remove (GES_CONTAINER (clip),
GES_TIMELINE_ELEMENT (track_element));
}
gst_object_unref (track);
g_clear_object (&existing_src);
/* And create copies to add to other tracks */
timeline->priv->ignore_track_element_added = clip;
for (i = 1; i < tracks->len; i++) {
GESTrack *track;
GESTrackElement *track_element_copy;
track = g_ptr_array_index (tracks, i);
if (is_source)
existing_src = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE);
if (existing_src == NULL) {
ges_container_remove (GES_CONTAINER (clip),
GES_TIMELINE_ELEMENT (track_element));
gst_object_unref (track);
/* FIXME: tracks is needed for the next loop after continue */
g_ptr_array_unref (tracks);
continue;
} else {
GST_INFO_OBJECT (clip, "Already had a Source Element in %" GST_PTR_FORMAT
" of type %s, removing new one. (stopping 'child-added' emission)",
track, G_OBJECT_TYPE_NAME (track_element));
g_signal_stop_emission_by_name (clip, "child-added");
ges_container_remove (GES_CONTAINER (clip),
GES_TIMELINE_ELEMENT (track_element));
}
/* FIXME: in both cases track_element is removed from the clip! */
g_clear_object (&existing_src);
track_element_copy =
GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT
(track_element), TRUE));
/* if a core child, mark the copy as core so it can be added */
if (ges_track_element_get_creators (track_element))
ges_track_element_add_creator (track_element_copy, clip);
GST_LOG_OBJECT (timeline, "Trying to add %p to track %p",
track_element_copy, track);
if (!ges_container_add (GES_CONTAINER (clip),
GES_TIMELINE_ELEMENT (track_element_copy))) {
GST_WARNING_OBJECT (clip, "Failed to add track element to clip");
gst_object_unref (track_element_copy);
/* FIXME: unref **all** the individual track in tracks */
g_ptr_array_unref (tracks);
return;
}
if (!ges_track_add_element (track, track_element_copy)) {
GST_WARNING_OBJECT (clip, "Failed to add track element to track");
ges_container_remove (GES_CONTAINER (clip),
GES_TIMELINE_ELEMENT (track_element_copy));
/* FIXME: should we also stop the child-added and child-removed
* emissions? */
gst_object_unref (track_element_copy);
/* FIXME: unref **all** the individual track in tracks */
g_ptr_array_unref (tracks);
return;
}
gst_object_unref (track);
}
timeline->priv->ignore_track_element_added = NULL;
g_ptr_array_unref (tracks);
if (GES_IS_SOURCE (track_element))
timeline_tree_create_transitions (timeline->priv->tree,
ges_timeline_find_auto_transition);
}
static void
clip_track_element_removed_cb (GESClip * clip,
GESTrackElement * track_element, GESTimeline * timeline)
{
GESTrack *track = ges_track_element_get_track (track_element);
if (track)
ges_track_remove_element (track, track_element);
}
static void
layer_object_added_cb (GESLayer * layer, GESClip * clip, GESTimeline * timeline)
/* returns TRUE if selecting of tracks did not error */
gboolean
ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip)
{
GESProject *project;
gboolean ret;
/* TODO: extend with GError ** argument, which is accepted by
* ges_clip_add_child_to_track */
/* We make sure not to be connected twice */
g_signal_handlers_disconnect_by_func (clip, clip_track_element_added_cb,
@ -1564,11 +1726,11 @@ layer_object_added_cb (GESLayer * layer, GESClip * clip, GESTimeline * timeline)
"TrackElement", clip);
timeline_tree_create_transitions (timeline->priv->tree,
ges_timeline_find_auto_transition);
return;
ret = TRUE;
} else {
ret = add_object_to_tracks (timeline, clip, NULL);
}
add_object_to_tracks (timeline, clip, NULL);
GST_DEBUG ("Making sure that the asset is in our project");
project =
GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline)));
@ -1576,6 +1738,8 @@ layer_object_added_cb (GESLayer * layer, GESClip * clip, GESTimeline * timeline)
ges_extractable_get_asset (GES_EXTRACTABLE (clip)));
GST_DEBUG ("Done");
return ret;
}
static void
@ -1589,11 +1753,10 @@ layer_priority_changed_cb (GESLayer * layer,
sort_layers);
}
static void
layer_object_removed_cb (GESLayer * layer, GESClip * clip,
GESTimeline * timeline)
void
ges_timeline_remove_clip (GESTimeline * timeline, GESClip * clip)
{
GList *trackelements, *tmp;
GList *tmp;
if (ges_clip_is_moving_from_layer (clip)) {
GST_DEBUG ("Clip %p is moving from a layer to another, not doing"
@ -1601,41 +1764,19 @@ layer_object_removed_cb (GESLayer * layer, GESClip * clip,
return;
}
GST_DEBUG_OBJECT (timeline, "Clip %" GES_FORMAT " removed from layer %p",
GES_ARGS (clip), layer);
GST_DEBUG_OBJECT (timeline, "Clip %" GES_FORMAT " removed from layer",
GES_ARGS (clip));
/* Go over the clip's track element and figure out which one belongs to
* the list of tracks we control */
LOCK_DYN (timeline);
for (tmp = timeline->tracks; tmp; tmp = tmp->next)
ges_clip_empty_from_track (clip, tmp->data);
UNLOCK_DYN (timeline);
trackelements = ges_container_get_children (GES_CONTAINER (clip), FALSE);
for (tmp = trackelements; tmp; tmp = tmp->next) {
GESTrackElement *track_element = (GESTrackElement *) tmp->data;
GESTrack *track = ges_track_element_get_track (track_element);
if (!track)
continue;
GST_DEBUG_OBJECT (timeline, "Trying to remove TrackElement %p",
track_element);
/* FIXME Check if we should actually check that we control the
* track in the new management of TrackElement context */
LOCK_DYN (timeline);
if (G_LIKELY (g_list_find_custom (timeline->priv->priv_tracks, track,
(GCompareFunc) custom_find_track) || track == NULL)) {
GST_DEBUG ("Belongs to one of the tracks we control");
ges_track_remove_element (track, track_element);
}
UNLOCK_DYN (timeline);
}
g_signal_handlers_disconnect_by_func (clip, clip_track_element_added_cb,
timeline);
g_signal_handlers_disconnect_by_func (clip, clip_track_element_removed_cb,
timeline);
g_list_free_full (trackelements, gst_object_unref);
GST_DEBUG ("Done");
}
@ -1971,6 +2112,17 @@ ges_timeline_append_layer (GESTimeline * timeline)
*
* Add a layer to the timeline.
*
* If the layer contains #GESClip-s, then this may trigger the creation of
* their core track element children for the timeline's tracks, and the
* placement of the clip's children in the tracks of the timeline using
* #GESTimeline::select-tracks-for-object. Some errors may occur if this
* would break one of the configuration rules of the timeline in one of
* its tracks. In such cases, some track elements would fail to be added
* to their tracks, but this method would still return %TRUE. As such, it
* is advised that you only add clips to layers that already part of a
* timeline. In such situations, ges_layer_add_clip() is able to fail if
* adding the clip would cause such an error.
*
* Deprecated: 1.18: This method requires you to ensure the layer's
* #GESLayer:priority will be unique to the timeline. Use
* ges_timeline_append_layer() and ges_timeline_move_layer() instead.
@ -2025,10 +2177,6 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer)
ges_layer_set_timeline (layer, timeline);
/* Connect to 'clip-added'/'clip-removed' signal from the new layer */
g_signal_connect_after (layer, "clip-added",
G_CALLBACK (layer_object_added_cb), timeline);
g_signal_connect_after (layer, "clip-removed",
G_CALLBACK (layer_object_removed_cb), timeline);
g_signal_connect (layer, "notify::priority",
G_CALLBACK (layer_priority_changed_cb), timeline);
g_signal_connect (layer, "notify::auto-transition",
@ -2041,12 +2189,9 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer)
/* add any existing clips to the timeline */
objects = ges_layer_get_clips (layer);
for (tmp = objects; tmp; tmp = tmp->next) {
layer_object_added_cb (layer, tmp->data, timeline);
gst_object_unref (tmp->data);
tmp->data = NULL;
}
g_list_free (objects);
for (tmp = objects; tmp; tmp = tmp->next)
ges_timeline_add_clip (timeline, tmp->data);
g_list_free_full (objects, gst_object_unref);
return TRUE;
}
@ -2080,18 +2225,12 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESLayer * layer)
/* remove objects from any private data structures */
layer_objects = ges_layer_get_clips (layer);
for (tmp = layer_objects; tmp; tmp = tmp->next) {
layer_object_removed_cb (layer, GES_CLIP (tmp->data), timeline);
gst_object_unref (G_OBJECT (tmp->data));
tmp->data = NULL;
}
g_list_free (layer_objects);
for (tmp = layer_objects; tmp; tmp = tmp->next)
ges_timeline_remove_clip (timeline, tmp->data);
g_list_free_full (layer_objects, gst_object_unref);
/* Disconnect signals */
GST_DEBUG ("Disconnecting signal callbacks");
g_signal_handlers_disconnect_by_func (layer, layer_object_added_cb, timeline);
g_signal_handlers_disconnect_by_func (layer, layer_object_removed_cb,
timeline);
g_signal_handlers_disconnect_by_func (layer, layer_priority_changed_cb,
timeline);
g_signal_handlers_disconnect_by_func (layer,
@ -2115,8 +2254,16 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESLayer * layer)
* @timeline: The #GESTimeline
* @track: (transfer full): The track to add
*
* Add a track to the timeline. Existing #GESClip-s in the timeline will,
* where appropriate, add their controlled elements to the new track.
* Add a track to the timeline.
*
* If the timeline already contains clips, then this may trigger the
* creation of their core track element children for the track, and the
* placement of the clip's children in the track of the timeline using
* #GESTimeline::select-tracks-for-object. Some errors may occur if this
* would break one of the configuration rules for the timeline in the
* track. In such cases, some track elements would fail to be added to the
* track, but this method would still return %TRUE. As such, it is advised
* that you avoid adding tracks to timelines that already contain clips.
*
* Returns: %TRUE if @track was properly added.
*/
@ -2184,13 +2331,10 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
GList *objects, *obj;
objects = ges_layer_get_clips (tmp->data);
for (obj = objects; obj; obj = obj->next) {
GESClip *clip = obj->data;
for (obj = objects; obj; obj = obj->next)
add_object_to_tracks (timeline, obj->data, track);
add_object_to_tracks (timeline, clip, track);
gst_object_unref (clip);
}
g_list_free (objects);
g_list_free_full (objects, gst_object_unref);
}
/* FIXME Check if we should rollback if we can't sync state */
@ -2240,8 +2384,22 @@ ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track)
gst_object_unref (tr_priv->pad);
priv->priv_tracks = g_list_remove (priv->priv_tracks, tr_priv);
UNLOCK_DYN (timeline);
timeline->tracks = g_list_remove (timeline->tracks, track);
/* empty track of all elements that belong to the timeline's clips */
/* elements with no parent can stay in the track, but their timeline
* will be set to NULL when the track's timeline is set to NULL */
for (tmp = timeline->layers; tmp; tmp = tmp->next) {
GList *clips, *clip;
clips = ges_layer_get_clips (tmp->data);
for (clip = clips; clip; clip = clip->next)
ges_clip_empty_from_track (clip->data, track);
g_list_free_full (clips, gst_object_unref);
}
timeline->tracks = g_list_remove (timeline->tracks, track);
ges_track_set_timeline (track, NULL);
/* Remove ghost pad */

View file

@ -1049,12 +1049,20 @@ ges_track_element_add_children_props (GESTrackElement * self,
gboolean
ges_track_element_set_track (GESTrackElement * object, GESTrack * track)
{
gboolean ret = TRUE;
GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (object);
g_return_val_if_fail (object->priv->nleobject, FALSE);
GST_DEBUG_OBJECT (object, "new track: %" GST_PTR_FORMAT, track);
if (GES_IS_CLIP (parent)
&& !ges_clip_can_set_track_of_child (GES_CLIP (parent), object, track)) {
GST_WARNING_OBJECT (object, "The parent clip %" GES_FORMAT " would "
"not allow the track to be set to %" GST_PTR_FORMAT,
GES_ARGS (parent), track);
return FALSE;
}
object->priv->track = track;
if (object->priv->track) {
@ -1065,7 +1073,7 @@ ges_track_element_set_track (GESTrackElement * object, GESTrack * track)
}
g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_TRACK]);
return ret;
return TRUE;
}
void

View file

@ -382,7 +382,8 @@ ges_track_get_composition (GESTrack * track)
* accessing it
*/
static gboolean
remove_object_internal (GESTrack * track, GESTrackElement * object)
remove_object_internal (GESTrack * track, GESTrackElement * object,
gboolean emit)
{
GESTrackPrivate *priv;
GstElement *nleobject;
@ -392,25 +393,30 @@ remove_object_internal (GESTrack * track, GESTrackElement * object)
priv = track->priv;
if (G_UNLIKELY (ges_track_element_get_track (object) != track)) {
GST_WARNING ("Object belongs to another track");
GST_WARNING_OBJECT (track, "Object belongs to another track");
return FALSE;
}
if (!ges_track_element_set_track (object, NULL)) {
GST_WARNING_OBJECT (track, "Failed to unset the track for %" GES_FORMAT,
GES_ARGS (object));
return FALSE;
}
ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL);
if ((nleobject = ges_track_element_get_nleobject (object))) {
GST_DEBUG ("Removing NleObject '%s' from composition '%s'",
GST_ELEMENT_NAME (nleobject), GST_ELEMENT_NAME (priv->composition));
if (!ges_nle_composition_remove_object (priv->composition, nleobject)) {
GST_WARNING ("Failed to remove nleobject from composition");
GST_WARNING_OBJECT (track, "Failed to remove nleobject from composition");
return FALSE;
}
}
ges_track_element_set_track (object, NULL);
ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL);
g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_REMOVED], 0,
GES_TRACK_ELEMENT (object));
if (emit)
g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_REMOVED], 0,
GES_TRACK_ELEMENT (object));
gst_object_unref (object);
@ -420,7 +426,7 @@ remove_object_internal (GESTrack * track, GESTrackElement * object)
static void
dispose_trackelements_foreach (GESTrackElement * trackelement, GESTrack * track)
{
remove_object_internal (track, trackelement);
remove_object_internal (track, trackelement, TRUE);
}
/* GstElement virtual methods */
@ -918,9 +924,19 @@ ges_track_new (GESTrackType type, GstCaps * caps)
void
ges_track_set_timeline (GESTrack * track, GESTimeline * timeline)
{
GSequenceIter *it;
g_return_if_fail (GES_IS_TRACK (track));
g_return_if_fail (timeline == NULL || GES_IS_TIMELINE (timeline));
GST_DEBUG ("track:%p, timeline:%p", track, timeline);
track->priv->timeline = timeline;
for (it = g_sequence_get_begin_iter (track->priv->trackelements_by_start);
g_sequence_iter_is_end (it) == FALSE; it = g_sequence_iter_next (it)) {
GESTimelineElement *trackelement =
GES_TIMELINE_ELEMENT (g_sequence_get (it));
ges_timeline_element_set_timeline (trackelement, timeline);
}
track_resort_and_fill_gaps (track);
}
@ -1089,6 +1105,31 @@ notify:
GST_DEBUG_OBJECT (track, "The track has been set to mixing = %d", mixing);
}
static gboolean
remove_element_internal (GESTrack * track, GESTrackElement * object,
gboolean emit)
{
GSequenceIter *it;
GESTrackPrivate *priv = track->priv;
GST_DEBUG_OBJECT (track, "Removing %" GST_PTR_FORMAT, object);
it = g_hash_table_lookup (priv->trackelements_iter, object);
g_sequence_remove (it);
if (remove_object_internal (track, object, emit) == TRUE) {
ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL);
return TRUE;
}
g_hash_table_insert (track->priv->trackelements_iter, object,
g_sequence_insert_sorted (track->priv->trackelements_by_start, object,
(GCompareDataFunc) element_start_compare, NULL));
return FALSE;
}
/**
* ges_track_add_element:
* @track: A #GESTrack
@ -1097,6 +1138,9 @@ notify:
* Adds the given track element to the track, which takes ownership of the
* element.
*
* Note that this can fail if it would break a configuration rule of the
* track's #GESTimeline.
*
* Note that a #GESTrackElement can only be added to one track.
*
* Returns: %TRUE if @object was successfully added to @track.
@ -1104,8 +1148,14 @@ notify:
gboolean
ges_track_add_element (GESTrack * track, GESTrackElement * object)
{
GESTimeline *timeline;
GESTimelineElement *el;
g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
el = GES_TIMELINE_ELEMENT (object);
CHECK_THREAD (track);
GST_DEBUG ("track:%p, object:%p", track, object);
@ -1117,12 +1167,14 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object)
return FALSE;
}
if (G_UNLIKELY (!ges_track_element_set_track (object, track))) {
GST_ERROR ("Couldn't properly add the object to the Track");
if (!ges_track_element_set_track (object, track)) {
GST_WARNING_OBJECT (track, "Failed to set the track for %" GES_FORMAT,
GES_ARGS (object));
gst_object_ref_sink (object);
gst_object_unref (object);
return FALSE;
}
ges_timeline_element_set_timeline (el, NULL);
GST_DEBUG ("Adding object %s to ourself %s",
GST_OBJECT_NAME (ges_track_element_get_nleobject (object)),
@ -1131,6 +1183,7 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object)
if (G_UNLIKELY (!ges_nle_composition_add_object (track->priv->composition,
ges_track_element_get_nleobject (object)))) {
GST_WARNING ("Couldn't add object to the NleComposition");
ges_track_element_set_track (object, NULL);
gst_object_ref_sink (object);
gst_object_unref (object);
return FALSE;
@ -1141,8 +1194,20 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object)
g_sequence_insert_sorted (track->priv->trackelements_by_start, object,
(GCompareDataFunc) element_start_compare, NULL));
ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object),
track->priv->timeline);
timeline = track->priv->timeline;
ges_timeline_element_set_timeline (el, timeline);
if (timeline
&& !timeline_tree_can_move_element (timeline_get_tree (timeline), el,
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (el), el->start, el->duration,
NULL)) {
GST_WARNING_OBJECT (track,
"Could not add the track element %" GES_FORMAT
" to the track because it breaks the timeline " "configuration rules",
GES_ARGS (el));
remove_element_internal (track, object, FALSE);
return FALSE;
}
g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_ADDED], 0,
GES_TRACK_ELEMENT (object));
@ -1188,31 +1253,12 @@ ges_track_get_elements (GESTrack * track)
gboolean
ges_track_remove_element (GESTrack * track, GESTrackElement * object)
{
GSequenceIter *it;
GESTrackPrivate *priv;
g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
CHECK_THREAD (track);
priv = track->priv;
GST_DEBUG_OBJECT (track, "Removing %" GST_PTR_FORMAT, object);
it = g_hash_table_lookup (priv->trackelements_iter, object);
g_sequence_remove (it);
if (remove_object_internal (track, object) == TRUE) {
ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL);
return TRUE;
}
g_hash_table_insert (track->priv->trackelements_iter, object,
g_sequence_insert_sorted (track->priv->trackelements_by_start, object,
(GCompareDataFunc) element_start_compare, NULL));
return FALSE;
return remove_element_internal (track, object, TRUE);
}
/**

View file

@ -54,8 +54,7 @@ GST_START_TEST (test_ges_scenario)
layers = ges_timeline_get_layers (timeline);
fail_unless (g_list_find (layers, layer) != NULL);
g_list_foreach (layers, (GFunc) gst_object_unref, NULL);
g_list_free (layers);
g_list_free_full (layers, gst_object_unref);
/* Give the Timeline a Track */
GST_DEBUG ("Create a Track");
@ -101,6 +100,7 @@ GST_START_TEST (test_ges_scenario)
* 3 by the timeline
* 1 by the track */
ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3);
fail_unless (ges_track_element_get_track (trackelement) == track);
GST_DEBUG ("Remove the Clip from the layer");
@ -108,6 +108,10 @@ GST_START_TEST (test_ges_scenario)
gst_object_ref (source);
ASSERT_OBJECT_REFCOUNT (layer, "layer", 1);
fail_unless (ges_layer_remove_clip (layer, GES_CLIP (source)));
/* track elements emptied from the track, but stay in clip */
fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) ==
GES_TIMELINE_ELEMENT (source));
fail_unless (ges_track_element_get_track (trackelement) == NULL);
ASSERT_OBJECT_REFCOUNT (source, "source", 1);
ASSERT_OBJECT_REFCOUNT (layer, "layer", 1);
tmp_layer = ges_clip_get_layer (GES_CLIP (source));
@ -118,7 +122,7 @@ GST_START_TEST (test_ges_scenario)
/* Remove the track from the timeline */
gst_object_ref (track);
fail_unless (ges_timeline_remove_track (timeline, track));
fail_unless (ges_track_get_timeline (track) == NULL);
assert_num_in_track (track, 0);
tracks = ges_timeline_get_tracks (timeline);
fail_unless (tracks == NULL);
@ -149,12 +153,23 @@ GST_END_TEST;
* and then add it to the timeline.
*/
#define _CREATE_SOURCE(layer, clip, start, duration) \
{ \
GESAsset *asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); \
GST_DEBUG ("Creating a source"); \
fail_unless (clip = ges_layer_add_asset (layer, asset, start, 0, \
duration, GES_TRACK_TYPE_UNKNOWN)); \
assert_layer(clip, layer); \
ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); \
gst_object_unref (asset); \
}
GST_START_TEST (test_ges_timeline_add_layer)
{
GESTimeline *timeline;
GESLayer *layer, *tmp_layer;
GESLayer *layer;
GESTrack *track;
GESTestClip *s1, *s2, *s3;
GESClip *s1, *s2, *s3;
GList *trackelements, *layers;
GESTrackElement *trackelement;
@ -180,34 +195,6 @@ GST_START_TEST (test_ges_timeline_add_layer)
fail_unless (ges_track_get_timeline (track) == timeline);
fail_unless ((gpointer) GST_ELEMENT_PARENT (track) == (gpointer) timeline);
/* Create a source and add it to the Layer */
GST_DEBUG ("Creating a source");
s1 = ges_test_clip_new ();
fail_unless (s1 != NULL);
fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1)));
tmp_layer = ges_clip_get_layer (GES_CLIP (s1));
fail_unless (tmp_layer == layer);
ASSERT_OBJECT_REFCOUNT (layer, "layer", 2);
gst_object_unref (tmp_layer);
GST_DEBUG ("Creating a source");
s2 = ges_test_clip_new ();
fail_unless (s2 != NULL);
fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2)));
tmp_layer = ges_clip_get_layer (GES_CLIP (s2));
fail_unless (tmp_layer == layer);
ASSERT_OBJECT_REFCOUNT (layer, "layer", 2);
gst_object_unref (tmp_layer);
GST_DEBUG ("Creating a source");
s3 = ges_test_clip_new ();
fail_unless (s3 != NULL);
fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3)));
tmp_layer = ges_clip_get_layer (GES_CLIP (s3));
fail_unless (tmp_layer == layer);
ASSERT_OBJECT_REFCOUNT (layer, "layer", 2);
gst_object_unref (tmp_layer);
GST_DEBUG ("Add the layer to the timeline");
fail_unless (ges_timeline_add_layer (timeline, layer));
/* The timeline steals our reference to the layer */
@ -215,8 +202,14 @@ GST_START_TEST (test_ges_timeline_add_layer)
fail_unless (layer->timeline == timeline);
layers = ges_timeline_get_layers (timeline);
fail_unless (g_list_find (layers, layer) != NULL);
g_list_foreach (layers, (GFunc) gst_object_unref, NULL);
g_list_free (layers);
g_list_free_full (layers, gst_object_unref);
_CREATE_SOURCE (layer, s1, 0, 10);
ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
_CREATE_SOURCE (layer, s2, 20, 10);
ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
_CREATE_SOURCE (layer, s3, 40, 10);
ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
/* Make sure the associated TrackElements are in the Track */
trackelements = GES_CONTAINER_CHILDREN (s1);
@ -266,9 +259,9 @@ GST_END_TEST;
GST_START_TEST (test_ges_timeline_add_layer_first)
{
GESTimeline *timeline;
GESLayer *layer, *tmp_layer;
GESLayer *layer;
GESTrack *track;
GESTestClip *s1, *s2, *s3;
GESClip *s1, *s2, *s3;
GList *trackelements, *tmp, *layers;
ges_init ();
@ -286,30 +279,9 @@ GST_START_TEST (test_ges_timeline_add_layer_first)
track = GES_TRACK (ges_video_track_new ());
fail_unless (track != NULL);
/* Create a source and add it to the Layer */
GST_DEBUG ("Creating a source");
s1 = ges_test_clip_new ();
fail_unless (s1 != NULL);
fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1)));
tmp_layer = ges_clip_get_layer (GES_CLIP (s1));
fail_unless (tmp_layer == layer);
gst_object_unref (tmp_layer);
GST_DEBUG ("Creating a source");
s2 = ges_test_clip_new ();
fail_unless (s2 != NULL);
fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2)));
tmp_layer = ges_clip_get_layer (GES_CLIP (s2));
fail_unless (tmp_layer == layer);
gst_object_unref (tmp_layer);
GST_DEBUG ("Creating a source");
s3 = ges_test_clip_new ();
fail_unless (s3 != NULL);
fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3)));
tmp_layer = ges_clip_get_layer (GES_CLIP (s3));
fail_unless (tmp_layer == layer);
gst_object_unref (tmp_layer);
_CREATE_SOURCE (layer, s1, 0, 10);
_CREATE_SOURCE (layer, s2, 20, 10);
_CREATE_SOURCE (layer, s3, 40, 10);
GST_DEBUG ("Add the layer to the timeline");
fail_unless (ges_timeline_add_layer (timeline, layer));
@ -318,8 +290,7 @@ GST_START_TEST (test_ges_timeline_add_layer_first)
fail_unless (layer->timeline == timeline);
layers = ges_timeline_get_layers (timeline);
fail_unless (g_list_find (layers, layer) != NULL);
g_list_foreach (layers, (GFunc) gst_object_unref, NULL);
g_list_free (layers);
g_list_free_full (layers, gst_object_unref);
GST_DEBUG ("Add the track to the timeline");
fail_unless (ges_timeline_add_track (timeline, track));
@ -369,9 +340,9 @@ GST_END_TEST;
GST_START_TEST (test_ges_timeline_remove_track)
{
GESTimeline *timeline;
GESLayer *layer, *tmp_layer;
GESLayer *layer;
GESTrack *track;
GESTestClip *s1, *s2, *s3;
GESClip *s1, *s2, *s3;
GESTrackElement *t1, *t2, *t3;
GList *trackelements, *tmp, *layers;
@ -390,32 +361,11 @@ GST_START_TEST (test_ges_timeline_remove_track)
track = GES_TRACK (ges_video_track_new ());
fail_unless (track != NULL);
/* Create a source and add it to the Layer */
GST_DEBUG ("Creating a source");
s1 = ges_test_clip_new ();
fail_unless (s1 != NULL);
fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1)));
tmp_layer = ges_clip_get_layer (GES_CLIP (s1));
fail_unless (tmp_layer == layer);
gst_object_unref (tmp_layer);
_CREATE_SOURCE (layer, s1, 0, 10);
ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
GST_DEBUG ("Creating a source");
s2 = ges_test_clip_new ();
fail_unless (s2 != NULL);
fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2)));
tmp_layer = ges_clip_get_layer (GES_CLIP (s2));
fail_unless (tmp_layer == layer);
gst_object_unref (tmp_layer);
_CREATE_SOURCE (layer, s2, 20, 10);
ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
GST_DEBUG ("Creating a source");
s3 = ges_test_clip_new ();
fail_unless (s3 != NULL);
fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3)));
tmp_layer = ges_clip_get_layer (GES_CLIP (s3));
fail_unless (tmp_layer == layer);
gst_object_unref (tmp_layer);
_CREATE_SOURCE (layer, s3, 40, 10);
ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
GST_DEBUG ("Add the layer to the timeline");
@ -426,8 +376,7 @@ GST_START_TEST (test_ges_timeline_remove_track)
layers = ges_timeline_get_layers (timeline);
fail_unless (g_list_find (layers, layer) != NULL);
g_list_foreach (layers, (GFunc) gst_object_unref, NULL);
g_list_free (layers);
g_list_free_full (layers, gst_object_unref);
ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
GST_DEBUG ("Add the track to the timeline");
@ -485,8 +434,18 @@ GST_START_TEST (test_ges_timeline_remove_track)
* 1 by the timeline */
ASSERT_OBJECT_REFCOUNT (t3, "t3", 3);
fail_unless (ges_track_element_get_track (t1) == track);
fail_unless (ges_track_element_get_track (t2) == track);
fail_unless (ges_track_element_get_track (t3) == track);
/* remove the track and check that the track elements have been released */
gst_object_ref (track);
fail_unless (ges_timeline_remove_track (timeline, track));
assert_num_in_track (track, 0);
gst_object_unref (track);
fail_unless (ges_track_element_get_track (t1) == NULL);
fail_unless (ges_track_element_get_track (t2) == NULL);
fail_unless (ges_track_element_get_track (t3) == NULL);
ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 1);
ASSERT_OBJECT_REFCOUNT (t2, "trackelement", 1);
@ -507,22 +466,67 @@ GST_END_TEST;
typedef struct
{
GESTestClip **o1, **o2, **o3;
GESTrack **tr1, **tr2;
GESClip *clips[4];
guint num_calls[4];
GESTrackElement *effects[3];
GESTrack *tr1, *tr2;
guint num_unrecognised;
} SelectTracksData;
static GPtrArray *
select_tracks_cb (GESTimeline * timeline, GESClip * clip,
GESTrackElement * track_element, SelectTracksData * st_data)
GESTrackElement * track_element, SelectTracksData * data)
{
GESTrack *track;
GPtrArray *ret = g_ptr_array_new ();
track = (clip == (GESClip *) * st_data->o2) ? *st_data->tr2 : *st_data->tr1;
gboolean track1 = FALSE;
gboolean track2 = FALSE;
guint i;
gboolean recognise_clip = FALSE;
gst_object_ref (track);
for (i = 0; i < 4; i++) {
if (clip == data->clips[i]) {
data->num_calls[i]++;
recognise_clip = TRUE;
}
}
g_ptr_array_add (ret, track);
if (!recognise_clip) {
GST_DEBUG_OBJECT (timeline, "unrecognised clip %" GES_FORMAT " for "
"track element %" GES_FORMAT, GES_ARGS (clip),
GES_ARGS (track_element));
data->num_unrecognised++;
return ret;
}
if (GES_IS_BASE_EFFECT (track_element)) {
if (track_element == data->effects[0]) {
track1 = TRUE;
} else if (track_element == data->effects[1]) {
track1 = TRUE;
track2 = TRUE;
} else if (track_element == data->effects[2]) {
track2 = TRUE;
} else {
GST_DEBUG_OBJECT (timeline, "unrecognised effect %" GES_FORMAT,
GES_ARGS (track_element));
data->num_unrecognised++;
}
} else if (GES_IS_SOURCE (track_element)) {
if (clip == data->clips[0] || clip == data->clips[1])
track1 = TRUE;
if (clip == data->clips[1] || clip == data->clips[2])
track2 = TRUE;
/* clips[3] has no tracks selected */
} else {
GST_DEBUG_OBJECT (timeline, "unrecognised track element %" GES_FORMAT,
GES_ARGS (track_element));
data->num_unrecognised++;
}
if (track1)
g_ptr_array_add (ret, gst_object_ref (data->tr1));
if (track2)
g_ptr_array_add (ret, gst_object_ref (data->tr2));
return ret;
}
@ -530,12 +534,14 @@ select_tracks_cb (GESTimeline * timeline, GESClip * clip,
GST_START_TEST (test_ges_timeline_multiple_tracks)
{
GESTimeline *timeline;
GESLayer *layer, *tmp_layer;
GESLayer *layer;
GESTrack *track1, *track2;
GESTestClip *s1, *s2, *s3;
GESTrackElement *t1, *t2, *t3;
GESClip *s1, *s2, *s3, *s4;
GESTrackElement *e1, *e2, *e3, *el, *el2, *e_copy;
gboolean found_e1 = FALSE, found_e2 = FALSE, found_e3 = FALSE;
GList *trackelements, *tmp, *layers;
SelectTracksData st_data = { &s1, &s2, &s3, &track1, &track2 };
GstControlSource *ctrl_source;
SelectTracksData st_data;
ges_init ();
@ -544,9 +550,6 @@ GST_START_TEST (test_ges_timeline_multiple_tracks)
timeline = ges_timeline_new ();
fail_unless (timeline != NULL);
g_signal_connect (timeline, "select-tracks-for-object",
G_CALLBACK (select_tracks_cb), &st_data);
GST_DEBUG ("Create a layer");
layer = ges_layer_new ();
fail_unless (layer != NULL);
@ -570,31 +573,71 @@ GST_START_TEST (test_ges_timeline_multiple_tracks)
fail_unless (ges_track_get_timeline (track2) == timeline);
fail_unless ((gpointer) GST_ELEMENT_PARENT (track2) == (gpointer) timeline);
/* Create a source and add it to the Layer */
GST_DEBUG ("Creating a source");
s1 = ges_test_clip_new ();
fail_unless (s1 != NULL);
fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1)));
tmp_layer = ges_clip_get_layer (GES_CLIP (s1));
fail_unless (tmp_layer == layer);
gst_object_unref (tmp_layer);
/* adding to the layer before it is part of the timeline does not
* trigger track selection */
/* s1 and s3 can overlap since they are destined for different tracks */
/* s2 will overlap both */
/* s4 destined for no track */
_CREATE_SOURCE (layer, s1, 0, 10);
_CREATE_SOURCE (layer, s2, 5, 10);
_CREATE_SOURCE (layer, s3, 0, 10);
_CREATE_SOURCE (layer, s4, 0, 20);
GST_DEBUG ("Creating a source");
s2 = ges_test_clip_new ();
fail_unless (s2 != NULL);
fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2)));
tmp_layer = ges_clip_get_layer (GES_CLIP (s2));
fail_unless (tmp_layer == layer);
gst_object_unref (tmp_layer);
e1 = GES_TRACK_ELEMENT (ges_effect_new ("videobalance"));
fail_unless (ges_container_add (GES_CONTAINER (s2),
GES_TIMELINE_ELEMENT (e1)));
e2 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv ! vertigotv"));
fail_unless (ges_container_add (GES_CONTAINER (s2),
GES_TIMELINE_ELEMENT (e2)));
e3 = GES_TRACK_ELEMENT (ges_effect_new ("alpha"));
fail_unless (ges_container_add (GES_CONTAINER (s2),
GES_TIMELINE_ELEMENT (e3)));
assert_equals_int (0,
ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e1)));
assert_equals_int (1,
ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e2)));
assert_equals_int (2,
ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e3)));
GST_DEBUG ("Creating a source");
s3 = ges_test_clip_new ();
fail_unless (s3 != NULL);
fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3)));
tmp_layer = ges_clip_get_layer (GES_CLIP (s3));
fail_unless (tmp_layer == layer);
gst_object_unref (tmp_layer);
assert_num_children (s1, 0);
assert_num_children (s2, 3);
assert_num_children (s3, 0);
ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (s2),
"scratch-lines", 2, "speed", 50.0, NULL);
ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ());
g_object_set (G_OBJECT (ctrl_source), "mode",
GST_INTERPOLATION_MODE_NONE, NULL);
fail_unless (gst_timed_value_control_source_set
(GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 0, 1.0));
fail_unless (gst_timed_value_control_source_set
(GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 4, 7.0));
fail_unless (gst_timed_value_control_source_set
(GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 8, 3.0));
fail_unless (ges_track_element_set_control_source (e2, ctrl_source,
"scratch-lines", "direct-absolute"));
gst_object_unref (ctrl_source);
st_data.tr1 = track1;
st_data.tr2 = track2;
st_data.clips[0] = s1;
st_data.clips[1] = s2;
st_data.clips[2] = s3;
st_data.clips[3] = s4;
st_data.num_calls[0] = 0;
st_data.num_calls[1] = 0;
st_data.num_calls[2] = 0;
st_data.num_calls[3] = 0;
st_data.effects[0] = e1;
st_data.effects[1] = e2;
st_data.effects[2] = e3;
st_data.num_unrecognised = 0;
g_signal_connect (timeline, "select-tracks-for-object",
G_CALLBACK (select_tracks_cb), &st_data);
/* adding layer to the timeline will trigger track selection, this */
GST_DEBUG ("Add the layer to the timeline");
fail_unless (ges_timeline_add_layer (timeline, layer));
/* The timeline steals our reference to the layer */
@ -603,70 +646,121 @@ GST_START_TEST (test_ges_timeline_multiple_tracks)
layers = ges_timeline_get_layers (timeline);
fail_unless (g_list_find (layers, layer) != NULL);
g_list_foreach (layers, (GFunc) gst_object_unref, NULL);
g_list_free (layers);
g_list_free_full (layers, gst_object_unref);
assert_equals_int (st_data.num_unrecognised, 0);
/* Make sure the associated TrackElements are in the Track */
trackelements = GES_CONTAINER_CHILDREN (s1);
fail_unless (trackelements != NULL);
t1 = GES_TRACK_ELEMENT ((trackelements)->data);
for (tmp = trackelements; tmp; tmp = tmp->next) {
/* There are 3 references held:
* 1 by the clip
* 1 by the track
* 1 by the timeline */
ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
fail_unless (ges_track_element_get_track (tmp->data) == track1);
}
gst_object_ref (t1);
/* There are 3 references held:
* 1 by the container
* 1 by the track
* 1 by the timeline
* 1 added by ourselves above (gst_object_ref (t1)) */
ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 4);
assert_num_children (s1, 1);
el = GES_CONTAINER_CHILDREN (s1)->data;
fail_unless (GES_IS_SOURCE (el));
fail_unless (ges_track_element_get_track (el) == track1);
ASSERT_OBJECT_REFCOUNT (el, "1 timeline + 1 track + 1 clip", 3);
/* called once for source */
assert_equals_int (st_data.num_calls[0], 1);
/* 2 sources + 4 effects */
assert_num_children (s2, 6);
trackelements = GES_CONTAINER_CHILDREN (s2);
fail_unless (trackelements != NULL);
t2 = GES_TRACK_ELEMENT (trackelements->data);
for (tmp = trackelements; tmp; tmp = tmp->next) {
/* There are 3 references held:
* 1 by the clip
* 1 by the track
* 1 by the timeline */
ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
fail_unless (ges_track_element_get_track (tmp->data) == track2);
}
gst_object_ref (t2);
/* There are 3 references held:
* 1 by the container
* 1 by the track
* 1 by the timeline
* 1 added by ourselves above (gst_object_ref (t2)) */
ASSERT_OBJECT_REFCOUNT (t2, "t2", 4);
/* sources at the end */
el = g_list_nth_data (trackelements, 5);
fail_unless (GES_IS_SOURCE (el));
el2 = g_list_nth_data (trackelements, 4);
fail_unless (GES_IS_SOURCE (el2));
/* font-desc is originally "", but on setting switches to Normal, so we
* set it explicitly */
ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (el),
"font-desc", "Normal", NULL);
assert_equal_children_properties (el, el2);
assert_equal_bindings (el, el2);
assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (el),
GES_TIMELINE_ELEMENT_PRIORITY (el2));
/* check one in each track */
fail_unless (ges_track_element_get_track (el)
!= ges_track_element_get_track (el2));
fail_unless (ges_track_element_get_track (el) == track1
|| ges_track_element_get_track (el2) == track1);
fail_unless (ges_track_element_get_track (el) == track2
|| ges_track_element_get_track (el2) == track2);
/* effects */
e_copy = NULL;
for (tmp = trackelements; tmp; tmp = tmp->next) {
el = tmp->data;
ASSERT_OBJECT_REFCOUNT (el, "1 timeline + 1 track + 1 clip", 3);
if (GES_IS_BASE_EFFECT (el)) {
if (el == e1) {
fail_if (found_e1);
found_e1 = TRUE;
} else if (el == e2) {
fail_if (found_e2);
found_e2 = TRUE;
} else if (el == e3) {
fail_if (found_e3);
found_e3 = TRUE;
} else {
fail_if (e_copy);
e_copy = el;
}
}
}
fail_unless (found_e1);
fail_unless (found_e2);
fail_unless (found_e3);
fail_unless (e_copy);
fail_unless (ges_track_element_get_track (e1) == track1);
fail_unless (ges_track_element_get_track (e3) == track2);
assert_equal_children_properties (e2, e_copy);
assert_equal_bindings (e2, e_copy);
/* check one in each track */
fail_unless (ges_track_element_get_track (e2)
!= ges_track_element_get_track (e_copy));
fail_unless (ges_track_element_get_track (e2) == track1
|| ges_track_element_get_track (e_copy) == track1);
fail_unless (ges_track_element_get_track (e2) == track2
|| ges_track_element_get_track (e_copy) == track2);
/* e2 copy placed next to e2 in top effect list */
assert_equals_int (0,
ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e1)));
assert_equals_int (1,
ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e2)));
assert_equals_int (2,
ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e_copy)));
assert_equals_int (3,
ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e3)));
/* called 4 times: 1 for source, and 1 for each effect (3) */
assert_equals_int (st_data.num_calls[1], 4);
assert_num_children (s3, 1);
el = GES_CONTAINER_CHILDREN (s3)->data;
fail_unless (GES_IS_SOURCE (el));
fail_unless (ges_track_element_get_track (el) == track2);
ASSERT_OBJECT_REFCOUNT (el, "1 timeline + 1 track + 1 clip", 3);
/* called once for source */
assert_equals_int (st_data.num_calls[2], 1);
/* one child but no track */
assert_num_children (s4, 1);
el = GES_CONTAINER_CHILDREN (s4)->data;
fail_unless (GES_IS_SOURCE (el));
fail_unless (ges_track_element_get_track (el) == NULL);
ASSERT_OBJECT_REFCOUNT (el, "1 clip", 1);
/* called once for source (where no track was selected) */
assert_equals_int (st_data.num_calls[0], 1);
/* 2 sources + 2 effects */
assert_num_in_track (track1, 4);
assert_num_in_track (track2, 4);
trackelements = GES_CONTAINER_CHILDREN (s3);
fail_unless (trackelements != NULL);
t3 = GES_TRACK_ELEMENT (trackelements->data);
for (tmp = trackelements; tmp; tmp = tmp->next) {
/* There are 3 references held:
* 1 by the clip
* 1 by the track
* 1 by the timeline */
ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
fail_unless (ges_track_element_get_track (tmp->data) == track1);
}
gst_object_ref (t3);
/* There are 3 references held:
* 1 by the container
* 1 by the track
* 1 by the timeline
* 1 added by ourselves above (gst_object_ref (t3)) */
ASSERT_OBJECT_REFCOUNT (t3, "t3", 4);
gst_object_unref (t1);
gst_object_unref (t2);
gst_object_unref (t3);
gst_object_unref (timeline);

File diff suppressed because it is too large Load diff

View file

@ -106,6 +106,23 @@ G_STMT_START { \
GST_TIME_ARGS (_MAX_DURATION(obj)), GST_TIME_ARGS (max_duration)); \
}
#define assert_num_in_track(track, val) \
{ \
GList *tmp = ges_track_get_elements (track); \
guint length = g_list_length (tmp); \
fail_unless (length == val, "Track %" GST_PTR_FORMAT \
" contains %u track elements, rather than %u", track, length, val); \
g_list_free_full (tmp, gst_object_unref); \
}
#define assert_num_children(clip, cmp) \
{ \
guint num_children = g_list_length (GES_CONTAINER_CHILDREN (clip)); \
fail_unless (cmp == num_children, \
"clip %s contains %u children rather than %u", \
GES_TIMELINE_ELEMENT_NAME (clip), num_children, cmp); \
}
/* assert that the time property (start, duration or in-point) is the
* same as @cmp for the clip and all its children */
#define assert_clip_children_time_val(clip, property, cmp) \
@ -142,6 +159,27 @@ G_STMT_START { \
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), layer_prio); \
}
#define assert_layer(clip, layer) \
{ \
GESLayer *tmp_layer = ges_clip_get_layer (GES_CLIP (clip)); \
fail_unless (tmp_layer == GES_LAYER (layer), "clip %s belongs to " \
"layer %u (timeline %" GST_PTR_FORMAT ") rather than layer %u " \
"(timeline %" GST_PTR_FORMAT ")", \
tmp_layer ? ges_layer_get_priority (tmp_layer) : 0, \
tmp_layer ? tmp_layer->timeline : NULL, \
layer ? ges_layer_get_priority (GES_LAYER (layer)) : 0, \
layer ? GES_LAYER (layer)->timeline : NULL); \
if (tmp_layer) \
gst_object_unref (tmp_layer); \
if (layer) { \
GList *layer_clips = ges_layer_get_clips (GES_LAYER (layer)); \
fail_unless (g_list_find (layer_clips, clip), "clip %s not found " \
"in layer %u (timeline %" GST_PTR_FORMAT ")", \
ges_layer_get_priority (GES_LAYER (layer)), layer->timeline); \
g_list_free_full (layer_clips, gst_object_unref); \
} \
}
/* test that the two property lists contain the same properties the same
* number of times */
#define assert_property_list_match(list1, len1, list2, len2) \
@ -188,5 +226,144 @@ G_STMT_START { \
g_free (count_list1); \
}
#define assert_equal_children_properties(el1, el2) \
{ \
guint i, num1, num2; \
const gchar *name1 = GES_TIMELINE_ELEMENT_NAME (el1); \
const gchar *name2 = GES_TIMELINE_ELEMENT_NAME (el2); \
GParamSpec **el_props1 = ges_timeline_element_list_children_properties ( \
GES_TIMELINE_ELEMENT (el1), &num1); \
GParamSpec **el_props2 = ges_timeline_element_list_children_properties ( \
GES_TIMELINE_ELEMENT (el2), &num2); \
assert_property_list_match (el_props1, num1, el_props2, num2); \
\
for (i = 0; i < num1; i++) { \
gchar *ser1, *ser2; \
GParamSpec *prop = el_props1[i]; \
GValue val1 = G_VALUE_INIT, val2 = G_VALUE_INIT; \
/* name property can be different */ \
if (g_strcmp0 (prop->name, "name") == 0) \
continue; \
if (g_strcmp0 (prop->name, "parent") == 0) \
continue; \
g_value_init (&val1, prop->value_type); \
g_value_init (&val2, prop->value_type); \
ges_timeline_element_get_child_property_by_pspec ( \
GES_TIMELINE_ELEMENT (el1), prop, &val1); \
ges_timeline_element_get_child_property_by_pspec ( \
GES_TIMELINE_ELEMENT (el2), prop, &val2); \
ser1 = gst_value_serialize (&val1); \
ser2 = gst_value_serialize (&val2); \
fail_unless (gst_value_compare (&val1, &val2) == GST_VALUE_EQUAL, \
"Child property '%s' for %s does not match that for %s (%s vs %s)", \
prop->name, name1, name2, ser1, ser2); \
g_free (ser1); \
g_free (ser2); \
g_value_unset (&val1); \
g_value_unset (&val2); \
} \
free_children_properties (el_props1, num1); \
free_children_properties (el_props2, num2); \
}
#define assert_equal_bindings(el1, el2) \
{ \
guint i, num1, num2; \
const gchar *name1 = GES_TIMELINE_ELEMENT_NAME (el1); \
const gchar *name2 = GES_TIMELINE_ELEMENT_NAME (el2); \
GParamSpec **props1 = ges_timeline_element_list_children_properties ( \
GES_TIMELINE_ELEMENT (el1), &num1); \
GParamSpec **props2 = ges_timeline_element_list_children_properties ( \
GES_TIMELINE_ELEMENT (el2), &num2); \
assert_property_list_match (props1, num1, props2, num2); \
\
for (i = 0; i < num1; i++) { \
const gchar *prop = props1[i]->name; \
GList *tmp1, *tmp2; \
GList *timed_vals1, *timed_vals2; \
GObject *object1, *object2; \
gboolean abs1, abs2; \
GstControlSource *source1, *source2; \
GstInterpolationMode mode1, mode2; \
GstControlBinding *binding1, *binding2; \
guint j; \
\
binding1 = ges_track_element_get_control_binding ( \
GES_TRACK_ELEMENT (el1), prop); \
binding2 = ges_track_element_get_control_binding ( \
GES_TRACK_ELEMENT (el2), prop); \
if (binding1 == NULL) { \
fail_unless (binding2 == NULL, "%s has a binding for property " \
" '%s', whilst %s does not", name2, prop, name1); \
continue; \
} \
if (binding2 == NULL) { \
fail_unless (binding1 == NULL, "%s has a binding for property " \
"'%s', whilst %s does not", name1, prop, name2); \
continue; \
} \
\
fail_unless (G_OBJECT_TYPE (binding1) == GST_TYPE_DIRECT_CONTROL_BINDING, \
"%s binding for property '%s' is not a direct control binding, " \
"so cannot be handled", prop, name1); \
fail_unless (G_OBJECT_TYPE (binding2) == GST_TYPE_DIRECT_CONTROL_BINDING, \
"%s binding for property '%s' is not a direct control binding, " \
"so cannot be handled", prop, name2); \
\
g_object_get (G_OBJECT (binding1), "control-source", &source1, \
"absolute", &abs1, "object", &object1, NULL); \
g_object_get (G_OBJECT (binding2), "control-source", &source2, \
"absolute", &abs2, "object", &object2, NULL); \
\
fail_unless (G_OBJECT_TYPE (object1) == G_OBJECT_TYPE (object2), \
"The child object for property '%s' for %s and %s correspond " \
"to different object types (%s vs %s)", prop, name1, name2, \
G_OBJECT_TYPE_NAME (object1), G_OBJECT_TYPE_NAME (object2)); \
gst_object_unref (object1); \
gst_object_unref (object2); \
\
fail_unless (abs1 == abs2, "control biding for property '%s' " \
" is %s absolute for %s, but %s absolute for %s", prop, \
abs1 ? "" : "not", name1, abs2 ? "" : "not", name2); \
\
fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source1), \
"%s does not have an interpolation control source for " \
"property '%s', so cannot be handled", name1, prop); \
fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source2), \
"%s does not have an interpolation control source for " \
"property '%s', so cannot be handled", name2, prop); \
g_object_get (G_OBJECT (source1), "mode", &mode1, NULL); \
g_object_get (G_OBJECT (source2), "mode", &mode2, NULL); \
fail_unless (mode1 == mode2, "control source for property '%s' " \
"has different modes for %s and %s (%i vs %i)", prop, \
name1, name2, mode1, mode2); \
\
timed_vals1 = gst_timed_value_control_source_get_all ( \
GST_TIMED_VALUE_CONTROL_SOURCE (source1)); \
timed_vals2 = gst_timed_value_control_source_get_all ( \
GST_TIMED_VALUE_CONTROL_SOURCE (source2)); \
\
for (j = 0, tmp1 = timed_vals1, tmp2 = timed_vals2; tmp1 && tmp2; \
j++, tmp1 = tmp1->next, tmp2 = tmp2->next) { \
GstTimedValue *val1 = tmp1->data, *val2 = tmp2->data; \
fail_unless (val1->timestamp == val2->timestamp && \
val1->value == val2->value, "The %uth timed value for property " \
"'%s' is different for %s and %s: (%" G_GUINT64_FORMAT ": %g) vs " \
"(%" G_GUINT64_FORMAT ": %g)", j, prop, name1, name2, \
val1->timestamp, val1->value, val2->timestamp, val2->value); \
} \
fail_unless (tmp1 == NULL, "Found too many timed values for " \
"property '%s' for %s", prop, name1); \
fail_unless (tmp2 == NULL, "Found too many timed values for " \
"property '%s' for %s", prop, name2); \
\
g_list_free (timed_vals1); \
g_list_free (timed_vals2); \
gst_object_unref (source1); \
gst_object_unref (source2); \
} \
free_children_properties (props1, num1); \
free_children_properties (props2, num2); \
}
void print_timeline(GESTimeline *timeline);