@koda-sl/baker-cli 0.116.0 → 0.118.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.
- package/README.md +10 -2
- package/dist/{chunk-ZWMMCLJ4.js → chunk-MF34WJ7M.js} +75 -11
- package/dist/chunk-MF34WJ7M.js.map +1 -0
- package/dist/cli.js +425 -84
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-ZWMMCLJ4.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -9,10 +9,11 @@ import {
|
|
|
9
9
|
createEngineFromEnv,
|
|
10
10
|
defaultRegistry,
|
|
11
11
|
describeFailureReason,
|
|
12
|
+
elementMentionKeywords,
|
|
12
13
|
generateCatalog,
|
|
13
14
|
resolveConcurrency,
|
|
14
15
|
validateCanvasDeep
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-MF34WJ7M.js";
|
|
16
17
|
|
|
17
18
|
// src/cli.ts
|
|
18
19
|
import { defineCommand as defineCommand157, runMain } from "citty";
|
|
@@ -13623,6 +13624,16 @@ function isLikelyOverSegmented(cuts, opts = {}) {
|
|
|
13623
13624
|
const median = gaps.length % 2 ? gaps[mid] : (gaps[mid - 1] + gaps[mid]) / 2;
|
|
13624
13625
|
return median < maxMedianGap;
|
|
13625
13626
|
}
|
|
13627
|
+
function mergeRecheckCuts(firstPass, recheck, isolationS = 0.75) {
|
|
13628
|
+
const kept = [...new Set(recheck)].sort((a, b) => a - b);
|
|
13629
|
+
const extras = [...new Set(firstPass)].filter((c) => kept.every((k) => Math.abs(k - c) >= isolationS)).sort((a, b) => a - b);
|
|
13630
|
+
const isolated = extras.filter((c, i) => {
|
|
13631
|
+
const prev = extras[i - 1];
|
|
13632
|
+
const next = extras[i + 1];
|
|
13633
|
+
return (prev === void 0 || c - prev >= isolationS) && (next === void 0 || next - c >= isolationS);
|
|
13634
|
+
});
|
|
13635
|
+
return [.../* @__PURE__ */ new Set([...kept, ...isolated])].sort((a, b) => a - b);
|
|
13636
|
+
}
|
|
13626
13637
|
function timecodeToSeconds(tc) {
|
|
13627
13638
|
const m = tc.trim().match(/^(\d+):(\d{1,2}):(\d{1,2}(?:\.\d+)?)$/);
|
|
13628
13639
|
if (!m) return null;
|
|
@@ -13676,12 +13687,13 @@ async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
|
|
|
13676
13687
|
const timeoutMs = opts.timeout_ms ?? 12e4;
|
|
13677
13688
|
const cuts = await runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs);
|
|
13678
13689
|
if (!pinned && isLikelyOverSegmented(cuts)) {
|
|
13679
|
-
|
|
13690
|
+
const rechecked = await runSceneDetectOnce(
|
|
13680
13691
|
filePath,
|
|
13681
13692
|
PYSCENEDETECT_RECHECK_THRESHOLD,
|
|
13682
13693
|
PYSCENEDETECT_RECHECK_MIN_SCENE_LEN_S,
|
|
13683
13694
|
timeoutMs
|
|
13684
13695
|
);
|
|
13696
|
+
return mergeRecheckCuts(cuts, rechecked);
|
|
13685
13697
|
}
|
|
13686
13698
|
return cuts;
|
|
13687
13699
|
}
|
|
@@ -13819,9 +13831,22 @@ function videoResolutionParam(videoModel, resolution) {
|
|
|
13819
13831
|
return { resolution: resolution ?? DEFAULT_VIDEO_RESOLUTION };
|
|
13820
13832
|
}
|
|
13821
13833
|
var WORDS_PER_SECOND = 2.5;
|
|
13834
|
+
function wordCount(text) {
|
|
13835
|
+
return text.trim().split(/\s+/).filter(Boolean).length;
|
|
13836
|
+
}
|
|
13822
13837
|
function estSpeechS(text) {
|
|
13823
|
-
|
|
13824
|
-
|
|
13838
|
+
return wordCount(text) / WORDS_PER_SECOND;
|
|
13839
|
+
}
|
|
13840
|
+
var OBSERVED_WPS_MIN = 1;
|
|
13841
|
+
var OBSERVED_WPS_MAX = 6;
|
|
13842
|
+
function estSpeechWindowS(text, startS, endS) {
|
|
13843
|
+
const words = wordCount(text);
|
|
13844
|
+
const window = (endS ?? 0) - (startS ?? 0);
|
|
13845
|
+
if (words > 0 && window > 0.3) {
|
|
13846
|
+
const wps = words / window;
|
|
13847
|
+
if (wps >= OBSERVED_WPS_MIN && wps <= OBSERVED_WPS_MAX) return window;
|
|
13848
|
+
}
|
|
13849
|
+
return estSpeechS(text);
|
|
13825
13850
|
}
|
|
13826
13851
|
var NARRATOR_SPEAKERS = /* @__PURE__ */ new Set([
|
|
13827
13852
|
"voiceover",
|
|
@@ -14007,7 +14032,13 @@ var DialogueLine = z11.object({
|
|
|
14007
14032
|
start_s: z11.number().optional(),
|
|
14008
14033
|
end_s: z11.number().optional(),
|
|
14009
14034
|
delivery: z11.string().optional(),
|
|
14010
|
-
voice_description: z11.string().optional()
|
|
14035
|
+
voice_description: z11.string().optional(),
|
|
14036
|
+
// DECON-supplied: is this speaker's FACE visibly speaking in THIS scene? Element
|
|
14037
|
+
// presence alone can't answer that — a founder pictured in a polaroid close-up is
|
|
14038
|
+
// "present" yet the line is voiceover, and treating it as on-camera produced a
|
|
14039
|
+
// native Seedance lip-sync clip of a still photograph. `false` pins the line to
|
|
14040
|
+
// the VO path; absent keeps the presence-based decision (old blueprints).
|
|
14041
|
+
on_camera: z11.boolean().optional()
|
|
14011
14042
|
}).loose();
|
|
14012
14043
|
var Sfx = z11.object({
|
|
14013
14044
|
at_s: z11.number().optional(),
|
|
@@ -14023,6 +14054,22 @@ var CompositionRegion = z11.object({
|
|
|
14023
14054
|
is_presenter: z11.boolean().optional(),
|
|
14024
14055
|
// The cast id shown/speaking in this region (routes lip-sync + element refs).
|
|
14025
14056
|
cast_ref: z11.string().optional(),
|
|
14057
|
+
// What the region's content IS: camera | screen_capture | static_graphic |
|
|
14058
|
+
// generated. Authoritative for routing when present (regex-over-prose fallback
|
|
14059
|
+
// otherwise): screen_capture/static_graphic are rebuilt from REAL surfaces on the
|
|
14060
|
+
// overlay layer, never AI-generated.
|
|
14061
|
+
kind: z11.string().optional(),
|
|
14062
|
+
// Opaque id naming the SPECIFIC on-screen document/note/app-state this
|
|
14063
|
+
// screen_capture region shows. Two scenes share it only when they show the SAME
|
|
14064
|
+
// recording continuing (scrolling/typing/waiting within it) — a genuinely
|
|
14065
|
+
// DIFFERENT document/note/recording (a source video splicing two screen captures)
|
|
14066
|
+
// gets a different id. Breaks a persistent-layout run into separate surface stubs
|
|
14067
|
+
// instead of asking the operator for one screenshot that can't cover both.
|
|
14068
|
+
surface_id: z11.string().optional(),
|
|
14069
|
+
// Camera bubble(s)/inset(s) embedded INSIDE this region's surface (a Loom-style
|
|
14070
|
+
// presenter bubble inside a screen recording) — video-in-video the reproduction
|
|
14071
|
+
// must re-composite, not paint into the surface.
|
|
14072
|
+
nested: z11.array(z11.object({}).loose()).optional(),
|
|
14026
14073
|
summary: z11.string().optional(),
|
|
14027
14074
|
frame_prompt: z11.string().optional(),
|
|
14028
14075
|
motion_prompt: z11.string().optional()
|
|
@@ -14076,6 +14123,11 @@ var Scene = z11.object({
|
|
|
14076
14123
|
sfx: z11.array(Sfx).optional(),
|
|
14077
14124
|
overlays: z11.array(z11.unknown()).optional(),
|
|
14078
14125
|
floating_elements: z11.array(z11.unknown()).optional(),
|
|
14126
|
+
// DECON-supplied: how much the picture itself moves within the shot. Gates the
|
|
14127
|
+
// flash-hold optimization — a sub-2s b-roll flash with REAL subject motion
|
|
14128
|
+
// (pouring, spreading, hands working) must stay a real clip; freezing it turns
|
|
14129
|
+
// a montage into a slideshow. Absent (old blueprints) keeps the cheap still.
|
|
14130
|
+
motion_level: z11.enum(["static", "subtle", "dynamic"]).optional(),
|
|
14079
14131
|
transcript_slice: z11.array(TranscriptWord).optional(),
|
|
14080
14132
|
start_frame_asset: FrameAsset,
|
|
14081
14133
|
end_frame_asset: FrameAsset,
|
|
@@ -14315,6 +14367,33 @@ function buildElementSlots(elements) {
|
|
|
14315
14367
|
function slotsForFrame(slots, sceneIndex, edge) {
|
|
14316
14368
|
return slots.filter((s) => s.presence.get(sceneIndex)?.has(edge));
|
|
14317
14369
|
}
|
|
14370
|
+
var MENTION_WIRED_TYPES = /* @__PURE__ */ new Set(["logo", "badge"]);
|
|
14371
|
+
function promptMentionsSlot(prompt, slot) {
|
|
14372
|
+
return elementMentionKeywords(slot).some(
|
|
14373
|
+
(w) => new RegExp(`\\b${w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i").test(prompt)
|
|
14374
|
+
);
|
|
14375
|
+
}
|
|
14376
|
+
function edgePromptText(scene, edge) {
|
|
14377
|
+
const regionPrompts = (compositeRegionsOf(scene) ?? []).map((r) => r.frame_prompt ?? "").filter(Boolean).join("\n");
|
|
14378
|
+
const own = (edge === "start" ? scene.start_frame_prompt : scene.end_frame_prompt) ?? "";
|
|
14379
|
+
return [own, regionPrompts].join("\n").trim();
|
|
14380
|
+
}
|
|
14381
|
+
function extendPresenceByPromptMentions(slots, blueprint) {
|
|
14382
|
+
const extendable = slots.filter((s) => MENTION_WIRED_TYPES.has(s.type.toLowerCase()));
|
|
14383
|
+
if (extendable.length === 0) return;
|
|
14384
|
+
blueprint.scenes.forEach((scene, i) => {
|
|
14385
|
+
for (const edge of EDGES) {
|
|
14386
|
+
const text = edgePromptText(scene, edge);
|
|
14387
|
+
if (!text) continue;
|
|
14388
|
+
for (const slot of extendable) {
|
|
14389
|
+
if (!promptMentionsSlot(text, slot)) continue;
|
|
14390
|
+
const set = slot.presence.get(i) ?? /* @__PURE__ */ new Set();
|
|
14391
|
+
set.add(edge);
|
|
14392
|
+
slot.presence.set(i, set);
|
|
14393
|
+
}
|
|
14394
|
+
}
|
|
14395
|
+
});
|
|
14396
|
+
}
|
|
14318
14397
|
var ACTOR_SHEET_MODEL = "google/gemini-3-pro-image-preview";
|
|
14319
14398
|
var SHEET_SUBJECT_TYPE = {
|
|
14320
14399
|
person: "person",
|
|
@@ -14497,21 +14576,94 @@ function seedanceAudioLine(scene, mode, audio, nativeLine) {
|
|
|
14497
14576
|
}
|
|
14498
14577
|
return null;
|
|
14499
14578
|
}
|
|
14500
|
-
|
|
14501
|
-
|
|
14579
|
+
var FLOAT_STOP_WORDS = /* @__PURE__ */ new Set([
|
|
14580
|
+
"with",
|
|
14581
|
+
"across",
|
|
14582
|
+
"around",
|
|
14583
|
+
"above",
|
|
14584
|
+
"below",
|
|
14585
|
+
"over",
|
|
14586
|
+
"under",
|
|
14587
|
+
"screen",
|
|
14588
|
+
"frame",
|
|
14589
|
+
"scene",
|
|
14590
|
+
"video",
|
|
14591
|
+
"element",
|
|
14592
|
+
"elements",
|
|
14593
|
+
"graphic",
|
|
14594
|
+
"overlay",
|
|
14595
|
+
"animated",
|
|
14596
|
+
"floating",
|
|
14597
|
+
"small",
|
|
14598
|
+
"large",
|
|
14599
|
+
"white",
|
|
14600
|
+
"black",
|
|
14601
|
+
"color",
|
|
14602
|
+
"colored"
|
|
14603
|
+
]);
|
|
14604
|
+
function floatTokens(desc) {
|
|
14605
|
+
return desc.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 4 && !FLOAT_STOP_WORDS.has(w));
|
|
14606
|
+
}
|
|
14607
|
+
function scrubFloatSentences(text, floatDescs) {
|
|
14608
|
+
if (floatDescs.length === 0 || !text) return text;
|
|
14609
|
+
const tokenSets = floatDescs.map((d) => new Set(floatTokens(d)));
|
|
14610
|
+
const kept = text.split(/(?<=[.!?])\s+/).filter((sentence) => {
|
|
14611
|
+
const words = new Set(floatTokens(sentence));
|
|
14612
|
+
return !tokenSets.some((ts) => {
|
|
14613
|
+
let hits = 0;
|
|
14614
|
+
for (const w of words) if (ts.has(w)) hits++;
|
|
14615
|
+
return hits >= 2;
|
|
14616
|
+
});
|
|
14617
|
+
}).join(" ").trim();
|
|
14618
|
+
return kept;
|
|
14619
|
+
}
|
|
14620
|
+
function sceneFloatDescs(scene) {
|
|
14621
|
+
const floats = z11.array(FloatingElement).safeParse(scene.floating_elements ?? []);
|
|
14622
|
+
if (!floats.success) return [];
|
|
14623
|
+
return floats.data.map((f) => f.description?.trim() ?? "").filter(Boolean);
|
|
14624
|
+
}
|
|
14625
|
+
function visualBriefParts(scene, sceneIndex, plate) {
|
|
14502
14626
|
const parts = [];
|
|
14503
|
-
const
|
|
14504
|
-
|
|
14505
|
-
|
|
14627
|
+
const floats = sceneFloatDescs(scene);
|
|
14628
|
+
const scrub = (s) => {
|
|
14629
|
+
if (!s) return s;
|
|
14630
|
+
const out = scrubFloatSentences(s, floats);
|
|
14631
|
+
return out || void 0;
|
|
14632
|
+
};
|
|
14633
|
+
const summary = scrub((plate ? plate.summary : scene.summary)?.trim());
|
|
14634
|
+
parts.push(summary ? `Scene ${sceneIndex + 1}: ${summary}` : `Scene ${sceneIndex + 1}`);
|
|
14635
|
+
const action = !plate ? scrub(scene.action_detail) : void 0;
|
|
14636
|
+
if (action) parts.push(`Action: ${action}`);
|
|
14506
14637
|
const cm = scene.camera_motion;
|
|
14507
14638
|
if (cm) {
|
|
14508
14639
|
const camera = [cm.movement, cm.detail].filter(Boolean).join(" \u2014 ");
|
|
14509
14640
|
if (camera) parts.push(`Camera: ${camera}`);
|
|
14510
14641
|
}
|
|
14511
|
-
|
|
14512
|
-
if (
|
|
14642
|
+
const motion = scrub(plate ? plate.motion_prompt : scene.motion_prompt);
|
|
14643
|
+
if (motion) parts.push(`Motion: ${motion}`);
|
|
14644
|
+
const uiFloats = floats.filter((d) => UI_SURFACE_RE.test(d));
|
|
14645
|
+
const fxFloats = floats.filter((d) => !UI_SURFACE_RE.test(d));
|
|
14646
|
+
if (plate || uiFloats.length > 0) {
|
|
14513
14647
|
parts.push(
|
|
14514
|
-
|
|
14648
|
+
"NEVER render any screen, phone, app, website, or UI element \u2014 this clip is the clean background plate; the screen surface is composited on top later as a separate real layer."
|
|
14649
|
+
);
|
|
14650
|
+
}
|
|
14651
|
+
if (fxFloats.length > 0) {
|
|
14652
|
+
parts.push(
|
|
14653
|
+
"NEVER render floating decorative elements \u2014 no hearts, sparkles, confetti, badges, stickers, icons, or particle effects. They are composited later as a separate real overlay layer; the picture stays clean of them."
|
|
14654
|
+
);
|
|
14655
|
+
}
|
|
14656
|
+
return parts;
|
|
14657
|
+
}
|
|
14658
|
+
function buildSeedancePrompt(scene, sceneIndex, present, mode, audio, nativeLine, nativeLang, uiRouted) {
|
|
14659
|
+
const loc = (s) => nativeLine ? localizeNumeralsForNative(s, nativeLang) : s;
|
|
14660
|
+
const routed = uiRouted ?? (compositeRegionsOf(scene) !== null && isUiOnlyComposite(compositeRegionsOf(scene) ?? []));
|
|
14661
|
+
const plate = routed ? compositePlateRegion(scene) ?? {} : null;
|
|
14662
|
+
const parts = visualBriefParts(scene, sceneIndex, plate);
|
|
14663
|
+
const refs = plate ? present.filter((s) => !UI_SURFACE_RE.test(s.description ?? "")) : present;
|
|
14664
|
+
if (refs.length > 0) {
|
|
14665
|
+
parts.push(
|
|
14666
|
+
`Keep these consistent with their references: ${refs.map((s) => `${s.label} (${s.description ?? s.type})`).join("; ")}`
|
|
14515
14667
|
);
|
|
14516
14668
|
}
|
|
14517
14669
|
if (nativeLine) {
|
|
@@ -14642,7 +14794,8 @@ function emitSceneClip(i, scene, present, mode, nativeTurn, ambientBroll, frames
|
|
|
14642
14794
|
mode,
|
|
14643
14795
|
Boolean(nativeTurn) || ambientBroll,
|
|
14644
14796
|
nativeTurn?.text,
|
|
14645
|
-
opts.nativeLang
|
|
14797
|
+
opts.nativeLang,
|
|
14798
|
+
opts.uiRouted
|
|
14646
14799
|
),
|
|
14647
14800
|
duration: lengths.genDur,
|
|
14648
14801
|
...videoResolutionParam(opts.videoModel, opts.resolution),
|
|
@@ -14671,12 +14824,59 @@ function emitSceneClip(i, scene, present, mode, nativeTurn, ambientBroll, frames
|
|
|
14671
14824
|
var COMPOSITE_LAYOUTS = /* @__PURE__ */ new Set(["split_screen", "pip", "keyed_overlay"]);
|
|
14672
14825
|
var UI_SURFACE_RE = /\b(?:app|ui|web ?site|web ?page|website|browser|chat|interface|mock-?up|in[- ]?app|dashboard|app screen|phone screen|screen[- ]?(?:recording|capture|grab|share))\b/i;
|
|
14673
14826
|
function regionIsUiSurface(r) {
|
|
14674
|
-
|
|
14827
|
+
const kind = (r.kind ?? "").trim().toLowerCase();
|
|
14828
|
+
if (kind === "screen_capture" || kind === "static_graphic") return true;
|
|
14829
|
+
if (kind === "camera") return false;
|
|
14830
|
+
return UI_SURFACE_RE.test(`${r.panel ?? ""} ${r.summary ?? ""}`);
|
|
14675
14831
|
}
|
|
14676
14832
|
function isUiOnlyComposite(regions) {
|
|
14677
14833
|
const ui = regions.filter(regionIsUiSurface).length;
|
|
14678
14834
|
return ui >= 1 && regions.length - ui <= 1;
|
|
14679
14835
|
}
|
|
14836
|
+
function compositeRegionsOf(scene) {
|
|
14837
|
+
const comp = scene.composition;
|
|
14838
|
+
const layout = (comp?.layout ?? "").toLowerCase();
|
|
14839
|
+
if (!COMPOSITE_LAYOUTS.has(layout)) return null;
|
|
14840
|
+
const regions = (comp?.regions ?? []).filter((r) => Boolean(r) && typeof r === "object");
|
|
14841
|
+
return regions.length >= 2 ? regions : null;
|
|
14842
|
+
}
|
|
14843
|
+
function compositePlateRegion(scene) {
|
|
14844
|
+
const regions = compositeRegionsOf(scene);
|
|
14845
|
+
if (!regions) return null;
|
|
14846
|
+
return regions.find((r) => r.is_presenter) ?? regions.find((r) => !regionIsUiSurface(r)) ?? null;
|
|
14847
|
+
}
|
|
14848
|
+
function uiRoutedSceneSet(input) {
|
|
14849
|
+
return new Set(uiRoutedRuns(input).flat());
|
|
14850
|
+
}
|
|
14851
|
+
function uiRoutedRuns(input) {
|
|
14852
|
+
const blueprint = VideoBlueprint.parse(input);
|
|
14853
|
+
const info = blueprint.scenes.map((scene) => {
|
|
14854
|
+
const regions = compositeRegionsOf(scene);
|
|
14855
|
+
if (!regions) return null;
|
|
14856
|
+
const comp = scene.composition;
|
|
14857
|
+
const panels = regions.map((r) => (r.panel ?? "").toLowerCase()).sort().join(",");
|
|
14858
|
+
const surfaceIds = regions.filter(regionIsUiSurface).map((r) => (r.surface_id ?? "").trim()).filter(Boolean).sort().join(",");
|
|
14859
|
+
const sig = surfaceIds ? `sid:${surfaceIds}` : `${(comp?.layout ?? "").toLowerCase()}|${(comp?.split_axis ?? "").toLowerCase()}|${panels}`;
|
|
14860
|
+
return { sig, ui: isUiOnlyComposite(regions) };
|
|
14861
|
+
});
|
|
14862
|
+
const runs = [];
|
|
14863
|
+
let run = [];
|
|
14864
|
+
const flushRun = () => {
|
|
14865
|
+
if (run.some((i) => info[i]?.ui)) runs.push(run);
|
|
14866
|
+
run = [];
|
|
14867
|
+
};
|
|
14868
|
+
info.forEach((inf, i) => {
|
|
14869
|
+
if (!inf) {
|
|
14870
|
+
flushRun();
|
|
14871
|
+
return;
|
|
14872
|
+
}
|
|
14873
|
+
const prev = i > 0 ? info[i - 1] : null;
|
|
14874
|
+
if (!prev || prev.sig !== inf.sig) flushRun();
|
|
14875
|
+
run.push(i);
|
|
14876
|
+
});
|
|
14877
|
+
flushRun();
|
|
14878
|
+
return runs;
|
|
14879
|
+
}
|
|
14680
14880
|
function sceneIsFullScreenUi(scene, present) {
|
|
14681
14881
|
if (scene.narrative_role?.trim() === "cta") return false;
|
|
14682
14882
|
const hasCast = present.some((s) => {
|
|
@@ -14710,14 +14910,11 @@ function screenStillArgs(durationS, dims) {
|
|
|
14710
14910
|
"{{out.video}}"
|
|
14711
14911
|
];
|
|
14712
14912
|
}
|
|
14713
|
-
function layeredComposition(scene) {
|
|
14714
|
-
const
|
|
14715
|
-
|
|
14716
|
-
if (
|
|
14717
|
-
|
|
14718
|
-
if (regions.length < 2) return null;
|
|
14719
|
-
if (isUiOnlyComposite(regions)) return null;
|
|
14720
|
-
return { layout, regions, comp: comp ?? {} };
|
|
14913
|
+
function layeredComposition(scene, uiRouted) {
|
|
14914
|
+
const regions = compositeRegionsOf(scene);
|
|
14915
|
+
if (!regions) return null;
|
|
14916
|
+
if (uiRouted) return null;
|
|
14917
|
+
return { layout: (scene.composition?.layout ?? "").toLowerCase(), regions, comp: scene.composition ?? {} };
|
|
14721
14918
|
}
|
|
14722
14919
|
function splitAxisOf(comp, regions) {
|
|
14723
14920
|
const panels = regions.map((r) => (r.panel ?? "").toLowerCase());
|
|
@@ -14808,7 +15005,7 @@ function buildCompositeScene(layout, regions, comp, scene, i, present, mode, nat
|
|
|
14808
15005
|
const bgIdx = regions.findIndex((_, k) => k !== presIdx);
|
|
14809
15006
|
const bgRef = regionRefs[bgIdx >= 0 ? bgIdx : 0];
|
|
14810
15007
|
let presRef = regionRefs[presIdx >= 0 ? presIdx : 1];
|
|
14811
|
-
if (layout === "keyed_overlay"
|
|
15008
|
+
if (layout === "keyed_overlay") {
|
|
14812
15009
|
const keyId = `s${i}_key`;
|
|
14813
15010
|
nodes.push({ id: keyId, type: "video_background_remove", inputs: { video: presRef }, params: {} });
|
|
14814
15011
|
presRef = `$ref:${keyId}.video`;
|
|
@@ -14909,6 +15106,67 @@ function emitScreenScene(i, scene, lengths, out, outAr, nodes, clips) {
|
|
|
14909
15106
|
});
|
|
14910
15107
|
clips.push({ ref: `$ref:s${i}_clip.video`, scene_s: lengths.dur, out });
|
|
14911
15108
|
}
|
|
15109
|
+
function sceneIsAllGraphic(scene) {
|
|
15110
|
+
const regions = (scene.composition?.regions ?? []).filter((r) => Boolean(r) && typeof r === "object");
|
|
15111
|
+
if (regions.length === 0 || regions.some((r) => (r.kind ?? "").toLowerCase() === "camera")) return false;
|
|
15112
|
+
return regions.some((r) => (r.kind ?? "").toLowerCase() === "static_graphic");
|
|
15113
|
+
}
|
|
15114
|
+
function sceneIsFullFrameGraphic(scene) {
|
|
15115
|
+
return sceneIsAllGraphic(scene);
|
|
15116
|
+
}
|
|
15117
|
+
function fullFrameGraphicScenes(blueprint) {
|
|
15118
|
+
const scenes = blueprint.scenes;
|
|
15119
|
+
const graphic = new Set(scenes.flatMap((s, i) => sceneIsFullFrameGraphic(s) ? [i] : []));
|
|
15120
|
+
const blocked = (s) => {
|
|
15121
|
+
const regions = (s.composition?.regions ?? []).filter((r) => Boolean(r) && typeof r === "object");
|
|
15122
|
+
if (regions.some((r) => (r.kind ?? "").toLowerCase() === "camera")) return true;
|
|
15123
|
+
return (s.dialogue ?? []).some((d) => d.on_camera === true);
|
|
15124
|
+
};
|
|
15125
|
+
let changed = true;
|
|
15126
|
+
while (changed) {
|
|
15127
|
+
changed = false;
|
|
15128
|
+
scenes.forEach((s, i) => {
|
|
15129
|
+
if (graphic.has(i)) return;
|
|
15130
|
+
const chainsToPrev = s.continues_previous && graphic.has(i - 1);
|
|
15131
|
+
const next = scenes[i + 1];
|
|
15132
|
+
const chainsToNext = Boolean(next?.continues_previous) && graphic.has(i + 1);
|
|
15133
|
+
if ((chainsToPrev || chainsToNext) && !blocked(s)) {
|
|
15134
|
+
graphic.add(i);
|
|
15135
|
+
changed = true;
|
|
15136
|
+
}
|
|
15137
|
+
});
|
|
15138
|
+
}
|
|
15139
|
+
return graphic;
|
|
15140
|
+
}
|
|
15141
|
+
function emitGraphicScene(i, scene, lengths, out, outAr, surfaceIngests, nodes, clips) {
|
|
15142
|
+
const regions = (scene.composition?.regions ?? []).filter((r) => Boolean(r) && typeof r === "object");
|
|
15143
|
+
const surfaceId = regions.find((r) => r.surface_id)?.surface_id;
|
|
15144
|
+
let refId = surfaceId ? surfaceIngests.get(surfaceId) : void 0;
|
|
15145
|
+
if (!refId) {
|
|
15146
|
+
const label = commentSafe((scene.summary || scene.start_frame_prompt || "the designed panel").slice(0, 120));
|
|
15147
|
+
refId = `s${i}_graphic_ref`;
|
|
15148
|
+
nodes.push({
|
|
15149
|
+
id: refId,
|
|
15150
|
+
type: "ingest",
|
|
15151
|
+
params: {
|
|
15152
|
+
source: "path",
|
|
15153
|
+
path: `[TODO: supply the REAL designed graphic for "${label}" \u2014 NEVER AI-generate a designed panel (its typography/wordmarks garble). Rebuild it as brand HTML in video-overlay-composition (the scene's text/logos are already seeded there), export the design from the brand kit, or source stock art; text rides the overlay layer, not this plate]`,
|
|
15154
|
+
expect: "image"
|
|
15155
|
+
}
|
|
15156
|
+
});
|
|
15157
|
+
if (surfaceId) surfaceIngests.set(surfaceId, refId);
|
|
15158
|
+
}
|
|
15159
|
+
nodes.push({
|
|
15160
|
+
id: `s${i}_clip`,
|
|
15161
|
+
type: "ffmpeg",
|
|
15162
|
+
inputs: { frame: `$ref:${refId}.asset` },
|
|
15163
|
+
params: {
|
|
15164
|
+
args: screenStillArgs(lengths.trimTarget, canvasDims(outAr)),
|
|
15165
|
+
outputs: { video: { kind: "video", ext: "mp4" } }
|
|
15166
|
+
}
|
|
15167
|
+
});
|
|
15168
|
+
clips.push({ ref: `$ref:s${i}_clip.video`, scene_s: lengths.dur, out });
|
|
15169
|
+
}
|
|
14912
15170
|
var BRAND_CARD_RE = /\b(?:solid|plain|flat|brand|logo|wordmark|end[- ]?card|cta card|title card|colou?r background|background colou?r)\b/i;
|
|
14913
15171
|
function sceneIsBrandCard(scene, present, isCta) {
|
|
14914
15172
|
if (!isCta) return false;
|
|
@@ -15149,8 +15407,12 @@ function buildPhrases(blueprint, canonical, compositeScenes, presenterPresent, p
|
|
|
15149
15407
|
speaker: sp,
|
|
15150
15408
|
// Shown = a cast member speaking AND their element is actually on screen
|
|
15151
15409
|
// here (not a cutaway). A b-roll cutaway mid-phrase fails this and gets
|
|
15152
|
-
// its own clip while the phrase voice plays under it.
|
|
15153
|
-
|
|
15410
|
+
// its own clip while the phrase voice plays under it. An explicit
|
|
15411
|
+
// deconstruct voiceover stamp (`on_camera: false`) wins over element
|
|
15412
|
+
// presence — a speaker pictured in a photo is "present" but not talking.
|
|
15413
|
+
// An all-graphic composition (no camera region) is voiceover by
|
|
15414
|
+
// definition: nobody is on screen to lip-sync.
|
|
15415
|
+
shown: l.on_camera !== false && !sceneIsAllGraphic(scene) && isOnCameraSpeaker(raw, casts, cameraOn) && !multiSpeaker.has(sceneIndex) && presenterPresent(sp, sceneIndex),
|
|
15154
15416
|
start,
|
|
15155
15417
|
// Real speech end. When the deconstruct gives no end_s, estimate it from
|
|
15156
15418
|
// the words — NOT the scene end (which would fabricate continuity across
|
|
@@ -15248,7 +15510,8 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
15248
15510
|
shootMode: mode,
|
|
15249
15511
|
ingestCache: env.ingestCache
|
|
15250
15512
|
};
|
|
15251
|
-
const
|
|
15513
|
+
const chained = anchorScene.continues_previous && out.phraseEnd?.scene === anchor - 1 ? out.phraseEnd.ref : void 0;
|
|
15514
|
+
const first = chained ?? buildFrameRef(
|
|
15252
15515
|
"start",
|
|
15253
15516
|
anchorScene.start_frame_asset?.url,
|
|
15254
15517
|
anchorScene.start_frame_prompt,
|
|
@@ -15275,7 +15538,16 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
15275
15538
|
const genDur = ceilToSeedance(phraseLen);
|
|
15276
15539
|
const clipParams = {
|
|
15277
15540
|
model: env.opts.videoModel,
|
|
15278
|
-
prompt: buildSeedancePrompt(
|
|
15541
|
+
prompt: buildSeedancePrompt(
|
|
15542
|
+
anchorScene,
|
|
15543
|
+
anchor,
|
|
15544
|
+
present,
|
|
15545
|
+
mode,
|
|
15546
|
+
true,
|
|
15547
|
+
phrase.text,
|
|
15548
|
+
env.ttsLanguageCode,
|
|
15549
|
+
env.uiRouted.has(anchor)
|
|
15550
|
+
),
|
|
15279
15551
|
duration: genDur,
|
|
15280
15552
|
...videoResolutionParam(env.opts.videoModel, env.opts.resolution),
|
|
15281
15553
|
generate_audio: true
|
|
@@ -15287,6 +15559,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
15287
15559
|
inputs: { first_frame: first, last_frame: last },
|
|
15288
15560
|
params: clipParams
|
|
15289
15561
|
});
|
|
15562
|
+
out.phraseEnd = { ref: last, scene: lastShown };
|
|
15290
15563
|
const clipRef = `$ref:s${anchor}_clip.video`;
|
|
15291
15564
|
const speechOffset = Math.max(0, phrase.start_s - clipStart);
|
|
15292
15565
|
const extractLen = Math.min(Math.max(0.5, phrase.end_s - phrase.start_s), Math.max(0.5, genDur - speechOffset));
|
|
@@ -15321,7 +15594,8 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
15321
15594
|
scene: anchor,
|
|
15322
15595
|
voice_convert_node: convId,
|
|
15323
15596
|
scene_s: Math.round(phraseLen * 100) / 100,
|
|
15324
|
-
est_speech_s: Math.round(
|
|
15597
|
+
est_speech_s: Math.round(estSpeechWindowS(phrase.text, phrase.start_s, phrase.end_s) * 100) / 100,
|
|
15598
|
+
speech_words: wordCount(phrase.text)
|
|
15325
15599
|
});
|
|
15326
15600
|
for (const s of phrase.shownScenes) {
|
|
15327
15601
|
const sc = env.blueprint.scenes[s];
|
|
@@ -15379,7 +15653,8 @@ function emitCompositeInTimeline(composite, scene, i, isLast, env, canonical, en
|
|
|
15379
15653
|
scene: i,
|
|
15380
15654
|
voice_convert_node: `${voiceNode}_conv`,
|
|
15381
15655
|
scene_s: Math.round(sceneDurationS(scene) * 100) / 100,
|
|
15382
|
-
est_speech_s: Math.round(
|
|
15656
|
+
est_speech_s: Math.round(estSpeechWindowS(text, start, end) * 100) / 100,
|
|
15657
|
+
speech_words: wordCount(text)
|
|
15383
15658
|
});
|
|
15384
15659
|
}
|
|
15385
15660
|
const mode = sceneShootMode(scene, present, nativeTurn, env.cameraOn, env.casts);
|
|
@@ -15471,6 +15746,10 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
15471
15746
|
shootMode: mode,
|
|
15472
15747
|
ingestCache: env.ingestCache
|
|
15473
15748
|
};
|
|
15749
|
+
if (!env.reuse && env.graphicScenes.has(i)) {
|
|
15750
|
+
emitGraphicScene(i, scene, lengths, lengths.out, env.outAr, env.surfaceIngests, nodes, out.clips);
|
|
15751
|
+
return void 0;
|
|
15752
|
+
}
|
|
15474
15753
|
if (!env.reuse && sceneIsFullScreenUi(scene, present)) {
|
|
15475
15754
|
emitScreenScene(i, scene, lengths, lengths.out, env.outAr, nodes, out.clips);
|
|
15476
15755
|
return void 0;
|
|
@@ -15480,7 +15759,7 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
15480
15759
|
emitBrandCardScene(i, lengths, lengths.out, env.outAr, brandPlateColor(env.blueprint), nodes, out.clips);
|
|
15481
15760
|
return void 0;
|
|
15482
15761
|
}
|
|
15483
|
-
if (!ambientBroll && lengths.dur <= FLASH_HOLD_MAX_S) {
|
|
15762
|
+
if (!ambientBroll && lengths.dur <= FLASH_HOLD_MAX_S && scene.motion_level !== "dynamic") {
|
|
15484
15763
|
emitFlashHold(i, scene, env.slots, ctx, lengths, lengths.out, env.outAr, nodes, out.clips);
|
|
15485
15764
|
return void 0;
|
|
15486
15765
|
}
|
|
@@ -15515,7 +15794,8 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
15515
15794
|
genAr: env.genAr,
|
|
15516
15795
|
videoModel: env.opts.videoModel,
|
|
15517
15796
|
resolution: env.opts.resolution,
|
|
15518
|
-
nativeLang: env.ttsLanguageCode
|
|
15797
|
+
nativeLang: env.ttsLanguageCode,
|
|
15798
|
+
uiRouted: env.uiRouted.has(i)
|
|
15519
15799
|
},
|
|
15520
15800
|
nodes
|
|
15521
15801
|
);
|
|
@@ -15536,10 +15816,11 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
15536
15816
|
}
|
|
15537
15817
|
function buildTimeline(blueprint, slots, opts, nodes) {
|
|
15538
15818
|
const reuse = opts.frames === "reuse";
|
|
15819
|
+
const uiRouted = uiRoutedSceneSet(blueprint);
|
|
15539
15820
|
const compositeScenes = /* @__PURE__ */ new Set();
|
|
15540
15821
|
if (!reuse) {
|
|
15541
15822
|
blueprint.scenes.forEach((s, i) => {
|
|
15542
|
-
if (layeredComposition(s)) compositeScenes.add(i);
|
|
15823
|
+
if (layeredComposition(s, uiRouted.has(i))) compositeScenes.add(i);
|
|
15543
15824
|
});
|
|
15544
15825
|
}
|
|
15545
15826
|
const canonical = collapseVoiceover(blueprint);
|
|
@@ -15555,6 +15836,9 @@ function buildTimeline(blueprint, slots, opts, nodes) {
|
|
|
15555
15836
|
cameraOn: onCameraDialogue(blueprint),
|
|
15556
15837
|
casts: castIdSet(blueprint),
|
|
15557
15838
|
ingestCache: /* @__PURE__ */ new Map(),
|
|
15839
|
+
uiRouted,
|
|
15840
|
+
graphicScenes: fullFrameGraphicScenes(blueprint),
|
|
15841
|
+
surfaceIngests: /* @__PURE__ */ new Map(),
|
|
15558
15842
|
ttsLanguageCode: deriveTtsLanguageCode(blueprint)
|
|
15559
15843
|
};
|
|
15560
15844
|
const out = {
|
|
@@ -15583,7 +15867,7 @@ function buildTimeline(blueprint, slots, opts, nodes) {
|
|
|
15583
15867
|
const lastIndex = blueprint.scenes.length - 1;
|
|
15584
15868
|
let prevEndFrame;
|
|
15585
15869
|
blueprint.scenes.forEach((scene, i) => {
|
|
15586
|
-
const composite = compositeScenes.has(i) ? layeredComposition(scene) : null;
|
|
15870
|
+
const composite = compositeScenes.has(i) ? layeredComposition(scene, uiRouted.has(i)) : null;
|
|
15587
15871
|
if (composite) {
|
|
15588
15872
|
emitCompositeInTimeline(
|
|
15589
15873
|
composite,
|
|
@@ -15780,9 +16064,42 @@ function sourceHint(fe) {
|
|
|
15780
16064
|
return `baker images icon "${desc}"`;
|
|
15781
16065
|
}
|
|
15782
16066
|
}
|
|
15783
|
-
function
|
|
15784
|
-
|
|
15785
|
-
|
|
16067
|
+
function floatKey(fe) {
|
|
16068
|
+
return [
|
|
16069
|
+
(fe.kind ?? "element").toLowerCase(),
|
|
16070
|
+
(fe.brand_name || fe.what_it_represents || fe.description || "").toLowerCase().trim(),
|
|
16071
|
+
positionClass(fe.position)
|
|
16072
|
+
].join("|");
|
|
16073
|
+
}
|
|
16074
|
+
function floatIsRoutedSurface(fe, sceneIsUiRouted) {
|
|
16075
|
+
return sceneIsUiRouted && (fe.kind ?? "").toLowerCase() === "ui_element" && UI_SURFACE_RE.test(fe.description ?? "");
|
|
16076
|
+
}
|
|
16077
|
+
function addFloatDetection(windows, fe, at, end) {
|
|
16078
|
+
const wins = windows.get(floatKey(fe)) ?? [];
|
|
16079
|
+
const last = wins[wins.length - 1];
|
|
16080
|
+
if (last && at <= last.end + OVERLAY_REDETECT_GAP_S) {
|
|
16081
|
+
last.end = Math.max(last.end, end);
|
|
16082
|
+
return;
|
|
16083
|
+
}
|
|
16084
|
+
wins.push({ fe, at, end });
|
|
16085
|
+
windows.set(floatKey(fe), wins);
|
|
16086
|
+
}
|
|
16087
|
+
function collectFloatWindows(blueprint, uiRouted) {
|
|
16088
|
+
const windows = /* @__PURE__ */ new Map();
|
|
16089
|
+
blueprint.scenes.forEach((scene, i) => {
|
|
16090
|
+
const sceneStart = scene.start_s ?? 0;
|
|
16091
|
+
const floats = z11.array(FloatingElement).safeParse(scene.floating_elements ?? []);
|
|
16092
|
+
if (!floats.success) return;
|
|
16093
|
+
for (const fe of floats.data) {
|
|
16094
|
+
const at = fe.appears_at_s ?? sceneStart;
|
|
16095
|
+
const dur = fe.duration_s ?? 2.5;
|
|
16096
|
+
if (floatIsRoutedSurface(fe, uiRouted.has(i)) || dur < OVERLAY_MIN_DUR_S) continue;
|
|
16097
|
+
addFloatDetection(windows, fe, at, at + dur);
|
|
16098
|
+
}
|
|
16099
|
+
});
|
|
16100
|
+
return [...windows.values()].flat().sort((a, b) => a.at - b.at);
|
|
16101
|
+
}
|
|
16102
|
+
function floatingStub(fe, at, dur) {
|
|
15786
16103
|
const kind = commentSafe(fe.kind ?? "element");
|
|
15787
16104
|
const label = commentSafe(fe.brand_name || fe.what_it_represents || fe.description || fe.kind || "element");
|
|
15788
16105
|
const hint = commentSafe(sourceHint(fe));
|
|
@@ -15792,27 +16109,49 @@ function floatingStub(fe, sceneStart) {
|
|
|
15792
16109
|
`<img class="ov clip ${positionClass(fe.position)}" src="your-${slug}.png" data-start="${at}" data-dur="${dur}" alt="" /> -->`
|
|
15793
16110
|
].join("\n");
|
|
15794
16111
|
}
|
|
15795
|
-
function
|
|
15796
|
-
const
|
|
15797
|
-
|
|
15798
|
-
|
|
15799
|
-
const
|
|
15800
|
-
|
|
15801
|
-
|
|
15802
|
-
|
|
15803
|
-
|
|
16112
|
+
function uiSurfaceStub(blueprint, run) {
|
|
16113
|
+
const scenes = run.map((i) => blueprint.scenes[i]).filter((s) => Boolean(s));
|
|
16114
|
+
if (scenes.length === 0) return "";
|
|
16115
|
+
const first = scenes[0];
|
|
16116
|
+
const last = scenes[scenes.length - 1];
|
|
16117
|
+
const surfaceOf = (scene) => {
|
|
16118
|
+
const regions = compositeRegionsOf(scene) ?? [];
|
|
16119
|
+
return regions.find(regionIsUiSurface) ?? regions.find((r) => !r.is_presenter);
|
|
16120
|
+
};
|
|
16121
|
+
const ui = scenes.map(surfaceOf).find(Boolean);
|
|
16122
|
+
const at = first.start_s ?? 0;
|
|
16123
|
+
const end = last.end_s ?? at + 2.5;
|
|
16124
|
+
const dur = Math.max(0.5, Math.round((end - at) * 100) / 100);
|
|
15804
16125
|
const label = commentSafe(ui?.summary || ui?.frame_prompt || ui?.panel || "the app screen");
|
|
15805
|
-
|
|
15806
|
-
|
|
16126
|
+
const states = scenes.length > 1 ? scenes.map((s) => {
|
|
16127
|
+
const beat = surfaceOf(s)?.summary || s.summary || "\u2026";
|
|
16128
|
+
return ` @ ${s.start_s ?? 0}s \u2014 ${commentSafe(beat)}`;
|
|
16129
|
+
}) : [];
|
|
16130
|
+
const isGraphic = scenes.some((s) => (compositeRegionsOf(s) ?? []).some((r) => r.kind === "static_graphic"));
|
|
16131
|
+
const sourcing = isGraphic ? [
|
|
16132
|
+
" Build it as a REAL designed panel, NEVER AI-generate its text/typography:",
|
|
16133
|
+
" hand-build a brand-accurate HTML block in this composition (restyle the seeded",
|
|
16134
|
+
" overlay text, or `npx hyperframes add <block>` for a ready-made card),",
|
|
16135
|
+
" \u2014 OR drop the design asset (export it from the brand kit) as panel.png here;",
|
|
16136
|
+
" then nest it as a timed clip:"
|
|
16137
|
+
] : [
|
|
15807
16138
|
" Build it as a REAL surface, NEVER AI: capture the live page \u2014",
|
|
15808
16139
|
" baker images screenshot https://<brand-domain>/<path> (image-library skill)",
|
|
15809
16140
|
" \u2014 OR hand-build a brand-accurate HTML screen; then frame it in a phone mockup:",
|
|
15810
16141
|
" npx hyperframes add phone-scroll (writes compositions/phone-scroll.html)",
|
|
15811
|
-
" drop the screenshot as screenshot.png in this dir and nest it as a PIP clip:"
|
|
16142
|
+
" drop the screenshot as screenshot.png in this dir and nest it as a PIP clip:"
|
|
16143
|
+
];
|
|
16144
|
+
const kindLabel = isGraphic ? "GRAPHIC PANEL" : "PHONE UI";
|
|
16145
|
+
const noun = isGraphic ? "the designed graphic panel this window shows" : "the app/site screen this window shows";
|
|
16146
|
+
return [
|
|
16147
|
+
`<!-- ${kindLabel} @ ${at}s for ${dur}s \u2014 ${noun}: ${label}.`,
|
|
16148
|
+
...states.length > 0 ? [" ONE continuous surface; its states over the window:", ...states] : [],
|
|
16149
|
+
...sourcing,
|
|
15812
16150
|
` <div class="clip" data-composition-src="compositions/phone-scroll.html" data-start="${at}" data-duration="${dur}" data-track-index="2" data-width="1080" data-height="1920"></div> -->`
|
|
15813
16151
|
].join("\n");
|
|
15814
16152
|
}
|
|
15815
|
-
|
|
16153
|
+
var CAPTION_ROLE_RE = /^(?:caption|captions|subtitle|subtitles|karaoke)$/i;
|
|
16154
|
+
function buildOverlayHtml(input, opts = {}) {
|
|
15816
16155
|
const blueprint = VideoBlueprint.parse(input);
|
|
15817
16156
|
const blocks = [
|
|
15818
16157
|
[
|
|
@@ -15831,33 +16170,15 @@ function buildOverlayHtml(input) {
|
|
|
15831
16170
|
" Positions: edit the .pos-* classes or add your own. -->"
|
|
15832
16171
|
].join("\n")
|
|
15833
16172
|
];
|
|
15834
|
-
const ovParts = mergeCaptions(blueprint).map((e) => overlayElement(e.ov, e.at, Math.round((e.end - e.at) * 1e3) / 1e3)).filter(Boolean);
|
|
16173
|
+
const ovParts = mergeCaptions(blueprint).filter((e) => !(opts.captionsActive && CAPTION_ROLE_RE.test(e.ov.role ?? ""))).map((e) => overlayElement(e.ov, e.at, Math.round((e.end - e.at) * 1e3) / 1e3)).filter(Boolean);
|
|
15835
16174
|
if (ovParts.length > 0) blocks.push(ovParts.join("\n"));
|
|
15836
|
-
const
|
|
15837
|
-
const
|
|
15838
|
-
|
|
15839
|
-
|
|
15840
|
-
|
|
15841
|
-
const
|
|
15842
|
-
|
|
15843
|
-
(fe.brand_name || fe.what_it_represents || fe.description || "").toLowerCase().trim(),
|
|
15844
|
-
positionClass(fe.position)
|
|
15845
|
-
].join("|");
|
|
15846
|
-
const lastEnd = seenFloats.get(key);
|
|
15847
|
-
if (lastEnd !== void 0 && at <= lastEnd + OVERLAY_REDETECT_GAP_S) {
|
|
15848
|
-
seenFloats.set(key, Math.max(lastEnd, at + dur));
|
|
15849
|
-
return false;
|
|
15850
|
-
}
|
|
15851
|
-
seenFloats.set(key, at + dur);
|
|
15852
|
-
return true;
|
|
15853
|
-
};
|
|
15854
|
-
for (const scene of blueprint.scenes) {
|
|
15855
|
-
const sceneStart = scene.start_s ?? 0;
|
|
15856
|
-
const floats = z11.array(FloatingElement).safeParse(scene.floating_elements ?? []);
|
|
15857
|
-
const parts = (floats.success ? floats.data.filter((fe) => keepFloat(fe, sceneStart)).map((fe) => floatingStub(fe, sceneStart)) : []).filter(Boolean);
|
|
15858
|
-
const pip = uiPipStub(scene);
|
|
15859
|
-
if (pip) parts.push(pip);
|
|
15860
|
-
if (parts.length > 0) blocks.push(parts.join("\n"));
|
|
16175
|
+
const runs = uiRoutedRuns(blueprint);
|
|
16176
|
+
const uiRouted = new Set(runs.flat());
|
|
16177
|
+
const floatParts = collectFloatWindows(blueprint, uiRouted).map((w) => floatingStub(w.fe, w.at, Math.round((w.end - w.at) * 100) / 100)).filter(Boolean);
|
|
16178
|
+
if (floatParts.length > 0) blocks.push(floatParts.join("\n"));
|
|
16179
|
+
for (const run of runs) {
|
|
16180
|
+
const stub = uiSurfaceStub(blueprint, run);
|
|
16181
|
+
if (stub) blocks.push(stub);
|
|
15861
16182
|
}
|
|
15862
16183
|
return blocks.join("\n\n");
|
|
15863
16184
|
}
|
|
@@ -15932,6 +16253,7 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
|
|
|
15932
16253
|
params: { source: "path", path: opts.blueprintPath ?? "./prompt.json", expect: "json" }
|
|
15933
16254
|
});
|
|
15934
16255
|
const slots = buildElementSlots(elements);
|
|
16256
|
+
extendPresenceByPromptMentions(slots, blueprint);
|
|
15935
16257
|
slots.forEach((slot, i) => {
|
|
15936
16258
|
nodes.push({
|
|
15937
16259
|
id: slot.id,
|
|
@@ -15945,7 +16267,7 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
|
|
|
15945
16267
|
let videoNode = "spine";
|
|
15946
16268
|
const overlays = blueprint.scenes.flatMap((s) => s.overlays ?? []);
|
|
15947
16269
|
const floating = blueprint.scenes.flatMap((s) => s.floating_elements ?? []);
|
|
15948
|
-
const hasUiPip = blueprint.
|
|
16270
|
+
const hasUiPip = uiRoutedRuns(blueprint).length > 0;
|
|
15949
16271
|
if (overlays.length > 0 || floating.length > 0 || hasUiPip) {
|
|
15950
16272
|
nodes.push({
|
|
15951
16273
|
id: "overlaid",
|
|
@@ -16328,7 +16650,10 @@ function collectClipAdvisories(scene, i, out) {
|
|
|
16328
16650
|
const window = sceneDurationS(scene);
|
|
16329
16651
|
if (window > SEEDANCE_SAFE_MAX_S)
|
|
16330
16652
|
out.oversize.push({ scene: i, scene_s: round22(window), clip_s: ceilToSeedance(window) });
|
|
16331
|
-
const speech = (scene.dialogue ?? []).reduce(
|
|
16653
|
+
const speech = (scene.dialogue ?? []).reduce(
|
|
16654
|
+
(s, line) => s + (line.line ? estSpeechWindowS(line.line, line.start_s, line.end_s) : 0),
|
|
16655
|
+
0
|
|
16656
|
+
);
|
|
16332
16657
|
if (speech > window * OVERSTUFF_RATIO)
|
|
16333
16658
|
out.overstuffed.push({ scene: i, scene_s: round22(window), est_speech_s: round22(speech) });
|
|
16334
16659
|
}
|
|
@@ -16517,7 +16842,7 @@ async function detectShotCutsBestEffort(videoPath, threshold) {
|
|
|
16517
16842
|
process.stderr.write(`Shot-cut detection skipped (${msg}); using LLM boundaries.
|
|
16518
16843
|
`);
|
|
16519
16844
|
}
|
|
16520
|
-
return
|
|
16845
|
+
return void 0;
|
|
16521
16846
|
}
|
|
16522
16847
|
}
|
|
16523
16848
|
function fail2(code, message) {
|
|
@@ -16546,7 +16871,7 @@ function buildDeconstructCanvas(videoPath, deconstructModel, opts) {
|
|
|
16546
16871
|
if (typeof opts.maxScenes === "number") deconstructParams.max_scenes = opts.maxScenes;
|
|
16547
16872
|
if (opts.language) deconstructParams.language = opts.language;
|
|
16548
16873
|
if (opts.focus) deconstructParams.focus = opts.focus;
|
|
16549
|
-
if (opts.shotCuts
|
|
16874
|
+
if (opts.shotCuts) deconstructParams.shot_cuts = opts.shotCuts;
|
|
16550
16875
|
deconstructParams.max_clip_s = SEEDANCE_DURATIONS[SEEDANCE_DURATIONS.length - 1];
|
|
16551
16876
|
return {
|
|
16552
16877
|
schema: "baker-canvas/1",
|
|
@@ -16697,7 +17022,7 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
16697
17022
|
await cp(SHIPPED_COMPOSITION_DIR, compositionDest, { recursive: true });
|
|
16698
17023
|
await stampCompositionDims(compositionDest, outDims);
|
|
16699
17024
|
const indexPath = path9.join(compositionDest, "index.html");
|
|
16700
|
-
const overlayHtml = buildOverlayHtml(blueprint);
|
|
17025
|
+
const overlayHtml = buildOverlayHtml(blueprint, { captionsActive: Boolean(transcript) });
|
|
16701
17026
|
const indexHtml = await readFile6(indexPath, "utf8");
|
|
16702
17027
|
const injected = indexHtml.replace("<!--OVERLAYS-->", () => overlayHtml);
|
|
16703
17028
|
if (injected === indexHtml && overlayHtml.trim()) {
|
|
@@ -16729,15 +17054,31 @@ var scaffoldVideoCommand = defineCommand87({
|
|
|
16729
17054
|
return fail2("scaffold", e instanceof Error ? e.message : String(e));
|
|
16730
17055
|
}
|
|
16731
17056
|
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(canvas, outDir), defaultRegistry());
|
|
17057
|
+
if (!validation.ok) {
|
|
17058
|
+
const meta = canvas.metadata;
|
|
17059
|
+
const todo = meta.todo ?? {};
|
|
17060
|
+
todo.blocking_validation_issues = validation.issues;
|
|
17061
|
+
meta.todo = todo;
|
|
17062
|
+
}
|
|
17063
|
+
await writeFile2(outPath, `${JSON.stringify(canvas, null, 2)}
|
|
17064
|
+
`, "utf8");
|
|
16732
17065
|
if (!validation.ok) {
|
|
16733
17066
|
process.stderr.write(
|
|
16734
|
-
`${JSON.stringify(
|
|
17067
|
+
`${JSON.stringify(
|
|
17068
|
+
{
|
|
17069
|
+
ok: false,
|
|
17070
|
+
error: { code: "validation", issues: validation.issues },
|
|
17071
|
+
canvas_path: outPath,
|
|
17072
|
+
prompt_path: blueprintPath,
|
|
17073
|
+
note: "The canvas WAS written despite the blocking issue(s) \u2014 fix them in place, then `baker canvas validate` before running."
|
|
17074
|
+
},
|
|
17075
|
+
null,
|
|
17076
|
+
2
|
|
17077
|
+
)}
|
|
16735
17078
|
`
|
|
16736
17079
|
);
|
|
16737
17080
|
process.exit(2);
|
|
16738
17081
|
}
|
|
16739
|
-
await writeFile2(outPath, `${JSON.stringify(canvas, null, 2)}
|
|
16740
|
-
`, "utf8");
|
|
16741
17082
|
await ensureGitignore(process.cwd(), ["canvas/", ".context/"]);
|
|
16742
17083
|
process.stdout.write(
|
|
16743
17084
|
`${JSON.stringify(
|