command-line-formatter: Refactor to generate the documentation automatically
[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
21 #include "ges-command-line-formatter.h"
22
23 #include "ges/ges-structured-interface.h"
24 #include "ges-structure-parser.h"
25 #include "ges-internal.h"
26 #define YY_NO_UNISTD_H
27 #include "ges-parse-lex.h"
28
29 struct _GESCommandLineFormatterPrivate
30 {
31   gpointer dummy;
32 };
33
34
35 G_DEFINE_TYPE (GESCommandLineFormatter, ges_command_line_formatter,
36     GES_TYPE_FORMATTER);
37
38 static gboolean
39 _ges_command_line_formatter_add_clip (GESTimeline * timeline,
40     GstStructure * structure, GError ** error);
41 static gboolean
42 _ges_command_line_formatter_add_effect (GESTimeline * timeline,
43     GstStructure * structure, GError ** error);
44 static gboolean
45 _ges_command_line_formatter_add_test_clip (GESTimeline * timeline,
46     GstStructure * structure, GError ** error);
47
48 typedef struct
49 {
50   const gchar *long_name;
51   const gchar *short_name;
52   GType type;
53   const gchar *new_name;
54   const gchar *desc;
55 } Property;
56
57 // Currently Clip has the most properties.. adapt as needed
58 #define MAX_PROPERTIES 8
59 typedef struct
60 {
61   const gchar *long_name;
62   gchar short_name;
63   ActionFromStructureFunc callback;
64   const gchar *description;
65   /* The first property must be the ID on the command line */
66   Property properties[MAX_PROPERTIES];
67 } GESCommandLineOption;
68
69 /*  *INDENT-OFF* */
70 static GESCommandLineOption options[] = {
71   {"clip", 'c', (ActionFromStructureFunc) _ges_command_line_formatter_add_clip,
72     "<clip uri> - Adds a clip in the timeline.",
73     {
74       {
75         "uri", "n", 0, "asset-id",
76         "The URI of the media file."
77       },
78       {
79         "name", "n", 0, NULL,
80         "The name of the clip, can be used as an ID later."
81       },
82       {
83         "start", "s",GST_TYPE_CLOCK_TIME, NULL,
84         "The starting position of the clip in the timeline."
85       },
86       {
87         "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
88         "The duration of the clip."
89       },
90       {
91         "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
92         "The inpoint of the clip (time in the input file to start playing from)."
93       },
94       {
95         "track-types", "tt", 0, NULL,
96         "The type of the tracks where the clip should be used (audio or video or audio+video)."
97       },
98       {
99         "layer", "l", 0, NULL,
100         "The priority of the layer into which the clip should be added."
101       },
102       {NULL, 0, 0, NULL, FALSE},
103     },
104   },
105   {"effect", 'e', (ActionFromStructureFunc) _ges_command_line_formatter_add_effect,
106     "<effect bin description> - Adds an effect as specified by 'bin-description',\n"
107     "similar to gst-launch-style pipeline description, without setting properties\n"
108     "(see `set-` for information about how to set properties).\n",
109     {
110       {
111         "bin-description", "d", 0, "asset-id",
112         "gst-launch style bin description."
113       },
114       {
115         "element-name", "e", 0, NULL,
116         "The name of the element to apply the effect on."
117       },
118       {
119         "name", "n", 0, "child-name",
120         "The name to be given to the effect."
121       },
122       {NULL, NULL, 0, NULL, FALSE},
123     },
124   },
125   {"test-clip", 0, (ActionFromStructureFunc) _ges_command_line_formatter_add_test_clip,
126     "<test clip pattern> - Add a test clip in the timeline.",
127     {
128       {
129         "pattern", "p", 0, NULL,
130         "The testsource pattern name."
131       },
132       {
133         "name", "n", 0, NULL,
134         "The name of the clip, can be used as an ID later."
135       },
136       {
137         "start", "s",GST_TYPE_CLOCK_TIME, NULL,
138         "The starting position of the clip in the timeline."
139       },
140       {
141         "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
142         "The duration of the clip."
143       },
144       {
145         "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
146         "The inpoint of the clip (time in the input file to start playing)."
147       },
148       {
149         "layer", "l", 0, NULL,
150         "The priority of the layer into which the clip should be added."
151       },
152       {NULL, 0, 0, NULL, FALSE},
153     },
154   },
155   {
156     "set-", 0, NULL,
157     "<property name> <value> - Set a property on the last added element.\n"
158     "Any child property that exists on the previously added element\n"
159     "can be used as <property name>",
160     {
161       {NULL, NULL, 0, NULL, FALSE},
162     },
163   },
164 };
165 /*  *INDENT-ON* */
166
167 /* Should always be in the same order as the options */
168 typedef enum
169 {
170   CLIP,
171   EFFECT,
172   TEST_CLIP,
173   SET,
174 } GESCommandLineOptionType;
175
176 static gint                     /*  -1: not present, 0: failure, 1: OK */
177 _convert_to_clocktime (GstStructure * structure, const gchar * name,
178     GstClockTime default_value)
179 {
180   gint res = 1;
181   gdouble val;
182   GValue d_val = { 0 };
183   GstClockTime timestamp;
184   const GValue *gvalue = gst_structure_get_value (structure, name);
185
186   if (gvalue == NULL) {
187     timestamp = default_value;
188
189     res = -1;
190
191     goto done;
192   }
193
194   if (G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME)
195     return 1;
196
197   g_value_init (&d_val, G_TYPE_DOUBLE);
198   if (!g_value_transform (gvalue, &d_val)) {
199     GST_ERROR ("Could not get timestamp for %s", name);
200
201     return 0;
202   }
203   val = g_value_get_double ((const GValue *) &d_val);
204
205   if (val == -1.0)
206     timestamp = GST_CLOCK_TIME_NONE;
207   else
208     timestamp = val * GST_SECOND;
209
210 done:
211   gst_structure_set (structure, name, G_TYPE_UINT64, timestamp, NULL);
212
213   return res;
214 }
215
216 static gboolean
217 _cleanup_fields (const Property * field_names, GstStructure * structure,
218     GError ** error)
219 {
220   guint i;
221
222   for (i = 0; field_names[i].long_name; i++) {
223     gboolean exists = FALSE;
224
225     /* Move shortly named fields to longname variante */
226     if (gst_structure_has_field (structure, field_names[i].short_name)) {
227       exists = TRUE;
228
229       if (gst_structure_has_field (structure, field_names[i].long_name)) {
230         *error = g_error_new (GES_ERROR, 0, "Using short and long name"
231             " at the same time for property: %s, which one should I use?!",
232             field_names[i].long_name);
233
234         return FALSE;
235       } else {
236         const GValue *val =
237             gst_structure_get_value (structure, field_names[i].short_name);
238
239         gst_structure_set_value (structure, field_names[i].long_name, val);
240         gst_structure_remove_field (structure, field_names[i].short_name);
241       }
242     } else if (gst_structure_has_field (structure, field_names[i].long_name)) {
243       exists = TRUE;
244     }
245
246     if (exists) {
247       if (field_names[i].type == GST_TYPE_CLOCK_TIME) {
248         if (_convert_to_clocktime (structure, field_names[i].long_name, 0) == 0) {
249           *error = g_error_new (GES_ERROR, 0, "Could not convert"
250               " %s to GstClockTime", field_names[i].long_name);
251
252           return FALSE;
253         }
254       }
255     }
256
257     if (field_names[i].new_name
258         && gst_structure_has_field (structure, field_names[i].long_name)) {
259       const GValue *val =
260           gst_structure_get_value (structure, field_names[i].long_name);
261
262       gst_structure_set_value (structure, field_names[i].new_name, val);
263       gst_structure_remove_field (structure, field_names[i].long_name);
264     }
265   }
266
267   return TRUE;
268 }
269
270 static gboolean
271 _ges_command_line_formatter_add_clip (GESTimeline * timeline,
272     GstStructure * structure, GError ** error)
273 {
274   if (!_cleanup_fields (options[CLIP].properties, structure, error))
275     return FALSE;
276
277   gst_structure_set (structure, "type", G_TYPE_STRING, "GESUriClip", NULL);
278
279   return _ges_add_clip_from_struct (timeline, structure, error);
280 }
281
282 static gboolean
283 _ges_command_line_formatter_add_test_clip (GESTimeline * timeline,
284     GstStructure * structure, GError ** error)
285 {
286   if (!_cleanup_fields (options[TEST_CLIP].properties, structure, error))
287     return FALSE;
288
289   gst_structure_set (structure, "type", G_TYPE_STRING, "GESTestClip", NULL);
290   gst_structure_set (structure, "asset-id", G_TYPE_STRING,
291       gst_structure_get_string (structure, "pattern"), NULL);
292
293   return _ges_add_clip_from_struct (timeline, structure, error);
294 }
295
296 static gboolean
297 _ges_command_line_formatter_add_effect (GESTimeline * timeline,
298     GstStructure * structure, GError ** error)
299 {
300   if (!_cleanup_fields (options[EFFECT].properties, structure, error))
301     return FALSE;
302
303   gst_structure_set (structure, "child-type", G_TYPE_STRING, "GESEffect", NULL);
304
305   return _ges_container_add_child_from_struct (timeline, structure, error);
306 }
307
308 gchar *
309 ges_command_line_formatter_get_help (gint nargs, gchar ** commands)
310 {
311   gint i;
312   GString *help = g_string_new (NULL);
313
314   for (i = 0; i < G_N_ELEMENTS (options); i++) {
315     gboolean print = nargs == 0;
316     GESCommandLineOption option = options[i];
317
318     if (!print) {
319       gint j;
320
321       for (j = 0; j < nargs; j++) {
322         gchar *cname = commands[j][0] == '+' ? &commands[j][1] : commands[j];
323
324         if (!g_strcmp0 (cname, option.long_name)) {
325           print = TRUE;
326           break;
327         }
328       }
329     }
330
331     if (print) {
332       g_string_append_printf (help, "%s%s %s\n",
333           option.properties[0].long_name ? "+" : "",
334           option.long_name, option.description);
335
336       if (option.properties[0].long_name) {
337         gint j;
338
339         g_string_append (help, "  Properties:\n");
340
341         for (j = 1; option.properties[j].long_name; j++) {
342           Property prop = option.properties[j];
343           g_string_append_printf (help, "    * %s: %s\n", prop.long_name,
344               prop.desc);
345         }
346       }
347
348       g_string_append (help, "\n");
349     }
350   }
351
352   return g_string_free (help, FALSE);
353 }
354
355
356 static gboolean
357 _set_child_property (GESTimeline * timeline, GstStructure * structure,
358     GError ** error)
359 {
360   return _ges_set_child_property_from_struct (timeline, structure, error);
361 }
362
363 #define EXEC(func,structure,error) G_STMT_START { \
364   gboolean res = ((ActionFromStructureFunc)func)(timeline, structure, error); \
365   if (!res) {\
366     GST_ERROR ("Could not execute: %" GST_PTR_FORMAT ", error: %s", structure, (*error)->message); \
367     goto fail; \
368   } \
369 } G_STMT_END
370
371
372 static GESStructureParser *
373 _parse_structures (const gchar * string)
374 {
375   yyscan_t scanner;
376   GESStructureParser *parser = ges_structure_parser_new ();
377
378   priv_ges_parse_yylex_init_extra (parser, &scanner);
379   priv_ges_parse_yy_scan_string (string, scanner);
380   priv_ges_parse_yylex (scanner);
381   priv_ges_parse_yylex_destroy (scanner);
382
383   ges_structure_parser_end_of_file (parser);
384   return parser;
385 }
386
387 static gboolean
388 _can_load (GESFormatter * dummy_formatter, const gchar * string,
389     GError ** error)
390 {
391   gboolean res = FALSE;
392   GESStructureParser *parser;
393
394   if (string == NULL)
395     return FALSE;
396
397   parser = _parse_structures (string);
398
399   if (parser->structures)
400     res = TRUE;
401
402   gst_object_unref (parser);
403
404   return res;
405 }
406
407 static gboolean
408 _load (GESFormatter * self, GESTimeline * timeline, const gchar * string,
409     GError ** error)
410 {
411   guint i;
412   GList *tmp;
413   GError *err;
414   GESStructureParser *parser = _parse_structures (string);
415
416   err = ges_structure_parser_get_error (parser);
417
418   if (err) {
419     if (error)
420       *error = err;
421
422     return FALSE;
423   }
424
425   g_object_set (timeline, "auto-transition", TRUE, NULL);
426   if (!(ges_timeline_add_track (timeline, GES_TRACK (ges_video_track_new ()))))
427     goto fail;
428
429   if (!(ges_timeline_add_track (timeline, GES_TRACK (ges_audio_track_new ()))))
430     goto fail;
431
432   /* Here we've finished initializing our timeline, we're
433    * ready to start using it... by solely working with the layer !*/
434   for (tmp = parser->structures; tmp; tmp = tmp->next) {
435     const gchar *name = gst_structure_get_name (tmp->data);
436     if (g_str_has_prefix (name, "set-")) {
437       EXEC (_set_child_property, tmp->data, &err);
438       continue;
439     }
440
441     for (i = 0; i < G_N_ELEMENTS (options); i++) {
442       if (gst_structure_has_name (tmp->data, options[i].long_name)
443           || (strlen (name) == 1 && *name == options[i].short_name)) {
444         EXEC (((ActionFromStructureFunc) options[i].callback), tmp->data, &err);
445       }
446     }
447   }
448
449   gst_object_unref (parser);
450
451   return TRUE;
452
453 fail:
454   gst_object_unref (parser);
455   if (err) {
456     if (error)
457       *error = err;
458   }
459
460   return FALSE;
461 }
462
463 static void
464 ges_command_line_formatter_init (GESCommandLineFormatter *
465     ges_command_line_formatter)
466 {
467   ges_command_line_formatter->priv =
468       G_TYPE_INSTANCE_GET_PRIVATE (ges_command_line_formatter,
469       GES_TYPE_COMMAND_LINE_FORMATTER, GESCommandLineFormatterPrivate);
470
471   /* TODO: Add initialization code here */
472 }
473
474 static void
475 ges_command_line_formatter_finalize (GObject * object)
476 {
477   /* TODO: Add deinitalization code here */
478
479   G_OBJECT_CLASS (ges_command_line_formatter_parent_class)->finalize (object);
480 }
481
482 static void
483 ges_command_line_formatter_class_init (GESCommandLineFormatterClass * klass)
484 {
485   GObjectClass *object_class = G_OBJECT_CLASS (klass);
486   GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (klass);
487
488   g_type_class_add_private (klass, sizeof (GESCommandLineFormatterPrivate));
489
490   object_class->finalize = ges_command_line_formatter_finalize;
491
492   formatter_klass->can_load_uri = _can_load;
493   formatter_klass->load_from_uri = _load;
494   formatter_klass->rank = GST_RANK_MARGINAL;
495 }