1 /*****************************************************************************
2 * subsvtt.c: Decoder for WEBVTT as ISO1446-30 payload
3 *****************************************************************************
4 * Copyright (C) 2017 VideoLabs, VLC authors and VideoLAN
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
21 /*****************************************************************************
23 *****************************************************************************/
29 #include <vlc_common.h>
30 #include <vlc_charset.h>
31 #include <vlc_subpicture.h>
32 #include <vlc_codec.h>
33 #include <vlc_stream.h>
34 #include <vlc_memstream.h>
37 #include "../codec/substext.h"
38 #include "../demux/mp4/minibox.h"
43 //#define SUBSVTT_DEBUG
45 /*****************************************************************************
47 *****************************************************************************/
49 typedef struct webvtt_region_t webvtt_region_t
;
50 typedef struct webvtt_dom_node_t webvtt_dom_node_t
;
51 typedef struct webvtt_dom_cue_t webvtt_dom_cue_t
;
53 #define WEBVTT_REGION_LINES_COUNT 18
54 #define WEBVTT_DEFAULT_LINE_HEIGHT_VH 5.33
55 #define WEBVTT_LINE_TO_HEIGHT_RATIO 1.06
70 enum webvtt_align_e vertical
;
73 enum webvtt_align_e linealign
;
75 enum webvtt_align_e positionalign
;
77 enum webvtt_align_e align
;
78 } webvtt_cue_settings_t
;
80 enum webvtt_node_type_e
89 #define WEBVTT_NODE_BASE_MEMBERS \
90 enum webvtt_node_type_e type;\
91 webvtt_dom_node_t *p_parent;\
92 webvtt_dom_node_t *p_next;
94 struct webvtt_region_t
96 WEBVTT_NODE_BASE_MEMBERS
99 unsigned i_lines_max_scroll
;
102 float viewport_anchor_x
;
103 float viewport_anchor_y
;
105 webvtt_dom_node_t
*p_child
;
108 struct webvtt_dom_cue_t
110 WEBVTT_NODE_BASE_MEMBERS
114 webvtt_cue_settings_t settings
;
116 webvtt_dom_node_t
*p_child
;
121 WEBVTT_NODE_BASE_MEMBERS
127 WEBVTT_NODE_BASE_MEMBERS
131 webvtt_dom_node_t
*p_child
;
136 WEBVTT_NODE_BASE_MEMBERS
137 webvtt_dom_node_t
*p_child
;
138 } webvtt_dom_video_t
;
140 struct webvtt_dom_node_t
142 WEBVTT_NODE_BASE_MEMBERS
147 webvtt_dom_video_t
*p_root
;
150 #define ATOM_iden VLC_FOURCC('i', 'd', 'e', 'n')
151 #define ATOM_payl VLC_FOURCC('p', 'a', 'y', 'l')
152 #define ATOM_sttg VLC_FOURCC('s', 't', 't', 'g')
153 #define ATOM_vttc VLC_FOURCC('v', 't', 't', 'c')
154 #define ATOM_vtte VLC_FOURCC('v', 't', 't', 'e')
155 #define ATOM_vttx VLC_FOURCC('v', 't', 't', 'x')
157 /*****************************************************************************
159 *****************************************************************************/
161 static bool parse_percent( const char *psz
, float *value
)
164 float d
= us_strtof( psz
, &psz_end
);
165 if( d
>= 0.0 && d
<= 100.0 && *psz_end
== '%' )
167 return psz_end
!= psz
;
170 static bool parse_percent_tuple( const char *psz
, float *x
, float *y
)
173 float a
= us_strtof( psz
, &psz_end
);
174 if( psz_end
!= psz
&&
175 a
>= 0.0 && a
<= 100.0 && psz_end
&& *psz_end
== '%' )
177 psz
= strchr( psz_end
, ',' );
180 float b
= us_strtof( ++psz
, &psz_end
);
181 if( psz_end
!= psz
&&
182 b
>= 0.0 && b
<= 100.0 && psz_end
&& *psz_end
== '%' )
193 static void webvtt_cue_settings_ParseTuple( webvtt_cue_settings_t
*p_settings
,
194 const char *psz_key
, const char *psz_value
)
196 if( !strcmp( psz_key
, "vertical" ) )
198 if( !strcmp( psz_value
, "rl" ) )
199 p_settings
->vertical
= WEBVTT_ALIGN_RIGHT
;
200 else if( !strcmp( psz_value
, "lr" ) )
201 p_settings
->vertical
= WEBVTT_ALIGN_LEFT
;
203 p_settings
->vertical
= WEBVTT_ALIGN_AUTO
;
205 else if( !strcmp( psz_key
, "line" ) )
207 if( strchr( psz_value
, '%' ) )
208 parse_percent( psz_value
, &p_settings
->line
);
210 const char *psz_align
= strchr( psz_value
, ',' );
213 if( !strcmp( psz_align
, "center" ) )
214 p_settings
->linealign
= WEBVTT_ALIGN_CENTER
;
215 else if( !strcmp( psz_align
, "end" ) )
216 p_settings
->linealign
= WEBVTT_ALIGN_END
;
218 p_settings
->linealign
= WEBVTT_ALIGN_START
;
221 else if( !strcmp( psz_key
, "position" ) )
223 parse_percent( psz_value
, &p_settings
->position
);
224 const char *psz_align
= strchr( psz_value
, ',' );
227 if( !strcmp( psz_align
, "line-left" ) )
228 p_settings
->linealign
= WEBVTT_ALIGN_LEFT
;
229 else if( !strcmp( psz_align
, "line-right" ) )
230 p_settings
->linealign
= WEBVTT_ALIGN_RIGHT
;
231 else if( !strcmp( psz_align
, "center" ) )
232 p_settings
->linealign
= WEBVTT_ALIGN_CENTER
;
234 p_settings
->linealign
= WEBVTT_ALIGN_AUTO
;
237 else if( !strcmp( psz_key
, "size" ) )
239 parse_percent( psz_value
, &p_settings
->size
);
241 else if( !strcmp( psz_key
, "region" ) )
243 free( p_settings
->psz_region
);
244 p_settings
->psz_region
= strdup( psz_value
);
246 else if( !strcmp( psz_key
, "align" ) )
248 if( !strcmp( psz_value
, "start" ) )
249 p_settings
->align
= WEBVTT_ALIGN_START
;
250 else if( !strcmp( psz_value
, "end" ) )
251 p_settings
->align
= WEBVTT_ALIGN_END
;
252 else if( !strcmp( psz_value
, "left" ) )
253 p_settings
->align
= WEBVTT_ALIGN_LEFT
;
254 else if( !strcmp( psz_value
, "right" ) )
255 p_settings
->align
= WEBVTT_ALIGN_RIGHT
;
257 p_settings
->align
= WEBVTT_ALIGN_CENTER
;
261 static void webvtt_cue_settings_Parse( webvtt_cue_settings_t
*p_settings
,
268 psz_tuple
= strtok_r( p_str
, " ", &p_save
);
272 const char *psz_split
= strchr( psz_tuple
, ':' );
273 if( psz_split
&& psz_split
[1] != 0 && psz_split
!= psz_tuple
)
275 char *psz_key
= strndup( psz_tuple
, psz_split
- psz_tuple
);
278 webvtt_cue_settings_ParseTuple( p_settings
, psz_key
, psz_split
+ 1 );
283 } while( psz_tuple
);
286 static void webvtt_cue_settings_Clean( webvtt_cue_settings_t
*p_settings
)
288 free( p_settings
->psz_region
);
291 static void webvtt_cue_settings_Init( webvtt_cue_settings_t
*p_settings
)
293 p_settings
->psz_region
= NULL
;
294 p_settings
->vertical
= WEBVTT_ALIGN_AUTO
;
295 p_settings
->b_snap_to_lines
= true;
296 p_settings
->line
= -1;
297 p_settings
->linealign
= WEBVTT_ALIGN_START
;
298 p_settings
->position
= -1;
299 p_settings
->positionalign
= WEBVTT_ALIGN_AUTO
;
300 p_settings
->size
= 1.0; /* 100% */
301 p_settings
->align
= WEBVTT_ALIGN_CENTER
;
304 /*****************************************************************************
306 *****************************************************************************/
308 static void webvtt_domnode_Debug( webvtt_dom_node_t
*p_node
, int i_depth
)
310 for( ; p_node
; p_node
= p_node
->p_next
)
312 for( int i
=0; i
<i_depth
; i
++) printf(" ");
313 if( p_node
->type
== NODE_TEXT
)
315 printf("TEXT %s\n", ((webvtt_dom_text_t
*)p_node
)->psz_text
);
317 else if( p_node
->type
== NODE_TAG
)
319 webvtt_dom_tag_t
*p_tag
= (webvtt_dom_tag_t
*)p_node
;
320 printf("TAG%s (%s)\n", p_tag
->psz_tag
, p_tag
->psz_attrs
);
321 webvtt_domnode_Debug( p_tag
->p_child
, i_depth
+ 1 );
323 else if( p_node
->type
== NODE_CUE
)
325 webvtt_dom_cue_t
*p_cue
= (webvtt_dom_cue_t
*)p_node
;
326 printf("CUE %s\n", p_cue
->psz_id
);
327 webvtt_domnode_Debug( p_cue
->p_child
, i_depth
+ 1 );
329 else if( p_node
->type
== NODE_REGION
)
331 webvtt_region_t
*p_region
= (webvtt_region_t
*)p_node
;
332 printf("REGION %s\n", p_region
->psz_id
);
333 webvtt_domnode_Debug( p_region
->p_child
, i_depth
+ 1 );
339 static void webvtt_domnode_ChainDelete( webvtt_dom_node_t
*p_node
);
340 static void webvtt_dom_cue_Delete( webvtt_dom_cue_t
*p_cue
);
341 static void webvtt_region_Delete( webvtt_region_t
*p_region
);
343 static void webvtt_dom_video_Delete( webvtt_dom_video_t
*p_node
)
345 webvtt_domnode_ChainDelete( p_node
->p_child
);
349 static void webvtt_dom_text_Delete( webvtt_dom_text_t
*p_node
)
351 free( p_node
->psz_text
);
355 static void webvtt_dom_tag_Delete( webvtt_dom_tag_t
*p_node
)
357 free( p_node
->psz_attrs
);
358 free( p_node
->psz_tag
);
359 webvtt_domnode_ChainDelete( p_node
->p_child
);
363 static void webvtt_domnode_AppendLast( webvtt_dom_node_t
**pp_append
,
364 webvtt_dom_node_t
*p_node
)
367 pp_append
= &((*pp_append
)->p_next
);
371 #define webvtt_domnode_AppendLast( a, b ) \
372 webvtt_domnode_AppendLast( (webvtt_dom_node_t **) a, (webvtt_dom_node_t *) b )
374 static void webvtt_domnode_ChainDelete( webvtt_dom_node_t
*p_node
)
378 webvtt_dom_node_t
*p_next
= p_node
->p_next
;
380 if( p_node
->type
== NODE_TAG
)
381 webvtt_dom_tag_Delete( (webvtt_dom_tag_t
*) p_node
);
382 else if( p_node
->type
== NODE_TEXT
)
383 webvtt_dom_text_Delete( (webvtt_dom_text_t
*) p_node
);
384 else if( p_node
->type
== NODE_CUE
)
385 webvtt_dom_cue_Delete( (webvtt_dom_cue_t
*) p_node
);
386 else if( p_node
->type
== NODE_REGION
)
387 webvtt_region_Delete( (webvtt_region_t
*) p_node
);
388 else if( p_node
->type
== NODE_VIDEO
)
389 webvtt_dom_video_Delete( (webvtt_dom_video_t
*) p_node
);
395 static webvtt_dom_video_t
* webvtt_dom_video_New( void )
397 webvtt_dom_video_t
*p_node
= calloc( 1, sizeof(*p_node
) );
399 p_node
->type
= NODE_VIDEO
;
403 static webvtt_dom_text_t
* webvtt_dom_text_New( webvtt_dom_node_t
*p_parent
)
405 webvtt_dom_text_t
*p_node
= calloc( 1, sizeof(*p_node
) );
408 p_node
->type
= NODE_TEXT
;
409 p_node
->p_parent
= p_parent
;
414 static webvtt_dom_tag_t
* webvtt_dom_tag_New( webvtt_dom_node_t
*p_parent
)
416 webvtt_dom_tag_t
*p_node
= calloc( 1, sizeof(*p_node
) );
419 p_node
->i_start
= -1;
420 p_node
->type
= NODE_TAG
;
421 p_node
->p_parent
= p_parent
;
426 static webvtt_dom_node_t
* webvtt_domnode_getParentByTag( webvtt_dom_node_t
*p_parent
,
427 const char *psz_tag
)
429 for( ; p_parent
; p_parent
= p_parent
->p_parent
)
431 if( p_parent
->type
== NODE_TAG
)
433 webvtt_dom_tag_t
*p_node
= (webvtt_dom_tag_t
*) p_parent
;
434 if( p_node
->psz_tag
&& psz_tag
&& !strcmp( p_node
->psz_tag
, psz_tag
) )
441 static const webvtt_dom_node_t
* webvtt_domnode_getFirstChild( const webvtt_dom_node_t
*p_node
)
443 webvtt_dom_node_t
*p_child
= NULL
;
444 switch( p_node
->type
)
447 p_child
= ((webvtt_dom_cue_t
*)p_node
)->p_child
;
450 p_child
= ((webvtt_region_t
*)p_node
)->p_child
;
453 p_child
= ((webvtt_dom_tag_t
*)p_node
)->p_child
;
461 static inline bool IsEndTag( const char *psz
)
463 return psz
[1] == '/';
466 /* returns first opening and last chars of next tag, only when valid */
467 static const char * FindNextTag( const char *psz
, const char **ppsz_taglast
)
469 psz
= strchr( psz
, '<' );
472 *ppsz_taglast
= strchr( psz
+ 1, '>' );
475 const size_t tagsize
= *ppsz_taglast
- psz
+ 1;
478 if( tagsize
< 2 || IsEndTag(psz
) )
479 *ppsz_taglast
= psz
= NULL
;
486 /* Points to first char of tag name and sets *ppsz_attrs to attributes */
487 static const char *SplitTag( const char *psz_tag
, size_t *pi_tag
, const char **ppsz_attrs
)
489 psz_tag
+= IsEndTag( psz_tag
) ? 2 : 1;
490 const char *p
= psz_tag
;
494 while( isalnum( *p
) )
499 while( isspace( *p
) )
506 /*****************************************************************************
508 *****************************************************************************/
509 static webvtt_dom_cue_t
* webvtt_dom_cue_New( mtime_t i_start
, mtime_t i_end
)
511 webvtt_dom_cue_t
*p_cue
= calloc( 1, sizeof(*p_cue
) );
514 p_cue
->type
= NODE_CUE
;
515 p_cue
->psz_id
= NULL
;
516 p_cue
->i_start
= i_start
;
517 p_cue
->i_stop
= i_end
;
518 p_cue
->p_child
= NULL
;
520 webvtt_cue_settings_Init( &p_cue
->settings
);
525 static void webvtt_dom_cue_ClearText( webvtt_dom_cue_t
*p_cue
)
527 webvtt_domnode_ChainDelete( p_cue
->p_child
);
528 p_cue
->p_child
= NULL
;
532 static void webvtt_dom_cue_Delete( webvtt_dom_cue_t
*p_cue
)
534 webvtt_dom_cue_ClearText( p_cue
);
535 webvtt_cue_settings_Clean( &p_cue
->settings
);
536 free( p_cue
->psz_id
);
540 /* reduces by one line */
541 static unsigned webvtt_dom_cue_Reduced( webvtt_dom_cue_t
*p_cue
)
543 if( p_cue
->i_lines
< 1 )
546 for( webvtt_dom_node_t
*p_node
= p_cue
->p_child
;
547 p_node
; p_node
= p_node
->p_next
)
549 if( p_node
->type
!= NODE_TEXT
)
551 webvtt_dom_text_t
*p_textnode
= (webvtt_dom_text_t
*) p_node
;
552 const char *nl
= strchr( p_textnode
->psz_text
, '\n' );
555 size_t i_len
= strlen( p_textnode
->psz_text
);
556 size_t i_remain
= i_len
- (nl
- p_textnode
->psz_text
);
557 char *psz_new
= strndup( nl
+ 1, i_remain
);
558 free( p_textnode
->psz_text
);
559 p_textnode
->psz_text
= psz_new
;
560 return --p_cue
->i_lines
;
564 free( p_textnode
->psz_text
);
565 p_textnode
->psz_text
= NULL
;
566 /* FIXME: probably can do a local nodes cleanup */
570 return p_cue
->i_lines
;
573 /*****************************************************************************
575 *****************************************************************************/
577 static void webvtt_region_ParseTuple( webvtt_region_t
*p_region
,
578 const char *psz_key
, const char *psz_value
)
580 if( !strcmp( psz_key
, "id" ) )
582 free( p_region
->psz_id
);
583 p_region
->psz_id
= strdup( psz_value
);
585 else if( !strcmp( psz_key
, "width" ) )
587 parse_percent( psz_value
, &p_region
->f_width
);
589 else if( !strcmp( psz_key
, "regionanchor" ) )
591 parse_percent_tuple( psz_value
, &p_region
->anchor_x
,
592 &p_region
->anchor_y
);
594 else if( !strcmp( psz_key
, "viewportanchor" ) )
596 parse_percent_tuple( psz_value
, &p_region
->viewport_anchor_x
,
597 &p_region
->viewport_anchor_y
);
599 else if( !strcmp( psz_key
, "lines" ) )
601 int i
= atoi( psz_value
);
603 p_region
->i_lines_max_scroll
= __MIN(i
, WEBVTT_REGION_LINES_COUNT
);
605 else if( !strcmp( psz_key
, "scroll" ) )
607 p_region
->b_scroll_up
= !strcmp( psz_value
, "up" );
611 static void webvtt_region_Parse( webvtt_region_t
*p_region
, char *psz_line
)
615 char *p_str
= psz_line
;
618 psz_tuple
= strtok_r( p_str
, " ", &p_save
);
622 const char *psz_split
= strchr( psz_tuple
, ':' );
623 if( psz_split
&& psz_split
[1] != 0 && psz_split
!= psz_tuple
)
625 char *psz_key
= strndup( psz_tuple
, psz_split
- psz_tuple
);
628 webvtt_region_ParseTuple( p_region
, psz_key
, psz_split
+ 1 );
633 } while( psz_tuple
);
636 static unsigned webvtt_region_CountLines( const webvtt_region_t
*p_region
)
638 unsigned i_lines
= 0;
639 for( const webvtt_dom_node_t
*p_node
= p_region
->p_child
;
640 p_node
; p_node
= p_node
->p_next
)
642 assert( p_node
->type
== NODE_CUE
);
643 if( p_node
->type
!= NODE_CUE
)
645 i_lines
+= ((const webvtt_dom_cue_t
*)p_node
)->i_lines
;
650 static void webvtt_region_ClearCues( webvtt_region_t
*p_region
)
652 webvtt_domnode_ChainDelete( p_region
->p_child
);
653 p_region
->p_child
= NULL
;
656 static void ClearCuesByTime( webvtt_dom_node_t
**pp_next
, mtime_t i_time
)
660 webvtt_dom_node_t
*p_node
= *pp_next
;
663 if( p_node
->type
== NODE_CUE
)
665 webvtt_dom_cue_t
*p_cue
= (webvtt_dom_cue_t
*)p_node
;
666 if( p_cue
->i_stop
<= i_time
)
668 *pp_next
= p_node
->p_next
;
669 p_node
->p_next
= NULL
;
670 webvtt_dom_cue_Delete( p_cue
);
674 else if( p_node
->type
== NODE_REGION
)
676 webvtt_region_t
*p_region
= (webvtt_region_t
*) p_node
;
677 ClearCuesByTime( &p_region
->p_child
, i_time
);
679 pp_next
= &p_node
->p_next
;
684 /* Remove top most line/cue for bottom insert */
685 static void webvtt_region_Reduce( webvtt_region_t
*p_region
)
687 if( p_region
->p_child
)
689 assert( p_region
->p_child
->type
== NODE_CUE
);
690 if( p_region
->p_child
->type
!= NODE_CUE
)
692 webvtt_dom_cue_t
*p_cue
= (webvtt_dom_cue_t
*)p_region
->p_child
;
693 if( p_cue
->i_lines
== 1 ||
694 webvtt_dom_cue_Reduced( p_cue
) < 1 )
696 p_region
->p_child
= p_cue
->p_next
;
697 p_cue
->p_next
= NULL
;
698 webvtt_dom_cue_Delete( p_cue
);
703 static void webvtt_region_AddCue( webvtt_region_t
*p_region
,
704 webvtt_dom_cue_t
*p_cue
)
706 webvtt_dom_node_t
**pp_add
= &p_region
->p_child
;
708 pp_add
= &((*pp_add
)->p_next
);
709 *pp_add
= (webvtt_dom_node_t
*)p_cue
;
710 p_cue
->p_parent
= (webvtt_dom_node_t
*)p_region
;
714 unsigned i_lines
= webvtt_region_CountLines( p_region
);
716 ( i_lines
> WEBVTT_REGION_LINES_COUNT
||
717 (p_region
->b_scroll_up
&& i_lines
> p_region
->i_lines_max_scroll
)) )
719 webvtt_region_Reduce( p_region
); /* scrolls up */
720 assert( webvtt_region_CountLines( p_region
) < i_lines
);
726 static void webvtt_region_Delete( webvtt_region_t
*p_region
)
728 webvtt_region_ClearCues( p_region
);
729 free( p_region
->psz_id
);
733 static webvtt_region_t
* webvtt_region_New( void )
735 webvtt_region_t
*p_region
= malloc(sizeof(*p_region
));
738 p_region
->type
= NODE_REGION
;
739 p_region
->psz_id
= NULL
;
740 p_region
->p_next
= NULL
;
741 p_region
->f_width
= 1.0; /* 100% */
742 p_region
->anchor_x
= 0;
743 p_region
->anchor_y
= 1.0; /* 100% */
744 p_region
->i_lines_max_scroll
= 3;
745 p_region
->viewport_anchor_x
= 0;
746 p_region
->viewport_anchor_y
= 1.0; /* 100% */
747 p_region
->b_scroll_up
= false;
748 p_region
->p_child
= NULL
;
753 static webvtt_region_t
* webvtt_region_GetByID( decoder_sys_t
*p_sys
,
758 for( webvtt_dom_node_t
*p_node
= p_sys
->p_root
->p_child
;
759 p_node
; p_node
= p_node
->p_next
)
761 if( p_node
->type
== NODE_REGION
)
763 webvtt_region_t
*p_region
= (webvtt_region_t
*) p_node
;
764 if( p_region
->psz_id
&& !strcmp( psz_id
, p_region
->psz_id
) )
771 /*****************************************************************************
773 *****************************************************************************/
774 static unsigned CountNewLines( const char *psz
)
778 psz
= strchr( psz
+ 1, '\n' );
782 static webvtt_dom_node_t
* CreateDomNodes( const char *psz_text
, unsigned *pi_lines
)
784 webvtt_dom_node_t
*p_head
= NULL
;
785 webvtt_dom_node_t
**pp_append
= &p_head
;
786 webvtt_dom_node_t
*p_parent
= p_head
;
791 const char *psz_taglast
;
792 const char *psz_tag
= FindNextTag( psz_text
, &psz_taglast
);
795 if( psz_tag
- psz_text
> 0 )
797 webvtt_dom_text_t
*p_node
= webvtt_dom_text_New( p_parent
);
800 p_node
->psz_text
= strndup( psz_text
, psz_tag
- psz_text
);
801 *pi_lines
+= ((*pi_lines
== 0) ? 1 : 0) + CountNewLines( p_node
->psz_text
);
802 *pp_append
= (webvtt_dom_node_t
*) p_node
;
803 pp_append
= &p_node
->p_next
;
807 if( ! IsEndTag( psz_tag
) )
809 webvtt_dom_tag_t
*p_node
= webvtt_dom_tag_New( p_parent
);
812 const char *psz_attrs
= NULL
;
814 const char *psz_name
= SplitTag( psz_tag
, &i_name
, &psz_attrs
);
815 p_node
->psz_tag
= strndup( psz_name
, i_name
);
816 if( psz_attrs
!= psz_taglast
)
817 p_node
->psz_attrs
= strndup( psz_attrs
, psz_taglast
- psz_attrs
);
818 /* <hh:mm::ss:fff> time tags */
819 if( p_node
->psz_attrs
&& isdigit(p_node
->psz_attrs
[0]) )
820 (void) webvtt_scan_time( p_node
->psz_attrs
, &p_node
->i_start
);
821 *pp_append
= (webvtt_dom_node_t
*) p_node
;
822 p_parent
= (webvtt_dom_node_t
*) p_node
;
823 pp_append
= &p_node
->p_child
;
830 const char *psz_attrs
= NULL
;
832 const char *psz_name
= SplitTag( psz_tag
, &i_name
, &psz_attrs
);
833 char *psz_tagname
= strndup( psz_name
, i_name
);
835 /* Close at matched parent node level due to unclosed tags
836 * like <b><v stuff>foo</b> */
837 p_parent
= webvtt_domnode_getParentByTag( p_parent
->p_parent
, psz_tagname
);
838 if( p_parent
) /* continue as parent next */
839 pp_append
= &p_parent
->p_next
;
840 else /* back as top node */
841 pp_append
= &p_head
->p_next
;
843 pp_append
= &((*pp_append
)->p_next
);
847 else break; /* End tag for non open tag */
849 psz_text
= psz_taglast
+ 1;
851 else /* Special case: end */
853 webvtt_dom_text_t
*p_node
= webvtt_dom_text_New( p_parent
);
856 p_node
->psz_text
= strdup( psz_text
);
857 *pi_lines
+= ((*pi_lines
== 0) ? 1 : 0) + CountNewLines( p_node
->psz_text
);
858 *pp_append
= (webvtt_dom_node_t
*) p_node
;
867 static void ProcessCue( decoder_t
*p_dec
, const char *psz
, webvtt_dom_cue_t
*p_cue
)
873 p_cue
->p_child
= CreateDomNodes( psz
, &p_cue
->i_lines
);
874 for( webvtt_dom_node_t
*p_child
= p_cue
->p_child
; p_child
; p_child
= p_child
->p_next
)
875 p_child
->p_parent
= (webvtt_dom_node_t
*)p_cue
;
877 webvtt_domnode_Debug( (webvtt_dom_node_t
*) p_cue
, 0 );
881 static text_style_t
* InheritStyles( decoder_t
*p_dec
, const webvtt_dom_node_t
*p_node
)
885 text_style_t
*p_style
= NULL
;
886 for( ; p_node
; p_node
= p_node
->p_parent
)
888 if( p_node
->type
== NODE_TAG
)
890 const webvtt_dom_tag_t
*p_tagnode
= (const webvtt_dom_tag_t
*)p_node
;
891 if ( p_tagnode
->psz_tag
== NULL
)
895 else if ( !strcmp( p_tagnode
->psz_tag
, "b" ) )
897 if( p_style
|| (p_style
= text_style_Create( STYLE_NO_DEFAULTS
)) )
899 p_style
->i_style_flags
|= STYLE_BOLD
;
900 p_style
->i_features
|= STYLE_HAS_FLAGS
;
903 else if ( !strcmp( p_tagnode
->psz_tag
, "i" ) )
905 if( p_style
|| (p_style
= text_style_Create( STYLE_NO_DEFAULTS
)) )
907 p_style
->i_style_flags
|= STYLE_ITALIC
;
908 p_style
->i_features
|= STYLE_HAS_FLAGS
;
911 else if ( !strcmp( p_tagnode
->psz_tag
, "u" ) )
913 if( p_style
|| (p_style
= text_style_Create( STYLE_NO_DEFAULTS
)) )
915 p_style
->i_style_flags
|= STYLE_UNDERLINE
;
916 p_style
->i_features
|= STYLE_HAS_FLAGS
;
919 else if ( !strcmp( p_tagnode
->psz_tag
, "v" ) && p_tagnode
->psz_attrs
)
921 if( p_style
|| (p_style
= text_style_Create( STYLE_NO_DEFAULTS
)) )
924 for( char *p
= p_tagnode
->psz_attrs
; *p
; p
++ )
926 p_style
->i_font_color
= (0x7F7F7F | a
) & 0xFFFFFF;
927 p_style
->i_features
|= STYLE_HAS_FONT_COLOR
;
935 static int GetCueTextAlignment( const webvtt_dom_cue_t
*p_cue
)
937 switch( p_cue
->settings
.align
)
939 case WEBVTT_ALIGN_LEFT
:
940 return SUBPICTURE_ALIGN_LEFT
;
941 case WEBVTT_ALIGN_RIGHT
:
942 return SUBPICTURE_ALIGN_RIGHT
;
943 case WEBVTT_ALIGN_START
:
944 return ((p_cue
->settings
.vertical
== WEBVTT_ALIGN_RIGHT
) ?
945 SUBPICTURE_ALIGN_LEFT
: SUBPICTURE_ALIGN_RIGHT
);
946 case WEBVTT_ALIGN_END
:
947 return ((p_cue
->settings
.vertical
== WEBVTT_ALIGN_RIGHT
)) ?
948 SUBPICTURE_ALIGN_RIGHT
: SUBPICTURE_ALIGN_LEFT
;
954 struct render_variables_s
956 const webvtt_region_t
*p_region
;
963 static text_segment_t
*ConvertNodesToSegments( decoder_t
*p_dec
,
964 struct render_variables_s
*p_vars
,
965 const webvtt_dom_cue_t
*p_cue
,
966 const webvtt_dom_node_t
*p_node
,
969 text_segment_t
*p_head
= NULL
;
970 text_segment_t
**pp_append
= &p_head
;
971 for( ; p_node
; p_node
= p_node
->p_next
)
974 pp_append
= &((*pp_append
)->p_next
);
976 if( p_node
->type
== NODE_TEXT
)
978 const webvtt_dom_text_t
*p_textnode
= (const webvtt_dom_text_t
*) p_node
;
979 if( p_textnode
->psz_text
== NULL
)
982 *pp_append
= text_segment_New( p_textnode
->psz_text
);
985 if( (*pp_append
)->psz_text
)
986 vlc_xml_decode( (*pp_append
)->psz_text
);
987 (*pp_append
)->style
= InheritStyles( p_dec
, p_node
);
990 else if( p_node
->type
== NODE_TAG
)
992 const webvtt_dom_tag_t
*p_tag
= (const webvtt_dom_tag_t
*)p_node
;
993 if( p_tag
->i_start
<= i_start
)
994 *pp_append
= ConvertNodesToSegments( p_dec
, p_vars
, p_cue
,
995 p_tag
->p_child
, i_start
);
1001 static text_segment_t
*ConvertCueToSegments( decoder_t
*p_dec
,
1002 struct render_variables_s
*p_vars
,
1003 const webvtt_dom_cue_t
*p_cue
,
1006 return ConvertNodesToSegments( p_dec
, p_vars
, p_cue
, p_cue
->p_child
, i_start
);
1009 static text_segment_t
* ConvertCuesToSegments( decoder_t
*p_dec
, mtime_t i_start
, mtime_t i_stop
,
1010 struct render_variables_s
*p_vars
,
1011 const webvtt_dom_cue_t
*p_cue
)
1013 text_segment_t
*p_segments
= NULL
;
1014 text_segment_t
**pp_append
= &p_segments
;
1017 for( ; p_cue
; p_cue
= (const webvtt_dom_cue_t
*) p_cue
->p_next
)
1019 assert( p_cue
->type
== NODE_CUE
);
1020 if( p_cue
->type
!= NODE_CUE
)
1023 if( p_cue
->i_start
> i_start
|| p_cue
->i_stop
<= i_start
)
1026 text_segment_t
*p_new
= ConvertCueToSegments( p_dec
, p_vars
, p_cue
, i_start
);
1030 pp_append
= &((*pp_append
)->p_next
);
1032 if( p_segments
) /* auto newlines */
1034 *pp_append
= text_segment_New( "\n" );
1036 pp_append
= &((*pp_append
)->p_next
);
1039 if( p_cue
->settings
.vertical
== WEBVTT_ALIGN_LEFT
) /* LTR */
1041 *pp_append
= text_segment_New( "\u2067" );
1043 pp_append
= &((*pp_append
)->p_next
);
1048 if( p_cue
->settings
.vertical
== WEBVTT_ALIGN_LEFT
)
1050 *pp_append
= text_segment_New( "\u2069" );
1052 pp_append
= &((*pp_append
)->p_next
);
1059 static void GetTimedTags( const webvtt_dom_node_t
*p_node
,
1060 mtime_t i_start
, mtime_t i_stop
, vlc_array_t
*p_times
)
1062 for( ; p_node
; p_node
= p_node
->p_next
)
1064 switch( p_node
->type
)
1068 const webvtt_dom_tag_t
*p_tag
= (const webvtt_dom_tag_t
*) p_node
;
1069 if( p_tag
->i_start
> -1 && p_tag
->i_start
>= i_start
&& p_tag
->i_start
< i_stop
)
1070 (void) vlc_array_append( p_times
, (void *) p_tag
);
1071 GetTimedTags( p_tag
->p_child
, i_start
, i_stop
, p_times
);
1076 GetTimedTags( webvtt_domnode_getFirstChild( p_node
),
1077 i_start
, i_stop
, p_times
);
1085 static void CreateSpuOrNewUpdaterRegion( decoder_t
*p_dec
,
1086 subpicture_t
**pp_spu
,
1087 subpicture_updater_sys_region_t
**pp_updtregion
)
1089 if( *pp_spu
== NULL
)
1091 *pp_spu
= decoder_NewSubpictureText( p_dec
);
1093 *pp_updtregion
= &(*pp_spu
)->updater
.p_sys
->region
;
1097 subpicture_updater_sys_region_t
*p_new
=
1098 SubpictureUpdaterSysRegionNew( );
1101 SubpictureUpdaterSysRegionAdd( *pp_updtregion
, p_new
);
1102 *pp_updtregion
= p_new
;
1107 static void RenderRegions( decoder_t
*p_dec
, mtime_t i_start
, mtime_t i_stop
)
1109 subpicture_t
*p_spu
= NULL
;
1110 subpicture_updater_sys_region_t
*p_updtregion
= NULL
;
1112 for( const webvtt_dom_node_t
*p_node
= p_dec
->p_sys
->p_root
->p_child
;
1113 p_node
; p_node
= p_node
->p_next
)
1115 if( p_node
->type
== NODE_REGION
)
1117 const webvtt_region_t
*p_vttregion
= (const webvtt_region_t
*) p_node
;
1119 struct render_variables_s v
;
1120 v
.p_region
= p_vttregion
;
1121 v
.i_left_offset
= p_vttregion
->anchor_x
* p_vttregion
->f_width
;
1122 v
.i_left
= p_vttregion
->viewport_anchor_x
- v
.i_left_offset
;
1123 v
.i_top_offset
= p_vttregion
->anchor_y
* p_vttregion
->i_lines_max_scroll
*
1124 WEBVTT_DEFAULT_LINE_HEIGHT_VH
/ 100.0;
1125 v
.i_top
= p_vttregion
->viewport_anchor_y
- v
.i_top_offset
;
1128 text_segment_t
*p_segments
=
1129 ConvertCuesToSegments( p_dec
, i_start
, i_stop
, &v
,
1130 (const webvtt_dom_cue_t
*)p_vttregion
->p_child
);
1134 CreateSpuOrNewUpdaterRegion( p_dec
, &p_spu
, &p_updtregion
);
1135 if( !p_spu
|| !p_updtregion
)
1137 text_segment_ChainDelete( p_segments
);
1141 p_updtregion
->align
= SUBPICTURE_ALIGN_TOP
|SUBPICTURE_ALIGN_LEFT
;
1142 p_updtregion
->inner_align
= GetCueTextAlignment( (const webvtt_dom_cue_t
*)p_vttregion
->p_child
);
1143 p_updtregion
->origin
.x
= v
.i_left
;
1144 p_updtregion
->origin
.y
= v
.i_top
;
1145 p_updtregion
->extent
.x
= p_vttregion
->f_width
;
1147 p_updtregion
->flags
= UPDT_REGION_ORIGIN_X_IS_RATIO
|UPDT_REGION_ORIGIN_Y_IS_RATIO
1148 | UPDT_REGION_EXTENT_X_IS_RATIO
;
1149 p_updtregion
->p_segments
= p_segments
;
1151 else if( p_node
->type
== NODE_CUE
) /* regionless cues */
1153 const webvtt_dom_cue_t
*p_cue
= (const webvtt_dom_cue_t
*) p_node
;
1155 struct render_variables_s v
;
1157 v
.i_left_offset
= 0.0;
1159 v
.i_top_offset
= 0.0;
1163 text_segment_t
*p_segments
= ConvertCuesToSegments( p_dec
, i_start
, i_stop
, &v
, p_cue
);
1167 CreateSpuOrNewUpdaterRegion( p_dec
, &p_spu
, &p_updtregion
);
1168 if( !p_spu
|| !p_updtregion
)
1170 text_segment_ChainDelete( p_segments
);
1174 p_updtregion
->align
= SUBPICTURE_ALIGN_BOTTOM
;
1175 p_updtregion
->inner_align
= GetCueTextAlignment( p_cue
);
1177 p_updtregion
->p_segments
= p_segments
;
1183 p_spu
->i_start
= i_start
;
1184 p_spu
->i_stop
= i_stop
;
1185 p_spu
->b_ephemer
= true; /* !important */
1186 p_spu
->b_absolute
= false;
1188 subpicture_updater_sys_t
*p_spu_sys
= p_spu
->updater
.p_sys
;
1189 p_spu_sys
->p_default_style
->f_font_relsize
= WEBVTT_DEFAULT_LINE_HEIGHT_VH
/
1190 WEBVTT_LINE_TO_HEIGHT_RATIO
;
1191 decoder_QueueSub( p_dec
, p_spu
);
1195 static int timedtagsArrayCmp( const void *a
, const void *b
)
1197 const webvtt_dom_tag_t
*ta
= *((const webvtt_dom_tag_t
**) a
);
1198 const webvtt_dom_tag_t
*tb
= *((const webvtt_dom_tag_t
**) b
);
1199 const int64_t result
= ta
->i_start
- tb
->i_start
;
1200 return result
== 0 ? 0 : result
> 0 ? 1 : -1;
1203 static void Render( decoder_t
*p_dec
, mtime_t i_start
, mtime_t i_stop
)
1205 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
1207 vlc_array_t timedtags
;
1208 vlc_array_init( &timedtags
);
1210 GetTimedTags( p_sys
->p_root
->p_child
, i_start
, i_stop
, &timedtags
);
1211 qsort( timedtags
.pp_elems
, timedtags
.i_count
, sizeof(*timedtags
.pp_elems
), timedtagsArrayCmp
);
1213 mtime_t i_substart
= i_start
;
1214 for( size_t i
=0; i
<timedtags
.i_count
; i
++ )
1216 const webvtt_dom_tag_t
*p_tag
=
1217 (const webvtt_dom_tag_t
*) vlc_array_item_at_index( &timedtags
, i
);
1218 if( p_tag
->i_start
!= i_substart
) /* might be duplicates */
1220 RenderRegions( p_dec
, i_substart
, p_tag
->i_start
);
1221 i_substart
= p_tag
->i_start
;
1224 if( i_substart
!= i_stop
)
1225 RenderRegions( p_dec
, i_substart
, i_stop
);
1227 vlc_array_clear( &timedtags
);
1230 static int ProcessISOBMFF( decoder_t
*p_dec
,
1231 const uint8_t *p_buffer
, size_t i_buffer
,
1232 mtime_t i_start
, mtime_t i_stop
)
1234 mp4_box_iterator_t it
;
1235 mp4_box_iterator_Init( &it
, p_buffer
, i_buffer
);
1236 while( mp4_box_iterator_Next( &it
) )
1238 if( it
.i_type
== ATOM_vttc
|| it
.i_type
== ATOM_vttx
)
1240 webvtt_dom_cue_t
*p_cue
= webvtt_dom_cue_New( i_start
, i_stop
);
1244 mp4_box_iterator_t vtcc
;
1245 mp4_box_iterator_Init( &vtcc
, it
.p_payload
, it
.i_payload
);
1246 while( mp4_box_iterator_Next( &vtcc
) )
1249 switch( vtcc
.i_type
)
1252 free( p_cue
->psz_id
);
1253 p_cue
->psz_id
= strndup( (char *) vtcc
.p_payload
, vtcc
.i_payload
);
1257 psz
= strndup( (char *) vtcc
.p_payload
, vtcc
.i_payload
);
1259 webvtt_cue_settings_Parse( &p_cue
->settings
, psz
);
1263 psz
= strndup( (char *) vtcc
.p_payload
, vtcc
.i_payload
);
1265 ProcessCue( p_dec
, psz
, p_cue
);
1271 webvtt_region_t
*p_region
= webvtt_region_GetByID( p_dec
->p_sys
,
1272 p_cue
->settings
.psz_region
);
1275 webvtt_region_AddCue( p_region
, p_cue
);
1276 assert( p_region
->p_child
);
1280 webvtt_domnode_AppendLast( &p_dec
->p_sys
->p_root
->p_child
, p_cue
);
1281 p_cue
->p_parent
= (webvtt_dom_node_t
*) p_dec
->p_sys
->p_root
;
1290 webvtt_region_t
*p_region
;
1294 static void ParserHeaderHandler( void *priv
, enum webvtt_header_line_e s
,
1295 bool b_new
, const char *psz_line
)
1297 struct parser_ctx
*ctx
= (struct parser_ctx
*)priv
;
1298 decoder_t
*p_dec
= ctx
->p_dec
;
1299 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
1301 if( b_new
|| !psz_line
/* commit */ )
1305 if( ctx
->p_region
->psz_id
)
1307 webvtt_domnode_AppendLast( &p_sys
->p_root
->p_child
, ctx
->p_region
);
1308 ctx
->p_region
->p_parent
= (webvtt_dom_node_t
*) p_sys
->p_root
;
1309 msg_Dbg( p_dec
, "added new region %s", ctx
->p_region
->psz_id
);
1311 /* incomplete region decl (no id at least) */
1312 else webvtt_region_Delete( ctx
->p_region
);
1313 ctx
->p_region
= NULL
;
1321 if( s
== WEBVTT_HEADER_REGION
)
1322 ctx
->p_region
= webvtt_region_New();
1327 if( s
== WEBVTT_HEADER_REGION
&& ctx
->p_region
)
1328 webvtt_region_Parse( ctx
->p_region
, (char*) psz_line
);
1331 static void LoadExtradata( decoder_t
*p_dec
)
1333 stream_t
*p_stream
= vlc_stream_MemoryNew( p_dec
,
1334 p_dec
->fmt_in
.p_extra
,
1335 p_dec
->fmt_in
.i_extra
,
1340 struct parser_ctx ctx
;
1341 ctx
.p_region
= NULL
;
1343 webvtt_text_parser_t
*p_parser
=
1344 webvtt_text_parser_New( &ctx
, NULL
, NULL
, ParserHeaderHandler
);
1348 while( (psz_line
= vlc_stream_ReadLine( p_stream
)) )
1349 webvtt_text_parser_Feed( p_parser
, psz_line
);
1350 webvtt_text_parser_Delete( p_parser
);
1351 /* commit using null */
1352 ParserHeaderHandler( &ctx
, 0, false, NULL
);
1355 vlc_stream_Delete( p_stream
);
1358 /****************************************************************************
1359 * DecodeBlock: decoder data entry point
1360 ****************************************************************************/
1361 static int DecodeBlock( decoder_t
*p_dec
, block_t
*p_block
)
1363 if( p_block
== NULL
) /* No Drain */
1364 return VLCDEC_SUCCESS
;
1366 if( p_block
->i_flags
& BLOCK_FLAG_DISCONTINUITY
)
1367 ClearCuesByTime( &p_dec
->p_sys
->p_root
->p_child
, INT64_MAX
);
1369 ClearCuesByTime( &p_dec
->p_sys
->p_root
->p_child
, p_block
->i_dts
);
1371 ProcessISOBMFF( p_dec
, p_block
->p_buffer
, p_block
->i_buffer
,
1372 p_block
->i_pts
, p_block
->i_pts
+ p_block
->i_length
);
1374 Render( p_dec
, p_block
->i_pts
, p_block
->i_pts
+ p_block
->i_length
);
1376 block_Release( p_block
);
1377 return VLCDEC_SUCCESS
;
1380 /*****************************************************************************
1381 * CloseDecoder: clean up the decoder
1382 *****************************************************************************/
1383 void CloseDecoder( vlc_object_t
*p_this
)
1385 decoder_t
*p_dec
= (decoder_t
*)p_this
;
1386 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
1388 webvtt_domnode_ChainDelete( (webvtt_dom_node_t
*) p_sys
->p_root
);
1393 /*****************************************************************************
1394 * OpenDecoder: probe the decoder and return score
1395 *****************************************************************************/
1396 int OpenDecoder( vlc_object_t
*p_this
)
1398 decoder_t
*p_dec
= (decoder_t
*)p_this
;
1399 decoder_sys_t
*p_sys
;
1401 if( p_dec
->fmt_in
.i_codec
!= VLC_CODEC_WEBVTT
)
1402 return VLC_EGENERIC
;
1404 /* Allocate the memory needed to store the decoder's structure */
1405 p_dec
->p_sys
= p_sys
= calloc( 1, sizeof( *p_sys
) );
1406 if( unlikely( p_sys
== NULL
) )
1409 p_sys
->p_root
= webvtt_dom_video_New();
1410 if( !p_sys
->p_root
)
1416 p_dec
->pf_decode
= DecodeBlock
;
1418 if( p_dec
->fmt_in
.i_extra
)
1419 LoadExtradata( p_dec
);