mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-07-04 13:45:52 +00:00
The out-point, which is an internal time, is used instead of the duration for determining the control binding value at the end of the element. Also, allow the user to switch off the auto-clamping of control sources if they are not desired. And allow them to clamp specific control sources individually. Also, fix a lot of memory leaks related to control sources. In particular, releasing the extra ref gained by source in g_object_get (binding, "control-source", &source, NULL); Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
2234 lines
71 KiB
C
2234 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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",
|
|
"in-point", "duration", "locked", "max-duration", "name", "priority",
|
|
NULL);
|
|
metas =
|
|
ges_meta_container_metas_to_string (GES_META_CONTAINER (trackelement));
|
|
extractable_id = ges_extractable_get_id (GES_EXTRACTABLE (trackelement));
|
|
append_escaped (str,
|
|
g_markup_printf_escaped (" <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
|