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