summaryrefslogtreecommitdiffstats
path: root/ext/annodex/gstcmmlparser.c
diff options
context:
space:
mode:
authorMichael Smith <msmith@xiph.org>2006-02-24 19:07:10 +0000
committerMichael Smith <msmith@xiph.org>2006-02-24 19:07:10 +0000
commit73ce8c5b9e9a9cb27c1307dbfee300939265650c (patch)
tree207f645f3849f86026894e7411d9335353a12e53 /ext/annodex/gstcmmlparser.c
parentdd8f37faac9ea109550d630f4d7dcbf295cd5fc1 (diff)
Add Annodex elements from Alessendro Decina: skeleton and CMML.
Original commit message from CVS: * configure.ac: * docs/plugins/gst-plugins-good-plugins-sections.txt: * ext/Makefile.am: * ext/annodex/Makefile.am: * ext/annodex/gstannodex.c: * ext/annodex/gstannodex.h: * ext/annodex/gstcmmldec.c: * ext/annodex/gstcmmldec.h: * ext/annodex/gstcmmlenc.c: * ext/annodex/gstcmmlenc.h: * ext/annodex/gstcmmlparser.c: * ext/annodex/gstcmmlparser.h: * ext/annodex/gstcmmltag.c: * ext/annodex/gstcmmltag.h: * ext/annodex/gstcmmlutils.c: * ext/annodex/gstcmmlutils.h: * ext/annodex/gstskeldec.c: * ext/annodex/gstskeldec.h: * ext/annodex/gstskeltag.c: * ext/annodex/gstskeltag.h: * tests/check/Makefile.am: * tests/check/elements/cmmldec.c: * tests/check/elements/cmmlenc.c: * tests/check/elements/skeldec.c: Add Annodex elements from Alessendro Decina: skeleton and CMML. Includes tests & docs, oh my! Passes Thomas's -good checklist entirely. Wow.
Diffstat (limited to 'ext/annodex/gstcmmlparser.c')
-rw-r--r--ext/annodex/gstcmmlparser.c617
1 files changed, 617 insertions, 0 deletions
diff --git a/ext/annodex/gstcmmlparser.c b/ext/annodex/gstcmmlparser.c
new file mode 100644
index 00000000..c7931ea6
--- /dev/null
+++ b/ext/annodex/gstcmmlparser.c
@@ -0,0 +1,617 @@
+/*
+ * gstcmmlparser.c - GStreamer CMML document parser
+ * Copyright (C) 2005 Alessandro Decina
+ *
+ * Authors:
+ * Alessandro Decina <alessandro@nnva.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <gst/gst.h>
+
+#include "gstcmmlparser.h"
+#include "gstannodex.h"
+#include "gstcmmlutils.h"
+
+static xmlNodePtr gst_cmml_parser_new_node (GstCmmlParser * parser,
+ const gchar * name, ...);
+static void
+gst_cmml_parser_parse_start_element_ns (xmlParserCtxt * ctxt,
+ const xmlChar * name, const xmlChar * prefix, const xmlChar * URI,
+ int nb_preferences, const xmlChar ** namespaces,
+ int nb_attributes, int nb_defaulted, const xmlChar ** attributes);
+static void gst_cmml_parser_parse_end_element_ns (xmlParserCtxt * ctxt,
+ const xmlChar * name, const xmlChar * prefix, const xmlChar * URI);
+static void gst_cmml_parser_parse_processing_instruction (xmlParserCtxtPtr ctxt,
+ const xmlChar * target, const xmlChar * data);
+static void gst_cmml_parser_meta_to_string (GstCmmlParser * parser,
+ xmlNodePtr parent, GValueArray * meta);
+
+/* create a new CMML parser
+ */
+GstCmmlParser *
+gst_cmml_parser_new (GstCmmlParserMode mode)
+{
+ GstCmmlParser *parser = g_malloc (sizeof (GstCmmlParser));
+
+ parser->mode = mode;
+ parser->context = xmlCreatePushParserCtxt (NULL, NULL,
+ NULL, 0, "cmml-bitstream");
+ xmlCtxtUseOptions (parser->context, XML_PARSE_NONET | XML_PARSE_NOERROR);
+ parser->context->_private = parser;
+ parser->context->sax->startElementNs =
+ (startElementNsSAX2Func) gst_cmml_parser_parse_start_element_ns;
+ parser->context->sax->endElementNs =
+ (endElementNsSAX2Func) gst_cmml_parser_parse_end_element_ns;
+ parser->context->sax->processingInstruction = (processingInstructionSAXFunc)
+ gst_cmml_parser_parse_processing_instruction;
+ parser->preamble_callback = NULL;
+ parser->cmml_end_callback = NULL;
+ parser->stream_callback = NULL;
+ parser->head_callback = NULL;
+ parser->clip_callback = NULL;
+ parser->user_data = NULL;
+
+ return parser;
+}
+
+/* free a CMML parser instance
+ */
+void
+gst_cmml_parser_free (GstCmmlParser * parser)
+{
+ if (parser) {
+ xmlFreeDoc (parser->context->myDoc);
+ xmlFreeParserCtxt (parser->context);
+ g_free (parser);
+ }
+}
+
+/* parse an xml chunk
+ *
+ * returns false if the xml is invalid
+ */
+gboolean
+gst_cmml_parser_parse_chunk (GstCmmlParser * parser,
+ const gchar * data, guint size, GError ** err)
+{
+ gint xmlres;
+
+ xmlres = xmlParseChunk (parser->context, data, size, 0);
+ if (xmlres != XML_ERR_OK) {
+ xmlErrorPtr xml_error = xmlCtxtGetLastError (parser->context);
+
+ g_set_error (err,
+ GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, xml_error->message);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* convert an xmlNodePtr to a string
+ */
+guchar *
+gst_cmml_parser_node_to_string (GstCmmlParser * parser, xmlNodePtr node)
+{
+ xmlBufferPtr xml_buffer;
+ xmlDocPtr doc;
+ guchar *str;
+
+ if (parser)
+ doc = parser->context->myDoc;
+ else
+ doc = NULL;
+
+ xml_buffer = xmlBufferCreate ();
+ xmlNodeDump (xml_buffer, doc, node, 0, 0);
+ str = xmlStrndup (xml_buffer->content, xml_buffer->use);
+ xmlBufferFree (xml_buffer);
+
+ return str;
+}
+
+guchar *
+gst_cmml_parser_tag_stream_to_string (GstCmmlParser * parser,
+ GstCmmlTagStream * stream)
+{
+ xmlNodePtr node;
+ xmlNodePtr import;
+ guchar *ret;
+
+ node = gst_cmml_parser_new_node (parser, "stream", NULL);
+ if (stream->timebase)
+ xmlSetProp (node, (xmlChar *) "timebase", stream->timebase);
+
+ if (stream->utc)
+ xmlSetProp (node, (xmlChar *) "utc", stream->utc);
+
+ if (stream->imports) {
+ gint i;
+ GValue *val;
+
+ for (i = 0; i < stream->imports->n_values; ++i) {
+ val = g_value_array_get_nth (stream->imports, i);
+ import = gst_cmml_parser_new_node (parser, "import",
+ "src", g_value_get_string (val), NULL);
+ xmlAddChild (node, import);
+ }
+ }
+
+ ret = gst_cmml_parser_node_to_string (parser, node);
+
+ xmlUnlinkNode (node);
+ xmlFreeNode (node);
+
+ return ret;
+}
+
+/* convert a GstCmmlTagHead to its string representation
+ */
+guchar *
+gst_cmml_parser_tag_head_to_string (GstCmmlParser * parser,
+ GstCmmlTagHead * head)
+{
+ xmlNodePtr node;
+ xmlNodePtr tmp;
+ guchar *ret;
+
+ node = gst_cmml_parser_new_node (parser, "head", NULL);
+ if (head->title) {
+ tmp = gst_cmml_parser_new_node (parser, "title", NULL);
+ xmlNodeSetContent (tmp, head->title);
+ xmlAddChild (node, tmp);
+ }
+
+ if (head->base) {
+ tmp = gst_cmml_parser_new_node (parser, "base", "uri", head->base, NULL);
+ xmlAddChild (node, tmp);
+ }
+
+ if (head->meta)
+ gst_cmml_parser_meta_to_string (parser, node, head->meta);
+
+ ret = gst_cmml_parser_node_to_string (parser, node);
+
+ xmlUnlinkNode (node);
+ xmlFreeNode (node);
+
+ return ret;
+}
+
+/* convert a GstCmmlTagClip to its string representation
+ */
+guchar *
+gst_cmml_parser_tag_clip_to_string (GstCmmlParser * parser,
+ GstCmmlTagClip * clip)
+{
+ xmlNodePtr node;
+ xmlNodePtr tmp;
+ guchar *ret;
+
+ node = gst_cmml_parser_new_node (parser, "clip",
+ "id", clip->id, "track", clip->track, NULL);
+ /* add the anchor element */
+ if (clip->anchor_href) {
+ tmp = gst_cmml_parser_new_node (parser, "a",
+ "href", clip->anchor_href, NULL);
+ if (clip->anchor_text)
+ xmlNodeSetContent (tmp, clip->anchor_text);
+
+ xmlAddChild (node, tmp);
+ }
+ /* add the img element */
+ if (clip->img_src) {
+ tmp = gst_cmml_parser_new_node (parser, "img",
+ "src", clip->img_src, "alt", clip->img_alt, NULL);
+
+ xmlAddChild (node, tmp);
+ }
+ /* add the desc element */
+ if (clip->desc_text) {
+ tmp = gst_cmml_parser_new_node (parser, "desc", NULL);
+ xmlNodeSetContent (tmp, clip->desc_text);
+
+ xmlAddChild (node, tmp);
+ }
+ /* add the meta elements */
+ if (clip->meta)
+ gst_cmml_parser_meta_to_string (parser, node, clip->meta);
+
+ if (parser->mode == GST_CMML_PARSER_DECODE) {
+ gchar *time_str;
+
+ time_str = gst_cmml_clock_time_to_npt (clip->start_time);
+ if (time_str == NULL)
+ goto fail;
+
+ xmlSetProp (node, (xmlChar *) "start", (xmlChar *) time_str);
+ g_free (time_str);
+
+ if (clip->end_time != GST_CLOCK_TIME_NONE) {
+ time_str = gst_cmml_clock_time_to_npt (clip->end_time);
+ if (time_str == NULL)
+ goto fail;
+
+ xmlSetProp (node, (xmlChar *) "end", (xmlChar *) time_str);
+ g_free (time_str);
+ }
+ }
+
+ ret = gst_cmml_parser_node_to_string (parser, node);
+
+ xmlUnlinkNode (node);
+ xmlFreeNode (node);
+
+ return ret;
+fail:
+ xmlUnlinkNode (node);
+ xmlFreeNode (node);
+ return NULL;
+}
+
+guchar *
+gst_cmml_parser_tag_object_to_string (GstCmmlParser * parser, GObject * tag)
+{
+ guchar *tag_string = NULL;
+ GType tag_type = G_OBJECT_TYPE (tag);
+
+ if (tag_type == GST_TYPE_CMML_TAG_STREAM)
+ tag_string = gst_cmml_parser_tag_stream_to_string (parser,
+ GST_CMML_TAG_STREAM (tag));
+ else if (tag_type == GST_TYPE_CMML_TAG_HEAD)
+ tag_string = gst_cmml_parser_tag_head_to_string (parser,
+ GST_CMML_TAG_HEAD (tag));
+ else if (tag_type == GST_TYPE_CMML_TAG_CLIP)
+ tag_string = gst_cmml_parser_tag_clip_to_string (parser,
+ GST_CMML_TAG_CLIP (tag));
+ else
+ g_warning ("could not convert object to cmml");
+
+ return tag_string;
+}
+
+/*** private section ***/
+
+/* create a new node
+ *
+ * helper to create a node and set its attributes
+ */
+static xmlNodePtr
+gst_cmml_parser_new_node (GstCmmlParser * parser, const gchar * name, ...)
+{
+ va_list args;
+ xmlNodePtr node;
+ xmlChar *prop_name, *prop_value;
+
+ node = xmlNewNode (NULL, (xmlChar *) name);
+
+ va_start (args, name);
+
+ prop_name = va_arg (args, xmlChar *);
+ while (prop_name != NULL) {
+ prop_value = va_arg (args, xmlChar *);
+ if (prop_value != NULL)
+ xmlSetProp (node, prop_name, prop_value);
+
+ prop_name = va_arg (args, xmlChar *);
+ }
+ va_end (args);
+
+ return node;
+}
+
+/* get the last node of the stream
+ *
+ * returns the last node at depth 1 (if any) or the root node
+ */
+static xmlNodePtr
+gst_cmml_parser_get_last_element (GstCmmlParser * parser)
+{
+ xmlNodePtr node;
+
+ node = xmlDocGetRootElement (parser->context->myDoc);
+ if (!node) {
+ g_warning ("no last cmml element");
+ return NULL;
+ }
+
+ if (node->children)
+ node = xmlGetLastChild (node);
+
+ return node;
+}
+
+static void
+gst_cmml_parser_parse_preamble (GstCmmlParser * parser,
+ const guchar * attributes)
+{
+ gchar *preamble;
+ gchar *element;
+ const gchar *version;
+ const gchar *encoding;
+ const gchar *standalone;
+ xmlDocPtr doc;
+
+ doc = parser->context->myDoc;
+
+ version = doc->version ? (gchar *) doc->version : "1.0";
+ encoding = doc->encoding ? (gchar *) doc->encoding : "UTF-8";
+ standalone = doc->standalone ? "yes" : "no";
+
+ preamble = g_strdup_printf ("<?xml version=\"%s\""
+ " encoding=\"%s\" standalone=\"%s\"?>\n"
+ "<!DOCTYPE cmml SYSTEM \"cmml.dtd\">\n", version, encoding, standalone);
+
+ if (attributes == NULL)
+ attributes = (guchar *) "";
+
+ if (parser->mode == GST_CMML_PARSER_ENCODE)
+ element = g_strdup_printf ("<?cmml %s?>", attributes);
+ else
+ element = g_strdup_printf ("<cmml %s>", attributes);
+
+ parser->preamble_callback (parser->user_data,
+ (guchar *) preamble, (guchar *) element);
+
+ g_free (preamble);
+ g_free (element);
+}
+
+/* parse the cmml stream tag */
+static void
+gst_cmml_parser_parse_stream (GstCmmlParser * parser, xmlNodePtr stream)
+{
+ GstCmmlTagStream *stream_tag;
+ GValue str_val = { 0 };
+ xmlNodePtr walk;
+ guchar *timebase;
+
+ g_value_init (&str_val, G_TYPE_STRING);
+
+ /* read the timebase and utc attributes */
+ timebase = xmlGetProp (stream, (xmlChar *) "timebase");
+ if (timebase == NULL)
+ timebase = (guchar *) g_strdup ("0");
+
+ stream_tag = g_object_new (GST_TYPE_CMML_TAG_STREAM,
+ "timebase", timebase, NULL);
+ g_free (timebase);
+
+ stream_tag->utc = xmlGetProp (stream, (xmlChar *) "utc");
+
+ /* walk the children nodes */
+ for (walk = stream->children; walk; walk = walk->next) {
+ /* for every import tag add its src attribute to stream_tag->imports */
+ if (!xmlStrcmp (walk->name, (xmlChar *) "import")) {
+ g_value_take_string (&str_val,
+ (gchar *) xmlGetProp (walk, (xmlChar *) "src"));
+
+ if (stream_tag->imports == NULL)
+ stream_tag->imports = g_value_array_new (0);
+
+ g_value_array_append (stream_tag->imports, &str_val);
+ }
+ }
+ g_value_unset (&str_val);
+
+ parser->stream_callback (parser->user_data, stream_tag);
+ g_object_unref (stream_tag);
+}
+
+/* parse the cmml head tag */
+static void
+gst_cmml_parser_parse_head (GstCmmlParser * parser, xmlNodePtr head)
+{
+ GstCmmlTagHead *head_tag;
+ xmlNodePtr walk;
+ GValue str_val = { 0 };
+
+ head_tag = g_object_new (GST_TYPE_CMML_TAG_HEAD, NULL);
+
+ g_value_init (&str_val, G_TYPE_STRING);
+
+ /* Parse the content of the node and setup the GST_TAG_CMML_HEAD tag.
+ * Create a GST_TAG_TITLE when we find the title element.
+ */
+ for (walk = head->children; walk; walk = walk->next) {
+ if (!xmlStrcmp (walk->name, (xmlChar *) "title")) {
+ head_tag->title = xmlNodeGetContent (walk);
+ } else if (!xmlStrcmp (walk->name, (xmlChar *) "base")) {
+ head_tag->base = xmlGetProp (walk, (xmlChar *) "uri");
+ } else if (!xmlStrcmp (walk->name, (xmlChar *) "meta")) {
+ if (head_tag->meta == NULL)
+ head_tag->meta = g_value_array_new (0);
+ /* add a pair name, content to the meta value array */
+ g_value_take_string (&str_val,
+ (gchar *) xmlGetProp (walk, (xmlChar *) "name"));
+ g_value_array_append (head_tag->meta, &str_val);
+ g_value_take_string (&str_val,
+ (gchar *) xmlGetProp (walk, (xmlChar *) "content"));
+ g_value_array_append (head_tag->meta, &str_val);
+ }
+ }
+ g_value_unset (&str_val);
+
+ parser->head_callback (parser->user_data, head_tag);
+ g_object_unref (head_tag);
+}
+
+/* parse a cmml clip tag */
+static void
+gst_cmml_parser_parse_clip (GstCmmlParser * parser, xmlNodePtr clip)
+{
+ GstCmmlTagClip *clip_tag;
+ GValue str_val = { 0 };
+ guchar *id, *track, *start, *end;
+ xmlNodePtr walk;
+ GstClockTime start_time = GST_CLOCK_TIME_NONE;
+ GstClockTime end_time = GST_CLOCK_TIME_NONE;
+
+ g_value_init (&str_val, G_TYPE_STRING);
+
+ id = xmlGetProp (clip, (xmlChar *) "id");
+ track = xmlGetProp (clip, (xmlChar *) "track");
+ start = xmlGetProp (clip, (xmlChar *) "start");
+ end = xmlGetProp (clip, (xmlChar *) "end");
+
+ if (start) {
+ if (!strncmp ((gchar *) start, "smpte", 5))
+ start_time = gst_cmml_clock_time_from_smpte ((gchar *) start);
+ else
+ start_time = gst_cmml_clock_time_from_npt ((gchar *) start);
+ }
+
+ if (end) {
+ if (!strncmp ((gchar *) end, "smpte", 5))
+ start_time = gst_cmml_clock_time_from_smpte ((gchar *) end);
+ else
+ end_time = gst_cmml_clock_time_from_npt ((gchar *) end);
+ }
+
+ if (track == NULL)
+ track = (guchar *) g_strdup ("default");
+
+ clip_tag = g_object_new (GST_TYPE_CMML_TAG_CLIP,
+ "id", id,
+ "track", track, "start-time", start_time, "end-time", end_time, NULL);
+
+ g_free (id);
+ g_free (track);
+ g_free (start);
+ g_free (end);
+
+ /* parse the children */
+ for (walk = clip->children; walk; walk = walk->next) {
+ /* the clip is not empty */
+ clip_tag->empty = FALSE;
+
+ if (!xmlStrcmp (walk->name, (xmlChar *) "a")) {
+ clip_tag->anchor_href = xmlGetProp (walk, (xmlChar *) "href");
+ clip_tag->anchor_text = xmlNodeGetContent (walk);
+ } else if (!xmlStrcmp (walk->name, (xmlChar *) "img")) {
+ clip_tag->img_src = xmlGetProp (walk, (xmlChar *) "src");
+ clip_tag->img_alt = xmlGetProp (walk, (xmlChar *) "alt");
+ } else if (!xmlStrcmp (walk->name, (xmlChar *) "desc")) {
+ clip_tag->desc_text = xmlNodeGetContent (walk);
+ } else if (!xmlStrcmp (walk->name, (xmlChar *) "meta")) {
+ if (clip_tag->meta == NULL)
+ clip_tag->meta = g_value_array_new (0);
+ /* add a pair name, content to the meta value array */
+ g_value_take_string (&str_val,
+ (char *) xmlGetProp (walk, (xmlChar *) "name"));
+ g_value_array_append (clip_tag->meta, &str_val);
+ g_value_take_string (&str_val,
+ (char *) xmlGetProp (walk, (xmlChar *) "content"));
+ g_value_array_append (clip_tag->meta, &str_val);
+ }
+ }
+ g_value_unset (&str_val);
+
+ parser->clip_callback (parser->user_data, clip_tag);
+ g_object_unref (clip_tag);
+}
+
+void
+gst_cmml_parser_meta_to_string (GstCmmlParser * parser,
+ xmlNodePtr parent, GValueArray * array)
+{
+ gint i;
+ xmlNodePtr node;
+ GValue *name, *content;
+
+ for (i = 0; i < array->n_values - 1; i += 2) {
+ name = g_value_array_get_nth (array, i);
+ content = g_value_array_get_nth (array, i + 1);
+ node = gst_cmml_parser_new_node (parser, "meta",
+ "name", g_value_get_string (name),
+ "content", g_value_get_string (content), NULL);
+ xmlAddChild (parent, node);
+ }
+}
+
+/* sax handler called when an element start tag is found
+ * this is used to parse the cmml start tag
+ */
+static void
+gst_cmml_parser_parse_start_element_ns (xmlParserCtxt * ctxt,
+ const xmlChar * name, const xmlChar * prefix, const xmlChar * URI,
+ int nb_preferences, const xmlChar ** namespaces,
+ int nb_attributes, int nb_defaulted, const xmlChar ** attributes)
+{
+ GstCmmlParser *parser = (GstCmmlParser *) ctxt->_private;
+
+ xmlSAX2StartElementNs (ctxt, name, prefix, URI, nb_preferences, namespaces,
+ nb_attributes, nb_defaulted, attributes);
+
+ if (parser->mode == GST_CMML_PARSER_ENCODE)
+ if (!xmlStrcmp (name, (xmlChar *) "cmml"))
+ if (parser->preamble_callback)
+ /* FIXME: parse attributes */
+ gst_cmml_parser_parse_preamble (parser, NULL);
+}
+
+/* sax processing instruction handler
+ * used to parse the cmml processing instruction
+ */
+static void
+gst_cmml_parser_parse_processing_instruction (xmlParserCtxtPtr ctxt,
+ const xmlChar * target, const xmlChar * data)
+{
+ GstCmmlParser *parser = (GstCmmlParser *) ctxt->_private;
+
+ xmlSAX2ProcessingInstruction (ctxt, target, data);
+
+ if (parser->mode == GST_CMML_PARSER_DECODE)
+ if (!xmlStrcmp (target, (xmlChar *) "cmml"))
+ if (parser->preamble_callback)
+ gst_cmml_parser_parse_preamble (parser, data);
+}
+
+/* sax handler called when an xml end tag is found
+ * used to parse the stream, head and clip nodes
+ */
+static void
+gst_cmml_parser_parse_end_element_ns (xmlParserCtxt * ctxt,
+ const xmlChar * name, const xmlChar * prefix, const xmlChar * URI)
+{
+ xmlNodePtr node;
+ GstCmmlParser *parser = (GstCmmlParser *) ctxt->_private;
+
+ xmlSAX2EndElementNs (ctxt, name, prefix, URI);
+
+ if (!xmlStrcmp (name, (xmlChar *) "clip")) {
+ if (parser->clip_callback) {
+ node = gst_cmml_parser_get_last_element (parser);
+ gst_cmml_parser_parse_clip (parser, node);
+ }
+ } else if (!xmlStrcmp (name, (xmlChar *) "cmml")) {
+ if (parser->cmml_end_callback)
+ parser->cmml_end_callback (parser->user_data);
+ } else if (!xmlStrcmp (name, (xmlChar *) "stream")) {
+ if (parser->stream_callback) {
+ node = gst_cmml_parser_get_last_element (parser);
+ gst_cmml_parser_parse_stream (parser, node);
+ }
+ } else if (!xmlStrcmp (name, (xmlChar *) "head")) {
+ if (parser->head_callback) {
+ node = gst_cmml_parser_get_last_element (parser);
+ gst_cmml_parser_parse_head (parser, node);
+ }
+ }
+}