/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 | | }; |