]> git.sesse.net Git - vlc/blob - modules/codec/subtitles/subsusf.c
0c5b773553edd806f898742257ff343b9488ecff
[vlc] / modules / codec / subtitles / subsusf.c
1 /*****************************************************************************
2  * subsusf.c : USF subtitles decoder
3  *****************************************************************************
4  * Copyright (C) 2000-2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Bernie Purcell <bitmap@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
26
27 #include "subsdec.h"
28 #include <vlc_plugin.h>
29 #include <assert.h>
30
31 /*****************************************************************************
32  * Local prototypes
33  *****************************************************************************/
34 static int  OpenDecoder   ( vlc_object_t * );
35 static void CloseDecoder  ( vlc_object_t * );
36
37 static subpicture_t *DecodeBlock   ( decoder_t *, block_t ** );
38 static char         *CreatePlainText( char * );
39 static int           ParseImageAttachments( decoder_t *p_dec );
40
41 static subpicture_t        *ParseText     ( decoder_t *, block_t * );
42 static void                 ParseUSFHeader( decoder_t * );
43 static subpicture_region_t *ParseUSFString( decoder_t *, char * );
44 static subpicture_region_t *LoadEmbeddedImage( decoder_t *p_dec, const char *psz_filename, int i_transparent_color );
45
46 /*****************************************************************************
47  * Module descriptor.
48  *****************************************************************************/
49
50 vlc_module_begin ()
51     set_capability( "decoder", 40 )
52     set_shortname( N_("USFSubs"))
53     set_description( N_("USF subtitles decoder") )
54     set_callbacks( OpenDecoder, CloseDecoder )
55     set_category( CAT_INPUT )
56     set_subcategory( SUBCAT_INPUT_SCODEC )
57     /* We inherit subsdec-align and subsdec-formatted from subsdec.c */
58 vlc_module_end ()
59
60 /*****************************************************************************
61  * OpenDecoder: probe the decoder and return score
62  *****************************************************************************
63  * Tries to launch a decoder and return score so that the interface is able
64  * to chose.
65  *****************************************************************************/
66 static int OpenDecoder( vlc_object_t *p_this )
67 {
68     decoder_t     *p_dec = (decoder_t*)p_this;
69     decoder_sys_t *p_sys;
70
71     if( p_dec->fmt_in.i_codec != VLC_CODEC_USF )
72         return VLC_EGENERIC;
73
74     /* Allocate the memory needed to store the decoder's structure */
75     if( ( p_dec->p_sys = p_sys = calloc(1, sizeof(decoder_sys_t)) ) == NULL )
76         return VLC_ENOMEM;
77
78     p_dec->pf_decode_sub = DecodeBlock;
79     p_dec->fmt_out.i_cat = SPU_ES;
80     p_dec->fmt_out.i_codec = 0;
81
82     /* Unused fields of p_sys - not needed for USF decoding */
83     p_sys->b_ass = false;
84     p_sys->iconv_handle = (vlc_iconv_t)-1;
85     p_sys->b_autodetect_utf8 = false;
86
87     /* init of p_sys */
88     p_sys->i_align = 0;
89     p_sys->i_original_height = 0;
90     p_sys->i_original_width = 0;
91     TAB_INIT( p_sys->i_ssa_styles, p_sys->pp_ssa_styles );
92     TAB_INIT( p_sys->i_images, p_sys->pp_images );
93
94     /* USF subtitles are mandated to be UTF-8, so don't need vlc_iconv */
95
96     p_sys->i_align = var_CreateGetInteger( p_dec, "subsdec-align" );
97
98     ParseImageAttachments( p_dec );
99
100     if( var_CreateGetBool( p_dec, "subsdec-formatted" ) )
101     {
102         if( p_dec->fmt_in.i_extra > 0 )
103             ParseUSFHeader( p_dec );
104     }
105
106     return VLC_SUCCESS;
107 }
108
109 /****************************************************************************
110  * DecodeBlock: the whole thing
111  ****************************************************************************
112  * This function must be fed with complete subtitles units.
113  ****************************************************************************/
114 static subpicture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block )
115 {
116     subpicture_t *p_spu;
117     block_t *p_block;
118
119     if( !pp_block || *pp_block == NULL )
120         return NULL;
121
122     p_block = *pp_block;
123
124     p_spu = ParseText( p_dec, p_block );
125
126     block_Release( p_block );
127     *pp_block = NULL;
128
129     return p_spu;
130 }
131
132 /*****************************************************************************
133  * CloseDecoder: clean up the decoder
134  *****************************************************************************/
135 static void CloseDecoder( vlc_object_t *p_this )
136 {
137     decoder_t *p_dec = (decoder_t *)p_this;
138     decoder_sys_t *p_sys = p_dec->p_sys;
139
140     if( p_sys->pp_ssa_styles )
141     {
142         int i;
143         for( i = 0; i < p_sys->i_ssa_styles; i++ )
144         {
145             if( !p_sys->pp_ssa_styles[i] )
146                 continue;
147
148             free( p_sys->pp_ssa_styles[i]->psz_stylename );
149             //FIXME: Make font_style a pointer and use text_style_* functions
150             free( p_sys->pp_ssa_styles[i]->font_style.psz_fontname );
151             free( p_sys->pp_ssa_styles[i] );
152         }
153         TAB_CLEAN( p_sys->i_ssa_styles, p_sys->pp_ssa_styles );
154     }
155     if( p_sys->pp_images )
156     {
157         int i;
158         for( i = 0; i < p_sys->i_images; i++ )
159         {
160             if( !p_sys->pp_images[i] )
161                 continue;
162
163             if( p_sys->pp_images[i]->p_pic )
164                 picture_Release( p_sys->pp_images[i]->p_pic );
165             free( p_sys->pp_images[i]->psz_filename );
166
167             free( p_sys->pp_images[i] );
168         }
169         TAB_CLEAN( p_sys->i_images, p_sys->pp_images );
170     }
171
172     free( p_sys );
173 }
174
175 /*****************************************************************************
176  * ParseText: parse an text subtitle packet and send it to the video output
177  *****************************************************************************/
178 static subpicture_t *ParseText( decoder_t *p_dec, block_t *p_block )
179 {
180     decoder_sys_t *p_sys = p_dec->p_sys;
181     subpicture_t *p_spu = NULL;
182     char *psz_subtitle = NULL;
183
184     /* We cannot display a subpicture with no date */
185     if( p_block->i_pts <= VLC_TS_INVALID )
186     {
187         msg_Warn( p_dec, "subtitle without a date" );
188         return NULL;
189     }
190
191     /* Check validity of packet data */
192     /* An "empty" line containing only \0 can be used to force
193        and ephemer picture from the screen */
194     if( p_block->i_buffer < 1 )
195     {
196         msg_Warn( p_dec, "no subtitle data" );
197         return NULL;
198     }
199
200     /* Should be resiliant against bad subtitles */
201     psz_subtitle = strndup( (const char *)p_block->p_buffer,
202                             p_block->i_buffer );
203     if( psz_subtitle == NULL )
204         return NULL;
205
206     /* USF Subtitles are mandated to be UTF-8 -- make sure it is */
207     if (EnsureUTF8( psz_subtitle ) == NULL)
208     {
209         msg_Err( p_dec, "USF subtitles must be in UTF-8 format.\n"
210                  "This stream contains USF subtitles which aren't." );
211     }
212
213     /* Create the subpicture unit */
214     p_spu = decoder_NewSubpicture( p_dec, NULL );
215     if( !p_spu )
216     {
217         msg_Warn( p_dec, "can't get spu buffer" );
218         free( psz_subtitle );
219         return NULL;
220     }
221
222     /* Decode USF strings */
223     p_spu->p_region = ParseUSFString( p_dec, psz_subtitle );
224
225     p_spu->i_start = p_block->i_pts;
226     p_spu->i_stop = p_block->i_pts + p_block->i_length;
227     p_spu->b_ephemer = (p_block->i_length == 0);
228     p_spu->b_absolute = false;
229     p_spu->i_original_picture_width = p_sys->i_original_width;
230     p_spu->i_original_picture_height = p_sys->i_original_height;
231
232     free( psz_subtitle );
233
234     return p_spu;
235 }
236
237 static char *GrabAttributeValue( const char *psz_attribute,
238                                  const char *psz_tag_start )
239 {
240     if( psz_attribute && psz_tag_start )
241     {
242         char *psz_tag_end = strchr( psz_tag_start, '>' );
243         char *psz_found   = strcasestr( psz_tag_start, psz_attribute );
244
245         if( psz_found )
246         {
247             psz_found += strlen( psz_attribute );
248
249             if(( *(psz_found++) == '=' ) &&
250                ( *(psz_found++) == '\"' ))
251             {
252                 if( psz_found < psz_tag_end )
253                 {
254                     int   i_len = strcspn( psz_found, "\"" );
255                     return strndup( psz_found, i_len );
256                 }
257             }
258         }
259     }
260     return NULL;
261 }
262
263 static ssa_style_t *ParseStyle( decoder_sys_t *p_sys, char *psz_subtitle )
264 {
265     ssa_style_t *p_ssa_style = NULL;
266     char        *psz_style = GrabAttributeValue( "style", psz_subtitle );
267
268     if( psz_style )
269     {
270         int i;
271
272         for( i = 0; i < p_sys->i_ssa_styles; i++ )
273         {
274             if( !strcmp( p_sys->pp_ssa_styles[i]->psz_stylename, psz_style ) )
275                 p_ssa_style = p_sys->pp_ssa_styles[i];
276         }
277         free( psz_style );
278     }
279     return p_ssa_style;
280 }
281
282 static int ParsePositionAttributeList( char *psz_subtitle, int *i_align,
283                                        int *i_x, int *i_y )
284 {
285     int   i_mask = 0;
286
287     char *psz_align    = GrabAttributeValue( "alignment", psz_subtitle );
288     char *psz_margin_x = GrabAttributeValue( "horizontal-margin", psz_subtitle );
289     char *psz_margin_y = GrabAttributeValue( "vertical-margin", psz_subtitle );
290     /* -- UNSUPPORTED
291     char *psz_relative = GrabAttributeValue( "relative-to", psz_subtitle );
292     char *psz_rotate_x = GrabAttributeValue( "rotate-x", psz_subtitle );
293     char *psz_rotate_y = GrabAttributeValue( "rotate-y", psz_subtitle );
294     char *psz_rotate_z = GrabAttributeValue( "rotate-z", psz_subtitle );
295     */
296
297     *i_align = SUBPICTURE_ALIGN_BOTTOM;
298     *i_x = 0;
299     *i_y = 0;
300
301     if( psz_align )
302     {
303         if( !strcasecmp( "TopLeft", psz_align ) )
304             *i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_LEFT;
305         else if( !strcasecmp( "TopCenter", psz_align ) )
306             *i_align = SUBPICTURE_ALIGN_TOP;
307         else if( !strcasecmp( "TopRight", psz_align ) )
308             *i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_RIGHT;
309         else if( !strcasecmp( "MiddleLeft", psz_align ) )
310             *i_align = SUBPICTURE_ALIGN_LEFT;
311         else if( !strcasecmp( "MiddleCenter", psz_align ) )
312             *i_align = 0;
313         else if( !strcasecmp( "MiddleRight", psz_align ) )
314             *i_align = SUBPICTURE_ALIGN_RIGHT;
315         else if( !strcasecmp( "BottomLeft", psz_align ) )
316             *i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_LEFT;
317         else if( !strcasecmp( "BottomCenter", psz_align ) )
318             *i_align = SUBPICTURE_ALIGN_BOTTOM;
319         else if( !strcasecmp( "BottomRight", psz_align ) )
320             *i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT;
321
322         i_mask |= ATTRIBUTE_ALIGNMENT;
323         free( psz_align );
324     }
325     if( psz_margin_x )
326     {
327         *i_x = atoi( psz_margin_x );
328         if( strchr( psz_margin_x, '%' ) )
329             i_mask |= ATTRIBUTE_X_PERCENT;
330         else
331             i_mask |= ATTRIBUTE_X;
332
333         free( psz_margin_x );
334     }
335     if( psz_margin_y )
336     {
337         *i_y = atoi( psz_margin_y );
338         if( strchr( psz_margin_y, '%' ) )
339             i_mask |= ATTRIBUTE_Y_PERCENT;
340         else
341             i_mask |= ATTRIBUTE_Y;
342
343         free( psz_margin_y );
344     }
345     return i_mask;
346 }
347
348 static void SetupPositions( subpicture_region_t *p_region, char *psz_subtitle )
349 {
350     int           i_mask = 0;
351     int           i_align;
352     int           i_x, i_y;
353
354     i_mask = ParsePositionAttributeList( psz_subtitle, &i_align, &i_x, &i_y );
355
356     if( i_mask & ATTRIBUTE_ALIGNMENT )
357         p_region->i_align = i_align;
358
359     /* TODO: Setup % based offsets properly, without adversely affecting
360      *       everything else in vlc. Will address with separate patch, to
361      *       prevent this one being any more complicated.
362      */
363     if( i_mask & ATTRIBUTE_X )
364         p_region->i_x = i_x;
365     else if( i_mask & ATTRIBUTE_X_PERCENT )
366         p_region->i_x = 0;
367
368     if( i_mask & ATTRIBUTE_Y )
369         p_region->i_y = i_y;
370     else if( i_mask & ATTRIBUTE_Y_PERCENT )
371         p_region->i_y = 0;
372 }
373
374 static subpicture_region_t *CreateTextRegion( decoder_t *p_dec,
375                                               char *psz_subtitle,
376                                               int i_len,
377                                               int i_sys_align )
378 {
379     decoder_sys_t        *p_sys = p_dec->p_sys;
380     subpicture_region_t  *p_text_region;
381     video_format_t        fmt;
382
383     /* Create a new subpicture region */
384     memset( &fmt, 0, sizeof(video_format_t) );
385     fmt.i_chroma = VLC_CODEC_TEXT;
386     fmt.i_width = fmt.i_height = 0;
387     fmt.i_x_offset = fmt.i_y_offset = 0;
388     p_text_region = subpicture_region_New( &fmt );
389
390     if( p_text_region != NULL )
391     {
392         ssa_style_t  *p_ssa_style = NULL;
393
394         p_text_region->psz_text = NULL;
395         p_text_region->psz_html = strndup( psz_subtitle, i_len );
396         if( ! p_text_region->psz_html )
397         {
398             subpicture_region_Delete( p_text_region );
399             return NULL;
400         }
401
402         p_ssa_style = ParseStyle( p_sys, p_text_region->psz_html );
403         if( !p_ssa_style )
404         {
405             int i;
406
407             for( i = 0; i < p_sys->i_ssa_styles; i++ )
408             {
409                 if( !strcasecmp( p_sys->pp_ssa_styles[i]->psz_stylename, "Default" ) )
410                     p_ssa_style = p_sys->pp_ssa_styles[i];
411             }
412         }
413
414         if( p_ssa_style )
415         {
416             msg_Dbg( p_dec, "style is: %s", p_ssa_style->psz_stylename );
417
418             p_text_region->p_style = text_style_Duplicate( &p_ssa_style->font_style );
419             p_text_region->i_align = p_ssa_style->i_align;
420
421             /* TODO: Setup % based offsets properly, without adversely affecting
422              *       everything else in vlc. Will address with separate patch,
423              *       to prevent this one being any more complicated.
424
425                      * p_ssa_style->i_margin_percent_h;
426                      * p_ssa_style->i_margin_percent_v;
427              */
428             p_text_region->i_x         = p_ssa_style->i_margin_h;
429             p_text_region->i_y         = p_ssa_style->i_margin_v;
430
431         }
432         else
433         {
434             p_text_region->i_align = SUBPICTURE_ALIGN_BOTTOM | i_sys_align;
435             p_text_region->i_x = i_sys_align ? 20 : 0;
436             p_text_region->i_y = 10;
437         }
438         /* Look for position arguments which may override the style-based
439          * defaults.
440          */
441         SetupPositions( p_text_region, psz_subtitle );
442
443         p_text_region->p_next = NULL;
444     }
445     return p_text_region;
446 }
447
448 static int ParseImageAttachments( decoder_t *p_dec )
449 {
450     decoder_sys_t        *p_sys = p_dec->p_sys;
451     input_attachment_t  **pp_attachments;
452     int                   i_attachments_cnt;
453     int                   k = 0;
454
455     if( VLC_SUCCESS != decoder_GetInputAttachments( p_dec, &pp_attachments, &i_attachments_cnt ))
456         return VLC_EGENERIC;
457
458     for( k = 0; k < i_attachments_cnt; k++ )
459     {
460         input_attachment_t *p_attach = pp_attachments[k];
461
462         vlc_fourcc_t type = image_Mime2Fourcc( p_attach->psz_mime );
463
464         if( ( type != 0 ) &&
465             ( p_attach->i_data > 0 ) &&
466             ( p_attach->p_data != NULL ) )
467         {
468             picture_t         *p_pic = NULL;
469             image_handler_t   *p_image;
470
471             p_image = image_HandlerCreate( p_dec );
472             if( p_image != NULL )
473             {
474                 block_t   *p_block;
475
476                 p_block = block_New( p_image->p_parent, p_attach->i_data );
477
478                 if( p_block != NULL )
479                 {
480                     video_format_t     fmt_in;
481                     video_format_t     fmt_out;
482
483                     memcpy( p_block->p_buffer, p_attach->p_data, p_attach->i_data );
484
485                     memset( &fmt_in,  0, sizeof( video_format_t));
486                     memset( &fmt_out, 0, sizeof( video_format_t));
487
488                     fmt_in.i_chroma  = type;
489                     fmt_out.i_chroma = VLC_CODEC_YUVA;
490
491                     /* Find a suitable decoder module */
492                     if( module_exists( "sdl_image" ) )
493                     {
494                         /* ffmpeg thinks it can handle bmp properly but it can't (at least
495                          * not all of them), so use sdl_image if it is available */
496
497                         vlc_value_t val;
498
499                         var_Create( p_dec, "codec", VLC_VAR_MODULE | VLC_VAR_DOINHERIT );
500                         val.psz_string = (char*) "sdl_image";
501                         var_Set( p_dec, "codec", val );
502                     }
503
504                     p_pic = image_Read( p_image, p_block, &fmt_in, &fmt_out );
505                     var_Destroy( p_dec, "codec" );
506                 }
507
508                 image_HandlerDelete( p_image );
509             }
510             if( p_pic )
511             {
512                 image_attach_t *p_picture = malloc( sizeof(image_attach_t) );
513
514                 if( p_picture )
515                 {
516                     p_picture->psz_filename = strdup( p_attach->psz_name );
517                     p_picture->p_pic = p_pic;
518
519                     TAB_APPEND( p_sys->i_images, p_sys->pp_images, p_picture );
520                 }
521             }
522         }
523         vlc_input_attachment_Delete( pp_attachments[ k ] );
524     }
525     free( pp_attachments );
526
527     return VLC_SUCCESS;
528 }
529
530 static void ParseUSFHeaderTags( decoder_t *p_dec, xml_reader_t *p_xml_reader )
531 {
532     decoder_sys_t *p_sys = p_dec->p_sys;
533     char *psz_node;
534     ssa_style_t *p_ssa_style = NULL;
535     int i_style_level = 0;
536     int i_metadata_level = 0;
537
538     while ( xml_ReaderRead( p_xml_reader ) == 1 )
539     {
540         switch ( xml_ReaderNodeType( p_xml_reader ) )
541         {
542             case XML_READER_TEXT:
543             case XML_READER_NONE:
544                 break;
545             case XML_READER_ENDELEM:
546                 psz_node = xml_ReaderName( p_xml_reader );
547
548                 if( !psz_node )
549                     break;
550                 switch (i_style_level)
551                 {
552                     case 0:
553                         if( !strcasecmp( "metadata", psz_node ) && (i_metadata_level == 1) )
554                         {
555                             i_metadata_level--;
556                         }
557                         break;
558                     case 1:
559                         if( !strcasecmp( "styles", psz_node ) )
560                         {
561                             i_style_level--;
562                         }
563                         break;
564                     case 2:
565                         if( !strcasecmp( "style", psz_node ) )
566                         {
567                             TAB_APPEND( p_sys->i_ssa_styles, p_sys->pp_ssa_styles, p_ssa_style );
568
569                             p_ssa_style = NULL;
570                             i_style_level--;
571                         }
572                         break;
573                 }
574
575                 free( psz_node );
576                 break;
577             case XML_READER_STARTELEM:
578                 psz_node = xml_ReaderName( p_xml_reader );
579
580                 if( !psz_node )
581                     break;
582
583                 if( !strcasecmp( "metadata", psz_node ) && (i_style_level == 0) )
584                 {
585                     i_metadata_level++;
586                 }
587                 else if( !strcasecmp( "resolution", psz_node ) &&
588                          ( i_metadata_level == 1) )
589                 {
590                     while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
591                     {
592                         char *psz_name = xml_ReaderName ( p_xml_reader );
593                         char *psz_value = xml_ReaderValue ( p_xml_reader );
594
595                         if( psz_name && psz_value )
596                         {
597                             if( !strcasecmp( "x", psz_name ) )
598                                 p_sys->i_original_width = atoi( psz_value );
599                             else if( !strcasecmp( "y", psz_name ) )
600                                 p_sys->i_original_height = atoi( psz_value );
601                         }
602                         free( psz_name );
603                         free( psz_value );
604                     }
605                 }
606                 else if( !strcasecmp( "styles", psz_node ) && (i_style_level == 0) )
607                 {
608                     i_style_level++;
609                 }
610                 else if( !strcasecmp( "style", psz_node ) && (i_style_level == 1) )
611                 {
612                     i_style_level++;
613
614                     p_ssa_style = calloc( 1, sizeof(ssa_style_t) );
615                     if( !p_ssa_style )
616                     {
617                         free( psz_node );
618                         return;
619                     }
620                     /* All styles are supposed to default to Default, and then
621                      * one or more settings are over-ridden.
622                      * At the moment this only effects styles defined AFTER
623                      * Default in the XML
624                      */
625                     int i;
626                     for( i = 0; i < p_sys->i_ssa_styles; i++ )
627                     {
628                         if( !strcasecmp( p_sys->pp_ssa_styles[i]->psz_stylename, "Default" ) )
629                         {
630                             ssa_style_t *p_default_style = p_sys->pp_ssa_styles[i];
631
632                             memcpy( p_ssa_style, p_default_style, sizeof( ssa_style_t ) );
633                             //FIXME: Make font_style a pointer. Actually we double copy some data here,
634                             //   we use text_style_Copy to avoid copying psz_fontname, though .
635                             text_style_Copy( &p_ssa_style->font_style, &p_default_style->font_style );
636                             p_ssa_style->psz_stylename = NULL;
637                         }
638                     }
639
640                     while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
641                     {
642                         char *psz_name = xml_ReaderName ( p_xml_reader );
643                         char *psz_value = xml_ReaderValue ( p_xml_reader );
644
645                         if( psz_name && psz_value )
646                         {
647                             if( !strcasecmp( "name", psz_name ) )
648                                 p_ssa_style->psz_stylename = strdup( psz_value );
649                         }
650                         free( psz_name );
651                         free( psz_value );
652                     }
653                 }
654                 else if( !strcasecmp( "fontstyle", psz_node ) && (i_style_level == 2) )
655                 {
656                     while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
657                     {
658                         char *psz_name = xml_ReaderName ( p_xml_reader );
659                         char *psz_value = xml_ReaderValue ( p_xml_reader );
660
661                         if( psz_name && psz_value )
662                         {
663                             if( !strcasecmp( "face", psz_name ) )
664                             {
665                                 free( p_ssa_style->font_style.psz_fontname );
666                                 p_ssa_style->font_style.psz_fontname = strdup( psz_value );
667                             }
668                             else if( !strcasecmp( "size", psz_name ) )
669                             {
670                                 if( ( *psz_value == '+' ) || ( *psz_value == '-' ) )
671                                 {
672                                     int i_value = atoi( psz_value );
673
674                                     if( ( i_value >= -5 ) && ( i_value <= 5 ) )
675                                         p_ssa_style->font_style.i_font_size  +=
676                                             ( i_value * p_ssa_style->font_style.i_font_size ) / 10;
677                                     else if( i_value < -5 )
678                                         p_ssa_style->font_style.i_font_size  = - i_value;
679                                     else if( i_value > 5 )
680                                         p_ssa_style->font_style.i_font_size  = i_value;
681                                 }
682                                 else
683                                     p_ssa_style->font_style.i_font_size  = atoi( psz_value );
684                             }
685                             else if( !strcasecmp( "italic", psz_name ) )
686                             {
687                                 if( !strcasecmp( "yes", psz_value ))
688                                     p_ssa_style->font_style.i_style_flags |= STYLE_ITALIC;
689                                 else
690                                     p_ssa_style->font_style.i_style_flags &= ~STYLE_ITALIC;
691                             }
692                             else if( !strcasecmp( "weight", psz_name ) )
693                             {
694                                 if( !strcasecmp( "bold", psz_value ))
695                                     p_ssa_style->font_style.i_style_flags |= STYLE_BOLD;
696                                 else
697                                     p_ssa_style->font_style.i_style_flags &= ~STYLE_BOLD;
698                             }
699                             else if( !strcasecmp( "underline", psz_name ) )
700                             {
701                                 if( !strcasecmp( "yes", psz_value ))
702                                     p_ssa_style->font_style.i_style_flags |= STYLE_UNDERLINE;
703                                 else
704                                     p_ssa_style->font_style.i_style_flags &= ~STYLE_UNDERLINE;
705                             }
706                             else if( !strcasecmp( "color", psz_name ) )
707                             {
708                                 if( *psz_value == '#' )
709                                 {
710                                     unsigned long col = strtol(psz_value+1, NULL, 16);
711                                     p_ssa_style->font_style.i_font_color = (col & 0x00ffffff);
712                                     p_ssa_style->font_style.i_font_alpha = (col >> 24) & 0xff;
713                                 }
714                             }
715                             else if( !strcasecmp( "outline-color", psz_name ) )
716                             {
717                                 if( *psz_value == '#' )
718                                 {
719                                     unsigned long col = strtol(psz_value+1, NULL, 16);
720                                     p_ssa_style->font_style.i_outline_color = (col & 0x00ffffff);
721                                     p_ssa_style->font_style.i_outline_alpha = (col >> 24) & 0xff;
722                                 }
723                             }
724                             else if( !strcasecmp( "outline-level", psz_name ) )
725                             {
726                                 p_ssa_style->font_style.i_outline_width = atoi( psz_value );
727                             }
728                             else if( !strcasecmp( "shadow-color", psz_name ) )
729                             {
730                                 if( *psz_value == '#' )
731                                 {
732                                     unsigned long col = strtol(psz_value+1, NULL, 16);
733                                     p_ssa_style->font_style.i_shadow_color = (col & 0x00ffffff);
734                                     p_ssa_style->font_style.i_shadow_alpha = (col >> 24) & 0xff;
735                                 }
736                             }
737                             else if( !strcasecmp( "shadow-level", psz_name ) )
738                             {
739                                 p_ssa_style->font_style.i_shadow_width = atoi( psz_value );
740                             }
741                             else if( !strcasecmp( "back-color", psz_name ) )
742                             {
743                                 if( *psz_value == '#' )
744                                 {
745                                     unsigned long col = strtol(psz_value+1, NULL, 16);
746                                     p_ssa_style->font_style.i_karaoke_background_color = (col & 0x00ffffff);
747                                     p_ssa_style->font_style.i_karaoke_background_alpha = (col >> 24) & 0xff;
748                                 }
749                             }
750                             else if( !strcasecmp( "spacing", psz_name ) )
751                             {
752                                 p_ssa_style->font_style.i_spacing = atoi( psz_value );
753                             }
754                         }
755                         free( psz_name );
756                         free( psz_value );
757                     }
758                 }
759                 else if( !strcasecmp( "position", psz_node ) && (i_style_level == 2) )
760                 {
761                     while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
762                     {
763                         char *psz_name = xml_ReaderName ( p_xml_reader );
764                         char *psz_value = xml_ReaderValue ( p_xml_reader );
765
766                         if( psz_name && psz_value )
767                         {
768                             if( !strcasecmp( "alignment", psz_name ) )
769                             {
770                                 if( !strcasecmp( "TopLeft", psz_value ) )
771                                     p_ssa_style->i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_LEFT;
772                                 else if( !strcasecmp( "TopCenter", psz_value ) )
773                                     p_ssa_style->i_align = SUBPICTURE_ALIGN_TOP;
774                                 else if( !strcasecmp( "TopRight", psz_value ) )
775                                     p_ssa_style->i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_RIGHT;
776                                 else if( !strcasecmp( "MiddleLeft", psz_value ) )
777                                     p_ssa_style->i_align = SUBPICTURE_ALIGN_LEFT;
778                                 else if( !strcasecmp( "MiddleCenter", psz_value ) )
779                                     p_ssa_style->i_align = 0;
780                                 else if( !strcasecmp( "MiddleRight", psz_value ) )
781                                     p_ssa_style->i_align = SUBPICTURE_ALIGN_RIGHT;
782                                 else if( !strcasecmp( "BottomLeft", psz_value ) )
783                                     p_ssa_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_LEFT;
784                                 else if( !strcasecmp( "BottomCenter", psz_value ) )
785                                     p_ssa_style->i_align = SUBPICTURE_ALIGN_BOTTOM;
786                                 else if( !strcasecmp( "BottomRight", psz_value ) )
787                                     p_ssa_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT;
788                             }
789                             else if( !strcasecmp( "horizontal-margin", psz_name ) )
790                             {
791                                 if( strchr( psz_value, '%' ) )
792                                 {
793                                     p_ssa_style->i_margin_h = 0;
794                                     p_ssa_style->i_margin_percent_h = atoi( psz_value );
795                                 }
796                                 else
797                                 {
798                                     p_ssa_style->i_margin_h = atoi( psz_value );
799                                     p_ssa_style->i_margin_percent_h = 0;
800                                 }
801                             }
802                             else if( !strcasecmp( "vertical-margin", psz_name ) )
803                             {
804                                 if( strchr( psz_value, '%' ) )
805                                 {
806                                     p_ssa_style->i_margin_v = 0;
807                                     p_ssa_style->i_margin_percent_v = atoi( psz_value );
808                                 }
809                                 else
810                                 {
811                                     p_ssa_style->i_margin_v = atoi( psz_value );
812                                     p_ssa_style->i_margin_percent_v = 0;
813                                 }
814                             }
815                         }
816                         free( psz_name );
817                         free( psz_value );
818                     }
819                 }
820
821                 free( psz_node );
822                 break;
823         }
824     }
825     free( p_ssa_style );
826 }
827
828
829
830 static subpicture_region_t *ParseUSFString( decoder_t *p_dec,
831                                             char *psz_subtitle )
832 {
833     decoder_sys_t        *p_sys = p_dec->p_sys;
834     subpicture_region_t  *p_region_first = NULL;
835     subpicture_region_t  *p_region_upto  = p_region_first;
836
837     while( *psz_subtitle )
838     {
839         if( *psz_subtitle == '<' )
840         {
841             char *psz_end = NULL;
842
843             if(( !strncasecmp( psz_subtitle, "<text ", 6 )) ||
844                ( !strncasecmp( psz_subtitle, "<text>", 6 )))
845             {
846                 psz_end = strcasestr( psz_subtitle, "</text>" );
847
848                 if( psz_end )
849                 {
850                     subpicture_region_t  *p_text_region;
851
852                     psz_end += strcspn( psz_end, ">" ) + 1;
853
854                     p_text_region = CreateTextRegion( p_dec,
855                                                       psz_subtitle,
856                                                       psz_end - psz_subtitle,
857                                                       p_sys->i_align );
858
859                     if( p_text_region )
860                     {
861                         p_text_region->psz_text = CreatePlainText( p_text_region->psz_html );
862
863                         if( ! var_CreateGetBool( p_dec, "subsdec-formatted" ) )
864                         {
865                             free( p_text_region->psz_html );
866                             p_text_region->psz_html = NULL;
867                         }
868                     }
869
870                     if( !p_region_first )
871                     {
872                         p_region_first = p_region_upto = p_text_region;
873                     }
874                     else if( p_text_region )
875                     {
876                         p_region_upto->p_next = p_text_region;
877                         p_region_upto = p_region_upto->p_next;
878                     }
879                 }
880             }
881             else if(( !strncasecmp( psz_subtitle, "<karaoke ", 9 )) ||
882                     ( !strncasecmp( psz_subtitle, "<karaoke>", 9 )))
883             {
884                 psz_end = strcasestr( psz_subtitle, "</karaoke>" );
885
886                 if( psz_end )
887                 {
888                     subpicture_region_t  *p_text_region;
889
890                     psz_end += strcspn( psz_end, ">" ) + 1;
891
892                     p_text_region = CreateTextRegion( p_dec,
893                                                       psz_subtitle,
894                                                       psz_end - psz_subtitle,
895                                                       p_sys->i_align );
896
897                     if( p_text_region )
898                     {
899                         if( ! var_CreateGetBool( p_dec, "subsdec-formatted" ) )
900                         {
901                             free( p_text_region->psz_html );
902                             p_text_region->psz_html = NULL;
903                         }
904                     }
905                     if( !p_region_first )
906                     {
907                         p_region_first = p_region_upto = p_text_region;
908                     }
909                     else if( p_text_region )
910                     {
911                         p_region_upto->p_next = p_text_region;
912                         p_region_upto = p_region_upto->p_next;
913                     }
914                 }
915             }
916             else if(( !strncasecmp( psz_subtitle, "<image ", 7 )) ||
917                     ( !strncasecmp( psz_subtitle, "<image>", 7 )))
918             {
919                 subpicture_region_t *p_image_region = NULL;
920
921                 char *psz_end = strcasestr( psz_subtitle, "</image>" );
922                 char *psz_content = strchr( psz_subtitle, '>' );
923                 int   i_transparent = -1;
924
925                 /* If a colorkey parameter is specified, then we have to map
926                  * that index in the picture through as transparent (it is
927                  * required by the USF spec but is also recommended that if the
928                  * creator really wants a transparent colour that they use a
929                  * type like PNG that properly supports it; this goes doubly
930                  * for VLC because the pictures are stored internally in YUV
931                  * and the resulting colour-matching may not produce the
932                  * desired results.)
933                  */
934                 char *psz_tmp = GrabAttributeValue( "colorkey", psz_subtitle );
935                 if( psz_tmp )
936                 {
937                     if( *psz_tmp == '#' )
938                         i_transparent = strtol( psz_tmp + 1, NULL, 16 ) & 0x00ffffff;
939                     free( psz_tmp );
940                 }
941                 if( psz_content && ( psz_content < psz_end ) )
942                 {
943                     char *psz_filename = strndup( &psz_content[1], psz_end - &psz_content[1] );
944                     if( psz_filename )
945                     {
946                         p_image_region = LoadEmbeddedImage( p_dec,
947                                             psz_filename, i_transparent );
948                         free( psz_filename );
949                     }
950                 }
951
952                 if( psz_end ) psz_end += strcspn( psz_end, ">" ) + 1;
953
954                 if( p_image_region )
955                 {
956                     SetupPositions( p_image_region, psz_subtitle );
957
958                     p_image_region->p_next   = NULL;
959                     p_image_region->psz_text = NULL;
960                     p_image_region->psz_html = NULL;
961
962                 }
963                 if( !p_region_first )
964                 {
965                     p_region_first = p_region_upto = p_image_region;
966                 }
967                 else if( p_image_region )
968                 {
969                     p_region_upto->p_next = p_image_region;
970                     p_region_upto = p_region_upto->p_next;
971                 }
972             }
973             if( psz_end )
974                 psz_subtitle = psz_end - 1;
975
976             psz_subtitle += strcspn( psz_subtitle, ">" );
977         }
978
979         psz_subtitle++;
980     }
981
982     return p_region_first;
983 }
984
985 /*****************************************************************************
986  * ParseUSFHeader: Retrieve global formatting information etc
987  *****************************************************************************/
988 static void ParseUSFHeader( decoder_t *p_dec )
989 {
990     stream_t      *p_sub = NULL;
991     xml_t         *p_xml = NULL;
992     xml_reader_t  *p_xml_reader = NULL;
993
994     p_sub = stream_MemoryNew( VLC_OBJECT(p_dec),
995                               p_dec->fmt_in.p_extra,
996                               p_dec->fmt_in.i_extra,
997                               true );
998     if( !p_sub )
999         return;
1000
1001     p_xml = xml_Create( p_dec );
1002     if( p_xml )
1003     {
1004         p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
1005         if( p_xml_reader )
1006         {
1007             /* Look for Root Node */
1008             if( xml_ReaderRead( p_xml_reader ) == 1 )
1009             {
1010                 char *psz_node = xml_ReaderName( p_xml_reader );
1011
1012                 if( !strcasecmp( "usfsubtitles", psz_node ) )
1013                     ParseUSFHeaderTags( p_dec, p_xml_reader );
1014
1015                 free( psz_node );
1016             }
1017
1018             xml_ReaderDelete( p_xml, p_xml_reader );
1019         }
1020         xml_Delete( p_xml );
1021     }
1022     stream_Delete( p_sub );
1023 }
1024
1025 /* Function now handles tags which has attribute values, and tries
1026  * to deal with &' commands too. It no longer modifies the string
1027  * in place, so that the original text can be reused
1028  */
1029 static char *StripTags( char *psz_subtitle )
1030 {
1031     char *psz_text_start;
1032     char *psz_text;
1033
1034     psz_text = psz_text_start = malloc( strlen( psz_subtitle ) + 1 );
1035     if( !psz_text_start )
1036         return NULL;
1037
1038     while( *psz_subtitle )
1039     {
1040         /* Mask out any pre-existing LFs in the subtitle */
1041         if( *psz_subtitle == '\n' )
1042             *psz_subtitle = ' ';
1043
1044         if( *psz_subtitle == '<' )
1045         {
1046             if( strncasecmp( psz_subtitle, "<br/>", 5 ) == 0 )
1047                 *psz_text++ = '\n';
1048
1049             psz_subtitle += strcspn( psz_subtitle, ">" );
1050         }
1051         else if( *psz_subtitle == '&' )
1052         {
1053             if( !strncasecmp( psz_subtitle, "&lt;", 4 ))
1054             {
1055                 *psz_text++ = '<';
1056                 psz_subtitle += strcspn( psz_subtitle, ";" );
1057             }
1058             else if( !strncasecmp( psz_subtitle, "&gt;", 4 ))
1059             {
1060                 *psz_text++ = '>';
1061                 psz_subtitle += strcspn( psz_subtitle, ";" );
1062             }
1063             else if( !strncasecmp( psz_subtitle, "&amp;", 5 ))
1064             {
1065                 *psz_text++ = '&';
1066                 psz_subtitle += strcspn( psz_subtitle, ";" );
1067             }
1068             else if( !strncasecmp( psz_subtitle, "&quot;", 6 ))
1069             {
1070                 *psz_text++ = '\"';
1071                 psz_subtitle += strcspn( psz_subtitle, ";" );
1072             }
1073             else
1074             {
1075                 /* Assume it is just a normal ampersand */
1076                 *psz_text++ = '&';
1077             }
1078         }
1079         else
1080         {
1081             *psz_text++ = *psz_subtitle;
1082         }
1083
1084         psz_subtitle++;
1085     }
1086     *psz_text = '\0';
1087
1088     char *psz = realloc( psz_text_start, strlen( psz_text_start ) + 1 );
1089     if( psz ) psz_text_start = psz;
1090
1091     return psz_text_start;
1092 }
1093
1094 /* Turn a HTML subtitle, turn into a plain-text version,
1095  *  complete with sensible whitespace compaction
1096  */
1097
1098 static char *CreatePlainText( char *psz_subtitle )
1099 {
1100     char *psz_text = StripTags( psz_subtitle );
1101     char *s;
1102
1103     if( !psz_text )
1104         return NULL;
1105
1106     s = strpbrk( psz_text, "\t\r\n " );
1107     while( s )
1108     {
1109         int   k;
1110         char  spc = ' ';
1111         int   i_whitespace = strspn( s, "\t\r\n " );
1112
1113         /* Favour '\n' over other whitespaces - if one of these
1114          * occurs in the whitespace use a '\n' as our value,
1115          * otherwise just use a ' '
1116          */
1117         for( k = 0; k < i_whitespace; k++ )
1118             if( s[k] == '\n' ) spc = '\n';
1119
1120         if( i_whitespace > 1 )
1121         {
1122             memmove( &s[1],
1123                      &s[i_whitespace],
1124                      strlen( s ) - i_whitespace + 1 );
1125         }
1126         *s++ = spc;
1127
1128         s = strpbrk( s, "\t\r\n " );
1129     }
1130     return psz_text;
1131 }
1132
1133 /****************************************************************************
1134  * download and resize image located at psz_url
1135  ***************************************************************************/
1136 static subpicture_region_t *LoadEmbeddedImage( decoder_t *p_dec,
1137                                                const char *psz_filename,
1138                                                int i_transparent_color )
1139 {
1140     decoder_sys_t         *p_sys = p_dec->p_sys;
1141     subpicture_region_t   *p_region;
1142     video_format_t         fmt_out;
1143     picture_t             *p_pic = NULL;
1144
1145     for( int k = 0; k < p_sys->i_images; k++ )
1146     {
1147         if( p_sys->pp_images &&
1148             !strcmp( p_sys->pp_images[k]->psz_filename, psz_filename ) )
1149         {
1150             p_pic = p_sys->pp_images[k]->p_pic;
1151             break;
1152         }
1153     }
1154
1155     if( !p_pic )
1156     {
1157         msg_Err( p_dec, "Unable to read image %s", psz_filename );
1158         return NULL;
1159     }
1160
1161     /* Display the feed's image */
1162     memset( &fmt_out, 0, sizeof( video_format_t));
1163
1164     fmt_out.i_chroma = VLC_CODEC_YUVA;
1165     fmt_out.i_sar_num = fmt_out.i_sar_den = 1;
1166     fmt_out.i_width =
1167         fmt_out.i_visible_width = p_pic->format.i_visible_width;
1168     fmt_out.i_height =
1169         fmt_out.i_visible_height = p_pic->format.i_visible_height;
1170
1171     p_region = subpicture_region_New( &fmt_out );
1172     if( !p_region )
1173     {
1174         msg_Err( p_dec, "cannot allocate SPU region" );
1175         return NULL;
1176     }
1177     assert( p_pic->format.i_chroma == VLC_CODEC_YUVA );
1178     /* FIXME the copy is probably not needed anymore */
1179     picture_CopyPixels( p_region->p_picture, p_pic );
1180
1181     /* This isn't the best way to do this - if you really want transparency, then
1182      * you're much better off using an image type that supports it like PNG. The
1183      * spec requires this support though.
1184      */
1185     if( i_transparent_color > 0 )
1186     {
1187         int i_r = ( i_transparent_color >> 16 ) & 0xff;
1188         int i_g = ( i_transparent_color >>  8 ) & 0xff;
1189         int i_b = ( i_transparent_color       ) & 0xff;
1190
1191         /* FIXME it cannot work as the yuv conversion code will probably NOT match
1192          * this one  */
1193         int i_y = ( ( (  66 * i_r + 129 * i_g +  25 * i_b + 128 ) >> 8 ) + 16 );
1194         int i_u =   ( ( -38 * i_r -  74 * i_g + 112 * i_b + 128 ) >> 8 ) + 128 ;
1195         int i_v =   ( ( 112 * i_r -  94 * i_g -  18 * i_b + 128 ) >> 8 ) + 128 ;
1196
1197         assert( p_region->fmt.i_chroma == VLC_CODEC_YUVA );
1198         for( unsigned int y = 0; y < p_region->fmt.i_height; y++ )
1199         {
1200             for( unsigned int x = 0; x < p_region->fmt.i_width; x++ )
1201             {
1202                 if( p_region->p_picture->Y_PIXELS[y*p_region->p_picture->Y_PITCH + x] != i_y ||
1203                     p_region->p_picture->U_PIXELS[y*p_region->p_picture->U_PITCH + x] != i_u ||
1204                     p_region->p_picture->V_PIXELS[y*p_region->p_picture->V_PITCH + x] != i_v )
1205                     continue;
1206                 p_region->p_picture->A_PIXELS[y*p_region->p_picture->A_PITCH + x] = 0;
1207
1208             }
1209         }
1210     }
1211     return p_region;
1212 }