@wji.ai/cli 0.8.0 → 0.8.2
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.
- package/dist/index.js +6 -0
- package/package.json +8 -9
- package/skill/short-drama/SKILL.md +43 -5
- package/skill/short-drama/dashboard.html +863 -0
- package/skill/short-drama/examples/night-shift/episodes/E01.json +1 -1
- package/skill/short-drama/reference/canvas-recipes.md +1 -1
- package/skill/short-drama/reference/edit-tools.md +65 -0
- package/skill/short-drama/reference/mix-over-video.md +5 -1
- package/skill/short-drama/reference/operations-cli.md +3 -2
- package/skill/short-drama/reference/schema.md +14 -10
- package/skill/short-drama/reference/stage-storyboard.md +3 -2
- package/skill/short-drama/scripts/selftest.mjs +333 -2
- package/skill/short-drama/scripts/short-drama.mjs +416 -64
|
@@ -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
|
-
#
|
|
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
|
```
|
|
@@ -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
|
|
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,12 +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
|
-
| `
|
|
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]` |
|
|
24
25
|
| `manifest` | `short-drama.mjs manifest {project} [--stage <stage>] [--ep N] [--json]` — exit 2 while anything is unbound |
|
|
25
26
|
| `bind` | `short-drama.mjs bind {project} [--ep N] <path> <value>` — record a WJI id in the JSON |
|
|
26
27
|
|
|
@@ -58,13 +58,14 @@ 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
|
|
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 |
|
|
68
69
|
| `scoreFps` | 24 | frame rate `prompt score` writes SMPTE `HH:MM:SS:FF` at (`--fps` overrides); set it to the edit's real fps |
|
|
69
70
|
| `subtitles` | false | `true` drops the subtitles-off sentence requirement from the prompts |
|
|
70
71
|
| `maxFrameChars` | 900 | frames longer than this are flagged as padding (advisory) |
|
|
@@ -78,6 +79,7 @@ All commands accept either the directory or the `project.json` path.
|
|
|
78
79
|
| `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
80
|
| `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
81
|
| `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 |
|
|
82
|
+
| `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
83
|
| `style` | `realistic` | `photoreal` (live action), `realistic` (semi-realistic painterly) or `ghibli`; one style for the whole drama (see stage-characters) |
|
|
82
84
|
| `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
85
|
| `hookSeconds` | 15 | the hook image (`hookBeat`) must be on screen by this cumulative second |
|
|
@@ -120,7 +122,7 @@ written exactly like 2.5 — they differ only in the ceiling (15 s, not 30) and
|
|
|
120
122
|
| `merge[]`, `risks[]` | no | `{what, why}` / `{what, plan}` |
|
|
121
123
|
| `cutNote`, `mergeNote` | no | decision summaries shown to the user at sign-off |
|
|
122
124
|
| `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
|
|
125
|
+
| `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
126
|
| `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
127
|
| `beats[]` | ≥1 | `{id:"B01", type, weight:"major"|"minor", episode, setup, payoff}`; `episode` is the only place a beat's position lives |
|
|
126
128
|
| `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 +185,12 @@ id or list) and `gag` (`setup` / `build` / `punchline`) with `runningGag`.
|
|
|
183
185
|
| --- | --- | --- |
|
|
184
186
|
| `tier` | — | `lead` (1–5, `arc` required) / `support` (≤10, `arc` required) / `functional` (≤10, `name` is a role label such as 急诊医生, `arc` optional) |
|
|
185
187
|
| `from[]` | — | change record: source character, merges, or `["原创"]` |
|
|
186
|
-
| `
|
|
188
|
+
| `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 |
|
|
189
|
+
| `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
190
|
| `profile.evidence[]` | source | verbatim quotes, never translated |
|
|
188
|
-
| `image.prompt` |
|
|
189
|
-
| `image.negativePrompt` |
|
|
190
|
-
| `image.sheet` |
|
|
191
|
+
| `image.prompt` | **`params.imagePromptLang`** (English by default) | portrait prompt; **no names or aliases**; must state ethnicity and era |
|
|
192
|
+
| `image.negativePrompt` | `imagePromptLang` | style-dependent, see stage-characters |
|
|
193
|
+
| `image.sheet` | `imagePromptLang` | 16:9 three-zone sheet prompt; no names; contains the style's render phrase (`STYLE_PRESETS[style].render`, or `.renderZh` in Chinese) |
|
|
191
194
|
| `voice.prompt` | English | ≤400 characters, a static voice identity, **no quoted lines** |
|
|
192
195
|
| `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
196
|
| `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 +212,8 @@ id or list) and `gag` (`setup` / `build` / `punchline`) with `runningGag`.
|
|
|
209
212
|
|
|
210
213
|
Outline stage: `id`, `name`, `primary`, `reusePlan` (required for a scene used in
|
|
211
214
|
only one episode). Art stage: `summary` (design intent), `anchors` (3–5,
|
|
212
|
-
paintable and checkable), `lighting` (≥1,
|
|
213
|
-
episodes it appears in), `image.*` (
|
|
215
|
+
paintable and checkable), `lighting` (≥1, prompts in `imagePromptLang`, derived from the
|
|
216
|
+
episodes it appears in), `image.*` (`imagePromptLang`, **empty scene — negative prompt
|
|
214
217
|
must ban people**), `variantOf` + `changes` for variants of a parent scene.
|
|
215
218
|
`wji.nodes.lighting` / `wji.assets.lighting` are keyed by lighting `state`.
|
|
216
219
|
|
|
@@ -283,14 +286,14 @@ prompt bans hands**). At most `thresholds.maxProps` (8).
|
|
|
283
286
|
| Field | Rules |
|
|
284
287
|
| --- | --- |
|
|
285
288
|
| `targetSeconds` | seeded from `params`; the episode estimate must be within `± tolerance` |
|
|
286
|
-
| `hook` / `cliff` | descriptions of the opening hook and closing suspense, not lines |
|
|
289
|
+
| `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
290
|
| `beatsClaimed[]` | outline beat ids this episode realises; must equal the beats whose `episode` is this one |
|
|
288
291
|
| `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
292
|
| `function` / `task` / `change` / `hookType` | seeded from the outline episode and gated equal to it (`script-design`) |
|
|
290
293
|
| `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
294
|
| `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
295
|
| `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
|
|
296
|
+
| `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
297
|
|
|
295
298
|
Duration: `Σ lines (chars ÷ charsPerSecond) + Σ actions × actionSeconds`.
|
|
296
299
|
|
|
@@ -323,6 +326,7 @@ Duration: `Σ lines (chars ÷ charsPerSecond) + Σ actions × actionSeconds`.
|
|
|
323
326
|
| `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) |
|
|
324
327
|
| `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
328
|
| `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 |
|
|
329
|
+
| `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
330
|
| `storyboard.visualPlans[]` | `{ sceneIndex, turn, alignment, pressure, progression, rhythm, landing, sound }` for the 爽点 scene and the cliff scene (seeded) — gate `visual-plan` |
|
|
327
331
|
| `props[].textPolicy` | optional `{ mode: readable \| symbolic \| blank, text }` — gate `text-policy` |
|
|
328
332
|
|
|
@@ -412,8 +412,9 @@ score. So by default (`params.audioTrack: post`) **the clips carry no music**: t
|
|
|
412
412
|
says `No music.` (H3 `non_diegetic_music: N/A`, Wan `无配乐`), keeps the dialogue, the ambience and
|
|
413
413
|
the in-picture sfx, and drops every sfx marked `post: true` (stingers, risers, whooshes, impact
|
|
414
414
|
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
|
|
416
|
-
|
|
415
|
+
cues) and the `post: true` hits: they are the **spotting notes**. When every clip is in, cut the
|
|
416
|
+
episode as in `edit-tools.md` (FilmCraft, or ffmpeg if `filmcraft-cli` is missing; the `.fcproj`
|
|
417
|
+
stays in the project), and score that locked picture as one piece on its timecode:
|
|
417
418
|
|
|
418
419
|
```bash
|
|
419
420
|
$SD prompt <project> score E01 # planned spotting sheet: SMPTE rows + ≤120 s Seed Audio sections
|