Files
hyperframes/skills/music-to-video/references/storyboard-format.md
T
Miao YangandClaude Opus 4.8 265b02738e style(skills): apply oxfmt to music-to-video and router docs
Fixes the Format / Preflight CI checks on the new skill files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 22:04:39 +08:00

95 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# STORYBOARD.md format — frames → groups
`STORYBOARD.md` is the single reviewable plan the user approves at Step 3 and the **manual**
each frame-worker follows at Step 4. It is **hierarchical**: one block per **frame**, written
as a `## Frame N — <frame_id>` heading (the parser recognizes `Frame`). **One frame = one
scene = one composition file.** Inside each frame block are its **groups** (the treatment
units). The assembler reads the frame level (`duration``data-start`, `src`); the worker
reads its own frame block. A frame's **composition id = its `src` file stem**
(`compositions/frames/01-f1.html``01-f1`), which the worker uses as `data-composition-id`
and the `window.__timelines` key.
Step 2 writes the skeleton (frame fields, groups `TBD`); Step 3 fills the groups + brand. It
is a build spec, not code — the planner writes WHAT, the worker decides HOW. **Never write
millisecond tweens here.**
## File shape
YAML frontmatter (the video-wide spine) + one `## Frame N — <frame_id>` block per frame.
```markdown
---
compositionId: bgm
duration_s: 30.0 # == audiomap.audio.duration_sec, exactly
canvas: { w: 1920, h: 1080, fps: 30 }
style: # brand spine — from the chosen frame.md preset (Step 3)
font: "EB Garamond / Inter / JetBrains Mono" # the preset's typography, verbatim
palette: ["#FAF9F5", "#141413", "#CC785C", "#181715"] # ≤46 swatches from the preset's colors
assets: false # false, or a note like "assets/ has 6 user photos"
build_notes: ["one paused timeline per frame", "no remote assets"]
avoid: ["generic slideshow", "tiny unreadable hero text"]
---
## Frame 1 — f1
- src: compositions/frames/01-f1.html # worker writes here; assembler refs it; stem (01-f1) = composition id
- duration: 7.198s # = span length; the assembler reads this for cumulative data-start
- span_sec: [0.0, 7.198] # track seconds; frames tile the track
- pacing: beat_cut # beat_cut | phrase_flow (from the skeleton; obey it)
- mood: [hype]
- feel: accelerating onset stream into a held downbeat
### Groups
- **g1** — template: `intro-kinetic-cascade`
- span_sec: [0.0, 4.017] # frame-LOCAL build is 0-based; these are TRACK seconds (worker subtracts frame start)
- params: { theme: "light", icon: "bolt", phrases: "[…]", climax: "{…}" }
- role_bindings: { phrase: { times: [0.14, 0.55, 0.87] }, climax: { in: 3.79, iconAt: 4.9 } }
- copy: "GROWTH THROUGH CREATIVITY"
- **g2** — free_design
- span_sec: [4.017, 7.198]
- free_design: { dominant_system: "per-onset typography", primitives: ["content-swap", "braam-punch"], density_topology: "accumulate" }
- anchors: [4.10, 4.80, 5.50, 6.20] # onset seconds the reveals ride (from audiomap)
- copy: ["BUILD", "SHIP", "REPEAT"]
## Frame 2 — f2
```
## Frame block — required fields
| field | meaning |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| heading `## Frame N — <frame_id>` | `frame_id` matches the `src` stem; `N` = the 1-based index. |
| `src` | `compositions/frames/NN-<frame_id>.html` — where the worker writes; the assembler references it. Stem = `data-composition-id`. |
| `duration` | the frame span length in seconds (e.g. `7.198s`) — **required**; the assembler sums these for cumulative `data-start`. |
| `span_sec` | `[start, end]` track seconds. `duration = end start`. |
| `pacing` | `beat_cut` \| `phrase_flow` — from the skeleton; the worker must obey (no hard-cut on `phrase_flow`). |
| `mood`, `feel` | from the skeleton; tone + the one-line music situation the planner matched against. |
| `### Groups` list | ≥1 group; groups tile the frame span in order. |
## Group entry — exactly one of three kinds
Every group is **template** OR **free_design** OR **asset** — never two, never none. All three
carry `span_sec` (track seconds, tiling the frame) and may carry `copy`.
- **template** — `template: <catalog id>` + `params` (keys from the catalog entry) + `role_bindings` (real audiomap anchor seconds) + `copy`.
- **free_design** — `free_design: { dominant_system, primitives: [catalog ids], density_topology }` + `anchors` (real beat / onset seconds) + `copy`.
- **asset** — `asset: { treatment, clips: [public/…], anchors?, overlay_copy? }`. `treatment``beat_cut` (one clip per anchor — only on a `beat_cut` frame) | `ken_burns` (slow push — fits `phrase_flow`) | `bg_under_text` (clip dimmed behind a template / free group). See [`montage.md`](montage.md).
## Rules
- Frames tile the track (gap-free, first at 0, last at `duration_s`); a frame's groups tile its span; no group < ~1 bar; no group boundary inside a `rolls[]` run.
- `params` keys come from the template's [`template-catalog.md`](template-catalog.md) entry.
- All anchor seconds are **track seconds from `audiomap.json`** — the worker converts to frame-local by subtracting the frame start.
- A `phrase_flow` frame MUST NOT use `beat_cut` asset treatment or per-onset hard cuts.
- The brand `style` is set once; every group's palette draws from it.
- Reviewable prose-plus-data; keep it scannable. No GSAP, no millisecond timing.
## Self-check (the planner runs `validate-plan.mjs`)
- frontmatter has `compositionId`, `duration_s` (== audiomap), `canvas`, `style`.
- every frame has `span_sec` + `src` + positive `duration` + `pacing` + ≥1 group; frames tile the track.
- every group is exactly one of template / free_design / asset; template ids exist in the catalog; anchors are real audiomap seconds; `phrase_flow` frames have no `beat_cut`.