Communitygithub.com

thativanng/reels-maker

Build and render a film, reel or animation as code. One HTML page draws any frame on demand with window.seek(t), and a headless browser renders every frame at 2x into an MP4 with sound. Use it whenever someone wants a video made with code rather than a video generator, says "make a film / reel / animated explainer / career video", "render this to MP4", "turn this page into a video", or needs the finishing steps (audio cues, loudness, colour-safe encode, phone preview). It is style-agnostic and renders 9:16, 4:5, 1:1 or 16:9. It runs after reel-intake (the brief) and reel-style (the look), builds on template/starter.html or a style pack such as voxel-walk, and passes film-audit before anything is shown to anyone.

What is reels-maker?

reels-maker is a Claude Code agent skill that build and render a film, reel or animation as code. One HTML page draws any frame on demand with window.seek(t), and a headless browser renders every frame at 2x into an MP4 with sound. Use it whenever someone wants a video made with code rather than a video generator, says "make a film / reel / animated explainer / career video", "render this to MP4", "turn this page into a video", or needs the finishing steps (audio cues, loudness, colour-safe encode, phone preview). It is style-agnostic and renders 9:16, 4:5, 1:1 or 16:9. It runs after reel-intake (the brief) and reel-style (the look), builds on template/starter.html or a style pack such as voxel-walk, and passes film-audit before anything is shown to anyone.

Works with~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/thativanng/reels-maker/tree/HEAD/skills/film-engine

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

film-engine: films drawn from code, one frame at a time

Where the kit lives: run every command in this skill from the reels-maker folder (the clone of github.com/thativanng/reels-maker that holds template/ and tools/). If the project has none, set it up first by following that repo's SETUP.md.

The idea in one paragraph

A film here is one HTML page with one rule: window.seek(t) must draw exactly the frame for time t, every time, from nothing but t. Nothing in the picture may read a real clock. A renderer then steps t at 30 fps, screenshots each frame at 2x and pipes it to ffmpeg. Because the page is deterministic, a re-render matches (on average to within a fraction of a level; GPU noise can move single pixels by up to about 60 levels), a still at 17.3 s is the frame the video will show, and every check (film-audit) can look at any moment without playing the film.

The route (do not skip a station)

  1. Brief and look first. reel-intake ends in a signed brief and storyboard (format, length, sound, what never appears); reel-style ends in a picked look. Don't open this skill's build steps without both.
  2. Storyboard. reel-intake writes it (time · on-screen words · sound and caption · why; references/brief.md), and the person signs it before you build. A style with a banner adds a banner column.
  3. Test strip. Build about the first 10 s in the chosen look (a walk-style film may need 15–30 s to show one chapter), take stills, get a yes. A full build on an unsigned look is wasted work.
  4. Build the full film (the style pack says how to build scenes).
  5. Gates (film-audit): the collision check at 40 fps, the pixel probes, then an independent auditor. Nothing goes to the person before this.
  6. Render: node tools/render.mjs --page reels/<name>/page.html --out reels/<name>/out/film-silent.mp4 (2x frames, scaled down with lanczos, no colour tags).
  7. Audio: python3 template/audio.py out/film.wav (a cue list on the same clock as the film).
  8. Finish: zsh tools/mux.sh out/film-silent.mp4 out/film.wav out/film.mp4. This gives −14 LUFS, a limiter, an untagged H.264 file and a phone preview the same shape as the film (short side 720 px).
  9. Cover: node tools/cover.mjs --page reels/<name>/page.html --out reels/<name>/out/cover.jpg. A page with a cover mode (the starter's ?cover, from config cover) draws the designed still; any other page gives the frame at T=. It is saved in the film's own shape and kept under 2 MB, plus the centre 3:4 crop for a 9:16 film (the Instagram grid, reported). LinkedIn takes a cover only at posting time: afterwards only a post's text can be edited (LinkedIn Help, "Edit a shared post", https://www.linkedin.com/help/linkedin/answer/a522811). Its thumbnail spec, JPG or PNG in the video's shape and at most 2 MB, comes from LinkedIn's help as quoted by a search summary; the page itself would not load, so treat it as unverified.
  10. Send the phone preview first, then the full file and the cover.

The page contract

  • window.seek(T): draws frame T. It is the only place that animates anything.
  • window.ready: a promise that resolves after fonts, textures and the first frame.
  • window.DUR: the film's length in seconds.
  • window.FILM = { w, h, format, safe }: the frame size and the keep-clear zone in pixels. Every tool sizes its window from this, so the page owns its format.
  • Optional: window.cover() draws the cover instead of a frame, and FILM.coverAt names the moment it sits on (tools/cover.mjs uses both; without them it takes the frame at T=).
  • window.collide(T) and window._dbg: hooks for film-audit (see that skill).
  • The tools serve the kit folder over http on their own (ES modules don't load from file://). To watch a page live in a browser, npm run serve and open http://127.0.0.1:8765/template/starter.html.

Formats, the starter, and other languages

  • template/formats.json holds the four sizes (9:16 1080×1920 · 4:5 1080×1350 · 1:1 1080×1080 · 16:9 1920×1080), the platform → format map, and each platform's keep-clear zone with its source. The brief's format line picks from it.
  • Every reel lives in its own folder, reels/<name>/ (gitignored): its brief, storyboard, page.html (a copy of the starter or of film.html), its reel.json or career.json, and its out/. Pages load fonts and formats.json from the kit's root, so a copy works from there. Point the tools at it: PAGE=reels/<name>/page.html.
  • template/starter.html + template/reel.json is the skeleton for every look except the voxel walk. Its words and timings live in the config, it sizes itself from ?fmt= or the config, and it keeps every word inside the platform's keep-clear core. ?guides shows the covered zones in red, for checking only; never render with it on. It is a skeleton, not a look: reel-style rewrites its tokens and layout.
  • template/film.html (the voxel template) is 4:5 only, and it opts out of keep-clear zones (safe is 0): its banner and subtitles sit near the edges by design, so the safe probe always passes there and the auditor's eye is the check. Its cameras are keyed for 4:5; another ratio means re-keying them and re-running every gate.
  • Chinese, Japanese, Korean: the starter loads Noto Sans TC from Google Fonts at render time (only the characters the config uses), and runs CJK type at about 0.72× the Latin size (their characters are a full em wide). Write \n where a line should break: Chinese breaks between any two characters, so a long line needs a break you chose. template/reel.zh.json is a worked example. Any other script needs its own font in the stack.

Clocks and timing

  • One film clock drives the scenes, the camera, the banner, the subtitles and the sound. When you insert a beat later without re-timing everything, use a remap: the scene clock pauses at the insert point while the overlays keep the real clock:
    const INS = [[24.0, 5.0, 'detail']];   // at scene time 24.0, play a 5 s inserted beat
    function remap(T) { let sh = 0; for (const [a, len, id] of INS) { if (T < a + sh) return { t: T - sh, ins: null }; if (T < a + sh + len) return { t: a, ins: id, u: T - sh - a, len }; sh += len; } return { t: T - sh, ins: null }; }
    
    The audio script needs the same table: write an RT(t) that adds the inserted lengths to a scene time, and place every cue at RT(t), or every cue after the insert drifts. (The template has no inserts, so its audio.py doesn't need one.)
  • Easing: eio for camera moves, eo for things arriving, trap (trapezoid speed) for walking and driving, so a mover doesn't "snap" at the ends.
  • Camera keys are absolute A(pos, target) or follow F(offset, targetOffset) from the hero's ground point. Blend between keys with eio. Hold on a beat that matters; a shot that moves all the time reads as nervous.

The overlays (in DOM, above the canvas)

  • Banner: year, then company, then the role pill, plus a timeline whose dot breathes on the current year. The banner changes when the new scene is ON SCREEN (its set has risen, the outfit has changed, the hero has arrived), never when the code starts building it. Check this on a frame sheet at ±1 s around every change.
  • Subtitles: one line of story per beat, with the key phrase in bold accent. Use text-wrap: balance. Give a reader about 3 words a second, and fade a subtitle out before a fast camera move uncovers the subject behind it.
  • A speech bubble that follows a character: project the head point and clamp the bubble ≥24 px inside the frame.
  • The end card: name, one tagline, what they're good at, one quote. The scene blurs behind it.

Audio

  • Cues are written on the film clock, next to the moment they belong to (steps per walk leg, a chime when a thing appears, a thud when it lands).
  • No whoosh sounds on transitions; they read as cheap. Use chimes, ticks and soft impacts.
  • The default bed in template/audio.py is generated from sine waves, so it's free to use anywhere. If someone brings an AI-generated track, check its licence: some models' weights only allow non-commercial use.
  • Loudness: two-pass loudnorm to −14 LUFS (measure, then apply linearly), then a limiter. One pass overshoots.

Render facts that cost us days (more in references/gotchas.md)

  • Render at 2x and scale down. EffectComposer drops the canvas anti-aliasing, so a 1x render has jagged edges everywhere.
  • No colour tags on the MP4. A JPEG pipe tags it as full-range, and some players then shift the colours. setparams=...=unknown in the filter.
  • Two faces in one plane flicker (z-fighting). Give the front one a depth offset.
  • Fonts must be loaded (document.fonts.load) before any canvas text is drawn, or it silently falls back.
  • GPU on in headless Chromium (--use-angle=metal on macOS). Without it a render is about 10x slower.

Working without a prompt at every step

The person said yes to the kit's allow rules once (SETUP.md step 4); keep inside them, or they get asked again and again:

  • Write and change files with the Write and Edit tools: pages, reel.json, brief, storyboard, AUDIT.md. Don't generate them with a Python or shell script, a heredoc or sed -i; each of those is an approval prompt.
  • Measure with the kit's tools (stills, probe, sheet, collide, render, cover), not with one-off python3 -c snippets.
  • No mkdir, loops or chains needed: every tool creates its own output folder, and takes a list of times in one call (stills.mjs … 1.5,6,12).
  • A reel's own scripts (its sound script, a helper) live in reels/<name>/ and run as python3 reels/<name>/audio.py or node reels/<name>/x.mjs, which the rules cover.

Commands

Run each tool as one plain command (flags, no leading VAR=…, no loops, no && chains). Then the person's allow rules (node tools/*) cover it and they aren't asked to approve every step. The environment variables still work for scripts.

npm run doctor                                  # checks this machine first
npm run demo                                    # the 12-second proof: out/demo/
node tools/stills.mjs "2,8.5,15"                # 2x stills → out/stills (the voxel template)
node tools/stills.mjs --page template/starter.html --format 16:9 1.6        # the starter at another ratio
node tools/stills.mjs --page template/starter.html --cfg reel.zh.json 1.6   # another config (here: Chinese)
node tools/stills.mjs --dsf 1 --out out/chk 2,8.5                           # quick 1x frames for a sheet
python3 tools/sheet.py out/chk/sheet.jpg 6 300 out/chk/t*.jpg
node tools/render.mjs --page reels/<name>/page.html --out reels/<name>/out/film-silent.mp4
python3 template/audio.py out/film.wav
zsh tools/mux.sh out/film-silent.mp4 out/film.wav out/film.mp4

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

thativanng/reels-maker

The gates every code-rendered film passes before anyone sees it. A 40 fps collision pass (no mover touches a solid or another mover), pixel probes (nothing sits over or behind a face, a head, a subtitle or the frame edge), then an independent auditor (a fresh agent) who reads frame sheets at ≥2 fps and signs off. The builder never signs off their own film. Use it before sending ANY render, after every fix round, and whenever someone says "check the film", "audit this", "is it ready", or "why does this frame look wrong".

thativanng/reels-maker

The interview before any video is built. It reads what the person already gave (source text, CV, links, brand files), then asks the rest in a few short rounds of numbered questions with a recommended answer marked, always including the format (platform, reel or video, ratio, layout, length, sound). It ends in a one-screen brief that splits "you said" from "I assumed", plus a storyboard, and builds nothing until the person says yes. Use it whenever someone asks for a reel, a video, an explainer, a career film, an animation or a "make a video about X", before film-engine or any style pack, and again when a request changes its purpose, platform or audience mid-way.

thativanng/reels-maker

Picks the look of a reel or film before it is built. It shows three genuinely different looks as stills of the same opening frame with the same words, so the person judges the look and not the content. It keeps a log of every look ever shown, so no look comes back unless asked for, and it locks one look on purpose for a series. Use it after reel-intake's brief is signed and before any test strip, whenever someone says "what should it look like", "give me options", "make it look different from last time", "it looks cheap", or starts a new reel or a new series.

thativanng/reels-maker

The voxel career-film style. A blocky 3D version of the person walks, without ever vanishing, through one scene per role. Their outfit changes as they walk, each scene rises out of the floor ahead of them, and a dark banner tracks year, company and role. Use it when someone wants their career, a journey or a "how I got here" told as a short 3D film for LinkedIn or a portfolio, says "make my career film", "turn my CV into a video", "voxel film", or wants a new chapter added to an existing one. It runs on film-engine (render, audio, finish) and must pass film-audit before anything is sent.

Related Skills