timeline: Added timeline and mode as properties
[platform/upstream/gstreamer.git] / ges / ges-timeline-pipeline.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-pipeline
23  * @short_description: Convenience #GstPipeline for editing.
24  *
25  * #GESTimelinePipeline allows developers to view and render #GESTimeline
26  * in a simple fashion.
27  * Its usage is inspired by the 'playbin' element from gst-plugins-base.
28  */
29
30 #include <gst/gst.h>
31 #include <stdio.h>
32 #include "ges-internal.h"
33 #include "ges-timeline-pipeline.h"
34 #include "ges-screenshot.h"
35
36 #define DEFAULT_TIMELINE_MODE  TIMELINE_MODE_PREVIEW
37
38 /* Structure corresponding to a timeline - sink link */
39
40 typedef struct
41 {
42   GESTrack *track;
43   GstElement *tee;
44   GstPad *srcpad;               /* Timeline source pad */
45   GstPad *playsinkpad;
46   GstPad *encodebinpad;
47   GstPad *blocked_pad;
48   gulong probe_id;
49 } OutputChain;
50
51 G_DEFINE_TYPE (GESTimelinePipeline, ges_timeline_pipeline, GST_TYPE_PIPELINE);
52
53 struct _GESTimelinePipelinePrivate
54 {
55   GESTimeline *timeline;
56   GstElement *playsink;
57   GstElement *encodebin;
58   /* Note : urisink is only created when a URI has been provided */
59   GstElement *urisink;
60
61   GESPipelineFlags mode;
62
63   GList *chains;
64
65   GstEncodingProfile *profile;
66 };
67
68 enum
69 {
70   PROP_0,
71   PROP_AUDIO_SINK,
72   PROP_VIDEO_SINK,
73   PROP_TIMELINE,
74   PROP_MODE,
75   PROP_LAST
76 };
77
78 static GParamSpec *properties[PROP_LAST];
79
80 static GstStateChangeReturn ges_timeline_pipeline_change_state (GstElement *
81     element, GstStateChange transition);
82
83 static OutputChain *get_output_chain_for_track (GESTimelinePipeline * self,
84     GESTrack * track);
85 static OutputChain *new_output_chain_for_track (GESTimelinePipeline * self,
86     GESTrack * track);
87 static gboolean play_sink_multiple_seeks_send_event (GstElement * element,
88     GstEvent * event);
89
90 static void
91 ges_timeline_pipeline_get_property (GObject * object, guint property_id,
92     GValue * value, GParamSpec * pspec)
93 {
94   GESTimelinePipeline *self = GES_TIMELINE_PIPELINE (object);
95
96   switch (property_id) {
97     case PROP_AUDIO_SINK:
98       g_object_get_property (G_OBJECT (self->priv->playsink), "audio-sink",
99                              value);
100       break;
101     case PROP_VIDEO_SINK:
102       g_object_get_property (G_OBJECT (self->priv->playsink), "video-sink",
103                              value);
104       break;
105     case PROP_TIMELINE:
106       g_value_set_object (value, self->priv->timeline);
107       break;
108     case PROP_MODE:
109       g_value_set_flags (value, self->priv->mode);
110       break;
111     default:
112       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
113   }
114 }
115
116 static void
117 ges_timeline_pipeline_set_property (GObject * object, guint property_id,
118     const GValue * value, GParamSpec * pspec)
119 {
120   GESTimelinePipeline *self = GES_TIMELINE_PIPELINE (object);
121
122   switch (property_id) {
123     case PROP_AUDIO_SINK:
124       g_object_set_property (G_OBJECT (self->priv->playsink), "audio-sink",
125                              value);
126       break;
127     case PROP_VIDEO_SINK:
128       g_object_set_property (G_OBJECT (self->priv->playsink), "video-sink",
129                              value);
130       break;
131     case PROP_TIMELINE:
132       ges_timeline_pipeline_add_timeline (GES_TIMELINE_PIPELINE (object),
133                                           g_value_get_object(value));
134       break;
135     case PROP_MODE:
136       ges_timeline_pipeline_set_mode (GES_TIMELINE_PIPELINE (object),
137                                       g_value_get_flags(value));
138       break;
139     default:
140       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
141   }
142 }
143
144 static void
145 ges_timeline_pipeline_dispose (GObject * object)
146 {
147   GESTimelinePipeline *self = GES_TIMELINE_PIPELINE (object);
148
149   if (self->priv->playsink) {
150     if (self->priv->mode & (TIMELINE_MODE_PREVIEW))
151       gst_bin_remove (GST_BIN (object), self->priv->playsink);
152     else
153       gst_object_unref (self->priv->playsink);
154     self->priv->playsink = NULL;
155   }
156
157   if (self->priv->encodebin) {
158     if (self->priv->mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))
159       gst_bin_remove (GST_BIN (object), self->priv->encodebin);
160     else
161       gst_object_unref (self->priv->encodebin);
162     self->priv->encodebin = NULL;
163   }
164
165   if (self->priv->profile) {
166     gst_encoding_profile_unref (self->priv->profile);
167     self->priv->profile = NULL;
168   }
169
170   G_OBJECT_CLASS (ges_timeline_pipeline_parent_class)->dispose (object);
171 }
172
173 static void
174 ges_timeline_pipeline_class_init (GESTimelinePipelineClass * klass)
175 {
176   GObjectClass *object_class = G_OBJECT_CLASS (klass);
177   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
178
179   g_type_class_add_private (klass, sizeof (GESTimelinePipelinePrivate));
180
181   object_class->dispose = ges_timeline_pipeline_dispose;
182   object_class->get_property = ges_timeline_pipeline_get_property;
183   object_class->set_property = ges_timeline_pipeline_set_property;
184
185   /**
186    * GESTimelinePipeline:audio-sink
187    *
188    * Audio sink for the preview.
189    */
190   properties[PROP_AUDIO_SINK] = g_param_spec_object ("audio-sink", "Audio Sink",
191       "Audio sink for the preview.",
192       GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
193   g_object_class_install_property (object_class, PROP_AUDIO_SINK,
194       properties[PROP_AUDIO_SINK]);
195
196   /**
197    * GESTimelinePipeline:video-sink
198    *
199    * Video sink for the preview.
200    */
201   properties[PROP_VIDEO_SINK] = g_param_spec_object ("video-sink", "Video Sink",
202       "Video sink for the preview.",
203       GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
204   g_object_class_install_property (object_class, PROP_VIDEO_SINK,
205       properties[PROP_VIDEO_SINK]);
206
207   /**
208    * GESTimelinePipeline:timeline
209    *
210    * Timeline to use in this pipeline. See also
211    * ges_timeline_pipeline_add_timeline() for more info.
212    */
213   properties[PROP_TIMELINE] = g_param_spec_object ("timeline", "Timeline",
214       "Timeline to use in this pipeline. See also "
215       "ges_timeline_pipeline_add_timeline() for more info.",
216       GES_TYPE_TIMELINE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
217   g_object_class_install_property (object_class, PROP_TIMELINE,
218       properties[PROP_TIMELINE]);
219
220   /**
221    * GESTimelinePipeline:mode
222    *
223    * Pipeline mode. See ges_timeline_pipeline_set_mode() for more
224    * info.
225    */
226   properties[PROP_MODE] = g_param_spec_flags ("mode", "Mode",
227       "Pipeline mode. See ges_timeline_pipeline_set_mode() for more info.",
228       GES_TYPE_PIPELINE_FLAGS, DEFAULT_TIMELINE_MODE,
229       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
230   g_object_class_install_property (object_class, PROP_MODE,
231       properties[PROP_MODE]);
232
233   element_class->change_state =
234       GST_DEBUG_FUNCPTR (ges_timeline_pipeline_change_state);
235
236   /* TODO : Add state_change handlers
237    * Don't change state if we don't have a timeline */
238 }
239
240 static void
241 ges_timeline_pipeline_init (GESTimelinePipeline * self)
242 {
243   GstElementClass *playsinkclass;
244
245   GST_INFO_OBJECT (self, "Creating new 'playsink'");
246   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
247       GES_TYPE_TIMELINE_PIPELINE, GESTimelinePipelinePrivate);
248
249   self->priv->playsink =
250       gst_element_factory_make ("playsink", "internal-sinks");
251   self->priv->encodebin =
252       gst_element_factory_make ("encodebin", "internal-encodebin");
253   /* Limit encodebin buffering to 1 buffer since we know the various
254    * stream fed to it are decoupled already */
255   g_object_set (self->priv->encodebin, "queue-buffers-max", (guint) 1,
256       "queue-bytes-max", (guint) 0, "queue-time-max", (guint64) 0,
257       "avoid-reencoding", TRUE, NULL);
258
259   if (G_UNLIKELY (self->priv->playsink == NULL))
260     goto no_playsink;
261   if (G_UNLIKELY (self->priv->encodebin == NULL))
262     goto no_encodebin;
263
264   /* TODO : Remove this hack once we depend on gst-p-base 0.10.37 */
265   /* HACK : Intercept events going through playsink */
266   playsinkclass = GST_ELEMENT_GET_CLASS (self->priv->playsink);
267   /* Replace playsink's GstBin::send_event with our own */
268   playsinkclass->send_event = play_sink_multiple_seeks_send_event;
269
270   ges_timeline_pipeline_set_mode (self, DEFAULT_TIMELINE_MODE);
271
272   return;
273
274 no_playsink:
275   {
276     GST_ERROR_OBJECT (self, "Can't create playsink instance !");
277     return;
278   }
279 no_encodebin:
280   {
281     GST_ERROR_OBJECT (self, "Can't create encodebin instance !");
282     return;
283   }
284 }
285
286 /**
287  * ges_timeline_pipeline_new:
288  *
289  * Creates a new conveninence #GESTimelinePipeline.
290  *
291  * Returns: the new #GESTimelinePipeline.
292  */
293 GESTimelinePipeline *
294 ges_timeline_pipeline_new (void)
295 {
296   return g_object_new (GES_TYPE_TIMELINE_PIPELINE, NULL);
297 }
298
299 #define TRACK_COMPATIBLE_PROFILE(tracktype, profile)                    \
300   ( (GST_IS_ENCODING_AUDIO_PROFILE (profile) && (tracktype) == GES_TRACK_TYPE_AUDIO) || \
301     (GST_IS_ENCODING_VIDEO_PROFILE (profile) && (tracktype) == GES_TRACK_TYPE_VIDEO))
302
303 static gboolean
304 ges_timeline_pipeline_update_caps (GESTimelinePipeline * self)
305 {
306   GList *ltrack, *tracks, *lstream;
307
308   if (!self->priv->profile)
309     return TRUE;
310
311   GST_DEBUG ("Updating track caps");
312
313   tracks = ges_timeline_get_tracks (self->priv->timeline);
314
315   /* Take each stream of the encoding profile and find a matching
316    * track to set the caps on */
317   for (ltrack = tracks; ltrack; ltrack = ltrack->next) {
318     GESTrack *track = (GESTrack *) ltrack->data;
319     GList *allstreams;
320
321     allstreams = (GList *)
322         gst_encoding_container_profile_get_profiles (
323         (GstEncodingContainerProfile *) self->priv->profile);
324
325     /* Find a matching stream setting */
326     for (lstream = allstreams; lstream; lstream = lstream->next) {
327       GstEncodingProfile *prof = (GstEncodingProfile *) lstream->data;
328
329       if (TRACK_COMPATIBLE_PROFILE (track->type, prof)) {
330         if (self->priv->mode == TIMELINE_MODE_SMART_RENDER) {
331           GstCaps *ocaps, *rcaps;
332
333           GST_DEBUG ("Smart Render mode, setting input caps");
334           ocaps = gst_encoding_profile_get_input_caps (prof);
335           if (track->type == GES_TRACK_TYPE_AUDIO)
336             rcaps = gst_caps_new_empty_simple ("audio/x-raw");
337           else
338             rcaps = gst_caps_new_empty_simple ("video/x-raw");
339           gst_caps_append (ocaps, rcaps);
340           ges_track_set_caps (track, ocaps);
341         } else {
342           GstCaps *caps = NULL;
343
344           /* Raw preview or rendering mode */
345           if (track->type == GES_TRACK_TYPE_VIDEO)
346             caps = gst_caps_new_empty_simple ("video/x-raw");
347           else if (track->type == GES_TRACK_TYPE_AUDIO)
348             caps = gst_caps_new_empty_simple ("audio/x-raw");
349
350           if (caps) {
351             ges_track_set_caps (track, caps);
352             gst_caps_unref (caps);
353           }
354         }
355         break;
356       }
357     }
358
359     g_object_unref (track);
360   }
361
362   if (tracks)
363     g_list_free (tracks);
364
365   GST_DEBUG ("Done updating caps");
366
367   return TRUE;
368 }
369
370 static GstStateChangeReturn
371 ges_timeline_pipeline_change_state (GstElement * element,
372     GstStateChange transition)
373 {
374   GESTimelinePipeline *self;
375   GstStateChangeReturn ret;
376
377   self = GES_TIMELINE_PIPELINE (element);
378
379   switch (transition) {
380     case GST_STATE_CHANGE_READY_TO_PAUSED:
381       if (G_UNLIKELY (self->priv->timeline == NULL)) {
382         GST_ERROR_OBJECT (element,
383             "No GESTimeline set on the pipeline, cannot play !");
384         ret = GST_STATE_CHANGE_FAILURE;
385         goto done;
386       }
387       if (self->priv->
388           mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))
389         GST_DEBUG ("rendering => Updating pipeline caps");
390       if (!ges_timeline_pipeline_update_caps (self)) {
391         GST_ERROR_OBJECT (element, "Error setting the caps for rendering");
392         ret = GST_STATE_CHANGE_FAILURE;
393         goto done;
394       }
395       /* Set caps on all tracks according to profile if present */
396       /* FIXME : Add a new SMART_RENDER mode to avoid decoding */
397       break;
398     default:
399       break;
400   }
401
402   ret =
403       GST_ELEMENT_CLASS (ges_timeline_pipeline_parent_class)->change_state
404       (element, transition);
405
406 done:
407   return ret;
408 }
409
410 static OutputChain *
411 new_output_chain_for_track (GESTimelinePipeline * self, GESTrack * track)
412 {
413   OutputChain *chain;
414
415   chain = g_new0 (OutputChain, 1);
416   chain->track = track;
417
418   return chain;
419 }
420
421 static OutputChain *
422 get_output_chain_for_track (GESTimelinePipeline * self, GESTrack * track)
423 {
424   GList *tmp;
425
426   for (tmp = self->priv->chains; tmp; tmp = tmp->next) {
427     OutputChain *chain = (OutputChain *) tmp->data;
428     if (chain->track == track)
429       return chain;
430   }
431
432   return NULL;
433 }
434
435 /* Fetches a compatible pad on the target element which isn't already
436  * linked */
437 static GstPad *
438 get_compatible_unlinked_pad (GstElement * element, GstPad * pad)
439 {
440   GstPad *res = NULL;
441   GstIterator *pads;
442   gboolean done = FALSE;
443   GstCaps *srccaps;
444   GValue paditem = { 0, };
445
446   if (G_UNLIKELY (pad == NULL))
447     goto no_pad;
448
449   GST_DEBUG ("element : %s, pad %s:%s",
450       GST_ELEMENT_NAME (element), GST_DEBUG_PAD_NAME (pad));
451
452   if (GST_PAD_DIRECTION (pad) == GST_PAD_SRC)
453     pads = gst_element_iterate_sink_pads (element);
454   else
455     pads = gst_element_iterate_src_pads (element);
456   srccaps = gst_pad_query_caps (pad, NULL);
457
458   GST_DEBUG ("srccaps %" GST_PTR_FORMAT, srccaps);
459
460   while (!done) {
461     switch (gst_iterator_next (pads, &paditem)) {
462       case GST_ITERATOR_OK:
463       {
464         GstPad *testpad = g_value_get_object (&paditem);
465
466         if (!gst_pad_is_linked (testpad)) {
467           GstCaps *sinkcaps = gst_pad_query_caps (testpad, NULL);
468
469           GST_DEBUG ("sinkccaps %" GST_PTR_FORMAT, sinkcaps);
470
471           if (gst_caps_can_intersect (srccaps, sinkcaps)) {
472             res = gst_object_ref (testpad);
473             done = TRUE;
474           }
475           gst_caps_unref (sinkcaps);
476         }
477         g_value_reset (&paditem);
478       }
479         break;
480       case GST_ITERATOR_DONE:
481       case GST_ITERATOR_ERROR:
482         done = TRUE;
483         break;
484       case GST_ITERATOR_RESYNC:
485         gst_iterator_resync (pads);
486         break;
487     }
488   }
489   g_value_reset (&paditem);
490   gst_iterator_free (pads);
491   gst_caps_unref (srccaps);
492
493   return res;
494
495 no_pad:
496   {
497     GST_ERROR ("No pad to check against");
498     return NULL;
499   }
500 }
501
502 static GstPadProbeReturn
503 pad_blocked (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
504 {
505   /* no nothing */
506   GST_DEBUG_OBJECT (pad, "blocked callback, blocked");
507   return GST_PAD_PROBE_OK;
508 }
509
510 static void
511 pad_added_cb (GstElement * timeline, GstPad * pad, GESTimelinePipeline * self)
512 {
513   OutputChain *chain;
514   GESTrack *track;
515   GstPad *sinkpad;
516   GstCaps *caps;
517   gboolean reconfigured = FALSE;
518
519   caps = gst_pad_query_caps (pad, NULL);
520
521   GST_DEBUG_OBJECT (self, "new pad %s:%s , caps:%" GST_PTR_FORMAT,
522       GST_DEBUG_PAD_NAME (pad), caps);
523
524   gst_caps_unref (caps);
525
526   track = ges_timeline_get_track_for_pad (self->priv->timeline, pad);
527
528   if (G_UNLIKELY (!track)) {
529     GST_WARNING_OBJECT (self, "Couldn't find coresponding track !");
530     return;
531   }
532
533   /* Don't connect track if it's not going to be used */
534   if (track->type == GES_TRACK_TYPE_VIDEO &&
535       !(self->priv->mode & TIMELINE_MODE_PREVIEW_VIDEO) &&
536       !(self->priv->mode & TIMELINE_MODE_RENDER) &&
537       !(self->priv->mode & TIMELINE_MODE_SMART_RENDER)) {
538     GST_DEBUG_OBJECT (self, "Video track... but we don't need it. Not linking");
539   }
540   if (track->type == GES_TRACK_TYPE_AUDIO &&
541       !(self->priv->mode & TIMELINE_MODE_PREVIEW_AUDIO) &&
542       !(self->priv->mode & TIMELINE_MODE_RENDER) &&
543       !(self->priv->mode & TIMELINE_MODE_SMART_RENDER)) {
544     GST_DEBUG_OBJECT (self, "Audio track... but we don't need it. Not linking");
545   }
546
547   /* Get an existing chain or create it */
548   if (!(chain = get_output_chain_for_track (self, track)))
549     chain = new_output_chain_for_track (self, track);
550   chain->srcpad = pad;
551
552   /* Adding tee */
553   chain->tee = gst_element_factory_make ("tee", NULL);
554   gst_bin_add (GST_BIN_CAST (self), chain->tee);
555   gst_element_sync_state_with_parent (chain->tee);
556
557   /* Linking pad to tee */
558   sinkpad = gst_element_get_static_pad (chain->tee, "sink");
559   gst_pad_link_full (pad, sinkpad, GST_PAD_LINK_CHECK_NOTHING);
560   gst_object_unref (sinkpad);
561
562   /* Connect playsink */
563   if (self->priv->mode & TIMELINE_MODE_PREVIEW) {
564     const gchar *sinkpad_name;
565     GstPad *tmppad;
566
567     GST_DEBUG_OBJECT (self, "Connecting to playsink");
568
569     switch (track->type) {
570       case GES_TRACK_TYPE_VIDEO:
571         sinkpad_name = "video_sink";
572         break;
573       case GES_TRACK_TYPE_AUDIO:
574         sinkpad_name = "audio_sink";
575         break;
576       case GES_TRACK_TYPE_TEXT:
577         sinkpad_name = "text_sink";
578         break;
579       default:
580         GST_WARNING_OBJECT (self, "Can't handle tracks of type %d yet",
581             track->type);
582         goto error;
583     }
584
585     /* Request a sinkpad from playsink */
586     if (G_UNLIKELY (!(sinkpad =
587                 gst_element_get_request_pad (self->priv->playsink,
588                     sinkpad_name)))) {
589       GST_ERROR_OBJECT (self, "Couldn't get a pad from the playsink !");
590       goto error;
591     }
592
593     tmppad = gst_element_get_request_pad (chain->tee, "src_%u");
594     if (G_UNLIKELY (gst_pad_link_full (tmppad, sinkpad,
595                 GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) {
596       GST_ERROR_OBJECT (self, "Couldn't link track pad to playsink");
597       gst_object_unref (tmppad);
598       goto error;
599     }
600     chain->blocked_pad = tmppad;
601     GST_DEBUG_OBJECT (tmppad, "blocking pad");
602     chain->probe_id = gst_pad_add_probe (tmppad,
603         GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, pad_blocked, NULL, NULL);
604
605     GST_DEBUG ("Reconfiguring playsink");
606
607     /* reconfigure playsink */
608     g_signal_emit_by_name (self->priv->playsink, "reconfigure", &reconfigured);
609     GST_DEBUG ("'reconfigure' returned %d", reconfigured);
610
611     /* We still hold a reference on the sinkpad */
612     chain->playsinkpad = sinkpad;
613   }
614
615   /* Connect to encodebin */
616   if (self->priv->mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER)) {
617     GstPad *tmppad;
618     GST_DEBUG_OBJECT (self, "Connecting to encodebin");
619
620     if (!chain->encodebinpad) {
621       /* Check for unused static pads */
622       sinkpad = get_compatible_unlinked_pad (self->priv->encodebin, pad);
623
624       if (sinkpad == NULL) {
625         GstCaps *caps = gst_pad_query_caps (pad, NULL);
626
627         /* If no compatible static pad is available, request a pad */
628         g_signal_emit_by_name (self->priv->encodebin, "request-pad", caps,
629             &sinkpad);
630         gst_caps_unref (caps);
631
632         if (G_UNLIKELY (sinkpad == NULL)) {
633           GST_ERROR_OBJECT (self, "Couldn't get a pad from encodebin !");
634           goto error;
635         }
636       }
637       chain->encodebinpad = sinkpad;
638     }
639
640     tmppad = gst_element_get_request_pad (chain->tee, "src_%u");
641     if (G_UNLIKELY (gst_pad_link_full (tmppad,
642                 chain->encodebinpad,
643                 GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) {
644       GST_WARNING_OBJECT (self, "Couldn't link track pad to playsink");
645       goto error;
646     }
647     gst_object_unref (tmppad);
648
649   }
650
651   /* If chain wasn't already present, insert it in list */
652   if (!get_output_chain_for_track (self, track))
653     self->priv->chains = g_list_append (self->priv->chains, chain);
654
655   GST_DEBUG ("done");
656   return;
657
658 error:
659   {
660     if (chain->tee) {
661       gst_bin_remove (GST_BIN_CAST (self), chain->tee);
662     }
663     if (sinkpad)
664       gst_object_unref (sinkpad);
665     g_free (chain);
666   }
667 }
668
669 static void
670 pad_removed_cb (GstElement * timeline, GstPad * pad, GESTimelinePipeline * self)
671 {
672   OutputChain *chain;
673   GESTrack *track;
674   GstPad *peer;
675
676   GST_DEBUG_OBJECT (self, "pad removed %s:%s", GST_DEBUG_PAD_NAME (pad));
677
678   if (G_UNLIKELY (!(track =
679               ges_timeline_get_track_for_pad (self->priv->timeline, pad)))) {
680     GST_WARNING_OBJECT (self, "Couldn't find coresponding track !");
681     return;
682   }
683
684   if (G_UNLIKELY (!(chain = get_output_chain_for_track (self, track)))) {
685     GST_DEBUG_OBJECT (self, "Pad wasn't used");
686     return;
687   }
688
689   /* Unlink encodebin */
690   if (chain->encodebinpad) {
691     peer = gst_pad_get_peer (chain->encodebinpad);
692     gst_pad_unlink (peer, chain->encodebinpad);
693     gst_object_unref (peer);
694     gst_element_release_request_pad (self->priv->encodebin,
695         chain->encodebinpad);
696   }
697
698   /* Unlink playsink */
699   if (chain->playsinkpad) {
700     peer = gst_pad_get_peer (chain->playsinkpad);
701     gst_pad_unlink (peer, chain->playsinkpad);
702     gst_object_unref (peer);
703     gst_element_release_request_pad (self->priv->playsink, chain->playsinkpad);
704     gst_object_unref (chain->playsinkpad);
705   }
706
707   if (chain->blocked_pad) {
708     GST_DEBUG_OBJECT (chain->blocked_pad, "unblocking pad");
709     gst_pad_remove_probe (chain->blocked_pad, chain->probe_id);
710     gst_object_unref (chain->blocked_pad);
711     chain->blocked_pad = NULL;
712     chain->probe_id = 0;
713   }
714
715   /* Unlike/remove tee */
716   peer = gst_element_get_static_pad (chain->tee, "sink");
717   gst_pad_unlink (pad, peer);
718   gst_object_unref (peer);
719   gst_element_set_state (chain->tee, GST_STATE_NULL);
720   gst_bin_remove (GST_BIN (self), chain->tee);
721
722   self->priv->chains = g_list_remove (self->priv->chains, chain);
723   g_free (chain);
724
725   GST_DEBUG ("done");
726 }
727
728 static void
729 no_more_pads_cb (GstElement * timeline, GESTimelinePipeline * self)
730 {
731   GList *tmp;
732
733   GST_DEBUG ("received no-more-pads");
734   for (tmp = self->priv->chains; tmp; tmp = g_list_next (tmp)) {
735     OutputChain *chain = (OutputChain *) tmp->data;
736
737     if (chain->blocked_pad) {
738       GST_DEBUG_OBJECT (chain->blocked_pad, "unblocking pad");
739       gst_pad_remove_probe (chain->blocked_pad, chain->probe_id);
740       gst_object_unref (chain->blocked_pad);
741       chain->blocked_pad = NULL;
742       chain->probe_id = 0;
743     }
744   }
745 }
746
747 /**
748  * ges_timeline_pipeline_add_timeline:
749  * @pipeline: a #GESTimelinePipeline
750  * @timeline: the #GESTimeline to set on the @pipeline.
751  *
752  * Sets the timeline to use in this pipeline.
753  *
754  * The reference to the @timeline will be stolen by the @pipeline.
755  *
756  * Returns: TRUE if the @timeline could be successfully set on the @pipeline,
757  * else FALSE.
758  */
759 gboolean
760 ges_timeline_pipeline_add_timeline (GESTimelinePipeline * pipeline,
761     GESTimeline * timeline)
762 {
763   g_return_val_if_fail (pipeline->priv->timeline == NULL, FALSE);
764   g_return_val_if_fail (timeline != NULL, FALSE);
765
766   GST_DEBUG ("pipeline:%p, timeline:%p", timeline, pipeline);
767
768   if (G_UNLIKELY (!gst_bin_add (GST_BIN_CAST (pipeline),
769               GST_ELEMENT (timeline)))) {
770     return FALSE;
771   }
772   pipeline->priv->timeline = timeline;
773
774   /* Connect to pipeline */
775   g_signal_connect (timeline, "pad-added", (GCallback) pad_added_cb, pipeline);
776   g_signal_connect (timeline, "pad-removed", (GCallback) pad_removed_cb,
777       pipeline);
778   g_signal_connect (timeline, "no-more-pads", (GCallback) no_more_pads_cb,
779       pipeline);
780
781   return TRUE;
782 }
783
784 /**
785  * ges_timeline_pipeline_set_render_settings:
786  * @pipeline: a #GESTimelinePipeline
787  * @output_uri: the URI to which the timeline will be rendered
788  * @profile: the #GstEncodingProfile to use to render the timeline.
789  *
790  * Specify where the pipeline shall be rendered and with what settings.
791  *
792  * A copy of @profile and @output_uri will be done internally, the caller can
793  * safely free those values afterwards.
794  *
795  * This method must be called before setting the pipeline mode to
796  * #TIMELINE_MODE_RENDER
797  *
798  * Returns: %TRUE if the settings were aknowledged properly, else %FALSE
799  */
800 gboolean
801 ges_timeline_pipeline_set_render_settings (GESTimelinePipeline * pipeline,
802     const gchar * output_uri, GstEncodingProfile * profile)
803 {
804   GError *err = NULL;
805
806   /* Clear previous URI sink if it existed */
807   /* FIXME : We should figure out if it was added to the pipeline,
808    * and if so, remove it. */
809   if (pipeline->priv->urisink) {
810     g_object_unref (pipeline->priv->urisink);
811     pipeline->priv->urisink = NULL;
812   }
813
814   pipeline->priv->urisink =
815       gst_element_make_from_uri (GST_URI_SINK, output_uri, "urisink", &err);
816   if (G_UNLIKELY (pipeline->priv->urisink == NULL)) {
817     GST_ERROR_OBJECT (pipeline, "Couldn't not create sink for URI %s: '%s'",
818         output_uri, ((err
819                 && err->message) ? err->message : "failed to create element"));
820     g_clear_error (&err);
821     return FALSE;
822   }
823
824   if (pipeline->priv->profile)
825     gst_encoding_profile_unref (pipeline->priv->profile);
826   g_object_set (pipeline->priv->encodebin, "avoid-reencoding",
827       !(!(pipeline->priv->mode & TIMELINE_MODE_SMART_RENDER)), NULL);
828   g_object_set (pipeline->priv->encodebin, "profile", profile, NULL);
829   pipeline->priv->profile =
830       (GstEncodingProfile *) gst_encoding_profile_ref (profile);
831
832   return TRUE;
833 }
834
835 /**
836  * ges_timeline_pipeline_set_mode:
837  * @pipeline: a #GESTimelinePipeline
838  * @mode: the #GESPipelineFlags to use
839  *
840  * switches the @pipeline to the specified @mode. The default mode when
841  * creating a #GESTimelinePipeline is #TIMELINE_MODE_PREVIEW.
842  *
843  * Note: The @pipeline will be set to #GST_STATE_NULL during this call due to
844  * the internal changes that happen. The caller will therefore have to 
845  * set the @pipeline to the requested state after calling this method.
846  *
847  * Returns: %TRUE if the mode was properly set, else %FALSE.
848  **/
849 gboolean
850 ges_timeline_pipeline_set_mode (GESTimelinePipeline * pipeline,
851     GESPipelineFlags mode)
852 {
853   GST_DEBUG_OBJECT (pipeline, "current mode : %d, mode : %d",
854       pipeline->priv->mode, mode);
855
856   /* fast-path, nothing to change */
857   if (mode == pipeline->priv->mode)
858     return TRUE;
859
860   /* FIXME: It would be nice if we are only (de)activating preview
861    * modes to not set the whole pipeline to NULL, but instead just
862    * do the proper (un)linking to playsink. */
863
864   /* Switch pipeline to NULL since we're changing the configuration */
865   gst_element_set_state (GST_ELEMENT_CAST (pipeline), GST_STATE_NULL);
866
867   /* remove no-longer needed components */
868   if (pipeline->priv->mode & TIMELINE_MODE_PREVIEW &&
869       !(mode & TIMELINE_MODE_PREVIEW)) {
870     /* Disable playsink */
871     GST_DEBUG ("Disabling playsink");
872     g_object_ref (pipeline->priv->playsink);
873     gst_bin_remove (GST_BIN_CAST (pipeline), pipeline->priv->playsink);
874   }
875   if ((pipeline->priv->mode &
876           (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER)) &&
877       !(mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))) {
878     /* Disable render bin */
879     GST_DEBUG ("Disabling rendering bin");
880     g_object_ref (pipeline->priv->encodebin);
881     g_object_ref (pipeline->priv->urisink);
882     gst_bin_remove_many (GST_BIN_CAST (pipeline),
883         pipeline->priv->encodebin, pipeline->priv->urisink, NULL);
884   }
885
886   /* Add new elements */
887   if (!(pipeline->priv->mode & TIMELINE_MODE_PREVIEW) &&
888       (mode & TIMELINE_MODE_PREVIEW)) {
889     /* Add playsink */
890     GST_DEBUG ("Adding playsink");
891
892     if (!gst_bin_add (GST_BIN_CAST (pipeline), pipeline->priv->playsink)) {
893       GST_ERROR_OBJECT (pipeline, "Couldn't add playsink");
894       return FALSE;
895     }
896   }
897   if (!(pipeline->priv->mode &
898           (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER)) &&
899       (mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))) {
900     /* Adding render bin */
901     GST_DEBUG ("Adding render bin");
902
903     if (G_UNLIKELY (pipeline->priv->urisink == NULL)) {
904       GST_ERROR_OBJECT (pipeline, "Output URI not set !");
905       return FALSE;
906     }
907     if (!gst_bin_add (GST_BIN_CAST (pipeline), pipeline->priv->encodebin)) {
908       GST_ERROR_OBJECT (pipeline, "Couldn't add encodebin");
909       return FALSE;
910     }
911     if (!gst_bin_add (GST_BIN_CAST (pipeline), pipeline->priv->urisink)) {
912       GST_ERROR_OBJECT (pipeline, "Couldn't add URI sink");
913       return FALSE;
914     }
915     g_object_set (pipeline->priv->encodebin, "avoid-reencoding",
916         !(!(mode & TIMELINE_MODE_SMART_RENDER)), NULL);
917
918     gst_element_link_pads_full (pipeline->priv->encodebin, "src",
919         pipeline->priv->urisink, "sink", GST_PAD_LINK_CHECK_NOTHING);
920   }
921
922   /* FIXUPS */
923   /* FIXME
924    * If we are rendering, set playsink to sync=False,
925    * If we are NOT rendering, set playsink to sync=TRUE */
926
927   pipeline->priv->mode = mode;
928
929   return TRUE;
930 }
931
932 /**
933  * ges_timeline_pipeline_get_thumbnail:
934  * @self: a #GESTimelinePipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
935  * @caps: (transfer none): caps specifying current format. Use %GST_CAPS_ANY
936  * for native size.
937  *
938  * Returns a #GstSample with the currently playing image in the format specified by
939  * caps. The caller should free the sample with #gst_sample_unref when finished. If ANY
940  * caps are specified, the information will be returned in the whatever format
941  * is currently used by the sink. This information can be retrieve from caps
942  * associated with the buffer.
943  *
944  * Returns: (transfer full): a #GstSample or %NULL
945  */
946
947 GstSample *
948 ges_timeline_pipeline_get_thumbnail (GESTimelinePipeline * self, GstCaps * caps)
949 {
950   GstElement *sink;
951
952   sink = self->priv->playsink;
953
954   if (!sink) {
955     GST_WARNING ("thumbnailing can only be done if we have a playsink");
956     return NULL;
957   }
958
959   return ges_play_sink_convert_frame (sink, caps);
960 }
961
962 /**
963  * ges_timeline_pipeline_save_thumbnail:
964  * @self: a #GESTimelinePipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
965  * @width: the requested width or -1 for native size
966  * @height: the requested height or -1 for native size
967  * @format: a string specifying the desired mime type (for example,
968  * image/jpeg)
969  * @location: the path to save the thumbnail
970  *
971  * Saves the current frame to the specified @location.
972  * 
973  * Returns: %TRUE if the thumbnail was properly save, else %FALSE.
974  */
975 /* FIXME 0.11: save_thumbnail should have a GError parameter */
976 gboolean
977 ges_timeline_pipeline_save_thumbnail (GESTimelinePipeline * self, int width, int
978     height, const gchar * format, const gchar * location)
979 {
980   GstMapInfo map_info;
981   GstBuffer *b;
982   GstSample *sample;
983   GstCaps *caps;
984   gboolean res = TRUE;
985
986   caps = gst_caps_from_string (format);
987
988   if (width > 1)
989     gst_caps_set_simple (caps, "width", G_TYPE_INT, width, NULL);
990
991   if (height > 1)
992     gst_caps_set_simple (caps, "height", G_TYPE_INT, height, NULL);
993
994   if (!(sample = ges_timeline_pipeline_get_thumbnail (self, caps))) {
995     gst_caps_unref (caps);
996     return res;
997   }
998
999   b = gst_sample_get_buffer (sample);
1000   if (gst_buffer_map (b, &map_info, GST_MAP_READ)) {
1001     GError *err = NULL;
1002
1003     if (!g_file_set_contents (location, (const char *) map_info.data,
1004             map_info.size, &err)) {
1005       GST_WARNING ("Could not save thumbnail: %s", err->message);
1006       g_error_free (err);
1007       res = FALSE;
1008     }
1009   }
1010
1011   gst_caps_unref (caps);
1012   gst_buffer_unmap (b, &map_info);
1013   gst_buffer_unref (b);
1014   gst_sample_unref (sample);
1015
1016   return res;
1017 }
1018
1019 /**
1020  * ges_timeline_pipeline_get_thumbnail_rgb24:
1021  * @self: a #GESTimelinePipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
1022  * @width: the requested width or -1 for native size
1023  * @height: the requested height or -1 for native size
1024  *
1025  * A convenience method for @ges_timeline_pipeline_get_thumbnail which
1026  * returns a buffer in 24-bit RGB, optionally scaled to the specified width
1027  * and height. If -1 is specified for either dimension, it will be left at
1028  * native size. You can retreive this information from the caps associated
1029  * with the buffer.
1030  * 
1031  * The caller is responsible for unreffing the returned sample with
1032  * #gst_sample_unref.
1033  *
1034  * Returns: (transfer full): a #GstSample or %NULL
1035  */
1036
1037 GstSample *
1038 ges_timeline_pipeline_get_thumbnail_rgb24 (GESTimelinePipeline * self,
1039     gint width, gint height)
1040 {
1041   GstSample *ret;
1042   GstCaps *caps;
1043
1044   caps = gst_caps_new_simple ("video/x-raw", "format", G_TYPE_STRING,
1045       "RGB", NULL);
1046
1047   if (width != -1)
1048     gst_caps_set_simple (caps, "width", G_TYPE_INT, (gint) width, NULL);
1049
1050   if (height != -1)
1051     gst_caps_set_simple (caps, "height", G_TYPE_INT, (gint) height, NULL);
1052
1053   ret = ges_timeline_pipeline_get_thumbnail (self, caps);
1054   gst_caps_unref (caps);
1055   return ret;
1056 }
1057
1058 /**
1059  * ges_timeline_pipeline_preview_get_video_sink:
1060  * @self: a #GESTimelinePipeline
1061  *
1062  * Obtains a pointer to playsink's video sink element that is used for
1063  * displaying video when the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
1064  *
1065  * The caller is responsible for unreffing the returned element with
1066  * #gst_object_unref.
1067  *
1068  * Returns: (transfer full): a pointer to the playsink video sink #GstElement
1069  */
1070 GstElement *
1071 ges_timeline_pipeline_preview_get_video_sink (GESTimelinePipeline * self)
1072 {
1073   GstElement *sink = NULL;
1074
1075   g_object_get (self->priv->playsink, "video-sink", &sink, NULL);
1076
1077   return sink;
1078 };
1079
1080 /**
1081  * ges_timeline_pipeline_preview_set_video_sink:
1082  * @self: a #GESTimelinePipeline in %GST_STATE_NULL
1083  * @sink: (transfer none): a video sink #GstElement
1084  *
1085  * Sets playsink's video sink element that is used for displaying video when
1086  * the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
1087  */
1088 void
1089 ges_timeline_pipeline_preview_set_video_sink (GESTimelinePipeline * self,
1090     GstElement * sink)
1091 {
1092   g_object_set (self->priv->playsink, "video-sink", sink, NULL);
1093 };
1094
1095 /**
1096  * ges_timeline_pipeline_preview_get_audio_sink:
1097  * @self: a #GESTimelinePipeline
1098  *
1099  * Obtains a pointer to playsink's audio sink element that is used for
1100  * displaying audio when the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
1101  *
1102  * The caller is responsible for unreffing the returned element with
1103  * #gst_object_unref.
1104  *
1105  * Returns: (transfer full): a pointer to the playsink audio sink #GstElement
1106  */
1107 GstElement *
1108 ges_timeline_pipeline_preview_get_audio_sink (GESTimelinePipeline * self)
1109 {
1110   GstElement *sink = NULL;
1111
1112   g_object_get (self->priv->playsink, "audio-sink", &sink, NULL);
1113
1114   return sink;
1115 };
1116
1117 /**
1118  * ges_timeline_pipeline_preview_set_audio_sink:
1119  * @self: a #GESTimelinePipeline in %GST_STATE_NULL
1120  * @sink: (transfer none): a audio sink #GstElement
1121  *
1122  * Sets playsink's audio sink element that is used for displaying audio when
1123  * the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
1124  */
1125 void
1126 ges_timeline_pipeline_preview_set_audio_sink (GESTimelinePipeline * self,
1127     GstElement * sink)
1128 {
1129   g_object_set (self->priv->playsink, "audio-sink", sink, NULL);
1130 };
1131
1132
1133 static gboolean
1134 play_sink_multiple_seeks_send_event (GstElement * element, GstEvent * event)
1135 {
1136   GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
1137
1138   GST_DEBUG ("%s", GST_EVENT_TYPE_NAME (event));
1139
1140   return
1141       GST_ELEMENT_CLASS (g_type_class_peek_parent (klass))->send_event (element,
1142       event);
1143 }