@koda-sl/baker-cli 0.112.1 → 0.113.1
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 +6 -1
- package/canvas/tiktok-captions-composition/index.html +4 -1
- package/canvas/video-overlay-composition/index.html +4 -1
- package/dist/{chunk-3JVYU72O.js → chunk-7K2YAWUT.js} +103 -4
- package/dist/chunk-7K2YAWUT.js.map +1 -0
- package/dist/cli.js +234 -39
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +2 -0
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-3JVYU72O.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
import {
|
|
3
3
|
ELEVENLABS_MAX_MUSIC_LENGTH_MS,
|
|
4
4
|
IMAGE_GENERATE_MODELS,
|
|
5
|
+
LayerExecutionError,
|
|
5
6
|
MODEL_REGISTRY,
|
|
6
7
|
SEEDANCE_DURATIONS,
|
|
7
8
|
ValidationError,
|
|
8
9
|
createEngineFromEnv,
|
|
9
10
|
defaultRegistry,
|
|
11
|
+
describeFailureReason,
|
|
10
12
|
generateCatalog,
|
|
13
|
+
resolveConcurrency,
|
|
11
14
|
validateCanvasDeep
|
|
12
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-7K2YAWUT.js";
|
|
13
16
|
|
|
14
17
|
// src/cli.ts
|
|
15
18
|
import { defineCommand as defineCommand155, runMain } from "citty";
|
|
@@ -11126,6 +11129,10 @@ var runCommand = defineCommand83({
|
|
|
11126
11129
|
"outputs-dir": { type: "string", description: "Per-run outputs root (default ./canvas)" },
|
|
11127
11130
|
"run-id": { type: "string", description: "Override run id" },
|
|
11128
11131
|
"cache-policy": { type: "string", description: "read_write | bypass | read_only" },
|
|
11132
|
+
concurrency: {
|
|
11133
|
+
type: "string",
|
|
11134
|
+
description: "Max nodes per layer in flight at once (default 5; env BAKER_CANVAS_CONCURRENCY)"
|
|
11135
|
+
},
|
|
11129
11136
|
"keep-runs": {
|
|
11130
11137
|
type: "string",
|
|
11131
11138
|
description: "After the run, prune old r_* run dirs, keeping the N newest (off by default)"
|
|
@@ -11173,7 +11180,11 @@ var runCommand = defineCommand83({
|
|
|
11173
11180
|
const policy = args["cache-policy"] ?? "read_write";
|
|
11174
11181
|
const result = await engine.run(parsed, {
|
|
11175
11182
|
run_id: args["run-id"] ? String(args["run-id"]) : void 0,
|
|
11176
|
-
cache_policy: policy
|
|
11183
|
+
cache_policy: policy,
|
|
11184
|
+
concurrency: resolveConcurrency(
|
|
11185
|
+
args.concurrency !== void 0 ? String(args.concurrency) : void 0,
|
|
11186
|
+
process.env.BAKER_CANVAS_CONCURRENCY
|
|
11187
|
+
)
|
|
11177
11188
|
});
|
|
11178
11189
|
const keepRuns = args["keep-runs"] !== void 0 ? Number(args["keep-runs"]) : void 0;
|
|
11179
11190
|
if (keepRuns !== void 0 && Number.isFinite(keepRuns)) {
|
|
@@ -11203,6 +11214,14 @@ var runCommand = defineCommand83({
|
|
|
11203
11214
|
);
|
|
11204
11215
|
process.exit(2);
|
|
11205
11216
|
}
|
|
11217
|
+
if (e instanceof LayerExecutionError) {
|
|
11218
|
+
const failures = e.failures.map((f) => ({ node_id: f.nodeId, message: describeFailureReason(f.reason) }));
|
|
11219
|
+
process.stderr.write(
|
|
11220
|
+
`${JSON.stringify({ ok: false, error: { code: "runtime", message: e.message, failures } }, null, 2)}
|
|
11221
|
+
`
|
|
11222
|
+
);
|
|
11223
|
+
process.exit(1);
|
|
11224
|
+
}
|
|
11206
11225
|
const msg = e instanceof Error ? e.message : String(e);
|
|
11207
11226
|
process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "runtime", message: msg } }, null, 2)}
|
|
11208
11227
|
`);
|
|
@@ -11534,8 +11553,9 @@ function buildDescribeCanvas(imageSource, imageIsUrl, describeModel, selectModel
|
|
|
11534
11553
|
prompt: LAYOUT_PROMPT
|
|
11535
11554
|
}
|
|
11536
11555
|
}
|
|
11537
|
-
]
|
|
11538
|
-
output:
|
|
11556
|
+
]
|
|
11557
|
+
// No declared output: the engine's prune-to-output would drop `layout` (nothing
|
|
11558
|
+
// consumes it) and runVisionPasses reads all THREE node outputs after the run.
|
|
11539
11559
|
};
|
|
11540
11560
|
}
|
|
11541
11561
|
async function runVisionPasses(canvas) {
|
|
@@ -11896,7 +11916,43 @@ var NARRATOR_SPEAKERS = /* @__PURE__ */ new Set([
|
|
|
11896
11916
|
"offscreen",
|
|
11897
11917
|
"off-screen"
|
|
11898
11918
|
]);
|
|
11899
|
-
var
|
|
11919
|
+
var SEEDANCE_GEN_ASPECTS = ["1:1", "3:4", "9:16", "4:3", "16:9", "21:9"];
|
|
11920
|
+
var OUTPUT_ASPECTS = ["1:1", "16:9", "9:16", "4:3", "3:4", "21:9", "4:5", "2:3", "3:2"];
|
|
11921
|
+
function parseRatio(ar) {
|
|
11922
|
+
const m = ar?.trim().match(/^(\d+(?:\.\d+)?)[:x/](\d+(?:\.\d+)?)$/);
|
|
11923
|
+
if (!m) return void 0;
|
|
11924
|
+
const w = Number(m[1]);
|
|
11925
|
+
const h = Number(m[2]);
|
|
11926
|
+
return w > 0 && h > 0 ? w / h : void 0;
|
|
11927
|
+
}
|
|
11928
|
+
function nearestAspect(ratio, candidates) {
|
|
11929
|
+
let best = candidates[0];
|
|
11930
|
+
let bestDelta = Number.POSITIVE_INFINITY;
|
|
11931
|
+
for (const c of candidates) {
|
|
11932
|
+
const r = parseRatio(c);
|
|
11933
|
+
if (r === void 0) continue;
|
|
11934
|
+
const delta = Math.abs(ratio - r);
|
|
11935
|
+
if (delta < bestDelta) {
|
|
11936
|
+
best = c;
|
|
11937
|
+
bestDelta = delta;
|
|
11938
|
+
}
|
|
11939
|
+
}
|
|
11940
|
+
return best;
|
|
11941
|
+
}
|
|
11942
|
+
function resolveAspect(sourceAr, override, genAspects = SEEDANCE_GEN_ASPECTS) {
|
|
11943
|
+
let outAr;
|
|
11944
|
+
if (override !== void 0) {
|
|
11945
|
+
if (!OUTPUT_ASPECTS.includes(override)) {
|
|
11946
|
+
throw new Error(`unsupported --aspect "${override}" \u2014 supported aspect ratios: ${OUTPUT_ASPECTS.join(", ")}`);
|
|
11947
|
+
}
|
|
11948
|
+
outAr = override;
|
|
11949
|
+
} else {
|
|
11950
|
+
const ratio = parseRatio(sourceAr);
|
|
11951
|
+
outAr = ratio === void 0 ? "9:16" : nearestAspect(ratio, OUTPUT_ASPECTS);
|
|
11952
|
+
}
|
|
11953
|
+
const genAr = genAspects.includes(outAr) ? outAr : nearestAspect(parseRatio(outAr), genAspects);
|
|
11954
|
+
return { outAr, genAr, remapped: genAr !== outAr };
|
|
11955
|
+
}
|
|
11900
11956
|
var EDGES = ["start", "end"];
|
|
11901
11957
|
function snapToSeedance(durationS) {
|
|
11902
11958
|
if (!Number.isFinite(durationS) || durationS <= 0) return SEEDANCE_DURATIONS[0];
|
|
@@ -11934,6 +11990,12 @@ function canvasDims(ar) {
|
|
|
11934
11990
|
return { w: 1080, h: 1440 };
|
|
11935
11991
|
case "21:9":
|
|
11936
11992
|
return { w: 1920, h: 822 };
|
|
11993
|
+
case "4:5":
|
|
11994
|
+
return { w: 1080, h: 1350 };
|
|
11995
|
+
case "2:3":
|
|
11996
|
+
return { w: 1080, h: 1620 };
|
|
11997
|
+
case "3:2":
|
|
11998
|
+
return { w: 1620, h: 1080 };
|
|
11937
11999
|
default:
|
|
11938
12000
|
return { w: 1080, h: 1920 };
|
|
11939
12001
|
}
|
|
@@ -11996,13 +12058,14 @@ function stillHoldArgs(durationS, dims) {
|
|
|
11996
12058
|
"{{out.video}}"
|
|
11997
12059
|
];
|
|
11998
12060
|
}
|
|
11999
|
-
function trimArgs(durationS, offsetS = 0) {
|
|
12061
|
+
function trimArgs(durationS, offsetS = 0, dims) {
|
|
12000
12062
|
return [
|
|
12001
12063
|
"-i",
|
|
12002
12064
|
"{{in.clip}}",
|
|
12003
12065
|
...offsetS > 0 ? ["-ss", offsetS.toFixed(3)] : [],
|
|
12004
12066
|
"-t",
|
|
12005
12067
|
durationS.toFixed(3),
|
|
12068
|
+
...dims ? ["-vf", `scale=${dims.w}:${dims.h}:force_original_aspect_ratio=increase,crop=${dims.w}:${dims.h},setsar=1`] : [],
|
|
12006
12069
|
"-an",
|
|
12007
12070
|
"-c:v",
|
|
12008
12071
|
"libx264",
|
|
@@ -12207,9 +12270,16 @@ function presenceOf(el) {
|
|
|
12207
12270
|
}
|
|
12208
12271
|
return map;
|
|
12209
12272
|
}
|
|
12210
|
-
function
|
|
12211
|
-
|
|
12212
|
-
|
|
12273
|
+
function aspectRemapTodo(aspect) {
|
|
12274
|
+
if (!aspect.remapped) return {};
|
|
12275
|
+
const dims = canvasDims(aspect.outAr);
|
|
12276
|
+
const trims = (parseRatio(aspect.genAr) ?? 0) > (parseRatio(aspect.outAr) ?? 0) ? "sides" : "top/bottom";
|
|
12277
|
+
return {
|
|
12278
|
+
aspect_remap: `Output is ${aspect.outAr} (${dims.w}\xD7${dims.h}) but the video model generates ${aspect.genAr}; every clip is center-cropped onto the ${aspect.outAr} canvas before the spine. Keep subjects centered in frame prompts \u2014 the crop trims the ${trims}.`
|
|
12279
|
+
};
|
|
12280
|
+
}
|
|
12281
|
+
function genAspectsFor(videoModel) {
|
|
12282
|
+
return /\bveo\b|\/veo-/.test(videoModel) ? ["16:9", "9:16"] : SEEDANCE_GEN_ASPECTS;
|
|
12213
12283
|
}
|
|
12214
12284
|
function annotateBlueprintWithElements(blueprintInput, elementsInput) {
|
|
12215
12285
|
if (!blueprintInput || typeof blueprintInput !== "object") return blueprintInput;
|
|
@@ -12350,7 +12420,10 @@ function buildElementSheets(slots, nodes) {
|
|
|
12350
12420
|
inputs: { references: [slot.ref] },
|
|
12351
12421
|
params: {
|
|
12352
12422
|
model: ACTOR_SHEET_MODEL,
|
|
12353
|
-
|
|
12423
|
+
// The clean-plate clause mirrors the frame prompts' CLEAN PLATE block: a sheet
|
|
12424
|
+
// that comes back with a fake camera app baked in (P2-22) poisons EVERY frame
|
|
12425
|
+
// grounded on it, so the suppression must live on the sheet too.
|
|
12426
|
+
subject_description: `${slot.description ?? `the ${slot.type}`} \u2014 clean plate: no phone-camera UI chrome, no app interface, no watermarks, no captions or on-image text`,
|
|
12354
12427
|
subject_type: subjectType,
|
|
12355
12428
|
// 4K: the sheet packs up to 8 cells (angles + tight face/detail close-ups), and
|
|
12356
12429
|
// it's the ONE reference every frame grounds on — per-cell sharpness here
|
|
@@ -12453,6 +12526,10 @@ function buildFramePrompt(edge, sceneIndex, framePrompt, present, hasAnchor, mod
|
|
|
12453
12526
|
"{{target_blueprint}}"
|
|
12454
12527
|
].join("\n");
|
|
12455
12528
|
}
|
|
12529
|
+
function silentClipTranscript(scene, native) {
|
|
12530
|
+
if (native) return "";
|
|
12531
|
+
return (scene.transcript_slice ?? []).map((w) => w.text?.trim()).filter(Boolean).join(" ").trim();
|
|
12532
|
+
}
|
|
12456
12533
|
function ingestFrameRef(url, edge, ctx, nodes) {
|
|
12457
12534
|
const cached2 = ctx.ingestCache?.get(url);
|
|
12458
12535
|
if (cached2) return cached2;
|
|
@@ -12479,7 +12556,7 @@ function buildFrameRef(edge, url, framePrompt, present, ctx, nodes) {
|
|
|
12479
12556
|
image_size: "2K",
|
|
12480
12557
|
prompt: buildFramePrompt(edge, ctx.sceneIndex, framePrompt, present, hasOriginal, ctx.shootMode)
|
|
12481
12558
|
};
|
|
12482
|
-
if (ctx.
|
|
12559
|
+
if (ctx.genAr) genParams.aspect_ratio = ctx.genAr;
|
|
12483
12560
|
const genId = `s${ctx.sceneIndex}${tag}_${edge}`;
|
|
12484
12561
|
nodes.push({
|
|
12485
12562
|
id: genId,
|
|
@@ -12528,7 +12605,7 @@ function buildSeedancePrompt(scene, sceneIndex, present, mode, audio, nativeLine
|
|
|
12528
12605
|
if (lines.length > 0)
|
|
12529
12606
|
parts.push(`Spoken context (do not render as audio): ${lines.map((l) => `"${l}"`).join(" ")}`);
|
|
12530
12607
|
}
|
|
12531
|
-
const transcript = (scene
|
|
12608
|
+
const transcript = silentClipTranscript(scene, Boolean(nativeLine));
|
|
12532
12609
|
if (transcript) parts.push(`Transcript: ${loc(transcript)}`);
|
|
12533
12610
|
const audioLine = seedanceAudioLine(scene, mode, audio, nativeLine);
|
|
12534
12611
|
if (audioLine) parts.push(audioLine);
|
|
@@ -12655,7 +12732,7 @@ function emitSceneClip(i, scene, present, mode, nativeTurn, ambientBroll, frames
|
|
|
12655
12732
|
// ambient b-roll beat generates diegetic ambient only; otherwise the clip is silent.
|
|
12656
12733
|
generate_audio: Boolean(nativeTurn) || ambientBroll
|
|
12657
12734
|
};
|
|
12658
|
-
if (opts.
|
|
12735
|
+
if (opts.genAr) clipParams.aspect_ratio = opts.genAr;
|
|
12659
12736
|
nodes.push({
|
|
12660
12737
|
id: `s${i}${tag}_clip`,
|
|
12661
12738
|
type: "video_generate",
|
|
@@ -12663,12 +12740,13 @@ function emitSceneClip(i, scene, present, mode, nativeTurn, ambientBroll, frames
|
|
|
12663
12740
|
params: clipParams
|
|
12664
12741
|
});
|
|
12665
12742
|
const base = `$ref:s${i}${tag}_clip.video`;
|
|
12666
|
-
|
|
12743
|
+
const normDims = opts.outAr && opts.genAr !== opts.outAr ? canvasDims(opts.outAr) : void 0;
|
|
12744
|
+
if (lengths.genDur === lengths.trimTarget && !normDims) return { ref: base, scene_s: lengths.dur, out };
|
|
12667
12745
|
nodes.push({
|
|
12668
12746
|
id: `s${i}${tag}_clip_trim`,
|
|
12669
12747
|
type: "ffmpeg",
|
|
12670
12748
|
inputs: { clip: base },
|
|
12671
|
-
params: { args: trimArgs(lengths.trimTarget), outputs: { video: { kind: "video", ext: "mp4" } } }
|
|
12749
|
+
params: { args: trimArgs(lengths.trimTarget, 0, normDims), outputs: { video: { kind: "video", ext: "mp4" } } }
|
|
12672
12750
|
});
|
|
12673
12751
|
return { ref: `$ref:s${i}${tag}_clip_trim.video`, scene_s: lengths.dur, out };
|
|
12674
12752
|
}
|
|
@@ -12750,7 +12828,7 @@ function slotsForRegion(present, isPresenter) {
|
|
|
12750
12828
|
});
|
|
12751
12829
|
}
|
|
12752
12830
|
function buildCompositeScene(layout, regions, comp, scene, i, present, mode, nativeTurn, lengths, out, opts, nodes) {
|
|
12753
|
-
const dims = canvasDims(opts.
|
|
12831
|
+
const dims = canvasDims(opts.outAr);
|
|
12754
12832
|
const presIdx = presenterIndexOf(regions, Boolean(nativeTurn));
|
|
12755
12833
|
const regionRefs = [];
|
|
12756
12834
|
let presenterPosition;
|
|
@@ -12760,7 +12838,7 @@ function buildCompositeScene(layout, regions, comp, scene, i, present, mode, nat
|
|
|
12760
12838
|
const regionSlots = slotsForRegion(present, isPresenter);
|
|
12761
12839
|
const ctx = {
|
|
12762
12840
|
sceneIndex: i,
|
|
12763
|
-
|
|
12841
|
+
genAr: opts.genAr,
|
|
12764
12842
|
reuse: opts.reuse,
|
|
12765
12843
|
imageModel: opts.imageModel,
|
|
12766
12844
|
shootMode: mode,
|
|
@@ -12787,7 +12865,13 @@ function buildCompositeScene(layout, regions, comp, scene, i, present, mode, nat
|
|
|
12787
12865
|
{ first, last },
|
|
12788
12866
|
lengths,
|
|
12789
12867
|
null,
|
|
12790
|
-
{
|
|
12868
|
+
{
|
|
12869
|
+
outAr: opts.outAr,
|
|
12870
|
+
genAr: opts.genAr,
|
|
12871
|
+
videoModel: opts.videoModel,
|
|
12872
|
+
resolution: opts.resolution,
|
|
12873
|
+
nativeLang: opts.nativeLang
|
|
12874
|
+
},
|
|
12791
12875
|
nodes,
|
|
12792
12876
|
tag
|
|
12793
12877
|
);
|
|
@@ -12864,7 +12948,7 @@ function emitCompositeScene(composite, scene, i, present, mode, nativeTurn, leng
|
|
|
12864
12948
|
);
|
|
12865
12949
|
clips.push(built.clip);
|
|
12866
12950
|
}
|
|
12867
|
-
function emitFlashHold(i, scene, slots, ctx, lengths, out,
|
|
12951
|
+
function emitFlashHold(i, scene, slots, ctx, lengths, out, outAr, nodes, clips) {
|
|
12868
12952
|
const frame = buildFrameRef(
|
|
12869
12953
|
"start",
|
|
12870
12954
|
scene.start_frame_asset?.url,
|
|
@@ -12878,13 +12962,13 @@ function emitFlashHold(i, scene, slots, ctx, lengths, out, ar, nodes, clips) {
|
|
|
12878
12962
|
type: "ffmpeg",
|
|
12879
12963
|
inputs: { frame },
|
|
12880
12964
|
params: {
|
|
12881
|
-
args: stillHoldArgs(lengths.trimTarget, canvasDims(
|
|
12965
|
+
args: stillHoldArgs(lengths.trimTarget, canvasDims(outAr)),
|
|
12882
12966
|
outputs: { video: { kind: "video", ext: "mp4" } }
|
|
12883
12967
|
}
|
|
12884
12968
|
});
|
|
12885
12969
|
clips.push({ ref: `$ref:s${i}_clip.video`, scene_s: lengths.dur, out });
|
|
12886
12970
|
}
|
|
12887
|
-
function emitScreenScene(i, scene, lengths, out,
|
|
12971
|
+
function emitScreenScene(i, scene, lengths, out, outAr, nodes, clips) {
|
|
12888
12972
|
const label = commentSafe((scene.summary || scene.start_frame_prompt || "the app screen").slice(0, 120));
|
|
12889
12973
|
const refId = `s${i}_screen_ref`;
|
|
12890
12974
|
nodes.push({
|
|
@@ -12901,7 +12985,7 @@ function emitScreenScene(i, scene, lengths, out, ar, nodes, clips) {
|
|
|
12901
12985
|
type: "ffmpeg",
|
|
12902
12986
|
inputs: { frame: `$ref:${refId}.asset` },
|
|
12903
12987
|
params: {
|
|
12904
|
-
args: screenStillArgs(lengths.trimTarget, canvasDims(
|
|
12988
|
+
args: screenStillArgs(lengths.trimTarget, canvasDims(outAr)),
|
|
12905
12989
|
outputs: { video: { kind: "video", ext: "mp4" } }
|
|
12906
12990
|
}
|
|
12907
12991
|
});
|
|
@@ -12943,13 +13027,13 @@ function colorPlateArgs(durationS, dims, color) {
|
|
|
12943
13027
|
"{{out.video}}"
|
|
12944
13028
|
];
|
|
12945
13029
|
}
|
|
12946
|
-
function emitBrandCardScene(i, lengths, out,
|
|
13030
|
+
function emitBrandCardScene(i, lengths, out, outAr, color, nodes, clips) {
|
|
12947
13031
|
nodes.push({
|
|
12948
13032
|
id: `s${i}_clip`,
|
|
12949
13033
|
type: "ffmpeg",
|
|
12950
13034
|
inputs: {},
|
|
12951
13035
|
params: {
|
|
12952
|
-
args: colorPlateArgs(lengths.trimTarget, canvasDims(
|
|
13036
|
+
args: colorPlateArgs(lengths.trimTarget, canvasDims(outAr), color),
|
|
12953
13037
|
outputs: { video: { kind: "video", ext: "mp4" } }
|
|
12954
13038
|
}
|
|
12955
13039
|
});
|
|
@@ -13081,6 +13165,35 @@ function makePresenterPresent(slots, canonical, opts = {}) {
|
|
|
13081
13165
|
var PAUSE_GAP_S = 0.6;
|
|
13082
13166
|
var SEEDANCE_SAFE_MAX_S = SEEDANCE_DURATIONS.find((d) => d >= 10) ?? 10;
|
|
13083
13167
|
var PHRASE_MAX_S = SEEDANCE_SAFE_MAX_S;
|
|
13168
|
+
var JOIN_DEDUP_MAX_WORDS = 4;
|
|
13169
|
+
function joinKey(word) {
|
|
13170
|
+
return word.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "");
|
|
13171
|
+
}
|
|
13172
|
+
function joinDialogueTexts(texts) {
|
|
13173
|
+
const out = [];
|
|
13174
|
+
for (const text of texts) {
|
|
13175
|
+
const prev = out[out.length - 1];
|
|
13176
|
+
if (!prev) {
|
|
13177
|
+
out.push(text);
|
|
13178
|
+
continue;
|
|
13179
|
+
}
|
|
13180
|
+
const prevWords = prev.split(/\s+/).filter(Boolean);
|
|
13181
|
+
const nextWords = text.split(/\s+/).filter(Boolean);
|
|
13182
|
+
let overlap = 0;
|
|
13183
|
+
const maxK = Math.min(JOIN_DEDUP_MAX_WORDS, prevWords.length, nextWords.length);
|
|
13184
|
+
for (let k = maxK; k >= 1; k--) {
|
|
13185
|
+
const tail = prevWords.slice(-k).map(joinKey).join(" ");
|
|
13186
|
+
const head = nextWords.slice(0, k).map(joinKey).join(" ");
|
|
13187
|
+
if (tail && tail === head) {
|
|
13188
|
+
overlap = k;
|
|
13189
|
+
break;
|
|
13190
|
+
}
|
|
13191
|
+
}
|
|
13192
|
+
const rest = nextWords.slice(overlap).join(" ");
|
|
13193
|
+
if (rest) out.push(rest);
|
|
13194
|
+
}
|
|
13195
|
+
return out.join(" ");
|
|
13196
|
+
}
|
|
13084
13197
|
function collapseVoiceover(blueprint) {
|
|
13085
13198
|
const casts = castIdSet(blueprint);
|
|
13086
13199
|
const cameraOn = onCameraDialogue(blueprint);
|
|
@@ -13138,7 +13251,7 @@ function buildPhrases(blueprint, canonical, compositeScenes, presenterPresent, p
|
|
|
13138
13251
|
speaker: cur.speaker,
|
|
13139
13252
|
start_s: cur.start,
|
|
13140
13253
|
end_s: cur.end,
|
|
13141
|
-
text: cur.texts
|
|
13254
|
+
text: joinDialogueTexts(cur.texts),
|
|
13142
13255
|
firstScene: cur.firstScene,
|
|
13143
13256
|
shownScenes,
|
|
13144
13257
|
presenterShown: shownScenes.length > 0
|
|
@@ -13211,7 +13324,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
13211
13324
|
const mode = sceneShootMode(anchorScene, present, nativeTurn, env.cameraOn, env.casts);
|
|
13212
13325
|
const ctx = {
|
|
13213
13326
|
sceneIndex: anchor,
|
|
13214
|
-
|
|
13327
|
+
genAr: env.genAr,
|
|
13215
13328
|
reuse: env.reuse,
|
|
13216
13329
|
imageModel: env.opts.imageModel,
|
|
13217
13330
|
shootMode: mode,
|
|
@@ -13249,7 +13362,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
13249
13362
|
...videoResolutionParam(env.opts.videoModel, env.opts.resolution),
|
|
13250
13363
|
generate_audio: true
|
|
13251
13364
|
};
|
|
13252
|
-
|
|
13365
|
+
clipParams.aspect_ratio = env.genAr;
|
|
13253
13366
|
nodes.push({
|
|
13254
13367
|
id: `s${anchor}_clip`,
|
|
13255
13368
|
type: "video_generate",
|
|
@@ -13363,7 +13476,8 @@ function emitCompositeInTimeline(composite, scene, i, isLast, env, canonical, en
|
|
|
13363
13476
|
lengths,
|
|
13364
13477
|
lengths.out,
|
|
13365
13478
|
{
|
|
13366
|
-
|
|
13479
|
+
outAr: env.outAr,
|
|
13480
|
+
genAr: env.genAr,
|
|
13367
13481
|
reuse: env.reuse,
|
|
13368
13482
|
imageModel: env.opts.imageModel,
|
|
13369
13483
|
videoModel: env.opts.videoModel,
|
|
@@ -13433,23 +13547,23 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
13433
13547
|
const lengths = sceneTiming(scene, isLast, void 0);
|
|
13434
13548
|
const ctx = {
|
|
13435
13549
|
sceneIndex: i,
|
|
13436
|
-
|
|
13550
|
+
genAr: env.genAr,
|
|
13437
13551
|
reuse: env.reuse,
|
|
13438
13552
|
imageModel: env.opts.imageModel,
|
|
13439
13553
|
shootMode: mode,
|
|
13440
13554
|
ingestCache: env.ingestCache
|
|
13441
13555
|
};
|
|
13442
13556
|
if (!env.reuse && sceneIsFullScreenUi(scene, present)) {
|
|
13443
|
-
emitScreenScene(i, scene, lengths, lengths.out, env.
|
|
13557
|
+
emitScreenScene(i, scene, lengths, lengths.out, env.outAr, nodes, out.clips);
|
|
13444
13558
|
return void 0;
|
|
13445
13559
|
}
|
|
13446
13560
|
const isCta = scene.narrative_role?.trim() === "cta" || isLast;
|
|
13447
13561
|
if (!env.reuse && sceneIsBrandCard(scene, present, isCta)) {
|
|
13448
|
-
emitBrandCardScene(i, lengths, lengths.out, env.
|
|
13562
|
+
emitBrandCardScene(i, lengths, lengths.out, env.outAr, brandPlateColor(env.blueprint), nodes, out.clips);
|
|
13449
13563
|
return void 0;
|
|
13450
13564
|
}
|
|
13451
13565
|
if (!ambientBroll && lengths.dur <= FLASH_HOLD_MAX_S) {
|
|
13452
|
-
emitFlashHold(i, scene, env.slots, ctx, lengths, lengths.out, env.
|
|
13566
|
+
emitFlashHold(i, scene, env.slots, ctx, lengths, lengths.out, env.outAr, nodes, out.clips);
|
|
13453
13567
|
return void 0;
|
|
13454
13568
|
}
|
|
13455
13569
|
const first = scene.continues_previous && prevEndFrame ? prevEndFrame : buildFrameRef(
|
|
@@ -13478,7 +13592,13 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
13478
13592
|
{ first, last },
|
|
13479
13593
|
{ dur: lengths.dur, trimTarget: lengths.trimTarget, genDur: lengths.genDur },
|
|
13480
13594
|
lengths.out,
|
|
13481
|
-
{
|
|
13595
|
+
{
|
|
13596
|
+
outAr: env.outAr,
|
|
13597
|
+
genAr: env.genAr,
|
|
13598
|
+
videoModel: env.opts.videoModel,
|
|
13599
|
+
resolution: env.opts.resolution,
|
|
13600
|
+
nativeLang: env.ttsLanguageCode
|
|
13601
|
+
},
|
|
13482
13602
|
nodes
|
|
13483
13603
|
);
|
|
13484
13604
|
if (ambientBroll) {
|
|
@@ -13506,11 +13626,13 @@ function buildTimeline(blueprint, slots, opts, nodes) {
|
|
|
13506
13626
|
}
|
|
13507
13627
|
const canonical = collapseVoiceover(blueprint);
|
|
13508
13628
|
const ensureVoiceNode = makeVoiceFactory(blueprint, canonical, nodes);
|
|
13629
|
+
const aspect = resolveAspect(blueprint.source?.aspect_ratio, opts.aspect, genAspectsFor(opts.videoModel));
|
|
13509
13630
|
const env = {
|
|
13510
13631
|
blueprint,
|
|
13511
13632
|
slots,
|
|
13512
13633
|
opts,
|
|
13513
|
-
|
|
13634
|
+
outAr: aspect.outAr,
|
|
13635
|
+
genAr: aspect.genAr,
|
|
13514
13636
|
reuse,
|
|
13515
13637
|
cameraOn: onCameraDialogue(blueprint),
|
|
13516
13638
|
casts: castIdSet(blueprint),
|
|
@@ -13562,7 +13684,8 @@ function buildTimeline(blueprint, slots, opts, nodes) {
|
|
|
13562
13684
|
}
|
|
13563
13685
|
const slice = out.sceneSlice.get(i);
|
|
13564
13686
|
if (slice) {
|
|
13565
|
-
const
|
|
13687
|
+
const normDims = env.genAr !== env.outAr ? canvasDims(env.outAr) : void 0;
|
|
13688
|
+
const whole = slice.offset === 0 && Math.abs(slice.len - slice.clipDur) <= 0.05 && !normDims;
|
|
13566
13689
|
if (whole) {
|
|
13567
13690
|
out.clips.push({ ref: slice.clipRef, scene_s: slice.len, out: null });
|
|
13568
13691
|
} else {
|
|
@@ -13570,7 +13693,10 @@ function buildTimeline(blueprint, slots, opts, nodes) {
|
|
|
13570
13693
|
id: `s${i}_seg`,
|
|
13571
13694
|
type: "ffmpeg",
|
|
13572
13695
|
inputs: { clip: slice.clipRef },
|
|
13573
|
-
params: {
|
|
13696
|
+
params: {
|
|
13697
|
+
args: trimArgs(slice.len, slice.offset, normDims),
|
|
13698
|
+
outputs: { video: { kind: "video", ext: "mp4" } }
|
|
13699
|
+
}
|
|
13574
13700
|
});
|
|
13575
13701
|
out.clips.push({ ref: `$ref:s${i}_seg.video`, scene_s: slice.len, out: null });
|
|
13576
13702
|
}
|
|
@@ -13687,6 +13813,8 @@ function collectCaptions(blueprint) {
|
|
|
13687
13813
|
}) : [];
|
|
13688
13814
|
}).sort((a, b) => a.at - b.at);
|
|
13689
13815
|
}
|
|
13816
|
+
var OVERLAY_REDETECT_GAP_S = 2;
|
|
13817
|
+
var OVERLAY_MIN_DUR_S = 0.5;
|
|
13690
13818
|
function mergeCaptions(blueprint) {
|
|
13691
13819
|
const byText = /* @__PURE__ */ new Map();
|
|
13692
13820
|
for (const e of collectCaptions(blueprint)) {
|
|
@@ -13698,7 +13826,7 @@ function mergeCaptions(blueprint) {
|
|
|
13698
13826
|
for (const arr of byText.values()) {
|
|
13699
13827
|
let cur = null;
|
|
13700
13828
|
for (const e of arr) {
|
|
13701
|
-
if (cur && e.at <= cur.end +
|
|
13829
|
+
if (cur && e.at <= cur.end + OVERLAY_REDETECT_GAP_S) cur.end = Math.max(cur.end, e.end);
|
|
13702
13830
|
else {
|
|
13703
13831
|
cur = { ...e };
|
|
13704
13832
|
merged.push(cur);
|
|
@@ -13787,10 +13915,28 @@ function buildOverlayHtml(input) {
|
|
|
13787
13915
|
];
|
|
13788
13916
|
const ovParts = mergeCaptions(blueprint).map((e) => overlayElement(e.ov, e.at, Math.round((e.end - e.at) * 1e3) / 1e3)).filter(Boolean);
|
|
13789
13917
|
if (ovParts.length > 0) blocks.push(ovParts.join("\n"));
|
|
13918
|
+
const seenFloats = /* @__PURE__ */ new Map();
|
|
13919
|
+
const keepFloat = (fe, sceneStart) => {
|
|
13920
|
+
const at = fe.appears_at_s ?? sceneStart;
|
|
13921
|
+
const dur = fe.duration_s ?? 2.5;
|
|
13922
|
+
if (dur < OVERLAY_MIN_DUR_S) return false;
|
|
13923
|
+
const key = [
|
|
13924
|
+
(fe.kind ?? "element").toLowerCase(),
|
|
13925
|
+
(fe.brand_name || fe.what_it_represents || fe.description || "").toLowerCase().trim(),
|
|
13926
|
+
positionClass(fe.position)
|
|
13927
|
+
].join("|");
|
|
13928
|
+
const lastEnd = seenFloats.get(key);
|
|
13929
|
+
if (lastEnd !== void 0 && at <= lastEnd + OVERLAY_REDETECT_GAP_S) {
|
|
13930
|
+
seenFloats.set(key, Math.max(lastEnd, at + dur));
|
|
13931
|
+
return false;
|
|
13932
|
+
}
|
|
13933
|
+
seenFloats.set(key, at + dur);
|
|
13934
|
+
return true;
|
|
13935
|
+
};
|
|
13790
13936
|
for (const scene of blueprint.scenes) {
|
|
13791
13937
|
const sceneStart = scene.start_s ?? 0;
|
|
13792
13938
|
const floats = z9.array(FloatingElement).safeParse(scene.floating_elements ?? []);
|
|
13793
|
-
const parts = (floats.success ? floats.data.map((fe) => floatingStub(fe, sceneStart)) : []).filter(Boolean);
|
|
13939
|
+
const parts = (floats.success ? floats.data.filter((fe) => keepFloat(fe, sceneStart)).map((fe) => floatingStub(fe, sceneStart)) : []).filter(Boolean);
|
|
13794
13940
|
const pip = uiPipStub(scene);
|
|
13795
13941
|
if (pip) parts.push(pip);
|
|
13796
13942
|
if (parts.length > 0) blocks.push(parts.join("\n"));
|
|
@@ -13994,12 +14140,16 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
|
|
|
13994
14140
|
});
|
|
13995
14141
|
videoNode = "final";
|
|
13996
14142
|
}
|
|
14143
|
+
const todo = {
|
|
14144
|
+
...buildVideoTodo(videoReport(input, elementsInput), overlays.length, floating.length, opts, blueprint),
|
|
14145
|
+
...aspectRemapTodo(resolveAspect(blueprint.source?.aspect_ratio, opts.aspect, genAspectsFor(opts.videoModel)))
|
|
14146
|
+
};
|
|
13997
14147
|
return {
|
|
13998
14148
|
schema: "baker-canvas/1",
|
|
13999
14149
|
metadata: {
|
|
14000
14150
|
name: "video reproduction",
|
|
14001
14151
|
description: VIDEO_GUIDE,
|
|
14002
|
-
todo
|
|
14152
|
+
todo,
|
|
14003
14153
|
// The timing plan `baker canvas validate` checks before any billed render:
|
|
14004
14154
|
// sequenced voiceover turns (no overlap), audio ≈ video length, and which
|
|
14005
14155
|
// scenes must be lip-synced.
|
|
@@ -14360,6 +14510,27 @@ async function stageCaptions(outDir, transcript) {
|
|
|
14360
14510
|
await cp(SHIPPED_CAPTIONS_DIR, compositionPath, { recursive: true });
|
|
14361
14511
|
return { compositionPath };
|
|
14362
14512
|
}
|
|
14513
|
+
function patchCompositionMeta(metaJson, dims) {
|
|
14514
|
+
try {
|
|
14515
|
+
const meta = JSON.parse(metaJson);
|
|
14516
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) return metaJson;
|
|
14517
|
+
return `${JSON.stringify({ ...meta, width: dims.w, height: dims.h }, null, 2)}
|
|
14518
|
+
`;
|
|
14519
|
+
} catch {
|
|
14520
|
+
return metaJson;
|
|
14521
|
+
}
|
|
14522
|
+
}
|
|
14523
|
+
function patchCompositionHtml(html, dims) {
|
|
14524
|
+
return html.replace(/(<meta\s+name="viewport"\s+content="width=)\d+(,\s*height=)\d+(")/i, `$1${dims.w}$2${dims.h}$3`).replace(/(width:\s*)\d+(px;\s*height:\s*)\d+(px;)/i, `$1${dims.w}$2${dims.h}$3`).replace(/(data-width=")\d+(")/i, `$1${dims.w}$2`).replace(/(data-height=")\d+(")/i, `$1${dims.h}$2`);
|
|
14525
|
+
}
|
|
14526
|
+
async function stampCompositionDims(compositionDir, dims) {
|
|
14527
|
+
const metaPath = path9.join(compositionDir, "meta.json");
|
|
14528
|
+
const rawMeta = await readFile6(metaPath, "utf8");
|
|
14529
|
+
await writeFile2(metaPath, patchCompositionMeta(rawMeta, dims), "utf8");
|
|
14530
|
+
const htmlPath = path9.join(compositionDir, "index.html");
|
|
14531
|
+
const rawHtml = await readFile6(htmlPath, "utf8");
|
|
14532
|
+
await writeFile2(htmlPath, patchCompositionHtml(rawHtml, dims), "utf8");
|
|
14533
|
+
}
|
|
14363
14534
|
function parseElements2(raw) {
|
|
14364
14535
|
const parsed = JSON.parse(raw);
|
|
14365
14536
|
if (Array.isArray(parsed)) return parsed;
|
|
@@ -14512,6 +14683,10 @@ var scaffoldVideoCommand = defineCommand85({
|
|
|
14512
14683
|
"select-model": { type: "string", description: "Override the text_generate model id for element selection" },
|
|
14513
14684
|
"image-model": { type: "string", description: "Override the image_generate model id for frames" },
|
|
14514
14685
|
"video-model": { type: "string", description: "Override the video_generate model id for clips" },
|
|
14686
|
+
aspect: {
|
|
14687
|
+
type: "string",
|
|
14688
|
+
description: `Output aspect ratio (e.g. "4:5"). Default: the source video's detected ratio. When the video model can't generate the ratio (Seedance has no 4:5) clips generate at the nearest supported ratio and are center-cropped onto the output canvas.`
|
|
14689
|
+
},
|
|
14515
14690
|
resolution: {
|
|
14516
14691
|
type: "string",
|
|
14517
14692
|
description: `Output resolution for generated clips (e.g. "1080p"). Default 1080p \u2014 the highest the video model supports \u2014 so clips keep the keyframe sharpness instead of the model's low default.`
|
|
@@ -14545,8 +14720,26 @@ var scaffoldVideoCommand = defineCommand85({
|
|
|
14545
14720
|
const annotated = annotateBlueprintWithElements(blueprint, elements);
|
|
14546
14721
|
await writeFile2(blueprintPath, `${JSON.stringify(annotated, null, 2)}
|
|
14547
14722
|
`, "utf8");
|
|
14723
|
+
let aspect;
|
|
14724
|
+
try {
|
|
14725
|
+
aspect = resolveAspect(
|
|
14726
|
+
blueprint.source?.aspect_ratio,
|
|
14727
|
+
args.aspect ? String(args.aspect) : void 0,
|
|
14728
|
+
genAspectsFor(videoModel)
|
|
14729
|
+
);
|
|
14730
|
+
} catch (e) {
|
|
14731
|
+
return fail2("aspect", e instanceof Error ? e.message : String(e));
|
|
14732
|
+
}
|
|
14733
|
+
const outDims = canvasDims(aspect.outAr);
|
|
14734
|
+
if (aspect.remapped) {
|
|
14735
|
+
process.stderr.write(
|
|
14736
|
+
`\u26A0\uFE0F ${aspect.outAr} output \u2192 clips generate at ${aspect.genAr} (the video model's nearest ratio) and are center-cropped to ${aspect.outAr} (${outDims.w}\xD7${outDims.h}) \u2014 keep subjects centered in frame prompts.
|
|
14737
|
+
`
|
|
14738
|
+
);
|
|
14739
|
+
}
|
|
14548
14740
|
const compositionDest = path9.join(outDir, "video-overlay-composition");
|
|
14549
14741
|
await cp(SHIPPED_COMPOSITION_DIR, compositionDest, { recursive: true });
|
|
14742
|
+
await stampCompositionDims(compositionDest, outDims);
|
|
14550
14743
|
const indexPath = path9.join(compositionDest, "index.html");
|
|
14551
14744
|
const overlayHtml = buildOverlayHtml(blueprint);
|
|
14552
14745
|
const indexHtml = await readFile6(indexPath, "utf8");
|
|
@@ -14559,6 +14752,7 @@ var scaffoldVideoCommand = defineCommand85({
|
|
|
14559
14752
|
}
|
|
14560
14753
|
await writeFile2(indexPath, injected, "utf8");
|
|
14561
14754
|
const captions = await stageCaptions(outDir, transcript);
|
|
14755
|
+
if (captions.compositionPath) await stampCompositionDims(captions.compositionPath, outDims);
|
|
14562
14756
|
const opts = {
|
|
14563
14757
|
imageModel,
|
|
14564
14758
|
videoModel,
|
|
@@ -14567,6 +14761,7 @@ var scaffoldVideoCommand = defineCommand85({
|
|
|
14567
14761
|
blueprintPath: path9.relative(outDir, blueprintPath),
|
|
14568
14762
|
frames,
|
|
14569
14763
|
ambient: Boolean(args.ambient),
|
|
14764
|
+
...args.aspect ? { aspect: String(args.aspect) } : {},
|
|
14570
14765
|
...args.resolution ? { resolution: String(args.resolution) } : {}
|
|
14571
14766
|
};
|
|
14572
14767
|
let canvas;
|