1 /* GStreamer Editing Services Pitivi Formatter
2 * Copyright (C) 2011-2012 Mathieu Duponchelle <seeed@laposte.net>
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.
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.
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.
21 * SECTION: gespitiviformatter
22 * @title: GESPitiviFormatter
23 * @short_description: A formatter for the obsolete Pitivi xptv project file format
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.
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>
43 #include "ges-internal.h"
44 #include <ges/ges-pitivi-formatter.h>
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
51 #undef GST_CAT_DEFAULT
52 GST_DEBUG_CATEGORY_STATIC (ges_pitivi_formatter_debug);
53 #define GST_CAT_DEFAULT ges_pitivi_formatter_debug
55 typedef struct SrcMapping
60 GList *track_element_ids;
63 struct _GESPitiviFormatterPrivate
65 xmlXPathContextPtr xpathCtx;
67 /* {"sourceId" : {"prop": "value"}} */
68 GHashTable *sources_table;
70 /* Used as a set of the uris */
71 GHashTable *source_uris;
73 /* {trackId: {"factory_ref": factoryId, ""}
75 * {"factory_ref": "effect",
77 * "effect_props": {"propname": value}}}
79 GHashTable *track_elements_table;
81 /* {factory-ref: [track-object-ref-id,...]} */
82 GHashTable *clips_table;
84 /* {layerPriority: layer} */
85 GHashTable *layers_table;
87 GESTimeline *timeline;
89 GESTrack *tracka, *trackv;
91 /* List the Clip that haven't been loaded yet */
92 GList *sources_to_load;
95 /* {factory_id: uri} */
96 GHashTable *saving_source_table;
100 G_DEFINE_TYPE_WITH_PRIVATE (GESPitiviFormatter, ges_pitivi_formatter,
105 list_table_destroyer (gpointer key, gpointer value, void *unused)
107 g_list_foreach (value, (GFunc) g_free, NULL);
112 pitivi_can_load_uri (GESFormatter * dummy_instance, const gchar * uri,
117 xmlXPathObjectPtr xpathObj;
118 xmlXPathContextPtr xpathCtx;
119 gchar *filename = g_filename_from_uri (uri, NULL, NULL);
121 if (!filename || !g_file_test (filename, G_FILE_TEST_EXISTS)) {
128 if (!(doc = xmlParseFile (uri))) {
129 GST_ERROR ("The xptv file for uri %s was badly formed", uri);
133 xpathCtx = xmlXPathNewContext (doc);
134 xpathObj = xmlXPathEvalExpression ((const xmlChar *) "/pitivi", xpathCtx);
135 if (!xpathObj || !xpathObj->nodesetval || xpathObj->nodesetval->nodeNr == 0)
139 xmlXPathFreeObject (xpathObj);
140 xmlXPathFreeContext (xpathCtx);
145 /* Project loading functions */
147 /* Return: a GHashTable containing:
151 get_nodes_infos (xmlNodePtr node)
154 GHashTable *props_table;
157 props_table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
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));
170 create_tracks (GESFormatter * self)
172 GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
173 GList *tracks = NULL;
175 tracks = ges_timeline_get_tracks (self->timeline);
177 GST_DEBUG ("Creating tracks, current number of tracks %d",
178 g_list_length (tracks));
183 for (tmp = tracks; tmp; tmp = tmp->next) {
185 if (track->type == GES_TRACK_TYPE_AUDIO) {
186 priv->tracka = track;
188 priv->trackv = track;
191 g_list_foreach (tracks, (GFunc) gst_object_unref, NULL);
192 g_list_free (tracks);
196 priv->tracka = GES_TRACK (ges_audio_track_new ());
197 priv->trackv = GES_TRACK (ges_video_track_new ());
199 if (!ges_timeline_add_track (self->timeline, priv->trackv)) {
203 if (!ges_timeline_add_track (self->timeline, priv->tracka)) {
211 parse_metadatas (GESFormatter * self)
217 xmlXPathObjectPtr xpathObj;
218 GESMetaContainer *metacontainer = GES_META_CONTAINER (self->project);
220 xpathObj = xmlXPathEvalExpression ((const xmlChar *)
221 "/pitivi/metadata", GES_PITIVI_FORMATTER (self)->priv->xpathCtx);
222 nodes = xpathObj->nodesetval;
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));
233 xmlXPathFreeObject (xpathObj);
237 list_sources (GESFormatter * self)
239 GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
240 xmlXPathObjectPtr xpathObj;
243 gchar *id, *filename;
246 xpathObj = xmlXPathEvalExpression ((const xmlChar *)
247 "/pitivi/factories/sources/source", priv->xpathCtx);
248 nodes = xpathObj->nodesetval;
250 size = (nodes) ? nodes->nodeNr : 0;
251 for (j = 0; j < size; ++j) {
252 table = get_nodes_infos (nodes->nodeTab[j]);
253 id = (gchar *) g_hash_table_lookup (table, (gchar *) "id");
254 filename = (gchar *) g_hash_table_lookup (table, (gchar *) "filename");
255 g_hash_table_insert (priv->sources_table, g_strdup (id), table);
256 g_hash_table_insert (priv->source_uris, g_strdup (filename),
257 g_strdup (filename));
259 ges_project_create_asset (self->project, filename, GES_TYPE_URI_CLIP);
262 xmlXPathFreeObject (xpathObj);
266 parse_track_elements (GESFormatter * self)
268 GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
269 xmlXPathObjectPtr xpathObj;
273 GHashTable *table = NULL, *effect_table = NULL;
274 xmlNode *first_child;
277 /* FIXME Make this whole function cleaner starting from
278 * "/pitivi/timeline/tracks/track/stream" and descending
279 * into the children. */
280 xpathObj = xmlXPathEvalExpression ((const xmlChar *)
281 "/pitivi/timeline/tracks/track/track-objects/track-object",
284 if (xpathObj == NULL) {
285 GST_DEBUG ("No track object found");
290 nodes = xpathObj->nodesetval;
291 size = (nodes) ? nodes->nodeNr : 0;
293 for (j = 0; j < size; ++j) {
294 xmlNodePtr node = nodes->nodeTab[j];
296 table = get_nodes_infos (nodes->nodeTab[j]);
297 id = (gchar *) g_hash_table_lookup (table, (gchar *) "id");
298 first_child = nodes->nodeTab[j]->children->next;
299 fac_ref = (gchar *) xmlGetProp (first_child, (xmlChar *) "id");
301 /* We check if the first child is "effect" */
302 if (!g_strcmp0 ((gchar *) first_child->name, (gchar *) "effect")) {
303 xmlChar *effect_name;
304 xmlNodePtr fact_node = first_child->children->next;
306 /* We have a node called "text" in between thus ->next->next */
307 xmlNodePtr elem_props_node = fact_node->next->next;
309 effect_name = xmlGetProp (fact_node, (xmlChar *) "name");
310 g_hash_table_insert (table, g_strdup ((gchar *) "effect_name"),
311 g_strdup ((gchar *) effect_name));
312 xmlFree (effect_name);
314 /* We put the effects properties in an hacktable (Lapsus is on :) */
315 effect_table = get_nodes_infos (elem_props_node);
317 g_hash_table_insert (table, g_strdup ((gchar *) "fac_ref"),
318 g_strdup ("effect"));
323 g_hash_table_insert (table, g_strdup ((gchar *) "fac_ref"),
328 /* Same as before, we got a text node in between, thus the 2 prev
329 * node->parent is <track-objects>, the one before is <stream>
331 media_type = (gchar *) xmlGetProp (node->parent->prev->prev,
332 (const xmlChar *) "type");
333 g_hash_table_insert (table, g_strdup ((gchar *) "media_type"),
334 g_strdup (media_type));
335 xmlFree (media_type);
339 g_hash_table_insert (table, g_strdup ("effect_props"), effect_table);
341 g_hash_table_insert (priv->track_elements_table, g_strdup (id), table);
344 xmlXPathFreeObject (xpathObj);
349 parse_clips (GESFormatter * self)
353 xmlXPathObjectPtr xpathObj;
354 xmlNodePtr clip_nd, tmp_nd, tmp_nd2;
355 xmlChar *trackelementrefId, *facrefId = NULL;
357 GList *reflist = NULL;
358 GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
359 GHashTable *clips_table = priv->clips_table;
361 xpathObj = xmlXPathEvalExpression ((const xmlChar *)
362 "/pitivi/timeline/timeline-objects/timeline-object", priv->xpathCtx);
364 if (xpathObj == NULL) {
365 xmlXPathFreeObject (xpathObj);
369 nodes = xpathObj->nodesetval;
370 size = (nodes) ? nodes->nodeNr : 0;
372 for (j = 0; j < size; j++) {
373 clip_nd = nodes->nodeTab[j];
375 for (tmp_nd = clip_nd->children; tmp_nd; tmp_nd = tmp_nd->next) {
376 /* We assume that factory-ref is always before the tckobjs-ref */
377 if (!xmlStrcmp (tmp_nd->name, (xmlChar *) "factory-ref")) {
378 facrefId = xmlGetProp (tmp_nd, (xmlChar *) "id");
380 } else if (!xmlStrcmp (tmp_nd->name, (xmlChar *) "track-object-refs")) {
382 for (tmp_nd2 = tmp_nd->children; tmp_nd2; tmp_nd2 = tmp_nd2->next) {
383 if (!xmlStrcmp (tmp_nd2->name, (xmlChar *) "track-object-ref")) {
384 /* We add the track object ref ID to the list of the current
385 * Clip tracks, this way we can merge 2
386 * Clip-s into 1 when we have unlinked TrackElement-s */
387 reflist = g_hash_table_lookup (clips_table, facrefId);
388 trackelementrefId = xmlGetProp (tmp_nd2, (xmlChar *) "id");
390 g_list_append (reflist, g_strdup ((gchar *) trackelementrefId));
391 g_hash_table_insert (clips_table, g_strdup ((gchar *) facrefId),
394 xmlFree (trackelementrefId);
401 xmlXPathFreeObject (xpathObj);
406 set_properties (GObject * obj, GHashTable * props_table)
409 gchar **prop_array, *valuestr;
412 gchar props[3][10] = { "duration", "in_point", "start" };
414 for (i = 0; i < 3; i++) {
415 valuestr = g_hash_table_lookup (props_table, props[i]);
416 prop_array = g_strsplit (valuestr, ")", 0);
417 value = g_ascii_strtoll (prop_array[1], NULL, 0);
418 g_object_set (obj, props[i], value, NULL);
420 g_strfreev (prop_array);
425 track_element_added_cb (GESClip * clip,
426 GESTrackElement * track_element, GHashTable * props_table)
428 GESPitiviFormatter *formatter;
430 formatter = GES_PITIVI_FORMATTER (g_hash_table_lookup (props_table,
431 "current-formatter"));
433 GESPitiviFormatterPrivate *priv = formatter->priv;
435 /* Make sure the hack to get a ref to the formatter
436 * doesn't break everything */
437 g_hash_table_steal (props_table, "current-formatter");
439 priv->sources_to_load = g_list_remove (priv->sources_to_load, clip);
440 if (!priv->sources_to_load && GES_FORMATTER (formatter)->project)
441 ges_project_set_loaded (GES_FORMATTER (formatter)->project,
442 GES_FORMATTER (formatter), NULL);
445 /* Disconnect the signal */
446 g_signal_handlers_disconnect_by_func (clip, track_element_added_cb,
451 make_source (GESFormatter * self, GList * reflist, GHashTable * source_table)
453 GHashTable *props_table, *effect_table;
456 GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
458 gchar *fac_ref = NULL, *media_type = NULL, *filename = NULL, *prio_str;
459 GList *tmp = NULL, *keys, *tmp_key;
460 GESUriClip *src = NULL;
462 gboolean a_avail = FALSE, v_avail = FALSE, video;
463 GHashTable *trackelement_table = priv->track_elements_table;
465 for (tmp = reflist; tmp; tmp = tmp->next) {
468 props_table = g_hash_table_lookup (trackelement_table, (gchar *) tmp->data);
469 prio_str = (gchar *) g_hash_table_lookup (props_table, "priority");
470 prio_array = g_strsplit (prio_str, ")", 0);
471 prio = (gint) g_ascii_strtod (prio_array[1], NULL);
472 g_strfreev (prio_array);
474 /* If we do not have any layer with this priority, create it */
475 if (!(layer = g_hash_table_lookup (priv->layers_table, &prio))) {
476 layer = ges_layer_new ();
477 g_object_set (layer, "auto-transition", TRUE, "priority", prio, NULL);
478 ges_timeline_add_layer (self->timeline, layer);
479 g_hash_table_insert (priv->layers_table, g_memdup (&prio,
480 sizeof (guint64)), layer);
483 fac_ref = (gchar *) g_hash_table_lookup (props_table, "fac_ref");
484 media_type = (gchar *) g_hash_table_lookup (props_table, "media_type");
486 if (!g_strcmp0 (media_type, "pitivi.stream.VideoStream"))
491 /* FIXME I am sure we could reimplement this whole part
492 * in a simpler way */
494 if (g_strcmp0 (fac_ref, (gchar *) "effect")) {
495 /* FIXME this is a hack to get a ref to the formatter when receiving
497 g_hash_table_insert (props_table, (gchar *) "current-formatter", self);
498 if (a_avail && (!video)) {
500 } else if (v_avail && (video)) {
504 /* If we only have audio or only video in the previous source,
507 ges_clip_set_supported_formats (GES_CLIP (src), GES_TRACK_TYPE_VIDEO);
508 } else if (v_avail) {
509 ges_clip_set_supported_formats (GES_CLIP (src), GES_TRACK_TYPE_AUDIO);
512 filename = (gchar *) g_hash_table_lookup (source_table, "filename");
514 src = ges_uri_clip_new (filename);
524 set_properties (G_OBJECT (src), props_table);
525 ges_layer_add_clip (layer, GES_CLIP (src));
527 g_signal_connect (src, "child-added",
528 G_CALLBACK (track_element_added_cb), props_table);
530 priv->sources_to_load = g_list_prepend (priv->sources_to_load, src);
535 gchar *active = (gchar *) g_hash_table_lookup (props_table, "active");
537 effect = ges_effect_new ((gchar *)
538 g_hash_table_lookup (props_table, (gchar *) "effect_name"));
539 ges_track_element_set_track_type (GES_TRACK_ELEMENT (effect),
540 (video ? GES_TRACK_TYPE_VIDEO : GES_TRACK_TYPE_AUDIO));
542 g_hash_table_lookup (props_table, (gchar *) "effect_props");
544 if (!ges_container_add (GES_CONTAINER (src),
545 GES_TIMELINE_ELEMENT (effect))) {
546 GST_ERROR ("%p could not add %p while"
547 " reloading, this should never happen", src, effect);
550 if (!g_strcmp0 (active, (gchar *) "(bool)False"))
551 ges_track_element_set_active (GES_TRACK_ELEMENT (effect), FALSE);
553 /* Set effect properties */
554 keys = g_hash_table_get_keys (effect_table);
555 for (tmp_key = keys; tmp_key; tmp_key = tmp_key->next) {
556 GstStructure *structure;
562 prop_val = (gchar *) g_hash_table_lookup (effect_table,
563 (gchar *) tmp_key->data);
565 if (g_strstr_len (prop_val, -1, "(GEnum)")) {
566 gchar **val = g_strsplit (prop_val, ")", 2);
568 ges_track_element_set_child_properties (GES_TRACK_ELEMENT (effect),
569 (gchar *) tmp_key->data, atoi (val[1]), NULL);
572 } else if (ges_track_element_lookup_child (GES_TRACK_ELEMENT (effect),
573 (gchar *) tmp->data, NULL, &spec)) {
574 gchar *caps_str = g_strdup_printf ("structure1, property1=%s;",
577 caps = gst_caps_from_string (caps_str);
579 structure = gst_caps_get_structure (caps, 0);
580 value = gst_structure_get_value (structure, "property1");
582 ges_track_element_set_child_property_by_pspec (GES_TRACK_ELEMENT
583 (effect), spec, (GValue *) value);
584 gst_caps_unref (caps);
591 ges_clip_set_supported_formats (GES_CLIP (src), GES_TRACK_TYPE_VIDEO);
592 } else if (v_avail) {
593 ges_clip_set_supported_formats (GES_CLIP (src), GES_TRACK_TYPE_AUDIO);
598 make_clips (GESFormatter * self)
600 GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
601 GHashTable *source_table;
603 GList *keys = NULL, *tmp = NULL, *reflist = NULL;
605 keys = g_hash_table_get_keys (priv->clips_table);
607 for (tmp = keys; tmp; tmp = tmp->next) {
608 gchar *fac_id = (gchar *) tmp->data;
610 reflist = g_hash_table_lookup (priv->clips_table, fac_id);
611 source_table = g_hash_table_lookup (priv->sources_table, fac_id);
612 make_source (self, reflist, source_table);
620 load_pitivi_file_from_uri (GESFormatter * self,
621 GESTimeline * timeline, const gchar * uri, GError ** error)
625 GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
628 gint *prio = malloc (sizeof (gint));
631 layer = ges_layer_new ();
632 g_object_set (layer, "auto-transition", TRUE, NULL);
634 g_hash_table_insert (priv->layers_table, prio, layer);
635 g_object_set (layer, "priority", (gint32) 0, NULL);
637 if (!ges_timeline_add_layer (timeline, layer)) {
638 GST_ERROR ("Couldn't add layer");
642 if (!(doc = xmlParseFile (uri))) {
643 GST_ERROR ("The xptv file for uri %s was badly formed or did not exist",
648 priv->xpathCtx = xmlXPathNewContext (doc);
651 parse_metadatas (self);
653 if (!create_tracks (self)) {
654 GST_ERROR ("Couldn't create tracks");
660 if (!parse_clips (self)) {
661 GST_ERROR ("Couldn't find clips markup in the xptv file");
665 if (!parse_track_elements (self)) {
666 GST_ERROR ("Couldn't find track objects markup in the xptv file");
672 /* If there are no clips to load we should emit
673 * 'project-loaded' signal.
675 if (!g_hash_table_size (priv->clips_table) && GES_FORMATTER (self)->project) {
676 ges_project_set_loaded (GES_FORMATTER (self)->project,
677 GES_FORMATTER (self), NULL);
679 if (!make_clips (self)) {
680 GST_ERROR ("Couldn't deserialise the project properly");
685 xmlXPathFreeContext (priv->xpathCtx);
690 /* Object functions */
692 ges_pitivi_formatter_finalize (GObject * object)
694 GESPitiviFormatter *self = GES_PITIVI_FORMATTER (object);
695 GESPitiviFormatterPrivate *priv = GES_PITIVI_FORMATTER (self)->priv;
697 g_hash_table_destroy (priv->sources_table);
698 g_hash_table_destroy (priv->source_uris);
700 g_hash_table_destroy (priv->saving_source_table);
701 g_list_free (priv->sources_to_load);
703 if (priv->clips_table != NULL) {
704 g_hash_table_foreach (priv->clips_table,
705 (GHFunc) list_table_destroyer, NULL);
706 g_hash_table_destroy (priv->clips_table);
709 if (priv->layers_table != NULL)
710 g_hash_table_destroy (priv->layers_table);
712 if (priv->track_elements_table != NULL) {
713 g_hash_table_destroy (priv->track_elements_table);
716 G_OBJECT_CLASS (ges_pitivi_formatter_parent_class)->finalize (object);
720 ges_pitivi_formatter_class_init (GESPitiviFormatterClass * klass)
722 GESFormatterClass *formatter_klass;
723 GObjectClass *object_class;
725 GST_DEBUG_CATEGORY_INIT (ges_pitivi_formatter_debug, "ges_pitivi_formatter",
726 GST_DEBUG_FG_YELLOW, "ges pitivi formatter");
728 object_class = G_OBJECT_CLASS (klass);
729 formatter_klass = GES_FORMATTER_CLASS (klass);
731 formatter_klass->can_load_uri = pitivi_can_load_uri;
732 formatter_klass->save_to_uri = NULL;
733 formatter_klass->load_from_uri = load_pitivi_file_from_uri;
734 object_class->finalize = ges_pitivi_formatter_finalize;
736 ges_formatter_class_register_metas (formatter_klass, "pitivi",
737 "Legacy Pitivi project files", "xptv", "text/x-xptv",
738 DOUBLE_VERSION, GST_RANK_MARGINAL);
742 ges_pitivi_formatter_init (GESPitiviFormatter * self)
744 GESPitiviFormatterPrivate *priv;
746 self->priv = ges_pitivi_formatter_get_instance_private (self);
750 priv->track_elements_table =
751 g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
752 (GDestroyNotify) g_hash_table_destroy);
755 g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
758 g_hash_table_new_full (g_int_hash, g_str_equal, g_free, gst_object_unref);
760 priv->sources_table =
761 g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
762 (GDestroyNotify) g_hash_table_destroy);
765 g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
767 priv->sources_to_load = NULL;
770 priv->saving_source_table =
771 g_hash_table_new_full (g_str_hash, g_int_equal, g_free, g_free);
772 priv->nb_sources = 1;
776 ges_pitivi_formatter_new (void)
778 return g_object_new (GES_TYPE_PITIVI_FORMATTER, NULL);