@remotion/gif
Version:
Embed GIFs in a Remotion video
159 lines (158 loc) • 6.21 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GIF = void 0;
const parser_1 = require("./parser");
const uint8_parser_1 = require("./uint8-parser");
// a set of 0x00 terminated subblocks
const subBlocksSchema = {
blocks: (stream) => {
const terminator = 0x00;
const chunks = [];
const streamSize = stream.data.length;
let total = 0;
for (let size = (0, uint8_parser_1.readByte)()(stream); size !== terminator; size = (0, uint8_parser_1.readByte)()(stream)) {
// size becomes undefined for some case when file is corrupted and terminator is not proper
// null check to avoid recursion
if (!size)
break;
// catch corrupted files with no terminator
if (stream.pos + size >= streamSize) {
const availableSize = streamSize - stream.pos;
chunks.push((0, uint8_parser_1.readBytes)(availableSize)(stream));
total += availableSize;
break;
}
chunks.push((0, uint8_parser_1.readBytes)(size)(stream));
total += size;
}
const result = new Uint8Array(total);
let offset = 0;
for (let i = 0; i < chunks.length; i++) {
result.set(chunks[i], offset);
offset += chunks[i].length;
}
return result;
},
};
// global control extension
const gceSchema = (0, parser_1.conditional)({
gce: [
{ codes: (0, uint8_parser_1.readBytes)(2) },
{ byteSize: (0, uint8_parser_1.readByte)() },
{
extras: (0, uint8_parser_1.readBits)({
future: { index: 0, length: 3 },
disposal: { index: 3, length: 3 },
userInput: { index: 6 },
transparentColorGiven: { index: 7 },
}),
},
{ delay: (0, uint8_parser_1.readUnsigned)(true) },
{ transparentColorIndex: (0, uint8_parser_1.readByte)() },
{ terminator: (0, uint8_parser_1.readByte)() },
],
}, (stream) => {
const codes = (0, uint8_parser_1.peekBytes)(2)(stream);
return codes[0] === 0x21 && codes[1] === 0xf9;
});
// image pipeline block
const imageSchema = (0, parser_1.conditional)({
image: [
{ code: (0, uint8_parser_1.readByte)() },
{
descriptor: [
{ left: (0, uint8_parser_1.readUnsigned)(true) },
{ top: (0, uint8_parser_1.readUnsigned)(true) },
{ width: (0, uint8_parser_1.readUnsigned)(true) },
{ height: (0, uint8_parser_1.readUnsigned)(true) },
{
lct: (0, uint8_parser_1.readBits)({
exists: { index: 0 },
interlaced: { index: 1 },
sort: { index: 2 },
future: { index: 3, length: 2 },
size: { index: 5, length: 3 },
}),
},
],
},
(0, parser_1.conditional)({
lct: (0, uint8_parser_1.readArray)(3, (_stream, _result, parent) => {
return 2 ** (parent.descriptor.lct.size + 1);
}),
}, (_stream, _result, parent) => {
return parent.descriptor.lct.exists;
}),
{ data: [{ minCodeSize: (0, uint8_parser_1.readByte)() }, subBlocksSchema] },
],
}, (stream) => {
return (0, uint8_parser_1.peekByte)()(stream) === 0x2c;
});
// plain text block
const textSchema = (0, parser_1.conditional)({
text: [
{ codes: (0, uint8_parser_1.readBytes)(2) },
{ blockSize: (0, uint8_parser_1.readByte)() },
{
preData: (stream, _result, parent) => (0, uint8_parser_1.readBytes)(parent.text.blockSize)(stream),
},
subBlocksSchema,
],
}, (stream) => {
const codes = (0, uint8_parser_1.peekBytes)(2)(stream);
return codes[0] === 0x21 && codes[1] === 0x01;
});
// application block
const applicationSchema = (0, parser_1.conditional)({
application: [
{ codes: (0, uint8_parser_1.readBytes)(2) },
{ blockSize: (0, uint8_parser_1.readByte)() },
{
id: (stream, _result, parent) => (0, uint8_parser_1.readString)(parent.blockSize)(stream),
},
subBlocksSchema,
],
}, (stream) => {
const codes = (0, uint8_parser_1.peekBytes)(2)(stream);
return codes[0] === 0x21 && codes[1] === 0xff;
});
// comment block
const commentSchema = (0, parser_1.conditional)({
comment: [{ codes: (0, uint8_parser_1.readBytes)(2) }, subBlocksSchema],
}, (stream) => {
const codes = (0, uint8_parser_1.peekBytes)(2)(stream);
return codes[0] === 0x21 && codes[1] === 0xfe;
});
exports.GIF = [
{ header: [{ signature: (0, uint8_parser_1.readString)(3) }, { version: (0, uint8_parser_1.readString)(3) }] },
{
lsd: [
{ width: (0, uint8_parser_1.readUnsigned)(true) },
{ height: (0, uint8_parser_1.readUnsigned)(true) },
{
gct: (0, uint8_parser_1.readBits)({
exists: { index: 0 },
resolution: { index: 1, length: 3 },
sort: { index: 4 },
size: { index: 5, length: 3 },
}),
},
{ backgroundColorIndex: (0, uint8_parser_1.readByte)() },
{ pixelAspectRatio: (0, uint8_parser_1.readByte)() },
],
},
(0, parser_1.conditional)({
gct: (0, uint8_parser_1.readArray)(3, (_stream, result) => 2 ** (result.lsd.gct.size + 1)),
}, (_stream, result) => result.lsd.gct.exists),
// content frames
{
frames: (0, parser_1.loop)([gceSchema, applicationSchema, commentSchema, imageSchema, textSchema], (stream) => {
const nextCode = (0, uint8_parser_1.peekByte)()(stream);
// rather than check for a terminator, we should check for the existence
// of an ext or image block to avoid infinite loops
// var terminator = 0x3B;
// return nextCode !== terminator;
return nextCode === 0x21 || nextCode === 0x2c;
}),
},
];