1 /* GStreamer Editing Services
3 * Copyright (C) <2015> Thibault Saunier <tsaunier@gnome.org>
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.
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.
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.
24 #include "ges-command-line-formatter.h"
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"
32 struct _GESCommandLineFormatterPrivate
38 G_DEFINE_TYPE_WITH_PRIVATE (GESCommandLineFormatter, ges_command_line_formatter,
42 _ges_command_line_formatter_add_clip (GESTimeline * timeline,
43 GstStructure * structure, GError ** error);
45 _ges_command_line_formatter_add_effect (GESTimeline * timeline,
46 GstStructure * structure, GError ** error);
48 _ges_command_line_formatter_add_test_clip (GESTimeline * timeline,
49 GstStructure * structure, GError ** error);
51 _ges_command_line_formatter_add_title_clip (GESTimeline * timeline,
52 GstStructure * structure, GError ** error);
54 _ges_command_line_formatter_add_track (GESTimeline * timeline,
55 GstStructure * structure, GError ** error);
59 const gchar *long_name;
60 const gchar *short_name;
62 const gchar *new_name;
66 // Currently Clip has the most properties.. adapt as needed
67 #define MAX_PROPERTIES 8
70 const gchar *long_name;
72 ActionFromStructureFunc callback;
73 const gchar *synopsis;
74 const gchar *description;
75 const gchar *examples;
76 /* The first property must be the ID on the command line */
77 Property properties[MAX_PROPERTIES];
78 } GESCommandLineOption;
81 static GESCommandLineOption options[] = {
82 {"clip", 'c', (ActionFromStructureFunc) _ges_command_line_formatter_add_clip,
84 "Adds a clip in the timeline. "
85 "See documentation for the --track-types option to ges-launch-1.0, as it "
86 " will affect the result of this command.",
87 " ges-launch-1.0 +clip /path/to/media\n\n"
88 "This will simply play the sample from its beginning to its end.\n\n"
89 " ges-launch-1.0 +clip /path/to/media inpoint=4.0\n\n"
90 "Assuming 'media' is a 10 second long media sample, this will play the sample\n"
91 "from the 4th second to the 10th, resulting in a 6-seconds long playback.\n\n"
92 " ges-launch-1.0 +clip /path/to/media inpoint=4.0 duration=2.0 start=4.0\n\n"
93 "Assuming \"media\" is an audio video sample longer than 6 seconds, this will play\n"
94 "a black frame and silence for 4 seconds, then the sample from its 4th second to\n"
95 "its sixth second, resulting in a 6-seconds long playback.\n\n"
96 " ges-launch-1.0 --track-types=audio +clip /path/to/media\n\n"
97 "Assuming \"media\" is an audio video sample, this will only play the audio of the\n"
98 "sample in its entirety.\n\n"
99 " ges-launch-1.0 +clip /path/to/media1 layer=1 set-alpha 0.9 +clip /path/to/media2 layer=0\n\n"
100 "Assume media1 and media2 both contain audio and video and last for 10 seconds.\n\n"
101 "This will first add media1 in a new layer of \"priority\" 1, thus implicitly\n"
102 "creating a layer of \"priority\" 0, the start of the clip will be 0 as no clip\n"
103 "had been added in that layer before.\n\n"
104 "It will then add media2 in the layer of \"priority\" 0 which was created\n"
105 "previously, the start of this new clip will also be 0 as no clip has been added\n"
106 "in this layer before.\n\n"
107 "Both clips will thus overlap on two layers for 10 seconds.\n\n"
108 "The \"alpha\" property of the second clip will finally be set to a value of 0.9.\n\n"
109 "All this will result in a 10 seconds playback, where media2 is barely visible\n"
110 "through media1, which is nearly opaque. If alpha was set to 0.5, both clips\n"
111 "would be equally visible, and if it was set to 0.0, media1 would be invisible\n"
112 "and media2 completely opaque.\n",
115 "uri", "u", 0, "asset-id",
116 "The URI of the media file."
119 "name", "n", 0, NULL,
120 "The name of the clip, can be used as an ID later."
123 "start", "s",GST_TYPE_CLOCK_TIME, NULL,
124 "The starting position of the clip in the timeline."
127 "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
128 "The duration of the clip."
131 "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
132 "The inpoint of the clip (time in the input file to start playing from)."
135 "track-types", "tt", 0, NULL,
136 "The type of the tracks where the clip should be used (audio or video or audio+video)."
139 "layer", "l", 0, NULL,
140 "The priority of the layer into which the clip should be added."
142 {NULL, 0, 0, NULL, FALSE},
145 {"effect", 'e', (ActionFromStructureFunc) _ges_command_line_formatter_add_effect,
146 "<effect bin description>",
147 "Adds an effect as specified by 'bin-description', similar to gst-launch-style"
148 " pipeline description, without setting properties (see `set-<property-name>` for information"
149 " about how to set properties).",
150 " ges-launch-1.0 +clip /path/to/media +effect \"agingtv\"\n\n"
151 "This will apply the agingtv effect to \"media\" and play it back.",
154 "bin-description", "d", 0, "asset-id",
155 "gst-launch style bin description."
158 "element-name", "e", 0, NULL,
159 "The name of the element to apply the effect on."
162 "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
163 "Implies that the effect has 'internal content'"
164 "(see [ges_track_element_set_has_internal_source](ges_track_element_set_has_internal_source))",
167 "name", "n", 0, "child-name",
168 "The name to be given to the effect."
170 {NULL, NULL, 0, NULL, FALSE},
173 {"test-clip", 0, (ActionFromStructureFunc) _ges_command_line_formatter_add_test_clip,
174 "<test clip pattern>", "Add a test clip in the timeline.",
178 "pattern", "p", 0, NULL,
179 "The testsource pattern name."
182 "name", "n", 0, NULL,
183 "The name of the clip, can be used as an ID later."
186 "start", "s", GST_TYPE_CLOCK_TIME, NULL,
187 "The starting position of the clip in the timeline."
190 "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
191 "The duration of the clip."
194 "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
195 "The inpoint of the clip (time in the input file to start playing)."
198 "layer", "l", 0, NULL,
199 "The priority of the layer into which the clip should be added."
201 {NULL, 0, 0, NULL, FALSE},
204 {"title", 'c', (ActionFromStructureFunc) _ges_command_line_formatter_add_title_clip,
205 "<title text>", "Adds a clip in the timeline.", NULL,
208 "text", "t", 0, NULL,
209 "The text to be used as title."
212 "name", "n", 0, NULL,
213 "The name of the clip, can be used as an ID later."
216 "start", "s",GST_TYPE_CLOCK_TIME, NULL,
217 "The starting position of the clip in the timeline."
220 "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
221 "The duration of the clip."
224 "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
225 "The inpoint of the clip (time in the input file to start playing from)."
228 "track-types", "tt", 0, NULL,
229 "The type of the tracks where the clip should be used (audio or video or audio+video)."
232 "layer", "l", 0, NULL,
233 "The priority of the layer into which the clip should be added."
235 {NULL, 0, 0, NULL, FALSE},
238 {"track", 't', (ActionFromStructureFunc) _ges_command_line_formatter_add_track,
239 "<track type>", "Adds a track to the timeline.", NULL,
242 "restrictions", "r", 0, NULL,
243 "The restriction caps to set on the track."
245 {NULL, 0, 0, NULL, FALSE},
250 "<property name> <value>", "Set a property on the last added element."
251 " Any child property that exists on the previously added element"
252 " can be used as <property name>"
253 "By default, set-<property-name> will lookup the property on the last added"
255 " ges-launch-1.0 +clip /path/to/media set-alpha 0.3\n\n"
256 "This will set the alpha property on \"media\" then play it back, assuming \"media\""
257 "contains a video stream.\n\n"
258 " ges-launch-1.0 +clip /path/to/media +effect \"agingtv\" set-dusts false\n\n"
259 "This will set the \"dusts\" property of the agingtv to false and play the\n"
262 {NULL, NULL, 0, NULL, FALSE},
268 /* Should always be in the same order as the options */
277 } GESCommandLineOptionType;
279 static gint /* -1: not present, 0: failure, 1: OK */
280 _convert_to_clocktime (GstStructure * structure, const gchar * name,
281 GstClockTime default_value)
285 GValue d_val = G_VALUE_INIT, converted = G_VALUE_INIT;
286 GstClockTime timestamp;
287 const GValue *gvalue = gst_structure_get_value (structure, name);
289 if (gvalue == NULL) {
290 timestamp = default_value;
297 if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING) {
298 const gchar *val_string = g_value_get_string (gvalue);
299 /* if starts with an 'f', interpret as a frame number, keep as
300 * a string for now */
301 if (val_string && val_string[0] == 'f')
303 /* else, try convert to a GstClockTime, or a double */
304 g_value_init (&converted, GST_TYPE_CLOCK_TIME);
305 if (!gst_value_deserialize (&converted, val_string)) {
306 g_value_unset (&converted);
307 g_value_init (&converted, G_TYPE_DOUBLE);
308 if (!gst_value_deserialize (&converted, val_string)) {
309 GST_ERROR ("Could not get timestamp for %s by deserializing %s",
315 g_value_init (&converted, G_VALUE_TYPE (gvalue));
316 g_value_copy (gvalue, &converted);
319 if (G_VALUE_TYPE (&converted) == GST_TYPE_CLOCK_TIME) {
320 timestamp = g_value_get_uint64 (&converted);
324 g_value_init (&d_val, G_TYPE_DOUBLE);
326 if (!g_value_transform (&converted, &d_val)) {
327 GST_ERROR ("Could not get timestamp for %s", name);
331 val = g_value_get_double ((const GValue *) &d_val);
332 g_value_unset (&d_val);
335 timestamp = GST_CLOCK_TIME_NONE;
337 timestamp = val * GST_SECOND;
340 gst_structure_set (structure, name, G_TYPE_UINT64, timestamp, NULL);
341 g_value_unset (&converted);
346 g_value_unset (&converted);
352 _cleanup_fields (const Property * field_names, GstStructure * structure,
357 for (i = 0; field_names[i].long_name; i++) {
358 gboolean exists = FALSE;
360 /* Move shortly named fields to longname variante */
361 if (gst_structure_has_field (structure, field_names[i].short_name)) {
364 if (gst_structure_has_field (structure, field_names[i].long_name)) {
365 *error = g_error_new (GES_ERROR, 0, "Using short and long name"
366 " at the same time for property: %s, which one should I use?!",
367 field_names[i].long_name);
372 gst_structure_get_value (structure, field_names[i].short_name);
374 gst_structure_set_value (structure, field_names[i].long_name, val);
375 gst_structure_remove_field (structure, field_names[i].short_name);
377 } else if (gst_structure_has_field (structure, field_names[i].long_name)) {
382 if (field_names[i].type == GST_TYPE_CLOCK_TIME) {
383 if (_convert_to_clocktime (structure, field_names[i].long_name, 0) == 0) {
384 *error = g_error_new (GES_ERROR, 0, "Could not convert"
385 " %s to GstClockTime", field_names[i].long_name);
392 if (field_names[i].new_name
393 && gst_structure_has_field (structure, field_names[i].long_name)) {
395 gst_structure_get_value (structure, field_names[i].long_name);
397 gst_structure_set_value (structure, field_names[i].new_name, val);
398 gst_structure_remove_field (structure, field_names[i].long_name);
406 _ges_command_line_formatter_add_clip (GESTimeline * timeline,
407 GstStructure * structure, GError ** error)
411 if (!_cleanup_fields (options[CLIP].properties, structure, error))
414 gst_structure_set (structure, "type", G_TYPE_STRING, "GESUriClip", NULL);
416 if (!_ges_add_clip_from_struct (timeline, structure, error))
419 proj = GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline)));
420 asset = _ges_get_asset_from_timeline (timeline, GES_TYPE_URI_CLIP,
421 gst_structure_get_string (structure, "asset-id"), NULL);
422 ges_project_add_asset (proj, asset);
428 _ges_command_line_formatter_add_test_clip (GESTimeline * timeline,
429 GstStructure * structure, GError ** error)
431 if (!_cleanup_fields (options[TEST_CLIP].properties, structure, error))
434 gst_structure_set (structure, "type", G_TYPE_STRING, "GESTestClip", NULL);
436 if (!gst_structure_has_field_typed (structure, "asset-id", G_TYPE_STRING))
437 gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTestClip",
440 return _ges_add_clip_from_struct (timeline, structure, error);
444 _ges_command_line_formatter_add_title_clip (GESTimeline * timeline,
445 GstStructure * structure, GError ** error)
447 if (!_cleanup_fields (options[TITLE].properties, structure, error))
450 gst_structure_set (structure, "type", G_TYPE_STRING, "GESTitleClip", NULL);
451 gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTitleClip",
453 GST_ERROR ("Structure: %" GST_PTR_FORMAT, structure);
455 return _ges_add_clip_from_struct (timeline, structure, error);
459 _ges_command_line_formatter_add_track (GESTimeline * timeline,
460 GstStructure * structure, GError ** error)
462 if (!_cleanup_fields (options[TRACK].properties, structure, error))
465 return _ges_add_track_from_struct (timeline, structure, error);
469 _ges_command_line_formatter_add_effect (GESTimeline * timeline,
470 GstStructure * structure, GError ** error)
472 if (!_cleanup_fields (options[EFFECT].properties, structure, error))
475 gst_structure_set (structure, "child-type", G_TYPE_STRING, "GESEffect", NULL);
477 return _ges_container_add_child_from_struct (timeline, structure, error);
481 ges_command_line_formatter_get_help (gint nargs, gchar ** commands)
484 GString *help = g_string_new (NULL);
486 for (i = 0; i < G_N_ELEMENTS (options); i++) {
487 gboolean print = nargs == 0;
488 GESCommandLineOption option = options[i];
493 for (j = 0; j < nargs; j++) {
494 gchar *cname = commands[j][0] == '+' ? &commands[j][1] : commands[j];
496 if (!g_strcmp0 (cname, option.long_name)) {
506 gchar *tmp = g_strdup_printf (" `%s%s` - %s\n",
507 option.properties[0].long_name ? "+" : "",
508 option.long_name, option.synopsis);
510 g_string_append (help, tmp);
511 g_string_append (help, " ");
512 g_string_append (help, "\n\n ");
515 for (j = 0; option.description[j] != '\0'; j++) {
517 if (j && (j % 80) == 0) {
518 while (option.description[j] != '\0' && option.description[j] != ' ')
519 g_string_append_c (help, option.description[j++]);
520 g_string_append (help, "\n ");
524 g_string_append_c (help, option.description[j]);
526 g_string_append_c (help, '\n');
528 if (option.properties[0].long_name) {
531 g_string_append (help, "\n Properties:\n\n");
533 for (j = 1; option.properties[j].long_name; j++) {
534 Property prop = option.properties[j];
535 g_string_append_printf (help, " * `%s`: %s\n", prop.long_name,
539 if (option.examples) {
541 gchar **examples = g_strsplit (option.examples, "\n", -1);
543 g_string_append (help, "\n Examples:\n\n");
544 for (j = 0; examples[j]; j++) {
546 g_string_append_printf (help, " %s", examples[j]);
547 g_string_append_c (help, '\n');
549 g_strfreev (examples);
552 g_string_append_c (help, '\n');
556 return g_string_free (help, FALSE);
561 _set_child_property (GESTimeline * timeline, GstStructure * structure,
564 return _ges_set_child_property_from_struct (timeline, structure, error);
567 #define EXEC(func,structure,error) G_STMT_START { \
568 gboolean res = ((ActionFromStructureFunc)func)(timeline, structure, error); \
570 GST_ERROR ("Could not execute: %" GST_PTR_FORMAT ", error: %s", structure, (*error)->message); \
576 static GESStructureParser *
577 _parse_structures (const gchar * string)
580 GESStructureParser *parser = ges_structure_parser_new ();
582 priv_ges_parse_yylex_init_extra (parser, &scanner);
583 priv_ges_parse_yy_scan_string (string, scanner);
584 priv_ges_parse_yylex (scanner);
585 priv_ges_parse_yylex_destroy (scanner);
587 ges_structure_parser_end_of_file (parser);
592 _can_load (GESFormatter * dummy_formatter, const gchar * string,
595 gboolean res = FALSE;
596 GESStructureParser *parser;
601 parser = _parse_structures (string);
603 if (parser->structures)
606 gst_object_unref (parser);
612 _load (GESFormatter * self, GESTimeline * timeline, const gchar * string,
618 GESStructureParser *parser = _parse_structures (string);
620 err = ges_structure_parser_get_error (parser);
629 g_object_set (timeline, "auto-transition", TRUE, NULL);
631 /* Here we've finished initializing our timeline, we're
632 * ready to start using it... by solely working with the layer !*/
633 for (tmp = parser->structures; tmp; tmp = tmp->next) {
634 const gchar *name = gst_structure_get_name (tmp->data);
635 if (g_str_has_prefix (name, "set-")) {
636 EXEC (_set_child_property, tmp->data, &err);
640 for (i = 0; i < G_N_ELEMENTS (options); i++) {
641 if (gst_structure_has_name (tmp->data, options[i].long_name)
642 || (strlen (name) == 1 && *name == options[i].short_name)) {
643 EXEC (((ActionFromStructureFunc) options[i].callback), tmp->data, &err);
648 gst_object_unref (parser);
653 gst_object_unref (parser);
663 ges_command_line_formatter_init (GESCommandLineFormatter * formatter)
665 formatter->priv = ges_command_line_formatter_get_instance_private (formatter);
669 ges_command_line_formatter_finalize (GObject * object)
671 G_OBJECT_CLASS (ges_command_line_formatter_parent_class)->finalize (object);
675 ges_command_line_formatter_class_init (GESCommandLineFormatterClass * klass)
677 GObjectClass *object_class = G_OBJECT_CLASS (klass);
678 GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (klass);
680 object_class->finalize = ges_command_line_formatter_finalize;
682 formatter_klass->can_load_uri = _can_load;
683 formatter_klass->load_from_uri = _load;
684 formatter_klass->rank = GST_RANK_MARGINAL;