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