@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/README.md
CHANGED
|
@@ -3855,7 +3855,7 @@ Turn a reference video into a **runnable, self-validated reproduction canvas** i
|
|
|
3855
3855
|
1. **`video_deconstruct`** (`~google/gemini-pro-latest`, full mode) — reverse-engineers the video into a scene-by-scene blueprint + word-level transcript, written next to the canvas as **`prompt.json`**. Each scene's `start_frame_prompt`/`end_frame_prompt` are inlined into the frame nodes (see below); `prompt.json` then rides along as the shared **global style reference** (palette, cast cohesion) and as provenance.
|
|
3856
3856
|
2. **recurring-element selection** (`~google/gemini-flash-latest`) — picks only the **recurring, identity-critical** elements (each `global.cast` person, a recurring animal, a showcased product, the brand logo) and the scene indices each appears in. One real reference image grounds each element across **every** frame it appears in, so the same actor stays consistent the whole video. This selection runs as a **second pass over a slimmed blueprint** (cast/branding + each scene's frame prompts only) — a long ad's full blueprint can exceed the engine's inline-prompt limit, so the heavy per-scene detail (dialogue, overlays, transcript) the selector never reads is dropped before the prompt.
|
|
3857
3857
|
|
|
3858
|
-
Before the deconstruct it runs a **local shot-cut pass** on the source file with **[PySceneDetect](https://www.scenedetect.com)** (`scenedetect` CLI, `detect-content` — the battle-tested HSV content detector, installed in the canvas sandbox) and passes the cut timestamps as `video_deconstruct`'s `shot_cuts`. The deconstruct snaps its scene boundaries onto those real cuts and **splits any scene that spans one**, so a scene's frames can never straddle a hard cut (the failure where a scene's start frame was the couch and its end frame the b-roll). Two knobs tuned for fast social ads: the content **threshold defaults to 18** (PySceneDetect's own default of 27 misses soft reframes) and the **minimum scene length is dropped to 0.25s** (its default ~0.6s merges away rapid montage flashes) — so super-fast cuts survive and become cheap still-holds downstream. The threshold is **adaptive**: if the first pass looks like a continuous shot shredded into many close micro-cuts (a talking-head selfie's natural motion), it re-runs at PySceneDetect's own default of 27 and
|
|
3858
|
+
Before the deconstruct it runs a **local shot-cut pass** on the source file with **[PySceneDetect](https://www.scenedetect.com)** (`scenedetect` CLI, `detect-content` — the battle-tested HSV content detector, installed in the canvas sandbox) and passes the cut timestamps as `video_deconstruct`'s `shot_cuts`. The deconstruct snaps its scene boundaries onto those real cuts and **splits any scene that spans one**, so a scene's frames can never straddle a hard cut (the failure where a scene's start frame was the couch and its end frame the b-roll). Two knobs tuned for fast social ads: the content **threshold defaults to 18** (PySceneDetect's own default of 27 misses soft reframes) and the **minimum scene length is dropped to 0.25s** (its default ~0.6s merges away rapid montage flashes) — so super-fast cuts survive and become cheap still-holds downstream. The threshold is **adaptive**: if the first pass looks like a continuous shot shredded into many close micro-cuts (a talking-head selfie's natural motion), it re-runs at PySceneDetect's own default of 27 and **merges the two passes** — the high-threshold set is the base, and the low pass's *isolated* extras (real soft blur-morph transitions that vanish at 27) are added back while clustered extras (motion shred) stay dropped. Pinning **`--shot-threshold N`** disables the re-check (lower = more cuts). The backend snap window is likewise **adaptive** (up to 1s onto an unambiguous nearest cut, shrinking around dense cut pairs so a boundary never jumps past the wrong cut), any scene spanning an interior cut is split, and the residual-sliver coalesce is **cut-aware**: a drift sliver folds backward across its non-cut edge and never re-merges across a real cut. If `scenedetect` is unavailable it warns loudly and degrades to LLM-only boundaries.
|
|
3859
3859
|
|
|
3860
3860
|
A shot longer than the video model's per-clip ceiling (Seedance's 15s, passed as `video_deconstruct`'s `max_clip_s`) is split into equal **continuation sub-scenes** that share their splice boundary exactly — so a long shot is reproduced in **full** (no truncation) and joins seamlessly. Each sub-scene carries `continues_previous`.
|
|
3861
3861
|
|
|
@@ -3863,9 +3863,17 @@ It then scaffolds the full pipeline like an **editing timeline**: each clip gets
|
|
|
3863
3863
|
|
|
3864
3864
|
**Composited scenes (split-screen / picture-in-picture / keyed presenter).** Real ads aren't always one full-frame shot — a frame can be **persistently divided** (b-roll on top, a presenter talking on the bottom) or **layer a presenter** over background footage (boxed in a corner, or green-screen keyed). The deconstruct now reports this per scene as `scene.composition` (`layout: split_screen | pip | keyed_overlay`, with one `region` per stream — each its own clean-plate frame + motion brief, the talking-head region flagged `is_presenter`). The scaffold reproduces a composited scene by building **one clip per region** (`s<i>_r0_*`, `s<i>_r1_*`, …) and compositing them with ffmpeg: a split-screen `vstack`/`hstack` (stack direction read from the region **panels**, so a top/bottom split always stacks vertically), or a picture-in-picture `overlay` of the presenter inset at its corner. A **keyed** presenter is first cut to transparency by `video_background_remove` (`s<i>_key`), then overlaid. The presenter region carries the native lip-synced voice; b-roll/render panels stay silent. To change a layout, edit `composition` in `prompt.json` and re-scaffold, or hand-edit the `s<i>_composite` ffmpeg args. Plain full-frame scenes (the default) are unaffected.
|
|
3865
3865
|
|
|
3866
|
+
**Typed region kinds & real screen surfaces.** Each composition region now carries a `kind` — `camera` (filmed footage, re-generated), `screen_capture` (app/site/document screen recording), `static_graphic` (designed text/graphic panel), or `generated` (3D/motion graphics) — plus an optional `nested` list for video-in-video (a Loom-style camera bubble inside a screen share). `kind` is authoritative for routing (prose keywords remain the fallback for older blueprints): `screen_capture`/`static_graphic` regions are **never generated by the video model** — the scene renders as a clean background plate (its clip prompt is scrubbed of all screen narration and forbids rendering UI) and the real surface is composited on the overlay layer. The route is decided **once per persistent layout run** (consecutive scenes sharing one composition signature), so a layout that runs unbroken across many scenes can't flip between pipelines on wording differences. A persistent surface seeds **ONE grouped stub** in `video-overlay-composition/index.html` spanning its whole window, with a per-scene **state timeline** — build one continuous screen recording/mockup, not one screenshot per scene. A `screen_capture` region also carries `surface_id`: a source video routinely **splices two unrelated screen recordings** under one persistent layout (a live app-processing capture, then an unrelated pre-made demo note) — the deconstruct assigns a stable id while the SAME recording continues and a new one when the on-screen content genuinely changes, so the run splits into **separate stubs** at the splice instead of asking for one screenshot that can't cover both. `baker canvas validate` additionally warns (`VIDEO_UI_IN_PROMPT`) if any clip prompt still narrates a screen surface, and (`VIDEO_BRANDMARK_IN_PROMPT`) if a generate prompt asks the model to paint a brand logo/wordmark (generation garbles marks; source the real one with `baker images logo` and composite it on the overlay layer).
|
|
3867
|
+
|
|
3868
|
+
**Designed graphics are rebuilt, not generated.** A `static_graphic` surface (a newspaper-collage panel, a meme card, a marketing composition) seeds a **GRAPHIC PANEL** stub — rebuild it as brand HTML or drop the design asset; it never gets the "screenshot the live page" instruction (there is no live page). A **full-frame** designed-graphic scene (the deconstruct emits one full-frame `static_graphic` region for meme/collage/motion-graphic beats) routes to a real design plate the same way screens do — no `image_generate`/`video_generate` — and dialogue over an all-graphic scene is voiceover by definition (nobody is on screen to lip-sync). A region typed `generated` whose own prose reads like a UI/designed panel is treated as a surface candidate too (the frame-grounded continuity checker delivers the verdict and corrects the kind), so one mistyped kind can't re-open the Seedance-paints-UI hole. Floating FX elements (hearts, sparkles, badges) ride the overlay layer: their narration is **scrubbed from clip briefs** and a categorical no-decorations directive is added, so the model can't bake a second, uneditable copy under the real composited one.
|
|
3869
|
+
|
|
3870
|
+
**Same-take continuity.** Scene boundaries that don't land on a detected shot cut are marked `continues_previous` (they're content beats inside one camera take — a genuinely cut-less video is ONE take), and a mid-take phrase clip **chains its start keyframe from the previous phrase's end keyframe**, so one continuous shot never jumps identity/pose at an invented boundary.
|
|
3871
|
+
|
|
3872
|
+
**Speech budgets use the speaker's observed pace.** `metadata.video.talking_scenes` stamps `est_speech_s` measured from the deconstruct's word timings plus `speech_words`; the `VIDEO_SPEECH_OVERRUN` check budgets a (re-authored) line at that real rate instead of a fixed words-per-second guess, so a fast talker's line that demonstrably fits its clip no longer fails validation. Numeral spelling for native clips applies **only to the spoken dialogue/transcript**, never to visual descriptions (a UI progress bar's "4%" stays "4%"). And on a blocking validation issue the canvas is **still written** (with the issues under `metadata.todo.blocking_validation_issues`) — the paid deconstruct is never discarded; fix in place and re-`validate`.
|
|
3873
|
+
|
|
3866
3874
|
**True output aspect, even when the video model can't generate it.** The scaffold resolves TWO ratios: the ad's **output** ratio (the source video's detected `aspect_ratio`, or **`--aspect`**, e.g. `4:5` → a 1080×1350 canvas) and the **generation** ratio — the nearest ratio the video model supports (Seedance has no 4:5, so clips + conditioning keyframes generate at `3:4`). When they differ the scaffold warns loudly, records `metadata.todo.aspect_remap`, normalizes **every clip** onto the output canvas (scale-to-fill, center-crop — keep subjects centered), sizes every ffmpeg plate/still/composite to the output dims, and stamps the copied overlay/caption compositions' `meta.json` to match. Nothing is silently dropped: an unknown ratio snaps to the nearest known one and stays explicit on every node.
|
|
3867
3875
|
|
|
3868
|
-
**Montage flashes held as stills.** A rapid-cut beat shorter than ~2s with no spoken line is a **flash** — Seedance's shortest clip is 4s, so generating one (then trimming away most of it) burns credits for motion no viewer perceives. The scaffold instead **holds one keyframe as a still** for the scene length (a cheap ffmpeg loop, no billed `video_generate`), same look at a fraction of the cost. Talking/ambient beats keep a real clip (they need motion + native audio).
|
|
3876
|
+
**Montage flashes held as stills — unless the picture really moves.** A rapid-cut beat shorter than ~2s with no spoken line is a **flash** — Seedance's shortest clip is 4s, so generating one (then trimming away most of it) burns credits for motion no viewer perceives. The scaffold instead **holds one keyframe as a still** for the scene length (a cheap ffmpeg loop, no billed `video_generate`), same look at a fraction of the cost. The deconstruct now stamps each scene's **`motion_level`** (`static` / `subtle` / `dynamic`): a **dynamic** flash (pouring chocolate, hands working, walking) keeps a **real trimmed clip** — freezing a moving montage turns it into a slideshow — while genuinely static beats (a logo card, a pinned photo, a product still) keep the cheap hold. Talking/ambient beats always keep a real clip (they need motion + native audio). The deconstruct also stamps each dialogue line's **`on_camera`** flag — a voice playing over b-roll, a graphic, or a mere *photo* of the speaker stays voiceover, so the scaffold never lip-syncs a scene with no speaking face (the polaroid close-up failure).
|
|
3869
3877
|
|
|
3870
3878
|
**The phrase model (voice cut at pauses, not at visual cuts).** The voice is grouped into **phrases** — runs of continuous speech with no real pause, which may span several visual scenes. A phrase is voiced ONCE (so a sentence the deconstruct split at a visual cut never breaks mid-word): if the speaker is **shown** anywhere in the phrase it's a single Seedance clip (`s<anchor>_clip`, native lip-sync + audio) re-voiced to the brand voice; if the speaker is **never shown** it's one ElevenLabs `tts` read. The picture is then assembled **scene by scene**: a scene that shows the speaker **slices its window** out of the phrase clip (`s<i>_seg`, an ffmpeg `-ss`/`-t` cut — video and audio come from the *same* clip, so lip-sync holds), and a **b-roll cutaway** gets its own silent clip while the phrase's voice plays underneath. "Shown" is decided by the **presenter element's per-scene presence**, not just who's speaking — a scene where a cast member narrates over b-roll (their element absent) is treated as a cutaway, so the talking head never appears where the original cut away. A presenter run longer than the **gateway-safe ~10s clip ceiling splits at a scene boundary** into contiguous takes (each its own clip + convert), so a sliced window never reads past its clip. (Seedance's *API* max is 15s, but the generation gateway frequently times out — **HTTP 524** — before it can deliver a clip longer than ~10s, so the scaffold never asks for one that long; 10s is a Seedance-allowed duration, so the split clip still snaps cleanly.) A b-roll cutaway *inside* a phrase lands at an **approximate** time (Seedance exposes no word timing) — nudge the scene boundary if it's off its beat.
|
|
3871
3879
|
|
|
@@ -1644,7 +1644,11 @@ var VideoMeta = z.object({
|
|
|
1644
1644
|
// Advisory: the scene's visual length vs the estimated spoken length, so
|
|
1645
1645
|
// a reviewer can see a native line that may run past its cut. Not gated.
|
|
1646
1646
|
scene_s: z.number().optional(),
|
|
1647
|
-
est_speech_s: z.number().optional()
|
|
1647
|
+
est_speech_s: z.number().optional(),
|
|
1648
|
+
// Word count of the line est_speech_s was measured for. Together they carry
|
|
1649
|
+
// the speaker's OBSERVED pace (from the deconstruct's word timings), so the
|
|
1650
|
+
// overrun check budgets re-authored lines at the real rate, not a wps guess.
|
|
1651
|
+
speech_words: z.number().optional()
|
|
1648
1652
|
}),
|
|
1649
1653
|
z.object({ scene: z.number(), lipsync_node: z.string() })
|
|
1650
1654
|
])
|
|
@@ -2174,7 +2178,9 @@ var STAGE_CODES = {
|
|
|
2174
2178
|
SPEECH_OVERRUN: "VIDEO_SPEECH_OVERRUN",
|
|
2175
2179
|
ASPECT_MISMATCH: "VIDEO_ASPECT_MISMATCH",
|
|
2176
2180
|
REFERENCE_MISSING: "VIDEO_REFERENCE_MISSING",
|
|
2177
|
-
SPAN_EXCEEDS_MODEL: "VIDEO_SPAN_EXCEEDS_MODEL"
|
|
2181
|
+
SPAN_EXCEEDS_MODEL: "VIDEO_SPAN_EXCEEDS_MODEL",
|
|
2182
|
+
UI_IN_PROMPT: "VIDEO_UI_IN_PROMPT",
|
|
2183
|
+
BRANDMARK_IN_PROMPT: "VIDEO_BRANDMARK_IN_PROMPT"
|
|
2178
2184
|
};
|
|
2179
2185
|
var SPAN_MODEL_SLACK_S = 0.25;
|
|
2180
2186
|
var VIDEO_TIME_SLACK_S = 0.75;
|
|
@@ -2553,6 +2559,8 @@ function checkVideoInvariants(ctx) {
|
|
|
2553
2559
|
}
|
|
2554
2560
|
checkSpeechOverrun(ctx, meta.talking_scenes);
|
|
2555
2561
|
checkAspectConsistency(ctx);
|
|
2562
|
+
checkUiInPrompt(ctx);
|
|
2563
|
+
checkBrandmarkInPrompt(ctx);
|
|
2556
2564
|
checkReferenceCompleteness(ctx, meta);
|
|
2557
2565
|
checkClipSpanFitsModel(ctx, meta);
|
|
2558
2566
|
}
|
|
@@ -2582,18 +2590,30 @@ function keywordTokens(text) {
|
|
|
2582
2590
|
if (!text) return [];
|
|
2583
2591
|
return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 3 && !KEYWORD_STOPWORDS.has(t));
|
|
2584
2592
|
}
|
|
2585
|
-
function
|
|
2586
|
-
const
|
|
2587
|
-
const typeWords = ELEMENT_TYPE_KEYWORDS[type] ?? [];
|
|
2593
|
+
function elementMentionKeywords(el) {
|
|
2594
|
+
const typeWords = ELEMENT_TYPE_KEYWORDS[el.type.toLowerCase()] ?? [];
|
|
2588
2595
|
return [.../* @__PURE__ */ new Set([...typeWords, ...keywordTokens(el.label), ...keywordTokens(el.description)])];
|
|
2589
2596
|
}
|
|
2597
|
+
function keywordsForElement(el) {
|
|
2598
|
+
return elementMentionKeywords(el);
|
|
2599
|
+
}
|
|
2590
2600
|
function containsWord(text, word) {
|
|
2591
2601
|
const esc = word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2592
2602
|
return new RegExp(`\\b${esc}\\b`, "i").test(text);
|
|
2593
2603
|
}
|
|
2604
|
+
var FRAME_DESCRIPTION_START = "FRAME DESCRIPTION (this frame's editable prompt):";
|
|
2605
|
+
var FRAME_DESCRIPTION_END = "Render exactly what the FRAME DESCRIPTION";
|
|
2606
|
+
function frameDescriptionOf(prompt) {
|
|
2607
|
+
const i = prompt.indexOf(FRAME_DESCRIPTION_START);
|
|
2608
|
+
if (i < 0) return prompt;
|
|
2609
|
+
const rest = prompt.slice(i + FRAME_DESCRIPTION_START.length);
|
|
2610
|
+
const j = rest.indexOf(FRAME_DESCRIPTION_END);
|
|
2611
|
+
return j < 0 ? rest : rest.slice(0, j);
|
|
2612
|
+
}
|
|
2594
2613
|
function checkFrameReferences(ctx, node, index, keyworded) {
|
|
2595
|
-
const
|
|
2596
|
-
if (typeof
|
|
2614
|
+
const rawPrompt = node.params?.prompt;
|
|
2615
|
+
if (typeof rawPrompt !== "string" || rawPrompt.length === 0) return;
|
|
2616
|
+
const prompt = frameDescriptionOf(rawPrompt);
|
|
2597
2617
|
const inputsBlob = JSON.stringify(node.inputs ?? {});
|
|
2598
2618
|
for (const { el, keywords } of keyworded) {
|
|
2599
2619
|
if (inputsBlob.includes(el.ref)) continue;
|
|
@@ -2643,13 +2663,24 @@ function checkClipSpanFitsModel(ctx, meta) {
|
|
|
2643
2663
|
});
|
|
2644
2664
|
}
|
|
2645
2665
|
}
|
|
2646
|
-
|
|
2666
|
+
var OBSERVED_WPS_MIN = 1;
|
|
2667
|
+
var OBSERVED_WPS_MAX = 6;
|
|
2668
|
+
function secondsPerWord(stamped) {
|
|
2669
|
+
const est = stamped?.est_speech_s;
|
|
2670
|
+
const words = stamped?.speech_words;
|
|
2671
|
+
if (est && words && est > 0 && words > 0) {
|
|
2672
|
+
const wps = words / est;
|
|
2673
|
+
if (wps >= OBSERVED_WPS_MIN && wps <= OBSERVED_WPS_MAX) return est / words;
|
|
2674
|
+
}
|
|
2675
|
+
return 1 / SPEECH_WORDS_PER_SECOND;
|
|
2676
|
+
}
|
|
2677
|
+
function speechOverrunOf(node, secPerWord) {
|
|
2647
2678
|
const params = node.params;
|
|
2648
2679
|
if (params?.generate_audio !== true) return null;
|
|
2649
2680
|
const line = nativeDialogueOf(params.prompt);
|
|
2650
2681
|
const duration = typeof params.duration === "number" ? params.duration : void 0;
|
|
2651
2682
|
if (!line || !duration) return null;
|
|
2652
|
-
const estSpeechS = line.split(/\s+/).filter(Boolean).length
|
|
2683
|
+
const estSpeechS = line.split(/\s+/).filter(Boolean).length * secPerWord;
|
|
2653
2684
|
return estSpeechS > duration * SPEECH_OVERRUN_RATIO ? { estSpeechS, duration } : null;
|
|
2654
2685
|
}
|
|
2655
2686
|
function checkSpeechOverrun(ctx, talkingScenes) {
|
|
@@ -2658,7 +2689,7 @@ function checkSpeechOverrun(ctx, talkingScenes) {
|
|
|
2658
2689
|
const nativeClipRe = new RegExp(`^s${entry.scene}(_r\\d+)?_clip$`);
|
|
2659
2690
|
for (const n of ctx.canvas.nodes) {
|
|
2660
2691
|
if (!nativeClipRe.test(n.id) || n.type !== "video_generate") continue;
|
|
2661
|
-
const overrun = speechOverrunOf(n);
|
|
2692
|
+
const overrun = speechOverrunOf(n, secondsPerWord(entry));
|
|
2662
2693
|
if (!overrun) continue;
|
|
2663
2694
|
ctx.issues.push({
|
|
2664
2695
|
path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].params.prompt`,
|
|
@@ -2668,6 +2699,38 @@ function checkSpeechOverrun(ctx, talkingScenes) {
|
|
|
2668
2699
|
}
|
|
2669
2700
|
}
|
|
2670
2701
|
}
|
|
2702
|
+
var UI_IN_PROMPT_RE = /\bscreen[- ]?(?:recording|capture|grab|share)\b|\bapp (?:interface|screen)\b|\bphone screen overlay\b/i;
|
|
2703
|
+
function checkUiInPrompt(ctx) {
|
|
2704
|
+
for (const n of ctx.canvas.nodes) {
|
|
2705
|
+
if (n.type !== "video_generate") continue;
|
|
2706
|
+
const prompt = n.params?.prompt;
|
|
2707
|
+
if (typeof prompt !== "string" || !UI_IN_PROMPT_RE.test(prompt)) continue;
|
|
2708
|
+
ctx.issues.push({
|
|
2709
|
+
path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].params.prompt`,
|
|
2710
|
+
code: STAGE_CODES.UI_IN_PROMPT,
|
|
2711
|
+
severity: "warning",
|
|
2712
|
+
message: `"${n.id}" asks the video model to render a screen/UI surface \u2014 generative video garbles UI text and chrome. Composite the real screen on the overlay layer (screenshot / brand HTML) and keep this prompt to the background plate`,
|
|
2713
|
+
node_id: n.id,
|
|
2714
|
+
node_type: "video_generate"
|
|
2715
|
+
});
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
var BRANDMARK_IN_PROMPT_RE = /\b(?:logo|wordmark) (?:overlay|animation|sting|card|reveal)\b|\b(?:google|facebook|instagram|tiktok|youtube|amazon|apple|microsoft|whatsapp|netflix|spotify|excel|trustpilot) (?:logo|wordmark|branding)\b/i;
|
|
2719
|
+
function checkBrandmarkInPrompt(ctx) {
|
|
2720
|
+
for (const n of ctx.canvas.nodes) {
|
|
2721
|
+
if (n.type !== "video_generate" && n.type !== "image_generate") continue;
|
|
2722
|
+
const prompt = n.params?.prompt;
|
|
2723
|
+
if (typeof prompt !== "string" || !BRANDMARK_IN_PROMPT_RE.test(prompt)) continue;
|
|
2724
|
+
ctx.issues.push({
|
|
2725
|
+
path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].params.prompt`,
|
|
2726
|
+
code: STAGE_CODES.BRANDMARK_IN_PROMPT,
|
|
2727
|
+
severity: "warning",
|
|
2728
|
+
message: `"${n.id}" asks the model to render a brand logo/wordmark \u2014 generation garbles marks and third-party logos carry IP exposure. Source the real mark (baker images logo <domain>) and composite it on the overlay layer`,
|
|
2729
|
+
node_id: n.id,
|
|
2730
|
+
node_type: n.type
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2671
2734
|
function checkAspectConsistency(ctx) {
|
|
2672
2735
|
const clips = ctx.canvas.nodes.filter((n) => n.type === "video_generate");
|
|
2673
2736
|
if (clips.length < 2) return;
|
|
@@ -6489,6 +6552,7 @@ export {
|
|
|
6489
6552
|
IMAGE_GENERATE_MODELS,
|
|
6490
6553
|
MODEL_REGISTRY,
|
|
6491
6554
|
resolveConcurrency,
|
|
6555
|
+
elementMentionKeywords,
|
|
6492
6556
|
BackendClient2 as BackendClient,
|
|
6493
6557
|
Engine2 as Engine,
|
|
6494
6558
|
LocalAssetStore2 as LocalAssetStore,
|
|
@@ -6499,4 +6563,4 @@ export {
|
|
|
6499
6563
|
defaultRegistry,
|
|
6500
6564
|
createEngineFromEnv
|
|
6501
6565
|
};
|
|
6502
|
-
//# sourceMappingURL=chunk-
|
|
6566
|
+
//# sourceMappingURL=chunk-MF34WJ7M.js.map
|