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