vlc | branch: master | Francois Cartegnie <[email protected]> | Fri Oct 13 11:49:17 2017 +0200| [77aef779664c7826ba819060d85833633602c71e] | committer: Francois Cartegnie
demux: add WEBVTT demuxer > http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=77aef779664c7826ba819060d85833633602c71e --- modules/MODULES_LIST | 2 +- modules/codec/Makefile.am | 1 + modules/codec/webvtt/webvtt.c | 8 + modules/codec/webvtt/webvtt.h | 3 + modules/demux/webvtt.c | 484 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 497 insertions(+), 1 deletion(-) diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST index ad9a1ca7df..fab4b269dc 100644 --- a/modules/MODULES_LIST +++ b/modules/MODULES_LIST @@ -460,7 +460,7 @@ $Id$ * wav: Wav demuxer * wave: Wave video effect * waveout: simple audio output module for Windows - * webvtt: WEBVTT subtitles decoder + * webvtt: WEBVTT subtitles decoder and demuxer * wgl: WGL extension for OpenGL * win_hotkeys: module to catch hotkeys when application doesn't have the focus * win_msg: Windows Messages module diff --git a/modules/codec/Makefile.am b/modules/codec/Makefile.am index d735cf0ae7..1ce67dd76e 100644 --- a/modules/codec/Makefile.am +++ b/modules/codec/Makefile.am @@ -225,6 +225,7 @@ codec_LTLIBRARIES += libttml_plugin.la libwebvtt_plugin_la_SOURCES = codec/webvtt/subsvtt.c \ codec/webvtt/webvtt.c \ codec/webvtt/webvtt.h \ + demux/webvtt.c \ demux/mp4/minibox.h codec_LTLIBRARIES += libwebvtt_plugin.la diff --git a/modules/codec/webvtt/webvtt.c b/modules/codec/webvtt/webvtt.c index ddcdfc6300..3d66af02af 100644 --- a/modules/codec/webvtt/webvtt.c +++ b/modules/codec/webvtt/webvtt.c @@ -42,6 +42,14 @@ vlc_module_begin () set_callbacks( OpenDecoder, CloseDecoder ) set_category( CAT_INPUT ) set_subcategory( SUBCAT_INPUT_SCODEC ) + add_submodule() + set_shortname( "WEBVTT" ) + set_description( N_("WEBVTT subtitles parser") ) + set_capability( "demux", 3 ) + set_category( CAT_INPUT ) + set_subcategory( SUBCAT_INPUT_DEMUX ) + set_callbacks( OpenDemux, CloseDemux ) + add_shortcut( "webvtt" ) vlc_module_end () struct webvtt_text_parser_t diff --git a/modules/codec/webvtt/webvtt.h b/modules/codec/webvtt/webvtt.h index 7fd8e5c3a3..a8a5158c84 100644 --- a/modules/codec/webvtt/webvtt.h +++ b/modules/codec/webvtt/webvtt.h @@ -23,6 +23,9 @@ int OpenDecoder ( vlc_object_t * ); void CloseDecoder ( vlc_object_t * ); +int OpenDemux ( vlc_object_t * ); +void CloseDemux ( vlc_object_t * ); + typedef struct webvtt_text_parser_t webvtt_text_parser_t; enum webvtt_header_line_e diff --git a/modules/demux/webvtt.c b/modules/demux/webvtt.c new file mode 100644 index 0000000000..1876c70ae0 --- /dev/null +++ b/modules/demux/webvtt.c @@ -0,0 +1,484 @@ +/***************************************************************************** + * webvtt.c: WEBVTT text demuxer (as ISO1446-30 payload) + ***************************************************************************** + * Copyright (C) 2017 VideoLabs, VLC authors and VideoLAN + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/***************************************************************************** + * Preamble + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <vlc_common.h> +#include <vlc_demux.h> +#include <vlc_memstream.h> + +#include "../codec/webvtt/webvtt.h" + +/***************************************************************************** + * Prototypes: + *****************************************************************************/ + +struct demux_sys_t +{ + es_out_id_t *es; + bool b_slave; + bool b_first_time; + mtime_t i_next_demux_time; + mtime_t i_length; + struct + { + void *p_data; + size_t i_data; + } regions_headers, styles_headers; + + struct + { + webvtt_cue_t *p_array; + size_t i_alloc; + size_t i_count; + size_t i_current; + } cues; +}; + +static int Demux( demux_t * ); +static int Control( demux_t *, int, va_list ); + +/***************************************************************************** + * + *****************************************************************************/ +static int cue_Compare( const void *a, const void *b ) +{ + const mtime_t diff = ((webvtt_cue_t *)a)->i_start - ((webvtt_cue_t *)b)->i_start; + return (diff) ? diff / (( diff > 0 ) ? diff : -diff) : 0; +} + +struct cue_searchkey +{ + webvtt_cue_t cue; + webvtt_cue_t *p_last; +}; + +static int cue_Bsearch_Compare( const void *key, const void *other ) +{ + struct cue_searchkey *p_key = (struct cue_searchkey *) key; + webvtt_cue_t cue = *((webvtt_cue_t *) other); + p_key->p_last = (webvtt_cue_t *) other; + return cue_Compare( &p_key->cue, &cue ); +} + +static size_t cue_GetIndexByTime( demux_sys_t *p_sys, mtime_t i_time ) +{ + size_t i_index = 0; + if( p_sys->cues.p_array ) + { + struct cue_searchkey key; + key.cue.i_start = i_time; + key.p_last = NULL; + + webvtt_cue_t *p_cue = bsearch( &key, p_sys->cues.p_array, p_sys->cues.i_count, + sizeof(webvtt_cue_t), cue_Bsearch_Compare ); + if( p_cue ) + key.p_last = p_cue; + + i_index = (key.p_last - p_sys->cues.p_array); + if( cue_Compare( key.p_last, &key ) < 0 ) + i_index++; + } + return i_index; +} + +static block_t *ConvertWEBVTT( const webvtt_cue_t *p_cue, bool b_continued ) +{ + struct vlc_memstream stream; + + if( vlc_memstream_open( &stream ) ) + return NULL; + + const size_t paylsize = 8 + strlen( p_cue->psz_text ); + const size_t idensize = (p_cue->psz_id) ? 8 + strlen( p_cue->psz_id ) : 0; + const size_t attrsize = (p_cue->psz_attrs) ? 8 + strlen( p_cue->psz_attrs ) : 0; + const size_t vttcsize = 8 + paylsize + attrsize + idensize; + + uint8_t vttcbox[8] = { 0, 0, 0, 0, 'v', 't', 't', 'c' }; + if( b_continued ) + vttcbox[7] = 'x'; + SetDWBE( vttcbox, vttcsize ); + vlc_memstream_write( &stream, vttcbox, 8 ); + + if( p_cue->psz_id ) + { + uint8_t idenbox[8] = { 0, 0, 0, 0, 'i', 'd', 'e', 'n' }; + SetDWBE( idenbox, idensize ); + vlc_memstream_write( &stream, idenbox, 8 ); + vlc_memstream_write( &stream, p_cue->psz_id, idensize - 8 ); + } + + if( p_cue->psz_attrs ) + { + uint8_t attrbox[8] = { 0, 0, 0, 0, 's', 't', 't', 'g' }; + SetDWBE( attrbox, attrsize ); + vlc_memstream_write( &stream, attrbox, 8 ); + vlc_memstream_write( &stream, p_cue->psz_attrs, attrsize - 8 ); + } + + uint8_t paylbox[8] = { 0, 0, 0, 0, 'p', 'a', 'y', 'l' }; + SetDWBE( paylbox, paylsize ); + vlc_memstream_write( &stream, paylbox, 8 ); + vlc_memstream_write( &stream, p_cue->psz_text, paylsize - 8 ); + + if( vlc_memstream_close( &stream ) == VLC_SUCCESS ) + return block_heap_Alloc( stream.ptr, stream.length ); + else + return NULL; +} + +static void memstream_Append( struct vlc_memstream *ms, const char *psz ) +{ + if( ms->stream != NULL ) + { + vlc_memstream_puts( ms, psz ); + vlc_memstream_putc( ms, '\n' ); + } +} + +static void memstream_Grab( struct vlc_memstream *ms, void **pp, size_t *pi ) +{ + if( ms->stream != NULL && vlc_memstream_close( ms ) == VLC_SUCCESS ) + { + *pp = ms->ptr; + *pi = ms->length; + } +} + +struct callback_ctx +{ + demux_t *p_demux; + struct vlc_memstream regions, styles; + bool b_ordered; +}; + +static webvtt_cue_t * ParserGetCueHandler( void *priv ) +{ + struct callback_ctx *ctx = (struct callback_ctx *) priv; + demux_sys_t *p_sys = ctx->p_demux->p_sys; + if( p_sys->cues.i_alloc <= p_sys->cues.i_count ) + { + webvtt_cue_t *p_realloc = realloc( p_sys->cues.p_array, + sizeof( webvtt_cue_t ) * ( p_sys->cues.i_alloc + 64 ) ); + if( p_realloc ) + { + p_sys->cues.p_array = p_realloc; + p_sys->cues.i_alloc += 64; + } + } + + if( p_sys->cues.i_alloc > p_sys->cues.i_count ) + return &p_sys->cues.p_array[p_sys->cues.i_count++]; + + return NULL; +} + +static void ParserCueDoneHandler( void *priv, webvtt_cue_t *p_cue ) +{ + struct callback_ctx *ctx = (struct callback_ctx *) priv; + demux_sys_t *p_sys = ctx->p_demux->p_sys; + if( p_cue->i_stop > p_sys->i_length ) + p_sys->i_length = p_cue->i_stop; + if( p_sys->cues.i_count > 0 && + p_sys->cues.p_array[p_sys->cues.i_count - 1].i_start != p_cue->i_start ) + ctx->b_ordered = false; +} + +static void ParserHeaderHandler( void *priv, enum webvtt_header_line_e s, + bool b_new, const char *psz_line ) +{ + VLC_UNUSED(b_new); + struct callback_ctx *ctx = (struct callback_ctx *) priv; + if( s == WEBVTT_HEADER_STYLE ) + memstream_Append( &ctx->styles, psz_line ); + else if( s == WEBVTT_HEADER_REGION ) + memstream_Append( &ctx->regions, psz_line ); +} + +static int ReadWEBVTT( demux_t *p_demux ) +{ + demux_sys_t *p_sys = p_demux->p_sys; + + struct callback_ctx ctx; + ctx.p_demux = p_demux; + ctx.b_ordered = true; + + webvtt_text_parser_t *p_parser = + webvtt_text_parser_New( &ctx, ParserGetCueHandler, + ParserCueDoneHandler, + ParserHeaderHandler ); + if( p_parser == NULL ) + return VLC_EGENERIC; + + (void) vlc_memstream_open( &ctx.regions ); + (void) vlc_memstream_open( &ctx.styles ); + + char *psz_line; + while( (psz_line = vlc_stream_ReadLine( p_demux->s )) ) + webvtt_text_parser_Feed( p_parser, psz_line ); + webvtt_text_parser_Feed( p_parser, NULL ); + + if( !ctx.b_ordered ) + qsort( p_sys->cues.p_array, p_sys->cues.i_count, sizeof(webvtt_cue_t), cue_Compare ); + + memstream_Grab( &ctx.regions, &p_sys->regions_headers.p_data, + &p_sys->regions_headers.i_data ); + memstream_Grab( &ctx.styles, &p_sys->styles_headers.p_data, + &p_sys->styles_headers.i_data ); + + webvtt_text_parser_Delete( p_parser ); + + return VLC_SUCCESS; +} + +static void MakeExtradata( demux_sys_t *p_sys, void **p_extra, size_t *pi_extra ) +{ + struct vlc_memstream extradata; + if( vlc_memstream_open( &extradata ) ) + return; + vlc_memstream_puts( &extradata, "WEBVTT\n\n"); + vlc_memstream_write( &extradata, p_sys->regions_headers.p_data, + p_sys->regions_headers.i_data ); + vlc_memstream_write( &extradata, p_sys->styles_headers.p_data, + p_sys->styles_headers.i_data ); + memstream_Grab( &extradata, p_extra, pi_extra ); +} + +/***************************************************************************** + * Control: + *****************************************************************************/ +static int Control( demux_t *p_demux, int i_query, va_list args ) +{ + demux_sys_t *p_sys = p_demux->p_sys; + int64_t *pi64, i64; + double *pf, f; + + switch( i_query ) + { + case DEMUX_CAN_SEEK: + *va_arg( args, bool * ) = true; + return VLC_SUCCESS; + + case DEMUX_GET_LENGTH: + *(va_arg( args, int64_t * )) = p_sys->i_length; + return VLC_SUCCESS; + + case DEMUX_GET_TIME: + pi64 = va_arg( args, int64_t * ); + *pi64 = p_sys->i_next_demux_time; + return VLC_SUCCESS; + + case DEMUX_SET_TIME: + i64 = va_arg( args, int64_t ); + { + p_sys->cues.i_current = cue_GetIndexByTime( p_sys, i64 ); + p_sys->b_first_time = true; + p_sys->i_next_demux_time = + p_sys->cues.p_array[p_sys->cues.i_current].i_start; + return VLC_SUCCESS; + } + + case DEMUX_GET_POSITION: + pf = va_arg( args, double * ); + if( p_sys->cues.i_current >= p_sys->cues.i_count ) + { + *pf = 1.0; + } + else if( p_sys->cues.i_count > 0 ) + { + *pf = (double) p_sys->i_next_demux_time / + (p_sys->i_length + 0.5); + } + else + { + *pf = 0.0; + } + return VLC_SUCCESS; + + case DEMUX_SET_POSITION: + f = va_arg( args, double ); + if( p_sys->cues.i_count ) + { + i64 = f * p_sys->i_length; + p_sys->cues.i_current = cue_GetIndexByTime( p_sys, i64 ); + p_sys->b_first_time = true; + p_sys->i_next_demux_time = + p_sys->cues.p_array[p_sys->cues.i_current].i_start; + return VLC_SUCCESS; + } + break; + + case DEMUX_SET_NEXT_DEMUX_TIME: + p_sys->b_slave = true; + p_sys->i_next_demux_time = va_arg( args, int64_t ) - VLC_TS_0; + return VLC_SUCCESS; + + case DEMUX_GET_PTS_DELAY: + case DEMUX_GET_FPS: + case DEMUX_GET_META: + case DEMUX_GET_ATTACHMENTS: + case DEMUX_GET_TITLE_INFO: + case DEMUX_HAS_UNSUPPORTED_META: + case DEMUX_CAN_RECORD: + default: + break; + + } + return VLC_EGENERIC; +} + +/***************************************************************************** + * Demux: Send subtitle to decoder + *****************************************************************************/ +static int Demux( demux_t *p_demux ) +{ + demux_sys_t *p_sys = p_demux->p_sys; + + int64_t i_barrier = p_sys->i_next_demux_time; + while( p_sys->cues.i_current < p_sys->cues.i_count && + p_sys->cues.p_array[p_sys->cues.i_current].i_start <= i_barrier ) + { + const webvtt_cue_t *p_cue = &p_sys->cues.p_array[p_sys->cues.i_current]; + + if ( !p_sys->b_slave && p_sys->b_first_time ) + { + es_out_SetPCR( p_demux->out, VLC_TS_0 + i_barrier ); + p_sys->b_first_time = false; + } + + if( p_cue->i_start >= 0 ) + { + block_t *p_block = ConvertWEBVTT( p_cue, p_sys->cues.i_current > 0 ); + if( p_block ) + { + p_block->i_dts = + p_block->i_pts = VLC_TS_0 + p_cue->i_start; + if( p_cue->i_stop >= 0 && p_cue->i_stop >= p_cue->i_start ) + p_block->i_length = p_cue->i_stop - p_cue->i_start; + + es_out_Send( p_demux->out, p_sys->es, p_block ); + } + } + + p_sys->cues.i_current++; + } + + if ( !p_sys->b_slave ) + { + es_out_SetPCR( p_demux->out, VLC_TS_0 + i_barrier ); + p_sys->i_next_demux_time += CLOCK_FREQ / 8; + } + + if( p_sys->cues.i_current >= p_sys->cues.i_count ) + return VLC_DEMUXER_EOF; + + return VLC_DEMUXER_SUCCESS; +} + + +/***************************************************************************** + * Module initializer + *****************************************************************************/ +int OpenDemux ( vlc_object_t *p_this ) +{ + demux_t *p_demux = (demux_t*)p_this; + demux_sys_t *p_sys; + + const uint8_t *p_peek; + size_t i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 16 ); + if( i_peek < 16 ) + return VLC_EGENERIC; + + if( !memcmp( p_peek, "\xEF\xBB\xBF", 3 ) ) + p_peek += 3; + + if( ( memcmp( p_peek, "WEBVTT", 6 ) || + ( p_peek[6] != '\n' && + p_peek[6] != ' ' && + p_peek[6] != '\t' && + ( p_peek[6] != '\r' || p_peek[7] != '\n' ) ) + ) && !p_demux->obj.force ) + { + msg_Dbg( p_demux, "subtitle demux discarded" ); + return VLC_EGENERIC; + } + + p_demux->pf_demux = Demux; + p_demux->pf_control = Control; + p_demux->p_sys = p_sys = malloc( sizeof( demux_sys_t ) ); + if( p_sys == NULL ) + return VLC_ENOMEM; + + p_sys->i_next_demux_time = 0; + p_sys->i_length = 0; + p_sys->b_slave = false; + + p_sys->regions_headers.p_data = NULL; + p_sys->regions_headers.i_data = 0; + p_sys->styles_headers.p_data = NULL; + p_sys->styles_headers.i_data = 0; + + p_sys->cues.i_count = 0; + p_sys->cues.i_alloc = 0; + p_sys->cues.p_array = NULL; + p_sys->cues.i_current = 0; + + if( ReadWEBVTT( p_demux ) != VLC_SUCCESS ) + { + CloseDemux( p_this ); + return VLC_EGENERIC; + } + + es_format_t fmt; + es_format_Init( &fmt, SPU_ES, VLC_CODEC_WEBVTT ); + size_t i_extra = 0; + MakeExtradata( p_sys, &fmt.p_extra, &i_extra ); + fmt.i_extra = i_extra; + p_sys->es = es_out_Add( p_demux->out, &fmt ); + es_format_Clean( &fmt ); + if( p_sys->es == NULL ) + { + CloseDemux( p_this ); + return VLC_EGENERIC; + } + + return VLC_SUCCESS; +} + +/***************************************************************************** + * Close: Close subtitle demux + *****************************************************************************/ +void CloseDemux( vlc_object_t *p_this ) +{ + demux_t *p_demux = (demux_t*)p_this; + demux_sys_t *p_sys = p_demux->p_sys; + + for( size_t i=0; i< p_sys->cues.i_count; i++ ) + webvtt_cue_Clean( &p_sys->cues.p_array[i] ); + free( p_sys->cues.p_array ); + + free( p_sys ); +} _______________________________________________ vlc-commits mailing list [email protected] https://mailman.videolan.org/listinfo/vlc-commits
