---
title: Video reference
description: Composition model, SDK surfaces, DSL fields, and renderer options for Cuitty Video.
section: Video
order: 2
updatedAt: 2026-05-24
slug: video/reference
---
# Video reference

Cuitty Video has four core surfaces:

| Surface | Package | Purpose |
| --- | --- | --- |
| SDK | `@cuitty/video` | Build a composition in TypeScript and export Composition IR. |
| DSL | `@cuitty/video-dsl` | Parse YAML or JSON into the same Composition IR. |
| Renderer | `@cuitty/video-render` | Render frames with Playwright and encode with FFmpeg. |
| Player | `@cuitty/video-player` | Preview Composition IR in the browser. |

The CLI and server are thin workflow layers over those packages.

## Composition IR

Every composition resolves to the same shape:

| Field | Type | Notes |
| --- | --- | --- |
| `id` | `string` | Stable composition identifier. |
| `width` | `number` | Output width in pixels. |
| `height` | `number` | Output height in pixels. |
| `fps` | `number` | Frames per second. |
| `durationInFrames` | `number` | Timeline length after duration parsing. |
| `layers` | `LayerIR[]` | Ordered visual layers. |
| `audioTracks` | `AudioIR[]` | Optional audio tracks. |
| `transitions` | `TransitionIR[]` | Optional clip transitions. |

Layers contain clips. Clips define `startFrame`, `endFrame`, `renderType`, `renderProps`, and optional animation metadata.

## TypeScript SDK

Use the SDK when composition data comes from code:

```ts
import { composition, tween, Easing } from "@cuitty/video";
import { render } from "@cuitty/video-render";

const video = composition({
  id: "status-report",
  width: 1920,
  height: 1080,
  fps: 30,
  durationInFrames: 180,
});

video.layer("title").clip({
  startFrame: 0,
  endFrame: 120,
  render: (ctx) => {
    const opacity = tween(ctx.frame, [0, 30], [0, 1], {
      clamp: true,
      easing: Easing.out(Easing.cubic),
    });

    return `<h1 style="opacity:${opacity}">Weekly status</h1>`;
  },
});

await render({
  composition: video,
  outputPath: "./output/status-report.mp4",
  codec: "h264",
  crf: 18,
});
```

## YAML DSL

Use the DSL when a composition should be editable as data:

```yaml
composition:
  id: status-report
  width: 1920
  height: 1080
  fps: 30
  duration: 6s

layers:
  - id: title
    clips:
      - id: headline
        start: 0s
        end: 4s
        render:
          type: text
          text: "Weekly status"
          fontSize: 72
          color: "#ffffff"
        animate:
          - property: opacity
            from: 0
            to: 1
            start: 0s
            duration: 1s
            easing: cubic-out
```

The DSL accepts YAML or JSON. `duration`, `start`, and `end` can be frame numbers or time strings such as `500ms`, `2s`, or `00:00:02`.

## Render options

| Option | Default | Notes |
| --- | --- | --- |
| `outputPath` | Required | Final output file path. |
| `codec` | `h264` | Supports `h264`, `h265`, `vp9`, `prores`, and `gif`. |
| `crf` | `18` | Lower is higher quality for CRF-based codecs. |
| `concurrency` | CPU-dependent | Parallel frame rendering. |
| `onProgress` | None | Receives frame count, total frames, and render FPS. |

## Server workflow

The render server accepts composition payloads, creates jobs, reports status, and runs the same render pipeline behind an HTTP boundary. Use it when renders should be queued or called by another service instead of run directly in the CLI.

## Observe integration

Video render jobs can emit progress, failure, and encoding metadata into Observe. The standalone Video product owns authoring and rendering; the existing [Video module](/docs/modules/video) remains the Observe module for browser-session recording job metadata.