From 28a1e97aa267a77dc057d59638e7f1b9e05cd787 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Mon, 23 Feb 2015 14:48:18 +0100 Subject: [PATCH] ges: Factor out a GESCommandLineFormatter class This formatter will allow any user to deserialize a timeline using the new ges-launch command line interface --- ges/Makefile.am | 11 +- ges/ges-command-line-formatter.c | 346 ++++++++++++++++++++++++ ges/ges-command-line-formatter.h | 58 ++++ ges/ges-formatter.c | 4 +- ges/ges-formatter.h | 3 + ges/ges-internal.h | 9 +- ges/ges-project.c | 6 +- ges/ges-project.h | 2 +- {tools => ges}/ges-structure-parser.c | 0 {tools => ges}/ges-structure-parser.h | 0 ges/ges-structured-interface.h | 13 +- ges/ges.c | 1 + ges/ges.h | 1 + {tools => ges}/parse.l | 0 tools/Makefile.am | 10 +- tools/ges-launch.c | 374 +++++++------------------- 16 files changed, 537 insertions(+), 301 deletions(-) create mode 100644 ges/ges-command-line-formatter.c create mode 100644 ges/ges-command-line-formatter.h rename {tools => ges}/ges-structure-parser.c (100%) rename {tools => ges}/ges-structure-parser.h (100%) rename {tools => ges}/parse.l (100%) diff --git a/ges/Makefile.am b/ges/Makefile.am index bbb3be37aa..a42c603137 100644 --- a/ges/Makefile.am +++ b/ges/Makefile.am @@ -6,7 +6,7 @@ lib_LTLIBRARIES = libges-@GST_API_VERSION@.la EXTRA_libges_@GST_API_VERSION@_la_SOURCES = gesmarshal.list -CLEANFILES = $(BUILT_SOURCES) $(built_header_make) $(built_source_make) *.gcno *.gcda *.gcov *.gcov.out +CLEANFILES = $(BUILT_SOURCES) $(built_header_make) $(built_source_make) *.gcno *.gcda *.gcov *.gcov.out lex.priv_ges_parse_yy.c libges_@GST_API_VERSION@_la_SOURCES = \ $(built_source_make) \ @@ -66,6 +66,7 @@ libges_@GST_API_VERSION@_la_SOURCES = \ ges-project.c \ ges-base-xml-formatter.c \ ges-xml-formatter.c \ + ges-command-line-formatter.c \ ges-auto-transition.c \ ges-timeline-element.c \ ges-container.c \ @@ -76,6 +77,7 @@ libges_@GST_API_VERSION@_la_SOURCES = \ ges-group.c \ ges-validate.c \ ges-structured-interface.c \ + ges-structure-parser.c \ gstframepositionner.c libges_@GST_API_VERSION@includedir = $(includedir)/gstreamer-@GST_API_VERSION@/ges/ @@ -133,6 +135,7 @@ libges_@GST_API_VERSION@include_HEADERS = \ ges-project.h \ ges-base-xml-formatter.h \ ges-xml-formatter.h \ + ges-command-line-formatter.h \ ges-timeline-element.h \ ges-container.h \ ges-effect-asset.h \ @@ -155,6 +158,7 @@ noinst_HEADERS = \ ges-internal.h \ ges-auto-transition.h \ ges-structured-interface.h \ + ges-structure-parser.h \ gstframepositionner.h libges_@GST_API_VERSION@_la_CFLAGS = -I$(top_srcdir) $(GST_PBUTILS_CFLAGS) \ @@ -166,6 +170,8 @@ libges_@GST_API_VERSION@_la_LIBADD = $(GST_PBUTILS_LIBS) \ libges_@GST_API_VERSION@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) \ $(GST_LT_LDFLAGS) $(GIO_CFLAGS) $(GST_VALIDATE_CFLAGS) +nodist_libges_@GST_API_VERSION@_la_SOURCES = lex.priv_ges_parse_yy.c parse_lex.h + DISTCLEANFILE = $(CLEANFILES) #files built on make all/check/instal @@ -267,3 +273,6 @@ Android.mk: Makefile.am $(BUILT_SOURCES) $(nodist_libges_@GST_API_VERSION@include_HEADERS) \ -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ > $@ + +lex.priv_ges_parse_yy.c parse_lex.h: parse.l + $(AM_V_GEN)$(FLEX_PATH) --header-file=parse_lex.h -Ppriv_ges_parse_yy $^ diff --git a/ges/ges-command-line-formatter.c b/ges/ges-command-line-formatter.c new file mode 100644 index 0000000000..138d376c38 --- /dev/null +++ b/ges/ges-command-line-formatter.c @@ -0,0 +1,346 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2015> Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "ges-command-line-formatter.h" + +#include "ges/ges-structured-interface.h" +#include "ges-structure-parser.h" +#include "ges-internal.h" +#include "parse_lex.h" + +struct _GESCommandLineFormatterPrivate +{ + gpointer dummy; +}; + + +G_DEFINE_TYPE (GESCommandLineFormatter, ges_command_line_formatter, + GES_TYPE_FORMATTER); + +typedef struct +{ + const gchar *long_name; + const gchar *short_name; + GType type; + const gchar *new_name; +} Properties; + +static gint /* -1: not present, 0: failure, 1: OK */ +_convert_to_clocktime (GstStructure * structure, const gchar * name, + GstClockTime default_value) +{ + gint res = 1; + gdouble val; + GValue d_val = { 0 }; + GstClockTime timestamp; + const GValue *gvalue = gst_structure_get_value (structure, name); + + if (gvalue == NULL) { + timestamp = default_value; + + res = -1; + + goto done; + } + + if (G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME) + return 1; + + g_value_init (&d_val, G_TYPE_DOUBLE); + if (!g_value_transform (gvalue, &d_val)) { + GST_ERROR ("Could not get timestamp for %s", name); + + return 0; + } + val = g_value_get_double ((const GValue *) &d_val); + + if (val == -1.0) + timestamp = GST_CLOCK_TIME_NONE; + else + timestamp = val * GST_SECOND; + +done: + gst_structure_set (structure, name, G_TYPE_UINT64, timestamp, NULL); + + return res; +} + +static gboolean +_cleanup_fields (const Properties * field_names, GstStructure * structure, + GError ** error) +{ + guint i; + + for (i = 0; field_names[i].long_name; i++) { + gboolean exists = FALSE; + + /* Move shortly named fields to longname variante */ + if (gst_structure_has_field (structure, field_names[i].short_name)) { + exists = TRUE; + + if (gst_structure_has_field (structure, field_names[i].long_name)) { + *error = g_error_new (GES_ERROR, 0, "Using short and long name" + " at the same time for property: %s, which one should I use?!", + field_names[i].long_name); + + return FALSE; + } else { + const GValue *val = + gst_structure_get_value (structure, field_names[i].short_name); + + gst_structure_set_value (structure, field_names[i].long_name, val); + gst_structure_remove_field (structure, field_names[i].short_name); + } + } else if (gst_structure_has_field (structure, field_names[i].long_name)) { + exists = TRUE; + } + + if (exists) { + if (field_names[i].type == GST_TYPE_CLOCK_TIME) { + if (_convert_to_clocktime (structure, field_names[i].long_name, 0) == 0) { + *error = g_error_new (GES_ERROR, 0, "Could not convert" + " %s to GstClockTime", field_names[i].long_name); + + return FALSE; + } + } + } + + if (field_names[i].new_name + && gst_structure_has_field (structure, field_names[i].long_name)) { + const GValue *val = + gst_structure_get_value (structure, field_names[i].long_name); + + gst_structure_set_value (structure, field_names[i].new_name, val); + gst_structure_remove_field (structure, field_names[i].long_name); + } + } + + return TRUE; +} + +static gboolean +_ges_command_line_formatter_add_clip (GESTimeline * timeline, + GstStructure * structure, GError ** error) +{ + const Properties field_names[] = { + {"uri", "n", 0, "asset-id"}, + {"name", "n", 0, NULL}, + {"start", "s", GST_TYPE_CLOCK_TIME, NULL}, + {"duration", "d", GST_TYPE_CLOCK_TIME, NULL}, + {"inpoint", "i", GST_TYPE_CLOCK_TIME, NULL}, + {"track-types", "tt", 0, NULL}, + {"layer", "l", 0, NULL}, + {NULL, 0, 0, NULL}, + }; + + if (!_cleanup_fields (field_names, structure, error)) + return FALSE; + + gst_structure_set (structure, "type", G_TYPE_STRING, "GESUriClip", NULL); + + return _ges_add_clip_from_struct (timeline, structure, error); +} + +static gboolean +_ges_command_line_formatter_add_effect (GESTimeline * timeline, + GstStructure * structure, GError ** error) +{ + const Properties field_names[] = { + {"element-name", "e", 0, NULL}, + {"bin-description", "d", 0, "asset-id"}, + {"name", "n", 0, "child-name"}, + {NULL, NULL, 0, NULL}, + }; + + if (!_cleanup_fields (field_names, structure, error)) + return FALSE; + + gst_structure_set (structure, "child-type", G_TYPE_STRING, "GESEffect", NULL); + + return _ges_container_add_child_from_struct (timeline, structure, error); +} + +static GOptionEntry timeline_parsing_options[] = { + {"clip", 'c', 0.0, G_OPTION_ARG_CALLBACK, + &_ges_command_line_formatter_add_clip, + "", + "Adds a clip in the timeline\n" + " * start - s : The start position of the element inside the layer.\n" + " * duration - d: The duration of the clip.\n" + " * inpoint - i : The inpoint of the clip.\n" + " * track-types - tt: The type of the tracks where the clip should be used:\n" + " Examples:\n" + " * audio / a\n" + " * video / v\n" + " * audio+video / a+v\n" + " Will default to all the media types in the clip that match the global track-types\n"}, + {"effect", 'e', 0.0, G_OPTION_ARG_CALLBACK, + &_ges_command_line_formatter_add_effect, "", + "Adds an effect as specified by 'bin-description'\n" + " * bin-description - d: The description of the effect bin with a gst-launch-style pipeline description.\n" + " * element-name - e : The name of the element to apply the effect on.\n"}, +}; + +GOptionGroup * +_ges_command_line_formatter_get_option_group (void) +{ + GOptionGroup *group; + + group = g_option_group_new ("GESCommandLineFormatter", + "GStreamer Editing Services command line options to describe a timeline", + "Show GStreamer Options", NULL, NULL); + g_option_group_add_entries (group, timeline_parsing_options); + + return group; +} + + +static gboolean +_set_child_property (GESTimeline * timeline, GstStructure * structure, + GError ** error) +{ + return _ges_set_child_property_from_struct (timeline, structure, error); +} + +#define EXEC(func,structure,error) G_STMT_START { \ + gboolean res = ((ActionFromStructureFunc)func)(timeline, structure, error); \ + if (!res) {\ + GST_ERROR ("Could not execute: %" GST_PTR_FORMAT ", error: %s", structure, (*error)->message); \ + goto fail; \ + } \ +} G_STMT_END + + +static GESStructureParser * +_parse_structures (const gchar * string) +{ + yyscan_t scanner; + GESStructureParser *parser = ges_structure_parser_new (); + + priv_ges_parse_yylex_init_extra (parser, &scanner); + priv_ges_parse_yy_scan_string (string, scanner); + priv_ges_parse_yylex (scanner); + priv_ges_parse_yylex_destroy (scanner); + + ges_structure_parser_end_of_file (parser); + return parser; +} + +static gboolean +_can_load (GESFormatter * dummy_formatter, const gchar * string, + GError ** error) +{ + gboolean res = FALSE; + GESStructureParser *parser; + + if (string == NULL) + return FALSE; + + parser = _parse_structures (string); + + if (parser->structures) + res = TRUE; + + gst_object_unref (parser); + + return res; +} + +static gboolean +_load (GESFormatter * self, GESTimeline * timeline, const gchar * string, + GError ** error) +{ + guint i; + GList *tmp; + GESStructureParser *parser = _parse_structures (string); + + g_object_set (timeline, "auto-transition", TRUE, NULL); + if (!(ges_timeline_add_track (timeline, GES_TRACK (ges_video_track_new ())))) + goto fail; + + if (!(ges_timeline_add_track (timeline, GES_TRACK (ges_audio_track_new ())))) + goto fail; + + /* Here we've finished initializing our timeline, we're + * ready to start using it... by solely working with the layer !*/ + for (tmp = parser->structures; tmp; tmp = tmp->next) { + const gchar *name = gst_structure_get_name (tmp->data); + GError *error = NULL; + + if (g_str_has_prefix (name, "set-")) { + EXEC (_set_child_property, tmp->data, &error); + continue; + } + + for (i = 0; i < G_N_ELEMENTS (timeline_parsing_options); i++) { + if (gst_structure_has_name (tmp->data, + timeline_parsing_options[i].long_name) + || (strlen (name) == 1 && + *name == timeline_parsing_options[i].short_name)) { + EXEC (((ActionFromStructureFunc) timeline_parsing_options[i].arg_data), + tmp->data, &error); + } + } + } + + gst_object_unref (parser); + + return TRUE; + +fail: + gst_object_unref (parser); + + return FALSE; +} + +static void +ges_command_line_formatter_init (GESCommandLineFormatter * + ges_command_line_formatter) +{ + ges_command_line_formatter->priv = + G_TYPE_INSTANCE_GET_PRIVATE (ges_command_line_formatter, + GES_TYPE_COMMAND_LINE_FORMATTER, GESCommandLineFormatterPrivate); + + /* TODO: Add initialization code here */ +} + +static void +ges_command_line_formatter_finalize (GObject * object) +{ + /* TODO: Add deinitalization code here */ + + G_OBJECT_CLASS (ges_command_line_formatter_parent_class)->finalize (object); +} + +static void +ges_command_line_formatter_class_init (GESCommandLineFormatterClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GESCommandLineFormatterPrivate)); + + object_class->finalize = ges_command_line_formatter_finalize; + + formatter_klass->can_load_uri = _can_load; + formatter_klass->load_from_uri = _load; + formatter_klass->rank = GST_RANK_MARGINAL; +} diff --git a/ges/ges-command-line-formatter.h b/ges/ges-command-line-formatter.h new file mode 100644 index 0000000000..1b26e5afe3 --- /dev/null +++ b/ges/ges-command-line-formatter.h @@ -0,0 +1,58 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2015> Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _GES_COMMAND_LINE_FORMATTER_H_ +#define _GES_COMMAND_LINE_FORMATTER_H_ + +#include +#include "ges-formatter.h" + +G_BEGIN_DECLS + +#define GES_TYPE_COMMAND_LINE_FORMATTER (ges_command_line_formatter_get_type ()) +#define GES_COMMAND_LINE_FORMATTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_COMMAND_LINE_FORMATTER, GESCommandLineFormatter)) +#define GES_COMMAND_LINE_FORMATTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_COMMAND_LINE_FORMATTER, GESCommandLineFormatterClass)) +#define GES_IS_COMMAND_LINE_FORMATTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_COMMAND_LINE_FORMATTER)) +#define GES_IS_COMMAND_LINE_FORMATTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_COMMAND_LINE_FORMATTER)) +#define GES_COMMAND_LINE_FORMATTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_COMMAND_LINE_FORMATTER, GESCommandLineFormatterClass)) + +typedef struct _GESCommandLineFormatterClass GESCommandLineFormatterClass; +typedef struct _GESCommandLineFormatter GESCommandLineFormatter; +typedef struct _GESCommandLineFormatterPrivate GESCommandLineFormatterPrivate; + + +struct _GESCommandLineFormatterClass +{ + GESFormatterClass parent_class; +}; + +struct _GESCommandLineFormatter +{ + GESFormatter parent_instance; + + GESCommandLineFormatterPrivate *priv; +}; + +GType ges_command_line_formatter_get_type (void); + +G_END_DECLS + +#endif /* _GES_COMMAND_LINE_FORMATTER_H_ */ + diff --git a/ges/ges-formatter.c b/ges/ges-formatter.c index f946622e41..a053901732 100644 --- a/ges/ges-formatter.c +++ b/ges/ges-formatter.c @@ -513,7 +513,7 @@ _sort_formatters (GESAsset * asset, GESAsset * asset1) } GESAsset * -_find_formatter_asset_for_uri (const gchar * uri) +_find_formatter_asset_for_id (const gchar * id) { GESFormatterClass *class = NULL; GList *formatter_assets, *tmp; @@ -528,7 +528,7 @@ _find_formatter_asset_for_uri (const gchar * uri) class = g_type_class_ref (ges_asset_get_extractable_type (asset)); dummy_instance = g_object_new (ges_asset_get_extractable_type (asset), NULL); - if (class->can_load_uri (dummy_instance, uri, NULL)) { + if (class->can_load_uri (dummy_instance, id, NULL)) { g_type_class_unref (class); asset = gst_object_ref (asset); gst_object_unref (dummy_instance); diff --git a/ges/ges-formatter.h b/ges/ges-formatter.h index ef2a355600..1834884918 100644 --- a/ges/ges-formatter.h +++ b/ges/ges-formatter.h @@ -116,6 +116,9 @@ typedef gboolean (*GESFormatterSaveToURIMethod) (GESFormatter *formatter, struct _GESFormatterClass { GInitiallyUnownedClass parent_class; + /* TODO 2.0: Rename the loading method to can_load and load. + * Technically we just pass data to load, it should not necessarily + * be a URI */ GESFormatterCanLoadURIMethod can_load_uri; GESFormatterLoadFromURIMethod load_from_uri; GESFormatterSaveToURIMethod save_to_uri; diff --git a/ges/ges-internal.h b/ges/ges-internal.h index d5efad8b33..5c2b6bb4c1 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -163,7 +163,7 @@ ges_formatter_set_project (GESFormatter *formatter, G_GNUC_INTERNAL GESProject * ges_formatter_get_project (GESFormatter *formatter); G_GNUC_INTERNAL GESAsset * -_find_formatter_asset_for_uri (const gchar *uri); +_find_formatter_asset_for_id (const gchar *id); @@ -289,6 +289,13 @@ ges_base_xml_formatter_set_timeline_properties(GESBaseXmlFormatter * self, const gchar *properties, const gchar *metadatas); + +/**************************************************** + * GESCommandLineFormatter * + ****************************************************/ +GOptionGroup * +_ges_command_line_formatter_get_option_group (void); + /**************************************************** * GESContainer * ****************************************************/ diff --git a/ges/ges-project.c b/ges/ges-project.c index c76bf3a385..b7bc3f905e 100644 --- a/ges/ges-project.c +++ b/ges/ges-project.c @@ -151,8 +151,8 @@ ges_project_set_uri (GESProject * project, const gchar * uri) return; } - if (uri == NULL || !gst_uri_is_valid (uri)) { - GST_LOG_OBJECT (project, "Invalid URI: %s", uri); + if (uri == NULL) { + GST_LOG_OBJECT (project, "Uri should not be NULL"); return; } @@ -189,7 +189,7 @@ _load_project (GESProject * project, GESTimeline * timeline, GError ** error) } if (priv->formatter_asset == NULL) - priv->formatter_asset = _find_formatter_asset_for_uri (priv->uri); + priv->formatter_asset = _find_formatter_asset_for_id (priv->uri); if (priv->formatter_asset == NULL) goto failed; diff --git a/ges/ges-project.h b/ges/ges-project.h index c2bd156644..3f8a24b017 100644 --- a/ges/ges-project.h +++ b/ges/ges-project.h @@ -87,7 +87,7 @@ gboolean ges_project_save (GESProject * project, gboolean ges_project_load (GESProject * project, GESTimeline * timeline, GError **error); -GESProject * ges_project_new (const gchar *uri); +GESProject * ges_project_new (const gchar *id); gchar * ges_project_get_uri (GESProject *project); GESAsset * ges_project_get_asset (GESProject * project, const gchar *id, diff --git a/tools/ges-structure-parser.c b/ges/ges-structure-parser.c similarity index 100% rename from tools/ges-structure-parser.c rename to ges/ges-structure-parser.c diff --git a/tools/ges-structure-parser.h b/ges/ges-structure-parser.h similarity index 100% rename from tools/ges-structure-parser.h rename to ges/ges-structure-parser.h diff --git a/ges/ges-structured-interface.h b/ges/ges-structured-interface.h index a210c8bc58..9c834610e7 100644 --- a/ges/ges-structured-interface.h +++ b/ges/ges-structured-interface.h @@ -27,7 +27,8 @@ typedef gboolean (*ActionFromStructureFunc) (GESTimeline * timeline, GstStructure * structure, GError ** error); -gboolean _ges_add_remove_keyframe_from_struct (GESTimeline * timeline, +G_GNUC_INTERNAL gboolean +_ges_add_remove_keyframe_from_struct (GESTimeline * timeline, GstStructure * structure, GError ** error); G_GNUC_INTERNAL gboolean @@ -35,20 +36,22 @@ _ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure, GError ** error); -gboolean +G_GNUC_INTERNAL gboolean _ges_container_add_child_from_struct (GESTimeline * timeline, GstStructure * structure, GError ** error); -gboolean +G_GNUC_INTERNAL gboolean _ges_set_child_property_from_struct (GESTimeline * timeline, GstStructure * structure, GError ** error); -GESAsset * _ges_get_asset_from_timeline (GESTimeline * timeline, +G_GNUC_INTERNAL GESAsset * +_ges_get_asset_from_timeline (GESTimeline * timeline, GType type, const gchar * id); -GESLayer * _ges_get_layer_by_priority (GESTimeline * timeline, +G_GNUC_INTERNAL GESLayer * +_ges_get_layer_by_priority (GESTimeline * timeline, gint priority); #endif /* __GES_STRUCTURED_INTERFACE__*/ diff --git a/ges/ges.c b/ges/ges.c index 9c00b151be..f16972955b 100644 --- a/ges/ges.c +++ b/ges/ges.c @@ -93,6 +93,7 @@ ges_init (void) /* register formatter types with the system */ GES_TYPE_PITIVI_FORMATTER; + GES_TYPE_COMMAND_LINE_FORMATTER; GES_TYPE_XML_FORMATTER; /* Register track elements */ diff --git a/ges/ges.h b/ges/ges.h index e346f144df..0b3141b746 100644 --- a/ges/ges.h +++ b/ges/ges.h @@ -74,6 +74,7 @@ #include #include #include +#include #include #include #include diff --git a/tools/parse.l b/ges/parse.l similarity index 100% rename from tools/parse.l rename to ges/parse.l diff --git a/tools/Makefile.am b/tools/Makefile.am index 6349bfadc8..9b4b83f2f9 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -3,13 +3,10 @@ bin_PROGRAMS = ges-launch-@GST_API_VERSION@ AM_CFLAGS = -I$(top_srcdir) $(GST_PBUTILS_CFLAGS) $(GST_CFLAGS) $(GIO_CFLAGS) $(GST_VALIDATE_CFLAGS) LDADD = $(top_builddir)/ges/libges-@GST_API_VERSION@.la $(GST_PBUTILS_LIBS) $(GST_LIBS) $(GIO_LIBS) $(GST_VALIDATE_LIBS) -noinst_HEADERS = ges-validate.h ges-structure-parser.h +noinst_HEADERS = ges-validate.h -ges_launch_@GST_API_VERSION@_SOURCES = ges-validate.c ges-launch.c ges-structure-parser.c +ges_launch_@GST_API_VERSION@_SOURCES = ges-validate.c ges-launch.c -nodist_ges_launch_@GST_API_VERSION@_SOURCES = lex.priv_ges_parse_yy.c parse_lex.h - -CLEANFILES = lex.priv_ges_parse_yy.c Android.mk: Makefile.am $(BUILT_SOURCES) androgenizer \ @@ -21,6 +18,3 @@ Android.mk: Makefile.am $(BUILT_SOURCES) -:LDFLAGS -lges-@GST_API_VERSION@ $(GST_PBUTILS_LIBS) $(GST_LIBS) \ -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ > $@ - -lex.priv_ges_parse_yy.c parse_lex.h: parse.l - $(AM_V_GEN)$(FLEX_PATH) --header-file=parse_lex.h -Ppriv_ges_parse_yy $^ diff --git a/tools/ges-launch.c b/tools/ges-launch.c index a32814c01c..91b5c2bb9e 100644 --- a/tools/ges-launch.c +++ b/tools/ges-launch.c @@ -27,7 +27,6 @@ #include #include #include -#include "../ges/ges-structured-interface.h" #include #include /* for LC_ALL */ @@ -36,9 +35,6 @@ #include #endif -#include "ges-structure-parser.h" -#include "parse_lex.h" - /* GLOBAL VARIABLE */ static guint repeat = 0; static gboolean mute = FALSE; @@ -136,6 +132,56 @@ error_loading_asset_cb (GESProject * project, GError * error, g_main_loop_quit (mainloop); } +static gboolean +_timeline_set_user_options (GESTimeline * timeline, const gchar * load_path) +{ + GList *tmp; + GESTrack *tracka, *trackv; + gboolean has_audio = FALSE, has_video = FALSE; + +retry: + for (tmp = timeline->tracks; tmp; tmp = tmp->next) { + + if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_VIDEO) + has_video = TRUE; + else if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_AUDIO) + has_audio = TRUE; + + if (disable_mixing) { + GST_ERROR_OBJECT (tmp->data, "DISABLE MIXING"); + ges_track_set_mixing (tmp->data, FALSE); + } + + if (!(GES_TRACK (tmp->data)->type & track_types)) { + ges_timeline_remove_track (timeline, tmp->data); + goto retry; + } + } + + if (scenario && !load_path) { + if (!has_video && track_types & GES_TRACK_TYPE_VIDEO) { + trackv = GES_TRACK (ges_video_track_new ()); + + if (disable_mixing) + ges_track_set_mixing (trackv, FALSE); + + if (!(ges_timeline_add_track (timeline, trackv))) + return FALSE; + } + + if (!has_audio && track_types & GES_TRACK_TYPE_AUDIO) { + tracka = GES_TRACK (ges_audio_track_new ()); + if (disable_mixing) + ges_track_set_mixing (tracka, FALSE); + + if (!(ges_timeline_add_track (timeline, tracka))) + return FALSE; + } + } + + return TRUE; +} + static void project_loaded_cb (GESProject * project, GESTimeline * timeline) { @@ -165,7 +211,10 @@ project_loaded_cb (GESProject * project, GESTimeline * timeline) } } - if (ges_validate_activate (GST_PIPELINE (pipeline), scenario, + _timeline_set_user_options (timeline, ges_project_get_uri (project)); + + if (ges_project_get_uri (project) + && ges_validate_activate (GST_PIPELINE (pipeline), scenario, &needs_set_state) == FALSE) { g_error ("Could not activate scenario %s", scenario); seenerrors = TRUE; @@ -178,264 +227,6 @@ project_loaded_cb (GESProject * project, GESTimeline * timeline) } } -static gint /* -1: not present, 0: failure, 1: OK */ -_convert_to_clocktime (GstStructure * structure, const gchar * name, - GstClockTime default_value) -{ - gint res = 1; - gdouble val; - GValue d_val = { 0 }; - GstClockTime timestamp; - const GValue *gvalue = gst_structure_get_value (structure, name); - - if (gvalue == NULL) { - timestamp = default_value; - - res = -1; - - goto done; - } - - if (G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME) - return 1; - - g_value_init (&d_val, G_TYPE_DOUBLE); - if (!g_value_transform (gvalue, &d_val)) { - GST_ERROR ("Could not get timestamp for %s", name); - - return 0; - } - val = g_value_get_double ((const GValue *) &d_val); - - if (val == -1.0) - timestamp = GST_CLOCK_TIME_NONE; - else - timestamp = val * GST_SECOND; - -done: - gst_structure_set (structure, name, G_TYPE_UINT64, timestamp, NULL); - - return res; -} - -typedef struct -{ - const gchar *long_name; - const gchar *short_name; - GType type; - const gchar *new_name; -} Properties; - -static gboolean -_cleanup_fields (const Properties * filed_names, GstStructure * structure, - GError ** error) -{ - guint i; - - for (i = 0; filed_names[i].long_name; i++) { - gboolean exists = FALSE; - - /* Move shortly named fields to longname variante */ - if (gst_structure_has_field (structure, filed_names[i].short_name)) { - exists = TRUE; - - if (gst_structure_has_field (structure, filed_names[i].long_name)) { - *error = g_error_new (GES_ERROR, 0, "Using short and long name" - " at the same time for property: %s, which one should I use?!", - filed_names[i].long_name); - - return FALSE; - } else { - const GValue *val = - gst_structure_get_value (structure, filed_names[i].short_name); - - gst_structure_set_value (structure, filed_names[i].long_name, val); - gst_structure_remove_field (structure, filed_names[i].short_name); - } - } else if (gst_structure_has_field (structure, filed_names[i].long_name)) { - exists = TRUE; - } - - if (exists) { - if (filed_names[i].type == GST_TYPE_CLOCK_TIME) { - if (_convert_to_clocktime (structure, filed_names[i].long_name, 0) == 0) { - *error = g_error_new (GES_ERROR, 0, "Could not convert" - " %s to GstClockTime", filed_names[i].long_name); - - return FALSE; - } - } - } - - if (filed_names[i].new_name - && gst_structure_has_field (structure, filed_names[i].long_name)) { - const GValue *val = - gst_structure_get_value (structure, filed_names[i].long_name); - - gst_structure_set_value (structure, filed_names[i].new_name, val); - gst_structure_remove_field (structure, filed_names[i].long_name); - } - } - - return TRUE; -} - -static gboolean -_add_clip (GESTimeline * timeline, GstStructure * structure, GError ** error) -{ - const Properties filed_names[] = { - {"uri", "n", 0, "asset-id"}, - {"name", "n", 0, NULL}, - {"start", "s", GST_TYPE_CLOCK_TIME, NULL}, - {"duration", "d", GST_TYPE_CLOCK_TIME, NULL}, - {"inpoint", "i", GST_TYPE_CLOCK_TIME, NULL}, - {"track-types", "tt", 0, NULL}, - {"layer", "l", 0, NULL}, - {NULL}, - }; - - if (!_cleanup_fields (filed_names, structure, error)) - return FALSE; - - gst_structure_set (structure, "type", G_TYPE_STRING, "GESUriClip", NULL); - - GST_ERROR ("Adding a clip %" GST_PTR_FORMAT, structure); - - return _ges_add_add_clip_from_struct (timeline, structure, error); -} - -static gboolean -_add_effect (GESTimeline * timeline, GstStructure * structure, GError ** error) -{ - const Properties filed_names[] = { - {"element-name", "e", 0, NULL}, - {"bin-description", "d", 0, "asset-id"}, - {"name", "n", 0, "child-name"}, - {NULL, NULL, 0, NULL}, - }; - - if (!_cleanup_fields (filed_names, structure, error)) - return FALSE; - - gst_structure_set (structure, "child-type", G_TYPE_STRING, "GESEffect", NULL); - - GST_ERROR ("Adding a clip %" GST_PTR_FORMAT, structure); - - return _ges_container_add_child_from_struct (timeline, structure, error); -} - -static gboolean -_set_child_property (GESTimeline * timeline, GstStructure * structure, - GError ** error) -{ - return _ges_set_child_property_from_struct (timeline, structure, error); -} - -static GOptionEntry timeline_parsing_options[] = { - {"clip", 'c', 0.0, G_OPTION_ARG_CALLBACK, &_add_clip, - "Adds a clip in the timeline", - " start - s: The start position of the element inside the layer.\n" - " duration - d: The duration of the clip.\n" - " inpoint - i: The inpoint of the clip\n." - " track-types - tt: The type of the tracks where the clip should be used:\n" - " Examples:\n" - " * audio / a\n" - " * video / v\n" - " * audio+video / a+v\n" - " Will default to all the media types in the clip that match the global track-types"}, - {"effect", 'e', 0.0, G_OPTION_ARG_CALLBACK, &_add_effect, - "Adds an effect as decribed by 'bin-description'", - " bin-description - d: The description of the effect bin with a gst-launch-style pipeline description." - " element-name - d: The name of the element to apply the effect on." - /* TODO: Implement that: - * " start - s: The start position of the element inside the layer -- implies creation of effect *Clip*.\n" - * " duration - d: The duration of the clip -- implies creation of effect *Clip*.\n" - * " inpoint - i: The inpoint of the clip-- implies creation of effect *Clip*.\n" */ - }, -}; - -#define EXEC(func,structure,error) G_STMT_START { \ - gboolean res = ((ActionFromStructureFunc)func)(timeline, structure, error); \ - if (!res) {\ - GST_ERROR ("Could not execute: %" GST_PTR_FORMAT ", error: %s", structure, (*error)->message); \ - goto build_failure; \ - } \ -} G_STMT_END - -static GESTimeline * -create_timeline (GList * structures, const gchar * proj_uri, - const gchar * scenario) -{ - guint i; - GList *tmp; - GESTimeline *timeline; - GESTrack *tracka = NULL, *trackv = NULL; - GESProject *project = ges_project_new (proj_uri); - - g_signal_connect (project, "error-loading-asset", - G_CALLBACK (error_loading_asset_cb), NULL); - - if (proj_uri != NULL) { - g_signal_connect (project, "loaded", G_CALLBACK (project_loaded_cb), NULL); - } - - timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), NULL)); - - if (proj_uri) { - goto done; - } - - g_object_set (timeline, "auto-transition", TRUE, NULL); - if (track_types & GES_TRACK_TYPE_VIDEO) { - trackv = GES_TRACK (ges_video_track_new ()); - - if (disable_mixing) - ges_track_set_mixing (trackv, FALSE); - - if (!(ges_timeline_add_track (timeline, trackv))) - goto build_failure; - } - - if (track_types & GES_TRACK_TYPE_AUDIO) { - tracka = GES_TRACK (ges_audio_track_new ()); - if (disable_mixing) - ges_track_set_mixing (tracka, FALSE); - - if (!(ges_timeline_add_track (timeline, tracka))) - goto build_failure; - } - - /* Here we've finished initializing our timeline, we're - * ready to start using it... by solely working with the layer !*/ - for (tmp = structures; tmp; tmp = tmp->next) { - const gchar *name = gst_structure_get_name (tmp->data); - GError *error = NULL; - - if (g_str_has_prefix (name, "set-")) { - EXEC (_set_child_property, tmp->data, &error); - continue; - } - - for (i = 0; i < G_N_ELEMENTS (timeline_parsing_options); i++) { - if (gst_structure_has_name (tmp->data, - timeline_parsing_options[i].long_name) - || (strlen (name) == 1 && - *name == timeline_parsing_options[i].short_name)) { - EXEC (((ActionFromStructureFunc) timeline_parsing_options[i].arg_data), - tmp->data, &error); - } - } - } - -done: - return timeline; - -build_failure: - { - gst_object_unref (timeline); - return NULL; - } -} static gboolean _save_timeline (GESTimeline * timeline, const gchar * load_path) @@ -453,9 +244,33 @@ _save_timeline (GESTimeline * timeline, const gchar * load_path) return TRUE; } +static GESTimeline * +create_timeline (const gchar * serialized_timeline, const gchar * proj_uri, + const gchar * scenario) +{ + GESTimeline *timeline; + GESProject *project; + + if (proj_uri != NULL) { + project = ges_project_new (proj_uri); + } else if (scenario == NULL) { + project = ges_project_new (serialized_timeline); + } else { + project = ges_project_new (NULL); + } + + g_signal_connect (project, "error-loading-asset", + G_CALLBACK (error_loading_asset_cb), NULL); + g_signal_connect (project, "loaded", G_CALLBACK (project_loaded_cb), NULL); + + timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), NULL)); + + return timeline; +} + static GESPipeline * create_pipeline (GESTimeline ** ret_timeline, gchar * load_path, - GList * structures, const gchar * scenario) + const gchar * serialized_timeline, const gchar * scenario) { gchar *uri = NULL; GESTimeline *timeline = NULL; @@ -472,8 +287,10 @@ create_pipeline (GESTimeline ** ret_timeline, gchar * load_path, pipeline = ges_pipeline_new (); - if (!(timeline = create_timeline (structures, uri, scenario))) + if (!(timeline = create_timeline (serialized_timeline, uri, scenario))) { + GST_ERROR ("Could not create the timeline"); goto failure; + } if (!load_path) ges_timeline_commit (timeline); @@ -790,15 +607,13 @@ sanitize_argument (gchar * arg) return new_string; } -static GESStructureParser * +static gchar * _parse_timeline (int argc, char **argv) { gint i; - yyscan_t scanner; - gchar *string = g_strdup (" "); - GESStructureParser *parser = ges_structure_parser_new (); - priv_ges_parse_yylex_init_extra (parser, &scanner); + gchar *string = g_strdup (" "); + for (i = 1; i < argc; i++) { gchar *new_string; gchar *sanitized = sanitize_argument (argv[i]); @@ -810,13 +625,8 @@ _parse_timeline (int argc, char **argv) string = new_string; } - priv_ges_parse_yy_scan_string (string, scanner); - priv_ges_parse_yylex (scanner); - g_free (string); - priv_ges_parse_yylex_destroy (scanner); - ges_structure_parser_end_of_file (parser); - return parser; + return string; } int @@ -824,7 +634,7 @@ main (int argc, gchar ** argv) { gint validate_res; GError *err = NULL; - gchar *outputuri = NULL; + gchar *outputuri = NULL, *serialized_timeline = NULL; const gchar *format = NULL; gchar *exclude_args = NULL; static gboolean smartrender = FALSE; @@ -836,7 +646,6 @@ main (int argc, gchar ** argv) gchar *videosink = NULL, *audiosink = NULL; gboolean inspect_action_type = FALSE; gchar *encoding_profile = NULL; - GESStructureParser *parser; GOptionEntry options[] = { {"thumbnail", 'm', 0.0, G_OPTION_ARG_DOUBLE, &thumbinterval, @@ -979,8 +788,8 @@ main (int argc, gchar ** argv) g_option_context_free (ctx); /* Create the pipeline */ - parser = _parse_timeline (argc, argv); - create_pipeline (&timeline, load_path, parser->structures, scenario); + serialized_timeline = _parse_timeline (argc, argv); + create_pipeline (&timeline, load_path, serialized_timeline, scenario); if (!pipeline) exit (1); @@ -1070,6 +879,11 @@ main (int argc, gchar ** argv) g_error ("Could not activate scenario %s", scenario); return 29; } + + if (!_timeline_set_user_options (timeline, NULL)) { + g_error ("Could not properly set tracks"); + return 29; + } } bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));