codec/demux: ttml: handle non-default namespaces

This commit is contained in:
François Cartegnie 2024-02-29 17:56:06 +01:00 committed by Felix Paul Kühne
parent a55c9e9a6b
commit 44879313ba
4 changed files with 196 additions and 145 deletions

View File

@ -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 );

View File

@ -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 )

View File

@ -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 );

View File

@ -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 );