ges-launch: add an option to use a custom video sink
[platform/upstream/gstreamer.git] / tools / ges-launch.c
1 /* GStreamer Editing Services
2  * Copyright (C) 2010 Edward Hervey <bilboed@bilboed.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <glib.h>
28 #include <glib/gprintf.h>
29 #include <ges/ges.h>
30 #include <gst/pbutils/encoding-profile.h>
31
32 #include <locale.h>             /* for LC_ALL */
33 #include "ges-validate.h"
34
35 /* GLOBAL VARIABLE */
36 static guint repeat = 0;
37 static gboolean mute = FALSE;
38 static gboolean disable_mixing = FALSE;
39 static GESPipeline *pipeline = NULL;
40 static gboolean seenerrors = FALSE;
41 static gchar *save_path = NULL;
42 static GPtrArray *new_paths = NULL;
43 static GMainLoop *mainloop;
44 static GHashTable *tried_uris;
45 static GESTrackType track_types = GES_TRACK_TYPE_AUDIO | GES_TRACK_TYPE_VIDEO;
46 static GESTimeline *timeline;
47
48 static gchar *
49 ensure_uri (gchar * location)
50 {
51   if (gst_uri_is_valid (location))
52     return g_strdup (location);
53   else
54     return gst_filename_to_uri (location, NULL);
55 }
56
57 static guint
58 get_flags_from_string (GType type, const gchar * str_flags)
59 {
60   guint i;
61   gint flags = 0;
62   GFlagsClass *class = g_type_class_ref (type);
63
64   for (i = 0; i < class->n_values; i++) {
65     if (g_strrstr (str_flags, class->values[i].value_nick)) {
66       flags |= class->values[i].value;
67     }
68   }
69   g_type_class_unref (class);
70
71   return flags;
72 }
73
74
75 static void
76 _add_media_new_paths_recursing (const gchar * value)
77 {
78   GFileInfo *info;
79   GFileEnumerator *fenum;
80   GFile *file = g_file_new_for_uri (value);
81
82   if (!(fenum = g_file_enumerate_children (file,
83               "standard::*", G_FILE_QUERY_INFO_NONE, NULL, NULL))) {
84     GST_INFO ("%s is not a folder", value);
85
86     goto done;
87   }
88
89   GST_INFO ("Adding folder: %s", value);
90   g_ptr_array_add (new_paths, g_strdup (value));
91   for (info = g_file_enumerator_next_file (fenum, NULL, NULL);
92       info; info = g_file_enumerator_next_file (fenum, NULL, NULL)) {
93
94     if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
95       GFile *f = g_file_enumerator_get_child (fenum, info);
96       gchar *uri = g_file_get_uri (f);
97
98       _add_media_new_paths_recursing (uri);
99       gst_object_unref (f);
100       g_free (uri);
101     }
102   }
103
104 done:
105   gst_object_unref (file);
106   if (fenum)
107     gst_object_unref (fenum);
108 }
109
110 static gboolean
111 _add_media_path (const gchar * option_name, const gchar * value,
112     gpointer udata, GError ** error)
113 {
114   g_return_val_if_fail (gst_uri_is_valid (value), FALSE);
115
116   if (g_strcmp0 (option_name, "--sample-path-recurse") == 0) {
117     _add_media_new_paths_recursing (value);
118   } else {
119     GST_INFO ("Adding folder: %s", value);
120     g_ptr_array_add (new_paths, g_strdup (value));
121   }
122
123   return TRUE;
124 }
125
126 static gboolean
127 parse_track_type (const gchar * option_name, const gchar * value,
128     gpointer udata, GError ** error)
129 {
130   track_types = get_flags_from_string (GES_TYPE_TRACK_TYPE, value);
131
132   if (track_types == 0)
133     return FALSE;
134
135   return TRUE;
136 }
137
138 static gboolean
139 thumbnail_cb (gpointer pipeline)
140 {
141   static int i = 0;
142   GESPipeline *p = (GESPipeline *) pipeline;
143   gchar *filename;
144   gboolean res;
145
146   filename = g_strdup_printf ("thumbnail%d.jpg", i++);
147
148   res = ges_pipeline_save_thumbnail (p, -1, -1,
149       (gchar *) "image/jpeg", filename, NULL);
150
151   g_free (filename);
152
153   return res;
154 }
155
156 static gchar *
157 source_moved_cb (GESProject * project, GError * error, GESAsset * asset)
158 {
159   gint i;
160   const gchar *old_uri = ges_asset_get_id (asset);
161
162   for (i = 0; i < new_paths->len; i++) {
163     gchar *basename, *res;
164
165     basename = g_path_get_basename (old_uri);
166     res = g_build_filename (new_paths->pdata[i], basename, NULL);
167     g_free (basename);
168
169     if (g_hash_table_lookup (tried_uris, res)) {
170       GST_DEBUG ("File already tried: %s\n", res);
171       g_free (res);
172     } else {
173       g_hash_table_add (tried_uris, g_strdup (res));
174       return res;
175     }
176   }
177
178   return NULL;
179 }
180
181 static void
182 error_loading_asset_cb (GESProject * project, GError * error,
183     const gchar * failed_id, GType extractable_type)
184 {
185   g_printerr ("Error loading asset %s: %s\n", failed_id, error->message);
186   seenerrors = TRUE;
187
188   g_main_loop_quit (mainloop);
189 }
190
191 static void
192 project_loaded_cb (GESProject * project, GESTimeline * timeline)
193 {
194   GST_INFO ("Project loaded, playing it");
195
196   if (save_path) {
197     gchar *uri;
198     GError *error = NULL;
199
200     if (g_strcmp0 (save_path, "+r") == 0) {
201       uri = ges_project_get_uri (project);
202     } else if (!(uri = ensure_uri (save_path))) {
203       g_error ("couldn't create uri for '%s", save_path);
204
205       seenerrors = TRUE;
206       g_main_loop_quit (mainloop);
207     }
208
209     g_print ("\nSaving project to %s\n", uri);
210     ges_project_save (project, timeline, uri, NULL, TRUE, &error);
211     g_free (uri);
212
213     g_assert_no_error (error);
214     if (error) {
215       seenerrors = TRUE;
216       g_main_loop_quit (mainloop);
217     }
218   }
219
220   if (gst_element_set_state (GST_ELEMENT (pipeline),
221           GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
222     g_error ("Failed to start the pipeline\n");
223   }
224 }
225
226 static gboolean
227 check_time (char *time)
228 {
229   static GRegex *re = NULL;
230
231   if (!re) {
232     if (NULL == (re = g_regex_new ("^[0-9]+(.[0-9]+)?$", G_REGEX_EXTENDED, 0,
233                 NULL)))
234       return FALSE;
235   }
236
237   if (g_regex_match (re, time, 0, NULL))
238     return TRUE;
239   return FALSE;
240 }
241
242 static guint64
243 str_to_time (char *time)
244 {
245   gdouble nsecs;
246
247   g_return_val_if_fail (check_time (time), 0);
248
249   nsecs = g_ascii_strtod (time, NULL);
250
251   return nsecs * GST_SECOND;
252 }
253
254 static GESTimeline *
255 create_timeline (int nbargs, gchar ** argv, const gchar * proj_uri, const gchar *scenario)
256 {
257   GESLayer *layer = NULL;
258   GESTrack *tracka = NULL, *trackv = NULL;
259   GESTimeline *timeline;
260   gboolean activate_before_paused = TRUE;
261   guint i;
262   GESProject *project = ges_project_new (proj_uri);
263
264   if (new_paths->len)
265     g_signal_connect (project, "missing-uri",
266         G_CALLBACK (source_moved_cb), NULL);
267
268   g_signal_connect (project, "error-loading-asset",
269       G_CALLBACK (error_loading_asset_cb), NULL);
270
271   if (proj_uri != NULL) {
272     g_signal_connect (project, "loaded", G_CALLBACK (project_loaded_cb), NULL);
273   }
274
275   timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), NULL));
276
277   if (proj_uri) {
278     activate_before_paused = FALSE;
279     goto activate_validate;
280   }
281
282   if (track_types & GES_TRACK_TYPE_AUDIO) {
283     tracka = GES_TRACK (ges_audio_track_new ());
284     if (disable_mixing)
285       ges_track_set_mixing (tracka, FALSE);
286
287     if (!(ges_timeline_add_track (timeline, tracka)))
288       goto build_failure;
289   }
290
291   if (track_types & GES_TRACK_TYPE_VIDEO) {
292     trackv = GES_TRACK (ges_video_track_new ());
293
294     if (disable_mixing)
295       ges_track_set_mixing (trackv, FALSE);
296
297     if (!(ges_timeline_add_track (timeline, trackv)))
298       goto build_failure;
299   }
300
301   /* Here we've finished initializing our timeline, we're
302    * ready to start using it... by solely working with the layer !*/
303
304   for (i = 0; i < nbargs / 3; i++) {
305     GESClip *clip;
306
307     char *source = argv[i * 3];
308     char *arg0 = argv[(i * 3) + 1];
309     guint64 duration = str_to_time (argv[(i * 3) + 2]);
310
311     if (i == 0) {
312       /* We are only going to be doing one layer of clips */
313       layer = (GESLayer *) ges_layer_new ();
314       activate_before_paused = FALSE;
315
316       /* Add the tracks and the layer to the timeline */
317       if (!ges_timeline_add_layer (timeline, layer))
318         goto build_failure;
319     }
320
321     if (duration == 0)
322       duration = GST_CLOCK_TIME_NONE;
323
324     if (!g_strcmp0 ("+pattern", source)) {
325       clip = GES_CLIP (ges_test_clip_new_for_nick (arg0));
326       if (!clip) {
327         g_error ("%s is an invalid pattern name!\n", arg0);
328         goto build_failure;
329       }
330
331       g_object_set (G_OBJECT (clip), "duration", duration, NULL);
332
333       g_printf ("Adding <pattern:%s> duration %" GST_TIME_FORMAT "\n", arg0,
334           GST_TIME_ARGS (duration));
335     }
336
337     else if (!g_strcmp0 ("+transition", source)) {
338       clip = GES_CLIP (ges_transition_clip_new_for_nick (arg0));
339
340       if (!clip) {
341         g_error ("invalid transition type\n");
342         goto build_failure;
343       }
344
345       g_object_set (G_OBJECT (clip), "duration", duration, NULL);
346
347       g_printf ("Adding <transition:%s> duration %" GST_TIME_FORMAT "\n", arg0,
348           GST_TIME_ARGS (duration));
349
350     }
351
352     else if (!g_strcmp0 ("+title", source)) {
353       clip = GES_CLIP (ges_title_clip_new ());
354
355       g_object_set (clip, "duration", duration, "text", arg0, NULL);
356
357       g_printf ("Adding <title:%s> duration %" GST_TIME_FORMAT "\n", arg0,
358           GST_TIME_ARGS (duration));
359     }
360
361     else {
362       gchar *uri;
363       GESAsset *asset;
364       guint64 inpoint;
365
366       GError *error = NULL;
367
368       if (!(uri = ensure_uri (source))) {
369         GST_ERROR ("couldn't create uri for '%s'", source);
370         goto build_failure;
371       }
372
373       inpoint = str_to_time (argv[i * 3 + 1]);
374       asset = GES_ASSET (ges_uri_clip_asset_request_sync (uri, &error));
375       if (error) {
376         g_printerr ("Can not create asset for %s", uri);
377
378         return NULL;
379       }
380
381       ges_project_add_asset (project, asset);
382       clip = GES_CLIP (ges_asset_extract (asset, &error));
383       if (error) {
384         g_printerr ("Can not extract asset for %s", uri);
385
386         return NULL;
387       }
388
389       if (!GST_CLOCK_TIME_IS_VALID (duration))
390         duration =
391             GES_TIMELINE_ELEMENT_DURATION (clip) - (GstClockTime) inpoint;
392
393       g_object_set (clip,
394           "in-point", (guint64) inpoint, "duration", (guint64) duration, NULL);
395
396       g_printf ("Adding clip %s inpoint:%" GST_TIME_FORMAT " duration:%"
397           GST_TIME_FORMAT "\n", uri, GST_TIME_ARGS (inpoint),
398           GST_TIME_ARGS (duration));
399
400       g_free (uri);
401     }
402
403     g_object_set (G_OBJECT (clip), "start", ges_layer_get_duration (layer),
404         NULL);
405     ges_layer_add_clip (layer, clip);
406   }
407
408 activate_validate:
409   if (ges_validate_activate (GST_PIPELINE (pipeline), scenario, activate_before_paused) == FALSE) {
410     g_error ("Could not activate scenario %s", scenario);
411     return NULL;
412   }
413   return timeline;
414
415 build_failure:
416   {
417     gst_object_unref (timeline);
418     return NULL;
419   }
420 }
421
422 static GESPipeline *
423 create_pipeline (GESTimeline ** ret_timeline, gchar * load_path,
424     int argc, char **argv, const gchar *scenario)
425 {
426   gchar *uri = NULL;
427   GESTimeline *timeline = NULL;
428
429   /* Timeline creation */
430   if (load_path) {
431     g_printf ("Loading project from : %s\n", load_path);
432
433     if (!(uri = ensure_uri (load_path))) {
434       g_error ("couldn't create uri for '%s'", load_path);
435       goto failure;
436     }
437   }
438
439   pipeline = ges_pipeline_new ();
440
441   if (!(timeline = create_timeline (argc, argv, uri, scenario)))
442     goto failure;
443
444   ges_timeline_commit (timeline);
445
446   if (uri)
447     g_free (uri);
448
449   /* save project if path is given. we do this now in case GES crashes or
450    * hangs during playback. */
451   if (save_path && !load_path) {
452     gchar *uri;
453     if (!(uri = ensure_uri (save_path))) {
454       g_error ("couldn't create uri for '%s", save_path);
455       goto failure;
456     }
457     ges_timeline_save_to_uri (timeline, uri, NULL, TRUE, NULL);
458     g_free (uri);
459   }
460
461   /* In order to view our timeline, let's grab a convenience pipeline to put
462    * our timeline in. */
463
464   if (mute) {
465     GstElement *sink = gst_element_factory_make ("fakesink", NULL);
466
467     g_object_set (sink, "sync", TRUE, NULL);
468     ges_pipeline_preview_set_audio_sink (pipeline, sink);
469
470     sink = gst_element_factory_make ("fakesink", NULL);
471     g_object_set (sink, "sync", TRUE, NULL);
472     ges_pipeline_preview_set_video_sink (pipeline, sink);
473   }
474
475   /* Add the timeline to that pipeline */
476   if (!ges_pipeline_set_timeline (pipeline, timeline))
477     goto failure;
478
479   *ret_timeline = timeline;
480   return pipeline;
481
482 failure:
483   {
484     if (uri)
485       g_free (uri);
486     if (timeline)
487       gst_object_unref (timeline);
488     if (pipeline)
489       gst_object_unref (pipeline);
490     return NULL;
491   }
492 }
493
494 static void
495 bus_message_cb (GstBus * bus, GstMessage * message, GMainLoop * mainloop)
496 {
497   switch (GST_MESSAGE_TYPE (message)) {
498     case GST_MESSAGE_WARNING:{
499       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
500           GST_DEBUG_GRAPH_SHOW_ALL, "ges-launch.warning");
501       break;
502     }
503     case GST_MESSAGE_ERROR:{
504       GError *err = NULL;
505       gchar *dbg_info = NULL;
506
507       gst_message_parse_error (message, &err, &dbg_info);
508       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
509           GST_DEBUG_GRAPH_SHOW_ALL, "ges-launch-error");
510       g_printerr ("ERROR from element %s: %s\n", GST_OBJECT_NAME (message->src),
511           err->message);
512       g_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
513       g_error_free (err);
514       g_free (dbg_info);
515       seenerrors = TRUE;
516       g_main_loop_quit (mainloop);
517       break;
518     }
519     case GST_MESSAGE_EOS:
520       if (repeat > 0) {
521         g_printerr ("Looping again\n");
522         if (!gst_element_seek_simple (GST_ELEMENT (pipeline), GST_FORMAT_TIME,
523                 GST_SEEK_FLAG_FLUSH, 0))
524           g_printerr ("seeking failed\n");
525         else
526           g_printerr ("seeking succeeded\n");
527         gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);
528         g_printerr ("Looping set\n");
529         repeat -= 1;
530       } else {
531         g_printerr ("\nDone\n");
532         g_main_loop_quit (mainloop);
533       }
534       break;
535     case GST_MESSAGE_STATE_CHANGED:
536       if (GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (pipeline)) {
537         gchar *dump_name;
538         GstState old, new, pending;
539         gchar *state_transition_name;
540
541         gst_message_parse_state_changed (message, &old, &new, &pending);
542         state_transition_name = g_strdup_printf ("%s_%s",
543             gst_element_state_get_name (old), gst_element_state_get_name (new));
544         dump_name = g_strconcat ("ges-launch.", state_transition_name, NULL);
545
546
547         GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
548             GST_DEBUG_GRAPH_SHOW_ALL, dump_name);
549
550         g_free (dump_name);
551         g_free (state_transition_name);
552       }
553       break;
554     default:
555       break;
556   }
557 }
558
559 static void
560 print_enum (GType enum_type)
561 {
562   GEnumClass *enum_class = G_ENUM_CLASS (g_type_class_ref (enum_type));
563   guint i;
564
565   for (i = 0; i < enum_class->n_values; i++) {
566     g_printf ("%s\n", enum_class->values[i].value_nick);
567   }
568
569   g_type_class_unref (enum_class);
570 }
571
572 static void
573 print_transition_list (void)
574 {
575   print_enum (GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE);
576 }
577
578 static void
579 print_pattern_list (void)
580 {
581   print_enum (GES_VIDEO_TEST_PATTERN_TYPE);
582 }
583
584 static GstEncodingProfile *
585 _parse_encoding_profile (const gchar * format)
586 {
587   GstCaps *caps;
588   char *preset_name = NULL;
589   GstEncodingProfile *encoding_profile;
590   gchar **restriction_format, **preset_v;
591
592   guint i, presence = 0;
593   GstCaps *restrictioncaps = NULL;
594   gchar **strpresence_v, **strcaps_v = g_strsplit (format, ":", 0);
595
596   if (strcaps_v[0] && *strcaps_v[0]) {
597     caps = gst_caps_from_string (strcaps_v[0]);
598     if (caps == NULL) {
599       g_printerr ("Could not parse caps %s", strcaps_v[0]);
600       return FALSE;
601     }
602     encoding_profile =
603         GST_ENCODING_PROFILE (gst_encoding_container_profile_new
604         ("User profile", "User profile", caps, NULL));
605     gst_caps_unref (caps);
606   } else {
607     encoding_profile = NULL;
608   }
609
610   for (i = 1; strcaps_v[i]; i++) {
611     GstEncodingProfile *profile = NULL;
612     gchar *strcaps, *strpresence;
613
614     restriction_format = g_strsplit (strcaps_v[i], "->", 0);
615     if (restriction_format[1]) {
616       restrictioncaps = gst_caps_from_string (restriction_format[0]);
617       strcaps = g_strdup (restriction_format[1]);
618     } else {
619       restrictioncaps = NULL;
620       strcaps = g_strdup (restriction_format[0]);
621     }
622     g_strfreev (restriction_format);
623
624     preset_v = g_strsplit (strcaps, "+", 0);
625     if (preset_v[1]) {
626       strpresence = preset_v[1];
627       g_free (strcaps);
628       strcaps = g_strdup (preset_v[0]);
629     } else {
630       strpresence = preset_v[0];
631     }
632
633     strpresence_v = g_strsplit (strpresence, "|", 0);
634     if (strpresence_v[1]) {     /* We have a presence */
635       gchar *endptr;
636
637       if (preset_v[1]) {        /* We have preset and presence */
638         preset_name = g_strdup (strpresence_v[0]);
639       } else {                  /* We have a presence but no preset */
640         g_free (strcaps);
641         strcaps = g_strdup (strpresence_v[0]);
642       }
643
644       presence = strtoll (strpresence_v[1], &endptr, 10);
645       if (endptr == strpresence_v[1]) {
646         g_printerr ("Wrong presence %s\n", strpresence_v[1]);
647
648         return FALSE;
649       }
650     } else {                    /* We have no presence */
651       if (preset_v[1]) {        /* Not presence but preset */
652         preset_name = g_strdup (preset_v[1]);
653         g_free (strcaps);
654         strcaps = g_strdup (preset_v[0]);
655       }                         /* Else we have no presence nor preset */
656     }
657     g_strfreev (strpresence_v);
658     g_strfreev (preset_v);
659
660     GST_DEBUG ("Creating preset with restrictions: %" GST_PTR_FORMAT
661         ", caps: %s, preset %s, presence %d", restrictioncaps, strcaps,
662         preset_name ? preset_name : "none", presence);
663
664     caps = gst_caps_from_string (strcaps);
665     g_free (strcaps);
666     if (caps == NULL) {
667       g_warning ("Could not create caps for %s", strcaps_v[i]);
668
669       return FALSE;
670     }
671
672     if (g_str_has_prefix (strcaps_v[i], "audio/")) {
673       profile = GST_ENCODING_PROFILE (gst_encoding_audio_profile_new (caps,
674               preset_name, restrictioncaps, presence));
675     } else if (g_str_has_prefix (strcaps_v[i], "video/") ||
676         g_str_has_prefix (strcaps_v[i], "image/")) {
677       profile = GST_ENCODING_PROFILE (gst_encoding_video_profile_new (caps,
678               preset_name, restrictioncaps, presence));
679     }
680
681     g_free (preset_name);
682     gst_caps_unref (caps);
683     if (restrictioncaps)
684       gst_caps_unref (restrictioncaps);
685
686     if (profile == NULL) {
687       g_warning ("No way to create a preset for caps: %s", strcaps_v[i]);
688
689       return NULL;
690     }
691
692     if (encoding_profile) {
693       if (gst_encoding_container_profile_add_profile
694           (GST_ENCODING_CONTAINER_PROFILE (encoding_profile),
695               profile) == FALSE) {
696         g_warning ("Can not create a preset for caps: %s", strcaps_v[i]);
697
698         return NULL;
699       }
700     } else {
701       encoding_profile = profile;
702     }
703   }
704   g_strfreev (strcaps_v);
705
706   return encoding_profile;
707 }
708
709 int
710 main (int argc, gchar ** argv)
711 {
712   gint validate_res;
713   GError *err = NULL;
714   gchar *outputuri = NULL;
715   const gchar *format = NULL;
716   gchar *exclude_args = NULL;
717   static gboolean smartrender = FALSE;
718   static gboolean list_transitions = FALSE;
719   static gboolean list_patterns = FALSE;
720   static gdouble thumbinterval = 0;
721   static gboolean verbose = FALSE;
722   gchar *load_path = NULL;
723   gchar *videosink = NULL;
724   const gchar *scenario = NULL;
725
726   GOptionEntry options[] = {
727     {"thumbnail", 'm', 0.0, G_OPTION_ARG_DOUBLE, &thumbinterval,
728         "Take thumbnails every n seconds (saved in current directory)", "N"},
729     {"smartrender", 's', 0, G_OPTION_ARG_NONE, &smartrender,
730         "Render to outputuri, and avoid decoding/reencoding", NULL},
731     {"outputuri", 'o', 0, G_OPTION_ARG_STRING, &outputuri,
732         "URI to encode to", "URI (<protocol>://<location>)"},
733     {"format", 'f', 0, G_OPTION_ARG_STRING, &format,
734           "Set the properties to use for the encoding profile "
735           "(in case of transcoding.) For example:\n"
736           "video/mpegts:video/x-raw,width=1920,height=1080->video/x-h264:audio/x-ac3\n"
737           "A preset name can be used by adding +presetname, eg:\n"
738           "video/webm:video/x-vp8+mypreset:audio/x-vorbis\n"
739           "The presence property of the profile can be specified with |<presence>, eg:\n"
740           "video/webm:video/x-vp8|<presence>:audio/x-vorbis\n",
741         "properties-values"},
742     {"repeat", 'r', 0, G_OPTION_ARG_INT, &repeat,
743         "Number of time to repeat timeline", NULL},
744     {"list-transitions", 't', 0, G_OPTION_ARG_NONE, &list_transitions,
745         "List valid transition types and exit", NULL},
746     {"list-patterns", 'p', 0, G_OPTION_ARG_NONE, &list_patterns,
747         "List patterns and exit", NULL},
748     {"save", 'z', 0, G_OPTION_ARG_STRING, &save_path,
749         "Save project to file before rendering", "<path>"},
750     {"load", 'l', 0, G_OPTION_ARG_STRING, &load_path,
751         "Load project from file before rendering", "<path>"},
752     {"verbose", 0, 0, G_OPTION_ARG_NONE, &verbose,
753         "Output status information and property notifications", NULL},
754     {"exclude", 'X', 0, G_OPTION_ARG_NONE, &exclude_args,
755         "Do not output status information of TYPE", "TYPE1,TYPE2,..."},
756     {"sample-paths", 'P', 0, G_OPTION_ARG_CALLBACK, &_add_media_path,
757         "List of pathes to look assets in if they were moved"},
758     {"sample-path-recurse", 'R', 0, G_OPTION_ARG_CALLBACK,
759           &_add_media_path,
760         "Same as --sample-paths but recursing into the folder"},
761     {"track-types", 'p', 0, G_OPTION_ARG_CALLBACK, &parse_track_type,
762         "Defines the track types to be created"},
763     {"mute", 0, 0, G_OPTION_ARG_NONE, &mute,
764         "Mute playback output, which means that we use faksinks"},
765     {"disable-mixing", 0, 0, G_OPTION_ARG_NONE, &disable_mixing,
766         "Do not use mixing element in the tracks"},
767     {"videosink", 'v', 0, G_OPTION_ARG_STRING, &videosink,
768       "The video sink used for playing back", "<videosink>"},
769 #ifdef HAVE_GST_VALIDATE
770     {"set-scenario", 0, 0, G_OPTION_ARG_STRING, &scenario,
771         "Specify a GstValidate scenario to run, 'none' means load gst-validate"
772           " but run no scenario on it", "<scenario_name>"},
773 #endif
774     {NULL}
775   };
776
777   GOptionContext *ctx;
778   GstBus *bus;
779
780   setlocale (LC_ALL, "");
781
782   new_paths = g_ptr_array_new_with_free_func (g_free);
783   ctx = g_option_context_new ("- plays or renders a timeline.");
784   g_option_context_set_summary (ctx,
785       "ges-launch renders a timeline, which can be specified on the commandline,\n"
786       "or loaded from a file using the -q option.\n\n"
787       "A timeline is a list of files, patterns, and transitions to be rendered\n"
788       "one after the other. Files and Patterns provide video and audio as the\n"
789       "primary input, and transitions animate between the end of one file/pattern\n"
790       "and the beginning of a new one. Hence, transitions can only be listed\n"
791       "in between patterns or files.\n\n"
792       "A file is a triplet of filename, inpoint (in seconds) and\n"
793       "duration (in seconds). If the duration is 0, the full file length is used.\n\n"
794       "Patterns and transitions are triplets that begin with either \"+pattern\"\n"
795       "or \"+transition\", followed by a <type> and duration (in seconds, must be\n"
796       "greater than 0)\n\n"
797       "Durations in all cases can be fractions of a second.\n\n"
798       "Example:\n"
799       "ges-launch file1.avi 0 45 +transition crossfade 3.5 file2.avi 0 0");
800   g_option_context_add_main_entries (ctx, options, NULL);
801   g_option_context_add_group (ctx, gst_init_get_option_group ());
802
803   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
804     g_printerr ("Error initializing: %s\n", err->message);
805     g_option_context_free (ctx);
806     exit (1);
807   }
808
809   /* Initialize the GStreamer Editing Services */
810   if (!ges_init ()) {
811     g_printerr ("Error initializing GES\n");
812
813     exit (1);
814   }
815
816   if (list_transitions) {
817     print_transition_list ();
818     exit (0);
819   }
820
821   if (list_patterns) {
822     print_pattern_list ();
823     exit (0);
824   }
825
826   tried_uris = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
827   if (((!load_path && !scenario && (argc < 4)))) {
828     g_printf ("%s", g_option_context_get_help (ctx, TRUE, NULL));
829     g_option_context_free (ctx);
830     exit (1);
831   }
832
833   g_option_context_free (ctx);
834
835   /* Create the pipeline */
836   create_pipeline (&timeline, load_path, argc - 1, argv + 1, scenario);
837   if (!pipeline)
838     exit (1);
839
840   if (videosink != NULL) {
841     GstElement *sink = gst_element_factory_make (videosink, "custom-videosink");
842     if (sink == NULL) {
843       GST_ERROR ("could not create the requested videosink %s, exiting", videosink);
844       exit (1);
845     }
846     ges_pipeline_preview_set_video_sink (pipeline, sink);
847   }
848
849   /* Setup profile/encoding if needed */
850   if (smartrender || outputuri) {
851     GstEncodingProfile *prof = NULL;
852
853     if (!format) {
854       GESProject *proj =
855           GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline)));
856       const GList *profiles = ges_project_list_encoding_profiles (proj);
857
858       prof = profiles ? profiles->data : NULL;
859     }
860
861     if (!prof) {
862       if (format == NULL)
863         format = "application/ogg:video/x-theora:audio/x-vorbis";
864
865       prof = _parse_encoding_profile (format);
866     }
867
868     if (outputuri)
869       outputuri = ensure_uri (outputuri);
870
871     if (!prof || !ges_pipeline_set_render_settings (pipeline, outputuri, prof)
872         || !ges_pipeline_set_mode (pipeline,
873             smartrender ? GES_PIPELINE_MODE_SMART_RENDER :
874             GES_PIPELINE_MODE_RENDER)) {
875       g_free (outputuri);
876       exit (1);
877     }
878     g_free (outputuri);
879
880     gst_encoding_profile_unref (prof);
881   } else {
882     ges_pipeline_set_mode (pipeline, GES_PIPELINE_MODE_PREVIEW);
883   }
884
885   if (verbose) {
886     gchar **exclude_list =
887         exclude_args ? g_strsplit (exclude_args, ",", 0) : NULL;
888     g_signal_connect (pipeline, "deep-notify",
889         G_CALLBACK (gst_object_default_deep_notify), exclude_list);
890   }
891
892   /* Play the pipeline */
893   mainloop = g_main_loop_new (NULL, FALSE);
894
895   if (thumbinterval != 0.0) {
896     g_printf ("thumbnailing every %f seconds\n", thumbinterval);
897     g_timeout_add (1000 * thumbinterval, thumbnail_cb, pipeline);
898   }
899
900   bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
901   gst_bus_add_signal_watch (bus);
902   g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), mainloop);
903
904   if (load_path == NULL && gst_element_set_state (GST_ELEMENT (pipeline),
905           GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
906     g_error ("Failed to start the pipeline\n");
907     return 1;
908   }
909   g_main_loop_run (mainloop);
910
911   gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL);
912
913   validate_res = ges_validate_clean (GST_PIPELINE (pipeline));
914   if (seenerrors == FALSE)
915     seenerrors = validate_res;
916
917   g_hash_table_unref (tried_uris);
918   g_ptr_array_unref (new_paths);
919
920   return (int) seenerrors;
921 }