Coverage Report

Created: 2025-11-09 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/modules/mux/webvtt.c
Line
Count
Source
1
/*****************************************************************************
2
 * webvtt.c: muxer for raw WEBVTT
3
 *****************************************************************************
4
 * Copyright (C) 2024 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
#ifdef HAVE_CONFIG_H
21
#include "config.h"
22
#endif
23
24
#include <vlc_common.h>
25
26
#include <vlc_codec.h>
27
#include <vlc_memstream.h>
28
#include <vlc_plugin.h>
29
#include <vlc_sout.h>
30
31
#include "../codec/webvtt/webvtt.h"
32
#include "../demux/mp4/minibox.h"
33
34
typedef struct
35
{
36
    bool header_done;
37
    const sout_input_t *input;
38
} sout_mux_sys_t;
39
40
static void OutputTime(struct vlc_memstream *ms, vlc_tick_t time)
41
0
{
42
0
    const vlc_tick_t secs = SEC_FROM_VLC_TICK(time);
43
0
    vlc_memstream_printf(ms,
44
0
                         "%02" PRIi64 ":%02" PRIi64 ":%02" PRIi64 ".%03" PRIi64,
45
0
                         secs / 3600,
46
0
                         secs % 3600 / 60,
47
0
                         secs % 60,
48
0
                         (time % CLOCK_FREQ) / 1000);
49
0
}
50
51
static block_t *FormatCue(const webvtt_cue_t *cue)
52
0
{
53
0
    struct vlc_memstream ms;
54
0
    if (vlc_memstream_open(&ms))
55
0
        return NULL;
56
57
0
    if (cue->psz_id != NULL)
58
0
        vlc_memstream_printf(&ms, "%s\n", cue->psz_id);
59
60
0
    OutputTime(&ms, cue->i_start);
61
0
    vlc_memstream_printf(&ms, " --> ");
62
0
    OutputTime(&ms, cue->i_stop);
63
64
0
    if (cue->psz_attrs != NULL)
65
0
        vlc_memstream_printf(&ms, " %s\n", cue->psz_attrs);
66
0
    else
67
0
        vlc_memstream_putc(&ms, '\n');
68
69
0
    vlc_memstream_printf(&ms, "%s\n\n", cue->psz_text);
70
71
0
    if (vlc_memstream_close(&ms))
72
0
        return NULL;
73
74
0
    block_t *formatted = block_heap_Alloc(ms.ptr, ms.length);
75
0
    formatted->i_length = cue->i_stop - cue->i_start;
76
0
    formatted->i_pts = formatted->i_dts = cue->i_stop - cue->i_start;
77
0
    return formatted;
78
0
}
79
80
static void UnpackISOBMFF(const vlc_frame_t *packed, webvtt_cue_t *out)
81
0
{
82
0
    mp4_box_iterator_t it;
83
0
    mp4_box_iterator_Init(&it, packed->p_buffer, packed->i_buffer);
84
0
    while (mp4_box_iterator_Next(&it))
85
0
    {
86
0
        if (it.i_type != ATOM_vttc && it.i_type != ATOM_vttx)
87
0
            continue;
88
89
0
        mp4_box_iterator_t vtcc;
90
0
        mp4_box_iterator_Init(&vtcc, it.p_payload, it.i_payload);
91
0
        while (mp4_box_iterator_Next(&vtcc))
92
0
        {
93
0
            switch (vtcc.i_type)
94
0
            {
95
0
                case ATOM_iden:
96
0
                    if (out->psz_id == NULL)
97
0
                        out->psz_id =
98
0
                            strndup((char *)vtcc.p_payload, vtcc.i_payload);
99
0
                    break;
100
0
                case ATOM_sttg:
101
0
                    if (out->psz_attrs)
102
0
                    {
103
0
                        char *dup = strndup((char *)vtcc.p_payload, vtcc.i_payload);
104
0
                        if (dup)
105
0
                        {
106
0
                            char *psz;
107
0
                            if (asprintf(&psz, "%s %s", out->psz_attrs, dup) >= 0)
108
0
                            {
109
0
                                free(out->psz_attrs);
110
0
                                out->psz_attrs = psz;
111
0
                            }
112
0
                            free(dup);
113
0
                        }
114
0
                    }
115
0
                    else
116
0
                        out->psz_attrs =
117
0
                            strndup((char *)vtcc.p_payload, vtcc.i_payload);
118
0
                    break;
119
0
                case ATOM_payl:
120
0
                    if (!out->psz_text)
121
0
                        out->psz_text =
122
0
                            strndup((char *)vtcc.p_payload, vtcc.i_payload);
123
0
                    break;
124
0
            }
125
0
        }
126
0
    }
127
0
}
128
129
static int Control(sout_mux_t *mux, int query, va_list args)
130
{
131
    switch (query)
132
    {
133
        case MUX_CAN_ADD_STREAM_WHILE_MUXING:
134
            *(va_arg(args, bool *)) = false;
135
            return VLC_SUCCESS;
136
        default:
137
            return VLC_ENOTSUP;
138
    }
139
    (void)mux;
140
}
141
142
static int AddStream(sout_mux_t *mux, sout_input_t *input)
143
0
{
144
0
    sout_mux_sys_t *sys = mux->p_sys;
145
0
    if ((input->fmt.i_codec != VLC_CODEC_WEBVTT &&
146
0
         input->fmt.i_codec != VLC_CODEC_TEXT) ||
147
0
        sys->input)
148
0
        return VLC_ENOTSUP;
149
0
    sys->input = input;
150
0
    return VLC_SUCCESS;
151
0
}
152
153
static void DelStream(sout_mux_t *mux, sout_input_t *input)
154
0
{
155
0
    sout_mux_sys_t *sys = mux->p_sys;
156
0
    if (input == sys->input)
157
0
        sys->input = NULL;
158
0
}
159
160
static int Mux(sout_mux_t *mux)
161
0
{
162
0
    sout_mux_sys_t *sys = mux->p_sys;
163
164
0
    if (mux->i_nb_inputs == 0)
165
0
        return VLC_SUCCESS;
166
167
0
    sout_input_t *input = mux->pp_inputs[0];
168
169
0
    if (!sys->header_done)
170
0
    {
171
0
        block_t *data = NULL;
172
0
        if (input->fmt.i_extra > 8 && !memcmp(input->fmt.p_extra, "WEBVTT", 6))
173
0
        {
174
0
            data = block_Alloc(input->fmt.i_extra + 2);
175
176
0
            if (unlikely(data == NULL))
177
0
                return VLC_ENOMEM;
178
0
            memcpy(data->p_buffer, input->fmt.p_extra, input->fmt.i_extra);
179
0
            data->p_buffer[data->i_buffer - 2] = '\n';
180
0
            data->p_buffer[data->i_buffer - 1] = '\n';
181
0
        }
182
0
        else
183
0
        {
184
0
            data = block_Alloc(8);
185
0
            if (unlikely(data == NULL))
186
0
                return VLC_ENOMEM;
187
0
            memcpy(data->p_buffer, "WEBVTT\n\n", 8);
188
0
        }
189
190
0
        if (data)
191
0
        {
192
0
            data->i_flags |= BLOCK_FLAG_HEADER;
193
0
            sout_AccessOutWrite(mux->p_access, data);
194
0
        }
195
0
        sys->header_done = true;
196
0
    }
197
198
0
    vlc_fifo_Lock(input->p_fifo);
199
0
    vlc_frame_t *chain = vlc_fifo_DequeueAllUnlocked(input->p_fifo);
200
0
    int status = VLC_SUCCESS;
201
0
    while (chain != NULL)
202
0
    {
203
        // Ephemer subtitles stop being displayed at the next SPU display time.
204
        // We need to delay if subsequent SPU aren't available yet.
205
0
        const bool is_ephemer = (chain->i_length == VLC_TICK_INVALID);
206
0
        if (is_ephemer && chain->p_next == NULL)
207
0
        {
208
0
            vlc_fifo_QueueUnlocked(input->p_fifo, chain);
209
0
            chain = NULL;
210
0
            break;
211
0
        }
212
213
0
        webvtt_cue_t cue = {};
214
215
0
        if (sys->input->fmt.i_codec != VLC_CODEC_WEBVTT)
216
0
            cue.psz_text = strndup((char *)chain->p_buffer, chain->i_buffer);
217
0
        else
218
0
            UnpackISOBMFF(chain, &cue);
219
220
0
        if (unlikely(cue.psz_text == NULL))
221
0
        {
222
0
            webvtt_cue_Clean(&cue);
223
0
            status = VLC_ENOMEM;
224
0
            break;
225
0
        }
226
227
0
        cue.i_start = chain->i_pts - VLC_TICK_0;
228
0
        if(is_ephemer)
229
0
            cue.i_stop = chain->p_next->i_pts - VLC_TICK_0;
230
0
        else
231
0
            cue.i_stop = cue.i_start + chain->i_length;
232
0
        block_t *to_write = FormatCue(&cue);
233
0
        webvtt_cue_Clean(&cue);
234
235
0
        ssize_t written = -1;
236
0
        if (likely(to_write != NULL))
237
0
            written = sout_AccessOutWrite(mux->p_access, to_write);
238
0
        if (written == -1)
239
0
        {
240
0
            status = VLC_EGENERIC;
241
0
            break;
242
0
        }
243
244
0
        vlc_frame_t *next = chain->p_next;
245
0
        vlc_frame_Release(chain);
246
0
        chain = next;
247
0
    }
248
249
0
    vlc_frame_ChainRelease(chain);
250
0
    vlc_fifo_Unlock(input->p_fifo);
251
0
    return status;
252
0
}
253
/*****************************************************************************
254
 * Open:
255
 *****************************************************************************/
256
int webvtt_OpenMuxer(vlc_object_t *this)
257
0
{
258
0
    sout_mux_t *mux = (sout_mux_t *)this;
259
0
    sout_mux_sys_t *sys;
260
261
0
    mux->p_sys = sys = malloc(sizeof(*sys));
262
0
    if (unlikely(sys == NULL))
263
0
        return VLC_ENOMEM;
264
265
0
    sys->header_done = false;
266
0
    sys->input = NULL;
267
268
0
    mux->pf_control = Control;
269
0
    mux->pf_addstream = AddStream;
270
0
    mux->pf_delstream = DelStream;
271
0
    mux->pf_mux = Mux;
272
273
0
    return VLC_SUCCESS;
274
0
}
275
276
/*****************************************************************************
277
 * Close:
278
 *****************************************************************************/
279
void webvtt_CloseMuxer(vlc_object_t *this)
280
0
{
281
0
    sout_mux_t *mux = (sout_mux_t *)this;
282
0
    free(mux->p_sys);
283
0
}