Log in
TechniqueBy HypernaturalVersion 1, updated

hn4a-compositor

hn/hn4a-compositor

The base for everything drawn with hn4a-compositor, hn4a's own compositor: a JSON layer spec (fills, images, video, text, SVG paths, groups that work as cameras or lie on a surface) played live in a browser page and rendered frame-exact to an MP4 with the audio mixed in. Holds the spec format, the keyframes, the commands, the first look in the chosen direction or a few relevant candidate treatments, the one gate every compositor job keeps (the person approves the live spec, then you render), re-rendering only a changed span, and the checks. The base every hn4a-compositor-* leaf loads; which of them fits the job (overlay motion graphics, ad titles, in-scene titles, hand-reactive titles, a product screen cut up or rebuilt, ad and explainer cameras, a news chyron, a storyboard, kinetic lyrics, an audio waveform, graphics on a surface in perspective) is picked in guide-add-titles-and-motion-graphics. Not Remotion, no build step.

SKILL.md

Give it a JSON layer spec: a canvas, fonts, an audio list and bottom-first layers of fills, images, video, text and SVG paths, each with a box, CSS, entrance and exit presets and keyframes. Get back a frame-exact H.264 MP4 at the spec's size and rate with its audio mixed in. The same runtime plays the spec live in a browser page, so what the person previews is what renders. This is the base leaf every hn4a-compositor-* leaf loads: the format, the commands, the first look, the live-spec gate and the checks. The craft of each kind of job is in its own leaf, and guide-add-titles-and-motion-graphics picks which one (Routes).

Inputs

  • Word times for anything cued to speech: python-read-word-timings on the exact voice track the spec plays. Before there is a voice, word times estimated from the script stand in for it, for the first look only (references/first-look.md).
  • Footage at its real path; long-GOP sources are proxied for you. ProRes, 4444 included, fails as "video failed": convert it with hn4a-compositor proxy, which keeps the alpha. A cutout needs a real alpha channel (birefnet-matte: VP9 WebM yuva420p).
  • Fonts: a Google Fonts CSS URL with display=block, or local font files.

The spec

One file per composition, in the job folder (slides/<name>.json), asset paths relative to it:

{
  "size": [1920, 1080], "fps": 30, "duration": 6, "background": "#000",
  "fonts": [{"css": "https://fonts.googleapis.com/css2?family=Inter:wght@500;700&display=block"}],
  "audio": [{"src": "../audio/track.wav", "start": 0, "trim": 0, "volume": 1}],
  "layers": [
    {"id": "bg", "type": "fill", "fill": "#1B2E24"},
    {"id": "heading", "type": "text",
     "runs": ["Three signs to ", {"text": "listen for.", "style": {"color": "#B8E62E"}}],
     "box": {"x": 120, "y": 170, "w": 1400, "h": 120},
     "style": {"font": "700 96px/1.05 Inter", "color": "#fff"},
     "start": 0.8, "end": 5.5, "in": {"fx": "fade", "dur": 0.6}, "out": {"fx": "fade", "dur": 0.4}}
  ]
}
  • Layer types: fill, image, video, text, path, group. Layers draw bottom first; start/end are comp seconds; box is canvas px; style is any CSS in camelCase.
  • Presets for in/out: fade, rise, pop, zoom, wipe, draw, cut.
  • anim keyframes per property, times relative to the layer's start: {"scale": [[0, 0.8], [0.3, 1, "out"]]}. Animatable: opacity, x, y, scale, rotate, blur, reveal, draw, radius, box, crop, a group's plane.
  • A group holds the layers whose parent is its id. Keyed, it is a camera over them; with plane it lays them on a surface in the footage (hn4a-compositor-perspective-planes).
  • Video: trim is the source time at the layer's start; crop picks a source region. Video layers are muted: the sound goes in audio.

Every field, the per-type options and the eases: references/spec-format.md. Writing the spec from a build script that cues it on a voice take, so a new take is a re-run: references/build-script.md. How fast the camera moves for each kind of piece, and a presenter moving between full frame, window and bubble, are layout craft: hn4a-compositor-motion-graphics.

Run it

Run through exec with cwd the job folder:

exec hn4a-compositor page   slides/slide_b.json                    # slide_b.html: play, scrub, frame-step
exec hn4a-compositor still  slides/slide_b.json stills/b_22.9.png --at 22.9
exec hn4a-compositor render slides/slide_b.json [out.mp4]          # --workers 8 (default), --format jpeg|png, --no-audio, --no-proxy
exec hn4a-compositor render slides/slide_b.json --frames 690:990 --into slides/slide_b.mp4
exec hn4a-compositor proxy  mattes/presenter_cutout.webm           # short-GOP copy by hand; keeps alpha

page, still and render rewrite the .html page from the JSON, so edit the JSON, never the HTML. On the page, Space plays with audio, , . step a frame, ?t=20.5 opens at a time.

The first look

At the workflow's look step, prove the opening scene or its important graphic relationship before building the whole composition. If the treatment is undecided, compare two or three relevant looks with review_choose; if the person supplied or approved one, develop that direction and show it with review_approve. Each .json spec plays live. Combine this with the workflow's design gate rather than add another review. Word times estimated from the script let this start before the voice exists; existing footage or a music-led piece uses its own cues. How: references/first-look.md.

Live spec, then render

Every compositor job keeps this gate; workflows name it "the live spec":

  1. Write the spec and make a still inside every scene or effect, tiled on one sheet (python-contact-sheet), and look at it once. Check the longest copy: text boxes don't shrink text, so a long heading wraps or overflows ("whiteSpace": "nowrap" forces one line). Fix what's broken; your look catches mistakes, it doesn't approve the design.
  2. review_approve with the spec as the subject's file (it plays live in the card, free, in seconds) and the stills; get ready on the type, colour, placement and entrances, and end your turn until it comes back.
  3. Only then render. Notes go into the build script and back as the next live spec.

Revisions re-render only the span that changed. --frames A:B --into FULL.mp4 (A:B the frames the edit touches, the layer's start..end × fps, each rounded once: ffmpeg-frame-exact-trim, The frame grid) widens the span to FULL's keyframes (at most a second each side), renders it, splices it in by stream copy keeping FULL's audio, and checks every frame outside the span is unchanged. A 10 s span of a 163 s comp: about 15 s render plus 0.4 s splice. A change to duration, fps or audio needs a whole render (--into refuses a frame-count mismatch).

What you get

An H.264 MP4, CRF 16, BT.709-tagged, with a keyframe on every whole second (which is what lets --into splice by stream copy), and the spec's audio mixed by ffmpeg, never captured, so it can't drift. Speed: about 13 fps with one browser, 35 fps with 8, when every video layer seeks fast.

Tips and traps

  • Fonts fail loudly. render and still abort on any warning, such as a font family with no loaded face. The first family named in each text layer's own font (not its html's) must be loaded in fonts: a layer with no family of its own falls back to Times and aborts.
  • A child's box defaults to the canvas, not its group: give a fill inside a group the group's own box.
  • Your own anim replaces the preset for the properties it sets: an anim.opacity skips the fade of both in and out, so write the whole curve.
  • The runtime owns transform and filter on every layer. Put mirrors on an inner element of the html, and use text-shadow, not filter: drop-shadow, for glow. Perspective is a plane group (hn4a-compositor-perspective-planes).
  • Images inside a text layer's html render blank. They show in the preview but not in render or still. Use an image layer, or inline the file as a data: URI.
  • Write words as html spans joined by spaces, not runs, when the gaps matter: runs collapsed the spaces between caption words.
  • Zooms on footage go in ffmpeg, not here. An animated crop on a video layer left the picture short of its box. Render the move into the base video (ffmpeg-eased-punch-in) and draw graphics over it.
  • Sound lives in audio, an entry with start, trim and volume but no length: cut a piece of a file to its own file first (references/build-script.md), on the same frame grid as the picture when it must line up with a cut (ffmpeg-frame-exact-trim, The frame grid).
  • The mix's channels follow its inputs. One mono voice file made a whole render mono and lost a sound panned left to right. Write every audio input as stereo.
  • Long-GOP video is proxied automatically into .proxies/ next to the spec when its keyframes are more than 12 frames apart: an H3 Max talking head went from 9 to 34 fps render speed, SSIM 0.998. --no-proxy renders from the sources.
  • Comparing a render to a source by pixels: the render is BT.709; decode both with their own matrices, or greens and skin look shifted.
  • The runtime seeks video half a frame in (frame k at (k + 0.5)/fps), which is why rendered frames match their source frames exactly; seekBias overrides it per layer.

Check it

  • Frame count = round(duration × fps): exec ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of csv=p=0 <out.mp4>
  • Two audio channels: exec ffprobe -v error -select_streams a:0 -show_entries stream=channels -of csv=p=0 <out.mp4>
  • A contact sheet of the render (python-contact-sheet), looked at; the first seconds listened to, or the person asked to.
  • After an --into revision, stills either side of the span.

Workflows that use this skill

FAQs

The base for everything drawn with hn4a-compositor, hn4a's own compositor: a JSON layer spec (fills, images, video, text, SVG paths, groups that work as cameras or lie on a surface) played live in a browser page and rendered frame-exact to an MP4 with the audio mixed in. Holds the spec format, the keyframes, the commands, the first look in the chosen direction or a few relevant candidate treatments, the one gate every compositor job keeps (the person approves the live spec, then you render), re-rendering only a changed span, and the checks. The base every hn4a-compositor-* leaf loads; which of them fits the job (overlay motion graphics, ad titles, in-scene titles, hand-reactive titles, a product screen cut up or rebuilt, ad and explainer cameras, a news chyron, a storyboard, kinetic lyrics, an audio waveform, graphics on a surface in perspective) is picked in guide-add-titles-and-motion-graphics. Not Remotion, no build step.

Install Hypernatural Desktop, a free Mac app, and connect it to Claude Code, Codex or Claude Desktop. Then describe the video you want and your agent finds the skill itself, or paste this page's link (https://hypernatural.ai/skill/hn/hn4a-compositor) and it installs hn/hn4a-compositor and follows it.

Nothing. The skill is free and runs with tools on your own Mac, like hn4a-compositor, ffmpeg, ffprobe and python.

Yes. Ask your agent to adjust it for your job, or to save its own version. You can publish your version under your own handle so anyone can install it.

Go make something cool.

Install the app, open your agent, describe the video. That's the whole onboarding.

Sticker HypernaturalSticker BannerSticker TVBoy