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 *****************************************************************************/
48 text_style_t* font_style;
49 unsigned i_cell_height;
53 bool b_preserve_space;
56 TTML_DISPLAY_UNKNOWN = 0,
64 vlc_dictionary_t regions;
65 tt_node_t * p_rootnode; /* for now. FIXME: split header */
70 subpicture_updater_sys_region_t updt;
71 text_segment_t **pp_last_segment;
83 UNICODE_BIDI_EMBEDDED = 2,
84 UNICODE_BIDI_OVERRIDE = 4,
88 * TTML Parsing and inheritance order:
89 * Each time a text node is found and belongs to out time interval,
90 * we backward merge attributes dictionnary up to root.
91 * Then we convert attributes, merging with style by id or region
92 * style, and sets from parent node.
94 static tt_node_t *ParseTTML( decoder_t *, const uint8_t *, size_t );
96 static void ttml_style_Delete( ttml_style_t* p_ttml_style )
98 text_style_Delete( p_ttml_style->font_style );
102 static ttml_style_t * ttml_style_New( )
104 ttml_style_t *p_ttml_style = calloc( 1, sizeof( ttml_style_t ) );
105 if( unlikely( !p_ttml_style ) )
108 p_ttml_style->i_cell_height = 15;
109 p_ttml_style->font_style = text_style_Create( STYLE_NO_DEFAULTS );
110 if( unlikely( !p_ttml_style->font_style ) )
112 free( p_ttml_style );
118 static void ttml_region_Delete( ttml_region_t *p_region )
120 SubpictureUpdaterSysRegionClean( &p_region->updt );
124 static ttml_style_t * ttml_style_Duplicate( const ttml_style_t *p_src )
126 ttml_style_t *p_dup = ttml_style_New( );
130 p_dup->font_style = text_style_Duplicate( p_src->font_style );
135 static void ttml_style_Merge( const ttml_style_t *p_src, ttml_style_t *p_dst )
139 if( p_src->font_style )
141 if( p_dst->font_style )
142 text_style_Merge( p_dst->font_style, p_src->font_style, true );
144 p_dst->font_style = text_style_Duplicate( p_src->font_style );
147 if( p_src->b_direction_set )
149 p_dst->b_direction_set = true;
150 p_dst->i_direction = p_src->i_direction;
153 if( p_src->display != TTML_DISPLAY_UNKNOWN )
154 p_dst->display = p_src->display;
158 static ttml_region_t *ttml_region_New( )
160 ttml_region_t *p_ttml_region = calloc( 1, sizeof( ttml_region_t ) );
161 if( unlikely( !p_ttml_region ) )
164 SubpictureUpdaterSysRegionInit( &p_ttml_region->updt );
165 p_ttml_region->pp_last_segment = &p_ttml_region->updt.p_segments;
166 /* Align to bottom by default. !Warn: center align is obtained with NO flags */
167 p_ttml_region->updt.align = SUBPICTURE_ALIGN_BOTTOM;
169 return p_ttml_region;
172 static tt_node_t * FindNode( tt_node_t *p_node, const char *psz_nodename,
173 size_t i_maxdepth, const char *psz_id )
175 if( !tt_node_NameCompare( p_node->psz_node_name, psz_nodename ) )
179 char *psz = vlc_dictionary_value_for_key( &p_node->attr_dict, "xml:id" );
180 if( psz && !strcmp( psz, psz_id ) )
186 if( i_maxdepth == 0 )
189 for( tt_basenode_t *p_child = p_node->p_child;
190 p_child; p_child = p_child->p_next )
192 if( p_child->i_type == TT_NODE_TYPE_TEXT )
195 p_node = FindNode( (tt_node_t *) p_child, psz_nodename, i_maxdepth - 1, psz_id );
203 static void FillTextStyle( const char *psz_attr, const char *psz_val,
204 const ttml_style_t *p_ttml_style, text_style_t *p_text_style )
206 if( !strcasecmp ( "tts:fontFamily", psz_attr ) )
208 free( p_text_style->psz_fontname );
209 p_text_style->psz_fontname = strdup( psz_val );
211 else if( !strcasecmp( "tts:opacity", psz_attr ) )
213 p_text_style->i_background_alpha = atoi( psz_val );
214 p_text_style->i_font_alpha = atoi( psz_val );
215 p_text_style->i_features |= STYLE_HAS_BACKGROUND_ALPHA | STYLE_HAS_FONT_ALPHA;
217 else if( !strcasecmp( "tts:fontSize", psz_attr ) )
219 char* psz_end = NULL;
220 float size = us_strtof( psz_val, &psz_end );
223 if( *psz_end == 'c' )
224 p_text_style->f_font_relsize = 100.0 * size / p_ttml_style->i_cell_height;
225 else if( *psz_end == '%' )
226 p_text_style->f_font_relsize = size / p_ttml_style->i_cell_height;
228 p_text_style->i_font_size = (int)( size + 0.5 );
231 else if( !strcasecmp( "tts:color", psz_attr ) )
233 unsigned int i_color = vlc_html_color( psz_val, NULL );
234 p_text_style->i_font_color = (i_color & 0xffffff);
235 p_text_style->i_font_alpha = (i_color & 0xFF000000) >> 24;
236 p_text_style->i_features |= STYLE_HAS_FONT_COLOR | STYLE_HAS_FONT_ALPHA;
238 else if( !strcasecmp( "tts:backgroundColor", psz_attr ) )
240 unsigned int i_color = vlc_html_color( psz_val, NULL );
241 p_text_style->i_background_color = i_color & 0xFFFFFF;
242 p_text_style->i_background_alpha = (i_color & 0xFF000000) >> 24;
243 p_text_style->i_features |= STYLE_HAS_BACKGROUND_COLOR
244 | STYLE_HAS_BACKGROUND_ALPHA;
245 p_text_style->i_style_flags |= STYLE_BACKGROUND;
247 else if( !strcasecmp( "tts:fontStyle", psz_attr ) )
249 if( !strcasecmp ( "italic", psz_val ) || !strcasecmp ( "oblique", psz_val ) )
250 p_text_style->i_style_flags |= STYLE_ITALIC;
252 p_text_style->i_style_flags &= ~STYLE_ITALIC;
253 p_text_style->i_features |= STYLE_HAS_FLAGS;
255 else if( !strcasecmp ( "tts:fontWeight", psz_attr ) )
257 if( !strcasecmp ( "bold", psz_val ) )
258 p_text_style->i_style_flags |= STYLE_BOLD;
260 p_text_style->i_style_flags &= ~STYLE_BOLD;
261 p_text_style->i_features |= STYLE_HAS_FLAGS;
263 else if( !strcasecmp ( "tts:textDecoration", psz_attr ) )
265 if( !strcasecmp ( "underline", psz_val ) )
266 p_text_style->i_style_flags |= STYLE_UNDERLINE;
267 else if( !strcasecmp ( "noUnderline", psz_val ) )
268 p_text_style->i_style_flags &= ~STYLE_UNDERLINE;
269 if( !strcasecmp ( "lineThrough", psz_val ) )
270 p_text_style->i_style_flags |= STYLE_STRIKEOUT;
271 else if( !strcasecmp ( "noLineThrough", psz_val ) )
272 p_text_style->i_style_flags &= ~STYLE_STRIKEOUT;
273 p_text_style->i_features |= STYLE_HAS_FLAGS;
275 else if( !strcasecmp( "tts:textOutline", psz_attr ) )
277 char *value = strdup( psz_val );
278 char* psz_saveptr = NULL;
279 char* token = (value) ? strtok_r( value, " ", &psz_saveptr ) : NULL;
280 // <color>? <length> <length>?
284 unsigned int color = vlc_html_color( token, &b_ok );
287 p_text_style->i_outline_color = color & 0xFFFFFF;
288 p_text_style->i_outline_alpha = (color & 0xFF000000) >> 24;
289 token = strtok_r( NULL, " ", &psz_saveptr );
292 char* psz_end = NULL;
293 int i_outline_width = strtol( token, &psz_end, 10 );
294 if( psz_end != token )
296 // Assume unit is pixel, and ignore border radius
297 p_text_style->i_outline_width = i_outline_width;
306 static void FillRegionStyle( const char *psz_attr, const char *psz_val,
307 ttml_region_t *p_region )
309 if( !strcasecmp( "tts:displayAlign", psz_attr ) )
311 if( !strcasecmp ( "top", psz_val ) )
312 p_region->updt.align = SUBPICTURE_ALIGN_TOP;
313 else if( !strcasecmp ( "center", psz_val ) )
314 p_region->updt.align = 0;
316 p_region->updt.align = SUBPICTURE_ALIGN_BOTTOM;
318 else if( !strcasecmp ( "tts:origin", psz_attr ) )
320 const char *psz_token = psz_val;
321 while( isspace( *psz_token ) )
324 const char *psz_separator = strchr( psz_token, ' ' );
325 if( psz_separator == NULL )
327 const char *psz_percent_sign = strchr( psz_token, '%' );
329 p_region->updt.origin.x = atoi( psz_token );
330 if( psz_percent_sign != NULL && psz_percent_sign < psz_separator )
331 p_region->updt.flags |= UPDT_REGION_EXTENT_X_IS_PERCENTILE;
333 while( isspace( *psz_separator ) )
335 psz_token = psz_separator;
336 psz_percent_sign = strchr( psz_token, '%' );
338 p_region->updt.origin.y = atoi( psz_token );
339 if( psz_percent_sign != NULL )
340 p_region->updt.flags |= UPDT_REGION_EXTENT_Y_IS_PERCENTILE;
344 static void FillTTMLStylePrio( const vlc_dictionary_t *p_dict,
345 ttml_style_t *p_ttml_style )
347 void *value = vlc_dictionary_value_for_key( p_dict, "ttp:cellResolution" );
348 if( value != kVLCDictionaryNotFound )
350 const char *psz_val = value;
352 if( sscanf( psz_val, "%u %u", &w, &h) == 2 && w && h )
353 p_ttml_style->i_cell_height = h;
357 static void FillTTMLStyle( const char *psz_attr, const char *psz_val,
358 ttml_style_t *p_ttml_style )
360 if( !strcasecmp( "tts:textAlign", psz_attr ) )
362 if( !strcasecmp ( "left", psz_val ) )
363 p_ttml_style->i_text_align = SUBPICTURE_ALIGN_LEFT;
364 else if( !strcasecmp ( "right", psz_val ) )
365 p_ttml_style->i_text_align = SUBPICTURE_ALIGN_RIGHT;
366 else if( !strcasecmp ( "center", psz_val ) )
367 p_ttml_style->i_text_align = 0;
368 else if( !strcasecmp ( "start", psz_val ) ) /* FIXME: should be BIDI based */
369 p_ttml_style->i_text_align = SUBPICTURE_ALIGN_LEFT;
370 else if( !strcasecmp ( "end", psz_val ) ) /* FIXME: should be BIDI based */
371 p_ttml_style->i_text_align = SUBPICTURE_ALIGN_RIGHT;
373 else if( !strcasecmp( "tts:direction", psz_attr ) )
375 if( !strcasecmp( "rtl", psz_val ) )
377 p_ttml_style->i_direction |= UNICODE_BIDI_RTL;
378 p_ttml_style->b_direction_set = true;
380 else if( !strcasecmp( "ltr", psz_val ) )
382 p_ttml_style->i_direction |= UNICODE_BIDI_LTR;
383 p_ttml_style->b_direction_set = true;
386 else if( !strcasecmp( "tts:unicodeBidi", psz_attr ) )
388 if( !strcasecmp( "bidiOverride", psz_val ) )
389 p_ttml_style->i_direction |= UNICODE_BIDI_OVERRIDE & ~UNICODE_BIDI_EMBEDDED;
390 else if( !strcasecmp( "embed", psz_val ) )
391 p_ttml_style->i_direction |= UNICODE_BIDI_EMBEDDED & ~UNICODE_BIDI_OVERRIDE;
393 else if( !strcasecmp( "tts:writingMode", psz_attr ) )
395 if( !strcasecmp( "rl", psz_val ) || !strcasecmp( "rltb", psz_val ) )
397 p_ttml_style->i_direction = UNICODE_BIDI_RTL | UNICODE_BIDI_OVERRIDE;
398 //p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT;
399 p_ttml_style->b_direction_set = true;
401 else if( !strcasecmp( "lr", psz_val ) || !strcasecmp( "lrtb", psz_val ) )
403 p_ttml_style->i_direction = UNICODE_BIDI_LTR | UNICODE_BIDI_OVERRIDE;
404 //p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_LEFT;
405 p_ttml_style->b_direction_set = true;
408 else if( !strcmp( "tts:display", psz_attr ) )
410 if( !strcmp( "none", psz_val ) )
411 p_ttml_style->display = TTML_DISPLAY_NONE;
413 p_ttml_style->display = TTML_DISPLAY_AUTO;
415 else if( !strcasecmp( "xml:space", psz_attr ) )
417 p_ttml_style->b_preserve_space = !strcmp( "preserve", psz_val );
419 else FillTextStyle( psz_attr, psz_val, p_ttml_style, p_ttml_style->font_style );
422 static void DictionaryMerge( const vlc_dictionary_t *p_src, vlc_dictionary_t *p_dst )
424 for( int i = 0; i < p_src->i_size; ++i )
426 for ( const vlc_dictionary_entry_t* p_entry = p_src->p_entries[i];
427 p_entry != NULL; p_entry = p_entry->p_next )
429 if( ( !strncmp( "tts:", p_entry->psz_key, 4 ) ||
430 !strncmp( "ttp:", p_entry->psz_key, 4 ) ||
431 !strcmp( "xml:space", p_entry->psz_key ) ) &&
432 !vlc_dictionary_has_key( p_dst, p_entry->psz_key ) )
433 vlc_dictionary_insert( p_dst, p_entry->psz_key, p_entry->p_value );
438 static void DictMergeWithStyleID( ttml_context_t *p_ctx, const char *psz_id,
439 vlc_dictionary_t *p_dst )
441 assert(p_ctx->p_rootnode);
442 if( psz_id && p_ctx->p_rootnode )
444 /* Lookup referenced style ID */
445 const tt_node_t *p_node = FindNode( p_ctx->p_rootnode,
446 "style", -1, psz_id );
448 DictionaryMerge( &p_node->attr_dict, p_dst );
452 static void DictMergeWithRegionID( ttml_context_t *p_ctx, const char *psz_id,
453 vlc_dictionary_t *p_dst )
455 assert(p_ctx->p_rootnode);
456 if( psz_id && p_ctx->p_rootnode )
458 const tt_node_t *p_regionnode = FindNode( p_ctx->p_rootnode,
459 "region", -1, psz_id );
463 DictionaryMerge( &p_regionnode->attr_dict, p_dst );
465 const char *psz_styleid = (const char *)
466 vlc_dictionary_value_for_key( &p_regionnode->attr_dict, "style" );
468 DictMergeWithStyleID( p_ctx, psz_styleid, p_dst );
470 for( const tt_basenode_t *p_child = p_regionnode->p_child;
471 p_child; p_child = p_child->p_next )
473 if( unlikely( p_child->i_type == TT_NODE_TYPE_TEXT ) )
476 const tt_node_t *p_node = (const tt_node_t *) p_child;
477 if( !tt_node_NameCompare( p_node->psz_node_name, "style" ) )
479 DictionaryMerge( &p_node->attr_dict, p_dst );
485 static void DictToTTMLStyle( const vlc_dictionary_t *p_dict, ttml_style_t *p_ttml_style )
487 /* Units, defaults, that must be set first to compute styles */
488 FillTTMLStylePrio( p_dict, p_ttml_style );
489 for( int i = 0; i < p_dict->i_size; ++i )
491 for ( vlc_dictionary_entry_t* p_entry = p_dict->p_entries[i];
492 p_entry != NULL; p_entry = p_entry->p_next )
494 FillTTMLStyle( p_entry->psz_key, p_entry->p_value, p_ttml_style );
499 static ttml_style_t * InheritTTMLStyles( ttml_context_t *p_ctx, tt_node_t *p_node )
502 ttml_style_t *p_ttml_style = NULL;
503 vlc_dictionary_t merged;
504 vlc_dictionary_init( &merged, 0 );
506 /* Merge dics backwards without overwriting */
507 for( ; p_node; p_node = p_node->p_parent )
509 DictionaryMerge( &p_node->attr_dict, &merged );
511 const char *psz_styleid = (const char *)
512 vlc_dictionary_value_for_key( &p_node->attr_dict, "style" );
514 DictMergeWithStyleID( p_ctx, psz_styleid, &merged );
516 const char *psz_regionid = (const char *)
517 vlc_dictionary_value_for_key( &p_node->attr_dict, "region" );
519 DictMergeWithRegionID( p_ctx, psz_regionid, &merged );
522 if( !vlc_dictionary_is_empty( &merged ) && (p_ttml_style = ttml_style_New()) )
524 DictToTTMLStyle( &merged, p_ttml_style );
527 vlc_dictionary_clear( &merged, NULL, NULL );
532 static int ParseTTMLChunk( xml_reader_t *p_reader, tt_node_t **pp_rootnode )
534 const char* psz_node_name;
538 int i_type = xml_ReaderNextNode( p_reader, &psz_node_name );
540 if( i_type <= XML_READER_NONE )
548 case XML_READER_STARTELEM:
549 if( tt_node_NameCompare( psz_node_name, "tt" ) ||
550 *pp_rootnode != NULL )
553 *pp_rootnode = tt_node_New( p_reader, NULL, psz_node_name );
555 tt_nodes_Read( p_reader, *pp_rootnode ) != VLC_SUCCESS )
559 case XML_READER_ENDELEM:
561 tt_node_NameCompare( psz_node_name, (*pp_rootnode)->psz_node_name ) )
568 if( *pp_rootnode == NULL )
574 static void BIDIConvert( text_segment_t *p_segment, int i_direction )
577 * For bidirectionnal support, we use different enum
578 * to recognize different cases, en then we add the
579 * corresponding unicode character to the text of
584 const char* psz_uni_start;
585 const char* psz_uni_end;
587 { "\u2066", "\u2069" },
588 { "\u2067", "\u2069" },
589 { "\u202A", "\u202C" },
590 { "\u202B", "\u202C" },
591 { "\u202D", "\u202C" },
592 { "\u202E", "\u202C" },
595 if( unlikely((size_t)i_direction >= ARRAY_SIZE(p_bidi)) )
598 char *psz_text = NULL;
599 if( asprintf( &psz_text, "%s%s%s", p_bidi[i_direction].psz_uni_start,
600 p_segment->psz_text, p_bidi[i_direction].psz_uni_end ) < 0 )
602 free( p_segment->psz_text );
603 p_segment->psz_text = psz_text;
607 static void StripSpacing( text_segment_t *p_segment )
609 /* Newlines must be replaced */
610 char *p = p_segment->psz_text;
611 while( (p = strchr( p, '\n' )) )
615 static ttml_region_t *GetTTMLRegion( ttml_context_t *p_ctx, const char *psz_region_id )
617 ttml_region_t *p_region = ( ttml_region_t * )
618 vlc_dictionary_value_for_key( &p_ctx->regions, psz_region_id ? psz_region_id : "" );
619 if( p_region == NULL )
621 if( psz_region_id && strcmp( psz_region_id, "" ) ) /* not default region */
623 /* Create if missing and exists as node */
624 const tt_node_t *p_node = FindNode( p_ctx->p_rootnode, "region", -1, psz_region_id );
625 if( p_node && (p_region = ttml_region_New()) )
627 /* Fill from its own attributes */
628 for( int i = 0; i < p_node->attr_dict.i_size; ++i )
630 for ( vlc_dictionary_entry_t* p_entry = p_node->attr_dict.p_entries[i];
631 p_entry != NULL; p_entry = p_entry->p_next )
633 FillRegionStyle( p_entry->psz_key, p_entry->p_value, p_region );
637 vlc_dictionary_insert( &p_ctx->regions, psz_region_id, p_region );
639 else if( (p_region = ttml_region_New()) ) /* create default */
641 vlc_dictionary_insert( &p_ctx->regions, "", p_region );
647 static void AppendLineBreakToRegion( ttml_region_t *p_region )
649 text_segment_t *p_segment = text_segment_New( "\n" );
652 *p_region->pp_last_segment = p_segment;
653 p_region->pp_last_segment = &p_segment->p_next;
657 static void AppendTextToRegion( ttml_context_t *p_ctx, const tt_textnode_t *p_ttnode,
658 const ttml_style_t *p_set_styles, ttml_region_t *p_region )
660 text_segment_t *p_segment;
662 if( p_region == NULL )
665 p_segment = text_segment_New( p_ttnode->psz_text );
668 bool b_preserve_space = false;
669 ttml_style_t *s = InheritTTMLStyles( p_ctx, p_ttnode->p_parent );
673 ttml_style_Merge( p_set_styles, s );
675 p_segment->style = s->font_style;
676 s->font_style = NULL;
678 b_preserve_space = s->b_preserve_space;
679 if( s->b_direction_set )
680 BIDIConvert( p_segment, s->i_direction );
682 if( s->display == TTML_DISPLAY_NONE )
684 /* Must not display, but still occupies space */
685 p_segment->style->i_features &= ~(STYLE_BACKGROUND|STYLE_OUTLINE|STYLE_STRIKEOUT|STYLE_SHADOW);
686 p_segment->style->i_font_alpha = STYLE_ALPHA_TRANSPARENT;
687 p_segment->style->i_features |= STYLE_HAS_FONT_ALPHA;
690 ttml_style_Delete( s );
693 if( !b_preserve_space )
694 StripSpacing( p_segment );
697 *p_region->pp_last_segment = p_segment;
698 p_region->pp_last_segment = &p_segment->p_next;
701 static void ConvertNodesToRegionContent( ttml_context_t *p_ctx, const tt_node_t *p_node,
702 ttml_region_t *p_region,
703 const ttml_style_t *p_upper_set_styles,
704 tt_time_t playbacktime )
706 if( tt_time_Valid( &playbacktime ) &&
707 !tt_timings_Contains( &p_node->timings, &playbacktime ) )
710 const char *psz_regionid = (const char *)
711 vlc_dictionary_value_for_key( &p_node->attr_dict, "region" );
713 /* Region isn't set or is changing */
714 if( psz_regionid || p_region == NULL )
715 p_region = GetTTMLRegion( p_ctx, psz_regionid );
717 /* awkward paragraph handling */
718 if( !tt_node_NameCompare( p_node->psz_node_name, "p" ) &&
719 p_region->updt.p_segments )
721 AppendLineBreakToRegion( p_region );
724 /* Styles from <set> element */
725 ttml_style_t *p_set_styles = (p_upper_set_styles)
726 ? ttml_style_Duplicate( p_upper_set_styles )
729 for( const tt_basenode_t *p_child = p_node->p_child;
730 p_child; p_child = p_child->p_next )
732 if( p_child->i_type == TT_NODE_TYPE_TEXT )
734 AppendTextToRegion( p_ctx, (const tt_textnode_t *) p_child,
735 p_set_styles, p_region );
737 else if( !tt_node_NameCompare( ((const tt_node_t *)p_child)->psz_node_name, "set" ) )
739 const tt_node_t *p_set = (const tt_node_t *)p_child;
740 if( !tt_time_Valid( &playbacktime ) ||
741 tt_timings_Contains( &p_set->timings, &playbacktime ) )
743 if( p_set_styles != NULL || (p_set_styles = ttml_style_New()) )
745 /* Merge with or create a local set of styles to apply to following childs */
746 DictToTTMLStyle( &p_set->attr_dict, p_set_styles );
750 else if( !tt_node_NameCompare( ((const tt_node_t *)p_child)->psz_node_name, "br" ) )
752 AppendLineBreakToRegion( p_region );
756 ConvertNodesToRegionContent( p_ctx, (const tt_node_t *) p_child,
757 p_region, p_set_styles, playbacktime );
762 ttml_style_Delete( p_set_styles );
765 static tt_node_t *ParseTTML( decoder_t *p_dec, const uint8_t *p_buffer, size_t i_buffer )
768 xml_reader_t* p_xml_reader;
770 p_sub = vlc_stream_MemoryNew( p_dec, (uint8_t*) p_buffer, i_buffer, true );
771 if( unlikely( p_sub == NULL ) )
774 p_xml_reader = xml_ReaderCreate( p_dec, p_sub );
775 if( unlikely( p_xml_reader == NULL ) )
777 vlc_stream_Delete( p_sub );
781 tt_node_t *p_rootnode = NULL;
782 if( ParseTTMLChunk( p_xml_reader, &p_rootnode ) != VLC_SUCCESS )
785 tt_node_RecursiveDelete( p_rootnode );
789 xml_ReaderDelete( p_xml_reader );
790 vlc_stream_Delete( p_sub );
795 static ttml_region_t *GenerateRegions( tt_node_t *p_rootnode, tt_time_t playbacktime )
797 ttml_region_t* p_regions = NULL;
798 ttml_region_t** pp_region_last = &p_regions;
800 if( !tt_node_NameCompare( p_rootnode->psz_node_name, "tt" ) )
802 const tt_node_t *p_bodynode = FindNode( p_rootnode, "body", 1, NULL );
805 ttml_context_t context;
806 context.p_rootnode = p_rootnode;
807 vlc_dictionary_init( &context.regions, 1 );
808 ConvertNodesToRegionContent( &context, p_bodynode, NULL, NULL, playbacktime );
810 for( int i = 0; i < context.regions.i_size; ++i )
812 for ( const vlc_dictionary_entry_t* p_entry = context.regions.p_entries[i];
813 p_entry != NULL; p_entry = p_entry->p_next )
815 *pp_region_last = (ttml_region_t *) p_entry->p_value;
816 pp_region_last = (ttml_region_t **) &(*pp_region_last)->updt.p_next;
820 vlc_dictionary_clear( &context.regions, NULL, NULL );
823 else if ( !tt_node_NameCompare( p_rootnode->psz_node_name, "div" ) ||
824 !tt_node_NameCompare( p_rootnode->psz_node_name, "p" ) )
832 static int ParseBlock( decoder_t *p_dec, const block_t *p_block )
834 tt_time_t *p_timings_array = NULL;
835 size_t i_timings_count = 0;
837 /* We Only support absolute timings */
838 tt_timings_t temporal_extent;
839 temporal_extent.i_type = TT_TIMINGS_PARALLEL;
840 tt_time_Init( &temporal_extent.begin );
841 tt_time_Init( &temporal_extent.end );
842 tt_time_Init( &temporal_extent.dur );
843 temporal_extent.begin.base = 0;
845 if( p_block->i_flags & BLOCK_FLAG_CORRUPTED )
846 return VLCDEC_SUCCESS;
848 /* We cannot display a subpicture with no date */
849 if( p_block->i_pts <= VLC_TS_INVALID )
851 msg_Warn( p_dec, "subtitle without a date" );
852 return VLCDEC_SUCCESS;
855 tt_node_t *p_rootnode = ParseTTML( p_dec, p_block->p_buffer, p_block->i_buffer );
857 return VLCDEC_SUCCESS;
859 tt_timings_Resolve( (tt_basenode_t *) p_rootnode, &temporal_extent,
860 &p_timings_array, &i_timings_count );
863 for( size_t i=0; i<i_timings_count; i++ )
864 printf("%ld ", tt_time_Convert( &p_timings_array[i] ) );
868 for( size_t i=0; i+1 < i_timings_count; i++ )
870 /* We Only support absolute timings (2) */
871 if( tt_time_Convert( &p_timings_array[i] ) + VLC_TS_0 < p_block->i_dts )
874 if( tt_time_Convert( &p_timings_array[i] ) + VLC_TS_0 > p_block->i_dts + p_block->i_length )
877 subpicture_t *p_spu = NULL;
878 ttml_region_t *p_regions = GenerateRegions( p_rootnode, p_timings_array[i] );
879 if( p_regions && ( p_spu = decoder_NewSubpictureText( p_dec ) ) )
881 p_spu->i_start = VLC_TS_0 + tt_time_Convert( &p_timings_array[i] );
882 p_spu->i_stop = VLC_TS_0 + tt_time_Convert( &p_timings_array[i+1] ) - 1;
883 p_spu->b_ephemer = true;
884 p_spu->b_absolute = false;
886 subpicture_updater_sys_t *p_spu_sys = p_spu->updater.p_sys;
887 subpicture_updater_sys_region_t *p_updtregion = NULL;
889 /* Create region update info from each ttml region */
890 for( ttml_region_t *p_region = p_regions;
891 p_region; p_region = (ttml_region_t *) p_region->updt.p_next )
893 if( p_updtregion == NULL )
895 p_updtregion = &p_spu_sys->region;
899 p_updtregion = SubpictureUpdaterSysRegionNew();
900 if( p_updtregion == NULL )
902 SubpictureUpdaterSysRegionAdd( &p_spu_sys->region, p_updtregion );
905 /* broken legacy align var (can't handle center...) */
906 if( p_dec->p_sys->i_align & SUBPICTURE_ALIGN_MASK )
908 p_spu_sys->region.align = p_dec->p_sys->i_align & (SUBPICTURE_ALIGN_BOTTOM|SUBPICTURE_ALIGN_TOP);
909 p_spu_sys->region.inner_align = p_dec->p_sys->i_align & (SUBPICTURE_ALIGN_LEFT|SUBPICTURE_ALIGN_RIGHT);
912 /* copy and take ownership of pointeds */
913 *p_updtregion = p_region->updt;
914 p_updtregion->p_next = NULL;
915 p_region->updt.p_region_style = NULL;
916 p_region->updt.p_segments = NULL;
924 ttml_region_t *p_nextregion = (ttml_region_t *) p_regions->updt.p_next;
925 ttml_region_Delete( p_regions );
926 p_regions = p_nextregion;
930 decoder_QueueSub( p_dec, p_spu );
933 tt_node_RecursiveDelete( p_rootnode );
935 free( p_timings_array );
937 return VLCDEC_SUCCESS;
942 /****************************************************************************
943 * DecodeBlock: the whole thing
944 ****************************************************************************/
945 static int DecodeBlock( decoder_t *p_dec, block_t *p_block )
947 if( p_block == NULL ) /* No Drain */
948 return VLCDEC_SUCCESS;
950 int ret = ParseBlock( p_dec, p_block );
952 if( p_block->i_buffer )
954 p_block->p_buffer[p_block->i_buffer - 1] = 0;
955 msg_Dbg(p_dec,"time %ld %s", p_block->i_dts, p_block->p_buffer);
958 block_Release( p_block );
962 /*****************************************************************************
963 * OpenDecoder: probe the decoder and return score
964 *****************************************************************************/
965 int OpenDecoder( vlc_object_t *p_this )
967 decoder_t *p_dec = (decoder_t*)p_this;
968 decoder_sys_t *p_sys;
970 if( p_dec->fmt_in.i_codec != VLC_CODEC_TTML )
973 /* Allocate the memory needed to store the decoder's structure */
974 p_dec->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) );
975 if( unlikely( p_sys == NULL ) )
978 p_dec->pf_decode = DecodeBlock;
979 p_dec->fmt_out.i_cat = SPU_ES;
980 p_sys->i_align = var_InheritInteger( p_dec, "ttml-align" );
985 /*****************************************************************************
986 * CloseDecoder: clean up the decoder
987 *****************************************************************************/
988 void CloseDecoder( vlc_object_t *p_this )
990 decoder_t *p_dec = (decoder_t *)p_this;
991 decoder_sys_t *p_sys = p_dec->p_sys;