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: * JACOsub subtitle decoder yading@10: * @see http://unicorn.us.com/jacosub/jscripts.html yading@10: */ yading@10: yading@10: #include yading@10: #include "ass.h" yading@10: #include "jacosub.h" yading@10: #include "libavutil/avstring.h" yading@10: #include "libavutil/bprint.h" yading@10: yading@10: #undef time yading@10: yading@10: static int insert_text(AVBPrint *dst, const char *in, const char *arg) yading@10: { yading@10: av_bprintf(dst, "%s", arg); yading@10: return 0; yading@10: } yading@10: yading@10: static int insert_datetime(AVBPrint *dst, const char *in, const char *arg) yading@10: { yading@10: char buf[16] = {0}; yading@10: time_t now = time(0); yading@10: struct tm ltime; yading@10: yading@10: #if HAVE_LOCALTIME_R yading@10: localtime_r(&now, <ime); yading@10: #else yading@10: ltime = *localtime(&now); yading@10: #endif yading@10: strftime(buf, sizeof(buf), arg, <ime); yading@10: av_bprintf(dst, "%s", buf); yading@10: return 0; yading@10: } yading@10: yading@10: static int insert_color(AVBPrint *dst, const char *in, const char *arg) yading@10: { yading@10: return 1; // skip id yading@10: } yading@10: yading@10: static int insert_font(AVBPrint *dst, const char *in, const char *arg) yading@10: { yading@10: return 1; // skip id yading@10: } yading@10: yading@10: static const struct { yading@10: const char *from; yading@10: const char *arg; yading@10: int (*func)(AVBPrint *dst, const char *in, const char *arg); yading@10: } ass_codes_map[] = { yading@10: {"\\~", "~", insert_text}, // tilde doesn't need escaping yading@10: {"~", "{\\h}", insert_text}, // hard space yading@10: {"\\n", "\\N", insert_text}, // newline yading@10: {"\\D", "%d %b %Y", insert_datetime}, // current date yading@10: {"\\T", "%H:%M", insert_datetime}, // current time yading@10: {"\\N", "{\\r}", insert_text}, // reset to default style yading@10: {"\\I", "{\\i1}", insert_text}, // italic on yading@10: {"\\i", "{\\i0}", insert_text}, // italic off yading@10: {"\\B", "{\\b1}", insert_text}, // bold on yading@10: {"\\b", "{\\b0}", insert_text}, // bold off yading@10: {"\\U", "{\\u1}", insert_text}, // underline on yading@10: {"\\u", "{\\u0}", insert_text}, // underline off yading@10: {"\\C", "", insert_color}, // TODO: color yading@10: {"\\F", "", insert_font}, // TODO: font yading@10: }; yading@10: yading@10: enum { yading@10: ALIGN_VB = 1<<0, // vertical bottom, default yading@10: ALIGN_VM = 1<<1, // vertical middle yading@10: ALIGN_VT = 1<<2, // vertical top yading@10: ALIGN_JC = 1<<3, // justify center, default yading@10: ALIGN_JL = 1<<4, // justify left yading@10: ALIGN_JR = 1<<5, // justify right yading@10: }; yading@10: yading@10: static void jacosub_to_ass(AVCodecContext *avctx, AVBPrint *dst, const char *src) yading@10: { yading@10: int i, valign = 0, halign = 0; yading@10: char c = av_toupper(*src); yading@10: char directives[128] = {0}; yading@10: yading@10: /* extract the optional directives */ yading@10: if ((c >= 'A' && c <= 'Z') || c == '[') { yading@10: char *p = directives; yading@10: char *pend = directives + sizeof(directives) - 1; yading@10: yading@10: do *p++ = av_toupper(*src++); yading@10: while (*src && !jss_whitespace(*src) && p < pend); yading@10: *p = 0; yading@10: src = jss_skip_whitespace(src); yading@10: } yading@10: yading@10: /* handle directives (TODO: handle more of them, and more reliably) */ yading@10: if (strstr(directives, "VB")) valign = ALIGN_VB; yading@10: else if (strstr(directives, "VM")) valign = ALIGN_VM; yading@10: else if (strstr(directives, "VT")) valign = ALIGN_VT; yading@10: if (strstr(directives, "JC")) halign = ALIGN_JC; yading@10: else if (strstr(directives, "JL")) halign = ALIGN_JL; yading@10: else if (strstr(directives, "JR")) halign = ALIGN_JR; yading@10: if (valign || halign) { yading@10: if (!valign) valign = ALIGN_VB; yading@10: if (!halign) halign = ALIGN_JC; yading@10: switch (valign | halign) { yading@10: case ALIGN_VB | ALIGN_JL: av_bprintf(dst, "{\\an1}"); break; // bottom left yading@10: case ALIGN_VB | ALIGN_JC: av_bprintf(dst, "{\\an2}"); break; // bottom center yading@10: case ALIGN_VB | ALIGN_JR: av_bprintf(dst, "{\\an3}"); break; // bottom right yading@10: case ALIGN_VM | ALIGN_JL: av_bprintf(dst, "{\\an4}"); break; // middle left yading@10: case ALIGN_VM | ALIGN_JC: av_bprintf(dst, "{\\an5}"); break; // middle center yading@10: case ALIGN_VM | ALIGN_JR: av_bprintf(dst, "{\\an6}"); break; // middle right yading@10: case ALIGN_VT | ALIGN_JL: av_bprintf(dst, "{\\an7}"); break; // top left yading@10: case ALIGN_VT | ALIGN_JC: av_bprintf(dst, "{\\an8}"); break; // top center yading@10: case ALIGN_VT | ALIGN_JR: av_bprintf(dst, "{\\an9}"); break; // top right yading@10: } yading@10: } yading@10: yading@10: /* process timed line */ yading@10: while (*src && *src != '\n') { yading@10: yading@10: /* text continue on the next line */ yading@10: if (src[0] == '\\' && src[1] == '\n') { yading@10: src += 2; yading@10: while (jss_whitespace(*src)) yading@10: src++; yading@10: continue; yading@10: } yading@10: yading@10: /* special character codes */ yading@10: for (i = 0; i < FF_ARRAY_ELEMS(ass_codes_map); i++) { yading@10: const char *from = ass_codes_map[i].from; yading@10: const char *arg = ass_codes_map[i].arg; yading@10: size_t codemap_len = strlen(from); yading@10: yading@10: if (!strncmp(src, from, codemap_len)) { yading@10: src += codemap_len; yading@10: src += ass_codes_map[i].func(dst, src, arg); yading@10: break; yading@10: } yading@10: } yading@10: yading@10: /* simple char copy */ yading@10: if (i == FF_ARRAY_ELEMS(ass_codes_map)) yading@10: av_bprintf(dst, "%c", *src++); yading@10: } yading@10: av_bprintf(dst, "\r\n"); yading@10: } yading@10: yading@10: static int jacosub_decode_frame(AVCodecContext *avctx, yading@10: void *data, int *got_sub_ptr, AVPacket *avpkt) yading@10: { yading@10: AVSubtitle *sub = data; yading@10: const char *ptr = avpkt->data; yading@10: yading@10: if (avpkt->size <= 0) yading@10: goto end; yading@10: yading@10: if (*ptr) { yading@10: AVBPrint buffer; yading@10: char *dec_sub; yading@10: yading@10: // skip timers yading@10: ptr = jss_skip_whitespace(ptr); yading@10: ptr = strchr(ptr, ' '); if (!ptr) goto end; ptr++; yading@10: ptr = strchr(ptr, ' '); if (!ptr) goto end; ptr++; yading@10: yading@10: av_bprint_init(&buffer, JSS_MAX_LINESIZE, JSS_MAX_LINESIZE); yading@10: jacosub_to_ass(avctx, &buffer, ptr); yading@10: av_bprint_finalize(&buffer, &dec_sub); yading@10: ff_ass_add_rect(sub, dec_sub, avpkt->pts, avpkt->duration, 0); yading@10: av_free(dec_sub); yading@10: } yading@10: yading@10: end: yading@10: *got_sub_ptr = sub->num_rects > 0; yading@10: return avpkt->size; yading@10: } yading@10: yading@10: AVCodec ff_jacosub_decoder = { yading@10: .name = "jacosub", yading@10: .long_name = NULL_IF_CONFIG_SMALL("JACOsub subtitle"), yading@10: .type = AVMEDIA_TYPE_SUBTITLE, yading@10: .id = AV_CODEC_ID_JACOSUB, yading@10: .init = ff_ass_subtitle_header_default, yading@10: .decode = jacosub_decode_frame, yading@10: };