Coverage Report

Created: 2026-10-28 08:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ffmpeg/libavcodec/microdvddec.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2012 Clément Bœsch
3
 *
4
 * This file is part of FFmpeg.
5
 *
6
 * FFmpeg is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public
8
 * License as published by the Free Software Foundation; either
9
 * version 2.1 of the License, or (at your option) any later version.
10
 *
11
 * FFmpeg 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 GNU
14
 * Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public
17
 * License along with FFmpeg; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
 */
20
21
/**
22
 * @file
23
 * MicroDVD subtitle decoder
24
 *
25
 * Based on the specifications found here:
26
 * https://trac.videolan.org/vlc/ticket/1825#comment:6
27
 */
28
29
#include "libavutil/attributes.h"
30
#include "libavutil/avstring.h"
31
#include "libavutil/parseutils.h"
32
#include "libavutil/bprint.h"
33
#include "avcodec.h"
34
#include "ass.h"
35
#include "codec_internal.h"
36
37
static int indexof(const char *s, int c)
38
13.7M
{
39
13.7M
    const char *f = strchr(s, c);
40
13.7M
    return f ? (f - s) : -1;
41
13.7M
}
42
43
struct microdvd_tag {
44
    char key;
45
    int persistent;
46
    uint32_t data1;
47
    uint32_t data2;
48
    char *data_string;
49
    int data_string_len;
50
};
51
52
54.7M
#define MICRODVD_PERSISTENT_OFF     0
53
48.6M
#define MICRODVD_PERSISTENT_ON      1
54
55.9M
#define MICRODVD_PERSISTENT_OPENED  2
55
56
// Color, Font, Size, cHarset, stYle, Position, cOordinate
57
83.3M
#define MICRODVD_TAGS "cfshyYpo"
58
59
static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
60
6.82M
{
61
6.82M
    int tag_index = indexof(MICRODVD_TAGS, tag.key);
62
63
6.82M
    if (tag_index < 0)
64
0
        return;
65
6.82M
    memcpy(&tags[tag_index], &tag, sizeof(tag));
66
6.82M
}
67
68
// italic, bold, underline, strike-through
69
95.5M
#define MICRODVD_STYLES "ibus"
70
71
/* some samples have lines that start with a / indicating non persistent italic
72
 * marker */
73
static char *check_for_italic_slash_marker(struct microdvd_tag *tags, char *s)
74
13.9M
{
75
13.9M
    if (*s == '/') {
76
6.81M
        struct microdvd_tag tag = tags[indexof(MICRODVD_TAGS, 'y')];
77
6.81M
        tag.key = 'y';
78
6.81M
        tag.data1 |= 1 << 0 /* 'i' position in MICRODVD_STYLES */;
79
6.81M
        microdvd_set_tag(tags, tag);
80
6.81M
        s++;
81
6.81M
    }
82
13.9M
    return s;
83
13.9M
}
84
85
static char *microdvd_load_tags(struct microdvd_tag *tags, char *s)
86
6.99M
{
87
6.99M
    s = check_for_italic_slash_marker(tags, s);
88
89
6.99M
    while (*s == '{') {
90
56.1k
        char *start = s;
91
56.1k
        char tag_char = *(s + 1);
92
56.1k
        struct microdvd_tag tag = {0};
93
94
56.1k
        if (!tag_char || *(s + 2) != ':')
95
44.3k
            break;
96
11.7k
        s += 3;
97
98
11.7k
        switch (tag_char) {
99
100
        /* Style */
101
841
        case 'Y':
102
841
            tag.persistent = MICRODVD_PERSISTENT_ON;
103
841
            av_fallthrough;
104
3.64k
        case 'y':
105
128k
            while (*s && *s != '}' && s - start < 256) {
106
125k
                int style_index = indexof(MICRODVD_STYLES, *s);
107
108
125k
                if (style_index >= 0)
109
7.95k
                    tag.data1 |= (1 << style_index);
110
125k
                s++;
111
125k
            }
112
3.64k
            if (*s != '}')
113
892
                break;
114
            /* We must distinguish persistent and non-persistent styles
115
             * to handle this kind of style tags: {y:ib}{Y:us} */
116
2.74k
            tag.key = tag_char;
117
2.74k
            break;
118
119
        /* Color */
120
407
        case 'C':
121
407
            tag.persistent = MICRODVD_PERSISTENT_ON;
122
407
            av_fallthrough;
123
899
        case 'c':
124
1.30k
            while (*s == '$' || *s == '#')
125
402
                s++;
126
899
            tag.data1 = strtol(s, &s, 16) & 0x00ffffff;
127
899
            if (*s != '}')
128
293
                break;
129
606
            tag.key = 'c';
130
606
            break;
131
132
        /* Font name */
133
627
        case 'F':
134
627
            tag.persistent = MICRODVD_PERSISTENT_ON;
135
627
            av_fallthrough;
136
1.78k
        case 'f': {
137
1.78k
            int len = indexof(s, '}');
138
1.78k
            if (len < 0)
139
498
                break;
140
1.28k
            tag.data_string = s;
141
1.28k
            tag.data_string_len = len;
142
1.28k
            s += len;
143
1.28k
            tag.key = 'f';
144
1.28k
            break;
145
1.78k
        }
146
147
        /* Font size */
148
474
        case 'S':
149
474
            tag.persistent = MICRODVD_PERSISTENT_ON;
150
474
            av_fallthrough;
151
1.44k
        case 's':
152
1.44k
            tag.data1 = strtol(s, &s, 10);
153
1.44k
            if (*s != '}')
154
489
                break;
155
959
            tag.key = 's';
156
959
            break;
157
158
        /* Charset */
159
659
        case 'H': {
160
            //TODO: not yet handled, just parsed.
161
659
            int len = indexof(s, '}');
162
659
            if (len < 0)
163
321
                break;
164
338
            tag.data_string = s;
165
338
            tag.data_string_len = len;
166
338
            s += len;
167
338
            tag.key = 'h';
168
338
            break;
169
659
        }
170
171
        /* Position */
172
1.09k
        case 'P':
173
1.09k
            if (!*s)
174
206
                break;
175
890
            tag.persistent = MICRODVD_PERSISTENT_ON;
176
890
            tag.data1 = (*s++ == '1');
177
890
            if (*s != '}')
178
272
                break;
179
618
            tag.key = 'p';
180
618
            break;
181
182
        /* Coordinates */
183
2.03k
        case 'o':
184
2.03k
            tag.persistent = MICRODVD_PERSISTENT_ON;
185
2.03k
            tag.data1 = strtol(s, &s, 10);
186
2.03k
            if (*s != ',')
187
465
                break;
188
1.57k
            s++;
189
1.57k
            tag.data2 = strtol(s, &s, 10);
190
1.57k
            if (*s != '}')
191
614
                break;
192
957
            tag.key = 'o';
193
957
            break;
194
195
212
        default:    /* Unknown tag, we consider it's text */
196
212
            break;
197
11.7k
        }
198
199
11.7k
        if (tag.key == 0)
200
4.26k
            return start;
201
202
7.51k
        microdvd_set_tag(tags, tag);
203
7.51k
        s++;
204
7.51k
    }
205
6.98M
    return check_for_italic_slash_marker(tags, s);
206
6.99M
}
207
208
static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
209
6.99M
{
210
6.99M
    int i, sidx;
211
62.9M
    for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
212
55.9M
        if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED)
213
7.23M
            continue;
214
48.6M
        switch (tags[i].key) {
215
448
        case 'Y':
216
6.81M
        case 'y':
217
34.0M
            for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
218
27.2M
                if (tags[i].data1 & (1 << sidx))
219
27.2M
                    av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
220
6.81M
            break;
221
222
279
        case 'c':
223
279
            av_bprintf(new_line, "{\\c&H%06"PRIX32"&}", tags[i].data1);
224
279
            break;
225
226
1.08k
        case 'f':
227
1.08k
            av_bprintf(new_line, "{\\fn%.*s}",
228
1.08k
                       tags[i].data_string_len, tags[i].data_string);
229
1.08k
            break;
230
231
638
        case 's':
232
638
            av_bprintf(new_line, "{\\fs%"PRId32"}", tags[i].data1);
233
638
            break;
234
235
430
        case 'p':
236
430
            if (tags[i].data1 == 0)
237
221
                av_bprintf(new_line, "{\\an8}");
238
430
            break;
239
240
700
        case 'o':
241
700
            av_bprintf(new_line, "{\\pos(%"PRId32",%"PRId32")}",
242
700
                       tags[i].data1, tags[i].data2);
243
700
            break;
244
48.6M
        }
245
48.6M
        if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
246
2.15k
            tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
247
48.6M
    }
248
6.99M
}
249
250
static void microdvd_close_no_persistent_tags(AVBPrint *new_line,
251
                                              struct microdvd_tag *tags)
252
6.83M
{
253
6.83M
    int i, sidx;
254
255
61.5M
    for (i = sizeof(MICRODVD_TAGS) - 2; i >= 0; i--) {
256
54.7M
        if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
257
7.23M
            continue;
258
47.4M
        switch (tags[i].key) {
259
260
6.81M
        case 'y':
261
34.0M
            for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
262
27.2M
                if (tags[i].data1 & (1 << sidx))
263
27.2M
                    av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
264
6.81M
            break;
265
266
255
        case 'c':
267
255
            av_bprintf(new_line, "{\\c}");
268
255
            break;
269
270
548
        case 'f':
271
548
            av_bprintf(new_line, "{\\fn}");
272
548
            break;
273
274
383
        case 's':
275
383
            av_bprintf(new_line, "{\\fs}");
276
383
            break;
277
47.4M
        }
278
47.4M
        tags[i].key = 0;
279
47.4M
    }
280
6.83M
}
281
282
static int microdvd_decode_frame(AVCodecContext *avctx, AVSubtitle *sub,
283
                                 int *got_sub_ptr, const AVPacket *avpkt)
284
166k
{
285
166k
    AVBPrint new_line;
286
166k
    char *line = avpkt->data;
287
166k
    char *end = avpkt->data + avpkt->size;
288
166k
    FFASSDecoderContext *s = avctx->priv_data;
289
166k
    struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
290
291
166k
    if (avpkt->size <= 0)
292
0
        return avpkt->size;
293
294
166k
    av_bprint_init(&new_line, 0, 2048);
295
296
    // subtitle content
297
7.15M
    while (line < end && *line) {
298
299
        // parse MicroDVD tags, and open them in ASS
300
6.99M
        line = microdvd_load_tags(tags, line);
301
6.99M
        microdvd_open_tags(&new_line, tags);
302
303
        // simple copy until EOL or forced carriage return
304
7.58M
        while (line < end && *line && *line != '|') {
305
595k
            av_bprint_chars(&new_line, *line, 1);
306
595k
            line++;
307
595k
        }
308
309
        // line split
310
6.99M
        if (line < end && *line == '|') {
311
6.83M
            microdvd_close_no_persistent_tags(&new_line, tags);
312
6.83M
            av_bprintf(&new_line, "\\N");
313
6.83M
            line++;
314
6.83M
        }
315
6.99M
    }
316
166k
    if (new_line.len) {
317
154k
        int ret = ff_ass_add_rect(sub, new_line.str, s->readorder++, 0, NULL, NULL);
318
154k
        av_bprint_finalize(&new_line, NULL);
319
154k
        if (ret < 0)
320
0
            return ret;
321
154k
    }
322
323
166k
    *got_sub_ptr = sub->num_rects > 0;
324
166k
    return avpkt->size;
325
166k
}
326
327
static av_cold int microdvd_init(AVCodecContext *avctx)
328
1.55k
{
329
1.55k
    int i, sidx;
330
1.55k
    AVBPrint font_buf;
331
1.55k
    int font_size    = ASS_DEFAULT_FONT_SIZE;
332
1.55k
    int color        = ASS_DEFAULT_COLOR;
333
1.55k
    int bold         = ASS_DEFAULT_BOLD;
334
1.55k
    int italic       = ASS_DEFAULT_ITALIC;
335
1.55k
    int underline    = ASS_DEFAULT_UNDERLINE;
336
1.55k
    int alignment    = ASS_DEFAULT_ALIGNMENT;
337
1.55k
    struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
338
339
1.55k
    av_bprint_init(&font_buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
340
1.55k
    av_bprintf(&font_buf, "%s", ASS_DEFAULT_FONT);
341
342
1.55k
    if (avctx->extradata) {
343
63
        microdvd_load_tags(tags, avctx->extradata);
344
567
        for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
345
504
            switch (av_tolower(tags[i].key)) {
346
24
            case 'y':
347
120
                for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) {
348
96
                    if (tags[i].data1 & (1 << sidx)) {
349
20
                        switch (MICRODVD_STYLES[sidx]) {
350
8
                        case 'i': italic    = 1; break;
351
4
                        case 'b': bold      = 1; break;
352
4
                        case 'u': underline = 1; break;
353
20
                        }
354
20
                    }
355
96
                }
356
24
                break;
357
358
24
            case 'c': color     = tags[i].data1; break;
359
6
            case 's': font_size = tags[i].data1; break;
360
3
            case 'p': alignment =             8; break;
361
362
6
            case 'f':
363
6
                av_bprint_clear(&font_buf);
364
6
                av_bprintf(&font_buf, "%.*s",
365
6
                           tags[i].data_string_len, tags[i].data_string);
366
6
                break;
367
504
            }
368
504
        }
369
63
    }
370
1.55k
    return ff_ass_subtitle_header(avctx, font_buf.str, font_size, color,
371
1.55k
                                  ASS_DEFAULT_BACK_COLOR, bold, italic,
372
1.55k
                                  underline, ASS_DEFAULT_BORDERSTYLE,
373
1.55k
                                  alignment);
374
1.55k
}
375
376
const FFCodec ff_microdvd_decoder = {
377
    .p.name       = "microdvd",
378
    CODEC_LONG_NAME("MicroDVD subtitle"),
379
    .p.type       = AVMEDIA_TYPE_SUBTITLE,
380
    .p.id         = AV_CODEC_ID_MICRODVD,
381
    .init         = microdvd_init,
382
    FF_CODEC_DECODE_SUB_CB(microdvd_decode_frame),
383
    .flush        = ff_ass_decoder_flush,
384
    .priv_data_size = sizeof(FFASSDecoderContext),
385
};