@effing/ffs 0.1.1 → 0.2.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.
- package/README.md +2 -0
- package/dist/{chunk-LK5K4SQV.js → chunk-6YHSYHDY.js} +182 -22
- package/dist/chunk-6YHSYHDY.js.map +1 -0
- package/dist/{chunk-RNE6TKMF.js → chunk-A7BAW24L.js} +212 -162
- package/dist/chunk-A7BAW24L.js.map +1 -0
- package/dist/handlers/index.d.ts +5 -4
- package/dist/handlers/index.js +2 -2
- package/dist/index.d.ts +19 -11
- package/dist/index.js +3 -9
- package/dist/proxy-BI8OMQl0.d.ts +68 -0
- package/dist/server.js +277 -66
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/cache-BUVFfGZF.d.ts +0 -25
- package/dist/chunk-LK5K4SQV.js.map +0 -1
- package/dist/chunk-RNE6TKMF.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/handlers/shared.ts","../src/handlers/caching.ts","../src/handlers/rendering.ts"],"sourcesContent":["import express from \"express\";\nimport type { CacheStorage } from \"../cache\";\nimport { createCacheStorage } from \"../cache\";\nimport type { EffieData, EffieSources } 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: string[];\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 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 function createServerContext(): ServerContext {\n const port = process.env.FFS_PORT || 2000;\n return {\n cacheStorage: createCacheStorage(),\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 express from \"express\";\nimport { Readable, Transform } from \"stream\";\nimport { randomUUID } from \"crypto\";\nimport { cacheKeys } from \"../cache\";\nimport { ffsFetch } from \"../fetch\";\nimport { extractEffieSources } from \"@effing/effie\";\nimport type { ServerContext, SSEEventSender, WarmupJob } from \"./shared\";\nimport {\n parseEffieData,\n setupCORSHeaders,\n setupSSEResponse,\n createSSEEventSender,\n} from \"./shared\";\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 = extractEffieSources(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 */\nexport async function warmupSources(\n sources: string[],\n sendEvent: SSEEventSender,\n ctx: ServerContext,\n): Promise<void> {\n const total = sources.length;\n const sourceCacheKeys = sources.map(cacheKeys.source);\n\n sendEvent(\"start\", { total });\n\n // Check what's already cached\n const existsMap = await ctx.cacheStorage.existsMany(sourceCacheKeys);\n\n let cached = 0;\n let failed = 0;\n\n // Report hits immediately\n for (let i = 0; i < sources.length; i++) {\n if (existsMap.get(sourceCacheKeys[i])) {\n cached++;\n sendEvent(\"progress\", {\n url: sources[i],\n status: \"hit\",\n cached,\n failed,\n total,\n });\n }\n }\n\n // Filter to uncached sources\n const uncached = sources.filter((_, i) => !existsMap.get(sourceCacheKeys[i]));\n\n if (uncached.length === 0) {\n sendEvent(\"summary\", { cached, failed, total });\n return;\n }\n\n // Keepalive interval for long-running fetches\n const keepalive = setInterval(() => {\n sendEvent(\"keepalive\", { cached, failed, 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 url = queue.shift()!;\n const cacheKey = cacheKeys.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(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,\n status: \"cached\",\n cached,\n failed,\n total,\n ms: Date.now() - startTime,\n });\n } catch (error) {\n inFlightFetches.delete(cacheKey);\n failed++;\n sendEvent(\"progress\", {\n url,\n status: \"error\",\n error: String(error),\n cached,\n failed,\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, 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 });\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, { cacheStorage: ctx.cacheStorage });\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":";;;;;;;;AAAA,OAAoB;AAIpB,SAAS,uBAAuB;AAkCzB,SAAS,sBAAqC;AACnD,QAAM,OAAO,QAAQ,IAAI,YAAY;AACrC,SAAO;AAAA,IACL,cAAc,mBAAmB;AAAA,IACjC,SAAS,QAAQ,IAAI,gBAAgB,oBAAoB,IAAI;AAAA,IAC7D,gBACE,CAAC,CAAC,QAAQ,IAAI,uBACd,QAAQ,IAAI,wBAAwB;AAAA,IACtC,kBAAkB,SAAS,QAAQ,IAAI,yBAAyB,KAAK,EAAE;AAAA,EACzE;AACF;AAKO,SAAS,eACd,MACA,gBACkB;AAElB,QAAM,YACJ,OAAO,SAAS,YAAY,SAAS,QAAQ,WAAW;AAC1D,QAAM,eAAe,YAAa,KAA4B,QAAQ;AAEtE,MAAI,CAAC,gBAAgB;AACnB,UAAM,SAAS,gBAAgB,UAAU,YAAY;AACrD,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO;AAAA,QACL,OAAO;AAAA,QACP,QAAQ,OAAO,MAAM,OAAO,IAAI,CAAC,WAAW;AAAA,UAC1C,MAAM,MAAM,KAAK,KAAK,GAAG;AAAA,UACzB,SAAS,MAAM;AAAA,QACjB,EAAE;AAAA,MACJ;AAAA,IACF;AACA,WAAO,EAAE,OAAO,OAAO,KAAK;AAAA,EAC9B,OAAO;AACL,UAAM,QAAQ;AACd,QAAI,CAAC,OAAO,UAAU;AACpB,aAAO,EAAE,OAAO,uCAAuC;AAAA,IACzD;AACA,WAAO,EAAE,MAAM;AAAA,EACjB;AACF;AAKO,SAAS,iBAAiB,KAA6B;AAC5D,MAAI,UAAU,+BAA+B,GAAG;AAChD,MAAI,UAAU,gCAAgC,KAAK;AACrD;AAKO,SAAS,iBAAiB,KAA6B;AAC5D,MAAI,UAAU,gBAAgB,mBAAmB;AACjD,MAAI,UAAU,iBAAiB,UAAU;AACzC,MAAI,UAAU,cAAc,YAAY;AACxC,MAAI,aAAa;AACnB;AAKO,SAAS,qBAAqB,KAAuC;AAC1E,SAAO,CAAC,OAAe,SAAiB;AACtC,QAAI,MAAM,UAAU,KAAK;AAAA,QAAW,KAAK,UAAU,IAAI,CAAC;AAAA;AAAA,CAAM;AAAA,EAChE;AACF;;;AC5GA,OAAoB;AACpB,SAAS,UAAU,iBAAiB;AACpC,SAAS,kBAAkB;AAG3B,SAAS,2BAA2B;AAUpC,IAAM,kBAAkB,oBAAI,IAA2B;AAMvD,eAAsB,gBACpB,KACA,KACA,KACe;AACf,MAAI;AACF,UAAM,cAAc,eAAe,IAAI,MAAM,IAAI,cAAc;AAC/D,QAAI,WAAW,aAAa;AAC1B,UAAI,OAAO,GAAG,EAAE,KAAK,WAAW;AAChC;AAAA,IACF;AAEA,UAAM,UAAU,oBAAoB,YAAY,KAAK;AACrD,UAAM,QAAQ,WAAW;AAGzB,UAAM,IAAI,aAAa,QAAQ,UAAU,UAAU,KAAK,GAAG,EAAE,QAAQ,CAAC;AAEtE,QAAI,KAAK;AAAA,MACP,IAAI;AAAA,MACJ,KAAK,GAAG,IAAI,OAAO,WAAW,KAAK;AAAA,IACrC,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAM,8BAA8B,KAAK;AACjD,QAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,8BAA8B,CAAC;AAAA,EAC/D;AACF;AAMA,eAAsB,gBACpB,KACA,KACA,KACe;AACf,MAAI;AACF,qBAAiB,GAAG;AAEpB,UAAM,QAAQ,IAAI,OAAO;AACzB,UAAM,cAAc,UAAU,UAAU,KAAK;AAC7C,UAAM,MAAM,MAAM,IAAI,aAAa,QAAmB,WAAW;AAEjE,QAAI,aAAa,OAAO,WAAW;AAEnC,QAAI,CAAC,KAAK;AACR,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,gBAAgB,CAAC;AAC/C;AAAA,IACF;AAEA,qBAAiB,GAAG;AACpB,UAAM,YAAY,qBAAqB,GAAG;AAE1C,QAAI;AACF,YAAM,cAAc,IAAI,SAAS,WAAW,GAAG;AAC/C,gBAAU,YAAY,EAAE,QAAQ,QAAQ,CAAC;AAAA,IAC3C,SAAS,OAAO;AACd,gBAAU,SAAS,EAAE,SAAS,OAAO,KAAK,EAAE,CAAC;AAAA,IAC/C,UAAE;AACA,UAAI,IAAI;AAAA,IACV;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,8BAA8B,KAAK;AACjD,QAAI,CAAC,IAAI,aAAa;AACpB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,0BAA0B,CAAC;AAAA,IAC3D,OAAO;AACL,UAAI,IAAI;AAAA,IACV;AAAA,EACF;AACF;AAKA,eAAsB,WACpB,KACA,KACA,KACe;AACf,MAAI;AACF,UAAM,cAAc,eAAe,IAAI,MAAM,IAAI,cAAc;AAC/D,QAAI,WAAW,aAAa;AAC1B,UAAI,OAAO,GAAG,EAAE,KAAK,WAAW;AAChC;AAAA,IACF;AAEA,UAAM,UAAU,oBAAoB,YAAY,KAAK;AAErD,QAAI,SAAS;AACb,eAAW,OAAO,SAAS;AACzB,YAAM,KAAK,UAAU,OAAO,GAAG;AAC/B,UAAI,MAAM,IAAI,aAAa,OAAO,EAAE,GAAG;AACrC,cAAM,IAAI,aAAa,OAAO,EAAE;AAChC;AAAA,MACF;AAAA,IACF;AAEA,QAAI,KAAK,EAAE,QAAQ,OAAO,QAAQ,OAAO,CAAC;AAAA,EAC5C,SAAS,OAAO;AACd,YAAQ,MAAM,wBAAwB,KAAK;AAC3C,QAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAA,EACzD;AACF;AAKA,eAAsB,cACpB,SACA,WACA,KACe;AACf,QAAM,QAAQ,QAAQ;AACtB,QAAM,kBAAkB,QAAQ,IAAI,UAAU,MAAM;AAEpD,YAAU,SAAS,EAAE,MAAM,CAAC;AAG5B,QAAM,YAAY,MAAM,IAAI,aAAa,WAAW,eAAe;AAEnE,MAAI,SAAS;AACb,MAAI,SAAS;AAGb,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,QAAI,UAAU,IAAI,gBAAgB,CAAC,CAAC,GAAG;AACrC;AACA,gBAAU,YAAY;AAAA,QACpB,KAAK,QAAQ,CAAC;AAAA,QACd,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,WAAW,QAAQ,OAAO,CAAC,GAAG,MAAM,CAAC,UAAU,IAAI,gBAAgB,CAAC,CAAC,CAAC;AAE5E,MAAI,SAAS,WAAW,GAAG;AACzB,cAAU,WAAW,EAAE,QAAQ,QAAQ,MAAM,CAAC;AAC9C;AAAA,EACF;AAGA,QAAM,YAAY,YAAY,MAAM;AAClC,cAAU,aAAa,EAAE,QAAQ,QAAQ,MAAM,CAAC;AAAA,EAClD,GAAG,IAAM;AAGT,QAAM,QAAQ,CAAC,GAAG,QAAQ;AAC1B,QAAM,UAAU,MAAM;AAAA,IACpB,EAAE,QAAQ,KAAK,IAAI,IAAI,kBAAkB,MAAM,MAAM,EAAE;AAAA,IACvD,YAAY;AACV,aAAO,MAAM,SAAS,GAAG;AACvB,cAAM,MAAM,MAAM,MAAM;AACxB,cAAM,WAAW,UAAU,OAAO,GAAG;AACrC,cAAM,YAAY,KAAK,IAAI;AAE3B,YAAI;AAEF,cAAI,eAAe,gBAAgB,IAAI,QAAQ;AAC/C,cAAI,CAAC,cAAc;AACjB,2BAAe,cAAc,KAAK,UAAU,WAAW,GAAG;AAC1D,4BAAgB,IAAI,UAAU,YAAY;AAAA,UAC5C;AAEA,gBAAM;AACN,0BAAgB,OAAO,QAAQ;AAE/B;AACA,oBAAU,YAAY;AAAA,YACpB;AAAA,YACA,QAAQ;AAAA,YACR;AAAA,YACA;AAAA,YACA;AAAA,YACA,IAAI,KAAK,IAAI,IAAI;AAAA,UACnB,CAAC;AAAA,QACH,SAAS,OAAO;AACd,0BAAgB,OAAO,QAAQ;AAC/B;AACA,oBAAU,YAAY;AAAA,YACpB;AAAA,YACA,QAAQ;AAAA,YACR,OAAO,OAAO,KAAK;AAAA,YACnB;AAAA,YACA;AAAA,YACA;AAAA,YACA,IAAI,KAAK,IAAI,IAAI;AAAA,UACnB,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ,IAAI,OAAO;AACzB,gBAAc,SAAS;AAEvB,YAAU,WAAW,EAAE,QAAQ,QAAQ,MAAM,CAAC;AAChD;AAKA,eAAsB,cACpB,KACA,UACA,WACA,KACe;AACf,QAAM,WAAW,MAAM,SAAS,KAAK;AAAA,IACnC,gBAAgB,KAAK,KAAK;AAAA;AAAA,IAC1B,aAAa,KAAK,KAAK;AAAA;AAAA,EACzB,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,GAAG,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,EAC7D;AAEA,YAAU,eAAe,EAAE,KAAK,QAAQ,WAAW,eAAe,EAAE,CAAC;AAGrE,QAAM,eAAe,SAAS;AAAA,IAC5B,SAAS;AAAA,EACX;AAEA,MAAI,aAAa;AACjB,MAAI,gBAAgB,KAAK,IAAI;AAC7B,QAAM,oBAAoB;AAE1B,QAAM,iBAAiB,IAAI,UAAU;AAAA,IACnC,UAAU,OAAO,WAAW,UAAU;AACpC,oBAAc,MAAM;AACpB,YAAM,MAAM,KAAK,IAAI;AACrB,UAAI,MAAM,iBAAiB,mBAAmB;AAC5C,kBAAU,eAAe;AAAA,UACvB;AAAA,UACA,QAAQ;AAAA,UACR,eAAe;AAAA,QACjB,CAAC;AACD,wBAAgB;AAAA,MAClB;AACA,eAAS,MAAM,KAAK;AAAA,IACtB;AAAA,EACF,CAAC;AAGD,QAAM,gBAAgB,aAAa,KAAK,cAAc;AACtD,QAAM,IAAI,aAAa,IAAI,UAAU,aAAa;AACpD;;;ACjRA,OAAoB;AACpB,SAAS,cAAAA,mBAAkB;AAI3B,SAAS,mBAAAC,wBAAuB;AAkBhC,eAAsB,gBACpB,KACA,KACA,KACe;AACf,MAAI;AAGF,UAAM,YAAY,WAAW,IAAI;AAEjC,QAAI;AACJ,QAAI;AACJ,QAAI;AAEJ,QAAI,WAAW;AAEb,YAAM,UAAU,IAAI;AAMpB,UAAI,OAAO,QAAQ,UAAU,UAAU;AAErC,cAAM,WAAW,MAAM,SAAS,QAAQ,KAAK;AAC7C,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,IAAI;AAAA,YACR,+BAA+B,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,UACvE;AAAA,QACF;AACA,uBAAe,MAAM,SAAS,KAAK;AAAA,MACrC,OAAO;AAEL,uBAAe,QAAQ;AAAA,MACzB;AAEA,cAAQ,QAAQ,SAAS;AACzB,eAAS,QAAQ;AAAA,IACnB,OAAO;AAEL,qBAAe,IAAI;AACnB,cAAQ,WAAW,IAAI,MAAM,OAAO,SAAS,KAAK,GAAG;AAAA,IACvD;AAGA,QAAI;AACJ,QAAI,CAAC,IAAI,gBAAgB;AACvB,YAAM,SAASC,iBAAgB,UAAU,YAAY;AACrD,UAAI,CAAC,OAAO,SAAS;AACnB,YAAI,OAAO,GAAG,EAAE,KAAK;AAAA,UACnB,OAAO;AAAA,UACP,QAAQ,OAAO,MAAM,OAAO,IAAI,CAAC,WAAW;AAAA,YAC1C,MAAM,MAAM,KAAK,KAAK,GAAG;AAAA,YACzB,SAAS,MAAM;AAAA,UACjB,EAAE;AAAA,QACJ,CAAC;AACD;AAAA,MACF;AACA,cAAQ,OAAO;AAAA,IACjB,OAAO;AAEL,YAAM,OAAO;AACb,UAAI,CAAC,MAAM,UAAU;AACnB,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,uCAAuC,CAAC;AACtE;AAAA,MACF;AACA,cAAQ;AAAA,IACV;AAGA,UAAM,QAAQC,YAAW;AACzB,UAAM,MAAiB;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,KAAK,IAAI;AAAA,IACtB;AAEA,UAAM,IAAI,aAAa,QAAQ,UAAU,UAAU,KAAK,GAAG,GAAG;AAE9D,QAAI,KAAK;AAAA,MACP,IAAI;AAAA,MACJ,KAAK,GAAG,IAAI,OAAO,WAAW,KAAK;AAAA,IACrC,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAM,8BAA8B,KAAK;AACjD,QAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,8BAA8B,CAAC;AAAA,EAC/D;AACF;AAMA,eAAsB,gBACpB,KACA,KACA,KACe;AACf,MAAI;AACF,qBAAiB,GAAG;AAEpB,UAAM,QAAQ,IAAI,OAAO;AACzB,UAAM,cAAc,UAAU,UAAU,KAAK;AAC7C,UAAM,MAAM,MAAM,IAAI,aAAa,QAAmB,WAAW;AAEjE,QAAI,aAAa,OAAO,WAAW;AAEnC,QAAI,CAAC,KAAK;AACR,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,2BAA2B,CAAC;AAC1D;AAAA,IACF;AAGA,QAAI,IAAI,QAAQ;AACd,YAAM,uBAAuB,KAAK,KAAK,GAAG;AAAA,IAC5C,OAAO;AACL,YAAM,mBAAmB,KAAK,KAAK,GAAG;AAAA,IACxC;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,oBAAoB,KAAK;AACvC,QAAI,CAAC,IAAI,aAAa;AACpB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,mBAAmB,CAAC;AAAA,IACpD,OAAO;AACL,UAAI,IAAI;AAAA,IACV;AAAA,EACF;AACF;AAKA,eAAsB,mBACpB,KACA,KACA,KACe;AACf,QAAM,WAAW,IAAI,cAAc,IAAI,OAAO;AAAA,IAC5C,cAAc,IAAI;AAAA,EACpB,CAAC;AACD,QAAM,cAAc,MAAM,SAAS,OAAO,IAAI,KAAK;AAEnD,MAAI,GAAG,SAAS,MAAM;AACpB,gBAAY,QAAQ;AACpB,aAAS,MAAM;AAAA,EACjB,CAAC;AAED,MAAI,IAAI,gBAAgB,WAAW;AACnC,cAAY,KAAK,GAAG;AACtB;AAKA,eAAsB,uBACpB,KACA,KACA,KACe;AACf,mBAAiB,GAAG;AACpB,QAAM,YAAY,qBAAqB,GAAG;AAG1C,QAAM,YAAY,YAAY,MAAM;AAClC,cAAU,aAAa,EAAE,QAAQ,YAAY,CAAC;AAAA,EAChD,GAAG,IAAM;AAET,MAAI;AACF,cAAU,WAAW,EAAE,QAAQ,YAAY,CAAC;AAE5C,UAAM,UAAU,MAAM;AAAA,MACpB,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,IACF;AAEA,cAAU,YAAY,EAAE,QAAQ,YAAY,QAAQ,CAAC;AAAA,EACvD,SAAS,OAAO;AACd,cAAU,SAAS,EAAE,SAAS,OAAO,KAAK,EAAE,CAAC;AAAA,EAC/C,UAAE;AACA,kBAAc,SAAS;AACvB,QAAI,IAAI;AAAA,EACV;AACF;AAMA,eAAsB,wBACpB,OACA,OACA,QACA,WACA,KACiC;AACjC,QAAM,UAAkC,CAAC;AAGzC,MAAI,OAAO,UAAU;AACnB,UAAM,sBAAsB,KAAK,IAAI;AACrC,UAAM,qBAAqB,MAAM,SAAS,MAAM,KAAK;AACrD,QAAI,CAAC,mBAAmB,IAAI;AAC1B,YAAM,IAAI;AAAA,QACR,gCAAgC,mBAAmB,MAAM,IAAI,mBAAmB,UAAU;AAAA,MAC5F;AAAA,IACF;AACA,UAAM,cAAc,OAAO,KAAK,MAAM,mBAAmB,YAAY,CAAC;AACtE,YAAQ,iBAAiB,KAAK,IAAI,IAAI;AAEtC,UAAM,uBAAuB,KAAK,IAAI;AACtC,UAAM,sBAAsB,MAAM,SAAS,OAAO,UAAU;AAAA,MAC1D,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,kBAAkB,YAAY,OAAO,SAAS;AAAA,MAChD;AAAA,IACF,CAAC;AACD,QAAI,CAAC,oBAAoB,IAAI;AAC3B,YAAM,IAAI;AAAA,QACR,2BAA2B,oBAAoB,MAAM,IAAI,oBAAoB,UAAU;AAAA,MACzF;AAAA,IACF;AACA,YAAQ,kBAAkB,KAAK,IAAI,IAAI;AAAA,EACzC;AAGA,QAAM,kBAAkB,KAAK,IAAI;AACjC,QAAM,WAAW,IAAI,cAAc,OAAO,EAAE,cAAc,IAAI,aAAa,CAAC;AAC5E,QAAM,cAAc,MAAM,SAAS,OAAO,KAAK;AAC/C,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,aAAa;AACrC,WAAO,KAAK,OAAO,KAAK,KAAK,CAAC;AAAA,EAChC;AACA,QAAM,cAAc,OAAO,OAAO,MAAM;AACxC,UAAQ,aAAa,KAAK,IAAI,IAAI;AAGlC,YAAU,aAAa,EAAE,QAAQ,YAAY,CAAC;AAG9C,QAAM,kBAAkB,KAAK,IAAI;AACjC,QAAM,iBAAiB,MAAM,SAAS,OAAO,UAAU;AAAA,IACrD,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,kBAAkB,YAAY,OAAO,SAAS;AAAA,IAChD;AAAA,EACF,CAAC;AACD,MAAI,CAAC,eAAe,IAAI;AACtB,UAAM,IAAI;AAAA,MACR,oCAAoC,eAAe,MAAM,IAAI,eAAe,UAAU;AAAA,IACxF;AAAA,EACF;AACA,UAAQ,aAAa,KAAK,IAAI,IAAI;AAElC,SAAO;AACT;","names":["randomUUID","effieDataSchema","effieDataSchema","randomUUID"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/motion.ts","../src/effect.ts","../src/ffmpeg.ts","../src/transition.ts","../src/render.ts","../src/fetch.ts","../src/cache.ts"],"sourcesContent":["import type { EffieMotion } from \"@effing/effie\";\n\n/**\n * Defines the building blocks for an FFmpeg motion expression.\n */\ntype MotionComponents = {\n initialX: string; // Expression for X before animation starts\n initialY: string; // Expression for Y before animation starts\n activeX: string; // Expression for X during animation (incorporates relative time)\n activeY: string; // Expression for Y during animation (incorporates relative time)\n finalX: string; // Expression for X after animation ends\n finalY: string; // Expression for Y after animation ends\n duration: number; // Duration of the animation effect itself\n};\n\nfunction getEasingExpression(\n tNormExpr: string,\n easingType: \"linear\" | \"ease-in\" | \"ease-out\" | \"ease-in-out\",\n): string {\n switch (easingType) {\n case \"ease-in\":\n // t^2\n return `pow(${tNormExpr},2)`;\n case \"ease-out\":\n // 1 - (1-t)^2\n return `(1-pow(1-(${tNormExpr}),2))`;\n case \"ease-in-out\":\n // t < 0.5 ? 2*t^2 : 1 - (-2*t + 2)^2 / 2\n return `if(lt(${tNormExpr},0.5),2*pow(${tNormExpr},2),1-pow(-2*(${tNormExpr})+2,2)/2)`;\n case \"linear\":\n default:\n // Default to linear if type is unknown or \"linear\"\n return `(${tNormExpr})`; // Ensure parentheses for safety if tNormExpr is complex\n }\n}\n\nfunction processSlideMotion(\n motion: Extract<EffieMotion, { type: \"slide\" }>,\n relativeTimeExpr: string,\n): MotionComponents {\n const duration = motion.duration ?? 1;\n const distance = motion.distance ?? 1;\n const reverse = motion.reverse ?? false;\n const easing = motion.easing ?? \"linear\"; // Default to linear easing\n\n // 1. Calculate normalized time (0 to 1 over the duration)\n // Assuming duration > 0\n const tNormExpr = `(${relativeTimeExpr})/${duration}`;\n\n // 2. Get the easing function expression applied to normalized time\n const easedProgressExpr = getEasingExpression(tNormExpr, easing);\n\n // 3. Determine the final time factor based on easing and direction (reverse)\n // - If reverse (slide out): Progress goes 0 -> 1 (eased)\n // - If not reverse (slide in): Progress goes 1 -> 0 (eased, so 1 - eased_progress)\n const finalTimeFactorExpr = reverse\n ? easedProgressExpr\n : `(1-(${easedProgressExpr}))`; // Parentheses around easedProgressExpr are crucial\n\n let activeX: string;\n let activeY: string;\n let initialX: string;\n let initialY: string;\n let finalX: string;\n let finalY: string;\n\n switch (motion.direction) {\n case \"left\": {\n const offsetXLeft = `${distance}*W`;\n activeX = `(${offsetXLeft})*${finalTimeFactorExpr}`;\n activeY = \"0\";\n initialX = reverse ? \"0\" : offsetXLeft;\n initialY = \"0\";\n finalX = reverse ? offsetXLeft : \"0\";\n finalY = \"0\";\n break;\n }\n case \"right\": {\n const offsetXRight = `-${distance}*W`;\n activeX = `(${offsetXRight})*${finalTimeFactorExpr}`;\n activeY = \"0\";\n initialX = reverse ? \"0\" : offsetXRight;\n initialY = \"0\";\n finalX = reverse ? offsetXRight : \"0\";\n finalY = \"0\";\n break;\n }\n case \"up\": {\n const offsetYUp = `${distance}*H`;\n activeX = \"0\";\n activeY = `(${offsetYUp})*${finalTimeFactorExpr}`;\n initialX = \"0\";\n initialY = reverse ? \"0\" : offsetYUp;\n finalX = \"0\";\n finalY = reverse ? offsetYUp : \"0\";\n break;\n }\n case \"down\": {\n const offsetYDown = `-${distance}*H`;\n activeX = \"0\";\n activeY = `(${offsetYDown})*${finalTimeFactorExpr}`;\n initialX = \"0\";\n initialY = reverse ? \"0\" : offsetYDown;\n finalX = \"0\";\n finalY = reverse ? offsetYDown : \"0\";\n break;\n }\n }\n\n return { initialX, initialY, activeX, activeY, finalX, finalY, duration };\n}\n\nfunction processBounceMotion(\n motion: Extract<EffieMotion, { type: \"bounce\" }>,\n relativeTimeExpr: string,\n): MotionComponents {\n const amplitude = motion.amplitude ?? 0.5;\n const duration = motion.duration ?? 1;\n const initialY = `-overlay_h*${amplitude}`;\n const finalY = \"0\";\n\n // Calculate the normalized time expression (ranging from 0 to 1 over the duration)\n // Note: Assumes duration > 0. FFmpeg might handle division by zero, but it's safer to ensure duration > 0.\n const tNormExpr = `(${relativeTimeExpr})/${duration}`;\n\n // Piecewise parabolic approximation using normalized time (tNormExpr)\n const activeBounceExpression =\n `if(lt(${tNormExpr},0.363636),${initialY}+overlay_h*${amplitude}*(7.5625*${tNormExpr}*${tNormExpr}),` +\n `if(lt(${tNormExpr},0.727273),${initialY}+overlay_h*${amplitude}*(7.5625*(${tNormExpr}-0.545455)*(${tNormExpr}-0.545455)+0.75),` +\n `if(lt(${tNormExpr},0.909091),${initialY}+overlay_h*${amplitude}*(7.5625*(${tNormExpr}-0.818182)*(${tNormExpr}-0.818182)+0.9375),` +\n `if(lt(${tNormExpr},0.954545),${initialY}+overlay_h*${amplitude}*(7.5625*(${tNormExpr}-0.954545)*(${tNormExpr}-0.954545)+0.984375),` +\n `${finalY}` + // Should settle to finalY as tNormExpr approaches 1\n `))))`;\n\n return {\n initialX: \"0\",\n initialY: initialY,\n activeX: \"0\",\n activeY: activeBounceExpression, // This expression now scales with duration\n finalX: \"0\",\n finalY: finalY,\n duration: duration, // Return the actual duration used\n };\n}\n\nfunction processShakeMotion(\n motion: Extract<EffieMotion, { type: \"shake\" }>,\n relativeTimeExpr: string,\n): MotionComponents {\n const intensity = motion.intensity ?? 10;\n const frequency = motion.frequency ?? 4;\n const duration = motion.duration ?? 1;\n\n const activeX = `${intensity}*sin(${relativeTimeExpr}*PI*${frequency})`;\n const activeY = `${intensity}*cos(${relativeTimeExpr}*PI*${frequency})`;\n\n return {\n initialX: \"0\",\n initialY: \"0\",\n activeX: activeX,\n activeY: activeY,\n finalX: \"0\",\n finalY: \"0\",\n duration: duration,\n };\n}\n\nexport function processMotion(delay: number, motion?: EffieMotion): string {\n if (!motion) return \"x=0:y=0\";\n\n const start = delay + (motion.start ?? 0);\n const relativeTimeExpr = `(t-${start})`;\n let components: MotionComponents;\n\n switch (motion.type) {\n case \"bounce\":\n components = processBounceMotion(motion, relativeTimeExpr);\n break;\n case \"shake\":\n components = processShakeMotion(motion, relativeTimeExpr);\n break;\n case \"slide\":\n components = processSlideMotion(motion, relativeTimeExpr);\n break;\n default:\n motion satisfies never;\n throw new Error(\n `Unsupported motion type: ${(motion as EffieMotion).type}`,\n );\n }\n\n const motionEndTime = start + components.duration;\n\n const xArg = `if(lt(t,${start}),${components.initialX},if(lt(t,${motionEndTime}),${components.activeX},${components.finalX}))`;\n const yArg = `if(lt(t,${start}),${components.initialY},if(lt(t,${motionEndTime}),${components.activeY},${components.finalY}))`;\n\n return `x='${xArg}':y='${yArg}'`;\n}\n","import type { EffieEffect } from \"@effing/effie\";\n\nfunction processFadeIn(\n effect: Extract<EffieEffect, { type: \"fade-in\" }>,\n _frameRate: number,\n _frameWidth: number,\n _frameHeight: number,\n): string {\n return `fade=t=in:st=${effect.start}:d=${effect.duration}:alpha=1`;\n}\n\nfunction processFadeOut(\n effect: Extract<EffieEffect, { type: \"fade-out\" }>,\n _frameRate: number,\n _frameWidth: number,\n _frameHeight: number,\n): string {\n return `fade=t=out:st=${effect.start}:d=${effect.duration}:alpha=1`;\n}\n\nfunction processSaturateIn(\n effect: Extract<EffieEffect, { type: \"saturate-in\" }>,\n _frameRate: number,\n _frameWidth: number,\n _frameHeight: number,\n): string {\n return `hue='s=max(0,min(1,(t-${effect.start})/${effect.duration}))'`;\n}\n\nfunction processSaturateOut(\n effect: Extract<EffieEffect, { type: \"saturate-out\" }>,\n _frameRate: number,\n _frameWidth: number,\n _frameHeight: number,\n): string {\n return `hue='s=max(0,min(1,(${effect.start + effect.duration}-t)/${effect.duration}))'`;\n}\n\nfunction processScroll(\n effect: Extract<EffieEffect, { type: \"scroll\" }>,\n frameRate: number,\n _frameWidth: number,\n _frameHeight: number,\n): string {\n const distance = effect.distance ?? 1;\n const scroll = distance / (1 + distance);\n const speed = scroll / (effect.duration * frameRate);\n switch (effect.direction) {\n case \"left\":\n return `scroll=h=${speed}`;\n case \"right\":\n return `scroll=hpos=${1 - scroll}:h=-${speed}`;\n case \"up\":\n return `scroll=v=${speed}`;\n case \"down\":\n return `scroll=vpos=${1 - scroll}:v=-${speed}`;\n }\n}\n\nfunction processEffect(\n effect: EffieEffect,\n frameRate: number,\n frameWidth: number,\n frameHeight: number,\n): string {\n switch (effect.type) {\n case \"fade-in\":\n return processFadeIn(effect, frameRate, frameWidth, frameHeight);\n case \"fade-out\":\n return processFadeOut(effect, frameRate, frameWidth, frameHeight);\n case \"saturate-in\":\n return processSaturateIn(effect, frameRate, frameWidth, frameHeight);\n case \"saturate-out\":\n return processSaturateOut(effect, frameRate, frameWidth, frameHeight);\n case \"scroll\":\n return processScroll(effect, frameRate, frameWidth, frameHeight);\n default:\n effect satisfies never;\n throw new Error(\n `Unsupported effect type: ${(effect as EffieEffect).type}`,\n );\n }\n}\n\nexport function processEffects(\n effects: EffieEffect[] | undefined,\n frameRate: number,\n frameWidth: number,\n frameHeight: number,\n): string {\n if (!effects || effects.length === 0) return \"\";\n\n const filters: string[] = [];\n\n for (const effect of effects) {\n const filter = processEffect(effect, frameRate, frameWidth, frameHeight);\n filters.push(filter);\n }\n\n return filters.join(\",\");\n}\n","import type { ChildProcess } from \"child_process\";\nimport { spawn } from \"child_process\";\nimport type { Readable } from \"stream\";\nimport { pipeline } from \"stream\";\nimport fs from \"fs/promises\";\nimport os from \"os\";\nimport path from \"path\";\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 sourceResolver: (input: {\n type: FFmpegInput[\"type\"];\n src: string;\n }) => Promise<Readable>,\n imageTransformer?: (imageStream: Readable) => Promise<Readable>,\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 const sourceCache = new Map<string, Promise<string>>();\n\n const fetchAndSaveSource = async (\n input: FFmpegInput,\n inputName: string,\n ): Promise<string> => {\n const stream = await sourceResolver({\n type: input.type,\n src: input.source,\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 // Only cache #reference sources (not data URLs or regular URLs to avoid\n // memory issues with large URL strings as cache keys)\n const shouldCache = input.source.startsWith(\"#\");\n\n if (shouldCache) {\n let fetchPromise = sourceCache.get(input.source);\n if (!fetchPromise) {\n fetchPromise = fetchAndSaveSource(input, inputName);\n sourceCache.set(input.source, fetchPromise);\n }\n const filePath = await fetchPromise;\n fileMapping.set(input.index, filePath);\n } else {\n const filePath = await fetchAndSaveSource(input, inputName);\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 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 { 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\";\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\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\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 }\n\n private async fetchSource(src: string): Promise<Readable> {\n if (src.startsWith(\"#\")) {\n const sourceName = src.slice(1);\n if (!(sourceName in this.effieData.sources!)) {\n throw new Error(`Named source \"${sourceName}\" not found`);\n }\n src = this.effieData.sources![sourceName];\n }\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 // 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 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\n filterParts.push(\n `[${globalBgInputIdx}:v]fps=${this.effieData.fps},scale=${frameWidth}x${frameHeight},trim=start=${backgroundSeek + currentTime}:duration=${segment.duration},setpts=PTS-STARTPTS[${bgLabel}]`,\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 * 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 return this.ffmpegRunner.run(\n async ({ src }) => this.fetchSource(src),\n this.createImageTransformer(scaleFactor),\n );\n }\n\n close(): void {\n if (this.ffmpegRunner) {\n this.ffmpegRunner.close();\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"]}
|