1 /*****************************************************************************
2 * subsusf.c : USF subtitles decoder
3 *****************************************************************************
4 * Copyright (C) 2000-2006 VLC authors and VideoLAN
7 * Authors: Bernie Purcell <bitmap@videolan.org>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
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 Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
28 #include <vlc_common.h>
29 #include <vlc_plugin.h>
30 #include <vlc_modules.h>
31 #include <vlc_codec.h>
32 #include <vlc_input.h>
33 #include <vlc_charset.h>
34 #include <vlc_image.h>
36 #include <vlc_stream.h>
38 /*****************************************************************************
40 *****************************************************************************/
41 static int OpenDecoder ( vlc_object_t
* );
42 static void CloseDecoder ( vlc_object_t
* );
44 #define FORMAT_TEXT N_("Formatted Subtitles")
45 #define FORMAT_LONGTEXT N_("Some subtitle formats allow for text formatting. " \
46 "VLC partly implements this, but you can choose to disable all formatting.")
49 set_capability( "spu decoder", 40 )
50 set_shortname( N_("USFSubs"))
51 set_description( N_("USF subtitles decoder") )
52 set_callbacks( OpenDecoder
, CloseDecoder
)
53 set_category( CAT_INPUT
)
54 set_subcategory( SUBCAT_INPUT_SCODEC
)
55 add_bool( "subsdec-formatted", true, FORMAT_TEXT
, FORMAT_LONGTEXT
,
60 /*****************************************************************************
62 *****************************************************************************/
65 ATTRIBUTE_ALIGNMENT
= (1 << 0),
66 ATTRIBUTE_X
= (1 << 1),
67 ATTRIBUTE_X_PERCENT
= (1 << 2),
68 ATTRIBUTE_Y
= (1 << 3),
69 ATTRIBUTE_Y_PERCENT
= (1 << 4),
80 char * psz_stylename
; /* The name of the style, no comma's allowed */
81 text_style_t
* p_style
;
85 int i_margin_percent_h
;
86 int i_margin_percent_v
;
91 int i_original_height
;
93 int i_align
; /* Subtitles alignment on the vout */
95 ssa_style_t
**pp_ssa_styles
;
98 image_attach_t
**pp_images
;
102 static int DecodeBlock ( decoder_t
*, block_t
* );
103 static char *CreatePlainText( char * );
104 static int ParseImageAttachments( decoder_t
*p_dec
);
106 static subpicture_t
*ParseText ( decoder_t
*, block_t
* );
107 static void ParseUSFHeader( decoder_t
* );
108 static subpicture_region_t
*ParseUSFString( decoder_t
*, char * );
109 static subpicture_region_t
*LoadEmbeddedImage( decoder_t
*p_dec
, const char *psz_filename
, int i_transparent_color
);
111 /*****************************************************************************
112 * OpenDecoder: probe the decoder and return score
113 *****************************************************************************
114 * Tries to launch a decoder and return score so that the interface is able
116 *****************************************************************************/
117 static int OpenDecoder( vlc_object_t
*p_this
)
119 decoder_t
*p_dec
= (decoder_t
*)p_this
;
120 decoder_sys_t
*p_sys
;
122 if( p_dec
->fmt_in
.i_codec
!= VLC_CODEC_USF
)
125 /* Allocate the memory needed to store the decoder's structure */
126 if( ( p_dec
->p_sys
= p_sys
= calloc(1, sizeof(decoder_sys_t
)) ) == NULL
)
129 p_dec
->pf_decode
= DecodeBlock
;
130 p_dec
->fmt_out
.i_codec
= 0;
133 TAB_INIT( p_sys
->i_ssa_styles
, p_sys
->pp_ssa_styles
);
134 TAB_INIT( p_sys
->i_images
, p_sys
->pp_images
);
136 /* USF subtitles are mandated to be UTF-8, so don't need vlc_iconv */
138 p_sys
->i_align
= var_CreateGetInteger( p_dec
, "subsdec-align" );
140 ParseImageAttachments( p_dec
);
142 if( var_CreateGetBool( p_dec
, "subsdec-formatted" ) )
144 if( p_dec
->fmt_in
.i_extra
> 0 )
145 ParseUSFHeader( p_dec
);
151 /****************************************************************************
152 * DecodeBlock: the whole thing
153 ****************************************************************************
154 * This function must be fed with complete subtitles units.
155 ****************************************************************************/
156 static int DecodeBlock( decoder_t
*p_dec
, block_t
*p_block
)
160 if( p_block
== NULL
) /* No Drain */
161 return VLCDEC_SUCCESS
;
163 p_spu
= ParseText( p_dec
, p_block
);
165 block_Release( p_block
);
167 decoder_QueueSub( p_dec
, p_spu
);
168 return VLCDEC_SUCCESS
;
171 /*****************************************************************************
172 * CloseDecoder: clean up the decoder
173 *****************************************************************************/
174 static void CloseDecoder( vlc_object_t
*p_this
)
176 decoder_t
*p_dec
= (decoder_t
*)p_this
;
177 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
179 if( p_sys
->pp_ssa_styles
)
181 for( int i
= 0; i
< p_sys
->i_ssa_styles
; i
++ )
183 if( !p_sys
->pp_ssa_styles
[i
] )
186 free( p_sys
->pp_ssa_styles
[i
]->psz_stylename
);
187 text_style_Delete( p_sys
->pp_ssa_styles
[i
]->p_style
);
188 free( p_sys
->pp_ssa_styles
[i
] );
190 TAB_CLEAN( p_sys
->i_ssa_styles
, p_sys
->pp_ssa_styles
);
192 if( p_sys
->pp_images
)
194 for( int i
= 0; i
< p_sys
->i_images
; i
++ )
196 if( !p_sys
->pp_images
[i
] )
199 if( p_sys
->pp_images
[i
]->p_pic
)
200 picture_Release( p_sys
->pp_images
[i
]->p_pic
);
201 free( p_sys
->pp_images
[i
]->psz_filename
);
203 free( p_sys
->pp_images
[i
] );
205 TAB_CLEAN( p_sys
->i_images
, p_sys
->pp_images
);
211 /*****************************************************************************
212 * ParseText: parse an text subtitle packet and send it to the video output
213 *****************************************************************************/
214 static subpicture_t
*ParseText( decoder_t
*p_dec
, block_t
*p_block
)
216 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
217 subpicture_t
*p_spu
= NULL
;
218 char *psz_subtitle
= NULL
;
220 if( p_block
->i_flags
& BLOCK_FLAG_CORRUPTED
)
223 /* We cannot display a subpicture with no date */
224 if( p_block
->i_pts
<= VLC_TS_INVALID
)
226 msg_Warn( p_dec
, "subtitle without a date" );
230 /* Check validity of packet data */
231 /* An "empty" line containing only \0 can be used to force
232 and ephemer picture from the screen */
233 if( p_block
->i_buffer
< 1 )
235 msg_Warn( p_dec
, "no subtitle data" );
239 /* Should be resiliant against bad subtitles */
240 psz_subtitle
= strndup( (const char *)p_block
->p_buffer
,
242 if( psz_subtitle
== NULL
)
245 /* USF Subtitles are mandated to be UTF-8 -- make sure it is */
246 if (EnsureUTF8( psz_subtitle
) == NULL
)
248 msg_Err( p_dec
, "USF subtitles must be in UTF-8 format.\n"
249 "This stream contains USF subtitles which aren't." );
252 /* Create the subpicture unit */
253 p_spu
= decoder_NewSubpicture( p_dec
, NULL
);
256 msg_Warn( p_dec
, "can't get spu buffer" );
257 free( psz_subtitle
);
261 /* Decode USF strings */
262 p_spu
->p_region
= ParseUSFString( p_dec
, psz_subtitle
);
264 p_spu
->i_start
= p_block
->i_pts
;
265 p_spu
->i_stop
= p_block
->i_pts
+ p_block
->i_length
;
266 p_spu
->b_ephemer
= (p_block
->i_length
== 0);
267 p_spu
->b_absolute
= false;
268 p_spu
->i_original_picture_width
= p_sys
->i_original_width
;
269 p_spu
->i_original_picture_height
= p_sys
->i_original_height
;
271 free( psz_subtitle
);
276 static char *GrabAttributeValue( const char *psz_attribute
,
277 const char *psz_tag_start
)
279 if( psz_attribute
&& psz_tag_start
)
281 char *psz_tag_end
= strchr( psz_tag_start
, '>' );
282 char *psz_found
= strcasestr( psz_tag_start
, psz_attribute
);
286 psz_found
+= strlen( psz_attribute
);
288 if(( *(psz_found
++) == '=' ) &&
289 ( *(psz_found
++) == '\"' ))
291 if( psz_found
< psz_tag_end
)
293 int i_len
= strcspn( psz_found
, "\"" );
294 return strndup( psz_found
, i_len
);
302 static ssa_style_t
*ParseStyle( decoder_sys_t
*p_sys
, char *psz_subtitle
)
304 ssa_style_t
*p_ssa_style
= NULL
;
305 char *psz_style
= GrabAttributeValue( "style", psz_subtitle
);
309 for( int i
= 0; i
< p_sys
->i_ssa_styles
; i
++ )
311 if( !strcmp( p_sys
->pp_ssa_styles
[i
]->psz_stylename
, psz_style
) )
312 p_ssa_style
= p_sys
->pp_ssa_styles
[i
];
319 static int ParsePositionAttributeList( char *psz_subtitle
, int *i_align
,
324 char *psz_align
= GrabAttributeValue( "alignment", psz_subtitle
);
325 char *psz_margin_x
= GrabAttributeValue( "horizontal-margin", psz_subtitle
);
326 char *psz_margin_y
= GrabAttributeValue( "vertical-margin", psz_subtitle
);
328 char *psz_relative = GrabAttributeValue( "relative-to", psz_subtitle );
329 char *psz_rotate_x = GrabAttributeValue( "rotate-x", psz_subtitle );
330 char *psz_rotate_y = GrabAttributeValue( "rotate-y", psz_subtitle );
331 char *psz_rotate_z = GrabAttributeValue( "rotate-z", psz_subtitle );
334 *i_align
= SUBPICTURE_ALIGN_BOTTOM
;
340 if( !strcasecmp( "TopLeft", psz_align
) )
341 *i_align
= SUBPICTURE_ALIGN_TOP
| SUBPICTURE_ALIGN_LEFT
;
342 else if( !strcasecmp( "TopCenter", psz_align
) )
343 *i_align
= SUBPICTURE_ALIGN_TOP
;
344 else if( !strcasecmp( "TopRight", psz_align
) )
345 *i_align
= SUBPICTURE_ALIGN_TOP
| SUBPICTURE_ALIGN_RIGHT
;
346 else if( !strcasecmp( "MiddleLeft", psz_align
) )
347 *i_align
= SUBPICTURE_ALIGN_LEFT
;
348 else if( !strcasecmp( "MiddleCenter", psz_align
) )
350 else if( !strcasecmp( "MiddleRight", psz_align
) )
351 *i_align
= SUBPICTURE_ALIGN_RIGHT
;
352 else if( !strcasecmp( "BottomLeft", psz_align
) )
353 *i_align
= SUBPICTURE_ALIGN_BOTTOM
| SUBPICTURE_ALIGN_LEFT
;
354 else if( !strcasecmp( "BottomCenter", psz_align
) )
355 *i_align
= SUBPICTURE_ALIGN_BOTTOM
;
356 else if( !strcasecmp( "BottomRight", psz_align
) )
357 *i_align
= SUBPICTURE_ALIGN_BOTTOM
| SUBPICTURE_ALIGN_RIGHT
;
359 i_mask
|= ATTRIBUTE_ALIGNMENT
;
364 *i_x
= atoi( psz_margin_x
);
365 if( strchr( psz_margin_x
, '%' ) )
366 i_mask
|= ATTRIBUTE_X_PERCENT
;
368 i_mask
|= ATTRIBUTE_X
;
370 free( psz_margin_x
);
374 *i_y
= atoi( psz_margin_y
);
375 if( strchr( psz_margin_y
, '%' ) )
376 i_mask
|= ATTRIBUTE_Y_PERCENT
;
378 i_mask
|= ATTRIBUTE_Y
;
380 free( psz_margin_y
);
385 static void SetupPositions( subpicture_region_t
*p_region
, char *psz_subtitle
)
391 i_mask
= ParsePositionAttributeList( psz_subtitle
, &i_align
, &i_x
, &i_y
);
393 if( i_mask
& ATTRIBUTE_ALIGNMENT
)
394 p_region
->i_align
= i_align
;
396 /* TODO: Setup % based offsets properly, without adversely affecting
397 * everything else in vlc. Will address with separate patch, to
398 * prevent this one being any more complicated.
400 if( i_mask
& ATTRIBUTE_X
)
402 else if( i_mask
& ATTRIBUTE_X_PERCENT
)
405 if( i_mask
& ATTRIBUTE_Y
)
407 else if( i_mask
& ATTRIBUTE_Y_PERCENT
)
411 static subpicture_region_t
*CreateTextRegion( decoder_t
*p_dec
,
415 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
416 subpicture_region_t
*p_text_region
;
419 /* Create a new subpicture region */
420 video_format_Init( &fmt
, VLC_CODEC_TEXT
);
421 fmt
.i_width
= fmt
.i_height
= 0;
422 fmt
.i_x_offset
= fmt
.i_y_offset
= 0;
423 p_text_region
= subpicture_region_New( &fmt
);
424 video_format_Clean( &fmt
);
426 if( p_text_region
!= NULL
)
428 ssa_style_t
*p_ssa_style
= NULL
;
430 p_ssa_style
= ParseStyle( p_sys
, psz_subtitle
);
433 for( int i
= 0; i
< p_sys
->i_ssa_styles
; i
++ )
435 if( !strcasecmp( p_sys
->pp_ssa_styles
[i
]->psz_stylename
, "Default" ) )
436 p_ssa_style
= p_sys
->pp_ssa_styles
[i
];
442 msg_Dbg( p_dec
, "style is: %s", p_ssa_style
->psz_stylename
);
444 p_text_region
->i_align
= p_ssa_style
->i_align
;
446 /* TODO: Setup % based offsets properly, without adversely affecting
447 * everything else in vlc. Will address with separate patch,
448 * to prevent this one being any more complicated.
450 * p_ssa_style->i_margin_percent_h;
451 * p_ssa_style->i_margin_percent_v;
453 p_text_region
->i_x
= p_ssa_style
->i_margin_h
;
454 p_text_region
->i_y
= p_ssa_style
->i_margin_v
;
455 p_text_region
->p_text
= text_segment_NewInheritStyle( p_ssa_style
->p_style
);
459 p_text_region
->i_align
= SUBPICTURE_ALIGN_BOTTOM
| i_sys_align
;
460 p_text_region
->i_x
= i_sys_align
? 20 : 0;
461 p_text_region
->i_y
= 10;
462 p_text_region
->p_text
= text_segment_New( NULL
);
464 /* Look for position arguments which may override the style-based
467 SetupPositions( p_text_region
, psz_subtitle
);
469 p_text_region
->p_next
= NULL
;
471 return p_text_region
;
474 static int ParseImageAttachments( decoder_t
*p_dec
)
476 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
477 input_attachment_t
**pp_attachments
;
478 int i_attachments_cnt
;
480 if( VLC_SUCCESS
!= decoder_GetInputAttachments( p_dec
, &pp_attachments
, &i_attachments_cnt
))
483 for( int k
= 0; k
< i_attachments_cnt
; k
++ )
485 input_attachment_t
*p_attach
= pp_attachments
[k
];
487 vlc_fourcc_t type
= image_Mime2Fourcc( p_attach
->psz_mime
);
490 ( p_attach
->i_data
> 0 ) &&
491 ( p_attach
->p_data
!= NULL
) )
493 picture_t
*p_pic
= NULL
;
494 image_handler_t
*p_image
;
496 p_image
= image_HandlerCreate( p_dec
);
497 if( p_image
!= NULL
)
501 p_block
= block_Alloc( p_attach
->i_data
);
503 if( p_block
!= NULL
)
505 video_format_t fmt_in
;
506 video_format_t fmt_out
;
508 memcpy( p_block
->p_buffer
, p_attach
->p_data
, p_attach
->i_data
);
510 memset( &fmt_in
, 0, sizeof( video_format_t
));
511 memset( &fmt_out
, 0, sizeof( video_format_t
));
513 fmt_in
.i_chroma
= type
;
514 fmt_out
.i_chroma
= VLC_CODEC_YUVA
;
516 /* Find a suitable decoder module */
517 if( module_exists( "sdl_image" ) )
519 /* ffmpeg thinks it can handle bmp properly but it can't (at least
520 * not all of them), so use sdl_image if it is available */
522 var_Create( p_dec
, "codec", VLC_VAR_STRING
| VLC_VAR_DOINHERIT
);
523 var_SetString( p_dec
, "codec", "sdl_image" );
526 p_pic
= image_Read( p_image
, p_block
, &fmt_in
, &fmt_out
);
527 var_Destroy( p_dec
, "codec" );
530 image_HandlerDelete( p_image
);
534 image_attach_t
*p_picture
= malloc( sizeof(image_attach_t
) );
538 p_picture
->psz_filename
= strdup( p_attach
->psz_name
);
539 p_picture
->p_pic
= p_pic
;
541 TAB_APPEND( p_sys
->i_images
, p_sys
->pp_images
, p_picture
);
545 vlc_input_attachment_Delete( pp_attachments
[ k
] );
547 free( pp_attachments
);
552 static void ParseUSFHeaderTags( decoder_t
*p_dec
, xml_reader_t
*p_xml_reader
)
554 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
556 ssa_style_t
*p_ssa_style
= NULL
;
557 int i_style_level
= 0;
558 int i_metadata_level
= 0;
561 while( (type
= xml_ReaderNextNode( p_xml_reader
, &node
)) > 0 )
565 case XML_READER_ENDELEM
:
566 switch (i_style_level
)
569 if( !strcasecmp( "metadata", node
) && (i_metadata_level
== 1) )
573 if( !strcasecmp( "styles", node
) )
577 if( !strcasecmp( "style", node
) )
579 TAB_APPEND( p_sys
->i_ssa_styles
, p_sys
->pp_ssa_styles
, p_ssa_style
);
588 case XML_READER_STARTELEM
:
589 if( !strcasecmp( "metadata", node
) && (i_style_level
== 0) )
591 else if( !strcasecmp( "resolution", node
) &&
592 ( i_metadata_level
== 1) )
594 const char *attr
, *val
;
595 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &val
)) )
597 if( !strcasecmp( "x", attr
) )
598 p_sys
->i_original_width
= atoi( val
);
599 else if( !strcasecmp( "y", attr
) )
600 p_sys
->i_original_height
= atoi( val
);
603 else if( !strcasecmp( "styles", node
) && (i_style_level
== 0) )
607 else if( !strcasecmp( "style", node
) && (i_style_level
== 1) )
611 p_ssa_style
= calloc( 1, sizeof(ssa_style_t
) );
612 if( unlikely(!p_ssa_style
) )
614 p_ssa_style
->p_style
= text_style_Create( STYLE_NO_DEFAULTS
);
615 if( unlikely(!p_ssa_style
->p_style
) )
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
625 for( int i
= 0; i
< p_sys
->i_ssa_styles
; i
++ )
627 if( !strcasecmp( p_sys
->pp_ssa_styles
[i
]->psz_stylename
, "Default" ) )
629 ssa_style_t
*p_default_style
= p_sys
->pp_ssa_styles
[i
];
630 text_style_t
*p_orig_text_style
= p_ssa_style
->p_style
;
632 memcpy( p_ssa_style
, p_default_style
, sizeof( ssa_style_t
) );
634 // reset data-members that are not to be overwritten
635 p_ssa_style
->p_style
= p_orig_text_style
;
636 p_ssa_style
->psz_stylename
= NULL
;
638 //FIXME: Make font_style a pointer. Actually we double copy some data here,
639 // we use text_style_Copy to avoid copying psz_fontname, though .
640 text_style_Copy( p_ssa_style
->p_style
, p_default_style
->p_style
);
644 const char *attr
, *val
;
645 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &val
)) )
647 if( !strcasecmp( "name", attr
) )
649 free( p_ssa_style
->psz_stylename
);
650 p_ssa_style
->psz_stylename
= strdup( val
);
654 else if( !strcasecmp( "fontstyle", node
) && (i_style_level
== 2) )
656 const char *attr
, *val
;
657 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &val
)) )
659 if( !strcasecmp( "face", attr
) )
661 free( p_ssa_style
->p_style
->psz_fontname
);
662 p_ssa_style
->p_style
->psz_fontname
= strdup( val
);
664 else if( !strcasecmp( "size", attr
) )
666 if( ( *val
== '+' ) || ( *val
== '-' ) )
668 int i_value
= atoi( val
);
670 if( ( i_value
>= -5 ) && ( i_value
<= 5 ) )
671 p_ssa_style
->p_style
->i_font_size
+=
672 ( i_value
* p_ssa_style
->p_style
->i_font_size
) / 10;
673 else if( i_value
< -5 )
674 p_ssa_style
->p_style
->i_font_size
= - i_value
;
675 else if( i_value
> 5 )
676 p_ssa_style
->p_style
->i_font_size
= i_value
;
679 p_ssa_style
->p_style
->i_font_size
= atoi( val
);
681 else if( !strcasecmp( "italic", attr
) )
683 if( !strcasecmp( "yes", val
))
684 p_ssa_style
->p_style
->i_style_flags
|= STYLE_ITALIC
;
686 p_ssa_style
->p_style
->i_style_flags
&= ~STYLE_ITALIC
;
687 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_FLAGS
;
689 else if( !strcasecmp( "weight", attr
) )
691 if( !strcasecmp( "bold", val
))
692 p_ssa_style
->p_style
->i_style_flags
|= STYLE_BOLD
;
694 p_ssa_style
->p_style
->i_style_flags
&= ~STYLE_BOLD
;
695 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_FLAGS
;
697 else if( !strcasecmp( "underline", attr
) )
699 if( !strcasecmp( "yes", val
))
700 p_ssa_style
->p_style
->i_style_flags
|= STYLE_UNDERLINE
;
702 p_ssa_style
->p_style
->i_style_flags
&= ~STYLE_UNDERLINE
;
703 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_FLAGS
;
705 else if( !strcasecmp( "color", attr
) )
709 unsigned long col
= strtol(val
+1, NULL
, 16);
710 p_ssa_style
->p_style
->i_font_color
= (col
& 0x00ffffff);
711 p_ssa_style
->p_style
->i_font_alpha
= (col
>> 24) & 0xff;
712 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_FONT_COLOR
713 | STYLE_HAS_FONT_ALPHA
;
716 else if( !strcasecmp( "outline-color", attr
) )
720 unsigned long col
= strtol(val
+1, NULL
, 16);
721 p_ssa_style
->p_style
->i_outline_color
= (col
& 0x00ffffff);
722 p_ssa_style
->p_style
->i_outline_alpha
= (col
>> 24) & 0xff;
723 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_OUTLINE_COLOR
724 | STYLE_HAS_OUTLINE_ALPHA
;
727 else if( !strcasecmp( "outline-level", attr
) )
729 p_ssa_style
->p_style
->i_outline_width
= atoi( val
);
731 else if( !strcasecmp( "shadow-color", attr
) )
735 unsigned long col
= strtol(val
+1, NULL
, 16);
736 p_ssa_style
->p_style
->i_shadow_color
= (col
& 0x00ffffff);
737 p_ssa_style
->p_style
->i_shadow_alpha
= (col
>> 24) & 0xff;
738 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_SHADOW_COLOR
739 | STYLE_HAS_SHADOW_ALPHA
;
742 else if( !strcasecmp( "shadow-level", attr
) )
744 p_ssa_style
->p_style
->i_shadow_width
= atoi( val
);
746 else if( !strcasecmp( "back-color", attr
) )
750 unsigned long col
= strtol(val
+1, NULL
, 16);
751 p_ssa_style
->p_style
->i_karaoke_background_color
= (col
& 0x00ffffff);
752 p_ssa_style
->p_style
->i_karaoke_background_alpha
= (col
>> 24) & 0xff;
753 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_K_BACKGROUND_COLOR
754 | STYLE_HAS_K_BACKGROUND_ALPHA
;
757 else if( !strcasecmp( "spacing", attr
) )
759 p_ssa_style
->p_style
->i_spacing
= atoi( val
);
763 else if( !strcasecmp( "position", node
) && (i_style_level
== 2) )
765 const char *attr
, *val
;
766 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &val
)) )
768 if( !strcasecmp( "alignment", attr
) )
770 if( !strcasecmp( "TopLeft", val
) )
771 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_TOP
| SUBPICTURE_ALIGN_LEFT
;
772 else if( !strcasecmp( "TopCenter", val
) )
773 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_TOP
;
774 else if( !strcasecmp( "TopRight", val
) )
775 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_TOP
| SUBPICTURE_ALIGN_RIGHT
;
776 else if( !strcasecmp( "MiddleLeft", val
) )
777 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_LEFT
;
778 else if( !strcasecmp( "MiddleCenter", val
) )
779 p_ssa_style
->i_align
= 0;
780 else if( !strcasecmp( "MiddleRight", val
) )
781 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_RIGHT
;
782 else if( !strcasecmp( "BottomLeft", val
) )
783 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_BOTTOM
| SUBPICTURE_ALIGN_LEFT
;
784 else if( !strcasecmp( "BottomCenter", val
) )
785 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_BOTTOM
;
786 else if( !strcasecmp( "BottomRight", val
) )
787 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_BOTTOM
| SUBPICTURE_ALIGN_RIGHT
;
789 else if( !strcasecmp( "horizontal-margin", attr
) )
791 if( strchr( val
, '%' ) )
793 p_ssa_style
->i_margin_h
= 0;
794 p_ssa_style
->i_margin_percent_h
= atoi( val
);
798 p_ssa_style
->i_margin_h
= atoi( val
);
799 p_ssa_style
->i_margin_percent_h
= 0;
802 else if( !strcasecmp( "vertical-margin", attr
) )
804 if( strchr( val
, '%' ) )
806 p_ssa_style
->i_margin_v
= 0;
807 p_ssa_style
->i_margin_percent_v
= atoi( val
);
811 p_ssa_style
->i_margin_v
= atoi( val
);
812 p_ssa_style
->i_margin_percent_v
= 0;
825 static subpicture_region_t
*ParseUSFString( decoder_t
*p_dec
,
828 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
829 subpicture_region_t
*p_region_first
= NULL
;
830 subpicture_region_t
*p_region_upto
= p_region_first
;
832 while( *psz_subtitle
)
834 if( *psz_subtitle
== '<' )
836 char *psz_end
= NULL
;
839 if(( !strncasecmp( psz_subtitle
, "<karaoke ", 9 )) ||
840 ( !strncasecmp( psz_subtitle
, "<karaoke>", 9 )))
842 psz_end
= strcasestr( psz_subtitle
, "</karaoke>" );
846 subpicture_region_t
*p_text_region
;
848 psz_end
+= strcspn( psz_end
, ">" ) + 1;
850 p_text_region
= CreateTextRegion( p_dec
,
854 if( !p_region_first
)
856 p_region_first
= p_region_upto
= p_text_region
;
858 else if( p_text_region
)
860 p_region_upto
->p_next
= p_text_region
;
861 p_region_upto
= p_region_upto
->p_next
;
865 else if(( !strncasecmp( psz_subtitle
, "<image ", 7 )) ||
866 ( !strncasecmp( psz_subtitle
, "<image>", 7 )))
868 subpicture_region_t
*p_image_region
= NULL
;
870 psz_end
= strcasestr( psz_subtitle
, "</image>" );
871 char *psz_content
= strchr( psz_subtitle
, '>' );
872 int i_transparent
= -1;
874 /* If a colorkey parameter is specified, then we have to map
875 * that index in the picture through as transparent (it is
876 * required by the USF spec but is also recommended that if the
877 * creator really wants a transparent colour that they use a
878 * type like PNG that properly supports it; this goes doubly
879 * for VLC because the pictures are stored internally in YUV
880 * and the resulting colour-matching may not produce the
883 char *psz_tmp
= GrabAttributeValue( "colorkey", psz_subtitle
);
886 if( *psz_tmp
== '#' )
887 i_transparent
= strtol( psz_tmp
+ 1, NULL
, 16 ) & 0x00ffffff;
890 if( psz_content
&& ( psz_content
< psz_end
) )
892 char *psz_filename
= strndup( &psz_content
[1], psz_end
- &psz_content
[1] );
895 p_image_region
= LoadEmbeddedImage( p_dec
,
896 psz_filename
, i_transparent
);
897 free( psz_filename
);
901 if( psz_end
) psz_end
+= strcspn( psz_end
, ">" ) + 1;
905 SetupPositions( p_image_region
, psz_subtitle
);
907 p_image_region
->p_next
= NULL
;
909 if( !p_region_first
)
911 p_region_first
= p_region_upto
= p_image_region
;
913 else if( p_image_region
)
915 p_region_upto
->p_next
= p_image_region
;
916 p_region_upto
= p_region_upto
->p_next
;
921 subpicture_region_t
*p_text_region
;
923 psz_end
= psz_subtitle
+ strlen( psz_subtitle
);
925 p_text_region
= CreateTextRegion( p_dec
,
931 free( p_text_region
->p_text
->psz_text
);
932 p_text_region
->p_text
->psz_text
= CreatePlainText( psz_subtitle
);
935 if( !p_region_first
)
937 p_region_first
= p_region_upto
= p_text_region
;
939 else if( p_text_region
)
941 p_region_upto
->p_next
= p_text_region
;
942 p_region_upto
= p_region_upto
->p_next
;
946 psz_subtitle
= psz_end
- 1;
948 psz_subtitle
+= strcspn( psz_subtitle
, ">" );
954 return p_region_first
;
957 /*****************************************************************************
958 * ParseUSFHeader: Retrieve global formatting information etc
959 *****************************************************************************/
960 static void ParseUSFHeader( decoder_t
*p_dec
)
962 stream_t
*p_sub
= NULL
;
963 xml_reader_t
*p_xml_reader
= NULL
;
965 p_sub
= vlc_stream_MemoryNew( VLC_OBJECT(p_dec
),
966 p_dec
->fmt_in
.p_extra
,
967 p_dec
->fmt_in
.i_extra
,
972 p_xml_reader
= xml_ReaderCreate( p_dec
, p_sub
);
973 if( likely(p_xml_reader
) )
977 /* Look for Root Node */
978 if( xml_ReaderNextNode( p_xml_reader
, &node
) == XML_READER_STARTELEM
979 && !strcasecmp( "usfsubtitles", node
) )
980 ParseUSFHeaderTags( p_dec
, p_xml_reader
);
982 xml_ReaderDelete( p_xml_reader
);
984 vlc_stream_Delete( p_sub
);
987 /* Function now handles tags which has attribute values, and tries
988 * to deal with &' commands too. It no longer modifies the string
989 * in place, so that the original text can be reused
991 static char *StripTags( char *psz_subtitle
)
993 char *psz_text_start
;
996 psz_text
= psz_text_start
= malloc( strlen( psz_subtitle
) + 1 );
997 if( !psz_text_start
)
1000 while( *psz_subtitle
)
1002 /* Mask out any pre-existing LFs in the subtitle */
1003 if( *psz_subtitle
== '\n' )
1004 *psz_subtitle
= ' ';
1006 if( *psz_subtitle
== '<' )
1008 if( strncasecmp( psz_subtitle
, "<br/>", 5 ) == 0 )
1011 psz_subtitle
+= strcspn( psz_subtitle
, ">" );
1013 else if( *psz_subtitle
== '&' )
1015 if( !strncasecmp( psz_subtitle
, "<", 4 ))
1018 psz_subtitle
+= strcspn( psz_subtitle
, ";" );
1020 else if( !strncasecmp( psz_subtitle
, ">", 4 ))
1023 psz_subtitle
+= strcspn( psz_subtitle
, ";" );
1025 else if( !strncasecmp( psz_subtitle
, "&", 5 ))
1028 psz_subtitle
+= strcspn( psz_subtitle
, ";" );
1030 else if( !strncasecmp( psz_subtitle
, """, 6 ))
1033 psz_subtitle
+= strcspn( psz_subtitle
, ";" );
1037 /* Assume it is just a normal ampersand */
1043 *psz_text
++ = *psz_subtitle
;
1046 /* Security fix: Account for the case where input ends early */
1047 if( *psz_subtitle
== '\0' ) break;
1053 char *psz
= realloc( psz_text_start
, psz_text
- psz_text_start
);
1054 return likely(psz
!= NULL
) ? psz
: psz_text_start
;
1057 /* Turn a HTML subtitle, turn into a plain-text version,
1058 * complete with sensible whitespace compaction
1061 static char *CreatePlainText( char *psz_subtitle
)
1063 char *psz_text
= StripTags( psz_subtitle
);
1069 s
= strpbrk( psz_text
, "\t\r\n " );
1073 int i_whitespace
= strspn( s
, "\t\r\n " );
1075 /* Favour '\n' over other whitespaces - if one of these
1076 * occurs in the whitespace use a '\n' as our value,
1077 * otherwise just use a ' '
1079 for( int k
= 0; k
< i_whitespace
; k
++ )
1080 if( s
[k
] == '\n' ) spc
= '\n';
1082 if( i_whitespace
> 1 )
1086 strlen( s
) - i_whitespace
+ 1 );
1090 s
= strpbrk( s
, "\t\r\n " );
1095 /****************************************************************************
1096 * download and resize image located at psz_url
1097 ***************************************************************************/
1098 static subpicture_region_t
*LoadEmbeddedImage( decoder_t
*p_dec
,
1099 const char *psz_filename
,
1100 int i_transparent_color
)
1102 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
1103 subpicture_region_t
*p_region
;
1104 video_format_t fmt_out
;
1105 picture_t
*p_pic
= NULL
;
1107 for( int k
= 0; k
< p_sys
->i_images
; k
++ )
1109 if( p_sys
->pp_images
&&
1110 !strcmp( p_sys
->pp_images
[k
]->psz_filename
, psz_filename
) )
1112 p_pic
= p_sys
->pp_images
[k
]->p_pic
;
1119 msg_Err( p_dec
, "Unable to read image %s", psz_filename
);
1123 /* Display the feed's image */
1124 memset( &fmt_out
, 0, sizeof( video_format_t
));
1126 fmt_out
.i_chroma
= VLC_CODEC_YUVA
;
1127 fmt_out
.i_sar_num
= fmt_out
.i_sar_den
= 1;
1129 fmt_out
.i_visible_width
= p_pic
->format
.i_visible_width
;
1131 fmt_out
.i_visible_height
= p_pic
->format
.i_visible_height
;
1133 p_region
= subpicture_region_New( &fmt_out
);
1136 msg_Err( p_dec
, "cannot allocate SPU region" );
1139 assert( p_pic
->format
.i_chroma
== VLC_CODEC_YUVA
);
1140 /* FIXME the copy is probably not needed anymore */
1141 picture_CopyPixels( p_region
->p_picture
, p_pic
);
1143 /* This isn't the best way to do this - if you really want transparency, then
1144 * you're much better off using an image type that supports it like PNG. The
1145 * spec requires this support though.
1147 if( i_transparent_color
> 0 )
1149 int i_r
= ( i_transparent_color
>> 16 ) & 0xff;
1150 int i_g
= ( i_transparent_color
>> 8 ) & 0xff;
1151 int i_b
= ( i_transparent_color
) & 0xff;
1153 /* FIXME it cannot work as the yuv conversion code will probably NOT match
1155 int i_y
= ( ( ( 66 * i_r
+ 129 * i_g
+ 25 * i_b
+ 128 ) >> 8 ) + 16 );
1156 int i_u
= ( ( -38 * i_r
- 74 * i_g
+ 112 * i_b
+ 128 ) >> 8 ) + 128 ;
1157 int i_v
= ( ( 112 * i_r
- 94 * i_g
- 18 * i_b
+ 128 ) >> 8 ) + 128 ;
1159 assert( p_region
->fmt
.i_chroma
== VLC_CODEC_YUVA
);
1160 for( unsigned int y
= 0; y
< p_region
->fmt
.i_height
; y
++ )
1162 for( unsigned int x
= 0; x
< p_region
->fmt
.i_width
; x
++ )
1164 if( p_region
->p_picture
->Y_PIXELS
[y
*p_region
->p_picture
->Y_PITCH
+ x
] != i_y
||
1165 p_region
->p_picture
->U_PIXELS
[y
*p_region
->p_picture
->U_PITCH
+ x
] != i_u
||
1166 p_region
->p_picture
->V_PIXELS
[y
*p_region
->p_picture
->V_PITCH
+ x
] != i_v
)
1168 p_region
->p_picture
->A_PIXELS
[y
*p_region
->p_picture
->A_PITCH
+ x
] = 0;