FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavcodec/microdvddec.c
Date: 2026-05-03 08:24:11
Exec Total Coverage
Lines: 146 209 69.9%
Functions: 8 8 100.0%
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/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 21 static int indexof(const char *s, int c)
38 {
39 21 char *f = strchr(s, c);
40
1/2
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
21 return f ? (f - s) : -1;
41 }
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 #define MICRODVD_PERSISTENT_OFF 0
53 #define MICRODVD_PERSISTENT_ON 1
54 #define MICRODVD_PERSISTENT_OPENED 2
55
56 // Color, Font, Size, cHarset, stYle, Position, cOordinate
57 #define MICRODVD_TAGS "cfshyYpo"
58
59 14 static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
60 {
61 14 int tag_index = indexof(MICRODVD_TAGS, tag.key);
62
63
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (tag_index < 0)
64 return;
65 14 memcpy(&tags[tag_index], &tag, sizeof(tag));
66 }
67
68 // italic, bold, underline, strike-through
69 #define MICRODVD_STYLES "ibus"
70
71 /* some samples have lines that start with a / indicating non persistent italic
72 * marker */
73 190 static char *check_for_italic_slash_marker(struct microdvd_tag *tags, char *s)
74 {
75
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 190 times.
190 if (*s == '/') {
76 struct microdvd_tag tag = tags[indexof(MICRODVD_TAGS, 'y')];
77 tag.key = 'y';
78 tag.data1 |= 1 << 0 /* 'i' position in MICRODVD_STYLES */;
79 microdvd_set_tag(tags, tag);
80 s++;
81 }
82 190 return s;
83 }
84
85 95 static char *microdvd_load_tags(struct microdvd_tag *tags, char *s)
86 {
87 95 s = check_for_italic_slash_marker(tags, s);
88
89
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 95 times.
109 while (*s == '{') {
90 14 char *start = s;
91 14 char tag_char = *(s + 1);
92 14 struct microdvd_tag tag = {0};
93
94
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
14 if (!tag_char || *(s + 2) != ':')
95 break;
96 14 s += 3;
97
98
6/12
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
14 switch (tag_char) {
99
100 /* Style */
101 1 case 'Y':
102 1 tag.persistent = MICRODVD_PERSISTENT_ON;
103 av_fallthrough;
104 2 case 'y':
105
4/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
6 while (*s && *s != '}' && s - start < 256) {
106 4 int style_index = indexof(MICRODVD_STYLES, *s);
107
108
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (style_index >= 0)
109 4 tag.data1 |= (1 << style_index);
110 4 s++;
111 }
112
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (*s != '}')
113 break;
114 /* We must distinguish persistent and non-persistent styles
115 * to handle this kind of style tags: {y:ib}{Y:us} */
116 2 tag.key = tag_char;
117 2 break;
118
119 /* Color */
120 case 'C':
121 tag.persistent = MICRODVD_PERSISTENT_ON;
122 av_fallthrough;
123 5 case 'c':
124
3/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
10 while (*s == '$' || *s == '#')
125 5 s++;
126 5 tag.data1 = strtol(s, &s, 16) & 0x00ffffff;
127
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (*s != '}')
128 break;
129 5 tag.key = 'c';
130 5 break;
131
132 /* Font name */
133 3 case 'F':
134 3 tag.persistent = MICRODVD_PERSISTENT_ON;
135 av_fallthrough;
136 3 case 'f': {
137 3 int len = indexof(s, '}');
138
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (len < 0)
139 break;
140 3 tag.data_string = s;
141 3 tag.data_string_len = len;
142 3 s += len;
143 3 tag.key = 'f';
144 3 break;
145 }
146
147 /* Font size */
148 3 case 'S':
149 3 tag.persistent = MICRODVD_PERSISTENT_ON;
150 av_fallthrough;
151 3 case 's':
152 3 tag.data1 = strtol(s, &s, 10);
153
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (*s != '}')
154 break;
155 3 tag.key = 's';
156 3 break;
157
158 /* Charset */
159 case 'H': {
160 //TODO: not yet handled, just parsed.
161 int len = indexof(s, '}');
162 if (len < 0)
163 break;
164 tag.data_string = s;
165 tag.data_string_len = len;
166 s += len;
167 tag.key = 'h';
168 break;
169 }
170
171 /* Position */
172 case 'P':
173 if (!*s)
174 break;
175 tag.persistent = MICRODVD_PERSISTENT_ON;
176 tag.data1 = (*s++ == '1');
177 if (*s != '}')
178 break;
179 tag.key = 'p';
180 break;
181
182 /* Coordinates */
183 1 case 'o':
184 1 tag.persistent = MICRODVD_PERSISTENT_ON;
185 1 tag.data1 = strtol(s, &s, 10);
186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (*s != ',')
187 break;
188 1 s++;
189 1 tag.data2 = strtol(s, &s, 10);
190
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (*s != '}')
191 break;
192 1 tag.key = 'o';
193 1 break;
194
195 default: /* Unknown tag, we consider it's text */
196 break;
197 }
198
199
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (tag.key == 0)
200 return start;
201
202 14 microdvd_set_tag(tags, tag);
203 14 s++;
204 }
205 95 return check_for_italic_slash_marker(tags, s);
206 }
207
208 92 static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
209 {
210 int i, sidx;
211
2/2
✓ Branch 0 taken 736 times.
✓ Branch 1 taken 92 times.
828 for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
212
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 735 times.
736 if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED)
213 1 continue;
214
4/7
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 730 times.
735 switch (tags[i].key) {
215 2 case 'Y':
216 case 'y':
217
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
218
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 if (tags[i].data1 & (1 << sidx))
219 4 av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
220 2 break;
221
222 2 case 'c':
223 2 av_bprintf(new_line, "{\\c&H%06"PRIX32"&}", tags[i].data1);
224 2 break;
225
226 case 'f':
227 av_bprintf(new_line, "{\\fn%.*s}",
228 tags[i].data_string_len, tags[i].data_string);
229 break;
230
231 case 's':
232 av_bprintf(new_line, "{\\fs%"PRId32"}", tags[i].data1);
233 break;
234
235 case 'p':
236 if (tags[i].data1 == 0)
237 av_bprintf(new_line, "{\\an8}");
238 break;
239
240 1 case 'o':
241 1 av_bprintf(new_line, "{\\pos(%"PRId32",%"PRId32")}",
242 1 tags[i].data1, tags[i].data2);
243 1 break;
244 }
245
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 733 times.
735 if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
246 2 tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
247 }
248 92 }
249
250 32 static void microdvd_close_no_persistent_tags(AVBPrint *new_line,
251 struct microdvd_tag *tags)
252 {
253 int i, sidx;
254
255
2/2
✓ Branch 0 taken 256 times.
✓ Branch 1 taken 32 times.
288 for (i = sizeof(MICRODVD_TAGS) - 2; i >= 0; i--) {
256
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 255 times.
256 if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
257 1 continue;
258
3/5
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 252 times.
255 switch (tags[i].key) {
259
260 1 case 'y':
261
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
262
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (tags[i].data1 & (1 << sidx))
263 2 av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
264 1 break;
265
266 2 case 'c':
267 2 av_bprintf(new_line, "{\\c}");
268 2 break;
269
270 case 'f':
271 av_bprintf(new_line, "{\\fn}");
272 break;
273
274 case 's':
275 av_bprintf(new_line, "{\\fs}");
276 break;
277 }
278 255 tags[i].key = 0;
279 }
280 32 }
281
282 60 static int microdvd_decode_frame(AVCodecContext *avctx, AVSubtitle *sub,
283 int *got_sub_ptr, const AVPacket *avpkt)
284 {
285 AVBPrint new_line;
286 60 char *line = avpkt->data;
287 60 char *end = avpkt->data + avpkt->size;
288 60 FFASSDecoderContext *s = avctx->priv_data;
289 60 struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
290
291
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 60 times.
60 if (avpkt->size <= 0)
292 return avpkt->size;
293
294 60 av_bprint_init(&new_line, 0, 2048);
295
296 // subtitle content
297
3/4
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 60 times.
✓ Branch 2 taken 92 times.
✗ Branch 3 not taken.
152 while (line < end && *line) {
298
299 // parse MicroDVD tags, and open them in ASS
300 92 line = microdvd_load_tags(tags, line);
301 92 microdvd_open_tags(&new_line, tags);
302
303 // simple copy until EOL or forced carriage return
304
5/6
✓ Branch 0 taken 3014 times.
✓ Branch 1 taken 60 times.
✓ Branch 2 taken 3014 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2982 times.
✓ Branch 5 taken 32 times.
3074 while (line < end && *line && *line != '|') {
305 2982 av_bprint_chars(&new_line, *line, 1);
306 2982 line++;
307 }
308
309 // line split
310
3/4
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 60 times.
✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
92 if (line < end && *line == '|') {
311 32 microdvd_close_no_persistent_tags(&new_line, tags);
312 32 av_bprintf(&new_line, "\\N");
313 32 line++;
314 }
315 }
316
1/2
✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
60 if (new_line.len) {
317 60 int ret = ff_ass_add_rect(sub, new_line.str, s->readorder++, 0, NULL, NULL);
318 60 av_bprint_finalize(&new_line, NULL);
319
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 60 times.
60 if (ret < 0)
320 return ret;
321 }
322
323 60 *got_sub_ptr = sub->num_rects > 0;
324 60 return avpkt->size;
325 }
326
327 5 static av_cold int microdvd_init(AVCodecContext *avctx)
328 {
329 int i, sidx;
330 AVBPrint font_buf;
331 5 int font_size = ASS_DEFAULT_FONT_SIZE;
332 5 int color = ASS_DEFAULT_COLOR;
333 5 int bold = ASS_DEFAULT_BOLD;
334 5 int italic = ASS_DEFAULT_ITALIC;
335 5 int underline = ASS_DEFAULT_UNDERLINE;
336 5 int alignment = ASS_DEFAULT_ALIGNMENT;
337 5 struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
338
339 5 av_bprint_init(&font_buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
340 5 av_bprintf(&font_buf, "%s", ASS_DEFAULT_FONT);
341
342
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if (avctx->extradata) {
343 3 microdvd_load_tags(tags, avctx->extradata);
344
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 3 times.
27 for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
345
4/6
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 15 times.
24 switch (av_tolower(tags[i].key)) {
346 case 'y':
347 for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) {
348 if (tags[i].data1 & (1 << sidx)) {
349 switch (MICRODVD_STYLES[sidx]) {
350 case 'i': italic = 1; break;
351 case 'b': bold = 1; break;
352 case 'u': underline = 1; break;
353 }
354 }
355 }
356 break;
357
358 3 case 'c': color = tags[i].data1; break;
359 3 case 's': font_size = tags[i].data1; break;
360 case 'p': alignment = 8; break;
361
362 3 case 'f':
363 3 av_bprint_clear(&font_buf);
364 3 av_bprintf(&font_buf, "%.*s",
365 tags[i].data_string_len, tags[i].data_string);
366 3 break;
367 }
368 }
369 }
370 5 return ff_ass_subtitle_header(avctx, font_buf.str, font_size, color,
371 ASS_DEFAULT_BACK_COLOR, bold, italic,
372 underline, ASS_DEFAULT_BORDERSTYLE,
373 alignment);
374 }
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 };
386