1 /*****************************************************************************
2 * substtml.c : TTML subtitles decoder
3 *****************************************************************************
4 * Copyright (C) 2015-2017 VLC authors and VideoLAN
6 * Authors: Hugo Beauzée-Luyssen <hugo@beauzee.fr>
7 * Sushma Reddy <sushma.reddy@research.iiit.ac.in>
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 *****************************************************************************/
27 #include <vlc_common.h>
28 #include <vlc_codec.h>
30 #include <vlc_stream.h>
31 #include <vlc_text_style.h>
32 #include <vlc_charset.h>
42 /*****************************************************************************
44 *****************************************************************************/
50 TTML_UNIT_UNKNOWN
= 0,
57 #define TTML_DEFAULT_CELL_RESOLUTION_H 32
58 #define TTML_DEFAULT_CELL_RESOLUTION_V 15
59 #define TTML_LINE_TO_HEIGHT_RATIO 1.06
63 text_style_t
* font_style
;
64 ttml_length_t font_size
;
65 ttml_length_t extent_h
, extent_v
;
67 bool b_text_align_set
;
70 bool b_preserve_space
;
73 TTML_DISPLAY_UNKNOWN
= 0,
81 vlc_dictionary_t regions
;
82 tt_node_t
* p_rootnode
; /* for now. FIXME: split header */
83 ttml_length_t root_extent_h
, root_extent_v
;
84 unsigned i_cell_resolution_v
;
85 unsigned i_cell_resolution_h
;
90 subpicture_updater_sys_region_t updt
;
91 text_segment_t
**pp_last_segment
;
101 UNICODE_BIDI_LTR
= 0,
102 UNICODE_BIDI_RTL
= 1,
103 UNICODE_BIDI_EMBEDDED
= 2,
104 UNICODE_BIDI_OVERRIDE
= 4,
108 * TTML Parsing and inheritance order:
109 * Each time a text node is found and belongs to out time interval,
110 * we backward merge attributes dictionnary up to root.
111 * Then we convert attributes, merging with style by id or region
112 * style, and sets from parent node.
114 static tt_node_t
*ParseTTML( decoder_t
*, const uint8_t *, size_t );
116 static void ttml_style_Delete( ttml_style_t
* p_ttml_style
)
118 text_style_Delete( p_ttml_style
->font_style
);
119 free( p_ttml_style
);
122 static ttml_style_t
* ttml_style_New( )
124 ttml_style_t
*p_ttml_style
= calloc( 1, sizeof( ttml_style_t
) );
125 if( unlikely( !p_ttml_style
) )
128 p_ttml_style
->extent_h
.i_value
= 100;
129 p_ttml_style
->extent_h
.unit
= TTML_UNIT_PERCENT
;
130 p_ttml_style
->extent_v
.i_value
= 100;
131 p_ttml_style
->extent_v
.unit
= TTML_UNIT_PERCENT
;
132 p_ttml_style
->font_size
.i_value
= 1.0;
133 p_ttml_style
->font_size
.unit
= TTML_UNIT_CELL
;
134 p_ttml_style
->font_style
= text_style_Create( STYLE_NO_DEFAULTS
);
135 if( unlikely( !p_ttml_style
->font_style
) )
137 free( p_ttml_style
);
143 static void ttml_region_Delete( ttml_region_t
*p_region
)
145 SubpictureUpdaterSysRegionClean( &p_region
->updt
);
149 static ttml_style_t
* ttml_style_Duplicate( const ttml_style_t
*p_src
)
151 ttml_style_t
*p_dup
= ttml_style_New( );
155 p_dup
->font_style
= text_style_Duplicate( p_src
->font_style
);
160 static void ttml_style_Merge( const ttml_style_t
*p_src
, ttml_style_t
*p_dst
)
164 if( p_src
->font_style
)
166 if( p_dst
->font_style
)
167 text_style_Merge( p_dst
->font_style
, p_src
->font_style
, true );
169 p_dst
->font_style
= text_style_Duplicate( p_src
->font_style
);
172 if( p_src
->b_direction_set
)
174 p_dst
->b_direction_set
= true;
175 p_dst
->i_direction
= p_src
->i_direction
;
178 if( p_src
->display
!= TTML_DISPLAY_UNKNOWN
)
179 p_dst
->display
= p_src
->display
;
183 static ttml_region_t
*ttml_region_New( )
185 ttml_region_t
*p_ttml_region
= calloc( 1, sizeof( ttml_region_t
) );
186 if( unlikely( !p_ttml_region
) )
189 SubpictureUpdaterSysRegionInit( &p_ttml_region
->updt
);
190 p_ttml_region
->pp_last_segment
= &p_ttml_region
->updt
.p_segments
;
191 /* Align to top by default. !Warn: center align is obtained with NO flags */
192 p_ttml_region
->updt
.align
= SUBPICTURE_ALIGN_TOP
|SUBPICTURE_ALIGN_LEFT
;
193 p_ttml_region
->updt
.inner_align
= SUBPICTURE_ALIGN_TOP
|SUBPICTURE_ALIGN_LEFT
;
195 return p_ttml_region
;
198 static ttml_length_t
ttml_read_length( const char *psz
)
200 ttml_length_t len
= { 0.0, TTML_UNIT_UNKNOWN
};
202 char* psz_end
= NULL
;
203 float size
= us_strtof( psz
, &psz_end
);
207 if( *psz_end
== 'c' || *psz_end
== 'r' )
208 len
.unit
= TTML_UNIT_CELL
;
209 else if( *psz_end
== '%' )
210 len
.unit
= TTML_UNIT_PERCENT
;
211 else if( *psz_end
== 'p' && *(psz_end
+ 1) == 'x' )
212 len
.unit
= TTML_UNIT_PIXELS
;
217 static ttml_length_t
ttml_rebase_length( ttml_length_t value
,
218 ttml_length_t reference
,
219 unsigned i_cell_resolution
)
221 if( value
.unit
== TTML_UNIT_PERCENT
)
223 value
.i_value
*= reference
.i_value
/ 100.0;
224 value
.unit
= reference
.unit
;
226 else if( value
.unit
== TTML_UNIT_CELL
)
228 value
.i_value
*= reference
.i_value
/ i_cell_resolution
;
229 value
.unit
= reference
.unit
;
235 static tt_node_t
* FindNode( tt_node_t
*p_node
, const char *psz_nodename
,
236 size_t i_maxdepth
, const char *psz_id
)
238 if( !tt_node_NameCompare( p_node
->psz_node_name
, psz_nodename
) )
242 char *psz
= vlc_dictionary_value_for_key( &p_node
->attr_dict
, "xml:id" );
243 if( psz
&& !strcmp( psz
, psz_id
) )
249 if( i_maxdepth
== 0 )
252 for( tt_basenode_t
*p_child
= p_node
->p_child
;
253 p_child
; p_child
= p_child
->p_next
)
255 if( p_child
->i_type
== TT_NODE_TYPE_TEXT
)
258 p_node
= FindNode( (tt_node_t
*) p_child
, psz_nodename
, i_maxdepth
- 1, psz_id
);
266 static void FillTextStyle( const char *psz_attr
, const char *psz_val
,
267 text_style_t
*p_text_style
)
269 if( !strcasecmp ( "tts:fontFamily", psz_attr
) )
271 free( p_text_style
->psz_fontname
);
272 p_text_style
->psz_fontname
= strdup( psz_val
);
274 else if( !strcasecmp( "tts:opacity", psz_attr
) )
276 p_text_style
->i_background_alpha
= atoi( psz_val
);
277 p_text_style
->i_font_alpha
= atoi( psz_val
);
278 p_text_style
->i_features
|= STYLE_HAS_BACKGROUND_ALPHA
| STYLE_HAS_FONT_ALPHA
;
280 else if( !strcasecmp( "tts:color", psz_attr
) )
282 unsigned int i_color
= vlc_html_color( psz_val
, NULL
);
283 p_text_style
->i_font_color
= (i_color
& 0xffffff);
284 p_text_style
->i_font_alpha
= (i_color
& 0xFF000000) >> 24;
285 p_text_style
->i_features
|= STYLE_HAS_FONT_COLOR
| STYLE_HAS_FONT_ALPHA
;
287 else if( !strcasecmp( "tts:backgroundColor", psz_attr
) )
289 unsigned int i_color
= vlc_html_color( psz_val
, NULL
);
290 p_text_style
->i_background_color
= i_color
& 0xFFFFFF;
291 p_text_style
->i_background_alpha
= (i_color
& 0xFF000000) >> 24;
292 p_text_style
->i_features
|= STYLE_HAS_BACKGROUND_COLOR
293 | STYLE_HAS_BACKGROUND_ALPHA
;
294 p_text_style
->i_style_flags
|= STYLE_BACKGROUND
;
296 else if( !strcasecmp( "tts:fontStyle", psz_attr
) )
298 if( !strcasecmp ( "italic", psz_val
) || !strcasecmp ( "oblique", psz_val
) )
299 p_text_style
->i_style_flags
|= STYLE_ITALIC
;
301 p_text_style
->i_style_flags
&= ~STYLE_ITALIC
;
302 p_text_style
->i_features
|= STYLE_HAS_FLAGS
;
304 else if( !strcasecmp ( "tts:fontWeight", psz_attr
) )
306 if( !strcasecmp ( "bold", psz_val
) )
307 p_text_style
->i_style_flags
|= STYLE_BOLD
;
309 p_text_style
->i_style_flags
&= ~STYLE_BOLD
;
310 p_text_style
->i_features
|= STYLE_HAS_FLAGS
;
312 else if( !strcasecmp ( "tts:textDecoration", psz_attr
) )
314 if( !strcasecmp ( "underline", psz_val
) )
315 p_text_style
->i_style_flags
|= STYLE_UNDERLINE
;
316 else if( !strcasecmp ( "noUnderline", psz_val
) )
317 p_text_style
->i_style_flags
&= ~STYLE_UNDERLINE
;
318 if( !strcasecmp ( "lineThrough", psz_val
) )
319 p_text_style
->i_style_flags
|= STYLE_STRIKEOUT
;
320 else if( !strcasecmp ( "noLineThrough", psz_val
) )
321 p_text_style
->i_style_flags
&= ~STYLE_STRIKEOUT
;
322 p_text_style
->i_features
|= STYLE_HAS_FLAGS
;
324 else if( !strcasecmp( "tts:textOutline", psz_attr
) )
326 char *value
= strdup( psz_val
);
327 char* psz_saveptr
= NULL
;
328 char* token
= (value
) ? strtok_r( value
, " ", &psz_saveptr
) : NULL
;
329 // <color>? <length> <length>?
333 unsigned int color
= vlc_html_color( token
, &b_ok
);
336 p_text_style
->i_outline_color
= color
& 0xFFFFFF;
337 p_text_style
->i_outline_alpha
= (color
& 0xFF000000) >> 24;
338 token
= strtok_r( NULL
, " ", &psz_saveptr
);
341 char* psz_end
= NULL
;
342 int i_outline_width
= strtol( token
, &psz_end
, 10 );
343 if( psz_end
!= token
)
345 // Assume unit is pixel, and ignore border radius
346 p_text_style
->i_outline_width
= i_outline_width
;
355 static void FillRegionStyle( const char *psz_attr
, const char *psz_val
,
356 ttml_context_t
*p_ctx
, ttml_region_t
*p_region
)
358 if( !strcasecmp( "tts:displayAlign", psz_attr
) )
360 p_region
->updt
.inner_align
&= ~(SUBPICTURE_ALIGN_TOP
|SUBPICTURE_ALIGN_BOTTOM
);
361 if( !strcasecmp( "after", psz_val
) )
362 p_region
->updt
.inner_align
|= SUBPICTURE_ALIGN_BOTTOM
;
363 else if( strcasecmp( "center", psz_val
) )
365 p_region
->updt
.inner_align
|= SUBPICTURE_ALIGN_TOP
;
367 else if( !strcasecmp ( "tts:origin", psz_attr
) ||
368 !strcasecmp ( "tts:extent", psz_attr
) )
370 const char *psz_token
= psz_val
;
371 while( isspace( *psz_token
) )
374 ttml_length_t x
= ttml_read_length( psz_token
);
376 while( *psz_token
&& !isspace( *psz_token
) )
378 while( *psz_token
&& isspace( *psz_token
) )
381 ttml_length_t y
= ttml_read_length( psz_token
);
383 if ( x
.unit
!= TTML_UNIT_UNKNOWN
&& y
.unit
!= TTML_UNIT_UNKNOWN
)
385 ttml_length_t base
= { 100.0, TTML_UNIT_PERCENT
};
386 x
= ttml_rebase_length( x
, base
, p_ctx
->i_cell_resolution_h
);
387 y
= ttml_rebase_length( y
, base
, p_ctx
->i_cell_resolution_v
);
388 if( psz_attr
[4] == 'o' )
390 p_region
->updt
.origin
.x
= x
.i_value
/ 100.0;
391 p_region
->updt
.flags
|= UPDT_REGION_ORIGIN_X_IS_RATIO
;
392 p_region
->updt
.origin
.y
= y
.i_value
/ 100.0;
393 p_region
->updt
.flags
|= UPDT_REGION_ORIGIN_Y_IS_RATIO
;
394 p_region
->updt
.align
= SUBPICTURE_ALIGN_TOP
|SUBPICTURE_ALIGN_LEFT
;
398 p_region
->updt
.extent
.x
= x
.i_value
/ 100.0;
399 p_region
->updt
.flags
|= UPDT_REGION_EXTENT_X_IS_RATIO
;
400 p_region
->updt
.extent
.y
= y
.i_value
/ 100.0;
401 p_region
->updt
.flags
|= UPDT_REGION_EXTENT_Y_IS_RATIO
;
407 static void ReadTTMLExtent( const char *value
, ttml_length_t
*h
, ttml_length_t
*v
)
409 ttml_length_t vals
[2] = { { 0.0, TTML_UNIT_UNKNOWN
},
410 { 0.0, TTML_UNIT_UNKNOWN
} };
411 char *dup
= strdup( value
);
412 char* psz_saveptr
= NULL
;
413 char* token
= (dup
) ? strtok_r( dup
, " ", &psz_saveptr
) : NULL
;
414 for(int i
=0; i
<2 && token
!= NULL
; i
++)
416 token
= strtok_r( NULL
, " ", &psz_saveptr
);
418 vals
[i
] = ttml_read_length( token
);
422 if( vals
[0].unit
!= TTML_UNIT_UNKNOWN
&&
423 vals
[1].unit
!= TTML_UNIT_UNKNOWN
)
430 static void ComputeTTMLStyles( ttml_context_t
*p_ctx
, const vlc_dictionary_t
*p_dict
,
431 ttml_style_t
*p_ttml_style
)
434 /* Values depending on multiple others are converted last
435 * Default value conversion must also not depend on attribute presence */
436 text_style_t
*p_text_style
= p_ttml_style
->font_style
;
437 ttml_length_t len
= p_ttml_style
->font_size
;
438 len
= ttml_rebase_length( len
, p_ctx
->root_extent_h
,
439 p_ctx
->i_cell_resolution_v
);
440 if( len
.unit
== TTML_UNIT_CELL
)
441 p_text_style
->f_font_relsize
= 100.0 * len
.i_value
/
442 (p_ctx
->i_cell_resolution_v
/ TTML_LINE_TO_HEIGHT_RATIO
);
443 else if( len
.unit
== TTML_UNIT_PERCENT
)
444 p_text_style
->f_font_relsize
= len
.i_value
/
445 (p_ctx
->i_cell_resolution_v
/ TTML_LINE_TO_HEIGHT_RATIO
);
446 else if( len
.unit
== TTML_UNIT_PIXELS
)
447 p_text_style
->i_font_size
= (int)( len
.i_value
+ 0.5 );
450 static void FillTTMLStyle( const char *psz_attr
, const char *psz_val
,
451 ttml_style_t
*p_ttml_style
)
453 if( !strcasecmp( "tts:extent", psz_attr
) )
455 ReadTTMLExtent( psz_attr
, &p_ttml_style
->extent_h
,
456 &p_ttml_style
->extent_v
);
458 else if( !strcasecmp( "tts:textAlign", psz_attr
) )
460 p_ttml_style
->i_text_align
&= ~(SUBPICTURE_ALIGN_LEFT
|SUBPICTURE_ALIGN_RIGHT
);
461 if( !strcasecmp ( "left", psz_val
) )
462 p_ttml_style
->i_text_align
|= SUBPICTURE_ALIGN_LEFT
;
463 else if( !strcasecmp ( "right", psz_val
) )
464 p_ttml_style
->i_text_align
|= SUBPICTURE_ALIGN_RIGHT
;
465 else if( !strcasecmp ( "end", psz_val
) ) /* FIXME: should be BIDI based */
466 p_ttml_style
->i_text_align
|= SUBPICTURE_ALIGN_RIGHT
;
467 else if( strcasecmp ( "center", psz_val
) )
468 /* == "start" FIXME: should be BIDI based */
469 p_ttml_style
->i_text_align
|= SUBPICTURE_ALIGN_LEFT
;
470 p_ttml_style
->b_text_align_set
= true;
472 printf("**%s %x\n", psz_val
, p_ttml_style
->i_text_align
);
475 else if( !strcasecmp( "tts:fontSize", psz_attr
) )
477 ttml_length_t len
= ttml_read_length( psz_val
);
478 if( len
.unit
!= TTML_UNIT_UNKNOWN
&& len
.i_value
> 0.0 )
479 p_ttml_style
->font_size
= len
;
481 else if( !strcasecmp( "tts:direction", psz_attr
) )
483 if( !strcasecmp( "rtl", psz_val
) )
485 p_ttml_style
->i_direction
|= UNICODE_BIDI_RTL
;
486 p_ttml_style
->b_direction_set
= true;
488 else if( !strcasecmp( "ltr", psz_val
) )
490 p_ttml_style
->i_direction
|= UNICODE_BIDI_LTR
;
491 p_ttml_style
->b_direction_set
= true;
494 else if( !strcasecmp( "tts:unicodeBidi", psz_attr
) )
496 if( !strcasecmp( "bidiOverride", psz_val
) )
497 p_ttml_style
->i_direction
|= UNICODE_BIDI_OVERRIDE
& ~UNICODE_BIDI_EMBEDDED
;
498 else if( !strcasecmp( "embed", psz_val
) )
499 p_ttml_style
->i_direction
|= UNICODE_BIDI_EMBEDDED
& ~UNICODE_BIDI_OVERRIDE
;
501 else if( !strcasecmp( "tts:writingMode", psz_attr
) )
503 if( !strcasecmp( "rl", psz_val
) || !strcasecmp( "rltb", psz_val
) )
505 p_ttml_style
->i_direction
= UNICODE_BIDI_RTL
| UNICODE_BIDI_OVERRIDE
;
506 //p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT;
507 p_ttml_style
->b_direction_set
= true;
509 else if( !strcasecmp( "lr", psz_val
) || !strcasecmp( "lrtb", psz_val
) )
511 p_ttml_style
->i_direction
= UNICODE_BIDI_LTR
| UNICODE_BIDI_OVERRIDE
;
512 //p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_LEFT;
513 p_ttml_style
->b_direction_set
= true;
516 else if( !strcmp( "tts:display", psz_attr
) )
518 if( !strcmp( "none", psz_val
) )
519 p_ttml_style
->display
= TTML_DISPLAY_NONE
;
521 p_ttml_style
->display
= TTML_DISPLAY_AUTO
;
523 else if( !strcasecmp( "xml:space", psz_attr
) )
525 p_ttml_style
->b_preserve_space
= !strcmp( "preserve", psz_val
);
527 else FillTextStyle( psz_attr
, psz_val
, p_ttml_style
->font_style
);
530 static void DictionaryMerge( const vlc_dictionary_t
*p_src
, vlc_dictionary_t
*p_dst
)
532 for( int i
= 0; i
< p_src
->i_size
; ++i
)
534 for ( const vlc_dictionary_entry_t
* p_entry
= p_src
->p_entries
[i
];
535 p_entry
!= NULL
; p_entry
= p_entry
->p_next
)
537 if( ( !strncmp( "tts:", p_entry
->psz_key
, 4 ) ||
538 !strncmp( "ttp:", p_entry
->psz_key
, 4 ) ||
539 !strcmp( "xml:space", p_entry
->psz_key
) ) &&
540 !vlc_dictionary_has_key( p_dst
, p_entry
->psz_key
) )
541 vlc_dictionary_insert( p_dst
, p_entry
->psz_key
, p_entry
->p_value
);
546 static void DictMergeWithStyleID( ttml_context_t
*p_ctx
, const char *psz_id
,
547 vlc_dictionary_t
*p_dst
)
549 assert(p_ctx
->p_rootnode
);
550 if( psz_id
&& p_ctx
->p_rootnode
)
552 /* Lookup referenced style ID */
553 const tt_node_t
*p_node
= FindNode( p_ctx
->p_rootnode
,
554 "style", -1, psz_id
);
556 DictionaryMerge( &p_node
->attr_dict
, p_dst
);
560 static void DictMergeWithRegionID( ttml_context_t
*p_ctx
, const char *psz_id
,
561 vlc_dictionary_t
*p_dst
)
563 assert(p_ctx
->p_rootnode
);
564 if( psz_id
&& p_ctx
->p_rootnode
)
566 const tt_node_t
*p_regionnode
= FindNode( p_ctx
->p_rootnode
,
567 "region", -1, psz_id
);
571 DictionaryMerge( &p_regionnode
->attr_dict
, p_dst
);
573 const char *psz_styleid
= (const char *)
574 vlc_dictionary_value_for_key( &p_regionnode
->attr_dict
, "style" );
576 DictMergeWithStyleID( p_ctx
, psz_styleid
, p_dst
);
578 for( const tt_basenode_t
*p_child
= p_regionnode
->p_child
;
579 p_child
; p_child
= p_child
->p_next
)
581 if( unlikely( p_child
->i_type
== TT_NODE_TYPE_TEXT
) )
584 const tt_node_t
*p_node
= (const tt_node_t
*) p_child
;
585 if( !tt_node_NameCompare( p_node
->psz_node_name
, "style" ) )
587 DictionaryMerge( &p_node
->attr_dict
, p_dst
);
593 static void DictToTTMLStyle( ttml_context_t
*p_ctx
, const vlc_dictionary_t
*p_dict
,
594 ttml_style_t
*p_ttml_style
)
596 for( int i
= 0; i
< p_dict
->i_size
; ++i
)
598 for ( vlc_dictionary_entry_t
* p_entry
= p_dict
->p_entries
[i
];
599 p_entry
!= NULL
; p_entry
= p_entry
->p_next
)
601 FillTTMLStyle( p_entry
->psz_key
, p_entry
->p_value
, p_ttml_style
);
604 ComputeTTMLStyles( p_ctx
, p_dict
, p_ttml_style
);
607 static ttml_style_t
* InheritTTMLStyles( ttml_context_t
*p_ctx
, tt_node_t
*p_node
)
610 ttml_style_t
*p_ttml_style
= NULL
;
611 vlc_dictionary_t merged
;
612 vlc_dictionary_init( &merged
, 0 );
614 /* Merge dics backwards without overwriting */
615 for( ; p_node
; p_node
= p_node
->p_parent
)
617 DictionaryMerge( &p_node
->attr_dict
, &merged
);
619 const char *psz_styleid
= (const char *)
620 vlc_dictionary_value_for_key( &p_node
->attr_dict
, "style" );
622 DictMergeWithStyleID( p_ctx
, psz_styleid
, &merged
);
624 const char *psz_regionid
= (const char *)
625 vlc_dictionary_value_for_key( &p_node
->attr_dict
, "region" );
627 DictMergeWithRegionID( p_ctx
, psz_regionid
, &merged
);
630 if( !vlc_dictionary_is_empty( &merged
) && (p_ttml_style
= ttml_style_New()) )
632 DictToTTMLStyle( p_ctx
, &merged
, p_ttml_style
);
635 vlc_dictionary_clear( &merged
, NULL
, NULL
);
640 static int ParseTTMLChunk( xml_reader_t
*p_reader
, tt_node_t
**pp_rootnode
)
642 const char* psz_node_name
;
646 int i_type
= xml_ReaderNextNode( p_reader
, &psz_node_name
);
648 if( i_type
<= XML_READER_NONE
)
656 case XML_READER_STARTELEM
:
657 if( tt_node_NameCompare( psz_node_name
, "tt" ) ||
658 *pp_rootnode
!= NULL
)
661 *pp_rootnode
= tt_node_New( p_reader
, NULL
, psz_node_name
);
663 tt_nodes_Read( p_reader
, *pp_rootnode
) != VLC_SUCCESS
)
667 case XML_READER_ENDELEM
:
669 tt_node_NameCompare( psz_node_name
, (*pp_rootnode
)->psz_node_name
) )
676 if( *pp_rootnode
== NULL
)
682 static void BIDIConvert( text_segment_t
*p_segment
, int i_direction
)
685 * For bidirectionnal support, we use different enum
686 * to recognize different cases, en then we add the
687 * corresponding unicode character to the text of
692 const char* psz_uni_start
;
693 const char* psz_uni_end
;
695 { "\u2066", "\u2069" },
696 { "\u2067", "\u2069" },
697 { "\u202A", "\u202C" },
698 { "\u202B", "\u202C" },
699 { "\u202D", "\u202C" },
700 { "\u202E", "\u202C" },
703 if( unlikely((size_t)i_direction
>= ARRAY_SIZE(p_bidi
)) )
706 char *psz_text
= NULL
;
707 if( asprintf( &psz_text
, "%s%s%s", p_bidi
[i_direction
].psz_uni_start
,
708 p_segment
->psz_text
, p_bidi
[i_direction
].psz_uni_end
) < 0 )
710 free( p_segment
->psz_text
);
711 p_segment
->psz_text
= psz_text
;
715 static void StripSpacing( text_segment_t
*p_segment
)
717 /* Newlines must be replaced */
718 char *p
= p_segment
->psz_text
;
719 while( (p
= strchr( p
, '\n' )) )
723 static ttml_region_t
*GetTTMLRegion( ttml_context_t
*p_ctx
, const char *psz_region_id
)
725 ttml_region_t
*p_region
= ( ttml_region_t
* )
726 vlc_dictionary_value_for_key( &p_ctx
->regions
, psz_region_id
? psz_region_id
: "" );
727 if( p_region
== NULL
)
729 if( psz_region_id
&& strcmp( psz_region_id
, "" ) ) /* not default region */
731 /* Create region if if missing */
733 vlc_dictionary_t merged
;
734 vlc_dictionary_init( &merged
, 0 );
735 /* Get all attributes, including region > style */
736 DictMergeWithRegionID( p_ctx
, psz_region_id
, &merged
);
737 if( (p_region
= ttml_region_New()) )
739 /* Fill from its own attributes */
740 for( int i
= 0; i
< merged
.i_size
; ++i
)
742 for ( vlc_dictionary_entry_t
* p_entry
= merged
.p_entries
[i
];
743 p_entry
!= NULL
; p_entry
= p_entry
->p_next
)
745 FillRegionStyle( p_entry
->psz_key
, p_entry
->p_value
,
750 vlc_dictionary_clear( &merged
, NULL
, NULL
);
752 vlc_dictionary_insert( &p_ctx
->regions
, psz_region_id
, p_region
);
754 else if( (p_region
= ttml_region_New()) ) /* create default */
756 vlc_dictionary_insert( &p_ctx
->regions
, "", p_region
);
762 static void AppendLineBreakToRegion( ttml_region_t
*p_region
)
764 text_segment_t
*p_segment
= text_segment_New( "\n" );
767 *p_region
->pp_last_segment
= p_segment
;
768 p_region
->pp_last_segment
= &p_segment
->p_next
;
772 static void AppendTextToRegion( ttml_context_t
*p_ctx
, const tt_textnode_t
*p_ttnode
,
773 const ttml_style_t
*p_set_styles
, ttml_region_t
*p_region
)
775 text_segment_t
*p_segment
;
777 if( p_region
== NULL
)
780 p_segment
= text_segment_New( p_ttnode
->psz_text
);
783 bool b_preserve_space
= false;
784 ttml_style_t
*s
= InheritTTMLStyles( p_ctx
, p_ttnode
->p_parent
);
788 ttml_style_Merge( p_set_styles
, s
);
790 p_segment
->style
= s
->font_style
;
791 s
->font_style
= NULL
;
793 b_preserve_space
= s
->b_preserve_space
;
794 if( s
->b_direction_set
)
795 BIDIConvert( p_segment
, s
->i_direction
);
797 if( s
->display
== TTML_DISPLAY_NONE
)
799 /* Must not display, but still occupies space */
800 p_segment
->style
->i_features
&= ~(STYLE_BACKGROUND
|STYLE_OUTLINE
|STYLE_STRIKEOUT
|STYLE_SHADOW
);
801 p_segment
->style
->i_font_alpha
= STYLE_ALPHA_TRANSPARENT
;
802 p_segment
->style
->i_features
|= STYLE_HAS_FONT_ALPHA
;
805 /* we don't have paragraph, so no per text line alignment.
806 * Text style brings horizontal textAlign to region.
807 * Region itself is styled with vertical displayAlign */
808 if( s
->b_text_align_set
)
810 p_region
->updt
.inner_align
&= ~(SUBPICTURE_ALIGN_LEFT
|SUBPICTURE_ALIGN_RIGHT
);
811 p_region
->updt
.inner_align
|= s
->i_text_align
;
814 ttml_style_Delete( s
);
817 if( !b_preserve_space
)
818 StripSpacing( p_segment
);
821 *p_region
->pp_last_segment
= p_segment
;
822 p_region
->pp_last_segment
= &p_segment
->p_next
;
825 static void ConvertNodesToRegionContent( ttml_context_t
*p_ctx
, const tt_node_t
*p_node
,
826 ttml_region_t
*p_region
,
827 const ttml_style_t
*p_upper_set_styles
,
828 tt_time_t playbacktime
)
830 if( tt_time_Valid( &playbacktime
) &&
831 !tt_timings_Contains( &p_node
->timings
, &playbacktime
) )
834 const char *psz_regionid
= (const char *)
835 vlc_dictionary_value_for_key( &p_node
->attr_dict
, "region" );
837 /* Region isn't set or is changing */
838 if( psz_regionid
|| p_region
== NULL
)
839 p_region
= GetTTMLRegion( p_ctx
, psz_regionid
);
841 /* awkward paragraph handling */
842 if( !tt_node_NameCompare( p_node
->psz_node_name
, "p" ) &&
843 p_region
->updt
.p_segments
)
845 AppendLineBreakToRegion( p_region
);
848 /* Styles from <set> element */
849 ttml_style_t
*p_set_styles
= (p_upper_set_styles
)
850 ? ttml_style_Duplicate( p_upper_set_styles
)
853 for( const tt_basenode_t
*p_child
= p_node
->p_child
;
854 p_child
; p_child
= p_child
->p_next
)
856 if( p_child
->i_type
== TT_NODE_TYPE_TEXT
)
858 AppendTextToRegion( p_ctx
, (const tt_textnode_t
*) p_child
,
859 p_set_styles
, p_region
);
861 else if( !tt_node_NameCompare( ((const tt_node_t
*)p_child
)->psz_node_name
, "set" ) )
863 const tt_node_t
*p_set
= (const tt_node_t
*)p_child
;
864 if( !tt_time_Valid( &playbacktime
) ||
865 tt_timings_Contains( &p_set
->timings
, &playbacktime
) )
867 if( p_set_styles
!= NULL
|| (p_set_styles
= ttml_style_New()) )
869 /* Merge with or create a local set of styles to apply to following childs */
870 DictToTTMLStyle( p_ctx
, &p_set
->attr_dict
, p_set_styles
);
874 else if( !tt_node_NameCompare( ((const tt_node_t
*)p_child
)->psz_node_name
, "br" ) )
876 AppendLineBreakToRegion( p_region
);
880 ConvertNodesToRegionContent( p_ctx
, (const tt_node_t
*) p_child
,
881 p_region
, p_set_styles
, playbacktime
);
886 ttml_style_Delete( p_set_styles
);
889 static tt_node_t
*ParseTTML( decoder_t
*p_dec
, const uint8_t *p_buffer
, size_t i_buffer
)
892 xml_reader_t
* p_xml_reader
;
894 p_sub
= vlc_stream_MemoryNew( p_dec
, (uint8_t*) p_buffer
, i_buffer
, true );
895 if( unlikely( p_sub
== NULL
) )
898 p_xml_reader
= xml_ReaderCreate( p_dec
, p_sub
);
899 if( unlikely( p_xml_reader
== NULL
) )
901 vlc_stream_Delete( p_sub
);
905 tt_node_t
*p_rootnode
= NULL
;
906 if( ParseTTMLChunk( p_xml_reader
, &p_rootnode
) != VLC_SUCCESS
)
909 tt_node_RecursiveDelete( p_rootnode
);
913 xml_ReaderDelete( p_xml_reader
);
914 vlc_stream_Delete( p_sub
);
919 static void InitTTMLContext( tt_node_t
*p_rootnode
, ttml_context_t
*p_ctx
)
921 p_ctx
->p_rootnode
= p_rootnode
;
922 /* set defaults required for size/cells computation */
923 p_ctx
->root_extent_h
.i_value
= 100;
924 p_ctx
->root_extent_h
.unit
= TTML_UNIT_PERCENT
;
925 p_ctx
->root_extent_v
.i_value
= 100;
926 p_ctx
->root_extent_v
.unit
= TTML_UNIT_PERCENT
;
927 p_ctx
->i_cell_resolution_v
= TTML_DEFAULT_CELL_RESOLUTION_V
;
928 p_ctx
->i_cell_resolution_h
= TTML_DEFAULT_CELL_RESOLUTION_H
;
929 /* and override them */
930 const char *value
= vlc_dictionary_value_for_key( &p_rootnode
->attr_dict
,
932 if( value
!= kVLCDictionaryNotFound
)
934 ReadTTMLExtent( value
, &p_ctx
->root_extent_h
,
935 &p_ctx
->root_extent_v
);
937 value
= vlc_dictionary_value_for_key( &p_rootnode
->attr_dict
,
938 "ttp:cellResolution" );
939 if( value
!= kVLCDictionaryNotFound
)
942 if( sscanf( value
, "%u %u", &w
, &h
) == 2 && w
&& h
)
944 p_ctx
->i_cell_resolution_h
= w
;
945 p_ctx
->i_cell_resolution_v
= h
;
950 static ttml_region_t
*GenerateRegions( tt_node_t
*p_rootnode
, tt_time_t playbacktime
)
952 ttml_region_t
* p_regions
= NULL
;
953 ttml_region_t
** pp_region_last
= &p_regions
;
955 if( !tt_node_NameCompare( p_rootnode
->psz_node_name
, "tt" ) )
957 const tt_node_t
*p_bodynode
= FindNode( p_rootnode
, "body", 1, NULL
);
960 ttml_context_t context
;
961 InitTTMLContext( p_rootnode
, &context
);
962 context
.p_rootnode
= p_rootnode
;
964 vlc_dictionary_init( &context
.regions
, 1 );
965 ConvertNodesToRegionContent( &context
, p_bodynode
, NULL
, NULL
, playbacktime
);
967 for( int i
= 0; i
< context
.regions
.i_size
; ++i
)
969 for ( const vlc_dictionary_entry_t
* p_entry
= context
.regions
.p_entries
[i
];
970 p_entry
!= NULL
; p_entry
= p_entry
->p_next
)
972 *pp_region_last
= (ttml_region_t
*) p_entry
->p_value
;
973 pp_region_last
= (ttml_region_t
**) &(*pp_region_last
)->updt
.p_next
;
977 vlc_dictionary_clear( &context
.regions
, NULL
, NULL
);
980 else if ( !tt_node_NameCompare( p_rootnode
->psz_node_name
, "div" ) ||
981 !tt_node_NameCompare( p_rootnode
->psz_node_name
, "p" ) )
989 static int ParseBlock( decoder_t
*p_dec
, const block_t
*p_block
)
991 tt_time_t
*p_timings_array
= NULL
;
992 size_t i_timings_count
= 0;
994 /* We Only support absolute timings */
995 tt_timings_t temporal_extent
;
996 temporal_extent
.i_type
= TT_TIMINGS_PARALLEL
;
997 tt_time_Init( &temporal_extent
.begin
);
998 tt_time_Init( &temporal_extent
.end
);
999 tt_time_Init( &temporal_extent
.dur
);
1000 temporal_extent
.begin
.base
= 0;
1002 if( p_block
->i_flags
& BLOCK_FLAG_CORRUPTED
)
1003 return VLCDEC_SUCCESS
;
1005 /* We cannot display a subpicture with no date */
1006 if( p_block
->i_pts
<= VLC_TS_INVALID
)
1008 msg_Warn( p_dec
, "subtitle without a date" );
1009 return VLCDEC_SUCCESS
;
1012 tt_node_t
*p_rootnode
= ParseTTML( p_dec
, p_block
->p_buffer
, p_block
->i_buffer
);
1014 return VLCDEC_SUCCESS
;
1016 tt_timings_Resolve( (tt_basenode_t
*) p_rootnode
, &temporal_extent
,
1017 &p_timings_array
, &i_timings_count
);
1020 for( size_t i
=0; i
<i_timings_count
; i
++ )
1021 printf("%ld ", tt_time_Convert( &p_timings_array
[i
] ) );
1025 for( size_t i
=0; i
+1 < i_timings_count
; i
++ )
1027 /* We Only support absolute timings (2) */
1028 if( tt_time_Convert( &p_timings_array
[i
] ) + VLC_TS_0
< p_block
->i_dts
)
1031 if( tt_time_Convert( &p_timings_array
[i
] ) + VLC_TS_0
> p_block
->i_dts
+ p_block
->i_length
)
1034 subpicture_t
*p_spu
= NULL
;
1035 ttml_region_t
*p_regions
= GenerateRegions( p_rootnode
, p_timings_array
[i
] );
1036 if( p_regions
&& ( p_spu
= decoder_NewSubpictureText( p_dec
) ) )
1038 p_spu
->i_start
= VLC_TS_0
+ tt_time_Convert( &p_timings_array
[i
] );
1039 p_spu
->i_stop
= VLC_TS_0
+ tt_time_Convert( &p_timings_array
[i
+1] ) - 1;
1040 p_spu
->b_ephemer
= true;
1041 p_spu
->b_absolute
= true;
1043 subpicture_updater_sys_t
*p_spu_sys
= p_spu
->updater
.p_sys
;
1044 subpicture_updater_sys_region_t
*p_updtregion
= NULL
;
1046 /* Create region update info from each ttml region */
1047 for( ttml_region_t
*p_region
= p_regions
;
1048 p_region
; p_region
= (ttml_region_t
*) p_region
->updt
.p_next
)
1050 if( p_updtregion
== NULL
)
1052 p_updtregion
= &p_spu_sys
->region
;
1056 p_updtregion
= SubpictureUpdaterSysRegionNew();
1057 if( p_updtregion
== NULL
)
1059 SubpictureUpdaterSysRegionAdd( &p_spu_sys
->region
, p_updtregion
);
1062 /* broken legacy align var (can't handle center...) */
1063 if( p_dec
->p_sys
->i_align
& SUBPICTURE_ALIGN_MASK
)
1065 p_spu_sys
->region
.align
= p_dec
->p_sys
->i_align
& (SUBPICTURE_ALIGN_BOTTOM
|SUBPICTURE_ALIGN_TOP
);
1066 p_spu_sys
->region
.inner_align
= p_dec
->p_sys
->i_align
& (SUBPICTURE_ALIGN_LEFT
|SUBPICTURE_ALIGN_RIGHT
);
1068 p_spu_sys
->margin_ratio
= 0.0;
1070 /* copy and take ownership of pointeds */
1071 *p_updtregion
= p_region
->updt
;
1072 p_updtregion
->p_next
= NULL
;
1073 p_region
->updt
.p_region_style
= NULL
;
1074 p_region
->updt
.p_segments
= NULL
;
1082 ttml_region_t
*p_nextregion
= (ttml_region_t
*) p_regions
->updt
.p_next
;
1083 ttml_region_Delete( p_regions
);
1084 p_regions
= p_nextregion
;
1088 decoder_QueueSub( p_dec
, p_spu
);
1091 tt_node_RecursiveDelete( p_rootnode
);
1093 free( p_timings_array
);
1095 return VLCDEC_SUCCESS
;
1100 /****************************************************************************
1101 * DecodeBlock: the whole thing
1102 ****************************************************************************/
1103 static int DecodeBlock( decoder_t
*p_dec
, block_t
*p_block
)
1105 if( p_block
== NULL
) /* No Drain */
1106 return VLCDEC_SUCCESS
;
1108 int ret
= ParseBlock( p_dec
, p_block
);
1110 if( p_block
->i_buffer
)
1112 p_block
->p_buffer
[p_block
->i_buffer
- 1] = 0;
1113 msg_Dbg(p_dec
,"time %ld %s", p_block
->i_dts
, p_block
->p_buffer
);
1116 block_Release( p_block
);
1120 /*****************************************************************************
1121 * tt_OpenDecoder: probe the decoder and return score
1122 *****************************************************************************/
1123 int tt_OpenDecoder( vlc_object_t
*p_this
)
1125 decoder_t
*p_dec
= (decoder_t
*)p_this
;
1126 decoder_sys_t
*p_sys
;
1128 if( p_dec
->fmt_in
.i_codec
!= VLC_CODEC_TTML
)
1129 return VLC_EGENERIC
;
1131 /* Allocate the memory needed to store the decoder's structure */
1132 p_dec
->p_sys
= p_sys
= calloc( 1, sizeof( *p_sys
) );
1133 if( unlikely( p_sys
== NULL
) )
1136 p_dec
->pf_decode
= DecodeBlock
;
1137 p_sys
->i_align
= var_InheritInteger( p_dec
, "ttml-align" );
1142 /*****************************************************************************
1143 * tt_CloseDecoder: clean up the decoder
1144 *****************************************************************************/
1145 void tt_CloseDecoder( vlc_object_t
*p_this
)
1147 decoder_t
*p_dec
= (decoder_t
*)p_this
;
1148 decoder_sys_t
*p_sys
= p_dec
->p_sys
;