@sogni-ai/sogni-intelligence-client 3.0.0-alpha.9 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/intentInput.js +1 -1
- package/dist/agent/intentInput.js.map +1 -1
- package/dist/contracts/data/promptContracts.d.ts.map +1 -1
- package/dist/contracts/data/promptContracts.js +19 -7
- package/dist/contracts/data/promptContracts.js.map +1 -1
- package/dist/public-skill-runtime/index.d.ts.map +1 -1
- package/dist/public-skill-runtime/index.js +212 -108
- package/dist/public-skill-runtime/index.js.map +1 -1
- package/dist/tools/definitions/animate-photo/definition.js +1 -1
- package/dist/tools/definitions/animate-photo/definition.js.map +1 -1
- package/dist/workflows/executor.d.ts +5 -3
- package/dist/workflows/executor.d.ts.map +1 -1
- package/dist/workflows/executor.js +82 -29
- package/dist/workflows/executor.js.map +1 -1
- package/dist/workflows/primitives/retryUntilCondition.d.ts.map +1 -1
- package/dist/workflows/primitives/retryUntilCondition.js.map +1 -1
- package/dist/workflows/types.d.ts +14 -0
- package/dist/workflows/types.d.ts.map +1 -1
- package/dist/workflows/validation.d.ts +4 -1
- package/dist/workflows/validation.d.ts.map +1 -1
- package/dist/workflows/validation.js +12 -3
- package/dist/workflows/validation.js.map +1 -1
- package/dist-esm/agent/intentInput.js +1 -1
- package/dist-esm/agent/intentInput.js.map +1 -1
- package/dist-esm/contracts/data/promptContracts.js +19 -7
- package/dist-esm/contracts/data/promptContracts.js.map +1 -1
- package/dist-esm/public-skill-runtime/index.js +212 -108
- package/dist-esm/public-skill-runtime/index.js.map +1 -1
- package/dist-esm/tools/definitions/animate-photo/definition.js +1 -1
- package/dist-esm/tools/definitions/animate-photo/definition.js.map +1 -1
- package/dist-esm/workflows/executor.js +82 -29
- package/dist-esm/workflows/executor.js.map +1 -1
- package/dist-esm/workflows/primitives/retryUntilCondition.js.map +1 -1
- package/dist-esm/workflows/validation.js +12 -3
- package/dist-esm/workflows/validation.js.map +1 -1
- package/package.json +2 -2
|
@@ -797,33 +797,48 @@ function compileStoryboardImagePromptFromProject(project) {
|
|
|
797
797
|
: `Overall storyboard canvas aspect ratio: ${layout.boardAspectRatio}.`;
|
|
798
798
|
return [
|
|
799
799
|
'CREATE:',
|
|
800
|
-
`Create exactly ${frameCount} sequential video storyboard frames as one
|
|
801
|
-
`Project title: ${project.title}.`,
|
|
802
|
-
project.durationSec !== null ? `Target duration: ${project.durationSec} seconds.` : 'Target duration: unspecified in source brief.',
|
|
803
|
-
'',
|
|
804
|
-
...compileStoryboardCountContractSection(project, layout),
|
|
800
|
+
`Create exactly ${frameCount} sequential video storyboard frames as one production storyboard sheet.`,
|
|
805
801
|
'',
|
|
806
|
-
|
|
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.',
|
|
807
806
|
'',
|
|
808
|
-
'
|
|
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(', ')}.`,
|
|
809
813
|
boardSizeLine,
|
|
810
814
|
`Individual scene-cell/frame aspect ratio: ${layout.cellAspectRatio}.`,
|
|
811
815
|
`Target final video aspect ratio: ${layout.targetVideoAspectRatio}.`,
|
|
812
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.',
|
|
813
820
|
'',
|
|
814
|
-
...
|
|
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.',
|
|
815
827
|
'',
|
|
816
|
-
|
|
817
|
-
project.creativeBrief.visualQualityBar,
|
|
828
|
+
...compileStoryboardStoryContinuitySection(project),
|
|
818
829
|
'',
|
|
819
830
|
'CRITICAL REQUIREMENTS:',
|
|
820
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}.`),
|
|
821
835
|
'',
|
|
822
836
|
...compileStoryboardScenesSection(project),
|
|
823
|
-
'TEXT
|
|
824
|
-
'
|
|
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.',
|
|
825
839
|
'Visible text listed on a scene belongs only in that scene; repeat visible text only on scenes that list it.',
|
|
826
840
|
...storyboardRequiredVisibleText(project).map(formatStoryboardRequiredVisibleTextLine),
|
|
841
|
+
project.endCard.logoUsage ? `Logo usage: ${project.endCard.logoUsage}` : '',
|
|
827
842
|
'',
|
|
828
843
|
'NEGATIVE / AVOID:',
|
|
829
844
|
...compileStoryboardAvoidSection(project.creativeBrief.concept).map(item => `- ${item}`),
|
|
@@ -1660,7 +1675,7 @@ function suggestedDurationForDialogue(prompt, currentDuration) {
|
|
|
1660
1675
|
const baseDuration = currentDuration ?? 0;
|
|
1661
1676
|
if (words <= 0)
|
|
1662
1677
|
return baseDuration;
|
|
1663
|
-
const speechSeconds = Math.ceil(words /
|
|
1678
|
+
const speechSeconds = Math.ceil(words / 3) + 2;
|
|
1664
1679
|
return Math.max(baseDuration, Math.min(20, speechSeconds));
|
|
1665
1680
|
}
|
|
1666
1681
|
function formatAudioIdPrompt(prompt, voiceName) {
|
|
@@ -2758,8 +2773,8 @@ const STORYBOARD_COUNT_WORDS = {
|
|
|
2758
2773
|
};
|
|
2759
2774
|
const DEFAULT_STORYBOARD_TIMING_RULES = {
|
|
2760
2775
|
normalWordsPerSecondMin: 2.0,
|
|
2761
|
-
normalWordsPerSecondMax: 3.
|
|
2762
|
-
fastWordsPerSecondMax:
|
|
2776
|
+
normalWordsPerSecondMax: 3.0,
|
|
2777
|
+
fastWordsPerSecondMax: 3.75,
|
|
2763
2778
|
minEndCardHoldSec: 2.0,
|
|
2764
2779
|
minPunchlineSec: 0.5,
|
|
2765
2780
|
toleranceSec: 0.25,
|
|
@@ -2952,10 +2967,13 @@ function describePortraitLetterboxCellArrangement(frameCount, cellAspectRatio) {
|
|
|
2952
2967
|
}
|
|
2953
2968
|
const columns = frameCount <= 8 ? 2 : frameCount <= 15 ? 3 : 4;
|
|
2954
2969
|
const rows = Math.ceil(frameCount / columns);
|
|
2970
|
+
const unusedSlotNote = columns * rows > frameCount
|
|
2971
|
+
? ['use unused grid slots as margin/notes space only']
|
|
2972
|
+
: [];
|
|
2955
2973
|
return [
|
|
2956
2974
|
`${frameCount} numbered scene slots arranged as a ${columns}-column x ${rows}-row grid inside a portrait sheet`,
|
|
2957
2975
|
`each slot contains one ${frameShape} with compact labels outside the rectangle`,
|
|
2958
|
-
|
|
2976
|
+
...unusedSlotNote,
|
|
2959
2977
|
].join('; ');
|
|
2960
2978
|
}
|
|
2961
2979
|
function describeLandscapePortraitCellArrangement(frameCount, cellAspectRatio) {
|
|
@@ -2965,10 +2983,13 @@ function describeLandscapePortraitCellArrangement(frameCount, cellAspectRatio) {
|
|
|
2965
2983
|
}
|
|
2966
2984
|
const rows = frameCount <= 8 ? 2 : frameCount <= 15 ? 3 : 4;
|
|
2967
2985
|
const columns = Math.ceil(frameCount / rows);
|
|
2986
|
+
const unusedSlotNote = columns * rows > frameCount
|
|
2987
|
+
? ['use unused grid slots as margin/notes space only']
|
|
2988
|
+
: [];
|
|
2968
2989
|
return [
|
|
2969
2990
|
`${frameCount} numbered scene slots arranged as a ${rows}-row x ${columns}-column grid inside a landscape board`,
|
|
2970
2991
|
`each slot contains one ${frameShape} with compact labels outside the rectangle`,
|
|
2971
|
-
|
|
2992
|
+
...unusedSlotNote,
|
|
2972
2993
|
].join('; ');
|
|
2973
2994
|
}
|
|
2974
2995
|
function parseAspectRatioPair(aspectRatio) {
|
|
@@ -3083,13 +3104,16 @@ function describeSingleOrientationStoryboardArrangement(boardAspectRatio, cellAs
|
|
|
3083
3104
|
const slotGrid = pickBalancedSlotGrid(frameCount);
|
|
3084
3105
|
const cols = cellOrientation === 'landscape' ? slotGrid.minor : slotGrid.major;
|
|
3085
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
|
+
: [];
|
|
3086
3110
|
if (boardOrientation === 'portrait') {
|
|
3087
3111
|
return {
|
|
3088
3112
|
layoutKind: 'portrait_grid',
|
|
3089
3113
|
layoutDescription: [
|
|
3090
3114
|
`${frameCount} numbered scene slots arranged as a ${cols}-column x ${rows}-row grid inside a portrait storyboard sheet`,
|
|
3091
3115
|
`each slot contains one ${frameShape} with compact labels outside the rectangle`,
|
|
3092
|
-
|
|
3116
|
+
...unusedSlotNote,
|
|
3093
3117
|
].join('; '),
|
|
3094
3118
|
};
|
|
3095
3119
|
}
|
|
@@ -3099,7 +3123,7 @@ function describeSingleOrientationStoryboardArrangement(boardAspectRatio, cellAs
|
|
|
3099
3123
|
layoutDescription: [
|
|
3100
3124
|
`${frameCount} numbered scene slots arranged as a ${rows}-row x ${cols}-column grid inside a ${boardLabel}`,
|
|
3101
3125
|
`each slot contains one ${frameShape} with compact labels outside the rectangle`,
|
|
3102
|
-
|
|
3126
|
+
...unusedSlotNote,
|
|
3103
3127
|
].join('; '),
|
|
3104
3128
|
};
|
|
3105
3129
|
}
|
|
@@ -3483,6 +3507,16 @@ function cleanStoryboardReferenceSubjectHint(context, index) {
|
|
|
3483
3507
|
const explicitSubject = cleanExplicitStoryboardReferenceSubject(source, index);
|
|
3484
3508
|
if (explicitSubject)
|
|
3485
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
|
+
}
|
|
3486
3520
|
const cleaned = source
|
|
3487
3521
|
.replace(new RegExp(String.raw `^(?:image|photo|picture|asset)\s*(?:#|number\s*)?${index}\s*(?:\([^)]*\))?\s*:?\s*`, 'i'), '')
|
|
3488
3522
|
.replace(new RegExp(String.raw `^(?:uploaded|attached|provided|reference|source|input)\s+(?:image|photo|picture|asset)\s*(?:#|number\s*)?${index}\s*:?\s*`, 'i'), '')
|
|
@@ -3590,6 +3624,51 @@ function buildStoryboardReferenceAssets(userIntentText, prompt) {
|
|
|
3590
3624
|
preservePriority: storyboardPreservePriorityFromRole(role),
|
|
3591
3625
|
}));
|
|
3592
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
|
+
}
|
|
3593
3672
|
function compileStoryboardReferenceSection(project) {
|
|
3594
3673
|
const refs = project.references;
|
|
3595
3674
|
if (refs.length <= 0) {
|
|
@@ -3637,6 +3716,31 @@ function extractStoryboardRequiredText(text) {
|
|
|
3637
3716
|
const visibleTextContextPattern = /\b(?:visible|on[-\s]?screen|in[-\s]?frame|text|copy|cta|tagline|headline|title\s+card|caption|subtitle|super|wordmark|spell(?:ed)?|read(?:s)?|slogan)\b/i;
|
|
3638
3717
|
const sceneHeadingPattern = /^\s*(?:[-*+]\s*)?(?:#{1,6}\s*)?(?:[*_]{1,3})?\s*(?:Scene|Shot|Beat|Panel|Frame)[\s_#-]*\d{1,2}\b/i;
|
|
3639
3718
|
const visibleTextRestrictionPattern = /\b(?:only\s+(?:text|copy|words?)|no\s+(?:captions?|subtitles?|overlays?|watermarks?|added\s+logos?|extra\s+logos?|logos?|text|copy)|without\s+(?:captions?|subtitles?|overlays?|watermarks?|added\s+logos?|extra\s+logos?|logos?|text|copy)|do\s+not\s+(?:add|include|render|show|write)\s+(?:captions?|subtitles?|overlays?|watermarks?|logos?|text|copy|words?))\b/i;
|
|
3719
|
+
const storyboardTextCandidateLooksLikeConversationInstruction = (candidate) => {
|
|
3720
|
+
const raw = compactStoryboardLine(stripStoryboardMarkup(candidate))
|
|
3721
|
+
.replace(/^["“”'`]+|["“”'`]+$/g, '')
|
|
3722
|
+
.trim();
|
|
3723
|
+
if (!raw)
|
|
3724
|
+
return false;
|
|
3725
|
+
if (/^\[(?:uploaded|attached|provided)\s+(?:images?|files?|videos?|audio)\s*:/i.test(raw))
|
|
3726
|
+
return true;
|
|
3727
|
+
if (/^(?:yes|no|approved?|looks?\s+good|ok(?:ay)?|sure)[.!?]?$/i.test(raw))
|
|
3728
|
+
return true;
|
|
3729
|
+
const value = raw.toLowerCase();
|
|
3730
|
+
const workflowTerm = /\b(?:script|prompt|story\s*board|storyboard|take|version|again|redo|retry|regenerate|re-generate|revise|revision|approve|approval|proceed|generate|render|run|do\s+it|best\s+judg(?:e)?ment)\b/i;
|
|
3731
|
+
if (/^use\s+your\s+best\s+judg(?:e)?ment\b/i.test(raw))
|
|
3732
|
+
return true;
|
|
3733
|
+
if (/^(?:try\s+another|another\s+take|regenerate|re-generate|redo|retry|revise)\b/i.test(raw))
|
|
3734
|
+
return true;
|
|
3735
|
+
if (/^(?:go\s+ahead|can\s+you|please)\b/i.test(raw) && workflowTerm.test(value))
|
|
3736
|
+
return true;
|
|
3737
|
+
if (/^let'?s\b|^lets\b/i.test(raw)) {
|
|
3738
|
+
return /\b(?:script|prompt|story\s*board|storyboard|take|version|again|redo|retry|regenerate|re-generate|revise|revision)\b/i.test(value)
|
|
3739
|
+
|| /\b(?:generate|render|create|do|run)\s+it\b/i.test(value)
|
|
3740
|
+
|| /\buse\s+your\s+best\s+judg(?:e)?ment\b/i.test(value);
|
|
3741
|
+
}
|
|
3742
|
+
return false;
|
|
3743
|
+
};
|
|
3640
3744
|
const shouldIgnoreRequiredText = (value, matchIndex, precedingText) => {
|
|
3641
3745
|
const lineStart = text.lastIndexOf('\n', matchIndex) + 1;
|
|
3642
3746
|
const nextLineBreak = text.indexOf('\n', matchIndex);
|
|
@@ -3672,6 +3776,9 @@ function extractStoryboardRequiredText(text) {
|
|
|
3672
3776
|
if (storyboardTextCandidateLooksLikeGenericProductionLabel(value)) {
|
|
3673
3777
|
return true;
|
|
3674
3778
|
}
|
|
3779
|
+
if (storyboardTextCandidateLooksLikeConversationInstruction(value)) {
|
|
3780
|
+
return true;
|
|
3781
|
+
}
|
|
3675
3782
|
if (isNestedSceneHeadingField) {
|
|
3676
3783
|
return true;
|
|
3677
3784
|
}
|
|
@@ -3746,6 +3853,8 @@ function extractStoryboardRequiredText(text) {
|
|
|
3746
3853
|
return false;
|
|
3747
3854
|
if (storyboardTextCandidateLooksLikeGenericProductionLabel(value))
|
|
3748
3855
|
return false;
|
|
3856
|
+
if (storyboardTextCandidateLooksLikeConversationInstruction(value))
|
|
3857
|
+
return false;
|
|
3749
3858
|
const contextNamesText = /\b(?:slogans?|taglines?|cta|copy|text|words?|wordmark|brand|logo|end\s+card|final\s+card|end\s+scene|final\s+scene|closing\s+card)\b/i
|
|
3750
3859
|
.test(context);
|
|
3751
3860
|
if (!contextNamesText)
|
|
@@ -3762,7 +3871,10 @@ function extractStoryboardRequiredText(text) {
|
|
|
3762
3871
|
|| /^(?:powered|made|built|created|generated|rendered|presented)\s+by\b/i.test(value);
|
|
3763
3872
|
return !productionDirection || copyOverridesProduction;
|
|
3764
3873
|
};
|
|
3765
|
-
const
|
|
3874
|
+
const endCardFollowingLineLooksLikeContinuationInstruction = (line) => {
|
|
3875
|
+
return storyboardTextCandidateLooksLikeConversationInstruction(line);
|
|
3876
|
+
};
|
|
3877
|
+
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;
|
|
3766
3878
|
for (const match of text.matchAll(endCardLabelPattern)) {
|
|
3767
3879
|
const matchIndex = match.index ?? 0;
|
|
3768
3880
|
const lineEnd = text.indexOf('\n', matchIndex);
|
|
@@ -3774,6 +3886,8 @@ function extractStoryboardRequiredText(text) {
|
|
|
3774
3886
|
break;
|
|
3775
3887
|
if (/^(?:#{1,6}\s+|\|\s*|(?:Scene|Shot|Beat|Panel|Frame)\s*\d{1,2}\b)/i.test(trimmed))
|
|
3776
3888
|
break;
|
|
3889
|
+
if (endCardFollowingLineLooksLikeContinuationInstruction(trimmed))
|
|
3890
|
+
break;
|
|
3777
3891
|
if (followingLines.length >= 5)
|
|
3778
3892
|
break;
|
|
3779
3893
|
followingLines.push(trimmed);
|
|
@@ -5088,10 +5202,10 @@ function alignAssistantStoryboardDialogueWithUserSource(scenes, userIntentText,
|
|
|
5088
5202
|
return { scenes, shouldRetime: false };
|
|
5089
5203
|
return { scenes: repaired, shouldRetime: true };
|
|
5090
5204
|
}
|
|
5091
|
-
function minimumStoryboardSceneDurationForDialogue(scene, rules) {
|
|
5205
|
+
function minimumStoryboardSceneDurationForDialogue(scene, rules, wordsPerSecondMax = rules.normalWordsPerSecondMax) {
|
|
5092
5206
|
const dialogueWords = countWords(scene.dialogue);
|
|
5093
5207
|
let minDuration = dialogueWords > 0
|
|
5094
|
-
? dialogueWords /
|
|
5208
|
+
? dialogueWords / wordsPerSecondMax
|
|
5095
5209
|
: 0.5;
|
|
5096
5210
|
const sceneContext = `${scene.title}\n${scene.visual}\n${scene.textInImage.join(' ')}`;
|
|
5097
5211
|
const hasReadableText = scene.textInImage.length > 0;
|
|
@@ -5114,13 +5228,22 @@ function retimeStoryboardScenesForDialogue(scenes, targetDurationSec, rules = DE
|
|
|
5114
5228
|
const needsRetiming = scenes.some((scene, index) => (scene.durationSec ?? 0) + rules.toleranceSec < minimumDurations[index]);
|
|
5115
5229
|
if (!needsRetiming)
|
|
5116
5230
|
return scenes;
|
|
5117
|
-
|
|
5231
|
+
let requiredDurations = minimumDurations;
|
|
5232
|
+
let minimumTotal = Math.round(requiredDurations.reduce((sum, duration) => sum + duration, 0) * 100) / 100;
|
|
5233
|
+
if (minimumTotal > targetDurationSec + rules.toleranceSec && rules.fastWordsPerSecondMax > rules.normalWordsPerSecondMax) {
|
|
5234
|
+
const fastDurations = scenes.map(scene => minimumStoryboardSceneDurationForDialogue(scene, rules, rules.fastWordsPerSecondMax));
|
|
5235
|
+
const fastTotal = Math.round(fastDurations.reduce((sum, duration) => sum + duration, 0) * 100) / 100;
|
|
5236
|
+
if (fastTotal <= targetDurationSec + rules.toleranceSec) {
|
|
5237
|
+
requiredDurations = fastDurations;
|
|
5238
|
+
minimumTotal = fastTotal;
|
|
5239
|
+
}
|
|
5240
|
+
}
|
|
5118
5241
|
if (minimumTotal > targetDurationSec + rules.toleranceSec)
|
|
5119
5242
|
return scenes;
|
|
5120
5243
|
const extraDuration = Math.max(0, targetDurationSec - minimumTotal);
|
|
5121
5244
|
const originalDurations = scenes.map(scene => scene.durationSec ?? 0);
|
|
5122
5245
|
const weightTotal = originalDurations.reduce((sum, duration) => sum + Math.max(0.01, duration), 0);
|
|
5123
|
-
const unroundedDurations =
|
|
5246
|
+
const unroundedDurations = requiredDurations.map((minimum, index) => (minimum + (extraDuration * Math.max(0.01, originalDurations[index]) / weightTotal)));
|
|
5124
5247
|
const timelineStartSec = scenes[0].startSec ?? 0;
|
|
5125
5248
|
const timelineEndSec = Math.round((timelineStartSec + targetDurationSec) * 100) / 100;
|
|
5126
5249
|
let cursor = timelineStartSec;
|
|
@@ -5292,6 +5415,27 @@ function finalStoryboardSceneVisibleText(scenes) {
|
|
|
5292
5415
|
}
|
|
5293
5416
|
return [];
|
|
5294
5417
|
}
|
|
5418
|
+
function applyStoryboardEndCardTextToScenes(scenes, endCardText) {
|
|
5419
|
+
if (scenes.length === 0 || endCardText.length === 0)
|
|
5420
|
+
return scenes;
|
|
5421
|
+
const finalSceneIndex = scenes.length - 1;
|
|
5422
|
+
const explicitEndCardIndex = (() => {
|
|
5423
|
+
for (let index = scenes.length - 1; index >= 0; index -= 1) {
|
|
5424
|
+
if (storyboardSceneLooksLikeEndCard(scenes[index]))
|
|
5425
|
+
return index;
|
|
5426
|
+
}
|
|
5427
|
+
return finalSceneIndex;
|
|
5428
|
+
})();
|
|
5429
|
+
return scenes.map((scene, index) => index === explicitEndCardIndex
|
|
5430
|
+
? {
|
|
5431
|
+
...scene,
|
|
5432
|
+
textInImage: uniqueStoryboardStrings([
|
|
5433
|
+
...scene.textInImage,
|
|
5434
|
+
...endCardText,
|
|
5435
|
+
]),
|
|
5436
|
+
}
|
|
5437
|
+
: scene);
|
|
5438
|
+
}
|
|
5295
5439
|
function storyboardRequiredTextKey(value) {
|
|
5296
5440
|
return compactStoryboardLine(value)
|
|
5297
5441
|
.toLowerCase()
|
|
@@ -5342,6 +5486,7 @@ function buildStoryboardProject(options) {
|
|
|
5342
5486
|
const userIntentText = canonicalStoryboardScriptContext(rawUserIntentText) || rawUserIntentText;
|
|
5343
5487
|
const narrativeUserIntentText = cleanStoryboardNarrativeSourceText(userIntentText);
|
|
5344
5488
|
const approvedScriptContext = cleanStoryboardNarrativeSourceText(canonicalStoryboardScriptContext(options.approvedScriptContext));
|
|
5489
|
+
const referencePromptContext = cleanStoryboardNarrativeSourceText(canonicalStoryboardScriptContext(prompt) || prompt);
|
|
5345
5490
|
const primarySourceBrief = selectStoryboardSourceBriefForCompile(options, userIntentText);
|
|
5346
5491
|
const sourceText = stripGenericStoryboardVisibleTextMetadata(sanitizeStoryboardExternalAudioReferences([
|
|
5347
5492
|
cleanStoryboardNarrativeSourceText(primarySourceBrief),
|
|
@@ -5365,7 +5510,7 @@ function buildStoryboardProject(options) {
|
|
|
5365
5510
|
const requestedDurationSec = inferRequestedTotalVideoDurationSeconds(userIntentText);
|
|
5366
5511
|
const durationSec = requestedDurationSec ?? inferRequestedTotalVideoDurationSeconds(sourceText);
|
|
5367
5512
|
const references = buildStoryboardReferenceAssets(userIntentText, [
|
|
5368
|
-
|
|
5513
|
+
referencePromptContext,
|
|
5369
5514
|
approvedScriptContext,
|
|
5370
5515
|
].filter(Boolean).join('\n\n'));
|
|
5371
5516
|
const approvedSections = approvedScriptContext
|
|
@@ -5396,21 +5541,25 @@ function buildStoryboardProject(options) {
|
|
|
5396
5541
|
return buildSceneFromSection(section, references, null, storyboardScenePlanningContractForIndex(options.planningContract, section.number));
|
|
5397
5542
|
})
|
|
5398
5543
|
: [];
|
|
5399
|
-
const scenes = parsedScenes
|
|
5544
|
+
const scenes = parsedScenes.map(scene => ({
|
|
5545
|
+
...scene,
|
|
5546
|
+
referenceUsage: normalizeStoryboardSceneReferenceUsage(scene.referenceUsage, references),
|
|
5547
|
+
}));
|
|
5400
5548
|
const timingNormalizedScenes = normalizeAssistantStoryboardSceneTiming(scenes, durationSec, options.promptAuthorship);
|
|
5401
5549
|
const dialogueAlignment = alignAssistantStoryboardDialogueWithUserSource(timingNormalizedScenes, userIntentText, options.promptAuthorship);
|
|
5402
|
-
const
|
|
5550
|
+
const dialogueTimedScenes = (options.promptAuthorship === 'assistant' || dialogueAlignment.shouldRetime)
|
|
5403
5551
|
? retimeStoryboardScenesForDialogue(dialogueAlignment.scenes, durationSec)
|
|
5404
5552
|
: dialogueAlignment.scenes;
|
|
5405
5553
|
const userConstraintSource = buildStoryboardUserConstraintSource(narrativeUserIntentText, cleanStoryboardNarrativeSourceText(primarySourceBrief), options);
|
|
5406
|
-
const { mustIncludeText, endCardText } = storyboardRequiredTextForProject(options, userConstraintSource,
|
|
5554
|
+
const { mustIncludeText, endCardText } = storyboardRequiredTextForProject(options, userConstraintSource, dialogueTimedScenes);
|
|
5555
|
+
const normalizedScenes = retimeStoryboardScenesForDialogue(applyStoryboardEndCardTextToScenes(dialogueTimedScenes, endCardText), durationSec);
|
|
5407
5556
|
const voiceLines = assignVoiceLinesToScenes(normalizedScenes, sourceText);
|
|
5408
5557
|
const storySpineFallback = approvedScriptContext
|
|
5409
5558
|
|| (options.promptAuthorship === 'assistant' ? narrativeUserIntentText : cleanStoryboardNarrativeSourceText(primarySourceBrief))
|
|
5410
5559
|
|| prompt
|
|
5411
5560
|
|| narrativeUserIntentText;
|
|
5412
5561
|
const storySpine = inferStoryboardStorySpine(allText, storySpineFallback);
|
|
5413
|
-
const productFeatureMap = inferStoryboardProductFeatureMap(allText,
|
|
5562
|
+
const productFeatureMap = inferStoryboardProductFeatureMap(allText, normalizedScenes);
|
|
5414
5563
|
const beatMarkerScripts = [approvedScriptContext, sourceText];
|
|
5415
5564
|
let directBeatMarkerCount = 0;
|
|
5416
5565
|
for (const text of beatMarkerScripts) {
|
|
@@ -5640,67 +5789,17 @@ function compileStoryboardStoryContinuitySection(project) {
|
|
|
5640
5789
|
'Keep product/feature/brand meaning in the associated notes. Do not force long product explanations or the whole script into tiny in-frame text.',
|
|
5641
5790
|
];
|
|
5642
5791
|
}
|
|
5643
|
-
function compileStoryboardCountContractSection(project, layout, expectedFrameCount = project.scenes.length) {
|
|
5644
|
-
if (expectedFrameCount <= 0)
|
|
5645
|
-
return [];
|
|
5646
|
-
const sceneSlots = Array.from({ length: expectedFrameCount }, (_, index) => {
|
|
5647
|
-
const scene = project.scenes[index];
|
|
5648
|
-
return `[${index + 1}] ${scene?.id.toUpperCase() ?? `SCENE_${String(index + 1).padStart(2, '0')}`}`;
|
|
5649
|
-
})
|
|
5650
|
-
.join(', ');
|
|
5651
|
-
return [
|
|
5652
|
-
'COUNT / GRID CONTRACT:',
|
|
5653
|
-
`Required scene count: exactly ${expectedFrameCount} numbered storyboard scene slots; do not render fewer slots and do not add extra scene slots.`,
|
|
5654
|
-
`Allocate the full ${expectedFrameCount}-slot storyboard grid before drawing details. Fill slots in reading order, left-to-right then top-to-bottom: ${sceneSlots}.`,
|
|
5655
|
-
`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.`,
|
|
5656
|
-
`Layout preset: ${layout.layoutKind} - ${layout.layoutDescription}. The final sheet should be visibly countable as ${expectedFrameCount} numbered scene slots at a glance.`,
|
|
5657
|
-
];
|
|
5658
|
-
}
|
|
5659
|
-
function compileStoryboardFrameGeometrySection(layout) {
|
|
5660
|
-
const cellOrientation = parseAspectRatioOrientation(layout.cellAspectRatio);
|
|
5661
|
-
if (cellOrientation === 'portrait') {
|
|
5662
|
-
return [
|
|
5663
|
-
'PORTRAIT FRAME GEOMETRY:',
|
|
5664
|
-
'Treat each numbered scene slot as a container, not as the artwork shape.',
|
|
5665
|
-
`Inside every numbered scene slot, draw one identical upright ${layout.cellAspectRatio} video-frame rectangle whose height is visibly greater than its width.`,
|
|
5666
|
-
`Keep all scene numbers, timing, titles, notes, headers, footers, and production labels outside the ${layout.cellAspectRatio} video-frame rectangles.`,
|
|
5667
|
-
`Do not make the numbered scene slots, frame artwork areas, or thumbnails square. Square cells violate the requested ${layout.cellAspectRatio} final video format.`,
|
|
5668
|
-
'Unused grid slots must remain blank margin/notes space only; do not stretch or square off the portrait frame rectangles to fill the grid.',
|
|
5669
|
-
];
|
|
5670
|
-
}
|
|
5671
|
-
if (cellOrientation === 'landscape') {
|
|
5672
|
-
return [
|
|
5673
|
-
'LANDSCAPE FRAME GEOMETRY:',
|
|
5674
|
-
'Treat each numbered scene slot as a container, not as the artwork shape.',
|
|
5675
|
-
`Inside every numbered scene slot, draw one identical wide ${layout.cellAspectRatio} video-frame rectangle whose width is visibly greater than its height.`,
|
|
5676
|
-
`Keep all scene numbers, timing, titles, notes, headers, footers, and production labels outside the ${layout.cellAspectRatio} video-frame rectangles.`,
|
|
5677
|
-
`Do not make the numbered scene slots, frame artwork areas, or thumbnails square. Square cells violate the requested ${layout.cellAspectRatio} final video format.`,
|
|
5678
|
-
'Unused grid slots must remain blank margin/notes space only; do not stretch or square off the landscape frame rectangles to fill the grid.',
|
|
5679
|
-
];
|
|
5680
|
-
}
|
|
5681
|
-
if (cellOrientation === 'square') {
|
|
5682
|
-
return [
|
|
5683
|
-
'SQUARE FRAME GEOMETRY:',
|
|
5684
|
-
'Treat each numbered scene slot as a container, not as the artwork shape.',
|
|
5685
|
-
`Inside every numbered scene slot, draw one identical square ${layout.cellAspectRatio} video-frame area plus compact notes outside that square.`,
|
|
5686
|
-
`Do not stretch square frame artwork areas into portrait, landscape, full-board, or any aspect ratio other than ${layout.cellAspectRatio}.`,
|
|
5687
|
-
];
|
|
5688
|
-
}
|
|
5689
|
-
return [
|
|
5690
|
-
'FRAME GEOMETRY:',
|
|
5691
|
-
`Inside every numbered scene slot, draw one identical ${layout.cellAspectRatio} video-frame rectangle plus compact notes outside that rectangle.`,
|
|
5692
|
-
`Do not let frame artwork areas drift to square, full-board, or any aspect ratio other than ${layout.cellAspectRatio}.`,
|
|
5693
|
-
];
|
|
5694
|
-
}
|
|
5695
5792
|
function promptReferenceUsageForScene(referenceUsage, references, modelId) {
|
|
5696
5793
|
if (referenceUsage.length === 0)
|
|
5697
5794
|
return '';
|
|
5698
5795
|
const referenceById = new Map(references.map((ref, index) => [ref.id, { ref, index }]));
|
|
5699
|
-
return uniqueStoryboardStrings(referenceUsage.
|
|
5796
|
+
return uniqueStoryboardStrings(referenceUsage.flatMap(value => {
|
|
5700
5797
|
const found = referenceById.get(value);
|
|
5701
|
-
if (
|
|
5702
|
-
return
|
|
5703
|
-
|
|
5798
|
+
if (found)
|
|
5799
|
+
return [formatModelRef(modelId, found.ref.index ?? found.index + 1, 'image')];
|
|
5800
|
+
if (looksLikeUnknownIndexedStoryboardReference(value))
|
|
5801
|
+
return [];
|
|
5802
|
+
return [value];
|
|
5704
5803
|
})).join(', ');
|
|
5705
5804
|
}
|
|
5706
5805
|
function storyboardMetadataLabelAliases(labels) {
|
|
@@ -5835,32 +5934,36 @@ function compileVideoStoryboardImagePrompt(options) {
|
|
|
5835
5934
|
: `Overall storyboard canvas aspect ratio: ${layout.boardAspectRatio}.`;
|
|
5836
5935
|
return [
|
|
5837
5936
|
'CREATE:',
|
|
5838
|
-
`Create exactly ${compiledFrameCount} sequential video storyboard frames as one
|
|
5839
|
-
`Project title: ${project.title}.`,
|
|
5840
|
-
project.durationSec !== null ? `Target duration: ${project.durationSec} seconds.` : 'Target duration: unspecified in source brief.',
|
|
5841
|
-
'',
|
|
5842
|
-
...compileStoryboardCountContractSection(project, layout, compiledFrameCount),
|
|
5937
|
+
`Create exactly ${compiledFrameCount} sequential video storyboard frames as one production storyboard sheet.`,
|
|
5843
5938
|
'',
|
|
5844
|
-
|
|
5939
|
+
'PROJECT:',
|
|
5940
|
+
`Title: ${project.title}.`,
|
|
5941
|
+
`Format: ${layout.targetVideoAspectRatio} video storyboard.`,
|
|
5942
|
+
project.durationSec !== null ? `Target duration: ${project.durationSec} seconds.` : 'Target duration: unspecified in source brief.',
|
|
5845
5943
|
'',
|
|
5846
|
-
'
|
|
5944
|
+
'LAYOUT CONTRACT:',
|
|
5945
|
+
`Create exactly ${compiledFrameCount} numbered storyboard panels; do not render fewer or more panels.`,
|
|
5946
|
+
`Arrange panels in reading order, left-to-right then top-to-bottom: ${Array.from({ length: compiledFrameCount }, (_, index) => {
|
|
5947
|
+
const scene = project.scenes[index];
|
|
5948
|
+
return `[${index + 1}] ${scene?.id.toUpperCase() ?? `SCENE_${String(index + 1).padStart(2, '0')}`}`;
|
|
5949
|
+
}).join(', ')}.`,
|
|
5847
5950
|
boardSizeLine,
|
|
5848
5951
|
`Individual scene-cell/frame aspect ratio: ${layout.cellAspectRatio}.`,
|
|
5849
5952
|
`Target final video aspect ratio: ${layout.targetVideoAspectRatio}.`,
|
|
5850
5953
|
`Layout preset: ${layout.layoutKind} - ${layout.layoutDescription}.`,
|
|
5851
|
-
`
|
|
5852
|
-
'Keep
|
|
5853
|
-
'
|
|
5954
|
+
`Each panel must contain one distinct ${layout.cellAspectRatio} cinematic video-frame rectangle with compact notes outside the frame.`,
|
|
5955
|
+
'Keep scene numbers, timecodes, titles, dialogue/VO, audio notes, and production notes outside the video-frame rectangles.',
|
|
5956
|
+
'Do not merge panels, create inset thumbnails, make panels square, or overlay storyboard metadata inside the artwork frames.',
|
|
5854
5957
|
'',
|
|
5855
|
-
...
|
|
5856
|
-
'',
|
|
5857
|
-
...compileStoryboardStoryContinuitySection(project),
|
|
5958
|
+
...compileStoryboardReferenceSection(project),
|
|
5858
5959
|
'',
|
|
5859
|
-
'
|
|
5960
|
+
'STYLE:',
|
|
5860
5961
|
`${project.creativeBrief.visualQualityBar} with cinematic shot language, coherent art direction, readable labels, and consistent reference usage.`,
|
|
5861
5962
|
'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.',
|
|
5862
5963
|
'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.',
|
|
5863
5964
|
'',
|
|
5965
|
+
...compileStoryboardStoryContinuitySection(project),
|
|
5966
|
+
'',
|
|
5864
5967
|
'CRITICAL REQUIREMENTS:',
|
|
5865
5968
|
...compileStoryboardCriticalRequirements().map((item, index) => `${index + 1}. ${item}`),
|
|
5866
5969
|
...project.references
|
|
@@ -5868,7 +5971,7 @@ function compileVideoStoryboardImagePrompt(options) {
|
|
|
5868
5971
|
.map((ref, index) => `${compileStoryboardCriticalRequirements().length + index + 1}. Critical reference lock: ${ref.id} (${ref.kind}) must remain bound to its assigned usage scope: ${ref.usageScope}.`),
|
|
5869
5972
|
'',
|
|
5870
5973
|
...compileStoryboardScenesSection(project),
|
|
5871
|
-
'TEXT
|
|
5974
|
+
'TEXT RULES:',
|
|
5872
5975
|
'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.',
|
|
5873
5976
|
'Visible text listed on a scene belongs only in that scene; repeat visible text only on scenes that list it.',
|
|
5874
5977
|
...storyboardRequiredVisibleText(project).map(formatStoryboardRequiredVisibleTextLine),
|
|
@@ -5887,27 +5990,28 @@ function ensureCompiledVideoStoryboardPromptPreservesSourceBrief(compiledPrompt,
|
|
|
5887
5990
|
function lintStoryboardImagePrompt(prompt, layout, project) {
|
|
5888
5991
|
const errors = [];
|
|
5889
5992
|
const warnings = [];
|
|
5993
|
+
const hasConsolidatedLayoutContract = /LAYOUT CONTRACT:/i.test(prompt);
|
|
5890
5994
|
if (!/CREATE:/i.test(prompt))
|
|
5891
5995
|
errors.push('missing CREATE section');
|
|
5892
|
-
if (!/COUNT \/ GRID CONTRACT:/i.test(prompt))
|
|
5996
|
+
if (!hasConsolidatedLayoutContract && !/COUNT \/ GRID CONTRACT:/i.test(prompt))
|
|
5893
5997
|
errors.push('missing COUNT / GRID CONTRACT section');
|
|
5894
5998
|
if (!/REFERENCE IMAGES:/i.test(prompt))
|
|
5895
5999
|
errors.push('missing REFERENCE IMAGES section');
|
|
5896
|
-
if (!/CANVAS \/ LAYOUT:/i.test(prompt))
|
|
6000
|
+
if (!hasConsolidatedLayoutContract && !/CANVAS \/ LAYOUT:/i.test(prompt))
|
|
5897
6001
|
errors.push('missing CANVAS / LAYOUT section');
|
|
5898
|
-
if (!/FRAME GEOMETRY:/i.test(prompt))
|
|
6002
|
+
if (!hasConsolidatedLayoutContract && !/FRAME GEOMETRY:/i.test(prompt))
|
|
5899
6003
|
errors.push('missing FRAME GEOMETRY section');
|
|
5900
6004
|
const cellOrientation = parseAspectRatioOrientation(layout.cellAspectRatio);
|
|
5901
|
-
if (cellOrientation === 'portrait' && !/PORTRAIT FRAME GEOMETRY:/i.test(prompt)) {
|
|
6005
|
+
if (!hasConsolidatedLayoutContract && cellOrientation === 'portrait' && !/PORTRAIT FRAME GEOMETRY:/i.test(prompt)) {
|
|
5902
6006
|
errors.push('missing PORTRAIT FRAME GEOMETRY section');
|
|
5903
6007
|
}
|
|
5904
|
-
if (cellOrientation === 'landscape' && !/LANDSCAPE FRAME GEOMETRY:/i.test(prompt)) {
|
|
6008
|
+
if (!hasConsolidatedLayoutContract && cellOrientation === 'landscape' && !/LANDSCAPE FRAME GEOMETRY:/i.test(prompt)) {
|
|
5905
6009
|
errors.push('missing LANDSCAPE FRAME GEOMETRY section');
|
|
5906
6010
|
}
|
|
5907
|
-
if (cellOrientation === 'square' && !/SQUARE FRAME GEOMETRY:/i.test(prompt)) {
|
|
6011
|
+
if (!hasConsolidatedLayoutContract && cellOrientation === 'square' && !/SQUARE FRAME GEOMETRY:/i.test(prompt)) {
|
|
5908
6012
|
errors.push('missing SQUARE FRAME GEOMETRY section');
|
|
5909
6013
|
}
|
|
5910
|
-
if (!/TEXT RENDERING:/i.test(prompt))
|
|
6014
|
+
if (!/(?:TEXT RENDERING|TEXT RULES):/i.test(prompt))
|
|
5911
6015
|
errors.push('missing TEXT RENDERING section');
|
|
5912
6016
|
if (!prompt.includes(`Overall storyboard canvas aspect ratio: ${layout.boardAspectRatio}`)
|
|
5913
6017
|
&& !prompt.includes(`(${layout.boardAspectRatio})`)) {
|
|
@@ -5978,7 +6082,7 @@ function inferCompiledStoryboardPromptAspectRatio(prompt, labelPattern) {
|
|
|
5978
6082
|
return normalizeAspectRatio(match?.[1]);
|
|
5979
6083
|
}
|
|
5980
6084
|
function extractCompiledStoryboardScenesBlock(prompt) {
|
|
5981
|
-
const match = prompt.match(/\nSCENES:\s*\n([\s\S]*?)(?:\nTEXT RENDERING:|\nSOURCE BRIEF TO FOLLOW:|\nNEGATIVE \/ AVOID:|$)/i);
|
|
6085
|
+
const match = prompt.match(/\nSCENES:\s*\n([\s\S]*?)(?:\nTEXT RENDERING:|\nTEXT RULES:|\nSOURCE BRIEF TO FOLLOW:|\nNEGATIVE \/ AVOID:|$)/i);
|
|
5982
6086
|
return match?.[1] ?? '';
|
|
5983
6087
|
}
|
|
5984
6088
|
function parseCompiledStoryboardHeadingTiming(heading) {
|