wav-info
Version:
Lightweight Node.js WAV file & buffer info helper
110 lines (94 loc) • 3.93 kB
JavaScript
const fs = require('fs');
let wi = {};
// this a list of sequenced bytes in the 40 byte header. This builds the read_result object.
// Property name / Data type / Length
const reads = [
['riff_head', 'string', 4],
['chunk_size','uinteger', 4],
['wave_identifier', 'string', 4],
['fmt_identifier', 'string', 4],
['subchunk_size', 'integer',4],
['audio_format', 'integer', 2],
['num_channels', 'integer', 2],
['sample_rate', 'uinteger', 4],
['byte_rate', 'integer', 4],
['block_align', 'integer', 2],
['bits_per_sample', 'integer', 2],
//['uhm','integer', 2],
['data_identifier','string', 4],
//['sub_chunk2_size', 'integer', 4],
]
function decode_wav(buffer, cb, stats) {
let read_result = {};
let i = 0;
let pointer = 0;
function read_wav() {
var read = reads[i];
i++;
if(read[1] === 'string') {
read_result[read[0]] = buffer.toString('ascii', pointer , pointer + read[2]);
pointer = pointer + read[2]; // pointer = pointer plus # bytes
}
else if(read[1] === 'integer') {
read_result[read[0]] = buffer.readUInt16LE(pointer, read[2])
pointer = pointer + read[2];
}
else if(read[1] === 'uinteger') {
read_result[read[0]] = buffer.readInt32LE(pointer, read[2])
pointer = pointer + read[2];
}
if(i < reads.length) { return read_wav() }
else { return post_process(read_result, cb, stats) }
}
return read_wav();
}
function post_process(read_result, cb, stats) {
var error = false;
var invalid_reasons = []
if (read_result.riff_head != "RIFF") invalid_reasons.push("Expected \"RIFF\" string at 0")
if (read_result.wave_identifier != "WAVE") invalid_reasons.push("Expected \"WAVE\" string at 4")
if (read_result.fmt_identifier != "fmt ") invalid_reasons.push("Expected \"fmt \" string at 8")
if (
(read_result.audio_format !== 1) && // Wav PCM
(read_result.audio_format !== 65534) && // Extensible PCM
(read_result.audio_format !== 2) && // Wav
(read_result.audio_format !== 6) && // Wav ALAW
(read_result.audio_format !== 7) && // Wav MULAW
(read_result.audio_format !== 22127) && // Vorbis ?? (issue #11)
(read_result.audio_format !== 3) // Wav
) invalid_reasons.push("Unknown format: " + read_result.audio_format);
if ((read_result.chunk_size + 8) !== stats.size) invalid_reasons.push("chunk_size does not match file size");
//if ((read_result.data_identifier) != "data") invalid_reasons.push("Expected data identifier at the end of the header")
if (invalid_reasons.length > 0) error = true;
if (error) return cb({
error : true,
invalid_reasons: invalid_reasons,
header: read_result,
stats: stats
});
return cb(null, {
header: read_result,
stats: stats,
duration: ((read_result.chunk_size) / (read_result.sample_rate * read_result.num_channels * (read_result.bits_per_sample / 8)))
});
}
wi.infoByBuffer = function(buffer, cb) {
const riffHeadBuffer = buffer.slice(0, 40);
return decode_wav(riffHeadBuffer, cb, {
size: buffer.length,
});
}
wi.infoByFile = function(filename, cb) {
const stats = fs.statSync(filename);
const buffer = new Buffer.alloc(40); // first 40 bytes are RIFF header
fs.open(filename, 'r', function(err, fd) {
if(err) return cb(err); // error probably TODO:check this!
// ex error -
// { [Error: ENOENT: no such file or directory, open './test.wav'] errno: -2, code: 'ENOENT', syscall: 'open', path: './test.wav' }
fs.read(fd, buffer, 0, 40, 0, function(err, num) {
decode_wav(buffer, cb, stats);
fs.close(fd);
});
});
}
module.exports = wi;