1 /* GStreamer Editing Services Pitivi Formatter
2  * Copyright (C) 2011-2012 Mathieu Duponchelle <seeed@laposte.net>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 /**
21  * SECTION: gespitiviformatter
22  * @title: GESPitiviFormatter
23  * @short_description: A formatter for the obsolete Pitivi xptv project file format
24  *
25  * This is a legacy format and you should avoid to use it. The formatter
26  * is really not in good shape and is deprecated.
27  *
28  * Deprecated: 1.0
29  */
30 #ifdef HAVE_CONFIG_H
31 #include "config.h"
32 #undef VERSION
33 #endif
34 
35 #include <libxml/xmlreader.h>
36 #include <libxml/tree.h>
37 #include <libxml/parser.h>
38 #include <libxml/xpath.h>
39 #include <libxml/xpathInternals.h>
40 #include <libxml/encoding.h>
41 #include <libxml/xmlwriter.h>
42 
43 #include "ges-internal.h"
44 #include <ges/ges-pitivi-formatter.h>
45 #include <ges/ges.h>
46 
47 /* The Pitivi etree formatter is 0.1 we set GES one to 0.2 */
48 //#define VERSION "0.2"
49 #define DOUBLE_VERSION 0.2
50 
51 #undef GST_CAT_DEFAULT
52 GST_DEBUG_CATEGORY_STATIC (ges_pitivi_formatter_debug);
53 #define GST_CAT_DEFAULT ges_pitivi_formatter_debug
54 
55 typedef struct SrcMapping
56 {
57   gchar *id;
58   GESClip *clip;
59   guint priority;
60   GList *track_element_ids;
61 } SrcMapping;
62 
63 struct _GESPitiviFormatterPrivate
64 {
65   xmlXPathContextPtr xpathCtx;
66 
67   /* {"sourceId" : {"prop": "value"}} */
68   GHashTable *sources_table;
69 
70   /* Used as a set of the uris */
71   GHashTable *source_uris;
72 
73   /* {trackId: {"factory_ref": factoryId, ""}
74    * if effect:
75    *      {"factory_ref": "effect",
76    *       "effect_name": name
77    *       "effect_props": {"propname": value}}}
78    */
79   GHashTable *track_elements_table;
80 
81   /* {factory-ref: [track-object-ref-id,...]} */
82   GHashTable *clips_table;
83 
84   /* {layerPriority: layer} */
85   GHashTable *layers_table;
86 
87   GESTimeline *timeline;
88 
89   GESTrack *tracka, *trackv;
90 
91   /* List the Clip that haven't been loaded yet */
92   GList *sources_to_load;
93 
94   /* Saving context */
95   /* {factory_id: uri} */
96   GHashTable *saving_source_table;
97   guint nb_sources;
98 };
99 
100 G_DEFINE_TYPE_WITH_PRIVATE (GESPitiviFormatter, ges_pitivi_formatter,
101     GES_TYPE_FORMATTER);
102 
103 
104 static void
list_table_destroyer(gpointer key,gpointer value,void * unused)105 list_table_destroyer (gpointer key, gpointer value, void *unused)
106 {
107   g_list_foreach (value, (GFunc) g_free, NULL);
108   g_list_free (value);
109 }
110 
111 static gboolean
pitivi_can_load_uri(GESFormatter * dummy_instance,const gchar * uri,GError ** error)112 pitivi_can_load_uri (GESFormatter * dummy_instance, const gchar * uri,
113     GError ** error)
114 {
115   xmlDocPtr doc;
116   gboolean ret = TRUE;
117   xmlXPathObjectPtr xpathObj;
118   xmlXPathContextPtr xpathCtx;
119   gchar *filename = g_filename_from_uri (uri, NULL, NULL);
120 
121   if (!filename || !g_file_test (filename, G_FILE_TEST_EXISTS)) {
122     g_free (filename);
123     return FALSE;
124   }
125 
126   g_free (filename);
127 
128   if (!(doc = xmlParseFile (uri))) {
129     GST_ERROR ("The xptv file for uri %s was badly formed", uri);
130     return FALSE;
131   }
132 
133   xpathCtx = xmlXPathNewContext (doc);
134   xpathObj = xmlXPathEvalExpression ((const xmlChar *) "/pitivi", xpathCtx);
135   if (!xpathObj || !xpathObj->nodesetval || xpathObj->nodesetval->nodeNr == 0)
136     ret = FALSE;
137 
138   xmlFreeDoc (doc);
139   xmlXPathFreeObject (xpathObj);
140   xmlXPathFreeContext (xpathCtx);
141 
142   return ret;
143 }
144 
145 /* Project loading functions */
146 
147 /* Return: a GHashTable containing:
148  *    {attr: value}
149  */
150 static GHashTable *
get_nodes_infos(xmlNodePtr node)151 get_nodes_infos (xmlNodePtr node)
152 {
153   xmlAttr *cur_attr;
154   GHashTable *props_table;
155   gchar *name, *value;
156 
157   props_table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
158 
159   for (cur_attr = node->properties; cur_attr; cur_attr = cur_attr->next) {
160     name = (gchar *) cur_attr->name;
161     value = (gchar *) xmlGetProp (node, cur_attr->name);
162     g_hash_table_insert (props_table, g_strdup (name), g_strdup (value));
163     xmlFree (value);
164   }
165 
166   return props_table;
167 }
168 
169 static gboolean
create_tracks(GESFormatter * self)170 create_tracks (GESFormatter * self)
171 {
172   GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
173   GList *tracks = NULL;
174 
175   tracks = ges_timeline_get_tracks (self->timeline);
176 
177   GST_DEBUG ("Creating tracks, current number of tracks %d",
178       g_list_length (tracks));
179 
180   if (tracks) {
181     GList *tmp = NULL;
182     GESTrack *track;
183     for (tmp = tracks; tmp; tmp = tmp->next) {
184       track = tmp->data;
185       if (track->type == GES_TRACK_TYPE_AUDIO) {
186         priv->tracka = track;
187       } else {
188         priv->trackv = track;
189       }
190     }
191     g_list_foreach (tracks, (GFunc) gst_object_unref, NULL);
192     g_list_free (tracks);
193     return TRUE;
194   }
195 
196   priv->tracka = GES_TRACK (ges_audio_track_new ());
197   priv->trackv = GES_TRACK (ges_video_track_new ());
198 
199   if (!ges_timeline_add_track (self->timeline, priv->trackv)) {
200     return FALSE;
201   }
202 
203   if (!ges_timeline_add_track (self->timeline, priv->tracka)) {
204     return FALSE;
205   }
206 
207   return TRUE;
208 }
209 
210 static void
parse_metadatas(GESFormatter * self)211 parse_metadatas (GESFormatter * self)
212 {
213   guint i, size;
214   xmlNodePtr node;
215   xmlAttr *cur_attr;
216   xmlNodeSetPtr nodes;
217   xmlXPathObjectPtr xpathObj;
218   GESMetaContainer *metacontainer = GES_META_CONTAINER (self->project);
219 
220   xpathObj = xmlXPathEvalExpression ((const xmlChar *)
221       "/pitivi/metadata", GES_PITIVI_FORMATTER (self)->priv->xpathCtx);
222   nodes = xpathObj->nodesetval;
223 
224   size = (nodes) ? nodes->nodeNr : 0;
225   for (i = 0; i < size; i++) {
226     node = nodes->nodeTab[i];
227     for (cur_attr = node->properties; cur_attr; cur_attr = cur_attr->next) {
228       ges_meta_container_set_string (metacontainer, (gchar *) cur_attr->name,
229           (gchar *) xmlGetProp (node, cur_attr->name));
230     }
231   }
232 }
233 
234 static void
list_sources(GESFormatter * self)235 list_sources (GESFormatter * self)
236 {
237   GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
238   xmlXPathObjectPtr xpathObj;
239   GHashTable *table;
240   int size, j;
241   gchar *id, *filename;
242   xmlNodeSetPtr nodes;
243 
244   xpathObj = xmlXPathEvalExpression ((const xmlChar *)
245       "/pitivi/factories/sources/source", priv->xpathCtx);
246   nodes = xpathObj->nodesetval;
247 
248   size = (nodes) ? nodes->nodeNr : 0;
249   for (j = 0; j < size; ++j) {
250     table = get_nodes_infos (nodes->nodeTab[j]);
251     id = (gchar *) g_hash_table_lookup (table, (gchar *) "id");
252     filename = (gchar *) g_hash_table_lookup (table, (gchar *) "filename");
253     g_hash_table_insert (priv->sources_table, g_strdup (id), table);
254     g_hash_table_insert (priv->source_uris, g_strdup (filename),
255         g_strdup (filename));
256     if (self->project)
257       ges_project_create_asset (self->project, filename, GES_TYPE_URI_CLIP);
258   }
259 
260   xmlXPathFreeObject (xpathObj);
261 }
262 
263 static gboolean
parse_track_elements(GESFormatter * self)264 parse_track_elements (GESFormatter * self)
265 {
266   GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
267   xmlXPathObjectPtr xpathObj;
268   xmlNodeSetPtr nodes;
269   int size, j;
270   gchar *id, *fac_ref;
271   GHashTable *table = NULL, *effect_table = NULL;
272   xmlNode *first_child;
273   gchar *media_type;
274 
275   /* FIXME Make this whole function cleaner starting from
276    * "/pitivi/timeline/tracks/track/stream" and descending
277    * into the children. */
278   xpathObj = xmlXPathEvalExpression ((const xmlChar *)
279       "/pitivi/timeline/tracks/track/track-objects/track-object",
280       priv->xpathCtx);
281 
282   if (xpathObj == NULL) {
283     GST_DEBUG ("No track object found");
284 
285     return FALSE;
286   }
287 
288   nodes = xpathObj->nodesetval;
289   size = (nodes) ? nodes->nodeNr : 0;
290 
291   for (j = 0; j < size; ++j) {
292     xmlNodePtr node = nodes->nodeTab[j];
293 
294     table = get_nodes_infos (nodes->nodeTab[j]);
295     id = (gchar *) g_hash_table_lookup (table, (gchar *) "id");
296     first_child = nodes->nodeTab[j]->children->next;
297     fac_ref = (gchar *) xmlGetProp (first_child, (xmlChar *) "id");
298 
299     /* We check if the first child is "effect" */
300     if (!g_strcmp0 ((gchar *) first_child->name, (gchar *) "effect")) {
301       xmlChar *effect_name;
302       xmlNodePtr fact_node = first_child->children->next;
303 
304       /* We have a node called "text" in between thus ->next->next */
305       xmlNodePtr elem_props_node = fact_node->next->next;
306 
307       effect_name = xmlGetProp (fact_node, (xmlChar *) "name");
308       g_hash_table_insert (table, g_strdup ((gchar *) "effect_name"),
309           g_strdup ((gchar *) effect_name));
310       xmlFree (effect_name);
311 
312       /* We put the effects properties in an hacktable (Lapsus is on :) */
313       effect_table = get_nodes_infos (elem_props_node);
314 
315       g_hash_table_insert (table, g_strdup ((gchar *) "fac_ref"),
316           g_strdup ("effect"));
317 
318       xmlFree (fac_ref);
319     } else {
320 
321       g_hash_table_insert (table, g_strdup ((gchar *) "fac_ref"),
322           g_strdup (fac_ref));
323       xmlFree (fac_ref);
324     }
325 
326     /* Same as before, we got a text node in between, thus the 2 prev
327      * node->parent is <track-objects>, the one before is <stream>
328      */
329     media_type = (gchar *) xmlGetProp (node->parent->prev->prev,
330         (const xmlChar *) "type");
331     g_hash_table_insert (table, g_strdup ((gchar *) "media_type"),
332         g_strdup (media_type));
333     xmlFree (media_type);
334 
335 
336     if (effect_table)
337       g_hash_table_insert (table, g_strdup ("effect_props"), effect_table);
338 
339     g_hash_table_insert (priv->track_elements_table, g_strdup (id), table);
340   }
341 
342   xmlXPathFreeObject (xpathObj);
343   return TRUE;
344 }
345 
346 static gboolean
parse_clips(GESFormatter * self)347 parse_clips (GESFormatter * self)
348 {
349   int size, j;
350   xmlNodeSetPtr nodes;
351   xmlXPathObjectPtr xpathObj;
352   xmlNodePtr clip_nd, tmp_nd, tmp_nd2;
353   xmlChar *trackelementrefId, *facrefId = NULL;
354 
355   GList *reflist = NULL;
356   GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
357   GHashTable *clips_table = priv->clips_table;
358 
359   xpathObj = xmlXPathEvalExpression ((const xmlChar *)
360       "/pitivi/timeline/timeline-objects/timeline-object", priv->xpathCtx);
361 
362   if (xpathObj == NULL) {
363     xmlXPathFreeObject (xpathObj);
364     return FALSE;
365   }
366 
367   nodes = xpathObj->nodesetval;
368   size = (nodes) ? nodes->nodeNr : 0;
369 
370   for (j = 0; j < size; j++) {
371     clip_nd = nodes->nodeTab[j];
372 
373     for (tmp_nd = clip_nd->children; tmp_nd; tmp_nd = tmp_nd->next) {
374       /* We assume that factory-ref is always before the tckobjs-ref */
375       if (!xmlStrcmp (tmp_nd->name, (xmlChar *) "factory-ref")) {
376         facrefId = xmlGetProp (tmp_nd, (xmlChar *) "id");
377 
378       } else if (!xmlStrcmp (tmp_nd->name, (xmlChar *) "track-object-refs")) {
379 
380         for (tmp_nd2 = tmp_nd->children; tmp_nd2; tmp_nd2 = tmp_nd2->next) {
381           if (!xmlStrcmp (tmp_nd2->name, (xmlChar *) "track-object-ref")) {
382             /* We add the track object ref ID to the list of the current
383              * Clip tracks, this way we can merge 2
384              * Clip-s into 1 when we have unlinked TrackElement-s */
385             reflist = g_hash_table_lookup (clips_table, facrefId);
386             trackelementrefId = xmlGetProp (tmp_nd2, (xmlChar *) "id");
387             reflist =
388                 g_list_append (reflist, g_strdup ((gchar *) trackelementrefId));
389             g_hash_table_insert (clips_table, g_strdup ((gchar *) facrefId),
390                 reflist);
391 
392             xmlFree (trackelementrefId);
393           }
394         }
395       }
396     }
397   }
398 
399   xmlXPathFreeObject (xpathObj);
400   return TRUE;
401 }
402 
403 static void
set_properties(GObject * obj,GHashTable * props_table)404 set_properties (GObject * obj, GHashTable * props_table)
405 {
406   gint i;
407   gchar **prop_array, *valuestr;
408   gint64 value;
409 
410   gchar props[3][10] = { "duration", "in_point", "start" };
411 
412   for (i = 0; i < 3; i++) {
413     valuestr = g_hash_table_lookup (props_table, props[i]);
414     prop_array = g_strsplit (valuestr, ")", 0);
415     value = g_ascii_strtoll (prop_array[1], NULL, 0);
416     g_object_set (obj, props[i], value, NULL);
417 
418     g_strfreev (prop_array);
419   }
420 }
421 
422 static void
track_element_added_cb(GESClip * clip,GESTrackElement * track_element,GHashTable * props_table)423 track_element_added_cb (GESClip * clip,
424     GESTrackElement * track_element, GHashTable * props_table)
425 {
426   GESPitiviFormatter *formatter;
427 
428   formatter = GES_PITIVI_FORMATTER (g_hash_table_lookup (props_table,
429           "current-formatter"));
430   if (formatter) {
431     GESPitiviFormatterPrivate *priv = formatter->priv;
432 
433     /* Make sure the hack to get a ref to the formatter
434      * doesn't break everything */
435     g_hash_table_steal (props_table, "current-formatter");
436 
437     priv->sources_to_load = g_list_remove (priv->sources_to_load, clip);
438     if (!priv->sources_to_load && GES_FORMATTER (formatter)->project)
439       ges_project_set_loaded (GES_FORMATTER (formatter)->project,
440           GES_FORMATTER (formatter));
441   }
442 
443   /* Disconnect the signal */
444   g_signal_handlers_disconnect_by_func (clip, track_element_added_cb,
445       props_table);
446 }
447 
448 static void
make_source(GESFormatter * self,GList * reflist,GHashTable * source_table)449 make_source (GESFormatter * self, GList * reflist, GHashTable * source_table)
450 {
451   GHashTable *props_table, *effect_table;
452   gchar **prio_array;
453   GESLayer *layer;
454   GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
455 
456   gchar *fac_ref = NULL, *media_type = NULL, *filename = NULL, *prio_str;
457   GList *tmp = NULL, *keys, *tmp_key;
458   GESUriClip *src = NULL;
459   gint prio;
460   gboolean a_avail = FALSE, v_avail = FALSE, video;
461   GHashTable *trackelement_table = priv->track_elements_table;
462 
463   for (tmp = reflist; tmp; tmp = tmp->next) {
464 
465     /* Get the layer */
466     props_table = g_hash_table_lookup (trackelement_table, (gchar *) tmp->data);
467     prio_str = (gchar *) g_hash_table_lookup (props_table, "priority");
468     prio_array = g_strsplit (prio_str, ")", 0);
469     prio = (gint) g_ascii_strtod (prio_array[1], NULL);
470     g_strfreev (prio_array);
471 
472     /* If we do not have any layer with this priority, create it */
473     if (!(layer = g_hash_table_lookup (priv->layers_table, &prio))) {
474       layer = ges_layer_new ();
475       g_object_set (layer, "auto-transition", TRUE, "priority", prio, NULL);
476       ges_timeline_add_layer (self->timeline, layer);
477       g_hash_table_insert (priv->layers_table, g_memdup (&prio,
478               sizeof (guint64)), layer);
479     }
480 
481     fac_ref = (gchar *) g_hash_table_lookup (props_table, "fac_ref");
482     media_type = (gchar *) g_hash_table_lookup (props_table, "media_type");
483 
484     if (!g_strcmp0 (media_type, "pitivi.stream.VideoStream"))
485       video = TRUE;
486     else
487       video = FALSE;
488 
489     /* FIXME I am sure we could reimplement this whole part
490      * in a simpler way */
491 
492     if (g_strcmp0 (fac_ref, (gchar *) "effect")) {
493       /* FIXME this is a hack to get a ref to the formatter when receiving
494        * child-added */
495       g_hash_table_insert (props_table, (gchar *) "current-formatter", self);
496       if (a_avail && (!video)) {
497         a_avail = FALSE;
498       } else if (v_avail && (video)) {
499         v_avail = FALSE;
500       } else {
501 
502         /* If we only have audio or only video in the previous source,
503          * set it has such */
504         if (a_avail) {
505           ges_clip_set_supported_formats (GES_CLIP (src), GES_TRACK_TYPE_VIDEO);
506         } else if (v_avail) {
507           ges_clip_set_supported_formats (GES_CLIP (src), GES_TRACK_TYPE_AUDIO);
508         }
509 
510         filename = (gchar *) g_hash_table_lookup (source_table, "filename");
511 
512         src = ges_uri_clip_new (filename);
513 
514         if (!video) {
515           v_avail = TRUE;
516           a_avail = FALSE;
517         } else {
518           a_avail = TRUE;
519           v_avail = FALSE;
520         }
521 
522         set_properties (G_OBJECT (src), props_table);
523         ges_layer_add_clip (layer, GES_CLIP (src));
524 
525         g_signal_connect (src, "child-added",
526             G_CALLBACK (track_element_added_cb), props_table);
527 
528         priv->sources_to_load = g_list_prepend (priv->sources_to_load, src);
529       }
530 
531     } else {
532       GESEffect *effect;
533       gchar *active = (gchar *) g_hash_table_lookup (props_table, "active");
534 
535       effect = ges_effect_new ((gchar *)
536           g_hash_table_lookup (props_table, (gchar *) "effect_name"));
537       ges_track_element_set_track_type (GES_TRACK_ELEMENT (effect),
538           (video ? GES_TRACK_TYPE_VIDEO : GES_TRACK_TYPE_AUDIO));
539       effect_table =
540           g_hash_table_lookup (props_table, (gchar *) "effect_props");
541 
542       ges_container_add (GES_CONTAINER (src), GES_TIMELINE_ELEMENT (effect));
543 
544       if (!g_strcmp0 (active, (gchar *) "(bool)False"))
545         ges_track_element_set_active (GES_TRACK_ELEMENT (effect), FALSE);
546 
547       /* Set effect properties */
548       keys = g_hash_table_get_keys (effect_table);
549       for (tmp_key = keys; tmp_key; tmp_key = tmp_key->next) {
550         GstStructure *structure;
551         const GValue *value;
552         GParamSpec *spec;
553         GstCaps *caps;
554         gchar *prop_val;
555 
556         prop_val = (gchar *) g_hash_table_lookup (effect_table,
557             (gchar *) tmp_key->data);
558 
559         if (g_strstr_len (prop_val, -1, "(GEnum)")) {
560           gchar **val = g_strsplit (prop_val, ")", 2);
561 
562           ges_track_element_set_child_properties (GES_TRACK_ELEMENT (effect),
563               (gchar *) tmp_key->data, atoi (val[1]), NULL);
564           g_strfreev (val);
565 
566         } else if (ges_track_element_lookup_child (GES_TRACK_ELEMENT (effect),
567                 (gchar *) tmp->data, NULL, &spec)) {
568           gchar *caps_str = g_strdup_printf ("structure1, property1=%s;",
569               prop_val);
570 
571           caps = gst_caps_from_string (caps_str);
572           g_free (caps_str);
573           structure = gst_caps_get_structure (caps, 0);
574           value = gst_structure_get_value (structure, "property1");
575 
576           ges_track_element_set_child_property_by_pspec (GES_TRACK_ELEMENT
577               (effect), spec, (GValue *) value);
578           gst_caps_unref (caps);
579         }
580       }
581     }
582   }
583 
584   if (a_avail) {
585     ges_clip_set_supported_formats (GES_CLIP (src), GES_TRACK_TYPE_VIDEO);
586   } else if (v_avail) {
587     ges_clip_set_supported_formats (GES_CLIP (src), GES_TRACK_TYPE_AUDIO);
588   }
589 }
590 
591 static gboolean
make_clips(GESFormatter * self)592 make_clips (GESFormatter * self)
593 {
594   GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
595   GHashTable *source_table;
596 
597   GList *keys = NULL, *tmp = NULL, *reflist = NULL;
598 
599   keys = g_hash_table_get_keys (priv->clips_table);
600 
601   for (tmp = keys; tmp; tmp = tmp->next) {
602     gchar *fac_id = (gchar *) tmp->data;
603 
604     reflist = g_hash_table_lookup (priv->clips_table, fac_id);
605     source_table = g_hash_table_lookup (priv->sources_table, fac_id);
606     make_source (self, reflist, source_table);
607   }
608 
609   g_list_free (keys);
610   return TRUE;
611 }
612 
613 static gboolean
load_pitivi_file_from_uri(GESFormatter * self,GESTimeline * timeline,const gchar * uri,GError ** error)614 load_pitivi_file_from_uri (GESFormatter * self,
615     GESTimeline * timeline, const gchar * uri, GError ** error)
616 {
617   xmlDocPtr doc;
618   GESLayer *layer;
619   GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
620 
621   gboolean ret = TRUE;
622   gint *prio = malloc (sizeof (gint));
623 
624   *prio = 0;
625   layer = ges_layer_new ();
626   g_object_set (layer, "auto-transition", TRUE, NULL);
627 
628   g_hash_table_insert (priv->layers_table, prio, layer);
629   g_object_set (layer, "priority", (gint32) 0, NULL);
630 
631   if (!ges_timeline_add_layer (timeline, layer)) {
632     GST_ERROR ("Couldn't add layer");
633     return FALSE;
634   }
635 
636   if (!(doc = xmlParseFile (uri))) {
637     GST_ERROR ("The xptv file for uri %s was badly formed or did not exist",
638         uri);
639     return FALSE;
640   }
641 
642   priv->xpathCtx = xmlXPathNewContext (doc);
643 
644   if (self->project)
645     parse_metadatas (self);
646 
647   if (!create_tracks (self)) {
648     GST_ERROR ("Couldn't create tracks");
649     return FALSE;
650   }
651 
652   list_sources (self);
653 
654   if (!parse_clips (self)) {
655     GST_ERROR ("Couldn't find clips markup in the xptv file");
656     return FALSE;
657   }
658 
659   if (!parse_track_elements (self)) {
660     GST_ERROR ("Couldn't find track objects markup in the xptv file");
661     return FALSE;
662   }
663 
664 
665 
666   /* If there are no clips to load we should emit
667    * 'project-loaded' signal.
668    */
669   if (!g_hash_table_size (priv->clips_table) && GES_FORMATTER (self)->project) {
670     ges_project_set_loaded (GES_FORMATTER (self)->project,
671         GES_FORMATTER (self));
672   } else {
673     if (!make_clips (self)) {
674       GST_ERROR ("Couldn't deserialise the project properly");
675       return FALSE;
676     }
677   }
678 
679   xmlXPathFreeContext (priv->xpathCtx);
680   xmlFreeDoc (doc);
681   return ret;
682 }
683 
684 /* Object functions */
685 static void
ges_pitivi_formatter_finalize(GObject * object)686 ges_pitivi_formatter_finalize (GObject * object)
687 {
688   GESPitiviFormatter *self = GES_PITIVI_FORMATTER (object);
689   GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
690 
691   g_hash_table_destroy (priv->sources_table);
692   g_hash_table_destroy (priv->source_uris);
693 
694   g_hash_table_destroy (priv->saving_source_table);
695   g_list_free (priv->sources_to_load);
696 
697   if (priv->clips_table != NULL) {
698     g_hash_table_foreach (priv->clips_table,
699         (GHFunc) list_table_destroyer, NULL);
700     g_hash_table_destroy (priv->clips_table);
701   }
702 
703   if (priv->layers_table != NULL)
704     g_hash_table_destroy (priv->layers_table);
705 
706   if (priv->track_elements_table != NULL) {
707     g_hash_table_destroy (priv->track_elements_table);
708   }
709 
710   G_OBJECT_CLASS (ges_pitivi_formatter_parent_class)->finalize (object);
711 }
712 
713 static void
ges_pitivi_formatter_class_init(GESPitiviFormatterClass * klass)714 ges_pitivi_formatter_class_init (GESPitiviFormatterClass * klass)
715 {
716   GESFormatterClass *formatter_klass;
717   GObjectClass *object_class;
718 
719   GST_DEBUG_CATEGORY_INIT (ges_pitivi_formatter_debug, "ges_pitivi_formatter",
720       GST_DEBUG_FG_YELLOW, "ges pitivi formatter");
721 
722   object_class = G_OBJECT_CLASS (klass);
723   formatter_klass = GES_FORMATTER_CLASS (klass);
724 
725   formatter_klass->can_load_uri = pitivi_can_load_uri;
726   formatter_klass->save_to_uri = NULL;
727   formatter_klass->load_from_uri = load_pitivi_file_from_uri;
728   object_class->finalize = ges_pitivi_formatter_finalize;
729 
730   ges_formatter_class_register_metas (formatter_klass, "pitivi",
731       "Legacy Pitivi project files", "xptv", "text/x-xptv",
732       DOUBLE_VERSION, GST_RANK_MARGINAL);
733 }
734 
735 static void
ges_pitivi_formatter_init(GESPitiviFormatter * self)736 ges_pitivi_formatter_init (GESPitiviFormatter * self)
737 {
738   GESPitiviFormatterPrivate *priv;
739 
740   self->priv = ges_pitivi_formatter_get_instance_private (self);
741 
742   priv = self->priv;
743 
744   priv->track_elements_table =
745       g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
746       (GDestroyNotify) g_hash_table_destroy);
747 
748   priv->clips_table =
749       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
750 
751   priv->layers_table =
752       g_hash_table_new_full (g_int_hash, g_str_equal, g_free, gst_object_unref);
753 
754   priv->sources_table =
755       g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
756       (GDestroyNotify) g_hash_table_destroy);
757 
758   priv->source_uris =
759       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
760 
761   priv->sources_to_load = NULL;
762 
763   /* Saving context */
764   priv->saving_source_table =
765       g_hash_table_new_full (g_str_hash, g_int_equal, g_free, g_free);
766   priv->nb_sources = 1;
767 }
768 
769 GESPitiviFormatter *
ges_pitivi_formatter_new(void)770 ges_pitivi_formatter_new (void)
771 {
772   return g_object_new (GES_TYPE_PITIVI_FORMATTER, NULL);
773 }
774