2 * Copyright (c) 2012 Clément Bœsch
4 * This file is part of FFmpeg.
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.
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.
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
23 * WebVTT subtitle demuxer
24 * @see https://www.w3.org/TR/webvtt1/
30 #include "subtitles.h"
31 #include "libavutil/bprint.h"
32 #include "libavutil/intreadwrite.h"
33 #include "libavutil/opt.h"
37 FFDemuxSubtitlesQueue q
;
41 static int webvtt_probe(const AVProbeData
*p
)
43 const uint8_t *ptr
= p
->buf
;
45 if (AV_RB24(ptr
) == 0xEFBBBF)
46 ptr
+= 3; /* skip UTF-8 BOM */
47 if (!strncmp(ptr
, "WEBVTT", 6) &&
48 (!ptr
[6] || strchr("\n\r\t ", ptr
[6])))
49 return AVPROBE_SCORE_MAX
;
53 static int64_t read_ts(const char *s
)
56 if (sscanf(s
, "%u:%u:%u.%u", &hh
, &mm
, &ss
, &ms
) == 4) return (hh
*3600LL + mm
*60LL + ss
) * 1000LL + ms
;
57 if (sscanf(s
, "%u:%u.%u", &mm
, &ss
, &ms
) == 3) return ( mm
*60LL + ss
) * 1000LL + ms
;
58 return AV_NOPTS_VALUE
;
61 static int webvtt_read_header(AVFormatContext
*s
)
63 WebVTTContext
*webvtt
= s
->priv_data
;
66 AVStream
*st
= avformat_new_stream(s
, NULL
);
69 return AVERROR(ENOMEM
);
70 avpriv_set_pts_info(st
, 64, 1, 1000);
71 st
->codecpar
->codec_type
= AVMEDIA_TYPE_SUBTITLE
;
72 st
->codecpar
->codec_id
= AV_CODEC_ID_WEBVTT
;
73 st
->disposition
|= webvtt
->kind
;
75 av_bprint_init(&cue
, 0, AV_BPRINT_SIZE_UNLIMITED
);
81 const char *p
, *identifier
, *settings
;
82 size_t identifier_len
, settings_len
;
83 int64_t ts_start
, ts_end
;
85 res
= ff_subtitles_read_chunk(s
->pb
, &cue
);
92 p
= identifier
= cue
.str
;
93 pos
= avio_tell(s
->pb
);
95 /* ignore header chunk */
96 if (!strncmp(p
, "\xEF\xBB\xBFWEBVTT", 9) ||
97 !strncmp(p
, "WEBVTT", 6) ||
98 !strncmp(p
, "STYLE", 5) ||
99 !strncmp(p
, "REGION", 6) ||
100 !strncmp(p
, "NOTE", 4))
103 /* optional cue identifier (can be a number like in SRT or some kind of
105 for (i
= 0; p
[i
] && p
[i
] != '\n' && p
[i
] != '\r'; i
++) {
106 if (!strncmp(p
+ i
, "-->", 3)) {
114 identifier_len
= strcspn(p
, "\r\n");
123 if ((ts_start
= read_ts(p
)) == AV_NOPTS_VALUE
)
125 if (!(p
= strstr(p
, "-->")))
128 do p
++; while (*p
== ' ' || *p
== '\t');
129 if ((ts_end
= read_ts(p
)) == AV_NOPTS_VALUE
)
132 /* optional cue settings */
133 p
+= strcspn(p
, "\n\r\t ");
134 while (*p
== '\t' || *p
== ' ')
137 settings_len
= strcspn(p
, "\r\n");
145 sub
= ff_subtitles_queue_insert(&webvtt
->q
, p
, strlen(p
), 0);
147 res
= AVERROR(ENOMEM
);
152 sub
->duration
= ts_end
- ts_start
;
154 #define SET_SIDE_DATA(name, type) do { \
156 uint8_t *buf = av_packet_new_side_data(sub, type, name##_len); \
158 res = AVERROR(ENOMEM); \
161 memcpy(buf, name, name##_len); \
165 SET_SIDE_DATA(identifier
, AV_PKT_DATA_WEBVTT_IDENTIFIER
);
166 SET_SIDE_DATA(settings
, AV_PKT_DATA_WEBVTT_SETTINGS
);
169 ff_subtitles_queue_finalize(s
, &webvtt
->q
);
172 av_bprint_finalize(&cue
, NULL
);
176 static int webvtt_read_packet(AVFormatContext
*s
, AVPacket
*pkt
)
178 WebVTTContext
*webvtt
= s
->priv_data
;
179 return ff_subtitles_queue_read_packet(&webvtt
->q
, pkt
);
182 static int webvtt_read_seek(AVFormatContext
*s
, int stream_index
,
183 int64_t min_ts
, int64_t ts
, int64_t max_ts
, int flags
)
185 WebVTTContext
*webvtt
= s
->priv_data
;
186 return ff_subtitles_queue_seek(&webvtt
->q
, s
, stream_index
,
187 min_ts
, ts
, max_ts
, flags
);
190 static int webvtt_read_close(AVFormatContext
*s
)
192 WebVTTContext
*webvtt
= s
->priv_data
;
193 ff_subtitles_queue_clean(&webvtt
->q
);
197 #define OFFSET(x) offsetof(WebVTTContext, x)
198 #define KIND_FLAGS AV_OPT_FLAG_SUBTITLE_PARAM|AV_OPT_FLAG_DECODING_PARAM
200 static const AVOption options
[] = {
201 { "kind", "Set kind of WebVTT track", OFFSET(kind
), AV_OPT_TYPE_INT
, { .i64
= 0 }, 0, INT_MAX
, KIND_FLAGS
, .unit
= "webvtt_kind" },
202 { "subtitles", "WebVTT subtitles kind", 0, AV_OPT_TYPE_CONST
, { .i64
= 0 }, INT_MIN
, INT_MAX
, KIND_FLAGS
, .unit
= "webvtt_kind" },
203 { "captions", "WebVTT captions kind", 0, AV_OPT_TYPE_CONST
, { .i64
= AV_DISPOSITION_CAPTIONS
}, INT_MIN
, INT_MAX
, KIND_FLAGS
, .unit
= "webvtt_kind" },
204 { "descriptions", "WebVTT descriptions kind", 0, AV_OPT_TYPE_CONST
, { .i64
= AV_DISPOSITION_DESCRIPTIONS
}, INT_MIN
, INT_MAX
, KIND_FLAGS
, .unit
= "webvtt_kind" },
205 { "metadata", "WebVTT metadata kind", 0, AV_OPT_TYPE_CONST
, { .i64
= AV_DISPOSITION_METADATA
}, INT_MIN
, INT_MAX
, KIND_FLAGS
, .unit
= "webvtt_kind" },
209 static const AVClass webvtt_demuxer_class
= {
210 .class_name
= "WebVTT demuxer",
211 .item_name
= av_default_item_name
,
213 .version
= LIBAVUTIL_VERSION_INT
,
216 const FFInputFormat ff_webvtt_demuxer
= {
218 .p
.long_name
= NULL_IF_CONFIG_SMALL("WebVTT subtitle"),
219 .p
.mime_type
= "text/vtt",
220 .p
.extensions
= "vtt,webvtt",
221 .p
.priv_class
= &webvtt_demuxer_class
,
222 .priv_data_size
= sizeof(WebVTTContext
),
223 .flags_internal
= FF_INFMT_FLAG_INIT_CLEANUP
,
224 .read_probe
= webvtt_probe
,
225 .read_header
= webvtt_read_header
,
226 .read_packet
= webvtt_read_packet
,
227 .read_seek2
= webvtt_read_seek
,
228 .read_close
= webvtt_read_close
,