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);
56 const gchar *long_name;
57 const gchar *short_name;
59 const gchar *new_name;
63 // Currently Clip has the most properties.. adapt as needed
64 #define MAX_PROPERTIES 8
67 const gchar *long_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;
78 static GESCommandLineOption options[] = {
79 {"clip", 'c', (ActionFromStructureFunc) _ges_command_line_formatter_add_clip,
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",
112 "uri", "n", 0, "asset-id",
113 "The URI of the media file."
116 "name", "n", 0, NULL,
117 "The name of the clip, can be used as an ID later."
120 "start", "s",GST_TYPE_CLOCK_TIME, NULL,
121 "The starting position of the clip in the timeline."
124 "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
125 "The duration of the clip."
128 "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
129 "The inpoint of the clip (time in the input file to start playing from)."
132 "track-types", "tt", 0, NULL,
133 "The type of the tracks where the clip should be used (audio or video or audio+video)."
136 "layer", "l", 0, NULL,
137 "The priority of the layer into which the clip should be added."
139 {NULL, 0, 0, NULL, FALSE},
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.",
151 "bin-description", "d", 0, "asset-id",
152 "gst-launch style bin description."
155 "element-name", "e", 0, NULL,
156 "The name of the element to apply the effect on."
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))",
164 "name", "n", 0, "child-name",
165 "The name to be given to the effect."
167 {NULL, NULL, 0, NULL, FALSE},
170 {"test-clip", 0, (ActionFromStructureFunc) _ges_command_line_formatter_add_test_clip,
171 "<test clip pattern>", "Add a test clip in the timeline.",
175 "pattern", "p", 0, NULL,
176 "The testsource pattern name."
179 "name", "n", 0, NULL,
180 "The name of the clip, can be used as an ID later."
183 "start", "s", GST_TYPE_CLOCK_TIME, NULL,
184 "The starting position of the clip in the timeline."
187 "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
188 "The duration of the clip."
191 "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
192 "The inpoint of the clip (time in the input file to start playing)."
195 "layer", "l", 0, NULL,
196 "The priority of the layer into which the clip should be added."
198 {NULL, 0, 0, NULL, FALSE},
201 {"title", 'c', (ActionFromStructureFunc) _ges_command_line_formatter_add_title_clip,
202 "<title text>", "Adds a clip in the timeline.", NULL,
205 "text", "n", 0, NULL,
206 "The text to be used as title."
209 "name", "n", 0, NULL,
210 "The name of the clip, can be used as an ID later."
213 "start", "s",GST_TYPE_CLOCK_TIME, NULL,
214 "The starting position of the clip in the timeline."
217 "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
218 "The duration of the clip."
221 "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
222 "The inpoint of the clip (time in the input file to start playing from)."
225 "track-types", "tt", 0, NULL,
226 "The type of the tracks where the clip should be used (audio or video or audio+video)."
229 "layer", "l", 0, NULL,
230 "The priority of the layer into which the clip should be added."
232 {NULL, 0, 0, NULL, FALSE},
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"
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"
249 {NULL, NULL, 0, NULL, FALSE},
255 /* Should always be in the same order as the options */
263 } GESCommandLineOptionType;
265 static gint /* -1: not present, 0: failure, 1: OK */
266 _convert_to_clocktime (GstStructure * structure, const gchar * name,
267 GstClockTime default_value)
271 GValue d_val = { 0 };
272 GstClockTime timestamp;
273 const GValue *gvalue = gst_structure_get_value (structure, name);
275 if (gvalue == NULL) {
276 timestamp = default_value;
283 if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING) {
284 const gchar *v = g_value_get_string (gvalue);
285 return v && v[0] == 'f';
288 if (G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME)
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);
297 val = g_value_get_double ((const GValue *) &d_val);
300 timestamp = GST_CLOCK_TIME_NONE;
302 timestamp = val * GST_SECOND;
305 gst_structure_set (structure, name, G_TYPE_UINT64, timestamp, NULL);
311 _cleanup_fields (const Property * field_names, GstStructure * structure,
316 for (i = 0; field_names[i].long_name; i++) {
317 gboolean exists = FALSE;
319 /* Move shortly named fields to longname variante */
320 if (gst_structure_has_field (structure, field_names[i].short_name)) {
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);
331 gst_structure_get_value (structure, field_names[i].short_name);
333 gst_structure_set_value (structure, field_names[i].long_name, val);
334 gst_structure_remove_field (structure, field_names[i].short_name);
336 } else if (gst_structure_has_field (structure, field_names[i].long_name)) {
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);
351 if (field_names[i].new_name
352 && gst_structure_has_field (structure, field_names[i].long_name)) {
354 gst_structure_get_value (structure, field_names[i].long_name);
356 gst_structure_set_value (structure, field_names[i].new_name, val);
357 gst_structure_remove_field (structure, field_names[i].long_name);
365 _ges_command_line_formatter_add_clip (GESTimeline * timeline,
366 GstStructure * structure, GError ** error)
370 if (!_cleanup_fields (options[CLIP].properties, structure, error))
373 gst_structure_set (structure, "type", G_TYPE_STRING, "GESUriClip", NULL);
375 if (!_ges_add_clip_from_struct (timeline, structure, error))
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);
387 _ges_command_line_formatter_add_test_clip (GESTimeline * timeline,
388 GstStructure * structure, GError ** error)
390 if (!_cleanup_fields (options[TEST_CLIP].properties, structure, error))
393 gst_structure_set (structure, "type", G_TYPE_STRING, "GESTestClip", NULL);
395 if (!gst_structure_has_field_typed (structure, "asset-id", G_TYPE_STRING))
396 gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTestClip",
399 return _ges_add_clip_from_struct (timeline, structure, error);
403 _ges_command_line_formatter_add_title_clip (GESTimeline * timeline,
404 GstStructure * structure, GError ** error)
406 if (!_cleanup_fields (options[TEST_CLIP].properties, structure, error))
409 gst_structure_set (structure, "type", G_TYPE_STRING, "GESTitleClip", NULL);
410 gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTitleClip",
412 GST_ERROR ("Structure: %" GST_PTR_FORMAT, structure);
414 return _ges_add_clip_from_struct (timeline, structure, error);
418 _ges_command_line_formatter_add_effect (GESTimeline * timeline,
419 GstStructure * structure, GError ** error)
421 if (!_cleanup_fields (options[EFFECT].properties, structure, error))
424 gst_structure_set (structure, "child-type", G_TYPE_STRING, "GESEffect", NULL);
426 return _ges_container_add_child_from_struct (timeline, structure, error);
430 ges_command_line_formatter_get_help (gint nargs, gchar ** commands)
433 GString *help = g_string_new (NULL);
435 for (i = 0; i < G_N_ELEMENTS (options); i++) {
436 gboolean print = nargs == 0;
437 GESCommandLineOption option = options[i];
442 for (j = 0; j < nargs; j++) {
443 gchar *cname = commands[j][0] == '+' ? &commands[j][1] : commands[j];
445 if (!g_strcmp0 (cname, option.long_name)) {
455 gchar *tmp = g_strdup_printf (" `%s%s` - %s\n",
456 option.properties[0].long_name ? "+" : "",
457 option.long_name, option.synopsis);
459 g_string_append (help, tmp);
460 g_string_append (help, " ");
461 g_string_append (help, "\n\n ");
464 for (j = 0; option.description[j] != '\0'; j++) {
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 ");
473 g_string_append_c (help, option.description[j]);
475 g_string_append_c (help, '\n');
477 if (option.properties[0].long_name) {
480 g_string_append (help, "\n Properties:\n\n");
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,
488 if (option.examples) {
490 gchar **examples = g_strsplit (option.examples, "\n", -1);
492 g_string_append (help, "\n Examples:\n\n");
493 for (j = 0; examples[j]; j++) {
495 g_string_append_printf (help, " %s", examples[j]);
496 g_string_append_c (help, '\n');
498 g_strfreev (examples);
501 g_string_append_c (help, '\n');
505 return g_string_free (help, FALSE);
510 _set_child_property (GESTimeline * timeline, GstStructure * structure,
513 return _ges_set_child_property_from_struct (timeline, structure, error);
516 #define EXEC(func,structure,error) G_STMT_START { \
517 gboolean res = ((ActionFromStructureFunc)func)(timeline, structure, error); \
519 GST_ERROR ("Could not execute: %" GST_PTR_FORMAT ", error: %s", structure, (*error)->message); \
525 static GESStructureParser *
526 _parse_structures (const gchar * string)
529 GESStructureParser *parser = ges_structure_parser_new ();
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);
536 ges_structure_parser_end_of_file (parser);
541 _can_load (GESFormatter * dummy_formatter, const gchar * string,
544 gboolean res = FALSE;
545 GESStructureParser *parser;
550 parser = _parse_structures (string);
552 if (parser->structures)
555 gst_object_unref (parser);
561 _load (GESFormatter * self, GESTimeline * timeline, const gchar * string,
567 GESStructureParser *parser = _parse_structures (string);
569 err = ges_structure_parser_get_error (parser);
578 g_object_set (timeline, "auto-transition", TRUE, NULL);
579 if (!(ges_timeline_add_track (timeline, GES_TRACK (ges_video_track_new ()))))
582 if (!(ges_timeline_add_track (timeline, GES_TRACK (ges_audio_track_new ()))))
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);
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);
602 gst_object_unref (parser);
607 gst_object_unref (parser);
617 ges_command_line_formatter_init (GESCommandLineFormatter * formatter)
619 formatter->priv = ges_command_line_formatter_get_instance_private (formatter);
623 ges_command_line_formatter_finalize (GObject * object)
625 G_OBJECT_CLASS (ges_command_line_formatter_parent_class)->finalize (object);
629 ges_command_line_formatter_class_init (GESCommandLineFormatterClass * klass)
631 GObjectClass *object_class = G_OBJECT_CLASS (klass);
632 GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (klass);
634 object_class->finalize = ges_command_line_formatter_finalize;
636 formatter_klass->can_load_uri = _can_load;
637 formatter_klass->load_from_uri = _load;
638 formatter_klass->rank = GST_RANK_MARGINAL;