df95c3c2240bc6dc3fd119eb76600d2795777819
[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   const gchar *scenario = NULL;
724   GOptionEntry options[] = {
725     {"thumbnail", 'm', 0.0, G_OPTION_ARG_DOUBLE, &thumbinterval,
726         "Take thumbnails every n seconds (saved in current directory)", "N"},
727     {"smartrender", 's', 0, G_OPTION_ARG_NONE, &smartrender,
728         "Render to outputuri, and avoid decoding/reencoding", NULL},
729     {"outputuri", 'o', 0, G_OPTION_ARG_STRING, &outputuri,
730         "URI to encode to", "URI (<protocol>://<location>)"},
731     {"format", 'f', 0, G_OPTION_ARG_STRING, &format,
732           "Set the properties to use for the encoding profile "
733           "(in case of transcoding.) For example:\n"
734           "video/mpegts:video/x-raw,width=1920,height=1080->video/x-h264:audio/x-ac3\n"
735           "A preset name can be used by adding +presetname, eg:\n"
736           "video/webm:video/x-vp8+mypreset:audio/x-vorbis\n"
737           "The presence property of the profile can be specified with |<presence>, eg:\n"
738           "video/webm:video/x-vp8|<presence>:audio/x-vorbis\n",
739         "properties-values"},
740     {"repeat", 'r', 0, G_OPTION_ARG_INT, &repeat,
741         "Number of time to repeat timeline", NULL},
742     {"list-transitions", 't', 0, G_OPTION_ARG_NONE, &list_transitions,
743         "List valid transition types and exit", NULL},
744     {"list-patterns", 'p', 0, G_OPTION_ARG_NONE, &list_patterns,
745         "List patterns and exit", NULL},
746     {"save", 'z', 0, G_OPTION_ARG_STRING, &save_path,
747         "Save project to file before rendering", "<path>"},
748     {"load", 'l', 0, G_OPTION_ARG_STRING, &load_path,
749         "Load project from file before rendering", "<path>"},
750     {"verbose", 0, 0, G_OPTION_ARG_NONE, &verbose,
751         "Output status information and property notifications", NULL},
752     {"exclude", 'X', 0, G_OPTION_ARG_NONE, &exclude_args,
753         "Do not output status information of TYPE", "TYPE1,TYPE2,..."},
754     {"sample-paths", 'P', 0, G_OPTION_ARG_CALLBACK, &_add_media_path,
755         "List of pathes to look assets in if they were moved"},
756     {"sample-path-recurse", 'R', 0, G_OPTION_ARG_CALLBACK,
757           &_add_media_path,
758         "Same as --sample-paths but recursing into the folder"},
759     {"track-types", 'p', 0, G_OPTION_ARG_CALLBACK, &parse_track_type,
760         "Defines the track types to be created"},
761     {"mute", 0, 0, G_OPTION_ARG_NONE, &mute,
762         "Mute playback output, which means that we use faksinks"},
763     {"disable-mixing", 0, 0, G_OPTION_ARG_NONE, &disable_mixing,
764         "Do not use mixing element in the tracks"},
765 #ifdef HAVE_GST_VALIDATE
766     {"set-scenario", 0, 0, G_OPTION_ARG_STRING, &scenario,
767         "Specify a GstValidate scenario to run, 'none' means load gst-validate"
768           " but run no scenario on it", "<scenario_name>"},
769 #endif
770     {NULL}
771   };
772
773   GOptionContext *ctx;
774   GstBus *bus;
775
776   setlocale (LC_ALL, "");
777
778   new_paths = g_ptr_array_new_with_free_func (g_free);
779   ctx = g_option_context_new ("- plays or renders a timeline.");
780   g_option_context_set_summary (ctx,
781       "ges-launch renders a timeline, which can be specified on the commandline,\n"
782       "or loaded from a file using the -q option.\n\n"
783       "A timeline is a list of files, patterns, and transitions to be rendered\n"
784       "one after the other. Files and Patterns provide video and audio as the\n"
785       "primary input, and transitions animate between the end of one file/pattern\n"
786       "and the beginning of a new one. Hence, transitions can only be listed\n"
787       "in between patterns or files.\n\n"
788       "A file is a triplet of filename, inpoint (in seconds) and\n"
789       "duration (in seconds). If the duration is 0, the full file length is used.\n\n"
790       "Patterns and transitions are triplets that begin with either \"+pattern\"\n"
791       "or \"+transition\", followed by a <type> and duration (in seconds, must be\n"
792       "greater than 0)\n\n"
793       "Durations in all cases can be fractions of a second.\n\n"
794       "Example:\n"
795       "ges-launch file1.avi 0 45 +transition crossfade 3.5 file2.avi 0 0");
796   g_option_context_add_main_entries (ctx, options, NULL);
797   g_option_context_add_group (ctx, gst_init_get_option_group ());
798
799   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
800     g_printerr ("Error initializing: %s\n", err->message);
801     g_option_context_free (ctx);
802     exit (1);
803   }
804
805   /* Initialize the GStreamer Editing Services */
806   if (!ges_init ()) {
807     g_printerr ("Error initializing GES\n");
808
809     exit (1);
810   }
811
812   if (list_transitions) {
813     print_transition_list ();
814     exit (0);
815   }
816
817   if (list_patterns) {
818     print_pattern_list ();
819     exit (0);
820   }
821
822   tried_uris = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
823   if (((!load_path && !scenario && (argc < 4)))) {
824     g_printf ("%s", g_option_context_get_help (ctx, TRUE, NULL));
825     g_option_context_free (ctx);
826     exit (1);
827   }
828
829   g_option_context_free (ctx);
830
831   /* Create the pipeline */
832   create_pipeline (&timeline, load_path, argc - 1, argv + 1, scenario);
833   if (!pipeline)
834     exit (1);
835
836   /* Setup profile/encoding if needed */
837   if (smartrender || outputuri) {
838     GstEncodingProfile *prof = NULL;
839
840     if (!format) {
841       GESProject *proj =
842           GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline)));
843       const GList *profiles = ges_project_list_encoding_profiles (proj);
844
845       prof = profiles ? profiles->data : NULL;
846     }
847
848     if (!prof) {
849       if (format == NULL)
850         format = "application/ogg:video/x-theora:audio/x-vorbis";
851
852       prof = _parse_encoding_profile (format);
853     }
854
855     if (outputuri)
856       outputuri = ensure_uri (outputuri);
857
858     if (!prof || !ges_pipeline_set_render_settings (pipeline, outputuri, prof)
859         || !ges_pipeline_set_mode (pipeline,
860             smartrender ? GES_PIPELINE_MODE_SMART_RENDER :
861             GES_PIPELINE_MODE_RENDER)) {
862       g_free (outputuri);
863       exit (1);
864     }
865     g_free (outputuri);
866
867     gst_encoding_profile_unref (prof);
868   } else {
869     ges_pipeline_set_mode (pipeline, GES_PIPELINE_MODE_PREVIEW);
870   }
871
872   if (verbose) {
873     gchar **exclude_list =
874         exclude_args ? g_strsplit (exclude_args, ",", 0) : NULL;
875     g_signal_connect (pipeline, "deep-notify",
876         G_CALLBACK (gst_object_default_deep_notify), exclude_list);
877   }
878
879   /* Play the pipeline */
880   mainloop = g_main_loop_new (NULL, FALSE);
881
882   if (thumbinterval != 0.0) {
883     g_printf ("thumbnailing every %f seconds\n", thumbinterval);
884     g_timeout_add (1000 * thumbinterval, thumbnail_cb, pipeline);
885   }
886
887   bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
888   gst_bus_add_signal_watch (bus);
889   g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), mainloop);
890
891   if (load_path == NULL && gst_element_set_state (GST_ELEMENT (pipeline),
892           GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
893     g_error ("Failed to start the pipeline\n");
894     return 1;
895   }
896   g_main_loop_run (mainloop);
897
898   gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL);
899
900   validate_res = ges_validate_clean (GST_PIPELINE (pipeline));
901   if (seenerrors == FALSE)
902     seenerrors = validate_res;
903
904   g_hash_table_unref (tried_uris);
905   g_ptr_array_unref (new_paths);
906
907   return (int) seenerrors;
908 }