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