Browse code

avcodec/webvttenc: add webvtt encoder

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>

Aman Gupta authored on 2014/05/23 13:20:34
Showing 9 changed files
... ...
@@ -25,6 +25,7 @@ version <next>:
25 25
 - libx264 reference frames count limiting depending on level
26 26
 - native Opus decoder
27 27
 - display matrix export and rotation api
28
+- WebVTT encoder
28 29
 
29 30
 
30 31
 version 2.2:
... ...
@@ -1036,7 +1036,7 @@ performance on systems without hardware floating point support).
1036 1036
 @item TED Talks captions @tab @tab X @tab   @tab X
1037 1037
 @item VobSub (IDX+SUB) @tab   @tab X @tab   @tab X
1038 1038
 @item VPlayer          @tab   @tab X @tab   @tab X
1039
-@item WebVTT           @tab X @tab X @tab   @tab X
1039
+@item WebVTT           @tab X @tab X @tab X @tab X
1040 1040
 @item XSUB             @tab   @tab   @tab X @tab X
1041 1041
 @end multitable
1042 1042
 
... ...
@@ -487,6 +487,7 @@ OBJS-$(CONFIG_WAVPACK_ENCODER)         += wavpackenc.o
487 487
 OBJS-$(CONFIG_WEBP_DECODER)            += vp8.o vp8dsp.o vp56rac.o
488 488
 OBJS-$(CONFIG_WEBP_DECODER)            += webp.o exif.o tiff_common.o
489 489
 OBJS-$(CONFIG_WEBVTT_DECODER)          += webvttdec.o
490
+OBJS-$(CONFIG_WEBVTT_ENCODER)          += webvttenc.o
490 491
 OBJS-$(CONFIG_WMALOSSLESS_DECODER)     += wmalosslessdec.o wma_common.o
491 492
 OBJS-$(CONFIG_WMAPRO_DECODER)          += wmaprodec.o wma.o wma_common.o
492 493
 OBJS-$(CONFIG_WMAV1_DECODER)           += wmadec.o wma.o wma_common.o aactab.o
... ...
@@ -495,7 +495,7 @@ void avcodec_register_all(void)
495 495
     REGISTER_DECODER(SUBVIEWER1,        subviewer1);
496 496
     REGISTER_DECODER(TEXT,              text);
497 497
     REGISTER_DECODER(VPLAYER,           vplayer);
498
-    REGISTER_DECODER(WEBVTT,            webvtt);
498
+    REGISTER_ENCDEC (WEBVTT,            webvtt);
499 499
     REGISTER_ENCDEC (XSUB,              xsub);
500 500
 
501 501
     /* external libraries */
... ...
@@ -29,7 +29,7 @@
29 29
 #include "libavutil/version.h"
30 30
 
31 31
 #define LIBAVCODEC_VERSION_MAJOR 55
32
-#define LIBAVCODEC_VERSION_MINOR  64
32
+#define LIBAVCODEC_VERSION_MINOR  65
33 33
 #define LIBAVCODEC_VERSION_MICRO 100
34 34
 
35 35
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
36 36
new file mode 100644
... ...
@@ -0,0 +1,219 @@
0
+/*
1
+ * WebVTT subtitle encoder
2
+ * Copyright (c) 2010  Aurelien Jacobs <aurel@gnuage.org>
3
+ * Copyright (c) 2014  Aman Gupta <ffmpeg@tmm1.net>
4
+ *
5
+ * This file is part of FFmpeg.
6
+ *
7
+ * FFmpeg is free software; you can redistribute it and/or
8
+ * modify it under the terms of the GNU Lesser General Public
9
+ * License as published by the Free Software Foundation; either
10
+ * version 2.1 of the License, or (at your option) any later version.
11
+ *
12
+ * FFmpeg is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
+ * Lesser General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU Lesser General Public
18
+ * License along with FFmpeg; if not, write to the Free Software
19
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
+ */
21
+
22
+#include <stdarg.h>
23
+#include "avcodec.h"
24
+#include "libavutil/avstring.h"
25
+#include "libavutil/bprint.h"
26
+#include "ass_split.h"
27
+#include "ass.h"
28
+
29
+#define WEBVTT_STACK_SIZE 64
30
+typedef struct {
31
+    AVCodecContext *avctx;
32
+    ASSSplitContext *ass_ctx;
33
+    AVBPrint buffer;
34
+    unsigned timestamp_end;
35
+    int count;
36
+    char stack[WEBVTT_STACK_SIZE];
37
+    int stack_ptr;
38
+} WebVTTContext;
39
+
40
+#ifdef __GNUC__
41
+__attribute__ ((__format__ (__printf__, 2, 3)))
42
+#endif
43
+static void webvtt_print(WebVTTContext *s, const char *str, ...)
44
+{
45
+    va_list vargs;
46
+    va_start(vargs, str);
47
+    av_vbprintf(&s->buffer, str, vargs);
48
+    va_end(vargs);
49
+}
50
+
51
+static int webvtt_stack_push(WebVTTContext *s, const char c)
52
+{
53
+    if (s->stack_ptr >= WEBVTT_STACK_SIZE)
54
+        return AVERROR(EOVERFLOW);
55
+    s->stack[s->stack_ptr++] = c;
56
+    return 0;
57
+}
58
+
59
+static char webvtt_stack_pop(WebVTTContext *s)
60
+{
61
+    if (s->stack_ptr <= 0)
62
+        return 0;
63
+    return s->stack[--s->stack_ptr];
64
+}
65
+
66
+static int webvtt_stack_find(WebVTTContext *s, const char c)
67
+{
68
+    int i;
69
+    for (i = s->stack_ptr-1; i >= 0; i--)
70
+        if (s->stack[i] == c)
71
+            break;
72
+    return i;
73
+}
74
+
75
+static void webvtt_close_tag(WebVTTContext *s, char tag)
76
+{
77
+    webvtt_print(s, "</%c>", tag);
78
+}
79
+
80
+static void webvtt_stack_push_pop(WebVTTContext *s, const char c, int close)
81
+{
82
+    if (close) {
83
+        int i = c ? webvtt_stack_find(s, c) : 0;
84
+        if (i < 0)
85
+            return;
86
+        while (s->stack_ptr != i)
87
+            webvtt_close_tag(s, webvtt_stack_pop(s));
88
+    } else if (webvtt_stack_push(s, c) < 0)
89
+        av_log(s->avctx, AV_LOG_ERROR, "tag stack overflow\n");
90
+}
91
+
92
+static void webvtt_style_apply(WebVTTContext *s, const char *style)
93
+{
94
+    ASSStyle *st = ff_ass_style_get(s->ass_ctx, style);
95
+    if (st) {
96
+        if (st->bold != ASS_DEFAULT_BOLD) {
97
+            webvtt_print(s, "<b>");
98
+            webvtt_stack_push(s, 'b');
99
+        }
100
+        if (st->italic != ASS_DEFAULT_ITALIC) {
101
+            webvtt_print(s, "<i>");
102
+            webvtt_stack_push(s, 'i');
103
+        }
104
+        if (st->underline != ASS_DEFAULT_UNDERLINE) {
105
+            webvtt_print(s, "<u>");
106
+            webvtt_stack_push(s, 'u');
107
+        }
108
+    }
109
+}
110
+
111
+static void webvtt_text_cb(void *priv, const char *text, int len)
112
+{
113
+    WebVTTContext *s = priv;
114
+    av_bprint_append_data(&s->buffer, text, len);
115
+}
116
+
117
+static void webvtt_new_line_cb(void *priv, int forced)
118
+{
119
+    webvtt_print(priv, "\n");
120
+}
121
+
122
+static void webvtt_style_cb(void *priv, char style, int close)
123
+{
124
+    if (style == 's') // strikethrough unsupported
125
+        return;
126
+
127
+    webvtt_stack_push_pop(priv, style, close);
128
+    if (!close)
129
+        webvtt_print(priv, "<%c>", style);
130
+}
131
+
132
+static void webvtt_cancel_overrides_cb(void *priv, const char *style)
133
+{
134
+    webvtt_stack_push_pop(priv, 0, 1);
135
+    webvtt_style_apply(priv, style);
136
+}
137
+
138
+static void webvtt_end_cb(void *priv)
139
+{
140
+    webvtt_stack_push_pop(priv, 0, 1);
141
+}
142
+
143
+static const ASSCodesCallbacks webvtt_callbacks = {
144
+    .text             = webvtt_text_cb,
145
+    .new_line         = webvtt_new_line_cb,
146
+    .style            = webvtt_style_cb,
147
+    .color            = NULL,
148
+    .font_name        = NULL,
149
+    .font_size        = NULL,
150
+    .alignment        = NULL,
151
+    .cancel_overrides = webvtt_cancel_overrides_cb,
152
+    .move             = NULL,
153
+    .end              = webvtt_end_cb,
154
+};
155
+
156
+static int webvtt_encode_frame(AVCodecContext *avctx,
157
+                               unsigned char *buf, int bufsize, const AVSubtitle *sub)
158
+{
159
+    WebVTTContext *s = avctx->priv_data;
160
+    ASSDialog *dialog;
161
+    int i, num;
162
+
163
+    av_bprint_clear(&s->buffer);
164
+
165
+    for (i=0; i<sub->num_rects; i++) {
166
+        if (sub->rects[i]->type != SUBTITLE_ASS) {
167
+            av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
168
+            return AVERROR(ENOSYS);
169
+        }
170
+
171
+        dialog = ff_ass_split_dialog(s->ass_ctx, sub->rects[i]->ass, 0, &num);
172
+        for (; dialog && num--; dialog++) {
173
+            webvtt_style_apply(s, dialog->style);
174
+            ff_ass_split_override_codes(&webvtt_callbacks, s, dialog->text);
175
+        }
176
+    }
177
+
178
+    if (!av_bprint_is_complete(&s->buffer))
179
+        return AVERROR(ENOMEM);
180
+    if (!s->buffer.len)
181
+        return 0;
182
+
183
+    if (s->buffer.len > bufsize) {
184
+        av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n");
185
+        return -1;
186
+    }
187
+    memcpy(buf, s->buffer.str, s->buffer.len);
188
+
189
+    return s->buffer.len;
190
+}
191
+
192
+static int webvtt_encode_close(AVCodecContext *avctx)
193
+{
194
+    WebVTTContext *s = avctx->priv_data;
195
+    ff_ass_split_free(s->ass_ctx);
196
+    av_bprint_finalize(&s->buffer, NULL);
197
+    return 0;
198
+}
199
+
200
+static av_cold int webvtt_encode_init(AVCodecContext *avctx)
201
+{
202
+    WebVTTContext *s = avctx->priv_data;
203
+    s->avctx = avctx;
204
+    s->ass_ctx = ff_ass_split(avctx->subtitle_header);
205
+    av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
206
+    return s->ass_ctx ? 0 : AVERROR_INVALIDDATA;
207
+}
208
+
209
+AVCodec ff_webvtt_encoder = {
210
+    .name           = "webvtt",
211
+    .long_name      = NULL_IF_CONFIG_SMALL("WebVTT subtitle"),
212
+    .type           = AVMEDIA_TYPE_SUBTITLE,
213
+    .id             = AV_CODEC_ID_WEBVTT,
214
+    .priv_data_size = sizeof(WebVTTContext),
215
+    .init           = webvtt_encode_init,
216
+    .encode_sub     = webvtt_encode_frame,
217
+    .close          = webvtt_encode_close,
218
+};
... ...
@@ -93,6 +93,7 @@ AVOutputFormat ff_webvtt_muxer = {
93 93
     .long_name         = NULL_IF_CONFIG_SMALL("WebVTT subtitle"),
94 94
     .extensions        = "vtt",
95 95
     .mime_type         = "text/vtt",
96
+    .flags             = AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT,
96 97
     .subtitle_codec    = AV_CODEC_ID_WEBVTT,
97 98
     .write_header      = webvtt_write_header,
98 99
     .write_packet      = webvtt_write_packet,
... ...
@@ -52,6 +52,9 @@ fate-sub-vplayer: CMD = md5 -i $(TARGET_SAMPLES)/sub/VPlayer_capability_tester.t
52 52
 FATE_SUBTITLES_ASS-$(call DEMDEC, WEBVTT, WEBVTT) += fate-sub-webvtt
53 53
 fate-sub-webvtt: CMD = md5 -i $(TARGET_SAMPLES)/sub/WebVTT_capability_tester.vtt -f ass
54 54
 
55
+FATE_SUBTITLES_ASS-$(call ENCMUX, WEBVTT, WEBVTT) += fate-sub-webvttenc
56
+fate-sub-webvttenc: CMD = md5 -i $(TARGET_SAMPLES)/sub/SubRip_capability_tester.srt -f webvtt
57
+
55 58
 FATE_SUBTITLES_ASS-$(call ALLYES, MICRODVD_DEMUXER MICRODVD_DECODER ICONV) += fate-sub-charenc
56 59
 fate-sub-charenc: CMD = md5 -sub_charenc cp1251 -i $(TARGET_SAMPLES)/sub/cp1251-subtitles.sub -f ass
57 60
 
58 61
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+8683216a86e147a98f29dafee33a0987