/* Gstreamer Editing Services * * Copyright (C) <2012> 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #undef VERSION #endif /* TODO Determine error codes numbers */ #include #include #include #include "ges.h" #include "ges-internal.h" #define parent_class ges_xml_formatter_parent_class #define API_VERSION 0 #define MINOR_VERSION 5 #define VERSION 0.5 #define COLLECT_STR_OPT (G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL) #define _GET_PRIV(o) (((GESXmlFormatter*)o)->priv) struct _GESXmlFormatterPrivate { gboolean ges_opened; gboolean project_opened; GString *str; GHashTable *element_id; guint nbelements; guint min_version; }; G_DEFINE_TYPE_WITH_PRIVATE (GESXmlFormatter, ges_xml_formatter, GES_TYPE_BASE_XML_FORMATTER); static inline void _parse_ges_element (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) { guint api_version; const gchar *version, *properties; gchar **split_version = NULL; if (g_strcmp0 (element_name, "ges")) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', Missing element'", element_name); return; } if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error, G_MARKUP_COLLECT_STRING, "version", &version, COLLECT_STR_OPT, "properties", &properties, G_MARKUP_COLLECT_INVALID)) { return; } split_version = g_strsplit (version, ".", 2); if (split_version[1] == NULL) goto failed; errno = 0; api_version = g_ascii_strtoull (split_version[0], NULL, 10); if (errno || api_version != API_VERSION) goto stroull_failed; self->priv->min_version = g_ascii_strtoull (split_version[1], NULL, 10); if (self->priv->min_version > MINOR_VERSION) goto failed; _GET_PRIV (self)->ges_opened = TRUE; g_strfreev (split_version); return; failed: g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', %s wrong version'", element_name, version); if (split_version) g_strfreev (split_version); return; stroull_failed: GST_WARNING_OBJECT (self, "Error while strtoull: %s", g_strerror (errno)); goto failed; } static inline void _parse_project (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) { const gchar *metadatas = NULL, *properties; GESXmlFormatterPrivate *priv = _GET_PRIV (self); if (g_strcmp0 (element_name, "project")) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Found element '%s', Missing '' element'", element_name); } else { priv->project_opened = TRUE; if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error, COLLECT_STR_OPT, "properties", &properties, COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID)) return; if (GES_FORMATTER (self)->project && metadatas) ges_meta_container_add_metas_from_string (GES_META_CONTAINER (GES_FORMATTER (self)->project), metadatas); } } static inline void _parse_encoding_profile (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) { GstCaps *capsformat = NULL; GstStructure *preset_properties = NULL; const gchar *name, *description, *type, *preset = NULL, *str_preset_properties = NULL, *preset_name = NULL, *format; if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error, G_MARKUP_COLLECT_STRING, "name", &name, G_MARKUP_COLLECT_STRING, "description", &description, G_MARKUP_COLLECT_STRING, "type", &type, COLLECT_STR_OPT, "preset", &preset, COLLECT_STR_OPT, "preset-properties", &str_preset_properties, COLLECT_STR_OPT, "preset-name", &preset_name, COLLECT_STR_OPT, "format", &format, G_MARKUP_COLLECT_INVALID)) return; if (format) capsformat = gst_caps_from_string (format); if (str_preset_properties) { preset_properties = gst_structure_from_string (str_preset_properties, NULL); if (preset_properties == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', Wrong preset-properties format.", element_name); return; } } ges_base_xml_formatter_add_encoding_profile (GES_BASE_XML_FORMATTER (self), type, NULL, name, description, capsformat, preset, preset_properties, preset_name, 0, 0, NULL, 0, FALSE, NULL, TRUE, error); } static inline void _parse_stream_profile (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) { gboolean variableframerate = FALSE, enabled = TRUE; guint id = 0, presence = 0, pass = 0; GstCaps *format_caps = NULL, *restriction_caps = NULL; GstStructure *preset_properties = NULL; const gchar *parent, *strid, *type, *strpresence, *format = NULL, *name = NULL, *description = NULL, *preset, *str_preset_properties = NULL, *preset_name = NULL, *restriction = NULL, *strpass = NULL, *strvariableframerate = NULL, *strenabled = NULL; /* FIXME Looks like there is a bug in that function, if we put the parent * at the beginning it set %NULL and not the real value... :/ */ if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error, G_MARKUP_COLLECT_STRING, "id", &strid, G_MARKUP_COLLECT_STRING, "type", &type, G_MARKUP_COLLECT_STRING, "presence", &strpresence, COLLECT_STR_OPT, "format", &format, COLLECT_STR_OPT, "name", &name, COLLECT_STR_OPT, "description", &description, COLLECT_STR_OPT, "preset", &preset, COLLECT_STR_OPT, "preset-properties", &str_preset_properties, COLLECT_STR_OPT, "preset-name", &preset_name, COLLECT_STR_OPT, "restriction", &restriction, COLLECT_STR_OPT, "pass", &strpass, COLLECT_STR_OPT, "variableframerate", &strvariableframerate, COLLECT_STR_OPT, "enabled", &strenabled, G_MARKUP_COLLECT_STRING, "parent", &parent, G_MARKUP_COLLECT_INVALID)) return; errno = 0; id = g_ascii_strtoll (strid, NULL, 10); if (errno) goto convertion_failed; if (strpresence) { presence = g_ascii_strtoll (strpresence, NULL, 10); if (errno) goto convertion_failed; } if (str_preset_properties) { preset_properties = gst_structure_from_string (str_preset_properties, NULL); if (preset_properties == NULL) goto convertion_failed; } if (strpass) { pass = g_ascii_strtoll (strpass, NULL, 10); if (errno) goto convertion_failed; } if (strvariableframerate) { variableframerate = g_ascii_strtoll (strvariableframerate, NULL, 10); if (errno) goto convertion_failed; } if (strenabled) { enabled = g_ascii_strtoll (strenabled, NULL, 10); if (errno) goto convertion_failed; } if (format) format_caps = gst_caps_from_string (format); if (restriction) restriction_caps = gst_caps_from_string (restriction); ges_base_xml_formatter_add_encoding_profile (GES_BASE_XML_FORMATTER (self), type, parent, name, description, format_caps, preset, preset_properties, preset_name, id, presence, restriction_caps, pass, variableframerate, NULL, enabled, error); if (preset_properties) gst_structure_free (preset_properties); return; convertion_failed: g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', Wrong property type, error: %s'", element_name, g_strerror (errno)); return; } static inline void _parse_timeline (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) { const gchar *metadatas = NULL, *properties = NULL; GESTimeline *timeline = GES_FORMATTER (self)->timeline; if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error, COLLECT_STR_OPT, "properties", &properties, COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID)) return; if (timeline == NULL) return; ges_base_xml_formatter_set_timeline_properties (GES_BASE_XML_FORMATTER (self), timeline, properties, metadatas); } static inline void _parse_asset (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) { GType extractable_type; const gchar *id, *extractable_type_name, *metadatas = NULL, *properties = NULL, *proxy_id = NULL; if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error, G_MARKUP_COLLECT_STRING, "id", &id, G_MARKUP_COLLECT_STRING, "extractable-type-name", &extractable_type_name, COLLECT_STR_OPT, "properties", &properties, COLLECT_STR_OPT, "metadatas", &metadatas, COLLECT_STR_OPT, "proxy-id", &proxy_id, G_MARKUP_COLLECT_INVALID)) return; extractable_type = g_type_from_name (extractable_type_name); if (extractable_type == G_TYPE_NONE) g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s' invalid extractable_type %s'", element_name, extractable_type_name); else if (!g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE)) g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', %s not an extractable_type'", element_name, extractable_type_name); else { GstStructure *props = NULL; if (properties) props = gst_structure_from_string (properties, NULL); ges_base_xml_formatter_add_asset (GES_BASE_XML_FORMATTER (self), id, extractable_type, props, metadatas, proxy_id, error); if (props) gst_structure_free (props); } } static inline void _parse_track (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) { GstCaps *caps; GESTrackType track_type; GstStructure *props = NULL; const gchar *strtrack_type, *strcaps, *strtrack_id, *metadatas = NULL, *properties = NULL; if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error, G_MARKUP_COLLECT_STRING, "track-type", &strtrack_type, G_MARKUP_COLLECT_STRING, "track-id", &strtrack_id, COLLECT_STR_OPT, "properties", &properties, COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_STRING, "caps", &strcaps, G_MARKUP_COLLECT_INVALID)) return; if ((caps = gst_caps_from_string (strcaps)) == NULL) goto wrong_caps; errno = 0; track_type = g_ascii_strtoll (strtrack_type, NULL, 10); if (errno) goto convertion_failed; if (properties) { props = gst_structure_from_string (properties, NULL); } ges_base_xml_formatter_add_track (GES_BASE_XML_FORMATTER (self), track_type, caps, strtrack_id, props, metadatas, error); if (props) gst_structure_free (props); gst_caps_unref (caps); return; wrong_caps: g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', Can not create caps: %s'", element_name, strcaps); return; convertion_failed: gst_caps_unref (caps); g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', Wrong property type, error: %s'", element_name, g_strerror (errno)); return; } static inline void _parse_layer (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) { GstStructure *props = NULL; guint priority; GType extractable_type = G_TYPE_NONE; const gchar *metadatas = NULL, *properties = NULL, *strprio = NULL, *extractable_type_name; if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error, G_MARKUP_COLLECT_STRING, "priority", &strprio, COLLECT_STR_OPT, "extractable-type-name", &extractable_type_name, COLLECT_STR_OPT, "properties", &properties, COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID)) return; if (extractable_type_name) { extractable_type = g_type_from_name (extractable_type_name); if (extractable_type == G_TYPE_NONE) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s' invalid extractable_type %s'", element_name, extractable_type_name); return; } else if (!g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE)) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', %s not an extractable_type'", element_name, extractable_type_name); return; } } if (properties) { props = gst_structure_from_string (properties, NULL); if (props == NULL) goto wrong_properties; } errno = 0; priority = g_ascii_strtoll (strprio, NULL, 10); if (errno) goto convertion_failed; ges_base_xml_formatter_add_layer (GES_BASE_XML_FORMATTER (self), extractable_type, priority, props, metadatas, error); done: if (props) gst_structure_free (props); return; convertion_failed: g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', Wrong property type, error: %s'", element_name, g_strerror (errno)); goto done; wrong_properties: g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', wrong layer properties '%s', could no be deserialized", element_name, properties); } static inline void _parse_clip (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) { GType type; GstStructure *props = NULL, *children_props = NULL; GESTrackType track_types; GstClockTime start, inpoint = 0, duration, layer_prio; const gchar *strid, *asset_id, *strstart, *strin, *strduration, *strrate, *strtrack_types, *strtype, *metadatas = NULL, *properties = NULL, *children_properties = NULL, *strlayer_prio; if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error, G_MARKUP_COLLECT_STRING, "id", &strid, G_MARKUP_COLLECT_STRING, "type-name", &strtype, G_MARKUP_COLLECT_STRING, "start", &strstart, G_MARKUP_COLLECT_STRING, "duration", &strduration, G_MARKUP_COLLECT_STRING, "asset-id", &asset_id, G_MARKUP_COLLECT_STRING, "track-types", &strtrack_types, G_MARKUP_COLLECT_STRING, "layer-priority", &strlayer_prio, COLLECT_STR_OPT, "properties", &properties, COLLECT_STR_OPT, "children-properties", &children_properties, COLLECT_STR_OPT, "metadatas", &metadatas, COLLECT_STR_OPT, "rate", &strrate, COLLECT_STR_OPT, "inpoint", &strin, G_MARKUP_COLLECT_INVALID)) { return; } type = g_type_from_name (strtype); if (!g_type_is_a (type, GES_TYPE_CLIP)) goto wrong_type; errno = 0; track_types = g_ascii_strtoll (strtrack_types, NULL, 10); if (errno) goto convertion_failed; layer_prio = g_ascii_strtoll (strlayer_prio, NULL, 10); if (errno) goto convertion_failed; if (strin) { inpoint = g_ascii_strtoull (strin, NULL, 10); if (errno) goto convertion_failed; } start = g_ascii_strtoull (strstart, NULL, 10); if (errno) goto convertion_failed; duration = g_ascii_strtoull (strduration, NULL, 10); if (errno) goto convertion_failed; if (properties) { props = gst_structure_from_string (properties, NULL); if (props == NULL) goto wrong_properties; } if (children_properties) { children_props = gst_structure_from_string (children_properties, NULL); if (children_props == NULL) goto wrong_children_properties; } ges_base_xml_formatter_add_clip (GES_BASE_XML_FORMATTER (self), strid, asset_id, type, start, inpoint, duration, layer_prio, track_types, props, children_props, metadatas, error); if (props) gst_structure_free (props); return; wrong_properties: g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', Clip %s properties '%s', could no be deserialized", element_name, asset_id, properties); return; wrong_children_properties: g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', Clip %s children properties '%s', could no be deserialized", element_name, asset_id, children_properties); return; convertion_failed: g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', Wrong property type, error: %s'", element_name, g_strerror (errno)); return; wrong_type: g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', %s not a GESClip'", element_name, strtype); } static inline void _parse_binding (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) { const gchar *type = NULL, *source_type = NULL, *timed_values = NULL, *property_name = NULL, *mode = NULL, *track_id = NULL; gchar **pairs, **tmp; gchar *pair; GSList *list = NULL; if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error, G_MARKUP_COLLECT_STRING, "type", &type, G_MARKUP_COLLECT_STRING, "source_type", &source_type, G_MARKUP_COLLECT_STRING, "property", &property_name, G_MARKUP_COLLECT_STRING, "mode", &mode, G_MARKUP_COLLECT_STRING, "track_id", &track_id, G_MARKUP_COLLECT_STRING, "values", &timed_values, G_MARKUP_COLLECT_INVALID)) { return; } pairs = g_strsplit (timed_values, " ", 0); for (tmp = pairs; tmp != NULL; tmp += 1) { gchar **value_pair; pair = *tmp; if (pair == NULL) break; if (strlen (pair)) { GstTimedValue *value; value = g_slice_new (GstTimedValue); value_pair = g_strsplit (pair, ":", 0); value->timestamp = g_ascii_strtoull (value_pair[0], NULL, 10); value->value = g_ascii_strtod (value_pair[1], NULL); list = g_slist_append (list, value); g_strfreev (value_pair); } } g_strfreev (pairs); ges_base_xml_formatter_add_control_binding (GES_BASE_XML_FORMATTER (self), type, source_type, property_name, (gint) g_ascii_strtoll (mode, NULL, 10), track_id, list); } static inline void _parse_source (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) { GstStructure *children_props = NULL; const gchar *track_id = NULL, *children_properties = NULL; if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error, G_MARKUP_COLLECT_STRING, "track-id", &track_id, COLLECT_STR_OPT, "children-properties", &children_properties, G_MARKUP_COLLECT_INVALID)) { return; } if (children_properties) { children_props = gst_structure_from_string (children_properties, NULL); if (children_props == NULL) goto wrong_children_properties; } ges_base_xml_formatter_add_source (GES_BASE_XML_FORMATTER (self), track_id, children_props); if (children_props) gst_structure_free (children_props); return; wrong_children_properties: g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', children properties '%s', could no be deserialized", element_name, children_properties); } static inline void _parse_effect (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) { GType type; GstStructure *children_props = NULL, *props = NULL; const gchar *asset_id = NULL, *strtype = NULL, *track_id = NULL, *metadatas = NULL, *properties = NULL, *track_type = NULL, *children_properties = NULL, *clip_id; if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error, COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_STRING, "asset-id", &asset_id, G_MARKUP_COLLECT_STRING, "clip-id", &clip_id, G_MARKUP_COLLECT_STRING, "type-name", &strtype, G_MARKUP_COLLECT_STRING, "track-id", &track_id, COLLECT_STR_OPT, "children-properties", &children_properties, COLLECT_STR_OPT, "track-type", &track_type, COLLECT_STR_OPT, "properties", &properties, G_MARKUP_COLLECT_INVALID)) { return; } type = g_type_from_name (strtype); if (!g_type_is_a (type, GES_TYPE_BASE_EFFECT)) goto wrong_type; if (children_properties) { children_props = gst_structure_from_string (children_properties, NULL); if (children_props == NULL) goto wrong_children_properties; } if (properties) { props = gst_structure_from_string (properties, NULL); if (props == NULL) goto wrong_properties; } ges_base_xml_formatter_add_track_element (GES_BASE_XML_FORMATTER (self), type, asset_id, track_id, clip_id, children_props, props, metadatas, error); out: if (props) gst_structure_free (props); if (children_props) gst_structure_free (children_props); return; wrong_properties: g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', Effect %s properties '%s', could no be deserialized", element_name, asset_id, properties); goto out; wrong_children_properties: g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', Effect %s children properties '%s', could no be deserialized", element_name, asset_id, children_properties); goto out; wrong_type: g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', %s not a GESBaseEffect'", element_name, strtype); } static inline void _parse_group (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) { const gchar *id, *properties, *metadatas = NULL; if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error, G_MARKUP_COLLECT_STRING, "id", &id, G_MARKUP_COLLECT_STRING, "properties", &properties, COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID)) { return; } ges_base_xml_formatter_add_group (GES_BASE_XML_FORMATTER (self), id, properties, metadatas); } static inline void _parse_group_child (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, GESXmlFormatter * self, GError ** error) { const gchar *child_id, *name; if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error, G_MARKUP_COLLECT_STRING, "id", &child_id, G_MARKUP_COLLECT_STRING, "name", &name, G_MARKUP_COLLECT_INVALID)) { return; } ges_base_xml_formatter_last_group_add_child (GES_BASE_XML_FORMATTER (self), child_id, name); } static void _parse_element_start (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, gpointer self, GError ** error) { GESXmlFormatterPrivate *priv = _GET_PRIV (self); if (!G_UNLIKELY (priv->ges_opened)) _parse_ges_element (context, element_name, attribute_names, attribute_values, self, error); else if (!G_UNLIKELY (priv->project_opened)) _parse_project (context, element_name, attribute_names, attribute_values, self, error); else if (g_strcmp0 (element_name, "encoding-profile") == 0) _parse_encoding_profile (context, element_name, attribute_names, attribute_values, self, error); else if (g_strcmp0 (element_name, "stream-profile") == 0) _parse_stream_profile (context, element_name, attribute_names, attribute_values, self, error); else if (g_strcmp0 (element_name, "timeline") == 0) _parse_timeline (context, element_name, attribute_names, attribute_values, self, error); else if (g_strcmp0 (element_name, "asset") == 0) _parse_asset (context, element_name, attribute_names, attribute_values, self, error); else if (g_strcmp0 (element_name, "track") == 0) _parse_track (context, element_name, attribute_names, attribute_values, self, error); else if (g_strcmp0 (element_name, "layer") == 0) _parse_layer (context, element_name, attribute_names, attribute_values, self, error); else if (g_strcmp0 (element_name, "clip") == 0) _parse_clip (context, element_name, attribute_names, attribute_values, self, error); else if (g_strcmp0 (element_name, "source") == 0) _parse_source (context, element_name, attribute_names, attribute_values, self, error); else if (g_strcmp0 (element_name, "effect") == 0) _parse_effect (context, element_name, attribute_names, attribute_values, self, error); else if (g_strcmp0 (element_name, "binding") == 0) _parse_binding (context, element_name, attribute_names, attribute_values, self, error); else if (g_strcmp0 (element_name, "group") == 0) _parse_group (context, element_name, attribute_names, attribute_values, self, error); else if (g_strcmp0 (element_name, "child") == 0) _parse_group_child (context, element_name, attribute_names, attribute_values, self, error); else GST_LOG_OBJECT (self, "Element %s not handled", element_name); } static void _parse_element_end (GMarkupParseContext * context, const gchar * element_name, gpointer self, GError ** error) { /*GESXmlFormatterPrivate *priv = _GET_PRIV (self); */ if (g_strcmp0 (element_name, "ges") == 0 && GES_FORMATTER (self)->project) { gchar *version = g_strdup_printf ("%d.%d", API_VERSION, GES_XML_FORMATTER (self)->priv->min_version); ges_meta_container_set_string (GES_META_CONTAINER (GES_FORMATTER (self)->project), GES_META_FORMAT_VERSION, version); g_free (version); } } static void _error_parsing (GMarkupParseContext * context, GError * error, gpointer user_data) { GST_WARNING ("Error occurred when parsing %s", error->message); } /*********************************************** * * * Saving implementation * * * ***********************************************/ /* XML writting utils */ static inline void append_escaped (GString * str, gchar * tmpstr) { g_string_append (str, tmpstr); g_free (tmpstr); } static inline gboolean _can_serialize_spec (GParamSpec * spec) { if (!(spec->flags & G_PARAM_WRITABLE)) { GST_DEBUG ("%s from %s is not writable", spec->name, g_type_name (spec->owner_type)); return FALSE; } else if (spec->flags & G_PARAM_CONSTRUCT_ONLY) { GST_DEBUG ("%s from %s is construct only", spec->name, g_type_name (spec->owner_type)); return FALSE; } else if (spec->flags & GES_PARAM_NO_SERIALIZATION && g_type_is_a (spec->owner_type, GES_TYPE_TIMELINE_ELEMENT)) { GST_DEBUG ("%s from %s is set as GES_PARAM_NO_SERIALIZATION", spec->name, g_type_name (spec->owner_type)); return FALSE; } else if (g_type_is_a (G_PARAM_SPEC_VALUE_TYPE (spec), G_TYPE_OBJECT)) { GST_DEBUG ("%s from %s contains GObject, can't serialize that.", spec->name, g_type_name (spec->owner_type)); return FALSE; } else if ((g_type_is_a (spec->owner_type, GST_TYPE_OBJECT) && !g_strcmp0 (spec->name, "name"))) { GST_DEBUG ("We do not want to serialize the name of GstObjects."); return FALSE; } else if (G_PARAM_SPEC_VALUE_TYPE (spec) == G_TYPE_GTYPE) { GST_DEBUG ("%s from %s contains a GType, can't serialize.", spec->name, g_type_name (spec->owner_type)); return FALSE; } return TRUE; } static inline void _init_value_from_spec_for_serialization (GValue * value, GParamSpec * spec) { if (g_type_is_a (spec->value_type, G_TYPE_ENUM) || g_type_is_a (spec->value_type, G_TYPE_FLAGS)) g_value_init (value, G_TYPE_INT); else g_value_init (value, spec->value_type); } static gchar * _serialize_properties (GObject * object, const gchar * fieldname, ...) { gchar *ret; guint n_props, j; GParamSpec *spec, **pspecs; GObjectClass *class = G_OBJECT_GET_CLASS (object); GstStructure *structure = gst_structure_new_empty ("properties"); pspecs = g_object_class_list_properties (class, &n_props); for (j = 0; j < n_props; j++) { GValue val = { 0 }; spec = pspecs[j]; if (spec->value_type == GST_TYPE_CAPS) { GstCaps *caps; gchar *caps_str; g_object_get (object, spec->name, &caps, NULL); caps_str = gst_caps_to_string (caps); gst_structure_set (structure, spec->name, G_TYPE_STRING, caps_str, NULL); if (caps) gst_caps_unref (caps); g_free (caps_str); } else if (_can_serialize_spec (spec)) { _init_value_from_spec_for_serialization (&val, spec); g_object_get_property (object, spec->name, &val); gst_structure_set_value (structure, spec->name, &val); g_value_unset (&val); } } g_free (pspecs); if (fieldname) { va_list varargs; va_start (varargs, fieldname); gst_structure_remove_fields_valist (structure, fieldname, varargs); va_end (varargs); } ret = gst_structure_to_string (structure); gst_structure_free (structure); return ret; } static inline void _save_assets (GESXmlFormatter * self, GString * str, GESProject * project) { char *properties, *metas; GESAsset *asset, *proxy; GList *assets, *tmp; assets = ges_project_list_assets (project, GES_TYPE_EXTRACTABLE); for (tmp = assets; tmp; tmp = tmp->next) { asset = GES_ASSET (tmp->data); properties = _serialize_properties (G_OBJECT (asset), NULL); metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (asset)); append_escaped (str, g_markup_printf_escaped (" next) tmp->next = g_list_last (assets); } self->priv->min_version = MAX (self->priv->min_version, 3); } g_string_append (str, "/>\n"); g_free (properties); g_free (metas); } g_list_free_full (assets, gst_object_unref); } static inline void _save_tracks (GESXmlFormatter * self, GString * str, GESTimeline * timeline) { gchar *strtmp, *metas; GESTrack *track; GList *tmp, *tracks; char *properties; guint nb_tracks = 0; tracks = ges_timeline_get_tracks (timeline); for (tmp = tracks; tmp; tmp = tmp->next) { track = GES_TRACK (tmp->data); properties = _serialize_properties (G_OBJECT (track), NULL); strtmp = gst_caps_to_string (ges_track_get_caps (track)); metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (track)); append_escaped (str, g_markup_printf_escaped (" \n", strtmp, track->type, nb_tracks++, properties, metas)); g_free (strtmp); g_free (metas); g_free (properties); } g_list_free_full (tracks, gst_object_unref); } static inline void _save_children_properties (GString * str, GESTimelineElement * element) { GstStructure *structure; GParamSpec **pspecs, *spec; guint i, n_props; gchar *struct_str; pspecs = ges_timeline_element_list_children_properties (element, &n_props); structure = gst_structure_new_empty ("properties"); for (i = 0; i < n_props; i++) { GValue val = { 0 }; spec = pspecs[i]; if (_can_serialize_spec (spec)) { gchar *spec_name = g_strdup_printf ("%s::%s", g_type_name (spec->owner_type), spec->name); _init_value_from_spec_for_serialization (&val, spec); ges_timeline_element_get_child_property_by_pspec (element, spec, &val); gst_structure_set_value (structure, spec_name, &val); g_free (spec_name); g_value_unset (&val); } g_param_spec_unref (spec); } g_free (pspecs); struct_str = gst_structure_to_string (structure); append_escaped (str, g_markup_printf_escaped (" children-properties='%s'", struct_str)); gst_structure_free (structure); g_free (struct_str); } /* TODO : Use this function for every track element with controllable properties */ static inline void _save_keyframes (GString * str, GESTrackElement * trackelement, gint index) { GHashTable *bindings_hashtable; GHashTableIter iter; gpointer key, value; bindings_hashtable = ges_track_element_get_all_control_bindings (trackelement); g_hash_table_iter_init (&iter, bindings_hashtable); /* We iterate over the bindings, and save the timed values */ while (g_hash_table_iter_next (&iter, &key, &value)) { if (GST_IS_DIRECT_CONTROL_BINDING ((GstControlBinding *) value)) { GstControlSource *source; gboolean absolute = FALSE; GstDirectControlBinding *binding; binding = (GstDirectControlBinding *) value; g_object_get (binding, "control-source", &source, "absolute", &absolute, NULL); if (GST_IS_INTERPOLATION_CONTROL_SOURCE (source)) { GList *timed_values, *tmp; GstInterpolationMode mode; append_escaped (str, g_markup_printf_escaped (" \n")); } else GST_DEBUG ("control source not in [interpolation]"); } else GST_DEBUG ("Binding type not in [direct, direct-absolute]"); } } static inline void _save_effect (GString * str, guint clip_id, GESTrackElement * trackelement, GESTimeline * timeline) { GESTrack *tck; GList *tmp, *tracks; gchar *properties, *metas; guint track_id = 0; gboolean serialize; gchar *extractable_id; g_object_get (trackelement, "serialize", &serialize, NULL); if (!serialize) { GST_DEBUG_OBJECT (trackelement, "Should not be serialized"); return; } tck = ges_track_element_get_track (trackelement); if (tck == NULL) { GST_WARNING_OBJECT (trackelement, " Not in any track, can not save it"); return; } tracks = ges_timeline_get_tracks (timeline); for (tmp = tracks; tmp; tmp = tmp->next) { if (tmp->data == tck) break; track_id++; } g_list_free_full (tracks, gst_object_unref); properties = _serialize_properties (G_OBJECT (trackelement), "start", "in-point", "duration", "locked", "max-duration", "name", "priority", NULL); metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (trackelement)); extractable_id = ges_extractable_get_id (GES_EXTRACTABLE (trackelement)); append_escaped (str, g_markup_printf_escaped (" type, track_id, properties, metas)); g_free (extractable_id); g_free (properties); g_free (metas); _save_children_properties (str, GES_TIMELINE_ELEMENT (trackelement)); append_escaped (str, g_markup_printf_escaped (">\n")); _save_keyframes (str, trackelement, -1); append_escaped (str, g_markup_printf_escaped (" \n")); } static inline void _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline) { gchar *properties, *metas; GESLayer *layer; GESClip *clip; GList *tmplayer, *tmpclip, *clips; GESXmlFormatterPrivate *priv = self->priv; for (tmplayer = timeline->layers; tmplayer; tmplayer = tmplayer->next) { guint priority; layer = GES_LAYER (tmplayer->data); priority = ges_layer_get_priority (layer); properties = _serialize_properties (G_OBJECT (layer), "priority", NULL); metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (layer)); append_escaped (str, g_markup_printf_escaped (" \n", priority, properties, metas)); g_free (properties); g_free (metas); clips = ges_layer_get_clips (layer); for (tmpclip = clips; tmpclip; tmpclip = tmpclip->next) { GList *effects, *tmpeffect; GList *tmptrackelement; GList *tracks; gboolean serialize; gchar *extractable_id; clip = GES_CLIP (tmpclip->data); g_object_get (clip, "serialize", &serialize, NULL); if (!serialize) { GST_DEBUG_OBJECT (clip, "Should not be serialized"); continue; } /* We escape all mandatrorry properties that are handled sparetely * and vtype for StandarTransition as it is the asset ID */ properties = _serialize_properties (G_OBJECT (clip), "supported-formats", "rate", "in-point", "start", "duration", "max-duration", "priority", "vtype", "uri", NULL); extractable_id = ges_extractable_get_id (GES_EXTRACTABLE (clip)); metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (clip)); append_escaped (str, g_markup_printf_escaped (" nbelements, extractable_id, g_type_name (G_OBJECT_TYPE (clip)), priority, ges_clip_get_supported_formats (clip), _START (clip), _DURATION (clip), _INPOINT (clip), 0, properties, metas)); g_free (metas); if (GES_IS_TRANSITION_CLIP (clip)) { _save_children_properties (str, GES_TIMELINE_ELEMENT (clip)); self->priv->min_version = MAX (self->priv->min_version, 4); } g_string_append (str, ">\n"); g_free (extractable_id); g_free (properties); g_hash_table_insert (self->priv->element_id, clip, GINT_TO_POINTER (priv->nbelements)); /* Effects must always be serialized in the right priority order. * List order is guaranteed by the fact that ges_clip_get_top_effects * sorts the effects. */ effects = ges_clip_get_top_effects (clip); for (tmpeffect = effects; tmpeffect; tmpeffect = tmpeffect->next) { _save_effect (str, priv->nbelements, GES_TRACK_ELEMENT (tmpeffect->data), timeline); } g_list_free (effects); tracks = ges_timeline_get_tracks (timeline); for (tmptrackelement = GES_CONTAINER_CHILDREN (clip); tmptrackelement; tmptrackelement = tmptrackelement->next) { gint index; gboolean serialize; if (!GES_IS_SOURCE (tmptrackelement->data)) continue; g_object_get (tmptrackelement->data, "serialize", &serialize, NULL); if (!serialize) { GST_DEBUG_OBJECT (tmptrackelement->data, "Should not be serialized"); continue; } index = g_list_index (tracks, ges_track_element_get_track (tmptrackelement->data)); append_escaped (str, g_markup_printf_escaped (" data); append_escaped (str, g_markup_printf_escaped (">\n")); _save_keyframes (str, tmptrackelement->data, index); append_escaped (str, g_markup_printf_escaped (" \n")); } g_list_free_full (tracks, gst_object_unref); g_string_append (str, " \n"); priv->nbelements++; } g_list_free_full (clips, (GDestroyNotify) gst_object_unref); g_string_append (str, " \n"); } } static void _save_group (GESXmlFormatter * self, GString * str, GList ** seen_groups, GESGroup * group) { GList *tmp; gboolean serialize; gchar *properties, *metadatas; g_object_get (group, "serialize", &serialize, NULL); if (!serialize) { GST_DEBUG_OBJECT (group, "Should not be serialized"); return; } if (g_list_find (*seen_groups, group)) { GST_DEBUG_OBJECT (group, "Already serialized"); return; } *seen_groups = g_list_prepend (*seen_groups, group); for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) { if (GES_IS_GROUP (tmp->data)) { _save_group (self, str, seen_groups, GES_GROUP (GES_TIMELINE_ELEMENT (tmp->data))); } } properties = _serialize_properties (G_OBJECT (group), NULL); metadatas = ges_meta_container_metas_to_string (GES_META_CONTAINER (group)); self->priv->min_version = MAX (self->priv->min_version, 5); g_string_append_printf (str, " \n", self->priv->nbelements, properties, metadatas); g_free (properties); g_free (metadatas); g_hash_table_insert (self->priv->element_id, group, GINT_TO_POINTER (self->priv->nbelements)); self->priv->nbelements++; for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) { gint id = GPOINTER_TO_INT (g_hash_table_lookup (self->priv->element_id, tmp->data)); g_string_append_printf (str, " \n", id, GES_TIMELINE_ELEMENT_NAME (tmp->data)); } g_string_append (str, " \n"); } static void _save_groups (GESXmlFormatter * self, GString * str, GESTimeline * timeline) { GList *tmp; GList *seen_groups = NULL; g_string_append (str, " \n"); for (tmp = ges_timeline_get_groups (timeline); tmp; tmp = tmp->next) { _save_group (self, str, &seen_groups, tmp->data); } g_list_free (seen_groups); g_string_append (str, " \n"); } static inline void _save_timeline (GESXmlFormatter * self, GString * str, GESTimeline * timeline) { gchar *properties = NULL, *metas = NULL; properties = _serialize_properties (G_OBJECT (timeline), "update", "name", "async-handling", "message-forward", NULL); ges_meta_container_set_uint64 (GES_META_CONTAINER (timeline), "duration", ges_timeline_get_duration (timeline)); metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (timeline)); append_escaped (str, g_markup_printf_escaped (" \n", properties, metas)); _save_tracks (self, str, timeline); _save_layers (self, str, timeline); _save_groups (self, str, timeline); g_string_append (str, " \n"); g_free (properties); g_free (metas); } static void _save_stream_profiles (GESXmlFormatter * self, GString * str, GstEncodingProfile * sprof, const gchar * profilename, guint id) { gchar *tmpc; GstCaps *tmpcaps; const gchar *preset, *preset_name, *name, *description; append_escaped (str, g_markup_printf_escaped (" priv->min_version = MAX (self->priv->min_version, 2); } tmpcaps = gst_encoding_profile_get_format (sprof); if (tmpcaps) { tmpc = gst_caps_to_string (tmpcaps); append_escaped (str, g_markup_printf_escaped ("format='%s' ", tmpc)); gst_caps_unref (tmpcaps); g_free (tmpc); } name = gst_encoding_profile_get_name (sprof); if (name) append_escaped (str, g_markup_printf_escaped ("name='%s' ", name)); description = gst_encoding_profile_get_description (sprof); if (description) append_escaped (str, g_markup_printf_escaped ("description='%s' ", description)); preset = gst_encoding_profile_get_preset (sprof); if (preset) { GstElement *encoder; append_escaped (str, g_markup_printf_escaped ("preset='%s' ", preset)); encoder = get_element_for_encoding_profile (sprof, GST_ELEMENT_FACTORY_TYPE_ENCODER); if (encoder) { if (GST_IS_PRESET (encoder) && gst_preset_load_preset (GST_PRESET (encoder), preset)) { gchar *settings = _serialize_properties (G_OBJECT (encoder), NULL); append_escaped (str, g_markup_printf_escaped ("preset-properties='%s' ", settings)); g_free (settings); } gst_object_unref (encoder); } } preset_name = gst_encoding_profile_get_preset_name (sprof); if (preset_name) append_escaped (str, g_markup_printf_escaped ("preset-name='%s' ", preset_name)); tmpcaps = gst_encoding_profile_get_restriction (sprof); if (tmpcaps) { tmpc = gst_caps_to_string (tmpcaps); append_escaped (str, g_markup_printf_escaped ("restriction='%s' ", tmpc)); gst_caps_unref (tmpcaps); g_free (tmpc); } if (GST_IS_ENCODING_VIDEO_PROFILE (sprof)) { GstEncodingVideoProfile *vp = (GstEncodingVideoProfile *) sprof; append_escaped (str, g_markup_printf_escaped ("pass='%d' variableframerate='%i' ", gst_encoding_video_profile_get_pass (vp), gst_encoding_video_profile_get_variableframerate (vp))); } g_string_append (str, "/>\n"); } static inline void _save_encoding_profiles (GESXmlFormatter * self, GString * str, GESProject * project) { GstCaps *profformat; const gchar *profname, *profdesc, *profpreset, *proftype, *profpresetname; const GList *tmp; GList *profiles = g_list_reverse (g_list_copy ((GList *) ges_project_list_encoding_profiles (project))); for (tmp = profiles; tmp; tmp = tmp->next) { GstEncodingProfile *prof = GST_ENCODING_PROFILE (tmp->data); profname = gst_encoding_profile_get_name (prof); profdesc = gst_encoding_profile_get_description (prof); profpreset = gst_encoding_profile_get_preset (prof); profpresetname = gst_encoding_profile_get_preset_name (prof); proftype = gst_encoding_profile_get_type_nick (prof); append_escaped (str, g_markup_printf_escaped (" \n"); if (GST_IS_ENCODING_CONTAINER_PROFILE (prof)) { guint i = 0; const GList *tmp2; GstEncodingContainerProfile *container_prof; container_prof = GST_ENCODING_CONTAINER_PROFILE (prof); for (tmp2 = gst_encoding_container_profile_get_profiles (container_prof); tmp2; tmp2 = tmp2->next, i++) { GstEncodingProfile *sprof = (GstEncodingProfile *) tmp2->data; _save_stream_profiles (self, str, sprof, profname, i); } } append_escaped (str, g_markup_printf_escaped (" \n")); } g_list_free (profiles); } static GString * _save (GESFormatter * formatter, GESTimeline * timeline, GError ** error) { GString *str; GESProject *project; gchar *projstr = NULL, *version; gchar *properties = NULL, *metas = NULL; GESXmlFormatter *self = GES_XML_FORMATTER (formatter); GESXmlFormatterPrivate *priv; priv = _GET_PRIV (formatter); priv->min_version = 1; project = formatter->project; str = priv->str = g_string_new (NULL); properties = _serialize_properties (G_OBJECT (project), NULL); metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (project)); append_escaped (str, g_markup_printf_escaped (" \n", properties, metas)); g_free (properties); g_free (metas); g_string_append (str, " \n"); _save_encoding_profiles (GES_XML_FORMATTER (formatter), str, project); g_string_append (str, " \n"); g_string_append (str, " \n"); _save_assets (self, str, project); g_string_append (str, " \n"); _save_timeline (self, str, timeline); g_string_append (str, "\n"); projstr = g_strdup_printf ("\n", API_VERSION, priv->min_version); g_string_prepend (str, projstr); g_free (projstr); ges_meta_container_set_int (GES_META_CONTAINER (project), GES_META_FORMAT_VERSION, priv->min_version); version = g_strdup_printf ("%d.%d", API_VERSION, GES_XML_FORMATTER (formatter)->priv->min_version); ges_meta_container_set_string (GES_META_CONTAINER (project), GES_META_FORMAT_VERSION, version); g_free (version); priv->str = NULL; return str; } /*********************************************** * * * GObject virtual methods implementation * * * ***********************************************/ static void _get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { } static void _set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) { } static void ges_xml_formatter_init (GESXmlFormatter * self) { GESXmlFormatterPrivate *priv = ges_xml_formatter_get_instance_private (self); priv->project_opened = FALSE; priv->element_id = g_hash_table_new (g_direct_hash, g_direct_equal); self->priv = priv; self->priv->min_version = 1; } static void _dispose (GObject * object) { g_clear_pointer (&GES_XML_FORMATTER (object)->priv->element_id, g_hash_table_unref); G_OBJECT_CLASS (parent_class)->dispose (object); } static void ges_xml_formatter_class_init (GESXmlFormatterClass * self_class) { GObjectClass *object_class = G_OBJECT_CLASS (self_class); GESBaseXmlFormatterClass *basexmlformatter_class; basexmlformatter_class = GES_BASE_XML_FORMATTER_CLASS (self_class); object_class->get_property = _get_property; object_class->set_property = _set_property; object_class->dispose = _dispose; basexmlformatter_class->content_parser.start_element = _parse_element_start; basexmlformatter_class->content_parser.end_element = _parse_element_end; basexmlformatter_class->content_parser.text = NULL; basexmlformatter_class->content_parser.passthrough = NULL; basexmlformatter_class->content_parser.error = _error_parsing; ges_formatter_class_register_metas (GES_FORMATTER_CLASS (self_class), "ges", "GStreamer Editing Services project files", "xges", "application/ges", VERSION, GST_RANK_PRIMARY); basexmlformatter_class->save = _save; } #undef COLLECT_STR_OPT