ges: Add keyframe support to the command line formatter
[platform/upstream/gst-editing-services.git] / ges / ges-command-line-formatter.c
1 /* GStreamer Editing Services
2  *
3  * Copyright (C) <2015> Thibault Saunier <tsaunier@gnome.org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "ges-command-line-formatter.h"
25
26 #include "ges/ges-structured-interface.h"
27 #include "ges-structure-parser.h"
28 #include "ges-internal.h"
29 #define YY_NO_UNISTD_H
30 #include "ges-parse-lex.h"
31
32 struct _GESCommandLineFormatterPrivate
33 {
34   gpointer dummy;
35 };
36
37
38 G_DEFINE_TYPE_WITH_PRIVATE (GESCommandLineFormatter, ges_command_line_formatter,
39     GES_TYPE_FORMATTER);
40
41 static gboolean
42 _ges_command_line_formatter_add_clip (GESTimeline * timeline,
43     GstStructure * structure, GError ** error);
44 static gboolean
45 _ges_command_line_formatter_add_effect (GESTimeline * timeline,
46     GstStructure * structure, GError ** error);
47 static gboolean
48 _ges_command_line_formatter_add_test_clip (GESTimeline * timeline,
49     GstStructure * structure, GError ** error);
50 static gboolean
51 _ges_command_line_formatter_add_title_clip (GESTimeline * timeline,
52     GstStructure * structure, GError ** error);
53 static gboolean
54 _ges_command_line_formatter_add_track (GESTimeline * timeline,
55     GstStructure * structure, GError ** error);
56 static gboolean
57 _ges_command_line_formatter_add_keyframes (GESTimeline * timeline,
58     GstStructure * structure, GError ** error);
59
60 typedef struct
61 {
62   const gchar *long_name;
63   const gchar *short_name;
64   GType type;
65   const gchar *new_name;
66   const gchar *desc;
67 } Property;
68
69 // Currently Clip has the most properties.. adapt as needed
70 #define MAX_PROPERTIES 8
71 typedef struct
72 {
73   const gchar *long_name;
74   gchar short_name;
75   ActionFromStructureFunc callback;
76   const gchar *synopsis;
77   const gchar *description;
78   const gchar *examples;
79   /* The first property must be the ID on the command line */
80   Property properties[MAX_PROPERTIES];
81 } GESCommandLineOption;
82
83 /*  *INDENT-OFF* */
84 static GESCommandLineOption options[] = {
85   {
86     .long_name = "clip",
87     .short_name='c',
88     .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_clip,
89     .synopsis="<clip uri>",
90     .description="Adds a clip in the timeline. "
91                  "See documentation for the --track-types option to ges-launch-1.0, as it "
92                  " will affect the result of this command.",
93     .examples="    ges-launch-1.0 +clip /path/to/media\n\n"
94               "This will simply play the sample from its beginning to its end.\n\n"
95               "    ges-launch-1.0 +clip /path/to/media inpoint=4.0\n\n"
96               "Assuming 'media' is a 10 second long media sample, this will play the sample\n"
97               "from the 4th second to the 10th, resulting in a 6-seconds long playback.\n\n"
98               "    ges-launch-1.0 +clip /path/to/media inpoint=4.0 duration=2.0 start=4.0\n\n"
99               "Assuming \"media\" is an audio video sample longer than 6 seconds, this will play\n"
100               "a black frame and silence for 4 seconds, then the sample from its 4th second to\n"
101               "its sixth second, resulting in a 6-seconds long playback.\n\n"
102               "    ges-launch-1.0 --track-types=audio +clip /path/to/media\n\n"
103               "Assuming \"media\" is an audio video sample, this will only play the audio of the\n"
104               "sample in its entirety.\n\n"
105               "    ges-launch-1.0 +clip /path/to/media1 layer=1 set-alpha 0.9 +clip /path/to/media2 layer=0\n\n"
106               "Assume media1 and media2 both contain audio and video and last for 10 seconds.\n\n"
107               "This will first add media1 in a new layer of \"priority\" 1, thus implicitly\n"
108               "creating a layer of \"priority\" 0, the start of the clip will be 0 as no clip\n"
109               "had been added in that layer before.\n\n"
110               "It will then add media2 in the layer of \"priority\" 0 which was created\n"
111               "previously, the start of this new clip will also be 0 as no clip has been added\n"
112               "in this layer before.\n\n"
113               "Both clips will thus overlap on two layers for 10 seconds.\n\n"
114               "The \"alpha\" property of the second clip will finally be set to a value of 0.9.\n\n"
115               "All this will result in a 10 seconds playback, where media2 is barely visible\n"
116               "through media1, which is nearly opaque. If alpha was set to 0.5, both clips\n"
117               "would be equally visible, and if it was set to 0.0, media1 would be invisible\n"
118               "and media2 completely opaque.\n",
119     .properties={
120       {
121         "uri", 0, 0, "asset-id",
122         "The URI of the media file."
123       },
124       {
125         "name", "n", 0, NULL,
126         "The name of the clip, can be used as an ID later."
127       },
128       {
129         "start", "s", GST_TYPE_CLOCK_TIME, NULL,
130         "The starting position of the clip in the timeline."
131       },
132       {
133         "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
134         "The duration of the clip."
135       },
136       {
137         "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
138         "The inpoint of the clip (time in the input file to start playing from)."
139       },
140       {
141         "track-types", "tt", 0, NULL,
142         "The type of the tracks where the clip should be used (audio or video or audio+video)."
143       },
144       {
145         "layer", "l", 0, NULL,
146         "The priority of the layer into which the clip should be added."
147       },
148       {NULL, 0, 0, NULL, FALSE},
149     },
150   },
151   {
152     .long_name="effect",
153     .short_name='e',
154     .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_effect,
155     .synopsis="<effect bin description>",
156     .description="Adds an effect as specified by 'bin-description', similar to gst-launch-style"
157                  " pipeline description, without setting properties (see `set-<property-name>` for information"
158                  " about how to set properties).",
159     .examples="    ges-launch-1.0 +clip /path/to/media +effect \"agingtv\"\n\n"
160               "This will apply the agingtv effect to \"media\" and play it back.",
161     {
162       {
163         "bin-description", "d", 0, "asset-id",
164         "gst-launch style bin description."
165       },
166       {
167         "element-name", "e", 0, NULL,
168         "The name of the element to apply the effect on."
169       },
170       {
171         "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
172         "Implies that the effect has 'internal content'"
173         "(see [ges_track_element_set_has_internal_source](ges_track_element_set_has_internal_source))",
174       },
175       {
176         "name", "n", 0, "child-name",
177         "The name to be given to the effect."
178       },
179       {NULL, NULL, 0, NULL, FALSE},
180     },
181   },
182   {
183     .long_name="test-clip",
184     .short_name=0,
185     .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_test_clip,
186     .synopsis="<test clip pattern>",
187     .description="Add a test clip in the timeline.",
188     .examples=NULL,
189     .properties={
190       {
191         "vpattern", "p", 0, NULL,
192         "The testsource pattern name."
193       },
194       {
195         "name", "n", 0, NULL,
196         "The name of the clip, can be used as an ID later."
197       },
198       {
199         "start", "s", GST_TYPE_CLOCK_TIME, NULL,
200         "The starting position of the clip in the timeline."
201       },
202       {
203         "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
204         "The duration of the clip."
205       },
206       {
207         "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
208         "The inpoint of the clip (time in the input file to start playing)."
209       },
210       {
211         "layer", "l", 0, NULL,
212         "The priority of the layer into which the clip should be added."
213       },
214       {NULL, 0, 0, NULL, FALSE},
215     },
216   },
217   {
218     .long_name="title",
219     .short_name='c',
220     .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_title_clip,
221     .synopsis="<title text>",
222     .description="Adds a clip in the timeline.",
223     .examples=NULL,
224     .properties={
225       {
226         "text", "t", 0, NULL,
227         "The text to be used as title."
228       },
229       {
230         "name", "n", 0, NULL,
231         "The name of the clip, can be used as an ID later."
232       },
233       {
234         "start", "s",GST_TYPE_CLOCK_TIME, NULL,
235         "The starting position of the clip in the timeline."
236       },
237       {
238         "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
239         "The duration of the clip."
240       },
241       {
242         "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
243         "The inpoint of the clip (time in the input file to start playing from)."
244       },
245       {
246         "track-types", "tt", 0, NULL,
247         "The type of the tracks where the clip should be used (audio or video or audio+video)."
248       },
249       {
250         "layer", "l", 0, NULL,
251         "The priority of the layer into which the clip should be added."
252       },
253       {NULL, 0, 0, NULL, FALSE},
254     },
255   },
256   {
257     .long_name="track",
258     .short_name='t',
259     .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_track,
260     .synopsis="<track type>",
261     .description="Adds a track to the timeline.",
262     .examples=NULL,
263     .properties={
264       {"track-type", 0, 0, NULL, NULL},
265       {
266         "restrictions", "r", 0, NULL,
267         "The restriction caps to set on the track."
268       },
269       {NULL, 0, 0, NULL, FALSE},
270     },
271   },
272   {
273     .long_name="keyframes",
274     .short_name='k',
275     .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_keyframes,
276     .synopsis="<property name>",
277     .description="Adds keyframes for the specified property in the form:\n\n",
278     .examples="    ges-launch-1.0 +test-clip blue d=1.0 +keyframes posx 0=0 1.0=1280 t=direct-absolute +k posy 0=0 1.0=720 t=direct-absolute\n\n"
279               "This add a testclip that will disappear in the bottom right corner",
280     .properties={
281       {"property-name", 0, 0, NULL, NULL},
282       {
283         "binding-type", "t", 0, NULL,
284         "The type of binding to use, eg. 'direct-absolute', 'direct'"
285       },
286       {
287         "interpolation-mode", "m", 0, NULL,
288         "The GstInterpolationMode to user."
289       },
290       {
291         "...", 0, 0, NULL,
292         "The list of keyframe_timestamp=value to be set."
293       },
294       {NULL, 0, 0, NULL, FALSE},
295     },
296   },
297   {
298     .long_name="set-",
299     .short_name=0,
300     .callback=NULL,
301     .synopsis="<property name> <value>",
302     .description="Set a property on the last added element."
303                  " Any child property that exists on the previously added element"
304                  " can be used as <property name>"
305                  "By default, set-<property-name> will lookup the property on the last added"
306                   "object.",
307     .examples="    ges-launch-1.0 +clip /path/to/media set-alpha 0.3\n\n"
308               "This will set the alpha property on \"media\" then play it back, assuming \"media\""
309               "contains a video stream.\n\n"
310               "    ges-launch-1.0 +clip /path/to/media +effect \"agingtv\" set-dusts false\n\n"
311               "This will set the \"dusts\" property of the agingtv to false and play the\n"
312               "timeline back.",
313     .properties={
314       {NULL, 0, 0, NULL, FALSE},
315     },
316   },
317 };
318 /*  *INDENT-ON* */
319
320 /* Should always be in the same order as the options */
321 typedef enum
322 {
323   CLIP,
324   EFFECT,
325   TEST_CLIP,
326   TITLE,
327   TRACK,
328   KEYFRAMES,
329   SET,
330 } GESCommandLineOptionType;
331
332 static gint                     /*  -1: not present, 0: failure, 1: OK */
333 _convert_to_clocktime (GstStructure * structure, const gchar * name,
334     GstClockTime default_value)
335 {
336   gint res = 1;
337   gdouble val;
338   GValue d_val = G_VALUE_INIT, converted = G_VALUE_INIT;
339   GstClockTime timestamp;
340   const GValue *gvalue = gst_structure_get_value (structure, name);
341
342   if (gvalue == NULL) {
343     timestamp = default_value;
344
345     res = -1;
346
347     goto done;
348   }
349
350   if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING) {
351     const gchar *val_string = g_value_get_string (gvalue);
352     /* if starts with an 'f', interpret as a frame number, keep as
353      * a string for now */
354     if (val_string && val_string[0] == 'f')
355       return 1;
356     /* else, try convert to a GstClockTime, or a double */
357     g_value_init (&converted, GST_TYPE_CLOCK_TIME);
358     if (!gst_value_deserialize (&converted, val_string)) {
359       g_value_unset (&converted);
360       g_value_init (&converted, G_TYPE_DOUBLE);
361       if (!gst_value_deserialize (&converted, val_string)) {
362         GST_ERROR ("Could not get timestamp for %s by deserializing %s",
363             name, val_string);
364         goto error;
365       }
366     }
367   } else {
368     g_value_init (&converted, G_VALUE_TYPE (gvalue));
369     g_value_copy (gvalue, &converted);
370   }
371
372   if (G_VALUE_TYPE (&converted) == GST_TYPE_CLOCK_TIME) {
373     timestamp = g_value_get_uint64 (&converted);
374     goto done;
375   }
376
377   g_value_init (&d_val, G_TYPE_DOUBLE);
378
379   if (!g_value_transform (&converted, &d_val)) {
380     GST_ERROR ("Could not get timestamp for %s", name);
381     goto error;
382   }
383
384   val = g_value_get_double ((const GValue *) &d_val);
385   g_value_unset (&d_val);
386
387   if (val == -1.0)
388     timestamp = GST_CLOCK_TIME_NONE;
389   else
390     timestamp = val * GST_SECOND;
391
392 done:
393   gst_structure_set (structure, name, G_TYPE_UINT64, timestamp, NULL);
394   g_value_unset (&converted);
395
396   return res;
397
398 error:
399   g_value_unset (&converted);
400
401   return 0;
402 }
403
404 static gboolean
405 _cleanup_fields (const Property * field_names, GstStructure * structure,
406     GError ** error)
407 {
408   guint i;
409
410   for (i = 0; field_names[i].long_name; i++) {
411     gboolean exists = FALSE;
412
413     /* Move shortly named fields to longname variante */
414     if (field_names[i].short_name &&
415         gst_structure_has_field (structure, field_names[i].short_name)) {
416       exists = TRUE;
417
418       if (gst_structure_has_field (structure, field_names[i].long_name)) {
419         gchar *str_info = gst_structure_serialize (structure, 0);
420
421         *error =
422             g_error_new (GES_ERROR, 0,
423             "Using short (%s) and long name (%s)"
424             " at the same time s in %s, which one should I use?!",
425             field_names[i].short_name, field_names[i].long_name, str_info);
426         g_free (str_info);
427
428         return FALSE;
429       } else {
430         const GValue *val =
431             gst_structure_get_value (structure, field_names[i].short_name);
432
433         gst_structure_set_value (structure, field_names[i].long_name, val);
434         gst_structure_remove_field (structure, field_names[i].short_name);
435       }
436     } else if (gst_structure_has_field (structure, field_names[i].long_name)) {
437       exists = TRUE;
438     }
439
440     if (exists) {
441       if (field_names[i].type == GST_TYPE_CLOCK_TIME) {
442         if (_convert_to_clocktime (structure, field_names[i].long_name, 0) == 0) {
443           *error = g_error_new (GES_ERROR, 0, "Could not convert"
444               " %s to GstClockTime", field_names[i].long_name);
445
446           return FALSE;
447         }
448       }
449     }
450
451     if (field_names[i].new_name
452         && gst_structure_has_field (structure, field_names[i].long_name)) {
453       const GValue *val =
454           gst_structure_get_value (structure, field_names[i].long_name);
455
456       gst_structure_set_value (structure, field_names[i].new_name, val);
457       gst_structure_remove_field (structure, field_names[i].long_name);
458     }
459   }
460
461   return TRUE;
462 }
463
464 static gboolean
465 _ges_command_line_formatter_add_clip (GESTimeline * timeline,
466     GstStructure * structure, GError ** error)
467 {
468   GESProject *proj;
469   GESAsset *asset;
470   if (!_cleanup_fields (options[CLIP].properties, structure, error))
471     return FALSE;
472
473   gst_structure_set (structure, "type", G_TYPE_STRING, "GESUriClip", NULL);
474
475   if (!_ges_add_clip_from_struct (timeline, structure, error))
476     return FALSE;
477
478   proj = GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline)));
479   asset = _ges_get_asset_from_timeline (timeline, GES_TYPE_URI_CLIP,
480       gst_structure_get_string (structure, "asset-id"), NULL);
481   ges_project_add_asset (proj, asset);
482
483   return TRUE;
484 }
485
486 static gboolean
487 _ges_command_line_formatter_add_test_clip (GESTimeline * timeline,
488     GstStructure * structure, GError ** error)
489 {
490   if (!_cleanup_fields (options[TEST_CLIP].properties, structure, error))
491     return FALSE;
492
493   gst_structure_set (structure, "type", G_TYPE_STRING, "GESTestClip", NULL);
494
495   if (!gst_structure_has_field_typed (structure, "asset-id", G_TYPE_STRING))
496     gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTestClip",
497         NULL);
498
499   return _ges_add_clip_from_struct (timeline, structure, error);
500 }
501
502 static gboolean
503 _ges_command_line_formatter_add_title_clip (GESTimeline * timeline,
504     GstStructure * structure, GError ** error)
505 {
506   if (!_cleanup_fields (options[TITLE].properties, structure, error))
507     return FALSE;
508
509   gst_structure_set (structure, "type", G_TYPE_STRING, "GESTitleClip", NULL);
510   gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTitleClip",
511       NULL);
512   GST_ERROR ("Structure: %" GST_PTR_FORMAT, structure);
513
514   return _ges_add_clip_from_struct (timeline, structure, error);
515 }
516
517 static gboolean
518 _ges_command_line_formatter_add_keyframes (GESTimeline * timeline,
519     GstStructure * structure, GError ** error)
520 {
521   if (!_cleanup_fields (options[KEYFRAMES].properties, structure, error))
522     return FALSE;
523
524   if (!_ges_set_control_source_from_struct (timeline, structure, error))
525     return FALSE;
526
527   return _ges_add_remove_keyframe_from_struct (timeline, structure, error);
528 }
529
530 static gboolean
531 _ges_command_line_formatter_add_track (GESTimeline * timeline,
532     GstStructure * structure, GError ** error)
533 {
534   if (!_cleanup_fields (options[TRACK].properties, structure, error))
535     return FALSE;
536
537   return _ges_add_track_from_struct (timeline, structure, error);
538 }
539
540 static gboolean
541 _ges_command_line_formatter_add_effect (GESTimeline * timeline,
542     GstStructure * structure, GError ** error)
543 {
544   if (!_cleanup_fields (options[EFFECT].properties, structure, error))
545     return FALSE;
546
547   gst_structure_set (structure, "child-type", G_TYPE_STRING, "GESEffect", NULL);
548
549   return _ges_container_add_child_from_struct (timeline, structure, error);
550 }
551
552 gchar *
553 ges_command_line_formatter_get_help (gint nargs, gchar ** commands)
554 {
555   gint i;
556   GString *help = g_string_new (NULL);
557
558   for (i = 0; i < G_N_ELEMENTS (options); i++) {
559     gboolean print = nargs == 0;
560     GESCommandLineOption option = options[i];
561
562     if (!print) {
563       gint j;
564
565       for (j = 0; j < nargs; j++) {
566         gchar *cname = commands[j][0] == '+' ? &commands[j][1] : commands[j];
567
568         if (!g_strcmp0 (cname, option.long_name)) {
569           print = TRUE;
570           break;
571         }
572       }
573     }
574
575     if (print) {
576       gint j;
577
578       gchar *tmp = g_strdup_printf ("  `%s%s` - %s\n",
579           option.properties[0].long_name ? "+" : "",
580           option.long_name, option.synopsis);
581
582       g_string_append (help, tmp);
583       g_string_append (help, "  ");
584       g_string_append (help, "\n\n  ");
585       g_free (tmp);
586
587       for (j = 0; option.description[j] != '\0'; j++) {
588
589         if (j && (j % 80) == 0) {
590           while (option.description[j] != '\0' && option.description[j] != ' ')
591             g_string_append_c (help, option.description[j++]);
592           g_string_append (help, "\n  ");
593           continue;
594         }
595
596         g_string_append_c (help, option.description[j]);
597       }
598       g_string_append_c (help, '\n');
599
600       if (option.properties[0].long_name) {
601         gint j;
602
603         g_string_append (help, "\n  Properties:\n\n");
604
605         for (j = 1; option.properties[j].long_name; j++) {
606           Property prop = option.properties[j];
607           g_string_append_printf (help, "    * `%s`: %s\n", prop.long_name,
608               prop.desc);
609         }
610       }
611       if (option.examples) {
612         gint j;
613         gchar **examples = g_strsplit (option.examples, "\n", -1);
614
615         g_string_append (help, "\n  Examples:\n\n");
616         for (j = 0; examples[j]; j++) {
617           if (examples[j])
618             g_string_append_printf (help, "    %s", examples[j]);
619           g_string_append_c (help, '\n');
620         }
621         g_strfreev (examples);
622       }
623
624       g_string_append_c (help, '\n');
625     }
626   }
627
628   return g_string_free (help, FALSE);
629 }
630
631
632 static gboolean
633 _set_child_property (GESTimeline * timeline, GstStructure * structure,
634     GError ** error)
635 {
636   return _ges_set_child_property_from_struct (timeline, structure, error);
637 }
638
639 #define EXEC(func,structure,error) G_STMT_START { \
640   gboolean res = ((ActionFromStructureFunc)func)(timeline, structure, error); \
641   if (!res) {\
642     GST_ERROR ("Could not execute: %" GST_PTR_FORMAT ", error: %s", structure, (*error)->message); \
643     goto fail; \
644   } \
645 } G_STMT_END
646
647
648 static GESStructureParser *
649 _parse_structures (const gchar * string)
650 {
651   yyscan_t scanner;
652   GESStructureParser *parser = ges_structure_parser_new ();
653
654   priv_ges_parse_yylex_init_extra (parser, &scanner);
655   priv_ges_parse_yy_scan_string (string, scanner);
656   priv_ges_parse_yylex (scanner);
657   priv_ges_parse_yylex_destroy (scanner);
658
659   ges_structure_parser_end_of_file (parser);
660   return parser;
661 }
662
663 static gboolean
664 _can_load (GESFormatter * dummy_formatter, const gchar * string,
665     GError ** error)
666 {
667   gboolean res = FALSE;
668   GESStructureParser *parser;
669
670   if (string == NULL)
671     return FALSE;
672
673   parser = _parse_structures (string);
674
675   if (parser->structures)
676     res = TRUE;
677
678   gst_object_unref (parser);
679
680   return res;
681 }
682
683 static gboolean
684 _load (GESFormatter * self, GESTimeline * timeline, const gchar * string,
685     GError ** error)
686 {
687   guint i;
688   GList *tmp;
689   GError *err;
690   GESStructureParser *parser = _parse_structures (string);
691
692   err = ges_structure_parser_get_error (parser);
693
694   if (err) {
695     if (error)
696       *error = err;
697
698     return FALSE;
699   }
700
701   g_object_set (timeline, "auto-transition", TRUE, NULL);
702
703   /* Here we've finished initializing our timeline, we're
704    * ready to start using it... by solely working with the layer !*/
705   for (tmp = parser->structures; tmp; tmp = tmp->next) {
706     const gchar *name = gst_structure_get_name (tmp->data);
707     if (g_str_has_prefix (name, "set-")) {
708       EXEC (_set_child_property, tmp->data, &err);
709       continue;
710     }
711
712     for (i = 0; i < G_N_ELEMENTS (options); i++) {
713       if (gst_structure_has_name (tmp->data, options[i].long_name)
714           || (strlen (name) == 1 && *name == options[i].short_name)) {
715         EXEC (((ActionFromStructureFunc) options[i].callback), tmp->data, &err);
716       }
717     }
718   }
719
720   gst_object_unref (parser);
721
722   return TRUE;
723
724 fail:
725   gst_object_unref (parser);
726   if (err) {
727     if (error)
728       *error = err;
729   }
730
731   return FALSE;
732 }
733
734 static void
735 ges_command_line_formatter_init (GESCommandLineFormatter * formatter)
736 {
737   formatter->priv = ges_command_line_formatter_get_instance_private (formatter);
738 }
739
740 static void
741 ges_command_line_formatter_finalize (GObject * object)
742 {
743   G_OBJECT_CLASS (ges_command_line_formatter_parent_class)->finalize (object);
744 }
745
746 static void
747 ges_command_line_formatter_class_init (GESCommandLineFormatterClass * klass)
748 {
749   GObjectClass *object_class = G_OBJECT_CLASS (klass);
750   GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (klass);
751
752   object_class->finalize = ges_command_line_formatter_finalize;
753
754   formatter_klass->can_load_uri = _can_load;
755   formatter_klass->load_from_uri = _load;
756   formatter_klass->rank = GST_RANK_MARGINAL;
757 }