@remotion/media-utils
Version:
Utilities for working with media files
96 lines (95 loc) • 4.21 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.probeWaveFile = exports.getInt8AsFloat = exports.getInt16AsFloat = void 0;
const fetch_with_cors_catch_1 = require("./fetch-with-cors-catch");
const getUint32 = (bytes, offset) => {
const val1 = bytes[offset + 3];
const val2 = bytes[offset + 2];
const val3 = bytes[offset + 1];
const val4 = bytes[offset];
return (val1 << 24) | (val2 << 16) | (val3 << 8) | val4;
};
const getUint16 = (bytes, offset) => {
const val1 = bytes[offset + 1];
const val2 = bytes[offset];
return (val1 << 8) | val2;
};
const getInt16AsFloat = (bytes, offset) => {
if (offset >= bytes.length) {
throw new Error(`Tried to read a 16-bit integer from offset ${offset} but the array length is ${bytes.length}`);
}
const val1 = bytes[offset + 1];
const val2 = bytes[offset];
const int16 = (val1 << 8) | val2;
return ((int16 << 16) >> 16) / 32768;
};
exports.getInt16AsFloat = getInt16AsFloat;
const getInt8AsFloat = (bytes, offset) => {
if (offset >= bytes.length) {
throw new Error(`Tried to read an 8-bit integer from offset ${offset} but the array length is ${bytes.length}`);
}
return bytes[offset] / 128;
};
exports.getInt8AsFloat = getInt8AsFloat;
const probeWaveFile = async (src, probeSize = 1024) => {
const response = await (0, fetch_with_cors_catch_1.fetchWithCorsCatch)(src, {
headers: {
range: `bytes=0-${probeSize - 1}`,
},
});
if (response.status === 416) {
throw new Error(`Tried to read bytes 0-1024 from ${src}, but the response status code was 416 "Range Not Satisfiable". Is the file at least 256 bytes long?`);
}
if (response.status !== 206) {
throw new Error(`Tried to read bytes 0-1024 from ${src}, but the response status code was ${response.status} (expected was 206). This means the server might not support returning a partial response.`);
}
const buffer = await response.arrayBuffer();
const uintArray = new Uint8Array(buffer);
const shouldBeRiff = new TextDecoder().decode(uintArray.slice(0, 4));
if (shouldBeRiff !== 'RIFF') {
throw new Error('getPartialAudioData() requires a WAVE file, but the first bytes are not RIFF. ');
}
const size = getUint32(uintArray, 4);
const shouldBeWAVE = new TextDecoder().decode(uintArray.slice(8, 12));
if (shouldBeWAVE !== 'WAVE') {
throw new Error('getPartialAudioData() requires a WAVE file, but the bytes 8-11 are not "WAVE". ');
}
const shouldBeFmt = new TextDecoder().decode(uintArray.slice(12, 16));
if (shouldBeFmt !== 'fmt ') {
throw new Error('getPartialAudioData() requires a WAVE file, but the bytes 12-15 are not "fmt ". ');
}
// const chunkSize = toUint32(uintArray.slice(16, 20));
const audioFormat = getUint16(uintArray, 20);
if (audioFormat !== 1) {
throw new Error('getPartialAudioData() supports only a WAVE file with PCM audio format, but the audio format is not PCM. ');
}
const numberOfChannels = getUint16(uintArray, 22);
const sampleRate = getUint32(uintArray, 24);
const blockAlign = getUint16(uintArray, 32);
const bitsPerSample = getUint16(uintArray, 34);
let offset = 36;
const shouldBeDataOrList = new TextDecoder().decode(uintArray.slice(offset, offset + 4));
if (shouldBeDataOrList === 'LIST') {
const listSize = getUint32(uintArray, 40);
offset += listSize;
offset += 8;
}
if (offset + 4 > probeSize) {
return (0, exports.probeWaveFile)(src, offset + 4);
}
const shouldBeData = new TextDecoder().decode(uintArray.slice(offset, offset + 4));
if (shouldBeData !== 'data') {
throw new Error(`getPartialAudioData() requires a WAVE file, but the bytes ${offset}-${offset + 4} are not "data". `);
}
const dataSize = getUint32(uintArray, offset + 4);
return {
dataOffset: offset + 8,
bitsPerSample,
numberOfChannels,
sampleRate,
blockAlign,
fileSize: size,
durationInSeconds: dataSize / (sampleRate * blockAlign),
};
};
exports.probeWaveFile = probeWaveFile;