@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 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 scaffoldVideoCanvas(input, elementsInput, opts) {
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: { type: "string", description: '"generate" (default, anchored regen) or "reuse" (wire real frames in)' },
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
- frames_mode: frames,
46255
+ ...framesModeReport(canvas, frames),
46237
46256
  models: {
46238
46257
  deconstruct: deconstructModel,
46239
46258
  select: selectModel,