Added ID3v2 support for FLAC files. (Fixes #417) (#418)

* Added ID3v2 support for FLAC files.  (Fixes #417)

* Fix ID3v2 header size calculation, adjust ID3v2 logic for FLAC files

---------

Co-authored-by: Vanilagy <[email protected]>
This commit is contained in:
Brad Isbell
2026-06-18 16:40:43 +00:00
committed by GitHub
co-authored by Vanilagy
parent 5b890efc29
commit 92a384f418
6 changed files with 117 additions and 17 deletions
+57
View File
@@ -1,5 +1,6 @@
import { expect, test } from 'vitest';
import path from 'node:path';
import fs from 'node:fs/promises';
import { assert, toUint8Array } from '../../src/misc.js';
import { Input } from '../../src/input.js';
import { BufferSource, FilePathSource } from '../../src/source.js';
@@ -284,3 +285,59 @@ test('appendOnly writes correct STREAMINFO header', async () => {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]));
});
test('can read a FLAC file with leading ID3v2 tags', async () => {
const createId3V23TitleTag = (title: string) => {
const titleBytes = new TextEncoder().encode(title);
const frame = new Uint8Array(11 + titleBytes.length);
frame.set([0x54, 0x49, 0x54, 0x32]); // TIT2
frame[7] = 1 + titleBytes.length;
frame[10] = 0; // ISO-8859-1
frame.set(titleBytes, 11);
const tag = new Uint8Array(10 + frame.length);
tag.set([0x49, 0x44, 0x33, 0x03, 0x00, 0x00]); // ID3v2.3
tag[9] = frame.length;
tag.set(frame, 10);
return tag;
};
const concatenateBytes = (...chunks: Uint8Array[]) => {
const result = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.byteLength;
}
return result;
};
using input = new Input({
source: new BufferSource(concatenateBytes(
createId3V23TitleTag('First tag'),
createId3V23TitleTag('Second tag'),
await fs.readFile(new URL('../public/sample.flac', import.meta.url)),
)),
formats: ALL_FORMATS,
});
expect(await input.canRead()).toBe(true);
expect(await input.getFormat()).toBe(FLAC);
const track = await input.getPrimaryAudioTrack();
assert(track);
expect(await track.getDurationFromMetadata()).toEqual(19.714285714285715);
const firstPacket = await new EncodedPacketSink(track).getPacket(0);
assert(firstPacket);
expect(firstPacket.sequenceNumber).toBe(0);
expect(firstPacket.timestamp).toBe(0);
const metadataTags = await input.getMetadataTags();
expect(metadataTags.title).toBe('First tag');
expect(metadataTags.raw!['TIT2']).toBe('First tag');
expect(metadataTags.raw!['TITLE']).toBe('The Happy Meeting');
});