Files
suaveplan/packages/media/audio/audio-metadata/src/parsers/id3v1/id3v1.test.ts
2026-05-27 23:47:53 +01:00

128 lines
3.9 KiB
TypeScript

import { describe, expect, it } from "@suaveplan/testing/runner";
import { parseId3v1 } from "./id3v1.js";
/**
* Build a minimal ID3v1 tag buffer.
* Returns a 128-byte buffer starting at offset 0 (the entire buffer IS the tag).
*/
function buildId3v1Tag({
title = "",
artist = "",
album = "",
year = "",
comment = "",
track = 0,
genreIdx = 255,
}: {
title?: string;
artist?: string;
album?: string;
year?: string;
comment?: string;
track?: number;
genreIdx?: number;
}): Uint8Array {
const buf = new Uint8Array(128);
buf[0] = 0x54;
buf[1] = 0x41;
buf[2] = 0x47; // TAG
const enc = new TextEncoder();
const writeField = (str: string, offset: number, maxLen: number) => {
const bytes = enc.encode(str);
const len = Math.min(bytes.length, maxLen);
buf.set(bytes.subarray(0, len), offset);
};
writeField(title, 3, 30);
writeField(artist, 33, 30);
writeField(album, 63, 30);
writeField(year, 93, 4);
if (track > 0) {
writeField(comment, 97, 28);
buf[125] = 0x00;
buf[126] = track;
} else {
writeField(comment, 97, 30);
}
buf[127] = genreIdx;
return buf;
}
describe("id3v1 parser", () => {
it("parses title, artist, album, year", { timeout: 5000 }, () => {
const buf = buildId3v1Tag({
title: "My Song",
artist: "My Artist",
album: "My Album",
year: "2023",
});
const track = parseId3v1(buf);
expect(track.title).toBe("My Song");
expect(track.artist).toBe("My Artist");
expect(track.album).toBe("My Album");
expect(track.year).toBe(2023);
expect(track.format).toBe("id3v1");
});
it("parses genre by index", { timeout: 5000 }, () => {
const buf = buildId3v1Tag({ genreIdx: 17 });
const track = parseId3v1(buf);
expect(track.genre).toBe("Rock");
});
it("parses track number (ID3v1.1)", { timeout: 5000 }, () => {
const buf = buildId3v1Tag({ comment: "Nice track", track: 5 });
const track = parseId3v1(buf);
expect(track.trackNumber).toBe(5);
expect(track.comment).toBe("Nice track");
});
it("parses comment without track number", { timeout: 5000 }, () => {
const buf = buildId3v1Tag({ comment: "Full comment here" });
const track = parseId3v1(buf);
expect(track.comment).toBe("Full comment here");
expect(track.trackNumber).toBeUndefined();
});
it("strips trailing nulls and whitespace", { timeout: 5000 }, () => {
const buf = buildId3v1Tag({ title: "Song " });
const track = parseId3v1(buf);
expect(track.title).toBe("Song");
});
it("returns empty track when buffer too short", { timeout: 5000 }, () => {
const track = parseId3v1(new Uint8Array(64));
expect(track).toEqual({});
});
it("returns empty track when TAG magic missing", { timeout: 5000 }, () => {
const buf = new Uint8Array(128);
// No TAG magic
const track = parseId3v1(buf);
expect(track).toEqual({});
});
it("handles unknown genre index gracefully", { timeout: 5000 }, () => {
const buf = buildId3v1Tag({ genreIdx: 200 }); // Beyond known list
const track = parseId3v1(buf);
expect(track.genre).toBeUndefined();
});
it("returns rawTags with parsed fields", { timeout: 5000 }, () => {
const buf = buildId3v1Tag({ title: "Test", artist: "Artist" });
const track = parseId3v1(buf);
expect(track.rawTags?.["TIT2"]).toBe("Test");
expect(track.rawTags?.["TPE1"]).toBe("Artist");
});
it("ignores non-numeric year string", { timeout: 5000 }, () => {
// year = "abcd" → parseInt returns NaN → year should not be set
const buf = buildId3v1Tag({ title: "Song", year: "abcd" });
const track = parseId3v1(buf);
expect(track.year).toBeUndefined();
});
});