Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
4349 | Serge | 1 | /* |
2 | * Copyright (c) 2012 Clément Bœsch |
||
3 | * |
||
4 | * This file is part of FFmpeg. |
||
5 | * |
||
6 | * FFmpeg is free software; you can redistribute it and/or |
||
7 | * modify it under the terms of the GNU Lesser General Public |
||
8 | * License as published by the Free Software Foundation; either |
||
9 | * version 2.1 of the License, or (at your option) any later version. |
||
10 | * |
||
11 | * FFmpeg is distributed in the hope that it will be useful, |
||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||
14 | * Lesser General Public License for more details. |
||
15 | * |
||
16 | * You should have received a copy of the GNU Lesser General Public |
||
17 | * License along with FFmpeg; if not, write to the Free Software |
||
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||
19 | */ |
||
20 | |||
21 | /** |
||
22 | * @file |
||
23 | * WebVTT subtitle demuxer |
||
24 | * @see http://dev.w3.org/html5/webvtt/ |
||
25 | */ |
||
26 | |||
27 | #include "avformat.h" |
||
28 | #include "internal.h" |
||
29 | #include "subtitles.h" |
||
30 | #include "libavutil/bprint.h" |
||
31 | #include "libavutil/intreadwrite.h" |
||
32 | #include "libavutil/opt.h" |
||
33 | |||
34 | typedef struct { |
||
35 | const AVClass *class; |
||
36 | FFDemuxSubtitlesQueue q; |
||
37 | int kind; |
||
38 | } WebVTTContext; |
||
39 | |||
40 | static int webvtt_probe(AVProbeData *p) |
||
41 | { |
||
42 | const uint8_t *ptr = p->buf; |
||
43 | |||
44 | if (AV_RB24(ptr) == 0xEFBBBF) |
||
45 | ptr += 3; /* skip UTF-8 BOM */ |
||
46 | if (!strncmp(ptr, "WEBVTT", 6) && |
||
47 | (!ptr[6] || strchr("\n\r\t ", ptr[6]))) |
||
48 | return AVPROBE_SCORE_MAX; |
||
49 | return 0; |
||
50 | } |
||
51 | |||
52 | static int64_t read_ts(const char *s) |
||
53 | { |
||
54 | int hh, mm, ss, ms; |
||
55 | if (sscanf(s, "%u:%u:%u.%u", &hh, &mm, &ss, &ms) == 4) return (hh*3600LL + mm*60LL + ss) * 1000LL + ms; |
||
56 | if (sscanf(s, "%u:%u.%u", &mm, &ss, &ms) == 3) return ( mm*60LL + ss) * 1000LL + ms; |
||
57 | return AV_NOPTS_VALUE; |
||
58 | } |
||
59 | |||
60 | static int webvtt_read_header(AVFormatContext *s) |
||
61 | { |
||
62 | WebVTTContext *webvtt = s->priv_data; |
||
63 | AVBPrint header, cue; |
||
64 | int res = 0; |
||
65 | AVStream *st = avformat_new_stream(s, NULL); |
||
66 | |||
67 | if (!st) |
||
68 | return AVERROR(ENOMEM); |
||
69 | avpriv_set_pts_info(st, 64, 1, 1000); |
||
70 | st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; |
||
71 | st->codec->codec_id = AV_CODEC_ID_WEBVTT; |
||
72 | st->disposition |= webvtt->kind; |
||
73 | |||
74 | av_bprint_init(&header, 0, AV_BPRINT_SIZE_UNLIMITED); |
||
75 | av_bprint_init(&cue, 0, AV_BPRINT_SIZE_UNLIMITED); |
||
76 | |||
77 | for (;;) { |
||
78 | int i; |
||
79 | int64_t pos; |
||
80 | AVPacket *sub; |
||
81 | const char *p, *identifier, *settings; |
||
82 | int identifier_len, settings_len; |
||
83 | int64_t ts_start, ts_end; |
||
84 | |||
85 | ff_subtitles_read_chunk(s->pb, &cue); |
||
86 | |||
87 | if (!cue.len) |
||
88 | break; |
||
89 | |||
90 | p = identifier = cue.str; |
||
91 | pos = avio_tell(s->pb); |
||
92 | |||
93 | /* ignore header chunk */ |
||
94 | if (!strncmp(p, "\xEF\xBB\xBFWEBVTT", 9) || |
||
95 | !strncmp(p, "WEBVTT", 6)) |
||
96 | continue; |
||
97 | |||
98 | /* optional cue identifier (can be a number like in SRT or some kind of |
||
99 | * chaptering id) */ |
||
100 | for (i = 0; p[i] && p[i] != '\n' && p[i] != '\r'; i++) { |
||
101 | if (!strncmp(p + i, "-->", 3)) { |
||
102 | identifier = NULL; |
||
103 | break; |
||
104 | } |
||
105 | } |
||
106 | if (!identifier) |
||
107 | identifier_len = 0; |
||
108 | else { |
||
109 | identifier_len = strcspn(p, "\r\n"); |
||
110 | p += identifier_len; |
||
111 | if (*p == '\r') |
||
112 | p++; |
||
113 | if (*p == '\n') |
||
114 | p++; |
||
115 | } |
||
116 | |||
117 | /* cue timestamps */ |
||
118 | if ((ts_start = read_ts(p)) == AV_NOPTS_VALUE) |
||
119 | break; |
||
120 | if (!(p = strstr(p, "-->"))) |
||
121 | break; |
||
122 | p += 3; |
||
123 | do p++; while (*p == ' ' || *p == '\t'); |
||
124 | if ((ts_end = read_ts(p)) == AV_NOPTS_VALUE) |
||
125 | break; |
||
126 | |||
127 | /* optional cue settings */ |
||
128 | p += strcspn(p, "\n\t "); |
||
129 | while (*p == '\t' || *p == ' ') |
||
130 | p++; |
||
131 | settings = p; |
||
132 | settings_len = strcspn(p, "\r\n"); |
||
133 | p += settings_len; |
||
134 | if (*p == '\r') |
||
135 | p++; |
||
136 | if (*p == '\n') |
||
137 | p++; |
||
138 | |||
139 | /* create packet */ |
||
140 | sub = ff_subtitles_queue_insert(&webvtt->q, p, strlen(p), 0); |
||
141 | if (!sub) { |
||
142 | res = AVERROR(ENOMEM); |
||
143 | goto end; |
||
144 | } |
||
145 | sub->pos = pos; |
||
146 | sub->pts = ts_start; |
||
147 | sub->duration = ts_end - ts_start; |
||
148 | |||
149 | #define SET_SIDE_DATA(name, type) do { \ |
||
150 | if (name##_len) { \ |
||
151 | uint8_t *buf = av_packet_new_side_data(sub, type, name##_len); \ |
||
152 | if (!buf) { \ |
||
153 | res = AVERROR(ENOMEM); \ |
||
154 | goto end; \ |
||
155 | } \ |
||
156 | memcpy(buf, name, name##_len); \ |
||
157 | } \ |
||
158 | } while (0) |
||
159 | |||
160 | SET_SIDE_DATA(identifier, AV_PKT_DATA_WEBVTT_IDENTIFIER); |
||
161 | SET_SIDE_DATA(settings, AV_PKT_DATA_WEBVTT_SETTINGS); |
||
162 | } |
||
163 | |||
164 | ff_subtitles_queue_finalize(&webvtt->q); |
||
165 | |||
166 | end: |
||
167 | av_bprint_finalize(&cue, NULL); |
||
168 | av_bprint_finalize(&header, NULL); |
||
169 | return res; |
||
170 | } |
||
171 | |||
172 | static int webvtt_read_packet(AVFormatContext *s, AVPacket *pkt) |
||
173 | { |
||
174 | WebVTTContext *webvtt = s->priv_data; |
||
175 | return ff_subtitles_queue_read_packet(&webvtt->q, pkt); |
||
176 | } |
||
177 | |||
178 | static int webvtt_read_seek(AVFormatContext *s, int stream_index, |
||
179 | int64_t min_ts, int64_t ts, int64_t max_ts, int flags) |
||
180 | { |
||
181 | WebVTTContext *webvtt = s->priv_data; |
||
182 | return ff_subtitles_queue_seek(&webvtt->q, s, stream_index, |
||
183 | min_ts, ts, max_ts, flags); |
||
184 | } |
||
185 | |||
186 | static int webvtt_read_close(AVFormatContext *s) |
||
187 | { |
||
188 | WebVTTContext *webvtt = s->priv_data; |
||
189 | ff_subtitles_queue_clean(&webvtt->q); |
||
190 | return 0; |
||
191 | } |
||
192 | |||
193 | #define OFFSET(x) offsetof(WebVTTContext, x) |
||
194 | #define KIND_FLAGS AV_OPT_FLAG_SUBTITLE_PARAM |
||
195 | |||
196 | static const AVOption options[] = { |
||
197 | { "kind", "Set kind of WebVTT track", OFFSET(kind), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, KIND_FLAGS, "webvtt_kind" }, |
||
198 | { "subtitles", "WebVTT subtitles kind", 0, AV_OPT_TYPE_CONST, { .i64 = 0 }, INT_MIN, INT_MAX, 0, "webvtt_kind" }, |
||
199 | { "captions", "WebVTT captions kind", 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_CAPTIONS }, INT_MIN, INT_MAX, 0, "webvtt_kind" }, |
||
200 | { "descriptions", "WebVTT descriptions kind", 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_DESCRIPTIONS }, INT_MIN, INT_MAX, 0, "webvtt_kind" }, |
||
201 | { "metadata", "WebVTT metadata kind", 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_METADATA }, INT_MIN, INT_MAX, 0, "webvtt_kind" }, |
||
202 | { NULL } |
||
203 | }; |
||
204 | |||
205 | static const AVClass webvtt_demuxer_class = { |
||
206 | .class_name = "WebVTT demuxer", |
||
207 | .item_name = av_default_item_name, |
||
208 | .option = options, |
||
209 | .version = LIBAVUTIL_VERSION_INT, |
||
210 | }; |
||
211 | |||
212 | AVInputFormat ff_webvtt_demuxer = { |
||
213 | .name = "webvtt", |
||
214 | .long_name = NULL_IF_CONFIG_SMALL("WebVTT subtitle"), |
||
215 | .priv_data_size = sizeof(WebVTTContext), |
||
216 | .read_probe = webvtt_probe, |
||
217 | .read_header = webvtt_read_header, |
||
218 | .read_packet = webvtt_read_packet, |
||
219 | .read_seek2 = webvtt_read_seek, |
||
220 | .read_close = webvtt_read_close, |
||
221 | .extensions = "vtt", |
||
222 | .priv_class = &webvtt_demuxer_class, |
||
223 | }; |