avcodec/webvttenc: add webvtt encoder
authorAman Gupta <ffmpeg@tmm1.net>
Fri, 23 May 2014 04:20:34 +0000 (21:20 -0700)
committerClément Bœsch <u@pkh.me>
Sun, 25 May 2014 01:04:14 +0000 (03:04 +0200)
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 <ffmpeg@tmm1.net>
Signed-off-by: Clément Bœsch <u@pkh.me>
Changelog
doc/general.texi
libavcodec/Makefile
libavcodec/allcodecs.c
libavcodec/version.h
libavcodec/webvttenc.c [new file with mode: 0644]
libavformat/webvttenc.c
tests/fate/subtitles.mak
tests/ref/fate/sub-webvttenc [new file with mode: 0644]

index 3e10b20..3d416c4 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -25,6 +25,7 @@ version <next>:
 - libx264 reference frames count limiting depending on level
 - native Opus decoder
 - display matrix export and rotation api
+- WebVTT encoder
 
 
 version 2.2:
index 8a73cbf..86569a2 100644 (file)
@@ -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
 
index f1690a8..7b0b99f 100644 (file)
@@ -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
index ac0f424..7650543 100644 (file)
@@ -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 */
index 83bd83b..4454bf1 100644 (file)
@@ -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 (file)
index 0000000..dbe7c3d
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * WebVTT subtitle encoder
+ * Copyright (c) 2010  Aurelien Jacobs <aurel@gnuage.org>
+ * Copyright (c) 2014  Aman Gupta <ffmpeg@tmm1.net>
+ *
+ * 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 <stdarg.h>
+#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, "</%c>", 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, "<b>");
+            webvtt_stack_push(s, 'b');
+        }
+        if (st->italic != ASS_DEFAULT_ITALIC) {
+            webvtt_print(s, "<i>");
+            webvtt_stack_push(s, 'i');
+        }
+        if (st->underline != ASS_DEFAULT_UNDERLINE) {
+            webvtt_print(s, "<u>");
+            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; i<sub->num_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,
+};
index bd19a83..b93993d 100644 (file)
@@ -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,
index 33d798e..0c71882 100644 (file)
@@ -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 (file)
index 0000000..3fe6669
--- /dev/null
@@ -0,0 +1 @@
+8683216a86e147a98f29dafee33a0987