var __dispose = Symbol.dispose || /* @__PURE__ */ Symbol.for("Symbol.dispose"); var __asyncDispose = Symbol.asyncDispose || /* @__PURE__ */ Symbol.for("Symbol.asyncDispose"); var __using = (stack, value, async) => { if (value != null) { if (typeof value !== "object" && typeof value !== "function") throw TypeError('Object expected to be assigned to "using" declaration'); var dispose; if (async) dispose = value[__asyncDispose]; if (dispose === undefined) dispose = value[__dispose]; if (typeof dispose !== "function") throw TypeError("Object not disposable"); stack.push([async, dispose, value]); } else if (async) { stack.push([async]); } return value; }; var __callDispose = (stack, error, hasError) => { var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) { return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _; }, fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e), next = (it) => { while (it = stack.pop()) { try { var result = it[1] && it[1].call(it[2]); if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next())); } catch (e) { fail(e); } } if (hasError) throw error; }; return next(); }; // src/can-render-media-on-web.ts import { canEncodeVideo } from "mediabunny"; // src/can-use-webfs-target.ts var canUseWebFsWriter = async () => { if (!("storage" in navigator)) { return false; } if (!("getDirectory" in navigator.storage)) { return false; } try { const directoryHandle = await navigator.storage.getDirectory(); const fileHandle = await directoryHandle.getFileHandle("remotion-probe-web-fs-support", { create: true }); const canUse = fileHandle.createWritable !== undefined; return canUse; } catch { return false; } }; // src/check-webgl-support.ts var checkWebGLSupport = () => { try { const canvas = new OffscreenCanvas(1, 1); const gl = canvas.getContext("webgl2") || canvas.getContext("webgl"); if (!gl) { return { type: "webgl-unsupported", message: "WebGL is not supported. 3D CSS transforms will fail.", severity: "error" }; } return null; } catch { return { type: "webgl-unsupported", message: "WebGL is not supported. 3D CSS transforms will fail.", severity: "error" }; } }; // src/mediabunny-mappings.ts import { Mp4OutputFormat, QUALITY_HIGH, QUALITY_LOW, QUALITY_MEDIUM, QUALITY_VERY_HIGH, QUALITY_VERY_LOW, WebMOutputFormat } from "mediabunny"; var codecToMediabunnyCodec = (codec) => { switch (codec) { case "h264": return "avc"; case "h265": return "hevc"; case "vp8": return "vp8"; case "vp9": return "vp9"; case "av1": return "av1"; default: throw new Error(`Unsupported codec: ${codec}`); } }; var containerToMediabunnyContainer = (container) => { switch (container) { case "mp4": return new Mp4OutputFormat; case "webm": return new WebMOutputFormat; default: throw new Error(`Unsupported container: ${container}`); } }; var getDefaultVideoCodecForContainer = (container) => { switch (container) { case "mp4": return "h264"; case "webm": return "vp8"; default: throw new Error(`Unsupported container: ${container}`); } }; var getQualityForWebRendererQuality = (quality) => { switch (quality) { case "very-low": return QUALITY_VERY_LOW; case "low": return QUALITY_LOW; case "medium": return QUALITY_MEDIUM; case "high": return QUALITY_HIGH; case "very-high": return QUALITY_VERY_HIGH; default: throw new Error(`Unsupported quality: ${quality}`); } }; var getMimeType = (container) => { switch (container) { case "mp4": return "video/mp4"; case "webm": return "video/webm"; default: throw new Error(`Unsupported container: ${container}`); } }; var getDefaultAudioCodecForContainer = (container) => { switch (container) { case "mp4": return "aac"; case "webm": return "opus"; default: throw new Error(`Unsupported container: ${container}`); } }; var WEB_RENDERER_VIDEO_CODECS = [ "h264", "h265", "vp8", "vp9", "av1" ]; var getSupportedVideoCodecsForContainer = (container) => { const format = containerToMediabunnyContainer(container); const allSupported = format.getSupportedVideoCodecs(); return WEB_RENDERER_VIDEO_CODECS.filter((codec) => allSupported.includes(codecToMediabunnyCodec(codec))); }; var WEB_RENDERER_AUDIO_CODECS = ["aac", "opus"]; var getSupportedAudioCodecsForContainer = (container) => { const format = containerToMediabunnyContainer(container); const allSupported = format.getSupportedAudioCodecs(); return WEB_RENDERER_AUDIO_CODECS.filter((codec) => allSupported.includes(codec)); }; var audioCodecToMediabunnyAudioCodec = (audioCodec) => { return audioCodec; }; // src/resolve-audio-codec.ts import { canEncodeAudio } from "mediabunny"; var resolveAudioCodec = async (options) => { const issues = []; const { container, requestedCodec, userSpecifiedAudioCodec, bitrate } = options; const audioCodec = requestedCodec ?? getDefaultAudioCodecForContainer(container); const supportedAudioCodecs = getSupportedAudioCodecsForContainer(container); if (!supportedAudioCodecs.includes(audioCodec)) { issues.push({ type: "audio-codec-unsupported", message: `Audio codec "${audioCodec}" is not supported for container "${container}". Supported: ${supportedAudioCodecs.join(", ")}`, severity: "error" }); return { codec: null, issues }; } const mediabunnyAudioCodec = audioCodecToMediabunnyAudioCodec(audioCodec); const canEncode = await canEncodeAudio(mediabunnyAudioCodec, { bitrate }); if (canEncode) { return { codec: audioCodec, issues }; } if (userSpecifiedAudioCodec) { issues.push({ type: "audio-codec-unsupported", message: `Audio codec "${audioCodec}" cannot be encoded by this browser. This is common for AAC on Firefox. Try using "opus" instead.`, severity: "error" }); return { codec: null, issues }; } for (const fallbackCodec of supportedAudioCodecs) { if (fallbackCodec !== audioCodec) { const fallbackMediabunnyCodec = audioCodecToMediabunnyAudioCodec(fallbackCodec); const canEncodeFallback = await canEncodeAudio(fallbackMediabunnyCodec, { bitrate }); if (canEncodeFallback) { issues.push({ type: "audio-codec-unsupported", message: `Falling back from audio codec "${audioCodec}" to "${fallbackCodec}" because the original codec cannot be encoded by this browser.`, severity: "warning" }); return { codec: fallbackCodec, issues }; } } } issues.push({ type: "audio-codec-unsupported", message: `No audio codec can be encoded by this browser for container "${container}".`, severity: "error" }); return { codec: null, issues }; }; // src/validate-dimensions.ts var validateDimensions = (options) => { const { width, height, codec } = options; if (codec === "h264" || codec === "h265") { if (width % 2 !== 0 || height % 2 !== 0) { return { type: "invalid-dimensions", message: `${codec.toUpperCase()} codec requires width and height to be multiples of 2. Got ${width}x${height}`, severity: "error" }; } } return null; }; // src/can-render-media-on-web.ts var canRenderMediaOnWeb = async (options) => { const issues = []; if (typeof VideoEncoder === "undefined") { issues.push({ type: "webcodecs-unavailable", message: "WebCodecs API is not available in this browser. A modern browser with WebCodecs support is required.", severity: "error" }); } const container = options.container ?? "mp4"; const videoCodec = options.videoCodec ?? getDefaultVideoCodecForContainer(container); const transparent = options.transparent ?? false; const muted = options.muted ?? false; const { width, height } = options; const resolvedVideoBitrate = typeof options.videoBitrate === "number" ? options.videoBitrate : getQualityForWebRendererQuality(options.videoBitrate ?? "medium"); const resolvedAudioBitrate = typeof options.audioBitrate === "number" ? options.audioBitrate : getQualityForWebRendererQuality(options.audioBitrate ?? "medium"); const format = containerToMediabunnyContainer(container); if (!format.getSupportedCodecs().includes(codecToMediabunnyCodec(videoCodec))) { issues.push({ type: "container-codec-mismatch", message: `Codec ${videoCodec} is not supported for container ${container}`, severity: "error" }); } const dimensionIssue = validateDimensions({ width, height, codec: videoCodec }); if (dimensionIssue) { issues.push(dimensionIssue); } const canEncodeVideoResult = await canEncodeVideo(codecToMediabunnyCodec(videoCodec), { bitrate: resolvedVideoBitrate }); if (!canEncodeVideoResult) { issues.push({ type: "video-codec-unsupported", message: `Video codec "${videoCodec}" cannot be encoded by this browser`, severity: "error" }); } if (transparent && !["vp8", "vp9"].includes(videoCodec)) { issues.push({ type: "transparent-video-unsupported", message: `Transparent video requires VP8 or VP9 codec with WebM container. ${videoCodec} does not support alpha channel.`, severity: "error" }); } let resolvedAudioCodec = null; if (!muted) { const audioResult = await resolveAudioCodec({ container, requestedCodec: options.audioCodec, userSpecifiedAudioCodec: options.audioCodec !== undefined && options.audioCodec !== null, bitrate: resolvedAudioBitrate }); resolvedAudioCodec = audioResult.codec; issues.push(...audioResult.issues); } const webglIssue = checkWebGLSupport(); if (webglIssue) { issues.push(webglIssue); } const canUseWebFs = await canUseWebFsWriter(); let resolvedOutputTarget; if (options.outputTarget === "web-fs") { if (!canUseWebFs) { issues.push({ type: "output-target-unsupported", message: 'The "web-fs" output target is not supported in this browser. The File System Access API is required.', severity: "error" }); } resolvedOutputTarget = "web-fs"; } else if (options.outputTarget === "arraybuffer") { resolvedOutputTarget = "arraybuffer"; } else { resolvedOutputTarget = canUseWebFs ? "web-fs" : "arraybuffer"; } return { canRender: issues.filter((i) => i.severity === "error").length === 0, issues, resolvedVideoCodec: videoCodec, resolvedAudioCodec, resolvedOutputTarget }; }; // src/get-encodable-codecs.ts import { getEncodableAudioCodecs as mediabunnyGetEncodableAudioCodecs, getEncodableVideoCodecs as mediabunnyGetEncodableVideoCodecs } from "mediabunny"; var getEncodableVideoCodecs = async (container, options) => { const supported = getSupportedVideoCodecsForContainer(container); const mediabunnyCodecs = supported.map(codecToMediabunnyCodec); const resolvedBitrate = options?.videoBitrate ? typeof options.videoBitrate === "number" ? options.videoBitrate : getQualityForWebRendererQuality(options.videoBitrate) : undefined; const encodable = await mediabunnyGetEncodableVideoCodecs(mediabunnyCodecs, { bitrate: resolvedBitrate }); return supported.filter((c) => encodable.includes(codecToMediabunnyCodec(c))); }; var getEncodableAudioCodecs = async (container, options) => { const supported = getSupportedAudioCodecsForContainer(container); const resolvedBitrate = options?.audioBitrate ? typeof options.audioBitrate === "number" ? options.audioBitrate : getQualityForWebRendererQuality(options.audioBitrate) : undefined; const encodable = await mediabunnyGetEncodableAudioCodecs(supported, { bitrate: resolvedBitrate }); return supported.filter((c) => encodable.includes(c)); }; // src/render-media-on-web.tsx import { BufferTarget, StreamTarget } from "mediabunny"; import { Internals as Internals8 } from "remotion"; // src/add-sample.ts import { AudioSample, VideoSample } from "mediabunny"; var addVideoSampleAndCloseFrame = async (frameToEncode, videoSampleSource) => { const sample = new VideoSample(frameToEncode); try { await videoSampleSource.add(sample); } finally { sample.close(); frameToEncode.close(); } }; var addAudioSample = async (audio, audioSampleSource) => { const sample = new AudioSample(audio); try { await audioSampleSource.add(sample); } finally { sample.close(); } }; // src/artifact.ts import { NoReactInternals } from "remotion/no-react"; var onlyArtifact = async ({ assets, frameBuffer }) => { const artifacts = assets.filter((asset) => asset.type === "artifact"); let frameBufferUint8 = null; const result = []; for (const artifact of artifacts) { if (artifact.contentType === "binary" || artifact.contentType === "text") { result.push({ frame: artifact.frame, content: artifact.content, filename: artifact.filename, downloadBehavior: artifact.downloadBehavior }); continue; } if (artifact.contentType === "thumbnail") { if (frameBuffer === null) { continue; } const ab = frameBuffer instanceof Blob ? await frameBuffer.arrayBuffer() : new Uint8Array(await (await frameBuffer.convertToBlob({ type: "image/png" })).arrayBuffer()); frameBufferUint8 = new Uint8Array(ab); result.push({ frame: artifact.frame, content: frameBufferUint8, filename: artifact.filename, downloadBehavior: artifact.downloadBehavior }); continue; } throw new Error("Unknown artifact type: " + artifact); } return result.filter(NoReactInternals.truthy); }; var handleArtifacts = () => { const previousArtifacts = []; const handle = async ({ imageData, frame, assets: artifactAssets, onArtifact }) => { const artifacts = await onlyArtifact({ assets: artifactAssets, frameBuffer: imageData }); for (const artifact of artifacts) { const previousArtifact = previousArtifacts.find((a) => a.filename === artifact.filename); if (previousArtifact) { throw new Error(`An artifact with output "${artifact.filename}" was already registered at frame ${previousArtifact.frame}, but now registered again at frame ${frame}. Artifacts must have unique names. https://remotion.dev/docs/artifacts`); } onArtifact(artifact); previousArtifacts.push({ frame, filename: artifact.filename }); } }; return { handle }; }; // src/audio.ts var TARGET_NUMBER_OF_CHANNELS = 2; var TARGET_SAMPLE_RATE = 48000; function mixAudio(waves, length) { if (waves.length === 1 && waves[0].length === length) { return waves[0]; } const mixed = new Int16Array(length); if (waves.length === 1) { mixed.set(waves[0].subarray(0, length)); return mixed; } for (let i = 0;i < length; i++) { const sum = waves.reduce((acc, wave) => { return acc + (wave[i] ?? 0); }, 0); mixed[i] = Math.max(-32768, Math.min(32767, sum)); } return mixed; } var onlyInlineAudio = ({ assets, fps, timestamp }) => { const inlineAudio = assets.filter((asset) => asset.type === "inline-audio"); if (inlineAudio.length === 0) { return null; } const expectedLength = Math.round(TARGET_NUMBER_OF_CHANNELS * TARGET_SAMPLE_RATE / fps); for (const asset of inlineAudio) { if (asset.toneFrequency !== 1) { throw new Error("Setting the toneFrequency is not supported yet in web rendering."); } } const mixedAudio = mixAudio(inlineAudio.map((asset) => asset.audio), expectedLength); return new AudioData({ data: mixedAudio, format: "s16", numberOfChannels: TARGET_NUMBER_OF_CHANNELS, numberOfFrames: expectedLength / TARGET_NUMBER_OF_CHANNELS, sampleRate: TARGET_SAMPLE_RATE, timestamp }); }; // src/background-keepalive.ts import { Internals } from "remotion"; var WORKER_CODE = ` let intervalId = null; self.onmessage = (e) => { if (e.data.type === 'start') { if (intervalId !== null) { clearInterval(intervalId); } intervalId = setInterval(() => self.postMessage('tick'), e.data.intervalMs); } else if (e.data.type === 'stop') { if (intervalId !== null) { clearInterval(intervalId); intervalId = null; } } }; `; function createBackgroundKeepalive({ fps, logLevel }) { const intervalMs = Math.round(1000 / fps); let pendingResolvers = []; let worker = null; let disposed = false; if (typeof Worker === "undefined") { Internals.Log.warn({ logLevel, tag: "@remotion/web-renderer" }, "Web Workers not available. Rendering may pause when tab is backgrounded."); return { waitForTick: () => { return new Promise((resolve) => { setTimeout(resolve, intervalMs); }); }, [Symbol.dispose]: () => {} }; } const blob = new Blob([WORKER_CODE], { type: "application/javascript" }); const workerUrl = URL.createObjectURL(blob); worker = new Worker(workerUrl); worker.onmessage = () => { const resolvers = pendingResolvers; pendingResolvers = []; for (const resolve of resolvers) { resolve(); } }; worker.onerror = (event) => { Internals.Log.error({ logLevel, tag: "@remotion/web-renderer" }, "Background keepalive worker encountered an error and will be terminated.", event); const resolvers = pendingResolvers; pendingResolvers = []; for (const resolve of resolvers) { resolve(); } if (!disposed) { disposed = true; worker?.terminate(); worker = null; URL.revokeObjectURL(workerUrl); } }; worker.postMessage({ type: "start", intervalMs }); return { waitForTick: () => { return new Promise((resolve) => { pendingResolvers.push(resolve); }); }, [Symbol.dispose]: () => { if (disposed) { return; } disposed = true; worker?.postMessage({ type: "stop" }); worker?.terminate(); worker = null; URL.revokeObjectURL(workerUrl); const resolvers = pendingResolvers; pendingResolvers = []; for (const resolve of resolvers) { resolve(); } } }; } // src/create-audio-sample-source.ts import { AudioSampleSource } from "mediabunny"; var createAudioSampleSource = ({ muted, codec, bitrate }) => { if (muted || codec === null) { return null; } const audioSampleSource = new AudioSampleSource({ codec, bitrate }); return { audioSampleSource, [Symbol.dispose]: () => audioSampleSource.close() }; }; // src/create-scaffold.tsx import { createRef } from "react"; import { flushSync as flushSync2 } from "react-dom"; import ReactDOM from "react-dom/client"; import { Internals as Internals3 } from "remotion"; // src/update-time.tsx import { useImperativeHandle, useState } from "react"; import { flushSync } from "react-dom"; import { Internals as Internals2 } from "remotion"; import { jsx } from "react/jsx-runtime"; var UpdateTime = ({ children, audioEnabled, videoEnabled, logLevel, compId, initialFrame, timeUpdater }) => { const [frame, setFrame] = useState(initialFrame); useImperativeHandle(timeUpdater, () => ({ update: (f) => { flushSync(() => { setFrame(f); }); } })); return /* @__PURE__ */ jsx(Internals2.RemotionRootContexts, { audioEnabled, videoEnabled, logLevel, numberOfAudioTags: 0, audioLatencyHint: "interactive", frameState: { [compId]: frame }, nonceContextSeed: 0, children }); }; // src/create-scaffold.tsx import { jsx as jsx2 } from "react/jsx-runtime"; function checkForError(errorHolder) { if (errorHolder.error) { throw errorHolder.error; } } function createScaffold({ width, height, delayRenderTimeoutInMilliseconds, logLevel, resolvedProps, id, mediaCacheSizeInBytes, durationInFrames, fps, initialFrame, schema, Component, audioEnabled, videoEnabled, defaultCodec, defaultOutName }) { if (!ReactDOM.createRoot) { throw new Error("@remotion/web-renderer requires React 18 or higher"); } const div = document.createElement("div"); div.style.position = "fixed"; div.style.display = "flex"; div.style.flexDirection = "column"; div.style.backgroundColor = "transparent"; div.style.width = `${width}px`; div.style.height = `${height}px`; div.style.zIndex = "-9999"; div.style.top = "0"; div.style.left = "0"; div.style.right = "0"; div.style.bottom = "0"; div.style.visibility = "hidden"; div.style.pointerEvents = "none"; const scaffoldClassName = `remotion-scaffold-${Math.random().toString(36).substring(2, 15)}`; div.className = scaffoldClassName; const cleanupCSS = Internals3.CSSUtils.injectCSS(Internals3.CSSUtils.makeDefaultPreviewCSS(`.${scaffoldClassName}`, "white")); document.body.appendChild(div); const errorHolder = { error: null }; const root = ReactDOM.createRoot(div, { onUncaughtError: (err) => { errorHolder.error = err instanceof Error ? err : new Error(String(err)); } }); const delayRenderScope = { remotion_renderReady: true, remotion_delayRenderTimeouts: {}, remotion_puppeteerTimeout: delayRenderTimeoutInMilliseconds, remotion_attempt: 0, remotion_delayRenderHandles: [] }; const timeUpdater = createRef(); const collectAssets = createRef(); flushSync2(() => { root.render(/* @__PURE__ */ jsx2(Internals3.MaxMediaCacheSizeContext.Provider, { value: mediaCacheSizeInBytes, children: /* @__PURE__ */ jsx2(Internals3.RemotionEnvironmentContext.Provider, { value: { isStudio: false, isRendering: true, isPlayer: false, isReadOnlyStudio: false, isClientSideRendering: true }, children: /* @__PURE__ */ jsx2(Internals3.DelayRenderContextType.Provider, { value: delayRenderScope, children: /* @__PURE__ */ jsx2(Internals3.CompositionManager.Provider, { value: { compositions: [ { id, component: Component, nonce: 0, defaultProps: {}, folderName: null, parentFolderName: null, schema: schema ?? null, calculateMetadata: null, durationInFrames, fps, height, width } ], canvasContent: { type: "composition", compositionId: id }, currentCompositionMetadata: { props: resolvedProps, durationInFrames, fps, height, width, defaultCodec: defaultCodec ?? null, defaultOutName: defaultOutName ?? null, defaultVideoImageFormat: null, defaultPixelFormat: null, defaultProResProfile: null }, folders: [] }, children: /* @__PURE__ */ jsx2(Internals3.RenderAssetManagerProvider, { collectAssets, children: /* @__PURE__ */ jsx2(UpdateTime, { audioEnabled, videoEnabled, logLevel, compId: id, initialFrame, timeUpdater, children: /* @__PURE__ */ jsx2(Internals3.CanUseRemotionHooks.Provider, { value: true, children: /* @__PURE__ */ jsx2(Component, { ...resolvedProps }) }) }) }) }) }) }) })); }); return { delayRenderScope, div, errorHolder, [Symbol.dispose]: () => { root.unmount(); div.remove(); cleanupCSS(); }, timeUpdater, collectAssets }; } // src/frame-range.ts var getRealFrameRange = (durationInFrames, frameRange) => { if (frameRange === null) { return [0, durationInFrames - 1]; } if (typeof frameRange === "number") { if (frameRange < 0 || frameRange >= durationInFrames) { throw new Error(`Frame number is out of range, must be between 0 and ${durationInFrames - 1} but got ${frameRange}`); } return [frameRange, frameRange]; } if (frameRange[1] >= durationInFrames || frameRange[0] < 0) { throw new Error(`The "durationInFrames" of the composition was evaluated to be ${durationInFrames}, but frame range ${frameRange.join("-")} is not inbetween 0-${durationInFrames - 1}`); } return frameRange; }; // src/internal-state.ts var makeInternalState = () => { let drawnPrecomposedPixels = 0; let precomposedTextures = 0; let waitForReadyTime = 0; let addSampleTime = 0; let createFrameTime = 0; let audioMixingTime = 0; const helperCanvasState = { current: null }; return { getDrawn3dPixels: () => drawnPrecomposedPixels, getPrecomposedTiles: () => precomposedTextures, addPrecompose: ({ canvasWidth, canvasHeight }) => { drawnPrecomposedPixels += canvasWidth * canvasHeight; precomposedTextures++; }, helperCanvasState, [Symbol.dispose]: () => { if (helperCanvasState.current) { helperCanvasState.current.cleanup(); } }, getWaitForReadyTime: () => waitForReadyTime, addWaitForReadyTime: (time) => { waitForReadyTime += time; }, getAddSampleTime: () => addSampleTime, addAddSampleTime: (time) => { addSampleTime += time; }, getCreateFrameTime: () => createFrameTime, addCreateFrameTime: (time) => { createFrameTime += time; }, getAudioMixingTime: () => audioMixingTime, addAudioMixingTime: (time) => { audioMixingTime += time; } }; }; // src/mediabunny-cleanups.ts import { Output, VideoSampleSource } from "mediabunny"; var makeOutputWithCleanup = (options) => { const output = new Output(options); return { output, [Symbol.dispose]: () => { if (output.state === "finalized" || output.state === "canceled") { return; } output.cancel(); } }; }; var makeVideoSampleSourceCleanup = (encodingConfig) => { const videoSampleSource = new VideoSampleSource(encodingConfig); return { videoSampleSource, [Symbol.dispose]: () => { videoSampleSource.close(); } }; }; // src/render-operations-queue.ts var onlyOneRenderAtATimeQueue = { ref: Promise.resolve() }; // ../licensing/dist/esm/index.mjs function isNetworkError(error) { if (error.message.includes("Failed to fetch") || error.message.includes("Load failed") || error.message.includes("NetworkError when attempting to fetch resource")) { return true; } return false; } var HOST = "https://www.remotion.pro"; var DEFAULT_MAX_RETRIES = 3; var exponentialBackoffMs = (attempt) => { return 1000 * 2 ** (attempt - 1); }; var sleep = (ms) => { return new Promise((resolve) => { setTimeout(resolve, ms); }); }; var internalRegisterUsageEvent = async ({ host, succeeded, event, isStill, isProduction, licenseKey }) => { let lastError; const totalAttempts = DEFAULT_MAX_RETRIES + 1; for (let attempt = 1;attempt <= totalAttempts; attempt++) { const abortController = new AbortController; const timeout = setTimeout(() => { abortController.abort(); }, 1e4); try { const res = await fetch(`${HOST}/api/track/register-usage-point`, { method: "POST", body: JSON.stringify({ event, apiKey: licenseKey, host, succeeded, isStill, isProduction }), headers: { "Content-Type": "application/json" }, signal: abortController.signal }); clearTimeout(timeout); const json = await res.json(); if (json.success) { return { billable: json.billable, classification: json.classification }; } if (!res.ok) { throw new Error(json.error); } throw new Error(`Unexpected response from server: ${JSON.stringify(json)}`); } catch (err) { clearTimeout(timeout); const error = err; const isTimeout = error.name === "AbortError"; const isRetryable = isNetworkError(error) || isTimeout; if (!isRetryable) { throw err; } lastError = isTimeout ? new Error("Request timed out after 10 seconds") : error; if (attempt < totalAttempts) { const backoffMs = exponentialBackoffMs(attempt); console.log(`Failed to send usage event (attempt ${attempt}/${totalAttempts}), retrying in ${backoffMs}ms...`, err); await sleep(backoffMs); } } } throw lastError; }; var LicensingInternals = { internalRegisterUsageEvent }; // src/send-telemetry-event.ts import { Internals as Internals4 } from "remotion"; var sendUsageEvent = async ({ licenseKey, succeeded, apiName, isStill, isProduction }) => { const host = typeof window === "undefined" ? null : typeof window.location === "undefined" ? null : window.location.origin ?? null; if (host === null) { return; } if (licenseKey === null) { Internals4.Log.warn({ logLevel: "warn", tag: "web-renderer" }, `Pass "licenseKey" to ${apiName}(). If you qualify for the Free License (https://remotion.dev/license), pass "free-license" instead.`); } await LicensingInternals.internalRegisterUsageEvent({ licenseKey: licenseKey === "free-license" ? null : licenseKey, event: "webcodec-conversion", host, succeeded, isStill, isProduction }); }; // src/tree-walker-cleanup-after-children.ts var createTreeWalkerCleanupAfterChildren = (treeWalker) => { const cleanupAfterChildren = []; const checkCleanUpAtBeginningOfIteration = () => { for (let i = 0;i < cleanupAfterChildren.length; ) { const cleanup = cleanupAfterChildren[i]; if (!(cleanup.element === treeWalker.currentNode || cleanup.element.contains(treeWalker.currentNode))) { cleanup.cleanupFn(); cleanupAfterChildren.splice(i, 1); } else { i++; } } }; const addCleanup = (element, cleanupFn) => { cleanupAfterChildren.unshift({ element, cleanupFn }); }; const cleanupInTheEndOfTheIteration = () => { for (const cleanup of cleanupAfterChildren) { cleanup.cleanupFn(); } }; return { checkCleanUpAtBeginningOfIteration, addCleanup, [Symbol.dispose]: cleanupInTheEndOfTheIteration }; }; // src/drawing/calculate-object-fit.ts var calculateFill = ({ containerSize, intrinsicSize }) => { return { sourceX: 0, sourceY: 0, sourceWidth: intrinsicSize.width, sourceHeight: intrinsicSize.height, destX: containerSize.left, destY: containerSize.top, destWidth: containerSize.width, destHeight: containerSize.height }; }; var calculateContain = ({ containerSize, intrinsicSize }) => { const containerAspect = containerSize.width / containerSize.height; const imageAspect = intrinsicSize.width / intrinsicSize.height; let destWidth; let destHeight; if (imageAspect > containerAspect) { destWidth = containerSize.width; destHeight = containerSize.width / imageAspect; } else { destHeight = containerSize.height; destWidth = containerSize.height * imageAspect; } const destX = containerSize.left + (containerSize.width - destWidth) / 2; const destY = containerSize.top + (containerSize.height - destHeight) / 2; return { sourceX: 0, sourceY: 0, sourceWidth: intrinsicSize.width, sourceHeight: intrinsicSize.height, destX, destY, destWidth, destHeight }; }; var calculateCover = ({ containerSize, intrinsicSize }) => { if (containerSize.height <= 0 || intrinsicSize.height <= 0) { return { sourceX: 0, sourceY: 0, sourceWidth: 0, sourceHeight: 0, destX: containerSize.left, destY: containerSize.top, destWidth: 0, destHeight: 0 }; } const containerAspect = containerSize.width / containerSize.height; const imageAspect = intrinsicSize.width / intrinsicSize.height; let sourceX = 0; let sourceY = 0; let sourceWidth = intrinsicSize.width; let sourceHeight = intrinsicSize.height; if (imageAspect > containerAspect) { sourceWidth = intrinsicSize.height * containerAspect; sourceX = (intrinsicSize.width - sourceWidth) / 2; } else { sourceHeight = intrinsicSize.width / containerAspect; sourceY = (intrinsicSize.height - sourceHeight) / 2; } return { sourceX, sourceY, sourceWidth, sourceHeight, destX: containerSize.left, destY: containerSize.top, destWidth: containerSize.width, destHeight: containerSize.height }; }; var calculateNone = ({ containerSize, intrinsicSize }) => { const centeredX = containerSize.left + (containerSize.width - intrinsicSize.width) / 2; const centeredY = containerSize.top + (containerSize.height - intrinsicSize.height) / 2; let sourceX = 0; let sourceY = 0; let sourceWidth = intrinsicSize.width; let sourceHeight = intrinsicSize.height; let destX = centeredX; let destY = centeredY; let destWidth = intrinsicSize.width; let destHeight = intrinsicSize.height; if (destX < containerSize.left) { const clipAmount = containerSize.left - destX; sourceX = clipAmount; sourceWidth -= clipAmount; destX = containerSize.left; destWidth -= clipAmount; } if (destY < containerSize.top) { const clipAmount = containerSize.top - destY; sourceY = clipAmount; sourceHeight -= clipAmount; destY = containerSize.top; destHeight -= clipAmount; } const containerRight = containerSize.left + containerSize.width; if (destX + destWidth > containerRight) { const clipAmount = destX + destWidth - containerRight; sourceWidth -= clipAmount; destWidth -= clipAmount; } const containerBottom = containerSize.top + containerSize.height; if (destY + destHeight > containerBottom) { const clipAmount = destY + destHeight - containerBottom; sourceHeight -= clipAmount; destHeight -= clipAmount; } return { sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight }; }; var calculateObjectFit = ({ objectFit, containerSize, intrinsicSize }) => { switch (objectFit) { case "fill": return calculateFill({ containerSize, intrinsicSize }); case "contain": return calculateContain({ containerSize, intrinsicSize }); case "cover": return calculateCover({ containerSize, intrinsicSize }); case "none": return calculateNone({ containerSize, intrinsicSize }); case "scale-down": { const containResult = calculateContain({ containerSize, intrinsicSize }); const noneResult = calculateNone({ containerSize, intrinsicSize }); const containArea = containResult.destWidth * containResult.destHeight; const noneArea = noneResult.destWidth * noneResult.destHeight; return containArea < noneArea ? containResult : noneResult; } default: { const exhaustiveCheck = objectFit; throw new Error(`Unknown object-fit value: ${exhaustiveCheck}`); } } }; var parseObjectFit = (value) => { if (!value) { return "fill"; } const normalized = value.trim().toLowerCase(); switch (normalized) { case "fill": case "contain": case "cover": case "none": case "scale-down": return normalized; default: return "fill"; } }; // src/drawing/fit-svg-into-its-dimensions.ts var fitSvgIntoItsContainer = ({ containerSize, elementSize }) => { if (Math.round(containerSize.width) === Math.round(elementSize.width) && Math.round(containerSize.height) === Math.round(elementSize.height)) { return { width: containerSize.width, height: containerSize.height, top: containerSize.top, left: containerSize.left }; } if (containerSize.width <= 0 || containerSize.height <= 0) { throw new Error(`Container must have positive dimensions, but got ${containerSize.width}x${containerSize.height}`); } if (elementSize.width <= 0 || elementSize.height <= 0) { throw new Error(`Element must have positive dimensions, but got ${elementSize.width}x${elementSize.height}`); } const heightRatio = containerSize.height / elementSize.height; const widthRatio = containerSize.width / elementSize.width; const ratio = Math.min(heightRatio, widthRatio); const newWidth = elementSize.width * ratio; const newHeight = elementSize.height * ratio; if (newWidth > containerSize.width + 0.000001 || newHeight > containerSize.height + 0.000001) { throw new Error(`Element is too big to fit into the container. Max size: ${containerSize.width}x${containerSize.height}, element size: ${newWidth}x${newHeight}`); } return { width: newWidth, height: newHeight, top: (containerSize.height - newHeight) / 2 + containerSize.top, left: (containerSize.width - newWidth) / 2 + containerSize.left }; }; // src/drawing/turn-svg-into-drawable.ts var turnSvgIntoDrawable = (svg) => { const { fill, color } = getComputedStyle(svg); const originalTransform = svg.style.transform; const originalTransformOrigin = svg.style.transformOrigin; const originalMarginLeft = svg.style.marginLeft; const originalMarginRight = svg.style.marginRight; const originalMarginTop = svg.style.marginTop; const originalMarginBottom = svg.style.marginBottom; const originalFill = svg.style.fill; const originalColor = svg.style.color; svg.style.transform = "none"; svg.style.transformOrigin = ""; svg.style.marginLeft = "0"; svg.style.marginRight = "0"; svg.style.marginTop = "0"; svg.style.marginBottom = "0"; svg.style.fill = fill; svg.style.color = color; const svgData = new XMLSerializer().serializeToString(svg); svg.style.marginLeft = originalMarginLeft; svg.style.marginRight = originalMarginRight; svg.style.marginTop = originalMarginTop; svg.style.marginBottom = originalMarginBottom; svg.style.transform = originalTransform; svg.style.transformOrigin = originalTransformOrigin; svg.style.fill = originalFill; svg.style.color = originalColor; return new Promise((resolve, reject) => { const image = new Image; const url = `data:image/svg+xml;base64,${btoa(svgData)}`; image.onload = function() { resolve(image); }; image.onerror = () => { reject(new Error("Failed to convert SVG to image")); }; image.src = url; }); }; // src/drawing/draw-dom-element.ts var getReadableImageError = (err, node) => { if (!(err instanceof DOMException)) { return null; } if (err.name === "SecurityError") { return new Error(`Could not draw image with src="${node.src}" to canvas: ` + `The image is tainted due to CORS restrictions. ` + `The server hosting this image must respond with the "Access-Control-Allow-Origin" header. ` + `See: https://remotion.dev/docs/client-side-rendering/migration`); } if (err.name === "InvalidStateError") { return new Error(`Could not draw image with src="${node.src}" to canvas: ` + `The image is in a broken state. ` + `This usually means the image failed to load - check that the URL is valid and accessible.`); } return null; }; var drawSvg = ({ drawable, dimensions, contextToDraw }) => { const fitted = fitSvgIntoItsContainer({ containerSize: dimensions, elementSize: { width: drawable.width, height: drawable.height } }); contextToDraw.drawImage(drawable, fitted.left, fitted.top, fitted.width, fitted.height); }; var drawReplacedElement = ({ drawable, dimensions, computedStyle, contextToDraw }) => { const objectFit = parseObjectFit(computedStyle.objectFit); const intrinsicSize = drawable instanceof HTMLImageElement ? { width: drawable.naturalWidth, height: drawable.naturalHeight } : { width: drawable.width, height: drawable.height }; const result = calculateObjectFit({ objectFit, containerSize: { width: dimensions.width, height: dimensions.height, left: dimensions.left, top: dimensions.top }, intrinsicSize }); contextToDraw.drawImage(drawable, result.sourceX, result.sourceY, result.sourceWidth, result.sourceHeight, result.destX, result.destY, result.destWidth, result.destHeight); }; var drawDomElement = (node) => { const domDrawFn = async ({ dimensions, contextToDraw, computedStyle }) => { if (node instanceof SVGSVGElement) { const drawable = await turnSvgIntoDrawable(node); drawSvg({ drawable, dimensions, contextToDraw }); return; } if (node instanceof HTMLImageElement || node instanceof HTMLCanvasElement) { try { drawReplacedElement({ drawable: node, dimensions, computedStyle, contextToDraw }); } catch (err) { if (node instanceof HTMLImageElement) { const readableError = getReadableImageError(err, node); if (readableError) { throw readableError; } } throw err; } } }; return domDrawFn; }; // src/drawing/process-node.ts import { Internals as Internals6 } from "remotion"; // src/drawing/has-transform.ts var hasTransformCssValue = (style) => { return style.transform !== "none" && style.transform !== ""; }; var hasRotateCssValue = (style) => { return style.rotate !== "none" && style.rotate !== ""; }; var hasScaleCssValue = (style) => { return style.scale !== "none" && style.scale !== ""; }; var hasAnyTransformCssValue = (style) => { return hasTransformCssValue(style) || hasRotateCssValue(style) || hasScaleCssValue(style); }; // src/drawing/parse-linear-gradient.ts import { NoReactInternals as NoReactInternals2 } from "remotion/no-react"; var isValidColor = (color) => { try { const result = NoReactInternals2.processColor(color); return result !== null && result !== undefined; } catch { return false; } }; var parseDirection = (directionStr) => { const trimmed = directionStr.trim().toLowerCase(); if (trimmed.startsWith("to ")) { const direction = trimmed.substring(3).trim(); switch (direction) { case "top": return 0; case "right": return 90; case "bottom": return 180; case "left": return 270; case "top right": case "right top": return 45; case "bottom right": case "right bottom": return 135; case "bottom left": case "left bottom": return 225; case "top left": case "left top": return 315; default: return 180; } } const angleMatch = trimmed.match(/^(-?\d+\.?\d*)(deg|rad|grad|turn)$/); if (angleMatch) { const value = parseFloat(angleMatch[1]); const unit = angleMatch[2]; switch (unit) { case "deg": return value; case "rad": return value * 180 / Math.PI; case "grad": return value * 360 / 400; case "turn": return value * 360; default: return value; } } return 180; }; var parseColorStops = (colorStopsStr) => { const parts = colorStopsStr.split(/,(?![^(]*\))/); const stops = []; for (const part of parts) { const trimmed = part.trim(); if (!trimmed) continue; const colorMatch = trimmed.match(/(rgba?\([^)]+\)|hsla?\([^)]+\)|#[0-9a-f]{3,8}|[a-z]+)/i); if (!colorMatch) { continue; } const colorStr = colorMatch[0]; if (!isValidColor(colorStr)) { continue; } const remaining = trimmed.substring(colorMatch.index + colorStr.length).trim(); const normalizedColor = colorStr; let position = null; if (remaining) { const posMatch = remaining.match(/(-?\d+\.?\d*)(%|px)?/); if (posMatch) { const value = parseFloat(posMatch[1]); const unit = posMatch[2]; if (unit === "%") { position = value / 100; } else if (unit === "px") { position = null; } else { position = value / 100; } } } stops.push({ color: normalizedColor, position: position !== null ? position : -1 }); } if (stops.length === 0) { return null; } let lastExplicitIndex = -1; let lastExplicitPosition = 0; for (let i = 0;i < stops.length; i++) { if (stops[i].position !== -1) { if (lastExplicitIndex >= 0) { const numImplicit = i - lastExplicitIndex - 1; if (numImplicit > 0) { const step = (stops[i].position - lastExplicitPosition) / (numImplicit + 1); for (let j = lastExplicitIndex + 1;j < i; j++) { stops[j].position = lastExplicitPosition + step * (j - lastExplicitIndex); } } } else { const numImplicit = i; if (numImplicit > 0) { const step = stops[i].position / (numImplicit + 1); for (let j = 0;j < i; j++) { stops[j].position = step * (j + 1); } } } lastExplicitIndex = i; lastExplicitPosition = stops[i].position; } } if (stops.every((s) => s.position === -1)) { if (stops.length === 1) { stops[0].position = 0.5; } else { for (let i = 0;i < stops.length; i++) { stops[i].position = i / (stops.length - 1); } } } else if (lastExplicitIndex < stops.length - 1) { const numImplicit = stops.length - 1 - lastExplicitIndex; const step = (1 - lastExplicitPosition) / (numImplicit + 1); for (let i = lastExplicitIndex + 1;i < stops.length; i++) { stops[i].position = lastExplicitPosition + step * (i - lastExplicitIndex); } } for (const stop of stops) { stop.position = Math.max(0, Math.min(1, stop.position)); } return stops; }; var extractGradientContent = (backgroundImage) => { const prefix = "linear-gradient("; const startIndex = backgroundImage.toLowerCase().indexOf(prefix); if (startIndex === -1) { return null; } let depth = 0; const contentStart = startIndex + prefix.length; for (let i = contentStart;i < backgroundImage.length; i++) { const char = backgroundImage[i]; if (char === "(") { depth++; } else if (char === ")") { if (depth === 0) { return backgroundImage.substring(contentStart, i).trim(); } depth--; } } return null; }; var parseLinearGradient = (backgroundImage) => { if (!backgroundImage || backgroundImage === "none") { return null; } const content = extractGradientContent(backgroundImage); if (!content) { return null; } const parts = content.split(/,(?![^(]*\))/); let angle = 180; let colorStopsStart = 0; if (parts.length > 0) { const firstPart = parts[0].trim(); const isDirection = firstPart.startsWith("to ") || /^-?\d+\.?\d*(deg|rad|grad|turn)$/.test(firstPart); if (isDirection) { angle = parseDirection(firstPart); colorStopsStart = 1; } } const colorStopsStr = parts.slice(colorStopsStart).join(","); const colorStops = parseColorStops(colorStopsStr); if (!colorStops || colorStops.length === 0) { return null; } return { angle, colorStops }; }; var createCanvasGradient = ({ ctx, rect, gradientInfo, offsetLeft, offsetTop }) => { const angleRad = (gradientInfo.angle - 90) * Math.PI / 180; const centerX = rect.left - offsetLeft + rect.width / 2; const centerY = rect.top - offsetTop + rect.height / 2; const cos = Math.cos(angleRad); const sin = Math.sin(angleRad); const halfWidth = rect.width / 2; const halfHeight = rect.height / 2; let length = Math.abs(cos) * halfWidth + Math.abs(sin) * halfHeight; if (!Number.isFinite(length) || length === 0) { length = Math.sqrt(halfWidth ** 2 + halfHeight ** 2); } const x0 = centerX - cos * length; const y0 = centerY - sin * length; const x1 = centerX + cos * length; const y1 = centerY + sin * length; const gradient = ctx.createLinearGradient(x0, y0, x1, y1); for (const stop of gradientInfo.colorStops) { gradient.addColorStop(stop.position, stop.color); } return gradient; }; // src/drawing/mask-image.ts var getMaskImageValue = (computedStyle) => { const { maskImage, webkitMaskImage } = computedStyle; const value = maskImage || webkitMaskImage; if (!value || value === "none") { return null; } return value; }; var parseMaskImage = (maskImageValue) => { return parseLinearGradient(maskImageValue); }; // src/drawing/parse-transform-origin.ts var parseTransformOrigin = (transformOrigin) => { if (transformOrigin.trim() === "") { return null; } const [x, y] = transformOrigin.split(" "); return { x: parseFloat(x), y: parseFloat(y) }; }; // src/drawing/calculate-transforms.ts var getInternalTransformOrigin = (transform) => { const centerX = transform.boundingClientRect.width / 2; const centerY = transform.boundingClientRect.height / 2; const origin = parseTransformOrigin(transform.transformOrigin) ?? { x: centerX, y: centerY }; return origin; }; var getGlobalTransformOrigin = ({ transform }) => { const { x: originX, y: originY } = getInternalTransformOrigin(transform); return { x: originX + transform.boundingClientRect.left, y: originY + transform.boundingClientRect.top }; }; var calculateTransforms = ({ element, rootElement }) => { let parent = element; const transforms = []; const toReset = []; let opacity = 1; let elementComputedStyle = null; let maskImageInfo = null; while (parent) { const computedStyle = getComputedStyle(parent); if (parent === element) { elementComputedStyle = computedStyle; opacity = parseFloat(computedStyle.opacity); const maskImageValue = getMaskImageValue(computedStyle); maskImageInfo = maskImageValue ? parseMaskImage(maskImageValue) : null; const originalMaskImage = parent.style.maskImage; const originalWebkitMaskImage = parent.style.webkitMaskImage; parent.style.maskImage = "none"; parent.style.webkitMaskImage = "none"; const parentRef = parent; toReset.push(() => { parentRef.style.maskImage = originalMaskImage; parentRef.style.webkitMaskImage = originalWebkitMaskImage; }); } if (hasAnyTransformCssValue(computedStyle) || parent === element) { const toParse = hasTransformCssValue(computedStyle) ? computedStyle.transform : undefined; const matrix = new DOMMatrix(toParse); const { transform, scale, rotate } = parent.style; const additionalMatrices = []; if (rotate !== "" && rotate !== "none") { additionalMatrices.push(new DOMMatrix(`rotate(${rotate})`)); } if (scale !== "" && scale !== "none") { additionalMatrices.push(new DOMMatrix(`scale(${scale})`)); } additionalMatrices.push(matrix); parent.style.transform = "none"; parent.style.scale = "none"; parent.style.rotate = "none"; transforms.push({ element: parent, transformOrigin: computedStyle.transformOrigin, boundingClientRect: null, matrices: additionalMatrices }); const parentRef = parent; toReset.push(() => { parentRef.style.transform = transform; parentRef.style.scale = scale; parentRef.style.rotate = rotate; }); } if (parent === rootElement) { break; } parent = parent.parentElement; } for (const transform of transforms) { transform.boundingClientRect = transform.element.getBoundingClientRect(); } const dimensions = transforms[0].boundingClientRect; const nativeTransformOrigin = getInternalTransformOrigin(transforms[0]); const totalMatrix = new DOMMatrix; for (const transform of transforms.slice().reverse()) { for (const matrix of transform.matrices) { const globalTransformOrigin = getGlobalTransformOrigin({ transform }); const transformMatrix = new DOMMatrix().translate(globalTransformOrigin.x, globalTransformOrigin.y).multiply(matrix).translate(-globalTransformOrigin.x, -globalTransformOrigin.y); totalMatrix.multiplySelf(transformMatrix); } } if (!elementComputedStyle) { throw new Error("Element computed style not found"); } const needs3DTransformViaWebGL = !totalMatrix.is2D; const needsMaskImage = maskImageInfo !== null; return { dimensions, totalMatrix, [Symbol.dispose]: () => { for (const reset of toReset) { reset(); } }, nativeTransformOrigin, computedStyle: elementComputedStyle, opacity, maskImageInfo, precompositing: { needs3DTransformViaWebGL, needsMaskImage: maskImageInfo, needsPrecompositing: Boolean(needs3DTransformViaWebGL || needsMaskImage) } }; }; // src/drawing/round-to-expand-rect.ts var roundToExpandRect = (rect) => { const left = Math.floor(rect.left); const top = Math.floor(rect.top); const right = Math.ceil(rect.right); const bottom = Math.ceil(rect.bottom); return new DOMRect(left, top, right - left, bottom - top); }; // src/drawing/clamp-rect-to-parent-bounds.ts var getNarrowerRect = ({ firstRect, secondRect }) => { const left = Math.max(firstRect.left, secondRect.left); const top = Math.max(firstRect.top, secondRect.top); const bottom = Math.min(firstRect.bottom, secondRect.bottom); const right = Math.min(firstRect.right, secondRect.right); return new DOMRect(left, top, right - left, bottom - top); }; var getWiderRectAndExpand = ({ firstRect, secondRect }) => { if (firstRect === null) { return roundToExpandRect(secondRect); } const left = Math.min(firstRect.left, secondRect.left); const top = Math.min(firstRect.top, secondRect.top); const bottom = Math.max(firstRect.bottom, secondRect.bottom); const right = Math.max(firstRect.right, secondRect.right); return roundToExpandRect(new DOMRect(left, top, right - left, bottom - top)); }; // src/drawing/do-rects-intersect.ts function doRectsIntersect(rect1, rect2) { return !(rect1.right <= rect2.left || rect1.left >= rect2.right || rect1.bottom <= rect2.top || rect1.top >= rect2.bottom); } // src/drawing/draw-rounded.ts var drawRoundedRectPath = ({ ctx, x, y, width, height, borderRadius }) => { ctx.beginPath(); ctx.moveTo(x + borderRadius.topLeft.horizontal, y); ctx.lineTo(x + width - borderRadius.topRight.horizontal, y); if (borderRadius.topRight.horizontal > 0 || borderRadius.topRight.vertical > 0) { ctx.ellipse(x + width - borderRadius.topRight.horizontal, y + borderRadius.topRight.vertical, borderRadius.topRight.horizontal, borderRadius.topRight.vertical, 0, -Math.PI / 2, 0); } ctx.lineTo(x + width, y + height - borderRadius.bottomRight.vertical); if (borderRadius.bottomRight.horizontal > 0 || borderRadius.bottomRight.vertical > 0) { ctx.ellipse(x + width - borderRadius.bottomRight.horizontal, y + height - borderRadius.bottomRight.vertical, borderRadius.bottomRight.horizontal, borderRadius.bottomRight.vertical, 0, 0, Math.PI / 2); } ctx.lineTo(x + borderRadius.bottomLeft.horizontal, y + height); if (borderRadius.bottomLeft.horizontal > 0 || borderRadius.bottomLeft.vertical > 0) { ctx.ellipse(x + borderRadius.bottomLeft.horizontal, y + height - borderRadius.bottomLeft.vertical, borderRadius.bottomLeft.horizontal, borderRadius.bottomLeft.vertical, 0, Math.PI / 2, Math.PI); } ctx.lineTo(x, y + borderRadius.topLeft.vertical); if (borderRadius.topLeft.horizontal > 0 || borderRadius.topLeft.vertical > 0) { ctx.ellipse(x + borderRadius.topLeft.horizontal, y + borderRadius.topLeft.vertical, borderRadius.topLeft.horizontal, borderRadius.topLeft.vertical, 0, Math.PI, Math.PI * 3 / 2); } ctx.closePath(); }; // src/drawing/get-padding-box.ts var getPaddingBox = (rect, computedStyle) => { const borderLeft = parseFloat(computedStyle.borderLeftWidth); const borderRight = parseFloat(computedStyle.borderRightWidth); const borderTop = parseFloat(computedStyle.borderTopWidth); const borderBottom = parseFloat(computedStyle.borderBottomWidth); return new DOMRect(rect.left + borderLeft, rect.top + borderTop, rect.width - borderLeft - borderRight, rect.height - borderTop - borderBottom); }; var getContentBox = (rect, computedStyle) => { const paddingBox = getPaddingBox(rect, computedStyle); const paddingLeft = parseFloat(computedStyle.paddingLeft); const paddingRight = parseFloat(computedStyle.paddingRight); const paddingTop = parseFloat(computedStyle.paddingTop); const paddingBottom = parseFloat(computedStyle.paddingBottom); return new DOMRect(paddingBox.left + paddingLeft, paddingBox.top + paddingTop, paddingBox.width - paddingLeft - paddingRight, paddingBox.height - paddingTop - paddingBottom); }; var getBoxBasedOnBackgroundClip = (rect, computedStyle, backgroundClip) => { if (!backgroundClip) { return rect; } if (backgroundClip.includes("text")) { return rect; } if (backgroundClip.includes("padding-box")) { return getPaddingBox(rect, computedStyle); } if (backgroundClip.includes("content-box")) { return getContentBox(rect, computedStyle); } return rect; }; // src/drawing/border-radius.ts function parseValue({ value, reference }) { value = value.trim(); if (value.endsWith("%")) { const percentage = parseFloat(value); return percentage / 100 * reference; } if (value.endsWith("px")) { return parseFloat(value); } return parseFloat(value); } function expandShorthand(values) { if (values.length === 1) { return [values[0], values[0], values[0], values[0]]; } if (values.length === 2) { return [values[0], values[1], values[0], values[1]]; } if (values.length === 3) { return [values[0], values[1], values[2], values[1]]; } return [values[0], values[1], values[2], values[3]]; } function clampBorderRadius({ borderRadius, width, height }) { const clamped = { topLeft: { ...borderRadius.topLeft }, topRight: { ...borderRadius.topRight }, bottomRight: { ...borderRadius.bottomRight }, bottomLeft: { ...borderRadius.bottomLeft } }; const topSum = clamped.topLeft.horizontal + clamped.topRight.horizontal; if (topSum > width) { const factor = width / topSum; clamped.topLeft.horizontal *= factor; clamped.topRight.horizontal *= factor; } const rightSum = clamped.topRight.vertical + clamped.bottomRight.vertical; if (rightSum > height) { const factor = height / rightSum; clamped.topRight.vertical *= factor; clamped.bottomRight.vertical *= factor; } const bottomSum = clamped.bottomRight.horizontal + clamped.bottomLeft.horizontal; if (bottomSum > width) { const factor = width / bottomSum; clamped.bottomRight.horizontal *= factor; clamped.bottomLeft.horizontal *= factor; } const leftSum = clamped.bottomLeft.vertical + clamped.topLeft.vertical; if (leftSum > height) { const factor = height / leftSum; clamped.bottomLeft.vertical *= factor; clamped.topLeft.vertical *= factor; } return clamped; } function parseBorderRadius({ borderRadius, width, height }) { const parts = borderRadius.split("/").map((part) => part.trim()); const horizontalPart = parts[0]; const verticalPart = parts[1]; const horizontalValues = horizontalPart.split(/\s+/).filter((v) => v); const verticalValues = verticalPart ? verticalPart.split(/\s+/).filter((v) => v) : horizontalValues; const [hTopLeft, hTopRight, hBottomRight, hBottomLeft] = expandShorthand(horizontalValues); const [vTopLeft, vTopRight, vBottomRight, vBottomLeft] = expandShorthand(verticalValues); return clampBorderRadius({ borderRadius: { topLeft: { horizontal: parseValue({ value: hTopLeft, reference: width }), vertical: parseValue({ value: vTopLeft, reference: height }) }, topRight: { horizontal: parseValue({ value: hTopRight, reference: width }), vertical: parseValue({ value: vTopRight, reference: height }) }, bottomRight: { horizontal: parseValue({ value: hBottomRight, reference: width }), vertical: parseValue({ value: vBottomRight, reference: height }) }, bottomLeft: { horizontal: parseValue({ value: hBottomLeft, reference: width }), vertical: parseValue({ value: vBottomLeft, reference: height }) } }, width, height }); } function setBorderRadius({ ctx, rect, borderRadius, forceClipEvenWhenZero = false, computedStyle, backgroundClip }) { if (borderRadius.topLeft.horizontal === 0 && borderRadius.topLeft.vertical === 0 && borderRadius.topRight.horizontal === 0 && borderRadius.topRight.vertical === 0 && borderRadius.bottomRight.horizontal === 0 && borderRadius.bottomRight.vertical === 0 && borderRadius.bottomLeft.horizontal === 0 && borderRadius.bottomLeft.vertical === 0 && !forceClipEvenWhenZero) { return () => {}; } ctx.save(); const boundingRect = getBoxBasedOnBackgroundClip(rect, computedStyle, backgroundClip); const actualBorderRadius = { topLeft: { horizontal: Math.max(0, borderRadius.topLeft.horizontal - (boundingRect.left - rect.left)), vertical: Math.max(0, borderRadius.topLeft.vertical - (boundingRect.top - rect.top)) }, topRight: { horizontal: Math.max(0, borderRadius.topRight.horizontal - (rect.right - boundingRect.right)), vertical: Math.max(0, borderRadius.topRight.vertical - (boundingRect.top - rect.top)) }, bottomRight: { horizontal: Math.max(0, borderRadius.bottomRight.horizontal - (rect.right - boundingRect.right)), vertical: Math.max(0, borderRadius.bottomRight.vertical - (rect.bottom - boundingRect.bottom)) }, bottomLeft: { horizontal: Math.max(0, borderRadius.bottomLeft.horizontal - (boundingRect.left - rect.left)), vertical: Math.max(0, borderRadius.bottomLeft.vertical - (rect.bottom - boundingRect.bottom)) } }; drawRoundedRectPath({ ctx, x: boundingRect.left, y: boundingRect.top, width: boundingRect.width, height: boundingRect.height, borderRadius: actualBorderRadius }); ctx.clip(); return () => { ctx.restore(); }; } // src/drawing/get-background-fill.ts var isColorTransparent = (color) => { return color === "transparent" || color.startsWith("rgba") && (color.endsWith(", 0)") || color.endsWith(",0")); }; var getBackgroundFill = ({ backgroundColor, backgroundImage, contextToDraw, boundingRect, offsetLeft, offsetTop }) => { if (backgroundImage && backgroundImage !== "none") { const gradientInfo = parseLinearGradient(backgroundImage); if (gradientInfo) { const gradient = createCanvasGradient({ ctx: contextToDraw, rect: boundingRect, gradientInfo, offsetLeft, offsetTop }); return gradient; } } if (backgroundColor && backgroundColor !== "transparent" && !isColorTransparent(backgroundColor)) { return backgroundColor; } return null; }; // src/drawing/draw-background.ts var drawBackground = async ({ backgroundImage, context, rect, backgroundColor, backgroundClip, element, logLevel, internalState, computedStyle, offsetLeft: parentOffsetLeft, offsetTop: parentOffsetTop, scale }) => { let __stack = []; try { let contextToDraw = context; const originalCompositeOperation = context.globalCompositeOperation; let offsetLeft = 0; let offsetTop = 0; const _ = __using(__stack, { [Symbol.dispose]: () => { context.globalCompositeOperation = originalCompositeOperation; if (context !== contextToDraw) { context.drawImage(contextToDraw.canvas, offsetLeft, offsetTop, contextToDraw.canvas.width / scale, contextToDraw.canvas.height / scale); } } }, 0); const boundingRect = getBoxBasedOnBackgroundClip(rect, computedStyle, backgroundClip); if (backgroundClip.includes("text")) { offsetLeft = boundingRect.left; offsetTop = boundingRect.top; const originalBackgroundClip = element.style.backgroundClip; const originalWebkitBackgroundClip = element.style.webkitBackgroundClip; element.style.backgroundClip = "initial"; element.style.webkitBackgroundClip = "initial"; const onlyBackgroundClipText = await createLayer({ element, cutout: new DOMRect(boundingRect.left + parentOffsetLeft, boundingRect.top + parentOffsetTop, boundingRect.width, boundingRect.height), logLevel, internalState, scale, onlyBackgroundClipText: true }); onlyBackgroundClipText.setTransform(new DOMMatrix().scale(scale, scale)); element.style.backgroundClip = originalBackgroundClip; element.style.webkitBackgroundClip = originalWebkitBackgroundClip; contextToDraw = onlyBackgroundClipText; contextToDraw.globalCompositeOperation = "source-in"; } const backgroundFill = getBackgroundFill({ backgroundImage, backgroundColor, contextToDraw, boundingRect, offsetLeft, offsetTop }); if (!backgroundFill) { return; } const originalFillStyle = contextToDraw.fillStyle; contextToDraw.fillStyle = backgroundFill; contextToDraw.fillRect(boundingRect.left - offsetLeft, boundingRect.top - offsetTop, boundingRect.width, boundingRect.height); contextToDraw.fillStyle = originalFillStyle; } catch (_catch) { var _err = _catch, _hasErr = 1; } finally { __callDispose(__stack, _err, _hasErr); } }; // src/drawing/draw-border.ts var parseBorderWidth = (value) => { return parseFloat(value) || 0; }; var getBorderSideProperties = (computedStyle) => { return { top: { width: parseBorderWidth(computedStyle.borderTopWidth), color: computedStyle.borderTopColor || computedStyle.borderColor || "black", style: computedStyle.borderTopStyle || computedStyle.borderStyle || "solid" }, right: { width: parseBorderWidth(computedStyle.borderRightWidth), color: computedStyle.borderRightColor || computedStyle.borderColor || "black", style: computedStyle.borderRightStyle || computedStyle.borderStyle || "solid" }, bottom: { width: parseBorderWidth(computedStyle.borderBottomWidth), color: computedStyle.borderBottomColor || computedStyle.borderColor || "black", style: computedStyle.borderBottomStyle || computedStyle.borderStyle || "solid" }, left: { width: parseBorderWidth(computedStyle.borderLeftWidth), color: computedStyle.borderLeftColor || computedStyle.borderColor || "black", style: computedStyle.borderLeftStyle || computedStyle.borderStyle || "solid" } }; }; var getLineDashPattern = (style, width) => { if (style === "dashed") { return [width * 2, width]; } if (style === "dotted") { return [width, width]; } return []; }; var drawBorderSide = ({ ctx, side, x, y, width, height, borderRadius, borderProperties }) => { const { width: borderWidth, color, style } = borderProperties; if (borderWidth <= 0 || style === "none" || style === "hidden") { return; } ctx.beginPath(); ctx.strokeStyle = color; ctx.lineWidth = borderWidth; ctx.setLineDash(getLineDashPattern(style, borderWidth)); const halfWidth = borderWidth / 2; if (side === "top") { const startX = x + borderRadius.topLeft.horizontal; const startY = y + halfWidth; const endX = x + width - borderRadius.topRight.horizontal; const endY = y + halfWidth; ctx.moveTo(startX, startY); ctx.lineTo(endX, endY); } else if (side === "right") { const startX = x + width - halfWidth; const startY = y + borderRadius.topRight.vertical; const endX = x + width - halfWidth; const endY = y + height - borderRadius.bottomRight.vertical; ctx.moveTo(startX, startY); ctx.lineTo(endX, endY); } else if (side === "bottom") { const startX = x + borderRadius.bottomLeft.horizontal; const startY = y + height - halfWidth; const endX = x + width - borderRadius.bottomRight.horizontal; const endY = y + height - halfWidth; ctx.moveTo(startX, startY); ctx.lineTo(endX, endY); } else if (side === "left") { const startX = x + halfWidth; const startY = y + borderRadius.topLeft.vertical; const endX = x + halfWidth; const endY = y + height - borderRadius.bottomLeft.vertical; ctx.moveTo(startX, startY); ctx.lineTo(endX, endY); } ctx.stroke(); }; var drawCorner = ({ ctx, corner, x, y, width, height, borderRadius, topBorder, rightBorder, bottomBorder, leftBorder }) => { const radius = borderRadius[corner]; if (radius.horizontal <= 0 && radius.vertical <= 0) { return; } let border1; let border2; let centerX; let centerY; let startAngle; let endAngle; if (corner === "topLeft") { border1 = leftBorder; border2 = topBorder; centerX = x + radius.horizontal; centerY = y + radius.vertical; startAngle = Math.PI; endAngle = Math.PI * 3 / 2; } else if (corner === "topRight") { border1 = topBorder; border2 = rightBorder; centerX = x + width - radius.horizontal; centerY = y + radius.vertical; startAngle = -Math.PI / 2; endAngle = 0; } else if (corner === "bottomRight") { border1 = rightBorder; border2 = bottomBorder; centerX = x + width - radius.horizontal; centerY = y + height - radius.vertical; startAngle = 0; endAngle = Math.PI / 2; } else { border1 = bottomBorder; border2 = leftBorder; centerX = x + radius.horizontal; centerY = y + height - radius.vertical; startAngle = Math.PI / 2; endAngle = Math.PI; } const avgWidth = (border1.width + border2.width) / 2; const useColor = border1.width >= border2.width ? border1.color : border2.color; const useStyle = border1.width >= border2.width ? border1.style : border2.style; if (avgWidth > 0 && useStyle !== "none" && useStyle !== "hidden") { ctx.beginPath(); ctx.strokeStyle = useColor; ctx.lineWidth = avgWidth; ctx.setLineDash(getLineDashPattern(useStyle, avgWidth)); const adjustedRadiusH = Math.max(0, radius.horizontal - avgWidth / 2); const adjustedRadiusV = Math.max(0, radius.vertical - avgWidth / 2); ctx.ellipse(centerX, centerY, adjustedRadiusH, adjustedRadiusV, 0, startAngle, endAngle); ctx.stroke(); } }; var drawUniformBorder = ({ ctx, x, y, width, height, borderRadius, borderWidth, borderColor, borderStyle }) => { ctx.beginPath(); ctx.strokeStyle = borderColor; ctx.lineWidth = borderWidth; ctx.setLineDash(getLineDashPattern(borderStyle, borderWidth)); const halfWidth = borderWidth / 2; const borderX = x + halfWidth; const borderY = y + halfWidth; const borderW = width - borderWidth; const borderH = height - borderWidth; const adjustedBorderRadius = { topLeft: { horizontal: Math.max(0, borderRadius.topLeft.horizontal - halfWidth), vertical: Math.max(0, borderRadius.topLeft.vertical - halfWidth) }, topRight: { horizontal: Math.max(0, borderRadius.topRight.horizontal - halfWidth), vertical: Math.max(0, borderRadius.topRight.vertical - halfWidth) }, bottomRight: { horizontal: Math.max(0, borderRadius.bottomRight.horizontal - halfWidth), vertical: Math.max(0, borderRadius.bottomRight.vertical - halfWidth) }, bottomLeft: { horizontal: Math.max(0, borderRadius.bottomLeft.horizontal - halfWidth), vertical: Math.max(0, borderRadius.bottomLeft.vertical - halfWidth) } }; ctx.moveTo(borderX + adjustedBorderRadius.topLeft.horizontal, borderY); ctx.lineTo(borderX + borderW - adjustedBorderRadius.topRight.horizontal, borderY); if (adjustedBorderRadius.topRight.horizontal > 0 || adjustedBorderRadius.topRight.vertical > 0) { ctx.ellipse(borderX + borderW - adjustedBorderRadius.topRight.horizontal, borderY + adjustedBorderRadius.topRight.vertical, adjustedBorderRadius.topRight.horizontal, adjustedBorderRadius.topRight.vertical, 0, -Math.PI / 2, 0); } ctx.lineTo(borderX + borderW, borderY + borderH - adjustedBorderRadius.bottomRight.vertical); if (adjustedBorderRadius.bottomRight.horizontal > 0 || adjustedBorderRadius.bottomRight.vertical > 0) { ctx.ellipse(borderX + borderW - adjustedBorderRadius.bottomRight.horizontal, borderY + borderH - adjustedBorderRadius.bottomRight.vertical, adjustedBorderRadius.bottomRight.horizontal, adjustedBorderRadius.bottomRight.vertical, 0, 0, Math.PI / 2); } ctx.lineTo(borderX + adjustedBorderRadius.bottomLeft.horizontal, borderY + borderH); if (adjustedBorderRadius.bottomLeft.horizontal > 0 || adjustedBorderRadius.bottomLeft.vertical > 0) { ctx.ellipse(borderX + adjustedBorderRadius.bottomLeft.horizontal, borderY + borderH - adjustedBorderRadius.bottomLeft.vertical, adjustedBorderRadius.bottomLeft.horizontal, adjustedBorderRadius.bottomLeft.vertical, 0, Math.PI / 2, Math.PI); } ctx.lineTo(borderX, borderY + adjustedBorderRadius.topLeft.vertical); if (adjustedBorderRadius.topLeft.horizontal > 0 || adjustedBorderRadius.topLeft.vertical > 0) { ctx.ellipse(borderX + adjustedBorderRadius.topLeft.horizontal, borderY + adjustedBorderRadius.topLeft.vertical, adjustedBorderRadius.topLeft.horizontal, adjustedBorderRadius.topLeft.vertical, 0, Math.PI, Math.PI * 3 / 2); } ctx.closePath(); ctx.stroke(); }; var drawBorder = ({ ctx, rect, borderRadius, computedStyle }) => { const borders = getBorderSideProperties(computedStyle); const hasBorder = borders.top.width > 0 || borders.right.width > 0 || borders.bottom.width > 0 || borders.left.width > 0; if (!hasBorder) { return; } const originalStrokeStyle = ctx.strokeStyle; const originalLineWidth = ctx.lineWidth; const originalLineDash = ctx.getLineDash(); const allSidesEqual = borders.top.width === borders.right.width && borders.top.width === borders.bottom.width && borders.top.width === borders.left.width && borders.top.color === borders.right.color && borders.top.color === borders.bottom.color && borders.top.color === borders.left.color && borders.top.style === borders.right.style && borders.top.style === borders.bottom.style && borders.top.style === borders.left.style && borders.top.width > 0; if (allSidesEqual) { drawUniformBorder({ ctx, x: rect.left, y: rect.top, width: rect.width, height: rect.height, borderRadius, borderWidth: borders.top.width, borderColor: borders.top.color, borderStyle: borders.top.style }); } else { drawCorner({ ctx, corner: "topLeft", x: rect.left, y: rect.top, width: rect.width, height: rect.height, borderRadius, topBorder: borders.top, rightBorder: borders.right, bottomBorder: borders.bottom, leftBorder: borders.left }); drawCorner({ ctx, corner: "topRight", x: rect.left, y: rect.top, width: rect.width, height: rect.height, borderRadius, topBorder: borders.top, rightBorder: borders.right, bottomBorder: borders.bottom, leftBorder: borders.left }); drawCorner({ ctx, corner: "bottomRight", x: rect.left, y: rect.top, width: rect.width, height: rect.height, borderRadius, topBorder: borders.top, rightBorder: borders.right, bottomBorder: borders.bottom, leftBorder: borders.left }); drawCorner({ ctx, corner: "bottomLeft", x: rect.left, y: rect.top, width: rect.width, height: rect.height, borderRadius, topBorder: borders.top, rightBorder: borders.right, bottomBorder: borders.bottom, leftBorder: borders.left }); drawBorderSide({ ctx, side: "top", x: rect.left, y: rect.top, width: rect.width, height: rect.height, borderRadius, borderProperties: borders.top }); drawBorderSide({ ctx, side: "right", x: rect.left, y: rect.top, width: rect.width, height: rect.height, borderRadius, borderProperties: borders.right }); drawBorderSide({ ctx, side: "bottom", x: rect.left, y: rect.top, width: rect.width, height: rect.height, borderRadius, borderProperties: borders.bottom }); drawBorderSide({ ctx, side: "left", x: rect.left, y: rect.top, width: rect.width, height: rect.height, borderRadius, borderProperties: borders.left }); } ctx.strokeStyle = originalStrokeStyle; ctx.lineWidth = originalLineWidth; ctx.setLineDash(originalLineDash); }; // src/drawing/draw-box-shadow.ts import { Internals as Internals5 } from "remotion"; var parseBoxShadow = (boxShadowValue) => { if (!boxShadowValue || boxShadowValue === "none") { return []; } const shadows = []; const shadowStrings = boxShadowValue.split(/,(?![^(]*\))/); for (const shadowStr of shadowStrings) { const trimmed = shadowStr.trim(); if (!trimmed || trimmed === "none") { continue; } const shadow = { offsetX: 0, offsetY: 0, blurRadius: 0, color: "rgba(0, 0, 0, 0.5)", inset: false }; shadow.inset = /\binset\b/i.test(trimmed); let remaining = trimmed.replace(/\binset\b/gi, "").trim(); const colorMatch = remaining.match(/(rgba?\([^)]+\)|hsla?\([^)]+\)|#[0-9a-f]{3,8}|[a-z]+)/i); if (colorMatch) { shadow.color = colorMatch[0]; remaining = remaining.replace(colorMatch[0], "").trim(); } const numbers = remaining.match(/[+-]?\d*\.?\d+(?:px|em|rem|%)?/gi) || []; const values = numbers.map((n) => parseFloat(n) || 0); if (values.length >= 2) { shadow.offsetX = values[0]; shadow.offsetY = values[1]; if (values.length >= 3) { shadow.blurRadius = Math.max(0, values[2]); } } shadows.push(shadow); } return shadows; }; var drawBorderRadius = ({ ctx, rect, borderRadius, computedStyle, logLevel }) => { const shadows = parseBoxShadow(computedStyle.boxShadow); if (shadows.length === 0) { return; } for (let i = shadows.length - 1;i >= 0; i--) { const shadow = shadows[i]; const newLeft = rect.left + Math.min(shadow.offsetX, 0) - shadow.blurRadius; const newRight = rect.right + Math.max(shadow.offsetX, 0) + shadow.blurRadius; const newTop = rect.top + Math.min(shadow.offsetY, 0) - shadow.blurRadius; const newBottom = rect.bottom + Math.max(shadow.offsetY, 0) + shadow.blurRadius; const newRect = new DOMRect(newLeft, newTop, newRight - newLeft, newBottom - newTop); const leftOffset = rect.left - newLeft; const topOffset = rect.top - newTop; const newCanvas = new OffscreenCanvas(newRect.width, newRect.height); const newCtx = newCanvas.getContext("2d"); if (!newCtx) { throw new Error("Failed to get context"); } if (shadow.inset) { Internals5.Log.warn({ logLevel, tag: "@remotion/web-renderer" }, 'Detected "box-shadow" with "inset". This is not yet supported in @remotion/web-renderer'); continue; } newCtx.shadowBlur = shadow.blurRadius; newCtx.shadowColor = shadow.color; newCtx.shadowOffsetX = shadow.offsetX; newCtx.shadowOffsetY = shadow.offsetY; newCtx.fillStyle = "black"; drawRoundedRectPath({ ctx: newCtx, x: leftOffset, y: topOffset, width: rect.width, height: rect.height, borderRadius }); newCtx.fill(); newCtx.shadowColor = "transparent"; newCtx.globalCompositeOperation = "destination-out"; drawRoundedRectPath({ ctx: newCtx, x: leftOffset, y: topOffset, width: rect.width, height: rect.height, borderRadius }); newCtx.fill(); ctx.drawImage(newCanvas, rect.left - leftOffset, rect.top - topOffset); } }; // src/drawing/draw-outline.ts var parseOutlineWidth = (value) => { return parseFloat(value) || 0; }; var parseOutlineOffset = (value) => { return parseFloat(value) || 0; }; var getLineDashPattern2 = (style, width) => { if (style === "dashed") { return [width * 2, width]; } if (style === "dotted") { return [width, width]; } return []; }; var drawOutline = ({ ctx, rect, borderRadius, computedStyle }) => { const outlineWidth = parseOutlineWidth(computedStyle.outlineWidth); const { outlineStyle } = computedStyle; const outlineColor = computedStyle.outlineColor || "black"; const outlineOffset = parseOutlineOffset(computedStyle.outlineOffset); if (outlineWidth <= 0 || outlineStyle === "none" || outlineStyle === "hidden") { return; } const originalStrokeStyle = ctx.strokeStyle; const originalLineWidth = ctx.lineWidth; const originalLineDash = ctx.getLineDash(); ctx.strokeStyle = outlineColor; ctx.lineWidth = outlineWidth; ctx.setLineDash(getLineDashPattern2(outlineStyle, outlineWidth)); const halfWidth = outlineWidth / 2; const offset = outlineOffset + halfWidth; const outlineX = rect.left - offset; const outlineY = rect.top - offset; const outlineW = rect.width + offset * 2; const outlineH = rect.height + offset * 2; const adjustedBorderRadius = { topLeft: { horizontal: borderRadius.topLeft.horizontal === 0 ? 0 : Math.max(0, borderRadius.topLeft.horizontal + offset), vertical: borderRadius.topLeft.vertical === 0 ? 0 : Math.max(0, borderRadius.topLeft.vertical + offset) }, topRight: { horizontal: borderRadius.topRight.horizontal === 0 ? 0 : Math.max(0, borderRadius.topRight.horizontal + offset), vertical: borderRadius.topRight.vertical === 0 ? 0 : Math.max(0, borderRadius.topRight.vertical + offset) }, bottomRight: { horizontal: borderRadius.bottomRight.horizontal === 0 ? 0 : Math.max(0, borderRadius.bottomRight.horizontal + offset), vertical: borderRadius.bottomRight.vertical === 0 ? 0 : Math.max(0, borderRadius.bottomRight.vertical + offset) }, bottomLeft: { horizontal: borderRadius.bottomLeft.horizontal === 0 ? 0 : Math.max(0, borderRadius.bottomLeft.horizontal + offset), vertical: borderRadius.bottomLeft.vertical === 0 ? 0 : Math.max(0, borderRadius.bottomLeft.vertical + offset) } }; drawRoundedRectPath({ ctx, x: outlineX, y: outlineY, width: outlineW, height: outlineH, borderRadius: adjustedBorderRadius }); ctx.stroke(); ctx.strokeStyle = originalStrokeStyle; ctx.lineWidth = originalLineWidth; ctx.setLineDash(originalLineDash); }; // src/drawing/opacity.ts var setOpacity = ({ ctx, opacity }) => { const previousAlpha = ctx.globalAlpha; ctx.globalAlpha = previousAlpha * opacity; return () => { ctx.globalAlpha = previousAlpha; }; }; // src/drawing/overflow.ts var setOverflowHidden = ({ ctx, rect, borderRadius, overflowHidden, computedStyle, backgroundClip }) => { if (!overflowHidden) { return () => {}; } return setBorderRadius({ ctx, rect, borderRadius, forceClipEvenWhenZero: true, computedStyle, backgroundClip }); }; // src/drawing/transform.ts var setTransform = ({ ctx, transform, parentRect, scale }) => { const offsetMatrix = new DOMMatrix().scale(scale, scale).translate(-parentRect.x, -parentRect.y).multiply(transform).translate(parentRect.x, parentRect.y); ctx.setTransform(offsetMatrix); return () => { ctx.setTransform(new DOMMatrix); }; }; // src/drawing/draw-element.ts var drawElement = async ({ rect, computedStyle, context, draw, opacity, totalMatrix, parentRect, logLevel, element, internalState, scale }) => { const { backgroundImage, backgroundColor, backgroundClip } = computedStyle; const borderRadius = parseBorderRadius({ borderRadius: computedStyle.borderRadius, width: rect.width, height: rect.height }); const finishTransform = setTransform({ ctx: context, transform: totalMatrix, parentRect, scale }); const finishOpacity = setOpacity({ ctx: context, opacity }); drawBorderRadius({ ctx: context, computedStyle, rect, borderRadius, logLevel }); const finishBorderRadius = setBorderRadius({ ctx: context, rect, borderRadius, forceClipEvenWhenZero: false, computedStyle, backgroundClip }); await drawBackground({ backgroundImage, context, rect, backgroundColor, backgroundClip, element, logLevel, internalState, computedStyle, offsetLeft: parentRect.left, offsetTop: parentRect.top, scale }); await draw({ dimensions: rect, computedStyle, contextToDraw: context }); finishBorderRadius(); drawBorder({ ctx: context, rect, borderRadius, computedStyle }); drawOutline({ ctx: context, rect, borderRadius, computedStyle }); const finishOverflowHidden = setOverflowHidden({ ctx: context, rect, borderRadius, overflowHidden: computedStyle.overflow === "hidden", computedStyle, backgroundClip }); finishTransform(); return { cleanupAfterChildren: () => { finishOpacity(); finishOverflowHidden(); } }; }; // src/walk-tree.ts function skipToNextNonDescendant(treeWalker) { if (treeWalker.nextSibling()) { return true; } while (treeWalker.parentNode()) { if (treeWalker.nextSibling()) { return true; } } return false; } // src/get-biggest-bounding-client-rect.ts var getBiggestBoundingClientRect = (element) => { const treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT); let mostLeft = Infinity; let mostTop = Infinity; let mostRight = -Infinity; let mostBottom = -Infinity; while (true) { const computedStyle = getComputedStyle(treeWalker.currentNode); const outlineWidth = parseOutlineWidth(computedStyle.outlineWidth); const outlineOffset = parseOutlineOffset(computedStyle.outlineOffset); const rect = treeWalker.currentNode.getBoundingClientRect(); const shadows = parseBoxShadow(computedStyle.boxShadow); let shadowLeft = 0; let shadowRight = 0; let shadowTop = 0; let shadowBottom = 0; for (const shadow of shadows) { if (!shadow.inset) { shadowLeft = Math.max(shadowLeft, Math.abs(Math.min(shadow.offsetX, 0)) + shadow.blurRadius); shadowRight = Math.max(shadowRight, Math.max(shadow.offsetX, 0) + shadow.blurRadius); shadowTop = Math.max(shadowTop, Math.abs(Math.min(shadow.offsetY, 0)) + shadow.blurRadius); shadowBottom = Math.max(shadowBottom, Math.max(shadow.offsetY, 0) + shadow.blurRadius); } } mostLeft = Math.min(mostLeft, rect.left - outlineOffset - outlineWidth - shadowLeft); mostTop = Math.min(mostTop, rect.top - outlineOffset - outlineWidth - shadowTop); mostRight = Math.max(mostRight, rect.right + outlineOffset + outlineWidth + shadowRight); mostBottom = Math.max(mostBottom, rect.bottom + outlineOffset + outlineWidth + shadowBottom); if (computedStyle.overflow === "hidden") { if (!skipToNextNonDescendant(treeWalker)) { break; } } if (!treeWalker.nextNode()) { break; } } return new DOMRect(mostLeft, mostTop, mostRight - mostLeft, mostBottom - mostTop); }; // src/drawing/get-pretransform-rect.ts var MAX_SCALE_FACTOR = 100; var isScaleTooBig = (matrix) => { const origin = new DOMPoint(0, 0).matrixTransform(matrix); const unitX = new DOMPoint(1, 0).matrixTransform(matrix); const unitY = new DOMPoint(0, 1).matrixTransform(matrix); const basisX = { x: unitX.x - origin.x, y: unitX.y - origin.y }; const basisY = { x: unitY.x - origin.x, y: unitY.y - origin.y }; const scaleX = 1 / Math.hypot(basisX.x, basisX.y); const scaleY = 1 / Math.hypot(basisY.x, basisY.y); const maxScale = Math.max(scaleX, scaleY); if (maxScale > MAX_SCALE_FACTOR) { return true; } return false; }; function invertProjectivePoint(xp, yp, matrix) { const A = matrix.m11 - xp * matrix.m14; const B = matrix.m21 - xp * matrix.m24; const C = xp * matrix.m44 - matrix.m41; const D = matrix.m12 - yp * matrix.m14; const E = matrix.m22 - yp * matrix.m24; const F = yp * matrix.m44 - matrix.m42; const det = A * E - B * D; if (Math.abs(det) < 0.0000000001) { return null; } const x = (C * E - B * F) / det; const y = (A * F - C * D) / det; return { x, y }; } function getPreTransformRect(targetRect, matrix) { if (isScaleTooBig(matrix)) { return null; } const corners = [ { x: targetRect.x, y: targetRect.y }, { x: targetRect.x + targetRect.width, y: targetRect.y }, { x: targetRect.x + targetRect.width, y: targetRect.y + targetRect.height }, { x: targetRect.x, y: targetRect.y + targetRect.height } ]; const invertedCorners = []; for (const corner of corners) { const inverted = invertProjectivePoint(corner.x, corner.y, matrix); if (inverted === null) { return null; } invertedCorners.push(inverted); } const xCoords = invertedCorners.map((p) => p.x); const yCoords = invertedCorners.map((p) => p.y); return new DOMRect(Math.min(...xCoords), Math.min(...yCoords), Math.max(...xCoords) - Math.min(...xCoords), Math.max(...yCoords) - Math.min(...yCoords)); } // src/drawing/transform-in-3d.ts var vsSource = ` attribute vec2 aPosition; attribute vec2 aTexCoord; uniform mat4 uTransform; uniform vec2 uResolution; uniform vec2 uOffset; varying vec2 vTexCoord; void main() { vec4 pos = uTransform * vec4(aPosition, 0.0, 1.0); pos.xy = pos.xy + uOffset * pos.w; // Convert homogeneous coords to clip space gl_Position = vec4( (pos.x / uResolution.x) * 2.0 - pos.w, // x pos.w - (pos.y / uResolution.y) * 2.0, // y (flipped) 0.0, pos.w ); vTexCoord = aTexCoord; } `; var fsSource = ` precision mediump float; uniform sampler2D uTexture; varying vec2 vTexCoord; void main() { gl_FragColor = texture2D(uTexture, vTexCoord); } `; function compileShader(shaderGl, source, type) { const shader = shaderGl.createShader(type); if (!shader) { throw new Error("Could not create shader"); } shaderGl.shaderSource(shader, source); shaderGl.compileShader(shader); if (!shaderGl.getShaderParameter(shader, shaderGl.COMPILE_STATUS)) { const log = shaderGl.getShaderInfoLog(shader); shaderGl.deleteShader(shader); throw new Error("Shader compile error: " + log); } return shader; } var createHelperCanvas = ({ canvasWidth, canvasHeight, helperCanvasState }) => { if (helperCanvasState.current) { if (helperCanvasState.current.canvas.width !== canvasWidth || helperCanvasState.current.canvas.height !== canvasHeight) { helperCanvasState.current.canvas.width = canvasWidth; helperCanvasState.current.canvas.height = canvasHeight; } helperCanvasState.current.gl.viewport(0, 0, canvasWidth, canvasHeight); helperCanvasState.current.gl.clearColor(0, 0, 0, 0); helperCanvasState.current.gl.clear(helperCanvasState.current.gl.COLOR_BUFFER_BIT); return helperCanvasState.current; } const canvas = new OffscreenCanvas(canvasWidth, canvasHeight); const gl = canvas.getContext("webgl", { premultipliedAlpha: true }) ?? undefined; if (!gl) { throw new Error("WebGL not supported"); } const vertexShader = compileShader(gl, vsSource, gl.VERTEX_SHADER); const fragmentShader = compileShader(gl, fsSource, gl.FRAGMENT_SHADER); const program = gl.createProgram(); if (!program) { throw new Error("Could not create program"); } gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { throw new Error("Program link error: " + gl.getProgramInfoLog(program)); } const locations = { aPosition: gl.getAttribLocation(program, "aPosition"), aTexCoord: gl.getAttribLocation(program, "aTexCoord"), uTransform: gl.getUniformLocation(program, "uTransform"), uResolution: gl.getUniformLocation(program, "uResolution"), uOffset: gl.getUniformLocation(program, "uOffset"), uTexture: gl.getUniformLocation(program, "uTexture") }; gl.deleteShader(vertexShader); gl.deleteShader(fragmentShader); const cleanup = () => { gl.deleteProgram(program); const loseContext = gl.getExtension("WEBGL_lose_context"); if (loseContext) { loseContext.loseContext(); } }; helperCanvasState.current = { canvas, gl, program, locations, cleanup }; return helperCanvasState.current; }; var transformIn3d = ({ matrix, sourceCanvas, sourceRect, destRect, internalState, scale }) => { const { canvas, gl, program, locations } = createHelperCanvas({ canvasWidth: destRect.width, canvasHeight: destRect.height, helperCanvasState: internalState.helperCanvasState }); gl.useProgram(program); gl.viewport(0, 0, destRect.width, destRect.height); gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); gl.enable(gl.BLEND); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); const positions = new Float32Array([ sourceRect.x, sourceRect.y, sourceRect.x + sourceRect.width, sourceRect.y, sourceRect.x, sourceRect.y + sourceRect.height, sourceRect.x, sourceRect.y + sourceRect.height, sourceRect.x + sourceRect.width, sourceRect.y, sourceRect.x + sourceRect.width, sourceRect.y + sourceRect.height ]); gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); gl.enableVertexAttribArray(locations.aPosition); gl.vertexAttribPointer(locations.aPosition, 2, gl.FLOAT, false, 0, 0); const texCoordBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); const texCoords = new Float32Array([ 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1 ]); gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW); gl.enableVertexAttribArray(locations.aTexCoord); gl.vertexAttribPointer(locations.aTexCoord, 2, gl.FLOAT, false, 0, 0); const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, sourceCanvas); const actualMatrix = scale !== 1 ? new DOMMatrix().scale(scale, scale).multiply(matrix) : matrix; const transformMatrix = actualMatrix.toFloat32Array(); gl.uniformMatrix4fv(locations.uTransform, false, transformMatrix); gl.uniform2f(locations.uResolution, destRect.width, destRect.height); gl.uniform2f(locations.uOffset, -destRect.x, -destRect.y); gl.uniform1i(locations.uTexture, 0); gl.drawArrays(gl.TRIANGLES, 0, 6); gl.disableVertexAttribArray(locations.aPosition); gl.disableVertexAttribArray(locations.aTexCoord); gl.deleteTexture(texture); gl.deleteBuffer(positionBuffer); gl.deleteBuffer(texCoordBuffer); gl.bindTexture(gl.TEXTURE_2D, null); gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.disable(gl.BLEND); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); return canvas; }; // src/drawing/handle-3d-transform.ts var getPrecomposeRectFor3DTransform = ({ element, parentRect, matrix }) => { const unclampedBiggestBoundingClientRect = getBiggestBoundingClientRect(element); const biggestPossiblePretransformRect = getPreTransformRect(parentRect, matrix); if (!biggestPossiblePretransformRect) { return null; } const preTransformRect = getNarrowerRect({ firstRect: unclampedBiggestBoundingClientRect, secondRect: biggestPossiblePretransformRect }); return preTransformRect; }; var handle3dTransform = ({ matrix, sourceRect, tempCanvas, rectAfterTransforms, internalState, scale }) => { if (rectAfterTransforms.width <= 0 || rectAfterTransforms.height <= 0) { return null; } const transformed = transformIn3d({ sourceRect, matrix, sourceCanvas: tempCanvas, destRect: rectAfterTransforms, internalState, scale }); return transformed; }; // src/drawing/handle-mask.ts var getPrecomposeRectForMask = (element) => { const boundingRect = getBiggestBoundingClientRect(element); return boundingRect; }; var handleMask = ({ gradientInfo, rect, precomposeRect, tempContext, scale }) => { const rectToFill = new DOMRect((rect.left - precomposeRect.left) * scale, (rect.top - precomposeRect.top) * scale, rect.width * scale, rect.height * scale); const gradient = createCanvasGradient({ ctx: tempContext, rect: rectToFill, gradientInfo, offsetLeft: 0, offsetTop: 0 }); tempContext.globalCompositeOperation = "destination-in"; tempContext.fillStyle = gradient; tempContext.fillRect(rectToFill.left, rectToFill.top, rectToFill.width, rectToFill.height); }; // src/drawing/scale-rect.ts var scaleRect = ({ rect, scale }) => { return new DOMRect(rect.x * scale, rect.y * scale, rect.width * scale, rect.height * scale); }; // src/drawing/transform-rect-with-matrix.ts function transformDOMRect({ rect, matrix }) { const topLeft = new DOMPointReadOnly(rect.left, rect.top); const topRight = new DOMPointReadOnly(rect.right, rect.top); const bottomLeft = new DOMPointReadOnly(rect.left, rect.bottom); const bottomRight = new DOMPointReadOnly(rect.right, rect.bottom); const transformedTopLeft = topLeft.matrixTransform(matrix); const transformedTopRight = topRight.matrixTransform(matrix); const transformedBottomLeft = bottomLeft.matrixTransform(matrix); const transformedBottomRight = bottomRight.matrixTransform(matrix); const minX = Math.min(transformedTopLeft.x / transformedTopLeft.w, transformedTopRight.x / transformedTopRight.w, transformedBottomLeft.x / transformedBottomLeft.w, transformedBottomRight.x / transformedBottomRight.w); const maxX = Math.max(transformedTopLeft.x / transformedTopLeft.w, transformedTopRight.x / transformedTopRight.w, transformedBottomLeft.x / transformedBottomLeft.w, transformedBottomRight.x / transformedBottomRight.w); const minY = Math.min(transformedTopLeft.y / transformedTopLeft.w, transformedTopRight.y / transformedTopRight.w, transformedBottomLeft.y / transformedBottomLeft.w, transformedBottomRight.y / transformedBottomRight.w); const maxY = Math.max(transformedTopLeft.y / transformedTopLeft.w, transformedTopRight.y / transformedTopRight.w, transformedBottomLeft.y / transformedBottomLeft.w, transformedBottomRight.y / transformedBottomRight.w); return new DOMRect(minX, minY, maxX - minX, maxY - minY); } // src/drawing/process-node.ts var processNode = async ({ element, context, draw, logLevel, parentRect, internalState, rootElement, scale }) => { let __stack = []; try { const transforms = __using(__stack, calculateTransforms({ element, rootElement }), 0); const { opacity, computedStyle, totalMatrix, dimensions, precompositing } = transforms; if (opacity === 0) { return { type: "skip-children" }; } if (computedStyle.backfaceVisibility === "hidden" && totalMatrix.m33 < 0) { return { type: "skip-children" }; } if (dimensions.width <= 0 || dimensions.height <= 0) { return { type: "continue", cleanupAfterChildren: null }; } const rect = new DOMRect(dimensions.left - parentRect.x, dimensions.top - parentRect.y, dimensions.width, dimensions.height); if (precompositing.needsPrecompositing) { const start = Date.now(); let precomposeRect = null; if (precompositing.needsMaskImage) { precomposeRect = roundToExpandRect(getPrecomposeRectForMask(element)); } if (precompositing.needs3DTransformViaWebGL) { const tentativePrecomposeRect = getPrecomposeRectFor3DTransform({ element, parentRect, matrix: totalMatrix }); if (!tentativePrecomposeRect) { return { type: "continue", cleanupAfterChildren: null }; } precomposeRect = roundToExpandRect(getWiderRectAndExpand({ firstRect: precomposeRect, secondRect: tentativePrecomposeRect })); } if (!precomposeRect) { throw new Error("Precompose rect not found"); } if (precomposeRect.width <= 0 || precomposeRect.height <= 0) { return { type: "continue", cleanupAfterChildren: null }; } if (!doRectsIntersect(precomposeRect, parentRect)) { return { type: "continue", cleanupAfterChildren: null }; } const tempContext = await createLayer({ cutout: precomposeRect, element, logLevel, internalState, scale, onlyBackgroundClipText: false }); let drawable = tempContext.canvas; const rectAfterTransforms = roundToExpandRect(scaleRect({ scale, rect: transformDOMRect({ rect: precomposeRect, matrix: totalMatrix }) })); if (precompositing.needsMaskImage) { handleMask({ gradientInfo: precompositing.needsMaskImage, rect, precomposeRect, tempContext, scale }); } if (precompositing.needs3DTransformViaWebGL) { const t = handle3dTransform({ matrix: totalMatrix, sourceRect: precomposeRect, tempCanvas: drawable, rectAfterTransforms, internalState, scale }); if (t) { drawable = t; } } const previousTransform = context.getTransform(); context.setTransform(new DOMMatrix); context.drawImage(drawable, 0, drawable.height - rectAfterTransforms.height, rectAfterTransforms.width, rectAfterTransforms.height, rectAfterTransforms.left - parentRect.x, rectAfterTransforms.top - parentRect.y, rectAfterTransforms.width, rectAfterTransforms.height); context.setTransform(previousTransform); Internals6.Log.trace({ logLevel, tag: "@remotion/web-renderer" }, `Transforming element in 3D - canvas size: ${precomposeRect.width}x${precomposeRect.height} - compose: ${Date.now() - start}ms - helper canvas: ${drawable.width}x${drawable.height}`); internalState.addPrecompose({ canvasWidth: precomposeRect.width, canvasHeight: precomposeRect.height }); return { type: "skip-children" }; } const { cleanupAfterChildren } = await drawElement({ rect, computedStyle, context, draw, opacity, totalMatrix, parentRect, logLevel, element, internalState, scale }); return { type: "continue", cleanupAfterChildren }; } catch (_catch) { var _err = _catch, _hasErr = 1; } finally { __callDispose(__stack, _err, _hasErr); } }; // src/drawing/text/draw-text.ts import { Internals as Internals7 } from "remotion"; // src/drawing/text/apply-text-transform.ts var applyTextTransform = (text, transform) => { if (transform === "uppercase") { return text.toUpperCase(); } if (transform === "lowercase") { return text.toLowerCase(); } if (transform === "capitalize") { return text.replace(/\b\w/g, (char) => char.toUpperCase()); } return text; }; // src/drawing/text/find-line-breaks.text.ts var findWords = (span) => { const originalText = span.textContent; const segmenter = new Intl.Segmenter("en", { granularity: "word" }); const segments = segmenter.segment(span.textContent); const words = Array.from(segments).map((s) => s.segment); const tokens = []; for (let i = 0;i < words.length; i++) { const wordsBefore = words.slice(0, i); const wordsAfter = words.slice(i + 1); const word = words[i]; const wordsBeforeText = wordsBefore.join(""); const wordsAfterText = wordsAfter.join(""); const beforeNode = document.createTextNode(wordsBeforeText); const afterNode = document.createTextNode(wordsAfterText); const interstitialNode = document.createElement("span"); interstitialNode.textContent = word; span.textContent = ""; span.appendChild(beforeNode); span.appendChild(interstitialNode); span.appendChild(afterNode); const rect = interstitialNode.getBoundingClientRect(); span.textContent = originalText; tokens.push({ text: word, rect }); } return tokens; }; // src/drawing/text/draw-text.ts var drawText = ({ span, logLevel, onlyBackgroundClipText, parentRect }) => { const drawFn = ({ computedStyle, contextToDraw }) => { const { fontFamily, fontSize, fontWeight, direction, writingMode, letterSpacing, textTransform, webkitTextFillColor } = computedStyle; const isVertical = writingMode !== "horizontal-tb"; if (isVertical) { Internals7.Log.warn({ logLevel, tag: "@remotion/web-renderer" }, 'Detected "writing-mode" CSS property. Vertical text is not yet supported in @remotion/web-renderer'); return; } contextToDraw.save(); const fontSizePx = parseFloat(fontSize); contextToDraw.font = `${fontWeight} ${fontSizePx}px ${fontFamily}`; contextToDraw.fillStyle = onlyBackgroundClipText ? "black" : webkitTextFillColor; contextToDraw.letterSpacing = letterSpacing; const isRTL = direction === "rtl"; contextToDraw.textAlign = isRTL ? "right" : "left"; contextToDraw.textBaseline = "alphabetic"; const originalText = span.textContent; const transformedText = applyTextTransform(originalText, textTransform); span.textContent = transformedText; const tokens = findWords(span); for (const token of tokens) { const measurements = contextToDraw.measureText(originalText); const { fontBoundingBoxDescent, fontBoundingBoxAscent } = measurements; const fontHeight = fontBoundingBoxAscent + fontBoundingBoxDescent; const leading = token.rect.height - fontHeight; const halfLeading = leading / 2; contextToDraw.fillText(token.text, (isRTL ? token.rect.right : token.rect.left) - parentRect.x, token.rect.top + fontBoundingBoxAscent + halfLeading - parentRect.y); } span.textContent = originalText; contextToDraw.restore(); }; return drawFn; }; // src/drawing/text/handle-text-node.ts var handleTextNode = async ({ node, context, logLevel, parentRect, internalState, rootElement, onlyBackgroundClipText, scale }) => { const span = document.createElement("span"); const parent = node.parentNode; if (!parent) { throw new Error("Text node has no parent"); } parent.insertBefore(span, node); span.appendChild(node); const value = await processNode({ context, element: span, draw: drawText({ span, logLevel, onlyBackgroundClipText, parentRect }), logLevel, parentRect, internalState, rootElement, scale }); parent.insertBefore(node, span); parent.removeChild(span); return value; }; // src/walk-over-node.ts var walkOverNode = ({ node, context, logLevel, parentRect, internalState, rootElement, onlyBackgroundClipText, scale }) => { if (node instanceof HTMLElement || node instanceof SVGElement) { return processNode({ element: node, context, draw: drawDomElement(node), logLevel, parentRect, internalState, rootElement, scale }); } if (node instanceof Text) { return handleTextNode({ node, context, logLevel, parentRect, internalState, rootElement, onlyBackgroundClipText, scale }); } throw new Error("Unknown node type"); }; // src/compose.ts var getFilterFunction = (node) => { if (!(node instanceof Element)) { return NodeFilter.FILTER_ACCEPT; } if (node.parentElement instanceof SVGSVGElement) { return NodeFilter.FILTER_REJECT; } const computedStyle = getComputedStyle(node); if (computedStyle.display === "none") { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; }; var compose = async ({ element, context, logLevel, parentRect, internalState, onlyBackgroundClipText, scale }) => { let __stack = []; try { const treeWalker = document.createTreeWalker(element, onlyBackgroundClipText ? NodeFilter.SHOW_TEXT : NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, getFilterFunction); if (onlyBackgroundClipText) { treeWalker.nextNode(); if (!treeWalker.currentNode) { return; } } const treeWalkerClean = __using(__stack, createTreeWalkerCleanupAfterChildren(treeWalker), 0); const { checkCleanUpAtBeginningOfIteration, addCleanup } = treeWalkerClean; while (true) { checkCleanUpAtBeginningOfIteration(); const val = await walkOverNode({ node: treeWalker.currentNode, context, logLevel, parentRect, internalState, rootElement: element, onlyBackgroundClipText, scale }); if (val.type === "skip-children") { if (!skipToNextNonDescendant(treeWalker)) { break; } } else { if (val.cleanupAfterChildren) { addCleanup(treeWalker.currentNode, val.cleanupAfterChildren); } if (!treeWalker.nextNode()) { break; } } } } catch (_catch) { var _err = _catch, _hasErr = 1; } finally { __callDispose(__stack, _err, _hasErr); } }; // src/take-screenshot.ts var createLayer = async ({ element, scale, logLevel, internalState, onlyBackgroundClipText, cutout }) => { const scaledWidth = Math.ceil(cutout.width * scale); const scaledHeight = Math.ceil(cutout.height * scale); const canvas = new OffscreenCanvas(scaledWidth, scaledHeight); const context = canvas.getContext("2d"); if (!context) { throw new Error("Could not get context"); } await compose({ element, context, logLevel, parentRect: cutout, internalState, onlyBackgroundClipText, scale }); return context; }; // src/throttle-progress.ts var DEFAULT_THROTTLE_MS = 250; var createThrottledProgressCallback = (callback, throttleMs = DEFAULT_THROTTLE_MS) => { if (!callback) { return null; } let lastCallTime = 0; let pendingUpdate = null; let timeoutId = null; const throttled = (progress) => { const now = Date.now(); const timeSinceLastCall = now - lastCallTime; pendingUpdate = progress; if (timeSinceLastCall >= throttleMs) { lastCallTime = now; callback(progress); pendingUpdate = null; if (timeoutId !== null) { clearTimeout(timeoutId); timeoutId = null; } } else if (timeoutId === null) { const remainingTime = throttleMs - timeSinceLastCall; timeoutId = setTimeout(() => { if (pendingUpdate !== null) { lastCallTime = Date.now(); callback(pendingUpdate); pendingUpdate = null; } timeoutId = null; }, remainingTime); } }; const cleanup = () => { if (timeoutId !== null) { clearTimeout(timeoutId); timeoutId = null; } pendingUpdate = null; }; return { throttled, [Symbol.dispose]: cleanup }; }; // src/validate-scale.ts var validateScale = (scale) => { if (typeof scale === "undefined") { return; } if (typeof scale !== "number") { throw new Error('Scale should be a number or undefined, but is "' + JSON.stringify(scale) + '"'); } if (Number.isNaN(scale)) { throw new Error("`scale` should not be NaN, but is NaN"); } if (!Number.isFinite(scale)) { throw new Error(`"scale" must be finite, but is ${scale}`); } if (scale <= 0) { throw new Error(`"scale" must be bigger than 0, but is ${scale}`); } if (scale > 16) { throw new Error(`"scale" must be smaller or equal than 16, but is ${scale}`); } }; // src/validate-video-frame.ts var validateVideoFrame = ({ originalFrame, returnedFrame, expectedWidth, expectedHeight, expectedTimestamp }) => { if (!(returnedFrame instanceof VideoFrame)) { originalFrame.close(); throw new Error("onFrame callback must return a VideoFrame or void"); } if (returnedFrame === originalFrame) { return returnedFrame; } if (returnedFrame.displayWidth !== expectedWidth || returnedFrame.displayHeight !== expectedHeight) { originalFrame.close(); returnedFrame.close(); throw new Error(`VideoFrame dimensions mismatch: expected ${expectedWidth}x${expectedHeight}, got ${returnedFrame.displayWidth}x${returnedFrame.displayHeight}`); } if (returnedFrame.timestamp !== expectedTimestamp) { originalFrame.close(); returnedFrame.close(); throw new Error(`VideoFrame timestamp mismatch: expected ${expectedTimestamp}, got ${returnedFrame.timestamp}`); } originalFrame.close(); return returnedFrame; }; // src/with-resolvers.ts var withResolvers = function() { let resolve; let reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject }; }; // src/wait-for-ready.ts var waitForReady = ({ timeoutInMilliseconds, scope, signal, apiName, internalState, keepalive }) => { const start = performance.now(); const { promise, resolve, reject } = withResolvers(); let cancelled = false; const check = () => { if (cancelled) { return; } if (signal?.aborted) { cancelled = true; internalState?.addWaitForReadyTime(performance.now() - start); reject(new Error(`${apiName}() was cancelled`)); return; } if (scope.remotion_renderReady === true) { internalState?.addWaitForReadyTime(performance.now() - start); resolve(); return; } if (scope.remotion_cancelledError !== undefined) { cancelled = true; internalState?.addWaitForReadyTime(performance.now() - start); const stack = scope.remotion_cancelledError; const message = stack.split(` `)[0].replace(/^Error: /, ""); const error = new Error(message); error.stack = stack; reject(error); return; } if (performance.now() - start > timeoutInMilliseconds + 3000) { cancelled = true; internalState?.addWaitForReadyTime(performance.now() - start); reject(new Error(Object.values(scope.remotion_delayRenderTimeouts).map((d) => d.label).join(", "))); return; } scheduleNextCheck(); }; const scheduleNextCheck = () => { const rafTick = new Promise((res) => { requestAnimationFrame(() => res()); }); const backgroundSafeTick = keepalive ? Promise.race([rafTick, keepalive.waitForTick()]) : rafTick; backgroundSafeTick.then(check); }; check(); return promise; }; // src/web-fs-target.ts var sessionId = null; var getPrefix = () => { if (!sessionId) { sessionId = crypto.randomUUID(); } return `__remotion_render:${sessionId}:`; }; var cleanupStaleOpfsFiles = async () => { try { const root = await navigator.storage.getDirectory(); for await (const [name] of root.entries()) { if (name.startsWith("__remotion_render:") && !name.startsWith(getPrefix())) { await root.removeEntry(name); } } } catch {} }; var createWebFsTarget = async () => { const directoryHandle = await navigator.storage.getDirectory(); const filename = `${getPrefix()}${crypto.randomUUID()}`; const fileHandle = await directoryHandle.getFileHandle(filename, { create: true }); const writable = await fileHandle.createWritable(); const stream = new WritableStream({ async write(chunk) { await writable.seek(chunk.position); await writable.write(chunk); } }); const getBlob = async () => { const handle = await directoryHandle.getFileHandle(filename); return handle.getFile(); }; const close = () => writable.close(); return { stream, getBlob, close }; }; // src/render-media-on-web.tsx var internalRenderMediaOnWeb = async ({ composition, inputProps, delayRenderTimeoutInMilliseconds, logLevel, mediaCacheSizeInBytes, schema, videoCodec: codec, audioCodec: unresolvedAudioCodec, audioBitrate, container, signal, onProgress, hardwareAcceleration, keyframeIntervalInSeconds, videoBitrate, frameRange, transparent, onArtifact, onFrame, outputTarget: userDesiredOutputTarget, licenseKey, muted, scale, isProduction }) => { let __stack2 = []; try { validateScale(scale); const outputTarget = userDesiredOutputTarget === null ? await canUseWebFsWriter() ? "web-fs" : "arraybuffer" : userDesiredOutputTarget; if (outputTarget === "web-fs") { await cleanupStaleOpfsFiles(); } const format = containerToMediabunnyContainer(container); if (codec && !format.getSupportedCodecs().includes(codecToMediabunnyCodec(codec))) { return Promise.reject(new Error(`Codec ${codec} is not supported for container ${container}`)); } const resolvedAudioBitrate = typeof audioBitrate === "number" ? audioBitrate : getQualityForWebRendererQuality(audioBitrate); let finalAudioCodec = null; if (!muted) { const audioResult = await resolveAudioCodec({ container, requestedCodec: unresolvedAudioCodec, userSpecifiedAudioCodec: unresolvedAudioCodec !== undefined && unresolvedAudioCodec !== null, bitrate: resolvedAudioBitrate }); for (const issue of audioResult.issues) { if (issue.severity === "error") { return Promise.reject(new Error(issue.message)); } Internals8.Log.warn({ logLevel, tag: "@remotion/web-renderer" }, issue.message); } finalAudioCodec = audioResult.codec; } const resolved = await Internals8.resolveVideoConfig({ calculateMetadata: composition.calculateMetadata ?? null, signal: signal ?? new AbortController().signal, defaultProps: composition.defaultProps ?? {}, inputProps: inputProps ?? {}, compositionId: composition.id, compositionDurationInFrames: composition.durationInFrames ?? null, compositionFps: composition.fps ?? null, compositionHeight: composition.height ?? null, compositionWidth: composition.width ?? null }); const realFrameRange = getRealFrameRange(resolved.durationInFrames, frameRange); if (signal?.aborted) { return Promise.reject(new Error("renderMediaOnWeb() was cancelled")); } const scaffold = __using(__stack2, createScaffold({ width: resolved.width, height: resolved.height, fps: resolved.fps, durationInFrames: resolved.durationInFrames, Component: composition.component, resolvedProps: resolved.props, id: resolved.id, delayRenderTimeoutInMilliseconds, logLevel, mediaCacheSizeInBytes, schema: schema ?? null, audioEnabled: !muted, videoEnabled: true, initialFrame: 0, defaultCodec: resolved.defaultCodec, defaultOutName: resolved.defaultOutName }), 0); const { delayRenderScope, div, timeUpdater, collectAssets, errorHolder } = scaffold; const internalState = __using(__stack2, makeInternalState(), 0); const keepalive = __using(__stack2, createBackgroundKeepalive({ fps: resolved.fps, logLevel }), 0); const artifactsHandler = handleArtifacts(); const webFsTarget = outputTarget === "web-fs" ? await createWebFsTarget() : null; const target = webFsTarget ? new StreamTarget(webFsTarget.stream) : new BufferTarget; const outputWithCleanup = __using(__stack2, makeOutputWithCleanup({ format, target }), 0); const throttledProgress = __using(__stack2, createThrottledProgressCallback(onProgress), 0); const throttledOnProgress = throttledProgress?.throttled ?? null; try { let __stack = []; try { if (signal?.aborted) { throw new Error("renderMediaOnWeb() was cancelled"); } await waitForReady({ timeoutInMilliseconds: delayRenderTimeoutInMilliseconds, scope: delayRenderScope, signal, apiName: "renderMediaOnWeb", internalState, keepalive }); checkForError(errorHolder); if (signal?.aborted) { throw new Error("renderMediaOnWeb() was cancelled"); } const videoSampleSource = __using(__stack, makeVideoSampleSourceCleanup({ codec: codecToMediabunnyCodec(codec), bitrate: typeof videoBitrate === "number" ? videoBitrate : getQualityForWebRendererQuality(videoBitrate), sizeChangeBehavior: "deny", hardwareAcceleration, latencyMode: "quality", keyFrameInterval: keyframeIntervalInSeconds, alpha: transparent ? "keep" : "discard" }), 0); outputWithCleanup.output.addVideoTrack(videoSampleSource.videoSampleSource); const audioSampleSource = __using(__stack, createAudioSampleSource({ muted, codec: finalAudioCodec ? audioCodecToMediabunnyAudioCodec(finalAudioCodec) : null, bitrate: resolvedAudioBitrate }), 0); if (audioSampleSource) { outputWithCleanup.output.addAudioTrack(audioSampleSource.audioSampleSource); } await outputWithCleanup.output.start(); if (signal?.aborted) { throw new Error("renderMediaOnWeb() was cancelled"); } const progress = { renderedFrames: 0, encodedFrames: 0 }; for (let frame = realFrameRange[0];frame <= realFrameRange[1]; frame++) { if (signal?.aborted) { throw new Error("renderMediaOnWeb() was cancelled"); } timeUpdater.current?.update(frame); await waitForReady({ timeoutInMilliseconds: delayRenderTimeoutInMilliseconds, scope: delayRenderScope, signal, apiName: "renderMediaOnWeb", keepalive, internalState }); checkForError(errorHolder); if (signal?.aborted) { throw new Error("renderMediaOnWeb() was cancelled"); } const createFrameStart = performance.now(); const layer = await createLayer({ element: div, scale, logLevel, internalState, onlyBackgroundClipText: false, cutout: new DOMRect(0, 0, resolved.width, resolved.height) }); internalState.addCreateFrameTime(performance.now() - createFrameStart); if (signal?.aborted) { throw new Error("renderMediaOnWeb() was cancelled"); } const timestamp = Math.round((frame - realFrameRange[0]) / resolved.fps * 1e6); const videoFrame = new VideoFrame(layer.canvas, { timestamp }); progress.renderedFrames++; throttledOnProgress?.({ ...progress }); let frameToEncode = videoFrame; if (onFrame) { const returnedFrame = await onFrame(videoFrame); if (signal?.aborted) { throw new Error("renderMediaOnWeb() was cancelled"); } frameToEncode = validateVideoFrame({ originalFrame: videoFrame, returnedFrame, expectedWidth: Math.round(resolved.width * scale), expectedHeight: Math.round(resolved.height * scale), expectedTimestamp: timestamp }); } const audioCombineStart = performance.now(); const assets = collectAssets.current.collectAssets(); if (onArtifact) { await artifactsHandler.handle({ imageData: layer.canvas, frame, assets, onArtifact }); } if (signal?.aborted) { throw new Error("renderMediaOnWeb() was cancelled"); } const audio = muted ? null : onlyInlineAudio({ assets, fps: resolved.fps, timestamp }); internalState.addAudioMixingTime(performance.now() - audioCombineStart); const addSampleStart = performance.now(); await Promise.all([ addVideoSampleAndCloseFrame(frameToEncode, videoSampleSource.videoSampleSource), audio && audioSampleSource ? addAudioSample(audio, audioSampleSource.audioSampleSource) : Promise.resolve() ]); internalState.addAddSampleTime(performance.now() - addSampleStart); progress.encodedFrames++; throttledOnProgress?.({ ...progress }); if (signal?.aborted) { throw new Error("renderMediaOnWeb() was cancelled"); } } onProgress?.({ ...progress }); videoSampleSource.videoSampleSource.close(); audioSampleSource?.audioSampleSource.close(); await outputWithCleanup.output.finalize(); Internals8.Log.verbose({ logLevel, tag: "web-renderer" }, `Render timings: waitForReady=${internalState.getWaitForReadyTime().toFixed(2)}ms, createFrame=${internalState.getCreateFrameTime().toFixed(2)}ms, addSample=${internalState.getAddSampleTime().toFixed(2)}ms, audioMixing=${internalState.getAudioMixingTime().toFixed(2)}ms`); if (webFsTarget) { sendUsageEvent({ licenseKey: licenseKey ?? null, succeeded: true, apiName: "renderMediaOnWeb", isStill: false, isProduction: isProduction ?? true }); await webFsTarget.close(); return { getBlob: () => { return webFsTarget.getBlob(); }, internalState }; } if (!(target instanceof BufferTarget)) { throw new Error("Expected target to be a BufferTarget"); } sendUsageEvent({ licenseKey: licenseKey ?? null, succeeded: true, apiName: "renderMediaOnWeb", isStill: false, isProduction: isProduction ?? true }); return { getBlob: () => { if (!target.buffer) { throw new Error("The resulting buffer is empty"); } return Promise.resolve(new Blob([target.buffer], { type: getMimeType(container) })); }, internalState }; } catch (_catch) { var _err = _catch, _hasErr = 1; } finally { __callDispose(__stack, _err, _hasErr); } } catch (err) { if (!signal?.aborted) { sendUsageEvent({ succeeded: false, licenseKey: licenseKey ?? null, apiName: "renderMediaOnWeb", isStill: false, isProduction: isProduction ?? true }).catch((err2) => { Internals8.Log.error({ logLevel: "error", tag: "web-renderer" }, "Failed to send usage event", err2); }); } throw err; } } catch (_catch2) { var _err2 = _catch2, _hasErr2 = 1; } finally { __callDispose(__stack2, _err2, _hasErr2); } }; var renderMediaOnWeb = (options) => { const container = options.container ?? "mp4"; const codec = options.videoCodec ?? getDefaultVideoCodecForContainer(container); onlyOneRenderAtATimeQueue.ref = onlyOneRenderAtATimeQueue.ref.catch(() => Promise.resolve()).then(() => internalRenderMediaOnWeb({ ...options, delayRenderTimeoutInMilliseconds: options.delayRenderTimeoutInMilliseconds ?? 30000, logLevel: options.logLevel ?? window.remotion_logLevel ?? "info", schema: options.schema ?? undefined, mediaCacheSizeInBytes: options.mediaCacheSizeInBytes ?? null, videoCodec: codec, audioCodec: options.audioCodec ?? null, audioBitrate: options.audioBitrate ?? "medium", container, signal: options.signal ?? null, onProgress: options.onProgress ?? null, hardwareAcceleration: options.hardwareAcceleration ?? "no-preference", keyframeIntervalInSeconds: options.keyframeIntervalInSeconds ?? 5, videoBitrate: options.videoBitrate ?? "medium", frameRange: options.frameRange ?? null, transparent: options.transparent ?? false, onArtifact: options.onArtifact ?? null, onFrame: options.onFrame ?? null, outputTarget: options.outputTarget ?? null, licenseKey: options.licenseKey ?? null, muted: options.muted ?? false, scale: options.scale ?? 1, isProduction: options.isProduction ?? true })); return onlyOneRenderAtATimeQueue.ref; }; // src/render-still-on-web.tsx import { Internals as Internals9 } from "remotion"; async function internalRenderStillOnWeb({ frame, delayRenderTimeoutInMilliseconds, logLevel, inputProps, schema, imageFormat, mediaCacheSizeInBytes, composition, signal, onArtifact, licenseKey, scale, isProduction }) { let __stack = []; try { validateScale(scale); const resolved = await Internals9.resolveVideoConfig({ calculateMetadata: composition.calculateMetadata ?? null, signal: signal ?? new AbortController().signal, defaultProps: composition.defaultProps ?? {}, inputProps: inputProps ?? {}, compositionId: composition.id, compositionDurationInFrames: composition.durationInFrames ?? null, compositionFps: composition.fps ?? null, compositionHeight: composition.height ?? null, compositionWidth: composition.width ?? null }); if (signal?.aborted) { return Promise.reject(new Error("renderStillOnWeb() was cancelled")); } const internalState = __using(__stack, makeInternalState(), 0); const scaffold = __using(__stack, createScaffold({ width: resolved.width, height: resolved.height, delayRenderTimeoutInMilliseconds, logLevel, resolvedProps: resolved.props, id: resolved.id, mediaCacheSizeInBytes, audioEnabled: false, Component: composition.component, videoEnabled: true, durationInFrames: resolved.durationInFrames, fps: resolved.fps, schema: schema ?? null, initialFrame: frame, defaultCodec: resolved.defaultCodec, defaultOutName: resolved.defaultOutName }), 0); const { delayRenderScope, div, collectAssets, errorHolder } = scaffold; const artifactsHandler = handleArtifacts(); try { if (signal?.aborted) { throw new Error("renderStillOnWeb() was cancelled"); } await waitForReady({ timeoutInMilliseconds: delayRenderTimeoutInMilliseconds, scope: delayRenderScope, signal, apiName: "renderStillOnWeb", internalState: null, keepalive: null }); checkForError(errorHolder); if (signal?.aborted) { throw new Error("renderStillOnWeb() was cancelled"); } const capturedFrame = await createLayer({ element: div, scale, logLevel, internalState, onlyBackgroundClipText: false, cutout: new DOMRect(0, 0, resolved.width, resolved.height) }); const imageData = await capturedFrame.canvas.convertToBlob({ type: `image/${imageFormat}` }); const assets = collectAssets.current.collectAssets(); if (onArtifact) { await artifactsHandler.handle({ imageData, frame, assets, onArtifact }); } sendUsageEvent({ licenseKey: licenseKey ?? null, succeeded: true, apiName: "renderStillOnWeb", isStill: true, isProduction }); return { blob: imageData, internalState }; } catch (err) { if (!signal?.aborted) { sendUsageEvent({ succeeded: false, licenseKey: licenseKey ?? null, apiName: "renderStillOnWeb", isStill: true, isProduction }).catch((err2) => { Internals9.Log.error({ logLevel: "error", tag: "web-renderer" }, "Failed to send usage event", err2); }); } throw err; } } catch (_catch) { var _err = _catch, _hasErr = 1; } finally { __callDispose(__stack, _err, _hasErr); } } var renderStillOnWeb = (options) => { onlyOneRenderAtATimeQueue.ref = onlyOneRenderAtATimeQueue.ref.catch(() => Promise.resolve()).then(() => internalRenderStillOnWeb({ ...options, delayRenderTimeoutInMilliseconds: options.delayRenderTimeoutInMilliseconds ?? 30000, logLevel: options.logLevel ?? window.remotion_logLevel ?? "info", schema: options.schema ?? undefined, mediaCacheSizeInBytes: options.mediaCacheSizeInBytes ?? null, signal: options.signal ?? null, onArtifact: options.onArtifact ?? null, licenseKey: options.licenseKey ?? null, scale: options.scale ?? 1, isProduction: options.isProduction ?? true })); return onlyOneRenderAtATimeQueue.ref; }; export { renderStillOnWeb, renderMediaOnWeb, getSupportedVideoCodecsForContainer, getSupportedAudioCodecsForContainer, getEncodableVideoCodecs, getEncodableAudioCodecs, getDefaultVideoCodecForContainer, getDefaultAudioCodecForContainer, canRenderMediaOnWeb };