@sogni-ai/sogni-intelligence-client 2.4.3 → 3.0.0-alpha.10
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/agent/index.d.ts +5 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +38 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/intentInput.d.ts +106 -0
- package/dist/agent/intentInput.d.ts.map +1 -0
- package/dist/agent/intentInput.js +410 -0
- package/dist/agent/intentInput.js.map +1 -0
- package/dist/agent/toolMetadata.d.ts +37 -0
- package/dist/agent/toolMetadata.d.ts.map +1 -0
- package/dist/agent/toolMetadata.js +146 -0
- package/dist/agent/toolMetadata.js.map +1 -0
- package/dist/agent/turnAnalysis.d.ts +34 -0
- package/dist/agent/turnAnalysis.d.ts.map +1 -0
- package/dist/agent/turnAnalysis.js +155 -0
- package/dist/agent/turnAnalysis.js.map +1 -0
- package/dist/agent/turnPlan.d.ts +40 -0
- package/dist/agent/turnPlan.d.ts.map +1 -0
- package/dist/agent/turnPlan.js +187 -0
- package/dist/agent/turnPlan.js.map +1 -0
- package/dist/artifacts/graph.d.ts +92 -0
- package/dist/artifacts/graph.d.ts.map +1 -0
- package/dist/artifacts/graph.js +484 -0
- package/dist/artifacts/graph.js.map +1 -0
- package/dist/artifacts/index.d.ts +3 -0
- package/dist/artifacts/index.d.ts.map +1 -0
- package/dist/artifacts/index.js +28 -0
- package/dist/artifacts/index.js.map +1 -0
- package/dist/artifacts/queries.d.ts +7 -0
- package/dist/artifacts/queries.d.ts.map +1 -0
- package/dist/artifacts/queries.js +85 -0
- package/dist/artifacts/queries.js.map +1 -0
- package/dist/billing/index.d.ts +3 -0
- package/dist/billing/index.d.ts.map +1 -0
- package/dist/billing/index.js +27 -0
- package/dist/billing/index.js.map +1 -0
- package/dist/billing/spendGate.d.ts +72 -0
- package/dist/billing/spendGate.d.ts.map +1 -0
- package/dist/billing/spendGate.js +344 -0
- package/dist/billing/spendGate.js.map +1 -0
- package/dist/billing/workflowAuthorization.d.ts +56 -0
- package/dist/billing/workflowAuthorization.d.ts.map +1 -0
- package/dist/billing/workflowAuthorization.js +270 -0
- package/dist/billing/workflowAuthorization.js.map +1 -0
- package/dist/contracts/data/promptContracts.d.ts.map +1 -1
- package/dist/contracts/data/promptContracts.js +48 -36
- package/dist/contracts/data/promptContracts.js.map +1 -1
- package/dist/contracts/data/toolPermissions.d.ts +2 -0
- package/dist/contracts/data/toolPermissions.d.ts.map +1 -1
- package/dist/contracts/data/toolPermissions.js +2 -13
- package/dist/contracts/data/toolPermissions.js.map +1 -1
- package/dist/contracts/index.d.ts +2 -2
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +5 -3
- package/dist/contracts/index.js.map +1 -1
- package/dist/contracts/toolGatingPolicy.d.ts.map +1 -1
- package/dist/contracts/toolGatingPolicy.js +6 -2
- package/dist/contracts/toolGatingPolicy.js.map +1 -1
- package/dist/contracts/toolSurface.d.ts +1 -0
- package/dist/contracts/toolSurface.d.ts.map +1 -1
- package/dist/contracts/toolSurface.js +12 -5
- package/dist/contracts/toolSurface.js.map +1 -1
- package/dist/contracts/turnPolicy.d.ts +2 -1
- package/dist/contracts/turnPolicy.d.ts.map +1 -1
- package/dist/contracts/turnPolicy.js +14 -0
- package/dist/contracts/turnPolicy.js.map +1 -1
- package/dist/events/index.d.ts +2 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +12 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/runEvent.d.ts +31 -0
- package/dist/events/runEvent.d.ts.map +1 -0
- package/dist/events/runEvent.js +161 -0
- package/dist/events/runEvent.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -1
- package/dist/index.js.map +1 -1
- package/dist/public-skill-runtime/index.d.ts +1 -1
- package/dist/public-skill-runtime/index.d.ts.map +1 -1
- package/dist/public-skill-runtime/index.js +229 -119
- package/dist/public-skill-runtime/index.js.map +1 -1
- package/dist/schemas/agent/intent-input.schema.json +128 -0
- package/dist/schemas/agent/turn-analysis.schema.json +75 -0
- package/dist/schemas/artifacts/artifact-graph.schema.json +42 -0
- package/dist/schemas/artifacts/artifact-node.schema.json +137 -0
- package/dist/schemas/billing/spend-gate.schema.json +130 -0
- package/dist/schemas/billing/workflow-authorization.schema.json +83 -0
- package/dist/schemas/events/run-event.schema.json +111 -0
- package/dist/schemas/tools/tool-metadata.schema.json +78 -0
- package/dist/tools/definitions/animate-photo/definition.js +5 -5
- package/dist/tools/definitions/animate-photo/definition.js.map +1 -1
- package/dist/tools/definitions/edit-image/definition.js +3 -3
- package/dist/tools/definitions/edit-image/definition.js.map +1 -1
- package/dist/tools/definitions/generate-image/definition.js +4 -4
- package/dist/tools/definitions/generate-image/definition.js.map +1 -1
- package/dist/tools/definitions/generate-music/definition.js +2 -2
- package/dist/tools/definitions/generate-music/definition.js.map +1 -1
- package/dist/tools/definitions/generate-video/definition.js +2 -2
- package/dist/tools/definitions/generate-video/definition.js.map +1 -1
- package/dist/tools/definitions/sound-to-video/definition.js +2 -2
- package/dist/tools/definitions/sound-to-video/definition.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/normalizeArgs.d.ts +1 -0
- package/dist/tools/normalizeArgs.d.ts.map +1 -1
- package/dist/tools/normalizeArgs.js +83 -8
- package/dist/tools/normalizeArgs.js.map +1 -1
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/helpers.js +4 -2
- package/dist/utils/helpers.js.map +1 -1
- package/dist/workflows/index.d.ts +2 -0
- package/dist/workflows/index.d.ts.map +1 -1
- package/dist/workflows/index.js +7 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/primitives/index.d.ts +1 -0
- package/dist/workflows/primitives/index.d.ts.map +1 -1
- package/dist/workflows/primitives/index.js +1 -0
- package/dist/workflows/primitives/index.js.map +1 -1
- package/dist/workflows/primitives/sanitizer.d.ts +22 -0
- package/dist/workflows/primitives/sanitizer.d.ts.map +1 -0
- package/dist/workflows/primitives/sanitizer.js +96 -0
- package/dist/workflows/primitives/sanitizer.js.map +1 -0
- package/dist-esm/agent/index.js +5 -0
- package/dist-esm/agent/index.js.map +1 -0
- package/dist-esm/agent/intentInput.js +397 -0
- package/dist-esm/agent/intentInput.js.map +1 -0
- package/dist-esm/agent/toolMetadata.js +136 -0
- package/dist-esm/agent/toolMetadata.js.map +1 -0
- package/dist-esm/agent/turnAnalysis.js +147 -0
- package/dist-esm/agent/turnAnalysis.js.map +1 -0
- package/dist-esm/agent/turnPlan.js +180 -0
- package/dist-esm/agent/turnPlan.js.map +1 -0
- package/dist-esm/artifacts/graph.js +465 -0
- package/dist-esm/artifacts/graph.js.map +1 -0
- package/dist-esm/artifacts/index.js +3 -0
- package/dist-esm/artifacts/index.js.map +1 -0
- package/dist-esm/artifacts/queries.js +78 -0
- package/dist-esm/artifacts/queries.js.map +1 -0
- package/dist-esm/billing/index.js +3 -0
- package/dist-esm/billing/index.js.map +1 -0
- package/dist-esm/billing/spendGate.js +329 -0
- package/dist-esm/billing/spendGate.js.map +1 -0
- package/dist-esm/billing/workflowAuthorization.js +260 -0
- package/dist-esm/billing/workflowAuthorization.js.map +1 -0
- package/dist-esm/contracts/data/promptContracts.js +48 -36
- package/dist-esm/contracts/data/promptContracts.js.map +1 -1
- package/dist-esm/contracts/data/toolPermissions.js +2 -13
- package/dist-esm/contracts/data/toolPermissions.js.map +1 -1
- package/dist-esm/contracts/index.js +2 -2
- package/dist-esm/contracts/index.js.map +1 -1
- package/dist-esm/contracts/toolGatingPolicy.js +6 -2
- package/dist-esm/contracts/toolGatingPolicy.js.map +1 -1
- package/dist-esm/contracts/toolSurface.js +11 -4
- package/dist-esm/contracts/toolSurface.js.map +1 -1
- package/dist-esm/contracts/turnPolicy.js +13 -0
- package/dist-esm/contracts/turnPolicy.js.map +1 -1
- package/dist-esm/events/index.js +2 -0
- package/dist-esm/events/index.js.map +1 -0
- package/dist-esm/events/runEvent.js +152 -0
- package/dist-esm/events/runEvent.js.map +1 -0
- package/dist-esm/index.js +5 -0
- package/dist-esm/index.js.map +1 -1
- package/dist-esm/public-skill-runtime/index.js +229 -119
- package/dist-esm/public-skill-runtime/index.js.map +1 -1
- package/dist-esm/schemas/agent/intent-input.schema.json +128 -0
- package/dist-esm/schemas/agent/turn-analysis.schema.json +75 -0
- package/dist-esm/schemas/artifacts/artifact-graph.schema.json +42 -0
- package/dist-esm/schemas/artifacts/artifact-node.schema.json +137 -0
- package/dist-esm/schemas/billing/spend-gate.schema.json +130 -0
- package/dist-esm/schemas/billing/workflow-authorization.schema.json +83 -0
- package/dist-esm/schemas/events/run-event.schema.json +111 -0
- package/dist-esm/schemas/tools/tool-metadata.schema.json +78 -0
- package/dist-esm/tools/definitions/animate-photo/definition.js +5 -5
- package/dist-esm/tools/definitions/animate-photo/definition.js.map +1 -1
- package/dist-esm/tools/definitions/edit-image/definition.js +3 -3
- package/dist-esm/tools/definitions/edit-image/definition.js.map +1 -1
- package/dist-esm/tools/definitions/generate-image/definition.js +4 -4
- package/dist-esm/tools/definitions/generate-image/definition.js.map +1 -1
- package/dist-esm/tools/definitions/generate-music/definition.js +2 -2
- package/dist-esm/tools/definitions/generate-music/definition.js.map +1 -1
- package/dist-esm/tools/definitions/generate-video/definition.js +2 -2
- package/dist-esm/tools/definitions/generate-video/definition.js.map +1 -1
- package/dist-esm/tools/definitions/sound-to-video/definition.js +2 -2
- package/dist-esm/tools/definitions/sound-to-video/definition.js.map +1 -1
- package/dist-esm/tools/index.js +1 -1
- package/dist-esm/tools/index.js.map +1 -1
- package/dist-esm/tools/normalizeArgs.js +82 -8
- package/dist-esm/tools/normalizeArgs.js.map +1 -1
- package/dist-esm/utils/helpers.js +4 -2
- package/dist-esm/utils/helpers.js.map +1 -1
- package/dist-esm/workflows/index.js +1 -0
- package/dist-esm/workflows/index.js.map +1 -1
- package/dist-esm/workflows/primitives/index.js +1 -0
- package/dist-esm/workflows/primitives/index.js.map +1 -1
- package/dist-esm/workflows/primitives/sanitizer.js +89 -0
- package/dist-esm/workflows/primitives/sanitizer.js.map +1 -0
- package/package.json +30 -3
|
@@ -99,6 +99,7 @@ exports.inferDefaultVideoSteps = inferDefaultVideoSteps;
|
|
|
99
99
|
exports.resolveVideoSteps = resolveVideoSteps;
|
|
100
100
|
exports.sanitizeBatchPrompt = sanitizeBatchPrompt;
|
|
101
101
|
const index_js_1 = require("../contracts/data/index.js");
|
|
102
|
+
const turnPolicy_js_1 = require("../contracts/turnPolicy.js");
|
|
102
103
|
function isLtxWorkflow(workflow) {
|
|
103
104
|
return workflow === 't2v' || workflow === 'i2v' || workflow === 'ia2v' || workflow === 'a2v' || workflow === 'v2v';
|
|
104
105
|
}
|
|
@@ -185,9 +186,18 @@ function createPublicSkillDefaultContractRuntime(input = {}) {
|
|
|
185
186
|
],
|
|
186
187
|
});
|
|
187
188
|
}
|
|
189
|
+
const PUBLIC_SKILL_ADVISORY_SIGNAL_SOURCES = new Set(['regex', 'fact_extractor']);
|
|
190
|
+
function normalizePublicSkillSignal(signal) {
|
|
191
|
+
const source = (0, turnPolicy_js_1.normalizeSignalSource)(String(signal.source));
|
|
192
|
+
if (source === signal.source)
|
|
193
|
+
return signal;
|
|
194
|
+
return { ...signal, source };
|
|
195
|
+
}
|
|
188
196
|
function publicSkillSignalSourcesByKind(signals) {
|
|
189
197
|
const sourcesByKind = new Map();
|
|
190
198
|
for (const signal of signals) {
|
|
199
|
+
if (PUBLIC_SKILL_ADVISORY_SIGNAL_SOURCES.has(signal.source))
|
|
200
|
+
continue;
|
|
191
201
|
const sources = sourcesByKind.get(signal.kind) ?? new Set();
|
|
192
202
|
sources.add(signal.source);
|
|
193
203
|
sourcesByKind.set(signal.kind, sources);
|
|
@@ -226,7 +236,7 @@ function classifyPublicSkillTurn(input) {
|
|
|
226
236
|
const signals = [
|
|
227
237
|
...(input.signals ?? []),
|
|
228
238
|
...publicSkillSessionSignals(input.sessionState),
|
|
229
|
-
];
|
|
239
|
+
].map(normalizePublicSkillSignal);
|
|
230
240
|
const sourcesByKind = publicSkillSignalSourcesByKind(signals);
|
|
231
241
|
const forbidden = new Set();
|
|
232
242
|
const required = new Set();
|
|
@@ -654,10 +664,10 @@ exports.IMAGE_GENERATION_SKILL = {
|
|
|
654
664
|
exports.IMAGE_EDITING_SKILL = {
|
|
655
665
|
id: 'image_editing',
|
|
656
666
|
name: 'Image editing',
|
|
657
|
-
description: 'Edit, restore, restyle, refine, or change the camera angle of an existing image. Includes persona-conditioned edits — persona images
|
|
667
|
+
description: 'Edit, restore, restyle, refine, or change the camera angle of an existing image. Includes persona-conditioned edits — persona images should use edit_image and reference photos rather than text-to-image.',
|
|
658
668
|
toolNames: ['edit_image', 'restore_photo', 'apply_style', 'change_angle', 'refine_result'],
|
|
659
669
|
constraints: [
|
|
660
|
-
'
|
|
670
|
+
'For persona image output, use edit_image with a reference photo rather than generate_image.',
|
|
661
671
|
'refine_result acts on a prior generation in the session; do not call it before any image has been produced or uploaded.',
|
|
662
672
|
],
|
|
663
673
|
};
|
|
@@ -667,7 +677,7 @@ exports.VIDEO_GENERATION_SKILL = {
|
|
|
667
677
|
description: 'Text-to-video synthesis (LTX-2). Use when the user wants a new video clip generated from a prompt with no source image, audio, or clip.',
|
|
668
678
|
toolNames: ['generate_video'],
|
|
669
679
|
constraints: [
|
|
670
|
-
'
|
|
680
|
+
'For My Personas video requests, default to image_editing first to produce a conditioned scene image before animation. Use direct video only when the user explicitly asks to animate an existing persona image/reference or no source image is available for a voice-only request.',
|
|
671
681
|
],
|
|
672
682
|
};
|
|
673
683
|
exports.VIDEO_EDITING_SKILL = {
|
|
@@ -687,7 +697,7 @@ exports.VIDEO_EDITING_SKILL = {
|
|
|
687
697
|
'add_subtitles',
|
|
688
698
|
],
|
|
689
699
|
constraints: [
|
|
690
|
-
'
|
|
700
|
+
'Preserve per-clip retry and batch progress semantics. Use one Dynamic Prompt project for prompt-only fan-out, and avoid serial waterfall calls for independent clips.',
|
|
691
701
|
'animate_photo errors with all_failed must surface to the user; do not auto-retry from inside the chat loop.',
|
|
692
702
|
],
|
|
693
703
|
};
|
|
@@ -709,7 +719,7 @@ exports.PERSONA_MANAGEMENT_SKILL = {
|
|
|
709
719
|
description: "Resolve named personas to their reference photos and read/write the user's long-term creative memory (preferences, named subjects, ongoing projects).",
|
|
710
720
|
toolNames: ['resolve_personas', 'manage_memory'],
|
|
711
721
|
constraints: [
|
|
712
|
-
'
|
|
722
|
+
'For clear My Personas references, call resolve_personas before persona-conditioned image or video tools; do not rely on name text alone for identity.',
|
|
713
723
|
],
|
|
714
724
|
};
|
|
715
725
|
exports.APP_SETTINGS_SKILL = {
|
|
@@ -787,33 +797,48 @@ function compileStoryboardImagePromptFromProject(project) {
|
|
|
787
797
|
: `Overall storyboard canvas aspect ratio: ${layout.boardAspectRatio}.`;
|
|
788
798
|
return [
|
|
789
799
|
'CREATE:',
|
|
790
|
-
`Create exactly ${frameCount} sequential video storyboard frames as one
|
|
791
|
-
`Project title: ${project.title}.`,
|
|
792
|
-
project.durationSec !== null ? `Target duration: ${project.durationSec} seconds.` : 'Target duration: unspecified in source brief.',
|
|
793
|
-
'',
|
|
794
|
-
...compileStoryboardCountContractSection(project, layout),
|
|
800
|
+
`Create exactly ${frameCount} sequential video storyboard frames as one production storyboard sheet.`,
|
|
795
801
|
'',
|
|
796
|
-
|
|
802
|
+
'PROJECT:',
|
|
803
|
+
`Title: ${project.title}.`,
|
|
804
|
+
`Format: ${layout.targetVideoAspectRatio} video storyboard.`,
|
|
805
|
+
project.durationSec !== null ? `Target duration: ${project.durationSec} seconds.` : 'Target duration: unspecified in source brief.',
|
|
797
806
|
'',
|
|
798
|
-
'
|
|
807
|
+
'LAYOUT CONTRACT:',
|
|
808
|
+
`Create exactly ${frameCount} numbered storyboard panels; do not render fewer or more panels.`,
|
|
809
|
+
`Arrange panels in reading order, left-to-right then top-to-bottom: ${Array.from({ length: frameCount }, (_, index) => {
|
|
810
|
+
const scene = project.scenes[index];
|
|
811
|
+
return `[${index + 1}] ${scene?.id.toUpperCase() ?? `SCENE_${String(index + 1).padStart(2, '0')}`}`;
|
|
812
|
+
}).join(', ')}.`,
|
|
799
813
|
boardSizeLine,
|
|
800
814
|
`Individual scene-cell/frame aspect ratio: ${layout.cellAspectRatio}.`,
|
|
801
815
|
`Target final video aspect ratio: ${layout.targetVideoAspectRatio}.`,
|
|
802
816
|
`Layout preset: ${layout.layoutKind} - ${layout.layoutDescription}.`,
|
|
817
|
+
`Each panel must contain one distinct ${layout.cellAspectRatio} cinematic video-frame rectangle with compact notes outside the frame.`,
|
|
818
|
+
'Keep scene numbers, timecodes, titles, dialogue/VO, audio notes, and production notes outside the video-frame rectangles.',
|
|
819
|
+
'Do not merge panels, create inset thumbnails, make panels square, or overlay storyboard metadata inside the artwork frames.',
|
|
803
820
|
'',
|
|
804
|
-
...
|
|
821
|
+
...compileStoryboardReferenceSection(project),
|
|
822
|
+
'',
|
|
823
|
+
'STYLE:',
|
|
824
|
+
`${project.creativeBrief.visualQualityBar} with cinematic shot language, coherent art direction, readable labels, and consistent reference usage.`,
|
|
825
|
+
'Reference-driven personality: before drawing, infer concrete visual and behavioral cues from uploaded/reference images, including character attitude, materials, props, palette, brand tone, typography style, and implied world. Let those cues make this storyboard specific to the supplied subject instead of a generic reusable template.',
|
|
826
|
+
'Vary composition within the required grid: keep the exact scene count, layout, and cell geometry, and make each panel intentionally staged with distinct shot scale, pose/action, camera angle, lighting beat, transition idea, and character-specific detail.',
|
|
805
827
|
'',
|
|
806
|
-
|
|
807
|
-
project.creativeBrief.visualQualityBar,
|
|
828
|
+
...compileStoryboardStoryContinuitySection(project),
|
|
808
829
|
'',
|
|
809
830
|
'CRITICAL REQUIREMENTS:',
|
|
810
831
|
...compileStoryboardCriticalRequirements().map((item, index) => `${index + 1}. ${item}`),
|
|
832
|
+
...project.references
|
|
833
|
+
.filter(ref => ref.preservePriority === 'critical')
|
|
834
|
+
.map((ref, index) => `${compileStoryboardCriticalRequirements().length + index + 1}. Critical reference lock: ${ref.id} (${ref.kind}) must remain bound to its assigned usage scope: ${ref.usageScope}.`),
|
|
811
835
|
'',
|
|
812
836
|
...compileStoryboardScenesSection(project),
|
|
813
|
-
'TEXT
|
|
814
|
-
'
|
|
837
|
+
'TEXT RULES:',
|
|
838
|
+
'Place scene number, timing, scene title, beat title, and compact production labels outside each video frame in a clearly associated header, footer strip, side rail, or table. Do not overlay scene numbers, timecodes, production notes, Dialogue/VO labels, Audio/SFX text, or SFX/action callout words such as Whoosh!, Impact!, Boom!, Thud!, Slash!, Crack!, or Pop! on top of the video-frame artwork. Also do not overlay scene/beat titles on top of the video-frame artwork. Project titles are metadata, not in-frame text. Only listed diegetic or brand text belongs inside a frame. Quote and spell any required visible text exactly.',
|
|
815
839
|
'Visible text listed on a scene belongs only in that scene; repeat visible text only on scenes that list it.',
|
|
816
840
|
...storyboardRequiredVisibleText(project).map(formatStoryboardRequiredVisibleTextLine),
|
|
841
|
+
project.endCard.logoUsage ? `Logo usage: ${project.endCard.logoUsage}` : '',
|
|
817
842
|
'',
|
|
818
843
|
'NEGATIVE / AVOID:',
|
|
819
844
|
...compileStoryboardAvoidSection(project.creativeBrief.concept).map(item => `- ${item}`),
|
|
@@ -2748,8 +2773,8 @@ const STORYBOARD_COUNT_WORDS = {
|
|
|
2748
2773
|
};
|
|
2749
2774
|
const DEFAULT_STORYBOARD_TIMING_RULES = {
|
|
2750
2775
|
normalWordsPerSecondMin: 2.0,
|
|
2751
|
-
normalWordsPerSecondMax:
|
|
2752
|
-
fastWordsPerSecondMax:
|
|
2776
|
+
normalWordsPerSecondMax: 2.5,
|
|
2777
|
+
fastWordsPerSecondMax: 3.0,
|
|
2753
2778
|
minEndCardHoldSec: 2.0,
|
|
2754
2779
|
minPunchlineSec: 0.5,
|
|
2755
2780
|
toleranceSec: 0.25,
|
|
@@ -2942,10 +2967,13 @@ function describePortraitLetterboxCellArrangement(frameCount, cellAspectRatio) {
|
|
|
2942
2967
|
}
|
|
2943
2968
|
const columns = frameCount <= 8 ? 2 : frameCount <= 15 ? 3 : 4;
|
|
2944
2969
|
const rows = Math.ceil(frameCount / columns);
|
|
2970
|
+
const unusedSlotNote = columns * rows > frameCount
|
|
2971
|
+
? ['use unused grid slots as margin/notes space only']
|
|
2972
|
+
: [];
|
|
2945
2973
|
return [
|
|
2946
2974
|
`${frameCount} numbered scene slots arranged as a ${columns}-column x ${rows}-row grid inside a portrait sheet`,
|
|
2947
2975
|
`each slot contains one ${frameShape} with compact labels outside the rectangle`,
|
|
2948
|
-
|
|
2976
|
+
...unusedSlotNote,
|
|
2949
2977
|
].join('; ');
|
|
2950
2978
|
}
|
|
2951
2979
|
function describeLandscapePortraitCellArrangement(frameCount, cellAspectRatio) {
|
|
@@ -2955,10 +2983,13 @@ function describeLandscapePortraitCellArrangement(frameCount, cellAspectRatio) {
|
|
|
2955
2983
|
}
|
|
2956
2984
|
const rows = frameCount <= 8 ? 2 : frameCount <= 15 ? 3 : 4;
|
|
2957
2985
|
const columns = Math.ceil(frameCount / rows);
|
|
2986
|
+
const unusedSlotNote = columns * rows > frameCount
|
|
2987
|
+
? ['use unused grid slots as margin/notes space only']
|
|
2988
|
+
: [];
|
|
2958
2989
|
return [
|
|
2959
2990
|
`${frameCount} numbered scene slots arranged as a ${rows}-row x ${columns}-column grid inside a landscape board`,
|
|
2960
2991
|
`each slot contains one ${frameShape} with compact labels outside the rectangle`,
|
|
2961
|
-
|
|
2992
|
+
...unusedSlotNote,
|
|
2962
2993
|
].join('; ');
|
|
2963
2994
|
}
|
|
2964
2995
|
function parseAspectRatioPair(aspectRatio) {
|
|
@@ -3073,13 +3104,16 @@ function describeSingleOrientationStoryboardArrangement(boardAspectRatio, cellAs
|
|
|
3073
3104
|
const slotGrid = pickBalancedSlotGrid(frameCount);
|
|
3074
3105
|
const cols = cellOrientation === 'landscape' ? slotGrid.minor : slotGrid.major;
|
|
3075
3106
|
const rows = cellOrientation === 'landscape' ? slotGrid.major : slotGrid.minor;
|
|
3107
|
+
const unusedSlotNote = cols * rows > frameCount
|
|
3108
|
+
? ['use unused grid slots as margin/notes space only']
|
|
3109
|
+
: [];
|
|
3076
3110
|
if (boardOrientation === 'portrait') {
|
|
3077
3111
|
return {
|
|
3078
3112
|
layoutKind: 'portrait_grid',
|
|
3079
3113
|
layoutDescription: [
|
|
3080
3114
|
`${frameCount} numbered scene slots arranged as a ${cols}-column x ${rows}-row grid inside a portrait storyboard sheet`,
|
|
3081
3115
|
`each slot contains one ${frameShape} with compact labels outside the rectangle`,
|
|
3082
|
-
|
|
3116
|
+
...unusedSlotNote,
|
|
3083
3117
|
].join('; '),
|
|
3084
3118
|
};
|
|
3085
3119
|
}
|
|
@@ -3089,7 +3123,7 @@ function describeSingleOrientationStoryboardArrangement(boardAspectRatio, cellAs
|
|
|
3089
3123
|
layoutDescription: [
|
|
3090
3124
|
`${frameCount} numbered scene slots arranged as a ${rows}-row x ${cols}-column grid inside a ${boardLabel}`,
|
|
3091
3125
|
`each slot contains one ${frameShape} with compact labels outside the rectangle`,
|
|
3092
|
-
|
|
3126
|
+
...unusedSlotNote,
|
|
3093
3127
|
].join('; '),
|
|
3094
3128
|
};
|
|
3095
3129
|
}
|
|
@@ -3473,6 +3507,16 @@ function cleanStoryboardReferenceSubjectHint(context, index) {
|
|
|
3473
3507
|
const explicitSubject = cleanExplicitStoryboardReferenceSubject(source, index);
|
|
3474
3508
|
if (explicitSubject)
|
|
3475
3509
|
return explicitSubject;
|
|
3510
|
+
const inlineReference = new RegExp(String.raw `\b(?:uploaded|attached|provided|reference|source|input)?\s*(?:image|photo|picture|asset)\s*(?:#|number\s*)?${index}\b\s*(?:is|=|:|-)?\s*([^.;,\n]{2,120})`, 'i').exec(source);
|
|
3511
|
+
if (inlineReference?.[1]) {
|
|
3512
|
+
const inlineSubject = compactStoryboardLine(stripStoryboardMarkup(inlineReference[1]))
|
|
3513
|
+
.replace(/\b(?:use|usage|preserve|reference|asset)\b[\s\S]*$/i, '')
|
|
3514
|
+
.replace(/[.\s]+$/g, '')
|
|
3515
|
+
.trim();
|
|
3516
|
+
if (inlineSubject && !/^(?:logo|brand|character|mascot|asset|reference|image|photo|picture)$/i.test(inlineSubject)) {
|
|
3517
|
+
return inlineSubject.slice(0, 180).replace(/\s+\S*$/, match => inlineSubject.length > 180 ? '' : match).trim();
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3476
3520
|
const cleaned = source
|
|
3477
3521
|
.replace(new RegExp(String.raw `^(?:image|photo|picture|asset)\s*(?:#|number\s*)?${index}\s*(?:\([^)]*\))?\s*:?\s*`, 'i'), '')
|
|
3478
3522
|
.replace(new RegExp(String.raw `^(?:uploaded|attached|provided|reference|source|input)\s+(?:image|photo|picture|asset)\s*(?:#|number\s*)?${index}\s*:?\s*`, 'i'), '')
|
|
@@ -3580,6 +3624,51 @@ function buildStoryboardReferenceAssets(userIntentText, prompt) {
|
|
|
3580
3624
|
preservePriority: storyboardPreservePriorityFromRole(role),
|
|
3581
3625
|
}));
|
|
3582
3626
|
}
|
|
3627
|
+
function storyboardReferenceIndexFromText(value) {
|
|
3628
|
+
const normalized = value.trim();
|
|
3629
|
+
if (!normalized)
|
|
3630
|
+
return null;
|
|
3631
|
+
const match = normalized.match(/^(?:@?\s*)?(?:image|photo|picture|asset|uploaded\s+asset|uploaded\s+image)[\s_#-]*(\d{1,2})$/i)
|
|
3632
|
+
?? normalized.match(/^image_(\d{1,2})$/i);
|
|
3633
|
+
if (!match)
|
|
3634
|
+
return null;
|
|
3635
|
+
const index = Number(match[1]);
|
|
3636
|
+
return Number.isInteger(index) && index > 0 ? index : null;
|
|
3637
|
+
}
|
|
3638
|
+
function looksLikeUnknownIndexedStoryboardReference(value) {
|
|
3639
|
+
return /^(?:@?\s*)?(?:image|photo|picture|asset|uploaded\s+asset|uploaded\s+image)[\s_#-]*\d{1,2}$/i.test(value.trim())
|
|
3640
|
+
|| /^image_\d{1,2}$/i.test(value.trim());
|
|
3641
|
+
}
|
|
3642
|
+
function normalizeStoryboardSceneReferenceUsage(referenceUsage, references) {
|
|
3643
|
+
if (referenceUsage.length === 0 || references.length === 0)
|
|
3644
|
+
return [];
|
|
3645
|
+
const knownById = new Map(references.map(ref => [ref.id.toLowerCase(), ref.id]));
|
|
3646
|
+
const knownByIndex = new Map(references
|
|
3647
|
+
.filter(ref => typeof ref.index === 'number')
|
|
3648
|
+
.map(ref => [ref.index, ref.id]));
|
|
3649
|
+
const normalized = [];
|
|
3650
|
+
for (const raw of referenceUsage) {
|
|
3651
|
+
const value = raw.trim();
|
|
3652
|
+
if (!value)
|
|
3653
|
+
continue;
|
|
3654
|
+
const knownId = knownById.get(value.toLowerCase());
|
|
3655
|
+
if (knownId) {
|
|
3656
|
+
normalized.push(knownId);
|
|
3657
|
+
continue;
|
|
3658
|
+
}
|
|
3659
|
+
const index = storyboardReferenceIndexFromText(value);
|
|
3660
|
+
if (index !== null) {
|
|
3661
|
+
const id = knownByIndex.get(index);
|
|
3662
|
+
if (id)
|
|
3663
|
+
normalized.push(id);
|
|
3664
|
+
continue;
|
|
3665
|
+
}
|
|
3666
|
+
if (looksLikeUnknownIndexedStoryboardReference(value))
|
|
3667
|
+
continue;
|
|
3668
|
+
normalized.push(value);
|
|
3669
|
+
}
|
|
3670
|
+
return uniqueStoryboardStrings(normalized);
|
|
3671
|
+
}
|
|
3583
3672
|
function compileStoryboardReferenceSection(project) {
|
|
3584
3673
|
const refs = project.references;
|
|
3585
3674
|
if (refs.length <= 0) {
|
|
@@ -3637,6 +3726,12 @@ function extractStoryboardRequiredText(text) {
|
|
|
3637
3726
|
const isProductionDirectionField = /\b(?:action|motion|camera|transition|audio|sfx|fx|foley|sound|music|dialogue|vo|voiceover|voice-over|narration|speech|lighting|style|performance|beat)\b/i.test(fieldLabel)
|
|
3638
3727
|
&& !visibleTextContextPattern.test(fieldLabel);
|
|
3639
3728
|
const hasExplicitVisibleTextContext = visibleTextContextPattern.test(line);
|
|
3729
|
+
const precedingCue = compactStoryboardLine([
|
|
3730
|
+
precedingText ?? '',
|
|
3731
|
+
line.slice(0, Math.max(0, matchIndex - lineStart)),
|
|
3732
|
+
].join(' ')).slice(-180);
|
|
3733
|
+
const spokenTextCue = /\b(?:dialogue|vo|v\.o\.|voiceover|voice-over|voice\s*over|narration|speech|spoken(?:\s+line)?|audio\s*\/\s*dialogue)\b/i.test(precedingCue)
|
|
3734
|
+
|| /\b(?:narrator|speaker|actor|character|person|subject|host|mascot|performer|he|she|they)\b[^"“`]{0,80}\b(?:says?|speaks?|whispers?|shouts?|asks?|replies?)\b/i.test(precedingCue);
|
|
3640
3735
|
const isRestrictionOnlyTextInstruction = visibleTextContextPattern.test(fieldLabel || line)
|
|
3641
3736
|
&& visibleTextRestrictionPattern.test(value)
|
|
3642
3737
|
&& !/["“”`]/.test(value);
|
|
@@ -3668,6 +3763,9 @@ function extractStoryboardRequiredText(text) {
|
|
|
3668
3763
|
if ((isProductionDirectionField || looksLikeActionOrSfxCallout) && !hasExplicitVisibleTextContext) {
|
|
3669
3764
|
return true;
|
|
3670
3765
|
}
|
|
3766
|
+
if (spokenTextCue && !/\b(?:visible|on[-\s]?screen|in[-\s]?frame|text|copy|cta|tagline|headline|title\s+card|caption|subtitle|super|wordmark|slogan|sign|poster|banner|label|placard|sticker|screen)\b/i.test(precedingCue)) {
|
|
3767
|
+
return true;
|
|
3768
|
+
}
|
|
3671
3769
|
if (looksLikeAssetHandle
|
|
3672
3770
|
&& /\b(?:asset|reference|image|photo|upload|file|filename|logo|brand)\b/i.test(line)) {
|
|
3673
3771
|
return true;
|
|
@@ -3743,7 +3841,7 @@ function extractStoryboardRequiredText(text) {
|
|
|
3743
3841
|
|| /^(?:powered|made|built|created|generated|rendered|presented)\s+by\b/i.test(value);
|
|
3744
3842
|
return !productionDirection || copyOverridesProduction;
|
|
3745
3843
|
};
|
|
3746
|
-
const endCardLabelPattern = /^\s*(?:[-*+]\s*)?(?:end|final|closing)\s+(?:scene|card|frame|shot|cta|logo(?:\s+lockup)?)\s*:\s*([^\n]*)$/gim;
|
|
3844
|
+
const endCardLabelPattern = /^\s*(?:[-*+]\s*)?(?:(?:end|final|closing)\s+(?:scene|card|frame|shot|cta|logo(?:\s+lockup)?)|(?:scene|shot|beat|panel|frame)\s*\d{1,2}\s+(?:visible\s+text|on[-\s]?screen\s+text|onscreen\s+text|text|copy|cta)(?:\s+must\s+be\s+exactly)?)\s*:\s*([^\n]*)$/gim;
|
|
3747
3845
|
for (const match of text.matchAll(endCardLabelPattern)) {
|
|
3748
3846
|
const matchIndex = match.index ?? 0;
|
|
3749
3847
|
const lineEnd = text.indexOf('\n', matchIndex);
|
|
@@ -3782,9 +3880,10 @@ function extractStoryboardRequiredText(text) {
|
|
|
3782
3880
|
for (const match of text.matchAll(exactTextPattern)) {
|
|
3783
3881
|
const fullMatch = match[0];
|
|
3784
3882
|
const quoteOpenInMatch = fullMatch.search(/["“`]/);
|
|
3785
|
-
const
|
|
3883
|
+
const quoteAbsIndex = (match.index ?? 0) + Math.max(0, quoteOpenInMatch);
|
|
3884
|
+
const lineStart = text.lastIndexOf('\n', quoteAbsIndex) + 1;
|
|
3786
3885
|
addRequiredText(match[1] ?? match[2] ?? match[3], match.index ?? 0, {
|
|
3787
|
-
precedingText:
|
|
3886
|
+
precedingText: text.slice(lineStart, quoteAbsIndex),
|
|
3788
3887
|
});
|
|
3789
3888
|
}
|
|
3790
3889
|
const labeledTextPattern = /\b(?:on[-\s]?screen\s+text|visible\s+text|text\s+only|text|end\s+card\s+text|final\s+text|tagline|cta|headline|title\s+card|copy)\s*:\s*([^\n]{1,260})/gi;
|
|
@@ -4243,6 +4342,26 @@ function storyboardTableRowLooksLikeCountedBeat(cells, headers) {
|
|
|
4243
4342
|
const candidate = compactStoryboardLine(cells[Math.max(0, headerIndex)] || cells[0] || '');
|
|
4244
4343
|
return /^(?:beat|scene|shot|panel|frame)?\s*(?:#\s*)?0?\d{1,2}\b/i.test(candidate);
|
|
4245
4344
|
}
|
|
4345
|
+
function normalizeStoryboardTableCellsToHeaders(cells, headers) {
|
|
4346
|
+
if (!headers || cells.length <= headers.length)
|
|
4347
|
+
return cells;
|
|
4348
|
+
const overflow = cells.length - headers.length;
|
|
4349
|
+
const combinedAudioDialogueIndex = headers.findIndex(header => /\baudio\b/i.test(header)
|
|
4350
|
+
&& /\b(?:dialogue|vo|v\.o\.|voiceover|speech|narration)\b/i.test(header));
|
|
4351
|
+
if (combinedAudioDialogueIndex < 0)
|
|
4352
|
+
return cells;
|
|
4353
|
+
const mergeEnd = combinedAudioDialogueIndex + overflow;
|
|
4354
|
+
if (mergeEnd >= cells.length)
|
|
4355
|
+
return cells;
|
|
4356
|
+
const overflowCells = cells.slice(combinedAudioDialogueIndex, mergeEnd + 1);
|
|
4357
|
+
if (!overflowCells.some(cell => storyboardTableCellLooksLikeUnlabeledSpeech(cell)))
|
|
4358
|
+
return cells;
|
|
4359
|
+
return [
|
|
4360
|
+
...cells.slice(0, combinedAudioDialogueIndex),
|
|
4361
|
+
overflowCells.join('\n'),
|
|
4362
|
+
...cells.slice(mergeEnd + 1),
|
|
4363
|
+
];
|
|
4364
|
+
}
|
|
4246
4365
|
function storyboardTableCellWithoutDialogue(cell, dialogue) {
|
|
4247
4366
|
const normalizedDialogue = compactStoryboardLine(dialogue).toLowerCase();
|
|
4248
4367
|
return cell
|
|
@@ -4300,13 +4419,14 @@ function splitStoryboardTableSections(text) {
|
|
|
4300
4419
|
const sections = [];
|
|
4301
4420
|
let headers = null;
|
|
4302
4421
|
for (const line of text.split(/\r?\n/)) {
|
|
4303
|
-
const
|
|
4304
|
-
if (
|
|
4422
|
+
const rawCells = storyboardMarkdownTableCells(line);
|
|
4423
|
+
if (rawCells.length < 3)
|
|
4305
4424
|
continue;
|
|
4306
|
-
if (looksLikeStoryboardTableHeader(
|
|
4307
|
-
headers =
|
|
4425
|
+
if (looksLikeStoryboardTableHeader(rawCells)) {
|
|
4426
|
+
headers = rawCells;
|
|
4308
4427
|
continue;
|
|
4309
4428
|
}
|
|
4429
|
+
const cells = normalizeStoryboardTableCellsToHeaders(rawCells, headers);
|
|
4310
4430
|
const timingCellIndex = cells.findIndex(cell => extractStoryboardTimingMarker(cell) !== null);
|
|
4311
4431
|
if (timingCellIndex < 0)
|
|
4312
4432
|
continue;
|
|
@@ -5047,10 +5167,10 @@ function alignAssistantStoryboardDialogueWithUserSource(scenes, userIntentText,
|
|
|
5047
5167
|
return { scenes, shouldRetime: false };
|
|
5048
5168
|
return { scenes: repaired, shouldRetime: true };
|
|
5049
5169
|
}
|
|
5050
|
-
function minimumStoryboardSceneDurationForDialogue(scene, rules) {
|
|
5170
|
+
function minimumStoryboardSceneDurationForDialogue(scene, rules, wordsPerSecondMax = rules.normalWordsPerSecondMax) {
|
|
5051
5171
|
const dialogueWords = countWords(scene.dialogue);
|
|
5052
5172
|
let minDuration = dialogueWords > 0
|
|
5053
|
-
? dialogueWords /
|
|
5173
|
+
? dialogueWords / wordsPerSecondMax
|
|
5054
5174
|
: 0.5;
|
|
5055
5175
|
const sceneContext = `${scene.title}\n${scene.visual}\n${scene.textInImage.join(' ')}`;
|
|
5056
5176
|
const hasReadableText = scene.textInImage.length > 0;
|
|
@@ -5073,13 +5193,22 @@ function retimeStoryboardScenesForDialogue(scenes, targetDurationSec, rules = DE
|
|
|
5073
5193
|
const needsRetiming = scenes.some((scene, index) => (scene.durationSec ?? 0) + rules.toleranceSec < minimumDurations[index]);
|
|
5074
5194
|
if (!needsRetiming)
|
|
5075
5195
|
return scenes;
|
|
5076
|
-
|
|
5196
|
+
let requiredDurations = minimumDurations;
|
|
5197
|
+
let minimumTotal = Math.round(requiredDurations.reduce((sum, duration) => sum + duration, 0) * 100) / 100;
|
|
5198
|
+
if (minimumTotal > targetDurationSec + rules.toleranceSec && rules.fastWordsPerSecondMax > rules.normalWordsPerSecondMax) {
|
|
5199
|
+
const fastDurations = scenes.map(scene => minimumStoryboardSceneDurationForDialogue(scene, rules, rules.fastWordsPerSecondMax));
|
|
5200
|
+
const fastTotal = Math.round(fastDurations.reduce((sum, duration) => sum + duration, 0) * 100) / 100;
|
|
5201
|
+
if (fastTotal <= targetDurationSec + rules.toleranceSec) {
|
|
5202
|
+
requiredDurations = fastDurations;
|
|
5203
|
+
minimumTotal = fastTotal;
|
|
5204
|
+
}
|
|
5205
|
+
}
|
|
5077
5206
|
if (minimumTotal > targetDurationSec + rules.toleranceSec)
|
|
5078
5207
|
return scenes;
|
|
5079
5208
|
const extraDuration = Math.max(0, targetDurationSec - minimumTotal);
|
|
5080
5209
|
const originalDurations = scenes.map(scene => scene.durationSec ?? 0);
|
|
5081
5210
|
const weightTotal = originalDurations.reduce((sum, duration) => sum + Math.max(0.01, duration), 0);
|
|
5082
|
-
const unroundedDurations =
|
|
5211
|
+
const unroundedDurations = requiredDurations.map((minimum, index) => (minimum + (extraDuration * Math.max(0.01, originalDurations[index]) / weightTotal)));
|
|
5083
5212
|
const timelineStartSec = scenes[0].startSec ?? 0;
|
|
5084
5213
|
const timelineEndSec = Math.round((timelineStartSec + targetDurationSec) * 100) / 100;
|
|
5085
5214
|
let cursor = timelineStartSec;
|
|
@@ -5251,6 +5380,27 @@ function finalStoryboardSceneVisibleText(scenes) {
|
|
|
5251
5380
|
}
|
|
5252
5381
|
return [];
|
|
5253
5382
|
}
|
|
5383
|
+
function applyStoryboardEndCardTextToScenes(scenes, endCardText) {
|
|
5384
|
+
if (scenes.length === 0 || endCardText.length === 0)
|
|
5385
|
+
return scenes;
|
|
5386
|
+
const finalSceneIndex = scenes.length - 1;
|
|
5387
|
+
const explicitEndCardIndex = (() => {
|
|
5388
|
+
for (let index = scenes.length - 1; index >= 0; index -= 1) {
|
|
5389
|
+
if (storyboardSceneLooksLikeEndCard(scenes[index]))
|
|
5390
|
+
return index;
|
|
5391
|
+
}
|
|
5392
|
+
return finalSceneIndex;
|
|
5393
|
+
})();
|
|
5394
|
+
return scenes.map((scene, index) => index === explicitEndCardIndex
|
|
5395
|
+
? {
|
|
5396
|
+
...scene,
|
|
5397
|
+
textInImage: uniqueStoryboardStrings([
|
|
5398
|
+
...scene.textInImage,
|
|
5399
|
+
...endCardText,
|
|
5400
|
+
]),
|
|
5401
|
+
}
|
|
5402
|
+
: scene);
|
|
5403
|
+
}
|
|
5254
5404
|
function storyboardRequiredTextKey(value) {
|
|
5255
5405
|
return compactStoryboardLine(value)
|
|
5256
5406
|
.toLowerCase()
|
|
@@ -5301,6 +5451,7 @@ function buildStoryboardProject(options) {
|
|
|
5301
5451
|
const userIntentText = canonicalStoryboardScriptContext(rawUserIntentText) || rawUserIntentText;
|
|
5302
5452
|
const narrativeUserIntentText = cleanStoryboardNarrativeSourceText(userIntentText);
|
|
5303
5453
|
const approvedScriptContext = cleanStoryboardNarrativeSourceText(canonicalStoryboardScriptContext(options.approvedScriptContext));
|
|
5454
|
+
const referencePromptContext = cleanStoryboardNarrativeSourceText(canonicalStoryboardScriptContext(prompt) || prompt);
|
|
5304
5455
|
const primarySourceBrief = selectStoryboardSourceBriefForCompile(options, userIntentText);
|
|
5305
5456
|
const sourceText = stripGenericStoryboardVisibleTextMetadata(sanitizeStoryboardExternalAudioReferences([
|
|
5306
5457
|
cleanStoryboardNarrativeSourceText(primarySourceBrief),
|
|
@@ -5324,7 +5475,7 @@ function buildStoryboardProject(options) {
|
|
|
5324
5475
|
const requestedDurationSec = inferRequestedTotalVideoDurationSeconds(userIntentText);
|
|
5325
5476
|
const durationSec = requestedDurationSec ?? inferRequestedTotalVideoDurationSeconds(sourceText);
|
|
5326
5477
|
const references = buildStoryboardReferenceAssets(userIntentText, [
|
|
5327
|
-
|
|
5478
|
+
referencePromptContext,
|
|
5328
5479
|
approvedScriptContext,
|
|
5329
5480
|
].filter(Boolean).join('\n\n'));
|
|
5330
5481
|
const approvedSections = approvedScriptContext
|
|
@@ -5355,21 +5506,25 @@ function buildStoryboardProject(options) {
|
|
|
5355
5506
|
return buildSceneFromSection(section, references, null, storyboardScenePlanningContractForIndex(options.planningContract, section.number));
|
|
5356
5507
|
})
|
|
5357
5508
|
: [];
|
|
5358
|
-
const scenes = parsedScenes
|
|
5509
|
+
const scenes = parsedScenes.map(scene => ({
|
|
5510
|
+
...scene,
|
|
5511
|
+
referenceUsage: normalizeStoryboardSceneReferenceUsage(scene.referenceUsage, references),
|
|
5512
|
+
}));
|
|
5359
5513
|
const timingNormalizedScenes = normalizeAssistantStoryboardSceneTiming(scenes, durationSec, options.promptAuthorship);
|
|
5360
5514
|
const dialogueAlignment = alignAssistantStoryboardDialogueWithUserSource(timingNormalizedScenes, userIntentText, options.promptAuthorship);
|
|
5361
|
-
const
|
|
5515
|
+
const dialogueTimedScenes = (options.promptAuthorship === 'assistant' || dialogueAlignment.shouldRetime)
|
|
5362
5516
|
? retimeStoryboardScenesForDialogue(dialogueAlignment.scenes, durationSec)
|
|
5363
5517
|
: dialogueAlignment.scenes;
|
|
5364
5518
|
const userConstraintSource = buildStoryboardUserConstraintSource(narrativeUserIntentText, cleanStoryboardNarrativeSourceText(primarySourceBrief), options);
|
|
5365
|
-
const { mustIncludeText, endCardText } = storyboardRequiredTextForProject(options, userConstraintSource,
|
|
5519
|
+
const { mustIncludeText, endCardText } = storyboardRequiredTextForProject(options, userConstraintSource, dialogueTimedScenes);
|
|
5520
|
+
const normalizedScenes = retimeStoryboardScenesForDialogue(applyStoryboardEndCardTextToScenes(dialogueTimedScenes, endCardText), durationSec);
|
|
5366
5521
|
const voiceLines = assignVoiceLinesToScenes(normalizedScenes, sourceText);
|
|
5367
5522
|
const storySpineFallback = approvedScriptContext
|
|
5368
5523
|
|| (options.promptAuthorship === 'assistant' ? narrativeUserIntentText : cleanStoryboardNarrativeSourceText(primarySourceBrief))
|
|
5369
5524
|
|| prompt
|
|
5370
5525
|
|| narrativeUserIntentText;
|
|
5371
5526
|
const storySpine = inferStoryboardStorySpine(allText, storySpineFallback);
|
|
5372
|
-
const productFeatureMap = inferStoryboardProductFeatureMap(allText,
|
|
5527
|
+
const productFeatureMap = inferStoryboardProductFeatureMap(allText, normalizedScenes);
|
|
5373
5528
|
const beatMarkerScripts = [approvedScriptContext, sourceText];
|
|
5374
5529
|
let directBeatMarkerCount = 0;
|
|
5375
5530
|
for (const text of beatMarkerScripts) {
|
|
@@ -5599,67 +5754,17 @@ function compileStoryboardStoryContinuitySection(project) {
|
|
|
5599
5754
|
'Keep product/feature/brand meaning in the associated notes. Do not force long product explanations or the whole script into tiny in-frame text.',
|
|
5600
5755
|
];
|
|
5601
5756
|
}
|
|
5602
|
-
function compileStoryboardCountContractSection(project, layout, expectedFrameCount = project.scenes.length) {
|
|
5603
|
-
if (expectedFrameCount <= 0)
|
|
5604
|
-
return [];
|
|
5605
|
-
const sceneSlots = Array.from({ length: expectedFrameCount }, (_, index) => {
|
|
5606
|
-
const scene = project.scenes[index];
|
|
5607
|
-
return `[${index + 1}] ${scene?.id.toUpperCase() ?? `SCENE_${String(index + 1).padStart(2, '0')}`}`;
|
|
5608
|
-
})
|
|
5609
|
-
.join(', ');
|
|
5610
|
-
return [
|
|
5611
|
-
'COUNT / GRID CONTRACT:',
|
|
5612
|
-
`Required scene count: exactly ${expectedFrameCount} numbered storyboard scene slots; do not render fewer slots and do not add extra scene slots.`,
|
|
5613
|
-
`Allocate the full ${expectedFrameCount}-slot storyboard grid before drawing details. Fill slots in reading order, left-to-right then top-to-bottom: ${sceneSlots}.`,
|
|
5614
|
-
`Each allocated scene slot gets one distinct ${layout.cellAspectRatio} cinematic video-frame rectangle plus its own compact notes outside that rectangle. Do not merge adjacent scenes, combine two beats into one slot, duplicate slots, or place thumbnail/inset panels inside a slot.`,
|
|
5615
|
-
`Layout preset: ${layout.layoutKind} - ${layout.layoutDescription}. The final sheet should be visibly countable as ${expectedFrameCount} numbered scene slots at a glance.`,
|
|
5616
|
-
];
|
|
5617
|
-
}
|
|
5618
|
-
function compileStoryboardFrameGeometrySection(layout) {
|
|
5619
|
-
const cellOrientation = parseAspectRatioOrientation(layout.cellAspectRatio);
|
|
5620
|
-
if (cellOrientation === 'portrait') {
|
|
5621
|
-
return [
|
|
5622
|
-
'PORTRAIT FRAME GEOMETRY:',
|
|
5623
|
-
'Treat each numbered scene slot as a container, not as the artwork shape.',
|
|
5624
|
-
`Inside every numbered scene slot, draw one identical upright ${layout.cellAspectRatio} video-frame rectangle whose height is visibly greater than its width.`,
|
|
5625
|
-
`Keep all scene numbers, timing, titles, notes, headers, footers, and production labels outside the ${layout.cellAspectRatio} video-frame rectangles.`,
|
|
5626
|
-
`Do not make the numbered scene slots, frame artwork areas, or thumbnails square. Square cells violate the requested ${layout.cellAspectRatio} final video format.`,
|
|
5627
|
-
'Unused grid slots must remain blank margin/notes space only; do not stretch or square off the portrait frame rectangles to fill the grid.',
|
|
5628
|
-
];
|
|
5629
|
-
}
|
|
5630
|
-
if (cellOrientation === 'landscape') {
|
|
5631
|
-
return [
|
|
5632
|
-
'LANDSCAPE FRAME GEOMETRY:',
|
|
5633
|
-
'Treat each numbered scene slot as a container, not as the artwork shape.',
|
|
5634
|
-
`Inside every numbered scene slot, draw one identical wide ${layout.cellAspectRatio} video-frame rectangle whose width is visibly greater than its height.`,
|
|
5635
|
-
`Keep all scene numbers, timing, titles, notes, headers, footers, and production labels outside the ${layout.cellAspectRatio} video-frame rectangles.`,
|
|
5636
|
-
`Do not make the numbered scene slots, frame artwork areas, or thumbnails square. Square cells violate the requested ${layout.cellAspectRatio} final video format.`,
|
|
5637
|
-
'Unused grid slots must remain blank margin/notes space only; do not stretch or square off the landscape frame rectangles to fill the grid.',
|
|
5638
|
-
];
|
|
5639
|
-
}
|
|
5640
|
-
if (cellOrientation === 'square') {
|
|
5641
|
-
return [
|
|
5642
|
-
'SQUARE FRAME GEOMETRY:',
|
|
5643
|
-
'Treat each numbered scene slot as a container, not as the artwork shape.',
|
|
5644
|
-
`Inside every numbered scene slot, draw one identical square ${layout.cellAspectRatio} video-frame area plus compact notes outside that square.`,
|
|
5645
|
-
`Do not stretch square frame artwork areas into portrait, landscape, full-board, or any aspect ratio other than ${layout.cellAspectRatio}.`,
|
|
5646
|
-
];
|
|
5647
|
-
}
|
|
5648
|
-
return [
|
|
5649
|
-
'FRAME GEOMETRY:',
|
|
5650
|
-
`Inside every numbered scene slot, draw one identical ${layout.cellAspectRatio} video-frame rectangle plus compact notes outside that rectangle.`,
|
|
5651
|
-
`Do not let frame artwork areas drift to square, full-board, or any aspect ratio other than ${layout.cellAspectRatio}.`,
|
|
5652
|
-
];
|
|
5653
|
-
}
|
|
5654
5757
|
function promptReferenceUsageForScene(referenceUsage, references, modelId) {
|
|
5655
5758
|
if (referenceUsage.length === 0)
|
|
5656
5759
|
return '';
|
|
5657
5760
|
const referenceById = new Map(references.map((ref, index) => [ref.id, { ref, index }]));
|
|
5658
|
-
return uniqueStoryboardStrings(referenceUsage.
|
|
5761
|
+
return uniqueStoryboardStrings(referenceUsage.flatMap(value => {
|
|
5659
5762
|
const found = referenceById.get(value);
|
|
5660
|
-
if (
|
|
5661
|
-
return
|
|
5662
|
-
|
|
5763
|
+
if (found)
|
|
5764
|
+
return [formatModelRef(modelId, found.ref.index ?? found.index + 1, 'image')];
|
|
5765
|
+
if (looksLikeUnknownIndexedStoryboardReference(value))
|
|
5766
|
+
return [];
|
|
5767
|
+
return [value];
|
|
5663
5768
|
})).join(', ');
|
|
5664
5769
|
}
|
|
5665
5770
|
function storyboardMetadataLabelAliases(labels) {
|
|
@@ -5794,32 +5899,36 @@ function compileVideoStoryboardImagePrompt(options) {
|
|
|
5794
5899
|
: `Overall storyboard canvas aspect ratio: ${layout.boardAspectRatio}.`;
|
|
5795
5900
|
return [
|
|
5796
5901
|
'CREATE:',
|
|
5797
|
-
`Create exactly ${compiledFrameCount} sequential video storyboard frames as one
|
|
5798
|
-
`Project title: ${project.title}.`,
|
|
5799
|
-
project.durationSec !== null ? `Target duration: ${project.durationSec} seconds.` : 'Target duration: unspecified in source brief.',
|
|
5800
|
-
'',
|
|
5801
|
-
...compileStoryboardCountContractSection(project, layout, compiledFrameCount),
|
|
5902
|
+
`Create exactly ${compiledFrameCount} sequential video storyboard frames as one production storyboard sheet.`,
|
|
5802
5903
|
'',
|
|
5803
|
-
|
|
5904
|
+
'PROJECT:',
|
|
5905
|
+
`Title: ${project.title}.`,
|
|
5906
|
+
`Format: ${layout.targetVideoAspectRatio} video storyboard.`,
|
|
5907
|
+
project.durationSec !== null ? `Target duration: ${project.durationSec} seconds.` : 'Target duration: unspecified in source brief.',
|
|
5804
5908
|
'',
|
|
5805
|
-
'
|
|
5909
|
+
'LAYOUT CONTRACT:',
|
|
5910
|
+
`Create exactly ${compiledFrameCount} numbered storyboard panels; do not render fewer or more panels.`,
|
|
5911
|
+
`Arrange panels in reading order, left-to-right then top-to-bottom: ${Array.from({ length: compiledFrameCount }, (_, index) => {
|
|
5912
|
+
const scene = project.scenes[index];
|
|
5913
|
+
return `[${index + 1}] ${scene?.id.toUpperCase() ?? `SCENE_${String(index + 1).padStart(2, '0')}`}`;
|
|
5914
|
+
}).join(', ')}.`,
|
|
5806
5915
|
boardSizeLine,
|
|
5807
5916
|
`Individual scene-cell/frame aspect ratio: ${layout.cellAspectRatio}.`,
|
|
5808
5917
|
`Target final video aspect ratio: ${layout.targetVideoAspectRatio}.`,
|
|
5809
5918
|
`Layout preset: ${layout.layoutKind} - ${layout.layoutDescription}.`,
|
|
5810
|
-
`
|
|
5811
|
-
'Keep
|
|
5812
|
-
'
|
|
5919
|
+
`Each panel must contain one distinct ${layout.cellAspectRatio} cinematic video-frame rectangle with compact notes outside the frame.`,
|
|
5920
|
+
'Keep scene numbers, timecodes, titles, dialogue/VO, audio notes, and production notes outside the video-frame rectangles.',
|
|
5921
|
+
'Do not merge panels, create inset thumbnails, make panels square, or overlay storyboard metadata inside the artwork frames.',
|
|
5813
5922
|
'',
|
|
5814
|
-
...
|
|
5815
|
-
'',
|
|
5816
|
-
...compileStoryboardStoryContinuitySection(project),
|
|
5923
|
+
...compileStoryboardReferenceSection(project),
|
|
5817
5924
|
'',
|
|
5818
|
-
'
|
|
5925
|
+
'STYLE:',
|
|
5819
5926
|
`${project.creativeBrief.visualQualityBar} with cinematic shot language, coherent art direction, readable labels, and consistent reference usage.`,
|
|
5820
5927
|
'Reference-driven personality: before drawing, infer concrete visual and behavioral cues from uploaded/reference images, including character attitude, materials, props, palette, brand tone, typography style, and implied world. Let those cues make this storyboard specific to the supplied subject instead of a generic reusable template.',
|
|
5821
5928
|
'Vary composition within the required grid: keep the exact scene count, layout, and cell geometry, and make each panel intentionally staged with distinct shot scale, pose/action, camera angle, lighting beat, transition idea, and character-specific detail.',
|
|
5822
5929
|
'',
|
|
5930
|
+
...compileStoryboardStoryContinuitySection(project),
|
|
5931
|
+
'',
|
|
5823
5932
|
'CRITICAL REQUIREMENTS:',
|
|
5824
5933
|
...compileStoryboardCriticalRequirements().map((item, index) => `${index + 1}. ${item}`),
|
|
5825
5934
|
...project.references
|
|
@@ -5827,7 +5936,7 @@ function compileVideoStoryboardImagePrompt(options) {
|
|
|
5827
5936
|
.map((ref, index) => `${compileStoryboardCriticalRequirements().length + index + 1}. Critical reference lock: ${ref.id} (${ref.kind}) must remain bound to its assigned usage scope: ${ref.usageScope}.`),
|
|
5828
5937
|
'',
|
|
5829
5938
|
...compileStoryboardScenesSection(project),
|
|
5830
|
-
'TEXT
|
|
5939
|
+
'TEXT RULES:',
|
|
5831
5940
|
'Place scene number, timing, scene title, beat title, and compact production labels outside each video frame in a clearly associated header, footer strip, side rail, or table. Do not overlay scene numbers, timecodes, production notes, Dialogue/VO labels, Audio/SFX text, or SFX/action callout words such as Whoosh!, Impact!, Boom!, Thud!, Slash!, Crack!, or Pop! on top of the video-frame artwork. Also do not overlay scene/beat titles on top of the video-frame artwork. Project titles are metadata, not in-frame text. Only listed diegetic or brand text belongs inside a frame. Quote and spell any required visible text exactly.',
|
|
5832
5941
|
'Visible text listed on a scene belongs only in that scene; repeat visible text only on scenes that list it.',
|
|
5833
5942
|
...storyboardRequiredVisibleText(project).map(formatStoryboardRequiredVisibleTextLine),
|
|
@@ -5846,27 +5955,28 @@ function ensureCompiledVideoStoryboardPromptPreservesSourceBrief(compiledPrompt,
|
|
|
5846
5955
|
function lintStoryboardImagePrompt(prompt, layout, project) {
|
|
5847
5956
|
const errors = [];
|
|
5848
5957
|
const warnings = [];
|
|
5958
|
+
const hasConsolidatedLayoutContract = /LAYOUT CONTRACT:/i.test(prompt);
|
|
5849
5959
|
if (!/CREATE:/i.test(prompt))
|
|
5850
5960
|
errors.push('missing CREATE section');
|
|
5851
|
-
if (!/COUNT \/ GRID CONTRACT:/i.test(prompt))
|
|
5961
|
+
if (!hasConsolidatedLayoutContract && !/COUNT \/ GRID CONTRACT:/i.test(prompt))
|
|
5852
5962
|
errors.push('missing COUNT / GRID CONTRACT section');
|
|
5853
5963
|
if (!/REFERENCE IMAGES:/i.test(prompt))
|
|
5854
5964
|
errors.push('missing REFERENCE IMAGES section');
|
|
5855
|
-
if (!/CANVAS \/ LAYOUT:/i.test(prompt))
|
|
5965
|
+
if (!hasConsolidatedLayoutContract && !/CANVAS \/ LAYOUT:/i.test(prompt))
|
|
5856
5966
|
errors.push('missing CANVAS / LAYOUT section');
|
|
5857
|
-
if (!/FRAME GEOMETRY:/i.test(prompt))
|
|
5967
|
+
if (!hasConsolidatedLayoutContract && !/FRAME GEOMETRY:/i.test(prompt))
|
|
5858
5968
|
errors.push('missing FRAME GEOMETRY section');
|
|
5859
5969
|
const cellOrientation = parseAspectRatioOrientation(layout.cellAspectRatio);
|
|
5860
|
-
if (cellOrientation === 'portrait' && !/PORTRAIT FRAME GEOMETRY:/i.test(prompt)) {
|
|
5970
|
+
if (!hasConsolidatedLayoutContract && cellOrientation === 'portrait' && !/PORTRAIT FRAME GEOMETRY:/i.test(prompt)) {
|
|
5861
5971
|
errors.push('missing PORTRAIT FRAME GEOMETRY section');
|
|
5862
5972
|
}
|
|
5863
|
-
if (cellOrientation === 'landscape' && !/LANDSCAPE FRAME GEOMETRY:/i.test(prompt)) {
|
|
5973
|
+
if (!hasConsolidatedLayoutContract && cellOrientation === 'landscape' && !/LANDSCAPE FRAME GEOMETRY:/i.test(prompt)) {
|
|
5864
5974
|
errors.push('missing LANDSCAPE FRAME GEOMETRY section');
|
|
5865
5975
|
}
|
|
5866
|
-
if (cellOrientation === 'square' && !/SQUARE FRAME GEOMETRY:/i.test(prompt)) {
|
|
5976
|
+
if (!hasConsolidatedLayoutContract && cellOrientation === 'square' && !/SQUARE FRAME GEOMETRY:/i.test(prompt)) {
|
|
5867
5977
|
errors.push('missing SQUARE FRAME GEOMETRY section');
|
|
5868
5978
|
}
|
|
5869
|
-
if (!/TEXT RENDERING:/i.test(prompt))
|
|
5979
|
+
if (!/(?:TEXT RENDERING|TEXT RULES):/i.test(prompt))
|
|
5870
5980
|
errors.push('missing TEXT RENDERING section');
|
|
5871
5981
|
if (!prompt.includes(`Overall storyboard canvas aspect ratio: ${layout.boardAspectRatio}`)
|
|
5872
5982
|
&& !prompt.includes(`(${layout.boardAspectRatio})`)) {
|
|
@@ -5937,7 +6047,7 @@ function inferCompiledStoryboardPromptAspectRatio(prompt, labelPattern) {
|
|
|
5937
6047
|
return normalizeAspectRatio(match?.[1]);
|
|
5938
6048
|
}
|
|
5939
6049
|
function extractCompiledStoryboardScenesBlock(prompt) {
|
|
5940
|
-
const match = prompt.match(/\nSCENES:\s*\n([\s\S]*?)(?:\nTEXT RENDERING:|\nSOURCE BRIEF TO FOLLOW:|\nNEGATIVE \/ AVOID:|$)/i);
|
|
6050
|
+
const match = prompt.match(/\nSCENES:\s*\n([\s\S]*?)(?:\nTEXT RENDERING:|\nTEXT RULES:|\nSOURCE BRIEF TO FOLLOW:|\nNEGATIVE \/ AVOID:|$)/i);
|
|
5941
6051
|
return match?.[1] ?? '';
|
|
5942
6052
|
}
|
|
5943
6053
|
function parseCompiledStoryboardHeadingTiming(heading) {
|