vlc | branch: master | Francois Cartegnie <[email protected]> | Thu Oct 19 17:59:42 2017 +0200| [c127c4c54f0229ae8620c4b8c4c13878c29959c2] | committer: Francois Cartegnie
codec: add WEBVTT decoder > http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=c127c4c54f0229ae8620c4b8c4c13878c29959c2 --- modules/MODULES_LIST | 1 + modules/codec/Makefile.am | 6 + modules/codec/webvtt/subsvtt.c | 1156 ++++++++++++++++++++++++++++++++++++++++ modules/codec/webvtt/webvtt.c | 249 +++++++++ modules/codec/webvtt/webvtt.h | 71 +++ modules/demux/mp4/minibox.h | 55 ++ 6 files changed, 1538 insertions(+) diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST index 1e4398e5e8..ad9a1ca7df 100644 --- a/modules/MODULES_LIST +++ b/modules/MODULES_LIST @@ -460,6 +460,7 @@ $Id$ * wav: Wav demuxer * wave: Wave video effect * waveout: simple audio output module for Windows + * webvtt: WEBVTT subtitles decoder * 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 38a4f3f959..d735cf0ae7 100644 --- a/modules/codec/Makefile.am +++ b/modules/codec/Makefile.am @@ -222,6 +222,12 @@ libttml_plugin_la_SOURCES = codec/ttml/substtml.c \ codec/ttml/ttml.h codec/ttml/ttml.c codec_LTLIBRARIES += libttml_plugin.la +libwebvtt_plugin_la_SOURCES = codec/webvtt/subsvtt.c \ + codec/webvtt/webvtt.c \ + codec/webvtt/webvtt.h \ + demux/mp4/minibox.h +codec_LTLIBRARIES += libwebvtt_plugin.la + libsvcdsub_plugin_la_SOURCES = codec/svcdsub.c codec_LTLIBRARIES += libsvcdsub_plugin.la diff --git a/modules/codec/webvtt/subsvtt.c b/modules/codec/webvtt/subsvtt.c new file mode 100644 index 0000000000..e147eae756 --- /dev/null +++ b/modules/codec/webvtt/subsvtt.c @@ -0,0 +1,1156 @@ +/***************************************************************************** + * subsvtt.c: Decoder for WEBVTT 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_charset.h> +#include <vlc_subpicture.h> +#include <vlc_codec.h> +#include <vlc_stream.h> +#include <vlc_memstream.h> +#include <assert.h> + +#include "../codec/substext.h" +#include "../demux/mp4/minibox.h" +#include "webvtt.h" + +#include <ctype.h> + +//#define SUBSVTT_DEBUG + +/***************************************************************************** + * Local prototypes + *****************************************************************************/ + +typedef struct webvtt_region_t webvtt_region_t; +typedef struct webvtt_domnode_t webvtt_domnode_t; +typedef struct webvtt_dom_cue_t webvtt_dom_cue_t; + +#define WEBVTT_REGION_LINES_COUNT 18 +#define WEBVTT_DEFAULT_LINE_HEIGHT_VH 5.33 +#define WEBVTT_LINE_TO_HEIGHT_RATIO 1.06 + +enum webvtt_align_e +{ + WEBVTT_ALIGN_AUTO, + WEBVTT_ALIGN_LEFT, + WEBVTT_ALIGN_CENTER, + WEBVTT_ALIGN_RIGHT, + WEBVTT_ALIGN_START, + WEBVTT_ALIGN_END, +}; + +typedef struct +{ + char *psz_region; + enum webvtt_align_e vertical; + bool b_snap_to_lines; + float line; + enum webvtt_align_e linealign; + float position; + enum webvtt_align_e positionalign; + float size; + enum webvtt_align_e align; +} webvtt_cue_settings_t; + +struct webvtt_dom_cue_t +{ + char *psz_id; + mtime_t i_start; + mtime_t i_stop; + webvtt_cue_settings_t settings; + webvtt_domnode_t *p_nodes; + unsigned i_lines; +}; + +struct webvtt_region_t +{ + char *psz_id; + float f_width; + unsigned i_lines_max_scroll; + float anchor_x; + float anchor_y; + float viewport_anchor_x; + float viewport_anchor_y; + bool b_scroll_up; + webvtt_dom_cue_t *p_cues[WEBVTT_REGION_LINES_COUNT]; /* worst case 1 line == 1 cue */ + webvtt_region_t *p_next; +}; + +struct webvtt_domnode_t +{ + char *psz_tag; + char *psz_attrs; + char *psz_text; + + webvtt_domnode_t *p_parent; + webvtt_domnode_t *p_next; + webvtt_domnode_t *p_child; +}; + +struct decoder_sys_t +{ + webvtt_region_t regions; + webvtt_region_t **pp_region_append; +}; + +#define ATOM_iden VLC_FOURCC('i', 'd', 'e', 'n') +#define ATOM_payl VLC_FOURCC('p', 'a', 'y', 'l') +#define ATOM_sttg VLC_FOURCC('s', 't', 't', 'g') +#define ATOM_vttc VLC_FOURCC('v', 't', 't', 'c') +#define ATOM_vtte VLC_FOURCC('v', 't', 't', 'e') +#define ATOM_vttx VLC_FOURCC('v', 't', 't', 'x') + +/***************************************************************************** + * + *****************************************************************************/ + +static bool parse_percent( const char *psz, float *value ) +{ + char *psz_end; + float d = us_strtof( psz, &psz_end ); + if( d >= 0.0 && d <= 100.0 && *psz_end == '%' ) + *value = d / 100.0; + return psz_end != psz; +} + +static bool parse_percent_tuple( const char *psz, float *x, float *y ) +{ + char *psz_end; + float a = us_strtof( psz, &psz_end ); + if( psz_end != psz && + a >= 0.0 && a <= 100.0 && psz_end && *psz_end == '%' ) + { + psz = strchr( psz_end, ',' ); + if( psz ) + { + float b = us_strtof( ++psz, &psz_end ); + if( psz_end != psz && + b >= 0.0 && b <= 100.0 && psz_end && *psz_end == '%' ) + { + *x = a / 100.0; + *y = b / 100.0; + return true; + } + } + } + return false; +} + +static void webvtt_cue_settings_ParseTuple( webvtt_cue_settings_t *p_settings, + const char *psz_key, const char *psz_value ) +{ + if( !strcmp( psz_key, "vertical" ) ) + { + if( !strcmp( psz_value, "rl" ) ) + p_settings->vertical = WEBVTT_ALIGN_RIGHT; + else if( !strcmp( psz_value, "lr" ) ) + p_settings->vertical = WEBVTT_ALIGN_LEFT; + else + p_settings->vertical = WEBVTT_ALIGN_AUTO; + } + else if( !strcmp( psz_key, "line" ) ) + { + if( strchr( psz_value, '%' ) ) + parse_percent( psz_value, &p_settings->line ); + // else /* todo */ + const char *psz_align = strchr( psz_value, ',' ); + if( psz_align++ ) + { + if( !strcmp( psz_align, "center" ) ) + p_settings->linealign = WEBVTT_ALIGN_CENTER; + else if( !strcmp( psz_align, "end" ) ) + p_settings->linealign = WEBVTT_ALIGN_END; + else + p_settings->linealign = WEBVTT_ALIGN_START; + } + } + else if( !strcmp( psz_key, "position" ) ) + { + parse_percent( psz_value, &p_settings->position ); + const char *psz_align = strchr( psz_value, ',' ); + if( psz_align++ ) + { + if( !strcmp( psz_align, "line-left" ) ) + p_settings->linealign = WEBVTT_ALIGN_LEFT; + else if( !strcmp( psz_align, "line-right" ) ) + p_settings->linealign = WEBVTT_ALIGN_RIGHT; + else if( !strcmp( psz_align, "center" ) ) + p_settings->linealign = WEBVTT_ALIGN_CENTER; + else + p_settings->linealign = WEBVTT_ALIGN_AUTO; + } + } + else if( !strcmp( psz_key, "size" ) ) + { + parse_percent( psz_value, &p_settings->size ); + } + else if( !strcmp( psz_key, "region" ) ) + { + free( p_settings->psz_region ); + p_settings->psz_region = strdup( psz_value ); + } + else if( !strcmp( psz_key, "align" ) ) + { + if( !strcmp( psz_value, "start" ) ) + p_settings->align = WEBVTT_ALIGN_START; + else if( !strcmp( psz_value, "end" ) ) + p_settings->align = WEBVTT_ALIGN_END; + else if( !strcmp( psz_value, "left" ) ) + p_settings->align = WEBVTT_ALIGN_LEFT; + else if( !strcmp( psz_value, "right" ) ) + p_settings->align = WEBVTT_ALIGN_RIGHT; + else + p_settings->align = WEBVTT_ALIGN_CENTER; + } +} + +static void webvtt_cue_settings_Parse( webvtt_cue_settings_t *p_settings, + char *p_str ) +{ + char *p_save; + char *psz_tuple; + do + { + psz_tuple = strtok_r( p_str, " ", &p_save ); + p_str = NULL; + if( psz_tuple ) + { + const char *psz_split = strchr( psz_tuple, ':' ); + if( psz_split && psz_split[1] != 0 && psz_split != psz_tuple ) + { + char *psz_key = strndup( psz_tuple, psz_split - psz_tuple ); + if( psz_key ) + { + webvtt_cue_settings_ParseTuple( p_settings, psz_key, psz_split + 1 ); + free( psz_key ); + } + } + } + } while( psz_tuple ); +} + +static void webvtt_cue_settings_Clean( webvtt_cue_settings_t *p_settings ) +{ + free( p_settings->psz_region ); +} + +static void webvtt_cue_settings_Init( webvtt_cue_settings_t *p_settings ) +{ + p_settings->psz_region = NULL; + p_settings->vertical = WEBVTT_ALIGN_AUTO; + p_settings->b_snap_to_lines = true; + p_settings->line = -1; + p_settings->linealign = WEBVTT_ALIGN_START; + p_settings->position = -1; + p_settings->positionalign = WEBVTT_ALIGN_AUTO; + p_settings->size = 1.0; /* 100% */ + p_settings->align = WEBVTT_ALIGN_CENTER; +} + +/***************************************************************************** + * + *****************************************************************************/ +#ifdef SUBSVTT_DEBUG +static void webvtt_domnode_Debug( webvtt_domnode_t *p_node, int i_depth ) +{ + for( ; p_node ; p_node = p_node->p_next ) + { + for( int i=0; i<i_depth; i++) printf(" "); + if( p_node->psz_text ) + printf("TEXT %s\n", p_node->psz_text ); + else + printf("TAG%s (%s)\n", p_node->psz_tag, p_node->psz_attrs ); + webvtt_domnode_Debug( p_node->p_child, i_depth + 1 ); + } +} +#endif +static void webvtt_domnode_RecursiveDelete( webvtt_domnode_t *p_node ) +{ + while( p_node ) + { + assert( p_node->psz_text == NULL || p_node->p_child == NULL ); + webvtt_domnode_t *p_next = p_node->p_next; + + webvtt_domnode_RecursiveDelete( p_node->p_child ); + free( p_node->psz_tag ); + free( p_node->psz_attrs ); + free( p_node->psz_text ); + free( p_node ); + + p_node = p_next; + } +} + +static webvtt_domnode_t * webvtt_domnode_New( webvtt_domnode_t *p_parent ) +{ + webvtt_domnode_t *p_node = calloc( 1, sizeof(*p_node) ); + if( p_node ) + p_node->p_parent = p_parent; + return p_node; +} + +static webvtt_domnode_t * webvtt_domnode_getParentByTag( webvtt_domnode_t *p_parent, + const char *psz_tag ) +{ + for( ; p_parent ; p_parent = p_parent->p_parent ) + { + if( p_parent->psz_tag && psz_tag && !strcmp( p_parent->psz_tag, psz_tag ) ) + break; + } + return p_parent; +} + +static inline bool IsEndTag( const char *psz ) +{ + return psz[1] == '/'; +} + +/* returns first opening and last chars of next tag, only when valid */ +static const char * FindNextTag( const char *psz, const char **ppsz_taglast ) +{ + psz = strchr( psz, '<' ); + if( psz ) + { + *ppsz_taglast = strchr( psz + 1, '>' ); + if( *ppsz_taglast ) + { + const size_t tagsize = *ppsz_taglast - psz + 1; + if( tagsize <= 3 ) + { + if( tagsize < 2 || IsEndTag(psz) ) + *ppsz_taglast = psz = NULL; + } + } else psz = NULL; + } + return psz; +} + +/* Points to first char of tag name and sets *ppsz_attrs to first of non-name */ +static const char *SplitTag( const char *psz_tag, const char **ppsz_attrs ) +{ + psz_tag += IsEndTag( psz_tag ) ? 2 : 1; + const char *p = psz_tag + 1; + while( !isblank( *p ) && !ispunct( *p ) && *p != '>' && *p != '/' ) + p++; + *ppsz_attrs = p; + return psz_tag; +} + +/***************************************************************************** + * + *****************************************************************************/ +static webvtt_dom_cue_t * webvtt_dom_cue_New( mtime_t i_start, mtime_t i_end ) +{ + webvtt_dom_cue_t *p_cue = calloc( 1, sizeof(*p_cue) ); + if( p_cue ) + { + p_cue->psz_id = NULL; + p_cue->i_start = i_start; + p_cue->i_stop = i_end; + p_cue->p_nodes = NULL; + p_cue->i_lines = 0; + webvtt_cue_settings_Init( &p_cue->settings ); + } + return p_cue; +} + +static void webvtt_dom_cue_ClearText( webvtt_dom_cue_t *p_cue ) +{ + webvtt_domnode_RecursiveDelete( p_cue->p_nodes ); + p_cue->p_nodes = NULL; + p_cue->i_lines = 0; +} + +static void webvtt_dom_cue_Delete( webvtt_dom_cue_t *p_cue ) +{ + webvtt_dom_cue_ClearText( p_cue ); + webvtt_cue_settings_Clean( &p_cue->settings ); + free( p_cue->psz_id ); + free( p_cue ); +} + +/* Returns reduced by one line cue or deletes it */ +static webvtt_dom_cue_t * webvtt_dom_cue_Reduced( webvtt_dom_cue_t *p_cue ) +{ + if( p_cue->i_lines <= 1 ) + { + webvtt_dom_cue_Delete( p_cue ); + return NULL; + } + + for( webvtt_domnode_t *p_node = p_cue->p_nodes; + p_node; p_node = p_node->p_next ) + { + if( !p_node->psz_text ) + continue; + const char *nl = strchr( p_node->psz_text, '\n' ); + if( nl ) + { + size_t i_len = strlen( p_node->psz_text ); + size_t i_remain = i_len - (nl - p_node->psz_text); + char *psz_new = strndup( nl + 1, i_remain ); + free( p_node->psz_text ); + p_node->psz_text = psz_new; + p_cue->i_lines--; + return p_cue; + } + else + { + free( p_node->psz_text ); + p_node->psz_text = NULL; + /* FIXME: probably can do a local nodes cleanup */ + } + } + + /* should not happen */ + webvtt_dom_cue_Delete( p_cue ); + return NULL; +} + +/***************************************************************************** + * + *****************************************************************************/ + +static void webvtt_region_ParseTuple( webvtt_region_t *p_region, + const char *psz_key, const char *psz_value ) +{ + if( !strcmp( psz_key, "id" ) ) + { + free( p_region->psz_id ); + p_region->psz_id = strdup( psz_value ); + } + else if( !strcmp( psz_key, "width" ) ) + { + parse_percent( psz_value, &p_region->f_width ); + } + else if( !strcmp( psz_key, "regionanchor" ) ) + { + parse_percent_tuple( psz_value, &p_region->anchor_x, + &p_region->anchor_y ); + } + else if( !strcmp( psz_key, "viewportanchor" ) ) + { + parse_percent_tuple( psz_value, &p_region->viewport_anchor_x, + &p_region->viewport_anchor_y ); + } + else if( !strcmp( psz_key, "lines" ) ) + { + int i = atoi( psz_value ); + if( i > 0 ) + p_region->i_lines_max_scroll = __MIN(i, WEBVTT_REGION_LINES_COUNT); + } + else if( !strcmp( psz_key, "scroll" ) ) + { + p_region->b_scroll_up = !strcmp( psz_value, "up" ); + } +} + +static void webvtt_region_Parse( webvtt_region_t *p_region, char *psz_line ) +{ + char *p_save; + char *psz_tuple; + char *p_str = psz_line; + do + { + psz_tuple = strtok_r( p_str, " ", &p_save ); + p_str = NULL; + if( psz_tuple ) + { + const char *psz_split = strchr( psz_tuple, ':' ); + if( psz_split && psz_split[1] != 0 && psz_split != psz_tuple ) + { + char *psz_key = strndup( psz_tuple, psz_split - psz_tuple ); + if( psz_key ) + { + webvtt_region_ParseTuple( p_region, psz_key, psz_split + 1 ); + free( psz_key ); + } + } + } + } while( psz_tuple ); +} + +static unsigned webvtt_region_CountLines( const webvtt_region_t *p_region ) +{ + unsigned i_lines = 0; + for( size_t i=0; i<WEBVTT_REGION_LINES_COUNT; i++ ) + if( p_region->p_cues[i] ) + i_lines += p_region->p_cues[i]->i_lines; + return i_lines; +} + +static void webvtt_region_ClearCues( webvtt_region_t *p_region ) +{ + for( size_t i=0; i<WEBVTT_REGION_LINES_COUNT; i++ ) + { + if( p_region->p_cues[i] == NULL ) + continue; + webvtt_dom_cue_Delete( p_region->p_cues[i] ); + p_region->p_cues[i] = NULL; + } +} + +static void webvtt_region_ClearCuesByTime( webvtt_region_t *p_region, mtime_t i_time ) +{ + for( size_t i=0; i<WEBVTT_REGION_LINES_COUNT; i++ ) + { + if( p_region->p_cues[i] == NULL ) + continue; + if( p_region->p_cues[i]->i_stop <= i_time ) + { + webvtt_dom_cue_Delete( p_region->p_cues[i] ); + for( size_t j=i; j<WEBVTT_REGION_LINES_COUNT - 1; j++ ) + p_region->p_cues[j] = p_region->p_cues[j] + 1; + p_region->p_cues[WEBVTT_REGION_LINES_COUNT - 1] = NULL; + } + } +} + +static void webvtt_region_Clean( webvtt_region_t *p_region ) +{ + webvtt_region_ClearCues( p_region ); + free( p_region->psz_id ); +} + +/* Remove top most line/cue for bottom insert */ +static void webvtt_region_Reduce( webvtt_region_t *p_region ) +{ + if( p_region->p_cues[0] ) + { + webvtt_dom_cue_Delete( p_region->p_cues[0] ); + } + else + { + for( int i=0; i<WEBVTT_REGION_LINES_COUNT; i++ ) + { + if( p_region->p_cues[i] ) + { + p_region->p_cues[i] = + webvtt_dom_cue_Reduced( p_region->p_cues[i] ); + break; + } + } + } +} + +static void webvtt_region_ScrollUp( webvtt_region_t *p_region ) +{ + if( p_region->p_cues[0] ) + webvtt_dom_cue_Delete( p_region->p_cues[0] ); + + memmove( &p_region->p_cues[0], &p_region->p_cues[1], + (WEBVTT_REGION_LINES_COUNT - 1) * sizeof(p_region->p_cues[0]) ); + p_region->p_cues[WEBVTT_REGION_LINES_COUNT - 1] = NULL; +} + +static void webvtt_region_AddCue( webvtt_region_t *p_region, + webvtt_dom_cue_t *p_cue ) +{ + if( p_region->b_scroll_up == false ) + { + webvtt_region_ClearCues( p_region ); + } + else + { + while( p_cue->i_lines > p_region->i_lines_max_scroll ) /* eh eh */ + { + p_cue = webvtt_dom_cue_Reduced( p_cue ); + assert( p_cue ); + if( unlikely(!p_cue) ) + return; + } + + while( webvtt_region_CountLines( p_region ) + p_cue->i_lines + > p_region->i_lines_max_scroll ) + webvtt_region_Reduce( p_region ); + + /* now move everything up */ + webvtt_region_ScrollUp( p_region ); + } + p_region->p_cues[WEBVTT_REGION_LINES_COUNT - 1] = p_cue; +} + +static void webvtt_region_Init( webvtt_region_t *p_region ) +{ + p_region->psz_id = NULL; + p_region->p_next = NULL; + p_region->f_width = 1.0; /* 100% */ + p_region->anchor_x = 0; + p_region->anchor_y = 1.0; /* 100% */ + p_region->i_lines_max_scroll = 3; + p_region->viewport_anchor_x = 0; + p_region->viewport_anchor_y = 1.0; /* 100% */ + p_region->b_scroll_up = false; + for( int i=0; i<WEBVTT_REGION_LINES_COUNT; i++ ) + p_region->p_cues[i] = NULL; +} + +static void webvtt_region_Delete( webvtt_region_t *p_region ) +{ + webvtt_region_Clean( p_region ); + free( p_region ); +} + +static webvtt_region_t * webvtt_region_New( void ) +{ + webvtt_region_t *p_region = malloc(sizeof(*p_region)); + if( p_region ) + webvtt_region_Init( p_region ); + return p_region; +} + +static webvtt_region_t * webvtt_region_GetByID( decoder_sys_t *p_sys, + const char *psz_id ) +{ + webvtt_region_t *p_region = &p_sys->regions; + if( psz_id != NULL ) + { + for( p_region = p_sys->regions.p_next; + p_region; p_region = p_region->p_next ) + { + if( !strcmp( psz_id, p_region->psz_id ) ) + return p_region; + } + } + return p_region; +} + +/***************************************************************************** + * + *****************************************************************************/ +static unsigned CountNewLines( const char *psz ) +{ + unsigned i = 0; + while( psz && *psz ) + psz = strchr( psz + 1, '\n' ); + return i; +} + +static webvtt_domnode_t * CreateDomNodes( const char *psz_text, unsigned *pi_lines ) +{ + webvtt_domnode_t *p_head = NULL; + webvtt_domnode_t **pp_append = &p_head; + webvtt_domnode_t *p_parent = p_head; + *pi_lines = 0; + + while( *psz_text ) + { + const char *psz_taglast; + const char *psz_tag = FindNextTag( psz_text, &psz_taglast ); + if( psz_tag ) + { + if( psz_tag - psz_text > 0 ) + { + webvtt_domnode_t *p_node = webvtt_domnode_New( p_parent ); + if( p_node ) + { + p_node->psz_text = strndup( psz_text, psz_tag - psz_text ); + *pi_lines += ((*pi_lines == 0) ? 1 : 0) + CountNewLines( p_node->psz_text ); + *pp_append = p_node; + pp_append = &p_node->p_next; + } + } + + if( ! IsEndTag( psz_tag ) ) + { + webvtt_domnode_t *p_node = webvtt_domnode_New( p_parent ); + if( p_node ) + { + const char *psz_attrs = NULL; + const char *psz_name = SplitTag( psz_tag, &psz_attrs ); + p_node->psz_tag = strndup( psz_name, psz_attrs - psz_name ); + if( psz_attrs != psz_taglast ) + p_node->psz_attrs = strndup( psz_attrs, psz_taglast - psz_attrs ); + *pp_append = p_node; + p_parent = p_node; + pp_append = &p_node->p_child; + } + } + else + { + if( p_parent ) + { + const char *psz_attrs = NULL; + const char *psz_name = SplitTag( psz_tag, &psz_attrs ); + char *psz_tagname = strndup( psz_name, psz_attrs - psz_name ); + + /* Close at matched parent node level due to unclosed tags + * like <b><v stuff>foo</b> */ + p_parent = webvtt_domnode_getParentByTag( p_parent->p_parent, psz_tagname ); + if( p_parent ) /* continue as parent next */ + pp_append = &p_parent->p_next; + else /* back as top node */ + pp_append = &p_head->p_next; + while( *pp_append ) + pp_append = &((*pp_append)->p_next); + + free( psz_tagname ); + } + else break; /* End tag for non open tag */ + } + psz_text = psz_taglast + 1; + } + else /* Special case: end */ + { + webvtt_domnode_t *p_node = webvtt_domnode_New( p_parent ); + if( p_node ) + { + p_node->psz_text = strdup( psz_text ); + *pi_lines += ((*pi_lines == 0) ? 1 : 0) + CountNewLines( p_node->psz_text ); + *pp_append = p_node; + } + break; + } + } + + return p_head; +} + +static void ExpireCues( decoder_t *p_dec, mtime_t i_time ) +{ + for( webvtt_region_t *p_vttregion = &p_dec->p_sys->regions; + p_vttregion; p_vttregion = p_vttregion->p_next ) + { + webvtt_region_ClearCuesByTime( p_vttregion, i_time ); + } +} + +static void ProcessCue( decoder_t *p_dec, const char *psz, webvtt_dom_cue_t *p_cue ) +{ + VLC_UNUSED(p_dec); + + if( p_cue->p_nodes ) + return; + p_cue->p_nodes = CreateDomNodes( psz, &p_cue->i_lines ); +#ifdef SUBSVTT_DEBUG + webvtt_domnode_Debug( p_cue->p_nodes, 0 ); +#endif +} + +static text_style_t * InheritStyles( decoder_t *p_dec, const webvtt_domnode_t *p_node ) +{ + VLC_UNUSED(p_dec); + + text_style_t *p_style = NULL; + for( ; p_node; p_node = p_node->p_parent ) + { + if( p_node->psz_tag ) + { + if ( !strcmp( p_node->psz_tag, "b" ) ) + { + if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) ) + { + p_style->i_style_flags |= STYLE_BOLD; + p_style->i_features |= STYLE_HAS_FLAGS; + } + } + else if ( !strcmp( p_node->psz_tag, "i" ) ) + { + if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) ) + { + p_style->i_style_flags |= STYLE_ITALIC; + p_style->i_features |= STYLE_HAS_FLAGS; + } + } + else if ( !strcmp( p_node->psz_tag, "u" ) ) + { + if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) ) + { + p_style->i_style_flags |= STYLE_UNDERLINE; + p_style->i_features |= STYLE_HAS_FLAGS; + } + } + else if ( !strcmp( p_node->psz_tag, "v" ) && p_node->psz_attrs ) + { + if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) ) + { + unsigned a = 0; + for( char *p = p_node->psz_attrs; *p; p++ ) + a = (a << 3) ^ *p; + p_style->i_font_color = (0x7F7F7F | a) & 0xFFFFFF; + p_style->i_features |= STYLE_HAS_FONT_COLOR; + } + } + } + } + return p_style; +} + +struct render_variables_s +{ + const webvtt_region_t *p_region; + float i_left_offset; + float i_left; + float i_top_offset; + float i_top; +}; + +static text_segment_t *ConvertNodesToSegments( decoder_t *p_dec, + struct render_variables_s *p_vars, + const webvtt_dom_cue_t *p_cue, + const webvtt_domnode_t *p_node ) +{ + text_segment_t *p_head = NULL; + text_segment_t **pp_append = &p_head; + for( ; p_node ; p_node = p_node->p_next ) + { + if( p_node->psz_text ) + { + *pp_append = text_segment_New( p_node->psz_text ); + if( *pp_append ) + { + if( (*pp_append)->psz_text ) + vlc_xml_decode( (*pp_append)->psz_text ); + (*pp_append)->style = InheritStyles( p_dec, p_node ); + pp_append = &((*pp_append)->p_next); + } + } + + *pp_append = ConvertNodesToSegments( p_dec, p_vars, p_cue, p_node->p_child ); + while( *pp_append ) + pp_append = &((*pp_append)->p_next); + } + return p_head; +} + +static text_segment_t *ConvertCueToSegments( decoder_t *p_dec, + struct render_variables_s *p_vars, + const webvtt_dom_cue_t *p_cue ) +{ + return ConvertNodesToSegments( p_dec, p_vars, p_cue, p_cue->p_nodes ); +} + +static void RenderRegions( decoder_t *p_dec, mtime_t i_start, mtime_t i_stop ) +{ + subpicture_t *p_spu = NULL; + subpicture_updater_sys_region_t *p_updtregion = NULL; + + for( webvtt_region_t *p_vttregion = &p_dec->p_sys->regions; + p_vttregion; p_vttregion = p_vttregion->p_next ) + { + text_segment_t *p_segments = NULL; + text_segment_t **pp_append = &p_segments; + + /* Variables */ + struct render_variables_s v; + v.p_region = p_vttregion; + v.i_left_offset = p_vttregion->anchor_x * p_vttregion->f_width; + v.i_left = p_vttregion->viewport_anchor_x - v.i_left_offset; + v.i_top_offset = p_vttregion->anchor_y * p_vttregion->i_lines_max_scroll * + WEBVTT_DEFAULT_LINE_HEIGHT_VH / 100.0; + v.i_top = p_vttregion->viewport_anchor_y - v.i_top_offset; + /* !Variables */ + + for( int i=0; i<WEBVTT_REGION_LINES_COUNT; i++ ) + { + if( !p_vttregion->p_cues[i] || + p_vttregion->p_cues[i]->i_start > i_start || + p_vttregion->p_cues[i]->i_stop <= i_start ) + continue; + + text_segment_t *p_new = ConvertCueToSegments( p_dec, &v, + p_vttregion->p_cues[i] ); + if( p_new ) + { + if( p_segments ) /* auto newlines */ + { + *pp_append = text_segment_New( "\n" ); + if( *pp_append ) + pp_append = &((*pp_append)->p_next); + } + + *pp_append = p_new; + while( *pp_append ) + pp_append = &((*pp_append)->p_next); + } + } + if( !p_segments ) + continue; + + if( p_spu == NULL ) + { + p_spu = decoder_NewSubpictureText( p_dec ); + if( p_spu ) + { + p_updtregion = &p_spu->updater.p_sys->region; + p_spu->i_start = i_start; + p_spu->i_stop = i_stop; + } + } + else + { + subpicture_updater_sys_region_t *p_new = + SubpictureUpdaterSysRegionNew( ); + if( p_new ) + { + SubpictureUpdaterSysRegionAdd( p_updtregion, p_new ); + p_updtregion = p_new; + } + } + + if( !p_spu || !p_updtregion ) + { + text_segment_ChainDelete( p_segments ); + continue; + } + + + if( p_vttregion == &p_dec->p_sys->regions ) + { + p_updtregion->align = SUBPICTURE_ALIGN_BOTTOM; + } + else + { + p_updtregion->align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT; + p_updtregion->origin.x = v.i_left; + p_updtregion->origin.y = v.i_top; + p_updtregion->extent.x = p_vttregion->f_width; + } + p_updtregion->flags = UPDT_REGION_ORIGIN_X_IS_RATIO|UPDT_REGION_ORIGIN_Y_IS_RATIO + | UPDT_REGION_EXTENT_X_IS_RATIO; + p_updtregion->p_segments = p_segments; + } + + if( p_spu ) + { + p_spu->b_ephemer = true; /* !important */ + p_spu->b_absolute = false; + + subpicture_updater_sys_t *p_spu_sys = p_spu->updater.p_sys; + p_spu_sys->p_default_style->f_font_relsize = WEBVTT_DEFAULT_LINE_HEIGHT_VH / + WEBVTT_LINE_TO_HEIGHT_RATIO; + decoder_QueueSub( p_dec, p_spu ); + } +} + +static int ProcessISOBMFF( decoder_t *p_dec, + const uint8_t *p_buffer, size_t i_buffer, + mtime_t i_start, mtime_t i_stop ) +{ + mp4_box_iterator_t it; + mp4_box_iterator_Init( &it, p_buffer, i_buffer ); + while( mp4_box_iterator_Next( &it ) ) + { + if( it.i_type == ATOM_vttc || it.i_type == ATOM_vttx ) + { + webvtt_dom_cue_t *p_cue = webvtt_dom_cue_New( i_start, i_stop ); + if( !p_cue ) + continue; + + mp4_box_iterator_t vtcc; + mp4_box_iterator_Init( &vtcc, it.p_payload, it.i_payload ); + while( mp4_box_iterator_Next( &vtcc ) ) + { + char *psz = NULL; + switch( vtcc.i_type ) + { + case ATOM_iden: + free( p_cue->psz_id ); + p_cue->psz_id = strndup( (char *) vtcc.p_payload, vtcc.i_payload ); + break; + case ATOM_sttg: + { + psz = strndup( (char *) vtcc.p_payload, vtcc.i_payload ); + if( psz ) + webvtt_cue_settings_Parse( &p_cue->settings, psz ); + } break; + case ATOM_payl: + { + psz = strndup( (char *) vtcc.p_payload, vtcc.i_payload ); + if( psz ) + ProcessCue( p_dec, psz, p_cue ); + } break; + } + free( psz ); + } + + webvtt_region_t *p_region = webvtt_region_GetByID( p_dec->p_sys, + p_cue->settings.psz_region ); + if( p_region == NULL ) + p_region = webvtt_region_GetByID( p_dec->p_sys, NULL /*defaut region*/ ); + assert( p_region ); + webvtt_region_AddCue( p_region, p_cue ); + } + } + return 0; +} + +struct parser_ctx +{ + webvtt_region_t *p_region; + struct vlc_memstream css; + decoder_t *p_dec; +}; + +static void ParserHeaderHandler( void *priv, enum webvtt_header_line_e s, + bool b_new, const char *psz_line ) +{ + struct parser_ctx *ctx = (struct parser_ctx *)priv; + decoder_t *p_dec = ctx->p_dec; + decoder_sys_t *p_sys = p_dec->p_sys; + + if( b_new || !psz_line /* commit */ ) + { + if( ctx->p_region ) + { + if( ctx->p_region->psz_id ) + { + msg_Dbg( p_dec, "added new region %s", ctx->p_region->psz_id ); + *p_sys->pp_region_append = ctx->p_region; + p_sys->pp_region_append = &ctx->p_region->p_next; + } + /* incomplete region decl (no id at least) */ + else webvtt_region_Delete( ctx->p_region ); + ctx->p_region = NULL; + } + else if( ctx->css.stream ) + { + if( vlc_memstream_close( &ctx->css ) == VLC_SUCCESS ) + { + char *psz = strndup( ctx->css.ptr, ctx->css.length ); + msg_Err(p_dec, "STYLE %s", psz); + free( psz ); + free( ctx->css.ptr ); + } + } + + if( !psz_line ) + return; + + if( b_new ) + { + if( s == WEBVTT_HEADER_REGION ) + ctx->p_region = webvtt_region_New(); + else if( s == WEBVTT_HEADER_STYLE ) + (void) vlc_memstream_open( &ctx->css ); + return; + } + } + + if( s == WEBVTT_HEADER_REGION && ctx->p_region ) + webvtt_region_Parse( ctx->p_region, (char*) psz_line ); + else if( s == WEBVTT_HEADER_STYLE && ctx->css.stream ) + { + vlc_memstream_puts( &ctx->css, psz_line ); + vlc_memstream_putc( &ctx->css, '\n' ); + } +} + +static void LoadExtradata( decoder_t *p_dec ) +{ + stream_t *p_stream = vlc_stream_MemoryNew( p_dec, + p_dec->fmt_in.p_extra, + p_dec->fmt_in.i_extra, + true ); + if( !p_stream ) + return; + + struct parser_ctx ctx; + ctx.css.stream = NULL; + ctx.p_region = NULL; + ctx.p_dec = p_dec; + webvtt_text_parser_t *p_parser = + webvtt_text_parser_New( &ctx, NULL, NULL, ParserHeaderHandler ); + if( p_parser ) + { + char *psz_line; + while( (psz_line = vlc_stream_ReadLine( p_stream )) ) + webvtt_text_parser_Feed( p_parser, psz_line ); + webvtt_text_parser_Delete( p_parser ); + /* commit using null */ + ParserHeaderHandler( &ctx, 0, false, NULL ); + } + + vlc_stream_Delete( p_stream ); +} + +/**************************************************************************** + * DecodeBlock: decoder data entry point + ****************************************************************************/ +static int DecodeBlock( decoder_t *p_dec, block_t *p_block ) +{ + if( p_block == NULL ) /* No Drain */ + return VLCDEC_SUCCESS; + + ExpireCues( p_dec, p_block->i_dts ); + + ProcessISOBMFF( p_dec, p_block->p_buffer, p_block->i_buffer, + p_block->i_pts, p_block->i_pts + p_block->i_length ); + + RenderRegions( p_dec, p_block->i_pts, p_block->i_pts + p_block->i_length ); + + block_Release( p_block ); + return VLCDEC_SUCCESS; +} + +/***************************************************************************** + * CloseDecoder: clean up the decoder + *****************************************************************************/ +void CloseDecoder( vlc_object_t *p_this ) +{ + decoder_t *p_dec = (decoder_t *)p_this; + decoder_sys_t *p_sys = p_dec->p_sys; + + while( p_sys->regions.p_next ) + { + webvtt_region_t *p_next = p_sys->regions.p_next->p_next; + webvtt_region_Clean( p_sys->regions.p_next ); + free( p_sys->regions.p_next ); + p_sys->regions.p_next = p_next; + } + webvtt_region_Clean( &p_sys->regions ); + + free( p_sys ); +} + +/***************************************************************************** + * OpenDecoder: probe the decoder and return score + *****************************************************************************/ +int OpenDecoder( vlc_object_t *p_this ) +{ + decoder_t *p_dec = (decoder_t*)p_this; + decoder_sys_t *p_sys; + + if( p_dec->fmt_in.i_codec != VLC_CODEC_WEBVTT ) + return VLC_EGENERIC; + + /* Allocate the memory needed to store the decoder's structure */ + p_dec->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) ); + if( unlikely( p_sys == NULL ) ) + return VLC_ENOMEM; + + webvtt_region_Init( &p_sys->regions ); + p_sys->pp_region_append = &p_sys->regions.p_next; + + p_dec->pf_decode = DecodeBlock; + + if( p_dec->fmt_in.i_extra ) + LoadExtradata( p_dec ); + + return VLC_SUCCESS; +} diff --git a/modules/codec/webvtt/webvtt.c b/modules/codec/webvtt/webvtt.c new file mode 100644 index 0000000000..ddcdfc6300 --- /dev/null +++ b/modules/codec/webvtt/webvtt.c @@ -0,0 +1,249 @@ +/***************************************************************************** + * webvtt.c: WEBVTT shared code + ***************************************************************************** + * 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. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <vlc_common.h> +#include <vlc_charset.h> +#include <vlc_plugin.h> + +#include "webvtt.h" + +#include <ctype.h> +#include <assert.h> + +/***************************************************************************** + * Modules descriptor. + *****************************************************************************/ + +vlc_module_begin () + set_capability( "spu decoder", 10 ) + set_shortname( N_("WEBVTT decoder")) + set_description( N_("WEBVTT subtitles decoder") ) + set_callbacks( OpenDecoder, CloseDecoder ) + set_category( CAT_INPUT ) + set_subcategory( SUBCAT_INPUT_SCODEC ) +vlc_module_end () + +struct webvtt_text_parser_t +{ + enum + { + WEBVTT_SECTION_UNDEFINED = WEBVTT_HEADER_STYLE - 1, + WEBVTT_SECTION_STYLE = WEBVTT_HEADER_STYLE, + WEBVTT_SECTION_REGION = WEBVTT_HEADER_REGION, + WEBVTT_SECTION_CUES, + } section; + char * reads[3]; + + void * priv; + webvtt_cue_t *(*pf_get_cue)( void * ); + void (*pf_cue_done)( void *, webvtt_cue_t * ); + void (*pf_header)( void *, enum webvtt_header_line_e, bool, const char * ); + + webvtt_cue_t *p_cue; +}; + +static mtime_t MakeTime( unsigned t[4] ) +{ + return t[0] * 3600 * CLOCK_FREQ + + t[1] * 60 * CLOCK_FREQ + + t[2] * CLOCK_FREQ + + t[3] * 1000; +} + +bool webvtt_scan_time( const char *psz, mtime_t *p_time ) +{ + unsigned t[4]; + if( sscanf( psz, "%2u:%2u.%3u", + &t[1], &t[2], &t[3] ) == 3 ) + { + t[0] = 0; + *p_time = MakeTime( t ); + return true; + } + else if( sscanf( psz, "%2u:%2u:%2u.%3u", + &t[0], &t[1], &t[2], &t[3] ) == 4 ) + { + *p_time = MakeTime( t ); + return true; + } + else return false; +} + +static bool KeywordMatch( const char *psz, const char *keyword ) +{ + const size_t i_len = strlen(keyword); + return( !strncmp( keyword, psz, i_len ) && (!psz[i_len] || isspace(psz[i_len])) ); +} + +/* + +*/ + +webvtt_text_parser_t * webvtt_text_parser_New( void *priv, + webvtt_cue_t *(*pf_get_cue)( void * ), + void (*pf_cue_done)( void *, webvtt_cue_t * ), + void (*pf_header)( void *, enum webvtt_header_line_e, bool, const char * ) ) +{ + webvtt_text_parser_t *p = malloc(sizeof(*p)); + if( p ) + { + p->section = WEBVTT_SECTION_UNDEFINED; + for( int i=0; i<3; i++ ) + p->reads[i] = NULL; + p->p_cue = NULL; + p->priv = priv; + p->pf_cue_done = pf_cue_done; + p->pf_get_cue = pf_get_cue; + p->pf_header = pf_header; + } + return p; +} + +void webvtt_text_parser_Delete( webvtt_text_parser_t *p ) +{ + for( int i=0; i<3; i++ ) + free( p->reads[i] ); + free( p ); +} + +static void forward_line( webvtt_text_parser_t *p, const char *psz_line, bool b_new ) +{ + if( p->pf_header ) + p->pf_header( p->priv, p->section, b_new, psz_line ); +} + +void webvtt_text_parser_Feed( webvtt_text_parser_t *p, char *psz_line ) +{ + if( psz_line == NULL ) + { + if( p->p_cue ) + { + if( p->pf_cue_done ) + p->pf_cue_done( p->priv, p->p_cue ); + p->p_cue = NULL; + } + return; + } + + free(p->reads[0]); + p->reads[0] = p->reads[1]; + p->reads[1] = p->reads[2]; + p->reads[2] = psz_line; + + /* Lookup keywords */ + if( unlikely(p->section == WEBVTT_SECTION_UNDEFINED) ) + { + if( KeywordMatch( psz_line, "\xEF\xBB\xBFWEBVTT" ) || + KeywordMatch( psz_line, "WEBVTT" ) ) + { + p->section = WEBVTT_SECTION_UNDEFINED; + if( p->p_cue ) + { + if( p->pf_cue_done ) + p->pf_cue_done( p->priv, p->p_cue ); + p->p_cue = NULL; + } + return; + } + else if( KeywordMatch( psz_line, "STYLE" ) ) + { + p->section = WEBVTT_SECTION_STYLE; + forward_line( p, psz_line, true ); + return; + } + else if( KeywordMatch( psz_line, "REGION" ) ) + { + p->section = WEBVTT_SECTION_REGION; + forward_line( p, psz_line, true ); + return; + } + else if( KeywordMatch( psz_line, "NOTE" ) ) + { + return; + } + else if( psz_line[0] != 0 ) + { + p->section = WEBVTT_SECTION_CUES; + } + } + + if( likely(p->section == WEBVTT_SECTION_CUES) ) + { + if( p->p_cue ) + { + if( psz_line[0] == 0 ) + { + p->p_cue = NULL; + } + else + { + char *psz_merged; + if( -1 < asprintf( &psz_merged, "%s\n%s", p->p_cue->psz_text, psz_line ) ) + { + free( p->p_cue->psz_text ); + p->p_cue->psz_text = psz_merged; + } + return; + } + } + + if( p->reads[1] == NULL ) + return; + + const char *psz_split = strstr( p->reads[1], " --> " ); + if( psz_split ) + { + mtime_t i_start, i_stop; + + if( webvtt_scan_time( p->reads[1], &i_start ) && + webvtt_scan_time( psz_split + 5, &i_stop ) ) + { + const char *psz_attrs = strchr( psz_split + 5 + 9, ' ' ); + p->p_cue = ( p->pf_get_cue ) ? p->pf_get_cue( p->priv ) : NULL; + if( p->p_cue ) + { + p->p_cue->psz_attrs = ( psz_attrs ) ? strdup( psz_attrs ) : NULL; + p->p_cue->psz_id = p->reads[0]; + p->reads[0] = NULL; + p->p_cue->psz_text = p->reads[2]; + p->reads[2] = NULL; + p->p_cue->i_start = i_start; + p->p_cue->i_stop = i_stop; + } + } + } + } + else if( p->section == WEBVTT_SECTION_STYLE ) + { + forward_line( p, psz_line, false ); + if( psz_line[0] == 0 ) + p->section = WEBVTT_SECTION_UNDEFINED; + } + else if( p->section == WEBVTT_SECTION_REGION ) + { + forward_line( p, psz_line, false ); + if( psz_line[0] == 0 ) /* End of region declaration */ + p->section = WEBVTT_SECTION_UNDEFINED; + } +} diff --git a/modules/codec/webvtt/webvtt.h b/modules/codec/webvtt/webvtt.h new file mode 100644 index 0000000000..7fd8e5c3a3 --- /dev/null +++ b/modules/codec/webvtt/webvtt.h @@ -0,0 +1,71 @@ +/***************************************************************************** + * webvtt.h: WEBVTT shared code + ***************************************************************************** + * 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. + *****************************************************************************/ +#ifndef WEBVTT_H +#define WEBVTT_H + +int OpenDecoder ( vlc_object_t * ); +void CloseDecoder ( vlc_object_t * ); + +typedef struct webvtt_text_parser_t webvtt_text_parser_t; + +enum webvtt_header_line_e +{ + WEBVTT_HEADER_STYLE = 1, + WEBVTT_HEADER_REGION, +}; + +typedef struct +{ + mtime_t i_start; + mtime_t i_stop; + char *psz_id; + char *psz_text; + char *psz_attrs; +} webvtt_cue_t; + +static inline void webvtt_cue_Init( webvtt_cue_t *c ) +{ + memset( c, 0, sizeof(*c) ); +} + +static inline void webvtt_cue_Clean( webvtt_cue_t *c ) +{ + free( c->psz_attrs ); + free( c->psz_text ); + free( c->psz_id ); +} + +webvtt_text_parser_t * webvtt_text_parser_New( + void *priv, + /* parser callback requesting a new cue to fill + * set to NULL if no cues data required */ + webvtt_cue_t *(*pf_get_cue)( void * ), + /* callback when cue is complete set to NULL for no callback */ + void (*pf_cue_done)( void *, webvtt_cue_t * ), + /* parser callback returning headers + * set to NULL for no callbacks */ + void (*pf_header)( void *, enum webvtt_header_line_e, bool, const char * ) + ); +void webvtt_text_parser_Delete( webvtt_text_parser_t *p ); +void webvtt_text_parser_Feed( webvtt_text_parser_t *p, char *psz_line ); + +bool webvtt_scan_time( const char *psz, mtime_t *p_time ); + +#endif diff --git a/modules/demux/mp4/minibox.h b/modules/demux/mp4/minibox.h new file mode 100644 index 0000000000..5204ef529c --- /dev/null +++ b/modules/demux/mp4/minibox.h @@ -0,0 +1,55 @@ +/***************************************************************************** + * minibox.h: minimal mp4 box iterator + ***************************************************************************** + * 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. + *****************************************************************************/ + +typedef struct +{ + const uint8_t *p_buffer; + size_t i_buffer; + const uint8_t *p_payload; + size_t i_payload; + vlc_fourcc_t i_type; +} mp4_box_iterator_t; + +static void mp4_box_iterator_Init( mp4_box_iterator_t *p_it, + const uint8_t *p_data, size_t i_data ) +{ + p_it->p_buffer = p_data; + p_it->i_buffer = i_data; +} + +static bool mp4_box_iterator_Next( mp4_box_iterator_t *p_it ) +{ + while( p_it->i_buffer > 8 ) + { + const uint8_t *p = p_it->p_buffer; + const size_t i_size = GetDWBE( p ); + p_it->i_type = VLC_FOURCC(p[4], p[5], p[6], p[7]); + if( i_size >= 8 && i_size <= p_it->i_buffer ) + { + p_it->p_payload = &p_it->p_buffer[8]; + p_it->i_payload = i_size - 8; + /* update for next run */ + p_it->p_buffer += i_size; + p_it->i_buffer -= i_size; + return true; + } + } + return false; +} _______________________________________________ vlc-commits mailing list [email protected] https://mailman.videolan.org/listinfo/vlc-commits
