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 34typedef struct { 35 const AVClass *class; 36 FFDemuxSubtitlesQueue q; 37 int kind; 38} WebVTTContext; 39 40static int webvtt_probe(const 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 52static 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 60static int webvtt_read_header(AVFormatContext *s) 61{ 62 WebVTTContext *webvtt = s->priv_data; 63 AVBPrint 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->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE; 71 st->codecpar->codec_id = AV_CODEC_ID_WEBVTT; 72 st->disposition |= webvtt->kind; 73 74 av_bprint_init(&cue, 0, AV_BPRINT_SIZE_UNLIMITED); 75 76 for (;;) { 77 int i; 78 int64_t pos; 79 AVPacket *sub; 80 const char *p, *identifier, *settings; 81 size_t identifier_len, settings_len; 82 int64_t ts_start, ts_end; 83 84 ff_subtitles_read_chunk(s->pb, &cue); 85 86 if (!cue.len) 87 break; 88 89 p = identifier = cue.str; 90 pos = avio_tell(s->pb); 91 92 /* ignore header chunk */ 93 if (!strncmp(p, "\xEF\xBB\xBFWEBVTT", 9) || 94 !strncmp(p, "WEBVTT", 6) || 95 !strncmp(p, "NOTE", 4)) 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 += 2; 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\r\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(s, &webvtt->q); 165 166end: 167 av_bprint_finalize(&cue, NULL); 168 return res; 169} 170 171static int webvtt_read_packet(AVFormatContext *s, AVPacket *pkt) 172{ 173 WebVTTContext *webvtt = s->priv_data; 174 return ff_subtitles_queue_read_packet(&webvtt->q, pkt); 175} 176 177static int webvtt_read_seek(AVFormatContext *s, int stream_index, 178 int64_t min_ts, int64_t ts, int64_t max_ts, int flags) 179{ 180 WebVTTContext *webvtt = s->priv_data; 181 return ff_subtitles_queue_seek(&webvtt->q, s, stream_index, 182 min_ts, ts, max_ts, flags); 183} 184 185static int webvtt_read_close(AVFormatContext *s) 186{ 187 WebVTTContext *webvtt = s->priv_data; 188 ff_subtitles_queue_clean(&webvtt->q); 189 return 0; 190} 191 192#define OFFSET(x) offsetof(WebVTTContext, x) 193#define KIND_FLAGS AV_OPT_FLAG_SUBTITLE_PARAM|AV_OPT_FLAG_DECODING_PARAM 194 195static const AVOption options[] = { 196 { "kind", "Set kind of WebVTT track", OFFSET(kind), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, KIND_FLAGS, "webvtt_kind" }, 197 { "subtitles", "WebVTT subtitles kind", 0, AV_OPT_TYPE_CONST, { .i64 = 0 }, INT_MIN, INT_MAX, KIND_FLAGS, "webvtt_kind" }, 198 { "captions", "WebVTT captions kind", 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_CAPTIONS }, INT_MIN, INT_MAX, KIND_FLAGS, "webvtt_kind" }, 199 { "descriptions", "WebVTT descriptions kind", 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_DESCRIPTIONS }, INT_MIN, INT_MAX, KIND_FLAGS, "webvtt_kind" }, 200 { "metadata", "WebVTT metadata kind", 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_METADATA }, INT_MIN, INT_MAX, KIND_FLAGS, "webvtt_kind" }, 201 { NULL } 202}; 203 204static const AVClass webvtt_demuxer_class = { 205 .class_name = "WebVTT demuxer", 206 .item_name = av_default_item_name, 207 .option = options, 208 .version = LIBAVUTIL_VERSION_INT, 209}; 210 211const AVInputFormat ff_webvtt_demuxer = { 212 .name = "webvtt", 213 .long_name = NULL_IF_CONFIG_SMALL("WebVTT subtitle"), 214 .priv_data_size = sizeof(WebVTTContext), 215 .flags_internal = FF_FMT_INIT_CLEANUP, 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}; 224