Coverage Report

Created: 2025-07-11 07:16

/src/vlc/modules/codec/webvtt/encvtt.c
Line
Count
Source (jump to first uncovered line)
1
/*****************************************************************************
2
 * encvtt.c: Encoder for WEBVTT as ISO1446-30 payload
3
 *****************************************************************************
4
 * Copyright (C) 2018 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
#include <vlc_plugin.h>
26
#include <vlc_codec.h>
27
#include <vlc_subpicture.h>
28
#include <vlc_boxes.h>
29
#include <vlc_charset.h>
30
#include "webvtt.h"
31
32
static block_t *Encode ( encoder_t *, subpicture_t * );
33
34
int webvtt_OpenEncoder( vlc_object_t *p_this )
35
0
{
36
0
    encoder_t *p_enc = (encoder_t *)p_this;
37
38
0
    if( p_enc->fmt_out.i_codec != VLC_CODEC_WEBVTT )
39
0
        return VLC_EGENERIC;
40
41
42
0
    static const struct vlc_encoder_operations ops =
43
0
        { .encode_sub = Encode };
44
0
    p_enc->ops = &ops;
45
46
0
    return VLC_SUCCESS;
47
0
}
48
49
static void bo_add_escaped( bo_t *box, size_t len, const char *psz )
50
0
{
51
0
    if( likely(strcspn(psz, "&<>") >= len) )
52
0
    {
53
0
        bo_add_mem( box, len, psz );
54
0
    }
55
0
    else
56
0
    {
57
0
        for( size_t i=0; i<len; i++ )
58
0
        {
59
0
            switch(*psz)
60
0
            {
61
0
            case '&':
62
0
                bo_add_mem( box, 6, "&#x26;" );
63
0
                break;
64
0
            case '<':
65
0
                bo_add_mem( box, 6, "&#x3c;" );
66
0
                break;
67
0
            case '>': /* escapes forbidden --> sequence */
68
0
                bo_add_mem( box, 6, "&#x3e;" );
69
0
                break;
70
0
            default:
71
0
                bo_add_8( box, *psz );
72
0
                break;
73
0
            }
74
0
            psz++;
75
0
        }
76
0
    }
77
0
}
78
79
static void WriteText( const char *psz, bo_t *box, char *c_last )
80
0
{
81
    /* We need to break any double newline sequence
82
     * in or over segments */
83
0
    while(*psz)
84
0
    {
85
0
        const char *p = strchr( psz, '\n' );
86
0
        if( p )
87
0
        {
88
0
            bo_add_escaped( box, p - psz, psz );
89
0
            if( (*c_last == '\n' || *c_last == '\0') && *psz == '\n' )
90
0
                bo_add_mem( box, 8, "&#x2028;" ); /* Add space for empty line */
91
0
            bo_add_8( box, '\n' );
92
0
            *c_last = '\n';
93
0
            psz = p + 1;
94
0
        }
95
0
        else
96
0
        {
97
0
            size_t len = strlen(psz);
98
0
            bo_add_escaped( box, len, psz );
99
0
            *c_last = (len > 0) ? psz[len - 1] : '\0';
100
0
            break;
101
0
        }
102
0
    }
103
0
}
104
105
static block_t *Encode( encoder_t *p_enc, subpicture_t *p_spu )
106
0
{
107
0
    VLC_UNUSED( p_enc );
108
109
0
    if( p_spu == NULL )
110
0
        return NULL;
111
112
0
    bo_t box;
113
0
    if( !bo_init( &box, 8 ) )
114
0
        return NULL;
115
116
0
    const subpicture_region_t *p_region;
117
0
    vlc_spu_regions_foreach_const(p_region, &p_spu->regions)
118
0
    {
119
0
        if(!subpicture_region_IsText( p_region )||
120
0
            p_region->p_text == NULL ||
121
0
            p_region->p_text->psz_text == NULL )
122
0
            continue;
123
124
0
        size_t i_offset = bo_size( &box );
125
126
0
        bo_add_32be( &box, 0 );
127
0
        bo_add_fourcc( &box, "vttc" );
128
129
        /* Payload */
130
131
0
        bo_add_32be( &box, 0 );
132
0
        bo_add_fourcc( &box, "payl" );
133
134
0
        char prevchar = '\0';
135
        /* This should already be UTF-8 encoded, so not much effort... */
136
0
        for( const text_segment_t *p_segment = p_region->p_text;
137
0
             p_segment; p_segment = p_segment->p_next )
138
0
        {
139
0
            if( p_segment->psz_text == NULL )
140
0
                continue;
141
142
0
            if( p_segment->p_ruby )
143
0
            {
144
0
                bo_add_mem( &box, 6, "<ruby>" );
145
0
                for( const text_segment_ruby_t *p_ruby = p_segment->p_ruby;
146
0
                                                p_ruby; p_ruby = p_ruby->p_next )
147
0
                {
148
0
                    WriteText( p_ruby->psz_base, &box, &prevchar );
149
0
                    bo_add_mem( &box, 4, "<rt>" );
150
0
                    WriteText( p_ruby->psz_rt, &box, &prevchar );
151
0
                    bo_add_mem( &box, 5, "</rt>" );
152
0
                }
153
0
                bo_add_mem( &box, 7, "</ruby>" );
154
0
                continue;
155
0
            }
156
157
0
            const text_style_t *style = p_segment->style;
158
0
            if( style && style->i_features )
159
0
            {
160
0
                if( style->i_features & STYLE_HAS_FLAGS )
161
0
                {
162
0
                    if( style->i_style_flags & STYLE_BOLD )
163
0
                        bo_add_mem( &box, 3, "<b>" );
164
0
                    if( style->i_style_flags & STYLE_UNDERLINE )
165
0
                        bo_add_mem( &box, 3, "<u>" );
166
0
                    if( style->i_style_flags & STYLE_ITALIC )
167
0
                        bo_add_mem( &box, 3, "<i>" );
168
0
                }
169
0
            }
170
171
0
            WriteText( p_segment->psz_text, &box, &prevchar );
172
173
0
            if( style && style->i_features )
174
0
            {
175
0
                if( style->i_features & STYLE_HAS_FLAGS )
176
0
                {
177
0
                    if( style->i_style_flags & STYLE_BOLD )
178
0
                        bo_add_mem( &box, 4, "</b>" );
179
0
                    if( style->i_style_flags & STYLE_UNDERLINE )
180
0
                        bo_add_mem( &box, 4, "</u>" );
181
0
                    if( style->i_style_flags & STYLE_ITALIC )
182
0
                        bo_add_mem( &box, 4, "</i>" );
183
0
                }
184
0
            }
185
0
        }
186
187
0
        bo_set_32be( &box, i_offset + 8, bo_size( &box ) - i_offset - 8 );
188
189
        /* Settings */
190
191
0
        if( (p_region->text_flags & (SUBPICTURE_ALIGN_LEFT|SUBPICTURE_ALIGN_RIGHT)) ||
192
0
                (p_region->i_align & SUBPICTURE_ALIGN_TOP) )
193
0
        {
194
0
            size_t i_start = bo_size( &box );
195
196
0
            bo_add_32be( &box, 0 );
197
0
            bo_add_fourcc( &box, "sttg" );
198
199
0
            if( p_region->text_flags & SUBPICTURE_ALIGN_LEFT )
200
0
                bo_add_mem( &box, 10, "align:left" );
201
0
            else if( p_region->text_flags & SUBPICTURE_ALIGN_RIGHT )
202
0
                bo_add_mem( &box, 11, "align:right" );
203
204
0
            if( p_region->i_align & SUBPICTURE_ALIGN_TOP )
205
0
            {
206
0
                float offset = 100.0;
207
0
                if( p_spu->i_original_picture_height > 0 )
208
0
                    offset = offset * p_region->i_y / p_spu->i_original_picture_height;
209
0
                if( bo_size( &box ) != i_start + 8 )
210
0
                    bo_add_8( &box, ' ' );
211
0
                char *psz;
212
0
                int i_printed = vlc_asprintf_c( &psz, "line:%2.2f%%", offset );
213
0
                if( i_printed >= 0 )
214
0
                {
215
0
                    if( i_printed > 0 )
216
0
                        bo_add_mem( &box, i_printed, psz );
217
0
                    free( psz );
218
0
                }
219
0
            }
220
0
            bo_set_32be( &box, i_start, bo_size( &box ) - i_start );
221
0
        }
222
223
224
0
        bo_set_32be( &box, i_offset, bo_size( &box ) - i_offset );
225
0
    }
226
227
0
    if( bo_size( &box ) == 0 ) /* No cue */
228
0
    {
229
0
        bo_add_32be( &box, 8 );
230
0
        bo_add_fourcc( &box, "vtte" );
231
0
    }
232
233
0
    block_t *p_block = box.b;
234
0
    box.b = NULL;
235
0
    bo_deinit( &box );
236
237
0
    if( p_block )
238
0
    {
239
0
        p_block->i_pts = p_block->i_dts = p_spu->i_start;
240
0
        if( p_spu->i_stop != VLC_TICK_INVALID && p_spu->i_stop > p_spu->i_start )
241
0
            p_block->i_length = p_spu->i_stop - p_spu->i_start;
242
0
    }
243
244
0
    return p_block;
245
0
}