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

libavformat reads embedded cover art unconditionally: id3v2 APIC frames are 
parsed with the tag, FLAC PICTURE blocks with the header chain, whether the 
caller wants them or not.
On slow storage that gets expensive for audio-only callers: a Morrowind 
soundtrack mod ships 21 FLAC tracks with ~4.3MB of art each, and through 
emulated slow storage (open 30ms, read 8ms) a single avformat_open_input takes 
4365ms against 25ms with the art hidden from the demuxer.

This adds an `AVFMT_FLAG_SKIP_ATTACHED_PICS` fflag: the id3v2 parser skips 
APIC/PIC payloads unread, the flac demuxer seeks past PICTURE blocks, and no 
attached_pic stream is created.
ff_id3v2_read_dict gains the AVFormatContext parameter so the generic open path 
can honor the flag; the hls and mpegts timed ID3 paths pass NULL and keep their 
behavior. Default unchanged.

Validated on mp3 and FLAC with embedded art: the flag drops the attached_pic 
stream, text metadata still reads (title checked on a real 4.3MB-art FLAC), 
files without art unaffected.
On non-seekable input the FLAC path degrades to read-and-discard, so the saving 
applies to seekable input. hls/mpegts ignore the flag on purpose, their APIC 
feeds consumers that may rely on it; extendable later if wanted.

Same caller class as #24084, a game engine opening audio at interactive latency 
(the OpenMW MR at gitlab.com/OpenMW/openmw/-/merge_requests/5492 carries the 
workaround this flag would retire).


>From 5926af2267a6d457a4a1d78b28d99f04c59f29b0 Mon Sep 17 00:00:00 2001
From: Thomas Portal <[email protected]>
Date: Tue, 11 Aug 2026 20:13:48 +0200
Subject: [PATCH] avformat: add AVFMT_FLAG_SKIP_ATTACHED_PICS

libavformat reads embedded cover art unconditionally: id3v2 APIC
frames are parsed with the tag and FLAC PICTURE blocks are read with
the header chain, whether or not the caller wants an attached_pic
stream. For audio-only callers on slow storage that is a large cost:
a soundtrack mod for OpenMW ships 21 FLAC tracks with ~4.3MB of art
each, and through emulated slow storage (open 30ms, read 8ms) a
single avformat_open_input takes 4365ms against 25ms with the art
bytes hidden from the demuxer.

Add an AVFMT_FLAG_SKIP_ATTACHED_PICS fflag. When set, the id3v2
parser skips APIC/PIC frame payloads unread and the flac demuxer
seeks past PICTURE metadata blocks; no attached_pic stream is
created. ff_id3v2_read_dict gains the AVFormatContext parameter so
the generic open path can honor the flag; the hls and mpegts timed
ID3 paths pass NULL and keep their current behavior. Default
behavior is unchanged.

Signed-off-by: Thomas Portal <[email protected]>
---
 doc/APIchanges              | 3 +++
 doc/formats.texi            | 5 +++++
 libavformat/aacdec.c        | 2 +-
 libavformat/avformat.h      | 1 +
 libavformat/demux.c         | 2 +-
 libavformat/flacdec.c       | 9 +++++++--
 libavformat/hls.c           | 2 +-
 libavformat/id3v2.c         | 9 +++++++--
 libavformat/id3v2.h         | 2 +-
 libavformat/mpegts.c        | 2 +-
 libavformat/options_table.h | 1 +
 libavformat/version.h       | 4 ++--
 12 files changed, 31 insertions(+), 11 deletions(-)

diff --git a/doc/APIchanges b/doc/APIchanges
index 7907af9290..51bc3cba49 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2026-06-23.
 
 API changes, most recent first:
 
+2026-08-11 - xxxxxxxxxx - lavf 63.6.100 - avformat.h
+  Add AVFMT_FLAG_SKIP_ATTACHED_PICS.
+
 2026-07-04 - xxxxxxxxxx - lavc 63.7.100 - codec_id.h
   Add AV_CODEC_ID_PCM_DVDA.
 
diff --git a/doc/formats.texi b/doc/formats.texi
index 0722e3f512..c7b0f7de13 100644
--- a/doc/formats.texi
+++ b/doc/formats.texi
@@ -56,6 +56,11 @@ Reduce the latency introduced by buffering during initial 
input streams analysis
 Do not fill in missing values in packet fields that can be exactly calculated.
 @item noparse
 Disable AVParsers, this needs @code{+nofillin} too.
+@item skip_attached_pics
+Do not read attached pictures (album art): id3v2 APIC frames and FLAC
+PICTURE blocks are skipped unread and no attached_pic streams are created.
+Saves opening cost on files with large embedded art when the pictures are
+not wanted.
 @item sortdts
 Try to interleave output packets by DTS. At present, available only for AVIs 
with an index.
 @end table
diff --git a/libavformat/aacdec.c b/libavformat/aacdec.c
index fef3c69f0b..a54574f46c 100644
--- a/libavformat/aacdec.c
+++ b/libavformat/aacdec.c
@@ -147,7 +147,7 @@ static int handle_id3(AVFormatContext *s, AVPacket *pkt)
         return ret;
 
     ffio_init_read_context(&pb, pkt->data, pkt->size);
-    ff_id3v2_read_dict(&pb.pub, &metadata, ID3v2_DEFAULT_MAGIC, 
&id3v2_extra_meta);
+    ff_id3v2_read_dict(&pb.pub, &metadata, s, ID3v2_DEFAULT_MAGIC, 
&id3v2_extra_meta);
     if ((ret = ff_id3v2_parse_priv_dict(&metadata, id3v2_extra_meta)) < 0)
         goto error;
 
diff --git a/libavformat/avformat.h b/libavformat/avformat.h
index d4f10122e6..4453a5bae4 100644
--- a/libavformat/avformat.h
+++ b/libavformat/avformat.h
@@ -1504,6 +1504,7 @@ typedef struct AVFormatContext {
 #define AVFMT_FLAG_SORT_DTS    0x10000 ///< try to interleave outputted 
packets by dts (using this flag can slow demuxing down)
 #define AVFMT_FLAG_FAST_SEEK   0x80000 ///< Enable fast, but inaccurate seeks 
for some formats
 #define AVFMT_FLAG_AUTO_BSF   0x200000 ///< Add bitstream filters as requested 
by the muxer
+#define AVFMT_FLAG_SKIP_ATTACHED_PICS 0x400000 ///< Do not read attached 
pictures (album art); no attached_pic streams are created.
 
     /**
      * Maximum number of bytes read from input in order to determine stream
diff --git a/libavformat/demux.c b/libavformat/demux.c
index 2375b277e4..0c4a031395 100644
--- a/libavformat/demux.c
+++ b/libavformat/demux.c
@@ -317,7 +317,7 @@ int avformat_open_input(AVFormatContext **ps, const char 
*filename,
 
     /* e.g. AVFMT_NOFILE formats will not have an AVIOContext */
     if (s->pb && is_id3v2_format(s->iformat))
-        ff_id3v2_read_dict(s->pb, &si->id3v2_meta, ID3v2_DEFAULT_MAGIC, 
&id3v2_extra_meta);
+        ff_id3v2_read_dict(s->pb, &si->id3v2_meta, s, ID3v2_DEFAULT_MAGIC, 
&id3v2_extra_meta);
 
     if (ffifmt(s->iformat)->read_header)
         if ((ret = ffifmt(s->iformat)->read_header(s)) < 0) {
diff --git a/libavformat/flacdec.c b/libavformat/flacdec.c
index e80b49307d..239d9548d0 100644
--- a/libavformat/flacdec.c
+++ b/libavformat/flacdec.c
@@ -91,10 +91,13 @@ static int flac_read_header(AVFormatContext *s)
         flac_parse_block_header(header, &metadata_last, &metadata_type,
                                    &metadata_size);
         switch (metadata_type) {
+        case FLAC_METADATA_TYPE_PICTURE:
+            if (s->flags & AVFMT_FLAG_SKIP_ATTACHED_PICS)
+                goto skip_block;
+            /* fall through */
         /* allocate and read metadata block for supported types */
         case FLAC_METADATA_TYPE_STREAMINFO:
         case FLAC_METADATA_TYPE_CUESHEET:
-        case FLAC_METADATA_TYPE_PICTURE:
         case FLAC_METADATA_TYPE_VORBIS_COMMENT:
         case FLAC_METADATA_TYPE_SEEKTABLE:
             buffer = av_mallocz(metadata_size + AV_INPUT_BUFFER_PADDING_SIZE);
@@ -108,6 +111,7 @@ static int flac_read_header(AVFormatContext *s)
             break;
         /* skip metadata block for unsupported types */
         default:
+        skip_block:
             ret = avio_skip(s->pb, metadata_size);
             if (ret < 0)
                 return ret;
@@ -165,7 +169,8 @@ static int flac_read_header(AVFormatContext *s)
                 avpriv_new_chapter(s, track, st->time_base, start, 
AV_NOPTS_VALUE, isrc);
             }
             av_freep(&buffer);
-        } else if (metadata_type == FLAC_METADATA_TYPE_PICTURE) {
+        } else if (metadata_type == FLAC_METADATA_TYPE_PICTURE
+                   && !(s->flags & AVFMT_FLAG_SKIP_ATTACHED_PICS)) {
             ret = ff_flac_parse_picture(s, &buffer, metadata_size, 1);
             av_freep(&buffer);
             if (ret < 0) {
diff --git a/libavformat/hls.c b/libavformat/hls.c
index eca5788279..8f853e69f0 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -1241,7 +1241,7 @@ static void parse_id3(AVFormatContext *s, AVIOContext *pb,
     static const char id3_priv_owner_audio_setup[] = 
"com.apple.streaming.audioDescription";
     ID3v2ExtraMeta *meta;
 
-    ff_id3v2_read_dict(pb, metadata, ID3v2_DEFAULT_MAGIC, extra_meta);
+    ff_id3v2_read_dict(pb, metadata, NULL, ID3v2_DEFAULT_MAGIC, extra_meta);
     for (meta = *extra_meta; meta; meta = meta->next) {
         if (!strcmp(meta->tag, "PRIV")) {
             ID3v2ExtraMetaPRIV *priv = &meta->data.priv;
diff --git a/libavformat/id3v2.c b/libavformat/id3v2.c
index ad670052ff..4202db5a7f 100644
--- a/libavformat/id3v2.c
+++ b/libavformat/id3v2.c
@@ -991,6 +991,10 @@ static void id3v2_parse(AVIOContext *pb, AVDictionary 
**metadata,
 
             av_log(s, AV_LOG_WARNING, "Skipping %s ID3v2 frame %s.\n", type, 
tag);
             avio_skip(pb, tlen);
+        /* attached pictures can be megabytes; drop them unread when asked */
+        } else if (s && (s->flags & AVFMT_FLAG_SKIP_ATTACHED_PICS) &&
+                   !strcmp(tag, isv34 ? "APIC" : "PIC")) {
+            avio_skip(pb, tlen);
         /* check for text tag or supported special meta tag */
         } else if (tag[0] == 'T' ||
                    !memcmp(tag, "USLT", 4) ||
@@ -1171,9 +1175,10 @@ static void id3v2_read_internal(AVIOContext *pb, 
AVDictionary **metadata,
 }
 
 void ff_id3v2_read_dict(AVIOContext *pb, AVDictionary **metadata,
-                        const char *magic, ID3v2ExtraMeta **extra_meta)
+                        AVFormatContext *s, const char *magic,
+                        ID3v2ExtraMeta **extra_meta)
 {
-    id3v2_read_internal(pb, metadata, NULL, magic, extra_meta, 0);
+    id3v2_read_internal(pb, metadata, s, magic, extra_meta, 0);
 }
 
 void ff_id3v2_read(AVFormatContext *s, const char *magic,
diff --git a/libavformat/id3v2.h b/libavformat/id3v2.h
index 9afa5a2ddc..fe5b772af4 100644
--- a/libavformat/id3v2.h
+++ b/libavformat/id3v2.h
@@ -114,7 +114,7 @@ int ff_id3v2_tag_len(const uint8_t *buf);
  * @param[out] extra_meta If not NULL, extra metadata is parsed into a list of
  * ID3v2ExtraMeta structs and *extra_meta points to the head of the list
  */
-void ff_id3v2_read_dict(AVIOContext *pb, AVDictionary **metadata, const char 
*magic, ID3v2ExtraMeta **extra_meta);
+void ff_id3v2_read_dict(AVIOContext *pb, AVDictionary **metadata, 
AVFormatContext *s, const char *magic, ID3v2ExtraMeta **extra_meta);
 
 /**
  * Read an ID3v2 tag, including supported extra metadata.
diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c
index 076020509d..0fa69c1b01 100644
--- a/libavformat/mpegts.c
+++ b/libavformat/mpegts.c
@@ -1048,7 +1048,7 @@ static int timed_id3_update_metadata(AVStream *s, 
AVPacket *pkt)
     int ret = 0;
 
     ffio_init_read_context(&id3_buf, pkt->data, pkt->size);
-    ff_id3v2_read_dict(&id3_buf.pub, &metadata, ID3v2_DEFAULT_MAGIC, 
&extra_meta);
+    ff_id3v2_read_dict(&id3_buf.pub, &metadata, NULL, ID3v2_DEFAULT_MAGIC, 
&extra_meta);
     ret = ff_id3v2_parse_priv_dict(&metadata, extra_meta);
     ff_id3v2_free_extra_meta(&extra_meta);
 
diff --git a/libavformat/options_table.h b/libavformat/options_table.h
index 915c553f54..6db99555ed 100644
--- a/libavformat/options_table.h
+++ b/libavformat/options_table.h
@@ -50,6 +50,7 @@ static const AVOption avformat_options[] = {
 {"sortdts", "try to interleave outputted packets by dts", 0, 
AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_SORT_DTS }, INT_MIN, INT_MAX, D, .unit = 
"fflags"},
 {"fastseek", "fast but inaccurate seeks", 0, AV_OPT_TYPE_CONST, {.i64 = 
AVFMT_FLAG_FAST_SEEK }, INT_MIN, INT_MAX, D, .unit = "fflags"},
 {"nobuffer", "reduce the latency introduced by optional buffering", 0, 
AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_NOBUFFER }, 0, INT_MAX, D, .unit = 
"fflags"},
+{"skip_attached_pics", "do not read attached pictures (album art)", 0, 
AV_OPT_TYPE_CONST, {.i64 = AVFMT_FLAG_SKIP_ATTACHED_PICS }, INT_MIN, INT_MAX, 
D, .unit = "fflags"},
 {"bitexact", "do not write random/volatile data", 0, AV_OPT_TYPE_CONST, { .i64 
= AVFMT_FLAG_BITEXACT }, 0, 0, E, .unit = "fflags" },
 {"autobsf", "add needed bsfs automatically", 0, AV_OPT_TYPE_CONST, { .i64 = 
AVFMT_FLAG_AUTO_BSF }, 0, 0, E, .unit = "fflags" },
 {"seek2any", "allow seeking to non-keyframes on demuxer level when supported", 
OFFSET(seek2any), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, D},
diff --git a/libavformat/version.h b/libavformat/version.h
index 384cbd49cc..4bde82abb4 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -31,8 +31,8 @@
 
 #include "version_major.h"
 
-#define LIBAVFORMAT_VERSION_MINOR   5
-#define LIBAVFORMAT_VERSION_MICRO 101
+#define LIBAVFORMAT_VERSION_MINOR   6
+#define LIBAVFORMAT_VERSION_MICRO 100
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
                                                LIBAVFORMAT_VERSION_MINOR, \
-- 
2.52.0

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

Reply via email to