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>
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.
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.
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.
31 #include <sys/types.h>
34 #include "gstsubparse.h"
35 #include "gstssaparse.h"
36 #include "samiparse.h"
37 #include "tmplayerparse.h"
38 #include "mpl2parse.h"
39 #include "qttextparse.h"
41 GST_DEBUG_CATEGORY (sub_parse_debug);
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))
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 };
56 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
57 #define DEFAULT_CURRENT_LANGUAGE NULL
64 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
65 PROP_EXTSUB_CURRENT_LANGUAGE
68 #ifdef TIZEN_FEATURE_HLS_WEBVTT
69 #define MPEGTIME_TO_GSTTIME(t) ((t) * (guint64)100000 / 9)
73 gst_sub_parse_set_property (GObject * object, guint prop_id,
74 const GValue * value, GParamSpec * pspec);
76 gst_sub_parse_get_property (GObject * object, guint prop_id,
77 GValue * value, GParamSpec * pspec);
80 static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink",
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")
89 static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
92 GST_STATIC_CAPS ("text/x-raw, format= { pango-markup, utf8 }")
96 static gboolean gst_sub_parse_src_event (GstPad * pad, GstObject * parent,
98 static gboolean gst_sub_parse_src_query (GstPad * pad, GstObject * parent,
100 static gboolean gst_sub_parse_sink_event (GstPad * pad, GstObject * parent,
103 static GstStateChangeReturn gst_sub_parse_change_state (GstElement * element,
104 GstStateChange transition);
106 static GstFlowReturn gst_sub_parse_chain (GstPad * sinkpad, GstObject * parent,
108 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
109 static gboolean gst_sub_parse_check_byte_seekability (GstSubParse * subparse);
111 #define gst_sub_parse_parent_class parent_class
112 G_DEFINE_TYPE (GstSubParse, gst_sub_parse, GST_TYPE_ELEMENT);
115 gst_sub_parse_dispose (GObject * object)
117 GstSubParse *subparse = GST_SUBPARSE (object);
119 GST_DEBUG_OBJECT (subparse, "cleaning up subtitle parser");
121 if (subparse->encoding) {
122 g_free (subparse->encoding);
123 subparse->encoding = NULL;
126 if (subparse->detected_encoding) {
127 g_free (subparse->detected_encoding);
128 subparse->detected_encoding = NULL;
131 if (subparse->adapter) {
132 g_object_unref (subparse->adapter);
133 subparse->adapter = NULL;
136 if (subparse->textbuf) {
137 g_string_free (subparse->textbuf, TRUE);
138 subparse->textbuf = NULL;
140 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
141 g_free (subparse->state.current_language);
142 subparse->state.current_language = NULL;
144 g_mutex_clear (&subparse->seek_lock);
146 GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
150 gst_sub_parse_class_init (GstSubParseClass * klass)
152 GObjectClass *object_class = G_OBJECT_CLASS (klass);
153 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
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;
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>");
167 element_class->change_state = gst_sub_parse_change_state;
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));
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));
194 gst_sub_parse_init (GstSubParse * subparse)
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);
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);
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;
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 ();
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;
229 g_mutex_init (&subparse->seek_lock);
234 * Source pad functions.
238 gst_sub_parse_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
240 GstSubParse *self = GST_SUBPARSE (parent);
241 gboolean ret = FALSE;
243 GST_DEBUG ("Handling %s query", GST_QUERY_TYPE_NAME (query));
245 switch (GST_QUERY_TYPE (query)) {
246 case GST_QUERY_POSITION:{
249 gst_query_parse_position (query, &fmt, NULL);
250 if (fmt != GST_FORMAT_TIME) {
251 ret = gst_pad_peer_query (self->sinkpad, query);
254 gst_query_set_position (query, GST_FORMAT_TIME, self->segment.position);
258 case GST_QUERY_SEEKING:
261 gboolean seekable = FALSE;
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);
269 seekable = gst_pad_peer_query (self->sinkpad, peerquery);
271 gst_query_parse_seeking (peerquery, NULL, &seekable, NULL, NULL);
272 gst_query_unref (peerquery);
275 gst_query_set_seeking (query, fmt, seekable, seekable ? 0 : -1, -1);
279 ret = gst_pad_query_default (pad, parent, query);
287 gst_sub_parse_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
289 GstSubParse *self = GST_SUBPARSE (parent);
290 gboolean ret = FALSE;
292 GST_DEBUG ("Handling %s event", GST_EVENT_TYPE_NAME (event));
294 switch (GST_EVENT_TYPE (event)) {
299 GstSeekType start_type, stop_type;
304 gst_event_parse_seek (event, &rate, &format, &flags,
305 &start_type, &start, &stop_type, &stop);
307 if (format != GST_FORMAT_TIME) {
308 GST_WARNING_OBJECT (self, "we only support seeking in TIME format");
309 gst_event_unref (event);
312 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
313 if (!gst_sub_parse_check_byte_seekability (self)) {
314 ret = gst_pad_event_default (pad, parent, event);
318 SUBPARSE_SEEK_LOCK (self);
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));
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);
331 GST_DEBUG_OBJECT (self, "segment after seek: %" GST_SEGMENT_FORMAT,
334 /* will mark need_segment when receiving segment from upstream,
335 * after FLUSH and all that has happened,
336 * rather than racing with chain */
338 GST_WARNING_OBJECT (self, "seek to 0 bytes failed");
341 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
342 SUBPARSE_SEEK_UNLOCK (self);
345 gst_event_unref (event);
349 ret = gst_pad_event_default (pad, parent, event);
357 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
359 gst_sub_parse_check_byte_seekability (GstSubParse * subparse)
362 gboolean seekable = FALSE;
364 query = gst_query_new_seeking (GST_FORMAT_BYTES);
366 if (gst_pad_peer_query (subparse->sinkpad, query)) {
367 gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL);
369 GST_DEBUG_OBJECT (subparse, "seeking query failed");
372 gst_query_unref (query);
374 GST_INFO_OBJECT (subparse, "byte seekable: %d", seekable);
381 gst_sub_parse_set_property (GObject * object, guint prop_id,
382 const GValue * value, GParamSpec * pspec)
384 GstSubParse *subparse = GST_SUBPARSE (object);
386 GST_OBJECT_LOCK (subparse);
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));
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,
401 if (!subparse->state.have_internal_fps) {
402 subparse->state.fps_n = subparse->fps_n;
403 subparse->state.fps_d = subparse->fps_d;
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);
417 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
420 GST_OBJECT_UNLOCK (subparse);
424 gst_sub_parse_get_property (GObject * object, guint prop_id,
425 GValue * value, GParamSpec * pspec)
427 GstSubParse *subparse = GST_SUBPARSE (object);
429 GST_OBJECT_LOCK (subparse);
432 g_value_set_string (value, subparse->encoding);
435 gst_value_set_fraction (value, subparse->fps_n, subparse->fps_d);
437 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
438 case PROP_EXTSUB_CURRENT_LANGUAGE:
439 g_value_set_string (value, subparse->state.current_language);
443 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
446 GST_OBJECT_UNLOCK (subparse);
450 gst_sub_parse_get_format_description (GstSubParseFormat format)
453 case GST_SUB_PARSE_FORMAT_MDVDSUB:
455 case GST_SUB_PARSE_FORMAT_SUBRIP:
457 case GST_SUB_PARSE_FORMAT_MPSUB:
459 case GST_SUB_PARSE_FORMAT_SAMI:
461 case GST_SUB_PARSE_FORMAT_TMPLAYER:
463 case GST_SUB_PARSE_FORMAT_MPL2:
465 case GST_SUB_PARSE_FORMAT_SUBVIEWER:
467 case GST_SUB_PARSE_FORMAT_DKS:
469 case GST_SUB_PARSE_FORMAT_VTT:
471 case GST_SUB_PARSE_FORMAT_QTTEXT:
473 case GST_SUB_PARSE_FORMAT_LRC:
476 case GST_SUB_PARSE_FORMAT_UNKNOWN:
483 gst_convert_to_utf8 (const gchar * str, gsize len, const gchar * encoding,
484 gsize * consumed, GError ** err)
489 /* The char cast is necessary in glib < 2.24 */
491 g_convert_with_fallback (str, len, "UTF-8", encoding, (char *) "*",
492 consumed, NULL, err);
496 /* + 3 to skip UTF-8 BOM if it was added */
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);
506 detect_encoding (const gchar * str, gsize len)
508 if (len >= 3 && (guint8) str[0] == 0xEF && (guint8) str[1] == 0xBB
509 && (guint8) str[2] == 0xBF)
510 return g_strdup ("UTF-8");
512 if (len >= 2 && (guint8) str[0] == 0xFE && (guint8) str[1] == 0xFF)
513 return g_strdup ("UTF-16BE");
515 if (len >= 2 && (guint8) str[0] == 0xFF && (guint8) str[1] == 0xFE)
516 return g_strdup ("UTF-16LE");
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");
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");
530 convert_encoding (GstSubParse * self, const gchar * str, gsize len,
533 const gchar *encoding;
539 /* First try any detected encoding */
540 if (self->detected_encoding) {
542 gst_convert_to_utf8 (str, len, self->detected_encoding, consumed, &err);
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);
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");
559 return g_strndup (str, len);
561 GST_INFO_OBJECT (self, "invalid UTF-8!");
562 self->valid_utf8 = FALSE;
565 /* Else try fallback */
566 encoding = self->encoding;
567 if (encoding == NULL || *encoding == '\0') {
568 encoding = g_getenv ("GST_SUBTITLE_ENCODING");
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";
578 ret = gst_convert_to_utf8 (str, len, encoding, consumed, &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);
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);
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
602 GST_LOG_OBJECT (self,
603 "successfully converted %" G_GSIZE_FORMAT " characters from %s to UTF-8",
610 get_next_line (GstSubParse * self)
613 const char *line_end;
615 gboolean have_r = FALSE;
617 line_end = strchr (self->textbuf->str, '\n');
620 /* end-of-line not found; return for more data */
624 /* get rid of '\r' */
625 if (line_end != self->textbuf->str && *(line_end - 1) == '\r') {
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));
638 parse_mdvdsub (ParserState * state, const gchar * line)
640 const gchar *line_split;
642 guint start_frame, end_frame;
643 guint64 clip_start = 0, clip_stop = 0;
644 gboolean in_seg = FALSE;
648 /* style variables */
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);
660 /* skip the {%u}{%u} part */
661 line = strchr (line, '}') + 1;
662 line = strchr (line, '}') + 1;
664 /* see if there's a first line with a framerate */
665 if (start_frame == 1 && end_frame == 1) {
666 gchar *rest, *end = NULL;
668 rest = g_strdup (line);
669 g_strdelimit (rest, ",", '.');
670 fps = g_ascii_strtod (rest, &end);
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,
681 gst_util_uint64_scale (start_frame, GST_SECOND * state->fps_d,
684 gst_util_uint64_scale (end_frame - start_frame, GST_SECOND * state->fps_d,
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,
692 /* No need to parse that text if it's out of segment */
694 state->start_time = clip_start;
695 state->duration = clip_stop - clip_start;
700 markup = g_string_new (NULL);
705 /* parse style markup */
706 if (strncmp (line, "{y:i}", 5) == 0) {
708 line = strchr (line, '}') + 1;
710 if (strncmp (line, "{y:b}", 5) == 0) {
712 line = strchr (line, '}') + 1;
714 if (sscanf (line, "{s:%u}", &fontsize) == 1) {
715 line = strchr (line, '}') + 1;
717 /* forward slashes at beginning/end signify italics too */
718 if (g_str_has_prefix (line, "/")) {
722 if ((line_split = strchr (line, '|')))
723 line_chunk = g_markup_escape_text (line, line_split - line);
725 line_chunk = g_markup_escape_text (line, strlen (line));
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';
733 markup = g_string_append (markup, "<span");
735 g_string_append (markup, " style=\"italic\"");
737 g_string_append (markup, " weight=\"bold\"");
739 g_string_append_printf (markup, " size=\"%u\"", fontsize * 1000);
740 g_string_append_printf (markup, ">%s</span>", line_chunk);
743 g_string_append (markup, "\n");
744 line = line_split + 1;
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);
758 strip_trailing_newlines (gchar * txt)
764 while (len > 1 && txt[len - 1] == '\n') {
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) */
777 subrip_unescape_formatting (gchar * txt, gconstpointer allowed_tags_ptr,
778 gboolean allows_tag_attributes)
782 gchar *allowed_tags_pattern, *search_pattern;
783 const gchar *replace_pattern;
785 /* No processing needed if no escaped tag marker found in the string. */
786 if (strstr (txt, "<") == NULL)
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 ("<(/)?\\ *(%s)(%s)>",
794 allowed_tags_pattern, ATTRIBUTE_REGEX);
795 /* And unescape appropriately */
796 if (allows_tag_attributes) {
797 replace_pattern = "<\\1\\2\\3>";
799 replace_pattern = "<\\1\\2>";
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);
806 /* res will always be shorter than the input or identical, so this
811 g_free (search_pattern);
812 g_free (allowed_tags_pattern);
814 g_regex_unref (tag_regex);
819 subrip_remove_unhandled_tag (gchar * start, gchar * stop)
823 tag = start + strlen ("<");
827 if (g_ascii_tolower (*tag) < 'a' || g_ascii_tolower (*tag) > 'z')
832 GST_LOG ("removing unhandled tag '%s'", start);
834 memmove (start, stop, strlen (stop) + 1);
838 /* remove tags we haven't explicitly allowed earlier on, like font tags
841 subrip_remove_unhandled_tags (gchar * txt)
845 for (pos = txt; pos != NULL && *pos != '\0'; ++pos) {
846 if (strncmp (pos, "<", 4) == 0 && (gt = strstr (pos + 4, ">"))) {
847 if (subrip_remove_unhandled_tag (pos, gt + strlen (">")))
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. */
859 subrip_fix_up_markup (gchar ** p_txt, gconstpointer allowed_tags_ptr)
861 gchar *cur, *next_tag;
862 GPtrArray *open_tags = NULL;
863 guint num_open_tags = 0;
864 const gchar *iter_tag;
870 GMatchInfo *match_info;
871 gchar **allowed_tags = (gchar **) allowed_tags_ptr;
873 g_assert (*p_txt != NULL);
875 open_tags = g_ptr_array_new_with_free_func (g_free);
877 while (*cur != '\0') {
878 next_tag = strchr (cur, '<');
879 if (next_tag == NULL)
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);
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);
899 g_match_info_free (match_info);
900 g_regex_unref (tag_regex);
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));
917 if (*next_tag == '<' && *(next_tag + 1) == '/') {
918 end_tag = strchr (cur, '>');
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);
930 g_ptr_array_remove_index (open_tags, num_open_tags);
938 if (num_open_tags > 0) {
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, '>');
952 *p_txt = g_string_free (s, FALSE);
954 g_ptr_array_free (open_tags, TRUE);
958 parse_subrip_time (const gchar * ts_string, GstClockTime * t)
960 gchar s[128] = { '\0', };
962 guint hour, min, sec, msec, len;
964 while (*ts_string == ' ')
967 g_strlcpy (s, ts_string, sizeof (s));
968 if ((end = strstr (s, "-->")))
972 /* ms may be in these formats:
973 * hh:mm:ss,500 = 500ms
976 * hh:mm:ss, 50 = 50ms
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?)
983 g_strdelimit (s, " ", '0');
984 g_strdelimit (s, ".", ',');
986 /* make sure we have exactly three digits after he comma */
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);
1001 g_strlcat (&p[len], "0", 2);
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
1010 * The hours component is optional with webVTT, for example
1011 * mm:ss,500 is a valid webVTT timestamp. When not present,
1016 if (sscanf (s, "%u:%u,%u", &min, &sec, &msec) != 3) {
1017 GST_WARNING ("failed to parse subrip timestamp string '%s'", s);
1021 GST_WARNING ("failed to parse subrip timestamp string '%s'", s);
1026 *t = ((hour * 3600) + (min * 60) + sec) * GST_SECOND + msec * GST_MSECOND;
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
1036 parse_webvtt_cue_settings (ParserState * state, const gchar * settings)
1038 gchar **splitted_settings = g_strsplit_set (settings, " \t", -1);
1040 gint16 text_position, text_size;
1041 gint16 line_position;
1042 gboolean vertical_found = FALSE;
1043 gboolean alignment_found = FALSE;
1045 while (i < g_strv_length (splitted_settings)) {
1046 gboolean valid_tag = FALSE;
1047 switch (splitted_settings[i][0]) {
1049 if (sscanf (splitted_settings[i], "T:%" G_GINT16_FORMAT "%%",
1050 &text_position) > 0) {
1051 state->text_position = (guint8) text_position;
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);
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;
1071 if (sscanf (splitted_settings[i], "L:%" G_GINT16_FORMAT,
1072 &line_position) > 0) {
1073 state->line_number = line_position;
1079 if (sscanf (splitted_settings[i], "S:%" G_GINT16_FORMAT "%%",
1081 state->text_size = (guint8) text_size;
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;
1097 GST_LOG ("Invalid or unrecognised setting found: %s",
1098 splitted_settings[i]);
1102 g_strfreev (splitted_settings);
1103 if (!vertical_found) {
1104 g_free (state->vertical);
1105 state->vertical = g_strdup ("");
1107 if (!alignment_found) {
1108 g_free (state->alignment);
1109 state->alignment = g_strdup ("");
1113 #ifdef TIZEN_FEATURE_HLS_WEBVTT
1115 parse_timestamp_map (ParserState * state, const gchar * timestamp_map)
1117 GstClockTime local = 0;
1119 gchar *local_start = NULL;
1120 gchar *mpegts_start = NULL;
1125 local_start = g_strrstr (timestamp_map, "LOCAL:");
1127 parse_subrip_time (local_start + strlen ("LOCAL:"), &local);
1129 mpegts_start = g_strrstr (timestamp_map, "MPEGTS:");
1131 mpegts = g_ascii_strtoull (mpegts_start + strlen ("MPEGTS:"), NULL, 10);
1133 GST_LOG ("parsed local time %" GST_TIME_FORMAT " MPEGTS: %" G_GUINT64_FORMAT,
1134 GST_TIME_ARGS (local), mpegts);
1136 state->local = local;
1137 state->mpegts = mpegts;
1141 send_fragment_timestamp_event (GstSubParse * self, GstClockTime timestamp)
1143 GstEvent *event = NULL;
1145 if (!GST_CLOCK_TIME_IS_VALID (timestamp))
1148 GST_LOG ("send fragment_timestamp %" GST_TIME_FORMAT,
1149 GST_TIME_ARGS (timestamp));
1151 event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM,
1152 gst_structure_new ("fragment_timestamp",
1153 "timestamp", G_TYPE_UINT64, timestamp, NULL));
1155 gst_pad_push_event (self->srcpad, event);
1160 parse_subrip (ParserState * state, const gchar * line)
1164 switch (state->state) {
1169 /* looking for a single integer as a Cue ID, but we
1170 * don't actually use it */
1172 id = g_ascii_strtoull (line, &endptr, 10);
1173 if (id == G_MAXUINT64 && errno == ERANGE)
1175 else if (id == 0 && errno == EINVAL)
1177 else if (endptr != line && *endptr == '\0')
1183 GstClockTime ts_start, ts_end;
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) {
1192 state->start_time = ts_start;
1193 state->duration = ts_end - ts_start;
1195 GST_DEBUG ("error parsing subrip time line '%s'", line);
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;
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);
1212 state->start_time = clip_start;
1213 state->duration = clip_stop - clip_start;
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);
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);
1236 g_return_val_if_reached (NULL);
1241 parse_lrc (ParserState * state, const gchar * line)
1250 if (sscanf (line, "[%u:%02u.%03u]", &m, &s, &c) != 3 &&
1251 sscanf (line, "[%u:%02u.%02u]", &m, &s, &c) != 3)
1254 start = strchr (line, ']');
1255 if (start - line == 9)
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;
1265 return g_strdup (start + 1);
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
1275 parse_webvtt (ParserState * state, const gchar * line)
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;
1283 gchar *cue_settings = NULL;
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) {
1291 state->start_time = ts_start;
1292 #ifdef TIZEN_FEATURE_HLS_WEBVTT
1293 state->start_time += MPEGTIME_TO_GSTTIME (state->mpegts) - state->local;
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);
1304 GST_DEBUG ("error parsing subrip time line '%s'", line);
1308 state->text_position = 0;
1309 state->text_size = 0;
1310 state->line_position = 0;
1311 state->line_number = 0;
1314 parse_webvtt_cue_settings (state, cue_settings + 1);
1316 g_free (state->vertical);
1317 state->vertical = g_strdup ("");
1318 g_free (state->alignment);
1319 state->alignment = g_strdup ("");
1324 return parse_subrip (state, line);
1328 unescape_newlines_br (gchar * read)
1330 gchar *write = read;
1332 /* Replace all occurences of '[br]' with a newline as version 2
1333 * of the subviewer format uses this for newlines */
1335 if (read[0] == '\0' || read[1] == '\0' || read[2] == '\0' || read[3] == '\0')
1339 if (strncmp (read, "[br]", 4) == 0) {
1353 parse_subviewer (ParserState * state, const gchar * line)
1355 guint h1, m1, s1, ms1;
1356 guint h2, m2, s2, ms2;
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 */
1363 switch (state->state) {
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) {
1370 (((guint64) h1) * 3600 + m1 * 60 + s1) * GST_SECOND +
1373 (((guint64) h2) * 3600 + m2 * 60 + s2) * GST_SECOND +
1374 ms2 * GST_MSECOND - state->start_time;
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;
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);
1389 state->start_time = clip_start;
1390 state->duration = clip_stop - clip_start;
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);
1410 g_assert_not_reached ();
1416 parse_mpsub (ParserState * state, const gchar * line)
1421 switch (state->state) {
1423 /* looking for two floats (offset, duration) */
1424 if (sscanf (line, "%f %f", &t1, &t2) == 2) {
1426 state->start_time += state->duration + GST_SECOND * t1;
1427 state->duration = GST_SECOND * t2;
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;
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);
1441 state->start_time = clip_start;
1442 state->duration = clip_stop - clip_start;
1448 /* looking for subtitle text; empty line ends this
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);
1461 g_assert_not_reached ();
1466 static const gchar *
1467 dks_skip_timestamp (const gchar * line)
1469 while (*line && *line != ']')
1477 parse_dks (ParserState * state, const gchar * line)
1481 switch (state->state) {
1483 /* Looking for the start time and text */
1484 if (sscanf (line, "[%u:%u:%u]", &h, &m, &s) == 3) {
1486 state->start_time = (((guint64) h) * 3600 + m * 60 + s) * GST_SECOND;
1487 text = dks_skip_timestamp (line);
1490 g_string_append (state->buf, text);
1496 guint64 clip_start = 0, clip_stop = 0;
1500 /* Looking for the end time */
1501 if (sscanf (line, "[%u:%u:%u]", &h, &m, &s) == 3) {
1503 state->duration = (((guint64) h) * 3600 + m * 60 + s) * GST_SECOND -
1506 GST_WARNING ("Failed to parse subtitle end time");
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);
1519 state->start_time = clip_start;
1520 state->duration = clip_stop - clip_start;
1522 ret = g_strdup (state->buf->str);
1523 g_string_truncate (state->buf, 0);
1524 unescape_newlines_br (ret);
1528 g_assert_not_reached ();
1534 parser_state_init (ParserState * state)
1536 GST_DEBUG ("initialising parser");
1539 g_string_truncate (state->buf, 0);
1541 state->buf = g_string_new (NULL);
1544 state->start_time = 0;
1545 state->duration = 0;
1546 state->max_duration = 0; /* no limit */
1548 state->segment = NULL;
1549 #ifdef TIZEN_FEATURE_HLS_WEBVTT
1554 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
1555 state->language_list = NULL;
1557 g_free (state->current_language);
1558 state->current_language = NULL;
1560 state->langlist_msg_posted = FALSE;
1565 parser_state_dispose (GstSubParse * self, ParserState * state)
1568 g_string_free (state->buf, TRUE);
1572 g_free (state->vertical);
1573 state->vertical = NULL;
1574 g_free (state->alignment);
1575 state->alignment = NULL;
1577 if (state->user_data) {
1578 switch (self->parser_type) {
1579 case GST_SUB_PARSE_FORMAT_QTTEXT:
1580 qttext_context_deinit (state);
1582 case GST_SUB_PARSE_FORMAT_SAMI:
1583 sami_context_deinit (state);
1589 state->allowed_tags = NULL;
1592 /* regex type enum */
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,
1603 gst_sub_parse_data_format_autodetect_regex_once (GstSubParseRegex regtype)
1605 gpointer result = NULL;
1606 GError *gerr = NULL;
1608 case GST_SUB_PARSE_REGEX_MDVDSUB:
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);
1617 case GST_SUB_PARSE_REGEX_SUBRIP:
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);
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);
1636 case GST_SUB_PARSE_REGEX_VTT:
1638 g_regex_new ("^(\\xef\\xbb\\xbf)?WEBVTT[\\xa\\xd\\x20\\x9]", 0, 0,
1640 if (result == NULL) {
1641 g_warning ("Compilation of vtt regex failed: %s", gerr->message);
1642 g_error_free (gerr);
1647 GST_WARNING ("Trying to allocate regex of unknown type %u", regtype);
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.
1658 static GstSubParseFormat
1659 gst_sub_parse_data_format_autodetect (gchar * match_str)
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;
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);
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;
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;
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;
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;
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;
1708 if (!strncmp (match_str, "FORMAT=TIME", 11)) {
1709 GST_LOG ("MPSub (time based) format detected");
1710 return GST_SUB_PARSE_FORMAT_MPSUB;
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;
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;
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;
1730 if (strstr (match_str, "[INFORMATION]") != NULL) {
1731 GST_LOG ("SubViewer (time based) format detected");
1732 return GST_SUB_PARSE_FORMAT_SUBVIEWER;
1734 if (strstr (match_str, "{QTtext}") != NULL) {
1735 GST_LOG ("QTtext (time based) format detected");
1736 return GST_SUB_PARSE_FORMAT_QTTEXT;
1738 /* We assume the LRC file starts immediately */
1739 if (match_str[0] == '[') {
1740 gboolean all_lines_good = TRUE;
1744 ptr = split = g_strsplit (match_str, "\n", -1);
1745 while (*ptr && *(ptr + 1)) {
1747 gint len = strlen (str);
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;
1755 all_lines_good = FALSE;
1764 return GST_SUB_PARSE_FORMAT_LRC;
1767 GST_DEBUG ("no subtitle format detected");
1768 return GST_SUB_PARSE_FORMAT_UNKNOWN;
1772 gst_sub_parse_format_autodetect (GstSubParse * self)
1775 GstSubParseFormat format;
1777 #ifdef TIZEN_FEATURE_UPSTREAM
1778 if (strlen (self->textbuf->str) < 6) {
1780 if (strlen (self->textbuf->str) < 30) {
1782 GST_DEBUG ("File too small to be a subtitles file");
1786 data = g_strndup (self->textbuf->str, 35);
1787 format = gst_sub_parse_data_format_autodetect (data);
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;
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:
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));
1857 feed_textbuf (GstSubParse * self, GstBuffer * buf)
1861 gchar *input = NULL;
1865 discont = GST_BUFFER_IS_DISCONT (buf);
1867 if (GST_BUFFER_OFFSET_IS_VALID (buf) &&
1868 GST_BUFFER_OFFSET (buf) != self->offset) {
1869 self->offset = GST_BUFFER_OFFSET (buf);
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. */
1886 self->offset += gst_buffer_get_size (buf);
1888 gst_adapter_push (self->adapter, buf);
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);
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);
1899 gst_adapter_unmap (self->adapter);
1905 #ifdef TIZEN_FEATURE_UPSTREAM
1907 xml_text (GMarkupParseContext * context,
1908 const gchar * text, gsize text_len, gpointer user_data, GError ** error)
1910 gchar **accum = (gchar **) user_data;
1914 concat = g_strconcat (*accum, text, NULL);
1918 *accum = g_strdup (text);
1923 strip_pango_markup (gchar * markup, GError ** error)
1925 GMarkupParser parser = { 0, };
1926 GMarkupParseContext *context;
1927 gchar *accum = NULL;
1929 parser.text = xml_text;
1930 context = g_markup_parse_context_new (&parser, 0, &accum, NULL);
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);
1938 g_markup_parse_context_end_parse (context, error);
1943 g_markup_parse_context_free (context);
1953 gst_sub_parse_negotiate (GstSubParse * self, GstCaps * preferred)
1956 gboolean ret = FALSE;
1957 const GstStructure *s1, *s2;
1959 caps = gst_pad_get_allowed_caps (self->srcpad);
1961 s1 = gst_caps_get_structure (preferred, 0);
1963 if (!g_strcmp0 (gst_structure_get_string (s1, "format"), "utf8")) {
1964 GstCaps *intersected = gst_caps_intersect (caps, preferred);
1965 gst_caps_unref (caps);
1969 caps = gst_caps_fixate (caps);
1971 if (gst_caps_is_empty (caps)) {
1975 s2 = gst_caps_get_structure (caps, 0);
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");
1981 if (self->strip_pango_markup) {
1982 GST_INFO_OBJECT (self, "We will convert from pango-markup to utf8");
1985 ret = gst_pad_set_caps (self->srcpad, caps);
1988 gst_caps_unref (caps);
1993 static GstFlowReturn
1994 handle_buffer (GstSubParse * self, GstBuffer * buf)
1996 GstFlowReturn ret = GST_FLOW_OK;
1997 #ifndef TIZEN_FEATURE_UPSTREAM
1998 GstCaps *caps = NULL;
2000 gchar *line, *subtitle;
2001 gboolean need_tags = FALSE;
2002 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
2003 GstMessage *m = NULL;
2005 #ifdef TIZEN_FEATURE_HLS_WEBVTT
2006 GstClockTime fragment_timestamp = GST_CLOCK_TIME_NONE;
2009 if (self->first_buffer) {
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;
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);
2024 feed_textbuf (self, buf);
2026 /* make sure we know the format */
2027 if (G_UNLIKELY (self->parser_type == GST_SUB_PARSE_FORMAT_UNKNOWN)) {
2028 #ifdef TIZEN_FEATURE_UPSTREAM
2031 if (!(preferred = gst_sub_parse_format_autodetect (self))) {
2032 return GST_FLOW_NOT_NEGOTIATED;
2035 if (!gst_sub_parse_negotiate (self, preferred)) {
2036 gst_caps_unref (preferred);
2037 return GST_FLOW_NOT_NEGOTIATED;
2040 gst_caps_unref (preferred);
2042 if (!(caps = gst_sub_parse_format_autodetect (self))) {
2043 return GST_FLOW_EOS;
2045 if (!gst_pad_set_caps (self->srcpad, caps)) {
2046 gst_caps_unref (caps);
2047 return GST_FLOW_EOS;
2049 gst_caps_unref (caps);
2054 /* Push newsegment if needed */
2055 if (self->need_segment) {
2056 GST_LOG_OBJECT (self, "pushing newsegment event with %" GST_SEGMENT_FORMAT,
2059 gst_pad_push_event (self->srcpad, gst_event_new_segment (&self->segment));
2060 self->need_segment = FALSE;
2065 if (self->subtitle_codec != NULL) {
2068 tags = gst_tag_list_new (GST_TAG_SUBTITLE_CODEC, self->subtitle_codec,
2070 gst_pad_push_event (self->srcpad, gst_event_new_tag (tags));
2073 #ifdef TIZEN_FEATURE_HLS_WEBVTT
2074 if (self->parser_type == GST_SUB_PARSE_FORMAT_VTT)
2075 send_fragment_timestamp_event (self, fragment_timestamp);
2078 while (!self->flushing && (line = get_next_line (self))) {
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,
2086 subtitle = self->parse_line (&self->state, line + offset);
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));
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));
2101 #ifdef TIZEN_FEATURE_UPSTREAM
2104 if (self->strip_pango_markup) {
2105 GError *error = NULL;
2108 if ((stripped = strip_pango_markup (subtitle, &error))) {
2110 subtitle = stripped;
2112 GST_WARNING_OBJECT (self, "Failed to strip pango markup: %s",
2117 subtitle_len = strlen (subtitle);
2119 guint subtitle_len = strlen (subtitle);
2121 /* +1 for terminating NUL character */
2122 buf = gst_buffer_new_and_alloc (subtitle_len + 1);
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);
2128 GST_BUFFER_TIMESTAMP (buf) = self->state.start_time;
2129 GST_BUFFER_DURATION (buf) = self->state.duration;
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;
2141 self->segment.position = self->state.start_time;
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));
2147 g_free (self->state.vertical);
2148 self->state.vertical = NULL;
2149 g_free (self->state.alignment);
2150 self->state.alignment = NULL;
2152 ret = gst_pad_push (self->srcpad, buf);
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;
2161 if (ret != GST_FLOW_OK) {
2162 GST_DEBUG_OBJECT (self, "flow: %s", gst_flow_get_name (ret));
2171 static GstFlowReturn
2172 gst_sub_parse_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * buf)
2177 self = GST_SUBPARSE (parent);
2179 ret = handle_buffer (self, buf);
2185 gst_sub_parse_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
2187 GstSubParse *self = GST_SUBPARSE (parent);
2188 gboolean ret = FALSE;
2190 GST_LOG_OBJECT (self, "%s event", GST_EVENT_TYPE_NAME (event));
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);
2205 GST_DEBUG_OBJECT (self, "%s: force pushing of any remaining text",
2206 GST_EVENT_TYPE_NAME (event));
2208 gst_buffer_fill (buf, 0, term_chars, 3);
2209 gst_buffer_set_size (buf, 2);
2211 GST_BUFFER_OFFSET (buf) = self->offset;
2212 gst_sub_parse_chain (pad, parent, buf);
2214 ret = gst_pad_event_default (pad, parent, event);
2217 case GST_EVENT_SEGMENT:
2219 const GstSegment *s;
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");
2227 gst_event_unref (event);
2231 SUBPARSE_SEEK_LOCK (self);
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));
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) */
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;
2256 #ifdef TIZEN_FEATURE_SUBPARSE_MODIFICATION
2257 SUBPARSE_SEEK_UNLOCK (self);
2262 case GST_EVENT_FLUSH_START:
2264 self->flushing = TRUE;
2266 ret = gst_pad_event_default (pad, parent, event);
2269 case GST_EVENT_FLUSH_STOP:
2271 self->flushing = FALSE;
2273 ret = gst_pad_event_default (pad, parent, event);
2277 ret = gst_pad_event_default (pad, parent, event);
2285 static GstStateChangeReturn
2286 gst_sub_parse_change_state (GstElement * element, GstStateChange transition)
2288 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2289 GstSubParse *self = GST_SUBPARSE (element);
2291 switch (transition) {
2292 case GST_STATE_CHANGE_READY_TO_PAUSED:
2293 /* format detection will init the parser state */
2295 self->parser_type = GST_SUB_PARSE_FORMAT_UNKNOWN;
2296 #ifdef TIZEN_FEATURE_UPSTREAM
2297 self->strip_pango_markup = FALSE;
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);
2310 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
2311 if (ret == GST_STATE_CHANGE_FAILURE)
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;
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))
2336 static GstStaticCaps tmp_caps =
2337 GST_STATIC_CAPS ("application/x-subtitle-tmplayer");
2338 #define TMP_CAPS (gst_static_caps_get (&tmp_caps))
2340 static GstStaticCaps sub_caps = GST_STATIC_CAPS ("application/x-subtitle");
2341 #define MPL2_CAPS (gst_static_caps_get (&mpl2_caps))
2343 static GstStaticCaps smi_caps = GST_STATIC_CAPS ("application/x-subtitle-sami");
2344 #define SAMI_CAPS (gst_static_caps_get (&smi_caps))
2346 static GstStaticCaps dks_caps = GST_STATIC_CAPS ("application/x-subtitle-dks");
2347 #define DKS_CAPS (gst_static_caps_get (&dks_caps))
2349 static GstStaticCaps vtt_caps = GST_STATIC_CAPS ("application/x-subtitle-vtt");
2350 #define VTT_CAPS (gst_static_caps_get (&vtt_caps))
2352 static GstStaticCaps qttext_caps =
2353 GST_STATIC_CAPS ("application/x-subtitle-qttext");
2354 #define QTTEXT_CAPS (gst_static_caps_get (&qttext_caps))
2356 static GstStaticCaps lrc_caps = GST_STATIC_CAPS ("application/x-subtitle-lrc");
2357 #define LRC_CAPS (gst_static_caps_get (&lrc_caps))
2360 gst_subparse_type_find (GstTypeFind * tf, gpointer private)
2362 GstSubParseFormat format;
2366 gchar *encoding = NULL;
2369 if (!(data = gst_type_find_peek (tf, 0, 129)))
2372 /* make sure string passed to _autodetect() is NUL-terminated */
2373 str = g_malloc0 (129);
2374 memcpy (str, data, 128);
2376 if ((encoding = detect_encoding (str, 128)) != NULL) {
2377 gchar *converted_str;
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,
2385 g_clear_error (&err);
2388 str = converted_str;
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;
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";
2408 converted_str = gst_convert_to_utf8 (str, 128, enc, &tmp, NULL);
2409 if (converted_str != NULL) {
2411 str = converted_str;
2415 format = gst_sub_parse_data_format_autodetect (str);
2419 case GST_SUB_PARSE_FORMAT_MDVDSUB:
2420 GST_DEBUG ("MicroDVD format detected");
2423 case GST_SUB_PARSE_FORMAT_SUBRIP:
2424 GST_DEBUG ("SubRip format detected");
2427 case GST_SUB_PARSE_FORMAT_MPSUB:
2428 GST_DEBUG ("MPSub format detected");
2431 case GST_SUB_PARSE_FORMAT_SAMI:
2432 GST_DEBUG ("SAMI (time-based) format detected");
2435 case GST_SUB_PARSE_FORMAT_TMPLAYER:
2436 GST_DEBUG ("TMPlayer (time based) format detected");
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");
2447 case GST_SUB_PARSE_FORMAT_SUBVIEWER:
2448 GST_DEBUG ("SubViewer format detected");
2451 case GST_SUB_PARSE_FORMAT_DKS:
2452 GST_DEBUG ("DKS format detected");
2455 case GST_SUB_PARSE_FORMAT_QTTEXT:
2456 GST_DEBUG ("QTtext format detected");
2459 case GST_SUB_PARSE_FORMAT_LRC:
2460 GST_DEBUG ("LRC format detected");
2463 case GST_SUB_PARSE_FORMAT_VTT:
2464 GST_DEBUG ("WebVTT format detected");
2468 case GST_SUB_PARSE_FORMAT_UNKNOWN:
2469 GST_DEBUG ("no subtitle format detected");
2473 /* if we're here, it's ok */
2474 gst_type_find_suggest (tf, GST_TYPE_FIND_MAXIMUM, caps);
2478 plugin_init (GstPlugin * plugin)
2480 GST_DEBUG_CATEGORY_INIT (sub_parse_debug, "subparse", 0, ".sub parser");
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))
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)) {
2497 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
2501 plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)