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