gstreamer/ges/ges-xml-formatter.c
Thibault Saunier 241e809a81 ges: Enhance xges format versioning
Summary:
Handle the fact that some new features can be added and that means
generated files will not be fully understandable by older versions of
the formatter.

Make sure that we set the format version to 0.2 when we serialize the
GstEncodingProfile.enabled property.

Add some tests around that.

+ Fix a minor bug in the test-utils
+ Add a meta on the projects to tell in what format version a project
  has been serialized/parsed back

API:
  GES_META_FORMAT_VERSION

Depends on D178

Reviewers: Mathieu_Du

Differential Revision: http://phabricator.freedesktop.org/D184
2015-05-18 21:27:48 +02:00

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