tools: Fix some naming
[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 _check_has_audio_video (GESLauncher * self, gint * n_audio, gint * n_video)
361 {
362   GList *tmp, *tracks = ges_timeline_get_tracks (self->priv->timeline);
363
364   *n_video = *n_audio = 0;
365   for (tmp = tracks; tmp; tmp = tmp->next) {
366     if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_VIDEO)
367       *n_video = *n_video + 1;
368     else if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_AUDIO)
369       *n_audio = *n_audio + 1;
370   }
371 }
372
373 #define N_INSTANCES "__n_instances"
374 static gint
375 sort_encoding_profiles (gconstpointer a, gconstpointer b)
376 {
377   const gint acount =
378       GPOINTER_TO_INT (g_object_get_data ((GObject *) a, N_INSTANCES));
379   const gint bcount =
380       GPOINTER_TO_INT (g_object_get_data ((GObject *) b, N_INSTANCES));
381
382   if (acount < bcount)
383     return -1;
384
385   if (acount == bcount)
386     return 0;
387
388   return 1;
389 }
390
391 static GstEncodingProfile *
392 get_smart_profile (GESLauncher * self)
393 {
394   gint n_audio, n_video;
395   GList *tmp, *assets = NULL, *possible_profiles = NULL;
396   GstEncodingProfile *res = NULL;
397
398   _check_has_audio_video (self, &n_audio, &n_video);
399   for (tmp = self->priv->timeline->layers; tmp; tmp = tmp->next) {
400     GList *tclip, *clips = ges_layer_get_clips (tmp->data);
401
402     for (tclip = clips; tclip; tclip = tclip->next) {
403       if (GES_IS_URI_CLIP (tclip->data))
404         assets =
405             g_list_append (assets, ges_extractable_get_asset (tclip->data));
406     }
407     g_list_free_full (clips, gst_object_unref);
408   }
409
410   for (tmp = assets; tmp; tmp = tmp->next) {
411     GESAsset *asset = tmp->data;
412     GList *audio_streams, *video_streams;
413     GstDiscovererInfo *info;
414
415     if (!GES_IS_URI_CLIP_ASSET (asset))
416       continue;
417
418     info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset));
419     audio_streams = gst_discoverer_info_get_audio_streams (info);
420     video_streams = gst_discoverer_info_get_video_streams (info);
421     if (g_list_length (audio_streams) >= n_audio
422         && g_list_length (video_streams) >= n_video) {
423       GstEncodingProfile *prof = gst_encoding_profile_from_discoverer (info);
424       GList *prevprof;
425
426       prevprof =
427           g_list_find_custom (possible_profiles, prof,
428           (GCompareFunc) gst_encoding_profile_is_equal);
429       if (prevprof) {
430         g_object_unref (prof);
431         prof = prevprof->data;
432       } else {
433         possible_profiles = g_list_prepend (possible_profiles, prof);
434       }
435
436       g_object_set_data ((GObject *) prof, N_INSTANCES,
437           GINT_TO_POINTER (GPOINTER_TO_INT (g_object_get_data ((GObject *) prof,
438                       N_INSTANCES)) + 1));
439     }
440     gst_discoverer_stream_info_list_free (audio_streams);
441     gst_discoverer_stream_info_list_free (video_streams);
442   }
443   g_list_free_full (assets, gst_object_unref);
444
445   if (possible_profiles) {
446     possible_profiles = g_list_sort (possible_profiles, sort_encoding_profiles);
447     res = gst_object_ref (possible_profiles->data);
448     g_list_free_full (possible_profiles, gst_object_unref);
449   }
450
451   return res;
452 }
453
454 static void
455 disable_bframe_for_smart_rendering_cb (GstBin * bin, GstBin * sub_bin,
456     GstElement * child)
457 {
458   GstElementFactory *factory = gst_element_get_factory (child);
459
460   if (factory && !g_strcmp0 (GST_OBJECT_NAME (factory), "x264enc")) {
461     g_object_set (child, "b-adapt", FALSE, "b-pyramid", FALSE, "bframes", 0,
462         NULL);
463   }
464 }
465
466 static gboolean
467 _set_rendering_details (GESLauncher * self)
468 {
469   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
470   gboolean smart_profile = FALSE;
471   GESPipelineFlags cmode = ges_pipeline_get_mode (self->priv->pipeline);
472   GESProject *proj;
473
474   if (cmode & GES_PIPELINE_MODE_RENDER
475       || cmode & GES_PIPELINE_MODE_SMART_RENDER) {
476     GST_INFO_OBJECT (self, "Rendering settings already set");
477     return TRUE;
478   }
479
480   proj =
481       GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (self->
482               priv->timeline)));
483
484   /* Setup profile/encoding if needed */
485   if (opts->outputuri) {
486     GstEncodingProfile *prof = NULL;
487     if (!opts->format) {
488       const GList *profiles = ges_project_list_encoding_profiles (proj);
489
490       if (profiles) {
491         prof = profiles->data;
492         if (opts->encoding_profile)
493           for (; profiles; profiles = profiles->next)
494             if (g_strcmp0 (opts->encoding_profile,
495                     gst_encoding_profile_get_name (profiles->data)) == 0)
496               prof = profiles->data;
497       }
498
499       if (prof)
500         prof = gst_object_ref (prof);
501     }
502
503     if (!prof) {
504       if (opts->format == NULL) {
505         if (opts->smartrender)
506           prof = get_smart_profile (self);
507         if (prof)
508           smart_profile = TRUE;
509         else {
510           opts->format = get_file_extension (opts->outputuri);
511           prof = parse_encoding_profile (opts->format);
512         }
513       } else {
514         prof = parse_encoding_profile (opts->format);
515         if (!prof)
516           g_error ("Invalid format specified: %s", opts->format);
517       }
518
519       if (!prof) {
520         ges_warn
521             ("No format specified and couldn't find one from output file extension, "
522             "falling back to theora+vorbis in ogg.");
523         g_free (opts->format);
524
525         opts->format =
526             g_strdup ("application/ogg:video/x-theora:audio/x-vorbis");
527         prof = parse_encoding_profile (opts->format);
528       }
529
530       if (!prof) {
531         ges_printerr ("Could not find any encoding format for %s\n",
532             opts->format);
533         return FALSE;
534       }
535
536       g_print ("\nEncoding details:\n");
537       g_print ("================\n");
538
539       g_print ("  -> Output file: %s\n", opts->outputuri);
540       g_print ("  -> Profile:%s\n",
541           smart_profile ?
542           " (selected from input files format for efficient smart rendering" :
543           "");
544       describe_encoding_profile (prof);
545       g_print ("\n");
546
547       ges_project_add_encoding_profile (proj, prof);
548     }
549
550     opts->outputuri = ensure_uri (opts->outputuri);
551     if (opts->smartrender) {
552       g_signal_connect (self->priv->pipeline, "deep-element-added",
553           G_CALLBACK (disable_bframe_for_smart_rendering_cb), NULL);
554     }
555     if (!prof
556         || !ges_pipeline_set_render_settings (self->priv->pipeline,
557             opts->outputuri, prof)
558         || !ges_pipeline_set_mode (self->priv->pipeline,
559             opts->smartrender ? GES_PIPELINE_MODE_SMART_RENDER :
560             GES_PIPELINE_MODE_RENDER)) {
561       return FALSE;
562     }
563
564     gst_encoding_profile_unref (prof);
565   } else {
566     ges_pipeline_set_mode (self->priv->pipeline, GES_PIPELINE_MODE_PREVIEW);
567   }
568   return TRUE;
569 }
570
571 static void
572 _track_set_mixing (GESTrack * track, GESLauncherParsedOptions * opts)
573 {
574   static gboolean printed_mixing_disabled = FALSE;
575
576   if (opts->disable_mixing || opts->smartrender)
577     ges_track_set_mixing (track, FALSE);
578   if (!opts->disable_mixing && opts->smartrender && !printed_mixing_disabled) {
579     g_print ("**Mixing is disabled for smart rendering to work**\n");
580     printed_mixing_disabled = TRUE;
581   }
582 }
583
584 static gboolean
585 _timeline_set_user_options (GESLauncher * self, GESTimeline * timeline,
586     const gchar * load_path)
587 {
588   GList *tmp;
589   GESTrack *tracka, *trackv;
590   gboolean has_audio = FALSE, has_video = FALSE;
591   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
592
593 retry:
594   for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
595
596     if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_VIDEO)
597       has_video = TRUE;
598     else if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_AUDIO)
599       has_audio = TRUE;
600
601     _track_set_mixing (tmp->data, opts);
602     if (!(GES_TRACK (tmp->data)->type & opts->track_types)) {
603       ges_timeline_remove_track (timeline, tmp->data);
604       goto retry;
605     }
606   }
607
608   if ((opts->scenario || opts->testfile) && !load_path) {
609     if (!has_video && opts->track_types & GES_TRACK_TYPE_VIDEO) {
610       trackv = GES_TRACK (ges_video_track_new ());
611
612       if (!_set_track_restriction_caps (trackv, opts->video_track_caps))
613         return FALSE;
614
615       _track_set_mixing (trackv, opts);
616
617       if (!(ges_timeline_add_track (timeline, trackv)))
618         return FALSE;
619     }
620
621     if (!has_audio && opts->track_types & GES_TRACK_TYPE_AUDIO) {
622       tracka = GES_TRACK (ges_audio_track_new ());
623
624       if (!_set_track_restriction_caps (tracka, opts->audio_track_caps))
625         return FALSE;
626
627       _track_set_mixing (tracka, opts);
628
629       if (!(ges_timeline_add_track (timeline, tracka)))
630         return FALSE;
631     }
632   } else {
633     _set_restriction_caps (timeline, opts);
634   }
635
636   return TRUE;
637 }
638
639 static void
640 _project_loading_error_cb (GESProject * project, GESTimeline * timeline,
641     GError * error, GESLauncher * self)
642 {
643   ges_printerr ("Error loading timeline: '%s'\n", error->message);
644   self->priv->seenerrors = TRUE;
645
646   g_application_quit (G_APPLICATION (self));
647 }
648
649 static void
650 _project_loaded_cb (GESProject * project, GESTimeline * timeline,
651     GESLauncher * self)
652 {
653   gchar *project_uri = NULL;
654   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
655   GST_INFO ("Project loaded, playing it");
656
657   if (opts->save_path) {
658     gchar *uri;
659     GError *error = NULL;
660
661     if (g_strcmp0 (opts->save_path, "+r") == 0) {
662       uri = ges_project_get_uri (project);
663     } else if (!(uri = ensure_uri (opts->save_path))) {
664       g_error ("couldn't create uri for '%s", opts->save_path);
665
666       self->priv->seenerrors = TRUE;
667       g_application_quit (G_APPLICATION (self));
668     }
669
670     g_print ("\nSaving project to %s\n", uri);
671     ges_project_save (project, timeline, uri, NULL, TRUE, &error);
672     g_free (uri);
673
674     g_assert_no_error (error);
675     if (error) {
676       self->priv->seenerrors = TRUE;
677       g_error_free (error);
678       g_application_quit (G_APPLICATION (self));
679     }
680   }
681
682   project_uri = ges_project_get_uri (project);
683
684   if (self->priv->parsed_options.load_path && project_uri
685       && ges_validate_activate (GST_PIPELINE (self->priv->pipeline),
686           self, opts) == FALSE) {
687     if (opts->scenario)
688       g_error ("Could not activate scenario %s", opts->scenario);
689     else
690       g_error ("Could not activate testfile %s", opts->testfile);
691     self->priv->seenerrors = TRUE;
692     g_application_quit (G_APPLICATION (self));
693   }
694   _timeline_set_user_options (self, timeline, project_uri);
695   if (project_uri) {
696     if (!_set_rendering_details (self))
697       g_error ("Failed to setup rendering details\n");
698   }
699
700   g_free (project_uri);
701
702   if (!self->priv->seenerrors && opts->needs_set_state &&
703       gst_element_set_state (GST_ELEMENT (self->priv->pipeline),
704           GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
705     g_error ("Failed to start the pipeline\n");
706   }
707 }
708
709 static void
710 _error_loading_asset_cb (GESProject * project, GError * error,
711     const gchar * failed_id, GType extractable_type, GESLauncher * self)
712 {
713   ges_printerr ("Error loading asset %s: %s\n", failed_id, error->message);
714   self->priv->seenerrors = TRUE;
715
716   g_application_quit (G_APPLICATION (self));
717 }
718
719 static gboolean
720 _create_timeline (GESLauncher * self, const gchar * serialized_timeline,
721     const gchar * proj_uri, gboolean validate)
722 {
723   GESProject *project;
724
725   GError *error = NULL;
726
727   if (proj_uri != NULL) {
728     project = ges_project_new (proj_uri);
729   } else if (!validate) {
730     project = ges_project_new (serialized_timeline);
731   } else {
732     project = ges_project_new (NULL);
733   }
734
735   g_signal_connect (project, "error-loading-asset",
736       G_CALLBACK (_error_loading_asset_cb), self);
737   g_signal_connect (project, "loaded", G_CALLBACK (_project_loaded_cb), self);
738   g_signal_connect (project, "error-loading",
739       G_CALLBACK (_project_loading_error_cb), self);
740
741   self->priv->timeline =
742       GES_TIMELINE (ges_asset_extract (GES_ASSET (project), &error));
743   gst_object_unref (project);
744
745   if (error) {
746     ges_printerr ("\nERROR: Could not create timeline because: %s\n\n",
747         error->message);
748     g_error_free (error);
749     return FALSE;
750   }
751
752   return TRUE;
753 }
754
755 typedef void (*SetSinkFunc) (GESPipeline * pipeline, GstElement * element);
756
757 static gboolean
758 _set_sink (GESLauncher * self, const gchar * sink_desc, SetSinkFunc set_func)
759 {
760   if (sink_desc != NULL) {
761     GError *err = NULL;
762     GstElement *sink = gst_parse_bin_from_description_full (sink_desc, TRUE,
763         NULL, GST_PARSE_FLAG_NO_SINGLE_ELEMENT_BINS, &err);
764     if (sink == NULL) {
765       GST_ERROR ("could not create the requested videosink %s (err: %s), "
766           "exiting", err ? err->message : "", sink_desc);
767       if (err)
768         g_error_free (err);
769       return FALSE;
770     }
771     set_func (self->priv->pipeline, sink);
772   }
773   return TRUE;
774 }
775
776 static gboolean
777 _set_playback_details (GESLauncher * self)
778 {
779   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
780
781   if (!_set_sink (self, opts->videosink, ges_pipeline_preview_set_video_sink) ||
782       !_set_sink (self, opts->audiosink, ges_pipeline_preview_set_audio_sink))
783     return FALSE;
784
785   return TRUE;
786 }
787
788 static void
789 bus_message_cb (GstBus * bus, GstMessage * message, GESLauncher * self)
790 {
791   switch (GST_MESSAGE_TYPE (message)) {
792     case GST_MESSAGE_WARNING:{
793       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->priv->pipeline),
794           GST_DEBUG_GRAPH_SHOW_ALL, "ges-launch.warning");
795       break;
796     }
797     case GST_MESSAGE_ERROR:{
798       GError *err = NULL;
799       gchar *dbg_info = NULL;
800
801       gst_message_parse_error (message, &err, &dbg_info);
802       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->priv->pipeline),
803           GST_DEBUG_GRAPH_SHOW_ALL, "ges-launch-error");
804       ges_printerr ("ERROR from element %s: %s\n",
805           GST_OBJECT_NAME (message->src), err->message);
806       ges_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
807       g_clear_error (&err);
808       g_free (dbg_info);
809       self->priv->seenerrors = TRUE;
810       g_application_quit (G_APPLICATION (self));
811       break;
812     }
813     case GST_MESSAGE_EOS:
814       if (!self->priv->parsed_options.ignore_eos) {
815         ges_ok ("\nDone\n");
816         g_application_quit (G_APPLICATION (self));
817       }
818       break;
819     case GST_MESSAGE_STATE_CHANGED:
820       if (GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (self->priv->pipeline)) {
821         gchar *dump_name;
822         GstState old, new, pending;
823         gchar *state_transition_name;
824
825         gst_message_parse_state_changed (message, &old, &new, &pending);
826         state_transition_name = g_strdup_printf ("%s_%s",
827             gst_element_state_get_name (old), gst_element_state_get_name (new));
828         dump_name = g_strconcat ("ges-launch.", state_transition_name, NULL);
829
830
831         GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->priv->pipeline),
832             GST_DEBUG_GRAPH_SHOW_ALL, dump_name);
833
834         g_free (dump_name);
835         g_free (state_transition_name);
836       }
837       break;
838     case GST_MESSAGE_REQUEST_STATE:
839       ges_validate_handle_request_state_change (message, G_APPLICATION (self));
840       break;
841     default:
842       break;
843   }
844 }
845
846 #ifdef G_OS_UNIX
847 static gboolean
848 intr_handler (GESLauncher * self)
849 {
850   g_print ("interrupt received.\n");
851
852   GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->priv->pipeline),
853       GST_DEBUG_GRAPH_SHOW_ALL, "ges-launch.interrupted");
854
855   g_application_quit (G_APPLICATION (self));
856
857   /* remove signal handler */
858   return TRUE;
859 }
860 #endif /* G_OS_UNIX */
861
862 static gboolean
863 _save_timeline (GESLauncher * self)
864 {
865   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
866
867
868   if (opts->embed_nesteds) {
869     GList *tmp, *assets;
870     GESProject *proj =
871         GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (self->priv->
872                 timeline)));
873
874     assets = ges_project_list_assets (proj, GES_TYPE_URI_CLIP);
875     for (tmp = assets; tmp; tmp = tmp->next) {
876       gboolean is_nested;
877
878       g_object_get (tmp->data, "is-nested-timeline", &is_nested, NULL);
879       if (is_nested) {
880         GESAsset *subproj =
881             ges_asset_request (GES_TYPE_TIMELINE, ges_asset_get_id (tmp->data),
882             NULL);
883
884         ges_project_add_asset (proj, subproj);
885       }
886     }
887     g_list_free_full (assets, gst_object_unref);
888   }
889
890   if (opts->save_only_path) {
891     gchar *uri;
892
893     if (!(uri = ensure_uri (opts->save_only_path))) {
894       g_error ("couldn't create uri for '%s", opts->save_only_path);
895       return FALSE;
896     }
897
898     return ges_timeline_save_to_uri (self->priv->timeline, uri, NULL, TRUE,
899         NULL);
900   }
901
902   if (opts->save_path && !opts->load_path) {
903     gchar *uri;
904     if (!(uri = ensure_uri (opts->save_path))) {
905       g_error ("couldn't create uri for '%s", opts->save_path);
906       return FALSE;
907     }
908
909     return ges_timeline_save_to_uri (self->priv->timeline, uri, NULL, TRUE,
910         NULL);
911   }
912
913   return TRUE;
914 }
915
916 static gboolean
917 _run_pipeline (GESLauncher * self)
918 {
919   GstBus *bus;
920   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
921
922   if (!opts->load_path) {
923     g_clear_pointer (&opts->sanitized_timeline, &g_free);
924     if (ges_validate_activate (GST_PIPELINE (self->priv->pipeline),
925             self, opts) == FALSE) {
926       g_error ("Could not activate scenario %s", opts->scenario);
927       return FALSE;
928     }
929
930     if (opts->sanitized_timeline) {
931       GESProject *project = ges_project_new (opts->sanitized_timeline);
932
933       if (!ges_project_load (project, self->priv->timeline, NULL)) {
934         ges_printerr ("Could not load timeline: %s\n",
935             opts->sanitized_timeline);
936         return FALSE;
937       }
938     }
939
940     if (!_timeline_set_user_options (self, self->priv->timeline, NULL)) {
941       ges_printerr ("Could not properly set tracks\n");
942       return FALSE;
943     }
944
945     if (!_set_rendering_details (self)) {
946       g_error ("Failed to setup rendering details\n");
947       return FALSE;
948     }
949
950     print_timeline (self->priv->timeline);
951   }
952
953   bus = gst_pipeline_get_bus (GST_PIPELINE (self->priv->pipeline));
954   gst_bus_add_signal_watch (bus);
955   g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), self);
956
957   if (!opts->load_path) {
958     if (opts->needs_set_state
959         && gst_element_set_state (GST_ELEMENT (self->priv->pipeline),
960             GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
961       g_error ("Failed to start the pipeline\n");
962       return FALSE;
963     }
964   }
965   g_application_hold (G_APPLICATION (self));
966
967   return TRUE;
968 }
969
970 static gboolean
971 _create_pipeline (GESLauncher * self, const gchar * serialized_timeline)
972 {
973   gchar *uri = NULL;
974   gboolean res = TRUE;
975   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
976
977   /* Timeline creation */
978   if (opts->load_path) {
979     g_printf ("Loading project from : %s\n", opts->load_path);
980
981     if (!(uri = ensure_uri (opts->load_path))) {
982       g_error ("couldn't create uri for '%s'", opts->load_path);
983       goto failure;
984     }
985   }
986
987   self->priv->pipeline = ges_pipeline_new ();
988
989   if (!_create_timeline (self, serialized_timeline, uri, opts->scenario
990           || opts->testfile)) {
991     GST_ERROR ("Could not create the timeline");
992     goto failure;
993   }
994
995   if (!opts->load_path)
996     ges_timeline_commit (self->priv->timeline);
997
998   /* save project if path is given. we do this now in case GES crashes or
999    * hangs during playback. */
1000   if (!_save_timeline (self))
1001     goto failure;
1002
1003   if (opts->save_only_path)
1004     goto done;
1005
1006   /* In order to view our timeline, let's grab a convenience pipeline to put
1007    * our timeline in. */
1008
1009   if (opts->mute) {
1010     GstElement *sink = gst_element_factory_make ("fakesink", NULL);
1011
1012     g_object_set (sink, "sync", TRUE, NULL);
1013     ges_pipeline_preview_set_audio_sink (self->priv->pipeline, sink);
1014
1015     sink = gst_element_factory_make ("fakesink", NULL);
1016     g_object_set (sink, "sync", TRUE, NULL);
1017     ges_pipeline_preview_set_video_sink (self->priv->pipeline, sink);
1018   }
1019
1020   /* Add the timeline to that pipeline */
1021   if (!ges_pipeline_set_timeline (self->priv->pipeline, self->priv->timeline))
1022     goto failure;
1023
1024 done:
1025   if (uri)
1026     g_free (uri);
1027
1028   return res;
1029
1030 failure:
1031   {
1032     if (self->priv->timeline)
1033       gst_object_unref (self->priv->timeline);
1034     if (self->priv->pipeline)
1035       gst_object_unref (self->priv->pipeline);
1036     self->priv->pipeline = NULL;
1037     self->priv->timeline = NULL;
1038
1039     res = FALSE;
1040     goto done;
1041   }
1042 }
1043
1044 static void
1045 _print_transition_list (void)
1046 {
1047   print_enum (GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE);
1048 }
1049
1050 static GOptionGroup *
1051 ges_launcher_get_project_option_group (GESLauncherParsedOptions * opts)
1052 {
1053   GOptionGroup *group;
1054
1055   GOptionEntry options[] = {
1056     {"load", 'l', 0, G_OPTION_ARG_STRING, &opts->load_path,
1057           "Load project from file. The project can be saved "
1058           "again with the --save option.",
1059         "<path>"},
1060     {"save", 's', 0, G_OPTION_ARG_STRING, &opts->save_path,
1061           "Save project to file before rendering. "
1062           "It can then be loaded with the --load option",
1063         "<path>"},
1064     {"save-only", 0, 0, G_OPTION_ARG_STRING, &opts->save_only_path,
1065           "Same as save project, except exit as soon as the timeline "
1066           "is saved instead of playing it back",
1067         "<path>"},
1068     {NULL}
1069   };
1070   group = g_option_group_new ("project", "Project Options",
1071       "Show project-related options", NULL, NULL);
1072
1073   g_option_group_add_entries (group, options);
1074
1075   return group;
1076 }
1077
1078 static GOptionGroup *
1079 ges_launcher_get_info_option_group (GESLauncherParsedOptions * opts)
1080 {
1081   GOptionGroup *group;
1082
1083   GOptionEntry options[] = {
1084 #ifdef HAVE_GST_VALIDATE
1085     {"inspect-action-type", 0, 0, G_OPTION_ARG_NONE, &opts->inspect_action_type,
1086           "Inspect the available action types that can be defined in a scenario "
1087           "set with --set-scenario. "
1088           "Will list all action-types if action-type is empty.",
1089         "<[action-type]>"},
1090 #endif
1091     {"list-transitions", 0, 0, G_OPTION_ARG_NONE, &opts->list_transitions,
1092           "List all valid transition types and exit. "
1093           "See ges-launch-1.0 help transition for more information.",
1094         NULL},
1095     {NULL}
1096   };
1097
1098   group = g_option_group_new ("informative", "Informative Options",
1099       "Show informative options", NULL, NULL);
1100
1101   g_option_group_add_entries (group, options);
1102
1103   return group;
1104 }
1105
1106 static GOptionGroup *
1107 ges_launcher_get_rendering_option_group (GESLauncherParsedOptions * opts)
1108 {
1109   GOptionGroup *group;
1110
1111   GOptionEntry options[] = {
1112     {"outputuri", 'o', 0, G_OPTION_ARG_STRING, &opts->outputuri,
1113           "If set, ges-launch-1.0 will render the timeline instead of playing "
1114           "it back. If no format `--format` is specified, the outputuri extension"
1115           " will be used to determine an encoding format, or default to theora+vorbis"
1116           " in ogg if that doesn't work out.",
1117         "<URI>"},
1118     {"format", 'f', 0, G_OPTION_ARG_STRING, &opts->format,
1119           "Set an encoding profile on the command line. "
1120           "See ges-launch-1.0 help profile for more information. "
1121           "This will have no effect if no outputuri has been specified.",
1122         "<profile>"},
1123     {"encoding-profile", 'e', 0, G_OPTION_ARG_STRING, &opts->encoding_profile,
1124           "Set an encoding profile from a preset file. "
1125           "See ges-launch-1.0 help profile for more information. "
1126           "This will have no effect if no outputuri has been specified.",
1127         "<profile-name>"},
1128     {"smart-rendering", 0, 0, G_OPTION_ARG_NONE, &opts->smartrender,
1129           "Avoid reencoding when rendering. This option implies --disable-mixing.",
1130         NULL},
1131     {NULL}
1132   };
1133
1134   group = g_option_group_new ("rendering", "Rendering Options",
1135       "Show rendering options", NULL, NULL);
1136
1137   g_option_group_add_entries (group, options);
1138
1139   return group;
1140 }
1141
1142 static GOptionGroup *
1143 ges_launcher_get_playback_option_group (GESLauncherParsedOptions * opts)
1144 {
1145   GOptionGroup *group;
1146
1147   GOptionEntry options[] = {
1148     {"videosink", 'v', 0, G_OPTION_ARG_STRING, &opts->videosink,
1149         "Set the videosink used for playback.", "<videosink>"},
1150     {"audiosink", 'a', 0, G_OPTION_ARG_STRING, &opts->audiosink,
1151         "Set the audiosink used for playback.", "<audiosink>"},
1152     {"mute", 'm', 0, G_OPTION_ARG_NONE, &opts->mute,
1153         "Mute playback output. This has no effect when rendering.", NULL},
1154     {NULL}
1155   };
1156
1157   group = g_option_group_new ("playback", "Playback Options",
1158       "Show playback options", NULL, NULL);
1159
1160   g_option_group_add_entries (group, options);
1161
1162   return group;
1163 }
1164
1165 gboolean
1166 ges_launcher_parse_options (GESLauncher * self,
1167     gchar ** arguments[], gint * argc, GOptionContext * ctx, GError ** error)
1168 {
1169   gboolean res;
1170   GOptionGroup *main_group;
1171   gint nargs = 0, tmpargc;
1172   gchar **commands = NULL, *help, *tmp;
1173   GError *err = NULL;
1174   gboolean owns_ctx = ctx == NULL;
1175   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
1176   gchar *prev_videosink = opts->videosink, *prev_audiosink = opts->audiosink;
1177 /*  *INDENT-OFF* */
1178   GOptionEntry options[] = {
1179     {"disable-mixing", 0, 0, G_OPTION_ARG_NONE, &opts->disable_mixing,
1180         "Do not use mixing elements to mix layers together.", NULL
1181     },
1182     {"track-types", 't', 0, G_OPTION_ARG_CALLBACK, &_parse_track_type,
1183           "Specify the track types to be created. "
1184           "When loading a project, only relevant tracks will be added to the "
1185           "timeline.",
1186         "<track-types>"
1187     },
1188     {
1189           "video-caps",
1190           0,
1191           0,
1192           G_OPTION_ARG_STRING,
1193           &opts->video_track_caps,
1194           "Specify the track restriction caps of the video track.",
1195     },
1196     {
1197           "audio-caps",
1198           0,
1199           0,
1200           G_OPTION_ARG_STRING,
1201           &opts->audio_track_caps,
1202           "Specify the track restriction caps of the audio track.",
1203     },
1204 #ifdef HAVE_GST_VALIDATE
1205     {"set-test-file", 0, 0, G_OPTION_ARG_STRING, &opts->testfile,
1206           "ges-launch-1.0 exposes gst-validate functionalities, such as test files and scenarios."
1207           " Scenarios describe actions to execute, such as seeks or setting of "
1208           "properties. "
1209           "GES implements editing-specific actions such as adding or removing "
1210           "clips. "
1211           "See gst-validate-1.0 --help for more info about validate and "
1212           "scenarios, " "and --inspect-action-type.",
1213         "</test/file/path>"
1214     },
1215     {"set-scenario", 0, 0, G_OPTION_ARG_STRING, &opts->scenario,
1216           "ges-launch-1.0 exposes gst-validate functionalities, such as scenarios."
1217           " Scenarios describe actions to execute, such as seeks or setting of "
1218           "properties. "
1219           "GES implements editing-specific actions such as adding or removing "
1220           "clips. "
1221           "See gst-validate-1.0 --help for more info about validate and "
1222           "scenarios, " "and --inspect-action-type.",
1223         "<scenario_name>"
1224     },
1225     {"disable-validate", 'n', 0, G_OPTION_ARG_NONE, &opts->disable_validate,
1226           "Do not run inside GstValidate.",
1227         "<scenario_name>"
1228     },
1229 #endif
1230     {
1231           "embed-nesteds",
1232           0,
1233           0,
1234           G_OPTION_ARG_NONE,
1235           &opts->embed_nesteds,
1236           "Embed nested timelines when saving.",
1237     },
1238     {"no-interactive", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,
1239           &opts->interactive,
1240         "Disable interactive control via the keyboard", NULL
1241     },
1242     {NULL}
1243   };
1244 /*  *INDENT-ON* */
1245
1246   if (owns_ctx) {
1247     opts->videosink = opts->audiosink = NULL;
1248     ctx = g_option_context_new ("- plays or renders a timeline.");
1249   }
1250   tmpargc = argc ? *argc : g_strv_length (*arguments);
1251
1252   if (tmpargc > 2) {
1253     nargs = tmpargc - 2;
1254     commands = &(*arguments)[2];
1255   }
1256
1257   tmp = ges_command_line_formatter_get_help (nargs, commands);
1258   help =
1259       g_strdup_printf ("%s\n\nTimeline description format:\n\n%s", HELP_SUMMARY,
1260       tmp);
1261   g_free (tmp);
1262   g_option_context_set_summary (ctx, help);
1263   g_free (help);
1264
1265   main_group =
1266       g_option_group_new ("launcher", "launcher options",
1267       "Main launcher options", opts, NULL);
1268   g_option_group_add_entries (main_group, options);
1269   g_option_context_set_main_group (ctx, main_group);
1270   g_option_context_add_group (ctx, gst_init_get_option_group ());
1271   g_option_context_add_group (ctx, ges_init_get_option_group ());
1272   g_option_context_add_group (ctx,
1273       ges_launcher_get_project_option_group (opts));
1274   g_option_context_add_group (ctx,
1275       ges_launcher_get_rendering_option_group (opts));
1276   g_option_context_add_group (ctx,
1277       ges_launcher_get_playback_option_group (opts));
1278   g_option_context_add_group (ctx, ges_launcher_get_info_option_group (opts));
1279   g_option_context_set_ignore_unknown_options (ctx, TRUE);
1280
1281   res = g_option_context_parse_strv (ctx, arguments, &err);
1282   if (argc)
1283     *argc = tmpargc;
1284
1285   if (err)
1286     g_propagate_error (error, err);
1287
1288   if (owns_ctx) {
1289     g_option_context_free (ctx);
1290     /* sinks passed in the command line are preferred. */
1291     if (prev_videosink) {
1292       g_free (opts->videosink);
1293       opts->videosink = prev_videosink;
1294     }
1295
1296     if (prev_audiosink) {
1297       g_free (opts->audiosink);
1298       opts->audiosink = prev_audiosink;
1299     }
1300     _set_playback_details (self);
1301   }
1302
1303   return res;
1304 }
1305
1306 static gboolean
1307 _local_command_line (GApplication * application, gchar ** arguments[],
1308     gint * exit_status)
1309 {
1310   gboolean res = TRUE;
1311   gint argc;
1312   GError *error = NULL;
1313   GESLauncher *self = GES_LAUNCHER (application);
1314   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
1315   GOptionContext *ctx = g_option_context_new ("- plays or renders a timeline.");
1316
1317   *exit_status = 0;
1318   argc = g_strv_length (*arguments);
1319
1320   gst_init (&argc, arguments);
1321   if (!ges_launcher_parse_options (self, arguments, &argc, ctx, &error)) {
1322     gst_init (NULL, NULL);
1323     g_option_context_free (ctx);
1324     if (error) {
1325       ges_printerr ("Error initializing: %s\n", error->message);
1326       g_error_free (error);
1327     } else {
1328       ges_printerr ("Error parsing command line arguments\n");
1329     }
1330     *exit_status = 1;
1331     goto done;
1332   }
1333
1334   if (opts->inspect_action_type) {
1335     ges_validate_print_action_types ((const gchar **) &((*arguments)[1]),
1336         argc - 1);
1337     goto done;
1338   }
1339
1340   if (!opts->load_path && !opts->scenario && !opts->testfile
1341       && !opts->list_transitions && (argc <= 1)) {
1342     g_printf ("%s", g_option_context_get_help (ctx, TRUE, NULL));
1343     g_option_context_free (ctx);
1344     *exit_status = 1;
1345     goto done;
1346   }
1347
1348   g_option_context_free (ctx);
1349
1350   opts->sanitized_timeline = sanitize_timeline_description (*arguments, opts);
1351
1352   if (!g_application_register (application, NULL, &error)) {
1353     *exit_status = 1;
1354     g_clear_error (&error);
1355     res = FALSE;
1356   }
1357
1358 done:
1359   return res;
1360 }
1361
1362 static void
1363 keyboard_cb (const gchar * key_input, gpointer user_data)
1364 {
1365   GESLauncher *self = (GESLauncher *) user_data;
1366   gchar key = '\0';
1367
1368   /* only want to switch/case on single char, not first char of string */
1369   if (key_input[0] != '\0' && key_input[1] == '\0')
1370     key = g_ascii_tolower (key_input[0]);
1371
1372   switch (key) {
1373     case 'k':
1374       print_keyboard_help ();
1375       break;
1376     case ' ':
1377       toggle_paused (self);
1378       break;
1379     case 'q':
1380     case 'Q':
1381       g_application_quit (G_APPLICATION (self));
1382       break;
1383     case '+':
1384       if (ABS (self->priv->rate) < 2.0)
1385         play_set_relative_playback_rate (self, 0.1, FALSE);
1386       else if (ABS (self->priv->rate) < 4.0)
1387         play_set_relative_playback_rate (self, 0.5, FALSE);
1388       else
1389         play_set_relative_playback_rate (self, 1.0, FALSE);
1390       break;
1391     case '-':
1392       if (ABS (self->priv->rate) <= 2.0)
1393         play_set_relative_playback_rate (self, -0.1, FALSE);
1394       else if (ABS (self->priv->rate) <= 4.0)
1395         play_set_relative_playback_rate (self, -0.5, FALSE);
1396       else
1397         play_set_relative_playback_rate (self, -1.0, FALSE);
1398       break;
1399     case 't':
1400       play_switch_trick_mode (self);
1401       break;
1402     case 27:                   /* ESC */
1403       if (key_input[1] == '\0') {
1404         g_application_quit (G_APPLICATION (self));
1405         break;
1406       }
1407     case '0':
1408       play_do_seek (self, 0, self->priv->rate, self->priv->trick_mode);
1409       break;
1410     default:
1411       if (strcmp (key_input, GST_PLAY_KB_ARROW_RIGHT) == 0) {
1412         relative_seek (self, +0.08);
1413       } else if (strcmp (key_input, GST_PLAY_KB_ARROW_LEFT) == 0) {
1414         relative_seek (self, -0.01);
1415       } else {
1416         GST_INFO ("keyboard input:");
1417         for (; *key_input != '\0'; ++key_input)
1418           GST_INFO ("  code %3d", *key_input);
1419       }
1420       break;
1421   }
1422 }
1423
1424 static void
1425 _startup (GApplication * application)
1426 {
1427   GESLauncher *self = GES_LAUNCHER (application);
1428   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
1429
1430 #ifdef G_OS_UNIX
1431   self->priv->signal_watch_id =
1432       g_unix_signal_add (SIGINT, (GSourceFunc) intr_handler, self);
1433 #endif
1434
1435   /* Initialize the GStreamer Editing Services */
1436   if (!ges_init ()) {
1437     ges_printerr ("Error initializing GES\n");
1438     goto done;
1439   }
1440
1441   if (opts->interactive && !opts->outputuri) {
1442     if (gst_play_kb_set_key_handler (keyboard_cb, self)) {
1443       gst_print ("Press 'k' to see a list of keyboard shortcuts.\n");
1444       atexit (restore_terminal);
1445     } else {
1446       gst_print ("Interactive keyboard handling in terminal not available.\n");
1447     }
1448   }
1449
1450   if (opts->list_transitions) {
1451     _print_transition_list ();
1452     goto done;
1453   }
1454
1455   if (!_create_pipeline (self, opts->sanitized_timeline))
1456     goto failure;
1457
1458   if (opts->save_only_path)
1459     goto done;
1460
1461   if (!_set_playback_details (self))
1462     goto failure;
1463
1464   if (!_run_pipeline (self))
1465     goto failure;
1466
1467 done:
1468   G_APPLICATION_CLASS (ges_launcher_parent_class)->startup (application);
1469
1470   return;
1471
1472 failure:
1473   self->priv->seenerrors = TRUE;
1474
1475   goto done;
1476 }
1477
1478 static void
1479 _shutdown (GApplication * application)
1480 {
1481   gint validate_res = 0;
1482   GESLauncher *self = GES_LAUNCHER (application);
1483   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
1484
1485   _save_timeline (self);
1486
1487   if (self->priv->pipeline) {
1488     gst_element_set_state (GST_ELEMENT (self->priv->pipeline), GST_STATE_NULL);
1489     validate_res = ges_validate_clean (GST_PIPELINE (self->priv->pipeline));
1490   }
1491
1492   if (self->priv->seenerrors == FALSE)
1493     self->priv->seenerrors = validate_res;
1494
1495 #ifdef G_OS_UNIX
1496   g_source_remove (self->priv->signal_watch_id);
1497 #endif
1498
1499   g_free (opts->sanitized_timeline);
1500
1501   G_APPLICATION_CLASS (ges_launcher_parent_class)->shutdown (application);
1502 }
1503
1504 static void
1505 _finalize (GObject * object)
1506 {
1507   GESLauncher *self = GES_LAUNCHER (object);
1508   GESLauncherParsedOptions *opts = &self->priv->parsed_options;
1509
1510   g_free (opts->load_path);
1511   g_free (opts->save_path);
1512   g_free (opts->save_only_path);
1513   g_free (opts->outputuri);
1514   g_free (opts->format);
1515   g_free (opts->encoding_profile);
1516   g_free (opts->videosink);
1517   g_free (opts->audiosink);
1518   g_free (opts->video_track_caps);
1519   g_free (opts->audio_track_caps);
1520   g_free (opts->scenario);
1521   g_free (opts->testfile);
1522
1523   G_OBJECT_CLASS (ges_launcher_parent_class)->finalize (object);
1524 }
1525
1526 static void
1527 ges_launcher_class_init (GESLauncherClass * klass)
1528 {
1529   G_APPLICATION_CLASS (klass)->local_command_line = _local_command_line;
1530   G_APPLICATION_CLASS (klass)->startup = _startup;
1531   G_APPLICATION_CLASS (klass)->shutdown = _shutdown;
1532
1533   G_OBJECT_CLASS (klass)->finalize = _finalize;
1534 }
1535
1536 static void
1537 ges_launcher_init (GESLauncher * self)
1538 {
1539   self->priv = ges_launcher_get_instance_private (self);
1540   self->priv->parsed_options.track_types =
1541       GES_TRACK_TYPE_AUDIO | GES_TRACK_TYPE_VIDEO;
1542   self->priv->parsed_options.interactive = TRUE;
1543   self->priv->desired_state = GST_STATE_PLAYING;
1544   self->priv->rate = 1.0;
1545   self->priv->trick_mode = GST_PLAY_TRICK_MODE_NONE;
1546 }
1547
1548 gint
1549 ges_launcher_get_exit_status (GESLauncher * self)
1550 {
1551   return self->priv->seenerrors;
1552 }
1553
1554 GESLauncher *
1555 ges_launcher_new (void)
1556 {
1557   return GES_LAUNCHER (g_object_new (ges_launcher_get_type (), "application-id",
1558           "org.gstreamer.geslaunch", "flags",
1559           G_APPLICATION_NON_UNIQUE | G_APPLICATION_HANDLES_COMMAND_LINE, NULL));
1560 }