From 2c77c90684e24ef16f7e7c4462e011434cee6a98 Mon Sep 17 00:00:00 2001 From: Aurelien Jacobs Date: Tue, 28 Dec 2010 23:52:53 +0000 Subject: [PATCH] add SubRip decoder Originally committed as revision 26119 to svn://svn.ffmpeg.org/ffmpeg/trunk --- Changelog | 1 + MAINTAINERS | 1 + doc/general.texi | 2 +- libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/ass.c | 40 +++++++ libavcodec/ass.h | 45 ++++++++ libavcodec/avcodec.h | 4 +- libavcodec/srtdec.c | 240 +++++++++++++++++++++++++++++++++++++++++ tests/fate.mak | 2 + tests/ref/fate/sub-srt | 1 + 11 files changed, 335 insertions(+), 3 deletions(-) create mode 100644 libavcodec/srtdec.c create mode 100644 tests/ref/fate/sub-srt diff --git a/Changelog b/Changelog index fa9f41f764..41ece6c503 100644 --- a/Changelog +++ b/Changelog @@ -69,6 +69,7 @@ version : - replace the ocv_smooth filter with a more generic ocv filter - Windows Televison (WTV) demuxer - FFmpeg metadata format muxer and demuxer +- SubRip (srt) subtitle decoder version 0.6: diff --git a/MAINTAINERS b/MAINTAINERS index 4bf28198d4..16d370222b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -198,6 +198,7 @@ Codecs: smc.c Mike Melanson snow.c Michael Niedermayer, Loren Merritt sonic.c Alex Beregszaszi + srtdec.c Aurelien Jacobs sunrast.c Ivo van Poorten svq3.c Michael Niedermayer targa.c Kostya Shishkov diff --git a/doc/general.texi b/doc/general.texi index 8004925285..2ba087bd72 100644 --- a/doc/general.texi +++ b/doc/general.texi @@ -694,7 +694,7 @@ performance on systems without hardware floating point support). @item DVB @tab X @tab X @tab X @tab X @item DVD @tab X @tab X @tab X @tab X @item PGS @tab @tab @tab @tab X -@item SubRip (SRT) @tab X @tab X +@item SubRip (SRT) @tab X @tab X @tab @tab X @item XSUB @tab @tab @tab X @tab X @end multitable diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 2abf7de858..43ab458449 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -337,6 +337,7 @@ OBJS-$(CONFIG_SONIC_DECODER) += sonic.o OBJS-$(CONFIG_SONIC_ENCODER) += sonic.o OBJS-$(CONFIG_SONIC_LS_ENCODER) += sonic.o OBJS-$(CONFIG_SP5X_DECODER) += sp5xdec.o mjpegdec.o mjpeg.o +OBJS-$(CONFIG_SRT_DECODER) += srtdec.o ass.o OBJS-$(CONFIG_SUNRAST_DECODER) += sunrast.o OBJS-$(CONFIG_SVQ1_DECODER) += svq1dec.o svq1.o h263.o \ mpegvideo.o error_resilience.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index b368ea08a9..5bcd9120cd 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -347,6 +347,7 @@ void avcodec_register_all(void) REGISTER_ENCDEC (DVBSUB, dvbsub); REGISTER_ENCDEC (DVDSUB, dvdsub); REGISTER_DECODER (PGSSUB, pgssub); + REGISTER_DECODER (SRT, srt); REGISTER_ENCDEC (XSUB, xsub); /* external libraries */ diff --git a/libavcodec/ass.c b/libavcodec/ass.c index cd5e652c56..7553bf0778 100644 --- a/libavcodec/ass.c +++ b/libavcodec/ass.c @@ -22,6 +22,46 @@ #include "avcodec.h" #include "ass.h" +int ff_ass_subtitle_header(AVCodecContext *avctx, + const char *font, int font_size, + int color, int back_color, + int bold, int italic, int underline, + int alignment) +{ + char header[512]; + + snprintf(header, sizeof(header), + "[Script Info]\r\n" + "ScriptType: v4.00+\r\n" + "\r\n" + "[V4+ Styles]\r\n" + "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding\r\n" + "Style: Default,%s,%d,&H%x,&H%x,&H%x,&H%x,%d,%d,%d,1,1,0,%d,10,10,10,0,0\r\n" + "\r\n" + "[Events]\r\n" + "Format: Layer, Start, End, Text\r\n", + font, font_size, color, color, back_color, back_color, + -bold, -italic, -underline, alignment); + + avctx->subtitle_header = av_strdup(header); + if (!avctx->subtitle_header) + return AVERROR(ENOMEM); + avctx->subtitle_header_size = strlen(avctx->subtitle_header); + return 0; +} + +int ff_ass_subtitle_header_default(AVCodecContext *avctx) +{ + return ff_ass_subtitle_header(avctx, ASS_DEFAULT_FONT, + ASS_DEFAULT_FONT_SIZE, + ASS_DEFAULT_COLOR, + ASS_DEFAULT_BACK_COLOR, + ASS_DEFAULT_BOLD, + ASS_DEFAULT_ITALIC, + ASS_DEFAULT_UNDERLINE, + ASS_DEFAULT_ALIGNMENT); +} + void ff_ass_init(AVSubtitle *sub) { memset(sub, 0, sizeof(*sub)); diff --git a/libavcodec/ass.h b/libavcodec/ass.h index 1c847f169b..e04b4cc747 100644 --- a/libavcodec/ass.h +++ b/libavcodec/ass.h @@ -24,6 +24,51 @@ #include "avcodec.h" +/** + * Default values for ASS style. + * @defgroup ass_default + * @{ + */ +#define ASS_DEFAULT_FONT "Arial" +#define ASS_DEFAULT_FONT_SIZE 16 +#define ASS_DEFAULT_COLOR 0xffffff +#define ASS_DEFAULT_BACK_COLOR 0 +#define ASS_DEFAULT_BOLD 0 +#define ASS_DEFAULT_ITALIC 0 +#define ASS_DEFAULT_UNDERLINE 0 +#define ASS_DEFAULT_ALIGNMENT 2 +/** @} */ + +/** + * Generate a suitable AVCodecContext.subtitle_header for SUBTITLE_ASS. + * + * @param avctx pointer to the AVCodecContext + * @param font name of the default font face to use + * @param font_size default font size to use + * @param color default text color to use (ABGR) + * @param back_color default background color to use (ABGR) + * @param bold 1 for bold text, 0 for normal text + * @param italic 1 for italic text, 0 for normal text + * @param underline 1 for underline text, 0 for normal text + * @param alignment position of the text (left, center, top...), defined after + * the layout of the numpad (1-3 sub, 4-6 mid, 7-9 top) + * @return >= 0 on success otherwise an error code <0 + */ +int ff_ass_subtitle_header(AVCodecContext *avctx, + const char *font, int font_size, + int color, int back_color, + int bold, int italic, int underline, + int alignment); + +/** + * Generate a suitable AVCodecContext.subtitle_header for SUBTITLE_ASS + * with default style. + * + * @param avctx pointer to the AVCodecContext + * @return >= 0 on success otherwise an error code <0 + */ +int ff_ass_subtitle_header_default(AVCodecContext *avctx); + /** * Initialize an AVSubtitle structure for use with ff_ass_add_rect(). * diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h index 87e67e59f2..c803ec6da3 100644 --- a/libavcodec/avcodec.h +++ b/libavcodec/avcodec.h @@ -32,8 +32,8 @@ #include "libavutil/cpu.h" #define LIBAVCODEC_VERSION_MAJOR 52 -#define LIBAVCODEC_VERSION_MINOR 100 -#define LIBAVCODEC_VERSION_MICRO 1 +#define LIBAVCODEC_VERSION_MINOR 101 +#define LIBAVCODEC_VERSION_MICRO 0 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ LIBAVCODEC_VERSION_MINOR, \ diff --git a/libavcodec/srtdec.c b/libavcodec/srtdec.c new file mode 100644 index 0000000000..42eae322d1 --- /dev/null +++ b/libavcodec/srtdec.c @@ -0,0 +1,240 @@ +/* + * SubRip subtitle decoder + * Copyright (c) 2010 Aurelien Jacobs + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/avstring.h" +#include "libavcore/parseutils.h" +#include "avcodec.h" +#include "ass.h" + +static int html_color_parse(AVCodecContext *avctx, const char *str) +{ + uint8_t rgba[4]; + if (av_parse_color(rgba, str, strcspn(str, "\" >"), avctx) < 0) + return -1; + return rgba[0] | rgba[1] << 8 | rgba[2] << 16; +} + +enum { + PARAM_UNKNOWN = -1, + PARAM_SIZE, + PARAM_COLOR, + PARAM_FACE, + PARAM_NUMBER +}; + +typedef struct { + char tag[128]; + char param[PARAM_NUMBER][128]; +} SrtStack; + +static const char *srt_to_ass(AVCodecContext *avctx, char *out, char *out_end, + const char *in, int x1, int y1, int x2, int y2) +{ + char c, *param, buffer[128], tmp[128]; + int len, tag_close, sptr = 1, line_start = 1, an = 0, end = 0; + SrtStack stack[16]; + + stack[0].tag[0] = 0; + strcpy(stack[0].param[PARAM_SIZE], "{\\fs}"); + strcpy(stack[0].param[PARAM_COLOR], "{\\c}"); + strcpy(stack[0].param[PARAM_FACE], "{\\fn}"); + + if (x1 >= 0 && y1 >= 0) { + if (x2 >= 0 && y2 >= 0 && (x2 != x1 || y2 != y1)) + out += snprintf(out, out_end-out, + "{\\an1}{\\move(%d,%d,%d,%d)}", x1, y1, x2, y2); + else + out += snprintf(out, out_end-out, "{\\an1}{\\pos(%d,%d)}", x1, y1); + } + + for (; *in && out < out_end && !end; in++) { + switch (*in) { + case '\r': + break; + case '\n': + if (line_start) { + end = 1; + break; + } + while (out[-1] == ' ') + out--; + out += snprintf(out, out_end-out, "\\N"); + line_start = 1; + break; + case ' ': + if (!line_start) + *out++ = *in; + break; + case '{': /* skip all {\xxx} substrings except for {\an%d} + and all microdvd like styles such as {Y:xxx} */ + an += sscanf(in, "{\\an%*1u}%c", &c) == 1; + if ((an != 1 && sscanf(in, "{\\%*[^}]}%n%c", &len, &c) > 0) || + sscanf(in, "{%*1[CcFfoPSsYy]:%*[^}]}%n%c", &len, &c) > 0) { + in += len - 1; + } else + *out++ = *in; + break; + case '<': + tag_close = in[1] == '/'; + if (sscanf(in+tag_close+1, "%128[^>]>%n%c", buffer, &len,&c) >= 2) { + if ((param = strchr(buffer, ' '))) + *param++ = 0; + if ((!tag_close && sptr < FF_ARRAY_ELEMS(stack)) || + ( tag_close && sptr > 0 && !strcmp(stack[sptr-1].tag, buffer))) { + int i, j, unknown = 0; + in += len + tag_close; + if (!tag_close) + memset(stack+sptr, 0, sizeof(*stack)); + if (!strcmp(buffer, "font")) { + if (tag_close) { + for (i=PARAM_NUMBER-1; i>=0; i--) + if (stack[sptr-1].param[i][0]) + for (j=sptr-2; j>=0; j--) + if (stack[j].param[i][0]) { + out += snprintf(out, out_end-out, + stack[j].param[i]); + break; + } + } else { + while (param) { + if (!strncmp(param, "size=", 5)) { + unsigned font_size; + param += 5 + (param[5] == '"'); + if (sscanf(param, "%u", &font_size) == 1) { + snprintf(stack[sptr].param[PARAM_SIZE], + sizeof(stack[0].param[PARAM_SIZE]), + "{\\fs%u}", font_size); + } + } else if (!strncmp(param, "color=", 6)) { + param += 6 + (param[6] == '"'); + snprintf(stack[sptr].param[PARAM_COLOR], + sizeof(stack[0].param[PARAM_COLOR]), + "{\\c&H%X&}", + html_color_parse(avctx, param)); + } else if (!strncmp(param, "face=", 5)) { + param += 5 + (param[5] == '"'); + len = strcspn(param, + param[-1] == '"' ? "\"" :" "); + av_strlcpy(tmp, param, + FFMIN(sizeof(tmp), len+1)); + param += len; + snprintf(stack[sptr].param[PARAM_FACE], + sizeof(stack[0].param[PARAM_FACE]), + "{\\fn%s}", tmp); + } + if ((param = strchr(param, ' '))) + param++; + } + for (i=0; i", buffer); + } + if (tag_close) { + sptr--; + } else if (unknown && !strstr(in, tmp)) { + in -= len + tag_close; + *out++ = *in; + } else + av_strlcpy(stack[sptr++].tag, buffer, + sizeof(stack[0].tag)); + break; + } + } + default: + *out++ = *in; + break; + } + if (*in != ' ' && *in != '\r' && *in != '\n') + line_start = 0; + } + + out = FFMIN(out, out_end-3); + while (!strncmp(out-2, "\\N", 2)) + out -= 2; + while (out[-1] == ' ') + out--; + out += snprintf(out, out_end-out, "\r\n"); + return in; +} + +static const char *read_ts(const char *buf, int *ts_start, int *ts_end, + int *x1, int *y1, int *x2, int *y2) +{ + int i, hs, ms, ss, he, me, se; + + for (i=0; i<2; i++) { + /* try to read timestamps in either the first or second line */ + int c = sscanf(buf, "%d:%2d:%2d%*1[,.]%3d --> %d:%2d:%2d%*1[,.]%3d" + "%*[ ]X1:%u X2:%u Y1:%u Y2:%u", + &hs, &ms, &ss, ts_start, &he, &me, &se, ts_end, + x1, x2, y1, y2); + buf += strcspn(buf, "\n") + 1; + if (c >= 8) { + *ts_start = 100*(ss + 60*(ms + 60*hs)) + *ts_start/10; + *ts_end = 100*(se + 60*(me + 60*he)) + *ts_end /10; + return buf; + } + } + return NULL; +} + +static int srt_decode_frame(AVCodecContext *avctx, + void *data, int *got_sub_ptr, AVPacket *avpkt) +{ + AVSubtitle *sub = data; + int ts_start, ts_end, x1 = -1, y1 = -1, x2 = -1, y2 = -1; + char buffer[2048]; + const char *ptr = avpkt->data; + + if (avpkt->size <= 0) + return avpkt->size; + + ff_ass_init(sub); + + while (*ptr) { + ptr = read_ts(ptr, &ts_start, &ts_end, &x1, &y1, &x2, &y2); + if (!ptr) + break; + ptr = srt_to_ass(avctx, buffer, buffer+sizeof(buffer), ptr, + x1, y1, x2, y2); + ff_ass_add_rect(sub, buffer, ts_start, ts_end, 0); + } + + *got_sub_ptr = sub->num_rects > 0; + return avpkt->size; +} + +AVCodec srt_decoder = { + .name = "srt", + .long_name = NULL_IF_CONFIG_SMALL("SubRip subtitle"), + .type = AVMEDIA_TYPE_SUBTITLE, + .id = CODEC_ID_SRT, + .init = ff_ass_subtitle_header_default, + .decode = srt_decode_frame, +}; diff --git a/tests/fate.mak b/tests/fate.mak index 0245a77cc6..76992c464b 100644 --- a/tests/fate.mak +++ b/tests/fate.mak @@ -282,6 +282,8 @@ FATE_TESTS += fate-smc fate-smc: CMD = framecrc -i $(SAMPLES)/smc/cass_schi.qt -vsync 0 -pix_fmt rgb24 FATE_TESTS += fate-sp5x fate-sp5x: CMD = framecrc -idct simple -i $(SAMPLES)/sp5x/sp5x_problem.avi +FATE_TESTS += fate-sub-srt +fate-sub-srt: CMD = md5 -i $(SAMPLES)/sub/SubRip_capability_tester.srt -f ass FATE_TESTS += fate-sunraster-1bit-raw fate-sunraster-1bit-raw: CMD = framecrc -i $(SAMPLES)/sunraster/lena-1bit-raw.sun FATE_TESTS += fate-sunraster-1bit-rle diff --git a/tests/ref/fate/sub-srt b/tests/ref/fate/sub-srt new file mode 100644 index 0000000000..5a1466437e --- /dev/null +++ b/tests/ref/fate/sub-srt @@ -0,0 +1 @@ +03b2a3f7e7e83624c8e4d1b5569df758