cappy.

Capture presets

A preset in cappy.config.json names the OBS scene to expect and the derivatives FFmpeg makes from each verified master. --preset <name> (-p) selects one; defaultPreset applies otherwise.

"presets": {
  "trailer": {
    "scene": "Capture",
    "derivatives": [
      { "kind": "mp4", "role": "delivery" },
      { "kind": "clip", "role": "moment", "options": {
        "start": { "event": "SPELL_CAST", "offset": -2, "where": { "spell": "fireball" } },
        "end": { "event": "IMPACT", "offset": 1 } } },
      { "kind": "still", "role": "impact", "options": { "at": { "event": "IMPACT" } } },
      { "kind": "thumbnail", "role": "thumb", "required": false, "options": { "at": 2 } }
    ]
  }
}

Every option is validated before the game launches. An invalid option fails with DERIVATIVE_OPTIONS_INVALID (exit 3), and every problem is listed.

Derivatives

Kind Output Options
mp4 <role>.mp4 (H.264/AAC, faststart) crf (0-51, default 20), preset (x264 preset, default medium), audio (default true)
clip <role>.mp4 start (required), and exactly one of duration (seconds) or end, plus the mp4 options
thumbnail <role>.jpg at (default 0), width in pixels (default 640, aspect ratio kept)
still <role>.png (full resolution) at (default 0)

role names the output and its manifest entry, and must be unique within the preset. A derivative is required unless it sets "required": false. A failed required derivative fails the capture but keeps the verified master. A failed optional one becomes a warning.

start, end, and at are either seconds from the start of the master, or an event anchor.

Event anchors

An anchor places a time relative to an event on the capture's timeline, instead of at a fixed second:

{ "event": "IMPACT", "offset": 0.5, "occurrence": 2, "where": { "target.kind": "boss" } }
Field Meaning
event Event type (required). Any timeline event works, including Cappy's SCENARIO_STARTED, REPLAY_STARTED, and *_COMPLETED.
offset Seconds after the event. Negative means before it. Default 0.
occurrence Which match to use: 1 for the first (the default), 2 for the second, and so on, "last", or "every" (see below).
where Payload conditions. Each key is a dot-separated path into the event's payload ("target.kind"), and each value is a string, number, boolean, or null. Every condition must match exactly, so 3 does not match "3".

How anchors resolve:

Presentation

presentation asks the game to show a capture differently, without changing what happens. It applies to scenario captures and replay captures:

"slowmo": { "presentation": { "timeScale": 0.5, "camera": "close" } }

Cappy checks those capabilities after connecting and before recording (CAPABILITY_MISSING). The manifest records the presentation as identity.presentation. Event times are what is on screen, so anchors and clips line up with the slowed-down video. The simulation time rides along in each event's payload as simT. cappy compare warns when two captures were presented differently.

One output per event

occurrence: "every" on a clip's start, or on a still's or thumbnail's at, makes one output per matching event, numbered in timeline order:

{ "kind": "clip", "role": "hit", "options": {
  "start": { "event": "HIT", "occurrence": "every", "offset": -0.5 },
  "end": { "event": "HIT", "offset": 1 } } }

This produces hit-1.mp4, hit-2.mp4, and so on.

Event times carry the recorder synchronization uncertainty recorded in the manifest (timing.sync.uncertaintyMs). Cappy does not pad for it; widen offset if you need margin.

Setup prompt

Read the guide