{
  "slug": "video/reference",
  "title": "Video reference",
  "description": "Composition model, SDK surfaces, DSL fields, and renderer options for Cuitty Video.",
  "url": "https://cuitty.com/docs/video/reference",
  "markdown_url": "https://cuitty.com/docs/video/reference.md",
  "json_url": "https://cuitty.com/docs/video/reference.json",
  "frontmatter": {
    "title": "Video reference",
    "description": "Composition model, SDK surfaces, DSL fields, and renderer options for Cuitty Video.",
    "order": 2,
    "section": "Video",
    "updatedAt": "2026-05-24"
  },
  "headings": [
    {
      "depth": 1,
      "slug": "video-reference",
      "text": "Video reference"
    },
    {
      "depth": 2,
      "slug": "composition-ir",
      "text": "Composition IR"
    },
    {
      "depth": 2,
      "slug": "typescript-sdk",
      "text": "TypeScript SDK"
    },
    {
      "depth": 2,
      "slug": "yaml-dsl",
      "text": "YAML DSL"
    },
    {
      "depth": 2,
      "slug": "render-options",
      "text": "Render options"
    },
    {
      "depth": 2,
      "slug": "server-workflow",
      "text": "Server workflow"
    },
    {
      "depth": 2,
      "slug": "observe-integration",
      "text": "Observe integration"
    }
  ],
  "body_markdown": "# Video reference\n\nCuitty Video has four core surfaces:\n\n| Surface | Package | Purpose |\n| --- | --- | --- |\n| SDK | `@cuitty/video` | Build a composition in TypeScript and export Composition IR. |\n| DSL | `@cuitty/video-dsl` | Parse YAML or JSON into the same Composition IR. |\n| Renderer | `@cuitty/video-render` | Render frames with Playwright and encode with FFmpeg. |\n| Player | `@cuitty/video-player` | Preview Composition IR in the browser. |\n\nThe CLI and server are thin workflow layers over those packages.\n\n## Composition IR\n\nEvery composition resolves to the same shape:\n\n| Field | Type | Notes |\n| --- | --- | --- |\n| `id` | `string` | Stable composition identifier. |\n| `width` | `number` | Output width in pixels. |\n| `height` | `number` | Output height in pixels. |\n| `fps` | `number` | Frames per second. |\n| `durationInFrames` | `number` | Timeline length after duration parsing. |\n| `layers` | `LayerIR[]` | Ordered visual layers. |\n| `audioTracks` | `AudioIR[]` | Optional audio tracks. |\n| `transitions` | `TransitionIR[]` | Optional clip transitions. |\n\nLayers contain clips. Clips define `startFrame`, `endFrame`, `renderType`, `renderProps`, and optional animation metadata.\n\n## TypeScript SDK\n\nUse the SDK when composition data comes from code:\n\n```ts\nimport { composition, tween, Easing } from \"@cuitty/video\";\nimport { render } from \"@cuitty/video-render\";\n\nconst video = composition({\n  id: \"status-report\",\n  width: 1920,\n  height: 1080,\n  fps: 30,\n  durationInFrames: 180,\n});\n\nvideo.layer(\"title\").clip({\n  startFrame: 0,\n  endFrame: 120,\n  render: (ctx) => {\n    const opacity = tween(ctx.frame, [0, 30], [0, 1], {\n      clamp: true,\n      easing: Easing.out(Easing.cubic),\n    });\n\n    return `<h1 style=\"opacity:${opacity}\">Weekly status</h1>`;\n  },\n});\n\nawait render({\n  composition: video,\n  outputPath: \"./output/status-report.mp4\",\n  codec: \"h264\",\n  crf: 18,\n});\n```\n\n## YAML DSL\n\nUse the DSL when a composition should be editable as data:\n\n```yaml\ncomposition:\n  id: status-report\n  width: 1920\n  height: 1080\n  fps: 30\n  duration: 6s\n\nlayers:\n  - id: title\n    clips:\n      - id: headline\n        start: 0s\n        end: 4s\n        render:\n          type: text\n          text: \"Weekly status\"\n          fontSize: 72\n          color: \"#ffffff\"\n        animate:\n          - property: opacity\n            from: 0\n            to: 1\n            start: 0s\n            duration: 1s\n            easing: cubic-out\n```\n\nThe DSL accepts YAML or JSON. `duration`, `start`, and `end` can be frame numbers or time strings such as `500ms`, `2s`, or `00:00:02`.\n\n## Render options\n\n| Option | Default | Notes |\n| --- | --- | --- |\n| `outputPath` | Required | Final output file path. |\n| `codec` | `h264` | Supports `h264`, `h265`, `vp9`, `prores`, and `gif`. |\n| `crf` | `18` | Lower is higher quality for CRF-based codecs. |\n| `concurrency` | CPU-dependent | Parallel frame rendering. |\n| `onProgress` | None | Receives frame count, total frames, and render FPS. |\n\n## Server workflow\n\nThe 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.\n\n## Observe integration\n\nVideo 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.",
  "body_html": "<h1 id=\"video-reference\">Video reference</h1>\n<p>Cuitty Video has four core surfaces:</p>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<table><thead><tr><th>Surface</th><th>Package</th><th>Purpose</th></tr></thead><tbody><tr><td>SDK</td><td><code>@cuitty/video</code></td><td>Build a composition in TypeScript and export Composition IR.</td></tr><tr><td>DSL</td><td><code>@cuitty/video-dsl</code></td><td>Parse YAML or JSON into the same Composition IR.</td></tr><tr><td>Renderer</td><td><code>@cuitty/video-render</code></td><td>Render frames with Playwright and encode with FFmpeg.</td></tr><tr><td>Player</td><td><code>@cuitty/video-player</code></td><td>Preview Composition IR in the browser.</td></tr></tbody></table>\n<p>The CLI and server are thin workflow layers over those packages.</p>\n<h2 id=\"composition-ir\">Composition IR</h2>\n<p>Every composition resolves to the same shape:</p>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<table><thead><tr><th>Field</th><th>Type</th><th>Notes</th></tr></thead><tbody><tr><td><code>id</code></td><td><code>string</code></td><td>Stable composition identifier.</td></tr><tr><td><code>width</code></td><td><code>number</code></td><td>Output width in pixels.</td></tr><tr><td><code>height</code></td><td><code>number</code></td><td>Output height in pixels.</td></tr><tr><td><code>fps</code></td><td><code>number</code></td><td>Frames per second.</td></tr><tr><td><code>durationInFrames</code></td><td><code>number</code></td><td>Timeline length after duration parsing.</td></tr><tr><td><code>layers</code></td><td><code>LayerIR[]</code></td><td>Ordered visual layers.</td></tr><tr><td><code>audioTracks</code></td><td><code>AudioIR[]</code></td><td>Optional audio tracks.</td></tr><tr><td><code>transitions</code></td><td><code>TransitionIR[]</code></td><td>Optional clip transitions.</td></tr></tbody></table>\n<p>Layers contain clips. Clips define <code>startFrame</code>, <code>endFrame</code>, <code>renderType</code>, <code>renderProps</code>, and optional animation metadata.</p>\n<h2 id=\"typescript-sdk\">TypeScript SDK</h2>\n<p>Use the SDK when composition data comes from code:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"ts\"><code><span class=\"line\"><span style=\"color:#F97583\">import</span><span style=\"color:#E1E4E8\"> { composition, tween, Easing } </span><span style=\"color:#F97583\">from</span><span style=\"color:#9ECBFF\"> \"@cuitty/video\"</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">import</span><span style=\"color:#E1E4E8\"> { render } </span><span style=\"color:#F97583\">from</span><span style=\"color:#9ECBFF\"> \"@cuitty/video-render\"</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#79B8FF\"> video</span><span style=\"color:#F97583\"> =</span><span style=\"color:#B392F0\"> composition</span><span style=\"color:#E1E4E8\">({</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  id: </span><span style=\"color:#9ECBFF\">\"status-report\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  width: </span><span style=\"color:#79B8FF\">1920</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  height: </span><span style=\"color:#79B8FF\">1080</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  fps: </span><span style=\"color:#79B8FF\">30</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  durationInFrames: </span><span style=\"color:#79B8FF\">180</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">});</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">video.</span><span style=\"color:#B392F0\">layer</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"title\"</span><span style=\"color:#E1E4E8\">).</span><span style=\"color:#B392F0\">clip</span><span style=\"color:#E1E4E8\">({</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  startFrame: </span><span style=\"color:#79B8FF\">0</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  endFrame: </span><span style=\"color:#79B8FF\">120</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">  render</span><span style=\"color:#E1E4E8\">: (</span><span style=\"color:#FFAB70\">ctx</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">=></span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">    const</span><span style=\"color:#79B8FF\"> opacity</span><span style=\"color:#F97583\"> =</span><span style=\"color:#B392F0\"> tween</span><span style=\"color:#E1E4E8\">(ctx.frame, [</span><span style=\"color:#79B8FF\">0</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">30</span><span style=\"color:#E1E4E8\">], [</span><span style=\"color:#79B8FF\">0</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">1</span><span style=\"color:#E1E4E8\">], {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">      clamp: </span><span style=\"color:#79B8FF\">true</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">      easing: Easing.</span><span style=\"color:#B392F0\">out</span><span style=\"color:#E1E4E8\">(Easing.cubic),</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    });</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">    return</span><span style=\"color:#9ECBFF\"> `&#x3C;h1 style=\"opacity:${</span><span style=\"color:#E1E4E8\">opacity</span><span style=\"color:#9ECBFF\">}\">Weekly status&#x3C;/h1>`</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  },</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">});</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">await</span><span style=\"color:#B392F0\"> render</span><span style=\"color:#E1E4E8\">({</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  composition: video,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  outputPath: </span><span style=\"color:#9ECBFF\">\"./output/status-report.mp4\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  codec: </span><span style=\"color:#9ECBFF\">\"h264\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  crf: </span><span style=\"color:#79B8FF\">18</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">});</span></span></code></pre>\n<h2 id=\"yaml-dsl\">YAML DSL</h2>\n<p>Use the DSL when a composition should be editable as data:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"yaml\"><code><span class=\"line\"><span style=\"color:#85E89D\">composition</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">  id</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">status-report</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">  width</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#79B8FF\">1920</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">  height</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#79B8FF\">1080</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">  fps</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#79B8FF\">30</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">  duration</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">6s</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#85E89D\">layers</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  - </span><span style=\"color:#85E89D\">id</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">title</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">    clips</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">      - </span><span style=\"color:#85E89D\">id</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">headline</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">        start</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">0s</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">        end</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">4s</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">        render</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">          type</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">text</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">          text</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"Weekly status\"</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">          fontSize</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#79B8FF\">72</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">          color</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"#ffffff\"</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">        animate</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">          - </span><span style=\"color:#85E89D\">property</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">opacity</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">            from</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#79B8FF\">0</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">            to</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#79B8FF\">1</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">            start</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">0s</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">            duration</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">1s</span></span>\n<span class=\"line\"><span style=\"color:#85E89D\">            easing</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">cubic-out</span></span></code></pre>\n<p>The DSL accepts YAML or JSON. <code>duration</code>, <code>start</code>, and <code>end</code> can be frame numbers or time strings such as <code>500ms</code>, <code>2s</code>, or <code>00:00:02</code>.</p>\n<h2 id=\"render-options\">Render options</h2>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<table><thead><tr><th>Option</th><th>Default</th><th>Notes</th></tr></thead><tbody><tr><td><code>outputPath</code></td><td>Required</td><td>Final output file path.</td></tr><tr><td><code>codec</code></td><td><code>h264</code></td><td>Supports <code>h264</code>, <code>h265</code>, <code>vp9</code>, <code>prores</code>, and <code>gif</code>.</td></tr><tr><td><code>crf</code></td><td><code>18</code></td><td>Lower is higher quality for CRF-based codecs.</td></tr><tr><td><code>concurrency</code></td><td>CPU-dependent</td><td>Parallel frame rendering.</td></tr><tr><td><code>onProgress</code></td><td>None</td><td>Receives frame count, total frames, and render FPS.</td></tr></tbody></table>\n<h2 id=\"server-workflow\">Server workflow</h2>\n<p>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.</p>\n<h2 id=\"observe-integration\">Observe integration</h2>\n<p>Video render jobs can emit progress, failure, and encoding metadata into Observe. The standalone Video product owns authoring and rendering; the existing <a href=\"/docs/modules/video\">Video module</a> remains the Observe module for browser-session recording job metadata.</p>",
  "links_out": [
    "/docs/modules/video"
  ]
}