2 * Copyright (c) 2012 Clément Bœsch
4 * This file is part of FFmpeg.
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.
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.
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
23 * MicroDVD subtitle decoder
25 * Based on the specifications found here:
26 * https://trac.videolan.org/vlc/ticket/1825#comment:6
29 #include "libavutil/avstring.h"
30 #include "libavutil/parseutils.h"
31 #include "libavutil/bprint.h"
35 static int indexof(const char *s, int c)
37 char *f = strchr(s, c);
38 return f ? (f - s) : -1;
50 #define MICRODVD_PERSISTENT_OFF 0
51 #define MICRODVD_PERSISTENT_ON 1
52 #define MICRODVD_PERSISTENT_OPENED 2
54 // Color, Font, Size, cHarset, stYle, Position, cOordinate
55 #define MICRODVD_TAGS "cfshyYpo"
57 static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
59 int tag_index = indexof(MICRODVD_TAGS, tag.key);
63 memcpy(&tags[tag_index], &tag, sizeof(tag));
66 // italic, bold, underline, strike-through
67 #define MICRODVD_STYLES "ibus"
69 static char *microdvd_load_tags(struct microdvd_tag *tags, char *s)
73 char tag_char = *(s + 1);
74 struct microdvd_tag tag = {0};
76 if (!tag_char || *(s + 2) != ':')
84 tag.persistent = MICRODVD_PERSISTENT_ON;
86 while (*s && *s != '}') {
87 int style_index = indexof(MICRODVD_STYLES, *s);
90 tag.data1 |= (1 << style_index);
95 /* We must distinguish persistent and non-persistent styles
96 * to handle this kind of style tags: {y:ib}{Y:us} */
102 tag.persistent = MICRODVD_PERSISTENT_ON;
106 tag.data1 = strtol(s, &s, 16) & 0x00ffffff;
114 tag.persistent = MICRODVD_PERSISTENT_ON;
116 int len = indexof(s, '}');
120 tag.data_string_len = len;
128 tag.persistent = MICRODVD_PERSISTENT_ON;
130 tag.data1 = strtol(s, &s, 10);
138 //TODO: not yet handled, just parsed.
139 int len = indexof(s, '}');
143 tag.data_string_len = len;
151 tag.persistent = MICRODVD_PERSISTENT_ON;
152 tag.data1 = (*s++ == '1');
160 tag.persistent = MICRODVD_PERSISTENT_ON;
161 tag.data1 = strtol(s, &s, 10);
165 tag.data2 = strtol(s, &s, 10);
171 default: /* Unknown tag, we consider it's text */
178 microdvd_set_tag(tags, tag);
184 static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
187 for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
188 if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED)
190 switch (tags[i].key) {
193 for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
194 if (tags[i].data1 & (1 << sidx))
195 av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
199 av_bprintf(new_line, "{\\c&H%06X&}", tags[i].data1);
203 av_bprintf(new_line, "{\\fn%.*s}",
204 tags[i].data_string_len, tags[i].data_string);
208 av_bprintf(new_line, "{\\fs%d}", tags[i].data1);
212 if (tags[i].data1 == 0)
213 av_bprintf(new_line, "{\\an8}");
217 av_bprintf(new_line, "{\\pos(%d,%d)}",
218 tags[i].data1, tags[i].data2);
221 if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
222 tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
226 static void microdvd_close_no_persistent_tags(AVBPrint *new_line,
227 struct microdvd_tag *tags)
231 for (i = sizeof(MICRODVD_TAGS) - 2; i >= 0; i--) {
232 if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
234 switch (tags[i].key) {
237 for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
238 if (tags[i].data1 & (1 << sidx))
239 av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
243 av_bprintf(new_line, "{\\c}");
247 av_bprintf(new_line, "{\\fn}");
251 av_bprintf(new_line, "{\\fs}");
258 static int microdvd_decode_frame(AVCodecContext *avctx,
259 void *data, int *got_sub_ptr, AVPacket *avpkt)
261 AVSubtitle *sub = data;
265 char *line = avpkt->data;
266 char *end = avpkt->data + avpkt->size;
267 struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
269 if (avpkt->size <= 0)
272 /* To be removed later */
273 if (sscanf(line, "{%*d}{%*[0123456789]}%c", &c) == 1 &&
274 line[avpkt->size - 1] == '\n') {
275 av_log(avctx, AV_LOG_ERROR, "AVPacket is not clean (contains timing "
276 "information and a trailing line break). You need to upgrade "
277 "your libavformat or sanitize your packet.\n");
278 return AVERROR_INVALIDDATA;
281 av_bprint_init(&new_line, 0, 2048);
284 while (line < end && *line) {
286 // parse MicroDVD tags, and open them in ASS
287 line = microdvd_load_tags(tags, line);
288 microdvd_open_tags(&new_line, tags);
290 // simple copy until EOL or forced carriage return
291 while (line < end && *line && *line != '|') {
292 av_bprint_chars(&new_line, *line, 1);
297 if (line < end && *line == '|') {
298 microdvd_close_no_persistent_tags(&new_line, tags);
299 av_bprintf(&new_line, "\\N");
304 av_bprintf(&new_line, "\r\n");
306 av_bprint_finalize(&new_line, &decoded_sub);
308 int64_t start = avpkt->pts;
309 int64_t duration = avpkt->duration;
310 int ts_start = av_rescale_q(start, avctx->time_base, (AVRational){1,100});
311 int ts_duration = duration != -1 ?
312 av_rescale_q(duration, avctx->time_base, (AVRational){1,100}) : -1;
313 ff_ass_add_rect(sub, decoded_sub, ts_start, ts_duration, 0);
315 av_free(decoded_sub);
318 *got_sub_ptr = sub->num_rects > 0;
322 static int microdvd_init(AVCodecContext *avctx)
326 int font_size = ASS_DEFAULT_FONT_SIZE;
327 int color = ASS_DEFAULT_COLOR;
328 int bold = ASS_DEFAULT_BOLD;
329 int italic = ASS_DEFAULT_ITALIC;
330 int underline = ASS_DEFAULT_UNDERLINE;
331 int alignment = ASS_DEFAULT_ALIGNMENT;
332 struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
334 av_bprint_init(&font_buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
335 av_bprintf(&font_buf, "%s", ASS_DEFAULT_FONT);
337 if (avctx->extradata) {
338 microdvd_load_tags(tags, avctx->extradata);
339 for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
340 switch (av_tolower(tags[i].key)) {
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;
353 case 'c': color = tags[i].data1; break;
354 case 's': font_size = tags[i].data1; break;
355 case 'p': alignment = 8; break;
358 av_bprint_clear(&font_buf);
359 av_bprintf(&font_buf, "%.*s",
360 tags[i].data_string_len, tags[i].data_string);
365 return ff_ass_subtitle_header(avctx, font_buf.str, font_size, color,
366 ASS_DEFAULT_BACK_COLOR, bold, italic,
367 underline, alignment);
370 AVCodec ff_microdvd_decoder = {
372 .long_name = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"),
373 .type = AVMEDIA_TYPE_SUBTITLE,
374 .id = AV_CODEC_ID_MICRODVD,
375 .init = microdvd_init,
376 .decode = microdvd_decode_frame,