@koda-sl/baker-cli 0.381.0 → 0.382.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 +2 -2
- package/dist/cli.js +22 -3
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5474,7 +5474,7 @@ Before the deconstruct it runs a **local shot-cut pass** on the source file with
|
|
|
5474
5474
|
|
|
5475
5475
|
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`.
|
|
5476
5476
|
|
|
5477
|
-
It then scaffolds the full pipeline like an **editing timeline**: each clip gets a **static-ad-grade start AND end keyframe** (`image_generate`, each with its **own `params.prompt`** regenerated from that scene's `scenes/sNN.json` — edit the scene file to change that frame, never the node prompt by hand; the slim `prompt.style.json` wired as the **shared `target_blueprint`** style reference, plus a per-element reference legend). Each keyframe is **fully recast** to the dropped `el_*` reference images. The original extracted frame is kept LAST as a **pure composition anchor** (framing / camera angle / shot size / pose) only in an ad with **nobody in it** — no deconstructed cast and no recurring person. In any ad with people, no generated frame takes a source frame as a reference: an image model copies the face of whoever is in its references, whatever the prompt says, and which scenes a person is in is itself a model's guess — so framing is carried by the frame prompt instead. **People are cast from company avatars** (`--avatar <handle>`): each recurring person grounds on that avatar's identity sheet, used as-is (no sheet is built from it); a person with no avatar gets a `[TODO]` asking for one, never for a found photo. Both keyframes feed `video_generate` (`first_frame`+`last_frame`, so Seedance interpolates real in-shot motion; ultra-detailed motion brief; duration snapped to the nearest allowed clip length). Every keyframe grounds **only on its own extracted frame + `el_*` slots** — no reference to any other generated frame — so all images render **in parallel** (no cascade). Source-frame URLs are **deduped** (each ingested once). `--frames reuse` wires the real source frame straight in.
|
|
5477
|
+
It then scaffolds the full pipeline like an **editing timeline**: each clip gets a **static-ad-grade start AND end keyframe** (`image_generate`, each with its **own `params.prompt`** regenerated from that scene's `scenes/sNN.json` — edit the scene file to change that frame, never the node prompt by hand; the slim `prompt.style.json` wired as the **shared `target_blueprint`** style reference, plus a per-element reference legend). Each keyframe is **fully recast** to the dropped `el_*` reference images. The original extracted frame is kept LAST as a **pure composition anchor** (framing / camera angle / shot size / pose) only in an ad with **nobody in it** — no deconstructed cast and no recurring person. In any ad with people, no generated frame takes a source frame as a reference: an image model copies the face of whoever is in its references, whatever the prompt says, and which scenes a person is in is itself a model's guess — so framing is carried by the frame prompt instead. **People are cast from company avatars** (`--avatar <handle>`): each recurring person grounds on that avatar's identity sheet, used as-is (no sheet is built from it); a person with no avatar gets a `[TODO]` asking for one, never for a found photo. Both keyframes feed `video_generate` (`first_frame`+`last_frame`, so Seedance interpolates real in-shot motion; ultra-detailed motion brief; duration snapped to the nearest allowed clip length). Every keyframe grounds **only on its own extracted frame + `el_*` slots** — no reference to any other generated frame — so all images render **in parallel** (no cascade). Source-frame URLs are **deduped** (each ingested once). `--frames reuse` wires the real source frame straight in — only on a video with nobody in it: when the deconstruct found a cast or a recurring person, reuse is refused (`frames_reuse_refused`) and every frame is generated, so the source's actors never reach the ad.
|
|
5478
5478
|
|
|
5479
5479
|
**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 that scene's `scenes/sNN.json` and re-scaffold, or hand-edit the `s<i>_composite` ffmpeg args. Plain full-frame scenes (the default) are unaffected.
|
|
5480
5480
|
|
|
@@ -5528,7 +5528,7 @@ baker canvas run ./reference-ad.video.canvas.json
|
|
|
5528
5528
|
|---|---|---|
|
|
5529
5529
|
| `--out <path>` | `<video-dir>/<name>.video.canvas.json` | Where to write the canvas (composition is copied alongside). |
|
|
5530
5530
|
| `--slug <slug>` | — | Creative slug (lowercase kebab): writes the canvas to `src/creatives/<slug>/<slug>.canvas.json` — the repo convention that attaches every run to the creative's dashboard generation history. `--out` wins over `--slug`. |
|
|
5531
|
-
| `--frames <mode>` | `generate` | `generate` emits ONE recast keyframe per scene (the original frame is dropped so the dropped `el_*` assets drive identity); `reuse` wires the real extracted first+last frames straight into the clips (faithful, cheaper, no recast). |
|
|
5531
|
+
| `--frames <mode>` | `generate` | `generate` emits ONE recast keyframe per scene (the original frame is dropped so the dropped `el_*` assets drive identity); `reuse` wires the real extracted first+last frames straight into the clips (faithful, cheaper, no recast) — only on a video with nobody in it; on one showing people it is refused and every frame is generated. |
|
|
5532
5532
|
| `--ambient` | off | Give silent **b-roll** scenes native diegetic ambient (Seedance `generate_audio`), mixed deep under the music bed. Talking scenes already carry voice; check levels don't muddy the mix before keeping it. |
|
|
5533
5533
|
| `--seam-dedup <mode>` | `head` | How to dedup the boundary frame two clips SHARE when a long shot is split for length (the second clip's first frame IS the first clip's last frame, so a plain concat freezes on it for a frame). `head` drops the second clip's first frame, `tail` drops the first clip's last frame, `off` keeps both. Only touches shared-frame continuation joins — a hard cut between two shots shares no frame. |
|
|
5534
5534
|
| `--max-scenes <n>` | all source scenes | **Cost lever that reduces fidelity** — caps the deconstruct, MERGING away every scene beyond the cap (fewer cuts, lost beats). Prints a warning when set; omit it to reproduce every scene. |
|
package/dist/cli.js
CHANGED
|
@@ -40790,10 +40790,18 @@ function dropUncastVoices(nodes) {
|
|
|
40790
40790
|
if (node && node.type === "voice_select" && !referenced.has(node.id)) nodes.splice(i, 1);
|
|
40791
40791
|
}
|
|
40792
40792
|
}
|
|
40793
|
-
function
|
|
40793
|
+
function framesReuseRefusal(blueprint, elements, requested) {
|
|
40794
|
+
if (requested !== "reuse") return void 0;
|
|
40795
|
+
const showsPeople = (blueprint.global?.cast ?? []).length > 0 || elements.some((e) => e.type.toLowerCase() === "person");
|
|
40796
|
+
if (!showsPeople) return void 0;
|
|
40797
|
+
return "--frames reuse was not applied: this video shows people, and reusing its frames would put the source's actors into our ad. Every frame is generated instead, recast to the el_* references. Reuse applies only to a video with nobody in it.";
|
|
40798
|
+
}
|
|
40799
|
+
function scaffoldVideoCanvas(input, elementsInput, requested) {
|
|
40794
40800
|
const blueprint = VideoBlueprint.parse(input);
|
|
40795
40801
|
injectHookPhysicality(blueprint);
|
|
40796
40802
|
const elements = RecurringElements.parse(elementsInput);
|
|
40803
|
+
const reuseRefused = framesReuseRefusal(blueprint, elements, requested.frames);
|
|
40804
|
+
const opts = reuseRefused ? { ...requested, frames: "generate" } : requested;
|
|
40797
40805
|
const clock = spineClockFor(blueprint);
|
|
40798
40806
|
const nodes = [];
|
|
40799
40807
|
emitBlueprintIngests(opts, nodes);
|
|
@@ -40896,6 +40904,7 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
|
|
|
40896
40904
|
}
|
|
40897
40905
|
const todo = {
|
|
40898
40906
|
...buildVideoTodo(videoReport(input, elementsInput), overlays.length, floating.length, opts, blueprint),
|
|
40907
|
+
...reuseRefused ? { frames_reuse_refused: reuseRefused } : {},
|
|
40899
40908
|
...aspectRemapTodo(resolveAspect(blueprint.source?.aspect_ratio, opts.aspect, genAspectsFor(opts.videoModel))),
|
|
40900
40909
|
model_constraints: buildModelConstraints()
|
|
40901
40910
|
};
|
|
@@ -45822,6 +45831,12 @@ function videoDefinitionDescription(blueprint) {
|
|
|
45822
45831
|
const product = g.branding?.product;
|
|
45823
45832
|
return typeof product === "string" && product.trim() ? product.trim() : void 0;
|
|
45824
45833
|
}
|
|
45834
|
+
function framesModeReport(canvas, requested) {
|
|
45835
|
+
const todo = canvas.metadata?.todo;
|
|
45836
|
+
const mode = typeof todo?.frames_mode === "string" ? todo.frames_mode : requested;
|
|
45837
|
+
const refused = typeof todo?.frames_reuse_refused === "string" ? todo.frames_reuse_refused : void 0;
|
|
45838
|
+
return { frames_mode: mode, ...refused ? { frames_reuse_refused: refused } : {} };
|
|
45839
|
+
}
|
|
45825
45840
|
async function materializeReferenceVideo(fileArg2) {
|
|
45826
45841
|
if (!/^https?:\/\//i.test(fileArg2)) return path26.resolve(fileArg2);
|
|
45827
45842
|
let bytes;
|
|
@@ -45979,7 +45994,10 @@ var scaffoldVideoCommand = defineCommand116({
|
|
|
45979
45994
|
type: "string",
|
|
45980
45995
|
description: "Creative slug \u2014 writes the canvas to src/creatives/<slug>/<slug>.canvas.json (repo convention)"
|
|
45981
45996
|
},
|
|
45982
|
-
frames: {
|
|
45997
|
+
frames: {
|
|
45998
|
+
type: "string",
|
|
45999
|
+
description: `"generate" (default, every frame regenerated for our ad) or "reuse" (wire the source's real frames in). Reuse applies only to a video with nobody in it \u2014 on a video showing people it is refused and every frame is generated, so the source's actors never reach our ad.`
|
|
46000
|
+
},
|
|
45983
46001
|
ambient: {
|
|
45984
46002
|
type: "boolean",
|
|
45985
46003
|
description: "Give silent b-roll scenes native diegetic ambient mixed deep under the music bed (off by default)"
|
|
@@ -46182,6 +46200,7 @@ var scaffoldVideoCommand = defineCommand116({
|
|
|
46182
46200
|
error: { code: "validation", issues: validation.issues },
|
|
46183
46201
|
canvas_path: outPath,
|
|
46184
46202
|
prompt_path: blueprintPath,
|
|
46203
|
+
...framesModeReport(canvas, frames),
|
|
46185
46204
|
note: "The canvas WAS written despite the blocking issue(s) \u2014 fix them in place, then `baker canvas validate` before running.",
|
|
46186
46205
|
...uncastHint
|
|
46187
46206
|
},
|
|
@@ -46233,7 +46252,7 @@ var scaffoldVideoCommand = defineCommand116({
|
|
|
46233
46252
|
composition_dir: compositionDest,
|
|
46234
46253
|
output: canvas.output,
|
|
46235
46254
|
...uncastHint,
|
|
46236
|
-
|
|
46255
|
+
...framesModeReport(canvas, frames),
|
|
46237
46256
|
models: {
|
|
46238
46257
|
deconstruct: deconstructModel,
|
|
46239
46258
|
select: selectModel,
|