PR #25662 opened by superuser404
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25662
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25662.patch

Attached files are read in full in `matroska_read_header()`, whether the 
Attachments element precedes the first Cluster (where mkvmerge and lavf write 
it) or follows the Clusters and is reached through the SeekHead. In practice 
they are the fonts of ASS subtitles, and releases carrying tens of megabytes of 
them are common. A caller that does not use them still waits for the whole 
transfer before the first packet; over a network input that dominates the open 
(the case that prompted this: 23 MB of fonts, about 11 s over a WAN connection).

This adds a `skip_attachments` demuxer option (default 0). When set:

- an Attachments element of known length is skipped like an element without a 
syntax,
- the SeekHead entry pointing at it is not followed,
- no attachment streams are exported, and tags targeting an attachment are 
dropped without the "non-existent attachment" warning, since their target was 
skipped on purpose.

Tracks are created before attachment streams, so stream indices and packets are 
unchanged. An Attachments element of unknown length is still parsed as before.

**Measurements** (ffprobe, `-v debug` AVIOContext statistics), 26 MB files with 
24 MB of fonts:

| layout | without | with `-skip_attachments 1` |
|---|---|---|
| attachments before the first Cluster (mkvmerge) | 25.15 MB read | 0.16 MB |
| attachments after the Clusters, via SeekHead (mkvpropedit) | 25.18 MB, 4 
seeks | 0.16 MB, 2 seeks |

Packet lists (`-show_entries packet=pts_time,stream_index`) and a seek to 6 s 
are identical with and without the option in both layouts.

**Tests:** new `fate-matroska-skip-attachments` on the existing 
`mkv/hdr10tags-both.mkv`, whose attachment follows the first Cluster, so the 
SeekHead path is covered; no new sample needed. `make fate-matroska` (55 tests) 
and `fate-source` pass. `doc/demuxers.texi` gains a matroska section for the 
option (I could not run makeinfo locally; the markup follows the neighbouring 
sections). lavf micro bumped.

A possible alternative would be reading attachments lazily on demand, but 
attachment data is exported as codecpar extradata at header time, so that would 
change the API contract; an opt-in option keeps the current behaviour untouched 
for everyone who does not set it.



>From 8adca69ad395bc26e981556fb6c6a28548d5860d Mon Sep 17 00:00:00 2001
From: Vincent Herbst <[email protected]>
Date: Sun, 1 Nov 2026 20:13:37 +0100
Subject: [PATCH] avformat/matroskadec: add skip_attachments option

Attached files are read in full in matroska_read_header(), whether the
Attachments element precedes the first Cluster (where mkvmerge and lavf
write it) or follows the Clusters and is reached through the SeekHead.
In practice they are the fonts of ASS subtitles, and releases that
carry tens of megabytes of them are common. A caller that does not use
them still waits for that whole transfer before the first packet, which
over a network input dominates the time to open (a reported case: 23 MB
of fonts, about 11 s).

The new skip_attachments option (default 0) skips the element instead:
inline it is passed over like an element without a syntax, and the
SeekHead entry pointing at it is not followed. No attachment streams
are exported, and tags targeting an attachment are dropped without the
"non-existent attachment" warning, since their target was skipped on
purpose. Tracks are created before attachment streams, so stream
indices and packets are unchanged.

Measured with ffprobe on 26 MB files carrying 24 MB of fonts, in both
layouts: about 25.2 MB read to open without the option, 0.13 to
0.16 MB with it, identical packets and seeks either way.

The FATE test uses mkv/hdr10tags-both.mkv, whose attachment follows the
first Cluster, so it covers the SeekHead path.

Signed-off-by: Vincent Herbst <[email protected]>
---
 doc/demuxers.texi                        | 17 +++++++++++++++++
 libavformat/matroskadec.c                | 24 +++++++++++++++++++++++-
 libavformat/version.h                    |  2 +-
 tests/fate/matroska.mak                  |  5 +++++
 tests/ref/fate/matroska-skip-attachments |  1 +
 5 files changed, 47 insertions(+), 2 deletions(-)
 create mode 100644 tests/ref/fate/matroska-skip-attachments

diff --git a/doc/demuxers.texi b/doc/demuxers.texi
index bc8116d9d23..c7dc7a24444 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -889,6 +889,23 @@ Set the sample rate for libopenmpt to output.
 Range is from 1000 to INT_MAX. The value default is 48000.
 @end table
 
+@section matroska
+
+Matroska and WebM demuxer.
+
+It accepts the following options:
+
+@table @option
+@item skip_attachments
+Skip the attached files (Attachments element) instead of reading them while
+opening the file, both where they precede the first Cluster and where only the
+SeekHead refers to them. No attachment streams are exported, and tags that
+target an attachment are dropped. Attachments are commonly the fonts of ASS
+subtitles and can be tens of megabytes, all of which are otherwise read before
+the first packet; this is useful for callers that do not use them, especially
+over slow inputs. Default is 0.
+@end table
+
 @anchor{mccdec}
 @section mcc
 
diff --git a/libavformat/matroskadec.c b/libavformat/matroskadec.c
index d36cc5c3452..e78476142b4 100644
--- a/libavformat/matroskadec.c
+++ b/libavformat/matroskadec.c
@@ -439,6 +439,9 @@ typedef struct MatroskaDemuxContext {
 
     /* Bandwidth value for WebM DASH Manifest */
     int bandwidth;
+
+    /* Skip the Attachments element instead of reading it */
+    int skip_attachments;
 } MatroskaDemuxContext;
 
 #define CHILD_OF(parent) { .def = { .n = parent } }
@@ -1476,6 +1479,9 @@ static int ebml_parse(MatroskaDemuxContext *matroska,
 
         if (!data && length != EBML_UNKNOWN_LENGTH)
             goto skip;
+        if (id == MATROSKA_ID_ATTACHMENTS && matroska->skip_attachments &&
+            length != EBML_UNKNOWN_LENGTH)
+            goto skip;
     }
 
     switch (syntax->type) {
@@ -1872,7 +1878,7 @@ static void matroska_convert_tags(AVFormatContext *s)
                     found = 1;
                 }
             }
-            if (!found) {
+            if (!found && !matroska->skip_attachments) {
                 av_log(s, AV_LOG_WARNING,
                        "The tags at index %d refer to a "
                        "non-existent attachment %"PRId64".\n",
@@ -1985,6 +1991,9 @@ static void 
matroska_execute_seekhead(MatroskaDemuxContext *matroska)
         if (id == MATROSKA_ID_CUES)
             continue;
 
+        if (id == MATROSKA_ID_ATTACHMENTS && matroska->skip_attachments)
+            continue;
+
         if (matroska_parse_seekhead_entry(matroska, pos) < 0) {
             // mark index as broken
             matroska->cues_parsing_deferred = -1;
@@ -5052,11 +5061,24 @@ const FFInputFormat ff_webm_dash_manifest_demuxer = {
 };
 #endif
 
+static const AVOption matroska_options[] = {
+    { "skip_attachments", "skip the attached files instead of reading them 
while opening", offsetof(MatroskaDemuxContext, skip_attachments), 
AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
+    { NULL },
+};
+
+static const AVClass matroska_class = {
+    .class_name = "matroska,webm demuxer",
+    .item_name  = av_default_item_name,
+    .option     = matroska_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
 const FFInputFormat ff_matroska_demuxer = {
     .p.name         = "matroska,webm",
     .p.long_name    = NULL_IF_CONFIG_SMALL("Matroska / WebM"),
     .p.extensions   = "mkv,mk3d,mka,mks,webm",
     .p.mime_type    = 
"audio/webm,audio/x-matroska,video/webm,video/x-matroska",
+    .p.priv_class   = &matroska_class,
     .priv_data_size = sizeof(MatroskaDemuxContext),
     .flags_internal = FF_INFMT_FLAG_INIT_CLEANUP,
     .read_probe     = matroska_probe,
diff --git a/libavformat/version.h b/libavformat/version.h
index 6a80f3ac4e6..be8ce011609 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -32,7 +32,7 @@
 #include "version_major.h"
 
 #define LIBAVFORMAT_VERSION_MINOR  18
-#define LIBAVFORMAT_VERSION_MICRO 100
+#define LIBAVFORMAT_VERSION_MICRO 101
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
                                                LIBAVFORMAT_VERSION_MINOR, \
diff --git a/tests/fate/matroska.mak b/tests/fate/matroska.mak
index f165ce19476..250c34ff4b9 100644
--- a/tests/fate/matroska.mak
+++ b/tests/fate/matroska.mak
@@ -249,6 +249,11 @@ fate-matroska-dvbsub-remux: CMD = transcode mpegts 
$(TARGET_SAMPLES)/sub/dvbsubt
 FATE_MATROSKA_FFPROBE-$(call DEMDEC, MATROSKA, H264) += 
fate-matroska-spherical-mono
 fate-matroska-spherical-mono: CMD = run ffprobe$(PROGSSUF)$(EXESUF) 
-show_entries stream_side_data_list -select_streams v -v 0 
$(TARGET_SAMPLES)/mkv/spherical.mkv
 
+# The attachment of this file follows the first Cluster and is only reachable
+# through the SeekHead; skip_attachments must not export it.
+FATE_MATROSKA_FFPROBE-$(call ALLYES, MATROSKA_DEMUXER) += 
fate-matroska-skip-attachments
+fate-matroska-skip-attachments: CMD = run ffprobe$(PROGSSUF)$(EXESUF) 
-show_entries stream=index,codec_type -of csv=p=0 -v 0 -skip_attachments 1 
$(TARGET_SAMPLES)/mkv/hdr10tags-both.mkv
+
 # This test tests the handling of AVStereo3D information, in particular
 # the ability to set it via metadata in the muxer (the file itself is
 # actually an ordinary file with a single view). It also tests
diff --git a/tests/ref/fate/matroska-skip-attachments 
b/tests/ref/fate/matroska-skip-attachments
new file mode 100644
index 00000000000..47bd876f767
--- /dev/null
+++ b/tests/ref/fate/matroska-skip-attachments
@@ -0,0 +1 @@
+0,video
-- 
2.54.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to