Skip to content

TIKA-4801: Extract cover art from MP3, MP4, Vorbis and FLAC audio - #2983

Open
dschmidt wants to merge 5 commits into
apache:mainfrom
dschmidt:audio-cover-art
Open

TIKA-4801: Extract cover art from MP3, MP4, Vorbis and FLAC audio#2983
dschmidt wants to merge 5 commits into
apache:mainfrom
dschmidt:audio-cover-art

Conversation

@dschmidt

@dschmidt dschmidt commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Fixes TIKA-4801: ID3v2 APIC frames (v2.2 PIC included), the MP4/M4A covr atom (previously explicitly skipped), Vorbis/Opus metadata_block_picture comments and native FLAC PICTURE blocks.

Each picture is emitted through the standard EmbeddedDocumentExtractor mechanism as an inline embedded document, the same way PDF and Office parsers emit embedded images: /rmeta returns one metadata entry per cover with the declared content type (or auto-detection where MP4 only has a type code), the declared description as dc:title and the picture type as dc:description; /unpack delivers the bytes. Deliberately no new metadata properties on the audio document and no mapping of the width/height fields that Vorbis picture blocks declare: ID3 and MP4 declare no dimensions at all, and the embedded entry carries the real ones once image parsers are on the classpath.

The raw metadata_block_picture comment is no longer passed through as a giant base64 vorbis: value, it becomes the embedded document instead.

Native FLAC PICTURE blocks are read by a small metadata block walk in FlacParser itself: vorbis-java 0.8 offers no public accessor for the blocks (their master already has FlacFile.getOtherMetadata(), but nothing has been released since 0.8), so the parser walks the native metadata region through a second stream and reuses the shared picture handler. A TODO in the code links Gagravarr/VorbisJava#46 so the walk can be dropped once a release ships. FLAC-in-Ogg needs no extra path, pictures arrive there as metadata_block_picture.

One commit per format, each with its own fixture and end-to-end test; the fixtures are minimally modified copies of existing test files carrying a 1x1 PNG cover (for the Ogg fixture the comment packet was rewritten with recomputed page CRC). All four tests fail without their parser change.

dschmidt added 4 commits July 30, 2026 15:28
Pictures embedded in ID3v2 tags (APIC in v2.3/v2.4, PIC in v2.2) are
handed to the standard EmbeddedDocumentExtractor as inline embedded
documents. The declared mime type becomes the embedded Content-Type,
the picture description becomes dc:title and the APIC picture type is
mapped to dc:description via the spec's label list. No new metadata
properties are recorded on the audio document itself: consumers that
only want to know whether cover art exists can use the embedded
document count.

Malformed picture frames (missing encoding, unterminated mime type or
description, empty picture data) are skipped silently, matching the
existing handling of malformed COMM frames. Linked v2.2 pictures
(image format "-->") are skipped as they carry a URL, not image data.

The fixture is a copy of testMP3id3v24.mp3 with a self-crafted APIC
frame (minimal 1x1 PNG) inserted before the existing frames and the
synchsafe tag size rewritten accordingly.
The covr ilst entry, previously skipped explicitly, is handed to the
standard EmbeddedDocumentExtractor as an inline embedded document, one
per data atom (the atom may repeat for multiple images). The data
atom's well-known value type declares the image format (13 = jpeg,
14 = png, 27 = bmp); any other type is left for auto-detection. As in
the ID3v2 commit, no new metadata properties are recorded on the audio
document itself.

The ParseContext needed for the embedded document extractor is plumbed
from MP4Parser through TikaMp4BoxHandler into TikaUserDataBox.

The fixture is a copy of testMP4.m4a with a self-crafted covr entry
(minimal 1x1 PNG, type 14) appended to the ilst box and the ancestor
box sizes patched.
METADATA_BLOCK_PICTURE comments, base64 encoded FLAC picture blocks as
used by Vorbis and Opus (and shared by all Ogg family parsers through
OggAudioParser.extractComments), are handed to the standard
EmbeddedDocumentExtractor as inline embedded documents. The declared
mime type becomes the embedded Content-Type, the description becomes
dc:title and the picture type is mapped to dc:description through the
ID3v2 APIC label list, which the FLAC picture spec reuses. The raw
comment is excluded from the generic vorbis:* passthrough: a giant
base64 metadata value helps nobody once the picture is an embedded
document. Malformed or truncated blocks and linked pictures (mime
"-->") are skipped silently.

Native FLAC PICTURE blocks remain out of scope: vorbis-java 0.8 keeps
them in FlacFile.otherMetadata without a public accessor.

The fixture is a copy of testVORBIS.ogg with a self-crafted
metadata_block_picture comment (minimal 1x1 PNG) inserted into the
comment header packet, with the Ogg page lacing values and CRC
rewritten.
vorbis-java 0.8 parses the native FLAC metadata blocks but offers no
accessor for them (FlacFile.getOtherMetadata() exists on their master,
unreleased since 0.8 in 2016), so FlacParser walks the metadata region
itself through a second stream over the spooled file: fLaC magic, then
per block 1 header byte (last-block flag plus type) and a 24 bit BE
length, stopping at the last-block flag, at end of stream or at a
block that declares more data than is left. The PICTURE block payload
(type 6) is byte-identical to the metadata_block_picture comment, so
the parsing and inline embedded-document emission are shared with
OggAudioParser through the extracted extractPictureBlock helper,
keeping the semantics of the previous commit unchanged.

Scope is native FLAC only: FLAC-in-Ogg carries its pictures in
metadata_block_picture comments, which the previous commit already
handles, and its PICTURE packets stay out of scope.

The fixture is a copy of testFLAC.flac from the standard integration
tests with a self-crafted PICTURE block (minimal 1x1 PNG) appended
after the previous last metadata block, whose last-block flag is
cleared.
@dschmidt dschmidt changed the title TIKA-4801: Extract cover art from MP3, MP4 and Vorbis audio TIKA-4801: Extract cover art from MP3, MP4, Vorbis and FLAC audio Jul 30, 2026
@dschmidt
dschmidt marked this pull request as ready for review July 30, 2026 13:57
An ID3v2.4 tag with several APIC frames only yielded the first
picture. ID3v2.4 encodes frame sizes as synchsafe integers, but
RawTag read them as plain integers, so any frame above 127 bytes,
which every real cover picture is, made the frame walk overshoot
into the middle of the following frames: with two covers the walk
consumed the second APIC as trailing bytes of the first and stopped
in the padding. The pictures were also emitted with that trailing
garbage attached.

RawTag now reads v2.4 frame sizes as synchsafe. Because widespread
taggers (e.g. older iTunes) wrote plain integers into v2.4 tags, the
two readings are disambiguated when they disagree: a size byte with
the high bit set forces plain, otherwise the reading wins that lands
the walk on a plausible next frame (frame id of capitals and digits,
padding, or the tag end). Sizes below 128 bytes, like all frames in
the existing v2.4 fixtures, are identical in both encodings.

The ID3v2.3 (plain sizes) path, the MP4 covr repeats, the FLAC
PICTURE walk and the Vorbis metadata_block_picture loop were checked
against the same loss pattern and were already correct; multi-cover
fixtures and tests for all of them are added as regression coverage.
The MP3 fixtures were generated with ffmpeg -id3v2_version 4 / 3
(front 64x40 png, back 30x30 png, both above 127 bytes); the MP4,
FLAC and Ogg fixtures extend the single-cover fixtures with a second
image (MP4: one covr record with a png and a jpeg data atom).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant