ges/: Fix copyright in headers
[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;
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 = g_list_next (tmp)) {
259     GESTrackObject *trobj = (GESTrackObject *) tmp->data;
260
261     GST_DEBUG ("Trying to remove TrackObject %p", trobj);
262     if (G_LIKELY (g_list_find_custom (timeline->tracks,
263                 (gconstpointer) trobj->track,
264                 (GCompareFunc) custom_find_track))) {
265       GST_DEBUG ("Belongs to one of the tracks we control");
266       ges_track_remove_object (trobj->track, trobj);
267
268       ges_timeline_object_release_track_object (object, trobj);
269     }
270   }
271
272   GST_DEBUG ("Done");
273 }
274
275 /**
276  * ges_timeline_add_layer:
277  * @timeline: a #GESTimeline
278  * @layer: the #GESTimelineLayer to add
279  *
280  * Add the layer to the timeline. The reference to the @layer will be stolen
281  * by the @timeline.
282  *
283  * Returns: TRUE if the layer was properly added, else FALSE.
284  */
285 gboolean
286 ges_timeline_add_layer (GESTimeline * timeline, GESTimelineLayer * layer)
287 {
288   GST_DEBUG ("timeline:%p, layer:%p", timeline, layer);
289
290   /* We can only add a layer that doesn't already belong to another timeline */
291   if (G_UNLIKELY (layer->timeline)) {
292     GST_WARNING ("Layer belongs to another timeline, can't add it");
293     return FALSE;
294   }
295
296   /* Add to the list of layers, make sure we don't already control it */
297   if (G_UNLIKELY (g_list_find (timeline->layers, (gconstpointer) layer))) {
298     GST_WARNING ("Layer is already controlled by this timeline");
299     return FALSE;
300   }
301
302   /* Reference is stolen */
303   timeline->layers = g_list_append (timeline->layers, layer);
304
305   /* Inform the layer that it belongs to a new timeline */
306   ges_timeline_layer_set_timeline (layer, timeline);
307
308   /* FIXME : GO OVER THE LIST OF ALREADY EXISTING TIMELINE OBJECTS IN THAT
309    * LAYER AND ADD THEM !!! */
310
311   /* Connect to 'object-added'/'object-removed' signal from the new layer */
312   g_signal_connect (layer, "object-added", G_CALLBACK (layer_object_added_cb),
313       timeline);
314   g_signal_connect (layer, "object-removed",
315       G_CALLBACK (layer_object_removed_cb), timeline);
316
317   GST_DEBUG ("Done adding layer, emitting 'layer-added' signal");
318   g_signal_emit (timeline, ges_timeline_signals[LAYER_ADDED], 0, layer);
319
320   return TRUE;
321 }
322
323 /**
324  * ges_timeline_remove_layer:
325  * @timeline: a #GESTimeline
326  * @layer: the #GESTimelineLayer to remove
327  *
328  * Removes the layer from the timeline. The reference that the @timeline holds on
329  * the layer will be dropped. If you wish to use the @layer after calling this
330  * method, you need to take a reference before calling.
331  *
332  * Returns: TRUE if the layer was properly removed, else FALSE.
333  */
334
335 gboolean
336 ges_timeline_remove_layer (GESTimeline * timeline, GESTimelineLayer * layer)
337 {
338   GST_DEBUG ("timeline:%p, layer:%p", timeline, layer);
339
340   if (G_UNLIKELY (!g_list_find (timeline->layers, layer))) {
341     GST_WARNING ("Layer doesn't belong to this timeline");
342     return FALSE;
343   }
344
345   /* Disconnect signals */
346   GST_DEBUG ("Disconnecting signal callbacks");
347   g_signal_handlers_disconnect_by_func (layer, layer_object_added_cb, timeline);
348   g_signal_handlers_disconnect_by_func (layer, layer_object_removed_cb,
349       timeline);
350
351   timeline->layers = g_list_remove (timeline->layers, layer);
352
353   ges_timeline_layer_set_timeline (layer, NULL);
354
355   g_signal_emit (timeline, ges_timeline_signals[LAYER_REMOVED], 0, layer);
356
357   g_object_unref (layer);
358
359   return TRUE;
360 }
361
362 static void
363 pad_added_cb (GESTrack * track, GstPad * pad, TrackPrivate * priv)
364 {
365   gchar *padname;
366
367   GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
368
369   if (G_UNLIKELY (priv->pad)) {
370     GST_WARNING ("We are already controlling a pad for this track");
371     return;
372   }
373
374   /* Remember the pad */
375   priv->pad = pad;
376
377   /* ghost it ! */
378   GST_DEBUG ("Ghosting pad and adding it to ourself");
379   padname = g_strdup_printf ("track_%p_src", track);
380   priv->ghostpad = gst_ghost_pad_new (padname, pad);
381   g_free (padname);
382   gst_pad_set_active (priv->ghostpad, TRUE);
383   gst_element_add_pad (GST_ELEMENT (priv->timeline), priv->ghostpad);
384 }
385
386 static void
387 pad_removed_cb (GESTrack * track, GstPad * pad, TrackPrivate * priv)
388 {
389   GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
390
391   if (G_UNLIKELY (priv->pad != pad)) {
392     GST_WARNING ("Not the pad we're controlling");
393     return;
394   }
395
396   if (G_UNLIKELY (priv->ghostpad == NULL)) {
397     GST_WARNING ("We don't have a ghostpad for this pad !");
398     return;
399   }
400
401   GST_DEBUG ("Removing ghostpad");
402   gst_pad_set_active (priv->ghostpad, FALSE);
403   gst_element_remove_pad (GST_ELEMENT (priv->timeline), priv->ghostpad);
404   gst_object_unref (priv->ghostpad);
405   priv->ghostpad = NULL;
406   priv->pad = NULL;
407 }
408
409 gint
410 custom_find_track (TrackPrivate * priv, GESTrack * track)
411 {
412   if (priv->track == track)
413     return 0;
414   return -1;
415 }
416
417 /**
418  * ges_timeline_add_track:
419  * @timeline: a #GESTimeline
420  * @track: the #GESTrack to add
421  *
422  * Add a track to the timeline. The reference to the track will be stolen by the
423  * pipeline.
424  *
425  * Returns: TRUE if the track was properly added, else FALSE.
426  */
427
428 gboolean
429 ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
430 {
431   GList *tmp;
432   TrackPrivate *priv;
433
434   GST_DEBUG ("timeline:%p, track:%p", timeline, track);
435
436   /* make sure we don't already control it */
437   if (G_UNLIKELY ((tmp =
438               g_list_find_custom (timeline->tracks, (gconstpointer) track,
439                   (GCompareFunc) custom_find_track)))) {
440     GST_WARNING ("Track is already controlled by this timeline");
441     return FALSE;
442   }
443
444   /* Add the track to ourself (as a GstBin) 
445    * Reference is stolen ! */
446   if (G_UNLIKELY (!gst_bin_add (GST_BIN (timeline), GST_ELEMENT (track)))) {
447     GST_WARNING ("Couldn't add track to ourself (GST)");
448     return FALSE;
449   }
450
451   priv = g_new0 (TrackPrivate, 1);
452   priv->timeline = timeline;
453   priv->track = track;
454
455   /* Add the track to the list of tracks we track */
456   timeline->tracks = g_list_append (timeline->tracks, priv);
457
458   /* Listen to pad-added/-removed */
459   g_signal_connect (track, "pad-added", (GCallback) pad_added_cb, priv);
460   g_signal_connect (track, "pad-removed", (GCallback) pad_removed_cb, priv);
461
462   /* Inform the track that it's currently being used by ourself */
463   ges_track_set_timeline (track, timeline);
464
465   GST_DEBUG ("Done adding track, emitting 'track-added' signal");
466
467   /* emit 'track-added' */
468   g_signal_emit (timeline, ges_timeline_signals[TRACK_ADDED], 0, track);
469
470   return TRUE;
471 }
472
473 /**
474  * ges_timeline_remove_track:
475  * @timeline: a #GESTimeline
476  * @track: the #GESTrack to remove
477  *
478  * Remove the @track from the @timeline. The reference stolen when adding the
479  * @track will be removed. If you wish to use the @track after calling this
480  * function you must ensure that you have a reference to it.
481  *
482  * Returns: TRUE if the @track was properly removed, else FALSE.
483  */
484 gboolean
485 ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track)
486 {
487   GList *tmp;
488   TrackPrivate *priv;
489
490   GST_DEBUG ("timeline:%p, track:%p", timeline, track);
491
492   if (G_UNLIKELY (!(tmp =
493               g_list_find_custom (timeline->tracks, (gconstpointer) track,
494                   (GCompareFunc) custom_find_track)))) {
495     GST_WARNING ("Track doesn't belong to this timeline");
496     return FALSE;
497   }
498
499   priv = tmp->data;
500   timeline->tracks = g_list_remove (timeline->tracks, priv);
501
502   ges_track_set_timeline (track, NULL);
503
504   /* Remove ghost pad */
505   if (priv->ghostpad) {
506     GST_DEBUG ("Removing ghostpad");
507     gst_pad_set_active (priv->ghostpad, FALSE);
508     gst_ghost_pad_set_target ((GstGhostPad *) priv->ghostpad, NULL);
509     gst_element_remove_pad (GST_ELEMENT (timeline), priv->ghostpad);
510   }
511
512   /* Remove pad-added/-removed handlers */
513   g_signal_handlers_disconnect_by_func (track, pad_added_cb, priv);
514   g_signal_handlers_disconnect_by_func (track, pad_removed_cb, priv);
515
516   /* Signal track removal to all layers/objects */
517   g_signal_emit (timeline, ges_timeline_signals[TRACK_REMOVED], 0, track);
518
519   /* remove track from our bin */
520   if (G_UNLIKELY (!gst_bin_remove (GST_BIN (timeline), GST_ELEMENT (track)))) {
521     GST_WARNING ("Couldn't remove track to ourself (GST)");
522     return FALSE;
523   }
524
525   g_free (priv);
526
527   return TRUE;
528 }
529
530 /**
531  * ges_timeline_get_track_for_pad:
532  * @timeline: The #GESTimeline
533  * @pad: The #GstPad
534  *
535  * Search the #GESTrack corresponding to the given @timeline's @pad.
536  *
537  * Returns: The corresponding #GESTrack if it is found, or #NULL if there is
538  * an error.
539  */
540
541 GESTrack *
542 ges_timeline_get_track_for_pad (GESTimeline * timeline, GstPad * pad)
543 {
544   GList *tmp;
545
546   for (tmp = timeline->tracks; tmp; tmp = g_list_next (tmp)) {
547     TrackPrivate *priv = (TrackPrivate *) tmp->data;
548     if (pad == priv->ghostpad)
549       return priv->track;
550   }
551
552   return NULL;
553 }