@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
|
@@ -696,33 +696,48 @@ function compileStoryboardImagePromptFromProject(project) {
|
|
|
696
696
|
: `Overall storyboard canvas aspect ratio: ${layout.boardAspectRatio}.`;
|
|
697
697
|
return [
|
|
698
698
|
'CREATE:',
|
|
699
|
-
`Create exactly ${frameCount} sequential video storyboard frames as one
|
|
700
|
-
`Project title: ${project.title}.`,
|
|
701
|
-
project.durationSec !== null ? `Target duration: ${project.durationSec} seconds.` : 'Target duration: unspecified in source brief.',
|
|
702
|
-
'',
|
|
703
|
-
...compileStoryboardCountContractSection(project, layout),
|
|
699
|
+
`Create exactly ${frameCount} sequential video storyboard frames as one production storyboard sheet.`,
|
|
704
700
|
'',
|
|
705
|
-
|
|
701
|
+
'PROJECT:',
|
|
702
|
+
`Title: ${project.title}.`,
|
|
703
|
+
`Format: ${layout.targetVideoAspectRatio} video storyboard.`,
|
|
704
|
+
project.durationSec !== null ? `Target duration: ${project.durationSec} seconds.` : 'Target duration: unspecified in source brief.',
|
|
706
705
|
'',
|
|
707
|
-
'
|
|
706
|
+
'LAYOUT CONTRACT:',
|
|
707
|
+
`Create exactly ${frameCount} numbered storyboard panels; do not render fewer or more panels.`,
|
|
708
|
+
`Arrange panels in reading order, left-to-right then top-to-bottom: ${Array.from({ length: frameCount }, (_, index) => {
|
|
709
|
+
const scene = project.scenes[index];
|
|
710
|
+
return `[${index + 1}] ${scene?.id.toUpperCase() ?? `SCENE_${String(index + 1).padStart(2, '0')}`}`;
|
|
711
|
+
}).join(', ')}.`,
|
|
708
712
|
boardSizeLine,
|
|
709
713
|
`Individual scene-cell/frame aspect ratio: ${layout.cellAspectRatio}.`,
|
|
710
714
|
`Target final video aspect ratio: ${layout.targetVideoAspectRatio}.`,
|
|
711
715
|
`Layout preset: ${layout.layoutKind} - ${layout.layoutDescription}.`,
|
|
716
|
+
`Each panel must contain one distinct ${layout.cellAspectRatio} cinematic video-frame rectangle with compact notes outside the frame.`,
|
|
717
|
+
'Keep scene numbers, timecodes, titles, dialogue/VO, audio notes, and production notes outside the video-frame rectangles.',
|
|
718
|
+
'Do not merge panels, create inset thumbnails, make panels square, or overlay storyboard metadata inside the artwork frames.',
|
|
712
719
|
'',
|
|
713
|
-
...
|
|
720
|
+
...compileStoryboardReferenceSection(project),
|
|
721
|
+
'',
|
|
722
|
+
'STYLE:',
|
|
723
|
+
`${project.creativeBrief.visualQualityBar} with cinematic shot language, coherent art direction, readable labels, and consistent reference usage.`,
|
|
724
|
+
'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.',
|
|
725
|
+
'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.',
|
|
714
726
|
'',
|
|
715
|
-
|
|
716
|
-
project.creativeBrief.visualQualityBar,
|
|
727
|
+
...compileStoryboardStoryContinuitySection(project),
|
|
717
728
|
'',
|
|
718
729
|
'CRITICAL REQUIREMENTS:',
|
|
719
730
|
...compileStoryboardCriticalRequirements().map((item, index) => `${index + 1}. ${item}`),
|
|
731
|
+
...project.references
|
|
732
|
+
.filter(ref => ref.preservePriority === 'critical')
|
|
733
|
+
.map((ref, index) => `${compileStoryboardCriticalRequirements().length + index + 1}. Critical reference lock: ${ref.id} (${ref.kind}) must remain bound to its assigned usage scope: ${ref.usageScope}.`),
|
|
720
734
|
'',
|
|
721
735
|
...compileStoryboardScenesSection(project),
|
|
722
|
-
'TEXT
|
|
723
|
-
'
|
|
736
|
+
'TEXT RULES:',
|
|
737
|
+
'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.',
|
|
724
738
|
'Visible text listed on a scene belongs only in that scene; repeat visible text only on scenes that list it.',
|
|
725
739
|
...storyboardRequiredVisibleText(project).map(formatStoryboardRequiredVisibleTextLine),
|
|
740
|
+
project.endCard.logoUsage ? `Logo usage: ${project.endCard.logoUsage}` : '',
|
|
726
741
|
'',
|
|
727
742
|
'NEGATIVE / AVOID:',
|
|
728
743
|
...compileStoryboardAvoidSection(project.creativeBrief.concept).map(item => `- ${item}`),
|
|
@@ -1558,7 +1573,7 @@ export function suggestedDurationForDialogue(prompt, currentDuration) {
|
|
|
1558
1573
|
const baseDuration = currentDuration ?? 0;
|
|
1559
1574
|
if (words <= 0)
|
|
1560
1575
|
return baseDuration;
|
|
1561
|
-
const speechSeconds = Math.ceil(words /
|
|
1576
|
+
const speechSeconds = Math.ceil(words / 3) + 2;
|
|
1562
1577
|
return Math.max(baseDuration, Math.min(20, speechSeconds));
|
|
1563
1578
|
}
|
|
1564
1579
|
export function formatAudioIdPrompt(prompt, voiceName) {
|
|
@@ -2656,8 +2671,8 @@ const STORYBOARD_COUNT_WORDS = {
|
|
|
2656
2671
|
};
|
|
2657
2672
|
const DEFAULT_STORYBOARD_TIMING_RULES = {
|
|
2658
2673
|
normalWordsPerSecondMin: 2.0,
|
|
2659
|
-
normalWordsPerSecondMax: 3.
|
|
2660
|
-
fastWordsPerSecondMax:
|
|
2674
|
+
normalWordsPerSecondMax: 3.0,
|
|
2675
|
+
fastWordsPerSecondMax: 3.75,
|
|
2661
2676
|
minEndCardHoldSec: 2.0,
|
|
2662
2677
|
minPunchlineSec: 0.5,
|
|
2663
2678
|
toleranceSec: 0.25,
|
|
@@ -2850,10 +2865,13 @@ function describePortraitLetterboxCellArrangement(frameCount, cellAspectRatio) {
|
|
|
2850
2865
|
}
|
|
2851
2866
|
const columns = frameCount <= 8 ? 2 : frameCount <= 15 ? 3 : 4;
|
|
2852
2867
|
const rows = Math.ceil(frameCount / columns);
|
|
2868
|
+
const unusedSlotNote = columns * rows > frameCount
|
|
2869
|
+
? ['use unused grid slots as margin/notes space only']
|
|
2870
|
+
: [];
|
|
2853
2871
|
return [
|
|
2854
2872
|
`${frameCount} numbered scene slots arranged as a ${columns}-column x ${rows}-row grid inside a portrait sheet`,
|
|
2855
2873
|
`each slot contains one ${frameShape} with compact labels outside the rectangle`,
|
|
2856
|
-
|
|
2874
|
+
...unusedSlotNote,
|
|
2857
2875
|
].join('; ');
|
|
2858
2876
|
}
|
|
2859
2877
|
function describeLandscapePortraitCellArrangement(frameCount, cellAspectRatio) {
|
|
@@ -2863,10 +2881,13 @@ function describeLandscapePortraitCellArrangement(frameCount, cellAspectRatio) {
|
|
|
2863
2881
|
}
|
|
2864
2882
|
const rows = frameCount <= 8 ? 2 : frameCount <= 15 ? 3 : 4;
|
|
2865
2883
|
const columns = Math.ceil(frameCount / rows);
|
|
2884
|
+
const unusedSlotNote = columns * rows > frameCount
|
|
2885
|
+
? ['use unused grid slots as margin/notes space only']
|
|
2886
|
+
: [];
|
|
2866
2887
|
return [
|
|
2867
2888
|
`${frameCount} numbered scene slots arranged as a ${rows}-row x ${columns}-column grid inside a landscape board`,
|
|
2868
2889
|
`each slot contains one ${frameShape} with compact labels outside the rectangle`,
|
|
2869
|
-
|
|
2890
|
+
...unusedSlotNote,
|
|
2870
2891
|
].join('; ');
|
|
2871
2892
|
}
|
|
2872
2893
|
function parseAspectRatioPair(aspectRatio) {
|
|
@@ -2981,13 +3002,16 @@ function describeSingleOrientationStoryboardArrangement(boardAspectRatio, cellAs
|
|
|
2981
3002
|
const slotGrid = pickBalancedSlotGrid(frameCount);
|
|
2982
3003
|
const cols = cellOrientation === 'landscape' ? slotGrid.minor : slotGrid.major;
|
|
2983
3004
|
const rows = cellOrientation === 'landscape' ? slotGrid.major : slotGrid.minor;
|
|
3005
|
+
const unusedSlotNote = cols * rows > frameCount
|
|
3006
|
+
? ['use unused grid slots as margin/notes space only']
|
|
3007
|
+
: [];
|
|
2984
3008
|
if (boardOrientation === 'portrait') {
|
|
2985
3009
|
return {
|
|
2986
3010
|
layoutKind: 'portrait_grid',
|
|
2987
3011
|
layoutDescription: [
|
|
2988
3012
|
`${frameCount} numbered scene slots arranged as a ${cols}-column x ${rows}-row grid inside a portrait storyboard sheet`,
|
|
2989
3013
|
`each slot contains one ${frameShape} with compact labels outside the rectangle`,
|
|
2990
|
-
|
|
3014
|
+
...unusedSlotNote,
|
|
2991
3015
|
].join('; '),
|
|
2992
3016
|
};
|
|
2993
3017
|
}
|
|
@@ -2997,7 +3021,7 @@ function describeSingleOrientationStoryboardArrangement(boardAspectRatio, cellAs
|
|
|
2997
3021
|
layoutDescription: [
|
|
2998
3022
|
`${frameCount} numbered scene slots arranged as a ${rows}-row x ${cols}-column grid inside a ${boardLabel}`,
|
|
2999
3023
|
`each slot contains one ${frameShape} with compact labels outside the rectangle`,
|
|
3000
|
-
|
|
3024
|
+
...unusedSlotNote,
|
|
3001
3025
|
].join('; '),
|
|
3002
3026
|
};
|
|
3003
3027
|
}
|
|
@@ -3381,6 +3405,16 @@ function cleanStoryboardReferenceSubjectHint(context, index) {
|
|
|
3381
3405
|
const explicitSubject = cleanExplicitStoryboardReferenceSubject(source, index);
|
|
3382
3406
|
if (explicitSubject)
|
|
3383
3407
|
return explicitSubject;
|
|
3408
|
+
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);
|
|
3409
|
+
if (inlineReference?.[1]) {
|
|
3410
|
+
const inlineSubject = compactStoryboardLine(stripStoryboardMarkup(inlineReference[1]))
|
|
3411
|
+
.replace(/\b(?:use|usage|preserve|reference|asset)\b[\s\S]*$/i, '')
|
|
3412
|
+
.replace(/[.\s]+$/g, '')
|
|
3413
|
+
.trim();
|
|
3414
|
+
if (inlineSubject && !/^(?:logo|brand|character|mascot|asset|reference|image|photo|picture)$/i.test(inlineSubject)) {
|
|
3415
|
+
return inlineSubject.slice(0, 180).replace(/\s+\S*$/, match => inlineSubject.length > 180 ? '' : match).trim();
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3384
3418
|
const cleaned = source
|
|
3385
3419
|
.replace(new RegExp(String.raw `^(?:image|photo|picture|asset)\s*(?:#|number\s*)?${index}\s*(?:\([^)]*\))?\s*:?\s*`, 'i'), '')
|
|
3386
3420
|
.replace(new RegExp(String.raw `^(?:uploaded|attached|provided|reference|source|input)\s+(?:image|photo|picture|asset)\s*(?:#|number\s*)?${index}\s*:?\s*`, 'i'), '')
|
|
@@ -3488,6 +3522,51 @@ function buildStoryboardReferenceAssets(userIntentText, prompt) {
|
|
|
3488
3522
|
preservePriority: storyboardPreservePriorityFromRole(role),
|
|
3489
3523
|
}));
|
|
3490
3524
|
}
|
|
3525
|
+
function storyboardReferenceIndexFromText(value) {
|
|
3526
|
+
const normalized = value.trim();
|
|
3527
|
+
if (!normalized)
|
|
3528
|
+
return null;
|
|
3529
|
+
const match = normalized.match(/^(?:@?\s*)?(?:image|photo|picture|asset|uploaded\s+asset|uploaded\s+image)[\s_#-]*(\d{1,2})$/i)
|
|
3530
|
+
?? normalized.match(/^image_(\d{1,2})$/i);
|
|
3531
|
+
if (!match)
|
|
3532
|
+
return null;
|
|
3533
|
+
const index = Number(match[1]);
|
|
3534
|
+
return Number.isInteger(index) && index > 0 ? index : null;
|
|
3535
|
+
}
|
|
3536
|
+
function looksLikeUnknownIndexedStoryboardReference(value) {
|
|
3537
|
+
return /^(?:@?\s*)?(?:image|photo|picture|asset|uploaded\s+asset|uploaded\s+image)[\s_#-]*\d{1,2}$/i.test(value.trim())
|
|
3538
|
+
|| /^image_\d{1,2}$/i.test(value.trim());
|
|
3539
|
+
}
|
|
3540
|
+
function normalizeStoryboardSceneReferenceUsage(referenceUsage, references) {
|
|
3541
|
+
if (referenceUsage.length === 0 || references.length === 0)
|
|
3542
|
+
return [];
|
|
3543
|
+
const knownById = new Map(references.map(ref => [ref.id.toLowerCase(), ref.id]));
|
|
3544
|
+
const knownByIndex = new Map(references
|
|
3545
|
+
.filter(ref => typeof ref.index === 'number')
|
|
3546
|
+
.map(ref => [ref.index, ref.id]));
|
|
3547
|
+
const normalized = [];
|
|
3548
|
+
for (const raw of referenceUsage) {
|
|
3549
|
+
const value = raw.trim();
|
|
3550
|
+
if (!value)
|
|
3551
|
+
continue;
|
|
3552
|
+
const knownId = knownById.get(value.toLowerCase());
|
|
3553
|
+
if (knownId) {
|
|
3554
|
+
normalized.push(knownId);
|
|
3555
|
+
continue;
|
|
3556
|
+
}
|
|
3557
|
+
const index = storyboardReferenceIndexFromText(value);
|
|
3558
|
+
if (index !== null) {
|
|
3559
|
+
const id = knownByIndex.get(index);
|
|
3560
|
+
if (id)
|
|
3561
|
+
normalized.push(id);
|
|
3562
|
+
continue;
|
|
3563
|
+
}
|
|
3564
|
+
if (looksLikeUnknownIndexedStoryboardReference(value))
|
|
3565
|
+
continue;
|
|
3566
|
+
normalized.push(value);
|
|
3567
|
+
}
|
|
3568
|
+
return uniqueStoryboardStrings(normalized);
|
|
3569
|
+
}
|
|
3491
3570
|
function compileStoryboardReferenceSection(project) {
|
|
3492
3571
|
const refs = project.references;
|
|
3493
3572
|
if (refs.length <= 0) {
|
|
@@ -3535,6 +3614,31 @@ function extractStoryboardRequiredText(text) {
|
|
|
3535
3614
|
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;
|
|
3536
3615
|
const sceneHeadingPattern = /^\s*(?:[-*+]\s*)?(?:#{1,6}\s*)?(?:[*_]{1,3})?\s*(?:Scene|Shot|Beat|Panel|Frame)[\s_#-]*\d{1,2}\b/i;
|
|
3537
3616
|
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;
|
|
3617
|
+
const storyboardTextCandidateLooksLikeConversationInstruction = (candidate) => {
|
|
3618
|
+
const raw = compactStoryboardLine(stripStoryboardMarkup(candidate))
|
|
3619
|
+
.replace(/^["“”'`]+|["“”'`]+$/g, '')
|
|
3620
|
+
.trim();
|
|
3621
|
+
if (!raw)
|
|
3622
|
+
return false;
|
|
3623
|
+
if (/^\[(?:uploaded|attached|provided)\s+(?:images?|files?|videos?|audio)\s*:/i.test(raw))
|
|
3624
|
+
return true;
|
|
3625
|
+
if (/^(?:yes|no|approved?|looks?\s+good|ok(?:ay)?|sure)[.!?]?$/i.test(raw))
|
|
3626
|
+
return true;
|
|
3627
|
+
const value = raw.toLowerCase();
|
|
3628
|
+
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;
|
|
3629
|
+
if (/^use\s+your\s+best\s+judg(?:e)?ment\b/i.test(raw))
|
|
3630
|
+
return true;
|
|
3631
|
+
if (/^(?:try\s+another|another\s+take|regenerate|re-generate|redo|retry|revise)\b/i.test(raw))
|
|
3632
|
+
return true;
|
|
3633
|
+
if (/^(?:go\s+ahead|can\s+you|please)\b/i.test(raw) && workflowTerm.test(value))
|
|
3634
|
+
return true;
|
|
3635
|
+
if (/^let'?s\b|^lets\b/i.test(raw)) {
|
|
3636
|
+
return /\b(?:script|prompt|story\s*board|storyboard|take|version|again|redo|retry|regenerate|re-generate|revise|revision)\b/i.test(value)
|
|
3637
|
+
|| /\b(?:generate|render|create|do|run)\s+it\b/i.test(value)
|
|
3638
|
+
|| /\buse\s+your\s+best\s+judg(?:e)?ment\b/i.test(value);
|
|
3639
|
+
}
|
|
3640
|
+
return false;
|
|
3641
|
+
};
|
|
3538
3642
|
const shouldIgnoreRequiredText = (value, matchIndex, precedingText) => {
|
|
3539
3643
|
const lineStart = text.lastIndexOf('\n', matchIndex) + 1;
|
|
3540
3644
|
const nextLineBreak = text.indexOf('\n', matchIndex);
|
|
@@ -3570,6 +3674,9 @@ function extractStoryboardRequiredText(text) {
|
|
|
3570
3674
|
if (storyboardTextCandidateLooksLikeGenericProductionLabel(value)) {
|
|
3571
3675
|
return true;
|
|
3572
3676
|
}
|
|
3677
|
+
if (storyboardTextCandidateLooksLikeConversationInstruction(value)) {
|
|
3678
|
+
return true;
|
|
3679
|
+
}
|
|
3573
3680
|
if (isNestedSceneHeadingField) {
|
|
3574
3681
|
return true;
|
|
3575
3682
|
}
|
|
@@ -3644,6 +3751,8 @@ function extractStoryboardRequiredText(text) {
|
|
|
3644
3751
|
return false;
|
|
3645
3752
|
if (storyboardTextCandidateLooksLikeGenericProductionLabel(value))
|
|
3646
3753
|
return false;
|
|
3754
|
+
if (storyboardTextCandidateLooksLikeConversationInstruction(value))
|
|
3755
|
+
return false;
|
|
3647
3756
|
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
|
|
3648
3757
|
.test(context);
|
|
3649
3758
|
if (!contextNamesText)
|
|
@@ -3660,7 +3769,10 @@ function extractStoryboardRequiredText(text) {
|
|
|
3660
3769
|
|| /^(?:powered|made|built|created|generated|rendered|presented)\s+by\b/i.test(value);
|
|
3661
3770
|
return !productionDirection || copyOverridesProduction;
|
|
3662
3771
|
};
|
|
3663
|
-
const
|
|
3772
|
+
const endCardFollowingLineLooksLikeContinuationInstruction = (line) => {
|
|
3773
|
+
return storyboardTextCandidateLooksLikeConversationInstruction(line);
|
|
3774
|
+
};
|
|
3775
|
+
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;
|
|
3664
3776
|
for (const match of text.matchAll(endCardLabelPattern)) {
|
|
3665
3777
|
const matchIndex = match.index ?? 0;
|
|
3666
3778
|
const lineEnd = text.indexOf('\n', matchIndex);
|
|
@@ -3672,6 +3784,8 @@ function extractStoryboardRequiredText(text) {
|
|
|
3672
3784
|
break;
|
|
3673
3785
|
if (/^(?:#{1,6}\s+|\|\s*|(?:Scene|Shot|Beat|Panel|Frame)\s*\d{1,2}\b)/i.test(trimmed))
|
|
3674
3786
|
break;
|
|
3787
|
+
if (endCardFollowingLineLooksLikeContinuationInstruction(trimmed))
|
|
3788
|
+
break;
|
|
3675
3789
|
if (followingLines.length >= 5)
|
|
3676
3790
|
break;
|
|
3677
3791
|
followingLines.push(trimmed);
|
|
@@ -4986,10 +5100,10 @@ function alignAssistantStoryboardDialogueWithUserSource(scenes, userIntentText,
|
|
|
4986
5100
|
return { scenes, shouldRetime: false };
|
|
4987
5101
|
return { scenes: repaired, shouldRetime: true };
|
|
4988
5102
|
}
|
|
4989
|
-
function minimumStoryboardSceneDurationForDialogue(scene, rules) {
|
|
5103
|
+
function minimumStoryboardSceneDurationForDialogue(scene, rules, wordsPerSecondMax = rules.normalWordsPerSecondMax) {
|
|
4990
5104
|
const dialogueWords = countWords(scene.dialogue);
|
|
4991
5105
|
let minDuration = dialogueWords > 0
|
|
4992
|
-
? dialogueWords /
|
|
5106
|
+
? dialogueWords / wordsPerSecondMax
|
|
4993
5107
|
: 0.5;
|
|
4994
5108
|
const sceneContext = `${scene.title}\n${scene.visual}\n${scene.textInImage.join(' ')}`;
|
|
4995
5109
|
const hasReadableText = scene.textInImage.length > 0;
|
|
@@ -5012,13 +5126,22 @@ function retimeStoryboardScenesForDialogue(scenes, targetDurationSec, rules = DE
|
|
|
5012
5126
|
const needsRetiming = scenes.some((scene, index) => (scene.durationSec ?? 0) + rules.toleranceSec < minimumDurations[index]);
|
|
5013
5127
|
if (!needsRetiming)
|
|
5014
5128
|
return scenes;
|
|
5015
|
-
|
|
5129
|
+
let requiredDurations = minimumDurations;
|
|
5130
|
+
let minimumTotal = Math.round(requiredDurations.reduce((sum, duration) => sum + duration, 0) * 100) / 100;
|
|
5131
|
+
if (minimumTotal > targetDurationSec + rules.toleranceSec && rules.fastWordsPerSecondMax > rules.normalWordsPerSecondMax) {
|
|
5132
|
+
const fastDurations = scenes.map(scene => minimumStoryboardSceneDurationForDialogue(scene, rules, rules.fastWordsPerSecondMax));
|
|
5133
|
+
const fastTotal = Math.round(fastDurations.reduce((sum, duration) => sum + duration, 0) * 100) / 100;
|
|
5134
|
+
if (fastTotal <= targetDurationSec + rules.toleranceSec) {
|
|
5135
|
+
requiredDurations = fastDurations;
|
|
5136
|
+
minimumTotal = fastTotal;
|
|
5137
|
+
}
|
|
5138
|
+
}
|
|
5016
5139
|
if (minimumTotal > targetDurationSec + rules.toleranceSec)
|
|
5017
5140
|
return scenes;
|
|
5018
5141
|
const extraDuration = Math.max(0, targetDurationSec - minimumTotal);
|
|
5019
5142
|
const originalDurations = scenes.map(scene => scene.durationSec ?? 0);
|
|
5020
5143
|
const weightTotal = originalDurations.reduce((sum, duration) => sum + Math.max(0.01, duration), 0);
|
|
5021
|
-
const unroundedDurations =
|
|
5144
|
+
const unroundedDurations = requiredDurations.map((minimum, index) => (minimum + (extraDuration * Math.max(0.01, originalDurations[index]) / weightTotal)));
|
|
5022
5145
|
const timelineStartSec = scenes[0].startSec ?? 0;
|
|
5023
5146
|
const timelineEndSec = Math.round((timelineStartSec + targetDurationSec) * 100) / 100;
|
|
5024
5147
|
let cursor = timelineStartSec;
|
|
@@ -5190,6 +5313,27 @@ function finalStoryboardSceneVisibleText(scenes) {
|
|
|
5190
5313
|
}
|
|
5191
5314
|
return [];
|
|
5192
5315
|
}
|
|
5316
|
+
function applyStoryboardEndCardTextToScenes(scenes, endCardText) {
|
|
5317
|
+
if (scenes.length === 0 || endCardText.length === 0)
|
|
5318
|
+
return scenes;
|
|
5319
|
+
const finalSceneIndex = scenes.length - 1;
|
|
5320
|
+
const explicitEndCardIndex = (() => {
|
|
5321
|
+
for (let index = scenes.length - 1; index >= 0; index -= 1) {
|
|
5322
|
+
if (storyboardSceneLooksLikeEndCard(scenes[index]))
|
|
5323
|
+
return index;
|
|
5324
|
+
}
|
|
5325
|
+
return finalSceneIndex;
|
|
5326
|
+
})();
|
|
5327
|
+
return scenes.map((scene, index) => index === explicitEndCardIndex
|
|
5328
|
+
? {
|
|
5329
|
+
...scene,
|
|
5330
|
+
textInImage: uniqueStoryboardStrings([
|
|
5331
|
+
...scene.textInImage,
|
|
5332
|
+
...endCardText,
|
|
5333
|
+
]),
|
|
5334
|
+
}
|
|
5335
|
+
: scene);
|
|
5336
|
+
}
|
|
5193
5337
|
function storyboardRequiredTextKey(value) {
|
|
5194
5338
|
return compactStoryboardLine(value)
|
|
5195
5339
|
.toLowerCase()
|
|
@@ -5240,6 +5384,7 @@ export function buildStoryboardProject(options) {
|
|
|
5240
5384
|
const userIntentText = canonicalStoryboardScriptContext(rawUserIntentText) || rawUserIntentText;
|
|
5241
5385
|
const narrativeUserIntentText = cleanStoryboardNarrativeSourceText(userIntentText);
|
|
5242
5386
|
const approvedScriptContext = cleanStoryboardNarrativeSourceText(canonicalStoryboardScriptContext(options.approvedScriptContext));
|
|
5387
|
+
const referencePromptContext = cleanStoryboardNarrativeSourceText(canonicalStoryboardScriptContext(prompt) || prompt);
|
|
5243
5388
|
const primarySourceBrief = selectStoryboardSourceBriefForCompile(options, userIntentText);
|
|
5244
5389
|
const sourceText = stripGenericStoryboardVisibleTextMetadata(sanitizeStoryboardExternalAudioReferences([
|
|
5245
5390
|
cleanStoryboardNarrativeSourceText(primarySourceBrief),
|
|
@@ -5263,7 +5408,7 @@ export function buildStoryboardProject(options) {
|
|
|
5263
5408
|
const requestedDurationSec = inferRequestedTotalVideoDurationSeconds(userIntentText);
|
|
5264
5409
|
const durationSec = requestedDurationSec ?? inferRequestedTotalVideoDurationSeconds(sourceText);
|
|
5265
5410
|
const references = buildStoryboardReferenceAssets(userIntentText, [
|
|
5266
|
-
|
|
5411
|
+
referencePromptContext,
|
|
5267
5412
|
approvedScriptContext,
|
|
5268
5413
|
].filter(Boolean).join('\n\n'));
|
|
5269
5414
|
const approvedSections = approvedScriptContext
|
|
@@ -5294,21 +5439,25 @@ export function buildStoryboardProject(options) {
|
|
|
5294
5439
|
return buildSceneFromSection(section, references, null, storyboardScenePlanningContractForIndex(options.planningContract, section.number));
|
|
5295
5440
|
})
|
|
5296
5441
|
: [];
|
|
5297
|
-
const scenes = parsedScenes
|
|
5442
|
+
const scenes = parsedScenes.map(scene => ({
|
|
5443
|
+
...scene,
|
|
5444
|
+
referenceUsage: normalizeStoryboardSceneReferenceUsage(scene.referenceUsage, references),
|
|
5445
|
+
}));
|
|
5298
5446
|
const timingNormalizedScenes = normalizeAssistantStoryboardSceneTiming(scenes, durationSec, options.promptAuthorship);
|
|
5299
5447
|
const dialogueAlignment = alignAssistantStoryboardDialogueWithUserSource(timingNormalizedScenes, userIntentText, options.promptAuthorship);
|
|
5300
|
-
const
|
|
5448
|
+
const dialogueTimedScenes = (options.promptAuthorship === 'assistant' || dialogueAlignment.shouldRetime)
|
|
5301
5449
|
? retimeStoryboardScenesForDialogue(dialogueAlignment.scenes, durationSec)
|
|
5302
5450
|
: dialogueAlignment.scenes;
|
|
5303
5451
|
const userConstraintSource = buildStoryboardUserConstraintSource(narrativeUserIntentText, cleanStoryboardNarrativeSourceText(primarySourceBrief), options);
|
|
5304
|
-
const { mustIncludeText, endCardText } = storyboardRequiredTextForProject(options, userConstraintSource,
|
|
5452
|
+
const { mustIncludeText, endCardText } = storyboardRequiredTextForProject(options, userConstraintSource, dialogueTimedScenes);
|
|
5453
|
+
const normalizedScenes = retimeStoryboardScenesForDialogue(applyStoryboardEndCardTextToScenes(dialogueTimedScenes, endCardText), durationSec);
|
|
5305
5454
|
const voiceLines = assignVoiceLinesToScenes(normalizedScenes, sourceText);
|
|
5306
5455
|
const storySpineFallback = approvedScriptContext
|
|
5307
5456
|
|| (options.promptAuthorship === 'assistant' ? narrativeUserIntentText : cleanStoryboardNarrativeSourceText(primarySourceBrief))
|
|
5308
5457
|
|| prompt
|
|
5309
5458
|
|| narrativeUserIntentText;
|
|
5310
5459
|
const storySpine = inferStoryboardStorySpine(allText, storySpineFallback);
|
|
5311
|
-
const productFeatureMap = inferStoryboardProductFeatureMap(allText,
|
|
5460
|
+
const productFeatureMap = inferStoryboardProductFeatureMap(allText, normalizedScenes);
|
|
5312
5461
|
const beatMarkerScripts = [approvedScriptContext, sourceText];
|
|
5313
5462
|
let directBeatMarkerCount = 0;
|
|
5314
5463
|
for (const text of beatMarkerScripts) {
|
|
@@ -5538,67 +5687,17 @@ function compileStoryboardStoryContinuitySection(project) {
|
|
|
5538
5687
|
'Keep product/feature/brand meaning in the associated notes. Do not force long product explanations or the whole script into tiny in-frame text.',
|
|
5539
5688
|
];
|
|
5540
5689
|
}
|
|
5541
|
-
function compileStoryboardCountContractSection(project, layout, expectedFrameCount = project.scenes.length) {
|
|
5542
|
-
if (expectedFrameCount <= 0)
|
|
5543
|
-
return [];
|
|
5544
|
-
const sceneSlots = Array.from({ length: expectedFrameCount }, (_, index) => {
|
|
5545
|
-
const scene = project.scenes[index];
|
|
5546
|
-
return `[${index + 1}] ${scene?.id.toUpperCase() ?? `SCENE_${String(index + 1).padStart(2, '0')}`}`;
|
|
5547
|
-
})
|
|
5548
|
-
.join(', ');
|
|
5549
|
-
return [
|
|
5550
|
-
'COUNT / GRID CONTRACT:',
|
|
5551
|
-
`Required scene count: exactly ${expectedFrameCount} numbered storyboard scene slots; do not render fewer slots and do not add extra scene slots.`,
|
|
5552
|
-
`Allocate the full ${expectedFrameCount}-slot storyboard grid before drawing details. Fill slots in reading order, left-to-right then top-to-bottom: ${sceneSlots}.`,
|
|
5553
|
-
`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.`,
|
|
5554
|
-
`Layout preset: ${layout.layoutKind} - ${layout.layoutDescription}. The final sheet should be visibly countable as ${expectedFrameCount} numbered scene slots at a glance.`,
|
|
5555
|
-
];
|
|
5556
|
-
}
|
|
5557
|
-
function compileStoryboardFrameGeometrySection(layout) {
|
|
5558
|
-
const cellOrientation = parseAspectRatioOrientation(layout.cellAspectRatio);
|
|
5559
|
-
if (cellOrientation === 'portrait') {
|
|
5560
|
-
return [
|
|
5561
|
-
'PORTRAIT FRAME GEOMETRY:',
|
|
5562
|
-
'Treat each numbered scene slot as a container, not as the artwork shape.',
|
|
5563
|
-
`Inside every numbered scene slot, draw one identical upright ${layout.cellAspectRatio} video-frame rectangle whose height is visibly greater than its width.`,
|
|
5564
|
-
`Keep all scene numbers, timing, titles, notes, headers, footers, and production labels outside the ${layout.cellAspectRatio} video-frame rectangles.`,
|
|
5565
|
-
`Do not make the numbered scene slots, frame artwork areas, or thumbnails square. Square cells violate the requested ${layout.cellAspectRatio} final video format.`,
|
|
5566
|
-
'Unused grid slots must remain blank margin/notes space only; do not stretch or square off the portrait frame rectangles to fill the grid.',
|
|
5567
|
-
];
|
|
5568
|
-
}
|
|
5569
|
-
if (cellOrientation === 'landscape') {
|
|
5570
|
-
return [
|
|
5571
|
-
'LANDSCAPE FRAME GEOMETRY:',
|
|
5572
|
-
'Treat each numbered scene slot as a container, not as the artwork shape.',
|
|
5573
|
-
`Inside every numbered scene slot, draw one identical wide ${layout.cellAspectRatio} video-frame rectangle whose width is visibly greater than its height.`,
|
|
5574
|
-
`Keep all scene numbers, timing, titles, notes, headers, footers, and production labels outside the ${layout.cellAspectRatio} video-frame rectangles.`,
|
|
5575
|
-
`Do not make the numbered scene slots, frame artwork areas, or thumbnails square. Square cells violate the requested ${layout.cellAspectRatio} final video format.`,
|
|
5576
|
-
'Unused grid slots must remain blank margin/notes space only; do not stretch or square off the landscape frame rectangles to fill the grid.',
|
|
5577
|
-
];
|
|
5578
|
-
}
|
|
5579
|
-
if (cellOrientation === 'square') {
|
|
5580
|
-
return [
|
|
5581
|
-
'SQUARE FRAME GEOMETRY:',
|
|
5582
|
-
'Treat each numbered scene slot as a container, not as the artwork shape.',
|
|
5583
|
-
`Inside every numbered scene slot, draw one identical square ${layout.cellAspectRatio} video-frame area plus compact notes outside that square.`,
|
|
5584
|
-
`Do not stretch square frame artwork areas into portrait, landscape, full-board, or any aspect ratio other than ${layout.cellAspectRatio}.`,
|
|
5585
|
-
];
|
|
5586
|
-
}
|
|
5587
|
-
return [
|
|
5588
|
-
'FRAME GEOMETRY:',
|
|
5589
|
-
`Inside every numbered scene slot, draw one identical ${layout.cellAspectRatio} video-frame rectangle plus compact notes outside that rectangle.`,
|
|
5590
|
-
`Do not let frame artwork areas drift to square, full-board, or any aspect ratio other than ${layout.cellAspectRatio}.`,
|
|
5591
|
-
];
|
|
5592
|
-
}
|
|
5593
5690
|
function promptReferenceUsageForScene(referenceUsage, references, modelId) {
|
|
5594
5691
|
if (referenceUsage.length === 0)
|
|
5595
5692
|
return '';
|
|
5596
5693
|
const referenceById = new Map(references.map((ref, index) => [ref.id, { ref, index }]));
|
|
5597
|
-
return uniqueStoryboardStrings(referenceUsage.
|
|
5694
|
+
return uniqueStoryboardStrings(referenceUsage.flatMap(value => {
|
|
5598
5695
|
const found = referenceById.get(value);
|
|
5599
|
-
if (
|
|
5600
|
-
return
|
|
5601
|
-
|
|
5696
|
+
if (found)
|
|
5697
|
+
return [formatModelRef(modelId, found.ref.index ?? found.index + 1, 'image')];
|
|
5698
|
+
if (looksLikeUnknownIndexedStoryboardReference(value))
|
|
5699
|
+
return [];
|
|
5700
|
+
return [value];
|
|
5602
5701
|
})).join(', ');
|
|
5603
5702
|
}
|
|
5604
5703
|
function storyboardMetadataLabelAliases(labels) {
|
|
@@ -5733,32 +5832,36 @@ export function compileVideoStoryboardImagePrompt(options) {
|
|
|
5733
5832
|
: `Overall storyboard canvas aspect ratio: ${layout.boardAspectRatio}.`;
|
|
5734
5833
|
return [
|
|
5735
5834
|
'CREATE:',
|
|
5736
|
-
`Create exactly ${compiledFrameCount} sequential video storyboard frames as one
|
|
5737
|
-
`Project title: ${project.title}.`,
|
|
5738
|
-
project.durationSec !== null ? `Target duration: ${project.durationSec} seconds.` : 'Target duration: unspecified in source brief.',
|
|
5739
|
-
'',
|
|
5740
|
-
...compileStoryboardCountContractSection(project, layout, compiledFrameCount),
|
|
5835
|
+
`Create exactly ${compiledFrameCount} sequential video storyboard frames as one production storyboard sheet.`,
|
|
5741
5836
|
'',
|
|
5742
|
-
|
|
5837
|
+
'PROJECT:',
|
|
5838
|
+
`Title: ${project.title}.`,
|
|
5839
|
+
`Format: ${layout.targetVideoAspectRatio} video storyboard.`,
|
|
5840
|
+
project.durationSec !== null ? `Target duration: ${project.durationSec} seconds.` : 'Target duration: unspecified in source brief.',
|
|
5743
5841
|
'',
|
|
5744
|
-
'
|
|
5842
|
+
'LAYOUT CONTRACT:',
|
|
5843
|
+
`Create exactly ${compiledFrameCount} numbered storyboard panels; do not render fewer or more panels.`,
|
|
5844
|
+
`Arrange panels in reading order, left-to-right then top-to-bottom: ${Array.from({ length: compiledFrameCount }, (_, index) => {
|
|
5845
|
+
const scene = project.scenes[index];
|
|
5846
|
+
return `[${index + 1}] ${scene?.id.toUpperCase() ?? `SCENE_${String(index + 1).padStart(2, '0')}`}`;
|
|
5847
|
+
}).join(', ')}.`,
|
|
5745
5848
|
boardSizeLine,
|
|
5746
5849
|
`Individual scene-cell/frame aspect ratio: ${layout.cellAspectRatio}.`,
|
|
5747
5850
|
`Target final video aspect ratio: ${layout.targetVideoAspectRatio}.`,
|
|
5748
5851
|
`Layout preset: ${layout.layoutKind} - ${layout.layoutDescription}.`,
|
|
5749
|
-
`
|
|
5750
|
-
'Keep
|
|
5751
|
-
'
|
|
5852
|
+
`Each panel must contain one distinct ${layout.cellAspectRatio} cinematic video-frame rectangle with compact notes outside the frame.`,
|
|
5853
|
+
'Keep scene numbers, timecodes, titles, dialogue/VO, audio notes, and production notes outside the video-frame rectangles.',
|
|
5854
|
+
'Do not merge panels, create inset thumbnails, make panels square, or overlay storyboard metadata inside the artwork frames.',
|
|
5752
5855
|
'',
|
|
5753
|
-
...
|
|
5754
|
-
'',
|
|
5755
|
-
...compileStoryboardStoryContinuitySection(project),
|
|
5856
|
+
...compileStoryboardReferenceSection(project),
|
|
5756
5857
|
'',
|
|
5757
|
-
'
|
|
5858
|
+
'STYLE:',
|
|
5758
5859
|
`${project.creativeBrief.visualQualityBar} with cinematic shot language, coherent art direction, readable labels, and consistent reference usage.`,
|
|
5759
5860
|
'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.',
|
|
5760
5861
|
'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.',
|
|
5761
5862
|
'',
|
|
5863
|
+
...compileStoryboardStoryContinuitySection(project),
|
|
5864
|
+
'',
|
|
5762
5865
|
'CRITICAL REQUIREMENTS:',
|
|
5763
5866
|
...compileStoryboardCriticalRequirements().map((item, index) => `${index + 1}. ${item}`),
|
|
5764
5867
|
...project.references
|
|
@@ -5766,7 +5869,7 @@ export function compileVideoStoryboardImagePrompt(options) {
|
|
|
5766
5869
|
.map((ref, index) => `${compileStoryboardCriticalRequirements().length + index + 1}. Critical reference lock: ${ref.id} (${ref.kind}) must remain bound to its assigned usage scope: ${ref.usageScope}.`),
|
|
5767
5870
|
'',
|
|
5768
5871
|
...compileStoryboardScenesSection(project),
|
|
5769
|
-
'TEXT
|
|
5872
|
+
'TEXT RULES:',
|
|
5770
5873
|
'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.',
|
|
5771
5874
|
'Visible text listed on a scene belongs only in that scene; repeat visible text only on scenes that list it.',
|
|
5772
5875
|
...storyboardRequiredVisibleText(project).map(formatStoryboardRequiredVisibleTextLine),
|
|
@@ -5785,27 +5888,28 @@ export function ensureCompiledVideoStoryboardPromptPreservesSourceBrief(compiled
|
|
|
5785
5888
|
export function lintStoryboardImagePrompt(prompt, layout, project) {
|
|
5786
5889
|
const errors = [];
|
|
5787
5890
|
const warnings = [];
|
|
5891
|
+
const hasConsolidatedLayoutContract = /LAYOUT CONTRACT:/i.test(prompt);
|
|
5788
5892
|
if (!/CREATE:/i.test(prompt))
|
|
5789
5893
|
errors.push('missing CREATE section');
|
|
5790
|
-
if (!/COUNT \/ GRID CONTRACT:/i.test(prompt))
|
|
5894
|
+
if (!hasConsolidatedLayoutContract && !/COUNT \/ GRID CONTRACT:/i.test(prompt))
|
|
5791
5895
|
errors.push('missing COUNT / GRID CONTRACT section');
|
|
5792
5896
|
if (!/REFERENCE IMAGES:/i.test(prompt))
|
|
5793
5897
|
errors.push('missing REFERENCE IMAGES section');
|
|
5794
|
-
if (!/CANVAS \/ LAYOUT:/i.test(prompt))
|
|
5898
|
+
if (!hasConsolidatedLayoutContract && !/CANVAS \/ LAYOUT:/i.test(prompt))
|
|
5795
5899
|
errors.push('missing CANVAS / LAYOUT section');
|
|
5796
|
-
if (!/FRAME GEOMETRY:/i.test(prompt))
|
|
5900
|
+
if (!hasConsolidatedLayoutContract && !/FRAME GEOMETRY:/i.test(prompt))
|
|
5797
5901
|
errors.push('missing FRAME GEOMETRY section');
|
|
5798
5902
|
const cellOrientation = parseAspectRatioOrientation(layout.cellAspectRatio);
|
|
5799
|
-
if (cellOrientation === 'portrait' && !/PORTRAIT FRAME GEOMETRY:/i.test(prompt)) {
|
|
5903
|
+
if (!hasConsolidatedLayoutContract && cellOrientation === 'portrait' && !/PORTRAIT FRAME GEOMETRY:/i.test(prompt)) {
|
|
5800
5904
|
errors.push('missing PORTRAIT FRAME GEOMETRY section');
|
|
5801
5905
|
}
|
|
5802
|
-
if (cellOrientation === 'landscape' && !/LANDSCAPE FRAME GEOMETRY:/i.test(prompt)) {
|
|
5906
|
+
if (!hasConsolidatedLayoutContract && cellOrientation === 'landscape' && !/LANDSCAPE FRAME GEOMETRY:/i.test(prompt)) {
|
|
5803
5907
|
errors.push('missing LANDSCAPE FRAME GEOMETRY section');
|
|
5804
5908
|
}
|
|
5805
|
-
if (cellOrientation === 'square' && !/SQUARE FRAME GEOMETRY:/i.test(prompt)) {
|
|
5909
|
+
if (!hasConsolidatedLayoutContract && cellOrientation === 'square' && !/SQUARE FRAME GEOMETRY:/i.test(prompt)) {
|
|
5806
5910
|
errors.push('missing SQUARE FRAME GEOMETRY section');
|
|
5807
5911
|
}
|
|
5808
|
-
if (!/TEXT RENDERING:/i.test(prompt))
|
|
5912
|
+
if (!/(?:TEXT RENDERING|TEXT RULES):/i.test(prompt))
|
|
5809
5913
|
errors.push('missing TEXT RENDERING section');
|
|
5810
5914
|
if (!prompt.includes(`Overall storyboard canvas aspect ratio: ${layout.boardAspectRatio}`)
|
|
5811
5915
|
&& !prompt.includes(`(${layout.boardAspectRatio})`)) {
|
|
@@ -5876,7 +5980,7 @@ function inferCompiledStoryboardPromptAspectRatio(prompt, labelPattern) {
|
|
|
5876
5980
|
return normalizeAspectRatio(match?.[1]);
|
|
5877
5981
|
}
|
|
5878
5982
|
function extractCompiledStoryboardScenesBlock(prompt) {
|
|
5879
|
-
const match = prompt.match(/\nSCENES:\s*\n([\s\S]*?)(?:\nTEXT RENDERING:|\nSOURCE BRIEF TO FOLLOW:|\nNEGATIVE \/ AVOID:|$)/i);
|
|
5983
|
+
const match = prompt.match(/\nSCENES:\s*\n([\s\S]*?)(?:\nTEXT RENDERING:|\nTEXT RULES:|\nSOURCE BRIEF TO FOLLOW:|\nNEGATIVE \/ AVOID:|$)/i);
|
|
5880
5984
|
return match?.[1] ?? '';
|
|
5881
5985
|
}
|
|
5882
5986
|
function parseCompiledStoryboardHeadingTiming(heading) {
|