@effing/ffs 0.1.2 → 0.3.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 +138 -16
- package/dist/{chunk-RNE6TKMF.js → chunk-J64HSZNQ.js} +276 -207
- package/dist/chunk-J64HSZNQ.js.map +1 -0
- package/dist/chunk-XSCNUWZJ.js +935 -0
- package/dist/chunk-XSCNUWZJ.js.map +1 -0
- package/dist/handlers/index.d.ts +40 -7
- package/dist/handlers/index.js +10 -2
- package/dist/index.d.ts +23 -15
- package/dist/index.js +3 -9
- package/dist/proxy-qTA69nOV.d.ts +72 -0
- package/dist/server.js +853 -283
- 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 +0 -439
- package/dist/chunk-LK5K4SQV.js.map +0 -1
- package/dist/chunk-RNE6TKMF.js.map +0 -1
package/README.md
CHANGED
|
@@ -57,24 +57,29 @@ curl -X POST http://localhost:2000/render \
|
|
|
57
57
|
curl http://localhost:2000/render/123e4567-e89b-12d3-a456-426614174000 -o output.mp4
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
The server uses an internal HTTP proxy for video/audio URLs to ensure reliable DNS resolution in containerized environments (e.g., Alpine Linux). This is why you might see another server running on a random port.
|
|
61
|
+
|
|
60
62
|
#### Environment Variables
|
|
61
63
|
|
|
62
|
-
| Variable
|
|
63
|
-
|
|
|
64
|
-
| `FFS_PORT`
|
|
65
|
-
| `FFS_BASE_URL`
|
|
66
|
-
| `FFS_API_KEY`
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
|
|
77
|
-
|
|
64
|
+
| Variable | Description |
|
|
65
|
+
| -------------------------------- | ---------------------------------------------------- |
|
|
66
|
+
| `FFS_PORT` | Server port (default: 2000) |
|
|
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
|
+
|
|
82
|
+
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.
|
|
78
83
|
|
|
79
84
|
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.
|
|
80
85
|
|
|
@@ -267,6 +272,95 @@ Purges cached sources for a given Effie composition.
|
|
|
267
272
|
{ "purged": 3, "total": 5 }
|
|
268
273
|
```
|
|
269
274
|
|
|
275
|
+
### `POST /warmup-and-render`
|
|
276
|
+
|
|
277
|
+
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.
|
|
278
|
+
|
|
279
|
+
**Request:**
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
type WarmupAndRenderOptions = {
|
|
283
|
+
effie: EffieData | string; // EffieData object or URL to fetch from
|
|
284
|
+
scale?: number; // Scale factor (default: 1)
|
|
285
|
+
upload?: {
|
|
286
|
+
videoUrl: string; // Pre-signed URL to upload rendered video
|
|
287
|
+
coverUrl?: string; // Pre-signed URL to upload cover image
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Response:**
|
|
293
|
+
|
|
294
|
+
```json
|
|
295
|
+
{
|
|
296
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
297
|
+
"url": "http://localhost:2000/warmup-and-render/550e8400-e29b-41d4-a716-446655440000"
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### `GET /warmup-and-render/:id`
|
|
302
|
+
|
|
303
|
+
Executes the combined warmup and render job, streaming progress via SSE. All events are prefixed with `warmup:` or `render:` to indicate the phase.
|
|
304
|
+
|
|
305
|
+
**Events:**
|
|
306
|
+
|
|
307
|
+
| Event | Phase | Data |
|
|
308
|
+
| -------------------- | ------ | ----------------------------------------------------------------- |
|
|
309
|
+
| `warmup:start` | warmup | `{ "total": 5 }` |
|
|
310
|
+
| `warmup:progress` | warmup | `{ "url": "...", "status": "hit"\|"cached"\|"error", ... }` |
|
|
311
|
+
| `warmup:downloading` | warmup | `{ "url": "...", "status": "downloading", "bytesReceived": ... }` |
|
|
312
|
+
| `warmup:summary` | warmup | `{ "cached": 5, "failed": 0, "skipped": 0, "total": 5 }` |
|
|
313
|
+
| `warmup:complete` | warmup | `{ "status": "ready" }` |
|
|
314
|
+
| `render:started` | render | `{ "status": "rendering" }` |
|
|
315
|
+
| `keepalive` | both | `{ "phase": "warmup" }` or `{ "phase": "render" }` |
|
|
316
|
+
| `render:complete` | render | `{ "status": "uploaded", "timings": {...} }` (upload mode) |
|
|
317
|
+
| `complete` | - | `{ "status": "ready", "videoUrl": "..." }` (non-upload mode) |
|
|
318
|
+
| `error` | any | `{ "phase": "warmup"\|"render", "message": "..." }` |
|
|
319
|
+
|
|
320
|
+
**Without upload** — Returns a `videoUrl` pointing to `/render/:id` for streaming:
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
const events = new EventSource(url);
|
|
324
|
+
events.addEventListener("complete", (e) => {
|
|
325
|
+
const { videoUrl } = JSON.parse(e.data);
|
|
326
|
+
// Fetch videoUrl to stream the rendered video
|
|
327
|
+
events.close();
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**With upload** — Uploads directly and streams progress:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
const events = new EventSource(url);
|
|
335
|
+
events.addEventListener("render:complete", (e) => {
|
|
336
|
+
const { timings } = JSON.parse(e.data);
|
|
337
|
+
console.log("Uploaded!", timings);
|
|
338
|
+
events.close();
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Backend Separation
|
|
343
|
+
|
|
344
|
+
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).
|
|
345
|
+
|
|
346
|
+
**Environment variables:**
|
|
347
|
+
|
|
348
|
+
- `FFS_WARMUP_BACKEND_BASE_URL` — Base URL for warmup backend (e.g., `https://warmup.your.app`)
|
|
349
|
+
- `FFS_RENDER_BACKEND_BASE_URL` — Base URL for render backend (e.g., `https://render.your.app`)
|
|
350
|
+
|
|
351
|
+
**Behavior when set:**
|
|
352
|
+
|
|
353
|
+
| Endpoint | Effect |
|
|
354
|
+
| ---------------------------- | ---------------------------------------------------- |
|
|
355
|
+
| `POST /warmup` | Returns URL pointing to local server (orchestrator) |
|
|
356
|
+
| `GET /warmup/:id` | Proxies SSE from warmup backend |
|
|
357
|
+
| `POST /render` | Returns URL pointing to local server (orchestrator) |
|
|
358
|
+
| `GET /render/:id` | Proxies from render backend (SSE or video stream) |
|
|
359
|
+
| `POST /warmup-and-render` | Returns URL pointing to local server (orchestrator) |
|
|
360
|
+
| `GET /warmup-and-render/:id` | Proxies SSE from warmup backend, then render backend |
|
|
361
|
+
|
|
362
|
+
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.
|
|
363
|
+
|
|
270
364
|
## Examples
|
|
271
365
|
|
|
272
366
|
### Scale Factor for Previews
|
|
@@ -362,6 +456,34 @@ events.addEventListener("complete", (e) => {
|
|
|
362
456
|
});
|
|
363
457
|
```
|
|
364
458
|
|
|
459
|
+
**Warmup and render in one stream:**
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
const { url } = await fetch("http://localhost:2000/warmup-and-render", {
|
|
463
|
+
method: "POST",
|
|
464
|
+
headers: { "Content-Type": "application/json" },
|
|
465
|
+
body: JSON.stringify({ effie: effieData, scale: 0.5 }),
|
|
466
|
+
}).then((r) => r.json());
|
|
467
|
+
|
|
468
|
+
// Connect to SSE for combined progress
|
|
469
|
+
const events = new EventSource(url);
|
|
470
|
+
|
|
471
|
+
events.addEventListener("warmup:progress", (e) => {
|
|
472
|
+
const { url, status, cached, total } = JSON.parse(e.data);
|
|
473
|
+
console.log(`Warmup: ${cached}/${total} - ${url} ${status}`);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
events.addEventListener("render:started", () => {
|
|
477
|
+
console.log("Rendering started...");
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
events.addEventListener("complete", (e) => {
|
|
481
|
+
const { videoUrl } = JSON.parse(e.data);
|
|
482
|
+
console.log("Ready! Video at:", videoUrl);
|
|
483
|
+
events.close();
|
|
484
|
+
});
|
|
485
|
+
```
|
|
486
|
+
|
|
365
487
|
## Related Packages
|
|
366
488
|
|
|
367
489
|
- [`@effing/effie`](../effie) — Define video compositions
|