@effing/ffs 0.3.0 → 0.4.1
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.
- package/README.md +5 -1
- package/dist/{chunk-J64HSZNQ.js → chunk-3SM6XYCZ.js} +8 -3
- package/dist/chunk-3SM6XYCZ.js.map +1 -0
- package/dist/{chunk-XSCNUWZJ.js → chunk-JDRYI7SR.js} +18 -10
- package/dist/chunk-JDRYI7SR.js.map +1 -0
- package/dist/handlers/index.d.ts +3 -1
- package/dist/handlers/index.js +2 -2
- package/dist/index.js +1 -1
- package/dist/server.js +200 -187
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-J64HSZNQ.js.map +0 -1
- package/dist/chunk-XSCNUWZJ.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ffmpeg.ts","../src/render.ts","../src/motion.ts","../src/effect.ts","../src/transition.ts","../src/fetch.ts","../src/storage.ts"],"sourcesContent":["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\";\nimport pathToFFmpeg from \"ffmpeg-static\";\nimport tar from \"tar-stream\";\nimport { createWriteStream } from \"fs\";\nimport { promisify } from \"util\";\n\nconst pump = promisify(pipeline);\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(process.env.FFMPEG ?? pathToFFmpeg!, 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 { 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},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 { 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","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 for sources: 60 minutes */\nconst DEFAULT_SOURCE_TTL_MS = 60 * 60 * 1000;\n/** Default TTL for job metadata: 8 hours */\nconst DEFAULT_JOB_METADATA_TTL_MS = 8 * 60 * 60 * 1000;\n\n/**\n * Transient store interface for caching sources and storing ephemeral job metadata.\n */\nexport interface TransientStore {\n /** TTL for cached sources in milliseconds */\n readonly sourceTtlMs: number;\n /** TTL for job metadata in milliseconds */\n readonly jobMetadataTtlMs: 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 sourceTtlMs: number;\n public readonly jobMetadataTtlMs: number;\n\n constructor(options: {\n endpoint?: string;\n region?: string;\n bucket: string;\n prefix?: string;\n accessKeyId?: string;\n secretAccessKey?: string;\n sourceTtlMs?: number;\n jobMetadataTtlMs?: 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 ?? \"\";\n this.sourceTtlMs = options.sourceTtlMs ?? DEFAULT_SOURCE_TTL_MS;\n this.jobMetadataTtlMs =\n options.jobMetadataTtlMs ?? DEFAULT_JOB_METADATA_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}${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.sourceTtlMs),\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.jobMetadataTtlMs),\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 sourceTtlMs: number;\n public readonly jobMetadataTtlMs: number;\n /** For cleanup, use the longer of the two TTLs */\n private maxTtlMs: number;\n\n constructor(options?: {\n baseDir?: string;\n sourceTtlMs?: number;\n jobMetadataTtlMs?: number;\n }) {\n this.baseDir = options?.baseDir ?? path.join(os.tmpdir(), \"ffs-transient\");\n this.sourceTtlMs = options?.sourceTtlMs ?? DEFAULT_SOURCE_TTL_MS;\n this.jobMetadataTtlMs =\n options?.jobMetadataTtlMs ?? DEFAULT_JOB_METADATA_TTL_MS;\n this.maxTtlMs = Math.max(this.sourceTtlMs, this.jobMetadataTtlMs);\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.maxTtlMs) {\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 // Parse TTLs from env\n const sourceTtlMs = process.env.FFS_SOURCE_CACHE_TTL_MS\n ? parseInt(process.env.FFS_SOURCE_CACHE_TTL_MS, 10)\n : DEFAULT_SOURCE_TTL_MS;\n const jobMetadataTtlMs = process.env.FFS_JOB_METADATA_TTL_MS\n ? parseInt(process.env.FFS_JOB_METADATA_TTL_MS, 10)\n : DEFAULT_JOB_METADATA_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 sourceTtlMs,\n jobMetadataTtlMs,\n });\n }\n\n return new LocalTransientStore({\n baseDir: process.env.FFS_TRANSIENT_STORE_LOCAL_DIR,\n sourceTtlMs,\n jobMetadataTtlMs,\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 WarmupAndRenderJobStoreKey =\n `jobs/warmup-and-render/${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 warmupAndRenderJobStoreKey(\n jobId: string,\n): WarmupAndRenderJobStoreKey {\n return `jobs/warmup-and-render/${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 warmupAndRenderJob: warmupAndRenderJobStoreKey,\n} as const;\n"],"mappings":"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","names":["stream","createReadStream","fs","createWriteStream","pipeline","path","os","createReadStream"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/handlers/shared.ts","../src/proxy.ts","../src/handlers/caching.ts","../src/handlers/orchestrating.ts","../src/handlers/rendering.ts"],"sourcesContent":["import express from \"express\";\nimport type { TransientStore } from \"../storage\";\nimport { createTransientStore } from \"../storage\";\nimport { HttpProxy } from \"../proxy\";\nimport type {\n EffieData,\n EffieSources,\n EffieSourceWithType,\n} from \"@effing/effie\";\nimport { effieDataSchema } from \"@effing/effie\";\n\nexport type UploadOptions = {\n videoUrl: string;\n coverUrl?: string;\n};\n\nexport type WarmupJob = {\n sources: EffieSourceWithType[];\n};\n\nexport type RenderJob = {\n effie: EffieData<EffieSources>;\n scale: number;\n upload?: UploadOptions;\n createdAt: number;\n};\n\nexport type WarmupAndRenderJob = {\n effie: EffieData<EffieSources>;\n sources: EffieSourceWithType[];\n scale: number;\n upload?: UploadOptions;\n warmupJobId: string;\n renderJobId: string;\n createdAt: number;\n};\n\nexport type ServerContext = {\n transientStore: TransientStore;\n httpProxy: HttpProxy;\n baseUrl: string;\n skipValidation: boolean;\n warmupConcurrency: number;\n warmupBackendBaseUrl?: string;\n renderBackendBaseUrl?: string;\n};\n\nexport type SSEEventSender = (event: string, data: object) => void;\n\nexport type ParseEffieResult =\n | { effie: EffieData<EffieSources> }\n | { error: string; issues?: object[] };\n\n/**\n * Create the server context with configuration from environment variables\n */\nexport async function createServerContext(): Promise<ServerContext> {\n const port = process.env.FFS_PORT || 2000;\n const httpProxy = new HttpProxy();\n await httpProxy.start();\n return {\n transientStore: createTransientStore(),\n httpProxy,\n baseUrl: process.env.FFS_BASE_URL || `http://localhost:${port}`,\n skipValidation:\n !!process.env.FFS_SKIP_VALIDATION &&\n process.env.FFS_SKIP_VALIDATION !== \"false\",\n warmupConcurrency: parseInt(process.env.FFS_WARMUP_CONCURRENCY || \"4\", 10),\n warmupBackendBaseUrl: process.env.FFS_WARMUP_BACKEND_BASE_URL,\n renderBackendBaseUrl: process.env.FFS_RENDER_BACKEND_BASE_URL,\n };\n}\n\n/**\n * Parse and validate Effie data from request body\n */\nexport function parseEffieData(\n body: unknown,\n skipValidation: boolean,\n): ParseEffieResult {\n // Wrapped format has `effie` property\n const isWrapped =\n typeof body === \"object\" && body !== null && \"effie\" in body;\n const rawEffieData = isWrapped ? (body as { effie: unknown }).effie : body;\n\n if (!skipValidation) {\n const result = effieDataSchema.safeParse(rawEffieData);\n if (!result.success) {\n return {\n error: \"Invalid effie data\",\n issues: result.error.issues.map((issue) => ({\n path: issue.path.join(\".\"),\n message: issue.message,\n })),\n };\n }\n return { effie: result.data };\n } else {\n const effie = rawEffieData as EffieData<EffieSources>;\n if (!effie?.segments) {\n return { error: \"Invalid effie data: missing segments\" };\n }\n return { effie };\n }\n}\n\n/**\n * Set up CORS headers for public endpoints\n */\nexport function setupCORSHeaders(res: express.Response): void {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET\");\n}\n\n/**\n * Set up SSE response headers\n */\nexport function setupSSEResponse(res: express.Response): void {\n res.setHeader(\"Content-Type\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n res.flushHeaders();\n}\n\n/**\n * Create an SSE event sender function for a response\n */\nexport function createSSEEventSender(res: express.Response): SSEEventSender {\n return (event: string, data: object) => {\n res.write(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n };\n}\n","import http from \"http\";\nimport type { AddressInfo, Server } from \"net\";\nimport { Readable } from \"stream\";\nimport { ffsFetch } from \"./fetch\";\n\n/**\n * HTTP proxy for FFmpeg URL handling.\n *\n * Static FFmpeg binaries can have DNS resolution issues on Alpine Linux (musl libc).\n * This proxy lets Node.js handle DNS lookups instead of FFmpeg by proxying HTTP\n * requests through localhost.\n *\n * URL scheme (M3U8-compatible):\n * - Original: https://cdn.example.com/path/to/stream.m3u8\n * - Proxy: http://127.0.0.1:{port}/https://cdn.example.com/path/to/stream.m3u8\n * - Relative: segment-0.ts → http://127.0.0.1:{port}/https://cdn.example.com/path/to/segment-0.ts\n */\nexport class HttpProxy {\n private server: Server | null = null;\n private _port: number | null = null;\n private startPromise: Promise<void> | null = null;\n\n get port(): number | null {\n return this._port;\n }\n\n /**\n * Transform a URL to go through the proxy.\n * @throws Error if proxy not started\n */\n transformUrl(url: string): string {\n if (this._port === null) throw new Error(\"Proxy not started\");\n return `http://127.0.0.1:${this._port}/${url}`;\n }\n\n /**\n * Start the proxy server. Safe to call multiple times.\n */\n async start(): Promise<void> {\n if (this._port !== null) return;\n if (this.startPromise) {\n await this.startPromise;\n return;\n }\n this.startPromise = this.doStart();\n await this.startPromise;\n }\n\n private async doStart(): Promise<void> {\n this.server = http.createServer(async (req, res) => {\n try {\n const originalUrl = this.parseProxyPath(req.url || \"\");\n if (!originalUrl) {\n res.writeHead(400, { \"Content-Type\": \"text/plain\" });\n res.end(\"Bad Request: invalid proxy path\");\n return;\n }\n\n const response = await ffsFetch(originalUrl, {\n method: req.method as \"GET\" | \"HEAD\" | undefined,\n headers: this.filterHeaders(req.headers),\n bodyTimeout: 0, // No timeout for streaming\n });\n\n // Convert response headers to plain object\n const headers: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n res.writeHead(response.status, headers);\n\n if (response.body) {\n const nodeStream = Readable.fromWeb(response.body);\n nodeStream.pipe(res);\n nodeStream.on(\"error\", (err) => {\n console.error(\"Proxy stream error:\", err);\n res.destroy();\n });\n } else {\n res.end();\n }\n } catch (err) {\n console.error(\"Proxy request error:\", err);\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"text/plain\" });\n res.end(\"Bad Gateway\");\n } else {\n res.destroy();\n }\n }\n });\n\n await new Promise<void>((resolve) => {\n this.server!.listen(0, \"127.0.0.1\", () => {\n this._port = (this.server!.address() as AddressInfo).port;\n resolve();\n });\n });\n }\n\n /**\n * Parse the proxy path to extract the original URL.\n * Path format: /{originalUrl}\n */\n private parseProxyPath(path: string): string | null {\n if (!path.startsWith(\"/http://\") && !path.startsWith(\"/https://\")) {\n return null;\n }\n return path.slice(1); // Remove leading /\n }\n\n /**\n * Filter headers to forward to the upstream server.\n * Removes hop-by-hop headers that shouldn't be forwarded.\n */\n private filterHeaders(\n headers: http.IncomingHttpHeaders,\n ): Record<string, string> {\n const skip = new Set([\n \"host\",\n \"connection\",\n \"keep-alive\",\n \"transfer-encoding\",\n \"te\",\n \"trailer\",\n \"upgrade\",\n \"proxy-authorization\",\n \"proxy-authenticate\",\n ]);\n\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (!skip.has(key.toLowerCase()) && typeof value === \"string\") {\n result[key] = value;\n }\n }\n return result;\n }\n\n /**\n * Close the proxy server and reset state.\n */\n close(): void {\n this.server?.close();\n this.server = null;\n this._port = null;\n this.startPromise = null;\n }\n}\n","import express from \"express\";\nimport { Readable, Transform } from \"stream\";\nimport { randomUUID } from \"crypto\";\nimport { storeKeys } from \"../storage\";\nimport { ffsFetch } from \"../fetch\";\nimport {\n extractEffieSources,\n extractEffieSourcesWithTypes,\n} from \"@effing/effie\";\nimport type { EffieSourceWithType } from \"@effing/effie\";\nimport type { ServerContext, SSEEventSender, WarmupJob } from \"./shared\";\nimport {\n parseEffieData,\n setupCORSHeaders,\n setupSSEResponse,\n createSSEEventSender,\n} from \"./shared\";\nimport { proxyRemoteSSE } from \"./orchestrating\";\n\n/**\n * Check if a source should be skipped during warmup.\n * Video/audio sources are passed directly to FFmpeg and don't need caching.\n */\nfunction shouldSkipWarmup(source: EffieSourceWithType): boolean {\n return source.type === \"video\" || source.type === \"audio\";\n}\n\n// Track in-flight fetches to avoid duplicate fetches within the same instance\nconst inFlightFetches = new Map<string, Promise<void>>();\n\n/**\n * POST /warmup - Create a warmup job\n * Stores the source list in cache and returns a job ID for SSE streaming\n */\nexport async function createWarmupJob(\n req: express.Request,\n res: express.Response,\n ctx: ServerContext,\n): Promise<void> {\n try {\n const parseResult = parseEffieData(req.body, ctx.skipValidation);\n if (\"error\" in parseResult) {\n res.status(400).json(parseResult);\n return;\n }\n\n const sources = extractEffieSourcesWithTypes(parseResult.effie);\n const jobId = randomUUID();\n\n // Store job in cache with job metadata TTL\n await ctx.transientStore.putJson(\n storeKeys.warmupJob(jobId),\n { sources },\n ctx.transientStore.jobMetadataTtlMs,\n );\n\n res.json({\n id: jobId,\n url: `${ctx.baseUrl}/warmup/${jobId}`,\n });\n } catch (error) {\n console.error(\"Error creating warmup job:\", error);\n res.status(500).json({ error: \"Failed to create warmup job\" });\n }\n}\n\n/**\n * GET /warmup/:id - Stream warmup progress via SSE\n * Fetches and caches sources, emitting progress events\n */\nexport async function streamWarmupJob(\n req: express.Request,\n res: express.Response,\n ctx: ServerContext,\n): Promise<void> {\n try {\n setupCORSHeaders(res);\n\n const jobId = req.params.id;\n\n // Proxy to warmup backend if configured\n if (ctx.warmupBackendBaseUrl) {\n setupSSEResponse(res);\n const sendEvent = createSSEEventSender(res);\n try {\n await proxyRemoteSSE(\n `${ctx.warmupBackendBaseUrl}/warmup/${jobId}`,\n sendEvent,\n \"\",\n res,\n );\n } finally {\n res.end();\n }\n return;\n }\n\n const jobCacheKey = storeKeys.warmupJob(jobId);\n const job = await ctx.transientStore.getJson<WarmupJob>(jobCacheKey);\n // only allow the warmup job to run once\n ctx.transientStore.delete(jobCacheKey);\n\n if (!job) {\n res.status(404).json({ error: \"Job not found\" });\n return;\n }\n\n setupSSEResponse(res);\n const sendEvent = createSSEEventSender(res);\n\n try {\n await warmupSources(job.sources, sendEvent, ctx);\n sendEvent(\"complete\", { status: \"ready\" });\n } catch (error) {\n sendEvent(\"error\", { message: String(error) });\n } finally {\n res.end();\n }\n } catch (error) {\n console.error(\"Error in warmup streaming:\", error);\n if (!res.headersSent) {\n res.status(500).json({ error: \"Warmup streaming failed\" });\n } else {\n res.end();\n }\n }\n}\n\n/**\n * POST /purge - Purge cached sources for an Effie composition\n */\nexport async function purgeCache(\n req: express.Request,\n res: express.Response,\n ctx: ServerContext,\n): Promise<void> {\n try {\n const parseResult = parseEffieData(req.body, ctx.skipValidation);\n if (\"error\" in parseResult) {\n res.status(400).json(parseResult);\n return;\n }\n\n const sources = extractEffieSources(parseResult.effie);\n\n let purged = 0;\n for (const url of sources) {\n const ck = storeKeys.source(url);\n if (await ctx.transientStore.exists(ck)) {\n await ctx.transientStore.delete(ck);\n purged++;\n }\n }\n\n res.json({ purged, total: sources.length });\n } catch (error) {\n console.error(\"Error purging cache:\", error);\n res.status(500).json({ error: \"Failed to purge cache\" });\n }\n}\n\n/**\n * Warm up sources by fetching and caching them.\n * HTTP(S) video/audio sources are skipped as they are passed directly to FFmpeg.\n */\nexport async function warmupSources(\n sources: EffieSourceWithType[],\n sendEvent: SSEEventSender,\n ctx: ServerContext,\n): Promise<void> {\n const total = sources.length;\n\n sendEvent(\"start\", { total });\n\n let cached = 0;\n let failed = 0;\n let skipped = 0;\n\n // Separate sources that need caching from those that should be skipped\n const sourcesToCache: EffieSourceWithType[] = [];\n for (const source of sources) {\n if (shouldSkipWarmup(source)) {\n skipped++;\n sendEvent(\"progress\", {\n url: source.url,\n status: \"skipped\",\n reason: \"http-video-audio-passthrough\",\n cached,\n failed,\n skipped,\n total,\n });\n } else {\n sourcesToCache.push(source);\n }\n }\n\n // Check what's already cached\n const sourceCacheKeys = sourcesToCache.map((s) => storeKeys.source(s.url));\n const existsMap = await ctx.transientStore.existsMany(sourceCacheKeys);\n\n // Report hits immediately\n for (let i = 0; i < sourcesToCache.length; i++) {\n if (existsMap.get(sourceCacheKeys[i])) {\n cached++;\n sendEvent(\"progress\", {\n url: sourcesToCache[i].url,\n status: \"hit\",\n cached,\n failed,\n skipped,\n total,\n });\n }\n }\n\n // Filter to uncached sources\n const uncached = sourcesToCache.filter(\n (_, i) => !existsMap.get(sourceCacheKeys[i]),\n );\n\n if (uncached.length === 0) {\n sendEvent(\"summary\", { cached, failed, skipped, total });\n return;\n }\n\n // Keepalive interval for long-running fetches\n const keepalive = setInterval(() => {\n sendEvent(\"keepalive\", { cached, failed, skipped, total });\n }, 25_000);\n\n // Fetch uncached sources with concurrency limit\n const queue = [...uncached];\n const workers = Array.from(\n { length: Math.min(ctx.warmupConcurrency, queue.length) },\n async () => {\n while (queue.length > 0) {\n const source = queue.shift()!;\n const cacheKey = storeKeys.source(source.url);\n const startTime = Date.now();\n\n try {\n // Check if another worker is already fetching this\n let fetchPromise = inFlightFetches.get(cacheKey);\n if (!fetchPromise) {\n fetchPromise = fetchAndCache(source.url, cacheKey, sendEvent, ctx);\n inFlightFetches.set(cacheKey, fetchPromise);\n }\n\n await fetchPromise;\n inFlightFetches.delete(cacheKey);\n\n cached++;\n sendEvent(\"progress\", {\n url: source.url,\n status: \"cached\",\n cached,\n failed,\n skipped,\n total,\n ms: Date.now() - startTime,\n });\n } catch (error) {\n inFlightFetches.delete(cacheKey);\n failed++;\n sendEvent(\"progress\", {\n url: source.url,\n status: \"error\",\n error: String(error),\n cached,\n failed,\n skipped,\n total,\n ms: Date.now() - startTime,\n });\n }\n }\n },\n );\n\n await Promise.all(workers);\n clearInterval(keepalive);\n\n sendEvent(\"summary\", { cached, failed, skipped, total });\n}\n\n/**\n * Fetch a source and cache it, with streaming progress events\n */\nexport async function fetchAndCache(\n url: string,\n cacheKey: string,\n sendEvent: SSEEventSender,\n ctx: ServerContext,\n): Promise<void> {\n const response = await ffsFetch(url, {\n headersTimeout: 10 * 60 * 1000, // 10 minutes\n bodyTimeout: 20 * 60 * 1000, // 20 minutes\n });\n\n if (!response.ok) {\n throw new Error(`${response.status} ${response.statusText}`);\n }\n\n sendEvent(\"downloading\", { url, status: \"started\", bytesReceived: 0 });\n\n // Stream through a progress tracker\n const sourceStream = Readable.fromWeb(\n response.body as import(\"stream/web\").ReadableStream,\n );\n\n let totalBytes = 0;\n let lastEventTime = Date.now();\n const PROGRESS_INTERVAL = 10_000; // 10 seconds\n\n const progressStream = new Transform({\n transform(chunk, _encoding, callback) {\n totalBytes += chunk.length;\n const now = Date.now();\n if (now - lastEventTime >= PROGRESS_INTERVAL) {\n sendEvent(\"downloading\", {\n url,\n status: \"downloading\",\n bytesReceived: totalBytes,\n });\n lastEventTime = now;\n }\n callback(null, chunk);\n },\n });\n\n // Pipe through progress tracker to cache storage with source TTL\n const trackedStream = sourceStream.pipe(progressStream);\n await ctx.transientStore.put(\n cacheKey,\n trackedStream,\n ctx.transientStore.sourceTtlMs,\n );\n}\n","import express from \"express\";\nimport { randomUUID } from \"crypto\";\nimport type { Response as UndiciResponse } from \"undici\";\nimport { storeKeys } from \"../storage\";\nimport { ffsFetch } from \"../fetch\";\nimport { extractEffieSourcesWithTypes, effieDataSchema } from \"@effing/effie\";\nimport type { EffieData, EffieSources } from \"@effing/effie\";\nimport type {\n ServerContext,\n SSEEventSender,\n WarmupAndRenderJob,\n RenderJob,\n UploadOptions,\n} from \"./shared\";\nimport {\n setupCORSHeaders,\n setupSSEResponse,\n createSSEEventSender,\n} from \"./shared\";\nimport { warmupSources } from \"./caching\";\nimport { renderAndUploadInternal } from \"./rendering\";\n\n/**\n * POST /warmup-and-render - Create a combined warmup and render job\n * Returns a job ID and URL for SSE streaming\n */\nexport async function createWarmupAndRenderJob(\n req: express.Request,\n res: express.Response,\n ctx: ServerContext,\n): Promise<void> {\n try {\n // Parse request body\n const options = req.body as {\n effie: unknown;\n scale?: number;\n upload?: UploadOptions;\n };\n\n let rawEffieData: unknown;\n if (typeof options.effie === \"string\") {\n // Effie is a URL to fetch the EffieData from\n const response = await ffsFetch(options.effie);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch Effie data: ${response.status} ${response.statusText}`,\n );\n }\n rawEffieData = await response.json();\n } else {\n rawEffieData = options.effie;\n }\n\n // Validate/parse effie data\n let effie: EffieData<EffieSources>;\n if (!ctx.skipValidation) {\n const result = effieDataSchema.safeParse(rawEffieData);\n if (!result.success) {\n res.status(400).json({\n error: \"Invalid effie data\",\n issues: result.error.issues.map((issue) => ({\n path: issue.path.join(\".\"),\n message: issue.message,\n })),\n });\n return;\n }\n effie = result.data;\n } else {\n const data = rawEffieData as EffieData<EffieSources>;\n if (!data?.segments) {\n res.status(400).json({ error: \"Invalid effie data: missing segments\" });\n return;\n }\n effie = data;\n }\n\n const sources = extractEffieSourcesWithTypes(effie);\n const scale = options.scale ?? 1;\n const upload = options.upload;\n\n // Create IDs for warmup and render sub-jobs\n const jobId = randomUUID();\n const warmupJobId = randomUUID();\n const renderJobId = randomUUID();\n\n // Store the combined job\n const job: WarmupAndRenderJob = {\n effie,\n sources,\n scale,\n upload,\n warmupJobId,\n renderJobId,\n createdAt: Date.now(),\n };\n\n await ctx.transientStore.putJson(\n storeKeys.warmupAndRenderJob(jobId),\n job,\n ctx.transientStore.jobMetadataTtlMs,\n );\n\n // Also store sub-jobs for backend execution\n await ctx.transientStore.putJson(\n storeKeys.warmupJob(warmupJobId),\n { sources },\n ctx.transientStore.jobMetadataTtlMs,\n );\n await ctx.transientStore.putJson(\n storeKeys.renderJob(renderJobId),\n {\n effie,\n scale,\n upload,\n createdAt: Date.now(),\n } satisfies RenderJob,\n ctx.transientStore.jobMetadataTtlMs,\n );\n\n res.json({\n id: jobId,\n url: `${ctx.baseUrl}/warmup-and-render/${jobId}`,\n });\n } catch (error) {\n console.error(\"Error creating warmup-and-render job:\", error);\n res.status(500).json({ error: \"Failed to create warmup-and-render job\" });\n }\n}\n\n/**\n * GET /warmup-and-render/:id - Stream warmup and render progress via SSE\n * Orchestrates warmup (local or remote) followed by render (local or remote)\n */\nexport async function streamWarmupAndRenderJob(\n req: express.Request,\n res: express.Response,\n ctx: ServerContext,\n): Promise<void> {\n try {\n setupCORSHeaders(res);\n\n const jobId = req.params.id;\n const jobCacheKey = storeKeys.warmupAndRenderJob(jobId);\n const job =\n await ctx.transientStore.getJson<WarmupAndRenderJob>(jobCacheKey);\n // Only allow the job to run once\n ctx.transientStore.delete(jobCacheKey);\n\n if (!job) {\n res.status(404).json({ error: \"Job not found\" });\n return;\n }\n\n setupSSEResponse(res);\n const sendEvent = createSSEEventSender(res);\n\n // Keepalive interval for long-running operations\n let keepalivePhase: \"warmup\" | \"render\" = \"warmup\";\n const keepalive = setInterval(() => {\n sendEvent(\"keepalive\", { phase: keepalivePhase });\n }, 25_000);\n\n try {\n // Phase 1: Warmup\n if (ctx.warmupBackendBaseUrl) {\n // Proxy warmup from remote backend\n await proxyRemoteSSE(\n `${ctx.warmupBackendBaseUrl}/warmup/${job.warmupJobId}`,\n sendEvent,\n \"warmup:\",\n res,\n );\n } else {\n // Local warmup execution\n const warmupSender = prefixEventSender(sendEvent, \"warmup:\");\n await warmupSources(job.sources, warmupSender, ctx);\n warmupSender(\"complete\", { status: \"ready\" });\n }\n\n // Phase 2: Render\n keepalivePhase = \"render\";\n\n if (ctx.renderBackendBaseUrl) {\n // Proxy render from remote backend\n await proxyRemoteSSE(\n `${ctx.renderBackendBaseUrl}/render/${job.renderJobId}`,\n sendEvent,\n \"render:\",\n res,\n );\n } else {\n // Local render execution\n const renderSender = prefixEventSender(sendEvent, \"render:\");\n\n if (job.upload) {\n // Upload mode: render and upload, emit SSE events\n renderSender(\"started\", { status: \"rendering\" });\n const timings = await renderAndUploadInternal(\n job.effie,\n job.scale,\n job.upload,\n renderSender,\n ctx,\n );\n renderSender(\"complete\", { status: \"uploaded\", timings });\n } else {\n // Non-upload mode: return URL to existing render job\n const videoUrl = `${ctx.baseUrl}/render/${job.renderJobId}`;\n sendEvent(\"complete\", { status: \"ready\", videoUrl });\n }\n }\n\n // Final complete event (only for upload mode, non-upload already sent complete)\n if (job.upload && !ctx.renderBackendBaseUrl) {\n sendEvent(\"complete\", { status: \"done\" });\n }\n } catch (error) {\n sendEvent(\"error\", {\n phase: keepalivePhase,\n message: String(error),\n });\n } finally {\n clearInterval(keepalive);\n res.end();\n }\n } catch (error) {\n console.error(\"Error in warmup-and-render streaming:\", error);\n if (!res.headersSent) {\n res.status(500).json({ error: \"Warmup-and-render streaming failed\" });\n } else {\n res.end();\n }\n }\n}\n\n/**\n * Create a prefixed event sender that adds a prefix to event names\n */\nexport function prefixEventSender(\n sendEvent: SSEEventSender,\n prefix: string,\n): SSEEventSender {\n return (event: string, data: object) => {\n sendEvent(`${prefix}${event}`, data);\n };\n}\n\n/**\n * Proxy SSE events from a remote backend, prefixing event names\n */\nexport async function proxyRemoteSSE(\n url: string,\n sendEvent: SSEEventSender,\n prefix: string,\n res: express.Response,\n): Promise<void> {\n const response = await ffsFetch(url, {\n headers: {\n Accept: \"text/event-stream\",\n },\n });\n\n if (!response.ok) {\n throw new Error(`Remote backend error: ${response.status}`);\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error(\"No response body from remote backend\");\n }\n\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n // Check if client disconnected\n if (res.destroyed) {\n reader.cancel();\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n\n // Parse SSE events from buffer\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\"; // Keep incomplete line in buffer\n\n let currentEvent = \"\";\n let currentData = \"\";\n\n for (const line of lines) {\n if (line.startsWith(\"event: \")) {\n currentEvent = line.slice(7);\n } else if (line.startsWith(\"data: \")) {\n currentData = line.slice(6);\n } else if (line === \"\" && currentEvent && currentData) {\n // End of event, forward it with prefix\n try {\n const data = JSON.parse(currentData);\n sendEvent(`${prefix}${currentEvent}`, data);\n } catch {\n // Skip malformed JSON\n }\n currentEvent = \"\";\n currentData = \"\";\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * Proxy a binary stream (e.g., video) from a fetch Response to an Express response.\n * Forwards Content-Type and Content-Length headers.\n */\nexport async function proxyBinaryStream(\n response: UndiciResponse,\n res: express.Response,\n): Promise<void> {\n const contentType = response.headers.get(\"content-type\");\n if (contentType) res.set(\"Content-Type\", contentType);\n\n const contentLength = response.headers.get(\"content-length\");\n if (contentLength) res.set(\"Content-Length\", contentLength);\n\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error(\"No response body\");\n }\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n if (res.destroyed) {\n reader.cancel();\n break;\n }\n\n res.write(value);\n }\n } finally {\n reader.releaseLock();\n res.end();\n }\n}\n","import express from \"express\";\nimport { randomUUID } from \"crypto\";\nimport { storeKeys } from \"../storage\";\nimport { ffsFetch } from \"../fetch\";\nimport { EffieRenderer } from \"../render\";\nimport { effieDataSchema } from \"@effing/effie\";\nimport type { EffieData, EffieSources } from \"@effing/effie\";\nimport type {\n ServerContext,\n SSEEventSender,\n RenderJob,\n UploadOptions,\n} from \"./shared\";\nimport {\n setupCORSHeaders,\n setupSSEResponse,\n createSSEEventSender,\n} from \"./shared\";\nimport { proxyBinaryStream } from \"./orchestrating\";\n\n/**\n * POST /render - Create a render job\n * Returns a job ID and URL for streaming the rendered video\n */\nexport async function createRenderJob(\n req: express.Request,\n res: express.Response,\n ctx: ServerContext,\n): Promise<void> {\n try {\n // Wrapped format has `effie` property,\n // otherwise it's just raw EffieData (which doesn't have an `effie` property)\n const isWrapped = \"effie\" in req.body;\n\n let rawEffieData: unknown;\n let scale: number;\n let upload: UploadOptions | undefined;\n\n if (isWrapped) {\n // Wrapped format: { effie: EffieData | string, scale?, upload? }\n const options = req.body as {\n effie: unknown;\n scale?: number;\n upload?: UploadOptions;\n };\n\n if (typeof options.effie === \"string\") {\n // Effie is a string, so it's a URL to fetch the EffieData from\n const response = await ffsFetch(options.effie);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch Effie data: ${response.status} ${response.statusText}`,\n );\n }\n rawEffieData = await response.json();\n } else {\n // Effie is an EffieData object\n rawEffieData = options.effie;\n }\n\n scale = options.scale ?? 1;\n upload = options.upload;\n } else {\n // Body is the EffieData, options in query params\n rawEffieData = req.body;\n scale = parseFloat(req.query.scale?.toString() || \"1\");\n }\n\n // Validate/parse effie data (validation can be disabled by setting FFS_SKIP_VALIDATION)\n let effie: EffieData<EffieSources>;\n if (!ctx.skipValidation) {\n const result = effieDataSchema.safeParse(rawEffieData);\n if (!result.success) {\n res.status(400).json({\n error: \"Invalid effie data\",\n issues: result.error.issues.map((issue) => ({\n path: issue.path.join(\".\"),\n message: issue.message,\n })),\n });\n return;\n }\n effie = result.data;\n } else {\n // Minimal validation when schema validation is disabled\n const data = rawEffieData as EffieData<EffieSources>;\n if (!data?.segments) {\n res.status(400).json({ error: \"Invalid effie data: missing segments\" });\n return;\n }\n effie = data;\n }\n\n // Create render job\n const jobId = randomUUID();\n const job: RenderJob = {\n effie,\n scale,\n upload,\n createdAt: Date.now(),\n };\n\n await ctx.transientStore.putJson(\n storeKeys.renderJob(jobId),\n job,\n ctx.transientStore.jobMetadataTtlMs,\n );\n\n res.json({\n id: jobId,\n url: `${ctx.baseUrl}/render/${jobId}`,\n });\n } catch (error) {\n console.error(\"Error creating render job:\", error);\n res.status(500).json({ error: \"Failed to create render job\" });\n }\n}\n\n/**\n * GET /render/:id - Execute render job\n * Streams video directly (no upload) or SSE progress events (with upload)\n */\nexport async function streamRenderJob(\n req: express.Request,\n res: express.Response,\n ctx: ServerContext,\n): Promise<void> {\n try {\n setupCORSHeaders(res);\n\n const jobId = req.params.id;\n\n // Proxy to render backend if configured\n if (ctx.renderBackendBaseUrl) {\n await proxyRenderFromBackend(res, jobId, ctx);\n return;\n }\n\n const jobCacheKey = storeKeys.renderJob(jobId);\n const job = await ctx.transientStore.getJson<RenderJob>(jobCacheKey);\n // only allow the render job to run once\n ctx.transientStore.delete(jobCacheKey);\n\n if (!job) {\n res.status(404).json({ error: \"Job not found or expired\" });\n return;\n }\n\n // Dispatch based on upload mode\n if (job.upload) {\n await streamRenderWithUpload(res, job, ctx);\n } else {\n await streamRenderDirect(res, job, ctx);\n }\n } catch (error) {\n console.error(\"Error in render:\", error);\n if (!res.headersSent) {\n res.status(500).json({ error: \"Rendering failed\" });\n } else {\n res.end();\n }\n }\n}\n\n/**\n * Stream video directly to the response (no upload)\n */\nexport async function streamRenderDirect(\n res: express.Response,\n job: RenderJob,\n ctx: ServerContext,\n): Promise<void> {\n const renderer = new EffieRenderer(job.effie, {\n transientStore: ctx.transientStore,\n httpProxy: ctx.httpProxy,\n });\n const videoStream = await renderer.render(job.scale);\n\n res.on(\"close\", () => {\n videoStream.destroy();\n renderer.close();\n });\n\n res.set(\"Content-Type\", \"video/mp4\");\n videoStream.pipe(res);\n}\n\n/**\n * Render and upload, streaming SSE progress events\n */\nexport async function streamRenderWithUpload(\n res: express.Response,\n job: RenderJob,\n ctx: ServerContext,\n): Promise<void> {\n setupSSEResponse(res);\n const sendEvent = createSSEEventSender(res);\n\n // Keepalive interval for long-running renders\n const keepalive = setInterval(() => {\n sendEvent(\"keepalive\", { status: \"rendering\" });\n }, 25_000);\n\n try {\n sendEvent(\"started\", { status: \"rendering\" });\n\n const timings = await renderAndUploadInternal(\n job.effie,\n job.scale,\n job.upload!,\n sendEvent,\n ctx,\n );\n\n sendEvent(\"complete\", { status: \"uploaded\", timings });\n } catch (error) {\n sendEvent(\"error\", { message: String(error) });\n } finally {\n clearInterval(keepalive);\n res.end();\n }\n}\n\n/**\n * Internal render and upload logic\n * Returns timings for the SSE complete event\n */\nexport async function renderAndUploadInternal(\n effie: EffieData<EffieSources>,\n scale: number,\n upload: UploadOptions,\n sendEvent: SSEEventSender,\n ctx: ServerContext,\n): Promise<Record<string, number>> {\n const timings: Record<string, number> = {};\n\n // Fetch and upload cover if coverUrl provided\n if (upload.coverUrl) {\n const fetchCoverStartTime = Date.now();\n const coverFetchResponse = await ffsFetch(effie.cover);\n if (!coverFetchResponse.ok) {\n throw new Error(\n `Failed to fetch cover image: ${coverFetchResponse.status} ${coverFetchResponse.statusText}`,\n );\n }\n const coverBuffer = Buffer.from(await coverFetchResponse.arrayBuffer());\n timings.fetchCoverTime = Date.now() - fetchCoverStartTime;\n\n const uploadCoverStartTime = Date.now();\n const uploadCoverResponse = await ffsFetch(upload.coverUrl, {\n method: \"PUT\",\n body: coverBuffer,\n headers: {\n \"Content-Type\": \"image/png\",\n \"Content-Length\": coverBuffer.length.toString(),\n },\n });\n if (!uploadCoverResponse.ok) {\n throw new Error(\n `Failed to upload cover: ${uploadCoverResponse.status} ${uploadCoverResponse.statusText}`,\n );\n }\n timings.uploadCoverTime = Date.now() - uploadCoverStartTime;\n }\n\n // Render effie data to video\n const renderStartTime = Date.now();\n const renderer = new EffieRenderer(effie, {\n transientStore: ctx.transientStore,\n httpProxy: ctx.httpProxy,\n });\n const videoStream = await renderer.render(scale);\n const chunks: Buffer[] = [];\n for await (const chunk of videoStream) {\n chunks.push(Buffer.from(chunk));\n }\n const videoBuffer = Buffer.concat(chunks);\n timings.renderTime = Date.now() - renderStartTime;\n\n // Update keepalive status for upload phase\n sendEvent(\"keepalive\", { status: \"uploading\" });\n\n // Upload rendered video\n const uploadStartTime = Date.now();\n const uploadResponse = await ffsFetch(upload.videoUrl, {\n method: \"PUT\",\n body: videoBuffer,\n headers: {\n \"Content-Type\": \"video/mp4\",\n \"Content-Length\": videoBuffer.length.toString(),\n },\n });\n if (!uploadResponse.ok) {\n throw new Error(\n `Failed to upload rendered video: ${uploadResponse.status} ${uploadResponse.statusText}`,\n );\n }\n timings.uploadTime = Date.now() - uploadStartTime;\n\n return timings;\n}\n\n/**\n * Proxy render from backend based on Content-Type.\n * SSE (upload mode) uses proxyRemoteSSE, video stream uses proxyBinaryStream.\n */\nasync function proxyRenderFromBackend(\n res: express.Response,\n jobId: string,\n ctx: ServerContext,\n): Promise<void> {\n const backendUrl = `${ctx.renderBackendBaseUrl}/render/${jobId}`;\n const response = await ffsFetch(backendUrl);\n\n if (!response.ok) {\n res.status(response.status).json({ error: \"Backend render failed\" });\n return;\n }\n\n const contentType = response.headers.get(\"content-type\") || \"\";\n\n if (contentType.includes(\"text/event-stream\")) {\n // Upload mode: proxy SSE events\n setupSSEResponse(res);\n const sendEvent = createSSEEventSender(res);\n\n const reader = response.body?.getReader();\n if (!reader) {\n sendEvent(\"error\", { message: \"No response body from backend\" });\n res.end();\n return;\n }\n\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n if (res.destroyed) {\n reader.cancel();\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n\n let currentEvent = \"\";\n let currentData = \"\";\n\n for (const line of lines) {\n if (line.startsWith(\"event: \")) {\n currentEvent = line.slice(7);\n } else if (line.startsWith(\"data: \")) {\n currentData = line.slice(6);\n } else if (line === \"\" && currentEvent && currentData) {\n try {\n const data = JSON.parse(currentData);\n sendEvent(currentEvent, data);\n } catch {\n // Skip malformed JSON\n }\n currentEvent = \"\";\n currentData = \"\";\n }\n }\n }\n } finally {\n reader.releaseLock();\n res.end();\n }\n } else {\n // Non-upload mode: proxy binary video stream\n await proxyBinaryStream(response, res);\n }\n}\n"],"mappings":"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","names":["Readable","randomUUID","extractEffieSourcesWithTypes","randomUUID","effieDataSchema","effieDataSchema","effieDataSchema","effieDataSchema","randomUUID","extractEffieSourcesWithTypes","randomUUID","sendEvent","Readable"]}
|