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