@wji.ai/cli 0.8.1 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -140,7 +140,7 @@
140
140
  {
141
141
  "speaker": "VO",
142
142
  "line": "他怎么知道那人穿雨衣。",
143
- "delivery": "林小夏的心声"
143
+ "delivery": "她的心声,压得很低"
144
144
  },
145
145
  {
146
146
  "action": "门口的风铃突然响了一声,两人同时转头,门外只有雨。"
@@ -12,7 +12,8 @@
12
12
  "videoResolution": "768P",
13
13
  "references": "keyframes",
14
14
  "style": "realistic",
15
- "register": "大陆普通话"
15
+ "register": "大陆普通话",
16
+ "foregroundLevel": "high"
16
17
  },
17
18
  "source": {
18
19
  "kind": "original",
@@ -278,7 +279,7 @@
278
279
  "image": {
279
280
  "prompt": "Portrait of a young East Asian woman in her mid-twenties, contemporary 2020s Chinese city, night-shift convenience store clerk, slim and upright, hair pulled into a loose low ponytail, faded navy jacket over a striped store uniform, faint shadows under alert eyes, thin lips pressed together, three-quarter view bust, plain neutral background, soft directional key light from the upper left with a cool fill, shallow depth of field",
280
281
  "negativePrompt": "plastic skin, over-smoothed face, perfectly symmetrical face, doll-like features, dead eyes without highlights, helmet hair, flat textureless fabric, stiff mannequin pose, logo, brand name, text, watermark",
281
- "sheet": "Single character model sheet on ONE 16:9 landscape canvas, pure white background. semi-realistic character illustration, painterly rendering with soft blended edges and visible brush texture, anatomically grounded. LEFT ZONE (about 34% of the canvas width): one bust portrait, front view, head and shoulders, both shoulders complete, flat straight bottom edge. RIGHT-TOP ZONE: three FULL-BODY views side by side (front, side, back) on one shared ground line, same height, same head-to-body ratio, PROPORTIONS ARE CRITICAL, no stretching or squashing. RIGHT-BOTTOM ZONE: four small isolated close-up studies (low ponytail tie, jacket cuff wear, store name tag, worn canvas sneakers); the detail studies give way, not the figures. Thin hairline rules separate the zones. Subject: a young East Asian woman in her mid-twenties, contemporary Chinese night-shift convenience store clerk, faded navy jacket over a striped uniform, alert tired eyes; all views share the SAME face as the bust portrait. LIGHTING IN THE LEFT ZONE ONLY: soft directional key from the upper left with ambient occlusion under the chin. LIGHTING IN THE RIGHT ZONES: flat even orthographic lighting, no cast shadows. No text, no watermark.",
282
+ "sheet": "Single character sheet on ONE 16:9 landscape canvas, three panels left to right, grey-white seamless studio background, studio lighting. semi-realistic character illustration, painterly rendering with soft blended edges and visible brush texture, anatomically grounded. LEFT: one headshot, the face filling the full height of the panel, facial features large and clear. CENTER: full-body front view, the face completely covered by a flat grey box so no facial features are visible. RIGHT: full-body back view, same height and body proportions as the front view, feet on the same ground line. Subject: a young East Asian woman in her mid-twenties, contemporary Chinese night-shift convenience store clerk, faded navy jacket over a striped uniform, alert tired eyes. Same costume on both full-body views. No side view, no extra characters, no scenery, no text, no watermark.",
282
283
  "tags": [
283
284
  "semi-realistic",
284
285
  "painterly",
@@ -442,7 +443,7 @@
442
443
  "image": {
443
444
  "prompt": "Portrait of an East Asian man in his early sixties, contemporary 2020s China, retired security guard, stocky build with a slight stoop, grey close-cropped crew cut, washed-out grey zip jacket, thick fingers, deep crow's feet, weathered skin with uneven tone and visible pores, three-quarter view bust, plain neutral background, soft directional key light from the upper left with a cool fill, shallow depth of field",
444
445
  "negativePrompt": "plastic skin, over-smoothed face, perfectly symmetrical face, young face, dead eyes without highlights, helmet hair, flat textureless fabric, stiff mannequin pose, logo, brand name, text, watermark",
445
- "sheet": "Single character model sheet on ONE 16:9 landscape canvas, pure white background. semi-realistic character illustration, painterly rendering with soft blended edges and visible brush texture, anatomically grounded. LEFT ZONE (about 34% of the canvas width): one bust portrait, front view, head and shoulders, both shoulders complete, flat straight bottom edge. RIGHT-TOP ZONE: three FULL-BODY views side by side (front, side, back) on one shared ground line, same height, same head-to-body ratio, PROPORTIONS ARE CRITICAL, no stretching or squashing. RIGHT-BOTTOM ZONE: four small isolated close-up studies (crew-cut hairline, jacket zipper pull, thick scarred knuckles, worn leather shoes); the detail studies give way, not the figures. Thin hairline rules separate the zones. Subject: a stocky East Asian man in his early sixties, contemporary retired security guard, grey crew cut, washed-out grey zip jacket, slight stoop; all views share the SAME face as the bust portrait. LIGHTING IN THE LEFT ZONE ONLY: soft directional key from the upper left with ambient occlusion under the chin. LIGHTING IN THE RIGHT ZONES: flat even orthographic lighting, no cast shadows. No text, no watermark.",
446
+ "sheet": "Single character sheet on ONE 16:9 landscape canvas, three panels left to right, grey-white seamless studio background, studio lighting. semi-realistic character illustration, painterly rendering with soft blended edges and visible brush texture, anatomically grounded. LEFT: one headshot, the face filling the full height of the panel, facial features large and clear. CENTER: full-body front view, the face completely covered by a flat grey box so no facial features are visible. RIGHT: full-body back view, same height and body proportions as the front view, feet on the same ground line. Subject: a stocky East Asian man in his early sixties, contemporary retired security guard, grey crew cut, washed-out grey zip jacket, slight stoop. Same costume on both full-body views. No side view, no extra characters, no scenery, no text, no watermark.",
446
447
  "tags": [
447
448
  "semi-realistic",
448
449
  "painterly",
@@ -538,7 +539,7 @@
538
539
  "image": {
539
540
  "prompt": "Portrait of a tall thin East Asian man around forty, contemporary 2020s Chinese city at night, black hooded raincoat zipped to the chin, hood pulled low so only the jaw and unshaven stubble show, rain beading on the wet plastic, three-quarter view bust, plain neutral background, hard cool rim light from behind and a dim key from the left, shallow depth of field",
540
541
  "negativePrompt": "plastic skin, over-smoothed face, perfectly symmetrical face, full face clearly visible, dead eyes, dry raincoat, flat textureless fabric, stiff mannequin pose, logo, brand name, text, watermark",
541
- "sheet": "Single character model sheet on ONE 16:9 landscape canvas, pure white background. semi-realistic character illustration, painterly rendering with soft blended edges and visible brush texture, anatomically grounded. LEFT ZONE (about 34% of the canvas width): one bust portrait, front view, head and shoulders, both shoulders complete, flat straight bottom edge. RIGHT-TOP ZONE: three FULL-BODY views side by side (front, side, back) on one shared ground line, same height, same head-to-body ratio, PROPORTIONS ARE CRITICAL, no stretching or squashing. RIGHT-BOTTOM ZONE: four small isolated close-up studies (raincoat hood seam, zipper pulled to the chin, wet rubber boots, stubbled jaw); the detail studies give way, not the figures. Thin hairline rules separate the zones. Subject: a tall thin East Asian man around forty, contemporary, black hooded raincoat with the hood low over the face, stubble; all views share the SAME face as the bust portrait. LIGHTING IN THE LEFT ZONE ONLY: dim key from the left with a cool rim light. LIGHTING IN THE RIGHT ZONES: flat even orthographic lighting, no cast shadows. No text, no watermark.",
542
+ "sheet": "Single character sheet on ONE 16:9 landscape canvas, three panels left to right, grey-white seamless studio background, studio lighting. semi-realistic character illustration, painterly rendering with soft blended edges and visible brush texture, anatomically grounded. LEFT: one headshot, the face filling the full height of the panel, facial features large and clear. CENTER: full-body front view, the face completely covered by a flat grey box so no facial features are visible. RIGHT: full-body back view, same height and body proportions as the front view, feet on the same ground line. Subject: a tall thin East Asian man around forty, contemporary, black hooded raincoat with the hood low over the face, stubble. Same costume on both full-body views. No side view, no extra characters, no scenery, no text, no watermark.",
542
543
  "tags": [
543
544
  "semi-realistic",
544
545
  "painterly",
@@ -185,7 +185,7 @@ SD prompt <project> score E01 > /tmp/E01-score.md # p
185
185
  # Sound Designer conforms it to the edit's real cuts and writes one bed prompt per section
186
186
  wji canvas add audio -c E1 -n "E01 score 1" -m seed-audio-1.0 --prompt-file /tmp/E01-score-1.txt --json | jq -r .id
187
187
  wji canvas gen <node> --yes --wait # ask first; ~0.15 credits a minute
188
- # mix the sections under the edit at their offsets (ffmpeg), then:
188
+ # lay the sections under the edit — SoundCraft, or ffmpeg if soundcraft-cli is missing (edit-tools.md)
189
189
  wji asset upload ./E01-mix.mp4 --json | jq -r .id # → MIX
190
190
  SD bind <project> --ep 1 wji.assets.mix MIX
191
191
  ```
@@ -78,13 +78,12 @@ the negative space is usually the dark part of the frame.
78
78
  - Write the placement into `frame`: `she sits in the lower-left third, the unlit shelves filling
79
79
  the right two-thirds of the frame`.
80
80
 
81
- **前景 — a foreground layer.** Most cuts shoot *past* something: the edge of a shelf, rain on the
82
- glass, a door frame, a hanging lamp, the shoulder of the other person (an over-the-shoulder). Take
83
- it from the scene's anchors where you can, so it also carries continuity. The existing `photography`
84
- gate already requires a named `foreground` on at least half the cuts of a segment, said to be out
85
- of focus. Aim higher than that minimum in this look.
81
+ **前景 — a foreground layer.** Where the story gives a reason, shoot *past* something: the edge of a
82
+ shelf, rain on the glass, a door frame, a hanging lamp, the shoulder of the other person (an
83
+ over-the-shoulder). Take it from the scene's anchors where you can, so it also carries continuity.
84
+ `params.foregroundLevel` sets how often; this look suits `medium` or `high`.
86
85
 
87
- **Three layers.** A defocused foreground, the person in the shadow in the mid-ground, and a
86
+ **Three layers, when a cut has a foreground.** A defocused foreground, the person in the shadow in the mid-ground, and a
88
87
  practical in the background that falls into bokeh. That depth is what the wide aperture exists for.
89
88
 
90
89
  ## 4. Lens — wide open, chosen per scene
@@ -46,7 +46,7 @@ storyboard (stage 5).
46
46
  | `scenes[].lighting[].prompt` | the preset's `framePhrase`, plus the sources that light the space | `scene-design` |
47
47
  | every `cut.frame` | the `framePhrase`, the direction phrase, `light.source` verbatim, the camera phrase, the foreground | `photography` |
48
48
  | every `cut.light` | `direction` (never one of the preset's `bannedLight` on a person), `source`, `note` | `photography` |
49
- | each segment | at least `preferredShare` of the cuts with people keyed from `preferredLight`; at least half with a foreground | `photography` |
49
+ | each segment | at least `preferredShare` of the cuts with people keyed from `preferredLight`; the episode's foreground share within `params.foregroundLevel` | `photography` |
50
50
  | every `cut.aperture` / `params.camera.aperture` | `maxStop` or faster | `photography` |
51
51
  | video prompt, `sheets` mode | the Look line carries the preset's `videoPhrase` (for `wan`: a `画面:` line with `videoPhraseZh`). `prompt segment` writes it for you | `video-prompt` |
52
52
 
@@ -19,7 +19,27 @@ text for a machine, not prose for a reader.
19
19
  what is received → one or two visible signals in a channel the size can read (gaze, breath,
20
20
  body set, object handling) → the decision (an action taken or withheld) → the landing at the
21
21
  end boundary. A wide cannot carry an eyelid; a close-up cannot carry a full-body retreat.
22
- Keeping still is a valid performance when it is the character's strategy.
22
+ Keeping still is a valid performance when it is the character's strategy — and even then they
23
+ breathe and blink (`idle: "still"`).
24
+
25
+ ### Everyone on screen is alive (gate `idle-motion`, sheets mode)
26
+
27
+ A model freezes whoever the prompt gives no motion, so a sub-character stands in the background like
28
+ a photo. `motion` is the one action that carries the cut; everyone else who is visible gets a small
29
+ motion of their own in `cut.idle: { C02: "shifts her weight, picking at her sleeve" }`. The skeleton
30
+ writes it into the shot. The rule, per cut with two or more visible people: all but the one carrying
31
+ the action have one (an over-the-shoulder's shoulder does not count). Give the speaker one too when
32
+ they would otherwise only talk.
33
+
34
+ - **Take it from why they are there.** Waiting: shifts weight, fiddles with a strap, checks the door.
35
+ Working: the work carries on. Listening: a small nod, the eyes moving over the other's face. Tense:
36
+ a tightening jaw, a thumb rubbing a finger. Do not reuse one motion across people or cuts: two people
37
+ breathing in time reads as a loop.
38
+ - **Small enough not to steal the cut:** one motion, in place. A line, a moving mouth or a change of
39
+ place is a cut's action, not an idle (gated, as is a length over 90 characters).
40
+ - **It must not break the boundaries:** hands, held props and positions end where `end` says.
41
+ - **Crowds and extras** with no sheet go in `background` as a motion, not a list of people: "shoppers
42
+ drifting past far behind her".
23
43
  4. **Camera** — `cameraNote`. A lock-off is written like a move: the framing, **what still moves
24
44
  inside it**, how long it holds (`hold`). An empty camera line is read as "free to drift", and
25
45
  the model grows a push-in nobody asked for. A move names its `motivation` (reveal / pressure
@@ -88,6 +88,30 @@ adjective:
88
88
  tighter) is a statement of power — make it on purpose.
89
89
  - **Over-the-shoulder** names whose shoulder is in the foreground (`foreground`); that decides
90
90
  whose shot it is.
91
+
92
+ ### Two people talking
93
+
94
+ A model puts into the shot everyone the shot names, and "looking at him" gives it no direction. In
95
+ sheets mode the gate `two-person-framing` holds every two-person segment to these, and `prompt
96
+ segment` writes the phrases for you:
97
+
98
+ - **Default pattern:** a two-shot once to set who stands where → **over-the-shoulder**, alternating,
99
+ for the exchange → a **clean single** for the punchline or the biggest reaction. A reaction lands
100
+ harder alone, so do not put every shot over a shoulder.
101
+ - **A single is single.** `characters` holds one person, the shot opens `Only @imageK in frame.`, and
102
+ the rest of the shot never describes the other person. Their reaction gets its own cut.
103
+ - **Every look has a direction.** `eyeline: { C01: "screen-left down" }` — `screen-left` or
104
+ `screen-right`, then `level`, `up` or `down`. The direction is toward the other person, so it follows
105
+ from the side they stand on. `"away screen-left"` is a deliberate look elsewhere; `"task"` keeps the
106
+ eyes on what the hands are doing. Height comes from the blocking: a person kneeling looks up, the
107
+ one standing looks down.
108
+ - **The sides are locked.** `axis.screenLeft` / `screenRight` become a `Sides:` line in the prompt, and
109
+ every shot holds them. Moving a person across needs `axisSide: cross` with a `note`.
110
+ - **Over-the-shoulder is its own setting.** `ots: "C01"` is the person whose shoulder is near the lens;
111
+ `characters` holds both people. The prompt says their face is not visible, so only one face is left
112
+ for the lip-sync and the eyes. It counts as a foreground for `params.foregroundLevel`, and that person
113
+ needs no `eyeline`. When one person is much lower, shoot up past the low shoulder or down past the
114
+ high one, and keep the sides.
91
115
  - **Crossing the axis** is legal with a neutral shot, an in-shot crossing, a deliberate
92
116
  disorientation or a re-established geography — `axisSide: cross` with a `note` that says which;
93
117
  the new working side is then inherited by the cuts after it.
@@ -0,0 +1,65 @@
1
+ # Picture edit and mix
2
+
3
+ Cut the episode before scoring it. The timeline stays in the drama folder, next to `project.json`, so a later session can reopen the cut. The mp4 uploaded to WJI is only the deliverable.
4
+
5
+ - Picture: [FilmCraft](https://getartcraft.com/apps/filmcraft), command `filmcraft-cli`. On macOS the CLI is a separate zip; the `.app` does not put it on `PATH`.
6
+ - Audio: [SoundCraft](https://getartcraft.com/apps/soundcraft), command `soundcraft-cli`.
7
+ - If `filmcraft-cli` is not installed, cut with ffmpeg. If `soundcraft-cli` is not installed, lay the mix with ffmpeg.
8
+
9
+ Look commands up. Do not invent ids.
10
+
11
+ ```bash
12
+ command -v filmcraft-cli && filmcraft-cli commands place
13
+ filmcraft-cli describe timeline.razor
14
+ command -v soundcraft-cli && soundcraft-cli commands
15
+ ```
16
+
17
+ ## Picture
18
+
19
+ Download each bound clip into `edits/E01/clips/` (segment id as the filename, story order) before cutting, so the project points at files that stay with the drama.
20
+
21
+ **FilmCraft** when `filmcraft-cli` is on `PATH`:
22
+
23
+ ```bash
24
+ filmcraft-cli --project edits/E01.fcproj --save import \
25
+ edits/E01/clips/E01-01.mp4 edits/E01/clips/E01-02.mp4
26
+ filmcraft-cli --project edits/E01.fcproj inspect sequence
27
+ filmcraft-cli --project edits/E01.fcproj export edits/E01-edit.mp4
28
+ ```
29
+
30
+ `--save` writes the timeline back to `edits/E01.fcproj`. Import puts media in the project; place and trim with a command you have `describe`d, clips in story order. `inspect sequence` is the cut list the score conforms to. Export the locked picture with no score yet. Leave `E01.fcproj` in the project after the upload. If `--project` refuses a missing file, follow `filmcraft-cli help` and still save to `edits/E01.fcproj`.
31
+
32
+ **ffmpeg** when `filmcraft-cli` is missing. Say that you fell back. There is no timeline to reopen; take cut times from the scene filter in the mix-over-video steps.
33
+
34
+ ```bash
35
+ # clips.txt, one line each: file 'edits/E01/clips/E01-01.mp4'
36
+ ffmpeg -f concat -safe 0 -i clips.txt -c copy edits/E01-edit.mp4
37
+ ```
38
+
39
+ Re-encode (`-c:v libx264 -c:a aac`) only when `-c copy` fails because the clips do not share a codec or timebase.
40
+
41
+ Upload `edits/E01-edit.mp4` and bind `wji.assets.edit`. Picture lock: do not change the cut's length after the cue sheet is written. A later trim means conforming the sheet again.
42
+
43
+ ## Audio
44
+
45
+ Lay the score sections under the locked picture. Dialogue already in the clips stays the anchor.
46
+
47
+ **SoundCraft** when `soundcraft-cli` is on `PATH`. The session is `edits/E01/E01.scraft`; SoundCraft keeps its media in `edits/E01/Audio Files/` beside that file. Discover import and edit ids with `soundcraft-cli commands`, then:
48
+
49
+ ```bash
50
+ soundcraft-cli run --in edits/E01/E01.scraft --cmd '<id>={...}'
51
+ soundcraft-cli run --in edits/E01/E01.scraft --bounce edits/E01/E01-mix.wav
52
+ ```
53
+
54
+ The bounce JSON reports integrated LUFS (`lufs`) and true peak (`true_peak_db`). Leave the `.scraft` and `Audio Files/` in the project.
55
+
56
+ **ffmpeg** when `soundcraft-cli` is missing: offset each stem with `adelay` and `amix`, and measure with `ffmpeg -af ebur128=peak=true`.
57
+
58
+ Mux a wav bounce onto the locked picture so WJI receives an mp4:
59
+
60
+ ```bash
61
+ ffmpeg -i edits/E01-edit.mp4 -i edits/E01/E01-mix.wav -map 0:v:0 -map 1:a:0 \
62
+ -c:v copy -c:a aac -shortest edits/E01-mix.mp4
63
+ ```
64
+
65
+ Upload `edits/E01-mix.mp4` and bind `wji.assets.mix`.
@@ -20,6 +20,8 @@ Rule: if the video already has it, don't add it again. Listen first and list wha
20
20
 
21
21
  ## Step 1. Analyse the video (local, free; no WJI)
22
22
 
23
+ If `edits/E01.fcproj` exists, take the cut list from `filmcraft-cli --project edits/E01.fcproj inspect sequence` (see `edit-tools.md`). Otherwise detect cuts from the exported picture:
24
+
23
25
  ```bash
24
26
  V=input.mp4
25
27
  ffprobe -v error -show_entries format=duration:stream=codec_type,r_frame_rate,codec_name -of json "$V"
@@ -74,6 +76,8 @@ Actions: `enter | swell | duck | drop | silence | sting | riser | whoosh | out |
74
76
 
75
77
  ## Step 5. Mix spec (for whoever lays it under the video)
76
78
 
79
+ Lay the stems with SoundCraft when `soundcraft-cli` is installed, and keep `edits/E01/E01.scraft`. Use ffmpeg only when that CLI is missing (`edit-tools.md`).
80
+
77
81
  - Dialogue is the anchor: centred; everything sits under it.
78
82
  - BGM ducks under dialogue via sidechain from the video's audio (or automation on the dialogue spans from Step 1). No standard fixes the dB; judge by ear.
79
83
  - BGE/ambience low and continuous; crossfade across clip seams.
@@ -85,5 +89,5 @@ Actions: `enter | swell | duck | drop | silence | sting | riser | whoosh | out |
85
89
  - No accidental vocals or dialogue (use spectrogram; ASR on music hallucinates, ignore nonsense text).
86
90
  - No doubled Foley or SFX against the video.
87
91
  - 起承轉合 arc audible; every `SPECIAL:` hit present at its timecode.
88
- - Loudness measured with `ffmpeg -af ebur128=peak=true` on the final mix.
92
+ - Loudness: the SoundCraft bounce JSON (`lufs`, `true_peak_db`) when the mix was bounced there; otherwise `ffmpeg -af ebur128=peak=true` on the final mix.
89
93
  FAIL → Producer before any paid regen.
@@ -15,13 +15,13 @@ node {baseDir}/scripts/short-drama.mjs <cmd> {project} …
15
15
 
16
16
  | Operation | Command |
17
17
  | --- | --- |
18
- | `init` | `short-drama.mjs init {project} --title "<t>" --episodes N --minutes M --genre "<g>" --register 台湾国语 [--adapt 抽核] [--ratio 9:16] [--video h3\|seedance\|seedance2\|seedance2fast\|wan] [--resolution 480P…] [--accent "<accent>"] [--style photoreal\|realistic\|ghibli] [--references sheets\|keyframes] [--duration auto\|fixed] [--look "<lens>"] [--cinematography low-key\|sweet-romance\|period-nostalgia\|costume-idol\|costume-dark\|scifi-hard\|scifi-soft\|none] [--action <style>\|none] [--violence blood\|implied] [--grammar drama\|action\|faceslap\|comedy] [--comedy slapstick\|deadpan\|romcom\|none] [--composition standard\|art-first] [--story revenge,sweet,…] [--market mainland\|overseas] [--format short\|medium\|movie] [--lang zh]` |
18
+ | `init` | `short-drama.mjs init {project} --title "<t>" --episodes N --minutes M --genre "<g>" --register 台湾国语 [--adapt 抽核] [--ratio 9:16] [--video h3\|seedance\|seedance2\|seedance2fast\|wan] [--resolution 480P…] [--accent "<accent>"] [--style photoreal\|realistic\|ghibli] [--references sheets\|keyframes] [--duration auto\|fixed] [--look "<lens>"] [--cinematography low-key\|sweet-romance\|period-nostalgia\|costume-idol\|costume-dark\|scifi-hard\|scifi-soft\|none] [--action <style>\|none] [--violence blood\|implied] [--grammar drama\|action\|faceslap\|comedy] [--comedy slapstick\|deadpan\|romcom\|none] [--composition standard\|art-first] [--story revenge,sweet,…] [--market mainland\|overseas] [--format short\|medium\|movie] [--lang zh] [--image-lang en\|zh]` |
19
19
  | `validate` | `short-drama.mjs validate {project} [--stage <stage>] [--ep N]` — exits 1 on a hard failure, 0 on advisories alone |
20
20
  | `checkup` | `short-drama.mjs checkup {project}` — same, but prints the passing and skipped rows too |
21
21
  | `seed` | `short-drama.mjs seed {project} script --ep N [--force]` · `… seed {project} storyboard --ep N [--force]` |
22
22
  | `render` | `short-drama.mjs render {project} outline \| guideline \| character C01 \| scene S01 \| prop P01 \| script --ep N \| dialogue --ep N \| segment E01-01` |
23
- | `dashboard` | `short-drama.mjs dashboard {project} [--output dashboard.html] [--assets <dir>] [--force]` — writes one self-contained production dashboard; each visual slot keeps a placeholder, and accepts a WJI image/video URL or a local file |
24
- | `prompt` | `short-drama.mjs prompt {project} segment E01-01 [--json]` · `… prompt {project} audio E01-01 [--full\|--bed]` · `… prompt {project} score E01 [--fps 24] [--json]` (post soundtrack spotting sheet) · `… prompt {project} voice C01` |
23
+ | `dashboard` | `short-drama.mjs dashboard {project} [--output dashboard.html] [--force]` — if `dashboard.html` is missing beside `project.json`, copy the static page there; if it is already there, leave it (`--force` replaces it). The page loads `project.json` itself. Asset ids are resolved with `POST /assets/check-status` (same `url` as `GET /assets/list/:id`); images are not downloaded. Canvas and dataset ids link to `https://app.wji.ai/app/workspace/<workspaceId>` |
24
+ | `prompt` | `short-drama.mjs prompt {project} segment E01-01 [--json]` · `… prompt {project} audio E01-01 [--full\|--bed]` · `… prompt {project} score E01 [--fps 24] [--json]` (post soundtrack spotting sheet) · `… prompt {project} voice C01` · `… prompt {project} image C01\|S01\|P01 [--asset headshot\|sheet\|lighting:<state>\|state:<state>] [--json]` |
25
25
  | `manifest` | `short-drama.mjs manifest {project} [--stage <stage>] [--ep N] [--json]` — exit 2 while anything is unbound |
26
26
  | `bind` | `short-drama.mjs bind {project} [--ep N] <path> <value>` — record a WJI id in the JSON |
27
27
 
@@ -58,13 +58,15 @@ All commands accept either the directory or the `project.json` path.
58
58
  | `speechAccent` | empty | e.g. `台湾口音国语` / `Taiwanese-accented Mandarin`. Any segment with dialogue must name it in its prompt (the video model speaks the lines) |
59
59
  | `references` | `sheets` | `sheets` (**the default — no storyboard images**): the video node references the scene / character / prop sheets directly (`segmentReferences` order: environment, people, props) and the prompt describes each shot. `keyframes`: one generated image per cut, the video node references them in cut order — more images to pay for and approve, worth it when a shot has to be composed exactly. Works with every mode; H3 in sheets mode opens with an `Image k is …` legend instead of the alignment line |
60
60
  | `humanReferences` | `true` | `false` drops character sheets from the video node's references (people are described in the prompt's Cast line instead). Any Seedance mode + `photoreal` requires it to be set explicitly: BytePlus refuses photorealistic people as reference images (`input image may contain real person`, HTTP 400) unless the account has the real-person permission enabled |
61
+ | `boundaryContrastCuts` | `2` | how many cuts on each side of a scene boundary the `boundary-contrast` gate compares (1–3); the last K cuts of a scene must not repeat the size + angle + people of the first K of the next scene. Mark a deliberate one with `note: "match-cut"`. The same check across episodes is the advisory `boundary-contrast-episode` |
61
62
  | `videoDuration` | `auto` | `auto` (**the default**) passes no `-d` and lets the model's `autoDuration` match the clip to the prompt; the cut seconds are then the *planned* timing. `fixed` passes `-d` = Σ cut seconds, and is what `init`/`paramsOf` fall back to on a model with no `autoDuration` parameter (today: `minimax-h3-ref`). Asking for `auto` on such a model is a gate failure, not a silent downgrade |
62
63
  | `camera` | ARRI ALEXA Mini LF / Zeiss Supreme Prime / T1.5 | the production's camera package: `{ body, lens, aperture }`. Every cut's `frame` names it verbatim, so the whole drama looks shot on one rig. A cut may override `focal` and `aperture` |
63
64
  | `videoReferences` | `none` | `previous` hands each segment the clip before it as `@video1` (continuity of grade, wardrobe and camera language); `none` leaves the sheets to carry it. Opt-in, because it chains the segments: regenerating one invalidates the reference of every segment after it |
64
65
  | `maxVideoRefs` / `maxAudioRefs` | per mode | how many clip / voice references the model takes; set them lower to tighten, never higher |
65
66
  | `lookPhraseZh` | empty | the same look phrase in the drama's language, gated in a **Chinese** prompt (`wan`) where the English `lookPhrase` would be both out of place and extra length for the content filter to judge |
66
67
  | `speechLeadSeconds` | 0.5 | added to a cut's dialogue seconds before the fit check — the mouth opens after the cut starts; also where a line starts in the `full` soundtrack prompt |
67
- | `audioTrack` | `post` | where the soundtrack is made. **`post`** (the default): the clips carry dialogue, ambience and in-picture sfx but **no music** (`No music.` / `non_diegetic_music: N/A` / `无配乐`) and no `post: true` sfx — music and sound design cannot carry across separately generated segments, so they are made once over the **edited episode**, on its timecode (`prompt score E01`, then `../short-drama-sound/reference/mix-over-video.md`); no per-segment audio node. `bed` (ambience + sfx + timed music, no voices) / `full` (the bed plus every line in the connected `@Audio1..3` voices — at most three speakers): a Seed Audio node per segment, music in the clip prompt. `none`: no soundtrack work |
68
+ | `audioTrack` | `post` | where the soundtrack is made. **`post`** (the default): the clips carry dialogue, ambience and in-picture sfx but **no music** (`No music.` / `non_diegetic_music: N/A` / `无配乐`) and no `post: true` sfx — music and sound design cannot carry across separately generated segments, so they are made once over the **edited episode**, on its timecode (`prompt score E01`, then `mix-over-video.md`); no per-segment audio node. `bed` (ambience + sfx + timed music, no voices) / `full` (the bed plus every line in the connected `@Audio1..3` voices — at most three speakers): a Seed Audio node per segment, music in the clip prompt. `none`: no soundtrack work |
69
+ | `foregroundLevel` | `medium`; `high` for 唯美 / 美感优先 (`composition: art-first`, `cinematography: sweet-romance` or `costume-idol`, a genre naming 唯美 or 美感) | how often a cut shoots past a foreground, measured across the episode: `low` up to 25 % of the cuts, `medium` 25–50 %, `high` 50 % or more (gate `photography`; `--foreground` at init). Which cuts: `stage-storyboard.md` |
68
70
  | `scoreFps` | 24 | frame rate `prompt score` writes SMPTE `HH:MM:SS:FF` at (`--fps` overrides); set it to the edit's real fps |
69
71
  | `subtitles` | false | `true` drops the subtitles-off sentence requirement from the prompts |
70
72
  | `maxFrameChars` | 900 | frames longer than this are flagged as padding (advisory) |
@@ -78,6 +80,7 @@ All commands accept either the directory or the `project.json` path.
78
80
  | `violence` | `blood` | `blood` lets frames and prompts show blood and wounds (never dismemberment, decapitation or organs); `implied` shows impact only through reaction and sound |
79
81
  | `cinematography` | picked by `init` from the genre (`none` when the key is missing) | the lighting and composition preset, `CINEMATOGRAPHY_PRESETS`: `low-key` (practical sources, people in shadow, side / back / rim light, negative space), `sweet-romance` (soft bright faces, backlit halo, pastel, the couple's distance as the story), `period-nostalgia` (film look, window sun and bare bulbs, ≤ T4), `scifi-hard` (engineered light, one harsh sun, no magic or holograms), `scifi-soft` (holograms, neon, dual-tone colour), `costume-idol` (luminous, gauze, backlit silk), `costume-dark` (firelight and lattice windows, faces in shadow); both costume presets need a wide with negative space in every segment or `none`. Every preset: a named `light.source`, a foreground, ≤ T2.8 unless it says otherwise (`sweet-romance` / `costume-idol`: ≤ T2, a T1.3 package from `init`, half the frames naming their bokeh); the period and costume ones refuse sources from the wrong era. Gated in `scene-design`, `photography` and `video-prompt`; see `cinematography.md` |
80
82
  | `lookPhrase` | empty | a lens / look phrase (e.g. `wide-aperture lens, shallow depth of field, creamy background bokeh and soft out-of-focus foreground elements`) that every segment prompt must carry verbatim |
83
+ | `imagePromptLang` | `en` | the language of every character / scene / prop image prompt (portrait, sheet, negative, lighting states, prop states): `en`, or `zh` when the image model takes Chinese — 简体 or 繁體, gated in that language (`init --image-lang zh`). Video prompts follow `params.video`; `identityEn`, `voice.prompt` and `videoDirection` stay English. `prompt <project> image C01 --asset headshot\|sheet\|lighting:<state>\|state:<state>` prints a node's full prompt in it |
81
84
  | `style` | `realistic` | `photoreal` (live action), `realistic` (semi-realistic painterly) or `ghibli`; one style for the whole drama (see stage-characters) |
82
85
  | `register` | required | the 语体 the lines are written in: `粤语` / `大陆普通话` / `台湾国语` / `港式普通话` / `古装` / `年代` / `英文美式` / `英文英式` (traditional spellings accepted). When `speechAccent` is empty it is derived from this in the prompt's language (`standard Mandarin` / `标准普通话`). A character may override it in `bible.speech.register`. The `dialogue-register` gate is **advisory** — it notes lines that break the register and never fails `validate` |
83
86
  | `hookSeconds` | 15 | the hook image (`hookBeat`) must be on screen by this cumulative second |
@@ -120,7 +123,7 @@ written exactly like 2.5 — they differ only in the ceiling (15 s, not 30) and
120
123
  | `merge[]`, `risks[]` | no | `{what, why}` / `{what, plan}` |
121
124
  | `cutNote`, `mergeNote` | no | decision summaries shown to the user at sign-off |
122
125
  | `brief` | yes | the 创作简报 — `{logline, audience, satisfactionEngine, mainHook, protagonistPressure, antagonistPressure, emotionalLine, promise}`, drama language, no quotes; `satisfactionEngine` names one of `打脸 / 复仇 / 强宠 / 身份揭露 / 智商碾压 / 情感释放` (and may say how it varies) |
123
- | `structure[]` | yes | the three phases `{phase: "golden"|"escalation"|"climax", episodes: [from, to], task, satisfactionPlan, hookPlan}` in that order, tiling `1..episodes`; golden ends within the first ~20 % (and never before episode 3 when there are ≥ 6). Two or fewer episodes: golden + climax only |
126
+ | `structure[]` | yes | the three phases `{phase: "golden"|"escalation"|"climax", episodes: [from, to], task, satisfactionPlan, hookPlan}` in that order, tiling `1..episodes`; golden ends within the first ~20 % (and never before episode 3 when there are ≥ 6). Two episodes: golden + climax only. **One episode**: golden + climax, both `[1, 1]` (retention and payoff in the same 集) |
124
127
  | `guideline` | yes | the 创作指引 — `{tone, emotionalIntensity, dialogueStyle, visualStyle, productionLimits, forbidden[] (≥3), priorities[] (3–5)}`; `render guideline` publishes it as its own dataset and it is re-read before every stage |
125
128
  | `beats[]` | ≥1 | `{id:"B01", type, weight:"major"|"minor", episode, setup, payoff}`; `episode` is the only place a beat's position lives |
126
129
  | `episodes[]` | exactly `params.episodes` | `{ep, function[], task: {goal, obstacle, stake}, change, satisfaction: {type, action}, hookType, synopsis, hook, suspense, sceneIds[], characterIds[], propIds[], crowdPlan?, warnings[]}` — narrative prose, **no dialogue quotes** (`「」『』“”`). `function` ⊆ `开局钩子 / 冲突升级 / 身份揭露 / 情感推进 / 大反转 / 清算收束`; `satisfaction.type` ∈ the six 爽点 types and `action` is the visible action that does it; `hookType` ∈ `身份 / 危机 / 结果 / 情感 / 反转 / 道具`. Episode 1 includes 开局钩子; with ≥ 3 episodes, episode 2's 爽点 type is the brief's engine and episode 3 includes 冲突升级 / 身份揭露 / 大反转; the finale includes 清算收束 or 大反转 |
@@ -183,11 +186,12 @@ id or list) and `gag` (`setup` / `build` / `punchline`) with `runningGag`.
183
186
  | --- | --- | --- |
184
187
  | `tier` | — | `lead` (1–5, `arc` required) / `support` (≤10, `arc` required) / `functional` (≤10, `name` is a role label such as 急诊医生, `arc` optional) |
185
188
  | `from[]` | — | change record: source character, merges, or `["原创"]` |
186
- | `oneLiner`, `profile.*`, `voice.timbre…emotion` | `lang` | human-readable |
189
+ | `disguise` | `lang` | optional `{ name, revealEpisode }` for a character who hides who they are (a villain under a false name, a secret identity): before `revealEpisode` every episode document — `render script` / `dialogue` / `segment`, the seed note — calls them `name` so the crew's scripts do not spoil the reveal. `name` is treated as an alias by the name gates. Gate `tiers`: `name` set and ≠ the real name, `revealEpisode` in 2–episodes |
190
+ | `oneLiner`, `profile.*`, `voice.timbre…emotion` | `lang` | human-readable. **No names or aliases**: in a Chinese (`wan`) prompt `timbre / pace / accent / emotion` are pasted beside the character's first line, and all six go into the voice-sample prompt — gate `voice` |
187
191
  | `profile.evidence[]` | source | verbatim quotes, never translated |
188
- | `image.prompt` | **English** | portrait prompt; **no names or aliases**; must state ethnicity and era |
189
- | `image.negativePrompt` | English | style-dependent, see stage-characters |
190
- | `image.sheet` | English | 16:9 three-zone sheet prompt; no names; contains the style's render phrase |
192
+ | `image.prompt` | **`params.imagePromptLang`** (English by default) | portrait prompt; **no names or aliases**; must state ethnicity and era |
193
+ | `image.negativePrompt` | `imagePromptLang` | style-dependent, see stage-characters |
194
+ | `image.sheet` | `imagePromptLang` | 16:9 three-panel sheet (headshot filling the height, front full body with the face covered by a grey box, back full body), grey-white studio background, studio lighting; no names; contains the style's render phrase (`STYLE_PRESETS[style].render`, or `.renderZh` in Chinese) |
191
195
  | `voice.prompt` | English | ≤400 characters, a static voice identity, **no quoted lines** |
192
196
  | `voice.videoDirection` | **English** | ≤160 characters, one clause: how they speak (pace, where the voice sits, what it does at the end of a phrase, accent). It is pasted beside **every line this character says** in a video prompt, and gated there — compress `voice.prompt`, do not repeat it |
193
197
  | `identityEn` | **English** | ≤80 characters, the name-free way a prompt refers to this person (`the woman in her mid thirties in headphones`). Distinct per character; no names. Used to attribute dialogue in `keyframes` mode and anywhere a `@imageK` label is not available |
@@ -209,8 +213,8 @@ id or list) and `gag` (`setup` / `build` / `punchline`) with `runningGag`.
209
213
 
210
214
  Outline stage: `id`, `name`, `primary`, `reusePlan` (required for a scene used in
211
215
  only one episode). Art stage: `summary` (design intent), `anchors` (3–5,
212
- paintable and checkable), `lighting` (≥1, English prompts, derived from the
213
- episodes it appears in), `image.*` (English, **empty scene — negative prompt
216
+ paintable and checkable), `lighting` (≥1, prompts in `imagePromptLang`, derived from the
217
+ episodes it appears in), `image.*` (`imagePromptLang`, **empty scene — negative prompt
214
218
  must ban people**), `variantOf` + `changes` for variants of a parent scene.
215
219
  `wji.nodes.lighting` / `wji.assets.lighting` are keyed by lighting `state`.
216
220
 
@@ -283,14 +287,14 @@ prompt bans hands**). At most `thresholds.maxProps` (8).
283
287
  | Field | Rules |
284
288
  | --- | --- |
285
289
  | `targetSeconds` | seeded from `params`; the episode estimate must be within `± tolerance` |
286
- | `hook` / `cliff` | descriptions of the opening hook and closing suspense, not lines |
290
+ | `hook` / `cliff` | descriptions of the opening hook and closing suspense, not lines. `seed script` leaves `hook` empty (the writer describes the opening image) and fills `cliff` from the outline episode's `hook` |
287
291
  | `beatsClaimed[]` | outline beat ids this episode realises; must equal the beats whose `episode` is this one |
288
292
  | `hookBeat` | `[sceneIndex, beatIndex]` (1-based) where the hook's concrete image lands — within the first `hookWindow` beats of the episode and by `hookSeconds` of cumulative time |
289
293
  | `function` / `task` / `change` / `hookType` | seeded from the outline episode and gated equal to it (`script-design`) |
290
294
  | `satisfactionBeat` | `[sceneIndex, beatIndex]` of the 爽点 — an action beat, or a line with an action beat beside it; after the hook, before the last beat |
291
295
  | `flow[].turn` | `true` on a beat that brings new information, an obstacle, a counter or a choice; the `rhythm` gate measures hook → turns → 爽点 → cliff against `turnSeconds` |
292
296
  | `scenes[].sceneId` / `lighting` / `characters` / `props` | must exist in `project.json`; `lighting` must be a state of that scene |
293
- | `flow[]` | alternating **action beats** `{action}` (narrative, one thing per beat, **common actions only**, no quotes) and **line beats** `{speaker, line, delivery?}`; `speaker` ∈ scene `characters` or `VO`; `line` ≤ `maxLineChars` non-whitespace characters |
297
+ | `flow[]` | alternating **action beats** `{action}` (narrative, one thing per beat, **common actions only**, no quotes) and **line beats** `{speaker, line, delivery?}`; `speaker` ∈ scene `characters` or `VO` (an off-screen voice: it gets no `@audio` reference, the prompt calls it off-screen and keeps every visible mouth closed while it speaks); `line` ≤ `maxLineChars` non-whitespace characters; `delivery` names nobody when the video prompt carries it (a Chinese `wan` prompt, or an English delivery) — gate `beat-flow` |
294
298
 
295
299
  Duration: `Σ lines (chars ÷ charsPerSecond) + Σ actions × actionSeconds`.
296
300
 
@@ -321,8 +325,12 @@ Duration: `Σ lines (chars ÷ charsPerSecond) + Σ actions × actionSeconds`.
321
325
  | `prompt` | the video prompt, shape by `params.video` (below); gate `prompt-hygiene` (no `…`, no ids / paths / notes, subtitles off unless `params.subtitles`, H3 lips-closed clause, sheets-mode `@imageK` per shot) |
322
326
  | `segments[].axis` | `{ between: [C01, C02], workingSide, screenLeft, screenRight }`, required when two or more people appear in the segment |
323
327
  | `segments[].grammar` / `exceptions` | `grammar` (a string or an array) says how the segment is cut — `drama` · `action` · `faceslap` · `comedy`; missing, it is `params.grammar`; the old `action: true` means `action`. Each grammar's rules apply (gates `grammar`, `action`, `faceslap`, `comedy`; advisory `action-advice`, `grammar-advice`) and the prompt carries each grammar's line. `exceptions: [{ rule, reason }]` waives one rule for this segment when the user asks — rules `open-wide` `one-move` `impact-reaction` `no-graphic` `reveal-insert` `reaction-chain` `punchline-reaction` `setup-first` (the old `actionExceptions` still works) |
328
+ | `cuts[].idle` | sheets mode: `{ <id>: "shifts her weight" }`, a small motion in place for a person other than the one carrying the action, or `"still"` (breathing and blinking only). In a cut with two or more visible people, all but one need one (gate `idle-motion`) |
329
+ | `cuts[].eyeline` | sheets mode, two-person segments, **required**: `{ <id>: "screen-left\|screen-right level\|up\|down" }` for everyone on screen, or `"away screen-left"`, or `"task"` (gate `two-person-framing`) |
330
+ | `cuts[].ots` | the person whose shoulder is near the lens in an over-the-shoulder; `characters` holds both people; counts as a foreground; that person needs no `eyeline` |
324
331
  | `segments[].sound` | `{ ambience, sfx: [{ cut, event, post? }], music }` — ambience identical across a scene's segments; `music` is `N/A`, an intent string, or `{ intent, cues: [{ at: <cut number \| "10.5s">, do: enter \| swell \| duck \| drop \| silence \| out, note }] }` — gate `sound`. An sfx with `post: true` is **sound design outside the story world** (stinger, riser, whoosh, impact hit, reverse cymbal) — never in the clip prompt, made over the edit. In `audioTrack: post` the `music` is the **spotting intent** for the episode score: it never enters the clip, and `prompt score E01` lays it, the post sfx, the lines and the scene beds on the episode timeline. In `bed` / `full`, `prompt audio E01-01` derives the segment's Seed Audio soundtrack prompt from it (timecoded `[start s:end s]` ambience / sfx / music, plus the lines with `--full`) — gate `audio-track` |
325
332
  | `wji.assets.edit` / `wji.assets.mix` (episode) | `audioTrack: post` only — the locked edit of the episode (clips cut together, no music) and the final mixed episode (edit + score + sound design). `manifest` asks for the edit once every clip is bound, then for the mix |
333
+ | `segments[].refOverride[]` | optional — the Director pins this segment's **picture** references instead of the derived set (environment, people, props): `[{ entity, asset?: "sheet" \| "headshot" \| "lighting:<state>", faceOf?, why?, use?, zh? }]` in the order they will be connected (`@image1…`). `faceOf: "C01"` says this picture supplies that character's face, so their lines bind to it even when a body sheet of theirs comes later. Voices and the previous clip are still appended after the pictures. An empty array means "derived" |
326
334
  | `storyboard.visualPlans[]` | `{ sceneIndex, turn, alignment, pressure, progression, rhythm, landing, sound }` for the 爽点 scene and the cliff scene (seeded) — gate `visual-plan` |
327
335
  | `props[].textPolicy` | optional `{ mode: readable \| symbolic \| blank, text }` — gate `text-policy` |
328
336
 
@@ -61,23 +61,32 @@ the `seedNote` and `render dialogue` puts them beside each line for the polish p
61
61
  **must** ban `photorealistic`. Both gated. Always add `logo, brand name, text` — a "store
62
62
  uniform" comes back wearing a real chain's badge otherwise (seen in testing), and a real brand
63
63
  on a character is a problem in every shot after it.
64
- - `image.sheet`: the model-sheet layout, written out in full every time:
64
+ - `image.sheet`: the three-panel sheet, written out in full every time. Left to right on
65
+ one 16:9 canvas, grey-white seamless studio background, studio lighting:
65
66
 
66
67
  ```
67
- ┌──────────┬────────────────────────────┐
68
- │ bust │ front side back │ ONE 16:9 canvas, pure white background
69
- │ portrait ├────────────────────────────┤ LEFT ZONE ≈ 34 % width: head-and-shoulders bust,
70
- │ (face │ detail · detail · detail │ both shoulders complete, flat bottom edge
71
- │ ref) │ │ RIGHT-TOP: three full-body views, same height,
72
- └──────────┴────────────────────────────┘ same head-to-body ratio, PROPORTIONS ARE CRITICAL
73
- RIGHT-BOTTOM: 4–5 small detail studies;
74
- the detail studies give way, not the figures
68
+ ┌────────────┬────────────┬────────────┐
69
+ │ headshot │ front │ back │ ONE 16:9 canvas
70
+ │ face fills│ full body │ full body │ grey-white studio background
71
+ │ the height│ grey box │ │ studio lighting
72
+ │ │ over face │ │
73
+ └────────────┴────────────┴────────────┘
75
74
  ```
76
75
 
77
- Plus: `all views share the SAME face as the bust portrait`, `LIGHTING IN THE LEFT ZONE ONLY:
78
- soft directional key…`, `LIGHTING IN THE RIGHT ZONES: flat even orthographic lighting`, the
79
- style's render phrase (`STYLE_PRESETS[style].render` — `node scripts/short-drama.mjs` exports
80
- it; copy it verbatim), `No text, no watermark`. Gated: `16:9`, render phrase, no names.
76
+ ```
77
+ Single character sheet on ONE 16:9 landscape canvas, three panels left to right,
78
+ grey-white seamless studio background, studio lighting. <style render phrase>.
79
+ LEFT: one headshot, the face filling the full height of the panel, facial features
80
+ large and clear. CENTER: full-body front view, the face completely covered by a flat
81
+ grey box so no facial features are visible. RIGHT: full-body back view, same height
82
+ and body proportions as the front view, feet on the same ground line. Subject:
83
+ <ethnicity, age, era, identity, hair, outfit>. Same costume on both full-body views.
84
+ No side view, no extra characters, no scenery, no text, no watermark.
85
+ ```
86
+
87
+ The face lives only in the left headshot. The center panel is the costume and the body.
88
+ Copy the style render phrase verbatim (`STYLE_PRESETS[style].render` — `node scripts/short-drama.mjs`
89
+ exports it). Gated: `16:9`, render phrase, no names.
81
90
 
82
91
  ## Voice
83
92
 
@@ -167,9 +176,10 @@ invents a voice that fits it, which is why the headshot must be approved first.
167
176
  one reference, so pass only the headshot node. About 3.6 credits a sample.
168
177
 
169
178
  Generate headshots first (a set of headshots is a batch — quote the total), **look at them**
170
- (download and open), then sheets and voices from the approved headshots. Check a sheet for: one
171
- face across all views, three full bodies of equal height, no squashed figures, white background.
172
- Regenerate rather than accept a sheet with two faces — every shot inherits it. Listen to the voice
179
+ (download and open), then sheets and voices from the approved headshots. Check a sheet for: the
180
+ headshot face fills the panel height, the front full body has its face covered by a flat grey
181
+ box, the back full body matches that height, grey-white studio background. Regenerate rather
182
+ than accept a sheet with a visible face on the front body — every shot inherits it. Listen to the voice
173
183
  before you attach it: the age and the weight of the voice should match the face.
174
184
 
175
185
  Attach and bind:
@@ -163,11 +163,37 @@ tables are in `cinematography.md` and the preset's guide.
163
163
 
164
164
  ### Foreground
165
165
 
166
- Depth needs something to be shallow *against*. `foreground` names what sits between the lens and
167
- the subject — a console edge, a curtain of rain off an awning, a blurred shoulder, a doorframe —
168
- and the frame prompt says it is thrown out of focus. `""` is a deliberately clean frame, and the
169
- gate allows it for up to half the cuts of a segment: a segment of clean frames reads flat, and a
170
- drama where every shot is clean reads like a corporate video.
166
+ `foreground` names what sits between the lens and the subject — a console edge, a curtain of rain
167
+ off an awning, a blurred shoulder, a doorframe — and the frame prompt says it is thrown out of focus.
168
+ `""` is a deliberately clean frame.
169
+
170
+ **How often** is `params.foregroundLevel`, gated across the episode so scenes can differ:
171
+
172
+ | Level | Cuts with a foreground | Use |
173
+ |---|---|---|
174
+ | `low` | up to 25 % | clean, direct, face-led; only where the story asks for one |
175
+ | `medium` (default) | 25–50 % | where the story or the shot type gives a reason |
176
+ | `high` (default for 唯美 / 美感优先) | 50 % or more | a layered, voyeuristic or crowded look; soft, composed beauty |
177
+
178
+ 唯美 / 美感优先 means `composition: art-first`, `cinematography: sweet-romance` or `costume-idol`, or a genre
179
+ that says 唯美 or 美感. Naming `foregroundLevel` overrides this.
180
+
181
+ **Which cuts** — story first, then shot type, then shot size. Remove it in your head: if the audience
182
+ loses no meaning, shoot the cut clean.
183
+
184
+ 1. **Story decides whether.** Threat (the barrel in front of him), watching or hiding (a doorframe, a
185
+ curtain), trapped or outnumbered (bodies closing in), distance between two people, power (the
186
+ powerful one's shoulder or weapon), closeness (her hair, soft), a clue that matters later.
187
+ 2. **Shot type decides what.** Over-the-shoulder always has one (the listener's shoulder); POV has what
188
+ they look past; an establishing shot has a layer of the place. Usually clean: a reaction close-up, an
189
+ insert, the 打脸 proof or reveal, an action impact, a moment of isolation.
190
+ 3. **Shot size decides how much.** A wide can take a large shape on one edge; a medium one edge; a close
191
+ a soft sliver, never near the eyes or mouth; an extreme close-up almost never.
192
+
193
+ Take it from the scene: it must already be there and on the camera's side of the action. Keep it off
194
+ the face, the mouth, the key prop and the action. Prefer simple still shapes (edges, frames, shoulders)
195
+ over foreground faces and hands, which can read as an extra person. Do not repeat one object across
196
+ cuts unless it is a story motif. At `high`, keep the same reasons and simply use them more.
171
197
 
172
198
  Directing feel (not gated): reverse angles for dialogue (asker's close-up, answerer's close-up);
173
199
  the first cut of an episode moves; a key action gets its own 2 s insert; a reaction shot after a
@@ -412,8 +438,9 @@ score. So by default (`params.audioTrack: post`) **the clips carry no music**: t
412
438
  says `No music.` (H3 `non_diegetic_music: N/A`, Wan `无配乐`), keeps the dialogue, the ambience and
413
439
  the in-picture sfx, and drops every sfx marked `post: true` (stingers, risers, whooshes, impact
414
440
  hits — sound design outside the story world). The storyboard still writes `sound.music` (intent and
415
- cues) and the `post: true` hits: they are the **spotting notes**. When every clip is in, the episode
416
- is edited, and the Sound Designer scores the edit as one piece on its timecode:
441
+ cues) and the `post: true` hits: they are the **spotting notes**. When every clip is in, cut the
442
+ episode as in `edit-tools.md` (FilmCraft, or ffmpeg if `filmcraft-cli` is missing; the `.fcproj`
443
+ stays in the project), and score that locked picture as one piece on its timecode:
417
444
 
418
445
  ```bash
419
446
  $SD prompt <project> score E01 # planned spotting sheet: SMPTE rows + ≤120 s Seed Audio sections
@@ -73,7 +73,7 @@ the headshot + sheet as image references and the voice as audio.
73
73
  ```
74
74
 
75
75
  - headshot: `canvas add image -n "C01 headshot" -m <image model> -r 1:1 -p "<image.prompt>"`
76
- - sheet: `canvas add image -n "C01 sheet" --from <headshotNode> -m <image model> -r 16:9 -p "@image1 is the face to match. <image.sheet>"`
76
+ - sheet: `canvas add image -n "C01 sheet" --from <headshotNode> -m <image model> -r 16:9 -p "@image1 is the face to match exactly. <image.sheet>"`
77
77
  - voice: `canvas add audio -n "C01 voice" -m <audio/tts model> -p "<voice.prompt>"` (optional; skip when no voice model is enabled)
78
78
  - title: `canvas add title -t "C01 林小夏" --at <x>,<y>` — one per row, rows 700 px apart
79
79