Communitygithub.com

smwbev/framewright

Short videos entirely from code: one HTML file, every frame a pure function of (frame, seed, width). Claude/Codex/Gemini/Cursor.

O que é framewright?

framewright is a Claude Code agent skill that short videos entirely from code: one HTML file, every frame a pure function of (frame, seed, width). Claude/Codex/Gemini/Cursor.

Funciona com✓Claude Code✓Codex CLI✓Cursor✓Gemini CLI
npx skills add smwbev/framewright

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

O que framewright faz?

You are going to make a short video the way a printmaker makes a print: one HTML file with a canvas, a list of scenes called plates, and a frame function that draws any frame on demand from (frame number, seed, width). No footage, no image files, nothing from a CDN. Frames are rendered in headless Chrome and assembled by ffmpeg. Sound is synthesized last, to the locked scene lengths.

Talk to the user in their language. Keep the rules below even when the user is in a hurry: every shortcut here costs more than it saves.

SKILL_DIR below means the folder that contains this file (in Claude Code it is ${CLAUDE_SKILL_DIR}; elsewhere it is .agents/skills/framewright). After step 3 the scripts also live in the project's own scripts/ folder.

Non-negotiables

  1. A frame depends only on frame number, seed and output width. No Math.random, no wall clock, no CSS animation, no requestAnimationFrame as a time source. scripts/check.mjs proves it before every full render.
  2. No image, video, font or library files, no base64, no CDN. Polygons traced from a photo by a script are data and are fine. System fonts are enough.
  3. You verify by looking at rendered frames, never by reading code and imagining. Any fix is confirmed with a frame.
  4. One scene at a time. Write a plate, shoot three frames, look, fix, then the next plate.
  5. Scene lengths are multiples of the beat from the first draft. Sound comes last.
  6. Full render only after a contact sheet of the whole video looks right.
  7. Never commit frames/, shots/, *.mp4, *.wav or the user's photos.
  8. Ask before installing system packages. Never publish a real person's likeness without the user confirming they have the right to use it.

Workflow at a glance

StepWhat happensCheckpoint with the user
0environment checkonly if something must be installed
1briefquestionnaire, one block
2conceptsthree options, pick one
3storyboard + scaffoldone "go"
4scenes, one by onecontact sheet every 2–3 scenes
5assets (photos, logos)preview of the traced portrait
6full sheet, render, buildpreview MP4
7soundrebuilt MP4
8delivery and variationsfinal

Typical wall-clock: brief and concepts 10 minutes, scaffold 10, each scene 10–20, render 2–4 minutes per minute of 1080p video, sound 15.

Step 0. Environment

bash SKILL_DIR/scripts/doctor.sh            # report; add --json for a machine-readable summary
bash SKILL_DIR/scripts/doctor.sh --install  # installs what is missing, asks before each step

Required: Node 20+, npm, ffmpeg with libx264, Puppeteer with Chrome in the project. Optional: Python 3 with numpy, scipy, Pillow, only when a photo will be traced. If the doctor reports missing tools, tell the user what will be installed and where, get a yes, then run --install (add --yes when the user already agreed). Do not start step 3 with a failing doctor.

Step 1. The brief

Run the questionnaire from references/questionnaire.md. Use the harness's structured question tool when one exists (Claude Code: AskUserQuestion, up to four questions per call, two to four options each, put the recommended option first and mark it). Without such a tool, print the questions as a numbered list with lettered options and a default per question, and accept terse answers like 1b 2a 3c. Always ask, in this order:

  1. Message: what must the viewer understand or feel, in one sentence. Names, dates, links, exact phrases that must appear on screen.
  2. Where it plays and the format: 16:9, 9:16, 1:1, 4:5.
  3. Length: 10–15, 20–25, 30–40, 45–60 seconds.
  4. Tone: playful, deadpan, warm, epic, technical, dark.
  5. Style: let you propose (default) or a family from references/styles.md.
  6. Assets: photos, logos, screenshots. Files must be placed in the project folder; an image pasted into chat cannot be saved by you. Ask about rights for real people.
  7. Sound: synthesized, none, or room for the user's track (then ask its BPM).
  8. Deliverables and quality: draft or final, extra cuts (vertical, GIF, poster frame, seed variations), a cover as the first frame of the MP4 (feeds show it as the preview).

If the user says "you decide", take the defaults, say which ones you took, and move on.

Step 2. Concepts

Propose exactly three concepts before any code. Build them from three angles: literal (show the subject), metaphor (a visual system that stands for the situation: a TV test card for waiting, a blueprint for a plan, a terminal for a process), and genre parody (news bulletin, arcade attract mode, safety card, weather forecast, teletext). For each concept give: a name, a one-sentence logline, the visual system and palette, three to five key scenes in order, the ending, a sound sketch, why it fits, and its main risk. Present a compact table, recommend one, and ask the user to pick. The method and worked examples are in references/questionnaire.md. A concept whose logline is one journey without cuts (a line that never lifts, a flight from a detail out to a landscape) is built as one continuous world (references/world.md); say so in its row, because it changes how scenes are built and changed. A concept that is one view drawn, painted and brought to life (a pencil drawing that becomes a living oil painting, styles 13–16) is built as a painting film (references/painting.md); say so in its row too: its plates are chapters of time over one painting, and the scene is the work.

Step 3. Storyboard and scaffold

Fill storyboard.md (template in assets/storyboard.md): one row per plate, lengths in bars, the beat grid (30 fps, 120 BPM: beat 15 frames, bar 60), the second of the main event, the total. The total must equal the agreed length; when a scene is added later, another scene gives up a bar. Show the table and ask for a single go.

Then scaffold:

bash SKILL_DIR/scripts/init.sh              # index.html, scripts/, audio.mjs, storyboard.md, package.json
bash SKILL_DIR/scripts/init.sh . --world    # the same, but index.html is the one-world skeleton (no cuts)
bash SKILL_DIR/scripts/init.sh . --painting # the painting skeleton; audio.mjs and nature.mjs make nature sound
npm install                                 # puppeteer (Chrome comes from the cache or is downloaded once)
node scripts/look.mjs shot 0,30,60 1200 7

Open the PNGs with your image viewing tool and look at them. The skeleton's demo plate must render before you touch it. Then replace the demo plate with your first scene. In a painting film frame 0 is blank paper: shoot 30,82,200 (the drawing, the colour arriving, the living painting) instead, and replace the demo's scene block while the painting kit stays as it is.

Step 4. Scenes

Read references/guide.md once before the first scene. The essentials:

  • plate('name', {len: 2*BAR}, (S, R) => { ... }). S.t is progress 0..1, S.i the local frame, S.f the global frame, S.g the 2D context in logical coordinates (short side 1080). R is a generator stable for the whole plate; S.b re-seeds every three frames and gives a live line; S.nz changes every frame for noise. Seeds derive from the plate name.
  • All geometry in logical units, centred through CX, CY. Keep important content inside the central 92 % of the frame; in 9:16 also out of the top 15 % and the bottom 20 %, where the platform's buttons and captions sit.
  • Time inside a plate in beats: Math.floor(S.i/BEAT), span(S.i, a, b), ease.out(...). Never hard-code global frame numbers inside a scene. Something that carries on across cuts takes another plate's time by name, at('plate', beats); a moment the sound must hit goes into CUES and is read with cue('name').
  • Helpers live above the plates block, never between plates.
  • Transitions are drawn by the engine at plate edges (cutIn/cutOut flags); scenes do not know about them.
  • A world film (--world) has no cuts. Plates are builders, build(W, {pen, b, R}), that append to one timeline: b(x) is beat x of the plate, the pen continues the line from where the previous plate left it, camKey moves one camera, title adds text. Read references/world.md before the first builder.
  • A painting film (--painting) draws one still painting in three media. Its plates all call drawPainting(S); the view lives in the SCENE object and its layers between /* ===== scene ===== */ and /* ===== end scene ===== */, and the painting kit above it stays as it is. Read references/painting.md before the first layer. Review the drawing alone first, then the reveal, then life (references/painting.md, section 13).

Per scene: write it, shoot the first frame, one in the middle, one five frames before the end, look, fix, look again. After every two or three scenes:

node scripts/look.mjs sheet 24 480 7 shots/sheet.png
FROM=240 TO=480 node scripts/look.mjs sheet 12 480 7 shots/sheet-b.png   # one section, denser

Checklist for the sheet, each item with your eyes: every cell reads as a still in 1.5 seconds; no text is clipped; neighbouring lines and panels do not touch; two adjacent cells of one plate never look identical; cuts land where planned and do not eat the first frame of a scene; nothing important sits in the outer 8 %; colour is not muddy; thin lines do not crumble; the main event sits on its planned second; the last frame looks like an ending; no two titles share the screen in the same place. A scene that fails "1.5 seconds" gets redesigned, not decorated. An empty scene gets a bigger object or a closer camera, never more detail; a busy close-up gets its decor dimmed around the subject. In a world film also shoot the last frame of each plate next to the first frame of the next: nothing may jump.

Send the sheet to the user at these points with two sentences of status. Do not narrate code.

Step 5. Assets

Photos become posterized polygons, never pixels:

bash scripts/portrait.sh photo.jpg --levels 0.14,0.28,0.42,0.56,0.70,0.84 --height 900
node scripts/look.mjs shot <frame in the portrait scene> 1200 7

Look at shots/portrait_preview.png and at the rendered frame. Tune levels, blur, crop and background threshold per references/photo.md. While the file is not yet in the folder, build the scene on the synthetic placeholder that ships in the skeleton and say so.

Step 6. Full sheet, render, build

node scripts/look.mjs sheet 24 480 7 shots/sheet.png      # look at it, fix, repeat
node scripts/check.mjs                                    # determinism and self-containment, must say OK
node scripts/render.mjs frames 7 1920 5                   # dir, seed, width (left out: 1920 landscape, 1080 portrait), tabs
bash scripts/build.sh out.mp4                             # frames (+ track.wav) -> mp4

The check renders five frames of every plate in seven orders (first frame of a new tab, forward, backward, shuffled, right after the previous frame, after another width, after a sweep over the film the way a render tab walks it) and demands identical pixels. It also scans the source for Math.random, Date and performance.now, fails when the page requests any file, and prints the beat grid and the expected render time. Fix every FAIL before rendering; make.sh runs the check and stops on a failure. A frame that changes with render order points at state that survives a frame: a pooled canvas drawn without clearing, a global a plate changes, a cache keyed without all its inputs.

Verify from the file, not from the frames: ffprobe frame count equals the total from look.mjs info, duration equals frames divided by 30, and a tile made from the MP4 matches the sheet:

ffmpeg -i out.mp4 -vf "select='not(mod(n\,50))',scale=480:-1,tile=6x4" -frames:v 1 shots/out_sheet.png

Send the first MP4 to the user as soon as it exists, even with placeholders, and say what is a placeholder.

Step 7. Sound

scripts/export-curves.mjs writes curves.json from the page: plate starts, the cues, and for every frame in a world film the speed and screen position of the line's head, in a painting film the revealed shares, life, gust and zoom. audio.mjs takes its timeline from it, so it never drifts from the picture (a page without RISO.curves() needs the starts from look.mjs info copied into T by hand). Write one block of events per plate using the cue map in references/audio.md; hits on something the picture marks take their time from the page's cues, cue('name') (CUES, or W.cues in a world film), not from beat numbers copied out of a plate; in a world film give the line or the character a voice that follows the curves (follow(), references/audio.md, section 7). A painting or a landscape gets nature ambience instead of beats: wind, water, rustle and birds from nature.mjs, timed to the cues and to the page's gust by the audio.mjs that --painting copies from assets/audio-nature.mjs (references/audio.md, section 8, Nature ambience). Then:

node scripts/export-curves.mjs curves.json
node audio.mjs track.wav
ffmpeg -i track.wav -filter_complex "showwavespic=s=1800x300:split_channels=1" -frames:v 1 shots/wave.png
bash scripts/build.sh out.mp4

You cannot listen, so read the waveform: no accidental silence inside active scenes, no scene pinned to the ceiling. Whenever a scene length changes, regenerate the track.

Step 8. Delivery

Deliver out.mp4 (1920×1080, H.264, AAC, ~0.7 MB per second on noisy styles) and say how to rebuild: bash scripts/make.sh [photo.jpg] [seed] [width]. Offer, do not impose: another seed (a different impression of the same plates), a vertical cut (AR=9:16 node scripts/render.mjs frames-v 7 1080 5, then check a sheet with AR=9:16; lower titles sit at 70 % of the height there, TITLE_Y, clear of the platform's buttons; a painting film stretches its 16:9 scene and needs its own 9:16 layout, references/painting.md, section 11), a GIF for chats, a poster frame (look.mjs shot <frame> 1920), the HTML itself as a live preview. When the brief asks for the cover as the first frame, frame 0 must be a finished picture: no fade-in, the title already up, marks already drawn (references/world.md, section 7); shoot it at full size as the poster too. Remove intermediate MP4s so one result remains.

Changing things later

Insert a scene: add the plate, shorten another to keep the total, export the curves and regenerate the track, reshoot the sheet. Seeds derive from plate names, so neighbours do not change. Change a text: reshoot the frames that show it, check fit. Swap a photo: rerun portrait.sh, reshoot the portrait scene. In a world film a retimed plate moves later plates in time and the pen carries its end point over: reshoot the boundary frames (references/world.md, section 9). Every change ends with a sheet and a rebuilt MP4.

Reference files

  • references/questionnaire.md: the brief, wording for both structured and plain-chat modes, the concept generator with worked examples.
  • references/styles.md: sixteen visual systems with palettes, motion language, signature objects, post-processing recipes and sound palettes.
  • references/guide.md: the engine, helpers, timing grid, review protocol, render and encoding numbers, file layout.
  • references/world.md: one continuous world for films without cuts: builders, the pen and its line, the keyed camera, the light layer, occlusion and dimming, spotlight, titles, review and changes, sound from the picture.
  • references/painting.md: painting films: the scene contract, marks, the pencil drawing, reveals, life, water, the camera, caches, costs, review and sound.
  • references/audio.md: cue map, synthesis blocks, mastering, checks, voices that follow the picture, nature ambience (section 8).
  • references/photo.md: photo to polygons, parameters, rendering and animation of portraits.
  • references/troubleshooting.md: symptoms, causes, fixes, including environment traps.
  • assets/skeleton.html: the starting file. assets/world.html: the starting file of a world film. assets/painting.html: the starting file of a painting film (the painting kit and a demo scene). assets/audio-template.mjs: the sound toolkit. assets/nature.mjs: nature sound blocks; assets/audio-nature.mjs: a soundtrack built on them. scripts/export-curves.mjs: plate starts and per-frame curves for the sound.
  • ../../examples/ris-tv/ in the repository: a finished 40-second video with eight plates, sound and a portrait pipeline, to read as a worked example.
  • ../../examples/honeybee/ in the repository: a finished 56-second film in one take, the one-world method at full scale with sound driven by the line.
  • ../../examples/lake-dawn/ in the repository: a finished 15-second painting film, a mountain lake at sunrise drawn in pencil, painted and brought to life, with nature sound.

Habilidades Relacionadas