@koda-sl/baker-cli 0.281.0-dev.1f1c09c80 → 0.281.2-dev.1f1c09c80
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 +55 -23
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -32699,9 +32699,17 @@ var Beat = z30.object({
|
|
|
32699
32699
|
*/
|
|
32700
32700
|
sound: z30.string().min(1).max(200).optional(),
|
|
32701
32701
|
/**
|
|
32702
|
-
*
|
|
32703
|
-
*
|
|
32704
|
-
*
|
|
32702
|
+
* Whether this beat's subject is talking to camera.
|
|
32703
|
+
*
|
|
32704
|
+
* Left unset it follows the cast: an ad that cast a presenter, in a shot they are in,
|
|
32705
|
+
* with a line, is that person speaking. Set it to FALSE for the exception — a cutaway,
|
|
32706
|
+
* a product shot, a beat you want a narrator to carry over.
|
|
32707
|
+
*
|
|
32708
|
+
* It used to be opt-in, back when the only route was dubbing and OFF was the safe
|
|
32709
|
+
* default. That inverted the moment on-camera started meaning the video model voices
|
|
32710
|
+
* the clip itself: measured on a real run, a brief that said "talking to camera all the
|
|
32711
|
+
* way through" produced an ad with her face on screen and a narrator cast from a prose
|
|
32712
|
+
* description reading her lines, because nothing set this flag.
|
|
32705
32713
|
*/
|
|
32706
32714
|
on_camera: z30.boolean().optional(),
|
|
32707
32715
|
/**
|
|
@@ -32770,14 +32778,19 @@ var AdSpec = z30.object({
|
|
|
32770
32778
|
message: "give `avatar` (a cast avatar's handle) or `description` \u2014 a cast with neither grounds on nothing"
|
|
32771
32779
|
}).optional(),
|
|
32772
32780
|
/**
|
|
32773
|
-
*
|
|
32774
|
-
* music when asked, and silence is a legitimate choice.
|
|
32781
|
+
* The bed, described in words — or `false` for an ad that is meant to be dry.
|
|
32775
32782
|
*
|
|
32776
|
-
*
|
|
32777
|
-
*
|
|
32778
|
-
*
|
|
32783
|
+
* Omitted lays a soft, neutral bed. It used to leave the ad silent, on the reasoning
|
|
32784
|
+
* that silence is a legitimate choice; it is, but only as a choice. In practice nothing
|
|
32785
|
+
* ever set this, and finished ads shipped with a voice over nothing — which reads as
|
|
32786
|
+
* unfinished, not as restraint. A bed is the ordinary case, so it is the default, and
|
|
32787
|
+
* an ad that wants silence says `false` and means it.
|
|
32788
|
+
*
|
|
32789
|
+
* The bed never plays over the first beat: the opening line is the scroll-stopper and
|
|
32790
|
+
* has to read clean. That is the engine's rule, not this one, and it is why the prompt
|
|
32791
|
+
* is all this needs to carry.
|
|
32779
32792
|
*/
|
|
32780
|
-
music: z30.string().min(1).optional(),
|
|
32793
|
+
music: z30.union([z30.string().min(1), z30.literal(false)]).optional(),
|
|
32781
32794
|
/**
|
|
32782
32795
|
* How the ad closes.
|
|
32783
32796
|
*
|
|
@@ -32948,10 +32961,10 @@ function adSpecCastElements(spec, avatar) {
|
|
|
32948
32961
|
}
|
|
32949
32962
|
];
|
|
32950
32963
|
}
|
|
32951
|
-
function motionPrompt(beat, isClosingCard, place, physics) {
|
|
32964
|
+
function motionPrompt(beat, toCamera, isClosingCard, place, physics) {
|
|
32952
32965
|
if (isClosingCard) return BRAND_PLATE;
|
|
32953
32966
|
const shot = `${beat.show}${place}${physics}`;
|
|
32954
|
-
if (
|
|
32967
|
+
if (toCamera) return shot;
|
|
32955
32968
|
return `${shot} Nobody in frame is speaking: mouths CLOSED and STILL throughout, no talking, no lip movement. Whoever is in shot is looking, reacting or working \u2014 never addressing the camera.`;
|
|
32956
32969
|
}
|
|
32957
32970
|
function staticTranscript(spec) {
|
|
@@ -32973,11 +32986,21 @@ function staticTranscript(spec) {
|
|
|
32973
32986
|
}
|
|
32974
32987
|
return out;
|
|
32975
32988
|
}
|
|
32989
|
+
function musicBed(spec) {
|
|
32990
|
+
if (spec.music === false) return null;
|
|
32991
|
+
if (typeof spec.music === "string") return spec.music;
|
|
32992
|
+
return "A soft, warm instrumental bed that sits well under a spoken voiceover: gentle acoustic guitar or soft piano with light pads, steady and unobtrusive, no vocals, no lyrics, no drop, no build to a climax, nothing that pulls attention off the voice.";
|
|
32993
|
+
}
|
|
32994
|
+
function beatToCamera(spec, beat, isClosingCard) {
|
|
32995
|
+
if (isClosingCard) return false;
|
|
32996
|
+
if (beat.on_camera !== void 0) return beat.on_camera;
|
|
32997
|
+
return Boolean(spec.cast) && beat.cast !== false;
|
|
32998
|
+
}
|
|
32976
32999
|
function wantsNativeSpeech(spec, closesOnCard) {
|
|
32977
33000
|
if (!spec.cast || spec.voiceover === false) return false;
|
|
32978
33001
|
const last = spec.beats.length - 1;
|
|
32979
33002
|
const speaking = spec.beats.filter((_, i) => !(closesOnCard && i === last));
|
|
32980
|
-
return speaking.length > 0 && speaking.every((beat) => beat
|
|
33003
|
+
return speaking.length > 0 && speaking.every((beat) => beatToCamera(spec, beat, false) && beat.cast !== false);
|
|
32981
33004
|
}
|
|
32982
33005
|
function beatDialogue(spec, beat, window2) {
|
|
32983
33006
|
if (spec.voiceover === false) return [];
|
|
@@ -32986,12 +33009,9 @@ function beatDialogue(spec, beat, window2) {
|
|
|
32986
33009
|
line: beat.say,
|
|
32987
33010
|
start_s: window2.start,
|
|
32988
33011
|
end_s: window2.end,
|
|
32989
|
-
//
|
|
32990
|
-
//
|
|
32991
|
-
|
|
32992
|
-
// saying someone is on camera there sent the engine looking for a mouth to lip-sync
|
|
32993
|
-
// on a picture that has no person.
|
|
32994
|
-
on_camera: beat.on_camera === true && !window2.isClosingCard,
|
|
33012
|
+
// Always stated, never left to the engine: absent, it guesses from who is in frame,
|
|
33013
|
+
// which is how a still photograph once got lip-synced.
|
|
33014
|
+
on_camera: beatToCamera(spec, beat, window2.isClosingCard),
|
|
32995
33015
|
// Naming the CAST as speaker is the other half of what sends this beat down the
|
|
32996
33016
|
// native path. Left unnamed, the speaker defaults to "voiceover" — a narrator, who is
|
|
32997
33017
|
// by definition never on camera — so every beat took the dubbed path: a clip generated
|
|
@@ -33044,7 +33064,7 @@ function adSpecToBlueprint(spec) {
|
|
|
33044
33064
|
isClosingCard,
|
|
33045
33065
|
nativeSpeech
|
|
33046
33066
|
}),
|
|
33047
|
-
motion_prompt: motionPrompt(beat, isClosingCard, place, physics),
|
|
33067
|
+
motion_prompt: motionPrompt(beat, beatToCamera(spec, beat, isClosingCard), isClosingCard, place, physics),
|
|
33048
33068
|
start_frame_prompt: isClosingCard ? BRAND_PLATE : `${beat.show}${place}${physics}`
|
|
33049
33069
|
};
|
|
33050
33070
|
});
|
|
@@ -33071,7 +33091,7 @@ function adSpecToBlueprint(spec) {
|
|
|
33071
33091
|
}
|
|
33072
33092
|
} : {},
|
|
33073
33093
|
...spec.brand?.palette?.length ? { style: { palette: spec.brand.palette.map((hex) => ({ hex })) } } : {},
|
|
33074
|
-
...spec
|
|
33094
|
+
...musicBed(spec) ? { music: { music_prompt: musicBed(spec) } } : {}
|
|
33075
33095
|
},
|
|
33076
33096
|
scenes
|
|
33077
33097
|
};
|
|
@@ -33233,7 +33253,7 @@ function pickBrandMark(files) {
|
|
|
33233
33253
|
const score = (f) => {
|
|
33234
33254
|
const name = f.toLowerCase();
|
|
33235
33255
|
let s = 0;
|
|
33236
|
-
if (
|
|
33256
|
+
if (/\.(png|webp)$/.test(name)) s += 1;
|
|
33237
33257
|
if (/\b(logo|wordmark|lockup)\b/.test(name)) s += 2;
|
|
33238
33258
|
if (/\b(mono|white|black|inverse|icon|favicon|mark-only|isotipo)\b/.test(name)) s -= 3;
|
|
33239
33259
|
return s;
|
|
@@ -33439,8 +33459,20 @@ var scaffoldAdCommand = defineCommand105({
|
|
|
33439
33459
|
return;
|
|
33440
33460
|
}
|
|
33441
33461
|
if (resolvedLogo?.toLowerCase().endsWith(".svg")) {
|
|
33442
|
-
const svg = await readFile16(resolvedLogo, "utf-8").catch(() =>
|
|
33443
|
-
if (svg
|
|
33462
|
+
const svg = await readFile16(resolvedLogo, "utf-8").catch(() => null);
|
|
33463
|
+
if (svg === null) {
|
|
33464
|
+
writeJson({
|
|
33465
|
+
ok: false,
|
|
33466
|
+
error: {
|
|
33467
|
+
code: "VALIDATION_ERROR",
|
|
33468
|
+
message: `The brand mark at \`${resolvedLogo}\` could not be read, so there is no way to tell whether it is a real logo or a typed stand-in. An ad is not shipped on a mark nobody could open.`,
|
|
33469
|
+
fix: `Check the file is there and readable, or put the client's logo in \`${BRAND_DIR}/logos/\` \u2014 a PNG at 1000px or wider, or an outlined SVG.`
|
|
33470
|
+
}
|
|
33471
|
+
});
|
|
33472
|
+
process.exit(1);
|
|
33473
|
+
return;
|
|
33474
|
+
}
|
|
33475
|
+
if (svgIsLiveText(svg)) {
|
|
33444
33476
|
writeJson({
|
|
33445
33477
|
ok: false,
|
|
33446
33478
|
error: {
|