@milenyumai/film-kit 2.3.2 → 2.3.4
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/MODEL_REGISTRY.md +136 -0
- package/README.md +28 -4
- package/build/index.d.ts +2 -0
- package/build/index.js +1 -0
- package/build/lib/cli.js +26 -10
- package/build/lib/configure.js +2 -8
- package/build/lib/defaults.js +10 -0
- package/build/lib/film-kit.js +16 -1
- package/build/lib/model-registry/index.d.ts +4 -0
- package/build/lib/model-registry/index.js +3 -0
- package/build/lib/model-registry/registry.d.ts +220 -0
- package/build/lib/model-registry/registry.js +191 -0
- package/build/lib/model-registry/selectors.d.ts +23 -0
- package/build/lib/model-registry/selectors.js +80 -0
- package/build/lib/model-registry/types.d.ts +59 -0
- package/build/lib/model-registry/types.js +1 -0
- package/build/lib/model-registry/validation.d.ts +3 -0
- package/build/lib/model-registry/validation.js +42 -0
- package/build/lib/storyboard-reference/adapters/base.js +2 -5
- package/build/lib/storyboard-reference/defaults.js +2 -1
- package/build/lib/storyboard-reference/output-writer.js +33 -0
- package/build/lib/storyboard-reference/prompt-bundle-builder.js +35 -3
- package/build/lib/storyboard-reference/request-normalizer.js +6 -4
- package/build/lib/storyboard-reference/validators.js +22 -7
- package/build/lib/storyboard-reference/visual-world-builder.js +1 -1
- package/build/lib/types.d.ts +2 -1
- package/package.json +3 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
export const MODEL_REGISTRY = {
|
|
2
|
+
veo31: {
|
|
3
|
+
id: "veo31",
|
|
4
|
+
displayName: "Google Flow + Veo 3.1",
|
|
5
|
+
provider: "google",
|
|
6
|
+
modalities: ["video"],
|
|
7
|
+
lifecycleStatus: "active",
|
|
8
|
+
supportedPresets: ["single", "multi", "studio"],
|
|
9
|
+
supportedReferenceModes: ["start-end", "storyboard-reference"],
|
|
10
|
+
supportedAspects: ["16:9", "9:16", "1:1"],
|
|
11
|
+
durationRange: { minSeconds: 4, maxSeconds: 8 },
|
|
12
|
+
durationPolicy: "Default Film-Kit Veo runtime duration is 8 seconds.",
|
|
13
|
+
adapterKey: "veo31",
|
|
14
|
+
promptGrammarKey: "veo31",
|
|
15
|
+
modelProfileKey: "veo31",
|
|
16
|
+
routingRole: "primary-video",
|
|
17
|
+
capabilities: {
|
|
18
|
+
characterReference: true,
|
|
19
|
+
storyboardReference: true,
|
|
20
|
+
audioPlan: true,
|
|
21
|
+
dialogue: true,
|
|
22
|
+
startEndFrames: true,
|
|
23
|
+
customStoryboardPhases: true,
|
|
24
|
+
maxStoryboardPhases: 4
|
|
25
|
+
},
|
|
26
|
+
docsVisibility: "public"
|
|
27
|
+
},
|
|
28
|
+
"seedance-2.0": {
|
|
29
|
+
id: "seedance-2.0",
|
|
30
|
+
displayName: "Seedance 2.0",
|
|
31
|
+
provider: "bytedance",
|
|
32
|
+
modalities: ["video"],
|
|
33
|
+
lifecycleStatus: "active",
|
|
34
|
+
supportedPresets: ["single", "multi", "studio"],
|
|
35
|
+
supportedReferenceModes: ["start-end", "storyboard-reference"],
|
|
36
|
+
supportedAspects: ["16:9", "9:16", "1:1"],
|
|
37
|
+
durationRange: { minSeconds: 4, maxSeconds: 15 },
|
|
38
|
+
durationPolicy: "Seedance storyboard-reference policy uses 4-15 second shots.",
|
|
39
|
+
adapterKey: "seedance20",
|
|
40
|
+
promptGrammarKey: "seedance20",
|
|
41
|
+
modelProfileKey: "seedance20",
|
|
42
|
+
routingRole: "storyboard-video",
|
|
43
|
+
capabilities: {
|
|
44
|
+
characterReference: true,
|
|
45
|
+
storyboardReference: true,
|
|
46
|
+
multipleImageReferences: true,
|
|
47
|
+
audioPlan: true,
|
|
48
|
+
dialogue: true,
|
|
49
|
+
customStoryboardPhases: true,
|
|
50
|
+
maxStoryboardPhases: 4,
|
|
51
|
+
maxReferenceImages: 9,
|
|
52
|
+
maxReferenceVideos: 3,
|
|
53
|
+
maxReferenceAudio: 3,
|
|
54
|
+
maxReferenceFiles: 12
|
|
55
|
+
},
|
|
56
|
+
docsVisibility: "public"
|
|
57
|
+
},
|
|
58
|
+
"kling-3.0": {
|
|
59
|
+
id: "kling-3.0",
|
|
60
|
+
displayName: "Kling 3.0",
|
|
61
|
+
provider: "kuaishou",
|
|
62
|
+
modalities: ["video"],
|
|
63
|
+
lifecycleStatus: "active",
|
|
64
|
+
supportedPresets: ["single", "multi", "hybrid", "hybrid-smart", "gpt-image-smart", "studio"],
|
|
65
|
+
supportedReferenceModes: ["start-end", "storyboard-reference", "hybrid"],
|
|
66
|
+
supportedAspects: ["16:9", "9:16", "1:1"],
|
|
67
|
+
durationRange: { minSeconds: 5, maxSeconds: 15 },
|
|
68
|
+
durationPolicy: "Kling 3.0 supports Film-Kit start/end and smart-routed video generation.",
|
|
69
|
+
adapterKey: "kling30",
|
|
70
|
+
promptGrammarKey: "kling30",
|
|
71
|
+
modelProfileKey: "kling30",
|
|
72
|
+
routingRole: "primary-video",
|
|
73
|
+
capabilities: {
|
|
74
|
+
characterReference: true,
|
|
75
|
+
storyboardReference: true,
|
|
76
|
+
audioPlan: true,
|
|
77
|
+
startEndFrames: true,
|
|
78
|
+
customStoryboardPhases: true,
|
|
79
|
+
maxStoryboardPhases: 4
|
|
80
|
+
},
|
|
81
|
+
docsVisibility: "public"
|
|
82
|
+
},
|
|
83
|
+
"gpt-image-2": {
|
|
84
|
+
id: "gpt-image-2",
|
|
85
|
+
displayName: "GPT Image 2",
|
|
86
|
+
provider: "openai",
|
|
87
|
+
modalities: ["image"],
|
|
88
|
+
lifecycleStatus: "active",
|
|
89
|
+
supportedPresets: ["hybrid", "hybrid-smart", "gpt-image-smart", "studio"],
|
|
90
|
+
supportedReferenceModes: ["start-end", "storyboard-reference", "hybrid"],
|
|
91
|
+
supportedAspects: ["16:9", "9:16", "1:1"],
|
|
92
|
+
durationPolicy: "Still-image model; video duration is not applicable.",
|
|
93
|
+
adapterKey: "gptImage2",
|
|
94
|
+
promptGrammarKey: "gptImage2",
|
|
95
|
+
modelProfileKey: "gptImage2",
|
|
96
|
+
routingRole: "image-reference",
|
|
97
|
+
capabilities: {
|
|
98
|
+
characterReference: true,
|
|
99
|
+
storyboardReference: true
|
|
100
|
+
},
|
|
101
|
+
docsVisibility: "public"
|
|
102
|
+
},
|
|
103
|
+
"gemini-3-pro-image-preview": {
|
|
104
|
+
id: "gemini-3-pro-image-preview",
|
|
105
|
+
displayName: "Gemini 3 Pro Image Preview",
|
|
106
|
+
provider: "google",
|
|
107
|
+
modalities: ["image"],
|
|
108
|
+
lifecycleStatus: "experimental",
|
|
109
|
+
supportedPresets: ["hybrid", "hybrid-smart"],
|
|
110
|
+
supportedReferenceModes: ["start-end", "storyboard-reference", "hybrid"],
|
|
111
|
+
supportedAspects: ["16:9", "9:16", "1:1"],
|
|
112
|
+
durationPolicy: "Still-image model used by internal hybrid presets; video duration is not applicable.",
|
|
113
|
+
adapterKey: "none",
|
|
114
|
+
promptGrammarKey: "generic",
|
|
115
|
+
modelProfileKey: "generic",
|
|
116
|
+
routingRole: "image-reference",
|
|
117
|
+
capabilities: {
|
|
118
|
+
characterReference: true,
|
|
119
|
+
storyboardReference: true
|
|
120
|
+
},
|
|
121
|
+
docsVisibility: "internal"
|
|
122
|
+
},
|
|
123
|
+
"smart-dialogue-router": {
|
|
124
|
+
id: "smart-dialogue-router",
|
|
125
|
+
displayName: "Smart Dialogue Router",
|
|
126
|
+
provider: "film-kit",
|
|
127
|
+
modalities: ["multimodal"],
|
|
128
|
+
lifecycleStatus: "active",
|
|
129
|
+
supportedPresets: ["hybrid-smart", "gpt-image-smart"],
|
|
130
|
+
supportedReferenceModes: ["start-end", "storyboard-reference", "hybrid"],
|
|
131
|
+
supportedAspects: ["16:9", "9:16", "1:1"],
|
|
132
|
+
durationPolicy: "Internal routing policy; routes dialogue/lip-sync to Veo and non-dialogue shots to Kling.",
|
|
133
|
+
adapterKey: "none",
|
|
134
|
+
promptGrammarKey: "generic",
|
|
135
|
+
modelProfileKey: "generic",
|
|
136
|
+
routingRole: "smart-router",
|
|
137
|
+
capabilities: {
|
|
138
|
+
smartRouting: true,
|
|
139
|
+
audioPlan: true,
|
|
140
|
+
dialogue: true
|
|
141
|
+
},
|
|
142
|
+
docsVisibility: "internal"
|
|
143
|
+
},
|
|
144
|
+
"veo2": {
|
|
145
|
+
id: "veo2",
|
|
146
|
+
displayName: "Veo 2",
|
|
147
|
+
provider: "google",
|
|
148
|
+
modalities: ["video"],
|
|
149
|
+
lifecycleStatus: "deprecated",
|
|
150
|
+
supportedPresets: ["single", "multi", "studio"],
|
|
151
|
+
supportedReferenceModes: ["start-end", "storyboard-reference"],
|
|
152
|
+
supportedAspects: ["16:9", "9:16", "1:1"],
|
|
153
|
+
durationRange: { minSeconds: 4, maxSeconds: 8 },
|
|
154
|
+
durationPolicy: "Deprecated legacy video model kept only for migration warnings.",
|
|
155
|
+
adapterKey: "generic",
|
|
156
|
+
promptGrammarKey: "generic",
|
|
157
|
+
modelProfileKey: "generic",
|
|
158
|
+
routingRole: "legacy-video",
|
|
159
|
+
capabilities: {
|
|
160
|
+
characterReference: true,
|
|
161
|
+
storyboardReference: true,
|
|
162
|
+
audioPlan: true,
|
|
163
|
+
dialogue: true,
|
|
164
|
+
startEndFrames: true,
|
|
165
|
+
customStoryboardPhases: true,
|
|
166
|
+
maxStoryboardPhases: 4
|
|
167
|
+
},
|
|
168
|
+
deprecationMessage: "veo2 is deprecated. Use veo31 for current Film-Kit video generation.",
|
|
169
|
+
replacementModelId: "veo31",
|
|
170
|
+
docsVisibility: "hidden"
|
|
171
|
+
},
|
|
172
|
+
"kling-2.0": {
|
|
173
|
+
id: "kling-2.0",
|
|
174
|
+
displayName: "Kling 2.0",
|
|
175
|
+
provider: "kuaishou",
|
|
176
|
+
modalities: ["video"],
|
|
177
|
+
lifecycleStatus: "removed",
|
|
178
|
+
supportedPresets: [],
|
|
179
|
+
supportedReferenceModes: [],
|
|
180
|
+
supportedAspects: ["16:9", "9:16", "1:1"],
|
|
181
|
+
durationPolicy: "Removed legacy model. Do not route generation here.",
|
|
182
|
+
adapterKey: "none",
|
|
183
|
+
promptGrammarKey: "generic",
|
|
184
|
+
modelProfileKey: "generic",
|
|
185
|
+
routingRole: "removed",
|
|
186
|
+
capabilities: {},
|
|
187
|
+
deprecationMessage: "kling-2.0 has been removed from Film-Kit. Use kling-3.0 instead.",
|
|
188
|
+
replacementModelId: "kling-3.0",
|
|
189
|
+
docsVisibility: "hidden"
|
|
190
|
+
}
|
|
191
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ActiveModelId, type DeprecatedModelId, type ImageModelId, type KnownModelId, type RemovedModelId, type VideoModelId } from "./registry.js";
|
|
2
|
+
import type { ModelAdapterKey, ModelRegistryEntry, ModelRegistryPreset, ModelRegistryReferenceMode } from "./types.js";
|
|
3
|
+
export declare function getModel(id: string): ModelRegistryEntry | undefined;
|
|
4
|
+
export declare function requireModel(id: string): ModelRegistryEntry;
|
|
5
|
+
export declare function listModels(): ModelRegistryEntry[];
|
|
6
|
+
export declare function listActiveModels(): ModelRegistryEntry[];
|
|
7
|
+
export declare function listVideoModels(): ModelRegistryEntry[];
|
|
8
|
+
export declare function listImageModels(): ModelRegistryEntry[];
|
|
9
|
+
export declare function listModelsForPreset(preset: ModelRegistryPreset): ModelRegistryEntry[];
|
|
10
|
+
export declare function listModelsForReferenceMode(referenceMode: ModelRegistryReferenceMode): ModelRegistryEntry[];
|
|
11
|
+
export declare function listRunnableVideoModels(): ModelRegistryEntry[];
|
|
12
|
+
export declare function listRunnableVideoModelIds(): VideoModelId[];
|
|
13
|
+
export declare function listActiveVideoModelIds(): VideoModelId[];
|
|
14
|
+
export declare function isKnownModelId(value: string): value is KnownModelId;
|
|
15
|
+
export declare function isActiveModelId(value: string): value is ActiveModelId;
|
|
16
|
+
export declare function isDeprecatedModelId(value: string): value is DeprecatedModelId;
|
|
17
|
+
export declare function isRemovedModelId(value: string): value is RemovedModelId;
|
|
18
|
+
export declare function isVideoModelId(value: string): value is VideoModelId;
|
|
19
|
+
export declare function isImageModelId(value: string): value is ImageModelId;
|
|
20
|
+
export declare function isModelSupportedByPreset(modelId: string, preset: ModelRegistryPreset): boolean;
|
|
21
|
+
export declare function getModelAdapterKey(modelId: string): ModelAdapterKey;
|
|
22
|
+
export declare function getReplacementModel(modelId: string): ModelRegistryEntry | undefined;
|
|
23
|
+
export declare function getModelDisplayName(modelId: string): string;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { MODEL_REGISTRY } from "./registry.js";
|
|
2
|
+
function entries() {
|
|
3
|
+
return Object.values(MODEL_REGISTRY);
|
|
4
|
+
}
|
|
5
|
+
function hasModality(model, modality) {
|
|
6
|
+
return model.modalities.includes(modality);
|
|
7
|
+
}
|
|
8
|
+
export function getModel(id) {
|
|
9
|
+
return MODEL_REGISTRY[id];
|
|
10
|
+
}
|
|
11
|
+
export function requireModel(id) {
|
|
12
|
+
const model = getModel(id);
|
|
13
|
+
if (!model) {
|
|
14
|
+
throw new Error(`Unknown model: ${id}.`);
|
|
15
|
+
}
|
|
16
|
+
return model;
|
|
17
|
+
}
|
|
18
|
+
export function listModels() {
|
|
19
|
+
return entries();
|
|
20
|
+
}
|
|
21
|
+
export function listActiveModels() {
|
|
22
|
+
return entries().filter(model => model.lifecycleStatus === "active" || model.lifecycleStatus === "experimental");
|
|
23
|
+
}
|
|
24
|
+
export function listVideoModels() {
|
|
25
|
+
return entries().filter(model => hasModality(model, "video"));
|
|
26
|
+
}
|
|
27
|
+
export function listImageModels() {
|
|
28
|
+
return entries().filter(model => hasModality(model, "image"));
|
|
29
|
+
}
|
|
30
|
+
export function listModelsForPreset(preset) {
|
|
31
|
+
return entries().filter(model => model.supportedPresets.includes(preset));
|
|
32
|
+
}
|
|
33
|
+
export function listModelsForReferenceMode(referenceMode) {
|
|
34
|
+
return entries().filter(model => model.supportedReferenceModes.includes(referenceMode));
|
|
35
|
+
}
|
|
36
|
+
export function listRunnableVideoModels() {
|
|
37
|
+
return listVideoModels().filter(model => model.lifecycleStatus !== "removed");
|
|
38
|
+
}
|
|
39
|
+
export function listRunnableVideoModelIds() {
|
|
40
|
+
return listRunnableVideoModels().map(model => model.id);
|
|
41
|
+
}
|
|
42
|
+
export function listActiveVideoModelIds() {
|
|
43
|
+
return listActiveModels()
|
|
44
|
+
.filter(model => hasModality(model, "video"))
|
|
45
|
+
.map(model => model.id);
|
|
46
|
+
}
|
|
47
|
+
export function isKnownModelId(value) {
|
|
48
|
+
return Object.prototype.hasOwnProperty.call(MODEL_REGISTRY, value);
|
|
49
|
+
}
|
|
50
|
+
export function isActiveModelId(value) {
|
|
51
|
+
const model = getModel(value);
|
|
52
|
+
return model?.lifecycleStatus === "active" || model?.lifecycleStatus === "experimental";
|
|
53
|
+
}
|
|
54
|
+
export function isDeprecatedModelId(value) {
|
|
55
|
+
return getModel(value)?.lifecycleStatus === "deprecated";
|
|
56
|
+
}
|
|
57
|
+
export function isRemovedModelId(value) {
|
|
58
|
+
return getModel(value)?.lifecycleStatus === "removed";
|
|
59
|
+
}
|
|
60
|
+
export function isVideoModelId(value) {
|
|
61
|
+
const model = getModel(value);
|
|
62
|
+
return Boolean(model && hasModality(model, "video"));
|
|
63
|
+
}
|
|
64
|
+
export function isImageModelId(value) {
|
|
65
|
+
const model = getModel(value);
|
|
66
|
+
return Boolean(model && hasModality(model, "image"));
|
|
67
|
+
}
|
|
68
|
+
export function isModelSupportedByPreset(modelId, preset) {
|
|
69
|
+
return Boolean(getModel(modelId)?.supportedPresets.includes(preset));
|
|
70
|
+
}
|
|
71
|
+
export function getModelAdapterKey(modelId) {
|
|
72
|
+
return requireModel(modelId).adapterKey;
|
|
73
|
+
}
|
|
74
|
+
export function getReplacementModel(modelId) {
|
|
75
|
+
const replacementModelId = getModel(modelId)?.replacementModelId;
|
|
76
|
+
return replacementModelId ? getModel(replacementModelId) : undefined;
|
|
77
|
+
}
|
|
78
|
+
export function getModelDisplayName(modelId) {
|
|
79
|
+
return requireModel(modelId).displayName;
|
|
80
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type ModelModality = "video" | "image" | "audio" | "multimodal";
|
|
2
|
+
export type ModelLifecycleStatus = "active" | "experimental" | "deprecated" | "removed";
|
|
3
|
+
export type ModelRegistryPreset = "single" | "multi" | "hybrid" | "hybrid-smart" | "gpt-image-smart" | "studio";
|
|
4
|
+
export type ModelRegistryReferenceMode = "start-end" | "storyboard-reference" | "hybrid";
|
|
5
|
+
export type ModelRegistryAspectRatio = "16:9" | "9:16" | "1:1";
|
|
6
|
+
export type ModelAdapterKey = "veo31" | "seedance20" | "kling30" | "generic" | "gptImage2" | "none";
|
|
7
|
+
export type PromptGrammarKey = "veo31" | "seedance20" | "kling30" | "gptImage2" | "generic";
|
|
8
|
+
export type ModelProfileKey = "veo31" | "seedance20" | "kling30" | "gptImage2" | "generic";
|
|
9
|
+
export type ModelRoutingRole = "primary-video" | "storyboard-video" | "image-reference" | "smart-router" | "legacy-video" | "removed";
|
|
10
|
+
export interface ModelDurationRange {
|
|
11
|
+
minSeconds: number;
|
|
12
|
+
maxSeconds: number;
|
|
13
|
+
}
|
|
14
|
+
export interface ModelCapabilities {
|
|
15
|
+
characterReference?: boolean;
|
|
16
|
+
storyboardReference?: boolean;
|
|
17
|
+
multipleImageReferences?: boolean;
|
|
18
|
+
audioPlan?: boolean;
|
|
19
|
+
dialogue?: boolean;
|
|
20
|
+
startEndFrames?: boolean;
|
|
21
|
+
smartRouting?: boolean;
|
|
22
|
+
customStoryboardPhases?: boolean;
|
|
23
|
+
maxStoryboardPhases?: number;
|
|
24
|
+
maxReferenceImages?: number;
|
|
25
|
+
maxReferenceVideos?: number;
|
|
26
|
+
maxReferenceAudio?: number;
|
|
27
|
+
maxReferenceFiles?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface ModelRegistryEntry {
|
|
30
|
+
id: string;
|
|
31
|
+
displayName: string;
|
|
32
|
+
provider: string;
|
|
33
|
+
modalities: readonly ModelModality[];
|
|
34
|
+
lifecycleStatus: ModelLifecycleStatus;
|
|
35
|
+
supportedPresets: readonly ModelRegistryPreset[];
|
|
36
|
+
supportedReferenceModes: readonly ModelRegistryReferenceMode[];
|
|
37
|
+
supportedAspects: readonly ModelRegistryAspectRatio[];
|
|
38
|
+
durationRange?: ModelDurationRange;
|
|
39
|
+
durationPolicy?: string;
|
|
40
|
+
adapterKey: ModelAdapterKey;
|
|
41
|
+
promptGrammarKey: PromptGrammarKey;
|
|
42
|
+
modelProfileKey: ModelProfileKey;
|
|
43
|
+
routingRole: ModelRoutingRole;
|
|
44
|
+
capabilities: ModelCapabilities;
|
|
45
|
+
deprecationMessage?: string;
|
|
46
|
+
replacementModelId?: string;
|
|
47
|
+
docsVisibility: "public" | "internal" | "hidden";
|
|
48
|
+
}
|
|
49
|
+
export interface ModelAllowedContext {
|
|
50
|
+
preset?: ModelRegistryPreset;
|
|
51
|
+
referenceMode?: ModelRegistryReferenceMode;
|
|
52
|
+
requiredModalities?: readonly ModelModality[];
|
|
53
|
+
allowDeprecated?: boolean;
|
|
54
|
+
usage?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface ModelAllowedResult {
|
|
57
|
+
model: ModelRegistryEntry;
|
|
58
|
+
warnings: string[];
|
|
59
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ModelAllowedContext, ModelAllowedResult, ModelRegistryEntry } from "./types.js";
|
|
2
|
+
export declare function validateModelAllowed(modelId: string, context?: ModelAllowedContext): ModelAllowedResult;
|
|
3
|
+
export declare function assertModelAllowed(modelId: string, context?: ModelAllowedContext): ModelRegistryEntry;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getModel, getReplacementModel, listRunnableVideoModelIds } from "./selectors.js";
|
|
2
|
+
function formatModelList(models) {
|
|
3
|
+
return models.join(", ");
|
|
4
|
+
}
|
|
5
|
+
function hasRequiredModalities(model, required) {
|
|
6
|
+
return !required || required.every(modality => model.modalities.includes(modality));
|
|
7
|
+
}
|
|
8
|
+
export function validateModelAllowed(modelId, context = {}) {
|
|
9
|
+
const model = getModel(modelId);
|
|
10
|
+
const usage = context.usage ? `${context.usage} ` : "";
|
|
11
|
+
if (!model) {
|
|
12
|
+
throw new Error(`Unknown ${usage}model: ${modelId}. Supported video models: ${formatModelList(listRunnableVideoModelIds())}`);
|
|
13
|
+
}
|
|
14
|
+
if (model.lifecycleStatus === "removed") {
|
|
15
|
+
const replacement = getReplacementModel(model.id);
|
|
16
|
+
const replacementText = replacement ? ` Use ${replacement.id} instead.` : "";
|
|
17
|
+
throw new Error(`${model.deprecationMessage ?? `${model.id} has been removed.`}${replacementText}`);
|
|
18
|
+
}
|
|
19
|
+
if (!hasRequiredModalities(model, context.requiredModalities)) {
|
|
20
|
+
throw new Error(`Model ${model.id} is not valid for ${usage.trim() || "this usage"}. ` +
|
|
21
|
+
`Required modalities: ${context.requiredModalities?.join(", ") ?? "none"}.`);
|
|
22
|
+
}
|
|
23
|
+
if (context.preset && !model.supportedPresets.includes(context.preset)) {
|
|
24
|
+
throw new Error(`Model ${model.id} is not supported by preset ${context.preset}.`);
|
|
25
|
+
}
|
|
26
|
+
if (context.referenceMode && !model.supportedReferenceModes.includes(context.referenceMode)) {
|
|
27
|
+
throw new Error(`Model ${model.id} is not supported by reference mode ${context.referenceMode}.`);
|
|
28
|
+
}
|
|
29
|
+
const warnings = [];
|
|
30
|
+
if (model.lifecycleStatus === "deprecated") {
|
|
31
|
+
if (context.allowDeprecated === false) {
|
|
32
|
+
throw new Error(model.deprecationMessage ?? `Model ${model.id} is deprecated.`);
|
|
33
|
+
}
|
|
34
|
+
const replacement = getReplacementModel(model.id);
|
|
35
|
+
warnings.push(model.deprecationMessage
|
|
36
|
+
?? `Model ${model.id} is deprecated.${replacement ? ` Use ${replacement.id} instead.` : ""}`);
|
|
37
|
+
}
|
|
38
|
+
return { model, warnings };
|
|
39
|
+
}
|
|
40
|
+
export function assertModelAllowed(modelId, context = {}) {
|
|
41
|
+
return validateModelAllowed(modelId, context).model;
|
|
42
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getModelDisplayName } from "../../model-registry/index.js";
|
|
1
2
|
export class BaseVideoModelPromptAdapter {
|
|
2
3
|
validate(output) {
|
|
3
4
|
const prompt = output.promptText;
|
|
@@ -53,9 +54,5 @@ export function formatPhaseLines(phases) {
|
|
|
53
54
|
.join("\n");
|
|
54
55
|
}
|
|
55
56
|
export function getDisplayName(model) {
|
|
56
|
-
|
|
57
|
-
return "Seedance 2.0";
|
|
58
|
-
if (model === "kling-3.0")
|
|
59
|
-
return "Kling 3.0";
|
|
60
|
-
return "Veo 3.1";
|
|
57
|
+
return getModelDisplayName(model);
|
|
61
58
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { listActiveVideoModelIds } from "../model-registry/index.js";
|
|
1
2
|
export const STORYBOARD_REFERENCE_MODE = "storyboard-reference";
|
|
2
3
|
export const DEFAULT_STORYBOARD_DURATION_SECONDS = 8;
|
|
3
4
|
export const DEFAULT_STORYBOARD_ASPECT_RATIO = "16:9";
|
|
4
5
|
export const DEFAULT_STORYBOARD_LANGUAGE = "NONE";
|
|
5
6
|
export const DEFAULT_STORYBOARD_OUTPUT_DIR = "./outputs";
|
|
6
|
-
export const DEFAULT_STORYBOARD_TARGET_MODELS =
|
|
7
|
+
export const DEFAULT_STORYBOARD_TARGET_MODELS = listActiveVideoModelIds();
|
|
7
8
|
export const DEFAULT_STORYBOARD_REFERENCE_RUNTIME = {
|
|
8
9
|
enabled: true,
|
|
9
10
|
maxStoryboardPhases: 4,
|
|
@@ -41,6 +41,37 @@ function formatProviderTokens(bundle) {
|
|
|
41
41
|
})
|
|
42
42
|
.join("\n");
|
|
43
43
|
}
|
|
44
|
+
function getProviderToken(bundle, role, fallback) {
|
|
45
|
+
return Object.values(bundle.providerAssetMapping)
|
|
46
|
+
.find(entry => entry.role === role)?.token ?? fallback;
|
|
47
|
+
}
|
|
48
|
+
function formatCoverageReferences(bundle) {
|
|
49
|
+
const interpretation = bundle.storyboardInterpretation;
|
|
50
|
+
const characterTokens = Object.values(bundle.providerAssetMapping)
|
|
51
|
+
.filter(entry => entry.role === "character_identity")
|
|
52
|
+
.map(entry => entry.token)
|
|
53
|
+
.join(", ") || "@Image1";
|
|
54
|
+
const storyboardToken = getProviderToken(bundle, "storyboard_plan", "@Image2");
|
|
55
|
+
const visualWorld = interpretation.visualWorld;
|
|
56
|
+
const camera = interpretation.cameraPlan;
|
|
57
|
+
const firstPhase = interpretation.phases[0];
|
|
58
|
+
const lastPhase = interpretation.phases.at(-1) ?? firstPhase;
|
|
59
|
+
return `## Coverage References In Same File
|
|
60
|
+
|
|
61
|
+
Coverage is standalone editorial material for this shot only. Do not chain coverage into the next main shot. Keep the same generated character identity, wardrobe, props, lighting direction, environment, screen direction, and visual_world.
|
|
62
|
+
|
|
63
|
+
### ${bundle.shotId}A - ECU Reaction | 3s
|
|
64
|
+
|
|
65
|
+
\`\`\`text
|
|
66
|
+
Use ${characterTokens} as the exact identity source and ${storyboardToken} only for scene continuity. Extreme close-up reaction coverage during ${firstPhase?.timeRange ?? "the opening beat"}: ${firstPhase?.subjectAction ?? "the character registers the story beat"}. Keep ${visualWorld.lighting}, ${visualWorld.atmosphere}, and ${camera.screenDirection}. Static or barely drifting camera, shallow depth of field, natural skin texture, visible breath or eye tension, no new action beat, no new identity. Audio: close breath, subtle clothing sound, matched ambience, Music: NONE. Avoid: identity drift, face drift, wardrobe drift, beauty filter, plastic skin, distorted eyes, flicker, warping, on-screen text, watermark, logo.
|
|
67
|
+
\`\`\`
|
|
68
|
+
|
|
69
|
+
### ${bundle.shotId}B - Insert / Environmental Detail | 3s
|
|
70
|
+
|
|
71
|
+
\`\`\`text
|
|
72
|
+
Use ${storyboardToken} only for composition and environment continuity; do not introduce a new character identity. Tight insert coverage tied to ${lastPhase?.timeRange ?? "the final beat"}: ${lastPhase?.subjectAction ?? "the action resolves into a clear visual detail"}. Show a physically plausible detail from the same visual world: foreground texture, prop contact, reflection, light spill, hand tension, fabric motion, or environment response. Keep ${visualWorld.environment}; ${visualWorld.foreground}; ${visualWorld.background}; ${visualWorld.lighting}. Audio: matched practical SFX and ambience, no dialogue unless explicitly present in the main Audio Plan, Music: NONE. Avoid: new location, new character, clear logo, readable text, impossible physics, wrong reflection angle, identity override, flicker, warping, cartoon style, CGI look.
|
|
73
|
+
\`\`\``;
|
|
74
|
+
}
|
|
44
75
|
export function renderShotMarkdown(bundle) {
|
|
45
76
|
const interpretation = bundle.storyboardInterpretation;
|
|
46
77
|
const audioPlan = getAudioPlan(bundle);
|
|
@@ -104,6 +135,8 @@ ${JSON.stringify(audioPlan ?? null, null, 2)}
|
|
|
104
135
|
|
|
105
136
|
${modelPrompts}
|
|
106
137
|
|
|
138
|
+
${formatCoverageReferences(bundle)}
|
|
139
|
+
|
|
107
140
|
## QA
|
|
108
141
|
${formatQa(bundle)}
|
|
109
142
|
`;
|
|
@@ -1,15 +1,47 @@
|
|
|
1
1
|
import { resolveAssetRoles } from "./asset-role-resolver.js";
|
|
2
|
+
import { GenericPromptAdapter } from "./adapters/generic.js";
|
|
2
3
|
import { Kling30PromptAdapter } from "./adapters/kling30.js";
|
|
3
4
|
import { Seedance20PromptAdapter } from "./adapters/seedance20.js";
|
|
4
5
|
import { Veo31PromptAdapter } from "./adapters/veo31.js";
|
|
6
|
+
import { getModelAdapterKey, requireModel } from "../model-registry/index.js";
|
|
5
7
|
import { normalizeVideoPromptRequest } from "./request-normalizer.js";
|
|
6
8
|
import { buildStoryboardPhaseBudget, inferContinuityMode, interpretStoryboard } from "./storyboard-interpreter.js";
|
|
7
9
|
import { assertSafeStoryboardReferenceRequest, validateCharacterReferenceSheetPrompts, validateModelPromptOutput, validatePromptBundle } from "./validators.js";
|
|
8
10
|
const ADAPTERS = {
|
|
9
11
|
veo31: new Veo31PromptAdapter(),
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
seedance20: new Seedance20PromptAdapter(),
|
|
13
|
+
kling30: new Kling30PromptAdapter()
|
|
12
14
|
};
|
|
15
|
+
function buildGenericCapabilities(model) {
|
|
16
|
+
const registryModel = requireModel(model);
|
|
17
|
+
const capabilities = {
|
|
18
|
+
supportsImageReference: Boolean(registryModel.capabilities.characterReference),
|
|
19
|
+
supportsMultipleImageReferences: Boolean(registryModel.capabilities.multipleImageReferences),
|
|
20
|
+
supportsStoryboardReference: Boolean(registryModel.capabilities.storyboardReference),
|
|
21
|
+
supportsAudio: Boolean(registryModel.capabilities.audioPlan),
|
|
22
|
+
supportsDialogue: Boolean(registryModel.capabilities.dialogue),
|
|
23
|
+
supportsCustomStoryboardPhases: Boolean(registryModel.capabilities.customStoryboardPhases),
|
|
24
|
+
preferredPromptShape: "natural-language"
|
|
25
|
+
};
|
|
26
|
+
if (registryModel.durationRange?.maxSeconds !== undefined) {
|
|
27
|
+
capabilities.maxDurationSeconds = registryModel.durationRange.maxSeconds;
|
|
28
|
+
}
|
|
29
|
+
if (registryModel.capabilities.maxStoryboardPhases !== undefined) {
|
|
30
|
+
capabilities.maxStoryboardPhases = registryModel.capabilities.maxStoryboardPhases;
|
|
31
|
+
}
|
|
32
|
+
return capabilities;
|
|
33
|
+
}
|
|
34
|
+
function resolvePromptAdapter(model) {
|
|
35
|
+
const adapterKey = getModelAdapterKey(model);
|
|
36
|
+
if (adapterKey === "generic") {
|
|
37
|
+
return new GenericPromptAdapter(model, buildGenericCapabilities(model));
|
|
38
|
+
}
|
|
39
|
+
const adapter = ADAPTERS[adapterKey];
|
|
40
|
+
if (!adapter) {
|
|
41
|
+
throw new Error(`Model ${model} does not have a storyboard-reference video adapter.`);
|
|
42
|
+
}
|
|
43
|
+
return adapter;
|
|
44
|
+
}
|
|
13
45
|
function getStoryboardPanelCount(request, phaseBudget) {
|
|
14
46
|
return request.storyboardPanelCountHint
|
|
15
47
|
?? request.shotCountHint
|
|
@@ -320,7 +352,7 @@ function buildStoryboardReferencePolicy(request, phaseBudget, providerFileLimits
|
|
|
320
352
|
function buildModelPrompts(request, bundleInput) {
|
|
321
353
|
const modelPrompts = {};
|
|
322
354
|
for (const model of request.targetModels) {
|
|
323
|
-
const adapter =
|
|
355
|
+
const adapter = resolvePromptAdapter(model);
|
|
324
356
|
const output = adapter.buildPrompt({ ...bundleInput, request });
|
|
325
357
|
const validatorQa = validateModelPromptOutput(output);
|
|
326
358
|
output.qa = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { validateModelAllowed } from "../model-registry/index.js";
|
|
1
2
|
import { DEFAULT_STORYBOARD_ASPECT_RATIO, DEFAULT_STORYBOARD_DURATION_SECONDS, DEFAULT_STORYBOARD_LANGUAGE, DEFAULT_STORYBOARD_OUTPUT_DIR, DEFAULT_STORYBOARD_TARGET_MODELS, STORYBOARD_REFERENCE_MODE, resolveStoryboardReferenceRuntime } from "./defaults.js";
|
|
2
|
-
const SUPPORTED_MODELS = ["veo31", "seedance-2.0", "kling-3.0"];
|
|
3
3
|
const SUPPORTED_ASPECT_RATIOS = ["16:9", "9:16", "1:1"];
|
|
4
4
|
function normalizePositiveInteger(value, fallback, fieldName) {
|
|
5
5
|
const normalized = value ?? fallback;
|
|
@@ -13,9 +13,11 @@ function assertSupportedModels(models) {
|
|
|
13
13
|
return DEFAULT_STORYBOARD_TARGET_MODELS.slice();
|
|
14
14
|
}
|
|
15
15
|
for (const model of models) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
validateModelAllowed(model, {
|
|
17
|
+
requiredModalities: ["video"],
|
|
18
|
+
referenceMode: STORYBOARD_REFERENCE_MODE,
|
|
19
|
+
usage: "storyboard target"
|
|
20
|
+
});
|
|
19
21
|
}
|
|
20
22
|
return Array.from(new Set(models));
|
|
21
23
|
}
|
|
@@ -99,8 +99,12 @@ export function validateModelPromptOutput(output) {
|
|
|
99
99
|
}
|
|
100
100
|
export function validatePromptBundle(bundle, assets, voiceCast = []) {
|
|
101
101
|
const roleIssues = validateResolvedAssetRoles(assets);
|
|
102
|
+
const modelValidationResults = Object.values(bundle.modelPrompts)
|
|
103
|
+
.filter((output) => Boolean(output))
|
|
104
|
+
.map(validateModelPromptOutput);
|
|
102
105
|
const modelIssues = Object.values(bundle.modelPrompts)
|
|
103
|
-
.flatMap(output => output?.qa.issues ?? [])
|
|
106
|
+
.flatMap(output => output?.qa.issues ?? [])
|
|
107
|
+
.concat(modelValidationResults.flatMap(result => result.issues));
|
|
104
108
|
const voiceCastKeys = new Set(voiceCast.map(entry => entry.speakerKey));
|
|
105
109
|
const audioPlans = Object.values(bundle.modelPrompts)
|
|
106
110
|
.map(output => output?.audioPlan)
|
|
@@ -110,9 +114,10 @@ export function validatePromptBundle(bundle, assets, voiceCast = []) {
|
|
|
110
114
|
hasStoryboardReference: assets.storyboards.length >= 1 || Boolean(bundle.storyboardImagePrompt),
|
|
111
115
|
hasStoryboardImagePrompt: Boolean(bundle.storyboardImagePrompt?.promptText.trim()),
|
|
112
116
|
storyboardPromptHasReferenceLockStructure: hasGptImageStillPromptContract(bundle.storyboardImagePrompt?.promptText ?? ""),
|
|
113
|
-
hasAvoidLineEverywhere: Object.values(bundle.modelPrompts).every(output => Boolean(output
|
|
117
|
+
hasAvoidLineEverywhere: Object.values(bundle.modelPrompts).every(output => Boolean(output) && hasAvoidOrNegativePrompt(output.promptText)),
|
|
114
118
|
storyboardPromptHasAvoidLine: hasAvoidOrNegativePrompt(bundle.storyboardImagePrompt?.promptText ?? ""),
|
|
115
|
-
modelGrammarPasses: Object.values(bundle.modelPrompts).every(output => output?.qa.verdict === "pass")
|
|
119
|
+
modelGrammarPasses: Object.values(bundle.modelPrompts).every(output => output?.qa.verdict === "pass")
|
|
120
|
+
&& modelValidationResults.every(result => result.verdict === "pass"),
|
|
116
121
|
storyboardDoesNotOverrideIdentity: Object.values(bundle.modelPrompts).every(output => /storyboard.*staging|storyboard.*planning|storyboard.*composition|storyboard.*only/i.test(output?.promptText ?? "")),
|
|
117
122
|
audioPlanPresent: Object.values(bundle.modelPrompts).every(output => Boolean(output?.audioPlan)),
|
|
118
123
|
speakingAudioPlansHaveActiveSpeaker: audioPlans.every(plan => plan.dialogueTranscript === "NONE" || Boolean(plan.activeSpeakerKey)),
|
|
@@ -131,10 +136,20 @@ export function validatePromptBundle(bundle, assets, voiceCast = []) {
|
|
|
131
136
|
};
|
|
132
137
|
}
|
|
133
138
|
export function assertStoryboardReferenceBuildPass(result) {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
const issues = validateCharacterReferenceSheetPrompts(result.characterReferenceSheetPrompts)
|
|
140
|
+
.map(issue => `character-sheet: ${issue}`);
|
|
141
|
+
for (const bundle of result.bundles) {
|
|
142
|
+
const currentQa = validatePromptBundle(bundle, result.assets, result.plan.voiceCast);
|
|
143
|
+
if (bundle.qa.verdict !== "pass" || currentQa.verdict !== "pass") {
|
|
144
|
+
const bundleIssues = [
|
|
145
|
+
...bundle.qa.issues,
|
|
146
|
+
...currentQa.issues
|
|
147
|
+
];
|
|
148
|
+
issues.push(...(bundleIssues.length > 0
|
|
149
|
+
? bundleIssues.map(issue => `${bundle.shotId}: ${issue}`)
|
|
150
|
+
: [`${bundle.shotId}: bundle QA failed without a detailed issue.`]));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
138
153
|
if (result.bundles.length === 0) {
|
|
139
154
|
issues.push("No storyboard-reference prompt bundles were generated.");
|
|
140
155
|
}
|
|
@@ -11,7 +11,7 @@ export function buildCameraPlan(request) {
|
|
|
11
11
|
lens: wideShot ? "35mm lens feel" : closeShot ? "85mm lens feel" : "50mm lens feel",
|
|
12
12
|
framing: wideShot ? "layered wide composition" : closeShot ? "eye-level close framing" : "stable mid-shot framing",
|
|
13
13
|
stabilization: "tripod-stable with minimal organic drift",
|
|
14
|
-
screenDirection: "
|
|
14
|
+
screenDirection: "storyboard screen direction and eyeline relationships"
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
export function buildVisualWorld(request) {
|
package/build/lib/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { VideoModelId } from "./model-registry/index.js";
|
|
1
2
|
export type SupportedPlatform = "cursor" | "claude" | "copilot" | "antigravity" | "codex";
|
|
2
|
-
export type SupportedModel =
|
|
3
|
+
export type SupportedModel = VideoModelId;
|
|
3
4
|
export type KlingPreset = "ultra-realism" | "balanced" | "custom";
|
|
4
5
|
export type FilmKitPreset = "single" | "multi" | "hybrid" | "hybrid-smart" | "gpt-image-smart" | "studio";
|
|
5
6
|
export type ReferenceMode = "start-end" | "storyboard-reference" | "hybrid";
|