$ npm i -g filmless

Stop screen-recording
your demos.

Record a real session in your web app once. filmless captures the DOM, not pixels — so you can retime the boring parts, zoom the camera on clicks, and re-render a crisp README GIF from the same take, forever.

Star on GitHub

This demo is not a video

It's the recorded DOM session replaying live in your browser — the same data the GIF is rendered from. Drag the sliders: you're editing the reel in real time.

Every knob here is a number in reel.json. Change it, re-render, get the exact same take with new direction.

Edit a recording like code

One JSON file is the whole edit: what to speed up, where the camera looks.

  1. record — a real browser session, captured as DOM events via rrweb. Data, not pixels.
  2. editreel.json declares retiming and camera keyframes. filmless suggest drafts it for you from your clicks and idle gaps.
  3. render — headless Chrome steps the replay frame by frame (pure function of time), ffmpeg packs a palette-optimized GIF. Byte-identical on every run.
{
  "session": "session.json",
  "retime": [
    { "from": 4200, "to": 9800, "speed": 4 },
    { "from": 12000, "to": 15000, "cut": true }
  ],
  "camera": [
    [0,    960, 540, 1.0 ],
    [1200, 960, 540, 1.0 ],   // hold wide
    [1700, 640, 380, 1.5 ],   // zoom to the click
    [3500, 640, 380, 1.5 ]    // hold
  ],
  "cursor": { "smooth": 120, "clickRipples": true }
}

When to use what

reproducibleeditable after the takeworks on
vhs yes — scriptedyes — it's a scriptterminal apps
screen recorders no — re-shoot every changevideo editing onlyanything
Screen Studio no — still a pixel recordingzoom/cursor, in-appmacOS
filmless yes — DOM replay, deterministicyes — JSON, versionableweb apps

Honest limitations

filmless replays the DOM, so anything that never enters the DOM never renders: