yading@10: /* yading@10: * Copyright (c) 2012 Clément Bœsch yading@10: * yading@10: * This file is part of FFmpeg. yading@10: * yading@10: * FFmpeg is free software; you can redistribute it and/or yading@10: * modify it under the terms of the GNU Lesser General Public yading@10: * License as published by the Free Software Foundation; either yading@10: * version 2.1 of the License, or (at your option) any later version. yading@10: * yading@10: * FFmpeg is distributed in the hope that it will be useful, yading@10: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@10: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU yading@10: * Lesser General Public License for more details. yading@10: * yading@10: * You should have received a copy of the GNU Lesser General Public yading@10: * License along with FFmpeg; if not, write to the Free Software yading@10: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@10: */ yading@10: yading@10: /** yading@10: * @file yading@10: * MicroDVD subtitle decoder yading@10: * yading@10: * Based on the specifications found here: yading@10: * https://trac.videolan.org/vlc/ticket/1825#comment:6 yading@10: */ yading@10: yading@10: #include "libavutil/avstring.h" yading@10: #include "libavutil/parseutils.h" yading@10: #include "libavutil/bprint.h" yading@10: #include "avcodec.h" yading@10: #include "ass.h" yading@10: yading@10: static int indexof(const char *s, int c) yading@10: { yading@10: char *f = strchr(s, c); yading@10: return f ? (f - s) : -1; yading@10: } yading@10: yading@10: struct microdvd_tag { yading@10: char key; yading@10: int persistent; yading@10: uint32_t data1; yading@10: uint32_t data2; yading@10: char *data_string; yading@10: int data_string_len; yading@10: }; yading@10: yading@10: #define MICRODVD_PERSISTENT_OFF 0 yading@10: #define MICRODVD_PERSISTENT_ON 1 yading@10: #define MICRODVD_PERSISTENT_OPENED 2 yading@10: yading@10: // Color, Font, Size, cHarset, stYle, Position, cOordinate yading@10: #define MICRODVD_TAGS "cfshyYpo" yading@10: yading@10: static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag) yading@10: { yading@10: int tag_index = indexof(MICRODVD_TAGS, tag.key); yading@10: yading@10: if (tag_index < 0) yading@10: return; yading@10: memcpy(&tags[tag_index], &tag, sizeof(tag)); yading@10: } yading@10: yading@10: // italic, bold, underline, strike-through yading@10: #define MICRODVD_STYLES "ibus" yading@10: yading@10: static char *microdvd_load_tags(struct microdvd_tag *tags, char *s) yading@10: { yading@10: while (*s == '{') { yading@10: char *start = s; yading@10: char tag_char = *(s + 1); yading@10: struct microdvd_tag tag = {0}; yading@10: yading@10: if (!tag_char || *(s + 2) != ':') yading@10: break; yading@10: s += 3; yading@10: yading@10: switch (tag_char) { yading@10: yading@10: /* Style */ yading@10: case 'Y': yading@10: tag.persistent = MICRODVD_PERSISTENT_ON; yading@10: case 'y': yading@10: while (*s && *s != '}') { yading@10: int style_index = indexof(MICRODVD_STYLES, *s); yading@10: yading@10: if (style_index >= 0) yading@10: tag.data1 |= (1 << style_index); yading@10: s++; yading@10: } yading@10: if (*s != '}') yading@10: break; yading@10: /* We must distinguish persistent and non-persistent styles yading@10: * to handle this kind of style tags: {y:ib}{Y:us} */ yading@10: tag.key = tag_char; yading@10: break; yading@10: yading@10: /* Color */ yading@10: case 'C': yading@10: tag.persistent = MICRODVD_PERSISTENT_ON; yading@10: case 'c': yading@10: if (*s == '$') yading@10: s++; yading@10: tag.data1 = strtol(s, &s, 16) & 0x00ffffff; yading@10: if (*s != '}') yading@10: break; yading@10: tag.key = 'c'; yading@10: break; yading@10: yading@10: /* Font name */ yading@10: case 'F': yading@10: tag.persistent = MICRODVD_PERSISTENT_ON; yading@10: case 'f': { yading@10: int len = indexof(s, '}'); yading@10: if (len < 0) yading@10: break; yading@10: tag.data_string = s; yading@10: tag.data_string_len = len; yading@10: s += len; yading@10: tag.key = 'f'; yading@10: break; yading@10: } yading@10: yading@10: /* Font size */ yading@10: case 'S': yading@10: tag.persistent = MICRODVD_PERSISTENT_ON; yading@10: case 's': yading@10: tag.data1 = strtol(s, &s, 10); yading@10: if (*s != '}') yading@10: break; yading@10: tag.key = 's'; yading@10: break; yading@10: yading@10: /* Charset */ yading@10: case 'H': { yading@10: //TODO: not yet handled, just parsed. yading@10: int len = indexof(s, '}'); yading@10: if (len < 0) yading@10: break; yading@10: tag.data_string = s; yading@10: tag.data_string_len = len; yading@10: s += len; yading@10: tag.key = 'h'; yading@10: break; yading@10: } yading@10: yading@10: /* Position */ yading@10: case 'P': yading@10: tag.persistent = MICRODVD_PERSISTENT_ON; yading@10: tag.data1 = (*s++ == '1'); yading@10: if (*s != '}') yading@10: break; yading@10: tag.key = 'p'; yading@10: break; yading@10: yading@10: /* Coordinates */ yading@10: case 'o': yading@10: tag.persistent = MICRODVD_PERSISTENT_ON; yading@10: tag.data1 = strtol(s, &s, 10); yading@10: if (*s != ',') yading@10: break; yading@10: s++; yading@10: tag.data2 = strtol(s, &s, 10); yading@10: if (*s != '}') yading@10: break; yading@10: tag.key = 'o'; yading@10: break; yading@10: yading@10: default: /* Unknown tag, we consider it's text */ yading@10: break; yading@10: } yading@10: yading@10: if (tag.key == 0) yading@10: return start; yading@10: yading@10: microdvd_set_tag(tags, tag); yading@10: s++; yading@10: } yading@10: return s; yading@10: } yading@10: yading@10: static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags) yading@10: { yading@10: int i, sidx; yading@10: for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) { yading@10: if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED) yading@10: continue; yading@10: switch (tags[i].key) { yading@10: case 'Y': yading@10: case 'y': yading@10: for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) yading@10: if (tags[i].data1 & (1 << sidx)) yading@10: av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]); yading@10: break; yading@10: yading@10: case 'c': yading@10: av_bprintf(new_line, "{\\c&H%06X&}", tags[i].data1); yading@10: break; yading@10: yading@10: case 'f': yading@10: av_bprintf(new_line, "{\\fn%.*s}", yading@10: tags[i].data_string_len, tags[i].data_string); yading@10: break; yading@10: yading@10: case 's': yading@10: av_bprintf(new_line, "{\\fs%d}", tags[i].data1); yading@10: break; yading@10: yading@10: case 'p': yading@10: if (tags[i].data1 == 0) yading@10: av_bprintf(new_line, "{\\an8}"); yading@10: break; yading@10: yading@10: case 'o': yading@10: av_bprintf(new_line, "{\\pos(%d,%d)}", yading@10: tags[i].data1, tags[i].data2); yading@10: break; yading@10: } yading@10: if (tags[i].persistent == MICRODVD_PERSISTENT_ON) yading@10: tags[i].persistent = MICRODVD_PERSISTENT_OPENED; yading@10: } yading@10: } yading@10: yading@10: static void microdvd_close_no_persistent_tags(AVBPrint *new_line, yading@10: struct microdvd_tag *tags) yading@10: { yading@10: int i, sidx; yading@10: yading@10: for (i = sizeof(MICRODVD_TAGS) - 2; i >= 0; i--) { yading@10: if (tags[i].persistent != MICRODVD_PERSISTENT_OFF) yading@10: continue; yading@10: switch (tags[i].key) { yading@10: yading@10: case 'y': yading@10: for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--) yading@10: if (tags[i].data1 & (1 << sidx)) yading@10: av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]); yading@10: break; yading@10: yading@10: case 'c': yading@10: av_bprintf(new_line, "{\\c}"); yading@10: break; yading@10: yading@10: case 'f': yading@10: av_bprintf(new_line, "{\\fn}"); yading@10: break; yading@10: yading@10: case 's': yading@10: av_bprintf(new_line, "{\\fs}"); yading@10: break; yading@10: } yading@10: tags[i].key = 0; yading@10: } yading@10: } yading@10: yading@10: static int microdvd_decode_frame(AVCodecContext *avctx, yading@10: void *data, int *got_sub_ptr, AVPacket *avpkt) yading@10: { yading@10: AVSubtitle *sub = data; yading@10: AVBPrint new_line; yading@10: char c; yading@10: char *decoded_sub; yading@10: char *line = avpkt->data; yading@10: char *end = avpkt->data + avpkt->size; yading@10: struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}}; yading@10: yading@10: if (avpkt->size <= 0) yading@10: return avpkt->size; yading@10: yading@10: /* To be removed later */ yading@10: if (sscanf(line, "{%*d}{%*[0123456789]}%c", &c) == 1 && yading@10: line[avpkt->size - 1] == '\n') { yading@10: av_log(avctx, AV_LOG_ERROR, "AVPacket is not clean (contains timing " yading@10: "information and a trailing line break). You need to upgrade " yading@10: "your libavformat or sanitize your packet.\n"); yading@10: return AVERROR_INVALIDDATA; yading@10: } yading@10: yading@10: av_bprint_init(&new_line, 0, 2048); yading@10: yading@10: // subtitle content yading@10: while (line < end && *line) { yading@10: yading@10: // parse MicroDVD tags, and open them in ASS yading@10: line = microdvd_load_tags(tags, line); yading@10: microdvd_open_tags(&new_line, tags); yading@10: yading@10: // simple copy until EOL or forced carriage return yading@10: while (line < end && *line && *line != '|') { yading@10: av_bprint_chars(&new_line, *line, 1); yading@10: line++; yading@10: } yading@10: yading@10: // line split yading@10: if (line < end && *line == '|') { yading@10: microdvd_close_no_persistent_tags(&new_line, tags); yading@10: av_bprintf(&new_line, "\\N"); yading@10: line++; yading@10: } yading@10: } yading@10: if (new_line.len) { yading@10: av_bprintf(&new_line, "\r\n"); yading@10: yading@10: av_bprint_finalize(&new_line, &decoded_sub); yading@10: if (*decoded_sub) { yading@10: int64_t start = avpkt->pts; yading@10: int64_t duration = avpkt->duration; yading@10: int ts_start = av_rescale_q(start, avctx->time_base, (AVRational){1,100}); yading@10: int ts_duration = duration != -1 ? yading@10: av_rescale_q(duration, avctx->time_base, (AVRational){1,100}) : -1; yading@10: ff_ass_add_rect(sub, decoded_sub, ts_start, ts_duration, 0); yading@10: } yading@10: av_free(decoded_sub); yading@10: } yading@10: yading@10: *got_sub_ptr = sub->num_rects > 0; yading@10: return avpkt->size; yading@10: } yading@10: yading@10: static int microdvd_init(AVCodecContext *avctx) yading@10: { yading@10: int i, sidx; yading@10: AVBPrint font_buf; yading@10: int font_size = ASS_DEFAULT_FONT_SIZE; yading@10: int color = ASS_DEFAULT_COLOR; yading@10: int bold = ASS_DEFAULT_BOLD; yading@10: int italic = ASS_DEFAULT_ITALIC; yading@10: int underline = ASS_DEFAULT_UNDERLINE; yading@10: int alignment = ASS_DEFAULT_ALIGNMENT; yading@10: struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}}; yading@10: yading@10: av_bprint_init(&font_buf, 0, AV_BPRINT_SIZE_AUTOMATIC); yading@10: av_bprintf(&font_buf, "%s", ASS_DEFAULT_FONT); yading@10: yading@10: if (avctx->extradata) { yading@10: microdvd_load_tags(tags, avctx->extradata); yading@10: for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) { yading@10: switch (av_tolower(tags[i].key)) { yading@10: case 'y': yading@10: for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) { yading@10: if (tags[i].data1 & (1 << sidx)) { yading@10: switch (MICRODVD_STYLES[sidx]) { yading@10: case 'i': italic = 1; break; yading@10: case 'b': bold = 1; break; yading@10: case 'u': underline = 1; break; yading@10: } yading@10: } yading@10: } yading@10: break; yading@10: yading@10: case 'c': color = tags[i].data1; break; yading@10: case 's': font_size = tags[i].data1; break; yading@10: case 'p': alignment = 8; break; yading@10: yading@10: case 'f': yading@10: av_bprint_clear(&font_buf); yading@10: av_bprintf(&font_buf, "%.*s", yading@10: tags[i].data_string_len, tags[i].data_string); yading@10: break; yading@10: } yading@10: } yading@10: } yading@10: return ff_ass_subtitle_header(avctx, font_buf.str, font_size, color, yading@10: ASS_DEFAULT_BACK_COLOR, bold, italic, yading@10: underline, alignment); yading@10: } yading@10: yading@10: AVCodec ff_microdvd_decoder = { yading@10: .name = "microdvd", yading@10: .long_name = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"), yading@10: .type = AVMEDIA_TYPE_SUBTITLE, yading@10: .id = AV_CODEC_ID_MICRODVD, yading@10: .init = microdvd_init, yading@10: .decode = microdvd_decode_frame, yading@10: };