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_TICK_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
== VLC_TICK_INVALID
);
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
];
440 /* Set default or user align/magin.
441 * Style overriden if no user value. */
442 p_text_region
->i_x
= i_sys_align
> 0 ? 20 : 0;
443 p_text_region
->i_y
= 10;
444 p_text_region
->i_align
= SUBPICTURE_ALIGN_BOTTOM
|
445 ((i_sys_align
> 0) ? i_sys_align
: 0);
449 msg_Dbg( p_dec
, "style is: %s", p_ssa_style
->psz_stylename
);
451 /* TODO: Setup % based offsets properly, without adversely affecting
452 * everything else in vlc. Will address with separate patch,
453 * to prevent this one being any more complicated.
455 * p_ssa_style->i_margin_percent_h;
456 * p_ssa_style->i_margin_percent_v;
458 if( i_sys_align
== -1 )
460 p_text_region
->i_align
= p_ssa_style
->i_align
;
461 p_text_region
->i_x
= p_ssa_style
->i_margin_h
;
462 p_text_region
->i_y
= p_ssa_style
->i_margin_v
;
464 p_text_region
->p_text
= text_segment_NewInheritStyle( p_ssa_style
->p_style
);
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
];
636 text_style_t
*p_orig_text_style
= p_ssa_style
->p_style
;
638 memcpy( p_ssa_style
, p_default_style
, sizeof( ssa_style_t
) );
640 // reset data-members that are not to be overwritten
641 p_ssa_style
->p_style
= p_orig_text_style
;
642 p_ssa_style
->psz_stylename
= NULL
;
644 //FIXME: Make font_style a pointer. Actually we double copy some data here,
645 // we use text_style_Copy to avoid copying psz_fontname, though .
646 text_style_Copy( p_ssa_style
->p_style
, p_default_style
->p_style
);
650 const char *attr
, *val
;
651 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &val
)) )
653 if( !strcasecmp( "name", attr
) )
655 free( p_ssa_style
->psz_stylename
);
656 p_ssa_style
->psz_stylename
= strdup( val
);
660 else if( !strcasecmp( "fontstyle", node
) && (i_style_level
== 2) )
662 const char *attr
, *val
;
663 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &val
)) )
665 if( !strcasecmp( "face", attr
) )
667 free( p_ssa_style
->p_style
->psz_fontname
);
668 p_ssa_style
->p_style
->psz_fontname
= strdup( val
);
670 else if( !strcasecmp( "size", attr
) )
672 if( ( *val
== '+' ) || ( *val
== '-' ) )
674 int i_value
= atoi( val
);
676 if( ( i_value
>= -5 ) && ( i_value
<= 5 ) )
677 p_ssa_style
->p_style
->i_font_size
+=
678 ( i_value
* p_ssa_style
->p_style
->i_font_size
) / 10;
679 else if( i_value
< -5 )
680 p_ssa_style
->p_style
->i_font_size
= - i_value
;
681 else if( i_value
> 5 )
682 p_ssa_style
->p_style
->i_font_size
= i_value
;
685 p_ssa_style
->p_style
->i_font_size
= atoi( val
);
687 else if( !strcasecmp( "italic", attr
) )
689 if( !strcasecmp( "yes", val
))
690 p_ssa_style
->p_style
->i_style_flags
|= STYLE_ITALIC
;
692 p_ssa_style
->p_style
->i_style_flags
&= ~STYLE_ITALIC
;
693 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_FLAGS
;
695 else if( !strcasecmp( "weight", attr
) )
697 if( !strcasecmp( "bold", val
))
698 p_ssa_style
->p_style
->i_style_flags
|= STYLE_BOLD
;
700 p_ssa_style
->p_style
->i_style_flags
&= ~STYLE_BOLD
;
701 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_FLAGS
;
703 else if( !strcasecmp( "underline", attr
) )
705 if( !strcasecmp( "yes", val
))
706 p_ssa_style
->p_style
->i_style_flags
|= STYLE_UNDERLINE
;
708 p_ssa_style
->p_style
->i_style_flags
&= ~STYLE_UNDERLINE
;
709 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_FLAGS
;
711 else if( !strcasecmp( "color", attr
) )
715 unsigned long col
= strtol(val
+1, NULL
, 16);
716 p_ssa_style
->p_style
->i_font_color
= (col
& 0x00ffffff);
717 p_ssa_style
->p_style
->i_font_alpha
= (col
>> 24) & 0xff;
718 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_FONT_COLOR
719 | STYLE_HAS_FONT_ALPHA
;
722 else if( !strcasecmp( "outline-color", attr
) )
726 unsigned long col
= strtol(val
+1, NULL
, 16);
727 p_ssa_style
->p_style
->i_outline_color
= (col
& 0x00ffffff);
728 p_ssa_style
->p_style
->i_outline_alpha
= (col
>> 24) & 0xff;
729 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_OUTLINE_COLOR
730 | STYLE_HAS_OUTLINE_ALPHA
;
733 else if( !strcasecmp( "outline-level", attr
) )
735 p_ssa_style
->p_style
->i_outline_width
= atoi( val
);
737 else if( !strcasecmp( "shadow-color", attr
) )
741 unsigned long col
= strtol(val
+1, NULL
, 16);
742 p_ssa_style
->p_style
->i_shadow_color
= (col
& 0x00ffffff);
743 p_ssa_style
->p_style
->i_shadow_alpha
= (col
>> 24) & 0xff;
744 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_SHADOW_COLOR
745 | STYLE_HAS_SHADOW_ALPHA
;
748 else if( !strcasecmp( "shadow-level", attr
) )
750 p_ssa_style
->p_style
->i_shadow_width
= atoi( val
);
752 else if( !strcasecmp( "back-color", attr
) )
756 unsigned long col
= strtol(val
+1, NULL
, 16);
757 p_ssa_style
->p_style
->i_karaoke_background_color
= (col
& 0x00ffffff);
758 p_ssa_style
->p_style
->i_karaoke_background_alpha
= (col
>> 24) & 0xff;
759 p_ssa_style
->p_style
->i_features
|= STYLE_HAS_K_BACKGROUND_COLOR
760 | STYLE_HAS_K_BACKGROUND_ALPHA
;
763 else if( !strcasecmp( "spacing", attr
) )
765 p_ssa_style
->p_style
->i_spacing
= atoi( val
);
769 else if( !strcasecmp( "position", node
) && (i_style_level
== 2) )
771 const char *attr
, *val
;
772 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &val
)) )
774 if( !strcasecmp( "alignment", attr
) )
776 if( !strcasecmp( "TopLeft", val
) )
777 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_TOP
| SUBPICTURE_ALIGN_LEFT
;
778 else if( !strcasecmp( "TopCenter", val
) )
779 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_TOP
;
780 else if( !strcasecmp( "TopRight", val
) )
781 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_TOP
| SUBPICTURE_ALIGN_RIGHT
;
782 else if( !strcasecmp( "MiddleLeft", val
) )
783 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_LEFT
;
784 else if( !strcasecmp( "MiddleCenter", val
) )
785 p_ssa_style
->i_align
= 0;
786 else if( !strcasecmp( "MiddleRight", val
) )
787 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_RIGHT
;
788 else if( !strcasecmp( "BottomLeft", val
) )
789 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_BOTTOM
| SUBPICTURE_ALIGN_LEFT
;
790 else if( !strcasecmp( "BottomCenter", val
) )
791 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_BOTTOM
;
792 else if( !strcasecmp( "BottomRight", val
) )
793 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_BOTTOM
| SUBPICTURE_ALIGN_RIGHT
;
795 else if( !strcasecmp( "horizontal-margin", attr
) )
797 if( strchr( val
, '%' ) )
799 p_ssa_style
->i_margin_h
= 0;
800 p_ssa_style
->i_margin_percent_h
= atoi( val
);
804 p_ssa_style
->i_margin_h
= atoi( val
);
805 p_ssa_style
->i_margin_percent_h
= 0;
808 else if( !strcasecmp( "vertical-margin", attr
) )
810 if( strchr( val
, '%' ) )
812 p_ssa_style
->i_margin_v
= 0;
813 p_ssa_style
->i_margin_percent_v
= atoi( val
);
817 p_ssa_style
->i_margin_v
= atoi( val
);
818 p_ssa_style
->i_margin_percent_v
= 0;
831 static subpicture_region_t
*ParseUSFString( decoder_t
*p_dec
,
834 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
835 subpicture_region_t
*p_region_first
= NULL
;
836 subpicture_region_t
*p_region_upto
= p_region_first
;
838 while( *psz_subtitle
)
840 if( *psz_subtitle
== '<' )
842 char *psz_end
= NULL
;
845 if(( !strncasecmp( psz_subtitle
, "<karaoke ", 9 )) ||
846 ( !strncasecmp( psz_subtitle
, "<karaoke>", 9 )))
848 psz_end
= strcasestr( psz_subtitle
, "</karaoke>" );
852 subpicture_region_t
*p_text_region
;
854 psz_end
+= strcspn( psz_end
, ">" ) + 1;
856 p_text_region
= CreateTextRegion( p_dec
,
860 if( !p_region_first
)
862 p_region_first
= p_region_upto
= p_text_region
;
864 else if( p_text_region
)
866 p_region_upto
->p_next
= p_text_region
;
867 p_region_upto
= p_region_upto
->p_next
;
871 else if(( !strncasecmp( psz_subtitle
, "<image ", 7 )) ||
872 ( !strncasecmp( psz_subtitle
, "<image>", 7 )))
874 subpicture_region_t
*p_image_region
= NULL
;
876 psz_end
= strcasestr( psz_subtitle
, "</image>" );
877 char *psz_content
= strchr( psz_subtitle
, '>' );
878 int i_transparent
= -1;
880 /* If a colorkey parameter is specified, then we have to map
881 * that index in the picture through as transparent (it is
882 * required by the USF spec but is also recommended that if the
883 * creator really wants a transparent colour that they use a
884 * type like PNG that properly supports it; this goes doubly
885 * for VLC because the pictures are stored internally in YUV
886 * and the resulting colour-matching may not produce the
889 char *psz_tmp
= GrabAttributeValue( "colorkey", psz_subtitle
);
892 if( *psz_tmp
== '#' )
893 i_transparent
= strtol( psz_tmp
+ 1, NULL
, 16 ) & 0x00ffffff;
896 if( psz_content
&& ( psz_content
< psz_end
) )
898 char *psz_filename
= strndup( &psz_content
[1], psz_end
- &psz_content
[1] );
901 p_image_region
= LoadEmbeddedImage( p_dec
,
902 psz_filename
, i_transparent
);
903 free( psz_filename
);
907 if( psz_end
) psz_end
+= strcspn( psz_end
, ">" ) + 1;
911 SetupPositions( p_image_region
, psz_subtitle
);
913 p_image_region
->p_next
= NULL
;
915 if( !p_region_first
)
917 p_region_first
= p_region_upto
= p_image_region
;
919 else if( p_image_region
)
921 p_region_upto
->p_next
= p_image_region
;
922 p_region_upto
= p_region_upto
->p_next
;
927 subpicture_region_t
*p_text_region
;
929 psz_end
= psz_subtitle
+ strlen( psz_subtitle
);
931 p_text_region
= CreateTextRegion( p_dec
,
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;