From 44879313bab5b7208b12cb4968cf03a6f521e531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Cartegnie?= Date: Thu, 29 Feb 2024 17:56:06 +0100 Subject: [PATCH] codec/demux: ttml: handle non-default namespaces --- modules/codec/ttml/substtml.c | 278 +++++++++++++++++++--------------- modules/codec/ttml/ttml.c | 33 ++-- modules/codec/ttml/ttml.h | 9 +- modules/demux/ttml.c | 21 ++- 4 files changed, 196 insertions(+), 145 deletions(-) diff --git a/modules/codec/ttml/substtml.c b/modules/codec/ttml/substtml.c index 53caeda347..f597a7c405 100644 --- a/modules/codec/ttml/substtml.c +++ b/modules/codec/ttml/substtml.c @@ -124,7 +124,8 @@ enum * Then we convert attributes, merging with style by id or region * style, and sets from parent node. */ -static tt_node_t *ParseTTML( decoder_t *, const uint8_t *, size_t ); +static tt_node_t *ParseTTML( decoder_t *, tt_namespaces_t *, + const uint8_t *, size_t ); static void ttml_style_Delete( ttml_style_t* p_ttml_style ) { @@ -280,16 +281,17 @@ static bool ttml_read_coords( const char *value, ttml_length_t *h, ttml_length_t return false; } -static tt_node_t * FindNode( tt_node_t *p_node, const char *psz_nodename, +static tt_node_t * FindNode( tt_namespaces_t *p_nss, tt_node_t *p_node, + const char *psz_nodename, const char *psz_namespace, size_t i_maxdepth, const char *psz_id ) { - if( !tt_node_NameCompare( p_node->psz_node_name, psz_nodename ) ) + if( tt_node_Match( p_node, psz_nodename, psz_namespace ) ) { if( psz_id != NULL ) { - char *psz = vlc_dictionary_value_for_key( &p_node->attr_dict, "xml:id" ); + const char *psz = tt_node_GetAttribute( p_nss, p_node, "id", TT_NS_XML ); if( !psz ) /* People can't do xml properly */ - psz = vlc_dictionary_value_for_key( &p_node->attr_dict, "id" ); + psz = tt_node_GetAttribute( p_nss, p_node, "id", NULL ); if( psz && !strcmp( psz, psz_id ) ) return p_node; } @@ -305,7 +307,9 @@ static tt_node_t * FindNode( tt_node_t *p_node, const char *psz_nodename, if( p_child->i_type == TT_NODE_TYPE_TEXT ) continue; - p_node = FindNode( (tt_node_t *) p_child, psz_nodename, i_maxdepth - 1, psz_id ); + p_node = FindNode( p_nss, (tt_node_t *) p_child, + psz_nodename, psz_namespace, + i_maxdepth - 1, psz_id ); if( p_node ) return p_node; } @@ -316,25 +320,25 @@ static tt_node_t * FindNode( tt_node_t *p_node, const char *psz_nodename, static void FillTextStyle( const char *psz_attr, const char *psz_val, text_style_t *p_text_style ) { - if( !strcasecmp ( "tts:fontFamily", psz_attr ) ) + if( !strcasecmp ( "fontFamily", psz_attr ) ) { free( p_text_style->psz_fontname ); p_text_style->psz_fontname = strdup( psz_val ); } - else if( !strcasecmp( "tts:opacity", psz_attr ) ) + else if( !strcasecmp( "opacity", psz_attr ) ) { p_text_style->i_background_alpha = atoi( psz_val ); p_text_style->i_font_alpha = atoi( psz_val ); p_text_style->i_features |= STYLE_HAS_BACKGROUND_ALPHA | STYLE_HAS_FONT_ALPHA; } - else if( !strcasecmp( "tts:color", psz_attr ) ) + else if( !strcasecmp( "color", psz_attr ) ) { unsigned int i_color = vlc_html_color( psz_val, NULL ); p_text_style->i_font_color = (i_color & 0xffffff); p_text_style->i_font_alpha = (i_color & 0xFF000000) >> 24; p_text_style->i_features |= STYLE_HAS_FONT_COLOR | STYLE_HAS_FONT_ALPHA; } - else if( !strcasecmp( "tts:backgroundColor", psz_attr ) ) + else if( !strcasecmp( "backgroundColor", psz_attr ) ) { unsigned int i_color = vlc_html_color( psz_val, NULL ); p_text_style->i_background_color = i_color & 0xFFFFFF; @@ -343,7 +347,7 @@ static void FillTextStyle( const char *psz_attr, const char *psz_val, | STYLE_HAS_BACKGROUND_ALPHA; p_text_style->i_style_flags |= STYLE_BACKGROUND; } - else if( !strcasecmp( "tts:fontStyle", psz_attr ) ) + else if( !strcasecmp( "fontStyle", psz_attr ) ) { if( !strcasecmp ( "italic", psz_val ) || !strcasecmp ( "oblique", psz_val ) ) p_text_style->i_style_flags |= STYLE_ITALIC; @@ -351,7 +355,7 @@ static void FillTextStyle( const char *psz_attr, const char *psz_val, p_text_style->i_style_flags &= ~STYLE_ITALIC; p_text_style->i_features |= STYLE_HAS_FLAGS; } - else if( !strcasecmp ( "tts:fontWeight", psz_attr ) ) + else if( !strcasecmp ( "fontWeight", psz_attr ) ) { if( !strcasecmp ( "bold", psz_val ) ) p_text_style->i_style_flags |= STYLE_BOLD; @@ -359,7 +363,7 @@ static void FillTextStyle( const char *psz_attr, const char *psz_val, p_text_style->i_style_flags &= ~STYLE_BOLD; p_text_style->i_features |= STYLE_HAS_FLAGS; } - else if( !strcasecmp ( "tts:textDecoration", psz_attr ) ) + else if( !strcasecmp ( "textDecoration", psz_attr ) ) { if( !strcasecmp ( "underline", psz_val ) ) p_text_style->i_style_flags |= STYLE_UNDERLINE; @@ -371,7 +375,7 @@ static void FillTextStyle( const char *psz_attr, const char *psz_val, p_text_style->i_style_flags &= ~STYLE_STRIKEOUT; p_text_style->i_features |= STYLE_HAS_FLAGS; } - else if( !strcasecmp( "tts:textOutline", psz_attr ) ) + else if( !strcasecmp( "textOutline", psz_attr ) ) { char *value = strdup( psz_val ); char* psz_saveptr = NULL; @@ -433,10 +437,13 @@ static void FillUpdaterCoords( ttml_context_t *p_ctx, ttml_length_t h, ttml_leng } static void FillRegionStyle( ttml_context_t *p_ctx, - const char *psz_attr, const char *psz_val, - ttml_region_t *p_region ) + const char *psz_attr, const char *psz_namespace, + const char *psz_val, ttml_region_t *p_region ) { - if( !strcasecmp( "tts:displayAlign", psz_attr ) ) + if( strcmp( psz_namespace, TT_NS_STYLING ) ) + return; + + if( !strcasecmp( "displayAlign", psz_attr ) ) { p_region->updt.inner_align &= ~(SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_BOTTOM); if( !strcasecmp( "after", psz_val ) ) @@ -445,12 +452,12 @@ static void FillRegionStyle( ttml_context_t *p_ctx, /* "before" */ p_region->updt.inner_align |= SUBPICTURE_ALIGN_TOP; } - else if( !strcasecmp ( "tts:origin", psz_attr ) || - !strcasecmp ( "tts:extent", psz_attr ) ) + else if( !strcasecmp ( "origin", psz_attr ) || + !strcasecmp ( "extent", psz_attr ) ) { ttml_length_t x, y; if( ttml_read_coords( psz_val, &x, &y ) ) - FillUpdaterCoords( p_ctx, x, y, (psz_attr[4] == 'o'), &p_region->updt ); + FillUpdaterCoords( p_ctx, x, y, (psz_attr[0] == 'o'), &p_region->updt ); } } @@ -483,20 +490,30 @@ static void ComputeTTMLStyles( ttml_context_t *p_ctx, const vlc_dictionary_t *p_ p_text_style->i_font_size = len.i_value; } -static void FillTTMLStyle( const char *psz_attr, const char *psz_val, - ttml_style_t *p_ttml_style ) +static void FillTTMLStyle( const char *psz_attr, const char *psz_namespace, + const char *psz_val, ttml_style_t *p_ttml_style ) { - if( !strcasecmp( "tts:extent", psz_attr ) ) + if( !strcmp( psz_namespace, TT_NS_XML ) ) + { + if( !strcasecmp( "space", psz_attr ) ) + p_ttml_style->b_preserve_space = !strcmp( "preserve", psz_val ); + return; + } + + if( strcmp( psz_namespace, TT_NS_STYLING ) ) + return; + + if( !strcasecmp( "extent", psz_attr ) ) { ttml_read_coords( psz_val, &p_ttml_style->extent_h, &p_ttml_style->extent_v ); } - else if( !strcasecmp( "tts:origin", psz_attr ) ) + else if( !strcasecmp( "origin", psz_attr ) ) { ttml_read_coords( psz_val, &p_ttml_style->origin_h, &p_ttml_style->origin_v ); } - else if( !strcasecmp( "tts:textAlign", psz_attr ) ) + else if( !strcasecmp( "textAlign", psz_attr ) ) { p_ttml_style->i_text_align &= ~(SUBPICTURE_ALIGN_LEFT|SUBPICTURE_ALIGN_RIGHT); if( !strcasecmp ( "left", psz_val ) ) @@ -513,13 +530,13 @@ static void FillTTMLStyle( const char *psz_attr, const char *psz_val, printf("**%s %x\n", psz_val, p_ttml_style->i_text_align); #endif } - else if( !strcasecmp( "tts:fontSize", psz_attr ) ) + else if( !strcasecmp( "fontSize", psz_attr ) ) { ttml_length_t len = ttml_read_length( psz_val ); if( len.unit != TTML_UNIT_UNKNOWN && len.i_value > 0.0 ) p_ttml_style->font_size = len; } - else if( !strcasecmp( "tts:direction", psz_attr ) ) + else if( !strcasecmp( "direction", psz_attr ) ) { if( !strcasecmp( "rtl", psz_val ) ) { @@ -532,14 +549,14 @@ static void FillTTMLStyle( const char *psz_attr, const char *psz_val, p_ttml_style->b_direction_set = true; } } - else if( !strcasecmp( "tts:unicodeBidi", psz_attr ) ) + else if( !strcasecmp( "unicodeBidi", psz_attr ) ) { if( !strcasecmp( "bidiOverride", psz_val ) ) p_ttml_style->i_direction |= UNICODE_BIDI_OVERRIDE & ~UNICODE_BIDI_EMBEDDED; else if( !strcasecmp( "embed", psz_val ) ) p_ttml_style->i_direction |= UNICODE_BIDI_EMBEDDED & ~UNICODE_BIDI_OVERRIDE; } - else if( !strcasecmp( "tts:writingMode", psz_attr ) ) + else if( !strcasecmp( "writingMode", psz_attr ) ) { if( !strcasecmp( "rl", psz_val ) || !strcasecmp( "rltb", psz_val ) ) { @@ -554,17 +571,13 @@ static void FillTTMLStyle( const char *psz_attr, const char *psz_val, p_ttml_style->b_direction_set = true; } } - else if( !strcmp( "tts:display", psz_attr ) ) + else if( !strcmp( "display", psz_attr ) ) { if( !strcmp( "none", psz_val ) ) p_ttml_style->display = TTML_DISPLAY_NONE; else p_ttml_style->display = TTML_DISPLAY_AUTO; } - else if( !strcasecmp( "xml:space", psz_attr ) ) - { - p_ttml_style->b_preserve_space = !strcmp( "preserve", psz_val ); - } else FillTextStyle( psz_attr, psz_val, p_ttml_style->font_style ); } @@ -576,27 +589,22 @@ static void DictionaryMerge( const vlc_dictionary_t *p_src, vlc_dictionary_t *p_ for ( const vlc_dictionary_entry_t* p_entry = p_src->p_entries[i]; p_entry != NULL; p_entry = p_entry->p_next ) { - if( !strncmp( "tts:", p_entry->psz_key, 4 ) || - !strncmp( "ttp:", p_entry->psz_key, 4 ) || - !strcmp( "xml:space", p_entry->psz_key ) ) + if( vlc_dictionary_has_key( p_dst, p_entry->psz_key ) ) { - if( vlc_dictionary_has_key( p_dst, p_entry->psz_key ) ) + if( b_override ) { - if( b_override ) - { - vlc_dictionary_remove_value_for_key( p_dst, p_entry->psz_key, NULL, NULL ); - vlc_dictionary_insert( p_dst, p_entry->psz_key, p_entry->p_value ); - } - } - else + vlc_dictionary_remove_value_for_key( p_dst, p_entry->psz_key, NULL, NULL ); vlc_dictionary_insert( p_dst, p_entry->psz_key, p_entry->p_value ); + } } + else + vlc_dictionary_insert( p_dst, p_entry->psz_key, p_entry->p_value ); } } } -static void DictMergeWithStyleID( ttml_context_t *p_ctx, const char *psz_styles, - vlc_dictionary_t *p_dst ) +static void DictMergeWithStyleID( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, + const char *psz_styles, vlc_dictionary_t *p_dst ) { assert(p_ctx->p_rootnode); char *psz_dup; @@ -612,8 +620,10 @@ static void DictMergeWithStyleID( ttml_context_t *p_ctx, const char *psz_styles, while( psz_id ) { /* Lookup referenced style ID */ - const tt_node_t *p_node = FindNode( p_ctx->p_rootnode, - "style", -1, psz_id ); + const tt_node_t *p_node = FindNode( p_nss, + p_ctx->p_rootnode, + "style", TT_NS, + -1, psz_id ); if( p_node ) DictionaryMerge( &p_node->attr_dict, &tempdict, true ); @@ -628,23 +638,25 @@ static void DictMergeWithStyleID( ttml_context_t *p_ctx, const char *psz_styles, } } -static void DictMergeWithRegionID( ttml_context_t *p_ctx, const char *psz_id, - vlc_dictionary_t *p_dst ) +static void DictMergeWithRegionID( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, + const char *psz_id, vlc_dictionary_t *p_dst ) { assert(p_ctx->p_rootnode); if( psz_id && p_ctx->p_rootnode ) { - const tt_node_t *p_regionnode = FindNode( p_ctx->p_rootnode, - "region", -1, psz_id ); + const tt_node_t *p_regionnode = FindNode( p_nss, + p_ctx->p_rootnode, + "region", TT_NS, + -1, psz_id ); if( !p_regionnode ) return; DictionaryMerge( &p_regionnode->attr_dict, p_dst, false ); - const char *psz_styleid = (const char *) - vlc_dictionary_value_for_key( &p_regionnode->attr_dict, "style" ); + const char *psz_styleid = + tt_node_GetAttribute( p_nss, p_regionnode, "style", NULL ); if( psz_styleid ) - DictMergeWithStyleID( p_ctx, psz_styleid, p_dst ); + DictMergeWithStyleID( p_ctx, p_nss, psz_styleid, p_dst ); for( const tt_basenode_t *p_child = p_regionnode->p_child; p_child; p_child = p_child->p_next ) @@ -653,7 +665,7 @@ static void DictMergeWithRegionID( ttml_context_t *p_ctx, const char *psz_id, continue; const tt_node_t *p_node = (const tt_node_t *) p_child; - if( !tt_node_NameCompare( p_node->psz_node_name, "style" ) ) + if( tt_node_Match( p_node, "style", TT_NS ) ) { DictionaryMerge( &p_node->attr_dict, p_dst, false ); } @@ -661,7 +673,8 @@ static void DictMergeWithRegionID( ttml_context_t *p_ctx, const char *psz_id, } } -static void DictToTTMLStyle( ttml_context_t *p_ctx, const vlc_dictionary_t *p_dict, +static void DictToTTMLStyle( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, + const vlc_dictionary_t *p_dict, ttml_style_t *p_ttml_style ) { for( int i = 0; i < p_dict->i_size; ++i ) @@ -669,13 +682,18 @@ static void DictToTTMLStyle( ttml_context_t *p_ctx, const vlc_dictionary_t *p_di for ( vlc_dictionary_entry_t* p_entry = p_dict->p_entries[i]; p_entry != NULL; p_entry = p_entry->p_next ) { - FillTTMLStyle( p_entry->psz_key, p_entry->p_value, p_ttml_style ); + const char *psz_namespace = tt_namespaces_GetURI( p_nss, p_entry->psz_key ); + if( !psz_namespace ) + continue; + const char *psz_name = tt_LocalName( p_entry->psz_key ); + FillTTMLStyle( psz_name, psz_namespace, p_entry->p_value, p_ttml_style ); } } ComputeTTMLStyles( p_ctx, p_dict, p_ttml_style ); } -static ttml_style_t * InheritTTMLStyles( ttml_context_t *p_ctx, tt_node_t *p_node ) +static ttml_style_t * InheritTTMLStyles( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, + tt_node_t *p_node ) { assert( p_node ); ttml_style_t *p_ttml_style = NULL; @@ -687,20 +705,18 @@ static ttml_style_t * InheritTTMLStyles( ttml_context_t *p_ctx, tt_node_t *p_nod { DictionaryMerge( &p_node->attr_dict, &merged, false ); - const char *psz_styleid = (const char *) - vlc_dictionary_value_for_key( &p_node->attr_dict, "style" ); + const char *psz_styleid = tt_node_GetAttribute( p_nss, p_node, "style", NULL ); if( psz_styleid ) - DictMergeWithStyleID( p_ctx, psz_styleid, &merged ); + DictMergeWithStyleID( p_ctx, p_nss, psz_styleid, &merged ); - const char *psz_regionid = (const char *) - vlc_dictionary_value_for_key( &p_node->attr_dict, "region" ); + const char *psz_regionid = tt_node_GetAttribute( p_nss, p_node, "region", NULL ); if( psz_regionid ) - DictMergeWithRegionID( p_ctx, psz_regionid, &merged ); + DictMergeWithRegionID( p_ctx, p_nss, psz_regionid, &merged ); } if( !vlc_dictionary_is_empty( &merged ) && (p_ttml_style = ttml_style_New()) ) { - DictToTTMLStyle( p_ctx, &merged, p_ttml_style ); + DictToTTMLStyle( p_ctx, p_nss, &merged, p_ttml_style ); } vlc_dictionary_clear( &merged, NULL, NULL ); @@ -708,14 +724,15 @@ static ttml_style_t * InheritTTMLStyles( ttml_context_t *p_ctx, tt_node_t *p_nod return p_ttml_style; } -static int ParseTTMLChunk( xml_reader_t *p_reader, tt_node_t **pp_rootnode ) +static int ParseTTMLChunk( xml_reader_t *p_reader, tt_namespaces_t *p_nss, + tt_node_t **pp_rootnode ) { const char *psz_node_name, *psz_node_namespace; do { int i_type = xml_ReaderNextNodeNS( p_reader, &psz_node_name, &psz_node_namespace ); - fprintf(stderr, "Parse %s %s \n", psz_node_name, psz_node_namespace); + if( i_type <= XML_READER_NONE ) break; @@ -725,20 +742,20 @@ static int ParseTTMLChunk( xml_reader_t *p_reader, tt_node_t **pp_rootnode ) break; case XML_READER_STARTELEM: - if( tt_node_NameCompare( psz_node_name, "tt" ) || + if( strcmp( psz_node_namespace, TT_NS ) || + strcmp( tt_LocalName( psz_node_name ), "tt" ) || *pp_rootnode != NULL ) return VLC_EGENERIC; - - *pp_rootnode = tt_node_NewRead( p_reader, NULL, psz_node_name, - psz_node_namespace ); + *pp_rootnode = tt_node_NewRead( p_reader, p_nss, NULL, + psz_node_name, psz_node_namespace ); if( !*pp_rootnode || - tt_nodes_Read( p_reader, *pp_rootnode ) != VLC_SUCCESS ) + tt_nodes_Read( p_reader, p_nss, *pp_rootnode ) != VLC_SUCCESS ) return VLC_EGENERIC; break; case XML_READER_ENDELEM: if( !*pp_rootnode || - tt_node_NameCompare( psz_node_name, (*pp_rootnode)->psz_node_name ) ) + strcmp( psz_node_name, (*pp_rootnode)->psz_node_name ) ) return VLC_EGENERIC; break; } @@ -792,7 +809,8 @@ static void StripSpacing( text_segment_t *p_segment ) *p = ' '; } -static ttml_region_t *GetTTMLRegion( ttml_context_t *p_ctx, const char *psz_region_id ) +static ttml_region_t *GetTTMLRegion( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, + const char *psz_region_id ) { ttml_region_t *p_region = ( ttml_region_t * ) vlc_dictionary_value_for_key( &p_ctx->regions, psz_region_id ? psz_region_id : "" ); @@ -805,7 +823,7 @@ static ttml_region_t *GetTTMLRegion( ttml_context_t *p_ctx, const char *psz_regi vlc_dictionary_t merged; vlc_dictionary_init( &merged, 0 ); /* Get all attributes, including region > style */ - DictMergeWithRegionID( p_ctx, psz_region_id, &merged ); + DictMergeWithRegionID( p_ctx, p_nss, psz_region_id, &merged ); if( (p_region = ttml_region_New( false )) ) { /* Fill from its own attributes */ @@ -814,8 +832,12 @@ static ttml_region_t *GetTTMLRegion( ttml_context_t *p_ctx, const char *psz_regi for ( vlc_dictionary_entry_t* p_entry = merged.p_entries[i]; p_entry != NULL; p_entry = p_entry->p_next ) { - FillRegionStyle( p_ctx, p_entry->psz_key, p_entry->p_value, - p_region ); + const char *psz_namespace = tt_namespaces_GetURI( p_nss, p_entry->psz_key ); + if( !psz_namespace ) + continue; + const char *psz_name = tt_LocalName( p_entry->psz_key ); + FillRegionStyle( p_ctx, psz_name, psz_namespace, + p_entry->p_value, p_region ); } } } @@ -841,7 +863,8 @@ static void AppendLineBreakToRegion( ttml_region_t *p_region ) } } -static void AppendTextToRegion( ttml_context_t *p_ctx, const tt_textnode_t *p_ttnode, +static void AppendTextToRegion( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, + const tt_textnode_t *p_ttnode, const ttml_style_t *p_set_styles, ttml_region_t *p_region ) { text_segment_t *p_segment; @@ -853,7 +876,7 @@ static void AppendTextToRegion( ttml_context_t *p_ctx, const tt_textnode_t *p_tt if( p_segment ) { bool b_preserve_space = false; - ttml_style_t *s = InheritTTMLStyles( p_ctx, p_ttnode->p_parent ); + ttml_style_t *s = InheritTTMLStyles( p_ctx, p_nss, p_ttnode->p_parent ); if( s ) { if( p_set_styles ) @@ -900,12 +923,13 @@ static void AppendTextToRegion( ttml_context_t *p_ctx, const tt_textnode_t *p_tt p_region->pp_last_segment = &p_segment->p_next; } -static const char * GetSMPTEImage( ttml_context_t *p_ctx, const char *psz_id ) +static const char * GetSMPTEImage( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, + const char *psz_id ) { if( !p_ctx->p_rootnode ) return NULL; - tt_node_t *p_head = FindNode( p_ctx->p_rootnode, "head", 1, NULL ); + tt_node_t *p_head = FindNode( p_nss, p_ctx->p_rootnode, "head", TT_NS, 1, NULL ); if( !p_head ) return NULL; @@ -916,10 +940,11 @@ static const char * GetSMPTEImage( ttml_context_t *p_ctx, const char *psz_id ) continue; tt_node_t *p_node = (tt_node_t *) p_child; - if( tt_node_NameCompare( p_node->psz_node_name, "metadata" ) ) + if( !tt_node_Match( p_node, "metadata", TT_NS ) ) continue; - tt_node_t *p_imagenode = FindNode( p_node, "smpte:image", 1, psz_id ); + tt_node_t *p_imagenode = FindNode( p_nss, p_node, "image", TT_NS_SMPTE_TT_EXT, + 1, psz_id ); if( !p_imagenode ) continue; @@ -936,7 +961,8 @@ static const char * GetSMPTEImage( ttml_context_t *p_ctx, const char *psz_id ) return NULL; } -static void ConvertNodesToRegionContent( ttml_context_t *p_ctx, const tt_node_t *p_node, +static void ConvertNodesToRegionContent( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, + const tt_node_t *p_node, ttml_region_t *p_region, const ttml_style_t *p_upper_set_styles, tt_time_t playbacktime ) @@ -945,34 +971,29 @@ static void ConvertNodesToRegionContent( ttml_context_t *p_ctx, const tt_node_t !tt_timings_Contains( &p_node->timings, &playbacktime ) ) return; - const char *psz_regionid = (const char *) - vlc_dictionary_value_for_key( &p_node->attr_dict, "region" ); + const char *psz_regionid = tt_node_GetAttribute( p_nss, p_node, "region", NULL ); /* Region isn't set or is changing */ if( psz_regionid || p_region == NULL ) - p_region = GetTTMLRegion( p_ctx, psz_regionid ); + p_region = GetTTMLRegion( p_ctx, p_nss, psz_regionid ); /* Check for bitmap profile defined by ST2052 / SMPTE-TT */ - if( !tt_node_NameCompare( p_node->psz_node_name, "div" ) && - vlc_dictionary_has_key( &p_node->attr_dict, "smpte:backgroundImage" ) ) + if( tt_node_Match( p_node, "div", TT_NS ) ) { - if( !p_region->bgbitmap.p_bytes ) + const char *psz_id = tt_node_GetAttribute( p_nss, p_node, "backgroundImage", + TT_NS_SMPTE_TT_EXT ); + if( !p_region->bgbitmap.p_bytes && psz_id && *psz_id == '#' ) { - const char *psz_id = vlc_dictionary_value_for_key( &p_node->attr_dict, - "smpte:backgroundImage" ); /* Seems SMPTE can't make diff between html and xml.. */ - if( psz_id && *psz_id == '#' ) - { - const char *psz_base64 = GetSMPTEImage( p_ctx, &psz_id[1] ); - if( psz_base64 ) - p_region->bgbitmap.i_bytes = - vlc_b64_decode_binary( &p_region->bgbitmap.p_bytes, psz_base64 ); - } + const char *psz_base64 = GetSMPTEImage( p_ctx, p_nss, &psz_id[1] ); + if( psz_base64 ) + p_region->bgbitmap.i_bytes = + vlc_b64_decode_binary( &p_region->bgbitmap.p_bytes, psz_base64 ); } } /* awkward paragraph handling */ - if( !tt_node_NameCompare( p_node->psz_node_name, "p" ) && + if( tt_node_Match( p_node, "p", TT_NS ) && p_region->updt.p_segments ) { AppendLineBreakToRegion( p_region ); @@ -988,10 +1009,10 @@ static void ConvertNodesToRegionContent( ttml_context_t *p_ctx, const tt_node_t { if( p_child->i_type == TT_NODE_TYPE_TEXT ) { - AppendTextToRegion( p_ctx, (const tt_textnode_t *) p_child, + AppendTextToRegion( p_ctx, p_nss, (const tt_textnode_t *) p_child, p_set_styles, p_region ); } - else if( !tt_node_NameCompare( ((const tt_node_t *)p_child)->psz_node_name, "set" ) ) + else if( tt_node_Match( (const tt_node_t *)p_child, "set", TT_NS ) ) { const tt_node_t *p_set = (const tt_node_t *)p_child; if( !tt_time_Valid( &playbacktime ) || @@ -1000,17 +1021,17 @@ static void ConvertNodesToRegionContent( ttml_context_t *p_ctx, const tt_node_t if( p_set_styles != NULL || (p_set_styles = ttml_style_New()) ) { /* Merge with or create a local set of styles to apply to following childs */ - DictToTTMLStyle( p_ctx, &p_set->attr_dict, p_set_styles ); + DictToTTMLStyle( p_ctx, p_nss, &p_set->attr_dict, p_set_styles ); } } } - else if( !tt_node_NameCompare( ((const tt_node_t *)p_child)->psz_node_name, "br" ) ) + else if( tt_node_Match( (const tt_node_t *)p_child, "br", TT_NS ) ) { AppendLineBreakToRegion( p_region ); } else { - ConvertNodesToRegionContent( p_ctx, (const tt_node_t *) p_child, + ConvertNodesToRegionContent( p_ctx, p_nss, (const tt_node_t *) p_child, p_region, p_set_styles, playbacktime ); } } @@ -1019,7 +1040,8 @@ static void ConvertNodesToRegionContent( ttml_context_t *p_ctx, const tt_node_t ttml_style_Delete( p_set_styles ); } -static tt_node_t *ParseTTML( decoder_t *p_dec, const uint8_t *p_buffer, size_t i_buffer ) +static tt_node_t *ParseTTML( decoder_t *p_dec, tt_namespaces_t *p_nss, + const uint8_t *p_buffer, size_t i_buffer ) { stream_t* p_sub; xml_reader_t* p_xml_reader; @@ -1036,7 +1058,7 @@ static tt_node_t *ParseTTML( decoder_t *p_dec, const uint8_t *p_buffer, size_t i } tt_node_t *p_rootnode = NULL; - if( ParseTTMLChunk( p_xml_reader, &p_rootnode ) != VLC_SUCCESS ) + if( ParseTTMLChunk( p_xml_reader, p_nss, &p_rootnode ) != VLC_SUCCESS ) { if( p_rootnode ) tt_node_RecursiveDelete( p_rootnode ); @@ -1049,7 +1071,8 @@ static tt_node_t *ParseTTML( decoder_t *p_dec, const uint8_t *p_buffer, size_t i return p_rootnode; } -static void InitTTMLContext( tt_node_t *p_rootnode, ttml_context_t *p_ctx ) +static void InitTTMLContext( tt_namespaces_t *p_nss, tt_node_t *p_rootnode, + ttml_context_t *p_ctx ) { p_ctx->p_rootnode = p_rootnode; /* set defaults required for size/cells computation */ @@ -1060,16 +1083,14 @@ static void InitTTMLContext( tt_node_t *p_rootnode, ttml_context_t *p_ctx ) p_ctx->i_cell_resolution_v = TTML_DEFAULT_CELL_RESOLUTION_V; p_ctx->i_cell_resolution_h = TTML_DEFAULT_CELL_RESOLUTION_H; /* and override them */ - const char *value = vlc_dictionary_value_for_key( &p_rootnode->attr_dict, - "tts:extent" ); - if( value != kVLCDictionaryNotFound ) + const char *value = tt_node_GetAttribute( p_nss, p_rootnode, "extent", TT_NS_STYLING ); + if( value ) { ttml_read_coords( value, &p_ctx->root_extent_h, &p_ctx->root_extent_v ); } - value = vlc_dictionary_value_for_key( &p_rootnode->attr_dict, - "ttp:cellResolution" ); - if( value != kVLCDictionaryNotFound ) + value = tt_node_GetAttribute( p_nss, p_rootnode, "cellResolution", TT_NS_PARAMETER ); + if( value ) { unsigned w, h; if( sscanf( value, "%u %u", &w, &h) == 2 && w && h ) @@ -1080,22 +1101,23 @@ static void InitTTMLContext( tt_node_t *p_rootnode, ttml_context_t *p_ctx ) } } -static ttml_region_t *GenerateRegions( tt_node_t *p_rootnode, tt_time_t playbacktime ) +static ttml_region_t *GenerateRegions( tt_namespaces_t *p_nss, tt_node_t *p_rootnode, + tt_time_t playbacktime ) { ttml_region_t* p_regions = NULL; ttml_region_t** pp_region_last = &p_regions; - if( !tt_node_NameCompare( p_rootnode->psz_node_name, "tt" ) ) + if( tt_node_Match( p_rootnode, "tt", TT_NS ) ) { - const tt_node_t *p_bodynode = FindNode( p_rootnode, "body", 1, NULL ); + const tt_node_t *p_bodynode = FindNode( p_nss, p_rootnode, "body", TT_NS, 1, NULL ); if( p_bodynode ) { ttml_context_t context; - InitTTMLContext( p_rootnode, &context ); + InitTTMLContext( p_nss, p_rootnode, &context ); context.p_rootnode = p_rootnode; vlc_dictionary_init( &context.regions, 1 ); - ConvertNodesToRegionContent( &context, p_bodynode, NULL, NULL, playbacktime ); + ConvertNodesToRegionContent( &context, p_nss, p_bodynode, NULL, NULL, playbacktime ); for( int i = 0; i < context.regions.i_size; ++i ) { @@ -1110,8 +1132,8 @@ static ttml_region_t *GenerateRegions( tt_node_t *p_rootnode, tt_time_t playback vlc_dictionary_clear( &context.regions, NULL, NULL ); } } - else if ( !tt_node_NameCompare( p_rootnode->psz_node_name, "div" ) || - !tt_node_NameCompare( p_rootnode->psz_node_name, "p" ) ) + else if ( tt_node_Match( p_rootnode, "div", TT_NS ) || + tt_node_Match( p_rootnode, "p", TT_NS ) ) { /* TODO */ } @@ -1315,9 +1337,14 @@ static int ParseBlock( decoder_t *p_dec, const block_t *p_block ) return VLCDEC_SUCCESS; } - tt_node_t *p_rootnode = ParseTTML( p_dec, p_block->p_buffer, p_block->i_buffer ); + tt_namespaces_t namespaces; + tt_namespaces_Init( &namespaces ); + tt_node_t *p_rootnode = ParseTTML( p_dec, &namespaces, p_block->p_buffer, p_block->i_buffer ); if( !p_rootnode ) + { + tt_namespaces_Clean( &namespaces ); return VLCDEC_SUCCESS; + } tt_timings_Resolve( (tt_basenode_t *) p_rootnode, &temporal_extent, &p_timings_array, &i_timings_count ); @@ -1347,7 +1374,7 @@ static int ParseBlock( decoder_t *p_dec, const block_t *p_block ) bool b_bitmap_regions = false; subpicture_t *p_spu = NULL; - ttml_region_t *p_regions = GenerateRegions( p_rootnode, p_timings_array[i] ); + ttml_region_t *p_regions = GenerateRegions( &namespaces, p_rootnode, p_timings_array[i] ); if( p_regions ) { if( p_regions->bgbitmap.i_bytes > 0 && p_regions->updt.p_segments == NULL ) @@ -1389,6 +1416,7 @@ static int ParseBlock( decoder_t *p_dec, const block_t *p_block ) } tt_node_RecursiveDelete( p_rootnode ); + tt_namespaces_Clean( &namespaces ); free( p_timings_array ); diff --git a/modules/codec/ttml/ttml.c b/modules/codec/ttml/ttml.c index 242ba8f666..6236f02df2 100644 --- a/modules/codec/ttml/ttml.c +++ b/modules/codec/ttml/ttml.c @@ -177,11 +177,22 @@ bool tt_node_Match( const tt_node_t *p_node, const char *psz_name, const char *p return !!psz_namespace == !!psz_nodens; } -int tt_node_NameCompare( const char* psz_tagname, const char* psz_pattern ) +const char * tt_node_GetAttribute( tt_namespaces_t *p_nss, const tt_node_t *p_node, + const char *psz_name, const char *psz_namespace ) { - if( !strncasecmp( "tt:", psz_tagname, 3 ) ) - psz_tagname += 3; - return strcasecmp( psz_tagname, psz_pattern ); + const void *value; + char *alloc = NULL; + if( psz_namespace ) + { + const char *psz_prefix = tt_namespaces_GetPrefix( p_nss, psz_namespace ); + if( psz_prefix == NULL || + asprintf( &alloc, "%s:%s", psz_prefix, psz_name ) < 1 ) + return NULL; + psz_name = alloc; + } + value = vlc_dictionary_value_for_key( &p_node->attr_dict, psz_name ); + free( alloc ); + return value != kVLCDictionaryNotFound ? (const char *)value : NULL; } bool tt_node_HasChild( const tt_node_t *p_node ) @@ -385,7 +396,8 @@ tt_node_t * tt_node_New( tt_node_t* p_parent, return p_node; } -tt_node_t * tt_node_NewRead( xml_reader_t* reader, tt_node_t* p_parent, +tt_node_t * tt_node_NewRead( xml_reader_t* reader, + tt_namespaces_t *p_nss, tt_node_t* p_parent, const char* psz_node_name, const char *psz_namespace ) { tt_node_t *p_node = tt_node_New( p_parent, psz_node_name, psz_namespace ); @@ -397,7 +409,8 @@ tt_node_t * tt_node_NewRead( xml_reader_t* reader, tt_node_t* p_parent, psz_key != NULL; psz_key = xml_ReaderNextAttrNS( reader, &psz_value, &psz_ns ) ) { - fprintf(stderr,"ATTR %s %s %s\n", psz_key, psz_value, psz_ns); + if( psz_ns && psz_key ) + tt_namespaces_Register( p_nss, psz_key, psz_ns ); char *psz_val = strdup( psz_value ); if( psz_val ) { @@ -474,7 +487,7 @@ static int tt_node_Skip( xml_reader_t *p_reader, const char *psz_skipped ) return VLC_EGENERIC; } #endif -int tt_nodes_Read( xml_reader_t *p_reader, tt_node_t *p_root_node ) +int tt_nodes_Read( xml_reader_t *p_reader, tt_namespaces_t *p_nss, tt_node_t *p_root_node ) { size_t i_depth = 0; tt_node_t *p_node = p_root_node; @@ -496,7 +509,9 @@ int tt_nodes_Read( xml_reader_t *p_reader, tt_node_t *p_root_node ) case XML_READER_STARTELEM: { - tt_node_t *p_newnode = tt_node_NewRead( p_reader, p_node, psz_node_name, + tt_namespaces_Register( p_nss, psz_node_name, psz_node_namespace ); + tt_node_t *p_newnode = tt_node_NewRead( p_reader, p_nss, p_node, + psz_node_name, psz_node_namespace ); if( !p_newnode ) return VLC_EGENERIC; @@ -517,7 +532,7 @@ int tt_nodes_Read( xml_reader_t *p_reader, tt_node_t *p_root_node ) case XML_READER_ENDELEM: { - if( strcmp( psz_node_name, p_node->psz_node_name ) ) + if( !tt_node_Match( p_node, psz_node_name, psz_node_namespace ) ) return VLC_EGENERIC; if( i_depth == 0 ) diff --git a/modules/codec/ttml/ttml.h b/modules/codec/ttml/ttml.h index 7e5ae4ce3c..9950e230ae 100644 --- a/modules/codec/ttml/ttml.h +++ b/modules/codec/ttml/ttml.h @@ -129,16 +129,17 @@ static inline const char *tt_LocalName( const char *psz_qname ) tt_textnode_t *tt_textnode_New( tt_node_t *p_parent, const char *psz_text ); tt_textnode_t *tt_subtextnode_New( tt_node_t *p_parent, const char *psz_text, size_t ); tt_node_t * tt_node_New( tt_node_t* p_parent, const char* psz_node_name, const char *psz_namespace ); -tt_node_t * tt_node_NewRead( xml_reader_t* reader, tt_node_t* p_parent, const char* psz_node_name, - const char *psz_namespace ); +tt_node_t * tt_node_NewRead( xml_reader_t* reader, tt_namespaces_t *, tt_node_t* p_parent, + const char* psz_node_name, const char *psz_namespace ); void tt_node_RecursiveDelete( tt_node_t *p_node ); bool tt_node_Match( const tt_node_t *p_node, const char* psz_name, const char* psz_namespace ); -int tt_node_NameCompare( const char* psz_tagname, const char* psz_pattern ); +const char * tt_node_GetAttribute( tt_namespaces_t *, const tt_node_t *p_node, + const char *psz_name, const char *psz_namespace ); bool tt_node_HasChild( const tt_node_t *p_node ); int tt_node_AddAttribute( tt_node_t *p_node, const char *key, const char *value ); void tt_node_RemoveAttribute( tt_node_t *p_node, const char *key ); -int tt_nodes_Read( xml_reader_t *p_reader, tt_node_t *p_root_node ); +int tt_nodes_Read( xml_reader_t *p_reader, tt_namespaces_t *, tt_node_t *p_root_node ); void tt_timings_Resolve( tt_basenode_t *p_child, const tt_timings_t *p_container_timings, tt_time_t **pp_array, size_t *pi_count ); diff --git a/modules/demux/ttml.c b/modules/demux/ttml.c index 965f79f050..a9352477f3 100644 --- a/modules/demux/ttml.c +++ b/modules/demux/ttml.c @@ -51,6 +51,7 @@ typedef struct bool b_first_time; tt_node_t *p_rootnode; + tt_namespaces_t namespaces; tt_timings_t temporal_extent; @@ -157,11 +158,11 @@ static int Control( demux_t* p_demux, int i_query, va_list args ) static int ReadTTML( demux_t* p_demux ) { demux_sys_t* p_sys = p_demux->p_sys; - const char* psz_node_name, *psz_namespace; + const char* psz_node_name, *psz_node_namespace; do { - int i_type = xml_ReaderNextNodeNS( p_sys->p_reader, &psz_node_name, &psz_namespace ); + int i_type = xml_ReaderNextNodeNS( p_sys->p_reader, &psz_node_name, &psz_node_namespace ); bool b_empty = xml_ReaderIsEmptyElement( p_sys->p_reader ); if( i_type <= XML_READER_NONE ) @@ -173,22 +174,25 @@ static int ReadTTML( demux_t* p_demux ) break; case XML_READER_STARTELEM: - if( tt_node_NameCompare( psz_node_name, "tt" ) || + if( strcmp( psz_node_namespace, TT_NS ) || + strcmp( tt_LocalName( psz_node_name ), "tt" ) || p_sys->p_rootnode != NULL ) return VLC_EGENERIC; - p_sys->p_rootnode = tt_node_NewRead( p_sys->p_reader, NULL, psz_node_name, - psz_namespace ); + p_sys->p_rootnode = tt_node_NewRead( p_sys->p_reader, &p_sys->namespaces, NULL, + psz_node_name, + psz_node_namespace ); if( b_empty ) break; if( !p_sys->p_rootnode || - tt_nodes_Read( p_sys->p_reader, p_sys->p_rootnode ) != VLC_SUCCESS ) + tt_nodes_Read( p_sys->p_reader, + &p_sys->namespaces, p_sys->p_rootnode ) != VLC_SUCCESS ) return VLC_EGENERIC; break; case XML_READER_ENDELEM: if( !p_sys->p_rootnode || - tt_node_NameCompare( psz_node_name, p_sys->p_rootnode->psz_node_name ) ) + strcmp( psz_node_name, p_sys->p_rootnode->psz_node_name ) ) return VLC_EGENERIC; break; } @@ -342,6 +346,7 @@ int tt_OpenDemux( vlc_object_t* p_this ) tt_time_Init( &p_sys->temporal_extent.end ); tt_time_Init( &p_sys->temporal_extent.dur ); p_sys->temporal_extent.begin.base = 0; + tt_namespaces_Init( &p_sys->namespaces ); p_sys->p_xml = xml_Create( p_demux ); if( !p_sys->p_xml ) @@ -419,6 +424,8 @@ void tt_CloseDemux( vlc_object_t* p_this ) if( p_sys->p_xml ) xml_Delete( p_sys->p_xml ); + tt_namespaces_Clean( &p_sys->namespaces ); + free( p_sys->times.p_array ); free( p_sys );