mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-27 18:50:48 +00:00
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:
parent
5c546c6fe7
commit
0a3da79e97
6 changed files with 235 additions and 53 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue