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]
