clip: preserve auto-transition in split

When splitting a clip, keep the auto-transition at the end of the clip
alive and move its source to that of the corresponding split track
element.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
This commit is contained in:
Henry Wilkes 2020-04-27 19:11:16 +01:00
parent 5c546c6fe7
commit 0a3da79e97
6 changed files with 235 additions and 53 deletions

View file

@ -39,8 +39,8 @@ static guint auto_transition_signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE (GESAutoTransition, ges_auto_transition, G_TYPE_OBJECT);
static void
neighbour_changed_cb (GESClip * clip, GParamSpec * arg G_GNUC_UNUSED,
GESAutoTransition * self)
neighbour_changed_cb (G_GNUC_UNUSED GObject * object,
G_GNUC_UNUSED GParamSpec * arg, GESAutoTransition * self)
{
gint64 new_duration;
guint32 layer_prio;
@ -110,7 +110,36 @@ _track_changed_cb (GESTrackElement * track_element,
g_signal_emit (self, auto_transition_signals[DESTROY_ME], 0);
}
}
static void
_connect_to_source (GESAutoTransition * self, GESTrackElement * source)
{
g_signal_connect (source, "notify::start",
G_CALLBACK (neighbour_changed_cb), self);
g_signal_connect_after (source, "notify::priority",
G_CALLBACK (neighbour_changed_cb), self);
g_signal_connect (source, "notify::duration",
G_CALLBACK (neighbour_changed_cb), self);
g_signal_connect (source, "notify::track",
G_CALLBACK (_track_changed_cb), self);
}
static void
_disconnect_from_source (GESAutoTransition * self, GESTrackElement * source)
{
g_signal_handlers_disconnect_by_func (source, neighbour_changed_cb, self);
g_signal_handlers_disconnect_by_func (source, _track_changed_cb, self);
}
void
ges_auto_transition_set_previous_source (GESAutoTransition * self,
GESTrackElement * source)
{
_disconnect_from_source (self, self->previous_source);
_connect_to_source (self, source);
self->previous_source = source;
}
static void
@ -123,16 +152,8 @@ ges_auto_transition_finalize (GObject * object)
{
GESAutoTransition *self = GES_AUTO_TRANSITION (object);
g_signal_handlers_disconnect_by_func (self->previous_source,
neighbour_changed_cb, self);
g_signal_handlers_disconnect_by_func (self->next_source, neighbour_changed_cb,
self);
g_signal_handlers_disconnect_by_func (self->next_source, _track_changed_cb,
self);
g_signal_handlers_disconnect_by_func (self->previous_source,
_track_changed_cb, self);
g_free (self->key);
_disconnect_from_source (self, self->previous_source);
_disconnect_from_source (self, self->next_source);
G_OBJECT_CLASS (ges_auto_transition_parent_class)->finalize (object);
}
@ -149,7 +170,6 @@ ges_auto_transition_class_init (GESAutoTransitionClass * klass)
object_class->finalize = ges_auto_transition_finalize;
}
GESAutoTransition *
ges_auto_transition_new (GESTrackElement * transition,
GESTrackElement * previous_source, GESTrackElement * next_source)
@ -161,48 +181,26 @@ ges_auto_transition_new (GESTrackElement * transition,
self->next_source = next_source;
self->transition = transition;
self->previous_clip =
GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (previous_source));
self->next_clip = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (next_source));
self->transition_clip = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (transition));
g_signal_connect (previous_source, "notify::start",
G_CALLBACK (neighbour_changed_cb), self);
g_signal_connect_after (previous_source, "notify::priority",
G_CALLBACK (neighbour_changed_cb), self);
g_signal_connect (next_source, "notify::start",
G_CALLBACK (neighbour_changed_cb), self);
g_signal_connect (next_source, "notify::priority",
G_CALLBACK (neighbour_changed_cb), self);
g_signal_connect (previous_source, "notify::duration",
G_CALLBACK (neighbour_changed_cb), self);
g_signal_connect (next_source, "notify::duration",
G_CALLBACK (neighbour_changed_cb), self);
g_signal_connect (next_source, "notify::track",
G_CALLBACK (_track_changed_cb), self);
g_signal_connect (previous_source, "notify::track",
G_CALLBACK (_track_changed_cb), self);
_connect_to_source (self, previous_source);
_connect_to_source (self, next_source);
GST_DEBUG_OBJECT (self, "Created transition %" GST_PTR_FORMAT
" between %" GST_PTR_FORMAT "[%" GST_TIME_FORMAT
" - %" GST_TIME_FORMAT "] and: %" GST_PTR_FORMAT
"[%" GST_TIME_FORMAT " - %" GST_TIME_FORMAT "]"
" in layer nb %i, start: %" GST_TIME_FORMAT " duration: %"
GST_TIME_FORMAT, transition, previous_source,
" in layer nb %" G_GUINT32_FORMAT ", start: %" GST_TIME_FORMAT
" duration: %" GST_TIME_FORMAT, transition, previous_source,
GST_TIME_ARGS (_START (previous_source)),
GST_TIME_ARGS (_END (previous_source)),
next_source,
GST_TIME_ARGS (_START (next_source)),
GST_TIME_ARGS (_END (next_source)),
ges_layer_get_priority (ges_clip_get_layer
(self->previous_clip)),
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (next_source),
GST_TIME_ARGS (_START (transition)),
GST_TIME_ARGS (_DURATION (transition)));
self->key = g_strdup_printf ("%p%p", self->previous_source,
self->next_source);
return self;
}
@ -211,5 +209,5 @@ 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);
neighbour_changed_cb (NULL, NULL, self);
}

View file

@ -49,15 +49,9 @@ struct _GESAutoTransition
GESTrackElement *next_source;
GESTrackElement *transition;
GESLayer *layer;
GESClip *previous_clip;
GESClip *next_clip;
GESClip *transition_clip;
gboolean positioning;
gchar *key;
gboolean frozen;
/* Padding for API extension */

View file

@ -2204,7 +2204,7 @@ ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect,
GESClip *
ges_clip_split (GESClip * clip, guint64 position)
{
GList *tmp;
GList *tmp, *transitions = NULL;
GESClip *new_object;
GstClockTime start, inpoint, duration, old_duration, new_duration;
gdouble media_duration_factor;
@ -2272,6 +2272,7 @@ ges_clip_split (GESClip * clip, guint64 position)
* binding values */
track_for_copy = g_hash_table_new_full (NULL, NULL,
gst_object_unref, gst_object_unref);
/* _add_child will add core elements at the lowest priority and new
* non-core effects at the lowest effect priority, so we need to add the
* highest priority children first to preserve the effect order. The
@ -2279,14 +2280,29 @@ ges_clip_split (GESClip * clip, guint64 position)
for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
GESTrackElement *copy, *orig = tmp->data;
GESTrack *track = ges_track_element_get_track (orig);
GESAutoTransition *trans;
/* FIXME: is position - start + inpoint always the correct splitting
* point for control bindings? What coordinate system are control
* bindings given in? */
copy = ges_clip_copy_track_element_into (new_object, orig,
position - start + inpoint);
if (copy && track)
if (!copy)
continue;
if (track)
g_hash_table_insert (track_for_copy, gst_object_ref (copy),
gst_object_ref (track));
trans = timeline ?
ges_timeline_get_auto_transition_at_end (timeline, orig) : NULL;
if (trans) {
trans->frozen = TRUE;
ges_auto_transition_set_previous_source (trans, copy);
transitions = g_list_append (transitions, trans);
}
}
GES_TIMELINE_ELEMENT_SET_BEING_EDITED (clip);
@ -2310,7 +2326,14 @@ ges_clip_split (GESClip * clip, guint64 position)
}
}
for (tmp = transitions; tmp; tmp = tmp->next) {
GESAutoTransition *trans = tmp->data;
trans->frozen = FALSE;
ges_auto_transition_update (trans);
}
g_hash_table_unref (track_for_copy);
g_list_free_full (transitions, gst_object_unref);
return new_object;
}

View file

@ -106,6 +106,9 @@ GstDebugCategory * _ges_debug (void);
G_GNUC_INTERNAL void
ges_timeline_freeze_auto_transitions (GESTimeline * timeline, gboolean freeze);
G_GNUC_INTERNAL GESAutoTransition *
ges_timeline_get_auto_transition_at_end (GESTimeline * timeline, GESTrackElement * source);
G_GNUC_INTERNAL gboolean
ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element,
GList * layers, gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
@ -155,6 +158,11 @@ ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip);
G_GNUC_INTERNAL void
ges_timeline_remove_clip (GESTimeline * timeline, GESClip * clip);
G_GNUC_INTERNAL void
ges_auto_transition_set_previous_source (GESAutoTransition * self, GESTrackElement * source);
G_GNUC_INTERNAL
void
track_resort_and_fill_gaps (GESTrack *track);

View file

@ -980,6 +980,34 @@ ges_timeline_find_auto_transition (GESTimeline * timeline,
return NULL;
}
GESAutoTransition *
ges_timeline_get_auto_transition_at_end (GESTimeline * timeline,
GESTrackElement * source)
{
GList *tmp, *auto_transitions;
GESAutoTransition *ret = NULL;
LOCK_DYN (timeline);
auto_transitions = g_list_copy_deep (timeline->priv->auto_transitions,
(GCopyFunc) gst_object_ref, NULL);
UNLOCK_DYN (timeline);
for (tmp = auto_transitions; tmp; tmp = tmp->next) {
GESAutoTransition *auto_trans = (GESAutoTransition *) tmp->data;
/* We already have a transition linked to one of the elements we want to
* find a transition for */
if (auto_trans->previous_source == source) {
ret = gst_object_ref (auto_trans);
break;
}
}
g_list_free_full (auto_transitions, gst_object_unref);
return ret;
}
static GESAutoTransition *
_create_auto_transition_from_transitions (GESTimeline * timeline,
GESTrackElement * prev, GESTrackElement * next,
@ -1168,12 +1196,12 @@ _edit_auto_transition (GESTimeline * timeline, GESTimelineElement * element,
}
if (edge == GES_EDGE_END)
replace = GES_TIMELINE_ELEMENT (auto_transition->previous_clip);
replace = GES_TIMELINE_ELEMENT (auto_transition->previous_source);
else
replace = GES_TIMELINE_ELEMENT (auto_transition->next_clip);
replace = GES_TIMELINE_ELEMENT (auto_transition->next_source);
GST_INFO_OBJECT (element, "Trimming clip %" GES_FORMAT " in place "
"of trimming the corresponding auto-transition", GES_ARGS (replace));
GST_INFO_OBJECT (element, "Trimming %" GES_FORMAT " in place of "
"trimming the corresponding auto-transition", GES_ARGS (replace));
return ges_timeline_element_edit (replace, layers, -1, mode, edge,
position);
}

View file

@ -308,6 +308,136 @@ GST_START_TEST (test_split_direct_absolute_bindings)
GST_END_TEST;
static GESTimelineElement *
_find_auto_transition (GESTrack * track, GESClip * from_clip, GESClip * to_clip)
{
GstClockTime start, end;
GList *tmp, *track_els;
GESTimelineElement *ret = NULL;
GESLayer *layer0, *layer1;
layer0 = ges_clip_get_layer (from_clip);
layer1 = ges_clip_get_layer (to_clip);
fail_unless (layer0 == layer1, "%" GES_FORMAT " and %" GES_FORMAT " do not "
"share the same layer", GES_ARGS (from_clip), GES_ARGS (to_clip));
gst_object_unref (layer1);
start = GES_TIMELINE_ELEMENT_START (to_clip);
end = GES_TIMELINE_ELEMENT_START (from_clip)
+ GES_TIMELINE_ELEMENT_DURATION (from_clip);
fail_if (end <= start, "%" GES_FORMAT " starts after %" GES_FORMAT " ends",
GES_ARGS (to_clip), GES_ARGS (from_clip));
track_els = ges_track_get_elements (track);
for (tmp = track_els; tmp; tmp = tmp->next) {
GESTimelineElement *el = tmp->data;
if (GES_IS_TRANSITION (el) && el->start == start
&& (el->start + el->duration) == end) {
fail_if (ret, "Found two transitions %" GES_FORMAT " and %" GES_FORMAT
" between %" GES_FORMAT " and %" GES_FORMAT " in track %"
GST_PTR_FORMAT, GES_ARGS (el), GES_ARGS (ret), GES_ARGS (from_clip),
GES_ARGS (to_clip), track);
ret = el;
}
}
fail_unless (ret, "Found no transitions between %" GES_FORMAT " and %"
GES_FORMAT " in track %" GST_PTR_FORMAT, GES_ARGS (from_clip),
GES_ARGS (to_clip), track);
g_list_free_full (track_els, gst_object_unref);
fail_unless (GES_IS_CLIP (ret->parent), "Transition %" GES_FORMAT
" between %" GES_FORMAT " and %" GES_FORMAT " in track %"
GST_PTR_FORMAT " has no parent clip", GES_ARGS (ret),
GES_ARGS (from_clip), GES_ARGS (to_clip), track);
layer1 = ges_clip_get_layer (GES_CLIP (ret->parent));
fail_unless (layer0 == layer1, "Transition %" GES_FORMAT " between %"
GES_FORMAT " and %" GES_FORMAT " in track %" GST_PTR_FORMAT
" belongs to layer %" GST_PTR_FORMAT " rather than %" GST_PTR_FORMAT,
GES_ARGS (ret), GES_ARGS (from_clip), GES_ARGS (to_clip), track,
layer1, layer0);
gst_object_unref (layer0);
gst_object_unref (layer1);
return ret;
}
GST_START_TEST (test_split_with_auto_transitions)
{
GESTimeline *timeline;
GESLayer *layer;
GESTrack *tracks[3];
GESTimelineElement *found;
GESTimelineElement *prev_trans[3];
GESTimelineElement *post_trans[3];
GESAsset *asset;
GESClip *clip, *split, *prev, *post;
guint i;
ges_init ();
timeline = ges_timeline_new ();
ges_timeline_set_auto_transition (timeline, TRUE);
tracks[0] = GES_TRACK (ges_audio_track_new ());
tracks[1] = GES_TRACK (ges_audio_track_new ());
tracks[2] = GES_TRACK (ges_video_track_new ());
for (i = 0; i < 3; i++)
fail_unless (ges_timeline_add_track (timeline, tracks[i]));
layer = ges_timeline_append_layer (timeline);
asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL);
prev = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN);
clip = ges_layer_add_asset (layer, asset, 5, 0, 20, GES_TRACK_TYPE_UNKNOWN);
post = ges_layer_add_asset (layer, asset, 20, 0, 10, GES_TRACK_TYPE_UNKNOWN);
fail_unless (prev);
fail_unless (clip);
fail_unless (post);
for (i = 0; i < 3; i++) {
prev_trans[i] = _find_auto_transition (tracks[i], prev, clip);
post_trans[i] = _find_auto_transition (tracks[i], clip, post);
/* 3 sources, 2 auto-transitions */
assert_num_in_track (tracks[i], 5);
}
/* cannot split within a transition */
fail_if (ges_clip_split (clip, 5));
fail_if (ges_clip_split (clip, 20));
/* we should keep the same auto-transitions during a split */
split = ges_clip_split (clip, 15);
fail_unless (split);
for (i = 0; i < 3; i++) {
found = _find_auto_transition (tracks[i], prev, clip);
fail_unless (found == prev_trans[i], "Transition between %" GES_FORMAT
" and %" GES_FORMAT " changed", GES_ARGS (prev), GES_ARGS (clip));
found = _find_auto_transition (tracks[i], split, post);
fail_unless (found == post_trans[i], "Transition between %" GES_FORMAT
" and %" GES_FORMAT " changed", GES_ARGS (clip), GES_ARGS (post));
}
gst_object_unref (timeline);
gst_object_unref (asset);
ges_deinit ();
}
GST_END_TEST;
static GPtrArray *
_select_none (GESTimeline * timeline, GESClip * clip,
GESTrackElement * track_element, guint * called_p)
@ -3329,6 +3459,7 @@ ges_suite (void)
tcase_add_test (tc_chain, test_split_ordering);
tcase_add_test (tc_chain, test_split_direct_bindings);
tcase_add_test (tc_chain, test_split_direct_absolute_bindings);
tcase_add_test (tc_chain, test_split_with_auto_transitions);
tcase_add_test (tc_chain, test_clip_group_ungroup);
tcase_add_test (tc_chain, test_clip_can_group);
tcase_add_test (tc_chain, test_adding_children_to_track);