Skip to content
seekreel

Ship the film, not the screen recording.

seekreel turns the animation you already built in the browser into finished video — every ratio, format and frame rate your feed asks for, cut from one render, with every frame drawn exactly as you designed it. Same result on your laptop and in CI.

  • Launch films
  • Feature reels
  • Store listings
npx github:highnet/seekreel init my-film

MIT · Node 20+ · installs straight from the repository

ONE FRAMEPER SEEK.000048096144192240frame 011 / 2884%
t = 5.208s · frame 125 / 28800:05.05 · 24fps · 1080×1080

Drag the scrubber: every moment you land on is exactly the frame the render would write to disk.

Re-cut one shot. Leave the other 287 alone.

Changed your mind about second eight? Render second eight. Check any single moment as a PNG in about a second — seekreel probe 3.25 — before committing to a full pass. Your film stops being a take you have to get right and starts being a file you edit.

  • ONE FRAMEPER SEEK.frame 000 / 2880%
    0.40s0010
  • ONE FRAMEPER SEEK.frame 000 / 2880%
    1.20s0029
  • ONE FRAMEPER SEEK.frame 000 / 2880%
    2.00s0048
  • ONE FRAMEPER SEEK.000048frame 000 / 2880%
    2.90s0070
  • ONE FRAMEPER SEEK.000048096144frame 000 / 2880%
    3.60s0086
  • ONE FRAMEPER SEEK.000048096144192240frame 001 / 2880%
    4.60s0110
  • ONE FRAMEPER SEEK.000048096144192240frame 029 / 28810%
    5.60s0134
  • ONE FRAMEPER SEEK.000048096144192240frame 177 / 28861%
    6.80s0163
  • ONE FRAMEPER SEEK.000048096144192240frame 270 / 28894%
    7.80s0187
  • ONE FRAMEPER SEEK.000048096144192240frame 288 / 288100%H.264
    8.70s0209
  • ONE FRAMEPER SEEK.000048096144192240frame 288 / 288100%H.264
    9.60s0230
  • ONE FRAMEPER SEEK.seekreeldeliver/reel.mp4000048096144192240frame 288 / 288100%H.264
    11.20s0269

If it runs in a browser, it ships as video.

No timeline app to learn, no export plugin, no runtime to install. Your page takes a timestamp and draws that moment. Three rules, and seekreel does the rest.

  1. 01Read the timestamp.It arrives as ?t= on the query string, in seconds.
  2. 02Draw that moment, synchronously.Set properties from t rather than leaving CSS transitions to their own clock.
  3. 03Say when the frame is final.Stamp data-seekreel-ready on the document and the shutter goes.
stage.html
<div id="box"></div>
<script>
  const t = parseFloat(new URLSearchParams(location.search).get("t") || "0");
  const span = (a, b) => Math.min(1, Math.max(0, (t - a) / (b - a)));

  box.style.opacity = span(0, 1);
  box.style.transform = `translateY(${40 * (1 - span(0, 1))}px)`;

  document.documentElement.setAttribute("data-seekreel-ready", "1");
</script>

Bring your own GSAP timeline

Keep the easing, the staggers and the choreography you already wrote. Build the timeline paused, seek it once, and it renders — just skip callbacks, which a seek passes over.

stage.js
const tl = gsap.timeline({ paused: true });
tl.from("#card", { y: 24, opacity: 0, duration: 0.9, ease: "expo.out" }, 0.1)
  .to("#bar i", { scaleX: 1, duration: 3.4, ease: "power1.inOut" }, 1.2)
  .to(".tray", { opacity: 1, duration: 0.5, stagger: 0.13 }, 6.0);

tl.time(t); // the only line that touches the clock

Never re-record because a font loaded late.

A recording keeps whatever your machine managed to draw that afternoon: dropped frames, a late font, a scroll two pixels off. Render instead and the file is the same every time — which also means it can rebuild itself in CI whenever the page changes.

The trade is speed, and we would rather say it up front: a page load and a screenshot per frame, so budget about a second each. A 43-second film takes roughly twenty minutes. Worth it for something you cut once and re-cut in pieces; wrong for anything interactive.

above · screen recording, 3 frames droppedbelow · seekreel, one frame per timestamp

One render. Every cut the feed asks for.

Ask for 9:16 and the canvas changes shape while the picture stays exactly where you put it — padded in your own background colour, or cropped if you say cover. Name a size instead when a platform wants pixels. Drop the frame rate for a gif. Rendering is the slow part, so it happens once and every deliverable is cut from the same frames.

mp4
H.264 + AAC. The one every feed accepts.
webm
VP9 + Opus, for autoplay on your own site.
gif
Palette-mapped, for a README or a changelog.
mov
ProRes 422 HQ, for an edit timeline.
16:99:164:51:1one render
seekreel.config.json
"variants": [
  { "name": "" },
  { "name": "4x5",    "ratio": "4:5" },
  { "name": "story",  "ratio": "9:16" },
  { "name": "og",     "size": "1200x628", "fit": "cover" },
  { "name": "web",    "format": "webm" },
  { "name": "loop",   "format": "gif", "fps": 12 },
  { "name": "silent", "audio": false }
]

From empty folder to finished cut.

Audio, then every frame, then ffmpeg — one command from a page to the files you upload.

terminal
$ seekreel build
  • soundtrack.wav
  • out/*.png
  • deliver/reel.mp4
  • deliver/reel-4x5.mp4

Sound you can move by half a second.

Cues are synthesized, not sampled — no library to licence, no clearance to chase, and no re-editing audio when a shot shifts. Change the number, rebuild, done. A looping bed sits under one-off hits placed by time, and all it needs is python3 and its standard library.

  • marimba
  • bass
  • pad
  • swell
  • chime
  • pip
  • tick
  • shutter
  • pop
  • whoosh
  • stamp
  • tray
  • shaker
cues.json
{
  "duration": 6,
  "bed": {
    "tempo": 100,
    "progression": [{ "tones": ["D3", "F3", "A3", "C4"], "root": "D2" }],
    "dense": [1.2, 5.2]
  },
  "cues": [
    { "at": 0.1, "sound": "swell", "note": "F2", "dur": 1.4 },
    { "at": 3.0, "sound": "pop" },
    { "at": 4.2, "sound": "chime", "pan": -0.2 }
  ]
}