structured: Enhance error message when no clip duration set
[platform/upstream/gst-editing-services.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 "ges-internal.h"
34
35 #define parent_class ges_xml_formatter_parent_class
36 #define API_VERSION 0
37 #define MINOR_VERSION 5
38 #define VERSION 0.5
39
40 #define COLLECT_STR_OPT (G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL)
41
42 #define _GET_PRIV(o) (((GESXmlFormatter*)o)->priv)
43
44 struct _GESXmlFormatterPrivate
45 {
46   gboolean ges_opened;
47   gboolean project_opened;
48
49   GString *str;
50
51   GHashTable *element_id;
52
53   guint nbelements;
54
55   guint min_version;
56 };
57
58 G_DEFINE_TYPE_WITH_PRIVATE (GESXmlFormatter, ges_xml_formatter,
59     GES_TYPE_BASE_XML_FORMATTER);
60
61 static inline void
62 _parse_ges_element (GMarkupParseContext * context, const gchar * element_name,
63     const gchar ** attribute_names, const gchar ** attribute_values,
64     GESXmlFormatter * self, GError ** error)
65 {
66   guint api_version;
67   const gchar *version, *properties;
68
69   gchar **split_version = NULL;
70
71   if (g_strcmp0 (element_name, "ges")) {
72     g_set_error (error, G_MARKUP_ERROR,
73         G_MARKUP_ERROR_INVALID_CONTENT,
74         "element '%s', Missing <ges> element'", element_name);
75     return;
76   }
77
78   if (!g_markup_collect_attributes (element_name, attribute_names,
79           attribute_values, error, G_MARKUP_COLLECT_STRING, "version", &version,
80           COLLECT_STR_OPT, "properties", &properties,
81           G_MARKUP_COLLECT_INVALID)) {
82     return;
83   }
84
85   split_version = g_strsplit (version, ".", 2);
86   if (split_version[1] == NULL)
87     goto failed;
88
89   errno = 0;
90   api_version = g_ascii_strtoull (split_version[0], NULL, 10);
91   if (errno || api_version != API_VERSION)
92     goto stroull_failed;
93
94   self->priv->min_version = g_ascii_strtoull (split_version[1], NULL, 10);
95   if (self->priv->min_version > MINOR_VERSION)
96     goto failed;
97
98   _GET_PRIV (self)->ges_opened = TRUE;
99   g_strfreev (split_version);
100   return;
101
102 failed:
103   g_set_error (error, G_MARKUP_ERROR,
104       G_MARKUP_ERROR_INVALID_CONTENT,
105       "element '%s', %s wrong version'", element_name, version);
106   if (split_version)
107     g_strfreev (split_version);
108
109   return;
110
111 stroull_failed:
112   GST_WARNING_OBJECT (self, "Error while strtoull: %s", g_strerror (errno));
113   goto failed;
114 }
115
116 static inline void
117 _parse_project (GMarkupParseContext * context, const gchar * element_name,
118     const gchar ** attribute_names, const gchar ** attribute_values,
119     GESXmlFormatter * self, GError ** error)
120 {
121   const gchar *metadatas = NULL, *properties;
122   GESXmlFormatterPrivate *priv = _GET_PRIV (self);
123
124   if (g_strcmp0 (element_name, "project")) {
125     g_set_error (error, G_MARKUP_ERROR,
126         G_MARKUP_ERROR_INVALID_CONTENT,
127         "Found element '%s', Missing '<project>' element'", element_name);
128   } else {
129     priv->project_opened = TRUE;
130     if (!g_markup_collect_attributes (element_name, attribute_names,
131             attribute_values, error,
132             COLLECT_STR_OPT, "properties", &properties,
133             COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID))
134       return;
135
136     if (GES_FORMATTER (self)->project && metadatas)
137       ges_meta_container_add_metas_from_string (GES_META_CONTAINER
138           (GES_FORMATTER (self)->project), metadatas);
139
140   }
141 }
142
143 static inline void
144 _parse_encoding_profile (GMarkupParseContext * context,
145     const gchar * element_name, const gchar ** attribute_names,
146     const gchar ** attribute_values, GESXmlFormatter * self, GError ** error)
147 {
148   GstCaps *capsformat = NULL;
149   GstStructure *preset_properties = NULL;
150   const gchar *name, *description, *type, *preset = NULL,
151       *str_preset_properties = NULL, *preset_name = NULL, *format;
152
153   if (!g_markup_collect_attributes (element_name, attribute_names,
154           attribute_values, error,
155           G_MARKUP_COLLECT_STRING, "name", &name,
156           G_MARKUP_COLLECT_STRING, "description", &description,
157           G_MARKUP_COLLECT_STRING, "type", &type,
158           COLLECT_STR_OPT, "preset", &preset,
159           COLLECT_STR_OPT, "preset-properties", &str_preset_properties,
160           COLLECT_STR_OPT, "preset-name", &preset_name,
161           COLLECT_STR_OPT, "format", &format, G_MARKUP_COLLECT_INVALID))
162     return;
163
164   if (format)
165     capsformat = gst_caps_from_string (format);
166
167   if (str_preset_properties) {
168     preset_properties = gst_structure_from_string (str_preset_properties, NULL);
169     if (preset_properties == NULL) {
170       g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
171           "element '%s', Wrong preset-properties format.", element_name);
172       return;
173     }
174   }
175
176   ges_base_xml_formatter_add_encoding_profile (GES_BASE_XML_FORMATTER (self),
177       type, NULL, name, description, capsformat, preset, preset_properties,
178       preset_name, 0, 0, NULL, 0, FALSE, NULL, TRUE, error);
179 }
180
181 static inline void
182 _parse_stream_profile (GMarkupParseContext * context,
183     const gchar * element_name, const gchar ** attribute_names,
184     const gchar ** attribute_values, GESXmlFormatter * self, GError ** error)
185 {
186   gboolean variableframerate = FALSE, enabled = TRUE;
187   guint id = 0, presence = 0, pass = 0;
188   GstCaps *format_caps = NULL, *restriction_caps = NULL;
189   GstStructure *preset_properties = NULL;
190   const gchar *parent, *strid, *type, *strpresence, *format = NULL,
191       *name = NULL, *description = NULL, *preset,
192       *str_preset_properties = NULL, *preset_name = NULL, *restriction = NULL,
193       *strpass = NULL, *strvariableframerate = NULL, *strenabled = NULL;
194
195   /* FIXME Looks like there is a bug in that function, if we put the parent
196    * at the beginning it set %NULL and not the real value... :/ */
197   if (!g_markup_collect_attributes (element_name, attribute_names,
198           attribute_values, error,
199           G_MARKUP_COLLECT_STRING, "id", &strid,
200           G_MARKUP_COLLECT_STRING, "type", &type,
201           G_MARKUP_COLLECT_STRING, "presence", &strpresence,
202           COLLECT_STR_OPT, "format", &format,
203           COLLECT_STR_OPT, "name", &name,
204           COLLECT_STR_OPT, "description", &description,
205           COLLECT_STR_OPT, "preset", &preset,
206           COLLECT_STR_OPT, "preset-properties", &str_preset_properties,
207           COLLECT_STR_OPT, "preset-name", &preset_name,
208           COLLECT_STR_OPT, "restriction", &restriction,
209           COLLECT_STR_OPT, "pass", &strpass,
210           COLLECT_STR_OPT, "variableframerate", &strvariableframerate,
211           COLLECT_STR_OPT, "enabled", &strenabled,
212           G_MARKUP_COLLECT_STRING, "parent", &parent, G_MARKUP_COLLECT_INVALID))
213     return;
214
215   errno = 0;
216   id = g_ascii_strtoll (strid, NULL, 10);
217   if (errno)
218     goto convertion_failed;
219
220   if (strpresence) {
221     presence = g_ascii_strtoll (strpresence, NULL, 10);
222     if (errno)
223       goto convertion_failed;
224   }
225
226   if (str_preset_properties) {
227     preset_properties = gst_structure_from_string (str_preset_properties, NULL);
228     if (preset_properties == NULL)
229       goto convertion_failed;
230   }
231
232   if (strpass) {
233     pass = g_ascii_strtoll (strpass, NULL, 10);
234     if (errno)
235       goto convertion_failed;
236   }
237
238   if (strvariableframerate) {
239     variableframerate = g_ascii_strtoll (strvariableframerate, NULL, 10);
240     if (errno)
241       goto convertion_failed;
242   }
243
244   if (strenabled) {
245     enabled = g_ascii_strtoll (strenabled, NULL, 10);
246     if (errno)
247       goto convertion_failed;
248   }
249
250   if (format)
251     format_caps = gst_caps_from_string (format);
252
253   if (restriction)
254     restriction_caps = gst_caps_from_string (restriction);
255
256   ges_base_xml_formatter_add_encoding_profile (GES_BASE_XML_FORMATTER (self),
257       type, parent, name, description, format_caps, preset, preset_properties,
258       preset_name, id, presence, restriction_caps, pass, variableframerate,
259       NULL, enabled, error);
260
261   if (preset_properties)
262     gst_structure_free (preset_properties);
263
264   return;
265
266 convertion_failed:
267   g_set_error (error, G_MARKUP_ERROR,
268       G_MARKUP_ERROR_INVALID_CONTENT,
269       "element '%s', Wrong property type, error: %s'", element_name,
270       g_strerror (errno));
271   return;
272 }
273
274 static inline void
275 _parse_timeline (GMarkupParseContext * context, const gchar * element_name,
276     const gchar ** attribute_names, const gchar ** attribute_values,
277     GESXmlFormatter * self, GError ** error)
278 {
279   const gchar *metadatas = NULL, *properties = NULL;
280   GESTimeline *timeline = GES_FORMATTER (self)->timeline;
281
282   if (!g_markup_collect_attributes (element_name, attribute_names,
283           attribute_values, error,
284           COLLECT_STR_OPT, "properties", &properties,
285           COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID))
286     return;
287
288   if (timeline == NULL)
289     return;
290
291   ges_base_xml_formatter_set_timeline_properties (GES_BASE_XML_FORMATTER (self),
292       timeline, properties, metadatas);
293 }
294
295 static inline void
296 _parse_asset (GMarkupParseContext * context, const gchar * element_name,
297     const gchar ** attribute_names, const gchar ** attribute_values,
298     GESXmlFormatter * self, GError ** error)
299 {
300   GType extractable_type;
301   const gchar *id, *extractable_type_name, *metadatas = NULL, *properties =
302       NULL, *proxy_id = NULL;
303
304   if (!g_markup_collect_attributes (element_name, attribute_names,
305           attribute_values, error, G_MARKUP_COLLECT_STRING, "id", &id,
306           G_MARKUP_COLLECT_STRING, "extractable-type-name",
307           &extractable_type_name,
308           COLLECT_STR_OPT, "properties", &properties,
309           COLLECT_STR_OPT, "metadatas", &metadatas,
310           COLLECT_STR_OPT, "proxy-id", &proxy_id, G_MARKUP_COLLECT_INVALID))
311     return;
312
313   extractable_type = g_type_from_name (extractable_type_name);
314   if (extractable_type == G_TYPE_NONE)
315     g_set_error (error, G_MARKUP_ERROR,
316         G_MARKUP_ERROR_INVALID_CONTENT,
317         "element '%s' invalid extractable_type %s'",
318         element_name, extractable_type_name);
319   else if (!g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE))
320     g_set_error (error, G_MARKUP_ERROR,
321         G_MARKUP_ERROR_INVALID_CONTENT,
322         "element '%s', %s not an extractable_type'",
323         element_name, extractable_type_name);
324   else {
325     GstStructure *props = NULL;
326     if (properties)
327       props = gst_structure_from_string (properties, NULL);
328
329     ges_base_xml_formatter_add_asset (GES_BASE_XML_FORMATTER (self), id,
330         extractable_type, props, metadatas, proxy_id, error);
331     if (props)
332       gst_structure_free (props);
333   }
334 }
335
336
337 static inline void
338 _parse_track (GMarkupParseContext * context, const gchar * element_name,
339     const gchar ** attribute_names, const gchar ** attribute_values,
340     GESXmlFormatter * self, GError ** error)
341 {
342   GstCaps *caps;
343   GESTrackType track_type;
344   GstStructure *props = NULL;
345   const gchar *strtrack_type, *strcaps, *strtrack_id, *metadatas =
346       NULL, *properties = NULL;
347
348   if (!g_markup_collect_attributes (element_name, attribute_names,
349           attribute_values, error,
350           G_MARKUP_COLLECT_STRING, "track-type", &strtrack_type,
351           G_MARKUP_COLLECT_STRING, "track-id", &strtrack_id,
352           COLLECT_STR_OPT, "properties", &properties,
353           COLLECT_STR_OPT, "metadatas", &metadatas,
354           G_MARKUP_COLLECT_STRING, "caps", &strcaps, G_MARKUP_COLLECT_INVALID))
355     return;
356
357   if ((caps = gst_caps_from_string (strcaps)) == NULL)
358     goto wrong_caps;
359
360   errno = 0;
361   track_type = g_ascii_strtoll (strtrack_type, NULL, 10);
362   if (errno)
363     goto convertion_failed;
364
365   if (properties) {
366     props = gst_structure_from_string (properties, NULL);
367   }
368
369   ges_base_xml_formatter_add_track (GES_BASE_XML_FORMATTER (self), track_type,
370       caps, strtrack_id, props, metadatas, error);
371
372   if (props)
373     gst_structure_free (props);
374
375   gst_caps_unref (caps);
376
377   return;
378
379 wrong_caps:
380   g_set_error (error, G_MARKUP_ERROR,
381       G_MARKUP_ERROR_INVALID_CONTENT,
382       "element '%s', Can not create caps: %s'", element_name, strcaps);
383   return;
384
385 convertion_failed:
386   gst_caps_unref (caps);
387   g_set_error (error, G_MARKUP_ERROR,
388       G_MARKUP_ERROR_INVALID_CONTENT,
389       "element '%s', Wrong property type, error: %s'", element_name,
390       g_strerror (errno));
391   return;
392
393 }
394
395 static inline void
396 _parse_layer (GMarkupParseContext * context, const gchar * element_name,
397     const gchar ** attribute_names, const gchar ** attribute_values,
398     GESXmlFormatter * self, GError ** error)
399 {
400   GstStructure *props = NULL;
401   guint priority;
402   GType extractable_type = G_TYPE_NONE;
403   const gchar *metadatas = NULL, *properties = NULL, *strprio = NULL,
404       *extractable_type_name;
405
406   if (!g_markup_collect_attributes (element_name, attribute_names,
407           attribute_values, error,
408           G_MARKUP_COLLECT_STRING, "priority", &strprio,
409           COLLECT_STR_OPT, "extractable-type-name", &extractable_type_name,
410           COLLECT_STR_OPT, "properties", &properties,
411           COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID))
412     return;
413
414   if (extractable_type_name) {
415     extractable_type = g_type_from_name (extractable_type_name);
416     if (extractable_type == G_TYPE_NONE) {
417       g_set_error (error, G_MARKUP_ERROR,
418           G_MARKUP_ERROR_INVALID_CONTENT,
419           "element '%s' invalid extractable_type %s'",
420           element_name, extractable_type_name);
421
422       return;
423     } else if (!g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE)) {
424       g_set_error (error, G_MARKUP_ERROR,
425           G_MARKUP_ERROR_INVALID_CONTENT,
426           "element '%s', %s not an extractable_type'",
427           element_name, extractable_type_name);
428
429       return;
430     }
431   }
432
433   if (properties) {
434     props = gst_structure_from_string (properties, NULL);
435     if (props == NULL)
436       goto wrong_properties;
437   }
438
439   errno = 0;
440   priority = g_ascii_strtoll (strprio, NULL, 10);
441   if (errno)
442     goto convertion_failed;
443
444   ges_base_xml_formatter_add_layer (GES_BASE_XML_FORMATTER (self),
445       extractable_type, priority, props, metadatas, error);
446
447 done:
448   if (props)
449     gst_structure_free (props);
450
451   return;
452
453 convertion_failed:
454   g_set_error (error, G_MARKUP_ERROR,
455       G_MARKUP_ERROR_INVALID_CONTENT,
456       "element '%s', Wrong property type, error: %s'", element_name,
457       g_strerror (errno));
458   goto done;
459
460 wrong_properties:
461   g_set_error (error, G_MARKUP_ERROR,
462       G_MARKUP_ERROR_INVALID_CONTENT,
463       "element '%s', wrong layer properties '%s', could no be deserialized",
464       element_name, properties);
465 }
466
467 static inline void
468 _parse_clip (GMarkupParseContext * context,
469     const gchar * element_name, const gchar ** attribute_names,
470     const gchar ** attribute_values, GESXmlFormatter * self, GError ** error)
471 {
472   GType type;
473   GstStructure *props = NULL, *children_props = NULL;
474   GESTrackType track_types;
475   GstClockTime start, inpoint = 0, duration, layer_prio;
476
477   const gchar *strid, *asset_id, *strstart, *strin, *strduration, *strrate,
478       *strtrack_types, *strtype, *metadatas = NULL, *properties =
479       NULL, *children_properties = NULL, *strlayer_prio;
480
481   if (!g_markup_collect_attributes (element_name, attribute_names,
482           attribute_values, error,
483           G_MARKUP_COLLECT_STRING, "id", &strid,
484           G_MARKUP_COLLECT_STRING, "type-name", &strtype,
485           G_MARKUP_COLLECT_STRING, "start", &strstart,
486           G_MARKUP_COLLECT_STRING, "duration", &strduration,
487           G_MARKUP_COLLECT_STRING, "asset-id", &asset_id,
488           G_MARKUP_COLLECT_STRING, "track-types", &strtrack_types,
489           G_MARKUP_COLLECT_STRING, "layer-priority", &strlayer_prio,
490           COLLECT_STR_OPT, "properties", &properties,
491           COLLECT_STR_OPT, "children-properties", &children_properties,
492           COLLECT_STR_OPT, "metadatas", &metadatas,
493           COLLECT_STR_OPT, "rate", &strrate,
494           COLLECT_STR_OPT, "inpoint", &strin, G_MARKUP_COLLECT_INVALID)) {
495     return;
496   }
497   type = g_type_from_name (strtype);
498   if (!g_type_is_a (type, GES_TYPE_CLIP))
499     goto wrong_type;
500
501   errno = 0;
502   track_types = g_ascii_strtoll (strtrack_types, NULL, 10);
503   if (errno)
504     goto convertion_failed;
505
506   layer_prio = g_ascii_strtoll (strlayer_prio, NULL, 10);
507   if (errno)
508     goto convertion_failed;
509
510   if (strin) {
511     inpoint = g_ascii_strtoull (strin, NULL, 10);
512     if (errno)
513       goto convertion_failed;
514   }
515
516   start = g_ascii_strtoull (strstart, NULL, 10);
517   if (errno)
518     goto convertion_failed;
519
520   duration = g_ascii_strtoull (strduration, NULL, 10);
521   if (errno)
522     goto convertion_failed;
523
524   if (properties) {
525     props = gst_structure_from_string (properties, NULL);
526     if (props == NULL)
527       goto wrong_properties;
528   }
529
530   if (children_properties) {
531     children_props = gst_structure_from_string (children_properties, NULL);
532     if (children_props == NULL)
533       goto wrong_children_properties;
534   }
535
536   ges_base_xml_formatter_add_clip (GES_BASE_XML_FORMATTER (self),
537       strid, asset_id, type, start, inpoint, duration, layer_prio,
538       track_types, props, children_props, metadatas, error);
539   if (props)
540     gst_structure_free (props);
541
542   return;
543
544 wrong_properties:
545   g_set_error (error, G_MARKUP_ERROR,
546       G_MARKUP_ERROR_INVALID_CONTENT,
547       "element '%s', Clip %s properties '%s', could no be deserialized",
548       element_name, asset_id, properties);
549   return;
550
551 wrong_children_properties:
552   g_set_error (error, G_MARKUP_ERROR,
553       G_MARKUP_ERROR_INVALID_CONTENT,
554       "element '%s', Clip %s children properties '%s', could no be deserialized",
555       element_name, asset_id, children_properties);
556   return;
557
558 convertion_failed:
559   g_set_error (error, G_MARKUP_ERROR,
560       G_MARKUP_ERROR_INVALID_CONTENT,
561       "element '%s', Wrong property type, error: %s'", element_name,
562       g_strerror (errno));
563   return;
564
565 wrong_type:
566   g_set_error (error, G_MARKUP_ERROR,
567       G_MARKUP_ERROR_INVALID_CONTENT,
568       "element '%s', %s not a GESClip'", element_name, strtype);
569 }
570
571 static inline void
572 _parse_binding (GMarkupParseContext * context, const gchar * element_name,
573     const gchar ** attribute_names, const gchar ** attribute_values,
574     GESXmlFormatter * self, GError ** error)
575 {
576   const gchar *type = NULL, *source_type = NULL, *timed_values =
577       NULL, *property_name = NULL, *mode = NULL, *track_id = NULL;
578   gchar **pairs, **tmp;
579   gchar *pair;
580   GSList *list = NULL;
581
582   if (!g_markup_collect_attributes (element_name, attribute_names,
583           attribute_values, error,
584           G_MARKUP_COLLECT_STRING, "type", &type,
585           G_MARKUP_COLLECT_STRING, "source_type", &source_type,
586           G_MARKUP_COLLECT_STRING, "property", &property_name,
587           G_MARKUP_COLLECT_STRING, "mode", &mode,
588           G_MARKUP_COLLECT_STRING, "track_id", &track_id,
589           G_MARKUP_COLLECT_STRING, "values", &timed_values,
590           G_MARKUP_COLLECT_INVALID)) {
591     return;
592   }
593
594   pairs = g_strsplit (timed_values, " ", 0);
595   for (tmp = pairs; tmp != NULL; tmp += 1) {
596     gchar **value_pair;
597
598     pair = *tmp;
599     if (pair == NULL)
600       break;
601     if (strlen (pair)) {
602       GstTimedValue *value;
603
604       value = g_slice_new (GstTimedValue);
605       value_pair = g_strsplit (pair, ":", 0);
606       value->timestamp = g_ascii_strtoull (value_pair[0], NULL, 10);
607       value->value = g_ascii_strtod (value_pair[1], NULL);
608       list = g_slist_append (list, value);
609       g_strfreev (value_pair);
610     }
611   }
612
613   g_strfreev (pairs);
614
615   ges_base_xml_formatter_add_control_binding (GES_BASE_XML_FORMATTER (self),
616       type,
617       source_type,
618       property_name, (gint) g_ascii_strtoll (mode, NULL, 10), track_id, list);
619 }
620
621 static inline void
622 _parse_source (GMarkupParseContext * context, const gchar * element_name,
623     const gchar ** attribute_names, const gchar ** attribute_values,
624     GESXmlFormatter * self, GError ** error)
625 {
626   GstStructure *children_props = NULL;
627   const gchar *track_id = NULL, *children_properties = NULL;
628
629   if (!g_markup_collect_attributes (element_name, attribute_names,
630           attribute_values, error,
631           G_MARKUP_COLLECT_STRING, "track-id", &track_id,
632           COLLECT_STR_OPT, "children-properties", &children_properties,
633           G_MARKUP_COLLECT_INVALID)) {
634     return;
635   }
636
637   if (children_properties) {
638     children_props = gst_structure_from_string (children_properties, NULL);
639     if (children_props == NULL)
640       goto wrong_children_properties;
641   }
642
643   ges_base_xml_formatter_add_source (GES_BASE_XML_FORMATTER (self), track_id,
644       children_props);
645
646   if (children_props)
647     gst_structure_free (children_props);
648
649   return;
650
651 wrong_children_properties:
652   g_set_error (error, G_MARKUP_ERROR,
653       G_MARKUP_ERROR_INVALID_CONTENT,
654       "element '%s', children properties '%s', could no be deserialized",
655       element_name, children_properties);
656 }
657
658 static inline void
659 _parse_effect (GMarkupParseContext * context, const gchar * element_name,
660     const gchar ** attribute_names, const gchar ** attribute_values,
661     GESXmlFormatter * self, GError ** error)
662 {
663   GType type;
664
665   GstStructure *children_props = NULL, *props = NULL;
666   const gchar *asset_id = NULL, *strtype = NULL, *track_id =
667       NULL, *metadatas = NULL, *properties = NULL, *track_type = NULL,
668       *children_properties = NULL, *clip_id;
669
670   if (!g_markup_collect_attributes (element_name, attribute_names,
671           attribute_values, error,
672           COLLECT_STR_OPT, "metadatas", &metadatas,
673           G_MARKUP_COLLECT_STRING, "asset-id", &asset_id,
674           G_MARKUP_COLLECT_STRING, "clip-id", &clip_id,
675           G_MARKUP_COLLECT_STRING, "type-name", &strtype,
676           G_MARKUP_COLLECT_STRING, "track-id", &track_id,
677           COLLECT_STR_OPT, "children-properties", &children_properties,
678           COLLECT_STR_OPT, "track-type", &track_type,
679           COLLECT_STR_OPT, "properties", &properties,
680           G_MARKUP_COLLECT_INVALID)) {
681     return;
682   }
683
684   type = g_type_from_name (strtype);
685   if (!g_type_is_a (type, GES_TYPE_BASE_EFFECT))
686     goto wrong_type;
687
688   if (children_properties) {
689     children_props = gst_structure_from_string (children_properties, NULL);
690     if (children_props == NULL)
691       goto wrong_children_properties;
692   }
693
694   if (properties) {
695     props = gst_structure_from_string (properties, NULL);
696     if (props == NULL)
697       goto wrong_properties;
698   }
699
700   ges_base_xml_formatter_add_track_element (GES_BASE_XML_FORMATTER (self),
701       type, asset_id, track_id, clip_id, children_props, props, metadatas,
702       error);
703
704 out:
705
706   if (props)
707     gst_structure_free (props);
708   if (children_props)
709     gst_structure_free (children_props);
710
711   return;
712
713 wrong_properties:
714   g_set_error (error, G_MARKUP_ERROR,
715       G_MARKUP_ERROR_INVALID_CONTENT,
716       "element '%s', Effect %s properties '%s', could no be deserialized",
717       element_name, asset_id, properties);
718   goto out;
719
720 wrong_children_properties:
721   g_set_error (error, G_MARKUP_ERROR,
722       G_MARKUP_ERROR_INVALID_CONTENT,
723       "element '%s', Effect %s children properties '%s', could no be deserialized",
724       element_name, asset_id, children_properties);
725   goto out;
726
727 wrong_type:
728   g_set_error (error, G_MARKUP_ERROR,
729       G_MARKUP_ERROR_INVALID_CONTENT,
730       "element '%s', %s not a GESBaseEffect'", element_name, strtype);
731 }
732
733
734 static inline void
735 _parse_group (GMarkupParseContext * context, const gchar * element_name,
736     const gchar ** attribute_names, const gchar ** attribute_values,
737     GESXmlFormatter * self, GError ** error)
738 {
739   const gchar *id, *properties, *metadatas = NULL;
740
741   if (!g_markup_collect_attributes (element_name, attribute_names,
742           attribute_values, error,
743           G_MARKUP_COLLECT_STRING, "id", &id,
744           G_MARKUP_COLLECT_STRING, "properties", &properties,
745           COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID)) {
746     return;
747   }
748
749   ges_base_xml_formatter_add_group (GES_BASE_XML_FORMATTER (self), id,
750       properties, metadatas);
751 }
752
753 static inline void
754 _parse_group_child (GMarkupParseContext * context, const gchar * element_name,
755     const gchar ** attribute_names, const gchar ** attribute_values,
756     GESXmlFormatter * self, GError ** error)
757 {
758   const gchar *child_id, *name;
759
760   if (!g_markup_collect_attributes (element_name, attribute_names,
761           attribute_values, error,
762           G_MARKUP_COLLECT_STRING, "id", &child_id,
763           G_MARKUP_COLLECT_STRING, "name", &name, G_MARKUP_COLLECT_INVALID)) {
764     return;
765   }
766
767   ges_base_xml_formatter_last_group_add_child (GES_BASE_XML_FORMATTER (self),
768       child_id, name);
769 }
770
771 static void
772 _parse_element_start (GMarkupParseContext * context, const gchar * element_name,
773     const gchar ** attribute_names, const gchar ** attribute_values,
774     gpointer self, GError ** error)
775 {
776   GESXmlFormatterPrivate *priv = _GET_PRIV (self);
777
778   if (!G_UNLIKELY (priv->ges_opened))
779     _parse_ges_element (context, element_name, attribute_names,
780         attribute_values, self, error);
781   else if (!G_UNLIKELY (priv->project_opened))
782     _parse_project (context, element_name, attribute_names, attribute_values,
783         self, error);
784   else if (g_strcmp0 (element_name, "encoding-profile") == 0)
785     _parse_encoding_profile (context, element_name, attribute_names,
786         attribute_values, self, error);
787   else if (g_strcmp0 (element_name, "stream-profile") == 0)
788     _parse_stream_profile (context, element_name, attribute_names,
789         attribute_values, self, error);
790   else if (g_strcmp0 (element_name, "timeline") == 0)
791     _parse_timeline (context, element_name, attribute_names, attribute_values,
792         self, error);
793   else if (g_strcmp0 (element_name, "asset") == 0)
794     _parse_asset (context, element_name, attribute_names, attribute_values,
795         self, error);
796   else if (g_strcmp0 (element_name, "track") == 0)
797     _parse_track (context, element_name, attribute_names,
798         attribute_values, self, error);
799   else if (g_strcmp0 (element_name, "layer") == 0)
800     _parse_layer (context, element_name, attribute_names,
801         attribute_values, self, error);
802   else if (g_strcmp0 (element_name, "clip") == 0)
803     _parse_clip (context, element_name, attribute_names,
804         attribute_values, self, error);
805   else if (g_strcmp0 (element_name, "source") == 0)
806     _parse_source (context, element_name, attribute_names,
807         attribute_values, self, error);
808   else if (g_strcmp0 (element_name, "effect") == 0)
809     _parse_effect (context, element_name, attribute_names,
810         attribute_values, self, error);
811   else if (g_strcmp0 (element_name, "binding") == 0)
812     _parse_binding (context, element_name, attribute_names,
813         attribute_values, self, error);
814   else if (g_strcmp0 (element_name, "group") == 0)
815     _parse_group (context, element_name, attribute_names,
816         attribute_values, self, error);
817   else if (g_strcmp0 (element_name, "child") == 0)
818     _parse_group_child (context, element_name, attribute_names,
819         attribute_values, self, error);
820   else
821     GST_LOG_OBJECT (self, "Element %s not handled", element_name);
822 }
823
824 static void
825 _parse_element_end (GMarkupParseContext * context,
826     const gchar * element_name, gpointer self, GError ** error)
827 {
828   /*GESXmlFormatterPrivate *priv = _GET_PRIV (self); */
829   if (g_strcmp0 (element_name, "ges") == 0 && GES_FORMATTER (self)->project) {
830     gchar *version = g_strdup_printf ("%d.%d",
831         API_VERSION, GES_XML_FORMATTER (self)->priv->min_version);
832
833     ges_meta_container_set_string (GES_META_CONTAINER (GES_FORMATTER
834             (self)->project), GES_META_FORMAT_VERSION, version);
835
836     g_free (version);
837   }
838 }
839
840 static void
841 _error_parsing (GMarkupParseContext * context, GError * error,
842     gpointer user_data)
843 {
844   GST_WARNING ("Error occurred when parsing %s", error->message);
845 }
846
847 /***********************************************
848  *                                             *
849  *            Saving implementation            *
850  *                                             *
851  ***********************************************/
852
853 /* XML writting utils */
854 static inline void
855 append_escaped (GString * str, gchar * tmpstr)
856 {
857   g_string_append (str, tmpstr);
858   g_free (tmpstr);
859 }
860
861 static inline gboolean
862 _can_serialize_spec (GParamSpec * spec)
863 {
864   if (!(spec->flags & G_PARAM_WRITABLE)) {
865     GST_DEBUG ("%s from %s is not writable",
866         spec->name, g_type_name (spec->owner_type));
867
868     return FALSE;
869   } else if (spec->flags & G_PARAM_CONSTRUCT_ONLY) {
870     GST_DEBUG ("%s from %s is construct only",
871         spec->name, g_type_name (spec->owner_type));
872
873     return FALSE;
874   } else if (spec->flags & GES_PARAM_NO_SERIALIZATION &&
875       g_type_is_a (spec->owner_type, GES_TYPE_TIMELINE_ELEMENT)) {
876     GST_DEBUG ("%s from %s is set as GES_PARAM_NO_SERIALIZATION",
877         spec->name, g_type_name (spec->owner_type));
878
879     return FALSE;
880   } else if (g_type_is_a (G_PARAM_SPEC_VALUE_TYPE (spec), G_TYPE_OBJECT)) {
881     GST_DEBUG ("%s from %s contains GObject, can't serialize that.",
882         spec->name, g_type_name (spec->owner_type));
883
884     return FALSE;
885   } else if ((g_type_is_a (spec->owner_type, GST_TYPE_OBJECT) &&
886           !g_strcmp0 (spec->name, "name"))) {
887
888     GST_DEBUG ("We do not want to serialize the name of GstObjects.");
889     return FALSE;
890   } else if (G_PARAM_SPEC_VALUE_TYPE (spec) == G_TYPE_GTYPE) {
891     GST_DEBUG ("%s from %s contains a GType, can't serialize.",
892         spec->name, g_type_name (spec->owner_type));
893     return FALSE;
894   }
895
896   return TRUE;
897 }
898
899 static inline void
900 _init_value_from_spec_for_serialization (GValue * value, GParamSpec * spec)
901 {
902
903   if (g_type_is_a (spec->value_type, G_TYPE_ENUM) ||
904       g_type_is_a (spec->value_type, G_TYPE_FLAGS))
905     g_value_init (value, G_TYPE_INT);
906   else
907     g_value_init (value, spec->value_type);
908 }
909
910 static gchar *
911 _serialize_properties (GObject * object, const gchar * fieldname, ...)
912 {
913   gchar *ret;
914   guint n_props, j;
915   GParamSpec *spec, **pspecs;
916   GObjectClass *class = G_OBJECT_GET_CLASS (object);
917   GstStructure *structure = gst_structure_new_empty ("properties");
918
919   pspecs = g_object_class_list_properties (class, &n_props);
920   for (j = 0; j < n_props; j++) {
921     GValue val = { 0 };
922
923     spec = pspecs[j];
924     if (spec->value_type == GST_TYPE_CAPS) {
925       GstCaps *caps;
926       gchar *caps_str;
927
928       g_object_get (object, spec->name, &caps, NULL);
929       caps_str = gst_caps_to_string (caps);
930       gst_structure_set (structure, spec->name, G_TYPE_STRING, caps_str, NULL);
931       if (caps)
932         gst_caps_unref (caps);
933       g_free (caps_str);
934     } else if (_can_serialize_spec (spec)) {
935       _init_value_from_spec_for_serialization (&val, spec);
936       g_object_get_property (object, spec->name, &val);
937       gst_structure_set_value (structure, spec->name, &val);
938       g_value_unset (&val);
939     }
940   }
941   g_free (pspecs);
942
943   if (fieldname) {
944     va_list varargs;
945     va_start (varargs, fieldname);
946     gst_structure_remove_fields_valist (structure, fieldname, varargs);
947     va_end (varargs);
948   }
949
950   ret = gst_structure_to_string (structure);
951   gst_structure_free (structure);
952
953   return ret;
954 }
955
956 static inline void
957 _save_assets (GESXmlFormatter * self, GString * str, GESProject * project)
958 {
959   char *properties, *metas;
960   GESAsset *asset, *proxy;
961   GList *assets, *tmp;
962
963   assets = ges_project_list_assets (project, GES_TYPE_EXTRACTABLE);
964   for (tmp = assets; tmp; tmp = tmp->next) {
965     asset = GES_ASSET (tmp->data);
966     properties = _serialize_properties (G_OBJECT (asset), NULL);
967     metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (asset));
968     append_escaped (str,
969         g_markup_printf_escaped
970         ("      <asset id='%s' extractable-type-name='%s' properties='%s' metadatas='%s' ",
971             ges_asset_get_id (asset),
972             g_type_name (ges_asset_get_extractable_type (asset)), properties,
973             metas));
974
975     /*TODO Save the whole list of proxies */
976     proxy = ges_asset_get_proxy (asset);
977     if (proxy) {
978       append_escaped (str, g_markup_printf_escaped (" proxy-id='%s' ",
979               ges_asset_get_id (proxy)));
980
981       if (!g_list_find (assets, proxy)) {
982         assets = g_list_append (assets, gst_object_ref (proxy));
983
984         if (!tmp->next)
985           tmp->next = g_list_last (assets);
986       }
987
988       self->priv->min_version = MAX (self->priv->min_version, 3);
989     }
990     g_string_append (str, "/>\n");
991     g_free (properties);
992     g_free (metas);
993   }
994   g_list_free_full (assets, gst_object_unref);
995 }
996
997 static inline void
998 _save_tracks (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
999 {
1000   gchar *strtmp, *metas;
1001   GESTrack *track;
1002   GList *tmp, *tracks;
1003   char *properties;
1004
1005   guint nb_tracks = 0;
1006
1007   tracks = ges_timeline_get_tracks (timeline);
1008   for (tmp = tracks; tmp; tmp = tmp->next) {
1009     track = GES_TRACK (tmp->data);
1010     properties = _serialize_properties (G_OBJECT (track), NULL);
1011     strtmp = gst_caps_to_string (ges_track_get_caps (track));
1012     metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (track));
1013     append_escaped (str,
1014         g_markup_printf_escaped
1015         ("      <track caps='%s' track-type='%i' track-id='%i' properties='%s' metadatas='%s'/>\n",
1016             strtmp, track->type, nb_tracks++, properties, metas));
1017     g_free (strtmp);
1018     g_free (metas);
1019     g_free (properties);
1020   }
1021   g_list_free_full (tracks, gst_object_unref);
1022 }
1023
1024 static inline void
1025 _save_children_properties (GString * str, GESTimelineElement * element)
1026 {
1027   GstStructure *structure;
1028   GParamSpec **pspecs, *spec;
1029   guint i, n_props;
1030   gchar *struct_str;
1031
1032   pspecs = ges_timeline_element_list_children_properties (element, &n_props);
1033
1034   structure = gst_structure_new_empty ("properties");
1035   for (i = 0; i < n_props; i++) {
1036     GValue val = { 0 };
1037     spec = pspecs[i];
1038
1039     if (_can_serialize_spec (spec)) {
1040       gchar *spec_name =
1041           g_strdup_printf ("%s::%s", g_type_name (spec->owner_type),
1042           spec->name);
1043
1044       _init_value_from_spec_for_serialization (&val, spec);
1045       ges_timeline_element_get_child_property_by_pspec (element, spec, &val);
1046       gst_structure_set_value (structure, spec_name, &val);
1047
1048       g_free (spec_name);
1049       g_value_unset (&val);
1050     }
1051     g_param_spec_unref (spec);
1052   }
1053   g_free (pspecs);
1054
1055   struct_str = gst_structure_to_string (structure);
1056   append_escaped (str,
1057       g_markup_printf_escaped (" children-properties='%s'", struct_str));
1058   gst_structure_free (structure);
1059   g_free (struct_str);
1060 }
1061
1062 /* TODO : Use this function for every track element with controllable properties */
1063 static inline void
1064 _save_keyframes (GString * str, GESTrackElement * trackelement, gint index)
1065 {
1066   GHashTable *bindings_hashtable;
1067   GHashTableIter iter;
1068   gpointer key, value;
1069
1070   bindings_hashtable =
1071       ges_track_element_get_all_control_bindings (trackelement);
1072
1073   g_hash_table_iter_init (&iter, bindings_hashtable);
1074
1075   /* We iterate over the bindings, and save the timed values */
1076   while (g_hash_table_iter_next (&iter, &key, &value)) {
1077     if (GST_IS_DIRECT_CONTROL_BINDING ((GstControlBinding *) value)) {
1078       GstControlSource *source;
1079       gboolean absolute = FALSE;
1080       GstDirectControlBinding *binding;
1081
1082       binding = (GstDirectControlBinding *) value;
1083
1084       g_object_get (binding, "control-source", &source,
1085           "absolute", &absolute, NULL);
1086
1087       if (GST_IS_INTERPOLATION_CONTROL_SOURCE (source)) {
1088         GList *timed_values, *tmp;
1089         GstInterpolationMode mode;
1090
1091         append_escaped (str,
1092             g_markup_printf_escaped
1093             ("            <binding type='%s' source_type='interpolation' property='%s'",
1094                 absolute ? "direct-absolute" : "direct", (gchar *) key));
1095
1096         g_object_get (source, "mode", &mode, NULL);
1097         append_escaped (str, g_markup_printf_escaped (" mode='%d'", mode));
1098         append_escaped (str, g_markup_printf_escaped (" track_id='%d'", index));
1099         append_escaped (str, g_markup_printf_escaped (" values ='"));
1100         timed_values =
1101             gst_timed_value_control_source_get_all
1102             (GST_TIMED_VALUE_CONTROL_SOURCE (source));
1103         for (tmp = timed_values; tmp; tmp = tmp->next) {
1104           gchar strbuf[G_ASCII_DTOSTR_BUF_SIZE];
1105           GstTimedValue *value;
1106
1107           value = (GstTimedValue *) tmp->data;
1108           append_escaped (str, g_markup_printf_escaped (" %" G_GUINT64_FORMAT
1109                   ":%s ", value->timestamp, g_ascii_dtostr (strbuf,
1110                       G_ASCII_DTOSTR_BUF_SIZE, value->value)));
1111         }
1112         append_escaped (str, g_markup_printf_escaped ("'/>\n"));
1113       } else
1114         GST_DEBUG ("control source not in [interpolation]");
1115     } else
1116       GST_DEBUG ("Binding type not in [direct, direct-absolute]");
1117   }
1118 }
1119
1120 static inline void
1121 _save_effect (GString * str, guint clip_id, GESTrackElement * trackelement,
1122     GESTimeline * timeline)
1123 {
1124   GESTrack *tck;
1125   GList *tmp, *tracks;
1126   gchar *properties, *metas;
1127   guint track_id = 0;
1128   gboolean serialize;
1129   gchar *extractable_id;
1130
1131   g_object_get (trackelement, "serialize", &serialize, NULL);
1132   if (!serialize) {
1133
1134     GST_DEBUG_OBJECT (trackelement, "Should not be serialized");
1135
1136     return;
1137   }
1138
1139   tck = ges_track_element_get_track (trackelement);
1140   if (tck == NULL) {
1141     GST_WARNING_OBJECT (trackelement, " Not in any track, can not save it");
1142
1143     return;
1144   }
1145
1146   tracks = ges_timeline_get_tracks (timeline);
1147   for (tmp = tracks; tmp; tmp = tmp->next) {
1148     if (tmp->data == tck)
1149       break;
1150     track_id++;
1151   }
1152   g_list_free_full (tracks, gst_object_unref);
1153
1154   properties = _serialize_properties (G_OBJECT (trackelement), "start",
1155       "in-point", "duration", "locked", "max-duration", "name", "priority",
1156       NULL);
1157   metas =
1158       ges_meta_container_metas_to_string (GES_META_CONTAINER (trackelement));
1159   extractable_id = ges_extractable_get_id (GES_EXTRACTABLE (trackelement));
1160   append_escaped (str,
1161       g_markup_printf_escaped ("          <effect asset-id='%s' clip-id='%u'"
1162           " type-name='%s' track-type='%i' track-id='%i' properties='%s' metadatas='%s'",
1163           extractable_id, clip_id,
1164           g_type_name (G_OBJECT_TYPE (trackelement)), tck->type, track_id,
1165           properties, metas));
1166   g_free (extractable_id);
1167   g_free (properties);
1168   g_free (metas);
1169
1170   _save_children_properties (str, GES_TIMELINE_ELEMENT (trackelement));
1171   append_escaped (str, g_markup_printf_escaped (">\n"));
1172
1173   _save_keyframes (str, trackelement, -1);
1174
1175   append_escaped (str, g_markup_printf_escaped ("          </effect>\n"));
1176 }
1177
1178 static inline void
1179 _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
1180 {
1181   gchar *properties, *metas;
1182   GESLayer *layer;
1183   GESClip *clip;
1184   GList *tmplayer, *tmpclip, *clips;
1185   GESXmlFormatterPrivate *priv = self->priv;
1186
1187   for (tmplayer = timeline->layers; tmplayer; tmplayer = tmplayer->next) {
1188     guint priority;
1189     layer = GES_LAYER (tmplayer->data);
1190
1191     priority = ges_layer_get_priority (layer);
1192     properties = _serialize_properties (G_OBJECT (layer), "priority", NULL);
1193     metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (layer));
1194     append_escaped (str,
1195         g_markup_printf_escaped
1196         ("      <layer priority='%i' properties='%s' metadatas='%s'>\n",
1197             priority, properties, metas));
1198     g_free (properties);
1199     g_free (metas);
1200
1201     clips = ges_layer_get_clips (layer);
1202     for (tmpclip = clips; tmpclip; tmpclip = tmpclip->next) {
1203       GList *effects, *tmpeffect;
1204       GList *tmptrackelement;
1205       GList *tracks;
1206       gboolean serialize;
1207       gchar *extractable_id;
1208
1209       clip = GES_CLIP (tmpclip->data);
1210
1211       g_object_get (clip, "serialize", &serialize, NULL);
1212       if (!serialize) {
1213         GST_DEBUG_OBJECT (clip, "Should not be serialized");
1214         continue;
1215       }
1216
1217       /* We escape all mandatrorry properties that are handled sparetely
1218        * and vtype for StandarTransition as it is the asset ID */
1219       properties = _serialize_properties (G_OBJECT (clip),
1220           "supported-formats", "rate", "in-point", "start", "duration",
1221           "max-duration", "priority", "vtype", "uri", NULL);
1222       extractable_id = ges_extractable_get_id (GES_EXTRACTABLE (clip));
1223       metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (clip));
1224       append_escaped (str,
1225           g_markup_printf_escaped ("        <clip id='%i' asset-id='%s'"
1226               " type-name='%s' layer-priority='%i' track-types='%i' start='%"
1227               G_GUINT64_FORMAT "' duration='%" G_GUINT64_FORMAT "' inpoint='%"
1228               G_GUINT64_FORMAT "' rate='%d' properties='%s' metadatas='%s'",
1229               priv->nbelements, extractable_id,
1230               g_type_name (G_OBJECT_TYPE (clip)), priority,
1231               ges_clip_get_supported_formats (clip), _START (clip),
1232               _DURATION (clip), _INPOINT (clip), 0, properties, metas));
1233       g_free (metas);
1234
1235       if (GES_IS_TRANSITION_CLIP (clip)) {
1236         _save_children_properties (str, GES_TIMELINE_ELEMENT (clip));
1237         self->priv->min_version = MAX (self->priv->min_version, 4);
1238       }
1239       g_string_append (str, ">\n");
1240
1241       g_free (extractable_id);
1242       g_free (properties);
1243
1244       g_hash_table_insert (self->priv->element_id, clip,
1245           GINT_TO_POINTER (priv->nbelements));
1246
1247
1248       /* Effects must always be serialized in the right priority order.
1249        * List order is guaranteed by the fact that ges_clip_get_top_effects
1250        * sorts the effects. */
1251       effects = ges_clip_get_top_effects (clip);
1252       for (tmpeffect = effects; tmpeffect; tmpeffect = tmpeffect->next) {
1253         _save_effect (str, priv->nbelements,
1254             GES_TRACK_ELEMENT (tmpeffect->data), timeline);
1255       }
1256       g_list_free (effects);
1257       tracks = ges_timeline_get_tracks (timeline);
1258
1259       for (tmptrackelement = GES_CONTAINER_CHILDREN (clip); tmptrackelement;
1260           tmptrackelement = tmptrackelement->next) {
1261         gint index;
1262         gboolean serialize;
1263
1264         if (!GES_IS_SOURCE (tmptrackelement->data))
1265           continue;
1266
1267         g_object_get (tmptrackelement->data, "serialize", &serialize, NULL);
1268         if (!serialize) {
1269           GST_DEBUG_OBJECT (tmptrackelement->data, "Should not be serialized");
1270           continue;
1271         }
1272
1273         index =
1274             g_list_index (tracks,
1275             ges_track_element_get_track (tmptrackelement->data));
1276         append_escaped (str,
1277             g_markup_printf_escaped ("          <source track-id='%i'", index));
1278         _save_children_properties (str, tmptrackelement->data);
1279         append_escaped (str, g_markup_printf_escaped (">\n"));
1280         _save_keyframes (str, tmptrackelement->data, index);
1281         append_escaped (str, g_markup_printf_escaped ("          </source>\n"));
1282       }
1283
1284       g_list_free_full (tracks, gst_object_unref);
1285
1286       g_string_append (str, "        </clip>\n");
1287
1288       priv->nbelements++;
1289     }
1290     g_list_free_full (clips, (GDestroyNotify) gst_object_unref);
1291     g_string_append (str, "      </layer>\n");
1292   }
1293 }
1294
1295 static void
1296 _save_group (GESXmlFormatter * self, GString * str, GList ** seen_groups,
1297     GESGroup * group)
1298 {
1299   GList *tmp;
1300   gboolean serialize;
1301   gchar *properties, *metadatas;
1302
1303   g_object_get (group, "serialize", &serialize, NULL);
1304   if (!serialize) {
1305
1306     GST_DEBUG_OBJECT (group, "Should not be serialized");
1307
1308     return;
1309   }
1310
1311   if (g_list_find (*seen_groups, group)) {
1312     GST_DEBUG_OBJECT (group, "Already serialized");
1313
1314     return;
1315   }
1316
1317   *seen_groups = g_list_prepend (*seen_groups, group);
1318   for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) {
1319     if (GES_IS_GROUP (tmp->data)) {
1320       _save_group (self, str, seen_groups,
1321           GES_GROUP (GES_TIMELINE_ELEMENT (tmp->data)));
1322     }
1323   }
1324
1325   properties = _serialize_properties (G_OBJECT (group), NULL);
1326
1327   metadatas = ges_meta_container_metas_to_string (GES_META_CONTAINER (group));
1328   self->priv->min_version = MAX (self->priv->min_version, 5);
1329
1330   g_string_append_printf (str,
1331       "        <group id='%d' properties='%s' metadatas='%s'>\n",
1332       self->priv->nbelements, properties, metadatas);
1333   g_free (properties);
1334   g_free (metadatas);
1335   g_hash_table_insert (self->priv->element_id, group,
1336       GINT_TO_POINTER (self->priv->nbelements));
1337   self->priv->nbelements++;
1338
1339   for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) {
1340     gint id = GPOINTER_TO_INT (g_hash_table_lookup (self->priv->element_id,
1341             tmp->data));
1342
1343     g_string_append_printf (str, "          <child id='%d' name='%s'/>\n", id,
1344         GES_TIMELINE_ELEMENT_NAME (tmp->data));
1345   }
1346   g_string_append (str, "        </group>\n");
1347 }
1348
1349 static void
1350 _save_groups (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
1351 {
1352   GList *tmp;
1353   GList *seen_groups = NULL;
1354
1355   g_string_append (str, "      <groups>\n");
1356   for (tmp = ges_timeline_get_groups (timeline); tmp; tmp = tmp->next) {
1357     _save_group (self, str, &seen_groups, tmp->data);
1358   }
1359   g_list_free (seen_groups);
1360   g_string_append (str, "      </groups>\n");
1361 }
1362
1363 static inline void
1364 _save_timeline (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
1365 {
1366   gchar *properties = NULL, *metas = NULL;
1367
1368   properties = _serialize_properties (G_OBJECT (timeline), "update", "name",
1369       "async-handling", "message-forward", NULL);
1370
1371   ges_meta_container_set_uint64 (GES_META_CONTAINER (timeline), "duration",
1372       ges_timeline_get_duration (timeline));
1373   metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (timeline));
1374   append_escaped (str,
1375       g_markup_printf_escaped
1376       ("    <timeline properties='%s' metadatas='%s'>\n", properties, metas));
1377
1378   _save_tracks (self, str, timeline);
1379   _save_layers (self, str, timeline);
1380   _save_groups (self, str, timeline);
1381
1382   g_string_append (str, "    </timeline>\n");
1383
1384   g_free (properties);
1385   g_free (metas);
1386 }
1387
1388 static void
1389 _save_stream_profiles (GESXmlFormatter * self, GString * str,
1390     GstEncodingProfile * sprof, const gchar * profilename, guint id)
1391 {
1392   gchar *tmpc;
1393   GstCaps *tmpcaps;
1394   const gchar *preset, *preset_name, *name, *description;
1395
1396   append_escaped (str,
1397       g_markup_printf_escaped
1398       ("        <stream-profile parent='%s' id='%d' type='%s' "
1399           "presence='%d' ", profilename, id,
1400           gst_encoding_profile_get_type_nick (sprof),
1401           gst_encoding_profile_get_presence (sprof)));
1402
1403   if (!gst_encoding_profile_is_enabled (sprof)) {
1404     append_escaped (str, g_strdup ("enabled='0' "));
1405
1406     self->priv->min_version = MAX (self->priv->min_version, 2);
1407   }
1408
1409   tmpcaps = gst_encoding_profile_get_format (sprof);
1410   if (tmpcaps) {
1411     tmpc = gst_caps_to_string (tmpcaps);
1412     append_escaped (str, g_markup_printf_escaped ("format='%s' ", tmpc));
1413     gst_caps_unref (tmpcaps);
1414     g_free (tmpc);
1415   }
1416
1417   name = gst_encoding_profile_get_name (sprof);
1418   if (name)
1419     append_escaped (str, g_markup_printf_escaped ("name='%s' ", name));
1420
1421   description = gst_encoding_profile_get_description (sprof);
1422   if (description)
1423     append_escaped (str, g_markup_printf_escaped ("description='%s' ",
1424             description));
1425
1426   preset = gst_encoding_profile_get_preset (sprof);
1427   if (preset) {
1428     GstElement *encoder;
1429
1430     append_escaped (str, g_markup_printf_escaped ("preset='%s' ", preset));
1431
1432     encoder = get_element_for_encoding_profile (sprof,
1433         GST_ELEMENT_FACTORY_TYPE_ENCODER);
1434     if (encoder) {
1435       if (GST_IS_PRESET (encoder) &&
1436           gst_preset_load_preset (GST_PRESET (encoder), preset)) {
1437
1438         gchar *settings = _serialize_properties (G_OBJECT (encoder), NULL);
1439         append_escaped (str,
1440             g_markup_printf_escaped ("preset-properties='%s' ", settings));
1441         g_free (settings);
1442       }
1443       gst_object_unref (encoder);
1444     }
1445   }
1446
1447   preset_name = gst_encoding_profile_get_preset_name (sprof);
1448   if (preset_name)
1449     append_escaped (str, g_markup_printf_escaped ("preset-name='%s' ",
1450             preset_name));
1451
1452   tmpcaps = gst_encoding_profile_get_restriction (sprof);
1453   if (tmpcaps) {
1454     tmpc = gst_caps_to_string (tmpcaps);
1455     append_escaped (str, g_markup_printf_escaped ("restriction='%s' ", tmpc));
1456     gst_caps_unref (tmpcaps);
1457     g_free (tmpc);
1458   }
1459
1460   if (GST_IS_ENCODING_VIDEO_PROFILE (sprof)) {
1461     GstEncodingVideoProfile *vp = (GstEncodingVideoProfile *) sprof;
1462
1463     append_escaped (str,
1464         g_markup_printf_escaped ("pass='%d' variableframerate='%i' ",
1465             gst_encoding_video_profile_get_pass (vp),
1466             gst_encoding_video_profile_get_variableframerate (vp)));
1467   }
1468
1469   g_string_append (str, "/>\n");
1470 }
1471
1472 static inline void
1473 _save_encoding_profiles (GESXmlFormatter * self, GString * str,
1474     GESProject * project)
1475 {
1476   GstCaps *profformat;
1477   const gchar *profname, *profdesc, *profpreset, *proftype, *profpresetname;
1478
1479   const GList *tmp;
1480   GList *profiles = g_list_reverse (g_list_copy ((GList *)
1481           ges_project_list_encoding_profiles (project)));
1482
1483   for (tmp = profiles; tmp; tmp = tmp->next) {
1484     GstEncodingProfile *prof = GST_ENCODING_PROFILE (tmp->data);
1485
1486     profname = gst_encoding_profile_get_name (prof);
1487     profdesc = gst_encoding_profile_get_description (prof);
1488     profpreset = gst_encoding_profile_get_preset (prof);
1489     profpresetname = gst_encoding_profile_get_preset_name (prof);
1490     proftype = gst_encoding_profile_get_type_nick (prof);
1491
1492     append_escaped (str,
1493         g_markup_printf_escaped
1494         ("      <encoding-profile name='%s' description='%s' type='%s' ",
1495             profname, profdesc, proftype));
1496
1497     if (profpreset) {
1498       GstElement *element;
1499
1500       append_escaped (str, g_markup_printf_escaped ("preset='%s' ",
1501               profpreset));
1502
1503       if (GST_IS_ENCODING_CONTAINER_PROFILE (prof)) {
1504         element = get_element_for_encoding_profile (prof,
1505             GST_ELEMENT_FACTORY_TYPE_MUXER);
1506       } else {
1507         element = get_element_for_encoding_profile (prof,
1508             GST_ELEMENT_FACTORY_TYPE_ENCODER);
1509       }
1510
1511       if (element) {
1512         if (GST_IS_PRESET (element) &&
1513             gst_preset_load_preset (GST_PRESET (element), profpreset)) {
1514           gchar *settings = _serialize_properties (G_OBJECT (element), NULL);
1515           append_escaped (str,
1516               g_markup_printf_escaped ("preset-properties='%s' ", settings));
1517           g_free (settings);
1518         }
1519         gst_object_unref (element);
1520       }
1521
1522     }
1523
1524     if (profpresetname)
1525       append_escaped (str, g_markup_printf_escaped ("preset-name='%s' ",
1526               profpresetname));
1527
1528     profformat = gst_encoding_profile_get_format (prof);
1529     if (profformat) {
1530       gchar *format = gst_caps_to_string (profformat);
1531       append_escaped (str, g_markup_printf_escaped ("format='%s' ", format));
1532       g_free (format);
1533       gst_caps_unref (profformat);
1534     }
1535
1536     g_string_append (str, ">\n");
1537
1538     if (GST_IS_ENCODING_CONTAINER_PROFILE (prof)) {
1539       guint i = 0;
1540       const GList *tmp2;
1541       GstEncodingContainerProfile *container_prof;
1542
1543       container_prof = GST_ENCODING_CONTAINER_PROFILE (prof);
1544       for (tmp2 = gst_encoding_container_profile_get_profiles (container_prof);
1545           tmp2; tmp2 = tmp2->next, i++) {
1546         GstEncodingProfile *sprof = (GstEncodingProfile *) tmp2->data;
1547         _save_stream_profiles (self, str, sprof, profname, i);
1548       }
1549     }
1550     append_escaped (str,
1551         g_markup_printf_escaped ("      </encoding-profile>\n"));
1552   }
1553   g_list_free (profiles);
1554 }
1555
1556 static GString *
1557 _save (GESFormatter * formatter, GESTimeline * timeline, GError ** error)
1558 {
1559   GString *str;
1560   GESProject *project;
1561
1562   gchar *projstr = NULL, *version;
1563   gchar *properties = NULL, *metas = NULL;
1564   GESXmlFormatter *self = GES_XML_FORMATTER (formatter);
1565   GESXmlFormatterPrivate *priv;
1566
1567
1568   priv = _GET_PRIV (formatter);
1569
1570   priv->min_version = 1;
1571   project = formatter->project;
1572   str = priv->str = g_string_new (NULL);
1573
1574   properties = _serialize_properties (G_OBJECT (project), NULL);
1575   metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (project));
1576   append_escaped (str,
1577       g_markup_printf_escaped ("  <project properties='%s' metadatas='%s'>\n",
1578           properties, metas));
1579   g_free (properties);
1580   g_free (metas);
1581
1582   g_string_append (str, "    <encoding-profiles>\n");
1583   _save_encoding_profiles (GES_XML_FORMATTER (formatter), str, project);
1584   g_string_append (str, "    </encoding-profiles>\n");
1585
1586   g_string_append (str, "    <ressources>\n");
1587   _save_assets (self, str, project);
1588   g_string_append (str, "    </ressources>\n");
1589
1590   _save_timeline (self, str, timeline);
1591   g_string_append (str, "</project>\n</ges>");
1592
1593   projstr = g_strdup_printf ("<ges version='%i.%i'>\n", API_VERSION,
1594       priv->min_version);
1595   g_string_prepend (str, projstr);
1596   g_free (projstr);
1597
1598   ges_meta_container_set_int (GES_META_CONTAINER (project),
1599       GES_META_FORMAT_VERSION, priv->min_version);
1600
1601   version = g_strdup_printf ("%d.%d", API_VERSION,
1602       GES_XML_FORMATTER (formatter)->priv->min_version);
1603
1604   ges_meta_container_set_string (GES_META_CONTAINER (project),
1605       GES_META_FORMAT_VERSION, version);
1606
1607   g_free (version);
1608
1609   priv->str = NULL;
1610
1611   return str;
1612 }
1613
1614 /***********************************************
1615  *                                             *
1616  *   GObject virtual methods implementation    *
1617  *                                             *
1618  ***********************************************/
1619
1620 static void
1621 _get_property (GObject * object, guint property_id,
1622     GValue * value, GParamSpec * pspec)
1623 {
1624 }
1625
1626 static void
1627 _set_property (GObject * object, guint property_id,
1628     const GValue * value, GParamSpec * pspec)
1629 {
1630 }
1631
1632 static void
1633 ges_xml_formatter_init (GESXmlFormatter * self)
1634 {
1635   GESXmlFormatterPrivate *priv = ges_xml_formatter_get_instance_private (self);
1636
1637   priv->project_opened = FALSE;
1638   priv->element_id = g_hash_table_new (g_direct_hash, g_direct_equal);
1639
1640   self->priv = priv;
1641   self->priv->min_version = 1;
1642 }
1643
1644 static void
1645 _dispose (GObject * object)
1646 {
1647   g_clear_pointer (&GES_XML_FORMATTER (object)->priv->element_id,
1648       g_hash_table_unref);
1649
1650   G_OBJECT_CLASS (parent_class)->dispose (object);
1651 }
1652
1653 static void
1654 ges_xml_formatter_class_init (GESXmlFormatterClass * self_class)
1655 {
1656   GObjectClass *object_class = G_OBJECT_CLASS (self_class);
1657   GESBaseXmlFormatterClass *basexmlformatter_class;
1658
1659   basexmlformatter_class = GES_BASE_XML_FORMATTER_CLASS (self_class);
1660
1661   object_class->get_property = _get_property;
1662   object_class->set_property = _set_property;
1663   object_class->dispose = _dispose;
1664
1665   basexmlformatter_class->content_parser.start_element = _parse_element_start;
1666   basexmlformatter_class->content_parser.end_element = _parse_element_end;
1667   basexmlformatter_class->content_parser.text = NULL;
1668   basexmlformatter_class->content_parser.passthrough = NULL;
1669   basexmlformatter_class->content_parser.error = _error_parsing;
1670
1671   ges_formatter_class_register_metas (GES_FORMATTER_CLASS (self_class),
1672       "ges", "GStreamer Editing Services project files",
1673       "xges", "application/ges", VERSION, GST_RANK_PRIMARY);
1674
1675   basexmlformatter_class->save = _save;
1676 }
1677
1678 #undef COLLECT_STR_OPT