diff --git a/ges/ges-auto-transition.c b/ges/ges-auto-transition.c index befea901d5..f28f37aee8 100644 --- a/ges/ges-auto-transition.c +++ b/ges/ges-auto-transition.c @@ -46,6 +46,10 @@ neighbour_changed_cb (GESClip * clip, GParamSpec * arg G_GNUC_UNUSED, GESTimelineElement *parent = ges_timeline_element_get_toplevel_parent (GES_TIMELINE_ELEMENT (clip)); + if (ELEMENT_FLAG_IS_SET (parent, GES_TIMELINE_ELEMENT_SET_SIMPLE)) { + return; + } + if (parent) { GESTimelineElement *prev_topparent = ges_timeline_element_get_toplevel_parent (GES_TIMELINE_ELEMENT @@ -54,6 +58,11 @@ neighbour_changed_cb (GESClip * clip, GParamSpec * arg G_GNUC_UNUSED, ges_timeline_element_get_toplevel_parent (GES_TIMELINE_ELEMENT (self->previous_source)); + if (ELEMENT_FLAG_IS_SET (prev_topparent, GES_TIMELINE_ELEMENT_SET_SIMPLE) || + ELEMENT_FLAG_IS_SET (next_topparent, GES_TIMELINE_ELEMENT_SET_SIMPLE)) { + return; + } + if (parent == prev_topparent && parent == next_topparent) { GST_DEBUG_OBJECT (self, "Moving all inside the same group, nothing to do"); @@ -192,3 +201,11 @@ ges_auto_transition_new (GESTrackElement * transition, return self; } + +void +ges_auto_transition_update (GESAutoTransition * self) +{ + GST_INFO ("Updating info %s", + GES_TIMELINE_ELEMENT_NAME (self->transition_clip)); + neighbour_changed_cb (self->previous_clip, NULL, self); +} diff --git a/ges/ges-auto-transition.h b/ges/ges-auto-transition.h index a8d80bd125..4058b0497d 100644 --- a/ges/ges-auto-transition.h +++ b/ges/ges-auto-transition.h @@ -71,6 +71,7 @@ struct _GESAutoTransition G_GNUC_INTERNAL GType ges_auto_transition_get_type (void) G_GNUC_CONST; +G_GNUC_INTERNAL void ges_auto_transition_update (GESAutoTransition *self); G_GNUC_INTERNAL GESAutoTransition * ges_auto_transition_new (GESTrackElement * transition, GESTrackElement * previous_source, GESTrackElement * next_source); diff --git a/ges/ges-clip.c b/ges/ges-clip.c index fab363bf8e..a1637cfc38 100644 --- a/ges/ges-clip.c +++ b/ges/ges-clip.c @@ -175,7 +175,6 @@ static gboolean _set_duration (GESTimelineElement * element, GstClockTime duration) { GList *tmp; - GESTimeline *timeline; GESContainer *container = GES_CONTAINER (element); @@ -183,16 +182,8 @@ _set_duration (GESTimelineElement * element, GstClockTime duration) for (tmp = container->children; tmp; tmp = g_list_next (tmp)) { GESTimelineElement *child = (GESTimelineElement *) tmp->data; - if (child != container->initiated_move) { - /* Make the snapping happen if in a timeline */ - timeline = GES_TIMELINE_ELEMENT_TIMELINE (child); - if (timeline == NULL - || ELEMENT_FLAG_IS_SET (element, GES_CLIP_IS_SPLITTING) - || (ges_timeline_trim_object_simple (timeline, child, NULL, - GES_EDGE_END, _START (child) + duration, TRUE) == FALSE)) { - _set_duration0 (GES_TIMELINE_ELEMENT (child), duration); - } - } + if (child != container->initiated_move) + _set_duration0 (GES_TIMELINE_ELEMENT (child), duration); } container->children_control_mode = GES_CHILDREN_UPDATE; @@ -628,9 +619,8 @@ static gboolean _edit (GESContainer * container, GList * layers, gint new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position) { - GList *tmp; - gboolean ret = TRUE; - GESLayer *layer; + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (container); + GESTimelineElement *element = GES_TIMELINE_ELEMENT (container); if (!G_UNLIKELY (GES_CONTAINER_CHILDREN (container))) { GST_WARNING_OBJECT (container, "Trying to edit, but not containing" @@ -638,31 +628,35 @@ _edit (GESContainer * container, GList * layers, return FALSE; } - for (tmp = GES_CONTAINER_CHILDREN (container); tmp; tmp = g_list_next (tmp)) { - if (GES_IS_SOURCE (tmp->data) || GES_IS_TRANSITION (tmp->data)) { - ret &= ges_track_element_edit (tmp->data, layers, mode, edge, position); - break; - } + if (!timeline) { + GST_WARNING_OBJECT (container, "Trying to edit, but not in any" + "timeline."); + return FALSE; } - /* Moving to layer */ - if (new_layer_priority == -1) { - GST_DEBUG_OBJECT (container, "Not moving new prio %d", new_layer_priority); - } else { - gint priority_offset; - - layer = GES_CLIP (container)->priv->layer; - if (layer == NULL) { - GST_WARNING_OBJECT (container, "Not in any layer yet, not moving"); - + switch (mode) { + case GES_EDIT_MODE_RIPPLE: + return timeline_ripple_object (timeline, element, + new_layer_priority < + 0 ? GES_TIMELINE_ELEMENT_LAYER_PRIORITY (container) : + new_layer_priority, layers, edge, position); + case GES_EDIT_MODE_TRIM: + return timeline_trim_object (timeline, element, + new_layer_priority < + 0 ? GES_TIMELINE_ELEMENT_LAYER_PRIORITY (container) : + new_layer_priority, layers, edge, position); + case GES_EDIT_MODE_NORMAL: + return timeline_move_object (timeline, element, + new_layer_priority < + 0 ? GES_TIMELINE_ELEMENT_LAYER_PRIORITY (container) : + new_layer_priority, layers, edge, position); + case GES_EDIT_MODE_ROLL: + return timeline_roll_object (timeline, element, layers, edge, position); + case GES_EDIT_MODE_SLIDE: + GST_ERROR ("Sliding not implemented."); return FALSE; - } - priority_offset = new_layer_priority - ges_layer_get_priority (layer); - - ret &= timeline_context_to_layer (layer->timeline, priority_offset); } - - return ret; + return FALSE; } static void @@ -1012,23 +1006,6 @@ ges_clip_set_layer (GESClip * clip, GESLayer * layer) g_object_notify_by_pspec (G_OBJECT (clip), properties[PROP_LAYER]); } -/** - * ges_clip_get_layer_priority: - * @clip: The clip to get the layer priority from - * - * Returns: The priority of the layer @clip is in, -1 if not in a layer. - */ -guint32 -ges_clip_get_layer_priority (GESClip * clip) -{ - g_return_val_if_fail (GES_IS_CLIP (clip), -1); - - if (clip->priv->layer == NULL) - return -1; - - return ges_layer_get_priority (clip->priv->layer); -} - /** * ges_clip_set_moving_from_layer: * @clip: a #GESClip @@ -1088,6 +1065,19 @@ ges_clip_move_to_layer (GESClip * clip, GESLayer * layer) g_return_val_if_fail (GES_IS_CLIP (clip), FALSE); g_return_val_if_fail (GES_IS_LAYER (layer), FALSE); + ELEMENT_SET_FLAG (clip, GES_CLIP_IS_MOVING); + if (layer->timeline + && !timeline_tree_can_move_element (timeline_get_tree (layer->timeline), + GES_TIMELINE_ELEMENT (clip), + ges_layer_get_priority (layer), + GES_TIMELINE_ELEMENT_START (clip), + GES_TIMELINE_ELEMENT_DURATION (clip), NULL)) { + GST_INFO_OBJECT (layer, "Clip %" GES_FORMAT " can't move to layer %d", + GES_ARGS (clip), ges_layer_get_priority (layer)); + ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING); + return FALSE; + } + current_layer = clip->priv->layer; if (current_layer == NULL) { @@ -1099,11 +1089,11 @@ ges_clip_move_to_layer (GESClip * clip, GESLayer * layer) GST_DEBUG_OBJECT (clip, "moving to layer %p, priority: %d", layer, ges_layer_get_priority (layer)); - ELEMENT_SET_FLAG (clip, GES_CLIP_IS_MOVING); gst_object_ref (clip); ret = ges_layer_remove_clip (current_layer, clip); if (!ret) { + ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING); gst_object_unref (clip); return FALSE; } @@ -1418,16 +1408,9 @@ ges_clip_split (GESClip * clip, guint64 position) position - start + inpoint); } - ELEMENT_SET_FLAG (clip, GES_CLIP_IS_SPLITTING); + ELEMENT_SET_FLAG (clip, GES_TIMELINE_ELEMENT_SET_SIMPLE); _set_duration0 (GES_TIMELINE_ELEMENT (clip), old_duration); - ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_SPLITTING); - - if (GES_TIMELINE_ELEMENT_TIMELINE (clip)) { - for (tmp = GES_CONTAINER_CHILDREN (new_object); tmp; tmp = tmp->next) { - timeline_create_transitions (GES_TIMELINE_ELEMENT_TIMELINE (tmp->data), - tmp->data); - } - } + ELEMENT_UNSET_FLAG (clip, GES_TIMELINE_ELEMENT_SET_SIMPLE); return new_object; } @@ -1466,134 +1449,40 @@ ges_clip_get_supported_formats (GESClip * clip) gboolean _ripple (GESTimelineElement * element, GstClockTime start) { - gboolean ret = TRUE; - GESTimeline *timeline; - GESClip *clip = GES_CLIP (element); - - timeline = ges_layer_get_timeline (clip->priv->layer); - - if (timeline == NULL) { - GST_DEBUG ("Not in a timeline yet"); - return FALSE; - } - - if (start > _END (element)) - start = _END (element); - - if (GES_CONTAINER_CHILDREN (element)) { - GESTrackElement *track_element = - GES_TRACK_ELEMENT (GES_CONTAINER_CHILDREN (element)->data); - - ret = timeline_ripple_object (timeline, track_element, NULL, GES_EDGE_NONE, - start); - } - - return ret; + return ges_container_edit (GES_CONTAINER (element), NULL, + ges_timeline_element_get_layer_priority (element), + GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, start); } static gboolean _ripple_end (GESTimelineElement * element, GstClockTime end) { - gboolean ret = TRUE; - GESTimeline *timeline; - GESClip *clip = GES_CLIP (element); - - timeline = ges_layer_get_timeline (clip->priv->layer); - - if (timeline == NULL) { - GST_DEBUG ("Not in a timeline yet"); - return FALSE; - } - - if (GES_CONTAINER_CHILDREN (element)) { - GESTrackElement *track_element = - GES_TRACK_ELEMENT (GES_CONTAINER_CHILDREN (element)->data); - - ret = timeline_ripple_object (timeline, track_element, NULL, GES_EDGE_END, - end); - } - - return ret; + return ges_container_edit (GES_CONTAINER (element), NULL, + ges_timeline_element_get_layer_priority (element), + GES_EDIT_MODE_RIPPLE, GES_EDGE_END, end); } gboolean _roll_start (GESTimelineElement * element, GstClockTime start) { - gboolean ret = TRUE; - GESTimeline *timeline; - - GESClip *clip = GES_CLIP (element); - - timeline = ges_layer_get_timeline (clip->priv->layer); - - if (timeline == NULL) { - GST_DEBUG ("Not in a timeline yet"); - return FALSE; - } - - if (GES_CONTAINER_CHILDREN (element)) { - GESTrackElement *track_element = - GES_TRACK_ELEMENT (GES_CONTAINER_CHILDREN (element)->data); - - ret = timeline_roll_object (timeline, track_element, NULL, GES_EDGE_START, - start); - } - - return ret; + return ges_container_edit (GES_CONTAINER (element), NULL, + ges_timeline_element_get_layer_priority (element), + GES_EDIT_MODE_ROLL, GES_EDGE_START, start); } gboolean _roll_end (GESTimelineElement * element, GstClockTime end) { - gboolean ret = TRUE; - GESTimeline *timeline; - - GESClip *clip = GES_CLIP (element); - - timeline = ges_layer_get_timeline (clip->priv->layer); - if (timeline == NULL) { - GST_DEBUG ("Not in a timeline yet"); - return FALSE; - } - - - if (GES_CONTAINER_CHILDREN (element)) { - GESTrackElement *track_element = - GES_TRACK_ELEMENT (GES_CONTAINER_CHILDREN (element)->data); - - ret = timeline_roll_object (timeline, track_element, - NULL, GES_EDGE_END, end); - } - - return ret; + return ges_container_edit (GES_CONTAINER (element), NULL, + ges_timeline_element_get_layer_priority (element), + GES_EDIT_MODE_ROLL, GES_EDGE_END, end); } gboolean _trim (GESTimelineElement * element, GstClockTime start) { - gboolean ret = TRUE; - GESTimeline *timeline; - - GESClip *clip = GES_CLIP (element); - - timeline = ges_layer_get_timeline (clip->priv->layer); - - if (timeline == NULL) { - GST_DEBUG ("Not in a timeline yet"); - return FALSE; - } - - if (GES_CONTAINER_CHILDREN (element)) { - GESTrackElement *track_element = - GES_TRACK_ELEMENT (GES_CONTAINER_CHILDREN (element)->data); - - GST_DEBUG_OBJECT (element, "Trimming child: %" GST_PTR_FORMAT, - track_element); - ret = timeline_trim_object (timeline, track_element, NULL, GES_EDGE_START, - start); - } - - return ret; + return ges_container_edit (GES_CONTAINER (element), NULL, -1, + GES_EDIT_MODE_TRIM, GES_EDGE_START, start); } /** diff --git a/ges/ges-clip.h b/ges/ges-clip.h index 848c288d2d..f0bbb1e228 100644 --- a/ges/ges-clip.h +++ b/ges/ges-clip.h @@ -163,8 +163,6 @@ GES_API GESLayer* ges_clip_get_layer (GESClip *clip); GES_API gboolean ges_clip_move_to_layer (GESClip *clip, GESLayer *layer); -GES_API -guint32 ges_clip_get_layer_priority (GESClip * clip); /**************************************************** * Effects * diff --git a/ges/ges-container.c b/ges/ges-container.c index 034fae2285..168b3e881a 100644 --- a/ges/ges-container.c +++ b/ges/ges-container.c @@ -524,10 +524,14 @@ _child_start_changed_cb (GESTimelineElement * child, GESContainerPrivate *priv = container->priv; GESTimelineElement *element = GES_TIMELINE_ELEMENT (container); + GESChildrenControlMode pmode = container->children_control_mode; map = g_hash_table_lookup (priv->mappings, child); g_assert (map); + if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE)) + container->children_control_mode = GES_CHILDREN_UPDATE_ALL_VALUES; + switch (container->children_control_mode) { case GES_CHILDREN_IGNORE_NOTIFIES: return; @@ -540,8 +544,8 @@ _child_start_changed_cb (GESTimelineElement * child, _DURATION (container) = _END (container) - start; _START (container) = start; - GST_DEBUG_OBJECT (container, "Child move made us " - "move to %" GST_TIME_FORMAT, GST_TIME_ARGS (_START (container))); + GST_DEBUG_OBJECT (container, "Child move made us move %" GES_FORMAT, + GES_ARGS (container)); g_object_notify (G_OBJECT (container), "start"); } @@ -560,6 +564,9 @@ _child_start_changed_cb (GESTimelineElement * child, default: break; } + + if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE)) + container->children_control_mode = pmode; } static void @@ -577,7 +584,8 @@ _child_inpoint_changed_cb (GESTimelineElement * child, map = g_hash_table_lookup (priv->mappings, child); g_assert (map); - if (container->children_control_mode == GES_CHILDREN_UPDATE_OFFSETS) { + if (container->children_control_mode == GES_CHILDREN_UPDATE_OFFSETS + || ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE)) { map->inpoint_offset = _START (container) - _START (child); return; @@ -599,10 +607,14 @@ _child_duration_changed_cb (GESTimelineElement * child, GstClockTime end = 0; GESContainerPrivate *priv = container->priv; GESTimelineElement *element = GES_TIMELINE_ELEMENT (container); + GESChildrenControlMode pmode = container->children_control_mode; if (container->children_control_mode == GES_CHILDREN_IGNORE_NOTIFIES) return; + if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE)) + container->children_control_mode = GES_CHILDREN_UPDATE_ALL_VALUES; + map = g_hash_table_lookup (priv->mappings, child); g_assert (map); @@ -632,6 +644,9 @@ _child_duration_changed_cb (GESTimelineElement * child, default: break; } + + if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE)) + container->children_control_mode = pmode; } /**************************************************** diff --git a/ges/ges-group.c b/ges/ges-group.c index c50a8a47d7..44c29bd8a7 100644 --- a/ges/ges-group.c +++ b/ges/ges-group.c @@ -255,11 +255,7 @@ _child_group_priority_changed (GESTimelineElement * child, static gboolean _trim (GESTimelineElement * group, GstClockTime start) { - GList *tmp; - GstClockTime last_child_end = 0, oldstart = _START (group); - GESContainer *container = GES_CONTAINER (group); GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (group); - gboolean ret = TRUE, expending = (start < _START (group)); if (timeline == NULL) { GST_DEBUG ("Not in a timeline yet"); @@ -267,40 +263,9 @@ _trim (GESTimelineElement * group, GstClockTime start) return FALSE; } - container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; - for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) { - GESTimelineElement *child = tmp->data; - - if (expending) { - /* If the start is bigger, we do not touch it (in case we are expending) */ - if (_START (child) > oldstart) { - GST_DEBUG_OBJECT (child, "Skipping as not at begining of the group"); - continue; - } - - ret &= ges_timeline_element_trim (child, start); - } else { - if (start > _END (child)) - ret &= ges_timeline_element_trim (child, _END (child)); - else if (_START (child) < start && _DURATION (child)) - ret &= ges_timeline_element_trim (child, start); - - } - } - - for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) { - if (_DURATION (tmp->data)) - last_child_end = - MAX (GES_TIMELINE_ELEMENT_END (tmp->data), last_child_end); - } - - GES_GROUP (group)->priv->setting_value = TRUE; - _set_start0 (group, start); - _set_duration0 (group, last_child_end - start); - GES_GROUP (group)->priv->setting_value = FALSE; - container->children_control_mode = GES_CHILDREN_UPDATE; - - return ret; + return timeline_tree_trim (timeline_get_tree (timeline), group, + 0, GST_CLOCK_DIFF (start, _START (group)), GES_EDGE_START, + ges_timeline_get_snapping_distance (timeline)); } static gboolean @@ -309,38 +274,44 @@ _set_priority (GESTimelineElement * element, guint32 priority) GList *tmp, *layers; gint diff = priority - _PRIORITY (element); GESContainer *container = GES_CONTAINER (element); + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (element); if (GES_GROUP (element)->priv->setting_value == TRUE) return TRUE; container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; + layers = GES_TIMELINE_ELEMENT_TIMELINE (element) ? GES_TIMELINE_ELEMENT_TIMELINE (element)->layers : NULL; - if (layers == NULL) { GST_WARNING_OBJECT (element, "Not any layer in the timeline, not doing" "anything, timeline: %" GST_PTR_FORMAT, GES_TIMELINE_ELEMENT_TIMELINE (element)); return FALSE; - } else if (priority + GES_CONTAINER_HEIGHT (container) - 1 > - g_list_length (layers)) { - GST_WARNING_OBJECT (container, "Trying to move to a layer outside of" - "the timeline layers"); - return FALSE; } for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { GESTimelineElement *child = tmp->data; - if (child != container->initiated_move) { + if (child != container->initiated_move + || ELEMENT_FLAG_IS_SET (container, GES_TIMELINE_ELEMENT_SET_SIMPLE)) { if (GES_IS_CLIP (child)) { guint32 layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child) + diff; + GESLayer *layer = + g_list_nth_data (GES_TIMELINE_GET_LAYERS (timeline), layer_prio); - GST_DEBUG_OBJECT (child, "moving from layer: %i to %i", - GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child), layer_prio); - ges_clip_move_to_layer (GES_CLIP (child), - g_list_nth_data (layers, layer_prio)); + if (layer == NULL) { + do { + layer = ges_timeline_append_layer (timeline); + } while (ges_layer_get_priority (layer) < layer_prio); + } + + GST_DEBUG ("%" GES_FORMAT "moving from layer: %i to %i", + GES_ARGS (child), GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child), + layer_prio); + ges_clip_move_to_layer (GES_CLIP (child), g_list_nth_data (layers, + layer_prio)); } else if (GES_IS_GROUP (child)) { GST_DEBUG_OBJECT (child, "moving from %i to %i", _PRIORITY (child), diff + _PRIORITY (child)); @@ -358,24 +329,31 @@ _set_start (GESTimelineElement * element, GstClockTime start) { GList *tmp; gint64 diff = start - _START (element); + GESTimeline *timeline; GESContainer *container = GES_CONTAINER (element); + GESTimelineElement *toplevel = + ges_timeline_element_get_toplevel_parent (element);; + gst_object_unref (toplevel); if (GES_GROUP (element)->priv->setting_value == TRUE) /* Let GESContainer update itself */ return GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_start (element, start); + if (ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE) || + ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) { + container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; + for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) + _set_start0 (tmp->data, _START (tmp->data) + diff); + container->children_control_mode = GES_CHILDREN_UPDATE; - container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; - for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { - GESTimelineElement *child = (GESTimelineElement *) tmp->data; - - if (child != container->initiated_move && - (_END (child) > _START (element) || _END (child) > start)) { - _set_start0 (child, _START (child) + diff); - } + return TRUE; } - container->children_control_mode = GES_CHILDREN_UPDATE; + + timeline = GES_TIMELINE_ELEMENT_TIMELINE (element); + if (timeline) + return ges_timeline_move_object_simple (timeline, element, NULL, + GES_EDGE_NONE, start); return TRUE; } @@ -399,6 +377,12 @@ _set_duration (GESTimelineElement * element, GstClockTime duration) return GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_duration (element, duration); + if (element->timeline + && !timeline_tree_can_move_element (timeline_get_tree (element->timeline), + element, _PRIORITY (element), element->start, duration, NULL)) { + return FALSE; + } + if (container->initiated_move == NULL) { gboolean expending = (_DURATION (element) < duration); @@ -471,6 +455,7 @@ _child_added (GESContainer * group, GESTimelineElement * child) } priv->setting_value = TRUE; + ELEMENT_SET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE); if (first_child_start != GES_TIMELINE_ELEMENT_START (group)) { group->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; _set_start0 (GES_TIMELINE_ELEMENT (group), first_child_start); @@ -480,6 +465,7 @@ _child_added (GESContainer * group, GESTimelineElement * child) _set_duration0 (GES_TIMELINE_ELEMENT (group), last_child_end - first_child_start); } + ELEMENT_UNSET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE); priv->setting_value = FALSE; group->children_control_mode = GES_CHILDREN_UPDATE; @@ -561,12 +547,14 @@ _child_removed (GESContainer * group, GESTimelineElement * child) } priv->setting_value = TRUE; + ELEMENT_SET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE); first_child_start = GES_TIMELINE_ELEMENT_START (children->data); if (first_child_start > GES_TIMELINE_ELEMENT_START (group)) { group->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; _set_start0 (GES_TIMELINE_ELEMENT (group), first_child_start); group->children_control_mode = GES_CHILDREN_UPDATE; } + ELEMENT_UNSET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE); priv->setting_value = FALSE; } diff --git a/ges/ges-internal.h b/ges/ges-internal.h index 51286b5284..e0c4eda9d2 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -30,6 +30,7 @@ #include "ges-asset.h" #include "ges-base-xml-formatter.h" +#include "ges-timeline-tree.h" G_BEGIN_DECLS @@ -50,7 +51,9 @@ GST_DEBUG_CATEGORY_EXTERN (_ges_debug); #define _DURATION(obj) GES_TIMELINE_ELEMENT_DURATION (obj) #define _MAXDURATION(obj) GES_TIMELINE_ELEMENT_MAX_DURATION (obj) #define _PRIORITY(obj) GES_TIMELINE_ELEMENT_PRIORITY (obj) +#ifndef _END #define _END(obj) (_START (obj) + _DURATION (obj)) +#endif #define _set_start0 ges_timeline_element_set_start #define _set_inpoint0 ges_timeline_element_set_inpoint #define _set_duration0 ges_timeline_element_set_duration @@ -60,44 +63,50 @@ GST_DEBUG_CATEGORY_EXTERN (_ges_debug); "s<%p>" \ " [ %" GST_TIME_FORMAT \ " (%" GST_TIME_FORMAT \ - ") - %" GST_TIME_FORMAT "]" + ") - %" GST_TIME_FORMAT "(%" GST_TIME_FORMAT") layer: %" G_GINT32_FORMAT "] " #define GES_TIMELINE_ELEMENT_ARGS(element) \ GES_TIMELINE_ELEMENT_NAME(element), element, \ GST_TIME_ARGS(GES_TIMELINE_ELEMENT_START(element)), \ GST_TIME_ARGS(GES_TIMELINE_ELEMENT_INPOINT(element)), \ - GST_TIME_ARGS(GES_TIMELINE_ELEMENT_DURATION(element)) + GST_TIME_ARGS(GES_TIMELINE_ELEMENT_DURATION(element)), \ + GST_TIME_ARGS(GES_TIMELINE_ELEMENT_MAX_DURATION(element)), \ + GES_TIMELINE_ELEMENT_LAYER_PRIORITY(element) + +#define GES_FORMAT GES_TIMELINE_ELEMENT_FORMAT +#define GES_ARGS GES_TIMELINE_ELEMENT_ARGS G_GNUC_INTERNAL gboolean -timeline_ripple_object (GESTimeline *timeline, GESTrackElement *obj, - GList * layers, GESEdge edge, - guint64 position); +timeline_ripple_object (GESTimeline *timeline, GESTimelineElement *obj, + gint new_layer_priority, + GList * layers, GESEdge edge, + guint64 position); G_GNUC_INTERNAL gboolean timeline_slide_object (GESTimeline *timeline, GESTrackElement *obj, GList * layers, GESEdge edge, guint64 position); G_GNUC_INTERNAL gboolean -timeline_roll_object (GESTimeline *timeline, GESTrackElement *obj, - GList * layers, GESEdge edge, guint64 position); +timeline_roll_object (GESTimeline *timeline, GESTimelineElement *obj, + GList * layers, GESEdge edge, guint64 position); G_GNUC_INTERNAL gboolean -timeline_trim_object (GESTimeline *timeline, GESTrackElement * object, - GList * layers, GESEdge edge, guint64 position); +timeline_trim_object (GESTimeline *timeline, GESTimelineElement * object, + guint32 new_layer_priority, GList * layers, GESEdge edge, + guint64 position); G_GNUC_INTERNAL gboolean ges_timeline_trim_object_simple (GESTimeline * timeline, GESTimelineElement * obj, - GList * layers, GESEdge edge, guint64 position, gboolean snapping); + guint32 new_layer_priority, GList * layers, GESEdge edge, + guint64 position, gboolean snapping); G_GNUC_INTERNAL gboolean ges_timeline_move_object_simple (GESTimeline * timeline, GESTimelineElement * object, GList * layers, GESEdge edge, guint64 position); G_GNUC_INTERNAL gboolean -timeline_move_object (GESTimeline *timeline, GESTrackElement * object, - GList * layers, GESEdge edge, guint64 position); - -G_GNUC_INTERNAL gboolean -timeline_context_to_layer (GESTimeline *timeline, gint offset); +timeline_move_object (GESTimeline *timeline, GESTimelineElement * object, + guint32 new_layer_priority, GList * layers, GESEdge edge, + guint64 position); G_GNUC_INTERNAL void timeline_add_group (GESTimeline *timeline, @@ -121,6 +130,14 @@ gboolean timeline_remove_element (GESTimeline *timeline, GESTimelineElement *element); +G_GNUC_INTERNAL +GNode * +timeline_get_tree (GESTimeline *timeline); + +G_GNUC_INTERNAL +void +timeline_update_transition (GESTimeline *timeline); + G_GNUC_INTERNAL void timeline_fill_gaps (GESTimeline *timeline); @@ -396,9 +413,8 @@ G_GNUC_INTERNAL GESVideoTestSource * ges_video_test_source_new (void); ****************************************************/ typedef enum { - GES_CLIP_IS_SPLITTING = (1 << 0), - GES_CLIP_IS_MOVING = (1 << 1), - GES_TIMELINE_ELEMENT_SET_SIMPLE = (1 << 2), + GES_CLIP_IS_MOVING = (1 << 0), + GES_TIMELINE_ELEMENT_SET_SIMPLE = (1 << 1), } GESTimelineElementFlags; G_GNUC_INTERNAL gdouble ges_timeline_element_get_media_duration_factor(GESTimelineElement *self); @@ -406,9 +422,6 @@ G_GNUC_INTERNAL GESTimelineElement * ges_timeline_element_get_copied_from (GESTi G_GNUC_INTERNAL GESTimelineElementFlags ges_timeline_element_flags (GESTimelineElement *self); G_GNUC_INTERNAL void ges_timeline_element_set_flags (GESTimelineElement *self, GESTimelineElementFlags flags); -/* FIXME: Provide a clean way to get layer prio generically */ -G_GNUC_INTERNAL gint32 _layer_priority (GESTimelineElement * element); - #define ELEMENT_FLAGS(obj) (ges_timeline_element_flags (GES_TIMELINE_ELEMENT(obj))) #define ELEMENT_SET_FLAG(obj,flag) (ges_timeline_element_set_flags(GES_TIMELINE_ELEMENT(obj), (ELEMENT_FLAGS(obj) | (flag)))) #define ELEMENT_UNSET_FLAG(obj,flag) (ges_timeline_element_set_flags(GES_TIMELINE_ELEMENT(obj), (ELEMENT_FLAGS(obj) & ~(flag)))) diff --git a/ges/ges-layer.c b/ges/ges-layer.c index 204efaccfc..248c1c08a2 100644 --- a/ges/ges-layer.c +++ b/ges/ges-layer.c @@ -666,6 +666,17 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip) /* emit 'clip-added' */ 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)); + ges_layer_remove_clip_internal (layer, clip, TRUE); + return FALSE; + } + return TRUE; } diff --git a/ges/ges-source-clip.c b/ges/ges-source-clip.c index 461bb702fa..755485a40d 100644 --- a/ges/ges-source-clip.c +++ b/ges/ges-source-clip.c @@ -49,43 +49,41 @@ G_DEFINE_TYPE_WITH_PRIVATE (GESSourceClip, ges_source_clip, GES_TYPE_CLIP); static gboolean _set_start (GESTimelineElement * element, GstClockTime start) { - GList *tmp; - GESTimeline *timeline; - GESContainer *container = GES_CONTAINER (element); - GstClockTime rollback_start = GES_TIMELINE_ELEMENT_START (element); + GESTimelineElement *toplevel = + ges_timeline_element_get_toplevel_parent (element); - GST_DEBUG_OBJECT (element, "Setting children start, (initiated_move: %" - GST_PTR_FORMAT ")", container->initiated_move); - - element->start = start; - g_object_notify (G_OBJECT (element), "start"); - container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; - for (tmp = container->children; tmp; tmp = g_list_next (tmp)) { - GESTimelineElement *child = (GESTimelineElement *) tmp->data; - - if (child != container->initiated_move) { - /* Make the snapping happen if in a timeline */ - timeline = GES_TIMELINE_ELEMENT_TIMELINE (child); - if (timeline && !container->initiated_move) { - if (!ges_timeline_move_object_simple (timeline, child, NULL, - GES_EDGE_NONE, start)) { - for (tmp = container->children; tmp; tmp = g_list_next (tmp)) - ges_timeline_element_set_start (tmp->data, rollback_start); - - element->start = rollback_start; - g_object_notify (G_OBJECT (element), "start"); - container->children_control_mode = GES_CHILDREN_UPDATE; - return FALSE; - } - } - - _set_start0 (GES_TIMELINE_ELEMENT (child), start); - } + gst_object_unref (toplevel); + if (element->timeline + && !ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE) + && !ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) { + ges_timeline_move_object_simple (element->timeline, element, NULL, + GES_EDGE_NONE, start); + return FALSE; } - container->children_control_mode = GES_CHILDREN_UPDATE; + return + GES_TIMELINE_ELEMENT_CLASS (ges_source_clip_parent_class)->set_start + (element, start); +} - return FALSE; +static gboolean +_set_duration (GESTimelineElement * element, GstClockTime duration) +{ + GESTimelineElement *toplevel = + ges_timeline_element_get_toplevel_parent (element); + + gst_object_unref (toplevel); + if (element->timeline + && !ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE) + && !ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) { + return !timeline_trim_object (element->timeline, element, + GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element), NULL, GES_EDGE_END, + element->start + duration); + } + + return + GES_TIMELINE_ELEMENT_CLASS (ges_source_clip_parent_class)->set_duration + (element, duration); } static void @@ -125,6 +123,7 @@ ges_source_clip_class_init (GESSourceClipClass * klass) object_class->finalize = ges_source_clip_finalize; element_class->set_start = _set_start; + element_class->set_duration = _set_duration; } static void diff --git a/ges/ges-timeline-element.c b/ges/ges-timeline-element.c index 040a7f90b6..39c65b726e 100644 --- a/ges/ges-timeline-element.c +++ b/ges/ges-timeline-element.c @@ -694,6 +694,9 @@ ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start) g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + if (self->start == start) + return TRUE; + klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); GST_DEBUG_OBJECT (self, "current start: %" GST_TIME_FORMAT diff --git a/ges/ges-timeline-tree.c b/ges/ges-timeline-tree.c new file mode 100644 index 0000000000..bc9a608c55 --- /dev/null +++ b/ges/ges-timeline-tree.c @@ -0,0 +1,1256 @@ +/* GStreamer Editing Services + * Copyright (C) 2019 Igalia S.L + * Author: 2019 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ges-timeline-tree.h" +#include "ges-internal.h" + +GST_DEBUG_CATEGORY_STATIC (tree_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT tree_debug + +#define ELEMENT_EDGE_VALUE(e, edge) ((edge == GES_EDGE_END) ? ((GstClockTimeDiff) _END (e)) : ((GstClockTimeDiff) _START (e))) +typedef struct +{ + GstClockTime distance; + gboolean on_end_only; + gboolean on_start_only; + + GESEdge edge; + GESTimelineElement *element; + + GESTimelineElement *moving_element; + GESEdge moving_edge; + GstClockTimeDiff diff; +} SnappingData; + +/* *INDENT-OFF* */ +struct _TreeIterationData +{ + GNode *root; + gboolean res; + + GstClockTimeDiff start_diff; + GstClockTimeDiff inpoint_diff; + GstClockTimeDiff duration_diff; + gint64 priority_diff; + + /* The element we are visiting */ + GESTimelineElement *element; + + /* All the TrackElement currently moving */ + GList *movings; + + /* Elements overlaping on the start/end of @element */ + GESTimelineElement *overlaping_on_start; + GESTimelineElement *overlaping_on_end; + + /* Timestamp after which elements will be rippled */ + GstClockTime ripple_time; + + SnappingData *snapping; + + /* The edge being trimmed or rippled */ + GESEdge edge; + GHashTable *moved_clips; + + GList *neighbours; +} tree_iteration_data_init = { + .root = NULL, + .res = TRUE, + .start_diff = 0, + .inpoint_diff = 0, + .duration_diff = 0, + .priority_diff = 0, + .element = NULL, + .movings = NULL, + .overlaping_on_start = NULL, + .overlaping_on_end = NULL, + .ripple_time = GST_CLOCK_TIME_NONE, + .snapping = NULL, + .edge = GES_EDGE_NONE, + .moved_clips = NULL, + .neighbours = NULL, +}; +/* *INDENT-ON* */ + +typedef struct _TreeIterationData TreeIterationData; + +static void +clean_iteration_data (TreeIterationData * data) +{ + g_list_free (data->neighbours); + g_list_free (data->movings); + if (data->moved_clips) + g_hash_table_unref (data->moved_clips); +} + +void +timeline_tree_init_debug (void) +{ + GST_DEBUG_CATEGORY_INIT (tree_debug, "gestree", + GST_DEBUG_FG_YELLOW, "timeline tree"); +} + + +static gboolean +print_node (GNode * node, gpointer unused_data) +{ + if (G_NODE_IS_ROOT (node)) { + g_print ("Timeline: %p\n", node->data); + return FALSE; + } + + g_print ("%*c- %" GES_FORMAT " - layer %" G_GINT32_FORMAT "\n", + 2 * g_node_depth (node), ' ', GES_ARGS (node->data), + ges_timeline_element_get_layer_priority (node->data)); + + return FALSE; +} + +void +timeline_tree_debug (GNode * root) +{ + g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + (GNodeTraverseFunc) print_node, NULL); +} + +static inline GESTimelineElement * +get_toplevel_container (gpointer element) +{ + GESTimelineElement *ret = + ges_timeline_element_get_toplevel_parent ((GESTimelineElement + *) (element)); + + /* We own a ref to the elements ourself */ + gst_object_unref (ret); + return ret; +} + +static gboolean +timeline_tree_can_move_element_internal (GNode * root, + GESTimelineElement * element, + gint64 priority, + GstClockTimeDiff start, + GstClockTimeDiff inpoint, + GstClockTimeDiff duration, + GList * moving_track_elements, + GstClockTime ripple_time, SnappingData * snapping, GESEdge edge); + +static GNode * +find_node (GNode * root, gpointer element) +{ + return g_node_find (root, G_IN_ORDER, G_TRAVERSE_ALL, element); +} + +static void +timeline_element_parent_cb (GESTimelineElement * child, GParamSpec * arg + G_GNUC_UNUSED, GNode * root) +{ + GNode *new_parent_node = NULL, *node = find_node (root, child); + + if (child->parent) + new_parent_node = find_node (root, child->parent); + + if (!new_parent_node) + new_parent_node = root; + + g_node_unlink (node); + g_node_prepend (new_parent_node, node); +} + +void +timeline_tree_track_element (GNode * root, GESTimelineElement * element) +{ + GNode *node; + GNode *parent; + GESTimelineElement *toplevel; + + if (find_node (root, element)) { + return; + } + + g_signal_connect (element, "notify::parent", + G_CALLBACK (timeline_element_parent_cb), root); + + toplevel = get_toplevel_container (element); + if (toplevel == element) { + GST_DEBUG ("Tracking toplevel element %" GES_FORMAT, GES_ARGS (element)); + + node = g_node_prepend_data (root, element); + } else { + parent = find_node (root, element->parent); + GST_LOG ("%" GES_FORMAT "parent is %" GES_FORMAT, GES_ARGS (element), + GES_ARGS (element->parent)); + g_assert (parent); + node = g_node_prepend_data (parent, element); + } + + if (GES_IS_CONTAINER (element)) { + GList *tmp; + + for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { + GNode *child_node = find_node (root, tmp->data); + + if (child_node) { + g_node_unlink (child_node); + g_node_prepend (node, child_node); + } else { + timeline_tree_track_element (root, tmp->data); + } + } + } + + timeline_update_duration (root->data); +} + +void +timeline_tree_stop_tracking_element (GNode * root, GESTimelineElement * element) +{ + GNode *node = find_node (root, element); + + node = find_node (root, element); + + /* Move children to the parent */ + while (node->children) { + GNode *tmp = node->children; + g_node_unlink (tmp); + g_node_prepend (node->parent, tmp); + } + + g_assert (node); + GST_DEBUG ("Stop tracking %" GES_FORMAT, GES_ARGS (element)); + g_signal_handlers_disconnect_by_func (element, timeline_element_parent_cb, + root); + + g_node_destroy (node); + timeline_update_duration (root->data); +} + +static inline gboolean +check_can_move_to_layer (GESTimelineElement * element, + gint layer_priority_offset) +{ + return (((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) - + layer_priority_offset) >= 0); +} + +/* *INDENT-OFF* */ +#define CHECK_AND_SNAP(diff_val,moving_edge_,edge_) \ +if (snapping->distance >= ABS(diff_val) && ABS(diff_val) <= ABS(snapping->diff)) { \ + snapping->element = element; \ + snapping->edge = edge_; \ + snapping->moving_element = moving_elem; \ + snapping->moving_edge = moving_edge_; \ + snapping->diff = (diff_val); \ + GST_LOG("Snapping %" GES_FORMAT "with %" GES_FORMAT " - diff: %" G_GINT64_FORMAT "", GES_ARGS (moving_elem), GES_ARGS(element), (diff_val)); \ +} + +static void +check_snapping (GESTimelineElement * element, GESTimelineElement * moving_elem, + SnappingData * snapping, GstClockTime start, GstClockTime end, + GstClockTime moving_start, GstClockTime moving_end) +{ + GstClockTimeDiff snap_end_end_diff; + GstClockTimeDiff snap_end_start_diff; + + if (element == moving_elem) + return; + + if (!snapping || ( + GES_IS_CLIP (element->parent) && element->parent == moving_elem->parent)) + return; + + snap_end_end_diff = (GstClockTimeDiff) moving_end - (GstClockTimeDiff) end; + snap_end_start_diff = (GstClockTimeDiff) moving_end - (GstClockTimeDiff) start; + + GST_DEBUG("Moving [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT "] element [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT "]", moving_start, moving_end, start, end); + /* Prefer snapping end */ + if (!snapping->on_start_only) { + CHECK_AND_SNAP(snap_end_end_diff, GES_EDGE_END, GES_EDGE_END) + else CHECK_AND_SNAP(snap_end_start_diff, GES_EDGE_END, GES_EDGE_START) + } + + if (!snapping->on_end_only) { + GstClockTimeDiff snap_start_end_diff = GST_CLOCK_DIFF(end, moving_start); + GstClockTimeDiff snap_start_start_diff = GST_CLOCK_DIFF(start, moving_start); + + CHECK_AND_SNAP(snap_start_end_diff, GES_EDGE_START, GES_EDGE_END) + else CHECK_AND_SNAP(snap_start_start_diff, GES_EDGE_START, GES_EDGE_START) + } +} +#undef CHECK_AND_SNAP +/* *INDENT-ON* */ + +static gboolean +check_track_elements_overlaps_and_values (GNode * node, + TreeIterationData * data) +{ + GESTimelineElement *e = (GESTimelineElement *) node->data; + GstClockTimeDiff moving_start, moving_end, start, inpoint, end, duration; + gint64 priority = ((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (e)); + gint64 moving_priority = + (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (data->element) - + data->priority_diff; + + gboolean can_overlap = e != data->element, in_movings, rippling, moving; + + if (!GES_IS_SOURCE (e)) + return FALSE; + + in_movings = ! !g_list_find (data->movings, e); + rippling = e != data->element && !in_movings + && (GST_CLOCK_TIME_IS_VALID (data->ripple_time) + && e->start >= data->ripple_time); + moving = in_movings || rippling || e == data->element; + + start = (GstClockTimeDiff) e->start; + inpoint = (GstClockTimeDiff) e->inpoint; + end = (GstClockTimeDiff) e->start + e->duration; + duration = e->duration; + moving_start = GST_CLOCK_DIFF (data->start_diff, data->element->start); + moving_end = + GST_CLOCK_DIFF (data->duration_diff, + moving_start + data->element->duration); + + if (moving) { + if (rippling) { + if (data->edge == GES_EDGE_END) { + /* Moving as rippled from the end of a previous element */ + start -= data->duration_diff; + } else + start -= data->start_diff; + } else { + start -= data->start_diff; + if (GES_TIMELINE_ELEMENT_GET_CLASS (e)->set_inpoint) + inpoint -= data->inpoint_diff; + duration -= data->duration_diff; + } + end = start + duration; + priority -= data->priority_diff; + + GST_DEBUG ("%s %" GES_FORMAT "to [%" G_GINT64_FORMAT "(%" + G_GINT64_FORMAT ") - %" G_GINT64_FORMAT "] - layer: %" G_GINT64_FORMAT, + rippling ? "Rippling" : "Moving", GES_ARGS (e), start, inpoint, + duration, priority); + } + + /* Not in the same track */ + if (ges_track_element_get_track ((GESTrackElement *) node->data) != + ges_track_element_get_track ((GESTrackElement *) data->element)) { + GST_LOG ("%" GES_FORMAT " and %" GES_FORMAT + " are not in the same track", GES_ARGS (node->data), + GES_ARGS (data->element)); + can_overlap = FALSE; + } + + /* Not in the same layer */ + if (priority != moving_priority) { + GST_LOG ("%" GST_PTR_FORMAT " and %" GST_PTR_FORMAT + " are not on the same layer (%d != %" G_GINT64_FORMAT ")", node->data, + data->element, GES_TIMELINE_ELEMENT_LAYER_PRIORITY (e), + moving_priority); + can_overlap = FALSE; + } + + if (start < 0) { + GST_INFO ("%" GES_FORMAT "start would be %" G_GINT64_FORMAT " < 0", + GES_ARGS (e), start); + goto error; + } + + if (duration < 0) { + GST_INFO ("%" GES_FORMAT "duration would be %" G_GINT64_FORMAT " < 0", + GES_ARGS (e), duration); + goto error; + } + + if (priority < 0) { + GST_INFO ("%" GES_FORMAT "priority would be %" G_GINT64_FORMAT " < 0", + GES_ARGS (e), priority); + goto error; + } + + if (inpoint < 0) { + GST_INFO ("%" GES_FORMAT " can't set inpoint %" G_GINT64_FORMAT, + GES_ARGS (e), inpoint); + goto error; + } + + if (inpoint + duration > e->maxduration) { + GST_INFO ("%" GES_FORMAT " inpoint + duration %" G_GINT64_FORMAT + " > max_duration %" G_GINT64_FORMAT, + GES_ARGS (e), inpoint + duration, e->maxduration); + goto error; + } + + if (!moving) + check_snapping (e, data->element, data->snapping, start, end, moving_start, + moving_end); + + if (!can_overlap) + return FALSE; + + if (start > moving_end || moving_start > end) { + /* They do not overlap at all */ + GST_LOG ("%" GES_FORMAT " and %" GES_FORMAT + " do not overlap at all.", + GES_ARGS (node->data), GES_ARGS (data->element)); + return FALSE; + } + + if ((moving_start <= start && moving_end >= end) || + (moving_start >= start && moving_end <= end)) { + GST_INFO ("Fully overlaped: %s<%p> [%" G_GINT64_FORMAT " - %" + G_GINT64_FORMAT "] and %s<%p> [%" G_GINT64_FORMAT " - %" G_GINT64_FORMAT + " (%" G_GINT64_FORMAT ")]", e->name, e, start, end, data->element->name, + data->element, moving_start, moving_end, data->duration_diff); + + goto error; + } + + if (moving_start < end && moving_start > start) { + GST_LOG ("Overlap start: %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT + "] and %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT " (%" + G_GINT64_FORMAT ")]", e->name, e, start, end, data->element->name, + data->element, moving_start, moving_end, data->duration_diff); + if (data->overlaping_on_start) { + GST_INFO ("Clip is overlapped by %s and %s at its start", + data->overlaping_on_start->name, e->name); + goto error; + } + + data->overlaping_on_start = node->data; + } else if (moving_end > end && end > moving_start) { + GST_LOG ("Overlap end: %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT + "] and %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT " (%" + G_GINT64_FORMAT ")]", e->name, e, start, end, data->element->name, + data->element, moving_start, moving_end, data->duration_diff); + + if (data->overlaping_on_end) { + GST_INFO ("Clip is overlapped by %s and %s at its end", + data->overlaping_on_end->name, e->name); + goto error; + } + data->overlaping_on_end = node->data; + } + + return FALSE; + +error: + data->res = FALSE; + return TRUE; +} + +static gboolean +check_can_move_children (GNode * node, TreeIterationData * data) +{ + GESTimelineElement *element = node->data; + GstClockTimeDiff start = GST_CLOCK_DIFF (data->start_diff, element->start); + GstClockTime inpoint = GST_CLOCK_DIFF (data->inpoint_diff, element->inpoint); + GstClockTime duration = + GST_CLOCK_DIFF (data->duration_diff, element->duration); + gint64 priority = + (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) - + data->priority_diff; + if (element == data->element) + return FALSE; + + data->res = + timeline_tree_can_move_element_internal (data->root, node->data, priority, + start, inpoint, duration, data->movings, data->ripple_time, + data->snapping, data->edge); + + return !data->res; +} + +static gboolean +timeline_tree_can_move_element_from_data (GNode * root, + TreeIterationData * data) +{ + GNode *node = find_node (root, data->element); + + g_assert (node); + if (G_NODE_IS_LEAF (node)) { + if (GES_IS_SOURCE (node->data)) { + g_node_traverse (root, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, + (GNodeTraverseFunc) check_track_elements_overlaps_and_values, data); + + return data->res; + } + + return TRUE; + } + + g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAFS, -1, + (GNodeTraverseFunc) check_can_move_children, data); + + return data->res; +} + +static gboolean +add_element_to_list (GNode * node, GList ** elements) +{ + *elements = g_list_prepend (*elements, node->data); + + return FALSE; +} + +static gboolean +timeline_tree_can_move_element_internal (GNode * root, + GESTimelineElement * element, gint64 priority, GstClockTimeDiff start, + GstClockTimeDiff inpoint, GstClockTimeDiff duration, + GList * moving_track_elements, GstClockTime ripple_time, + SnappingData * snapping, GESEdge edge) +{ + gboolean res; + TreeIterationData data = tree_iteration_data_init; + + data.root = root; + data.start_diff = GST_CLOCK_DIFF (start, element->start); + data.inpoint_diff = GST_CLOCK_DIFF (inpoint, element->inpoint); + data.duration_diff = GST_CLOCK_DIFF (duration, element->duration); + data.priority_diff = + (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) - priority; + data.element = element; + data.movings = g_list_copy (moving_track_elements); + data.ripple_time = ripple_time; + data.snapping = snapping; + data.edge = edge; + + if (GES_IS_SOURCE (element)) + data.priority_diff = + GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) - priority; + + res = timeline_tree_can_move_element_from_data (root, &data); + clean_iteration_data (&data); + + return res; +} + +gboolean +timeline_tree_can_move_element (GNode * root, + GESTimelineElement * element, guint32 priority, GstClockTime start, + GstClockTime duration, GList * moving_track_elements) +{ + GESTimelineElement *toplevel; + GstClockTimeDiff start_offset, duration_offset; + gint64 priority_diff; + + toplevel = get_toplevel_container (element); + if (ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE) || + ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) + return TRUE; + + start_offset = GST_CLOCK_DIFF (start, element->start); + duration_offset = GST_CLOCK_DIFF (duration, element->duration); + priority_diff = + (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (toplevel) - + (gint64) priority; + + g_node_traverse (find_node (root, toplevel), G_IN_ORDER, + G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list, + &moving_track_elements); + + return timeline_tree_can_move_element_internal (root, toplevel, + GES_TIMELINE_ELEMENT_LAYER_PRIORITY (toplevel) - priority_diff, + GST_CLOCK_DIFF (start_offset, toplevel->start), + toplevel->inpoint, + GST_CLOCK_DIFF (duration_offset, toplevel->duration), + moving_track_elements, GST_CLOCK_TIME_NONE, NULL, GES_EDGE_NONE); +} + +static void +move_to_new_layer (GESTimelineElement * elem, gint layer_priority_offset) +{ + guint32 nprio = + GES_TIMELINE_ELEMENT_LAYER_PRIORITY (elem) - layer_priority_offset; + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (elem); + + if (!layer_priority_offset) + return; + + GST_DEBUG ("%s moving from %" G_GUINT32_FORMAT " to %" G_GUINT32_FORMAT " (%" + G_GUINT32_FORMAT ")", elem->name, elem->priority, nprio, + layer_priority_offset); + if (GES_IS_CLIP (elem)) { + GESLayer *layer = ges_timeline_get_layer (timeline, nprio); + + if (layer == NULL) { + do { + layer = ges_timeline_append_layer (timeline); + } while (ges_layer_get_priority (layer) < nprio); + } else { + gst_object_unref (layer); + } + + ges_clip_move_to_layer (GES_CLIP (elem), layer); + } else if (GES_IS_GROUP (elem)) { + ges_timeline_element_set_priority (elem, nprio); + } else { + g_assert_not_reached (); + } +} + +gboolean +timeline_tree_ripple (GNode * root, gint64 layer_priority_offset, + GstClockTimeDiff offset, GESTimelineElement * rippled_element, + GESEdge edge, GstClockTime snapping_distance) +{ + GNode *node; + GHashTableIter iter; + GESTimelineElement *elem; + GstClockTimeDiff start, duration; + gboolean res = TRUE; + GHashTable *to_move = g_hash_table_new (g_direct_hash, g_direct_equal); + GList *moving_track_elements = NULL; + SnappingData snapping = { + .distance = snapping_distance, + .on_end_only = edge == GES_EDGE_END, + .on_start_only = FALSE, + .element = NULL, + .edge = GES_EDGE_NONE, + .diff = (GstClockTimeDiff) snapping_distance, + }; + gint64 new_layer_priority = + ((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (rippled_element)) - + layer_priority_offset; + GESTimelineElement *ripple_toplevel = + get_toplevel_container (rippled_element); + GstClockTimeDiff ripple_time = ELEMENT_EDGE_VALUE (rippled_element, edge); + + if (edge == GES_EDGE_END) { + if (ripple_toplevel != rippled_element) { + GST_FIXME ("Trying to ripple end %" GES_FORMAT " but in %" GES_FORMAT + " we do not know how to do that yet!", + GES_ARGS (rippled_element), GES_ARGS (ripple_toplevel)); + goto error; + } + } else { + g_node_traverse (find_node (root, ripple_toplevel), G_IN_ORDER, + G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list, + &moving_track_elements); + } + + GST_INFO ("Moving %" GES_FORMAT " with offset %" G_GINT64_FORMAT "", + GES_ARGS (ripple_toplevel), offset); + + if (edge == GES_EDGE_END) { + start = _START (rippled_element); + duration = GST_CLOCK_DIFF (offset, _DURATION (rippled_element)); + } else { + start = GST_CLOCK_DIFF (offset, _START (rippled_element)); + duration = _DURATION (rippled_element); + } + + if (!timeline_tree_can_move_element_internal (root, rippled_element, + new_layer_priority, start, rippled_element->inpoint, duration, NULL, + ripple_time, snapping_distance ? &snapping : NULL, edge)) { + goto error; + } + + if (snapping_distance) { + if (snapping.element) { + offset = + GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), + ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge)); + + if (edge == GES_EDGE_END) { + start = _START (rippled_element); + duration = GST_CLOCK_DIFF (offset, _DURATION (rippled_element)); + } else { + start = GST_CLOCK_DIFF (offset, _START (rippled_element)); + duration = _DURATION (rippled_element); + } + + GST_INFO ("Snapping on %" GES_FORMAT "%s %" G_GINT64_FORMAT "", + GES_ARGS (snapping.element), + snapping.edge == GES_EDGE_END ? "end" : "start", + ELEMENT_EDGE_VALUE (snapping.element, snapping.edge)); + if (!timeline_tree_can_move_element_internal (root, rippled_element, + new_layer_priority, start, rippled_element->inpoint, duration, + NULL, ripple_time, NULL, edge)) { + goto error; + } + } + + ges_timeline_emit_snapping (root->data, rippled_element, snapping.element, + snapping.element ? ELEMENT_EDGE_VALUE (snapping.element, + snapping.edge) : GST_CLOCK_TIME_NONE); + } + + /* Make sure we can ripple all toplevels after the rippled element */ + for (node = root->children; node; node = node->next) { + GESTimelineElement *toplevel = get_toplevel_container (node->data); + + if (GES_TIMELINE_ELEMENT_START (toplevel) < ripple_time + && (edge == GES_EDGE_END || toplevel != ripple_toplevel)) + continue; + + if (!timeline_tree_can_move_element_internal (root, node->data, + ((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (node->data)) - + layer_priority_offset, + GST_CLOCK_DIFF (offset, _START (node->data)), + _INPOINT (node->data), + _DURATION (node->data), moving_track_elements, ripple_time, NULL, + GES_EDGE_NONE)) { + goto error; + } + + if (!check_can_move_to_layer (toplevel, layer_priority_offset)) { + GST_INFO ("%" GES_FORMAT " would land in a layer with negative priority", + GES_ARGS (toplevel)); + goto error; + } + + g_hash_table_add (to_move, toplevel); + } + + if (edge == GES_EDGE_END) { + if (!check_can_move_to_layer (rippled_element, layer_priority_offset)) { + GST_INFO ("%" GES_FORMAT " would land in a layer with negative priority", + GES_ARGS (rippled_element)); + + goto error; + } + + if (duration < 0) { + GST_INFO ("Would set duration to %" G_GINT64_FORMAT " <= 0", duration); + goto error; + } + + ELEMENT_SET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE); + ges_timeline_element_set_duration (rippled_element, duration); + ELEMENT_UNSET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE); + } + + g_hash_table_iter_init (&iter, to_move); + while (g_hash_table_iter_next (&iter, (gpointer *) & elem, NULL)) { + GST_LOG ("Moving %" GES_FORMAT " to %" G_GINT64_FORMAT " - layer %" + G_GINT64_FORMAT "", GES_ARGS (elem), + GES_TIMELINE_ELEMENT_START (elem) - offset, + (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (elem) - + layer_priority_offset); + + ELEMENT_SET_FLAG (elem, GES_TIMELINE_ELEMENT_SET_SIMPLE); + ges_timeline_element_set_start (elem, + GST_CLOCK_DIFF (offset, GES_TIMELINE_ELEMENT_START (elem))); + move_to_new_layer (elem, layer_priority_offset); + ELEMENT_UNSET_FLAG (elem, GES_TIMELINE_ELEMENT_SET_SIMPLE); + } + + ELEMENT_SET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE); + if (edge == GES_EDGE_END) + move_to_new_layer (rippled_element, layer_priority_offset); + ELEMENT_UNSET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE); + + timeline_tree_create_transitions (root, ges_timeline_find_auto_transition); + timeline_update_transition (root->data); + timeline_update_duration (root->data); + +done: + g_hash_table_unref (to_move); + g_list_free (moving_track_elements); + return res; + +error: + res = FALSE; + goto done; +} + +static gboolean +check_trim_child (GNode * node, TreeIterationData * data) +{ + GESTimelineElement *e = node->data; + GstClockTimeDiff n_start = GST_CLOCK_DIFF (data->start_diff, e->start); + GstClockTimeDiff n_inpoint = GST_CLOCK_DIFF (data->inpoint_diff, e->inpoint); + GstClockTimeDiff n_duration = data->edge == GES_EDGE_END ? + GST_CLOCK_DIFF (data->duration_diff, e->duration) : + GST_CLOCK_DIFF (n_start, (GstClockTimeDiff) e->start + e->duration); + + if (!timeline_tree_can_move_element_internal (data->root, e, + (gint64) ges_timeline_element_get_layer_priority (e) - + data->priority_diff, n_start, n_inpoint, n_duration, NULL, + GST_CLOCK_TIME_NONE, data->snapping, GES_EDGE_NONE)) + goto error; + + if (GES_IS_CLIP (e->parent)) + g_hash_table_add (data->moved_clips, e->parent); + else if (GES_IS_CLIP (e)) + g_hash_table_add (data->moved_clips, e); + + return FALSE; + +error: + data->res = FALSE; + + return TRUE; +} + +static gboolean +timeline_tree_can_trim_element_internal (GNode * root, TreeIterationData * data) +{ + g_node_traverse (find_node (root, data->element), G_IN_ORDER, + G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) check_trim_child, data); + + return data->res; +} + +static void +trim_simple (GESTimelineElement * element, GstClockTimeDiff offset, + GESEdge edge) +{ + ELEMENT_SET_FLAG (element, GES_TIMELINE_ELEMENT_SET_SIMPLE); + if (edge == GES_EDGE_END) { + ges_timeline_element_set_duration (element, GST_CLOCK_DIFF (offset, + element->duration)); + } else { + ges_timeline_element_set_start (element, GST_CLOCK_DIFF (offset, + element->start)); + ges_timeline_element_set_inpoint (element, GST_CLOCK_DIFF (offset, + element->inpoint)); + ges_timeline_element_set_duration (element, element->duration + offset); + } + GST_LOG ("Trimmed %" GES_FORMAT, GES_ARGS (element)); + ELEMENT_UNSET_FLAG (element, GES_TIMELINE_ELEMENT_SET_SIMPLE); +} + +#define SET_TRIMMING_DATA(data, _edge, offset) G_STMT_START { \ + data.edge = (_edge); \ + data.start_diff = (_edge) == GES_EDGE_END ? 0 : (offset); \ + data.inpoint_diff = (_edge) == GES_EDGE_END ? 0 : (offset); \ + data.duration_diff = (_edge) == GES_EDGE_END ? (offset) : -(offset); \ +} G_STMT_END + + +gboolean +timeline_tree_trim (GNode * root, GESTimelineElement * element, + gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge, + GstClockTime snapping_distance) +{ + GHashTableIter iter; + gboolean res = TRUE; + GESTimelineElement *elem; + gint64 new_layer_priority = + ((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element)) - + layer_priority_offset; + SnappingData snapping = { + .distance = snapping_distance, + .on_end_only = edge == GES_EDGE_END, + .on_start_only = edge != GES_EDGE_END, + .element = NULL, + .edge = GES_EDGE_NONE, + .diff = (GstClockTimeDiff) snapping_distance, + }; + TreeIterationData data = tree_iteration_data_init; + + data.root = root; + data.element = element; + data.priority_diff = + (gint64) ges_timeline_element_get_layer_priority (element) - + new_layer_priority; + data.snapping = snapping_distance ? &snapping : NULL; + data.moved_clips = g_hash_table_new (g_direct_hash, g_direct_equal); + + SET_TRIMMING_DATA (data, edge, offset); + GST_INFO ("%" GES_FORMAT " trimming %s with offset %" G_GINT64_FORMAT "", + GES_ARGS (element), edge == GES_EDGE_END ? "end" : "start", offset); + g_node_traverse (find_node (root, element), G_IN_ORDER, + G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list, + &data.movings); + + if (!timeline_tree_can_trim_element_internal (root, &data)) { + GST_INFO ("Can not trim object."); + goto error; + } + + if (snapping_distance) { + if (snapping.element) { + offset = + GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), + ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge)); + + GST_INFO ("Snapping on %" GES_FORMAT "%s %" G_GINT64_FORMAT + " -- offset: %" G_GINT64_FORMAT "", GES_ARGS (snapping.element), + snapping.edge == GES_EDGE_END ? "end" : "start", + ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), offset); + } + + ges_timeline_emit_snapping (root->data, element, + snapping.element, + snapping.element ? ELEMENT_EDGE_VALUE (snapping.element, + snapping.edge) : GST_CLOCK_TIME_NONE); + } + + g_hash_table_iter_init (&iter, data.moved_clips); + while (g_hash_table_iter_next (&iter, (gpointer *) & elem, NULL)) + trim_simple (elem, offset, edge); + + timeline_tree_create_transitions (root, ges_timeline_find_auto_transition); + timeline_update_transition (root->data); + timeline_update_duration (root->data); + + return TRUE; + +done: + clean_iteration_data (&data); + return res; + +error: + res = FALSE; + goto done; +} + +gboolean +timeline_tree_move (GNode * root, GESTimelineElement * element, + gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge, + GstClockTime snapping_distance) +{ + gboolean res = TRUE; + GESTimelineElement *toplevel = get_toplevel_container (element); + TreeIterationData data = tree_iteration_data_init; + SnappingData snapping = { + .distance = snapping_distance, + .on_end_only = edge == GES_EDGE_END, + .on_start_only = edge == GES_EDGE_END, + .element = NULL, + .edge = GES_EDGE_NONE, + .diff = (GstClockTimeDiff) snapping_distance, + }; + + data.root = root; + data.element = edge == GES_EDGE_END ? element : toplevel; + data.edge = edge; + data.priority_diff = layer_priority_offset; + data.snapping = snapping_distance ? &snapping : NULL; + data.start_diff = edge == GES_EDGE_END ? 0 : offset; + data.duration_diff = edge == GES_EDGE_END ? offset : 0; + + GST_INFO ("%" GES_FORMAT + " moving %s with offset %" G_GINT64_FORMAT ", (snaping distance: %" + G_GINT64_FORMAT ")", GES_ARGS (element), + edge == GES_EDGE_END ? "end" : "start", offset, snapping_distance); + g_node_traverse (find_node (root, data.element), G_IN_ORDER, + G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list, + &data.movings); + + if (!timeline_tree_can_move_element_from_data (root, &data)) { + GST_INFO ("Can not move object."); + return FALSE; + } + + if (snapping_distance) { + if (snapping.element) { + gint64 noffset = + GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), + ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge)); + + GST_INFO ("Snapping %" GES_FORMAT " (%s) with %" GES_FORMAT + "%s %" G_GINT64_FORMAT " -- offset: %" G_GINT64_FORMAT + " (previous offset: %" G_GINT64_FORMAT ")", + GES_ARGS (snapping.moving_element), + snapping.moving_edge == GES_EDGE_END ? "end" : "start", + GES_ARGS (snapping.element), + snapping.edge == GES_EDGE_END ? "end" : "start", + ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), noffset, + offset); + offset = noffset; + data.start_diff = edge == GES_EDGE_END ? 0 : offset; + data.duration_diff = edge == GES_EDGE_END ? offset : 0; + data.snapping = NULL; + if (!timeline_tree_can_move_element_from_data (root, &data)) { + GST_INFO ("Can not move object."); + goto error; + } + } + + ges_timeline_emit_snapping (root->data, element, + snapping.element, + snapping.element ? ELEMENT_EDGE_VALUE (snapping.element, + snapping.edge) : GST_CLOCK_TIME_NONE); + } + + if (!check_can_move_to_layer (toplevel, layer_priority_offset)) { + GST_INFO ("%" GES_FORMAT " would land in a layer with negative priority", + GES_ARGS (toplevel)); + goto error; + } + + ELEMENT_SET_FLAG (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE); + if (edge == GES_EDGE_END) + ges_timeline_element_set_duration (element, GST_CLOCK_DIFF (offset, + element->duration)); + else + ges_timeline_element_set_start (toplevel, GST_CLOCK_DIFF (offset, + toplevel->start)); + move_to_new_layer (toplevel, layer_priority_offset); + ELEMENT_UNSET_FLAG (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE); + + timeline_tree_create_transitions (root, ges_timeline_find_auto_transition); + timeline_update_transition (root->data); + timeline_update_duration (root->data); + + GST_LOG ("Moved %" GES_FORMAT, GES_ARGS (element)); + + return res; + +error: + return FALSE; +} + +static gboolean +find_neighbour (GNode * node, TreeIterationData * data) +{ + gboolean in_same_track = FALSE; + GList *tmp; + + if (!GES_IS_SOURCE (node->data)) { + return FALSE; + } + + + for (tmp = GES_CONTAINER_CHILDREN (data->element); tmp; tmp = tmp->next) { + if (tmp->data == node->data) + return FALSE; + + if (ges_track_element_get_track (node->data) == + ges_track_element_get_track (tmp->data)) { + in_same_track = TRUE; + } + } + + if (!in_same_track) { + return FALSE; + } + + if (ELEMENT_EDGE_VALUE (node->data, + data->edge == GES_EDGE_START ? GES_EDGE_END : GES_EDGE_START) == + ELEMENT_EDGE_VALUE (data->element, data->edge)) { + if (!g_list_find (data->neighbours, + GES_TIMELINE_ELEMENT_PARENT (node->data))) + data->neighbours = + g_list_prepend (data->neighbours, + GES_TIMELINE_ELEMENT_PARENT (node->data)); + } + + return FALSE; +} + +gboolean +timeline_tree_roll (GNode * root, GESTimelineElement * element, + GstClockTimeDiff offset, GESEdge edge, GstClockTime snapping_distance) +{ + gboolean res = TRUE; + GList *tmp; + GESEdge neighbour_edge; + TreeIterationData data = tree_iteration_data_init; + SnappingData snapping = { + .distance = snapping_distance, + .on_end_only = edge == GES_EDGE_END, + .on_start_only = edge == GES_EDGE_END, + .element = NULL, + .edge = GES_EDGE_NONE, + .moving_edge = GES_EDGE_NONE, + .diff = (GstClockTimeDiff) snapping_distance, + }; + + data.root = root; + data.element = element; + data.edge = edge; + data.snapping = snapping_distance ? &snapping : NULL; + data.start_diff = edge == GES_EDGE_END ? 0 : offset; + data.inpoint_diff = edge == GES_EDGE_END ? 0 : offset; + data.duration_diff = edge == GES_EDGE_END ? offset : -offset; + data.ripple_time = GST_CLOCK_TIME_NONE; + neighbour_edge = data.edge == GES_EDGE_END ? GES_EDGE_START : GES_EDGE_END; + + SET_TRIMMING_DATA (data, edge, offset); + g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1, + (GNodeTraverseFunc) find_neighbour, &data); + + if (data.neighbours == NULL) { + GST_INFO ("%s doesn't have any direct neighbour on edge %s", + element->name, ges_edge_name (edge)); + + return timeline_tree_trim (root, element, 0, offset, edge, + snapping_distance); + } + + GST_INFO ("Trimming %" GES_FORMAT " %s to %" G_GINT64_FORMAT "", + GES_ARGS (data.element), ges_edge_name (edge), offset); + + if (!timeline_tree_can_move_element_from_data (root, &data)) + goto error; + + + if (snapping_distance) { + if (snapping.element) { + gint64 noffset = + GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), + ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge)); + + GST_INFO ("Snapping %" GES_FORMAT " (%s) with %" GES_FORMAT + "%s %" G_GINT64_FORMAT " -- offset: %" G_GINT64_FORMAT + " (previous offset: %" G_GINT64_FORMAT ")", + GES_ARGS (snapping.moving_element), + snapping.moving_edge == GES_EDGE_END ? "end" : "start", + GES_ARGS (snapping.element), + snapping.edge == GES_EDGE_END ? "end" : "start", + ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), noffset, + offset); + offset = noffset; + + SET_TRIMMING_DATA (data, edge, offset); + + if (!timeline_tree_can_move_element_from_data (root, &data)) { + GST_INFO ("Can not move object."); + goto error; + } + } + } + + if (snapping_distance && snapping.element) { + ges_timeline_emit_snapping (root->data, element, + snapping.element, + snapping.element ? ELEMENT_EDGE_VALUE (snapping.element, + snapping.edge) : GST_CLOCK_TIME_NONE); + } + + data.snapping = NULL; + SET_TRIMMING_DATA (data, neighbour_edge, offset); + for (tmp = data.neighbours; tmp; tmp = tmp->next) { + data.element = tmp->data; + + GST_INFO ("Trimming %" GES_FORMAT " %s to %" G_GINT64_FORMAT "", + GES_ARGS (data.element), ges_edge_name (data.edge), offset); + if (!timeline_tree_can_move_element_from_data (root, &data)) { + GST_INFO ("Can not move object."); + goto error; + } + } + + trim_simple (element, offset, edge); + for (tmp = data.neighbours; tmp; tmp = tmp->next) + trim_simple (tmp->data, offset, data.edge); + +done: + timeline_update_duration (root->data); + return res; + +error: + res = FALSE; + goto done; +} + +static void +create_transition_if_needed (GESTimeline * timeline, GESTrackElement * prev, + GESTrackElement * next, GESTreeGetAutoTransitionFunc get_auto_transition) +{ + GstClockTime duration = _END (prev) - _START (next); + GESAutoTransition *trans = + get_auto_transition (timeline, prev, next, duration); + + if (!trans) { + GESLayer *layer = ges_timeline_get_layer (timeline, + GES_TIMELINE_ELEMENT_LAYER_PRIORITY (prev)); + gst_object_unref (layer); + + GST_INFO ("Creating transition [%" G_GINT64_FORMAT " - %" G_GINT64_FORMAT + "]", _START (next), duration); + ges_timeline_create_transition (timeline, prev, next, NULL, layer, + _START (next), duration); + } +} + +static gboolean +create_transitions (GNode * node, + GESTreeGetAutoTransitionFunc get_auto_transition) +{ + TreeIterationData data = tree_iteration_data_init; + GESTimeline *timeline; + GESLayer *layer; + + if (G_NODE_IS_ROOT (node)) + return FALSE; + + timeline = GES_TIMELINE_ELEMENT_TIMELINE (node->data); + data.element = node->data; + if (!GES_IS_SOURCE (node->data)) + return FALSE; + + if (!timeline) { + GST_INFO ("%" GES_FORMAT " not in timeline yet", GES_ARGS (node->data)); + + return FALSE; + } + + layer = + ges_timeline_get_layer (timeline, + GES_TIMELINE_ELEMENT_LAYER_PRIORITY (node->data)); + gst_object_unref (layer); + + if (!ges_layer_get_auto_transition (layer)) + return FALSE; + + g_node_traverse (g_node_get_root (node), G_IN_ORDER, G_TRAVERSE_LEAVES, -1, + (GNodeTraverseFunc) check_track_elements_overlaps_and_values, &data); + + if (data.overlaping_on_start) + create_transition_if_needed (timeline, + GES_TRACK_ELEMENT (data.overlaping_on_start), node->data, + get_auto_transition); + + if (data.overlaping_on_end) + create_transition_if_needed (timeline, node->data, + GES_TRACK_ELEMENT (data.overlaping_on_end), get_auto_transition); + + return FALSE; +} + +void +timeline_tree_create_transitions (GNode * root, + GESTreeGetAutoTransitionFunc get_auto_transition) +{ + g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1, + (GNodeTraverseFunc) create_transitions, get_auto_transition); +} + +static gboolean +compute_duration (GNode * node, GstClockTime * duration) +{ + *duration = MAX (_END (node->data), *duration); + + return FALSE; +} + +GstClockTime +timeline_tree_get_duration (GNode * root) +{ + GstClockTime duration = 0; + + if (root->children) + g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1, + (GNodeTraverseFunc) compute_duration, &duration); + + return duration; +} diff --git a/ges/ges-timeline-tree.h b/ges/ges-timeline-tree.h new file mode 100644 index 0000000000..e21c74883e --- /dev/null +++ b/ges/ges-timeline-tree.h @@ -0,0 +1,79 @@ +#ifndef __GES_TIMELINE_TREE__ +#define __GES_TIMELINE_TREE__ + +#include +#include "ges-auto-transition.h" + +void timeline_tree_track_element (GNode *root, + GESTimelineElement *element); + +void timeline_tree_stop_tracking_element (GNode *root, + GESTimelineElement *element); + +gboolean timeline_tree_can_move_element (GNode *root, + GESTimelineElement *element, + guint32 priority, + GstClockTime start, + GstClockTime duration, + GList *moving_track_elements); + +gboolean timeline_tree_ripple (GNode *root, + gint64 layer_priority_offset, + GstClockTimeDiff offset, + GESTimelineElement *rippled_element, + GESEdge moving_edge, + GstClockTime snapping_distance); + +void ges_timeline_emit_snapping (GESTimeline * timeline, + GESTimelineElement * elem1, + GESTimelineElement * elem2, + GstClockTime snap_time); + +gboolean timeline_tree_trim (GNode *root, + GESTimelineElement *element, + gint64 layer_priority_offset, + GstClockTimeDiff offset, + GESEdge edge, + GstClockTime snapping_distance); + + +gboolean timeline_tree_move (GNode *root, + GESTimelineElement *element, + gint64 layer_priority_offset, + GstClockTimeDiff offset, + GESEdge edge, + GstClockTime snapping_distance); + +gboolean timeline_tree_roll (GNode * root, + GESTimelineElement * element, + GstClockTimeDiff offset, + GESEdge edge, + GstClockTime snapping_distance); + +typedef GESAutoTransition * +(*GESTreeGetAutoTransitionFunc) (GESTimeline * timeline, + GESTrackElement * previous, + GESTrackElement * next, + GstClockTime transition_duration); + +void timeline_tree_create_transitions (GNode *root, + GESTreeGetAutoTransitionFunc get_auto_transition); + +GstClockTime timeline_tree_get_duration (GNode *root); + +void timeline_tree_debug (GNode * root); + +GESAutoTransition * +ges_timeline_create_transition (GESTimeline * timeline, GESTrackElement * previous, + GESTrackElement * next, GESClip * transition, + GESLayer * layer, guint64 start, guint64 duration); +GESAutoTransition * +ges_timeline_find_auto_transition (GESTimeline * timeline, GESTrackElement * prev, + GESTrackElement * next, GstClockTime transition_duration); + +void +timeline_update_duration (GESTimeline * timeline); + +void timeline_tree_init_debug (void); + +#endif // __GES_TIMELINE_TREE__ diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index 62b1210c80..125da3fa71 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -4,6 +4,8 @@ * 2012 Thibault Saunier * 2012 Collabora Ltd. * Author: Sebastian Dröge + * 2019 Igalia S.L + * Author: Thibault Saunier * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -48,16 +50,15 @@ #include "ges-project.h" #include "ges-container.h" #include "ges-timeline.h" +#include "ges-timeline-tree.h" #include "ges-track.h" #include "ges-layer.h" #include "ges-auto-transition.h" #include "ges.h" -typedef struct _MoveContext MoveContext; static GPtrArray *select_tracks_for_object_default (GESTimeline * timeline, GESClip * clip, GESTrackElement * tr_obj, gpointer user_data); -static inline void init_movecontext (MoveContext * mv_ctx, gboolean first_init); static void ges_extractable_interface_init (GESExtractableInterface * iface); static void ges_meta_container_interface_init (GESMetaContainerInterface * iface); @@ -86,110 +87,30 @@ GST_DEBUG_CATEGORY_STATIC (ges_timeline_debug); #define CHECK_THREAD(timeline) g_assert(timeline->priv->valid_thread == g_thread_self()) -typedef struct TrackObjIters -{ - GSequenceIter *iter_start; - GSequenceIter *iter_end; - GSequenceIter *iter_obj; - GSequenceIter *iter_by_layer; - - GESLayer *layer; - GESTrackElement *trackelement; -} TrackObjIters; - -static void -_destroy_obj_iters (TrackObjIters * iters) -{ - g_slice_free (TrackObjIters, iters); -} - -/* The move context is used for the timeline editing modes functions in order to - * + Ripple / Roll / Slide / Move / Trim - * - * The context aims at avoiding to recalculate values/objects on each call of the - * editing functions. - */ -struct _MoveContext -{ - GESClip *clip; - GESEdge edge; - GESEditMode mode; - - /* The start of the moving context */ - GstClockTime start; - - /* Ripple and Roll Objects */ - GList *moving_trackelements; - - /* We use it as a set of Clip to move between layers */ - GHashTable *toplevel_containers; - /* Min priority of the objects currently in toplevel_containers */ - guint min_move_layer; - /* Max priority of the objects currently in toplevel_containers */ - guint max_layer_prio; - - /* Never trim so duration would become < 0 */ - guint64 max_trim_pos; - - /* Never trim so inpoint + duration would change */ - guint64 min_trim_pos; - - /* fields to force/avoid new context */ - /* Set to %TRUE when the track is doing updates of track element - * properties so we don't end up always needing new move context */ - gboolean ignore_needs_ctx; - gboolean needs_move_ctx; - - /* Last snapping properties */ - GESTrackElement *last_snaped1; - GESTrackElement *last_snaped2; - GstClockTime *last_snap_ts; - - /* Priority of the layer where we are moving current clip - * -1 if not moving any clip to a new layer. */ - GESLayer *moving_to_layer; -}; - struct _GESTimelinePrivate { + GNode *tree; + /* The duration of the timeline */ gint64 duration; /* The auto-transition of the timeline */ gboolean auto_transition; - /* Use to determine that a edit action should be rolled - * back because it leads to a wrong state of the element - * position (currently only happens if 3 clips overlap) */ - gboolean needs_rollback; - gboolean rolling_back; /* Timeline edition modes and snapping management */ guint64 snapping_distance; - /* FIXME: Should we offer an API over those fields ? - * FIXME: Should other classes than subclasses of Source also - * be tracked? */ - - /* Snapping fields */ - GHashTable *by_start; /* {Source: start} */ - GHashTable *by_end; /* {Source: end} */ - GHashTable *by_object; /* {timecode: Source} */ - GHashTable *obj_iters; /* {Source: TrackObjIters} */ - GSequence *starts_ends; /* Sorted list of starts/ends */ - /* We keep 1 reference to our trackelement here */ - GSequence *tracksources; /* Source-s sorted by start/priorities */ - GRecMutex dyn_mutex; GList *priv_tracks; - /* FIXME: We should definitly offer an API over this, - * probably through a ges_layer_get_track_elements () method */ - GHashTable *by_layer; /* {layer: GSequence of TrackElement by start/priorities} */ /* Avoid sorting layers when we are actually resyncing them ourself */ gboolean resyncing_layers; GList *auto_transitions; - MoveContext movecontext; + /* Last snapping properties */ + GstClockTime last_snap_ts; + GESTrackElement *last_snaped1; + GESTrackElement *last_snaped2; /* This variable is set to %TRUE when it makes sense to update the transitions, * and %FALSE otherwize */ @@ -378,16 +299,6 @@ ges_timeline_dispose (GObject * object) g_list_free (priv->groups); g_list_free (groups); - g_hash_table_unref (priv->by_start); - g_hash_table_unref (priv->by_end); - g_hash_table_unref (priv->by_object); - g_hash_table_unref (priv->by_layer); - g_hash_table_unref (priv->obj_iters); - g_sequence_free (priv->starts_ends); - g_sequence_free (priv->tracksources); - g_list_free (priv->movecontext.moving_trackelements); - g_hash_table_unref (priv->movecontext.toplevel_containers); - g_list_free_full (priv->auto_transitions, gst_object_unref); g_hash_table_unref (priv->all_elements); @@ -401,6 +312,7 @@ ges_timeline_finalize (GObject * object) GESTimeline *tl = GES_TIMELINE (object); g_rec_mutex_clear (&tl->priv->dyn_mutex); + g_node_destroy (tl->priv->tree); G_OBJECT_CLASS (ges_timeline_parent_class)->finalize (object); } @@ -486,6 +398,7 @@ ges_timeline_class_init (GESTimelineClass * klass) GST_DEBUG_CATEGORY_INIT (ges_timeline_debug, "gestimeline", GST_DEBUG_FG_YELLOW, "ges timeline"); + timeline_tree_init_debug (); parent_class = g_type_class_peek_parent (klass); @@ -666,6 +579,7 @@ ges_timeline_init (GESTimeline * self) GESTimelinePrivate *priv = self->priv; self->priv = ges_timeline_get_instance_private (self); + self->priv->tree = g_node_new (self); priv = self->priv; self->layers = NULL; @@ -676,21 +590,9 @@ ges_timeline_init (GESTimeline * self) priv->expected_async_done = 0; priv->expected_commited = 0; - /* Move context initialization */ - init_movecontext (&self->priv->movecontext, TRUE); - priv->movecontext.ignore_needs_ctx = FALSE; + self->priv->last_snap_ts = GST_CLOCK_TIME_NONE; priv->priv_tracks = NULL; - priv->by_start = g_hash_table_new (g_direct_hash, g_direct_equal); - priv->by_end = g_hash_table_new (g_direct_hash, g_direct_equal); - priv->by_object = g_hash_table_new (g_direct_hash, g_direct_equal); - priv->by_layer = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, - (GDestroyNotify) g_sequence_free); - priv->obj_iters = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, - (GDestroyNotify) _destroy_obj_iters); - priv->starts_ends = g_sequence_new (g_free); - priv->tracksources = g_sequence_new (gst_object_unref); - priv->needs_transitions_update = TRUE; priv->all_elements = @@ -755,63 +657,22 @@ _resync_layers (GESTimeline * timeline) timeline->priv->resyncing_layers = FALSE; } -static void +void timeline_update_duration (GESTimeline * timeline) { - GstClockTime *cduration; - GSequenceIter *it = g_sequence_get_end_iter (timeline->priv->starts_ends); + GstClockTime duration = timeline_tree_get_duration (timeline->priv->tree); - it = g_sequence_iter_prev (it); - - if (g_sequence_iter_is_end (it)) { - timeline->priv->duration = 0; - g_object_notify_by_pspec (G_OBJECT (timeline), properties[PROP_DURATION]); - return; - } - - cduration = g_sequence_get (it); - - if (cduration && timeline->priv->duration != *cduration) { + if (timeline->priv->duration != duration) { GST_DEBUG ("track duration : %" GST_TIME_FORMAT " current : %" - GST_TIME_FORMAT, GST_TIME_ARGS (*cduration), + GST_TIME_FORMAT, GST_TIME_ARGS (duration), GST_TIME_ARGS (timeline->priv->duration)); - timeline->priv->duration = *cduration; + timeline->priv->duration = duration; g_object_notify_by_pspec (G_OBJECT (timeline), properties[PROP_DURATION]); } } -static gint -find_layer_by_prio (GESLayer * a, gpointer pprio) -{ - gint prio = GPOINTER_TO_INT (pprio), lprio = ges_layer_get_priority (a); - - if (lprio < prio) - return -1; - if (lprio > prio) - return 1; - return 0; -} - -static void -sort_track_elements (GESTimeline * timeline, TrackObjIters * iters) -{ - g_sequence_sort_changed (iters->iter_obj, - (GCompareDataFunc) element_start_compare, NULL); -} - -static gint -compare_uint64 (guint64 * a, guint64 * b, gpointer user_data) -{ - if (*a > *b) - return 1; - else if (*a == *b) - return 0; - else - return -1; -} - static gint custom_find_track (TrackPrivate * tr_priv, GESTrack * track) { @@ -820,71 +681,26 @@ custom_find_track (TrackPrivate * tr_priv, GESTrack * track) return -1; } -static inline void -sort_starts_ends_end (GESTimeline * timeline, TrackObjIters * iters) -{ - GESTimelineElement *obj = GES_TIMELINE_ELEMENT (iters->trackelement); - GESTimelinePrivate *priv = timeline->priv; - guint64 *end = g_hash_table_lookup (priv->by_end, obj); - - *end = _START (obj) + _DURATION (obj); - - g_sequence_sort_changed (iters->iter_end, (GCompareDataFunc) compare_uint64, - NULL); - timeline_update_duration (timeline); -} - -static inline void -sort_starts_ends_start (GESTimeline * timeline, TrackObjIters * iters) -{ - GESTimelineElement *obj = GES_TIMELINE_ELEMENT (iters->trackelement); - GESTimelinePrivate *priv = timeline->priv; - guint64 *start = g_hash_table_lookup (priv->by_start, obj); - - *start = _START (obj); - - g_sequence_sort_changed (iters->iter_start, - (GCompareDataFunc) compare_uint64, NULL); - timeline_update_duration (timeline); -} - static void _destroy_auto_transition_cb (GESAutoTransition * auto_transition, GESTimeline * timeline) { GESTimelinePrivate *priv = timeline->priv; - MoveContext *mv_ctx = &timeline->priv->movecontext; GESClip *transition = auto_transition->transition_clip; GESLayer *layer = ges_clip_get_layer (transition); - GESContainer *toplevel_prev = - get_toplevel_container (auto_transition->previous_clip), *toplevel_next = - get_toplevel_container (auto_transition->next_clip); - - if (g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel_prev) && - g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel_next)) { - GESLayer *nlayer = mv_ctx->moving_to_layer; - - if (!nlayer) - return; - - ges_clip_move_to_layer (transition, nlayer); - - return; - } ges_layer_remove_clip (layer, transition); g_signal_handlers_disconnect_by_func (auto_transition, _destroy_auto_transition_cb, timeline); - priv->auto_transitions = g_list_remove (priv->auto_transitions, auto_transition); gst_object_unref (auto_transition); } -static GESAutoTransition * -create_transition (GESTimeline * timeline, GESTrackElement * previous, - GESTrackElement * next, GESClip * transition, +GESAutoTransition * +ges_timeline_create_transition (GESTimeline * timeline, + GESTrackElement * previous, GESTrackElement * next, GESClip * transition, GESLayer * layer, guint64 start, guint64 duration) { GESAsset *asset; @@ -915,14 +731,10 @@ create_transition (GESTimeline * timeline, GESTrackElement * previous, return auto_transition; } -typedef GESAutoTransition *(*GetAutoTransitionFunc) (GESTimeline * timeline, - GESLayer * layer, GESTrack * track, GESTrackElement * previous, - GESTrackElement * next, GstClockTime transition_duration); - -static GESAutoTransition * -_find_transition_from_auto_transitions (GESTimeline * timeline, - GESLayer * layer, GESTrack * track, GESTrackElement * prev, - GESTrackElement * next, GstClockTime transition_duration) +GESAutoTransition * +ges_timeline_find_auto_transition (GESTimeline * timeline, + GESTrackElement * prev, GESTrackElement * next, + GstClockTime transition_duration) { GList *tmp; @@ -934,8 +746,7 @@ _find_transition_from_auto_transitions (GESTimeline * timeline, if (auto_trans->previous_source == prev || auto_trans->next_source == next) { if (auto_trans->previous_source != prev || auto_trans->next_source != next) { - timeline->priv->needs_rollback = TRUE; - GST_INFO_OBJECT (timeline, "Failed creating auto transition, " + GST_ERROR_OBJECT (timeline, "Failed creating auto transition, " " trying to have 3 clips overlapping, rolling back"); } @@ -948,42 +759,27 @@ _find_transition_from_auto_transitions (GESTimeline * timeline, static GESAutoTransition * _create_auto_transition_from_transitions (GESTimeline * timeline, - GESLayer * layer, GESTrack * track, GESTrackElement * prev, - GESTrackElement * next, GstClockTime transition_duration) + GESTrackElement * prev, GESTrackElement * next, + GstClockTime transition_duration) { - GSequenceIter *tmp_iter; - GSequence *by_layer_sequence; - - GESTimelinePrivate *priv = timeline->priv; + GList *tmp, *elements; + GESLayer *layer; + guint32 layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (prev); + GESTrack *track; GESAutoTransition *auto_transition = - _find_transition_from_auto_transitions (timeline, layer, track, prev, - next, transition_duration); + ges_timeline_find_auto_transition (timeline, prev, next, + transition_duration); - - if (auto_transition) { - if (timeline->priv->needs_rollback) { - GST_WARNING_OBJECT (timeline, - "Created an auto transition where we have 3 overlapping clips" - " removing it as this case is NOT allowed nor supported"); - g_signal_emit_by_name (auto_transition, "destroy-me"); - timeline->priv->needs_rollback = FALSE; - return NULL; - } + if (auto_transition) return auto_transition; - } + layer = ges_timeline_get_layer (timeline, layer_prio); + track = ges_track_element_get_track (prev); + elements = ges_track_get_elements (track); + for (tmp = elements; tmp; tmp = tmp->next) { + GESTrackElement *maybe_transition = tmp->data; - /* Try to find a transition that perfectly fits with the one that - * should be added at that place - * optimize: Use g_sequence_search instead of going over all the - * sequence */ - by_layer_sequence = g_hash_table_lookup (priv->by_layer, layer); - for (tmp_iter = g_sequence_get_begin_iter (by_layer_sequence); - tmp_iter && !g_sequence_iter_is_end (tmp_iter); - tmp_iter = g_sequence_iter_next (tmp_iter)) { - GESTrackElement *maybe_transition = g_sequence_get (tmp_iter); - - if (ges_track_element_get_track (maybe_transition) != track) + if (ges_timeline_element_get_layer_priority (tmp->data) != layer_prio) continue; if (_START (maybe_transition) > _START (next)) @@ -991,587 +787,156 @@ _create_auto_transition_from_transitions (GESTimeline * timeline, else if (_START (maybe_transition) != _START (next) || _DURATION (maybe_transition) != transition_duration) continue; - else if (GES_IS_TRANSITION (maybe_transition)) + else if (GES_IS_TRANSITION (maybe_transition)) { /* Use that transition */ /* TODO We should make sure that the transition contains only * TrackElement-s in @track and if it is not the case properly unlink the * object to use it */ - return create_transition (timeline, prev, next, + auto_transition = ges_timeline_create_transition (timeline, prev, next, GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (maybe_transition)), layer, _START (next), transition_duration); - } - return NULL; + break; + } + } + gst_object_unref (layer); + g_list_free_full (elements, gst_object_unref); + + return auto_transition; } -/* Create all transition that do not exist on @layer. - * @get_auto_transition is called to check if a particular transition exists. - * If @track is specified, we will create the transitions only for that particular - * track. */ -static void -_create_transitions_on_layer (GESTimeline * timeline, GESLayer * layer, - GESTrack * track, GESTrackElement * initiating_obj, - GetAutoTransitionFunc get_auto_transition) -{ - guint32 layer_prio; - GSequenceIter *iter; - GESAutoTransition *transition; - GESContainer *toplevel_next; - MoveContext *mv_ctx = &timeline->priv->movecontext; - GESTrack *ctrack = track; - GList *entered = NULL; /* List of TrackElement for wich we walk through the - * "start" but not the "end" in the starts_ends list */ - GESTimelinePrivate *priv = timeline->priv; - - if (!layer || !ges_layer_get_auto_transition (layer)) - return; - - layer_prio = ges_layer_get_priority (layer); - for (iter = g_sequence_get_begin_iter (priv->starts_ends); - iter && !g_sequence_iter_is_end (iter); - iter = g_sequence_iter_next (iter)) { - GList *tmp; - guint *start_or_end = g_sequence_get (iter); - GESTrackElement *next = g_hash_table_lookup (timeline->priv->by_object, - start_or_end); - GESContainer *toplevel = - get_toplevel_container (GES_TIMELINE_ELEMENT (next)); - - /* Only object that are in that layer and track */ - if (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (next) != layer_prio || - (track && track != ges_track_element_get_track (next))) - continue; - - if (track == NULL) - ctrack = ges_track_element_get_track (next); - - if (start_or_end == g_hash_table_lookup (priv->by_end, next)) { - if (initiating_obj == next) { - /* We passed the objects that initiated the research - * we are now done */ - g_list_free (entered); - return; - } - entered = g_list_remove (entered, next); - - continue; - } - - toplevel_next = get_toplevel_container (next); - for (tmp = entered; tmp; tmp = tmp->next) { - gint64 transition_duration; - GESTrackElement *prev = tmp->data; - GESContainer *toplevel_prev = get_toplevel_container (prev); - - /* If we are not in the same track, we do not create a transition */ - if (ctrack != ges_track_element_get_track (prev)) - continue; - - /* If elements are in the same toplevel element, we do not create a transition */ - if (get_toplevel_container (GES_TIMELINE_ELEMENT (prev)) == toplevel) - continue; - - /* If the element is inside a container we are moving, we do not - * create a transition */ - if (g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel_prev) && - g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel_next)) - continue; - - transition_duration = (_START (prev) + _DURATION (prev)) - _START (next); - if (transition_duration > 0 && transition_duration < _DURATION (prev) && - transition_duration < _DURATION (next)) { - transition = - get_auto_transition (timeline, layer, ctrack, prev, next, - transition_duration); - if (!transition) - create_transition (timeline, prev, next, NULL, layer, - _START (next), transition_duration); - } - } - - /* And add that object to the entered list so that it we can possibly set - * a transition on its end edge */ - entered = g_list_append (entered, next); - } -} - -/* @track_element must be a GESSource */ void -timeline_create_transitions (GESTimeline * timeline, - GESTrackElement * track_element) +ges_timeline_emit_snapping (GESTimeline * timeline, GESTimelineElement * elem1, + GESTimelineElement * elem2, GstClockTime snap_time) { - GESTrack *track; - GList *layer_node; - GESTimelinePrivate *priv = timeline->priv; - MoveContext *mv_ctx = &timeline->priv->movecontext; + GstClockTime last_snap_ts = timeline->priv->last_snap_ts; - if (!priv->needs_transitions_update) - return; - - if (mv_ctx->moving_trackelements && - GES_TIMELINE_ELEMENT_START (track_element) > mv_ctx->start) { - GST_DEBUG_OBJECT (timeline, "Not creating transition around %" - GES_TIMELINE_ELEMENT_FORMAT " as it is not the first rippled" - " element", GES_TIMELINE_ELEMENT_ARGS (track_element)); - return; - } - - track = ges_track_element_get_track (track_element); - layer_node = g_list_find_custom (timeline->layers, - GINT_TO_POINTER (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (track_element)), - (GCompareFunc) find_layer_by_prio); - - _create_transitions_on_layer (timeline, - layer_node ? layer_node->data : NULL, track, track_element, - _find_transition_from_auto_transitions); - - GST_DEBUG_OBJECT (timeline, "Done updating transitions"); -} - -/* Timeline edition functions */ -static inline void -init_movecontext (MoveContext * mv_ctx, gboolean first_init) -{ - if (G_UNLIKELY (first_init)) - mv_ctx->toplevel_containers = - g_hash_table_new (g_direct_hash, g_direct_equal); - - mv_ctx->moving_trackelements = NULL; - mv_ctx->start = G_MAXUINT64; - mv_ctx->max_trim_pos = G_MAXUINT64; - mv_ctx->min_move_layer = G_MAXUINT; - mv_ctx->max_layer_prio = 0; - mv_ctx->last_snaped1 = NULL; - mv_ctx->last_snaped2 = NULL; - mv_ctx->last_snap_ts = NULL; - mv_ctx->moving_to_layer = NULL; -} - -static inline void -clean_movecontext (MoveContext * mv_ctx) -{ - g_list_free (mv_ctx->moving_trackelements); - g_hash_table_remove_all (mv_ctx->toplevel_containers); - init_movecontext (mv_ctx, FALSE); -} - -static void -stop_tracking_track_element (GESTimeline * timeline, - GESTrackElement * trackelement) -{ - guint64 *start, *end; - TrackObjIters *iters; - GESTimelinePrivate *priv = timeline->priv; - - iters = g_hash_table_lookup (priv->obj_iters, trackelement); - if (G_LIKELY (iters->iter_by_layer)) { - g_sequence_remove (iters->iter_by_layer); - } else { - GST_WARNING_OBJECT (timeline, "TrackElement %p was in no layer", - trackelement); - } - - if (GES_IS_SOURCE (trackelement)) { - start = g_hash_table_lookup (priv->by_start, trackelement); - end = g_hash_table_lookup (priv->by_end, trackelement); - - g_hash_table_remove (priv->by_start, trackelement); - g_hash_table_remove (priv->by_end, trackelement); - g_hash_table_remove (priv->by_object, end); - g_hash_table_remove (priv->by_object, start); - g_sequence_remove (iters->iter_start); - g_sequence_remove (iters->iter_end); - g_sequence_remove (iters->iter_obj); - timeline_update_duration (timeline); - } - g_hash_table_remove (priv->obj_iters, trackelement); -} - -static void -start_tracking_track_element (GESTimeline * timeline, - GESTrackElement * trackelement) -{ - guint64 *pstart, *pend; - GSequence *by_layer_sequence; - TrackObjIters *iters; - GESTimelinePrivate *priv = timeline->priv; - - guint layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (trackelement); - GList *layer_node = g_list_find_custom (timeline->layers, - GINT_TO_POINTER (layer_prio), (GCompareFunc) find_layer_by_prio); - GESLayer *layer = layer_node ? layer_node->data : NULL; - - iters = g_slice_new0 (TrackObjIters); - - /* We add all TrackElement to obj_iters as we always follow them - * in the by_layer Sequences */ - g_hash_table_insert (priv->obj_iters, trackelement, iters); - - /* Track all objects by layer */ - if (G_UNLIKELY (layer == NULL)) { - /* We handle the case where we have TrackElement that are in no layer by not - * tracking them - * - * FIXME: Check if we should rather try to track them in some sort of - * dummy layer, or even refuse TrackElements to be added in Tracks if - * they land in no layer the timeline controls. - */ - GST_ERROR_OBJECT (timeline, "Adding a TrackElement that lands in no layer " - "we are controlling"); - } else { - by_layer_sequence = g_hash_table_lookup (priv->by_layer, layer); - iters->iter_by_layer = - g_sequence_insert_sorted (by_layer_sequence, trackelement, - (GCompareDataFunc) element_start_compare, NULL); - iters->layer = layer; - } - - if (GES_IS_SOURCE (trackelement)) { - /* Track only sources for timeline edition and snapping */ - pstart = g_malloc (sizeof (guint64)); - pend = g_malloc (sizeof (guint64)); - *pstart = _START (trackelement); - *pend = *pstart + _DURATION (trackelement); - - iters->iter_start = g_sequence_insert_sorted (priv->starts_ends, pstart, - (GCompareDataFunc) compare_uint64, NULL); - iters->iter_end = g_sequence_insert_sorted (priv->starts_ends, pend, - (GCompareDataFunc) compare_uint64, NULL); - iters->iter_obj = - g_sequence_insert_sorted (priv->tracksources, - gst_object_ref (trackelement), (GCompareDataFunc) element_start_compare, - NULL); - iters->trackelement = trackelement; - - g_hash_table_insert (priv->by_start, trackelement, pstart); - g_hash_table_insert (priv->by_object, pstart, trackelement); - g_hash_table_insert (priv->by_end, trackelement, pend); - g_hash_table_insert (priv->by_object, pend, trackelement); - - timeline->priv->movecontext.needs_move_ctx = TRUE; - - timeline_update_duration (timeline); - timeline_create_transitions (timeline, trackelement); - } -} - -static inline void -ges_timeline_emit_snappig (GESTimeline * timeline, GESTrackElement * obj1, - guint64 * timecode) -{ - GESTrackElement *obj2; - MoveContext *mv_ctx = &timeline->priv->movecontext; - GstClockTime snap_time = timecode ? *timecode : 0; - GstClockTime last_snap_ts = mv_ctx->last_snap_ts ? - *mv_ctx->last_snap_ts : GST_CLOCK_TIME_NONE; - - GST_DEBUG_OBJECT (timeline, "Distance: %" GST_TIME_FORMAT " snapping at %" - GST_TIME_FORMAT, GST_TIME_ARGS (timeline->priv->snapping_distance), - GST_TIME_ARGS (snap_time)); - - if (timecode == NULL) { - if (mv_ctx->last_snaped1 != NULL && mv_ctx->last_snaped2 != NULL) { + if (!GST_CLOCK_TIME_IS_VALID (snap_time)) { + if (priv->last_snaped1 != NULL && priv->last_snaped2 != NULL) { g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0, - mv_ctx->last_snaped1, mv_ctx->last_snaped2, last_snap_ts); - - /* We then need to recalculate the moving context */ - timeline->priv->movecontext.needs_move_ctx = TRUE; + priv->last_snaped1, priv->last_snaped2, last_snap_ts); + priv->last_snaped1 = NULL; + priv->last_snaped2 = NULL; + priv->last_snap_ts = GST_CLOCK_TIME_NONE; } return; } - obj2 = g_hash_table_lookup (timeline->priv->by_object, timecode); + g_assert (elem1 != elem2); + if (GES_IS_CLIP (elem1)) { + g_assert (GES_CONTAINER_CHILDREN (elem1)); + elem1 = GES_CONTAINER_CHILDREN (elem1)->data; + } - if (last_snap_ts != *timecode) { + if (GES_IS_CLIP (elem2)) { + g_assert (GES_CONTAINER_CHILDREN (elem2)); + elem2 = GES_CONTAINER_CHILDREN (elem2)->data; + } + + if (last_snap_ts != snap_time) { g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0, - mv_ctx->last_snaped1, mv_ctx->last_snaped2, (last_snap_ts)); + priv->last_snaped1, priv->last_snaped2, (last_snap_ts)); /* We want the snap start signal to be emited anyway */ - mv_ctx->last_snap_ts = NULL; + timeline->priv->last_snap_ts = GST_CLOCK_TIME_NONE; } - if (mv_ctx->last_snap_ts == NULL) { - - mv_ctx->last_snaped1 = obj1; - mv_ctx->last_snaped2 = obj2; - mv_ctx->last_snap_ts = timecode; + if (!GST_CLOCK_TIME_IS_VALID (timeline->priv->last_snap_ts)) { + priv->last_snaped1 = (GESTrackElement *) elem1; + priv->last_snaped2 = (GESTrackElement *) elem2; + timeline->priv->last_snap_ts = snap_time; g_signal_emit (timeline, ges_timeline_signals[SNAPING_STARTED], 0, - obj1, obj2, *timecode); - + elem1, elem2, snap_time); } + } -static GstClockTime * -ges_timeline_snap_position (GESTimeline * timeline, - GESTrackElement * trackelement, GstClockTime * current, - GstClockTime timecode, gboolean emit) +gboolean +ges_timeline_trim_object_simple (GESTimeline * timeline, + GESTimelineElement * element, guint32 new_layer_priority, + GList * layers, GESEdge edge, guint64 position, gboolean snapping) { - GESTimelinePrivate *priv = timeline->priv; - GSequenceIter *iter, *end_iter; - GESContainer *container = get_toplevel_container (trackelement); - GstClockTime *ret = NULL; - GstClockTime smallest_offset = G_MAXUINT64; - GstClockTime tmp_pos; - tmp_pos = timecode - priv->snapping_distance; - /* Rippling, not snapping with previous elements */ - if (priv->movecontext.moving_trackelements) - tmp_pos = timecode; - iter = g_sequence_search (priv->starts_ends, &tmp_pos, - (GCompareDataFunc) compare_uint64, NULL); - - tmp_pos = timecode + priv->snapping_distance; - end_iter = g_sequence_search (priv->starts_ends, &tmp_pos, - (GCompareDataFunc) compare_uint64, NULL); - - for (; iter != end_iter && !g_sequence_iter_is_end (iter); - iter = g_sequence_iter_next (iter)) { - GstClockTime *iter_tc = g_sequence_get (iter); - GESTrackElement *tmp_trackelement = - g_hash_table_lookup (priv->by_object, iter_tc); - GESContainer *tmp_container = get_toplevel_container (tmp_trackelement); - GstClockTimeDiff diff; - - if (tmp_container == container) - continue; - - if (g_hash_table_lookup (priv->movecontext.toplevel_containers, - tmp_container)) - continue; - - if (timecode > *iter_tc) - diff = timecode - *iter_tc; - else - diff = *iter_tc - timecode; - - if (diff > smallest_offset) - break; - - smallest_offset = diff; - ret = iter_tc; - } - - /* We emit the snapping signal only if we snapped with a different value - * than the current one */ - if (emit) { - GstClockTime snap_time = ret ? *ret : GST_CLOCK_TIME_NONE; - - if (!timeline->priv->needs_rollback) - ges_timeline_emit_snappig (timeline, trackelement, ret); - else - ges_timeline_emit_snappig (timeline, trackelement, NULL); - - GST_DEBUG_OBJECT (timeline, "Snaping at %" GST_TIME_FORMAT, - GST_TIME_ARGS (snap_time)); - } - - return ret; + return timeline_trim_object (timeline, element, new_layer_priority, layers, + edge, position); } -static inline GESContainer * -add_toplevel_container (MoveContext * mv_ctx, GESTrackElement * trackelement) +gboolean +timeline_ripple_object (GESTimeline * timeline, GESTimelineElement * obj, + gint new_layer_priority, GList * layers, GESEdge edge, guint64 position) { - guint layer_prio; - GESContainer *toplevel = get_toplevel_container (trackelement); + gboolean res = TRUE; + guint64 new_duration; + GstClockTimeDiff diff; - /* Avoid recalculating */ - if (!g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel)) { - if (GES_IS_CLIP (toplevel)) { - - layer_prio = ges_clip_get_layer_priority (GES_CLIP (toplevel)); - if (layer_prio == (guint32) - 1) { - GST_WARNING_OBJECT (toplevel, "Not in any layer, can not move" - " between layers"); - - return toplevel; - } - mv_ctx->min_move_layer = MIN (mv_ctx->min_move_layer, layer_prio); - mv_ctx->max_layer_prio = MAX (mv_ctx->max_layer_prio, layer_prio); - } else if GES_IS_GROUP - (toplevel) { - mv_ctx->min_move_layer = MIN (mv_ctx->min_move_layer, - _PRIORITY (toplevel)); - mv_ctx->max_layer_prio = MAX (mv_ctx->max_layer_prio, - _PRIORITY (toplevel) + GES_CONTAINER_HEIGHT (toplevel)); - } else - g_assert_not_reached (); - - mv_ctx->start = MIN (mv_ctx->start, GES_TIMELINE_ELEMENT_START (toplevel)); - g_hash_table_insert (mv_ctx->toplevel_containers, toplevel, toplevel); - - } - - return toplevel; -} - -static gboolean -ges_move_context_set_objects (GESTimeline * timeline, GESTrackElement * obj, - GESEdge edge) -{ - TrackObjIters *iters; - GESTrackElement *tmptrackelement; - guint64 start, tmpend, moving_point = _START (obj); - GSequenceIter *iter, *trackelement_iter, *tmpiter; - - MoveContext *mv_ctx = &timeline->priv->movecontext; - iters = g_hash_table_lookup (timeline->priv->obj_iters, obj); - trackelement_iter = iters->iter_obj; switch (edge) { - case GES_EDGE_START: - /* set it properly in the context of "trimming" */ - mv_ctx->max_trim_pos = 0; - mv_ctx->min_trim_pos = 0; - start = _START (obj); + case GES_EDGE_NONE: + GST_DEBUG ("Simply rippling"); + diff = GST_CLOCK_DIFF (position, _START (obj)); - if (g_sequence_iter_is_begin (trackelement_iter)) - break; + timeline->priv->needs_transitions_update = FALSE; + res = timeline_tree_ripple (timeline->priv->tree, + (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (obj) - + (gint64) new_layer_priority, diff, obj, + GES_EDGE_NONE, timeline->priv->snapping_distance); + timeline->priv->needs_transitions_update = TRUE; - /* Look for the objects */ - for (iter = g_sequence_iter_prev (trackelement_iter); - iter && !g_sequence_iter_is_end (iter); - iter = g_sequence_iter_prev (iter)) { - - tmptrackelement = GES_TRACK_ELEMENT (g_sequence_get (iter)); - tmpend = _START (tmptrackelement) + _DURATION (tmptrackelement); - - if (tmpend <= start && GES_TIMELINE_ELEMENT_PARENT (tmptrackelement) - != GES_TIMELINE_ELEMENT_PARENT (obj)) { - mv_ctx->max_trim_pos = - MAX (mv_ctx->max_trim_pos, _START (tmptrackelement)); - mv_ctx->min_trim_pos = MAX (mv_ctx->min_trim_pos, - _START (tmptrackelement) - _INPOINT (tmptrackelement)); - mv_ctx->moving_trackelements = - g_list_prepend (mv_ctx->moving_trackelements, tmptrackelement); - } - - - if (g_sequence_iter_is_begin (iter)) - break; - } break; - case GES_EDGE_END: - moving_point = _START (obj) + _DURATION (obj); - /* fall-through */ - case GES_EDGE_NONE: /* In this case only works for ripple */ - mv_ctx->max_trim_pos = G_MAXUINT64; + GST_DEBUG ("Rippling end"); - iter = trackelement_iter; - tmpiter = g_sequence_iter_prev (iter); + timeline->priv->needs_transitions_update = FALSE; + new_duration = + CLAMP (position - obj->start, 0, obj->maxduration - obj->inpoint); + res = + timeline_tree_ripple (timeline->priv->tree, + (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (obj) - + (gint64) new_layer_priority, + _DURATION (obj) - new_duration, obj, + GES_EDGE_END, timeline->priv->snapping_distance); + timeline->priv->needs_transitions_update = TRUE; - /* Make sure to get the first TimelineElement starting at - * @moving_point */ - while (tmpiter && !g_sequence_iter_is_end (tmpiter)) { - tmptrackelement = GES_TRACK_ELEMENT (g_sequence_get (iter)); - - if (GES_TIMELINE_ELEMENT_START (tmptrackelement) != moving_point) - break; - - iter = tmpiter; - tmpiter = g_sequence_iter_prev (tmpiter); - - if (g_sequence_iter_is_begin (tmpiter)) - break; - } - - /* Look for folowing objects */ - for (; iter && !g_sequence_iter_is_end (iter); - iter = g_sequence_iter_next (iter)) { - tmptrackelement = GES_TRACK_ELEMENT (g_sequence_get (iter)); - - if (_START (tmptrackelement) >= moving_point && - GES_TIMELINE_ELEMENT_PARENT (tmptrackelement) != - GES_TIMELINE_ELEMENT_PARENT (obj)) { - tmpend = _START (tmptrackelement) + _DURATION (tmptrackelement); - mv_ctx->max_trim_pos = MIN (mv_ctx->max_trim_pos, tmpend); - mv_ctx->moving_trackelements = - g_list_prepend (mv_ctx->moving_trackelements, tmptrackelement); - } - } + GST_DEBUG ("Done Rippling end"); + break; + case GES_EDGE_START: + GST_INFO ("Ripple start doesn't make sense, trimming instead"); + if (!timeline_trim_object (timeline, obj, -1, layers, edge, position)) + goto error; break; default: - GST_DEBUG ("Edge type %d no supported", edge); - return FALSE; + GST_DEBUG ("Can not ripple edge: %i", edge); + + break; } - return TRUE; + return res; + +error: + + return FALSE; +} + +gboolean +timeline_slide_object (GESTimeline * timeline, GESTrackElement * obj, + GList * layers, GESEdge edge, guint64 position) +{ + + /* FIXME implement me! */ + GST_FIXME_OBJECT (timeline, "Slide mode editing not implemented yet"); + + return FALSE; } static gboolean -ges_timeline_set_moving_context (GESTimeline * timeline, GESTrackElement * obj, - GESEditMode mode, GESEdge edge, GList * layers) +_trim_transition (GESTimeline * timeline, GESTimelineElement * element, + GESEdge edge, GstClockTime position) { - /* A TrackElement that could initiate movement for other object */ - GESTrackElement *editor_trackelement = NULL; - MoveContext *mv_ctx = &timeline->priv->movecontext; - GESClip *clip = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (obj)); - - /* Still in the same mv_ctx */ - if ((mv_ctx->clip == clip && mv_ctx->mode == mode && - mv_ctx->edge == edge && !mv_ctx->needs_move_ctx)) { - - GST_DEBUG ("Keeping the same moving mv_ctx"); - return TRUE; - } - - - GST_DEBUG_OBJECT (clip, - "Changing context:\nold: obj: %p, mode: %d, edge: %d \n" - "new: obj: %p, mode: %d, edge: %d ! Has changed %i", mv_ctx->clip, - mv_ctx->mode, mv_ctx->edge, clip, mode, edge, mv_ctx->needs_move_ctx); - - /* Make sure snapping context is reset when changing the moving context */ - ges_timeline_emit_snappig (timeline, NULL, NULL); - clean_movecontext (mv_ctx); - mv_ctx->edge = edge; - mv_ctx->mode = mode; - mv_ctx->clip = clip; - mv_ctx->needs_move_ctx = FALSE; - - /* We try to find a Source inside the Clip so we can set the - * moving context Else we just move the selected one only */ - if (GES_IS_SOURCE (obj) == FALSE) { - GList *tmp; - - for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { - if (GES_IS_SOURCE (tmp->data)) { - editor_trackelement = tmp->data; - break; - } - } - } else { - editor_trackelement = obj; - } - - if (editor_trackelement) { - switch (mode) { - case GES_EDIT_MODE_RIPPLE: - case GES_EDIT_MODE_ROLL: - if (!(ges_move_context_set_objects (timeline, editor_trackelement, - edge))) - return FALSE; - default: - break; - } - add_toplevel_container (&timeline->priv->movecontext, editor_trackelement); - } else { - /* We add the main object to the toplevel_containers set */ - add_toplevel_container (&timeline->priv->movecontext, obj); - } - - - return TRUE; -} - -static gboolean -_trim_transition (GESTimeline * timeline, GESLayer * layer, - GESTrackElement * element, GESEdge edge, GstClockTime position) -{ - GList *tmp; + GESLayer *layer = ges_timeline_get_layer (timeline, + GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element)); if (!ges_layer_get_auto_transition (layer)) goto fail; @@ -1580,8 +945,9 @@ _trim_transition (GESTimeline * timeline, GESLayer * layer, for (tmp = timeline->priv->auto_transitions; tmp; tmp = tmp->next) { GESAutoTransition *auto_transition = tmp->data; - if (auto_transition->transition == GES_TRACK_ELEMENT (element)) { - /* Trimming an auto transition mean trimming its neighboors */ + if (GES_TIMELINE_ELEMENT (auto_transition->transition) == element || + GES_TIMELINE_ELEMENT (auto_transition->transition_clip) == element) { + /* Trimming an auto transition means trimming its neighboors */ if (!auto_transition->positioning) { if (edge == GES_EDGE_END) { ges_container_edit (GES_CONTAINER (auto_transition->previous_clip), @@ -1605,627 +971,61 @@ fail: return FALSE; } + gboolean -ges_timeline_trim_object_simple (GESTimeline * timeline, - GESTimelineElement * element, GList * layers, GESEdge edge, - guint64 position, gboolean snapping) +timeline_trim_object (GESTimeline * timeline, GESTimelineElement * object, + guint32 new_layer_priority, GList * layers, GESEdge edge, guint64 position) { - guint64 start, inpoint, duration, max_duration, *snapped, *cur; - gboolean ret = TRUE; - gint64 real_dur; - GESTrackElement *track_element; - - if (GES_IS_TRANSITION (element)) { - return _trim_transition (timeline, - ges_clip_get_layer (GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (element))), - GES_TRACK_ELEMENT (element), edge, position); - } else if (GES_IS_SOURCE (element) == FALSE) { - return FALSE; + if ((GES_IS_TRANSITION (object) || GES_IS_TRANSITION_CLIP (object)) && + !ELEMENT_FLAG_IS_SET (object, GES_TIMELINE_ELEMENT_SET_SIMPLE)) { + return _trim_transition (timeline, object, edge, position); } - track_element = GES_TRACK_ELEMENT (element); - GST_DEBUG_OBJECT (track_element, "Trimming to %" GST_TIME_FORMAT - " %s snaping, edge %i", GST_TIME_ARGS (position), - snapping ? "Is" : "Not", edge); - - start = _START (track_element); - g_object_get (track_element, "max-duration", &max_duration, NULL); - - switch (edge) { - case GES_EDGE_START: - { - GESTimelineElement *toplevel; - GESChildrenControlMode old_mode; - gboolean use_inpoint; - toplevel = ges_timeline_element_get_toplevel_parent (element); - - if (position < _START (toplevel) && _START (toplevel) < _START (element)) { - GST_DEBUG_OBJECT (toplevel, "Not trimming %p as not at begining " - "of the container", element); - - gst_object_unref (toplevel); - return FALSE; - } - - old_mode = GES_CONTAINER (toplevel)->children_control_mode; - if (GES_IS_GROUP (toplevel) && old_mode == GES_CHILDREN_UPDATE) { - GST_DEBUG_OBJECT (toplevel, "Setting children udpate mode to" - " UPDDATE_ALL_VALUES so we can trim without moving the contained"); - /* The container will update its values itself according to new - * values of the children */ - GES_CONTAINER (toplevel)->children_control_mode = - GES_CHILDREN_UPDATE_ALL_VALUES; - } - - inpoint = _INPOINT (track_element); - duration = _DURATION (track_element); - - if (snapping) { - cur = g_hash_table_lookup (timeline->priv->by_start, track_element); - - snapped = ges_timeline_snap_position (timeline, track_element, cur, - position, TRUE); - if (snapped) - position = *snapped; - } - - /* Calculate new values */ - position = MIN (position, start + duration); - - use_inpoint = - GES_TIMELINE_ELEMENT_GET_CLASS (track_element)->set_inpoint ? TRUE : - FALSE; - - if (use_inpoint && inpoint + position < start) { - GST_ERROR_OBJECT (timeline, "Track element %s inpoint %" GST_TIME_FORMAT - " would be negative," - " not trimming", GES_TIMELINE_ELEMENT_NAME (track_element), - GST_TIME_ARGS (inpoint)); - gst_object_unref (toplevel); - return FALSE; - } - - inpoint = inpoint + position - start; - real_dur = _END (element) - position; - if (use_inpoint) - duration = CLAMP (real_dur, 0, max_duration > inpoint ? - max_duration - inpoint : G_MAXUINT64); - else - duration = real_dur; - - - /* If we already are at max duration or duration == 0 do no useless work */ - if ((duration == _DURATION (track_element) && - _DURATION (track_element) == _MAXDURATION (track_element)) || - (duration == 0 && _DURATION (element) == 0)) { - GST_DEBUG_OBJECT (track_element, - "Duration already == max_duration, no triming"); - gst_object_unref (toplevel); - return FALSE; - } - - timeline->priv->needs_transitions_update = FALSE; - _set_start0 (GES_TIMELINE_ELEMENT (track_element), position); - _set_inpoint0 (GES_TIMELINE_ELEMENT (track_element), inpoint); - timeline->priv->needs_transitions_update = TRUE; - - _set_duration0 (GES_TIMELINE_ELEMENT (track_element), duration); - if (GES_IS_GROUP (toplevel)) - GES_CONTAINER (toplevel)->children_control_mode = old_mode; - - gst_object_unref (toplevel); - break; - } - case GES_EDGE_END: - { - cur = g_hash_table_lookup (timeline->priv->by_end, track_element); - snapped = ges_timeline_snap_position (timeline, track_element, cur, - position, TRUE); - if (snapped) - position = *snapped; - - /* Calculate new values */ - real_dur = position - start; - duration = MAX (0, real_dur); - duration = MIN (duration, max_duration - _INPOINT (track_element)); - - if (duration == 0) { - GST_INFO_OBJECT (timeline, "Duration would be 0, not rippling"); - return FALSE; - } - - /* Not moving, avoid overhead */ - if (duration == _DURATION (track_element)) { - GST_DEBUG_OBJECT (track_element, "No change in duration"); - return TRUE; - } - - _set_duration0 (GES_TIMELINE_ELEMENT (track_element), duration); - break; - } - default: - GST_WARNING ("Can not trim with %i GESEdge", edge); - return FALSE; - } - - return ret; + return timeline_tree_trim (timeline->priv->tree, + GES_TIMELINE_ELEMENT (object), new_layer_priority > 0 ? (gint64) + ges_timeline_element_get_layer_priority (GES_TIMELINE_ELEMENT (object)) - + new_layer_priority : 0, edge == GES_EDGE_END ? GST_CLOCK_DIFF (position, + _START (object) + _DURATION (object)) : GST_CLOCK_DIFF (position, + GES_TIMELINE_ELEMENT_START (object)), edge, + timeline->priv->snapping_distance); } gboolean -timeline_ripple_object (GESTimeline * timeline, GESTrackElement * obj, +timeline_roll_object (GESTimeline * timeline, GESTimelineElement * element, GList * layers, GESEdge edge, guint64 position) { - GList *tmp, *moved_clips = NULL; - GESTrackElement *trackelement; - GESContainer *container; - guint64 duration, new_start, *snapped, *cur; - gint64 offset; - - MoveContext *mv_ctx = &timeline->priv->movecontext; - - mv_ctx->ignore_needs_ctx = TRUE; - timeline->priv->needs_rollback = FALSE; - if (!ges_timeline_set_moving_context (timeline, obj, GES_EDIT_MODE_RIPPLE, - edge, layers)) - goto error; - - switch (edge) { - case GES_EDGE_NONE: - GST_DEBUG ("Simply rippling"); - - /* We should be smart here to avoid recalculate transitions when possible */ - cur = g_hash_table_lookup (timeline->priv->by_end, obj); - snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE); - if (snapped) - position = *snapped; - - offset = position - _START (obj); - - for (tmp = mv_ctx->moving_trackelements; tmp; tmp = tmp->next) { - trackelement = GES_TRACK_ELEMENT (tmp->data); - new_start = _START (trackelement) + offset; - - container = add_toplevel_container (mv_ctx, trackelement); - /* Make sure not to move 2 times the same Clip */ - if (g_list_find (moved_clips, container) == NULL) { - _set_start0 (GES_TIMELINE_ELEMENT (trackelement), new_start); - moved_clips = g_list_prepend (moved_clips, container); - } - - } - g_list_free (moved_clips); - _set_start0 (GES_TIMELINE_ELEMENT (obj), position); - - moved_clips = NULL; - if (timeline->priv->needs_rollback && !timeline->priv->rolling_back) { - timeline->priv->rolling_back = TRUE; - for (tmp = mv_ctx->moving_trackelements; tmp; tmp = tmp->next) { - trackelement = GES_TRACK_ELEMENT (tmp->data); - new_start = _START (trackelement) - offset; - - container = add_toplevel_container (mv_ctx, trackelement); - /* Make sure not to move 2 times the same Clip */ - if (g_list_find (moved_clips, container) == NULL) { - _set_start0 (GES_TIMELINE_ELEMENT (trackelement), new_start); - moved_clips = g_list_prepend (moved_clips, container); - } - - } - g_list_free (moved_clips); - moved_clips = NULL; - _set_start0 (GES_TIMELINE_ELEMENT (obj), position - offset); - - ges_timeline_emit_snappig (timeline, obj, NULL); - mv_ctx->needs_move_ctx = TRUE; - timeline->priv->rolling_back = FALSE; - - goto error; - } - - break; - case GES_EDGE_END: - timeline->priv->needs_transitions_update = FALSE; - GST_DEBUG ("Rippling end"); - - cur = g_hash_table_lookup (timeline->priv->by_end, obj); - snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE); - if (snapped) - position = *snapped; - - duration = _DURATION (obj); - - if (!ges_timeline_trim_object_simple (timeline, - GES_TIMELINE_ELEMENT (obj), NULL, GES_EDGE_END, position, - FALSE)) { - return FALSE; - } - - offset = _DURATION (obj) - duration; - for (tmp = mv_ctx->moving_trackelements; tmp; tmp = tmp->next) { - trackelement = GES_TRACK_ELEMENT (tmp->data); - new_start = _START (trackelement) + offset; - - container = add_toplevel_container (mv_ctx, trackelement); - if (GES_IS_GROUP (container)) - container->children_control_mode = GES_CHILDREN_UPDATE_OFFSETS; - /* Make sure not to move 2 times the same Clip */ - if (g_list_find (moved_clips, container) == NULL) { - _set_start0 (GES_TIMELINE_ELEMENT (trackelement), new_start); - moved_clips = g_list_prepend (moved_clips, container); - } - if (GES_IS_GROUP (container)) - container->children_control_mode = GES_CHILDREN_UPDATE; - } - - g_list_free (moved_clips); - timeline->priv->needs_transitions_update = TRUE; - GST_DEBUG ("Done Rippling end"); - break; - case GES_EDGE_START: - GST_INFO ("Ripple start doesn't make sense, trimming instead"); - timeline->priv->movecontext.needs_move_ctx = TRUE; - if (!timeline_trim_object (timeline, obj, layers, edge, position)) - goto error; - break; - default: - GST_DEBUG ("Can not ripple edge: %i", edge); - - break; - } - - mv_ctx->ignore_needs_ctx = FALSE; - - return TRUE; - -error: - mv_ctx->ignore_needs_ctx = FALSE; - - return FALSE; + return timeline_tree_roll (timeline->priv->tree, + element, + (edge == GES_EDGE_END) ? + GST_CLOCK_DIFF (position, _END (element)) : + GST_CLOCK_DIFF (position, _START (element)), + edge, timeline->priv->snapping_distance); } gboolean -timeline_slide_object (GESTimeline * timeline, GESTrackElement * obj, - GList * layers, GESEdge edge, guint64 position) -{ - - /* FIXME implement me! */ - GST_FIXME_OBJECT (timeline, "Slide mode editing not implemented yet"); - - return FALSE; -} - -gboolean -timeline_trim_object (GESTimeline * timeline, GESTrackElement * object, - GList * layers, GESEdge edge, guint64 position) +timeline_move_object (GESTimeline * timeline, GESTimelineElement * object, + guint32 new_layer_priority, GList * layers, GESEdge edge, guint64 position) { gboolean ret = FALSE; - GstClockTime cpos; - MoveContext *mv_ctx = &timeline->priv->movecontext; + GstClockTimeDiff offset = edge == GES_EDGE_END ? + GST_CLOCK_DIFF (position, _START (object) + _DURATION (object)) : + GST_CLOCK_DIFF (position, GES_TIMELINE_ELEMENT_START (object)); - mv_ctx->ignore_needs_ctx = TRUE; - - timeline->priv->needs_rollback = FALSE; - if (!ges_timeline_set_moving_context (timeline, object, GES_EDIT_MODE_TRIM, - edge, layers)) - goto end; - - switch (edge) { - case GES_EDGE_START: - cpos = GES_TIMELINE_ELEMENT_START (object); - break; - case GES_EDGE_END: - cpos = GES_TIMELINE_ELEMENT_END (object); - break; - default: - goto end; - } - ret = ges_timeline_trim_object_simple (timeline, - GES_TIMELINE_ELEMENT (object), layers, edge, position, TRUE); - - if (timeline->priv->needs_rollback && !timeline->priv->rolling_back) { - timeline->priv->rolling_back = TRUE; - ret = FALSE; - timeline_trim_object (timeline, object, layers, edge, cpos); - ges_timeline_emit_snappig (timeline, object, NULL); - timeline->priv->rolling_back = FALSE; - } - -end: - mv_ctx->ignore_needs_ctx = FALSE; + ret = timeline_tree_move (timeline->priv->tree, + GES_TIMELINE_ELEMENT (object), new_layer_priority < 0 ? 0 : (gint64) + ges_timeline_element_get_layer_priority (GES_TIMELINE_ELEMENT (object)) - + new_layer_priority, offset, edge, timeline->priv->snapping_distance); return ret; } -gboolean -timeline_roll_object (GESTimeline * timeline, GESTrackElement * obj, - GList * layers, GESEdge edge, guint64 position) -{ - MoveContext *mv_ctx = &timeline->priv->movecontext; - guint64 start, duration, end, tmpstart, tmpduration, tmpend, *snapped, *cur; - gboolean ret = TRUE; - GList *tmp; - - mv_ctx->ignore_needs_ctx = TRUE; - - GST_DEBUG_OBJECT (obj, "Rolling object to %" GST_TIME_FORMAT, - GST_TIME_ARGS (position)); - - if (!ges_timeline_set_moving_context (timeline, obj, GES_EDIT_MODE_ROLL, - edge, layers)) - goto error; - - start = _START (obj); - duration = _DURATION (obj); - end = start + duration; - - timeline->priv->needs_transitions_update = FALSE; - switch (edge) { - case GES_EDGE_START: - - /* Avoid negative durations */ - if (position < mv_ctx->max_trim_pos || position > end || - position < mv_ctx->min_trim_pos) - goto error; - - cur = g_hash_table_lookup (timeline->priv->by_start, obj); - snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE); - if (snapped) - position = *snapped; - - ret &= ges_timeline_trim_object_simple (timeline, - GES_TIMELINE_ELEMENT (obj), layers, GES_EDGE_START, position, FALSE); - - if (!ret) { - GST_INFO_OBJECT (timeline, "Could not trim %s", - GES_TIMELINE_ELEMENT_NAME (obj)); - - return FALSE; - } - - - /* In the case we reached max_duration we just make sure to roll - * everything to the real new position */ - position = _START (obj); - - /* Send back changes to the neighbourhood */ - for (tmp = mv_ctx->moving_trackelements; tmp; tmp = tmp->next) { - GESTimelineElement *tmpelement = GES_TIMELINE_ELEMENT (tmp->data); - - tmpstart = _START (tmpelement); - tmpduration = _DURATION (tmpelement); - tmpend = tmpstart + tmpduration; - - /* Check that the object should be resized at this position - * even if an error accurs, we keep doing our job */ - if (tmpend == start) { - ret &= ges_timeline_trim_object_simple (timeline, tmpelement, NULL, - GES_EDGE_END, position, FALSE); - break; - } - } - break; - case GES_EDGE_END: - - /* Avoid negative durations */ - if (position > mv_ctx->max_trim_pos || position < start) - goto error; - - end = _START (obj) + _DURATION (obj); - - cur = g_hash_table_lookup (timeline->priv->by_end, obj); - snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE); - if (snapped) - position = *snapped; - - ret &= ges_timeline_trim_object_simple (timeline, - GES_TIMELINE_ELEMENT (obj), NULL, GES_EDGE_END, position, FALSE); - - if (ret == FALSE) { - GST_DEBUG_OBJECT (timeline, "No triming, bailing out"); - goto done; - } - - /* In the case we reached max_duration we just make sure to roll - * everything to the real new position */ - position = _START (obj) + _DURATION (obj); - - /* Send back changes to the neighbourhood */ - for (tmp = mv_ctx->moving_trackelements; tmp; tmp = tmp->next) { - GESTimelineElement *tmpelement = GES_TIMELINE_ELEMENT (tmp->data); - - tmpstart = _START (tmpelement); - tmpduration = _DURATION (tmpelement); - tmpend = tmpstart + tmpduration; - - /* Check that the object should be resized at this position - * even if an error accure, we keep doing our job */ - if (end == tmpstart) { - ret &= ges_timeline_trim_object_simple (timeline, tmpelement, NULL, - GES_EDGE_START, position, FALSE); - } - } - break; - default: - GST_DEBUG ("Edge type %i not handled here", edge); - break; - } - -done: - timeline->priv->needs_transitions_update = TRUE; - mv_ctx->ignore_needs_ctx = FALSE; - - return ret; - -error: - GST_DEBUG_OBJECT (obj, "Could not roll edge %d to %" GST_TIME_FORMAT, - edge, GST_TIME_ARGS (position)); - - ret = FALSE; - goto done; -} - -gboolean -timeline_move_object (GESTimeline * timeline, GESTrackElement * object, - GList * layers, GESEdge edge, guint64 position) -{ - if (!ges_timeline_set_moving_context (timeline, object, GES_EDIT_MODE_NORMAL, - edge, layers)) { - GST_DEBUG_OBJECT (object, "Could not move to %" GST_TIME_FORMAT, - GST_TIME_ARGS (position)); - - return FALSE; - } - - return ges_timeline_move_object_simple (timeline, - GES_TIMELINE_ELEMENT (object), layers, edge, position); -} - gboolean ges_timeline_move_object_simple (GESTimeline * timeline, GESTimelineElement * element, GList * layers, GESEdge edge, guint64 position) { - GstClockTime cpos = GES_TIMELINE_ELEMENT_START (element); - guint64 *snap_end, *snap_st, *cur, position_offset, off1, off2, top_end; - GESTrackElement *track_element; - GESContainer *toplevel; - - /* We only work with GESSource-s and we check that we are not already moving - * the specified element ourself */ - if (GES_IS_SOURCE (element) == FALSE) { - GST_INFO_OBJECT (element, "Not a source, not moving."); - return FALSE; - } - - if (g_list_find (timeline->priv->movecontext.moving_trackelements, element)) { - GST_DEBUG_OBJECT (element, "Already part of the moving context."); - return TRUE; - } - - timeline->priv->needs_rollback = FALSE; - track_element = GES_TRACK_ELEMENT (element); - toplevel = get_toplevel_container (track_element); - position_offset = position - _START (track_element); - - top_end = _START (toplevel) + _DURATION (toplevel) + position_offset; - cur = g_hash_table_lookup (timeline->priv->by_end, track_element); - - GST_DEBUG_OBJECT (timeline, "Moving %" GST_PTR_FORMAT " to %" - GST_TIME_FORMAT " (end %" GST_TIME_FORMAT ")", element, - GST_TIME_ARGS (position), GST_TIME_ARGS (top_end)); - - snap_end = ges_timeline_snap_position (timeline, track_element, cur, top_end, - FALSE); - if (snap_end) - off1 = top_end > *snap_end ? top_end - *snap_end : *snap_end - top_end; - else - off1 = G_MAXUINT64; - - cur = g_hash_table_lookup (timeline->priv->by_start, track_element); - snap_st = - ges_timeline_snap_position (timeline, track_element, cur, position, - FALSE); - if (snap_st) - off2 = position > *snap_st ? position - *snap_st : *snap_st - position; - else - off2 = G_MAXUINT64; - - /* In the case we could snap on both sides, we snap on the end */ - if (snap_end && off1 <= off2) { - position = position + *snap_end - top_end; - ges_timeline_emit_snappig (timeline, track_element, snap_end); - } else if (snap_st) { - position = position + *snap_st - position; - ges_timeline_emit_snappig (timeline, track_element, snap_st); - } else - ges_timeline_emit_snappig (timeline, track_element, NULL); - timeline->priv->needs_rollback = FALSE; - - _set_start0 (GES_TIMELINE_ELEMENT (track_element), position); - - if (timeline->priv->needs_rollback && !timeline->priv->rolling_back) { - timeline->priv->needs_rollback = FALSE; - timeline->priv->rolling_back = TRUE; - ges_timeline_move_object_simple (timeline, element, layers, edge, cpos); - ges_timeline_emit_snappig (timeline, track_element, NULL); - timeline->priv->rolling_back = FALSE; - - return FALSE; - } - - return TRUE; -} - -gboolean -timeline_context_to_layer (GESTimeline * timeline, gint offset) -{ - gboolean ret = TRUE; - GHashTableIter iter; - GESContainer *key, *value; - GESLayer *new_layer; - guint prio; - MoveContext *mv_ctx = &timeline->priv->movecontext; - - /* Layer's priority is always positive */ - if (offset == 0) - return ret; - - if (offset < 0 && mv_ctx->min_move_layer < -offset) - return ret; - - GST_DEBUG ("Moving %d object, offset %d", - g_hash_table_size (mv_ctx->toplevel_containers), offset); - - mv_ctx->ignore_needs_ctx = TRUE; - timeline->priv->needs_rollback = FALSE; - g_hash_table_iter_init (&iter, mv_ctx->toplevel_containers); - while (g_hash_table_iter_next (&iter, (gpointer *) & key, - (gpointer *) & value)) { - - if (GES_IS_CLIP (value)) { - prio = ges_clip_get_layer_priority (GES_CLIP (value)); - - /* We know that the layer exists as we created it */ - new_layer = GES_LAYER (g_list_nth_data (timeline->layers, prio + offset)); - - if (new_layer == NULL) { - do { - new_layer = ges_timeline_append_layer (timeline); - } while (ges_layer_get_priority (new_layer) < prio + offset); - } - - mv_ctx->moving_to_layer = new_layer; - ret &= ges_clip_move_to_layer (GES_CLIP (key), new_layer); - } else if (GES_IS_GROUP (value)) { - guint32 last_prio = _PRIORITY (value) + offset + - GES_CONTAINER_HEIGHT (value) - 1; - - new_layer = GES_LAYER (g_list_nth_data (timeline->layers, last_prio)); - - if (new_layer == NULL) { - do { - new_layer = ges_timeline_append_layer (timeline); - } while (ges_layer_get_priority (new_layer) < last_prio); - } - - mv_ctx->moving_to_layer = NULL; - _set_priority0 (GES_TIMELINE_ELEMENT (value), _PRIORITY (value) + offset); - } - } - - /* Readjust min_move_layer */ - mv_ctx->min_move_layer = mv_ctx->min_move_layer + offset; - mv_ctx->ignore_needs_ctx = FALSE; - - if (timeline->priv->needs_rollback && !timeline->priv->rolling_back) { - ret = FALSE; - timeline->priv->rolling_back = TRUE; - timeline_context_to_layer (timeline, -offset); - timeline->priv->rolling_back = FALSE; - } - mv_ctx->moving_to_layer = NULL; - - return ret; + return timeline_move_object (timeline, element, + ges_timeline_element_get_layer_priority (element), NULL, edge, position); } void @@ -2233,13 +1033,23 @@ timeline_add_group (GESTimeline * timeline, GESGroup * group) { GST_DEBUG_OBJECT (timeline, "Adding group %" GST_PTR_FORMAT, group); - timeline->priv->movecontext.needs_move_ctx = TRUE; timeline->priv->groups = g_list_prepend (timeline->priv->groups, gst_object_ref_sink (group)); ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (group), timeline); } +void +timeline_update_transition (GESTimeline * timeline) +{ + GList *tmp, *auto_transs; + + auto_transs = g_list_copy (timeline->priv->auto_transitions); + for (tmp = auto_transs; tmp; tmp = tmp->next) + ges_auto_transition_update (tmp->data); + g_list_free (auto_transs); +} + /** * timeline_emit_group_added: * @timeline: a #GESTimeline @@ -2257,7 +1067,6 @@ timeline_emit_group_added (GESTimeline * timeline, GESGroup * group) * timeline_emit_group_removed: * @timeline: a #GESTimeline * @group: group that was removed - * @array: (element-type GESTimelineElement): children that were removed * * Emit group-removed signal. */ @@ -2276,7 +1085,6 @@ timeline_remove_group (GESTimeline * timeline, GESGroup * group) timeline->priv->groups = g_list_remove (timeline->priv->groups, group); - timeline->priv->movecontext.needs_move_ctx = TRUE; ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (group), NULL); gst_object_unref (group); } @@ -2336,10 +1144,8 @@ layer_auto_transition_changed_cb (GESLayer * layer, { GList *tmp, *clips; - timeline->priv->needs_rollback = FALSE; - _create_transitions_on_layer (timeline, layer, NULL, NULL, + timeline_tree_create_transitions (timeline->priv->tree, _create_auto_transition_from_transitions); - clips = ges_layer_get_clips (layer); for (tmp = clips; tmp; tmp = tmp->next) { if (GES_IS_TRANSITION_CLIP (tmp->data)) { @@ -2489,6 +1295,9 @@ clip_track_element_added_cb (GESClip * clip, } 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 @@ -2521,13 +1330,11 @@ layer_object_added_cb (GESLayer * layer, GESClip * clip, GESTimeline * timeline) if (ges_clip_is_moving_from_layer (clip)) { GST_DEBUG ("Clip %p moving from one layer to another, not creating " "TrackElement", clip); - timeline->priv->movecontext.needs_move_ctx = TRUE; - _create_transitions_on_layer (timeline, layer, NULL, NULL, - _find_transition_from_auto_transitions); + timeline_tree_create_transitions (timeline->priv->tree, + ges_timeline_find_auto_transition); return; } - add_object_to_tracks (timeline, clip, NULL); GST_DEBUG ("Making sure that the asset is in our project"); @@ -2603,98 +1410,7 @@ static void trackelement_start_changed_cb (GESTrackElement * child, GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) { - GESTimelinePrivate *priv = timeline->priv; - TrackObjIters *iters = g_hash_table_lookup (priv->obj_iters, child); - - if (G_LIKELY (iters->iter_by_layer)) - g_sequence_sort_changed (iters->iter_by_layer, - (GCompareDataFunc) element_start_compare, NULL); - - if (GES_IS_SOURCE (child)) { - sort_track_elements (timeline, iters); - sort_starts_ends_start (timeline, iters); - sort_starts_ends_end (timeline, iters); - - /* If the timeline is set to snap objects together, we - * are sure that all movement of TrackElement-s are done within - * the moving context, so we do not need to recalculate the - * move context as often */ - if (timeline->priv->movecontext.ignore_needs_ctx && - timeline->priv->snapping_distance == 0) - timeline->priv->movecontext.needs_move_ctx = TRUE; - - timeline_create_transitions (timeline, child); - } -} - -static void -trackelement_priority_changed_cb (GESTrackElement * child, - GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) -{ - GESTimelinePrivate *priv = timeline->priv; - - GList *layer_node = g_list_find_custom (timeline->layers, - GINT_TO_POINTER (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child)), - (GCompareFunc) find_layer_by_prio); - GESLayer *layer = layer_node ? layer_node->data : NULL; - TrackObjIters *iters = g_hash_table_lookup (priv->obj_iters, - child); - - if (G_UNLIKELY (layer == NULL)) { - GST_ERROR_OBJECT (timeline, - "Changing a TrackElement prio, which would not " - "land in no layer we are controlling"); - if (iters->iter_by_layer) - g_sequence_remove (iters->iter_by_layer); - iters->iter_by_layer = NULL; - iters->layer = NULL; - } else { - /* If it moves from layer, properly change it */ - if (layer != iters->layer) { - GSequence *by_layer_sequence = - g_hash_table_lookup (priv->by_layer, layer); - - GST_DEBUG_OBJECT (child, "Moved from layer %" GST_PTR_FORMAT - "(prio %d) to" " %" GST_PTR_FORMAT " (prio %d)", layer, - ges_layer_get_priority (layer), iters->layer, - ges_layer_get_priority (iters->layer)); - - g_sequence_remove (iters->iter_by_layer); - iters->iter_by_layer = - g_sequence_insert_sorted (by_layer_sequence, child, - (GCompareDataFunc) element_start_compare, NULL); - iters->layer = layer; - } else { - g_sequence_sort_changed (iters->iter_by_layer, - (GCompareDataFunc) element_start_compare, NULL); - } - } - - if (GES_IS_SOURCE (child)) - sort_track_elements (timeline, iters); -} - -static void -trackelement_duration_changed_cb (GESTrackElement * child, - GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) -{ - GESTimelinePrivate *priv = timeline->priv; - TrackObjIters *iters = g_hash_table_lookup (priv->obj_iters, child); - - if (GES_IS_SOURCE (child)) { - sort_starts_ends_end (timeline, iters); - - /* If the timeline is set to snap objects together, we - * are sure that all movement of TrackElement-s are done within - * the moving context, so we do not need to recalculate the - * move context as often */ - if (timeline->priv->movecontext.ignore_needs_ctx && - timeline->priv->snapping_distance == 0) { - timeline->priv->movecontext.needs_move_ctx = TRUE; - } - - timeline_create_transitions (timeline, child); - } + timeline_update_duration (timeline); } static void @@ -2704,35 +1420,15 @@ track_element_added_cb (GESTrack * track, GESTrackElement * track_element, /* Auto transition should be updated before we receive the signal */ g_signal_connect_after (GES_TRACK_ELEMENT (track_element), "notify::start", G_CALLBACK (trackelement_start_changed_cb), timeline); - g_signal_connect_after (GES_TRACK_ELEMENT (track_element), - "notify::duration", G_CALLBACK (trackelement_duration_changed_cb), - timeline); - g_signal_connect_after (GES_TRACK_ELEMENT (track_element), - "notify::priority", G_CALLBACK (trackelement_priority_changed_cb), - timeline); - - start_tracking_track_element (timeline, track_element); } static void track_element_removed_cb (GESTrack * track, GESTrackElement * track_element, GESTimeline * timeline) { - - if (GES_IS_SOURCE (track_element)) { - /* Make sure to reinitialise the moving context next time */ - timeline->priv->movecontext.needs_move_ctx = TRUE; - } - /* Disconnect all signal handlers */ g_signal_handlers_disconnect_by_func (track_element, trackelement_start_changed_cb, timeline); - g_signal_handlers_disconnect_by_func (track_element, - trackelement_duration_changed_cb, timeline); - g_signal_handlers_disconnect_by_func (track_element, - trackelement_priority_changed_cb, timeline); - - stop_tracking_track_element (timeline, track_element); } static GstPadProbeReturn @@ -2825,13 +1521,21 @@ timeline_add_element (GESTimeline * timeline, GESTimelineElement * element) g_hash_table_insert (timeline->priv->all_elements, ges_timeline_element_get_name (element), gst_object_ref (element)); + timeline_tree_track_element (timeline->priv->tree, element); + return TRUE; } gboolean timeline_remove_element (GESTimeline * timeline, GESTimelineElement * element) { - return g_hash_table_remove (timeline->priv->all_elements, element->name); + if (g_hash_table_remove (timeline->priv->all_elements, element->name)) { + timeline_tree_stop_tracking_element (timeline->priv->tree, element); + + return TRUE; + } + + return FALSE; } void @@ -2844,6 +1548,12 @@ timeline_fill_gaps (GESTimeline * timeline) } } +GNode * +timeline_get_tree (GESTimeline * timeline) +{ + return timeline->priv->tree; +} + /**** API *****/ /** * ges_timeline_new: @@ -3057,8 +1767,6 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer) /* Inform the layer that it belongs to a new timeline */ ges_layer_set_timeline (layer, timeline); - g_hash_table_insert (timeline->priv->by_layer, layer, g_sequence_new (NULL)); - /* 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); @@ -3081,8 +1789,6 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer) } g_list_free (objects); - timeline->priv->movecontext.needs_move_ctx = TRUE; - return TRUE; } @@ -3134,14 +1840,12 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESLayer * layer) g_signal_handlers_disconnect_by_func (layer, layer_auto_transition_changed_cb, timeline); - g_hash_table_remove (timeline->priv->by_layer, layer); timeline->layers = g_list_remove (timeline->layers, layer); ges_layer_set_timeline (layer, NULL); g_signal_emit (timeline, ges_timeline_signals[LAYER_REMOVED], 0, layer); gst_object_unref (layer); - timeline->priv->movecontext.needs_move_ctx = TRUE; return TRUE; } @@ -3451,12 +2155,11 @@ ges_timeline_commit_unlocked (GESTimeline * timeline) GST_DEBUG_OBJECT (timeline, "commiting changes"); + timeline_tree_create_transitions (timeline->priv->tree, + ges_timeline_find_auto_transition); for (tmp = timeline->layers; tmp; tmp = tmp->next) { GESLayer *layer = tmp->data; - _create_transitions_on_layer (timeline, layer, NULL, NULL, - _find_transition_from_auto_transitions); - /* Ensure clip priorities are correct after an edit */ ges_layer_resync_priorities (layer); } @@ -3475,9 +2178,6 @@ ges_timeline_commit_unlocked (GESTimeline * timeline) } } - /* Make sure we reset the context */ - timeline->priv->movecontext.needs_move_ctx = TRUE; - return res; } @@ -3521,7 +2221,7 @@ ges_timeline_commit (GESTimeline * timeline) ret = ges_timeline_commit_unlocked (timeline); UNLOCK_DYN (timeline); - ges_timeline_emit_snappig (timeline, NULL, NULL); + ges_timeline_emit_snapping (timeline, NULL, NULL, GST_CLOCK_TIME_NONE); return ret; } diff --git a/ges/ges-track-element.c b/ges/ges-track-element.c index f744649ec2..ca18bd50b1 100644 --- a/ges/ges-track-element.c +++ b/ges/ges-track-element.c @@ -1364,16 +1364,21 @@ ges_track_element_edit (GESTrackElement * object, switch (mode) { case GES_EDIT_MODE_NORMAL: - return timeline_move_object (timeline, object, layers, edge, position); + return timeline_move_object (timeline, GES_TIMELINE_ELEMENT (object), -1, + layers, edge, position); break; case GES_EDIT_MODE_TRIM: - return timeline_trim_object (timeline, object, layers, edge, position); + return timeline_trim_object (timeline, GES_TIMELINE_ELEMENT (object), -1, + layers, edge, position); break; case GES_EDIT_MODE_RIPPLE: - return timeline_ripple_object (timeline, object, layers, edge, position); + return timeline_ripple_object (timeline, GES_TIMELINE_ELEMENT (object), + GES_TIMELINE_ELEMENT_PRIORITY (object) / LAYER_HEIGHT, + layers, edge, position); break; case GES_EDIT_MODE_ROLL: - return timeline_roll_object (timeline, object, layers, edge, position); + return timeline_roll_object (timeline, GES_TIMELINE_ELEMENT (object), + layers, edge, position); break; case GES_EDIT_MODE_SLIDE: return timeline_slide_object (timeline, object, layers, edge, position); diff --git a/ges/ges-uri-clip.c b/ges/ges-uri-clip.c index 3f8f63c538..91ef527442 100644 --- a/ges/ges-uri-clip.c +++ b/ges/ges-uri-clip.c @@ -276,6 +276,24 @@ extractable_set_asset (GESExtractable * self, GESAsset * asset) g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (asset), FALSE); uri_clip_asset = GES_URI_CLIP_ASSET (asset); + if (GST_CLOCK_TIME_IS_VALID (GES_TIMELINE_ELEMENT_DURATION (self)) && + ges_uri_clip_asset_get_duration (uri_clip_asset) < + GES_TIMELINE_ELEMENT_INPOINT (self) + + GES_TIMELINE_ELEMENT_DURATION (self)) { + GST_INFO_OBJECT (self, + "Can not set asset to %p as its duration is %" GST_TIME_FORMAT + " < to inpoint %" GST_TIME_FORMAT " + %" GST_TIME_FORMAT " = %" + GST_TIME_FORMAT, asset, + GST_TIME_ARGS (ges_uri_clip_asset_get_duration (uri_clip_asset)), + GST_TIME_ARGS (GES_TIMELINE_ELEMENT_INPOINT (self)), + GST_TIME_ARGS (GES_TIMELINE_ELEMENT_DURATION (self)), + GST_TIME_ARGS (GES_TIMELINE_ELEMENT_INPOINT (self) + + GES_TIMELINE_ELEMENT_DURATION (self))); + + + return FALSE; + } + if (GST_CLOCK_TIME_IS_VALID (GES_TIMELINE_ELEMENT_DURATION (clip)) == FALSE) _set_duration0 (GES_TIMELINE_ELEMENT (uriclip), ges_uri_clip_asset_get_duration (uri_clip_asset)); diff --git a/ges/meson.build b/ges/meson.build index ba89315adb..8b57f61a23 100644 --- a/ges/meson.build +++ b/ges/meson.build @@ -51,6 +51,7 @@ ges_sources = [ 'ges-command-line-formatter.c', 'ges-auto-transition.c', 'ges-timeline-element.c', + 'ges-timeline-tree.c', 'ges-container.c', 'ges-effect-asset.c', 'ges-smart-adder.c', diff --git a/tests/check/ges/asset.c b/tests/check/ges/asset.c index da8cd1d201..c604c42c67 100644 --- a/tests/check/ges/asset.c +++ b/tests/check/ges/asset.c @@ -227,6 +227,9 @@ GST_START_TEST (test_uri_clip_change_asset) asset1 = GES_ASSET (ges_uri_clip_asset_request_sync (uri1, NULL)); fail_unless_equals_int (g_list_length (GES_CONTAINER_CHILDREN (extractable)), 2); + fail_if (ges_extractable_set_asset (extractable, asset1)); + ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (extractable), + ges_uri_clip_asset_get_duration (GES_URI_CLIP_ASSET (asset1))); fail_unless (ges_extractable_set_asset (extractable, asset1)); fail_unless_equals_int (g_list_length (GES_CONTAINER_CHILDREN (extractable)), 1); diff --git a/tests/check/ges/basic.c b/tests/check/ges/basic.c index 9945256fa1..d37d9c1a9c 100644 --- a/tests/check/ges/basic.c +++ b/tests/check/ges/basic.c @@ -94,13 +94,13 @@ GST_START_TEST (test_ges_scenario) /* There are 3 references: * 1 by the clip * 1 by the track - * 2 by the timeline */ - ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 4); + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3); /* There are 3 references: * 1 by the clip - * 2 by the timeline + * 3 by the timeline * 1 by the track */ - ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 4); + ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3); GST_DEBUG ("Remove the Clip from the layer"); @@ -225,13 +225,13 @@ GST_START_TEST (test_ges_timeline_add_layer) /* There are 3 references: * 1 by the clip * 1 by the trackelement - * 2 by the timeline */ - ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 4); + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3); /* There are 3 references: * 1 by the clip * 1 by the timeline - * 2 by the trackelement */ - ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 4); + * 1 by the trackelement */ + ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3); trackelements = GES_CONTAINER_CHILDREN (s2); trackelement = GES_TRACK_ELEMENT (trackelements->data); @@ -240,8 +240,8 @@ GST_START_TEST (test_ges_timeline_add_layer) /* There are 3 references: * 1 by the clip * 1 by the timeline - * 2 by the trackelement */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (trackelement), "trackelement", 4); + * 1 by the trackelement */ + ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (trackelement), "trackelement", 3); trackelements = GES_CONTAINER_CHILDREN (s3); trackelement = GES_TRACK_ELEMENT (trackelements->data); @@ -251,7 +251,7 @@ GST_START_TEST (test_ges_timeline_add_layer) * 1 by the clip * 1 by the timeline * 2 by the trackelement */ - ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 4); + ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3); /* theoretically this is all we need to do to ensure cleanup */ gst_object_unref (timeline); @@ -334,8 +334,8 @@ GST_START_TEST (test_ges_timeline_add_layer_first) /* Each object has 3 references: * 1 by the clip * 1 by the track - * 2 by the timeline */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4); + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3); } trackelements = GES_CONTAINER_CHILDREN (s2); @@ -344,8 +344,8 @@ GST_START_TEST (test_ges_timeline_add_layer_first) /* Each object has 3 references: * 1 by the clip * 1 by the track - * 2 by the timeline */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4); + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3); } trackelements = GES_CONTAINER_CHILDREN (s3); @@ -354,8 +354,8 @@ GST_START_TEST (test_ges_timeline_add_layer_first) /* Each object has 3 references: * 1 by the clip * 1 by the track - * 2 by the timeline */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4); + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3); } /* theoretically this is all we need to do to ensure cleanup */ @@ -444,14 +444,14 @@ GST_START_TEST (test_ges_timeline_remove_track) /* There are 3 references held: * 1 by the clip * 1 by the track - * 2 by the timeline */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4); + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3); } /* There are 3 references held: * 1 by the container * 1 by the track - * 2 by the timeline */ - ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 4); + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 3); trackelements = GES_CONTAINER_CHILDREN (s2); fail_unless (trackelements != NULL); @@ -460,14 +460,14 @@ GST_START_TEST (test_ges_timeline_remove_track) /* There are 3 references held: * 1 by the clip * 1 by the track - * 2 by the timeline */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4); + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3); } /* There are 3 references held: * 1 by the container * 1 by the track - * 2 by the timeline */ - ASSERT_OBJECT_REFCOUNT (t2, "t2", 4); + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (t2, "t2", 3); trackelements = GES_CONTAINER_CHILDREN (s3); fail_unless (trackelements != NULL); @@ -476,21 +476,21 @@ GST_START_TEST (test_ges_timeline_remove_track) /* There are 3 references held: * 1 by the clip * 1 by the track - * 2 by the timeline */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4); + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3); } /* There are 3 references held: * 1 by the container * 1 by the track - * 2 by the timeline */ - ASSERT_OBJECT_REFCOUNT (t3, "t3", 4); + * 1 by the timeline */ + ASSERT_OBJECT_REFCOUNT (t3, "t3", 3); /* remove the track and check that the track elements have been released */ fail_unless (ges_timeline_remove_track (timeline, track)); - ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 2); - ASSERT_OBJECT_REFCOUNT (t2, "trackelement", 2); - ASSERT_OBJECT_REFCOUNT (t3, "trackelement", 2); + ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 1); + ASSERT_OBJECT_REFCOUNT (t2, "trackelement", 1); + ASSERT_OBJECT_REFCOUNT (t3, "trackelement", 1); ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); ASSERT_OBJECT_REFCOUNT (timeline, "1 for the us", 1); tmp = ges_layer_get_clips (layer); @@ -614,17 +614,17 @@ GST_START_TEST (test_ges_timeline_multiple_tracks) /* There are 3 references held: * 1 by the clip * 1 by the track - * 2 by the timeline */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4); + * 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 - * 2 by the timeline + * 1 by the timeline * 1 added by ourselves above (gst_object_ref (t1)) */ - ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 5); + ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 4); trackelements = GES_CONTAINER_CHILDREN (s2); fail_unless (trackelements != NULL); @@ -633,17 +633,17 @@ GST_START_TEST (test_ges_timeline_multiple_tracks) /* There are 3 references held: * 1 by the clip * 1 by the track - * 2 by the timeline */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4); + * 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 - * 2 by the timeline + * 1 by the timeline * 1 added by ourselves above (gst_object_ref (t2)) */ - ASSERT_OBJECT_REFCOUNT (t2, "t2", 5); + ASSERT_OBJECT_REFCOUNT (t2, "t2", 4); trackelements = GES_CONTAINER_CHILDREN (s3); fail_unless (trackelements != NULL); @@ -652,17 +652,17 @@ GST_START_TEST (test_ges_timeline_multiple_tracks) /* There are 3 references held: * 1 by the clip * 1 by the track - * 2 by the timeline */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4); + * 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 - * 2 by the timeline + * 1 by the timeline * 1 added by ourselves above (gst_object_ref (t3)) */ - ASSERT_OBJECT_REFCOUNT (t3, "t3", 5); + ASSERT_OBJECT_REFCOUNT (t3, "t3", 4); gst_object_unref (t1); gst_object_unref (t2); diff --git a/tests/check/ges/clip.c b/tests/check/ges/clip.c index 7bdf773208..5068cba7fb 100644 --- a/tests/check/ges/clip.c +++ b/tests/check/ges/clip.c @@ -365,7 +365,7 @@ GST_START_TEST (test_split_object) /* 1 ref for the Clip, 1 ref for the Track and 2 ref for the timeline * (1 for the "all_element" hashtable, another for the sequence of TrackElement*/ ASSERT_OBJECT_REFCOUNT (splittrackelement, - "1 ref for the Clip, 1 ref for the Track and 2 ref for the timeline", 4); + "1 ref for the Clip, 1 ref for the Track and 1 ref for the timeline", 3); check_destroyed (G_OBJECT (timeline), G_OBJECT (splitclip), clip, splittrackelement, NULL); @@ -429,7 +429,7 @@ GST_START_TEST (test_clip_group_ungroup) tmp = ges_track_get_elements (audio_track); assert_equals_int (g_list_length (tmp), 1); ASSERT_OBJECT_REFCOUNT (tmp->data, "1 for the track + 1 for the container " - "+ 2 for the timeline + 1 in tmp list", 5); + "+ 1 for the timeline + 1 in tmp list", 4); assert_equals_int (ges_track_element_get_track_type (tmp->data), GES_TRACK_TYPE_AUDIO); assert_equals_int (ges_clip_get_supported_formats (GES_CLIP @@ -438,7 +438,7 @@ GST_START_TEST (test_clip_group_ungroup) tmp = ges_track_get_elements (video_track); assert_equals_int (g_list_length (tmp), 1); ASSERT_OBJECT_REFCOUNT (tmp->data, "1 for the track + 1 for the container " - "+ 2 for the timeline + 1 in tmp list", 5); + "+ 1 for the timeline + 1 in tmp list", 4); assert_equals_int (ges_track_element_get_track_type (tmp->data), GES_TRACK_TYPE_VIDEO); assert_equals_int (ges_clip_get_supported_formats (GES_CLIP @@ -489,7 +489,7 @@ GST_START_TEST (test_clip_group_ungroup) tmp = ges_track_get_elements (video_track); assert_equals_int (g_list_length (tmp), 1); ASSERT_OBJECT_REFCOUNT (tmp->data, "1 for the track + 1 for the container " - "+ 2 for the timeline + 1 in tmp list", 5); + "+ 1 for the timeline + 1 in tmp list", 4); assert_equals_int (ges_track_element_get_track_type (tmp->data), GES_TRACK_TYPE_VIDEO); fail_unless (GES_CONTAINER (ges_timeline_element_get_parent (tmp->data)) == diff --git a/tests/check/ges/group.c b/tests/check/ges/group.c index 84944b0226..373eacfa5b 100644 --- a/tests/check/ges/group.c +++ b/tests/check/ges/group.c @@ -165,147 +165,69 @@ GST_START_TEST (test_move_group) CHECK_OBJECT_PROPS (clip2, 60, 0, 50); CHECK_OBJECT_PROPS (group, 10, 0, 100); ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); - - /* - * 0 20---Group1---------------110 - * | | - * layer: | | - * | | - * |--------------------------| - * 5--------- 0------------| - * layer1: | clip1 | | clip2 | - * 20--------30 60----------| - * |--------------------------| - */ - ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 20); - CHECK_OBJECT_PROPS (clip, 15, 5, 0); + fail_if (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 20)); + CHECK_OBJECT_PROPS (clip, 10, 0, 5); CHECK_OBJECT_PROPS (clip1, 20, 5, 10); CHECK_OBJECT_PROPS (clip2, 60, 0, 50); - CHECK_OBJECT_PROPS (group, 20, 0, 90); - - /* - * 0 25---Group1---------------110 - * | | - * layer: | | - * | | - * |--------------------------| - * 10------ 0------------| - * layer1: | clip1 | | clip2 | - * 25------30 60----------| - * |--------------------------| - */ - ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 25); - CHECK_OBJECT_PROPS (clip, 15, 5, 0); - CHECK_OBJECT_PROPS (clip1, 25, 10, 5); - CHECK_OBJECT_PROPS (clip2, 60, 0, 50); - CHECK_OBJECT_PROPS (group, 25, 0, 85); + CHECK_OBJECT_PROPS (group, 10, 0, 100); ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); - /* - * 0 10------------Group1------------------110 - * |------ | - * layer: |clip | | - * |-----15 | - * |-------------------------------------| - * | 10------ 0------------| - * layer1: | | clip1 | | clip2 | - * | 25------30 60----------| - * | |--------------------------| - * |-------------------------------------| - */ + fail_if (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 25)); + CHECK_OBJECT_PROPS (clip, 10, 0, 5); + CHECK_OBJECT_PROPS (clip1, 20, 5, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 10, 0, 100); + ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); + + /* Same thing in the end... */ ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 10); CHECK_OBJECT_PROPS (clip, 10, 0, 5); - CHECK_OBJECT_PROPS (clip1, 25, 10, 5); + CHECK_OBJECT_PROPS (clip1, 20, 5, 10); CHECK_OBJECT_PROPS (clip2, 60, 0, 50); CHECK_OBJECT_PROPS (group, 10, 0, 100); ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); /* - * 0 25---Group1---------------110 - * | | - * layer: 15 | | - * |clip | | - * - |--------------------------| - * 10------ 0------------| - * layer1: | clip1 | | clip2 | - * 25------30 60----------| - * |--------------------------| + * 0 12------------Group1---------------110 + * 2------ | + * layer: |clip | | + * |-----15 | + * |----------------------------------| + * | 7--------- 2----------| + * layer1: | | clip1 | | clip2 | + * | 22--------30 62----------| + * |----------------------------------| */ - ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 25); - CHECK_OBJECT_PROPS (clip, 15, 5, 0); - CHECK_OBJECT_PROPS (clip1, 25, 10, 5); - CHECK_OBJECT_PROPS (clip2, 60, 0, 50); - CHECK_OBJECT_PROPS (group, 25, 0, 85); + ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 12); + CHECK_OBJECT_PROPS (clip, 12, 2, 3); + CHECK_OBJECT_PROPS (clip1, 22, 7, 8); + CHECK_OBJECT_PROPS (clip2, 62, 2, 48); + CHECK_OBJECT_PROPS (group, 12, 0, 98); ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); - /* - * 0 25---Group1--30 - * | | - * layer: 15 | | - * |clip | | - * - |------------ - * 15-----------| 60 - * layer1: | clip1 | |clip2 - * 25------------| - - * |------------| - */ + /* Setting the duration would lead to overlaps */ ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (group), 10); - CHECK_OBJECT_PROPS (clip, 15, 5, 0); - CHECK_OBJECT_PROPS (clip1, 25, 10, 5); - CHECK_OBJECT_PROPS (clip2, 60, 0, 0); - CHECK_OBJECT_PROPS (group, 25, 0, 5); - - /* - * 0 25---Group1---------------125 - * | | - * layer: 15 | | - * |clip | | - * - |--------------------------| - * 10-------------------------| - * layer1: | clip1 | clip2 | - * 25--------------60----------| - * |--------------------------| - */ + CHECK_OBJECT_PROPS (clip, 12, 2, 3); + CHECK_OBJECT_PROPS (clip1, 22, 7, 8); + CHECK_OBJECT_PROPS (clip2, 62, 2, 48); + CHECK_OBJECT_PROPS (group, 12, 0, 98); ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (group), 100); - CHECK_OBJECT_PROPS (clip, 15, 5, 0); - CHECK_OBJECT_PROPS (clip1, 25, 10, 100); - CHECK_OBJECT_PROPS (clip2, 60, 0, 65); - CHECK_OBJECT_PROPS (group, 25, 0, 100); - ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); + CHECK_OBJECT_PROPS (clip, 12, 2, 3); + CHECK_OBJECT_PROPS (clip1, 22, 7, 8); + CHECK_OBJECT_PROPS (clip2, 62, 2, 50); + CHECK_OBJECT_PROPS (group, 12, 0, 100); - /* - * 0 20---Group1---------------120 - * | | - * layer: 15 | | - * |clip| | - * - |--------------------------| - * 10-------------------------| - * layer1: | clip1 | clip2 | - * 20-------------55----------| - * |--------------------------| - */ ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (group), 20); - CHECK_OBJECT_PROPS (clip, 15, 5, 0); - CHECK_OBJECT_PROPS (clip1, 20, 10, 100); - CHECK_OBJECT_PROPS (clip2, 55, 0, 65); + CHECK_OBJECT_PROPS (clip, 20, 2, 3); + CHECK_OBJECT_PROPS (clip1, 30, 7, 8); + CHECK_OBJECT_PROPS (clip2, 70, 2, 50); CHECK_OBJECT_PROPS (group, 20, 0, 100); - /* - * 0 10---Group1---------------120 - * |-----15 | - * layer: | clip| | - * |------ | - * |--------------------------| - * 5--------------------------| - * layer1: | clip1 | clip2 | - * 10-------------55----------| - * |--------------------------| - */ - ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 10); - CHECK_OBJECT_PROPS (clip, 10, 0, 5); - CHECK_OBJECT_PROPS (clip1, 10, 0, 110); - CHECK_OBJECT_PROPS (clip2, 55, 0, 65); - CHECK_OBJECT_PROPS (group, 10, 0, 110); + fail_if (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 10)); + CHECK_OBJECT_PROPS (clip, 20, 2, 3); + CHECK_OBJECT_PROPS (clip1, 30, 7, 8); + CHECK_OBJECT_PROPS (clip2, 70, 2, 50); + CHECK_OBJECT_PROPS (group, 20, 0, 100); ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2); check_destroyed (G_OBJECT (timeline), G_OBJECT (group), NULL); diff --git a/tests/check/ges/layer.c b/tests/check/ges/layer.c index cc61de6845..8b36fac362 100644 --- a/tests/check/ges/layer.c +++ b/tests/check/ges/layer.c @@ -308,7 +308,7 @@ GST_START_TEST (test_single_layer_automatic_transition) { GESAsset *asset; GESTimeline *timeline; - GList *objects, *current; + GList *objects; GESClip *transition; GESLayer *layer; GESTimelineElement *src, *src1, *src2; @@ -379,24 +379,19 @@ GST_START_TEST (test_single_layer_automatic_transition) ges_timeline_element_set_start (src, 250); /* - * 500_____transition____1250 - * 250___________src_________1250 + * 600_____transition______1500 + * 600___________src_________1600 * 500___________src1_________1500 */ GST_DEBUG ("Checking src timing values"); - assert_equals_uint64 (_START (src), 250); - assert_equals_uint64 (_DURATION (src), 1250 - 250); - assert_equals_uint64 (_START (src1), 500); - assert_equals_uint64 (_DURATION (src1), 1500 - 500); + CHECK_OBJECT_PROPS (src, 250, 0, 1000); + CHECK_OBJECT_PROPS (src1, 500, 0, 1000); objects = ges_layer_get_clips (layer); assert_equals_int (g_list_length (objects), 4); - assert_is_type (objects->data, GES_TYPE_TEST_CLIP); - transition = objects->next->data; assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); - assert_equals_uint64 (_START (transition), 500); - assert_equals_uint64 (_DURATION (transition), 750); + CHECK_OBJECT_PROPS (transition, 500, 0, 750); transition = objects->next->next->data; assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); @@ -404,295 +399,65 @@ GST_START_TEST (test_single_layer_automatic_transition) assert_equals_uint64 (_DURATION (transition), 750); g_list_free_full (objects, gst_object_unref); - GST_DEBUG ("Moving second source to 250, the transitions should be removed"); - ges_timeline_element_set_start (src1, 250); + fail_if (ges_timeline_element_set_start (src1, 250)); - /* The transition should be removed - * 250___________src_________1250 - * 250___________src1________1250 + fail_if (ges_container_edit (GES_CONTAINER (src), NULL, -1, + GES_EDIT_MODE_TRIM, GES_EDGE_START, 500)); + CHECK_OBJECT_PROPS (src, 250, 0, 1000); + CHECK_OBJECT_PROPS (src1, 500, 0, 1000); + fail_if (ges_timeline_element_trim (src, 500)); + CHECK_OBJECT_PROPS (src, 250, 0, 1000); + CHECK_OBJECT_PROPS (src1, 500, 0, 1000); + fail_if (ges_timeline_element_trim (src, 750)); + CHECK_OBJECT_PROPS (src, 250, 0, 1000); + CHECK_OBJECT_PROPS (src1, 500, 0, 1000); + fail_if (ges_timeline_element_set_start (src, 500)); + CHECK_OBJECT_PROPS (src, 250, 0, 1000); + CHECK_OBJECT_PROPS (src1, 500, 0, 1000); + + /* + * 600_____transition______1500 + * 600___________src_________1600 + * 500___________src1_________1500 */ - GST_DEBUG ("Checking src timing values"); - assert_equals_uint64 (_START (src), 250); - assert_equals_uint64 (_DURATION (src), 1250 - 250); - assert_equals_uint64 (_START (src1), 250); - assert_equals_uint64 (_DURATION (src1), 1250 - 250); - - objects = ges_layer_get_clips (layer); - assert_equals_int (g_list_length (objects), 2); - g_list_free_full (objects, gst_object_unref); - - GST_DEBUG ("Trimming second source to 500 no transition should be created " - "as they have the same end"); - ges_container_edit (GES_CONTAINER (src1), NULL, -1, - GES_EDIT_MODE_TRIM, GES_EDGE_START, 500); - - /* 250___________src_________1250 - * 500______src1_______1250 - */ - GST_DEBUG ("Checking src timing values"); - assert_equals_uint64 (_START (src), 250); - assert_equals_uint64 (_DURATION (src), 1250 - 250); - assert_equals_uint64 (_START (src1), 500); - assert_equals_uint64 (_DURATION (src1), 1250 - 500); - - objects = ges_layer_get_clips (layer); - assert_equals_int (g_list_length (objects), 2); - g_list_free_full (objects, gst_object_unref); - - GST_DEBUG ("Trimming second source to 500, no transition should be created"); - ges_timeline_element_trim (src, 500); - - /* 500___________src_________1250 - * 500___________src1________1250 - */ - GST_DEBUG ("Checking src timing values"); - assert_equals_uint64 (_START (src), 500); - assert_equals_uint64 (_DURATION (src), 1250 - 500); - assert_equals_uint64 (_START (src1), 500); - assert_equals_uint64 (_DURATION (src1), 1250 - 500); - - GST_DEBUG ("Trimming first source to 750, no transition should be created"); - ges_timeline_element_trim (src, 750); - - /* 750_______src_______1250 - * 500___________src1________1250 - */ - GST_DEBUG ("Checking src timing values"); - assert_equals_uint64 (_START (src), 750); - assert_equals_uint64 (_DURATION (src), 1250 - 750); - assert_equals_uint64 (_START (src1), 500); - assert_equals_uint64 (_DURATION (src1), 1250 - 500); - - objects = ges_layer_get_clips (layer); - assert_equals_int (g_list_length (objects), 2); - g_list_free_full (objects, gst_object_unref); - - objects = ges_layer_get_clips (layer); - assert_equals_int (g_list_length (objects), 2); - g_list_free_full (objects, gst_object_unref); - - GST_DEBUG ("Moving first source to 500, no transition should be created"); - ges_timeline_element_set_start (src, 500); - - /* 500________src______1000 - * 500___________src1________1250 - */ - GST_DEBUG ("Checking src timing values"); - assert_equals_uint64 (_START (src), 500); - assert_equals_uint64 (_DURATION (src), 1000 - 500); - assert_equals_uint64 (_START (src1), 500); - assert_equals_uint64 (_DURATION (src1), 1250 - 500); - - objects = ges_layer_get_clips (layer); - assert_equals_int (g_list_length (objects), 2); - g_list_free_full (objects, gst_object_unref); - - objects = ges_layer_get_clips (layer); - assert_equals_int (g_list_length (objects), 2); - g_list_free_full (objects, gst_object_unref); - - GST_DEBUG ("Moving first source to 600, no transition should be created"); ges_timeline_element_set_start (src, 600); - /* 600____src___1100 - * 500___________src1________1250 - */ - GST_DEBUG ("Checking src timing values"); - assert_equals_uint64 (_START (src), 600); - assert_equals_uint64 (_DURATION (src), 1100 - 600); - assert_equals_uint64 (_START (src1), 500); - assert_equals_uint64 (_DURATION (src1), 1250 - 500); - + CHECK_OBJECT_PROPS (src, 600, 0, 1000); + CHECK_OBJECT_PROPS (src1, 500, 0, 1000); objects = ges_layer_get_clips (layer); - assert_equals_int (g_list_length (objects), 2); - g_list_free_full (objects, gst_object_unref); - - objects = ges_layer_get_clips (layer); - assert_equals_int (g_list_length (objects), 2); + assert_equals_int (g_list_length (objects), 4); + transition = objects->next->data; + assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); + CHECK_OBJECT_PROPS (transition, 600, 0, 900); g_list_free_full (objects, gst_object_unref); GST_DEBUG ("Adding asset to first layer"); GST_DEBUG ("Adding clip from 1250 -- 1000 to first layer"); - src2 = - GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 1250, 0, + fail_if (ges_layer_add_asset (layer, asset, 1250, 0, 1000, GES_TRACK_TYPE_UNKNOWN)); + + /* + * 1500___________src2________2000 + * 1500_trans_1600 + * 600______________src________________1600 + * 600_____transition______1500 + * 500___________src1_________1500 + */ + src2 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 1500, 0, + 500, GES_TRACK_TYPE_UNKNOWN)); assert_is_type (src2, GES_TYPE_TEST_CLIP); - /* 600____src___1100 - * 500___________src1________1250 - * 1250___________src2________2250 - */ - assert_equals_uint64 (_START (src), 600); - assert_equals_uint64 (_DURATION (src), 1100 - 600); - assert_equals_uint64 (_START (src1), 500); - assert_equals_uint64 (_DURATION (src1), 1250 - 500); - assert_equals_uint64 (_START (src2), 1250); - assert_equals_uint64 (_DURATION (src2), 1000); - + CHECK_OBJECT_PROPS (src, 600, 0, 1000); + CHECK_OBJECT_PROPS (src1, 500, 0, 1000); + CHECK_OBJECT_PROPS (src2, 1500, 0, 500); objects = ges_layer_get_clips (layer); - assert_equals_int (g_list_length (objects), 3); - g_list_free_full (objects, gst_object_unref); - - GST_DEBUG - ("Changig first source duration to 800 2 transitions should be created"); - ges_timeline_element_set_duration (src, 800); - ges_timeline_commit (timeline); - /* 600__________________src_____________1400 - * 500___________src1________1250 - * 1250___________src2________2250 - * 600_____trans1_______1250 - * 1250___trans2___1400 - */ - GST_DEBUG ("Checking src timing values"); - assert_equals_uint64 (_START (src), 600); - assert_equals_uint64 (_DURATION (src), 1400 - 600); - assert_equals_uint64 (_START (src1), 500); - assert_equals_uint64 (_DURATION (src1), 1250 - 500); - - current = objects = ges_layer_get_clips (layer); + transition = objects->next->next->data; assert_equals_int (g_list_length (objects), 7); - assert_is_type (objects->data, GES_TYPE_TEST_CLIP); - fail_unless (objects->data == src1); - - current = current->next; - transition = current->data; assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); - assert_equals_uint64 (_START (transition), 600); - assert_equals_uint64 (_DURATION (transition), 1250 - 600); - ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline + ourself", 3); - - current = current->next; - transition = current->data; + CHECK_OBJECT_PROPS (transition, 600, 0, 900); + transition = objects->next->next->next->next->data; assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); - assert_equals_uint64 (_START (transition), 600); - assert_equals_uint64 (_DURATION (transition), 1250 - 600); - ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline + ourself", 3); + CHECK_OBJECT_PROPS (transition, 1500, 0, 100); - current = current->next; - fail_unless (current->data == src); - - current = current->next; - transition = current->data; - assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); - assert_equals_uint64 (_START (transition), 1250); - assert_equals_uint64 (_DURATION (transition), 1400 - 1250); - ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline + ourself", 3); - - current = current->next; - transition = current->data; - assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); - assert_equals_uint64 (_START (transition), 1250); - assert_equals_uint64 (_DURATION (transition), 1400 - 1250); - ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline + ourself", 3); - - current = current->next; - fail_unless (current->data == src2); - g_list_free_full (objects, gst_object_unref); - - GST_DEBUG ("Back to previous state"); - /* Make sure to keep 1 ref so we can check_destroyed afterward */ - gst_object_ref (transition); - ges_timeline_element_set_duration (src, 1100 - 600); - /* 600____src___1100 - * 500___________src1________1250 - * 1250___________src2________2250 - */ - assert_equals_uint64 (_START (src), 600); - assert_equals_uint64 (_DURATION (src), 1100 - 600); - assert_equals_uint64 (_START (src1), 500); - assert_equals_uint64 (_DURATION (src1), 1250 - 500); - assert_equals_uint64 (_START (src2), 1250); - assert_equals_uint64 (_DURATION (src2), 1000); - - /* We check that the transition as actually been freed */ - check_destroyed (G_OBJECT (transition), NULL, NULL); - - objects = ges_layer_get_clips (layer); - assert_equals_int (g_list_length (objects), 3); - g_list_free_full (objects, gst_object_unref); - - GST_DEBUG - ("Set third clip start to 1100, 1 new transition should be created"); - ges_timeline_element_set_start (src2, 1100); - ges_timeline_commit (timeline); - /* 600____src___1100 - * 500___________src1________1250 - * 1100___________src2________2100 - * ^__trans___^ - */ - assert_equals_uint64 (_START (src), 600); - assert_equals_uint64 (_DURATION (src), 1100 - 600); - assert_equals_uint64 (_START (src1), 500); - assert_equals_uint64 (_DURATION (src1), 1250 - 500); - assert_equals_uint64 (_START (src2), 1100); - assert_equals_uint64 (_DURATION (src2), 1000); - - current = objects = ges_layer_get_clips (layer); - assert_equals_int (g_list_length (objects), 5); - assert_is_type (objects->data, GES_TYPE_TEST_CLIP); - fail_unless (current->data == src1); - - current = current->next; - fail_unless (current->data == src); - - current = current->next; - transition = current->data; - assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); - assert_equals_uint64 (_START (transition), 1100); - assert_equals_uint64 (_DURATION (transition), 1250 - 1100); - - current = current->next; - transition = current->data; - assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); - assert_equals_uint64 (_START (transition), 1100); - assert_equals_uint64 (_DURATION (transition), 1250 - 1100); - - current = current->next; - fail_unless (current->data == src2); - g_list_free_full (objects, gst_object_unref); - - GST_DEBUG ("Check that we can not create 2 transitions at the same place"); - fail_if (ges_container_edit (GES_CONTAINER (src2), NULL, -1, - GES_EDIT_MODE_NORMAL, GES_EDGE_START, 1000)); - - /* - * 500___________src1________1250 - * 1000___________src2________2000 - * ^____trans____^ - */ - ges_layer_remove_clip (layer, GES_CLIP (src)); - fail_unless (ges_container_edit (GES_CONTAINER (src2), NULL, -1, - GES_EDIT_MODE_NORMAL, GES_EDGE_START, 1000)); - assert_equals_uint64 (_START (src1), 500); - assert_equals_uint64 (_DURATION (src1), 1250 - 500); - assert_equals_uint64 (_START (src2), 1000); - assert_equals_uint64 (_DURATION (src2), 1000); - - current = objects = ges_layer_get_clips (layer); - current = objects; - assert_equals_int (g_list_length (objects), 4); - assert_is_type (objects->data, GES_TYPE_TEST_CLIP); - transition = objects->next->data; - assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); - fail_unless (current->data == src1); - g_list_free_full (objects, gst_object_unref); - - /* - * 500___________src1________1250 - * ^____trans____^ - * 1100___________src2________2000 - */ - ges_container_edit (GES_CONTAINER (transition), - NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, 1100); - assert_equals_uint64 (_START (src1), 500); - assert_equals_uint64 (_DURATION (src1), 1250 - 500); - assert_equals_uint64 (_START (src2), 1100); - assert_equals_uint64 (_DURATION (src2), 2000 - 1100); - - current = objects = ges_layer_get_clips (layer); - current = objects; - assert_equals_int (g_list_length (objects), 4); - assert_is_type (objects->data, GES_TYPE_TEST_CLIP); - fail_unless (current->data == src1); g_list_free_full (objects, gst_object_unref); gst_object_unref (timeline); @@ -1006,7 +771,7 @@ GST_START_TEST (test_multi_layer_automatic_transition) GST_DEBUG ("Moving src to second layer, should remove first transition on first layer"); - ges_clip_move_to_layer (GES_CLIP (src), layer1); + fail_if (ges_clip_move_to_layer (GES_CLIP (src), layer1)); /* 500___________src1_________1500 * 1000___________src3_________2000 Layer @@ -1027,31 +792,8 @@ GST_START_TEST (test_multi_layer_automatic_transition) GST_DEBUG ("Checking transitions on first layer"); current = objects = ges_layer_get_clips (layer); - assert_equals_int (g_list_length (objects), 4); - fail_unless (current->data == src1); + assert_equals_int (g_list_length (objects), 7); - current = current->next; - transition = current->data; - assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); - assert_equals_uint64 (_START (transition), 1000); - assert_equals_uint64 (_DURATION (transition), 500); - - current = current->next; - transition = current->data; - assert_is_type (transition, GES_TYPE_TRANSITION_CLIP); - assert_equals_uint64 (_START (transition), 1000); - assert_equals_uint64 (_DURATION (transition), 500); - - current = current->next; - fail_unless (current->data == src3); - g_list_free_full (objects, gst_object_unref); - ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); - - GST_DEBUG ("Checking second layer"); - current = objects = ges_layer_get_clips (layer1); - assert_equals_int (g_list_length (objects), 2); - assert_is_type (current->data, GES_TYPE_TEST_CLIP); - assert_is_type (current->next->data, GES_TYPE_TEST_CLIP); g_list_free_full (objects, gst_object_unref); ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2); diff --git a/tests/check/ges/test-utils.h b/tests/check/ges/test-utils.h index 7b8199fe5d..f7382c065c 100644 --- a/tests/check/ges/test-utils.h +++ b/tests/check/ges/test-utils.h @@ -83,30 +83,37 @@ G_STMT_START { \ #define _DURATION(obj) GES_TIMELINE_ELEMENT_DURATION (obj) #define _INPOINT(obj) GES_TIMELINE_ELEMENT_INPOINT (obj) #define _PRIORITY(obj) GES_TIMELINE_ELEMENT_PRIORITY (obj) +#ifndef _END +#define _END(obj) (_START(obj) + _DURATION(obj)) +#endif #define CHECK_OBJECT_PROPS(obj, start, inpoint, duration) {\ - assert_equals_uint64 (_START (obj), start);\ - assert_equals_uint64 (_INPOINT (obj), inpoint);\ - assert_equals_uint64 (_DURATION (obj), duration);\ + fail_unless (_START (obj) == start, "%s start is %" GST_TIME_FORMAT " != %" GST_TIME_FORMAT, GES_TIMELINE_ELEMENT_NAME(obj), GST_TIME_ARGS (_START(obj)), GST_TIME_ARGS (start));\ + fail_unless (_INPOINT (obj) == inpoint, "%s inpoint is %" GST_TIME_FORMAT " != %" GST_TIME_FORMAT, GES_TIMELINE_ELEMENT_NAME(obj), GST_TIME_ARGS (_INPOINT(obj)), GST_TIME_ARGS (inpoint));\ + fail_unless (_DURATION (obj) == duration, "%s duration is %" GST_TIME_FORMAT " != %" GST_TIME_FORMAT, GES_TIMELINE_ELEMENT_NAME(obj), GST_TIME_ARGS (_DURATION(obj)), GST_TIME_ARGS (duration));\ } -#define check_layer(clip, layer_prio) { \ - GESLayer *tmplayer = ges_clip_get_layer ((clip)); \ - assert_equals_int (ges_layer_get_priority (tmplayer), (layer_prio)); \ - gst_object_unref (tmplayer); \ +#define check_layer(clip, layer_prio) { \ + fail_unless (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip) == (layer_prio), \ + "%s in layer %d instead of %d", GES_TIMELINE_ELEMENT_NAME (clip), \ + GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), layer_prio); \ } #define GES_TIMELINE_ELEMENT_FORMAT \ "s<%p>" \ " [ %" GST_TIME_FORMAT \ " (%" GST_TIME_FORMAT \ - ") - %" GST_TIME_FORMAT "]" + ") - %" GST_TIME_FORMAT "(%" GST_TIME_FORMAT") layer: %" G_GINT32_FORMAT "] " #define GES_TIMELINE_ELEMENT_ARGS(element) \ GES_TIMELINE_ELEMENT_NAME(element), element, \ GST_TIME_ARGS(GES_TIMELINE_ELEMENT_START(element)), \ GST_TIME_ARGS(GES_TIMELINE_ELEMENT_INPOINT(element)), \ - GST_TIME_ARGS(GES_TIMELINE_ELEMENT_DURATION(element)) + GST_TIME_ARGS(GES_TIMELINE_ELEMENT_DURATION(element)), \ + GST_TIME_ARGS(GES_TIMELINE_ELEMENT_MAX_DURATION(element)), \ + GES_TIMELINE_ELEMENT_LAYER_PRIORITY(element) +#define GES_ARGS GES_TIMELINE_ELEMENT_ARGS +#define GES_FORMAT GES_TIMELINE_ELEMENT_FORMAT void print_timeline(GESTimeline *timeline); diff --git a/tests/check/ges/timelineedition.c b/tests/check/ges/timelineedition.c index 144db177eb..8625780ba9 100644 --- a/tests/check/ges/timelineedition.c +++ b/tests/check/ges/timelineedition.c @@ -25,19 +25,21 @@ #define DEEP_CHECK(element, start, inpoint, duration) \ { \ GList *track_elements, *tmp; \ - \ - assert_equals_uint64 (_START (element), start); \ - assert_equals_uint64 (_INPOINT (element), inpoint); \ - assert_equals_uint64 (_DURATION (element), duration); \ + CHECK_OBJECT_PROPS (element, start, inpoint, duration) \ \ track_elements = GES_CONTAINER_CHILDREN (element); \ for (tmp = track_elements; tmp; tmp = tmp->next) { \ - assert_equals_uint64 (_START (tmp->data), start); \ - assert_equals_uint64 (_INPOINT (tmp->data), inpoint); \ - assert_equals_uint64 (_DURATION (tmp->data), duration); \ + CHECK_OBJECT_PROPS (tmp->data, start, inpoint, duration) \ } \ } +#define CHECK_CLIP(element, start, inpoint, duration, layer_prio) \ +{ \ + DEEP_CHECK(element, start, inpoint, duration);\ + check_layer (element, layer_prio); \ +}\ + + GST_START_TEST (test_basic_timeline_edition) { GESAsset *asset; @@ -288,7 +290,7 @@ GST_START_TEST (test_snapping) fail_unless (ges_track_element_get_track (trackelement) == track); assert_equals_uint64 (_DURATION (trackelement), 37); - ASSERT_OBJECT_REFCOUNT (trackelement, "track + timeline + clip", 4); + ASSERT_OBJECT_REFCOUNT (trackelement, "track + timeline + clip", 3); ASSERT_OBJECT_REFCOUNT (clip, "layer + timeline", 2); fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip1))); @@ -299,7 +301,7 @@ GST_START_TEST (test_snapping) assert_equals_uint64 (_DURATION (trackelement1), 15); /* Same ref logic */ - ASSERT_OBJECT_REFCOUNT (trackelement1, "First trackelement", 4); + ASSERT_OBJECT_REFCOUNT (trackelement1, "First trackelement", 3); ASSERT_OBJECT_REFCOUNT (clip1, "First clip", 2); fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip2))); @@ -310,7 +312,7 @@ GST_START_TEST (test_snapping) assert_equals_uint64 (_DURATION (trackelement2), 60); /* Same ref logic */ - ASSERT_OBJECT_REFCOUNT (trackelement2, "First trackelement", 4); + ASSERT_OBJECT_REFCOUNT (trackelement2, "First trackelement", 3); ASSERT_OBJECT_REFCOUNT (clip2, "First clip", 2); /* Snaping to edge, so no move */ @@ -322,8 +324,7 @@ GST_START_TEST (test_snapping) CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); /* Snaping to edge, so no move */ - fail_if (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM, - GES_EDGE_END, 27)); + ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 27); CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); CHECK_OBJECT_PROPS (trackelement1, 20, 0, 5); CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); @@ -338,144 +339,78 @@ GST_START_TEST (test_snapping) */ g_object_set (timeline, "snapping-distance", (guint64) 0, NULL); ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip1), 10); - CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); - CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); - CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + DEEP_CHECK (clip, 25, 0, 37); + DEEP_CHECK (clip1, 20, 0, 10); + DEEP_CHECK (clip2, 62, 0, 60); - /** - * New timeline(the "layers" are just to help reading diagram, nothing else): - * ------------ - * 0---------- - * | clip | - * 25---------62 - * inpoints 0----------------------- 10-------- - * | clip1 || clip2 | - * time 20---------------------- 72 --------122 - */ - /* Rolling involves only neighbour that are currently snapping */ - fail_unless (ges_timeline_element_roll_end (GES_TIMELINE_ELEMENT (clip1), - 62)); - fail_unless (ges_timeline_element_roll_end (GES_TIMELINE_ELEMENT (clip1), + /* clip and clip1 would fully overlap ... forbiden */ + fail_if (ges_timeline_element_roll_end (GES_TIMELINE_ELEMENT (clip1), 62)); + DEEP_CHECK (clip, 25, 0, 37); + DEEP_CHECK (clip1, 20, 0, 10); + DEEP_CHECK (clip2, 62, 0, 60); + fail_if (ges_timeline_element_roll_end (GES_TIMELINE_ELEMENT (clip1), 72) == TRUE); - CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); - CHECK_OBJECT_PROPS (trackelement1, 20, 0, 52); - CHECK_OBJECT_PROPS (trackelement2, 72, 10, 50); + DEEP_CHECK (clip, 25, 0, 37); + DEEP_CHECK (clip1, 20, 0, 10); + DEEP_CHECK (clip2, 62, 0, 60); /** - * 0---------- - * | clip | - * 25---------62 - * inpoints 5--------------- 10-------- - * | clip1 || clip2 | - * time 25------------- 72 --------122 + * 30-------+0-------------+ + * inpoints 0-----------5 clip || clip2 | + * | clip1 |------- 62 -----------122 + * time 20----------30 */ g_object_set (timeline, "snapping-distance", (guint64) 4, NULL); - fail_unless (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (clip1), + fail_unless (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (clip), 28) == TRUE); - CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); - CHECK_OBJECT_PROPS (trackelement1, 25, 5, 47); - CHECK_OBJECT_PROPS (trackelement2, 72, 10, 50); + DEEP_CHECK (clip, 30, 5, 32); + DEEP_CHECK (clip1, 20, 0, 10); + DEEP_CHECK (clip2, 62, 0, 60); /** - * 0---------- - * | clip | - * 25---------62 - * inpoints 5---------- 0--------- - * | clip1 || clip2 | - * time 25-------- 62 --------122 + * 30-------+0-------------+ + * inpoints 0-----------5 clip || clip2 | + * | clip1 |------- 62 -----------122 + * time 20----------30 */ + fail_unless (ges_timeline_element_set_inpoint (GES_TIMELINE_ELEMENT (clip2), + 5)); fail_unless (ges_timeline_element_roll_start (GES_TIMELINE_ELEMENT (clip2), - 59) == TRUE); - CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); - CHECK_OBJECT_PROPS (trackelement1, 25, 5, 37); - CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + 60)); + DEEP_CHECK (clip, 30, 5, 32); + DEEP_CHECK (clip1, 20, 0, 10); + DEEP_CHECK (clip2, 62, 5, 60); /** - * 0---------- - * | clip | - * 25--------62 - * inpoints 5-----------------------+ - * | clip1 || clip2 | - * time 30------------]--------122 - * 67 + * 30-------+0-------------+ + * inpoints 0-----------5 clip || clip2 | + * | clip1 |------- 62 -----------122 + * time 20----------30 */ - ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL, GES_EDGE_NONE, 30); - CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); - CHECK_OBJECT_PROPS (trackelement1, 30, 5, 37); - CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + /* Moving clip1 to 26 would lead to snapping to 30, and clip1 and clip + * would fully overlap */ + fail_if (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 26) == TRUE); + DEEP_CHECK (clip, 30, 5, 32); + DEEP_CHECK (clip1, 20, 0, 10); + DEEP_CHECK (clip2, 62, 5, 60); /** - * inpoints 0----------5-------------- - * | clip || clip1 | - * time 25----------62----------99 - * 0----------- - * | clip2 | - * 98--------168 - * Check that clip1 snaps with the end of clip */ - fail_unless (ges_timeline_element_ripple (GES_TIMELINE_ELEMENT (clip1), - 58) == TRUE); - CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); - CHECK_OBJECT_PROPS (trackelement1, 62, 5, 37); - CHECK_OBJECT_PROPS (trackelement2, 94, 0, 60); - - /** - * inpoints 0----------- 5------------ 0----------- - * | clip || clip1 | | clip2 | - * time 25----------62----------99 110--------170 - */ - ges_container_edit (clip2, NULL, -1, GES_EDIT_MODE_NORMAL, GES_EDGE_NONE, - 110); - CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); - CHECK_OBJECT_PROPS (trackelement1, 62, 5, 37); - CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60); - - /** - * inpoints 0----------5 5 --------- 0---------- - * | clip | | clip1 || clip2 | - * time 25---------62 73---------110--------170 - */ + * 30-------+0-------------+ + * inpoints 5 clip || clip2 |-------------+ + * +------- 62 -----------122 clip1 | + * time +------------132 + * Check that clip1 snaps with the end of clip2 */ fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL, - GES_EDGE_NONE, 72) == TRUE); - CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); - CHECK_OBJECT_PROPS (trackelement1, 73, 5, 37); - CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60); - - /** - * inpoints 0----------5---------- 0---------- - * | clip || clip1 | | clip2 | - * time 25---------62-------- 99 110--------170 - */ - fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL, - GES_EDGE_NONE, 58) == TRUE); - CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); - CHECK_OBJECT_PROPS (trackelement1, 62, 5, 37); - CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60); - - - /** - * inpoints 0----------5---------- 0---------- - * | clip || clip1 || clip2 | - * time 25---------62--------110--------170 - */ - g_object_set (clip1, "duration", (guint64) 46, NULL); - CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); - CHECK_OBJECT_PROPS (trackelement1, 62, 5, 48); - CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60); - - /** - * inpoints 5----------- 0--------- 0---------- - * | clip1 || clip2 || clip | - * time 62---------110--------170--------207 - */ - ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_NORMAL, GES_EDGE_NONE, 168); - CHECK_OBJECT_PROPS (trackelement, 170, 0, 37); - CHECK_OBJECT_PROPS (trackelement1, 62, 5, 48); - CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60); + GES_EDGE_NONE, 125) == TRUE); + DEEP_CHECK (clip, 30, 5, 32); + DEEP_CHECK (clip1, 122, 0, 10); + DEEP_CHECK (clip2, 62, 5, 60); /* Check we didn't lose/screwed any references */ - ASSERT_OBJECT_REFCOUNT (trackelement, "First trackelement", 4); - ASSERT_OBJECT_REFCOUNT (trackelement1, "Second trackelement", 4); - ASSERT_OBJECT_REFCOUNT (trackelement2, "Third trackelement", 4); + ASSERT_OBJECT_REFCOUNT (trackelement, "First trackelement", 3); + ASSERT_OBJECT_REFCOUNT (trackelement1, "Second trackelement", 3); + ASSERT_OBJECT_REFCOUNT (trackelement2, "Third trackelement", 3); ASSERT_OBJECT_REFCOUNT (clip, "First clip", 2); ASSERT_OBJECT_REFCOUNT (clip1, "Second clip", 2); ASSERT_OBJECT_REFCOUNT (clip2, "Third clip", 2); @@ -782,6 +717,15 @@ GST_START_TEST (test_timeline_edition_mode) assert_equals_int (ges_layer_get_priority (layer), 2); gst_object_unref (layer); + /* Roll end clip back to 35 */ + /* Can not move to the first layer as clip2 should move to a layer with priority < 0 */ + fail_if (ges_container_edit (clip, NULL, 0, GES_EDIT_MODE_RIPPLE, + GES_EDGE_END, 52)); + CHECK_OBJECT_PROPS (trackelement, 32, 5, 3); + CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); + CHECK_OBJECT_PROPS (trackelement2, 35, 0, 60); + assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), 2); + /* Ripple clip end to 52 * New timeline: * ------------ @@ -795,8 +739,7 @@ GST_START_TEST (test_timeline_edition_mode) * 32------52 * */ - /* Can not move to the first layer as clip2 should move to a layer with priority < 0 */ - fail_unless (ges_container_edit (clip, NULL, 0, GES_EDIT_MODE_RIPPLE, + fail_unless (ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_RIPPLE, GES_EDGE_END, 52) == TRUE); CHECK_OBJECT_PROPS (trackelement, 32, 5, 20); CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10); @@ -817,9 +760,9 @@ GST_START_TEST (test_timeline_edition_mode) /* We have 3 references: * track + timeline + clip */ - ASSERT_OBJECT_REFCOUNT (trackelement, "First trackelement", 4); - ASSERT_OBJECT_REFCOUNT (trackelement1, "Second trackelement", 4); - ASSERT_OBJECT_REFCOUNT (trackelement2, "Third trackelement", 4); + ASSERT_OBJECT_REFCOUNT (trackelement, "First trackelement", 3); + ASSERT_OBJECT_REFCOUNT (trackelement1, "Second trackelement", 3); + ASSERT_OBJECT_REFCOUNT (trackelement2, "Third trackelement", 3); ASSERT_OBJECT_REFCOUNT (clip, "First clip", 2); ASSERT_OBJECT_REFCOUNT (clip1, "Second clip", 2); ASSERT_OBJECT_REFCOUNT (clip2, "Third clip", 2); @@ -904,16 +847,13 @@ GST_START_TEST (test_timeline_edition_mode) /* Snaping to edge, so no move */ g_object_set (timeline, "snapping-distance", (guint64) 3, NULL); - fail_if (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM, - GES_EDGE_END, 27)); + ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 27); CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); CHECK_OBJECT_PROPS (trackelement1, 20, 0, 5); CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); /* Snaping to edge, so no move */ - fail_if (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM, - GES_EDGE_END, 27)); - + ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 27); CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); CHECK_OBJECT_PROPS (trackelement1, 20, 0, 5); CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); @@ -939,13 +879,13 @@ GST_START_TEST (test_timeline_edition_mode) * 0---------- * | clip | * 25---------62 + * ------------------------------------------------- * inpoints 0----------------------- 10-------- * | clip1 || clip2 | * time 20---------------------- 72 --------122 */ /* Rolling involves only neighbours that are currently snapping */ - fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_ROLL, - GES_EDGE_END, 62) == TRUE); + ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_ROLL, GES_EDGE_END, 62); fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_ROLL, GES_EDGE_END, 72) == TRUE); CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); @@ -968,19 +908,8 @@ GST_START_TEST (test_timeline_edition_mode) CHECK_OBJECT_PROPS (trackelement1, 25, 5, 47); CHECK_OBJECT_PROPS (trackelement2, 72, 10, 50); - /** - * 0---------- - * | clip | - * 25---------62 - * inpoints 5---------- 0--------- - * | clip1 || clip2 | - * time 25-------- 62 --------122 - */ - fail_unless (ges_container_edit (clip2, NULL, -1, GES_EDIT_MODE_ROLL, - GES_EDGE_START, 59) == TRUE); - CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); - CHECK_OBJECT_PROPS (trackelement1, 25, 5, 37); - CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); + fail_if (ges_container_edit (clip2, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_START, 59)); ges_deinit (); } @@ -1040,131 +969,70 @@ GST_START_TEST (test_groups) g_list_free (clips); fail_unless (GES_IS_GROUP (group)); - DEEP_CHECK (c, 0, 0, 10); - DEEP_CHECK (c1, 10, 0, 10); - DEEP_CHECK (c2, 20, 0, 10); + CHECK_CLIP (c, 0, 0, 10, 0); + CHECK_CLIP (c1, 10, 0, 10, 1); + CHECK_CLIP (c2, 20, 0, 10, 1); CHECK_OBJECT_PROPS (group, 0, 0, 30); c3 = ges_layer_add_asset (layer, asset, 30, 0, 20, GES_TRACK_TYPE_UNKNOWN); c4 = ges_layer_add_asset (layer1, asset, 40, 0, 20, GES_TRACK_TYPE_UNKNOWN); c5 = ges_layer_add_asset (layer2, asset, 50, 0, 20, GES_TRACK_TYPE_UNKNOWN); - DEEP_CHECK (c3, 30, 0, 20); - DEEP_CHECK (c4, 40, 0, 20); - DEEP_CHECK (c5, 50, 0, 20); + CHECK_CLIP (c3, 30, 0, 20, 0); + CHECK_CLIP (c4, 40, 0, 20, 1); + CHECK_CLIP (c5, 50, 0, 20, 2); check_layer (c, 0); check_layer (c1, 1); check_layer (c2, 1); - check_layer (c3, 0); - check_layer (c4, 1); - check_layer (c5, 2); fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, -1, GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, 10) == TRUE); - DEEP_CHECK (c, 10, 0, 10); - DEEP_CHECK (c1, 10, 0, 10); - DEEP_CHECK (c2, 30, 0, 10); - DEEP_CHECK (c3, 40, 0, 20); - DEEP_CHECK (c4, 50, 0, 20); - DEEP_CHECK (c5, 60, 0, 20); - check_layer (c, 0); - check_layer (c1, 1); - check_layer (c2, 1); - check_layer (c3, 0); - check_layer (c4, 1); - check_layer (c5, 2); + CHECK_CLIP (c, 10, 0, 10, 0); + CHECK_CLIP (c1, 20, 0, 10, 1); + CHECK_CLIP (c2, 30, 0, 10, 1); + CHECK_CLIP (c3, 40, 0, 20, 0); + CHECK_CLIP (c4, 50, 0, 20, 1); + CHECK_CLIP (c5, 60, 0, 20, 2); fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, 1, GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, 10) == TRUE); - DEEP_CHECK (c, 10, 0, 10); - DEEP_CHECK (c1, 10, 0, 10); - DEEP_CHECK (c2, 30, 0, 10); - DEEP_CHECK (c3, 40, 0, 20); - DEEP_CHECK (c4, 50, 0, 20); - DEEP_CHECK (c5, 60, 0, 20); - check_layer (c, 1); - check_layer (c1, 2); - check_layer (c2, 2); - check_layer (c3, 1); - check_layer (c4, 2); - check_layer (c5, 3); + CHECK_CLIP (c, 10, 0, 10, 1); + CHECK_CLIP (c1, 20, 0, 10, 2); + CHECK_CLIP (c2, 30, 0, 10, 2); + CHECK_CLIP (c3, 40, 0, 20, 1); + CHECK_CLIP (c4, 50, 0, 20, 2); + CHECK_CLIP (c5, 60, 0, 20, 3); - fail_unless (ges_container_edit (GES_CONTAINER (c1), NULL, 2, + fail_if (ges_container_edit (GES_CONTAINER (c1), NULL, 2, GES_EDIT_MODE_RIPPLE, GES_EDGE_END, 40) == TRUE); - DEEP_CHECK (c, 10, 0, 10); - DEEP_CHECK (c1, 10, 0, 30); - DEEP_CHECK (c2, 50, 0, 10); - DEEP_CHECK (c3, 60, 0, 20); - DEEP_CHECK (c4, 70, 0, 20); - DEEP_CHECK (c5, 80, 0, 20); - check_layer (c, 1); - check_layer (c1, 2); - check_layer (c2, 2); - check_layer (c3, 1); - check_layer (c4, 2); - check_layer (c5, 3); - - fail_unless (ges_container_edit (GES_CONTAINER (c1), NULL, 2, + fail_if (ges_container_edit (GES_CONTAINER (c1), NULL, 2, GES_EDIT_MODE_RIPPLE, GES_EDGE_END, 30) == TRUE); - DEEP_CHECK (c, 10, 0, 10); - DEEP_CHECK (c1, 10, 0, 20); - DEEP_CHECK (c2, 40, 0, 10); - DEEP_CHECK (c3, 50, 0, 20); - DEEP_CHECK (c4, 60, 0, 20); - DEEP_CHECK (c5, 70, 0, 20); - check_layer (c, 1); - check_layer (c1, 2); - check_layer (c2, 2); - check_layer (c3, 1); - check_layer (c4, 2); - check_layer (c5, 3); - + CHECK_CLIP (c, 10, 0, 10, 1); + CHECK_CLIP (c1, 20, 0, 10, 2); + CHECK_CLIP (c2, 30, 0, 10, 2); + CHECK_CLIP (c3, 40, 0, 20, 1); + CHECK_CLIP (c4, 50, 0, 20, 2); + CHECK_CLIP (c5, 60, 0, 20, 3); fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, 0, GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, 0) == TRUE); - DEEP_CHECK (c, 0, 0, 10); - DEEP_CHECK (c1, 10, 0, 20); - DEEP_CHECK (c2, 30, 0, 10); - DEEP_CHECK (c3, 40, 0, 20); - DEEP_CHECK (c4, 50, 0, 20); - DEEP_CHECK (c5, 60, 0, 20); - check_layer (c, 0); - check_layer (c1, 1); - check_layer (c2, 1); - check_layer (c3, 0); - check_layer (c4, 1); - check_layer (c5, 2); - - fail_if (ges_container_edit (GES_CONTAINER (c2), NULL, -1, - GES_EDIT_MODE_ROLL, GES_EDGE_END, 40) == TRUE); - DEEP_CHECK (c, 0, 0, 10); - DEEP_CHECK (c1, 10, 0, 20); - DEEP_CHECK (c2, 30, 0, 10); - DEEP_CHECK (c3, 40, 0, 20); - DEEP_CHECK (c4, 50, 0, 20); - DEEP_CHECK (c5, 60, 0, 20); - check_layer (c, 0); - check_layer (c1, 1); - check_layer (c2, 1); - check_layer (c3, 0); - check_layer (c4, 1); - check_layer (c5, 2); + CHECK_CLIP (c, 0, 0, 10, 0); + CHECK_CLIP (c1, 10, 0, 10, 1); + CHECK_CLIP (c2, 20, 0, 10, 1); + CHECK_CLIP (c3, 30, 0, 20, 0); + CHECK_CLIP (c4, 40, 0, 20, 1); + CHECK_CLIP (c5, 50, 0, 20, 2); + CHECK_OBJECT_PROPS (group, 0, 0, 30); fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, 0, GES_EDIT_MODE_TRIM, GES_EDGE_START, 5) == TRUE); - CHECK_OBJECT_PROPS (c, 5, 5, 5); - DEEP_CHECK (c1, 10, 0, 20); - DEEP_CHECK (c2, 30, 0, 10); - DEEP_CHECK (c3, 40, 0, 20); - DEEP_CHECK (c4, 50, 0, 20); - DEEP_CHECK (c5, 60, 0, 20); - CHECK_OBJECT_PROPS (group, 5, 0, 35); - check_layer (c, 0); - check_layer (c1, 1); - check_layer (c2, 1); - check_layer (c3, 0); - check_layer (c4, 1); - check_layer (c5, 2); + CHECK_CLIP (c, 5, 5, 5, 0); + CHECK_CLIP (c1, 10, 0, 10, 1); + CHECK_CLIP (c2, 20, 0, 10, 1); + CHECK_CLIP (c3, 30, 0, 20, 0); + CHECK_CLIP (c4, 40, 0, 20, 1); + CHECK_CLIP (c5, 50, 0, 20, 2); + CHECK_OBJECT_PROPS (group, 5, 0, 25); gst_object_unref (timeline); gst_object_unref (asset); diff --git a/tests/check/ges/uriclip.c b/tests/check/ges/uriclip.c index 2444f8ae89..3eb9dfe464 100644 --- a/tests/check/ges/uriclip.c +++ b/tests/check/ges/uriclip.c @@ -263,7 +263,7 @@ GST_START_TEST (test_filesource_images) fail_unless (GES_IS_IMAGE_SOURCE (track_element)); ASSERT_OBJECT_REFCOUNT (track_element, "1 in track, 1 in clip 2 in timeline", - 4); + 3); gst_object_unref (asset); gst_object_unref (timeline); diff --git a/tests/check/python/common.py b/tests/check/python/common.py index 0da9927b60..52d62c8cf0 100644 --- a/tests/check/python/common.py +++ b/tests/check/python/common.py @@ -194,7 +194,16 @@ class GESSimpleTimelineTest(GESTest): return clip - def assertTimelineTopology(self, topology): + def append_clip(self, layer=0): + layer = self.timeline.get_layers()[layer] + clip = GES.TestClip() + clip.props.start = layer.get_duration() + clip.props.duration = 10 + self.assertTrue(layer.add_clip(clip)) + + return clip + + def assertTimelineTopology(self, topology, groups=[]): res = [] for layer in self.timeline.get_layers(): layer_timings = [] @@ -203,6 +212,14 @@ class GESSimpleTimelineTest(GESTest): (type(clip), clip.props.start, clip.props.duration)) res.append(layer_timings) + if topology != res: + Gst.error(self.timeline_as_str()) + self.assertEqual(topology, res) - self.assertEqual(topology, res) - return res \ No newline at end of file + timeline_groups = self.timeline.get_groups() + if groups and timeline_groups: + for i, group in enumerate(groups): + self.assertEqual(set(group), set(timeline_groups[i].get_children(False))) + self.assertEqual(len(timeline_groups), i + 1) + + return res diff --git a/tests/check/python/test_group.py b/tests/check/python/test_group.py index feafd13808..eb5a874e68 100644 --- a/tests/check/python/test_group.py +++ b/tests/check/python/test_group.py @@ -251,6 +251,8 @@ class TestGroup(common.GESSimpleTimelineTest): self.assertEqual(audio_transition.props.duration, 10) def test_moving_group_snapping_from_the_middle(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() snapped_positions = [] def snapping_started_cb(timeline, first_element, second_element, position, snapped_positions): @@ -272,10 +274,88 @@ class TestGroup(common.GESSimpleTimelineTest): group = GES.Container.group(clips[1:3]) self.assertIsNotNone(group) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 5), + (GES.TestClip, 5, 5), + (GES.TestClip, 10, 5), + (GES.TestClip, 15, 5), + ], + ], groups=[clips[1:3]]) + self.assertEqual(clips[1].props.start, 5) self.assertEqual(clips[2].props.start, 10) clips[2].edit([], 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 11) - self.assertEqual(snapped_positions[0], clips[2].start + clips[2].duration) - self.assertEqual(clips[1].props.start, 5) - self.assertEqual(clips[2].props.start, 10) + self.assertEqual(snapped_positions[0], 5) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 5), + (GES.TestClip, 5, 5), + (GES.TestClip, 10, 5), + (GES.TestClip, 15, 5), + ], + ], groups=[clips[1:3]]) + + def test_rippling_with_group(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + for _ in range(4): + self.append_clip() + + snapped_positions = [] + def snapping_started_cb(timeline, first_element, second_element, + position, snapped_positions): + snapped_positions.append(position) + + self.timeline.props.snapping_distance = 5 + self.timeline.connect("snapping-started", snapping_started_cb, + snapped_positions) + + clips = self.layer.get_clips() + self.assertEqual(len(clips), 4) + + group_clips = clips[1:3] + GES.Container.group(group_clips) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ], + ], groups=[group_clips]) + + self.assertFalse(clips[2].edit([], 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 5)) + + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ], + ], groups=[group_clips]) + + # Negative start... + self.assertFalse(clips[2].edit([], 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 1)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ], + ], groups=[group_clips]) + + self.assertTrue(clips[2].edit([], 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 20)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + ], + [ + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ], + ], groups=[group_clips]) \ No newline at end of file diff --git a/tests/check/python/test_timeline.py b/tests/check/python/test_timeline.py index 6f3b46557b..67905b0eef 100644 --- a/tests/check/python/test_timeline.py +++ b/tests/check/python/test_timeline.py @@ -35,7 +35,7 @@ Gst.init(None) GES.init() -class TestTimeline(unittest.TestCase): +class TestTimeline(common.GESSimpleTimelineTest): def test_signals_not_emitted_when_loading(self): mainloop = common.create_main_loop() @@ -63,13 +63,31 @@ class TestTimeline(unittest.TestCase): self.assertTrue(loaded_called) handle.assert_not_called() + def test_timeline_duration(self): + self.append_clip() + self.append_clip() + clips = self.layer.get_clips() -class TestSplitting(common.GESSimpleTimelineTest): - def setUp(self): - self.track_types = [GES.TrackType.AUDIO] - super(TestSplitting, self).setUp() + self.assertEqual(self.timeline.props.duration, 20) + self.layer.remove_clip(clips[1]) + self.assertEqual(self.timeline.props.duration, 10) + + self.append_clip() + self.append_clip() + clips = self.layer.get_clips() + self.assertEqual(self.timeline.props.duration, 30) + + group = GES.Container.group(clips[1:]) + self.assertEqual(self.timeline.props.duration, 30) + + group1 = GES.Container.group([]) + group1.add(group) + self.assertEqual(self.timeline.props.duration, 30) def test_spliting_with_auto_transition_on_the_left(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + self.timeline.props.auto_transition = True clip1 = self.add_clip(0, 0, 100) clip2 = self.add_clip(50, 0, 100) @@ -117,6 +135,102 @@ class TestEditing(common.GESSimpleTimelineTest): self.assertEqual(len(self.layer.get_clips()), 1) self.assertEqual(len(layer2.get_clips()), 1) + def activate_snapping(self): + self.timeline.set_snapping_distance(5) + self.snapped_at = [] + + def _snapped_cb(timeline, elem1, elem2, position): + self.snapped_at.append(position) + Gst.error('%s' % position) + + def _snapped_end_cb(timeline, elem1, elem2, position): + if self.snapped_at: # Ignoring first snap end. + self.snapped_at.append(Gst.CLOCK_TIME_NONE) + Gst.error('%s' % position) + + self.timeline.connect("snapping-started", _snapped_cb) + self.timeline.connect("snapping-ended", _snapped_end_cb) + + def test_snap_start_snap_end(self): + clip = self.append_clip() + self.append_clip() + + self.activate_snapping() + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + clip.props.start = 18 + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + ] + ]) + self.assertEqual(self.snapped_at, [20]) + + clip.props.start = 30 + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 10, 10), + (GES.TestClip, 30, 10), + ] + ]) + self.assertEqual(self.snapped_at, [20, Gst.CLOCK_TIME_NONE]) + + clip.props.start = 18 + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + ] + ]) + self.assertEqual(self.snapped_at, [20, Gst.CLOCK_TIME_NONE, + Gst.CLOCK_TIME_NONE, 20]) + clip.props.start = 19 + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + ] + ]) + self.assertEqual(self.snapped_at, [20, Gst.CLOCK_TIME_NONE, + Gst.CLOCK_TIME_NONE, 20]) + + def test_rippling_snaps(self): + self.timeline.props.auto_transition = True + self.append_clip() + clip = self.append_clip() + + self.activate_snapping() + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + clip.edit([], 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 15) + self.assertEqual(self.snapped_at, [10]) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + clip.edit([], 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 20) + self.assertEqual(self.snapped_at, [10, Gst.CLOCK_TIME_NONE]) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 20, 10), + ] + ]) + def test_transition_moves_when_rippling_to_another_layer(self): self.timeline.props.auto_transition = True clip1 = self.add_clip(0, 0, 100) @@ -160,6 +274,451 @@ class TestEditing(common.GESSimpleTimelineTest): GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 35 * Gst.SECOND) self.assertEqual(len(self.layer.get_clips()), 4) + def test_trim_transition(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + + self.timeline.props.auto_transition = True + self.add_clip(0, 0, 10) + self.add_clip(5, 0, 10) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TransitionClip, 5, 5), + (GES.TestClip, 5, 10), + ] + ]) + transition = self.layer.get_clips()[1] + self.assertTrue(transition.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 7)) + + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TransitionClip, 7, 3), + (GES.TestClip, 7, 8), + ] + ]) + + def test_trim_start(self): + clip = self.append_clip() + self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 10, 10), + ] + ]) + + self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_NONE, 0)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 10, 10), + ] + ]) + + def test_ripple_end(self): + clip = self.append_clip() + clip.set_max_duration(20) + self.append_clip().set_max_duration(10) + self.append_clip().set_max_duration(10) + self.print_timeline() + self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 20)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 20), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ] + ]) + + self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 15)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 15), + (GES.TestClip, 15, 10), + (GES.TestClip, 25, 10), + ] + ]) + + def test_move_group_full_overlap(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + + for _ in range(4): + self.append_clip() + clips = self.layer.get_clips() + + self.assertTrue(clips[0].ripple(20)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + (GES.TestClip, 40, 10), + (GES.TestClip, 50, 10), + ] + ]) + group = GES.Container.group(clips[1:]) + self.print_timeline() + self.assertFalse(group.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0)) + self.print_timeline() + self.assertTimelineTopology([ + [ + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + (GES.TestClip, 40, 10), + (GES.TestClip, 50, 10), + ] + ]) + + self.assertFalse(clips[1].edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0)) + self.print_timeline() + self.assertTimelineTopology([ + [ + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + (GES.TestClip, 40, 10), + (GES.TestClip, 50, 10), + ] + ]) + + def test_trim_inside_group(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + + for _ in range(2): + self.append_clip() + clips = self.layer.get_clips() + group = GES.Container.group(clips) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + self.assertEqual(group.props.start, 0) + self.assertEqual(group.props.duration, 20) + + clips[0].trim(5) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 5, 5), + (GES.TestClip, 10, 10), + ] + ]) + self.assertEqual(group.props.start, 5) + self.assertEqual(group.props.duration, 15) + + def test_trim_end_past_max_duration(self): + clip = self.append_clip() + max_duration = clip.props.duration + clip.set_max_duration(max_duration) + self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 5, 5), + ] + ]) + + self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 5, 5), + ] + ]) + + +class TestInvalidOverlaps(common.GESSimpleTimelineTest): + + def test_adding_or_moving(self): + clip1 = self.add_clip(start=10, in_point=0, duration=3) + self.assertIsNotNone(clip1) + + def check_add_move_clip(start, duration): + self.timeline.props.auto_transition = True + self.layer.props.auto_transition = True + clip2 = GES.TestClip() + clip2.props.start = start + clip2.props.duration = duration + self.assertFalse(self.layer.add_clip(clip2)) + self.assertEqual(len(self.layer.get_clips()), 1) + + # Add the clip at a different position. + clip2.props.start = 25 + self.assertTrue(self.layer.add_clip(clip2)) + self.assertEqual(clip2.props.start, 25) + + # Try to move the second clip by editing it. + self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, start)) + self.assertEqual(clip2.props.start, 25) + + # Try to put it in a group and move the group. + clip3 = GES.TestClip() + clip3.props.start = 20 + clip3.props.duration = 1 + self.assertTrue(self.layer.add_clip(clip3)) + group = GES.Container.group([clip3, clip2]) + self.assertTrue(group.props.start, 20) + self.assertFalse(group.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, start - 5)) + self.assertEqual(group.props.start, 20) + self.assertEqual(clip3.props.start, 20) + self.assertEqual(clip2.props.start, 25) + + for clip in group.ungroup(False): + self.assertTrue(self.layer.remove_clip(clip)) + + # clip1 contains... + check_add_move_clip(start=10, duration=1) + check_add_move_clip(start=11, duration=1) + check_add_move_clip(start=12, duration=1) + + def test_splitting(self): + clip1 = self.add_clip(start=9, in_point=0, duration=3) + clip2 = self.add_clip(start=10, in_point=0, duration=4) + clip3 = self.add_clip(start=12, in_point=0, duration=3) + + self.assertIsNone(clip1.split(13)) + self.assertIsNone(clip1.split(8)) + + self.assertIsNone(clip3.split(12)) + self.assertIsNone(clip3.split(15)) + + def test_changing_duration(self): + clip1 = self.add_clip(start=9, in_point=0, duration=2) + clip2 = self.add_clip(start=10, in_point=0, duration=2) + + self.assertFalse(clip1.set_start(10)) + self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, clip2.props.start + clip2.props.duration)) + self.assertFalse(clip1.ripple_end(clip2.props.start + clip2.props.duration)) + self.assertFalse(clip1.roll_end(clip2.props.start + clip2.props.duration)) + + # clip2's end edge to the left, to decrease its duration. + self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, clip1.props.start + clip1.props.duration)) + self.assertFalse(clip2.ripple_end(clip1.props.start + clip1.props.duration)) + self.assertFalse(clip2.roll_end(clip1.props.start + clip1.props.duration)) + + # clip2's start edge to the left, to increase its duration. + self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, clip1.props.start)) + self.assertFalse(clip2.trim(clip1.props.start)) + + # clip1's start edge to the right, to decrease its duration. + self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, clip2.props.start)) + self.assertFalse(clip1.trim(clip2.props.start)) + + def test_rippling_backward(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + self.maxDiff = None + for i in range(4): + self.append_clip() + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ] + ]) + + clip = self.layer.get_clips()[2] + self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip.props.start - 20)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ] + ]) + self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip.props.start + 10)) + + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + (GES.TestClip, 30, 10), + (GES.TestClip, 40, 10), + ] + ]) + + self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip.props.start -20)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + (GES.TestClip, 30, 10), + (GES.TestClip, 40, 10), + ] + ]) + + def test_rolling(self): + clip1 = self.add_clip(start=9, in_point=0, duration=2) + clip2 = self.add_clip(start=10, in_point=0, duration=2) + clip3 = self.add_clip(start=11, in_point=0, duration=2) + + # Rolling clip1's end -1 would lead to clip3 to overlap 100% with clip2. + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 9, 2), + (GES.TestClip, 10, 2), + (GES.TestClip, 11, 2) + ] + ]) + self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, clip1.props.start + clip1.props.duration - 1)) + self.assertFalse(clip1.roll_end(13)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 9, 2), + (GES.TestClip, 10, 2), + (GES.TestClip, 11, 2) + ] + ]) + + # Rolling clip3's start +1 would lead to clip1 to overlap 100% with clip2. + self.assertFalse(clip3.edit([], -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 12)) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 9, 2), + (GES.TestClip, 10, 2), + (GES.TestClip, 11, 2) + ] + ]) + + def test_layers(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + self.maxDiff = None + self.timeline.append_layer() + + for i in range(2): + self.append_clip() + self.append_clip(1) + + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ], + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + clip = self.layer.get_clips()[0] + self.assertFalse(clip.edit([], 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ], + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + def test_rippling(self): + self.timeline.remove_track(self.timeline.get_tracks()[0]) + clip1 = self.add_clip(start=9, in_point=0, duration=2) + clip2 = self.add_clip(start=10, in_point=0, duration=2) + clip3 = self.add_clip(start=11, in_point=0, duration=2) + + # Rippling clip2's start -2 would bring clip3 exactly on top of clip1. + self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 8)) + self.assertFalse(clip2.ripple(8)) + + # Rippling clip1's end -1 would bring clip3 exactly on top of clip2. + self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 8)) + self.assertFalse(clip1.ripple_end(8)) + + def test_move_group_to_layer(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + self.append_clip() + self.append_clip() + self.append_clip() + + clips = self.layer.get_clips() + + clips[1].props.start += 2 + group = GES.Container.group(clips[1:]) + self.assertTrue(clips[1].edit([], 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, + group.props.start)) + + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + ], + [ + (GES.TestClip, 12, 10), + (GES.TestClip, 20, 10), + ] + ]) + + clips[0].props.start = 15 + self.assertTimelineTopology([ + [ + (GES.TestClip, 15, 10), + ], + [ + (GES.TestClip, 12, 10), + (GES.TestClip, 20, 10), + ] + ]) + + self.assertFalse(clips[1].edit([], 0, GES.EditMode.EDIT_NORMAL, + GES.Edge.EDGE_NONE, group.props.start)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 15, 10), + ], + [ + (GES.TestClip, 12, 10), + (GES.TestClip, 20, 10), + ] + ]) + + def test_move_group_with_overlaping_clips(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + self.append_clip() + self.append_clip() + self.append_clip() + + self.timeline.props.auto_transition = True + clips = self.layer.get_clips() + + clips[1].props.start += 5 + group = GES.Container.group(clips[1:]) + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 10), + (GES.TestClip, 15, 10), + (GES.TransitionClip, 20, 5), + (GES.TestClip, 20, 10), + ] + ]) + + clips[0].props.start = 30 + self.assertTimelineTopology([ + [ + (GES.TestClip, 15, 10), + (GES.TransitionClip, 20, 5), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ] + ]) + + # the 3 clips would overlap + self.assertFalse(clips[1].edit([], 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 25)) + self.assertTimelineTopology([ + [ + (GES.TestClip, 15, 10), + (GES.TransitionClip, 20, 5), + (GES.TestClip, 20, 10), + (GES.TestClip, 30, 10), + ] + ]) + class TestSnapping(common.GESSimpleTimelineTest): @@ -180,14 +739,77 @@ class TestSnapping(common.GESSimpleTimelineTest): clip2.props.start - 1) self.assertEqual(clip2.props.start, split_position) + def test_trim_snapps_inside_group(self): + self.track_types = [GES.TrackType.AUDIO] + super().setUp() + + self.timeline.props.auto_transition = True + self.timeline.set_snapping_distance(5) + + snaps = [] + def snapping_started_cb(timeline, element1, element2, dist, self): + snaps.append(set([element1, element2])) + + self.timeline.connect('snapping-started', snapping_started_cb, self) + clip = self.append_clip() + clip1 = self.append_clip() + + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + + clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 15) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 10, 10), + ] + ]) + self.assertEqual(snaps[0], set([clip.get_children(False)[0], clip1.get_children(False)[0]])) + + clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 16) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 0, 10), + (GES.TestClip, 16, 4), + ] + ]) + + def test_trim_no_snapping_on_same_clip(self): + self.timeline.props.auto_transition = True + self.timeline.set_snapping_distance(1) + + not_called = [] + def snapping_started_cb(timeline, element1, element2, dist, self): + not_called.append("No snapping should happen") + + self.timeline.connect('snapping-started', snapping_started_cb, self) + clip = self.append_clip() + clip.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5) + self.assertEqual(not_called, []) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 5, 5), + ] + ]) + + clip.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 4) + self.assertEqual(not_called, []) + self.assertTimelineTopology([ + [ # Unique layer + (GES.TestClip, 4, 6), + ] + ]) + def test_no_snapping_on_split(self): self.timeline.props.auto_transition = True self.timeline.set_snapping_distance(1) not_called = [] def snapping_started_cb(timeline, element1, element2, dist, self): - Gst.error("Here %s %s" % (Gst.TIME_ARGS(element1.props.start + element1.props.duration), - Gst.TIME_ARGS(element2.props.start))) not_called.append("No snapping should happen") self.timeline.connect('snapping-started', snapping_started_cb, self) @@ -296,9 +918,9 @@ class TestTransitions(common.GESSimpleTimelineTest): self.assertLess(clip1.props.start + clip1.props.duration, clip2.props.start + clip2.props.duration) self.assertEqual(len(clips), 3) - # Even though 3 clips overlap 1 transition will be created + # 3 clips would be overlapping, 1 of them wasn't added! clips = layers[1].get_clips() - self.assertEqual(len(clips), 4) + self.assertEqual(len(clips), 3) class TestPriorities(common.GESSimpleTimelineTest):