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( "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 subpicture_t
*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_sub
= DecodeBlock
;
130 p_dec
->fmt_out
.i_cat
= SPU_ES
;
131 p_dec
->fmt_out
.i_codec
= 0;
134 TAB_INIT( p_sys
->i_ssa_styles
, p_sys
->pp_ssa_styles
);
135 TAB_INIT( p_sys
->i_images
, p_sys
->pp_images
);
137 /* USF subtitles are mandated to be UTF-8, so don't need vlc_iconv */
139 p_sys
->i_align
= var_CreateGetInteger( p_dec
, "subsdec-align" );
141 ParseImageAttachments( p_dec
);
143 if( var_CreateGetBool( p_dec
, "subsdec-formatted" ) )
145 if( p_dec
->fmt_in
.i_extra
> 0 )
146 ParseUSFHeader( p_dec
);
152 /****************************************************************************
153 * DecodeBlock: the whole thing
154 ****************************************************************************
155 * This function must be fed with complete subtitles units.
156 ****************************************************************************/
157 static subpicture_t
*DecodeBlock( decoder_t
*p_dec
, block_t
**pp_block
)
162 if( !pp_block
|| *pp_block
== NULL
)
167 p_spu
= ParseText( p_dec
, p_block
);
169 block_Release( p_block
);
175 /*****************************************************************************
176 * CloseDecoder: clean up the decoder
177 *****************************************************************************/
178 static void CloseDecoder( vlc_object_t
*p_this
)
180 decoder_t
*p_dec
= (decoder_t
*)p_this
;
181 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
183 if( p_sys
->pp_ssa_styles
)
185 for( int i
= 0; i
< p_sys
->i_ssa_styles
; i
++ )
187 if( !p_sys
->pp_ssa_styles
[i
] )
190 free( p_sys
->pp_ssa_styles
[i
]->psz_stylename
);
191 text_style_Delete( p_sys
->pp_ssa_styles
[i
]->p_style
);
192 free( p_sys
->pp_ssa_styles
[i
] );
194 TAB_CLEAN( p_sys
->i_ssa_styles
, p_sys
->pp_ssa_styles
);
196 if( p_sys
->pp_images
)
198 for( int i
= 0; i
< p_sys
->i_images
; i
++ )
200 if( !p_sys
->pp_images
[i
] )
203 if( p_sys
->pp_images
[i
]->p_pic
)
204 picture_Release( p_sys
->pp_images
[i
]->p_pic
);
205 free( p_sys
->pp_images
[i
]->psz_filename
);
207 free( p_sys
->pp_images
[i
] );
209 TAB_CLEAN( p_sys
->i_images
, p_sys
->pp_images
);
215 /*****************************************************************************
216 * ParseText: parse an text subtitle packet and send it to the video output
217 *****************************************************************************/
218 static subpicture_t
*ParseText( decoder_t
*p_dec
, block_t
*p_block
)
220 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
221 subpicture_t
*p_spu
= NULL
;
222 char *psz_subtitle
= NULL
;
224 if( p_block
->i_flags
& BLOCK_FLAG_CORRUPTED
)
227 /* We cannot display a subpicture with no date */
228 if( p_block
->i_pts
<= VLC_TS_INVALID
)
230 msg_Warn( p_dec
, "subtitle without a date" );
234 /* Check validity of packet data */
235 /* An "empty" line containing only \0 can be used to force
236 and ephemer picture from the screen */
237 if( p_block
->i_buffer
< 1 )
239 msg_Warn( p_dec
, "no subtitle data" );
243 /* Should be resiliant against bad subtitles */
244 psz_subtitle
= strndup( (const char *)p_block
->p_buffer
,
246 if( psz_subtitle
== NULL
)
249 /* USF Subtitles are mandated to be UTF-8 -- make sure it is */
250 if (EnsureUTF8( psz_subtitle
) == NULL
)
252 msg_Err( p_dec
, "USF subtitles must be in UTF-8 format.\n"
253 "This stream contains USF subtitles which aren't." );
256 /* Create the subpicture unit */
257 p_spu
= decoder_NewSubpicture( p_dec
, NULL
);
260 msg_Warn( p_dec
, "can't get spu buffer" );
261 free( psz_subtitle
);
265 /* Decode USF strings */
266 p_spu
->p_region
= ParseUSFString( p_dec
, psz_subtitle
);
268 p_spu
->i_start
= p_block
->i_pts
;
269 p_spu
->i_stop
= p_block
->i_pts
+ p_block
->i_length
;
270 p_spu
->b_ephemer
= (p_block
->i_length
== 0);
271 p_spu
->b_absolute
= false;
272 p_spu
->i_original_picture_width
= p_sys
->i_original_width
;
273 p_spu
->i_original_picture_height
= p_sys
->i_original_height
;
275 free( psz_subtitle
);
280 static char *GrabAttributeValue( const char *psz_attribute
,
281 const char *psz_tag_start
)
283 if( psz_attribute
&& psz_tag_start
)
285 char *psz_tag_end
= strchr( psz_tag_start
, '>' );
286 char *psz_found
= strcasestr( psz_tag_start
, psz_attribute
);
290 psz_found
+= strlen( psz_attribute
);
292 if(( *(psz_found
++) == '=' ) &&
293 ( *(psz_found
++) == '\"' ))
295 if( psz_found
< psz_tag_end
)
297 int i_len
= strcspn( psz_found
, "\"" );
298 return strndup( psz_found
, i_len
);
306 static ssa_style_t
*ParseStyle( decoder_sys_t
*p_sys
, char *psz_subtitle
)
308 ssa_style_t
*p_ssa_style
= NULL
;
309 char *psz_style
= GrabAttributeValue( "style", psz_subtitle
);
313 for( int i
= 0; i
< p_sys
->i_ssa_styles
; i
++ )
315 if( !strcmp( p_sys
->pp_ssa_styles
[i
]->psz_stylename
, psz_style
) )
316 p_ssa_style
= p_sys
->pp_ssa_styles
[i
];
323 static int ParsePositionAttributeList( char *psz_subtitle
, int *i_align
,
328 char *psz_align
= GrabAttributeValue( "alignment", psz_subtitle
);
329 char *psz_margin_x
= GrabAttributeValue( "horizontal-margin", psz_subtitle
);
330 char *psz_margin_y
= GrabAttributeValue( "vertical-margin", psz_subtitle
);
332 char *psz_relative = GrabAttributeValue( "relative-to", psz_subtitle );
333 char *psz_rotate_x = GrabAttributeValue( "rotate-x", psz_subtitle );
334 char *psz_rotate_y = GrabAttributeValue( "rotate-y", psz_subtitle );
335 char *psz_rotate_z = GrabAttributeValue( "rotate-z", psz_subtitle );
338 *i_align
= SUBPICTURE_ALIGN_BOTTOM
;
344 if( !strcasecmp( "TopLeft", psz_align
) )
345 *i_align
= SUBPICTURE_ALIGN_TOP
| SUBPICTURE_ALIGN_LEFT
;
346 else if( !strcasecmp( "TopCenter", psz_align
) )
347 *i_align
= SUBPICTURE_ALIGN_TOP
;
348 else if( !strcasecmp( "TopRight", psz_align
) )
349 *i_align
= SUBPICTURE_ALIGN_TOP
| SUBPICTURE_ALIGN_RIGHT
;
350 else if( !strcasecmp( "MiddleLeft", psz_align
) )
351 *i_align
= SUBPICTURE_ALIGN_LEFT
;
352 else if( !strcasecmp( "MiddleCenter", psz_align
) )
354 else if( !strcasecmp( "MiddleRight", psz_align
) )
355 *i_align
= SUBPICTURE_ALIGN_RIGHT
;
356 else if( !strcasecmp( "BottomLeft", psz_align
) )
357 *i_align
= SUBPICTURE_ALIGN_BOTTOM
| SUBPICTURE_ALIGN_LEFT
;
358 else if( !strcasecmp( "BottomCenter", psz_align
) )
359 *i_align
= SUBPICTURE_ALIGN_BOTTOM
;
360 else if( !strcasecmp( "BottomRight", psz_align
) )
361 *i_align
= SUBPICTURE_ALIGN_BOTTOM
| SUBPICTURE_ALIGN_RIGHT
;
363 i_mask
|= ATTRIBUTE_ALIGNMENT
;
368 *i_x
= atoi( psz_margin_x
);
369 if( strchr( psz_margin_x
, '%' ) )
370 i_mask
|= ATTRIBUTE_X_PERCENT
;
372 i_mask
|= ATTRIBUTE_X
;
374 free( psz_margin_x
);
378 *i_y
= atoi( psz_margin_y
);
379 if( strchr( psz_margin_y
, '%' ) )
380 i_mask
|= ATTRIBUTE_Y_PERCENT
;
382 i_mask
|= ATTRIBUTE_Y
;
384 free( psz_margin_y
);
389 static void SetupPositions( subpicture_region_t
*p_region
, char *psz_subtitle
)
395 i_mask
= ParsePositionAttributeList( psz_subtitle
, &i_align
, &i_x
, &i_y
);
397 if( i_mask
& ATTRIBUTE_ALIGNMENT
)
398 p_region
->i_align
= i_align
;
400 /* TODO: Setup % based offsets properly, without adversely affecting
401 * everything else in vlc. Will address with separate patch, to
402 * prevent this one being any more complicated.
404 if( i_mask
& ATTRIBUTE_X
)
406 else if( i_mask
& ATTRIBUTE_X_PERCENT
)
409 if( i_mask
& ATTRIBUTE_Y
)
411 else if( i_mask
& ATTRIBUTE_Y_PERCENT
)
415 static subpicture_region_t
*CreateTextRegion( decoder_t
*p_dec
,
420 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
421 subpicture_region_t
*p_text_region
;
425 /* Create a new subpicture region */
426 video_format_Init( &fmt
, VLC_CODEC_TEXT
);
427 fmt
.i_width
= fmt
.i_height
= 0;
428 fmt
.i_x_offset
= fmt
.i_y_offset
= 0;
429 p_text_region
= subpicture_region_New( &fmt
);
430 video_format_Clean( &fmt
);
432 if( p_text_region
!= NULL
)
434 ssa_style_t
*p_ssa_style
= NULL
;
436 p_ssa_style
= ParseStyle( p_sys
, psz_subtitle
);
439 for( int i
= 0; i
< p_sys
->i_ssa_styles
; i
++ )
441 if( !strcasecmp( p_sys
->pp_ssa_styles
[i
]->psz_stylename
, "Default" ) )
442 p_ssa_style
= p_sys
->pp_ssa_styles
[i
];
448 msg_Dbg( p_dec
, "style is: %s", p_ssa_style
->psz_stylename
);
450 p_text_region
->i_align
= p_ssa_style
->i_align
;
452 /* TODO: Setup % based offsets properly, without adversely affecting
453 * everything else in vlc. Will address with separate patch,
454 * to prevent this one being any more complicated.
456 * p_ssa_style->i_margin_percent_h;
457 * p_ssa_style->i_margin_percent_v;
459 p_text_region
->i_x
= p_ssa_style
->i_margin_h
;
460 p_text_region
->i_y
= p_ssa_style
->i_margin_v
;
461 p_text_region
->p_text
= text_segment_NewInheritStyle( p_ssa_style
->p_style
);
465 p_text_region
->i_align
= SUBPICTURE_ALIGN_BOTTOM
| i_sys_align
;
466 p_text_region
->i_x
= i_sys_align
? 20 : 0;
467 p_text_region
->i_y
= 10;
468 p_text_region
->p_text
= text_segment_New( NULL
);
470 /* Look for position arguments which may override the style-based
473 SetupPositions( p_text_region
, psz_subtitle
);
475 p_text_region
->p_next
= NULL
;
477 return p_text_region
;
480 static int ParseImageAttachments( decoder_t
*p_dec
)
482 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
483 input_attachment_t
**pp_attachments
;
484 int i_attachments_cnt
;
486 if( VLC_SUCCESS
!= decoder_GetInputAttachments( p_dec
, &pp_attachments
, &i_attachments_cnt
))
489 for( int k
= 0; k
< i_attachments_cnt
; k
++ )
491 input_attachment_t
*p_attach
= pp_attachments
[k
];
493 vlc_fourcc_t type
= image_Mime2Fourcc( p_attach
->psz_mime
);
496 ( p_attach
->i_data
> 0 ) &&
497 ( p_attach
->p_data
!= NULL
) )
499 picture_t
*p_pic
= NULL
;
500 image_handler_t
*p_image
;
502 p_image
= image_HandlerCreate( p_dec
);
503 if( p_image
!= NULL
)
507 p_block
= block_Alloc( p_attach
->i_data
);
509 if( p_block
!= NULL
)
511 video_format_t fmt_in
;
512 video_format_t fmt_out
;
514 memcpy( p_block
->p_buffer
, p_attach
->p_data
, p_attach
->i_data
);
516 memset( &fmt_in
, 0, sizeof( video_format_t
));
517 memset( &fmt_out
, 0, sizeof( video_format_t
));
519 fmt_in
.i_chroma
= type
;
520 fmt_out
.i_chroma
= VLC_CODEC_YUVA
;
522 /* Find a suitable decoder module */
523 if( module_exists( "sdl_image" ) )
525 /* ffmpeg thinks it can handle bmp properly but it can't (at least
526 * not all of them), so use sdl_image if it is available */
528 var_Create( p_dec
, "codec", VLC_VAR_STRING
| VLC_VAR_DOINHERIT
);
529 var_SetString( p_dec
, "codec", "sdl_image" );
532 p_pic
= image_Read( p_image
, p_block
, &fmt_in
, &fmt_out
);
533 var_Destroy( p_dec
, "codec" );
536 image_HandlerDelete( p_image
);
540 image_attach_t
*p_picture
= malloc( sizeof(image_attach_t
) );
544 p_picture
->psz_filename
= strdup( p_attach
->psz_name
);
545 p_picture
->p_pic
= p_pic
;
547 TAB_APPEND( p_sys
->i_images
, p_sys
->pp_images
, p_picture
);
551 vlc_input_attachment_Delete( pp_attachments
[ k
] );
553 free( pp_attachments
);
558 static void ParseUSFHeaderTags( decoder_t
*p_dec
, xml_reader_t
*p_xml_reader
)
560 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
562 ssa_style_t
*p_ssa_style
= NULL
;
563 int i_style_level
= 0;
564 int i_metadata_level
= 0;
567 while( (type
= xml_ReaderNextNode( p_xml_reader
, &node
)) > 0 )
571 case XML_READER_ENDELEM
:
572 switch (i_style_level
)
575 if( !strcasecmp( "metadata", node
) && (i_metadata_level
== 1) )
579 if( !strcasecmp( "styles", node
) )
583 if( !strcasecmp( "style", node
) )
585 TAB_APPEND( p_sys
->i_ssa_styles
, p_sys
->pp_ssa_styles
, p_ssa_style
);
594 case XML_READER_STARTELEM
:
595 if( !strcasecmp( "metadata", node
) && (i_style_level
== 0) )
597 else if( !strcasecmp( "resolution", node
) &&
598 ( i_metadata_level
== 1) )
600 const char *attr
, *val
;
601 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &val
)) )
603 if( !strcasecmp( "x", attr
) )
604 p_sys
->i_original_width
= atoi( val
);
605 else if( !strcasecmp( "y", attr
) )
606 p_sys
->i_original_height
= atoi( val
);
609 else if( !strcasecmp( "styles", node
) && (i_style_level
== 0) )
613 else if( !strcasecmp( "style", node
) && (i_style_level
== 1) )
617 p_ssa_style
= calloc( 1, sizeof(ssa_style_t
) );
618 if( unlikely(!p_ssa_style
) )
620 p_ssa_style
->p_style
= text_style_Create( STYLE_NO_DEFAULTS
);
621 if( unlikely(!p_ssa_style
->p_style
) )
626 /* All styles are supposed to default to Default, and then
627 * one or more settings are over-ridden.
628 * At the moment this only effects styles defined AFTER
631 for( int i
= 0; i
< p_sys
->i_ssa_styles
; i
++ )
633 if( !strcasecmp( p_sys
->pp_ssa_styles
[i
]->psz_stylename
, "Default" ) )
635 ssa_style_t
*p_default_style
= p_sys
->pp_ssa_styles
[i
];
637 memcpy( p_ssa_style
, p_default_style
, sizeof( ssa_style_t
) );
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
);
641 p_ssa_style
->psz_stylename
= NULL
;
645 const char *attr
, *val
;
646 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &val
)) )
648 if( !strcasecmp( "name", attr
) )
650 free( p_ssa_style
->psz_stylename
);
651 p_ssa_style
->psz_stylename
= strdup( val
);
655 else if( !strcasecmp( "fontstyle", node
) && (i_style_level
== 2) )
657 const char *attr
, *val
;
658 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &val
)) )
660 if( !strcasecmp( "face", attr
) )
662 free( p_ssa_style
->p_style
->psz_fontname
);
663 p_ssa_style
->p_style
->psz_fontname
= strdup( val
);
665 else if( !strcasecmp( "size", attr
) )
667 if( ( *val
== '+' ) || ( *val
== '-' ) )
669 int i_value
= atoi( val
);
671 if( ( i_value
>= -5 ) && ( i_value
<= 5 ) )
672 p_ssa_style
->p_style
->i_font_size
+=
673 ( i_value
* p_ssa_style
->p_style
->i_font_size
) / 10;
674 else if( i_value
< -5 )
675 p_ssa_style
->p_style
->i_font_size
= - i_value
;
676 else if( i_value
> 5 )
677 p_ssa_style
->p_style
->i_font_size
= i_value
;
680 p_ssa_style
->p_style
->i_font_size
= atoi( val
);
682 else if( !strcasecmp( "italic", attr
) )
684 if( !strcasecmp( "yes", val
))
685 p_ssa_style
->p_style
->i_style_flags
|= STYLE_ITALIC
;
687 p_ssa_style
->p_style
->i_style_flags
&= ~STYLE_ITALIC
;
688 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_FLAGS
;
690 else if( !strcasecmp( "weight", attr
) )
692 if( !strcasecmp( "bold", val
))
693 p_ssa_style
->p_style
->i_style_flags
|= STYLE_BOLD
;
695 p_ssa_style
->p_style
->i_style_flags
&= ~STYLE_BOLD
;
696 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_FLAGS
;
698 else if( !strcasecmp( "underline", attr
) )
700 if( !strcasecmp( "yes", val
))
701 p_ssa_style
->p_style
->i_style_flags
|= STYLE_UNDERLINE
;
703 p_ssa_style
->p_style
->i_style_flags
&= ~STYLE_UNDERLINE
;
704 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_FLAGS
;
706 else if( !strcasecmp( "color", attr
) )
710 unsigned long col
= strtol(val
+1, NULL
, 16);
711 p_ssa_style
->p_style
->i_font_color
= (col
& 0x00ffffff);
712 p_ssa_style
->p_style
->i_font_alpha
= (col
>> 24) & 0xff;
713 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_FONT_COLOR
714 | STYLE_HAS_FONT_ALPHA
;
717 else if( !strcasecmp( "outline-color", attr
) )
721 unsigned long col
= strtol(val
+1, NULL
, 16);
722 p_ssa_style
->p_style
->i_outline_color
= (col
& 0x00ffffff);
723 p_ssa_style
->p_style
->i_outline_alpha
= (col
>> 24) & 0xff;
724 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_OUTLINE_COLOR
725 | STYLE_HAS_OUTLINE_ALPHA
;
728 else if( !strcasecmp( "outline-level", attr
) )
730 p_ssa_style
->p_style
->i_outline_width
= atoi( val
);
732 else if( !strcasecmp( "shadow-color", attr
) )
736 unsigned long col
= strtol(val
+1, NULL
, 16);
737 p_ssa_style
->p_style
->i_shadow_color
= (col
& 0x00ffffff);
738 p_ssa_style
->p_style
->i_shadow_alpha
= (col
>> 24) & 0xff;
739 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_SHADOW_COLOR
740 | STYLE_HAS_SHADOW_ALPHA
;
743 else if( !strcasecmp( "shadow-level", attr
) )
745 p_ssa_style
->p_style
->i_shadow_width
= atoi( val
);
747 else if( !strcasecmp( "back-color", attr
) )
751 unsigned long col
= strtol(val
+1, NULL
, 16);
752 p_ssa_style
->p_style
->i_karaoke_background_color
= (col
& 0x00ffffff);
753 p_ssa_style
->p_style
->i_karaoke_background_alpha
= (col
>> 24) & 0xff;
754 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_K_BACKGROUND_COLOR
755 | STYLE_HAS_K_BACKGROUND_ALPHA
;
758 else if( !strcasecmp( "spacing", attr
) )
760 p_ssa_style
->p_style
->i_spacing
= atoi( val
);
764 else if( !strcasecmp( "position", node
) && (i_style_level
== 2) )
766 const char *attr
, *val
;
767 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &val
)) )
769 if( !strcasecmp( "alignment", attr
) )
771 if( !strcasecmp( "TopLeft", val
) )
772 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_TOP
| SUBPICTURE_ALIGN_LEFT
;
773 else if( !strcasecmp( "TopCenter", val
) )
774 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_TOP
;
775 else if( !strcasecmp( "TopRight", val
) )
776 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_TOP
| SUBPICTURE_ALIGN_RIGHT
;
777 else if( !strcasecmp( "MiddleLeft", val
) )
778 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_LEFT
;
779 else if( !strcasecmp( "MiddleCenter", val
) )
780 p_ssa_style
->i_align
= 0;
781 else if( !strcasecmp( "MiddleRight", val
) )
782 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_RIGHT
;
783 else if( !strcasecmp( "BottomLeft", val
) )
784 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_BOTTOM
| SUBPICTURE_ALIGN_LEFT
;
785 else if( !strcasecmp( "BottomCenter", val
) )
786 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_BOTTOM
;
787 else if( !strcasecmp( "BottomRight", val
) )
788 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_BOTTOM
| SUBPICTURE_ALIGN_RIGHT
;
790 else if( !strcasecmp( "horizontal-margin", attr
) )
792 if( strchr( val
, '%' ) )
794 p_ssa_style
->i_margin_h
= 0;
795 p_ssa_style
->i_margin_percent_h
= atoi( val
);
799 p_ssa_style
->i_margin_h
= atoi( val
);
800 p_ssa_style
->i_margin_percent_h
= 0;
803 else if( !strcasecmp( "vertical-margin", attr
) )
805 if( strchr( val
, '%' ) )
807 p_ssa_style
->i_margin_v
= 0;
808 p_ssa_style
->i_margin_percent_v
= atoi( val
);
812 p_ssa_style
->i_margin_v
= atoi( val
);
813 p_ssa_style
->i_margin_percent_v
= 0;
826 static subpicture_region_t
*ParseUSFString( decoder_t
*p_dec
,
829 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
830 subpicture_region_t
*p_region_first
= NULL
;
831 subpicture_region_t
*p_region_upto
= p_region_first
;
833 while( *psz_subtitle
)
835 if( *psz_subtitle
== '<' )
837 char *psz_end
= NULL
;
840 if(( !strncasecmp( psz_subtitle
, "<karaoke ", 9 )) ||
841 ( !strncasecmp( psz_subtitle
, "<karaoke>", 9 )))
843 psz_end
= strcasestr( psz_subtitle
, "</karaoke>" );
847 subpicture_region_t
*p_text_region
;
849 psz_end
+= strcspn( psz_end
, ">" ) + 1;
851 p_text_region
= CreateTextRegion( p_dec
,
853 psz_end
- psz_subtitle
,
856 if( !p_region_first
)
858 p_region_first
= p_region_upto
= p_text_region
;
860 else if( p_text_region
)
862 p_region_upto
->p_next
= p_text_region
;
863 p_region_upto
= p_region_upto
->p_next
;
867 else if(( !strncasecmp( psz_subtitle
, "<image ", 7 )) ||
868 ( !strncasecmp( psz_subtitle
, "<image>", 7 )))
870 subpicture_region_t
*p_image_region
= NULL
;
872 char *psz_end
= strcasestr( psz_subtitle
, "</image>" );
873 char *psz_content
= strchr( psz_subtitle
, '>' );
874 int i_transparent
= -1;
876 /* If a colorkey parameter is specified, then we have to map
877 * that index in the picture through as transparent (it is
878 * required by the USF spec but is also recommended that if the
879 * creator really wants a transparent colour that they use a
880 * type like PNG that properly supports it; this goes doubly
881 * for VLC because the pictures are stored internally in YUV
882 * and the resulting colour-matching may not produce the
885 char *psz_tmp
= GrabAttributeValue( "colorkey", psz_subtitle
);
888 if( *psz_tmp
== '#' )
889 i_transparent
= strtol( psz_tmp
+ 1, NULL
, 16 ) & 0x00ffffff;
892 if( psz_content
&& ( psz_content
< psz_end
) )
894 char *psz_filename
= strndup( &psz_content
[1], psz_end
- &psz_content
[1] );
897 p_image_region
= LoadEmbeddedImage( p_dec
,
898 psz_filename
, i_transparent
);
899 free( psz_filename
);
903 if( psz_end
) psz_end
+= strcspn( psz_end
, ">" ) + 1;
907 SetupPositions( p_image_region
, psz_subtitle
);
909 p_image_region
->p_next
= NULL
;
911 if( !p_region_first
)
913 p_region_first
= p_region_upto
= p_image_region
;
915 else if( p_image_region
)
917 p_region_upto
->p_next
= p_image_region
;
918 p_region_upto
= p_region_upto
->p_next
;
923 subpicture_region_t
*p_text_region
;
926 psz_end
+= strcspn( psz_end
, ">" ) + 1;
928 psz_end
= psz_subtitle
+ strlen( psz_subtitle
);
930 p_text_region
= CreateTextRegion( p_dec
,
932 psz_end
- psz_subtitle
,
937 free( p_text_region
->p_text
->psz_text
);
938 p_text_region
->p_text
->psz_text
= CreatePlainText( psz_subtitle
);
941 if( !p_region_first
)
943 p_region_first
= p_region_upto
= p_text_region
;
945 else if( p_text_region
)
947 p_region_upto
->p_next
= p_text_region
;
948 p_region_upto
= p_region_upto
->p_next
;
952 psz_subtitle
= psz_end
- 1;
954 psz_subtitle
+= strcspn( psz_subtitle
, ">" );
960 return p_region_first
;
963 /*****************************************************************************
964 * ParseUSFHeader: Retrieve global formatting information etc
965 *****************************************************************************/
966 static void ParseUSFHeader( decoder_t
*p_dec
)
968 stream_t
*p_sub
= NULL
;
969 xml_reader_t
*p_xml_reader
= NULL
;
971 p_sub
= vlc_stream_MemoryNew( VLC_OBJECT(p_dec
),
972 p_dec
->fmt_in
.p_extra
,
973 p_dec
->fmt_in
.i_extra
,
978 p_xml_reader
= xml_ReaderCreate( p_dec
, p_sub
);
979 if( likely(p_xml_reader
) )
983 /* Look for Root Node */
984 if( xml_ReaderNextNode( p_xml_reader
, &node
) == XML_READER_STARTELEM
985 && !strcasecmp( "usfsubtitles", node
) )
986 ParseUSFHeaderTags( p_dec
, p_xml_reader
);
988 xml_ReaderDelete( p_xml_reader
);
990 vlc_stream_Delete( p_sub
);
993 /* Function now handles tags which has attribute values, and tries
994 * to deal with &' commands too. It no longer modifies the string
995 * in place, so that the original text can be reused
997 static char *StripTags( char *psz_subtitle
)
999 char *psz_text_start
;
1002 psz_text
= psz_text_start
= malloc( strlen( psz_subtitle
) + 1 );
1003 if( !psz_text_start
)
1006 while( *psz_subtitle
)
1008 /* Mask out any pre-existing LFs in the subtitle */
1009 if( *psz_subtitle
== '\n' )
1010 *psz_subtitle
= ' ';
1012 if( *psz_subtitle
== '<' )
1014 if( strncasecmp( psz_subtitle
, "<br/>", 5 ) == 0 )
1017 psz_subtitle
+= strcspn( psz_subtitle
, ">" );
1019 else if( *psz_subtitle
== '&' )
1021 if( !strncasecmp( psz_subtitle
, "<", 4 ))
1024 psz_subtitle
+= strcspn( psz_subtitle
, ";" );
1026 else if( !strncasecmp( psz_subtitle
, ">", 4 ))
1029 psz_subtitle
+= strcspn( psz_subtitle
, ";" );
1031 else if( !strncasecmp( psz_subtitle
, "&", 5 ))
1034 psz_subtitle
+= strcspn( psz_subtitle
, ";" );
1036 else if( !strncasecmp( psz_subtitle
, """, 6 ))
1039 psz_subtitle
+= strcspn( psz_subtitle
, ";" );
1043 /* Assume it is just a normal ampersand */
1049 *psz_text
++ = *psz_subtitle
;
1052 /* Security fix: Account for the case where input ends early */
1053 if( *psz_subtitle
== '\0' ) break;
1059 char *psz
= realloc( psz_text_start
, psz_text
- psz_text_start
);
1060 return likely(psz
!= NULL
) ? psz
: psz_text_start
;
1063 /* Turn a HTML subtitle, turn into a plain-text version,
1064 * complete with sensible whitespace compaction
1067 static char *CreatePlainText( char *psz_subtitle
)
1069 char *psz_text
= StripTags( psz_subtitle
);
1075 s
= strpbrk( psz_text
, "\t\r\n " );
1079 int i_whitespace
= strspn( s
, "\t\r\n " );
1081 /* Favour '\n' over other whitespaces - if one of these
1082 * occurs in the whitespace use a '\n' as our value,
1083 * otherwise just use a ' '
1085 for( int k
= 0; k
< i_whitespace
; k
++ )
1086 if( s
[k
] == '\n' ) spc
= '\n';
1088 if( i_whitespace
> 1 )
1092 strlen( s
) - i_whitespace
+ 1 );
1096 s
= strpbrk( s
, "\t\r\n " );
1101 /****************************************************************************
1102 * download and resize image located at psz_url
1103 ***************************************************************************/
1104 static subpicture_region_t
*LoadEmbeddedImage( decoder_t
*p_dec
,
1105 const char *psz_filename
,
1106 int i_transparent_color
)
1108 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
1109 subpicture_region_t
*p_region
;
1110 video_format_t fmt_out
;
1111 picture_t
*p_pic
= NULL
;
1113 for( int k
= 0; k
< p_sys
->i_images
; k
++ )
1115 if( p_sys
->pp_images
&&
1116 !strcmp( p_sys
->pp_images
[k
]->psz_filename
, psz_filename
) )
1118 p_pic
= p_sys
->pp_images
[k
]->p_pic
;
1125 msg_Err( p_dec
, "Unable to read image %s", psz_filename
);
1129 /* Display the feed's image */
1130 memset( &fmt_out
, 0, sizeof( video_format_t
));
1132 fmt_out
.i_chroma
= VLC_CODEC_YUVA
;
1133 fmt_out
.i_sar_num
= fmt_out
.i_sar_den
= 1;
1135 fmt_out
.i_visible_width
= p_pic
->format
.i_visible_width
;
1137 fmt_out
.i_visible_height
= p_pic
->format
.i_visible_height
;
1139 p_region
= subpicture_region_New( &fmt_out
);
1142 msg_Err( p_dec
, "cannot allocate SPU region" );
1145 assert( p_pic
->format
.i_chroma
== VLC_CODEC_YUVA
);
1146 /* FIXME the copy is probably not needed anymore */
1147 picture_CopyPixels( p_region
->p_picture
, p_pic
);
1149 /* This isn't the best way to do this - if you really want transparency, then
1150 * you're much better off using an image type that supports it like PNG. The
1151 * spec requires this support though.
1153 if( i_transparent_color
> 0 )
1155 int i_r
= ( i_transparent_color
>> 16 ) & 0xff;
1156 int i_g
= ( i_transparent_color
>> 8 ) & 0xff;
1157 int i_b
= ( i_transparent_color
) & 0xff;
1159 /* FIXME it cannot work as the yuv conversion code will probably NOT match
1161 int i_y
= ( ( ( 66 * i_r
+ 129 * i_g
+ 25 * i_b
+ 128 ) >> 8 ) + 16 );
1162 int i_u
= ( ( -38 * i_r
- 74 * i_g
+ 112 * i_b
+ 128 ) >> 8 ) + 128 ;
1163 int i_v
= ( ( 112 * i_r
- 94 * i_g
- 18 * i_b
+ 128 ) >> 8 ) + 128 ;
1165 assert( p_region
->fmt
.i_chroma
== VLC_CODEC_YUVA
);
1166 for( unsigned int y
= 0; y
< p_region
->fmt
.i_height
; y
++ )
1168 for( unsigned int x
= 0; x
< p_region
->fmt
.i_width
; x
++ )
1170 if( p_region
->p_picture
->Y_PIXELS
[y
*p_region
->p_picture
->Y_PITCH
+ x
] != i_y
||
1171 p_region
->p_picture
->U_PIXELS
[y
*p_region
->p_picture
->U_PITCH
+ x
] != i_u
||
1172 p_region
->p_picture
->V_PIXELS
[y
*p_region
->p_picture
->V_PITCH
+ x
] != i_v
)
1174 p_region
->p_picture
->A_PIXELS
[y
*p_region
->p_picture
->A_PITCH
+ x
] = 0;