GESTimeline: New method ges_timeline_get_tracks
[platform/upstream/gstreamer.git] / ges / ges-timeline.c
1 /* GStreamer Editing Services
2  * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk>
3  *               2009 Nokia Corporation
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 /**
22  * SECTION:ges-timeline
23  * @short_description: Multimedia timeline
24  *
25  * #GESTimeline is the central object for any multimedia timeline.
26  * 
27  * Contains a list of #GESTimelineLayer which users should use to arrange the
28  * various timeline objects through time.
29  *
30  * The output type is determined by the #GESTimelineTrack that are set on
31  * the #GESTimeline.
32  */
33
34 #include "gesmarshal.h"
35 #include "ges-internal.h"
36 #include "ges-timeline.h"
37 #include "ges-track.h"
38 #include "ges-timeline-layer.h"
39 #include "ges.h"
40
41
42 G_DEFINE_TYPE (GESTimeline, ges_timeline, GST_TYPE_BIN);
43
44 /* private structure to contain our track-related information */
45
46 typedef struct
47 {
48   GESTimeline *timeline;
49   GESTrack *track;
50   GstPad *pad;                  /* Pad from the track */
51   GstPad *ghostpad;
52 } TrackPrivate;
53
54 enum
55 {
56   TRACK_ADDED,
57   TRACK_REMOVED,
58   LAYER_ADDED,
59   LAYER_REMOVED,
60   LAST_SIGNAL
61 };
62
63 static guint ges_timeline_signals[LAST_SIGNAL] = { 0 };
64
65 gint custom_find_track (TrackPrivate * priv, GESTrack * track);
66 static void
67 ges_timeline_get_property (GObject * object, guint property_id,
68     GValue * value, GParamSpec * pspec)
69 {
70   switch (property_id) {
71     default:
72       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
73   }
74 }
75
76 static void
77 ges_timeline_set_property (GObject * object, guint property_id,
78     const GValue * value, GParamSpec * pspec)
79 {
80   switch (property_id) {
81     default:
82       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
83   }
84 }
85
86 static void
87 ges_timeline_dispose (GObject * object)
88 {
89   G_OBJECT_CLASS (ges_timeline_parent_class)->dispose (object);
90 }
91
92 static void
93 ges_timeline_finalize (GObject * object)
94 {
95   G_OBJECT_CLASS (ges_timeline_parent_class)->finalize (object);
96 }
97
98 static void
99 ges_timeline_class_init (GESTimelineClass * klass)
100 {
101   GObjectClass *object_class = G_OBJECT_CLASS (klass);
102
103   object_class->get_property = ges_timeline_get_property;
104   object_class->set_property = ges_timeline_set_property;
105   object_class->dispose = ges_timeline_dispose;
106   object_class->finalize = ges_timeline_finalize;
107
108
109   /**
110    * GESTimeline::track-added
111    * @timeline: the #GESTimeline
112    * @track: the #GESTrack that was added to the timeline
113    *
114    * Will be emitted after the track was added to the timeline.
115    */
116   ges_timeline_signals[TRACK_ADDED] =
117       g_signal_new ("track-added", G_TYPE_FROM_CLASS (klass),
118       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, track_added), NULL,
119       NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GES_TYPE_TRACK);
120
121   /**
122    * GESTimeline::track-removed
123    * @timeline: the #GESTimeline
124    * @track: the #GESTrack that was removed from the timeline
125    *
126    * Will be emitted after the track was removed from the timeline.
127    */
128   ges_timeline_signals[TRACK_REMOVED] =
129       g_signal_new ("track-removed", G_TYPE_FROM_CLASS (klass),
130       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, track_removed),
131       NULL, NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GES_TYPE_TRACK);
132
133   /**
134    * GESTimeline::layer-added
135    * @timeline: the #GESTimeline
136    * @layer: the #GESTimelineLayer that was added to the timeline
137    *
138    * Will be emitted after the layer was added to the timeline.
139    */
140   ges_timeline_signals[LAYER_ADDED] =
141       g_signal_new ("layer-added", G_TYPE_FROM_CLASS (klass),
142       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, layer_added), NULL,
143       NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GES_TYPE_TIMELINE_LAYER);
144
145   /**
146    * GESTimeline::layer-removed
147    * @timeline: the #GESTimeline
148    * @layer: the #GESTimelineLayer that was removed from the timeline
149    *
150    * Will be emitted after the layer was removed from the timeline.
151    */
152   ges_timeline_signals[LAYER_REMOVED] =
153       g_signal_new ("layer-removed", G_TYPE_FROM_CLASS (klass),
154       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, layer_removed),
155       NULL, NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
156       GES_TYPE_TIMELINE_LAYER);
157 }
158
159 static void
160 ges_timeline_init (GESTimeline * self)
161 {
162   self->layers = NULL;
163   self->tracks = NULL;
164 }
165
166 /**
167  * ges_timeline_new:
168  *
169  * Creates a new empty #GESTimeline.
170  *
171  * Returns: The new timeline.
172  */
173
174 GESTimeline *
175 ges_timeline_new (void)
176 {
177   return g_object_new (GES_TYPE_TIMELINE, NULL);
178 }
179
180 /**
181  * ges_timeline_load_from_uri:
182  * @uri: The URI to load from
183  *
184  * Creates a timeline from the contents of given uri.
185  *
186  * NOT_IMPLEMENTED !
187  *
188  * Returns: A new #GESTimeline if loading was successful, else NULL.
189  */
190
191 GESTimeline *
192 ges_timeline_load_from_uri (gchar * uri)
193 {
194   /* FIXME : IMPLEMENT */
195   return NULL;
196 }
197
198 /**
199  * ges_timeline_save:
200  * @timeline: a #GESTimeline
201  * @uri: The location to save to
202  *
203  * Saves the timeline to the given location
204  *
205  * NOT_IMPLEMENTED !
206  *
207  * Returns: TRUE if the timeline was successfully saved to the given location,
208  * else FALSE.
209  */
210
211 gboolean
212 ges_timeline_save (GESTimeline * timeline, gchar * uri)
213 {
214   /* FIXME : IMPLEMENT */
215   return FALSE;
216 }
217
218 static void
219 layer_object_added_cb (GESTimelineLayer * layer, GESTimelineObject * object,
220     GESTimeline * timeline)
221 {
222   GList *tmp;
223
224   GST_DEBUG ("New TimelineObject %p added to layer %p", object, layer);
225
226   for (tmp = timeline->tracks; tmp; tmp = g_list_next (tmp)) {
227     TrackPrivate *priv = (TrackPrivate *) tmp->data;
228     GESTrack *track = priv->track;
229     GESTrackObject *trobj;
230
231     GST_LOG ("Trying with track %p", track);
232
233     if (G_UNLIKELY (!(trobj =
234                 ges_timeline_object_create_track_object (object, track)))) {
235       GST_WARNING ("Couldn't create TrackObject for TimelineObject");
236       continue;
237     }
238
239     GST_LOG ("Got new TrackObject %p, adding it to track", trobj);
240     ges_track_add_object (track, trobj);
241   }
242
243   GST_DEBUG ("done");
244 }
245
246
247 static void
248 layer_object_removed_cb (GESTimelineLayer * layer, GESTimelineObject * object,
249     GESTimeline * timeline)
250 {
251   GList *tmp, *next;
252
253   GST_DEBUG ("TimelineObject %p removed from layer %p", object, layer);
254
255   /* Go over the object's track objects and figure out which one belongs to
256    * the list of tracks we control */
257
258   for (tmp = object->trackobjects; tmp; tmp = next) {
259     GESTrackObject *trobj = (GESTrackObject *) tmp->data;
260
261     next = g_list_next (tmp);
262
263     GST_DEBUG ("Trying to remove TrackObject %p", trobj);
264     if (G_LIKELY (g_list_find_custom (timeline->tracks,
265                 (gconstpointer) trobj->track,
266                 (GCompareFunc) custom_find_track))) {
267       GST_DEBUG ("Belongs to one of the tracks we control");
268       ges_track_remove_object (trobj->track, trobj);
269
270       ges_timeline_object_release_track_object (object, trobj);
271     }
272   }
273
274   GST_DEBUG ("Done");
275 }
276
277 /**
278  * ges_timeline_add_layer:
279  * @timeline: a #GESTimeline
280  * @layer: the #GESTimelineLayer to add
281  *
282  * Add the layer to the timeline. The reference to the @layer will be stolen
283  * by the @timeline.
284  *
285  * Returns: TRUE if the layer was properly added, else FALSE.
286  */
287 gboolean
288 ges_timeline_add_layer (GESTimeline * timeline, GESTimelineLayer * layer)
289 {
290   GST_DEBUG ("timeline:%p, layer:%p", timeline, layer);
291
292   /* We can only add a layer that doesn't already belong to another timeline */
293   if (G_UNLIKELY (layer->timeline)) {
294     GST_WARNING ("Layer belongs to another timeline, can't add it");
295     return FALSE;
296   }
297
298   /* Add to the list of layers, make sure we don't already control it */
299   if (G_UNLIKELY (g_list_find (timeline->layers, (gconstpointer) layer))) {
300     GST_WARNING ("Layer is already controlled by this timeline");
301     return FALSE;
302   }
303
304   /* Reference is stolen */
305   timeline->layers = g_list_append (timeline->layers, layer);
306
307   /* Inform the layer that it belongs to a new timeline */
308   ges_timeline_layer_set_timeline (layer, timeline);
309
310   /* FIXME : GO OVER THE LIST OF ALREADY EXISTING TIMELINE OBJECTS IN THAT
311    * LAYER AND ADD THEM !!! */
312
313   /* Connect to 'object-added'/'object-removed' signal from the new layer */
314   g_signal_connect (layer, "object-added", G_CALLBACK (layer_object_added_cb),
315       timeline);
316   g_signal_connect (layer, "object-removed",
317       G_CALLBACK (layer_object_removed_cb), timeline);
318
319   GST_DEBUG ("Done adding layer, emitting 'layer-added' signal");
320   g_signal_emit (timeline, ges_timeline_signals[LAYER_ADDED], 0, layer);
321
322   return TRUE;
323 }
324
325 /**
326  * ges_timeline_remove_layer:
327  * @timeline: a #GESTimeline
328  * @layer: the #GESTimelineLayer to remove
329  *
330  * Removes the layer from the timeline. The reference that the @timeline holds on
331  * the layer will be dropped. If you wish to use the @layer after calling this
332  * method, you need to take a reference before calling.
333  *
334  * Returns: TRUE if the layer was properly removed, else FALSE.
335  */
336
337 gboolean
338 ges_timeline_remove_layer (GESTimeline * timeline, GESTimelineLayer * layer)
339 {
340   GST_DEBUG ("timeline:%p, layer:%p", timeline, layer);
341
342   if (G_UNLIKELY (!g_list_find (timeline->layers, layer))) {
343     GST_WARNING ("Layer doesn't belong to this timeline");
344     return FALSE;
345   }
346
347   /* Disconnect signals */
348   GST_DEBUG ("Disconnecting signal callbacks");
349   g_signal_handlers_disconnect_by_func (layer, layer_object_added_cb, timeline);
350   g_signal_handlers_disconnect_by_func (layer, layer_object_removed_cb,
351       timeline);
352
353   timeline->layers = g_list_remove (timeline->layers, layer);
354
355   ges_timeline_layer_set_timeline (layer, NULL);
356
357   g_signal_emit (timeline, ges_timeline_signals[LAYER_REMOVED], 0, layer);
358
359   g_object_unref (layer);
360
361   return TRUE;
362 }
363
364 static void
365 pad_added_cb (GESTrack * track, GstPad * pad, TrackPrivate * priv)
366 {
367   gchar *padname;
368
369   GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
370
371   if (G_UNLIKELY (priv->pad)) {
372     GST_WARNING ("We are already controlling a pad for this track");
373     return;
374   }
375
376   /* Remember the pad */
377   priv->pad = pad;
378
379   /* ghost it ! */
380   GST_DEBUG ("Ghosting pad and adding it to ourself");
381   padname = g_strdup_printf ("track_%p_src", track);
382   priv->ghostpad = gst_ghost_pad_new (padname, pad);
383   g_free (padname);
384   gst_pad_set_active (priv->ghostpad, TRUE);
385   gst_element_add_pad (GST_ELEMENT (priv->timeline), priv->ghostpad);
386 }
387
388 static void
389 pad_removed_cb (GESTrack * track, GstPad * pad, TrackPrivate * priv)
390 {
391   GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
392
393   if (G_UNLIKELY (priv->pad != pad)) {
394     GST_WARNING ("Not the pad we're controlling");
395     return;
396   }
397
398   if (G_UNLIKELY (priv->ghostpad == NULL)) {
399     GST_WARNING ("We don't have a ghostpad for this pad !");
400     return;
401   }
402
403   GST_DEBUG ("Removing ghostpad");
404   gst_pad_set_active (priv->ghostpad, FALSE);
405   gst_element_remove_pad (GST_ELEMENT (priv->timeline), priv->ghostpad);
406   gst_object_unref (priv->ghostpad);
407   priv->ghostpad = NULL;
408   priv->pad = NULL;
409 }
410
411 gint
412 custom_find_track (TrackPrivate * priv, GESTrack * track)
413 {
414   if (priv->track == track)
415     return 0;
416   return -1;
417 }
418
419 /**
420  * ges_timeline_add_track:
421  * @timeline: a #GESTimeline
422  * @track: the #GESTrack to add
423  *
424  * Add a track to the timeline. The reference to the track will be stolen by the
425  * pipeline.
426  *
427  * Returns: TRUE if the track was properly added, else FALSE.
428  */
429
430 gboolean
431 ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
432 {
433   GList *tmp;
434   TrackPrivate *priv;
435
436   GST_DEBUG ("timeline:%p, track:%p", timeline, track);
437
438   /* make sure we don't already control it */
439   if (G_UNLIKELY ((tmp =
440               g_list_find_custom (timeline->tracks, (gconstpointer) track,
441                   (GCompareFunc) custom_find_track)))) {
442     GST_WARNING ("Track is already controlled by this timeline");
443     return FALSE;
444   }
445
446   /* Add the track to ourself (as a GstBin) 
447    * Reference is stolen ! */
448   if (G_UNLIKELY (!gst_bin_add (GST_BIN (timeline), GST_ELEMENT (track)))) {
449     GST_WARNING ("Couldn't add track to ourself (GST)");
450     return FALSE;
451   }
452
453   priv = g_new0 (TrackPrivate, 1);
454   priv->timeline = timeline;
455   priv->track = track;
456
457   /* Add the track to the list of tracks we track */
458   timeline->tracks = g_list_append (timeline->tracks, priv);
459
460   /* Listen to pad-added/-removed */
461   g_signal_connect (track, "pad-added", (GCallback) pad_added_cb, priv);
462   g_signal_connect (track, "pad-removed", (GCallback) pad_removed_cb, priv);
463
464   /* Inform the track that it's currently being used by ourself */
465   ges_track_set_timeline (track, timeline);
466
467   GST_DEBUG ("Done adding track, emitting 'track-added' signal");
468
469   /* emit 'track-added' */
470   g_signal_emit (timeline, ges_timeline_signals[TRACK_ADDED], 0, track);
471
472   return TRUE;
473 }
474
475 /**
476  * ges_timeline_remove_track:
477  * @timeline: a #GESTimeline
478  * @track: the #GESTrack to remove
479  *
480  * Remove the @track from the @timeline. The reference stolen when adding the
481  * @track will be removed. If you wish to use the @track after calling this
482  * function you must ensure that you have a reference to it.
483  *
484  * Returns: TRUE if the @track was properly removed, else FALSE.
485  */
486 gboolean
487 ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track)
488 {
489   GList *tmp;
490   TrackPrivate *priv;
491
492   GST_DEBUG ("timeline:%p, track:%p", timeline, track);
493
494   if (G_UNLIKELY (!(tmp =
495               g_list_find_custom (timeline->tracks, (gconstpointer) track,
496                   (GCompareFunc) custom_find_track)))) {
497     GST_WARNING ("Track doesn't belong to this timeline");
498     return FALSE;
499   }
500
501   priv = tmp->data;
502   timeline->tracks = g_list_remove (timeline->tracks, priv);
503
504   ges_track_set_timeline (track, NULL);
505
506   /* Remove ghost pad */
507   if (priv->ghostpad) {
508     GST_DEBUG ("Removing ghostpad");
509     gst_pad_set_active (priv->ghostpad, FALSE);
510     gst_ghost_pad_set_target ((GstGhostPad *) priv->ghostpad, NULL);
511     gst_element_remove_pad (GST_ELEMENT (timeline), priv->ghostpad);
512   }
513
514   /* Remove pad-added/-removed handlers */
515   g_signal_handlers_disconnect_by_func (track, pad_added_cb, priv);
516   g_signal_handlers_disconnect_by_func (track, pad_removed_cb, priv);
517
518   /* Signal track removal to all layers/objects */
519   g_signal_emit (timeline, ges_timeline_signals[TRACK_REMOVED], 0, track);
520
521   /* remove track from our bin */
522   if (G_UNLIKELY (!gst_bin_remove (GST_BIN (timeline), GST_ELEMENT (track)))) {
523     GST_WARNING ("Couldn't remove track to ourself (GST)");
524     return FALSE;
525   }
526
527   g_free (priv);
528
529   return TRUE;
530 }
531
532 /**
533  * ges_timeline_get_track_for_pad:
534  * @timeline: The #GESTimeline
535  * @pad: The #GstPad
536  *
537  * Search the #GESTrack corresponding to the given @timeline's @pad.
538  *
539  * Returns: The corresponding #GESTrack if it is found, or #NULL if there is
540  * an error.
541  */
542
543 GESTrack *
544 ges_timeline_get_track_for_pad (GESTimeline * timeline, GstPad * pad)
545 {
546   GList *tmp;
547
548   for (tmp = timeline->tracks; tmp; tmp = g_list_next (tmp)) {
549     TrackPrivate *priv = (TrackPrivate *) tmp->data;
550     if (pad == priv->ghostpad)
551       return priv->track;
552   }
553
554   return NULL;
555 }
556
557 /**
558  * ges_timeline_get_tracks:
559  * @timeline: a #GESTimeline
560  *
561  * Returns the list of #GESTrack used by the Timeline.
562  *
563  * Returns: A list of #GESTrack. The caller should unref each track
564  * once he is done with them. */
565 GList *
566 ges_timeline_get_tracks (GESTimeline * timeline)
567 {
568   GList *tmp, *res = NULL;
569
570   for (tmp = timeline->tracks; tmp; tmp = g_list_next (tmp)) {
571     TrackPrivate *priv = (TrackPrivate *) tmp->data;
572     res = g_list_append (res, g_object_ref (priv->track));
573   }
574
575   return res;
576 }