View raw

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:

  • Libraryimport { render } from '@clipkit/render-service'. Used by clipkit render --local and by your own Node services.
  • HTTP serverclipkit-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#

FieldTypeDefaultDescription
sourceSourcerequiredA schema-valid CKP/1.0 Source.
backend'auto' | 'webgpu' | 'webgl2''auto'Which compositor backend to force.
onProgress(frame, total) => voidundefinedCalled once per encoded frame.
timeoutMsnumber300_000Hard 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
  • RenderTimeoutError enforcement

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.