gstreamer/ges/ges-xml-formatter.c
Thibault Saunier db5c62ad4c formatter: Fix saving/loading project with clip speed rate control
We need to ensure that clips duration is set after time effects are
added and we now need to serialize effects inpoints and max duration.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
2020-05-25 11:20:38 +01:00

2235 lines
71 KiB
C

/* Gstreamer Editing Services
*
* Copyright (C) <2012> Thibault Saunier <thibault.saunier@collabora.com>
*
* 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 <string.h>
#include <errno.h>
#include <locale.h>
#include "ges.h"
#include <glib/gstdio.h>
#include "ges-internal.h"
#define parent_class ges_xml_formatter_parent_class
#define API_VERSION 0
#define MINOR_VERSION 7
#define VERSION 0.7
#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 '<ges>' 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 '<project>' 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_malloc0 (sizeof (SubprojectData));
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);
}
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, *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;
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,
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);
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 *subproject_content = NULL;
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);
subproject_content = g_malloc (sizeof (gchar) * size);
memcpy (subproject_content, &xml[start], end - start);
subproject_content[end - start] = '\0';
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, subproject_content, -1,
error);
g_free (subproject_content);
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);
}
static inline gboolean
_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 (!_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);
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
(" <asset id='%s' extractable-type-name='%s' properties='%s' metadatas='%s'>\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);
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, " </asset>\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
(" <stream-info id='%s' extractable-type-name='%s' properties='%s' metadatas='%s' caps='%s'/>\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
(" <asset id='%s' extractable-type-name='%s' properties='%s' metadatas='%s' ",
id, g_type_name (ges_asset_get_extractable_type (asset)),
properties, metas), depth);
/*TODO Save the whole list of proxies */
proxy = ges_asset_get_proxy (asset);
if (proxy) {
const gchar *proxy_id = ges_asset_get_id (proxy);
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, 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, " </asset>\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
(" <track caps='%s' track-type='%i' track-id='%i' properties='%s' metadatas='%s'/>\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 (_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
(" <binding type='%s' source_type='interpolation' property='%s'",
absolute ? "direct-absolute" : "direct", (gchar *) key), depth);
g_object_get (source, "mode", &mode, NULL);
append_escaped (str, g_markup_printf_escaped (" mode='%d'", mode),
depth);
append_escaped (str, g_markup_printf_escaped (" track_id='%d'", index),
depth);
append_escaped (str, g_markup_printf_escaped (" values ='"), depth);
timed_values =
gst_timed_value_control_source_get_all
(GST_TIMED_VALUE_CONTROL_SOURCE (source));
for (tmp = timed_values; tmp; tmp = tmp->next) {
gchar strbuf[G_ASCII_DTOSTR_BUF_SIZE];
GstTimedValue *value;
value = (GstTimedValue *) tmp->data;
append_escaped (str, g_markup_printf_escaped (" %" G_GUINT64_FORMAT
":%s ", value->timestamp, g_ascii_dtostr (strbuf,
G_ASCII_DTOSTR_BUF_SIZE, value->value)), depth);
}
g_list_free (timed_values);
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 (" <effect asset-id='%s' clip-id='%u'"
" type-name='%s' track-type='%i' track-id='%i' properties='%s' metadatas='%s'",
extractable_id, clip_id,
g_type_name (G_OBJECT_TYPE (trackelement)), tck->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 (" </effect>\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;
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
(" <source track-id='%i' ", index), depth);
properties = _serialize_properties (G_OBJECT (element), &n_props,
"in-point", "priority", "start", "duration", "track", "track-type"
"uri", "name", "max-duration", NULL);
/* Try as possible to allow older versions of GES to load the files */
if (n_props) {
self->priv->min_version = MAX (self->priv->min_version, 7);
g_string_append_printf (str, "properties='%s' ", properties);
}
g_free (properties);
_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 (" </source>\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
(" <layer priority='%i' properties='%s' metadatas='%s'",
priority, properties, metas), depth);
g_free (properties);
g_free (metas);
_save_layer_track_activness (self, layer, str, timeline, depth);
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), 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))
extractable_id =
g_strdup (g_hash_table_lookup (priv->subprojects_map,
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 (" <clip id='%i' asset-id='%s'"
" type-name='%s' layer-priority='%i' track-types='%i' start='%"
G_GUINT64_FORMAT "' duration='%" G_GUINT64_FORMAT "' inpoint='%"
G_GUINT64_FORMAT "' rate='%d' properties='%s' metadatas='%s'",
priv->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, " </clip>\n", depth);
priv->nbelements++;
}
g_list_free_full (clips, (GDestroyNotify) gst_object_unref);
string_append_with_depth (str, " </layer>\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,
" <group id='%d' properties='%s' metadatas='%s'>\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, " <child id='%d' name='%s'/>\n", id,
GES_TIMELINE_ELEMENT_NAME (tmp->data));
}
string_append_with_depth (str, " </group>\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, " <groups>\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, " </groups>\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
(" <timeline properties='%s' metadatas='%s'>\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, " </timeline>\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;
const gchar *preset, *preset_name, *name, *description;
append_escaped (str,
g_markup_printf_escaped
(" <stream-profile parent='%s' id='%d' type='%s' "
"presence='%d' ", profilename, id,
gst_encoding_profile_get_type_nick (sprof),
gst_encoding_profile_get_presence (sprof)), depth);
if (!gst_encoding_profile_is_enabled (sprof)) {
append_escaped (str, g_strdup ("enabled='0' "), depth);
self->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) {
GstElement *encoder;
append_escaped (str, g_markup_printf_escaped ("preset='%s' ", preset),
depth);
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, NULL);
append_escaped (str, g_markup_printf_escaped ("preset-properties='%s' ",
settings), depth);
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), 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;
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
(" <encoding-profile name='%s' description='%s' type='%s' ",
profname, profdesc, proftype), depth);
if (profpreset) {
GstElement *element;
append_escaped (str, g_markup_printf_escaped ("preset='%s' ",
profpreset), depth);
if (GST_IS_ENCODING_CONTAINER_PROFILE (prof)) {
element = get_element_for_encoding_profile (prof,
GST_ELEMENT_FACTORY_TYPE_MUXER);
} else {
element = get_element_for_encoding_profile (prof,
GST_ELEMENT_FACTORY_TYPE_ENCODER);
}
if (element) {
if (GST_IS_PRESET (element) &&
gst_preset_load_preset (GST_PRESET (element), profpreset)) {
gchar *settings =
_serialize_properties (G_OBJECT (element), NULL, NULL);
append_escaped (str,
g_markup_printf_escaped ("preset-properties='%s' ", settings),
depth);
g_free (settings);
}
gst_object_unref (element);
}
}
if (profpresetname)
append_escaped (str, g_markup_printf_escaped ("preset-name='%s' ",
profpresetname), depth);
profformat = gst_encoding_profile_get_format (prof);
if (profformat) {
gchar *format = gst_caps_to_string (profformat);
append_escaped (str, g_markup_printf_escaped ("format='%s' ", format),
depth);
g_free (format);
gst_caps_unref (profformat);
}
g_string_append (str, ">\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 (" </encoding-profile>\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 (" <project properties='%s' metadatas='%s'>\n",
properties, metas), depth);
g_free (properties);
g_free (metas);
string_append_with_depth (str, " <encoding-profiles>\n", depth);
_save_encoding_profiles (GES_XML_FORMATTER (formatter), str, project, depth);
string_append_with_depth (str, " </encoding-profiles>\n", depth);
string_append_with_depth (str, " <ressources>\n", depth);
if (!_save_assets (self, str, project, error, depth)) {
g_string_free (str, TRUE);
return NULL;
}
string_append_with_depth (str, " </ressources>\n", depth);
_save_timeline (self, str, timeline, depth);
string_append_with_depth (str, " </project>\n", depth);
string_append_with_depth (str, "</ges>\n", depth);
projstr = g_strdup_printf ("<ges version='%i.%i'>\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