PR #20875 opened by socram URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20875 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20875.patch
[As previously discussed in the mailing list](https://ffmpeg.org/pipermail/ffmpeg-devel/2025-July/346806.html) this change makes the decoder ignore all unknown blocks, only failing if the file header is not valid, which follows closer the official specification. This is required to properly parse for some WebVTT that use [an older specification of the "Region" chunk](https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/region.html), such as https://statics.3cat.cat/multimedia/vtt/2/4/1745967620742.vtt used by 3Cat, the streaming service of a national TV broadcaster. As requested on the mailing list, this splits the patch in two for easier verification: one simply moves the logic to the function, and the other actually makes the change in logic. >From f2c112a6e07d3aa9cb79e2d8598a409d69c4b393 Mon Sep 17 00:00:00 2001 From: Marcos Del Sol Vives <[email protected]> Date: Sun, 9 Nov 2025 14:26:33 +0100 Subject: [PATCH 1/2] avformat/webvttdec: move cue processing logic to its own function --- libavformat/webvttdec.c | 166 ++++++++++++++++++++-------------------- 1 file changed, 85 insertions(+), 81 deletions(-) diff --git a/libavformat/webvttdec.c b/libavformat/webvttdec.c index 4ca1e939b1..2e9e9a50b4 100644 --- a/libavformat/webvttdec.c +++ b/libavformat/webvttdec.c @@ -58,6 +58,89 @@ static int64_t read_ts(const char *s) return AV_NOPTS_VALUE; } +static int webvtt_parse_cue(WebVTTContext *webvtt, AVBPrint *cue, int64_t pos) +{ + int i; + AVPacket *sub; + const char *p, *identifier, *settings; + size_t identifier_len, settings_len; + int64_t ts_start, ts_end; + + p = identifier = cue->str; + + /* ignore header chunk */ + if (!strncmp(p, "\xEF\xBB\xBFWEBVTT", 9) || + !strncmp(p, "WEBVTT", 6) || + !strncmp(p, "STYLE", 5) || + !strncmp(p, "REGION", 6) || + !strncmp(p, "NOTE", 4)) + return 0; + + /* optional cue identifier (can be a number like in SRT or some kind of + * chaptering id) */ + for (i = 0; p[i] && p[i] != '\n' && p[i] != '\r'; i++) { + if (!strncmp(p + i, "-->", 3)) { + identifier = NULL; + break; + } + } + if (!identifier) + identifier_len = 0; + else { + identifier_len = strcspn(p, "\r\n"); + p += identifier_len; + if (*p == '\r') + p++; + if (*p == '\n') + p++; + } + + /* cue timestamps */ + if ((ts_start = read_ts(p)) == AV_NOPTS_VALUE) + return AVERROR_INVALIDDATA; + if (!(p = strstr(p, "-->"))) + return AVERROR_INVALIDDATA; + p += 2; + do p++; while (*p == ' ' || *p == '\t'); + if ((ts_end = read_ts(p)) == AV_NOPTS_VALUE) + return AVERROR_INVALIDDATA; + + /* optional cue settings */ + p += strcspn(p, "\n\r\t "); + while (*p == '\t' || *p == ' ') + p++; + settings = p; + settings_len = strcspn(p, "\r\n"); + p += settings_len; + if (*p == '\r') + p++; + if (*p == '\n') + p++; + + /* create packet */ + sub = ff_subtitles_queue_insert(&webvtt->q, p, strlen(p), 0); + if (!sub) { + return AVERROR(ENOMEM); + } + sub->pos = pos; + sub->pts = ts_start; + sub->duration = ts_end - ts_start; + +#define SET_SIDE_DATA(name, type) do { \ + if (name##_len) { \ + uint8_t *buf = av_packet_new_side_data(sub, type, name##_len); \ + if (!buf) \ + return AVERROR(ENOMEM); \ + memcpy(buf, name, name##_len); \ + } \ +} while (0) + + SET_SIDE_DATA(identifier, AV_PKT_DATA_WEBVTT_IDENTIFIER); + SET_SIDE_DATA(settings, AV_PKT_DATA_WEBVTT_SETTINGS); + + return 0; +} + static int webvtt_read_header(AVFormatContext *s) { WebVTTContext *webvtt = s->priv_data; @@ -75,13 +158,6 @@ static int webvtt_read_header(AVFormatContext *s) av_bprint_init(&cue, 0, AV_BPRINT_SIZE_UNLIMITED); for (;;) { - int i; - int64_t pos; - AVPacket *sub; - const char *p, *identifier, *settings; - size_t identifier_len, settings_len; - int64_t ts_start, ts_end; - res = ff_subtitles_read_chunk(s->pb, &cue); if (res < 0) goto end; @@ -89,81 +165,9 @@ static int webvtt_read_header(AVFormatContext *s) if (!cue.len) break; - p = identifier = cue.str; - pos = avio_tell(s->pb); - - /* ignore header chunk */ - if (!strncmp(p, "\xEF\xBB\xBFWEBVTT", 9) || - !strncmp(p, "WEBVTT", 6) || - !strncmp(p, "STYLE", 5) || - !strncmp(p, "REGION", 6) || - !strncmp(p, "NOTE", 4)) - continue; - - /* optional cue identifier (can be a number like in SRT or some kind of - * chaptering id) */ - for (i = 0; p[i] && p[i] != '\n' && p[i] != '\r'; i++) { - if (!strncmp(p + i, "-->", 3)) { - identifier = NULL; - break; - } - } - if (!identifier) - identifier_len = 0; - else { - identifier_len = strcspn(p, "\r\n"); - p += identifier_len; - if (*p == '\r') - p++; - if (*p == '\n') - p++; - } - - /* cue timestamps */ - if ((ts_start = read_ts(p)) == AV_NOPTS_VALUE) + res = webvtt_parse_cue(webvtt, &cue, avio_tell(s->pb)); + if (res < 0) break; - if (!(p = strstr(p, "-->"))) - break; - p += 2; - do p++; while (*p == ' ' || *p == '\t'); - if ((ts_end = read_ts(p)) == AV_NOPTS_VALUE) - break; - - /* optional cue settings */ - p += strcspn(p, "\n\r\t "); - while (*p == '\t' || *p == ' ') - p++; - settings = p; - settings_len = strcspn(p, "\r\n"); - p += settings_len; - if (*p == '\r') - p++; - if (*p == '\n') - p++; - - /* create packet */ - sub = ff_subtitles_queue_insert(&webvtt->q, p, strlen(p), 0); - if (!sub) { - res = AVERROR(ENOMEM); - goto end; - } - sub->pos = pos; - sub->pts = ts_start; - sub->duration = ts_end - ts_start; - -#define SET_SIDE_DATA(name, type) do { \ - if (name##_len) { \ - uint8_t *buf = av_packet_new_side_data(sub, type, name##_len); \ - if (!buf) { \ - res = AVERROR(ENOMEM); \ - goto end; \ - } \ - memcpy(buf, name, name##_len); \ - } \ -} while (0) - - SET_SIDE_DATA(identifier, AV_PKT_DATA_WEBVTT_IDENTIFIER); - SET_SIDE_DATA(settings, AV_PKT_DATA_WEBVTT_SETTINGS); } ff_subtitles_queue_finalize(s, &webvtt->q); -- 2.49.1 >From caf1c80be0fa7431ce7c3799dcae846b25180ebe Mon Sep 17 00:00:00 2001 From: Marcos Del Sol Vives <[email protected]> Date: Sun, 9 Nov 2025 15:18:26 +0100 Subject: [PATCH 2/2] avformat/webvttdec: ignore unknown blocks --- libavformat/webvttdec.c | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/libavformat/webvttdec.c b/libavformat/webvttdec.c index 2e9e9a50b4..19289b1e0a 100644 --- a/libavformat/webvttdec.c +++ b/libavformat/webvttdec.c @@ -68,14 +68,6 @@ static int webvtt_parse_cue(WebVTTContext *webvtt, AVBPrint *cue, int64_t pos) p = identifier = cue->str; - /* ignore header chunk */ - if (!strncmp(p, "\xEF\xBB\xBFWEBVTT", 9) || - !strncmp(p, "WEBVTT", 6) || - !strncmp(p, "STYLE", 5) || - !strncmp(p, "REGION", 6) || - !strncmp(p, "NOTE", 4)) - return 0; - /* optional cue identifier (can be a number like in SRT or some kind of * chaptering id) */ for (i = 0; p[i] && p[i] != '\n' && p[i] != '\r'; i++) { @@ -157,7 +149,28 @@ static int webvtt_read_header(AVFormatContext *s) av_bprint_init(&cue, 0, AV_BPRINT_SIZE_UNLIMITED); + res = ff_subtitles_read_chunk(s->pb, &cue); + if (res < 0) { + av_log(s, AV_LOG_ERROR, "Unable to read file header\n"); + goto end; + } + + if (!cue.len) { + av_log(s, AV_LOG_ERROR, "Unable to read file header\n"); + res = AVERROR_EOF; + goto end; + } + + if (!strncmp(cue.str, "\xEF\xBB\xBFWEBVTT", 9) && + !strncmp(cue.str, "WEBVTT", 6)) { + av_log(s, AV_LOG_ERROR, "Invalid file header\n"); + res = AVERROR_INVALIDDATA; + goto end; + } + for (;;) { + int64_t pos = avio_tell(s->pb); + res = ff_subtitles_read_chunk(s->pb, &cue); if (res < 0) goto end; @@ -165,9 +178,12 @@ static int webvtt_read_header(AVFormatContext *s) if (!cue.len) break; - res = webvtt_parse_cue(webvtt, &cue, avio_tell(s->pb)); - if (res < 0) - break; + res = webvtt_parse_cue(webvtt, &cue, pos); + if (res < 0) { + if (res != AVERROR_INVALIDDATA) + goto end; + av_log(s, AV_LOG_DEBUG, "Ignoring non-cue block at 0x%"PRIx64"\n", pos); + } } ff_subtitles_queue_finalize(s, &webvtt->q); -- 2.49.1 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
