Reimplement the auto-transition feature

+ Actually implement unit tests
This commit is contained in:
Thibault Saunier 2013-01-10 18:50:54 -03:00
parent 5f69200a3a
commit 02652902f5
6 changed files with 1646 additions and 502 deletions

View file

@ -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) \

182
ges/ges-auto-transition.c Normal file
View file

@ -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 <thibault.saunier@collabora.com>
*
* 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 <http://www.gnu.org/licenses/>.";
*/
/* 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;
}

78
ges/ges-auto-transition.h Normal file
View file

@ -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 <thibault.saunier@collabora.com>
*
* 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 <http://www.gnu.org/licenses/>.";
*/
#ifndef _GES_AUTO_TRANSITION_H_
#define _GES_AUTO_TRANSITION_H_
#include <glib-object.h>
#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;
/* <read only and construct only> */
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_ */

View file

@ -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;
}

View file

@ -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]);

File diff suppressed because it is too large Load diff