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

6.1 KiB
Raw Blame History

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 (durationdata-start, src); the worker reads its own frame block. A frame's composition id = its src file stem (compositions/frames/01-f1.html01-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.

---
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.

  • templatetemplate: <catalog id> + params (keys from the catalog entry) + role_bindings (real audiomap anchor seconds) + copy.
  • free_designfree_design: { dominant_system, primitives: [catalog ids], density_topology } + anchors (real beat / onset seconds) + copy.
  • assetasset: { treatment, clips: [public/…], anchors?, overlay_copy? }. treatmentbeat_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.

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 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.