@clipwright/core 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/__fixtures__/queue-promises.d.ts +5 -84
  3. package/dist/__fixtures__/queue-promises.js +7 -84
  4. package/dist/__fixtures__/queue-promises.js.map +1 -1
  5. package/dist/account-actors.d.ts +25 -25
  6. package/dist/account-actors.js +25 -25
  7. package/dist/account-actors.js.map +1 -1
  8. package/dist/account.d.ts +4 -10
  9. package/dist/account.js +6 -12
  10. package/dist/account.js.map +1 -1
  11. package/dist/actor-bounds.d.ts +4 -4
  12. package/dist/actor-bounds.js +4 -4
  13. package/dist/actor-bounds.js.map +1 -1
  14. package/dist/actor-qualities.d.ts +3 -3
  15. package/dist/actor-qualities.js +3 -3
  16. package/dist/actor-qualities.js.map +1 -1
  17. package/dist/actors.d.ts +8 -8
  18. package/dist/actors.js +9 -9
  19. package/dist/actors.js.map +1 -1
  20. package/dist/api-failure.d.ts +28 -129
  21. package/dist/api-failure.js +52 -225
  22. package/dist/api-failure.js.map +1 -1
  23. package/dist/aspect.d.ts +26 -96
  24. package/dist/aspect.js +32 -108
  25. package/dist/aspect.js.map +1 -1
  26. package/dist/contract-dispositions.d.ts +42 -167
  27. package/dist/contract-dispositions.js +54 -184
  28. package/dist/contract-dispositions.js.map +1 -1
  29. package/dist/endpoints.d.ts +5 -5
  30. package/dist/endpoints.js +3 -3
  31. package/dist/endpoints.js.map +1 -1
  32. package/dist/errors.d.ts +33 -101
  33. package/dist/errors.js +42 -108
  34. package/dist/errors.js.map +1 -1
  35. package/dist/faceless-cut-style.d.ts +115 -0
  36. package/dist/faceless-cut-style.js +109 -0
  37. package/dist/faceless-cut-style.js.map +1 -0
  38. package/dist/faceless-image-qualification.d.ts +97 -0
  39. package/dist/faceless-image-qualification.js +145 -0
  40. package/dist/faceless-image-qualification.js.map +1 -0
  41. package/dist/faceless-short-61.d.ts +83 -0
  42. package/dist/faceless-short-61.js +152 -0
  43. package/dist/faceless-short-61.js.map +1 -0
  44. package/dist/faceless-storyboard-timing.d.ts +40 -0
  45. package/dist/faceless-storyboard-timing.js +97 -0
  46. package/dist/faceless-storyboard-timing.js.map +1 -0
  47. package/dist/faceless-storyboard-v2-plan.d.ts +3 -0
  48. package/dist/faceless-storyboard-v2-plan.js +112 -0
  49. package/dist/faceless-storyboard-v2-plan.js.map +1 -0
  50. package/dist/faceless-storyboard-v2-timing.d.ts +52 -0
  51. package/dist/faceless-storyboard-v2-timing.js +136 -0
  52. package/dist/faceless-storyboard-v2-timing.js.map +1 -0
  53. package/dist/faceless-storyboard-v2.d.ts +44 -0
  54. package/dist/faceless-storyboard-v2.js +43 -0
  55. package/dist/faceless-storyboard-v2.js.map +1 -0
  56. package/dist/faceless-storyboard.d.ts +55 -0
  57. package/dist/faceless-storyboard.js +105 -0
  58. package/dist/faceless-storyboard.js.map +1 -0
  59. package/dist/idempotency.d.ts +8 -44
  60. package/dist/idempotency.js +10 -44
  61. package/dist/idempotency.js.map +1 -1
  62. package/dist/index.js +21 -25
  63. package/dist/index.js.map +1 -1
  64. package/dist/long-form-pricing.d.ts +4 -4
  65. package/dist/long-form-pricing.js +4 -4
  66. package/dist/long-form-pricing.js.map +1 -1
  67. package/dist/long-form-profile.d.ts +2 -2
  68. package/dist/long-form-profile.js +2 -2
  69. package/dist/long-form-profile.js.map +1 -1
  70. package/dist/pacing.d.ts +1 -1
  71. package/dist/pacing.js +1 -1
  72. package/dist/pacing.js.map +1 -1
  73. package/dist/public-endpoints.d.ts +9 -11
  74. package/dist/public-endpoints.js +11 -13
  75. package/dist/public-endpoints.js.map +1 -1
  76. package/dist/rate-limit.d.ts +23 -130
  77. package/dist/rate-limit.js +27 -132
  78. package/dist/rate-limit.js.map +1 -1
  79. package/dist/render-backend.d.ts +30 -145
  80. package/dist/render-backend.js +4 -27
  81. package/dist/render-backend.js.map +1 -1
  82. package/dist/run-preflight.d.ts +4 -4
  83. package/dist/run-preflight.js +10 -10
  84. package/dist/run-preflight.js.map +1 -1
  85. package/dist/run-static-warnings.d.ts +8 -8
  86. package/dist/run-static-warnings.js +24 -24
  87. package/dist/run-static-warnings.js.map +1 -1
  88. package/dist/runs.d.ts +18 -55
  89. package/dist/runs.js +26 -94
  90. package/dist/runs.js.map +1 -1
  91. package/dist/script-integrity.d.ts +6 -7
  92. package/dist/script-integrity.js +7 -8
  93. package/dist/script-integrity.js.map +1 -1
  94. package/dist/semantic-inserts.d.ts +4 -4
  95. package/dist/semantic-inserts.js +6 -6
  96. package/dist/semantic-inserts.js.map +1 -1
  97. package/dist/skills.d.ts +33 -107
  98. package/dist/skills.js +69 -160
  99. package/dist/skills.js.map +1 -1
  100. package/dist/tts-backend.d.ts +13 -42
  101. package/dist/tts-backend.js +2 -10
  102. package/dist/tts-backend.js.map +1 -1
  103. package/dist/uploads.d.ts +4 -4
  104. package/dist/uploads.js +4 -4
  105. package/dist/uploads.js.map +1 -1
  106. package/dist/vendor-response.d.ts +17 -80
  107. package/dist/vendor-response.js +19 -67
  108. package/dist/vendor-response.js.map +1 -1
  109. package/dist/voice-admission.d.ts +5 -5
  110. package/dist/voice-admission.js +6 -6
  111. package/dist/voice-admission.js.map +1 -1
  112. package/dist/voice-catalog-data.d.ts +2 -2
  113. package/dist/voice-catalog-data.js +2 -2
  114. package/dist/voice-catalog-data.js.map +1 -1
  115. package/dist/voice-catalog.d.ts +5 -5
  116. package/dist/voice-catalog.js +6 -6
  117. package/dist/voice-catalog.js.map +1 -1
  118. package/dist/voice-listing.d.ts +4 -4
  119. package/dist/voice-listing.js +6 -6
  120. package/dist/voice-listing.js.map +1 -1
  121. package/dist/voices.d.ts +40 -82
  122. package/dist/voices.js +56 -99
  123. package/dist/voices.js.map +1 -1
  124. package/package.json +1 -1
@@ -0,0 +1,152 @@
1
+ import { z } from "zod";
2
+ import { brollPolicy } from "./broll-policy.js";
3
+ import { CANVAS_FPS } from "./media-timebase.js";
4
+ import { speechTextFor, TTS_MODEL_CHAR_CAP, TTS_MODELS } from "./voices.js";
5
+ export const SHORT61_BOUNDARIES = [
6
+ 0, 93, 162, 309, 412, 472, 562, 643, 753, 822, 902, 948,
7
+ 1010, 1081, 1192, 1243, 1316, 1344, 1373, 1410, 1447, 1484, 1522,
8
+ ];
9
+ const id = z.string().trim().min(1);
10
+ const imageAsset = z.strictObject({
11
+ id,
12
+ role: z.enum(["scene", "style", "character"]),
13
+ prompt: z.string().trim().min(1),
14
+ origin: z.enum(["prior_probe", "new"]),
15
+ styleAssetId: id.optional(),
16
+ characterAssetIds: z.array(id).optional(),
17
+ });
18
+ const replacementAttempt = z.strictObject({
19
+ id,
20
+ replacesAssetId: id,
21
+ prompt: z.string().trim().min(1),
22
+ });
23
+ const shot = z.strictObject({
24
+ id,
25
+ mediaType: z.enum(["video", "image"]),
26
+ assetId: id,
27
+ startFrame: z.number().int().nonnegative().safe(),
28
+ endFrame: z.number().int().positive().safe(),
29
+ motion: z.enum(["PAN_UP", "PAN_DOWN"]).optional(),
30
+ });
31
+ const captionPhrase = z.strictObject({
32
+ wordStart: z.number().int().nonnegative(),
33
+ wordEnd: z.number().int().positive(),
34
+ accentWord: z.number().int().nonnegative(),
35
+ });
36
+ const plan = z.strictObject({
37
+ version: z.literal("faceless_short_61_v1"),
38
+ pricingVersion: z.literal("faceless-image-v1-shadow"),
39
+ budgetVersion: z.literal("short61-five-usd-provisional-v1"),
40
+ aspectRatio: z.literal("9:16"),
41
+ resolution: z.literal("1080p"),
42
+ brollPolicy,
43
+ script: z.string().trim().min(1),
44
+ voice: z.strictObject({ model: z.enum(TTS_MODELS), voiceId: id }),
45
+ styleAssetId: id,
46
+ characterAssetId: id,
47
+ imageAssets: z.array(imageAsset),
48
+ replacementAttempts: z.array(replacementAttempt),
49
+ openingVideo: z.strictObject({ assetId: id, prompt: z.string().trim().min(1) }),
50
+ shots: z.array(shot).length(22),
51
+ captionPhrases: z.array(captionPhrase).min(1),
52
+ });
53
+ export function parseShort61Plan(input) {
54
+ const parsed = plan.parse(input);
55
+ if (speechTextFor(parsed.script, parsed.voice.model).length > TTS_MODEL_CHAR_CAP[parsed.voice.model]) {
56
+ throw new Error("short61 script exceeds the selected speech model limit");
57
+ }
58
+ if (parsed.brollPolicy !== "anyone")
59
+ throw new Error("short61 Mira scenes require the anyone policy");
60
+ const assets = new Map();
61
+ for (const asset of parsed.imageAssets) {
62
+ if (assets.has(asset.id) || asset.id === parsed.openingVideo.assetId)
63
+ throw new Error(`duplicate short61 asset ID: ${asset.id}`);
64
+ assets.set(asset.id, asset);
65
+ }
66
+ if (assets.get(parsed.styleAssetId)?.role !== "style" || assets.get(parsed.characterAssetId)?.role !== "character") {
67
+ throw new Error("short61 style and character references have wrong roles");
68
+ }
69
+ if (parsed.styleAssetId === parsed.characterAssetId)
70
+ throw new Error("short61 reference IDs must differ");
71
+ const sceneAssets = parsed.imageAssets.filter(asset => asset.role === "scene");
72
+ if (sceneAssets.length !== 21 || parsed.imageAssets.length !== 23) {
73
+ throw new Error("short61 requires 21 scenes and two shared references");
74
+ }
75
+ for (const asset of parsed.imageAssets) {
76
+ if (asset.role !== "scene") {
77
+ if (asset.styleAssetId !== undefined || asset.characterAssetIds !== undefined) {
78
+ throw new Error(`short61 references belong only on scene assets: ${asset.id}`);
79
+ }
80
+ continue;
81
+ }
82
+ if (asset.styleAssetId !== parsed.styleAssetId ||
83
+ asset.characterAssetIds?.length !== 1 ||
84
+ asset.characterAssetIds[0] !== parsed.characterAssetId) {
85
+ throw new Error(`short61 scene ${asset.id} must use shared style and Mira references`);
86
+ }
87
+ }
88
+ const usedImages = new Set();
89
+ const shotIds = new Set();
90
+ for (let i = 0; i < parsed.shots.length; i++) {
91
+ const current = parsed.shots[i];
92
+ if (shotIds.has(current.id))
93
+ throw new Error(`duplicate short61 shot ID: ${current.id}`);
94
+ shotIds.add(current.id);
95
+ if (current.id !== `shot_${i + 1}` ||
96
+ current.startFrame !== SHORT61_BOUNDARIES[i] ||
97
+ current.endFrame !== SHORT61_BOUNDARIES[i + 1] ||
98
+ current.mediaType !== (i === 0 ? "video" : "image") ||
99
+ (i === 0 && (current.assetId !== parsed.openingVideo.assetId || current.motion !== undefined)) ||
100
+ (i > 0 && current.motion !== (i % 2 === 1 ? "PAN_UP" : "PAN_DOWN"))) {
101
+ throw new Error(`short61 shot ${i + 1} differs from the frozen frame plan`);
102
+ }
103
+ if (i > 0) {
104
+ if (assets.get(current.assetId)?.role !== "scene" || usedImages.has(current.assetId)) {
105
+ throw new Error(`short61 shot ${i + 1} needs a distinct scene image`);
106
+ }
107
+ usedImages.add(current.assetId);
108
+ }
109
+ }
110
+ for (const asset of sceneAssets) {
111
+ if (!usedImages.has(asset.id))
112
+ throw new Error(`unreferenced short61 scene image: ${asset.id}`);
113
+ }
114
+ const attemptIds = new Set(assets.keys());
115
+ attemptIds.add(parsed.openingVideo.assetId);
116
+ for (const attempt of parsed.replacementAttempts) {
117
+ if (attemptIds.has(attempt.id))
118
+ throw new Error(`duplicate short61 replacement ID: ${attempt.id}`);
119
+ attemptIds.add(attempt.id);
120
+ if (assets.get(attempt.replacesAssetId)?.role !== "scene") {
121
+ throw new Error(`short61 replacement target is not a scene: ${attempt.replacesAssetId}`);
122
+ }
123
+ }
124
+ const outputFrames = SHORT61_BOUNDARIES.at(-1);
125
+ const imageAllowance = Math.floor(24 * outputFrames / (60 * CANVAS_FPS));
126
+ if (parsed.imageAssets.length + parsed.replacementAttempts.length > imageAllowance) {
127
+ throw new Error("short61 image allowance exceeded");
128
+ }
129
+ for (const phrase of parsed.captionPhrases) {
130
+ if (phrase.wordEnd <= phrase.wordStart || phrase.accentWord < phrase.wordStart || phrase.accentWord >= phrase.wordEnd) {
131
+ throw new Error("short61 caption phrase indices are invalid");
132
+ }
133
+ }
134
+ return parsed;
135
+ }
136
+ export function reportShort61(input) {
137
+ const parsed = parseShort61Plan(input);
138
+ const baseGeneratedImages = parsed.imageAssets.length;
139
+ const replacementAttempts = parsed.replacementAttempts.length;
140
+ return {
141
+ outputFrames: 1522,
142
+ imageAllowance: 24,
143
+ baseGeneratedImages,
144
+ replacementAttempts,
145
+ retryHeadroom: 24 - baseGeneratedImages - replacementAttempts,
146
+ imageOnlyComparisonCredits: 200,
147
+ videoPriceStatus: "unpriced",
148
+ maxPaidAttempts: 0,
149
+ maxUsdSpend: 0,
150
+ };
151
+ }
152
+ //# sourceMappingURL=faceless-short-61.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"faceless-short-61.js","sourceRoot":"","sources":["../src/faceless-short-61.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import { type StoryboardProposal } from "./faceless-storyboard.js";
2
+ type WordTiming = {
3
+ word: string;
4
+ startSec: number;
5
+ endSec: number;
6
+ };
7
+ type PhraseRange = {
8
+ wordStart: number;
9
+ wordEnd: number;
10
+ accentWord: number;
11
+ };
12
+ type Cut = {
13
+ afterWord: number;
14
+ frame: number;
15
+ reason: string;
16
+ };
17
+ export type StoryboardFramePlan = {
18
+ version: "faceless_storyboard_61_v1";
19
+ outputFrames: 1522;
20
+ script: string;
21
+ captionPhrases: PhraseRange[];
22
+ narrationStartFrame: number;
23
+ shots: Array<{
24
+ id: string;
25
+ mediaType: "video" | "image";
26
+ startFrame: number;
27
+ endFrame: number;
28
+ motion?: "PAN_UP" | "PAN_DOWN";
29
+ beatIds: string[];
30
+ cutAfterWord?: number;
31
+ cutReason?: string;
32
+ }>;
33
+ rejectedCandidates: Cut[];
34
+ imageBudget: {
35
+ allowance: 24;
36
+ remaining: number;
37
+ };
38
+ };
39
+ export declare function planStoryboardFrames(input: StoryboardProposal, words: readonly WordTiming[], narrationStartFrame: number): StoryboardFramePlan;
40
+ export {};
@@ -0,0 +1,97 @@
1
+ import { assertStoryboardWords, parseStoryboardProposal, storyboardImageBudget } from "./faceless-storyboard.js";
2
+ const priority = { place: 4, reveal: 4, action: 3, emphasis: 1 };
3
+ function better(candidate, current) {
4
+ return !current || candidate.score > current.score ||
5
+ (candidate.score === current.score && candidate.penalty < current.penalty);
6
+ }
7
+ export function planStoryboardFrames(input, words, narrationStartFrame) {
8
+ const proposal = parseStoryboardProposal(input);
9
+ assertStoryboardWords(proposal.script, words);
10
+ if (!Number.isSafeInteger(narrationStartFrame) || narrationStartFrame < 0 || narrationStartFrame >= 1522) {
11
+ throw new Error("invalid narration start frame");
12
+ }
13
+ const speechEnd = narrationStartFrame / 25 + words[words.length - 1].endSec;
14
+ if (speechEnd > 1522 / 25)
15
+ throw new Error("narration exceeds output duration");
16
+ if (1522 / 25 - speechEnd > 0.5 + 1e-9)
17
+ throw new Error("narration leaves more than 0.5 seconds tail");
18
+ const candidates = proposal.beats.slice(1).map((beat, i) => {
19
+ const afterWord = beat.wordStart - 1;
20
+ const previous = words[afterWord];
21
+ const next = words[afterWord + 1];
22
+ const edgeSec = next.startSec > previous.endSec ? (previous.endSec + next.startSec) / 2 : next.startSec;
23
+ const frame = narrationStartFrame + Math.round(edgeSec * 25);
24
+ if (!Number.isSafeInteger(frame) || frame <= 0 || frame >= 1522)
25
+ throw new Error("cut outside output");
26
+ const time = (frame - narrationStartFrame) / 25;
27
+ const inside = words.some(w => time - w.startSec > 1 / 25 + 1e-9 && w.endSec - time > 1 / 25 + 1e-9);
28
+ return { afterWord, frame, reason: beat.reason, beatIndex: i + 1, priority: priority[beat.reason],
29
+ ...(inside ? { rejection: "cut more than one frame inside timed word" } : {}) };
30
+ });
31
+ const opener = candidates[0];
32
+ if (opener.rejection || opener.frame < 75 || opener.frame > 125) {
33
+ throw new Error("no feasible storyboard: mandatory VIDEO boundary violates timing");
34
+ }
35
+ const nodes = [...candidates, { frame: 1522, beatIndex: proposal.beats.length, priority: 0, afterWord: words.length - 1, reason: "output end" }];
36
+ const states = nodes.map(() => Array(20));
37
+ states[0][0] = { score: opener.priority, penalty: 0, previous: -1 };
38
+ for (let end = 1; end < nodes.length; end++) {
39
+ const destination = nodes[end];
40
+ if ("rejection" in destination && destination.rejection)
41
+ continue;
42
+ for (let start = 0; start < end; start++) {
43
+ const duration = destination.frame - nodes[start].frame;
44
+ if (duration < 25 || duration > 150)
45
+ continue;
46
+ for (let count = 1; count <= 19; count++) {
47
+ const prior = states[start][count - 1];
48
+ if (!prior)
49
+ continue;
50
+ const state = { score: prior.score + destination.priority,
51
+ penalty: prior.penalty + Math.abs(duration - 68.75), previous: start };
52
+ if (better(state, states[end][count]))
53
+ states[end][count] = state;
54
+ }
55
+ }
56
+ }
57
+ const last = nodes.length - 1;
58
+ let imageCount = 0;
59
+ let best;
60
+ for (let count = 1; count <= 19; count++) {
61
+ const state = states[last][count];
62
+ if (state && better(state, best)) {
63
+ best = state;
64
+ imageCount = count;
65
+ }
66
+ }
67
+ if (!best)
68
+ throw new Error("no feasible storyboard within scene duration and image budget limits");
69
+ const chosen = [last];
70
+ let cursor = last;
71
+ for (let count = imageCount; count > 0; count--) {
72
+ cursor = states[cursor][count].previous;
73
+ chosen.push(cursor);
74
+ }
75
+ chosen.reverse();
76
+ const selected = new Set(chosen);
77
+ const shots = [];
78
+ let startFrame = 0;
79
+ let beatStart = 0;
80
+ for (const [shotIndex, nodeIndex] of chosen.entries()) {
81
+ const node = nodes[nodeIndex];
82
+ shots.push({ id: proposal.beats[beatStart].id, mediaType: shotIndex === 0 ? "video" : "image",
83
+ startFrame, endFrame: node.frame,
84
+ ...(shotIndex > 0 ? { motion: shotIndex % 2 === 1 ? "PAN_UP" : "PAN_DOWN" } : {}),
85
+ beatIds: proposal.beats.slice(beatStart, node.beatIndex).map(b => b.id),
86
+ ...(nodeIndex < last ? { cutAfterWord: node.afterWord, cutReason: node.reason } : {}), });
87
+ startFrame = node.frame;
88
+ beatStart = node.beatIndex;
89
+ }
90
+ return { version: proposal.version, outputFrames: 1522, script: proposal.script,
91
+ captionPhrases: proposal.captionPhrases, narrationStartFrame, shots,
92
+ rejectedCandidates: candidates.flatMap((candidate, i) => selected.has(i) ? [] : [{
93
+ afterWord: candidate.afterWord, frame: candidate.frame,
94
+ reason: candidate.rejection ?? `unselected ${candidate.reason} boundary under timing and image budget constraints`,
95
+ }]), imageBudget: storyboardImageBudget(imageCount, 2) };
96
+ }
97
+ //# sourceMappingURL=faceless-storyboard-timing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"faceless-storyboard-timing.js","sourceRoot":"","sources":["../src/faceless-storyboard-timing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { StoryboardV2FramePlan } from "./faceless-storyboard-v2-timing.js";
2
+ import type { WordTiming } from "./tts-backend.js";
3
+ export declare function parseStoryboardV2Plan(input: unknown, words: readonly WordTiming[]): StoryboardV2FramePlan;
@@ -0,0 +1,112 @@
1
+ import { assertStoryboardWords } from "./faceless-storyboard.js";
2
+ const phases = ["setup", "development", "climax", "resolution"];
3
+ const reasons = ["action", "place", "reveal", "emphasis"];
4
+ function record(value, keys) {
5
+ if (!value || typeof value !== "object" || Array.isArray(value) ||
6
+ Object.keys(value).some(key => !keys.includes(key)))
7
+ throw new Error("invalid storyboard v2 object or unknown fields");
8
+ return value;
9
+ }
10
+ function uint(value) { return Number.isSafeInteger(value) && value >= 0; }
11
+ function finite(value) { return typeof value === "number" && Number.isFinite(value) && value >= 0; }
12
+ function label(value) { return typeof value === "string" && value.trim().length > 0; }
13
+ function score(value) {
14
+ const s = record(value, ["semanticValue", "cutCost", "pacePenalty", "net"]);
15
+ if (!finite(s.semanticValue) || !finite(s.cutCost) || !finite(s.pacePenalty) ||
16
+ typeof s.net !== "number" || !Number.isFinite(s.net) ||
17
+ Math.abs(s.net - (s.semanticValue - s.cutCost - s.pacePenalty)) > 1e-8)
18
+ throw new Error("invalid storyboard v2 score");
19
+ }
20
+ export function parseStoryboardV2Plan(input, words) {
21
+ const raw = record(input, ["version", "outputFrames", "script", "narrationStartFrame", "captionPhrases", "shots", "rejectedCandidates", "scoreBreakdown"]);
22
+ if (raw.version !== "faceless_storyboard_61_v2" || raw.outputFrames !== 1522 || !label(raw.script) ||
23
+ !uint(raw.narrationStartFrame) || raw.narrationStartFrame > 50 ||
24
+ !Array.isArray(raw.shots) || raw.shots.length < 2 ||
25
+ !Array.isArray(raw.captionPhrases) || !raw.captionPhrases.length || !Array.isArray(raw.rejectedCandidates)) {
26
+ throw new Error("invalid storyboard v2 frame plan");
27
+ }
28
+ assertStoryboardWords(raw.script, words);
29
+ if (raw.narrationStartFrame + words.at(-1).endSec * 25 > 1522 + 1e-9 ||
30
+ 1522 - raw.narrationStartFrame - words.at(-1).endSec * 25 > 12.5 + 1e-9)
31
+ throw new Error("invalid storyboard v2 narration tail");
32
+ let captionEnd = 0;
33
+ for (const phrase of raw.captionPhrases) {
34
+ const p = record(phrase, ["wordStart", "wordEnd", "accentWord"]);
35
+ if (!uint(p.wordStart) || !uint(p.wordEnd) || !uint(p.accentWord) || p.wordStart !== captionEnd || p.wordStart >= p.wordEnd ||
36
+ p.wordEnd > words.length || p.accentWord < p.wordStart || p.accentWord >= p.wordEnd)
37
+ throw new Error("invalid storyboard v2 caption");
38
+ captionEnd = p.wordEnd;
39
+ }
40
+ if (captionEnd !== words.length)
41
+ throw new Error("incomplete storyboard v2 captions");
42
+ const ids = new Set();
43
+ const beatIds = new Set();
44
+ let end = 0;
45
+ let previousWord = -1;
46
+ let previousPhase = -1;
47
+ let semanticValue = 0, cutCost = 0, pacePenalty = 0, total = 0, cuts = 0;
48
+ for (const [i, value] of raw.shots.entries()) {
49
+ const shot = record(value, ["id", "mediaType", "startFrame", "endFrame", "beatIds", "phase", "mergedPhases", "motion", "panExceptionReason", "cutAfterWord", "cutReason", "score"]);
50
+ if (!label(shot.id) || ids.has(shot.id))
51
+ throw new Error("duplicate or invalid storyboard v2 shot ID");
52
+ ids.add(shot.id);
53
+ if (!uint(shot.startFrame) || !uint(shot.endFrame) || shot.startFrame !== end || shot.endFrame > 1522 ||
54
+ shot.endFrame - shot.startFrame < (i ? 25 : 50) || shot.endFrame - shot.startFrame > (i ? 150 : 125) ||
55
+ shot.mediaType !== (i ? "image" : "video") || (i ? !(typeof shot.motion === "string" && ["PAN_UP", "PAN_DOWN"].includes(shot.motion)) : shot.motion !== undefined) ||
56
+ !Array.isArray(shot.beatIds) || !shot.beatIds.length || !shot.beatIds.every(label) ||
57
+ !(typeof shot.phase === "string" && phases.includes(shot.phase)) || phases.indexOf(shot.phase) < previousPhase ||
58
+ (shot.mergedPhases !== undefined && (!Array.isArray(shot.mergedPhases) || !shot.mergedPhases.length || !shot.mergedPhases.every(p => phases.includes(p)))) ||
59
+ (shot.panExceptionReason !== undefined && (!i || !label(shot.panExceptionReason))))
60
+ throw new Error("invalid storyboard v2 shot coverage, duration, or fields");
61
+ for (const beat of shot.beatIds) {
62
+ if (beatIds.has(beat))
63
+ throw new Error("duplicate storyboard v2 beat ID");
64
+ beatIds.add(beat);
65
+ }
66
+ previousPhase = phases.indexOf(shot.phase);
67
+ end = shot.endFrame;
68
+ score(shot.score);
69
+ const s = shot.score;
70
+ semanticValue += s.semanticValue;
71
+ cutCost += s.cutCost;
72
+ pacePenalty += s.pacePenalty;
73
+ total += s.net;
74
+ if (i < raw.shots.length - 1) {
75
+ if (!uint(shot.cutAfterWord) || shot.cutAfterWord <= previousWord || shot.cutAfterWord >= words.length - 1 ||
76
+ !(typeof shot.cutReason === "string" && reasons.includes(shot.cutReason)))
77
+ throw new Error("invalid storyboard v2 cut word or reason");
78
+ const previous = words[shot.cutAfterWord];
79
+ const next = words[shot.cutAfterWord + 1];
80
+ const edgeSec = next.startSec > previous.endSec ? (previous.endSec + next.startSec) / 2 : next.startSec;
81
+ const frame = raw.narrationStartFrame + Math.round(edgeSec * 25);
82
+ const sec = (shot.endFrame - raw.narrationStartFrame) / 25;
83
+ if (shot.endFrame !== frame || words.some(w => sec - w.startSec > 1 / 25 + 1e-9 && w.endSec - sec > 1 / 25 + 1e-9)) {
84
+ throw new Error("storyboard v2 cut lies mid-word or differs from measured boundary");
85
+ }
86
+ previousWord = shot.cutAfterWord;
87
+ cuts++;
88
+ }
89
+ else if (shot.cutAfterWord !== undefined || shot.cutReason !== undefined)
90
+ throw new Error("final storyboard v2 shot cannot end in a cut");
91
+ }
92
+ if (end !== 1522)
93
+ throw new Error("incomplete storyboard v2 output coverage");
94
+ for (const value of raw.rejectedCandidates) {
95
+ const candidate = record(value, ["afterWord", "frame", "beatId", "reason", "semanticValue", "cutCost", "pacePenalty", "net"]);
96
+ if (!uint(candidate.afterWord) || candidate.afterWord >= words.length - 1 || !uint(candidate.frame) || candidate.frame > 1522 ||
97
+ !label(candidate.beatId) || !label(candidate.reason) || !finite(candidate.semanticValue) || !finite(candidate.cutCost) ||
98
+ (candidate.pacePenalty !== null && !finite(candidate.pacePenalty)) ||
99
+ (candidate.net !== null && (typeof candidate.net !== "number" || !Number.isFinite(candidate.net))) ||
100
+ ((candidate.pacePenalty === null) !== (candidate.net === null)))
101
+ throw new Error("invalid storyboard v2 rejected candidate");
102
+ }
103
+ const summary = record(raw.scoreBreakdown, ["semanticValue", "cutCost", "pacePenalty", "total", "cuts"]);
104
+ if (!finite(summary.semanticValue) || !finite(summary.cutCost) || !finite(summary.pacePenalty) ||
105
+ typeof summary.total !== "number" || !Number.isFinite(summary.total) || !uint(summary.cuts) ||
106
+ Math.abs(summary.semanticValue - semanticValue) > 1e-8 || Math.abs(summary.cutCost - cutCost) > 1e-8 ||
107
+ Math.abs(summary.pacePenalty - pacePenalty) > 1e-8 || Math.abs(summary.total - total) > 1e-8 || summary.cuts !== cuts) {
108
+ throw new Error("invalid storyboard v2 score breakdown");
109
+ }
110
+ return raw;
111
+ }
112
+ //# sourceMappingURL=faceless-storyboard-v2-plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"faceless-storyboard-v2-plan.js","sourceRoot":"","sources":["../src/faceless-storyboard-v2-plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import { type StoryboardV2Proposal } from "./faceless-storyboard-v2.js";
2
+ import type { CutStyleProfile } from "./faceless-cut-style.js";
3
+ import type { WordTiming } from "./tts-backend.js";
4
+ type Phase = StoryboardV2Proposal["beats"][number]["phase"];
5
+ type Score = {
6
+ semanticValue: number;
7
+ cutCost: number;
8
+ pacePenalty: number;
9
+ net: number;
10
+ };
11
+ export type StoryboardV2FramePlan = {
12
+ version: "faceless_storyboard_61_v2";
13
+ outputFrames: 1522;
14
+ script: string;
15
+ captionPhrases: StoryboardV2Proposal["captionPhrases"];
16
+ narrationStartFrame: number;
17
+ shots: Array<{
18
+ id: string;
19
+ mediaType: "video" | "image";
20
+ startFrame: number;
21
+ endFrame: number;
22
+ beatIds: string[];
23
+ phase: Phase;
24
+ mergedPhases?: Phase[];
25
+ motion?: "PAN_UP" | "PAN_DOWN";
26
+ panExceptionReason?: string;
27
+ cutAfterWord?: number;
28
+ cutReason?: string;
29
+ score: Score;
30
+ }>;
31
+ /** Rejected scores are local incoming-edge estimates, not alternate complete-path scores.
32
+ * Null pacePenalty/net means no feasible incoming edge was reachable. */
33
+ rejectedCandidates: Array<{
34
+ afterWord: number;
35
+ frame: number;
36
+ beatId: string;
37
+ reason: string;
38
+ semanticValue: number;
39
+ cutCost: number;
40
+ pacePenalty: number | null;
41
+ net: number | null;
42
+ }>;
43
+ scoreBreakdown: {
44
+ semanticValue: number;
45
+ cutCost: number;
46
+ pacePenalty: number;
47
+ total: number;
48
+ cuts: number;
49
+ };
50
+ };
51
+ export declare function planStoryboardV2(input: StoryboardV2Proposal, words: readonly WordTiming[], narrationStartFrame: number, style: CutStyleProfile): StoryboardV2FramePlan;
52
+ export {};
@@ -0,0 +1,136 @@
1
+ import { assertStoryboardWords } from "./faceless-storyboard.js";
2
+ import { parseStoryboardV2 } from "./faceless-storyboard-v2.js";
3
+ const semantic = { place: 4, reveal: 4, action: 3, emphasis: 1 };
4
+ const CUT_COST = 2;
5
+ const OUTPUT_FRAMES = 1522;
6
+ const FPS = 25;
7
+ function better(a, b) {
8
+ if (!b)
9
+ return true;
10
+ if (Math.abs(a.total - b.total) > 1e-9)
11
+ return a.total > b.total;
12
+ if (a.cuts !== b.cuts)
13
+ return a.cuts < b.cuts;
14
+ for (let i = 0; i < a.path.length; i++) {
15
+ if (a.path[i] !== b.path[i])
16
+ return a.path[i] < b.path[i];
17
+ }
18
+ return false;
19
+ }
20
+ export function planStoryboardV2(input, words, narrationStartFrame, style) {
21
+ const proposal = parseStoryboardV2(input);
22
+ assertStoryboardWords(proposal.script, words);
23
+ if (!Number.isSafeInteger(narrationStartFrame) || narrationStartFrame < 0 || narrationStartFrame > 50) {
24
+ throw new Error("invalid narration start frame");
25
+ }
26
+ const speechEndFrame = narrationStartFrame + words.at(-1).endSec * FPS;
27
+ if (speechEndFrame > OUTPUT_FRAMES + 1e-9)
28
+ throw new Error("narration exceeds output duration");
29
+ if (OUTPUT_FRAMES - speechEndFrame > 12.5 + 1e-9)
30
+ throw new Error("narration leaves more than 0.5 seconds tail");
31
+ const candidates = proposal.beats.slice(1).map((beat, i) => {
32
+ const afterWord = beat.wordStart - 1;
33
+ const previous = words[afterWord];
34
+ const next = words[afterWord + 1];
35
+ const edgeSec = next.startSec > previous.endSec ? (previous.endSec + next.startSec) / 2 : next.startSec;
36
+ const frame = narrationStartFrame + Math.round(edgeSec * FPS);
37
+ const time = (frame - narrationStartFrame) / FPS;
38
+ const inside = words.some(w => time - w.startSec > 1 / FPS + 1e-9 && w.endSec - time > 1 / FPS + 1e-9);
39
+ return { beatIndex: i + 1, afterWord, frame, reason: beat.reason,
40
+ ...(frame <= 0 || frame >= OUTPUT_FRAMES ? { invalidReason: "candidate lies outside output frames" } : {}),
41
+ ...(inside ? { invalidReason: "candidate lies more than one frame inside a timed word" } : {}) };
42
+ });
43
+ const opener = candidates[0];
44
+ if (opener.invalidReason || opener.frame < 50 || opener.frame > 125) {
45
+ throw new Error(`no feasible storyboard: mandatory VIDEO boundary violates timing${opener.invalidReason ? ` (${opener.invalidReason})` : ""}`);
46
+ }
47
+ const nodes = [...candidates, { beatIndex: proposal.beats.length, afterWord: words.length - 1, frame: OUTPUT_FRAMES, reason: "output end" }];
48
+ const final = nodes.length - 1;
49
+ const states = Array(nodes.length);
50
+ const openerValue = semantic[proposal.beats[1].reason];
51
+ states[0] = { total: openerValue - CUT_COST, semantic: openerValue, cutCost: CUT_COST, pacePenalty: 0,
52
+ cuts: 1, path: [0], previous: -1, edge: { semanticValue: openerValue, cutCost: CUT_COST, pacePenalty: 0, net: openerValue - CUT_COST } };
53
+ function pacePenalty(start, end) {
54
+ const phase = proposal.beats[nodes[start].beatIndex].phase;
55
+ const phaseRange = style.pacing.by_phase[phase];
56
+ const range = phaseRange && phaseRange.n > 0 ? phaseRange : style.pacing.global_image_frames;
57
+ const duration = nodes[end].frame - nodes[start].frame;
58
+ const distance = duration < range.p25 ? range.p25 - duration : duration > range.p75 ? duration - range.p75 : 0;
59
+ return 0.25 * distance / 25;
60
+ }
61
+ for (let end = 1; end <= final; end++) {
62
+ const destination = nodes[end];
63
+ if (destination.invalidReason)
64
+ continue;
65
+ const value = end === final ? 0 : semantic[proposal.beats[destination.beatIndex].reason];
66
+ const cost = end === final ? 0 : CUT_COST;
67
+ for (let start = 0; start < end; start++) {
68
+ const prior = states[start];
69
+ if (!prior)
70
+ continue;
71
+ const duration = destination.frame - nodes[start].frame;
72
+ if (duration < 25 || duration > 150)
73
+ continue;
74
+ const penalty = pacePenalty(start, end);
75
+ const edge = { semanticValue: value, cutCost: cost, pacePenalty: penalty, net: value - cost - penalty };
76
+ const next = { total: prior.total + edge.net, semantic: prior.semantic + value,
77
+ cutCost: prior.cutCost + cost, pacePenalty: prior.pacePenalty + penalty,
78
+ cuts: prior.cuts + (end === final ? 0 : 1), path: [...prior.path, end], previous: start, edge };
79
+ if (better(next, states[end]))
80
+ states[end] = next;
81
+ }
82
+ }
83
+ const best = states[final];
84
+ if (!best)
85
+ throw new Error("no feasible storyboard within VIDEO and IMAGE duration bounds");
86
+ const selected = new Set(best.path);
87
+ const shots = [];
88
+ let startFrame = 0;
89
+ let beatStart = 0;
90
+ for (const [shotIndex, nodeIndex] of best.path.entries()) {
91
+ const node = nodes[nodeIndex];
92
+ const current = states[nodeIndex];
93
+ const included = proposal.beats.slice(beatStart, node.beatIndex);
94
+ const phases = [...new Set(included.map(b => b.phase))];
95
+ const holdMotion = style.motion.pan_transition_n > 0 && style.motion.pan_flip_probability !== null && style.motion.pan_flip_probability < 0.5;
96
+ const motion = shotIndex === 0 ? undefined : holdMotion ? "PAN_UP" : shotIndex % 2 === 1 ? "PAN_UP" : "PAN_DOWN";
97
+ shots.push({ id: included[0].id, mediaType: shotIndex === 0 ? "video" : "image", startFrame, endFrame: node.frame,
98
+ beatIds: included.map(b => b.id), phase: included[0].phase,
99
+ ...(phases.length > 1 ? { mergedPhases: phases } : {}),
100
+ ...(motion ? { motion } : {}),
101
+ ...(holdMotion && shotIndex > 1 ? { panExceptionReason: "style profile favors holding PAN direction" } : {}),
102
+ ...(nodeIndex < final ? { cutAfterWord: node.afterWord, cutReason: node.reason } : {}),
103
+ score: current.edge });
104
+ startFrame = node.frame;
105
+ beatStart = node.beatIndex;
106
+ }
107
+ const rejectedCandidates = candidates.flatMap((candidate, index) => {
108
+ if (selected.has(index))
109
+ return [];
110
+ let possiblePenalty = null;
111
+ if (!candidate.invalidReason) {
112
+ for (let start = 0; start < index; start++) {
113
+ if (!states[start])
114
+ continue;
115
+ const duration = candidate.frame - nodes[start].frame;
116
+ if (duration < 25 || duration > 150)
117
+ continue;
118
+ const penalty = pacePenalty(start, index);
119
+ possiblePenalty = possiblePenalty === null ? penalty : Math.min(possiblePenalty, penalty);
120
+ }
121
+ }
122
+ const value = semantic[proposal.beats[candidate.beatIndex].reason];
123
+ const reason = candidate.invalidReason ?? (possiblePenalty === null
124
+ ? "no reachable preceding IMAGE shot within 25–150 frames"
125
+ : value < CUT_COST ? "semantic value does not cover fixed cut cost"
126
+ : "lower total path score or deterministic tie break after IMAGE duration and pace costs");
127
+ return [{ afterWord: candidate.afterWord, frame: candidate.frame, beatId: proposal.beats[candidate.beatIndex].id,
128
+ reason, semanticValue: value, cutCost: CUT_COST, pacePenalty: possiblePenalty,
129
+ net: possiblePenalty === null ? null : value - CUT_COST - possiblePenalty }];
130
+ });
131
+ return { version: proposal.version, outputFrames: OUTPUT_FRAMES, script: proposal.script,
132
+ captionPhrases: proposal.captionPhrases, narrationStartFrame, shots, rejectedCandidates,
133
+ scoreBreakdown: { semanticValue: best.semantic, cutCost: best.cutCost,
134
+ pacePenalty: best.pacePenalty, total: best.total, cuts: best.cuts } };
135
+ }
136
+ //# sourceMappingURL=faceless-storyboard-v2-timing.js.map