Skip to content
@deckyard/mp3

The ID3 formats on disk

File layout

+------------------+  0
| ID3v2 tag        |  "ID3", optional, states its own length
+------------------+  id3v2Size(data)
| MPEG audio       |  frames, back to back
+------------------+  len(data) - 128
| ID3v1 tag        |  "TAG", optional, always 128 bytes
+------------------+  len(data)

Both tags are optional. Junk between the ID3v2 tag and the first frame is common, so the frame scanner searches instead of assuming the audio starts at id3v2Size(data). An ID3v1 tag is recognised only by TAG at len(data) - 128.

ID3v1

128 bytes, all ISO-8859-1, NUL-padded.

OffsetLengthField
03TAG
330title
3330artist
6330album
934year
9730comment
1271genre

ID3v1.1 splits the comment:

OffsetLengthField
9728comment
125100, the marker
1261track number, 1-255

A NUL at 125 and a non-NUL at 126 marks ID3v1.1.

Genre 0-191 indexes the table; 255 means "no genre". See ID3v1 and genres.

ID3v2 header

Ten bytes.

OffsetLengthField
03ID3
31major version: 2, 3 or 4
41revision
51flags
64size, synchsafe, excluding this header

A major version outside 2-4, or a version or revision byte of ff, makes the reader treat the file as untagged.

FlagBitMeans
unsynchronisation0x80the body is unsynchronised
extended header0x40an extended header follows (ID3v2.3 and up)
experimental0x20ignored
footer0x10a ten-byte footer closes the tag (ID3v2.4)

The footer is the header with 3DI in place of ID3. It is not included in the stated size; id3v2Size adds its ten bytes. The result is clamped to the buffer length.

Synchsafe integers

Four bytes of seven bits each, high bit clear, so the field never contains a byte that could start an MPEG sync word.

value = (b0 & 0x7f) << 21 | (b1 & 0x7f) << 14 | (b2 & 0x7f) << 7 | (b3 & 0x7f)

The maximum is 268435455 (SYNCSAFE_MAX). On read, a set high bit is masked off, not rejected, because real files set it.

Extended header

ID3v2.3 states a size that excludes its own four-byte length field; ID3v2.4 states one that includes it. The deck skips the extended header without reading it. On ID3v2.2 the same flag bit means whole-tag compression, which is ignored.

Frame header

ID3v2.2ID3v2.3ID3v2.4
identifier3 bytes4 bytes4 bytes
size3 bytes, big-endian4 bytes, big-endian4 bytes, synchsafe
flagsnone2 bytes2 bytes
total61010

An identifier is upper-case letters and digits only. A NUL where one belongs starts the padding and ends the frame list; any other illegal identifier also ends it.

Plain sizes in ID3v2.4

Several widely used taggers wrote a plain big-endian size into ID3v2.4 frame headers instead of a synchsafe one. When the two readings differ, the reader takes the synchsafe one if it ends on a plausible frame boundary - the end of the tag, the padding, or a legal identifier - otherwise the plain one if that does, otherwise the synchsafe one. The writer always writes synchsafe.

This keeps those files readable, at a price: a crafted tag can split into different frames here than in a strict reader. Do not use this deck to vet a file that a strict parser will read later, or the other way round - a frame one of them sees can be invisible to the other.

ID3v2.3 frame flags

Status byte, then format byte.

BitStatusFormat
0x80tag alter preservationcompression (zlib)
0x40file alter preservationencryption
0x20read onlygrouping

Extra bytes at the front of the body, in order: a four-byte decompressed size when compressed, an encryption method byte when encrypted, a group byte when grouped.

ID3v2.4 frame flags

BitStatusFormat
0x40tag alter preservationgrouping
0x20file alter preservation
0x10read only
0x08compression (zlib)
0x04encryption
0x02unsynchronisation
0x01data length indicator

Extra bytes at the front of the body, in order: a group byte, an encryption method byte, a four-byte synchsafe data length indicator.

ID3v2.4 unsynchronises per frame, and the stated frame size is the size after unsynchronisation. ID3v2.3 unsynchronises the whole tag, and its frame sizes describe the body with unsynchronisation already undone. The reader therefore undoes it before splitting frames on ID3v2.3 and earlier, and per frame body on ID3v2.4 (when either the tag flag or the frame flag is set).

Unsynchronisation

Writing: insert a 00 after every ff followed by 00 or by a byte >= e0, and after a trailing ff.

Reading: replace every ff 00 pair with a single ff.

Text encodings

The first byte of a text frame's body.

ByteEncodingVersions
0ISO-8859-1all
1UTF-16 with a byte-order markall
2UTF-16BE, no markID3v2.4
3UTF-8ID3v2.4

A string ends at a NUL in the frame's own encoding: two NUL bytes at an even offset from the string start for the UTF-16 encodings, one NUL otherwise. The last string of a frame is usually unterminated and runs to the end.

ID3v2.4 allows several values in one text frame, separated by that terminator. Earlier versions allow one, so a trailing NUL there is padding. Empty values are dropped either way.

UTF-16 (1) honours either byte-order mark; without one it is read little-endian. The writer emits ff fe (little-endian). An odd trailing byte or unpaired surrogate decodes to U+FFFD.

URL frames (W...) carry no encoding byte at all and are always ISO-8859-1. The user-defined WXXX is the exception: it has one, like TXXX.

Frame bodies

TXXX - user-defined text

encoding(1)  description...00  value...

COMM and USLT - comment, lyrics

encoding(1)  language(3)  description...00  text...

The language is a three-character ISO-639-2 code, eng by convention.

APIC - attached picture

encoding(1)  mime-type...00  type(1)  description...00  image...

The media type is ISO-8859-1 and ends at the NUL. A type outside printable ASCII is written as image/jpeg, so it can neither fail the write nor end the field early.

PIC - the ID3v2.2 attached picture

encoding(1)  format(3)  type(1)  description...00  image...

format is a three-character image format - JPG, PNG - where every later version stores a media type.

The picture type byte indexes PICTURE_TYPES; 3 is the front cover.

ID3v2.2 to ID3v2.3 frame identifiers

canonicalFrameId maps left to right; writing ID3v2.2 maps right to left and throws for an id not in this table.

2.22.32.22.32.22.3
TT1TIT1TOTTOALCOMCOMM
TT2TIT2TOATOPEULTUSLT
TT3TIT3TOLTOLYPICAPIC
TP1TPE1TORTORYGEOGEOB
TP2TPE2TRCTSRCCNTPCNT
TP3TPE3TKETKEYPOPPOPM
TP4TPE4TLATLANUFIUFID
TALTALBTMTTMEDSLTSYLT
TCMTCOMTFTTFLTMCIMCDI
TXTTEXTTDYTDLYWARWOAR
TCOTCONTLETLENWAFWOAF
TRKTRCKTBPTBPMWASWOAS
TPATPOSTPBTPUBWCMWCOM
TYETYERTENTENCWCPWCOP
TDATDATTCRTCOPWPBWPUB
TIMTIMETSSTSSEWXXWXXX
TRDTRDATXXTXXX

Sources