UNPKG

@remotion/media-utils

Version:

Utilities for working with media files

96 lines (95 loc) 4.21 kB
"use strict"; 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;