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 struct memstream_wrap
143 struct vlc_memstream memstream
;
147 static void memstream_Append( struct memstream_wrap
*mw
, const char *psz
)
151 vlc_memstream_puts( &mw
->memstream
, psz
);
152 vlc_memstream_putc( &mw
->memstream
, '\n' );
156 static void memstream_Grab( struct memstream_wrap
*mw
, void **pp
, size_t *pi
)
158 if( mw
->b_opened
&& vlc_memstream_close( &mw
->memstream
) == VLC_SUCCESS
)
160 if( mw
->memstream
.length
== 0 )
162 free( mw
->memstream
.ptr
);
163 mw
->memstream
.ptr
= NULL
;
165 *pp
= mw
->memstream
.ptr
;
166 *pi
= mw
->memstream
.length
;
170 /*****************************************************************************
171 * Seekable demux Open() parser callbacks
172 *****************************************************************************/
176 struct memstream_wrap regions
, styles
;
180 static webvtt_cue_t
* ParserGetCueHandler( void *priv
)
182 struct callback_ctx
*ctx
= (struct callback_ctx
*) priv
;
183 demux_sys_t
*p_sys
= ctx
->p_demux
->p_sys
;
184 /* invalid recycled cue */
185 if( p_sys
->cues
.i_count
&&
186 p_sys
->cues
.p_array
[p_sys
->cues
.i_count
- 1].psz_text
== NULL
)
188 return &p_sys
->cues
.p_array
[p_sys
->cues
.i_count
- 1];
191 if( p_sys
->cues
.i_alloc
<= p_sys
->cues
.i_count
&&
192 (SIZE_MAX
/ sizeof(webvtt_cue_t
)) - WEBVTT_PREALLOC
> p_sys
->cues
.i_alloc
)
194 webvtt_cue_t
*p_realloc
= realloc( p_sys
->cues
.p_array
,
195 sizeof(webvtt_cue_t
) * ( p_sys
->cues
.i_alloc
+ WEBVTT_PREALLOC
) );
198 p_sys
->cues
.p_array
= p_realloc
;
199 p_sys
->cues
.i_alloc
+= WEBVTT_PREALLOC
;
203 if( p_sys
->cues
.i_alloc
> p_sys
->cues
.i_count
)
204 return &p_sys
->cues
.p_array
[p_sys
->cues
.i_count
++];
209 static void ParserCueDoneHandler( void *priv
, webvtt_cue_t
*p_cue
)
211 struct callback_ctx
*ctx
= (struct callback_ctx
*) priv
;
212 demux_sys_t
*p_sys
= ctx
->p_demux
->p_sys
;
213 if( p_cue
->psz_text
== NULL
)
215 webvtt_cue_Clean( p_cue
);
216 webvtt_cue_Init( p_cue
);
219 if( p_cue
->i_stop
> p_sys
->i_length
)
220 p_sys
->i_length
= p_cue
->i_stop
;
221 if( p_sys
->cues
.i_count
> 0 &&
222 p_sys
->cues
.p_array
[p_sys
->cues
.i_count
- 1].i_start
!= p_cue
->i_start
)
223 ctx
->b_ordered
= false;
226 if( p_sys
->index
.i_alloc
<= p_sys
->index
.i_count
&&
227 (SIZE_MAX
/ sizeof(struct index_entry_s
)) - WEBVTT_PREALLOC
* 2 > p_sys
->index
.i_alloc
)
229 void *p_realloc
= realloc( p_sys
->index
.p_array
,
230 sizeof(struct index_entry_s
) *
231 ( p_sys
->index
.i_alloc
+ WEBVTT_PREALLOC
* 2 ) );
234 p_sys
->index
.p_array
= p_realloc
;
235 p_sys
->index
.i_alloc
+= WEBVTT_PREALLOC
* 2;
238 if( p_sys
->index
.i_alloc
> p_sys
->index
.i_count
)
240 p_sys
->index
.p_array
[p_sys
->index
.i_count
].active
= 1; /* tmp start tag */
241 p_sys
->index
.p_array
[p_sys
->index
.i_count
++].time
= p_cue
->i_start
;
242 p_sys
->index
.p_array
[p_sys
->index
.i_count
].active
= 0;
243 p_sys
->index
.p_array
[p_sys
->index
.i_count
++].time
= p_cue
->i_stop
;
247 static void ParserHeaderHandler( void *priv
, enum webvtt_header_line_e s
,
248 bool b_new
, const char *psz_line
)
251 struct callback_ctx
*ctx
= (struct callback_ctx
*) priv
;
252 if( s
== WEBVTT_HEADER_STYLE
)
253 memstream_Append( &ctx
->styles
, psz_line
);
254 else if( s
== WEBVTT_HEADER_REGION
)
255 memstream_Append( &ctx
->regions
, psz_line
);
258 /*****************************************************************************
259 * Streamed cues DemuxStream() parser callbacks
260 *****************************************************************************/
262 static webvtt_cue_t
* StreamParserGetCueHandler( void *priv
)
265 return malloc( sizeof(webvtt_cue_t
) );
268 static void StreamParserCueDoneHandler( void *priv
, webvtt_cue_t
*p_cue
)
270 demux_t
*p_demux
= (demux_t
*) priv
;
271 demux_sys_t
*p_sys
= p_demux
->p_sys
;
273 if( p_cue
->psz_text
)
275 block_t
*p_block
= ConvertWEBVTT( p_cue
, true );
278 if ( p_sys
->b_first_time
)
280 es_out_SetPCR( p_demux
->out
, p_cue
->i_start
+ VLC_TICK_0
);
281 p_sys
->b_first_time
= false;
283 p_sys
->i_next_demux_time
= p_cue
->i_start
;
285 p_block
->i_pts
= VLC_TICK_0
+ p_cue
->i_start
;
286 if( p_cue
->i_stop
>= 0 && p_cue
->i_stop
>= p_cue
->i_start
)
287 p_block
->i_length
= p_cue
->i_stop
- p_cue
->i_start
;
288 es_out_Send( p_demux
->out
, p_sys
->es
, p_block
);
289 es_out_SetPCR( p_demux
->out
, p_cue
->i_start
+ VLC_TICK_0
);
292 webvtt_cue_Clean( p_cue
);
296 /*****************************************************************************
298 *****************************************************************************/
300 static int index_Compare( const void *a_
, const void *b_
)
302 struct index_entry_s
*a
= (struct index_entry_s
*) a_
;
303 struct index_entry_s
*b
= (struct index_entry_s
*) b_
;
304 if( a
->time
== b
->time
)
306 if( a
->active
> b
->active
)
309 return b
->active
- a
->active
;
311 else return a
->time
< b
->time
? -1 : 1;
314 static size_t getIndexByTime( demux_sys_t
*p_sys
, vlc_tick_t i_time
)
316 for( size_t i
=0; i
<p_sys
->index
.i_count
; i
++ )
318 if( p_sys
->index
.p_array
[i
].time
>= i_time
)
324 static void BuildIndex( demux_t
*p_demux
)
326 demux_sys_t
*p_sys
= p_demux
->p_sys
;
328 /* Order time entries ascending, start time before end time */
329 qsort( p_sys
->index
.p_array
, p_sys
->index
.i_count
,
330 sizeof(struct index_entry_s
), index_Compare
);
332 /* Build actives count
334 TIME 14500 count 2 (1 overlap)
335 TIME 16100 count 3 (2 overlaps)
336 TIME 16100 count 2 (1 overlap.. because there next start == end)
338 TIME 18000 count 2 */
339 unsigned i_overlaps
= 0;
340 for( size_t i
=0; i
<p_sys
->index
.i_count
; i
++ )
342 if( p_sys
->index
.p_array
[i
].active
)
343 p_sys
->index
.p_array
[i
].active
= ++i_overlaps
;
345 p_sys
->index
.p_array
[i
].active
= --i_overlaps
;
349 static block_t
*demux_From( demux_t
*p_demux
, vlc_tick_t i_start
)
351 demux_sys_t
*p_sys
= p_demux
->p_sys
;
353 block_t
*p_list
= NULL
;
354 block_t
**pp_append
= &p_list
;
355 for( size_t i
=0; i
<p_sys
->cues
.i_count
; i
++ )
357 const webvtt_cue_t
*p_cue
= &p_sys
->cues
.p_array
[i
];
358 if( p_cue
->i_start
> i_start
)
362 else if( p_cue
->i_start
<= i_start
&& p_cue
->i_stop
> i_start
)
364 *pp_append
= ConvertWEBVTT( p_cue
, p_sys
->index
.i_current
> 0 );
366 pp_append
= &((*pp_append
)->p_next
);
370 return ( p_list
) ? block_ChainGather( p_list
) : NULL
;
373 static int ReadWEBVTT( demux_t
*p_demux
)
375 demux_sys_t
*p_sys
= p_demux
->p_sys
;
377 struct callback_ctx ctx
;
378 ctx
.p_demux
= p_demux
;
379 ctx
.b_ordered
= true;
381 webvtt_text_parser_t
*p_parser
=
382 webvtt_text_parser_New( &ctx
, ParserGetCueHandler
,
383 ParserCueDoneHandler
,
384 ParserHeaderHandler
);
385 if( p_parser
== NULL
)
388 ctx
.regions
.b_opened
= !vlc_memstream_open( &ctx
.regions
.memstream
);
389 ctx
.styles
.b_opened
= !vlc_memstream_open( &ctx
.styles
.memstream
);
392 while( (psz_line
= vlc_stream_ReadLine( p_demux
->s
)) )
393 webvtt_text_parser_Feed( p_parser
, psz_line
);
394 webvtt_text_parser_Feed( p_parser
, NULL
);
397 qsort( p_sys
->cues
.p_array
, p_sys
->cues
.i_count
, sizeof(webvtt_cue_t
), cue_Compare
);
399 BuildIndex( p_demux
);
401 memstream_Grab( &ctx
.regions
, &p_sys
->regions_headers
.p_data
,
402 &p_sys
->regions_headers
.i_data
);
403 memstream_Grab( &ctx
.styles
, &p_sys
->styles_headers
.p_data
,
404 &p_sys
->styles_headers
.i_data
);
406 webvtt_text_parser_Delete( p_parser
);
411 static void MakeExtradata( demux_sys_t
*p_sys
, void **p_extra
, size_t *pi_extra
)
413 struct vlc_memstream extradata
;
414 if( vlc_memstream_open( &extradata
) )
416 vlc_memstream_puts( &extradata
, "WEBVTT\n\n");
417 vlc_memstream_write( &extradata
, p_sys
->regions_headers
.p_data
,
418 p_sys
->regions_headers
.i_data
);
419 vlc_memstream_write( &extradata
, p_sys
->styles_headers
.p_data
,
420 p_sys
->styles_headers
.i_data
);
421 if( vlc_memstream_close( &extradata
) == VLC_SUCCESS
)
423 if( extradata
.length
)
425 *p_extra
= extradata
.ptr
;
426 *pi_extra
= extradata
.length
;
428 else free( extradata
.ptr
);
432 /*****************************************************************************
434 *****************************************************************************/
435 static int Control( demux_t
*p_demux
, int i_query
, va_list args
)
437 demux_sys_t
*p_sys
= p_demux
->p_sys
;
443 *va_arg( args
, bool * ) = true;
446 case DEMUX_GET_LENGTH
:
447 *(va_arg( args
, vlc_tick_t
* )) = p_sys
->i_length
;
451 *va_arg( args
, vlc_tick_t
* ) = p_sys
->i_next_demux_time
;
455 if( p_sys
->cues
.i_count
)
457 p_sys
->index
.i_current
= getIndexByTime( p_sys
, va_arg( args
, vlc_tick_t
) );
458 p_sys
->b_first_time
= true;
459 p_sys
->i_next_demux_time
=
460 p_sys
->index
.p_array
[p_sys
->index
.i_current
].time
;
461 p_sys
->i_next_block_flags
|= BLOCK_FLAG_DISCONTINUITY
;
466 case DEMUX_GET_POSITION
:
467 pf
= va_arg( args
, double * );
468 if( p_sys
->index
.i_current
>= p_sys
->index
.i_count
)
472 else if( p_sys
->index
.i_count
> 0 )
474 *pf
= (double) p_sys
->i_next_demux_time
/
475 (p_sys
->i_length
+ VLC_TICK_FROM_MS(500));
483 case DEMUX_SET_POSITION
:
484 if( p_sys
->cues
.i_count
)
486 double f
= va_arg( args
, double );
487 p_sys
->index
.i_current
= getIndexByTime( p_sys
, p_sys
->i_length
* f
);
488 p_sys
->b_first_time
= true;
489 p_sys
->i_next_demux_time
=
490 p_sys
->index
.p_array
[p_sys
->index
.i_current
].time
;
491 p_sys
->i_next_block_flags
|= BLOCK_FLAG_DISCONTINUITY
;
496 case DEMUX_SET_NEXT_DEMUX_TIME
:
497 p_sys
->b_slave
= true;
498 p_sys
->i_next_demux_time
= va_arg( args
, vlc_tick_t
) - VLC_TICK_0
;
501 case DEMUX_CAN_PAUSE
:
502 case DEMUX_SET_PAUSE_STATE
:
503 case DEMUX_CAN_CONTROL_PACE
:
504 return demux_vaControlHelper( p_demux
->s
, 0, -1, 0, 1, i_query
, args
);
506 case DEMUX_GET_PTS_DELAY
:
509 case DEMUX_GET_ATTACHMENTS
:
510 case DEMUX_GET_TITLE_INFO
:
511 case DEMUX_HAS_UNSUPPORTED_META
:
512 case DEMUX_CAN_RECORD
:
520 static int ControlStream( demux_t
*p_demux
, int i_query
, va_list args
)
522 demux_sys_t
*p_sys
= p_demux
->p_sys
;
527 if( p_sys
->i_next_demux_time
!= VLC_TICK_INVALID
)
529 *va_arg( args
, vlc_tick_t
* ) = p_sys
->i_next_demux_time
;
539 /*****************************************************************************
540 * Demux: Send subtitle to decoder
541 *****************************************************************************/
542 static int Demux( demux_t
*p_demux
)
544 demux_sys_t
*p_sys
= p_demux
->p_sys
;
546 vlc_tick_t i_barrier
= p_sys
->i_next_demux_time
;
548 while( p_sys
->index
.i_current
< p_sys
->index
.i_count
&&
549 p_sys
->index
.p_array
[p_sys
->index
.i_current
].time
<= i_barrier
)
551 /* Find start and end of our interval */
552 vlc_tick_t i_start_time
= p_sys
->index
.p_array
[p_sys
->index
.i_current
].time
;
553 vlc_tick_t i_end_time
= i_start_time
;
554 /* use next interval time as end time */
555 while( ++p_sys
->index
.i_current
< p_sys
->index
.i_count
)
557 if( i_start_time
!= p_sys
->index
.p_array
[p_sys
->index
.i_current
].time
)
559 i_end_time
= p_sys
->index
.p_array
[p_sys
->index
.i_current
].time
;
564 block_t
*p_block
= demux_From( p_demux
, i_start_time
);
567 p_block
->i_length
= i_end_time
- i_start_time
;
568 p_block
->i_dts
= p_block
->i_pts
= VLC_TICK_0
+ i_start_time
;
570 if( p_sys
->i_next_block_flags
)
572 p_block
->i_flags
= p_sys
->i_next_block_flags
;
573 p_sys
->i_next_block_flags
= 0;
576 if ( !p_sys
->b_slave
&& p_sys
->b_first_time
)
578 es_out_SetPCR( p_demux
->out
, p_block
->i_dts
);
579 p_sys
->b_first_time
= false;
582 es_out_Send( p_demux
->out
, p_sys
->es
, p_block
);
585 if( p_sys
->index
.i_current
< p_sys
->index
.i_count
&&
586 p_sys
->index
.p_array
[p_sys
->index
.i_current
].active
> 1 )
588 /* we'll need to clear up overlaps */
589 p_sys
->i_next_block_flags
|= BLOCK_FLAG_DISCONTINUITY
;
593 if ( !p_sys
->b_slave
)
595 es_out_SetPCR( p_demux
->out
, VLC_TICK_0
+ i_barrier
);
596 p_sys
->i_next_demux_time
+= VLC_TICK_FROM_SEC(1);
599 if( p_sys
->index
.i_current
>= p_sys
->index
.i_count
)
600 return VLC_DEMUXER_EOF
;
602 return VLC_DEMUXER_SUCCESS
;
605 static int DemuxStream( demux_t
*p_demux
)
607 demux_sys_t
*p_sys
= p_demux
->p_sys
;
609 char *psz_line
= vlc_stream_ReadLine( p_demux
->s
);
610 webvtt_text_parser_Feed( p_sys
->p_streamparser
, psz_line
);
612 return ( psz_line
== NULL
) ? VLC_DEMUXER_EOF
: VLC_DEMUXER_SUCCESS
;
615 /*****************************************************************************
616 * Module initializers common
617 *****************************************************************************/
618 static int ProbeWEBVTT( demux_t
*p_demux
)
620 const uint8_t *p_peek
;
621 size_t i_peek
= vlc_stream_Peek( p_demux
->s
, &p_peek
, 16 );
625 if( !memcmp( p_peek
, "\xEF\xBB\xBF", 3 ) )
628 if( ( memcmp( p_peek
, "WEBVTT", 6 ) ||
629 ( p_peek
[6] != '\n' &&
632 ( p_peek
[6] != '\r' || p_peek
[7] != '\n' ) )
633 ) && !p_demux
->obj
.force
)
635 msg_Dbg( p_demux
, "subtitle demux discarded" );
642 /*****************************************************************************
643 * Module initializers
644 *****************************************************************************/
645 int webvtt_OpenDemux ( vlc_object_t
*p_this
)
647 demux_t
*p_demux
= (demux_t
*)p_this
;
650 int i_ret
= ProbeWEBVTT( p_demux
);
651 if( i_ret
!= VLC_SUCCESS
)
654 p_demux
->pf_demux
= Demux
;
655 p_demux
->pf_control
= Control
;
657 p_demux
->p_sys
= p_sys
= calloc( 1, sizeof( demux_sys_t
) );
661 if( ReadWEBVTT( p_demux
) != VLC_SUCCESS
)
663 webvtt_CloseDemux( p_this
);
668 es_format_Init( &fmt
, SPU_ES
, VLC_CODEC_WEBVTT
);
670 MakeExtradata( p_sys
, &fmt
.p_extra
, &i_extra
);
671 fmt
.i_extra
= i_extra
;
673 p_sys
->es
= es_out_Add( p_demux
->out
, &fmt
);
674 es_format_Clean( &fmt
);
675 if( p_sys
->es
== NULL
)
677 webvtt_CloseDemux( p_this
);
684 int webvtt_OpenDemuxStream ( vlc_object_t
*p_this
)
686 demux_t
*p_demux
= (demux_t
*)p_this
;
689 int i_ret
= ProbeWEBVTT( p_demux
);
690 if( i_ret
!= VLC_SUCCESS
)
693 p_demux
->pf_demux
= DemuxStream
;
694 p_demux
->pf_control
= ControlStream
;
696 p_demux
->p_sys
= p_sys
= calloc( 1, sizeof( demux_sys_t
) );
700 p_sys
->p_streamparser
= webvtt_text_parser_New( p_demux
,
701 StreamParserGetCueHandler
,
702 StreamParserCueDoneHandler
,
704 if( !p_sys
->p_streamparser
)
706 webvtt_CloseDemux( p_this
);
711 es_format_Init( &fmt
, SPU_ES
, VLC_CODEC_WEBVTT
);
712 p_sys
->es
= es_out_Add( p_demux
->out
, &fmt
);
713 es_format_Clean( &fmt
);
714 if( p_sys
->es
== NULL
)
716 webvtt_CloseDemux( p_this
);
723 /*****************************************************************************
724 * Close: Close subtitle demux
725 *****************************************************************************/
726 void webvtt_CloseDemux( vlc_object_t
*p_this
)
728 demux_t
*p_demux
= (demux_t
*)p_this
;
729 demux_sys_t
*p_sys
= p_demux
->p_sys
;
731 for( size_t i
=0; i
< p_sys
->cues
.i_count
; i
++ )
732 webvtt_cue_Clean( &p_sys
->cues
.p_array
[i
] );
733 free( p_sys
->cues
.p_array
);
735 free( p_sys
->index
.p_array
);
737 if( p_sys
->p_streamparser
)
739 webvtt_text_parser_Feed( p_sys
->p_streamparser
, NULL
);
740 webvtt_text_parser_Delete( p_sys
->p_streamparser
);