Coverage Report

Created: 2026-07-12 08:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/modules/codec/webvtt/webvtt.c
Line
Count
Source
1
/*****************************************************************************
2
 * webvtt.c: WEBVTT shared code
3
 *****************************************************************************
4
 * Copyright (C) 2017 VideoLabs, VLC authors and VideoLAN
5
 *
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.
10
 *
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.
15
 *
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
 *****************************************************************************/
20
21
#ifdef HAVE_CONFIG_H
22
# include "config.h"
23
#endif
24
25
#include <vlc_common.h>
26
#include <vlc_charset.h>
27
#include <vlc_plugin.h>
28
29
#include "webvtt.h"
30
31
#include <ctype.h>
32
#include <assert.h>
33
34
/*****************************************************************************
35
 * Modules descriptor.
36
 *****************************************************************************/
37
38
168
vlc_module_begin ()
39
84
    set_capability( "spu decoder", 10 )
40
84
    set_shortname( N_("WEBVTT decoder"))
41
84
    set_description( N_("WEBVTT subtitles decoder") )
42
168
    set_callbacks( webvtt_OpenDecoder, webvtt_CloseDecoder )
43
84
    set_subcategory( SUBCAT_INPUT_SCODEC )
44
84
    add_submodule()
45
84
        set_shortname( "WEBVTT" )
46
84
        set_description( N_("WEBVTT subtitles parser") )
47
84
        set_capability( "demux", 11 )
48
84
        set_subcategory( SUBCAT_INPUT_DEMUX )
49
84
        set_callbacks( webvtt_OpenDemux, webvtt_CloseDemux )
50
84
        add_shortcut( "webvtt" )
51
84
    add_submodule()
52
84
        set_shortname( "WEBVTT" )
53
84
        set_description( N_("WEBVTT subtitles parser") )
54
84
        set_capability( "demux", 0 )
55
84
        set_subcategory( SUBCAT_INPUT_DEMUX )
56
84
        set_callbacks( webvtt_OpenDemuxStream, webvtt_CloseDemux )
57
84
        add_shortcut( "webvttstream" )
58
84
#ifdef ENABLE_SOUT
59
84
    add_submodule()
60
84
        set_description( "WEBVTT text encoder" )
61
84
        set_capability( "spu encoder", 101 )
62
84
        set_subcategory( SUBCAT_INPUT_SCODEC )
63
84
        set_callback( webvtt_OpenEncoder )
64
84
    add_submodule()
65
84
        set_description( N_("Raw WebVTT muxer") )
66
84
        set_capability( "sout mux", 0 )
67
84
        set_subcategory( SUBCAT_SOUT_MUX )
68
84
        add_shortcut( "webvtt", "rawvtt" )
69
168
        set_callbacks( webvtt_OpenMuxer, webvtt_CloseMuxer )
70
84
#endif
71
84
vlc_module_end ()
72
73
struct webvtt_text_parser_t
74
{
75
    enum
76
    {
77
        WEBVTT_SECTION_UNDEFINED = WEBVTT_HEADER_STYLE - 1,
78
        WEBVTT_SECTION_STYLE = WEBVTT_HEADER_STYLE,
79
        WEBVTT_SECTION_REGION = WEBVTT_HEADER_REGION,
80
        WEBVTT_SECTION_NOTE,
81
        WEBVTT_SECTION_CUES,
82
    } section;
83
    char * reads[3];
84
85
    void * priv;
86
    webvtt_cue_t *(*pf_get_cue)( void * );
87
    void (*pf_cue_done)( void *, webvtt_cue_t * );
88
    void (*pf_header)( void *, enum webvtt_header_line_e, bool, const char * );
89
90
    webvtt_cue_t *p_cue;
91
};
92
93
static vlc_tick_t MakeTime( int32_t t[4] )
94
966k
{
95
966k
    return vlc_tick_from_sec( (int64_t)t[0] * 3600 + t[1] * 60 + t[2] ) +
96
966k
           VLC_TICK_FROM_MS(t[3]);
97
966k
}
98
99
bool webvtt_scan_time( const char *psz, vlc_tick_t *p_time )
100
1.05M
{
101
1.05M
    int32_t t[4];
102
1.05M
    if( sscanf( psz, "%2" SCNd32 ":%2" SCNd32 ".%3" SCNd32,
103
1.05M
                      &t[1], &t[2], &t[3] ) == 3 )
104
61.9k
    {
105
61.9k
        t[0] = 0;
106
61.9k
        if( t[1] < 0 || t[2] < 0 || t[3] < 0 )
107
1.55k
            return false;
108
109
60.3k
        *p_time = MakeTime( t );
110
60.3k
        return true;
111
61.9k
    }
112
996k
    else if( sscanf( psz, "%" SCNd32 ":%2" SCNd32 ":%2" SCNd32 ".%3" SCNd32,
113
996k
                          &t[0], &t[1], &t[2], &t[3] ) == 4 )
114
915k
    {
115
915k
        if( t[0] < 0 || t[1] < 0 || t[2] < 0 || t[3] < 0 )
116
9.24k
            return false;
117
118
905k
        *p_time = MakeTime( t );
119
905k
        return true;
120
915k
    }
121
81.2k
    else return false;
122
1.05M
}
123
124
static bool KeywordMatch( const char *psz, const char *keyword )
125
228k
{
126
228k
    const size_t i_len = strlen(keyword);
127
228k
    return( !strncmp( keyword, psz, i_len ) && (!psz[i_len] || isspace(psz[i_len])) );
128
228k
}
129
130
/*
131
132
*/
133
134
webvtt_text_parser_t * webvtt_text_parser_New( void *priv,
135
                    webvtt_cue_t *(*pf_get_cue)( void * ),
136
                    void (*pf_cue_done)( void *, webvtt_cue_t * ),
137
                    void (*pf_header)( void *, enum webvtt_header_line_e, bool, const char * ) )
138
15.5k
{
139
15.5k
    webvtt_text_parser_t *p = malloc(sizeof(*p));
140
15.5k
    if( p )
141
15.5k
    {
142
15.5k
        p->section = WEBVTT_SECTION_UNDEFINED;
143
62.1k
        for( int i=0; i<3; i++ )
144
46.6k
            p->reads[i] = NULL;
145
15.5k
        p->p_cue = NULL;
146
15.5k
        p->priv = priv;
147
15.5k
        p->pf_cue_done = pf_cue_done;
148
15.5k
        p->pf_get_cue = pf_get_cue;
149
15.5k
        p->pf_header = pf_header;
150
15.5k
    }
151
15.5k
    return p;
152
15.5k
}
153
154
void webvtt_text_parser_Delete( webvtt_text_parser_t *p )
155
15.5k
{
156
62.1k
    for( int i=0; i<3; i++ )
157
46.6k
        free( p->reads[i] );
158
15.5k
    free( p );
159
15.5k
}
160
161
static void forward_line( webvtt_text_parser_t *p, const char *psz_line, bool b_new )
162
172k
{
163
172k
    if( p->pf_header )
164
172k
        p->pf_header( p->priv, (enum webvtt_header_line_e)p->section,
165
172k
                      b_new, psz_line );
166
172k
}
167
168
void webvtt_text_parser_Feed( webvtt_text_parser_t *p, char *psz_line )
169
1.94M
{
170
1.94M
    if( psz_line == NULL )
171
7.77k
    {
172
7.77k
        if( p->p_cue )
173
4.20k
        {
174
4.20k
            if( p->pf_cue_done )
175
4.20k
                p->pf_cue_done( p->priv, p->p_cue );
176
4.20k
            p->p_cue = NULL;
177
4.20k
        }
178
7.77k
        return;
179
7.77k
    }
180
181
1.93M
    free(p->reads[0]);
182
1.93M
    p->reads[0] = p->reads[1];
183
1.93M
    p->reads[1] = p->reads[2];
184
1.93M
    p->reads[2] = psz_line;
185
186
    /* Lookup keywords */
187
1.93M
    if( unlikely(p->section == WEBVTT_SECTION_UNDEFINED) )
188
63.0k
    {
189
63.0k
        if( KeywordMatch( psz_line, "\xEF\xBB\xBFWEBVTT" ) ||
190
60.2k
            KeywordMatch( psz_line, "WEBVTT" )  )
191
11.6k
        {
192
11.6k
            p->section = WEBVTT_SECTION_UNDEFINED;
193
11.6k
            if( p->p_cue )
194
0
            {
195
0
                if( p->pf_cue_done )
196
0
                    p->pf_cue_done( p->priv, p->p_cue );
197
0
                p->p_cue = NULL;
198
0
            }
199
11.6k
            return;
200
11.6k
        }
201
51.3k
        else if( KeywordMatch( psz_line, "STYLE" ) )
202
21.2k
        {
203
21.2k
            p->section = WEBVTT_SECTION_STYLE;
204
21.2k
            forward_line( p, psz_line, true );
205
21.2k
            return;
206
21.2k
        }
207
30.0k
        else if( KeywordMatch( psz_line, "REGION" ) )
208
6.67k
        {
209
6.67k
            p->section = WEBVTT_SECTION_REGION;
210
6.67k
            forward_line( p, psz_line, true );
211
6.67k
            return;
212
6.67k
        }
213
23.3k
        else if( KeywordMatch( psz_line, "NOTE" ) )
214
232
        {
215
232
            p->section = WEBVTT_SECTION_NOTE;
216
232
            return;
217
232
        }
218
23.1k
        else if( psz_line[0] != 0 )
219
5.75k
        {
220
5.75k
            p->section = WEBVTT_SECTION_CUES;
221
5.75k
        }
222
63.0k
    }
223
224
1.89M
    if( likely(p->section == WEBVTT_SECTION_CUES) )
225
1.73M
    {
226
1.73M
        if( p->p_cue )
227
493k
        {
228
493k
            if( psz_line[0] == 0 )
229
419k
            {
230
419k
                if( p->p_cue )
231
419k
                {
232
419k
                    if( p->pf_cue_done )
233
419k
                        p->pf_cue_done( p->priv, p->p_cue );
234
419k
                    p->p_cue = NULL;
235
419k
                }
236
419k
            }
237
73.8k
            else
238
73.8k
            {
239
73.8k
                char *psz_merged;
240
73.8k
                if( -1 < asprintf( &psz_merged, "%s\n%s", p->p_cue->psz_text, psz_line ) )
241
73.8k
                {
242
73.8k
                    free( p->p_cue->psz_text );
243
73.8k
                    p->p_cue->psz_text = psz_merged;
244
73.8k
                }
245
73.8k
                return;
246
73.8k
            }
247
493k
        }
248
249
1.65M
        if( p->reads[1] == NULL )
250
384k
            return;
251
252
1.27M
        const char *psz_split = strstr( p->reads[1], " --> " );
253
1.27M
        if( psz_split )
254
479k
        {
255
479k
            vlc_tick_t i_start, i_stop;
256
257
479k
            if( webvtt_scan_time( p->reads[1], &i_start ) &&
258
465k
                webvtt_scan_time( psz_split + 5,  &i_stop ) && i_start <= i_stop )
259
424k
            {
260
424k
                const char *psz_attrs = strchr( psz_split + 5 + 5, ' ' );
261
424k
                p->p_cue = ( p->pf_get_cue ) ? p->pf_get_cue( p->priv ) : NULL;
262
424k
                if( p->p_cue )
263
423k
                {
264
423k
                    p->p_cue->psz_attrs = ( psz_attrs ) ? strdup( psz_attrs ) : NULL;
265
423k
                    p->p_cue->psz_id = p->reads[0];
266
423k
                    p->reads[0] = NULL;
267
423k
                    p->p_cue->psz_text = p->reads[2];
268
423k
                    p->reads[2] = NULL;
269
423k
                    p->p_cue->i_start = i_start;
270
423k
                    p->p_cue->i_stop = i_stop;
271
423k
                }
272
424k
            }
273
479k
        }
274
1.27M
    }
275
162k
    else if( p->section == WEBVTT_SECTION_STYLE )
276
109k
    {
277
109k
        forward_line( p, psz_line, false );
278
109k
        if( psz_line[0] == 0 )
279
18.3k
            p->section = WEBVTT_SECTION_UNDEFINED;
280
109k
    }
281
52.4k
    else if( p->section == WEBVTT_SECTION_REGION )
282
34.6k
    {
283
34.6k
        forward_line( p, psz_line, false );
284
34.6k
        if( psz_line[0] == 0 ) /* End of region declaration */
285
5.69k
            p->section = WEBVTT_SECTION_UNDEFINED;
286
34.6k
    }
287
17.8k
    else if( p->section == WEBVTT_SECTION_NOTE )
288
433
    {
289
433
        if( psz_line[0] == 0 )
290
221
            p->section = WEBVTT_SECTION_UNDEFINED;
291
433
    }
292
1.89M
}