@remotion/media-parser
Version:
A pure JavaScript library for parsing video files
179 lines (178 loc) • 6.94 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseFlacFrame = exports.parseFrameHeader = void 0;
const convert_audio_or_video_sample_1 = require("../../convert-audio-or-video-sample");
const buffer_iterator_1 = require("../../iterator/buffer-iterator");
const get_block_size_1 = require("./get-block-size");
const get_channel_count_1 = require("./get-channel-count");
const get_sample_rate_1 = require("./get-sample-rate");
// https://www.rfc-editor.org/rfc/rfc9639.html#section-9.1.1
function calculateCRC8(data) {
const polynomial = 0x07; // x^8 + x^2 + x^1 + x^0
let crc = 0x00; // Initialize CRC to 0
for (const byte of data) {
crc ^= byte; // XOR byte into least significant byte of crc
for (let i = 0; i < 8; i++) {
// For each bit in the byte
if ((crc & 0x80) !== 0) {
// If the leftmost bit (MSB) is set
crc = (crc << 1) ^ polynomial; // Shift left and XOR with polynomial
}
else {
crc <<= 1; // Just shift left
}
crc &= 0xff; // Ensure CRC remains 8-bit
}
}
return crc;
}
const parseFrameHeader = ({ iterator, state, }) => {
if (iterator.bytesRemaining() < 10) {
return null;
}
const startOffset = iterator.counter.getOffset();
iterator.discard(2); // sync code
iterator.startReadingBits();
const blockSizeBits = (0, get_block_size_1.getBlockSize)(iterator);
const sampleRateBits = (0, get_sample_rate_1.getSampleRate)(iterator, state);
(0, get_channel_count_1.getChannelCount)(iterator); // channel count
iterator.getBits(3); // bit depth
iterator.getBits(1);
const num = iterator.getFlacCodecNumber();
const blockSize = blockSizeBits === 'uncommon-u16'
? iterator.getBits(16) + 1
: blockSizeBits === 'uncommon-u8'
? iterator.getBits(8) + 1
: blockSizeBits;
const sampleRate = sampleRateBits === 'uncommon-u16'
? iterator.getBits(16)
: sampleRateBits === 'uncommon-u16-10'
? iterator.getBits(16) * 10
: sampleRateBits === 'uncommon-u8'
? iterator.getBits(8)
: sampleRateBits;
iterator.stopReadingBits();
const size = iterator.counter.getOffset() - startOffset;
const crc = iterator.getUint8();
iterator.counter.decrement(size + 1);
const crcCalculated = calculateCRC8(iterator.getSlice(size));
iterator.counter.decrement(size);
if (crcCalculated !== crc) {
return null;
}
return { num, blockSize, sampleRate };
};
exports.parseFrameHeader = parseFrameHeader;
const emitSample = async ({ state, data, offset, }) => {
const iterator = (0, buffer_iterator_1.getArrayBufferIterator)(data, data.length);
const parsed = (0, exports.parseFrameHeader)({ iterator, state });
if (!parsed) {
throw new Error('Invalid CRC');
}
const { blockSize, num, sampleRate } = parsed;
const duration = blockSize / sampleRate;
const structure = state.structure.getFlacStructure();
const streamInfo = structure.boxes.find((box) => box.type === 'flac-streaminfo');
if (!streamInfo) {
throw new Error('Stream info not found');
}
if (streamInfo.minimumBlockSize !== streamInfo.maximumBlockSize) {
throw new Error('Cannot determine timestamp');
}
const timestamp = (num * streamInfo.maximumBlockSize) / streamInfo.sampleRate;
state.flac.audioSamples.addSample({
timeInSeconds: timestamp,
offset,
durationInSeconds: duration,
});
const audioSample = (0, convert_audio_or_video_sample_1.convertAudioOrVideoSampleToWebCodecsTimestamps)({
sample: {
data,
duration,
decodingTimestamp: timestamp,
timestamp,
type: 'key',
offset,
},
timescale: 1,
});
await state.callbacks.onAudioSample({
audioSample,
trackId: 0,
});
iterator.destroy();
};
const parseFlacFrame = async ({ state, iterator, }) => {
var _a, _b;
const blockingBit = state.flac.getBlockingBitStrategy();
const offset = iterator.counter.getOffset();
const { returnToCheckpoint } = iterator.startCheckpoint();
iterator.startReadingBits();
if (blockingBit === undefined) {
const bits = iterator.getBits(15);
if (bits !== 0b111111111111100) {
throw new Error('Invalid sync code');
}
state.flac.setBlockingBitStrategy(iterator.getBits(1));
}
else if (blockingBit === 1) {
const bits = iterator.getBits(16);
if (bits !== 0b1111111111111001) {
throw new Error('Blocking bit changed, it should not');
}
}
else if (blockingBit === 0) {
const bits = iterator.getBits(16);
if (bits !== 0b1111111111111000) {
throw new Error('Blocking bit changed, it should not');
}
}
const setBlockingBit = state.flac.getBlockingBitStrategy();
if (setBlockingBit === undefined) {
throw new Error('Blocking bit should be set');
}
iterator.stopReadingBits();
const structure = state.structure.getFlacStructure();
const minimumFrameSize = (_b = (_a = structure.boxes.find((b) => b.type === 'flac-streaminfo')) === null || _a === void 0 ? void 0 : _a.minimumFrameSize) !== null && _b !== void 0 ? _b : null;
if (minimumFrameSize === null) {
throw new Error('Expected flac-streaminfo');
}
if (minimumFrameSize !== 0) {
iterator.getSlice(minimumFrameSize - 2);
}
while (true) {
if (iterator.counter.getOffset() === state.contentLength) {
const size = iterator.counter.getOffset() - offset;
returnToCheckpoint();
const slice = iterator.getSlice(size);
await emitSample({ state, data: slice, offset });
break;
}
if (iterator.bytesRemaining() === 0) {
returnToCheckpoint();
break;
}
const nextByte = iterator.getUint8();
if (nextByte === 0xff) {
const nextBits = iterator.getUint8();
const expected = setBlockingBit === 1 ? 249 : 248;
if (nextBits !== expected) {
iterator.counter.decrement(1);
continue;
}
iterator.counter.decrement(2);
const nextIsLegit = (0, exports.parseFrameHeader)({ iterator, state });
if (!nextIsLegit) {
iterator.discard(1);
continue;
}
const size = iterator.counter.getOffset() - offset;
returnToCheckpoint();
const data = iterator.getSlice(size);
await emitSample({ state, data, offset });
break;
}
}
return null;
};
exports.parseFlacFrame = parseFlacFrame;