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);
57 _ges_command_line_formatter_add_keyframes (GESTimeline * timeline,
58 GstStructure * structure, GError ** error);
62 const gchar *long_name;
63 const gchar *short_name;
65 const gchar *new_name;
69 // Currently Clip has the most properties.. adapt as needed
70 #define MAX_PROPERTIES 8
73 const gchar *long_name;
75 ActionFromStructureFunc callback;
76 const gchar *synopsis;
77 const gchar *description;
78 const gchar *examples;
79 /* The first property must be the ID on the command line */
80 Property properties[MAX_PROPERTIES];
81 } GESCommandLineOption;
84 static GESCommandLineOption options[] = {
88 .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_clip,
89 .synopsis="<clip uri>",
90 .description="Adds a clip in the timeline. "
91 "See documentation for the --track-types option to ges-launch-1.0, as it "
92 " will affect the result of this command.",
93 .examples=" ges-launch-1.0 +clip /path/to/media\n\n"
94 "This will simply play the sample from its beginning to its end.\n\n"
95 " ges-launch-1.0 +clip /path/to/media inpoint=4.0\n\n"
96 "Assuming 'media' is a 10 second long media sample, this will play the sample\n"
97 "from the 4th second to the 10th, resulting in a 6-seconds long playback.\n\n"
98 " ges-launch-1.0 +clip /path/to/media inpoint=4.0 duration=2.0 start=4.0\n\n"
99 "Assuming \"media\" is an audio video sample longer than 6 seconds, this will play\n"
100 "a black frame and silence for 4 seconds, then the sample from its 4th second to\n"
101 "its sixth second, resulting in a 6-seconds long playback.\n\n"
102 " ges-launch-1.0 --track-types=audio +clip /path/to/media\n\n"
103 "Assuming \"media\" is an audio video sample, this will only play the audio of the\n"
104 "sample in its entirety.\n\n"
105 " ges-launch-1.0 +clip /path/to/media1 layer=1 set-alpha 0.9 +clip /path/to/media2 layer=0\n\n"
106 "Assume media1 and media2 both contain audio and video and last for 10 seconds.\n\n"
107 "This will first add media1 in a new layer of \"priority\" 1, thus implicitly\n"
108 "creating a layer of \"priority\" 0, the start of the clip will be 0 as no clip\n"
109 "had been added in that layer before.\n\n"
110 "It will then add media2 in the layer of \"priority\" 0 which was created\n"
111 "previously, the start of this new clip will also be 0 as no clip has been added\n"
112 "in this layer before.\n\n"
113 "Both clips will thus overlap on two layers for 10 seconds.\n\n"
114 "The \"alpha\" property of the second clip will finally be set to a value of 0.9.\n\n"
115 "All this will result in a 10 seconds playback, where media2 is barely visible\n"
116 "through media1, which is nearly opaque. If alpha was set to 0.5, both clips\n"
117 "would be equally visible, and if it was set to 0.0, media1 would be invisible\n"
118 "and media2 completely opaque.\n",
121 "uri", 0, 0, "asset-id",
122 "The URI of the media file."
125 "name", "n", 0, NULL,
126 "The name of the clip, can be used as an ID later."
129 "start", "s", GST_TYPE_CLOCK_TIME, NULL,
130 "The starting position of the clip in the timeline."
133 "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
134 "The duration of the clip."
137 "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
138 "The inpoint of the clip (time in the input file to start playing from)."
141 "track-types", "tt", 0, NULL,
142 "The type of the tracks where the clip should be used (audio or video or audio+video)."
145 "layer", "l", 0, NULL,
146 "The priority of the layer into which the clip should be added."
148 {NULL, 0, 0, NULL, FALSE},
154 .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_effect,
155 .synopsis="<effect bin description>",
156 .description="Adds an effect as specified by 'bin-description', similar to gst-launch-style"
157 " pipeline description, without setting properties (see `set-<property-name>` for information"
158 " about how to set properties).",
159 .examples=" ges-launch-1.0 +clip /path/to/media +effect \"agingtv\"\n\n"
160 "This will apply the agingtv effect to \"media\" and play it back.",
163 "bin-description", "d", 0, "asset-id",
164 "gst-launch style bin description."
167 "element-name", "e", 0, NULL,
168 "The name of the element to apply the effect on."
171 "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
172 "Implies that the effect has 'internal content'"
173 "(see [ges_track_element_set_has_internal_source](ges_track_element_set_has_internal_source))",
176 "name", "n", 0, "child-name",
177 "The name to be given to the effect."
179 {NULL, NULL, 0, NULL, FALSE},
183 .long_name="test-clip",
185 .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_test_clip,
186 .synopsis="<test clip pattern>",
187 .description="Add a test clip in the timeline.",
191 "vpattern", "p", 0, NULL,
192 "The testsource pattern name."
195 "name", "n", 0, NULL,
196 "The name of the clip, can be used as an ID later."
199 "start", "s", GST_TYPE_CLOCK_TIME, NULL,
200 "The starting position of the clip in the timeline."
203 "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
204 "The duration of the clip."
207 "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
208 "The inpoint of the clip (time in the input file to start playing)."
211 "layer", "l", 0, NULL,
212 "The priority of the layer into which the clip should be added."
214 {NULL, 0, 0, NULL, FALSE},
220 .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_title_clip,
221 .synopsis="<title text>",
222 .description="Adds a clip in the timeline.",
226 "text", "t", 0, NULL,
227 "The text to be used as title."
230 "name", "n", 0, NULL,
231 "The name of the clip, can be used as an ID later."
234 "start", "s",GST_TYPE_CLOCK_TIME, NULL,
235 "The starting position of the clip in the timeline."
238 "duration", "d", GST_TYPE_CLOCK_TIME, NULL,
239 "The duration of the clip."
242 "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
243 "The inpoint of the clip (time in the input file to start playing from)."
246 "track-types", "tt", 0, NULL,
247 "The type of the tracks where the clip should be used (audio or video or audio+video)."
250 "layer", "l", 0, NULL,
251 "The priority of the layer into which the clip should be added."
253 {NULL, 0, 0, NULL, FALSE},
259 .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_track,
260 .synopsis="<track type>",
261 .description="Adds a track to the timeline.",
264 {"track-type", 0, 0, NULL, NULL},
266 "restrictions", "r", 0, NULL,
267 "The restriction caps to set on the track."
269 {NULL, 0, 0, NULL, FALSE},
273 .long_name="keyframes",
275 .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_keyframes,
276 .synopsis="<property name>",
277 .description="Adds keyframes for the specified property in the form:\n\n",
278 .examples=" ges-launch-1.0 +test-clip blue d=1.0 +keyframes posx 0=0 1.0=1280 t=direct-absolute +k posy 0=0 1.0=720 t=direct-absolute\n\n"
279 "This add a testclip that will disappear in the bottom right corner",
281 {"property-name", 0, 0, NULL, NULL},
283 "binding-type", "t", 0, NULL,
284 "The type of binding to use, eg. 'direct-absolute', 'direct'"
287 "interpolation-mode", "m", 0, NULL,
288 "The GstInterpolationMode to user."
292 "The list of keyframe_timestamp=value to be set."
294 {NULL, 0, 0, NULL, FALSE},
301 .synopsis="<property name> <value>",
302 .description="Set a property on the last added element."
303 " Any child property that exists on the previously added element"
304 " can be used as <property name>"
305 "By default, set-<property-name> will lookup the property on the last added"
307 .examples=" ges-launch-1.0 +clip /path/to/media set-alpha 0.3\n\n"
308 "This will set the alpha property on \"media\" then play it back, assuming \"media\""
309 "contains a video stream.\n\n"
310 " ges-launch-1.0 +clip /path/to/media +effect \"agingtv\" set-dusts false\n\n"
311 "This will set the \"dusts\" property of the agingtv to false and play the\n"
314 {NULL, 0, 0, NULL, FALSE},
320 /* Should always be in the same order as the options */
330 } GESCommandLineOptionType;
332 static gint /* -1: not present, 0: failure, 1: OK */
333 _convert_to_clocktime (GstStructure * structure, const gchar * name,
334 GstClockTime default_value)
338 GValue d_val = G_VALUE_INIT, converted = G_VALUE_INIT;
339 GstClockTime timestamp;
340 const GValue *gvalue = gst_structure_get_value (structure, name);
342 if (gvalue == NULL) {
343 timestamp = default_value;
350 if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING) {
351 const gchar *val_string = g_value_get_string (gvalue);
352 /* if starts with an 'f', interpret as a frame number, keep as
353 * a string for now */
354 if (val_string && val_string[0] == 'f')
356 /* else, try convert to a GstClockTime, or a double */
357 g_value_init (&converted, GST_TYPE_CLOCK_TIME);
358 if (!gst_value_deserialize (&converted, val_string)) {
359 g_value_unset (&converted);
360 g_value_init (&converted, G_TYPE_DOUBLE);
361 if (!gst_value_deserialize (&converted, val_string)) {
362 GST_ERROR ("Could not get timestamp for %s by deserializing %s",
368 g_value_init (&converted, G_VALUE_TYPE (gvalue));
369 g_value_copy (gvalue, &converted);
372 if (G_VALUE_TYPE (&converted) == GST_TYPE_CLOCK_TIME) {
373 timestamp = g_value_get_uint64 (&converted);
377 g_value_init (&d_val, G_TYPE_DOUBLE);
379 if (!g_value_transform (&converted, &d_val)) {
380 GST_ERROR ("Could not get timestamp for %s", name);
384 val = g_value_get_double ((const GValue *) &d_val);
385 g_value_unset (&d_val);
388 timestamp = GST_CLOCK_TIME_NONE;
390 timestamp = val * GST_SECOND;
393 gst_structure_set (structure, name, G_TYPE_UINT64, timestamp, NULL);
394 g_value_unset (&converted);
399 g_value_unset (&converted);
405 _cleanup_fields (const Property * field_names, GstStructure * structure,
410 for (i = 0; field_names[i].long_name; i++) {
411 gboolean exists = FALSE;
413 /* Move shortly named fields to longname variante */
414 if (field_names[i].short_name &&
415 gst_structure_has_field (structure, field_names[i].short_name)) {
418 if (gst_structure_has_field (structure, field_names[i].long_name)) {
419 gchar *str_info = gst_structure_serialize (structure, 0);
422 g_error_new (GES_ERROR, 0,
423 "Using short (%s) and long name (%s)"
424 " at the same time s in %s, which one should I use?!",
425 field_names[i].short_name, field_names[i].long_name, str_info);
431 gst_structure_get_value (structure, field_names[i].short_name);
433 gst_structure_set_value (structure, field_names[i].long_name, val);
434 gst_structure_remove_field (structure, field_names[i].short_name);
436 } else if (gst_structure_has_field (structure, field_names[i].long_name)) {
441 if (field_names[i].type == GST_TYPE_CLOCK_TIME) {
442 if (_convert_to_clocktime (structure, field_names[i].long_name, 0) == 0) {
443 *error = g_error_new (GES_ERROR, 0, "Could not convert"
444 " %s to GstClockTime", field_names[i].long_name);
451 if (field_names[i].new_name
452 && gst_structure_has_field (structure, field_names[i].long_name)) {
454 gst_structure_get_value (structure, field_names[i].long_name);
456 gst_structure_set_value (structure, field_names[i].new_name, val);
457 gst_structure_remove_field (structure, field_names[i].long_name);
465 _ges_command_line_formatter_add_clip (GESTimeline * timeline,
466 GstStructure * structure, GError ** error)
470 if (!_cleanup_fields (options[CLIP].properties, structure, error))
473 gst_structure_set (structure, "type", G_TYPE_STRING, "GESUriClip", NULL);
475 if (!_ges_add_clip_from_struct (timeline, structure, error))
478 proj = GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline)));
479 asset = _ges_get_asset_from_timeline (timeline, GES_TYPE_URI_CLIP,
480 gst_structure_get_string (structure, "asset-id"), NULL);
481 ges_project_add_asset (proj, asset);
487 _ges_command_line_formatter_add_test_clip (GESTimeline * timeline,
488 GstStructure * structure, GError ** error)
490 if (!_cleanup_fields (options[TEST_CLIP].properties, structure, error))
493 gst_structure_set (structure, "type", G_TYPE_STRING, "GESTestClip", NULL);
495 if (!gst_structure_has_field_typed (structure, "asset-id", G_TYPE_STRING))
496 gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTestClip",
499 return _ges_add_clip_from_struct (timeline, structure, error);
503 _ges_command_line_formatter_add_title_clip (GESTimeline * timeline,
504 GstStructure * structure, GError ** error)
506 if (!_cleanup_fields (options[TITLE].properties, structure, error))
509 gst_structure_set (structure, "type", G_TYPE_STRING, "GESTitleClip", NULL);
510 gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTitleClip",
513 return _ges_add_clip_from_struct (timeline, structure, error);
517 _ges_command_line_formatter_add_keyframes (GESTimeline * timeline,
518 GstStructure * structure, GError ** error)
520 if (!_cleanup_fields (options[KEYFRAMES].properties, structure, error))
523 if (!_ges_set_control_source_from_struct (timeline, structure, error))
526 return _ges_add_remove_keyframe_from_struct (timeline, structure, error);
530 _ges_command_line_formatter_add_track (GESTimeline * timeline,
531 GstStructure * structure, GError ** error)
533 if (!_cleanup_fields (options[TRACK].properties, structure, error))
536 return _ges_add_track_from_struct (timeline, structure, error);
540 _ges_command_line_formatter_add_effect (GESTimeline * timeline,
541 GstStructure * structure, GError ** error)
543 if (!_cleanup_fields (options[EFFECT].properties, structure, error))
546 gst_structure_set (structure, "child-type", G_TYPE_STRING, "GESEffect", NULL);
548 return _ges_container_add_child_from_struct (timeline, structure, error);
552 ges_command_line_formatter_get_help (gint nargs, gchar ** commands)
555 GString *help = g_string_new (NULL);
557 for (i = 0; i < G_N_ELEMENTS (options); i++) {
558 gboolean print = nargs == 0;
559 GESCommandLineOption option = options[i];
564 for (j = 0; j < nargs; j++) {
565 gchar *cname = commands[j][0] == '+' ? &commands[j][1] : commands[j];
567 if (!g_strcmp0 (cname, option.long_name)) {
577 gchar *tmp = g_strdup_printf (" `%s%s` - %s\n",
578 option.properties[0].long_name ? "+" : "",
579 option.long_name, option.synopsis);
581 g_string_append (help, tmp);
582 g_string_append (help, " ");
583 g_string_append (help, "\n\n ");
586 for (j = 0; option.description[j] != '\0'; j++) {
588 if (j && (j % 80) == 0) {
589 while (option.description[j] != '\0' && option.description[j] != ' ')
590 g_string_append_c (help, option.description[j++]);
591 g_string_append (help, "\n ");
595 g_string_append_c (help, option.description[j]);
597 g_string_append_c (help, '\n');
599 if (option.properties[0].long_name) {
602 g_string_append (help, "\n Properties:\n\n");
604 for (j = 1; option.properties[j].long_name; j++) {
605 Property prop = option.properties[j];
606 g_string_append_printf (help, " * `%s`: %s\n", prop.long_name,
610 if (option.examples) {
612 gchar **examples = g_strsplit (option.examples, "\n", -1);
614 g_string_append (help, "\n Examples:\n\n");
615 for (j = 0; examples[j]; j++) {
617 g_string_append_printf (help, " %s", examples[j]);
618 g_string_append_c (help, '\n');
620 g_strfreev (examples);
623 g_string_append_c (help, '\n');
627 return g_string_free (help, FALSE);
632 _set_child_property (GESTimeline * timeline, GstStructure * structure,
635 return _ges_set_child_property_from_struct (timeline, structure, error);
638 #define EXEC(func,structure,error) G_STMT_START { \
639 gboolean res = ((ActionFromStructureFunc)func)(timeline, structure, error); \
641 GST_ERROR ("Could not execute: %" GST_PTR_FORMAT ", error: %s", structure, (*error)->message); \
647 static GESStructureParser *
648 _parse_structures (const gchar * string)
651 GESStructureParser *parser = ges_structure_parser_new ();
653 priv_ges_parse_yylex_init_extra (parser, &scanner);
654 priv_ges_parse_yy_scan_string (string, scanner);
655 priv_ges_parse_yylex (scanner);
656 priv_ges_parse_yylex_destroy (scanner);
658 ges_structure_parser_end_of_file (parser);
662 /* @uri: (transfer full): */
664 get_timeline_desc_from_uri (GstUri * uri)
671 /* Working around parser requiring a space to begin with */
672 path = gst_uri_get_path (uri);
673 res = g_strconcat (" ", path, NULL);
682 _can_load (GESFormatter * dummy_formatter, const gchar * string,
685 gboolean res = FALSE;
688 gchar *timeline_desc = NULL;
689 GESStructureParser *parser;
691 if (string == NULL) {
692 GST_ERROR ("No URI!");
696 uri = gst_uri_from_string (string);
698 GST_INFO_OBJECT (dummy_formatter, "Wrong uri: %s", string);
702 scheme = gst_uri_get_scheme (uri);
703 if (!g_strcmp0 (scheme, "ges:")) {
704 GST_INFO_OBJECT (dummy_formatter, "Wrong scheme: %s", string);
710 timeline_desc = get_timeline_desc_from_uri (uri);
711 parser = _parse_structures (timeline_desc);
712 if (parser->structures)
715 gst_object_unref (parser);
716 g_free (timeline_desc);
722 _set_project_loaded (GESFormatter * self)
724 ges_project_set_loaded (self->project, self, NULL);
725 gst_object_unref (self);
731 _load (GESFormatter * self, GESTimeline * timeline, const gchar * string,
737 gchar *timeline_desc =
738 get_timeline_desc_from_uri (gst_uri_from_string (string));
739 GESStructureParser *parser = _parse_structures (timeline_desc);
741 g_free (timeline_desc);
743 err = ges_structure_parser_get_error (parser);
752 g_object_set (timeline, "auto-transition", TRUE, NULL);
754 /* Here we've finished initializing our timeline, we're
755 * ready to start using it... by solely working with the layer !*/
756 for (tmp = parser->structures; tmp; tmp = tmp->next) {
757 const gchar *name = gst_structure_get_name (tmp->data);
758 if (g_str_has_prefix (name, "set-")) {
759 EXEC (_set_child_property, tmp->data, &err);
763 for (i = 0; i < G_N_ELEMENTS (options); i++) {
764 if (gst_structure_has_name (tmp->data, options[i].long_name)
765 || (strlen (name) == 1 && *name == options[i].short_name)) {
766 EXEC (((ActionFromStructureFunc) options[i].callback), tmp->data, &err);
772 gst_object_unref (parser);
774 ges_idle_add ((GSourceFunc) _set_project_loaded, g_object_ref (self), NULL);
779 gst_object_unref (parser);
789 ges_command_line_formatter_init (GESCommandLineFormatter * formatter)
791 formatter->priv = ges_command_line_formatter_get_instance_private (formatter);
795 ges_command_line_formatter_finalize (GObject * object)
797 G_OBJECT_CLASS (ges_command_line_formatter_parent_class)->finalize (object);
801 ges_command_line_formatter_class_init (GESCommandLineFormatterClass * klass)
803 GObjectClass *object_class = G_OBJECT_CLASS (klass);
804 GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (klass);
806 object_class->finalize = ges_command_line_formatter_finalize;
808 formatter_klass->can_load_uri = _can_load;
809 formatter_klass->load_from_uri = _load;
810 formatter_klass->rank = GST_RANK_MARGINAL;
813 /* Copy of GST_ASCII_IS_STRING */
814 #define ASCII_IS_STRING(c) (g_ascii_isalnum((c)) || ((c) == '_') || \
815 ((c) == '-') || ((c) == '+') || ((c) == '/') || ((c) == ':') || \
819 _sanitize_argument (const gchar * arg, GString * res)
821 gboolean need_wrap = FALSE;
822 const gchar *tmp_string;
824 for (tmp_string = arg; *tmp_string != '\0'; tmp_string++) {
825 if (!ASCII_IS_STRING (*tmp_string) || (*tmp_string == '\n')) {
832 g_string_append (res, arg);
836 g_string_append_c (res, '"');
837 while (*arg != '\0') {
838 if (*arg == '"' || *arg == '\\') {
839 g_string_append_c (res, '\\');
840 } else if (*arg == '\n') {
841 g_string_append (res, "\\n");
846 g_string_append_c (res, *(arg++));
848 g_string_append_c (res, '"');
852 _serialize_control_binding (GESTrackElement * e, const gchar * prop,
855 GstInterpolationMode mode;
856 GstControlSource *source = NULL;
857 GList *timed_values, *tmp;
858 gboolean absolute = FALSE;
859 GstControlBinding *binding = ges_track_element_get_control_binding (e, prop);
864 if (!GST_IS_DIRECT_CONTROL_BINDING (binding)) {
865 g_warning ("Unsupported control binding type: %s",
866 G_OBJECT_TYPE_NAME (binding));
870 g_object_get (binding, "control-source", &source,
871 "absolute", &absolute, NULL);
873 if (!GST_IS_INTERPOLATION_CONTROL_SOURCE (source)) {
874 g_warning ("Unsupported control source type: %s",
875 G_OBJECT_TYPE_NAME (source));
879 g_object_get (source, "mode", &mode, NULL);
880 g_string_append_printf (res, " +keyframes %s t=%s",
881 prop, absolute ? "direct-absolute" : "direct");
883 if (mode != GST_INTERPOLATION_MODE_LINEAR)
884 g_string_append_printf (res, " mode=%s",
885 g_enum_get_value (g_type_class_peek (GST_TYPE_INTERPOLATION_MODE),
889 gst_timed_value_control_source_get_all
890 (GST_TIMED_VALUE_CONTROL_SOURCE (source));
891 for (tmp = timed_values; tmp; tmp = tmp->next) {
892 gchar strbuf[G_ASCII_DTOSTR_BUF_SIZE];
893 GstTimedValue *value;
895 value = (GstTimedValue *) tmp->data;
896 g_string_append_printf (res, " %f=%s",
897 (gdouble) value->timestamp / (gdouble) GST_SECOND,
898 g_ascii_dtostr (strbuf, G_ASCII_DTOSTR_BUF_SIZE, value->value));
900 g_list_free (timed_values);
903 g_clear_object (&source);
908 _serialize_object_properties (GObject * object, GESCommandLineOption * option,
909 gboolean children_props, GString * res)
912 GParamSpec *spec, **pspecs;
913 GObjectClass *class = G_OBJECT_GET_CLASS (object);
914 const gchar *ignored_props[] = {
915 "max-duration", "supported-formats", "priority", "video-direction",
920 pspecs = g_object_class_list_properties (class, &n_props);
923 ges_timeline_element_list_children_properties (GES_TIMELINE_ELEMENT
925 g_assert (GES_IS_TRACK_ELEMENT (object));
928 for (j = 0; j < n_props; j++) {
930 gchar *value_str = NULL;
935 if (!ges_util_can_serialize_spec (spec))
938 g_value_init (&val, spec->value_type);
940 g_object_get_property (object, spec->name, &val);
942 ges_timeline_element_get_child_property_by_pspec (GES_TIMELINE_ELEMENT
943 (object), spec, &val);
945 if (gst_value_compare (g_param_spec_get_default_value (spec),
946 &val) == GST_VALUE_EQUAL) {
947 GST_INFO ("Ignoring %s as it is using the default value", spec->name);
952 if (!children_props && !g_strcmp0 (name, "in-point"))
955 for (i = 0; option->properties[i].long_name; i++) {
956 if (!g_strcmp0 (spec->name, option->properties[i].long_name)) {
957 if (children_props) {
960 name = option->properties[i].short_name;
961 if (option->properties[i].type == GST_TYPE_CLOCK_TIME)
963 g_strdup_printf ("%f",
964 (gdouble) (g_value_get_uint64 (&val) / GST_SECOND));
967 } else if (!g_strcmp0 (spec->name, option->properties[0].long_name)) {
973 for (i = 0; i < G_N_ELEMENTS (ignored_props); i++) {
974 if (!g_strcmp0 (spec->name, ignored_props[i])) {
985 if (GES_IS_TRACK_ELEMENT (object) &&
986 _serialize_control_binding (GES_TRACK_ELEMENT (object), name, res)) {
992 value_str = gst_value_serialize (&val);
994 g_string_append_printf (res, " %s%s%s",
995 children_props ? "set-" : "", name, children_props ? " " : "=");
996 _sanitize_argument (value_str, res);
1000 g_value_unset (&val);
1006 _serialize_clip_track_types (GESClip * clip, GESTrackType tt, GString * res)
1008 GValue v = G_VALUE_INIT;
1011 if (ges_clip_get_supported_formats (clip) == tt)
1014 g_value_init (&v, GES_TYPE_TRACK_TYPE);
1015 g_value_set_flags (&v, ges_clip_get_supported_formats (clip));
1017 ttype_str = gst_value_serialize (&v);
1019 g_string_append_printf (res, " tt=%s", ttype_str);
1025 _serialize_clip_effects (GESClip * clip, GString * res)
1027 GList *tmpeffect, *effects;
1029 effects = ges_clip_get_top_effects (clip);
1030 for (tmpeffect = effects; tmpeffect; tmpeffect = tmpeffect->next) {
1033 g_object_get (tmpeffect->data, "bin-description", &bin_desc, NULL);
1035 g_string_append_printf (res, " +effect %s", bin_desc);
1038 g_list_free_full (effects, gst_object_unref);
1043 * ges_command_line_formatter_get_timeline_uri:
1044 * @timeline: A GESTimeline to serialize
1049 ges_command_line_formatter_get_timeline_uri (GESTimeline * timeline)
1054 GString *res = g_string_new ("ges:");
1055 GESTrackType tt = 0;
1060 for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
1061 GstCaps *caps, *default_caps;
1062 GESTrack *tmptrack, *track = tmp->data;
1064 if (GES_IS_VIDEO_TRACK (track))
1065 tmptrack = GES_TRACK (ges_video_track_new ());
1066 else if (GES_IS_AUDIO_TRACK (track))
1067 tmptrack = GES_TRACK (ges_audio_track_new ());
1069 g_warning ("Unhandled track type: %s", G_OBJECT_TYPE_NAME (track));
1075 g_string_append_printf (res, " +track %s",
1076 (track->type == GES_TRACK_TYPE_VIDEO) ? "video" : "audio");
1078 default_caps = ges_track_get_restriction_caps (tmptrack);
1079 caps = ges_track_get_restriction_caps (track);
1080 if (!gst_caps_is_equal (caps, default_caps)) {
1081 tmpstr = gst_caps_serialize (caps, 0);
1083 g_string_append (res, " restrictions=");
1084 _sanitize_argument (tmpstr, res);
1087 gst_caps_unref (default_caps);
1088 gst_caps_unref (caps);
1089 gst_object_unref (tmptrack);
1092 for (tmp = timeline->layers, i = 0; tmp; tmp = tmp->next, i++) {
1093 GList *tmpclip, *clips = ges_layer_get_clips (tmp->data);
1094 GList *tmptrackelem;
1096 for (tmpclip = clips; tmpclip; tmpclip = tmpclip->next) {
1097 GESClip *clip = tmpclip->data;
1098 GESCommandLineOption *option = NULL;
1100 if (GES_IS_TEST_CLIP (clip)) {
1101 GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (clip));
1102 const gchar *id = ges_asset_get_id (asset);
1104 g_string_append (res, " +test-clip ");
1106 _sanitize_argument (g_enum_get_value (g_type_class_peek
1107 (GES_VIDEO_TEST_PATTERN_TYPE),
1108 ges_test_clip_get_vpattern (GES_TEST_CLIP (clip)))->value_nick,
1111 if (g_strcmp0 (id, "GESTestClip")) {
1112 g_string_append (res, " asset-id=");
1113 _sanitize_argument (id, res);
1116 option = &options[TEST_CLIP];
1117 } else if (GES_IS_TITLE_CLIP (clip)) {
1118 g_string_append (res, " +title ");
1119 _sanitize_argument (ges_title_clip_get_text (GES_TITLE_CLIP (clip)),
1121 option = &options[TITLE];
1122 } else if (GES_IS_URI_CLIP (clip)) {
1123 g_string_append (res, " +clip ");
1125 _sanitize_argument (ges_uri_clip_get_uri (GES_URI_CLIP (clip)), res);
1126 option = &options[CLIP];
1128 g_warning ("Unhandled clip type: %s", G_OBJECT_TYPE_NAME (clip));
1132 _serialize_clip_track_types (clip, tt, res);
1135 g_string_append_printf (res, " layer=%d", i);
1137 _serialize_object_properties (G_OBJECT (clip), option, FALSE, res);
1138 _serialize_clip_effects (clip, res);
1140 for (tmptrackelem = GES_CONTAINER_CHILDREN (clip); tmptrackelem;
1141 tmptrackelem = tmptrackelem->next)
1142 _serialize_object_properties (G_OBJECT (tmptrackelem->data), option,
1145 g_list_free_full (clips, gst_object_unref);
1149 return g_string_free (res, FALSE);
1151 GstUri *uri = gst_uri_from_string (res->str);
1152 gchar *uri_str = gst_uri_to_string (uri);
1154 g_string_free (res, TRUE);