From 4372fb7a5722a0b2aa126f4d4e6d617f237918f6 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Thu, 22 May 2014 21:20:34 -0700 Subject: [PATCH] avcodec/webvttenc: add webvtt encoder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based off the srt encoder. The following features are unimplemented: - fonts, colors, sizes - alignment and positioning The rest works well. For example, use ffmpeg to convert subtitles into the .vtt format: ffmpeg -i input.srt output.vtt Signed-off-by: Aman Gupta Signed-off-by: Clément Bœsch --- Changelog | 1 + doc/general.texi | 2 +- libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 2 +- libavcodec/version.h | 2 +- libavcodec/webvttenc.c | 219 +++++++++++++++++++++++++++++++++++ libavformat/webvttenc.c | 1 + tests/fate/subtitles.mak | 3 + tests/ref/fate/sub-webvttenc | 1 + 9 files changed, 229 insertions(+), 3 deletions(-) create mode 100644 libavcodec/webvttenc.c create mode 100644 tests/ref/fate/sub-webvttenc diff --git a/Changelog b/Changelog index 3e10b2001e..3d416c4d2f 100644 --- a/Changelog +++ b/Changelog @@ -25,6 +25,7 @@ version : - libx264 reference frames count limiting depending on level - native Opus decoder - display matrix export and rotation api +- WebVTT encoder version 2.2: diff --git a/doc/general.texi b/doc/general.texi index 8a73cbf5be..86569a20e7 100644 --- a/doc/general.texi +++ b/doc/general.texi @@ -1036,7 +1036,7 @@ performance on systems without hardware floating point support). @item TED Talks captions @tab @tab X @tab @tab X @item VobSub (IDX+SUB) @tab @tab X @tab @tab X @item VPlayer @tab @tab X @tab @tab X -@item WebVTT @tab X @tab X @tab @tab X +@item WebVTT @tab X @tab X @tab X @tab X @item XSUB @tab @tab @tab X @tab X @end multitable diff --git a/libavcodec/Makefile b/libavcodec/Makefile index f1690a82ed..7b0b99f470 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -487,6 +487,7 @@ OBJS-$(CONFIG_WAVPACK_ENCODER) += wavpackenc.o OBJS-$(CONFIG_WEBP_DECODER) += vp8.o vp8dsp.o vp56rac.o OBJS-$(CONFIG_WEBP_DECODER) += webp.o exif.o tiff_common.o OBJS-$(CONFIG_WEBVTT_DECODER) += webvttdec.o +OBJS-$(CONFIG_WEBVTT_ENCODER) += webvttenc.o OBJS-$(CONFIG_WMALOSSLESS_DECODER) += wmalosslessdec.o wma_common.o OBJS-$(CONFIG_WMAPRO_DECODER) += wmaprodec.o wma.o wma_common.o OBJS-$(CONFIG_WMAV1_DECODER) += wmadec.o wma.o wma_common.o aactab.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index ac0f424d6a..7650543ac4 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -495,7 +495,7 @@ void avcodec_register_all(void) REGISTER_DECODER(SUBVIEWER1, subviewer1); REGISTER_DECODER(TEXT, text); REGISTER_DECODER(VPLAYER, vplayer); - REGISTER_DECODER(WEBVTT, webvtt); + REGISTER_ENCDEC (WEBVTT, webvtt); REGISTER_ENCDEC (XSUB, xsub); /* external libraries */ diff --git a/libavcodec/version.h b/libavcodec/version.h index 83bd83b9d4..4454bf1cde 100644 --- a/libavcodec/version.h +++ b/libavcodec/version.h @@ -29,7 +29,7 @@ #include "libavutil/version.h" #define LIBAVCODEC_VERSION_MAJOR 55 -#define LIBAVCODEC_VERSION_MINOR 64 +#define LIBAVCODEC_VERSION_MINOR 65 #define LIBAVCODEC_VERSION_MICRO 100 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ diff --git a/libavcodec/webvttenc.c b/libavcodec/webvttenc.c new file mode 100644 index 0000000000..dbe7c3d26e --- /dev/null +++ b/libavcodec/webvttenc.c @@ -0,0 +1,219 @@ +/* + * WebVTT subtitle encoder + * Copyright (c) 2010 Aurelien Jacobs + * Copyright (c) 2014 Aman Gupta + * + * 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 +#include "avcodec.h" +#include "libavutil/avstring.h" +#include "libavutil/bprint.h" +#include "ass_split.h" +#include "ass.h" + +#define WEBVTT_STACK_SIZE 64 +typedef struct { + AVCodecContext *avctx; + ASSSplitContext *ass_ctx; + AVBPrint buffer; + unsigned timestamp_end; + int count; + char stack[WEBVTT_STACK_SIZE]; + int stack_ptr; +} WebVTTContext; + +#ifdef __GNUC__ +__attribute__ ((__format__ (__printf__, 2, 3))) +#endif +static void webvtt_print(WebVTTContext *s, const char *str, ...) +{ + va_list vargs; + va_start(vargs, str); + av_vbprintf(&s->buffer, str, vargs); + va_end(vargs); +} + +static int webvtt_stack_push(WebVTTContext *s, const char c) +{ + if (s->stack_ptr >= WEBVTT_STACK_SIZE) + return AVERROR(EOVERFLOW); + s->stack[s->stack_ptr++] = c; + return 0; +} + +static char webvtt_stack_pop(WebVTTContext *s) +{ + if (s->stack_ptr <= 0) + return 0; + return s->stack[--s->stack_ptr]; +} + +static int webvtt_stack_find(WebVTTContext *s, const char c) +{ + int i; + for (i = s->stack_ptr-1; i >= 0; i--) + if (s->stack[i] == c) + break; + return i; +} + +static void webvtt_close_tag(WebVTTContext *s, char tag) +{ + webvtt_print(s, "", tag); +} + +static void webvtt_stack_push_pop(WebVTTContext *s, const char c, int close) +{ + if (close) { + int i = c ? webvtt_stack_find(s, c) : 0; + if (i < 0) + return; + while (s->stack_ptr != i) + webvtt_close_tag(s, webvtt_stack_pop(s)); + } else if (webvtt_stack_push(s, c) < 0) + av_log(s->avctx, AV_LOG_ERROR, "tag stack overflow\n"); +} + +static void webvtt_style_apply(WebVTTContext *s, const char *style) +{ + ASSStyle *st = ff_ass_style_get(s->ass_ctx, style); + if (st) { + if (st->bold != ASS_DEFAULT_BOLD) { + webvtt_print(s, ""); + webvtt_stack_push(s, 'b'); + } + if (st->italic != ASS_DEFAULT_ITALIC) { + webvtt_print(s, ""); + webvtt_stack_push(s, 'i'); + } + if (st->underline != ASS_DEFAULT_UNDERLINE) { + webvtt_print(s, ""); + webvtt_stack_push(s, 'u'); + } + } +} + +static void webvtt_text_cb(void *priv, const char *text, int len) +{ + WebVTTContext *s = priv; + av_bprint_append_data(&s->buffer, text, len); +} + +static void webvtt_new_line_cb(void *priv, int forced) +{ + webvtt_print(priv, "\n"); +} + +static void webvtt_style_cb(void *priv, char style, int close) +{ + if (style == 's') // strikethrough unsupported + return; + + webvtt_stack_push_pop(priv, style, close); + if (!close) + webvtt_print(priv, "<%c>", style); +} + +static void webvtt_cancel_overrides_cb(void *priv, const char *style) +{ + webvtt_stack_push_pop(priv, 0, 1); + webvtt_style_apply(priv, style); +} + +static void webvtt_end_cb(void *priv) +{ + webvtt_stack_push_pop(priv, 0, 1); +} + +static const ASSCodesCallbacks webvtt_callbacks = { + .text = webvtt_text_cb, + .new_line = webvtt_new_line_cb, + .style = webvtt_style_cb, + .color = NULL, + .font_name = NULL, + .font_size = NULL, + .alignment = NULL, + .cancel_overrides = webvtt_cancel_overrides_cb, + .move = NULL, + .end = webvtt_end_cb, +}; + +static int webvtt_encode_frame(AVCodecContext *avctx, + unsigned char *buf, int bufsize, const AVSubtitle *sub) +{ + WebVTTContext *s = avctx->priv_data; + ASSDialog *dialog; + int i, num; + + av_bprint_clear(&s->buffer); + + for (i=0; inum_rects; i++) { + if (sub->rects[i]->type != SUBTITLE_ASS) { + av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n"); + return AVERROR(ENOSYS); + } + + dialog = ff_ass_split_dialog(s->ass_ctx, sub->rects[i]->ass, 0, &num); + for (; dialog && num--; dialog++) { + webvtt_style_apply(s, dialog->style); + ff_ass_split_override_codes(&webvtt_callbacks, s, dialog->text); + } + } + + if (!av_bprint_is_complete(&s->buffer)) + return AVERROR(ENOMEM); + if (!s->buffer.len) + return 0; + + if (s->buffer.len > bufsize) { + av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n"); + return -1; + } + memcpy(buf, s->buffer.str, s->buffer.len); + + return s->buffer.len; +} + +static int webvtt_encode_close(AVCodecContext *avctx) +{ + WebVTTContext *s = avctx->priv_data; + ff_ass_split_free(s->ass_ctx); + av_bprint_finalize(&s->buffer, NULL); + return 0; +} + +static av_cold int webvtt_encode_init(AVCodecContext *avctx) +{ + WebVTTContext *s = avctx->priv_data; + s->avctx = avctx; + s->ass_ctx = ff_ass_split(avctx->subtitle_header); + av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED); + return s->ass_ctx ? 0 : AVERROR_INVALIDDATA; +} + +AVCodec ff_webvtt_encoder = { + .name = "webvtt", + .long_name = NULL_IF_CONFIG_SMALL("WebVTT subtitle"), + .type = AVMEDIA_TYPE_SUBTITLE, + .id = AV_CODEC_ID_WEBVTT, + .priv_data_size = sizeof(WebVTTContext), + .init = webvtt_encode_init, + .encode_sub = webvtt_encode_frame, + .close = webvtt_encode_close, +}; diff --git a/libavformat/webvttenc.c b/libavformat/webvttenc.c index bd19a83454..b93993d55c 100644 --- a/libavformat/webvttenc.c +++ b/libavformat/webvttenc.c @@ -93,6 +93,7 @@ AVOutputFormat ff_webvtt_muxer = { .long_name = NULL_IF_CONFIG_SMALL("WebVTT subtitle"), .extensions = "vtt", .mime_type = "text/vtt", + .flags = AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT, .subtitle_codec = AV_CODEC_ID_WEBVTT, .write_header = webvtt_write_header, .write_packet = webvtt_write_packet, diff --git a/tests/fate/subtitles.mak b/tests/fate/subtitles.mak index 33d798e64c..0c7188272f 100644 --- a/tests/fate/subtitles.mak +++ b/tests/fate/subtitles.mak @@ -52,6 +52,9 @@ fate-sub-vplayer: CMD = md5 -i $(TARGET_SAMPLES)/sub/VPlayer_capability_tester.t FATE_SUBTITLES_ASS-$(call DEMDEC, WEBVTT, WEBVTT) += fate-sub-webvtt fate-sub-webvtt: CMD = md5 -i $(TARGET_SAMPLES)/sub/WebVTT_capability_tester.vtt -f ass +FATE_SUBTITLES_ASS-$(call ENCMUX, WEBVTT, WEBVTT) += fate-sub-webvttenc +fate-sub-webvttenc: CMD = md5 -i $(TARGET_SAMPLES)/sub/SubRip_capability_tester.srt -f webvtt + FATE_SUBTITLES_ASS-$(call ALLYES, MICRODVD_DEMUXER MICRODVD_DECODER ICONV) += fate-sub-charenc fate-sub-charenc: CMD = md5 -sub_charenc cp1251 -i $(TARGET_SAMPLES)/sub/cp1251-subtitles.sub -f ass diff --git a/tests/ref/fate/sub-webvttenc b/tests/ref/fate/sub-webvttenc new file mode 100644 index 0000000000..3fe6669371 --- /dev/null +++ b/tests/ref/fate/sub-webvttenc @@ -0,0 +1 @@ +8683216a86e147a98f29dafee33a0987