UNPKG

remotion

Version:

Make videos programmatically

352 lines (351 loc) • 13.2 kB
"use strict"; 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 */