@effing/ffs 0.2.0 → 0.4.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 +1 @@
1
- {"version":3,"sources":["../src/server.ts"],"sourcesContent":["import express from \"express\";\nimport bodyParser from \"body-parser\";\nimport {\n createServerContext,\n createWarmupJob,\n streamWarmupJob,\n purgeCache,\n createRenderJob,\n streamRenderJob,\n} from \"./handlers\";\n\nconst app: express.Express = express();\napp.use(bodyParser.json({ limit: \"50mb\" })); // Support large JSON requests\n\nconst ctx = await createServerContext();\nconsole.log(`FFS HTTP proxy listening on port ${ctx.httpProxy.port}`);\n\nfunction validateAuth(req: express.Request, res: express.Response): boolean {\n const apiKey = process.env.FFS_API_KEY;\n if (!apiKey) return true; // No auth required if api key not set\n\n const authHeader = req.headers.authorization;\n if (!authHeader || authHeader !== `Bearer ${apiKey}`) {\n res.status(401).json({ error: \"Unauthorized\" });\n return false;\n }\n return true;\n}\n\n// Routes with auth (POST endpoints)\napp.post(\"/warmup\", (req, res) => {\n if (!validateAuth(req, res)) return;\n createWarmupJob(req, res, ctx);\n});\napp.post(\"/purge\", (req, res) => {\n if (!validateAuth(req, res)) return;\n purgeCache(req, res, ctx);\n});\napp.post(\"/render\", (req, res) => {\n if (!validateAuth(req, res)) return;\n createRenderJob(req, res, ctx);\n});\n\n// Routes without auth (GET endpoints use job ID as capability token)\napp.get(\"/warmup/:id\", (req, res) => streamWarmupJob(req, res, ctx));\napp.get(\"/render/:id\", (req, res) => streamRenderJob(req, res, ctx));\n\n// Server lifecycle\nconst port = process.env.FFS_PORT || 2000; // ffmpeg was conceived in the year 2000\nconst server = app.listen(port, () => {\n console.log(`FFS server listening on port ${port}`);\n});\n\nfunction shutdown() {\n console.log(\"Shutting down FFS server...\");\n ctx.httpProxy.close();\n ctx.cacheStorage.close();\n server.close(() => {\n console.log(\"FFS server stopped\");\n process.exit(0);\n });\n}\n\nprocess.on(\"SIGTERM\", shutdown);\nprocess.on(\"SIGINT\", shutdown);\n\nexport { app };\n"],"mappings":";;;;;;;;;;;AAAA,OAAO,aAAa;AACpB,OAAO,gBAAgB;AAUvB,IAAM,MAAuB,QAAQ;AACrC,IAAI,IAAI,WAAW,KAAK,EAAE,OAAO,OAAO,CAAC,CAAC;AAE1C,IAAM,MAAM,MAAM,oBAAoB;AACtC,QAAQ,IAAI,oCAAoC,IAAI,UAAU,IAAI,EAAE;AAEpE,SAAS,aAAa,KAAsB,KAAgC;AAC1E,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,aAAa,IAAI,QAAQ;AAC/B,MAAI,CAAC,cAAc,eAAe,UAAU,MAAM,IAAI;AACpD,QAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,eAAe,CAAC;AAC9C,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGA,IAAI,KAAK,WAAW,CAAC,KAAK,QAAQ;AAChC,MAAI,CAAC,aAAa,KAAK,GAAG,EAAG;AAC7B,kBAAgB,KAAK,KAAK,GAAG;AAC/B,CAAC;AACD,IAAI,KAAK,UAAU,CAAC,KAAK,QAAQ;AAC/B,MAAI,CAAC,aAAa,KAAK,GAAG,EAAG;AAC7B,aAAW,KAAK,KAAK,GAAG;AAC1B,CAAC;AACD,IAAI,KAAK,WAAW,CAAC,KAAK,QAAQ;AAChC,MAAI,CAAC,aAAa,KAAK,GAAG,EAAG;AAC7B,kBAAgB,KAAK,KAAK,GAAG;AAC/B,CAAC;AAGD,IAAI,IAAI,eAAe,CAAC,KAAK,QAAQ,gBAAgB,KAAK,KAAK,GAAG,CAAC;AACnE,IAAI,IAAI,eAAe,CAAC,KAAK,QAAQ,gBAAgB,KAAK,KAAK,GAAG,CAAC;AAGnE,IAAM,OAAO,QAAQ,IAAI,YAAY;AACrC,IAAM,SAAS,IAAI,OAAO,MAAM,MAAM;AACpC,UAAQ,IAAI,gCAAgC,IAAI,EAAE;AACpD,CAAC;AAED,SAAS,WAAW;AAClB,UAAQ,IAAI,6BAA6B;AACzC,MAAI,UAAU,MAAM;AACpB,MAAI,aAAa,MAAM;AACvB,SAAO,MAAM,MAAM;AACjB,YAAQ,IAAI,oBAAoB;AAChC,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;AAEA,QAAQ,GAAG,WAAW,QAAQ;AAC9B,QAAQ,GAAG,UAAU,QAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["import express from \"express\";\nimport bodyParser from \"body-parser\";\nimport {\n createServerContext,\n createWarmupJob,\n streamWarmupJob,\n purgeCache,\n createRenderJob,\n streamRenderJob,\n createWarmupAndRenderJob,\n streamWarmupAndRenderJob,\n} from \"./handlers\";\n\nconst app: express.Express = express();\napp.use(bodyParser.json({ limit: \"50mb\" })); // Support large JSON requests\n\nconst ctx = await createServerContext();\nconsole.log(`FFS HTTP proxy listening on port ${ctx.httpProxy.port}`);\n\nfunction validateAuth(req: express.Request, res: express.Response): boolean {\n const apiKey = process.env.FFS_API_KEY;\n if (!apiKey) return true; // No auth required if api key not set\n\n const authHeader = req.headers.authorization;\n if (!authHeader || authHeader !== `Bearer ${apiKey}`) {\n res.status(401).json({ error: \"Unauthorized\" });\n return false;\n }\n return true;\n}\n\n// Routes with auth (POST endpoints)\napp.post(\"/warmup\", (req, res) => {\n if (!validateAuth(req, res)) return;\n createWarmupJob(req, res, ctx);\n});\napp.post(\"/purge\", (req, res) => {\n if (!validateAuth(req, res)) return;\n purgeCache(req, res, ctx);\n});\napp.post(\"/render\", (req, res) => {\n if (!validateAuth(req, res)) return;\n createRenderJob(req, res, ctx);\n});\napp.post(\"/warmup-and-render\", (req, res) => {\n if (!validateAuth(req, res)) return;\n createWarmupAndRenderJob(req, res, ctx);\n});\n\n// Routes without auth (GET endpoints use job ID as capability token)\napp.get(\"/warmup/:id\", (req, res) => streamWarmupJob(req, res, ctx));\napp.get(\"/render/:id\", (req, res) => streamRenderJob(req, res, ctx));\napp.get(\"/warmup-and-render/:id\", (req, res) =>\n streamWarmupAndRenderJob(req, res, ctx),\n);\n\n// Server lifecycle\nconst port = process.env.FFS_PORT || process.env.PORT || 2000; // ffmpeg was conceived in the year 2000\nconst server = app.listen(port, () => {\n console.log(`FFS server listening on port ${port}`);\n});\n\nfunction shutdown() {\n console.log(\"Shutting down FFS server...\");\n ctx.httpProxy.close();\n ctx.transientStore.close();\n server.close(() => {\n console.log(\"FFS server stopped\");\n process.exit(0);\n });\n}\n\nprocess.on(\"SIGTERM\", shutdown);\nprocess.on(\"SIGINT\", shutdown);\n\nexport { app };\n"],"mappings":";;;;;;;;;;;;;AAAA,OAAO,aAAa;AACpB,OAAO,gBAAgB;AAYvB,IAAM,MAAuB,QAAQ;AACrC,IAAI,IAAI,WAAW,KAAK,EAAE,OAAO,OAAO,CAAC,CAAC;AAE1C,IAAM,MAAM,MAAM,oBAAoB;AACtC,QAAQ,IAAI,oCAAoC,IAAI,UAAU,IAAI,EAAE;AAEpE,SAAS,aAAa,KAAsB,KAAgC;AAC1E,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,aAAa,IAAI,QAAQ;AAC/B,MAAI,CAAC,cAAc,eAAe,UAAU,MAAM,IAAI;AACpD,QAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,eAAe,CAAC;AAC9C,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGA,IAAI,KAAK,WAAW,CAAC,KAAK,QAAQ;AAChC,MAAI,CAAC,aAAa,KAAK,GAAG,EAAG;AAC7B,kBAAgB,KAAK,KAAK,GAAG;AAC/B,CAAC;AACD,IAAI,KAAK,UAAU,CAAC,KAAK,QAAQ;AAC/B,MAAI,CAAC,aAAa,KAAK,GAAG,EAAG;AAC7B,aAAW,KAAK,KAAK,GAAG;AAC1B,CAAC;AACD,IAAI,KAAK,WAAW,CAAC,KAAK,QAAQ;AAChC,MAAI,CAAC,aAAa,KAAK,GAAG,EAAG;AAC7B,kBAAgB,KAAK,KAAK,GAAG;AAC/B,CAAC;AACD,IAAI,KAAK,sBAAsB,CAAC,KAAK,QAAQ;AAC3C,MAAI,CAAC,aAAa,KAAK,GAAG,EAAG;AAC7B,2BAAyB,KAAK,KAAK,GAAG;AACxC,CAAC;AAGD,IAAI,IAAI,eAAe,CAAC,KAAK,QAAQ,gBAAgB,KAAK,KAAK,GAAG,CAAC;AACnE,IAAI,IAAI,eAAe,CAAC,KAAK,QAAQ,gBAAgB,KAAK,KAAK,GAAG,CAAC;AACnE,IAAI;AAAA,EAAI;AAAA,EAA0B,CAAC,KAAK,QACtC,yBAAyB,KAAK,KAAK,GAAG;AACxC;AAGA,IAAM,OAAO,QAAQ,IAAI,YAAY,QAAQ,IAAI,QAAQ;AACzD,IAAM,SAAS,IAAI,OAAO,MAAM,MAAM;AACpC,UAAQ,IAAI,gCAAgC,IAAI,EAAE;AACpD,CAAC;AAED,SAAS,WAAW;AAClB,UAAQ,IAAI,6BAA6B;AACzC,MAAI,UAAU,MAAM;AACpB,MAAI,eAAe,MAAM;AACzB,SAAO,MAAM,MAAM;AACjB,YAAQ,IAAI,oBAAoB;AAChC,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;AAEA,QAAQ,GAAG,WAAW,QAAQ;AAC9B,QAAQ,GAAG,UAAU,QAAQ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effing/ffs",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "FFmpeg-based effie rendering service",
5
5
  "type": "module",
6
6
  "exports": {
@@ -33,7 +33,7 @@
33
33
  "tar-stream": "^3.1.7",
34
34
  "undici": "^7.3.0",
35
35
  "zod": "^3.25.76",
36
- "@effing/effie": "0.2.0"
36
+ "@effing/effie": "0.4.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/body-parser": "^1.19.5",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/handlers/shared.ts","../src/proxy.ts","../src/handlers/caching.ts","../src/handlers/rendering.ts"],"sourcesContent":["import express from \"express\";\nimport type { CacheStorage } from \"../cache\";\nimport { createCacheStorage } from \"../cache\";\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 ServerContext = {\n cacheStorage: CacheStorage;\n httpProxy: HttpProxy;\n baseUrl: string;\n skipValidation: boolean;\n cacheConcurrency: number;\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 cacheStorage: createCacheStorage(),\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 cacheConcurrency: parseInt(process.env.FFS_CACHE_CONCURRENCY || \"4\", 10),\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 { cacheKeys } from \"../cache\";\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\";\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\n await ctx.cacheStorage.putJson(cacheKeys.warmupJob(jobId), { sources });\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 const jobCacheKey = cacheKeys.warmupJob(jobId);\n const job = await ctx.cacheStorage.getJson<WarmupJob>(jobCacheKey);\n // only allow the warmup job to run once\n ctx.cacheStorage.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 = cacheKeys.source(url);\n if (await ctx.cacheStorage.exists(ck)) {\n await ctx.cacheStorage.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) => cacheKeys.source(s.url));\n const existsMap = await ctx.cacheStorage.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.cacheConcurrency, queue.length) },\n async () => {\n while (queue.length > 0) {\n const source = queue.shift()!;\n const cacheKey = cacheKeys.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\n const trackedStream = sourceStream.pipe(progressStream);\n await ctx.cacheStorage.put(cacheKey, trackedStream);\n}\n","import express from \"express\";\nimport { randomUUID } from \"crypto\";\nimport { cacheKeys } from \"../cache\";\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\";\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.cacheStorage.putJson(cacheKeys.renderJob(jobId), job);\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 const jobCacheKey = cacheKeys.renderJob(jobId);\n const job = await ctx.cacheStorage.getJson<RenderJob>(jobCacheKey);\n // only allow the render job to run once\n ctx.cacheStorage.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 cacheStorage: ctx.cacheStorage,\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 cacheStorage: ctx.cacheStorage,\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"],"mappings":"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","names":["Readable","Readable","randomUUID","effieDataSchema","effieDataSchema","randomUUID"]}
@@ -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/cache.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 { cacheKeys } from \"./cache\";\nimport type { CacheStorage } from \"./cache\";\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 * Cache storage instance for source lookups.\n * If not provided, a shared lazy-initialized cache will be used.\n */\n cacheStorage?: CacheStorage;\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 cacheStorage?: CacheStorage;\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.cacheStorage = options?.cacheStorage;\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 cache, check the cache first\n if (this.cacheStorage) {\n const cachedStream = await this.cacheStorage.getStream(\n cacheKeys.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/**\n * Cache storage interface\n */\nexport interface CacheStorage {\n /** Store a stream with the given key */\n put(key: string, stream: Readable): 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 */\n putJson(key: string, data: object): 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 cache storage implementation\n */\nexport class S3CacheStorage implements CacheStorage {\n private client: S3Client;\n private bucket: string;\n private prefix: string;\n private 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 ?? \"\";\n this.ttlMs = options.ttlMs ?? 60 * 60 * 1000; // Default: 60 minutes\n }\n\n private getExpires(): Date {\n return new Date(Date.now() + this.ttlMs);\n }\n\n private getFullKey(key: string): string {\n return `${this.prefix}${key}`;\n }\n\n async put(key: string, stream: Readable): 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(),\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): 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(),\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 cache storage implementation\n */\nexport class LocalCacheStorage implements CacheStorage {\n private baseDir: string;\n private initialized = false;\n private cleanupInterval?: ReturnType<typeof setInterval>;\n private ttlMs: number;\n\n constructor(baseDir?: string, ttlMs: number = 60 * 60 * 1000) {\n this.baseDir = baseDir ?? path.join(os.tmpdir(), \"ffs-cache\");\n this.ttlMs = ttlMs;\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 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): Promise<void> {\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): Promise<void> {\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 cache storage instance based on environment variables.\n * Uses S3 if FFS_CACHE_BUCKET is set, otherwise uses local filesystem.\n */\nexport function createCacheStorage(): CacheStorage {\n // Parse TTL from env (default: 60 minutes)\n const ttlMs = process.env.FFS_CACHE_TTL_MS\n ? parseInt(process.env.FFS_CACHE_TTL_MS, 10)\n : 60 * 60 * 1000;\n\n if (process.env.FFS_CACHE_BUCKET) {\n return new S3CacheStorage({\n endpoint: process.env.FFS_CACHE_ENDPOINT,\n region: process.env.FFS_CACHE_REGION ?? \"auto\",\n bucket: process.env.FFS_CACHE_BUCKET,\n prefix: process.env.FFS_CACHE_PREFIX,\n accessKeyId: process.env.FFS_CACHE_ACCESS_KEY,\n secretAccessKey: process.env.FFS_CACHE_SECRET_KEY,\n ttlMs,\n });\n }\n\n return new LocalCacheStorage(process.env.FFS_CACHE_LOCAL_DIR, ttlMs);\n}\n\nexport function hashUrl(url: string): string {\n return crypto.createHash(\"sha256\").update(url).digest(\"hex\").slice(0, 16);\n}\n\nexport type SourceCacheKey = `sources/${string}`;\nexport type WarmupJobCacheKey = `jobs/warmup/${string}.json`;\nexport type RenderJobCacheKey = `jobs/render/${string}.json`;\n\n/**\n * Build the cache key for a source URL (hashing is handled internally).\n */\nexport function sourceCacheKey(url: string): SourceCacheKey {\n return `sources/${hashUrl(url)}`;\n}\n\nexport function warmupJobCacheKey(jobId: string): WarmupJobCacheKey {\n return `jobs/warmup/${jobId}.json`;\n}\n\nexport function renderJobCacheKey(jobId: string): RenderJobCacheKey {\n return `jobs/render/${jobId}.json`;\n}\n\n/**\n * Centralized cache key builders for known namespaces.\n * Prefer using these helpers over manual string interpolation.\n */\nexport const cacheKeys = {\n source: sourceCacheKey,\n warmupJob: warmupJobCacheKey,\n renderJob: renderJobCacheKey,\n} as const;\n"],"mappings":"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","names":["stream","createReadStream","fs","createWriteStream","pipeline","path","os","createReadStream"]}