UNPKG

remotion

Version:

Make videos programmatically

248 lines (247 loc) • 11.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CanvasImage = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = require("react"); const calculate_image_fit_js_1 = require("../calculate-image-fit.js"); const run_effect_chain_js_1 = require("../effects/run-effect-chain.js"); const use_effect_chain_state_js_1 = require("../effects/use-effect-chain-state.js"); const use_memoized_effects_js_1 = require("../effects/use-memoized-effects.js"); const enable_sequence_stack_traces_js_1 = require("../enable-sequence-stack-traces.js"); const Img_js_1 = require("../Img.js"); const prefetch_js_1 = require("../prefetch.js"); const sequence_field_schema_js_1 = require("../sequence-field-schema.js"); const Sequence_js_1 = require("../Sequence.js"); const SequenceContext_js_1 = require("../SequenceContext.js"); const use_buffer_state_js_1 = require("../use-buffer-state.js"); const use_delay_render_js_1 = require("../use-delay-render.js"); const wrap_in_schema_js_1 = require("../wrap-in-schema.js"); const canvasImageSchema = { fit: { type: 'enum', default: 'fill', description: 'Fit', variants: { fill: {}, contain: {}, cover: {}, }, }, ...sequence_field_schema_js_1.sequenceVisualStyleSchema, hidden: sequence_field_schema_js_1.hiddenField, }; const makeAbortError = () => { if (typeof DOMException !== 'undefined') { return new DOMException('Image loading was aborted', 'AbortError'); } const error = new Error('Image loading was aborted'); error.name = 'AbortError'; return error; }; const loadImage = ({ src, signal, }) => { return new Promise((resolve, reject) => { const image = new Image(); let settled = false; function cleanup() { image.onload = null; image.onerror = null; } function settle(callback) { if (settled) { return; } settled = true; cleanup(); callback(); } function onAbort() { settle(() => reject(makeAbortError())); } image.onload = () => { var _a; Promise.resolve((_a = image.decode) === null || _a === void 0 ? void 0 : _a.call(image)) .catch(() => undefined) .then(() => { const imageWidth = image.naturalWidth || image.width; const imageHeight = image.naturalHeight || image.height; if (imageWidth <= 0 || imageHeight <= 0) { settle(() => reject(new Error(`Could not determine dimensions for <CanvasImage> with src="${(0, Img_js_1.truncateSrcForLabel)(src)}"`))); return; } settle(() => resolve({ element: image, width: imageWidth, height: imageHeight })); }); }; image.onerror = () => { settle(() => reject(new Error(`Could not load <CanvasImage> with src="${(0, Img_js_1.truncateSrcForLabel)(src)}"`))); }; signal.addEventListener('abort', onAbort, { once: true }); if (signal.aborted) { onAbort(); return; } image.crossOrigin = 'anonymous'; image.src = src; }); }; function exponentialBackoff(errorCount) { return 1000 * 2 ** (errorCount - 1); } const CanvasImageContent = (0, react_1.forwardRef)(({ src, width, height, fit = 'fill', effects, controls, onError, className, style, id, pauseWhenLoading, maxRetries = 2, delayRenderRetries, delayRenderTimeoutInMilliseconds, }, ref) => { var _a; const { delayRender, continueRender, cancelRender } = (0, use_delay_render_js_1.useDelayRender)(); const { delayPlayback } = (0, use_buffer_state_js_1.useBufferState)(); const [outputCanvas, setOutputCanvas] = (0, react_1.useState)(null); const actualSrc = (0, prefetch_js_1.usePreload)(src); const chainState = (0, use_effect_chain_state_js_1.useEffectChainState)(); const memoizedEffects = (0, use_memoized_effects_js_1.useMemoizedEffects)({ effects, overrideId: (_a = controls === null || controls === void 0 ? void 0 : controls.overrideId) !== null && _a !== void 0 ? _a : null, }); const sequenceContext = (0, react_1.useContext)(SequenceContext_js_1.SequenceContext); const sourceCanvas = (0, react_1.useMemo)(() => { if (typeof document === 'undefined') { return null; } return document.createElement('canvas'); }, []); const canvasRef = (0, react_1.useCallback)((canvas) => { setOutputCanvas(canvas); if (typeof ref === 'function') { ref(canvas); } else if (ref) { ref.current = canvas; } }, [ref]); (0, react_1.useEffect)(() => { if (!outputCanvas || !sourceCanvas) { return; } const isPremounting = Boolean(sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.premounting); const isPostmounting = Boolean(sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.postmounting); const handle = delayRender(`Rendering <CanvasImage> with src="${(0, Img_js_1.truncateSrcForLabel)(actualSrc)}"`, { retries: delayRenderRetries !== null && delayRenderRetries !== void 0 ? delayRenderRetries : undefined, timeoutInMilliseconds: delayRenderTimeoutInMilliseconds !== null && delayRenderTimeoutInMilliseconds !== void 0 ? delayRenderTimeoutInMilliseconds : undefined, }); const unblock = pauseWhenLoading && !isPremounting && !isPostmounting ? delayPlayback().unblock : () => undefined; const controller = new AbortController(); let cancelled = false; let continued = false; let errorCount = 0; let timeoutId = null; const continueRenderOnce = () => { if (continued) { return; } continued = true; unblock(); continueRender(handle); }; const attemptLoad = () => { loadImage({ src: actualSrc, signal: controller.signal }) .then((image) => { if (cancelled) { return; } const canvasWidth = width !== null && width !== void 0 ? width : image.width; const canvasHeight = height !== null && height !== void 0 ? height : image.height; const sourceContext = sourceCanvas.getContext('2d', { colorSpace: 'srgb', }); if (!sourceContext) { throw new Error('Could not get 2D context for <CanvasImage> source canvas'); } sourceCanvas.width = canvasWidth; sourceCanvas.height = canvasHeight; outputCanvas.width = canvasWidth; outputCanvas.height = canvasHeight; sourceContext.clearRect(0, 0, canvasWidth, canvasHeight); sourceContext.drawImage(image.element, ...(0, calculate_image_fit_js_1.calculateImageFit)(fit, { width: image.width, height: image.height }, { width: canvasWidth, height: canvasHeight })); return (0, run_effect_chain_js_1.runEffectChain)({ state: chainState.get(canvasWidth, canvasHeight), source: sourceCanvas, effects: memoizedEffects, output: outputCanvas, width: canvasWidth, height: canvasHeight, }); }) .then((completed) => { if (completed && !cancelled) { continueRenderOnce(); } }) .catch((err) => { if (err.name === 'AbortError') { continueRenderOnce(); return; } errorCount++; if (errorCount <= maxRetries) { const backoff = exponentialBackoff(errorCount); // eslint-disable-next-line no-console console.warn(`Could not load <CanvasImage> with src="${(0, Img_js_1.truncateSrcForLabel)(actualSrc)}", retrying in ${backoff}ms`); timeoutId = setTimeout(() => { if (!cancelled) { attemptLoad(); } }, backoff); } else if (onError) { onError(err); continueRenderOnce(); } else { cancelRender(err); } }); }; attemptLoad(); return () => { cancelled = true; if (timeoutId !== null) { clearTimeout(timeoutId); } controller.abort(); continueRenderOnce(); }; }, [ actualSrc, cancelRender, chainState, continueRender, delayPlayback, delayRender, delayRenderRetries, delayRenderTimeoutInMilliseconds, fit, height, maxRetries, memoizedEffects, onError, outputCanvas, pauseWhenLoading, sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.postmounting, sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.premounting, sourceCanvas, width, ]); return ((0, jsx_runtime_1.jsx)("canvas", { ref: canvasRef, width: width, height: height, className: className, style: style, id: id })); }); CanvasImageContent.displayName = 'CanvasImageContent'; const CanvasImageInner = (0, react_1.forwardRef)(({ src, width, height, fit, effects = [], className, style, id, onError, pauseWhenLoading, maxRetries, delayRenderRetries, delayRenderTimeoutInMilliseconds, durationInFrames, from, hidden, name, showInTimeline, stack, _experimentalControls: controls, }, ref) => { if (!src) { throw new Error('No "src" prop was passed to <CanvasImage>.'); } const memoizedEffectDefinitions = (0, use_memoized_effects_js_1.useMemoizedEffectDefinitions)(effects); return ((0, jsx_runtime_1.jsx)(Sequence_js_1.Sequence, { layout: "none", from: from !== null && from !== void 0 ? from : 0, durationInFrames: durationInFrames !== null && durationInFrames !== void 0 ? durationInFrames : Infinity, hidden: hidden, showInTimeline: showInTimeline !== null && showInTimeline !== void 0 ? showInTimeline : true, name: name !== null && name !== void 0 ? name : '<CanvasImage>', _experimentalControls: controls, _remotionInternalEffects: memoizedEffectDefinitions, _remotionInternalIsMedia: { type: 'image', src }, _remotionInternalStack: stack, children: (0, jsx_runtime_1.jsx)(CanvasImageContent, { ref: ref, src: src, width: width, height: height, fit: fit, effects: effects, controls: controls, className: className, style: style, id: id, onError: onError, pauseWhenLoading: pauseWhenLoading, maxRetries: maxRetries, delayRenderRetries: delayRenderRetries, delayRenderTimeoutInMilliseconds: delayRenderTimeoutInMilliseconds }) })); }); /* * @description Renders a static image to a `<canvas>` and applies Remotion effects. * @see [Documentation](https://www.remotion.dev/docs/canvasimage) */ exports.CanvasImage = (0, wrap_in_schema_js_1.wrapInSchema)(CanvasImageInner, canvasImageSchema); exports.CanvasImage.displayName = 'CanvasImage'; (0, enable_sequence_stack_traces_js_1.addSequenceStackTraces)(exports.CanvasImage);