1 /*****************************************************************************
2 * webvtt.c: WEBVTT text demuxer (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_demux.h>
31 #include <vlc_memstream.h>
33 #include "../codec/webvtt/webvtt.h"
35 /*****************************************************************************
37 *****************************************************************************/
50 int i_next_block_flags
;
51 vlc_tick_t i_next_demux_time
;
57 } regions_headers
, styles_headers
;
61 webvtt_cue_t
*p_array
;
68 struct index_entry_s
*p_array
;
74 webvtt_text_parser_t
*p_streamparser
;
77 #define WEBVTT_PREALLOC 64
79 /*****************************************************************************
81 *****************************************************************************/
82 static int cue_Compare( const void *a_
, const void *b_
)
84 webvtt_cue_t
*a
= (webvtt_cue_t
*)a_
;
85 webvtt_cue_t
*b
= (webvtt_cue_t
*)b_
;
86 if( a
->i_start
== b
->i_start
)
88 if( a
->i_stop
> b
->i_stop
)
91 return ( a
->i_stop
< b
->i_stop
) ? 1 : 0;
93 else return a
->i_start
< b
->i_start
? -1 : 1;
96 static block_t
*ConvertWEBVTT( const webvtt_cue_t
*p_cue
, bool b_continued
)
98 struct vlc_memstream stream
;
100 if( vlc_memstream_open( &stream
) )
103 const size_t paylsize
= 8 + strlen( p_cue
->psz_text
);
104 const size_t idensize
= (p_cue
->psz_id
) ? 8 + strlen( p_cue
->psz_id
) : 0;
105 const size_t attrsize
= (p_cue
->psz_attrs
) ? 8 + strlen( p_cue
->psz_attrs
) : 0;
106 const size_t vttcsize
= 8 + paylsize
+ attrsize
+ idensize
;
108 uint8_t vttcbox
[8] = { 0, 0, 0, 0, 'v', 't', 't', 'c' };
111 SetDWBE( vttcbox
, vttcsize
);
112 vlc_memstream_write( &stream
, vttcbox
, 8 );
116 uint8_t idenbox
[8] = { 0, 0, 0, 0, 'i', 'd', 'e', 'n' };
117 SetDWBE( idenbox
, idensize
);
118 vlc_memstream_write( &stream
, idenbox
, 8 );
119 vlc_memstream_write( &stream
, p_cue
->psz_id
, idensize
- 8 );
122 if( p_cue
->psz_attrs
)
124 uint8_t attrbox
[8] = { 0, 0, 0, 0, 's', 't', 't', 'g' };
125 SetDWBE( attrbox
, attrsize
);
126 vlc_memstream_write( &stream
, attrbox
, 8 );
127 vlc_memstream_write( &stream
, p_cue
->psz_attrs
, attrsize
- 8 );
130 uint8_t paylbox
[8] = { 0, 0, 0, 0, 'p', 'a', 'y', 'l' };
131 SetDWBE( paylbox
, paylsize
);
132 vlc_memstream_write( &stream
, paylbox
, 8 );
133 vlc_memstream_write( &stream
, p_cue
->psz_text
, paylsize
- 8 );
135 if( vlc_memstream_close( &stream
) == VLC_SUCCESS
)
136 return block_heap_Alloc( stream
.ptr
, stream
.length
);
141 static void memstream_Append( struct vlc_memstream
*ms
, const char *psz
)
143 if( ms
->stream
!= NULL
)
145 vlc_memstream_puts( ms
, psz
);
146 vlc_memstream_putc( ms
, '\n' );
150 static void memstream_Grab( struct vlc_memstream
*ms
, void **pp
, size_t *pi
)
152 if( ms
->stream
!= NULL
&& vlc_memstream_close( ms
) == VLC_SUCCESS
)
154 if( ms
->length
== 0 )
164 /*****************************************************************************
165 * Seekable demux Open() parser callbacks
166 *****************************************************************************/
170 struct vlc_memstream regions
, styles
;
174 static webvtt_cue_t
* ParserGetCueHandler( void *priv
)
176 struct callback_ctx
*ctx
= (struct callback_ctx
*) priv
;
177 demux_sys_t
*p_sys
= ctx
->p_demux
->p_sys
;
178 /* invalid recycled cue */
179 if( p_sys
->cues
.i_count
&&
180 p_sys
->cues
.p_array
[p_sys
->cues
.i_count
- 1].psz_text
== NULL
)
182 return &p_sys
->cues
.p_array
[p_sys
->cues
.i_count
- 1];
185 if( p_sys
->cues
.i_alloc
<= p_sys
->cues
.i_count
&&
186 (SIZE_MAX
/ sizeof(webvtt_cue_t
)) - WEBVTT_PREALLOC
> p_sys
->cues
.i_alloc
)
188 webvtt_cue_t
*p_realloc
= realloc( p_sys
->cues
.p_array
,
189 sizeof(webvtt_cue_t
) * ( p_sys
->cues
.i_alloc
+ WEBVTT_PREALLOC
) );
192 p_sys
->cues
.p_array
= p_realloc
;
193 p_sys
->cues
.i_alloc
+= WEBVTT_PREALLOC
;
197 if( p_sys
->cues
.i_alloc
> p_sys
->cues
.i_count
)
198 return &p_sys
->cues
.p_array
[p_sys
->cues
.i_count
++];
203 static void ParserCueDoneHandler( void *priv
, webvtt_cue_t
*p_cue
)
205 struct callback_ctx
*ctx
= (struct callback_ctx
*) priv
;
206 demux_sys_t
*p_sys
= ctx
->p_demux
->p_sys
;
207 if( p_cue
->psz_text
== NULL
)
209 webvtt_cue_Clean( p_cue
);
210 webvtt_cue_Init( p_cue
);
213 if( p_cue
->i_stop
> p_sys
->i_length
)
214 p_sys
->i_length
= p_cue
->i_stop
;
215 if( p_sys
->cues
.i_count
> 0 &&
216 p_sys
->cues
.p_array
[p_sys
->cues
.i_count
- 1].i_start
!= p_cue
->i_start
)
217 ctx
->b_ordered
= false;
220 if( p_sys
->index
.i_alloc
<= p_sys
->index
.i_count
&&
221 (SIZE_MAX
/ sizeof(struct index_entry_s
)) - WEBVTT_PREALLOC
* 2 > p_sys
->index
.i_alloc
)
223 void *p_realloc
= realloc( p_sys
->index
.p_array
,
224 sizeof(struct index_entry_s
) *
225 ( p_sys
->index
.i_alloc
+ WEBVTT_PREALLOC
* 2 ) );
228 p_sys
->index
.p_array
= p_realloc
;
229 p_sys
->index
.i_alloc
+= WEBVTT_PREALLOC
* 2;
232 if( p_sys
->index
.i_alloc
> p_sys
->index
.i_count
)
234 p_sys
->index
.p_array
[p_sys
->index
.i_count
].active
= 1; /* tmp start tag */
235 p_sys
->index
.p_array
[p_sys
->index
.i_count
++].time
= p_cue
->i_start
;
236 p_sys
->index
.p_array
[p_sys
->index
.i_count
].active
= 0;
237 p_sys
->index
.p_array
[p_sys
->index
.i_count
++].time
= p_cue
->i_stop
;
241 static void ParserHeaderHandler( void *priv
, enum webvtt_header_line_e s
,
242 bool b_new
, const char *psz_line
)
245 struct callback_ctx
*ctx
= (struct callback_ctx
*) priv
;
246 if( s
== WEBVTT_HEADER_STYLE
)
247 memstream_Append( &ctx
->styles
, psz_line
);
248 else if( s
== WEBVTT_HEADER_REGION
)
249 memstream_Append( &ctx
->regions
, psz_line
);
252 /*****************************************************************************
253 * Streamed cues DemuxStream() parser callbacks
254 *****************************************************************************/
256 static webvtt_cue_t
* StreamParserGetCueHandler( void *priv
)
259 return malloc( sizeof(webvtt_cue_t
) );
262 static void StreamParserCueDoneHandler( void *priv
, webvtt_cue_t
*p_cue
)
264 demux_t
*p_demux
= (demux_t
*) priv
;
265 demux_sys_t
*p_sys
= p_demux
->p_sys
;
267 if( p_cue
->psz_text
)
269 block_t
*p_block
= ConvertWEBVTT( p_cue
, true );
272 if ( p_sys
->b_first_time
)
274 es_out_SetPCR( p_demux
->out
, p_cue
->i_start
+ VLC_TICK_0
);
275 p_sys
->b_first_time
= false;
277 p_sys
->i_next_demux_time
= p_cue
->i_start
;
279 p_block
->i_pts
= VLC_TICK_0
+ p_cue
->i_start
;
280 if( p_cue
->i_stop
>= 0 && p_cue
->i_stop
>= p_cue
->i_start
)
281 p_block
->i_length
= p_cue
->i_stop
- p_cue
->i_start
;
282 es_out_Send( p_demux
->out
, p_sys
->es
, p_block
);
283 es_out_SetPCR( p_demux
->out
, p_cue
->i_start
+ VLC_TICK_0
);
286 webvtt_cue_Clean( p_cue
);
290 /*****************************************************************************
292 *****************************************************************************/
294 static int index_Compare( const void *a_
, const void *b_
)
296 struct index_entry_s
*a
= (struct index_entry_s
*) a_
;
297 struct index_entry_s
*b
= (struct index_entry_s
*) b_
;
298 if( a
->time
== b
->time
)
300 if( a
->active
> b
->active
)
303 return b
->active
- a
->active
;
305 else return a
->time
< b
->time
? -1 : 1;
308 static size_t getIndexByTime( demux_sys_t
*p_sys
, vlc_tick_t i_time
)
310 for( size_t i
=0; i
<p_sys
->index
.i_count
; i
++ )
312 if( p_sys
->index
.p_array
[i
].time
>= i_time
)
318 static void BuildIndex( demux_t
*p_demux
)
320 demux_sys_t
*p_sys
= p_demux
->p_sys
;
322 /* Order time entries ascending, start time before end time */
323 qsort( p_sys
->index
.p_array
, p_sys
->index
.i_count
,
324 sizeof(struct index_entry_s
), index_Compare
);
326 /* Build actives count
328 TIME 14500 count 2 (1 overlap)
329 TIME 16100 count 3 (2 overlaps)
330 TIME 16100 count 2 (1 overlap.. because there next start == end)
332 TIME 18000 count 2 */
333 unsigned i_overlaps
= 0;
334 for( size_t i
=0; i
<p_sys
->index
.i_count
; i
++ )
336 if( p_sys
->index
.p_array
[i
].active
)
337 p_sys
->index
.p_array
[i
].active
= ++i_overlaps
;
339 p_sys
->index
.p_array
[i
].active
= --i_overlaps
;
343 static block_t
*demux_From( demux_t
*p_demux
, vlc_tick_t i_start
)
345 demux_sys_t
*p_sys
= p_demux
->p_sys
;
347 block_t
*p_list
= NULL
;
348 block_t
**pp_append
= &p_list
;
349 for( size_t i
=0; i
<p_sys
->cues
.i_count
; i
++ )
351 const webvtt_cue_t
*p_cue
= &p_sys
->cues
.p_array
[i
];
352 if( p_cue
->i_start
> i_start
)
356 else if( p_cue
->i_start
<= i_start
&& p_cue
->i_stop
> i_start
)
358 *pp_append
= ConvertWEBVTT( p_cue
, p_sys
->index
.i_current
> 0 );
360 pp_append
= &((*pp_append
)->p_next
);
364 return ( p_list
) ? block_ChainGather( p_list
) : NULL
;
367 static int ReadWEBVTT( demux_t
*p_demux
)
369 demux_sys_t
*p_sys
= p_demux
->p_sys
;
371 struct callback_ctx ctx
;
372 ctx
.p_demux
= p_demux
;
373 ctx
.b_ordered
= true;
375 webvtt_text_parser_t
*p_parser
=
376 webvtt_text_parser_New( &ctx
, ParserGetCueHandler
,
377 ParserCueDoneHandler
,
378 ParserHeaderHandler
);
379 if( p_parser
== NULL
)
382 (void) vlc_memstream_open( &ctx
.regions
);
383 (void) vlc_memstream_open( &ctx
.styles
);
386 while( (psz_line
= vlc_stream_ReadLine( p_demux
->s
)) )
387 webvtt_text_parser_Feed( p_parser
, psz_line
);
388 webvtt_text_parser_Feed( p_parser
, NULL
);
391 qsort( p_sys
->cues
.p_array
, p_sys
->cues
.i_count
, sizeof(webvtt_cue_t
), cue_Compare
);
393 BuildIndex( p_demux
);
395 memstream_Grab( &ctx
.regions
, &p_sys
->regions_headers
.p_data
,
396 &p_sys
->regions_headers
.i_data
);
397 memstream_Grab( &ctx
.styles
, &p_sys
->styles_headers
.p_data
,
398 &p_sys
->styles_headers
.i_data
);
400 webvtt_text_parser_Delete( p_parser
);
405 static void MakeExtradata( demux_sys_t
*p_sys
, void **p_extra
, size_t *pi_extra
)
407 struct vlc_memstream extradata
;
408 if( vlc_memstream_open( &extradata
) )
410 vlc_memstream_puts( &extradata
, "WEBVTT\n\n");
411 vlc_memstream_write( &extradata
, p_sys
->regions_headers
.p_data
,
412 p_sys
->regions_headers
.i_data
);
413 vlc_memstream_write( &extradata
, p_sys
->styles_headers
.p_data
,
414 p_sys
->styles_headers
.i_data
);
415 memstream_Grab( &extradata
, p_extra
, pi_extra
);
418 /*****************************************************************************
420 *****************************************************************************/
421 static int Control( demux_t
*p_demux
, int i_query
, va_list args
)
423 demux_sys_t
*p_sys
= p_demux
->p_sys
;
430 *va_arg( args
, bool * ) = true;
433 case DEMUX_GET_LENGTH
:
434 *(va_arg( args
, int64_t * )) = p_sys
->i_length
;
438 pi64
= va_arg( args
, int64_t * );
439 *pi64
= p_sys
->i_next_demux_time
;
443 i64
= va_arg( args
, int64_t );
445 p_sys
->index
.i_current
= getIndexByTime( p_sys
, i64
);
446 p_sys
->b_first_time
= true;
447 p_sys
->i_next_demux_time
=
448 p_sys
->index
.p_array
[p_sys
->index
.i_current
].time
;
449 p_sys
->i_next_block_flags
|= BLOCK_FLAG_DISCONTINUITY
;
453 case DEMUX_GET_POSITION
:
454 pf
= va_arg( args
, double * );
455 if( p_sys
->index
.i_current
>= p_sys
->index
.i_count
)
459 else if( p_sys
->index
.i_count
> 0 )
461 *pf
= (double) p_sys
->i_next_demux_time
/
462 (p_sys
->i_length
+ 0.5);
470 case DEMUX_SET_POSITION
:
471 f
= va_arg( args
, double );
472 if( p_sys
->cues
.i_count
)
474 i64
= f
* p_sys
->i_length
;
475 p_sys
->index
.i_current
= getIndexByTime( p_sys
, i64
);
476 p_sys
->b_first_time
= true;
477 p_sys
->i_next_demux_time
=
478 p_sys
->index
.p_array
[p_sys
->index
.i_current
].time
;
479 p_sys
->i_next_block_flags
|= BLOCK_FLAG_DISCONTINUITY
;
484 case DEMUX_SET_NEXT_DEMUX_TIME
:
485 p_sys
->b_slave
= true;
486 p_sys
->i_next_demux_time
= va_arg( args
, vlc_tick_t
) - VLC_TICK_0
;
489 case DEMUX_CAN_PAUSE
:
490 case DEMUX_SET_PAUSE_STATE
:
491 case DEMUX_CAN_CONTROL_PACE
:
492 return demux_vaControlHelper( p_demux
->s
, 0, -1, 0, 1, i_query
, args
);
494 case DEMUX_GET_PTS_DELAY
:
497 case DEMUX_GET_ATTACHMENTS
:
498 case DEMUX_GET_TITLE_INFO
:
499 case DEMUX_HAS_UNSUPPORTED_META
:
500 case DEMUX_CAN_RECORD
:
508 static int ControlStream( demux_t
*p_demux
, int i_query
, va_list args
)
510 demux_sys_t
*p_sys
= p_demux
->p_sys
;
516 pi64
= va_arg( args
, int64_t * );
517 if( p_sys
->i_next_demux_time
!= VLC_TICK_INVALID
)
519 *pi64
= p_sys
->i_next_demux_time
;
529 /*****************************************************************************
530 * Demux: Send subtitle to decoder
531 *****************************************************************************/
532 static int Demux( demux_t
*p_demux
)
534 demux_sys_t
*p_sys
= p_demux
->p_sys
;
536 vlc_tick_t i_barrier
= p_sys
->i_next_demux_time
;
538 while( p_sys
->index
.i_current
< p_sys
->index
.i_count
&&
539 p_sys
->index
.p_array
[p_sys
->index
.i_current
].time
<= i_barrier
)
541 /* Find start and end of our interval */
542 vlc_tick_t i_start_time
= p_sys
->index
.p_array
[p_sys
->index
.i_current
].time
;
543 vlc_tick_t i_end_time
= i_start_time
;
544 /* use next interval time as end time */
545 while( ++p_sys
->index
.i_current
< p_sys
->index
.i_count
)
547 if( i_start_time
!= p_sys
->index
.p_array
[p_sys
->index
.i_current
].time
)
549 i_end_time
= p_sys
->index
.p_array
[p_sys
->index
.i_current
].time
;
554 block_t
*p_block
= demux_From( p_demux
, i_start_time
);
557 p_block
->i_length
= i_end_time
- i_start_time
;
558 p_block
->i_dts
= p_block
->i_pts
= VLC_TICK_0
+ i_start_time
;
560 if( p_sys
->i_next_block_flags
)
562 p_block
->i_flags
= p_sys
->i_next_block_flags
;
563 p_sys
->i_next_block_flags
= 0;
566 if ( !p_sys
->b_slave
&& p_sys
->b_first_time
)
568 es_out_SetPCR( p_demux
->out
, p_block
->i_dts
);
569 p_sys
->b_first_time
= false;
572 es_out_Send( p_demux
->out
, p_sys
->es
, p_block
);
575 if( p_sys
->index
.i_current
< p_sys
->index
.i_count
&&
576 p_sys
->index
.p_array
[p_sys
->index
.i_current
].active
> 1 )
578 /* we'll need to clear up overlaps */
579 p_sys
->i_next_block_flags
|= BLOCK_FLAG_DISCONTINUITY
;
583 if ( !p_sys
->b_slave
)
585 es_out_SetPCR( p_demux
->out
, VLC_TICK_0
+ i_barrier
);
586 p_sys
->i_next_demux_time
+= CLOCK_FREQ
;
589 if( p_sys
->index
.i_current
>= p_sys
->index
.i_count
)
590 return VLC_DEMUXER_EOF
;
592 return VLC_DEMUXER_SUCCESS
;
595 static int DemuxStream( demux_t
*p_demux
)
597 demux_sys_t
*p_sys
= p_demux
->p_sys
;
599 char *psz_line
= vlc_stream_ReadLine( p_demux
->s
);
600 webvtt_text_parser_Feed( p_sys
->p_streamparser
, psz_line
);
602 return ( psz_line
== NULL
) ? VLC_DEMUXER_EOF
: VLC_DEMUXER_SUCCESS
;
605 /*****************************************************************************
606 * Module initializers common
607 *****************************************************************************/
608 static int ProbeWEBVTT( demux_t
*p_demux
)
610 const uint8_t *p_peek
;
611 size_t i_peek
= vlc_stream_Peek( p_demux
->s
, &p_peek
, 16 );
615 if( !memcmp( p_peek
, "\xEF\xBB\xBF", 3 ) )
618 if( ( memcmp( p_peek
, "WEBVTT", 6 ) ||
619 ( p_peek
[6] != '\n' &&
622 ( p_peek
[6] != '\r' || p_peek
[7] != '\n' ) )
623 ) && !p_demux
->obj
.force
)
625 msg_Dbg( p_demux
, "subtitle demux discarded" );
632 /*****************************************************************************
633 * Module initializers
634 *****************************************************************************/
635 int webvtt_OpenDemux ( vlc_object_t
*p_this
)
637 demux_t
*p_demux
= (demux_t
*)p_this
;
640 int i_ret
= ProbeWEBVTT( p_demux
);
641 if( i_ret
!= VLC_SUCCESS
)
644 p_demux
->pf_demux
= Demux
;
645 p_demux
->pf_control
= Control
;
647 p_demux
->p_sys
= p_sys
= calloc( 1, sizeof( demux_sys_t
) );
651 if( ReadWEBVTT( p_demux
) != VLC_SUCCESS
)
653 webvtt_CloseDemux( p_this
);
658 es_format_Init( &fmt
, SPU_ES
, VLC_CODEC_WEBVTT
);
660 MakeExtradata( p_sys
, &fmt
.p_extra
, &i_extra
);
661 fmt
.i_extra
= i_extra
;
662 p_sys
->es
= es_out_Add( p_demux
->out
, &fmt
);
663 es_format_Clean( &fmt
);
664 if( p_sys
->es
== NULL
)
666 webvtt_CloseDemux( p_this
);
673 int webvtt_OpenDemuxStream ( vlc_object_t
*p_this
)
675 demux_t
*p_demux
= (demux_t
*)p_this
;
678 int i_ret
= ProbeWEBVTT( p_demux
);
679 if( i_ret
!= VLC_SUCCESS
)
682 p_demux
->pf_demux
= DemuxStream
;
683 p_demux
->pf_control
= ControlStream
;
685 p_demux
->p_sys
= p_sys
= calloc( 1, sizeof( demux_sys_t
) );
689 p_sys
->p_streamparser
= webvtt_text_parser_New( p_demux
,
690 StreamParserGetCueHandler
,
691 StreamParserCueDoneHandler
,
693 if( !p_sys
->p_streamparser
)
695 webvtt_CloseDemux( p_this
);
700 es_format_Init( &fmt
, SPU_ES
, VLC_CODEC_WEBVTT
);
701 p_sys
->es
= es_out_Add( p_demux
->out
, &fmt
);
702 es_format_Clean( &fmt
);
703 if( p_sys
->es
== NULL
)
705 webvtt_CloseDemux( p_this
);
712 /*****************************************************************************
713 * Close: Close subtitle demux
714 *****************************************************************************/
715 void webvtt_CloseDemux( vlc_object_t
*p_this
)
717 demux_t
*p_demux
= (demux_t
*)p_this
;
718 demux_sys_t
*p_sys
= p_demux
->p_sys
;
720 for( size_t i
=0; i
< p_sys
->cues
.i_count
; i
++ )
721 webvtt_cue_Clean( &p_sys
->cues
.p_array
[i
] );
722 free( p_sys
->cues
.p_array
);
724 free( p_sys
->index
.p_array
);
726 if( p_sys
->p_streamparser
)
728 webvtt_text_parser_Feed( p_sys
->p_streamparser
, NULL
);
729 webvtt_text_parser_Delete( p_sys
->p_streamparser
);