0016332906b4a8c52eb9537058589f7bad300ee6
[vlc.git] / modules / codec / subsdec.c
1 /*****************************************************************************
2  * subsdec.c : text subtitle decoder
3  *****************************************************************************
4  * Copyright (C) 2000-2006 VLC authors and VideoLAN
5  *
6  * Authors: Gildas Bazin <gbazin@videolan.org>
7  *          Samuel Hocevar <sam@zoy.org>
8  *          Derk-Jan Hartman <hartman at videolan dot org>
9  *          Bernie Purcell <bitmap@videolan.org>
10  *
11  * This program is free software; you can redistribute it and/or modify it
12  * under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation; either version 2.1 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with this program; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 /*****************************************************************************
27  * Preamble
28  *****************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <limits.h>
34 #include <errno.h>
35 #include <ctype.h>
36
37 #include <vlc_common.h>
38 #include <vlc_plugin.h>
39 #include <vlc_codec.h>
40 #include <vlc_charset.h>
41 #include <vlc_xml.h>
42
43 #include "substext.h"
44
45 /*****************************************************************************
46  * Module descriptor.
47  *****************************************************************************/
48 static const char *const ppsz_encodings[] = {
49     "",
50     "system",
51     "UTF-8",
52     "UTF-16",
53     "UTF-16BE",
54     "UTF-16LE",
55     "GB18030",
56     "ISO-8859-15",
57     "Windows-1252",
58     "IBM850",
59     "ISO-8859-2",
60     "Windows-1250",
61     "ISO-8859-3",
62     "ISO-8859-10",
63     "Windows-1251",
64     "KOI8-R",
65     "KOI8-U",
66     "ISO-8859-6",
67     "Windows-1256",
68     "ISO-8859-7",
69     "Windows-1253",
70     "ISO-8859-8",
71     "Windows-1255",
72     "ISO-8859-9",
73     "Windows-1254",
74     "ISO-8859-11",
75     "Windows-874",
76     "ISO-8859-13",
77     "Windows-1257",
78     "ISO-8859-14",
79     "ISO-8859-16",
80     "ISO-2022-CN-EXT",
81     "EUC-CN",
82     "ISO-2022-JP-2",
83     "EUC-JP",
84     "Shift_JIS",
85     "CP949",
86     "ISO-2022-KR",
87     "Big5",
88     "ISO-2022-TW",
89     "Big5-HKSCS",
90     "VISCII",
91     "Windows-1258",
92 };
93
94 static const char *const ppsz_encoding_names[] = {
95     /* xgettext:
96       The character encoding name in parenthesis corresponds to that used for
97       the GetACP translation. "Windows-1252" applies to Western European
98       languages using the Latin alphabet. */
99     N_("Default (Windows-1252)"),
100     N_("System codeset"),
101     N_("Universal (UTF-8)"),
102     N_("Universal (UTF-16)"),
103     N_("Universal (big endian UTF-16)"),
104     N_("Universal (little endian UTF-16)"),
105     N_("Universal, Chinese (GB18030)"),
106
107   /* ISO 8859 and the likes */
108     /* 1 */
109     N_("Western European (Latin-9)"), /* mostly superset of Latin-1 */
110     N_("Western European (Windows-1252)"),
111     N_("Western European (IBM 00850)"),
112     /* 2 */
113     N_("Eastern European (Latin-2)"),
114     N_("Eastern European (Windows-1250)"),
115     /* 3 */
116     N_("Esperanto (Latin-3)"),
117     /* 4 */
118     N_("Nordic (Latin-6)"), /* Latin 6 supersedes Latin 4 */
119     /* 5 */
120     N_("Cyrillic (Windows-1251)"), /* ISO 8859-5 is not practically used */
121     N_("Russian (KOI8-R)"),
122     N_("Ukrainian (KOI8-U)"),
123     /* 6 */
124     N_("Arabic (ISO 8859-6)"),
125     N_("Arabic (Windows-1256)"),
126     /* 7 */
127     N_("Greek (ISO 8859-7)"),
128     N_("Greek (Windows-1253)"),
129     /* 8 */
130     N_("Hebrew (ISO 8859-8)"),
131     N_("Hebrew (Windows-1255)"),
132     /* 9 */
133     N_("Turkish (ISO 8859-9)"),
134     N_("Turkish (Windows-1254)"),
135     /* 10 -> 4 */
136     /* 11 */
137     N_("Thai (TIS 620-2533/ISO 8859-11)"),
138     N_("Thai (Windows-874)"),
139     /* 13 */
140     N_("Baltic (Latin-7)"),
141     N_("Baltic (Windows-1257)"),
142     /* 12 -> /dev/null */
143     /* 14 */
144     N_("Celtic (Latin-8)"),
145     /* 15 -> 1 */
146     /* 16 */
147     N_("South-Eastern European (Latin-10)"),
148   /* CJK families */
149     N_("Simplified Chinese (ISO-2022-CN-EXT)"),
150     N_("Simplified Chinese Unix (EUC-CN)"),
151     N_("Japanese (7-bits JIS/ISO-2022-JP-2)"),
152     N_("Japanese Unix (EUC-JP)"),
153     N_("Japanese (Shift JIS)"),
154     N_("Korean (EUC-KR/CP949)"),
155     N_("Korean (ISO-2022-KR)"),
156     N_("Traditional Chinese (Big5)"),
157     N_("Traditional Chinese Unix (EUC-TW)"),
158     N_("Hong-Kong Supplementary (HKSCS)"),
159   /* Other */
160     N_("Vietnamese (VISCII)"),
161     N_("Vietnamese (Windows-1258)"),
162 };
163
164 static const int  pi_justification[] = { -1, 0, 1, 2 };
165 static const char *const ppsz_justification_text[] = {
166     N_("Auto"),N_("Center"),N_("Left"),N_("Right")
167 };
168
169 #define ENCODING_TEXT N_("Subtitle text encoding")
170 #define ENCODING_LONGTEXT N_("Set the encoding used in text subtitles")
171 #define ALIGN_TEXT N_("Subtitle justification")
172 #define ALIGN_LONGTEXT N_("Set the justification of subtitles")
173 #define AUTODETECT_UTF8_TEXT N_("UTF-8 subtitle autodetection")
174 #define AUTODETECT_UTF8_LONGTEXT N_("This enables automatic detection of " \
175             "UTF-8 encoding within subtitle files.")
176
177 static int  OpenDecoder   ( vlc_object_t * );
178 static void CloseDecoder  ( vlc_object_t * );
179
180 vlc_module_begin ()
181     set_shortname( N_("Subtitles"))
182     set_description( N_("Text subtitle decoder") )
183     set_capability( "spu decoder", 50 )
184     set_callbacks( OpenDecoder, CloseDecoder )
185     set_category( CAT_INPUT )
186     set_subcategory( SUBCAT_INPUT_SCODEC )
187
188     add_integer( "subsdec-align", -1, ALIGN_TEXT, ALIGN_LONGTEXT,
189                  false )
190         change_integer_list( pi_justification, ppsz_justification_text )
191     add_string( "subsdec-encoding", "",
192                 ENCODING_TEXT, ENCODING_LONGTEXT, false )
193         change_string_list( ppsz_encodings, ppsz_encoding_names )
194     add_bool( "subsdec-autodetect-utf8", true,
195               AUTODETECT_UTF8_TEXT, AUTODETECT_UTF8_LONGTEXT, false )
196 vlc_module_end ()
197
198 /*****************************************************************************
199  * Local prototypes
200  *****************************************************************************/
201 #define NO_BREAKING_SPACE  "&#160;"
202
203 typedef struct
204 {
205     int                 i_align;          /* Subtitles alignment on the vout */
206
207     vlc_iconv_t         iconv_handle;            /* handle to iconv instance */
208     bool                b_autodetect_utf8;
209 } decoder_sys_t;
210
211
212 static int             DecodeBlock   ( decoder_t *, block_t * );
213 static subpicture_t   *ParseText     ( decoder_t *, block_t * );
214 static text_segment_t *ParseSubtitles(int *pi_align, const char * );
215
216 /*****************************************************************************
217  * OpenDecoder: probe the decoder and return score
218  *****************************************************************************
219  * Tries to launch a decoder and return score so that the interface is able
220  * to chose.
221  *****************************************************************************/
222 static int OpenDecoder( vlc_object_t *p_this )
223 {
224     decoder_t     *p_dec = (decoder_t*)p_this;
225     decoder_sys_t *p_sys;
226
227     switch( p_dec->fmt_in.i_codec )
228     {
229         case VLC_CODEC_SUBT:
230         case VLC_CODEC_ITU_T140:
231             break;
232         default:
233             return VLC_EGENERIC;
234     }
235
236     /* Allocate the memory needed to store the decoder's structure */
237     p_dec->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) );
238     if( p_sys == NULL )
239         return VLC_ENOMEM;
240
241     p_dec->pf_decode = DecodeBlock;
242     p_dec->fmt_out.i_codec = 0;
243
244     /* init of p_sys */
245     p_sys->i_align = -1;
246     p_sys->iconv_handle = (vlc_iconv_t)-1;
247     p_sys->b_autodetect_utf8 = false;
248
249     const char *encoding;
250     char *var = NULL;
251
252     /* First try demux-specified encoding */
253     if( p_dec->fmt_in.i_codec == VLC_CODEC_ITU_T140 )
254         encoding = "UTF-8"; /* IUT T.140 is always using UTF-8 */
255     else
256     if( p_dec->fmt_in.subs.psz_encoding && *p_dec->fmt_in.subs.psz_encoding )
257     {
258         encoding = p_dec->fmt_in.subs.psz_encoding;
259         msg_Dbg (p_dec, "trying demuxer-specified character encoding: %s",
260                  encoding);
261     }
262     else
263     {
264         /* Second, try configured encoding */
265         if ((var = var_InheritString (p_dec, "subsdec-encoding")) != NULL)
266         {
267             msg_Dbg (p_dec, "trying configured character encoding: %s", var);
268             if (!strcmp (var, "system"))
269             {
270                 free (var);
271                 var = NULL;
272                 encoding = "";
273                 /* ^ iconv() treats "" as nl_langinfo(CODESET) */
274             }
275             else
276                 encoding = var;
277         }
278         else
279         /* Third, try "local" encoding */
280         {
281         /* xgettext:
282            The Windows ANSI code page most commonly used for this language.
283            VLC uses this as a guess of the subtitle files character set
284            (if UTF-8 and UTF-16 autodetection fails).
285            Western European languages normally use "CP1252", which is a
286            Microsoft-variant of ISO 8859-1. That suits the Latin alphabet.
287            Other scripts use other code pages.
288
289            This MUST be a valid iconv character set. If unsure, please refer
290            the VideoLAN translators mailing list. */
291             encoding = vlc_pgettext("GetACP", "CP1252");
292             msg_Dbg (p_dec, "trying default character encoding: %s", encoding);
293         }
294
295         /* Check UTF-8 autodetection */
296         if (var_InheritBool (p_dec, "subsdec-autodetect-utf8"))
297         {
298             msg_Dbg (p_dec, "using automatic UTF-8 detection");
299             p_sys->b_autodetect_utf8 = true;
300         }
301     }
302
303     if (strcasecmp (encoding, "UTF-8") && strcasecmp (encoding, "utf8"))
304     {
305         p_sys->iconv_handle = vlc_iconv_open ("UTF-8", encoding);
306         if (p_sys->iconv_handle == (vlc_iconv_t)(-1))
307             msg_Err (p_dec, "cannot convert from %s: %s", encoding,
308                      vlc_strerror_c(errno));
309     }
310     free (var);
311
312     p_sys->i_align = var_InheritInteger( p_dec, "subsdec-align" );
313
314     return VLC_SUCCESS;
315 }
316
317 /****************************************************************************
318  * DecodeBlock: the whole thing
319  ****************************************************************************
320  * This function must be fed with complete subtitles units.
321  ****************************************************************************/
322 static int DecodeBlock( decoder_t *p_dec, block_t *p_block )
323 {
324     subpicture_t *p_spu;
325
326     if( p_block == NULL ) /* No Drain */
327         return VLCDEC_SUCCESS;
328
329     if( p_block->i_flags & BLOCK_FLAG_CORRUPTED )
330     {
331         block_Release( p_block );
332         return VLCDEC_SUCCESS;
333     }
334
335     p_spu = ParseText( p_dec, p_block );
336
337     block_Release( p_block );
338     if( p_spu != NULL )
339         decoder_QueueSub( p_dec, p_spu );
340     return VLCDEC_SUCCESS;
341 }
342
343 /*****************************************************************************
344  * CloseDecoder: clean up the decoder
345  *****************************************************************************/
346 static void CloseDecoder( vlc_object_t *p_this )
347 {
348     decoder_t *p_dec = (decoder_t *)p_this;
349     decoder_sys_t *p_sys = p_dec->p_sys;
350
351     if( p_sys->iconv_handle != (vlc_iconv_t)-1 )
352         vlc_iconv_close( p_sys->iconv_handle );
353
354     free( p_sys );
355 }
356
357 /*****************************************************************************
358  * ParseText: parse an text subtitle packet and send it to the video output
359  *****************************************************************************/
360 static subpicture_t *ParseText( decoder_t *p_dec, block_t *p_block )
361 {
362     decoder_sys_t *p_sys = p_dec->p_sys;
363     subpicture_t *p_spu = NULL;
364
365     if( p_block->i_flags & BLOCK_FLAG_CORRUPTED )
366         return NULL;
367
368     /* We cannot display a subpicture with no date */
369     if( p_block->i_pts == VLC_TICK_INVALID )
370     {
371         msg_Warn( p_dec, "subtitle without a date" );
372         return NULL;
373     }
374
375     /* Check validity of packet data */
376     /* An "empty" line containing only \0 can be used to force
377        and ephemer picture from the screen */
378     if( p_block->i_buffer < 1 )
379     {
380         msg_Warn( p_dec, "no subtitle data" );
381         return NULL;
382     }
383
384     char *psz_subtitle = NULL;
385
386     /* Should be resiliant against bad subtitles */
387     if( p_sys->iconv_handle == (vlc_iconv_t)-1 ||
388         p_sys->b_autodetect_utf8 )
389     {
390         psz_subtitle = malloc( p_block->i_buffer + 1 );
391         if( psz_subtitle == NULL )
392             return NULL;
393         memcpy( psz_subtitle, p_block->p_buffer, p_block->i_buffer );
394         psz_subtitle[p_block->i_buffer] = '\0';
395     }
396
397     if( p_sys->iconv_handle == (vlc_iconv_t)-1 )
398     {
399         if (EnsureUTF8( psz_subtitle ) == NULL)
400         {
401             msg_Err( p_dec, "failed to convert subtitle encoding.\n"
402                      "Try manually setting a character-encoding "
403                      "before you open the file." );
404         }
405     }
406     else
407     {
408         if( p_sys->b_autodetect_utf8 )
409         {
410             if( IsUTF8( psz_subtitle ) == NULL )
411             {
412                 msg_Dbg( p_dec, "invalid UTF-8 sequence: "
413                          "disabling UTF-8 subtitles autodetection" );
414                 p_sys->b_autodetect_utf8 = false;
415             }
416         }
417
418         if( !p_sys->b_autodetect_utf8 )
419         {
420             size_t inbytes_left = p_block->i_buffer;
421             size_t outbytes_left = 6 * inbytes_left;
422             char *psz_new_subtitle = xmalloc( outbytes_left + 1 );
423             char *psz_convert_buffer_out = psz_new_subtitle;
424             const char *psz_convert_buffer_in =
425                     psz_subtitle ? psz_subtitle : (char *)p_block->p_buffer;
426
427             size_t ret = vlc_iconv( p_sys->iconv_handle,
428                                     &psz_convert_buffer_in, &inbytes_left,
429                                     &psz_convert_buffer_out, &outbytes_left );
430
431             *psz_convert_buffer_out++ = '\0';
432             free( psz_subtitle );
433
434             if( ( ret == (size_t)(-1) ) || inbytes_left )
435             {
436                 free( psz_new_subtitle );
437                 msg_Err( p_dec, "failed to convert subtitle encoding.\n"
438                         "Try manually setting a character-encoding "
439                                 "before you open the file." );
440                 return NULL;
441             }
442
443             psz_subtitle = realloc( psz_new_subtitle,
444                                     psz_convert_buffer_out - psz_new_subtitle );
445             if( !psz_subtitle )
446                 psz_subtitle = psz_new_subtitle;
447         }
448     }
449
450     /* Create the subpicture unit */
451     p_spu = decoder_NewSubpictureText( p_dec );
452     if( !p_spu )
453     {
454         free( psz_subtitle );
455         return NULL;
456     }
457     p_spu->i_start    = p_block->i_pts;
458     p_spu->i_stop     = p_block->i_pts + p_block->i_length;
459     p_spu->b_ephemer  = (p_block->i_length == VLC_TICK_INVALID);
460     p_spu->b_absolute = false;
461
462     subtext_updater_sys_t *p_spu_sys = p_spu->updater.p_sys;
463
464     int i_inline_align = -1;
465     p_spu_sys->region.p_segments = ParseSubtitles( &i_inline_align, psz_subtitle );
466     free( psz_subtitle );
467     if( p_sys->i_align >= 0 ) /* bottom ; left, right or centered */
468     {
469         p_spu_sys->region.align = SUBPICTURE_ALIGN_BOTTOM | p_sys->i_align;
470         p_spu_sys->region.inner_align = p_sys->i_align;
471     }
472     else if( i_inline_align >= 0 )
473     {
474         p_spu_sys->region.align = i_inline_align;
475         p_spu_sys->region.inner_align = i_inline_align;
476     }
477     else /* default, bottom ; centered */
478     {
479         p_spu_sys->region.align = SUBPICTURE_ALIGN_BOTTOM;
480         p_spu_sys->region.inner_align = 0;
481     }
482
483     return p_spu;
484 }
485
486 static bool AppendCharacter( text_segment_t* p_segment, char c )
487 {
488     char* tmp;
489     if ( asprintf( &tmp, "%s%c", p_segment->psz_text ? p_segment->psz_text : "", c ) < 0 )
490         return false;
491     free( p_segment->psz_text );
492     p_segment->psz_text = tmp;
493     return true;
494 }
495
496 static bool AppendString( text_segment_t* p_segment, const char* psz_str )
497 {
498     char* tmp;
499     if ( asprintf( &tmp, "%s%s", p_segment->psz_text ? p_segment->psz_text : "", psz_str ) < 0 )
500         return false;
501     free( p_segment->psz_text );
502     p_segment->psz_text = tmp;
503     return true;
504 }
505
506 static char* ConsumeAttribute( const char** ppsz_subtitle, char** ppsz_attribute_value )
507 {
508     const char* psz_subtitle = *ppsz_subtitle;
509     char* psz_attribute_name;
510     *ppsz_attribute_value = NULL;
511
512     while (*psz_subtitle == ' ')
513         psz_subtitle++;
514
515     size_t attr_len = 0;
516     char delimiter;
517
518     while ( *psz_subtitle && isalpha( *psz_subtitle ) )
519     {
520         psz_subtitle++;
521         attr_len++;
522     }
523     if ( !*psz_subtitle || attr_len == 0 )
524         return NULL;
525     psz_attribute_name = malloc( attr_len + 1 );
526     if ( unlikely( !psz_attribute_name ) )
527         return NULL;
528     strncpy( psz_attribute_name, psz_subtitle - attr_len, attr_len );
529     psz_attribute_name[attr_len] = 0;
530
531     // Skip over to the attribute value
532     while ( *psz_subtitle && *psz_subtitle != '=' )
533         psz_subtitle++;
534     if ( !*psz_subtitle )
535     {
536         *ppsz_subtitle = psz_subtitle;
537         return psz_attribute_name;
538     }
539     // Skip the '=' sign
540     psz_subtitle++;
541
542     // Aknoledge the delimiter if any
543     while ( *psz_subtitle && isspace( *psz_subtitle) )
544         psz_subtitle++;
545
546     if ( *psz_subtitle == '\'' || *psz_subtitle == '"' )
547     {
548         // Save the delimiter and skip it
549         delimiter = *psz_subtitle;
550         psz_subtitle++;
551     }
552     else
553         delimiter = 0;
554
555     // Skip spaces, just in case
556     while ( *psz_subtitle && isspace( *psz_subtitle ) )
557         psz_subtitle++;
558
559     attr_len = 0;
560     while ( *psz_subtitle && ( ( delimiter != 0 && *psz_subtitle != delimiter ) ||
561                                ( delimiter == 0 && ( !isspace(*psz_subtitle) && *psz_subtitle != '>' ) ) ) )
562     {
563         psz_subtitle++;
564         attr_len++;
565     }
566     if ( attr_len == 0 )
567     {
568         *ppsz_subtitle = psz_subtitle;
569         return psz_attribute_name;
570     }
571     if ( unlikely( !( *ppsz_attribute_value = malloc( attr_len + 1 ) ) ) )
572     {
573         free( psz_attribute_name );
574         return NULL;
575     }
576     strncpy( *ppsz_attribute_value, psz_subtitle - attr_len, attr_len );
577     (*ppsz_attribute_value)[attr_len] = 0;
578     // Finally, skip over the final delimiter
579     if (delimiter != 0 && *psz_subtitle)
580         psz_subtitle++;
581     *ppsz_subtitle = psz_subtitle;
582     return psz_attribute_name;
583 }
584
585 // Returns the next tag and consume the string up to after the tag name, or
586 // returns NULL and doesn't advance if the angle bracket was not a tag opening
587 // For instance, if psz_subtitle == "<some_tag attribute=value>"
588 // GetTag will return "some_tag", and will advance up to the first 'a' in "attribute"
589 // The returned value must be freed.
590 static char* GetTag( const char** ppsz_subtitle, bool b_closing )
591 {
592     const char* psz_subtitle = *ppsz_subtitle;
593     if ( *psz_subtitle != '<' )
594         return NULL;
595     // Skip the '<'
596     psz_subtitle++;
597     if ( b_closing && *psz_subtitle == '/' )
598         psz_subtitle++;
599     // Skip potential spaces
600     while ( *psz_subtitle == ' ' )
601         psz_subtitle++;
602     // Now we need to verify if what comes next is a valid tag:
603     if ( !isalpha( *psz_subtitle ) )
604         return NULL;
605     size_t tag_size = 1;
606     while ( isalnum( psz_subtitle[tag_size] ) || psz_subtitle[tag_size] == '_' )
607         tag_size++;
608     char* psz_tagname = vlc_alloc( tag_size + 1, sizeof( *psz_tagname ) );
609     if ( unlikely( !psz_tagname ) )
610         return NULL;
611     strncpy( psz_tagname, psz_subtitle, tag_size );
612     psz_tagname[tag_size] = 0;
613     psz_subtitle += tag_size;
614     *ppsz_subtitle = psz_subtitle;
615     return psz_tagname;
616 }
617
618 static bool IsClosed( const char* psz_subtitle, const char* psz_tagname )
619 {
620     const char* psz_tagpos = strcasestr( psz_subtitle, psz_tagname );
621     if ( !psz_tagpos )
622         return false;
623     // Search for '</' and '>' immediatly before & after (minding the potential spaces)
624     const char* psz_endtag = psz_tagpos + strlen( psz_tagname );
625     while ( *psz_endtag == ' ' )
626         psz_endtag++;
627     if ( *psz_endtag != '>' )
628         return false;
629     // Skip back before the tag itself
630     psz_tagpos--;
631     while ( *psz_tagpos == ' ' && psz_tagpos > psz_subtitle )
632         psz_tagpos--;
633     if ( *psz_tagpos-- != '/' )
634         return false;
635     if ( *psz_tagpos != '<' )
636         return false;
637     return true;
638 }
639
640 typedef struct tag_stack tag_stack_t;
641 struct tag_stack
642 {
643     char* psz_tagname;
644     tag_stack_t *p_next;
645 };
646
647 static void AppendTag( tag_stack_t **pp_stack, char* psz_tagname )
648 {
649     tag_stack_t* p_elem = malloc( sizeof( *p_elem ) );
650     if ( unlikely( !p_elem ) )
651         return;
652     p_elem->p_next = *pp_stack;
653     p_elem->psz_tagname = psz_tagname;
654     *pp_stack = p_elem;
655 }
656
657 static bool HasTag( tag_stack_t **pp_stack, const char* psz_tagname )
658 {
659     tag_stack_t *p_prev = NULL;
660     for ( tag_stack_t* p_current = *pp_stack; p_current; p_current = p_current->p_next )
661     {
662         if ( !strcasecmp( psz_tagname, p_current->psz_tagname ) )
663         {
664             if ( p_current == *pp_stack )
665             {
666                 *pp_stack = p_current->p_next;
667             }
668             else
669             {
670                 p_prev->p_next = p_current->p_next;
671             }
672             free( p_current->psz_tagname );
673             free( p_current );
674             return true;
675         }
676         p_prev = p_current;
677     }
678     return false;
679 }
680
681 /*
682  * mini style stack implementation
683  */
684 typedef struct style_stack style_stack_t;
685 struct  style_stack
686 {
687     text_style_t* p_style;
688     style_stack_t* p_next;
689 };
690
691 static text_style_t* DuplicateAndPushStyle(style_stack_t** pp_stack)
692 {
693     text_style_t* p_dup = ( *pp_stack ) ? text_style_Duplicate( (*pp_stack)->p_style ) : text_style_Create( STYLE_NO_DEFAULTS );
694     if ( unlikely( !p_dup ) )
695         return NULL;
696     style_stack_t* p_entry = malloc( sizeof( *p_entry ) );
697     if ( unlikely( !p_entry ) )
698     {
699         text_style_Delete( p_dup );
700         return NULL;
701     }
702     // Give the style ownership to the segment.
703     p_entry->p_style = p_dup;
704     p_entry->p_next = *pp_stack;
705     *pp_stack = p_entry;
706     return p_dup;
707 }
708
709 static void PopStyle(style_stack_t** pp_stack)
710 {
711     style_stack_t* p_old = *pp_stack;
712     if ( !p_old )
713         return;
714     *pp_stack = p_old->p_next;
715     // Don't free the style, it is now owned by the text_segment_t
716     free( p_old );
717 }
718
719 static text_segment_t* NewTextSegmentPushStyle( text_segment_t* p_segment, style_stack_t** pp_stack )
720 {
721     text_segment_t* p_new = text_segment_New( NULL );
722     if ( unlikely( p_new == NULL ) )
723         return NULL;
724     text_style_t* p_style = DuplicateAndPushStyle( pp_stack );
725     p_new->style = p_style;
726     p_segment->p_next = p_new;
727     return p_new;
728 }
729
730 static text_segment_t* NewTextSegmentPopStyle( text_segment_t* p_segment, style_stack_t** pp_stack )
731 {
732     text_segment_t* p_new = text_segment_New( NULL );
733     if ( unlikely( p_new == NULL ) )
734         return NULL;
735     // We shouldn't have an empty stack since this happens when closing a tag,
736     // but better be safe than sorry if (/when) we encounter a broken subtitle file.
737     PopStyle( pp_stack );
738     text_style_t* p_dup = ( *pp_stack ) ? text_style_Duplicate( (*pp_stack)->p_style ) : text_style_Create( STYLE_NO_DEFAULTS );
739     p_new->style = p_dup;
740     p_segment->p_next = p_new;
741     return p_new;
742 }
743
744 static text_segment_t* ParseSubtitles( int *pi_align, const char *psz_subtitle )
745 {
746     text_segment_t* p_segment;
747     text_segment_t* p_first_segment;
748     style_stack_t* p_stack = NULL;
749     tag_stack_t* p_tag_stack = NULL;
750
751     //FIXME: Remove initial allocation? Might make the below code more complicated
752     p_first_segment = p_segment = text_segment_New( "" );
753
754     *pi_align = -1;
755
756     /* */
757     while( *psz_subtitle )
758     {
759         /* HTML extensions */
760         if( *psz_subtitle == '<' )
761         {
762             char *psz_tagname = GetTag( &psz_subtitle, false );
763             if ( psz_tagname != NULL )
764             {
765                 if( !strcasecmp( psz_tagname, "br" ) )
766                 {
767                     if ( !AppendCharacter( p_segment, '\n' ) )
768                     {
769                         free( psz_tagname );
770                         goto fail;
771                     }
772                 }
773                 else if( !strcasecmp( psz_tagname, "b" ) )
774                 {
775                     p_segment = NewTextSegmentPushStyle( p_segment, &p_stack );
776                     p_segment->style->i_style_flags |= STYLE_BOLD;
777                     p_segment->style->i_features |= STYLE_HAS_FLAGS;
778                 }
779                 else if( !strcasecmp( psz_tagname, "i" ) )
780                 {
781                     p_segment = NewTextSegmentPushStyle( p_segment, &p_stack );
782                     p_segment->style->i_style_flags |= STYLE_ITALIC;
783                     p_segment->style->i_features |= STYLE_HAS_FLAGS;
784                 }
785                 else if( !strcasecmp( psz_tagname, "u" ) )
786                 {
787                     p_segment = NewTextSegmentPushStyle( p_segment, &p_stack );
788                     p_segment->style->i_style_flags |= STYLE_UNDERLINE;
789                     p_segment->style->i_features |= STYLE_HAS_FLAGS;
790                 }
791                 else if( !strcasecmp( psz_tagname, "s" ) )
792                 {
793                     p_segment = NewTextSegmentPushStyle( p_segment, &p_stack );
794                     p_segment->style->i_style_flags |= STYLE_STRIKEOUT;
795                     p_segment->style->i_features |= STYLE_HAS_FLAGS;
796                 }
797                 else if( !strcasecmp( psz_tagname, "font" ) )
798                 {
799                     p_segment = NewTextSegmentPushStyle( p_segment, &p_stack );
800
801                     char* psz_attribute_name;
802                     char* psz_attribute_value;
803
804                     while( ( psz_attribute_name = ConsumeAttribute( &psz_subtitle, &psz_attribute_value ) ) )
805                     {
806                         if ( !psz_attribute_value )
807                         {
808                             free( psz_attribute_name );
809                             continue;
810                         }
811                         if ( !strcasecmp( psz_attribute_name, "face" ) )
812                         {
813                             free(p_segment->style->psz_fontname);
814                             p_segment->style->psz_fontname = psz_attribute_value;
815                             // We don't want to free the attribute value since it has become our fontname
816                             psz_attribute_value = NULL;
817                         }
818                         else if ( !strcasecmp( psz_attribute_name, "family" ) )
819                         {
820                             free(p_segment->style->psz_monofontname);
821                             p_segment->style->psz_monofontname = psz_attribute_value;
822                             psz_attribute_value = NULL;
823                         }
824                         else if ( !strcasecmp( psz_attribute_name, "size" ) )
825                         {
826                             int size = atoi( psz_attribute_value );
827                             if( size )
828                             {
829                                 p_segment->style->i_font_size = size;
830                                 p_segment->style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE *
831                                         STYLE_DEFAULT_FONT_SIZE / p_segment->style->i_font_size;
832                             }
833                         }
834                         else if ( !strcasecmp( psz_attribute_name, "color" ) )
835                         {
836                             p_segment->style->i_font_color = vlc_html_color( psz_attribute_value, NULL );
837                             p_segment->style->i_features |= STYLE_HAS_FONT_COLOR;
838                         }
839                         else if ( !strcasecmp( psz_attribute_name, "outline-color" ) )
840                         {
841                             p_segment->style->i_outline_color = vlc_html_color( psz_attribute_value, NULL );
842                             p_segment->style->i_features |= STYLE_HAS_OUTLINE_COLOR;
843                         }
844                         else if ( !strcasecmp( psz_attribute_name, "shadow-color" ) )
845                         {
846                             p_segment->style->i_shadow_color = vlc_html_color( psz_attribute_value, NULL );
847                             p_segment->style->i_features |= STYLE_HAS_SHADOW_COLOR;
848                         }
849                         else if ( !strcasecmp( psz_attribute_name, "outline-level" ) )
850                         {
851                             p_segment->style->i_outline_width = atoi( psz_attribute_value );
852                         }
853                         else if ( !strcasecmp( psz_attribute_name, "shadow-level" ) )
854                         {
855                             p_segment->style->i_shadow_width = atoi( psz_attribute_value );
856                         }
857                         else if ( !strcasecmp( psz_attribute_name, "back-color" ) )
858                         {
859                             p_segment->style->i_background_color = vlc_html_color( psz_attribute_value, NULL );
860                             p_segment->style->i_features |= STYLE_HAS_BACKGROUND_COLOR;
861                         }
862                         else if ( !strcasecmp( psz_attribute_name, "alpha" ) )
863                         {
864                             p_segment->style->i_font_alpha = atoi( psz_attribute_value );
865                             p_segment->style->i_features |= STYLE_HAS_FONT_ALPHA;
866                         }
867
868                         free( psz_attribute_name );
869                         free( psz_attribute_value );
870                     }
871                 }
872                 else
873                 {
874                     // This is an unknown tag. We need to hide it if it's properly closed, and display it otherwise
875                     if ( !IsClosed( psz_subtitle, psz_tagname ) )
876                     {
877                         AppendCharacter( p_segment, '<' );
878                         AppendString( p_segment, psz_tagname );
879                         AppendCharacter( p_segment, '>' );
880                     }
881                     else
882                     {
883                         AppendTag( &p_tag_stack, psz_tagname );
884                         // We don't want to free the tagname now, it will be freed when the tag
885                         // gets poped from the stack.
886                         psz_tagname = NULL;
887                     }
888                     // In any case, fall through and skip to the closing tag.
889                 }
890                 // Skip potential spaces & end tag
891                 while ( *psz_subtitle && *psz_subtitle != '>' )
892                     psz_subtitle++;
893                 if ( *psz_subtitle == '>' )
894                     psz_subtitle++;
895
896                 free( psz_tagname );
897             }
898             else if( !strncmp( psz_subtitle, "</", 2 ))
899             {
900                 char* psz_closetagname = GetTag( &psz_subtitle, true );
901                 if ( psz_closetagname != NULL )
902                 {
903                     if ( !strcasecmp( psz_closetagname, "b" ) ||
904                          !strcasecmp( psz_closetagname, "i" ) ||
905                          !strcasecmp( psz_closetagname, "u" ) ||
906                          !strcasecmp( psz_closetagname, "s" ) ||
907                          !strcasecmp( psz_closetagname, "font" ) )
908                     {
909                         // A closing tag for one of the tags we handle, meaning
910                         // we pushed a style onto the stack earlier
911                         p_segment = NewTextSegmentPopStyle( p_segment, &p_stack );
912                     }
913                     else
914                     {
915                         // Unknown closing tag. If it is closing an unknown tag, ignore it. Otherwise, display it
916                         if ( !HasTag( &p_tag_stack, psz_closetagname ) )
917                         {
918                             AppendString( p_segment, "</" );
919                             AppendString( p_segment, psz_closetagname );
920                             AppendCharacter( p_segment, '>' );
921                         }
922                     }
923                     while ( *psz_subtitle == ' ' )
924                         psz_subtitle++;
925                     if ( *psz_subtitle == '>' )
926                         psz_subtitle++;
927                     free( psz_closetagname );
928                 }
929                 else
930                 {
931                     /**
932                       * This doesn't appear to be a valid tag closing syntax.
933                       * Simply append the text
934                       */
935                     AppendString( p_segment, "</" );
936                     psz_subtitle += 2;
937                 }
938             }
939             else
940             {
941                 /* We have an unknown tag, just append it, and move on.
942                  * The rest of the string won't be recognized as a tag, and
943                  * we will ignore unknown closing tag
944                  */
945                 AppendCharacter( p_segment, '<' );
946                 psz_subtitle++;
947             }
948         }
949         /* SSA extensions */
950         else if( psz_subtitle[0] == '{' && psz_subtitle[1] == '\\' &&
951                  strchr( psz_subtitle, '}' ) )
952         {
953             /* Check for forced alignment */
954             if( *pi_align < 0 &&
955                 !strncmp( psz_subtitle, "{\\an", 4 ) && psz_subtitle[4] >= '1' && psz_subtitle[4] <= '9' && psz_subtitle[5] == '}' )
956             {
957                 static const int pi_vertical[3] = { SUBPICTURE_ALIGN_BOTTOM, 0, SUBPICTURE_ALIGN_TOP };
958                 static const int pi_horizontal[3] = { SUBPICTURE_ALIGN_LEFT, 0, SUBPICTURE_ALIGN_RIGHT };
959                 const int i_id = psz_subtitle[4] - '1';
960
961                 *pi_align = pi_vertical[i_id/3] | pi_horizontal[i_id%3];
962             }
963             /* TODO fr -> rotation */
964
965             /* Hide {\stupidity} */
966             psz_subtitle = strchr( psz_subtitle, '}' ) + 1;
967         }
968         /* MicroDVD extensions */
969         /* FIXME:
970          *  - Currently, we don't do difference between X and x, and we should:
971          *    Capital Letters applies to the whole text and not one line
972          *  - We don't support Position and Coordinates
973          *  - We don't support the DEFAULT flag (HEADER)
974          */
975
976         else if( psz_subtitle[0] == '{' && psz_subtitle[1] != 0 &&
977                  psz_subtitle[2] == ':' && strchr( &psz_subtitle[2], '}' ) )
978         {
979             const char *psz_tag_end = strchr( &psz_subtitle[2], '}' );
980             size_t i_len = psz_tag_end - &psz_subtitle[3];
981
982             if( psz_subtitle[1] == 'Y' || psz_subtitle[1] == 'y' )
983             {
984                 if( psz_subtitle[3] == 'i' )
985                 {
986                     p_segment = NewTextSegmentPushStyle( p_segment, &p_stack );
987                     p_segment->style->i_style_flags |= STYLE_ITALIC;
988                     p_segment->style->i_features |= STYLE_HAS_FLAGS;
989                     psz_subtitle++;
990                 }
991                 if( psz_subtitle[3] == 'b' )
992                 {
993                     p_segment = NewTextSegmentPushStyle( p_segment, &p_stack );
994                     p_segment->style->i_style_flags |= STYLE_BOLD;
995                     p_segment->style->i_features |= STYLE_HAS_FLAGS;
996                     psz_subtitle++;
997                 }
998                 if( psz_subtitle[3] == 'u' )
999                 {
1000                     p_segment = NewTextSegmentPushStyle( p_segment, &p_stack );
1001                     p_segment->style->i_style_flags |= STYLE_UNDERLINE;
1002                     p_segment->style->i_features |= STYLE_HAS_FLAGS;
1003                     psz_subtitle++;
1004                 }
1005             }
1006             else if( (psz_subtitle[1] == 'C' || psz_subtitle[1] == 'c' )
1007                     && psz_subtitle[3] == '$' && i_len >= 7 )
1008             {
1009                 /* Yes, they use BBGGRR, instead of RRGGBB */
1010                 char psz_color[7];
1011                 psz_color[0] = psz_subtitle[8]; psz_color[1] = psz_subtitle[9];
1012                 psz_color[2] = psz_subtitle[6]; psz_color[3] = psz_subtitle[7];
1013                 psz_color[4] = psz_subtitle[4]; psz_color[5] = psz_subtitle[5];
1014                 psz_color[6] = '\0';
1015                 p_segment = NewTextSegmentPushStyle( p_segment, &p_stack );
1016                 p_segment->style->i_font_color = vlc_html_color( psz_color, NULL );
1017                 p_segment->style->i_features |= STYLE_HAS_FONT_COLOR;
1018             }
1019             else if( psz_subtitle[1] == 'F' || psz_subtitle[1] == 'f' )
1020             {
1021                 p_segment = NewTextSegmentPushStyle( p_segment, &p_stack );
1022                 free(p_segment->style->psz_fontname);
1023                 p_segment->style->psz_fontname = strndup( &psz_subtitle[3], i_len );
1024             }
1025             else if( psz_subtitle[1] == 'S' || psz_subtitle[1] == 's' )
1026             {
1027                 int size = atoi( &psz_subtitle[3] );
1028                 if( size )
1029                 {
1030                     p_segment = NewTextSegmentPushStyle( p_segment, &p_stack );
1031                     p_segment->style->i_font_size = size;
1032                     p_segment->style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE *
1033                                 STYLE_DEFAULT_FONT_SIZE / p_segment->style->i_font_size;
1034
1035                 }
1036             }
1037             /* Currently unsupported since we don't have access to the i_align flag here
1038             else if( psz_subtitle[1] == 'P' )
1039             {
1040                 if( psz_subtitle[3] == "1" )
1041                     i_align = SUBPICTURE_ALIGN_TOP;
1042                 else if( psz_subtitle[3] == "0" )
1043                     i_align = SUBPICTURE_ALIGN_BOTTOM;
1044             } */
1045             // Hide other {x:y} atrocities, notably {o:x}
1046             psz_subtitle = psz_tag_end + 1;
1047         }
1048         else
1049         {
1050             if( *psz_subtitle == '\n' || !strncasecmp( psz_subtitle, "\\n", 2 ) )
1051             {
1052                 if ( !AppendCharacter( p_segment, '\n' ) )
1053                     goto fail;
1054                 if ( *psz_subtitle == '\n' )
1055                     psz_subtitle++;
1056                 else
1057                     psz_subtitle += 2;
1058             }
1059             else if( !strncasecmp( psz_subtitle, "\\h", 2 ) )
1060             {
1061                 if ( !AppendString( p_segment, "\xC2\xA0" ) )
1062                     goto fail;
1063                 psz_subtitle += 2;
1064             }
1065             else
1066             {
1067                 //FIXME: Highly inneficient
1068                 AppendCharacter( p_segment, *psz_subtitle );
1069                 psz_subtitle++;
1070             }
1071         }
1072     }
1073     while ( p_stack )
1074         PopStyle( &p_stack );
1075     while ( p_tag_stack )
1076     {
1077         tag_stack_t *p_tag = p_tag_stack;
1078         p_tag_stack = p_tag_stack->p_next;
1079         free( p_tag->psz_tagname );
1080         free( p_tag );
1081     }
1082
1083     return p_first_segment;
1084
1085 fail:
1086     text_segment_ChainDelete( p_first_segment );
1087     return NULL;
1088 }