Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [FFmpeg-devel] [PATCH] Ignore unknown blocks in WebVTT decoding (PR #20875)
@ 2025-11-09 14:25 socram via ffmpeg-devel
  0 siblings, 0 replies; only message in thread
From: socram via ffmpeg-devel @ 2025-11-09 14:25 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: socram

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 <marcos@orca.pet>
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 <marcos@orca.pet>
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 -- ffmpeg-devel@ffmpeg.org
To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2025-11-09 14:26 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-11-09 14:25 [FFmpeg-devel] [PATCH] Ignore unknown blocks in WebVTT decoding (PR #20875) socram via ffmpeg-devel

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git