structure-interface: Convert fields type as much as possible
[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", G_TYPE_INT, 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
513   return _ges_add_clip_from_struct (timeline, structure, error);
514 }
515
516 static gboolean
517 _ges_command_line_formatter_add_keyframes (GESTimeline * timeline,
518     GstStructure * structure, GError ** error)
519 {
520   if (!_cleanup_fields (options[KEYFRAMES].properties, structure, error))
521     return FALSE;
522
523   if (!_ges_set_control_source_from_struct (timeline, structure, error))
524     return FALSE;
525
526   return _ges_add_remove_keyframe_from_struct (timeline, structure, error);
527 }
528
529 static gboolean
530 _ges_command_line_formatter_add_track (GESTimeline * timeline,
531     GstStructure * structure, GError ** error)
532 {
533   if (!_cleanup_fields (options[TRACK].properties, structure, error))
534     return FALSE;
535
536   return _ges_add_track_from_struct (timeline, structure, error);
537 }
538
539 static gboolean
540 _ges_command_line_formatter_add_effect (GESTimeline * timeline,
541     GstStructure * structure, GError ** error)
542 {
543   if (!_cleanup_fields (options[EFFECT].properties, structure, error))
544     return FALSE;
545
546   gst_structure_set (structure, "child-type", G_TYPE_STRING, "GESEffect", NULL);
547
548   return _ges_container_add_child_from_struct (timeline, structure, error);
549 }
550
551 gchar *
552 ges_command_line_formatter_get_help (gint nargs, gchar ** commands)
553 {
554   gint i;
555   GString *help = g_string_new (NULL);
556
557   for (i = 0; i < G_N_ELEMENTS (options); i++) {
558     gboolean print = nargs == 0;
559     GESCommandLineOption option = options[i];
560
561     if (!print) {
562       gint j;
563
564       for (j = 0; j < nargs; j++) {
565         gchar *cname = commands[j][0] == '+' ? &commands[j][1] : commands[j];
566
567         if (!g_strcmp0 (cname, option.long_name)) {
568           print = TRUE;
569           break;
570         }
571       }
572     }
573
574     if (print) {
575       gint j;
576
577       gchar *tmp = g_strdup_printf ("  `%s%s` - %s\n",
578           option.properties[0].long_name ? "+" : "",
579           option.long_name, option.synopsis);
580
581       g_string_append (help, tmp);
582       g_string_append (help, "  ");
583       g_string_append (help, "\n\n  ");
584       g_free (tmp);
585
586       for (j = 0; option.description[j] != '\0'; j++) {
587
588         if (j && (j % 80) == 0) {
589           while (option.description[j] != '\0' && option.description[j] != ' ')
590             g_string_append_c (help, option.description[j++]);
591           g_string_append (help, "\n  ");
592           continue;
593         }
594
595         g_string_append_c (help, option.description[j]);
596       }
597       g_string_append_c (help, '\n');
598
599       if (option.properties[0].long_name) {
600         gint j;
601
602         g_string_append (help, "\n  Properties:\n\n");
603
604         for (j = 1; option.properties[j].long_name; j++) {
605           Property prop = option.properties[j];
606           g_string_append_printf (help, "    * `%s`: %s\n", prop.long_name,
607               prop.desc);
608         }
609       }
610       if (option.examples) {
611         gint j;
612         gchar **examples = g_strsplit (option.examples, "\n", -1);
613
614         g_string_append (help, "\n  Examples:\n\n");
615         for (j = 0; examples[j]; j++) {
616           if (examples[j])
617             g_string_append_printf (help, "    %s", examples[j]);
618           g_string_append_c (help, '\n');
619         }
620         g_strfreev (examples);
621       }
622
623       g_string_append_c (help, '\n');
624     }
625   }
626
627   return g_string_free (help, FALSE);
628 }
629
630
631 static gboolean
632 _set_child_property (GESTimeline * timeline, GstStructure * structure,
633     GError ** error)
634 {
635   return _ges_set_child_property_from_struct (timeline, structure, error);
636 }
637
638 #define EXEC(func,structure,error) G_STMT_START { \
639   gboolean res = ((ActionFromStructureFunc)func)(timeline, structure, error); \
640   if (!res) {\
641     GST_ERROR ("Could not execute: %" GST_PTR_FORMAT ", error: %s", structure, (*error)->message); \
642     goto fail; \
643   } \
644 } G_STMT_END
645
646
647 static GESStructureParser *
648 _parse_structures (const gchar * string)
649 {
650   yyscan_t scanner;
651   GESStructureParser *parser = ges_structure_parser_new ();
652
653   priv_ges_parse_yylex_init_extra (parser, &scanner);
654   priv_ges_parse_yy_scan_string (string, scanner);
655   priv_ges_parse_yylex (scanner);
656   priv_ges_parse_yylex_destroy (scanner);
657
658   ges_structure_parser_end_of_file (parser);
659   return parser;
660 }
661
662 /* @uri: (transfer full): */
663 static gchar *
664 get_timeline_desc_from_uri (GstUri * uri)
665 {
666   gchar *res, *path;
667
668   if (!uri)
669     return NULL;
670
671   /* Working around parser requiring a space to begin with */
672   path = gst_uri_get_path (uri);
673   res = g_strconcat (" ", path, NULL);
674   g_free (path);
675
676   gst_uri_unref (uri);
677
678   return res;
679 }
680
681 static gboolean
682 _can_load (GESFormatter * dummy_formatter, const gchar * string,
683     GError ** error)
684 {
685   gboolean res = FALSE;
686   GstUri *uri;
687   const gchar *scheme;
688   gchar *timeline_desc = NULL;
689   GESStructureParser *parser;
690
691   if (string == NULL) {
692     GST_ERROR ("No URI!");
693     return FALSE;
694   }
695
696   uri = gst_uri_from_string (string);
697   if (!uri) {
698     GST_INFO_OBJECT (dummy_formatter, "Wrong uri: %s", string);
699     return FALSE;
700   }
701
702   scheme = gst_uri_get_scheme (uri);
703   if (!g_strcmp0 (scheme, "ges:")) {
704     GST_INFO_OBJECT (dummy_formatter, "Wrong scheme: %s", string);
705     gst_uri_unref (uri);
706
707     return FALSE;
708   }
709
710   timeline_desc = get_timeline_desc_from_uri (uri);
711   parser = _parse_structures (timeline_desc);
712   if (parser->structures)
713     res = TRUE;
714
715   gst_object_unref (parser);
716   g_free (timeline_desc);
717
718   return res;
719 }
720
721 static gboolean
722 _set_project_loaded (GESFormatter * self)
723 {
724   ges_project_set_loaded (self->project, self, NULL);
725   gst_object_unref (self);
726
727   return FALSE;
728 }
729
730 static gboolean
731 _load (GESFormatter * self, GESTimeline * timeline, const gchar * string,
732     GError ** error)
733 {
734   guint i;
735   GList *tmp;
736   GError *err;
737   gchar *timeline_desc =
738       get_timeline_desc_from_uri (gst_uri_from_string (string));
739   GESStructureParser *parser = _parse_structures (timeline_desc);
740
741   g_free (timeline_desc);
742
743   err = ges_structure_parser_get_error (parser);
744
745   if (err) {
746     if (error)
747       *error = err;
748
749     return FALSE;
750   }
751
752   g_object_set (timeline, "auto-transition", TRUE, NULL);
753
754   /* Here we've finished initializing our timeline, we're
755    * ready to start using it... by solely working with the layer !*/
756   for (tmp = parser->structures; tmp; tmp = tmp->next) {
757     const gchar *name = gst_structure_get_name (tmp->data);
758     if (g_str_has_prefix (name, "set-")) {
759       EXEC (_set_child_property, tmp->data, &err);
760       continue;
761     }
762
763     for (i = 0; i < G_N_ELEMENTS (options); i++) {
764       if (gst_structure_has_name (tmp->data, options[i].long_name)
765           || (strlen (name) == 1 && *name == options[i].short_name)) {
766         EXEC (((ActionFromStructureFunc) options[i].callback), tmp->data, &err);
767         break;
768       }
769     }
770   }
771
772   gst_object_unref (parser);
773
774   ges_idle_add ((GSourceFunc) _set_project_loaded, g_object_ref (self), NULL);
775
776   return TRUE;
777
778 fail:
779   gst_object_unref (parser);
780   if (err) {
781     if (error)
782       *error = err;
783   }
784
785   return FALSE;
786 }
787
788 static void
789 ges_command_line_formatter_init (GESCommandLineFormatter * formatter)
790 {
791   formatter->priv = ges_command_line_formatter_get_instance_private (formatter);
792 }
793
794 static void
795 ges_command_line_formatter_finalize (GObject * object)
796 {
797   G_OBJECT_CLASS (ges_command_line_formatter_parent_class)->finalize (object);
798 }
799
800 static void
801 ges_command_line_formatter_class_init (GESCommandLineFormatterClass * klass)
802 {
803   GObjectClass *object_class = G_OBJECT_CLASS (klass);
804   GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (klass);
805
806   object_class->finalize = ges_command_line_formatter_finalize;
807
808   formatter_klass->can_load_uri = _can_load;
809   formatter_klass->load_from_uri = _load;
810   formatter_klass->rank = GST_RANK_MARGINAL;
811 }
812
813 /* Copy of GST_ASCII_IS_STRING */
814 #define ASCII_IS_STRING(c) (g_ascii_isalnum((c)) || ((c) == '_') || \
815     ((c) == '-') || ((c) == '+') || ((c) == '/') || ((c) == ':') || \
816     ((c) == '.'))
817
818 static void
819 _sanitize_argument (const gchar * arg, GString * res)
820 {
821   gboolean need_wrap = FALSE;
822   const gchar *tmp_string;
823
824   for (tmp_string = arg; *tmp_string != '\0'; tmp_string++) {
825     if (!ASCII_IS_STRING (*tmp_string) || (*tmp_string == '\n')) {
826       need_wrap = TRUE;
827       break;
828     }
829   }
830
831   if (!need_wrap) {
832     g_string_append (res, arg);
833     return;
834   }
835
836   g_string_append_c (res, '"');
837   while (*arg != '\0') {
838     if (*arg == '"' || *arg == '\\') {
839       g_string_append_c (res, '\\');
840     } else if (*arg == '\n') {
841       g_string_append (res, "\\n");
842       arg++;
843       continue;
844     }
845
846     g_string_append_c (res, *(arg++));
847   }
848   g_string_append_c (res, '"');
849 }
850
851 static gboolean
852 _serialize_control_binding (GESTrackElement * e, const gchar * prop,
853     GString * res)
854 {
855   GstInterpolationMode mode;
856   GstControlSource *source = NULL;
857   GList *timed_values, *tmp;
858   gboolean absolute = FALSE;
859   GstControlBinding *binding = ges_track_element_get_control_binding (e, prop);
860
861   if (!binding)
862     return FALSE;
863
864   if (!GST_IS_DIRECT_CONTROL_BINDING (binding)) {
865     g_warning ("Unsupported control binding type: %s",
866         G_OBJECT_TYPE_NAME (binding));
867     goto done;
868   }
869
870   g_object_get (binding, "control-source", &source,
871       "absolute", &absolute, NULL);
872
873   if (!GST_IS_INTERPOLATION_CONTROL_SOURCE (source)) {
874     g_warning ("Unsupported control source type: %s",
875         G_OBJECT_TYPE_NAME (source));
876     goto done;
877   }
878
879   g_object_get (source, "mode", &mode, NULL);
880   g_string_append_printf (res, " +keyframes %s t=%s",
881       prop, absolute ? "direct-absolute" : "direct");
882
883   if (mode != GST_INTERPOLATION_MODE_LINEAR)
884     g_string_append_printf (res, " mode=%s",
885         g_enum_get_value (g_type_class_peek (GST_TYPE_INTERPOLATION_MODE),
886             mode)->value_nick);
887
888   timed_values =
889       gst_timed_value_control_source_get_all
890       (GST_TIMED_VALUE_CONTROL_SOURCE (source));
891   for (tmp = timed_values; tmp; tmp = tmp->next) {
892     gchar strbuf[G_ASCII_DTOSTR_BUF_SIZE];
893     GstTimedValue *value;
894
895     value = (GstTimedValue *) tmp->data;
896     g_string_append_printf (res, " %f=%s",
897         (gdouble) value->timestamp / (gdouble) GST_SECOND,
898         g_ascii_dtostr (strbuf, G_ASCII_DTOSTR_BUF_SIZE, value->value));
899   }
900   g_list_free (timed_values);
901
902 done:
903   g_clear_object (&source);
904   return TRUE;
905 }
906
907 static void
908 _serialize_object_properties (GObject * object, GESCommandLineOption * option,
909     gboolean children_props, GString * res)
910 {
911   guint n_props, j;
912   GParamSpec *spec, **pspecs;
913   GObjectClass *class = G_OBJECT_GET_CLASS (object);
914   const gchar *ignored_props[] = {
915     "max-duration", "supported-formats", "priority", "video-direction",
916     "is-image", NULL,
917   };
918
919   if (!children_props)
920     pspecs = g_object_class_list_properties (class, &n_props);
921   else {
922     pspecs =
923         ges_timeline_element_list_children_properties (GES_TIMELINE_ELEMENT
924         (object), &n_props);
925     g_assert (GES_IS_TRACK_ELEMENT (object));
926   }
927
928   for (j = 0; j < n_props; j++) {
929     const gchar *name;
930     gchar *value_str = NULL;
931     GValue val = { 0 };
932     gint i;
933
934     spec = pspecs[j];
935     if (!ges_util_can_serialize_spec (spec))
936       continue;
937
938     g_value_init (&val, spec->value_type);
939     if (!children_props)
940       g_object_get_property (object, spec->name, &val);
941     else
942       ges_timeline_element_get_child_property_by_pspec (GES_TIMELINE_ELEMENT
943           (object), spec, &val);
944
945     if (gst_value_compare (g_param_spec_get_default_value (spec),
946             &val) == GST_VALUE_EQUAL) {
947       GST_INFO ("Ignoring %s as it is using the default value", spec->name);
948       goto next;
949     }
950
951     name = spec->name;
952     if (!children_props && !g_strcmp0 (name, "in-point"))
953       name = "inpoint";
954
955     for (i = 0; option->properties[i].long_name; i++) {
956       if (!g_strcmp0 (spec->name, option->properties[i].long_name)) {
957         if (children_props) {
958           name = NULL;
959         } else {
960           name = option->properties[i].short_name;
961           if (option->properties[i].type == GST_TYPE_CLOCK_TIME)
962             value_str =
963                 g_strdup_printf ("%f",
964                 (gdouble) (g_value_get_uint64 (&val) / GST_SECOND));
965         }
966         break;
967       } else if (!g_strcmp0 (spec->name, option->properties[0].long_name)) {
968         name = NULL;
969         break;
970       }
971     }
972
973     for (i = 0; i < G_N_ELEMENTS (ignored_props); i++) {
974       if (!g_strcmp0 (spec->name, ignored_props[i])) {
975         name = NULL;
976         break;
977       }
978     }
979
980     if (!name) {
981       g_free (value_str);
982       continue;
983     }
984
985     if (GES_IS_TRACK_ELEMENT (object) &&
986         _serialize_control_binding (GES_TRACK_ELEMENT (object), name, res)) {
987       g_free (value_str);
988       continue;
989     }
990
991     if (!value_str)
992       value_str = gst_value_serialize (&val);
993
994     g_string_append_printf (res, " %s%s%s",
995         children_props ? "set-" : "", name, children_props ? " " : "=");
996     _sanitize_argument (value_str, res);
997     g_free (value_str);
998
999   next:
1000     g_value_unset (&val);
1001   }
1002   g_free (pspecs);
1003 }
1004
1005 static void
1006 _serialize_clip_track_types (GESClip * clip, GESTrackType tt, GString * res)
1007 {
1008   GValue v = G_VALUE_INIT;
1009   gchar *ttype_str;
1010
1011   if (ges_clip_get_supported_formats (clip) == tt)
1012     return;
1013
1014   g_value_init (&v, GES_TYPE_TRACK_TYPE);
1015   g_value_set_flags (&v, ges_clip_get_supported_formats (clip));
1016
1017   ttype_str = gst_value_serialize (&v);
1018
1019   g_string_append_printf (res, " tt=%s", ttype_str);
1020   g_value_reset (&v);
1021   g_free (ttype_str);
1022 }
1023
1024 static void
1025 _serialize_clip_effects (GESClip * clip, GString * res)
1026 {
1027   GList *tmpeffect, *effects;
1028
1029   effects = ges_clip_get_top_effects (clip);
1030   for (tmpeffect = effects; tmpeffect; tmpeffect = tmpeffect->next) {
1031     gchar *bin_desc;
1032
1033     g_object_get (tmpeffect->data, "bin-description", &bin_desc, NULL);
1034
1035     g_string_append_printf (res, " +effect %s", bin_desc);
1036     g_free (bin_desc);
1037   }
1038   g_list_free_full (effects, gst_object_unref);
1039
1040 }
1041
1042 /**
1043  * ges_command_line_formatter_get_timeline_uri:
1044  * @timeline: A GESTimeline to serialize
1045  *
1046  * Since: 1.20
1047  */
1048 gchar *
1049 ges_command_line_formatter_get_timeline_uri (GESTimeline * timeline)
1050 {
1051   gchar *tmpstr;
1052   GList *tmp;
1053   gint i;
1054   GString *res = g_string_new ("ges:");
1055   GESTrackType tt = 0;
1056
1057   if (!timeline)
1058     goto done;
1059
1060   for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
1061     GstCaps *caps, *default_caps;
1062     GESTrack *tmptrack, *track = tmp->data;
1063
1064     if (GES_IS_VIDEO_TRACK (track))
1065       tmptrack = GES_TRACK (ges_video_track_new ());
1066     else if (GES_IS_AUDIO_TRACK (track))
1067       tmptrack = GES_TRACK (ges_audio_track_new ());
1068     else {
1069       g_warning ("Unhandled track type: %s", G_OBJECT_TYPE_NAME (track));
1070       continue;
1071     }
1072
1073     tt |= track->type;
1074
1075     g_string_append_printf (res, " +track %s",
1076         (track->type == GES_TRACK_TYPE_VIDEO) ? "video" : "audio");
1077
1078     default_caps = ges_track_get_restriction_caps (tmptrack);
1079     caps = ges_track_get_restriction_caps (track);
1080     if (!gst_caps_is_equal (caps, default_caps)) {
1081       tmpstr = gst_caps_serialize (caps, 0);
1082
1083       g_string_append (res, " restrictions=");
1084       _sanitize_argument (tmpstr, res);
1085       g_free (tmpstr);
1086     }
1087     gst_caps_unref (default_caps);
1088     gst_caps_unref (caps);
1089     gst_object_unref (tmptrack);
1090   }
1091
1092   for (tmp = timeline->layers, i = 0; tmp; tmp = tmp->next, i++) {
1093     GList *tmpclip, *clips = ges_layer_get_clips (tmp->data);
1094     GList *tmptrackelem;
1095
1096     for (tmpclip = clips; tmpclip; tmpclip = tmpclip->next) {
1097       GESClip *clip = tmpclip->data;
1098       GESCommandLineOption *option = NULL;
1099
1100       if (GES_IS_TEST_CLIP (clip)) {
1101         GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (clip));
1102         const gchar *id = ges_asset_get_id (asset);
1103
1104         g_string_append (res, " +test-clip ");
1105
1106         _sanitize_argument (g_enum_get_value (g_type_class_peek
1107                 (GES_VIDEO_TEST_PATTERN_TYPE),
1108                 ges_test_clip_get_vpattern (GES_TEST_CLIP (clip)))->value_nick,
1109             res);
1110
1111         if (g_strcmp0 (id, "GESTestClip")) {
1112           g_string_append (res, " asset-id=");
1113           _sanitize_argument (id, res);
1114         }
1115
1116         option = &options[TEST_CLIP];
1117       } else if (GES_IS_TITLE_CLIP (clip)) {
1118         g_string_append (res, " +title ");
1119         _sanitize_argument (ges_title_clip_get_text (GES_TITLE_CLIP (clip)),
1120             res);
1121         option = &options[TITLE];
1122       } else if (GES_IS_URI_CLIP (clip)) {
1123         g_string_append (res, " +clip ");
1124
1125         _sanitize_argument (ges_uri_clip_get_uri (GES_URI_CLIP (clip)), res);
1126         option = &options[CLIP];
1127       } else {
1128         g_warning ("Unhandled clip type: %s", G_OBJECT_TYPE_NAME (clip));
1129         continue;
1130       }
1131
1132       _serialize_clip_track_types (clip, tt, res);
1133
1134       if (i)
1135         g_string_append_printf (res, " layer=%d", i);
1136
1137       _serialize_object_properties (G_OBJECT (clip), option, FALSE, res);
1138       _serialize_clip_effects (clip, res);
1139
1140       for (tmptrackelem = GES_CONTAINER_CHILDREN (clip); tmptrackelem;
1141           tmptrackelem = tmptrackelem->next)
1142         _serialize_object_properties (G_OBJECT (tmptrackelem->data), option,
1143             TRUE, res);
1144     }
1145     g_list_free_full (clips, gst_object_unref);
1146   }
1147
1148 done:
1149   return g_string_free (res, FALSE);
1150   {
1151     GstUri *uri = gst_uri_from_string (res->str);
1152     gchar *uri_str = gst_uri_to_string (uri);
1153
1154     g_string_free (res, TRUE);
1155
1156     return uri_str;
1157   }
1158 }