remotion
Version:
Make videos programmatically
352 lines (351 loc) • 13.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.assertValidInterpolateEasingOption = assertValidInterpolateEasingOption;
exports.assertValidInterpolatePosterizeOption = assertValidInterpolatePosterizeOption;
exports.interpolate = interpolate;
const normalize_number_js_1 = require("./normalize-number.js");
const angleUnits = new Set(['deg', 'rad', 'grad', 'turn']);
const lengthUnits = new Set([
'%',
'cap',
'ch',
'cm',
'cqb',
'cqh',
'cqi',
'cqmax',
'cqmin',
'cqw',
'dvh',
'dvw',
'em',
'ex',
'ic',
'in',
'lh',
'lvh',
'lvw',
'mm',
'pc',
'pt',
'px',
'q',
'rem',
'rlh',
'svh',
'svw',
'vb',
'vh',
'vi',
'vmax',
'vmin',
'vw',
]);
const cssNumberRegex = /^([+-]?(?:\d+\.?\d*|\.\d+))([a-zA-Z%]+)?$/;
const stringifyNumber = (value) => {
return String((0, normalize_number_js_1.normalizeNumber)(value));
};
const parseStringInterpolationComponent = (component, value) => {
var _a;
const match = cssNumberRegex.exec(component);
if (match === null) {
throw new TypeError(`Cannot interpolate "${value}" because "${component}" is not a supported scale, translate, or rotate value`);
}
const unit = (_a = match[2]) !== null && _a !== void 0 ? _a : null;
const numberValue = Number(match[1]);
if (!Number.isFinite(numberValue)) {
throw new TypeError(`Cannot interpolate "${value}" because "${component}" is not finite`);
}
if (unit === null) {
return { kind: 'scale', value: numberValue, unit: null };
}
if (angleUnits.has(unit)) {
return { kind: 'rotate', value: numberValue, unit };
}
if (lengthUnits.has(unit)) {
return { kind: 'translate', value: numberValue, unit };
}
throw new TypeError(`Cannot interpolate "${value}" because "${unit}" is not a supported translate or rotate unit`);
};
const parseStringInterpolationValue = (output) => {
var _a, _b, _c, _d, _e, _f;
var _g, _h, _j, _k, _l, _m;
if (typeof output === 'number') {
if (!Number.isFinite(output)) {
throw new Error(`outputRange must contain only finite numbers, but got [${output}]`);
}
return {
kind: 'scale',
values: [output, output, 1],
units: [null, null, null],
dimensions: 1,
};
}
const parts = output.trim().split(/\s+/);
if (parts.length < 1 || parts.length > 3 || parts[0] === '') {
throw new TypeError(`String outputRange values must contain 1 to 3 components, but got "${output}"`);
}
const parsed = parts.map((part) => parseStringInterpolationComponent(part, output));
const [{ kind }] = parsed;
for (const part of parsed) {
if (part.kind !== kind) {
throw new TypeError(`Cannot interpolate "${output}" because it mixes ${kind} and ${part.kind} values`);
}
}
if (kind === 'scale') {
const x = parsed[0].value;
const y = (_g = (_a = parsed[1]) === null || _a === void 0 ? void 0 : _a.value) !== null && _g !== void 0 ? _g : x;
const z = (_h = (_b = parsed[2]) === null || _b === void 0 ? void 0 : _b.value) !== null && _h !== void 0 ? _h : 1;
return {
kind,
values: [x, y, z],
units: [null, null, null],
dimensions: parsed.length,
};
}
return {
kind,
values: [parsed[0].value, (_j = (_c = parsed[1]) === null || _c === void 0 ? void 0 : _c.value) !== null && _j !== void 0 ? _j : 0, (_k = (_d = parsed[2]) === null || _d === void 0 ? void 0 : _d.value) !== null && _k !== void 0 ? _k : 0],
units: [parsed[0].unit, (_l = (_e = parsed[1]) === null || _e === void 0 ? void 0 : _e.unit) !== null && _l !== void 0 ? _l : null, (_m = (_f = parsed[2]) === null || _f === void 0 ? void 0 : _f.unit) !== null && _m !== void 0 ? _m : null],
dimensions: parsed.length,
};
};
const serializeStringInterpolationValue = ({ kind, values, units, dimensions, }) => {
if (kind === 'scale') {
return values
.slice(0, dimensions)
.map((value) => stringifyNumber(value))
.join(' ');
}
return values
.slice(0, dimensions)
.map((value, index) => `${stringifyNumber(value)}${units[index]}`)
.join(' ');
};
function interpolateFunction(input, inputRange, outputRange, options) {
const { extrapolateLeft, extrapolateRight, easing } = options;
let result = input;
const [inputMin, inputMax] = inputRange;
const [outputMin, outputMax] = outputRange;
if (result < inputMin) {
if (extrapolateLeft === 'identity') {
return result;
}
if (extrapolateLeft === 'clamp') {
result = inputMin;
}
else if (extrapolateLeft === 'wrap') {
const range = inputMax - inputMin;
result = ((((result - inputMin) % range) + range) % range) + inputMin;
}
else if (extrapolateLeft === 'extend') {
// Noop
}
}
if (result > inputMax) {
if (extrapolateRight === 'identity') {
return result;
}
if (extrapolateRight === 'clamp') {
result = inputMax;
}
else if (extrapolateRight === 'wrap') {
const range = inputMax - inputMin;
result = ((((result - inputMin) % range) + range) % range) + inputMin;
}
else if (extrapolateRight === 'extend') {
// Noop
}
}
if (outputMin === outputMax) {
return outputMin;
}
// Input Range
result = (result - inputMin) / (inputMax - inputMin);
// Easing
result = easing(result);
// Output Range
result = result * (outputMax - outputMin) + outputMin;
return result;
}
function findRange(input, inputRange) {
let i;
for (i = 1; i < inputRange.length - 1; ++i) {
if (inputRange[i] >= input) {
break;
}
}
return i - 1;
}
const defaultEasing = (num) => num;
const interpolateNumber = ({ input, inputRange, outputRange, options, }) => {
if (inputRange.length === 1) {
return outputRange[0];
}
const easingOption = options === null || options === void 0 ? void 0 : options.easing;
const resolveEasingForSegment = (segmentIndex) => {
if (easingOption === undefined) {
return defaultEasing;
}
if (typeof easingOption === 'function') {
return easingOption;
}
// `segmentIndex` is in [0, inputRange.length - 2]; array length was validated above.
return easingOption[segmentIndex];
};
let extrapolateLeft = 'extend';
if ((options === null || options === void 0 ? void 0 : options.extrapolateLeft) !== undefined) {
extrapolateLeft = options.extrapolateLeft;
}
let extrapolateRight = 'extend';
if ((options === null || options === void 0 ? void 0 : options.extrapolateRight) !== undefined) {
extrapolateRight = options.extrapolateRight;
}
const posterizedInput = (options === null || options === void 0 ? void 0 : options.posterize) === undefined
? input
: Math.floor(input / options.posterize) * options.posterize;
const range = findRange(posterizedInput, inputRange);
return interpolateFunction(posterizedInput, [inputRange[range], inputRange[range + 1]], [outputRange[range], outputRange[range + 1]], {
easing: resolveEasingForSegment(range),
extrapolateLeft,
extrapolateRight,
});
};
const interpolateString = ({ input, inputRange, outputRange, options, }) => {
var _a;
const parsedOutputRange = outputRange.map(parseStringInterpolationValue);
const kind = (_a = parsedOutputRange[0]) === null || _a === void 0 ? void 0 : _a.kind;
if (kind === undefined) {
throw new Error('outputRange must have at least 1 element');
}
for (const parsed of parsedOutputRange) {
if (parsed.kind !== kind) {
throw new TypeError(`Cannot interpolate ${kind} values with ${parsed.kind} values`);
}
}
const dimensions = Math.max(...parsedOutputRange.map((parsed) => parsed.dimensions));
const units = [
null,
null,
null,
];
if (kind !== 'scale') {
for (let axis = 0; axis < dimensions; axis++) {
for (const parsed of parsedOutputRange) {
const unit = parsed.units[axis];
if (unit === null) {
continue;
}
if (units[axis] === null) {
units[axis] = unit;
continue;
}
if (units[axis] !== unit) {
throw new TypeError(`Cannot interpolate ${kind} values with different units on axis ${axis + 1}: ${units[axis]} and ${unit}`);
}
}
if (units[axis] === null) {
throw new TypeError(`Cannot interpolate ${kind} values because axis ${axis + 1} has no unit`);
}
}
}
return serializeStringInterpolationValue({
kind,
values: [0, 0, 0].map((_, axis) => interpolateNumber({
input,
inputRange,
outputRange: parsedOutputRange.map((parsed) => parsed.values[axis]),
options,
})),
units,
dimensions,
});
};
function checkValidInputRange(arr) {
for (let i = 1; i < arr.length; ++i) {
if (!(arr[i] > arr[i - 1])) {
throw new Error(`inputRange must be strictly monotonically increasing but got [${arr.join(',')}]`);
}
}
}
function checkInfiniteRange(name, arr) {
if (arr.length < 1) {
throw new Error(name + ' must have at least 1 element');
}
for (const element of arr) {
if (typeof element !== 'number') {
throw new Error(`${name} must contain only numbers`);
}
if (!Number.isFinite(element)) {
throw new Error(`${name} must contain only finite numbers, but got [${arr.join(',')}]`);
}
}
}
function assertValidInterpolateEasingOption(easing, inputRangeLength) {
if (easing === undefined) {
return;
}
if (typeof easing === 'function') {
return;
}
const expectedLength = inputRangeLength - 1;
if (easing.length !== expectedLength) {
throw new Error(`When easing is an array, it must have one entry per segment between keyframes (length inputRange.length - 1 = ${expectedLength}), but got length ${easing.length}`);
}
for (let i = 0; i < easing.length; i++) {
if (typeof easing[i] !== 'function') {
throw new Error(`easing[${i}] must be a function`);
}
}
}
function assertValidInterpolatePosterizeOption(posterize) {
if (posterize === undefined) {
return;
}
if (typeof posterize !== 'number' ||
!Number.isFinite(posterize) ||
posterize <= 0) {
throw new Error(`posterize must be a positive finite number, but got ${posterize}`);
}
}
function interpolate(input, inputRange, outputRange, options) {
if (typeof input === 'undefined') {
throw new Error('input can not be undefined');
}
if (typeof inputRange === 'undefined') {
throw new Error('inputRange can not be undefined');
}
if (typeof outputRange === 'undefined') {
throw new Error('outputRange can not be undefined');
}
if (inputRange.length !== outputRange.length) {
throw new Error('inputRange (' +
inputRange.length +
') and outputRange (' +
outputRange.length +
') must have the same length');
}
checkInfiniteRange('inputRange', inputRange);
checkValidInputRange(inputRange);
assertValidInterpolateEasingOption(options === null || options === void 0 ? void 0 : options.easing, inputRange.length);
assertValidInterpolatePosterizeOption(options === null || options === void 0 ? void 0 : options.posterize);
if (typeof input !== 'number') {
throw new TypeError('Cannot interpolate an input which is not a number');
}
if (!Array.isArray(outputRange)) {
throw new Error('outputRange must contain only numbers');
}
const hasStringOutput = outputRange.some((output) => typeof output === 'string');
if (hasStringOutput) {
if (!outputRange.every((output) => typeof output === 'string' || typeof output === 'number')) {
throw new TypeError('outputRange must contain only numbers, or supported scale, translate, and rotate strings');
}
return interpolateString({ input, inputRange, outputRange, options });
}
if (!outputRange.every((output) => typeof output === 'number')) {
throw new TypeError('outputRange must contain only numbers, or supported scale, translate, and rotate strings');
}
checkInfiniteRange('outputRange', outputRange);
return interpolateNumber({ input, inputRange, outputRange, options });
}
/* eslint-enable no-redeclare */