1 /* GStreamer Editing Services
2 * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk>
3 * 2009 Nokia Corporation
4 * 2012 Thibault Saunier <tsaunier@gnome.org>
6 * Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public
19 * License along with this library; if not, write to the
20 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
27 * @short_description: Multimedia timeline
29 * #GESTimeline is the central object for any multimedia timeline.
31 * Contains a list of #GESLayer which users should use to arrange the
32 * various clips through time.
34 * The output type is determined by the #GESTrack that are set on
37 * To save/load a timeline, you can use the ges_timeline_load_from_uri() and
38 * ges_timeline_save_to_uri() methods to use the default format. If you wish
40 * Note that any change you make in the timeline will not actually be taken
41 * into account until you call the #ges_timeline_commit method.
47 #include "ges-internal.h"
48 #include "ges-project.h"
49 #include "ges-container.h"
50 #include "ges-timeline.h"
51 #include "ges-track.h"
52 #include "ges-layer.h"
53 #include "ges-auto-transition.h"
56 typedef struct _MoveContext MoveContext;
58 static GPtrArray *select_tracks_for_object_default (GESTimeline * timeline,
59 GESClip * clip, GESTrackElement * tr_obj, gpointer user_data);
60 static inline void init_movecontext (MoveContext * mv_ctx, gboolean first_init);
61 static void ges_extractable_interface_init (GESExtractableInterface * iface);
62 static void ges_meta_container_interface_init
63 (GESMetaContainerInterface * iface);
65 GST_DEBUG_CATEGORY_STATIC (ges_timeline_debug);
66 #undef GST_CAT_DEFAULT
67 #define GST_CAT_DEFAULT ges_timeline_debug
69 /* lock to protect dynamic callbacks, like pad-added */
70 #define DYN_LOCK(timeline) (&GES_TIMELINE (timeline)->priv->dyn_mutex)
71 #define LOCK_DYN(timeline) G_STMT_START { \
72 GST_LOG_OBJECT (timeline, "Getting dynamic lock from %p", \
74 g_rec_mutex_lock (DYN_LOCK (timeline)); \
75 GST_LOG_OBJECT (timeline, "Got Dynamic lock from %p", \
79 #define UNLOCK_DYN(timeline) G_STMT_START { \
80 GST_LOG_OBJECT (timeline, "Unlocking dynamic lock from %p", \
82 g_rec_mutex_unlock (DYN_LOCK (timeline)); \
83 GST_LOG_OBJECT (timeline, "Unlocked Dynamic lock from %p", \
87 #define CHECK_THREAD(timeline) g_assert(timeline->priv->valid_thread == g_thread_self())
89 typedef struct TrackObjIters
91 GSequenceIter *iter_start;
92 GSequenceIter *iter_end;
93 GSequenceIter *iter_obj;
94 GSequenceIter *iter_by_layer;
97 GESTrackElement *trackelement;
101 _destroy_obj_iters (TrackObjIters * iters)
103 g_slice_free (TrackObjIters, iters);
106 /* The move context is used for the timeline editing modes functions in order to
107 * + Ripple / Roll / Slide / Move / Trim
109 * The context aims at avoiding to recalculate values/objects on each call of the
118 /* The start of the moving context */
121 /* Ripple and Roll Objects */
122 GList *moving_trackelements;
124 /* We use it as a set of Clip to move between layers */
125 GHashTable *toplevel_containers;
126 /* Min priority of the objects currently in toplevel_containers */
127 guint min_move_layer;
128 /* Max priority of the objects currently in toplevel_containers */
129 guint max_layer_prio;
131 /* Never trim so duration would become < 0 */
132 guint64 max_trim_pos;
134 /* Never trim so inpoint + duration would change */
135 guint64 min_trim_pos;
137 /* fields to force/avoid new context */
138 /* Set to %TRUE when the track is doing updates of track element
139 * properties so we don't end up always needing new move context */
140 gboolean ignore_needs_ctx;
141 gboolean needs_move_ctx;
143 /* Last snapping properties */
144 GESTrackElement *last_snaped1;
145 GESTrackElement *last_snaped2;
146 GstClockTime *last_snap_ts;
148 /* Priority of the layer where we are moving current clip
149 * -1 if not moving any clip to a new layer. */
150 GESLayer *moving_to_layer;
153 struct _GESTimelinePrivate
155 /* The duration of the timeline */
158 /* The auto-transition of the timeline */
159 gboolean auto_transition;
160 /* Use to determine that a edit action should be rolled
161 * back because it leads to a wrong state of the element
162 * position (currently only happens if 3 clips overlap) */
163 gboolean needs_rollback;
164 gboolean rolling_back;
166 /* Timeline edition modes and snapping management */
167 guint64 snapping_distance;
169 /* FIXME: Should we offer an API over those fields ?
170 * FIXME: Should other classes than subclasses of Source also
173 /* Snapping fields */
174 GHashTable *by_start; /* {Source: start} */
175 GHashTable *by_end; /* {Source: end} */
176 GHashTable *by_object; /* {timecode: Source} */
177 GHashTable *obj_iters; /* {Source: TrackObjIters} */
178 GSequence *starts_ends; /* Sorted list of starts/ends */
179 /* We keep 1 reference to our trackelement here */
180 GSequence *tracksources; /* Source-s sorted by start/priorities */
184 /* FIXME: We should definitly offer an API over this,
185 * probably through a ges_layer_get_track_elements () method */
186 GHashTable *by_layer; /* {layer: GSequence of TrackElement by start/priorities} */
188 /* Avoid sorting layers when we are actually resyncing them ourself */
189 gboolean resyncing_layers;
190 GList *auto_transitions;
192 MoveContext movecontext;
194 /* This variable is set to %TRUE when it makes sense to update the transitions,
195 * and %FALSE otherwize */
196 gboolean needs_transitions_update;
198 /* While we are creating and adding the TrackElements for a clip, we need to
199 * ignore the child-added signal */
200 GESClip *ignore_track_element_added;
205 GHashTable *all_elements;
207 /* With GST_OBJECT_LOCK */
208 guint expected_async_done;
209 /* With GST_OBJECT_LOCK */
210 guint expected_commited;
212 /* For ges_timeline_commit_sync */
213 GMutex commited_lock;
216 GThread *valid_thread;
219 /* private structure to contain our track-related information */
223 GESTimeline *timeline;
225 GstPad *pad; /* Pad from the track */
235 PROP_AUTO_TRANSITION,
236 PROP_SNAPPING_DISTANCE,
241 static GParamSpec *properties[PROP_LAST];
253 SELECT_TRACKS_FOR_OBJECT,
258 G_DEFINE_TYPE_WITH_CODE (GESTimeline, ges_timeline, GST_TYPE_BIN,
259 G_ADD_PRIVATE (GESTimeline)
260 G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, ges_extractable_interface_init)
261 G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER,
262 ges_meta_container_interface_init));
264 static GstBinClass *parent_class;
266 static guint ges_timeline_signals[LAST_SIGNAL] = { 0 };
268 static gint custom_find_track (TrackPrivate * tr_priv, GESTrack * track);
270 static guint nb_assets = 0;
272 /* GESExtractable implementation */
274 extractable_check_id (GType type, const gchar * id)
279 res = g_strdup_printf ("%s-%i", "project", nb_assets);
289 extractable_get_id (GESExtractable * self)
293 if (!(asset = ges_extractable_get_asset (self)))
296 return g_strdup (ges_asset_get_id (asset));
300 ges_extractable_interface_init (GESExtractableInterface * iface)
302 iface->asset_type = GES_TYPE_PROJECT;
303 iface->check_id = (GESExtractableCheckId) extractable_check_id;
304 iface->get_id = extractable_get_id;
308 ges_meta_container_interface_init (GESMetaContainerInterface * iface)
312 /* GObject Standard vmethods*/
314 ges_timeline_get_property (GObject * object, guint property_id,
315 GValue * value, GParamSpec * pspec)
317 GESTimeline *timeline = GES_TIMELINE (object);
319 switch (property_id) {
321 g_value_set_uint64 (value, timeline->priv->duration);
323 case PROP_AUTO_TRANSITION:
324 g_value_set_boolean (value, timeline->priv->auto_transition);
326 case PROP_SNAPPING_DISTANCE:
327 g_value_set_uint64 (value, timeline->priv->snapping_distance);
330 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
335 ges_timeline_set_property (GObject * object, guint property_id,
336 const GValue * value, GParamSpec * pspec)
338 GESTimeline *timeline = GES_TIMELINE (object);
340 switch (property_id) {
341 case PROP_AUTO_TRANSITION:
342 ges_timeline_set_auto_transition (timeline, g_value_get_boolean (value));
344 case PROP_SNAPPING_DISTANCE:
345 timeline->priv->snapping_distance = g_value_get_uint64 (value);
348 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
353 ges_timeline_dispose (GObject * object)
355 GESTimeline *tl = GES_TIMELINE (object);
356 GESTimelinePrivate *priv = tl->priv;
360 GESLayer *layer = (GESLayer *) tl->layers->data;
361 ges_timeline_remove_layer (GES_TIMELINE (object), layer);
364 /* FIXME: it should be possible to remove tracks before removing
365 * layers, but at the moment this creates a problem because the track
366 * objects aren't notified that their nleobjects have been destroyed.
370 ges_timeline_remove_track (GES_TIMELINE (object), tl->tracks->data);
372 groups = g_list_copy (priv->groups);
373 for (tmp = groups; tmp; tmp = tmp->next) {
374 GList *elems = ges_container_ungroup (tmp->data, FALSE);
376 g_list_free_full (elems, gst_object_unref);
378 g_list_free (priv->groups);
379 g_list_free (groups);
381 g_hash_table_unref (priv->by_start);
382 g_hash_table_unref (priv->by_end);
383 g_hash_table_unref (priv->by_object);
384 g_hash_table_unref (priv->by_layer);
385 g_hash_table_unref (priv->obj_iters);
386 g_sequence_free (priv->starts_ends);
387 g_sequence_free (priv->tracksources);
388 g_list_free (priv->movecontext.moving_trackelements);
389 g_hash_table_unref (priv->movecontext.toplevel_containers);
391 g_list_free_full (priv->auto_transitions, gst_object_unref);
393 g_hash_table_unref (priv->all_elements);
395 G_OBJECT_CLASS (ges_timeline_parent_class)->dispose (object);
399 ges_timeline_finalize (GObject * object)
401 GESTimeline *tl = GES_TIMELINE (object);
403 g_rec_mutex_clear (&tl->priv->dyn_mutex);
405 G_OBJECT_CLASS (ges_timeline_parent_class)->finalize (object);
411 ges_timeline_handle_message (GstBin * bin, GstMessage * message)
413 GESTimeline *timeline = GES_TIMELINE (bin);
415 if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT) {
416 GstMessage *amessage = NULL;
417 const GstStructure *mstructure = gst_message_get_structure (message);
419 if (gst_structure_has_name (mstructure, "NleCompositionStartUpdate")) {
420 if (g_strcmp0 (gst_structure_get_string (mstructure, "reason"), "Seek")) {
421 GST_INFO_OBJECT (timeline,
422 "A composition is starting an update because of %s"
423 " not considering async", gst_structure_get_string (mstructure,
429 GST_OBJECT_LOCK (timeline);
430 if (timeline->priv->expected_async_done == 0) {
431 amessage = gst_message_new_async_start (GST_OBJECT_CAST (bin));
432 timeline->priv->expected_async_done = g_list_length (timeline->tracks);
433 GST_INFO_OBJECT (timeline, "Posting ASYNC_START %s",
434 gst_structure_get_string (mstructure, "reason"));
436 GST_OBJECT_UNLOCK (timeline);
438 } else if (gst_structure_has_name (mstructure, "NleCompositionUpdateDone")) {
439 if (g_strcmp0 (gst_structure_get_string (mstructure, "reason"), "Seek")) {
440 GST_INFO_OBJECT (timeline,
441 "A composition is done updating because of %s"
442 " not considering async", gst_structure_get_string (mstructure,
448 GST_OBJECT_LOCK (timeline);
449 timeline->priv->expected_async_done -= 1;
450 if (timeline->priv->expected_async_done == 0) {
451 amessage = gst_message_new_async_done (GST_OBJECT_CAST (bin),
452 GST_CLOCK_TIME_NONE);
453 GST_INFO_OBJECT (timeline, "Posting ASYNC_DONE %s",
454 gst_structure_get_string (mstructure, "reason"));
456 GST_OBJECT_UNLOCK (timeline);
460 gst_element_post_message (GST_ELEMENT_CAST (bin), amessage);
464 gst_element_post_message (GST_ELEMENT_CAST (bin), message);
467 /* we collect the first result */
469 _gst_array_accumulator (GSignalInvocationHint * ihint,
470 GValue * return_accu, const GValue * handler_return, gpointer dummy)
474 array = g_value_get_boxed (handler_return);
475 if (!(ihint->run_type & G_SIGNAL_RUN_CLEANUP))
476 g_value_set_boxed (return_accu, array);
482 ges_timeline_class_init (GESTimelineClass * klass)
484 GObjectClass *object_class = G_OBJECT_CLASS (klass);
485 GstBinClass *bin_class = GST_BIN_CLASS (klass);
487 GST_DEBUG_CATEGORY_INIT (ges_timeline_debug, "gestimeline",
488 GST_DEBUG_FG_YELLOW, "ges timeline");
490 parent_class = g_type_class_peek_parent (klass);
492 object_class->get_property = ges_timeline_get_property;
493 object_class->set_property = ges_timeline_set_property;
494 object_class->dispose = ges_timeline_dispose;
495 object_class->finalize = ges_timeline_finalize;
497 bin_class->handle_message = GST_DEBUG_FUNCPTR (ges_timeline_handle_message);
500 * GESTimeline:duration:
502 * Current duration (in nanoseconds) of the #GESTimeline
504 properties[PROP_DURATION] =
505 g_param_spec_uint64 ("duration", "Duration",
506 "The duration of the timeline", 0, G_MAXUINT64,
507 GST_CLOCK_TIME_NONE, G_PARAM_READABLE);
508 g_object_class_install_property (object_class, PROP_DURATION,
509 properties[PROP_DURATION]);
512 * GESTimeline:auto-transition:
514 * Sets whether transitions are added automagically when clips overlap.
516 g_object_class_install_property (object_class, PROP_AUTO_TRANSITION,
517 g_param_spec_boolean ("auto-transition", "Auto-Transition",
518 "whether the transitions are added", FALSE, G_PARAM_READWRITE));
521 * GESTimeline:snapping-distance:
523 * Distance (in nanoseconds) from which a moving object will snap
524 * with it neighboors. 0 means no snapping.
526 properties[PROP_SNAPPING_DISTANCE] =
527 g_param_spec_uint64 ("snapping-distance", "Snapping distance",
528 "Distance from which moving an object will snap with neighboors", 0,
529 G_MAXUINT64, 0, G_PARAM_READWRITE);
530 g_object_class_install_property (object_class, PROP_SNAPPING_DISTANCE,
531 properties[PROP_SNAPPING_DISTANCE]);
534 * GESTimeline::track-added:
535 * @timeline: the #GESTimeline
536 * @track: the #GESTrack that was added to the timeline
538 * Will be emitted after the track was added to the timeline.
540 ges_timeline_signals[TRACK_ADDED] =
541 g_signal_new ("track-added", G_TYPE_FROM_CLASS (klass),
542 G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, track_added), NULL,
543 NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_TRACK);
546 * GESTimeline::track-removed:
547 * @timeline: the #GESTimeline
548 * @track: the #GESTrack that was removed from the timeline
550 * Will be emitted after the track was removed from the timeline.
552 ges_timeline_signals[TRACK_REMOVED] =
553 g_signal_new ("track-removed", G_TYPE_FROM_CLASS (klass),
554 G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, track_removed),
555 NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_TRACK);
558 * GESTimeline::layer-added:
559 * @timeline: the #GESTimeline
560 * @layer: the #GESLayer that was added to the timeline
562 * Will be emitted after a new layer is added to the timeline.
564 ges_timeline_signals[LAYER_ADDED] =
565 g_signal_new ("layer-added", G_TYPE_FROM_CLASS (klass),
566 G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, layer_added), NULL,
567 NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_LAYER);
570 * GESTimeline::layer-removed:
571 * @timeline: the #GESTimeline
572 * @layer: the #GESLayer that was removed from the timeline
574 * Will be emitted after the layer was removed from the timeline.
576 ges_timeline_signals[LAYER_REMOVED] =
577 g_signal_new ("layer-removed", G_TYPE_FROM_CLASS (klass),
578 G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, layer_removed),
579 NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_LAYER);
582 * GESTimeline::group-added
583 * @timeline: the #GESTimeline
584 * @group: the #GESGroup
586 * Will be emitted after a new group is added to to the timeline.
588 ges_timeline_signals[GROUP_ADDED] =
589 g_signal_new ("group-added", G_TYPE_FROM_CLASS (klass),
590 G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, group_added), NULL,
591 NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_GROUP);
594 * GESTimeline::group-removed
595 * @timeline: the #GESTimeline
596 * @group: the #GESGroup
597 * @children: (element-type GES.Container) (transfer container): a list of #GESContainer
599 * Will be emitted after a group has been removed from the timeline.
601 ges_timeline_signals[GROUP_REMOVED] =
602 g_signal_new ("group-removed", G_TYPE_FROM_CLASS (klass),
603 G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, group_removed),
604 NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, GES_TYPE_GROUP,
608 * GESTimeline::snapping-started:
609 * @timeline: the #GESTimeline
610 * @obj1: the first #GESTrackElement that was snapping.
611 * @obj2: the second #GESTrackElement that was snapping.
612 * @position: the position where the two objects finally snapping.
614 * Will be emitted when the 2 #GESTrackElement first snapped
616 ges_timeline_signals[SNAPING_STARTED] =
617 g_signal_new ("snapping-started", G_TYPE_FROM_CLASS (klass),
618 G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
619 G_TYPE_NONE, 3, GES_TYPE_TRACK_ELEMENT, GES_TYPE_TRACK_ELEMENT,
623 * GESTimeline::snapping-ended:
624 * @timeline: the #GESTimeline
625 * @obj1: the first #GESTrackElement that was snapping.
626 * @obj2: the second #GESTrackElement that was snapping.
627 * @position: the position where the two objects finally snapping.
629 * Will be emitted when the 2 #GESTrackElement ended to snap
631 ges_timeline_signals[SNAPING_ENDED] =
632 g_signal_new ("snapping-ended", G_TYPE_FROM_CLASS (klass),
633 G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
634 G_TYPE_NONE, 3, GES_TYPE_TRACK_ELEMENT, GES_TYPE_TRACK_ELEMENT,
638 * GESTimeline::select-tracks-for-object:
639 * @timeline: the #GESTimeline
640 * @clip: The #GESClip on which @track_element will land
641 * @track_element: The #GESTrackElement for which to choose the tracks it should land into
643 * Returns: (transfer full) (element-type GESTrack): a #GPtrArray of #GESTrack-s where that object should be added
645 ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT] =
646 g_signal_new ("select-tracks-for-object", G_TYPE_FROM_CLASS (klass),
647 G_SIGNAL_RUN_LAST, 0, _gst_array_accumulator, NULL, NULL,
648 G_TYPE_PTR_ARRAY, 2, GES_TYPE_CLIP, GES_TYPE_TRACK_ELEMENT);
651 * GESTimeline::commited:
652 * @timeline: the #GESTimeline
654 * This signal will be emitted once the changes initiated by #ges_timeline_commit
655 * have been executed in the backend. Use #ges_timeline_commit_sync if you
656 * don't need to do anything in the meantime.
658 ges_timeline_signals[COMMITED] =
659 g_signal_new ("commited", G_TYPE_FROM_CLASS (klass),
660 G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
664 ges_timeline_init (GESTimeline * self)
666 GESTimelinePrivate *priv = self->priv;
668 self->priv = ges_timeline_get_instance_private (self);
673 self->priv->duration = 0;
674 self->priv->auto_transition = FALSE;
675 priv->snapping_distance = 0;
676 priv->expected_async_done = 0;
677 priv->expected_commited = 0;
679 /* Move context initialization */
680 init_movecontext (&self->priv->movecontext, TRUE);
681 priv->movecontext.ignore_needs_ctx = FALSE;
683 priv->priv_tracks = NULL;
684 priv->by_start = g_hash_table_new (g_direct_hash, g_direct_equal);
685 priv->by_end = g_hash_table_new (g_direct_hash, g_direct_equal);
686 priv->by_object = g_hash_table_new (g_direct_hash, g_direct_equal);
687 priv->by_layer = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
688 (GDestroyNotify) g_sequence_free);
689 priv->obj_iters = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
690 (GDestroyNotify) _destroy_obj_iters);
691 priv->starts_ends = g_sequence_new (g_free);
692 priv->tracksources = g_sequence_new (gst_object_unref);
694 priv->needs_transitions_update = TRUE;
697 g_hash_table_new_full (g_str_hash, g_str_equal, g_free, gst_object_unref);
701 g_signal_connect_after (self, "select-tracks-for-object",
702 G_CALLBACK (select_tracks_for_object_default), NULL);
704 g_rec_mutex_init (&priv->dyn_mutex);
705 g_mutex_init (&priv->commited_lock);
706 priv->valid_thread = g_thread_self ();
709 /* Private methods */
711 static inline GESContainer *
712 get_toplevel_container (gpointer element)
714 GESTimelineElement *ret =
715 ges_timeline_element_get_toplevel_parent ((GESTimelineElement
718 /* We own a ref to the elements ourself */
719 gst_object_unref (ret);
720 return (GESContainer *) ret;
725 sort_layers (gpointer a, gpointer b)
727 GESLayer *layer_a, *layer_b;
728 guint prio_a, prio_b;
730 layer_a = GES_LAYER (a);
731 layer_b = GES_LAYER (b);
733 prio_a = ges_layer_get_priority (layer_a);
734 prio_b = ges_layer_get_priority (layer_b);
736 if ((gint) prio_a > (guint) prio_b)
738 if ((guint) prio_a < (guint) prio_b)
745 _resync_layers (GESTimeline * timeline)
750 timeline->priv->resyncing_layers = TRUE;
751 for (tmp = timeline->layers; tmp; tmp = tmp->next) {
752 layer_set_priority (tmp->data, i, TRUE);
755 timeline->priv->resyncing_layers = FALSE;
759 timeline_update_duration (GESTimeline * timeline)
761 GstClockTime *cduration;
762 GSequenceIter *it = g_sequence_get_end_iter (timeline->priv->starts_ends);
764 it = g_sequence_iter_prev (it);
766 if (g_sequence_iter_is_end (it)) {
767 timeline->priv->duration = 0;
768 g_object_notify_by_pspec (G_OBJECT (timeline), properties[PROP_DURATION]);
772 cduration = g_sequence_get (it);
774 if (cduration && timeline->priv->duration != *cduration) {
775 GST_DEBUG ("track duration : %" GST_TIME_FORMAT " current : %"
776 GST_TIME_FORMAT, GST_TIME_ARGS (*cduration),
777 GST_TIME_ARGS (timeline->priv->duration));
779 timeline->priv->duration = *cduration;
781 g_object_notify_by_pspec (G_OBJECT (timeline), properties[PROP_DURATION]);
786 find_layer_by_prio (GESLayer * a, gpointer pprio)
788 gint prio = GPOINTER_TO_INT (pprio), lprio = ges_layer_get_priority (a);
798 sort_track_elements (GESTimeline * timeline, TrackObjIters * iters)
800 g_sequence_sort_changed (iters->iter_obj,
801 (GCompareDataFunc) element_start_compare, NULL);
805 compare_uint64 (guint64 * a, guint64 * b, gpointer user_data)
816 custom_find_track (TrackPrivate * tr_priv, GESTrack * track)
818 if (tr_priv->track == track)
824 sort_starts_ends_end (GESTimeline * timeline, TrackObjIters * iters)
826 GESTimelineElement *obj = GES_TIMELINE_ELEMENT (iters->trackelement);
827 GESTimelinePrivate *priv = timeline->priv;
828 guint64 *end = g_hash_table_lookup (priv->by_end, obj);
830 *end = _START (obj) + _DURATION (obj);
832 g_sequence_sort_changed (iters->iter_end, (GCompareDataFunc) compare_uint64,
834 timeline_update_duration (timeline);
838 sort_starts_ends_start (GESTimeline * timeline, TrackObjIters * iters)
840 GESTimelineElement *obj = GES_TIMELINE_ELEMENT (iters->trackelement);
841 GESTimelinePrivate *priv = timeline->priv;
842 guint64 *start = g_hash_table_lookup (priv->by_start, obj);
844 *start = _START (obj);
846 g_sequence_sort_changed (iters->iter_start,
847 (GCompareDataFunc) compare_uint64, NULL);
848 timeline_update_duration (timeline);
852 _destroy_auto_transition_cb (GESAutoTransition * auto_transition,
853 GESTimeline * timeline)
855 GESTimelinePrivate *priv = timeline->priv;
856 MoveContext *mv_ctx = &timeline->priv->movecontext;
857 GESClip *transition = auto_transition->transition_clip;
858 GESLayer *layer = ges_clip_get_layer (transition);
859 GESContainer *toplevel_prev =
860 get_toplevel_container (auto_transition->previous_clip), *toplevel_next =
861 get_toplevel_container (auto_transition->next_clip);
863 if (g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel_prev) &&
864 g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel_next)) {
865 GESLayer *nlayer = mv_ctx->moving_to_layer;
870 ges_clip_move_to_layer (transition, nlayer);
875 ges_layer_remove_clip (layer, transition);
876 g_signal_handlers_disconnect_by_func (auto_transition,
877 _destroy_auto_transition_cb, timeline);
880 priv->auto_transitions =
881 g_list_remove (priv->auto_transitions, auto_transition);
882 gst_object_unref (auto_transition);
885 static GESAutoTransition *
886 create_transition (GESTimeline * timeline, GESTrackElement * previous,
887 GESTrackElement * next, GESClip * transition,
888 GESLayer * layer, guint64 start, guint64 duration)
891 GESAutoTransition *auto_transition;
893 if (transition == NULL) {
894 /* TODO make it possible to specify a Transition asset in the API */
895 asset = ges_asset_request (GES_TYPE_TRANSITION_CLIP, "crossfade", NULL);
897 ges_layer_add_asset (layer, asset, start, 0, duration,
898 ges_track_element_get_track_type (next));
900 GST_DEBUG_OBJECT (timeline,
901 "Reusing already existing transition: %" GST_PTR_FORMAT, transition);
904 /* We know there is only 1 TrackElement */
906 ges_auto_transition_new (GES_CONTAINER_CHILDREN (transition)->data,
909 g_signal_connect (auto_transition, "destroy-me",
910 G_CALLBACK (_destroy_auto_transition_cb), timeline);
912 timeline->priv->auto_transitions =
913 g_list_prepend (timeline->priv->auto_transitions, auto_transition);
915 return auto_transition;
918 typedef GESAutoTransition *(*GetAutoTransitionFunc) (GESTimeline * timeline,
919 GESLayer * layer, GESTrack * track, GESTrackElement * previous,
920 GESTrackElement * next, GstClockTime transition_duration);
922 static GESAutoTransition *
923 _find_transition_from_auto_transitions (GESTimeline * timeline,
924 GESLayer * layer, GESTrack * track, GESTrackElement * prev,
925 GESTrackElement * next, GstClockTime transition_duration)
929 for (tmp = timeline->priv->auto_transitions; tmp; tmp = tmp->next) {
930 GESAutoTransition *auto_trans = (GESAutoTransition *) tmp->data;
932 /* We already have a transition linked to one of the elements we want to
933 * find a transition for */
934 if (auto_trans->previous_source == prev || auto_trans->next_source == next) {
935 if (auto_trans->previous_source != prev
936 || auto_trans->next_source != next) {
937 timeline->priv->needs_rollback = TRUE;
938 GST_INFO_OBJECT (timeline, "Failed creating auto transition, "
939 " trying to have 3 clips overlapping, rolling back");
949 static GESAutoTransition *
950 _create_auto_transition_from_transitions (GESTimeline * timeline,
951 GESLayer * layer, GESTrack * track, GESTrackElement * prev,
952 GESTrackElement * next, GstClockTime transition_duration)
954 GSequenceIter *tmp_iter;
955 GSequence *by_layer_sequence;
957 GESTimelinePrivate *priv = timeline->priv;
958 GESAutoTransition *auto_transition =
959 _find_transition_from_auto_transitions (timeline, layer, track, prev,
960 next, transition_duration);
963 if (auto_transition) {
964 if (timeline->priv->needs_rollback) {
965 GST_WARNING_OBJECT (timeline,
966 "Created an auto transition where we have 3 overlapping clips"
967 " removing it as this case is NOT allowed nor supported");
968 g_signal_emit_by_name (auto_transition, "destroy-me");
969 timeline->priv->needs_rollback = FALSE;
972 return auto_transition;
976 /* Try to find a transition that perfectly fits with the one that
977 * should be added at that place
978 * optimize: Use g_sequence_search instead of going over all the
980 by_layer_sequence = g_hash_table_lookup (priv->by_layer, layer);
981 for (tmp_iter = g_sequence_get_begin_iter (by_layer_sequence);
982 tmp_iter && !g_sequence_iter_is_end (tmp_iter);
983 tmp_iter = g_sequence_iter_next (tmp_iter)) {
984 GESTrackElement *maybe_transition = g_sequence_get (tmp_iter);
986 if (ges_track_element_get_track (maybe_transition) != track)
989 if (_START (maybe_transition) > _START (next))
991 else if (_START (maybe_transition) != _START (next) ||
992 _DURATION (maybe_transition) != transition_duration)
994 else if (GES_IS_TRANSITION (maybe_transition))
995 /* Use that transition */
996 /* TODO We should make sure that the transition contains only
997 * TrackElement-s in @track and if it is not the case properly unlink the
998 * object to use it */
999 return create_transition (timeline, prev, next,
1000 GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (maybe_transition)), layer,
1001 _START (next), transition_duration);
1007 /* Create all transition that do not exist on @layer.
1008 * @get_auto_transition is called to check if a particular transition exists.
1009 * If @track is specified, we will create the transitions only for that particular
1012 _create_transitions_on_layer (GESTimeline * timeline, GESLayer * layer,
1013 GESTrack * track, GESTrackElement * initiating_obj,
1014 GetAutoTransitionFunc get_auto_transition)
1017 GSequenceIter *iter;
1018 GESAutoTransition *transition;
1019 GESContainer *toplevel_next;
1020 MoveContext *mv_ctx = &timeline->priv->movecontext;
1021 GESTrack *ctrack = track;
1022 GList *entered = NULL; /* List of TrackElement for wich we walk through the
1023 * "start" but not the "end" in the starts_ends list */
1024 GESTimelinePrivate *priv = timeline->priv;
1026 if (!layer || !ges_layer_get_auto_transition (layer))
1029 layer_prio = ges_layer_get_priority (layer);
1030 for (iter = g_sequence_get_begin_iter (priv->starts_ends);
1031 iter && !g_sequence_iter_is_end (iter);
1032 iter = g_sequence_iter_next (iter)) {
1034 guint *start_or_end = g_sequence_get (iter);
1035 GESTrackElement *next = g_hash_table_lookup (timeline->priv->by_object,
1037 GESContainer *toplevel =
1038 get_toplevel_container (GES_TIMELINE_ELEMENT (next));
1040 /* Only object that are in that layer and track */
1041 if (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (next) != layer_prio ||
1042 (track && track != ges_track_element_get_track (next)))
1046 ctrack = ges_track_element_get_track (next);
1048 if (start_or_end == g_hash_table_lookup (priv->by_end, next)) {
1049 if (initiating_obj == next) {
1050 /* We passed the objects that initiated the research
1051 * we are now done */
1052 g_list_free (entered);
1055 entered = g_list_remove (entered, next);
1060 toplevel_next = get_toplevel_container (next);
1061 for (tmp = entered; tmp; tmp = tmp->next) {
1062 gint64 transition_duration;
1063 GESTrackElement *prev = tmp->data;
1064 GESContainer *toplevel_prev = get_toplevel_container (prev);
1066 /* If we are not in the same track, we do not create a transition */
1067 if (ctrack != ges_track_element_get_track (prev))
1070 /* If elements are in the same toplevel element, we do not create a transition */
1071 if (get_toplevel_container (GES_TIMELINE_ELEMENT (prev)) == toplevel)
1074 /* If the element is inside a container we are moving, we do not
1075 * create a transition */
1076 if (g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel_prev) &&
1077 g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel_next))
1080 transition_duration = (_START (prev) + _DURATION (prev)) - _START (next);
1081 if (transition_duration > 0 && transition_duration < _DURATION (prev) &&
1082 transition_duration < _DURATION (next)) {
1084 get_auto_transition (timeline, layer, ctrack, prev, next,
1085 transition_duration);
1087 create_transition (timeline, prev, next, NULL, layer,
1088 _START (next), transition_duration);
1092 /* And add that object to the entered list so that it we can possibly set
1093 * a transition on its end edge */
1094 entered = g_list_append (entered, next);
1098 /* @track_element must be a GESSource */
1100 timeline_create_transitions (GESTimeline * timeline,
1101 GESTrackElement * track_element)
1106 GESTimelinePrivate *priv = timeline->priv;
1107 MoveContext *mv_ctx = &timeline->priv->movecontext;
1109 if (!priv->needs_transitions_update)
1112 if (mv_ctx->moving_trackelements &&
1113 GES_TIMELINE_ELEMENT_START (track_element) > mv_ctx->start) {
1114 GST_DEBUG_OBJECT (timeline, "Not creating transition around %"
1115 GES_TIMELINE_ELEMENT_FORMAT " as it is not the first rippled"
1116 " element", GES_TIMELINE_ELEMENT_ARGS (track_element));
1120 track = ges_track_element_get_track (track_element);
1121 layer_node = g_list_find_custom (timeline->layers,
1122 GINT_TO_POINTER (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (track_element)),
1123 (GCompareFunc) find_layer_by_prio);
1125 _create_transitions_on_layer (timeline,
1126 layer_node ? layer_node->data : NULL, track, track_element,
1127 _find_transition_from_auto_transitions);
1129 GST_DEBUG_OBJECT (timeline, "Done updating transitions");
1132 /* Timeline edition functions */
1134 init_movecontext (MoveContext * mv_ctx, gboolean first_init)
1136 if (G_UNLIKELY (first_init))
1137 mv_ctx->toplevel_containers =
1138 g_hash_table_new (g_direct_hash, g_direct_equal);
1140 mv_ctx->moving_trackelements = NULL;
1141 mv_ctx->start = G_MAXUINT64;
1142 mv_ctx->max_trim_pos = G_MAXUINT64;
1143 mv_ctx->min_move_layer = G_MAXUINT;
1144 mv_ctx->max_layer_prio = 0;
1145 mv_ctx->last_snaped1 = NULL;
1146 mv_ctx->last_snaped2 = NULL;
1147 mv_ctx->last_snap_ts = NULL;
1148 mv_ctx->moving_to_layer = NULL;
1152 clean_movecontext (MoveContext * mv_ctx)
1154 g_list_free (mv_ctx->moving_trackelements);
1155 g_hash_table_remove_all (mv_ctx->toplevel_containers);
1156 init_movecontext (mv_ctx, FALSE);
1160 stop_tracking_track_element (GESTimeline * timeline,
1161 GESTrackElement * trackelement)
1163 guint64 *start, *end;
1164 TrackObjIters *iters;
1165 GESTimelinePrivate *priv = timeline->priv;
1167 iters = g_hash_table_lookup (priv->obj_iters, trackelement);
1168 if (G_LIKELY (iters->iter_by_layer)) {
1169 g_sequence_remove (iters->iter_by_layer);
1171 GST_WARNING_OBJECT (timeline, "TrackElement %p was in no layer",
1175 if (GES_IS_SOURCE (trackelement)) {
1176 start = g_hash_table_lookup (priv->by_start, trackelement);
1177 end = g_hash_table_lookup (priv->by_end, trackelement);
1179 g_hash_table_remove (priv->by_start, trackelement);
1180 g_hash_table_remove (priv->by_end, trackelement);
1181 g_hash_table_remove (priv->by_object, end);
1182 g_hash_table_remove (priv->by_object, start);
1183 g_sequence_remove (iters->iter_start);
1184 g_sequence_remove (iters->iter_end);
1185 g_sequence_remove (iters->iter_obj);
1186 timeline_update_duration (timeline);
1188 g_hash_table_remove (priv->obj_iters, trackelement);
1192 start_tracking_track_element (GESTimeline * timeline,
1193 GESTrackElement * trackelement)
1195 guint64 *pstart, *pend;
1196 GSequence *by_layer_sequence;
1197 TrackObjIters *iters;
1198 GESTimelinePrivate *priv = timeline->priv;
1200 guint layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (trackelement);
1201 GList *layer_node = g_list_find_custom (timeline->layers,
1202 GINT_TO_POINTER (layer_prio), (GCompareFunc) find_layer_by_prio);
1203 GESLayer *layer = layer_node ? layer_node->data : NULL;
1205 iters = g_slice_new0 (TrackObjIters);
1207 /* We add all TrackElement to obj_iters as we always follow them
1208 * in the by_layer Sequences */
1209 g_hash_table_insert (priv->obj_iters, trackelement, iters);
1211 /* Track all objects by layer */
1212 if (G_UNLIKELY (layer == NULL)) {
1213 /* We handle the case where we have TrackElement that are in no layer by not
1216 * FIXME: Check if we should rather try to track them in some sort of
1217 * dummy layer, or even refuse TrackElements to be added in Tracks if
1218 * they land in no layer the timeline controls.
1220 GST_ERROR_OBJECT (timeline, "Adding a TrackElement that lands in no layer "
1221 "we are controlling");
1223 by_layer_sequence = g_hash_table_lookup (priv->by_layer, layer);
1224 iters->iter_by_layer =
1225 g_sequence_insert_sorted (by_layer_sequence, trackelement,
1226 (GCompareDataFunc) element_start_compare, NULL);
1227 iters->layer = layer;
1230 if (GES_IS_SOURCE (trackelement)) {
1231 /* Track only sources for timeline edition and snapping */
1232 pstart = g_malloc (sizeof (guint64));
1233 pend = g_malloc (sizeof (guint64));
1234 *pstart = _START (trackelement);
1235 *pend = *pstart + _DURATION (trackelement);
1237 iters->iter_start = g_sequence_insert_sorted (priv->starts_ends, pstart,
1238 (GCompareDataFunc) compare_uint64, NULL);
1239 iters->iter_end = g_sequence_insert_sorted (priv->starts_ends, pend,
1240 (GCompareDataFunc) compare_uint64, NULL);
1242 g_sequence_insert_sorted (priv->tracksources,
1243 gst_object_ref (trackelement), (GCompareDataFunc) element_start_compare,
1245 iters->trackelement = trackelement;
1247 g_hash_table_insert (priv->by_start, trackelement, pstart);
1248 g_hash_table_insert (priv->by_object, pstart, trackelement);
1249 g_hash_table_insert (priv->by_end, trackelement, pend);
1250 g_hash_table_insert (priv->by_object, pend, trackelement);
1252 timeline->priv->movecontext.needs_move_ctx = TRUE;
1254 timeline_update_duration (timeline);
1255 timeline_create_transitions (timeline, trackelement);
1260 ges_timeline_emit_snappig (GESTimeline * timeline, GESTrackElement * obj1,
1263 GESTrackElement *obj2;
1264 MoveContext *mv_ctx = &timeline->priv->movecontext;
1265 GstClockTime snap_time = timecode ? *timecode : 0;
1266 GstClockTime last_snap_ts = mv_ctx->last_snap_ts ?
1267 *mv_ctx->last_snap_ts : GST_CLOCK_TIME_NONE;
1269 GST_DEBUG_OBJECT (timeline, "Distance: %" GST_TIME_FORMAT " snapping at %"
1270 GST_TIME_FORMAT, GST_TIME_ARGS (timeline->priv->snapping_distance),
1271 GST_TIME_ARGS (snap_time));
1273 if (timecode == NULL) {
1274 if (mv_ctx->last_snaped1 != NULL && mv_ctx->last_snaped2 != NULL) {
1275 g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0,
1276 mv_ctx->last_snaped1, mv_ctx->last_snaped2, last_snap_ts);
1278 /* We then need to recalculate the moving context */
1279 timeline->priv->movecontext.needs_move_ctx = TRUE;
1285 obj2 = g_hash_table_lookup (timeline->priv->by_object, timecode);
1287 if (last_snap_ts != *timecode) {
1288 g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0,
1289 mv_ctx->last_snaped1, mv_ctx->last_snaped2, (last_snap_ts));
1291 /* We want the snap start signal to be emited anyway */
1292 mv_ctx->last_snap_ts = NULL;
1295 if (mv_ctx->last_snap_ts == NULL) {
1297 mv_ctx->last_snaped1 = obj1;
1298 mv_ctx->last_snaped2 = obj2;
1299 mv_ctx->last_snap_ts = timecode;
1301 g_signal_emit (timeline, ges_timeline_signals[SNAPING_STARTED], 0,
1302 obj1, obj2, *timecode);
1307 static GstClockTime *
1308 ges_timeline_snap_position (GESTimeline * timeline,
1309 GESTrackElement * trackelement, GstClockTime * current,
1310 GstClockTime timecode, gboolean emit)
1312 GESTimelinePrivate *priv = timeline->priv;
1313 GSequenceIter *iter, *end_iter;
1314 GESContainer *container = get_toplevel_container (trackelement);
1315 GstClockTime *ret = NULL;
1316 GstClockTime smallest_offset = G_MAXUINT64;
1317 GstClockTime tmp_pos;
1319 tmp_pos = timecode - priv->snapping_distance;
1320 /* Rippling, not snapping with previous elements */
1321 if (priv->movecontext.moving_trackelements)
1323 iter = g_sequence_search (priv->starts_ends, &tmp_pos,
1324 (GCompareDataFunc) compare_uint64, NULL);
1326 tmp_pos = timecode + priv->snapping_distance;
1327 end_iter = g_sequence_search (priv->starts_ends, &tmp_pos,
1328 (GCompareDataFunc) compare_uint64, NULL);
1330 for (; iter != end_iter && !g_sequence_iter_is_end (iter);
1331 iter = g_sequence_iter_next (iter)) {
1332 GstClockTime *iter_tc = g_sequence_get (iter);
1333 GESTrackElement *tmp_trackelement =
1334 g_hash_table_lookup (priv->by_object, iter_tc);
1335 GESContainer *tmp_container = get_toplevel_container (tmp_trackelement);
1336 GstClockTimeDiff diff;
1338 if (tmp_container == container)
1341 if (g_hash_table_lookup (priv->movecontext.toplevel_containers,
1345 if (timecode > *iter_tc)
1346 diff = timecode - *iter_tc;
1348 diff = *iter_tc - timecode;
1350 if (diff > smallest_offset)
1353 smallest_offset = diff;
1357 /* We emit the snapping signal only if we snapped with a different value
1358 * than the current one */
1360 GstClockTime snap_time = ret ? *ret : GST_CLOCK_TIME_NONE;
1362 if (!timeline->priv->needs_rollback)
1363 ges_timeline_emit_snappig (timeline, trackelement, ret);
1365 ges_timeline_emit_snappig (timeline, trackelement, NULL);
1367 GST_DEBUG_OBJECT (timeline, "Snaping at %" GST_TIME_FORMAT,
1368 GST_TIME_ARGS (snap_time));
1374 static inline GESContainer *
1375 add_toplevel_container (MoveContext * mv_ctx, GESTrackElement * trackelement)
1378 GESContainer *toplevel = get_toplevel_container (trackelement);
1380 /* Avoid recalculating */
1381 if (!g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel)) {
1382 if (GES_IS_CLIP (toplevel)) {
1384 layer_prio = ges_clip_get_layer_priority (GES_CLIP (toplevel));
1385 if (layer_prio == (guint32) - 1) {
1386 GST_WARNING_OBJECT (toplevel, "Not in any layer, can not move"
1391 mv_ctx->min_move_layer = MIN (mv_ctx->min_move_layer, layer_prio);
1392 mv_ctx->max_layer_prio = MAX (mv_ctx->max_layer_prio, layer_prio);
1393 } else if GES_IS_GROUP
1395 mv_ctx->min_move_layer = MIN (mv_ctx->min_move_layer,
1396 _PRIORITY (toplevel));
1397 mv_ctx->max_layer_prio = MAX (mv_ctx->max_layer_prio,
1398 _PRIORITY (toplevel) + GES_CONTAINER_HEIGHT (toplevel));
1400 g_assert_not_reached ();
1402 mv_ctx->start = MIN (mv_ctx->start, GES_TIMELINE_ELEMENT_START (toplevel));
1403 g_hash_table_insert (mv_ctx->toplevel_containers, toplevel, toplevel);
1411 ges_move_context_set_objects (GESTimeline * timeline, GESTrackElement * obj,
1414 TrackObjIters *iters;
1415 GESTrackElement *tmptrackelement;
1416 guint64 start, tmpend, moving_point = _START (obj);
1417 GSequenceIter *iter, *trackelement_iter, *tmpiter;
1419 MoveContext *mv_ctx = &timeline->priv->movecontext;
1420 iters = g_hash_table_lookup (timeline->priv->obj_iters, obj);
1421 trackelement_iter = iters->iter_obj;
1423 case GES_EDGE_START:
1424 /* set it properly in the context of "trimming" */
1425 mv_ctx->max_trim_pos = 0;
1426 mv_ctx->min_trim_pos = 0;
1427 start = _START (obj);
1429 if (g_sequence_iter_is_begin (trackelement_iter))
1432 /* Look for the objects */
1433 for (iter = g_sequence_iter_prev (trackelement_iter);
1434 iter && !g_sequence_iter_is_end (iter);
1435 iter = g_sequence_iter_prev (iter)) {
1437 tmptrackelement = GES_TRACK_ELEMENT (g_sequence_get (iter));
1438 tmpend = _START (tmptrackelement) + _DURATION (tmptrackelement);
1440 if (tmpend <= start && GES_TIMELINE_ELEMENT_PARENT (tmptrackelement)
1441 != GES_TIMELINE_ELEMENT_PARENT (obj)) {
1442 mv_ctx->max_trim_pos =
1443 MAX (mv_ctx->max_trim_pos, _START (tmptrackelement));
1444 mv_ctx->min_trim_pos = MAX (mv_ctx->min_trim_pos,
1445 _START (tmptrackelement) - _INPOINT (tmptrackelement));
1446 mv_ctx->moving_trackelements =
1447 g_list_prepend (mv_ctx->moving_trackelements, tmptrackelement);
1451 if (g_sequence_iter_is_begin (iter))
1457 moving_point = _START (obj) + _DURATION (obj);
1459 case GES_EDGE_NONE: /* In this case only works for ripple */
1460 mv_ctx->max_trim_pos = G_MAXUINT64;
1462 iter = trackelement_iter;
1463 tmpiter = g_sequence_iter_prev (iter);
1465 /* Make sure to get the first TimelineElement starting at
1467 while (tmpiter && !g_sequence_iter_is_end (tmpiter)) {
1468 tmptrackelement = GES_TRACK_ELEMENT (g_sequence_get (iter));
1470 if (GES_TIMELINE_ELEMENT_START (tmptrackelement) != moving_point)
1474 tmpiter = g_sequence_iter_prev (tmpiter);
1476 if (g_sequence_iter_is_begin (tmpiter))
1480 /* Look for folowing objects */
1481 for (; iter && !g_sequence_iter_is_end (iter);
1482 iter = g_sequence_iter_next (iter)) {
1483 tmptrackelement = GES_TRACK_ELEMENT (g_sequence_get (iter));
1485 if (_START (tmptrackelement) >= moving_point &&
1486 GES_TIMELINE_ELEMENT_PARENT (tmptrackelement) !=
1487 GES_TIMELINE_ELEMENT_PARENT (obj)) {
1488 tmpend = _START (tmptrackelement) + _DURATION (tmptrackelement);
1489 mv_ctx->max_trim_pos = MIN (mv_ctx->max_trim_pos, tmpend);
1490 mv_ctx->moving_trackelements =
1491 g_list_prepend (mv_ctx->moving_trackelements, tmptrackelement);
1496 GST_DEBUG ("Edge type %d no supported", edge);
1504 ges_timeline_set_moving_context (GESTimeline * timeline, GESTrackElement * obj,
1505 GESEditMode mode, GESEdge edge, GList * layers)
1507 /* A TrackElement that could initiate movement for other object */
1508 GESTrackElement *editor_trackelement = NULL;
1509 MoveContext *mv_ctx = &timeline->priv->movecontext;
1510 GESClip *clip = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (obj));
1512 /* Still in the same mv_ctx */
1513 if ((mv_ctx->clip == clip && mv_ctx->mode == mode &&
1514 mv_ctx->edge == edge && !mv_ctx->needs_move_ctx)) {
1516 GST_DEBUG ("Keeping the same moving mv_ctx");
1521 GST_DEBUG_OBJECT (clip,
1522 "Changing context:\nold: obj: %p, mode: %d, edge: %d \n"
1523 "new: obj: %p, mode: %d, edge: %d ! Has changed %i", mv_ctx->clip,
1524 mv_ctx->mode, mv_ctx->edge, clip, mode, edge, mv_ctx->needs_move_ctx);
1526 /* Make sure snapping context is reset when changing the moving context */
1527 ges_timeline_emit_snappig (timeline, NULL, NULL);
1528 clean_movecontext (mv_ctx);
1529 mv_ctx->edge = edge;
1530 mv_ctx->mode = mode;
1531 mv_ctx->clip = clip;
1532 mv_ctx->needs_move_ctx = FALSE;
1534 /* We try to find a Source inside the Clip so we can set the
1535 * moving context Else we just move the selected one only */
1536 if (GES_IS_SOURCE (obj) == FALSE) {
1539 for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
1540 if (GES_IS_SOURCE (tmp->data)) {
1541 editor_trackelement = tmp->data;
1546 editor_trackelement = obj;
1549 if (editor_trackelement) {
1551 case GES_EDIT_MODE_RIPPLE:
1552 case GES_EDIT_MODE_ROLL:
1553 if (!(ges_move_context_set_objects (timeline, editor_trackelement,
1559 add_toplevel_container (&timeline->priv->movecontext, editor_trackelement);
1561 /* We add the main object to the toplevel_containers set */
1562 add_toplevel_container (&timeline->priv->movecontext, obj);
1570 _trim_transition (GESTimeline * timeline, GESLayer * layer,
1571 GESTrackElement * element, GESEdge edge, GstClockTime position)
1576 if (!ges_layer_get_auto_transition (layer))
1579 gst_object_unref (layer);
1580 for (tmp = timeline->priv->auto_transitions; tmp; tmp = tmp->next) {
1581 GESAutoTransition *auto_transition = tmp->data;
1583 if (auto_transition->transition == GES_TRACK_ELEMENT (element)) {
1584 /* Trimming an auto transition mean trimming its neighboors */
1585 if (!auto_transition->positioning) {
1586 if (edge == GES_EDGE_END) {
1587 ges_container_edit (GES_CONTAINER (auto_transition->previous_clip),
1588 NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, position);
1590 ges_container_edit (GES_CONTAINER (auto_transition->next_clip),
1591 NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, position);
1604 gst_object_unref (layer);
1609 ges_timeline_trim_object_simple (GESTimeline * timeline,
1610 GESTimelineElement * element, GList * layers, GESEdge edge,
1611 guint64 position, gboolean snapping)
1613 guint64 start, inpoint, duration, max_duration, *snapped, *cur;
1614 gboolean ret = TRUE;
1616 GESTrackElement *track_element;
1618 if (GES_IS_TRANSITION (element)) {
1619 return _trim_transition (timeline,
1620 ges_clip_get_layer (GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (element))),
1621 GES_TRACK_ELEMENT (element), edge, position);
1622 } else if (GES_IS_SOURCE (element) == FALSE) {
1626 track_element = GES_TRACK_ELEMENT (element);
1627 GST_DEBUG_OBJECT (track_element, "Trimming to %" GST_TIME_FORMAT
1628 " %s snaping, edge %i", GST_TIME_ARGS (position),
1629 snapping ? "Is" : "Not", edge);
1631 start = _START (track_element);
1632 g_object_get (track_element, "max-duration", &max_duration, NULL);
1635 case GES_EDGE_START:
1637 GESTimelineElement *toplevel;
1638 GESChildrenControlMode old_mode;
1639 gboolean use_inpoint;
1640 toplevel = ges_timeline_element_get_toplevel_parent (element);
1642 if (position < _START (toplevel) && _START (toplevel) < _START (element)) {
1643 GST_DEBUG_OBJECT (toplevel, "Not trimming %p as not at begining "
1644 "of the container", element);
1646 gst_object_unref (toplevel);
1650 old_mode = GES_CONTAINER (toplevel)->children_control_mode;
1651 if (GES_IS_GROUP (toplevel) && old_mode == GES_CHILDREN_UPDATE) {
1652 GST_DEBUG_OBJECT (toplevel, "Setting children udpate mode to"
1653 " UPDDATE_ALL_VALUES so we can trim without moving the contained");
1654 /* The container will update its values itself according to new
1655 * values of the children */
1656 GES_CONTAINER (toplevel)->children_control_mode =
1657 GES_CHILDREN_UPDATE_ALL_VALUES;
1660 inpoint = _INPOINT (track_element);
1661 duration = _DURATION (track_element);
1664 cur = g_hash_table_lookup (timeline->priv->by_start, track_element);
1666 snapped = ges_timeline_snap_position (timeline, track_element, cur,
1669 position = *snapped;
1672 /* Calculate new values */
1673 position = MIN (position, start + duration);
1676 GES_TIMELINE_ELEMENT_GET_CLASS (track_element)->set_inpoint ? TRUE :
1679 if (use_inpoint && inpoint + position < start) {
1680 GST_ERROR_OBJECT (timeline, "Track element %s inpoint %" GST_TIME_FORMAT
1681 " would be negative,"
1682 " not trimming", GES_TIMELINE_ELEMENT_NAME (track_element),
1683 GST_TIME_ARGS (inpoint));
1684 gst_object_unref (toplevel);
1688 inpoint = inpoint + position - start;
1689 real_dur = _END (element) - position;
1691 duration = CLAMP (real_dur, 0, max_duration > inpoint ?
1692 max_duration - inpoint : G_MAXUINT64);
1694 duration = real_dur;
1697 /* If we already are at max duration or duration == 0 do no useless work */
1698 if ((duration == _DURATION (track_element) &&
1699 _DURATION (track_element) == _MAXDURATION (track_element)) ||
1700 (duration == 0 && _DURATION (element) == 0)) {
1701 GST_DEBUG_OBJECT (track_element,
1702 "Duration already == max_duration, no triming");
1703 gst_object_unref (toplevel);
1707 timeline->priv->needs_transitions_update = FALSE;
1708 _set_start0 (GES_TIMELINE_ELEMENT (track_element), position);
1709 _set_inpoint0 (GES_TIMELINE_ELEMENT (track_element), inpoint);
1710 timeline->priv->needs_transitions_update = TRUE;
1712 _set_duration0 (GES_TIMELINE_ELEMENT (track_element), duration);
1713 if (GES_IS_GROUP (toplevel))
1714 GES_CONTAINER (toplevel)->children_control_mode = old_mode;
1716 gst_object_unref (toplevel);
1721 cur = g_hash_table_lookup (timeline->priv->by_end, track_element);
1722 snapped = ges_timeline_snap_position (timeline, track_element, cur,
1725 position = *snapped;
1727 /* Calculate new values */
1728 real_dur = position - start;
1729 duration = MAX (0, real_dur);
1730 duration = MIN (duration, max_duration - _INPOINT (track_element));
1732 if (duration == 0) {
1733 GST_INFO_OBJECT (timeline, "Duration would be 0, not rippling");
1737 /* Not moving, avoid overhead */
1738 if (duration == _DURATION (track_element)) {
1739 GST_DEBUG_OBJECT (track_element, "No change in duration");
1743 _set_duration0 (GES_TIMELINE_ELEMENT (track_element), duration);
1747 GST_WARNING ("Can not trim with %i GESEdge", edge);
1755 timeline_ripple_object (GESTimeline * timeline, GESTrackElement * obj,
1756 GList * layers, GESEdge edge, guint64 position)
1758 GList *tmp, *moved_clips = NULL;
1759 GESTrackElement *trackelement;
1760 GESContainer *container;
1761 guint64 duration, new_start, *snapped, *cur;
1764 MoveContext *mv_ctx = &timeline->priv->movecontext;
1766 mv_ctx->ignore_needs_ctx = TRUE;
1767 timeline->priv->needs_rollback = FALSE;
1768 if (!ges_timeline_set_moving_context (timeline, obj, GES_EDIT_MODE_RIPPLE,
1774 GST_DEBUG ("Simply rippling");
1776 /* We should be smart here to avoid recalculate transitions when possible */
1777 cur = g_hash_table_lookup (timeline->priv->by_end, obj);
1778 snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE);
1780 position = *snapped;
1782 offset = position - _START (obj);
1784 for (tmp = mv_ctx->moving_trackelements; tmp; tmp = tmp->next) {
1785 trackelement = GES_TRACK_ELEMENT (tmp->data);
1786 new_start = _START (trackelement) + offset;
1788 container = add_toplevel_container (mv_ctx, trackelement);
1789 /* Make sure not to move 2 times the same Clip */
1790 if (g_list_find (moved_clips, container) == NULL) {
1791 _set_start0 (GES_TIMELINE_ELEMENT (trackelement), new_start);
1792 moved_clips = g_list_prepend (moved_clips, container);
1796 g_list_free (moved_clips);
1797 _set_start0 (GES_TIMELINE_ELEMENT (obj), position);
1800 if (timeline->priv->needs_rollback && !timeline->priv->rolling_back) {
1801 timeline->priv->rolling_back = TRUE;
1802 for (tmp = mv_ctx->moving_trackelements; tmp; tmp = tmp->next) {
1803 trackelement = GES_TRACK_ELEMENT (tmp->data);
1804 new_start = _START (trackelement) - offset;
1806 container = add_toplevel_container (mv_ctx, trackelement);
1807 /* Make sure not to move 2 times the same Clip */
1808 if (g_list_find (moved_clips, container) == NULL) {
1809 _set_start0 (GES_TIMELINE_ELEMENT (trackelement), new_start);
1810 moved_clips = g_list_prepend (moved_clips, container);
1814 g_list_free (moved_clips);
1816 _set_start0 (GES_TIMELINE_ELEMENT (obj), position - offset);
1818 ges_timeline_emit_snappig (timeline, obj, NULL);
1819 mv_ctx->needs_move_ctx = TRUE;
1820 timeline->priv->rolling_back = FALSE;
1827 timeline->priv->needs_transitions_update = FALSE;
1828 GST_DEBUG ("Rippling end");
1830 cur = g_hash_table_lookup (timeline->priv->by_end, obj);
1831 snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE);
1833 position = *snapped;
1835 duration = _DURATION (obj);
1837 if (!ges_timeline_trim_object_simple (timeline,
1838 GES_TIMELINE_ELEMENT (obj), NULL, GES_EDGE_END, position,
1843 offset = _DURATION (obj) - duration;
1844 for (tmp = mv_ctx->moving_trackelements; tmp; tmp = tmp->next) {
1845 trackelement = GES_TRACK_ELEMENT (tmp->data);
1846 new_start = _START (trackelement) + offset;
1848 container = add_toplevel_container (mv_ctx, trackelement);
1849 if (GES_IS_GROUP (container))
1850 container->children_control_mode = GES_CHILDREN_UPDATE_OFFSETS;
1851 /* Make sure not to move 2 times the same Clip */
1852 if (g_list_find (moved_clips, container) == NULL) {
1853 _set_start0 (GES_TIMELINE_ELEMENT (trackelement), new_start);
1854 moved_clips = g_list_prepend (moved_clips, container);
1856 if (GES_IS_GROUP (container))
1857 container->children_control_mode = GES_CHILDREN_UPDATE;
1860 g_list_free (moved_clips);
1861 timeline->priv->needs_transitions_update = TRUE;
1862 GST_DEBUG ("Done Rippling end");
1864 case GES_EDGE_START:
1865 GST_INFO ("Ripple start doesn't make sense, trimming instead");
1866 timeline->priv->movecontext.needs_move_ctx = TRUE;
1867 timeline_trim_object (timeline, obj, layers, edge, position);
1870 GST_DEBUG ("Can not ripple edge: %i", edge);
1875 mv_ctx->ignore_needs_ctx = FALSE;
1880 mv_ctx->ignore_needs_ctx = FALSE;
1886 timeline_slide_object (GESTimeline * timeline, GESTrackElement * obj,
1887 GList * layers, GESEdge edge, guint64 position)
1890 /* FIXME implement me! */
1891 GST_FIXME_OBJECT (timeline, "Slide mode editing not implemented yet");
1897 timeline_trim_object (GESTimeline * timeline, GESTrackElement * object,
1898 GList * layers, GESEdge edge, guint64 position)
1900 gboolean ret = FALSE;
1902 MoveContext *mv_ctx = &timeline->priv->movecontext;
1904 mv_ctx->ignore_needs_ctx = TRUE;
1906 timeline->priv->needs_rollback = FALSE;
1907 if (!ges_timeline_set_moving_context (timeline, object, GES_EDIT_MODE_TRIM,
1912 case GES_EDGE_START:
1913 cpos = GES_TIMELINE_ELEMENT_START (object);
1916 cpos = GES_TIMELINE_ELEMENT_END (object);
1921 ret = ges_timeline_trim_object_simple (timeline,
1922 GES_TIMELINE_ELEMENT (object), layers, edge, position, TRUE);
1924 if (timeline->priv->needs_rollback && !timeline->priv->rolling_back) {
1925 timeline->priv->rolling_back = TRUE;
1927 timeline_trim_object (timeline, object, layers, edge, cpos);
1928 ges_timeline_emit_snappig (timeline, object, NULL);
1929 timeline->priv->rolling_back = FALSE;
1933 mv_ctx->ignore_needs_ctx = FALSE;
1939 timeline_roll_object (GESTimeline * timeline, GESTrackElement * obj,
1940 GList * layers, GESEdge edge, guint64 position)
1942 MoveContext *mv_ctx = &timeline->priv->movecontext;
1943 guint64 start, duration, end, tmpstart, tmpduration, tmpend, *snapped, *cur;
1944 gboolean ret = TRUE;
1947 mv_ctx->ignore_needs_ctx = TRUE;
1949 GST_DEBUG_OBJECT (obj, "Rolling object to %" GST_TIME_FORMAT,
1950 GST_TIME_ARGS (position));
1952 if (!ges_timeline_set_moving_context (timeline, obj, GES_EDIT_MODE_ROLL,
1956 start = _START (obj);
1957 duration = _DURATION (obj);
1958 end = start + duration;
1960 timeline->priv->needs_transitions_update = FALSE;
1962 case GES_EDGE_START:
1964 /* Avoid negative durations */
1965 if (position < mv_ctx->max_trim_pos || position > end ||
1966 position < mv_ctx->min_trim_pos)
1969 cur = g_hash_table_lookup (timeline->priv->by_start, obj);
1970 snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE);
1972 position = *snapped;
1974 ret &= ges_timeline_trim_object_simple (timeline,
1975 GES_TIMELINE_ELEMENT (obj), layers, GES_EDGE_START, position, FALSE);
1978 GST_INFO_OBJECT (timeline, "Could not trim %s",
1979 GES_TIMELINE_ELEMENT_NAME (obj));
1985 /* In the case we reached max_duration we just make sure to roll
1986 * everything to the real new position */
1987 position = _START (obj);
1989 /* Send back changes to the neighbourhood */
1990 for (tmp = mv_ctx->moving_trackelements; tmp; tmp = tmp->next) {
1991 GESTimelineElement *tmpelement = GES_TIMELINE_ELEMENT (tmp->data);
1993 tmpstart = _START (tmpelement);
1994 tmpduration = _DURATION (tmpelement);
1995 tmpend = tmpstart + tmpduration;
1997 /* Check that the object should be resized at this position
1998 * even if an error accurs, we keep doing our job */
1999 if (tmpend == start) {
2000 ret &= ges_timeline_trim_object_simple (timeline, tmpelement, NULL,
2001 GES_EDGE_END, position, FALSE);
2008 /* Avoid negative durations */
2009 if (position > mv_ctx->max_trim_pos || position < start)
2012 end = _START (obj) + _DURATION (obj);
2014 cur = g_hash_table_lookup (timeline->priv->by_end, obj);
2015 snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE);
2017 position = *snapped;
2019 ret &= ges_timeline_trim_object_simple (timeline,
2020 GES_TIMELINE_ELEMENT (obj), NULL, GES_EDGE_END, position, FALSE);
2023 GST_DEBUG_OBJECT (timeline, "No triming, bailing out");
2027 /* In the case we reached max_duration we just make sure to roll
2028 * everything to the real new position */
2029 position = _START (obj) + _DURATION (obj);
2031 /* Send back changes to the neighbourhood */
2032 for (tmp = mv_ctx->moving_trackelements; tmp; tmp = tmp->next) {
2033 GESTimelineElement *tmpelement = GES_TIMELINE_ELEMENT (tmp->data);
2035 tmpstart = _START (tmpelement);
2036 tmpduration = _DURATION (tmpelement);
2037 tmpend = tmpstart + tmpduration;
2039 /* Check that the object should be resized at this position
2040 * even if an error accure, we keep doing our job */
2041 if (end == tmpstart) {
2042 ret &= ges_timeline_trim_object_simple (timeline, tmpelement, NULL,
2043 GES_EDGE_START, position, FALSE);
2048 GST_DEBUG ("Edge type %i not handled here", edge);
2053 timeline->priv->needs_transitions_update = TRUE;
2054 mv_ctx->ignore_needs_ctx = FALSE;
2059 GST_DEBUG_OBJECT (obj, "Could not roll edge %d to %" GST_TIME_FORMAT,
2060 edge, GST_TIME_ARGS (position));
2067 timeline_move_object (GESTimeline * timeline, GESTrackElement * object,
2068 GList * layers, GESEdge edge, guint64 position)
2070 if (!ges_timeline_set_moving_context (timeline, object, GES_EDIT_MODE_NORMAL,
2072 GST_DEBUG_OBJECT (object, "Could not move to %" GST_TIME_FORMAT,
2073 GST_TIME_ARGS (position));
2078 return ges_timeline_move_object_simple (timeline,
2079 GES_TIMELINE_ELEMENT (object), layers, edge, position);
2083 ges_timeline_move_object_simple (GESTimeline * timeline,
2084 GESTimelineElement * element, GList * layers, GESEdge edge,
2087 GstClockTime cpos = GES_TIMELINE_ELEMENT_START (element);
2088 guint64 *snap_end, *snap_st, *cur, position_offset, off1, off2, top_end;
2089 GESTrackElement *track_element;
2090 GESContainer *toplevel;
2092 /* We only work with GESSource-s and we check that we are not already moving
2093 * the specified element ourself */
2094 if (GES_IS_SOURCE (element) == FALSE ||
2095 g_list_find (timeline->priv->movecontext.moving_trackelements, element))
2098 timeline->priv->needs_rollback = FALSE;
2099 track_element = GES_TRACK_ELEMENT (element);
2100 toplevel = get_toplevel_container (track_element);
2101 position_offset = position - _START (track_element);
2103 top_end = _START (toplevel) + _DURATION (toplevel) + position_offset;
2104 cur = g_hash_table_lookup (timeline->priv->by_end, track_element);
2106 GST_DEBUG_OBJECT (timeline, "Moving %" GST_PTR_FORMAT " to %"
2107 GST_TIME_FORMAT " (end %" GST_TIME_FORMAT ")", element,
2108 GST_TIME_ARGS (position), GST_TIME_ARGS (top_end));
2110 snap_end = ges_timeline_snap_position (timeline, track_element, cur, top_end,
2113 off1 = top_end > *snap_end ? top_end - *snap_end : *snap_end - top_end;
2117 cur = g_hash_table_lookup (timeline->priv->by_start, track_element);
2119 ges_timeline_snap_position (timeline, track_element, cur, position,
2122 off2 = position > *snap_st ? position - *snap_st : *snap_st - position;
2126 /* In the case we could snap on both sides, we snap on the end */
2127 if (snap_end && off1 <= off2) {
2128 position = position + *snap_end - top_end;
2129 ges_timeline_emit_snappig (timeline, track_element, snap_end);
2130 } else if (snap_st) {
2131 position = position + *snap_st - position;
2132 ges_timeline_emit_snappig (timeline, track_element, snap_st);
2134 ges_timeline_emit_snappig (timeline, track_element, NULL);
2135 timeline->priv->needs_rollback = FALSE;
2137 _set_start0 (GES_TIMELINE_ELEMENT (track_element), position);
2139 if (timeline->priv->needs_rollback && !timeline->priv->rolling_back) {
2140 timeline->priv->needs_rollback = FALSE;
2141 timeline->priv->rolling_back = TRUE;
2142 ges_timeline_move_object_simple (timeline, element, layers, edge, cpos);
2143 ges_timeline_emit_snappig (timeline, track_element, NULL);
2144 timeline->priv->rolling_back = FALSE;
2153 timeline_context_to_layer (GESTimeline * timeline, gint offset)
2155 gboolean ret = TRUE;
2156 GHashTableIter iter;
2157 GESContainer *key, *value;
2158 GESLayer *new_layer;
2160 MoveContext *mv_ctx = &timeline->priv->movecontext;
2162 /* Layer's priority is always positive */
2166 if (offset < 0 && mv_ctx->min_move_layer < -offset)
2169 GST_DEBUG ("Moving %d object, offset %d",
2170 g_hash_table_size (mv_ctx->toplevel_containers), offset);
2172 mv_ctx->ignore_needs_ctx = TRUE;
2173 timeline->priv->needs_rollback = FALSE;
2174 g_hash_table_iter_init (&iter, mv_ctx->toplevel_containers);
2175 while (g_hash_table_iter_next (&iter, (gpointer *) & key,
2176 (gpointer *) & value)) {
2178 if (GES_IS_CLIP (value)) {
2179 prio = ges_clip_get_layer_priority (GES_CLIP (value));
2181 /* We know that the layer exists as we created it */
2182 new_layer = GES_LAYER (g_list_nth_data (timeline->layers, prio + offset));
2184 if (new_layer == NULL) {
2186 new_layer = ges_timeline_append_layer (timeline);
2187 } while (ges_layer_get_priority (new_layer) < prio + offset);
2190 mv_ctx->moving_to_layer = new_layer;
2191 ret &= ges_clip_move_to_layer (GES_CLIP (key), new_layer);
2192 } else if (GES_IS_GROUP (value)) {
2193 guint32 last_prio = _PRIORITY (value) + offset +
2194 GES_CONTAINER_HEIGHT (value) - 1;
2196 new_layer = GES_LAYER (g_list_nth_data (timeline->layers, last_prio));
2198 if (new_layer == NULL) {
2200 new_layer = ges_timeline_append_layer (timeline);
2201 } while (ges_layer_get_priority (new_layer) < last_prio);
2204 mv_ctx->moving_to_layer = NULL;
2205 _set_priority0 (GES_TIMELINE_ELEMENT (value), _PRIORITY (value) + offset);
2209 /* Readjust min_move_layer */
2210 mv_ctx->min_move_layer = mv_ctx->min_move_layer + offset;
2211 mv_ctx->ignore_needs_ctx = FALSE;
2213 if (timeline->priv->needs_rollback && !timeline->priv->rolling_back) {
2215 timeline->priv->rolling_back = TRUE;
2216 timeline_context_to_layer (timeline, -offset);
2217 timeline->priv->rolling_back = FALSE;
2219 mv_ctx->moving_to_layer = NULL;
2225 timeline_add_group (GESTimeline * timeline, GESGroup * group)
2227 GST_DEBUG_OBJECT (timeline, "Adding group %" GST_PTR_FORMAT, group);
2229 timeline->priv->movecontext.needs_move_ctx = TRUE;
2230 timeline->priv->groups = g_list_prepend (timeline->priv->groups,
2231 gst_object_ref_sink (group));
2233 ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (group), timeline);
2237 * timeline_emit_group_added:
2238 * @timeline: a #GESTimeline
2239 * @group: group that was added
2241 * Emit group-added signal.
2244 timeline_emit_group_added (GESTimeline * timeline, GESGroup * group)
2246 g_signal_emit (timeline, ges_timeline_signals[GROUP_ADDED], 0, group);
2250 * timeline_emit_group_removed:
2251 * @timeline: a #GESTimeline
2252 * @group: group that was removed
2253 * @array: (element-type GESTimelineElement): children that were removed
2255 * Emit group-removed signal.
2258 timeline_emit_group_removed (GESTimeline * timeline, GESGroup * group,
2261 g_signal_emit (timeline, ges_timeline_signals[GROUP_REMOVED], 0, group,
2266 timeline_remove_group (GESTimeline * timeline, GESGroup * group)
2268 GST_DEBUG_OBJECT (timeline, "Removing group %" GST_PTR_FORMAT, group);
2270 timeline->priv->groups = g_list_remove (timeline->priv->groups, group);
2272 timeline->priv->movecontext.needs_move_ctx = TRUE;
2273 ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (group), NULL);
2274 gst_object_unref (group);
2278 select_tracks_for_object_default (GESTimeline * timeline,
2279 GESClip * clip, GESTrackElement * tr_object, gpointer user_data)
2284 result = g_ptr_array_new ();
2286 for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
2287 GESTrack *track = GES_TRACK (tmp->data);
2289 if ((track->type & ges_track_element_get_track_type (tr_object))) {
2290 gst_object_ref (track);
2291 g_ptr_array_add (result, track);
2299 add_object_to_tracks (GESTimeline * timeline, GESClip * clip, GESTrack * track)
2303 GESTrackType types, visited_type = GES_TRACK_TYPE_UNKNOWN;
2305 GST_DEBUG_OBJECT (timeline, "Creating %" GST_PTR_FORMAT
2306 " trackelements and adding them to our tracks", clip);
2308 types = ges_clip_get_supported_formats (clip);
2310 if ((types & track->type) == 0)
2312 types = track->type;
2315 for (i = 0, tmp = timeline->tracks; tmp; tmp = tmp->next, i++) {
2316 GESTrack *track = GES_TRACK (tmp->data);
2318 if (((track->type & types) == 0 || (track->type & visited_type)))
2321 list = ges_clip_create_track_elements (clip, track->type);
2327 layer_auto_transition_changed_cb (GESLayer * layer,
2328 GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
2332 timeline->priv->needs_rollback = FALSE;
2333 _create_transitions_on_layer (timeline, layer, NULL, NULL,
2334 _create_auto_transition_from_transitions);
2336 clips = ges_layer_get_clips (layer);
2337 for (tmp = clips; tmp; tmp = tmp->next) {
2338 if (GES_IS_TRANSITION_CLIP (tmp->data)) {
2339 GList *tmpautotrans;
2340 gboolean found = FALSE;
2342 for (tmpautotrans = timeline->priv->auto_transitions; tmpautotrans;
2343 tmpautotrans = tmpautotrans->next) {
2344 if (GES_AUTO_TRANSITION (tmpautotrans->data)->transition_clip ==
2352 GST_ERROR_OBJECT (timeline,
2353 "Transition %s could not be wrapped into an auto transition"
2354 " REMOVING it", GES_TIMELINE_ELEMENT_NAME (tmp->data));
2356 ges_layer_remove_clip (layer, tmp->data);
2360 g_list_free_full (clips, gst_object_unref);
2364 clip_track_element_added_cb (GESClip * clip,
2365 GESTrackElement * track_element, GESTimeline * timeline)
2370 GPtrArray *tracks = NULL;
2371 GESTrackElement *existing_src = NULL;
2373 if (timeline->priv->ignore_track_element_added == clip) {
2374 GST_DEBUG_OBJECT (timeline, "Ignoring element added (%" GST_PTR_FORMAT
2375 " in %" GST_PTR_FORMAT, track_element, clip);
2380 if (ges_track_element_get_track (track_element)) {
2381 GST_WARNING_OBJECT (track_element, "Already in a track");
2386 g_signal_emit (G_OBJECT (timeline),
2387 ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT], 0, clip, track_element,
2390 if (!tracks || tracks->len == 0) {
2391 GST_WARNING_OBJECT (timeline, "Got no Track to add %p (type %s), removing"
2392 " from clip (stopping 'child-added' signal emission).",
2393 track_element, ges_track_type_name (ges_track_element_get_track_type
2397 g_ptr_array_unref (tracks);
2399 g_signal_stop_emission_by_name (clip, "child-added");
2400 ges_container_remove (GES_CONTAINER (clip),
2401 GES_TIMELINE_ELEMENT (track_element));
2406 /* We add the current element to the first track */
2407 track = g_ptr_array_index (tracks, 0);
2409 is_source = g_type_is_a (G_OBJECT_TYPE (track_element), GES_TYPE_SOURCE);
2411 existing_src = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE);
2413 if (existing_src == NULL) {
2414 if (!ges_track_add_element (track, track_element)) {
2415 GST_WARNING_OBJECT (clip, "Failed to add track element to track");
2416 ges_container_remove (GES_CONTAINER (clip),
2417 GES_TIMELINE_ELEMENT (track_element));
2418 g_ptr_array_unref (tracks);
2422 GST_INFO_OBJECT (clip, "Already had a Source Element in %" GST_PTR_FORMAT
2423 " of type %s, removing new one. (stopping 'child-added' emission)",
2424 track, G_OBJECT_TYPE_NAME (track_element));
2425 g_signal_stop_emission_by_name (clip, "child-added");
2426 ges_container_remove (GES_CONTAINER (clip),
2427 GES_TIMELINE_ELEMENT (track_element));
2429 gst_object_unref (track);
2430 g_clear_object (&existing_src);
2432 /* And create copies to add to other tracks */
2433 timeline->priv->ignore_track_element_added = clip;
2434 for (i = 1; i < tracks->len; i++) {
2436 GESTrackElement *track_element_copy;
2438 track = g_ptr_array_index (tracks, i);
2440 existing_src = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE);
2441 if (existing_src == NULL) {
2442 ges_container_remove (GES_CONTAINER (clip),
2443 GES_TIMELINE_ELEMENT (track_element));
2444 gst_object_unref (track);
2445 g_ptr_array_unref (tracks);
2448 GST_INFO_OBJECT (clip, "Already had a Source Element in %" GST_PTR_FORMAT
2449 " of type %s, removing new one. (stopping 'child-added' emission)",
2450 track, G_OBJECT_TYPE_NAME (track_element));
2451 g_signal_stop_emission_by_name (clip, "child-added");
2452 ges_container_remove (GES_CONTAINER (clip),
2453 GES_TIMELINE_ELEMENT (track_element));
2455 g_clear_object (&existing_src);
2457 track_element_copy =
2458 GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT
2459 (track_element), TRUE));
2461 GST_LOG_OBJECT (timeline, "Trying to add %p to track %p",
2462 track_element_copy, track);
2464 if (!ges_container_add (GES_CONTAINER (clip),
2465 GES_TIMELINE_ELEMENT (track_element_copy))) {
2466 GST_WARNING_OBJECT (clip, "Failed to add track element to clip");
2467 gst_object_unref (track_element_copy);
2468 g_ptr_array_unref (tracks);
2472 if (!ges_track_add_element (track, track_element_copy)) {
2473 GST_WARNING_OBJECT (clip, "Failed to add track element to track");
2474 ges_container_remove (GES_CONTAINER (clip),
2475 GES_TIMELINE_ELEMENT (track_element_copy));
2476 gst_object_unref (track_element_copy);
2477 g_ptr_array_unref (tracks);
2481 gst_object_unref (track);
2483 timeline->priv->ignore_track_element_added = NULL;
2484 g_ptr_array_unref (tracks);
2488 clip_track_element_removed_cb (GESClip * clip,
2489 GESTrackElement * track_element, GESTimeline * timeline)
2491 GESTrack *track = ges_track_element_get_track (track_element);
2494 ges_track_remove_element (track, track_element);
2498 layer_object_added_cb (GESLayer * layer, GESClip * clip, GESTimeline * timeline)
2500 GESProject *project;
2502 /* We make sure not to be connected twice */
2503 g_signal_handlers_disconnect_by_func (clip, clip_track_element_added_cb,
2505 g_signal_handlers_disconnect_by_func (clip, clip_track_element_removed_cb,
2508 /* And we connect to the object */
2509 g_signal_connect (clip, "child-added",
2510 G_CALLBACK (clip_track_element_added_cb), timeline);
2511 g_signal_connect (clip, "child-removed",
2512 G_CALLBACK (clip_track_element_removed_cb), timeline);
2514 if (ges_clip_is_moving_from_layer (clip)) {
2515 GST_DEBUG ("Clip %p moving from one layer to another, not creating "
2516 "TrackElement", clip);
2517 timeline->priv->movecontext.needs_move_ctx = TRUE;
2518 _create_transitions_on_layer (timeline, layer, NULL, NULL,
2519 _find_transition_from_auto_transitions);
2524 add_object_to_tracks (timeline, clip, NULL);
2526 GST_DEBUG ("Making sure that the asset is in our project");
2528 GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline)));
2529 ges_project_add_asset (project,
2530 ges_extractable_get_asset (GES_EXTRACTABLE (clip)));
2536 layer_priority_changed_cb (GESLayer * layer,
2537 GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
2539 if (timeline->priv->resyncing_layers)
2542 timeline->layers = g_list_sort (timeline->layers, (GCompareFunc)
2547 layer_object_removed_cb (GESLayer * layer, GESClip * clip,
2548 GESTimeline * timeline)
2550 GList *trackelements, *tmp;
2552 if (ges_clip_is_moving_from_layer (clip)) {
2553 GST_DEBUG ("Clip %p is moving from a layer to another, not doing"
2554 " anything on it", clip);
2558 GST_DEBUG ("Clip %p removed from layer %p", clip, layer);
2560 /* Go over the clip's track element and figure out which one belongs to
2561 * the list of tracks we control */
2563 trackelements = ges_container_get_children (GES_CONTAINER (clip), FALSE);
2564 for (tmp = trackelements; tmp; tmp = tmp->next) {
2565 GESTrackElement *track_element = (GESTrackElement *) tmp->data;
2566 GESTrack *track = ges_track_element_get_track (track_element);
2571 GST_DEBUG_OBJECT (timeline, "Trying to remove TrackElement %p",
2574 /* FIXME Check if we should actually check that we control the
2575 * track in the new management of TrackElement context */
2576 LOCK_DYN (timeline);
2577 if (G_LIKELY (g_list_find_custom (timeline->priv->priv_tracks, track,
2578 (GCompareFunc) custom_find_track) || track == NULL)) {
2579 GST_DEBUG ("Belongs to one of the tracks we control");
2581 ges_track_remove_element (track, track_element);
2583 UNLOCK_DYN (timeline);
2585 g_signal_handlers_disconnect_by_func (clip, clip_track_element_added_cb,
2587 g_signal_handlers_disconnect_by_func (clip, clip_track_element_removed_cb,
2590 g_list_free_full (trackelements, gst_object_unref);
2596 trackelement_start_changed_cb (GESTrackElement * child,
2597 GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
2599 GESTimelinePrivate *priv = timeline->priv;
2600 TrackObjIters *iters = g_hash_table_lookup (priv->obj_iters, child);
2602 if (G_LIKELY (iters->iter_by_layer))
2603 g_sequence_sort_changed (iters->iter_by_layer,
2604 (GCompareDataFunc) element_start_compare, NULL);
2606 if (GES_IS_SOURCE (child)) {
2607 sort_track_elements (timeline, iters);
2608 sort_starts_ends_start (timeline, iters);
2609 sort_starts_ends_end (timeline, iters);
2611 /* If the timeline is set to snap objects together, we
2612 * are sure that all movement of TrackElement-s are done within
2613 * the moving context, so we do not need to recalculate the
2614 * move context as often */
2615 if (timeline->priv->movecontext.ignore_needs_ctx &&
2616 timeline->priv->snapping_distance == 0)
2617 timeline->priv->movecontext.needs_move_ctx = TRUE;
2619 timeline_create_transitions (timeline, child);
2624 trackelement_priority_changed_cb (GESTrackElement * child,
2625 GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
2627 GESTimelinePrivate *priv = timeline->priv;
2629 GList *layer_node = g_list_find_custom (timeline->layers,
2630 GINT_TO_POINTER (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child)),
2631 (GCompareFunc) find_layer_by_prio);
2632 GESLayer *layer = layer_node ? layer_node->data : NULL;
2633 TrackObjIters *iters = g_hash_table_lookup (priv->obj_iters,
2636 if (G_UNLIKELY (layer == NULL)) {
2637 GST_ERROR_OBJECT (timeline,
2638 "Changing a TrackElement prio, which would not "
2639 "land in no layer we are controlling");
2640 if (iters->iter_by_layer)
2641 g_sequence_remove (iters->iter_by_layer);
2642 iters->iter_by_layer = NULL;
2643 iters->layer = NULL;
2645 /* If it moves from layer, properly change it */
2646 if (layer != iters->layer) {
2647 GSequence *by_layer_sequence =
2648 g_hash_table_lookup (priv->by_layer, layer);
2650 GST_DEBUG_OBJECT (child, "Moved from layer %" GST_PTR_FORMAT
2651 "(prio %d) to" " %" GST_PTR_FORMAT " (prio %d)", layer,
2652 ges_layer_get_priority (layer), iters->layer,
2653 ges_layer_get_priority (iters->layer));
2655 g_sequence_remove (iters->iter_by_layer);
2656 iters->iter_by_layer =
2657 g_sequence_insert_sorted (by_layer_sequence, child,
2658 (GCompareDataFunc) element_start_compare, NULL);
2659 iters->layer = layer;
2661 g_sequence_sort_changed (iters->iter_by_layer,
2662 (GCompareDataFunc) element_start_compare, NULL);
2666 if (GES_IS_SOURCE (child))
2667 sort_track_elements (timeline, iters);
2671 trackelement_duration_changed_cb (GESTrackElement * child,
2672 GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
2674 GESTimelinePrivate *priv = timeline->priv;
2675 TrackObjIters *iters = g_hash_table_lookup (priv->obj_iters, child);
2677 if (GES_IS_SOURCE (child)) {
2678 sort_starts_ends_end (timeline, iters);
2680 /* If the timeline is set to snap objects together, we
2681 * are sure that all movement of TrackElement-s are done within
2682 * the moving context, so we do not need to recalculate the
2683 * move context as often */
2684 if (timeline->priv->movecontext.ignore_needs_ctx &&
2685 timeline->priv->snapping_distance == 0) {
2686 timeline->priv->movecontext.needs_move_ctx = TRUE;
2689 timeline_create_transitions (timeline, child);
2694 track_element_added_cb (GESTrack * track, GESTrackElement * track_element,
2695 GESTimeline * timeline)
2697 /* Auto transition should be updated before we receive the signal */
2698 g_signal_connect_after (GES_TRACK_ELEMENT (track_element), "notify::start",
2699 G_CALLBACK (trackelement_start_changed_cb), timeline);
2700 g_signal_connect_after (GES_TRACK_ELEMENT (track_element),
2701 "notify::duration", G_CALLBACK (trackelement_duration_changed_cb),
2703 g_signal_connect_after (GES_TRACK_ELEMENT (track_element),
2704 "notify::priority", G_CALLBACK (trackelement_priority_changed_cb),
2707 start_tracking_track_element (timeline, track_element);
2711 track_element_removed_cb (GESTrack * track,
2712 GESTrackElement * track_element, GESTimeline * timeline)
2715 if (GES_IS_SOURCE (track_element)) {
2716 /* Make sure to reinitialise the moving context next time */
2717 timeline->priv->movecontext.needs_move_ctx = TRUE;
2720 /* Disconnect all signal handlers */
2721 g_signal_handlers_disconnect_by_func (track_element,
2722 trackelement_start_changed_cb, timeline);
2723 g_signal_handlers_disconnect_by_func (track_element,
2724 trackelement_duration_changed_cb, timeline);
2725 g_signal_handlers_disconnect_by_func (track_element,
2726 trackelement_priority_changed_cb, timeline);
2728 stop_tracking_track_element (timeline, track_element);
2731 static GstPadProbeReturn
2732 _pad_probe_cb (GstPad * mixer_pad, GstPadProbeInfo * info,
2733 GESTimeline * timeline)
2735 GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
2736 if (GST_EVENT_TYPE (event) == GST_EVENT_STREAM_START) {
2737 LOCK_DYN (timeline);
2738 if (timeline->priv->group_id == -1) {
2739 if (!gst_event_parse_group_id (event, &timeline->priv->group_id))
2740 timeline->priv->group_id = gst_util_group_id_next ();
2743 info->data = gst_event_make_writable (event);
2744 gst_event_set_group_id (GST_PAD_PROBE_INFO_EVENT (info),
2745 timeline->priv->group_id);
2746 UNLOCK_DYN (timeline);
2748 return GST_PAD_PROBE_REMOVE;
2751 return GST_PAD_PROBE_OK;
2755 _ghost_track_srcpad (TrackPrivate * tr_priv)
2761 GESTrack *track = tr_priv->track;
2763 pad = gst_element_get_static_pad (GST_ELEMENT (track), "src");
2765 GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
2767 /* Remember the pad */
2768 LOCK_DYN (tr_priv->timeline);
2769 GST_OBJECT_LOCK (track);
2773 for (tmp = tr_priv->timeline->priv->priv_tracks; tmp; tmp = g_list_next (tmp)) {
2774 TrackPrivate *tr_priv = (TrackPrivate *) tmp->data;
2776 if (!tr_priv->pad) {
2777 GST_LOG ("Found track without pad %p", tr_priv->track);
2781 GST_OBJECT_UNLOCK (track);
2784 GST_DEBUG ("Ghosting pad and adding it to ourself");
2785 padname = g_strdup_printf ("track_%p_src", track);
2786 tr_priv->ghostpad = gst_ghost_pad_new (padname, pad);
2788 gst_pad_set_active (tr_priv->ghostpad, TRUE);
2789 gst_element_add_pad (GST_ELEMENT (tr_priv->timeline), tr_priv->ghostpad);
2792 GST_DEBUG ("Signaling no-more-pads");
2793 gst_element_no_more_pads (GST_ELEMENT (tr_priv->timeline));
2796 tr_priv->probe_id = gst_pad_add_probe (pad,
2797 GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
2798 (GstPadProbeCallback) _pad_probe_cb, tr_priv->timeline, NULL);
2800 UNLOCK_DYN (tr_priv->timeline);
2804 timeline_add_element (GESTimeline * timeline, GESTimelineElement * element)
2806 GESTimelineElement *same_name =
2807 g_hash_table_lookup (timeline->priv->all_elements,
2810 GST_DEBUG_OBJECT (timeline, "Adding element: %s", element->name);
2812 GST_ERROR_OBJECT (timeline, "%s Already in the timeline %" GST_PTR_FORMAT,
2813 element->name, same_name);
2817 g_hash_table_insert (timeline->priv->all_elements,
2818 ges_timeline_element_get_name (element), gst_object_ref (element));
2824 timeline_remove_element (GESTimeline * timeline, GESTimelineElement * element)
2826 return g_hash_table_remove (timeline->priv->all_elements, element->name);
2830 timeline_fill_gaps (GESTimeline * timeline)
2834 for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
2835 track_resort_and_fill_gaps (tmp->data);
2843 * Creates a new empty #GESTimeline.
2845 * Returns: (transfer floating): The new timeline.
2849 ges_timeline_new (void)
2851 GESProject *project = ges_project_new (NULL);
2852 GESExtractable *timeline = g_object_new (GES_TYPE_TIMELINE, NULL);
2854 ges_extractable_set_asset (timeline, GES_ASSET (project));
2855 gst_object_unref (project);
2857 return GES_TIMELINE (timeline);
2861 * ges_timeline_new_from_uri:
2862 * @uri: the URI to load from
2863 * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL
2865 * Creates a timeline from the given URI.
2867 * Returns: (transfer floating) (nullable): A new timeline if the uri was loaded
2868 * successfully, or %NULL if the uri could not be loaded.
2871 ges_timeline_new_from_uri (const gchar * uri, GError ** error)
2874 GESProject *project = ges_project_new (uri);
2876 ret = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), error));
2877 gst_object_unref (project);
2883 * ges_timeline_load_from_uri:
2884 * @timeline: an empty #GESTimeline into which to load the formatter
2885 * @uri: The URI to load from
2886 * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL
2888 * Loads the contents of URI into the given timeline.
2890 * Returns: %TRUE if the timeline was loaded successfully, or %FALSE if the uri
2891 * could not be loaded.
2894 ges_timeline_load_from_uri (GESTimeline * timeline, const gchar * uri,
2897 GESProject *project;
2898 gboolean ret = FALSE;
2900 g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
2901 g_return_val_if_fail ((ges_extractable_get_asset (GES_EXTRACTABLE
2902 (timeline)) == NULL), FALSE);
2904 project = ges_project_new (uri);
2905 ret = ges_project_load (project, timeline, error);
2906 gst_object_unref (project);
2912 * ges_timeline_save_to_uri:
2913 * @timeline: a #GESTimeline
2914 * @uri: The location to save to
2915 * @formatter_asset: (allow-none): The formatter asset to use or %NULL. If %NULL,
2916 * will try to save in the same format as the one from which the timeline as been loaded
2917 * or default to the formatter with highest rank
2918 * @overwrite: %TRUE to overwrite file if it exists
2919 * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL
2921 * Saves the timeline to the given location
2923 * Returns: %TRUE if the timeline was successfully saved to the given location,
2927 ges_timeline_save_to_uri (GESTimeline * timeline, const gchar * uri,
2928 GESAsset * formatter_asset, gboolean overwrite, GError ** error)
2930 GESProject *project;
2932 gboolean ret, created_proj = FALSE;
2934 g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
2936 GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline)));
2938 if (project == NULL) {
2939 project = ges_project_new (NULL);
2940 created_proj = TRUE;
2943 ret = ges_project_save (project, timeline, uri, formatter_asset, overwrite,
2947 gst_object_unref (project);
2953 * ges_timeline_get_groups:
2954 * @timeline: a #GESTimeline
2956 * Get the list of #GESGroup present in the Timeline.
2958 * Returns: (transfer none) (element-type GESGroup): the list of
2959 * #GESGroup that contain clips present in the timeline's layers.
2960 * Must not be changed.
2963 ges_timeline_get_groups (GESTimeline * timeline)
2965 g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL);
2966 CHECK_THREAD (timeline);
2968 return timeline->priv->groups;
2972 * ges_timeline_append_layer:
2973 * @timeline: a #GESTimeline
2975 * Append a newly created #GESLayer to @timeline
2976 * Note that you do not own any reference to the returned layer.
2978 * Returns: (transfer none): The newly created #GESLayer, or the last (empty)
2979 * #GESLayer of @timeline.
2982 ges_timeline_append_layer (GESTimeline * timeline)
2987 g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL);
2988 CHECK_THREAD (timeline);
2990 layer = ges_layer_new ();
2991 priority = g_list_length (timeline->layers);
2992 ges_layer_set_priority (layer, priority);
2994 ges_timeline_add_layer (timeline, layer);
3000 * ges_timeline_add_layer:
3001 * @timeline: a #GESTimeline
3002 * @layer: (transfer floating): the #GESLayer to add
3004 * Add the layer to the timeline. The reference to the @layer will be stolen
3007 * Returns: %TRUE if the layer was properly added, else %FALSE.
3010 ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer)
3012 gboolean auto_transition;
3013 GList *objects, *tmp;
3015 g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
3016 g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
3017 CHECK_THREAD (timeline);
3019 GST_DEBUG ("timeline:%p, layer:%p", timeline, layer);
3021 /* We can only add a layer that doesn't already belong to another timeline */
3022 if (G_UNLIKELY (layer->timeline)) {
3023 GST_WARNING ("Layer belongs to another timeline, can't add it");
3024 gst_object_ref_sink (layer);
3025 gst_object_unref (layer);
3029 /* Add to the list of layers, make sure we don't already control it */
3030 if (G_UNLIKELY (g_list_find (timeline->layers, (gconstpointer) layer))) {
3031 GST_WARNING ("Layer is already controlled by this timeline");
3032 gst_object_ref_sink (layer);
3033 gst_object_unref (layer);
3037 auto_transition = ges_layer_get_auto_transition (layer);
3039 /* If the user doesn't explicitely set layer auto_transition, then set our */
3040 if (!auto_transition) {
3041 auto_transition = ges_timeline_get_auto_transition (timeline);
3042 ges_layer_set_auto_transition (layer, auto_transition);
3045 gst_object_ref_sink (layer);
3046 timeline->layers = g_list_insert_sorted (timeline->layers, layer,
3047 (GCompareFunc) sort_layers);
3049 /* Inform the layer that it belongs to a new timeline */
3050 ges_layer_set_timeline (layer, timeline);
3052 g_hash_table_insert (timeline->priv->by_layer, layer, g_sequence_new (NULL));
3054 /* Connect to 'clip-added'/'clip-removed' signal from the new layer */
3055 g_signal_connect_after (layer, "clip-added",
3056 G_CALLBACK (layer_object_added_cb), timeline);
3057 g_signal_connect_after (layer, "clip-removed",
3058 G_CALLBACK (layer_object_removed_cb), timeline);
3059 g_signal_connect (layer, "notify::priority",
3060 G_CALLBACK (layer_priority_changed_cb), timeline);
3061 g_signal_connect (layer, "notify::auto-transition",
3062 G_CALLBACK (layer_auto_transition_changed_cb), timeline);
3064 GST_DEBUG ("Done adding layer, emitting 'layer-added' signal");
3065 g_signal_emit (timeline, ges_timeline_signals[LAYER_ADDED], 0, layer);
3067 /* add any existing clips to the timeline */
3068 objects = ges_layer_get_clips (layer);
3069 for (tmp = objects; tmp; tmp = tmp->next) {
3070 layer_object_added_cb (layer, tmp->data, timeline);
3071 gst_object_unref (tmp->data);
3074 g_list_free (objects);
3076 timeline->priv->movecontext.needs_move_ctx = TRUE;
3082 * ges_timeline_remove_layer:
3083 * @timeline: a #GESTimeline
3084 * @layer: the #GESLayer to remove
3086 * Removes the layer from the timeline. The reference that the @timeline holds on
3087 * the layer will be dropped. If you wish to use the @layer after calling this
3088 * method, you need to take a reference before calling.
3090 * Returns: %TRUE if the layer was properly removed, else %FALSE.
3094 ges_timeline_remove_layer (GESTimeline * timeline, GESLayer * layer)
3096 GList *layer_objects, *tmp;
3098 g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
3099 g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
3100 CHECK_THREAD (timeline);
3102 GST_DEBUG ("timeline:%p, layer:%p", timeline, layer);
3104 if (G_UNLIKELY (!g_list_find (timeline->layers, layer))) {
3105 GST_WARNING ("Layer doesn't belong to this timeline");
3109 /* remove objects from any private data structures */
3111 layer_objects = ges_layer_get_clips (layer);
3112 for (tmp = layer_objects; tmp; tmp = tmp->next) {
3113 layer_object_removed_cb (layer, GES_CLIP (tmp->data), timeline);
3114 gst_object_unref (G_OBJECT (tmp->data));
3117 g_list_free (layer_objects);
3119 /* Disconnect signals */
3120 GST_DEBUG ("Disconnecting signal callbacks");
3121 g_signal_handlers_disconnect_by_func (layer, layer_object_added_cb, timeline);
3122 g_signal_handlers_disconnect_by_func (layer, layer_object_removed_cb,
3124 g_signal_handlers_disconnect_by_func (layer, layer_priority_changed_cb,
3126 g_signal_handlers_disconnect_by_func (layer,
3127 layer_auto_transition_changed_cb, timeline);
3129 g_hash_table_remove (timeline->priv->by_layer, layer);
3130 timeline->layers = g_list_remove (timeline->layers, layer);
3131 ges_layer_set_timeline (layer, NULL);
3133 g_signal_emit (timeline, ges_timeline_signals[LAYER_REMOVED], 0, layer);
3135 gst_object_unref (layer);
3136 timeline->priv->movecontext.needs_move_ctx = TRUE;
3142 * ges_timeline_add_track:
3143 * @timeline: a #GESTimeline
3144 * @track: (transfer full): the #GESTrack to add
3146 * Add a track to the timeline. The reference to the track will be stolen by the
3149 * Returns: %TRUE if the track was properly added, else %FALSE.
3152 /* FIXME: create track elements for clips which have already been
3153 * added to existing layers.
3157 ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
3159 TrackPrivate *tr_priv;
3162 g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
3163 g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
3164 CHECK_THREAD (timeline);
3166 GST_DEBUG ("timeline:%p, track:%p", timeline, track);
3168 /* make sure we don't already control it */
3169 if (G_UNLIKELY (g_list_find (timeline->tracks, (gconstpointer) track))) {
3170 GST_WARNING ("Track is already controlled by this timeline");
3174 /* Add the track to ourself (as a GstBin)
3175 * Reference is stolen ! */
3176 if (G_UNLIKELY (!gst_bin_add (GST_BIN (timeline), GST_ELEMENT (track)))) {
3177 GST_WARNING ("Couldn't add track to ourself (GST)");
3181 tr_priv = g_new0 (TrackPrivate, 1);
3182 tr_priv->timeline = timeline;
3183 tr_priv->track = track;
3185 /* Add the track to the list of tracks we track */
3186 LOCK_DYN (timeline);
3187 timeline->priv->priv_tracks = g_list_append (timeline->priv->priv_tracks,
3189 UNLOCK_DYN (timeline);
3190 timeline->tracks = g_list_append (timeline->tracks, track);
3192 /* Inform the track that it's currently being used by ourself */
3193 ges_track_set_timeline (track, timeline);
3195 GST_DEBUG ("Done adding track, emitting 'track-added' signal");
3197 _ghost_track_srcpad (tr_priv);
3199 /* emit 'track-added' */
3200 g_signal_emit (timeline, ges_timeline_signals[TRACK_ADDED], 0, track);
3202 /* ensure that each existing clip has the opportunity to create a
3203 * track element for this track*/
3205 /* We connect to the object for the timeline editing mode management */
3206 g_signal_connect (G_OBJECT (track), "track-element-added",
3207 G_CALLBACK (track_element_added_cb), timeline);
3208 g_signal_connect (G_OBJECT (track), "track-element-removed",
3209 G_CALLBACK (track_element_removed_cb), timeline);
3211 for (tmp = timeline->layers; tmp; tmp = tmp->next) {
3212 GList *objects, *obj;
3213 objects = ges_layer_get_clips (tmp->data);
3215 for (obj = objects; obj; obj = obj->next) {
3216 GESClip *clip = obj->data;
3218 add_object_to_tracks (timeline, clip, track);
3219 gst_object_unref (clip);
3221 g_list_free (objects);
3224 /* FIXME Check if we should rollback if we can't sync state */
3225 gst_element_sync_state_with_parent (GST_ELEMENT (track));
3226 g_object_set (track, "message-forward", TRUE, NULL);
3232 * ges_timeline_remove_track:
3233 * @timeline: a #GESTimeline
3234 * @track: the #GESTrack to remove
3236 * Remove the @track from the @timeline. The reference stolen when adding the
3237 * @track will be removed. If you wish to use the @track after calling this
3238 * function you must ensure that you have a reference to it.
3240 * Returns: %TRUE if the @track was properly removed, else %FALSE.
3243 /* FIXME: release any track elements associated with this layer. currenly this
3244 * will not happen if you remove the track before removing *all*
3245 * clips which have a track element in this track.
3249 ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track)
3252 TrackPrivate *tr_priv;
3253 GESTimelinePrivate *priv;
3255 g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
3256 g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
3257 CHECK_THREAD (timeline);
3259 GST_DEBUG ("timeline:%p, track:%p", timeline, track);
3261 priv = timeline->priv;
3262 LOCK_DYN (timeline);
3263 if (G_UNLIKELY (!(tmp = g_list_find_custom (priv->priv_tracks,
3264 track, (GCompareFunc) custom_find_track)))) {
3265 GST_WARNING ("Track doesn't belong to this timeline");
3266 UNLOCK_DYN (timeline);
3270 tr_priv = tmp->data;
3271 gst_object_unref (tr_priv->pad);
3272 priv->priv_tracks = g_list_remove (priv->priv_tracks, tr_priv);
3273 UNLOCK_DYN (timeline);
3274 timeline->tracks = g_list_remove (timeline->tracks, track);
3276 ges_track_set_timeline (track, NULL);
3278 /* Remove ghost pad */
3279 if (tr_priv->ghostpad) {
3280 GST_DEBUG ("Removing ghostpad");
3281 gst_pad_set_active (tr_priv->ghostpad, FALSE);
3282 gst_ghost_pad_set_target ((GstGhostPad *) tr_priv->ghostpad, NULL);
3283 gst_element_remove_pad (GST_ELEMENT (timeline), tr_priv->ghostpad);
3286 /* Remove pad-added/-removed handlers */
3287 g_signal_handlers_disconnect_by_func (track, track_element_added_cb,
3289 g_signal_handlers_disconnect_by_func (track, track_element_removed_cb,
3292 /* Signal track removal to all layers/objects */
3293 g_signal_emit (timeline, ges_timeline_signals[TRACK_REMOVED], 0, track);
3295 /* remove track from our bin */
3296 gst_object_ref (track);
3297 if (G_UNLIKELY (!gst_bin_remove (GST_BIN (timeline), GST_ELEMENT (track)))) {
3298 GST_WARNING ("Couldn't remove track to ourself (GST)");
3299 gst_object_unref (track);
3303 /* set track state to NULL */
3304 gst_element_set_state (GST_ELEMENT (track), GST_STATE_NULL);
3306 gst_object_unref (track);
3314 * ges_timeline_get_track_for_pad:
3315 * @timeline: The #GESTimeline
3318 * Search the #GESTrack corresponding to the given @timeline's @pad.
3320 * Returns: (transfer none) (nullable): The corresponding #GESTrack if it is
3321 * found, or %NULL if there is an error.
3325 ges_timeline_get_track_for_pad (GESTimeline * timeline, GstPad * pad)
3329 g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL);
3331 LOCK_DYN (timeline);
3332 for (tmp = timeline->priv->priv_tracks; tmp; tmp = g_list_next (tmp)) {
3333 TrackPrivate *tr_priv = (TrackPrivate *) tmp->data;
3334 if (pad == tr_priv->ghostpad) {
3335 UNLOCK_DYN (timeline);
3336 return tr_priv->track;
3339 UNLOCK_DYN (timeline);
3345 * ges_timeline_get_pad_for_track:
3346 * @timeline: The #GESTimeline
3347 * @track: The #GESTrack
3349 * Search the #GstPad corresponding to the given @timeline's @track.
3351 * Returns: (transfer none) (nullable): The corresponding #GstPad if it is
3352 * found, or %NULL if there is an error.
3356 ges_timeline_get_pad_for_track (GESTimeline * timeline, GESTrack * track)
3360 LOCK_DYN (timeline);
3361 for (tmp = timeline->priv->priv_tracks; tmp; tmp = g_list_next (tmp)) {
3362 TrackPrivate *tr_priv = (TrackPrivate *) tmp->data;
3364 if (track == tr_priv->track) {
3365 if (tr_priv->ghostpad)
3366 gst_object_ref (tr_priv->ghostpad);
3368 UNLOCK_DYN (timeline);
3369 return tr_priv->ghostpad;
3372 UNLOCK_DYN (timeline);
3378 * ges_timeline_get_tracks:
3379 * @timeline: a #GESTimeline
3381 * Returns the list of #GESTrack used by the Timeline.
3383 * Returns: (transfer full) (element-type GESTrack): A list of #GESTrack.
3384 * The caller should unref each track once he is done with them.
3387 ges_timeline_get_tracks (GESTimeline * timeline)
3389 g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL);
3390 CHECK_THREAD (timeline);
3392 return g_list_copy_deep (timeline->tracks, (GCopyFunc) gst_object_ref, NULL);
3396 * ges_timeline_get_layers:
3397 * @timeline: a #GESTimeline
3399 * Get the list of #GESLayer present in the Timeline.
3401 * Returns: (transfer full) (element-type GESLayer): the list of
3402 * #GESLayer present in the Timeline sorted by priority.
3403 * The caller should unref each Layer once he is done with them.
3406 ges_timeline_get_layers (GESTimeline * timeline)
3408 GList *tmp, *res = NULL;
3410 g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL);
3411 CHECK_THREAD (timeline);
3413 for (tmp = timeline->layers; tmp; tmp = g_list_next (tmp)) {
3414 res = g_list_insert_sorted (res, gst_object_ref (tmp->data),
3415 (GCompareFunc) sort_layers);
3422 track_commited_cb (GESTrack * track, GESTimeline * timeline)
3424 gboolean emit_commited = FALSE;
3425 GST_OBJECT_LOCK (timeline);
3426 timeline->priv->expected_commited -= 1;
3427 if (timeline->priv->expected_commited == 0)
3428 emit_commited = TRUE;
3429 g_signal_handlers_disconnect_by_func (track, track_commited_cb, timeline);
3430 GST_OBJECT_UNLOCK (timeline);
3432 if (emit_commited) {
3433 g_signal_emit (timeline, ges_timeline_signals[COMMITED], 0);
3437 /* Must be called with the timeline's DYN_LOCK */
3439 ges_timeline_commit_unlocked (GESTimeline * timeline)
3442 gboolean res = TRUE;
3444 GST_DEBUG_OBJECT (timeline, "commiting changes");
3446 for (tmp = timeline->layers; tmp; tmp = tmp->next) {
3447 GESLayer *layer = tmp->data;
3449 _create_transitions_on_layer (timeline, layer, NULL, NULL,
3450 _find_transition_from_auto_transitions);
3452 /* Ensure clip priorities are correct after an edit */
3453 ges_layer_resync_priorities (layer);
3456 timeline->priv->expected_commited =
3457 g_list_length (timeline->priv->priv_tracks);
3459 if (timeline->priv->expected_commited == 0) {
3460 g_signal_emit (timeline, ges_timeline_signals[COMMITED], 0);
3462 for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
3463 g_signal_connect (tmp->data, "commited", G_CALLBACK (track_commited_cb),
3465 if (!ges_track_commit (GES_TRACK (tmp->data)))
3470 /* Make sure we reset the context */
3471 timeline->priv->movecontext.needs_move_ctx = TRUE;
3477 * ges_timeline_commit:
3478 * @timeline: a #GESTimeline
3480 * Commit all the pending changes of the clips contained in the
3483 * When changes happen in a timeline, they are not
3484 * directly executed in the non-linear engine. Call this method once you are
3485 * done with a set of changes and want it to be executed.
3487 * The #GESTimeline::commited signal will be emitted when the (possibly updated)
3488 * #GstPipeline is ready to output data again, except if the state of the
3489 * timeline was #GST_STATE_READY or #GST_STATE_NULL.
3491 * Note that all the pending changes will automatically be executed when the
3492 * timeline goes from #GST_STATE_READY to #GST_STATE_PAUSED, which usually is
3493 * triggered by corresponding state changes in a containing #GESPipeline.
3495 * You should not try to change the state of the timeline, seek it or add
3496 * tracks to it during a commit operation, that is between a call to this
3497 * function and after receiving the #GESTimeline::commited signal.
3499 * See #ges_timeline_commit_sync if you don't want to bother with waiting
3502 * Returns: %TRUE if pending changes were commited or %FALSE if nothing needed
3506 ges_timeline_commit (GESTimeline * timeline)
3510 g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
3512 LOCK_DYN (timeline);
3513 ret = ges_timeline_commit_unlocked (timeline);
3514 UNLOCK_DYN (timeline);
3516 ges_timeline_emit_snappig (timeline, NULL, NULL);
3521 commited_cb (GESTimeline * timeline)
3523 g_mutex_lock (&timeline->priv->commited_lock);
3524 g_cond_signal (&timeline->priv->commited_cond);
3525 g_mutex_unlock (&timeline->priv->commited_lock);
3529 * ges_timeline_commit_sync:
3530 * @timeline: a #GESTimeline
3532 * Commit all the pending changes of the #GESClips contained in the
3535 * Will return once the update is complete, that is when the
3536 * (possibly updated) #GstPipeline is ready to output data again, or if the
3537 * state of the timeline was #GST_STATE_READY or #GST_STATE_NULL.
3539 * This function will wait for any pending state change of the timeline by
3540 * calling #gst_element_get_state with a #GST_CLOCK_TIME_NONE timeout, you
3541 * should not try to change the state from another thread before this function
3544 * See #ges_timeline_commit for more information.
3546 * Returns: %TRUE if pending changes were commited or %FALSE if nothing needed
3550 ges_timeline_commit_sync (GESTimeline * timeline)
3553 gboolean wait_for_signal;
3555 g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
3557 /* Let's make sure our state is stable */
3558 gst_element_get_state (GST_ELEMENT (timeline), NULL, NULL,
3559 GST_CLOCK_TIME_NONE);
3561 /* Let's make sure no track gets added between now and the actual commiting */
3562 LOCK_DYN (timeline);
3563 wait_for_signal = g_list_length (timeline->priv->priv_tracks) > 0
3564 && GST_STATE (timeline) >= GST_STATE_PAUSED;
3566 if (!wait_for_signal) {
3567 ret = ges_timeline_commit_unlocked (timeline);
3570 g_signal_connect (timeline, "commited", (GCallback) commited_cb, NULL);
3572 g_mutex_lock (&timeline->priv->commited_lock);
3574 ret = ges_timeline_commit_unlocked (timeline);
3575 g_cond_wait (&timeline->priv->commited_cond,
3576 &timeline->priv->commited_lock);
3577 g_mutex_unlock (&timeline->priv->commited_lock);
3578 g_signal_handler_disconnect (timeline, handler_id);
3581 UNLOCK_DYN (timeline);
3587 * ges_timeline_get_duration:
3588 * @timeline: a #GESTimeline
3590 * Get the current duration of @timeline
3592 * Returns: The current duration of @timeline
3595 ges_timeline_get_duration (GESTimeline * timeline)
3597 g_return_val_if_fail (GES_IS_TIMELINE (timeline), GST_CLOCK_TIME_NONE);
3598 CHECK_THREAD (timeline);
3600 return timeline->priv->duration;
3604 * ges_timeline_get_auto_transition:
3605 * @timeline: a #GESTimeline
3607 * Gets whether transitions are automatically added when objects
3610 * Returns: %TRUE if transitions are automatically added, else %FALSE.
3613 ges_timeline_get_auto_transition (GESTimeline * timeline)
3615 g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
3616 CHECK_THREAD (timeline);
3618 return timeline->priv->auto_transition;
3622 * ges_timeline_set_auto_transition:
3623 * @timeline: a #GESLayer
3624 * @auto_transition: whether the auto_transition is active
3626 * Sets the layer to the given @auto_transition. See the documentation of the
3627 * property auto_transition for more information.
3630 ges_timeline_set_auto_transition (GESTimeline * timeline,
3631 gboolean auto_transition)
3636 g_return_if_fail (GES_IS_TIMELINE (timeline));
3637 CHECK_THREAD (timeline);
3639 timeline->priv->auto_transition = auto_transition;
3640 g_object_notify (G_OBJECT (timeline), "auto-transition");
3642 layers = timeline->layers;
3643 for (; layers; layers = layers->next) {
3644 layer = layers->data;
3645 ges_layer_set_auto_transition (layer, auto_transition);
3650 * ges_timeline_get_snapping_distance:
3651 * @timeline: a #GESTimeline
3653 * Gets the configured snapping distance of the timeline. See
3654 * the documentation of the property snapping_distance for more
3657 * Returns: The @snapping_distance property of the timeline
3660 ges_timeline_get_snapping_distance (GESTimeline * timeline)
3662 g_return_val_if_fail (GES_IS_TIMELINE (timeline), GST_CLOCK_TIME_NONE);
3663 CHECK_THREAD (timeline);
3665 return timeline->priv->snapping_distance;
3670 * ges_timeline_set_snapping_distance:
3671 * @timeline: a #GESLayer
3672 * @snapping_distance: whether the snapping_distance is active
3674 * Sets the @snapping_distance of the timeline. See the documentation of the
3675 * property snapping_distance for more information.
3678 ges_timeline_set_snapping_distance (GESTimeline * timeline,
3679 GstClockTime snapping_distance)
3681 g_return_if_fail (GES_IS_TIMELINE (timeline));
3682 CHECK_THREAD (timeline);
3684 timeline->priv->snapping_distance = snapping_distance;
3688 * ges_timeline_get_element:
3689 * @timeline: a #GESTimeline
3691 * Gets a #GESTimelineElement contained in the timeline
3693 * Returns: (transfer full) (nullable): The #GESTimelineElement or %NULL if
3696 GESTimelineElement *
3697 ges_timeline_get_element (GESTimeline * timeline, const gchar * name)
3699 GESTimelineElement *ret;
3701 g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL);
3702 CHECK_THREAD (timeline);
3704 ret = g_hash_table_lookup (timeline->priv->all_elements, name);
3707 return gst_object_ref (ret);
3709 #ifndef GST_DISABLE_GST_DEBUG
3711 GList *element_names, *tmp;
3712 element_names = g_hash_table_get_keys (timeline->priv->all_elements);
3714 GST_INFO_OBJECT (timeline, "Does not contain element %s", name);
3716 for (tmp = element_names; tmp; tmp = tmp->next) {
3717 GST_DEBUG_OBJECT (timeline, "Containes: %s", (gchar *) tmp->data);
3719 g_list_free (element_names);
3727 * ges_timeline_is_empty:
3728 * @timeline: a #GESTimeline
3730 * Check whether a #GESTimeline is empty or not
3732 * Returns: %TRUE if the timeline is empty %FALSE otherwize
3735 ges_timeline_is_empty (GESTimeline * timeline)
3737 GHashTableIter iter;
3738 gpointer key, value;
3740 g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
3741 CHECK_THREAD (timeline);
3743 if (g_hash_table_size (timeline->priv->all_elements) == 0)
3746 g_hash_table_iter_init (&iter, timeline->priv->all_elements);
3747 while (g_hash_table_iter_next (&iter, &key, &value)) {
3748 if (GES_IS_SOURCE (value) &&
3749 ges_track_element_is_active (GES_TRACK_ELEMENT (value)))
3757 * ges_timeline_get_layer:
3758 * @timeline: The #GESTimeline to retrive a layer from
3759 * @priority: The priority of the layer to find
3761 * Retrieve the layer with @priority as a priority
3763 * Returns: (transfer full) (nullable): A #GESLayer or %NULL if no layer with
3764 * @priority was found
3769 ges_timeline_get_layer (GESTimeline * timeline, guint priority)
3772 GESLayer *layer = NULL;
3774 g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL);
3775 CHECK_THREAD (timeline);
3777 for (tmp = timeline->layers; tmp; tmp = tmp->next) {
3778 GESLayer *tmp_layer = GES_LAYER (tmp->data);
3781 g_object_get (tmp_layer, "priority", &tmp_priority, NULL);
3782 if (tmp_priority == priority) {
3783 layer = gst_object_ref (tmp_layer);
3792 * ges_timeline_paste_element:
3793 * @timeline: The #GESTimeline onto which the #GESTimelineElement should be pasted
3794 * @element: The #GESTimelineElement to paste
3795 * @position: The position in the timeline the element should
3796 * be pasted to, meaning it will become the start of @element
3797 * @layer_priority: The #GESLayer to which the element should be pasted to.
3798 * -1 means paste to the same layer from which the @element has been copied from.
3800 * Paste @element inside the timeline. @element must have been
3801 * created using ges_timeline_element_copy with deep=TRUE set,
3802 * i.e. it must be a deep copy, otherwise it will fail.
3804 * Returns: (transfer none): Shallow copy of the @element pasted
3806 GESTimelineElement *
3807 ges_timeline_paste_element (GESTimeline * timeline,
3808 GESTimelineElement * element, GstClockTime position, gint layer_priority)
3810 GESTimelineElement *res, *copied_from;
3811 GESTimelineElementClass *element_class;
3813 g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
3814 g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (element), FALSE);
3815 CHECK_THREAD (timeline);
3817 element_class = GES_TIMELINE_ELEMENT_GET_CLASS (element);
3818 copied_from = ges_timeline_element_get_copied_from (element);
3821 GST_ERROR_OBJECT (element, "Is not being 'deeply' copied!");
3826 if (!element_class->paste) {
3827 GST_ERROR_OBJECT (element, "No paste vmethod implemented");
3833 * Currently the API only supports pasting onto the same layer from which
3834 * the @element has been copied from, i.e., @layer_priority needs to be -1.
3836 if (layer_priority != -1) {
3837 GST_WARNING_OBJECT (timeline,
3838 "Only -1 value for layer priority is supported");
3841 res = element_class->paste (element, copied_from, position);
3843 g_clear_object (&copied_from);
3845 return g_object_ref (res);
3849 * ges_timeline_move_layer:
3850 * @timeline: The timeline in which @layer must be
3851 * @layer: The layer to move at @new_layer_priority
3852 * @new_layer_priority: The index at which @layer should land
3854 * Moves @layer at @new_layer_priority meaning that @layer
3855 * we land at that position in the stack of layers inside
3856 * the timeline. If @new_layer_priority is superior than the number
3857 * of layers present in the time, it will move to the end of the
3861 ges_timeline_move_layer (GESTimeline * timeline, GESLayer * layer,
3862 guint new_layer_priority)
3864 gint current_priority;
3866 g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
3867 g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
3868 g_return_val_if_fail (ges_layer_get_timeline (layer) == timeline, FALSE);
3869 CHECK_THREAD (timeline);
3871 current_priority = ges_layer_get_priority (layer);
3873 if (new_layer_priority == current_priority) {
3874 GST_DEBUG_OBJECT (timeline,
3875 "Nothing to do for %" GST_PTR_FORMAT ", same priorities", layer);
3880 timeline->layers = g_list_remove (timeline->layers, layer);
3881 timeline->layers = g_list_insert (timeline->layers, layer,
3882 (gint) new_layer_priority);
3884 _resync_layers (timeline);