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