Validate WAV audio metadata
This commit is contained in:
@@ -195,6 +195,28 @@ function validateAudioFile(errors, context, path) {
|
|||||||
if (path.toLowerCase().endsWith('.wav')) {
|
if (path.toLowerCase().endsWith('.wav')) {
|
||||||
if (bytes.subarray(0, 4).toString('ascii') !== 'RIFF' || bytes.subarray(8, 12).toString('ascii') !== 'WAVE') {
|
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`);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
@@ -211,6 +233,39 @@ function validateAudioFile(errors, context, path) {
|
|||||||
errors.push(`${context} at "${path}" uses an unsupported audio extension`);
|
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) {
|
function lineForIndex(source, index) {
|
||||||
return source.slice(0, index).split(/\r?\n/).length;
|
return source.slice(0, index).split(/\r?\n/).length;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user