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
+28 -2
View File
@@ -37,6 +37,7 @@ import {
getSampleRateOrUncommon,
} from './flac-misc';
import { Bitstream } from '../../shared/bitstream';
import { ID3_V2_HEADER_SIZE, parseId3V2Tag, readId3V2Header } from '../id3';
type FlacAudioInfo = {
numberOfChannels: number;
@@ -102,9 +103,34 @@ export class FlacDemuxer extends Demuxer {
}
async readMetadata() {
let currentPos = 4; // Skip 'fLaC'
return (this.metadataPromise ??= (async () => {
// Read all ID3v2 tags at the start of the file
let currentPos = 0;
while (true) {
let headerSlice = this.reader.requestSlice(currentPos, ID3_V2_HEADER_SIZE);
if (headerSlice instanceof Promise) headerSlice = await headerSlice;
if (!headerSlice) {
this.lastSampleLoaded = true;
return;
}
const id3V2Header = readId3V2Header(headerSlice);
if (!id3V2Header) {
break;
}
let contentSlice = this.reader.requestSlice(headerSlice.filePos, id3V2Header.size);
if (contentSlice instanceof Promise) contentSlice = await contentSlice;
assert(contentSlice);
parseId3V2Tag(contentSlice, id3V2Header, this.metadataTags);
currentPos = headerSlice.filePos + id3V2Header.size;
}
currentPos += 4; // Skip 'fLaC'
while (
this.reader.fileSize === null
|| currentPos < this.reader.fileSize
+10 -11
View File
@@ -152,7 +152,11 @@ export const readId3V2Header = (slice: FileSlice): Id3V2Header | null => {
return null;
}
const size = decodeSynchsafe(sizeRaw);
let size = decodeSynchsafe(sizeRaw);
if (flags & Id3V2HeaderFlags.Footer) {
size += ID3_V2_HEADER_SIZE;
}
return { majorVersion, revision, flags, size };
};
@@ -165,12 +169,12 @@ export const parseId3V2Tag = (slice: FileSlice, header: Id3V2Header, tags: Metad
return;
}
const bytes = readBytes(slice, header.size);
const reader = new Id3V2Reader(header, bytes);
const dataSize = (header.flags & Id3V2HeaderFlags.Footer)
? header.size - ID3_V2_HEADER_SIZE
: header.size;
if (header.flags & Id3V2HeaderFlags.Footer) {
reader.removeFooter();
}
const bytes = readBytes(slice, dataSize);
const reader = new Id3V2Reader(header, bytes);
if ((header.flags & Id3V2HeaderFlags.Unsynchronisation) && header.majorVersion === 3) {
reader.ununsynchronizeAll();
@@ -463,11 +467,6 @@ export class Id3V2Reader {
this.view = new DataView(this.bytes.buffer);
}
removeFooter() {
this.bytes = this.bytes.subarray(0, this.bytes.length - ID3_V2_HEADER_SIZE);
this.view = new DataView(this.bytes.buffer);
}
readBytes(length: number) {
const slice = this.bytes.subarray(this.pos, this.pos + length);
this.pos += length;
+17 -1
View File
@@ -446,7 +446,23 @@ export class OggInputFormat extends InputFormat {
export class FlacInputFormat extends InputFormat {
/** @internal */
async _canReadInput(input: Input) {
let slice = input._reader.requestSlice(0, 4);
let currentPos = 0;
// There might be ID3v2 headers at the start, skip 'em
while (true) {
let slice = input._reader.requestSlice(currentPos, ID3_V2_HEADER_SIZE);
if (slice instanceof Promise) slice = await slice;
if (!slice) break;
const id3V2Header = readId3V2Header(slice);
if (!id3V2Header) {
break;
}
currentPos = slice.filePos + id3V2Header.size;
}
let slice = input._reader.requestSlice(currentPos, 4);
if (slice instanceof Promise) slice = await slice;
if (!slice) return false;
+4 -2
View File
@@ -20,7 +20,8 @@ import { isRecordStringString } from './misc';
* in Vorbis-style comment headers.
* - For WAVE files, the metadata refers to the chunks within the RIFF INFO chunk.
* - For ADTS files, the metadata refers to the ID3v2 tags.
* - For FLAC files, the metadata lives in Vorbis style in the Vorbis comment block.
* - For FLAC files, the metadata lives in Vorbis style in the Vorbis comment block, or sometimes in ID3v2 tags at the
* start of the file.
* - For MPEG-TS files, metadata tags are currently not supported.
*
* @group Metadata tags
@@ -78,7 +79,8 @@ export type MetadataTags = {
* Additionally, the `'vendor'` key refers to the vendor string within this header.
* - WAVE: The individual metadata chunks within the RIFF INFO chunk. Values are always ISO 8859-1 strings.
* - FLAC: The key-value string pairs from the vorbis metadata block (see RFC 9639, Section D.2.3).
* Additionally, the `'vendor'` key refers to the vendor string within this header.
* Additionally, the `'vendor'` key refers to the vendor string within this header. If ID3v2 tags appear at the
* start of the file, their content is stored just like for MP3.
* - MPEG-TS: Not supported.
*/
raw?: Record<string, string | Uint8Array | RichImageData | AttachedFile | Record<string, string> | null>;