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