/* 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 #include "ges-internal.h" #define parent_class ges_xml_formatter_parent_class #define API_VERSION 0 #define MINOR_VERSION 8 #define VERSION 0.8 #define COLLECT_STR_OPT (G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL) #define _GET_PRIV(o) (((GESXmlFormatter*)o)->priv) typedef struct { const gchar *id; gint start_line; gint start_char; gint fd; gchar *filename; GError *error; GMainLoop *ml; } SubprojectData; struct _GESXmlFormatterPrivate { gboolean ges_opened; gboolean project_opened; GString *str; GHashTable *element_id; GHashTable *subprojects_map; SubprojectData *subproject; gint subproject_depth; guint nbelements; guint min_version; }; G_LOCK_DEFINE_STATIC (uri_subprojects_map_lock); /* { project_uri: { subproject_uri: new_suproject_uri}} */ static GHashTable *uri_subprojects_map = NULL; G_DEFINE_TYPE_WITH_PRIVATE (GESXmlFormatter, ges_xml_formatter, GES_TYPE_BASE_XML_FORMATTER); static GString *_save_project (GESFormatter * formatter, GString * str, GESProject * project, GESTimeline * timeline, GError ** error, guint depth); 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, "Found 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) { gst_caps_unref (capsformat); 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); if (preset_properties) gst_structure_free (preset_properties); } 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; GESXmlFormatterPrivate *priv = _GET_PRIV (self); 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 == GES_TYPE_TIMELINE) { SubprojectData *subproj_data = g_new0 (SubprojectData, 1); const gchar *nid; priv->subproject = subproj_data; G_LOCK (uri_subprojects_map_lock); nid = g_hash_table_lookup (priv->subprojects_map, id); G_UNLOCK (uri_subprojects_map_lock); if (!nid) { subproj_data->id = id; subproj_data->fd = g_file_open_tmp ("XXXXXX.xges", &subproj_data->filename, error); if (subproj_data->fd == -1) { GST_ERROR_OBJECT (self, "Could not create subproject file for %s", id); return; } g_markup_parse_context_get_position (context, &subproj_data->start_line, &subproj_data->start_char); id = g_filename_to_uri (subproj_data->filename, NULL, NULL); G_LOCK (uri_subprojects_map_lock); g_hash_table_insert (priv->subprojects_map, g_strdup (subproj_data->id), (gchar *) id); G_UNLOCK (uri_subprojects_map_lock); GST_INFO_OBJECT (self, "Serialized subproject %sis now at: %s", subproj_data->id, id); } else { GST_DEBUG_OBJECT (self, "Subproject already exists: %s -> %s", id, nid); id = nid; subproj_data->start_line = -1; } } 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); if (extractable_type == GES_TYPE_URI_CLIP) { G_LOCK (uri_subprojects_map_lock); if (g_hash_table_contains (priv->subprojects_map, id)) { id = g_hash_table_lookup (priv->subprojects_map, id); GST_DEBUG_OBJECT (self, "Using subproject %s", id); } G_UNLOCK (uri_subprojects_map_lock); } 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); if (!props) goto wrong_properties; } 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; wrong_properties: gst_clear_caps (&caps); g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', Can not create properties: %s'", element_name, properties); 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, *deactivated_tracks_str; gchar **deactivated_tracks = NULL; 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, "deactivated-tracks", &deactivated_tracks_str, 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; if (deactivated_tracks_str) deactivated_tracks = g_strsplit (deactivated_tracks_str, " ", -1); ges_base_xml_formatter_add_layer (GES_BASE_XML_FORMATTER (self), extractable_type, priority, props, metadatas, deactivated_tracks, error); g_strfreev (deactivated_tracks); 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; GESXmlFormatterPrivate *priv = _GET_PRIV (self); 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; } G_LOCK (uri_subprojects_map_lock); if (g_hash_table_contains (priv->subprojects_map, asset_id)) { asset_id = g_hash_table_lookup (priv->subprojects_map, asset_id); GST_DEBUG_OBJECT (self, "Using subproject %s", asset_id); } G_UNLOCK (uri_subprojects_map_lock); 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); 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', 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); 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)); 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_new0 (GstTimedValue, 1); 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, *props = NULL; const gchar *track_id = NULL, *children_properties = NULL, *properties = NULL, *metadatas = 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, COLLECT_STR_OPT, "properties", &properties, COLLECT_STR_OPT, "metadatas", &metadatas, 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; } if (properties) { props = gst_structure_from_string (properties, NULL); if (props == NULL) goto wrong_properties; } ges_base_xml_formatter_add_source (GES_BASE_XML_FORMATTER (self), track_id, children_props, props, metadatas); done: if (children_props) gst_structure_free (children_props); if (props) gst_structure_free (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); goto done; wrong_properties: g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "element '%s', properties '%s', could no be deserialized", element_name, properties); goto done; } 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 (priv->subproject) { if (g_strcmp0 (element_name, "ges") == 0) { priv->subproject_depth += 1; } return; } 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, "ges") == 0) { } 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 gboolean _save_subproject_data (GESXmlFormatter * self, SubprojectData * subproj_data, gint subproject_end_line, gint subproject_end_char, GError ** error) { gsize size; gint line = 1, i; gboolean res = FALSE; gsize start = 0, end = 0; gchar *xml = GES_BASE_XML_FORMATTER (self)->xmlcontent; for (i = 0; xml[i] != '\0'; i++) { if (!start && line == subproj_data->start_line) { i += subproj_data->start_char - 1; start = i; } if (line == subproject_end_line) { end = i + subproject_end_char - 1; break; } if (xml[i] == '\n') line++; } g_assert (start && end); size = (end - start); GST_INFO_OBJECT (self, "Saving subproject %s from %d:%d(%" G_GSIZE_FORMAT ") to %d:%d(%" G_GSIZE_FORMAT ")", subproj_data->id, subproj_data->start_line, subproj_data->start_char, start, subproject_end_line, subproject_end_char, end); res = g_file_set_contents (subproj_data->filename, &xml[start], size, error); return res; } static void _parse_element_end (GMarkupParseContext * context, const gchar * element_name, gpointer self, GError ** error) { GESXmlFormatterPrivate *priv = _GET_PRIV (self); SubprojectData *subproj_data = priv->subproject; /*GESXmlFormatterPrivate *priv = _GET_PRIV (self); */ if (!g_strcmp0 (element_name, "ges")) { gint subproject_end_line, subproject_end_char; if (priv->subproject_depth) priv->subproject_depth -= 1; if (!subproj_data) { if (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); _GET_PRIV (self)->ges_opened = FALSE; } } else if (subproj_data->start_line != -1 && !priv->subproject_depth) { g_markup_parse_context_get_position (context, &subproject_end_line, &subproject_end_char); _save_subproject_data (GES_XML_FORMATTER (self), subproj_data, subproject_end_line, subproject_end_char, error); subproj_data->filename = NULL; g_close (subproj_data->fd, error); subproj_data->id = NULL; subproj_data->start_line = 0; subproj_data->start_char = 0; } if (!priv->subproject_depth) { g_clear_pointer (&priv->subproject, g_free); } } else if (!g_strcmp0 (element_name, "clip")) { if (!priv->subproject) ges_base_xml_formatter_end_current_clip (GES_BASE_XML_FORMATTER (self)); } } 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 string_add_indents (GString * str, guint depth, gboolean prepend) { gint i; for (i = 0; i < depth; i++) prepend ? g_string_prepend (str, " ") : g_string_append (str, " "); } static inline void string_append_with_depth (GString * str, const gchar * string, guint depth) { string_add_indents (str, depth, FALSE); g_string_append (str, string); } static inline void append_escaped (GString * str, gchar * tmpstr, guint depth) { string_append_with_depth (str, tmpstr, depth); g_free (tmpstr); } gboolean ges_util_can_serialize_spec (GParamSpec * spec) { if (!(spec->flags & G_PARAM_WRITABLE)) { GST_LOG ("%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_LOG ("%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_LOG ("%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_LOG ("%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_LOG ("We do not want to serialize the name of GstObjects."); return FALSE; } else if (G_PARAM_SPEC_VALUE_TYPE (spec) == G_TYPE_GTYPE) { GST_LOG ("%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, gint * ret_n_props, 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 (!ges_util_can_serialize_spec (spec)) continue; _init_value_from_spec_for_serialization (&val, spec); g_object_get_property (object, spec->name, &val); if (gst_value_compare (g_param_spec_get_default_value (spec), &val) == GST_VALUE_EQUAL) { GST_INFO ("Ignoring %s as it is using the default value", spec->name); goto next; } if (spec->value_type == GST_TYPE_CAPS) { gchar *caps_str; const GstCaps *caps = gst_value_get_caps (&val); caps_str = gst_caps_to_string (caps); gst_structure_set (structure, spec->name, G_TYPE_STRING, caps_str, NULL); g_free (caps_str); goto next; } gst_structure_set_value (structure, spec->name, &val); next: 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); if (ret_n_props) *ret_n_props = gst_structure_n_fields (structure); gst_structure_free (structure); return ret; } static void project_loaded_cb (GESProject * project, GESTimeline * timeline, SubprojectData * data) { g_main_loop_quit (data->ml); } static void error_loading_asset_cb (GESProject * project, GError * err, const gchar * unused_id, GType extractable_type, SubprojectData * data) { data->error = g_error_copy (err); g_main_loop_quit (data->ml); } static gboolean _save_subproject (GESXmlFormatter * self, GString * str, GESProject * project, GESAsset * subproject, GError ** error, guint depth) { GString *substr; GESTimeline *timeline; gchar *properties, *metas; GESXmlFormatterPrivate *priv = self->priv; GMainContext *context = g_main_context_get_thread_default (); const gchar *id = ges_asset_get_id (subproject); SubprojectData data = { 0, }; if (!g_strcmp0 (ges_asset_get_id (GES_ASSET (project)), id)) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Project %s trying to recurse into itself", id); return FALSE; } G_LOCK (uri_subprojects_map_lock); g_hash_table_insert (priv->subprojects_map, g_strdup (id), g_strdup (id)); G_UNLOCK (uri_subprojects_map_lock); timeline = GES_TIMELINE (ges_asset_extract (subproject, error)); if (!timeline) { return FALSE; } if (!context) context = g_main_context_default (); data.ml = g_main_loop_new (context, TRUE); g_signal_connect (subproject, "loaded", (GCallback) project_loaded_cb, &data); g_signal_connect (subproject, "error-loading-asset", (GCallback) error_loading_asset_cb, &data); g_main_loop_run (data.ml); g_signal_handlers_disconnect_by_func (subproject, project_loaded_cb, &data); g_signal_handlers_disconnect_by_func (subproject, error_loading_asset_cb, &data); g_main_loop_unref (data.ml); if (data.error) { g_propagate_error (error, data.error); return FALSE; } subproject = ges_extractable_get_asset (GES_EXTRACTABLE (timeline)); substr = g_string_new (NULL); properties = _serialize_properties (G_OBJECT (subproject), NULL, NULL); metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (subproject)); append_escaped (str, g_markup_printf_escaped (" \n", ges_asset_get_id (subproject), g_type_name (ges_asset_get_extractable_type (subproject)), properties, metas), depth); self->priv->min_version = MAX (self->priv->min_version, 6); g_free (properties); g_free (metas); depth += 4; GST_DEBUG_OBJECT (self, "Saving subproject %s (depth: %d)", ges_asset_get_id (subproject), depth / 4); if (!_save_project (GES_FORMATTER (self), substr, GES_PROJECT (subproject), timeline, error, depth)) { g_string_free (substr, TRUE); g_object_unref (subproject); goto err; } GST_DEBUG_OBJECT (self, "DONE Saving subproject %s", ges_asset_get_id (subproject)); depth -= 4; g_string_append (str, substr->str); g_string_free (substr, TRUE); string_append_with_depth (str, " \n", depth); err: g_object_unref (subproject); return TRUE; } static gint sort_assets (GESAsset * a, GESAsset * b) { if (GES_IS_PROJECT (a)) return -1; if (GES_IS_PROJECT (b)) return 1; return 0; } static void _serialize_streams (GESXmlFormatter * self, GString * str, GESUriClipAsset * asset, GError ** error, guint depth) { const GList *tmp, *streams = ges_uri_clip_asset_get_stream_assets (asset); for (tmp = streams; tmp; tmp = tmp->next) { gchar *properties, *metas, *capsstr; const gchar *id = ges_asset_get_id (tmp->data); GstDiscovererStreamInfo *sinfo = ges_uri_source_asset_get_stream_info (tmp->data); GstCaps *caps = gst_discoverer_stream_info_get_caps (sinfo); properties = _serialize_properties (tmp->data, NULL, NULL); metas = ges_meta_container_metas_to_string (tmp->data); capsstr = gst_caps_to_string (caps); append_escaped (str, g_markup_printf_escaped (" \n", id, g_type_name (ges_asset_get_extractable_type (tmp->data)), properties, metas, capsstr), depth); self->priv->min_version = MAX (self->priv->min_version, 6); g_free (metas); g_free (properties); g_free (capsstr); gst_caps_unref (caps); } } static inline gboolean _save_assets (GESXmlFormatter * self, GString * str, GESProject * project, GError ** error, guint depth) { gchar *properties, *metas; GESAsset *asset, *proxy; GList *assets, *tmp; const gchar *id; GESXmlFormatterPrivate *priv = self->priv; assets = ges_project_list_assets (project, GES_TYPE_EXTRACTABLE); for (tmp = g_list_sort (assets, (GCompareFunc) sort_assets); tmp; tmp = tmp->next) { asset = GES_ASSET (tmp->data); id = ges_asset_get_id (asset); if (GES_IS_PROJECT (asset)) { if (!_save_subproject (self, str, project, asset, error, depth)) return FALSE; continue; } if (ges_asset_get_extractable_type (asset) == GES_TYPE_URI_CLIP) { G_LOCK (uri_subprojects_map_lock); if (g_hash_table_contains (priv->subprojects_map, id)) { id = g_hash_table_lookup (priv->subprojects_map, id); GST_DEBUG_OBJECT (self, "Using subproject %s", id); } G_UNLOCK (uri_subprojects_map_lock); } properties = _serialize_properties (G_OBJECT (asset), NULL, NULL); metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (asset)); append_escaped (str, g_markup_printf_escaped (" subprojects_map, proxy_id)) { proxy_id = g_hash_table_lookup (priv->subprojects_map, proxy_id); GST_DEBUG_OBJECT (self, "Using subproject %s", id); } G_UNLOCK (uri_subprojects_map_lock); } append_escaped (str, g_markup_printf_escaped (" proxy-id='%s' ", proxy_id), depth); if (!g_list_find (assets, proxy)) { assets = g_list_append (assets, gst_object_ref (proxy)); if (!tmp->next) tmp->next = g_list_last (assets); } self->priv->min_version = MAX (self->priv->min_version, 3); } g_string_append (str, ">\n"); if (GES_IS_URI_CLIP_ASSET (asset)) { _serialize_streams (self, str, GES_URI_CLIP_ASSET (asset), error, depth); } string_append_with_depth (str, " \n", depth); g_free (properties); g_free (metas); } g_list_free_full (assets, gst_object_unref); return TRUE; } static inline void _save_tracks (GESXmlFormatter * self, GString * str, GESTimeline * timeline, guint depth) { 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, "caps", 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), depth); 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, guint depth) { 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 (ges_util_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), 0); 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, guint depth) { 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"), depth); } else GST_DEBUG ("control source not in [interpolation]"); gst_object_unref (source); } else GST_DEBUG ("Binding type not in [direct, direct-absolute]"); } } static inline void _save_effect (GString * str, guint clip_id, GESTrackElement * trackelement, GESTimeline * timeline, guint depth) { 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), NULL, "start", "duration", "locked", "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), depth); g_free (extractable_id); g_free (properties); g_free (metas); _save_children_properties (str, GES_TIMELINE_ELEMENT (trackelement), depth); append_escaped (str, g_markup_printf_escaped (">\n"), depth); _save_keyframes (str, trackelement, -1, depth); append_escaped (str, g_markup_printf_escaped (" \n"), depth); } static inline void _save_layer_track_activness (GESXmlFormatter * self, GESLayer * layer, GString * str, GESTimeline * timeline, guint depth) { guint nb_tracks = 0, i; GList *tmp, *tracks = ges_timeline_get_tracks (timeline); GArray *deactivated_tracks = g_array_new (TRUE, FALSE, sizeof (gint32)); for (tmp = tracks; tmp; tmp = tmp->next, nb_tracks++) { if (!ges_layer_get_active_for_track (layer, tmp->data)) g_array_append_val (deactivated_tracks, nb_tracks); } if (!deactivated_tracks->len) { g_string_append (str, ">\n"); goto done; } self->priv->min_version = MAX (self->priv->min_version, 7); g_string_append (str, " deactivated-tracks='"); for (i = 0; i < deactivated_tracks->len; i++) g_string_append_printf (str, "%d ", g_array_index (deactivated_tracks, gint, i)); g_string_append (str, "'>\n"); done: g_array_free (deactivated_tracks, TRUE); g_list_free_full (tracks, gst_object_unref); } static void _save_source (GESXmlFormatter * self, GString * str, GESTimelineElement * element, GESTimeline * timeline, GList * tracks, guint depth) { gint index, n_props; gboolean serialize; gchar *properties, *metas; if (!GES_IS_SOURCE (element)) return; g_object_get (element, "serialize", &serialize, NULL); if (!serialize) { GST_DEBUG_OBJECT (element, "Should not be serialized"); return; } index = g_list_index (tracks, ges_track_element_get_track (GES_TRACK_ELEMENT (element))); append_escaped (str, g_markup_printf_escaped (" priv->min_version = MAX (self->priv->min_version, 7); g_string_append_printf (str, "properties='%s' ", properties); } g_free (properties); metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (element)); g_string_append_printf (str, "metadatas='%s' ", metas); g_free (metas); _save_children_properties (str, element, depth); append_escaped (str, g_markup_printf_escaped (">\n"), depth); _save_keyframes (str, GES_TRACK_ELEMENT (element), index, depth); append_escaped (str, g_markup_printf_escaped (" \n"), depth); } static inline void _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline, guint depth) { 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), NULL, "priority", NULL); metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (layer)); append_escaped (str, g_markup_printf_escaped (" 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), NULL, "supported-formats", "rate", "in-point", "start", "duration", "max-duration", "priority", "vtype", "uri", NULL); extractable_id = ges_extractable_get_id (GES_EXTRACTABLE (clip)); if (GES_IS_URI_CLIP (clip)) { G_LOCK (uri_subprojects_map_lock); if (g_hash_table_contains (priv->subprojects_map, extractable_id)) { gchar *new_extractable_id = g_strdup (g_hash_table_lookup (priv->subprojects_map, extractable_id)); g_free (extractable_id); extractable_id = new_extractable_id; } G_UNLOCK (uri_subprojects_map_lock); } 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), depth); g_free (metas); if (GES_IS_TRANSITION_CLIP (clip)) { _save_children_properties (str, GES_TIMELINE_ELEMENT (clip), depth); 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, depth); } g_list_free (effects); tracks = ges_timeline_get_tracks (timeline); for (tmptrackelement = GES_CONTAINER_CHILDREN (clip); tmptrackelement; tmptrackelement = tmptrackelement->next) { _save_source (self, str, tmptrackelement->data, timeline, tracks, depth); } g_list_free_full (tracks, gst_object_unref); string_append_with_depth (str, " \n", depth); priv->nbelements++; } g_list_free_full (clips, (GDestroyNotify) gst_object_unref); string_append_with_depth (str, " \n", depth); } } static void _save_group (GESXmlFormatter * self, GString * str, GList ** seen_groups, GESGroup * group, guint depth) { 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)), depth); } } properties = _serialize_properties (G_OBJECT (group), NULL, NULL); metadatas = ges_meta_container_metas_to_string (GES_META_CONTAINER (group)); self->priv->min_version = MAX (self->priv->min_version, 5); string_add_indents (str, depth, FALSE); 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)); string_add_indents (str, depth, FALSE); g_string_append_printf (str, " \n", id, GES_TIMELINE_ELEMENT_NAME (tmp->data)); } string_append_with_depth (str, " \n", depth); } static void _save_groups (GESXmlFormatter * self, GString * str, GESTimeline * timeline, guint depth) { GList *tmp; GList *seen_groups = NULL; string_append_with_depth (str, " \n", depth); for (tmp = ges_timeline_get_groups (timeline); tmp; tmp = tmp->next) { _save_group (self, str, &seen_groups, tmp->data, depth); } g_list_free (seen_groups); string_append_with_depth (str, " \n", depth); } static inline void _save_timeline (GESXmlFormatter * self, GString * str, GESTimeline * timeline, guint depth) { gchar *properties = NULL, *metas = NULL; properties = _serialize_properties (G_OBJECT (timeline), NULL, "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), depth); _save_tracks (self, str, timeline, depth); _save_layers (self, str, timeline, depth); _save_groups (self, str, timeline, depth); string_append_with_depth (str, " \n", depth); g_free (properties); g_free (metas); } static void _save_stream_profiles (GESXmlFormatter * self, GString * str, GstEncodingProfile * sprof, const gchar * profilename, guint id, guint depth) { gchar *tmpc; GstCaps *tmpcaps; GstStructure *properties; 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), depth); 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), depth); description = gst_encoding_profile_get_description (sprof); if (description) append_escaped (str, g_markup_printf_escaped ("description='%s' ", description), depth); preset = gst_encoding_profile_get_preset (sprof); if (preset) { append_escaped (str, g_markup_printf_escaped ("preset='%s' ", preset), depth); } properties = gst_encoding_profile_get_element_properties (sprof); if (properties) { gchar *props_str = gst_structure_to_string (properties); append_escaped (str, g_markup_printf_escaped ("preset-properties='%s' ", props_str), depth); g_free (props_str); gst_structure_free (properties); } preset_name = gst_encoding_profile_get_preset_name (sprof); if (preset_name) append_escaped (str, g_markup_printf_escaped ("preset-name='%s' ", preset_name), depth); 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), depth); 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)), depth); } g_string_append (str, "/>\n"); } static inline void _save_encoding_profiles (GESXmlFormatter * self, GString * str, GESProject * project, guint depth) { GstCaps *profformat; GstStructure *properties; 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, depth); } } append_escaped (str, g_markup_printf_escaped (" \n"), depth); } g_list_free (profiles); } static GString * _save (GESFormatter * formatter, GESTimeline * timeline, GError ** error) { GString *str; GESProject *project; GESXmlFormatterPrivate *priv = _GET_PRIV (formatter); priv->min_version = 1; project = formatter->project; str = priv->str = g_string_new (NULL); return _save_project (formatter, str, project, timeline, error, 0); } static GString * _save_project (GESFormatter * formatter, GString * str, GESProject * project, GESTimeline * timeline, GError ** error, guint depth) { gchar *projstr = NULL, *version; gchar *properties = NULL, *metas = NULL; GESXmlFormatter *self = GES_XML_FORMATTER (formatter); GESXmlFormatterPrivate *priv = _GET_PRIV (formatter); properties = _serialize_properties (G_OBJECT (project), NULL, NULL); metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (project)); append_escaped (str, g_markup_printf_escaped (" \n", properties, metas), depth); g_free (properties); g_free (metas); string_append_with_depth (str, " \n", depth); _save_encoding_profiles (GES_XML_FORMATTER (formatter), str, project, depth); string_append_with_depth (str, " \n", depth); string_append_with_depth (str, " \n", depth); if (!_save_assets (self, str, project, error, depth)) { g_string_free (str, TRUE); return NULL; } string_append_with_depth (str, " \n", depth); _save_timeline (self, str, timeline, depth); string_append_with_depth (str, " \n", depth); string_append_with_depth (str, "\n", depth); projstr = g_strdup_printf ("\n", API_VERSION, priv->min_version); g_string_prepend (str, projstr); string_add_indents (str, depth, TRUE); 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; } static void _setup_subprojects_map (GESXmlFormatterPrivate * priv, const gchar * uri) { GHashTable *subprojects_map; G_LOCK (uri_subprojects_map_lock); if (!uri_subprojects_map) uri_subprojects_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_hash_table_unref); subprojects_map = g_hash_table_lookup (uri_subprojects_map, uri); if (!subprojects_map) { subprojects_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); g_hash_table_insert (uri_subprojects_map, g_strdup (uri), subprojects_map); } priv->subprojects_map = subprojects_map; G_UNLOCK (uri_subprojects_map_lock); } void ges_xml_formatter_deinit (void) { GST_DEBUG ("Deinit"); G_LOCK (uri_subprojects_map_lock); if (uri_subprojects_map) { g_hash_table_unref (uri_subprojects_map); uri_subprojects_map = NULL; } G_UNLOCK (uri_subprojects_map_lock); } static gboolean _save_to_uri (GESFormatter * formatter, GESTimeline * timeline, const gchar * uri, gboolean overwrite, GError ** error) { _setup_subprojects_map (_GET_PRIV (formatter), uri); return GES_FORMATTER_CLASS (parent_class)->save_to_uri (formatter, timeline, uri, overwrite, error); } static gboolean _can_load_uri (GESFormatter * formatter, const gchar * uri, GError ** error) { _setup_subprojects_map (_GET_PRIV (formatter), uri); return GES_FORMATTER_CLASS (parent_class)->can_load_uri (formatter, uri, error); } static gboolean _load_from_uri (GESFormatter * formatter, GESTimeline * timeline, const gchar * uri, GError ** error) { _setup_subprojects_map (_GET_PRIV (formatter), uri); return GES_FORMATTER_CLASS (parent_class)->load_from_uri (formatter, timeline, uri, error); } /*********************************************** * * * 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; GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (self_class); basexmlformatter_class = GES_BASE_XML_FORMATTER_CLASS (self_class); formatter_klass->save_to_uri = _save_to_uri; formatter_klass->can_load_uri = _can_load_uri; formatter_klass->load_from_uri = _load_from_uri; 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/xges", VERSION, GST_RANK_PRIMARY); basexmlformatter_class->save = _save; } #undef COLLECT_STR_OPT