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