1 /*****************************************************************************
2 * subsusf.c : USF subtitles decoder
3 *****************************************************************************
4 * Copyright (C) 2000-2006 VLC authors and VideoLAN
6 * Authors: Bernie Purcell <bitmap@videolan.org>
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 *****************************************************************************/
27 #include <vlc_common.h>
28 #include <vlc_plugin.h>
29 #include <vlc_modules.h>
30 #include <vlc_codec.h>
31 #include <vlc_input.h>
32 #include <vlc_charset.h>
33 #include <vlc_image.h>
35 #include <vlc_stream.h>
37 /*****************************************************************************
39 *****************************************************************************/
40 static int OpenDecoder ( vlc_object_t
* );
41 static void CloseDecoder ( vlc_object_t
* );
43 #define FORMAT_TEXT N_("Formatted Subtitles")
44 #define FORMAT_LONGTEXT N_("Some subtitle formats allow for text formatting. " \
45 "VLC partly implements this, but you can choose to disable all formatting.")
48 set_capability( "spu decoder", 40 )
49 set_shortname( N_("USFSubs"))
50 set_description( N_("USF subtitles decoder") )
51 set_callbacks( OpenDecoder
, CloseDecoder
)
52 set_category( CAT_INPUT
)
53 set_subcategory( SUBCAT_INPUT_SCODEC
)
54 add_bool( "subsdec-formatted", true, FORMAT_TEXT
, FORMAT_LONGTEXT
,
59 /*****************************************************************************
61 *****************************************************************************/
64 ATTRIBUTE_ALIGNMENT
= (1 << 0),
65 ATTRIBUTE_X
= (1 << 1),
66 ATTRIBUTE_X_PERCENT
= (1 << 2),
67 ATTRIBUTE_Y
= (1 << 3),
68 ATTRIBUTE_Y_PERCENT
= (1 << 4),
79 char * psz_stylename
; /* The name of the style, no comma's allowed */
80 text_style_t
* p_style
;
84 int i_margin_percent_h
;
85 int i_margin_percent_v
;
90 int i_original_height
;
92 int i_align
; /* Subtitles alignment on the vout */
94 ssa_style_t
**pp_ssa_styles
;
97 image_attach_t
**pp_images
;
101 static int DecodeBlock ( decoder_t
*, block_t
* );
102 static char *CreatePlainText( char * );
103 static char *StripTags( char *psz_subtitle
);
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
)
512 video_format_t fmt_out
;
514 memcpy( p_block
->p_buffer
, p_attach
->p_data
, p_attach
->i_data
);
516 es_format_Init( &es_in
, VIDEO_ES
, type
);
517 es_in
.video
.i_chroma
= type
;
518 video_format_Init( &fmt_out
, VLC_CODEC_YUVA
);
520 /* Find a suitable decoder module */
521 if( module_exists( "sdl_image" ) )
523 /* ffmpeg thinks it can handle bmp properly but it can't (at least
524 * not all of them), so use sdl_image if it is available */
526 var_Create( p_dec
, "codec", VLC_VAR_STRING
| VLC_VAR_DOINHERIT
);
527 var_SetString( p_dec
, "codec", "sdl_image" );
530 p_pic
= image_Read( p_image
, p_block
, &es_in
, &fmt_out
);
531 var_Destroy( p_dec
, "codec" );
532 es_format_Clean( &es_in
);
533 video_format_Clean( &fmt_out
);
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( "spacing", attr
) )
754 p_ssa_style
->p_style
->i_spacing
= atoi( val
);
758 else if( !strcasecmp( "position", node
) && (i_style_level
== 2) )
760 const char *attr
, *val
;
761 while( (attr
= xml_ReaderNextAttr( p_xml_reader
, &val
)) )
763 if( !strcasecmp( "alignment", attr
) )
765 if( !strcasecmp( "TopLeft", val
) )
766 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_TOP
| SUBPICTURE_ALIGN_LEFT
;
767 else if( !strcasecmp( "TopCenter", val
) )
768 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_TOP
;
769 else if( !strcasecmp( "TopRight", val
) )
770 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_TOP
| SUBPICTURE_ALIGN_RIGHT
;
771 else if( !strcasecmp( "MiddleLeft", val
) )
772 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_LEFT
;
773 else if( !strcasecmp( "MiddleCenter", val
) )
774 p_ssa_style
->i_align
= 0;
775 else if( !strcasecmp( "MiddleRight", val
) )
776 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_RIGHT
;
777 else if( !strcasecmp( "BottomLeft", val
) )
778 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_BOTTOM
| SUBPICTURE_ALIGN_LEFT
;
779 else if( !strcasecmp( "BottomCenter", val
) )
780 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_BOTTOM
;
781 else if( !strcasecmp( "BottomRight", val
) )
782 p_ssa_style
->i_align
= SUBPICTURE_ALIGN_BOTTOM
| SUBPICTURE_ALIGN_RIGHT
;
784 else if( !strcasecmp( "horizontal-margin", attr
) )
786 if( strchr( val
, '%' ) )
788 p_ssa_style
->i_margin_h
= 0;
789 p_ssa_style
->i_margin_percent_h
= atoi( val
);
793 p_ssa_style
->i_margin_h
= atoi( val
);
794 p_ssa_style
->i_margin_percent_h
= 0;
797 else if( !strcasecmp( "vertical-margin", attr
) )
799 if( strchr( val
, '%' ) )
801 p_ssa_style
->i_margin_v
= 0;
802 p_ssa_style
->i_margin_percent_v
= atoi( val
);
806 p_ssa_style
->i_margin_v
= atoi( val
);
807 p_ssa_style
->i_margin_percent_v
= 0;
820 static subpicture_region_t
*ParseUSFString( decoder_t
*p_dec
,
823 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
824 subpicture_region_t
*p_region_first
= NULL
;
825 subpicture_region_t
*p_region_upto
= p_region_first
;
827 while( *psz_subtitle
)
829 if( *psz_subtitle
== '<' )
831 char *psz_end
= NULL
;
834 if(( !strncasecmp( psz_subtitle
, "<karaoke ", 9 )) ||
835 ( !strncasecmp( psz_subtitle
, "<karaoke>", 9 )))
837 psz_end
= strcasestr( psz_subtitle
, "</karaoke>" );
841 subpicture_region_t
*p_text_region
;
843 char *psz_flat
= NULL
;
844 char *psz_knodes
= strndup( &psz_subtitle
[9], psz_end
- &psz_subtitle
[9] );
847 /* remove timing <k> tags */
848 psz_flat
= CreatePlainText( psz_knodes
);
852 p_text_region
= CreateTextRegion( p_dec
,
857 free( p_text_region
->p_text
->psz_text
);
858 p_text_region
->p_text
->psz_text
= psz_flat
;
859 if( !p_region_first
)
861 p_region_first
= p_region_upto
= p_text_region
;
863 else if( p_text_region
)
865 p_region_upto
->p_next
= p_text_region
;
866 p_region_upto
= p_region_upto
->p_next
;
869 else free( psz_flat
);
873 psz_end
+= strcspn( psz_end
, ">" ) + 1;
876 else if(( !strncasecmp( psz_subtitle
, "<image ", 7 )) ||
877 ( !strncasecmp( psz_subtitle
, "<image>", 7 )))
879 subpicture_region_t
*p_image_region
= NULL
;
881 psz_end
= strcasestr( psz_subtitle
, "</image>" );
882 char *psz_content
= strchr( psz_subtitle
, '>' );
883 int i_transparent
= -1;
885 /* If a colorkey parameter is specified, then we have to map
886 * that index in the picture through as transparent (it is
887 * required by the USF spec but is also recommended that if the
888 * creator really wants a transparent colour that they use a
889 * type like PNG that properly supports it; this goes doubly
890 * for VLC because the pictures are stored internally in YUV
891 * and the resulting colour-matching may not produce the
894 char *psz_tmp
= GrabAttributeValue( "colorkey", psz_subtitle
);
897 if( *psz_tmp
== '#' )
898 i_transparent
= strtol( psz_tmp
+ 1, NULL
, 16 ) & 0x00ffffff;
901 if( psz_content
&& ( psz_content
< psz_end
) )
903 char *psz_filename
= strndup( &psz_content
[1], psz_end
- &psz_content
[1] );
906 p_image_region
= LoadEmbeddedImage( p_dec
,
907 psz_filename
, i_transparent
);
908 free( psz_filename
);
912 if( psz_end
) psz_end
+= strcspn( psz_end
, ">" ) + 1;
916 SetupPositions( p_image_region
, psz_subtitle
);
918 p_image_region
->p_next
= NULL
;
920 if( !p_region_first
)
922 p_region_first
= p_region_upto
= p_image_region
;
924 else if( p_image_region
)
926 p_region_upto
->p_next
= p_image_region
;
927 p_region_upto
= p_region_upto
->p_next
;
932 subpicture_region_t
*p_text_region
;
934 psz_end
= psz_subtitle
+ strlen( psz_subtitle
);
936 p_text_region
= CreateTextRegion( p_dec
,
942 free( p_text_region
->p_text
->psz_text
);
943 p_text_region
->p_text
->psz_text
= CreatePlainText( psz_subtitle
);
946 if( !p_region_first
)
948 p_region_first
= p_region_upto
= p_text_region
;
950 else if( p_text_region
)
952 p_region_upto
->p_next
= p_text_region
;
953 p_region_upto
= p_region_upto
->p_next
;
957 psz_subtitle
= psz_end
- 1;
959 psz_subtitle
+= strcspn( psz_subtitle
, ">" );
965 return p_region_first
;
968 /*****************************************************************************
969 * ParseUSFHeader: Retrieve global formatting information etc
970 *****************************************************************************/
971 static void ParseUSFHeader( decoder_t
*p_dec
)
973 stream_t
*p_sub
= NULL
;
974 xml_reader_t
*p_xml_reader
= NULL
;
976 p_sub
= vlc_stream_MemoryNew( VLC_OBJECT(p_dec
),
977 p_dec
->fmt_in
.p_extra
,
978 p_dec
->fmt_in
.i_extra
,
983 p_xml_reader
= xml_ReaderCreate( p_dec
, p_sub
);
984 if( likely(p_xml_reader
) )
988 /* Look for Root Node */
989 if( xml_ReaderNextNode( p_xml_reader
, &node
) == XML_READER_STARTELEM
990 && !strcasecmp( "usfsubtitles", node
) )
991 ParseUSFHeaderTags( p_dec
, p_xml_reader
);
993 xml_ReaderDelete( p_xml_reader
);
995 vlc_stream_Delete( p_sub
);
998 /* Function now handles tags which has attribute values, and tries
999 * to deal with &' commands too. It no longer modifies the string
1000 * in place, so that the original text can be reused
1002 static char *StripTags( char *psz_subtitle
)
1004 char *psz_text_start
;
1007 psz_text
= psz_text_start
= malloc( strlen( psz_subtitle
) + 1 );
1008 if( !psz_text_start
)
1011 while( *psz_subtitle
)
1013 /* Mask out any pre-existing LFs in the subtitle */
1014 if( *psz_subtitle
== '\n' )
1015 *psz_subtitle
= ' ';
1017 if( *psz_subtitle
== '<' )
1019 if( strncasecmp( psz_subtitle
, "<br/>", 5 ) == 0 )
1022 psz_subtitle
+= strcspn( psz_subtitle
, ">" );
1024 else if( *psz_subtitle
== '&' )
1026 if( !strncasecmp( psz_subtitle
, "<", 4 ))
1029 psz_subtitle
+= strcspn( psz_subtitle
, ";" );
1031 else if( !strncasecmp( psz_subtitle
, ">", 4 ))
1034 psz_subtitle
+= strcspn( psz_subtitle
, ";" );
1036 else if( !strncasecmp( psz_subtitle
, "&", 5 ))
1039 psz_subtitle
+= strcspn( psz_subtitle
, ";" );
1041 else if( !strncasecmp( psz_subtitle
, """, 6 ))
1044 psz_subtitle
+= strcspn( psz_subtitle
, ";" );
1048 /* Assume it is just a normal ampersand */
1054 *psz_text
++ = *psz_subtitle
;
1057 /* Security fix: Account for the case where input ends early */
1058 if( *psz_subtitle
== '\0' ) break;
1064 char *psz
= realloc( psz_text_start
, psz_text
- psz_text_start
);
1065 return likely(psz
!= NULL
) ? psz
: psz_text_start
;
1068 /* Turn a HTML subtitle, turn into a plain-text version,
1069 * complete with sensible whitespace compaction
1072 static char *CreatePlainText( char *psz_subtitle
)
1074 char *psz_text
= StripTags( psz_subtitle
);
1080 s
= strpbrk( psz_text
, "\t\r\n " );
1084 int i_whitespace
= strspn( s
, "\t\r\n " );
1086 /* Favour '\n' over other whitespaces - if one of these
1087 * occurs in the whitespace use a '\n' as our value,
1088 * otherwise just use a ' '
1090 for( int k
= 0; k
< i_whitespace
; k
++ )
1091 if( s
[k
] == '\n' ) spc
= '\n';
1093 if( i_whitespace
> 1 )
1097 strlen( s
) - i_whitespace
+ 1 );
1101 s
= strpbrk( s
, "\t\r\n " );
1106 /****************************************************************************
1107 * download and resize image located at psz_url
1108 ***************************************************************************/
1109 static subpicture_region_t
*LoadEmbeddedImage( decoder_t
*p_dec
,
1110 const char *psz_filename
,
1111 int i_transparent_color
)
1113 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
1114 subpicture_region_t
*p_region
;
1115 video_format_t fmt_out
;
1116 picture_t
*p_pic
= NULL
;
1118 for( int k
= 0; k
< p_sys
->i_images
; k
++ )
1120 if( p_sys
->pp_images
&&
1121 !strcmp( p_sys
->pp_images
[k
]->psz_filename
, psz_filename
) )
1123 p_pic
= p_sys
->pp_images
[k
]->p_pic
;
1130 msg_Err( p_dec
, "Unable to read image %s", psz_filename
);
1134 /* Display the feed's image */
1135 memset( &fmt_out
, 0, sizeof( video_format_t
));
1137 fmt_out
.i_chroma
= VLC_CODEC_YUVA
;
1138 fmt_out
.i_sar_num
= fmt_out
.i_sar_den
= 1;
1140 fmt_out
.i_visible_width
= p_pic
->format
.i_visible_width
;
1142 fmt_out
.i_visible_height
= p_pic
->format
.i_visible_height
;
1144 p_region
= subpicture_region_New( &fmt_out
);
1147 msg_Err( p_dec
, "cannot allocate SPU region" );
1150 assert( p_pic
->format
.i_chroma
== VLC_CODEC_YUVA
);
1151 /* FIXME the copy is probably not needed anymore */
1152 picture_CopyPixels( p_region
->p_picture
, p_pic
);
1154 /* This isn't the best way to do this - if you really want transparency, then
1155 * you're much better off using an image type that supports it like PNG. The
1156 * spec requires this support though.
1158 if( i_transparent_color
> 0 )
1160 int i_r
= ( i_transparent_color
>> 16 ) & 0xff;
1161 int i_g
= ( i_transparent_color
>> 8 ) & 0xff;
1162 int i_b
= ( i_transparent_color
) & 0xff;
1164 /* FIXME it cannot work as the yuv conversion code will probably NOT match
1166 int i_y
= ( ( ( 66 * i_r
+ 129 * i_g
+ 25 * i_b
+ 128 ) >> 8 ) + 16 );
1167 int i_u
= ( ( -38 * i_r
- 74 * i_g
+ 112 * i_b
+ 128 ) >> 8 ) + 128 ;
1168 int i_v
= ( ( 112 * i_r
- 94 * i_g
- 18 * i_b
+ 128 ) >> 8 ) + 128 ;
1170 assert( p_region
->fmt
.i_chroma
== VLC_CODEC_YUVA
);
1171 for( unsigned int y
= 0; y
< p_region
->fmt
.i_height
; y
++ )
1173 for( unsigned int x
= 0; x
< p_region
->fmt
.i_width
; x
++ )
1175 if( p_region
->p_picture
->Y_PIXELS
[y
*p_region
->p_picture
->Y_PITCH
+ x
] != i_y
||
1176 p_region
->p_picture
->U_PIXELS
[y
*p_region
->p_picture
->U_PITCH
+ x
] != i_u
||
1177 p_region
->p_picture
->V_PIXELS
[y
*p_region
->p_picture
->V_PITCH
+ x
] != i_v
)
1179 p_region
->p_picture
->A_PIXELS
[y
*p_region
->p_picture
->A_PITCH
+ x
] = 0;