UNPKG

remotion

Version:

Make videos programmatically

308 lines (307 loc) • 14.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CanvasImage = exports.canvasImageSchema = 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 interactivity_schema_js_1 = require("../interactivity-schema.js"); const prefetch_js_1 = require("../prefetch.js"); const Sequence_js_1 = require("../Sequence.js"); const SequenceContext_js_1 = require("../SequenceContext.js"); const truncate_src_for_label_js_1 = require("../truncate-src-for-label.js"); const use_buffer_state_js_1 = require("../use-buffer-state.js"); const use_delay_render_js_1 = require("../use-delay-render.js"); const with_interactivity_schema_js_1 = require("../with-interactivity-schema.js"); exports.canvasImageSchema = { ...interactivity_schema_js_1.baseSchema, fit: { type: 'enum', default: 'fill', description: 'Fit', variants: { fill: {}, contain: {}, cover: {}, }, }, ...interactivity_schema_js_1.transformSchema, }; 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, truncate_src_for_label_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, truncate_src_for_label_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, refForOutline, ...canvasProps }, 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 [loadedImage, setLoadedImage] = (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 (refForOutline) { refForOutline.current = canvas; } if (typeof ref === 'function') { ref(canvas); } else if (ref) { ref.current = canvas; } }, [ref, refForOutline]); (0, react_1.useEffect)(() => { 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, truncate_src_for_label_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; setLoadedImage(null); const continueRenderOnce = () => { if (continued) { return; } continued = true; unblock(); continueRender(handle); }; const attemptLoad = () => { loadImage({ src: actualSrc, signal: controller.signal }) .then((image) => { if (cancelled) { return; } setLoadedImage(image); }) .then(() => { if (!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, truncate_src_for_label_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, continueRender, delayPlayback, delayRender, delayRenderRetries, delayRenderTimeoutInMilliseconds, maxRetries, onError, pauseWhenLoading, sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.postmounting, sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.premounting, ]); (0, react_1.useEffect)(() => { if (!loadedImage || !outputCanvas || !sourceCanvas) { return; } const handle = delayRender(`Applying effects to <CanvasImage> with src="${(0, truncate_src_for_label_js_1.truncateSrcForLabel)(actualSrc)}"`); let cancelled = false; let continued = false; const continueRenderOnce = () => { if (continued) { return; } continued = true; continueRender(handle); }; const canvasWidth = width !== null && width !== void 0 ? width : loadedImage.width; const canvasHeight = height !== null && height !== void 0 ? height : loadedImage.height; const sourceContext = sourceCanvas.getContext('2d', { colorSpace: 'srgb', }); if (!sourceContext) { cancelRender(new Error('Could not get 2D context for <CanvasImage> source canvas')); continueRenderOnce(); return () => { continueRenderOnce(); }; } sourceCanvas.width = canvasWidth; sourceCanvas.height = canvasHeight; outputCanvas.width = canvasWidth; outputCanvas.height = canvasHeight; sourceContext.clearRect(0, 0, canvasWidth, canvasHeight); sourceContext.drawImage(loadedImage.element, ...(0, calculate_image_fit_js_1.calculateImageFit)(fit, { width: loadedImage.width, height: loadedImage.height }, { width: canvasWidth, height: canvasHeight })); (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 (cancelled) { return; } if (onError) { onError(err); continueRenderOnce(); } else { cancelRender(err); } }); return () => { cancelled = true; continueRenderOnce(); }; }, [ actualSrc, cancelRender, chainState, continueRender, delayRender, fit, height, loadedImage, memoizedEffects, onError, outputCanvas, sourceCanvas, width, ]); return (jsx_runtime_1.jsx("canvas", { ...canvasProps, 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, trimBefore, freeze, hidden, name, showInTimeline, stack, controls, _remotionInternalDocumentationLink, outlineRef, ...canvasProps }, ref) => { if (!src) { throw new Error('No "src" prop was passed to <CanvasImage>.'); } const memoizedEffectDefinitions = (0, use_memoized_effects_js_1.useMemoizedEffectDefinitions)(effects); const actualRef = (0, react_1.useRef)(null); (0, react_1.useImperativeHandle)(ref, () => { return actualRef.current; }, []); return (jsx_runtime_1.jsx(Sequence_js_1.Sequence, { layout: "none", from: from !== null && from !== void 0 ? from : 0, trimBefore: trimBefore, durationInFrames: durationInFrames !== null && durationInFrames !== void 0 ? durationInFrames : Infinity, freeze: freeze, hidden: hidden, showInTimeline: showInTimeline !== null && showInTimeline !== void 0 ? showInTimeline : true, name: name !== null && name !== void 0 ? name : '<CanvasImage>', _remotionInternalDocumentationLink: _remotionInternalDocumentationLink !== null && _remotionInternalDocumentationLink !== void 0 ? _remotionInternalDocumentationLink : 'https://www.remotion.dev/docs/canvasimage', controls: controls, _remotionInternalEffects: memoizedEffectDefinitions, _remotionInternalIsMedia: { type: 'image', src }, _remotionInternalStack: stack, outlineRef: outlineRef !== null && outlineRef !== void 0 ? outlineRef : actualRef, children: jsx_runtime_1.jsx(CanvasImageContent, { ref: actualRef, 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, refForOutline: outlineRef !== null && outlineRef !== void 0 ? outlineRef : null, ...canvasProps }) })); }); /* * @description Renders a static image to a `<canvas>` and applies Remotion effects. * @see [Documentation](https://www.remotion.dev/docs/canvasimage) */ exports.CanvasImage = (0, with_interactivity_schema_js_1.withInteractivitySchema)({ Component: CanvasImageInner, componentName: '<CanvasImage>', componentIdentity: 'dev.remotion.remotion.CanvasImage', schema: exports.canvasImageSchema, supportsEffects: true, }); exports.CanvasImage.displayName = 'CanvasImage'; (0, enable_sequence_stack_traces_js_1.addSequenceStackTraces)(exports.CanvasImage);