Fix WAV files with embedded ID3 metadata returning empty tags (#110)

* Read ID3v2 tags from WAV file

* remove js import

Co-authored-by: David P. <[email protected]>

* revert slicing in wave-demuxer.ts and add id3.ts for further sharing

* Add `writeId3Tag` to the `WavOutputFormat` constructor options

* add more context in private and public APIs that it's non-standard

* compare actual values in the test with real file

* remove unnecesarry double slice requesting

* trim test audio file (i tried my best to preserve ID3, including artwork)

* Couple of code cleanups

* const -> using

* Fix linting errors

---------

Co-authored-by: David P. <[email protected]>
This commit is contained in:
Igor Samokhovets
2025-09-22 14:06:06 +02:00
committed by GitHub
co-authored by David P.
parent cab8319051
commit 6aee4e6a6a
11 changed files with 1099 additions and 898 deletions
+15 -2
View File
@@ -626,8 +626,18 @@ export type WavOutputFormatOptions = {
large?: boolean;
/**
* Will be called once the file header is written. The header consists of the RIFF header, the format chunk, and the
* start of the data chunk (with a placeholder size of 0).
* The metadata format to use for writing metadata tags.
*
* - `'info'` (default): Writes metadata into a RIFF INFO LIST chunk, the default way to contain metadata tags
* within WAVE. Only allows for a limited subset of tags to be written.
* - `'id3'`: Writes metadata into an ID3 chunk. Non-default, but used by many taggers in practice. Allows for a
* much larger and richer set of tags to be written.
*/
metadataFormat?: 'info' | 'id3';
/**
* Will be called once the file header is written. The header consists of the RIFF header, the format chunk,
* metadata chunks, and the start of the data chunk (with a placeholder size of 0).
*/
onHeader?: (data: Uint8Array, position: number) => unknown;
};
@@ -649,6 +659,9 @@ export class WavOutputFormat extends OutputFormat {
if (options.large !== undefined && typeof options.large !== 'boolean') {
throw new TypeError('options.large, when provided, must be a boolean.');
}
if (options.metadataFormat !== undefined && !['info', 'id3'].includes(options.metadataFormat)) {
throw new TypeError('options.metadataFormat, when provided, must be either \'info\' or \'id3\'.');
}
if (options.onHeader !== undefined && typeof options.onHeader !== 'function') {
throw new TypeError('options.onHeader, when provided, must be a function.');
}