Validate WAV audio metadata

This commit is contained in:
2026-07-05 15:33:23 +09:00
parent 0493d40060
commit e54c45cfb9

View File

@@ -195,6 +195,28 @@ function validateAudioFile(errors, context, path) {
if (path.toLowerCase().endsWith('.wav')) {
if (bytes.subarray(0, 4).toString('ascii') !== 'RIFF' || bytes.subarray(8, 12).toString('ascii') !== 'WAVE') {
errors.push(`${context} at "${path}" does not have a WAV header`);
return;
}
const metadata = readWavMetadata(bytes);
if (!metadata) {
errors.push(`${context} at "${path}" is missing readable WAV fmt/data chunks`);
return;
}
if (metadata.audioFormat !== 1) {
errors.push(`${context} at "${path}" should be PCM WAV audio, got format ${metadata.audioFormat}`);
}
if (metadata.channels < 1 || metadata.channels > 2) {
errors.push(`${context} at "${path}" should be mono or stereo WAV audio, got ${metadata.channels} channels`);
}
if (metadata.sampleRate < 22050) {
errors.push(`${context} at "${path}" should use at least 22050 Hz WAV audio, got ${metadata.sampleRate} Hz`);
}
if (![16, 24, 32].includes(metadata.bitsPerSample)) {
errors.push(`${context} at "${path}" should use 16/24/32-bit WAV audio, got ${metadata.bitsPerSample}-bit`);
}
if (metadata.dataSize <= 0) {
errors.push(`${context} at "${path}" does not contain WAV sample data`);
}
return;
}
@@ -211,6 +233,39 @@ function validateAudioFile(errors, context, path) {
errors.push(`${context} at "${path}" uses an unsupported audio extension`);
}
function readWavMetadata(bytes) {
let offset = 12;
let format;
let dataSize = 0;
while (offset + 8 <= bytes.length) {
const chunkType = bytes.subarray(offset, offset + 4).toString('ascii');
const chunkSize = bytes.readUInt32LE(offset + 4);
const dataOffset = offset + 8;
if (dataOffset + chunkSize > bytes.length) {
return undefined;
}
if (chunkType === 'fmt ' && chunkSize >= 16) {
format = {
audioFormat: bytes.readUInt16LE(dataOffset),
channels: bytes.readUInt16LE(dataOffset + 2),
sampleRate: bytes.readUInt32LE(dataOffset + 4),
bitsPerSample: bytes.readUInt16LE(dataOffset + 14)
};
} else if (chunkType === 'data') {
dataSize += chunkSize;
}
offset = dataOffset + chunkSize + (chunkSize % 2);
}
if (!format || dataSize <= 0) {
return undefined;
}
return { ...format, dataSize };
}
function lineForIndex(source, index) {
return source.slice(0, index).split(/\r?\n/).length;
}