38b1d2c4a92616a70eebe201ac2879eebfe240d6
[platform/upstream/gst-plugins-base.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  * Copyright (C) 2016 Philippe Normand <pnormand@igalia.com>
6  * Copyright (C) 2016 Jan Schmidt <jan@centricular.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/types.h>
32 #include <glib.h>
33
34 #include "gstsubparse.h"
35 #include "gstssaparse.h"
36 #include "samiparse.h"
37 #include "tmplayerparse.h"
38 #include "mpl2parse.h"
39 #include "qttextparse.h"
40
41 GST_DEBUG_CATEGORY (sub_parse_debug);
42
43 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
44 #define SUBPARSE_SEEK_GET_LOCK(elem)    (&elem->seek_lock)
45 #define SUBPARSE_SEEK_LOCK(elem)        g_mutex_lock(SUBPARSE_SEEK_GET_LOCK(elem))
46 #define SUBPARSE_SEEK_TRYLOCK(elem)     g_mutex_trylock(SUBPARSE_SEEK_GET_LOCK(elem))
47 #define SUBPARSE_SEEK_UNLOCK(elem)      g_mutex_unlock(SUBPARSE_SEEK_GET_LOCK(elem))
48 #endif
49
50 #define DEFAULT_ENCODING   NULL
51 #define ATTRIBUTE_REGEX "\\s?[a-zA-Z0-9\\. \t\\(\\)]*"
52 static const gchar *allowed_srt_tags[] = { "i", "b", "u", NULL };
53 static const gchar *allowed_vtt_tags[] =
54     { "i", "b", "c", "u", "v", "ruby", "rt", NULL };
55
56 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
57 #define DEFAULT_CURRENT_LANGUAGE   NULL
58 #endif
59 enum
60 {
61   PROP_0,
62   PROP_ENCODING,
63   PROP_VIDEOFPS,
64 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
65   PROP_EXTSUB_CURRENT_LANGUAGE
66 #endif
67 };
68 #ifdef TIZEN_FEATURE_HLS_WEBVTT
69 #define MPEGTIME_TO_GSTTIME(t) ((t) * (guint64)100000 / 9)
70 #endif
71
72 static void
73 gst_sub_parse_set_property (GObject * object, guint prop_id,
74     const GValue * value, GParamSpec * pspec);
75 static void
76 gst_sub_parse_get_property (GObject * object, guint prop_id,
77     GValue * value, GParamSpec * pspec);
78
79
80 static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink",
81     GST_PAD_SINK,
82     GST_PAD_ALWAYS,
83     GST_STATIC_CAPS ("application/x-subtitle; application/x-subtitle-sami; "
84         "application/x-subtitle-tmplayer; application/x-subtitle-mpl2; "
85         "application/x-subtitle-dks; application/x-subtitle-qttext;"
86         "application/x-subtitle-lrc; application/x-subtitle-vtt")
87     );
88
89 static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
90     GST_PAD_SRC,
91     GST_PAD_ALWAYS,
92     GST_STATIC_CAPS ("text/x-raw, format= { pango-markup, utf8 }")
93     );
94
95
96 static gboolean gst_sub_parse_src_event (GstPad * pad, GstObject * parent,
97     GstEvent * event);
98 static gboolean gst_sub_parse_src_query (GstPad * pad, GstObject * parent,
99     GstQuery * query);
100 static gboolean gst_sub_parse_sink_event (GstPad * pad, GstObject * parent,
101     GstEvent * event);
102
103 static GstStateChangeReturn gst_sub_parse_change_state (GstElement * element,
104     GstStateChange transition);
105
106 static GstFlowReturn gst_sub_parse_chain (GstPad * sinkpad, GstObject * parent,
107     GstBuffer * buf);
108 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
109 static gboolean gst_sub_parse_check_byte_seekability (GstSubParse * subparse);
110 #endif
111 #define gst_sub_parse_parent_class parent_class
112 G_DEFINE_TYPE (GstSubParse, gst_sub_parse, GST_TYPE_ELEMENT);
113
114 static void
115 gst_sub_parse_dispose (GObject * object)
116 {
117   GstSubParse *subparse = GST_SUBPARSE (object);
118
119   GST_DEBUG_OBJECT (subparse, "cleaning up subtitle parser");
120
121   if (subparse->encoding) {
122     g_free (subparse->encoding);
123     subparse->encoding = NULL;
124   }
125
126   if (subparse->detected_encoding) {
127     g_free (subparse->detected_encoding);
128     subparse->detected_encoding = NULL;
129   }
130
131   if (subparse->adapter) {
132     g_object_unref (subparse->adapter);
133     subparse->adapter = NULL;
134   }
135
136   if (subparse->textbuf) {
137     g_string_free (subparse->textbuf, TRUE);
138     subparse->textbuf = NULL;
139   }
140 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
141   g_free (subparse->state.current_language);
142   subparse->state.current_language = NULL;
143
144   g_mutex_clear (&subparse->seek_lock);
145 #endif
146   GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
147 }
148
149 static void
150 gst_sub_parse_class_init (GstSubParseClass * klass)
151 {
152   GObjectClass *object_class = G_OBJECT_CLASS (klass);
153   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
154
155   object_class->dispose = gst_sub_parse_dispose;
156   object_class->set_property = gst_sub_parse_set_property;
157   object_class->get_property = gst_sub_parse_get_property;
158
159   gst_element_class_add_static_pad_template (element_class, &sink_templ);
160   gst_element_class_add_static_pad_template (element_class, &src_templ);
161   gst_element_class_set_static_metadata (element_class,
162       "Subtitle parser", "Codec/Parser/Subtitle",
163       "Parses subtitle (.sub) files into text streams",
164       "Gustavo J. A. M. Carneiro <gjc@inescporto.pt>, "
165       "GStreamer maintainers <gstreamer-devel@lists.freedesktop.org>");
166
167   element_class->change_state = gst_sub_parse_change_state;
168
169   g_object_class_install_property (object_class, PROP_ENCODING,
170       g_param_spec_string ("subtitle-encoding", "subtitle charset encoding",
171           "Encoding to assume if input subtitles are not in UTF-8 or any other "
172           "Unicode encoding. If not set, the GST_SUBTITLE_ENCODING environment "
173           "variable will be checked for an encoding to use. If that is not set "
174           "either, ISO-8859-15 will be assumed.", DEFAULT_ENCODING,
175           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
176
177   g_object_class_install_property (object_class, PROP_VIDEOFPS,
178       gst_param_spec_fraction ("video-fps", "Video framerate",
179           "Framerate of the video stream. This is needed by some subtitle "
180           "formats to synchronize subtitles and video properly. If not set "
181           "and the subtitle format requires it subtitles may be out of sync.",
182           0, 1, G_MAXINT, 1, 24000, 1001,
183           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
184 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
185   g_object_class_install_property (object_class, PROP_EXTSUB_CURRENT_LANGUAGE,
186       g_param_spec_string ("current-language", "Current language",
187           "Current language of the subtitle in external subtitle case.",
188           DEFAULT_CURRENT_LANGUAGE,
189           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
190 #endif
191 }
192
193 static void
194 gst_sub_parse_init (GstSubParse * subparse)
195 {
196   subparse->sinkpad = gst_pad_new_from_static_template (&sink_templ, "sink");
197   gst_pad_set_chain_function (subparse->sinkpad,
198       GST_DEBUG_FUNCPTR (gst_sub_parse_chain));
199   gst_pad_set_event_function (subparse->sinkpad,
200       GST_DEBUG_FUNCPTR (gst_sub_parse_sink_event));
201   gst_element_add_pad (GST_ELEMENT (subparse), subparse->sinkpad);
202
203   subparse->srcpad = gst_pad_new_from_static_template (&src_templ, "src");
204   gst_pad_set_event_function (subparse->srcpad,
205       GST_DEBUG_FUNCPTR (gst_sub_parse_src_event));
206   gst_pad_set_query_function (subparse->srcpad,
207       GST_DEBUG_FUNCPTR (gst_sub_parse_src_query));
208   gst_element_add_pad (GST_ELEMENT (subparse), subparse->srcpad);
209
210   subparse->textbuf = g_string_new (NULL);
211   subparse->parser_type = GST_SUB_PARSE_FORMAT_UNKNOWN;
212 #ifdef TIZEN_FEATURE_UPSTREAM
213   subparse->strip_pango_markup = FALSE;
214 #endif
215   subparse->flushing = FALSE;
216   gst_segment_init (&subparse->segment, GST_FORMAT_TIME);
217   subparse->need_segment = TRUE;
218   subparse->encoding = g_strdup (DEFAULT_ENCODING);
219   subparse->detected_encoding = NULL;
220   subparse->adapter = gst_adapter_new ();
221
222   subparse->fps_n = 24000;
223   subparse->fps_d = 1001;
224 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
225   subparse->state.language_list = NULL;
226   subparse->state.current_language = NULL;
227   subparse->state.langlist_msg_posted = FALSE;
228
229   g_mutex_init (&subparse->seek_lock);
230 #endif
231 }
232
233 /*
234  * Source pad functions.
235  */
236
237 static gboolean
238 gst_sub_parse_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
239 {
240   GstSubParse *self = GST_SUBPARSE (parent);
241   gboolean ret = FALSE;
242
243   GST_DEBUG ("Handling %s query", GST_QUERY_TYPE_NAME (query));
244
245   switch (GST_QUERY_TYPE (query)) {
246     case GST_QUERY_POSITION:{
247       GstFormat fmt;
248
249       gst_query_parse_position (query, &fmt, NULL);
250       if (fmt != GST_FORMAT_TIME) {
251         ret = gst_pad_peer_query (self->sinkpad, query);
252       } else {
253         ret = TRUE;
254         gst_query_set_position (query, GST_FORMAT_TIME, self->segment.position);
255       }
256       break;
257     }
258     case GST_QUERY_SEEKING:
259     {
260       GstFormat fmt;
261       gboolean seekable = FALSE;
262
263       ret = TRUE;
264
265       gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL);
266       if (fmt == GST_FORMAT_TIME) {
267         GstQuery *peerquery = gst_query_new_seeking (GST_FORMAT_BYTES);
268
269         seekable = gst_pad_peer_query (self->sinkpad, peerquery);
270         if (seekable)
271           gst_query_parse_seeking (peerquery, NULL, &seekable, NULL, NULL);
272         gst_query_unref (peerquery);
273       }
274
275       gst_query_set_seeking (query, fmt, seekable, seekable ? 0 : -1, -1);
276       break;
277     }
278     default:
279       ret = gst_pad_query_default (pad, parent, query);
280       break;
281   }
282
283   return ret;
284 }
285
286 static gboolean
287 gst_sub_parse_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
288 {
289   GstSubParse *self = GST_SUBPARSE (parent);
290   gboolean ret = FALSE;
291
292   GST_DEBUG ("Handling %s event", GST_EVENT_TYPE_NAME (event));
293
294   switch (GST_EVENT_TYPE (event)) {
295     case GST_EVENT_SEEK:
296     {
297       GstFormat format;
298       GstSeekFlags flags;
299       GstSeekType start_type, stop_type;
300       gint64 start, stop;
301       gdouble rate;
302       gboolean update;
303
304       gst_event_parse_seek (event, &rate, &format, &flags,
305           &start_type, &start, &stop_type, &stop);
306
307       if (format != GST_FORMAT_TIME) {
308         GST_WARNING_OBJECT (self, "we only support seeking in TIME format");
309         gst_event_unref (event);
310         goto beach;
311       }
312 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
313       if (!gst_sub_parse_check_byte_seekability (self)) {
314         ret = gst_pad_event_default (pad, parent, event);
315         break;
316       }
317
318       SUBPARSE_SEEK_LOCK (self);
319 #endif
320       /* Convert that seek to a seeking in bytes at position 0,
321          FIXME: could use an index */
322       ret = gst_pad_push_event (self->sinkpad,
323           gst_event_new_seek (rate, GST_FORMAT_BYTES, flags,
324               GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, 0));
325
326       if (ret) {
327         /* Apply the seek to our segment */
328         gst_segment_do_seek (&self->segment, rate, format, flags,
329             start_type, start, stop_type, stop, &update);
330
331         GST_DEBUG_OBJECT (self, "segment after seek: %" GST_SEGMENT_FORMAT,
332             &self->segment);
333
334         /* will mark need_segment when receiving segment from upstream,
335          * after FLUSH and all that has happened,
336          * rather than racing with chain */
337       } else {
338         GST_WARNING_OBJECT (self, "seek to 0 bytes failed");
339       }
340
341 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
342       SUBPARSE_SEEK_UNLOCK (self);
343 #endif
344
345       gst_event_unref (event);
346       break;
347     }
348     default:
349       ret = gst_pad_event_default (pad, parent, event);
350       break;
351   }
352
353 beach:
354   return ret;
355 }
356
357 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
358 static gboolean
359 gst_sub_parse_check_byte_seekability (GstSubParse * subparse)
360 {
361   GstQuery *query;
362   gboolean seekable = FALSE;
363
364   query = gst_query_new_seeking (GST_FORMAT_BYTES);
365
366   if (gst_pad_peer_query (subparse->sinkpad, query)) {
367     gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL);
368   } else {
369     GST_DEBUG_OBJECT (subparse, "seeking query failed");
370   }
371
372   gst_query_unref (query);
373
374   GST_INFO_OBJECT (subparse, "byte seekable: %d", seekable);
375
376   return seekable;
377 }
378 #endif
379
380 static void
381 gst_sub_parse_set_property (GObject * object, guint prop_id,
382     const GValue * value, GParamSpec * pspec)
383 {
384   GstSubParse *subparse = GST_SUBPARSE (object);
385
386   GST_OBJECT_LOCK (subparse);
387   switch (prop_id) {
388     case PROP_ENCODING:
389       g_free (subparse->encoding);
390       subparse->encoding = g_value_dup_string (value);
391       GST_LOG_OBJECT (object, "subtitle encoding set to %s",
392           GST_STR_NULL (subparse->encoding));
393       break;
394     case PROP_VIDEOFPS:
395     {
396       subparse->fps_n = gst_value_get_fraction_numerator (value);
397       subparse->fps_d = gst_value_get_fraction_denominator (value);
398       GST_DEBUG_OBJECT (object, "video framerate set to %d/%d", subparse->fps_n,
399           subparse->fps_d);
400
401       if (!subparse->state.have_internal_fps) {
402         subparse->state.fps_n = subparse->fps_n;
403         subparse->state.fps_d = subparse->fps_d;
404       }
405       break;
406     }
407 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
408     case PROP_EXTSUB_CURRENT_LANGUAGE:
409       g_free (subparse->state.current_language);
410       subparse->state.current_language = g_value_dup_string (value);
411       GST_LOG_OBJECT (subparse, "subtitle current language set to %s",
412           GST_STR_NULL (subparse->state.current_language));
413       sami_context_change_language (&subparse->state);
414       break;
415 #endif
416     default:
417       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
418       break;
419   }
420   GST_OBJECT_UNLOCK (subparse);
421 }
422
423 static void
424 gst_sub_parse_get_property (GObject * object, guint prop_id,
425     GValue * value, GParamSpec * pspec)
426 {
427   GstSubParse *subparse = GST_SUBPARSE (object);
428
429   GST_OBJECT_LOCK (subparse);
430   switch (prop_id) {
431     case PROP_ENCODING:
432       g_value_set_string (value, subparse->encoding);
433       break;
434     case PROP_VIDEOFPS:
435       gst_value_set_fraction (value, subparse->fps_n, subparse->fps_d);
436       break;
437 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
438     case PROP_EXTSUB_CURRENT_LANGUAGE:
439       g_value_set_string (value, subparse->state.current_language);
440       break;
441 #endif
442     default:
443       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
444       break;
445   }
446   GST_OBJECT_UNLOCK (subparse);
447 }
448
449 static const gchar *
450 gst_sub_parse_get_format_description (GstSubParseFormat format)
451 {
452   switch (format) {
453     case GST_SUB_PARSE_FORMAT_MDVDSUB:
454       return "MicroDVD";
455     case GST_SUB_PARSE_FORMAT_SUBRIP:
456       return "SubRip";
457     case GST_SUB_PARSE_FORMAT_MPSUB:
458       return "MPSub";
459     case GST_SUB_PARSE_FORMAT_SAMI:
460       return "SAMI";
461     case GST_SUB_PARSE_FORMAT_TMPLAYER:
462       return "TMPlayer";
463     case GST_SUB_PARSE_FORMAT_MPL2:
464       return "MPL2";
465     case GST_SUB_PARSE_FORMAT_SUBVIEWER:
466       return "SubViewer";
467     case GST_SUB_PARSE_FORMAT_DKS:
468       return "DKS";
469     case GST_SUB_PARSE_FORMAT_VTT:
470       return "WebVTT";
471     case GST_SUB_PARSE_FORMAT_QTTEXT:
472       return "QTtext";
473     case GST_SUB_PARSE_FORMAT_LRC:
474       return "LRC";
475     default:
476     case GST_SUB_PARSE_FORMAT_UNKNOWN:
477       break;
478   }
479   return NULL;
480 }
481
482 static gchar *
483 gst_convert_to_utf8 (const gchar * str, gsize len, const gchar * encoding,
484     gsize * consumed, GError ** err)
485 {
486   gchar *ret = NULL;
487
488   *consumed = 0;
489   /* The char cast is necessary in glib < 2.24 */
490   ret =
491       g_convert_with_fallback (str, len, "UTF-8", encoding, (char *) "*",
492       consumed, NULL, err);
493   if (ret == NULL)
494     return ret;
495
496   /* + 3 to skip UTF-8 BOM if it was added */
497   len = strlen (ret);
498   if (len >= 3 && (guint8) ret[0] == 0xEF && (guint8) ret[1] == 0xBB
499       && (guint8) ret[2] == 0xBF)
500     memmove (ret, ret + 3, len + 1 - 3);
501
502   return ret;
503 }
504
505 static gchar *
506 detect_encoding (const gchar * str, gsize len)
507 {
508   if (len >= 3 && (guint8) str[0] == 0xEF && (guint8) str[1] == 0xBB
509       && (guint8) str[2] == 0xBF)
510     return g_strdup ("UTF-8");
511
512   if (len >= 2 && (guint8) str[0] == 0xFE && (guint8) str[1] == 0xFF)
513     return g_strdup ("UTF-16BE");
514
515   if (len >= 2 && (guint8) str[0] == 0xFF && (guint8) str[1] == 0xFE)
516     return g_strdup ("UTF-16LE");
517
518   if (len >= 4 && (guint8) str[0] == 0x00 && (guint8) str[1] == 0x00
519       && (guint8) str[2] == 0xFE && (guint8) str[3] == 0xFF)
520     return g_strdup ("UTF-32BE");
521
522   if (len >= 4 && (guint8) str[0] == 0xFF && (guint8) str[1] == 0xFE
523       && (guint8) str[2] == 0x00 && (guint8) str[3] == 0x00)
524     return g_strdup ("UTF-32LE");
525
526   return NULL;
527 }
528
529 static gchar *
530 convert_encoding (GstSubParse * self, const gchar * str, gsize len,
531     gsize * consumed)
532 {
533   const gchar *encoding;
534   GError *err = NULL;
535   gchar *ret = NULL;
536
537   *consumed = 0;
538
539   /* First try any detected encoding */
540   if (self->detected_encoding) {
541     ret =
542         gst_convert_to_utf8 (str, len, self->detected_encoding, consumed, &err);
543
544     if (!err)
545       return ret;
546
547     GST_WARNING_OBJECT (self, "could not convert string from '%s' to UTF-8: %s",
548         self->detected_encoding, err->message);
549     g_free (self->detected_encoding);
550     self->detected_encoding = NULL;
551     g_clear_error (&err);
552   }
553
554   /* Otherwise check if it's UTF8 */
555   if (self->valid_utf8) {
556     if (g_utf8_validate (str, len, NULL)) {
557       GST_LOG_OBJECT (self, "valid UTF-8, no conversion needed");
558       *consumed = len;
559       return g_strndup (str, len);
560     }
561     GST_INFO_OBJECT (self, "invalid UTF-8!");
562     self->valid_utf8 = FALSE;
563   }
564
565   /* Else try fallback */
566   encoding = self->encoding;
567   if (encoding == NULL || *encoding == '\0') {
568     encoding = g_getenv ("GST_SUBTITLE_ENCODING");
569   }
570   if (encoding == NULL || *encoding == '\0') {
571     /* if local encoding is UTF-8 and no encoding specified
572      * via the environment variable, assume ISO-8859-15 */
573     if (g_get_charset (&encoding)) {
574       encoding = "ISO-8859-15";
575     }
576   }
577
578   ret = gst_convert_to_utf8 (str, len, encoding, consumed, &err);
579
580   if (err) {
581     GST_WARNING_OBJECT (self, "could not convert string from '%s' to UTF-8: %s",
582         encoding, err->message);
583     g_clear_error (&err);
584
585 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
586     if (!g_strcmp0 (self->encoding, "EUC-KR")) {
587       GST_LOG_OBJECT (self, "use CP949 as fallback");
588       g_free (self->encoding);
589       self->encoding = g_strdup ("CP949");
590       encoding = self->encoding;
591       ret = gst_convert_to_utf8 (str, len, encoding, consumed, &err);
592     } else {
593 #endif
594       /* invalid input encoding, fall back to ISO-8859-15 (always succeeds) */
595       GST_LOG_OBJECT (self, "use ISO-8859-15 as fallback");
596       ret = gst_convert_to_utf8 (str, len, "ISO-8859-15", consumed, NULL);
597 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
598     }
599 #endif
600   }
601
602   GST_LOG_OBJECT (self,
603       "successfully converted %" G_GSIZE_FORMAT " characters from %s to UTF-8",
604       len, encoding);
605
606   return ret;
607 }
608
609 static gchar *
610 get_next_line (GstSubParse * self)
611 {
612   char *line = NULL;
613   const char *line_end;
614   int line_len;
615   gboolean have_r = FALSE;
616
617   line_end = strchr (self->textbuf->str, '\n');
618
619   if (!line_end) {
620     /* end-of-line not found; return for more data */
621     return NULL;
622   }
623
624   /* get rid of '\r' */
625   if (line_end != self->textbuf->str && *(line_end - 1) == '\r') {
626     line_end--;
627     have_r = TRUE;
628   }
629
630   line_len = line_end - self->textbuf->str;
631   line = g_strndup (self->textbuf->str, line_len);
632   self->textbuf = g_string_erase (self->textbuf, 0,
633       line_len + (have_r ? 2 : 1));
634   return line;
635 }
636
637 static gchar *
638 parse_mdvdsub (ParserState * state, const gchar * line)
639 {
640   const gchar *line_split;
641   gchar *line_chunk;
642   guint start_frame, end_frame;
643   guint64 clip_start = 0, clip_stop = 0;
644   gboolean in_seg = FALSE;
645   GString *markup;
646   gchar *ret;
647
648   /* style variables */
649   gboolean italic;
650   gboolean bold;
651   guint fontsize;
652   gdouble fps = 0.0;
653
654   if (sscanf (line, "{%u}{%u}", &start_frame, &end_frame) != 2) {
655     g_warning ("Parse of the following line, assumed to be in microdvd .sub"
656         " format, failed:\n%s", line);
657     return NULL;
658   }
659
660   /* skip the {%u}{%u} part */
661   line = strchr (line, '}') + 1;
662   line = strchr (line, '}') + 1;
663
664   /* see if there's a first line with a framerate */
665   if (start_frame == 1 && end_frame == 1) {
666     gchar *rest, *end = NULL;
667
668     rest = g_strdup (line);
669     g_strdelimit (rest, ",", '.');
670     fps = g_ascii_strtod (rest, &end);
671     if (end != rest) {
672       gst_util_double_to_fraction (fps, &state->fps_n, &state->fps_d);
673       GST_INFO ("framerate from file: %d/%d ('%s')", state->fps_n,
674           state->fps_d, rest);
675     }
676     g_free (rest);
677     return NULL;
678   }
679
680   state->start_time =
681       gst_util_uint64_scale (start_frame, GST_SECOND * state->fps_d,
682       state->fps_n);
683   state->duration =
684       gst_util_uint64_scale (end_frame - start_frame, GST_SECOND * state->fps_d,
685       state->fps_n);
686
687   /* Check our segment start/stop */
688   in_seg = gst_segment_clip (state->segment, GST_FORMAT_TIME,
689       state->start_time, state->start_time + state->duration, &clip_start,
690       &clip_stop);
691
692   /* No need to parse that text if it's out of segment */
693   if (in_seg) {
694     state->start_time = clip_start;
695     state->duration = clip_stop - clip_start;
696   } else {
697     return NULL;
698   }
699
700   markup = g_string_new (NULL);
701   while (1) {
702     italic = FALSE;
703     bold = FALSE;
704     fontsize = 0;
705     /* parse style markup */
706     if (strncmp (line, "{y:i}", 5) == 0) {
707       italic = TRUE;
708       line = strchr (line, '}') + 1;
709     }
710     if (strncmp (line, "{y:b}", 5) == 0) {
711       bold = TRUE;
712       line = strchr (line, '}') + 1;
713     }
714     if (sscanf (line, "{s:%u}", &fontsize) == 1) {
715       line = strchr (line, '}') + 1;
716     }
717     /* forward slashes at beginning/end signify italics too */
718     if (g_str_has_prefix (line, "/")) {
719       italic = TRUE;
720       ++line;
721     }
722     if ((line_split = strchr (line, '|')))
723       line_chunk = g_markup_escape_text (line, line_split - line);
724     else
725       line_chunk = g_markup_escape_text (line, strlen (line));
726
727     /* Remove italics markers at end of line/stanza (CHECKME: are end slashes
728      * always at the end of a line or can they span multiple lines?) */
729     if (g_str_has_suffix (line_chunk, "/")) {
730       line_chunk[strlen (line_chunk) - 1] = '\0';
731     }
732
733     markup = g_string_append (markup, "<span");
734     if (italic)
735       g_string_append (markup, " style=\"italic\"");
736     if (bold)
737       g_string_append (markup, " weight=\"bold\"");
738     if (fontsize)
739       g_string_append_printf (markup, " size=\"%u\"", fontsize * 1000);
740     g_string_append_printf (markup, ">%s</span>", line_chunk);
741     g_free (line_chunk);
742     if (line_split) {
743       g_string_append (markup, "\n");
744       line = line_split + 1;
745     } else {
746       break;
747     }
748   }
749   ret = markup->str;
750   g_string_free (markup, FALSE);
751   GST_DEBUG ("parse_mdvdsub returning (%f+%f): %s",
752       state->start_time / (double) GST_SECOND,
753       state->duration / (double) GST_SECOND, ret);
754   return ret;
755 }
756
757 static void
758 strip_trailing_newlines (gchar * txt)
759 {
760   if (txt) {
761     guint len;
762
763     len = strlen (txt);
764     while (len > 1 && txt[len - 1] == '\n') {
765       txt[len - 1] = '\0';
766       --len;
767     }
768   }
769 }
770
771 /* we want to escape text in general, but retain basic markup like
772  * <i></i>, <u></u>, and <b></b>. The easiest and safest way is to
773  * just unescape a white list of allowed markups again after
774  * escaping everything (the text between these simple markers isn't
775  * necessarily escaped, so it seems best to do it like this) */
776 static void
777 subrip_unescape_formatting (gchar * txt, gconstpointer allowed_tags_ptr,
778     gboolean allows_tag_attributes)
779 {
780   gchar *res;
781   GRegex *tag_regex;
782   gchar *allowed_tags_pattern, *search_pattern;
783   const gchar *replace_pattern;
784
785   /* No processing needed if no escaped tag marker found in the string. */
786   if (strstr (txt, "&lt;") == NULL)
787     return;
788
789   /* Build a list of alternates for our regexp.
790    * FIXME: Could be built once and stored */
791   allowed_tags_pattern = g_strjoinv ("|", (gchar **) allowed_tags_ptr);
792   /* Look for starting/ending escaped tags with optional attributes. */
793   search_pattern = g_strdup_printf ("&lt;(/)?\\ *(%s)(%s)&gt;",
794       allowed_tags_pattern, ATTRIBUTE_REGEX);
795   /* And unescape appropriately */
796   if (allows_tag_attributes) {
797     replace_pattern = "<\\1\\2\\3>";
798   } else {
799     replace_pattern = "<\\1\\2>";
800   }
801
802   tag_regex = g_regex_new (search_pattern, 0, 0, NULL);
803   res = g_regex_replace (tag_regex, txt, strlen (txt), 0,
804       replace_pattern, 0, NULL);
805
806   /* res will always be shorter than the input or identical, so this
807    * copy is OK */
808   strcpy (txt, res);
809
810   g_free (res);
811   g_free (search_pattern);
812   g_free (allowed_tags_pattern);
813
814   g_regex_unref (tag_regex);
815 }
816
817
818 static gboolean
819 subrip_remove_unhandled_tag (gchar * start, gchar * stop)
820 {
821   gchar *tag, saved;
822
823   tag = start + strlen ("&lt;");
824   if (*tag == '/')
825     ++tag;
826
827   if (g_ascii_tolower (*tag) < 'a' || g_ascii_tolower (*tag) > 'z')
828     return FALSE;
829
830   saved = *stop;
831   *stop = '\0';
832   GST_LOG ("removing unhandled tag '%s'", start);
833   *stop = saved;
834   memmove (start, stop, strlen (stop) + 1);
835   return TRUE;
836 }
837
838 /* remove tags we haven't explicitly allowed earlier on, like font tags
839  * for example */
840 static void
841 subrip_remove_unhandled_tags (gchar * txt)
842 {
843   gchar *pos, *gt;
844
845   for (pos = txt; pos != NULL && *pos != '\0'; ++pos) {
846     if (strncmp (pos, "&lt;", 4) == 0 && (gt = strstr (pos + 4, "&gt;"))) {
847       if (subrip_remove_unhandled_tag (pos, gt + strlen ("&gt;")))
848         --pos;
849     }
850   }
851 }
852
853 /* we only allow a fixed set of tags like <i>, <u> and <b>, so let's
854  * take a simple approach. This code assumes the input has been
855  * escaped and subrip_unescape_formatting() has then been run over the
856  * input! This function adds missing closing markup tags and removes
857  * broken closing tags for tags that have never been opened. */
858 static void
859 subrip_fix_up_markup (gchar ** p_txt, gconstpointer allowed_tags_ptr)
860 {
861   gchar *cur, *next_tag;
862   GPtrArray *open_tags = NULL;
863   guint num_open_tags = 0;
864   const gchar *iter_tag;
865   guint offset = 0;
866   guint index;
867   gchar *cur_tag;
868   gchar *end_tag;
869   GRegex *tag_regex;
870   GMatchInfo *match_info;
871   gchar **allowed_tags = (gchar **) allowed_tags_ptr;
872
873   g_assert (*p_txt != NULL);
874
875   open_tags = g_ptr_array_new_with_free_func (g_free);
876   cur = *p_txt;
877   while (*cur != '\0') {
878     next_tag = strchr (cur, '<');
879     if (next_tag == NULL)
880       break;
881     offset = 0;
882     index = 0;
883     while (index < g_strv_length (allowed_tags)) {
884       iter_tag = allowed_tags[index];
885       /* Look for a white listed tag */
886       cur_tag = g_strconcat ("<", iter_tag, ATTRIBUTE_REGEX, ">", NULL);
887       tag_regex = g_regex_new (cur_tag, 0, 0, NULL);
888       (void) g_regex_match (tag_regex, next_tag, 0, &match_info);
889
890       if (g_match_info_matches (match_info)) {
891         gint start_pos, end_pos;
892         gchar *word = g_match_info_fetch (match_info, 0);
893         g_match_info_fetch_pos (match_info, 0, &start_pos, &end_pos);
894         if (start_pos == 0) {
895           offset = strlen (word);
896         }
897         g_free (word);
898       }
899       g_match_info_free (match_info);
900       g_regex_unref (tag_regex);
901       g_free (cur_tag);
902       index++;
903       if (offset) {
904         /* OK we found a tag, let's keep track of it */
905         g_ptr_array_add (open_tags, g_ascii_strdown (iter_tag, -1));
906         ++num_open_tags;
907         break;
908       }
909     }
910
911     if (offset) {
912       next_tag += offset;
913       cur = next_tag;
914       continue;
915     }
916
917     if (*next_tag == '<' && *(next_tag + 1) == '/') {
918       end_tag = strchr (cur, '>');
919       if (end_tag) {
920         const gchar *last = NULL;
921         if (num_open_tags > 0)
922           last = g_ptr_array_index (open_tags, num_open_tags - 1);
923         if (num_open_tags == 0
924             || g_ascii_strncasecmp (end_tag - 1, last, strlen (last))) {
925           GST_LOG ("broken input, closing tag '%s' is not open", end_tag - 1);
926           memmove (next_tag, end_tag + 1, strlen (end_tag) + 1);
927           next_tag -= strlen (end_tag);
928         } else {
929           --num_open_tags;
930           g_ptr_array_remove_index (open_tags, num_open_tags);
931         }
932       }
933     }
934     ++next_tag;
935     cur = next_tag;
936   }
937
938   if (num_open_tags > 0) {
939     GString *s;
940
941     s = g_string_new (*p_txt);
942     while (num_open_tags > 0) {
943       GST_LOG ("adding missing closing tag '%s'",
944           (char *) g_ptr_array_index (open_tags, num_open_tags - 1));
945       g_string_append_c (s, '<');
946       g_string_append_c (s, '/');
947       g_string_append (s, g_ptr_array_index (open_tags, num_open_tags - 1));
948       g_string_append_c (s, '>');
949       --num_open_tags;
950     }
951     g_free (*p_txt);
952     *p_txt = g_string_free (s, FALSE);
953   }
954   g_ptr_array_free (open_tags, TRUE);
955 }
956
957 static gboolean
958 parse_subrip_time (const gchar * ts_string, GstClockTime * t)
959 {
960   gchar s[128] = { '\0', };
961   gchar *end, *p;
962   guint hour, min, sec, msec, len;
963
964   while (*ts_string == ' ')
965     ++ts_string;
966
967   g_strlcpy (s, ts_string, sizeof (s));
968   if ((end = strstr (s, "-->")))
969     *end = '\0';
970   g_strchomp (s);
971
972   /* ms may be in these formats:
973    * hh:mm:ss,500 = 500ms
974    * hh:mm:ss,  5 =   5ms
975    * hh:mm:ss, 5  =  50ms
976    * hh:mm:ss, 50 =  50ms
977    * hh:mm:ss,5   = 500ms
978    * and the same with . instead of ,.
979    * sscanf() doesn't differentiate between '  5' and '5' so munge
980    * the white spaces within the timestamp to '0' (I'm sure there's a
981    * way to make sscanf() do this for us, but how?)
982    */
983   g_strdelimit (s, " ", '0');
984   g_strdelimit (s, ".", ',');
985
986   /* make sure we have exactly three digits after he comma */
987   p = strchr (s, ',');
988   if (p == NULL) {
989     /* If there isn't a ',' the timestamp is broken */
990     /* https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/issues/532#note_100179 */
991     GST_WARNING ("failed to parse subrip timestamp string '%s'", s);
992     return FALSE;
993   }
994
995   ++p;
996   len = strlen (p);
997   if (len > 3) {
998     p[3] = '\0';
999   } else
1000     while (len < 3) {
1001       g_strlcat (&p[len], "0", 2);
1002       ++len;
1003     }
1004
1005   GST_LOG ("parsing timestamp '%s'", s);
1006   if (sscanf (s, "%u:%u:%u,%u", &hour, &min, &sec, &msec) != 4) {
1007 #ifdef TIZEN_FEATURE_UPSTREAM
1008     /* https://www.w3.org/TR/webvtt1/#webvtt-timestamp
1009      *
1010      * The hours component is optional with webVTT, for example
1011      * mm:ss,500 is a valid webVTT timestamp. When not present,
1012      * hours is 0.
1013      */
1014     hour = 0;
1015
1016     if (sscanf (s, "%u:%u,%u", &min, &sec, &msec) != 3) {
1017       GST_WARNING ("failed to parse subrip timestamp string '%s'", s);
1018       return FALSE;
1019     }
1020 #else
1021     GST_WARNING ("failed to parse subrip timestamp string '%s'", s);
1022     return FALSE;
1023 #endif
1024   }
1025
1026   *t = ((hour * 3600) + (min * 60) + sec) * GST_SECOND + msec * GST_MSECOND;
1027   return TRUE;
1028 }
1029
1030 /* cue settings are part of the WebVTT specification. They are
1031  * declared after the time interval in the first line of the
1032  * cue. Example: 00:00:01,000 --> 00:00:02,000 D:vertical-lr A:start
1033  * See also http://www.whatwg.org/specs/web-apps/current-work/webvtt.html
1034  */
1035 static void
1036 parse_webvtt_cue_settings (ParserState * state, const gchar * settings)
1037 {
1038   gchar **splitted_settings = g_strsplit_set (settings, " \t", -1);
1039   gint i = 0;
1040   gint16 text_position, text_size;
1041   gint16 line_position;
1042   gboolean vertical_found = FALSE;
1043   gboolean alignment_found = FALSE;
1044
1045   while (i < g_strv_length (splitted_settings)) {
1046     gboolean valid_tag = FALSE;
1047     switch (splitted_settings[i][0]) {
1048       case 'T':
1049         if (sscanf (splitted_settings[i], "T:%" G_GINT16_FORMAT "%%",
1050                 &text_position) > 0) {
1051           state->text_position = (guint8) text_position;
1052           valid_tag = TRUE;
1053         }
1054         break;
1055       case 'D':
1056         if (strlen (splitted_settings[i]) > 2) {
1057           vertical_found = TRUE;
1058           g_free (state->vertical);
1059           state->vertical = g_strdup (splitted_settings[i] + 2);
1060           valid_tag = TRUE;
1061         }
1062         break;
1063       case 'L':
1064         if (g_str_has_suffix (splitted_settings[i], "%")) {
1065           if (sscanf (splitted_settings[i], "L:%" G_GINT16_FORMAT "%%",
1066                   &line_position) > 0) {
1067             state->line_position = line_position;
1068             valid_tag = TRUE;
1069           }
1070         } else {
1071           if (sscanf (splitted_settings[i], "L:%" G_GINT16_FORMAT,
1072                   &line_position) > 0) {
1073             state->line_number = line_position;
1074             valid_tag = TRUE;
1075           }
1076         }
1077         break;
1078       case 'S':
1079         if (sscanf (splitted_settings[i], "S:%" G_GINT16_FORMAT "%%",
1080                 &text_size) > 0) {
1081           state->text_size = (guint8) text_size;
1082           valid_tag = TRUE;
1083         }
1084         break;
1085       case 'A':
1086         if (strlen (splitted_settings[i]) > 2) {
1087           g_free (state->alignment);
1088           state->alignment = g_strdup (splitted_settings[i] + 2);
1089           alignment_found = TRUE;
1090           valid_tag = TRUE;
1091         }
1092         break;
1093       default:
1094         break;
1095     }
1096     if (!valid_tag) {
1097       GST_LOG ("Invalid or unrecognised setting found: %s",
1098           splitted_settings[i]);
1099     }
1100     i++;
1101   }
1102   g_strfreev (splitted_settings);
1103   if (!vertical_found) {
1104     g_free (state->vertical);
1105     state->vertical = g_strdup ("");
1106   }
1107   if (!alignment_found) {
1108     g_free (state->alignment);
1109     state->alignment = g_strdup ("");
1110   }
1111 }
1112
1113 #ifdef TIZEN_FEATURE_HLS_WEBVTT
1114 static void
1115 parse_timestamp_map (ParserState * state, const gchar * timestamp_map)
1116 {
1117   GstClockTime local = 0;
1118   guint64 mpegts = 0;
1119   gchar *local_start = NULL;
1120   gchar *mpegts_start = NULL;
1121
1122   if (!timestamp_map)
1123     return;
1124
1125   local_start = g_strrstr (timestamp_map, "LOCAL:");
1126   if (local_start)
1127     parse_subrip_time (local_start + strlen ("LOCAL:"), &local);
1128
1129   mpegts_start = g_strrstr (timestamp_map, "MPEGTS:");
1130   if (mpegts_start)
1131     mpegts = g_ascii_strtoull (mpegts_start + strlen ("MPEGTS:"), NULL, 10);
1132
1133   GST_LOG ("parsed local time %" GST_TIME_FORMAT " MPEGTS: %" G_GUINT64_FORMAT,
1134       GST_TIME_ARGS (local), mpegts);
1135
1136   state->local = local;
1137   state->mpegts = mpegts;
1138 }
1139
1140 static void
1141 send_fragment_timestamp_event (GstSubParse * self, GstClockTime timestamp)
1142 {
1143   GstEvent *event = NULL;
1144
1145   if (!GST_CLOCK_TIME_IS_VALID (timestamp))
1146     return;
1147
1148   GST_LOG ("send fragment_timestamp %" GST_TIME_FORMAT,
1149       GST_TIME_ARGS (timestamp));
1150
1151   event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM,
1152       gst_structure_new ("fragment_timestamp",
1153           "timestamp", G_TYPE_UINT64, timestamp, NULL));
1154
1155   gst_pad_push_event (self->srcpad, event);
1156 }
1157 #endif
1158
1159 static gchar *
1160 parse_subrip (ParserState * state, const gchar * line)
1161 {
1162   gchar *ret;
1163
1164   switch (state->state) {
1165     case 0:{
1166       char *endptr;
1167       guint64 id;
1168
1169       /* looking for a single integer as a Cue ID, but we
1170        * don't actually use it */
1171       errno = 0;
1172       id = g_ascii_strtoull (line, &endptr, 10);
1173       if (id == G_MAXUINT64 && errno == ERANGE)
1174         state->state = 1;
1175       else if (id == 0 && errno == EINVAL)
1176         state->state = 1;
1177       else if (endptr != line && *endptr == '\0')
1178         state->state = 1;
1179       return NULL;
1180     }
1181     case 1:
1182     {
1183       GstClockTime ts_start, ts_end;
1184       gchar *end_time;
1185
1186       /* looking for start_time --> end_time */
1187       if ((end_time = strstr (line, " --> ")) &&
1188           parse_subrip_time (line, &ts_start) &&
1189           parse_subrip_time (end_time + strlen (" --> "), &ts_end) &&
1190           state->start_time <= ts_end) {
1191         state->state = 2;
1192         state->start_time = ts_start;
1193         state->duration = ts_end - ts_start;
1194       } else {
1195         GST_DEBUG ("error parsing subrip time line '%s'", line);
1196         state->state = 0;
1197       }
1198       return NULL;
1199     }
1200     case 2:
1201     {
1202       /* No need to parse that text if it's out of segment */
1203       guint64 clip_start = 0, clip_stop = 0;
1204       gboolean in_seg = FALSE;
1205
1206       /* Check our segment start/stop */
1207       in_seg = gst_segment_clip (state->segment, GST_FORMAT_TIME,
1208           state->start_time, state->start_time + state->duration,
1209           &clip_start, &clip_stop);
1210
1211       if (in_seg) {
1212         state->start_time = clip_start;
1213         state->duration = clip_stop - clip_start;
1214       } else {
1215         state->state = 0;
1216         return NULL;
1217       }
1218     }
1219       /* looking for subtitle text; empty line ends this subtitle entry */
1220       if (state->buf->len)
1221         g_string_append_c (state->buf, '\n');
1222       g_string_append (state->buf, line);
1223       if (strlen (line) == 0) {
1224         ret = g_markup_escape_text (state->buf->str, state->buf->len);
1225         g_string_truncate (state->buf, 0);
1226         state->state = 0;
1227         subrip_unescape_formatting (ret, state->allowed_tags,
1228             state->allows_tag_attributes);
1229         subrip_remove_unhandled_tags (ret);
1230         strip_trailing_newlines (ret);
1231         subrip_fix_up_markup (&ret, state->allowed_tags);
1232         return ret;
1233       }
1234       return NULL;
1235     default:
1236       g_return_val_if_reached (NULL);
1237   }
1238 }
1239
1240 static gchar *
1241 parse_lrc (ParserState * state, const gchar * line)
1242 {
1243   gint m, s, c;
1244   const gchar *start;
1245   gint milli;
1246
1247   if (line[0] != '[')
1248     return NULL;
1249
1250   if (sscanf (line, "[%u:%02u.%03u]", &m, &s, &c) != 3 &&
1251       sscanf (line, "[%u:%02u.%02u]", &m, &s, &c) != 3)
1252     return NULL;
1253
1254   start = strchr (line, ']');
1255   if (start - line == 9)
1256     milli = 10;
1257   else
1258     milli = 1;
1259
1260   state->start_time = gst_util_uint64_scale (m, 60 * GST_SECOND, 1)
1261       + gst_util_uint64_scale (s, GST_SECOND, 1)
1262       + gst_util_uint64_scale (c, milli * GST_MSECOND, 1);
1263   state->duration = GST_CLOCK_TIME_NONE;
1264
1265   return g_strdup (start + 1);
1266 }
1267
1268 /* WebVTT is a new subtitle format for the upcoming HTML5 video track
1269  * element. This format is similar to Subrip, the biggest differences
1270  * are that there can be cue settings detailing how to display the cue
1271  * text and more markup tags are allowed.
1272  * See also http://www.whatwg.org/specs/web-apps/current-work/webvtt.html
1273  */
1274 static gchar *
1275 parse_webvtt (ParserState * state, const gchar * line)
1276 {
1277   /* Cue IDs are optional in WebVTT, but not in subrip,
1278    * so when in state 0 (cue ID), also check if we're
1279    * already at the start --> end time marker */
1280   if (state->state == 0 || state->state == 1) {
1281     GstClockTime ts_start, ts_end;
1282     gchar *end_time;
1283     gchar *cue_settings = NULL;
1284
1285     /* looking for start_time --> end_time */
1286     if ((end_time = strstr (line, " --> ")) &&
1287         parse_subrip_time (line, &ts_start) &&
1288         parse_subrip_time (end_time + strlen (" --> "), &ts_end) &&
1289         state->start_time <= ts_end) {
1290       state->state = 2;
1291       state->start_time = ts_start;
1292 #ifdef TIZEN_FEATURE_HLS_WEBVTT
1293       state->start_time += MPEGTIME_TO_GSTTIME (state->mpegts) - state->local;
1294 #endif
1295       state->duration = ts_end - ts_start;
1296       cue_settings = strstr (end_time + strlen (" --> "), " ");
1297 #ifdef TIZEN_FEATURE_HLS_WEBVTT
1298     } else if (strstr (line, "X-TIMESTAMP-MAP")) {
1299       GST_DEBUG ("got X-TIMESTAMP-MAP '%s'", line);
1300       parse_timestamp_map (state, line);
1301       state->state = 0;
1302 #endif
1303     } else {
1304       GST_DEBUG ("error parsing subrip time line '%s'", line);
1305       state->state = 0;
1306     }
1307
1308     state->text_position = 0;
1309     state->text_size = 0;
1310     state->line_position = 0;
1311     state->line_number = 0;
1312
1313     if (cue_settings)
1314       parse_webvtt_cue_settings (state, cue_settings + 1);
1315     else {
1316       g_free (state->vertical);
1317       state->vertical = g_strdup ("");
1318       g_free (state->alignment);
1319       state->alignment = g_strdup ("");
1320     }
1321
1322     return NULL;
1323   } else
1324     return parse_subrip (state, line);
1325 }
1326
1327 static void
1328 unescape_newlines_br (gchar * read)
1329 {
1330   gchar *write = read;
1331
1332   /* Replace all occurences of '[br]' with a newline as version 2
1333    * of the subviewer format uses this for newlines */
1334
1335   if (read[0] == '\0' || read[1] == '\0' || read[2] == '\0' || read[3] == '\0')
1336     return;
1337
1338   do {
1339     if (strncmp (read, "[br]", 4) == 0) {
1340       *write = '\n';
1341       read += 4;
1342     } else {
1343       *write = *read;
1344       read++;
1345     }
1346     write++;
1347   } while (*read);
1348
1349   *write = '\0';
1350 }
1351
1352 static gchar *
1353 parse_subviewer (ParserState * state, const gchar * line)
1354 {
1355   guint h1, m1, s1, ms1;
1356   guint h2, m2, s2, ms2;
1357   gchar *ret;
1358
1359   /* TODO: Maybe also parse the fields in the header, especially DELAY.
1360    * For examples see the unit test or
1361    * http://www.doom9.org/index.html?/sub.htm */
1362
1363   switch (state->state) {
1364     case 0:
1365       /* looking for start_time,end_time */
1366       if (sscanf (line, "%u:%u:%u.%u,%u:%u:%u.%u",
1367               &h1, &m1, &s1, &ms1, &h2, &m2, &s2, &ms2) == 8) {
1368         state->state = 1;
1369         state->start_time =
1370             (((guint64) h1) * 3600 + m1 * 60 + s1) * GST_SECOND +
1371             ms1 * GST_MSECOND;
1372         state->duration =
1373             (((guint64) h2) * 3600 + m2 * 60 + s2) * GST_SECOND +
1374             ms2 * GST_MSECOND - state->start_time;
1375       }
1376       return NULL;
1377     case 1:
1378     {
1379       /* No need to parse that text if it's out of segment */
1380       guint64 clip_start = 0, clip_stop = 0;
1381       gboolean in_seg = FALSE;
1382
1383       /* Check our segment start/stop */
1384       in_seg = gst_segment_clip (state->segment, GST_FORMAT_TIME,
1385           state->start_time, state->start_time + state->duration,
1386           &clip_start, &clip_stop);
1387
1388       if (in_seg) {
1389         state->start_time = clip_start;
1390         state->duration = clip_stop - clip_start;
1391       } else {
1392         state->state = 0;
1393         return NULL;
1394       }
1395     }
1396       /* looking for subtitle text; empty line ends this subtitle entry */
1397       if (state->buf->len)
1398         g_string_append_c (state->buf, '\n');
1399       g_string_append (state->buf, line);
1400       if (strlen (line) == 0) {
1401         ret = g_strdup (state->buf->str);
1402         unescape_newlines_br (ret);
1403         strip_trailing_newlines (ret);
1404         g_string_truncate (state->buf, 0);
1405         state->state = 0;
1406         return ret;
1407       }
1408       return NULL;
1409     default:
1410       g_assert_not_reached ();
1411       return NULL;
1412   }
1413 }
1414
1415 static gchar *
1416 parse_mpsub (ParserState * state, const gchar * line)
1417 {
1418   gchar *ret;
1419   float t1, t2;
1420
1421   switch (state->state) {
1422     case 0:
1423       /* looking for two floats (offset, duration) */
1424       if (sscanf (line, "%f %f", &t1, &t2) == 2) {
1425         state->state = 1;
1426         state->start_time += state->duration + GST_SECOND * t1;
1427         state->duration = GST_SECOND * t2;
1428       }
1429       return NULL;
1430     case 1:
1431     {                           /* No need to parse that text if it's out of segment */
1432       guint64 clip_start = 0, clip_stop = 0;
1433       gboolean in_seg = FALSE;
1434
1435       /* Check our segment start/stop */
1436       in_seg = gst_segment_clip (state->segment, GST_FORMAT_TIME,
1437           state->start_time, state->start_time + state->duration,
1438           &clip_start, &clip_stop);
1439
1440       if (in_seg) {
1441         state->start_time = clip_start;
1442         state->duration = clip_stop - clip_start;
1443       } else {
1444         state->state = 0;
1445         return NULL;
1446       }
1447     }
1448       /* looking for subtitle text; empty line ends this
1449        * subtitle entry */
1450       if (state->buf->len)
1451         g_string_append_c (state->buf, '\n');
1452       g_string_append (state->buf, line);
1453       if (strlen (line) == 0) {
1454         ret = g_strdup (state->buf->str);
1455         g_string_truncate (state->buf, 0);
1456         state->state = 0;
1457         return ret;
1458       }
1459       return NULL;
1460     default:
1461       g_assert_not_reached ();
1462       return NULL;
1463   }
1464 }
1465
1466 static const gchar *
1467 dks_skip_timestamp (const gchar * line)
1468 {
1469   while (*line && *line != ']')
1470     line++;
1471   if (*line == ']')
1472     line++;
1473   return line;
1474 }
1475
1476 static gchar *
1477 parse_dks (ParserState * state, const gchar * line)
1478 {
1479   guint h, m, s;
1480
1481   switch (state->state) {
1482     case 0:
1483       /* Looking for the start time and text */
1484       if (sscanf (line, "[%u:%u:%u]", &h, &m, &s) == 3) {
1485         const gchar *text;
1486         state->start_time = (((guint64) h) * 3600 + m * 60 + s) * GST_SECOND;
1487         text = dks_skip_timestamp (line);
1488         if (*text) {
1489           state->state = 1;
1490           g_string_append (state->buf, text);
1491         }
1492       }
1493       return NULL;
1494     case 1:
1495     {
1496       guint64 clip_start = 0, clip_stop = 0;
1497       gboolean in_seg;
1498       gchar *ret;
1499
1500       /* Looking for the end time */
1501       if (sscanf (line, "[%u:%u:%u]", &h, &m, &s) == 3) {
1502         state->state = 0;
1503         state->duration = (((guint64) h) * 3600 + m * 60 + s) * GST_SECOND -
1504             state->start_time;
1505       } else {
1506         GST_WARNING ("Failed to parse subtitle end time");
1507         return NULL;
1508       }
1509
1510       /* Check if this subtitle is out of the current segment */
1511       in_seg = gst_segment_clip (state->segment, GST_FORMAT_TIME,
1512           state->start_time, state->start_time + state->duration,
1513           &clip_start, &clip_stop);
1514
1515       if (!in_seg) {
1516         return NULL;
1517       }
1518
1519       state->start_time = clip_start;
1520       state->duration = clip_stop - clip_start;
1521
1522       ret = g_strdup (state->buf->str);
1523       g_string_truncate (state->buf, 0);
1524       unescape_newlines_br (ret);
1525       return ret;
1526     }
1527     default:
1528       g_assert_not_reached ();
1529       return NULL;
1530   }
1531 }
1532
1533 static void
1534 parser_state_init (ParserState * state)
1535 {
1536   GST_DEBUG ("initialising parser");
1537
1538   if (state->buf) {
1539     g_string_truncate (state->buf, 0);
1540   } else {
1541     state->buf = g_string_new (NULL);
1542   }
1543
1544   state->start_time = 0;
1545   state->duration = 0;
1546   state->max_duration = 0;      /* no limit */
1547   state->state = 0;
1548   state->segment = NULL;
1549 #ifdef TIZEN_FEATURE_HLS_WEBVTT
1550   state->local = 0;
1551   state->mpegts = 0;
1552 #endif
1553
1554 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
1555   state->language_list = NULL;
1556
1557   g_free (state->current_language);
1558   state->current_language = NULL;
1559
1560   state->langlist_msg_posted = FALSE;
1561 #endif
1562 }
1563
1564 static void
1565 parser_state_dispose (GstSubParse * self, ParserState * state)
1566 {
1567   if (state->buf) {
1568     g_string_free (state->buf, TRUE);
1569     state->buf = NULL;
1570   }
1571
1572   g_free (state->vertical);
1573   state->vertical = NULL;
1574   g_free (state->alignment);
1575   state->alignment = NULL;
1576
1577   if (state->user_data) {
1578     switch (self->parser_type) {
1579       case GST_SUB_PARSE_FORMAT_QTTEXT:
1580         qttext_context_deinit (state);
1581         break;
1582       case GST_SUB_PARSE_FORMAT_SAMI:
1583         sami_context_deinit (state);
1584         break;
1585       default:
1586         break;
1587     }
1588   }
1589   state->allowed_tags = NULL;
1590 }
1591
1592 /* regex type enum */
1593 typedef enum
1594 {
1595   GST_SUB_PARSE_REGEX_UNKNOWN = 0,
1596   GST_SUB_PARSE_REGEX_MDVDSUB = 1,
1597   GST_SUB_PARSE_REGEX_SUBRIP = 2,
1598   GST_SUB_PARSE_REGEX_DKS = 3,
1599   GST_SUB_PARSE_REGEX_VTT = 4,
1600 } GstSubParseRegex;
1601
1602 static gpointer
1603 gst_sub_parse_data_format_autodetect_regex_once (GstSubParseRegex regtype)
1604 {
1605   gpointer result = NULL;
1606   GError *gerr = NULL;
1607   switch (regtype) {
1608     case GST_SUB_PARSE_REGEX_MDVDSUB:
1609       result =
1610           (gpointer) g_regex_new ("^\\{[0-9]+\\}\\{[0-9]+\\}",
1611           G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, &gerr);
1612       if (result == NULL) {
1613         g_warning ("Compilation of mdvd regex failed: %s", gerr->message);
1614         g_clear_error (&gerr);
1615       }
1616       break;
1617     case GST_SUB_PARSE_REGEX_SUBRIP:
1618       result = (gpointer)
1619           g_regex_new ("^[\\s\\n]*[\\n]? {0,3}[ 0-9]{1,4}\\s*(\x0d)?\x0a"
1620           " ?[0-9]{1,2}: ?[0-9]{1,2}: ?[0-9]{1,2}[,.] {0,2}[0-9]{1,3}"
1621           " +--> +[0-9]{1,2}: ?[0-9]{1,2}: ?[0-9]{1,2}[,.] {0,2}[0-9]{1,2}",
1622           G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, &gerr);
1623       if (result == NULL) {
1624         g_warning ("Compilation of subrip regex failed: %s", gerr->message);
1625         g_clear_error (&gerr);
1626       }
1627       break;
1628     case GST_SUB_PARSE_REGEX_DKS:
1629       result = (gpointer) g_regex_new ("^\\[[0-9]+:[0-9]+:[0-9]+\\].*",
1630           G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, &gerr);
1631       if (result == NULL) {
1632         g_warning ("Compilation of dks regex failed: %s", gerr->message);
1633         g_clear_error (&gerr);
1634       }
1635       break;
1636     case GST_SUB_PARSE_REGEX_VTT:
1637       result = (gpointer)
1638           g_regex_new ("^(\\xef\\xbb\\xbf)?WEBVTT[\\xa\\xd\\x20\\x9]", 0, 0,
1639           &gerr);
1640       if (result == NULL) {
1641         g_warning ("Compilation of vtt regex failed: %s", gerr->message);
1642         g_error_free (gerr);
1643       }
1644       break;
1645
1646     default:
1647       GST_WARNING ("Trying to allocate regex of unknown type %u", regtype);
1648   }
1649   return result;
1650 }
1651
1652 /*
1653  * FIXME: maybe we should pass along a second argument, the preceding
1654  * text buffer, because that is how this originally worked, even though
1655  * I don't really see the use of that.
1656  */
1657
1658 static GstSubParseFormat
1659 gst_sub_parse_data_format_autodetect (gchar * match_str)
1660 {
1661   guint n1, n2, n3;
1662
1663   static GOnce mdvd_rx_once = G_ONCE_INIT;
1664   static GOnce subrip_rx_once = G_ONCE_INIT;
1665   static GOnce dks_rx_once = G_ONCE_INIT;
1666   static GOnce vtt_rx_once = G_ONCE_INIT;
1667
1668   GRegex *mdvd_grx;
1669   GRegex *subrip_grx;
1670   GRegex *dks_grx;
1671   GRegex *vtt_grx;
1672
1673   g_once (&mdvd_rx_once,
1674       (GThreadFunc) gst_sub_parse_data_format_autodetect_regex_once,
1675       (gpointer) GST_SUB_PARSE_REGEX_MDVDSUB);
1676   g_once (&subrip_rx_once,
1677       (GThreadFunc) gst_sub_parse_data_format_autodetect_regex_once,
1678       (gpointer) GST_SUB_PARSE_REGEX_SUBRIP);
1679   g_once (&dks_rx_once,
1680       (GThreadFunc) gst_sub_parse_data_format_autodetect_regex_once,
1681       (gpointer) GST_SUB_PARSE_REGEX_DKS);
1682   g_once (&vtt_rx_once,
1683       (GThreadFunc) gst_sub_parse_data_format_autodetect_regex_once,
1684       (gpointer) GST_SUB_PARSE_REGEX_VTT);
1685
1686   mdvd_grx = (GRegex *) mdvd_rx_once.retval;
1687   subrip_grx = (GRegex *) subrip_rx_once.retval;
1688   dks_grx = (GRegex *) dks_rx_once.retval;
1689   vtt_grx = (GRegex *) vtt_rx_once.retval;
1690
1691   if (g_regex_match (mdvd_grx, match_str, 0, NULL)) {
1692     GST_LOG ("MicroDVD (frame based) format detected");
1693     return GST_SUB_PARSE_FORMAT_MDVDSUB;
1694   }
1695   if (g_regex_match (subrip_grx, match_str, 0, NULL)) {
1696     GST_LOG ("SubRip (time based) format detected");
1697     return GST_SUB_PARSE_FORMAT_SUBRIP;
1698   }
1699   if (g_regex_match (dks_grx, match_str, 0, NULL)) {
1700     GST_LOG ("DKS (time based) format detected");
1701     return GST_SUB_PARSE_FORMAT_DKS;
1702   }
1703   if (g_regex_match (vtt_grx, match_str, 0, NULL) == TRUE) {
1704     GST_LOG ("WebVTT (time based) format detected");
1705     return GST_SUB_PARSE_FORMAT_VTT;
1706   }
1707
1708   if (!strncmp (match_str, "FORMAT=TIME", 11)) {
1709     GST_LOG ("MPSub (time based) format detected");
1710     return GST_SUB_PARSE_FORMAT_MPSUB;
1711   }
1712   if (strstr (match_str, "<SAMI>") != NULL ||
1713       strstr (match_str, "<sami>") != NULL) {
1714     GST_LOG ("SAMI (time based) format detected");
1715     return GST_SUB_PARSE_FORMAT_SAMI;
1716   }
1717   /* we're boldly assuming the first subtitle appears within the first hour */
1718   if (sscanf (match_str, "0:%02u:%02u:", &n1, &n2) == 2 ||
1719       sscanf (match_str, "0:%02u:%02u=", &n1, &n2) == 2 ||
1720       sscanf (match_str, "00:%02u:%02u:", &n1, &n2) == 2 ||
1721       sscanf (match_str, "00:%02u:%02u=", &n1, &n2) == 2 ||
1722       sscanf (match_str, "00:%02u:%02u,%u=", &n1, &n2, &n3) == 3) {
1723     GST_LOG ("TMPlayer (time based) format detected");
1724     return GST_SUB_PARSE_FORMAT_TMPLAYER;
1725   }
1726   if (sscanf (match_str, "[%u][%u]", &n1, &n2) == 2) {
1727     GST_LOG ("MPL2 (time based) format detected");
1728     return GST_SUB_PARSE_FORMAT_MPL2;
1729   }
1730   if (strstr (match_str, "[INFORMATION]") != NULL) {
1731     GST_LOG ("SubViewer (time based) format detected");
1732     return GST_SUB_PARSE_FORMAT_SUBVIEWER;
1733   }
1734   if (strstr (match_str, "{QTtext}") != NULL) {
1735     GST_LOG ("QTtext (time based) format detected");
1736     return GST_SUB_PARSE_FORMAT_QTTEXT;
1737   }
1738   /* We assume the LRC file starts immediately */
1739   if (match_str[0] == '[') {
1740     gboolean all_lines_good = TRUE;
1741     gchar **split;
1742     gchar **ptr;
1743
1744     ptr = split = g_strsplit (match_str, "\n", -1);
1745     while (*ptr && *(ptr + 1)) {
1746       gchar *str = *ptr;
1747       gint len = strlen (str);
1748
1749       if (sscanf (str, "[%u:%02u.%02u]", &n1, &n2, &n3) == 3 ||
1750           sscanf (str, "[%u:%02u.%03u]", &n1, &n2, &n3) == 3) {
1751         all_lines_good = TRUE;
1752       } else if (str[len - 1] == ']' && strchr (str, ':') != NULL) {
1753         all_lines_good = TRUE;
1754       } else {
1755         all_lines_good = FALSE;
1756         break;
1757       }
1758
1759       ptr++;
1760     }
1761     g_strfreev (split);
1762
1763     if (all_lines_good)
1764       return GST_SUB_PARSE_FORMAT_LRC;
1765   }
1766
1767   GST_DEBUG ("no subtitle format detected");
1768   return GST_SUB_PARSE_FORMAT_UNKNOWN;
1769 }
1770
1771 static GstCaps *
1772 gst_sub_parse_format_autodetect (GstSubParse * self)
1773 {
1774   gchar *data;
1775   GstSubParseFormat format;
1776
1777 #ifdef TIZEN_FEATURE_UPSTREAM
1778   if (strlen (self->textbuf->str) < 6) {
1779 #else
1780   if (strlen (self->textbuf->str) < 30) {
1781 #endif
1782     GST_DEBUG ("File too small to be a subtitles file");
1783     return NULL;
1784   }
1785
1786   data = g_strndup (self->textbuf->str, 35);
1787   format = gst_sub_parse_data_format_autodetect (data);
1788   g_free (data);
1789
1790   self->parser_type = format;
1791   self->subtitle_codec = gst_sub_parse_get_format_description (format);
1792   parser_state_init (&self->state);
1793   self->state.allowed_tags = NULL;
1794
1795   switch (format) {
1796     case GST_SUB_PARSE_FORMAT_MDVDSUB:
1797       self->parse_line = parse_mdvdsub;
1798       return gst_caps_new_simple ("text/x-raw",
1799           "format", G_TYPE_STRING, "pango-markup", NULL);
1800     case GST_SUB_PARSE_FORMAT_SUBRIP:
1801       self->state.allowed_tags = (gpointer) allowed_srt_tags;
1802       self->state.allows_tag_attributes = FALSE;
1803       self->parse_line = parse_subrip;
1804       return gst_caps_new_simple ("text/x-raw",
1805           "format", G_TYPE_STRING, "pango-markup", NULL);
1806     case GST_SUB_PARSE_FORMAT_MPSUB:
1807       self->parse_line = parse_mpsub;
1808       return gst_caps_new_simple ("text/x-raw",
1809           "format", G_TYPE_STRING, "utf8", NULL);
1810     case GST_SUB_PARSE_FORMAT_SAMI:
1811       self->parse_line = parse_sami;
1812       sami_context_init (&self->state);
1813       return gst_caps_new_simple ("text/x-raw",
1814           "format", G_TYPE_STRING, "pango-markup", NULL);
1815     case GST_SUB_PARSE_FORMAT_TMPLAYER:
1816       self->parse_line = parse_tmplayer;
1817       self->state.max_duration = 5 * GST_SECOND;
1818       return gst_caps_new_simple ("text/x-raw",
1819           "format", G_TYPE_STRING, "utf8", NULL);
1820     case GST_SUB_PARSE_FORMAT_MPL2:
1821       self->parse_line = parse_mpl2;
1822       return gst_caps_new_simple ("text/x-raw",
1823           "format", G_TYPE_STRING, "pango-markup", NULL);
1824     case GST_SUB_PARSE_FORMAT_DKS:
1825       self->parse_line = parse_dks;
1826       return gst_caps_new_simple ("text/x-raw",
1827           "format", G_TYPE_STRING, "utf8", NULL);
1828     case GST_SUB_PARSE_FORMAT_VTT:
1829       self->state.allowed_tags = (gpointer) allowed_vtt_tags;
1830       self->state.allows_tag_attributes = TRUE;
1831       self->parse_line = parse_webvtt;
1832       return gst_caps_new_simple ("text/x-raw",
1833           "format", G_TYPE_STRING, "pango-markup", NULL);
1834     case GST_SUB_PARSE_FORMAT_SUBVIEWER:
1835       self->parse_line = parse_subviewer;
1836       return gst_caps_new_simple ("text/x-raw",
1837           "format", G_TYPE_STRING, "utf8", NULL);
1838     case GST_SUB_PARSE_FORMAT_QTTEXT:
1839       self->parse_line = parse_qttext;
1840       qttext_context_init (&self->state);
1841       return gst_caps_new_simple ("text/x-raw",
1842           "format", G_TYPE_STRING, "pango-markup", NULL);
1843     case GST_SUB_PARSE_FORMAT_LRC:
1844       self->parse_line = parse_lrc;
1845       return gst_caps_new_simple ("text/x-raw",
1846           "format", G_TYPE_STRING, "utf8", NULL);
1847     case GST_SUB_PARSE_FORMAT_UNKNOWN:
1848     default:
1849       GST_DEBUG ("no subtitle format detected");
1850       GST_ELEMENT_ERROR (self, STREAM, WRONG_TYPE,
1851           ("The input is not a valid/supported subtitle file"), (NULL));
1852       return NULL;
1853   }
1854 }
1855
1856 static void
1857 feed_textbuf (GstSubParse * self, GstBuffer * buf)
1858 {
1859   gboolean discont;
1860   gsize consumed;
1861   gchar *input = NULL;
1862   const guint8 *data;
1863   gsize avail;
1864
1865   discont = GST_BUFFER_IS_DISCONT (buf);
1866
1867   if (GST_BUFFER_OFFSET_IS_VALID (buf) &&
1868       GST_BUFFER_OFFSET (buf) != self->offset) {
1869     self->offset = GST_BUFFER_OFFSET (buf);
1870     discont = TRUE;
1871   }
1872
1873   if (discont) {
1874     GST_INFO ("discontinuity");
1875     /* flush the parser state */
1876     parser_state_init (&self->state);
1877     g_string_truncate (self->textbuf, 0);
1878     gst_adapter_clear (self->adapter);
1879     if (self->parser_type == GST_SUB_PARSE_FORMAT_SAMI)
1880       sami_context_reset (&self->state);
1881     /* we could set a flag to make sure that the next buffer we push out also
1882      * has the DISCONT flag set, but there's no point really given that it's
1883      * subtitles which are discontinuous by nature. */
1884   }
1885
1886   self->offset += gst_buffer_get_size (buf);
1887
1888   gst_adapter_push (self->adapter, buf);
1889
1890   avail = gst_adapter_available (self->adapter);
1891   data = gst_adapter_map (self->adapter, avail);
1892   input = convert_encoding (self, (const gchar *) data, avail, &consumed);
1893
1894   if (input && consumed > 0) {
1895     self->textbuf = g_string_append (self->textbuf, input);
1896     gst_adapter_unmap (self->adapter);
1897     gst_adapter_flush (self->adapter, consumed);
1898   } else {
1899     gst_adapter_unmap (self->adapter);
1900   }
1901
1902   g_free (input);
1903 }
1904
1905 #ifdef TIZEN_FEATURE_UPSTREAM
1906 static void
1907 xml_text (GMarkupParseContext * context,
1908     const gchar * text, gsize text_len, gpointer user_data, GError ** error)
1909 {
1910   gchar **accum = (gchar **) user_data;
1911   gchar *concat;
1912
1913   if (*accum) {
1914     concat = g_strconcat (*accum, text, NULL);
1915     g_free (*accum);
1916     *accum = concat;
1917   } else {
1918     *accum = g_strdup (text);
1919   }
1920 }
1921
1922 static gchar *
1923 strip_pango_markup (gchar * markup, GError ** error)
1924 {
1925   GMarkupParser parser = { 0, };
1926   GMarkupParseContext *context;
1927   gchar *accum = NULL;
1928
1929   parser.text = xml_text;
1930   context = g_markup_parse_context_new (&parser, 0, &accum, NULL);
1931
1932   g_markup_parse_context_parse (context, "<root>", 6, NULL);
1933   g_markup_parse_context_parse (context, markup, strlen (markup), error);
1934   g_markup_parse_context_parse (context, "</root>", 7, NULL);
1935   if (*error)
1936     goto error;
1937
1938   g_markup_parse_context_end_parse (context, error);
1939   if (*error)
1940     goto error;
1941
1942 done:
1943   g_markup_parse_context_free (context);
1944   return accum;
1945
1946 error:
1947   g_free (accum);
1948   accum = NULL;
1949   goto done;
1950 }
1951
1952 static gboolean
1953 gst_sub_parse_negotiate (GstSubParse * self, GstCaps * preferred)
1954 {
1955   GstCaps *caps;
1956   gboolean ret = FALSE;
1957   const GstStructure *s1, *s2;
1958
1959   caps = gst_pad_get_allowed_caps (self->srcpad);
1960
1961   s1 = gst_caps_get_structure (preferred, 0);
1962
1963   if (!g_strcmp0 (gst_structure_get_string (s1, "format"), "utf8")) {
1964     GstCaps *intersected = gst_caps_intersect (caps, preferred);
1965     gst_caps_unref (caps);
1966     caps = intersected;
1967   }
1968
1969   caps = gst_caps_fixate (caps);
1970
1971   if (gst_caps_is_empty (caps)) {
1972     goto done;
1973   }
1974
1975   s2 = gst_caps_get_structure (caps, 0);
1976
1977   self->strip_pango_markup =
1978       !g_strcmp0 (gst_structure_get_string (s2, "format"), "utf8")
1979       && !g_strcmp0 (gst_structure_get_string (s1, "format"), "pango-markup");
1980
1981   if (self->strip_pango_markup) {
1982     GST_INFO_OBJECT (self, "We will convert from pango-markup to utf8");
1983   }
1984
1985   ret = gst_pad_set_caps (self->srcpad, caps);
1986
1987 done:
1988   gst_caps_unref (caps);
1989   return ret;
1990 }
1991 #endif
1992
1993 static GstFlowReturn
1994 handle_buffer (GstSubParse * self, GstBuffer * buf)
1995 {
1996   GstFlowReturn ret = GST_FLOW_OK;
1997 #ifndef TIZEN_FEATURE_UPSTREAM
1998   GstCaps *caps = NULL;
1999 #endif
2000   gchar *line, *subtitle;
2001   gboolean need_tags = FALSE;
2002 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
2003   GstMessage *m = NULL;
2004 #endif
2005 #ifdef TIZEN_FEATURE_HLS_WEBVTT
2006   GstClockTime fragment_timestamp = GST_CLOCK_TIME_NONE;
2007 #endif
2008
2009   if (self->first_buffer) {
2010     GstMapInfo map;
2011
2012     gst_buffer_map (buf, &map, GST_MAP_READ);
2013     self->detected_encoding = detect_encoding ((gchar *) map.data, map.size);
2014     gst_buffer_unmap (buf, &map);
2015     self->first_buffer = FALSE;
2016     self->state.fps_n = self->fps_n;
2017     self->state.fps_d = self->fps_d;
2018   }
2019 #ifdef TIZEN_FEATURE_HLS_WEBVTT
2020   if (GST_BUFFER_IS_DISCONT (buf) && GST_BUFFER_PTS_IS_VALID (buf))
2021     fragment_timestamp = GST_BUFFER_PTS (buf);
2022 #endif
2023
2024   feed_textbuf (self, buf);
2025
2026   /* make sure we know the format */
2027   if (G_UNLIKELY (self->parser_type == GST_SUB_PARSE_FORMAT_UNKNOWN)) {
2028 #ifdef TIZEN_FEATURE_UPSTREAM
2029     GstCaps *preferred;
2030
2031     if (!(preferred = gst_sub_parse_format_autodetect (self))) {
2032       return GST_FLOW_NOT_NEGOTIATED;
2033     }
2034
2035     if (!gst_sub_parse_negotiate (self, preferred)) {
2036       gst_caps_unref (preferred);
2037       return GST_FLOW_NOT_NEGOTIATED;
2038     }
2039
2040     gst_caps_unref (preferred);
2041 #else
2042     if (!(caps = gst_sub_parse_format_autodetect (self))) {
2043       return GST_FLOW_EOS;
2044     }
2045     if (!gst_pad_set_caps (self->srcpad, caps)) {
2046       gst_caps_unref (caps);
2047       return GST_FLOW_EOS;
2048     }
2049     gst_caps_unref (caps);
2050 #endif
2051     need_tags = TRUE;
2052   }
2053
2054   /* Push newsegment if needed */
2055   if (self->need_segment) {
2056     GST_LOG_OBJECT (self, "pushing newsegment event with %" GST_SEGMENT_FORMAT,
2057         &self->segment);
2058
2059     gst_pad_push_event (self->srcpad, gst_event_new_segment (&self->segment));
2060     self->need_segment = FALSE;
2061   }
2062
2063   if (need_tags) {
2064     /* push tags */
2065     if (self->subtitle_codec != NULL) {
2066       GstTagList *tags;
2067
2068       tags = gst_tag_list_new (GST_TAG_SUBTITLE_CODEC, self->subtitle_codec,
2069           NULL);
2070       gst_pad_push_event (self->srcpad, gst_event_new_tag (tags));
2071     }
2072   }
2073 #ifdef TIZEN_FEATURE_HLS_WEBVTT
2074   if (self->parser_type == GST_SUB_PARSE_FORMAT_VTT)
2075     send_fragment_timestamp_event (self, fragment_timestamp);
2076 #endif
2077
2078   while (!self->flushing && (line = get_next_line (self))) {
2079     guint offset = 0;
2080
2081     /* Set segment on our parser state machine */
2082     self->state.segment = &self->segment;
2083     /* Now parse the line, out of segment lines will just return NULL */
2084     GST_LOG_OBJECT (self, "State %d. Parsing line '%s'", self->state.state,
2085         line + offset);
2086     subtitle = self->parse_line (&self->state, line + offset);
2087     g_free (line);
2088 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
2089     if (!self->state.langlist_msg_posted && self->state.language_list) {
2090       m = gst_message_new_element (GST_OBJECT_CAST (self),
2091           gst_structure_new ("Ext_Sub_Language_List", "lang_list",
2092               G_TYPE_POINTER, self->state.language_list, NULL));
2093
2094       gst_element_post_message (GST_ELEMENT_CAST (self), m);
2095       self->state.langlist_msg_posted = TRUE;
2096       GST_DEBUG_OBJECT (self, "curr lang as : %s ",
2097           GST_STR_NULL (self->state.current_language));
2098     }
2099 #endif
2100     if (subtitle) {
2101 #ifdef TIZEN_FEATURE_UPSTREAM
2102       guint subtitle_len;
2103
2104       if (self->strip_pango_markup) {
2105         GError *error = NULL;
2106         gchar *stripped;
2107
2108         if ((stripped = strip_pango_markup (subtitle, &error))) {
2109           g_free (subtitle);
2110           subtitle = stripped;
2111         } else {
2112           GST_WARNING_OBJECT (self, "Failed to strip pango markup: %s",
2113               error->message);
2114         }
2115       }
2116
2117       subtitle_len = strlen (subtitle);
2118 #else
2119       guint subtitle_len = strlen (subtitle);
2120 #endif
2121       /* +1 for terminating NUL character */
2122       buf = gst_buffer_new_and_alloc (subtitle_len + 1);
2123
2124       /* copy terminating NUL character as well */
2125       gst_buffer_fill (buf, 0, subtitle, subtitle_len + 1);
2126       gst_buffer_set_size (buf, subtitle_len);
2127
2128       GST_BUFFER_TIMESTAMP (buf) = self->state.start_time;
2129       GST_BUFFER_DURATION (buf) = self->state.duration;
2130
2131       /* in some cases (e.g. tmplayer) we can only determine the duration
2132        * of a text chunk from the timestamp of the next text chunk; in those
2133        * cases, we probably want to limit the duration to something
2134        * reasonable, so we don't end up showing some text for e.g. 40 seconds
2135        * just because nothing else is being said during that time */
2136       if (self->state.max_duration > 0 && GST_BUFFER_DURATION_IS_VALID (buf)) {
2137         if (GST_BUFFER_DURATION (buf) > self->state.max_duration)
2138           GST_BUFFER_DURATION (buf) = self->state.max_duration;
2139       }
2140
2141       self->segment.position = self->state.start_time;
2142
2143       GST_DEBUG_OBJECT (self, "Sending text '%s', %" GST_TIME_FORMAT " + %"
2144           GST_TIME_FORMAT, subtitle, GST_TIME_ARGS (self->state.start_time),
2145           GST_TIME_ARGS (self->state.duration));
2146
2147       g_free (self->state.vertical);
2148       self->state.vertical = NULL;
2149       g_free (self->state.alignment);
2150       self->state.alignment = NULL;
2151
2152       ret = gst_pad_push (self->srcpad, buf);
2153
2154       /* move this forward (the tmplayer parser needs this) */
2155       if (self->state.duration != GST_CLOCK_TIME_NONE)
2156         self->state.start_time += self->state.duration;
2157
2158       g_free (subtitle);
2159       subtitle = NULL;
2160
2161       if (ret != GST_FLOW_OK) {
2162         GST_DEBUG_OBJECT (self, "flow: %s", gst_flow_get_name (ret));
2163         break;
2164       }
2165     }
2166   }
2167
2168   return ret;
2169 }
2170
2171 static GstFlowReturn
2172 gst_sub_parse_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * buf)
2173 {
2174   GstFlowReturn ret;
2175   GstSubParse *self;
2176
2177   self = GST_SUBPARSE (parent);
2178
2179   ret = handle_buffer (self, buf);
2180
2181   return ret;
2182 }
2183
2184 static gboolean
2185 gst_sub_parse_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
2186 {
2187   GstSubParse *self = GST_SUBPARSE (parent);
2188   gboolean ret = FALSE;
2189
2190   GST_LOG_OBJECT (self, "%s event", GST_EVENT_TYPE_NAME (event));
2191
2192   switch (GST_EVENT_TYPE (event)) {
2193     case GST_EVENT_STREAM_GROUP_DONE:
2194     case GST_EVENT_EOS:{
2195       /* Make sure the last subrip chunk is pushed out even
2196        * if the file does not have an empty line at the end */
2197       if (self->parser_type == GST_SUB_PARSE_FORMAT_SUBRIP ||
2198           self->parser_type == GST_SUB_PARSE_FORMAT_TMPLAYER ||
2199           self->parser_type == GST_SUB_PARSE_FORMAT_MPL2 ||
2200           self->parser_type == GST_SUB_PARSE_FORMAT_QTTEXT ||
2201           self->parser_type == GST_SUB_PARSE_FORMAT_VTT) {
2202         gchar term_chars[] = { '\n', '\n', '\0' };
2203         GstBuffer *buf = gst_buffer_new_and_alloc (2 + 1);
2204
2205         GST_DEBUG_OBJECT (self, "%s: force pushing of any remaining text",
2206             GST_EVENT_TYPE_NAME (event));
2207
2208         gst_buffer_fill (buf, 0, term_chars, 3);
2209         gst_buffer_set_size (buf, 2);
2210
2211         GST_BUFFER_OFFSET (buf) = self->offset;
2212         gst_sub_parse_chain (pad, parent, buf);
2213       }
2214       ret = gst_pad_event_default (pad, parent, event);
2215       break;
2216     }
2217     case GST_EVENT_SEGMENT:
2218     {
2219       const GstSegment *s;
2220
2221 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
2222       if (self->first_buffer) {
2223         if (!SUBPARSE_SEEK_TRYLOCK (self)) {
2224           /* new seeking request is in process */
2225           GST_WARNING_OBJECT (self, "ignore the old newsegment event");
2226           ret = TRUE;
2227           gst_event_unref (event);
2228           break;
2229         }
2230       } else {
2231         SUBPARSE_SEEK_LOCK (self);
2232       }
2233 #endif
2234
2235       gst_event_parse_segment (event, &s);
2236       if (s->format == GST_FORMAT_TIME)
2237         gst_event_copy_segment (event, &self->segment);
2238       GST_DEBUG_OBJECT (self, "newsegment (%s)",
2239           gst_format_get_name (self->segment.format));
2240
2241       /* if not time format, we'll either start with a 0 timestamp anyway or
2242        * it's following a seek in which case we'll have saved the requested
2243        * seek segment and don't want to overwrite it (remember that on a seek
2244        * we always just seek back to the start in BYTES format and just throw
2245        * away all text that's before the requested position; if the subtitles
2246        * come from an upstream demuxer, it won't be able to handle our BYTES
2247        * seek request and instead send us a newsegment from the seek request
2248        * it received via its video pads instead, so all is fine then too) */
2249       ret = TRUE;
2250       gst_event_unref (event);
2251       /* in either case, let's not simply discard this event;
2252        * trigger sending of the saved requested seek segment
2253        * or the one taken here from upstream */
2254       self->need_segment = TRUE;
2255
2256 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
2257       SUBPARSE_SEEK_UNLOCK (self);
2258 #endif
2259
2260       break;
2261     }
2262     case GST_EVENT_FLUSH_START:
2263     {
2264       self->flushing = TRUE;
2265
2266       ret = gst_pad_event_default (pad, parent, event);
2267       break;
2268     }
2269     case GST_EVENT_FLUSH_STOP:
2270     {
2271       self->flushing = FALSE;
2272
2273       ret = gst_pad_event_default (pad, parent, event);
2274       break;
2275     }
2276     default:
2277       ret = gst_pad_event_default (pad, parent, event);
2278       break;
2279   }
2280
2281   return ret;
2282 }
2283
2284
2285 static GstStateChangeReturn
2286 gst_sub_parse_change_state (GstElement * element, GstStateChange transition)
2287 {
2288   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2289   GstSubParse *self = GST_SUBPARSE (element);
2290
2291   switch (transition) {
2292     case GST_STATE_CHANGE_READY_TO_PAUSED:
2293       /* format detection will init the parser state */
2294       self->offset = 0;
2295       self->parser_type = GST_SUB_PARSE_FORMAT_UNKNOWN;
2296 #ifdef TIZEN_FEATURE_UPSTREAM
2297       self->strip_pango_markup = FALSE;
2298 #endif
2299       self->valid_utf8 = TRUE;
2300       self->first_buffer = TRUE;
2301       g_free (self->detected_encoding);
2302       self->detected_encoding = NULL;
2303       g_string_truncate (self->textbuf, 0);
2304       gst_adapter_clear (self->adapter);
2305       break;
2306     default:
2307       break;
2308   }
2309
2310   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
2311   if (ret == GST_STATE_CHANGE_FAILURE)
2312     return ret;
2313
2314   switch (transition) {
2315     case GST_STATE_CHANGE_PAUSED_TO_READY:
2316       parser_state_dispose (self, &self->state);
2317       self->parser_type = GST_SUB_PARSE_FORMAT_UNKNOWN;
2318       break;
2319     default:
2320       break;
2321   }
2322
2323   return ret;
2324 }
2325
2326 /*
2327  * Typefind support.
2328  */
2329
2330 /* FIXME 0.11: these caps are ugly, use app/x-subtitle + type field or so;
2331  * also, give different  subtitle formats really different types */
2332 static GstStaticCaps mpl2_caps =
2333 GST_STATIC_CAPS ("application/x-subtitle-mpl2");
2334 #define SUB_CAPS (gst_static_caps_get (&sub_caps))
2335
2336 static GstStaticCaps tmp_caps =
2337 GST_STATIC_CAPS ("application/x-subtitle-tmplayer");
2338 #define TMP_CAPS (gst_static_caps_get (&tmp_caps))
2339
2340 static GstStaticCaps sub_caps = GST_STATIC_CAPS ("application/x-subtitle");
2341 #define MPL2_CAPS (gst_static_caps_get (&mpl2_caps))
2342
2343 static GstStaticCaps smi_caps = GST_STATIC_CAPS ("application/x-subtitle-sami");
2344 #define SAMI_CAPS (gst_static_caps_get (&smi_caps))
2345
2346 static GstStaticCaps dks_caps = GST_STATIC_CAPS ("application/x-subtitle-dks");
2347 #define DKS_CAPS (gst_static_caps_get (&dks_caps))
2348
2349 static GstStaticCaps vtt_caps = GST_STATIC_CAPS ("application/x-subtitle-vtt");
2350 #define VTT_CAPS (gst_static_caps_get (&vtt_caps))
2351
2352 static GstStaticCaps qttext_caps =
2353 GST_STATIC_CAPS ("application/x-subtitle-qttext");
2354 #define QTTEXT_CAPS (gst_static_caps_get (&qttext_caps))
2355
2356 static GstStaticCaps lrc_caps = GST_STATIC_CAPS ("application/x-subtitle-lrc");
2357 #define LRC_CAPS (gst_static_caps_get (&lrc_caps))
2358
2359 static void
2360 gst_subparse_type_find (GstTypeFind * tf, gpointer private)
2361 {
2362   GstSubParseFormat format;
2363   const guint8 *data;
2364   GstCaps *caps;
2365   gchar *str;
2366   gchar *encoding = NULL;
2367   const gchar *end;
2368
2369   if (!(data = gst_type_find_peek (tf, 0, 129)))
2370     return;
2371
2372   /* make sure string passed to _autodetect() is NUL-terminated */
2373   str = g_malloc0 (129);
2374   memcpy (str, data, 128);
2375
2376   if ((encoding = detect_encoding (str, 128)) != NULL) {
2377     gchar *converted_str;
2378     GError *err = NULL;
2379     gsize tmp;
2380
2381     converted_str = gst_convert_to_utf8 (str, 128, encoding, &tmp, &err);
2382     if (converted_str == NULL) {
2383       GST_DEBUG ("Encoding '%s' detected but conversion failed: %s", encoding,
2384           err->message);
2385       g_clear_error (&err);
2386     } else {
2387       g_free (str);
2388       str = converted_str;
2389     }
2390     g_free (encoding);
2391   }
2392
2393   /* Check if at least the first 120 chars are valid UTF8,
2394    * otherwise convert as always */
2395   if (!g_utf8_validate (str, 128, &end) && (end - str) < 120) {
2396     gchar *converted_str;
2397     gsize tmp;
2398     const gchar *enc;
2399
2400     enc = g_getenv ("GST_SUBTITLE_ENCODING");
2401     if (enc == NULL || *enc == '\0') {
2402       /* if local encoding is UTF-8 and no encoding specified
2403        * via the environment variable, assume ISO-8859-15 */
2404       if (g_get_charset (&enc)) {
2405         enc = "ISO-8859-15";
2406       }
2407     }
2408     converted_str = gst_convert_to_utf8 (str, 128, enc, &tmp, NULL);
2409     if (converted_str != NULL) {
2410       g_free (str);
2411       str = converted_str;
2412     }
2413   }
2414
2415   format = gst_sub_parse_data_format_autodetect (str);
2416   g_free (str);
2417
2418   switch (format) {
2419     case GST_SUB_PARSE_FORMAT_MDVDSUB:
2420       GST_DEBUG ("MicroDVD format detected");
2421       caps = SUB_CAPS;
2422       break;
2423     case GST_SUB_PARSE_FORMAT_SUBRIP:
2424       GST_DEBUG ("SubRip format detected");
2425       caps = SUB_CAPS;
2426       break;
2427     case GST_SUB_PARSE_FORMAT_MPSUB:
2428       GST_DEBUG ("MPSub format detected");
2429       caps = SUB_CAPS;
2430       break;
2431     case GST_SUB_PARSE_FORMAT_SAMI:
2432       GST_DEBUG ("SAMI (time-based) format detected");
2433       caps = SAMI_CAPS;
2434       break;
2435     case GST_SUB_PARSE_FORMAT_TMPLAYER:
2436       GST_DEBUG ("TMPlayer (time based) format detected");
2437       caps = TMP_CAPS;
2438       break;
2439       /* FIXME: our MPL2 typefinding is not really good enough to warrant
2440        * returning a high probability (however, since we registered our
2441        * typefinder here with a rank of MARGINAL we should pretty much only
2442        * be called if most other typefinders have already run */
2443     case GST_SUB_PARSE_FORMAT_MPL2:
2444       GST_DEBUG ("MPL2 (time based) format detected");
2445       caps = MPL2_CAPS;
2446       break;
2447     case GST_SUB_PARSE_FORMAT_SUBVIEWER:
2448       GST_DEBUG ("SubViewer format detected");
2449       caps = SUB_CAPS;
2450       break;
2451     case GST_SUB_PARSE_FORMAT_DKS:
2452       GST_DEBUG ("DKS format detected");
2453       caps = DKS_CAPS;
2454       break;
2455     case GST_SUB_PARSE_FORMAT_QTTEXT:
2456       GST_DEBUG ("QTtext format detected");
2457       caps = QTTEXT_CAPS;
2458       break;
2459     case GST_SUB_PARSE_FORMAT_LRC:
2460       GST_DEBUG ("LRC format detected");
2461       caps = LRC_CAPS;
2462       break;
2463     case GST_SUB_PARSE_FORMAT_VTT:
2464       GST_DEBUG ("WebVTT format detected");
2465       caps = VTT_CAPS;
2466       break;
2467     default:
2468     case GST_SUB_PARSE_FORMAT_UNKNOWN:
2469       GST_DEBUG ("no subtitle format detected");
2470       return;
2471   }
2472
2473   /* if we're here, it's ok */
2474   gst_type_find_suggest (tf, GST_TYPE_FIND_MAXIMUM, caps);
2475 }
2476
2477 static gboolean
2478 plugin_init (GstPlugin * plugin)
2479 {
2480   GST_DEBUG_CATEGORY_INIT (sub_parse_debug, "subparse", 0, ".sub parser");
2481
2482   if (!gst_type_find_register (plugin, "subparse_typefind", GST_RANK_MARGINAL,
2483           gst_subparse_type_find, "srt,sub,mpsub,mdvd,smi,txt,dks,vtt",
2484           SUB_CAPS, NULL, NULL))
2485     return FALSE;
2486
2487   if (!gst_element_register (plugin, "subparse",
2488           GST_RANK_PRIMARY, GST_TYPE_SUBPARSE) ||
2489       !gst_element_register (plugin, "ssaparse",
2490           GST_RANK_PRIMARY, GST_TYPE_SSA_PARSE)) {
2491     return FALSE;
2492   }
2493
2494   return TRUE;
2495 }
2496
2497 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
2498     GST_VERSION_MINOR,
2499     subparse,
2500     "Subtitle parsing",
2501     plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)