Export

The three export engines, which range gets exported, and the ffmpeg encoding profiles.

Export is the Export button → dialog, or fablecut_export from an agent (below). Three engines:

  1. Fast - browser renders each frame with the normal compositor (SVG, keys, AI masks), streams JPEGs + an offline WAV mix to the server; a single ffmpeg pass encodes them via an encoding profile into ./exports/. Quality / software path; keeps rendering if you switch tabs.
  2. WebCodecs - same frame-accurate compositor loop, but the browser’s VideoEncoder produces Annex-B H.264 (Main 4:2:0) and the server stream-copies (-c:v copy) while muxing the WAV. Faster uploads, less server CPU. Requires Chromium-class VideoEncoder with avc: { format: "annexb" } plus ffmpeg. Encoding profiles do not apply (the bitstream is already encoded). No ffmpeg-style CRF - quality is bitrate + VBR/CBR (export dialog; remembered in localStorage). Optional bitrateMode: "quantizer" (fixed QP) exists in the spec but is rarely supported by hardware encoders with Annex-B. Unavailable while an exportFrame crop is set - use Fast for cropped delivery.
  3. Realtime (MediaRecorder) - automatic offline fallback when the server, ffmpeg, or WebCodecs is unavailable. Plays the timeline once and records it; keep the tab focused.

Fast and WebCodecs export decode MP4 / MOV clips with WebCodecs (mp4-demux.js + VideoDecoder), frame-exact. Fragmented, rotated or undecodable files and bgRemove clips fall back to the <video> element.

The exported span is chosen in the Export dialog (Range: Entire timeline / IN–OUT). IN–OUT is the default when inPoint / outPoint are set; pick Entire timeline to keep those markers for split/trim. Effective IN/OUT export bounds are clamped to projDur so an IN past the last clip cannot produce a one-frame black file.

  1. Entire timeline (or no markers) → the whole timeline
  2. IN–OUT, IN only → from inPoint to the end
  3. IN–OUT, OUT only → from the start to outPoint
  4. IN–OUT, both → the range between them

Agents export with fablecut_export (or POST /api/export/request). It is the same Fast export as the button - same compositor, audio mix and encoding profile - so it needs ffmpeg on PATH. The job goes to an open editor tab (the user sees the progress bar), or, with none open, the server starts a headless Chrome / Edge on the editor and closes it when the file is written (FABLECUT_CHROME picks the browser). The tab first loads the revision the agent last wrote, so an export right after a patch includes that patch.

Delivery presets#

The Export dialog's Preset list. Each preset is a structured spec - no ffmpeg arguments to write - that delivery.js turns into one ffmpeg pass:

PresetOutput
youtube1920×1080, H.264 CRF 18, keyframe every 0.5 s, AAC 384k 48 kHz
youtube-shorts1080×1920, as YouTube; warns past 3 min
instagram-reels1080×1920, H.264 CRF 19 capped at 25 Mb/s, AAC 256k; ≤ 60 fps, warns past 3 min
tiktok1080×1920, H.264 CRF 19 capped at 20 Mb/s, AAC 256k; warns past 10 min
x1920×1080, H.264 CRF 20 capped at 25 Mb/s, AAC 192k; warns past 2:20 and 512 MB
linkedin1920×1080, H.264 CRF 20 capped at 20 Mb/s, AAC 192k; warns past 15 min and 5 GB
master (default)the picture's own size, H.264 CRF 16, AAC 320k
broadcast1920×1080 ProRes 422 HQ MOV, PCM 24-bit 48 kHz, Rec. 709, normalized to −23 LUFS / −1 dBTP (EBU R128); warns off 25 / 29.97 / 50 / 59.94 fps
web1280×720 H.264 CRF 23, AAC 128k, fast start
archivethe picture's own size, lossless FFV1 4:2:2 10-bit MKV, PCM 24-bit
customyour own settings

A spec (every key optional - what is left out comes from its preset):

{
  "preset": "youtube",
  "width": 1920, "height": 1080,   // null = the picture's own size (the export frame, else the canvas)
  "fps": null,                     // null = the project's; 23.976 / 29.97 / 59.94 are exact NTSC rates
  "reframe": "fill",               // another shape: fill crops the middle (or the export frame), fit pads, stretch
  "container": "mp4",              // mp4 · mov · mkv · webm · mxf
  "video": {
    "codec": "h264",               // h264 · hevc · av1 · vp9 · prores · dnxhr · mpeg2 (XDCAM HD422) · ffv1
    "encoder": "libx264",          // or h264_nvenc / h264_qsv / h264_amf … — listed only when they work here
    "rate": "crf",                 // crf · vbr · cbr (ProRes, DNxHR, XDCAM, FFV1 are fixed)
    "crf": 18, "bitrate": 12000, "maxrate": null,   // kb/s; maxrate caps a CRF or VBR encode
    "keyint": 2,                   // seconds between keyframes, 0 = the encoder's default
    "speed": "balanced",           // fast · balanced · best
    "pixFmt": "yuv420p",           // 4:2:0 / 4:2:2 / 4:4:4, 8 or 10 bit, as the codec allows
    "profile": null                // ProRes: proxy lt standard hq 4444 4444xq · DNxHR: lb sq hq hqx 444
  },
  "audio": { "codec": "aac", "bitrate": 320, "sampleRate": 48000, "channels": 2,
             "loudness": null, "truePeak": -1 },
                                   // aac · opus · mp3 · pcm16 · pcm24 · flac · none
                                   // loudness: a LUFS target (−14 streaming, −16 podcast, −23 EBU R128, −24 ATSC) or null
  "color": "bt709",                // bt709 · bt709-full · bt601-625 (PAL) · bt601-525 (NTSC)
  "filename": "{project}_{preset}_{res}",
  // tokens: {project} {preset} {res} (1080p, 1080x1920, 1080sq) {w} {h} {fps} {codec} {date} {time} {range} {n}
  "metadata": { "title": "", "artist": "", "description": "", "copyright": "", "date": "", "keywords": "" }
}

Batch outputs and the export queue#

One project → many files. project.outputs is the project's list of files to make (up to 16), each a delivery spec plus {id, on}. In the Export dialog, Batch outputs lists them: + Add these settings adds the preset shown above, + Typical set adds Master, YouTube 1080p, Reel 1080×1920, Square 1080 and a 720p preview, the checkbox picks which go out, Load shows one's settings above. Export checked queues the checked ones and starts.

// setProject {outputs:[…]} — a preset id or a spec each; stored complete with an id
{"op":"setProject","set":{"outputs":[
  "master", "youtube",
  {"preset":"instagram-reels"},
  {"preset":"custom","width":1080,"height":1080,"filename":"{project}_square_{res}"},
  {"preset":"web","filename":"{project}_preview_{res}","on":false}
]}}

Encoding profiles encoding-profiles.json#

User-editable at the repo root. A profile is a raw ffmpeg argument list plus the things that are not ffmpeg arguments: jpegQuality (browser frame quality), extension (output container), and optional color (output matrix / range tags). Edit the file while the server runs - the UI hot-reloads the profile list via an SSE profiles event (no full project reload). Fast export only; WebCodecs ignores them.

{
  "default": "delivery",           // profile id used when nothing else is set
  "profiles": {
    "draft": {
      "label": "Draft · H.264 fast",
      "description": "Quick preview — smaller file, faster encode.",
      "jpegQuality": 0.85,         // browser JPEG frame quality (0.1–1)
      "extension": ".mp4",
      // Output color (optional — defaults to BT.709 limited/tv). Independent of
      // -pix_fmt: yuv420p and yuv422p10le both typically use bt709 for HD SDR.
      "color": { "matrix": "bt709", "primaries": "bt709", "trc": "bt709", "range": "tv" },
      "args": ["-c:v", "libx264", "-preset", "veryfast", "-crf", "23",
               "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "128k",
               "-movflags", "+faststart", "-shortest"]
    }
  }
}

Export is one ffmpeg pass. The server owns the input side and the output path; args is everything in between (plus JPEG color conversion derived from color):

ffmpeg -y -f image2pipe -framerate <fps> -i - [-i audio.wav] <jpeg-color> <args…> exports/<name><extension>

Note: Fast export pipes composited JPEG frames from the browser (-f image2pipe), not -i source.mp4. Filters and codec settings apply to that frame stream. To transcode an existing file verbatim, run ffmpeg directly - that is outside the compositor path.

Encoding profiles are listed at the bottom of the Export dialog's Preset list.

What the Export dialog opens on (priority):

  1. project.delivery, then project.encodeProfile - set with setProject
  2. The last preset or profile used in this browser
  3. The master delivery preset

MCP: fablecut_encode_profiles lists profiles; {detail:true} includes each args array; {profile:"hq"} returns one profile. fablecut_status shows the effective profile.