@vibeframe/cli 0.113.2 → 0.113.3
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/commands/_shared/build-cache.d.ts +16 -1
- package/dist/commands/_shared/scene-build.d.ts +3 -0
- package/dist/commands/_shared/storyboard-edit.d.ts +1 -1
- package/dist/commands/_shared/storyboard-parse.d.ts +7 -0
- package/dist/index.js +169 -12
- package/dist/tools/manifest/index.js +168 -11
- package/package.json +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type BuildAssetKind = "narration" | "backdrop" | "character" | "video" | "music";
|
|
1
|
+
export type BuildAssetKind = "narration" | "backdrop" | "character" | "keyframe" | "video" | "music";
|
|
2
2
|
export interface CacheAssetDescriptor {
|
|
3
3
|
key: string;
|
|
4
4
|
path: string;
|
|
@@ -27,6 +27,16 @@ export declare function characterCacheDescriptor(opts: {
|
|
|
27
27
|
quality: "standard" | "hd";
|
|
28
28
|
size: string;
|
|
29
29
|
}): CacheAssetDescriptor;
|
|
30
|
+
export declare function keyframeCacheDescriptor(opts: {
|
|
31
|
+
beatId: string;
|
|
32
|
+
cue: string;
|
|
33
|
+
provider: string;
|
|
34
|
+
quality: "standard" | "hd";
|
|
35
|
+
size: string;
|
|
36
|
+
ratio?: string;
|
|
37
|
+
/** Character sheet paths used as edit references — changing them re-keys the keyframe. */
|
|
38
|
+
characters?: string[];
|
|
39
|
+
}): CacheAssetDescriptor;
|
|
30
40
|
export declare function imageRatioForSize(size: string | undefined): string;
|
|
31
41
|
export declare function videoCacheDescriptor(opts: {
|
|
32
42
|
beatId: string;
|
|
@@ -35,6 +45,11 @@ export declare function videoCacheDescriptor(opts: {
|
|
|
35
45
|
duration: number | undefined;
|
|
36
46
|
/** Character reference image paths — changing them must invalidate the clip. */
|
|
37
47
|
characters?: string[];
|
|
48
|
+
/**
|
|
49
|
+
* Keyframe still path for image-to-video mode — changing the keyframe must
|
|
50
|
+
* invalidate the clip. Omitted for text/reference-to-video clips.
|
|
51
|
+
*/
|
|
52
|
+
keyframe?: string;
|
|
38
53
|
}): CacheAssetDescriptor;
|
|
39
54
|
export declare function musicCacheDescriptor(opts: {
|
|
40
55
|
beatId: string;
|
|
@@ -168,6 +168,9 @@ export interface BeatBuildOutcome {
|
|
|
168
168
|
videoMetadataPath?: string;
|
|
169
169
|
videoFreshness?: AssetFreshness;
|
|
170
170
|
videoSourcePath?: string;
|
|
171
|
+
/** Keyframe still status when the beat uses keyframe → image-to-video mode. */
|
|
172
|
+
keyframeStatus?: PrimitiveStatus;
|
|
173
|
+
keyframePath?: string;
|
|
171
174
|
musicStatus: PrimitiveStatus;
|
|
172
175
|
musicPath?: string;
|
|
173
176
|
musicJobId?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Beat } from "./storyboard-parse.js";
|
|
2
|
-
export declare const STORYBOARD_CUE_KEYS: readonly ["duration", "narration", "backdrop", "video", "motion", "voice", "music", "asset", "characters"];
|
|
2
|
+
export declare const STORYBOARD_CUE_KEYS: readonly ["duration", "narration", "backdrop", "video", "keyframe", "motion", "voice", "music", "asset", "characters"];
|
|
3
3
|
export type StoryboardCueKey = (typeof STORYBOARD_CUE_KEYS)[number];
|
|
4
4
|
export interface StoryboardValidationIssue {
|
|
5
5
|
severity: "error" | "warning";
|
|
@@ -113,6 +113,13 @@ export interface BeatCues {
|
|
|
113
113
|
* reference-to-video generation. A single name or a list.
|
|
114
114
|
*/
|
|
115
115
|
characters?: string | string[];
|
|
116
|
+
/**
|
|
117
|
+
* Keyframe still prompt. When set, the build generates a keyframe image for
|
|
118
|
+
* this beat (using the character sheet as an edit reference when `characters`
|
|
119
|
+
* is also present) and runs Seedance image-to-video on it. The `video` cue,
|
|
120
|
+
* if present, supplies the motion prompt; otherwise this text is used.
|
|
121
|
+
*/
|
|
122
|
+
keyframe?: string;
|
|
116
123
|
[key: string]: unknown;
|
|
117
124
|
}
|
|
118
125
|
/** Normalize a beat's `characters` cue to a clean string[] (single or list). */
|
package/dist/index.js
CHANGED
|
@@ -31869,6 +31869,7 @@ var init_storyboard_edit = __esm({
|
|
|
31869
31869
|
"narration",
|
|
31870
31870
|
"backdrop",
|
|
31871
31871
|
"video",
|
|
31872
|
+
"keyframe",
|
|
31872
31873
|
"motion",
|
|
31873
31874
|
"voice",
|
|
31874
31875
|
"music",
|
|
@@ -31878,7 +31879,7 @@ var init_storyboard_edit = __esm({
|
|
|
31878
31879
|
HEADING_RE2 = /^##\s+(.+?)\s*$/gm;
|
|
31879
31880
|
LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
|
|
31880
31881
|
ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
|
|
31881
|
-
STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "motion", "voice", "music", "asset"]);
|
|
31882
|
+
STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "keyframe", "motion", "voice", "music", "asset"]);
|
|
31882
31883
|
MAX_RECOMMENDED_BEAT_SEC = 15;
|
|
31883
31884
|
}
|
|
31884
31885
|
});
|
|
@@ -60422,6 +60423,7 @@ var init_build_asset_reference = __esm({
|
|
|
60422
60423
|
narration: [".mp3", ".wav", ".m4a"],
|
|
60423
60424
|
backdrop: [".png", ".jpg", ".jpeg", ".webp"],
|
|
60424
60425
|
character: [".png", ".jpg", ".jpeg", ".webp"],
|
|
60426
|
+
keyframe: [".png", ".jpg", ".jpeg", ".webp"],
|
|
60425
60427
|
video: [".mp4", ".mov", ".webm"],
|
|
60426
60428
|
music: [".mp3", ".wav", ".m4a"]
|
|
60427
60429
|
};
|
|
@@ -60469,6 +60471,18 @@ function characterCacheDescriptor(opts) {
|
|
|
60469
60471
|
ext: "png"
|
|
60470
60472
|
});
|
|
60471
60473
|
}
|
|
60474
|
+
function keyframeCacheDescriptor(opts) {
|
|
60475
|
+
return cacheAssetDescriptor("keyframe", {
|
|
60476
|
+
beatId: opts.beatId,
|
|
60477
|
+
cue: opts.cue,
|
|
60478
|
+
provider: opts.provider,
|
|
60479
|
+
quality: opts.quality,
|
|
60480
|
+
size: opts.size,
|
|
60481
|
+
ratio: opts.ratio,
|
|
60482
|
+
characters: opts.characters && opts.characters.length > 0 ? opts.characters : void 0,
|
|
60483
|
+
ext: "png"
|
|
60484
|
+
});
|
|
60485
|
+
}
|
|
60472
60486
|
function imageRatioForSize(size) {
|
|
60473
60487
|
switch (size) {
|
|
60474
60488
|
case "1024x1024":
|
|
@@ -60490,6 +60504,8 @@ function videoCacheDescriptor(opts) {
|
|
|
60490
60504
|
ratio: "16:9",
|
|
60491
60505
|
// Omit when empty so existing (character-less) clips keep their cache key.
|
|
60492
60506
|
characters: opts.characters && opts.characters.length > 0 ? opts.characters : void 0,
|
|
60507
|
+
// Omit when absent so existing (non-keyframe) clips keep their cache key.
|
|
60508
|
+
keyframe: opts.keyframe || void 0,
|
|
60493
60509
|
ext: "mp4"
|
|
60494
60510
|
});
|
|
60495
60511
|
}
|
|
@@ -60640,6 +60656,7 @@ async function createBuildPlan(opts) {
|
|
|
60640
60656
|
const backdropPrompt = stringOrUndefined3(cue.backdrop);
|
|
60641
60657
|
const augmentedBackdropPrompt = backdropPrompt ? augmentBackdropPrompt(backdropPrompt) : null;
|
|
60642
60658
|
const videoPrompt = stringOrUndefined3(cue.video);
|
|
60659
|
+
const keyframePrompt = stringOrUndefined3(cue.keyframe);
|
|
60643
60660
|
const musicPrompt = stringOrUndefined3(cue.music);
|
|
60644
60661
|
const genericReference = resolveGenericAssetReference(projectDir, cue.asset);
|
|
60645
60662
|
const narrationReference = resolveTypedAssetReference(projectDir, "narration", cue.narration);
|
|
@@ -60648,7 +60665,7 @@ async function createBuildPlan(opts) {
|
|
|
60648
60665
|
const musicReference = resolveTypedAssetReference(projectDir, "music", cue.music) ?? (!musicPrompt && genericReference?.kind === "music" ? genericReference : null);
|
|
60649
60666
|
const narrationCue = narrationText ?? narrationReference?.raw;
|
|
60650
60667
|
const backdropCue = backdropPrompt ?? backdropReference?.raw;
|
|
60651
|
-
const videoCue = videoPrompt ?? videoReference?.raw;
|
|
60668
|
+
const videoCue = videoPrompt ?? videoReference?.raw ?? keyframePrompt;
|
|
60652
60669
|
const musicCue = musicPrompt ?? musicReference?.raw;
|
|
60653
60670
|
const narrationCost = resolved.narration.resolved === "elevenlabs" ? ELEVENLABS_NARRATION_COST_USD : resolved.narration.resolved === "openai" ? OPENAI_NARRATION_COST_USD : 0;
|
|
60654
60671
|
const narrationCache = narrationText && !narrationReference ? narrationCacheDescriptor({
|
|
@@ -60666,11 +60683,12 @@ async function createBuildPlan(opts) {
|
|
|
60666
60683
|
size: imageSize2,
|
|
60667
60684
|
ratio: imageRatio
|
|
60668
60685
|
}) : null;
|
|
60669
|
-
const videoCache = videoPrompt && !videoReference ? videoCacheDescriptor({
|
|
60686
|
+
const videoCache = (videoPrompt || keyframePrompt) && !videoReference ? videoCacheDescriptor({
|
|
60670
60687
|
beatId: beat.id,
|
|
60671
|
-
cue: videoPrompt,
|
|
60688
|
+
cue: videoPrompt ?? keyframePrompt,
|
|
60672
60689
|
provider: resolved.video.resolved,
|
|
60673
|
-
duration: beat.duration
|
|
60690
|
+
duration: beat.duration,
|
|
60691
|
+
keyframe: keyframePrompt
|
|
60674
60692
|
}) : null;
|
|
60675
60693
|
const musicCache = musicPrompt && !musicReference ? musicCacheDescriptor({
|
|
60676
60694
|
beatId: beat.id,
|
|
@@ -60765,6 +60783,12 @@ async function createBuildPlan(opts) {
|
|
|
60765
60783
|
if (backdrop?.willGenerate && isSupportedBuildImageProvider(resolved.image.resolved))
|
|
60766
60784
|
noteProviderNeed(resolved.image, "Backdrop generation");
|
|
60767
60785
|
if (video?.willGenerate) noteProviderNeed(resolved.video, "Video generation");
|
|
60786
|
+
if (keyframePrompt && video?.willGenerate) {
|
|
60787
|
+
estimatedCostUsd += backdropCostUsd(imageQuality);
|
|
60788
|
+
providers.add(resolved.image.resolved);
|
|
60789
|
+
if (isSupportedBuildImageProvider(resolved.image.resolved))
|
|
60790
|
+
noteProviderNeed(resolved.image, "Keyframe generation");
|
|
60791
|
+
}
|
|
60768
60792
|
if (music?.willGenerate) noteProviderNeed(resolved.music, "Music generation");
|
|
60769
60793
|
if (!compositionExists) missing.add("compositions");
|
|
60770
60794
|
if (includeCompose && !compositionExists && mode !== "agent") {
|
|
@@ -62670,6 +62694,8 @@ async function buildBeatPrimitives(beat, ctx) {
|
|
|
62670
62694
|
videoMetadataPath: video.metadataPath,
|
|
62671
62695
|
videoFreshness: video.freshness,
|
|
62672
62696
|
videoSourcePath: video.sourcePath,
|
|
62697
|
+
keyframeStatus: video.keyframeStatus,
|
|
62698
|
+
keyframePath: video.keyframePath,
|
|
62673
62699
|
musicStatus: music.status,
|
|
62674
62700
|
musicPath: music.path,
|
|
62675
62701
|
musicJobId: music.job?.id,
|
|
@@ -63114,6 +63140,112 @@ async function generateBackdropImage(prompt3, ctx, ratio) {
|
|
|
63114
63140
|
});
|
|
63115
63141
|
return imageBufferFromResult(result);
|
|
63116
63142
|
}
|
|
63143
|
+
async function editKeyframeImage(prompt3, sheetBuffers, ctx, ratio) {
|
|
63144
|
+
loadSceneBuildEnv(ctx.projectDir);
|
|
63145
|
+
const keyInfo = imageProviderKeyInfo(ctx.imageProvider);
|
|
63146
|
+
const apiKey = await getApiKeyFromConfig(keyInfo.configKey, { cwd: ctx.projectDir }) ?? process.env[keyInfo.envVar] ?? "";
|
|
63147
|
+
if (!apiKey) {
|
|
63148
|
+
return {
|
|
63149
|
+
success: false,
|
|
63150
|
+
error: `${keyInfo.envVar} not set \u2014 cannot generate keyframe with ${ctx.imageProvider}`
|
|
63151
|
+
};
|
|
63152
|
+
}
|
|
63153
|
+
if (ctx.imageProvider === "openai") {
|
|
63154
|
+
const provider2 = new OpenAIImageProvider();
|
|
63155
|
+
await provider2.initialize({ apiKey });
|
|
63156
|
+
const result2 = await provider2.editImage(sheetBuffers, prompt3);
|
|
63157
|
+
return imageBufferFromResult(result2);
|
|
63158
|
+
}
|
|
63159
|
+
if (ctx.imageProvider === "gemini") {
|
|
63160
|
+
const provider2 = new GeminiProvider();
|
|
63161
|
+
await provider2.initialize({ apiKey });
|
|
63162
|
+
const result2 = await provider2.editImage(sheetBuffers, prompt3, {
|
|
63163
|
+
model: "flash",
|
|
63164
|
+
aspectRatio: ratio
|
|
63165
|
+
});
|
|
63166
|
+
return imageBufferFromResult(result2);
|
|
63167
|
+
}
|
|
63168
|
+
const provider = new GrokProvider();
|
|
63169
|
+
await provider.initialize({ apiKey });
|
|
63170
|
+
const result = await provider.editImage(sheetBuffers[0], prompt3, { aspectRatio: ratio });
|
|
63171
|
+
return imageBufferFromResult(result);
|
|
63172
|
+
}
|
|
63173
|
+
async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
|
|
63174
|
+
const rel = `assets/keyframe-${beat.id}.png`;
|
|
63175
|
+
const abs = join25(ctx.projectDir, rel);
|
|
63176
|
+
const size = ctx.imageSize ?? "1536x1024";
|
|
63177
|
+
const ratio = imageRatioForSize(size);
|
|
63178
|
+
const metadataOptions = { quality: ctx.imageQuality, size, ratio };
|
|
63179
|
+
const cache = keyframeCacheDescriptor({
|
|
63180
|
+
beatId: beat.id,
|
|
63181
|
+
cue: keyframeCue,
|
|
63182
|
+
provider: ctx.imageProvider,
|
|
63183
|
+
quality: ctx.imageQuality,
|
|
63184
|
+
size,
|
|
63185
|
+
ratio,
|
|
63186
|
+
characters: characterRefsRel
|
|
63187
|
+
});
|
|
63188
|
+
if (existsSync49(abs) && !ctx.force) {
|
|
63189
|
+
const metadata = readAssetMetadata(ctx.projectDir, "keyframe", beat.id);
|
|
63190
|
+
if (!metadata || isFreshCanonicalAsset({
|
|
63191
|
+
projectDir: ctx.projectDir,
|
|
63192
|
+
kind: "keyframe",
|
|
63193
|
+
beatId: beat.id,
|
|
63194
|
+
cue: keyframeCue,
|
|
63195
|
+
provider: ctx.imageProvider,
|
|
63196
|
+
options: metadataOptions,
|
|
63197
|
+
cacheKey: cache.key
|
|
63198
|
+
})) {
|
|
63199
|
+
return { status: "cached", rel };
|
|
63200
|
+
}
|
|
63201
|
+
}
|
|
63202
|
+
const cacheAbs = join25(ctx.projectDir, cache.path);
|
|
63203
|
+
if (existsSync49(cacheAbs) && !ctx.force) {
|
|
63204
|
+
await mkdir26(dirname24(abs), { recursive: true });
|
|
63205
|
+
await copyFile5(cacheAbs, abs);
|
|
63206
|
+
await writeAssetMetadata({
|
|
63207
|
+
projectDir: ctx.projectDir,
|
|
63208
|
+
kind: "keyframe",
|
|
63209
|
+
beatId: beat.id,
|
|
63210
|
+
cue: keyframeCue,
|
|
63211
|
+
provider: ctx.imageProvider,
|
|
63212
|
+
options: metadataOptions,
|
|
63213
|
+
cacheKey: cache.key,
|
|
63214
|
+
canonicalPath: rel,
|
|
63215
|
+
cachePath: cache.path
|
|
63216
|
+
});
|
|
63217
|
+
return { status: "cached", rel };
|
|
63218
|
+
}
|
|
63219
|
+
const sheetBuffers = [];
|
|
63220
|
+
for (const relPath of characterRefsRel) {
|
|
63221
|
+
const sheetAbs = join25(ctx.projectDir, relPath);
|
|
63222
|
+
if (existsSync49(sheetAbs)) sheetBuffers.push(await readFile46(sheetAbs));
|
|
63223
|
+
}
|
|
63224
|
+
const imgCtx = {
|
|
63225
|
+
projectDir: ctx.projectDir,
|
|
63226
|
+
imageProvider: ctx.imageProvider,
|
|
63227
|
+
imageSize: size,
|
|
63228
|
+
imageQuality: ctx.imageQuality
|
|
63229
|
+
};
|
|
63230
|
+
const generated = sheetBuffers.length > 0 ? await editKeyframeImage(keyframeCue, sheetBuffers, imgCtx, ratio) : await generateBackdropImage(keyframeCue, imgCtx, ratio);
|
|
63231
|
+
if (!generated.success) return { status: "failed", error: generated.error };
|
|
63232
|
+
await mkdir26(dirname24(abs), { recursive: true });
|
|
63233
|
+
await writeFile50(abs, generated.buffer);
|
|
63234
|
+
await mkdir26(dirname24(cacheAbs), { recursive: true });
|
|
63235
|
+
await writeFile50(cacheAbs, generated.buffer);
|
|
63236
|
+
await writeAssetMetadata({
|
|
63237
|
+
projectDir: ctx.projectDir,
|
|
63238
|
+
kind: "keyframe",
|
|
63239
|
+
beatId: beat.id,
|
|
63240
|
+
cue: keyframeCue,
|
|
63241
|
+
provider: ctx.imageProvider,
|
|
63242
|
+
options: metadataOptions,
|
|
63243
|
+
cacheKey: cache.key,
|
|
63244
|
+
canonicalPath: rel,
|
|
63245
|
+
cachePath: cache.path
|
|
63246
|
+
});
|
|
63247
|
+
return { status: "generated", rel };
|
|
63248
|
+
}
|
|
63117
63249
|
async function imageBufferFromResult(result) {
|
|
63118
63250
|
const image = result.images?.[0];
|
|
63119
63251
|
if (!result.success || !image) {
|
|
@@ -63137,7 +63269,8 @@ function imageProviderKeyInfo(provider) {
|
|
|
63137
63269
|
async function dispatchVideo(beat, ctx) {
|
|
63138
63270
|
const reference = assetReferenceForBeat(ctx.projectDir, "video", beat);
|
|
63139
63271
|
if (reference) return referencePrimitiveOutcome("video", beat, ctx, reference);
|
|
63140
|
-
const
|
|
63272
|
+
const keyframeCue = stringOrUndefined4(beat.cues?.keyframe);
|
|
63273
|
+
const prompt3 = stringOrUndefined4(beat.cues?.video) ?? keyframeCue;
|
|
63141
63274
|
if (!prompt3) return { status: "no-cue" };
|
|
63142
63275
|
const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p) => typeof p === "string");
|
|
63143
63276
|
const characterRefsAbs = characterRefsRel.map((p) => join25(ctx.projectDir, p));
|
|
@@ -63148,7 +63281,10 @@ async function dispatchVideo(beat, ctx) {
|
|
|
63148
63281
|
cue: prompt3,
|
|
63149
63282
|
provider: ctx.videoProvider,
|
|
63150
63283
|
duration: normalizeVideoDuration(beat.duration),
|
|
63151
|
-
|
|
63284
|
+
// Keyframe mode keys the clip to the keyframe prompt; refs feed the keyframe,
|
|
63285
|
+
// not the clip directly.
|
|
63286
|
+
characters: keyframeCue ? void 0 : characterRefsRel,
|
|
63287
|
+
keyframe: keyframeCue
|
|
63152
63288
|
});
|
|
63153
63289
|
const metadataPath = assetMetadataPath("video", beat.id);
|
|
63154
63290
|
if (existsSync49(abs) && !ctx.force) {
|
|
@@ -63201,6 +63337,19 @@ async function dispatchVideo(beat, ctx) {
|
|
|
63201
63337
|
};
|
|
63202
63338
|
}
|
|
63203
63339
|
loadSceneBuildEnv(ctx.projectDir);
|
|
63340
|
+
let keyframeStatus;
|
|
63341
|
+
let keyframeRel;
|
|
63342
|
+
let keyframeImageAbs;
|
|
63343
|
+
if (keyframeCue) {
|
|
63344
|
+
const kf = await ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel);
|
|
63345
|
+
if (kf.status === "failed") {
|
|
63346
|
+
ctx.onProgress({ type: "video-failed", beatId: beat.id, error: kf.error });
|
|
63347
|
+
return { status: "failed", error: kf.error };
|
|
63348
|
+
}
|
|
63349
|
+
keyframeStatus = kf.status;
|
|
63350
|
+
keyframeRel = kf.rel;
|
|
63351
|
+
keyframeImageAbs = join25(ctx.projectDir, kf.rel);
|
|
63352
|
+
}
|
|
63204
63353
|
const result = await executeVideoGenerate({
|
|
63205
63354
|
prompt: prompt3,
|
|
63206
63355
|
provider: ctx.videoProvider,
|
|
@@ -63208,7 +63357,10 @@ async function dispatchVideo(beat, ctx) {
|
|
|
63208
63357
|
ratio: "16:9",
|
|
63209
63358
|
output: abs,
|
|
63210
63359
|
wait: false,
|
|
63211
|
-
|
|
63360
|
+
// Keyframe mode → single init frame (image-to-video); otherwise character
|
|
63361
|
+
// reference-to-video.
|
|
63362
|
+
image: keyframeImageAbs,
|
|
63363
|
+
refImages: keyframeImageAbs ? void 0 : characterRefsAbs.length > 0 ? characterRefsAbs : void 0,
|
|
63212
63364
|
apiKey: await apiKeyForVideoProvider(ctx.videoProvider, ctx.projectDir)
|
|
63213
63365
|
});
|
|
63214
63366
|
if (!result.success || !result.taskId) {
|
|
@@ -63243,14 +63395,16 @@ async function dispatchVideo(beat, ctx) {
|
|
|
63243
63395
|
cachePath: cache.path,
|
|
63244
63396
|
cacheKey: cache.key,
|
|
63245
63397
|
metadataPath,
|
|
63246
|
-
freshness: "fresh"
|
|
63398
|
+
freshness: "fresh",
|
|
63399
|
+
keyframeStatus,
|
|
63400
|
+
keyframePath: keyframeRel
|
|
63247
63401
|
};
|
|
63248
63402
|
}
|
|
63249
63403
|
const job = await createAndWriteJobRecord({
|
|
63250
63404
|
jobType: "generate-video",
|
|
63251
63405
|
provider: result.provider ?? ctx.videoProvider,
|
|
63252
63406
|
providerTaskId: result.taskId,
|
|
63253
|
-
providerTaskType: "text2video",
|
|
63407
|
+
providerTaskType: keyframeImageAbs ? "image2video" : "text2video",
|
|
63254
63408
|
status: "running",
|
|
63255
63409
|
projectDir: ctx.projectDir,
|
|
63256
63410
|
workingDirectory: ctx.projectDir,
|
|
@@ -63275,7 +63429,9 @@ async function dispatchVideo(beat, ctx) {
|
|
|
63275
63429
|
provider: job.provider,
|
|
63276
63430
|
cachePath: cache.path,
|
|
63277
63431
|
cacheKey: cache.key,
|
|
63278
|
-
metadataPath
|
|
63432
|
+
metadataPath,
|
|
63433
|
+
keyframeStatus,
|
|
63434
|
+
keyframePath: keyframeRel
|
|
63279
63435
|
};
|
|
63280
63436
|
}
|
|
63281
63437
|
async function dispatchMusic(beat, ctx) {
|
|
@@ -63577,6 +63733,7 @@ function estimateActualAssetCost(outcomes, imageQuality) {
|
|
|
63577
63733
|
for (const outcome of outcomes) {
|
|
63578
63734
|
if (outcome.narrationStatus === "generated") cost += 0.05;
|
|
63579
63735
|
if (outcome.backdropStatus === "generated") cost += backdropCostUsd(imageQuality);
|
|
63736
|
+
if (outcome.keyframeStatus === "generated") cost += backdropCostUsd(imageQuality);
|
|
63580
63737
|
if (outcome.videoStatus === "generated" || outcome.videoStatus === "pending") {
|
|
63581
63738
|
cost += outcome.videoProvider === "seedance" || outcome.videoProvider === "fal" ? estimateSeedanceVideoCostUsd({
|
|
63582
63739
|
durationSec: normalizeVideoDuration(outcome.sceneDurationSec),
|
|
@@ -74662,7 +74819,7 @@ init_source();
|
|
|
74662
74819
|
// package.json
|
|
74663
74820
|
var package_default = {
|
|
74664
74821
|
name: "@vibeframe/cli",
|
|
74665
|
-
version: "0.113.
|
|
74822
|
+
version: "0.113.3",
|
|
74666
74823
|
description: "VibeFrame CLI - agentic video workflow runtime",
|
|
74667
74824
|
private: false,
|
|
74668
74825
|
license: "MIT",
|
|
@@ -32480,6 +32480,7 @@ var init_storyboard_edit = __esm({
|
|
|
32480
32480
|
"narration",
|
|
32481
32481
|
"backdrop",
|
|
32482
32482
|
"video",
|
|
32483
|
+
"keyframe",
|
|
32483
32484
|
"motion",
|
|
32484
32485
|
"voice",
|
|
32485
32486
|
"music",
|
|
@@ -32489,7 +32490,7 @@ var init_storyboard_edit = __esm({
|
|
|
32489
32490
|
HEADING_RE2 = /^##\s+(.+?)\s*$/gm;
|
|
32490
32491
|
LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
|
|
32491
32492
|
ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
|
|
32492
|
-
STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "motion", "voice", "music", "asset"]);
|
|
32493
|
+
STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "keyframe", "motion", "voice", "music", "asset"]);
|
|
32493
32494
|
MAX_RECOMMENDED_BEAT_SEC = 15;
|
|
32494
32495
|
}
|
|
32495
32496
|
});
|
|
@@ -33411,6 +33412,7 @@ var init_build_asset_reference = __esm({
|
|
|
33411
33412
|
narration: [".mp3", ".wav", ".m4a"],
|
|
33412
33413
|
backdrop: [".png", ".jpg", ".jpeg", ".webp"],
|
|
33413
33414
|
character: [".png", ".jpg", ".jpeg", ".webp"],
|
|
33415
|
+
keyframe: [".png", ".jpg", ".jpeg", ".webp"],
|
|
33414
33416
|
video: [".mp4", ".mov", ".webm"],
|
|
33415
33417
|
music: [".mp3", ".wav", ".m4a"]
|
|
33416
33418
|
};
|
|
@@ -33458,6 +33460,18 @@ function characterCacheDescriptor(opts) {
|
|
|
33458
33460
|
ext: "png"
|
|
33459
33461
|
});
|
|
33460
33462
|
}
|
|
33463
|
+
function keyframeCacheDescriptor(opts) {
|
|
33464
|
+
return cacheAssetDescriptor("keyframe", {
|
|
33465
|
+
beatId: opts.beatId,
|
|
33466
|
+
cue: opts.cue,
|
|
33467
|
+
provider: opts.provider,
|
|
33468
|
+
quality: opts.quality,
|
|
33469
|
+
size: opts.size,
|
|
33470
|
+
ratio: opts.ratio,
|
|
33471
|
+
characters: opts.characters && opts.characters.length > 0 ? opts.characters : void 0,
|
|
33472
|
+
ext: "png"
|
|
33473
|
+
});
|
|
33474
|
+
}
|
|
33461
33475
|
function imageRatioForSize(size) {
|
|
33462
33476
|
switch (size) {
|
|
33463
33477
|
case "1024x1024":
|
|
@@ -33479,6 +33493,8 @@ function videoCacheDescriptor(opts) {
|
|
|
33479
33493
|
ratio: "16:9",
|
|
33480
33494
|
// Omit when empty so existing (character-less) clips keep their cache key.
|
|
33481
33495
|
characters: opts.characters && opts.characters.length > 0 ? opts.characters : void 0,
|
|
33496
|
+
// Omit when absent so existing (non-keyframe) clips keep their cache key.
|
|
33497
|
+
keyframe: opts.keyframe || void 0,
|
|
33482
33498
|
ext: "mp4"
|
|
33483
33499
|
});
|
|
33484
33500
|
}
|
|
@@ -33629,6 +33645,7 @@ async function createBuildPlan(opts) {
|
|
|
33629
33645
|
const backdropPrompt = stringOrUndefined3(cue.backdrop);
|
|
33630
33646
|
const augmentedBackdropPrompt = backdropPrompt ? augmentBackdropPrompt(backdropPrompt) : null;
|
|
33631
33647
|
const videoPrompt = stringOrUndefined3(cue.video);
|
|
33648
|
+
const keyframePrompt = stringOrUndefined3(cue.keyframe);
|
|
33632
33649
|
const musicPrompt = stringOrUndefined3(cue.music);
|
|
33633
33650
|
const genericReference = resolveGenericAssetReference(projectDir, cue.asset);
|
|
33634
33651
|
const narrationReference = resolveTypedAssetReference(projectDir, "narration", cue.narration);
|
|
@@ -33637,7 +33654,7 @@ async function createBuildPlan(opts) {
|
|
|
33637
33654
|
const musicReference = resolveTypedAssetReference(projectDir, "music", cue.music) ?? (!musicPrompt && genericReference?.kind === "music" ? genericReference : null);
|
|
33638
33655
|
const narrationCue = narrationText ?? narrationReference?.raw;
|
|
33639
33656
|
const backdropCue = backdropPrompt ?? backdropReference?.raw;
|
|
33640
|
-
const videoCue = videoPrompt ?? videoReference?.raw;
|
|
33657
|
+
const videoCue = videoPrompt ?? videoReference?.raw ?? keyframePrompt;
|
|
33641
33658
|
const musicCue = musicPrompt ?? musicReference?.raw;
|
|
33642
33659
|
const narrationCost = resolved.narration.resolved === "elevenlabs" ? ELEVENLABS_NARRATION_COST_USD : resolved.narration.resolved === "openai" ? OPENAI_NARRATION_COST_USD : 0;
|
|
33643
33660
|
const narrationCache = narrationText && !narrationReference ? narrationCacheDescriptor({
|
|
@@ -33655,11 +33672,12 @@ async function createBuildPlan(opts) {
|
|
|
33655
33672
|
size: imageSize2,
|
|
33656
33673
|
ratio: imageRatio
|
|
33657
33674
|
}) : null;
|
|
33658
|
-
const videoCache = videoPrompt && !videoReference ? videoCacheDescriptor({
|
|
33675
|
+
const videoCache = (videoPrompt || keyframePrompt) && !videoReference ? videoCacheDescriptor({
|
|
33659
33676
|
beatId: beat.id,
|
|
33660
|
-
cue: videoPrompt,
|
|
33677
|
+
cue: videoPrompt ?? keyframePrompt,
|
|
33661
33678
|
provider: resolved.video.resolved,
|
|
33662
|
-
duration: beat.duration
|
|
33679
|
+
duration: beat.duration,
|
|
33680
|
+
keyframe: keyframePrompt
|
|
33663
33681
|
}) : null;
|
|
33664
33682
|
const musicCache = musicPrompt && !musicReference ? musicCacheDescriptor({
|
|
33665
33683
|
beatId: beat.id,
|
|
@@ -33754,6 +33772,12 @@ async function createBuildPlan(opts) {
|
|
|
33754
33772
|
if (backdrop?.willGenerate && isSupportedBuildImageProvider(resolved.image.resolved))
|
|
33755
33773
|
noteProviderNeed(resolved.image, "Backdrop generation");
|
|
33756
33774
|
if (video?.willGenerate) noteProviderNeed(resolved.video, "Video generation");
|
|
33775
|
+
if (keyframePrompt && video?.willGenerate) {
|
|
33776
|
+
estimatedCostUsd += backdropCostUsd(imageQuality);
|
|
33777
|
+
providers.add(resolved.image.resolved);
|
|
33778
|
+
if (isSupportedBuildImageProvider(resolved.image.resolved))
|
|
33779
|
+
noteProviderNeed(resolved.image, "Keyframe generation");
|
|
33780
|
+
}
|
|
33757
33781
|
if (music?.willGenerate) noteProviderNeed(resolved.music, "Music generation");
|
|
33758
33782
|
if (!compositionExists) missing.add("compositions");
|
|
33759
33783
|
if (includeCompose && !compositionExists && mode !== "agent") {
|
|
@@ -35164,6 +35188,8 @@ async function buildBeatPrimitives(beat, ctx) {
|
|
|
35164
35188
|
videoMetadataPath: video.metadataPath,
|
|
35165
35189
|
videoFreshness: video.freshness,
|
|
35166
35190
|
videoSourcePath: video.sourcePath,
|
|
35191
|
+
keyframeStatus: video.keyframeStatus,
|
|
35192
|
+
keyframePath: video.keyframePath,
|
|
35167
35193
|
musicStatus: music.status,
|
|
35168
35194
|
musicPath: music.path,
|
|
35169
35195
|
musicJobId: music.job?.id,
|
|
@@ -35608,6 +35634,112 @@ async function generateBackdropImage(prompt3, ctx, ratio) {
|
|
|
35608
35634
|
});
|
|
35609
35635
|
return imageBufferFromResult(result);
|
|
35610
35636
|
}
|
|
35637
|
+
async function editKeyframeImage(prompt3, sheetBuffers, ctx, ratio) {
|
|
35638
|
+
loadSceneBuildEnv(ctx.projectDir);
|
|
35639
|
+
const keyInfo = imageProviderKeyInfo(ctx.imageProvider);
|
|
35640
|
+
const apiKey = await getApiKeyFromConfig(keyInfo.configKey, { cwd: ctx.projectDir }) ?? process.env[keyInfo.envVar] ?? "";
|
|
35641
|
+
if (!apiKey) {
|
|
35642
|
+
return {
|
|
35643
|
+
success: false,
|
|
35644
|
+
error: `${keyInfo.envVar} not set \u2014 cannot generate keyframe with ${ctx.imageProvider}`
|
|
35645
|
+
};
|
|
35646
|
+
}
|
|
35647
|
+
if (ctx.imageProvider === "openai") {
|
|
35648
|
+
const provider2 = new OpenAIImageProvider();
|
|
35649
|
+
await provider2.initialize({ apiKey });
|
|
35650
|
+
const result2 = await provider2.editImage(sheetBuffers, prompt3);
|
|
35651
|
+
return imageBufferFromResult(result2);
|
|
35652
|
+
}
|
|
35653
|
+
if (ctx.imageProvider === "gemini") {
|
|
35654
|
+
const provider2 = new GeminiProvider();
|
|
35655
|
+
await provider2.initialize({ apiKey });
|
|
35656
|
+
const result2 = await provider2.editImage(sheetBuffers, prompt3, {
|
|
35657
|
+
model: "flash",
|
|
35658
|
+
aspectRatio: ratio
|
|
35659
|
+
});
|
|
35660
|
+
return imageBufferFromResult(result2);
|
|
35661
|
+
}
|
|
35662
|
+
const provider = new GrokProvider();
|
|
35663
|
+
await provider.initialize({ apiKey });
|
|
35664
|
+
const result = await provider.editImage(sheetBuffers[0], prompt3, { aspectRatio: ratio });
|
|
35665
|
+
return imageBufferFromResult(result);
|
|
35666
|
+
}
|
|
35667
|
+
async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
|
|
35668
|
+
const rel = `assets/keyframe-${beat.id}.png`;
|
|
35669
|
+
const abs = join16(ctx.projectDir, rel);
|
|
35670
|
+
const size = ctx.imageSize ?? "1536x1024";
|
|
35671
|
+
const ratio = imageRatioForSize(size);
|
|
35672
|
+
const metadataOptions = { quality: ctx.imageQuality, size, ratio };
|
|
35673
|
+
const cache = keyframeCacheDescriptor({
|
|
35674
|
+
beatId: beat.id,
|
|
35675
|
+
cue: keyframeCue,
|
|
35676
|
+
provider: ctx.imageProvider,
|
|
35677
|
+
quality: ctx.imageQuality,
|
|
35678
|
+
size,
|
|
35679
|
+
ratio,
|
|
35680
|
+
characters: characterRefsRel
|
|
35681
|
+
});
|
|
35682
|
+
if (existsSync19(abs) && !ctx.force) {
|
|
35683
|
+
const metadata = readAssetMetadata(ctx.projectDir, "keyframe", beat.id);
|
|
35684
|
+
if (!metadata || isFreshCanonicalAsset({
|
|
35685
|
+
projectDir: ctx.projectDir,
|
|
35686
|
+
kind: "keyframe",
|
|
35687
|
+
beatId: beat.id,
|
|
35688
|
+
cue: keyframeCue,
|
|
35689
|
+
provider: ctx.imageProvider,
|
|
35690
|
+
options: metadataOptions,
|
|
35691
|
+
cacheKey: cache.key
|
|
35692
|
+
})) {
|
|
35693
|
+
return { status: "cached", rel };
|
|
35694
|
+
}
|
|
35695
|
+
}
|
|
35696
|
+
const cacheAbs = join16(ctx.projectDir, cache.path);
|
|
35697
|
+
if (existsSync19(cacheAbs) && !ctx.force) {
|
|
35698
|
+
await mkdir10(dirname10(abs), { recursive: true });
|
|
35699
|
+
await copyFile3(cacheAbs, abs);
|
|
35700
|
+
await writeAssetMetadata({
|
|
35701
|
+
projectDir: ctx.projectDir,
|
|
35702
|
+
kind: "keyframe",
|
|
35703
|
+
beatId: beat.id,
|
|
35704
|
+
cue: keyframeCue,
|
|
35705
|
+
provider: ctx.imageProvider,
|
|
35706
|
+
options: metadataOptions,
|
|
35707
|
+
cacheKey: cache.key,
|
|
35708
|
+
canonicalPath: rel,
|
|
35709
|
+
cachePath: cache.path
|
|
35710
|
+
});
|
|
35711
|
+
return { status: "cached", rel };
|
|
35712
|
+
}
|
|
35713
|
+
const sheetBuffers = [];
|
|
35714
|
+
for (const relPath of characterRefsRel) {
|
|
35715
|
+
const sheetAbs = join16(ctx.projectDir, relPath);
|
|
35716
|
+
if (existsSync19(sheetAbs)) sheetBuffers.push(await readFile18(sheetAbs));
|
|
35717
|
+
}
|
|
35718
|
+
const imgCtx = {
|
|
35719
|
+
projectDir: ctx.projectDir,
|
|
35720
|
+
imageProvider: ctx.imageProvider,
|
|
35721
|
+
imageSize: size,
|
|
35722
|
+
imageQuality: ctx.imageQuality
|
|
35723
|
+
};
|
|
35724
|
+
const generated = sheetBuffers.length > 0 ? await editKeyframeImage(keyframeCue, sheetBuffers, imgCtx, ratio) : await generateBackdropImage(keyframeCue, imgCtx, ratio);
|
|
35725
|
+
if (!generated.success) return { status: "failed", error: generated.error };
|
|
35726
|
+
await mkdir10(dirname10(abs), { recursive: true });
|
|
35727
|
+
await writeFile18(abs, generated.buffer);
|
|
35728
|
+
await mkdir10(dirname10(cacheAbs), { recursive: true });
|
|
35729
|
+
await writeFile18(cacheAbs, generated.buffer);
|
|
35730
|
+
await writeAssetMetadata({
|
|
35731
|
+
projectDir: ctx.projectDir,
|
|
35732
|
+
kind: "keyframe",
|
|
35733
|
+
beatId: beat.id,
|
|
35734
|
+
cue: keyframeCue,
|
|
35735
|
+
provider: ctx.imageProvider,
|
|
35736
|
+
options: metadataOptions,
|
|
35737
|
+
cacheKey: cache.key,
|
|
35738
|
+
canonicalPath: rel,
|
|
35739
|
+
cachePath: cache.path
|
|
35740
|
+
});
|
|
35741
|
+
return { status: "generated", rel };
|
|
35742
|
+
}
|
|
35611
35743
|
async function imageBufferFromResult(result) {
|
|
35612
35744
|
const image = result.images?.[0];
|
|
35613
35745
|
if (!result.success || !image) {
|
|
@@ -35631,7 +35763,8 @@ function imageProviderKeyInfo(provider) {
|
|
|
35631
35763
|
async function dispatchVideo(beat, ctx) {
|
|
35632
35764
|
const reference = assetReferenceForBeat(ctx.projectDir, "video", beat);
|
|
35633
35765
|
if (reference) return referencePrimitiveOutcome("video", beat, ctx, reference);
|
|
35634
|
-
const
|
|
35766
|
+
const keyframeCue = stringOrUndefined4(beat.cues?.keyframe);
|
|
35767
|
+
const prompt3 = stringOrUndefined4(beat.cues?.video) ?? keyframeCue;
|
|
35635
35768
|
if (!prompt3) return { status: "no-cue" };
|
|
35636
35769
|
const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p) => typeof p === "string");
|
|
35637
35770
|
const characterRefsAbs = characterRefsRel.map((p) => join16(ctx.projectDir, p));
|
|
@@ -35642,7 +35775,10 @@ async function dispatchVideo(beat, ctx) {
|
|
|
35642
35775
|
cue: prompt3,
|
|
35643
35776
|
provider: ctx.videoProvider,
|
|
35644
35777
|
duration: normalizeVideoDuration(beat.duration),
|
|
35645
|
-
|
|
35778
|
+
// Keyframe mode keys the clip to the keyframe prompt; refs feed the keyframe,
|
|
35779
|
+
// not the clip directly.
|
|
35780
|
+
characters: keyframeCue ? void 0 : characterRefsRel,
|
|
35781
|
+
keyframe: keyframeCue
|
|
35646
35782
|
});
|
|
35647
35783
|
const metadataPath = assetMetadataPath("video", beat.id);
|
|
35648
35784
|
if (existsSync19(abs) && !ctx.force) {
|
|
@@ -35695,6 +35831,19 @@ async function dispatchVideo(beat, ctx) {
|
|
|
35695
35831
|
};
|
|
35696
35832
|
}
|
|
35697
35833
|
loadSceneBuildEnv(ctx.projectDir);
|
|
35834
|
+
let keyframeStatus;
|
|
35835
|
+
let keyframeRel;
|
|
35836
|
+
let keyframeImageAbs;
|
|
35837
|
+
if (keyframeCue) {
|
|
35838
|
+
const kf = await ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel);
|
|
35839
|
+
if (kf.status === "failed") {
|
|
35840
|
+
ctx.onProgress({ type: "video-failed", beatId: beat.id, error: kf.error });
|
|
35841
|
+
return { status: "failed", error: kf.error };
|
|
35842
|
+
}
|
|
35843
|
+
keyframeStatus = kf.status;
|
|
35844
|
+
keyframeRel = kf.rel;
|
|
35845
|
+
keyframeImageAbs = join16(ctx.projectDir, kf.rel);
|
|
35846
|
+
}
|
|
35698
35847
|
const result = await executeVideoGenerate({
|
|
35699
35848
|
prompt: prompt3,
|
|
35700
35849
|
provider: ctx.videoProvider,
|
|
@@ -35702,7 +35851,10 @@ async function dispatchVideo(beat, ctx) {
|
|
|
35702
35851
|
ratio: "16:9",
|
|
35703
35852
|
output: abs,
|
|
35704
35853
|
wait: false,
|
|
35705
|
-
|
|
35854
|
+
// Keyframe mode → single init frame (image-to-video); otherwise character
|
|
35855
|
+
// reference-to-video.
|
|
35856
|
+
image: keyframeImageAbs,
|
|
35857
|
+
refImages: keyframeImageAbs ? void 0 : characterRefsAbs.length > 0 ? characterRefsAbs : void 0,
|
|
35706
35858
|
apiKey: await apiKeyForVideoProvider(ctx.videoProvider, ctx.projectDir)
|
|
35707
35859
|
});
|
|
35708
35860
|
if (!result.success || !result.taskId) {
|
|
@@ -35737,14 +35889,16 @@ async function dispatchVideo(beat, ctx) {
|
|
|
35737
35889
|
cachePath: cache.path,
|
|
35738
35890
|
cacheKey: cache.key,
|
|
35739
35891
|
metadataPath,
|
|
35740
|
-
freshness: "fresh"
|
|
35892
|
+
freshness: "fresh",
|
|
35893
|
+
keyframeStatus,
|
|
35894
|
+
keyframePath: keyframeRel
|
|
35741
35895
|
};
|
|
35742
35896
|
}
|
|
35743
35897
|
const job = await createAndWriteJobRecord({
|
|
35744
35898
|
jobType: "generate-video",
|
|
35745
35899
|
provider: result.provider ?? ctx.videoProvider,
|
|
35746
35900
|
providerTaskId: result.taskId,
|
|
35747
|
-
providerTaskType: "text2video",
|
|
35901
|
+
providerTaskType: keyframeImageAbs ? "image2video" : "text2video",
|
|
35748
35902
|
status: "running",
|
|
35749
35903
|
projectDir: ctx.projectDir,
|
|
35750
35904
|
workingDirectory: ctx.projectDir,
|
|
@@ -35769,7 +35923,9 @@ async function dispatchVideo(beat, ctx) {
|
|
|
35769
35923
|
provider: job.provider,
|
|
35770
35924
|
cachePath: cache.path,
|
|
35771
35925
|
cacheKey: cache.key,
|
|
35772
|
-
metadataPath
|
|
35926
|
+
metadataPath,
|
|
35927
|
+
keyframeStatus,
|
|
35928
|
+
keyframePath: keyframeRel
|
|
35773
35929
|
};
|
|
35774
35930
|
}
|
|
35775
35931
|
async function dispatchMusic(beat, ctx) {
|
|
@@ -36071,6 +36227,7 @@ function estimateActualAssetCost(outcomes, imageQuality) {
|
|
|
36071
36227
|
for (const outcome of outcomes) {
|
|
36072
36228
|
if (outcome.narrationStatus === "generated") cost += 0.05;
|
|
36073
36229
|
if (outcome.backdropStatus === "generated") cost += backdropCostUsd(imageQuality);
|
|
36230
|
+
if (outcome.keyframeStatus === "generated") cost += backdropCostUsd(imageQuality);
|
|
36074
36231
|
if (outcome.videoStatus === "generated" || outcome.videoStatus === "pending") {
|
|
36075
36232
|
cost += outcome.videoProvider === "seedance" || outcome.videoProvider === "fal" ? estimateSeedanceVideoCostUsd({
|
|
36076
36233
|
durationSec: normalizeVideoDuration(outcome.sceneDurationSec),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibeframe/cli",
|
|
3
|
-
"version": "0.113.
|
|
3
|
+
"version": "0.113.3",
|
|
4
4
|
"description": "VibeFrame CLI - agentic video workflow runtime",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"tsx": "^4.21.0",
|
|
84
84
|
"typescript": "^5.3.3",
|
|
85
85
|
"vitest": "^1.2.2",
|
|
86
|
-
"@vibeframe/
|
|
87
|
-
"@vibeframe/
|
|
86
|
+
"@vibeframe/core": "0.113.3",
|
|
87
|
+
"@vibeframe/ai-providers": "0.113.3"
|
|
88
88
|
},
|
|
89
89
|
"engines": {
|
|
90
90
|
"node": ">=20"
|