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