Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
From: Marcos Del Sol via ffmpeg-devel <ffmpeg-devel@ffmpeg.org>
To: ffmpeg-devel@ffmpeg.org
Cc: Marcos Del Sol <marcos@orca.pet>
Subject: Re: [FFmpeg-devel] [PATCH] avformat/webvttdec: improve WebVTT parsing
Date: Tue, 27 May 2025 10:40:36 +0000 (UTC)
Message-ID: <1210481741.184664378.1748342436436.JavaMail.zimbra@orca.pet> (raw)
In-Reply-To: <20250527102811.369474-1-marcos@orca.pet>

A note on this change: I found some .vtt files while using yt-dlp that follow
a draft version of the WebVTT standard (probably
https://www.w3.org/2013/07/webvtt.html) that use "Region:" for regions instead
of "REGION", and that was causing the conversion to .srt to fail completely.

An example of those files is available at
https://statics.3cat.cat/multimedia/vtt/2/4/1745967620742.vtt, which belongs
to a state-owned free online streaming service in Spain.

While the diff might seem large, I basically moved the current cue parsing
to a function because it made error handling much easier. The logic is
the exact same.

-----Mensaje original-----
De: Marcos <marcos@orca.pet>
Para: ffmpeg-devel <ffmpeg-devel@ffmpeg.org>
CC: Marcos <marcos@orca.pet>
Fecha: martes, 27 de mayo de 2025 12:29 CEST
Asunto: [PATCH] avformat/webvttdec: improve WebVTT parsing


The parser will now strictly check if WebVTT files start with the correct
"WEBVTT" marker. Before, files were not checked if they truly started
with it.

It will also now ignore all non-cue blocks, instead of only a hardcoded
list. This is closer to the specification that calls for no action
if unknown blocks are encountered.

Signed-off-by: Marcos Del Sol Vives <marcos@orca.pet>
---
 libavformat/webvttdec.c | 178 ++++++++++++++++++++++------------------
 1 file changed, 98 insertions(+), 80 deletions(-)

diff --git a/libavformat/webvttdec.c b/libavformat/webvttdec.c
index 6feda1585e..b454b2c1cf 100644
--- a/libavformat/webvttdec.c
+++ b/libavformat/webvttdec.c
@@ -58,6 +58,79 @@ 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;
+
+    /* 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;
@@ -74,13 +147,27 @@ 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 (;;) {
-        int i;
-        int64_t pos;
-        AVPacket *sub;
-        const char *p, *identifier, *settings;
-        size_t identifier_len, settings_len;
-        int64_t ts_start, ts_end;
+        int64_t pos = avio_tell(s->pb);
 
         res = ff_subtitles_read_chunk(s->pb, &cue);
         if (res < 0)
@@ -89,81 +176,12 @@ 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++;
+        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);
         }
-
-        /* cue timestamps */
-        if ((ts_start = read_ts(p)) == AV_NOPTS_VALUE)
-            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.34.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

  reply	other threads:[~2025-05-27 10:40 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-05-27 10:28 Marcos Del Sol Vives via ffmpeg-devel
2025-05-27 10:40 ` Marcos Del Sol via ffmpeg-devel [this message]
2025-06-06 19:43   ` Tomas Härdin
2025-06-06 20:22     ` Marcos Del Sol Vives
2025-06-09 21:51       ` Tomas Härdin
2025-06-09 23:51         ` Marcos Del Sol
2025-06-10 11:42           ` Marcos Del Sol
2025-06-13 13:03             ` Marcos Del Sol
2025-06-18  7:01               ` Tomas Härdin

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1210481741.184664378.1748342436436.JavaMail.zimbra@orca.pet \
    --to=ffmpeg-devel@ffmpeg.org \
    --cc=marcos@orca.pet \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

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