@koda-sl/baker-cli 0.276.0-dev.1f1c09c80 → 0.279.0-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
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
ulid,
|
|
59
59
|
validateCanvasDeep,
|
|
60
60
|
ytDlpBlockSignal
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-AUOSDGWY.js";
|
|
62
62
|
import {
|
|
63
63
|
csvOrJson,
|
|
64
64
|
daysAgoIso,
|
|
@@ -3699,14 +3699,22 @@ var avatarSummarySchema = z7.object({
|
|
|
3699
3699
|
subjectDescription: z7.string(),
|
|
3700
3700
|
/** The identity sheet to wire into `--reference`. Absent until the build settles. */
|
|
3701
3701
|
sheetUrl: z7.string().optional(),
|
|
3702
|
+
/**
|
|
3703
|
+
* The avatar's pinned voice.
|
|
3704
|
+
*
|
|
3705
|
+
* A casting essential, not a profile detail: an avatar is a face AND a voice, and
|
|
3706
|
+
* casting the face without it is what makes a presenter speak in somebody else's.
|
|
3707
|
+
* It lived only behind `--full`, so an ad that grounded every frame on the identity
|
|
3708
|
+
* sheet still had its lines read by a voice cast from a prose description.
|
|
3709
|
+
*/
|
|
3710
|
+
voiceId: z7.string().optional(),
|
|
3711
|
+
voiceDescription: z7.string().optional(),
|
|
3702
3712
|
coverUrl: z7.string().optional(),
|
|
3703
3713
|
errorMessage: z7.string().optional()
|
|
3704
3714
|
});
|
|
3705
3715
|
var avatarDetailSchema = avatarSummarySchema.extend({
|
|
3706
3716
|
profile: avatarProfileSchema,
|
|
3707
3717
|
sourceUrls: z7.array(z7.string()),
|
|
3708
|
-
voiceId: z7.string().optional(),
|
|
3709
|
-
voiceDescription: z7.string().optional(),
|
|
3710
3718
|
createdAt: z7.number()
|
|
3711
3719
|
});
|
|
3712
3720
|
var avatarsListRequestSchema = z7.object({
|
|
@@ -28705,7 +28713,7 @@ function buildPhrases(blueprint, canonical2, compositeScenes, presenterPresent,
|
|
|
28705
28713
|
flush();
|
|
28706
28714
|
return phrases;
|
|
28707
28715
|
}
|
|
28708
|
-
function makeVoiceFactory(blueprint, canonical2, nodes, voiceLanguage) {
|
|
28716
|
+
function makeVoiceFactory(blueprint, canonical2, nodes, voiceLanguage, pinnedVoiceId) {
|
|
28709
28717
|
const bySpeaker = /* @__PURE__ */ new Map();
|
|
28710
28718
|
const describe = (speaker) => {
|
|
28711
28719
|
for (const scene of blueprint.scenes)
|
|
@@ -28721,7 +28729,9 @@ function makeVoiceFactory(blueprint, canonical2, nodes, voiceLanguage) {
|
|
|
28721
28729
|
const description = describe(speaker);
|
|
28722
28730
|
const traits = parseVoiceTraits(description);
|
|
28723
28731
|
if (voiceLanguage) traits.language = voiceLanguage;
|
|
28724
|
-
nodes.push(
|
|
28732
|
+
nodes.push(
|
|
28733
|
+
pinnedVoiceId ? { id, type: "voice_select", params: { description, voice_id: pinnedVoiceId, ...traits } } : { id, type: "voice_select", params: { description, ...traits } }
|
|
28734
|
+
);
|
|
28725
28735
|
bySpeaker.set(speaker, id);
|
|
28726
28736
|
return id;
|
|
28727
28737
|
};
|
|
@@ -29122,7 +29132,7 @@ function buildTimeline(blueprint, slots, opts, nodes) {
|
|
|
29122
29132
|
});
|
|
29123
29133
|
}
|
|
29124
29134
|
const canonical2 = collapseVoiceover(blueprint);
|
|
29125
|
-
const ensureVoiceNode = makeVoiceFactory(blueprint, canonical2, nodes, opts.voiceLanguage);
|
|
29135
|
+
const ensureVoiceNode = makeVoiceFactory(blueprint, canonical2, nodes, opts.voiceLanguage, opts.voiceId);
|
|
29126
29136
|
const aspect = resolveAspect(blueprint.source?.aspect_ratio, opts.aspect, genAspectsFor(opts.videoModel));
|
|
29127
29137
|
const env = {
|
|
29128
29138
|
blueprint,
|
|
@@ -30357,7 +30367,11 @@ function buildRunRecord(result, meta, plan, finalLabels) {
|
|
|
30357
30367
|
},
|
|
30358
30368
|
nodes,
|
|
30359
30369
|
finalOutputs: finalOutputs.length > 0 ? finalOutputs : void 0,
|
|
30360
|
-
|
|
30370
|
+
// Sent whenever the review RAN, empty list included. Sending it only when there
|
|
30371
|
+
// were findings makes a clean render and an unreviewed one look identical in the
|
|
30372
|
+
// record — which is the exact ambiguity this field was added to remove: proving a
|
|
30373
|
+
// review had happened once meant re-running a cached canvas and reading raw JSON.
|
|
30374
|
+
...meta.review ? { review: meta.review.slice(0, 40) } : {}
|
|
30361
30375
|
};
|
|
30362
30376
|
}
|
|
30363
30377
|
function buildInitialRunRecord(runId, meta) {
|
|
@@ -31030,6 +31044,32 @@ function videoPathFromOutput(output) {
|
|
|
31030
31044
|
}
|
|
31031
31045
|
return found;
|
|
31032
31046
|
}
|
|
31047
|
+
function classifyAudio(volumedetect, hasAudioStream) {
|
|
31048
|
+
if (!hasAudioStream) {
|
|
31049
|
+
return [
|
|
31050
|
+
{
|
|
31051
|
+
severity: "blocking",
|
|
31052
|
+
what: "The finished video has no audio track at all \u2014 no voice, no music, nothing. An ad that plays silent in a feed is not an ad."
|
|
31053
|
+
}
|
|
31054
|
+
];
|
|
31055
|
+
}
|
|
31056
|
+
const mean = Number.parseFloat(volumedetect.match(/mean_volume:\s*(-?[\d.]+) dB/)?.[1] ?? "");
|
|
31057
|
+
const max = Number.parseFloat(volumedetect.match(/max_volume:\s*(-?[\d.]+) dB/)?.[1] ?? "");
|
|
31058
|
+
const findings = [];
|
|
31059
|
+
if (Number.isFinite(mean) && mean < -30) {
|
|
31060
|
+
findings.push({
|
|
31061
|
+
severity: "warning",
|
|
31062
|
+
what: `The mix is very quiet (${mean} dB average). On a phone at half volume this is close to silence \u2014 check the music bed is not ducked into nothing under the voice.`
|
|
31063
|
+
});
|
|
31064
|
+
}
|
|
31065
|
+
if (Number.isFinite(max) && max > -0.5) {
|
|
31066
|
+
findings.push({
|
|
31067
|
+
severity: "warning",
|
|
31068
|
+
what: `The mix peaks at ${max} dB, which is at or over the ceiling \u2014 the voice will distort on a phone speaker. Bring the loudest track down a few dB.`
|
|
31069
|
+
});
|
|
31070
|
+
}
|
|
31071
|
+
return findings;
|
|
31072
|
+
}
|
|
31033
31073
|
var RENDER_REVIEW_UNAVAILABLE = "The render was NOT reviewed \u2014 no frame-vision key in this environment. Watch it yourself before sending it on.";
|
|
31034
31074
|
var REVIEW_CONCURRENCY = 3;
|
|
31035
31075
|
async function mapWithLimit(items, limit, fn) {
|
|
@@ -31045,6 +31085,21 @@ async function mapWithLimit(items, limit, fn) {
|
|
|
31045
31085
|
return out;
|
|
31046
31086
|
}
|
|
31047
31087
|
var execFileAsync2 = promisify2(execFile2);
|
|
31088
|
+
async function measureAudio(video) {
|
|
31089
|
+
const streams = await execFileAsync2(
|
|
31090
|
+
"ffprobe",
|
|
31091
|
+
["-v", "error", "-select_streams", "a", "-show_entries", "stream=codec_type", "-of", "csv=p=0", video],
|
|
31092
|
+
{ timeout: 2e4 }
|
|
31093
|
+
).catch(() => null);
|
|
31094
|
+
if (!streams) return [];
|
|
31095
|
+
const hasAudio = streams.stdout.trim().length > 0;
|
|
31096
|
+
if (!hasAudio) return classifyAudio("", false);
|
|
31097
|
+
const vol = await execFileAsync2("ffmpeg", ["-i", video, "-map", "0:a:0", "-af", "volumedetect", "-f", "null", "-"], {
|
|
31098
|
+
timeout: 12e4,
|
|
31099
|
+
maxBuffer: 8 * 1024 * 1024
|
|
31100
|
+
}).catch((e) => ({ stdout: "", stderr: e.stderr ?? "" }));
|
|
31101
|
+
return classifyAudio(vol.stderr ?? "", true);
|
|
31102
|
+
}
|
|
31048
31103
|
async function videoDuration(video) {
|
|
31049
31104
|
try {
|
|
31050
31105
|
const { stdout } = await execFileAsync2(
|
|
@@ -31108,7 +31163,12 @@ async function reviewRenderedVideo(opts) {
|
|
|
31108
31163
|
}));
|
|
31109
31164
|
const unread = perFrame.filter((f) => f.json === null).length;
|
|
31110
31165
|
if (unread === perFrame.length && reel === null) return null;
|
|
31111
|
-
const findings = [
|
|
31166
|
+
const findings = [
|
|
31167
|
+
...classifyFrameFindings(perFrame, opts.windows ?? []),
|
|
31168
|
+
...classifyReelFindings(reel),
|
|
31169
|
+
// Measured, not judged: a model looking at frames cannot hear the mix.
|
|
31170
|
+
...await measureAudio(opts.video)
|
|
31171
|
+
];
|
|
31112
31172
|
if (unread > 0) {
|
|
31113
31173
|
findings.push({
|
|
31114
31174
|
severity: "warning",
|
|
@@ -31434,6 +31494,9 @@ ${describeRewrites(healed.rewrites)}
|
|
|
31434
31494
|
] : [];
|
|
31435
31495
|
if (poster) {
|
|
31436
31496
|
await poster.flush(
|
|
31497
|
+
// `review` is null only when nobody looked (no key, no ffmpeg, unreadable video);
|
|
31498
|
+
// an empty array is a render that was reviewed and came back clean. The record
|
|
31499
|
+
// has to keep those apart.
|
|
31437
31500
|
buildRunRecord(result, { ...recordMeta, ...review ? { review } : {} }, progress?.planInfo(), finalLabels)
|
|
31438
31501
|
);
|
|
31439
31502
|
}
|
|
@@ -32511,7 +32574,7 @@ var scaffoldStaticAdCommand = defineCommand103({
|
|
|
32511
32574
|
});
|
|
32512
32575
|
|
|
32513
32576
|
// src/commands/canvas/scaffold-ad.ts
|
|
32514
|
-
import { copyFile, cp, mkdir as mkdir7, readFile as readFile16, writeFile as writeFile9 } from "fs/promises";
|
|
32577
|
+
import { copyFile, cp, mkdir as mkdir7, readFile as readFile16, stat as stat4, writeFile as writeFile9 } from "fs/promises";
|
|
32515
32578
|
import path24 from "path";
|
|
32516
32579
|
import { defineCommand as defineCommand104 } from "citty";
|
|
32517
32580
|
|
|
@@ -32530,6 +32593,23 @@ var Beat = z30.object({
|
|
|
32530
32593
|
).refine(oneClause, "a beat is one clause: split a second sentence into its own beat"),
|
|
32531
32594
|
/** What is on screen while it is said, as a shot brief for the video model. */
|
|
32532
32595
|
show: z30.string().min(1),
|
|
32596
|
+
/**
|
|
32597
|
+
* What this shot SOUNDS like — one effect, in words. Usually absent.
|
|
32598
|
+
*
|
|
32599
|
+
* An effect earns its place when the picture shows a SPECIFIC PHYSICAL EVENT the
|
|
32600
|
+
* viewer expects to hear: a drill tightening a bolt, a door closing, rain on glass, a
|
|
32601
|
+
* card tapping a reader, a graphic snapping into place. It grounds the shot.
|
|
32602
|
+
*
|
|
32603
|
+
* It does NOT belong on a talking head, on a lifestyle shot with no event in it, or
|
|
32604
|
+
* on a beat that is simply someone sitting and reacting. Sound laid over those reads
|
|
32605
|
+
* as a stock-library sting, and a bed of them turns a calm ad into a noisy one. Most
|
|
32606
|
+
* ads are a music bed and a voice; a couple of effects at the right moments, or none
|
|
32607
|
+
* at all, is a normal and often better answer than one per beat.
|
|
32608
|
+
*
|
|
32609
|
+
* Describe the SOUND, not the picture: "a cordless drill tightening a bolt", not "the
|
|
32610
|
+
* fitter works on the rail".
|
|
32611
|
+
*/
|
|
32612
|
+
sound: z30.string().min(1).max(200).optional(),
|
|
32533
32613
|
/**
|
|
32534
32614
|
* Set only when this beat's subject is talking to camera. Off by default, and
|
|
32535
32615
|
* the default is load-bearing: a person visibly speaking under a voice that is
|
|
@@ -32775,7 +32855,8 @@ function adSpecCastElements(spec, avatar) {
|
|
|
32775
32855
|
function motionPrompt(beat, isClosingCard, place, physics) {
|
|
32776
32856
|
if (isClosingCard) return BRAND_PLATE;
|
|
32777
32857
|
const shot = `${beat.show}${place}${physics}`;
|
|
32778
|
-
|
|
32858
|
+
if (beat.on_camera) return shot;
|
|
32859
|
+
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.`;
|
|
32779
32860
|
}
|
|
32780
32861
|
function staticTranscript(spec) {
|
|
32781
32862
|
const out = [];
|
|
@@ -32823,6 +32904,10 @@ function adSpecToBlueprint(spec) {
|
|
|
32823
32904
|
// No dialogue at all when nobody speaks: an empty array is what stops the engine
|
|
32824
32905
|
// wiring a voice track, and no voice track is what makes this a music-led ad
|
|
32825
32906
|
// rather than one with a silent narrator.
|
|
32907
|
+
// One effect per beat, placed on this beat's window. The engine clamps the
|
|
32908
|
+
// length to what the provider accepts. An ad emitted none of these until now,
|
|
32909
|
+
// so every one shipped with a music bed and no sound design at all.
|
|
32910
|
+
...beat.sound?.trim() ? { sfx: [{ sound_effect_prompt: beat.sound.trim(), duration_s: Math.min(duration, 30) }] } : {},
|
|
32826
32911
|
dialogue: spec.voiceover === false ? [] : [
|
|
32827
32912
|
{
|
|
32828
32913
|
line: beat.say,
|
|
@@ -33069,7 +33154,7 @@ registerSchema({
|
|
|
33069
33154
|
spec: {
|
|
33070
33155
|
type: "string",
|
|
33071
33156
|
required: true,
|
|
33072
|
-
description: 'Path to the ad spec JSON. Shape: { format?, market?, brand?, cast?, end_card?, voice?, music?, beats: [{ say, show, on_camera?, cast? }] }. Fill `brand` from src/brand/BRAND.md \u2014 `palette` (its hex tokens, most important first) and `logo` (the repo path to the mark) are what make the ad look like the client rather than like stock. `market` is where the ad is SET; omitted, it is inferred from the voice language, and getting it wrong is what fills a Spanish ad with British houses. `say` is ONE clause ending in its own punctuation \u2014 it becomes a caption card verbatim, so two sentences in one beat produce a card holding both. `show` is the shot brief for that line. Set `on_camera` ONLY when that beat\'s subject talks to camera. `voiceover: false` makes it a MUSIC-LED ad: nobody speaks, the `say` lines become on-screen text captioned straight from the script, and it needs `music`. `cast` is WHO the ad is about, and an ad with people in it needs one: `{ "avatar": "marta" }` names a cast avatar (`baker avatars list`) and every beat is grounded on that avatar\'s identity sheet with its subject description copied verbatim \u2014 the same face here as in the rest of the company\'s work. Without it each beat invents its own stranger, which is how one 28-second ad came back with five different men playing one customer. `{ "description": "..." }` is the fallback when there is no avatar, and it scaffolds a canvas that asks you to drop a photo before it can run \u2014 so prefer the avatar. A beat sets `cast: false` for a shot they are not in. `end_card` is on by default whenever `brand` is set: the last beat becomes a flat brand plate with the mark and a call to action drawn over it. `{ "cta": "..." }` sets the button copy, `false` keeps the footage.'
|
|
33157
|
+
description: 'Path to the ad spec JSON. Shape: { format?, market?, brand?, cast?, end_card?, voice?, music?, beats: [{ say, show, on_camera?, cast? }] }. Fill `brand` from src/brand/BRAND.md \u2014 `palette` (its hex tokens, most important first) and `logo` (the repo path to the mark) are what make the ad look like the client rather than like stock. `market` is where the ad is SET; omitted, it is inferred from the voice language, and getting it wrong is what fills a Spanish ad with British houses. `say` is ONE clause ending in its own punctuation \u2014 it becomes a caption card verbatim, so two sentences in one beat produce a card holding both. `show` is the shot brief for that line. Set `on_camera` ONLY when that beat\'s subject talks to camera. A beat\'s `sound` is one sound EFFECT for that shot \u2014 and MOST BEATS SHOULD NOT HAVE ONE. Add it only where the picture shows a specific physical event the viewer expects to hear (a drill tightening a bolt, a door closing, rain on glass, a graphic snapping in). On a talking head or a calm lifestyle shot it reads as a stock sting, and one per beat turns a quiet ad into a noisy one. A music bed plus two well-placed effects beats eight. `voiceover: false` makes it a MUSIC-LED ad: nobody speaks, the `say` lines become on-screen text captioned straight from the script, and it needs `music`. `cast` is WHO the ad is about, and an ad with people in it needs one: `{ "avatar": "marta" }` names a cast avatar (`baker avatars list`) and every beat is grounded on that avatar\'s identity sheet with its subject description copied verbatim \u2014 the same face here as in the rest of the company\'s work. Without it each beat invents its own stranger, which is how one 28-second ad came back with five different men playing one customer. `{ "description": "..." }` is the fallback when there is no avatar, and it scaffolds a canvas that asks you to drop a photo before it can run \u2014 so prefer the avatar. A beat sets `cast: false` for a shot they are not in. `end_card` is on by default whenever `brand` is set: the last beat becomes a flat brand plate with the mark and a call to action drawn over it. `{ "cta": "..." }` sets the button copy, `false` keeps the footage.'
|
|
33073
33158
|
},
|
|
33074
33159
|
avatar: {
|
|
33075
33160
|
type: "string",
|
|
@@ -33180,6 +33265,33 @@ var scaffoldAdCommand = defineCommand104({
|
|
|
33180
33265
|
...filledPalette ? { palette: filledPalette } : {}
|
|
33181
33266
|
};
|
|
33182
33267
|
}
|
|
33268
|
+
const speaksOnCamera = spec.data.beats.some((beat) => beat.on_camera === true);
|
|
33269
|
+
if (avatar && !avatar.voiceId && speaksOnCamera) {
|
|
33270
|
+
writeJson({
|
|
33271
|
+
ok: false,
|
|
33272
|
+
error: {
|
|
33273
|
+
code: "VALIDATION_ERROR",
|
|
33274
|
+
message: `Avatar \`${handle}\` has no pinned voice, and this ad has them speaking on camera. Their lines would be read by a voice cast from a description \u2014 a mouth moving under somebody else's voice, which is exactly what dubbing looks like.`,
|
|
33275
|
+
fix: `Give \`${handle}\` a voice first, or drop \`on_camera\` from every beat so they appear without speaking and the narration carries the read. An avatar is a face AND a voice; casting one without the other is what puts the wrong voice in their mouth.`
|
|
33276
|
+
}
|
|
33277
|
+
});
|
|
33278
|
+
process.exit(1);
|
|
33279
|
+
return;
|
|
33280
|
+
}
|
|
33281
|
+
const resolvedLogo = spec.data.brand?.logo?.trim();
|
|
33282
|
+
const logoExists = resolvedLogo ? await stat4(resolvedLogo).then(() => true, () => false) : false;
|
|
33283
|
+
if (!logoExists) {
|
|
33284
|
+
writeJson({
|
|
33285
|
+
ok: false,
|
|
33286
|
+
error: {
|
|
33287
|
+
code: "VALIDATION_ERROR",
|
|
33288
|
+
message: resolvedLogo ? `The brand mark at \`${resolvedLogo}\` does not exist, so this ad has no logo to put on screen.` : `This company has no brand mark in \`${BRAND_DIR}/logos/\`, so an ad cannot carry its logo.`,
|
|
33289
|
+
fix: `Put the client's real mark in \`${BRAND_DIR}/logos/\` \u2014 an SVG is best, a PNG is fine \u2014 and run this again. Ask them for it if you have to; do NOT draw one, and do not pass a placeholder. The mark is drawn on every frame and on the closing card, so a stand-in ships an ad wearing the wrong brand.`
|
|
33290
|
+
}
|
|
33291
|
+
});
|
|
33292
|
+
process.exit(1);
|
|
33293
|
+
return;
|
|
33294
|
+
}
|
|
33183
33295
|
const blueprint = adSpecToBlueprint(spec.data);
|
|
33184
33296
|
const slug = args.slug ?? path24.basename(specPath).replace(/\.[^.]+$/, "");
|
|
33185
33297
|
const outPath = args.out ?? (args.slug ? path24.join("src/creatives", slug, `${slug}.canvas.json`) : path24.join(path24.dirname(specPath), `${slug}.canvas.json`));
|
|
@@ -33219,6 +33331,10 @@ var scaffoldAdCommand = defineCommand104({
|
|
|
33219
33331
|
// Told, not guessed: the voice description is written in the ad's own language
|
|
33220
33332
|
// and the engine's trait parser reads English only.
|
|
33221
33333
|
voiceLanguage: voiceLanguageFor(spec.data),
|
|
33334
|
+
// The cast avatar's own voice. An avatar is a face AND a voice; grounding every
|
|
33335
|
+
// frame on their identity sheet while a separately cast voice reads the lines is
|
|
33336
|
+
// how a presenter ends up speaking in somebody else's.
|
|
33337
|
+
...avatar?.voiceId ? { voiceId: avatar.voiceId } : {},
|
|
33222
33338
|
...spec.data.voiceover === false ? { staticTranscriptPath: path24.relative(outDir, transcriptPath) } : {}
|
|
33223
33339
|
};
|
|
33224
33340
|
const canvas = scaffoldVideoCanvas(blueprint, adSpecCastElements(spec.data, avatar), opts);
|
|
@@ -33272,6 +33388,9 @@ var scaffoldAdCommand = defineCommand104({
|
|
|
33272
33388
|
] : [],
|
|
33273
33389
|
...avatarError ? [avatarError] : [],
|
|
33274
33390
|
...avatar ? [
|
|
33391
|
+
...avatar?.voiceId ? [] : [
|
|
33392
|
+
`Avatar \`${handle}\` has no pinned voice, so the ad's lines are read by a voice cast from a description \u2014 it will not sound like them. Give them one (\`baker avatars\` voice) before this ships.`
|
|
33393
|
+
],
|
|
33275
33394
|
`Every beat the customer appears in is grounded on \`${handle}\`'s identity sheet, and their subject description was copied into the frames verbatim \u2014 so it is the same face throughout and the same face as the rest of this company's work.`
|
|
33276
33395
|
] : [],
|
|
33277
33396
|
...presenterIsInEveryShot(spec.data) ? [
|
|
@@ -39043,7 +39162,7 @@ function cropSprite(input, region) {
|
|
|
39043
39162
|
|
|
39044
39163
|
// src/lib/image/io.ts
|
|
39045
39164
|
import { randomBytes } from "crypto";
|
|
39046
|
-
import { glob as fsGlob, readFile as readFile23, rename, stat as
|
|
39165
|
+
import { glob as fsGlob, readFile as readFile23, rename, stat as stat5, writeFile as writeFile12 } from "fs/promises";
|
|
39047
39166
|
import { dirname as dirname2, extname as extname2, join as join5, resolve as resolve4 } from "path";
|
|
39048
39167
|
var REMOTE_RE = /^https?:\/\//i;
|
|
39049
39168
|
var GLOB_RE = /[*?[\]{}]/;
|
|
@@ -39080,7 +39199,7 @@ async function readImageBuffer(pathOrUrl) {
|
|
|
39080
39199
|
}
|
|
39081
39200
|
async function isDirectory(path41) {
|
|
39082
39201
|
try {
|
|
39083
|
-
const s = await
|
|
39202
|
+
const s = await stat5(path41);
|
|
39084
39203
|
return s.isDirectory();
|
|
39085
39204
|
} catch {
|
|
39086
39205
|
return false;
|
|
@@ -42477,7 +42596,7 @@ Full guide: __tooling__/docs/tools/baker/images.md`
|
|
|
42477
42596
|
import { defineCommand as defineCommand167 } from "citty";
|
|
42478
42597
|
|
|
42479
42598
|
// src/commands/landing/critique.ts
|
|
42480
|
-
import { readdir as readdir11, stat as
|
|
42599
|
+
import { readdir as readdir11, stat as stat7 } from "fs/promises";
|
|
42481
42600
|
import path31 from "path";
|
|
42482
42601
|
import { defineCommand as defineCommand157 } from "citty";
|
|
42483
42602
|
|
|
@@ -43464,7 +43583,7 @@ async function writeCritiqueSnapshot(projectRoot, snapshot) {
|
|
|
43464
43583
|
}
|
|
43465
43584
|
|
|
43466
43585
|
// src/commands/landing/source-version.ts
|
|
43467
|
-
import { readdir as readdir10, readFile as readFile24, stat as
|
|
43586
|
+
import { readdir as readdir10, readFile as readFile24, stat as stat6 } from "fs/promises";
|
|
43468
43587
|
import path30 from "path";
|
|
43469
43588
|
async function landingSourceRelPaths(landingDir) {
|
|
43470
43589
|
const rel = [];
|
|
@@ -43497,7 +43616,7 @@ async function computeLandingSourceSha(landingDir) {
|
|
|
43497
43616
|
}
|
|
43498
43617
|
async function isFile(p) {
|
|
43499
43618
|
try {
|
|
43500
|
-
return (await
|
|
43619
|
+
return (await stat6(p)).isFile();
|
|
43501
43620
|
} catch {
|
|
43502
43621
|
return false;
|
|
43503
43622
|
}
|
|
@@ -43650,7 +43769,7 @@ function present(f) {
|
|
|
43650
43769
|
}
|
|
43651
43770
|
async function isDir(p) {
|
|
43652
43771
|
try {
|
|
43653
|
-
return (await
|
|
43772
|
+
return (await stat7(p)).isDirectory();
|
|
43654
43773
|
} catch {
|
|
43655
43774
|
return false;
|
|
43656
43775
|
}
|
|
@@ -51345,7 +51464,7 @@ var groupCommand2 = defineCommand210({
|
|
|
51345
51464
|
});
|
|
51346
51465
|
|
|
51347
51466
|
// src/commands/videos/ingest.ts
|
|
51348
|
-
import { mkdtemp as mkdtemp3, rm as rm8, stat as
|
|
51467
|
+
import { mkdtemp as mkdtemp3, rm as rm8, stat as stat8 } from "fs/promises";
|
|
51349
51468
|
import { tmpdir as tmpdir4 } from "os";
|
|
51350
51469
|
import path40 from "path";
|
|
51351
51470
|
import { defineCommand as defineCommand211 } from "citty";
|
|
@@ -51729,7 +51848,7 @@ async function downloadThenIngest(args, country) {
|
|
|
51729
51848
|
// we allow to fetch it cannot drift apart.
|
|
51730
51849
|
timeoutMs: downloadTimeoutMs(durationSeconds(probe.info))
|
|
51731
51850
|
});
|
|
51732
|
-
const stats = await
|
|
51851
|
+
const stats = await stat8(filePath);
|
|
51733
51852
|
if (stats.size > MAX_VIDEO_INGEST_BYTES) {
|
|
51734
51853
|
throw new ApiError(
|
|
51735
51854
|
"VALIDATION_ERROR",
|
|
@@ -51834,7 +51953,7 @@ var searchCommand4 = defineCommand212({
|
|
|
51834
51953
|
var tagsCommand6 = makeTagsCommand("videos", "video", "/api/videos/tags");
|
|
51835
51954
|
|
|
51836
51955
|
// src/commands/videos/upload.ts
|
|
51837
|
-
import { readFile as readFile26, stat as
|
|
51956
|
+
import { readFile as readFile26, stat as stat9 } from "fs/promises";
|
|
51838
51957
|
import { basename as basename3, extname as extname4 } from "path";
|
|
51839
51958
|
import { defineCommand as defineCommand213 } from "citty";
|
|
51840
51959
|
var MIME_MAP = {
|
|
@@ -51918,7 +52037,7 @@ var uploadCommand2 = defineCommand213({
|
|
|
51918
52037
|
const originalFilename = basename3(filePath);
|
|
51919
52038
|
const descriptionContext = args.context;
|
|
51920
52039
|
if (args["dry-run"]) {
|
|
51921
|
-
const fileStats = await
|
|
52040
|
+
const fileStats = await stat9(filePath);
|
|
51922
52041
|
writeJson({
|
|
51923
52042
|
ok: true,
|
|
51924
52043
|
dryRun: true,
|