A package for handling MP4 and ID3 metadata and related operations.
The package can perform the following tasks:
- Read MP4 and ID3 metadata from files that support MP4 and ID3 codecs.
- Add, update and remove MP4 and ID3 metadata.
Initialize the respective handlers.
final handler = MP4Handler(filePath);
print(handler.metadata);final handler = ID3Handler(filePath);
print(handler.metadata);Queue operations, then apply them all at once. Applying rewrites the file and
re-reads it, so handler.metadata shows what is on disk afterwards.
handler.addMetadata({
'title': 'New Title',
'artist': 'Artist',
});
handler.updateMetadata({'genre': 'New Genre'});
handler.removeMetadata(['artist']);
try {
await handler.applyMetadataChanges();
print('Metadata changes applied successfully');
} catch (e) {
// ID3Exception or MP4Exception
print('Failed to apply metadata changes: $e');
}How writing behaves:
- Nothing is written until
applyMetadataChanges(), and the operations are only removed from the queue once the file was written. - If the new tag fits in the space the old one took (its padding, or the
freeatom next to it), only the tag is overwritten in the file itself and the audio is not copied. That is fast for big files, but a disk failure half way through could leave a damaged tag. PassinPlace: falseto always write a new file (<name>.tmp) and rename it over the original, so that a failure leaves the original untouched. A tag that has to grow is always written to a new file, with some padding for the next edit. - Audio is copied byte for byte.
updatesets a key: it replaces every frame or atom for it, andremoveremoves them all.addis the same, except for cover art (cover,picture), where it appends to the pictures that are already there. The other frames and atoms keep their order. - A value that cannot be stored (an unknown key, a number out of range, the wrong type) throws and changes nothing.
- Missing tag structures are created: an MP3 gets an ID3v2 tag, an MP4 gets
udta/meta/ilst. - Copying a file does not block the event loop, but parsing in the constructor
does. In a Flutter app read with
await ID3Handler.readMetadata(path)orawait MP4Handler.readMetadata(path), which parse in another isolate and return theMetadata.
- The tag keeps its version (2.3 or 2.4; a new tag is 2.4). Text is UTF-8 in
2.4, and ISO-8859-1 or UTF-16 in 2.3.
yearis stored inTDRCin 2.4 and inTYERin 2.3; reading aTDRCalso givesyear(its first four digits). - Values: strings; numbers;
[number, total]fortrackanddiscNumber(stored as3/12); a list of strings for several values; image bytes, or a map withdata,mimeType,pictureTypeanddescription, or a list of those, forpicture(all of them are read aspictures); a map withlanguage,descriptionandtextforcommentandlyrics; an integer from 0 to 255 forrating(aPOPMframe); a URL, or a map withdescriptionandurl, foruserDefinedUrl(WXXX:Descriptionfor one description); a map withlanguage,timeStampFormat,contentType,descriptionandlyrics(a list oftimeStampandtextmaps) forsynchronizedLyrics; a map withstartTime,endTime(milliseconds),startOffset,endOffsetandtitle(orsubFrames, a map of text frame ID to text) forchapter_<id>; a map withchildren(a list of IDs),isOrdered,isTopLevelandtitlefortableOfContents_<id>. TXXX:Descriptionsets the user text frame with that description and leaves the others alone. Other frame IDs can be given as the key with the raw frame body as aUint8List.- An ID3v1 tag at the end of the file is kept, and the fields you change are updated in it too. ID3v2 values win over ID3v1 ones when reading.
- A tag that is written to a new file gets
ID3Handler.tagPadding(1024) bytes of padding. Parts of a tag that look like the start of an MPEG frame (FF E0and up, as in JPEG data) are unsynchronized when writing: the whole tag in 2.3, the frame in 2.4. Reading undoes it.
- Values: strings;
trackNumberanddiscNumberas[number, total]; integers forbpm,ratingand the like; booleans forcompilationandgapless; image bytes (JPEG, PNG, GIF or BMP) or a list of them forcover(all of them are read ascovers). - Freeform atoms are read and written with the key
mean.name, for examplecom.apple.iTunes.MOOD. - A change in the size of the metadata is taken out of (or given to) the
freeatom afterilstor aftermoov, so the media stays where it is. If there is no room andmoovcomes beforemdat, the media moves and the offsets are adjusted: the chunk offsets (stco/co64) and, in fragmented files, thetfhdbase data offsets, thesidxfirst offsets and thetframoofoffsets.MP4Handler.freePadding(1024) bytes offreeatom are left aftermoovthen. Withmoovat the end of the file it simply grows or shrinks. durationis in milliseconds, like for ID3.timescaleanddurationTickshold the raw values.
| MP4 | ID3 |
|---|---|
| title | title |
| artist | artist |
| album | album |
| year | year |
| genre | genre |
| composer | composer |
| lyrics | lyrics |
| comment | comment |
| albumArtist | albumArtist |
| trackNumber | track |
| discNumber | discNumber |
| bpm | bpm |
| cover | picture |
| description | |
| copyright | copyright |
| rating | rating |
| duration | duration |
Metadata.get<T> returns null when the value is not a T, instead of
throwing.
MIT, see LICENSE.