1 /*****************************************************************************
2 * substtml.c : TTML subtitles decoder
3 *****************************************************************************
4 * Copyright (C) 2015 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 *****************************************************************************/
28 #include <vlc_common.h>
29 #include <vlc_plugin.h>
30 #include <vlc_modules.h>
31 #include <vlc_codec.h>
33 #include <vlc_stream.h>
34 #include <vlc_text_style.h>
40 #define ALIGN_TEXT N_("Subtitle justification")
41 #define ALIGN_LONGTEXT N_("Set the justification of subtitles")
43 /*****************************************************************************
45 *****************************************************************************/
46 static int OpenDecoder ( vlc_object_t
* );
47 static void CloseDecoder ( vlc_object_t
* );
49 static text_segment_t
*ParseTTMLSubtitles( decoder_t
*, subpicture_updater_sys_t
*, char * );
52 set_capability( "decoder", 10 )
53 set_shortname( N_("TTML decoder"))
54 set_description( N_("TTML subtitles decoder") )
55 set_callbacks( OpenDecoder
, CloseDecoder
)
56 set_category( CAT_INPUT
)
57 set_subcategory( SUBCAT_INPUT_SCODEC
)
58 add_integer( "ttml-align", 0, ALIGN_TEXT
, ALIGN_LONGTEXT
, false )
61 /*****************************************************************************
63 *****************************************************************************/
68 text_style_t
* font_style
;
72 int i_margin_percent_h
;
73 int i_margin_percent_v
;
79 ttml_style_t
** pp_styles
;
83 static void MergeTTMLStyle( ttml_style_t
*p_dst
, const ttml_style_t
*p_src
)
85 text_style_Merge( p_dst
->font_style
, p_src
->font_style
, false );
86 if( !( p_dst
->i_align
& SUBPICTURE_ALIGN_MASK
) )
87 p_dst
->i_align
|= p_src
->i_align
;
89 if( !p_dst
->i_margin_h
)
90 p_dst
->i_margin_h
= p_src
->i_margin_h
;
92 if( !p_dst
->i_margin_v
)
93 p_dst
->i_margin_v
= p_src
->i_margin_v
;
95 if( !p_dst
->i_margin_percent_h
)
96 p_dst
->i_margin_percent_h
= p_src
->i_margin_percent_h
;
98 if( !p_dst
->i_margin_percent_v
)
99 p_dst
->i_margin_percent_v
= p_src
->i_margin_percent_v
;
102 static ttml_style_t
* DuplicateStyle( ttml_style_t
* p_style_src
)
104 ttml_style_t
* p_style
= calloc( 1, sizeof( *p_style
) );
105 if( unlikely( p_style
== NULL
) )
108 *p_style
= *p_style_src
;
109 p_style
->psz_styleid
= strdup( p_style_src
->psz_styleid
);
110 if( unlikely( p_style
->psz_styleid
== NULL
) )
116 p_style
->font_style
= text_style_Duplicate( p_style_src
->font_style
);
117 if( unlikely( p_style
->font_style
== NULL
) )
119 free( p_style
->psz_styleid
);
126 static void CleanupStyle( ttml_style_t
* p_ttml_style
)
128 text_style_Delete( p_ttml_style
->font_style
);
129 free( p_ttml_style
->psz_styleid
);
130 free( p_ttml_style
);
133 static ttml_style_t
*FindTextStyle( decoder_t
*p_dec
, const char *psz_style
)
135 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
137 for( size_t i
= 0; i
< p_sys
->i_styles
; i
++ )
139 if( !strcmp( p_sys
->pp_styles
[i
]->psz_styleid
, psz_style
) )
140 return DuplicateStyle( p_sys
->pp_styles
[i
] );
146 typedef struct style_stack_t
148 ttml_style_t
* p_style
;
149 struct style_stack_t
* p_next
;
152 static bool PushStyle( style_stack_t
**pp_stack
, ttml_style_t
* p_style
)
154 style_stack_t
* p_entry
= malloc( sizeof( *p_entry
) );
155 if( unlikely( p_entry
== NULL
) )
157 p_entry
->p_style
= p_style
;
158 p_entry
->p_next
= *pp_stack
;
163 static void PopStyle( style_stack_t
** pp_stack
)
165 if( *pp_stack
== NULL
)
167 style_stack_t
* p_next
= (*pp_stack
)->p_next
;
172 static void ClearStack( style_stack_t
* p_stack
)
174 while( p_stack
!= NULL
)
176 style_stack_t
* p_next
= p_stack
->p_next
;
182 static text_style_t
* CurrentStyle( style_stack_t
* p_stack
)
184 if( p_stack
== NULL
)
185 return text_style_Create( STYLE_NO_DEFAULTS
);
187 return text_style_Duplicate( p_stack
->p_style
->font_style
);
190 static ttml_style_t
* ParseTTMLStyle( decoder_t
*p_dec
, xml_reader_t
* p_reader
, const char* psz_node_name
)
192 decoder_sys_t
* p_sys
= p_dec
->p_sys
;
193 ttml_style_t
*p_ttml_style
= NULL
;
194 ttml_style_t
*p_base_style
= NULL
;
196 p_ttml_style
= calloc( 1, sizeof( ttml_style_t
) );
197 if( unlikely( !p_ttml_style
) )
200 p_ttml_style
->font_style
= text_style_Create( STYLE_NO_DEFAULTS
);
201 if( unlikely( !p_ttml_style
->font_style
) )
203 free( p_ttml_style
);
207 const char *attr
, *val
;
209 while( (attr
= xml_ReaderNextAttr( p_reader
, &val
) ) )
211 /* searching previous styles for inheritence */
212 if( !strcasecmp( attr
, "style" ) || !strcasecmp( attr
, "region" ) )
214 if( !strcasecmp( psz_node_name
, "style" ) || !strcasecmp( psz_node_name
, "tt:style" ) ||
215 !strcasecmp( psz_node_name
, "region" ) || !strcasecmp( psz_node_name
, "tt:region" ) )
217 for( size_t i
= 0; i
< p_sys
->i_styles
; i
++ )
219 if( !strcasecmp( p_sys
->pp_styles
[i
]->psz_styleid
, val
) )
221 p_base_style
= DuplicateStyle( p_sys
->pp_styles
[i
] );
222 if( unlikely( p_base_style
== NULL
) )
230 * In p nodes, style attribute has this format :
231 * style="style1 style2 style3" where style1 and style2 are
232 * style applied on the parents of p in that order.
234 * In span node, we can apply several styles in the same order than
235 * in p nodes with the same inheritance order.
237 * In order to preserve this style predominance, we merge the styles
238 * in the from right to left ( the right one being predominant ) .
240 else if( !strcasecmp( psz_node_name
, "p" ) || !strcasecmp( psz_node_name
, "tt:p" ) ||
241 !strcasecmp( psz_node_name
, "span" ) || !strcasecmp( psz_node_name
, "tt:span" ) )
244 char *value
= strdup( val
);
245 if( unlikely( value
== NULL
) )
248 char *token
= strtok_r( value
, " ", &tmp
);
249 ttml_style_t
* p_style
= FindTextStyle( p_dec
, token
);
250 if( p_style
== NULL
)
252 msg_Warn( p_dec
, "Style \"%s\" not found", token
);
257 while( ( token
= strtok_r( NULL
, " ", &tmp
) ) != NULL
)
259 ttml_style_t
* p_next_style
= FindTextStyle( p_dec
, token
);
260 if( p_next_style
== NULL
)
262 msg_Warn( p_dec
, "Style \"%s\" not found", token
);
266 MergeTTMLStyle( p_next_style
, p_style
);
267 CleanupStyle( p_style
);
268 p_style
= p_next_style
;
270 MergeTTMLStyle( p_style
, p_ttml_style
);
272 CleanupStyle( p_ttml_style
);
273 p_ttml_style
= p_style
;
277 ttml_style_t
* p_style
= FindTextStyle( p_dec
, val
);
278 if( p_style
== NULL
)
280 msg_Warn( p_dec
, "Style \"%s\" not found", val
);
283 MergeTTMLStyle( p_style
, p_ttml_style
);
284 CleanupStyle( p_ttml_style
);
285 p_ttml_style
= p_style
;
288 else if( !strcasecmp( "xml:id", attr
) )
290 free( p_ttml_style
->psz_styleid
);
291 p_ttml_style
->psz_styleid
= strdup( val
);
293 else if( !strcasecmp ( "tts:fontFamily", attr
) )
295 free( p_ttml_style
->font_style
->psz_fontname
);
296 p_ttml_style
->font_style
->psz_fontname
= strdup( val
);
297 if( unlikely( p_ttml_style
->font_style
->psz_fontname
== NULL
) )
299 CleanupStyle( p_ttml_style
);
303 else if( !strcasecmp( "tts:opacity", attr
) )
305 p_ttml_style
->font_style
->i_background_alpha
= atoi( val
);
306 p_ttml_style
->font_style
->i_font_alpha
= atoi( val
);
307 p_ttml_style
->font_style
->i_features
|= STYLE_HAS_BACKGROUND_ALPHA
| STYLE_HAS_FONT_ALPHA
;
309 else if( !strcasecmp( "tts:fontSize", attr
) )
311 char* psz_end
= NULL
;
312 float size
= strtof( val
, &psz_end
);
313 if( *psz_end
== '%' )
314 p_ttml_style
->font_style
->f_font_relsize
= size
;
316 p_ttml_style
->font_style
->i_font_size
= (int)( size
+ 0.5 );
318 else if( !strcasecmp( "tts:color", attr
) )
320 unsigned int i_color
= vlc_html_color( val
, NULL
);
321 p_ttml_style
->font_style
->i_font_color
= (i_color
& 0xffffff);
322 p_ttml_style
->font_style
->i_font_alpha
= (i_color
& 0xFF000000) >> 24;
323 p_ttml_style
->font_style
->i_features
|= STYLE_HAS_FONT_COLOR
| STYLE_HAS_FONT_ALPHA
;
325 else if( !strcasecmp( "tts:backgroundColor", attr
) )
327 unsigned int i_color
= vlc_html_color( val
, NULL
);
328 p_ttml_style
->font_style
->i_background_color
= i_color
& 0xFFFFFF;
329 p_ttml_style
->font_style
->i_background_alpha
= (i_color
& 0xFF000000) >> 24;
330 p_ttml_style
->font_style
->i_features
|= STYLE_HAS_BACKGROUND_COLOR
331 | STYLE_HAS_BACKGROUND_ALPHA
;
332 p_ttml_style
->font_style
->i_style_flags
|= STYLE_BACKGROUND
;
334 else if( !strcasecmp( "tts:textAlign", attr
) )
336 if( !strcasecmp ( "left", val
) )
337 p_ttml_style
->i_align
= SUBPICTURE_ALIGN_TOP
| SUBPICTURE_ALIGN_LEFT
;
338 else if( !strcasecmp ( "right", val
) )
339 p_ttml_style
->i_align
= SUBPICTURE_ALIGN_TOP
| SUBPICTURE_ALIGN_RIGHT
;
340 else if( !strcasecmp ( "center", val
) )
341 p_ttml_style
->i_align
= SUBPICTURE_ALIGN_BOTTOM
;
342 else if( !strcasecmp ( "start", val
) )
343 p_ttml_style
->i_align
= SUBPICTURE_ALIGN_BOTTOM
| SUBPICTURE_ALIGN_LEFT
;
344 else if( !strcasecmp ( "end", val
) )
345 p_ttml_style
->i_align
= SUBPICTURE_ALIGN_BOTTOM
| SUBPICTURE_ALIGN_RIGHT
;
347 else if( !strcasecmp( "tts:fontStyle", attr
) )
349 if( !strcasecmp ( "italic", val
) || !strcasecmp ( "oblique", val
) )
350 p_ttml_style
->font_style
->i_style_flags
|= STYLE_ITALIC
;
352 p_ttml_style
->font_style
->i_style_flags
&= ~STYLE_ITALIC
;
353 p_ttml_style
->font_style
->i_features
|= STYLE_HAS_FLAGS
;
355 else if( !strcasecmp ( "tts:fontWeight", attr
) )
357 if( !strcasecmp ( "bold", val
) )
358 p_ttml_style
->font_style
->i_style_flags
|= STYLE_BOLD
;
360 p_ttml_style
->font_style
->i_style_flags
&= ~STYLE_BOLD
;
361 p_ttml_style
->font_style
->i_features
|= STYLE_HAS_FLAGS
;
363 else if( !strcasecmp ( "tts:textDecoration", attr
) )
365 if( !strcasecmp ( "underline", val
) )
366 p_ttml_style
->font_style
->i_style_flags
|= STYLE_UNDERLINE
;
367 else if( !strcasecmp ( "noUnderline", val
) )
368 p_ttml_style
->font_style
->i_style_flags
&= ~STYLE_UNDERLINE
;
369 if( !strcasecmp ( "lineThrough", val
) )
370 p_ttml_style
->font_style
->i_style_flags
|= STYLE_STRIKEOUT
;
371 else if( !strcasecmp ( "noLineThrough", val
) )
372 p_ttml_style
->font_style
->i_style_flags
&= ~STYLE_STRIKEOUT
;
373 p_ttml_style
->font_style
->i_features
|= STYLE_HAS_FLAGS
;
375 else if( !strcasecmp ( "tts:origin", attr
) )
377 const char *psz_token
= val
;
378 while( isspace( *psz_token
) )
381 const char *psz_separator
= strchr( psz_token
, ' ' );
382 if( psz_separator
== NULL
)
384 msg_Warn( p_dec
, "Invalid origin attribute: \"%s\"", val
);
387 const char *psz_percent_sign
= strchr( psz_token
, '%' );
389 if( psz_percent_sign
!= NULL
&& psz_percent_sign
< psz_separator
)
391 p_ttml_style
->i_margin_h
= 0;
392 p_ttml_style
->i_margin_percent_h
= atoi( psz_token
);
396 p_ttml_style
->i_margin_h
= atoi( psz_token
);
397 p_ttml_style
->i_margin_percent_h
= 0;
399 while( isspace( *psz_separator
) )
401 psz_token
= psz_separator
;
402 psz_percent_sign
= strchr( psz_token
, '%' );
403 if( psz_percent_sign
!= NULL
)
405 p_ttml_style
->i_margin_v
= 0;
406 p_ttml_style
->i_margin_percent_v
= atoi( val
);
410 p_ttml_style
->i_margin_v
= atoi( val
);
411 p_ttml_style
->i_margin_percent_v
= 0;
414 else if( !strcasecmp( "tts:textOutline", attr
) )
416 char *value
= strdup( val
);
417 char* psz_saveptr
= NULL
;
418 char* token
= strtok_r( value
, " ", &psz_saveptr
);
419 // <color>? <length> <length>?
421 unsigned int color
= vlc_html_color( token
, &b_ok
);
424 p_ttml_style
->font_style
->i_outline_color
= color
& 0xFFFFFF;
425 p_ttml_style
->font_style
->i_outline_alpha
= (color
& 0xFF000000) >> 24;
426 token
= strtok_r( NULL
, " ", &psz_saveptr
);
428 char* psz_end
= NULL
;
429 int i_outline_width
= strtol( token
, &psz_end
, 10 );
430 if( psz_end
!= token
)
432 // Assume unit is pixel, and ignore border radius
433 p_ttml_style
->font_style
->i_outline_width
= i_outline_width
;
438 if( p_base_style
!= NULL
)
440 MergeTTMLStyle( p_ttml_style
, p_base_style
);
442 if( p_ttml_style
->psz_styleid
== NULL
)
444 CleanupStyle( p_ttml_style
);
450 static void ParseTTMLStyles( decoder_t
* p_dec
)
452 stream_t
* p_stream
= vlc_stream_MemoryNew( p_dec
, (uint8_t*)p_dec
->fmt_in
.p_extra
, p_dec
->fmt_in
.i_extra
, true );
453 if( unlikely( p_stream
== NULL
) )
456 xml_reader_t
* p_reader
= xml_ReaderCreate( p_dec
, p_stream
);
457 if( unlikely( p_reader
== NULL
) )
459 vlc_stream_Delete( p_stream
);
462 const char* psz_node_name
;
463 int i_type
= xml_ReaderNextNode( p_reader
, &psz_node_name
);
465 if( i_type
== XML_READER_STARTELEM
&& ( !strcasecmp( psz_node_name
, "tt" ) || !strcasecmp( psz_node_name
, "tt:tt" ) ) )
467 int i_type
= xml_ReaderNextNode( p_reader
, &psz_node_name
);
468 while( i_type
!= XML_READER_STARTELEM
|| ( strcasecmp( psz_node_name
, "styling" ) && strcasecmp( psz_node_name
, "tt:styling" ) ) )
469 i_type
= xml_ReaderNextNode( p_reader
, &psz_node_name
);
473 /* region and style tag are respectively inside layout and styling tags */
474 if( !strcasecmp( psz_node_name
, "styling" ) || !strcasecmp( psz_node_name
, "layout" ) ||
475 !strcasecmp( psz_node_name
, "tt:styling" ) || !strcasecmp( psz_node_name
, "tt:layout" ) )
477 i_type
= xml_ReaderNextNode( p_reader
, &psz_node_name
);
478 while( i_type
!= XML_READER_ENDELEM
)
480 ttml_style_t
* p_ttml_style
= ParseTTMLStyle( p_dec
, p_reader
, psz_node_name
);
481 if ( p_ttml_style
== NULL
)
483 xml_ReaderDelete( p_reader
);
484 vlc_stream_Delete( p_stream
);
487 decoder_sys_t
* p_sys
= p_dec
->p_sys
;
488 TAB_APPEND( p_sys
->i_styles
, p_sys
->pp_styles
, p_ttml_style
);
489 i_type
= xml_ReaderNextNode( p_reader
, &psz_node_name
);
492 i_type
= xml_ReaderNextNode( p_reader
, &psz_node_name
);
493 }while( i_type
!= XML_READER_ENDELEM
|| ( strcasecmp( psz_node_name
, "head" ) && strcasecmp( psz_node_name
, "tt:head" ) ) );
495 xml_ReaderDelete( p_reader
);
496 vlc_stream_Delete( p_stream
);
499 static text_segment_t
*ParseTTMLSubtitles( decoder_t
*p_dec
, subpicture_updater_sys_t
*p_update_sys
, char *psz_subtitle
)
501 stream_t
* p_sub
= NULL
;
502 xml_reader_t
* p_xml_reader
= NULL
;
503 text_segment_t
* p_first_segment
= NULL
;
504 text_segment_t
* p_current_segment
= NULL
;
505 style_stack_t
* p_style_stack
= NULL
;
506 ttml_style_t
* p_style
= NULL
;
508 p_sub
= vlc_stream_MemoryNew( p_dec
, (uint8_t*)psz_subtitle
, strlen( psz_subtitle
), true );
509 if( unlikely( p_sub
== NULL
) )
512 p_xml_reader
= xml_ReaderCreate( p_dec
, p_sub
);
513 if( unlikely( p_xml_reader
== NULL
) )
515 vlc_stream_Delete( p_sub
);
522 i_type
= xml_ReaderNextNode( p_xml_reader
, &node
);
523 while( i_type
!= XML_READER_NONE
&& i_type
> 0 )
526 * We parse the styles and put them on the style stack
527 * until we reach a text node.
529 if( i_type
== XML_READER_STARTELEM
&& ( !strcasecmp( node
, "p" ) || !strcasecmp( node
, "tt:p" ) ||
530 !strcasecmp( node
, "span") || !strcasecmp( node
, "tt:span") ) )
532 p_style
= ParseTTMLStyle( p_dec
, p_xml_reader
, node
);
533 if( unlikely( p_style
== NULL
) )
536 if( p_style_stack
!= NULL
&& p_style_stack
->p_style
!= NULL
)
537 MergeTTMLStyle( p_style
, p_style_stack
->p_style
);
539 if( PushStyle( &p_style_stack
, p_style
) == false )
543 else if( i_type
== XML_READER_TEXT
)
546 * Once we have a text node, we create a segment, apply the
547 * latest style put on the style stack and fill it with the
548 * content of the node.
550 text_segment_t
* p_segment
= text_segment_New( NULL
);
551 if( unlikely( p_segment
== NULL
) )
554 p_segment
->psz_text
= strdup( node
);
555 if( unlikely( p_segment
->psz_text
== NULL
) )
557 text_segment_Delete( p_segment
);
561 vlc_xml_decode( p_segment
->psz_text
);
562 if( p_segment
->style
== NULL
&& p_style_stack
== NULL
)
564 p_segment
->style
= text_style_Create( STYLE_NO_DEFAULTS
);
566 else if( p_segment
->style
== NULL
)
568 p_segment
->style
= CurrentStyle( p_style_stack
);
569 if( p_segment
->style
->f_font_relsize
&& !p_segment
->style
->i_font_size
)
570 p_segment
->style
->i_font_size
= (int)( ( p_segment
->style
->f_font_relsize
* STYLE_DEFAULT_FONT_SIZE
/ 100 ) + 0.5 );
572 if( p_style_stack
->p_style
->i_margin_h
)
573 p_update_sys
->x
= p_style_stack
->p_style
->i_margin_h
;
575 p_update_sys
->x
= p_style_stack
->p_style
->i_margin_percent_h
;
577 if( p_style_stack
->p_style
->i_margin_v
)
578 p_update_sys
->y
= p_style_stack
->p_style
->i_margin_v
;
580 p_update_sys
->y
= p_style_stack
->p_style
->i_margin_percent_v
;
582 p_update_sys
->align
|= p_style_stack
->p_style
->i_align
;
584 if( p_first_segment
== NULL
)
586 p_first_segment
= p_segment
;
587 p_current_segment
= p_segment
;
589 else if( p_current_segment
->psz_text
!= NULL
)
591 p_current_segment
->p_next
= p_segment
;
592 p_current_segment
= p_segment
;
597 * If p_first_segment isn't NULL but p_current_segment->psz_text is NULL
598 * this means that something went wrong in the decoding of the
599 * first segment text:
601 * Indeed, to allocate p_first_segment ( aka non NULL ), we must have
602 * - i_type == XML_READER_TEXT
603 * - passed the allocation of p_segment->psz_text without any error
605 * This would mean that vlc_xml_decode failed and p_first_segment->psz_text
608 text_segment_Delete( p_segment
);
612 else if( i_type
== XML_READER_ENDELEM
&& ( !strcasecmp( node
, "span" ) || !strcasecmp( node
, "tt:span" ) ) )
614 if( p_style_stack
->p_next
)
615 PopStyle( &p_style_stack
);
617 else if( i_type
== XML_READER_ENDELEM
&& ( !strcasecmp( node
, "p" ) || !strcasecmp( node
, "tt:p" ) ) )
619 PopStyle( &p_style_stack
);
620 p_current_segment
->p_next
= NULL
;
622 else if( i_type
== XML_READER_STARTELEM
&& !strcasecmp( node
, "br" ) )
624 if( p_current_segment
!= NULL
&& p_current_segment
->psz_text
!= NULL
)
626 char* psz_text
= NULL
;
627 if( asprintf( &psz_text
, "%s\n", p_current_segment
->psz_text
) != -1 )
629 free( p_current_segment
->psz_text
);
630 p_current_segment
->psz_text
= psz_text
;
634 i_type
= xml_ReaderNextNode( p_xml_reader
, &node
);
636 ClearStack( p_style_stack
);
637 xml_ReaderDelete( p_xml_reader
);
638 vlc_stream_Delete( p_sub
);
640 return p_first_segment
;
643 text_segment_ChainDelete( p_first_segment
);
644 ClearStack( p_style_stack
);
645 xml_ReaderDelete( p_xml_reader
);
646 vlc_stream_Delete( p_sub
);
650 static subpicture_t
*ParseText( decoder_t
*p_dec
, block_t
*p_block
)
652 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
653 subpicture_t
*p_spu
= NULL
;
654 char *psz_subtitle
= NULL
;
656 if( p_block
->i_flags
& BLOCK_FLAG_CORRUPTED
)
659 /* We cannot display a subpicture with no date */
660 if( p_block
->i_pts
<= VLC_TS_INVALID
)
662 msg_Warn( p_dec
, "subtitle without a date" );
666 /* Check validity of packet data */
667 /* An "empty" line containing only \0 can be used to force
668 and ephemer picture from the screen */
670 if( p_block
->i_buffer
< 1 )
672 msg_Warn( p_dec
, "no subtitle data" );
676 psz_subtitle
= malloc( p_block
->i_buffer
);
677 if( unlikely( psz_subtitle
== NULL
) )
679 memcpy( psz_subtitle
, p_block
->p_buffer
, p_block
->i_buffer
);
681 /* Create the subpicture unit */
682 p_spu
= decoder_NewSubpictureText( p_dec
);
685 free( psz_subtitle
);
688 p_spu
->i_start
= p_block
->i_pts
;
689 p_spu
->i_stop
= p_block
->i_pts
+ p_block
->i_length
;
690 p_spu
->b_ephemer
= (p_block
->i_length
== 0);
691 p_spu
->b_absolute
= false;
693 subpicture_updater_sys_t
*p_spu_sys
= p_spu
->updater
.p_sys
;
695 p_spu_sys
->align
= SUBPICTURE_ALIGN_BOTTOM
| p_sys
->i_align
;
696 p_spu_sys
->p_segments
= ParseTTMLSubtitles( p_dec
, p_spu_sys
, psz_subtitle
);
697 free( psz_subtitle
);
704 /****************************************************************************
705 * DecodeBlock: the whole thing
706 ****************************************************************************/
707 static subpicture_t
*DecodeBlock( decoder_t
*p_dec
, block_t
**pp_block
)
709 if( !pp_block
|| *pp_block
== NULL
)
712 block_t
* p_block
= *pp_block
;
713 subpicture_t
*p_spu
= ParseText( p_dec
, p_block
);
715 block_Release( p_block
);
721 /*****************************************************************************
722 * OpenDecoder: probe the decoder and return score
723 *****************************************************************************/
724 static int OpenDecoder( vlc_object_t
*p_this
)
726 decoder_t
*p_dec
= (decoder_t
*)p_this
;
727 decoder_sys_t
*p_sys
;
729 if( p_dec
->fmt_in
.i_codec
!= VLC_CODEC_TTML
)
732 /* Allocate the memory needed to store the decoder's structure */
733 p_dec
->p_sys
= p_sys
= calloc( 1, sizeof( *p_sys
) );
734 if( unlikely( p_sys
== NULL
) )
737 if( p_dec
->fmt_in
.p_extra
!= NULL
&& p_dec
->fmt_in
.i_extra
> 0 )
738 ParseTTMLStyles( p_dec
);
740 p_dec
->pf_decode_sub
= DecodeBlock
;
741 p_dec
->fmt_out
.i_cat
= SPU_ES
;
742 p_sys
->i_align
= var_InheritInteger( p_dec
, "ttml-align" );
747 /*****************************************************************************
748 * CloseDecoder: clean up the decoder
749 *****************************************************************************/
750 static void CloseDecoder( vlc_object_t
*p_this
)
752 decoder_t
*p_dec
= (decoder_t
*)p_this
;
753 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
755 for( size_t i
= 0; i
< p_sys
->i_styles
; ++i
)
757 free( p_sys
->pp_styles
[i
]->psz_styleid
);
758 text_style_Delete( p_sys
->pp_styles
[i
]->font_style
);
759 free( p_sys
->pp_styles
[i
] );
761 TAB_CLEAN( p_sys
->i_styles
, p_sys
->pp_styles
);