@effing/ffs 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ffsFetch,
3
3
  storeKeys
4
- } from "./chunk-UN34ESVZ.js";
4
+ } from "./chunk-WB5NFSQQ.js";
5
5
 
6
6
  // src/render.ts
7
7
  import { Readable } from "stream";
@@ -290,6 +290,9 @@ var FFmpegRunner = class {
290
290
  transformedStream.pipe(writeStream);
291
291
  writeStream.on("finish", next);
292
292
  writeStream.on("error", reject);
293
+ } else {
294
+ stream2.resume();
295
+ next();
293
296
  }
294
297
  });
295
298
  extract.on("finish", resolve);
@@ -936,4 +939,4 @@ export {
936
939
  FFmpegRunner,
937
940
  EffieRenderer
938
941
  };
939
- //# sourceMappingURL=chunk-2SB5D32U.js.map
942
+ //# sourceMappingURL=chunk-O3UH3WAZ.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/render.ts","../src/motion.ts","../src/effect.ts","../src/ffmpeg.ts","../src/transition.ts"],"sourcesContent":["import { Readable } from \"stream\";\nimport { createReadStream } from \"fs\";\nimport { processMotion } from \"./motion\";\nimport { processEffects } from \"./effect\";\nimport type { FFmpegInput } from \"./ffmpeg\";\nimport { FFmpegCommand, FFmpegRunner } from \"./ffmpeg\";\nimport { processTransition } from \"./transition\";\nimport type { EffieData, EffieSources, EffieWebUrl } from \"@effing/effie\";\nimport sharp from \"sharp\";\nimport { ffsFetch } from \"./fetch\";\nimport { fileURLToPath } from \"url\";\nimport { storeKeys } from \"./storage\";\nimport type { TransientStore } from \"./storage\";\nimport type { HttpProxy } from \"./proxy\";\n\nexport type EffieRendererOptions = {\n /**\n * Allow reading from local file paths.\n * WARNING: Only enable this for trusted internal operations.\n * Enabling this for user-provided data is a security risk.\n * @default false\n */\n allowLocalFiles?: boolean;\n /**\n * Transient store instance for source lookups.\n * If not provided, sources will be fetched directly from network.\n */\n transientStore?: TransientStore;\n /**\n * HTTP proxy for video/audio URLs.\n * When provided, HTTP(S) URLs for video/audio inputs will be routed\n * through this proxy, allowing Node.js to handle DNS resolution\n * instead of FFmpeg (useful for Alpine Linux with musl libc).\n */\n httpProxy?: HttpProxy;\n};\n\nexport class EffieRenderer<U extends string = EffieWebUrl> {\n private effieData: EffieData<EffieSources<U>, U>;\n private ffmpegRunner?: FFmpegRunner;\n private allowLocalFiles: boolean;\n private transientStore?: TransientStore;\n private httpProxy?: HttpProxy;\n\n constructor(\n effieData: EffieData<EffieSources<U>, U>,\n options?: EffieRendererOptions,\n ) {\n this.effieData = effieData;\n this.allowLocalFiles = options?.allowLocalFiles ?? false;\n this.transientStore = options?.transientStore;\n this.httpProxy = options?.httpProxy;\n }\n\n private async fetchSource(src: string): Promise<Readable> {\n // src is already a resolved URL - no #ref handling needed\n // (references are resolved in FFmpegRunner via referenceResolver)\n\n // Handle data URLs (inline, no actual fetch or cache needed)\n if (src.startsWith(\"data:\")) {\n const commaIndex = src.indexOf(\",\");\n if (commaIndex === -1) {\n throw new Error(\"Invalid data URL\");\n }\n const meta = src.slice(5, commaIndex); // after \"data:\"\n const isBase64 = meta.endsWith(\";base64\");\n const data = src.slice(commaIndex + 1);\n const buffer = isBase64\n ? Buffer.from(data, \"base64\")\n : Buffer.from(decodeURIComponent(data));\n return Readable.from(buffer);\n }\n\n // Handle local file paths, if allowed\n if (src.startsWith(\"file:\")) {\n if (!this.allowLocalFiles) {\n throw new Error(\n \"Local file paths are not allowed. Use allowLocalFiles option for trusted operations.\",\n );\n }\n return createReadStream(fileURLToPath(src));\n }\n\n // If we have a transient store, check the store first\n if (this.transientStore) {\n const cachedStream = await this.transientStore.getStream(\n storeKeys.source(src),\n );\n if (cachedStream) {\n return cachedStream;\n }\n }\n\n // Fetch from network\n const response = await ffsFetch(src, {\n headersTimeout: 10 * 60 * 1000, // 10 minutes\n bodyTimeout: 20 * 60 * 1000, // 20 minutes\n });\n if (!response.ok) {\n throw new Error(\n `Failed to fetch ${src}: ${response.status} ${response.statusText}`,\n );\n }\n if (!response.body) {\n throw new Error(`No body for ${src}`);\n }\n // Convert WHATWG ReadableStream to Node.js Readable\n return Readable.fromWeb(response.body);\n }\n\n private buildAudioFilter({\n duration,\n volume,\n fadeIn,\n fadeOut,\n }: {\n duration: number;\n volume?: number;\n fadeIn?: number;\n fadeOut?: number;\n }) {\n const filters = [];\n if (volume !== undefined) {\n filters.push(`volume=${volume}`);\n }\n if (fadeIn !== undefined) {\n filters.push(`afade=type=in:start_time=0:duration=${fadeIn}`);\n }\n if (fadeOut !== undefined) {\n filters.push(\n `afade=type=out:start_time=${duration - fadeOut}:duration=${fadeOut}`,\n );\n }\n return filters.length ? filters.join(\",\") : \"anull\";\n }\n\n private getFrameDimensions(scaleFactor: number) {\n // Round down to the nearest even number for H.264 compatibility\n return {\n frameWidth: Math.floor((this.effieData.width * scaleFactor) / 2) * 2,\n frameHeight: Math.floor((this.effieData.height * scaleFactor) / 2) * 2,\n };\n }\n\n /**\n * Builds an FFmpeg input for a background (global or segment).\n */\n private buildBackgroundInput(\n background: EffieData<EffieSources<U>, U>[\"background\"],\n inputIndex: number,\n frameWidth: number,\n frameHeight: number,\n ): FFmpegInput {\n if (background.type === \"image\") {\n return {\n index: inputIndex,\n source: background.source,\n preArgs: [\"-loop\", \"1\", \"-framerate\", this.effieData.fps.toString()],\n type: \"image\",\n };\n } else if (background.type === \"video\") {\n return {\n index: inputIndex,\n source: background.source,\n preArgs: [\"-stream_loop\", \"-1\"],\n type: \"video\",\n };\n }\n // Color background - use lavfi to generate\n return {\n index: inputIndex,\n source: \"\",\n preArgs: [\n \"-f\",\n \"lavfi\",\n \"-i\",\n `color=${background.color}:size=${frameWidth}x${frameHeight}:rate=${this.effieData.fps}`,\n ],\n type: \"color\",\n };\n }\n\n private buildOutputArgs(outputFilename: string): string[] {\n return [\n \"-map\",\n \"[outv]\",\n \"-map\",\n \"[outa]\",\n \"-c:v\",\n \"libx264\",\n \"-r\",\n this.effieData.fps.toString(),\n \"-pix_fmt\",\n \"yuv420p\",\n \"-preset\",\n \"fast\",\n \"-crf\",\n \"28\",\n \"-c:a\",\n \"aac\",\n \"-movflags\",\n \"frag_keyframe+empty_moov\",\n \"-f\",\n \"mp4\",\n outputFilename,\n ];\n }\n\n private buildLayerInput(\n layer: EffieData<EffieSources<U>, U>[\"segments\"][0][\"layers\"][0],\n duration: number,\n inputIndex: number,\n ): FFmpegInput {\n let preArgs: string[] = [];\n if (layer.type === \"image\") {\n preArgs = [\n \"-loop\",\n \"1\",\n \"-t\",\n duration.toString(),\n \"-framerate\",\n this.effieData.fps.toString(),\n ];\n } else if (layer.type === \"animation\") {\n preArgs = [\"-f\", \"image2\", \"-framerate\", this.effieData.fps.toString()];\n }\n return {\n index: inputIndex,\n source: layer.source,\n preArgs,\n type: layer.type,\n };\n }\n\n /**\n * Builds filter chain for all layers in a segment.\n * @param segment - The segment containing layers\n * @param bgLabel - Label for the background input (e.g., \"bg_seg0\" or \"bg_seg\")\n * @param labelPrefix - Prefix for generated labels (e.g., \"seg0_\" or \"\")\n * @param layerInputOffset - Starting input index for layers\n * @param frameWidth - Frame width for nullsrc\n * @param frameHeight - Frame height for nullsrc\n * @param outputLabel - Label for the final video output\n * @returns Array of filter parts to add to the filter chain\n */\n private buildLayerFilters(\n segment: EffieData<EffieSources<U>, U>[\"segments\"][0],\n bgLabel: string,\n labelPrefix: string,\n layerInputOffset: number,\n frameWidth: number,\n frameHeight: number,\n outputLabel: string,\n ): string[] {\n const filterParts: string[] = [];\n let currentVidLabel = bgLabel;\n\n for (let l = 0; l < segment.layers.length; l++) {\n const inputIdx = layerInputOffset + l;\n const layerLabel = `${labelPrefix}layer${l}`;\n const layer = segment.layers[l];\n const effectChain = layer.effects\n ? processEffects(\n layer.effects,\n this.effieData.fps,\n frameWidth,\n frameHeight,\n )\n : \"\";\n filterParts.push(\n `[${inputIdx}:v]trim=start=0:duration=${segment.duration},${\n effectChain ? effectChain + \",\" : \"\"\n }setsar=1,setpts=PTS-STARTPTS[${layerLabel}]`,\n );\n let overlayInputLabel = layerLabel;\n const delay = layer.delay ?? 0;\n if (delay > 0) {\n filterParts.push(\n `nullsrc=size=${frameWidth}x${frameHeight}:duration=${delay},setpts=PTS-STARTPTS[null_${layerLabel}]`,\n );\n filterParts.push(\n `[null_${layerLabel}][${layerLabel}]concat=n=2:v=1:a=0[delayed_${layerLabel}]`,\n );\n overlayInputLabel = `delayed_${layerLabel}`;\n }\n const overlayOutputLabel = `${labelPrefix}tmp${l}`;\n const offset = layer.motion ? processMotion(delay, layer.motion) : \"0:0\";\n const fromTime = layer.from ?? 0;\n const untilTime = layer.until ?? segment.duration;\n filterParts.push(\n `[${currentVidLabel}][${overlayInputLabel}]overlay=${offset}:enable='between(t,${fromTime},${untilTime})',fps=${this.effieData.fps}[${overlayOutputLabel}]`,\n );\n currentVidLabel = overlayOutputLabel;\n }\n filterParts.push(`[${currentVidLabel}]null[${outputLabel}]`);\n\n return filterParts;\n }\n\n /**\n * Applies xfade/concat transitions between video segments.\n * Modifies videoSegmentLabels in place to update labels after transitions.\n * @param filterParts - Array to append filter parts to\n * @param videoSegmentLabels - Array of video segment labels (modified in place)\n */\n private applyTransitions(\n filterParts: string[],\n videoSegmentLabels: string[],\n ): void {\n let transitionOffset = 0;\n this.effieData.segments.forEach((segment, i) => {\n if (i === 0) {\n transitionOffset = segment.duration;\n return;\n }\n const combineLabel = `[vid_com${i}]`;\n if (!segment.transition) {\n transitionOffset += segment.duration;\n filterParts.push(\n `${videoSegmentLabels[i - 1]}${\n videoSegmentLabels[i]\n }concat=n=2:v=1:a=0,fps=${this.effieData.fps}${combineLabel}`,\n );\n videoSegmentLabels[i] = combineLabel;\n return;\n }\n const transitionName = processTransition(segment.transition);\n const transitionDuration = segment.transition.duration;\n transitionOffset -= transitionDuration;\n filterParts.push(\n `${videoSegmentLabels[i - 1]}${\n videoSegmentLabels[i]\n }xfade=transition=${transitionName}:duration=${transitionDuration}:offset=${transitionOffset}${combineLabel}`,\n );\n videoSegmentLabels[i] = combineLabel;\n transitionOffset += segment.duration;\n });\n filterParts.push(`${videoSegmentLabels.at(-1)}null[outv]`);\n }\n\n /**\n * Applies general audio mixing: concats segment audio and mixes with global audio if present.\n * @param filterParts - Array to append filter parts to\n * @param audioSegmentLabels - Array of audio segment labels to concat\n * @param totalDuration - Total duration for audio trimming\n * @param generalAudioInputIndex - Input index for general audio (if present)\n */\n private applyGeneralAudio(\n filterParts: string[],\n audioSegmentLabels: string[],\n totalDuration: number,\n generalAudioInputIndex: number,\n ): void {\n if (this.effieData.audio) {\n const audioSeek = this.effieData.audio.seek ?? 0;\n const generalAudioFilter = this.buildAudioFilter({\n duration: totalDuration,\n volume: this.effieData.audio.volume,\n fadeIn: this.effieData.audio.fadeIn,\n fadeOut: this.effieData.audio.fadeOut,\n });\n filterParts.push(\n `[${generalAudioInputIndex}:a]atrim=start=${audioSeek}:duration=${totalDuration},${generalAudioFilter},asetpts=PTS-STARTPTS[general_audio]`,\n );\n filterParts.push(\n `${audioSegmentLabels.join(\"\")}concat=n=${\n this.effieData.segments.length\n }:v=0:a=1,atrim=start=0:duration=${totalDuration}[segments_audio]`,\n );\n filterParts.push(\n `[general_audio][segments_audio]amix=inputs=2:duration=longest[outa]`,\n );\n } else {\n filterParts.push(\n `${audioSegmentLabels.join(\"\")}concat=n=${\n this.effieData.segments.length\n }:v=0:a=1[outa]`,\n );\n }\n }\n\n private buildFFmpegCommand(\n outputFilename: string,\n scaleFactor: number = 1,\n ): FFmpegCommand {\n const globalArgs: string[] = [\"-y\", \"-loglevel\", \"error\"];\n const inputs: FFmpegInput[] = [];\n let inputIndex = 0;\n\n const { frameWidth, frameHeight } = this.getFrameDimensions(scaleFactor);\n const backgroundSeek =\n this.effieData.background.type === \"video\"\n ? (this.effieData.background.seek ?? 0)\n : 0;\n\n // Global background input:\n inputs.push(\n this.buildBackgroundInput(\n this.effieData.background,\n inputIndex,\n frameWidth,\n frameHeight,\n ),\n );\n const globalBgInputIdx = inputIndex;\n inputIndex++;\n\n // Segment background inputs:\n const segmentBgInputIndices: (number | null)[] = [];\n for (const segment of this.effieData.segments) {\n if (segment.background) {\n inputs.push(\n this.buildBackgroundInput(\n segment.background,\n inputIndex,\n frameWidth,\n frameHeight,\n ),\n );\n segmentBgInputIndices.push(inputIndex);\n inputIndex++;\n } else {\n segmentBgInputIndices.push(null);\n }\n }\n\n // Identify segments using global background\n const globalBgSegmentIndices: number[] = [];\n for (let i = 0; i < this.effieData.segments.length; i++) {\n if (segmentBgInputIndices[i] === null) {\n globalBgSegmentIndices.push(i);\n }\n }\n\n // Layer inputs:\n for (const segment of this.effieData.segments) {\n for (const layer of segment.layers) {\n inputs.push(this.buildLayerInput(layer, segment.duration, inputIndex));\n inputIndex++;\n }\n }\n\n // Audio inputs:\n for (const segment of this.effieData.segments) {\n if (segment.audio) {\n inputs.push({\n index: inputIndex,\n source: segment.audio.source,\n preArgs: [],\n type: \"audio\",\n });\n inputIndex++;\n }\n }\n\n // General audio input:\n if (this.effieData.audio) {\n inputs.push({\n index: inputIndex,\n source: this.effieData.audio.source,\n preArgs: [],\n type: \"audio\",\n });\n inputIndex++;\n }\n\n // Compute how many video inputs we have:\n const numSegmentBgInputs = segmentBgInputIndices.filter(\n (i) => i !== null,\n ).length;\n const numVideoInputs =\n 1 +\n numSegmentBgInputs +\n this.effieData.segments.reduce((sum, seg) => sum + seg.layers.length, 0);\n let audioCounter = 0;\n\n // Build filter_complex:\n let currentTime = 0;\n let layerInputOffset = 1 + numSegmentBgInputs; // Global background is input 0\n const filterParts: string[] = [];\n const videoSegmentLabels: string[] = [];\n const audioSegmentLabels: string[] = [];\n\n // Build split/fifo chain for global background\n const globalBgFifoLabels: Map<number, string> = new Map();\n const bgFilter = `fps=${this.effieData.fps},scale=${frameWidth}x${frameHeight}:force_original_aspect_ratio=increase,crop=${frameWidth}:${frameHeight}`;\n if (globalBgSegmentIndices.length === 1) {\n // Single segment - no split needed, just fifo\n const fifoLabel = `bg_fifo_0`;\n filterParts.push(`[${globalBgInputIdx}:v]${bgFilter},fifo[${fifoLabel}]`);\n globalBgFifoLabels.set(globalBgSegmentIndices[0], fifoLabel);\n } else if (globalBgSegmentIndices.length > 1) {\n // Multiple segments - use split + fifo\n const splitCount = globalBgSegmentIndices.length;\n const splitOutputLabels = globalBgSegmentIndices.map(\n (_, i) => `bg_split_${i}`,\n );\n\n filterParts.push(\n `[${globalBgInputIdx}:v]${bgFilter},split=${splitCount}${splitOutputLabels.map((l) => `[${l}]`).join(\"\")}`,\n );\n\n for (let i = 0; i < splitCount; i++) {\n const fifoLabel = `bg_fifo_${i}`;\n filterParts.push(`[${splitOutputLabels[i]}]fifo[${fifoLabel}]`);\n globalBgFifoLabels.set(globalBgSegmentIndices[i], fifoLabel);\n }\n }\n\n for (let segIdx = 0; segIdx < this.effieData.segments.length; segIdx++) {\n const segment = this.effieData.segments[segIdx];\n\n // Determine background for this segment (segment bg overrides global bg)\n const bgLabel = `bg_seg${segIdx}`;\n if (segment.background) {\n // Use segment background\n const segBgInputIdx = segmentBgInputIndices[segIdx]!;\n const segBgSeek =\n segment.background.type === \"video\"\n ? (segment.background.seek ?? 0)\n : 0;\n filterParts.push(\n `[${segBgInputIdx}:v]fps=${this.effieData.fps},scale=${frameWidth}x${frameHeight}:force_original_aspect_ratio=increase,crop=${frameWidth}:${frameHeight},trim=start=${segBgSeek}:duration=${segment.duration},setpts=PTS-STARTPTS[${bgLabel}]`,\n );\n } else {\n // Use global background (via split/fifo chain)\n const fifoLabel = globalBgFifoLabels.get(segIdx);\n if (fifoLabel) {\n // fps/scale already applied in split/fifo chain\n filterParts.push(\n `[${fifoLabel}]trim=start=${backgroundSeek + currentTime}:duration=${segment.duration},setpts=PTS-STARTPTS[${bgLabel}]`,\n );\n }\n }\n\n // Process layers\n const vidLabel = `vid_seg${segIdx}`;\n filterParts.push(\n ...this.buildLayerFilters(\n segment,\n bgLabel,\n `seg${segIdx}_`,\n layerInputOffset,\n frameWidth,\n frameHeight,\n vidLabel,\n ),\n );\n layerInputOffset += segment.layers.length;\n videoSegmentLabels.push(`[${vidLabel}]`);\n\n const nextSegment = this.effieData.segments[segIdx + 1];\n const transitionDuration = nextSegment?.transition?.duration ?? 0;\n // Ensure audio duration is always at least 0.001 seconds to avoid FFmpeg misbehavior\n const realDuration = Math.max(\n 0.001,\n segment.duration - transitionDuration,\n );\n\n // Process audio: use the corresponding audio input index if audio exists\n if (segment.audio) {\n // Audio inputs start after all video inputs\n const audioInputIndex = numVideoInputs + audioCounter;\n const audioFilter = this.buildAudioFilter({\n duration: realDuration,\n volume: segment.audio.volume,\n fadeIn: segment.audio.fadeIn,\n fadeOut: segment.audio.fadeOut,\n });\n filterParts.push(\n `[${audioInputIndex}:a]atrim=start=0:duration=${realDuration},${audioFilter},asetpts=PTS-STARTPTS[aud_seg${segIdx}]`,\n );\n audioCounter++;\n } else {\n filterParts.push(\n `anullsrc=r=44100:cl=stereo,atrim=start=0:duration=${realDuration},asetpts=PTS-STARTPTS[aud_seg${segIdx}]`,\n );\n }\n audioSegmentLabels.push(`[aud_seg${segIdx}]`);\n\n currentTime += realDuration;\n }\n\n // Add general audio if present\n this.applyGeneralAudio(\n filterParts,\n audioSegmentLabels,\n currentTime,\n numVideoInputs + audioCounter,\n );\n\n // Apply transitions between video segments\n this.applyTransitions(filterParts, videoSegmentLabels);\n\n const filterComplex = filterParts.join(\";\");\n const outputArgs = this.buildOutputArgs(outputFilename);\n\n return new FFmpegCommand(globalArgs, inputs, filterComplex, outputArgs);\n }\n\n private createImageTransformer(scaleFactor: number) {\n return async (imageStream: Readable): Promise<Readable> => {\n if (scaleFactor === 1) return imageStream;\n\n const sharpTransformer = sharp();\n imageStream.on(\"error\", (err) => {\n if (!sharpTransformer.destroyed) {\n sharpTransformer.destroy(err);\n }\n });\n sharpTransformer.on(\"error\", (err) => {\n if (!imageStream.destroyed) {\n imageStream.destroy(err);\n }\n });\n imageStream.pipe(sharpTransformer);\n try {\n const metadata = await sharpTransformer.metadata();\n const imageWidth = metadata.width ?? this.effieData.width;\n const imageHeight = metadata.height ?? this.effieData.height;\n return sharpTransformer.resize({\n width: Math.floor(imageWidth * scaleFactor),\n height: Math.floor(imageHeight * scaleFactor),\n });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (!sharpTransformer.destroyed) {\n sharpTransformer.destroy(error);\n }\n throw error;\n }\n };\n }\n\n /**\n * Resolves a source reference to its actual URL.\n * If the source is a #reference, returns the resolved URL.\n * Otherwise, returns the source as-is.\n */\n private resolveReference(src: string): string {\n if (src.startsWith(\"#\")) {\n const sourceName = src.slice(1);\n if (sourceName in this.effieData.sources!) {\n return this.effieData.sources![sourceName];\n }\n }\n return src;\n }\n\n /**\n * Renders the effie data to a video stream.\n * @param scaleFactor - Scale factor for output dimensions\n */\n async render(scaleFactor = 1): Promise<Readable> {\n const ffmpegCommand = this.buildFFmpegCommand(\"-\", scaleFactor);\n this.ffmpegRunner = new FFmpegRunner(ffmpegCommand);\n\n // Create URL transformer for proxy if available\n const urlTransformer = this.httpProxy\n ? (url: string) => this.httpProxy!.transformUrl(url)\n : undefined;\n\n return this.ffmpegRunner.run(\n async ({ src }) => this.fetchSource(src),\n this.createImageTransformer(scaleFactor),\n (src) => this.resolveReference(src),\n urlTransformer,\n );\n }\n\n close(): void {\n if (this.ffmpegRunner) {\n this.ffmpegRunner.close();\n }\n }\n}\n","import type { EffieMotion } from \"@effing/effie\";\n\n/**\n * Defines the building blocks for an FFmpeg motion expression.\n */\ntype MotionComponents = {\n initialX: string; // Expression for X before animation starts\n initialY: string; // Expression for Y before animation starts\n activeX: string; // Expression for X during animation (incorporates relative time)\n activeY: string; // Expression for Y during animation (incorporates relative time)\n finalX: string; // Expression for X after animation ends\n finalY: string; // Expression for Y after animation ends\n duration: number; // Duration of the animation effect itself\n};\n\nfunction getEasingExpression(\n tNormExpr: string,\n easingType: \"linear\" | \"ease-in\" | \"ease-out\" | \"ease-in-out\",\n): string {\n switch (easingType) {\n case \"ease-in\":\n // t^2\n return `pow(${tNormExpr},2)`;\n case \"ease-out\":\n // 1 - (1-t)^2\n return `(1-pow(1-(${tNormExpr}),2))`;\n case \"ease-in-out\":\n // t < 0.5 ? 2*t^2 : 1 - (-2*t + 2)^2 / 2\n return `if(lt(${tNormExpr},0.5),2*pow(${tNormExpr},2),1-pow(-2*(${tNormExpr})+2,2)/2)`;\n case \"linear\":\n default:\n // Default to linear if type is unknown or \"linear\"\n return `(${tNormExpr})`; // Ensure parentheses for safety if tNormExpr is complex\n }\n}\n\nfunction processSlideMotion(\n motion: Extract<EffieMotion, { type: \"slide\" }>,\n relativeTimeExpr: string,\n): MotionComponents {\n const duration = motion.duration ?? 1;\n const distance = motion.distance ?? 1;\n const reverse = motion.reverse ?? false;\n const easing = motion.easing ?? \"linear\"; // Default to linear easing\n\n // 1. Calculate normalized time (0 to 1 over the duration)\n // Assuming duration > 0\n const tNormExpr = `(${relativeTimeExpr})/${duration}`;\n\n // 2. Get the easing function expression applied to normalized time\n const easedProgressExpr = getEasingExpression(tNormExpr, easing);\n\n // 3. Determine the final time factor based on easing and direction (reverse)\n // - If reverse (slide out): Progress goes 0 -> 1 (eased)\n // - If not reverse (slide in): Progress goes 1 -> 0 (eased, so 1 - eased_progress)\n const finalTimeFactorExpr = reverse\n ? easedProgressExpr\n : `(1-(${easedProgressExpr}))`; // Parentheses around easedProgressExpr are crucial\n\n let activeX: string;\n let activeY: string;\n let initialX: string;\n let initialY: string;\n let finalX: string;\n let finalY: string;\n\n switch (motion.direction) {\n case \"left\": {\n const offsetXLeft = `${distance}*W`;\n activeX = `(${offsetXLeft})*${finalTimeFactorExpr}`;\n activeY = \"0\";\n initialX = reverse ? \"0\" : offsetXLeft;\n initialY = \"0\";\n finalX = reverse ? offsetXLeft : \"0\";\n finalY = \"0\";\n break;\n }\n case \"right\": {\n const offsetXRight = `-${distance}*W`;\n activeX = `(${offsetXRight})*${finalTimeFactorExpr}`;\n activeY = \"0\";\n initialX = reverse ? \"0\" : offsetXRight;\n initialY = \"0\";\n finalX = reverse ? offsetXRight : \"0\";\n finalY = \"0\";\n break;\n }\n case \"up\": {\n const offsetYUp = `${distance}*H`;\n activeX = \"0\";\n activeY = `(${offsetYUp})*${finalTimeFactorExpr}`;\n initialX = \"0\";\n initialY = reverse ? \"0\" : offsetYUp;\n finalX = \"0\";\n finalY = reverse ? offsetYUp : \"0\";\n break;\n }\n case \"down\": {\n const offsetYDown = `-${distance}*H`;\n activeX = \"0\";\n activeY = `(${offsetYDown})*${finalTimeFactorExpr}`;\n initialX = \"0\";\n initialY = reverse ? \"0\" : offsetYDown;\n finalX = \"0\";\n finalY = reverse ? offsetYDown : \"0\";\n break;\n }\n }\n\n return { initialX, initialY, activeX, activeY, finalX, finalY, duration };\n}\n\nfunction processBounceMotion(\n motion: Extract<EffieMotion, { type: \"bounce\" }>,\n relativeTimeExpr: string,\n): MotionComponents {\n const amplitude = motion.amplitude ?? 0.5;\n const duration = motion.duration ?? 1;\n const initialY = `-overlay_h*${amplitude}`;\n const finalY = \"0\";\n\n // Calculate the normalized time expression (ranging from 0 to 1 over the duration)\n // Note: Assumes duration > 0. FFmpeg might handle division by zero, but it's safer to ensure duration > 0.\n const tNormExpr = `(${relativeTimeExpr})/${duration}`;\n\n // Piecewise parabolic approximation using normalized time (tNormExpr)\n const activeBounceExpression =\n `if(lt(${tNormExpr},0.363636),${initialY}+overlay_h*${amplitude}*(7.5625*${tNormExpr}*${tNormExpr}),` +\n `if(lt(${tNormExpr},0.727273),${initialY}+overlay_h*${amplitude}*(7.5625*(${tNormExpr}-0.545455)*(${tNormExpr}-0.545455)+0.75),` +\n `if(lt(${tNormExpr},0.909091),${initialY}+overlay_h*${amplitude}*(7.5625*(${tNormExpr}-0.818182)*(${tNormExpr}-0.818182)+0.9375),` +\n `if(lt(${tNormExpr},0.954545),${initialY}+overlay_h*${amplitude}*(7.5625*(${tNormExpr}-0.954545)*(${tNormExpr}-0.954545)+0.984375),` +\n `${finalY}` + // Should settle to finalY as tNormExpr approaches 1\n `))))`;\n\n return {\n initialX: \"0\",\n initialY: initialY,\n activeX: \"0\",\n activeY: activeBounceExpression, // This expression now scales with duration\n finalX: \"0\",\n finalY: finalY,\n duration: duration, // Return the actual duration used\n };\n}\n\nfunction processShakeMotion(\n motion: Extract<EffieMotion, { type: \"shake\" }>,\n relativeTimeExpr: string,\n): MotionComponents {\n const intensity = motion.intensity ?? 10;\n const frequency = motion.frequency ?? 4;\n const duration = motion.duration ?? 1;\n\n const activeX = `${intensity}*sin(${relativeTimeExpr}*PI*${frequency})`;\n const activeY = `${intensity}*cos(${relativeTimeExpr}*PI*${frequency})`;\n\n return {\n initialX: \"0\",\n initialY: \"0\",\n activeX: activeX,\n activeY: activeY,\n finalX: \"0\",\n finalY: \"0\",\n duration: duration,\n };\n}\n\nexport function processMotion(delay: number, motion?: EffieMotion): string {\n if (!motion) return \"x=0:y=0\";\n\n const start = delay + (motion.start ?? 0);\n const relativeTimeExpr = `(t-${start})`;\n let components: MotionComponents;\n\n switch (motion.type) {\n case \"bounce\":\n components = processBounceMotion(motion, relativeTimeExpr);\n break;\n case \"shake\":\n components = processShakeMotion(motion, relativeTimeExpr);\n break;\n case \"slide\":\n components = processSlideMotion(motion, relativeTimeExpr);\n break;\n default:\n motion satisfies never;\n throw new Error(\n `Unsupported motion type: ${(motion as EffieMotion).type}`,\n );\n }\n\n const motionEndTime = start + components.duration;\n\n const xArg = `if(lt(t,${start}),${components.initialX},if(lt(t,${motionEndTime}),${components.activeX},${components.finalX}))`;\n const yArg = `if(lt(t,${start}),${components.initialY},if(lt(t,${motionEndTime}),${components.activeY},${components.finalY}))`;\n\n return `x='${xArg}':y='${yArg}'`;\n}\n","import type { EffieEffect } from \"@effing/effie\";\n\nfunction processFadeIn(\n effect: Extract<EffieEffect, { type: \"fade-in\" }>,\n _frameRate: number,\n _frameWidth: number,\n _frameHeight: number,\n): string {\n return `fade=t=in:st=${effect.start}:d=${effect.duration}:alpha=1`;\n}\n\nfunction processFadeOut(\n effect: Extract<EffieEffect, { type: \"fade-out\" }>,\n _frameRate: number,\n _frameWidth: number,\n _frameHeight: number,\n): string {\n return `fade=t=out:st=${effect.start}:d=${effect.duration}:alpha=1`;\n}\n\nfunction processSaturateIn(\n effect: Extract<EffieEffect, { type: \"saturate-in\" }>,\n _frameRate: number,\n _frameWidth: number,\n _frameHeight: number,\n): string {\n return `hue='s=max(0,min(1,(t-${effect.start})/${effect.duration}))'`;\n}\n\nfunction processSaturateOut(\n effect: Extract<EffieEffect, { type: \"saturate-out\" }>,\n _frameRate: number,\n _frameWidth: number,\n _frameHeight: number,\n): string {\n return `hue='s=max(0,min(1,(${effect.start + effect.duration}-t)/${effect.duration}))'`;\n}\n\nfunction processScroll(\n effect: Extract<EffieEffect, { type: \"scroll\" }>,\n frameRate: number,\n _frameWidth: number,\n _frameHeight: number,\n): string {\n const distance = effect.distance ?? 1;\n const scroll = distance / (1 + distance);\n const speed = scroll / (effect.duration * frameRate);\n switch (effect.direction) {\n case \"left\":\n return `scroll=h=${speed}`;\n case \"right\":\n return `scroll=hpos=${1 - scroll}:h=-${speed}`;\n case \"up\":\n return `scroll=v=${speed}`;\n case \"down\":\n return `scroll=vpos=${1 - scroll}:v=-${speed}`;\n }\n}\n\nfunction processEffect(\n effect: EffieEffect,\n frameRate: number,\n frameWidth: number,\n frameHeight: number,\n): string {\n switch (effect.type) {\n case \"fade-in\":\n return processFadeIn(effect, frameRate, frameWidth, frameHeight);\n case \"fade-out\":\n return processFadeOut(effect, frameRate, frameWidth, frameHeight);\n case \"saturate-in\":\n return processSaturateIn(effect, frameRate, frameWidth, frameHeight);\n case \"saturate-out\":\n return processSaturateOut(effect, frameRate, frameWidth, frameHeight);\n case \"scroll\":\n return processScroll(effect, frameRate, frameWidth, frameHeight);\n default:\n effect satisfies never;\n throw new Error(\n `Unsupported effect type: ${(effect as EffieEffect).type}`,\n );\n }\n}\n\nexport function processEffects(\n effects: EffieEffect[] | undefined,\n frameRate: number,\n frameWidth: number,\n frameHeight: number,\n): string {\n if (!effects || effects.length === 0) return \"\";\n\n const filters: string[] = [];\n\n for (const effect of effects) {\n const filter = processEffect(effect, frameRate, frameWidth, frameHeight);\n filters.push(filter);\n }\n\n return filters.join(\",\");\n}\n","import type { ChildProcess } from \"child_process\";\nimport { spawn } from \"child_process\";\nimport type { Readable } from \"stream\";\nimport { pipeline } from \"stream\";\nimport fs from \"fs/promises\";\nimport os from \"os\";\nimport path from \"path\";\n\nimport tar from \"tar-stream\";\nimport { createWriteStream } from \"fs\";\nimport { promisify } from \"util\";\n\nconst pump = promisify(pipeline);\n\nlet resolvedBin: string | undefined;\nasync function getFFmpegBin(): Promise<string> {\n if (resolvedBin) return resolvedBin;\n if (process.env.FFMPEG) {\n resolvedBin = process.env.FFMPEG;\n return resolvedBin;\n }\n try {\n const { pathToFFmpeg } = await import(\"@effing/ffmpeg\");\n if (pathToFFmpeg) {\n resolvedBin = pathToFFmpeg;\n return resolvedBin;\n }\n } catch {\n // @effing/ffmpeg not installed\n }\n resolvedBin = \"ffmpeg\";\n return resolvedBin;\n}\n\n/**\n * Each input is represented by its index, its source, and the pre–arguments\n * that must appear immediately before its \"-i\" option.\n */\nexport type FFmpegInput = {\n index: number;\n source: string;\n preArgs: string[];\n type: \"image\" | \"video\" | \"audio\" | \"color\" | \"animation\";\n};\n\nexport class FFmpegCommand {\n globalArgs: string[];\n inputs: FFmpegInput[];\n filterComplex: string;\n outputArgs: string[];\n\n constructor(\n globalArgs: string[],\n inputs: FFmpegInput[],\n filterComplex: string,\n outputArgs: string[],\n ) {\n this.globalArgs = globalArgs;\n this.inputs = inputs;\n this.filterComplex = filterComplex;\n this.outputArgs = outputArgs;\n }\n\n buildArgs(inputResolver: (input: FFmpegInput) => string): string[] {\n const inputArgs: string[] = [];\n for (const input of this.inputs) {\n if (input.type === \"color\") {\n inputArgs.push(...input.preArgs);\n } else if (input.type === \"animation\") {\n inputArgs.push(\n ...input.preArgs,\n \"-i\",\n path.join(inputResolver(input), \"frame_%05d\"),\n );\n } else {\n inputArgs.push(...input.preArgs, \"-i\", inputResolver(input));\n }\n }\n const args = [\n ...this.globalArgs,\n ...inputArgs,\n \"-filter_complex\",\n this.filterComplex,\n ...this.outputArgs,\n ];\n return args;\n }\n}\n\nexport class FFmpegRunner {\n private command: FFmpegCommand;\n\n private ffmpegProc?: ChildProcess;\n\n constructor(command: FFmpegCommand) {\n this.command = command;\n }\n\n async run(\n sourceFetcher: (input: {\n type: FFmpegInput[\"type\"];\n src: string;\n }) => Promise<Readable>,\n imageTransformer?: (imageStream: Readable) => Promise<Readable>,\n referenceResolver?: (src: string) => string,\n urlTransformer?: (url: string) => string,\n ): Promise<Readable> {\n const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), \"ffs-\"));\n const fileMapping = new Map<number, string>();\n // Cache for #reference sources to avoid duplicate fetches.\n // Uses promises to handle concurrent requests for the same source.\n // Key is input.source (the original #ref) to preserve deduplication.\n const fetchCache = new Map<string, Promise<string>>();\n\n const fetchAndSaveSource = async (\n input: FFmpegInput,\n sourceUrl: string,\n inputName: string,\n ): Promise<string> => {\n const stream = await sourceFetcher({\n type: input.type,\n src: sourceUrl,\n });\n\n if (input.type === \"animation\") {\n // we expect annie files for animations,\n // which are a TAR that needs to be extracted\n const extractionDir = path.join(tempDir, inputName);\n await fs.mkdir(extractionDir, { recursive: true });\n const extract = tar.extract();\n const extractPromise = new Promise<void>((resolve, reject) => {\n extract.on(\"entry\", async (header, stream, next) => {\n if (header.name.startsWith(\"frame_\")) {\n const transformedStream = imageTransformer\n ? await imageTransformer(stream)\n : stream;\n const outputPath = path.join(extractionDir, header.name);\n const writeStream = createWriteStream(outputPath);\n transformedStream.pipe(writeStream);\n writeStream.on(\"finish\", next);\n writeStream.on(\"error\", reject);\n }\n });\n extract.on(\"finish\", resolve);\n extract.on(\"error\", reject);\n });\n stream.pipe(extract);\n await extractPromise;\n return extractionDir;\n } else if (input.type === \"image\" && imageTransformer) {\n const tempFile = path.join(tempDir, inputName);\n const transformedStream = await imageTransformer(stream);\n const writeStream = createWriteStream(tempFile);\n transformedStream.on(\"error\", (e) => writeStream.destroy(e));\n await pump(transformedStream, writeStream);\n return tempFile;\n } else {\n const tempFile = path.join(tempDir, inputName);\n const writeStream = createWriteStream(tempFile);\n stream.on(\"error\", (e) => writeStream.destroy(e));\n await pump(stream, writeStream);\n return tempFile;\n }\n };\n\n await Promise.all(\n this.command.inputs.map(async (input) => {\n if (input.type === \"color\") return;\n\n const inputName = `ffmpeg_input_${input.index\n .toString()\n .padStart(3, \"0\")}`;\n\n // Resolve #references to get the actual URL\n const sourceUrl = referenceResolver\n ? referenceResolver(input.source)\n : input.source;\n\n // Pass HTTP(S) video/audio URLs directly to FFmpeg without downloading\n // If urlTransformer is provided, transform the URL (e.g., for proxy)\n if (\n (input.type === \"video\" || input.type === \"audio\") &&\n (sourceUrl.startsWith(\"http://\") || sourceUrl.startsWith(\"https://\"))\n ) {\n const finalUrl = urlTransformer\n ? urlTransformer(sourceUrl)\n : sourceUrl;\n fileMapping.set(input.index, finalUrl);\n return;\n }\n\n // Deduplicate fetches when the same #ref appears multiple times.\n // Only for #refs since they're short strings; data URLs would bloat the map.\n const shouldCache = input.source.startsWith(\"#\");\n if (shouldCache) {\n let fetchPromise = fetchCache.get(input.source);\n if (!fetchPromise) {\n fetchPromise = fetchAndSaveSource(input, sourceUrl, inputName);\n fetchCache.set(input.source, fetchPromise);\n }\n const filePath = await fetchPromise;\n fileMapping.set(input.index, filePath);\n } else {\n const filePath = await fetchAndSaveSource(\n input,\n sourceUrl,\n inputName,\n );\n fileMapping.set(input.index, filePath);\n }\n }),\n );\n\n const finalArgs = this.command.buildArgs((input) => {\n const filePath = fileMapping.get(input.index);\n if (!filePath)\n throw new Error(`File for input index ${input.index} not found`);\n return filePath;\n });\n const ffmpegProc = spawn(await getFFmpegBin(), finalArgs);\n ffmpegProc.stderr!.on(\"data\", (data) => {\n console.error(data.toString());\n });\n\n ffmpegProc.on(\"close\", async () => {\n try {\n await fs.rm(tempDir, { recursive: true, force: true });\n } catch (err) {\n console.error(\"Error removing temp directory:\", err);\n }\n });\n\n this.ffmpegProc = ffmpegProc;\n return ffmpegProc.stdout as Readable;\n }\n\n close(): void {\n if (this.ffmpegProc) {\n this.ffmpegProc.kill(\"SIGTERM\");\n this.ffmpegProc = undefined;\n }\n }\n}\n","import type { EffieTransition } from \"@effing/effie\";\n\nexport function processTransition(transition: EffieTransition): string {\n switch (transition.type) {\n case \"fade\": {\n if (\"through\" in transition) {\n // Fade through color: fadeblack, fadewhite, fadegrays\n return `fade${transition.through}`;\n }\n // Crossfade with easing\n const easing = transition.easing ?? \"linear\";\n return {\n linear: \"fade\",\n \"ease-in\": \"fadeslow\",\n \"ease-out\": \"fadefast\",\n }[easing];\n }\n case \"barn\": {\n // Barn door wipes: vertopen, vertclose, horzopen, horzclose\n const orientation = transition.orientation ?? \"horizontal\";\n const mode = transition.mode ?? \"open\";\n const prefix = orientation === \"vertical\" ? \"vert\" : \"horz\";\n return `${prefix}${mode}`;\n }\n case \"circle\": {\n // Circle wipes: circleopen, circleclose, circlecrop\n const mode = transition.mode ?? \"open\";\n return `circle${mode}`;\n }\n case \"wipe\":\n case \"slide\":\n case \"smooth\": {\n const direction = transition.direction ?? \"left\";\n return `${transition.type}${direction}`;\n }\n case \"slice\": {\n const direction = transition.direction ?? \"left\";\n const prefix = {\n left: \"hl\",\n right: \"hr\",\n up: \"vu\",\n down: \"vd\",\n }[direction];\n return `${prefix}${transition.type}`;\n }\n case \"zoom\": {\n return \"zoomin\";\n }\n case \"dissolve\":\n case \"pixelize\":\n case \"radial\":\n return transition.type;\n default:\n transition satisfies never;\n throw new Error(\n `Unsupported transition type: ${(transition as EffieTransition).type}`,\n );\n }\n}\n"],"mappings":"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","names":["stream"]}
1
+ {"version":3,"sources":["../src/render.ts","../src/motion.ts","../src/effect.ts","../src/ffmpeg.ts","../src/transition.ts"],"sourcesContent":["import { Readable } from \"stream\";\nimport { createReadStream } from \"fs\";\nimport { processMotion } from \"./motion\";\nimport { processEffects } from \"./effect\";\nimport type { FFmpegInput } from \"./ffmpeg\";\nimport { FFmpegCommand, FFmpegRunner } from \"./ffmpeg\";\nimport { processTransition } from \"./transition\";\nimport type { EffieData, EffieSources, EffieWebUrl } from \"@effing/effie\";\nimport sharp from \"sharp\";\nimport { ffsFetch } from \"./fetch\";\nimport { fileURLToPath } from \"url\";\nimport { storeKeys } from \"./storage\";\nimport type { TransientStore } from \"./storage\";\nimport type { HttpProxy } from \"./proxy\";\n\nexport type EffieRendererOptions = {\n /**\n * Allow reading from local file paths.\n * WARNING: Only enable this for trusted internal operations.\n * Enabling this for user-provided data is a security risk.\n * @default false\n */\n allowLocalFiles?: boolean;\n /**\n * Transient store instance for source lookups.\n * If not provided, sources will be fetched directly from network.\n */\n transientStore?: TransientStore;\n /**\n * HTTP proxy for video/audio URLs.\n * When provided, HTTP(S) URLs for video/audio inputs will be routed\n * through this proxy, allowing Node.js to handle DNS resolution\n * instead of FFmpeg (useful for Alpine Linux with musl libc).\n */\n httpProxy?: HttpProxy;\n};\n\nexport class EffieRenderer<U extends string = EffieWebUrl> {\n private effieData: EffieData<EffieSources<U>, U>;\n private ffmpegRunner?: FFmpegRunner;\n private allowLocalFiles: boolean;\n private transientStore?: TransientStore;\n private httpProxy?: HttpProxy;\n\n constructor(\n effieData: EffieData<EffieSources<U>, U>,\n options?: EffieRendererOptions,\n ) {\n this.effieData = effieData;\n this.allowLocalFiles = options?.allowLocalFiles ?? false;\n this.transientStore = options?.transientStore;\n this.httpProxy = options?.httpProxy;\n }\n\n private async fetchSource(src: string): Promise<Readable> {\n // src is already a resolved URL - no #ref handling needed\n // (references are resolved in FFmpegRunner via referenceResolver)\n\n // Handle data URLs (inline, no actual fetch or cache needed)\n if (src.startsWith(\"data:\")) {\n const commaIndex = src.indexOf(\",\");\n if (commaIndex === -1) {\n throw new Error(\"Invalid data URL\");\n }\n const meta = src.slice(5, commaIndex); // after \"data:\"\n const isBase64 = meta.endsWith(\";base64\");\n const data = src.slice(commaIndex + 1);\n const buffer = isBase64\n ? Buffer.from(data, \"base64\")\n : Buffer.from(decodeURIComponent(data));\n return Readable.from(buffer);\n }\n\n // Handle local file paths, if allowed\n if (src.startsWith(\"file:\")) {\n if (!this.allowLocalFiles) {\n throw new Error(\n \"Local file paths are not allowed. Use allowLocalFiles option for trusted operations.\",\n );\n }\n return createReadStream(fileURLToPath(src));\n }\n\n // If we have a transient store, check the store first\n if (this.transientStore) {\n const cachedStream = await this.transientStore.getStream(\n storeKeys.source(src),\n );\n if (cachedStream) {\n return cachedStream;\n }\n }\n\n // Fetch from network\n const response = await ffsFetch(src, {\n headersTimeout: 10 * 60 * 1000, // 10 minutes\n bodyTimeout: 20 * 60 * 1000, // 20 minutes\n });\n if (!response.ok) {\n throw new Error(\n `Failed to fetch ${src}: ${response.status} ${response.statusText}`,\n );\n }\n if (!response.body) {\n throw new Error(`No body for ${src}`);\n }\n // Convert WHATWG ReadableStream to Node.js Readable\n return Readable.fromWeb(response.body);\n }\n\n private buildAudioFilter({\n duration,\n volume,\n fadeIn,\n fadeOut,\n }: {\n duration: number;\n volume?: number;\n fadeIn?: number;\n fadeOut?: number;\n }) {\n const filters = [];\n if (volume !== undefined) {\n filters.push(`volume=${volume}`);\n }\n if (fadeIn !== undefined) {\n filters.push(`afade=type=in:start_time=0:duration=${fadeIn}`);\n }\n if (fadeOut !== undefined) {\n filters.push(\n `afade=type=out:start_time=${duration - fadeOut}:duration=${fadeOut}`,\n );\n }\n return filters.length ? filters.join(\",\") : \"anull\";\n }\n\n private getFrameDimensions(scaleFactor: number) {\n // Round down to the nearest even number for H.264 compatibility\n return {\n frameWidth: Math.floor((this.effieData.width * scaleFactor) / 2) * 2,\n frameHeight: Math.floor((this.effieData.height * scaleFactor) / 2) * 2,\n };\n }\n\n /**\n * Builds an FFmpeg input for a background (global or segment).\n */\n private buildBackgroundInput(\n background: EffieData<EffieSources<U>, U>[\"background\"],\n inputIndex: number,\n frameWidth: number,\n frameHeight: number,\n ): FFmpegInput {\n if (background.type === \"image\") {\n return {\n index: inputIndex,\n source: background.source,\n preArgs: [\"-loop\", \"1\", \"-framerate\", this.effieData.fps.toString()],\n type: \"image\",\n };\n } else if (background.type === \"video\") {\n return {\n index: inputIndex,\n source: background.source,\n preArgs: [\"-stream_loop\", \"-1\"],\n type: \"video\",\n };\n }\n // Color background - use lavfi to generate\n return {\n index: inputIndex,\n source: \"\",\n preArgs: [\n \"-f\",\n \"lavfi\",\n \"-i\",\n `color=${background.color}:size=${frameWidth}x${frameHeight}:rate=${this.effieData.fps}`,\n ],\n type: \"color\",\n };\n }\n\n private buildOutputArgs(outputFilename: string): string[] {\n return [\n \"-map\",\n \"[outv]\",\n \"-map\",\n \"[outa]\",\n \"-c:v\",\n \"libx264\",\n \"-r\",\n this.effieData.fps.toString(),\n \"-pix_fmt\",\n \"yuv420p\",\n \"-preset\",\n \"fast\",\n \"-crf\",\n \"28\",\n \"-c:a\",\n \"aac\",\n \"-movflags\",\n \"frag_keyframe+empty_moov\",\n \"-f\",\n \"mp4\",\n outputFilename,\n ];\n }\n\n private buildLayerInput(\n layer: EffieData<EffieSources<U>, U>[\"segments\"][0][\"layers\"][0],\n duration: number,\n inputIndex: number,\n ): FFmpegInput {\n let preArgs: string[] = [];\n if (layer.type === \"image\") {\n preArgs = [\n \"-loop\",\n \"1\",\n \"-t\",\n duration.toString(),\n \"-framerate\",\n this.effieData.fps.toString(),\n ];\n } else if (layer.type === \"animation\") {\n preArgs = [\"-f\", \"image2\", \"-framerate\", this.effieData.fps.toString()];\n }\n return {\n index: inputIndex,\n source: layer.source,\n preArgs,\n type: layer.type,\n };\n }\n\n /**\n * Builds filter chain for all layers in a segment.\n * @param segment - The segment containing layers\n * @param bgLabel - Label for the background input (e.g., \"bg_seg0\" or \"bg_seg\")\n * @param labelPrefix - Prefix for generated labels (e.g., \"seg0_\" or \"\")\n * @param layerInputOffset - Starting input index for layers\n * @param frameWidth - Frame width for nullsrc\n * @param frameHeight - Frame height for nullsrc\n * @param outputLabel - Label for the final video output\n * @returns Array of filter parts to add to the filter chain\n */\n private buildLayerFilters(\n segment: EffieData<EffieSources<U>, U>[\"segments\"][0],\n bgLabel: string,\n labelPrefix: string,\n layerInputOffset: number,\n frameWidth: number,\n frameHeight: number,\n outputLabel: string,\n ): string[] {\n const filterParts: string[] = [];\n let currentVidLabel = bgLabel;\n\n for (let l = 0; l < segment.layers.length; l++) {\n const inputIdx = layerInputOffset + l;\n const layerLabel = `${labelPrefix}layer${l}`;\n const layer = segment.layers[l];\n const effectChain = layer.effects\n ? processEffects(\n layer.effects,\n this.effieData.fps,\n frameWidth,\n frameHeight,\n )\n : \"\";\n filterParts.push(\n `[${inputIdx}:v]trim=start=0:duration=${segment.duration},${\n effectChain ? effectChain + \",\" : \"\"\n }setsar=1,setpts=PTS-STARTPTS[${layerLabel}]`,\n );\n let overlayInputLabel = layerLabel;\n const delay = layer.delay ?? 0;\n if (delay > 0) {\n filterParts.push(\n `nullsrc=size=${frameWidth}x${frameHeight}:duration=${delay},setpts=PTS-STARTPTS[null_${layerLabel}]`,\n );\n filterParts.push(\n `[null_${layerLabel}][${layerLabel}]concat=n=2:v=1:a=0[delayed_${layerLabel}]`,\n );\n overlayInputLabel = `delayed_${layerLabel}`;\n }\n const overlayOutputLabel = `${labelPrefix}tmp${l}`;\n const offset = layer.motion ? processMotion(delay, layer.motion) : \"0:0\";\n const fromTime = layer.from ?? 0;\n const untilTime = layer.until ?? segment.duration;\n filterParts.push(\n `[${currentVidLabel}][${overlayInputLabel}]overlay=${offset}:enable='between(t,${fromTime},${untilTime})',fps=${this.effieData.fps}[${overlayOutputLabel}]`,\n );\n currentVidLabel = overlayOutputLabel;\n }\n filterParts.push(`[${currentVidLabel}]null[${outputLabel}]`);\n\n return filterParts;\n }\n\n /**\n * Applies xfade/concat transitions between video segments.\n * Modifies videoSegmentLabels in place to update labels after transitions.\n * @param filterParts - Array to append filter parts to\n * @param videoSegmentLabels - Array of video segment labels (modified in place)\n */\n private applyTransitions(\n filterParts: string[],\n videoSegmentLabels: string[],\n ): void {\n let transitionOffset = 0;\n this.effieData.segments.forEach((segment, i) => {\n if (i === 0) {\n transitionOffset = segment.duration;\n return;\n }\n const combineLabel = `[vid_com${i}]`;\n if (!segment.transition) {\n transitionOffset += segment.duration;\n filterParts.push(\n `${videoSegmentLabels[i - 1]}${\n videoSegmentLabels[i]\n }concat=n=2:v=1:a=0,fps=${this.effieData.fps}${combineLabel}`,\n );\n videoSegmentLabels[i] = combineLabel;\n return;\n }\n const transitionName = processTransition(segment.transition);\n const transitionDuration = segment.transition.duration;\n transitionOffset -= transitionDuration;\n filterParts.push(\n `${videoSegmentLabels[i - 1]}${\n videoSegmentLabels[i]\n }xfade=transition=${transitionName}:duration=${transitionDuration}:offset=${transitionOffset}${combineLabel}`,\n );\n videoSegmentLabels[i] = combineLabel;\n transitionOffset += segment.duration;\n });\n filterParts.push(`${videoSegmentLabels.at(-1)}null[outv]`);\n }\n\n /**\n * Applies general audio mixing: concats segment audio and mixes with global audio if present.\n * @param filterParts - Array to append filter parts to\n * @param audioSegmentLabels - Array of audio segment labels to concat\n * @param totalDuration - Total duration for audio trimming\n * @param generalAudioInputIndex - Input index for general audio (if present)\n */\n private applyGeneralAudio(\n filterParts: string[],\n audioSegmentLabels: string[],\n totalDuration: number,\n generalAudioInputIndex: number,\n ): void {\n if (this.effieData.audio) {\n const audioSeek = this.effieData.audio.seek ?? 0;\n const generalAudioFilter = this.buildAudioFilter({\n duration: totalDuration,\n volume: this.effieData.audio.volume,\n fadeIn: this.effieData.audio.fadeIn,\n fadeOut: this.effieData.audio.fadeOut,\n });\n filterParts.push(\n `[${generalAudioInputIndex}:a]atrim=start=${audioSeek}:duration=${totalDuration},${generalAudioFilter},asetpts=PTS-STARTPTS[general_audio]`,\n );\n filterParts.push(\n `${audioSegmentLabels.join(\"\")}concat=n=${\n this.effieData.segments.length\n }:v=0:a=1,atrim=start=0:duration=${totalDuration}[segments_audio]`,\n );\n filterParts.push(\n `[general_audio][segments_audio]amix=inputs=2:duration=longest[outa]`,\n );\n } else {\n filterParts.push(\n `${audioSegmentLabels.join(\"\")}concat=n=${\n this.effieData.segments.length\n }:v=0:a=1[outa]`,\n );\n }\n }\n\n private buildFFmpegCommand(\n outputFilename: string,\n scaleFactor: number = 1,\n ): FFmpegCommand {\n const globalArgs: string[] = [\"-y\", \"-loglevel\", \"error\"];\n const inputs: FFmpegInput[] = [];\n let inputIndex = 0;\n\n const { frameWidth, frameHeight } = this.getFrameDimensions(scaleFactor);\n const backgroundSeek =\n this.effieData.background.type === \"video\"\n ? (this.effieData.background.seek ?? 0)\n : 0;\n\n // Global background input:\n inputs.push(\n this.buildBackgroundInput(\n this.effieData.background,\n inputIndex,\n frameWidth,\n frameHeight,\n ),\n );\n const globalBgInputIdx = inputIndex;\n inputIndex++;\n\n // Segment background inputs:\n const segmentBgInputIndices: (number | null)[] = [];\n for (const segment of this.effieData.segments) {\n if (segment.background) {\n inputs.push(\n this.buildBackgroundInput(\n segment.background,\n inputIndex,\n frameWidth,\n frameHeight,\n ),\n );\n segmentBgInputIndices.push(inputIndex);\n inputIndex++;\n } else {\n segmentBgInputIndices.push(null);\n }\n }\n\n // Identify segments using global background\n const globalBgSegmentIndices: number[] = [];\n for (let i = 0; i < this.effieData.segments.length; i++) {\n if (segmentBgInputIndices[i] === null) {\n globalBgSegmentIndices.push(i);\n }\n }\n\n // Layer inputs:\n for (const segment of this.effieData.segments) {\n for (const layer of segment.layers) {\n inputs.push(this.buildLayerInput(layer, segment.duration, inputIndex));\n inputIndex++;\n }\n }\n\n // Audio inputs:\n for (const segment of this.effieData.segments) {\n if (segment.audio) {\n inputs.push({\n index: inputIndex,\n source: segment.audio.source,\n preArgs: [],\n type: \"audio\",\n });\n inputIndex++;\n }\n }\n\n // General audio input:\n if (this.effieData.audio) {\n inputs.push({\n index: inputIndex,\n source: this.effieData.audio.source,\n preArgs: [],\n type: \"audio\",\n });\n inputIndex++;\n }\n\n // Compute how many video inputs we have:\n const numSegmentBgInputs = segmentBgInputIndices.filter(\n (i) => i !== null,\n ).length;\n const numVideoInputs =\n 1 +\n numSegmentBgInputs +\n this.effieData.segments.reduce((sum, seg) => sum + seg.layers.length, 0);\n let audioCounter = 0;\n\n // Build filter_complex:\n let currentTime = 0;\n let layerInputOffset = 1 + numSegmentBgInputs; // Global background is input 0\n const filterParts: string[] = [];\n const videoSegmentLabels: string[] = [];\n const audioSegmentLabels: string[] = [];\n\n // Build split/fifo chain for global background\n const globalBgFifoLabels: Map<number, string> = new Map();\n const bgFilter = `fps=${this.effieData.fps},scale=${frameWidth}x${frameHeight}:force_original_aspect_ratio=increase,crop=${frameWidth}:${frameHeight}`;\n if (globalBgSegmentIndices.length === 1) {\n // Single segment - no split needed, just fifo\n const fifoLabel = `bg_fifo_0`;\n filterParts.push(`[${globalBgInputIdx}:v]${bgFilter},fifo[${fifoLabel}]`);\n globalBgFifoLabels.set(globalBgSegmentIndices[0], fifoLabel);\n } else if (globalBgSegmentIndices.length > 1) {\n // Multiple segments - use split + fifo\n const splitCount = globalBgSegmentIndices.length;\n const splitOutputLabels = globalBgSegmentIndices.map(\n (_, i) => `bg_split_${i}`,\n );\n\n filterParts.push(\n `[${globalBgInputIdx}:v]${bgFilter},split=${splitCount}${splitOutputLabels.map((l) => `[${l}]`).join(\"\")}`,\n );\n\n for (let i = 0; i < splitCount; i++) {\n const fifoLabel = `bg_fifo_${i}`;\n filterParts.push(`[${splitOutputLabels[i]}]fifo[${fifoLabel}]`);\n globalBgFifoLabels.set(globalBgSegmentIndices[i], fifoLabel);\n }\n }\n\n for (let segIdx = 0; segIdx < this.effieData.segments.length; segIdx++) {\n const segment = this.effieData.segments[segIdx];\n\n // Determine background for this segment (segment bg overrides global bg)\n const bgLabel = `bg_seg${segIdx}`;\n if (segment.background) {\n // Use segment background\n const segBgInputIdx = segmentBgInputIndices[segIdx]!;\n const segBgSeek =\n segment.background.type === \"video\"\n ? (segment.background.seek ?? 0)\n : 0;\n filterParts.push(\n `[${segBgInputIdx}:v]fps=${this.effieData.fps},scale=${frameWidth}x${frameHeight}:force_original_aspect_ratio=increase,crop=${frameWidth}:${frameHeight},trim=start=${segBgSeek}:duration=${segment.duration},setpts=PTS-STARTPTS[${bgLabel}]`,\n );\n } else {\n // Use global background (via split/fifo chain)\n const fifoLabel = globalBgFifoLabels.get(segIdx);\n if (fifoLabel) {\n // fps/scale already applied in split/fifo chain\n filterParts.push(\n `[${fifoLabel}]trim=start=${backgroundSeek + currentTime}:duration=${segment.duration},setpts=PTS-STARTPTS[${bgLabel}]`,\n );\n }\n }\n\n // Process layers\n const vidLabel = `vid_seg${segIdx}`;\n filterParts.push(\n ...this.buildLayerFilters(\n segment,\n bgLabel,\n `seg${segIdx}_`,\n layerInputOffset,\n frameWidth,\n frameHeight,\n vidLabel,\n ),\n );\n layerInputOffset += segment.layers.length;\n videoSegmentLabels.push(`[${vidLabel}]`);\n\n const nextSegment = this.effieData.segments[segIdx + 1];\n const transitionDuration = nextSegment?.transition?.duration ?? 0;\n // Ensure audio duration is always at least 0.001 seconds to avoid FFmpeg misbehavior\n const realDuration = Math.max(\n 0.001,\n segment.duration - transitionDuration,\n );\n\n // Process audio: use the corresponding audio input index if audio exists\n if (segment.audio) {\n // Audio inputs start after all video inputs\n const audioInputIndex = numVideoInputs + audioCounter;\n const audioFilter = this.buildAudioFilter({\n duration: realDuration,\n volume: segment.audio.volume,\n fadeIn: segment.audio.fadeIn,\n fadeOut: segment.audio.fadeOut,\n });\n filterParts.push(\n `[${audioInputIndex}:a]atrim=start=0:duration=${realDuration},${audioFilter},asetpts=PTS-STARTPTS[aud_seg${segIdx}]`,\n );\n audioCounter++;\n } else {\n filterParts.push(\n `anullsrc=r=44100:cl=stereo,atrim=start=0:duration=${realDuration},asetpts=PTS-STARTPTS[aud_seg${segIdx}]`,\n );\n }\n audioSegmentLabels.push(`[aud_seg${segIdx}]`);\n\n currentTime += realDuration;\n }\n\n // Add general audio if present\n this.applyGeneralAudio(\n filterParts,\n audioSegmentLabels,\n currentTime,\n numVideoInputs + audioCounter,\n );\n\n // Apply transitions between video segments\n this.applyTransitions(filterParts, videoSegmentLabels);\n\n const filterComplex = filterParts.join(\";\");\n const outputArgs = this.buildOutputArgs(outputFilename);\n\n return new FFmpegCommand(globalArgs, inputs, filterComplex, outputArgs);\n }\n\n private createImageTransformer(scaleFactor: number) {\n return async (imageStream: Readable): Promise<Readable> => {\n if (scaleFactor === 1) return imageStream;\n\n const sharpTransformer = sharp();\n imageStream.on(\"error\", (err) => {\n if (!sharpTransformer.destroyed) {\n sharpTransformer.destroy(err);\n }\n });\n sharpTransformer.on(\"error\", (err) => {\n if (!imageStream.destroyed) {\n imageStream.destroy(err);\n }\n });\n imageStream.pipe(sharpTransformer);\n try {\n const metadata = await sharpTransformer.metadata();\n const imageWidth = metadata.width ?? this.effieData.width;\n const imageHeight = metadata.height ?? this.effieData.height;\n return sharpTransformer.resize({\n width: Math.floor(imageWidth * scaleFactor),\n height: Math.floor(imageHeight * scaleFactor),\n });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (!sharpTransformer.destroyed) {\n sharpTransformer.destroy(error);\n }\n throw error;\n }\n };\n }\n\n /**\n * Resolves a source reference to its actual URL.\n * If the source is a #reference, returns the resolved URL.\n * Otherwise, returns the source as-is.\n */\n private resolveReference(src: string): string {\n if (src.startsWith(\"#\")) {\n const sourceName = src.slice(1);\n if (sourceName in this.effieData.sources!) {\n return this.effieData.sources![sourceName];\n }\n }\n return src;\n }\n\n /**\n * Renders the effie data to a video stream.\n * @param scaleFactor - Scale factor for output dimensions\n */\n async render(scaleFactor = 1): Promise<Readable> {\n const ffmpegCommand = this.buildFFmpegCommand(\"-\", scaleFactor);\n this.ffmpegRunner = new FFmpegRunner(ffmpegCommand);\n\n // Create URL transformer for proxy if available\n const urlTransformer = this.httpProxy\n ? (url: string) => this.httpProxy!.transformUrl(url)\n : undefined;\n\n return this.ffmpegRunner.run(\n async ({ src }) => this.fetchSource(src),\n this.createImageTransformer(scaleFactor),\n (src) => this.resolveReference(src),\n urlTransformer,\n );\n }\n\n close(): void {\n if (this.ffmpegRunner) {\n this.ffmpegRunner.close();\n }\n }\n}\n","import type { EffieMotion } from \"@effing/effie\";\n\n/**\n * Defines the building blocks for an FFmpeg motion expression.\n */\ntype MotionComponents = {\n initialX: string; // Expression for X before animation starts\n initialY: string; // Expression for Y before animation starts\n activeX: string; // Expression for X during animation (incorporates relative time)\n activeY: string; // Expression for Y during animation (incorporates relative time)\n finalX: string; // Expression for X after animation ends\n finalY: string; // Expression for Y after animation ends\n duration: number; // Duration of the animation effect itself\n};\n\nfunction getEasingExpression(\n tNormExpr: string,\n easingType: \"linear\" | \"ease-in\" | \"ease-out\" | \"ease-in-out\",\n): string {\n switch (easingType) {\n case \"ease-in\":\n // t^2\n return `pow(${tNormExpr},2)`;\n case \"ease-out\":\n // 1 - (1-t)^2\n return `(1-pow(1-(${tNormExpr}),2))`;\n case \"ease-in-out\":\n // t < 0.5 ? 2*t^2 : 1 - (-2*t + 2)^2 / 2\n return `if(lt(${tNormExpr},0.5),2*pow(${tNormExpr},2),1-pow(-2*(${tNormExpr})+2,2)/2)`;\n case \"linear\":\n default:\n // Default to linear if type is unknown or \"linear\"\n return `(${tNormExpr})`; // Ensure parentheses for safety if tNormExpr is complex\n }\n}\n\nfunction processSlideMotion(\n motion: Extract<EffieMotion, { type: \"slide\" }>,\n relativeTimeExpr: string,\n): MotionComponents {\n const duration = motion.duration ?? 1;\n const distance = motion.distance ?? 1;\n const reverse = motion.reverse ?? false;\n const easing = motion.easing ?? \"linear\"; // Default to linear easing\n\n // 1. Calculate normalized time (0 to 1 over the duration)\n // Assuming duration > 0\n const tNormExpr = `(${relativeTimeExpr})/${duration}`;\n\n // 2. Get the easing function expression applied to normalized time\n const easedProgressExpr = getEasingExpression(tNormExpr, easing);\n\n // 3. Determine the final time factor based on easing and direction (reverse)\n // - If reverse (slide out): Progress goes 0 -> 1 (eased)\n // - If not reverse (slide in): Progress goes 1 -> 0 (eased, so 1 - eased_progress)\n const finalTimeFactorExpr = reverse\n ? easedProgressExpr\n : `(1-(${easedProgressExpr}))`; // Parentheses around easedProgressExpr are crucial\n\n let activeX: string;\n let activeY: string;\n let initialX: string;\n let initialY: string;\n let finalX: string;\n let finalY: string;\n\n switch (motion.direction) {\n case \"left\": {\n const offsetXLeft = `${distance}*W`;\n activeX = `(${offsetXLeft})*${finalTimeFactorExpr}`;\n activeY = \"0\";\n initialX = reverse ? \"0\" : offsetXLeft;\n initialY = \"0\";\n finalX = reverse ? offsetXLeft : \"0\";\n finalY = \"0\";\n break;\n }\n case \"right\": {\n const offsetXRight = `-${distance}*W`;\n activeX = `(${offsetXRight})*${finalTimeFactorExpr}`;\n activeY = \"0\";\n initialX = reverse ? \"0\" : offsetXRight;\n initialY = \"0\";\n finalX = reverse ? offsetXRight : \"0\";\n finalY = \"0\";\n break;\n }\n case \"up\": {\n const offsetYUp = `${distance}*H`;\n activeX = \"0\";\n activeY = `(${offsetYUp})*${finalTimeFactorExpr}`;\n initialX = \"0\";\n initialY = reverse ? \"0\" : offsetYUp;\n finalX = \"0\";\n finalY = reverse ? offsetYUp : \"0\";\n break;\n }\n case \"down\": {\n const offsetYDown = `-${distance}*H`;\n activeX = \"0\";\n activeY = `(${offsetYDown})*${finalTimeFactorExpr}`;\n initialX = \"0\";\n initialY = reverse ? \"0\" : offsetYDown;\n finalX = \"0\";\n finalY = reverse ? offsetYDown : \"0\";\n break;\n }\n }\n\n return { initialX, initialY, activeX, activeY, finalX, finalY, duration };\n}\n\nfunction processBounceMotion(\n motion: Extract<EffieMotion, { type: \"bounce\" }>,\n relativeTimeExpr: string,\n): MotionComponents {\n const amplitude = motion.amplitude ?? 0.5;\n const duration = motion.duration ?? 1;\n const initialY = `-overlay_h*${amplitude}`;\n const finalY = \"0\";\n\n // Calculate the normalized time expression (ranging from 0 to 1 over the duration)\n // Note: Assumes duration > 0. FFmpeg might handle division by zero, but it's safer to ensure duration > 0.\n const tNormExpr = `(${relativeTimeExpr})/${duration}`;\n\n // Piecewise parabolic approximation using normalized time (tNormExpr)\n const activeBounceExpression =\n `if(lt(${tNormExpr},0.363636),${initialY}+overlay_h*${amplitude}*(7.5625*${tNormExpr}*${tNormExpr}),` +\n `if(lt(${tNormExpr},0.727273),${initialY}+overlay_h*${amplitude}*(7.5625*(${tNormExpr}-0.545455)*(${tNormExpr}-0.545455)+0.75),` +\n `if(lt(${tNormExpr},0.909091),${initialY}+overlay_h*${amplitude}*(7.5625*(${tNormExpr}-0.818182)*(${tNormExpr}-0.818182)+0.9375),` +\n `if(lt(${tNormExpr},0.954545),${initialY}+overlay_h*${amplitude}*(7.5625*(${tNormExpr}-0.954545)*(${tNormExpr}-0.954545)+0.984375),` +\n `${finalY}` + // Should settle to finalY as tNormExpr approaches 1\n `))))`;\n\n return {\n initialX: \"0\",\n initialY: initialY,\n activeX: \"0\",\n activeY: activeBounceExpression, // This expression now scales with duration\n finalX: \"0\",\n finalY: finalY,\n duration: duration, // Return the actual duration used\n };\n}\n\nfunction processShakeMotion(\n motion: Extract<EffieMotion, { type: \"shake\" }>,\n relativeTimeExpr: string,\n): MotionComponents {\n const intensity = motion.intensity ?? 10;\n const frequency = motion.frequency ?? 4;\n const duration = motion.duration ?? 1;\n\n const activeX = `${intensity}*sin(${relativeTimeExpr}*PI*${frequency})`;\n const activeY = `${intensity}*cos(${relativeTimeExpr}*PI*${frequency})`;\n\n return {\n initialX: \"0\",\n initialY: \"0\",\n activeX: activeX,\n activeY: activeY,\n finalX: \"0\",\n finalY: \"0\",\n duration: duration,\n };\n}\n\nexport function processMotion(delay: number, motion?: EffieMotion): string {\n if (!motion) return \"x=0:y=0\";\n\n const start = delay + (motion.start ?? 0);\n const relativeTimeExpr = `(t-${start})`;\n let components: MotionComponents;\n\n switch (motion.type) {\n case \"bounce\":\n components = processBounceMotion(motion, relativeTimeExpr);\n break;\n case \"shake\":\n components = processShakeMotion(motion, relativeTimeExpr);\n break;\n case \"slide\":\n components = processSlideMotion(motion, relativeTimeExpr);\n break;\n default:\n motion satisfies never;\n throw new Error(\n `Unsupported motion type: ${(motion as EffieMotion).type}`,\n );\n }\n\n const motionEndTime = start + components.duration;\n\n const xArg = `if(lt(t,${start}),${components.initialX},if(lt(t,${motionEndTime}),${components.activeX},${components.finalX}))`;\n const yArg = `if(lt(t,${start}),${components.initialY},if(lt(t,${motionEndTime}),${components.activeY},${components.finalY}))`;\n\n return `x='${xArg}':y='${yArg}'`;\n}\n","import type { EffieEffect } from \"@effing/effie\";\n\nfunction processFadeIn(\n effect: Extract<EffieEffect, { type: \"fade-in\" }>,\n _frameRate: number,\n _frameWidth: number,\n _frameHeight: number,\n): string {\n return `fade=t=in:st=${effect.start}:d=${effect.duration}:alpha=1`;\n}\n\nfunction processFadeOut(\n effect: Extract<EffieEffect, { type: \"fade-out\" }>,\n _frameRate: number,\n _frameWidth: number,\n _frameHeight: number,\n): string {\n return `fade=t=out:st=${effect.start}:d=${effect.duration}:alpha=1`;\n}\n\nfunction processSaturateIn(\n effect: Extract<EffieEffect, { type: \"saturate-in\" }>,\n _frameRate: number,\n _frameWidth: number,\n _frameHeight: number,\n): string {\n return `hue='s=max(0,min(1,(t-${effect.start})/${effect.duration}))'`;\n}\n\nfunction processSaturateOut(\n effect: Extract<EffieEffect, { type: \"saturate-out\" }>,\n _frameRate: number,\n _frameWidth: number,\n _frameHeight: number,\n): string {\n return `hue='s=max(0,min(1,(${effect.start + effect.duration}-t)/${effect.duration}))'`;\n}\n\nfunction processScroll(\n effect: Extract<EffieEffect, { type: \"scroll\" }>,\n frameRate: number,\n _frameWidth: number,\n _frameHeight: number,\n): string {\n const distance = effect.distance ?? 1;\n const scroll = distance / (1 + distance);\n const speed = scroll / (effect.duration * frameRate);\n switch (effect.direction) {\n case \"left\":\n return `scroll=h=${speed}`;\n case \"right\":\n return `scroll=hpos=${1 - scroll}:h=-${speed}`;\n case \"up\":\n return `scroll=v=${speed}`;\n case \"down\":\n return `scroll=vpos=${1 - scroll}:v=-${speed}`;\n }\n}\n\nfunction processEffect(\n effect: EffieEffect,\n frameRate: number,\n frameWidth: number,\n frameHeight: number,\n): string {\n switch (effect.type) {\n case \"fade-in\":\n return processFadeIn(effect, frameRate, frameWidth, frameHeight);\n case \"fade-out\":\n return processFadeOut(effect, frameRate, frameWidth, frameHeight);\n case \"saturate-in\":\n return processSaturateIn(effect, frameRate, frameWidth, frameHeight);\n case \"saturate-out\":\n return processSaturateOut(effect, frameRate, frameWidth, frameHeight);\n case \"scroll\":\n return processScroll(effect, frameRate, frameWidth, frameHeight);\n default:\n effect satisfies never;\n throw new Error(\n `Unsupported effect type: ${(effect as EffieEffect).type}`,\n );\n }\n}\n\nexport function processEffects(\n effects: EffieEffect[] | undefined,\n frameRate: number,\n frameWidth: number,\n frameHeight: number,\n): string {\n if (!effects || effects.length === 0) return \"\";\n\n const filters: string[] = [];\n\n for (const effect of effects) {\n const filter = processEffect(effect, frameRate, frameWidth, frameHeight);\n filters.push(filter);\n }\n\n return filters.join(\",\");\n}\n","import type { ChildProcess } from \"child_process\";\nimport { spawn } from \"child_process\";\nimport type { Readable } from \"stream\";\nimport { pipeline } from \"stream\";\nimport fs from \"fs/promises\";\nimport os from \"os\";\nimport path from \"path\";\n\nimport tar from \"tar-stream\";\nimport { createWriteStream } from \"fs\";\nimport { promisify } from \"util\";\n\nconst pump = promisify(pipeline);\n\nlet resolvedBin: string | undefined;\nasync function getFFmpegBin(): Promise<string> {\n if (resolvedBin) return resolvedBin;\n if (process.env.FFMPEG) {\n resolvedBin = process.env.FFMPEG;\n return resolvedBin;\n }\n try {\n const { pathToFFmpeg } = await import(\"@effing/ffmpeg\");\n if (pathToFFmpeg) {\n resolvedBin = pathToFFmpeg;\n return resolvedBin;\n }\n } catch {\n // @effing/ffmpeg not installed\n }\n resolvedBin = \"ffmpeg\";\n return resolvedBin;\n}\n\n/**\n * Each input is represented by its index, its source, and the pre–arguments\n * that must appear immediately before its \"-i\" option.\n */\nexport type FFmpegInput = {\n index: number;\n source: string;\n preArgs: string[];\n type: \"image\" | \"video\" | \"audio\" | \"color\" | \"animation\";\n};\n\nexport class FFmpegCommand {\n globalArgs: string[];\n inputs: FFmpegInput[];\n filterComplex: string;\n outputArgs: string[];\n\n constructor(\n globalArgs: string[],\n inputs: FFmpegInput[],\n filterComplex: string,\n outputArgs: string[],\n ) {\n this.globalArgs = globalArgs;\n this.inputs = inputs;\n this.filterComplex = filterComplex;\n this.outputArgs = outputArgs;\n }\n\n buildArgs(inputResolver: (input: FFmpegInput) => string): string[] {\n const inputArgs: string[] = [];\n for (const input of this.inputs) {\n if (input.type === \"color\") {\n inputArgs.push(...input.preArgs);\n } else if (input.type === \"animation\") {\n inputArgs.push(\n ...input.preArgs,\n \"-i\",\n path.join(inputResolver(input), \"frame_%05d\"),\n );\n } else {\n inputArgs.push(...input.preArgs, \"-i\", inputResolver(input));\n }\n }\n const args = [\n ...this.globalArgs,\n ...inputArgs,\n \"-filter_complex\",\n this.filterComplex,\n ...this.outputArgs,\n ];\n return args;\n }\n}\n\nexport class FFmpegRunner {\n private command: FFmpegCommand;\n\n private ffmpegProc?: ChildProcess;\n\n constructor(command: FFmpegCommand) {\n this.command = command;\n }\n\n async run(\n sourceFetcher: (input: {\n type: FFmpegInput[\"type\"];\n src: string;\n }) => Promise<Readable>,\n imageTransformer?: (imageStream: Readable) => Promise<Readable>,\n referenceResolver?: (src: string) => string,\n urlTransformer?: (url: string) => string,\n ): Promise<Readable> {\n const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), \"ffs-\"));\n const fileMapping = new Map<number, string>();\n // Cache for #reference sources to avoid duplicate fetches.\n // Uses promises to handle concurrent requests for the same source.\n // Key is input.source (the original #ref) to preserve deduplication.\n const fetchCache = new Map<string, Promise<string>>();\n\n const fetchAndSaveSource = async (\n input: FFmpegInput,\n sourceUrl: string,\n inputName: string,\n ): Promise<string> => {\n const stream = await sourceFetcher({\n type: input.type,\n src: sourceUrl,\n });\n\n if (input.type === \"animation\") {\n // we expect annie files for animations,\n // which are a TAR that needs to be extracted\n const extractionDir = path.join(tempDir, inputName);\n await fs.mkdir(extractionDir, { recursive: true });\n const extract = tar.extract();\n const extractPromise = new Promise<void>((resolve, reject) => {\n extract.on(\"entry\", async (header, stream, next) => {\n if (header.name.startsWith(\"frame_\")) {\n const transformedStream = imageTransformer\n ? await imageTransformer(stream)\n : stream;\n const outputPath = path.join(extractionDir, header.name);\n const writeStream = createWriteStream(outputPath);\n transformedStream.pipe(writeStream);\n writeStream.on(\"finish\", next);\n writeStream.on(\"error\", reject);\n } else {\n stream.resume();\n next();\n }\n });\n extract.on(\"finish\", resolve);\n extract.on(\"error\", reject);\n });\n stream.pipe(extract);\n await extractPromise;\n return extractionDir;\n } else if (input.type === \"image\" && imageTransformer) {\n const tempFile = path.join(tempDir, inputName);\n const transformedStream = await imageTransformer(stream);\n const writeStream = createWriteStream(tempFile);\n transformedStream.on(\"error\", (e) => writeStream.destroy(e));\n await pump(transformedStream, writeStream);\n return tempFile;\n } else {\n const tempFile = path.join(tempDir, inputName);\n const writeStream = createWriteStream(tempFile);\n stream.on(\"error\", (e) => writeStream.destroy(e));\n await pump(stream, writeStream);\n return tempFile;\n }\n };\n\n await Promise.all(\n this.command.inputs.map(async (input) => {\n if (input.type === \"color\") return;\n\n const inputName = `ffmpeg_input_${input.index\n .toString()\n .padStart(3, \"0\")}`;\n\n // Resolve #references to get the actual URL\n const sourceUrl = referenceResolver\n ? referenceResolver(input.source)\n : input.source;\n\n // Pass HTTP(S) video/audio URLs directly to FFmpeg without downloading\n // If urlTransformer is provided, transform the URL (e.g., for proxy)\n if (\n (input.type === \"video\" || input.type === \"audio\") &&\n (sourceUrl.startsWith(\"http://\") || sourceUrl.startsWith(\"https://\"))\n ) {\n const finalUrl = urlTransformer\n ? urlTransformer(sourceUrl)\n : sourceUrl;\n fileMapping.set(input.index, finalUrl);\n return;\n }\n\n // Deduplicate fetches when the same #ref appears multiple times.\n // Only for #refs since they're short strings; data URLs would bloat the map.\n const shouldCache = input.source.startsWith(\"#\");\n if (shouldCache) {\n let fetchPromise = fetchCache.get(input.source);\n if (!fetchPromise) {\n fetchPromise = fetchAndSaveSource(input, sourceUrl, inputName);\n fetchCache.set(input.source, fetchPromise);\n }\n const filePath = await fetchPromise;\n fileMapping.set(input.index, filePath);\n } else {\n const filePath = await fetchAndSaveSource(\n input,\n sourceUrl,\n inputName,\n );\n fileMapping.set(input.index, filePath);\n }\n }),\n );\n\n const finalArgs = this.command.buildArgs((input) => {\n const filePath = fileMapping.get(input.index);\n if (!filePath)\n throw new Error(`File for input index ${input.index} not found`);\n return filePath;\n });\n const ffmpegProc = spawn(await getFFmpegBin(), finalArgs);\n ffmpegProc.stderr!.on(\"data\", (data) => {\n console.error(data.toString());\n });\n\n ffmpegProc.on(\"close\", async () => {\n try {\n await fs.rm(tempDir, { recursive: true, force: true });\n } catch (err) {\n console.error(\"Error removing temp directory:\", err);\n }\n });\n\n this.ffmpegProc = ffmpegProc;\n return ffmpegProc.stdout as Readable;\n }\n\n close(): void {\n if (this.ffmpegProc) {\n this.ffmpegProc.kill(\"SIGTERM\");\n this.ffmpegProc = undefined;\n }\n }\n}\n","import type { EffieTransition } from \"@effing/effie\";\n\nexport function processTransition(transition: EffieTransition): string {\n switch (transition.type) {\n case \"fade\": {\n if (\"through\" in transition) {\n // Fade through color: fadeblack, fadewhite, fadegrays\n return `fade${transition.through}`;\n }\n // Crossfade with easing\n const easing = transition.easing ?? \"linear\";\n return {\n linear: \"fade\",\n \"ease-in\": \"fadeslow\",\n \"ease-out\": \"fadefast\",\n }[easing];\n }\n case \"barn\": {\n // Barn door wipes: vertopen, vertclose, horzopen, horzclose\n const orientation = transition.orientation ?? \"horizontal\";\n const mode = transition.mode ?? \"open\";\n const prefix = orientation === \"vertical\" ? \"vert\" : \"horz\";\n return `${prefix}${mode}`;\n }\n case \"circle\": {\n // Circle wipes: circleopen, circleclose, circlecrop\n const mode = transition.mode ?? \"open\";\n return `circle${mode}`;\n }\n case \"wipe\":\n case \"slide\":\n case \"smooth\": {\n const direction = transition.direction ?? \"left\";\n return `${transition.type}${direction}`;\n }\n case \"slice\": {\n const direction = transition.direction ?? \"left\";\n const prefix = {\n left: \"hl\",\n right: \"hr\",\n up: \"vu\",\n down: \"vd\",\n }[direction];\n return `${prefix}${transition.type}`;\n }\n case \"zoom\": {\n return \"zoomin\";\n }\n case \"dissolve\":\n case \"pixelize\":\n case \"radial\":\n return transition.type;\n default:\n transition satisfies never;\n throw new Error(\n `Unsupported transition type: ${(transition as EffieTransition).type}`,\n );\n }\n}\n"],"mappings":"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","names":["stream"]}
@@ -32,14 +32,14 @@ var S3TransientStore = class {
32
32
  forcePathStyle: !!options.endpoint
33
33
  });
34
34
  this.bucket = options.bucket;
35
- this.prefix = options.prefix ?? "";
35
+ this.prefix = (options.prefix ?? "").replace(/\/+$/, "");
36
36
  this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
37
37
  }
38
38
  getExpires(ttlMs) {
39
39
  return new Date(Date.now() + ttlMs);
40
40
  }
41
41
  getFullKey(key) {
42
- return `${this.prefix}${key}`;
42
+ return this.prefix ? `${this.prefix}/${key}` : key;
43
43
  }
44
44
  async put(key, stream, ttlMs) {
45
45
  const upload = new Upload({
@@ -51,14 +51,14 @@ var S3TransientStore = class {
51
51
  forcePathStyle: !!options.endpoint
52
52
  });
53
53
  this.bucket = options.bucket;
54
- this.prefix = options.prefix ?? "";
54
+ this.prefix = (options.prefix ?? "").replace(/\/+$/, "");
55
55
  this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
56
56
  }
57
57
  getExpires(ttlMs) {
58
58
  return new Date(Date.now() + ttlMs);
59
59
  }
60
60
  getFullKey(key) {
61
- return `${this.prefix}${key}`;
61
+ return this.prefix ? `${this.prefix}/${key}` : key;
62
62
  }
63
63
  async put(key, stream, ttlMs) {
64
64
  const upload = new Upload({
@@ -327,4 +327,4 @@ export {
327
327
  createTransientStore,
328
328
  storeKeys
329
329
  };
330
- //# sourceMappingURL=chunk-UN34ESVZ.js.map
330
+ //# sourceMappingURL=chunk-WB5NFSQQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/fetch.ts","../src/storage.ts"],"sourcesContent":["import { fetch, Agent, type Response, type BodyInit } from \"undici\";\n\n/**\n * Options for ffsFetch function\n */\nexport type FfsFetchOptions = {\n /** HTTP method */\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"HEAD\" | \"OPTIONS\";\n /** Request body */\n body?: BodyInit;\n /** Headers to send (merged with default User-Agent) */\n headers?: Record<string, string>;\n /** Timeout for receiving response headers in ms. @default 300000 (5 min) */\n headersTimeout?: number;\n /** Timeout between body data chunks in ms. 0 = no timeout. @default 300000 (5 min) */\n bodyTimeout?: number;\n};\n\n/**\n * Fetch with default User-Agent and configurable timeouts.\n *\n * @example\n * // Simple GET\n * const response = await ffsFetch(\"https://example.com/data.json\");\n *\n * @example\n * // Large file with infinite body timeout\n * const response = await ffsFetch(\"https://example.com/video.mp4\", {\n * bodyTimeout: 0,\n * });\n *\n * @example\n * // PUT upload\n * const response = await ffsFetch(\"https://s3.example.com/video.mp4\", {\n * method: \"PUT\",\n * body: videoBuffer,\n * bodyTimeout: 0,\n * headers: { \"Content-Type\": \"video/mp4\" },\n * });\n */\nexport async function ffsFetch(\n url: string,\n options?: FfsFetchOptions,\n): Promise<Response> {\n const {\n method,\n body,\n headers,\n headersTimeout = 300000, // 5 minutes\n bodyTimeout = 300000, // 5 minutes\n } = options ?? {};\n\n const agent = new Agent({ headersTimeout, bodyTimeout });\n\n return fetch(url, {\n method,\n body,\n headers: { \"User-Agent\": \"FFS (+https://effing.dev/ffs)\", ...headers },\n dispatcher: agent,\n });\n}\n","import {\n S3Client,\n PutObjectCommand,\n GetObjectCommand,\n HeadObjectCommand,\n DeleteObjectCommand,\n} from \"@aws-sdk/client-s3\";\nimport { Upload } from \"@aws-sdk/lib-storage\";\nimport fs from \"fs/promises\";\nimport { createReadStream, createWriteStream, existsSync } from \"fs\";\nimport { pipeline } from \"stream/promises\";\nimport path from \"path\";\nimport os from \"os\";\nimport crypto from \"crypto\";\nimport type { Readable } from \"stream\";\n\n/** Default TTL: 60 minutes */\nconst DEFAULT_TTL_MS = 60 * 60 * 1000;\n\n/**\n * Transient store interface for caching sources and storing ephemeral job data.\n */\nexport interface TransientStore {\n /** TTL in milliseconds */\n readonly ttlMs: number;\n /** Store a stream with the given key and optional TTL override */\n put(key: string, stream: Readable, ttlMs?: number): Promise<void>;\n /** Get a stream for the given key, or null if not found */\n getStream(key: string): Promise<Readable | null>;\n /** Check if a key exists */\n exists(key: string): Promise<boolean>;\n /** Check if multiple keys exist (batch operation) */\n existsMany(keys: string[]): Promise<Map<string, boolean>>;\n /** Delete a key */\n delete(key: string): Promise<void>;\n /** Store JSON data with optional TTL override */\n putJson(key: string, data: object, ttlMs?: number): Promise<void>;\n /** Get JSON data, or null if not found */\n getJson<T>(key: string): Promise<T | null>;\n /** Close and cleanup resources */\n close(): void;\n}\n\n/**\n * S3-compatible transient store implementation\n */\nexport class S3TransientStore implements TransientStore {\n private client: S3Client;\n private bucket: string;\n private prefix: string;\n public readonly ttlMs: number;\n\n constructor(options: {\n endpoint?: string;\n region?: string;\n bucket: string;\n prefix?: string;\n accessKeyId?: string;\n secretAccessKey?: string;\n ttlMs?: number;\n }) {\n this.client = new S3Client({\n endpoint: options.endpoint,\n region: options.region ?? \"auto\",\n credentials: options.accessKeyId\n ? {\n accessKeyId: options.accessKeyId,\n secretAccessKey: options.secretAccessKey!,\n }\n : undefined,\n forcePathStyle: !!options.endpoint,\n });\n this.bucket = options.bucket;\n this.prefix = (options.prefix ?? \"\").replace(/\\/+$/, \"\");\n this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;\n }\n\n private getExpires(ttlMs: number): Date {\n return new Date(Date.now() + ttlMs);\n }\n\n private getFullKey(key: string): string {\n return this.prefix ? `${this.prefix}/${key}` : key;\n }\n\n async put(key: string, stream: Readable, ttlMs?: number): Promise<void> {\n const upload = new Upload({\n client: this.client,\n params: {\n Bucket: this.bucket,\n Key: this.getFullKey(key),\n Body: stream,\n Expires: this.getExpires(ttlMs ?? this.ttlMs),\n },\n });\n await upload.done();\n }\n\n async getStream(key: string): Promise<Readable | null> {\n try {\n const response = await this.client.send(\n new GetObjectCommand({\n Bucket: this.bucket,\n Key: this.getFullKey(key),\n }),\n );\n return response.Body as Readable;\n } catch (err: unknown) {\n const error = err as {\n name?: string;\n $metadata?: { httpStatusCode?: number };\n };\n if (\n error.name === \"NoSuchKey\" ||\n error.$metadata?.httpStatusCode === 404\n ) {\n return null;\n }\n throw err;\n }\n }\n\n async exists(key: string): Promise<boolean> {\n try {\n await this.client.send(\n new HeadObjectCommand({\n Bucket: this.bucket,\n Key: this.getFullKey(key),\n }),\n );\n return true;\n } catch (err: unknown) {\n const error = err as {\n name?: string;\n $metadata?: { httpStatusCode?: number };\n };\n if (\n error.name === \"NotFound\" ||\n error.$metadata?.httpStatusCode === 404\n ) {\n return false;\n }\n throw err;\n }\n }\n\n async existsMany(keys: string[]): Promise<Map<string, boolean>> {\n const results = await Promise.all(\n keys.map(async (key) => [key, await this.exists(key)] as const),\n );\n return new Map(results);\n }\n\n async delete(key: string): Promise<void> {\n try {\n await this.client.send(\n new DeleteObjectCommand({\n Bucket: this.bucket,\n Key: this.getFullKey(key),\n }),\n );\n } catch (err: unknown) {\n const error = err as {\n name?: string;\n $metadata?: { httpStatusCode?: number };\n };\n if (\n error.name === \"NoSuchKey\" ||\n error.$metadata?.httpStatusCode === 404\n ) {\n return;\n }\n throw err;\n }\n }\n\n async putJson(key: string, data: object, ttlMs?: number): Promise<void> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.bucket,\n Key: this.getFullKey(key),\n Body: JSON.stringify(data),\n ContentType: \"application/json\",\n Expires: this.getExpires(ttlMs ?? this.ttlMs),\n }),\n );\n }\n\n async getJson<T>(key: string): Promise<T | null> {\n try {\n const response = await this.client.send(\n new GetObjectCommand({\n Bucket: this.bucket,\n Key: this.getFullKey(key),\n }),\n );\n const body = await response.Body?.transformToString();\n if (!body) return null;\n return JSON.parse(body) as T;\n } catch (err: unknown) {\n const error = err as {\n name?: string;\n $metadata?: { httpStatusCode?: number };\n };\n if (\n error.name === \"NoSuchKey\" ||\n error.$metadata?.httpStatusCode === 404\n ) {\n return null;\n }\n throw err;\n }\n }\n\n close(): void {\n // nothing to do here\n }\n}\n\n/**\n * Local filesystem transient store implementation\n */\nexport class LocalTransientStore implements TransientStore {\n private baseDir: string;\n private initialized = false;\n private cleanupInterval?: ReturnType<typeof setInterval>;\n public readonly ttlMs: number;\n\n constructor(options?: { baseDir?: string; ttlMs?: number }) {\n this.baseDir = options?.baseDir ?? path.join(os.tmpdir(), \"ffs-transient\");\n this.ttlMs = options?.ttlMs ?? DEFAULT_TTL_MS;\n\n // Cleanup expired files every 5 minutes\n this.cleanupInterval = setInterval(() => {\n this.cleanupExpired().catch(console.error);\n }, 300_000);\n }\n\n /**\n * Remove files older than max TTL\n */\n public async cleanupExpired(): Promise<void> {\n if (!this.initialized) return;\n\n const now = Date.now();\n await this.cleanupDir(this.baseDir, now);\n }\n\n private async cleanupDir(dir: string, now: number): Promise<void> {\n let entries;\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch {\n return; // Directory doesn't exist or can't be read\n }\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n await this.cleanupDir(fullPath, now);\n // Remove empty directories\n try {\n await fs.rmdir(fullPath);\n } catch {\n // Directory not empty or other error, ignore\n }\n } else if (entry.isFile()) {\n try {\n const stat = await fs.stat(fullPath);\n if (now - stat.mtimeMs > this.ttlMs) {\n await fs.rm(fullPath, { force: true });\n }\n } catch {\n // File may have been deleted, ignore\n }\n }\n }\n }\n\n private async ensureDir(filePath: string): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n this.initialized = true;\n }\n\n private filePath(key: string): string {\n return path.join(this.baseDir, key);\n }\n\n private tmpPathFor(finalPath: string): string {\n const rand = crypto.randomBytes(8).toString(\"hex\");\n // Keep tmp file in the same directory so rename stays atomic on POSIX filesystems.\n return `${finalPath}.tmp-${process.pid}-${rand}`;\n }\n\n async put(key: string, stream: Readable, _ttlMs?: number): Promise<void> {\n // Note: TTL is not used for local storage; cleanup uses file mtime\n const fp = this.filePath(key);\n await this.ensureDir(fp);\n\n // Write to temp file, then rename for atomicity (no partial reads).\n const tmpPath = this.tmpPathFor(fp);\n try {\n const writeStream = createWriteStream(tmpPath);\n await pipeline(stream, writeStream);\n await fs.rename(tmpPath, fp);\n } catch (err) {\n await fs.rm(tmpPath, { force: true }).catch(() => {});\n throw err;\n }\n }\n\n async getStream(key: string): Promise<Readable | null> {\n const fp = this.filePath(key);\n if (!existsSync(fp)) return null;\n return createReadStream(fp);\n }\n\n async exists(key: string): Promise<boolean> {\n try {\n await fs.access(this.filePath(key));\n return true;\n } catch {\n return false;\n }\n }\n\n async existsMany(keys: string[]): Promise<Map<string, boolean>> {\n const results = await Promise.all(\n keys.map(async (key) => [key, await this.exists(key)] as const),\n );\n return new Map(results);\n }\n\n async delete(key: string): Promise<void> {\n await fs.rm(this.filePath(key), { force: true });\n }\n\n async putJson(key: string, data: object, _ttlMs?: number): Promise<void> {\n // Note: TTL is not used for local storage; cleanup uses file mtime\n const fp = this.filePath(key);\n await this.ensureDir(fp);\n\n // Write to temp file, then rename for atomicity (no partial reads).\n const tmpPath = this.tmpPathFor(fp);\n try {\n await fs.writeFile(tmpPath, JSON.stringify(data));\n await fs.rename(tmpPath, fp);\n } catch (err) {\n await fs.rm(tmpPath, { force: true }).catch(() => {});\n throw err;\n }\n }\n\n async getJson<T>(key: string): Promise<T | null> {\n try {\n const content = await fs.readFile(this.filePath(key), \"utf-8\");\n return JSON.parse(content) as T;\n } catch {\n return null;\n }\n }\n\n close(): void {\n // Stop the cleanup interval\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n }\n }\n}\n\n/**\n * Create a transient store instance based on environment variables.\n * Uses S3 if FFS_TRANSIENT_STORE_BUCKET is set, otherwise uses local filesystem.\n */\nexport function createTransientStore(): TransientStore {\n const ttlMs = process.env.FFS_TRANSIENT_STORE_TTL_MS\n ? parseInt(process.env.FFS_TRANSIENT_STORE_TTL_MS, 10)\n : DEFAULT_TTL_MS;\n\n if (process.env.FFS_TRANSIENT_STORE_BUCKET) {\n return new S3TransientStore({\n endpoint: process.env.FFS_TRANSIENT_STORE_ENDPOINT,\n region: process.env.FFS_TRANSIENT_STORE_REGION ?? \"auto\",\n bucket: process.env.FFS_TRANSIENT_STORE_BUCKET,\n prefix: process.env.FFS_TRANSIENT_STORE_PREFIX,\n accessKeyId: process.env.FFS_TRANSIENT_STORE_ACCESS_KEY,\n secretAccessKey: process.env.FFS_TRANSIENT_STORE_SECRET_KEY,\n ttlMs,\n });\n }\n\n return new LocalTransientStore({\n baseDir: process.env.FFS_TRANSIENT_STORE_LOCAL_DIR,\n ttlMs,\n });\n}\n\nexport function hashUrl(url: string): string {\n return crypto.createHash(\"sha256\").update(url).digest(\"hex\").slice(0, 16);\n}\n\nexport type SourceStoreKey = `sources/${string}`;\nexport type WarmupJobStoreKey = `jobs/warmup/${string}.json`;\nexport type RenderJobStoreKey = `jobs/render/${string}.json`;\nexport type VideoJobStoreKey = `jobs/video/${string}.json`;\n\n/**\n * Build the store key for a source URL (hashing is handled internally).\n */\nexport function sourceStoreKey(url: string): SourceStoreKey {\n return `sources/${hashUrl(url)}`;\n}\n\nexport function warmupJobStoreKey(jobId: string): WarmupJobStoreKey {\n return `jobs/warmup/${jobId}.json`;\n}\n\nexport function renderJobStoreKey(jobId: string): RenderJobStoreKey {\n return `jobs/render/${jobId}.json`;\n}\n\nexport function videoJobStoreKey(jobId: string): VideoJobStoreKey {\n return `jobs/video/${jobId}.json`;\n}\n\n/**\n * Centralized store key builders for known namespaces.\n * Prefer using these helpers over manual string interpolation.\n */\nexport const storeKeys = {\n source: sourceStoreKey,\n warmupJob: warmupJobStoreKey,\n renderJob: renderJobStoreKey,\n videoJob: videoJobStoreKey,\n} as const;\n"],"mappings":";AAAA,SAAS,OAAO,aAA2C;AAwC3D,eAAsB,SACpB,KACA,SACmB;AACnB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA;AAAA,IACjB,cAAc;AAAA;AAAA,EAChB,IAAI,WAAW,CAAC;AAEhB,QAAM,QAAQ,IAAI,MAAM,EAAE,gBAAgB,YAAY,CAAC;AAEvD,SAAO,MAAM,KAAK;AAAA,IAChB;AAAA,IACA;AAAA,IACA,SAAS,EAAE,cAAc,iCAAiC,GAAG,QAAQ;AAAA,IACrE,YAAY;AAAA,EACd,CAAC;AACH;;;AC5DA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc;AACvB,OAAO,QAAQ;AACf,SAAS,kBAAkB,mBAAmB,kBAAkB;AAChE,SAAS,gBAAgB;AACzB,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,OAAO,YAAY;AAInB,IAAM,iBAAiB,KAAK,KAAK;AA6B1B,IAAM,mBAAN,MAAiD;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACQ;AAAA,EAEhB,YAAY,SAQT;AACD,SAAK,SAAS,IAAI,SAAS;AAAA,MACzB,UAAU,QAAQ;AAAA,MAClB,QAAQ,QAAQ,UAAU;AAAA,MAC1B,aAAa,QAAQ,cACjB;AAAA,QACE,aAAa,QAAQ;AAAA,QACrB,iBAAiB,QAAQ;AAAA,MAC3B,IACA;AAAA,MACJ,gBAAgB,CAAC,CAAC,QAAQ;AAAA,IAC5B,CAAC;AACD,SAAK,SAAS,QAAQ;AACtB,SAAK,UAAU,QAAQ,UAAU,IAAI,QAAQ,QAAQ,EAAE;AACvD,SAAK,QAAQ,QAAQ,SAAS;AAAA,EAChC;AAAA,EAEQ,WAAW,OAAqB;AACtC,WAAO,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK;AAAA,EACpC;AAAA,EAEQ,WAAW,KAAqB;AACtC,WAAO,KAAK,SAAS,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK;AAAA,EACjD;AAAA,EAEA,MAAM,IAAI,KAAa,QAAkB,OAA+B;AACtE,UAAM,SAAS,IAAI,OAAO;AAAA,MACxB,QAAQ,KAAK;AAAA,MACb,QAAQ;AAAA,QACN,QAAQ,KAAK;AAAA,QACb,KAAK,KAAK,WAAW,GAAG;AAAA,QACxB,MAAM;AAAA,QACN,SAAS,KAAK,WAAW,SAAS,KAAK,KAAK;AAAA,MAC9C;AAAA,IACF,CAAC;AACD,UAAM,OAAO,KAAK;AAAA,EACpB;AAAA,EAEA,MAAM,UAAU,KAAuC;AACrD,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,OAAO;AAAA,QACjC,IAAI,iBAAiB;AAAA,UACnB,QAAQ,KAAK;AAAA,UACb,KAAK,KAAK,WAAW,GAAG;AAAA,QAC1B,CAAC;AAAA,MACH;AACA,aAAO,SAAS;AAAA,IAClB,SAAS,KAAc;AACrB,YAAM,QAAQ;AAId,UACE,MAAM,SAAS,eACf,MAAM,WAAW,mBAAmB,KACpC;AACA,eAAO;AAAA,MACT;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,KAA+B;AAC1C,QAAI;AACF,YAAM,KAAK,OAAO;AAAA,QAChB,IAAI,kBAAkB;AAAA,UACpB,QAAQ,KAAK;AAAA,UACb,KAAK,KAAK,WAAW,GAAG;AAAA,QAC1B,CAAC;AAAA,MACH;AACA,aAAO;AAAA,IACT,SAAS,KAAc;AACrB,YAAM,QAAQ;AAId,UACE,MAAM,SAAS,cACf,MAAM,WAAW,mBAAmB,KACpC;AACA,eAAO;AAAA,MACT;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,MAA+C;AAC9D,UAAM,UAAU,MAAM,QAAQ;AAAA,MAC5B,KAAK,IAAI,OAAO,QAAQ,CAAC,KAAK,MAAM,KAAK,OAAO,GAAG,CAAC,CAAU;AAAA,IAChE;AACA,WAAO,IAAI,IAAI,OAAO;AAAA,EACxB;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,QAAI;AACF,YAAM,KAAK,OAAO;AAAA,QAChB,IAAI,oBAAoB;AAAA,UACtB,QAAQ,KAAK;AAAA,UACb,KAAK,KAAK,WAAW,GAAG;AAAA,QAC1B,CAAC;AAAA,MACH;AAAA,IACF,SAAS,KAAc;AACrB,YAAM,QAAQ;AAId,UACE,MAAM,SAAS,eACf,MAAM,WAAW,mBAAmB,KACpC;AACA;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,KAAa,MAAc,OAA+B;AACtE,UAAM,KAAK,OAAO;AAAA,MAChB,IAAI,iBAAiB;AAAA,QACnB,QAAQ,KAAK;AAAA,QACb,KAAK,KAAK,WAAW,GAAG;AAAA,QACxB,MAAM,KAAK,UAAU,IAAI;AAAA,QACzB,aAAa;AAAA,QACb,SAAS,KAAK,WAAW,SAAS,KAAK,KAAK;AAAA,MAC9C,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,QAAW,KAAgC;AAC/C,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,OAAO;AAAA,QACjC,IAAI,iBAAiB;AAAA,UACnB,QAAQ,KAAK;AAAA,UACb,KAAK,KAAK,WAAW,GAAG;AAAA,QAC1B,CAAC;AAAA,MACH;AACA,YAAM,OAAO,MAAM,SAAS,MAAM,kBAAkB;AACpD,UAAI,CAAC,KAAM,QAAO;AAClB,aAAO,KAAK,MAAM,IAAI;AAAA,IACxB,SAAS,KAAc;AACrB,YAAM,QAAQ;AAId,UACE,MAAM,SAAS,eACf,MAAM,WAAW,mBAAmB,KACpC;AACA,eAAO;AAAA,MACT;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,QAAc;AAAA,EAEd;AACF;AAKO,IAAM,sBAAN,MAAoD;AAAA,EACjD;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACQ;AAAA,EAEhB,YAAY,SAAgD;AAC1D,SAAK,UAAU,SAAS,WAAW,KAAK,KAAK,GAAG,OAAO,GAAG,eAAe;AACzE,SAAK,QAAQ,SAAS,SAAS;AAG/B,SAAK,kBAAkB,YAAY,MAAM;AACvC,WAAK,eAAe,EAAE,MAAM,QAAQ,KAAK;AAAA,IAC3C,GAAG,GAAO;AAAA,EACZ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAa,iBAAgC;AAC3C,QAAI,CAAC,KAAK,YAAa;AAEvB,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,KAAK,WAAW,KAAK,SAAS,GAAG;AAAA,EACzC;AAAA,EAEA,MAAc,WAAW,KAAa,KAA4B;AAChE,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM,GAAG,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IACzD,QAAQ;AACN;AAAA,IACF;AAEA,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAW,KAAK,KAAK,KAAK,MAAM,IAAI;AAE1C,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,KAAK,WAAW,UAAU,GAAG;AAEnC,YAAI;AACF,gBAAM,GAAG,MAAM,QAAQ;AAAA,QACzB,QAAQ;AAAA,QAER;AAAA,MACF,WAAW,MAAM,OAAO,GAAG;AACzB,YAAI;AACF,gBAAM,OAAO,MAAM,GAAG,KAAK,QAAQ;AACnC,cAAI,MAAM,KAAK,UAAU,KAAK,OAAO;AACnC,kBAAM,GAAG,GAAG,UAAU,EAAE,OAAO,KAAK,CAAC;AAAA,UACvC;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,UAAU,UAAiC;AACvD,UAAM,GAAG,MAAM,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAC1D,SAAK,cAAc;AAAA,EACrB;AAAA,EAEQ,SAAS,KAAqB;AACpC,WAAO,KAAK,KAAK,KAAK,SAAS,GAAG;AAAA,EACpC;AAAA,EAEQ,WAAW,WAA2B;AAC5C,UAAM,OAAO,OAAO,YAAY,CAAC,EAAE,SAAS,KAAK;AAEjD,WAAO,GAAG,SAAS,QAAQ,QAAQ,GAAG,IAAI,IAAI;AAAA,EAChD;AAAA,EAEA,MAAM,IAAI,KAAa,QAAkB,QAAgC;AAEvE,UAAM,KAAK,KAAK,SAAS,GAAG;AAC5B,UAAM,KAAK,UAAU,EAAE;AAGvB,UAAM,UAAU,KAAK,WAAW,EAAE;AAClC,QAAI;AACF,YAAM,cAAc,kBAAkB,OAAO;AAC7C,YAAM,SAAS,QAAQ,WAAW;AAClC,YAAM,GAAG,OAAO,SAAS,EAAE;AAAA,IAC7B,SAAS,KAAK;AACZ,YAAM,GAAG,GAAG,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACpD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,KAAuC;AACrD,UAAM,KAAK,KAAK,SAAS,GAAG;AAC5B,QAAI,CAAC,WAAW,EAAE,EAAG,QAAO;AAC5B,WAAO,iBAAiB,EAAE;AAAA,EAC5B;AAAA,EAEA,MAAM,OAAO,KAA+B;AAC1C,QAAI;AACF,YAAM,GAAG,OAAO,KAAK,SAAS,GAAG,CAAC;AAClC,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,MAA+C;AAC9D,UAAM,UAAU,MAAM,QAAQ;AAAA,MAC5B,KAAK,IAAI,OAAO,QAAQ,CAAC,KAAK,MAAM,KAAK,OAAO,GAAG,CAAC,CAAU;AAAA,IAChE;AACA,WAAO,IAAI,IAAI,OAAO;AAAA,EACxB;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,UAAM,GAAG,GAAG,KAAK,SAAS,GAAG,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,EACjD;AAAA,EAEA,MAAM,QAAQ,KAAa,MAAc,QAAgC;AAEvE,UAAM,KAAK,KAAK,SAAS,GAAG;AAC5B,UAAM,KAAK,UAAU,EAAE;AAGvB,UAAM,UAAU,KAAK,WAAW,EAAE;AAClC,QAAI;AACF,YAAM,GAAG,UAAU,SAAS,KAAK,UAAU,IAAI,CAAC;AAChD,YAAM,GAAG,OAAO,SAAS,EAAE;AAAA,IAC7B,SAAS,KAAK;AACZ,YAAM,GAAG,GAAG,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACpD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,QAAW,KAAgC;AAC/C,QAAI;AACF,YAAM,UAAU,MAAM,GAAG,SAAS,KAAK,SAAS,GAAG,GAAG,OAAO;AAC7D,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,QAAc;AAEZ,QAAI,KAAK,iBAAiB;AACxB,oBAAc,KAAK,eAAe;AAClC,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AACF;AAMO,SAAS,uBAAuC;AACrD,QAAM,QAAQ,QAAQ,IAAI,6BACtB,SAAS,QAAQ,IAAI,4BAA4B,EAAE,IACnD;AAEJ,MAAI,QAAQ,IAAI,4BAA4B;AAC1C,WAAO,IAAI,iBAAiB;AAAA,MAC1B,UAAU,QAAQ,IAAI;AAAA,MACtB,QAAQ,QAAQ,IAAI,8BAA8B;AAAA,MAClD,QAAQ,QAAQ,IAAI;AAAA,MACpB,QAAQ,QAAQ,IAAI;AAAA,MACpB,aAAa,QAAQ,IAAI;AAAA,MACzB,iBAAiB,QAAQ,IAAI;AAAA,MAC7B;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO,IAAI,oBAAoB;AAAA,IAC7B,SAAS,QAAQ,IAAI;AAAA,IACrB;AAAA,EACF,CAAC;AACH;AAEO,SAAS,QAAQ,KAAqB;AAC3C,SAAO,OAAO,WAAW,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAC1E;AAUO,SAAS,eAAe,KAA6B;AAC1D,SAAO,WAAW,QAAQ,GAAG,CAAC;AAChC;AAEO,SAAS,kBAAkB,OAAkC;AAClE,SAAO,eAAe,KAAK;AAC7B;AAEO,SAAS,kBAAkB,OAAkC;AAClE,SAAO,eAAe,KAAK;AAC7B;AAEO,SAAS,iBAAiB,OAAiC;AAChE,SAAO,cAAc,KAAK;AAC5B;AAMO,IAAM,YAAY;AAAA,EACvB,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,WAAW;AAAA,EACX,UAAU;AACZ;","names":[]}
@@ -1,9 +1,32 @@
1
1
  import express from 'express';
2
2
  import { Response } from 'undici';
3
3
  import { T as TransientStore, H as HttpProxy } from '../proxy-BNr00n_4.js';
4
+ import { EventSender } from '../sse.js';
4
5
  import { EffieSourceWithType, EffieData, EffieSources } from '@effing/effie';
5
6
  import 'stream';
6
7
 
8
+ declare const ErrorCode: {
9
+ readonly UNAUTHORIZED: "UNAUTHORIZED";
10
+ readonly INVALID_EFFIE: "INVALID_EFFIE";
11
+ readonly NOT_FOUND: "NOT_FOUND";
12
+ readonly BACKEND_FAILED: "BACKEND_FAILED";
13
+ readonly INTERNAL_ERROR: "INTERNAL_ERROR";
14
+ readonly FETCH_FAILED: "FETCH_FAILED";
15
+ };
16
+ type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
17
+ type ApiError = {
18
+ error: string;
19
+ code: ErrorCode;
20
+ issues?: Array<{
21
+ path: string;
22
+ message: string;
23
+ }>;
24
+ };
25
+ declare function sendError(res: express.Response, status: number, code: ErrorCode, message: string, issues?: Array<{
26
+ path: string;
27
+ message: string;
28
+ }>): void;
29
+
7
30
  type UploadOptions = {
8
31
  videoUrl: string;
9
32
  coverUrl?: string;
@@ -31,6 +54,7 @@ type RenderJob = {
31
54
  type VideoJob = {
32
55
  effie: EffieData<EffieSources>;
33
56
  scale: number;
57
+ metadata?: Record<string, unknown>;
34
58
  };
35
59
  type ServerContext = {
36
60
  transientStore: TransientStore;
@@ -41,7 +65,6 @@ type ServerContext = {
41
65
  warmupBackendResolver?: WarmupBackendResolver;
42
66
  renderBackendResolver?: RenderBackendResolver;
43
67
  };
44
- type SSEEventSender = (event: string, data: object) => void;
45
68
  /**
46
69
  * Create the server context with configuration from environment variables
47
70
  */
@@ -53,7 +76,7 @@ declare function createServerContext(options?: {
53
76
  /**
54
77
  * Proxy SSE events from a remote backend, prefixing event names
55
78
  */
56
- declare function proxyRemoteSSE(url: string, sendEvent: SSEEventSender, prefix: string, res: express.Response, headers?: Record<string, string>): Promise<void>;
79
+ declare function proxyRemoteSSE(url: string, sendEvent: EventSender, prefix: string, res: express.Response, headers?: Record<string, string>): Promise<void>;
57
80
  /**
58
81
  * Proxy a binary stream (e.g., video) from a fetch Response to an Express response.
59
82
  * Forwards Content-Type and Content-Length headers.
@@ -95,4 +118,4 @@ declare function streamRenderProgress(req: express.Request, res: express.Respons
95
118
  */
96
119
  declare function streamRenderVideo(req: express.Request, res: express.Response, ctx: ServerContext): Promise<void>;
97
120
 
98
- export { type BackendConfig, type RenderBackendResolver, type RenderJob, type SSEEventSender, type ServerContext, type UploadOptions, type VideoJob, type WarmupBackendResolver, type WarmupJob, createRenderJob, createServerContext, createWarmupJob, proxyBinaryStream, proxyRemoteSSE, purgeCache, streamRenderProgress, streamRenderVideo, streamWarmupProgress };
121
+ export { type ApiError, type BackendConfig, ErrorCode, EventSender, type RenderBackendResolver, type RenderJob, type ServerContext, type UploadOptions, type VideoJob, type WarmupBackendResolver, type WarmupJob, createRenderJob, createServerContext, createWarmupJob, proxyBinaryStream, proxyRemoteSSE, purgeCache, sendError, streamRenderProgress, streamRenderVideo, streamWarmupProgress };
@@ -1,22 +1,26 @@
1
1
  import {
2
+ ErrorCode,
2
3
  createRenderJob,
3
4
  createServerContext,
4
5
  createWarmupJob,
5
6
  proxyBinaryStream,
6
7
  proxyRemoteSSE,
7
8
  purgeCache,
9
+ sendError,
8
10
  streamRenderProgress,
9
11
  streamRenderVideo,
10
12
  streamWarmupProgress
11
- } from "../chunk-B4M6KS75.js";
12
- import "../chunk-UN34ESVZ.js";
13
+ } from "../chunk-4WWHUVBG.js";
14
+ import "../chunk-WB5NFSQQ.js";
13
15
  export {
16
+ ErrorCode,
14
17
  createRenderJob,
15
18
  createServerContext,
16
19
  createWarmupJob,
17
20
  proxyBinaryStream,
18
21
  proxyRemoteSSE,
19
22
  purgeCache,
23
+ sendError,
20
24
  streamRenderProgress,
21
25
  streamRenderVideo,
22
26
  streamWarmupProgress
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  EffieRenderer,
3
3
  FFmpegCommand,
4
4
  FFmpegRunner
5
- } from "./chunk-2SB5D32U.js";
6
- import "./chunk-UN34ESVZ.js";
5
+ } from "./chunk-O3UH3WAZ.js";
6
+ import "./chunk-WB5NFSQQ.js";
7
7
  export {
8
8
  EffieRenderer,
9
9
  FFmpegCommand,
@@ -0,0 +1,8 @@
1
+ import {
2
+ EffieRenderer
3
+ } from "./chunk-O3UH3WAZ.js";
4
+ import "./chunk-WB5NFSQQ.js";
5
+ export {
6
+ EffieRenderer
7
+ };
8
+ //# sourceMappingURL=render-DMHG3YJU.js.map
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ffsFetch,
4
4
  storeKeys
5
- } from "./chunk-AUH73KG2.js";
5
+ } from "./chunk-UVBU2OFO.js";
6
6
 
7
7
  // src/render.ts
8
8
  import { Readable } from "stream";
@@ -291,6 +291,9 @@ var FFmpegRunner = class {
291
291
  transformedStream.pipe(writeStream);
292
292
  writeStream.on("finish", next);
293
293
  writeStream.on("error", reject);
294
+ } else {
295
+ stream2.resume();
296
+ next();
294
297
  }
295
298
  });
296
299
  extract.on("finish", resolve);