From 02652902f57054fe31c3b8cbcced71ca4b0f0b25 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Thu, 10 Jan 2013 18:50:54 -0300 Subject: [PATCH] Reimplement the auto-transition feature + Actually implement unit tests --- ges/Makefile.am | 2 + ges/ges-auto-transition.c | 182 ++++++ ges/ges-auto-transition.h | 78 +++ ges/ges-timeline-layer.c | 479 +--------------- ges/ges-timeline.c | 285 +++++++++- tests/check/ges/layer.c | 1122 ++++++++++++++++++++++++++++++++++++- 6 files changed, 1646 insertions(+), 502 deletions(-) create mode 100644 ges/ges-auto-transition.c create mode 100644 ges/ges-auto-transition.h diff --git a/ges/Makefile.am b/ges/Makefile.am index 4d53b65576..92f5953edb 100644 --- a/ges/Makefile.am +++ b/ges/Makefile.am @@ -55,6 +55,7 @@ libges_@GST_API_VERSION@_la_SOURCES = \ ges-project.c \ ges-base-xml-formatter.c \ ges-xml-formatter.c \ + ges-auto-transition.c \ ges-utils.c libges_@GST_API_VERSION@includedir = $(includedir)/gstreamer-@GST_API_VERSION@/ges/ @@ -111,6 +112,7 @@ libges_@GST_API_VERSION@include_HEADERS = \ noinst_HEADERS = \ ges-internal.h \ + ges-auto-transition.h \ ges-internal-enums.h libges_@GST_API_VERSION@_la_CFLAGS = -I$(top_srcdir) $(GST_PBUTILS_CFLAGS) \ diff --git a/ges/ges-auto-transition.c b/ges/ges-auto-transition.c new file mode 100644 index 0000000000..12f2d8014d --- /dev/null +++ b/ges/ges-auto-transition.c @@ -0,0 +1,182 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */ +/* + * gst-editing-services + * Copyright (C) 2013 Thibault Saunier + * + * gst-editing-services is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * gst-editing-services 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see ."; + */ + +/* This class warps a GESTimelineTransition, letting any implementation + * of a GESTimelineTransition to be used. + * + * NOTE: This is for internal use exclusively + */ + +#include "ges-auto-transition.h" +#include "ges-internal.h" +enum +{ + DESTROY_ME, + LAST_SIGNAL +}; + +static guint auto_transition_signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (GESAutoTransition, ges_auto_transition, G_TYPE_OBJECT); + +static void +neighbour_changed_cb (GESTimelineObject * obj, GParamSpec * arg G_GNUC_UNUSED, + GESAutoTransition * self) +{ + gint64 new_duration; + + if (self->next_source->priority / LAYER_HEIGHT != + self->previous_source->priority / LAYER_HEIGHT) { + GST_DEBUG_OBJECT (self, "Destroy changed layer"); + g_signal_emit (self, auto_transition_signals[DESTROY_ME], 0); + return; + } + + new_duration = + (self->previous_source->start + self->previous_source->duration) - + self->next_source->start; + + if (new_duration <= 0 || new_duration >= self->previous_source->duration || + new_duration >= self->next_source->duration) { + + GST_DEBUG_OBJECT (self, "Destroy %" G_GINT64_FORMAT " not a valid duration", + new_duration); + g_signal_emit (self, auto_transition_signals[DESTROY_ME], 0); + return; + } + + ges_timeline_object_set_start (self->timeline_transition, + self->next_source->start); + ges_timeline_object_set_duration (self->timeline_transition, new_duration); +} + +static void +_height_changed_cb (GESTimelineObject * obj, GParamSpec * arg G_GNUC_UNUSED, + GESAutoTransition * self) +{ + /* FIXME This is really not smart and we should properly implement timelineobject + * priority management at the TimelineLayer level */ + ges_timeline_object_set_priority (self->next_timeline_object, + self->previous_timeline_object->priority + + self->previous_timeline_object->height); +} + +static void +_track_changed_cb (GESTrackObject * obj, GParamSpec * arg G_GNUC_UNUSED, + GESAutoTransition * self) +{ + if (ges_track_object_get_track (obj) == NULL) { + GST_DEBUG_OBJECT (self, "Neighboor %" GST_PTR_FORMAT + " removed from track ... auto destructing", obj); + + g_signal_emit (self, auto_transition_signals[DESTROY_ME], 0); + } + +} + +static void +ges_auto_transition_init (GESAutoTransition * ges_auto_transition) +{ +} + +static void +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->previous_timeline_object, + _height_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); + + G_OBJECT_CLASS (ges_auto_transition_parent_class)->finalize (object); +} + +static void +ges_auto_transition_class_init (GESAutoTransitionClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + auto_transition_signals[DESTROY_ME] = + g_signal_new ("destroy-me", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + object_class->finalize = ges_auto_transition_finalize; +} + + +GESAutoTransition * +ges_auto_transition_new (GESTrackObject * transition, + GESTrackObject * previous_source, GESTrackObject * next_source) +{ + GESAutoTransition *self = g_object_new (GES_TYPE_AUTO_TRANSITION, NULL); + + self->previous_source = previous_source; + self->next_source = next_source; + self->transition = transition; + + self->previous_timeline_object = + ges_track_object_get_timeline_object (previous_source); + self->next_timeline_object = + ges_track_object_get_timeline_object (next_source); + self->timeline_transition = ges_track_object_get_timeline_object (transition); + + g_signal_connect (previous_source, "notify::start", + G_CALLBACK (neighbour_changed_cb), self); + g_signal_connect (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 (self->previous_timeline_object, "notify::height", + G_CALLBACK (_height_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); + + _height_changed_cb (self->previous_timeline_object, NULL, self); + + GST_DEBUG_OBJECT (self, "Created transition %" GST_PTR_FORMAT + " between %" GST_PTR_FORMAT " and: %" GST_PTR_FORMAT + " in layer nb %i, start: %" GST_TIME_FORMAT " duration: %" + GST_TIME_FORMAT, transition, next_source, previous_source, + ges_timeline_layer_get_priority (ges_timeline_object_get_layer + (self->previous_timeline_object)), GST_TIME_ARGS (transition->start), + GST_TIME_ARGS (transition->duration)); + + self->key = g_strdup_printf ("%p%p", self->previous_source, + self->next_source); + + return self; +} diff --git a/ges/ges-auto-transition.h b/ges/ges-auto-transition.h new file mode 100644 index 0000000000..be07e54964 --- /dev/null +++ b/ges/ges-auto-transition.h @@ -0,0 +1,78 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */ +/* + * gst-editing-services + * Copyright (C) 2013 Thibault Saunier + * + * gst-editing-services is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * gst-editing-services 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see ."; + */ + +#ifndef _GES_AUTO_TRANSITION_H_ +#define _GES_AUTO_TRANSITION_H_ + +#include +#include "ges-track-object.h" +#include "ges-timeline-object.h" +#include "ges-timeline-layer.h" + +G_BEGIN_DECLS + +#define GES_TYPE_AUTO_TRANSITION (ges_auto_transition_get_type ()) +#define GES_AUTO_TRANSITION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_AUTO_TRANSITION, GESAutoTransition)) +#define GES_AUTO_TRANSITION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_AUTO_TRANSITION, GESAutoTransitionClass)) +#define GES_IS_AUTO_TRANSITION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_AUTO_TRANSITION)) +#define GES_IS_AUTO_TRANSITION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_AUTO_TRANSITION)) +#define GES_AUTO_TRANSITION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_AUTO_TRANSITION, GESAutoTransitionClass)) + +typedef struct _GESAutoTransitionClass GESAutoTransitionClass; +typedef struct _GESAutoTransition GESAutoTransition; + + + +struct _GESAutoTransitionClass +{ + GObjectClass parent_class; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESAutoTransition +{ + GObject parent_instance; + + /* */ + GESTrackObject *previous_source; + GESTrackObject *next_source; + GESTrackObject *transition; + + GESTimelineLayer *layer; + + GESTimelineObject *previous_timeline_object; + GESTimelineObject *next_timeline_object; + GESTimelineObject *timeline_transition; + + gchar *key; + + /* Padding for API extension */ + gpointer _ges_reserved[GES_PADDING]; +}; + +GType ges_auto_transition_get_type (void) G_GNUC_CONST; + +GESAutoTransition * ges_auto_transition_new (GESTrackObject * transition, + GESTrackObject * previous_source, + GESTrackObject * next_source); + +G_END_DECLS +#endif /* _GES_AUTO_TRANSITION_H_ */ diff --git a/ges/ges-timeline-layer.c b/ges/ges-timeline-layer.c index 91b06f6b1f..1b9c3287f4 100644 --- a/ges/ges-timeline-layer.c +++ b/ges/ges-timeline-layer.c @@ -35,10 +35,6 @@ #include "ges.h" #include "ges-timeline-source.h" -static void -timeline_object_height_changed_cb (GESTimelineObject * obj, - GESTrackEffect * tr_eff, GESTimelineObject * second_obj); - static void ges_meta_container_interface_init (GESMetaContainerInterface * iface); @@ -211,316 +207,6 @@ ges_timeline_layer_init (GESTimelineLayer * self) self->max_gnl_priority = LAYER_HEIGHT; } -static GList * -track_get_by_layer (GESTimelineLayer * layer, GESTrack * track) -{ - GESTrackObject *tckobj; - guint32 layer_prio = layer->priv->priority; - - GList *tck_objects_list = NULL, *tmp = NULL, *return_list = NULL; - - tck_objects_list = ges_track_get_objects (track); - for (tmp = tck_objects_list; tmp; tmp = tmp->next) { - tckobj = GES_TRACK_OBJECT (tmp->data); - - if (tckobj->priority / LAYER_HEIGHT == layer_prio) { - /* We steal the reference from tck_objects_list */ - return_list = g_list_append (return_list, tmp->data); - } else - g_object_unref (tmp->data); - } - - return return_list; -} - -/* Compare: - * @compared: The #GList of #GESTrackObjects that we compare with @track_object - * @track_object: The #GESTrackObject that serves as a reference - * @ahead: %TRUE if we are comparing frontward %FALSE if we are comparing - * backward*/ -static void -compare (GList * compared, GESTrackObject * track_object, gboolean ahead) -{ - GList *tmp; - gint64 start, duration, compared_start, compared_duration, end, compared_end, - tr_start, tr_duration; - GESTimelineStandardTransition *trans = NULL; - GESTrack *track; - GESTimelineLayer *layer; - GESTimelineObject *object, *compared_object, *first_object, *second_object; - gint priority; - - g_return_if_fail (compared); - - GST_DEBUG ("Recalculating transitions"); - - object = ges_track_object_get_timeline_object (track_object); - - if (!object) { - GST_WARNING ("Trackobject not in a timeline object: " - "Can not calulate transitions"); - - return; - } - - compared_object = ges_track_object_get_timeline_object (compared->data); - layer = ges_timeline_object_get_layer (object); - - start = ges_track_object_get_start (track_object); - duration = ges_track_object_get_duration (track_object); - compared_start = ges_track_object_get_start (compared->data); - compared_duration = ges_track_object_get_duration (compared->data); - end = start + duration; - compared_end = compared_start + compared_duration; - - if (ahead) { - /* Make sure we remove the last transition we created it is not needed - * FIXME make it a smarter way */ - if (compared->prev && GES_IS_TRACK_TRANSITION (compared->prev->data)) { - trans = GES_TIMELINE_STANDARD_TRANSITION - (ges_track_object_get_timeline_object (compared->prev->data)); - g_object_get (compared->prev->data, "start", &tr_start, "duration", - &tr_duration, NULL); - if (tr_start >= compared_start && tr_start + tr_duration <= compared_end) - ges_timeline_layer_remove_object (layer, GES_TIMELINE_OBJECT (trans)); - trans = NULL; - } - - for (tmp = compared->next; tmp; tmp = tmp->next) { - /* If we have a transitionmnmnm we recaluculuculate its values */ - if (GES_IS_TRACK_TRANSITION (tmp->data)) { - g_object_get (tmp->data, "start", &tr_start, "duration", &tr_duration, - NULL); - - if (tr_start + tr_duration == compared_start + compared_duration) { - GESTimelineObject *tlobj; - tlobj = ges_track_object_get_timeline_object (tmp->data); - - trans = GES_TIMELINE_STANDARD_TRANSITION (tlobj); - break; - } - } - } - - if (compared_end <= start) { - if (trans) { - ges_timeline_layer_remove_object (layer, GES_TIMELINE_OBJECT (trans)); - g_object_get (compared_object, "priority", &priority, NULL); - g_object_set (object, "priority", priority, NULL); - } - - goto clean; - } else if (start > compared_start && end < compared_end) { - if (trans) { - /* Transition not needed anymore */ - ges_timeline_layer_remove_object (layer, GES_TIMELINE_OBJECT (trans)); - } - goto clean; - } else if (start <= compared_start) { - if (trans) { - ges_timeline_layer_remove_object (layer, GES_TIMELINE_OBJECT (trans)); - } - - goto clean; - } - - } else { - if (compared->next && GES_IS_TRACK_TRANSITION (compared->next->data)) { - trans = GES_TIMELINE_STANDARD_TRANSITION - (ges_track_object_get_timeline_object (compared->next->data)); - g_object_get (compared->prev->data, "start", &tr_start, "duration", - &tr_duration, NULL); - if (tr_start >= compared_start && tr_start + tr_duration <= compared_end) - ges_timeline_layer_remove_object (layer, GES_TIMELINE_OBJECT (trans)); - trans = NULL; - } - for (tmp = compared->prev; tmp; tmp = tmp->prev) { - if GES_IS_TRACK_TRANSITION - (tmp->data) { - g_object_get (tmp->data, "start", &tr_start, "duration", &tr_duration, - NULL); - if (tr_start == compared_start) { - trans = GES_TIMELINE_STANDARD_TRANSITION - (ges_track_object_get_timeline_object (tmp->data)); - break; - } - } - } - - if (start + duration <= compared_start) { - if (trans) { - ges_timeline_layer_remove_object (layer, GES_TIMELINE_OBJECT (trans)); - g_object_get (object, "priority", &priority, NULL); - g_object_set (compared_object, "priority", priority, NULL); - } - goto clean; - - } else if (start > compared_start) { - if (trans) - ges_timeline_layer_remove_object (layer, GES_TIMELINE_OBJECT (trans)); - - goto clean; - } else if (start < compared_start && end > compared_end) { - if (trans) { - ges_timeline_layer_remove_object (layer, GES_TIMELINE_OBJECT (trans)); - } - - goto clean; - } - } - - if (!trans) { - gint height; - - trans = - ges_timeline_standard_transition_new_for_nick ((gchar *) "crossfade"); - track = ges_track_object_get_track (track_object); - - ges_timeline_object_set_supported_formats (GES_TIMELINE_OBJECT (trans), - track->type); - - ges_timeline_layer_add_object (layer, GES_TIMELINE_OBJECT (trans)); - - if (ahead) { - first_object = ges_track_object_get_timeline_object (compared->data); - second_object = object; - } else { - second_object = ges_track_object_get_timeline_object (compared->data); - first_object = object; - } - - g_object_get (first_object, "priority", &priority, "height", &height, NULL); - g_object_set (second_object, "priority", priority + height, NULL); - g_signal_connect (first_object, "notify::height", - (GCallback) timeline_object_height_changed_cb, second_object); - } - - if (ahead) { - g_object_set (trans, "start", start, "duration", - compared_duration + compared_start - start, NULL); - } else { - g_object_set (trans, "start", compared_start, "duration", - start + duration - compared_start, NULL); - } - -clean: - g_object_unref (layer); -} - -static void -calculate_next_transition_with_list (GESTrackObject * track_object, - GList * tckobjs_in_layer, GESTimelineLayer * layer) -{ - GList *compared; - - if (!(compared = g_list_find (tckobjs_in_layer, track_object))) - return; - - if (compared == NULL) - /* This is the last TrackObject of the Track */ - return; - - do { - compared = compared->next; - if (compared == NULL) - return; - } while (!GES_IS_TRACK_SOURCE (compared->data)); - - compare (compared, track_object, FALSE); -} - - -static void -calculate_next_transition (GESTrackObject * track_object, - GESTimelineLayer * layer) -{ - GESTrack *track; - GList *tckobjs_in_layer; - - if ((track = ges_track_object_get_track (track_object))) { - tckobjs_in_layer = track_get_by_layer (layer, track); - calculate_next_transition_with_list (track_object, tckobjs_in_layer, layer); - - g_list_foreach (tckobjs_in_layer, (GFunc) g_object_unref, NULL); - g_list_free (tckobjs_in_layer); - } -} - -static void -calculate_transitions (GESTrackObject * track_object) -{ - GList *tckobjs_in_layer, *compared; - GESTimelineLayer *layer; - GESTimelineObject *tlobj; - - GESTrack *track = ges_track_object_get_track (track_object); - - if (track == NULL) - return; - - tlobj = ges_track_object_get_timeline_object (track_object); - layer = ges_timeline_object_get_layer (tlobj); - tckobjs_in_layer = track_get_by_layer (layer, track); - if (!(compared = g_list_find (tckobjs_in_layer, track_object))) - return; - do { - compared = compared->prev; - - if (compared == NULL) { - /* Nothing before, let's check after */ - calculate_next_transition_with_list (track_object, tckobjs_in_layer, - layer); - goto done; - - } - } while (!GES_IS_TRACK_SOURCE (compared->data)); - - compare (compared, track_object, TRUE); - - calculate_next_transition_with_list (track_object, tckobjs_in_layer, layer); - -done: - g_list_foreach (tckobjs_in_layer, (GFunc) g_object_unref, NULL); - g_list_free (tckobjs_in_layer); -} - - -static void -look_for_transition (GESTrackObject * track_object, GESTimelineLayer * layer) -{ - GESTrack *track; - GList *track_objects, *tmp, *cur; - - track = ges_track_object_get_track (track_object); - track_objects = ges_track_get_objects (track); - - cur = g_list_find (track_objects, track_object); - - for (tmp = cur->next; tmp; tmp = tmp->next) { - if (GES_IS_TRACK_SOURCE (tmp->data)) { - break; - } - if (GES_IS_TRACK_AUDIO_TRANSITION (tmp->data) - || GES_IS_TRACK_VIDEO_TRANSITION (tmp->data)) { - ges_timeline_layer_remove_object (layer, - ges_track_object_get_timeline_object (tmp->data)); - } - } - - for (tmp = cur->prev; tmp; tmp = tmp->prev) { - if (GES_IS_TRACK_SOURCE (tmp->data)) { - break; - } - if (GES_IS_TRACK_AUDIO_TRANSITION (tmp->data) - || GES_IS_TRACK_VIDEO_TRANSITION (tmp->data)) { - ges_timeline_layer_remove_object (layer, - ges_track_object_get_timeline_object (tmp->data)); - } - } - g_list_foreach (track_objects, (GFunc) g_object_unref, NULL); - g_list_free (track_objects); -} - /** * ges_timeline_layer_resync_priorities: * @layer: a #GESTimelineLayer @@ -548,140 +234,6 @@ ges_timeline_layer_resync_priorities (GESTimelineLayer * layer) return TRUE; } -/* Callbacks */ - -static void -track_object_duration_cb (GESTrackObject * track_object, - GParamSpec * arg G_GNUC_UNUSED) -{ - GESTimelineLayer *layer; - GESTimelineObject *tlobj; - - tlobj = ges_track_object_get_timeline_object (track_object); - layer = ges_timeline_object_get_layer (tlobj); - if (G_LIKELY (GES_IS_TRACK_SOURCE (track_object))) - calculate_next_transition (track_object, layer); -} - -static void -track_object_removed_cb (GESTrack * track, GESTrackObject * track_object) -{ - GList *track_objects, *tmp, *cur; - GESTimelineLayer *layer; - - track_objects = ges_track_get_objects (track); - cur = g_list_find (track_objects, track_object); - for (tmp = cur->next; tmp; tmp = tmp->next) { - if (GES_IS_TRACK_SOURCE (tmp->data)) { - break; - } - if (GES_IS_TRACK_AUDIO_TRANSITION (tmp->data) - || GES_IS_TRACK_VIDEO_TRANSITION (tmp->data)) { - layer = - ges_timeline_object_get_layer (ges_track_object_get_timeline_object - (tmp->data)); - if (ges_timeline_layer_get_auto_transition (layer)) { - ges_track_enable_update (track, FALSE); - ges_timeline_layer_remove_object (layer, - ges_track_object_get_timeline_object (tmp->data)); - ges_track_enable_update (track, TRUE); - } - g_object_unref (layer); - } - } - - for (tmp = cur->prev; tmp; tmp = tmp->prev) { - if (GES_IS_TRACK_SOURCE (tmp->data)) { - break; - } - if (GES_IS_TRACK_AUDIO_TRANSITION (tmp->data) - || GES_IS_TRACK_VIDEO_TRANSITION (tmp->data)) { - layer = - ges_timeline_object_get_layer (ges_track_object_get_timeline_object - (tmp->data)); - if (ges_timeline_layer_get_auto_transition (layer)) { - ges_track_enable_update (track, FALSE); - ges_timeline_layer_remove_object (layer, - ges_track_object_get_timeline_object (tmp->data)); - ges_track_enable_update (track, TRUE); - } - g_object_unref (layer); - } - } - g_object_unref (track_object); -} - -static void -track_object_changed_cb (GESTrackObject * track_object, - GParamSpec * arg G_GNUC_UNUSED) -{ - if (G_LIKELY (GES_IS_TRACK_SOURCE (track_object))) - calculate_transitions (track_object); -} - -static void -track_object_added_cb (GESTrack * track, GESTrackObject * track_object, - GESTimelineLayer * layer) -{ - if (GES_IS_TRACK_SOURCE (track_object)) { - g_signal_connect (G_OBJECT (track_object), "notify::start", - G_CALLBACK (track_object_changed_cb), NULL); - g_signal_connect (G_OBJECT (track_object), "notify::duration", - G_CALLBACK (track_object_duration_cb), NULL); - calculate_transitions (track_object); - } - -} - -static void -track_removed_cb (GESTrack * track, GESTrackObject * track_object, - GESTimelineLayer * layer) -{ - g_signal_handlers_disconnect_by_func (track, track_object_added_cb, layer); - g_signal_handlers_disconnect_by_func (track, track_object_removed_cb, NULL); -} - -static void -track_added_cb (GESTimeline * timeline, GESTrack * track, - GESTimelineLayer * layer) -{ - g_signal_connect (track, "track-object-removed", - (GCallback) track_object_removed_cb, NULL); - g_signal_connect (track, "track-object-added", - (GCallback) track_object_added_cb, NULL); -} - -static void -timeline_object_height_changed_cb (GESTimelineObject * obj, - GESTrackEffect * tr_eff, GESTimelineObject * second_obj) -{ - gint priority, height; - g_object_get (obj, "height", &height, "priority", &priority, NULL); - g_object_set (second_obj, "priority", priority + height, NULL); -} - -static void -start_calculating_transitions (GESTimelineLayer * layer) -{ - GList *tmp, *tracks = ges_timeline_get_tracks (layer->timeline); - - g_signal_connect (layer->timeline, "track-added", G_CALLBACK (track_added_cb), - layer); - g_signal_connect (layer->timeline, "track-removed", - G_CALLBACK (track_removed_cb), layer); - - for (tmp = tracks; tmp; tmp = tmp->next) { - g_signal_connect (G_OBJECT (tmp->data), "track-object-added", - G_CALLBACK (track_object_added_cb), layer); - g_signal_connect (G_OBJECT (tmp->data), "track-object-removed", - G_CALLBACK (track_object_removed_cb), NULL); - } - - g_list_free_full (tracks, g_object_unref); - - /* FIXME calculate all the transitions at that time */ -} - static void new_asset_cb (GESAsset * source, GAsyncResult * res, NewAssetUData * udata) { @@ -741,7 +293,6 @@ ges_timeline_layer_remove_object (GESTimelineLayer * layer, GESTimelineObject * object) { GESTimelineLayer *current_layer; - GList *trackobjects, *tmp; g_return_val_if_fail (GES_IS_TIMELINE_LAYER (layer), FALSE); g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); @@ -759,17 +310,6 @@ ges_timeline_layer_remove_object (GESTimelineLayer * layer, } g_object_unref (current_layer); - if (layer->priv->auto_transition && GES_IS_TIMELINE_SOURCE (object)) { - trackobjects = ges_timeline_object_get_track_objects (object); - - for (tmp = trackobjects; tmp; tmp = tmp->next) { - look_for_transition (tmp->data, layer); - } - - g_list_foreach (trackobjects, (GFunc) g_object_unref, NULL); - g_list_free (trackobjects); - } - /* emit 'object-removed' */ g_signal_emit (layer, ges_timeline_layer_signals[OBJECT_REMOVED], 0, object); @@ -842,9 +382,6 @@ ges_timeline_layer_set_auto_transition (GESTimelineLayer * layer, g_return_if_fail (GES_IS_TIMELINE_LAYER (layer)); - if (auto_transition && layer->timeline) - start_calculating_transitions (layer); - layer->priv->auto_transition = auto_transition; g_object_notify (G_OBJECT (layer), "auto-transition"); } @@ -946,7 +483,6 @@ ges_timeline_layer_add_object (GESTimelineLayer * layer, priv = layer->priv; current_layer = ges_timeline_object_get_layer (object); - if (G_UNLIKELY (current_layer)) { GST_WARNING ("TimelineObject %p already belongs to another layer", object); g_object_unref (current_layer); @@ -1107,18 +643,5 @@ ges_timeline_layer_set_timeline (GESTimelineLayer * layer, { GST_DEBUG ("layer:%p, timeline:%p", layer, timeline); - if (layer->priv->auto_transition == TRUE) { - if (layer->timeline != NULL) { - g_signal_handlers_disconnect_by_func (layer->timeline, track_added_cb, - layer); - g_signal_handlers_disconnect_by_func (layer->timeline, track_removed_cb, - layer); - } - - layer->timeline = timeline; - if (timeline != NULL) - start_calculating_transitions (layer); - - } else - layer->timeline = timeline; + layer->timeline = timeline; } diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index 758e5d2500..b8088ba8f8 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -42,6 +42,7 @@ #include "ges-timeline.h" #include "ges-track.h" #include "ges-timeline-layer.h" +#include "ges-auto-transition.h" #include "ges.h" typedef struct _MoveContext MoveContext; @@ -143,8 +144,17 @@ struct _GESTimelinePrivate * probably through a ges_timeline_layer_get_track_objects () method */ GHashTable *by_layer; /* {layer: GSequence of TrackObject by start/priorities} */ + /* The set of auto_transitions we control, currently the key is + * pointerToPreviousiTrackObjAdresspointerToNextTrackObjAdress as a string, + * ... not really optimal but it works */ + GHashTable *auto_transitions; + MoveContext movecontext; + /* This variable is set to %TRUE when it makes sense to update the transitions, + * and %FALSE otherwize */ + gboolean needs_transitions_update; + gboolean updates_enabled; }; @@ -292,12 +302,15 @@ ges_timeline_dispose (GObject * object) 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_tckobjs); g_hash_table_unref (priv->movecontext.moving_tlobjs); + g_hash_table_unref (priv->auto_transitions); + G_OBJECT_CLASS (ges_timeline_parent_class)->dispose (object); } @@ -513,6 +526,12 @@ ges_timeline_init (GESTimeline * self) priv->starts_ends = g_sequence_new (g_free); priv->tracksources = g_sequence_new (g_object_unref); + priv->auto_transitions = + g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gst_object_unref); + priv->needs_transitions_update = TRUE; + + priv->updates_enabled = TRUE; + g_signal_connect_after (self, "select-tracks-for-object", G_CALLBACK (select_tracks_for_object_default), NULL); } @@ -629,6 +648,227 @@ sort_starts_ends_start (GESTimeline * timeline, TrackObjIters * iters) timeline_update_duration (timeline); } +static void +_destroy_auto_transition_cb (GESAutoTransition * auto_transition, + GESTimeline * timeline) +{ + GESTimelinePrivate *priv = timeline->priv; + GESTimelineObject *transition = auto_transition->timeline_transition; + GESTimelineLayer *layer = ges_timeline_object_get_layer (transition); + + ges_timeline_layer_remove_object (layer, transition); + g_signal_handlers_disconnect_by_func (auto_transition, + _destroy_auto_transition_cb, timeline); + + if (!g_hash_table_remove (priv->auto_transitions, auto_transition->key)) + GST_WARNING_OBJECT (timeline, "Could not remove auto_transition %" + GST_PTR_FORMAT, auto_transition->key); +} + +static GESAutoTransition * +create_transition (GESTimeline * timeline, GESTrackObject * previous, + GESTrackObject * next, GESTimelineObject * transition, + GESTimelineLayer * layer, guint64 start, guint64 duration) +{ + GList *tckobjs; + GESAsset *asset; + GESAutoTransition *auto_transition; + + if (transition == NULL) { + /* TODO make it possible to specify a Transition asset in the API */ + asset = ges_asset_request (GES_TYPE_TIMELINE_STANDARD_TRANSITION, + "crossfade", NULL); + transition = + ges_timeline_layer_add_asset (layer, asset, start, 0, duration, 1, + ges_track_object_get_track_type (next)); + } else { + GST_DEBUG_OBJECT (timeline, + "Reusing already existing transition: %" GST_PTR_FORMAT, transition); + } + + /* We know there is only 1 TrackObject */ + tckobjs = ges_timeline_object_get_track_objects (transition); + auto_transition = ges_auto_transition_new (tckobjs->data, previous, next); + g_list_free_full (tckobjs, gst_object_unref); + + g_signal_connect (auto_transition, "destroy-me", + G_CALLBACK (_destroy_auto_transition_cb), timeline); + + g_hash_table_insert (timeline->priv->auto_transitions, + auto_transition->key, auto_transition); + + return auto_transition; +} + +typedef GESAutoTransition *(*GetAutoTransitionFunc) (GESTimeline * timeline, + GESTimelineLayer * layer, GESTrack * track, GESTrackObject * previous, + GESTrackObject * next, GstClockTime transition_duration); + +static GESAutoTransition * +_find_transition_from_auto_transitions (GESTimeline * timeline, + GESTimelineLayer * layer, GESTrack * track, GESTrackObject * prev, + GESTrackObject * next, GstClockTime transition_duration) +{ + GESAutoTransition *auto_transition; + + gchar *key = g_strdup_printf ("%p%p", prev, next); + + auto_transition = g_hash_table_lookup (timeline->priv->auto_transitions, key); + g_free (key); + + return auto_transition; +} + +static GESAutoTransition * +_create_auto_transition_from_transitions (GESTimeline * timeline, + GESTimelineLayer * layer, GESTrack * track, GESTrackObject * prev, + GESTrackObject * next, GstClockTime transition_duration) +{ + GSequenceIter *tmp_iter; + GSequence *by_layer_sequence; + + GESTimelinePrivate *priv = timeline->priv; + GESAutoTransition *auto_transition = + _find_transition_from_auto_transitions (timeline, layer, track, prev, + next, transition_duration); + + if (auto_transition) + return auto_transition; + + + /* 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)) { + GESTrackObject *maybe_transition = g_sequence_get (tmp_iter); + + if (ges_track_object_get_track (maybe_transition) != track) + continue; + + if (maybe_transition->start > next->start) + break; + else if (maybe_transition->start != next->start || + maybe_transition->duration != transition_duration) + continue; + else if (GES_IS_TRACK_TRANSITION (maybe_transition)) + /* Use that transition */ + /* TODO We should make sure that the transition contains only + * TrackObject-s in @track and if it is not the case properly unlink the + * object to use it */ + return create_transition (timeline, prev, next, + ges_track_object_get_timeline_object (maybe_transition), layer, + next->start, transition_duration); + } + + return NULL; +} + +/* 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, GESTimelineLayer * layer, + GESTrack * track, GESTrackObject * initiating_obj, + GetAutoTransitionFunc get_auto_transition) +{ + guint32 layer_prio; + GSequenceIter *iter; + GESAutoTransition *transition; + + GESTrack *ctrack = track; + GList *entered = NULL; /* List of TrackObject for wich we walk through the + * "start" but not the "end" in the starts_ends list */ + GESTimelinePrivate *priv = timeline->priv; + + if (!layer || !ges_timeline_layer_get_auto_transition (layer)) + return; + + layer_prio = ges_timeline_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); + GESTrackObject *next = g_hash_table_lookup (timeline->priv->by_object, + start_or_end); + + /* Only object that are in that layer and track */ + if ((next->priority / LAYER_HEIGHT) != layer_prio || + (track && track != ges_track_object_get_track (next))) + continue; + + if (track == NULL) + ctrack = ges_track_object_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; + } + + for (tmp = entered; tmp; tmp = tmp->next) { + gint64 transition_duration; + + GESTrackObject *prev = tmp->data; + + if (ctrack != ges_track_object_get_track (prev)) + continue; + + transition_duration = (prev->start + prev->duration) - next->start; + if (transition_duration > 0 && transition_duration < prev->duration && + transition_duration < next->duration) { + transition = + get_auto_transition (timeline, layer, ctrack, prev, next, + transition_duration); + if (!transition) + transition = create_transition (timeline, prev, next, NULL, layer, + next->start, 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); + } +} + +/* @tck_obj must be a GESTrackSource */ +static void +create_transitions (GESTimeline * timeline, GESTrackObject * tck_obj) +{ + GESTrack *track; + GList *layer_node; + + GESTimelinePrivate *priv = timeline->priv; + + if (!priv->needs_transitions_update || !priv->updates_enabled) + return; + + GST_DEBUG_OBJECT (timeline, "Creating transitions around %p", tck_obj); + + track = ges_track_object_get_track (tck_obj); + layer_node = g_list_find_custom (timeline->layers, + GINT_TO_POINTER (tck_obj->priority / LAYER_HEIGHT), + (GCompareFunc) find_layer_by_prio); + + _create_transitions_on_layer (timeline, + layer_node ? layer_node->data : NULL, track, tck_obj, + _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) @@ -744,6 +984,7 @@ start_tracking_track_object (GESTimeline * timeline, GESTrackObject * tckobj) timeline->priv->movecontext.needs_move_ctx = TRUE; timeline_update_duration (timeline); + create_transitions (timeline, tckobj); } } @@ -1084,9 +1325,12 @@ ges_timeline_trim_object_simple (GESTimeline * timeline, GESTrackObject * obj, duration = MAX (0, real_dur); duration = MIN (duration, max_duration - obj->inpoint); + timeline->priv->needs_transitions_update = FALSE; ges_track_object_set_start (obj, nstart); - ges_track_object_set_duration (obj, duration); ges_track_object_set_inpoint (obj, inpoint); + timeline->priv->needs_transitions_update = TRUE; + + ges_track_object_set_duration (obj, duration); break; case GES_EDGE_END: { @@ -1133,6 +1377,7 @@ timeline_ripple_object (GESTimeline * timeline, GESTrackObject * obj, 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) @@ -1163,6 +1408,7 @@ timeline_ripple_object (GESTimeline * timeline, GESTrackObject * obj, 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); @@ -1195,6 +1441,7 @@ timeline_ripple_object (GESTimeline * timeline, GESTrackObject * obj, } g_list_free (moved_tlobjs); + timeline->priv->needs_transitions_update = TRUE; GST_DEBUG ("Done Rippling end"); break; case GES_EDGE_START: @@ -1273,6 +1520,7 @@ timeline_roll_object (GESTimeline * timeline, GESTrackObject * obj, duration = ges_track_object_get_duration (obj); end = start + duration; + timeline->priv->needs_transitions_update = FALSE; switch (edge) { case GES_EDGE_START: @@ -1587,6 +1835,15 @@ add_object_to_tracks (GESTimeline * timeline, GESTimelineObject * object, } } +static void +layer_auto_transition_changed_cb (GESTimelineLayer * layer, + GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) +{ + _create_transitions_on_layer (timeline, layer, NULL, NULL, + _create_auto_transition_from_transitions); + +} + static void layer_object_added_cb (GESTimelineLayer * layer, GESTimelineObject * object, GESTimeline * timeline) @@ -1595,6 +1852,10 @@ layer_object_added_cb (GESTimelineLayer * layer, GESTimelineObject * object, GST_DEBUG ("TimelineObject %p is moving from a layer to another, not doing" " anything on it", object); timeline->priv->movecontext.needs_move_ctx = TRUE; + + _create_transitions_on_layer (timeline, layer, NULL, NULL, + _find_transition_from_auto_transitions); + return; } @@ -1637,8 +1898,8 @@ layer_object_removed_cb (GESTimelineLayer * layer, GESTimelineObject * object, ges_track_object_get_track (trobj), (GCompareFunc) custom_find_track))) { GST_DEBUG ("Belongs to one of the tracks we control"); - ges_track_remove_object (ges_track_object_get_track (trobj), trobj); + ges_track_remove_object (ges_track_object_get_track (trobj), trobj); ges_timeline_object_release_track_object (object, trobj); } /* removing the reference added by _get_track_objects() */ @@ -1674,6 +1935,7 @@ trackobj_start_changed_cb (GESTrackObject * child, timeline->priv->snapping_distance == 0) timeline->priv->movecontext.needs_move_ctx = TRUE; + create_transitions (timeline, child); } } @@ -1736,6 +1998,8 @@ trackobj_duration_changed_cb (GESTrackObject * child, timeline->priv->snapping_distance == 0) { timeline->priv->movecontext.needs_move_ctx = TRUE; } + + create_transitions (timeline, child); } } @@ -1760,16 +2024,18 @@ track_object_removed_cb (GESTrack * track, GESTrackObject * object, { if (GES_IS_TRACK_SOURCE (object)) { - /* 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 (object, trackobj_start_changed_cb, + NULL); g_signal_handlers_disconnect_by_func (object, trackobj_duration_changed_cb, NULL); g_signal_handlers_disconnect_by_func (object, trackobj_priority_changed_cb, NULL); + stop_tracking_track_object (timeline, object); } @@ -2014,6 +2280,8 @@ ges_timeline_add_layer (GESTimeline * timeline, GESTimelineLayer * layer) G_CALLBACK (layer_object_removed_cb), timeline); g_signal_connect (layer, "notify::priority", G_CALLBACK (layer_priority_changed_cb), timeline); + g_signal_connect (layer, "notify::auto-transition", + G_CALLBACK (layer_auto_transition_changed_cb), timeline); GST_DEBUG ("Done adding layer, emitting 'layer-added' signal"); g_signal_emit (timeline, ges_timeline_signals[LAYER_ADDED], 0, layer); @@ -2069,10 +2337,13 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESTimelineLayer * layer) g_signal_handlers_disconnect_by_func (layer, layer_object_added_cb, timeline); g_signal_handlers_disconnect_by_func (layer, layer_object_removed_cb, timeline); + g_signal_handlers_disconnect_by_func (layer, layer_priority_changed_cb, + timeline); + g_signal_handlers_disconnect_by_func (layer, + layer_auto_transition_changed_cb, timeline); g_hash_table_remove (timeline->priv->by_layer, layer); timeline->layers = g_list_remove (timeline->layers, layer); - ges_timeline_layer_set_timeline (layer, NULL); g_signal_emit (timeline, ges_timeline_signals[LAYER_REMOVED], 0, layer); @@ -2358,6 +2629,12 @@ ges_timeline_enable_update (GESTimeline * timeline, gboolean enabled) /* Make sure we reset the context */ timeline->priv->movecontext.needs_move_ctx = TRUE; timeline->priv->updates_enabled = enabled; + + for (tmp = timeline->layers; tmp; tmp = tmp->next) { + _create_transitions_on_layer (timeline, GES_TIMELINE_LAYER (tmp->data), + NULL, NULL, _find_transition_from_auto_transitions); + } + if (res) g_object_notify_by_pspec (G_OBJECT (timeline), properties[PROP_UPDATE]); diff --git a/tests/check/ges/layer.c b/tests/check/ges/layer.c index 66f4040874..12f602d3ac 100644 --- a/tests/check/ges/layer.c +++ b/tests/check/ges/layer.c @@ -282,40 +282,1120 @@ GST_START_TEST (test_layer_priorities) GST_END_TEST; -GST_START_TEST (test_layer_automatic_transition) +GST_START_TEST (test_single_layer_automatic_transition) { + GESAsset *asset; GESTimeline *timeline; + GList *objects, *current; + GESTimelineObject *transition; GESTimelineLayer *layer; - GESTimelineTestSource *src, *srcbis; - GList *objects = NULL, *tmp = NULL; - gboolean res = FALSE; + GESTimelineObject *src, *src1, *src2; ges_init (); + asset = ges_asset_request (GES_TYPE_TIMELINE_TEST_SOURCE, NULL, NULL); + fail_unless (GES_IS_ASSET (asset)); + + GST_DEBUG ("Create timeline"); timeline = ges_timeline_new_audio_video (); + assert_is_type (timeline, GES_TYPE_TIMELINE); + + GST_DEBUG ("Create first layer"); layer = ges_timeline_layer_new (); - ges_timeline_add_layer (timeline, layer); + assert_is_type (layer, GES_TYPE_TIMELINE_LAYER); - g_object_set (layer, "auto-transition", TRUE, NULL); - src = ges_timeline_test_source_new (); - srcbis = ges_timeline_test_source_new (); + GST_DEBUG ("Add first layer to timeline"); + fail_unless (ges_timeline_add_layer (timeline, layer)); - g_object_set (srcbis, "start", (gint64) 5000, "duration", (gint64) 10000LL, - NULL); - g_object_set (src, "start", (gint64) 0, "duration", (gint64) 10000LL, NULL); + GST_DEBUG ("Set auto transition to first layer"); + ges_timeline_layer_set_auto_transition (layer, TRUE); - ges_timeline_layer_add_object (layer, GES_TIMELINE_OBJECT (src)); - ges_timeline_layer_add_object (layer, GES_TIMELINE_OBJECT (srcbis)); + GST_DEBUG ("Check that auto-transition was properly set"); + fail_unless (ges_timeline_layer_get_auto_transition (layer)); + + GST_DEBUG ("Adding assets to first layer"); + GST_DEBUG ("Adding object from 0 -- 1000 to first layer"); + src = GES_TIMELINE_OBJECT (ges_timeline_layer_add_asset (layer, asset, 0, 0, + 1000, 1, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_TIMELINE_OBJECT (src)); + + GST_DEBUG ("Adding object from 500 -- 1000 to first layer"); + src1 = GES_TIMELINE_OBJECT (ges_timeline_layer_add_asset (layer, asset, 500, + 0, 1000, 1, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_TIMELINE_OBJECT (src1)); + + /* + * 500__transition__1000 + * 0___________src_________1000 + * 500___________src1_________1500 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 0); + assert_equals_uint64 (src->duration, 1000); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1500 - 500); + + GST_DEBUG ("Checking that a transition has been added"); + objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 4); + assert_is_type (objects->data, GES_TYPE_TIMELINE_TEST_SOURCE); + + transition = objects->next->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + + transition = objects->next->next->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + GST_DEBUG ("Moving first source to 250"); + ges_timeline_object_set_start (src, 250); + + /* + * 500_____transition____1250 + * 250___________src_________1250 + * 500___________src1_________1500 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 250); + assert_equals_uint64 (src->duration, 1250 - 250); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1500 - 500); objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 4); + assert_is_type (objects->data, GES_TYPE_TIMELINE_TEST_SOURCE); - for (tmp = objects; tmp; tmp = tmp->next) { - if (GES_IS_TIMELINE_STANDARD_TRANSITION (tmp->data)) { - res = TRUE; - } - } + transition = objects->next->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 750); - fail_unless (res == TRUE); + transition = objects->next->next->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_int (transition->start, 500); + assert_equals_uint64 (transition->duration, 750); + g_list_free_full (objects, gst_object_unref); + + GST_DEBUG ("Moving second source to 250, the transitions should be removed"); + ges_timeline_object_set_start (src1, 250); + + /* The transition should be removed + * 250___________src_________1250 + * 250___________src1________1250 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 250); + assert_equals_uint64 (src->duration, 1250 - 250); + assert_equals_uint64 (src1->start, 250); + assert_equals_uint64 (src1->duration, 1250 - 250); + + objects = ges_timeline_layer_get_objects (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_timeline_object_edit (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 (src->start, 250); + assert_equals_uint64 (src->duration, 1250 - 250); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1250 - 500); + + objects = ges_timeline_layer_get_objects (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_object_trim_start (src, 500); + + /* 500___________src_________1250 + * 500___________src1________1250 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 500); + assert_equals_uint64 (src->duration, 1250 - 500); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1250 - 500); + + GST_DEBUG ("Trimming first source to 750, no transition should be created"); + ges_timeline_object_trim_start (src, 750); + + /* 750_______src_______1250 + * 500___________src1________1250 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 750); + assert_equals_uint64 (src->duration, 1250 - 750); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1250 - 500); + + objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 2); + g_list_free_full (objects, gst_object_unref); + + objects = ges_timeline_layer_get_objects (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_object_set_start (src, 500); + + /* 500________src______1000 + * 500___________src1________1250 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 500); + assert_equals_uint64 (src->duration, 1000 - 500); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1250 - 500); + + objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 2); + g_list_free_full (objects, gst_object_unref); + + objects = ges_timeline_layer_get_objects (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_object_set_start (src, 600); + /* 600____src___1100 + * 500___________src1________1250 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 600); + assert_equals_uint64 (src->duration, 1100 - 600); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1250 - 500); + + objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 2); + g_list_free_full (objects, gst_object_unref); + + objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 2); + g_list_free_full (objects, gst_object_unref); + + GST_DEBUG ("Adding asset to first layer"); + GST_DEBUG ("Adding object from 1250 -- 1000 to first layer"); + src2 = + GES_TIMELINE_OBJECT (ges_timeline_layer_add_asset (layer, asset, 1250, 0, + 1000, 1, GES_TRACK_TYPE_UNKNOWN)); + assert_is_type (src2, GES_TYPE_TIMELINE_TEST_SOURCE); + + /* 600____src___1100 + * 500___________src1________1250 + * 1250___________src2________2250 + */ + assert_equals_uint64 (src->start, 600); + assert_equals_uint64 (src->duration, 1100 - 600); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1250 - 500); + assert_equals_uint64 (src2->start, 1250); + assert_equals_uint64 (src2->duration, 1000); + + objects = ges_timeline_layer_get_objects (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_object_set_duration (src, 800); + /* 600__________________src_____________1400 + * 500___________src1________1250 + * 1250___________src2________2250 + * 600_____trans1_______1250 + * 1250___trans2___1400 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 600); + assert_equals_uint64 (src->duration, 1400 - 600); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1250 - 500); + + current = objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 7); + assert_is_type (objects->data, GES_TYPE_TIMELINE_TEST_SOURCE); + fail_unless (objects->data == src1); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 600); + assert_equals_uint64 (transition->duration, 1250 - 600); + ASSERT_OBJECT_REFCOUNT (transition, "The layer and ourself own a ref", 2); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 600); + assert_equals_uint64 (transition->duration, 1250 - 600); + ASSERT_OBJECT_REFCOUNT (transition, "The layer and ourself own a ref", 2); + + current = current->next; + fail_unless (current->data == src); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1250); + assert_equals_uint64 (transition->duration, 1400 - 1250); + ASSERT_OBJECT_REFCOUNT (transition, "The layer and ourself own a ref", 2); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1250); + assert_equals_uint64 (transition->duration, 1400 - 1250); + ASSERT_OBJECT_REFCOUNT (transition, "The layer and ourself own a ref", 2); + + current = current->next; + fail_unless (current->data == src2); + g_list_free_full (objects, gst_object_unref); + + GST_DEBUG ("Back to previous state"); + ges_timeline_object_set_duration (src, 1100 - 600); + /* 600____src___1100 + * 500___________src1________1250 + * 1250___________src2________2250 + */ + assert_equals_uint64 (src->start, 600); + assert_equals_uint64 (src->duration, 1100 - 600); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1250 - 500); + assert_equals_uint64 (src2->start, 1250); + assert_equals_uint64 (src2->duration, 1000); + + /* We check that the transition as actually been freed */ + fail_if (GES_IS_TIMELINE_STANDARD_TRANSITION (transition)); + + objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 3); + g_list_free_full (objects, gst_object_unref); + + GST_DEBUG + ("Set third object start to 1100, 1 new transition should be created"); + ges_timeline_object_set_start (src2, 1100); + /* 600____src___1100 + * 500___________src1________1250 + * 1100___________src2________2100 + * ^__trans___^ + */ + assert_equals_uint64 (src->start, 600); + assert_equals_uint64 (src->duration, 1100 - 600); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1250 - 500); + assert_equals_uint64 (src2->start, 1100); + assert_equals_uint64 (src2->duration, 1000); + + current = objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 5); + assert_is_type (objects->data, GES_TYPE_TIMELINE_TEST_SOURCE); + 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_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1100); + assert_equals_uint64 (transition->duration, 1250 - 1100); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1100); + assert_equals_uint64 (transition->duration, 1250 - 1100); + + current = current->next; + fail_unless (current->data == src2); + g_list_free_full (objects, gst_object_unref); + + GST_DEBUG ("Set third object start to 1000, Transition should be updated"); + ges_timeline_object_edit (src2, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_START, 1000); + /* 600____src___1100 + * !_tr__^ + * 500___________src1________1250 + * 1000___________src2________2000 + * ^____trans____^ + */ + assert_equals_uint64 (src->start, 600); + assert_equals_uint64 (src->duration, 500); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1250 - 500); + assert_equals_uint64 (src2->start, 1000); + assert_equals_uint64 (src2->duration, 1000); + + current = objects = ges_timeline_layer_get_objects (layer); + current = objects; + assert_equals_int (g_list_length (objects), 7); + assert_is_type (objects->data, GES_TYPE_TIMELINE_TEST_SOURCE); + 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_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1000); + assert_equals_uint64 (transition->duration, 1100 - 1000); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1000); + assert_equals_uint64 (transition->duration, 1100 - 1000); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1000); + assert_equals_uint64 (transition->duration, 1250 - 1000); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1000); + assert_equals_uint64 (transition->duration, 1250 - 1000); + + current = current->next; + fail_unless (current->data == src2); + g_list_free_full (objects, gst_object_unref); + + g_object_unref (timeline); +} + +GST_END_TEST; + +GST_START_TEST (test_multi_layer_automatic_transition) +{ + GESAsset *asset; + GESTimeline *timeline; + GList *objects, *current; + GESTimelineObject *transition; + GESTimelineLayer *layer, *layer1; + GESTimelineObject *src, *src1, *src2, *src3; + + ges_init (); + + asset = ges_asset_request (GES_TYPE_TIMELINE_TEST_SOURCE, NULL, NULL); + fail_unless (GES_IS_ASSET (asset)); + + GST_DEBUG ("Create timeline"); + timeline = ges_timeline_new_audio_video (); + assert_is_type (timeline, GES_TYPE_TIMELINE); + + GST_DEBUG ("Create first layer"); + layer = ges_timeline_layer_new (); + assert_is_type (layer, GES_TYPE_TIMELINE_LAYER); + + GST_DEBUG ("Add first layer to timeline"); + fail_unless (ges_timeline_add_layer (timeline, layer)); + + GST_DEBUG ("Append a new layer to the timeline"); + layer1 = ges_timeline_append_layer (timeline); + assert_is_type (layer1, GES_TYPE_TIMELINE_LAYER); + + GST_DEBUG ("Set auto transition to first layer"); + ges_timeline_layer_set_auto_transition (layer, TRUE); + + GST_DEBUG ("Check that auto-transition was properly set"); + fail_unless (ges_timeline_layer_get_auto_transition (layer)); + fail_if (ges_timeline_layer_get_auto_transition (layer1)); + + GST_DEBUG ("Adding assets to first layer"); + GST_DEBUG ("Adding object from 0 -- 1000 to first layer"); + src = GES_TIMELINE_OBJECT (ges_timeline_layer_add_asset (layer, asset, 0, 0, + 1000, 1, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_TIMELINE_OBJECT (src)); + + GST_DEBUG ("Adding object from 500 -- 1000 to first layer"); + src1 = GES_TIMELINE_OBJECT (ges_timeline_layer_add_asset (layer, asset, 500, + 0, 1000, 1, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_TIMELINE_OBJECT (src1)); + + /* + * 500__transition__1000 + * 0___________src_________1000 + * 500___________src1_________1500 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 0); + assert_equals_uint64 (src->duration, 1000); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1500 - 500); + + GST_DEBUG ("Checking that a transition has been added"); + current = objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 4); + assert_is_type (current->data, GES_TYPE_TIMELINE_TEST_SOURCE); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + GST_DEBUG ("Adding object 2 from 500 -- 1000 to second layer"); + src2 = GES_TIMELINE_OBJECT (ges_timeline_layer_add_asset (layer1, asset, 0, + 0, 1000, 1, GES_TRACK_TYPE_UNKNOWN)); + GST_DEBUG ("Adding object 3 from 500 -- 1000 to second layer"); + src3 = GES_TIMELINE_OBJECT (ges_timeline_layer_add_asset (layer1, asset, 500, + 0, 1000, 1, GES_TRACK_TYPE_UNKNOWN)); + assert_is_type (src3, GES_TYPE_TIMELINE_TEST_SOURCE); + + /* 500__transition__1000 + * 0___________src_________1000 + * 500___________src1_________1500 + *---------------------------------------------------- + * 0___________src2_________1000 + * 500___________src3_________1500 Layer1 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 0); + assert_equals_uint64 (src->duration, 1000); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1500 - 500); + assert_equals_uint64 (src2->start, 0); + assert_equals_uint64 (src2->duration, 1000); + assert_equals_uint64 (src3->start, 500); + assert_equals_uint64 (src3->duration, 1500 - 500); + + GST_DEBUG ("Checking transitions on first layer"); + current = objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 4); + assert_is_type (current->data, GES_TYPE_TIMELINE_TEST_SOURCE); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + GST_DEBUG ("Checking transitions on second layer"); + current = objects = ges_timeline_layer_get_objects (layer1); + assert_equals_int (g_list_length (objects), 2); + fail_unless (current->data == src2); + fail_unless (current->next->data == src3); + g_list_free_full (objects, gst_object_unref); + + GST_DEBUG + ("Set auto transition to second layer, a new transition should be added"); + ges_timeline_layer_set_auto_transition (layer1, TRUE); + + /* 500__transition__1000 + * 0___________src_________1000 + * 500___________src1_________1500 + *---------------------------------------------------- + * 500__transition__1000 + * 0__________src2_________1000 + * 500___________src3_________1500 Layer1 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 0); + assert_equals_uint64 (src->duration, 1000); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1500 - 500); + assert_equals_uint64 (src2->start, 0); + assert_equals_uint64 (src2->duration, 1000); + assert_equals_uint64 (src3->start, 500); + assert_equals_uint64 (src3->duration, 1500 - 500); + + GST_DEBUG ("Checking transitions on first layer"); + current = objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 4); + assert_is_type (current->data, GES_TYPE_TIMELINE_TEST_SOURCE); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + GST_DEBUG ("Checking transitions has been added on second layer"); + current = objects = ges_timeline_layer_get_objects (layer1); + assert_equals_int (g_list_length (objects), 4); + assert_is_type (current->data, GES_TYPE_TIMELINE_TEST_SOURCE); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + GST_DEBUG ("Moving src3 to 1000. should remove transition"); + ges_timeline_object_set_start (src3, 1000); + + /* 500__transition__1000 + * 0___________src_________1000 + * 500___________src1_________1500 Layer + *---------------------------------------------------- + * 0__________src2_________1000 + * 1000___________src3_________2000 Layer1 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 0); + assert_equals_uint64 (src->duration, 1000); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1500 - 500); + assert_equals_uint64 (src2->start, 0); + assert_equals_uint64 (src2->duration, 1000); + assert_equals_uint64 (src3->start, 1000); + assert_equals_uint64 (src3->duration, 2000 - 1000); + + GST_DEBUG ("Checking transitions on first layer"); + current = objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 4); + assert_is_type (current->data, GES_TYPE_TIMELINE_TEST_SOURCE); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + GST_DEBUG ("Checking transitions has been removed on second layer"); + current = objects = ges_timeline_layer_get_objects (layer1); + assert_equals_int (g_list_length (objects), 2); + fail_unless (current->data == src2); + fail_unless (current->next->data == src3); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + GST_DEBUG ("Moving src3 to first layer, should add a transition"); + ges_timeline_object_move_to_layer (src3, layer); + + /* 500__transition__1000 + * 0___________src_________1000 + * 500___________src1_________1500 + * 1000___________src3_________2000 Layer + * 1000__tr__1500 + *---------------------------------------------------- + * 0__________src2_________1000 Layer1 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 0); + assert_equals_uint64 (src->duration, 1000); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1500 - 500); + assert_equals_uint64 (src2->start, 0); + assert_equals_uint64 (src2->duration, 1000); + assert_equals_uint64 (src3->start, 1000); + assert_equals_uint64 (src3->duration, 2000 - 1000); + + GST_DEBUG ("Checking transitions on first layer"); + current = objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 7); + fail_unless (current->data == src); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + fail_unless (current->data == src1); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1000); + assert_equals_uint64 (transition->duration, 1500 - 1000); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1000); + assert_equals_uint64 (transition->duration, 1500 - 1000); + + current = current->next; + fail_unless (current->data == src3); + + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + GST_DEBUG ("Checking second layer"); + current = objects = ges_timeline_layer_get_objects (layer1); + assert_equals_int (g_list_length (objects), 1); + fail_unless (current->data == src2); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + GST_DEBUG + ("Moving src to second layer, should remove first transition on first layer"); + ges_timeline_object_move_to_layer (src, layer1); + + /* 500___________src1_________1500 + * 1000___________src3_________2000 Layer + * 1000__tr__1500 + *---------------------------------------------------- + * 0___________src_________1000 + * 0__________src2_________1000 Layer1 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 0); + assert_equals_uint64 (src->duration, 1000); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1500 - 500); + assert_equals_uint64 (src2->start, 0); + assert_equals_uint64 (src2->duration, 1000); + assert_equals_uint64 (src3->start, 1000); + assert_equals_uint64 (src3->duration, 2000 - 1000); + + GST_DEBUG ("Checking transitions on first layer"); + current = objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 4); + fail_unless (current->data == src1); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1000); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1000); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + fail_unless (current->data == src3); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + GST_DEBUG ("Checking second layer"); + current = objects = ges_timeline_layer_get_objects (layer1); + assert_equals_int (g_list_length (objects), 2); + assert_is_type (current->data, GES_TYPE_TIMELINE_TEST_SOURCE); + assert_is_type (current->next->data, GES_TYPE_TIMELINE_TEST_SOURCE); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + GST_DEBUG ("Edit src to first layer start=1500"); + ges_timeline_object_edit (src, NULL, 0, GES_EDIT_MODE_NORMAL, GES_EDGE_NONE, + 1500); + /* 1500___________src_________2500 + * 1500______tr______2000 + * 500___________src1_________1500 ^ + * 1000_________^_src3_________2000 Layer + * 1000__tr__1500 + *--------------------------------------------------------------------------- + * 0__________src2_________1000 Layer1 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 1500); + assert_equals_uint64 (src->duration, 1000); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1500 - 500); + assert_equals_uint64 (src2->start, 0); + assert_equals_uint64 (src2->duration, 1000); + assert_equals_uint64 (src3->start, 1000); + assert_equals_uint64 (src3->duration, 2000 - 1000); + + GST_DEBUG ("Checking transitions on first layer"); + current = objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 7); + fail_unless (current->data == src1); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1000); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1000); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + fail_unless (current->data == src3); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1500); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1500); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + fail_unless (current->data == src); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + GST_DEBUG ("Checking second layer"); + current = objects = ges_timeline_layer_get_objects (layer1); + assert_equals_int (g_list_length (objects), 1); + assert_is_type (current->data, GES_TYPE_TIMELINE_TEST_SOURCE); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + GST_DEBUG ("Ripple src1 to 700"); + ges_timeline_object_edit (src1, NULL, 0, GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, + 700); + /* 1700___________src_________2700 + * 1700__tr__2000 + * 700___________src1_________1700 + * 1000___________src3_________2000 Layer + * 1000______tr______1700 + *--------------------------------------------------------------------------- + * 0__________src2_________1000 Layer1 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 1700); + assert_equals_uint64 (src->duration, 1000); + assert_equals_uint64 (src1->start, 700); + assert_equals_uint64 (src1->duration, 1700 - 700); + assert_equals_uint64 (src2->start, 0); + assert_equals_uint64 (src2->duration, 1000); + assert_equals_uint64 (src3->start, 1000); + assert_equals_uint64 (src3->duration, 2000 - 1000); + + GST_DEBUG ("Checking transitions on first layer"); + current = objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 7); + fail_unless (current->data == src1); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1000); + assert_equals_uint64 (transition->duration, 1700 - 1000); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1000); + assert_equals_uint64 (transition->duration, 1700 - 1000); + + current = current->next; + fail_unless (current->data == src3); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1700); + assert_equals_uint64 (transition->duration, 2000 - 1700); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1700); + assert_equals_uint64 (transition->duration, 2000 - 1700); + + current = current->next; + fail_unless (current->data == src); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + GST_DEBUG ("Checking second layer"); + current = objects = ges_timeline_layer_get_objects (layer1); + assert_equals_int (g_list_length (objects), 1); + assert_is_type (current->data, GES_TYPE_TIMELINE_TEST_SOURCE); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + g_object_unref (timeline); +} + +GST_END_TEST; + +GST_START_TEST (test_layer_activate_automatic_transition) +{ + GESAsset *asset, *transition_asset; + GESTimeline *timeline; + GESTimelineLayer *layer; + GList *objects, *current; + GESTimelineObject *transition; + GESTimelineObject *src, *src1, *src2, *src3; + + ges_init (); + + asset = ges_asset_request (GES_TYPE_TIMELINE_TEST_SOURCE, NULL, NULL); + transition_asset = + ges_asset_request (GES_TYPE_TIMELINE_STANDARD_TRANSITION, "crossfade", + NULL); + fail_unless (GES_IS_ASSET (asset)); + + GST_DEBUG ("Create timeline"); + timeline = ges_timeline_new_audio_video (); + assert_is_type (timeline, GES_TYPE_TIMELINE); + + GST_DEBUG ("Append a layer to the timeline"); + layer = ges_timeline_append_layer (timeline); + assert_is_type (layer, GES_TYPE_TIMELINE_LAYER); + + GST_DEBUG ("Adding object from 0 -- 1000 to layer"); + src = GES_TIMELINE_OBJECT (ges_timeline_layer_add_asset (layer, asset, 0, 0, + 1000, 1, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_TIMELINE_OBJECT (src)); + + GST_DEBUG ("Adding object from 500 -- 1000 to first layer"); + src1 = GES_TIMELINE_OBJECT (ges_timeline_layer_add_asset (layer, asset, 500, + 0, 1000, 1, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_TIMELINE_OBJECT (src1)); + + GST_DEBUG ("Adding object from 1000 -- 2000 to layer"); + src2 = GES_TIMELINE_OBJECT (ges_timeline_layer_add_asset (layer, asset, 1000, + 0, 1000, 1, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_TIMELINE_OBJECT (src2)); + + GST_DEBUG ("Adding object from 2000 -- 2500 to layer"); + src3 = GES_TIMELINE_OBJECT (ges_timeline_layer_add_asset (layer, asset, 2000, + 0, 500, 1, GES_TRACK_TYPE_UNKNOWN)); + fail_unless (GES_IS_TIMELINE_OBJECT (src3)); + + /* + * 0___________src_________1000 + * 500___________src1_________1500 + * 1000____src2_______2000 + * 2000_______src2_____2500 + */ + GST_DEBUG ("Checking src timing values"); + assert_equals_uint64 (src->start, 0); + assert_equals_uint64 (src->duration, 1000); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1500 - 500); + assert_equals_uint64 (src2->start, 1000); + assert_equals_uint64 (src2->duration, 1000); + assert_equals_uint64 (src3->start, 2000); + assert_equals_uint64 (src3->duration, 500); + + GST_DEBUG ("Checking that no transition has been added"); + current = objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 4); + assert_is_type (current->data, GES_TYPE_TIMELINE_TEST_SOURCE); + g_list_free_full (objects, gst_object_unref); + + GST_DEBUG ("Adding transition from 1000 -- 1500 to layer"); + transition = + GES_TIMELINE_OBJECT (ges_timeline_layer_add_asset (layer, + transition_asset, 1000, 0, 500, 1, GES_TRACK_TYPE_VIDEO)); + fail_unless (GES_IS_TIMELINE_STANDARD_TRANSITION (transition)); + objects = ges_timeline_object_get_track_objects (transition); + assert_equals_int (g_list_length (objects), 1); + g_list_free_full (objects, gst_object_unref); + + GST_DEBUG ("Checking the transitions"); + /* + * 0___________src_________1000 + * 500___________src1_________1500 + * 1000__tr__1500 (1 of the 2 tracks only) + * 1000____src2_______2000 + * 2000_______src3_____2500 + */ + current = objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 5); + current = current->next; + assert_is_type (current->data, GES_TYPE_TIMELINE_TEST_SOURCE); + current = current->next; + assert_is_type (current->data, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + current = current->next; + assert_is_type (current->data, GES_TYPE_TIMELINE_TEST_SOURCE); + current = current->next; + assert_is_type (current->data, GES_TYPE_TIMELINE_TEST_SOURCE); + g_list_free_full (objects, gst_object_unref); + + ges_timeline_layer_set_auto_transition (layer, TRUE); + /* + * 0___________src_________1000 + * 500______tr______1000 + * 500___________src1_________1500 + * 1000__tr__1500 + * 1000____src2_______2000 + * 2000_______src3_____2500 + */ + current = objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 8); + assert_equals_uint64 (src->start, 0); + assert_equals_uint64 (src->duration, 1000); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1500 - 500); + assert_equals_uint64 (src2->start, 1000); + assert_equals_uint64 (src2->duration, 1000); + assert_equals_uint64 (src3->start, 2000); + assert_equals_uint64 (src3->duration, 500); + + GST_DEBUG ("Checking transitions"); + fail_unless (current->data == src); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + fail_unless (current->data == src1); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1000); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1000); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + fail_unless (current->data == src2); + + current = current->next; + fail_unless (current->data == src3); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + GST_DEBUG ("Moving src2 to 1200, check everything updates properly"); + ges_timeline_object_set_start (src2, 1200); + /* + * 0___________src_________1000 + * 500______tr______1000 + * 500___________src1_________1500 + * 1200_tr_1500 + * 1200____src2_______2200 + * !__tr__^ + * 2000_______src3_____2500 + */ + current = objects = ges_timeline_layer_get_objects (layer); + assert_equals_int (g_list_length (objects), 10); + assert_equals_uint64 (src->start, 0); + assert_equals_uint64 (src->duration, 1000); + assert_equals_uint64 (src1->start, 500); + assert_equals_uint64 (src1->duration, 1500 - 500); + assert_equals_uint64 (src2->start, 1200); + assert_equals_uint64 (src2->duration, 1000); + assert_equals_uint64 (src3->start, 2000); + assert_equals_uint64 (src3->duration, 500); + + GST_DEBUG ("Checking transitions"); + fail_unless (current->data == src); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 500); + assert_equals_uint64 (transition->duration, 500); + + current = current->next; + fail_unless (current->data == src1); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1200); + assert_equals_uint64 (transition->duration, 300); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 1200); + assert_equals_uint64 (transition->duration, 300); + + current = current->next; + fail_unless (current->data == src2); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 2000); + assert_equals_uint64 (transition->duration, 200); + + current = current->next; + transition = current->data; + assert_is_type (transition, GES_TYPE_TIMELINE_STANDARD_TRANSITION); + assert_equals_uint64 (transition->start, 2000); + assert_equals_uint64 (transition->duration, 200); + + current = current->next; + fail_unless (current->data == src3); + g_list_free_full (objects, gst_object_unref); + ASSERT_OBJECT_REFCOUNT (transition, "Only the layer owns a ref", 1); + + + gst_object_unref (timeline); } GST_END_TEST; @@ -660,7 +1740,9 @@ ges_suite (void) tcase_add_test (tc_chain, test_layer_properties); tcase_add_test (tc_chain, test_layer_priorities); - tcase_add_test (tc_chain, test_layer_automatic_transition); + tcase_add_test (tc_chain, test_single_layer_automatic_transition); + tcase_add_test (tc_chain, test_multi_layer_automatic_transition); + tcase_add_test (tc_chain, test_layer_activate_automatic_transition); tcase_add_test (tc_chain, test_layer_meta_string); tcase_add_test (tc_chain, test_layer_meta_boolean); tcase_add_test (tc_chain, test_layer_meta_int);