subsdec.c
上传用户:kjfoods
上传日期:2020-07-06
资源大小:29949k
文件大小:34k
源码类别:

midi

开发平台:

Unix_Linux

  1. /*****************************************************************************
  2.  * subsdec.c : text subtitles decoder
  3.  *****************************************************************************
  4.  * Copyright (C) 2000-2006 the VideoLAN team
  5.  * $Id: 6a3612f1d3ec233e382b2b80f19d2336e8b79636 $
  6.  *
  7.  * Authors: Gildas Bazin <gbazin@videolan.org>
  8.  *          Samuel Hocevar <sam@zoy.org>
  9.  *          Derk-Jan Hartman <hartman at videolan dot org>
  10.  *          Bernie Purcell <bitmap@videolan.org>
  11.  *
  12.  * This program is free software; you can redistribute it and/or modify
  13.  * it under the terms of the GNU General Public License as published by
  14.  * the Free Software Foundation; either version 2 of the License, or
  15.  * (at your option) any later version.
  16.  *
  17.  * This program is distributed in the hope that it will be useful,
  18.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20.  * GNU General Public License for more details.
  21.  *
  22.  * You should have received a copy of the GNU General Public License
  23.  * along with this program; if not, write to the Free Software
  24.  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  25.  *****************************************************************************/
  26. /*****************************************************************************
  27.  * Preamble
  28.  *****************************************************************************/
  29. #ifdef HAVE_CONFIG_H
  30. # include "config.h"
  31. #endif
  32. #include "subsdec.h"
  33. #include <vlc_plugin.h>
  34. /*****************************************************************************
  35.  * Local prototypes
  36.  *****************************************************************************/
  37. static int  OpenDecoder   ( vlc_object_t * );
  38. static void CloseDecoder  ( vlc_object_t * );
  39. static subpicture_t   *DecodeBlock   ( decoder_t *, block_t ** );
  40. static subpicture_t   *ParseText     ( decoder_t *, block_t * );
  41. static char           *StripTags      ( char * );
  42. static char           *CreateHtmlSubtitle( int *pi_align, char * );
  43. /*****************************************************************************
  44.  * Module descriptor.
  45.  *****************************************************************************/
  46. static const char *const ppsz_encodings[] = {
  47.     "",
  48.     "UTF-8",
  49.     "UTF-16",
  50.     "UTF-16BE",
  51.     "UTF-16LE",
  52.     "GB18030",
  53.     "ISO-8859-15",
  54.     "Windows-1252",
  55.     "ISO-8859-2",
  56.     "Windows-1250",
  57.     "ISO-8859-3",
  58.     "ISO-8859-10",
  59.     "Windows-1251",
  60.     "KOI8-R",
  61.     "KOI8-U",
  62.     "ISO-8859-6",
  63.     "Windows-1256",
  64.     "ISO-8859-7",
  65.     "Windows-1253",
  66.     "ISO-8859-8",
  67.     "Windows-1255",
  68.     "ISO-8859-9",
  69.     "Windows-1254",
  70.     "ISO-8859-11",
  71.     "Windows-874",
  72.     "ISO-8859-13",
  73.     "Windows-1257",
  74.     "ISO-8859-14",
  75.     "ISO-8859-16",
  76.     "ISO-2022-CN-EXT",
  77.     "EUC-CN",
  78.     "ISO-2022-JP-2",
  79.     "EUC-JP",
  80.     "Shift_JIS",
  81.     "CP949",
  82.     "ISO-2022-KR",
  83.     "Big5",
  84.     "ISO-2022-TW",
  85.     "Big5-HKSCS",
  86.     "VISCII",
  87.     "Windows-1258",
  88. };
  89. static const char *const ppsz_encoding_names[] = {
  90.     N_("Auto"),
  91.     N_("Universal (UTF-8)"),
  92.     N_("Universal (UTF-16)"),
  93.     N_("Universal (big endian UTF-16)"),
  94.     N_("Universal (little endian UTF-16)"),
  95.     N_("Universal, Chinese (GB18030)"),
  96.   /* ISO 8859 and the likes */
  97.     /* 1 */
  98.     N_("Western European (Latin-9)"), /* mostly superset of Latin-1 */
  99.     N_("Western European (Windows-1252)"),
  100.     /* 2 */
  101.     N_("Eastern European (Latin-2)"),
  102.     N_("Eastern European (Windows-1250)"),
  103.     /* 3 */
  104.     N_("Esperanto (Latin-3)"),
  105.     /* 4 */
  106.     N_("Nordic (Latin-6)"), /* Latin 6 supersedes Latin 4 */
  107.     /* 5 */
  108.     N_("Cyrillic (Windows-1251)"), /* ISO 8859-5 is not practically used */
  109.     N_("Russian (KOI8-R)"),
  110.     N_("Ukrainian (KOI8-U)"),
  111.     /* 6 */
  112.     N_("Arabic (ISO 8859-6)"),
  113.     N_("Arabic (Windows-1256)"),
  114.     /* 7 */
  115.     N_("Greek (ISO 8859-7)"),
  116.     N_("Greek (Windows-1253)"),
  117.     /* 8 */
  118.     N_("Hebrew (ISO 8859-8)"),
  119.     N_("Hebrew (Windows-1255)"),
  120.     /* 9 */
  121.     N_("Turkish (ISO 8859-9)"),
  122.     N_("Turkish (Windows-1254)"),
  123.     /* 10 -> 4 */
  124.     /* 11 */
  125.     N_("Thai (TIS 620-2533/ISO 8859-11)"),
  126.     N_("Thai (Windows-874)"),
  127.     /* 13 */
  128.     N_("Baltic (Latin-7)"),
  129.     N_("Baltic (Windows-1257)"),
  130.     /* 12 -> /dev/null */
  131.     /* 14 */
  132.     N_("Celtic (Latin-8)"),
  133.     /* 15 -> 1 */
  134.     /* 16 */
  135.     N_("South-Eastern European (Latin-10)"),
  136.   /* CJK families */
  137.     N_("Simplified Chinese (ISO-2022-CN-EXT)"),
  138.     N_("Simplified Chinese Unix (EUC-CN)"),
  139.     N_("Japanese (7-bits JIS/ISO-2022-JP-2)"),
  140.     N_("Japanese Unix (EUC-JP)"),
  141.     N_("Japanese (Shift JIS)"),
  142.     N_("Korean (EUC-KR/CP949)"),
  143.     N_("Korean (ISO-2022-KR)"),
  144.     N_("Traditional Chinese (Big5)"),
  145.     N_("Traditional Chinese Unix (EUC-TW)"),
  146.     N_("Hong-Kong Supplementary (HKSCS)"),
  147.   /* Other */
  148.     N_("Vietnamese (VISCII)"),
  149.     N_("Vietnamese (Windows-1258)"),
  150. };
  151. /*
  152. SSA supports charset selection.
  153. The following known charsets are used:
  154. 0 = Ansi - Western European
  155. 1 = default
  156. 2 = symbol
  157. 3 = invalid
  158. 77 = Mac
  159. 128 = Japanese (Shift JIS)
  160. 129 = Hangul
  161. 130 = Johab
  162. 134 = GB2312 Simplified Chinese
  163. 136 = Big5 Traditional Chinese
  164. 161 = Greek
  165. 162 = Turkish
  166. 163 = Vietnamese
  167. 177 = Hebrew
  168. 178 = Arabic
  169. 186 = Baltic
  170. 204 = Russian (Cyrillic)
  171. 222 = Thai
  172. 238 = Eastern European
  173. 254 = PC 437
  174. */
  175. static const int  pi_justification[] = { 0, 1, 2 };
  176. static const char *const ppsz_justification_text[] = {
  177.     N_("Center"),N_("Left"),N_("Right")};
  178. #define ENCODING_TEXT N_("Subtitles text encoding")
  179. #define ENCODING_LONGTEXT N_("Set the encoding used in text subtitles")
  180. #define ALIGN_TEXT N_("Subtitles justification")
  181. #define ALIGN_LONGTEXT N_("Set the justification of subtitles")
  182. #define AUTODETECT_UTF8_TEXT N_("UTF-8 subtitles autodetection")
  183. #define AUTODETECT_UTF8_LONGTEXT N_("This enables automatic detection of " 
  184.             "UTF-8 encoding within subtitles files.")
  185. #define FORMAT_TEXT N_("Formatted Subtitles")
  186. #define FORMAT_LONGTEXT N_("Some subtitle formats allow for text formatting. " 
  187.  "VLC partly implements this, but you can choose to disable all formatting.")
  188. vlc_module_begin ()
  189.     set_shortname( N_("Subtitles"))
  190.     set_description( N_("Text subtitles decoder") )
  191.     set_capability( "decoder", 50 )
  192.     set_callbacks( OpenDecoder, CloseDecoder )
  193.     set_category( CAT_INPUT )
  194.     set_subcategory( SUBCAT_INPUT_SCODEC )
  195.     add_integer( "subsdec-align", 0, NULL, ALIGN_TEXT, ALIGN_LONGTEXT,
  196.                  false )
  197.         change_integer_list( pi_justification, ppsz_justification_text, NULL )
  198.     add_string( "subsdec-encoding", "", NULL,
  199.                 ENCODING_TEXT, ENCODING_LONGTEXT, false )
  200.         change_string_list( ppsz_encodings, ppsz_encoding_names, 0 )
  201.     add_bool( "subsdec-autodetect-utf8", true, NULL,
  202.               AUTODETECT_UTF8_TEXT, AUTODETECT_UTF8_LONGTEXT, false )
  203.     add_bool( "subsdec-formatted", true, NULL, FORMAT_TEXT, FORMAT_LONGTEXT,
  204.                  false )
  205. vlc_module_end ()
  206. /*****************************************************************************
  207.  * OpenDecoder: probe the decoder and return score
  208.  *****************************************************************************
  209.  * Tries to launch a decoder and return score so that the interface is able
  210.  * to chose.
  211.  *****************************************************************************/
  212. static int OpenDecoder( vlc_object_t *p_this )
  213. {
  214.     decoder_t     *p_dec = (decoder_t*)p_this;
  215.     decoder_sys_t *p_sys;
  216.     vlc_value_t    val;
  217.     switch( p_dec->fmt_in.i_codec )
  218.     {
  219.         case VLC_FOURCC('s','u','b','t'):
  220.         case VLC_FOURCC('s','s','a',' '):
  221.         case VLC_FOURCC('t','1','4','0'):
  222.             break;
  223.         default:
  224.             return VLC_EGENERIC;
  225.     }
  226.     p_dec->pf_decode_sub = DecodeBlock;
  227.     p_dec->fmt_out.i_cat = SPU_ES;
  228.     p_dec->fmt_out.i_codec = 0;
  229.     /* Allocate the memory needed to store the decoder's structure */
  230.     p_dec->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) );
  231.     if( p_sys == NULL )
  232.         return VLC_ENOMEM;
  233.     /* init of p_sys */
  234.     p_sys->i_align = 0;
  235.     p_sys->iconv_handle = (vlc_iconv_t)-1;
  236.     p_sys->b_autodetect_utf8 = false;
  237.     p_sys->b_ass = false;
  238.     p_sys->i_original_height = -1;
  239.     p_sys->i_original_width = -1;
  240.     TAB_INIT( p_sys->i_ssa_styles, p_sys->pp_ssa_styles );
  241.     TAB_INIT( p_sys->i_images, p_sys->pp_images );
  242.     char *psz_charset = NULL;
  243.     /* First try demux-specified encoding */
  244.     if( p_dec->fmt_in.i_codec == VLC_FOURCC('t','1','4','0') )
  245.         psz_charset = strdup( "UTF-8" ); /* IUT T.140 is always using UTF-8 */
  246.     else
  247.     if( p_dec->fmt_in.subs.psz_encoding && *p_dec->fmt_in.subs.psz_encoding )
  248.     {
  249.         psz_charset = strdup (p_dec->fmt_in.subs.psz_encoding);
  250.         msg_Dbg (p_dec, "trying demuxer-specified character encoding: %s",
  251.                  p_dec->fmt_in.subs.psz_encoding ?
  252.                  p_dec->fmt_in.subs.psz_encoding : "not specified");
  253.     }
  254.     /* Second, try configured encoding */
  255.     if (psz_charset == NULL)
  256.     {
  257.         psz_charset = var_CreateGetNonEmptyString (p_dec, "subsdec-encoding");
  258.         msg_Dbg (p_dec, "trying configured character encoding: %s",
  259.                  psz_charset ? psz_charset : "not specified");
  260.     }
  261.     /* Third, try "local" encoding with optional UTF-8 autodetection */
  262.     if (psz_charset == NULL)
  263.     {
  264.         psz_charset = strdup (GetFallbackEncoding ());
  265.         msg_Dbg (p_dec, "trying default character encoding: %s",
  266.                  psz_charset ? psz_charset : "not specified");
  267.         if (var_CreateGetBool (p_dec, "subsdec-autodetect-utf8"))
  268.         {
  269.             msg_Dbg (p_dec, "using automatic UTF-8 detection");
  270.             p_sys->b_autodetect_utf8 = true;
  271.         }
  272.     }
  273.     /* Forth, don't do character decoding, i.e. assume UTF-8 */
  274.     if (psz_charset == NULL)
  275.     {
  276.         psz_charset = strdup ("UTF-8");
  277.         msg_Dbg (p_dec, "using UTF-8 character encoding" );
  278.     }
  279.     if ((psz_charset != NULL)
  280.      && strcasecmp (psz_charset, "UTF-8")
  281.      && strcasecmp (psz_charset, "utf8"))
  282.     {
  283.         p_sys->iconv_handle = vlc_iconv_open ("UTF-8", psz_charset);
  284.         if (p_sys->iconv_handle == (vlc_iconv_t)(-1))
  285.             msg_Err (p_dec, "cannot convert from %s: %m", psz_charset);
  286.     }
  287.     free (psz_charset);
  288.     var_Create( p_dec, "subsdec-align", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
  289.     var_Get( p_dec, "subsdec-align", &val );
  290.     p_sys->i_align = val.i_int;
  291.     if( p_dec->fmt_in.i_codec == VLC_FOURCC('s','s','a',' ')
  292.      && var_CreateGetBool( p_dec, "subsdec-formatted" ) )
  293.     {
  294.         if( p_dec->fmt_in.i_extra > 0 )
  295.             ParseSSAHeader( p_dec );
  296.     }
  297.     return VLC_SUCCESS;
  298. }
  299. /****************************************************************************
  300.  * DecodeBlock: the whole thing
  301.  ****************************************************************************
  302.  * This function must be fed with complete subtitles units.
  303.  ****************************************************************************/
  304. static subpicture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block )
  305. {
  306.     subpicture_t *p_spu;
  307.     block_t *p_block;
  308.     if( !pp_block || *pp_block == NULL )
  309.         return NULL;
  310.     p_block = *pp_block;
  311.     if( p_block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED) )
  312.     {
  313.         block_Release( p_block );
  314.         return NULL;
  315.     }
  316.     p_spu = ParseText( p_dec, p_block );
  317.     block_Release( p_block );
  318.     *pp_block = NULL;
  319.     return p_spu;
  320. }
  321. /*****************************************************************************
  322.  * CloseDecoder: clean up the decoder
  323.  *****************************************************************************/
  324. static void CloseDecoder( vlc_object_t *p_this )
  325. {
  326.     decoder_t *p_dec = (decoder_t *)p_this;
  327.     decoder_sys_t *p_sys = p_dec->p_sys;
  328.     if( p_sys->iconv_handle != (vlc_iconv_t)-1 )
  329.         vlc_iconv_close( p_sys->iconv_handle );
  330.     if( p_sys->pp_ssa_styles )
  331.     {
  332.         int i;
  333.         for( i = 0; i < p_sys->i_ssa_styles; i++ )
  334.         {
  335.             if( !p_sys->pp_ssa_styles[i] )
  336.                 continue;
  337.             free( p_sys->pp_ssa_styles[i]->psz_stylename );
  338.             free( p_sys->pp_ssa_styles[i]->font_style.psz_fontname );
  339.             free( p_sys->pp_ssa_styles[i] );
  340.         }
  341.         TAB_CLEAN( p_sys->i_ssa_styles, p_sys->pp_ssa_styles );
  342.     }
  343.     if( p_sys->pp_images )
  344.     {
  345.         int i;
  346.         for( i = 0; i < p_sys->i_images; i++ )
  347.         {
  348.             if( !p_sys->pp_images[i] )
  349.                 continue;
  350.             if( p_sys->pp_images[i]->p_pic )
  351.                 picture_Release( p_sys->pp_images[i]->p_pic );
  352.             free( p_sys->pp_images[i]->psz_filename );
  353.             free( p_sys->pp_images[i] );
  354.         }
  355.         TAB_CLEAN( p_sys->i_images, p_sys->pp_images );
  356.     }
  357.     free( p_sys );
  358. }
  359. /*****************************************************************************
  360.  * ParseText: parse an text subtitle packet and send it to the video output
  361.  *****************************************************************************/
  362. static subpicture_t *ParseText( decoder_t *p_dec, block_t *p_block )
  363. {
  364.     decoder_sys_t *p_sys = p_dec->p_sys;
  365.     subpicture_t *p_spu = NULL;
  366.     char *psz_subtitle = NULL;
  367.     video_format_t fmt;
  368.     /* We cannot display a subpicture with no date */
  369.     if( p_block->i_pts == 0 )
  370.     {
  371.         msg_Warn( p_dec, "subtitle without a date" );
  372.         return NULL;
  373.     }
  374.     /* Check validity of packet data */
  375.     /* An "empty" line containing only  can be used to force
  376.        and ephemer picture from the screen */
  377.     if( p_block->i_buffer < 1 )
  378.     {
  379.         msg_Warn( p_dec, "no subtitle data" );
  380.         return NULL;
  381.     }
  382.     /* Should be resiliant against bad subtitles */
  383.     psz_subtitle = strndup( (const char *)p_block->p_buffer,
  384.                             p_block->i_buffer );
  385.     if( psz_subtitle == NULL )
  386.         return NULL;
  387.     if( p_sys->iconv_handle == (vlc_iconv_t)-1 )
  388.     {
  389.         if (EnsureUTF8( psz_subtitle ) == NULL)
  390.         {
  391.             msg_Err( p_dec, "failed to convert subtitle encoding.n"
  392.                      "Try manually setting a character-encoding "
  393.                      "before you open the file." );
  394.         }
  395.     }
  396.     else
  397.     {
  398.         if( p_sys->b_autodetect_utf8 )
  399.         {
  400.             if( IsUTF8( psz_subtitle ) == NULL )
  401.             {
  402.                 msg_Dbg( p_dec, "invalid UTF-8 sequence: "
  403.                          "disabling UTF-8 subtitles autodetection" );
  404.                 p_sys->b_autodetect_utf8 = false;
  405.             }
  406.         }
  407.         if( !p_sys->b_autodetect_utf8 )
  408.         {
  409.             size_t inbytes_left = strlen( psz_subtitle );
  410.             size_t outbytes_left = 6 * inbytes_left;
  411.             char *psz_new_subtitle = malloc( outbytes_left + 1 );
  412.             char *psz_convert_buffer_out = psz_new_subtitle;
  413.             const char *psz_convert_buffer_in = psz_subtitle;
  414.             size_t ret = vlc_iconv( p_sys->iconv_handle,
  415.                                     &psz_convert_buffer_in, &inbytes_left,
  416.                                     &psz_convert_buffer_out, &outbytes_left );
  417.             *psz_convert_buffer_out++ = '';
  418.             free( psz_subtitle );
  419.             if( ( ret == (size_t)(-1) ) || inbytes_left )
  420.             {
  421.                 free( psz_new_subtitle );
  422.                 msg_Err( p_dec, "failed to convert subtitle encoding.n"
  423.                         "Try manually setting a character-encoding "
  424.                                 "before you open the file." );
  425.                 return NULL;
  426.             }
  427.             psz_subtitle = realloc( psz_new_subtitle,
  428.                                     psz_convert_buffer_out - psz_new_subtitle );
  429.         }
  430.     }
  431.     /* Create the subpicture unit */
  432.     p_spu = decoder_NewSubpicture( p_dec );
  433.     if( !p_spu )
  434.     {
  435.         msg_Warn( p_dec, "can't get spu buffer" );
  436.         free( psz_subtitle );
  437.         return NULL;
  438.     }
  439.     /* Create a new subpicture region */
  440.     memset( &fmt, 0, sizeof(video_format_t) );
  441.     fmt.i_chroma = VLC_FOURCC('T','E','X','T');
  442.     fmt.i_aspect = 0;
  443.     fmt.i_width = fmt.i_height = 0;
  444.     fmt.i_x_offset = fmt.i_y_offset = 0;
  445.     p_spu->p_region = subpicture_region_New( &fmt );
  446.     if( !p_spu->p_region )
  447.     {
  448.         msg_Err( p_dec, "cannot allocate SPU region" );
  449.         free( psz_subtitle );
  450.         decoder_DeleteSubpicture( p_dec, p_spu );
  451.         return NULL;
  452.     }
  453.     /* Decode and format the subpicture unit */
  454.     if( p_dec->fmt_in.i_codec != VLC_FOURCC('s','s','a',' ') )
  455.     {
  456.         /* Normal text subs, easy markup */
  457.         p_spu->p_region->i_align = SUBPICTURE_ALIGN_BOTTOM | p_sys->i_align;
  458.         p_spu->p_region->i_x = p_sys->i_align ? 20 : 0;
  459.         p_spu->p_region->i_y = 10;
  460.         /* Remove formatting from string */
  461.         p_spu->p_region->psz_text = StripTags( psz_subtitle );
  462.         if( var_CreateGetBool( p_dec, "subsdec-formatted" ) )
  463.         {
  464.             p_spu->p_region->psz_html = CreateHtmlSubtitle( &p_spu->p_region->i_align, psz_subtitle );
  465.         }
  466.         p_spu->i_start = p_block->i_pts;
  467.         p_spu->i_stop = p_block->i_pts + p_block->i_length;
  468.         p_spu->b_ephemer = (p_block->i_length == 0);
  469.         p_spu->b_absolute = false;
  470.     }
  471.     else
  472.     {
  473.         /* Decode SSA/USF strings */
  474.         ParseSSAString( p_dec, psz_subtitle, p_spu );
  475.         p_spu->i_start = p_block->i_pts;
  476.         p_spu->i_stop = p_block->i_pts + p_block->i_length;
  477.         p_spu->b_ephemer = (p_block->i_length == 0);
  478.         p_spu->b_absolute = false;
  479.         p_spu->i_original_picture_width = p_sys->i_original_width;
  480.         p_spu->i_original_picture_height = p_sys->i_original_height;
  481.     }
  482.     free( psz_subtitle );
  483.     return p_spu;
  484. }
  485. char* GotoNextLine( char *psz_text )
  486. {
  487.     char *p_newline = psz_text;
  488.     while( p_newline[0] != '' )
  489.     {
  490.         if( p_newline[0] == 'n' || p_newline[0] == 'r' )
  491.         {
  492.             p_newline++;
  493.             while( p_newline[0] == 'n' || p_newline[0] == 'r' )
  494.                 p_newline++;
  495.             break;
  496.         }
  497.         else p_newline++;
  498.     }
  499.     return p_newline;
  500. }
  501. /* Function now handles tags with attribute values, and tries
  502.  * to deal with &' commands too. It no longer modifies the string
  503.  * in place, so that the original text can be reused
  504.  */
  505. static char *StripTags( char *psz_subtitle )
  506. {
  507.     char *psz_text_start;
  508.     char *psz_text;
  509.     psz_text = psz_text_start = malloc( strlen( psz_subtitle ) + 1 );
  510.     if( !psz_text_start )
  511.         return NULL;
  512.     while( *psz_subtitle )
  513.     {
  514.         if( *psz_subtitle == '<' )
  515.         {
  516.             if( strncasecmp( psz_subtitle, "<br/>", 5 ) == 0 )
  517.                 *psz_text++ = 'n';
  518.             psz_subtitle += strcspn( psz_subtitle, ">" );
  519.         }
  520.         else if( *psz_subtitle == '&' )
  521.         {
  522.             if( !strncasecmp( psz_subtitle, "&lt;", 4 ))
  523.             {
  524.                 *psz_text++ = '<';
  525.                 psz_subtitle += strcspn( psz_subtitle, ";" );
  526.             }
  527.             else if( !strncasecmp( psz_subtitle, "&gt;", 4 ))
  528.             {
  529.                 *psz_text++ = '>';
  530.                 psz_subtitle += strcspn( psz_subtitle, ";" );
  531.             }
  532.             else if( !strncasecmp( psz_subtitle, "&amp;", 5 ))
  533.             {
  534.                 *psz_text++ = '&';
  535.                 psz_subtitle += strcspn( psz_subtitle, ";" );
  536.             }
  537.             else if( !strncasecmp( psz_subtitle, "&quot;", 6 ))
  538.             {
  539.                 *psz_text++ = '"';
  540.                 psz_subtitle += strcspn( psz_subtitle, ";" );
  541.             }
  542.             else
  543.             {
  544.                 /* Assume it is just a normal ampersand */
  545.                 *psz_text++ = '&';
  546.             }
  547.         }
  548.         else
  549.         {
  550.             *psz_text++ = *psz_subtitle;
  551.         }
  552.         psz_subtitle++;
  553.     }
  554.     *psz_text = '';
  555.     psz_text_start = realloc( psz_text_start, strlen( psz_text_start ) + 1 );
  556.     return psz_text_start;
  557. }
  558. /* Try to respect any style tags present in the subtitle string. The main
  559.  * problem here is a lack of adequate specs for the subtitle formats.
  560.  * SSA/ASS and USF are both detail spec'ed -- but they are handled elsewhere.
  561.  * SAMI has a detailed spec, but extensive rework is needed in the demux
  562.  * code to prevent all this style information being excised, as it presently
  563.  * does.
  564.  * That leaves the others - none of which were (I guess) originally intended
  565.  * to be carrying style information. Over time people have used them that way.
  566.  * In the absence of specifications from which to work, the tags supported
  567.  * have been restricted to the simple set permitted by the USF DTD, ie. :
  568.  *  Basic: <br>, <i>, <b>, <u>
  569.  *  Extended: <font>
  570.  *    Attributes: face
  571.  *                family
  572.  *                size
  573.  *                color
  574.  *                outline-color
  575.  *                shadow-color
  576.  *                outline-level
  577.  *                shadow-level
  578.  *                back-color
  579.  *                alpha
  580.  * There is also the further restriction that the subtitle be well-formed
  581.  * as an XML entity, ie. the HTML sentence:
  582.  *        <b><i>Bold and Italics</b></i>
  583.  * doesn't qualify because the tags aren't nested one inside the other.
  584.  * <text> tags are automatically added to the output to ensure
  585.  * well-formedness.
  586.  * If the text doesn't qualify for any reason, a NULL string is
  587.  * returned, and the rendering engine will fall back to the
  588.  * plain text version of the subtitle.
  589.  */
  590. static void HtmlNPut( char **ppsz_html, const char *psz_text, int i_max )
  591. {
  592.     const int i_len = strlen(psz_text);
  593.     strncpy( *ppsz_html, psz_text, i_max );
  594.     *ppsz_html += __MIN(i_max,i_len);
  595. }
  596. static void HtmlPut( char **ppsz_html, const char *psz_text )
  597. {
  598.     strcpy( *ppsz_html, psz_text );
  599.     *ppsz_html += strlen(psz_text);
  600. }
  601. static void HtmlCopy( char **ppsz_html, char **ppsz_subtitle, const char *psz_text )
  602. {
  603.     HtmlPut( ppsz_html, psz_text );
  604.     *ppsz_subtitle += strlen(psz_text);
  605. }
  606. static char *CreateHtmlSubtitle( int *pi_align, char *psz_subtitle )
  607. {
  608.     /* */
  609.     char *psz_tag = malloc( ( strlen( psz_subtitle ) / 3 ) + 1 );
  610.     if( !psz_tag )
  611.         return NULL;
  612.     psz_tag[ 0 ] = '';
  613.     /* */
  614.     size_t i_buf_size = strlen( psz_subtitle ) + 100;
  615.     char   *psz_html_start = malloc( i_buf_size );
  616.     char   *psz_html = psz_html_start;
  617.     if( psz_html_start == NULL )
  618.     {
  619.         free( psz_tag );
  620.         return NULL;
  621.     }
  622.     psz_html[0] = '';
  623.     bool b_has_align = false;
  624.     HtmlPut( &psz_html, "<text>" );
  625.     /* */
  626.     while( *psz_subtitle )
  627.     {
  628.         if( *psz_subtitle == 'n' )
  629.         {
  630.             HtmlPut( &psz_html, "<br/>" );
  631.             psz_subtitle++;
  632.         }
  633.         else if( *psz_subtitle == '<' )
  634.         {
  635.             if( !strncasecmp( psz_subtitle, "<br/>", 5 ))
  636.             {
  637.                 HtmlCopy( &psz_html, &psz_subtitle, "<br/>" );
  638.             }
  639.             else if( !strncasecmp( psz_subtitle, "<b>", 3 ) )
  640.             {
  641.                 HtmlCopy( &psz_html, &psz_subtitle, "<b>" );
  642.                 strcat( psz_tag, "b" );
  643.             }
  644.             else if( !strncasecmp( psz_subtitle, "<i>", 3 ) )
  645.             {
  646.                 HtmlCopy( &psz_html, &psz_subtitle, "<i>" );
  647.                 strcat( psz_tag, "i" );
  648.             }
  649.             else if( !strncasecmp( psz_subtitle, "<u>", 3 ) )
  650.             {
  651.                 HtmlCopy( &psz_html, &psz_subtitle, "<u>" );
  652.                 strcat( psz_tag, "u" );
  653.             }
  654.             else if( !strncasecmp( psz_subtitle, "<font ", 6 ))
  655.             {
  656.                 const char *psz_attribs[] = { "face=", "family=", "size=",
  657.                         "color=", "outline-color=", "shadow-color=",
  658.                         "outline-level=", "shadow-level=", "back-color=",
  659.                         "alpha=", NULL };
  660.                 HtmlCopy( &psz_html, &psz_subtitle, "<font " );
  661.                 strcat( psz_tag, "f" );
  662.                 while( *psz_subtitle != '>' )
  663.                 {
  664.                     int  k;
  665.                     for( k=0; psz_attribs[ k ]; k++ )
  666.                     {
  667.                         int i_len = strlen( psz_attribs[ k ] );
  668.                         if( !strncasecmp( psz_subtitle, psz_attribs[k], i_len ) )
  669.                         {
  670.                             /* */
  671.                             HtmlPut( &psz_html, psz_attribs[k] );
  672.                             psz_subtitle += i_len;
  673.                             /* */
  674.                             if( *psz_subtitle == '"' )
  675.                             {
  676.                                 psz_subtitle++;
  677.                                 i_len = strcspn( psz_subtitle, """ );
  678.                             }
  679.                             else
  680.                             {
  681.                                 i_len = strcspn( psz_subtitle, " t>" );
  682.                             }
  683.                             HtmlPut( &psz_html, """ );
  684.                             if( !strcmp( psz_attribs[ k ], "color=" ) && *psz_subtitle >= '0' && *psz_subtitle <= '9' )
  685.                                 HtmlPut( &psz_html, "#" );
  686.                             HtmlNPut( &psz_html, psz_subtitle, i_len );
  687.                             HtmlPut( &psz_html, """ );
  688.                             psz_subtitle += i_len;
  689.                             if( *psz_subtitle == '"' )
  690.                                 psz_subtitle++;
  691.                             break;
  692.                         }
  693.                     }
  694.                     if( psz_attribs[ k ] == NULL )
  695.                     {
  696.                         /* Jump over unrecognised tag */
  697.                         int i_len = strcspn( psz_subtitle, """ ) + 1;
  698.                         i_len += strcspn( psz_subtitle + i_len, """ ) + 1;
  699.                         psz_subtitle += i_len;
  700.                     }
  701.                     while (*psz_subtitle == ' ')
  702.                         *psz_html++ = *psz_subtitle++;
  703.                 }
  704.                 *psz_html++ = *psz_subtitle++;
  705.             }
  706.             else if( !strncmp( psz_subtitle, "</", 2 ))
  707.             {
  708.                 bool   b_match     = false;
  709.                 bool   b_ignore    = false;
  710.                 int    i_len       = strlen( psz_tag ) - 1;
  711.                 char  *psz_lastTag = NULL;
  712.                 if( i_len >= 0 )
  713.                 {
  714.                     psz_lastTag = psz_tag + i_len;
  715.                     i_len = 0;
  716.                     switch( *psz_lastTag )
  717.                     {
  718.                     case 'b':
  719.                         b_match = !strncasecmp( psz_subtitle, "</b>", 4 );
  720.                         i_len   = 4;
  721.                         break;
  722.                     case 'i':
  723.                         b_match = !strncasecmp( psz_subtitle, "</i>", 4 );
  724.                         i_len   = 4;
  725.                         break;
  726.                     case 'u':
  727.                         b_match = !strncasecmp( psz_subtitle, "</u>", 4 );
  728.                         i_len   = 4;
  729.                         break;
  730.                     case 'f':
  731.                         b_match = !strncasecmp( psz_subtitle, "</font>", 7 );
  732.                         i_len   = 7;
  733.                         break;
  734.                     case 'I':
  735.                         i_len = strcspn( psz_subtitle, ">" );
  736.                         b_match = psz_subtitle[i_len] == '>';
  737.                         b_ignore = true;
  738.                         if( b_match )
  739.                             i_len++;
  740.                         break;
  741.                     }
  742.                 }
  743.                 if( !b_match )
  744.                 {
  745.                     /* Not well formed -- kill everything */
  746.                     free( psz_html_start );
  747.                     psz_html_start = NULL;
  748.                     break;
  749.                 }
  750.                 *psz_lastTag = '';
  751.                 if( !b_ignore )
  752.                     HtmlNPut( &psz_html, psz_subtitle, i_len );
  753.                 psz_subtitle += i_len;
  754.             }
  755.             else if( ( psz_subtitle[1] < 'a' || psz_subtitle[1] > 'z' ) &&
  756.                      ( psz_subtitle[1] < 'A' || psz_subtitle[1] > 'Z' ) )
  757.             {
  758.                 /* We have a single < */
  759.                 HtmlPut( &psz_html, "&lt;" );
  760.                 psz_subtitle++;
  761.             }
  762.             else
  763.             {
  764.                 /* We have an unknown tag or a single < */
  765.                 /* Search for the next tag or end of tag or end of string */
  766.                 char *psz_stop = psz_subtitle + 1 + strcspn( &psz_subtitle[1], "<>" );
  767.                 char *psz_closing = strstr( psz_subtitle, "/>" );
  768.                 if( psz_closing && psz_closing < psz_stop )
  769.                 {
  770.                     /* We have a self closed tag, remove it */
  771.                     psz_subtitle = &psz_closing[2];
  772.                 }
  773.                 else if( *psz_stop == '>' )
  774.                 {
  775.                     char psz_match[256];
  776.                     snprintf( psz_match, sizeof(psz_match), "</%s", &psz_subtitle[1] );
  777.                     psz_match[strcspn( psz_match, " t>" )] = '';
  778.                     if( strstr( psz_subtitle, psz_match ) )
  779.                     {
  780.                         /* We have the closing tag, ignore it TODO */
  781.                         psz_subtitle = &psz_stop[1];
  782.                         strcat( psz_tag, "I" );
  783.                     }
  784.                     else
  785.                     {
  786.                         int i_len = psz_stop + 1 - psz_subtitle;
  787.                         /* Copy the whole data */
  788.                         for( ; i_len > 0; i_len--, psz_subtitle++ )
  789.                         {
  790.                             if( *psz_subtitle == '<' )
  791.                                 HtmlPut( &psz_html, "&lt;" );
  792.                             else if( *psz_subtitle == '>' )
  793.                                 HtmlPut( &psz_html, "&gt;" );
  794.                             else
  795.                                 *psz_html++ = *psz_subtitle;
  796.                         }
  797.                     }
  798.                 }
  799.                 else
  800.                 {
  801.                     /* We have a single < */
  802.                     HtmlPut( &psz_html, "&lt;" );
  803.                     psz_subtitle++;
  804.                 }
  805.             }
  806.         }
  807.         else if( *psz_subtitle == '&' )
  808.         {
  809.             if( !strncasecmp( psz_subtitle, "&lt;", 4 ))
  810.             {
  811.                 HtmlCopy( &psz_html, &psz_subtitle, "&lt;" );
  812.             }
  813.             else if( !strncasecmp( psz_subtitle, "&gt;", 4 ))
  814.             {
  815.                 HtmlCopy( &psz_html, &psz_subtitle, "&gt;" );
  816.             }
  817.             else if( !strncasecmp( psz_subtitle, "&amp;", 5 ))
  818.             {
  819.                 HtmlCopy( &psz_html, &psz_subtitle, "&amp;" );
  820.             }
  821.             else
  822.             {
  823.                 HtmlPut( &psz_html, "&amp;" );
  824.                 psz_subtitle++;
  825.             }
  826.         }
  827.         else if( *psz_subtitle == '>' )
  828.         {
  829.             HtmlPut( &psz_html, "&gt;" );
  830.             psz_subtitle++;
  831.         }
  832.         else if( psz_subtitle[0] == '{' && psz_subtitle[1] == '\' &&
  833.                  strchr( psz_subtitle, '}' ) )
  834.         {
  835.             /* Check for forced alignment */
  836.             if( !b_has_align &&
  837.                 !strncmp( psz_subtitle, "{\an", 4 ) && psz_subtitle[4] >= '1' && psz_subtitle[4] <= '9' && psz_subtitle[5] == '}' )
  838.             {
  839.                 static const int pi_vertical[3] = { SUBPICTURE_ALIGN_BOTTOM, 0, SUBPICTURE_ALIGN_TOP };
  840.                 static const int pi_horizontal[3] = { SUBPICTURE_ALIGN_LEFT, 0, SUBPICTURE_ALIGN_RIGHT };
  841.                 const int i_id = psz_subtitle[4] - '1';
  842.                 b_has_align = true;
  843.                 *pi_align = pi_vertical[i_id/3] | pi_horizontal[i_id%3];
  844.             }
  845.             /* TODO fr -> rotation */
  846.             /* Hide {stupidity} */
  847.             psz_subtitle = strchr( psz_subtitle, '}' ) + 1;
  848.         }
  849.         else if( psz_subtitle[0] == '{' && psz_subtitle[1] == 'Y'
  850.                 && psz_subtitle[2] == ':' && strchr( psz_subtitle, '}' ) )
  851.         {
  852.             /* Hide {Y:stupidity} */
  853.             psz_subtitle = strchr( psz_subtitle, '}' ) + 1;
  854.         }
  855.         else if( psz_subtitle[0] == '\' && psz_subtitle[1] )
  856.         {
  857.             if( psz_subtitle[1] == 'N' || psz_subtitle[1] == 'n' )
  858.             {
  859.                 HtmlPut( &psz_html, "<br/>" );
  860.                 psz_subtitle += 2;
  861.             }
  862.             else if( psz_subtitle[1] == 'h' )
  863.             {
  864.                 /* Non breakable space */
  865.                 HtmlPut( &psz_html, NO_BREAKING_SPACE );
  866.                 psz_subtitle += 2;
  867.             }
  868.             else
  869.             {
  870.                 HtmlPut( &psz_html, "\" );
  871.                 psz_subtitle++;
  872.             }
  873.         }
  874.         else
  875.         {
  876.             *psz_html = *psz_subtitle;
  877.             if( psz_html > psz_html_start )
  878.             {
  879.                 /* Check for double whitespace */
  880.                 if( ( *psz_html == ' '  || *psz_html == 't' ) &&
  881.                     ( *(psz_html-1) == ' ' || *(psz_html-1) == 't' ) )
  882.                 {
  883.                     HtmlPut( &psz_html, NO_BREAKING_SPACE );
  884.                     psz_html--;
  885.                 }
  886.             }
  887.             psz_html++;
  888.             psz_subtitle++;
  889.         }
  890.         if( ( size_t )( psz_html - psz_html_start ) > i_buf_size - 50 )
  891.         {
  892.             const int i_len = psz_html - psz_html_start;
  893.             i_buf_size += 200;
  894.             char *psz_new = realloc( psz_html_start, i_buf_size );
  895.             if( !psz_new )
  896.                 break;
  897.             psz_html_start = psz_new;
  898.             psz_html = &psz_new[i_len];
  899.         }
  900.     }
  901.     if( psz_html_start )
  902.     {
  903.         static const char *psz_text_close = "</text>";
  904.         static const char *psz_tag_long = "/font>";
  905.         /* Realloc for closing tags and shrink memory */
  906.         const size_t i_length = (size_t)( psz_html - psz_html_start );
  907.         const size_t i_size = i_length + strlen(psz_tag_long) * strlen(psz_tag) + strlen(psz_text_close) + 1;
  908.         char *psz_new = realloc( psz_html_start, i_size );
  909.         if( psz_new )
  910.         {
  911.             psz_html_start = psz_new;
  912.             psz_html = &psz_new[i_length];
  913.             /* Close not well formed subtitle */
  914.             while( *psz_tag )
  915.             {
  916.                 /* */
  917.                 char *psz_last = &psz_tag[strlen(psz_tag)-1];
  918.                 switch( *psz_last )
  919.                 {
  920.                 case 'b':
  921.                     HtmlPut( &psz_html, "</b>" );
  922.                     break;
  923.                 case 'i':
  924.                     HtmlPut( &psz_html, "</i>" );
  925.                     break;
  926.                 case 'u':
  927.                     HtmlPut( &psz_html, "</u>" );
  928.                     break;
  929.                 case 'f':
  930.                     HtmlPut( &psz_html, "/font>" );
  931.                     break;
  932.                 case 'I':
  933.                     break;
  934.                 }
  935.                 *psz_last = '';
  936.             }
  937.             HtmlPut( &psz_html, psz_text_close );
  938.         }
  939.     }
  940.     free( psz_tag );
  941.     return psz_html_start;
  942. }