ges: Refactor the way we plug converters in effects
[platform/upstream/gstreamer.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
54 typedef struct
55 {
56   const gchar *long_name;
57   const gchar *short_name;
58   GType type;
59   const gchar *new_name;
60   const gchar *desc;
61 } Property;
62
63 // Currently Clip has the most properties.. adapt as needed
64 #define MAX_PROPERTIES 8
65 typedef struct
66 {
67   const gchar *long_name;
68   gchar short_name;
69   ActionFromStructureFunc callback;
70   const gchar *synopsis;
71   const gchar *description;
72   const gchar *examples;
73   /* The first property must be the ID on the command line */
74   Property properties[MAX_PROPERTIES];
75 } GESCommandLineOption;
76
77 /*  *INDENT-OFF* */
78 static GESCommandLineOption options[] = {
79   {"clip", 'c', (ActionFromStructureFunc) _ges_command_line_formatter_add_clip,
80     "<clip uri>",
81     "Adds a clip in the timeline. "
82     "See documentation for the --track-types option to ges-launch-1.0, as it "
83     " will affect the result of this command.",
84     "    ges-launch-1.0 +clip /path/to/media\n\n"
85     "This will simply play the sample from its beginning to its end.\n\n"
86     "    ges-launch-1.0 +clip /path/to/media inpoint=4.0\n\n"
87     "Assuming 'media' is a 10 second long media sample, this will play the sample\n"
88     "from the 4th second to the 10th, resulting in a 6-seconds long playback.\n\n"
89     "    ges-launch-1.0 +clip /path/to/media inpoint=4.0 duration=2.0 start=4.0\n\n"
90     "Assuming \"media\" is an audio video sample longer than 6 seconds, this will play\n"
91     "a black frame and silence for 4 seconds, then the sample from its 4th second to\n"
92     "its sixth second, resulting in a 6-seconds long playback.\n\n"
93     "    ges-launch-1.0 --track-types=audio +clip /path/to/media\n\n"
94     "Assuming \"media\" is an audio video sample, this will only play the audio of the\n"
95     "sample in its entirety.\n\n"
96     "    ges-launch-1.0 +clip /path/to/media1 layer=1 set-alpha 0.9 +clip /path/to/media2 layer=0\n\n"
97     "Assume media1 and media2 both contain audio and video and last for 10 seconds.\n\n"
98     "This will first add media1 in a new layer of \"priority\" 1, thus implicitly\n"
99     "creating a layer of \"priority\" 0, the start of the clip will be 0 as no clip\n"
100     "had been added in that layer before.\n\n"
101     "It will then add media2 in the layer of \"priority\" 0 which was created\n"
102     "previously, the start of this new clip will also be 0 as no clip has been added\n"
103     "in this layer before.\n\n"
104     "Both clips will thus overlap on two layers for 10 seconds.\n\n"
105     "The \"alpha\" property of the second clip will finally be set to a value of 0.9.\n\n"
106     "All this will result in a 10 seconds playback, where media2 is barely visible\n"
107     "through media1, which is nearly opaque. If alpha was set to 0.5, both clips\n"
108     "would be equally visible, and if it was set to 0.0, media1 would be invisible\n"
109     "and media2 completely opaque.\n",
110     {
111       {
112         "uri", "n", 0, "asset-id",
113         "The URI of the media file."
114       },
115       {
116         "name", "n", 0, NULL,
117         "The name of the clip, can be used as an ID later."
118       },
119       {
120         "start", "s",GST_TYPE_CLOCK_TIME, NULL,
121         "The starting position of the clip in the timeline."
122       },
123       {
124         "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
125         "The duration of the clip."
126       },
127       {
128         "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
129         "The inpoint of the clip (time in the input file to start playing from)."
130       },
131       {
132         "track-types", "tt", 0, NULL,
133         "The type of the tracks where the clip should be used (audio or video or audio+video)."
134       },
135       {
136         "layer", "l", 0, NULL,
137         "The priority of the layer into which the clip should be added."
138       },
139       {NULL, 0, 0, NULL, FALSE},
140     },
141   },
142   {"effect", 'e', (ActionFromStructureFunc) _ges_command_line_formatter_add_effect,
143     "<effect bin description>",
144     "Adds an effect as specified by 'bin-description', similar to gst-launch-style"
145     " pipeline description, without setting properties (see `set-<property-name>` for information"
146     " about how to set properties).",
147     "    ges-launch-1.0 +clip /path/to/media +effect \"agingtv\"\n\n"
148     "This will apply the agingtv effect to \"media\" and play it back.",
149     {
150       {
151         "bin-description", "d", 0, "asset-id",
152         "gst-launch style bin description."
153       },
154       {
155         "element-name", "e", 0, NULL,
156         "The name of the element to apply the effect on."
157       },
158       {
159         "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
160         "Implies that the effect has 'internal content'"
161         "(see [ges_track_element_set_has_internal_source](ges_track_element_set_has_internal_source))",
162       },
163       {
164         "name", "n", 0, "child-name",
165         "The name to be given to the effect."
166       },
167       {NULL, NULL, 0, NULL, FALSE},
168     },
169   },
170   {"test-clip", 0, (ActionFromStructureFunc) _ges_command_line_formatter_add_test_clip,
171     "<test clip pattern>", "Add a test clip in the timeline.",
172     NULL,
173     {
174       {
175         "pattern", "p", 0, NULL,
176         "The testsource pattern name."
177       },
178       {
179         "name", "n", 0, NULL,
180         "The name of the clip, can be used as an ID later."
181       },
182       {
183         "start", "s", GST_TYPE_CLOCK_TIME, NULL,
184         "The starting position of the clip in the timeline."
185       },
186       {
187         "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
188         "The duration of the clip."
189       },
190       {
191         "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
192         "The inpoint of the clip (time in the input file to start playing)."
193       },
194       {
195         "layer", "l", 0, NULL,
196         "The priority of the layer into which the clip should be added."
197       },
198       {NULL, 0, 0, NULL, FALSE},
199     },
200   },
201   {"title", 'c', (ActionFromStructureFunc) _ges_command_line_formatter_add_title_clip,
202     "<title text>", "Adds a clip in the timeline.", NULL,
203     {
204       {
205         "text", "n", 0, NULL,
206         "The text to be used as title."
207       },
208       {
209         "name", "n", 0, NULL,
210         "The name of the clip, can be used as an ID later."
211       },
212       {
213         "start", "s",GST_TYPE_CLOCK_TIME, NULL,
214         "The starting position of the clip in the timeline."
215       },
216       {
217         "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
218         "The duration of the clip."
219       },
220       {
221         "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
222         "The inpoint of the clip (time in the input file to start playing from)."
223       },
224       {
225         "track-types", "tt", 0, NULL,
226         "The type of the tracks where the clip should be used (audio or video or audio+video)."
227       },
228       {
229         "layer", "l", 0, NULL,
230         "The priority of the layer into which the clip should be added."
231       },
232       {NULL, 0, 0, NULL, FALSE},
233     },
234   },
235   {
236     "set-", 0, NULL,
237     "<property name> <value>", "Set a property on the last added element."
238     " Any child property that exists on the previously added element"
239     " can be used as <property name>"
240     "By default, set-<property-name> will lookup the property on the last added"
241     "object.",
242     "    ges-launch-1.0 +clip /path/to/media set-alpha 0.3\n\n"
243     "This will set the alpha property on \"media\" then play it back, assuming \"media\""
244     "contains a video stream.\n\n"
245     "    ges-launch-1.0 +clip /path/to/media +effect \"agingtv\" set-dusts false\n\n"
246     "This will set the \"dusts\" property of the agingtv to false and play the\n"
247     "timeline back.",
248     {
249       {NULL, NULL, 0, NULL, FALSE},
250     },
251   },
252 };
253 /*  *INDENT-ON* */
254
255 /* Should always be in the same order as the options */
256 typedef enum
257 {
258   CLIP,
259   EFFECT,
260   TEST_CLIP,
261   TITLE,
262   SET,
263 } GESCommandLineOptionType;
264
265 static gint                     /*  -1: not present, 0: failure, 1: OK */
266 _convert_to_clocktime (GstStructure * structure, const gchar * name,
267     GstClockTime default_value)
268 {
269   gint res = 1;
270   gdouble val;
271   GValue d_val = { 0 };
272   GstClockTime timestamp;
273   const GValue *gvalue = gst_structure_get_value (structure, name);
274
275   if (gvalue == NULL) {
276     timestamp = default_value;
277
278     res = -1;
279
280     goto done;
281   }
282
283   if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING) {
284     const gchar *v = g_value_get_string (gvalue);
285     return v && v[0] == 'f';
286   }
287
288   if (G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME)
289     return 1;
290
291   g_value_init (&d_val, G_TYPE_DOUBLE);
292   if (!g_value_transform (gvalue, &d_val)) {
293     GST_ERROR ("Could not get timestamp for %s", name);
294
295     return 0;
296   }
297   val = g_value_get_double ((const GValue *) &d_val);
298
299   if (val == -1.0)
300     timestamp = GST_CLOCK_TIME_NONE;
301   else
302     timestamp = val * GST_SECOND;
303
304 done:
305   gst_structure_set (structure, name, G_TYPE_UINT64, timestamp, NULL);
306
307   return res;
308 }
309
310 static gboolean
311 _cleanup_fields (const Property * field_names, GstStructure * structure,
312     GError ** error)
313 {
314   guint i;
315
316   for (i = 0; field_names[i].long_name; i++) {
317     gboolean exists = FALSE;
318
319     /* Move shortly named fields to longname variante */
320     if (gst_structure_has_field (structure, field_names[i].short_name)) {
321       exists = TRUE;
322
323       if (gst_structure_has_field (structure, field_names[i].long_name)) {
324         *error = g_error_new (GES_ERROR, 0, "Using short and long name"
325             " at the same time for property: %s, which one should I use?!",
326             field_names[i].long_name);
327
328         return FALSE;
329       } else {
330         const GValue *val =
331             gst_structure_get_value (structure, field_names[i].short_name);
332
333         gst_structure_set_value (structure, field_names[i].long_name, val);
334         gst_structure_remove_field (structure, field_names[i].short_name);
335       }
336     } else if (gst_structure_has_field (structure, field_names[i].long_name)) {
337       exists = TRUE;
338     }
339
340     if (exists) {
341       if (field_names[i].type == GST_TYPE_CLOCK_TIME) {
342         if (_convert_to_clocktime (structure, field_names[i].long_name, 0) == 0) {
343           *error = g_error_new (GES_ERROR, 0, "Could not convert"
344               " %s to GstClockTime", field_names[i].long_name);
345
346           return FALSE;
347         }
348       }
349     }
350
351     if (field_names[i].new_name
352         && gst_structure_has_field (structure, field_names[i].long_name)) {
353       const GValue *val =
354           gst_structure_get_value (structure, field_names[i].long_name);
355
356       gst_structure_set_value (structure, field_names[i].new_name, val);
357       gst_structure_remove_field (structure, field_names[i].long_name);
358     }
359   }
360
361   return TRUE;
362 }
363
364 static gboolean
365 _ges_command_line_formatter_add_clip (GESTimeline * timeline,
366     GstStructure * structure, GError ** error)
367 {
368   GESProject *proj;
369   GESAsset *asset;
370   if (!_cleanup_fields (options[CLIP].properties, structure, error))
371     return FALSE;
372
373   gst_structure_set (structure, "type", G_TYPE_STRING, "GESUriClip", NULL);
374
375   if (!_ges_add_clip_from_struct (timeline, structure, error))
376     return FALSE;
377
378   proj = GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline)));
379   asset = _ges_get_asset_from_timeline (timeline, GES_TYPE_URI_CLIP,
380       gst_structure_get_string (structure, "asset-id"), NULL);
381   ges_project_add_asset (proj, asset);
382
383   return TRUE;
384 }
385
386 static gboolean
387 _ges_command_line_formatter_add_test_clip (GESTimeline * timeline,
388     GstStructure * structure, GError ** error)
389 {
390   if (!_cleanup_fields (options[TEST_CLIP].properties, structure, error))
391     return FALSE;
392
393   gst_structure_set (structure, "type", G_TYPE_STRING, "GESTestClip", NULL);
394
395   if (!gst_structure_has_field_typed (structure, "asset-id", G_TYPE_STRING))
396     gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTestClip",
397         NULL);
398
399   return _ges_add_clip_from_struct (timeline, structure, error);
400 }
401
402 static gboolean
403 _ges_command_line_formatter_add_title_clip (GESTimeline * timeline,
404     GstStructure * structure, GError ** error)
405 {
406   if (!_cleanup_fields (options[TEST_CLIP].properties, structure, error))
407     return FALSE;
408
409   gst_structure_set (structure, "type", G_TYPE_STRING, "GESTitleClip", NULL);
410   gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTitleClip",
411       NULL);
412   GST_ERROR ("Structure: %" GST_PTR_FORMAT, structure);
413
414   return _ges_add_clip_from_struct (timeline, structure, error);
415 }
416
417 static gboolean
418 _ges_command_line_formatter_add_effect (GESTimeline * timeline,
419     GstStructure * structure, GError ** error)
420 {
421   if (!_cleanup_fields (options[EFFECT].properties, structure, error))
422     return FALSE;
423
424   gst_structure_set (structure, "child-type", G_TYPE_STRING, "GESEffect", NULL);
425
426   return _ges_container_add_child_from_struct (timeline, structure, error);
427 }
428
429 gchar *
430 ges_command_line_formatter_get_help (gint nargs, gchar ** commands)
431 {
432   gint i;
433   GString *help = g_string_new (NULL);
434
435   for (i = 0; i < G_N_ELEMENTS (options); i++) {
436     gboolean print = nargs == 0;
437     GESCommandLineOption option = options[i];
438
439     if (!print) {
440       gint j;
441
442       for (j = 0; j < nargs; j++) {
443         gchar *cname = commands[j][0] == '+' ? &commands[j][1] : commands[j];
444
445         if (!g_strcmp0 (cname, option.long_name)) {
446           print = TRUE;
447           break;
448         }
449       }
450     }
451
452     if (print) {
453       gint j;
454
455       gchar *tmp = g_strdup_printf ("  `%s%s` - %s\n",
456           option.properties[0].long_name ? "+" : "",
457           option.long_name, option.synopsis);
458
459       g_string_append (help, tmp);
460       g_string_append (help, "  ");
461       g_string_append (help, "\n\n  ");
462       g_free (tmp);
463
464       for (j = 0; option.description[j] != '\0'; j++) {
465
466         if (j && (j % 80) == 0) {
467           while (option.description[j] != '\0' && option.description[j] != ' ')
468             g_string_append_c (help, option.description[j++]);
469           g_string_append (help, "\n  ");
470           continue;
471         }
472
473         g_string_append_c (help, option.description[j]);
474       }
475       g_string_append_c (help, '\n');
476
477       if (option.properties[0].long_name) {
478         gint j;
479
480         g_string_append (help, "\n  Properties:\n\n");
481
482         for (j = 1; option.properties[j].long_name; j++) {
483           Property prop = option.properties[j];
484           g_string_append_printf (help, "    * `%s`: %s\n", prop.long_name,
485               prop.desc);
486         }
487       }
488       if (option.examples) {
489         gint j;
490         gchar **examples = g_strsplit (option.examples, "\n", -1);
491
492         g_string_append (help, "\n  Examples:\n\n");
493         for (j = 0; examples[j]; j++) {
494           if (examples[j])
495             g_string_append_printf (help, "    %s", examples[j]);
496           g_string_append_c (help, '\n');
497         }
498         g_strfreev (examples);
499       }
500
501       g_string_append_c (help, '\n');
502     }
503   }
504
505   return g_string_free (help, FALSE);
506 }
507
508
509 static gboolean
510 _set_child_property (GESTimeline * timeline, GstStructure * structure,
511     GError ** error)
512 {
513   return _ges_set_child_property_from_struct (timeline, structure, error);
514 }
515
516 #define EXEC(func,structure,error) G_STMT_START { \
517   gboolean res = ((ActionFromStructureFunc)func)(timeline, structure, error); \
518   if (!res) {\
519     GST_ERROR ("Could not execute: %" GST_PTR_FORMAT ", error: %s", structure, (*error)->message); \
520     goto fail; \
521   } \
522 } G_STMT_END
523
524
525 static GESStructureParser *
526 _parse_structures (const gchar * string)
527 {
528   yyscan_t scanner;
529   GESStructureParser *parser = ges_structure_parser_new ();
530
531   priv_ges_parse_yylex_init_extra (parser, &scanner);
532   priv_ges_parse_yy_scan_string (string, scanner);
533   priv_ges_parse_yylex (scanner);
534   priv_ges_parse_yylex_destroy (scanner);
535
536   ges_structure_parser_end_of_file (parser);
537   return parser;
538 }
539
540 static gboolean
541 _can_load (GESFormatter * dummy_formatter, const gchar * string,
542     GError ** error)
543 {
544   gboolean res = FALSE;
545   GESStructureParser *parser;
546
547   if (string == NULL)
548     return FALSE;
549
550   parser = _parse_structures (string);
551
552   if (parser->structures)
553     res = TRUE;
554
555   gst_object_unref (parser);
556
557   return res;
558 }
559
560 static gboolean
561 _load (GESFormatter * self, GESTimeline * timeline, const gchar * string,
562     GError ** error)
563 {
564   guint i;
565   GList *tmp;
566   GError *err;
567   GESStructureParser *parser = _parse_structures (string);
568
569   err = ges_structure_parser_get_error (parser);
570
571   if (err) {
572     if (error)
573       *error = err;
574
575     return FALSE;
576   }
577
578   g_object_set (timeline, "auto-transition", TRUE, NULL);
579   if (!(ges_timeline_add_track (timeline, GES_TRACK (ges_video_track_new ()))))
580     goto fail;
581
582   if (!(ges_timeline_add_track (timeline, GES_TRACK (ges_audio_track_new ()))))
583     goto fail;
584
585   /* Here we've finished initializing our timeline, we're
586    * ready to start using it... by solely working with the layer !*/
587   for (tmp = parser->structures; tmp; tmp = tmp->next) {
588     const gchar *name = gst_structure_get_name (tmp->data);
589     if (g_str_has_prefix (name, "set-")) {
590       EXEC (_set_child_property, tmp->data, &err);
591       continue;
592     }
593
594     for (i = 0; i < G_N_ELEMENTS (options); i++) {
595       if (gst_structure_has_name (tmp->data, options[i].long_name)
596           || (strlen (name) == 1 && *name == options[i].short_name)) {
597         EXEC (((ActionFromStructureFunc) options[i].callback), tmp->data, &err);
598       }
599     }
600   }
601
602   gst_object_unref (parser);
603
604   return TRUE;
605
606 fail:
607   gst_object_unref (parser);
608   if (err) {
609     if (error)
610       *error = err;
611   }
612
613   return FALSE;
614 }
615
616 static void
617 ges_command_line_formatter_init (GESCommandLineFormatter * formatter)
618 {
619   formatter->priv = ges_command_line_formatter_get_instance_private (formatter);
620 }
621
622 static void
623 ges_command_line_formatter_finalize (GObject * object)
624 {
625   G_OBJECT_CLASS (ges_command_line_formatter_parent_class)->finalize (object);
626 }
627
628 static void
629 ges_command_line_formatter_class_init (GESCommandLineFormatterClass * klass)
630 {
631   GObjectClass *object_class = G_OBJECT_CLASS (klass);
632   GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (klass);
633
634   object_class->finalize = ges_command_line_formatter_finalize;
635
636   formatter_klass->can_load_uri = _can_load;
637   formatter_klass->load_from_uri = _load;
638   formatter_klass->rank = GST_RANK_MARGINAL;
639 }