Browse code

WebVTT demuxer and decoder.

Clément Bœsch authored on 2012/09/01 21:34:24
Showing 14 changed files
... ...
@@ -63,6 +63,7 @@ version next:
63 63
 - Smooth Streaming live segmenter muxer
64 64
 - F4V muxer
65 65
 - sendcmd and asendcmd filters
66
+- WebVTT demuxer and decoder (simple tags supported)
66 67
 
67 68
 
68 69
 version 0.11:
... ...
@@ -898,6 +898,7 @@ performance on systems without hardware floating point support).
898 898
 @item SubRip (SRT)     @tab X @tab X @tab X @tab X
899 899
 @item SubViewer        @tab   @tab X @tab   @tab X
900 900
 @item 3GPP Timed Text  @tab   @tab   @tab X @tab X
901
+@item WebVTT           @tab   @tab X @tab   @tab X
901 902
 @item XSUB             @tab   @tab   @tab X @tab X
902 903
 @end multitable
903 904
 
... ...
@@ -463,6 +463,7 @@ OBJS-$(CONFIG_VP6_DECODER)             += vp6.o vp56.o vp56data.o vp56dsp.o \
463 463
 OBJS-$(CONFIG_VP8_DECODER)             += vp8.o vp8dsp.o vp56rac.o
464 464
 OBJS-$(CONFIG_VQA_DECODER)             += vqavideo.o
465 465
 OBJS-$(CONFIG_WAVPACK_DECODER)         += wavpack.o
466
+OBJS-$(CONFIG_WEBVTT_DECODER)          += webvttdec.o
466 467
 OBJS-$(CONFIG_WMALOSSLESS_DECODER)     += wmalosslessdec.o wma_common.o
467 468
 OBJS-$(CONFIG_WMAPRO_DECODER)          += wmaprodec.o wma.o wma_common.o
468 469
 OBJS-$(CONFIG_WMAV1_DECODER)           += wmadec.o wma.o wma_common.o aactab.o
... ...
@@ -422,6 +422,7 @@ void avcodec_register_all(void)
422 422
     REGISTER_ENCDEC  (SRT, srt);
423 423
     REGISTER_ENCDEC  (SUBRIP, subrip);
424 424
     REGISTER_DECODER (SUBVIEWER, subviewer);
425
+    REGISTER_DECODER (WEBVTT, webvtt);
425 426
     REGISTER_ENCDEC  (XSUB, xsub);
426 427
 
427 428
     /* external libraries */
... ...
@@ -448,6 +448,7 @@ enum AVCodecID {
448 448
     AV_CODEC_ID_REALTEXT   = MKBETAG('R','T','X','T'),
449 449
     AV_CODEC_ID_SUBVIEWER  = MKBETAG('S','u','b','V'),
450 450
     AV_CODEC_ID_SUBRIP     = MKBETAG('S','R','i','p'),
451
+    AV_CODEC_ID_WEBVTT     = MKBETAG('W','V','T','T'),
451 452
 
452 453
     /* other specific kind of codecs (generally used for attachments) */
453 454
     AV_CODEC_ID_FIRST_UNKNOWN = 0x18000,           ///< A dummy ID pointing at the start of various fake codecs.
... ...
@@ -2343,6 +2343,12 @@ static const AVCodecDescriptor codec_descriptors[] = {
2343 2343
         .long_name = NULL_IF_CONFIG_SMALL("SubViewer subtitle"),
2344 2344
     },
2345 2345
     {
2346
+        .id        = AV_CODEC_ID_WEBVTT,
2347
+        .type      = AVMEDIA_TYPE_SUBTITLE,
2348
+        .name      = "webvtt",
2349
+        .long_name = NULL_IF_CONFIG_SMALL("WebVTT subtitle"),
2350
+    },
2351
+    {
2346 2352
         .id        = AV_CODEC_ID_BINTEXT,
2347 2353
         .type      = AVMEDIA_TYPE_VIDEO,
2348 2354
         .name      = "bintext",
... ...
@@ -27,7 +27,7 @@
27 27
  */
28 28
 
29 29
 #define LIBAVCODEC_VERSION_MAJOR 54
30
-#define LIBAVCODEC_VERSION_MINOR 57
30
+#define LIBAVCODEC_VERSION_MINOR 58
31 31
 #define LIBAVCODEC_VERSION_MICRO 100
32 32
 
33 33
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
34 34
new file mode 100644
... ...
@@ -0,0 +1,100 @@
0
+/*
1
+ * Copyright (c) 2012 Clément Bœsch
2
+ *
3
+ * This file is part of FFmpeg.
4
+ *
5
+ * FFmpeg is free software; you can redistribute it and/or
6
+ * modify it under the terms of the GNU Lesser General Public
7
+ * License as published by the Free Software Foundation; either
8
+ * version 2.1 of the License, or (at your option) any later version.
9
+ *
10
+ * FFmpeg is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
+ * Lesser General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Lesser General Public
16
+ * License along with FFmpeg; if not, write to the Free Software
17
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+ */
19
+
20
+/**
21
+ * @file
22
+ * WebVTT subtitle decoder
23
+ * @see http://dev.w3.org/html5/webvtt/
24
+ * @todo need to support extended markups and cue settings
25
+ */
26
+
27
+#include "avcodec.h"
28
+#include "ass.h"
29
+#include "libavutil/bprint.h"
30
+
31
+static const struct {
32
+    const char *from;
33
+    const char *to;
34
+} webvtt_tag_replace[] = {
35
+    {"<i>", "{\\i1}"}, {"</i>", "{\\i0}"},
36
+    {"<b>", "{\\b1}"}, {"</b>", "{\\b0}"},
37
+    {"<u>", "{\\u1}"}, {"</u>", "{\\u0}"},
38
+    {"{", "\\{"}, {"}", "\\}"}, // escape to avoid ASS markup conflicts
39
+};
40
+
41
+static int webvtt_event_to_ass(AVBPrint *buf, const char *p)
42
+{
43
+    int i, skip = 0;
44
+
45
+    while (*p) {
46
+
47
+        for (i = 0; i < FF_ARRAY_ELEMS(webvtt_tag_replace); i++) {
48
+            const char *from = webvtt_tag_replace[i].from;
49
+            const size_t len = strlen(from);
50
+            if (!strncmp(p, from, len)) {
51
+                av_bprintf(buf, "%s", webvtt_tag_replace[i].to);
52
+                p += len;
53
+                break;
54
+            }
55
+        }
56
+        if (!*p)
57
+            break;
58
+
59
+        if (*p == '<')
60
+            skip = 1;
61
+        else if (*p == '>')
62
+            skip = 0;
63
+        else if (p[0] == '\n' && p[1])
64
+            av_bprintf(buf, "\\N");
65
+        else if (!skip && *p != '\r')
66
+            av_bprint_chars(buf, *p, 1);
67
+        p++;
68
+    }
69
+    av_bprintf(buf, "\r\n");
70
+    return 0;
71
+}
72
+
73
+static int webvtt_decode_frame(AVCodecContext *avctx,
74
+                               void *data, int *got_sub_ptr, AVPacket *avpkt)
75
+{
76
+    AVSubtitle *sub = data;
77
+    const char *ptr = avpkt->data;
78
+    AVBPrint buf;
79
+
80
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
81
+    if (ptr && avpkt->size > 0 && !webvtt_event_to_ass(&buf, ptr)) {
82
+        int ts_start     = av_rescale_q(avpkt->pts, avctx->time_base, (AVRational){1,100});
83
+        int ts_duration  = avpkt->duration != -1 ?
84
+                           av_rescale_q(avpkt->duration, avctx->time_base, (AVRational){1,100}) : -1;
85
+        ff_ass_add_rect(sub, buf.str, ts_start, ts_duration, 0);
86
+    }
87
+    *got_sub_ptr = sub->num_rects > 0;
88
+    av_bprint_finalize(&buf, NULL);
89
+    return avpkt->size;
90
+}
91
+
92
+AVCodec ff_webvtt_decoder = {
93
+    .name           = "webvtt",
94
+    .long_name      = NULL_IF_CONFIG_SMALL("WebVTT subtitle"),
95
+    .type           = AVMEDIA_TYPE_SUBTITLE,
96
+    .id             = AV_CODEC_ID_WEBVTT,
97
+    .decode         = webvtt_decode_frame,
98
+    .init           = ff_ass_subtitle_header_default,
99
+};
... ...
@@ -355,6 +355,7 @@ OBJS-$(CONFIG_WC3_DEMUXER)               += wc3movie.o
355 355
 OBJS-$(CONFIG_WEBM_MUXER)                += matroskaenc.o matroska.o \
356 356
                                             isom.o avc.o \
357 357
                                             flacenc_header.o avlanguage.o
358
+OBJS-$(CONFIG_WEBVTT_DEMUXER)            += webvttdec.o
358 359
 OBJS-$(CONFIG_WSAUD_DEMUXER)             += westwood_aud.o
359 360
 OBJS-$(CONFIG_WSVQA_DEMUXER)             += westwood_vqa.o
360 361
 OBJS-$(CONFIG_WTV_DEMUXER)               += wtvdec.o wtv.o asfdec.o asf.o asfcrypt.o \
... ...
@@ -252,6 +252,7 @@ void av_register_all(void)
252 252
     REGISTER_MUXDEMUX (WAV, wav);
253 253
     REGISTER_DEMUXER  (WC3, wc3);
254 254
     REGISTER_MUXER    (WEBM, webm);
255
+    REGISTER_DEMUXER  (WEBVTT, webvtt);
255 256
     REGISTER_DEMUXER  (WSAUD, wsaud);
256 257
     REGISTER_DEMUXER  (WSVQA, wsvqa);
257 258
     REGISTER_MUXDEMUX (WTV, wtv);
... ...
@@ -30,8 +30,8 @@
30 30
 #include "libavutil/avutil.h"
31 31
 
32 32
 #define LIBAVFORMAT_VERSION_MAJOR 54
33
-#define LIBAVFORMAT_VERSION_MINOR 27
34
-#define LIBAVFORMAT_VERSION_MICRO 101
33
+#define LIBAVFORMAT_VERSION_MINOR 28
34
+#define LIBAVFORMAT_VERSION_MICRO 100
35 35
 
36 36
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
37 37
                                                LIBAVFORMAT_VERSION_MINOR, \
38 38
new file mode 100644
... ...
@@ -0,0 +1,188 @@
0
+/*
1
+ * Copyright (c) 2012 Clément Bœsch
2
+ *
3
+ * This file is part of FFmpeg.
4
+ *
5
+ * FFmpeg is free software; you can redistribute it and/or
6
+ * modify it under the terms of the GNU Lesser General Public
7
+ * License as published by the Free Software Foundation; either
8
+ * version 2.1 of the License, or (at your option) any later version.
9
+ *
10
+ * FFmpeg is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
+ * Lesser General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Lesser General Public
16
+ * License along with FFmpeg; if not, write to the Free Software
17
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+ */
19
+
20
+/**
21
+ * @file
22
+ * WebVTT subtitle demuxer
23
+ * @see http://dev.w3.org/html5/webvtt/
24
+ */
25
+
26
+#include "avformat.h"
27
+#include "internal.h"
28
+#include "subtitles.h"
29
+#include "libavutil/bprint.h"
30
+#include "libavutil/intreadwrite.h"
31
+
32
+typedef struct {
33
+    FFDemuxSubtitlesQueue q;
34
+} WebVTTContext;
35
+
36
+static int webvtt_probe(AVProbeData *p)
37
+{
38
+    const uint8_t *ptr = p->buf;
39
+
40
+    if (AV_RB24(ptr) == 0xEFBBBF)
41
+        ptr += 3;  /* skip UTF-8 BOM */
42
+    if (!strncmp(ptr, "WEBVTT", 6) &&
43
+        (!ptr[6] || strchr("\n\r\t ", ptr[6])))
44
+        return AVPROBE_SCORE_MAX;
45
+    return 0;
46
+}
47
+
48
+static int64_t read_ts(const char *s)
49
+{
50
+    int hh, mm, ss, ms;
51
+    if (sscanf(s, "%u:%u:%u.%u", &hh, &mm, &ss, &ms) == 4) return (hh*3600 + mm*60 + ss) * 1000 + ms;
52
+    if (sscanf(s,    "%u:%u.%u",      &mm, &ss, &ms) == 3) return (          mm*60 + ss) * 1000 + ms;
53
+    return AV_NOPTS_VALUE;
54
+}
55
+
56
+static int64_t extract_cue(AVBPrint *buf, AVIOContext *pb)
57
+{
58
+    int prev_chr_is_eol = 0;
59
+    int64_t pos = avio_tell(pb);
60
+
61
+    av_bprint_clear(buf);
62
+    for (;;) {
63
+        char c = avio_r8(pb);
64
+        if (!c)
65
+            break;
66
+        if (c == '\r' || c == '\n') {
67
+            if (prev_chr_is_eol)
68
+                break;
69
+            prev_chr_is_eol = (c == '\n');
70
+        } else
71
+            prev_chr_is_eol = 0;
72
+        if (c != '\r')
73
+            av_bprint_chars(buf, c, 1);
74
+    }
75
+    av_bprint_chars(buf, '\0', 1);
76
+    return pos;
77
+}
78
+
79
+static int webvtt_read_header(AVFormatContext *s)
80
+{
81
+    WebVTTContext *webvtt = s->priv_data;
82
+    AVBPrint header, cue;
83
+    int res = 0;
84
+    AVStream *st = avformat_new_stream(s, NULL);
85
+
86
+    if (!st)
87
+        return AVERROR(ENOMEM);
88
+    avpriv_set_pts_info(st, 64, 1, 1000);
89
+    st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE;
90
+    st->codec->codec_id   = AV_CODEC_ID_WEBVTT;
91
+
92
+    av_bprint_init(&header, 0, AV_BPRINT_SIZE_UNLIMITED);
93
+    av_bprint_init(&cue,    0, AV_BPRINT_SIZE_UNLIMITED);
94
+
95
+    for (;;) {
96
+        int i, len;
97
+        int64_t pos = extract_cue(&cue, s->pb);
98
+        AVPacket *sub;
99
+        const char *p = cue.str;
100
+        const char *identifier = p;
101
+        //const char *settings = NULL;
102
+        int64_t ts_start, ts_end;
103
+
104
+        if (!*p) // EOF
105
+            break;
106
+
107
+        /* ignore header chunk */
108
+        if (!strncmp(p, "\xEF\xBB\xBFWEBVTT", 9) ||
109
+            !strncmp(p, "WEBVTT", 6))
110
+            continue;
111
+
112
+        /* optional cue identifier (can be a number like in SRT or some kind of
113
+         * chaptering id), silently skip it */
114
+        for (i = 0; p[i] && p[i] != '\n'; i++) {
115
+            if (!strncmp(p + i, "-->", 3)) {
116
+                identifier = NULL;
117
+                break;
118
+            }
119
+        }
120
+        if (identifier)
121
+            p += strcspn(p, "\n");
122
+
123
+        /* cue timestamps */
124
+        if ((ts_start = read_ts(p)) == AV_NOPTS_VALUE)
125
+            break;
126
+        if (!(p = strstr(p, "-->")))
127
+            break;
128
+        p += 3;
129
+        do p++; while (*p == ' ' || *p == '\t');
130
+        if ((ts_end = read_ts(p)) == AV_NOPTS_VALUE)
131
+            break;
132
+
133
+        /* optional cue settings, TODO: store in side_data */
134
+        p += strcspn(p, "\n\t ");
135
+        while (*p == '\t' || *p == ' ')
136
+            p++;
137
+        if (*p != '\n') {
138
+            //settings = p;
139
+            p += strcspn(p, "\n");
140
+        }
141
+        if (*p == '\n')
142
+            p++;
143
+
144
+        /* create packet */
145
+        len = cue.str + cue.len - p - 1;
146
+        sub = ff_subtitles_queue_insert(&webvtt->q, p, len, 0);
147
+        if (!sub) {
148
+            res = AVERROR(ENOMEM);
149
+            goto end;
150
+        }
151
+        sub->pos = pos;
152
+        sub->pts = ts_start;
153
+        sub->duration = ts_end - ts_start;
154
+    }
155
+
156
+    ff_subtitles_queue_finalize(&webvtt->q);
157
+
158
+end:
159
+    av_bprint_finalize(&cue,    NULL);
160
+    av_bprint_finalize(&header, NULL);
161
+    return res;
162
+}
163
+
164
+static int webvtt_read_packet(AVFormatContext *s, AVPacket *pkt)
165
+{
166
+    WebVTTContext *webvtt = s->priv_data;
167
+    return ff_subtitles_queue_read_packet(&webvtt->q, pkt);
168
+}
169
+
170
+static int webvtt_read_close(AVFormatContext *s)
171
+{
172
+    WebVTTContext *webvtt = s->priv_data;
173
+    ff_subtitles_queue_clean(&webvtt->q);
174
+    return 0;
175
+}
176
+
177
+AVInputFormat ff_webvtt_demuxer = {
178
+    .name           = "webvtt",
179
+    .long_name      = NULL_IF_CONFIG_SMALL("WebVTT subtitle"),
180
+    .priv_data_size = sizeof(WebVTTContext),
181
+    .read_probe     = webvtt_probe,
182
+    .read_header    = webvtt_read_header,
183
+    .read_packet    = webvtt_read_packet,
184
+    .read_close     = webvtt_read_close,
185
+    .flags          = AVFMT_GENERIC_INDEX,
186
+    .extensions     = "vtt",
187
+};
... ...
@@ -25,5 +25,8 @@ fate-sub-subripenc: CMD = md5 -i $(SAMPLES)/sub/MovText_capability_tester.mp4 -s
25 25
 FATE_SUBTITLES += fate-sub-subviewer
26 26
 fate-sub-subviewer: CMD = md5 -i $(SAMPLES)/sub/SubViewer_capability_tester.sub -f ass
27 27
 
28
+FATE_SUBTITLES += fate-sub-webvtt
29
+fate-sub-webvtt: CMD = md5 -i $(SAMPLES)/sub/WebVTT_capability_tester.vtt -f ass
30
+
28 31
 FATE_SAMPLES_FFMPEG += $(FATE_SUBTITLES)
29 32
 fate-subtitles: $(FATE_SUBTITLES)
30 33
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+5384a70c89ddca4b007fb7ffba95cffb