cmmlenc: Remove hack to let oggmux start a new page for every CMML buffer
[platform/upstream/gst-plugins-good.git] / ext / annodex / gstcmmlparser.c
1 /*
2  * gstcmmlparser.c - GStreamer CMML document parser
3  * Copyright (C) 2005 Alessandro Decina
4  * 
5  * Authors:
6  *   Alessandro Decina <alessandro@nnva.org>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #include <string.h>
25 #include <stdarg.h>
26 #include <gst/gst.h>
27
28 #include "gstcmmlparser.h"
29 #include "gstannodex.h"
30 #include "gstcmmlutils.h"
31
32 GST_DEBUG_CATEGORY_STATIC (cmmlparser);
33 #define GST_CAT_DEFAULT cmmlparser
34
35 static void gst_cmml_parser_generic_error (void *ctx, const char *msg, ...);
36 static xmlNodePtr gst_cmml_parser_new_node (GstCmmlParser * parser,
37     const gchar * name, ...);
38 static void
39 gst_cmml_parser_parse_start_element_ns (xmlParserCtxt * ctxt,
40     const xmlChar * name, const xmlChar * prefix, const xmlChar * URI,
41     int nb_preferences, const xmlChar ** namespaces,
42     int nb_attributes, int nb_defaulted, const xmlChar ** attributes);
43 static void gst_cmml_parser_parse_end_element_ns (xmlParserCtxt * ctxt,
44     const xmlChar * name, const xmlChar * prefix, const xmlChar * URI);
45 static void gst_cmml_parser_parse_processing_instruction (xmlParserCtxtPtr ctxt,
46     const xmlChar * target, const xmlChar * data);
47 static void gst_cmml_parser_meta_to_string (GstCmmlParser * parser,
48     xmlNodePtr parent, GValueArray * meta);
49
50 /* initialize the parser */
51 void
52 gst_cmml_parser_init (void)
53 {
54   GST_DEBUG_CATEGORY_INIT (cmmlparser, "cmmlparser", 0, "annodex CMML parser");
55
56   xmlGenericError = gst_cmml_parser_generic_error;
57 }
58
59 /* create a new CMML parser
60  */
61 GstCmmlParser *
62 gst_cmml_parser_new (GstCmmlParserMode mode)
63 {
64   GstCmmlParser *parser = g_malloc (sizeof (GstCmmlParser));
65
66   parser->mode = mode;
67   parser->context = xmlCreatePushParserCtxt (NULL, NULL,
68       NULL, 0, "cmml-bitstream");
69   xmlCtxtUseOptions (parser->context, XML_PARSE_NONET | XML_PARSE_NOERROR);
70   parser->context->_private = parser;
71   parser->context->sax->startElementNs =
72       (startElementNsSAX2Func) gst_cmml_parser_parse_start_element_ns;
73   parser->context->sax->endElementNs =
74       (endElementNsSAX2Func) gst_cmml_parser_parse_end_element_ns;
75   parser->context->sax->processingInstruction = (processingInstructionSAXFunc)
76       gst_cmml_parser_parse_processing_instruction;
77   parser->preamble_callback = NULL;
78   parser->cmml_end_callback = NULL;
79   parser->stream_callback = NULL;
80   parser->head_callback = NULL;
81   parser->clip_callback = NULL;
82   parser->user_data = NULL;
83
84   return parser;
85 }
86
87 /* free a CMML parser instance
88  */
89 void
90 gst_cmml_parser_free (GstCmmlParser * parser)
91 {
92   if (parser) {
93     xmlFreeDoc (parser->context->myDoc);
94     xmlFreeParserCtxt (parser->context);
95     g_free (parser);
96   }
97 }
98
99 /* parse an xml chunk
100  *
101  * returns false if the xml is invalid
102  */
103 gboolean
104 gst_cmml_parser_parse_chunk (GstCmmlParser * parser,
105     const gchar * data, guint size, GError ** err)
106 {
107   gint xmlres;
108
109   xmlres = xmlParseChunk (parser->context, data, size, 0);
110   if (xmlres != XML_ERR_OK) {
111     xmlErrorPtr xml_error = xmlCtxtGetLastError (parser->context);
112
113     GST_DEBUG ("Error occurred decoding chunk %s", data);
114     g_set_error (err,
115         GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, "%s", xml_error->message);
116     return FALSE;
117   }
118
119   return TRUE;
120 }
121
122 /* convert an xmlNodePtr to a string
123  */
124 static guchar *
125 gst_cmml_parser_node_to_string (GstCmmlParser * parser, xmlNodePtr node)
126 {
127   xmlBufferPtr xml_buffer;
128   xmlDocPtr doc;
129   guchar *str;
130
131   if (parser)
132     doc = parser->context->myDoc;
133   else
134     doc = NULL;
135
136   xml_buffer = xmlBufferCreate ();
137   xmlNodeDump (xml_buffer, doc, node, 0, 0);
138   str = xmlStrndup (xml_buffer->content, xml_buffer->use);
139   xmlBufferFree (xml_buffer);
140
141   return str;
142 }
143
144 guchar *
145 gst_cmml_parser_tag_stream_to_string (GstCmmlParser * parser,
146     GstCmmlTagStream * stream)
147 {
148   xmlNodePtr node;
149   xmlNodePtr import;
150   guchar *ret;
151
152   node = gst_cmml_parser_new_node (parser, "stream", NULL);
153   if (stream->timebase)
154     xmlSetProp (node, (xmlChar *) "timebase", stream->timebase);
155
156   if (stream->utc)
157     xmlSetProp (node, (xmlChar *) "utc", stream->utc);
158
159   if (stream->imports) {
160     gint i;
161     GValue *val;
162
163     for (i = 0; i < stream->imports->n_values; ++i) {
164       val = g_value_array_get_nth (stream->imports, i);
165       import = gst_cmml_parser_new_node (parser, "import",
166           "src", g_value_get_string (val), NULL);
167       xmlAddChild (node, import);
168     }
169   }
170
171   ret = gst_cmml_parser_node_to_string (parser, node);
172
173   xmlUnlinkNode (node);
174   xmlFreeNode (node);
175
176   return ret;
177 }
178
179 /* convert a GstCmmlTagHead to its string representation
180  */
181 guchar *
182 gst_cmml_parser_tag_head_to_string (GstCmmlParser * parser,
183     GstCmmlTagHead * head)
184 {
185   xmlNodePtr node;
186   xmlNodePtr tmp;
187   guchar *ret;
188
189   node = gst_cmml_parser_new_node (parser, "head", NULL);
190   if (head->title) {
191     tmp = gst_cmml_parser_new_node (parser, "title", NULL);
192     xmlNodeSetContent (tmp, head->title);
193     xmlAddChild (node, tmp);
194   }
195
196   if (head->base) {
197     tmp = gst_cmml_parser_new_node (parser, "base", "uri", head->base, NULL);
198     xmlAddChild (node, tmp);
199   }
200
201   if (head->meta)
202     gst_cmml_parser_meta_to_string (parser, node, head->meta);
203
204   ret = gst_cmml_parser_node_to_string (parser, node);
205
206   xmlUnlinkNode (node);
207   xmlFreeNode (node);
208
209   return ret;
210 }
211
212 /* convert a GstCmmlTagClip to its string representation
213  */
214 guchar *
215 gst_cmml_parser_tag_clip_to_string (GstCmmlParser * parser,
216     GstCmmlTagClip * clip)
217 {
218   xmlNodePtr node;
219   xmlNodePtr tmp;
220   guchar *ret;
221
222   node = gst_cmml_parser_new_node (parser, "clip",
223       "id", clip->id, "track", clip->track, NULL);
224   /* add the anchor element */
225   if (clip->anchor_href) {
226     tmp = gst_cmml_parser_new_node (parser, "a",
227         "href", clip->anchor_href, NULL);
228     if (clip->anchor_text)
229       xmlNodeSetContent (tmp, clip->anchor_text);
230
231     xmlAddChild (node, tmp);
232   }
233   /* add the img element */
234   if (clip->img_src) {
235     tmp = gst_cmml_parser_new_node (parser, "img",
236         "src", clip->img_src, "alt", clip->img_alt, NULL);
237
238     xmlAddChild (node, tmp);
239   }
240   /* add the desc element */
241   if (clip->desc_text) {
242     tmp = gst_cmml_parser_new_node (parser, "desc", NULL);
243     xmlNodeSetContent (tmp, clip->desc_text);
244
245     xmlAddChild (node, tmp);
246   }
247   /* add the meta elements */
248   if (clip->meta)
249     gst_cmml_parser_meta_to_string (parser, node, clip->meta);
250
251   if (parser->mode == GST_CMML_PARSER_DECODE) {
252     gchar *time_str;
253
254     time_str = gst_cmml_clock_time_to_npt (clip->start_time);
255     if (time_str == NULL)
256       goto fail;
257
258     xmlSetProp (node, (xmlChar *) "start", (xmlChar *) time_str);
259     g_free (time_str);
260
261     if (clip->end_time != GST_CLOCK_TIME_NONE) {
262       time_str = gst_cmml_clock_time_to_npt (clip->end_time);
263       if (time_str == NULL)
264         goto fail;
265
266       xmlSetProp (node, (xmlChar *) "end", (xmlChar *) time_str);
267       g_free (time_str);
268     }
269   }
270
271   ret = gst_cmml_parser_node_to_string (parser, node);
272
273   xmlUnlinkNode (node);
274   xmlFreeNode (node);
275
276   return ret;
277 fail:
278   xmlUnlinkNode (node);
279   xmlFreeNode (node);
280   return NULL;
281 }
282
283 guchar *
284 gst_cmml_parser_tag_object_to_string (GstCmmlParser * parser, GObject * tag)
285 {
286   guchar *tag_string = NULL;
287   GType tag_type = G_OBJECT_TYPE (tag);
288
289   if (tag_type == GST_TYPE_CMML_TAG_STREAM)
290     tag_string = gst_cmml_parser_tag_stream_to_string (parser,
291         GST_CMML_TAG_STREAM (tag));
292   else if (tag_type == GST_TYPE_CMML_TAG_HEAD)
293     tag_string = gst_cmml_parser_tag_head_to_string (parser,
294         GST_CMML_TAG_HEAD (tag));
295   else if (tag_type == GST_TYPE_CMML_TAG_CLIP)
296     tag_string = gst_cmml_parser_tag_clip_to_string (parser,
297         GST_CMML_TAG_CLIP (tag));
298   else
299     g_warning ("could not convert object to cmml");
300
301   return tag_string;
302 }
303
304 /*** private section ***/
305
306 /* create a new node
307  *
308  * helper to create a node and set its attributes
309  */
310 static xmlNodePtr
311 gst_cmml_parser_new_node (GstCmmlParser * parser, const gchar * name, ...)
312 {
313   va_list args;
314   xmlNodePtr node;
315   xmlChar *prop_name, *prop_value;
316
317   node = xmlNewNode (NULL, (xmlChar *) name);
318
319   va_start (args, name);
320
321   prop_name = va_arg (args, xmlChar *);
322   while (prop_name != NULL) {
323     prop_value = va_arg (args, xmlChar *);
324     if (prop_value != NULL)
325       xmlSetProp (node, prop_name, prop_value);
326
327     prop_name = va_arg (args, xmlChar *);
328   }
329   va_end (args);
330
331   return node;
332 }
333
334 /* get the last node of the stream
335  *
336  * returns the last node at depth 1 (if any) or the root node
337  */
338 static xmlNodePtr
339 gst_cmml_parser_get_last_element (GstCmmlParser * parser)
340 {
341   xmlNodePtr node;
342
343   node = xmlDocGetRootElement (parser->context->myDoc);
344   if (!node) {
345     g_warning ("no last cmml element");
346     return NULL;
347   }
348
349   if (node->children)
350     node = xmlGetLastChild (node);
351
352   return node;
353 }
354
355 static void
356 gst_cmml_parser_parse_preamble (GstCmmlParser * parser,
357     const guchar * attributes)
358 {
359   gchar *preamble;
360   gchar *element;
361   const gchar *version;
362   const gchar *encoding;
363   const gchar *standalone;
364   xmlDocPtr doc;
365
366   doc = parser->context->myDoc;
367
368   version = doc->version ? (gchar *) doc->version : "1.0";
369   encoding = doc->encoding ? (gchar *) doc->encoding : "UTF-8";
370   standalone = doc->standalone ? "yes" : "no";
371
372   preamble = g_strdup_printf ("<?xml version=\"%s\""
373       " encoding=\"%s\" standalone=\"%s\"?>\n"
374       "<!DOCTYPE cmml SYSTEM \"cmml.dtd\">\n", version, encoding, standalone);
375
376   if (attributes == NULL)
377     attributes = (guchar *) "";
378
379   if (parser->mode == GST_CMML_PARSER_ENCODE)
380     element = g_strdup_printf ("<?cmml %s?>", attributes);
381   else
382     element = g_strdup_printf ("<cmml %s>", attributes);
383
384   parser->preamble_callback (parser->user_data,
385       (guchar *) preamble, (guchar *) element);
386
387   g_free (preamble);
388   g_free (element);
389 }
390
391 /* parse the cmml stream tag */
392 static void
393 gst_cmml_parser_parse_stream (GstCmmlParser * parser, xmlNodePtr stream)
394 {
395   GstCmmlTagStream *stream_tag;
396   GValue str_val = { 0 };
397   xmlNodePtr walk;
398   guchar *timebase;
399
400   g_value_init (&str_val, G_TYPE_STRING);
401
402   /* read the timebase and utc attributes */
403   timebase = xmlGetProp (stream, (xmlChar *) "timebase");
404   if (timebase == NULL)
405     timebase = (guchar *) g_strdup ("0");
406
407   stream_tag = g_object_new (GST_TYPE_CMML_TAG_STREAM,
408       "timebase", timebase, NULL);
409   g_free (timebase);
410
411   stream_tag->utc = xmlGetProp (stream, (xmlChar *) "utc");
412
413   /* walk the children nodes */
414   for (walk = stream->children; walk; walk = walk->next) {
415     /* for every import tag add its src attribute to stream_tag->imports */
416     if (!xmlStrcmp (walk->name, (xmlChar *) "import")) {
417       g_value_take_string (&str_val,
418           (gchar *) xmlGetProp (walk, (xmlChar *) "src"));
419
420       if (stream_tag->imports == NULL)
421         stream_tag->imports = g_value_array_new (0);
422
423       g_value_array_append (stream_tag->imports, &str_val);
424     }
425   }
426   g_value_unset (&str_val);
427
428   parser->stream_callback (parser->user_data, stream_tag);
429   g_object_unref (stream_tag);
430 }
431
432 /* parse the cmml head tag */
433 static void
434 gst_cmml_parser_parse_head (GstCmmlParser * parser, xmlNodePtr head)
435 {
436   GstCmmlTagHead *head_tag;
437   xmlNodePtr walk;
438   GValue str_val = { 0 };
439
440   head_tag = g_object_new (GST_TYPE_CMML_TAG_HEAD, NULL);
441
442   g_value_init (&str_val, G_TYPE_STRING);
443
444   /* Parse the content of the node and setup the GST_TAG_CMML_HEAD tag.
445    * Create a GST_TAG_TITLE when we find the title element.
446    */
447   for (walk = head->children; walk; walk = walk->next) {
448     if (!xmlStrcmp (walk->name, (xmlChar *) "title")) {
449       head_tag->title = xmlNodeGetContent (walk);
450     } else if (!xmlStrcmp (walk->name, (xmlChar *) "base")) {
451       head_tag->base = xmlGetProp (walk, (xmlChar *) "uri");
452     } else if (!xmlStrcmp (walk->name, (xmlChar *) "meta")) {
453       if (head_tag->meta == NULL)
454         head_tag->meta = g_value_array_new (0);
455       /* add a pair name, content to the meta value array */
456       g_value_take_string (&str_val,
457           (gchar *) xmlGetProp (walk, (xmlChar *) "name"));
458       g_value_array_append (head_tag->meta, &str_val);
459       g_value_take_string (&str_val,
460           (gchar *) xmlGetProp (walk, (xmlChar *) "content"));
461       g_value_array_append (head_tag->meta, &str_val);
462     }
463   }
464   g_value_unset (&str_val);
465
466   parser->head_callback (parser->user_data, head_tag);
467   g_object_unref (head_tag);
468 }
469
470 /* parse a cmml clip tag */
471 static void
472 gst_cmml_parser_parse_clip (GstCmmlParser * parser, xmlNodePtr clip)
473 {
474   GstCmmlTagClip *clip_tag;
475   GValue str_val = { 0 };
476   guchar *id, *track, *start, *end;
477   xmlNodePtr walk;
478   GstClockTime start_time = GST_CLOCK_TIME_NONE;
479   GstClockTime end_time = GST_CLOCK_TIME_NONE;
480
481   start = xmlGetProp (clip, (xmlChar *) "start");
482   if (parser->mode == GST_CMML_PARSER_ENCODE && start == NULL)
483     /* XXX: validate the document */
484     return;
485
486   id = xmlGetProp (clip, (xmlChar *) "id");
487   track = xmlGetProp (clip, (xmlChar *) "track");
488   end = xmlGetProp (clip, (xmlChar *) "end");
489
490   if (track == NULL)
491     track = (guchar *) g_strdup ("default");
492
493   if (start) {
494     if (!strncmp ((gchar *) start, "smpte", 5))
495       start_time = gst_cmml_clock_time_from_smpte ((gchar *) start);
496     else
497       start_time = gst_cmml_clock_time_from_npt ((gchar *) start);
498   }
499
500   if (end) {
501     if (!strncmp ((gchar *) end, "smpte", 5))
502       start_time = gst_cmml_clock_time_from_smpte ((gchar *) end);
503     else
504       end_time = gst_cmml_clock_time_from_npt ((gchar *) end);
505   }
506
507   clip_tag = g_object_new (GST_TYPE_CMML_TAG_CLIP, "id", id,
508       "track", track, "start-time", start_time, "end-time", end_time, NULL);
509
510   g_free (id);
511   g_free (track);
512   g_free (start);
513   g_free (end);
514
515   g_value_init (&str_val, G_TYPE_STRING);
516
517   /* parse the children */
518   for (walk = clip->children; walk; walk = walk->next) {
519     /* the clip is not empty */
520     clip_tag->empty = FALSE;
521
522     if (!xmlStrcmp (walk->name, (xmlChar *) "a")) {
523       clip_tag->anchor_href = xmlGetProp (walk, (xmlChar *) "href");
524       clip_tag->anchor_text = xmlNodeGetContent (walk);
525     } else if (!xmlStrcmp (walk->name, (xmlChar *) "img")) {
526       clip_tag->img_src = xmlGetProp (walk, (xmlChar *) "src");
527       clip_tag->img_alt = xmlGetProp (walk, (xmlChar *) "alt");
528     } else if (!xmlStrcmp (walk->name, (xmlChar *) "desc")) {
529       clip_tag->desc_text = xmlNodeGetContent (walk);
530     } else if (!xmlStrcmp (walk->name, (xmlChar *) "meta")) {
531       if (clip_tag->meta == NULL)
532         clip_tag->meta = g_value_array_new (0);
533       /* add a pair name, content to the meta value array */
534       g_value_take_string (&str_val,
535           (char *) xmlGetProp (walk, (xmlChar *) "name"));
536       g_value_array_append (clip_tag->meta, &str_val);
537       g_value_take_string (&str_val,
538           (char *) xmlGetProp (walk, (xmlChar *) "content"));
539       g_value_array_append (clip_tag->meta, &str_val);
540     }
541   }
542   g_value_unset (&str_val);
543
544   parser->clip_callback (parser->user_data, clip_tag);
545   g_object_unref (clip_tag);
546 }
547
548 void
549 gst_cmml_parser_meta_to_string (GstCmmlParser * parser,
550     xmlNodePtr parent, GValueArray * array)
551 {
552   gint i;
553   xmlNodePtr node;
554   GValue *name, *content;
555
556   for (i = 0; i < array->n_values - 1; i += 2) {
557     name = g_value_array_get_nth (array, i);
558     content = g_value_array_get_nth (array, i + 1);
559     node = gst_cmml_parser_new_node (parser, "meta",
560         "name", g_value_get_string (name),
561         "content", g_value_get_string (content), NULL);
562     xmlAddChild (parent, node);
563   }
564 }
565
566 static void
567 gst_cmml_parser_generic_error (void *ctx, const char *msg, ...)
568 {
569 #ifndef GST_DISABLE_GST_DEBUG
570   va_list varargs;
571
572   va_start (varargs, msg);
573   gst_debug_log_valist (GST_CAT_DEFAULT, GST_LEVEL_WARNING,
574       "", "", 0, NULL, msg, varargs);
575   va_end (varargs);
576 #endif /* GST_DISABLE_GST_DEBUG */
577 }
578
579 /* sax handler called when an element start tag is found
580  * this is used to parse the cmml start tag
581  */
582 static void
583 gst_cmml_parser_parse_start_element_ns (xmlParserCtxt * ctxt,
584     const xmlChar * name, const xmlChar * prefix, const xmlChar * URI,
585     int nb_preferences, const xmlChar ** namespaces,
586     int nb_attributes, int nb_defaulted, const xmlChar ** attributes)
587 {
588   GstCmmlParser *parser = (GstCmmlParser *) ctxt->_private;
589
590   xmlSAX2StartElementNs (ctxt, name, prefix, URI, nb_preferences, namespaces,
591       nb_attributes, nb_defaulted, attributes);
592
593   if (parser->mode == GST_CMML_PARSER_ENCODE)
594     if (!xmlStrcmp (name, (xmlChar *) "cmml"))
595       if (parser->preamble_callback)
596         /* FIXME: parse attributes */
597         gst_cmml_parser_parse_preamble (parser, NULL);
598 }
599
600 /* sax processing instruction handler
601  * used to parse the cmml processing instruction
602  */
603 static void
604 gst_cmml_parser_parse_processing_instruction (xmlParserCtxtPtr ctxt,
605     const xmlChar * target, const xmlChar * data)
606 {
607   GstCmmlParser *parser = (GstCmmlParser *) ctxt->_private;
608
609   xmlSAX2ProcessingInstruction (ctxt, target, data);
610
611   if (parser->mode == GST_CMML_PARSER_DECODE)
612     if (!xmlStrcmp (target, (xmlChar *) "cmml"))
613       if (parser->preamble_callback)
614         gst_cmml_parser_parse_preamble (parser, data);
615 }
616
617 /* sax handler called when an xml end tag is found
618  * used to parse the stream, head and clip nodes
619  */
620 static void
621 gst_cmml_parser_parse_end_element_ns (xmlParserCtxt * ctxt,
622     const xmlChar * name, const xmlChar * prefix, const xmlChar * URI)
623 {
624   xmlNodePtr node;
625   GstCmmlParser *parser = (GstCmmlParser *) ctxt->_private;
626
627   xmlSAX2EndElementNs (ctxt, name, prefix, URI);
628
629   if (!xmlStrcmp (name, (xmlChar *) "clip")) {
630     if (parser->clip_callback) {
631       node = gst_cmml_parser_get_last_element (parser);
632       gst_cmml_parser_parse_clip (parser, node);
633     }
634   } else if (!xmlStrcmp (name, (xmlChar *) "cmml")) {
635     if (parser->cmml_end_callback)
636       parser->cmml_end_callback (parser->user_data);
637   } else if (!xmlStrcmp (name, (xmlChar *) "stream")) {
638     if (parser->stream_callback) {
639       node = gst_cmml_parser_get_last_element (parser);
640       gst_cmml_parser_parse_stream (parser, node);
641     }
642   } else if (!xmlStrcmp (name, (xmlChar *) "head")) {
643     if (parser->head_callback) {
644       node = gst_cmml_parser_get_last_element (parser);
645       gst_cmml_parser_parse_head (parser, node);
646     }
647   }
648 }