@milenyumai/film-kit 2.2.0 → 2.3.0
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/README.md +68 -17
- package/build/index.d.ts +1 -1
- package/build/lib/cli.js +2 -2
- package/build/lib/film-kit.js +6 -4
- package/build/lib/storyboard-reference/adapters/kling30.js +3 -1
- package/build/lib/storyboard-reference/adapters/seedance20.d.ts +4 -0
- package/build/lib/storyboard-reference/adapters/seedance20.js +72 -13
- package/build/lib/storyboard-reference/adapters/veo31.js +3 -1
- package/build/lib/storyboard-reference/index.d.ts +1 -1
- package/build/lib/storyboard-reference/output-writer.js +84 -6
- package/build/lib/storyboard-reference/prompt-bundle-builder.js +295 -8
- package/build/lib/storyboard-reference/request-normalizer.js +8 -4
- package/build/lib/storyboard-reference/storyboard-interpreter.d.ts +3 -1
- package/build/lib/storyboard-reference/storyboard-interpreter.js +21 -1
- package/build/lib/storyboard-reference/types.d.ts +151 -2
- package/build/lib/storyboard-reference/validators.js +2 -5
- package/build/lib/templates.js +10 -6
- package/content/ARCHITECTURE.md +4 -4
- package/content/MASTER.md +2 -2
- package/content/RULES.md +4 -4
- package/content/agents/prompt-engineer.md +7 -7
- package/content/skills/prompt-structure/SKILL.md +14 -11
- package/content/skills/reference-locking/SKILL.md +6 -4
- package/content/skills/semantic-consistency/SKILL.md +1 -1
- package/content/skills/storyboard-reference/SKILL.md +54 -13
- package/content/workflows/generate-storyboard.md +37 -16
- package/content/workflows/generate.md +7 -7
- package/content/workflows/safety-check.md +2 -2
- package/package.json +1 -1
- package/packages/gpt-image-smart/content/skills/storyboard-reference/SKILL.md +104 -12
- package/packages/gpt-image-smart/content/workflows/generate-storyboard.md +89 -12
- package/packages/hybrid/content/skills/storyboard-reference/SKILL.md +104 -12
- package/packages/hybrid/content/workflows/generate-storyboard.md +89 -12
- package/packages/hybrid-smart/content/skills/storyboard-reference/SKILL.md +104 -12
- package/packages/hybrid-smart/content/workflows/generate-storyboard.md +89 -12
- package/packages/multi/build/cli.js +39 -0
- package/packages/multi/build/index.d.ts +1 -1
- package/packages/multi/build/lib/configure.js +208 -1
- package/packages/multi/build/lib/defaults.d.ts +3 -1
- package/packages/multi/build/lib/defaults.js +32 -0
- package/packages/multi/build/lib/templates.js +146 -60
- package/packages/multi/build/lib/types.d.ts +16 -0
- package/packages/multi/content/agents/continuity-editor.md +6 -6
- package/packages/multi/content/agents/delivery-editor.md +2 -2
- package/packages/multi/content/agents/lead-director.md +18 -10
- package/packages/multi/content/agents/semantic-auditor.md +4 -5
- package/packages/multi/content/agents/shot-generator.md +9 -27
- package/packages/multi/content/skills/storyboard-reference/SKILL.md +104 -12
- package/packages/multi/content/workflows/chain-multi.md +4 -4
- package/packages/multi/content/workflows/generate-multi.md +6 -6
- package/packages/multi/content/workflows/generate-storyboard.md +89 -12
- package/packages/multi/content/workflows/generate-teammate.md +8 -14
- package/packages/multi/content/workflows/safety-check-multi.md +7 -11
- package/packages/studio/content/skills/storyboard-reference/SKILL.md +104 -12
- package/packages/studio/content/workflows/generate-storyboard.md +89 -12
|
@@ -3,19 +3,29 @@ import { Kling30PromptAdapter } from "./adapters/kling30.js";
|
|
|
3
3
|
import { Seedance20PromptAdapter } from "./adapters/seedance20.js";
|
|
4
4
|
import { Veo31PromptAdapter } from "./adapters/veo31.js";
|
|
5
5
|
import { normalizeVideoPromptRequest } from "./request-normalizer.js";
|
|
6
|
-
import { interpretStoryboard } from "./storyboard-interpreter.js";
|
|
6
|
+
import { buildStoryboardPhaseBudget, inferContinuityMode, interpretStoryboard } from "./storyboard-interpreter.js";
|
|
7
7
|
import { assertSafeStoryboardReferenceRequest, validateModelPromptOutput, validatePromptBundle } from "./validators.js";
|
|
8
8
|
const ADAPTERS = {
|
|
9
9
|
veo31: new Veo31PromptAdapter(),
|
|
10
10
|
"seedance-2.0": new Seedance20PromptAdapter(),
|
|
11
11
|
"kling-3.0": new Kling30PromptAdapter()
|
|
12
12
|
};
|
|
13
|
-
function getStoryboardPanelCount(request) {
|
|
14
|
-
return request.storyboardPanelCountHint
|
|
13
|
+
function getStoryboardPanelCount(request, phaseBudget) {
|
|
14
|
+
return request.storyboardPanelCountHint
|
|
15
|
+
?? request.shotCountHint
|
|
16
|
+
?? phaseBudget.recommendedMaxPhases;
|
|
15
17
|
}
|
|
16
18
|
function buildShotId(index) {
|
|
17
19
|
return `SHOT${String(index + 1).padStart(2, "0")}`;
|
|
18
20
|
}
|
|
21
|
+
function safeFileId(value) {
|
|
22
|
+
const normalized = value
|
|
23
|
+
.trim()
|
|
24
|
+
.replace(/[^a-z0-9_-]+/gi, "-")
|
|
25
|
+
.replace(/^-+|-+$/g, "")
|
|
26
|
+
.toUpperCase();
|
|
27
|
+
return normalized || "REFERENCE";
|
|
28
|
+
}
|
|
19
29
|
function getSplitPhaseCounts(panelCount, maxPhases) {
|
|
20
30
|
const counts = [];
|
|
21
31
|
let remaining = panelCount;
|
|
@@ -26,6 +36,243 @@ function getSplitPhaseCounts(panelCount, maxPhases) {
|
|
|
26
36
|
}
|
|
27
37
|
return counts.length > 0 ? counts : [1];
|
|
28
38
|
}
|
|
39
|
+
function detectProviderFileKind(pathOrUrl) {
|
|
40
|
+
const cleanPath = pathOrUrl.toLowerCase().split("?")[0] ?? "";
|
|
41
|
+
if (/\.(png|jpe?g|webp|gif|avif|heic|heif)$/.test(cleanPath))
|
|
42
|
+
return "image";
|
|
43
|
+
if (/\.(mp4|mov|webm|m4v|avi|mkv)$/.test(cleanPath))
|
|
44
|
+
return "video";
|
|
45
|
+
if (/\.(mp3|wav|m4a|aac|ogg|flac|aiff?)$/.test(cleanPath))
|
|
46
|
+
return "audio";
|
|
47
|
+
return "unknown";
|
|
48
|
+
}
|
|
49
|
+
function buildProviderFileLimitReport(request, generatedImageReferenceCount) {
|
|
50
|
+
const counts = {
|
|
51
|
+
image: generatedImageReferenceCount,
|
|
52
|
+
video: 0,
|
|
53
|
+
audio: 0,
|
|
54
|
+
unknown: 0
|
|
55
|
+
};
|
|
56
|
+
for (const asset of request.additionalRefs) {
|
|
57
|
+
counts[detectProviderFileKind(asset.pathOrUrl)] += 1;
|
|
58
|
+
}
|
|
59
|
+
const observedTotalFiles = generatedImageReferenceCount + request.additionalRefs.length;
|
|
60
|
+
const report = {
|
|
61
|
+
maxImages: 9,
|
|
62
|
+
maxVideos: 3,
|
|
63
|
+
maxAudio: 3,
|
|
64
|
+
maxTotalFiles: 12,
|
|
65
|
+
observedImages: counts.image,
|
|
66
|
+
observedVideos: counts.video,
|
|
67
|
+
observedAudio: counts.audio,
|
|
68
|
+
observedUnknown: counts.unknown,
|
|
69
|
+
observedTotalFiles,
|
|
70
|
+
withinLimits: counts.image <= 9
|
|
71
|
+
&& counts.video <= 3
|
|
72
|
+
&& counts.audio <= 3
|
|
73
|
+
&& observedTotalFiles <= 12
|
|
74
|
+
};
|
|
75
|
+
return report;
|
|
76
|
+
}
|
|
77
|
+
function buildShotHandoffNote(input) {
|
|
78
|
+
const firstPhase = input.storyboardInterpretation.phases[0];
|
|
79
|
+
const lastPhase = input.storyboardInterpretation.phases.at(-1);
|
|
80
|
+
return {
|
|
81
|
+
shotId: input.shotId,
|
|
82
|
+
continuityMode: input.continuityMode,
|
|
83
|
+
phaseCount: input.storyboardInterpretation.phases.length,
|
|
84
|
+
entryHandoff: firstPhase
|
|
85
|
+
? `${firstPhase.timeRange}: ${firstPhase.subjectAction}`
|
|
86
|
+
: "No storyboard phase entry available.",
|
|
87
|
+
exitHandoff: lastPhase
|
|
88
|
+
? `${lastPhase.timeRange}: ${lastPhase.handoffTrigger ?? lastPhase.subjectAction}`
|
|
89
|
+
: "No storyboard phase exit available."
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function buildCharacterReferenceSheetPrompts(assets, outputDir) {
|
|
93
|
+
return assets.characters.map((character, index) => {
|
|
94
|
+
const id = `character-sheet-${character.asset.id}`;
|
|
95
|
+
const sourceLabel = character.asset.label ?? character.asset.id;
|
|
96
|
+
const outputPath = `${outputDir}/reference-prep/CHARACTER-SHEET-${safeFileId(character.asset.id)}.md`;
|
|
97
|
+
return {
|
|
98
|
+
id,
|
|
99
|
+
characterAssetId: character.asset.id,
|
|
100
|
+
sourceToken: character.token,
|
|
101
|
+
outputPath,
|
|
102
|
+
provider: "gpt-image-2",
|
|
103
|
+
aspectRatio: "16:9",
|
|
104
|
+
cellCount: 6,
|
|
105
|
+
generatedOnce: true,
|
|
106
|
+
promptText: `Create one 16:9 realistic production storyboard character reference sheet from ${character.token} (${sourceLabel}). Use the uploaded reference only as the identity source. Preserve the exact face, skull shape, hair, skin texture, body proportions, wardrobe, accessories, and visible props; do not beautify, redesign, age-shift, or stylize the character. Arrange six clean storyboard cells in a single widescreen sheet: front full body, back full body, left profile, right profile, three-quarter full body, and close-up face. Keep the same wardrobe and props in every cell. Use neutral studio lighting, consistent scale, uncluttered pale gray background, readable silhouette, realistic pencil-and-wash production storyboard finish, subtle tonal shading, and no dramatic lens distortion. No text, labels, arrows, captions, panel numbers, watermarks, logos, extra characters, alternate outfits, or background story elements. This sheet is generated once and becomes the identity reference for all later shot storyboards.${index === 0 ? "" : " Match visual finish to the earlier character sheets."}`
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function buildStoryboardImagePrompt(input) {
|
|
111
|
+
const { request, assets, shotId, interpretation, characterReferenceSheetPrompts } = input;
|
|
112
|
+
const characterSheetIds = characterReferenceSheetPrompts.map(prompt => prompt.id);
|
|
113
|
+
const externalStoryboardGuideIds = assets.storyboards.map(storyboard => storyboard.asset.id);
|
|
114
|
+
const sheetRefs = characterReferenceSheetPrompts
|
|
115
|
+
.map((prompt, index) => `${prompt.id} as character ${index + 1}`)
|
|
116
|
+
.join(", ");
|
|
117
|
+
const externalGuideText = assets.storyboards.length > 0
|
|
118
|
+
? `Use external storyboard guide tokens ${assets.storyboards.map(storyboard => storyboard.token).join(", ")} only for composition, blocking, camera direction, timing, and action rhythm.`
|
|
119
|
+
: "No external storyboard guide is required; infer the board from the screenplay brief, continuity notes, and visual world.";
|
|
120
|
+
return {
|
|
121
|
+
shotId,
|
|
122
|
+
outputPath: `${request.outputDir}/storyboard-prompts/${shotId}-GPT-IMAGE-2-STORYBOARD.md`,
|
|
123
|
+
provider: "gpt-image-2",
|
|
124
|
+
aspectRatio: "16:9",
|
|
125
|
+
panelCount: interpretation.panelCount,
|
|
126
|
+
style: "realistic-production-storyboard",
|
|
127
|
+
characterSheetIds,
|
|
128
|
+
externalStoryboardGuideIds,
|
|
129
|
+
previousShotHandoff: input.previousShotHandoff,
|
|
130
|
+
nextShotHandoff: input.nextShotHandoff,
|
|
131
|
+
promptText: `Create a professional 16:9 realistic production storyboard image for ${shotId} with exactly ${interpretation.panelCount} readable panel${interpretation.panelCount === 1 ? "" : "s"}. Use the generated character sheet reference${characterReferenceSheetPrompts.length === 1 ? "" : "s"} (${sheetRefs}) as the only source for character identity, face, body proportions, wardrobe, accessories, and visible props. ${externalGuideText} The scene brief is: ${request.brief}. Continue from the previous handoff: ${input.previousShotHandoff}. End on the next handoff: ${input.nextShotHandoff}. Show the camera plan clearly: ${interpretation.cameraPlan.framing}, ${interpretation.cameraPlan.movement}, ${interpretation.cameraPlan.lens}, preserving ${interpretation.cameraPlan.screenDirection}. Maintain the same environment, lighting, atmosphere, color grade, foreground, midground, and background continuity: ${interpretation.visualWorld.environment}; ${interpretation.visualWorld.lighting}; ${interpretation.visualWorld.atmosphere}. Each panel must show only the phase action needed for the shot, with clean blocking, motivated eyelines, stable scale, readable silhouettes, and practical production-board detail. Do not create final render polish, alternate character designs, text, captions, labels, panel numbers, watermarks, logos, decorative borders, extra scene cuts, or new locations.`
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function isImageReference(asset) {
|
|
135
|
+
return detectProviderFileKind(asset.asset.pathOrUrl) === "image";
|
|
136
|
+
}
|
|
137
|
+
function buildProviderAssetMapping(assets, characterReferenceSheetPrompts, storyboardImagePrompt) {
|
|
138
|
+
const mapping = {};
|
|
139
|
+
characterReferenceSheetPrompts.forEach((sheet, index) => {
|
|
140
|
+
const token = `@Image${index + 1}`;
|
|
141
|
+
mapping[token] = {
|
|
142
|
+
token,
|
|
143
|
+
legacyAlias: `@image${index + 1}`,
|
|
144
|
+
sourceToken: sheet.sourceToken,
|
|
145
|
+
sourceAssetId: sheet.id,
|
|
146
|
+
role: "character_identity",
|
|
147
|
+
description: "Generated character sheet: identity, face, body proportions, wardrobe, accessories, and visible props only.",
|
|
148
|
+
generatedPromptPath: sheet.outputPath
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
const storyboardIndex = characterReferenceSheetPrompts.length + 1;
|
|
152
|
+
const storyboardToken = `@Image${storyboardIndex}`;
|
|
153
|
+
mapping[storyboardToken] = {
|
|
154
|
+
token: storyboardToken,
|
|
155
|
+
legacyAlias: `@image${storyboardIndex}`,
|
|
156
|
+
sourceToken: `@${storyboardImagePrompt.shotId.toLowerCase()}Storyboard`,
|
|
157
|
+
sourceAssetId: `${storyboardImagePrompt.shotId}-storyboard-image`,
|
|
158
|
+
role: "storyboard_plan",
|
|
159
|
+
description: "Generated shot storyboard: composition, blocking, camera direction, timing, action rhythm, and phase order only.",
|
|
160
|
+
generatedPromptPath: storyboardImagePrompt.outputPath
|
|
161
|
+
};
|
|
162
|
+
let nextImageIndex = storyboardIndex + 1;
|
|
163
|
+
for (const ref of assets.additional.filter(isImageReference)) {
|
|
164
|
+
const token = `@Image${nextImageIndex}`;
|
|
165
|
+
mapping[token] = {
|
|
166
|
+
token,
|
|
167
|
+
legacyAlias: `@image${nextImageIndex}`,
|
|
168
|
+
sourceToken: ref.token,
|
|
169
|
+
sourceAssetId: ref.asset.id,
|
|
170
|
+
role: ref.asset.role,
|
|
171
|
+
description: "Additional image reference; keep below character identity and generated storyboard authority."
|
|
172
|
+
};
|
|
173
|
+
nextImageIndex += 1;
|
|
174
|
+
}
|
|
175
|
+
return mapping;
|
|
176
|
+
}
|
|
177
|
+
function buildReferenceAssetRequirements(providerAssetMapping) {
|
|
178
|
+
return Object.values(providerAssetMapping).map(entry => {
|
|
179
|
+
const source = entry.role === "character_identity"
|
|
180
|
+
? "character-sheet"
|
|
181
|
+
: entry.role === "storyboard_plan"
|
|
182
|
+
? "shot-storyboard"
|
|
183
|
+
: "additional-reference";
|
|
184
|
+
const requirement = {
|
|
185
|
+
token: entry.token,
|
|
186
|
+
source,
|
|
187
|
+
assetId: entry.sourceAssetId,
|
|
188
|
+
role: entry.role,
|
|
189
|
+
required: entry.role === "character_identity" || entry.role === "storyboard_plan",
|
|
190
|
+
notes: entry.description
|
|
191
|
+
};
|
|
192
|
+
if (entry.legacyAlias)
|
|
193
|
+
requirement.legacyAlias = entry.legacyAlias;
|
|
194
|
+
if (entry.generatedPromptPath)
|
|
195
|
+
requirement.generatedPromptPath = entry.generatedPromptPath;
|
|
196
|
+
return requirement;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
function buildProviderReferenceTokens(providerAssetMapping) {
|
|
200
|
+
const characterIdentities = Object.values(providerAssetMapping)
|
|
201
|
+
.filter(entry => entry.role === "character_identity")
|
|
202
|
+
.map(entry => entry.token);
|
|
203
|
+
const storyboardReference = Object.values(providerAssetMapping)
|
|
204
|
+
.find(entry => entry.role === "storyboard_plan")?.token ?? "@Image2";
|
|
205
|
+
return {
|
|
206
|
+
characterIdentity: characterIdentities[0] ?? "@Image1",
|
|
207
|
+
characterIdentities,
|
|
208
|
+
storyboardReference,
|
|
209
|
+
legacyAliases: {
|
|
210
|
+
characterIdentity: characterIdentities[0]?.replace("@Image", "@image") ?? "@image1",
|
|
211
|
+
characterIdentities: characterIdentities.map(token => token.replace("@Image", "@image")),
|
|
212
|
+
storyboardReference: storyboardReference.replace("@Image", "@image")
|
|
213
|
+
},
|
|
214
|
+
videoReference: "@video1",
|
|
215
|
+
audioReference: "@audio1"
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function buildStoryboardReferencePolicy(request, phaseBudget, providerFileLimits, bundles, providerReferenceTokens, maxPhases, splitRequired) {
|
|
219
|
+
const continuityModes = Array.from(new Set(bundles.map(bundle => bundle.continuityMode)));
|
|
220
|
+
return {
|
|
221
|
+
project: {
|
|
222
|
+
reference_mode: "storyboard-reference"
|
|
223
|
+
},
|
|
224
|
+
storyboard_reference: {
|
|
225
|
+
enabled: true,
|
|
226
|
+
max_storyboard_phases: maxPhases,
|
|
227
|
+
recommended_phase_budget: phaseBudget,
|
|
228
|
+
provider_reference_tokens: providerReferenceTokens,
|
|
229
|
+
continuity_mode: continuityModes.length === 1
|
|
230
|
+
? continuityModes[0] ?? "multi-shot-storyboard"
|
|
231
|
+
: "mixed",
|
|
232
|
+
provider_file_limits: providerFileLimits,
|
|
233
|
+
character_reference_sheets: {
|
|
234
|
+
enabled: true,
|
|
235
|
+
provider: "gpt-image-2",
|
|
236
|
+
generated_once_per_character: true,
|
|
237
|
+
aspect_ratio: "16:9",
|
|
238
|
+
required_views: [
|
|
239
|
+
"front full body",
|
|
240
|
+
"back full body",
|
|
241
|
+
"left profile",
|
|
242
|
+
"right profile",
|
|
243
|
+
"three-quarter full body",
|
|
244
|
+
"close-up face"
|
|
245
|
+
]
|
|
246
|
+
},
|
|
247
|
+
storyboard_prompt_policy: {
|
|
248
|
+
enabled: true,
|
|
249
|
+
provider: "gpt-image-2",
|
|
250
|
+
generated_per_shot: true,
|
|
251
|
+
aspect_ratio: "16:9",
|
|
252
|
+
max_panels_per_shot: maxPhases,
|
|
253
|
+
panel_budget: phaseBudget.policy,
|
|
254
|
+
professional_prompt_target_words: "160-240 words per shot storyboard prompt"
|
|
255
|
+
},
|
|
256
|
+
role_separation: {
|
|
257
|
+
character_identity: `${providerReferenceTokens.characterIdentities.join(", ")} control identity, face, hair, body proportions, wardrobe, accessories, and visible props.`,
|
|
258
|
+
storyboard_reference: `${providerReferenceTokens.storyboardReference} controls composition, blocking, camera direction, timing, action rhythm, and phase order only.`,
|
|
259
|
+
storyboard_never_identity_source: [
|
|
260
|
+
"storyboard text",
|
|
261
|
+
"panel borders",
|
|
262
|
+
"watermarks",
|
|
263
|
+
"logos",
|
|
264
|
+
"alternate character designs"
|
|
265
|
+
]
|
|
266
|
+
},
|
|
267
|
+
split_policy: {
|
|
268
|
+
split_storyboard_overload: request.storyboardReferenceMode.splitStoryboardOverload,
|
|
269
|
+
split_at_phase_count: splitRequired ? maxPhases + 1 : phaseBudget.splitAtPhaseCount,
|
|
270
|
+
split_target: "SHOTNN.md"
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
shot_handoff_notes: bundles.map(bundle => bundle.handoffNote)
|
|
274
|
+
};
|
|
275
|
+
}
|
|
29
276
|
function buildModelPrompts(request, bundleInput) {
|
|
30
277
|
const modelPrompts = {};
|
|
31
278
|
for (const model of request.targetModels) {
|
|
@@ -45,8 +292,11 @@ export function buildStoryboardReferencePromptBundles(input) {
|
|
|
45
292
|
const request = normalizeVideoPromptRequest(input);
|
|
46
293
|
assertSafeStoryboardReferenceRequest(request);
|
|
47
294
|
const assets = resolveAssetRoles(request.characterRefs, request.storyboardRefs, request.additionalRefs);
|
|
48
|
-
const
|
|
49
|
-
const
|
|
295
|
+
const characterReferenceSheetPrompts = buildCharacterReferenceSheetPrompts(assets, request.outputDir);
|
|
296
|
+
const phaseBudget = buildStoryboardPhaseBudget(request.durationSeconds, request.storyboardReferenceMode.maxStoryboardPhases);
|
|
297
|
+
const providerFileLimits = buildProviderFileLimitReport(request, characterReferenceSheetPrompts.length + 1);
|
|
298
|
+
const panelCount = getStoryboardPanelCount(request, phaseBudget);
|
|
299
|
+
const maxPhases = phaseBudget.effectiveMaxStoryboardPhases;
|
|
50
300
|
const splitRequired = panelCount > maxPhases;
|
|
51
301
|
if (splitRequired && !request.storyboardReferenceMode.splitStoryboardOverload) {
|
|
52
302
|
throw new Error(`Storyboard has ${panelCount} phases, which exceeds maxStoryboardPhases=${maxPhases}. Enable splitStoryboardOverload or split the storyboard manually.`);
|
|
@@ -55,27 +305,61 @@ export function buildStoryboardReferencePromptBundles(input) {
|
|
|
55
305
|
? getSplitPhaseCounts(panelCount, maxPhases)
|
|
56
306
|
: [Math.min(panelCount, maxPhases)];
|
|
57
307
|
const generatedAt = new Date().toISOString();
|
|
58
|
-
const bundles =
|
|
308
|
+
const bundles = [];
|
|
309
|
+
let previousShotHandoff = "Project opening: start from the screenplay's first visual beat.";
|
|
310
|
+
phaseCounts.forEach((phaseCount, index) => {
|
|
311
|
+
const shotId = buildShotId(index);
|
|
59
312
|
const interpretation = interpretStoryboard(request, assets, phaseCount, splitRequired);
|
|
313
|
+
const continuityMode = inferContinuityMode(interpretation.phases);
|
|
314
|
+
const handoffNote = buildShotHandoffNote({
|
|
315
|
+
shotId,
|
|
316
|
+
continuityMode,
|
|
317
|
+
storyboardInterpretation: interpretation
|
|
318
|
+
});
|
|
319
|
+
const storyboardImagePrompt = buildStoryboardImagePrompt({
|
|
320
|
+
request,
|
|
321
|
+
assets,
|
|
322
|
+
shotId,
|
|
323
|
+
interpretation,
|
|
324
|
+
characterReferenceSheetPrompts,
|
|
325
|
+
previousShotHandoff,
|
|
326
|
+
nextShotHandoff: handoffNote.exitHandoff
|
|
327
|
+
});
|
|
328
|
+
const providerAssetMapping = buildProviderAssetMapping(assets, characterReferenceSheetPrompts, storyboardImagePrompt);
|
|
329
|
+
const referenceAssetRequirements = buildReferenceAssetRequirements(providerAssetMapping);
|
|
60
330
|
const modelPrompts = buildModelPrompts(request, {
|
|
61
331
|
assets,
|
|
62
332
|
interpretation,
|
|
333
|
+
continuityMode,
|
|
334
|
+
phaseBudget,
|
|
335
|
+
providerFileLimits,
|
|
336
|
+
storyboardImagePrompt,
|
|
337
|
+
providerAssetMapping,
|
|
63
338
|
visualWorld: interpretation.visualWorld
|
|
64
339
|
});
|
|
65
340
|
const bundle = {
|
|
66
|
-
shotId
|
|
341
|
+
shotId,
|
|
67
342
|
mode: "storyboard-reference",
|
|
68
343
|
durationSeconds: request.durationSeconds,
|
|
69
344
|
aspectRatio: request.aspectRatio,
|
|
70
345
|
modelPrompts,
|
|
71
346
|
storyboardInterpretation: interpretation,
|
|
72
347
|
assetRoles: assets.assetRoles,
|
|
348
|
+
continuityMode,
|
|
349
|
+
phaseBudget,
|
|
350
|
+
handoffNote,
|
|
351
|
+
storyboardImagePrompt,
|
|
352
|
+
referenceAssetRequirements,
|
|
353
|
+
providerAssetMapping,
|
|
73
354
|
qa: { verdict: "pass", checks: {}, issues: [], splitRequired },
|
|
74
355
|
generatedAt
|
|
75
356
|
};
|
|
76
357
|
bundle.qa = validatePromptBundle(bundle, assets);
|
|
77
|
-
|
|
358
|
+
bundles.push(bundle);
|
|
359
|
+
previousShotHandoff = handoffNote.exitHandoff;
|
|
78
360
|
});
|
|
361
|
+
const providerReferenceTokens = buildProviderReferenceTokens(bundles[0]?.providerAssetMapping ?? {});
|
|
362
|
+
const policy = buildStoryboardReferencePolicy(request, phaseBudget, providerFileLimits, bundles, providerReferenceTokens, maxPhases, splitRequired);
|
|
79
363
|
return {
|
|
80
364
|
plan: {
|
|
81
365
|
mode: "storyboard-reference",
|
|
@@ -83,10 +367,13 @@ export function buildStoryboardReferencePromptBundles(input) {
|
|
|
83
367
|
shotCount: bundles.length,
|
|
84
368
|
splitRequired,
|
|
85
369
|
assetRoles: assets.assetRoles,
|
|
370
|
+
characterReferenceSheetPrompts,
|
|
371
|
+
policy,
|
|
86
372
|
generatedAt
|
|
87
373
|
},
|
|
88
374
|
request,
|
|
89
375
|
assets,
|
|
376
|
+
characterReferenceSheetPrompts,
|
|
90
377
|
bundles
|
|
91
378
|
};
|
|
92
379
|
}
|
|
@@ -25,8 +25,11 @@ function assertAspectRatio(aspectRatio) {
|
|
|
25
25
|
}
|
|
26
26
|
return aspectRatio;
|
|
27
27
|
}
|
|
28
|
-
function assertReferenceAssets(assets, fieldName) {
|
|
29
|
-
if (assets.length === 0) {
|
|
28
|
+
function assertReferenceAssets(assets, fieldName, options) {
|
|
29
|
+
if (!assets || assets.length === 0) {
|
|
30
|
+
if (!options.required) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
30
33
|
throw new Error(`storyboard-reference mode requires at least one ${fieldName}.`);
|
|
31
34
|
}
|
|
32
35
|
for (const asset of assets) {
|
|
@@ -42,8 +45,8 @@ export function normalizeVideoPromptRequest(input) {
|
|
|
42
45
|
if (!input.brief.trim()) {
|
|
43
46
|
throw new Error("storyboard-reference mode requires a non-empty brief.");
|
|
44
47
|
}
|
|
45
|
-
assertReferenceAssets(input.characterRefs, "character reference");
|
|
46
|
-
assertReferenceAssets(input.storyboardRefs, "storyboard reference");
|
|
48
|
+
assertReferenceAssets(input.characterRefs, "character reference", { required: true });
|
|
49
|
+
assertReferenceAssets(input.storyboardRefs, "storyboard reference", { required: false });
|
|
47
50
|
const storyboardReferenceMode = resolveStoryboardReferenceRuntime(input.storyboardReferenceMode);
|
|
48
51
|
if (!storyboardReferenceMode.enabled) {
|
|
49
52
|
throw new Error("storyboard-reference mode is disabled by storyboardReferenceMode.enabled=false.");
|
|
@@ -51,6 +54,7 @@ export function normalizeVideoPromptRequest(input) {
|
|
|
51
54
|
const normalized = {
|
|
52
55
|
...input,
|
|
53
56
|
targetModels: assertSupportedModels(input.targetModels),
|
|
57
|
+
storyboardRefs: input.storyboardRefs ?? [],
|
|
54
58
|
durationSeconds: normalizePositiveInteger(input.durationSeconds, DEFAULT_STORYBOARD_DURATION_SECONDS, "durationSeconds"),
|
|
55
59
|
aspectRatio: assertAspectRatio(input.aspectRatio ?? DEFAULT_STORYBOARD_ASPECT_RATIO),
|
|
56
60
|
language: input.language?.trim() || DEFAULT_STORYBOARD_LANGUAGE,
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
import type { NormalizedVideoPromptRequest, ResolvedAssetRoles, StoryboardInterpretation } from "./types.js";
|
|
1
|
+
import type { NormalizedVideoPromptRequest, ResolvedAssetRoles, SeedanceContinuityMode, StoryboardInterpretation, StoryboardPhaseBudget, StoryboardPhase } from "./types.js";
|
|
2
|
+
export declare function buildStoryboardPhaseBudget(durationSeconds: number, configuredMaxStoryboardPhases: number): StoryboardPhaseBudget;
|
|
3
|
+
export declare function inferContinuityMode(phases: StoryboardPhase[]): SeedanceContinuityMode;
|
|
2
4
|
export declare function interpretStoryboard(request: NormalizedVideoPromptRequest, assets: ResolvedAssetRoles, phaseCount: number, splitRequired: boolean): StoryboardInterpretation;
|
|
@@ -36,6 +36,26 @@ function formatTimeRange(index, phaseCount, durationSeconds) {
|
|
|
36
36
|
const end = (durationSeconds * (index + 1)) / phaseCount;
|
|
37
37
|
return `${start.toFixed(1)}s-${end.toFixed(1)}s`;
|
|
38
38
|
}
|
|
39
|
+
export function buildStoryboardPhaseBudget(durationSeconds, configuredMaxStoryboardPhases) {
|
|
40
|
+
const durationPolicy = durationSeconds <= 6
|
|
41
|
+
? { min: 1, max: 2, policy: "4-6s:1-2" }
|
|
42
|
+
: durationSeconds <= 10
|
|
43
|
+
? { min: 2, max: 3, policy: "7-10s:2-3" }
|
|
44
|
+
: { min: 3, max: 4, policy: "11-15s:3-4" };
|
|
45
|
+
const effectiveMaxStoryboardPhases = Math.max(1, Math.min(configuredMaxStoryboardPhases, durationPolicy.max));
|
|
46
|
+
return {
|
|
47
|
+
durationSeconds,
|
|
48
|
+
providerDurationRangeSeconds: { min: 4, max: 15 },
|
|
49
|
+
recommendedMinPhases: durationPolicy.min,
|
|
50
|
+
recommendedMaxPhases: durationPolicy.max,
|
|
51
|
+
effectiveMaxStoryboardPhases,
|
|
52
|
+
splitAtPhaseCount: effectiveMaxStoryboardPhases + 1,
|
|
53
|
+
policy: durationPolicy.policy
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function inferContinuityMode(phases) {
|
|
57
|
+
return phases.length <= 1 ? "continuous-shot" : "multi-shot-storyboard";
|
|
58
|
+
}
|
|
39
59
|
function buildPhases(request, phaseCount) {
|
|
40
60
|
return Array.from({ length: phaseCount }, (_, index) => {
|
|
41
61
|
const job = PHASE_JOBS[Math.min(index, PHASE_JOBS.length - 1)] ?? "handoff";
|
|
@@ -63,7 +83,7 @@ export function interpretStoryboard(request, assets, phaseCount, splitRequired)
|
|
|
63
83
|
riskFlags.push("dialogue-needs-voice-cast");
|
|
64
84
|
}
|
|
65
85
|
return {
|
|
66
|
-
storyboardAssetId: assets.storyboards[0]?.asset.id ?? "
|
|
86
|
+
storyboardAssetId: assets.storyboards[0]?.asset.id ?? "generated-shot-storyboard",
|
|
67
87
|
panelCount: phaseCount,
|
|
68
88
|
phases: buildPhases(request, phaseCount),
|
|
69
89
|
editorialFunction: inferEditorialFunction(request.brief),
|
|
@@ -24,7 +24,7 @@ export interface VideoPromptRequest {
|
|
|
24
24
|
targetModels: SupportedModel[];
|
|
25
25
|
brief: string;
|
|
26
26
|
characterRefs: ReferenceAsset[];
|
|
27
|
-
storyboardRefs
|
|
27
|
+
storyboardRefs?: ReferenceAsset[];
|
|
28
28
|
additionalRefs?: ReferenceAsset[];
|
|
29
29
|
durationSeconds?: number;
|
|
30
30
|
aspectRatio?: HybridAspectRatio;
|
|
@@ -39,6 +39,7 @@ export interface VideoPromptRequest {
|
|
|
39
39
|
storyboardReferenceMode?: Partial<StoryboardReferenceConfig>;
|
|
40
40
|
}
|
|
41
41
|
export interface NormalizedVideoPromptRequest extends VideoPromptRequest {
|
|
42
|
+
storyboardRefs: ReferenceAsset[];
|
|
42
43
|
durationSeconds: number;
|
|
43
44
|
aspectRatio: HybridAspectRatio;
|
|
44
45
|
language: string;
|
|
@@ -66,6 +67,94 @@ export interface ResolvedAssetRoles {
|
|
|
66
67
|
label?: string;
|
|
67
68
|
}>;
|
|
68
69
|
}
|
|
70
|
+
export type SeedanceContinuityMode = "continuous-shot" | "multi-shot-storyboard";
|
|
71
|
+
export interface StoryboardPhaseBudget {
|
|
72
|
+
durationSeconds: number;
|
|
73
|
+
providerDurationRangeSeconds: {
|
|
74
|
+
min: 4;
|
|
75
|
+
max: 15;
|
|
76
|
+
};
|
|
77
|
+
recommendedMinPhases: number;
|
|
78
|
+
recommendedMaxPhases: number;
|
|
79
|
+
effectiveMaxStoryboardPhases: number;
|
|
80
|
+
splitAtPhaseCount: number;
|
|
81
|
+
policy: "4-6s:1-2" | "7-10s:2-3" | "11-15s:3-4";
|
|
82
|
+
}
|
|
83
|
+
export interface ProviderFileLimitReport {
|
|
84
|
+
maxImages: 9;
|
|
85
|
+
maxVideos: 3;
|
|
86
|
+
maxAudio: 3;
|
|
87
|
+
maxTotalFiles: 12;
|
|
88
|
+
observedImages: number;
|
|
89
|
+
observedVideos: number;
|
|
90
|
+
observedAudio: number;
|
|
91
|
+
observedUnknown: number;
|
|
92
|
+
observedTotalFiles: number;
|
|
93
|
+
withinLimits: boolean;
|
|
94
|
+
}
|
|
95
|
+
export interface ProviderReferenceTokenPolicy {
|
|
96
|
+
characterIdentity: string;
|
|
97
|
+
characterIdentities: string[];
|
|
98
|
+
storyboardReference: string;
|
|
99
|
+
legacyAliases: {
|
|
100
|
+
characterIdentity: string;
|
|
101
|
+
characterIdentities: string[];
|
|
102
|
+
storyboardReference: string;
|
|
103
|
+
};
|
|
104
|
+
videoReference: "@video1";
|
|
105
|
+
audioReference: "@audio1";
|
|
106
|
+
}
|
|
107
|
+
export interface ShotHandoffNote {
|
|
108
|
+
shotId: string;
|
|
109
|
+
continuityMode: SeedanceContinuityMode;
|
|
110
|
+
phaseCount: number;
|
|
111
|
+
entryHandoff: string;
|
|
112
|
+
exitHandoff: string;
|
|
113
|
+
}
|
|
114
|
+
export interface CharacterReferenceSheetPrompt {
|
|
115
|
+
id: string;
|
|
116
|
+
characterAssetId: string;
|
|
117
|
+
sourceToken: string;
|
|
118
|
+
outputPath: string;
|
|
119
|
+
provider: "gpt-image-2";
|
|
120
|
+
aspectRatio: "16:9";
|
|
121
|
+
cellCount: 6;
|
|
122
|
+
generatedOnce: true;
|
|
123
|
+
promptText: string;
|
|
124
|
+
}
|
|
125
|
+
export interface StoryboardImagePrompt {
|
|
126
|
+
shotId: string;
|
|
127
|
+
outputPath: string;
|
|
128
|
+
provider: "gpt-image-2";
|
|
129
|
+
aspectRatio: "16:9";
|
|
130
|
+
panelCount: number;
|
|
131
|
+
style: "realistic-production-storyboard";
|
|
132
|
+
characterSheetIds: string[];
|
|
133
|
+
externalStoryboardGuideIds: string[];
|
|
134
|
+
previousShotHandoff: string;
|
|
135
|
+
nextShotHandoff: string;
|
|
136
|
+
promptText: string;
|
|
137
|
+
}
|
|
138
|
+
export type ProviderAssetMappingRole = "character_identity" | "storyboard_plan" | "style_reference" | "camera_reference" | "action_reference" | "prop_reference";
|
|
139
|
+
export interface ProviderAssetMappingEntry {
|
|
140
|
+
token: string;
|
|
141
|
+
sourceToken: string;
|
|
142
|
+
sourceAssetId: string;
|
|
143
|
+
role: ProviderAssetMappingRole;
|
|
144
|
+
description: string;
|
|
145
|
+
legacyAlias?: string;
|
|
146
|
+
generatedPromptPath?: string;
|
|
147
|
+
}
|
|
148
|
+
export interface ReferenceAssetRequirement {
|
|
149
|
+
token: string;
|
|
150
|
+
legacyAlias?: string;
|
|
151
|
+
source: "character-sheet" | "shot-storyboard" | "additional-reference";
|
|
152
|
+
assetId: string;
|
|
153
|
+
role: ProviderAssetMappingRole;
|
|
154
|
+
required: boolean;
|
|
155
|
+
generatedPromptPath?: string;
|
|
156
|
+
notes: string;
|
|
157
|
+
}
|
|
69
158
|
export interface StoryboardPhase {
|
|
70
159
|
index: number;
|
|
71
160
|
timeRange: string;
|
|
@@ -131,10 +220,16 @@ export interface ModelPromptQa {
|
|
|
131
220
|
checks: Record<string, boolean>;
|
|
132
221
|
issues: string[];
|
|
133
222
|
}
|
|
223
|
+
export interface ModelRouteMetadata extends Record<string, unknown> {
|
|
224
|
+
continuityMode?: SeedanceContinuityMode;
|
|
225
|
+
providerAssetMapping?: Record<string, ProviderAssetMappingEntry>;
|
|
226
|
+
phaseBudget?: StoryboardPhaseBudget;
|
|
227
|
+
providerFileLimits?: ProviderFileLimitReport;
|
|
228
|
+
}
|
|
134
229
|
export interface ModelPromptOutput {
|
|
135
230
|
model: SupportedModel;
|
|
136
231
|
displayName: string;
|
|
137
|
-
routeMetadata:
|
|
232
|
+
routeMetadata: ModelRouteMetadata;
|
|
138
233
|
promptText: string;
|
|
139
234
|
audioPlan?: AudioPlan;
|
|
140
235
|
warnings: string[];
|
|
@@ -154,6 +249,12 @@ export interface PromptBundle {
|
|
|
154
249
|
modelPrompts: Partial<Record<SupportedModel, ModelPromptOutput>>;
|
|
155
250
|
storyboardInterpretation: StoryboardInterpretation;
|
|
156
251
|
assetRoles: ResolvedAssetRoles["assetRoles"];
|
|
252
|
+
continuityMode: SeedanceContinuityMode;
|
|
253
|
+
phaseBudget: StoryboardPhaseBudget;
|
|
254
|
+
handoffNote: ShotHandoffNote;
|
|
255
|
+
storyboardImagePrompt: StoryboardImagePrompt;
|
|
256
|
+
referenceAssetRequirements: ReferenceAssetRequirement[];
|
|
257
|
+
providerAssetMapping: Record<string, ProviderAssetMappingEntry>;
|
|
157
258
|
qa: PromptBundleQa;
|
|
158
259
|
generatedAt: string;
|
|
159
260
|
}
|
|
@@ -161,6 +262,11 @@ export interface AdapterInput {
|
|
|
161
262
|
request: NormalizedVideoPromptRequest;
|
|
162
263
|
assets: ResolvedAssetRoles;
|
|
163
264
|
interpretation: StoryboardInterpretation;
|
|
265
|
+
continuityMode: SeedanceContinuityMode;
|
|
266
|
+
phaseBudget: StoryboardPhaseBudget;
|
|
267
|
+
providerFileLimits: ProviderFileLimitReport;
|
|
268
|
+
storyboardImagePrompt: StoryboardImagePrompt;
|
|
269
|
+
providerAssetMapping: Record<string, ProviderAssetMappingEntry>;
|
|
164
270
|
voiceCast?: VoiceCastEntry[];
|
|
165
271
|
visualWorld: VisualWorld;
|
|
166
272
|
}
|
|
@@ -173,18 +279,61 @@ export interface VideoModelPromptAdapter {
|
|
|
173
279
|
buildPrompt(input: AdapterInput): ModelPromptOutput;
|
|
174
280
|
validate(output: ModelPromptOutput): ModelPromptQa;
|
|
175
281
|
}
|
|
282
|
+
export interface StoryboardReferencePolicy {
|
|
283
|
+
project: {
|
|
284
|
+
reference_mode: "storyboard-reference";
|
|
285
|
+
};
|
|
286
|
+
storyboard_reference: {
|
|
287
|
+
enabled: true;
|
|
288
|
+
max_storyboard_phases: number;
|
|
289
|
+
recommended_phase_budget: StoryboardPhaseBudget;
|
|
290
|
+
provider_reference_tokens: ProviderReferenceTokenPolicy;
|
|
291
|
+
continuity_mode: SeedanceContinuityMode | "mixed";
|
|
292
|
+
provider_file_limits: ProviderFileLimitReport;
|
|
293
|
+
character_reference_sheets: {
|
|
294
|
+
enabled: true;
|
|
295
|
+
provider: "gpt-image-2";
|
|
296
|
+
generated_once_per_character: true;
|
|
297
|
+
aspect_ratio: "16:9";
|
|
298
|
+
required_views: string[];
|
|
299
|
+
};
|
|
300
|
+
storyboard_prompt_policy: {
|
|
301
|
+
enabled: true;
|
|
302
|
+
provider: "gpt-image-2";
|
|
303
|
+
generated_per_shot: true;
|
|
304
|
+
aspect_ratio: "16:9";
|
|
305
|
+
max_panels_per_shot: number;
|
|
306
|
+
panel_budget: string;
|
|
307
|
+
professional_prompt_target_words: string;
|
|
308
|
+
};
|
|
309
|
+
role_separation: {
|
|
310
|
+
character_identity: string;
|
|
311
|
+
storyboard_reference: string;
|
|
312
|
+
storyboard_never_identity_source: string[];
|
|
313
|
+
};
|
|
314
|
+
split_policy: {
|
|
315
|
+
split_storyboard_overload: boolean;
|
|
316
|
+
split_at_phase_count: number;
|
|
317
|
+
split_target: "SHOTNN.md";
|
|
318
|
+
};
|
|
319
|
+
};
|
|
320
|
+
shot_handoff_notes: ShotHandoffNote[];
|
|
321
|
+
}
|
|
176
322
|
export interface StoryboardReferencePlan {
|
|
177
323
|
mode: "storyboard-reference";
|
|
178
324
|
targetModels: SupportedModel[];
|
|
179
325
|
shotCount: number;
|
|
180
326
|
splitRequired: boolean;
|
|
181
327
|
assetRoles: ResolvedAssetRoles["assetRoles"];
|
|
328
|
+
characterReferenceSheetPrompts: CharacterReferenceSheetPrompt[];
|
|
329
|
+
policy: StoryboardReferencePolicy;
|
|
182
330
|
generatedAt: string;
|
|
183
331
|
}
|
|
184
332
|
export interface StoryboardReferenceBuildResult {
|
|
185
333
|
plan: StoryboardReferencePlan;
|
|
186
334
|
request: NormalizedVideoPromptRequest;
|
|
187
335
|
assets: ResolvedAssetRoles;
|
|
336
|
+
characterReferenceSheetPrompts: CharacterReferenceSheetPrompt[];
|
|
188
337
|
bundles: PromptBundle[];
|
|
189
338
|
}
|
|
190
339
|
export interface StoryboardReferenceWriteResult {
|