ttml codec: sending a duplicate of the style instead of the original
[vlc.git] / modules / codec / substtml.c
blobf20eb19c6bf96556905aed7d958befc96504baba
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 *****************************************************************************/
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
28 #include <vlc_common.h>
29 #include <vlc_plugin.h>
30 #include <vlc_modules.h>
31 #include <vlc_codec.h>
32 #include <vlc_xml.h>
33 #include <vlc_stream.h>
34 #include <vlc_text_style.h>
36 #include "substext.h"
38 #include <ctype.h>
40 #define ALIGN_TEXT N_("Subtitle justification")
41 #define ALIGN_LONGTEXT N_("Set the justification of subtitles")
43 /*****************************************************************************
44 * Module descriptor.
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 * );
51 vlc_module_begin ()
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 )
59 vlc_module_end ();
61 /*****************************************************************************
62 * Local prototypes
63 *****************************************************************************/
65 typedef struct
67 char* psz_styleid;
68 text_style_t* font_style;
69 int i_align;
70 int i_margin_h;
71 int i_margin_v;
72 int i_margin_percent_h;
73 int i_margin_percent_v;
74 } ttml_style_t;
76 struct decoder_sys_t
78 int i_align;
79 ttml_style_t** pp_styles;
80 size_t i_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 ) )
106 return 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 ) )
112 free( p_style );
113 return 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 );
120 free( p_style );
121 return NULL;
123 return p_style;
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] );
143 return NULL;
146 typedef struct style_stack_t
148 ttml_style_t* p_style;
149 struct style_stack_t* p_next;
150 } style_stack_t ;
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 ) )
156 return false;
157 p_entry->p_style = p_style;
158 p_entry->p_next = *pp_stack;
159 *pp_stack = p_entry;
160 return true;
163 static void PopStyle( style_stack_t** pp_stack )
165 if( *pp_stack == NULL )
166 return;
167 style_stack_t* p_next = (*pp_stack)->p_next;
168 free( *pp_stack );
169 *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;
177 free( p_stack );
178 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 ) )
198 return NULL;
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 );
204 return NULL;
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 ) )
223 return NULL;
225 break;
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" ) )
243 char *tmp;
244 char *value = strdup( val );
245 if( unlikely( value == NULL ) )
246 return 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 );
253 free( value );
254 break;
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 );
263 free( value );
264 break;
266 MergeTTMLStyle( p_next_style, p_style );
267 CleanupStyle( p_style );
268 p_style = p_next_style;
270 MergeTTMLStyle( p_style, p_ttml_style );
271 free( value );
272 CleanupStyle( p_ttml_style );
273 p_ttml_style = p_style;
275 else
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 );
281 break;
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 );
300 return NULL;
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;
315 else
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;
351 else
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;
359 else
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 ) )
379 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 );
385 continue;
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 );
394 else
396 p_ttml_style->i_margin_h = atoi( psz_token );
397 p_ttml_style->i_margin_percent_h = 0;
399 while( isspace( *psz_separator ) )
400 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 );
408 else
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>?
420 bool b_ok = false;
421 unsigned int color = vlc_html_color( token, &b_ok );
422 if( 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;
435 free( value );
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 );
445 return NULL;
447 return 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 ) )
454 return ;
456 xml_reader_t* p_reader = xml_ReaderCreate( p_dec, p_stream );
457 if( unlikely( p_reader == NULL ) )
459 vlc_stream_Delete( p_stream );
460 return ;
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 );
485 return;
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 ) )
510 return NULL;
512 p_xml_reader = xml_ReaderCreate( p_dec, p_sub );
513 if( unlikely( p_xml_reader == NULL ) )
515 vlc_stream_Delete( p_sub );
516 return NULL;
519 const char *node;
520 int i_type;
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 ) )
534 goto fail;
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 )
540 goto fail;
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 ) )
552 goto fail;
554 p_segment->psz_text = strdup( node );
555 if( unlikely( p_segment->psz_text == NULL ) )
557 text_segment_Delete( p_segment );
558 goto fail;
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;
574 else
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;
579 else
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;
594 else
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
606 * is NULL.
608 text_segment_Delete( p_segment );
609 goto fail;
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;
642 fail:
643 text_segment_ChainDelete( p_first_segment );
644 ClearStack( p_style_stack );
645 xml_ReaderDelete( p_xml_reader );
646 vlc_stream_Delete( p_sub );
647 return NULL;
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 )
657 return NULL;
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" );
663 return NULL;
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" );
673 return NULL;
676 psz_subtitle = malloc( p_block->i_buffer );
677 if( unlikely( psz_subtitle == NULL ) )
678 return 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 );
683 if( !p_spu )
685 free( psz_subtitle );
686 return NULL;
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 );
699 return p_spu;
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 )
710 return NULL;
712 block_t* p_block = *pp_block;
713 subpicture_t *p_spu = ParseText( p_dec, p_block );
715 block_Release( p_block );
716 *pp_block = NULL;
718 return p_spu;
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 )
730 return VLC_EGENERIC;
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 ) )
735 return VLC_ENOMEM;
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" );
744 return VLC_SUCCESS;
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 );
763 free( p_sys );