Use when the user asks for a motion reel, showreel, lyric video, beat-synced edit, promo reel, or any short animated video set to a song. Builds it in Remotion from an analysis of the supplied song, with a fresh art direction per reel that is checked against a log of earlier reels.
2 stars
0 votes
0 copies
0 views
Added September 27, 2026
designpythonrustgoshellbashnodeapibackend
Works with
cli
api
Security analysis
A92/100
mediumInstalls packages at runtime which could introduce malicious dependencies
Installs into .claude/skills of the current project.
Are you the author of Motion Reel?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/deanspn-motion-reel)
---
name: motion-reel
description: Use when the user asks for a motion reel, showreel, lyric video, beat-synced edit, promo reel, or any short animated video set to a song. Builds it in Remotion from an analysis of the supplied song, with a fresh art direction per reel that is checked against a log of earlier reels.
---
# Motion reel
The goal is a reel that could only have been made for this song and this subject. The song's own structure sets the timing, the subject sets the look, and a log of earlier reels keeps each one from drifting toward the last.
`SKILL_DIR` below is the folder this SKILL.md is in (`~/.claude/skills/motion-reel` for a manual install; the plugin folder when installed as a plugin).
## The brief
Collect these before starting, asking once for whatever is missing. Length and aspect ratio have no defaults; they come from the brief.
- Subject and what the reel should say or show.
- Song: a local audio file, the excerpt to use (start time and length), and whether lyrics appear on screen. For lyrics, an `.lrc` file if the user has one; otherwise they get transcribed.
- Aspect ratio and resolution (for example 1080x1920 for 9:16, 1920x1080 for 16:9) and frame rate (60 unless the brief says otherwise).
- Where it plays: a website section that autoplays muted and loops, a social post with sound, or both. A muted loop needs a first frame that works as a poster and a last frame that leads back into it.
- Supplied assets: footage, images, logos, and any colours or typefaces the brand requires.
Work only from audio files the user supplies; never download a song.
## Workflow
1. **Scaffold.** Copy `SKILL_DIR/template` to a new folder named after the reel (inside the current working directory unless the user names a place), put supplied assets under `public/`, and run `npm install`. When the reel is for a named brand, site or repo, run the discovery in `SKILL_DIR/reference/brand.md` first: verbatim copy with sources, typefaces, colours, marks and the live page.
2. **Analyse the song.** When the brief doesn't fix the excerpt, choose it yourself first: run `analyze.py` on the whole song into a scratch folder (no `--start`/`--duration`) and map the vocals across it with `uv run --with faster-whisper python SKILL_DIR/scripts/vocals.py --song <song> --out <scratch>/vocals.json`. If the song has vocals, the excerpt should carry them, the chorus above all; an energy-only pick lands on instrumental stretches. Start on a downbeat, take whole bars (bars ≈ seconds × bpm / 240), and end on a real section boundary or let the last ~1.5 s fade.
When the best material is in more than one place (an opening hook and the final chorus, say), join the stretches on measured downbeats instead of settling for one continuous excerpt, then use the edit as the song:
```bash
uv run --with librosa --with soundfile --with imageio-ffmpeg python SKILL_DIR/scripts/cut.py <song> --cues <scratch>/src/cues.json --bars "a-b,c-d" --out <scratch>/edit.wav --fade-out 1.5 --vocals <scratch>/vocals.json
```
`--bars` takes downbeat indices from the whole-song analysis (end exclusive). Joins are short overlapping crossfades; `--vocals` warns when a cut lands inside a sung phrase, so move that cut to a gap.
Analyse the excerpt (or the edit, with `--start 0` and no `--duration`) into the reel:
```bash
uv run --with librosa --with soundfile --with imageio-ffmpeg python SKILL_DIR/scripts/analyze.py <song> --project <reel-dir> --start <sec> --duration <sec> --fps 60 --width <w> --height <h>
```
This writes the trimmed excerpt to `public/song.wav` and `src/cues.json`: bpm, beat and downbeat frames, strong/low/high hit frames, sections with an energy level, and a per-frame energy curve. Read the printed summary to learn the song's shape before designing anything. Beat trackers sometimes lock onto half or double the real tempo; if the printed bpm is wrong for the song (or the user states it), re-run with `--bpm <value>`. Downbeats are estimated from beat strength; if the first strong hit falls between them, trust the hit.
Then write the excerpt's vocal phrase timings into the cues (`CUES.vocals`: start/end frame, word count, per-word frames):
```bash
uv run --with faster-whisper python SKILL_DIR/scripts/vocals.py --project <reel-dir>
```
Both `vocals.py` modes print timings only. Never print, paste or quote the lyrics of a commercial song in the conversation: output filters block reproduced song lyrics and the session errors out.
Lyrics, when the brief asks for them on screen:
```bash
uv run --with faster-whisper python SKILL_DIR/scripts/lyrics.py --project <reel-dir> --transcribe
uv run python SKILL_DIR/scripts/lyrics.py --project <reel-dir> --lrc <file.lrc>
```
Transcribed sung vocals contain mistakes. Have the user check the lines (for a commercial song, in `src/cues.json` rather than pasted into the chat), or correct them from lyrics the user provides, before animating any text.
3. **Treatment.** Run `node SKILL_DIR/scripts/reel-log.mjs recent`, then write `<reel-dir>/treatment.json`:
```json
{ "title": "", "brand": "", "reference": "", "palette": ["#hex"], "type": ["Family"], "motion": "", "transitions": [""], "camera": "", "texture": "", "structure": "" }
```
`brand` is optional: fill it when the reel is for a named brand, so a later reel for the same brand is compared on everything except its fixed palette and typefaces. Describe the treatment to the user in a few sentences, then run `node SKILL_DIR/scripts/reel-log.mjs check <reel-dir>/treatment.json`. When it fails, change the direction on the axes it names; rewording the same idea to get past the check defeats its purpose. Pass `--allow-type "<family>"` only when the brief or brand requires that typeface.
4. **Build.** The composition's structure comes from the treatment and the song's sections, not from a fixed shot list. `src/lib/cues.ts` exposes the analysis plus helpers: `bar(n, beatsIn)`, `beat(n)`, `section(n)` and `vocal(n)` address the song's own grid, and `pulse`, `sinceEvent`, `sectionAt`, `energyAt`, `lyricAt` read it per frame. Scene code names moments through those helpers and the `CUES` arrays, never as literal frame numbers or seconds, so a different excerpt re-times the whole reel instead of breaking it. Keep renders deterministic: `random(seed)` from `remotion`, never `Math.random()` or the clock. Load typefaces with `@remotion/google-fonts` or from `public/fonts` via `@remotion/fonts`. Look up a Remotion API in the docs (context7) before using one you are unsure of.
5. **Review.** Stills catch composition and legibility; short previews catch motion and sync.
```bash
node scripts/stills.mjs 0.5 auto
uv run --with pillow python SKILL_DIR/scripts/sheet.py out/sheet.png
npx remotion render Reel out/preview.mp4 --frames=<from>-<to> --scale=0.5
```
`auto` picks every section start and middle, the strongest hits, and the last frame. Motion reads from strips of consecutive frames (`node scripts/stills.mjs 0.5 <from>:<to>:2` around each cue), which also show whether an accent lands on its frame. `sheet.py` splits long runs into `sheet-1.png`, `sheet-2.png`… so each page stays legible. View the sheets and judge them against the treatment and the banned defaults below. For vertical reels, keep text clear of the top eighth and bottom fifth, where platform UI sits. Fix, then review again. Time a 300-frame full-resolution slice before the final render to know how long it will take.
6. **Render and deliver.** `npx remotion render Reel out/<slug>.mp4`, then turn the master into the files people actually play:
```bash
uv run --with imageio-ffmpeg --with numpy python SKILL_DIR/scripts/deliver.py out/<slug>.mp4 --out out/deliver --bg <page background hex> --loop --muted
```
It writes `<slug>-web.mp4` (H.264, BT.709, faststart), `<slug>-muted.mp4` with `--muted` (for autoplaying loops), `<slug>-web.webm` (VP9) and `<slug>-poster.jpg`, then decodes every file and exits non-zero if a check fails: duration, audio present or absent, colour tags, frame 0 against the master, the corner colour against `--bg` (the page it sits on, so an embedded reel shows no box), and with `--loop` the seam between the last frame and the first. Drop `--loop` and `--muted` for a reel that doesn't loop. Then look at frames sampled at a few strong hits to see the hits landing.
7. **Log.** `node SKILL_DIR/scripts/reel-log.mjs add <reel-dir>/treatment.json --output <reel-dir>/out/deliver/<slug>-web.mp4`, then report the file paths, length, and the treatment in a line or two.
## Designing a reel that is its own
- **The song leads.** Tempo, section changes, energy, and timbre decide pacing: a sparse piano piece and a trap drop should not move the same way. Big moves belong to section changes and the strongest hits; beats drive small rhythmic motion; quiet passages get stillness. Accenting every beat flattens the whole reel into one level.
- **A real-world reference.** Anchor the look in a specific visual tradition that fits the subject (for example a transit map, a 1970s broadcast ident, a risograph zine, a museum wall label, a weather radar, an engraved banknote) and let it set palette, type, and motion. Name it in `reference`.
- **Structure from the song.** Shots span the song's sections at their real lengths, which differ. Transitions come from the reference, not from a stock set.
- **Supplied footage and images** get treated through the reference too (masking, duotone, crops, split frames, speed changes), not dropped in full-frame between graphics.
## Defects that showed up in review
- **Text colliding at handoffs.** The outgoing line has to be gone before the incoming one arrives; a crossfade in the same spot reads as a smear. Swap text in place at the peak of a blur, and never move a line while it is still visible.
- **Lit backgrounds brighter than the text on them.** Surfaces behind type stay dim so the type is the brightest thing in its area.
- **The biggest musical moment not being the biggest visual one.** Check that the frame after the drop or crash is measurably brighter or busier than the passage before it.
- **Display faces with ambiguous lowercase.** Some show fonts draw a lowercase `l` like `I` or the period as a raised dot; set those faces in capitals.
- **CSS blend and filter order.** A `mix-blend-mode` on a child of a `clip-path` (or `filter`) container only blends inside that container, so put the blend on the container. `filter` runs before `clip-path`, so a soft edge needs the blur on an outer wrapper around the clipped element.
## Banned defaults
These are the looks most generated reels fall back on. Use one only when the brief or the chosen reference calls for it, and say so in the treatment.
- A scale punch, zoom bounce, or camera shake as the accent on every beat
- RGB-split or glitch as the go-to transition; whip pans with motion-blur streaks as the go-to transition
- Film grain plus vignette as a default layer over everything
- Bokeh, floating dust, or generic particle fields as background filler
- Neon purple and cyan, or a purple-to-pink gradient palette; magenta accents on near-black
- Ken Burns pans on every still
- Centred bold all-caps captions popping in word by word; typewriter reveals
- Inter, Montserrat, Poppins, Bebas Neue, Roboto, Open Sans, Geist, or Space Grotesk as the typeface
- Light leaks, lens flares, and glassmorphism cards
- A slow push-in camera wrapped around the whole reel
- Equal-length shot blocks in a row, ending on a centred logo lockup
## Rendering hosts
The same steps work on Windows and Linux. The first render downloads Remotion's headless Chrome into `node_modules/.remotion`. On a fresh Ubuntu host it needed `libnss3` plus `fontconfig` and one font package (`fonts-dejavu-core`) as a fallback; if Chrome still fails to launch, `ldd` on the `chrome-headless-shell` binary lists what else is missing. `remotion.config.ts` and `scripts/stills.mjs` pick the GL backend per platform (`angle` on Windows, `swangle` on Linux); if WebGL frames come out blank, that setting is the first thing to change.