@akabeko/music-metadata-editor
v1.0.0
Published
This is a library for reading and writing metadata of music files.
Maintainers
Readme
@akabeko/music-metadata-editor
English / Japanese
A Node.js + TypeScript library for reading and writing audio file metadata. Designed as a function-only API (no classes) with first-class support for ESM and Node.js 24+.
Supported containers / tag formats:
| Container | Read | Write | Notes |
| --- | --- | --- | --- |
| MP3 | ✓ | ✓ | ID3v2.3 / 2.4 + APE Tag + ID3v1 |
| FLAC | ✓ | ✓ | Vorbis Comment + PICTURE block |
| MP4 / M4A | ✓ | ✓ | iTunes-style atoms (moov/udta/meta/ilst) |
| OGG | ✓ | ✓ | Vorbis / Opus comment headers |
| APE | ✓ | ✓ | Monkey's Audio + APE Tag v1/v2 |
| WAV (RIFF) | ✓ | ✓ | LIST INFO + BEXT + ID3 chunk |
| AIFF | ✓ | ✓ | Native annotation chunks + ID3 chunk |
| WMA / ASF | ✓ | ✓ | Content Description + Extended Content |
Install
pnpm add @akabeko/music-metadata-editor
# or: npm install @akabeko/music-metadata-editorRequires Node.js 24 or newer.
Quick start
Load a track
loadTrack accepts both a file path (string) and pre-loaded bytes (Uint8Array). The returned Track is a Plain Object.
import { loadTrack } from "@akabeko/music-metadata-editor";
const track = await loadTrack("./song.mp3");
console.log(track.audioFormat); // "mp3"
console.log(track.tag.title); // "Hello"
console.log(track.tag.artist); // "akabeko"
console.log(track.durationMs); // 215000 (or `undefined` when not derivable)
console.log(track.pictures.length);durationMs is a read-only audio-derived field. The reader computes it from sample-count / sample-rate / bitrate fields, and saveTrack never writes it back to the file (the writer recomputes it on the next read).
It is undefined when the source does not carry the values needed (e.g. a stripped-down fixture with no audio frames).
MP3 caveat: Only CBR streams are supported. VBR-encoded MP3 (Xing / Info / VBRI headers) is not parsed; on VBR files the returned duration is a CBR-based estimate, so it may diverge from the true playback time.
Save a modified track
import { loadTrack, saveTrack } from "@akabeko/music-metadata-editor";
const track = await loadTrack("./song.mp3");
const edited = {
...track,
tag: { ...track.tag, title: "New Title", artist: "New Artist" },
};
// Overwrite the source file in place
await saveTrack(edited, { source: "./song.mp3" });
// Or write to a different path
await saveTrack(edited, { source: "./song.mp3", outputPath: "./out.mp3" });
// Or rebuild bytes without writing to a file
const bytes = await saveTrack(edited, { source: await readFile("./song.mp3") });Edit cover art
import { loadTrack, saveTrack, PictureKind } from "@akabeko/music-metadata-editor";
import { readFile } from "node:fs/promises";
const track = await loadTrack("./song.mp3");
const cover = await readFile("./cover.jpg");
const edited = {
...track,
pictures: [
{ mimeType: "image/jpeg", kind: PictureKind.CoverFront, data: cover },
],
};
await saveTrack(edited, { source: "./song.mp3" });Edit lyrics
const edited = {
...track,
lyrics: {
language: "eng",
description: "Lyrics",
unsynchronized: "Hello, world\nLine two\n",
},
};
await saveTrack(edited, { source: "./song.mp3" });For synchronized lyrics, populate lyrics.synchronized with { timeMs, text }[] (sorted by timeMs).
Two-layer API
| Layer | Functions | When to use |
| --- | --- | --- |
| High-level | loadTrack, saveTrack | Most workflows. Returns a stable Track Plain Object with additionalFields / warnings defaults. |
| Low-level | readMetadata, writeMetadata | When you need the raw MetadataReadResult or want to pass WriteOptions directly. |
Both layers honour the same ReadOptions (e.g. tagPriority for MP3) and format override for files without recognizable extensions or signatures.
import { readMetadata } from "@akabeko/music-metadata-editor";
const result = await readMetadata("./song.mp3", { tagPriority: ["ape", "id3v2", "id3v1"] });Errors and warnings
All thrown errors are MmeError, a tagged Error with a defined code.
import { loadTrack, isMmeError } from "@akabeko/music-metadata-editor";
try {
await loadTrack("./mystery.bin");
} catch (error) {
if (isMmeError(error) && error.code === "unsupported-format") {
// ...
}
}| Code | Meaning |
| --- | --- |
| unsupported-format | Format could not be detected, or no reader/writer is registered. |
| invalid-tag | A tag block was found but its bytes were structurally invalid. |
| truncated-input | The input ended before a required structure could be read in full. |
| unsupported-feature | The input uses a feature not yet supported (e.g. compression / encryption). |
Recoverable problems do not throw — they are collected as non-fatal diagnostics on Track.warnings: readonly Warning[]. For example, a single malformed frame inside an otherwise valid tag falls into this category.
Field mapping
The mapping between each tag format and the common TagData shape is documented in docs/field-mapping.md.
Documentation
docs/README.md— documentation indexdocs/rules/— coding / testing / git rulesdocs/plan/— phase-by-phase implementation plan
References
Reference implementation:
- Zeugma440/atldotnet
- C# audio-metadata library used as the behavioural reference for this project.
- Cross-format compatibility table: Google Sheet
Specifications and supporting documentation:
- ID3v2.3 / ID3v2.4 structure / ID3v2.4 frames
- APE Tag (HydrogenAudio wiki) and Monkey's Audio
- FLAC format and Vorbis Comment
- RFC 3533 — Ogg Encapsulation and RFC 7845 — Ogg Encapsulation for Opus
- ISO/IEC 14496-12 — ISO Base Media File Format (MP4) and iTunes Metadata atoms (AtomicParsley)
- RIFF (Wikipedia), WAV
LIST/INFO, and AIFF (Wikipedia) - Advanced Systems Format (ASF / WMA)
