Render service#
@clipkit/render-service is the headless rendering backend for Clipkit. It drives a Playwright-controlled Chromium that loads @clipkit/runtime and exports MP4 via the same WebCodecs path the playground uses — so what you see in the browser is exactly what comes out of the renderer.
Two entry points:
- Library —
import { render } from '@clipkit/render-service'. Used byclipkit render --localand by your own Node services. - HTTP server —
clipkit-render-service serve --port 4000. The same surface the hosted API at clipkit.dev runs.
License#
Business Source License 1.1 (BSL 1.1). Free for all non-competing-service use — local development, internal company tools, hosted apps you build on top, customer-facing products. Converts to Apache-2.0 four years after each release.
The license is intentionally narrow: it prevents reselling Clipkit as a video render API. It does not prevent anything else. See /LICENSING.md in the repo root for the full rationale.
Install#
npm install @clipkit/render-service npx playwright install chromium # one-time, ~300 MB
Playwright's Chromium is required — the renderer is browser-driven, not pure-Node. The download is one-time per machine.
Library usage#
import { render } from '@clipkit/render-service'; import { writeFile } from 'node:fs/promises'; const result = await render({ source: { /* a CKP/1.0 Source */ }, backend: 'auto', onProgress: (frame, total) => process.stdout.write(`\r${frame}/${total}`), timeoutMs: 5 * 60_000, }); await writeFile('out.mp4', result.buffer);
render(options) options#
| Field | Type | Default | Description |
|---|---|---|---|
source | Source | required | A schema-valid CKP/1.0 Source. |
backend | 'auto' | 'webgpu' | 'webgl2' | 'auto' | Which compositor backend to force. |
onProgress | (frame, total) => void | undefined | Called once per encoded frame. |
timeoutMs | number | 300_000 | Hard timeout. Throws RenderTimeoutError if exceeded. |
Return value#
interface RenderResult { buffer: Uint8Array; // MP4 bytes mimeType: 'video/mp4'; width: number; height: number; duration: number; // seconds frame_rate: number; frame_count: number; elapsed_ms: number; }
Server usage#
clipkit-render-service serve --port 4000 --backend auto
POST /render#
Request body: a CKP/1.0 Source as JSON.
curl -X POST http://localhost:4000/render \ -H 'content-type: application/json' \ --data @video.json \ -o out.mp4
Success: MP4 bytes inline with Content-Type: video/mp4 and the result metadata in response headers (X-Clipkit-Frames, X-Clipkit-Elapsed-Ms).
Failure: application/json with { error, code, details? } and an appropriate 4xx/5xx status. Schema validation failures return 422 with the offending JSON paths.
GET /health#
Returns ok (200) once Chromium has been warmed up and is reachable.
Backends#
The runtime negotiates WebGPU → WebGL2 automatically. The backend option forces a specific choice:
'auto'(default) — try WebGPU, fall back to WebGL2.'webgl2'— skip WebGPU. Use on servers where WebGPU is unstable.'webgpu'— refuse to fall back. Render fails if WebGPU isn't available.
On Linux servers without a real GPU: Chromium runs WebGPU under SwiftShader (software). That's ~2–5× slower than realtime. If render speed matters, deploy on a machine with an actual GPU.
Assets#
v1 supports HTTP(S) URLs for all asset fields (source on video, image, audio elements). Local file paths fail with a clear error.
Local-file asset resolution is on the roadmap — see ROADMAP.md §4 in the repo.
What's in v1, what isn't#
Shipped:
- ✅ Library + server, sync mode
- ✅ One Chromium per render (no pool)
- ✅ HTTP(S) assets
- ✅ Buffer / response-body output
- ✅ Schema pre-validation
- ✅
RenderTimeoutErrorenforcement
Deferred:
- ⏳ Async / queued mode (BullMQ + Redis)
- ⏳ Browser pool
- ⏳ Storage backends (S3 / R2)
- ⏳ Local-file asset resolution
- ⏳ Auth / multi-tenant isolation
- ⏳ Streaming MP4 output
- ⏳ OpenTelemetry / Prometheus metrics
- ⏳ Drain-on-shutdown
If a deferred item blocks you, open an issue — the order is driven by what users actually need.