gst/subparse/gstsubparse.c: Add missing closing tags for markup and fix broken markup...
[platform/upstream/gstreamer.git] / gst / subparse / gstsubparse.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  * Copyright (C) 2004 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
4  * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include <string.h>
27 #include <stdlib.h>
28 #include <sys/types.h>
29 #include <regex.h>
30
31 #include "gstsubparse.h"
32 #include "gstssaparse.h"
33 #include "samiparse.h"
34
35 GST_DEBUG_CATEGORY_STATIC (sub_parse_debug);
36 #define GST_CAT_DEFAULT sub_parse_debug
37
38 #define DEFAULT_ENCODING   NULL
39
40 enum
41 {
42   PROP_0,
43   PROP_ENCODING
44 };
45
46 static void
47 gst_sub_parse_set_property (GObject * object, guint prop_id,
48     const GValue * value, GParamSpec * pspec);
49 static void
50 gst_sub_parse_get_property (GObject * object, guint prop_id,
51     GValue * value, GParamSpec * pspec);
52
53
54 static const GstElementDetails sub_parse_details =
55 GST_ELEMENT_DETAILS ("Subtitle parser",
56     "Codec/Parser/Subtitle",
57     "Parses subtitle (.sub) files into text streams",
58     "Gustavo J. A. M. Carneiro <gjc@inescporto.pt>\n"
59     "Ronald S. Bultje <rbultje@ronald.bitfreak.net>");
60
61 #ifndef GST_DISABLE_LOADSAVE_REGISTRY
62 static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink",
63     GST_PAD_SINK,
64     GST_PAD_ALWAYS,
65     GST_STATIC_CAPS ("application/x-subtitle; application/x-subtitle-sami")
66     );
67 #else
68 static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink",
69     GST_PAD_SINK,
70     GST_PAD_ALWAYS,
71     GST_STATIC_CAPS ("application/x-subtitle")
72     );
73 #endif
74
75 static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
76     GST_PAD_SRC,
77     GST_PAD_ALWAYS,
78     GST_STATIC_CAPS ("text/plain; text/x-pango-markup")
79     );
80
81 static void gst_sub_parse_base_init (GstSubParseClass * klass);
82 static void gst_sub_parse_class_init (GstSubParseClass * klass);
83 static void gst_sub_parse_init (GstSubParse * subparse);
84
85 static gboolean gst_sub_parse_src_event (GstPad * pad, GstEvent * event);
86 static gboolean gst_sub_parse_sink_event (GstPad * pad, GstEvent * event);
87
88 static GstStateChangeReturn gst_sub_parse_change_state (GstElement * element,
89     GstStateChange transition);
90
91 static GstFlowReturn gst_sub_parse_chain (GstPad * sinkpad, GstBuffer * buf);
92
93 static GstElementClass *parent_class = NULL;
94
95 GType
96 gst_sub_parse_get_type (void)
97 {
98   static GType sub_parse_type = 0;
99
100   if (!sub_parse_type) {
101     static const GTypeInfo sub_parse_info = {
102       sizeof (GstSubParseClass),
103       (GBaseInitFunc) gst_sub_parse_base_init,
104       NULL,
105       (GClassInitFunc) gst_sub_parse_class_init,
106       NULL,
107       NULL,
108       sizeof (GstSubParse),
109       0,
110       (GInstanceInitFunc) gst_sub_parse_init,
111     };
112
113     sub_parse_type = g_type_register_static (GST_TYPE_ELEMENT,
114         "GstSubParse", &sub_parse_info, 0);
115   }
116
117   return sub_parse_type;
118 }
119
120 static void
121 gst_sub_parse_base_init (GstSubParseClass * klass)
122 {
123   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
124
125   gst_element_class_add_pad_template (element_class,
126       gst_static_pad_template_get (&sink_templ));
127   gst_element_class_add_pad_template (element_class,
128       gst_static_pad_template_get (&src_templ));
129   gst_element_class_set_details (element_class, &sub_parse_details);
130 }
131
132 static void
133 gst_sub_parse_dispose (GObject * object)
134 {
135   GstSubParse *subparse = GST_SUBPARSE (object);
136
137   GST_DEBUG_OBJECT (subparse, "cleaning up subtitle parser");
138
139   if (subparse->segment) {
140     gst_segment_free (subparse->segment);
141     subparse->segment = NULL;
142   }
143   if (subparse->encoding) {
144     g_free (subparse->encoding);
145     subparse->encoding = NULL;
146   }
147   if (subparse->textbuf) {
148     g_string_free (subparse->textbuf, TRUE);
149     subparse->textbuf = NULL;
150   }
151   sami_context_deinit (&subparse->state);
152
153   GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
154 }
155
156 static void
157 gst_sub_parse_class_init (GstSubParseClass * klass)
158 {
159   GObjectClass *object_class = G_OBJECT_CLASS (klass);
160   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
161
162   parent_class = g_type_class_peek_parent (klass);
163
164   object_class->dispose = gst_sub_parse_dispose;
165   object_class->set_property = gst_sub_parse_set_property;
166   object_class->get_property = gst_sub_parse_get_property;
167
168   element_class->change_state = gst_sub_parse_change_state;
169
170   g_object_class_install_property (object_class, PROP_ENCODING,
171       g_param_spec_string ("subtitle-encoding", "subtitle charset encoding",
172           "Encoding to assume if input subtitles are not in UTF-8 encoding. "
173           "If not set, the GST_SUBTITLE_ENCODING environment variable will "
174           "be checked for an encoding to use. If that is not set either, "
175           "ISO-8859-15 will be assumed.", DEFAULT_ENCODING, G_PARAM_READWRITE));
176 }
177
178 static void
179 gst_sub_parse_init (GstSubParse * subparse)
180 {
181   subparse->sinkpad = gst_pad_new_from_static_template (&sink_templ, "sink");
182   gst_pad_set_chain_function (subparse->sinkpad,
183       GST_DEBUG_FUNCPTR (gst_sub_parse_chain));
184   gst_pad_set_event_function (subparse->sinkpad,
185       GST_DEBUG_FUNCPTR (gst_sub_parse_sink_event));
186   gst_element_add_pad (GST_ELEMENT (subparse), subparse->sinkpad);
187
188   subparse->srcpad = gst_pad_new_from_static_template (&src_templ, "src");
189   gst_pad_set_event_function (subparse->srcpad,
190       GST_DEBUG_FUNCPTR (gst_sub_parse_src_event));
191   gst_element_add_pad (GST_ELEMENT (subparse), subparse->srcpad);
192
193   subparse->textbuf = g_string_new (NULL);
194   subparse->parser_type = GST_SUB_PARSE_FORMAT_UNKNOWN;
195   subparse->flushing = FALSE;
196   subparse->segment = gst_segment_new ();
197   if (subparse->segment) {
198     gst_segment_init (subparse->segment, GST_FORMAT_TIME);
199     subparse->need_segment = TRUE;
200   } else {
201     GST_WARNING_OBJECT (subparse, "segment creation failed");
202     g_assert_not_reached ();
203   }
204   subparse->encoding = g_strdup (DEFAULT_ENCODING);
205 }
206
207 /*
208  * Source pad functions.
209  */
210
211 static gboolean
212 gst_sub_parse_src_event (GstPad * pad, GstEvent * event)
213 {
214   GstSubParse *self = GST_SUBPARSE (gst_pad_get_parent (pad));
215   gboolean ret = FALSE;
216
217   GST_DEBUG ("Handling %s event", GST_EVENT_TYPE_NAME (event));
218
219   switch (GST_EVENT_TYPE (event)) {
220     case GST_EVENT_SEEK:
221     {
222       GstFormat format;
223       GstSeekType start_type, stop_type;
224       gint64 start, stop;
225       gdouble rate;
226       gboolean update;
227
228       gst_event_parse_seek (event, &rate, &format, &self->segment_flags,
229           &start_type, &start, &stop_type, &stop);
230
231       if (format != GST_FORMAT_TIME) {
232         GST_WARNING_OBJECT (self, "we only support seeking in TIME format");
233         gst_event_unref (event);
234         goto beach;
235       }
236
237       /* Convert that seek to a seeking in bytes at position 0,
238          FIXME: could use an index */
239       ret = gst_pad_push_event (self->sinkpad,
240           gst_event_new_seek (rate, GST_FORMAT_BYTES, self->segment_flags,
241               GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, 0));
242
243       if (ret) {
244         /* Apply the seek to our segment */
245         gst_segment_set_seek (self->segment, rate, format, self->segment_flags,
246             start_type, start, stop_type, stop, &update);
247
248         GST_DEBUG_OBJECT (self, "segment configured from %" GST_TIME_FORMAT
249             " to %" GST_TIME_FORMAT ", position %" GST_TIME_FORMAT,
250             GST_TIME_ARGS (self->segment->start),
251             GST_TIME_ARGS (self->segment->stop),
252             GST_TIME_ARGS (self->segment->last_stop));
253
254         self->next_offset = 0;
255
256         self->need_segment = TRUE;
257       } else {
258         GST_WARNING_OBJECT (self, "seek to 0 bytes failed");
259       }
260
261       gst_event_unref (event);
262       break;
263     }
264     default:
265       ret = gst_pad_event_default (pad, event);
266       break;
267   }
268
269 beach:
270   gst_object_unref (self);
271
272   return ret;
273 }
274
275 static void
276 gst_sub_parse_set_property (GObject * object, guint prop_id,
277     const GValue * value, GParamSpec * pspec)
278 {
279   GstSubParse *subparse = GST_SUBPARSE (object);
280
281   GST_OBJECT_LOCK (subparse);
282   switch (prop_id) {
283     case PROP_ENCODING:
284       g_free (subparse->encoding);
285       subparse->encoding = g_value_dup_string (value);
286       GST_LOG_OBJECT (object, "subtitle encoding set to %s",
287           GST_STR_NULL (subparse->encoding));
288       break;
289     default:
290       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
291       break;
292   }
293   GST_OBJECT_UNLOCK (subparse);
294 }
295
296 static void
297 gst_sub_parse_get_property (GObject * object, guint prop_id,
298     GValue * value, GParamSpec * pspec)
299 {
300   GstSubParse *subparse = GST_SUBPARSE (object);
301
302   GST_OBJECT_LOCK (subparse);
303   switch (prop_id) {
304     case PROP_ENCODING:
305       g_value_set_string (value, subparse->encoding);
306       break;
307     default:
308       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
309       break;
310   }
311   GST_OBJECT_UNLOCK (subparse);
312 }
313
314 static gchar *
315 convert_encoding (GstSubParse * self, const gchar * str, gsize len)
316 {
317   const gchar *encoding;
318   GError *err = NULL;
319   gchar *ret;
320
321   if (self->valid_utf8) {
322     if (g_utf8_validate (str, len, NULL)) {
323       GST_LOG_OBJECT (self, "valid UTF-8, no conversion needed");
324       return g_strndup (str, len);
325     }
326     GST_INFO_OBJECT (self, "invalid UTF-8!");
327     self->valid_utf8 = FALSE;
328   }
329
330   encoding = self->encoding;
331   if (encoding == NULL || *encoding == '\0') {
332     encoding = g_getenv ("GST_SUBTITLE_ENCODING");
333   }
334   if (encoding == NULL || *encoding == '\0') {
335     /* if local encoding is UTF-8 and no encoding specified
336      * via the environment variable, assume ISO-8859-15 */
337     if (g_get_charset (&encoding)) {
338       encoding = "ISO-8859-15";
339     }
340   }
341
342   ret = g_convert_with_fallback (str, len, "UTF-8", encoding, "*", NULL,
343       NULL, &err);
344
345   if (err) {
346     GST_WARNING_OBJECT (self, "could not convert string from '%s' to UTF-8: %s",
347         encoding, err->message);
348     g_error_free (err);
349
350     /* invalid input encoding, fall back to ISO-8859-15 (always succeeds) */
351     ret = g_convert_with_fallback (str, len, "UTF-8", "ISO-8859-15", "*",
352         NULL, NULL, NULL);
353   }
354
355   GST_LOG_OBJECT (self, "successfully converted %d characters from %s to UTF-8"
356       "%s", len, encoding, (err) ? " , using ISO-8859-15 as fallback" : "");
357
358   return ret;
359 }
360
361 static gchar *
362 get_next_line (GstSubParse * self)
363 {
364   char *line = NULL;
365   const char *line_end;
366   int line_len;
367   gboolean have_r = FALSE;
368
369   line_end = strchr (self->textbuf->str, '\n');
370
371   if (!line_end) {
372     /* end-of-line not found; return for more data */
373     return NULL;
374   }
375
376   /* get rid of '\r' */
377   if (line_end != self->textbuf->str && *(line_end - 1) == '\r') {
378     line_end--;
379     have_r = TRUE;
380   }
381
382   line_len = line_end - self->textbuf->str;
383   line = convert_encoding (self, self->textbuf->str, line_len);
384   self->textbuf = g_string_erase (self->textbuf, 0,
385       line_len + (have_r ? 2 : 1));
386   return line;
387 }
388
389 static gchar *
390 parse_mdvdsub (ParserState * state, const gchar * line)
391 {
392   const gchar *line_split;
393   gchar *line_chunk;
394   guint start_frame, end_frame;
395   gint64 clip_start = 0, clip_stop = 0;
396   gboolean in_seg = FALSE;
397
398   /* FIXME: hardcoded for now, but detecting the correct value is
399    * not going to be easy, I suspect... */
400   const double frames_per_sec = 24000 / 1001.;
401   GString *markup;
402   gchar *ret;
403
404   /* style variables */
405   gboolean italic;
406   gboolean bold;
407   guint fontsize;
408
409   if (sscanf (line, "{%u}{%u}", &start_frame, &end_frame) != 2) {
410     g_warning ("Parse of the following line, assumed to be in microdvd .sub"
411         " format, failed:\n%s", line);
412     return NULL;
413   }
414
415   state->start_time = (start_frame - 1000) / frames_per_sec * GST_SECOND;
416   state->duration = (end_frame - start_frame) / frames_per_sec * GST_SECOND;
417
418   /* Check our segment start/stop */
419   in_seg = gst_segment_clip (state->segment, GST_FORMAT_TIME,
420       state->start_time, state->start_time + state->duration, &clip_start,
421       &clip_stop);
422
423   /* No need to parse that text if it's out of segment */
424   if (in_seg) {
425     state->start_time = clip_start;
426     state->duration = clip_stop - clip_start;
427   } else {
428     return NULL;
429   }
430
431   /* skip the {%u}{%u} part */
432   line = strchr (line, '}') + 1;
433   line = strchr (line, '}') + 1;
434
435   markup = g_string_new (NULL);
436   while (1) {
437     italic = FALSE;
438     bold = FALSE;
439     fontsize = 0;
440     /* parse style markup */
441     if (strncmp (line, "{y:i}", 5) == 0) {
442       italic = TRUE;
443       line = strchr (line, '}') + 1;
444     }
445     if (strncmp (line, "{y:b}", 5) == 0) {
446       bold = TRUE;
447       line = strchr (line, '}') + 1;
448     }
449     if (sscanf (line, "{s:%u}", &fontsize) == 1) {
450       line = strchr (line, '}') + 1;
451     }
452     if ((line_split = strchr (line, '|')))
453       line_chunk = g_markup_escape_text (line, line_split - line);
454     else
455       line_chunk = g_markup_escape_text (line, strlen (line));
456     markup = g_string_append (markup, "<span");
457     if (italic)
458       g_string_append (markup, " style=\"italic\"");
459     if (bold)
460       g_string_append (markup, " weight=\"bold\"");
461     if (fontsize)
462       g_string_append_printf (markup, " size=\"%u\"", fontsize * 1000);
463     g_string_append_printf (markup, ">%s</span>", line_chunk);
464     g_free (line_chunk);
465     if (line_split) {
466       g_string_append (markup, "\n");
467       line = line_split + 1;
468     } else {
469       break;
470     }
471   }
472   ret = markup->str;
473   g_string_free (markup, FALSE);
474   GST_DEBUG ("parse_mdvdsub returning (%f+%f): %s",
475       state->start_time / (double) GST_SECOND,
476       state->duration / (double) GST_SECOND, ret);
477   return ret;
478 }
479
480 static void
481 strip_trailing_newlines (gchar * txt)
482 {
483   if (txt) {
484     guint len;
485
486     len = strlen (txt);
487     while (len > 1 && txt[len - 1] == '\n') {
488       txt[len - 1] = '\0';
489       --len;
490     }
491   }
492 }
493
494 /* we want to escape text in general, but retain basic markup like
495  * <i></i>, <u></u>, and <b></b>. The easiest and safest way is to
496  * just unescape a white list of allowed markups again after
497  * escaping everything (the text between these simple markers isn't
498  * necessarily escaped, so it seems best to do it like this) */
499 static void
500 subrip_unescape_formatting (gchar * txt)
501 {
502   gchar *pos;
503
504   for (pos = txt; pos != NULL && *pos != '\0'; ++pos) {
505     if (g_ascii_strncasecmp (pos, "&lt;u&gt;", 9) == 0 ||
506         g_ascii_strncasecmp (pos, "&lt;i&gt;", 9) == 0 ||
507         g_ascii_strncasecmp (pos, "&lt;b&gt;", 9) == 0) {
508       pos[0] = '<';
509       pos[1] = g_ascii_tolower (pos[4]);
510       pos[2] = '>';
511       /* move NUL terminator as well */
512       g_memmove (pos + 3, pos + 9, strlen (pos + 9) + 1);
513       pos += 2;
514     }
515   }
516
517   for (pos = txt; pos != NULL && *pos != '\0'; ++pos) {
518     if (g_ascii_strncasecmp (pos, "&lt;/u&gt;", 10) == 0 ||
519         g_ascii_strncasecmp (pos, "&lt;/i&gt;", 10) == 0 ||
520         g_ascii_strncasecmp (pos, "&lt;/b&gt;", 10) == 0) {
521       pos[0] = '<';
522       pos[1] = '/';
523       pos[2] = g_ascii_tolower (pos[5]);
524       pos[3] = '>';
525       /* move NUL terminator as well */
526       g_memmove (pos + 4, pos + 10, strlen (pos + 10) + 1);
527       pos += 3;
528     }
529   }
530 }
531
532 /* we only allow <i>, <u> and <b>, so let's take a simple approach. This code
533  * assumes the input has been escaped and subrip_unescape_formatting() has then
534  * been run over the input! This function adds missing closing markup tags and
535  * removes broken closing tags for tags that have never been opened. */
536 static void
537 subrip_fix_up_markup (gchar ** p_txt)
538 {
539   gchar *cur, *next_tag;
540   gchar open_tags[32];
541   guint num_open_tags = 0;
542
543   g_assert (*p_txt != NULL);
544
545   cur = *p_txt;
546   while (*cur != '\0') {
547     next_tag = strchr (cur, '<');
548     if (next_tag == NULL)
549       break;
550     ++next_tag;
551     switch (*next_tag) {
552       case '/':{
553         ++next_tag;
554         if (num_open_tags == 0 || open_tags[num_open_tags - 1] != *next_tag) {
555           GST_LOG ("broken input, closing tag '%c' is not open", *next_tag);
556           g_memmove (next_tag - 2, next_tag + 2, strlen (next_tag + 2) + 1);
557           next_tag -= 2;
558         } else {
559           /* it's all good, closing tag which is open */
560           --num_open_tags;
561         }
562         break;
563       }
564       case 'i':
565       case 'b':
566       case 'u':
567         if (num_open_tags == G_N_ELEMENTS (open_tags))
568           return;               /* something dodgy is going on, stop parsing */
569         open_tags[num_open_tags] = *next_tag;
570         ++num_open_tags;
571         break;
572       default:
573         GST_ERROR ("unexpected tag '%c' (%s)", *next_tag, next_tag);
574         g_assert_not_reached ();
575         break;
576     }
577     cur = next_tag;
578   }
579
580   if (num_open_tags > 0) {
581     GString *s;
582
583     s = g_string_new (*p_txt);
584     while (num_open_tags > 0) {
585       GST_LOG ("adding missing closing tag '%c'", open_tags[num_open_tags - 1]);
586       g_string_append_c (s, '<');
587       g_string_append_c (s, '/');
588       g_string_append_c (s, open_tags[num_open_tags - 1]);
589       g_string_append_c (s, '>');
590       --num_open_tags;
591     }
592     g_free (*p_txt);
593     *p_txt = g_string_free (s, FALSE);
594   }
595 }
596
597 static gchar *
598 parse_subrip (ParserState * state, const gchar * line)
599 {
600   guint h1, m1, s1, ms1;
601   guint h2, m2, s2, ms2;
602   int subnum;
603   gchar *ret;
604
605   switch (state->state) {
606     case 0:
607       /* looking for a single integer */
608       if (sscanf (line, "%u", &subnum) == 1)
609         state->state = 1;
610       return NULL;
611     case 1:
612       /* looking for start_time --> end_time */
613       if (sscanf (line, "%u:%u:%u,%u --> %u:%u:%u,%u",
614               &h1, &m1, &s1, &ms1, &h2, &m2, &s2, &ms2) == 8) {
615         state->state = 2;
616         state->start_time =
617             (((guint64) h1) * 3600 + m1 * 60 + s1) * GST_SECOND +
618             ms1 * GST_MSECOND;
619         state->duration =
620             (((guint64) h2) * 3600 + m2 * 60 + s2) * GST_SECOND +
621             ms2 * GST_MSECOND - state->start_time;
622       } else {
623         GST_DEBUG ("error parsing subrip time line");
624         state->state = 0;
625       }
626       return NULL;
627     case 2:
628     {                           /* No need to parse that text if it's out of segment */
629       gint64 clip_start = 0, clip_stop = 0;
630       gboolean in_seg = FALSE;
631
632       /* Check our segment start/stop */
633       in_seg = gst_segment_clip (state->segment, GST_FORMAT_TIME,
634           state->start_time, state->start_time + state->duration,
635           &clip_start, &clip_stop);
636
637       if (in_seg) {
638         state->start_time = clip_start;
639         state->duration = clip_stop - clip_start;
640       } else {
641         state->state = 0;
642         return NULL;
643       }
644     }
645       /* looking for subtitle text; empty line ends this
646        * subtitle entry */
647       if (state->buf->len)
648         g_string_append_c (state->buf, '\n');
649       g_string_append (state->buf, line);
650       if (strlen (line) == 0) {
651         ret = g_markup_escape_text (state->buf->str, state->buf->len);
652         g_string_truncate (state->buf, 0);
653         state->state = 0;
654         subrip_unescape_formatting (ret);
655         strip_trailing_newlines (ret);
656         subrip_fix_up_markup (&ret);
657         return ret;
658       }
659       return NULL;
660     default:
661       g_return_val_if_reached (NULL);
662   }
663 }
664
665 static gchar *
666 parse_mpsub (ParserState * state, const gchar * line)
667 {
668   gchar *ret;
669   float t1, t2;
670
671   switch (state->state) {
672     case 0:
673       /* looking for two floats (offset, duration) */
674       if (sscanf (line, "%f %f", &t1, &t2) == 2) {
675         state->state = 1;
676         state->start_time += state->duration + GST_SECOND * t1;
677         state->duration = GST_SECOND * t2;
678       }
679       return NULL;
680     case 1:
681     {                           /* No need to parse that text if it's out of segment */
682       gint64 clip_start = 0, clip_stop = 0;
683       gboolean in_seg = FALSE;
684
685       /* Check our segment start/stop */
686       in_seg = gst_segment_clip (state->segment, GST_FORMAT_TIME,
687           state->start_time, state->start_time + state->duration,
688           &clip_start, &clip_stop);
689
690       if (in_seg) {
691         state->start_time = clip_start;
692         state->duration = clip_stop - clip_start;
693       } else {
694         state->state = 0;
695         return NULL;
696       }
697     }
698       /* looking for subtitle text; empty line ends this
699        * subtitle entry */
700       if (state->buf->len)
701         g_string_append_c (state->buf, '\n');
702       g_string_append (state->buf, line);
703       if (strlen (line) == 0) {
704         ret = g_strdup (state->buf->str);
705         g_string_truncate (state->buf, 0);
706         state->state = 0;
707         return ret;
708       }
709       return NULL;
710     default:
711       g_assert_not_reached ();
712       return NULL;
713   }
714 }
715
716 static void
717 parser_state_init (ParserState * state)
718 {
719   GST_DEBUG ("initialising parser");
720
721   if (state->buf) {
722     g_string_truncate (state->buf, 0);
723   } else {
724     state->buf = g_string_new (NULL);
725   }
726
727   state->start_time = 0;
728   state->duration = 0;
729   state->state = 0;
730   state->segment = NULL;
731 }
732
733 static void
734 parser_state_dispose (ParserState * state)
735 {
736   if (state->buf) {
737     g_string_free (state->buf, TRUE);
738     state->buf = NULL;
739   }
740   if (state->user_data) {
741     sami_context_reset (state);
742   }
743 }
744
745 /*
746  * FIXME: maybe we should pass along a second argument, the preceding
747  * text buffer, because that is how this originally worked, even though
748  * I don't really see the use of that.
749  */
750
751 static GstSubParseFormat
752 gst_sub_parse_data_format_autodetect (gchar * match_str)
753 {
754   static gboolean need_init_regexps = TRUE;
755   static regex_t mdvd_rx;
756   static regex_t subrip_rx;
757
758   /* initialize the regexps used the first time around */
759   if (need_init_regexps) {
760     int err;
761     char errstr[128];
762
763     need_init_regexps = FALSE;
764     if ((err = regcomp (&mdvd_rx, "^\\{[0-9]+\\}\\{[0-9]+\\}",
765                 REG_EXTENDED | REG_NEWLINE | REG_NOSUB) != 0) ||
766         (err = regcomp (&subrip_rx, "^[1-9]([0-9]){0,3}(\x0d)?\x0a"
767                 "[0-9][0-9]:[0-9][0-9]:[0-9][0-9],[0-9]{3}"
768                 " --> [0-9][0-9]:[0-9][0-9]:[0-9][0-9],[0-9]{3}",
769                 REG_EXTENDED | REG_NEWLINE | REG_NOSUB)) != 0) {
770       regerror (err, &subrip_rx, errstr, 127);
771       GST_WARNING ("Compilation of subrip regex failed: %s", errstr);
772     }
773   }
774
775   if (regexec (&mdvd_rx, match_str, 0, NULL, 0) == 0) {
776     GST_LOG ("MicroDVD (frame based) format detected");
777     return GST_SUB_PARSE_FORMAT_MDVDSUB;
778   }
779   if (regexec (&subrip_rx, match_str, 0, NULL, 0) == 0) {
780     GST_LOG ("SubRip (time based) format detected");
781     return GST_SUB_PARSE_FORMAT_SUBRIP;
782   }
783   if (!strncmp (match_str, "FORMAT=TIME", 11)) {
784     GST_LOG ("MPSub (time based) format detected");
785     return GST_SUB_PARSE_FORMAT_MPSUB;
786   }
787   if (strstr (match_str, "<SAMI>") != NULL ||
788       strstr (match_str, "<sami>") != NULL) {
789     GST_LOG ("SAMI (time based) format detected");
790     return GST_SUB_PARSE_FORMAT_SAMI;
791   }
792
793   GST_DEBUG ("no subtitle format detected");
794   return GST_SUB_PARSE_FORMAT_UNKNOWN;
795 }
796
797 static GstCaps *
798 gst_sub_parse_format_autodetect (GstSubParse * self)
799 {
800   gchar *data;
801   GstSubParseFormat format;
802
803   if (strlen (self->textbuf->str) < 35) {
804     GST_DEBUG ("File too small to be a subtitles file");
805     return NULL;
806   }
807
808   data = g_strndup (self->textbuf->str, 35);
809   format = gst_sub_parse_data_format_autodetect (data);
810   g_free (data);
811
812   self->parser_type = format;
813   parser_state_init (&self->state);
814
815   switch (format) {
816     case GST_SUB_PARSE_FORMAT_MDVDSUB:
817       self->parse_line = parse_mdvdsub;
818       return gst_caps_new_simple ("text/x-pango-markup", NULL);
819     case GST_SUB_PARSE_FORMAT_SUBRIP:
820       self->parse_line = parse_subrip;
821       return gst_caps_new_simple ("text/x-pango-markup", NULL);
822     case GST_SUB_PARSE_FORMAT_MPSUB:
823       self->parse_line = parse_mpsub;
824       return gst_caps_new_simple ("text/plain", NULL);
825     case GST_SUB_PARSE_FORMAT_SAMI:
826       self->parse_line = parse_sami;
827       sami_context_init (&self->state);
828       return gst_caps_new_simple ("text/x-pango-markup", NULL);
829     case GST_SUB_PARSE_FORMAT_UNKNOWN:
830     default:
831       GST_DEBUG ("no subtitle format detected");
832       GST_ELEMENT_ERROR (self, STREAM, WRONG_TYPE,
833           ("The input is not a valid/supported subtitle file"), (NULL));
834       return NULL;
835   }
836 }
837
838 static void
839 feed_textbuf (GstSubParse * self, GstBuffer * buf)
840 {
841   if (GST_BUFFER_OFFSET (buf) != self->offset) {
842     /* flush the parser state */
843     parser_state_init (&self->state);
844     g_string_truncate (self->textbuf, 0);
845     sami_context_reset (&self->state);
846   }
847
848   self->textbuf = g_string_append_len (self->textbuf,
849       (gchar *) GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
850   self->offset = GST_BUFFER_OFFSET (buf) + GST_BUFFER_SIZE (buf);
851   self->next_offset = self->offset;
852
853   gst_buffer_unref (buf);
854 }
855
856 static GstFlowReturn
857 handle_buffer (GstSubParse * self, GstBuffer * buf)
858 {
859   GstFlowReturn ret = GST_FLOW_OK;
860   GstCaps *caps = NULL;
861   gchar *line, *subtitle;
862
863   feed_textbuf (self, buf);
864
865   /* make sure we know the format */
866   if (G_UNLIKELY (self->parser_type == GST_SUB_PARSE_FORMAT_UNKNOWN)) {
867     if (!(caps = gst_sub_parse_format_autodetect (self))) {
868       return GST_FLOW_UNEXPECTED;
869     }
870     if (!gst_pad_set_caps (self->srcpad, caps)) {
871       gst_caps_unref (caps);
872       return GST_FLOW_UNEXPECTED;
873     }
874     gst_caps_unref (caps);
875   }
876
877   while ((line = get_next_line (self)) && !self->flushing) {
878     /* Set segment on our parser state machine */
879     self->state.segment = self->segment;
880     /* Now parse the line, out of segment lines will just return NULL */
881     GST_DEBUG ("Parsing line '%s'", line);
882     subtitle = self->parse_line (&self->state, line);
883     g_free (line);
884
885     if (subtitle) {
886       guint subtitle_len = strlen (subtitle);
887
888       /* +1 for terminating NUL character */
889       ret = gst_pad_alloc_buffer_and_set_caps (self->srcpad,
890           GST_BUFFER_OFFSET_NONE, subtitle_len + 1,
891           GST_PAD_CAPS (self->srcpad), &buf);
892
893       if (ret == GST_FLOW_OK) {
894         /* copy terminating NUL character as well */
895         memcpy (GST_BUFFER_DATA (buf), subtitle, subtitle_len + 1);
896         GST_BUFFER_SIZE (buf) = subtitle_len;
897         GST_BUFFER_TIMESTAMP (buf) = self->state.start_time;
898         GST_BUFFER_DURATION (buf) = self->state.duration;
899
900         gst_segment_set_last_stop (self->segment, GST_FORMAT_TIME,
901             self->state.start_time);
902
903         GST_DEBUG ("Sending text '%s', %" GST_TIME_FORMAT " + %"
904             GST_TIME_FORMAT, subtitle, GST_TIME_ARGS (self->state.start_time),
905             GST_TIME_ARGS (self->state.duration));
906
907         ret = gst_pad_push (self->srcpad, buf);
908       }
909
910       g_free (subtitle);
911       subtitle = NULL;
912
913       if (GST_FLOW_IS_FATAL (ret))
914         break;
915     }
916   }
917
918   return ret;
919 }
920
921 static GstFlowReturn
922 gst_sub_parse_chain (GstPad * sinkpad, GstBuffer * buf)
923 {
924   GstFlowReturn ret;
925   GstSubParse *self;
926
927   GST_DEBUG ("gst_sub_parse_chain");
928   self = GST_SUBPARSE (gst_pad_get_parent (sinkpad));
929
930   /* Push newsegment if needed */
931   if (self->need_segment) {
932     gst_pad_push_event (self->srcpad, gst_event_new_new_segment (FALSE,
933             self->segment->rate, self->segment->format,
934             self->segment->last_stop, self->segment->stop,
935             self->segment->time));
936     self->need_segment = FALSE;
937   }
938
939   ret = handle_buffer (self, buf);
940
941   gst_object_unref (self);
942
943   return ret;
944 }
945
946 static gboolean
947 gst_sub_parse_sink_event (GstPad * pad, GstEvent * event)
948 {
949   GstSubParse *self = GST_SUBPARSE (gst_pad_get_parent (pad));
950   gboolean ret = FALSE;
951
952   GST_DEBUG ("Handling %s event", GST_EVENT_TYPE_NAME (event));
953
954   switch (GST_EVENT_TYPE (event)) {
955     case GST_EVENT_EOS:{
956       /* Make sure the last subrip chunk is pushed out even
957        * if the file does not have an empty line at the end */
958       if (self->parser_type == GST_SUB_PARSE_FORMAT_SUBRIP) {
959         GstBuffer *buf = gst_buffer_new_and_alloc (1 + 1);
960
961         GST_DEBUG ("EOS. Pushing remaining text (if any)");
962         GST_BUFFER_DATA (buf)[0] = '\n';
963         GST_BUFFER_DATA (buf)[1] = '\0';        /* play it safe */
964         GST_BUFFER_SIZE (buf) = 1;
965         GST_BUFFER_OFFSET (buf) = self->offset;
966         gst_sub_parse_chain (pad, buf);
967       }
968       ret = gst_pad_event_default (pad, event);
969       break;
970     }
971     case GST_EVENT_NEWSEGMENT:
972     {
973       GstFormat format;
974       gdouble rate;
975       gint64 start, stop, time;
976       gboolean update;
977
978       GST_DEBUG_OBJECT (self, "received new segment");
979
980       gst_event_parse_new_segment (event, &update, &rate, &format, &start,
981           &stop, &time);
982
983       /* now copy over the values */
984       gst_segment_set_newsegment (self->segment, update, rate, format,
985           start, stop, time);
986
987       ret = TRUE;
988       gst_event_unref (event);
989       break;
990     }
991     case GST_EVENT_FLUSH_START:
992     {
993       self->flushing = TRUE;
994
995       ret = gst_pad_event_default (pad, event);
996       break;
997     }
998     case GST_EVENT_FLUSH_STOP:
999     {
1000       self->flushing = FALSE;
1001
1002       ret = gst_pad_event_default (pad, event);
1003       break;
1004     }
1005     default:
1006       ret = gst_pad_event_default (pad, event);
1007       break;
1008   }
1009
1010   gst_object_unref (self);
1011
1012   return ret;
1013 }
1014
1015
1016 static GstStateChangeReturn
1017 gst_sub_parse_change_state (GstElement * element, GstStateChange transition)
1018 {
1019   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1020   GstSubParse *self = GST_SUBPARSE (element);
1021
1022   switch (transition) {
1023     case GST_STATE_CHANGE_READY_TO_PAUSED:
1024       /* format detection will init the parser state */
1025       self->offset = 0;
1026       self->next_offset = 0;
1027       self->parser_type = GST_SUB_PARSE_FORMAT_UNKNOWN;
1028       self->valid_utf8 = TRUE;
1029       g_string_truncate (self->textbuf, 0);
1030       break;
1031     default:
1032       break;
1033   }
1034
1035   ret = parent_class->change_state (element, transition);
1036   if (ret == GST_STATE_CHANGE_FAILURE)
1037     return ret;
1038
1039   switch (transition) {
1040     case GST_STATE_CHANGE_PAUSED_TO_READY:
1041       parser_state_dispose (&self->state);
1042       self->parser_type = GST_SUB_PARSE_FORMAT_UNKNOWN;
1043       break;
1044     default:
1045       break;
1046   }
1047
1048   return ret;
1049 }
1050
1051 /*
1052  * Typefind support.
1053  */
1054
1055 static GstStaticCaps smi_caps = GST_STATIC_CAPS ("application/x-subtitle-sami");
1056 static GstStaticCaps sub_caps = GST_STATIC_CAPS ("application/x-subtitle");
1057
1058 #define SUB_CAPS (gst_static_caps_get (&sub_caps))
1059 #define SAMI_CAPS (gst_static_caps_get (&smi_caps))
1060
1061 static void
1062 gst_subparse_type_find (GstTypeFind * tf, gpointer private)
1063 {
1064   GstSubParseFormat format;
1065   const guint8 *data;
1066   GstCaps *caps;
1067   gchar *str;
1068
1069   if (!(data = gst_type_find_peek (tf, 0, 36)))
1070     return;
1071
1072   /* make sure string passed to _autodetect() is NUL-terminated */
1073   str = g_strndup ((gchar *) data, 35);
1074   format = gst_sub_parse_data_format_autodetect (str);
1075   g_free (str);
1076
1077   switch (format) {
1078     case GST_SUB_PARSE_FORMAT_MDVDSUB:
1079       GST_DEBUG ("MicroDVD format detected");
1080       caps = SUB_CAPS;
1081       break;
1082     case GST_SUB_PARSE_FORMAT_SUBRIP:
1083       GST_DEBUG ("SubRip format detected");
1084       caps = SUB_CAPS;
1085       break;
1086     case GST_SUB_PARSE_FORMAT_MPSUB:
1087       GST_DEBUG ("MPSub format detected");
1088       caps = SUB_CAPS;
1089       break;
1090     case GST_SUB_PARSE_FORMAT_SAMI:
1091       GST_DEBUG ("SAMI (time-based) format detected");
1092       caps = SAMI_CAPS;
1093       break;
1094     default:
1095     case GST_SUB_PARSE_FORMAT_UNKNOWN:
1096       GST_DEBUG ("no subtitle format detected");
1097       return;
1098   }
1099
1100   /* if we're here, it's ok */
1101   gst_type_find_suggest (tf, GST_TYPE_FIND_MAXIMUM, caps);
1102 }
1103
1104 static gboolean
1105 plugin_init (GstPlugin * plugin)
1106 {
1107   static gchar *sub_exts[] = { "srt", "sub", "mpsub", "mdvd", "smi", NULL };
1108
1109   GST_DEBUG_CATEGORY_INIT (sub_parse_debug, "subparse", 0, ".sub parser");
1110
1111   if (!gst_type_find_register (plugin, "subparse_typefind", GST_RANK_MARGINAL,
1112           gst_subparse_type_find, sub_exts, SUB_CAPS, NULL, NULL))
1113     return FALSE;
1114
1115   if (!gst_element_register (plugin, "subparse",
1116           GST_RANK_PRIMARY, GST_TYPE_SUBPARSE) ||
1117       !gst_element_register (plugin, "ssaparse",
1118           GST_RANK_PRIMARY, GST_TYPE_SSA_PARSE)) {
1119     return FALSE;
1120   }
1121
1122   return TRUE;
1123 }
1124
1125 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1126     GST_VERSION_MINOR,
1127     "subparse",
1128     "Subtitle parsing",
1129     plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)