GCC Code Coverage Report
Directory: ../../../ffmpeg/ Exec Total Coverage
File: src/libavcodec/microdvddec.c Lines: 148 211 70.1 %
Date: 2019-11-18 18:00:01 Branches: 75 126 59.5 %

Line Branch Exec 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/avstring.h"
30
#include "libavutil/parseutils.h"
31
#include "libavutil/bprint.h"
32
#include "avcodec.h"
33
#include "ass.h"
34
35
21
static int indexof(const char *s, int c)
36
{
37
21
    char *f = strchr(s, c);
38
21
    return f ? (f - s) : -1;
39
}
40
41
struct microdvd_tag {
42
    char key;
43
    int persistent;
44
    uint32_t data1;
45
    uint32_t data2;
46
    char *data_string;
47
    int data_string_len;
48
};
49
50
#define MICRODVD_PERSISTENT_OFF     0
51
#define MICRODVD_PERSISTENT_ON      1
52
#define MICRODVD_PERSISTENT_OPENED  2
53
54
// Color, Font, Size, cHarset, stYle, Position, cOordinate
55
#define MICRODVD_TAGS "cfshyYpo"
56
57
14
static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
58
{
59
14
    int tag_index = indexof(MICRODVD_TAGS, tag.key);
60
61
14
    if (tag_index < 0)
62
        return;
63
14
    memcpy(&tags[tag_index], &tag, sizeof(tag));
64
}
65
66
// italic, bold, underline, strike-through
67
#define MICRODVD_STYLES "ibus"
68
69
/* some samples have lines that start with a / indicating non persistent italic
70
 * marker */
71
190
static char *check_for_italic_slash_marker(struct microdvd_tag *tags, char *s)
72
{
73
190
    if (*s == '/') {
74
        struct microdvd_tag tag = tags[indexof(MICRODVD_TAGS, 'y')];
75
        tag.key = 'y';
76
        tag.data1 |= 1 << 0 /* 'i' position in MICRODVD_STYLES */;
77
        microdvd_set_tag(tags, tag);
78
        s++;
79
    }
80
190
    return s;
81
}
82
83
95
static char *microdvd_load_tags(struct microdvd_tag *tags, char *s)
84
{
85
95
    s = check_for_italic_slash_marker(tags, s);
86
87
109
    while (*s == '{') {
88
14
        char *start = s;
89
14
        char tag_char = *(s + 1);
90
14
        struct microdvd_tag tag = {0};
91
92

14
        if (!tag_char || *(s + 2) != ':')
93
            break;
94
14
        s += 3;
95
96



14
        switch (tag_char) {
97
98
        /* Style */
99
1
        case 'Y':
100
1
            tag.persistent = MICRODVD_PERSISTENT_ON;
101
2
        case 'y':
102

6
            while (*s && *s != '}' && s - start < 256) {
103
4
                int style_index = indexof(MICRODVD_STYLES, *s);
104
105
4
                if (style_index >= 0)
106
4
                    tag.data1 |= (1 << style_index);
107
4
                s++;
108
            }
109
2
            if (*s != '}')
110
                break;
111
            /* We must distinguish persistent and non-persistent styles
112
             * to handle this kind of style tags: {y:ib}{Y:us} */
113
2
            tag.key = tag_char;
114
2
            break;
115
116
        /* Color */
117
        case 'C':
118
            tag.persistent = MICRODVD_PERSISTENT_ON;
119
5
        case 'c':
120

10
            while (*s == '$' || *s == '#')
121
5
                s++;
122
5
            tag.data1 = strtol(s, &s, 16) & 0x00ffffff;
123
5
            if (*s != '}')
124
                break;
125
5
            tag.key = 'c';
126
5
            break;
127
128
        /* Font name */
129
3
        case 'F':
130
3
            tag.persistent = MICRODVD_PERSISTENT_ON;
131
3
        case 'f': {
132
3
            int len = indexof(s, '}');
133
3
            if (len < 0)
134
                break;
135
3
            tag.data_string = s;
136
3
            tag.data_string_len = len;
137
3
            s += len;
138
3
            tag.key = 'f';
139
3
            break;
140
        }
141
142
        /* Font size */
143
3
        case 'S':
144
3
            tag.persistent = MICRODVD_PERSISTENT_ON;
145
3
        case 's':
146
3
            tag.data1 = strtol(s, &s, 10);
147
3
            if (*s != '}')
148
                break;
149
3
            tag.key = 's';
150
3
            break;
151
152
        /* Charset */
153
        case 'H': {
154
            //TODO: not yet handled, just parsed.
155
            int len = indexof(s, '}');
156
            if (len < 0)
157
                break;
158
            tag.data_string = s;
159
            tag.data_string_len = len;
160
            s += len;
161
            tag.key = 'h';
162
            break;
163
        }
164
165
        /* Position */
166
        case 'P':
167
            if (!*s)
168
                break;
169
            tag.persistent = MICRODVD_PERSISTENT_ON;
170
            tag.data1 = (*s++ == '1');
171
            if (*s != '}')
172
                break;
173
            tag.key = 'p';
174
            break;
175
176
        /* Coordinates */
177
1
        case 'o':
178
1
            tag.persistent = MICRODVD_PERSISTENT_ON;
179
1
            tag.data1 = strtol(s, &s, 10);
180
1
            if (*s != ',')
181
                break;
182
1
            s++;
183
1
            tag.data2 = strtol(s, &s, 10);
184
1
            if (*s != '}')
185
                break;
186
1
            tag.key = 'o';
187
1
            break;
188
189
        default:    /* Unknown tag, we consider it's text */
190
            break;
191
        }
192
193
14
        if (tag.key == 0)
194
            return start;
195
196
14
        microdvd_set_tag(tags, tag);
197
14
        s++;
198
    }
199
95
    return check_for_italic_slash_marker(tags, s);
200
}
201
202
92
static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
203
{
204
    int i, sidx;
205
828
    for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
206
736
        if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED)
207
1
            continue;
208

735
        switch (tags[i].key) {
209
2
        case 'Y':
210
        case 'y':
211
10
            for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
212
8
                if (tags[i].data1 & (1 << sidx))
213
4
                    av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
214
2
            break;
215
216
2
        case 'c':
217
2
            av_bprintf(new_line, "{\\c&H%06"PRIX32"&}", tags[i].data1);
218
2
            break;
219
220
        case 'f':
221
            av_bprintf(new_line, "{\\fn%.*s}",
222
                       tags[i].data_string_len, tags[i].data_string);
223
            break;
224
225
        case 's':
226
            av_bprintf(new_line, "{\\fs%"PRId32"}", tags[i].data1);
227
            break;
228
229
        case 'p':
230
            if (tags[i].data1 == 0)
231
                av_bprintf(new_line, "{\\an8}");
232
            break;
233
234
1
        case 'o':
235
1
            av_bprintf(new_line, "{\\pos(%"PRId32",%"PRId32")}",
236
1
                       tags[i].data1, tags[i].data2);
237
1
            break;
238
        }
239
735
        if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
240
2
            tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
241
    }
242
92
}
243
244
32
static void microdvd_close_no_persistent_tags(AVBPrint *new_line,
245
                                              struct microdvd_tag *tags)
246
{
247
    int i, sidx;
248
249
288
    for (i = sizeof(MICRODVD_TAGS) - 2; i >= 0; i--) {
250
256
        if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
251
1
            continue;
252

255
        switch (tags[i].key) {
253
254
1
        case 'y':
255
5
            for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
256
4
                if (tags[i].data1 & (1 << sidx))
257
2
                    av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
258
1
            break;
259
260
2
        case 'c':
261
2
            av_bprintf(new_line, "{\\c}");
262
2
            break;
263
264
        case 'f':
265
            av_bprintf(new_line, "{\\fn}");
266
            break;
267
268
        case 's':
269
            av_bprintf(new_line, "{\\fs}");
270
            break;
271
        }
272
255
        tags[i].key = 0;
273
    }
274
32
}
275
276
60
static int microdvd_decode_frame(AVCodecContext *avctx,
277
                                 void *data, int *got_sub_ptr, AVPacket *avpkt)
278
{
279
60
    AVSubtitle *sub = data;
280
    AVBPrint new_line;
281
60
    char *line = avpkt->data;
282
60
    char *end = avpkt->data + avpkt->size;
283
60
    FFASSDecoderContext *s = avctx->priv_data;
284
60
    struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
285
286
60
    if (avpkt->size <= 0)
287
        return avpkt->size;
288
289
60
    av_bprint_init(&new_line, 0, 2048);
290
291
    // subtitle content
292

152
    while (line < end && *line) {
293
294
        // parse MicroDVD tags, and open them in ASS
295
92
        line = microdvd_load_tags(tags, line);
296
92
        microdvd_open_tags(&new_line, tags);
297
298
        // simple copy until EOL or forced carriage return
299

3074
        while (line < end && *line && *line != '|') {
300
2982
            av_bprint_chars(&new_line, *line, 1);
301
2982
            line++;
302
        }
303
304
        // line split
305

92
        if (line < end && *line == '|') {
306
32
            microdvd_close_no_persistent_tags(&new_line, tags);
307
32
            av_bprintf(&new_line, "\\N");
308
32
            line++;
309
        }
310
    }
311
60
    if (new_line.len) {
312
60
        int ret = ff_ass_add_rect(sub, new_line.str, s->readorder++, 0, NULL, NULL);
313
60
        av_bprint_finalize(&new_line, NULL);
314
60
        if (ret < 0)
315
            return ret;
316
    }
317
318
60
    *got_sub_ptr = sub->num_rects > 0;
319
60
    return avpkt->size;
320
}
321
322
5
static int microdvd_init(AVCodecContext *avctx)
323
{
324
    int i, sidx;
325
    AVBPrint font_buf;
326
5
    int font_size    = ASS_DEFAULT_FONT_SIZE;
327
5
    int color        = ASS_DEFAULT_COLOR;
328
5
    int bold         = ASS_DEFAULT_BOLD;
329
5
    int italic       = ASS_DEFAULT_ITALIC;
330
5
    int underline    = ASS_DEFAULT_UNDERLINE;
331
5
    int alignment    = ASS_DEFAULT_ALIGNMENT;
332
5
    struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
333
334
5
    av_bprint_init(&font_buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
335
5
    av_bprintf(&font_buf, "%s", ASS_DEFAULT_FONT);
336
337
5
    if (avctx->extradata) {
338
3
        microdvd_load_tags(tags, avctx->extradata);
339
27
        for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
340

24
            switch (av_tolower(tags[i].key)) {
341
            case 'y':
342
                for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) {
343
                    if (tags[i].data1 & (1 << sidx)) {
344
                        switch (MICRODVD_STYLES[sidx]) {
345
                        case 'i': italic    = 1; break;
346
                        case 'b': bold      = 1; break;
347
                        case 'u': underline = 1; break;
348
                        }
349
                    }
350
                }
351
                break;
352
353
3
            case 'c': color     = tags[i].data1; break;
354
3
            case 's': font_size = tags[i].data1; break;
355
            case 'p': alignment =             8; break;
356
357
3
            case 'f':
358
3
                av_bprint_clear(&font_buf);
359
3
                av_bprintf(&font_buf, "%.*s",
360
                           tags[i].data_string_len, tags[i].data_string);
361
3
                break;
362
            }
363
24
        }
364
    }
365
5
    return ff_ass_subtitle_header(avctx, font_buf.str, font_size, color,
366
                                  ASS_DEFAULT_BACK_COLOR, bold, italic,
367
                                  underline, ASS_DEFAULT_BORDERSTYLE,
368
                                  alignment);
369
}
370
371
AVCodec ff_microdvd_decoder = {
372
    .name         = "microdvd",
373
    .long_name    = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"),
374
    .type         = AVMEDIA_TYPE_SUBTITLE,
375
    .id           = AV_CODEC_ID_MICRODVD,
376
    .init         = microdvd_init,
377
    .decode       = microdvd_decode_frame,
378
    .flush        = ff_ass_decoder_flush,
379
    .priv_data_size = sizeof(FFASSDecoderContext),
380
};