subparse: Implement SEEKING query
[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 <glib.h>
30 #include <regex.h>
31
32 #include "gstsubparse.h"
33 #include "gstssaparse.h"
34 #include "samiparse.h"
35 #include "tmplayerparse.h"
36 #include "mpl2parse.h"
37
38 GST_DEBUG_CATEGORY (sub_parse_debug);
39
40 #define DEFAULT_ENCODING   NULL
41
42 enum
43 {
44   PROP_0,
45   PROP_ENCODING
46 };
47
48 static void
49 gst_sub_parse_set_property (GObject * object, guint prop_id,
50     const GValue * value, GParamSpec * pspec);
51 static void
52 gst_sub_parse_get_property (GObject * object, guint prop_id,
53     GValue * value, GParamSpec * pspec);
54
55
56 static const GstElementDetails sub_parse_details =
57 GST_ELEMENT_DETAILS ("Subtitle parser",
58     "Codec/Parser/Subtitle",
59     "Parses subtitle (.sub) files into text streams",
60     "Gustavo J. A. M. Carneiro <gjc@inescporto.pt>\n"
61     "Ronald S. Bultje <rbultje@ronald.bitfreak.net>");
62
63 #ifndef GST_DISABLE_XML
64 static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink",
65     GST_PAD_SINK,
66     GST_PAD_ALWAYS,
67     GST_STATIC_CAPS ("application/x-subtitle; application/x-subtitle-sami; "
68         "application/x-subtitle-tmplayer; application/x-subtitle-mpl2")
69     );
70 #else
71 static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink",
72     GST_PAD_SINK,
73     GST_PAD_ALWAYS,
74     GST_STATIC_CAPS ("application/x-subtitle")
75     );
76 #endif
77
78 static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
79     GST_PAD_SRC,
80     GST_PAD_ALWAYS,
81     GST_STATIC_CAPS ("text/plain; text/x-pango-markup")
82     );
83
84 static void gst_sub_parse_base_init (GstSubParseClass * klass);
85 static void gst_sub_parse_class_init (GstSubParseClass * klass);
86 static void gst_sub_parse_init (GstSubParse * subparse);
87
88 static gboolean gst_sub_parse_src_event (GstPad * pad, GstEvent * event);
89 static gboolean gst_sub_parse_src_query (GstPad * pad, GstQuery * query);
90 static gboolean gst_sub_parse_sink_event (GstPad * pad, GstEvent * event);
91
92 static GstStateChangeReturn gst_sub_parse_change_state (GstElement * element,
93     GstStateChange transition);
94
95 static GstFlowReturn gst_sub_parse_chain (GstPad * sinkpad, GstBuffer * buf);
96
97 static GstElementClass *parent_class = NULL;
98
99 GType
100 gst_sub_parse_get_type (void)
101 {
102   static GType sub_parse_type = 0;
103
104   if (!sub_parse_type) {
105     static const GTypeInfo sub_parse_info = {
106       sizeof (GstSubParseClass),
107       (GBaseInitFunc) gst_sub_parse_base_init,
108       NULL,
109       (GClassInitFunc) gst_sub_parse_class_init,
110       NULL,
111       NULL,
112       sizeof (GstSubParse),
113       0,
114       (GInstanceInitFunc) gst_sub_parse_init,
115     };
116
117     sub_parse_type = g_type_register_static (GST_TYPE_ELEMENT,
118         "GstSubParse", &sub_parse_info, 0);
119   }
120
121   return sub_parse_type;
122 }
123
124 static void
125 gst_sub_parse_base_init (GstSubParseClass * klass)
126 {
127   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
128
129   gst_element_class_add_pad_template (element_class,
130       gst_static_pad_template_get (&sink_templ));
131   gst_element_class_add_pad_template (element_class,
132       gst_static_pad_template_get (&src_templ));
133   gst_element_class_set_details (element_class, &sub_parse_details);
134 }
135
136 static void
137 gst_sub_parse_dispose (GObject * object)
138 {
139   GstSubParse *subparse = GST_SUBPARSE (object);
140
141   GST_DEBUG_OBJECT (subparse, "cleaning up subtitle parser");
142
143   if (subparse->encoding) {
144     g_free (subparse->encoding);
145     subparse->encoding = NULL;
146   }
147
148   if (subparse->detected_encoding) {
149     g_free (subparse->detected_encoding);
150     subparse->detected_encoding = NULL;
151   }
152
153   if (subparse->adapter) {
154     gst_object_unref (subparse->adapter);
155     subparse->adapter = NULL;
156   }
157
158   if (subparse->textbuf) {
159     g_string_free (subparse->textbuf, TRUE);
160     subparse->textbuf = NULL;
161   }
162 #ifndef GST_DISABLE_XML
163   sami_context_deinit (&subparse->state);
164 #endif
165
166   GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
167 }
168
169 static void
170 gst_sub_parse_class_init (GstSubParseClass * klass)
171 {
172   GObjectClass *object_class = G_OBJECT_CLASS (klass);
173   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
174
175   parent_class = g_type_class_peek_parent (klass);
176
177   object_class->dispose = gst_sub_parse_dispose;
178   object_class->set_property = gst_sub_parse_set_property;
179   object_class->get_property = gst_sub_parse_get_property;
180
181   element_class->change_state = gst_sub_parse_change_state;
182
183   g_object_class_install_property (object_class, PROP_ENCODING,
184       g_param_spec_string ("subtitle-encoding", "subtitle charset encoding",
185           "Encoding to assume if input subtitles are not in UTF-8 or any other "
186           "Unicode encoding. If not set, the GST_SUBTITLE_ENCODING environment "
187           "variable will be checked for an encoding to use. If that is not set "
188           "either, ISO-8859-15 will be assumed.", DEFAULT_ENCODING,
189           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
190 }
191
192 static void
193 gst_sub_parse_init (GstSubParse * subparse)
194 {
195   subparse->sinkpad = gst_pad_new_from_static_template (&sink_templ, "sink");
196   gst_pad_set_chain_function (subparse->sinkpad,
197       GST_DEBUG_FUNCPTR (gst_sub_parse_chain));
198   gst_pad_set_event_function (subparse->sinkpad,
199       GST_DEBUG_FUNCPTR (gst_sub_parse_sink_event));
200   gst_element_add_pad (GST_ELEMENT (subparse), subparse->sinkpad);
201
202   subparse->srcpad = gst_pad_new_from_static_template (&src_templ, "src");
203   gst_pad_set_event_function (subparse->srcpad,
204       GST_DEBUG_FUNCPTR (gst_sub_parse_src_event));
205   gst_pad_set_query_function (subparse->srcpad,
206       GST_DEBUG_FUNCPTR (gst_sub_parse_src_query));
207   gst_element_add_pad (GST_ELEMENT (subparse), subparse->srcpad);
208
209   subparse->textbuf = g_string_new (NULL);
210   subparse->parser_type = GST_SUB_PARSE_FORMAT_UNKNOWN;
211   subparse->flushing = FALSE;
212   gst_segment_init (&subparse->segment, GST_FORMAT_TIME);
213   subparse->need_segment = TRUE;
214   subparse->encoding = g_strdup (DEFAULT_ENCODING);
215   subparse->detected_encoding = NULL;
216   subparse->adapter = gst_adapter_new ();
217 }
218
219 /*
220  * Source pad functions.
221  */
222
223 static gboolean
224 gst_sub_parse_src_query (GstPad * pad, GstQuery * query)
225 {
226   GstSubParse *self = GST_SUBPARSE (gst_pad_get_parent (pad));
227   gboolean ret = FALSE;
228
229   GST_DEBUG ("Handling %s query", GST_QUERY_TYPE_NAME (query));
230
231   switch (GST_QUERY_TYPE (query)) {
232     case GST_QUERY_SEEKING:
233     {
234       GstFormat fmt;
235       gboolean seekable = FALSE;
236
237       ret = TRUE;
238
239       gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL);
240       if (fmt == GST_FORMAT_TIME) {
241         GstQuery *peerquery = gst_query_new_seeking (GST_FORMAT_BYTES);
242
243         seekable = gst_pad_peer_query (self->sinkpad, peerquery);
244         if (seekable)
245           gst_query_parse_seeking (peerquery, NULL, &seekable, NULL, NULL);
246         gst_query_unref (peerquery);
247       }
248
249       gst_query_set_seeking (query, fmt, seekable, seekable ? 0 : -1, -1);
250
251       break;
252     }
253     default:
254       ret = gst_pad_peer_query (self->sinkpad, query);
255       break;
256   }
257
258   gst_object_unref (self);
259
260   return ret;
261 }
262
263 static gboolean
264 gst_sub_parse_src_event (GstPad * pad, GstEvent * event)
265 {
266   GstSubParse *self = GST_SUBPARSE (gst_pad_get_parent (pad));
267   gboolean ret = FALSE;
268
269   GST_DEBUG ("Handling %s event", GST_EVENT_TYPE_NAME (event));
270
271   switch (GST_EVENT_TYPE (event)) {
272     case GST_EVENT_SEEK:
273     {
274       GstFormat format;
275       GstSeekType start_type, stop_type;
276       gint64 start, stop;
277       gdouble rate;
278       gboolean update;
279
280       gst_event_parse_seek (event, &rate, &format, &self->segment_flags,
281           &start_type, &start, &stop_type, &stop);
282
283       if (format != GST_FORMAT_TIME) {
284         GST_WARNING_OBJECT (self, "we only support seeking in TIME format");
285         gst_event_unref (event);
286         goto beach;
287       }
288
289       /* Convert that seek to a seeking in bytes at position 0,
290          FIXME: could use an index */
291       ret = gst_pad_push_event (self->sinkpad,
292           gst_event_new_seek (rate, GST_FORMAT_BYTES, self->segment_flags,
293               GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, 0));
294
295       if (ret) {
296         /* Apply the seek to our segment */
297         gst_segment_set_seek (&self->segment, rate, format, self->segment_flags,
298             start_type, start, stop_type, stop, &update);
299
300         GST_DEBUG_OBJECT (self, "segment after seek: %" GST_SEGMENT_FORMAT,
301             &self->segment);
302
303         self->next_offset = 0;
304
305         self->need_segment = TRUE;
306       } else {
307         GST_WARNING_OBJECT (self, "seek to 0 bytes failed");
308       }
309
310       gst_event_unref (event);
311       break;
312     }
313     default:
314       ret = gst_pad_event_default (pad, event);
315       break;
316   }
317
318 beach:
319   gst_object_unref (self);
320
321   return ret;
322 }
323
324 static void
325 gst_sub_parse_set_property (GObject * object, guint prop_id,
326     const GValue * value, GParamSpec * pspec)
327 {
328   GstSubParse *subparse = GST_SUBPARSE (object);
329
330   GST_OBJECT_LOCK (subparse);
331   switch (prop_id) {
332     case PROP_ENCODING:
333       g_free (subparse->encoding);
334       subparse->encoding = g_value_dup_string (value);
335       GST_LOG_OBJECT (object, "subtitle encoding set to %s",
336           GST_STR_NULL (subparse->encoding));
337       break;
338     default:
339       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
340       break;
341   }
342   GST_OBJECT_UNLOCK (subparse);
343 }
344
345 static void
346 gst_sub_parse_get_property (GObject * object, guint prop_id,
347     GValue * value, GParamSpec * pspec)
348 {
349   GstSubParse *subparse = GST_SUBPARSE (object);
350
351   GST_OBJECT_LOCK (subparse);
352   switch (prop_id) {
353     case PROP_ENCODING:
354       g_value_set_string (value, subparse->encoding);
355       break;
356     default:
357       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
358       break;
359   }
360   GST_OBJECT_UNLOCK (subparse);
361 }
362
363 static gchar *
364 gst_sub_parse_get_format_description (GstSubParseFormat format)
365 {
366   switch (format) {
367     case GST_SUB_PARSE_FORMAT_MDVDSUB:
368       return "MicroDVD";
369     case GST_SUB_PARSE_FORMAT_SUBRIP:
370       return "SubRip";
371     case GST_SUB_PARSE_FORMAT_MPSUB:
372       return "MPSub";
373     case GST_SUB_PARSE_FORMAT_SAMI:
374       return "SAMI";
375     case GST_SUB_PARSE_FORMAT_TMPLAYER:
376       return "TMPlayer";
377     case GST_SUB_PARSE_FORMAT_MPL2:
378       return "MPL2";
379     case GST_SUB_PARSE_FORMAT_SUBVIEWER:
380       return "SubViewer";
381     default:
382     case GST_SUB_PARSE_FORMAT_UNKNOWN:
383       break;
384   }
385   return NULL;
386 }
387
388 static gchar *
389 gst_convert_to_utf8 (const gchar * str, gsize len, const gchar * encoding,
390     gsize * consumed, GError ** err)
391 {
392   gchar *ret = NULL;
393
394   *consumed = 0;
395   ret =
396       g_convert_with_fallback (str, len, "UTF-8", encoding, "*", consumed, NULL,
397       err);
398   if (ret == NULL)
399     return ret;
400
401   /* + 3 to skip UTF-8 BOM if it was added */
402   len = strlen (ret);
403   if (len >= 3 && (guint8) ret[0] == 0xEF && (guint8) ret[1] == 0xBB
404       && (guint8) ret[2] == 0xBF)
405     g_memmove (ret, ret + 3, len + 1 - 3);
406
407   return ret;
408 }
409
410 static gchar *
411 detect_encoding (const gchar * str, gsize len)
412 {
413   if (len >= 3 && (guint8) str[0] == 0xEF && (guint8) str[1] == 0xBB
414       && (guint8) str[2] == 0xBF)
415     return g_strdup ("UTF-8");
416
417   if (len >= 2 && (guint8) str[0] == 0xFE && (guint8) str[1] == 0xFF)
418     return g_strdup ("UTF-16BE");
419
420   if (len >= 2 && (guint8) str[0] == 0xFF && (guint8) str[1] == 0xFE)
421     return g_strdup ("UTF-16LE");
422
423   if (len >= 4 && (guint8) str[0] == 0x00 && (guint8) str[1] == 0x00
424       && (guint8) str[2] == 0xFE && (guint8) str[3] == 0xFF)
425     return g_strdup ("UTF-32BE");
426
427   if (len >= 4 && (guint8) str[0] == 0xFF && (guint8) str[1] == 0xFE
428       && (guint8) str[2] == 0x00 && (guint8) str[3] == 0x00)
429     return g_strdup ("UTF-32LE");
430
431   return NULL;
432 }
433
434 static gchar *
435 convert_encoding (GstSubParse * self, const gchar * str, gsize len,
436     gsize * consumed)
437 {
438   const gchar *encoding;
439   GError *err = NULL;
440   gchar *ret = NULL;
441
442   *consumed = 0;
443
444   /* First try any detected encoding */
445   if (self->detected_encoding) {
446     ret =
447         gst_convert_to_utf8 (str, len, self->detected_encoding, consumed, &err);
448
449     if (!err)
450       return ret;
451
452     GST_WARNING_OBJECT (self, "could not convert string from '%s' to UTF-8: %s",
453         self->detected_encoding, err->message);
454     g_free (self->detected_encoding);
455     self->detected_encoding = NULL;
456     g_error_free (err);
457   }
458
459   /* Otherwise check if it's UTF8 */
460   if (self->valid_utf8) {
461     if (g_utf8_validate (str, len, NULL)) {
462       GST_LOG_OBJECT (self, "valid UTF-8, no conversion needed");
463       *consumed = len;
464       return g_strndup (str, len);
465     }
466     GST_INFO_OBJECT (self, "invalid UTF-8!");
467     self->valid_utf8 = FALSE;
468   }
469
470   /* Else try fallback */
471   encoding = self->encoding;
472   if (encoding == NULL || *encoding == '\0') {
473     encoding = g_getenv ("GST_SUBTITLE_ENCODING");
474   }
475   if (encoding == NULL || *encoding == '\0') {
476     /* if local encoding is UTF-8 and no encoding specified
477      * via the environment variable, assume ISO-8859-15 */
478     if (g_get_charset (&encoding)) {
479       encoding = "ISO-8859-15";
480     }
481   }
482
483   ret = gst_convert_to_utf8 (str, len, encoding, consumed, &err);
484
485   if (err) {
486     GST_WARNING_OBJECT (self, "could not convert string from '%s' to UTF-8: %s",
487         encoding, err->message);
488     g_error_free (err);
489
490     /* invalid input encoding, fall back to ISO-8859-15 (always succeeds) */
491     ret = gst_convert_to_utf8 (str, len, "ISO-8859-15", consumed, NULL);
492   }
493
494   GST_LOG_OBJECT (self,
495       "successfully converted %" G_GSIZE_FORMAT " characters from %s to UTF-8"
496       "%s", len, encoding, (err) ? " , using ISO-8859-15 as fallback" : "");
497
498   return ret;
499 }
500
501 static gchar *
502 get_next_line (GstSubParse * self)
503 {
504   char *line = NULL;
505   const char *line_end;
506   int line_len;
507   gboolean have_r = FALSE;
508
509   line_end = strchr (self->textbuf->str, '\n');
510
511   if (!line_end) {
512     /* end-of-line not found; return for more data */
513     return NULL;
514   }
515
516   /* get rid of '\r' */
517   if (line_end != self->textbuf->str && *(line_end - 1) == '\r') {
518     line_end--;
519     have_r = TRUE;
520   }
521
522   line_len = line_end - self->textbuf->str;
523   line = g_strndup (self->textbuf->str, line_len);
524   self->textbuf = g_string_erase (self->textbuf, 0,
525       line_len + (have_r ? 2 : 1));
526   return line;
527 }
528
529 static gchar *
530 parse_mdvdsub (ParserState * state, const gchar * line)
531 {
532   const gchar *line_split;
533   gchar *line_chunk;
534   guint start_frame, end_frame;
535   gint64 clip_start = 0, clip_stop = 0;
536   gboolean in_seg = FALSE;
537   GString *markup;
538   gchar *ret;
539
540   /* style variables */
541   gboolean italic;
542   gboolean bold;
543   guint fontsize;
544
545   if (sscanf (line, "{%u}{%u}", &start_frame, &end_frame) != 2) {
546     g_warning ("Parse of the following line, assumed to be in microdvd .sub"
547         " format, failed:\n%s", line);
548     return NULL;
549   }
550
551   /* skip the {%u}{%u} part */
552   line = strchr (line, '}') + 1;
553   line = strchr (line, '}') + 1;
554
555   /* see if there's a first line with a framerate */
556   if (state->fps == 0.0 && start_frame == 1 && end_frame == 1) {
557     gchar *rest, *end = NULL;
558
559     rest = g_strdup (line);
560     g_strdelimit (rest, ",", '.');
561     state->fps = g_ascii_strtod (rest, &end);
562     if (end == rest)
563       state->fps = 0.0;
564     GST_INFO ("framerate from file: %f ('%s')", state->fps, rest);
565     g_free (rest);
566     return NULL;
567   }
568
569   if (state->fps == 0.0) {
570     /* FIXME: hardcoded for now, is there a better way/assumption? */
571     state->fps = 24000.0 / 1001.0;
572     GST_INFO ("no framerate specified, assuming %f", state->fps);
573   }
574
575   state->start_time = start_frame / state->fps * GST_SECOND;
576   state->duration = (end_frame - start_frame) / state->fps * GST_SECOND;
577
578   /* Check our segment start/stop */
579   in_seg = gst_segment_clip (state->segment, GST_FORMAT_TIME,
580       state->start_time, state->start_time + state->duration, &clip_start,
581       &clip_stop);
582
583   /* No need to parse that text if it's out of segment */
584   if (in_seg) {
585     state->start_time = clip_start;
586     state->duration = clip_stop - clip_start;
587   } else {
588     return NULL;
589   }
590
591   markup = g_string_new (NULL);
592   while (1) {
593     italic = FALSE;
594     bold = FALSE;
595     fontsize = 0;
596     /* parse style markup */
597     if (strncmp (line, "{y:i}", 5) == 0) {
598       italic = TRUE;
599       line = strchr (line, '}') + 1;
600     }
601     if (strncmp (line, "{y:b}", 5) == 0) {
602       bold = TRUE;
603       line = strchr (line, '}') + 1;
604     }
605     if (sscanf (line, "{s:%u}", &fontsize) == 1) {
606       line = strchr (line, '}') + 1;
607     }
608     /* forward slashes at beginning/end signify italics too */
609     if (g_str_has_prefix (line, "/")) {
610       italic = TRUE;
611       ++line;
612     }
613     if ((line_split = strchr (line, '|')))
614       line_chunk = g_markup_escape_text (line, line_split - line);
615     else
616       line_chunk = g_markup_escape_text (line, strlen (line));
617
618     /* Remove italics markers at end of line/stanza (CHECKME: are end slashes
619      * always at the end of a line or can they span multiple lines?) */
620     if (g_str_has_suffix (line_chunk, "/")) {
621       line_chunk[strlen (line_chunk) - 1] = '\0';
622     }
623
624     markup = g_string_append (markup, "<span");
625     if (italic)
626       g_string_append (markup, " style=\"italic\"");
627     if (bold)
628       g_string_append (markup, " weight=\"bold\"");
629     if (fontsize)
630       g_string_append_printf (markup, " size=\"%u\"", fontsize * 1000);
631     g_string_append_printf (markup, ">%s</span>", line_chunk);
632     g_free (line_chunk);
633     if (line_split) {
634       g_string_append (markup, "\n");
635       line = line_split + 1;
636     } else {
637       break;
638     }
639   }
640   ret = markup->str;
641   g_string_free (markup, FALSE);
642   GST_DEBUG ("parse_mdvdsub returning (%f+%f): %s",
643       state->start_time / (double) GST_SECOND,
644       state->duration / (double) GST_SECOND, ret);
645   return ret;
646 }
647
648 static void
649 strip_trailing_newlines (gchar * txt)
650 {
651   if (txt) {
652     guint len;
653
654     len = strlen (txt);
655     while (len > 1 && txt[len - 1] == '\n') {
656       txt[len - 1] = '\0';
657       --len;
658     }
659   }
660 }
661
662 /* we want to escape text in general, but retain basic markup like
663  * <i></i>, <u></u>, and <b></b>. The easiest and safest way is to
664  * just unescape a white list of allowed markups again after
665  * escaping everything (the text between these simple markers isn't
666  * necessarily escaped, so it seems best to do it like this) */
667 static void
668 subrip_unescape_formatting (gchar * txt)
669 {
670   gchar *pos;
671
672   for (pos = txt; pos != NULL && *pos != '\0'; ++pos) {
673     if (g_ascii_strncasecmp (pos, "&lt;u&gt;", 9) == 0 ||
674         g_ascii_strncasecmp (pos, "&lt;i&gt;", 9) == 0 ||
675         g_ascii_strncasecmp (pos, "&lt;b&gt;", 9) == 0) {
676       pos[0] = '<';
677       pos[1] = g_ascii_tolower (pos[4]);
678       pos[2] = '>';
679       /* move NUL terminator as well */
680       g_memmove (pos + 3, pos + 9, strlen (pos + 9) + 1);
681       pos += 2;
682     }
683   }
684
685   for (pos = txt; pos != NULL && *pos != '\0'; ++pos) {
686     if (g_ascii_strncasecmp (pos, "&lt;/u&gt;", 10) == 0 ||
687         g_ascii_strncasecmp (pos, "&lt;/i&gt;", 10) == 0 ||
688         g_ascii_strncasecmp (pos, "&lt;/b&gt;", 10) == 0) {
689       pos[0] = '<';
690       pos[1] = '/';
691       pos[2] = g_ascii_tolower (pos[5]);
692       pos[3] = '>';
693       /* move NUL terminator as well */
694       g_memmove (pos + 4, pos + 10, strlen (pos + 10) + 1);
695       pos += 3;
696     }
697   }
698 }
699
700
701 static gboolean
702 subrip_remove_unhandled_tag (gchar * start, gchar * stop)
703 {
704   gchar *tag, saved;
705
706   tag = start + strlen ("&lt;");
707   if (*tag == '/')
708     ++tag;
709
710   if (g_ascii_tolower (*tag) < 'a' || g_ascii_tolower (*tag) > 'z')
711     return FALSE;
712
713   saved = *stop;
714   *stop = '\0';
715   GST_LOG ("removing unhandled tag '%s'", start);
716   *stop = saved;
717   g_memmove (start, stop, strlen (stop) + 1);
718   return TRUE;
719 }
720
721 /* remove tags we haven't explicitly allowed earlier on, like font tags
722  * for example */
723 static void
724 subrip_remove_unhandled_tags (gchar * txt)
725 {
726   gchar *pos, *gt;
727
728   for (pos = txt; pos != NULL && *pos != '\0'; ++pos) {
729     if (strncmp (pos, "&lt;", 4) == 0 && (gt = strstr (pos + 4, "&gt;"))) {
730       if (subrip_remove_unhandled_tag (pos, gt + strlen ("&gt;")))
731         --pos;
732     }
733   }
734 }
735
736 /* we only allow <i>, <u> and <b>, so let's take a simple approach. This code
737  * assumes the input has been escaped and subrip_unescape_formatting() has then
738  * been run over the input! This function adds missing closing markup tags and
739  * removes broken closing tags for tags that have never been opened. */
740 static void
741 subrip_fix_up_markup (gchar ** p_txt)
742 {
743   gchar *cur, *next_tag;
744   gchar open_tags[32];
745   guint num_open_tags = 0;
746
747   g_assert (*p_txt != NULL);
748
749   cur = *p_txt;
750   while (*cur != '\0') {
751     next_tag = strchr (cur, '<');
752     if (next_tag == NULL)
753       break;
754     ++next_tag;
755     switch (*next_tag) {
756       case '/':{
757         ++next_tag;
758         if (num_open_tags == 0 || open_tags[num_open_tags - 1] != *next_tag) {
759           GST_LOG ("broken input, closing tag '%c' is not open", *next_tag);
760           g_memmove (next_tag - 2, next_tag + 2, strlen (next_tag + 2) + 1);
761           next_tag -= 2;
762         } else {
763           /* it's all good, closing tag which is open */
764           --num_open_tags;
765         }
766         break;
767       }
768       case 'i':
769       case 'b':
770       case 'u':
771         if (num_open_tags == G_N_ELEMENTS (open_tags))
772           return;               /* something dodgy is going on, stop parsing */
773         open_tags[num_open_tags] = *next_tag;
774         ++num_open_tags;
775         break;
776       default:
777         GST_ERROR ("unexpected tag '%c' (%s)", *next_tag, next_tag);
778         g_assert_not_reached ();
779         break;
780     }
781     cur = next_tag;
782   }
783
784   if (num_open_tags > 0) {
785     GString *s;
786
787     s = g_string_new (*p_txt);
788     while (num_open_tags > 0) {
789       GST_LOG ("adding missing closing tag '%c'", open_tags[num_open_tags - 1]);
790       g_string_append_c (s, '<');
791       g_string_append_c (s, '/');
792       g_string_append_c (s, open_tags[num_open_tags - 1]);
793       g_string_append_c (s, '>');
794       --num_open_tags;
795     }
796     g_free (*p_txt);
797     *p_txt = g_string_free (s, FALSE);
798   }
799 }
800
801 static gboolean
802 parse_subrip_time (const gchar * ts_string, GstClockTime * t)
803 {
804   gchar s[128] = { '\0', };
805   gchar *end, *p;
806   guint hour, min, sec, msec, len;
807
808   while (*ts_string == ' ')
809     ++ts_string;
810
811   g_strlcpy (s, ts_string, sizeof (s));
812   if ((end = strstr (s, "-->")))
813     *end = '\0';
814   g_strchomp (s);
815
816   /* ms may be in these formats:
817    * hh:mm:ss,500 = 500ms
818    * hh:mm:ss,  5 =   5ms
819    * hh:mm:ss, 5  =  50ms
820    * hh:mm:ss, 50 =  50ms
821    * hh:mm:ss,5   = 500ms
822    * and sscanf() doesn't differentiate between '  5' and '5' so munge
823    * the white spaces within the timestamp to '0' (I'm sure there's a
824    * way to make sscanf() do this for us, but how?)
825    */
826   g_strdelimit (s, " ", '0');
827
828   /* make sure we have exactly three digits after he comma */
829   p = strchr (s, ',');
830   g_assert (p != NULL);
831   ++p;
832   len = strlen (p);
833   if (len > 3) {
834     p[3] = '\0';
835   } else
836     while (len < 3) {
837       g_strlcat (&p[len], "0", 2);
838       ++len;
839     }
840
841   GST_LOG ("parsing timestamp '%s'", s);
842   if (sscanf (s, "%u:%u:%u,%u", &hour, &min, &sec, &msec) != 4) {
843     GST_WARNING ("failed to parse subrip timestamp string '%s'", s);
844     return FALSE;
845   }
846
847   *t = ((hour * 3600) + (min * 60) + sec) * GST_SECOND + msec * GST_MSECOND;
848   return TRUE;
849 }
850
851 static gchar *
852 parse_subrip (ParserState * state, const gchar * line)
853 {
854   int subnum;
855   gchar *ret;
856
857   switch (state->state) {
858     case 0:
859       /* looking for a single integer */
860       if (sscanf (line, "%u", &subnum) == 1)
861         state->state = 1;
862       return NULL;
863     case 1:
864     {
865       GstClockTime ts_start, ts_end;
866       gchar *end_time;
867
868       /* looking for start_time --> end_time */
869       if ((end_time = strstr (line, " --> ")) &&
870           parse_subrip_time (line, &ts_start) &&
871           parse_subrip_time (end_time + strlen (" --> "), &ts_end) &&
872           state->start_time <= ts_end) {
873         state->state = 2;
874         state->start_time = ts_start;
875         state->duration = ts_end - ts_start;
876       } else {
877         GST_DEBUG ("error parsing subrip time line '%s'", line);
878         state->state = 0;
879       }
880       return NULL;
881     }
882     case 2:
883     {
884       /* No need to parse that text if it's out of segment */
885       gint64 clip_start = 0, clip_stop = 0;
886       gboolean in_seg = FALSE;
887
888       /* Check our segment start/stop */
889       in_seg = gst_segment_clip (state->segment, GST_FORMAT_TIME,
890           state->start_time, state->start_time + state->duration,
891           &clip_start, &clip_stop);
892
893       if (in_seg) {
894         state->start_time = clip_start;
895         state->duration = clip_stop - clip_start;
896       } else {
897         state->state = 0;
898         return NULL;
899       }
900     }
901       /* looking for subtitle text; empty line ends this subtitle entry */
902       if (state->buf->len)
903         g_string_append_c (state->buf, '\n');
904       g_string_append (state->buf, line);
905       if (strlen (line) == 0) {
906         ret = g_markup_escape_text (state->buf->str, state->buf->len);
907         g_string_truncate (state->buf, 0);
908         state->state = 0;
909         subrip_unescape_formatting (ret);
910         subrip_remove_unhandled_tags (ret);
911         strip_trailing_newlines (ret);
912         subrip_fix_up_markup (&ret);
913         return ret;
914       }
915       return NULL;
916     default:
917       g_return_val_if_reached (NULL);
918   }
919 }
920
921 static void
922 subviewer_unescape_newlines (gchar * read)
923 {
924   gchar *write = read;
925
926   /* Replace all occurences of '[br]' with a newline as version 2
927    * of the subviewer format uses this for newlines */
928
929   if (read[0] == '\0' || read[1] == '\0' || read[2] == '\0' || read[3] == '\0')
930     return;
931
932   do {
933     if (strncmp (read, "[br]", 4) == 0) {
934       *write = '\n';
935       read += 4;
936     } else {
937       *write = *read;
938       read++;
939     }
940     write++;
941   } while (*read);
942
943   *write = '\0';
944 }
945
946 static gchar *
947 parse_subviewer (ParserState * state, const gchar * line)
948 {
949   guint h1, m1, s1, ms1;
950   guint h2, m2, s2, ms2;
951   gchar *ret;
952
953   /* TODO: Maybe also parse the fields in the header, especially DELAY.
954    * For examples see the unit test or
955    * http://www.doom9.org/index.html?/sub.htm */
956
957   switch (state->state) {
958     case 0:
959       /* looking for start_time,end_time */
960       if (sscanf (line, "%u:%u:%u.%u,%u:%u:%u.%u",
961               &h1, &m1, &s1, &ms1, &h2, &m2, &s2, &ms2) == 8) {
962         state->state = 1;
963         state->start_time =
964             (((guint64) h1) * 3600 + m1 * 60 + s1) * GST_SECOND +
965             ms1 * GST_MSECOND;
966         state->duration =
967             (((guint64) h2) * 3600 + m2 * 60 + s2) * GST_SECOND +
968             ms2 * GST_MSECOND - state->start_time;
969       }
970       return NULL;
971     case 1:
972     {
973       /* No need to parse that text if it's out of segment */
974       gint64 clip_start = 0, clip_stop = 0;
975       gboolean in_seg = FALSE;
976
977       /* Check our segment start/stop */
978       in_seg = gst_segment_clip (state->segment, GST_FORMAT_TIME,
979           state->start_time, state->start_time + state->duration,
980           &clip_start, &clip_stop);
981
982       if (in_seg) {
983         state->start_time = clip_start;
984         state->duration = clip_stop - clip_start;
985       } else {
986         state->state = 0;
987         return NULL;
988       }
989     }
990       /* looking for subtitle text; empty line ends this subtitle entry */
991       if (state->buf->len)
992         g_string_append_c (state->buf, '\n');
993       g_string_append (state->buf, line);
994       if (strlen (line) == 0) {
995         ret = g_strdup (state->buf->str);
996         subviewer_unescape_newlines (ret);
997         strip_trailing_newlines (ret);
998         g_string_truncate (state->buf, 0);
999         state->state = 0;
1000         return ret;
1001       }
1002       return NULL;
1003     default:
1004       g_assert_not_reached ();
1005       return NULL;
1006   }
1007 }
1008
1009 static gchar *
1010 parse_mpsub (ParserState * state, const gchar * line)
1011 {
1012   gchar *ret;
1013   float t1, t2;
1014
1015   switch (state->state) {
1016     case 0:
1017       /* looking for two floats (offset, duration) */
1018       if (sscanf (line, "%f %f", &t1, &t2) == 2) {
1019         state->state = 1;
1020         state->start_time += state->duration + GST_SECOND * t1;
1021         state->duration = GST_SECOND * t2;
1022       }
1023       return NULL;
1024     case 1:
1025     {                           /* No need to parse that text if it's out of segment */
1026       gint64 clip_start = 0, clip_stop = 0;
1027       gboolean in_seg = FALSE;
1028
1029       /* Check our segment start/stop */
1030       in_seg = gst_segment_clip (state->segment, GST_FORMAT_TIME,
1031           state->start_time, state->start_time + state->duration,
1032           &clip_start, &clip_stop);
1033
1034       if (in_seg) {
1035         state->start_time = clip_start;
1036         state->duration = clip_stop - clip_start;
1037       } else {
1038         state->state = 0;
1039         return NULL;
1040       }
1041     }
1042       /* looking for subtitle text; empty line ends this
1043        * subtitle entry */
1044       if (state->buf->len)
1045         g_string_append_c (state->buf, '\n');
1046       g_string_append (state->buf, line);
1047       if (strlen (line) == 0) {
1048         ret = g_strdup (state->buf->str);
1049         g_string_truncate (state->buf, 0);
1050         state->state = 0;
1051         return ret;
1052       }
1053       return NULL;
1054     default:
1055       g_assert_not_reached ();
1056       return NULL;
1057   }
1058 }
1059
1060 static void
1061 parser_state_init (ParserState * state)
1062 {
1063   GST_DEBUG ("initialising parser");
1064
1065   if (state->buf) {
1066     g_string_truncate (state->buf, 0);
1067   } else {
1068     state->buf = g_string_new (NULL);
1069   }
1070
1071   state->start_time = 0;
1072   state->duration = 0;
1073   state->max_duration = 0;      /* no limit */
1074   state->state = 0;
1075   state->segment = NULL;
1076 }
1077
1078 static void
1079 parser_state_dispose (ParserState * state)
1080 {
1081   if (state->buf) {
1082     g_string_free (state->buf, TRUE);
1083     state->buf = NULL;
1084   }
1085 #ifndef GST_DISABLE_XML
1086   if (state->user_data) {
1087     sami_context_reset (state);
1088   }
1089 #endif
1090 }
1091
1092 /* regex type enum */
1093 typedef enum
1094 {
1095   GST_SUB_PARSE_REGEX_UNKNOWN = 0,
1096   GST_SUB_PARSE_REGEX_MDVDSUB = 1,
1097   GST_SUB_PARSE_REGEX_SUBRIP = 2,
1098 } GstSubParseRegex;
1099
1100 static gpointer
1101 gst_sub_parse_data_format_autodetect_regex_once (GstSubParseRegex regtype)
1102 {
1103   gpointer result = NULL;
1104   GError *gerr = NULL;
1105   switch (regtype) {
1106     case GST_SUB_PARSE_REGEX_MDVDSUB:
1107       result =
1108           (gpointer) g_regex_new ("^\\{[0-9]+\\}\\{[0-9]+\\}", 0, 0, &gerr);
1109       if (result == NULL) {
1110         g_warning ("Compilation of mdvd regex failed: %s", gerr->message);
1111         g_error_free (gerr);
1112       }
1113       break;
1114     case GST_SUB_PARSE_REGEX_SUBRIP:
1115       result = (gpointer) g_regex_new ("^([ 0-9]){0,3}[0-9]\\s*(\x0d)?\x0a"
1116           "[ 0-9][0-9]:[ 0-9][0-9]:[ 0-9][0-9],[ 0-9]{0,2}[0-9]"
1117           " +--> +([ 0-9])?[0-9]:[ 0-9][0-9]:[ 0-9][0-9],[ 0-9]{0,2}[0-9]",
1118           0, 0, &gerr);
1119       if (result == NULL) {
1120         g_warning ("Compilation of subrip regex failed: %s", gerr->message);
1121         g_error_free (gerr);
1122       }
1123       break;
1124     default:
1125       GST_WARNING ("Trying to allocate regex of unknown type %u", regtype);
1126   }
1127   return result;
1128 }
1129
1130 /*
1131  * FIXME: maybe we should pass along a second argument, the preceding
1132  * text buffer, because that is how this originally worked, even though
1133  * I don't really see the use of that.
1134  */
1135
1136 static GstSubParseFormat
1137 gst_sub_parse_data_format_autodetect (gchar * match_str)
1138 {
1139   guint n1, n2, n3;
1140
1141   static GOnce mdvd_rx_once = G_ONCE_INIT;
1142   static GOnce subrip_rx_once = G_ONCE_INIT;
1143
1144   GRegex *mdvd_grx;
1145   GRegex *subrip_grx;
1146
1147   g_once (&mdvd_rx_once,
1148       (GThreadFunc) gst_sub_parse_data_format_autodetect_regex_once,
1149       (gpointer) GST_SUB_PARSE_REGEX_MDVDSUB);
1150   g_once (&subrip_rx_once,
1151       (GThreadFunc) gst_sub_parse_data_format_autodetect_regex_once,
1152       (gpointer) GST_SUB_PARSE_REGEX_SUBRIP);
1153
1154   mdvd_grx = (GRegex *) mdvd_rx_once.retval;
1155   subrip_grx = (GRegex *) subrip_rx_once.retval;
1156
1157   if (g_regex_match (mdvd_grx, match_str, 0, NULL) == TRUE) {
1158     GST_LOG ("MicroDVD (frame based) format detected");
1159     return GST_SUB_PARSE_FORMAT_MDVDSUB;
1160   }
1161   if (g_regex_match (subrip_grx, match_str, 0, NULL) == TRUE) {
1162     GST_LOG ("SubRip (time based) format detected");
1163     return GST_SUB_PARSE_FORMAT_SUBRIP;
1164   }
1165
1166   if (!strncmp (match_str, "FORMAT=TIME", 11)) {
1167     GST_LOG ("MPSub (time based) format detected");
1168     return GST_SUB_PARSE_FORMAT_MPSUB;
1169   }
1170 #ifndef GST_DISABLE_XML
1171   if (strstr (match_str, "<SAMI>") != NULL ||
1172       strstr (match_str, "<sami>") != NULL) {
1173     GST_LOG ("SAMI (time based) format detected");
1174     return GST_SUB_PARSE_FORMAT_SAMI;
1175   }
1176 #endif
1177   /* we're boldly assuming the first subtitle appears within the first hour */
1178   if (sscanf (match_str, "0:%02u:%02u:", &n1, &n2) == 2 ||
1179       sscanf (match_str, "0:%02u:%02u=", &n1, &n2) == 2 ||
1180       sscanf (match_str, "00:%02u:%02u:", &n1, &n2) == 2 ||
1181       sscanf (match_str, "00:%02u:%02u=", &n1, &n2) == 2 ||
1182       sscanf (match_str, "00:%02u:%02u,%u=", &n1, &n2, &n3) == 3) {
1183     GST_LOG ("TMPlayer (time based) format detected");
1184     return GST_SUB_PARSE_FORMAT_TMPLAYER;
1185   }
1186   if (sscanf (match_str, "[%u][%u]", &n1, &n2) == 2) {
1187     GST_LOG ("MPL2 (time based) format detected");
1188     return GST_SUB_PARSE_FORMAT_MPL2;
1189   }
1190   if (strstr (match_str, "[INFORMATION]") != NULL) {
1191     GST_LOG ("SubViewer (time based) format detected");
1192     return GST_SUB_PARSE_FORMAT_SUBVIEWER;
1193   }
1194
1195   GST_DEBUG ("no subtitle format detected");
1196   return GST_SUB_PARSE_FORMAT_UNKNOWN;
1197 }
1198
1199 static GstCaps *
1200 gst_sub_parse_format_autodetect (GstSubParse * self)
1201 {
1202   gchar *data;
1203   GstSubParseFormat format;
1204
1205   if (strlen (self->textbuf->str) < 30) {
1206     GST_DEBUG ("File too small to be a subtitles file");
1207     return NULL;
1208   }
1209
1210   data = g_strndup (self->textbuf->str, 35);
1211   format = gst_sub_parse_data_format_autodetect (data);
1212   g_free (data);
1213
1214   self->parser_type = format;
1215   self->subtitle_codec = gst_sub_parse_get_format_description (format);
1216   parser_state_init (&self->state);
1217
1218   switch (format) {
1219     case GST_SUB_PARSE_FORMAT_MDVDSUB:
1220       self->parse_line = parse_mdvdsub;
1221       return gst_caps_new_simple ("text/x-pango-markup", NULL);
1222     case GST_SUB_PARSE_FORMAT_SUBRIP:
1223       self->parse_line = parse_subrip;
1224       return gst_caps_new_simple ("text/x-pango-markup", NULL);
1225     case GST_SUB_PARSE_FORMAT_MPSUB:
1226       self->parse_line = parse_mpsub;
1227       return gst_caps_new_simple ("text/plain", NULL);
1228 #ifndef GST_DISABLE_XML
1229     case GST_SUB_PARSE_FORMAT_SAMI:
1230       self->parse_line = parse_sami;
1231       sami_context_init (&self->state);
1232       return gst_caps_new_simple ("text/x-pango-markup", NULL);
1233 #endif
1234     case GST_SUB_PARSE_FORMAT_TMPLAYER:
1235       self->parse_line = parse_tmplayer;
1236       self->state.max_duration = 5 * GST_SECOND;
1237       return gst_caps_new_simple ("text/plain", NULL);
1238     case GST_SUB_PARSE_FORMAT_MPL2:
1239       self->parse_line = parse_mpl2;
1240       return gst_caps_new_simple ("text/x-pango-markup", NULL);
1241     case GST_SUB_PARSE_FORMAT_SUBVIEWER:
1242       self->parse_line = parse_subviewer;
1243       return gst_caps_new_simple ("text/plain", NULL);
1244     case GST_SUB_PARSE_FORMAT_UNKNOWN:
1245     default:
1246       GST_DEBUG ("no subtitle format detected");
1247       GST_ELEMENT_ERROR (self, STREAM, WRONG_TYPE,
1248           ("The input is not a valid/supported subtitle file"), (NULL));
1249       return NULL;
1250   }
1251 }
1252
1253 static void
1254 feed_textbuf (GstSubParse * self, GstBuffer * buf)
1255 {
1256   gboolean discont;
1257   gsize consumed;
1258   gchar *input = NULL;
1259
1260   discont = GST_BUFFER_IS_DISCONT (buf);
1261
1262   if (GST_BUFFER_OFFSET_IS_VALID (buf) &&
1263       GST_BUFFER_OFFSET (buf) != self->offset) {
1264     self->offset = GST_BUFFER_OFFSET (buf);
1265     discont = TRUE;
1266   }
1267
1268   if (discont) {
1269     GST_INFO ("discontinuity");
1270     /* flush the parser state */
1271     parser_state_init (&self->state);
1272     g_string_truncate (self->textbuf, 0);
1273     gst_adapter_clear (self->adapter);
1274 #ifndef GST_DISABLE_XML
1275     sami_context_reset (&self->state);
1276 #endif
1277     /* we could set a flag to make sure that the next buffer we push out also
1278      * has the DISCONT flag set, but there's no point really given that it's
1279      * subtitles which are discontinuous by nature. */
1280   }
1281
1282   self->offset = GST_BUFFER_OFFSET (buf) + GST_BUFFER_SIZE (buf);
1283   self->next_offset = self->offset;
1284
1285   gst_adapter_push (self->adapter, buf);
1286
1287   input =
1288       convert_encoding (self, (const gchar *) gst_adapter_peek (self->adapter,
1289           gst_adapter_available (self->adapter)),
1290       (gsize) gst_adapter_available (self->adapter), &consumed);
1291
1292   if (input && consumed > 0) {
1293     self->textbuf = g_string_append (self->textbuf, input);
1294     gst_adapter_flush (self->adapter, consumed);
1295   }
1296
1297   g_free (input);
1298 }
1299
1300 static GstFlowReturn
1301 handle_buffer (GstSubParse * self, GstBuffer * buf)
1302 {
1303   GstFlowReturn ret = GST_FLOW_OK;
1304   GstCaps *caps = NULL;
1305   gchar *line, *subtitle;
1306
1307   if (self->first_buffer) {
1308     self->detected_encoding =
1309         detect_encoding ((gchar *) GST_BUFFER_DATA (buf),
1310         GST_BUFFER_SIZE (buf));
1311     self->first_buffer = FALSE;
1312   }
1313
1314   feed_textbuf (self, buf);
1315
1316   /* make sure we know the format */
1317   if (G_UNLIKELY (self->parser_type == GST_SUB_PARSE_FORMAT_UNKNOWN)) {
1318     if (!(caps = gst_sub_parse_format_autodetect (self))) {
1319       return GST_FLOW_UNEXPECTED;
1320     }
1321     if (!gst_pad_set_caps (self->srcpad, caps)) {
1322       gst_caps_unref (caps);
1323       return GST_FLOW_UNEXPECTED;
1324     }
1325     gst_caps_unref (caps);
1326
1327     /* push tags */
1328     if (self->subtitle_codec != NULL) {
1329       GstTagList *tags;
1330
1331       tags = gst_tag_list_new ();
1332       gst_tag_list_add (tags, GST_TAG_MERGE_APPEND, GST_TAG_SUBTITLE_CODEC,
1333           self->subtitle_codec, NULL);
1334       gst_element_found_tags_for_pad (GST_ELEMENT (self), self->srcpad, tags);
1335     }
1336   }
1337
1338   while (!self->flushing && (line = get_next_line (self))) {
1339     guint offset = 0;
1340
1341     /* Set segment on our parser state machine */
1342     self->state.segment = &self->segment;
1343     /* Now parse the line, out of segment lines will just return NULL */
1344     GST_LOG_OBJECT (self, "Parsing line '%s'", line + offset);
1345     subtitle = self->parse_line (&self->state, line + offset);
1346     g_free (line);
1347
1348     if (subtitle) {
1349       guint subtitle_len = strlen (subtitle);
1350
1351       /* +1 for terminating NUL character */
1352       ret = gst_pad_alloc_buffer_and_set_caps (self->srcpad,
1353           GST_BUFFER_OFFSET_NONE, subtitle_len + 1,
1354           GST_PAD_CAPS (self->srcpad), &buf);
1355
1356       if (ret == GST_FLOW_OK) {
1357         /* copy terminating NUL character as well */
1358         memcpy (GST_BUFFER_DATA (buf), subtitle, subtitle_len + 1);
1359         GST_BUFFER_SIZE (buf) = subtitle_len;
1360         GST_BUFFER_TIMESTAMP (buf) = self->state.start_time;
1361         GST_BUFFER_DURATION (buf) = self->state.duration;
1362
1363         /* in some cases (e.g. tmplayer) we can only determine the duration
1364          * of a text chunk from the timestamp of the next text chunk; in those
1365          * cases, we probably want to limit the duration to something
1366          * reasonable, so we don't end up showing some text for e.g. 40 seconds
1367          * just because nothing else is being said during that time */
1368         if (self->state.max_duration > 0 && GST_BUFFER_DURATION_IS_VALID (buf)) {
1369           if (GST_BUFFER_DURATION (buf) > self->state.max_duration)
1370             GST_BUFFER_DURATION (buf) = self->state.max_duration;
1371         }
1372
1373         gst_segment_set_last_stop (&self->segment, GST_FORMAT_TIME,
1374             self->state.start_time);
1375
1376         GST_DEBUG_OBJECT (self, "Sending text '%s', %" GST_TIME_FORMAT " + %"
1377             GST_TIME_FORMAT, subtitle, GST_TIME_ARGS (self->state.start_time),
1378             GST_TIME_ARGS (self->state.duration));
1379
1380         ret = gst_pad_push (self->srcpad, buf);
1381       }
1382
1383       /* move this forward (the tmplayer parser needs this) */
1384       if (self->state.duration != GST_CLOCK_TIME_NONE)
1385         self->state.start_time += self->state.duration;
1386
1387       g_free (subtitle);
1388       subtitle = NULL;
1389
1390       if (ret != GST_FLOW_OK) {
1391         GST_DEBUG_OBJECT (self, "flow: %s", gst_flow_get_name (ret));
1392         break;
1393       }
1394     }
1395   }
1396
1397   return ret;
1398 }
1399
1400 static GstFlowReturn
1401 gst_sub_parse_chain (GstPad * sinkpad, GstBuffer * buf)
1402 {
1403   GstFlowReturn ret;
1404   GstSubParse *self;
1405
1406   self = GST_SUBPARSE (GST_PAD_PARENT (sinkpad));
1407
1408   /* Push newsegment if needed */
1409   if (self->need_segment) {
1410     GST_LOG_OBJECT (self, "pushing newsegment event with %" GST_SEGMENT_FORMAT,
1411         &self->segment);
1412
1413     gst_pad_push_event (self->srcpad, gst_event_new_new_segment (FALSE,
1414             self->segment.rate, self->segment.format,
1415             self->segment.last_stop, self->segment.stop, self->segment.time));
1416     self->need_segment = FALSE;
1417   }
1418
1419   ret = handle_buffer (self, buf);
1420
1421   return ret;
1422 }
1423
1424 static gboolean
1425 gst_sub_parse_sink_event (GstPad * pad, GstEvent * event)
1426 {
1427   GstSubParse *self = GST_SUBPARSE (gst_pad_get_parent (pad));
1428   gboolean ret = FALSE;
1429
1430   GST_DEBUG ("Handling %s event", GST_EVENT_TYPE_NAME (event));
1431
1432   switch (GST_EVENT_TYPE (event)) {
1433     case GST_EVENT_EOS:{
1434       /* Make sure the last subrip chunk is pushed out even
1435        * if the file does not have an empty line at the end */
1436       if (self->parser_type == GST_SUB_PARSE_FORMAT_SUBRIP ||
1437           self->parser_type == GST_SUB_PARSE_FORMAT_TMPLAYER ||
1438           self->parser_type == GST_SUB_PARSE_FORMAT_MPL2) {
1439         GstBuffer *buf = gst_buffer_new_and_alloc (2 + 1);
1440
1441         GST_DEBUG ("EOS. Pushing remaining text (if any)");
1442         GST_BUFFER_DATA (buf)[0] = '\n';
1443         GST_BUFFER_DATA (buf)[1] = '\n';
1444         GST_BUFFER_DATA (buf)[2] = '\0';        /* play it safe */
1445         GST_BUFFER_SIZE (buf) = 2;
1446         GST_BUFFER_OFFSET (buf) = self->offset;
1447         gst_sub_parse_chain (pad, buf);
1448       }
1449       ret = gst_pad_event_default (pad, event);
1450       break;
1451     }
1452     case GST_EVENT_NEWSEGMENT:
1453     {
1454       GstFormat format;
1455       gdouble rate;
1456       gint64 start, stop, time;
1457       gboolean update;
1458
1459       gst_event_parse_new_segment (event, &update, &rate, &format, &start,
1460           &stop, &time);
1461
1462       GST_DEBUG_OBJECT (self, "newsegment (%s)", gst_format_get_name (format));
1463
1464       if (format == GST_FORMAT_TIME) {
1465         gst_segment_set_newsegment (&self->segment, update, rate, format,
1466             start, stop, time);
1467       } else {
1468         /* if not time format, we'll either start with a 0 timestamp anyway or
1469          * it's following a seek in which case we'll have saved the requested
1470          * seek segment and don't want to overwrite it (remember that on a seek
1471          * we always just seek back to the start in BYTES format and just throw
1472          * away all text that's before the requested position; if the subtitles
1473          * come from an upstream demuxer, it won't be able to handle our BYTES
1474          * seek request and instead send us a newsegment from the seek request
1475          * it received via its video pads instead, so all is fine then too) */
1476       }
1477
1478       ret = TRUE;
1479       gst_event_unref (event);
1480       break;
1481     }
1482     case GST_EVENT_FLUSH_START:
1483     {
1484       self->flushing = TRUE;
1485
1486       ret = gst_pad_event_default (pad, event);
1487       break;
1488     }
1489     case GST_EVENT_FLUSH_STOP:
1490     {
1491       self->flushing = FALSE;
1492
1493       ret = gst_pad_event_default (pad, event);
1494       break;
1495     }
1496     default:
1497       ret = gst_pad_event_default (pad, event);
1498       break;
1499   }
1500
1501   gst_object_unref (self);
1502
1503   return ret;
1504 }
1505
1506
1507 static GstStateChangeReturn
1508 gst_sub_parse_change_state (GstElement * element, GstStateChange transition)
1509 {
1510   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1511   GstSubParse *self = GST_SUBPARSE (element);
1512
1513   switch (transition) {
1514     case GST_STATE_CHANGE_READY_TO_PAUSED:
1515       /* format detection will init the parser state */
1516       self->offset = 0;
1517       self->next_offset = 0;
1518       self->parser_type = GST_SUB_PARSE_FORMAT_UNKNOWN;
1519       self->valid_utf8 = TRUE;
1520       self->first_buffer = TRUE;
1521       g_free (self->detected_encoding);
1522       self->detected_encoding = NULL;
1523       g_string_truncate (self->textbuf, 0);
1524       gst_adapter_clear (self->adapter);
1525       break;
1526     default:
1527       break;
1528   }
1529
1530   ret = parent_class->change_state (element, transition);
1531   if (ret == GST_STATE_CHANGE_FAILURE)
1532     return ret;
1533
1534   switch (transition) {
1535     case GST_STATE_CHANGE_PAUSED_TO_READY:
1536       parser_state_dispose (&self->state);
1537       self->parser_type = GST_SUB_PARSE_FORMAT_UNKNOWN;
1538       break;
1539     default:
1540       break;
1541   }
1542
1543   return ret;
1544 }
1545
1546 /*
1547  * Typefind support.
1548  */
1549
1550 /* FIXME 0.11: these caps are ugly, use app/x-subtitle + type field or so;
1551  * also, give different  subtitle formats really different types */
1552 static GstStaticCaps mpl2_caps =
1553 GST_STATIC_CAPS ("application/x-subtitle-mpl2");
1554 #define SUB_CAPS (gst_static_caps_get (&sub_caps))
1555
1556 static GstStaticCaps tmp_caps =
1557 GST_STATIC_CAPS ("application/x-subtitle-tmplayer");
1558 #define TMP_CAPS (gst_static_caps_get (&tmp_caps))
1559
1560 static GstStaticCaps sub_caps = GST_STATIC_CAPS ("application/x-subtitle");
1561 #define MPL2_CAPS (gst_static_caps_get (&mpl2_caps))
1562
1563 #ifndef GST_DISABLE_XML
1564 static GstStaticCaps smi_caps = GST_STATIC_CAPS ("application/x-subtitle-sami");
1565 #define SAMI_CAPS (gst_static_caps_get (&smi_caps))
1566 #endif
1567
1568 static void
1569 gst_subparse_type_find (GstTypeFind * tf, gpointer private)
1570 {
1571   GstSubParseFormat format;
1572   const guint8 *data;
1573   GstCaps *caps;
1574   gchar *str;
1575   gchar *encoding = NULL;
1576   const gchar *end;
1577
1578   if (!(data = gst_type_find_peek (tf, 0, 129)))
1579     return;
1580
1581   /* make sure string passed to _autodetect() is NUL-terminated */
1582   str = g_malloc0 (129);
1583   memcpy (str, data, 128);
1584
1585   if ((encoding = detect_encoding (str, 128)) != NULL) {
1586     gchar *converted_str;
1587     GError *err = NULL;
1588     gsize tmp;
1589
1590     converted_str = gst_convert_to_utf8 (str, 128, encoding, &tmp, &err);
1591     if (converted_str == NULL) {
1592       GST_DEBUG ("Encoding '%s' detected but conversion failed: %s", encoding,
1593           err->message);
1594       g_error_free (err);
1595       g_free (encoding);
1596     } else {
1597       g_free (str);
1598       str = converted_str;
1599       g_free (encoding);
1600     }
1601   }
1602
1603   /* Check if at least the first 120 chars are valid UTF8,
1604    * otherwise convert as always */
1605   if (!g_utf8_validate (str, 128, &end) && (end - str) < 120) {
1606     gchar *converted_str;
1607     GError *err = NULL;
1608     gsize tmp;
1609     const gchar *enc;
1610
1611     enc = g_getenv ("GST_SUBTITLE_ENCODING");
1612     if (enc == NULL || *enc == '\0') {
1613       /* if local encoding is UTF-8 and no encoding specified
1614        * via the environment variable, assume ISO-8859-15 */
1615       if (g_get_charset (&enc)) {
1616         enc = "ISO-8859-15";
1617       }
1618     }
1619     converted_str = gst_convert_to_utf8 (str, 128, enc, &tmp, &err);
1620     if (converted_str == NULL) {
1621       GST_DEBUG ("Charset conversion failed: %s", err->message);
1622       g_error_free (err);
1623       g_free (str);
1624       return;
1625     } else {
1626       g_free (str);
1627       str = converted_str;
1628     }
1629   }
1630
1631   format = gst_sub_parse_data_format_autodetect (str);
1632   g_free (str);
1633
1634   switch (format) {
1635     case GST_SUB_PARSE_FORMAT_MDVDSUB:
1636       GST_DEBUG ("MicroDVD format detected");
1637       caps = SUB_CAPS;
1638       break;
1639     case GST_SUB_PARSE_FORMAT_SUBRIP:
1640       GST_DEBUG ("SubRip format detected");
1641       caps = SUB_CAPS;
1642       break;
1643     case GST_SUB_PARSE_FORMAT_MPSUB:
1644       GST_DEBUG ("MPSub format detected");
1645       caps = SUB_CAPS;
1646       break;
1647 #ifndef GST_DISABLE_XML
1648     case GST_SUB_PARSE_FORMAT_SAMI:
1649       GST_DEBUG ("SAMI (time-based) format detected");
1650       caps = SAMI_CAPS;
1651       break;
1652 #endif
1653     case GST_SUB_PARSE_FORMAT_TMPLAYER:
1654       GST_DEBUG ("TMPlayer (time based) format detected");
1655       caps = TMP_CAPS;
1656       break;
1657       /* FIXME: our MPL2 typefinding is not really good enough to warrant
1658        * returning a high probability (however, since we registered our
1659        * typefinder here with a rank of MARGINAL we should pretty much only
1660        * be called if most other typefinders have already run */
1661     case GST_SUB_PARSE_FORMAT_MPL2:
1662       GST_DEBUG ("MPL2 (time based) format detected");
1663       caps = MPL2_CAPS;
1664       break;
1665     case GST_SUB_PARSE_FORMAT_SUBVIEWER:
1666       GST_DEBUG ("SubViewer format detected");
1667       caps = SUB_CAPS;
1668       break;
1669     default:
1670     case GST_SUB_PARSE_FORMAT_UNKNOWN:
1671       GST_DEBUG ("no subtitle format detected");
1672       return;
1673   }
1674
1675   /* if we're here, it's ok */
1676   gst_type_find_suggest (tf, GST_TYPE_FIND_MAXIMUM, caps);
1677 }
1678
1679 static gboolean
1680 plugin_init (GstPlugin * plugin)
1681 {
1682   static gchar *sub_exts[] = { "srt", "sub", "mpsub", "mdvd", "smi", "txt",
1683     NULL
1684   };
1685
1686   GST_DEBUG_CATEGORY_INIT (sub_parse_debug, "subparse", 0, ".sub parser");
1687
1688   if (!gst_type_find_register (plugin, "subparse_typefind", GST_RANK_MARGINAL,
1689           gst_subparse_type_find, sub_exts, SUB_CAPS, NULL, NULL))
1690     return FALSE;
1691
1692   if (!gst_element_register (plugin, "subparse",
1693           GST_RANK_PRIMARY, GST_TYPE_SUBPARSE) ||
1694       !gst_element_register (plugin, "ssaparse",
1695           GST_RANK_PRIMARY, GST_TYPE_SSA_PARSE)) {
1696     return FALSE;
1697   }
1698
1699   return TRUE;
1700 }
1701
1702 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1703     GST_VERSION_MINOR,
1704     "subparse",
1705     "Subtitle parsing",
1706     plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)