@koda-sl/baker-cli 0.305.0-dev.4e8ef1cd7 → 0.305.0-dev.a04fd407d
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/dist/cli.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
RunAbortedError,
|
|
17
17
|
SEEDANCE_DURATIONS,
|
|
18
18
|
SEEDANCE_PROFILE,
|
|
19
|
+
SPOKEN_CLIP_DURATION_S,
|
|
19
20
|
SPOKEN_WORDS_PER_SECOND,
|
|
20
21
|
VIDEO_GENERATE_MODELS,
|
|
21
22
|
ValidationError,
|
|
@@ -65,7 +66,7 @@ import {
|
|
|
65
66
|
ulid,
|
|
66
67
|
validateCanvasDeep,
|
|
67
68
|
ytDlpBlockSignal
|
|
68
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-ZNN6ZHDS.js";
|
|
69
70
|
import {
|
|
70
71
|
csvOrJson,
|
|
71
72
|
daysAgoIso,
|
|
@@ -34098,6 +34099,27 @@ function joinDialogueTexts(texts) {
|
|
|
34098
34099
|
}
|
|
34099
34100
|
return out.join(" ");
|
|
34100
34101
|
}
|
|
34102
|
+
function foldUnshownPhrases(phrases, keepRenderedVoice) {
|
|
34103
|
+
if (!keepRenderedVoice) return [...phrases];
|
|
34104
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
34105
|
+
const kept = [];
|
|
34106
|
+
for (const phrase of phrases) {
|
|
34107
|
+
const available = phrase.shownScenes.filter((scene) => !claimed.has(scene));
|
|
34108
|
+
if (phrase.presenterShown && available.length > 0) {
|
|
34109
|
+
for (const scene of available) claimed.add(scene);
|
|
34110
|
+
kept.push(phrase);
|
|
34111
|
+
continue;
|
|
34112
|
+
}
|
|
34113
|
+
const host = [...kept].reverse().find((candidate) => candidate.speaker === phrase.speaker);
|
|
34114
|
+
if (!host) {
|
|
34115
|
+
kept.push(phrase);
|
|
34116
|
+
continue;
|
|
34117
|
+
}
|
|
34118
|
+
host.text = joinDialogueTexts([host.text, phrase.text]);
|
|
34119
|
+
host.end_s = Math.max(host.end_s, phrase.end_s);
|
|
34120
|
+
}
|
|
34121
|
+
return kept;
|
|
34122
|
+
}
|
|
34101
34123
|
function collapseVoiceover(blueprint) {
|
|
34102
34124
|
const casts = castIdSet(blueprint);
|
|
34103
34125
|
const cameraOn = onCameraDialogue(blueprint);
|
|
@@ -34660,7 +34682,7 @@ function buildTimeline(blueprint, slots, opts, nodes) {
|
|
|
34660
34682
|
const phrases = buildPhrases(blueprint, canonical2, compositeScenes, presenterPresent, presentStrict);
|
|
34661
34683
|
const usedVoIds = /* @__PURE__ */ new Set();
|
|
34662
34684
|
const claimed = /* @__PURE__ */ new Set();
|
|
34663
|
-
phrases.forEach((phrase, k) => {
|
|
34685
|
+
foldUnshownPhrases(phrases, env.keepRenderedVoice === true).forEach((phrase, k) => {
|
|
34664
34686
|
const voiceNode = ensureVoiceNode(phrase.speaker);
|
|
34665
34687
|
const available = phrase.shownScenes.filter((s) => !claimed.has(s));
|
|
34666
34688
|
if (phrase.presenterShown && available.length > 0) {
|
|
@@ -38557,6 +38579,7 @@ var CURRENCY_BY_MARKET = {
|
|
|
38557
38579
|
"the united states": "$ (US dollars)"
|
|
38558
38580
|
};
|
|
38559
38581
|
var MARKERS_TO_NAME_A_PLACE = 3;
|
|
38582
|
+
var WORDS_PER_SECOND2 = 3.06;
|
|
38560
38583
|
var MARKET_BY_LANGUAGE = {
|
|
38561
38584
|
es: "Spain",
|
|
38562
38585
|
"es-es": "Spain",
|
|
@@ -38629,9 +38652,10 @@ function marketFor(spec) {
|
|
|
38629
38652
|
}
|
|
38630
38653
|
function beatSeconds(line, videoModel) {
|
|
38631
38654
|
const words2 = line.trim().split(/\s+/).length;
|
|
38632
|
-
const spoken = words2 /
|
|
38655
|
+
const spoken = words2 / WORDS_PER_SECOND2;
|
|
38633
38656
|
const allowed = (videoModel ? clipDurationsFor(videoModel) : void 0) ?? [...SEEDANCE_DURATIONS].sort((a, b) => a - b);
|
|
38634
|
-
|
|
38657
|
+
const wanted = Math.max(spoken, SPOKEN_CLIP_DURATION_S);
|
|
38658
|
+
return allowed.find((d) => d >= wanted) ?? allowed[allowed.length - 1];
|
|
38635
38659
|
}
|
|
38636
38660
|
var BRAND_PLATE = "A solid flat brand colour plate \u2014 an end card. No people, no objects, no scenery, no text of any kind.";
|
|
38637
38661
|
var ASKS_FOR_LEGIBLE = /\b(focus on the (number|amount|total|price|figure|text)|readable|legible|clearly (shows?|visible)|highlighted (number|amount|total)|showing the (number|amount|total|price))\b/i;
|
|
@@ -38861,6 +38885,22 @@ function adSpecToBlueprint(spec, opts = {}) {
|
|
|
38861
38885
|
scenes
|
|
38862
38886
|
};
|
|
38863
38887
|
}
|
|
38888
|
+
function beatsTooShortToPlay(spec) {
|
|
38889
|
+
return spec.beats.map((beat, i) => ({ i, spoken: beat.say.trim().split(/\s+/).filter(Boolean).length / WORDS_PER_SECOND2 })).filter(({ spoken }) => spoken < SPOKEN_CLIP_DURATION_S).map(({ i }) => i + 1);
|
|
38890
|
+
}
|
|
38891
|
+
function withoutCastName(text2, names) {
|
|
38892
|
+
let out = text2;
|
|
38893
|
+
for (const name of names) {
|
|
38894
|
+
const token = name.trim();
|
|
38895
|
+
if (token.length < 3) continue;
|
|
38896
|
+
const pattern = new RegExp(`(?<![\\p{L}\\p{N}])${escapeForPattern(token)}(?![\\p{L}\\p{N}])`, "giu");
|
|
38897
|
+
out = out.replace(pattern, "the person in the reference");
|
|
38898
|
+
}
|
|
38899
|
+
return out;
|
|
38900
|
+
}
|
|
38901
|
+
function escapeForPattern(value) {
|
|
38902
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
38903
|
+
}
|
|
38864
38904
|
|
|
38865
38905
|
// src/engine/scaffold/ad-brand-overlay.ts
|
|
38866
38906
|
var markFallback = (name) => name.trim() ? ` onerror="this.replaceWith(Object.assign(document.createElement('span'),{className:this.className+' brand-wordmark',textContent:${JSON.stringify(name.trim())}}))"` : "";
|
|
@@ -39362,6 +39402,13 @@ var scaffoldAdCommand = defineCommand111({
|
|
|
39362
39402
|
process.exit(1);
|
|
39363
39403
|
return;
|
|
39364
39404
|
}
|
|
39405
|
+
if (handle) {
|
|
39406
|
+
const names = [handle, avatar?.name].filter((value) => Boolean(value));
|
|
39407
|
+
spec.data.beats = spec.data.beats.map((beat) => ({ ...beat, show: withoutCastName(beat.show, names) }));
|
|
39408
|
+
if (spec.data.cast?.description) {
|
|
39409
|
+
spec.data.cast = { ...spec.data.cast, description: withoutCastName(spec.data.cast.description, names) };
|
|
39410
|
+
}
|
|
39411
|
+
}
|
|
39365
39412
|
const blueprint = adSpecToBlueprint(spec.data, { avatarAccent: avatar?.profile?.accent, videoModel: chosenVideoModel });
|
|
39366
39413
|
const slug = args.slug ?? path24.basename(specPath).replace(/\.[^.]+$/, "");
|
|
39367
39414
|
const outPath = args.out ?? (args.slug ? path24.join("src/creatives", slug, `${slug}.canvas.json`) : path24.join(path24.dirname(specPath), `${slug}.canvas.json`));
|
|
@@ -39453,6 +39500,9 @@ var scaffoldAdCommand = defineCommand111({
|
|
|
39453
39500
|
ok: true,
|
|
39454
39501
|
data: { canvas: outPath, beats: spec.data.beats.length, slug },
|
|
39455
39502
|
hints: [
|
|
39503
|
+
// Before anything else: a beat too short to play is a beat to merge, and it is
|
|
39504
|
+
// free to fix here and expensive to discover in a finished ad.
|
|
39505
|
+
...shortBeatHint(beatsTooShortToPlay(spec.data), spec.data.beats.length),
|
|
39456
39506
|
...noBrand ? [
|
|
39457
39507
|
"Built with NO brand mark and no closing card, because you passed --no-brand. Say that in your reply: the user is getting an unbranded ad and should know it, not discover it. When they do have a logo, put it in `src/brand/logos/` and re-run without the flag."
|
|
39458
39508
|
] : [],
|
|
@@ -39511,6 +39561,12 @@ var scaffoldAdCommand = defineCommand111({
|
|
|
39511
39561
|
});
|
|
39512
39562
|
}
|
|
39513
39563
|
});
|
|
39564
|
+
function shortBeatHint(short, total) {
|
|
39565
|
+
if (short.length === 0) return [];
|
|
39566
|
+
return [
|
|
39567
|
+
`Beat${short.length === 1 ? "" : "s"} ${short.join(", ")} of ${total} ${short.length === 1 ? "is" : "are"} under ${SPOKEN_CLIP_DURATION_S}s \u2014 a beat lasts exactly as long as its line takes to read, so a two-word line is a three-second shot, and a run of those plays as a slideshow rather than an ad. Do NOT pad them: that buys seconds of somebody standing still. Merge them, so each beat carries a whole thought \u2014 an ad of this length usually wants two or three beats, not six. Edit the spec and re-run this command.`
|
|
39568
|
+
];
|
|
39569
|
+
}
|
|
39514
39570
|
|
|
39515
39571
|
// src/commands/canvas/scaffold-video.ts
|
|
39516
39572
|
import { access as access2, cp as cp2, mkdir as mkdir8, readFile as readFile19, rm as rm7, writeFile as writeFile10 } from "fs/promises";
|
|
@@ -57795,23 +57851,23 @@ function uploadFailure(path47) {
|
|
|
57795
57851
|
}
|
|
57796
57852
|
|
|
57797
57853
|
// src/commands/studio/spokenPace.ts
|
|
57798
|
-
var
|
|
57854
|
+
var WORDS_PER_SECOND3 = SPOKEN_WORDS_PER_SECOND;
|
|
57799
57855
|
var spokenWords = spokenWordsIn;
|
|
57800
57856
|
function wordsThatFit(seconds) {
|
|
57801
|
-
return Math.floor(seconds *
|
|
57857
|
+
return Math.floor(seconds * WORDS_PER_SECOND3);
|
|
57802
57858
|
}
|
|
57803
57859
|
function spokenPaceHint(prompt, seconds, model) {
|
|
57804
57860
|
const words2 = spokenWords(prompt);
|
|
57805
57861
|
if (words2 === 0) return null;
|
|
57806
57862
|
const split = model ? splitScriptFor(words2, model) : null;
|
|
57807
57863
|
if (split) {
|
|
57808
|
-
return `The line is ${words2} words \u2014 about ${Math.ceil(words2 /
|
|
57864
|
+
return `The line is ${words2} words \u2014 about ${Math.ceil(words2 / WORDS_PER_SECOND3)}s of speech \u2014 and ${model} renders at most ${split.maxSeconds}s (~${Math.floor(split.maxSeconds * WORDS_PER_SECOND3)} words). It will not refuse and it will not cut: it will RUSH, swallowing syllables. Split the script into ${split.clips} takes of roughly ${split.wordsPerClip} words each, rendered separately and cut together \u2014 break it where the thought breaks, not mid-sentence. Or move to a model that renders longer (bytedance/seedance-2.5 goes to 30s).`;
|
|
57809
57865
|
}
|
|
57810
57866
|
if (!seconds) return null;
|
|
57811
57867
|
const fits = wordsThatFit(seconds);
|
|
57812
57868
|
if (words2 <= fits) return null;
|
|
57813
57869
|
const rate = (words2 / seconds).toFixed(1);
|
|
57814
|
-
return `The line is ${words2} words and this clip is ${seconds}s \u2014 that is ${rate} words a second against a natural ${
|
|
57870
|
+
return `The line is ${words2} words and this clip is ${seconds}s \u2014 that is ${rate} words a second against a natural ${WORDS_PER_SECOND3}. The model will not run over, it will RUSH: syllables swallowed, endings clipped, and the take unusable however good the picture. Cut the line to about ${fits} words, or give it the length it needs (${Math.ceil(words2 / WORDS_PER_SECOND3)}s). Do not re-render the same line at the same length and hope.`;
|
|
57815
57871
|
}
|
|
57816
57872
|
|
|
57817
57873
|
// src/commands/studio/animate.ts
|