ges: Add APIs to have a sens of frame numbers
[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         "name", "n", 0, "child-name",
160         "The name to be given to the effect."
161       },
162       {NULL, NULL, 0, NULL, FALSE},
163     },
164   },
165   {"test-clip", 0, (ActionFromStructureFunc) _ges_command_line_formatter_add_test_clip,
166     "<test clip pattern>", "Add a test clip in the timeline.",
167     NULL,
168     {
169       {
170         "pattern", "p", 0, NULL,
171         "The testsource pattern name."
172       },
173       {
174         "name", "n", 0, NULL,
175         "The name of the clip, can be used as an ID later."
176       },
177       {
178         "start", "s", GST_TYPE_CLOCK_TIME, NULL,
179         "The starting position of the clip in the timeline."
180       },
181       {
182         "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
183         "The duration of the clip."
184       },
185       {
186         "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
187         "The inpoint of the clip (time in the input file to start playing)."
188       },
189       {
190         "layer", "l", 0, NULL,
191         "The priority of the layer into which the clip should be added."
192       },
193       {NULL, 0, 0, NULL, FALSE},
194     },
195   },
196   {"title", 'c', (ActionFromStructureFunc) _ges_command_line_formatter_add_title_clip,
197     "<title text>", "Adds a clip in the timeline.", NULL,
198     {
199       {
200         "text", "n", 0, NULL,
201         "The text to be used as title."
202       },
203       {
204         "name", "n", 0, NULL,
205         "The name of the clip, can be used as an ID later."
206       },
207       {
208         "start", "s",GST_TYPE_CLOCK_TIME, NULL,
209         "The starting position of the clip in the timeline."
210       },
211       {
212         "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
213         "The duration of the clip."
214       },
215       {
216         "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
217         "The inpoint of the clip (time in the input file to start playing from)."
218       },
219       {
220         "track-types", "tt", 0, NULL,
221         "The type of the tracks where the clip should be used (audio or video or audio+video)."
222       },
223       {
224         "layer", "l", 0, NULL,
225         "The priority of the layer into which the clip should be added."
226       },
227       {NULL, 0, 0, NULL, FALSE},
228     },
229   },
230   {
231     "set-", 0, NULL,
232     "<property name> <value>", "Set a property on the last added element."
233     " Any child property that exists on the previously added element"
234     " can be used as <property name>"
235     "By default, set-<property-name> will lookup the property on the last added"
236     "object.",
237     "    ges-launch-1.0 +clip /path/to/media set-alpha 0.3\n\n"
238     "This will set the alpha property on \"media\" then play it back, assuming \"media\""
239     "contains a video stream.\n\n"
240     "    ges-launch-1.0 +clip /path/to/media +effect \"agingtv\" set-dusts false\n\n"
241     "This will set the \"dusts\" property of the agingtv to false and play the\n"
242     "timeline back.",
243     {
244       {NULL, NULL, 0, NULL, FALSE},
245     },
246   },
247 };
248 /*  *INDENT-ON* */
249
250 /* Should always be in the same order as the options */
251 typedef enum
252 {
253   CLIP,
254   EFFECT,
255   TEST_CLIP,
256   TITLE,
257   SET,
258 } GESCommandLineOptionType;
259
260 static gint                     /*  -1: not present, 0: failure, 1: OK */
261 _convert_to_clocktime (GstStructure * structure, const gchar * name,
262     GstClockTime default_value)
263 {
264   gint res = 1;
265   gdouble val;
266   GValue d_val = { 0 };
267   GstClockTime timestamp;
268   const GValue *gvalue = gst_structure_get_value (structure, name);
269
270   if (gvalue == NULL) {
271     timestamp = default_value;
272
273     res = -1;
274
275     goto done;
276   }
277
278   if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING) {
279     const gchar *v = g_value_get_string (gvalue);
280     return v && v[0] == 'f';
281   }
282
283   if (G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME)
284     return 1;
285
286   g_value_init (&d_val, G_TYPE_DOUBLE);
287   if (!g_value_transform (gvalue, &d_val)) {
288     GST_ERROR ("Could not get timestamp for %s", name);
289
290     return 0;
291   }
292   val = g_value_get_double ((const GValue *) &d_val);
293
294   if (val == -1.0)
295     timestamp = GST_CLOCK_TIME_NONE;
296   else
297     timestamp = val * GST_SECOND;
298
299 done:
300   gst_structure_set (structure, name, G_TYPE_UINT64, timestamp, NULL);
301
302   return res;
303 }
304
305 static gboolean
306 _cleanup_fields (const Property * field_names, GstStructure * structure,
307     GError ** error)
308 {
309   guint i;
310
311   for (i = 0; field_names[i].long_name; i++) {
312     gboolean exists = FALSE;
313
314     /* Move shortly named fields to longname variante */
315     if (gst_structure_has_field (structure, field_names[i].short_name)) {
316       exists = TRUE;
317
318       if (gst_structure_has_field (structure, field_names[i].long_name)) {
319         *error = g_error_new (GES_ERROR, 0, "Using short and long name"
320             " at the same time for property: %s, which one should I use?!",
321             field_names[i].long_name);
322
323         return FALSE;
324       } else {
325         const GValue *val =
326             gst_structure_get_value (structure, field_names[i].short_name);
327
328         gst_structure_set_value (structure, field_names[i].long_name, val);
329         gst_structure_remove_field (structure, field_names[i].short_name);
330       }
331     } else if (gst_structure_has_field (structure, field_names[i].long_name)) {
332       exists = TRUE;
333     }
334
335     if (exists) {
336       if (field_names[i].type == GST_TYPE_CLOCK_TIME) {
337         if (_convert_to_clocktime (structure, field_names[i].long_name, 0) == 0) {
338           *error = g_error_new (GES_ERROR, 0, "Could not convert"
339               " %s to GstClockTime", field_names[i].long_name);
340
341           return FALSE;
342         }
343       }
344     }
345
346     if (field_names[i].new_name
347         && gst_structure_has_field (structure, field_names[i].long_name)) {
348       const GValue *val =
349           gst_structure_get_value (structure, field_names[i].long_name);
350
351       gst_structure_set_value (structure, field_names[i].new_name, val);
352       gst_structure_remove_field (structure, field_names[i].long_name);
353     }
354   }
355
356   return TRUE;
357 }
358
359 static gboolean
360 _ges_command_line_formatter_add_clip (GESTimeline * timeline,
361     GstStructure * structure, GError ** error)
362 {
363   GESProject *proj;
364   GESAsset *asset;
365   if (!_cleanup_fields (options[CLIP].properties, structure, error))
366     return FALSE;
367
368   gst_structure_set (structure, "type", G_TYPE_STRING, "GESUriClip", NULL);
369
370   if (!_ges_add_clip_from_struct (timeline, structure, error))
371     return FALSE;
372
373   proj = GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline)));
374   asset = _ges_get_asset_from_timeline (timeline, GES_TYPE_URI_CLIP,
375       gst_structure_get_string (structure, "asset-id"), NULL);
376   ges_project_add_asset (proj, asset);
377
378   return TRUE;
379 }
380
381 static gboolean
382 _ges_command_line_formatter_add_test_clip (GESTimeline * timeline,
383     GstStructure * structure, GError ** error)
384 {
385   if (!_cleanup_fields (options[TEST_CLIP].properties, structure, error))
386     return FALSE;
387
388   gst_structure_set (structure, "type", G_TYPE_STRING, "GESTestClip", NULL);
389   gst_structure_set (structure, "asset-id", G_TYPE_STRING,
390       gst_structure_get_string (structure, "pattern"), NULL);
391
392   return _ges_add_clip_from_struct (timeline, structure, error);
393 }
394
395 static gboolean
396 _ges_command_line_formatter_add_title_clip (GESTimeline * timeline,
397     GstStructure * structure, GError ** error)
398 {
399   if (!_cleanup_fields (options[TEST_CLIP].properties, structure, error))
400     return FALSE;
401
402   gst_structure_set (structure, "type", G_TYPE_STRING, "GESTitleClip", NULL);
403   gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTitleClip",
404       NULL);
405   GST_ERROR ("Structure: %" GST_PTR_FORMAT, structure);
406
407   return _ges_add_clip_from_struct (timeline, structure, error);
408 }
409
410 static gboolean
411 _ges_command_line_formatter_add_effect (GESTimeline * timeline,
412     GstStructure * structure, GError ** error)
413 {
414   if (!_cleanup_fields (options[EFFECT].properties, structure, error))
415     return FALSE;
416
417   gst_structure_set (structure, "child-type", G_TYPE_STRING, "GESEffect", NULL);
418
419   return _ges_container_add_child_from_struct (timeline, structure, error);
420 }
421
422 gchar *
423 ges_command_line_formatter_get_help (gint nargs, gchar ** commands)
424 {
425   gint i;
426   GString *help = g_string_new (NULL);
427
428   for (i = 0; i < G_N_ELEMENTS (options); i++) {
429     gboolean print = nargs == 0;
430     GESCommandLineOption option = options[i];
431
432     if (!print) {
433       gint j;
434
435       for (j = 0; j < nargs; j++) {
436         gchar *cname = commands[j][0] == '+' ? &commands[j][1] : commands[j];
437
438         if (!g_strcmp0 (cname, option.long_name)) {
439           print = TRUE;
440           break;
441         }
442       }
443     }
444
445     if (print) {
446       gint j;
447
448       gchar *tmp = g_strdup_printf ("  `%s%s` - %s\n",
449           option.properties[0].long_name ? "+" : "",
450           option.long_name, option.synopsis);
451
452       g_string_append (help, tmp);
453       g_string_append (help, "  ");
454       g_string_append (help, "\n\n  ");
455       g_free (tmp);
456
457       for (j = 0; option.description[j] != '\0'; j++) {
458
459         if (j && (j % 80) == 0) {
460           while (option.description[j] != '\0' && option.description[j] != ' ')
461             g_string_append_c (help, option.description[j++]);
462           g_string_append (help, "\n  ");
463           continue;
464         }
465
466         g_string_append_c (help, option.description[j]);
467       }
468       g_string_append_c (help, '\n');
469
470       if (option.properties[0].long_name) {
471         gint j;
472
473         g_string_append (help, "\n  Properties:\n\n");
474
475         for (j = 1; option.properties[j].long_name; j++) {
476           Property prop = option.properties[j];
477           g_string_append_printf (help, "    * `%s`: %s\n", prop.long_name,
478               prop.desc);
479         }
480       }
481       if (option.examples) {
482         gint j;
483         gchar **examples = g_strsplit (option.examples, "\n", -1);
484
485         g_string_append (help, "\n  Examples:\n\n");
486         for (j = 0; examples[j]; j++) {
487           if (examples[j])
488             g_string_append_printf (help, "    %s", examples[j]);
489           g_string_append_c (help, '\n');
490         }
491         g_strfreev (examples);
492       }
493
494       g_string_append_c (help, '\n');
495     }
496   }
497
498   return g_string_free (help, FALSE);
499 }
500
501
502 static gboolean
503 _set_child_property (GESTimeline * timeline, GstStructure * structure,
504     GError ** error)
505 {
506   return _ges_set_child_property_from_struct (timeline, structure, error);
507 }
508
509 #define EXEC(func,structure,error) G_STMT_START { \
510   gboolean res = ((ActionFromStructureFunc)func)(timeline, structure, error); \
511   if (!res) {\
512     GST_ERROR ("Could not execute: %" GST_PTR_FORMAT ", error: %s", structure, (*error)->message); \
513     goto fail; \
514   } \
515 } G_STMT_END
516
517
518 static GESStructureParser *
519 _parse_structures (const gchar * string)
520 {
521   yyscan_t scanner;
522   GESStructureParser *parser = ges_structure_parser_new ();
523
524   priv_ges_parse_yylex_init_extra (parser, &scanner);
525   priv_ges_parse_yy_scan_string (string, scanner);
526   priv_ges_parse_yylex (scanner);
527   priv_ges_parse_yylex_destroy (scanner);
528
529   ges_structure_parser_end_of_file (parser);
530   return parser;
531 }
532
533 static gboolean
534 _can_load (GESFormatter * dummy_formatter, const gchar * string,
535     GError ** error)
536 {
537   gboolean res = FALSE;
538   GESStructureParser *parser;
539
540   if (string == NULL)
541     return FALSE;
542
543   parser = _parse_structures (string);
544
545   if (parser->structures)
546     res = TRUE;
547
548   gst_object_unref (parser);
549
550   return res;
551 }
552
553 static gboolean
554 _load (GESFormatter * self, GESTimeline * timeline, const gchar * string,
555     GError ** error)
556 {
557   guint i;
558   GList *tmp;
559   GError *err;
560   GESStructureParser *parser = _parse_structures (string);
561
562   err = ges_structure_parser_get_error (parser);
563
564   if (err) {
565     if (error)
566       *error = err;
567
568     return FALSE;
569   }
570
571   g_object_set (timeline, "auto-transition", TRUE, NULL);
572   if (!(ges_timeline_add_track (timeline, GES_TRACK (ges_video_track_new ()))))
573     goto fail;
574
575   if (!(ges_timeline_add_track (timeline, GES_TRACK (ges_audio_track_new ()))))
576     goto fail;
577
578   /* Here we've finished initializing our timeline, we're
579    * ready to start using it... by solely working with the layer !*/
580   for (tmp = parser->structures; tmp; tmp = tmp->next) {
581     const gchar *name = gst_structure_get_name (tmp->data);
582     if (g_str_has_prefix (name, "set-")) {
583       EXEC (_set_child_property, tmp->data, &err);
584       continue;
585     }
586
587     for (i = 0; i < G_N_ELEMENTS (options); i++) {
588       if (gst_structure_has_name (tmp->data, options[i].long_name)
589           || (strlen (name) == 1 && *name == options[i].short_name)) {
590         EXEC (((ActionFromStructureFunc) options[i].callback), tmp->data, &err);
591       }
592     }
593   }
594
595   gst_object_unref (parser);
596
597   return TRUE;
598
599 fail:
600   gst_object_unref (parser);
601   if (err) {
602     if (error)
603       *error = err;
604   }
605
606   return FALSE;
607 }
608
609 static void
610 ges_command_line_formatter_init (GESCommandLineFormatter * formatter)
611 {
612   formatter->priv = ges_command_line_formatter_get_instance_private (formatter);
613 }
614
615 static void
616 ges_command_line_formatter_finalize (GObject * object)
617 {
618   G_OBJECT_CLASS (ges_command_line_formatter_parent_class)->finalize (object);
619 }
620
621 static void
622 ges_command_line_formatter_class_init (GESCommandLineFormatterClass * klass)
623 {
624   GObjectClass *object_class = G_OBJECT_CLASS (klass);
625   GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (klass);
626
627   object_class->finalize = ges_command_line_formatter_finalize;
628
629   formatter_klass->can_load_uri = _can_load;
630   formatter_klass->load_from_uri = _load;
631   formatter_klass->rank = GST_RANK_MARGINAL;
632 }