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-filmMIT · Node 20+ · installs straight from the repository
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.
0.40s0010 1.20s0029 2.00s0048 2.90s0070 3.60s0086 4.60s0110 5.60s0134 6.80s0163 7.80s0187 8.70s0209 9.60s0230 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.
- 01Read the timestamp.It arrives as ?t= on the query string, in seconds.
- 02Draw that moment, synchronously.Set properties from t rather than leaving CSS transitions to their own clock.
- 03Say when the frame is final.Stamp data-seekreel-ready on the document and the shutter goes.
<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.
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 clockNever 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.
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.
"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.
$ 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
{
"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 }
]
}