mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 19:03:46 +02:00
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:
co-authored by
David P.
parent
cab8319051
commit
6aee4e6a6a
@@ -15,6 +15,7 @@ import { MetadataTags } from '../tags';
|
||||
import { assert, UNDETERMINED_LANGUAGE } from '../misc';
|
||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
||||
import { readAscii, readBytes, Reader, readU16, readU32, readU64 } from '../reader';
|
||||
import { parseId3V2Tag, readId3V2Header } from '../id3';
|
||||
|
||||
export enum WaveFormat {
|
||||
PCM = 0x0001,
|
||||
@@ -107,6 +108,8 @@ export class WaveDemuxer extends Demuxer {
|
||||
totalFileSize = Math.min(riffChunkSize + 8, this.reader.fileSize ?? Infinity);
|
||||
} else if (chunkId === 'LIST') {
|
||||
await this.parseListChunk(startPos, chunkSize, littleEndian);
|
||||
} else if (chunkId === 'ID3 ' || chunkId === 'id3 ') {
|
||||
await this.parseId3Chunk(startPos, chunkSize);
|
||||
}
|
||||
|
||||
currentPos = startPos + chunkSize + (chunkSize & 1); // Handle padding
|
||||
@@ -271,6 +274,21 @@ export class WaveDemuxer extends Demuxer {
|
||||
}
|
||||
}
|
||||
|
||||
private async parseId3Chunk(startPos: number, size: number) {
|
||||
// Parse ID3 tag embedded in WAV file (non-default, but used a lot in practice anyway)
|
||||
let slice = this.reader.requestSlice(startPos, size);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (!slice) return; // File too short
|
||||
|
||||
const id3V2Header = readId3V2Header(slice);
|
||||
if (id3V2Header) {
|
||||
// Extract the content portion (skip the 10-byte header)
|
||||
const contentSlice = slice.slice(startPos + 10, id3V2Header.size);
|
||||
|
||||
parseId3V2Tag(contentSlice, id3V2Header, this.metadataTags);
|
||||
}
|
||||
}
|
||||
|
||||
getCodec(): AudioCodec | null {
|
||||
assert(this.audioInfo);
|
||||
|
||||
|
||||
+34
-2
@@ -16,6 +16,7 @@ import { EncodedPacket } from '../packet';
|
||||
import { WavOutputFormat } from '../output-format';
|
||||
import { assert, assertNever, isIso88591Compatible, keyValueIterator } from '../misc';
|
||||
import { MetadataTags, metadataTagsAreEmpty } from '../tags';
|
||||
import { Id3V2Writer } from '../id3';
|
||||
|
||||
export class WaveMuxer extends Muxer {
|
||||
private format: WavOutputFormat;
|
||||
@@ -159,9 +160,17 @@ export class WaveMuxer extends Muxer {
|
||||
this.riffWriter.writeU16(blockSize);
|
||||
this.riffWriter.writeU16(8 * pcmInfo.sampleSize);
|
||||
|
||||
// Metadata tags
|
||||
if (!metadataTagsAreEmpty(this.output._metadataTags)) {
|
||||
// Metadata exists, let's write an INFO chunk
|
||||
this.writeInfoChunk(this.output._metadataTags);
|
||||
const metadataFormat = this.format._options.metadataFormat ?? 'info';
|
||||
|
||||
if (metadataFormat === 'info') {
|
||||
this.writeInfoChunk(this.output._metadataTags);
|
||||
} else if (metadataFormat === 'id3') {
|
||||
this.writeId3Chunk(this.output._metadataTags);
|
||||
} else {
|
||||
assertNever(metadataFormat);
|
||||
}
|
||||
}
|
||||
|
||||
// data chunk
|
||||
@@ -300,6 +309,29 @@ export class WaveMuxer extends Muxer {
|
||||
}
|
||||
}
|
||||
|
||||
private writeId3Chunk(metadata: MetadataTags) {
|
||||
const startPos = this.writer.getPos();
|
||||
|
||||
// Write RIFF chunk header
|
||||
this.riffWriter.writeAscii('ID3 ');
|
||||
this.riffWriter.writeU32(0); // Size placeholder
|
||||
|
||||
const id3Writer = new Id3V2Writer(this.writer);
|
||||
const id3TagSize = id3Writer.writeId3V2Tag(metadata);
|
||||
|
||||
const endPos = this.writer.getPos();
|
||||
|
||||
// Update RIFF chunk size
|
||||
this.writer.seek(startPos + 4);
|
||||
this.riffWriter.writeU32(id3TagSize);
|
||||
this.writer.seek(endPos);
|
||||
|
||||
// Add padding byte if chunk size is odd
|
||||
if (id3TagSize & 1) {
|
||||
this.writer.write(new Uint8Array(1));
|
||||
}
|
||||
}
|
||||
|
||||
async finalize() {
|
||||
const release = await this.mutex.acquire();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user