timeline: Disable movement that lead to 2 transition at a position

We should never let 3 objects to overlap at a same position, for that
we introduce a "rollback" feature and whenever such an editing happens,
we rollback object position to whatever it was before the move.
This commit is contained in:
Thibault Saunier 2015-06-23 16:11:26 +02:00
parent 42700e98c5
commit d23e43ae1a
3 changed files with 115 additions and 68 deletions

View file

@ -153,6 +153,11 @@ struct _GESTimelinePrivate
/* The auto-transition of the timeline */ /* The auto-transition of the timeline */
gboolean auto_transition; 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 */ /* Timeline edition modes and snapping management */
guint64 snapping_distance; guint64 snapping_distance;
@ -844,19 +849,25 @@ _find_transition_from_auto_transitions (GESTimeline * timeline,
GESTrackElement * next, GstClockTime transition_duration) GESTrackElement * next, GstClockTime transition_duration)
{ {
GList *tmp; GList *tmp;
GESAutoTransition *auto_transition = NULL;
gchar *key = g_strdup_printf ("%p%p", prev, next);
for (tmp = timeline->priv->auto_transitions; tmp; tmp = tmp->next) { for (tmp = timeline->priv->auto_transitions; tmp; tmp = tmp->next) {
if (!g_strcmp0 (GES_AUTO_TRANSITION (tmp->data)->key, key)) { GESAutoTransition *auto_trans = (GESAutoTransition *) tmp->data;
auto_transition = tmp->data;
break; /* We already have a transition linked to one of the elements we want to
* find a transition for */
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, "
" trying to have 3 clips overlapping, rolling back");
}
return auto_trans;
} }
} }
g_free (key);
return auto_transition; return NULL;
} }
static GESAutoTransition * static GESAutoTransition *
@ -1282,7 +1293,10 @@ done:
if (emit) { if (emit) {
GstClockTime snap_time = ret ? *ret : GST_CLOCK_TIME_NONE; GstClockTime snap_time = ret ? *ret : GST_CLOCK_TIME_NONE;
ges_timeline_emit_snappig (timeline, trackelement, ret); 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_DEBUG_OBJECT (timeline, "Snaping at %" GST_TIME_FORMAT,
GST_TIME_ARGS (snap_time)); GST_TIME_ARGS (snap_time));
@ -1646,7 +1660,7 @@ timeline_ripple_object (GESTimeline * timeline, GESTrackElement * obj,
MoveContext *mv_ctx = &timeline->priv->movecontext; MoveContext *mv_ctx = &timeline->priv->movecontext;
mv_ctx->ignore_needs_ctx = TRUE; mv_ctx->ignore_needs_ctx = TRUE;
timeline->priv->needs_rollback = FALSE;
if (!ges_timeline_set_moving_context (timeline, obj, GES_EDIT_MODE_RIPPLE, if (!ges_timeline_set_moving_context (timeline, obj, GES_EDIT_MODE_RIPPLE,
edge, layers)) edge, layers))
goto error; goto error;
@ -1678,6 +1692,30 @@ timeline_ripple_object (GESTimeline * timeline, GESTrackElement * obj,
g_list_free (moved_clips); g_list_free (moved_clips);
_set_start0 (GES_TIMELINE_ELEMENT (obj), position); _set_start0 (GES_TIMELINE_ELEMENT (obj), position);
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);
_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; break;
case GES_EDGE_END: case GES_EDGE_END:
timeline->priv->needs_transitions_update = FALSE; timeline->priv->needs_transitions_update = FALSE;
@ -1711,7 +1749,6 @@ timeline_ripple_object (GESTimeline * timeline, GESTrackElement * obj,
} }
if (GES_IS_GROUP (container)) if (GES_IS_GROUP (container))
container->children_control_mode = GES_CHILDREN_UPDATE; container->children_control_mode = GES_CHILDREN_UPDATE;
} }
g_list_free (moved_clips); g_list_free (moved_clips);
@ -1755,17 +1792,37 @@ timeline_trim_object (GESTimeline * timeline, GESTrackElement * object,
GList * layers, GESEdge edge, guint64 position) GList * layers, GESEdge edge, guint64 position)
{ {
gboolean ret = FALSE; gboolean ret = FALSE;
GstClockTime cpos;
MoveContext *mv_ctx = &timeline->priv->movecontext; MoveContext *mv_ctx = &timeline->priv->movecontext;
mv_ctx->ignore_needs_ctx = TRUE; mv_ctx->ignore_needs_ctx = TRUE;
timeline->priv->needs_rollback = FALSE;
if (!ges_timeline_set_moving_context (timeline, object, GES_EDIT_MODE_TRIM, if (!ges_timeline_set_moving_context (timeline, object, GES_EDIT_MODE_TRIM,
edge, layers)) edge, layers))
goto end; 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, ret = ges_timeline_trim_object_simple (timeline,
GES_TIMELINE_ELEMENT (object), layers, edge, position, TRUE); 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: end:
mv_ctx->ignore_needs_ctx = FALSE; mv_ctx->ignore_needs_ctx = FALSE;
@ -1921,6 +1978,7 @@ ges_timeline_move_object_simple (GESTimeline * timeline,
GESTimelineElement * element, GList * layers, GESEdge edge, GESTimelineElement * element, GList * layers, GESEdge edge,
guint64 position) guint64 position)
{ {
GstClockTime cpos = GES_TIMELINE_ELEMENT_START (element);
guint64 *snap_end, *snap_st, *cur, off1, off2, end; guint64 *snap_end, *snap_st, *cur, off1, off2, end;
GESTrackElement *track_element; GESTrackElement *track_element;
@ -1930,6 +1988,7 @@ ges_timeline_move_object_simple (GESTimeline * timeline,
g_list_find (timeline->priv->movecontext.moving_trackelements, element)) g_list_find (timeline->priv->movecontext.moving_trackelements, element))
return FALSE; return FALSE;
timeline->priv->needs_rollback = FALSE;
track_element = GES_TRACK_ELEMENT (element); track_element = GES_TRACK_ELEMENT (element);
end = position + _DURATION (get_toplevel_container (track_element)); end = position + _DURATION (get_toplevel_container (track_element));
cur = g_hash_table_lookup (timeline->priv->by_end, track_element); cur = g_hash_table_lookup (timeline->priv->by_end, track_element);
@ -1963,10 +2022,20 @@ ges_timeline_move_object_simple (GESTimeline * timeline,
ges_timeline_emit_snappig (timeline, track_element, snap_st); ges_timeline_emit_snappig (timeline, track_element, snap_st);
} else } else
ges_timeline_emit_snappig (timeline, track_element, NULL); ges_timeline_emit_snappig (timeline, track_element, NULL);
timeline->priv->needs_rollback = FALSE;
_set_start0 (GES_TIMELINE_ELEMENT (track_element), position); _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; return TRUE;
} }
@ -1976,8 +2045,6 @@ timeline_context_to_layer (GESTimeline * timeline, gint offset)
gboolean ret = TRUE; gboolean ret = TRUE;
MoveContext *mv_ctx = &timeline->priv->movecontext; MoveContext *mv_ctx = &timeline->priv->movecontext;
/* Layer's priority is always positive */ /* Layer's priority is always positive */
if (offset != 0 && (offset > 0 || mv_ctx->min_move_layer >= -offset)) { if (offset != 0 && (offset > 0 || mv_ctx->min_move_layer >= -offset)) {
GHashTableIter iter; GHashTableIter iter;
@ -1990,6 +2057,7 @@ timeline_context_to_layer (GESTimeline * timeline, gint offset)
GST_DEBUG ("Moving %d object, offset %d", GST_DEBUG ("Moving %d object, offset %d",
g_hash_table_size (mv_ctx->toplevel_containers), offset); g_hash_table_size (mv_ctx->toplevel_containers), offset);
timeline->priv->needs_rollback = FALSE;
g_hash_table_iter_init (&iter, mv_ctx->toplevel_containers); g_hash_table_iter_init (&iter, mv_ctx->toplevel_containers);
while (g_hash_table_iter_next (&iter, (gpointer *) & key, while (g_hash_table_iter_next (&iter, (gpointer *) & key,
(gpointer *) & value)) { (gpointer *) & value)) {
@ -2029,6 +2097,13 @@ timeline_context_to_layer (GESTimeline * timeline, gint offset)
mv_ctx->min_move_layer = mv_ctx->min_move_layer + offset; mv_ctx->min_move_layer = mv_ctx->min_move_layer + offset;
mv_ctx->ignore_needs_ctx = FALSE; 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;
}
} }
return ret; return ret;
@ -2117,9 +2192,30 @@ static void
layer_auto_transition_changed_cb (GESLayer * layer, layer_auto_transition_changed_cb (GESLayer * layer,
GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
{ {
timeline->priv->needs_rollback = FALSE;
_create_transitions_on_layer (timeline, layer, NULL, NULL, _create_transitions_on_layer (timeline, layer, NULL, NULL,
_create_auto_transition_from_transitions); _create_auto_transition_from_transitions);
if (timeline->priv->needs_rollback) {
GList *tmp, *trans;
ges_layer_set_auto_transition (layer, FALSE);
GST_ERROR_OBJECT (layer, "Has overlapping transition, "
" we can't handle that, setting auto_transition"
" to FALSE, and removing all transitions");
trans = g_list_copy (timeline->priv->auto_transitions);
for (tmp = trans; tmp; tmp = tmp->next) {
g_signal_emit_by_name (tmp->data, "destroy-me");
}
g_list_free (trans);
trans = ges_layer_get_clips (layer);
for (tmp = trans; tmp; tmp = tmp->next) {
if (GES_IS_TRANSITION_CLIP (tmp->data))
ges_layer_remove_clip (layer, tmp->data);
}
g_list_free_full (trans, gst_object_unref);
}
} }
static void static void

View file

@ -141,7 +141,6 @@ sync_properties_from_caps (GstFramePositionner * pos, GstCaps * caps)
pos->track_width = width; pos->track_width = width;
pos->track_height = height; pos->track_height = height;
GST_ERROR_OBJECT (pos, "syncing size from caps : %d %d", width, height);
GST_DEBUG_OBJECT (pos, "syncing framerate from caps : %d/%d", pos->fps_n, GST_DEBUG_OBJECT (pos, "syncing framerate from caps : %d/%d", pos->fps_n,
pos->fps_d); pos->fps_d);

View file

@ -671,59 +671,9 @@ GST_START_TEST (test_single_layer_automatic_transition)
fail_unless (current->data == src2); fail_unless (current->data == src2);
g_list_free_full (objects, gst_object_unref); g_list_free_full (objects, gst_object_unref);
GST_DEBUG ("Set third clip start to 1000, Transition should be updated"); GST_DEBUG ("Check that we can not create 2 transitions at the same place");
ges_container_edit (GES_CONTAINER (src2), NULL, -1, fail_if (ges_container_edit (GES_CONTAINER (src2), NULL, -1,
GES_EDIT_MODE_NORMAL, GES_EDGE_START, 1000); GES_EDIT_MODE_NORMAL, GES_EDGE_START, 1000));
ges_timeline_commit (timeline);
/* 600____src___1100
* !_tr__^
* 500___________src1________1250
* 1000___________src2________2000
* ^____trans____^
*/
assert_equals_uint64 (_START (src), 600);
assert_equals_uint64 (_DURATION (src), 500);
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), 7);
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), 1000);
assert_equals_uint64 (_DURATION (transition), 1100 - 1000);
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), 1100 - 1000);
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), 1250 - 1000);
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), 1250 - 1000);
current = current->next;
fail_unless (current->data == src2);
g_list_free_full (objects, gst_object_unref);
/* /*
* 500___________src1________1250 * 500___________src1________1250
@ -731,6 +681,8 @@ GST_START_TEST (test_single_layer_automatic_transition)
* ^____trans____^ * ^____trans____^
*/ */
ges_layer_remove_clip (layer, GES_CLIP (src)); 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 (_START (src1), 500);
assert_equals_uint64 (_DURATION (src1), 1250 - 500); assert_equals_uint64 (_DURATION (src1), 1250 - 500);
assert_equals_uint64 (_START (src2), 1000); assert_equals_uint64 (_START (src2), 1000);