Validate WAV audio metadata
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user