[![中文](https://img.shields.io/badge/%E4%B8%AD%E6%96%87-f2e3e3?style=for-the-badge&labelColor=f2e3e3&color=b07070)](README.md) [![English](https://img.shields.io/badge/English-8b1a1a?style=for-the-badge)](README.en.md) [![Follow on X](https://img.shields.io/badge/Follow-%40eternityspring-b07070?style=for-the-badge&labelColor=8b1a1a&logo=x&logoColor=f2e3e3)](https://x.com/eternityspring) # novel-storyboard Storyboarding for AI short drama: turns novel-script's beat flow into a worklist you can hand straight to a video model. This is the first layer in the pipeline that talks directly to that model, and the premise is baked in: **shots are generated, so one more cut costs almost nothing** — and the short-drama attention span runs on ~3-second cuts. Hence a three-level structure: ``` segment = one video-generation call, ≤ 15s, never crosses scenes ├─ cuts × 3–5 = intra-segment edits, 2–5s each (hard gate), each claiming script beats ├─ frames = one keyframe per cut: the master frame pinned at 0.00s, │ sub-frames pinned at their own cut marks └─ H3 prompt = one per segment; multi-picture alignment + [Shot k] cut times audited verbatim ``` - **A dialogue's shot–reverse-shot lives inside one segment, one generation** — wide, close on A, close on B are separate 2–5s cuts, each composition controlled by its own storyboard frame instead of gambling on prose - **The alignment instruction is derived, not written** — the multi-picture line (`Picture 2 aligns with the 3.00-second mark…`) and every `[Shot k] At 00:0X.XXX` cut time are computed from cut durations, and validate audits them **character for character**: change a duration without updating the prompt and it blocks - **Prompts follow the official spec: English by default, one shot per line** — each shot on its own line with its cut time; dialogue, lyrics and on-screen text keep their original language per the official rules (`[Chinese] …` verbatim). `promptLang: 'zh'` switches the whole prompt to Chinese. The writing spec is internalized as `references/h3-prompt.md` — **this skill is self-contained and depends on no external skill** - **Frames are asset composition, not invention** — every frame carries a reference manifest: the scene / character / prop sheets it must be built from. **This skill does not generate the images**; the prompt and that manifest are the deliverable Outputs `storyboard.json`, a Markdown shot list, and a self-contained `storyboard-report.html`: ![storyboard-report.html](assets/report.webp) ## Eighteen quality gates, all code Same stance as the other four skills in this repo: **a checklist the model grades itself on is worthless.** | Gate | Rule | | --- | --- | | **Full beat coverage** | every script beat claimed exactly once, in order, contiguous, cut-level | | Segment duration | 0 < Σcuts ≤ 15s (the single-generation cap; tune `params.maxSegmentSeconds`) | | **Cut duration** | every cut 2–5s — the ~3s rhythm is a **hard gate**, not advice | | Dialogue fits | dialogue seconds of claimed beats ≤ cut seconds, per cut | | Episode total | Σ segments within ±15% of the script's `targetSeconds` | | On-screen cap | ≤ 3 characters per cut, more requires a breakdown note | | Segment ID discipline | `E01-01` format, sequential — the segment ID is the asset filename | | Size word | the Chinese shot-size word (e.g. 特写) must appear in the cut's frame prompt | | Camera vocabulary | camera moves use H3's official terms (`Push In` / `Pan Left` / `Tracking Shot`…) and must appear inside **that cut's own [Shot k] passage** | | **H3 structure** | the alignment line is **derived from the cut structure and audited verbatim**; three fields in order; every `[Shot k]` cut time equals the running sum of prior cut durations | | **H3 dialogue verbatim** | every claimed line appears verbatim inside a `` block — one changed punctuation mark fails | | **Prompt language consistency** | prose audited both ways against `promptLang`: Chinese drama written in English fails, English mode mixing Chinese fails | | Frame-prompt hygiene | Chinese, non-empty (it names characters on purpose — a name points at the attached design sheet) | | No names in video prompts | the H3 body (in either language) and the Seedance shot text carry no character names or aliases — required by both official prompt guides. Checked with `--outline` / `--cast`; skipping is **announced** | | **Composition fields** | `blocking` per segment; lens / camera position / composition / eyeline / focus / stability per cut, stability from the enum | | **Seedance shot text** | every cut's `shot` is Chinese and non-empty, with no timings, shot numbers, image references, H3 markers or `{}` `<>` `()` — the program adds those from the real structure | | Reference integrity | scene index / characters / props all audited against the script scene | | **Shot recipe** (optional mount) | only checked with `--shots `: a cut's `recipe` id exists in the library, every must-phrase of that card appears in the cut's frame prompt, and a multi-cut recipe runs long enough. Without `--shots` the skip is **announced**; so is "no cut references a recipe" | The selftest **defeats every gate on purpose** to prove each one actually blocks. **Shot recipes are an optional vocabulary layer.** A cut may carry an optional `recipe` — a card id from an external optional card library, **per cut, not per segment**, with **multi-cut recipes expressed as a run of consecutive cuts sharing the id** rather than an array. Without an external card library installed everything still runs: this skill is self-contained, down to its own 25-line restricted frontmatter parser instead of a cross-directory import. A card's suggested sizes and cameras are **deliberately not gated** — the report's Recipe column marks deviations with `≠` (hover for the suggestion) and `checkup` prints a note. A recipe is vocabulary, not law; make an optional mount stricter and nobody mounts it, and **a gate that blocks wrongly is worse than no gate**. ## Gate failures accumulate, and `stats` tells you which rule the model breaks most Every `validate` and `checkup` appends the gate outcome to `.gates.jsonl` in the **current directory**. After a few dozen runs: ```bash node scripts/novel-storyboard.mjs stats ``` It answers three questions: | Question | What it tells you | | --- | --- | | **Which gate fires most** | That rule is the one the model ignores — **the wording is what needs fixing, not the model** | | **Which gate never fires** | Either a dead gate, or a rule the model has internalised | | **What the failure details look like** | Problems that recur without a gate of their own only surface by reading this free text | This is the one idea worth borrowing from SkillOpt's "the skill document is trainable state": **a document is not a spec written once, it is something you iterate from feedback** — but the iteration needs evidence rather than impressions. The log only accumulates evidence; what to change stays a human call. Pass `--no-log` to skip it. If the file cannot be written the step is skipped silently and validation is unaffected. `.gates.jsonl` is already in `.gitignore`. ## The report A single-page, 1600px-wide review document. Reports render with a Chinese UI by default; pass `--lang en` to `render` for a fully English report (zh / en built in). This only switches the UI labels — it is independent of `promptLang`, which controls the H3 prompt language (English by default). In English mode the quality-gate labels are translated too (thresholds kept as computed); failing-gate details and all data stay as authored. - **KPI band**: segments / cuts with average length / total vs target / generation batches / segments carrying dialogue - **Cut rhythm strip** (the signature chart): one band per episode, **thick separators = segment boundaries (one generation each)**, slice width = cut duration share, color depth = shot size; click a slice to jump to its segment card - **Segment cards**: the master frame follows the top-level `aspectRatio` contract (`16:9` by default, `9:16` for a vertical board; legacy `_meta.orientation` and frame-tail tags remain compatibility fallbacks) (an honest prompt placeholder when not generated), a sub-frame strip, then a **50/50 split**: cut rows on the left (start mark · seconds · size · camera · recipe · picture summary **auto-derived from the claimed script beats**), and an H3 prompt panel on the right — one shot per line, with one-click copy - **Generation batch list**: segments sharing a scene + lighting state form one batch around one environment reference image - **Audio alignment list**: every dialogue line mapped to **segment#cut** — the worklist for placing TTS audio, fully computed - **Quality gates** panel + header badge + **Export JSON** (downloads `storyboard.json` verbatim) - All graphics inline CSS/SVG, zero external resources, opens offline ## The relay — the pipeline closes here ``` novel-outline → outline.json (what: structure) novel-characters → cast.json (who: character sheets) novel-art → art.json (where: scene & prop sheets) novel-script → script.json (the drama: scenes, beats, lines) novel-storyboard → storyboard.json (how to shoot: segments, cuts, frames, H3 prompts) ``` `seed --eps 1-3 --aspect 16:9` deterministically expands each scene's beat list (numbers, per-beat seconds, speakers) as the cutting worksheet and writes the official top-level `aspectRatio`; accepted values are `16:9` and `9:16`, with `16:9` as the default. `validate --script` is mandatory; `--outline` / `--cast` enable the name ban, `--art` gets scene names and sheet thumbnails into the report. Each frame ships a prompt plus the reference manifest naming the upstream sheets it must be built from; generation happens downstream. The H3 prompt plus the frame set goes straight to MiniMax H3. ## CLI ```bash node scripts/novel-storyboard.mjs seed script.json --eps 1 --aspect 16:9 node scripts/novel-storyboard.mjs validate sb.json --script script.json --outline outline.json --cast cast.json node scripts/novel-storyboard.mjs checkup sb.json --script script.json node scripts/novel-storyboard.mjs validate sb.json --script script.json --shots /path/to/cards # optional: the 18th gate node scripts/novel-storyboard.mjs render sb.json --html --script script.json --outline outline.json --art art.json > storyboard-report.html node scripts/novel-storyboard.mjs render sb.json --html --lang en --script script.json --outline outline.json --art art.json > storyboard-report.html # English report UI node scripts/novel-storyboard.mjs export sb.json --script script.json # per-segment folders: f1..fN.png + prompt.md ``` ## Limits - No writing or rewriting dialogue, no design sheets, no video generation or editing - Lip-sync is out of scope for now — that belongs to the generation pipeline - Seconds are a **generation order, not an estimate**; tune the segment cap and cut-rhythm range in `params` per your model - Report UI ships in Chinese (default) and English — pick with `--lang`; the prompt language is controlled separately by `promptLang` (English by default) - Generate the first segment's full frame set (3–5 images) for approval before committing — one episode is ~30–40 frames, and a wrong art direction wastes the batch ## Selftest ```bash node scripts/selftest.mjs ``` 336 assertions — beat expansion, H3 skeleton derivation, Seedance assembly, stats and batching, gate-defeating cases, recipe-card parsing and mounting, seed, rendering (both report UI languages, frame-slot aspect), H3 and Seedance export. No model calls, runs in about a second. The bundled example (`examples/渡口-storyboard.json`) is a complete episode-1 storyboard — 10 segments, 34 cuts claiming all 35 script beats at ~3.5s per cut, 119s against a 120s target, 2 generation batches, every segment carrying a fully audited H3 prompt. **Only tested on macOS + Node 24.**