Merge branch 'tizen' into tizen_gst_1.19.2
[platform/upstream/gst-editing-services.git] / tools / ges-launcher.c
1 /* GStreamer Editing Services
2  * Copyright (C) 2015 Mathieu Duponchelle <mathieu.duponchelle@opencreed.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 <glib.h>
25 #include <glib/gprintf.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #ifdef G_OS_UNIX
29 #include <glib-unix.h>
30 #endif
31 #include "ges-launcher.h"
32 #include "ges-validate.h"
33 #include "utils.h"
34 #include "ges-launcher-kb.h"
35
36 typedef enum
37 {
38   GST_PLAY_TRICK_MODE_NONE = 0,
39   GST_PLAY_TRICK_MODE_DEFAULT,
40   GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO,
41   GST_PLAY_TRICK_MODE_KEY_UNITS,
42   GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO,
43   GST_PLAY_TRICK_MODE_INSTANT_RATE,
44   GST_PLAY_TRICK_MODE_LAST
45 } GstPlayTrickMode;
46
47 struct _GESLauncherPrivate
48 {
49   GESTimeline *timeline;
50   GESPipeline *pipeline;
51   gboolean seenerrors;
52 #ifdef G_OS_UNIX
53   guint signal_watch_id;
54 #endif
55   GESLauncherParsedOptions parsed_options;
56
57   GstPlayTrickMode trick_mode;
58   gdouble rate;
59
60   GstState desired_state;       /* as per user interaction, PAUSED or PLAYING */
61 };
62
63 G_DEFINE_TYPE_WITH_PRIVATE (GESLauncher, ges_launcher, G_TYPE_APPLICATION);
64
65 static const gchar *HELP_SUMMARY =
66     "  `ges-launch-1.0` creates a multimedia timeline and plays it back,\n"
67     "  or renders it to the specified format.\n\n"
68     "  It can load a timeline from an existing project, or create one\n"
69     "  using the 'Timeline description format', specified in the section\n"
70     "  of the same name.\n\n"
71     "  Updating an existing project can be done through `--set-scenario`\n"
72     "  if ges-launch-1.0 has been compiled with gst-validate, see\n"
73     "  `ges-launch-1.0 --inspect-action-type` for the available commands.\n\n"
74     "  By default, ges-launch-1.0 is in \"playback-mode\".";
75
76 static gboolean
77 play_do_seek (GESLauncher * self, gint64 pos, gdouble rate,
78     GstPlayTrickMode mode)
79 {
80   GstSeekFlags seek_flags;
81   GstEvent *seek;
82
83   seek_flags = 0;
84
85   switch (mode) {
86     case GST_PLAY_TRICK_MODE_DEFAULT:
87       seek_flags |= GST_SEEK_FLAG_TRICKMODE;
88       break;
89     case GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO:
90       seek_flags |= GST_SEEK_FLAG_TRICKMODE | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO;
91       break;
92     case GST_PLAY_TRICK_MODE_KEY_UNITS:
93       seek_flags |= GST_SEEK_FLAG_TRICKMODE_KEY_UNITS;
94       break;
95     case GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO:
96       seek_flags |=
97           GST_SEEK_FLAG_TRICKMODE_KEY_UNITS | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO;
98       break;
99     case GST_PLAY_TRICK_MODE_NONE:
100     default:
101       break;
102   }
103
104   /* See if we can do an instant rate change (not changing dir) */
105   if (mode & GST_PLAY_TRICK_MODE_INSTANT_RATE && rate * self->priv->rate > 0) {
106     seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
107         seek_flags | GST_SEEK_FLAG_INSTANT_RATE_CHANGE,
108         GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE,
109         GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
110     if (gst_element_send_event (GST_ELEMENT (self->priv->pipeline), seek)) {
111       goto done;
112     }
113   }
114
115   /* No instant rate change, need to do a flushing seek */
116   seek_flags |= GST_SEEK_FLAG_FLUSH;
117   if (rate >= 0)
118     seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
119         seek_flags | GST_SEEK_FLAG_ACCURATE,
120         /* start */ GST_SEEK_TYPE_SET, pos,
121         /* stop */ GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE);
122   else
123     seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
124         seek_flags | GST_SEEK_FLAG_ACCURATE,
125         /* start */ GST_SEEK_TYPE_SET, 0,
126         /* stop */ GST_SEEK_TYPE_SET, pos);
127
128   if (!gst_element_send_event (GST_ELEMENT (self->priv->pipeline), seek))
129     return FALSE;
130
131 done:
132   self->priv->rate = rate;
133   self->priv->trick_mode = mode & ~GST_PLAY_TRICK_MODE_INSTANT_RATE;
134   return TRUE;
135 }
136
137 static void
138 restore_terminal (void)
139 {
140   gst_play_kb_set_key_handler (NULL, NULL);
141 }
142
143 static void
144 toggle_paused (GESLauncher * self)
145 {
146   if (self->priv->desired_state == GST_STATE_PLAYING)
147     self->priv->desired_state = GST_STATE_PAUSED;
148   else
149     self->priv->desired_state = GST_STATE_PLAYING;
150
151   gst_element_set_state (GST_ELEMENT (self->priv->pipeline),
152       self->priv->desired_state);
153 }
154
155 static void
156 relative_seek (GESLauncher * self, gdouble percent)
157 {
158   gint64 pos = -1, step, dur;
159
160   g_return_if_fail (percent >= -1.0 && percent <= 1.0);
161
162   if (!gst_element_query_position (GST_ELEMENT (self->priv->pipeline),
163           GST_FORMAT_TIME, &pos))
164     goto seek_failed;
165
166   if (!gst_element_query_duration (GST_ELEMENT (self->priv->pipeline),
167           GST_FORMAT_TIME, &dur)) {
168     goto seek_failed;
169   }
170
171   step = dur * percent;
172   if (ABS (step) < GST_SECOND)
173     step = (percent < 0) ? -GST_SECOND : GST_SECOND;
174
175   pos = pos + step;
176   if (pos > dur) {
177     gst_print ("\n%s\n", "Reached end of self list.");
178     g_application_quit (G_APPLICATION (self));
179   } else {
180     if (pos < 0)
181       pos = 0;
182
183     play_do_seek (self, pos, self->priv->rate, self->priv->trick_mode);
184   }
185
186   return;
187
188 seek_failed:
189   {
190     gst_print ("\nCould not seek.\n");
191   }
192 }
193
194 static gboolean
195 play_set_rate_and_trick_mode (GESLauncher * self, gdouble rate,
196     GstPlayTrickMode mode)
197 {
198   gint64 pos = -1;
199
200   g_return_val_if_fail (rate != 0, FALSE);
201
202   if (!gst_element_query_position (GST_ELEMENT (self->priv->pipeline),
203           GST_FORMAT_TIME, &pos))
204     return FALSE;
205
206   return play_do_seek (self, pos, rate, mode);
207 }
208
209 static void
210 play_set_playback_rate (GESLauncher * self, gdouble rate)
211 {
212   GstPlayTrickMode mode = self->priv->trick_mode;
213
214   if (play_set_rate_and_trick_mode (self, rate, mode)) {
215     gst_print ("Playback rate: %.2f", rate);
216     gst_print ("                               \n");
217   } else {
218     gst_print ("\n");
219     gst_print ("Could not change playback rate to %.2f", rate);
220     gst_print (".\n");
221   }
222 }
223
224 static void
225 play_set_relative_playback_rate (GESLauncher * self, gdouble rate_step,
226     gboolean reverse_direction)
227 {
228   gdouble new_rate = self->priv->rate + rate_step;
229
230   play_set_playback_rate (self, new_rate);
231 }
232
233 static const gchar *
234 trick_mode_get_description (GstPlayTrickMode mode)
235 {
236   switch (mode) {
237     case GST_PLAY_TRICK_MODE_NONE:
238       return "normal playback, trick modes disabled";
239     case GST_PLAY_TRICK_MODE_DEFAULT:
240       return "trick mode: default";
241     case GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO:
242       return "trick mode: default, no audio";
243     case GST_PLAY_TRICK_MODE_KEY_UNITS:
244       return "trick mode: key frames only";
245     case GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO:
246       return "trick mode: key frames only, no audio";
247     default:
248       break;
249   }
250   return "unknown trick mode";
251 }
252
253 static void
254 play_switch_trick_mode (GESLauncher * self)
255 {
256   GstPlayTrickMode new_mode = ++self->priv->trick_mode;
257   const gchar *mode_desc;
258
259   if (new_mode == GST_PLAY_TRICK_MODE_LAST)
260     new_mode = GST_PLAY_TRICK_MODE_NONE;
261
262   mode_desc = trick_mode_get_description (new_mode);
263
264   if (play_set_rate_and_trick_mode (self, self->priv->rate, new_mode)) {
265     gst_print ("Rate: %.2f (%s)                      \n", self->priv->rate,
266         mode_desc);
267   } else {
268     gst_print ("\nCould not change trick mode to %s.\n", mode_desc);
269   }
270 }
271
272 static void
273 print_keyboard_help (void)
274 {
275   static struct
276   {
277     const gchar *key_desc;
278     const gchar *key_help;
279   } key_controls[] = {
280     {
281     "space", "pause/unpause"}, {
282     "q or ESC", "quit"}, {
283     "\342\206\222", "seek forward"}, {
284     "\342\206\220", "seek backward"}, {
285     "+", "increase playback rate"}, {
286     "-", "decrease playback rate"}, {
287     "t", "enable/disable trick modes"}, {
288     "s", "change subtitle track"}, {
289     "0", "seek to beginning"}, {
290   "k", "show keyboard shortcuts"},};
291   guint i, chars_to_pad, desc_len, max_desc_len = 0;
292
293   gst_print ("\n\n%s\n\n", "Interactive mode - keyboard controls:");
294
295   for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
296     desc_len = g_utf8_strlen (key_controls[i].key_desc, -1);
297     max_desc_len = MAX (max_desc_len, desc_len);
298   }
299   ++max_desc_len;
300
301   for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
302     chars_to_pad = max_desc_len - g_utf8_strlen (key_controls[i].key_desc, -1);
303     gst_print ("\t%s", key_controls[i].key_desc);
304     gst_print ("%-*s: ", chars_to_pad, "");
305     gst_print ("%s\n", key_controls[i].key_help);
306   }
307   gst_print ("\n");
308 }
309
310 static gboolean
311 _parse_track_type (const gchar * option_name, const gchar * value,
312     GESLauncherParsedOptions * opts, GError ** error)
313 {
314   if (!get_flags_from_string (GES_TYPE_TRACK_TYPE, value, &opts->track_types))
315     return FALSE;
316
317   return TRUE;
318 }
319
320 static gboolean
321 _set_track_restriction_caps (GESTrack * track, const gchar * caps_str)
322 {
323   GstCaps *caps;
324
325   if (!caps_str)
326     return TRUE;
327
328   caps = gst_caps_from_string (caps_str);
329
330   if (!caps) {
331     g_error ("Could not create caps for %s from: %s",
332         G_OBJECT_TYPE_NAME (track), caps_str);
333
334     return FALSE;
335   }
336
337   ges_track_set_restriction_caps (track, caps);
338
339   gst_caps_unref (caps);
340   return TRUE;
341 }
342
343 static void
344 _set_restriction_caps (GESTimeline * timeline, GESLauncherParsedOptions * opts)
345 {
346   GList *tmp, *tracks = ges_timeline_get_tracks (timeline);
347
348   for (tmp = tracks; tmp; tmp = tmp->next) {
349     if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_VIDEO)
350       _set_track_restriction_caps (tmp->data, opts->video_track_caps);
351     else if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_AUDIO)
352       _set_track_restriction_caps (tmp->data, opts->audio_track_caps);
353   }
354
355   g_list_free_full (tracks, gst_object_unref);
356
357 }
358
359 static void
360 _set_track_forward_tags (const GValue * item, gpointer unused)
361 {
362   GstElement *comp = g_value_get_object (item);
363
364   g_object_set (comp, "drop-tags", FALSE, NULL);
365 }
366
367 static void
368 _set_tracks_forward_tags (GESTimeline * timeline,
369     GESLauncherParsedOptions * opts)
370 {
371   GList *tmp, *tracks;
372
373   if (!opts->forward_tags)
374     return;
375
376   tracks = ges_timeline_get_tracks (timeline);
377
378   for (tmp = tracks; tmp; tmp = tmp->next) {
379     GstIterator *it =
380         gst_bin_iterate_all_by_element_factory_name (GST_BIN (tmp->data),
381         "nlecomposition");
382
383     gst_iterator_foreach (it,
384         (GstIteratorForeachFunction) _set_track_forward_tags, NULL);
385     gst_iterator_free (it);
386   }
387
388   g_list_free_full (tracks, gst_object_unref);
389
390 }
391
392 static void
393 _check_has_audio_video (GESLauncher * self, gint * n_audio, gint * n_video)
394 {
395   GList *tmp, *tracks = ges_timeline_get_tracks (self->priv->timeline);
396
397   *n_video = *n_audio = 0;
398   for (tmp = tracks; tmp; tmp = tmp->next) {
399     if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_VIDEO)
400       *n_video = *n_video + 1;
401     else if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_AUDIO)
402       *n_audio = *n_audio + 1;
403   }
404 }
405
406 #define N_INSTANCES "__n_instances"
407 static gint
408 sort_encoding_profiles (gconstpointer a, gconstpointer b)
409 {
410   const gint acount =
411       GPOINTER_TO_INT (g_object_get_data ((GObject *) a, N_INSTANCES));
412   const gint bcount =
413       GPOINTER_TO_INT (g_object_get_data ((GObject *) b, N_INSTANCES));
414
415   if (acount < bcount)
416     return -1;
417
418   if (acount == bcount)
419     return 0;
420
421   return 1;
422 }
423
424 static GList *
425 _timeline_assets (GESLauncher * self)
426 {
427   GList *tmp, *assets = NULL;
428
429   for (tmp = self->priv->timeline->layers; tmp; tmp = tmp->next) {
430     GList *tclip, *clips = ges_layer_get_clips (tmp->data);
431
432     for (tclip = clips; tclip; tclip = tclip->next) {
433       if (GES_IS_URI_CLIP (tclip->data)) {
434         assets =
435             g_list_append (assets, ges_extractable_get_asset (tclip->data));
436       }
437     }
438     g_list_free_full (clips, gst_object_unref);
439   }
440
441   return assets;
442 }
443
444 static GESAsset *
445 _asset_for_named_clip (GESLauncher * self, const gchar * name)
446 {
447   GList *tmp;
448   GESAsset *ret = NULL;
449
450   for (tmp = self->priv->timeline->layers; tmp; tmp = tmp->next) {
451     GList *tclip, *clips = ges_layer_get_clips (tmp->data);
452
453     for (tclip = clips; tclip; tclip = tclip->next) {
454       if (GES_IS_URI_CLIP (tclip->data) &&
455           !g_strcmp0 (name, ges_timeline_element_get_name (tclip->data))) {
456         ret = ges_extractable_get_asset (tclip->data);
457         break;
458       }
459     }
460
461     g_list_free_full (clips, gst_object_unref);
462
463     if (ret)
464       break;
465   }
466
467   return ret;
468 }
469
470 static GstEncodingProfile *
471 _get_profile_from (GESLauncher * self)
472 {
473   GESAsset *asset =
474       _asset_for_named_clip (self, self->priv->parsed_options.profile_from);
475   GstDiscovererInfo *info;
476   GstEncodingProfile *prof;
477
478   g_assert (asset);
479
480   info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset));
481   prof = gst_encoding_profile_from_discoverer (info);
482
483   return prof;
484 }
485
486 static GstEncodingProfile *
487 get_smart_profile (GESLauncher * self)
488 {
489   gint n_audio, n_video;
490   GList *tmp, *assets, *possible_profiles = NULL;
491   GstEncodingProfile *res = NULL;
492
493   if (self->priv->parsed_options.profile_from) {
494     GESAsset *asset =
495         _asset_for_named_clip (self, self->priv->parsed_options.profile_from);
496     GstDiscovererInfo *info;
497     GstEncodingProfile *prof;
498
499     g_assert (asset);
500
501     info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset));
502     prof = gst_encoding_profile_from_discoverer (info);
503
504     return prof;
505   }
506
507   _check_has_audio_video (self, &n_audio, &n_video);
508
509   assets = _timeline_assets (self);
510
511   for (tmp = assets; tmp; tmp = tmp->next) {
512     GESAsset *asset = tmp->data;
513     GList *audio_streams, *video_streams;
514     GstDiscovererInfo *info;
515
516     if (!GES_IS_URI_CLIP_ASSET (asset))
517       continue;
518
519     info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset));
520     audio_streams = gst_discoverer_info_get_audio_streams (info);
521     video_streams = gst_discoverer_info_get_video_streams (info);
522     if (g_list_length (audio_streams) >= n_audio
523         && g_list_length (video_streams) >= n_video) {
524       GstEncodingProfile *prof = gst_encoding_profile_from_discoverer (info);
525       GList *prevprof;
526
527       prevprof =
528           g_list_find_custom (possible_profiles, prof,
529           (GCompareFunc) gst_encoding_profile_is_equal);
530       if (prevprof) {
531         g_object_unref (prof);
532         prof = prevprof->data;
533       } else {
534         possible_profiles = g_list_prepend (possible_profiles, prof);
535       }
536
537       g_object_set_data ((GObject *) prof, N_INSTANCES,
538           GINT_TO_POINTER (GPOINTER_TO_INT (g_object_get_data ((GObject *) prof,
539                       N_INSTANCES)) + 1));
540     }
541     gst_discoverer_stream_info_list_free (audio_streams);
542     gst_discoverer_stream_info_list_free (video_streams);
543   }
544
545   g_list_free (assets);
546
547   if (possible_profiles) {
548     possible_profiles = g_list_sort (possible_profiles, sort_encoding_profiles);
549     res = gst_object_ref (possible_profiles->data);
550     g_list_free_full (possible_profiles, gst_object_unref);
551   }
552
553   return res;
554 }
555
556 static void
557 disable_bframe_for_smart_rendering_cb (GstBin * bin, GstBin * sub_bin,
558     GstElement * child)
559 {
560   GstElementFactory *factory = gst_element_get_factory (child);
561
562   if (factory && !g_strcmp0 (GST_OBJECT_NAME (factory), "x264enc")) {
563     g_object_set (child, "b-adapt", FALSE, "b-pyramid", FALSE, "bframes", 0,
564         NULL);
565   }
566 }
567
568 static gboolean
569 _set_rendering_details (GESLauncher * self)
570 {
571   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
572   gboolean smart_profile = FALSE;
573   GESPipelineFlags cmode = ges_pipeline_get_mode (self->priv->pipeline);
574   GESProject *proj;
575
576   if (cmode & GES_PIPELINE_MODE_RENDER
577       || cmode & GES_PIPELINE_MODE_SMART_RENDER) {
578     GST_INFO_OBJECT (self, "Rendering settings already set");
579     return TRUE;
580   }
581
582   proj =
583       GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (self->priv->
584               timeline)));
585
586   /* Setup profile/encoding if needed */
587   if (opts->outputuri) {
588     GstEncodingProfile *prof = NULL;
589     if (!opts->format) {
590       const GList *profiles = ges_project_list_encoding_profiles (proj);
591
592       if (profiles) {
593         prof = profiles->data;
594         if (opts->encoding_profile)
595           for (; profiles; profiles = profiles->next)
596             if (g_strcmp0 (opts->encoding_profile,
597                     gst_encoding_profile_get_name (profiles->data)) == 0)
598               prof = profiles->data;
599       }
600
601       if (prof)
602         prof = gst_object_ref (prof);
603     }
604
605     if (!prof) {
606       if (opts->format == NULL) {
607         if (opts->profile_from)
608           prof = _get_profile_from (self);
609         else if (opts->smartrender)
610           prof = get_smart_profile (self);
611         if (prof)
612           smart_profile = TRUE;
613         else {
614           opts->format = get_file_extension (opts->outputuri);
615           prof = parse_encoding_profile (opts->format);
616         }
617       } else {
618         prof = parse_encoding_profile (opts->format);
619         if (!prof)
620           g_error ("Invalid format specified: %s", opts->format);
621       }
622
623       if (!prof) {
624         ges_warn
625             ("No format specified and couldn't find one from output file extension, "
626             "falling back to theora+vorbis in ogg.");
627         g_free (opts->format);
628
629         opts->format =
630             g_strdup ("application/ogg:video/x-theora:audio/x-vorbis");
631         prof = parse_encoding_profile (opts->format);
632       }
633
634       if (!prof) {
635         ges_printerr ("Could not find any encoding format for %s\n",
636             opts->format);
637         return FALSE;
638       }
639
640       gst_print ("\nEncoding details:\n");
641       gst_print ("================\n");
642
643       gst_print ("  -> Output file: %s\n", opts->outputuri);
644       gst_print ("  -> Profile:%s\n",
645           smart_profile ?
646           " (selected from input files format for efficient smart rendering" :
647           "");
648       describe_encoding_profile (prof);
649       gst_print ("\n");
650
651       ges_project_add_encoding_profile (proj, prof);
652     }
653
654     opts->outputuri = ensure_uri (opts->outputuri);
655     if (opts->smartrender) {
656       g_signal_connect (self->priv->pipeline, "deep-element-added",
657           G_CALLBACK (disable_bframe_for_smart_rendering_cb), NULL);
658     }
659     if (!prof
660         || !ges_pipeline_set_render_settings (self->priv->pipeline,
661             opts->outputuri, prof)
662         || !ges_pipeline_set_mode (self->priv->pipeline,
663             opts->smartrender ? GES_PIPELINE_MODE_SMART_RENDER :
664             GES_PIPELINE_MODE_RENDER)) {
665       return FALSE;
666     }
667
668     gst_encoding_profile_unref (prof);
669   } else {
670     ges_pipeline_set_mode (self->priv->pipeline, GES_PIPELINE_MODE_PREVIEW);
671   }
672   return TRUE;
673 }
674
675 static void
676 _track_set_mixing (GESTrack * track, GESLauncherParsedOptions * opts)
677 {
678   static gboolean printed_mixing_disabled = FALSE;
679
680   if (opts->disable_mixing || opts->smartrender)
681     ges_track_set_mixing (track, FALSE);
682   if (!opts->disable_mixing && opts->smartrender && !printed_mixing_disabled) {
683     gst_print ("**Mixing is disabled for smart rendering to work**\n");
684     printed_mixing_disabled = TRUE;
685   }
686 }
687
688 static gboolean
689 _timeline_set_user_options (GESLauncher * self, GESTimeline * timeline,
690     const gchar * load_path)
691 {
692   GList *tmp;
693   GESTrack *tracka, *trackv;
694   gboolean has_audio = FALSE, has_video = FALSE;
695   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
696
697   if (self->priv->parsed_options.profile_from) {
698     GList *tmp, *tracks;
699     GList *audio_streams, *video_streams;
700     GESAsset *asset =
701         _asset_for_named_clip (self, self->priv->parsed_options.profile_from);
702     GstDiscovererInfo *info;
703     guint i;
704
705     if (!asset) {
706       ges_printerr
707           ("\nERROR: can't create profile from named clip, no such clip %s\n\n",
708           self->priv->parsed_options.profile_from);
709       return FALSE;
710     }
711
712     tracks = ges_timeline_get_tracks (self->priv->timeline);
713
714     for (tmp = tracks; tmp; tmp = tmp->next) {
715       ges_timeline_remove_track (timeline, tmp->data);
716     }
717
718     g_list_free_full (tracks, gst_object_unref);
719
720     info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset));
721
722     audio_streams = gst_discoverer_info_get_audio_streams (info);
723     video_streams = gst_discoverer_info_get_video_streams (info);
724
725     for (i = 0; i < g_list_length (audio_streams); i++) {
726       ges_timeline_add_track (timeline, GES_TRACK (ges_audio_track_new ()));
727     }
728
729     for (i = 0; i < g_list_length (video_streams); i++) {
730       ges_timeline_add_track (timeline, GES_TRACK (ges_video_track_new ()));
731     }
732
733     gst_discoverer_stream_info_list_free (audio_streams);
734     gst_discoverer_stream_info_list_free (video_streams);
735   }
736
737 retry:
738   for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
739
740     if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_VIDEO)
741       has_video = TRUE;
742     else if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_AUDIO)
743       has_audio = TRUE;
744
745     _track_set_mixing (tmp->data, opts);
746
747     if (!self->priv->parsed_options.profile_from) {
748       if (!(GES_TRACK (tmp->data)->type & opts->track_types)) {
749         ges_timeline_remove_track (timeline, tmp->data);
750         goto retry;
751       }
752     }
753   }
754
755   if ((opts->scenario || opts->testfile) && !load_path
756       && !self->priv->parsed_options.profile_from) {
757     if (!has_video && opts->track_types & GES_TRACK_TYPE_VIDEO) {
758       trackv = GES_TRACK (ges_video_track_new ());
759
760       if (!_set_track_restriction_caps (trackv, opts->video_track_caps))
761         return FALSE;
762
763       _track_set_mixing (trackv, opts);
764
765       if (!(ges_timeline_add_track (timeline, trackv)))
766         return FALSE;
767     }
768
769     if (!has_audio && opts->track_types & GES_TRACK_TYPE_AUDIO) {
770       tracka = GES_TRACK (ges_audio_track_new ());
771
772       if (!_set_track_restriction_caps (tracka, opts->audio_track_caps))
773         return FALSE;
774
775       _track_set_mixing (tracka, opts);
776
777       if (!(ges_timeline_add_track (timeline, tracka)))
778         return FALSE;
779     }
780   } else {
781     _set_restriction_caps (timeline, opts);
782   }
783
784   _set_tracks_forward_tags (timeline, opts);
785
786   return TRUE;
787 }
788
789 static void
790 _project_loading_error_cb (GESProject * project, GESTimeline * timeline,
791     GError * error, GESLauncher * self)
792 {
793   ges_printerr ("Error loading timeline: '%s'\n", error->message);
794   self->priv->seenerrors = TRUE;
795
796   g_application_quit (G_APPLICATION (self));
797 }
798
799 static void
800 _project_loaded_cb (GESProject * project, GESTimeline * timeline,
801     GESLauncher * self)
802 {
803   gchar *project_uri = NULL;
804   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
805   GST_INFO ("Project loaded, playing it");
806
807   if (opts->save_path) {
808     gchar *uri;
809     GError *error = NULL;
810
811     if (g_strcmp0 (opts->save_path, "+r") == 0) {
812       uri = ges_project_get_uri (project);
813     } else if (!(uri = ensure_uri (opts->save_path))) {
814       g_error ("couldn't create uri for '%s", opts->save_path);
815
816       self->priv->seenerrors = TRUE;
817       g_application_quit (G_APPLICATION (self));
818     }
819
820     gst_print ("\nSaving project to %s\n", uri);
821     ges_project_save (project, timeline, uri, NULL, TRUE, &error);
822     g_free (uri);
823
824     g_assert_no_error (error);
825     if (error) {
826       self->priv->seenerrors = TRUE;
827       g_error_free (error);
828       g_application_quit (G_APPLICATION (self));
829     }
830   }
831
832   project_uri = ges_project_get_uri (project);
833
834   if (self->priv->parsed_options.load_path && project_uri
835       && ges_validate_activate (GST_PIPELINE (self->priv->pipeline),
836           self, opts) == FALSE) {
837     if (opts->scenario)
838       g_error ("Could not activate scenario %s", opts->scenario);
839     else
840       g_error ("Could not activate testfile %s", opts->testfile);
841     self->priv->seenerrors = TRUE;
842     g_application_quit (G_APPLICATION (self));
843   }
844
845   if (!_timeline_set_user_options (self, timeline, project_uri)) {
846     g_error ("Failed to set user options on timeline\n");
847   } else if (project_uri) {
848     if (!_set_rendering_details (self))
849       g_error ("Failed to setup rendering details\n");
850   }
851
852   print_timeline (self->priv->timeline);
853
854   g_free (project_uri);
855
856   if (!self->priv->seenerrors && opts->needs_set_state &&
857       gst_element_set_state (GST_ELEMENT (self->priv->pipeline),
858           GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
859     g_error ("Failed to start the pipeline\n");
860   }
861 }
862
863 static void
864 _error_loading_asset_cb (GESProject * project, GError * error,
865     const gchar * failed_id, GType extractable_type, GESLauncher * self)
866 {
867   ges_printerr ("Error loading asset %s: %s\n", failed_id, error->message);
868   self->priv->seenerrors = TRUE;
869
870   g_application_quit (G_APPLICATION (self));
871 }
872
873 static gboolean
874 _create_timeline (GESLauncher * self, const gchar * serialized_timeline,
875     const gchar * proj_uri, gboolean validate)
876 {
877   GESProject *project;
878
879   GError *error = NULL;
880
881   if (proj_uri != NULL) {
882     project = ges_project_new (proj_uri);
883   } else if (!validate) {
884     project = ges_project_new (serialized_timeline);
885   } else {
886     project = ges_project_new (NULL);
887   }
888
889   g_signal_connect (project, "error-loading-asset",
890       G_CALLBACK (_error_loading_asset_cb), self);
891   g_signal_connect (project, "loaded", G_CALLBACK (_project_loaded_cb), self);
892   g_signal_connect (project, "error-loading",
893       G_CALLBACK (_project_loading_error_cb), self);
894
895   self->priv->timeline =
896       GES_TIMELINE (ges_asset_extract (GES_ASSET (project), &error));
897   gst_object_unref (project);
898
899   if (error) {
900     ges_printerr ("\nERROR: Could not create timeline because: %s\n\n",
901         error->message);
902     g_error_free (error);
903     return FALSE;
904   }
905
906   return TRUE;
907 }
908
909 typedef void (*SetSinkFunc) (GESPipeline * pipeline, GstElement * element);
910
911 static gboolean
912 _set_sink (GESLauncher * self, const gchar * sink_desc, SetSinkFunc set_func)
913 {
914   if (sink_desc != NULL) {
915     GError *err = NULL;
916     GstElement *sink = gst_parse_bin_from_description_full (sink_desc, TRUE,
917         NULL, GST_PARSE_FLAG_NO_SINGLE_ELEMENT_BINS, &err);
918     if (sink == NULL) {
919       GST_ERROR ("could not create the requested videosink %s (err: %s), "
920           "exiting", err ? err->message : "", sink_desc);
921       if (err)
922         g_error_free (err);
923       return FALSE;
924     }
925     set_func (self->priv->pipeline, sink);
926   }
927   return TRUE;
928 }
929
930 static gboolean
931 _set_playback_details (GESLauncher * self)
932 {
933   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
934
935   if (!_set_sink (self, opts->videosink, ges_pipeline_preview_set_video_sink) ||
936       !_set_sink (self, opts->audiosink, ges_pipeline_preview_set_audio_sink))
937     return FALSE;
938
939   return TRUE;
940 }
941
942 static void
943 bus_message_cb (GstBus * bus, GstMessage * message, GESLauncher * self)
944 {
945   switch (GST_MESSAGE_TYPE (message)) {
946     case GST_MESSAGE_WARNING:{
947       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->priv->pipeline),
948           GST_DEBUG_GRAPH_SHOW_ALL, "ges-launch.warning");
949       break;
950     }
951     case GST_MESSAGE_ERROR:{
952       GError *err = NULL;
953       gchar *dbg_info = NULL;
954
955       gst_message_parse_error (message, &err, &dbg_info);
956       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->priv->pipeline),
957           GST_DEBUG_GRAPH_SHOW_ALL, "ges-launch-error");
958       ges_printerr ("ERROR from element %s: %s\n",
959           GST_OBJECT_NAME (message->src), err->message);
960       ges_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
961       g_clear_error (&err);
962       g_free (dbg_info);
963       self->priv->seenerrors = TRUE;
964       g_application_quit (G_APPLICATION (self));
965       break;
966     }
967     case GST_MESSAGE_EOS:
968       if (!self->priv->parsed_options.ignore_eos) {
969         ges_ok ("\nDone\n");
970         g_application_quit (G_APPLICATION (self));
971       }
972       break;
973     case GST_MESSAGE_STATE_CHANGED:
974       if (GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (self->priv->pipeline)) {
975         gchar *dump_name;
976         GstState old, new, pending;
977         gchar *state_transition_name;
978
979         gst_message_parse_state_changed (message, &old, &new, &pending);
980         state_transition_name = g_strdup_printf ("%s_%s",
981             gst_element_state_get_name (old), gst_element_state_get_name (new));
982         dump_name = g_strconcat ("ges-launch.", state_transition_name, NULL);
983
984
985         GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->priv->pipeline),
986             GST_DEBUG_GRAPH_SHOW_ALL, dump_name);
987
988         g_free (dump_name);
989         g_free (state_transition_name);
990       }
991       break;
992     case GST_MESSAGE_REQUEST_STATE:
993       ges_validate_handle_request_state_change (message, G_APPLICATION (self));
994       break;
995     default:
996       break;
997   }
998 }
999
1000 #ifdef G_OS_UNIX
1001 static gboolean
1002 intr_handler (GESLauncher * self)
1003 {
1004   gst_print ("interrupt received.\n");
1005
1006   GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->priv->pipeline),
1007       GST_DEBUG_GRAPH_SHOW_ALL, "ges-launch.interrupted");
1008
1009   g_application_quit (G_APPLICATION (self));
1010
1011   /* remove signal handler */
1012   return TRUE;
1013 }
1014 #endif /* G_OS_UNIX */
1015
1016 static gboolean
1017 _save_timeline (GESLauncher * self)
1018 {
1019   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
1020
1021
1022   if (opts->embed_nesteds) {
1023     GList *tmp, *assets;
1024     GESProject *proj =
1025         GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (self->
1026                 priv->timeline)));
1027
1028     assets = ges_project_list_assets (proj, GES_TYPE_URI_CLIP);
1029     for (tmp = assets; tmp; tmp = tmp->next) {
1030       gboolean is_nested;
1031
1032       g_object_get (tmp->data, "is-nested-timeline", &is_nested, NULL);
1033       if (is_nested) {
1034         GESAsset *subproj =
1035             ges_asset_request (GES_TYPE_TIMELINE, ges_asset_get_id (tmp->data),
1036             NULL);
1037
1038         ges_project_add_asset (proj, subproj);
1039       }
1040     }
1041     g_list_free_full (assets, gst_object_unref);
1042   }
1043
1044   if (opts->save_only_path) {
1045     gchar *uri;
1046
1047     if (!(uri = ensure_uri (opts->save_only_path))) {
1048       g_error ("couldn't create uri for '%s", opts->save_only_path);
1049       return FALSE;
1050     }
1051
1052     return ges_timeline_save_to_uri (self->priv->timeline, uri, NULL, TRUE,
1053         NULL);
1054   }
1055
1056   if (opts->save_path && !opts->load_path) {
1057     gchar *uri;
1058     if (!(uri = ensure_uri (opts->save_path))) {
1059       g_error ("couldn't create uri for '%s", opts->save_path);
1060       return FALSE;
1061     }
1062
1063     return ges_timeline_save_to_uri (self->priv->timeline, uri, NULL, TRUE,
1064         NULL);
1065   }
1066
1067   return TRUE;
1068 }
1069
1070 static gboolean
1071 _run_pipeline (GESLauncher * self)
1072 {
1073   GstBus *bus;
1074   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
1075
1076   if (!opts->load_path) {
1077     g_clear_pointer (&opts->sanitized_timeline, &g_free);
1078     if (ges_validate_activate (GST_PIPELINE (self->priv->pipeline),
1079             self, opts) == FALSE) {
1080       g_error ("Could not activate scenario %s", opts->scenario);
1081       return FALSE;
1082     }
1083
1084     if (opts->sanitized_timeline) {
1085       GESProject *project = ges_project_new (opts->sanitized_timeline);
1086
1087       if (!ges_project_load (project, self->priv->timeline, NULL)) {
1088         ges_printerr ("Could not load timeline: %s\n",
1089             opts->sanitized_timeline);
1090         return FALSE;
1091       }
1092     }
1093
1094     if (!_timeline_set_user_options (self, self->priv->timeline, NULL)) {
1095       ges_printerr ("Could not properly set tracks\n");
1096       return FALSE;
1097     }
1098
1099     if (!_set_rendering_details (self)) {
1100       g_error ("Failed to setup rendering details\n");
1101       return FALSE;
1102     }
1103   }
1104
1105   bus = gst_pipeline_get_bus (GST_PIPELINE (self->priv->pipeline));
1106   gst_bus_add_signal_watch (bus);
1107   g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), self);
1108
1109   g_application_hold (G_APPLICATION (self));
1110
1111   return TRUE;
1112 }
1113
1114 static gboolean
1115 _create_pipeline (GESLauncher * self, const gchar * serialized_timeline)
1116 {
1117   gchar *uri = NULL;
1118   gboolean res = TRUE;
1119   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
1120
1121   /* Timeline creation */
1122   if (opts->load_path) {
1123     gst_print ("Loading project from : %s\n", opts->load_path);
1124
1125     if (!(uri = ensure_uri (opts->load_path))) {
1126       g_error ("couldn't create uri for '%s'", opts->load_path);
1127       goto failure;
1128     }
1129   }
1130
1131   self->priv->pipeline = ges_pipeline_new ();
1132
1133   if (opts->outputuri)
1134     ges_pipeline_set_mode (self->priv->pipeline, 0);
1135
1136   if (!_create_timeline (self, serialized_timeline, uri, opts->scenario
1137           || opts->testfile)) {
1138     GST_ERROR ("Could not create the timeline");
1139     goto failure;
1140   }
1141
1142   if (!opts->load_path)
1143     ges_timeline_commit (self->priv->timeline);
1144
1145   /* save project if path is given. we do this now in case GES crashes or
1146    * hangs during playback. */
1147   if (!_save_timeline (self))
1148     goto failure;
1149
1150   if (opts->save_only_path)
1151     goto done;
1152
1153   /* In order to view our timeline, let's grab a convenience pipeline to put
1154    * our timeline in. */
1155
1156   if (opts->mute) {
1157     GstElement *sink = gst_element_factory_make ("fakeaudiosink", NULL);
1158     ges_pipeline_preview_set_audio_sink (self->priv->pipeline, sink);
1159
1160     sink = gst_element_factory_make ("fakevideosink", NULL);
1161     ges_pipeline_preview_set_video_sink (self->priv->pipeline, sink);
1162   }
1163
1164   /* Add the timeline to that pipeline */
1165   if (!ges_pipeline_set_timeline (self->priv->pipeline, self->priv->timeline))
1166     goto failure;
1167
1168 done:
1169   if (uri)
1170     g_free (uri);
1171
1172   return res;
1173
1174 failure:
1175   {
1176     if (self->priv->timeline)
1177       gst_object_unref (self->priv->timeline);
1178     if (self->priv->pipeline)
1179       gst_object_unref (self->priv->pipeline);
1180     self->priv->pipeline = NULL;
1181     self->priv->timeline = NULL;
1182
1183     res = FALSE;
1184     goto done;
1185   }
1186 }
1187
1188 static void
1189 _print_transition_list (void)
1190 {
1191   print_enum (GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE);
1192 }
1193
1194 static GOptionGroup *
1195 ges_launcher_get_project_option_group (GESLauncherParsedOptions * opts)
1196 {
1197   GOptionGroup *group;
1198
1199   GOptionEntry options[] = {
1200     {"load", 'l', 0, G_OPTION_ARG_STRING, &opts->load_path,
1201           "Load project from file. The project can be saved "
1202           "again with the --save option.",
1203         "<path>"},
1204     {"save", 's', 0, G_OPTION_ARG_STRING, &opts->save_path,
1205           "Save project to file before rendering. "
1206           "It can then be loaded with the --load option",
1207         "<path>"},
1208     {"save-only", 0, 0, G_OPTION_ARG_STRING, &opts->save_only_path,
1209           "Same as save project, except exit as soon as the timeline "
1210           "is saved instead of playing it back",
1211         "<path>"},
1212     {NULL}
1213   };
1214   group = g_option_group_new ("project", "Project Options",
1215       "Show project-related options", NULL, NULL);
1216
1217   g_option_group_add_entries (group, options);
1218
1219   return group;
1220 }
1221
1222 static GOptionGroup *
1223 ges_launcher_get_info_option_group (GESLauncherParsedOptions * opts)
1224 {
1225   GOptionGroup *group;
1226
1227   GOptionEntry options[] = {
1228 #ifdef HAVE_GST_VALIDATE
1229     {"inspect-action-type", 0, 0, G_OPTION_ARG_NONE, &opts->inspect_action_type,
1230           "Inspect the available action types that can be defined in a scenario "
1231           "set with --set-scenario. "
1232           "Will list all action-types if action-type is empty.",
1233         "<[action-type]>"},
1234 #endif
1235     {"list-transitions", 0, 0, G_OPTION_ARG_NONE, &opts->list_transitions,
1236           "List all valid transition types and exit. "
1237           "See ges-launch-1.0 help transition for more information.",
1238         NULL},
1239     {NULL}
1240   };
1241
1242   group = g_option_group_new ("informative", "Informative Options",
1243       "Show informative options", NULL, NULL);
1244
1245   g_option_group_add_entries (group, options);
1246
1247   return group;
1248 }
1249
1250 static GOptionGroup *
1251 ges_launcher_get_rendering_option_group (GESLauncherParsedOptions * opts)
1252 {
1253   GOptionGroup *group;
1254
1255   GOptionEntry options[] = {
1256     {"outputuri", 'o', 0, G_OPTION_ARG_STRING, &opts->outputuri,
1257           "If set, ges-launch-1.0 will render the timeline instead of playing "
1258           "it back. If no format `--format` is specified, the outputuri extension"
1259           " will be used to determine an encoding format, or default to theora+vorbis"
1260           " in ogg if that doesn't work out.",
1261         "<URI>"},
1262     {"format", 'f', 0, G_OPTION_ARG_STRING, &opts->format,
1263           "Set an encoding profile on the command line. "
1264           "See ges-launch-1.0 help profile for more information. "
1265           "This will have no effect if no outputuri has been specified.",
1266         "<profile>"},
1267     {"encoding-profile", 'e', 0, G_OPTION_ARG_STRING, &opts->encoding_profile,
1268           "Set an encoding profile from a preset file. "
1269           "See ges-launch-1.0 help profile for more information. "
1270           "This will have no effect if no outputuri has been specified.",
1271         "<profile-name>"},
1272     {"profile-from", 0, 0, G_OPTION_ARG_STRING, &opts->profile_from,
1273           "Use clip with name <clip-name> to determine the topology and profile "
1274           "of the rendered output. This will have no effect if no outputuri "
1275           "has been specified.",
1276         "<clip-name>"},
1277     {"forward-tags", 0, 0, G_OPTION_ARG_NONE, &opts->forward_tags,
1278           "Forward tags from input files to the output",
1279         NULL},
1280     {"smart-rendering", 0, 0, G_OPTION_ARG_NONE, &opts->smartrender,
1281           "Avoid reencoding when rendering. This option implies --disable-mixing.",
1282         NULL},
1283     {NULL}
1284   };
1285
1286   group = g_option_group_new ("rendering", "Rendering Options",
1287       "Show rendering options", NULL, NULL);
1288
1289   g_option_group_add_entries (group, options);
1290
1291   return group;
1292 }
1293
1294 static GOptionGroup *
1295 ges_launcher_get_playback_option_group (GESLauncherParsedOptions * opts)
1296 {
1297   GOptionGroup *group;
1298
1299   GOptionEntry options[] = {
1300     {"videosink", 'v', 0, G_OPTION_ARG_STRING, &opts->videosink,
1301         "Set the videosink used for playback.", "<videosink>"},
1302     {"audiosink", 'a', 0, G_OPTION_ARG_STRING, &opts->audiosink,
1303         "Set the audiosink used for playback.", "<audiosink>"},
1304     {"mute", 'm', 0, G_OPTION_ARG_NONE, &opts->mute,
1305         "Mute playback output. This has no effect when rendering.", NULL},
1306     {NULL}
1307   };
1308
1309   group = g_option_group_new ("playback", "Playback Options",
1310       "Show playback options", NULL, NULL);
1311
1312   g_option_group_add_entries (group, options);
1313
1314   return group;
1315 }
1316
1317 gboolean
1318 ges_launcher_parse_options (GESLauncher * self,
1319     gchar ** arguments[], gint * argc, GOptionContext * ctx, GError ** error)
1320 {
1321   gboolean res;
1322   GOptionGroup *main_group;
1323   gint nargs = 0, tmpargc;
1324   gchar **commands = NULL, *help, *tmp;
1325   GError *err = NULL;
1326   gboolean owns_ctx = ctx == NULL;
1327   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
1328   gchar *prev_videosink = opts->videosink, *prev_audiosink = opts->audiosink;
1329 /*  *INDENT-OFF* */
1330   GOptionEntry options[] = {
1331     {"disable-mixing", 0, 0, G_OPTION_ARG_NONE, &opts->disable_mixing,
1332         "Do not use mixing elements to mix layers together.", NULL
1333     },
1334     {"track-types", 't', 0, G_OPTION_ARG_CALLBACK, &_parse_track_type,
1335           "Specify the track types to be created. "
1336           "When loading a project, only relevant tracks will be added to the "
1337           "timeline.",
1338         "<track-types>"
1339     },
1340     {
1341           "video-caps",
1342           0,
1343           0,
1344           G_OPTION_ARG_STRING,
1345           &opts->video_track_caps,
1346           "Specify the track restriction caps of the video track.",
1347     },
1348     {
1349           "audio-caps",
1350           0,
1351           0,
1352           G_OPTION_ARG_STRING,
1353           &opts->audio_track_caps,
1354           "Specify the track restriction caps of the audio track.",
1355     },
1356 #ifdef HAVE_GST_VALIDATE
1357     {"set-test-file", 0, 0, G_OPTION_ARG_STRING, &opts->testfile,
1358           "ges-launch-1.0 exposes gst-validate functionalities, such as test files and scenarios."
1359           " Scenarios describe actions to execute, such as seeks or setting of "
1360           "properties. "
1361           "GES implements editing-specific actions such as adding or removing "
1362           "clips. "
1363           "See gst-validate-1.0 --help for more info about validate and "
1364           "scenarios, " "and --inspect-action-type.",
1365         "</test/file/path>"
1366     },
1367     {"set-scenario", 0, 0, G_OPTION_ARG_STRING, &opts->scenario,
1368           "ges-launch-1.0 exposes gst-validate functionalities, such as scenarios."
1369           " Scenarios describe actions to execute, such as seeks or setting of "
1370           "properties. "
1371           "GES implements editing-specific actions such as adding or removing "
1372           "clips. "
1373           "See gst-validate-1.0 --help for more info about validate and "
1374           "scenarios, " "and --inspect-action-type.",
1375         "<scenario_name>"
1376     },
1377     {"enable-validate", 0, 0, G_OPTION_ARG_NONE, &opts->enable_validate,
1378           "Run inside GstValidate.", NULL,
1379     },
1380 #endif
1381     {
1382           "embed-nesteds",
1383           0,
1384           0,
1385           G_OPTION_ARG_NONE,
1386           &opts->embed_nesteds,
1387           "Embed nested timelines when saving.",
1388           NULL,
1389     },
1390     {"no-interactive", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,
1391           &opts->interactive,
1392         "Disable interactive control via the keyboard", NULL
1393     },
1394     {NULL}
1395   };
1396 /*  *INDENT-ON* */
1397
1398   if (owns_ctx) {
1399     opts->videosink = opts->audiosink = NULL;
1400     ctx = g_option_context_new ("- plays or renders a timeline.");
1401   }
1402   tmpargc = argc ? *argc : g_strv_length (*arguments);
1403
1404   if (tmpargc > 2) {
1405     nargs = tmpargc - 2;
1406     commands = &(*arguments)[2];
1407   }
1408
1409   tmp = ges_command_line_formatter_get_help (nargs, commands);
1410   help =
1411       g_strdup_printf ("%s\n\nTimeline description format:\n\n%s", HELP_SUMMARY,
1412       tmp);
1413   g_free (tmp);
1414   g_option_context_set_summary (ctx, help);
1415   g_free (help);
1416
1417   main_group =
1418       g_option_group_new ("launcher", "launcher options",
1419       "Main launcher options", opts, NULL);
1420   g_option_group_add_entries (main_group, options);
1421   g_option_context_set_main_group (ctx, main_group);
1422   g_option_context_add_group (ctx, gst_init_get_option_group ());
1423   g_option_context_add_group (ctx, ges_init_get_option_group ());
1424   g_option_context_add_group (ctx,
1425       ges_launcher_get_project_option_group (opts));
1426   g_option_context_add_group (ctx,
1427       ges_launcher_get_rendering_option_group (opts));
1428   g_option_context_add_group (ctx,
1429       ges_launcher_get_playback_option_group (opts));
1430   g_option_context_add_group (ctx, ges_launcher_get_info_option_group (opts));
1431   g_option_context_set_ignore_unknown_options (ctx, TRUE);
1432
1433   res = g_option_context_parse_strv (ctx, arguments, &err);
1434   if (argc)
1435     *argc = tmpargc;
1436
1437   if (err)
1438     g_propagate_error (error, err);
1439
1440   opts->enable_validate |= opts->testfile || opts->scenario
1441       || g_getenv ("GST_VALIDATE_SCENARIO");
1442
1443   if (owns_ctx) {
1444     g_option_context_free (ctx);
1445     /* sinks passed in the command line are preferred. */
1446     if (prev_videosink) {
1447       g_free (opts->videosink);
1448       opts->videosink = prev_videosink;
1449     }
1450
1451     if (prev_audiosink) {
1452       g_free (opts->audiosink);
1453       opts->audiosink = prev_audiosink;
1454     }
1455     _set_playback_details (self);
1456   }
1457
1458   return res;
1459 }
1460
1461 static gboolean
1462 _local_command_line (GApplication * application, gchar ** arguments[],
1463     gint * exit_status)
1464 {
1465   gboolean res = TRUE;
1466   gint argc;
1467   GError *error = NULL;
1468   GESLauncher *self = GES_LAUNCHER (application);
1469   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
1470   GOptionContext *ctx = g_option_context_new ("- plays or renders a timeline.");
1471
1472   *exit_status = 0;
1473   argc = g_strv_length (*arguments);
1474
1475   gst_init (&argc, arguments);
1476   if (!ges_launcher_parse_options (self, arguments, &argc, ctx, &error)) {
1477     gst_init (NULL, NULL);
1478     g_option_context_free (ctx);
1479     if (error) {
1480       ges_printerr ("Error initializing: %s\n", error->message);
1481       g_error_free (error);
1482     } else {
1483       ges_printerr ("Error parsing command line arguments\n");
1484     }
1485     *exit_status = 1;
1486     goto done;
1487   }
1488
1489   if (opts->inspect_action_type) {
1490     ges_validate_print_action_types ((const gchar **) &((*arguments)[1]),
1491         argc - 1);
1492     goto done;
1493   }
1494
1495   if (!opts->load_path && !opts->scenario && !opts->testfile
1496       && !opts->list_transitions && (argc <= 1)) {
1497     gst_print ("%s", g_option_context_get_help (ctx, TRUE, NULL));
1498     g_option_context_free (ctx);
1499     *exit_status = 1;
1500     goto done;
1501   }
1502
1503   g_option_context_free (ctx);
1504
1505   opts->sanitized_timeline = sanitize_timeline_description (*arguments, opts);
1506
1507   if (!g_application_register (application, NULL, &error)) {
1508     *exit_status = 1;
1509     g_clear_error (&error);
1510     res = FALSE;
1511   }
1512
1513 done:
1514   return res;
1515 }
1516
1517 static void
1518 keyboard_cb (const gchar * key_input, gpointer user_data)
1519 {
1520   GESLauncher *self = (GESLauncher *) user_data;
1521   gchar key = '\0';
1522
1523   /* only want to switch/case on single char, not first char of string */
1524   if (key_input[0] != '\0' && key_input[1] == '\0')
1525     key = g_ascii_tolower (key_input[0]);
1526
1527   switch (key) {
1528     case 'k':
1529       print_keyboard_help ();
1530       break;
1531     case ' ':
1532       toggle_paused (self);
1533       break;
1534     case 'q':
1535     case 'Q':
1536       g_application_quit (G_APPLICATION (self));
1537       break;
1538     case '+':
1539       if (ABS (self->priv->rate) < 2.0)
1540         play_set_relative_playback_rate (self, 0.1, FALSE);
1541       else if (ABS (self->priv->rate) < 4.0)
1542         play_set_relative_playback_rate (self, 0.5, FALSE);
1543       else
1544         play_set_relative_playback_rate (self, 1.0, FALSE);
1545       break;
1546     case '-':
1547       if (ABS (self->priv->rate) <= 2.0)
1548         play_set_relative_playback_rate (self, -0.1, FALSE);
1549       else if (ABS (self->priv->rate) <= 4.0)
1550         play_set_relative_playback_rate (self, -0.5, FALSE);
1551       else
1552         play_set_relative_playback_rate (self, -1.0, FALSE);
1553       break;
1554     case 't':
1555       play_switch_trick_mode (self);
1556       break;
1557     case 27:                   /* ESC */
1558       if (key_input[1] == '\0') {
1559         g_application_quit (G_APPLICATION (self));
1560         break;
1561       }
1562     case '0':
1563       play_do_seek (self, 0, self->priv->rate, self->priv->trick_mode);
1564       break;
1565     default:
1566       if (strcmp (key_input, GST_PLAY_KB_ARROW_RIGHT) == 0) {
1567         relative_seek (self, +0.08);
1568       } else if (strcmp (key_input, GST_PLAY_KB_ARROW_LEFT) == 0) {
1569         relative_seek (self, -0.01);
1570       } else {
1571         GST_INFO ("keyboard input:");
1572         for (; *key_input != '\0'; ++key_input)
1573           GST_INFO ("  code %3d", *key_input);
1574       }
1575       break;
1576   }
1577 }
1578
1579 static void
1580 _startup (GApplication * application)
1581 {
1582   GESLauncher *self = GES_LAUNCHER (application);
1583   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
1584
1585 #ifdef G_OS_UNIX
1586   self->priv->signal_watch_id =
1587       g_unix_signal_add (SIGINT, (GSourceFunc) intr_handler, self);
1588 #endif
1589
1590   /* Initialize the GStreamer Editing Services */
1591   if (!ges_init ()) {
1592     ges_printerr ("Error initializing GES\n");
1593     goto done;
1594   }
1595
1596   if (opts->interactive && !opts->outputuri) {
1597     if (gst_play_kb_set_key_handler (keyboard_cb, self)) {
1598       gst_print ("Press 'k' to see a list of keyboard shortcuts.\n");
1599       atexit (restore_terminal);
1600     } else {
1601       gst_print ("Interactive keyboard handling in terminal not available.\n");
1602     }
1603   }
1604
1605   if (opts->list_transitions) {
1606     _print_transition_list ();
1607     goto done;
1608   }
1609
1610   if (!_create_pipeline (self, opts->sanitized_timeline))
1611     goto failure;
1612
1613   if (opts->save_only_path)
1614     goto done;
1615
1616   if (!_set_playback_details (self))
1617     goto failure;
1618
1619   if (!_run_pipeline (self))
1620     goto failure;
1621
1622 done:
1623   G_APPLICATION_CLASS (ges_launcher_parent_class)->startup (application);
1624
1625   return;
1626
1627 failure:
1628   self->priv->seenerrors = TRUE;
1629
1630   goto done;
1631 }
1632
1633 static void
1634 _shutdown (GApplication * application)
1635 {
1636   gint validate_res = 0;
1637   GESLauncher *self = GES_LAUNCHER (application);
1638   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
1639
1640   _save_timeline (self);
1641
1642   if (self->priv->pipeline) {
1643     gst_element_set_state (GST_ELEMENT (self->priv->pipeline), GST_STATE_NULL);
1644     validate_res = ges_validate_clean (GST_PIPELINE (self->priv->pipeline));
1645   }
1646
1647   if (self->priv->seenerrors == FALSE)
1648     self->priv->seenerrors = validate_res;
1649
1650 #ifdef G_OS_UNIX
1651   g_source_remove (self->priv->signal_watch_id);
1652 #endif
1653
1654   g_free (opts->sanitized_timeline);
1655
1656   G_APPLICATION_CLASS (ges_launcher_parent_class)->shutdown (application);
1657 }
1658
1659 static void
1660 _finalize (GObject * object)
1661 {
1662   GESLauncher *self = GES_LAUNCHER (object);
1663   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
1664
1665   g_free (opts->load_path);
1666   g_free (opts->save_path);
1667   g_free (opts->save_only_path);
1668   g_free (opts->outputuri);
1669   g_free (opts->format);
1670   g_free (opts->encoding_profile);
1671   g_free (opts->profile_from);
1672   g_free (opts->videosink);
1673   g_free (opts->audiosink);
1674   g_free (opts->video_track_caps);
1675   g_free (opts->audio_track_caps);
1676   g_free (opts->scenario);
1677   g_free (opts->testfile);
1678
1679   G_OBJECT_CLASS (ges_launcher_parent_class)->finalize (object);
1680 }
1681
1682 static void
1683 ges_launcher_class_init (GESLauncherClass * klass)
1684 {
1685   G_APPLICATION_CLASS (klass)->local_command_line = _local_command_line;
1686   G_APPLICATION_CLASS (klass)->startup = _startup;
1687   G_APPLICATION_CLASS (klass)->shutdown = _shutdown;
1688
1689   G_OBJECT_CLASS (klass)->finalize = _finalize;
1690 }
1691
1692 static void
1693 ges_launcher_init (GESLauncher * self)
1694 {
1695   self->priv = ges_launcher_get_instance_private (self);
1696   self->priv->parsed_options.track_types =
1697       GES_TRACK_TYPE_AUDIO | GES_TRACK_TYPE_VIDEO;
1698   self->priv->parsed_options.interactive = TRUE;
1699   self->priv->desired_state = GST_STATE_PLAYING;
1700   self->priv->rate = 1.0;
1701   self->priv->trick_mode = GST_PLAY_TRICK_MODE_NONE;
1702 }
1703
1704 gint
1705 ges_launcher_get_exit_status (GESLauncher * self)
1706 {
1707   return self->priv->seenerrors;
1708 }
1709
1710 GESLauncher *
1711 ges_launcher_new (void)
1712 {
1713   return GES_LAUNCHER (g_object_new (ges_launcher_get_type (), "application-id",
1714           "org.gstreamer.geslaunch", "flags",
1715           G_APPLICATION_NON_UNIQUE | G_APPLICATION_HANDLES_COMMAND_LINE, NULL));
1716 }