1 /*****************************************************************************
2 * subsusf.c : USF subtitles decoder
3 *****************************************************************************
4 * Copyright (C) 2000-2006 the VideoLAN team
5 * $Id: subsdec.c 20996 2007-08-05 20:01:21Z jb $
7 * Authors: Gildas Bazin <gbazin@videolan.org>
8 * Samuel Hocevar <sam@zoy.org>
9 * Derk-Jan Hartman <hartman at videolan dot org>
10 * Bernie Purcell <b dot purcell at adbglobal dot com>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 *****************************************************************************/
29 static void ParseUSFHeaderTags( decoder_t *p_dec, xml_reader_t *p_xml_reader )
31 decoder_sys_t *p_sys = p_dec->p_sys;
33 ssa_style_t *p_style = NULL;
34 int i_style_level = 0;
35 int i_metadata_level = 0;
37 while ( xml_ReaderRead( p_xml_reader ) == 1 )
39 switch ( xml_ReaderNodeType( p_xml_reader ) )
44 case XML_READER_ENDELEM:
45 psz_node = xml_ReaderName( p_xml_reader );
49 switch (i_style_level)
52 if( !strcasecmp( "metadata", psz_node ) && (i_metadata_level == 1) )
58 if( !strcasecmp( "styles", psz_node ) )
64 if( !strcasecmp( "style", psz_node ) )
66 TAB_APPEND( p_sys->i_ssa_styles, p_sys->pp_ssa_styles, p_style );
76 case XML_READER_STARTELEM:
77 psz_node = xml_ReaderName( p_xml_reader );
82 if( !strcasecmp( "metadata", psz_node ) && (i_style_level == 0) )
86 else if( !strcasecmp( "resolution", psz_node ) &&
87 ( i_metadata_level == 1) )
89 while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
91 char *psz_name = xml_ReaderName ( p_xml_reader );
92 char *psz_value = xml_ReaderValue ( p_xml_reader );
94 if( psz_name && psz_value )
96 if( !strcasecmp( "x", psz_name ) )
97 p_sys->i_original_width = atoi( psz_value );
98 else if( !strcasecmp( "y", psz_name ) )
99 p_sys->i_original_height = atoi( psz_value );
101 if( psz_name ) free( psz_name );
102 if( psz_value ) free( psz_value );
105 else if( !strcasecmp( "styles", psz_node ) && (i_style_level == 0) )
109 else if( !strcasecmp( "style", psz_node ) && (i_style_level == 1) )
113 p_style = calloc( 1, sizeof(ssa_style_t) );
116 msg_Err( p_dec, "out of memory" );
120 /* All styles are supposed to default to Default, and then
121 * one or more settings are over-ridden.
122 * At the moment this only effects styles defined AFTER
126 for( i = 0; i < p_sys->i_ssa_styles; i++ )
128 if( !strcasecmp( p_sys->pp_ssa_styles[i]->psz_stylename, "Default" ) )
130 ssa_style_t *p_default_style = p_sys->pp_ssa_styles[i];
132 memcpy( p_style, p_default_style, sizeof( ssa_style_t ) );
133 p_style->font_style.psz_fontname = strdup( p_style->font_style.psz_fontname );
134 p_style->psz_stylename = NULL;
138 while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
140 char *psz_name = xml_ReaderName ( p_xml_reader );
141 char *psz_value = xml_ReaderValue ( p_xml_reader );
143 if( psz_name && psz_value )
145 if( !strcasecmp( "name", psz_name ) )
146 p_style->psz_stylename = strdup( psz_value);
148 if( psz_name ) free( psz_name );
149 if( psz_value ) free( psz_value );
152 else if( !strcasecmp( "fontstyle", psz_node ) && (i_style_level == 2) )
154 while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
156 char *psz_name = xml_ReaderName ( p_xml_reader );
157 char *psz_value = xml_ReaderValue ( p_xml_reader );
159 if( psz_name && psz_value )
161 if( !strcasecmp( "face", psz_name ) )
163 if( p_style->font_style.psz_fontname )
164 free( p_style->font_style.psz_fontname );
165 p_style->font_style.psz_fontname = strdup( psz_value );
167 else if( !strcasecmp( "size", psz_name ) )
169 if( ( *psz_value == '+' ) || ( *psz_value == '-' ) )
171 int i_value = atoi( psz_value );
173 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
174 p_style->font_style.i_font_size +=
175 ( i_value * p_style->font_style.i_font_size ) / 10;
176 else if( i_value < -5 )
177 p_style->font_style.i_font_size = - i_value;
178 else if( i_value > 5 )
179 p_style->font_style.i_font_size = i_value;
182 p_style->font_style.i_font_size = atoi( psz_value );
184 else if( !strcasecmp( "italic", psz_name ) )
186 if( !strcasecmp( "yes", psz_value ))
187 p_style->font_style.i_style_flags |= STYLE_ITALIC;
189 p_style->font_style.i_style_flags &= ~STYLE_ITALIC;
191 else if( !strcasecmp( "weight", psz_name ) )
193 if( !strcasecmp( "bold", psz_value ))
194 p_style->font_style.i_style_flags |= STYLE_BOLD;
196 p_style->font_style.i_style_flags &= ~STYLE_BOLD;
198 else if( !strcasecmp( "underline", psz_name ) )
200 if( !strcasecmp( "yes", psz_value ))
201 p_style->font_style.i_style_flags |= STYLE_UNDERLINE;
203 p_style->font_style.i_style_flags &= ~STYLE_UNDERLINE;
205 else if( !strcasecmp( "color", psz_name ) )
207 if( *psz_value == '#' )
209 unsigned long col = strtol(psz_value+1, NULL, 16);
210 p_style->font_style.i_font_color = (col & 0x00ffffff);
211 p_style->font_style.i_font_alpha = (col >> 24) & 0xff;
214 else if( !strcasecmp( "outline-color", psz_name ) )
216 if( *psz_value == '#' )
218 unsigned long col = strtol(psz_value+1, NULL, 16);
219 p_style->font_style.i_outline_color = (col & 0x00ffffff);
220 p_style->font_style.i_outline_alpha = (col >> 24) & 0xff;
223 else if( !strcasecmp( "outline-level", psz_name ) )
225 p_style->font_style.i_outline_width = atoi( psz_value );
227 else if( !strcasecmp( "shadow-color", psz_name ) )
229 if( *psz_value == '#' )
231 unsigned long col = strtol(psz_value+1, NULL, 16);
232 p_style->font_style.i_shadow_color = (col & 0x00ffffff);
233 p_style->font_style.i_shadow_alpha = (col >> 24) & 0xff;
236 else if( !strcasecmp( "shadow-level", psz_name ) )
238 p_style->font_style.i_shadow_width = atoi( psz_value );
240 else if( !strcasecmp( "back-color", psz_name ) )
242 if( *psz_value == '#' )
244 unsigned long col = strtol(psz_value+1, NULL, 16);
245 p_style->font_style.i_karaoke_background_color = (col & 0x00ffffff);
246 p_style->font_style.i_karaoke_background_alpha = (col >> 24) & 0xff;
249 else if( !strcasecmp( "spacing", psz_name ) )
251 p_style->font_style.i_spacing = atoi( psz_value );
254 if( psz_name ) free( psz_name );
255 if( psz_value ) free( psz_value );
258 else if( !strcasecmp( "position", psz_node ) && (i_style_level == 2) )
260 while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
262 char *psz_name = xml_ReaderName ( p_xml_reader );
263 char *psz_value = xml_ReaderValue ( p_xml_reader );
265 if( psz_name && psz_value )
267 if( !strcasecmp( "alignment", psz_name ) )
269 if( !strcasecmp( "TopLeft", psz_value ) )
270 p_style->i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_LEFT;
271 else if( !strcasecmp( "TopCenter", psz_value ) )
272 p_style->i_align = SUBPICTURE_ALIGN_TOP;
273 else if( !strcasecmp( "TopRight", psz_value ) )
274 p_style->i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_RIGHT;
275 else if( !strcasecmp( "MiddleLeft", psz_value ) )
276 p_style->i_align = SUBPICTURE_ALIGN_LEFT;
277 else if( !strcasecmp( "MiddleCenter", psz_value ) )
278 p_style->i_align = 0;
279 else if( !strcasecmp( "MiddleRight", psz_value ) )
280 p_style->i_align = SUBPICTURE_ALIGN_RIGHT;
281 else if( !strcasecmp( "BottomLeft", psz_value ) )
282 p_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_LEFT;
283 else if( !strcasecmp( "BottomCenter", psz_value ) )
284 p_style->i_align = SUBPICTURE_ALIGN_BOTTOM;
285 else if( !strcasecmp( "BottomRight", psz_value ) )
286 p_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT;
288 else if( !strcasecmp( "horizontal-margin", psz_name ) )
290 if( strchr( psz_value, '%' ) )
292 p_style->i_margin_h = 0;
293 p_style->i_margin_percent_h = atoi( psz_value );
297 p_style->i_margin_h = atoi( psz_value );
298 p_style->i_margin_percent_h = 0;
301 else if( !strcasecmp( "vertical-margin", psz_name ) )
303 if( strchr( psz_value, '%' ) )
305 p_style->i_margin_v = 0;
306 p_style->i_margin_percent_v = atoi( psz_value );
310 p_style->i_margin_v = atoi( psz_value );
311 p_style->i_margin_percent_v = 0;
315 if( psz_name ) free( psz_name );
316 if( psz_value ) free( psz_value );
324 if( p_style ) free( p_style );
329 subpicture_region_t *ParseUSFString( decoder_t *p_dec,
331 subpicture_t *p_spu_in )
333 decoder_sys_t *p_sys = p_dec->p_sys;
334 subpicture_t *p_spu = p_spu_in;
335 subpicture_region_t *p_region_first = NULL;
336 subpicture_region_t *p_region_upto = p_region_first;
338 while( *psz_subtitle )
340 if( *psz_subtitle == '<' )
342 char *psz_end = NULL;
344 if(( !strncasecmp( psz_subtitle, "<text ", 6 )) ||
345 ( !strncasecmp( psz_subtitle, "<text>", 6 )))
347 psz_end = strcasestr( psz_subtitle, "</text>" );
351 subpicture_region_t *p_text_region;
353 psz_end += strcspn( psz_end, ">" ) + 1;
355 p_text_region = CreateTextRegion( p_dec,
358 psz_end - psz_subtitle,
363 p_text_region->psz_text = CreatePlainText( p_text_region->psz_html );
365 if( ! var_CreateGetBool( p_dec, "subsdec-formatted" ) )
367 free( p_text_region->psz_html );
368 p_text_region->psz_html = NULL;
372 if( !p_region_first )
374 p_region_first = p_region_upto = p_text_region;
376 else if( p_text_region )
378 p_region_upto->p_next = p_text_region;
379 p_region_upto = p_region_upto->p_next;
383 else if(( !strncasecmp( psz_subtitle, "<karaoke ", 9 )) ||
384 ( !strncasecmp( psz_subtitle, "<karaoke>", 9 )))
386 psz_end = strcasestr( psz_subtitle, "</karaoke>" );
390 subpicture_region_t *p_text_region;
392 psz_end += strcspn( psz_end, ">" ) + 1;
394 p_text_region = CreateTextRegion( p_dec,
397 psz_end - psz_subtitle,
402 if( ! var_CreateGetBool( p_dec, "subsdec-formatted" ) )
404 free( p_text_region->psz_html );
405 p_text_region->psz_html = NULL;
408 if( !p_region_first )
410 p_region_first = p_region_upto = p_text_region;
412 else if( p_text_region )
414 p_region_upto->p_next = p_text_region;
415 p_region_upto = p_region_upto->p_next;
419 else if(( !strncasecmp( psz_subtitle, "<image ", 7 )) ||
420 ( !strncasecmp( psz_subtitle, "<image>", 7 )))
422 subpicture_region_t *p_image_region = NULL;
424 char *psz_end = strcasestr( psz_subtitle, "</image>" );
425 char *psz_content = strchr( psz_subtitle, '>' );
426 int i_transparent = -1;
428 /* If a colorkey parameter is specified, then we have to map
429 * that index in the picture through as transparent (it is
430 * required by the USF spec but is also recommended that if the
431 * creator really wants a transparent colour that they use a
432 * type like PNG that properly supports it; this goes doubly
433 * for VLC because the pictures are stored internally in YUV
434 * and the resulting colour-matching may not produce the
437 char *psz_tmp = GrabAttributeValue( "colorkey", psz_subtitle );
440 if( *psz_tmp == '#' )
441 i_transparent = strtol( psz_tmp + 1, NULL, 16 ) & 0x00ffffff;
444 if( psz_content && ( psz_content < psz_end ) )
446 char *psz_filename = strndup( &psz_content[1], psz_end - &psz_content[1] );
449 p_image_region = LoadEmbeddedImage( p_dec, p_spu,
450 psz_filename, i_transparent );
451 free( psz_filename );
455 if( psz_end ) psz_end += strcspn( psz_end, ">" ) + 1;
459 SetupPositions( p_image_region, psz_subtitle );
461 p_image_region->p_next = NULL;
462 p_image_region->psz_text = NULL;
463 p_image_region->psz_html = NULL;
466 if( !p_region_first )
468 p_region_first = p_region_upto = p_image_region;
470 else if( p_image_region )
472 p_region_upto->p_next = p_image_region;
473 p_region_upto = p_region_upto->p_next;
477 psz_subtitle = psz_end - 1;
479 psz_subtitle += strcspn( psz_subtitle, ">" );
485 return p_region_first;
488 /*****************************************************************************
489 * ParseUSFHeader: Retrieve global formatting information etc
490 *****************************************************************************/
491 void ParseUSFHeader( decoder_t *p_dec )
493 stream_t *p_sub = NULL;
495 xml_reader_t *p_xml_reader = NULL;
497 p_sub = stream_MemoryNew( VLC_OBJECT(p_dec),
498 p_dec->fmt_in.p_extra,
499 p_dec->fmt_in.i_extra,
504 p_xml = xml_Create( p_dec );
507 p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
510 /* Look for Root Node */
511 if( xml_ReaderRead( p_xml_reader ) == 1 )
513 char *psz_node = xml_ReaderName( p_xml_reader );
515 if( !strcasecmp( "usfsubtitles", psz_node ) )
516 ParseUSFHeaderTags( p_dec, p_xml_reader );
521 xml_ReaderDelete( p_xml, p_xml_reader );
525 stream_Delete( p_sub );