@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.
- package/README.md +140 -16
- package/dist/{chunk-6YHSYHDY.js → chunk-7FMPCMLO.js} +559 -215
- package/dist/chunk-7FMPCMLO.js.map +1 -0
- package/dist/{chunk-A7BAW24L.js → chunk-J64HSZNQ.js} +65 -46
- package/dist/chunk-J64HSZNQ.js.map +1 -0
- package/dist/handlers/index.d.ts +38 -4
- package/dist/handlers/index.js +10 -2
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1 -1
- package/dist/{proxy-BI8OMQl0.d.ts → proxy-qTA69nOV.d.ts} +11 -7
- package/dist/server.js +660 -293
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-6YHSYHDY.js.map +0 -1
- package/dist/chunk-A7BAW24L.js.map +0 -1
package/README.md
CHANGED
|
@@ -61,22 +61,27 @@ The server uses an internal HTTP proxy for video/audio URLs to ensure reliable D
|
|
|
61
61
|
|
|
62
62
|
#### Environment Variables
|
|
63
63
|
|
|
64
|
-
| Variable
|
|
65
|
-
|
|
|
66
|
-
| `FFS_PORT`
|
|
67
|
-
| `FFS_BASE_URL`
|
|
68
|
-
| `FFS_API_KEY`
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
|
|
79
|
-
|
|
64
|
+
| Variable | Description |
|
|
65
|
+
| -------------------------------- | ---------------------------------------------------- |
|
|
66
|
+
| `FFS_PORT` | Server port (default: 2000, falls back to `PORT`) |
|
|
67
|
+
| `FFS_BASE_URL` | Base URL for returned URLs |
|
|
68
|
+
| `FFS_API_KEY` | API key for authentication (optional) |
|
|
69
|
+
| `FFS_TRANSIENT_STORE_BUCKET` | S3 bucket for transient store (enables S3 mode) |
|
|
70
|
+
| `FFS_TRANSIENT_STORE_ENDPOINT` | S3-compatible endpoint (for e.g. R2 or MinIO) |
|
|
71
|
+
| `FFS_TRANSIENT_STORE_REGION` | AWS region (default: "auto") |
|
|
72
|
+
| `FFS_TRANSIENT_STORE_PREFIX` | Key prefix for stored objects |
|
|
73
|
+
| `FFS_TRANSIENT_STORE_ACCESS_KEY` | S3 access key ID |
|
|
74
|
+
| `FFS_TRANSIENT_STORE_SECRET_KEY` | S3 secret access key |
|
|
75
|
+
| `FFS_TRANSIENT_STORE_LOCAL_DIR` | Local storage directory (when not using S3) |
|
|
76
|
+
| `FFS_SOURCE_CACHE_TTL_MS` | TTL for cached sources in ms (default: 60 min) |
|
|
77
|
+
| `FFS_JOB_METADATA_TTL_MS` | TTL for job metadata in ms (default: 8 hours) |
|
|
78
|
+
| `FFS_WARMUP_CONCURRENCY` | Concurrent source fetches during warmup (default: 4) |
|
|
79
|
+
| `FFS_WARMUP_BACKEND_BASE_URL` | Separate backend for warmup (see Backend Separation) |
|
|
80
|
+
| `FFS_RENDER_BACKEND_BASE_URL` | Separate backend for render (see Backend Separation) |
|
|
81
|
+
| `FFS_WARMUP_BACKEND_API_KEY` | API key for authenticating to the warmup backend |
|
|
82
|
+
| `FFS_RENDER_BACKEND_API_KEY` | API key for authenticating to the render backend |
|
|
83
|
+
|
|
84
|
+
When `FFS_TRANSIENT_STORE_BUCKET` is not set, FFS uses the local filesystem for storage (default: system temp directory). Local files are automatically cleaned up after the TTL expires.
|
|
80
85
|
|
|
81
86
|
For S3 storage, the TTL is set as the `Expires` header on objects. Note that this is metadata only. To enable automatic deletion, configure [S3 lifecycle rules](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html) on your bucket to delete expired objects.
|
|
82
87
|
|
|
@@ -269,6 +274,97 @@ Purges cached sources for a given Effie composition.
|
|
|
269
274
|
{ "purged": 3, "total": 5 }
|
|
270
275
|
```
|
|
271
276
|
|
|
277
|
+
### `POST /warmup-and-render`
|
|
278
|
+
|
|
279
|
+
Creates a combined warmup and render job that runs both phases in a single SSE stream. This is useful when you want to warmup sources and render in one request.
|
|
280
|
+
|
|
281
|
+
**Request:**
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
type WarmupAndRenderOptions = {
|
|
285
|
+
effie: EffieData | string; // EffieData object or URL to fetch from
|
|
286
|
+
scale?: number; // Scale factor (default: 1)
|
|
287
|
+
upload?: {
|
|
288
|
+
videoUrl: string; // Pre-signed URL to upload rendered video
|
|
289
|
+
coverUrl?: string; // Pre-signed URL to upload cover image
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Response:**
|
|
295
|
+
|
|
296
|
+
```json
|
|
297
|
+
{
|
|
298
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
299
|
+
"url": "http://localhost:2000/warmup-and-render/550e8400-e29b-41d4-a716-446655440000"
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### `GET /warmup-and-render/:id`
|
|
304
|
+
|
|
305
|
+
Executes the combined warmup and render job, streaming progress via SSE. All events are prefixed with `warmup:` or `render:` to indicate the phase.
|
|
306
|
+
|
|
307
|
+
**Events:**
|
|
308
|
+
|
|
309
|
+
| Event | Phase | Data |
|
|
310
|
+
| -------------------- | ------ | ----------------------------------------------------------------- |
|
|
311
|
+
| `warmup:start` | warmup | `{ "total": 5 }` |
|
|
312
|
+
| `warmup:progress` | warmup | `{ "url": "...", "status": "hit"\|"cached"\|"error", ... }` |
|
|
313
|
+
| `warmup:downloading` | warmup | `{ "url": "...", "status": "downloading", "bytesReceived": ... }` |
|
|
314
|
+
| `warmup:summary` | warmup | `{ "cached": 5, "failed": 0, "skipped": 0, "total": 5 }` |
|
|
315
|
+
| `warmup:complete` | warmup | `{ "status": "ready" }` |
|
|
316
|
+
| `render:started` | render | `{ "status": "rendering" }` |
|
|
317
|
+
| `keepalive` | both | `{ "phase": "warmup" }` or `{ "phase": "render" }` |
|
|
318
|
+
| `render:complete` | render | `{ "status": "uploaded", "timings": {...} }` (upload mode) |
|
|
319
|
+
| `complete` | - | `{ "status": "ready", "videoUrl": "..." }` (non-upload mode) |
|
|
320
|
+
| `error` | any | `{ "phase": "warmup"\|"render", "message": "..." }` |
|
|
321
|
+
|
|
322
|
+
**Without upload** — Returns a `videoUrl` pointing to `/render/:id` for streaming:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
const events = new EventSource(url);
|
|
326
|
+
events.addEventListener("complete", (e) => {
|
|
327
|
+
const { videoUrl } = JSON.parse(e.data);
|
|
328
|
+
// Fetch videoUrl to stream the rendered video
|
|
329
|
+
events.close();
|
|
330
|
+
});
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
**With upload** — Uploads directly and streams progress:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
const events = new EventSource(url);
|
|
337
|
+
events.addEventListener("render:complete", (e) => {
|
|
338
|
+
const { timings } = JSON.parse(e.data);
|
|
339
|
+
console.log("Uploaded!", timings);
|
|
340
|
+
events.close();
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Backend Separation
|
|
345
|
+
|
|
346
|
+
FFS supports running warmup and render on separate backends, useful for scaling or resource isolation. When backend URLs are configured, the cache storage must be shared between services (e.g., using S3).
|
|
347
|
+
|
|
348
|
+
**Environment variables:**
|
|
349
|
+
|
|
350
|
+
- `FFS_WARMUP_BACKEND_BASE_URL` — Base URL for warmup backend (e.g., `https://warmup.your.app`)
|
|
351
|
+
- `FFS_RENDER_BACKEND_BASE_URL` — Base URL for render backend (e.g., `https://render.your.app`)
|
|
352
|
+
|
|
353
|
+
**Behavior when set:**
|
|
354
|
+
|
|
355
|
+
| Endpoint | Effect |
|
|
356
|
+
| ---------------------------- | ---------------------------------------------------- |
|
|
357
|
+
| `POST /warmup` | Returns URL pointing to local server (orchestrator) |
|
|
358
|
+
| `GET /warmup/:id` | Proxies SSE from warmup backend |
|
|
359
|
+
| `POST /render` | Returns URL pointing to local server (orchestrator) |
|
|
360
|
+
| `GET /render/:id` | Proxies from render backend (SSE or video stream) |
|
|
361
|
+
| `POST /warmup-and-render` | Returns URL pointing to local server (orchestrator) |
|
|
362
|
+
| `GET /warmup-and-render/:id` | Proxies SSE from warmup backend, then render backend |
|
|
363
|
+
|
|
364
|
+
All GET endpoints proxy requests to the configured backend, keeping backend URLs hidden from clients. This ensures compatibility with EventSource (which doesn't follow redirects) and simplifies CORS configuration since only the orchestrator needs to be publicly accessible.
|
|
365
|
+
|
|
366
|
+
If the backends have `FFS_API_KEY` set, configure `FFS_WARMUP_BACKEND_API_KEY` and/or `FFS_RENDER_BACKEND_API_KEY` on the orchestrator so it can authenticate when proxying requests. The orchestrator sends these as `Authorization: Bearer <key>` headers.
|
|
367
|
+
|
|
272
368
|
## Examples
|
|
273
369
|
|
|
274
370
|
### Scale Factor for Previews
|
|
@@ -364,6 +460,34 @@ events.addEventListener("complete", (e) => {
|
|
|
364
460
|
});
|
|
365
461
|
```
|
|
366
462
|
|
|
463
|
+
**Warmup and render in one stream:**
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
const { url } = await fetch("http://localhost:2000/warmup-and-render", {
|
|
467
|
+
method: "POST",
|
|
468
|
+
headers: { "Content-Type": "application/json" },
|
|
469
|
+
body: JSON.stringify({ effie: effieData, scale: 0.5 }),
|
|
470
|
+
}).then((r) => r.json());
|
|
471
|
+
|
|
472
|
+
// Connect to SSE for combined progress
|
|
473
|
+
const events = new EventSource(url);
|
|
474
|
+
|
|
475
|
+
events.addEventListener("warmup:progress", (e) => {
|
|
476
|
+
const { url, status, cached, total } = JSON.parse(e.data);
|
|
477
|
+
console.log(`Warmup: ${cached}/${total} - ${url} ${status}`);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
events.addEventListener("render:started", () => {
|
|
481
|
+
console.log("Rendering started...");
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
events.addEventListener("complete", (e) => {
|
|
485
|
+
const { videoUrl } = JSON.parse(e.data);
|
|
486
|
+
console.log("Ready! Video at:", videoUrl);
|
|
487
|
+
events.close();
|
|
488
|
+
});
|
|
489
|
+
```
|
|
490
|
+
|
|
367
491
|
## Related Packages
|
|
368
492
|
|
|
369
493
|
- [`@effing/effie`](../effie) — Define video compositions
|