command-line-formatter: Stop uselessly looping over options
[platform/upstream/gstreamer.git] / ges / ges-xml-formatter.c
1 /* Gstreamer Editing Services
2  *
3  * Copyright (C) <2012> Thibault Saunier <thibault.saunier@collabora.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #undef VERSION
23 #endif
24
25
26 /* TODO Determine error codes numbers */
27
28 #include <string.h>
29 #include <errno.h>
30 #include <locale.h>
31
32 #include "ges.h"
33 #include <glib/gstdio.h>
34 #include "ges-internal.h"
35
36 #define parent_class ges_xml_formatter_parent_class
37 #define API_VERSION 0
38 #define MINOR_VERSION 7
39 #define VERSION 0.7
40
41 #define COLLECT_STR_OPT (G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL)
42
43 #define _GET_PRIV(o) (((GESXmlFormatter*)o)->priv)
44
45 typedef struct
46 {
47   const gchar *id;
48   gint start_line;
49   gint start_char;
50   gint fd;
51   gchar *filename;
52   GError *error;
53   GMainLoop *ml;
54 } SubprojectData;
55
56 struct _GESXmlFormatterPrivate
57 {
58   gboolean ges_opened;
59   gboolean project_opened;
60
61   GString *str;
62
63   GHashTable *element_id;
64   GHashTable *subprojects_map;
65   SubprojectData *subproject;
66   gint subproject_depth;
67
68   guint nbelements;
69
70   guint min_version;
71 };
72
73 G_LOCK_DEFINE_STATIC (uri_subprojects_map_lock);
74 /* { project_uri: { subproject_uri: new_suproject_uri}} */
75 static GHashTable *uri_subprojects_map = NULL;
76
77 G_DEFINE_TYPE_WITH_PRIVATE (GESXmlFormatter, ges_xml_formatter,
78     GES_TYPE_BASE_XML_FORMATTER);
79
80 static GString *_save_project (GESFormatter * formatter, GString * str,
81     GESProject * project, GESTimeline * timeline, GError ** error, guint depth);
82
83 static inline void
84 _parse_ges_element (GMarkupParseContext * context, const gchar * element_name,
85     const gchar ** attribute_names, const gchar ** attribute_values,
86     GESXmlFormatter * self, GError ** error)
87 {
88   guint api_version;
89   const gchar *version, *properties;
90
91   gchar **split_version = NULL;
92
93   if (g_strcmp0 (element_name, "ges")) {
94     g_set_error (error, G_MARKUP_ERROR,
95         G_MARKUP_ERROR_INVALID_CONTENT,
96         "Found element '%s', Missing '<ges>' element'", element_name);
97     return;
98   }
99
100   if (!g_markup_collect_attributes (element_name, attribute_names,
101           attribute_values, error, G_MARKUP_COLLECT_STRING, "version", &version,
102           COLLECT_STR_OPT, "properties", &properties,
103           G_MARKUP_COLLECT_INVALID)) {
104     return;
105   }
106
107   split_version = g_strsplit (version, ".", 2);
108   if (split_version[1] == NULL)
109     goto failed;
110
111   errno = 0;
112   api_version = g_ascii_strtoull (split_version[0], NULL, 10);
113   if (errno || api_version != API_VERSION)
114     goto stroull_failed;
115
116   self->priv->min_version = g_ascii_strtoull (split_version[1], NULL, 10);
117   if (self->priv->min_version > MINOR_VERSION)
118     goto failed;
119
120   _GET_PRIV (self)->ges_opened = TRUE;
121   g_strfreev (split_version);
122   return;
123
124 failed:
125   g_set_error (error, G_MARKUP_ERROR,
126       G_MARKUP_ERROR_INVALID_CONTENT,
127       "element '%s', %s wrong version'", element_name, version);
128   if (split_version)
129     g_strfreev (split_version);
130
131   return;
132
133 stroull_failed:
134   GST_WARNING_OBJECT (self, "Error while strtoull: %s", g_strerror (errno));
135   goto failed;
136 }
137
138 static inline void
139 _parse_project (GMarkupParseContext * context, const gchar * element_name,
140     const gchar ** attribute_names, const gchar ** attribute_values,
141     GESXmlFormatter * self, GError ** error)
142 {
143   const gchar *metadatas = NULL, *properties;
144   GESXmlFormatterPrivate *priv = _GET_PRIV (self);
145
146   if (g_strcmp0 (element_name, "project")) {
147     g_set_error (error, G_MARKUP_ERROR,
148         G_MARKUP_ERROR_INVALID_CONTENT,
149         "Found element '%s', Missing '<project>' element'", element_name);
150   } else {
151     priv->project_opened = TRUE;
152     if (!g_markup_collect_attributes (element_name, attribute_names,
153             attribute_values, error,
154             COLLECT_STR_OPT, "properties", &properties,
155             COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID))
156       return;
157
158     if (GES_FORMATTER (self)->project && metadatas)
159       ges_meta_container_add_metas_from_string (GES_META_CONTAINER
160           (GES_FORMATTER (self)->project), metadatas);
161
162   }
163 }
164
165 static inline void
166 _parse_encoding_profile (GMarkupParseContext * context,
167     const gchar * element_name, const gchar ** attribute_names,
168     const gchar ** attribute_values, GESXmlFormatter * self, GError ** error)
169 {
170   GstCaps *capsformat = NULL;
171   GstStructure *preset_properties = NULL;
172   const gchar *name, *description, *type, *preset = NULL,
173       *str_preset_properties = NULL, *preset_name = NULL, *format;
174
175   if (!g_markup_collect_attributes (element_name, attribute_names,
176           attribute_values, error,
177           G_MARKUP_COLLECT_STRING, "name", &name,
178           G_MARKUP_COLLECT_STRING, "description", &description,
179           G_MARKUP_COLLECT_STRING, "type", &type,
180           COLLECT_STR_OPT, "preset", &preset,
181           COLLECT_STR_OPT, "preset-properties", &str_preset_properties,
182           COLLECT_STR_OPT, "preset-name", &preset_name,
183           COLLECT_STR_OPT, "format", &format, G_MARKUP_COLLECT_INVALID))
184     return;
185
186   if (format)
187     capsformat = gst_caps_from_string (format);
188
189   if (str_preset_properties) {
190     preset_properties = gst_structure_from_string (str_preset_properties, NULL);
191     if (preset_properties == NULL) {
192       gst_caps_unref (capsformat);
193       g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
194           "element '%s', Wrong preset-properties format.", element_name);
195       return;
196     }
197   }
198
199   ges_base_xml_formatter_add_encoding_profile (GES_BASE_XML_FORMATTER (self),
200       type, NULL, name, description, capsformat, preset, preset_properties,
201       preset_name, 0, 0, NULL, 0, FALSE, NULL, TRUE, error);
202
203   if (preset_properties)
204     gst_structure_free (preset_properties);
205 }
206
207 static inline void
208 _parse_stream_profile (GMarkupParseContext * context,
209     const gchar * element_name, const gchar ** attribute_names,
210     const gchar ** attribute_values, GESXmlFormatter * self, GError ** error)
211 {
212   gboolean variableframerate = FALSE, enabled = TRUE;
213   guint id = 0, presence = 0, pass = 0;
214   GstCaps *format_caps = NULL, *restriction_caps = NULL;
215   GstStructure *preset_properties = NULL;
216   const gchar *parent, *strid, *type, *strpresence, *format = NULL,
217       *name = NULL, *description = NULL, *preset,
218       *str_preset_properties = NULL, *preset_name = NULL, *restriction = NULL,
219       *strpass = NULL, *strvariableframerate = NULL, *strenabled = NULL;
220
221   /* FIXME Looks like there is a bug in that function, if we put the parent
222    * at the beginning it set %NULL and not the real value... :/ */
223   if (!g_markup_collect_attributes (element_name, attribute_names,
224           attribute_values, error,
225           G_MARKUP_COLLECT_STRING, "id", &strid,
226           G_MARKUP_COLLECT_STRING, "type", &type,
227           G_MARKUP_COLLECT_STRING, "presence", &strpresence,
228           COLLECT_STR_OPT, "format", &format,
229           COLLECT_STR_OPT, "name", &name,
230           COLLECT_STR_OPT, "description", &description,
231           COLLECT_STR_OPT, "preset", &preset,
232           COLLECT_STR_OPT, "preset-properties", &str_preset_properties,
233           COLLECT_STR_OPT, "preset-name", &preset_name,
234           COLLECT_STR_OPT, "restriction", &restriction,
235           COLLECT_STR_OPT, "pass", &strpass,
236           COLLECT_STR_OPT, "variableframerate", &strvariableframerate,
237           COLLECT_STR_OPT, "enabled", &strenabled,
238           G_MARKUP_COLLECT_STRING, "parent", &parent, G_MARKUP_COLLECT_INVALID))
239     return;
240
241   errno = 0;
242   id = g_ascii_strtoll (strid, NULL, 10);
243   if (errno)
244     goto convertion_failed;
245
246   if (strpresence) {
247     presence = g_ascii_strtoll (strpresence, NULL, 10);
248     if (errno)
249       goto convertion_failed;
250   }
251
252   if (str_preset_properties) {
253     preset_properties = gst_structure_from_string (str_preset_properties, NULL);
254     if (preset_properties == NULL)
255       goto convertion_failed;
256   }
257
258   if (strpass) {
259     pass = g_ascii_strtoll (strpass, NULL, 10);
260     if (errno)
261       goto convertion_failed;
262   }
263
264   if (strvariableframerate) {
265     variableframerate = g_ascii_strtoll (strvariableframerate, NULL, 10);
266     if (errno)
267       goto convertion_failed;
268   }
269
270   if (strenabled) {
271     enabled = g_ascii_strtoll (strenabled, NULL, 10);
272     if (errno)
273       goto convertion_failed;
274   }
275
276   if (format)
277     format_caps = gst_caps_from_string (format);
278
279   if (restriction)
280     restriction_caps = gst_caps_from_string (restriction);
281
282   ges_base_xml_formatter_add_encoding_profile (GES_BASE_XML_FORMATTER (self),
283       type, parent, name, description, format_caps, preset, preset_properties,
284       preset_name, id, presence, restriction_caps, pass, variableframerate,
285       NULL, enabled, error);
286
287   if (preset_properties)
288     gst_structure_free (preset_properties);
289
290   return;
291
292 convertion_failed:
293   g_set_error (error, G_MARKUP_ERROR,
294       G_MARKUP_ERROR_INVALID_CONTENT,
295       "element '%s', Wrong property type, error: %s'", element_name,
296       g_strerror (errno));
297   return;
298 }
299
300 static inline void
301 _parse_timeline (GMarkupParseContext * context, const gchar * element_name,
302     const gchar ** attribute_names, const gchar ** attribute_values,
303     GESXmlFormatter * self, GError ** error)
304 {
305   const gchar *metadatas = NULL, *properties = NULL;
306   GESTimeline *timeline = GES_FORMATTER (self)->timeline;
307
308   if (!g_markup_collect_attributes (element_name, attribute_names,
309           attribute_values, error,
310           COLLECT_STR_OPT, "properties", &properties,
311           COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID))
312     return;
313
314   if (timeline == NULL)
315     return;
316
317   ges_base_xml_formatter_set_timeline_properties (GES_BASE_XML_FORMATTER (self),
318       timeline, properties, metadatas);
319 }
320
321 static inline void
322 _parse_asset (GMarkupParseContext * context, const gchar * element_name,
323     const gchar ** attribute_names, const gchar ** attribute_values,
324     GESXmlFormatter * self, GError ** error)
325 {
326   GType extractable_type;
327   const gchar *id, *extractable_type_name, *metadatas = NULL, *properties =
328       NULL, *proxy_id = NULL;
329   GESXmlFormatterPrivate *priv = _GET_PRIV (self);
330
331   if (!g_markup_collect_attributes (element_name, attribute_names,
332           attribute_values, error, G_MARKUP_COLLECT_STRING, "id", &id,
333           G_MARKUP_COLLECT_STRING, "extractable-type-name",
334           &extractable_type_name,
335           COLLECT_STR_OPT, "properties", &properties,
336           COLLECT_STR_OPT, "metadatas", &metadatas,
337           COLLECT_STR_OPT, "proxy-id", &proxy_id, G_MARKUP_COLLECT_INVALID))
338     return;
339
340   extractable_type = g_type_from_name (extractable_type_name);
341   if (extractable_type == GES_TYPE_TIMELINE) {
342     SubprojectData *subproj_data = g_malloc0 (sizeof (SubprojectData));
343     const gchar *nid;
344
345     priv->subproject = subproj_data;
346     G_LOCK (uri_subprojects_map_lock);
347     nid = g_hash_table_lookup (priv->subprojects_map, id);
348     G_UNLOCK (uri_subprojects_map_lock);
349
350     if (!nid) {
351       subproj_data->id = id;
352       subproj_data->fd =
353           g_file_open_tmp ("XXXXXX.xges", &subproj_data->filename, error);
354       if (subproj_data->fd == -1) {
355         GST_ERROR_OBJECT (self, "Could not create subproject file for %s", id);
356         return;
357       }
358       g_markup_parse_context_get_position (context, &subproj_data->start_line,
359           &subproj_data->start_char);
360       id = g_filename_to_uri (subproj_data->filename, NULL, NULL);
361       G_LOCK (uri_subprojects_map_lock);
362       g_hash_table_insert (priv->subprojects_map, g_strdup (subproj_data->id),
363           (gchar *) id);
364       G_UNLOCK (uri_subprojects_map_lock);
365       GST_INFO_OBJECT (self, "Serialized subproject %sis now at: %s",
366           subproj_data->id, id);
367     } else {
368       GST_DEBUG_OBJECT (self, "Subproject already exists: %s -> %s", id, nid);
369       id = nid;
370       subproj_data->start_line = -1;
371     }
372   }
373
374   if (extractable_type == G_TYPE_NONE)
375     g_set_error (error, G_MARKUP_ERROR,
376         G_MARKUP_ERROR_INVALID_CONTENT,
377         "element '%s' invalid extractable_type %s'",
378         element_name, extractable_type_name);
379   else if (!g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE))
380     g_set_error (error, G_MARKUP_ERROR,
381         G_MARKUP_ERROR_INVALID_CONTENT,
382         "element '%s', %s not an extractable_type'",
383         element_name, extractable_type_name);
384   else {
385     GstStructure *props = NULL;
386     if (properties)
387       props = gst_structure_from_string (properties, NULL);
388
389     if (extractable_type == GES_TYPE_URI_CLIP) {
390       G_LOCK (uri_subprojects_map_lock);
391       if (g_hash_table_contains (priv->subprojects_map, id)) {
392         id = g_hash_table_lookup (priv->subprojects_map, id);
393
394         GST_DEBUG_OBJECT (self, "Using subproject %s", id);
395       }
396       G_UNLOCK (uri_subprojects_map_lock);
397     }
398
399     ges_base_xml_formatter_add_asset (GES_BASE_XML_FORMATTER (self), id,
400         extractable_type, props, metadatas, proxy_id, error);
401     if (props)
402       gst_structure_free (props);
403   }
404 }
405
406
407 static inline void
408 _parse_track (GMarkupParseContext * context, const gchar * element_name,
409     const gchar ** attribute_names, const gchar ** attribute_values,
410     GESXmlFormatter * self, GError ** error)
411 {
412   GstCaps *caps;
413   GESTrackType track_type;
414   GstStructure *props = NULL;
415   const gchar *strtrack_type, *strcaps, *strtrack_id, *metadatas =
416       NULL, *properties = NULL;
417
418   if (!g_markup_collect_attributes (element_name, attribute_names,
419           attribute_values, error,
420           G_MARKUP_COLLECT_STRING, "track-type", &strtrack_type,
421           G_MARKUP_COLLECT_STRING, "track-id", &strtrack_id,
422           COLLECT_STR_OPT, "properties", &properties,
423           COLLECT_STR_OPT, "metadatas", &metadatas,
424           G_MARKUP_COLLECT_STRING, "caps", &strcaps, G_MARKUP_COLLECT_INVALID))
425     return;
426
427   if ((caps = gst_caps_from_string (strcaps)) == NULL)
428     goto wrong_caps;
429
430   errno = 0;
431   track_type = g_ascii_strtoll (strtrack_type, NULL, 10);
432   if (errno)
433     goto convertion_failed;
434
435   if (properties) {
436     props = gst_structure_from_string (properties, NULL);
437   }
438
439   ges_base_xml_formatter_add_track (GES_BASE_XML_FORMATTER (self), track_type,
440       caps, strtrack_id, props, metadatas, error);
441
442   if (props)
443     gst_structure_free (props);
444
445   gst_caps_unref (caps);
446
447   return;
448
449 wrong_caps:
450   g_set_error (error, G_MARKUP_ERROR,
451       G_MARKUP_ERROR_INVALID_CONTENT,
452       "element '%s', Can not create caps: %s'", element_name, strcaps);
453   return;
454
455 convertion_failed:
456   gst_caps_unref (caps);
457   g_set_error (error, G_MARKUP_ERROR,
458       G_MARKUP_ERROR_INVALID_CONTENT,
459       "element '%s', Wrong property type, error: %s'", element_name,
460       g_strerror (errno));
461   return;
462
463 }
464
465 static inline void
466 _parse_layer (GMarkupParseContext * context, const gchar * element_name,
467     const gchar ** attribute_names, const gchar ** attribute_values,
468     GESXmlFormatter * self, GError ** error)
469 {
470   GstStructure *props = NULL;
471   guint priority;
472   GType extractable_type = G_TYPE_NONE;
473   const gchar *metadatas = NULL, *properties = NULL, *strprio = NULL,
474       *extractable_type_name, *deactivated_tracks_str;
475
476   gchar **deactivated_tracks = NULL;
477
478   if (!g_markup_collect_attributes (element_name, attribute_names,
479           attribute_values, error,
480           G_MARKUP_COLLECT_STRING, "priority", &strprio,
481           COLLECT_STR_OPT, "extractable-type-name", &extractable_type_name,
482           COLLECT_STR_OPT, "properties", &properties,
483           COLLECT_STR_OPT, "deactivated-tracks", &deactivated_tracks_str,
484           COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID))
485     return;
486
487   if (extractable_type_name) {
488     extractable_type = g_type_from_name (extractable_type_name);
489     if (extractable_type == G_TYPE_NONE) {
490       g_set_error (error, G_MARKUP_ERROR,
491           G_MARKUP_ERROR_INVALID_CONTENT,
492           "element '%s' invalid extractable_type %s'",
493           element_name, extractable_type_name);
494
495       return;
496     } else if (!g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE)) {
497       g_set_error (error, G_MARKUP_ERROR,
498           G_MARKUP_ERROR_INVALID_CONTENT,
499           "element '%s', %s not an extractable_type'",
500           element_name, extractable_type_name);
501
502       return;
503     }
504   }
505
506   if (properties) {
507     props = gst_structure_from_string (properties, NULL);
508     if (props == NULL)
509       goto wrong_properties;
510   }
511
512   errno = 0;
513   priority = g_ascii_strtoll (strprio, NULL, 10);
514   if (errno)
515     goto convertion_failed;
516
517   if (deactivated_tracks_str)
518     deactivated_tracks = g_strsplit (deactivated_tracks_str, " ", -1);
519
520   ges_base_xml_formatter_add_layer (GES_BASE_XML_FORMATTER (self),
521       extractable_type, priority, props, metadatas, deactivated_tracks, error);
522
523   g_strfreev (deactivated_tracks);
524
525 done:
526   if (props)
527     gst_structure_free (props);
528
529   return;
530
531 convertion_failed:
532   g_set_error (error, G_MARKUP_ERROR,
533       G_MARKUP_ERROR_INVALID_CONTENT,
534       "element '%s', Wrong property type, error: %s'", element_name,
535       g_strerror (errno));
536   goto done;
537
538 wrong_properties:
539   g_set_error (error, G_MARKUP_ERROR,
540       G_MARKUP_ERROR_INVALID_CONTENT,
541       "element '%s', wrong layer properties '%s', could no be deserialized",
542       element_name, properties);
543 }
544
545 static inline void
546 _parse_clip (GMarkupParseContext * context,
547     const gchar * element_name, const gchar ** attribute_names,
548     const gchar ** attribute_values, GESXmlFormatter * self, GError ** error)
549 {
550   GType type;
551   GstStructure *props = NULL, *children_props = NULL;
552   GESTrackType track_types;
553   GstClockTime start, inpoint = 0, duration, layer_prio;
554   GESXmlFormatterPrivate *priv = _GET_PRIV (self);
555
556   const gchar *strid, *asset_id, *strstart, *strin, *strduration, *strrate,
557       *strtrack_types, *strtype, *metadatas = NULL, *properties =
558       NULL, *children_properties = NULL, *strlayer_prio;
559
560   if (!g_markup_collect_attributes (element_name, attribute_names,
561           attribute_values, error,
562           G_MARKUP_COLLECT_STRING, "id", &strid,
563           G_MARKUP_COLLECT_STRING, "type-name", &strtype,
564           G_MARKUP_COLLECT_STRING, "start", &strstart,
565           G_MARKUP_COLLECT_STRING, "duration", &strduration,
566           G_MARKUP_COLLECT_STRING, "asset-id", &asset_id,
567           G_MARKUP_COLLECT_STRING, "track-types", &strtrack_types,
568           G_MARKUP_COLLECT_STRING, "layer-priority", &strlayer_prio,
569           COLLECT_STR_OPT, "properties", &properties,
570           COLLECT_STR_OPT, "children-properties", &children_properties,
571           COLLECT_STR_OPT, "metadatas", &metadatas,
572           COLLECT_STR_OPT, "rate", &strrate,
573           COLLECT_STR_OPT, "inpoint", &strin, G_MARKUP_COLLECT_INVALID)) {
574     return;
575   }
576   type = g_type_from_name (strtype);
577   if (!g_type_is_a (type, GES_TYPE_CLIP))
578     goto wrong_type;
579
580   errno = 0;
581   track_types = g_ascii_strtoll (strtrack_types, NULL, 10);
582   if (errno)
583     goto convertion_failed;
584
585   layer_prio = g_ascii_strtoll (strlayer_prio, NULL, 10);
586   if (errno)
587     goto convertion_failed;
588
589   if (strin) {
590     inpoint = g_ascii_strtoull (strin, NULL, 10);
591     if (errno)
592       goto convertion_failed;
593   }
594
595   start = g_ascii_strtoull (strstart, NULL, 10);
596   if (errno)
597     goto convertion_failed;
598
599   duration = g_ascii_strtoull (strduration, NULL, 10);
600   if (errno)
601     goto convertion_failed;
602
603   if (properties) {
604     props = gst_structure_from_string (properties, NULL);
605     if (props == NULL)
606       goto wrong_properties;
607   }
608
609   if (children_properties) {
610     children_props = gst_structure_from_string (children_properties, NULL);
611     if (children_props == NULL)
612       goto wrong_children_properties;
613   }
614
615   G_LOCK (uri_subprojects_map_lock);
616   if (g_hash_table_contains (priv->subprojects_map, asset_id)) {
617     asset_id = g_hash_table_lookup (priv->subprojects_map, asset_id);
618     GST_DEBUG_OBJECT (self, "Using subproject %s", asset_id);
619   }
620   G_UNLOCK (uri_subprojects_map_lock);
621   ges_base_xml_formatter_add_clip (GES_BASE_XML_FORMATTER (self),
622       strid, asset_id, type, start, inpoint, duration, layer_prio,
623       track_types, props, children_props, metadatas, error);
624   if (props)
625     gst_structure_free (props);
626   if (children_props)
627     gst_structure_free (children_props);
628
629   return;
630
631 wrong_properties:
632   g_set_error (error, G_MARKUP_ERROR,
633       G_MARKUP_ERROR_INVALID_CONTENT,
634       "element '%s', Clip %s properties '%s', could no be deserialized",
635       element_name, asset_id, properties);
636   return;
637
638 wrong_children_properties:
639   g_set_error (error, G_MARKUP_ERROR,
640       G_MARKUP_ERROR_INVALID_CONTENT,
641       "element '%s', Clip %s children properties '%s', could no be deserialized",
642       element_name, asset_id, children_properties);
643   if (props)
644     gst_structure_free (props);
645   return;
646
647 convertion_failed:
648   g_set_error (error, G_MARKUP_ERROR,
649       G_MARKUP_ERROR_INVALID_CONTENT,
650       "element '%s', Wrong property type, error: %s'", element_name,
651       g_strerror (errno));
652   return;
653
654 wrong_type:
655   g_set_error (error, G_MARKUP_ERROR,
656       G_MARKUP_ERROR_INVALID_CONTENT,
657       "element '%s', %s not a GESClip'", element_name, strtype);
658 }
659
660 static inline void
661 _parse_binding (GMarkupParseContext * context, const gchar * element_name,
662     const gchar ** attribute_names, const gchar ** attribute_values,
663     GESXmlFormatter * self, GError ** error)
664 {
665   const gchar *type = NULL, *source_type = NULL, *timed_values =
666       NULL, *property_name = NULL, *mode = NULL, *track_id = NULL;
667   gchar **pairs, **tmp;
668   gchar *pair;
669   GSList *list = NULL;
670
671   if (!g_markup_collect_attributes (element_name, attribute_names,
672           attribute_values, error,
673           G_MARKUP_COLLECT_STRING, "type", &type,
674           G_MARKUP_COLLECT_STRING, "source_type", &source_type,
675           G_MARKUP_COLLECT_STRING, "property", &property_name,
676           G_MARKUP_COLLECT_STRING, "mode", &mode,
677           G_MARKUP_COLLECT_STRING, "track_id", &track_id,
678           G_MARKUP_COLLECT_STRING, "values", &timed_values,
679           G_MARKUP_COLLECT_INVALID)) {
680     return;
681   }
682
683   pairs = g_strsplit (timed_values, " ", 0);
684   for (tmp = pairs; tmp != NULL; tmp += 1) {
685     gchar **value_pair;
686
687     pair = *tmp;
688     if (pair == NULL)
689       break;
690     if (strlen (pair)) {
691       GstTimedValue *value;
692
693       value = g_new0 (GstTimedValue, 1);
694       value_pair = g_strsplit (pair, ":", 0);
695       value->timestamp = g_ascii_strtoull (value_pair[0], NULL, 10);
696       value->value = g_ascii_strtod (value_pair[1], NULL);
697       list = g_slist_append (list, value);
698       g_strfreev (value_pair);
699     }
700   }
701
702   g_strfreev (pairs);
703
704   ges_base_xml_formatter_add_control_binding (GES_BASE_XML_FORMATTER (self),
705       type,
706       source_type,
707       property_name, (gint) g_ascii_strtoll (mode, NULL, 10), track_id, list);
708 }
709
710 static inline void
711 _parse_source (GMarkupParseContext * context, const gchar * element_name,
712     const gchar ** attribute_names, const gchar ** attribute_values,
713     GESXmlFormatter * self, GError ** error)
714 {
715   GstStructure *children_props = NULL, *props = NULL;
716   const gchar *track_id = NULL, *children_properties = NULL, *properties = NULL;
717
718   if (!g_markup_collect_attributes (element_name, attribute_names,
719           attribute_values, error,
720           G_MARKUP_COLLECT_STRING, "track-id", &track_id,
721           COLLECT_STR_OPT, "children-properties", &children_properties,
722           COLLECT_STR_OPT, "properties", &properties,
723           G_MARKUP_COLLECT_INVALID)) {
724     return;
725   }
726
727   if (children_properties) {
728     children_props = gst_structure_from_string (children_properties, NULL);
729     if (children_props == NULL)
730       goto wrong_children_properties;
731   }
732
733   if (properties) {
734     props = gst_structure_from_string (properties, NULL);
735     if (props == NULL)
736       goto wrong_properties;
737   }
738
739   ges_base_xml_formatter_add_source (GES_BASE_XML_FORMATTER (self), track_id,
740       children_props, props);
741
742 done:
743   if (children_props)
744     gst_structure_free (children_props);
745
746   if (props)
747     gst_structure_free (props);
748
749   return;
750
751 wrong_children_properties:
752   g_set_error (error, G_MARKUP_ERROR,
753       G_MARKUP_ERROR_INVALID_CONTENT,
754       "element '%s', children properties '%s', could no be deserialized",
755       element_name, children_properties);
756   goto done;
757
758 wrong_properties:
759   g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
760       "element '%s', properties '%s', could no be deserialized",
761       element_name, properties);
762   goto done;
763 }
764
765 static inline void
766 _parse_effect (GMarkupParseContext * context, const gchar * element_name,
767     const gchar ** attribute_names, const gchar ** attribute_values,
768     GESXmlFormatter * self, GError ** error)
769 {
770   GType type;
771
772   GstStructure *children_props = NULL, *props = NULL;
773   const gchar *asset_id = NULL, *strtype = NULL, *track_id =
774       NULL, *metadatas = NULL, *properties = NULL, *track_type = NULL,
775       *children_properties = NULL, *clip_id;
776
777   if (!g_markup_collect_attributes (element_name, attribute_names,
778           attribute_values, error,
779           COLLECT_STR_OPT, "metadatas", &metadatas,
780           G_MARKUP_COLLECT_STRING, "asset-id", &asset_id,
781           G_MARKUP_COLLECT_STRING, "clip-id", &clip_id,
782           G_MARKUP_COLLECT_STRING, "type-name", &strtype,
783           G_MARKUP_COLLECT_STRING, "track-id", &track_id,
784           COLLECT_STR_OPT, "children-properties", &children_properties,
785           COLLECT_STR_OPT, "track-type", &track_type,
786           COLLECT_STR_OPT, "properties", &properties,
787           G_MARKUP_COLLECT_INVALID)) {
788     return;
789   }
790
791   type = g_type_from_name (strtype);
792   if (!g_type_is_a (type, GES_TYPE_BASE_EFFECT))
793     goto wrong_type;
794
795   if (children_properties) {
796     children_props = gst_structure_from_string (children_properties, NULL);
797     if (children_props == NULL)
798       goto wrong_children_properties;
799   }
800
801   if (properties) {
802     props = gst_structure_from_string (properties, NULL);
803     if (props == NULL)
804       goto wrong_properties;
805   }
806
807   ges_base_xml_formatter_add_track_element (GES_BASE_XML_FORMATTER (self),
808       type, asset_id, track_id, clip_id, children_props, props, metadatas,
809       error);
810
811 out:
812
813   if (props)
814     gst_structure_free (props);
815   if (children_props)
816     gst_structure_free (children_props);
817
818   return;
819
820 wrong_properties:
821   g_set_error (error, G_MARKUP_ERROR,
822       G_MARKUP_ERROR_INVALID_CONTENT,
823       "element '%s', Effect %s properties '%s', could no be deserialized",
824       element_name, asset_id, properties);
825   goto out;
826
827 wrong_children_properties:
828   g_set_error (error, G_MARKUP_ERROR,
829       G_MARKUP_ERROR_INVALID_CONTENT,
830       "element '%s', Effect %s children properties '%s', could no be deserialized",
831       element_name, asset_id, children_properties);
832   goto out;
833
834 wrong_type:
835   g_set_error (error, G_MARKUP_ERROR,
836       G_MARKUP_ERROR_INVALID_CONTENT,
837       "element '%s', %s not a GESBaseEffect'", element_name, strtype);
838 }
839
840
841 static inline void
842 _parse_group (GMarkupParseContext * context, const gchar * element_name,
843     const gchar ** attribute_names, const gchar ** attribute_values,
844     GESXmlFormatter * self, GError ** error)
845 {
846   const gchar *id, *properties, *metadatas = NULL;
847
848   if (!g_markup_collect_attributes (element_name, attribute_names,
849           attribute_values, error,
850           G_MARKUP_COLLECT_STRING, "id", &id,
851           G_MARKUP_COLLECT_STRING, "properties", &properties,
852           COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID)) {
853     return;
854   }
855
856   ges_base_xml_formatter_add_group (GES_BASE_XML_FORMATTER (self), id,
857       properties, metadatas);
858 }
859
860 static inline void
861 _parse_group_child (GMarkupParseContext * context, const gchar * element_name,
862     const gchar ** attribute_names, const gchar ** attribute_values,
863     GESXmlFormatter * self, GError ** error)
864 {
865   const gchar *child_id, *name;
866
867   if (!g_markup_collect_attributes (element_name, attribute_names,
868           attribute_values, error,
869           G_MARKUP_COLLECT_STRING, "id", &child_id,
870           G_MARKUP_COLLECT_STRING, "name", &name, G_MARKUP_COLLECT_INVALID)) {
871     return;
872   }
873
874   ges_base_xml_formatter_last_group_add_child (GES_BASE_XML_FORMATTER (self),
875       child_id, name);
876 }
877
878 static void
879 _parse_element_start (GMarkupParseContext * context, const gchar * element_name,
880     const gchar ** attribute_names, const gchar ** attribute_values,
881     gpointer self, GError ** error)
882 {
883   GESXmlFormatterPrivate *priv = _GET_PRIV (self);
884
885   if (priv->subproject) {
886     if (g_strcmp0 (element_name, "ges") == 0) {
887       priv->subproject_depth += 1;
888     }
889     return;
890   }
891
892   if (!G_UNLIKELY (priv->ges_opened)) {
893     _parse_ges_element (context, element_name, attribute_names,
894         attribute_values, self, error);
895   } else if (!G_UNLIKELY (priv->project_opened))
896     _parse_project (context, element_name, attribute_names, attribute_values,
897         self, error);
898   else if (g_strcmp0 (element_name, "ges") == 0) {
899   } else if (g_strcmp0 (element_name, "encoding-profile") == 0)
900     _parse_encoding_profile (context, element_name, attribute_names,
901         attribute_values, self, error);
902   else if (g_strcmp0 (element_name, "stream-profile") == 0)
903     _parse_stream_profile (context, element_name, attribute_names,
904         attribute_values, self, error);
905   else if (g_strcmp0 (element_name, "timeline") == 0)
906     _parse_timeline (context, element_name, attribute_names, attribute_values,
907         self, error);
908   else if (g_strcmp0 (element_name, "asset") == 0)
909     _parse_asset (context, element_name, attribute_names, attribute_values,
910         self, error);
911   else if (g_strcmp0 (element_name, "track") == 0)
912     _parse_track (context, element_name, attribute_names,
913         attribute_values, self, error);
914   else if (g_strcmp0 (element_name, "layer") == 0)
915     _parse_layer (context, element_name, attribute_names,
916         attribute_values, self, error);
917   else if (g_strcmp0 (element_name, "clip") == 0)
918     _parse_clip (context, element_name, attribute_names,
919         attribute_values, self, error);
920   else if (g_strcmp0 (element_name, "source") == 0)
921     _parse_source (context, element_name, attribute_names,
922         attribute_values, self, error);
923   else if (g_strcmp0 (element_name, "effect") == 0)
924     _parse_effect (context, element_name, attribute_names,
925         attribute_values, self, error);
926   else if (g_strcmp0 (element_name, "binding") == 0)
927     _parse_binding (context, element_name, attribute_names,
928         attribute_values, self, error);
929   else if (g_strcmp0 (element_name, "group") == 0)
930     _parse_group (context, element_name, attribute_names,
931         attribute_values, self, error);
932   else if (g_strcmp0 (element_name, "child") == 0)
933     _parse_group_child (context, element_name, attribute_names,
934         attribute_values, self, error);
935   else
936     GST_LOG_OBJECT (self, "Element %s not handled", element_name);
937 }
938
939 static gboolean
940 _save_subproject_data (GESXmlFormatter * self, SubprojectData * subproj_data,
941     gint subproject_end_line, gint subproject_end_char, GError ** error)
942 {
943   gsize size;
944   gint line = 1, i;
945   gboolean res = FALSE;
946   gsize start = 0, end = 0;
947   gchar *subproject_content = NULL;
948   gchar *xml = GES_BASE_XML_FORMATTER (self)->xmlcontent;
949
950   for (i = 0; xml[i] != '\0'; i++) {
951     if (!start && line == subproj_data->start_line) {
952       i += subproj_data->start_char - 1;
953       start = i;
954     }
955
956     if (line == subproject_end_line) {
957       end = i + subproject_end_char - 1;
958       break;
959     }
960
961     if (xml[i] == '\n')
962       line++;
963   }
964   g_assert (start && end);
965   size = (end - start);
966
967   subproject_content = g_malloc (sizeof (gchar) * size);
968   memcpy (subproject_content, &xml[start], end - start);
969   subproject_content[end - start] = '\0';
970   GST_INFO_OBJECT (self, "Saving subproject %s from %d:%d(%" G_GSIZE_FORMAT
971       ") to %d:%d(%" G_GSIZE_FORMAT ")",
972       subproj_data->id, subproj_data->start_line, subproj_data->start_char,
973       start, subproject_end_line, subproject_end_char, end);
974
975   res = g_file_set_contents (subproj_data->filename, subproject_content, -1,
976       error);
977   g_free (subproject_content);
978
979   return res;
980 }
981
982 static void
983 _parse_element_end (GMarkupParseContext * context,
984     const gchar * element_name, gpointer self, GError ** error)
985 {
986   GESXmlFormatterPrivate *priv = _GET_PRIV (self);
987   SubprojectData *subproj_data = priv->subproject;
988
989   /*GESXmlFormatterPrivate *priv = _GET_PRIV (self); */
990
991   if (!g_strcmp0 (element_name, "ges")) {
992     gint subproject_end_line, subproject_end_char;
993
994     if (priv->subproject_depth)
995       priv->subproject_depth -= 1;
996
997     if (!subproj_data) {
998       if (GES_FORMATTER (self)->project) {
999         gchar *version = g_strdup_printf ("%d.%d",
1000             API_VERSION, GES_XML_FORMATTER (self)->priv->min_version);
1001
1002         ges_meta_container_set_string (GES_META_CONTAINER (GES_FORMATTER
1003                 (self)->project), GES_META_FORMAT_VERSION, version);
1004
1005         g_free (version);
1006         _GET_PRIV (self)->ges_opened = FALSE;
1007       }
1008     } else if (subproj_data->start_line != -1 && !priv->subproject_depth) {
1009       g_markup_parse_context_get_position (context, &subproject_end_line,
1010           &subproject_end_char);
1011       _save_subproject_data (GES_XML_FORMATTER (self), subproj_data,
1012           subproject_end_line, subproject_end_char, error);
1013
1014       subproj_data->filename = NULL;
1015       g_close (subproj_data->fd, error);
1016       subproj_data->id = NULL;
1017       subproj_data->start_line = 0;
1018       subproj_data->start_char = 0;
1019     }
1020
1021     if (!priv->subproject_depth) {
1022       g_clear_pointer (&priv->subproject, g_free);
1023     }
1024   } else if (!g_strcmp0 (element_name, "clip")) {
1025     if (!priv->subproject)
1026       ges_base_xml_formatter_end_current_clip (GES_BASE_XML_FORMATTER (self));
1027   }
1028 }
1029
1030 static void
1031 _error_parsing (GMarkupParseContext * context, GError * error,
1032     gpointer user_data)
1033 {
1034   GST_WARNING ("Error occurred when parsing %s", error->message);
1035 }
1036
1037 /***********************************************
1038  *                                             *
1039  *            Saving implementation            *
1040  *                                             *
1041  ***********************************************/
1042
1043 /* XML writting utils */
1044 static inline void
1045 string_add_indents (GString * str, guint depth, gboolean prepend)
1046 {
1047   gint i;
1048   for (i = 0; i < depth; i++)
1049     prepend ? g_string_prepend (str, "  ") : g_string_append (str, "  ");
1050 }
1051
1052 static inline void
1053 string_append_with_depth (GString * str, const gchar * string, guint depth)
1054 {
1055   string_add_indents (str, depth, FALSE);
1056   g_string_append (str, string);
1057 }
1058
1059 static inline void
1060 append_escaped (GString * str, gchar * tmpstr, guint depth)
1061 {
1062   string_append_with_depth (str, tmpstr, depth);
1063   g_free (tmpstr);
1064 }
1065
1066 gboolean
1067 ges_util_can_serialize_spec (GParamSpec * spec)
1068 {
1069   if (!(spec->flags & G_PARAM_WRITABLE)) {
1070     GST_LOG ("%s from %s is not writable",
1071         spec->name, g_type_name (spec->owner_type));
1072
1073     return FALSE;
1074   } else if (spec->flags & G_PARAM_CONSTRUCT_ONLY) {
1075     GST_LOG ("%s from %s is construct only",
1076         spec->name, g_type_name (spec->owner_type));
1077
1078     return FALSE;
1079   } else if (spec->flags & GES_PARAM_NO_SERIALIZATION &&
1080       g_type_is_a (spec->owner_type, GES_TYPE_TIMELINE_ELEMENT)) {
1081     GST_LOG ("%s from %s is set as GES_PARAM_NO_SERIALIZATION",
1082         spec->name, g_type_name (spec->owner_type));
1083
1084     return FALSE;
1085   } else if (g_type_is_a (G_PARAM_SPEC_VALUE_TYPE (spec), G_TYPE_OBJECT)) {
1086     GST_LOG ("%s from %s contains GObject, can't serialize that.",
1087         spec->name, g_type_name (spec->owner_type));
1088
1089     return FALSE;
1090   } else if ((g_type_is_a (spec->owner_type, GST_TYPE_OBJECT) &&
1091           !g_strcmp0 (spec->name, "name"))) {
1092
1093     GST_LOG ("We do not want to serialize the name of GstObjects.");
1094     return FALSE;
1095   } else if (G_PARAM_SPEC_VALUE_TYPE (spec) == G_TYPE_GTYPE) {
1096     GST_LOG ("%s from %s contains a GType, can't serialize.",
1097         spec->name, g_type_name (spec->owner_type));
1098     return FALSE;
1099   }
1100
1101   return TRUE;
1102 }
1103
1104 static inline void
1105 _init_value_from_spec_for_serialization (GValue * value, GParamSpec * spec)
1106 {
1107
1108   if (g_type_is_a (spec->value_type, G_TYPE_ENUM) ||
1109       g_type_is_a (spec->value_type, G_TYPE_FLAGS))
1110     g_value_init (value, G_TYPE_INT);
1111   else
1112     g_value_init (value, spec->value_type);
1113 }
1114
1115 static gchar *
1116 _serialize_properties (GObject * object, gint * ret_n_props,
1117     const gchar * fieldname, ...)
1118 {
1119   gchar *ret;
1120   guint n_props, j;
1121   GParamSpec *spec, **pspecs;
1122   GObjectClass *class = G_OBJECT_GET_CLASS (object);
1123   GstStructure *structure = gst_structure_new_empty ("properties");
1124
1125   pspecs = g_object_class_list_properties (class, &n_props);
1126   for (j = 0; j < n_props; j++) {
1127     GValue val = { 0 };
1128
1129     spec = pspecs[j];
1130     if (!ges_util_can_serialize_spec (spec))
1131       continue;
1132
1133     _init_value_from_spec_for_serialization (&val, spec);
1134     g_object_get_property (object, spec->name, &val);
1135     if (gst_value_compare (g_param_spec_get_default_value (spec),
1136             &val) == GST_VALUE_EQUAL) {
1137       GST_INFO ("Ignoring %s as it is using the default value", spec->name);
1138       goto next;
1139     }
1140
1141     if (spec->value_type == GST_TYPE_CAPS) {
1142       gchar *caps_str;
1143       const GstCaps *caps = gst_value_get_caps (&val);
1144
1145       caps_str = gst_caps_to_string (caps);
1146       gst_structure_set (structure, spec->name, G_TYPE_STRING, caps_str, NULL);
1147       g_free (caps_str);
1148       goto next;
1149     }
1150
1151     gst_structure_set_value (structure, spec->name, &val);
1152
1153   next:
1154     g_value_unset (&val);
1155   }
1156   g_free (pspecs);
1157
1158   if (fieldname) {
1159     va_list varargs;
1160     va_start (varargs, fieldname);
1161     gst_structure_remove_fields_valist (structure, fieldname, varargs);
1162     va_end (varargs);
1163   }
1164
1165   ret = gst_structure_to_string (structure);
1166   if (ret_n_props)
1167     *ret_n_props = gst_structure_n_fields (structure);
1168   gst_structure_free (structure);
1169
1170   return ret;
1171 }
1172
1173 static void
1174 project_loaded_cb (GESProject * project, GESTimeline * timeline,
1175     SubprojectData * data)
1176 {
1177   g_main_loop_quit (data->ml);
1178 }
1179
1180 static void
1181 error_loading_asset_cb (GESProject * project, GError * err,
1182     const gchar * unused_id, GType extractable_type, SubprojectData * data)
1183 {
1184   data->error = g_error_copy (err);
1185   g_main_loop_quit (data->ml);
1186 }
1187
1188 static gboolean
1189 _save_subproject (GESXmlFormatter * self, GString * str, GESProject * project,
1190     GESAsset * subproject, GError ** error, guint depth)
1191 {
1192   GString *substr;
1193   GESTimeline *timeline;
1194   gchar *properties, *metas;
1195   GESXmlFormatterPrivate *priv = self->priv;
1196   GMainContext *context = g_main_context_get_thread_default ();
1197   const gchar *id = ges_asset_get_id (subproject);
1198   SubprojectData data = { 0, };
1199
1200   if (!g_strcmp0 (ges_asset_get_id (GES_ASSET (project)), id)) {
1201     g_set_error (error, G_MARKUP_ERROR,
1202         G_MARKUP_ERROR_INVALID_CONTENT,
1203         "Project %s trying to recurse into itself", id);
1204     return FALSE;
1205   }
1206
1207   G_LOCK (uri_subprojects_map_lock);
1208   g_hash_table_insert (priv->subprojects_map, g_strdup (id), g_strdup (id));
1209   G_UNLOCK (uri_subprojects_map_lock);
1210   timeline = GES_TIMELINE (ges_asset_extract (subproject, error));
1211   if (!timeline) {
1212     return FALSE;
1213   }
1214
1215   if (!context)
1216     context = g_main_context_default ();
1217
1218   data.ml = g_main_loop_new (context, TRUE);
1219   g_signal_connect (subproject, "loaded", (GCallback) project_loaded_cb, &data);
1220   g_signal_connect (subproject, "error-loading-asset",
1221       (GCallback) error_loading_asset_cb, &data);
1222   g_main_loop_run (data.ml);
1223
1224   g_signal_handlers_disconnect_by_func (subproject, project_loaded_cb, &data);
1225   g_signal_handlers_disconnect_by_func (subproject, error_loading_asset_cb,
1226       &data);
1227   if (data.error) {
1228     g_propagate_error (error, data.error);
1229     return FALSE;
1230   }
1231
1232   subproject = ges_extractable_get_asset (GES_EXTRACTABLE (timeline));
1233   substr = g_string_new (NULL);
1234   properties = _serialize_properties (G_OBJECT (subproject), NULL, NULL);
1235   metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (subproject));
1236   append_escaped (str,
1237       g_markup_printf_escaped
1238       ("      <asset id='%s' extractable-type-name='%s' properties='%s' metadatas='%s'>\n",
1239           ges_asset_get_id (subproject),
1240           g_type_name (ges_asset_get_extractable_type (subproject)), properties,
1241           metas), depth);
1242   self->priv->min_version = MAX (self->priv->min_version, 6);
1243
1244   depth += 4;
1245   GST_DEBUG_OBJECT (self, "Saving subproject %s (depth: %d)",
1246       ges_asset_get_id (subproject), depth / 4);
1247   if (!_save_project (GES_FORMATTER (self), substr, GES_PROJECT (subproject),
1248           timeline, error, depth)) {
1249     g_string_free (substr, TRUE);
1250     g_object_unref (subproject);
1251     goto err;
1252   }
1253   GST_DEBUG_OBJECT (self, "DONE Saving subproject %s",
1254       ges_asset_get_id (subproject));
1255   depth -= 4;
1256
1257   g_string_append (str, substr->str);
1258   g_string_free (substr, TRUE);
1259   string_append_with_depth (str, "      </asset>\n", depth);
1260
1261 err:
1262   g_object_unref (subproject);
1263
1264   return TRUE;
1265 }
1266
1267 static gint
1268 sort_assets (GESAsset * a, GESAsset * b)
1269 {
1270   if (GES_IS_PROJECT (a))
1271     return -1;
1272
1273   if (GES_IS_PROJECT (b))
1274     return 1;
1275
1276   return 0;
1277 }
1278
1279 static void
1280 _serialize_streams (GESXmlFormatter * self, GString * str,
1281     GESUriClipAsset * asset, GError ** error, guint depth)
1282 {
1283   const GList *tmp, *streams = ges_uri_clip_asset_get_stream_assets (asset);
1284
1285   for (tmp = streams; tmp; tmp = tmp->next) {
1286     gchar *properties, *metas, *capsstr;
1287     const gchar *id = ges_asset_get_id (tmp->data);
1288     GstDiscovererStreamInfo *sinfo =
1289         ges_uri_source_asset_get_stream_info (tmp->data);
1290     GstCaps *caps = gst_discoverer_stream_info_get_caps (sinfo);
1291
1292     properties = _serialize_properties (tmp->data, NULL, NULL);
1293     metas = ges_meta_container_metas_to_string (tmp->data);
1294     capsstr = gst_caps_to_string (caps);
1295
1296     append_escaped (str,
1297         g_markup_printf_escaped
1298         ("        <stream-info id='%s' extractable-type-name='%s' properties='%s' metadatas='%s' caps='%s'/>\n",
1299             id, g_type_name (ges_asset_get_extractable_type (tmp->data)),
1300             properties, metas, capsstr), depth);
1301     self->priv->min_version = MAX (self->priv->min_version, 6);
1302     g_free (metas);
1303     g_free (properties);
1304     g_free (capsstr);
1305     gst_caps_unref (caps);
1306   }
1307
1308 }
1309
1310 static inline gboolean
1311 _save_assets (GESXmlFormatter * self, GString * str, GESProject * project,
1312     GError ** error, guint depth)
1313 {
1314   gchar *properties, *metas;
1315   GESAsset *asset, *proxy;
1316   GList *assets, *tmp;
1317   const gchar *id;
1318   GESXmlFormatterPrivate *priv = self->priv;
1319
1320   assets = ges_project_list_assets (project, GES_TYPE_EXTRACTABLE);
1321   for (tmp = g_list_sort (assets, (GCompareFunc) sort_assets); tmp;
1322       tmp = tmp->next) {
1323     asset = GES_ASSET (tmp->data);
1324     id = ges_asset_get_id (asset);
1325
1326     if (GES_IS_PROJECT (asset)) {
1327       if (!_save_subproject (self, str, project, asset, error, depth))
1328         return FALSE;
1329
1330       continue;
1331     }
1332
1333     if (ges_asset_get_extractable_type (asset) == GES_TYPE_URI_CLIP) {
1334       G_LOCK (uri_subprojects_map_lock);
1335       if (g_hash_table_contains (priv->subprojects_map, id)) {
1336         id = g_hash_table_lookup (priv->subprojects_map, id);
1337
1338         GST_DEBUG_OBJECT (self, "Using subproject %s", id);
1339       }
1340       G_UNLOCK (uri_subprojects_map_lock);
1341     }
1342
1343     properties = _serialize_properties (G_OBJECT (asset), NULL, NULL);
1344     metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (asset));
1345     append_escaped (str,
1346         g_markup_printf_escaped
1347         ("      <asset id='%s' extractable-type-name='%s' properties='%s' metadatas='%s' ",
1348             id, g_type_name (ges_asset_get_extractable_type (asset)),
1349             properties, metas), depth);
1350
1351     /*TODO Save the whole list of proxies */
1352     proxy = ges_asset_get_proxy (asset);
1353     if (proxy) {
1354       const gchar *proxy_id = ges_asset_get_id (proxy);
1355
1356       if (ges_asset_get_extractable_type (asset) == GES_TYPE_URI_CLIP) {
1357         G_LOCK (uri_subprojects_map_lock);
1358         if (g_hash_table_contains (priv->subprojects_map, proxy_id)) {
1359           proxy_id = g_hash_table_lookup (priv->subprojects_map, proxy_id);
1360
1361           GST_DEBUG_OBJECT (self, "Using subproject %s", id);
1362         }
1363         G_UNLOCK (uri_subprojects_map_lock);
1364       }
1365       append_escaped (str, g_markup_printf_escaped (" proxy-id='%s' ",
1366               proxy_id), depth);
1367
1368       if (!g_list_find (assets, proxy)) {
1369         assets = g_list_append (assets, gst_object_ref (proxy));
1370
1371         if (!tmp->next)
1372           tmp->next = g_list_last (assets);
1373       }
1374
1375       self->priv->min_version = MAX (self->priv->min_version, 3);
1376     }
1377     g_string_append (str, ">\n");
1378
1379     if (GES_IS_URI_CLIP_ASSET (asset)) {
1380       _serialize_streams (self, str, GES_URI_CLIP_ASSET (asset), error, depth);
1381     }
1382
1383     string_append_with_depth (str, "      </asset>\n", depth);
1384     g_free (properties);
1385     g_free (metas);
1386   }
1387
1388   g_list_free_full (assets, gst_object_unref);
1389
1390   return TRUE;
1391 }
1392
1393 static inline void
1394 _save_tracks (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
1395     guint depth)
1396 {
1397   gchar *strtmp, *metas;
1398   GESTrack *track;
1399   GList *tmp, *tracks;
1400   char *properties;
1401
1402   guint nb_tracks = 0;
1403
1404   tracks = ges_timeline_get_tracks (timeline);
1405   for (tmp = tracks; tmp; tmp = tmp->next) {
1406     track = GES_TRACK (tmp->data);
1407     properties = _serialize_properties (G_OBJECT (track), NULL, "caps", NULL);
1408     strtmp = gst_caps_to_string (ges_track_get_caps (track));
1409     metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (track));
1410     append_escaped (str,
1411         g_markup_printf_escaped
1412         ("      <track caps='%s' track-type='%i' track-id='%i' properties='%s' metadatas='%s'/>\n",
1413             strtmp, track->type, nb_tracks++, properties, metas), depth);
1414     g_free (strtmp);
1415     g_free (metas);
1416     g_free (properties);
1417   }
1418   g_list_free_full (tracks, gst_object_unref);
1419 }
1420
1421 static inline void
1422 _save_children_properties (GString * str, GESTimelineElement * element,
1423     guint depth)
1424 {
1425   GstStructure *structure;
1426   GParamSpec **pspecs, *spec;
1427   guint i, n_props;
1428   gchar *struct_str;
1429
1430   pspecs = ges_timeline_element_list_children_properties (element, &n_props);
1431
1432   structure = gst_structure_new_empty ("properties");
1433   for (i = 0; i < n_props; i++) {
1434     GValue val = { 0 };
1435     spec = pspecs[i];
1436
1437     if (ges_util_can_serialize_spec (spec)) {
1438       gchar *spec_name =
1439           g_strdup_printf ("%s::%s", g_type_name (spec->owner_type),
1440           spec->name);
1441
1442       _init_value_from_spec_for_serialization (&val, spec);
1443       ges_timeline_element_get_child_property_by_pspec (element, spec, &val);
1444       gst_structure_set_value (structure, spec_name, &val);
1445
1446       g_free (spec_name);
1447       g_value_unset (&val);
1448     }
1449     g_param_spec_unref (spec);
1450   }
1451   g_free (pspecs);
1452
1453   struct_str = gst_structure_to_string (structure);
1454   append_escaped (str,
1455       g_markup_printf_escaped (" children-properties='%s'", struct_str), 0);
1456   gst_structure_free (structure);
1457   g_free (struct_str);
1458 }
1459
1460 /* TODO : Use this function for every track element with controllable properties */
1461 static inline void
1462 _save_keyframes (GString * str, GESTrackElement * trackelement, gint index,
1463     guint depth)
1464 {
1465   GHashTable *bindings_hashtable;
1466   GHashTableIter iter;
1467   gpointer key, value;
1468
1469   bindings_hashtable =
1470       ges_track_element_get_all_control_bindings (trackelement);
1471
1472   g_hash_table_iter_init (&iter, bindings_hashtable);
1473
1474   /* We iterate over the bindings, and save the timed values */
1475   while (g_hash_table_iter_next (&iter, &key, &value)) {
1476     if (GST_IS_DIRECT_CONTROL_BINDING ((GstControlBinding *) value)) {
1477       GstControlSource *source;
1478       gboolean absolute = FALSE;
1479       GstDirectControlBinding *binding;
1480
1481       binding = (GstDirectControlBinding *) value;
1482
1483       g_object_get (binding, "control-source", &source,
1484           "absolute", &absolute, NULL);
1485
1486       if (GST_IS_INTERPOLATION_CONTROL_SOURCE (source)) {
1487         GList *timed_values, *tmp;
1488         GstInterpolationMode mode;
1489
1490         append_escaped (str,
1491             g_markup_printf_escaped
1492             ("            <binding type='%s' source_type='interpolation' property='%s'",
1493                 absolute ? "direct-absolute" : "direct", (gchar *) key), depth);
1494
1495         g_object_get (source, "mode", &mode, NULL);
1496         append_escaped (str, g_markup_printf_escaped (" mode='%d'", mode),
1497             depth);
1498         append_escaped (str, g_markup_printf_escaped (" track_id='%d'", index),
1499             depth);
1500         append_escaped (str, g_markup_printf_escaped (" values ='"), depth);
1501         timed_values =
1502             gst_timed_value_control_source_get_all
1503             (GST_TIMED_VALUE_CONTROL_SOURCE (source));
1504         for (tmp = timed_values; tmp; tmp = tmp->next) {
1505           gchar strbuf[G_ASCII_DTOSTR_BUF_SIZE];
1506           GstTimedValue *value;
1507
1508           value = (GstTimedValue *) tmp->data;
1509           append_escaped (str, g_markup_printf_escaped (" %" G_GUINT64_FORMAT
1510                   ":%s ", value->timestamp, g_ascii_dtostr (strbuf,
1511                       G_ASCII_DTOSTR_BUF_SIZE, value->value)), depth);
1512         }
1513         g_list_free (timed_values);
1514         append_escaped (str, g_markup_printf_escaped ("'/>\n"), depth);
1515       } else
1516         GST_DEBUG ("control source not in [interpolation]");
1517
1518       gst_object_unref (source);
1519     } else
1520       GST_DEBUG ("Binding type not in [direct, direct-absolute]");
1521   }
1522 }
1523
1524 static inline void
1525 _save_effect (GString * str, guint clip_id, GESTrackElement * trackelement,
1526     GESTimeline * timeline, guint depth)
1527 {
1528   GESTrack *tck;
1529   GList *tmp, *tracks;
1530   gchar *properties, *metas;
1531   guint track_id = 0;
1532   gboolean serialize;
1533   gchar *extractable_id;
1534
1535   g_object_get (trackelement, "serialize", &serialize, NULL);
1536   if (!serialize) {
1537
1538     GST_DEBUG_OBJECT (trackelement, "Should not be serialized");
1539
1540     return;
1541   }
1542
1543   tck = ges_track_element_get_track (trackelement);
1544   if (tck == NULL) {
1545     GST_WARNING_OBJECT (trackelement, " Not in any track, can not save it");
1546
1547     return;
1548   }
1549
1550   tracks = ges_timeline_get_tracks (timeline);
1551   for (tmp = tracks; tmp; tmp = tmp->next) {
1552     if (tmp->data == tck)
1553       break;
1554     track_id++;
1555   }
1556   g_list_free_full (tracks, gst_object_unref);
1557
1558   properties = _serialize_properties (G_OBJECT (trackelement), NULL, "start",
1559       "duration", "locked", "name", "priority", NULL);
1560   metas =
1561       ges_meta_container_metas_to_string (GES_META_CONTAINER (trackelement));
1562   extractable_id = ges_extractable_get_id (GES_EXTRACTABLE (trackelement));
1563   append_escaped (str,
1564       g_markup_printf_escaped ("          <effect asset-id='%s' clip-id='%u'"
1565           " type-name='%s' track-type='%i' track-id='%i' properties='%s' metadatas='%s'",
1566           extractable_id, clip_id,
1567           g_type_name (G_OBJECT_TYPE (trackelement)), tck->type, track_id,
1568           properties, metas), depth);
1569   g_free (extractable_id);
1570   g_free (properties);
1571   g_free (metas);
1572
1573   _save_children_properties (str, GES_TIMELINE_ELEMENT (trackelement), depth);
1574   append_escaped (str, g_markup_printf_escaped (">\n"), depth);
1575
1576   _save_keyframes (str, trackelement, -1, depth);
1577
1578   append_escaped (str, g_markup_printf_escaped ("          </effect>\n"),
1579       depth);
1580 }
1581
1582 static inline void
1583 _save_layer_track_activness (GESXmlFormatter * self, GESLayer * layer,
1584     GString * str, GESTimeline * timeline, guint depth)
1585 {
1586   guint nb_tracks = 0, i;
1587   GList *tmp, *tracks = ges_timeline_get_tracks (timeline);
1588   GArray *deactivated_tracks = g_array_new (TRUE, FALSE, sizeof (gint32));
1589
1590   for (tmp = tracks; tmp; tmp = tmp->next, nb_tracks++) {
1591     if (!ges_layer_get_active_for_track (layer, tmp->data))
1592       g_array_append_val (deactivated_tracks, nb_tracks);
1593   }
1594
1595   if (!deactivated_tracks->len) {
1596     g_string_append (str, ">\n");
1597     goto done;
1598   }
1599
1600   self->priv->min_version = MAX (self->priv->min_version, 7);
1601   g_string_append (str, " deactivated-tracks='");
1602   for (i = 0; i < deactivated_tracks->len; i++)
1603     g_string_append_printf (str, "%d ", g_array_index (deactivated_tracks, gint,
1604             i));
1605   g_string_append (str, "'>\n");
1606
1607 done:
1608   g_array_free (deactivated_tracks, TRUE);
1609   g_list_free_full (tracks, gst_object_unref);
1610 }
1611
1612 static void
1613 _save_source (GESXmlFormatter * self, GString * str,
1614     GESTimelineElement * element, GESTimeline * timeline, GList * tracks,
1615     guint depth)
1616 {
1617   gint index, n_props;
1618   gboolean serialize;
1619   gchar *properties;
1620
1621   if (!GES_IS_SOURCE (element))
1622     return;
1623
1624   g_object_get (element, "serialize", &serialize, NULL);
1625   if (!serialize) {
1626     GST_DEBUG_OBJECT (element, "Should not be serialized");
1627     return;
1628   }
1629
1630   index =
1631       g_list_index (tracks,
1632       ges_track_element_get_track (GES_TRACK_ELEMENT (element)));
1633   append_escaped (str,
1634       g_markup_printf_escaped
1635       ("          <source track-id='%i' ", index), depth);
1636
1637   properties = _serialize_properties (G_OBJECT (element), &n_props,
1638       "in-point", "priority", "start", "duration", "track", "track-type"
1639       "uri", "name", "max-duration", NULL);
1640
1641   /* Try as possible to allow older versions of GES to load the files */
1642   if (n_props) {
1643     self->priv->min_version = MAX (self->priv->min_version, 7);
1644     g_string_append_printf (str, "properties='%s' ", properties);
1645   }
1646   g_free (properties);
1647
1648   _save_children_properties (str, element, depth);
1649   append_escaped (str, g_markup_printf_escaped (">\n"), depth);
1650   _save_keyframes (str, GES_TRACK_ELEMENT (element), index, depth);
1651   append_escaped (str, g_markup_printf_escaped ("          </source>\n"),
1652       depth);
1653 }
1654
1655 static inline void
1656 _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
1657     guint depth)
1658 {
1659   gchar *properties, *metas;
1660   GESLayer *layer;
1661   GESClip *clip;
1662   GList *tmplayer, *tmpclip, *clips;
1663   GESXmlFormatterPrivate *priv = self->priv;
1664
1665   for (tmplayer = timeline->layers; tmplayer; tmplayer = tmplayer->next) {
1666     guint priority;
1667     layer = GES_LAYER (tmplayer->data);
1668
1669     priority = ges_layer_get_priority (layer);
1670     properties =
1671         _serialize_properties (G_OBJECT (layer), NULL, "priority", NULL);
1672     metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (layer));
1673     append_escaped (str,
1674         g_markup_printf_escaped
1675         ("      <layer priority='%i' properties='%s' metadatas='%s'",
1676             priority, properties, metas), depth);
1677     g_free (properties);
1678     g_free (metas);
1679
1680     _save_layer_track_activness (self, layer, str, timeline, depth);
1681
1682     clips = ges_layer_get_clips (layer);
1683     for (tmpclip = clips; tmpclip; tmpclip = tmpclip->next) {
1684       GList *effects, *tmpeffect;
1685       GList *tmptrackelement;
1686       GList *tracks;
1687       gboolean serialize;
1688       gchar *extractable_id;
1689
1690       clip = GES_CLIP (tmpclip->data);
1691
1692       g_object_get (clip, "serialize", &serialize, NULL);
1693       if (!serialize) {
1694         GST_DEBUG_OBJECT (clip, "Should not be serialized");
1695         continue;
1696       }
1697
1698       /* We escape all mandatrorry properties that are handled sparetely
1699        * and vtype for StandarTransition as it is the asset ID */
1700       properties = _serialize_properties (G_OBJECT (clip), NULL,
1701           "supported-formats", "rate", "in-point", "start", "duration",
1702           "max-duration", "priority", "vtype", "uri", NULL);
1703       extractable_id = ges_extractable_get_id (GES_EXTRACTABLE (clip));
1704       if (GES_IS_URI_CLIP (clip)) {
1705         G_LOCK (uri_subprojects_map_lock);
1706         if (g_hash_table_contains (priv->subprojects_map, extractable_id))
1707           extractable_id =
1708               g_strdup (g_hash_table_lookup (priv->subprojects_map,
1709                   extractable_id));
1710         G_UNLOCK (uri_subprojects_map_lock);
1711       }
1712       metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (clip));
1713       append_escaped (str,
1714           g_markup_printf_escaped ("        <clip id='%i' asset-id='%s'"
1715               " type-name='%s' layer-priority='%i' track-types='%i' start='%"
1716               G_GUINT64_FORMAT "' duration='%" G_GUINT64_FORMAT "' inpoint='%"
1717               G_GUINT64_FORMAT "' rate='%d' properties='%s' metadatas='%s'",
1718               priv->nbelements, extractable_id,
1719               g_type_name (G_OBJECT_TYPE (clip)), priority,
1720               ges_clip_get_supported_formats (clip), _START (clip),
1721               _DURATION (clip), _INPOINT (clip), 0, properties, metas), depth);
1722       g_free (metas);
1723
1724       if (GES_IS_TRANSITION_CLIP (clip)) {
1725         _save_children_properties (str, GES_TIMELINE_ELEMENT (clip), depth);
1726         self->priv->min_version = MAX (self->priv->min_version, 4);
1727       }
1728       g_string_append (str, ">\n");
1729
1730       g_free (extractable_id);
1731       g_free (properties);
1732
1733       g_hash_table_insert (self->priv->element_id, clip,
1734           GINT_TO_POINTER (priv->nbelements));
1735
1736
1737       /* Effects must always be serialized in the right priority order.
1738        * List order is guaranteed by the fact that ges_clip_get_top_effects
1739        * sorts the effects. */
1740       effects = ges_clip_get_top_effects (clip);
1741       for (tmpeffect = effects; tmpeffect; tmpeffect = tmpeffect->next) {
1742         _save_effect (str, priv->nbelements,
1743             GES_TRACK_ELEMENT (tmpeffect->data), timeline, depth);
1744       }
1745       g_list_free (effects);
1746       tracks = ges_timeline_get_tracks (timeline);
1747
1748       for (tmptrackelement = GES_CONTAINER_CHILDREN (clip); tmptrackelement;
1749           tmptrackelement = tmptrackelement->next) {
1750         _save_source (self, str, tmptrackelement->data, timeline, tracks,
1751             depth);
1752       }
1753       g_list_free_full (tracks, gst_object_unref);
1754
1755       string_append_with_depth (str, "        </clip>\n", depth);
1756
1757       priv->nbelements++;
1758     }
1759     g_list_free_full (clips, (GDestroyNotify) gst_object_unref);
1760     string_append_with_depth (str, "      </layer>\n", depth);
1761   }
1762 }
1763
1764 static void
1765 _save_group (GESXmlFormatter * self, GString * str, GList ** seen_groups,
1766     GESGroup * group, guint depth)
1767 {
1768   GList *tmp;
1769   gboolean serialize;
1770   gchar *properties, *metadatas;
1771
1772   g_object_get (group, "serialize", &serialize, NULL);
1773   if (!serialize) {
1774
1775     GST_DEBUG_OBJECT (group, "Should not be serialized");
1776
1777     return;
1778   }
1779
1780   if (g_list_find (*seen_groups, group)) {
1781     GST_DEBUG_OBJECT (group, "Already serialized");
1782
1783     return;
1784   }
1785
1786   *seen_groups = g_list_prepend (*seen_groups, group);
1787   for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) {
1788     if (GES_IS_GROUP (tmp->data)) {
1789       _save_group (self, str, seen_groups,
1790           GES_GROUP (GES_TIMELINE_ELEMENT (tmp->data)), depth);
1791     }
1792   }
1793
1794   properties = _serialize_properties (G_OBJECT (group), NULL, NULL);
1795
1796   metadatas = ges_meta_container_metas_to_string (GES_META_CONTAINER (group));
1797   self->priv->min_version = MAX (self->priv->min_version, 5);
1798
1799   string_add_indents (str, depth, FALSE);
1800   g_string_append_printf (str,
1801       "        <group id='%d' properties='%s' metadatas='%s'>\n",
1802       self->priv->nbelements, properties, metadatas);
1803   g_free (properties);
1804   g_free (metadatas);
1805   g_hash_table_insert (self->priv->element_id, group,
1806       GINT_TO_POINTER (self->priv->nbelements));
1807   self->priv->nbelements++;
1808
1809   for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) {
1810     gint id = GPOINTER_TO_INT (g_hash_table_lookup (self->priv->element_id,
1811             tmp->data));
1812
1813     string_add_indents (str, depth, FALSE);
1814     g_string_append_printf (str, "          <child id='%d' name='%s'/>\n", id,
1815         GES_TIMELINE_ELEMENT_NAME (tmp->data));
1816   }
1817   string_append_with_depth (str, "        </group>\n", depth);
1818 }
1819
1820 static void
1821 _save_groups (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
1822     guint depth)
1823 {
1824   GList *tmp;
1825   GList *seen_groups = NULL;
1826
1827   string_append_with_depth (str, "      <groups>\n", depth);
1828   for (tmp = ges_timeline_get_groups (timeline); tmp; tmp = tmp->next) {
1829     _save_group (self, str, &seen_groups, tmp->data, depth);
1830   }
1831   g_list_free (seen_groups);
1832   string_append_with_depth (str, "      </groups>\n", depth);
1833 }
1834
1835 static inline void
1836 _save_timeline (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
1837     guint depth)
1838 {
1839   gchar *properties = NULL, *metas = NULL;
1840
1841   properties =
1842       _serialize_properties (G_OBJECT (timeline), NULL, "update", "name",
1843       "async-handling", "message-forward", NULL);
1844
1845   ges_meta_container_set_uint64 (GES_META_CONTAINER (timeline), "duration",
1846       ges_timeline_get_duration (timeline));
1847   metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (timeline));
1848   append_escaped (str,
1849       g_markup_printf_escaped
1850       ("    <timeline properties='%s' metadatas='%s'>\n", properties, metas),
1851       depth);
1852
1853   _save_tracks (self, str, timeline, depth);
1854   _save_layers (self, str, timeline, depth);
1855   _save_groups (self, str, timeline, depth);
1856
1857   string_append_with_depth (str, "    </timeline>\n", depth);
1858
1859   g_free (properties);
1860   g_free (metas);
1861 }
1862
1863 static void
1864 _save_stream_profiles (GESXmlFormatter * self, GString * str,
1865     GstEncodingProfile * sprof, const gchar * profilename, guint id,
1866     guint depth)
1867 {
1868   gchar *tmpc;
1869   GstCaps *tmpcaps;
1870   GstStructure *properties;
1871   const gchar *preset, *preset_name, *name, *description;
1872
1873   append_escaped (str,
1874       g_markup_printf_escaped
1875       ("        <stream-profile parent='%s' id='%d' type='%s' "
1876           "presence='%d' ", profilename, id,
1877           gst_encoding_profile_get_type_nick (sprof),
1878           gst_encoding_profile_get_presence (sprof)), depth);
1879
1880   if (!gst_encoding_profile_is_enabled (sprof)) {
1881     append_escaped (str, g_strdup ("enabled='0' "), depth);
1882
1883     self->priv->min_version = MAX (self->priv->min_version, 2);
1884   }
1885
1886   tmpcaps = gst_encoding_profile_get_format (sprof);
1887   if (tmpcaps) {
1888     tmpc = gst_caps_to_string (tmpcaps);
1889     append_escaped (str, g_markup_printf_escaped ("format='%s' ", tmpc), depth);
1890     gst_caps_unref (tmpcaps);
1891     g_free (tmpc);
1892   }
1893
1894   name = gst_encoding_profile_get_name (sprof);
1895   if (name)
1896     append_escaped (str, g_markup_printf_escaped ("name='%s' ", name), depth);
1897
1898   description = gst_encoding_profile_get_description (sprof);
1899   if (description)
1900     append_escaped (str, g_markup_printf_escaped ("description='%s' ",
1901             description), depth);
1902
1903   preset = gst_encoding_profile_get_preset (sprof);
1904   if (preset) {
1905     append_escaped (str, g_markup_printf_escaped ("preset='%s' ", preset),
1906         depth);
1907   }
1908
1909   properties = gst_encoding_profile_get_element_properties (sprof);
1910   if (properties) {
1911     gchar *props_str = gst_structure_to_string (properties);
1912
1913     append_escaped (str,
1914         g_markup_printf_escaped ("preset-properties='%s' ", props_str), depth);
1915     g_free (props_str);
1916     gst_structure_free (properties);
1917   }
1918
1919   preset_name = gst_encoding_profile_get_preset_name (sprof);
1920   if (preset_name)
1921     append_escaped (str, g_markup_printf_escaped ("preset-name='%s' ",
1922             preset_name), depth);
1923
1924   tmpcaps = gst_encoding_profile_get_restriction (sprof);
1925   if (tmpcaps) {
1926     tmpc = gst_caps_to_string (tmpcaps);
1927     append_escaped (str, g_markup_printf_escaped ("restriction='%s' ", tmpc),
1928         depth);
1929     gst_caps_unref (tmpcaps);
1930     g_free (tmpc);
1931   }
1932
1933   if (GST_IS_ENCODING_VIDEO_PROFILE (sprof)) {
1934     GstEncodingVideoProfile *vp = (GstEncodingVideoProfile *) sprof;
1935
1936     append_escaped (str,
1937         g_markup_printf_escaped ("pass='%d' variableframerate='%i' ",
1938             gst_encoding_video_profile_get_pass (vp),
1939             gst_encoding_video_profile_get_variableframerate (vp)), depth);
1940   }
1941
1942   g_string_append (str, "/>\n");
1943 }
1944
1945 static inline void
1946 _save_encoding_profiles (GESXmlFormatter * self, GString * str,
1947     GESProject * project, guint depth)
1948 {
1949   GstCaps *profformat;
1950   GstStructure *properties;
1951   const gchar *profname, *profdesc, *profpreset, *proftype, *profpresetname;
1952
1953   const GList *tmp;
1954   GList *profiles = g_list_reverse (g_list_copy ((GList *)
1955           ges_project_list_encoding_profiles (project)));
1956
1957   for (tmp = profiles; tmp; tmp = tmp->next) {
1958     GstEncodingProfile *prof = GST_ENCODING_PROFILE (tmp->data);
1959
1960     profname = gst_encoding_profile_get_name (prof);
1961     profdesc = gst_encoding_profile_get_description (prof);
1962     profpreset = gst_encoding_profile_get_preset (prof);
1963     profpresetname = gst_encoding_profile_get_preset_name (prof);
1964     proftype = gst_encoding_profile_get_type_nick (prof);
1965
1966     append_escaped (str,
1967         g_markup_printf_escaped
1968         ("      <encoding-profile name='%s' description='%s' type='%s' ",
1969             profname, profdesc, proftype), depth);
1970
1971     if (profpreset) {
1972       append_escaped (str, g_markup_printf_escaped ("preset='%s' ",
1973               profpreset), depth);
1974     }
1975
1976     properties = gst_encoding_profile_get_element_properties (prof);
1977     if (properties) {
1978       gchar *props_str = gst_structure_to_string (properties);
1979
1980       append_escaped (str,
1981           g_markup_printf_escaped ("preset-properties='%s' ", props_str),
1982           depth);
1983       g_free (props_str);
1984       gst_structure_free (properties);
1985     }
1986
1987     if (profpresetname)
1988       append_escaped (str, g_markup_printf_escaped ("preset-name='%s' ",
1989               profpresetname), depth);
1990
1991     profformat = gst_encoding_profile_get_format (prof);
1992     if (profformat) {
1993       gchar *format = gst_caps_to_string (profformat);
1994       append_escaped (str, g_markup_printf_escaped ("format='%s' ", format),
1995           depth);
1996       g_free (format);
1997       gst_caps_unref (profformat);
1998     }
1999
2000     g_string_append (str, ">\n");
2001
2002     if (GST_IS_ENCODING_CONTAINER_PROFILE (prof)) {
2003       guint i = 0;
2004       const GList *tmp2;
2005       GstEncodingContainerProfile *container_prof;
2006
2007       container_prof = GST_ENCODING_CONTAINER_PROFILE (prof);
2008       for (tmp2 = gst_encoding_container_profile_get_profiles (container_prof);
2009           tmp2; tmp2 = tmp2->next, i++) {
2010         GstEncodingProfile *sprof = (GstEncodingProfile *) tmp2->data;
2011         _save_stream_profiles (self, str, sprof, profname, i, depth);
2012       }
2013     }
2014     append_escaped (str,
2015         g_markup_printf_escaped ("      </encoding-profile>\n"), depth);
2016   }
2017   g_list_free (profiles);
2018 }
2019
2020 static GString *
2021 _save (GESFormatter * formatter, GESTimeline * timeline, GError ** error)
2022 {
2023   GString *str;
2024   GESProject *project;
2025   GESXmlFormatterPrivate *priv = _GET_PRIV (formatter);
2026
2027   priv->min_version = 1;
2028   project = formatter->project;
2029   str = priv->str = g_string_new (NULL);
2030
2031   return _save_project (formatter, str, project, timeline, error, 0);
2032 }
2033
2034 static GString *
2035 _save_project (GESFormatter * formatter, GString * str, GESProject * project,
2036     GESTimeline * timeline, GError ** error, guint depth)
2037 {
2038   gchar *projstr = NULL, *version;
2039   gchar *properties = NULL, *metas = NULL;
2040   GESXmlFormatter *self = GES_XML_FORMATTER (formatter);
2041   GESXmlFormatterPrivate *priv = _GET_PRIV (formatter);
2042
2043   properties = _serialize_properties (G_OBJECT (project), NULL, NULL);
2044   metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (project));
2045   append_escaped (str,
2046       g_markup_printf_escaped ("  <project properties='%s' metadatas='%s'>\n",
2047           properties, metas), depth);
2048   g_free (properties);
2049   g_free (metas);
2050
2051   string_append_with_depth (str, "    <encoding-profiles>\n", depth);
2052   _save_encoding_profiles (GES_XML_FORMATTER (formatter), str, project, depth);
2053   string_append_with_depth (str, "    </encoding-profiles>\n", depth);
2054
2055   string_append_with_depth (str, "    <ressources>\n", depth);
2056   if (!_save_assets (self, str, project, error, depth)) {
2057     g_string_free (str, TRUE);
2058     return NULL;
2059   }
2060   string_append_with_depth (str, "    </ressources>\n", depth);
2061
2062   _save_timeline (self, str, timeline, depth);
2063   string_append_with_depth (str, "  </project>\n", depth);
2064   string_append_with_depth (str, "</ges>\n", depth);
2065
2066   projstr = g_strdup_printf ("<ges version='%i.%i'>\n", API_VERSION,
2067       priv->min_version);
2068   g_string_prepend (str, projstr);
2069   string_add_indents (str, depth, TRUE);
2070   g_free (projstr);
2071
2072   ges_meta_container_set_int (GES_META_CONTAINER (project),
2073       GES_META_FORMAT_VERSION, priv->min_version);
2074
2075   version = g_strdup_printf ("%d.%d", API_VERSION,
2076       GES_XML_FORMATTER (formatter)->priv->min_version);
2077
2078   ges_meta_container_set_string (GES_META_CONTAINER (project),
2079       GES_META_FORMAT_VERSION, version);
2080
2081   g_free (version);
2082
2083   priv->str = NULL;
2084
2085   return str;
2086 }
2087
2088 static void
2089 _setup_subprojects_map (GESXmlFormatterPrivate * priv, const gchar * uri)
2090 {
2091   GHashTable *subprojects_map;
2092
2093   G_LOCK (uri_subprojects_map_lock);
2094   if (!uri_subprojects_map)
2095     uri_subprojects_map =
2096         g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
2097         (GDestroyNotify) g_hash_table_unref);
2098
2099   subprojects_map = g_hash_table_lookup (uri_subprojects_map, uri);
2100   if (!subprojects_map) {
2101     subprojects_map =
2102         g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
2103     g_hash_table_insert (uri_subprojects_map, g_strdup (uri), subprojects_map);
2104   }
2105   priv->subprojects_map = subprojects_map;
2106   G_UNLOCK (uri_subprojects_map_lock);
2107
2108 }
2109
2110 void
2111 ges_xml_formatter_deinit (void)
2112 {
2113   GST_DEBUG ("Deinit");
2114   G_LOCK (uri_subprojects_map_lock);
2115   if (uri_subprojects_map) {
2116     g_hash_table_unref (uri_subprojects_map);
2117     uri_subprojects_map = NULL;
2118   }
2119   G_UNLOCK (uri_subprojects_map_lock);
2120 }
2121
2122 static gboolean
2123 _save_to_uri (GESFormatter * formatter, GESTimeline * timeline,
2124     const gchar * uri, gboolean overwrite, GError ** error)
2125 {
2126   _setup_subprojects_map (_GET_PRIV (formatter), uri);
2127   return GES_FORMATTER_CLASS (parent_class)->save_to_uri (formatter, timeline,
2128       uri, overwrite, error);
2129 }
2130
2131 static gboolean
2132 _can_load_uri (GESFormatter * formatter, const gchar * uri, GError ** error)
2133 {
2134   _setup_subprojects_map (_GET_PRIV (formatter), uri);
2135   return GES_FORMATTER_CLASS (parent_class)->can_load_uri (formatter, uri,
2136       error);
2137 }
2138
2139 static gboolean
2140 _load_from_uri (GESFormatter * formatter, GESTimeline * timeline,
2141     const gchar * uri, GError ** error)
2142 {
2143   _setup_subprojects_map (_GET_PRIV (formatter), uri);
2144   return GES_FORMATTER_CLASS (parent_class)->load_from_uri (formatter, timeline,
2145       uri, error);
2146 }
2147
2148 /***********************************************
2149  *                                             *
2150  *   GObject virtual methods implementation    *
2151  *                                             *
2152  ***********************************************/
2153
2154 static void
2155 _get_property (GObject * object, guint property_id,
2156     GValue * value, GParamSpec * pspec)
2157 {
2158 }
2159
2160 static void
2161 _set_property (GObject * object, guint property_id,
2162     const GValue * value, GParamSpec * pspec)
2163 {
2164 }
2165
2166 static void
2167 ges_xml_formatter_init (GESXmlFormatter * self)
2168 {
2169   GESXmlFormatterPrivate *priv = ges_xml_formatter_get_instance_private (self);
2170
2171   priv->project_opened = FALSE;
2172   priv->element_id = g_hash_table_new (g_direct_hash, g_direct_equal);
2173
2174   self->priv = priv;
2175   self->priv->min_version = 1;
2176 }
2177
2178 static void
2179 _dispose (GObject * object)
2180 {
2181   g_clear_pointer (&GES_XML_FORMATTER (object)->priv->element_id,
2182       g_hash_table_unref);
2183
2184   G_OBJECT_CLASS (parent_class)->dispose (object);
2185 }
2186
2187 static void
2188 ges_xml_formatter_class_init (GESXmlFormatterClass * self_class)
2189 {
2190   GObjectClass *object_class = G_OBJECT_CLASS (self_class);
2191   GESBaseXmlFormatterClass *basexmlformatter_class;
2192   GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (self_class);
2193
2194   basexmlformatter_class = GES_BASE_XML_FORMATTER_CLASS (self_class);
2195
2196   formatter_klass->save_to_uri = _save_to_uri;
2197   formatter_klass->can_load_uri = _can_load_uri;
2198   formatter_klass->load_from_uri = _load_from_uri;
2199
2200   object_class->get_property = _get_property;
2201   object_class->set_property = _set_property;
2202   object_class->dispose = _dispose;
2203
2204   basexmlformatter_class->content_parser.start_element = _parse_element_start;
2205   basexmlformatter_class->content_parser.end_element = _parse_element_end;
2206   basexmlformatter_class->content_parser.text = NULL;
2207   basexmlformatter_class->content_parser.passthrough = NULL;
2208   basexmlformatter_class->content_parser.error = _error_parsing;
2209
2210   ges_formatter_class_register_metas (GES_FORMATTER_CLASS (self_class),
2211       "ges", "GStreamer Editing Services project files",
2212       "xges", "application/xges", VERSION, GST_RANK_PRIMARY);
2213
2214   basexmlformatter_class->save = _save;
2215 }
2216
2217 #undef COLLECT_STR_OPT