Style-swap prompt system: blocks.json + 4 looks + promptc compiler
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
9a5ed2d02b
commit
c4ec145455
6
PLAN.md
6
PLAN.md
@ -58,7 +58,11 @@ Concept shot list (full prompts in BIBLE.md §Concept-art prompts):
|
|||||||
8. `title_card` — RECORD STORE GUY logo/marquee concept
|
8. `title_card` — RECORD STORE GUY logo/marquee concept
|
||||||
Run 3–4 seeds each, keep best. Free, so iterate hard BEFORE touching Flow.
|
Run 3–4 seeds each, keep best. Free, so iterate hard BEFORE touching Flow.
|
||||||
|
|
||||||
### Phase 3 — Flow consistency test (~100 credits MAX, gated on John's approval)
|
### Phase 3 — Flow style + consistency test (~180 credits MAX, gated on John's approval)
|
||||||
|
**3a Style test first:** compile `shots/t2_s3_sharkrun.json` in all 4 looks
|
||||||
|
(`tools/promptc.py ... --all`), gen each once in Veo Fast (~80 cr) with the matching
|
||||||
|
FLUX ingredient still per look. John picks the look (default cel80s — the build
|
||||||
|
assumes it; a different pick only swaps prompt text, zero code change). **3b then:**
|
||||||
In Google Flow (browser; John logs in himself — never handle his credentials):
|
In Google Flow (browser; John logs in himself — never handle his credentials):
|
||||||
1. Upload approved `hero_face` + `hero_sheet` FLUX stills as **ingredients**.
|
1. Upload approved `hero_face` + `hero_sheet` FLUX stills as **ingredients**.
|
||||||
2. Generate 5 test clips, Veo Fast (~20 cr each): same character, same set block,
|
2. Generate 5 test clips, Veo Fast (~20 cr each): same character, same set block,
|
||||||
|
|||||||
@ -12,9 +12,15 @@ ingredient stills before generating any more clips with it.
|
|||||||
> rim lighting, saturated jewel-tone palette with deep shadows, subtle film grain,
|
> rim lighting, saturated jewel-tone palette with deep shadows, subtle film grain,
|
||||||
> 2D animation cel look, no photorealism, no 3D render, no CGI.
|
> 2D animation cel look, no photorealism, no 3D render, no CGI.
|
||||||
|
|
||||||
(If John flips to live-action FMV cheese, replace with: "early 1990s live-action
|
**DECIDED: cel animation is canonical for the build** (John, 2026-07-19 — in the
|
||||||
full-motion-video game footage, shot on Betacam, slightly soft, practical neon
|
spirit of Dragon's Lair/Space Ace). The look is now a SWAPPABLE layer: blocks live
|
||||||
lighting, theatrical overacting" — and regenerate everything. Decide BEFORE Phase 3.)
|
in `production/prompts/` (blocks.json = characters/sets, looks/*.json = styles:
|
||||||
|
cel80s ✦canonical, scannerdarkly rotoscope, fmv90s, heavymetal). Compile any shot
|
||||||
|
in any look with `tools/promptc.py shots/<shot>.json [--look NAME | --all]`;
|
||||||
|
`--flux hero --look NAME` emits the matching ingredient-still prompt (each look
|
||||||
|
needs its OWN hero ingredient still — ingredients are style-baked). Style test
|
||||||
|
plan: 1 busy shot (t2_s3_sharkrun) × 4 looks ≈ 80 credits, judge, then commit.
|
||||||
|
Alt-look re-releases later = re-run the same shot files with a different --look.
|
||||||
|
|
||||||
## HERO BLOCK v1 — "Dez" (placeholder design, John approves at concept review)
|
## HERO BLOCK v1 — "Dez" (placeholder design, John approves at concept review)
|
||||||
> Dez, a lanky 20-something record store clerk with a tall messy black quiff,
|
> Dez, a lanky 20-something record store clerk with a tall messy black quiff,
|
||||||
|
|||||||
19
production/prompts/blocks.json
Normal file
19
production/prompts/blocks.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"_readme": "Style-INDEPENDENT blocks: characters and sets. The look/style lives in looks/*.json. Edit here → bump version in the name comment and regenerate ingredients.",
|
||||||
|
"characters": {
|
||||||
|
"hero": "Dez, a lanky 20-something record store clerk with a tall messy black quiff, olive skin, thick eyebrows, wearing an open red flannel shirt over a faded black band t-shirt, rolled dark jeans, scuffed white hi-top sneakers, oversized silver headphones worn around the neck, expressive rubber-limbed acting",
|
||||||
|
"villain": "the Dead Wax, a towering shifting mass of glossy black molten liquid vinyl with dripping tonearm limbs and a spinning record for a face"
|
||||||
|
},
|
||||||
|
"sets": {
|
||||||
|
"store": "inside a cluttered independent record store at night, rows of wooden crates packed with vinyl records, walls covered in gig posters and album sleeves, a buzzing neon sign glowing red and cyan, dusty air, single flickering fluorescent tube, checkerboard floor",
|
||||||
|
"backroom": "the record store stockroom, floor-to-ceiling industrial shelving stacked with record boxes, a single hanging bulb, a service hatch, cobwebs, teetering towers of 7-inch singles",
|
||||||
|
"booth": "a vintage wood-paneled listening booth with a glowing turntable console, padded walls, a porthole window, warm amber light",
|
||||||
|
"sea": "the record store flooded into an open ocean at night, aisles of wooden record racks bobbing like rafts on rolling waves, neon store signs glowing underwater, spray and foam, no walls, horizon of black water under a starless sky",
|
||||||
|
"lava": "the record store as a volcanic cavern, floor of glowing molten orange vinyl, record crates floating like obsidian rafts, dripping melted records hanging from shelves like stalactites, ember flecks in the air, red-black palette",
|
||||||
|
"space": "the record store torn open to deep space, shelving units drifting in zero gravity trailing debris fields of floating records, Earth tiny below, nebula light in purple and teal, silent starfield",
|
||||||
|
"grid": "inside a computerized hi-fi system rendered as a neon wireframe world, glowing cyan and magenta grid floor like a giant equalizer, towers of circuit-board skyscrapers, records rendered as glowing light-discs, black sky with scrolling waveform aurora",
|
||||||
|
"runout": "a surreal endless landscape inside a record's run-out groove, black glossy vinyl canyons with concentric ridge walls curving to the horizon, a distant impossibly huge chrome needle plowing the groove, dust motes the size of boulders, single spotlight sun",
|
||||||
|
"altar": "the record store bathed in black glossy liquid vinyl dripping upward, a sacred glowing turntable altar at the listening booth"
|
||||||
|
},
|
||||||
|
"negative_base": "no text, no captions, no watermark, no extra people"
|
||||||
|
}
|
||||||
6
production/prompts/looks/cel80s.json
Normal file
6
production/prompts/looks/cel80s.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "cel80s",
|
||||||
|
"label": "80s theatrical cel animation (CANONICAL — the code build ships this)",
|
||||||
|
"style": "1980s hand-drawn cel animation, theatrical animated feature quality, thick confident ink outlines, rich painterly gouache backgrounds, dramatic colored rim lighting, saturated jewel-tone palette with deep shadows, subtle film grain, 2D animation cel look",
|
||||||
|
"negative": "no photorealism, no 3D render, no CGI"
|
||||||
|
}
|
||||||
6
production/prompts/looks/fmv90s.json
Normal file
6
production/prompts/looks/fmv90s.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "fmv90s",
|
||||||
|
"label": "Early-90s live-action FMV cheese",
|
||||||
|
"style": "early 1990s live-action full-motion-video game footage, shot on Betacam, slightly soft interlaced video, practical neon and gel lighting, theatrical overacting, low-budget physical props and rubber monster suits",
|
||||||
|
"negative": "no animation, no cartoon look"
|
||||||
|
}
|
||||||
6
production/prompts/looks/heavymetal.json
Normal file
6
production/prompts/looks/heavymetal.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "heavymetal",
|
||||||
|
"label": "Airbrushed 80s adult-animation (Heavy Metal magazine vibe)",
|
||||||
|
"style": "1981 adult animated anthology film style, lush airbrushed painterly animation, chrome and haze, moody rim-lit frames like an album cover come to life, heavy blacks, analog optical glow, hand-painted sci-fi fantasy cel animation",
|
||||||
|
"negative": "no photorealism, no 3D render, no kids-cartoon look"
|
||||||
|
}
|
||||||
6
production/prompts/looks/scannerdarkly.json
Normal file
6
production/prompts/looks/scannerdarkly.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "scannerdarkly",
|
||||||
|
"label": "Interpolated rotoscope (A Scanner Darkly vibe)",
|
||||||
|
"style": "interpolated rotoscope animation traced over live-action footage, flat shifting planes of color, hand-traced outlines that subtly wobble and drift from frame to frame, posterized painterly shading, realistic human proportions and motion beneath the paint, hallucinatory 2000s rotoscoped art film look",
|
||||||
|
"negative": "no photorealism, no 3D render, no cartoon squash-and-stretch"
|
||||||
|
}
|
||||||
12
production/prompts/shots/t2_s3_sharkrun.json
Normal file
12
production/prompts/shots/t2_s3_sharkrun.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"id": "t2_s3_sharkrun",
|
||||||
|
"world": "Track 2 — Side A / Sea",
|
||||||
|
"kind": "action",
|
||||||
|
"set": "sea",
|
||||||
|
"characters": ["hero"],
|
||||||
|
"action": "Dez sprints and leaps across a chain of half-sunken record crates as sharks with vinyl record dorsal fins lunge out of the waves snapping at his heels",
|
||||||
|
"camera": "tracking wide shot moving with him, slight low angle",
|
||||||
|
"duration": "5s",
|
||||||
|
"qte": [{"t": [2.6, 3.3], "input": "left"}, {"t": [3.8, 4.4], "input": "up"}],
|
||||||
|
"notes": "STYLE TEST SHOT — run in all 4 looks. Busy enough to stress each style: character + creatures + water + motion."
|
||||||
|
}
|
||||||
77
tools/promptc.py
Executable file
77
tools/promptc.py
Executable file
@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""promptc — compile shot spec + look into a ready-to-paste Flow/Veo prompt.
|
||||||
|
|
||||||
|
tools/promptc.py shots/t2_s3_sharkrun.json # canonical look (cel80s)
|
||||||
|
tools/promptc.py shots/t2_s3_sharkrun.json --look scannerdarkly
|
||||||
|
tools/promptc.py shots/t2_s3_sharkrun.json --all # every look, labeled
|
||||||
|
tools/promptc.py --flux hero --look scannerdarkly # ingredient still prompt
|
||||||
|
# (hero_face for that look)
|
||||||
|
Paths resolve relative to production/prompts/. Zero deps, stdlib only.
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent / "production" / "prompts"
|
||||||
|
CANONICAL = "cel80s"
|
||||||
|
|
||||||
|
|
||||||
|
def load(p):
|
||||||
|
return json.loads(Path(p).read_text())
|
||||||
|
|
||||||
|
|
||||||
|
def looks():
|
||||||
|
return {f.stem: load(f) for f in (ROOT / "looks").glob("*.json")}
|
||||||
|
|
||||||
|
|
||||||
|
def compile_prompt(shot, look, blocks):
|
||||||
|
parts = [look["style"]]
|
||||||
|
parts += [blocks["characters"][c] for c in shot.get("characters", [])]
|
||||||
|
if shot.get("set"):
|
||||||
|
parts.append(blocks["sets"][shot["set"]])
|
||||||
|
parts.append(shot["action"])
|
||||||
|
if shot.get("camera"):
|
||||||
|
parts.append(shot["camera"])
|
||||||
|
neg = ", ".join(x for x in [blocks["negative_base"], look.get("negative", "")] if x)
|
||||||
|
return ". ".join(p.strip().rstrip(".") for p in parts) + ". Avoid: " + neg + "."
|
||||||
|
|
||||||
|
|
||||||
|
def flux_ingredient(char, look, blocks):
|
||||||
|
"""Prompt for the character ingredient still in a given look (FLUX, free)."""
|
||||||
|
return (f"{look['style']}. {blocks['characters'][char]}. close-up head and "
|
||||||
|
f"shoulders portrait, three-quarter view, confident smirk, plain warm "
|
||||||
|
f"gray background. Avoid: {blocks['negative_base']}, {look.get('negative','')}.")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("shot", nargs="?", help="path to shot json (rel to production/prompts/)")
|
||||||
|
ap.add_argument("--look", default=CANONICAL)
|
||||||
|
ap.add_argument("--all", action="store_true", help="compile shot in every look")
|
||||||
|
ap.add_argument("--flux", metavar="CHAR", help="emit FLUX ingredient-still prompt for a character")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
blocks = load(ROOT / "blocks.json")
|
||||||
|
lks = looks()
|
||||||
|
if args.look not in lks:
|
||||||
|
sys.exit(f"unknown look {args.look!r}; have: {', '.join(sorted(lks))}")
|
||||||
|
|
||||||
|
if args.flux:
|
||||||
|
print(flux_ingredient(args.flux, lks[args.look], blocks))
|
||||||
|
return
|
||||||
|
|
||||||
|
if not args.shot:
|
||||||
|
ap.error("need a shot json (or --flux CHAR)")
|
||||||
|
p = Path(args.shot)
|
||||||
|
shot = load(p if p.exists() else ROOT / args.shot)
|
||||||
|
|
||||||
|
chosen = sorted(lks) if args.all else [args.look]
|
||||||
|
for name in chosen:
|
||||||
|
if args.all:
|
||||||
|
print(f"\n=== {name} — {lks[name]['label']} ===")
|
||||||
|
print(compile_prompt(shot, lks[name], blocks))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue
Block a user