@vibeframe/cli 0.91.8 → 0.92.0
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/index.js +1576 -964
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -14443,8 +14443,8 @@ var init_api_keys = __esm({
|
|
|
14443
14443
|
configKey: "imgbb",
|
|
14444
14444
|
envVar: "IMGBB_API_KEY",
|
|
14445
14445
|
label: "ImgBB",
|
|
14446
|
-
showInSetup:
|
|
14447
|
-
|
|
14446
|
+
showInSetup: true,
|
|
14447
|
+
setupDescription: "image hosting for Seedance/Kling image-to-video uploads",
|
|
14448
14448
|
envExampleComment: "ImgBB API Key (image hosting \u2014 used by Kling and Seedance for image-to-video uploads)",
|
|
14449
14449
|
envExampleUrl: "https://api.imgbb.com/",
|
|
14450
14450
|
// ImgBB issues 32-character lowercase hex keys. Soft-warn if a pasted
|
|
@@ -14453,9 +14453,7 @@ var init_api_keys = __esm({
|
|
|
14453
14453
|
keyFormat: { prefix: /^[a-f0-9]{32}$/, example: "32-char hex" },
|
|
14454
14454
|
// ImgBB has no provider class (envvar-only); doctor still shows what it
|
|
14455
14455
|
// unlocks at the apiKey level.
|
|
14456
|
-
commandsUnlocked: [
|
|
14457
|
-
"generate video -p kling/seedance (image-to-video upload host)"
|
|
14458
|
-
]
|
|
14456
|
+
commandsUnlocked: ["generate video -p kling/seedance (image-to-video upload host)"]
|
|
14459
14457
|
});
|
|
14460
14458
|
defineProvider({
|
|
14461
14459
|
id: "openrouter",
|
|
@@ -25677,6 +25675,13 @@ function createDefaultConfig() {
|
|
|
25677
25675
|
aspectRatio: "16:9",
|
|
25678
25676
|
exportQuality: "standard"
|
|
25679
25677
|
},
|
|
25678
|
+
upload: {
|
|
25679
|
+
provider: "imgbb",
|
|
25680
|
+
ttlSeconds: 3600,
|
|
25681
|
+
s3: {
|
|
25682
|
+
prefix: "vibeframe/tmp"
|
|
25683
|
+
}
|
|
25684
|
+
},
|
|
25680
25685
|
repl: {
|
|
25681
25686
|
autoSave: true
|
|
25682
25687
|
}
|
|
@@ -25700,6 +25705,26 @@ var init_schema = __esm({
|
|
|
25700
25705
|
});
|
|
25701
25706
|
|
|
25702
25707
|
// src/config/index.ts
|
|
25708
|
+
var config_exports = {};
|
|
25709
|
+
__export(config_exports, {
|
|
25710
|
+
CONFIG_DIR: () => CONFIG_DIR,
|
|
25711
|
+
CONFIG_PATH: () => CONFIG_PATH,
|
|
25712
|
+
PROVIDER_ENV_VARS: () => PROVIDER_ENV_VARS,
|
|
25713
|
+
PROVIDER_NAMES: () => PROVIDER_NAMES,
|
|
25714
|
+
USER_CONFIG_DIR: () => USER_CONFIG_DIR,
|
|
25715
|
+
USER_CONFIG_PATH: () => USER_CONFIG_PATH,
|
|
25716
|
+
createDefaultConfig: () => createDefaultConfig,
|
|
25717
|
+
getActiveScope: () => getActiveScope,
|
|
25718
|
+
getApiKeyFromConfig: () => getApiKeyFromConfig,
|
|
25719
|
+
getConfigDir: () => getConfigDir,
|
|
25720
|
+
getConfigPath: () => getConfigPath,
|
|
25721
|
+
getProjectConfigDir: () => getProjectConfigDir,
|
|
25722
|
+
getProjectConfigPath: () => getProjectConfigPath,
|
|
25723
|
+
isConfigured: () => isConfigured,
|
|
25724
|
+
loadConfig: () => loadConfig,
|
|
25725
|
+
saveConfig: () => saveConfig,
|
|
25726
|
+
updateProviderKey: () => updateProviderKey
|
|
25727
|
+
});
|
|
25703
25728
|
import { resolve as resolve4 } from "node:path";
|
|
25704
25729
|
import { homedir } from "node:os";
|
|
25705
25730
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, access as access2 } from "node:fs/promises";
|
|
@@ -25734,6 +25759,11 @@ function applyDefaults(parsed) {
|
|
|
25734
25759
|
llm: { ...defaults.llm, ...parsed.llm },
|
|
25735
25760
|
providers: { ...defaults.providers, ...parsed.providers },
|
|
25736
25761
|
defaults: { ...defaults.defaults, ...parsed.defaults },
|
|
25762
|
+
upload: {
|
|
25763
|
+
...defaults.upload,
|
|
25764
|
+
...parsed.upload,
|
|
25765
|
+
s3: { ...defaults.upload.s3, ...parsed.upload?.s3 }
|
|
25766
|
+
},
|
|
25737
25767
|
repl: { ...defaults.repl, ...parsed.repl }
|
|
25738
25768
|
};
|
|
25739
25769
|
}
|
|
@@ -25761,6 +25791,11 @@ async function loadConfig(options = {}) {
|
|
|
25761
25791
|
llm: { ...user.llm, ...project2.llm },
|
|
25762
25792
|
providers: { ...user.providers, ...project2.providers },
|
|
25763
25793
|
defaults: { ...user.defaults, ...project2.defaults },
|
|
25794
|
+
upload: {
|
|
25795
|
+
...user.upload,
|
|
25796
|
+
...project2.upload,
|
|
25797
|
+
s3: { ...user.upload.s3, ...project2.upload.s3 }
|
|
25798
|
+
},
|
|
25764
25799
|
repl: { ...user.repl, ...project2.repl }
|
|
25765
25800
|
};
|
|
25766
25801
|
}
|
|
@@ -25802,7 +25837,14 @@ async function getApiKeyFromConfig(providerKey) {
|
|
|
25802
25837
|
if (envVar) return process.env[envVar];
|
|
25803
25838
|
return void 0;
|
|
25804
25839
|
}
|
|
25805
|
-
|
|
25840
|
+
async function updateProviderKey(providerKey, apiKey, options = {}) {
|
|
25841
|
+
const scope = options.scope ?? await getActiveScope(options.cwd);
|
|
25842
|
+
let config2 = await loadConfig({ scope, cwd: options.cwd });
|
|
25843
|
+
if (!config2) config2 = createDefaultConfig();
|
|
25844
|
+
config2.providers[providerKey] = apiKey;
|
|
25845
|
+
await saveConfig(config2, { scope, cwd: options.cwd });
|
|
25846
|
+
}
|
|
25847
|
+
var import_yaml, USER_CONFIG_DIR, USER_CONFIG_PATH, CONFIG_DIR, CONFIG_PATH;
|
|
25806
25848
|
var init_config = __esm({
|
|
25807
25849
|
"src/config/index.ts"() {
|
|
25808
25850
|
"use strict";
|
|
@@ -25811,6 +25853,7 @@ var init_config = __esm({
|
|
|
25811
25853
|
init_schema();
|
|
25812
25854
|
USER_CONFIG_DIR = resolve4(homedir(), ".vibeframe");
|
|
25813
25855
|
USER_CONFIG_PATH = resolve4(USER_CONFIG_DIR, "config.yaml");
|
|
25856
|
+
CONFIG_DIR = USER_CONFIG_DIR;
|
|
25814
25857
|
CONFIG_PATH = USER_CONFIG_PATH;
|
|
25815
25858
|
}
|
|
25816
25859
|
});
|
|
@@ -25910,7 +25953,9 @@ async function getApiKey(envVar, providerName, optionValue) {
|
|
|
25910
25953
|
}
|
|
25911
25954
|
console.log();
|
|
25912
25955
|
console.log(source_default.yellow(`${providerName} API key not found.`));
|
|
25913
|
-
console.log(
|
|
25956
|
+
console.log(
|
|
25957
|
+
source_default.dim(`Set ${envVar} in .env (current directory), run 'vibe setup', or enter below.`)
|
|
25958
|
+
);
|
|
25914
25959
|
console.log();
|
|
25915
25960
|
const apiKey = await prompt(source_default.cyan(`Enter ${providerName} API key: `), true);
|
|
25916
25961
|
if (!apiKey || apiKey.trim() === "") {
|
|
@@ -25969,7 +26014,8 @@ var init_api_key = __esm({
|
|
|
25969
26014
|
ELEVENLABS_API_KEY: "https://elevenlabs.io/app/settings/api-keys",
|
|
25970
26015
|
RUNWAY_API_SECRET: "https://app.runwayml.com/settings/api-keys",
|
|
25971
26016
|
KLING_API_KEY: "https://klingai.com/dev",
|
|
25972
|
-
REPLICATE_API_TOKEN: "https://replicate.com/account/api-tokens"
|
|
26017
|
+
REPLICATE_API_TOKEN: "https://replicate.com/account/api-tokens",
|
|
26018
|
+
IMGBB_API_KEY: "https://api.imgbb.com/"
|
|
25973
26019
|
};
|
|
25974
26020
|
ApiKeyError = class extends Error {
|
|
25975
26021
|
envVar;
|
|
@@ -31792,7 +31838,7 @@ async function promptHidden(question, opts = {}) {
|
|
|
31792
31838
|
}
|
|
31793
31839
|
});
|
|
31794
31840
|
}
|
|
31795
|
-
async function promptSelect(_question, options, defaultIndex = 0) {
|
|
31841
|
+
async function promptSelect(_question, options, defaultIndex = 0, opts = {}) {
|
|
31796
31842
|
const input = getTTYInputStream();
|
|
31797
31843
|
if (typeof input.setRawMode === "function") {
|
|
31798
31844
|
return new Promise((resolve64) => {
|
|
@@ -31827,6 +31873,12 @@ async function promptSelect(_question, options, defaultIndex = 0) {
|
|
|
31827
31873
|
input.setRawMode(false);
|
|
31828
31874
|
process.stdout.write("\n");
|
|
31829
31875
|
process.exit(1);
|
|
31876
|
+
} else if (opts.backIndex !== void 0 && (char === "\x1B" || char === "\x1B[D")) {
|
|
31877
|
+
finished = true;
|
|
31878
|
+
input.setRawMode(false);
|
|
31879
|
+
input.removeListener("data", onData);
|
|
31880
|
+
process.stdout.write("\n");
|
|
31881
|
+
resolve64(opts.backIndex);
|
|
31830
31882
|
} else if (char === "\x1B[A" || char === "k") {
|
|
31831
31883
|
selected = (selected - 1 + options.length) % options.length;
|
|
31832
31884
|
render();
|
|
@@ -31982,6 +32034,11 @@ async function promptMultiSelect(_question, options, defaultSelected = [], opts
|
|
|
31982
32034
|
input.setRawMode(false);
|
|
31983
32035
|
process.stdout.write("\n");
|
|
31984
32036
|
process.exit(1);
|
|
32037
|
+
} else if (opts.backIndex !== void 0 && (char === "\x1B" || char === "\x1B[D")) {
|
|
32038
|
+
for (let i = 0; i < selected.length; i++) selected[i] = false;
|
|
32039
|
+
selected[opts.backIndex] = true;
|
|
32040
|
+
finished = true;
|
|
32041
|
+
finish();
|
|
31985
32042
|
} else if (char === " ") {
|
|
31986
32043
|
selected[cursor] = !selected[cursor];
|
|
31987
32044
|
userToggled = true;
|
|
@@ -33342,29 +33399,61 @@ var init_video_extend = __esm({
|
|
|
33342
33399
|
});
|
|
33343
33400
|
|
|
33344
33401
|
// src/utils/provider-resolver.ts
|
|
33402
|
+
function loadProviderDefaultsFromConfig(config2) {
|
|
33403
|
+
configDefaults = {};
|
|
33404
|
+
if (config2?.defaults.imageProvider) configDefaults.image = config2.defaults.imageProvider;
|
|
33405
|
+
if (config2?.defaults.videoProvider) configDefaults.video = config2.defaults.videoProvider;
|
|
33406
|
+
configProviderKeys = new Set(
|
|
33407
|
+
Object.entries(config2?.providers ?? {}).filter(([, value]) => Boolean(value)).map(([key]) => key)
|
|
33408
|
+
);
|
|
33409
|
+
}
|
|
33410
|
+
async function loadProviderDefaults() {
|
|
33411
|
+
try {
|
|
33412
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
33413
|
+
const config2 = await loadConfig2();
|
|
33414
|
+
loadProviderDefaultsFromConfig(config2);
|
|
33415
|
+
} catch {
|
|
33416
|
+
configDefaults = null;
|
|
33417
|
+
configProviderKeys = null;
|
|
33418
|
+
}
|
|
33419
|
+
}
|
|
33420
|
+
function providerKeyForEnvVar(envVar) {
|
|
33421
|
+
const envVars = getProviderEnvVars();
|
|
33422
|
+
for (const [providerKey, candidateEnvVar] of Object.entries(envVars)) {
|
|
33423
|
+
if (candidateEnvVar === envVar) return providerKey;
|
|
33424
|
+
}
|
|
33425
|
+
return null;
|
|
33426
|
+
}
|
|
33427
|
+
function hasCandidateKey(candidate) {
|
|
33428
|
+
if (candidate.envVar === null) return true;
|
|
33429
|
+
if (hasApiKey(candidate.envVar)) return true;
|
|
33430
|
+
const providerKey = providerKeyForEnvVar(candidate.envVar);
|
|
33431
|
+
return providerKey ? Boolean(configProviderKeys?.has(providerKey)) : false;
|
|
33432
|
+
}
|
|
33345
33433
|
function resolveProvider(category) {
|
|
33346
33434
|
const candidates = getProvidersFor(category);
|
|
33347
33435
|
if (candidates.length === 0) return null;
|
|
33348
33436
|
if (configDefaults?.[category]) {
|
|
33349
33437
|
const preferred = candidates.find((c) => c.name === configDefaults[category]);
|
|
33350
|
-
if (preferred && (preferred
|
|
33438
|
+
if (preferred && hasCandidateKey(preferred)) {
|
|
33351
33439
|
return { name: preferred.name, label: preferred.label };
|
|
33352
33440
|
}
|
|
33353
33441
|
}
|
|
33354
33442
|
for (const candidate of candidates) {
|
|
33355
|
-
if (
|
|
33443
|
+
if (hasCandidateKey(candidate)) {
|
|
33356
33444
|
return { name: candidate.name, label: candidate.label };
|
|
33357
33445
|
}
|
|
33358
33446
|
}
|
|
33359
33447
|
return null;
|
|
33360
33448
|
}
|
|
33361
|
-
var configDefaults;
|
|
33449
|
+
var configDefaults, configProviderKeys;
|
|
33362
33450
|
var init_provider_resolver = __esm({
|
|
33363
33451
|
"src/utils/provider-resolver.ts"() {
|
|
33364
33452
|
"use strict";
|
|
33365
33453
|
init_dist2();
|
|
33366
33454
|
init_api_key();
|
|
33367
33455
|
configDefaults = null;
|
|
33456
|
+
configProviderKeys = null;
|
|
33368
33457
|
}
|
|
33369
33458
|
});
|
|
33370
33459
|
|
|
@@ -33401,13 +33490,26 @@ import { resolve as resolve23, dirname as dirname7 } from "node:path";
|
|
|
33401
33490
|
import { fileURLToPath } from "node:url";
|
|
33402
33491
|
import { writeFile as writeFile21, mkdir as mkdir11 } from "node:fs/promises";
|
|
33403
33492
|
function registerImageCommand(parent) {
|
|
33404
|
-
parent.command("image").alias("img").description("Generate image using AI (Gemini, OpenAI gpt-image, Grok, or Runway)").argument("[prompt]", "Image description prompt (interactive if omitted)").option(
|
|
33493
|
+
parent.command("image").alias("img").description("Generate image using AI (Gemini, OpenAI gpt-image, Grok, or Runway)").argument("[prompt]", "Image description prompt (interactive if omitted)").option(
|
|
33494
|
+
"-p, --provider <provider>",
|
|
33495
|
+
"Provider: openai (default when OPENAI_API_KEY set), gemini, grok, runway"
|
|
33496
|
+
).option("-k, --api-key <key>", "API key (or set env: OPENAI_API_KEY, GOOGLE_API_KEY)").option("-o, --output <path>", "Output file path (downloads image)").option("--size <size>", "Image size (openai: 1024x1024, 1536x1024, 1024x1536)", "1024x1024").option(
|
|
33497
|
+
"-r, --ratio <ratio>",
|
|
33498
|
+
"Aspect ratio (gemini: 1:1, 1:4, 1:8, 4:1, 8:1, 16:9, 9:16, 3:4, 4:3, etc.)",
|
|
33499
|
+
"1:1"
|
|
33500
|
+
).option("--quality <quality>", "Quality: standard, hd (openai only)", "standard").option("--style <style>", "Style: vivid, natural (openai only)", "vivid").option("--count <n>", "Number of images to generate", "1").option(
|
|
33501
|
+
"-m, --model <model>",
|
|
33502
|
+
"Model. Gemini: flash, 3.1-flash, latest, pro. OpenAI: 1.5 (default), 2 (gpt-image-2)"
|
|
33503
|
+
).option("--dry-run", "Preview parameters without executing").addHelpText(
|
|
33504
|
+
"after",
|
|
33505
|
+
`
|
|
33405
33506
|
Examples:
|
|
33406
33507
|
$ vibe generate image "a sunset over the ocean" -o sunset.png
|
|
33407
33508
|
$ vibe gen img "logo design" -o logo.png -p openai
|
|
33408
33509
|
$ vibe gen img "landscape photo" -o wide.png -r 16:9
|
|
33409
33510
|
$ vibe gen img "portrait" -o portrait.png -p gemini -m pro
|
|
33410
|
-
$ vibe gen img "product shot" --dry-run --json`
|
|
33511
|
+
$ vibe gen img "product shot" --dry-run --json`
|
|
33512
|
+
).action(async (prompt3, options) => {
|
|
33411
33513
|
const startedAt = Date.now();
|
|
33412
33514
|
try {
|
|
33413
33515
|
if (!prompt3) {
|
|
@@ -33418,10 +33520,7 @@ Examples:
|
|
|
33418
33520
|
}
|
|
33419
33521
|
} else {
|
|
33420
33522
|
exitWithError(
|
|
33421
|
-
usageError(
|
|
33422
|
-
"Prompt argument is required.",
|
|
33423
|
-
"Usage: vibe generate image <prompt>"
|
|
33424
|
-
)
|
|
33523
|
+
usageError("Prompt argument is required.", "Usage: vibe generate image <prompt>")
|
|
33425
33524
|
);
|
|
33426
33525
|
}
|
|
33427
33526
|
}
|
|
@@ -33429,13 +33528,16 @@ Examples:
|
|
|
33429
33528
|
if (options.output) {
|
|
33430
33529
|
validateOutputPath(options.output);
|
|
33431
33530
|
}
|
|
33531
|
+
await loadProviderDefaults();
|
|
33432
33532
|
if (options.count !== void 0) {
|
|
33433
33533
|
const n = parseInt(options.count, 10);
|
|
33434
33534
|
if (!Number.isFinite(n) || n < 1 || n > 10) {
|
|
33435
|
-
exitWithError(
|
|
33436
|
-
|
|
33437
|
-
|
|
33438
|
-
|
|
33535
|
+
exitWithError(
|
|
33536
|
+
usageError(
|
|
33537
|
+
`Invalid --count: ${options.count}`,
|
|
33538
|
+
"Must be an integer between 1 and 10."
|
|
33539
|
+
)
|
|
33540
|
+
);
|
|
33439
33541
|
}
|
|
33440
33542
|
}
|
|
33441
33543
|
const imageRegistry = getProvidersFor("image");
|
|
@@ -33500,11 +33602,9 @@ Examples:
|
|
|
33500
33602
|
const apiKey = await requireApiKey(envKey, providerName, options.apiKey);
|
|
33501
33603
|
const spinner2 = ora(`Generating image with ${providerName}...`).start();
|
|
33502
33604
|
if (provider === "openai") {
|
|
33503
|
-
const { result, modelLabel } = await executeOpenAIImageGenerate(
|
|
33504
|
-
|
|
33505
|
-
|
|
33506
|
-
{ apiKey }
|
|
33507
|
-
);
|
|
33605
|
+
const { result, modelLabel } = await executeOpenAIImageGenerate(prompt3, options, {
|
|
33606
|
+
apiKey
|
|
33607
|
+
});
|
|
33508
33608
|
if (!result.success || !result.images) {
|
|
33509
33609
|
spinner2.fail(result.error || "Image generation failed");
|
|
33510
33610
|
exitWithError(apiError(result.error || "Image generation failed", true));
|
|
@@ -33726,9 +33826,7 @@ Examples:
|
|
|
33726
33826
|
spinner2.fail(result.error || "Image generation failed");
|
|
33727
33827
|
exitWithError(apiError(result.error || "Image generation failed", true));
|
|
33728
33828
|
}
|
|
33729
|
-
spinner2.succeed(
|
|
33730
|
-
source_default.green(`Generated ${result.images.length} image(s) with xAI Grok`)
|
|
33731
|
-
);
|
|
33829
|
+
spinner2.succeed(source_default.green(`Generated ${result.images.length} image(s) with xAI Grok`));
|
|
33732
33830
|
if (isJsonMode()) {
|
|
33733
33831
|
const outputPath = options.output ? resolve23(process.cwd(), options.output) : void 0;
|
|
33734
33832
|
if (outputPath && result.images.length > 0) {
|
|
@@ -34961,606 +35059,206 @@ var init_video_utils = __esm({
|
|
|
34961
35059
|
}
|
|
34962
35060
|
});
|
|
34963
35061
|
|
|
34964
|
-
// src/
|
|
34965
|
-
|
|
34966
|
-
|
|
34967
|
-
|
|
34968
|
-
|
|
34969
|
-
|
|
34970
|
-
|
|
34971
|
-
|
|
34972
|
-
aspectRatio: options.aspectRatio,
|
|
34973
|
-
referenceImage: options.referenceImage
|
|
34974
|
-
});
|
|
34975
|
-
if (result.status !== "failed" && result.id) {
|
|
34976
|
-
return { requestId: result.id };
|
|
34977
|
-
}
|
|
34978
|
-
const providerErr = result.error || "Grok returned failed status";
|
|
34979
|
-
if (attempt < maxRetries) {
|
|
34980
|
-
onProgress?.(
|
|
34981
|
-
`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`
|
|
34982
|
-
);
|
|
34983
|
-
await sleep(RETRY_DELAY_MS);
|
|
34984
|
-
} else {
|
|
34985
|
-
console.error(source_default.dim(`
|
|
34986
|
-
[Grok error: ${providerErr}]`));
|
|
34987
|
-
}
|
|
34988
|
-
} catch (err) {
|
|
34989
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
34990
|
-
if (attempt < maxRetries) {
|
|
34991
|
-
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
34992
|
-
await sleep(RETRY_DELAY_MS);
|
|
34993
|
-
} else {
|
|
34994
|
-
console.error(source_default.dim(`
|
|
34995
|
-
[Grok error: ${errMsg}]`));
|
|
34996
|
-
}
|
|
34997
|
-
}
|
|
34998
|
-
}
|
|
34999
|
-
return null;
|
|
35062
|
+
// src/utils/upload-host.ts
|
|
35063
|
+
import { createHash as createHash4, createHmac as createHmac2, randomUUID } from "node:crypto";
|
|
35064
|
+
import { extname as extname3 } from "node:path";
|
|
35065
|
+
function envNumber(name) {
|
|
35066
|
+
const value = process.env[name];
|
|
35067
|
+
if (!value) return void 0;
|
|
35068
|
+
const n = Number.parseInt(value, 10);
|
|
35069
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
35000
35070
|
}
|
|
35001
|
-
|
|
35002
|
-
|
|
35003
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
35004
|
-
try {
|
|
35005
|
-
const result = await kling.generateVideo(prompt3, {
|
|
35006
|
-
prompt: prompt3,
|
|
35007
|
-
// Pass reference image (base64 or URL) - KlingProvider handles v1.5 fallback for base64
|
|
35008
|
-
referenceImage: options.referenceImage,
|
|
35009
|
-
duration: options.duration,
|
|
35010
|
-
aspectRatio: options.aspectRatio,
|
|
35011
|
-
mode: "std"
|
|
35012
|
-
// std mode for faster generation
|
|
35013
|
-
});
|
|
35014
|
-
if (result.status !== "failed" && result.id) {
|
|
35015
|
-
return {
|
|
35016
|
-
taskId: result.id,
|
|
35017
|
-
type: options.referenceImage ? "image2video" : "text2video"
|
|
35018
|
-
};
|
|
35019
|
-
}
|
|
35020
|
-
const providerErr = result.error || "Kling returned failed status";
|
|
35021
|
-
if (attempt < maxRetries) {
|
|
35022
|
-
onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
35023
|
-
await sleep(RETRY_DELAY_MS);
|
|
35024
|
-
} else {
|
|
35025
|
-
console.error(source_default.dim(`
|
|
35026
|
-
[Kling error: ${providerErr}]`));
|
|
35027
|
-
}
|
|
35028
|
-
} catch (err) {
|
|
35029
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
35030
|
-
if (attempt < maxRetries) {
|
|
35031
|
-
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
35032
|
-
await sleep(RETRY_DELAY_MS);
|
|
35033
|
-
} else {
|
|
35034
|
-
console.error(source_default.dim(`
|
|
35035
|
-
[Kling error: ${errMsg}]`));
|
|
35036
|
-
}
|
|
35037
|
-
}
|
|
35038
|
-
}
|
|
35039
|
-
return null;
|
|
35071
|
+
function safePrefix(prefix) {
|
|
35072
|
+
return (prefix ?? "vibeframe/tmp").replace(/^\/+|\/+$/g, "");
|
|
35040
35073
|
}
|
|
35041
|
-
|
|
35042
|
-
|
|
35043
|
-
|
|
35044
|
-
|
|
35045
|
-
|
|
35046
|
-
|
|
35047
|
-
|
|
35048
|
-
|
|
35049
|
-
});
|
|
35050
|
-
if (result.status !== "failed" && result.id) {
|
|
35051
|
-
return { taskId: result.id };
|
|
35052
|
-
}
|
|
35053
|
-
const providerErr = result.error || "Runway returned failed status";
|
|
35054
|
-
if (attempt < maxRetries) {
|
|
35055
|
-
onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
35056
|
-
await sleep(RETRY_DELAY_MS);
|
|
35057
|
-
} else {
|
|
35058
|
-
console.error(source_default.dim(`
|
|
35059
|
-
[Runway error: ${providerErr}]`));
|
|
35060
|
-
}
|
|
35061
|
-
} catch (err) {
|
|
35062
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
35063
|
-
if (attempt < maxRetries) {
|
|
35064
|
-
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
35065
|
-
await sleep(RETRY_DELAY_MS);
|
|
35066
|
-
} else {
|
|
35067
|
-
console.error(source_default.dim(`
|
|
35068
|
-
[Runway error: ${errMsg}]`));
|
|
35069
|
-
}
|
|
35070
|
-
}
|
|
35071
|
-
}
|
|
35072
|
-
return null;
|
|
35074
|
+
function extensionFor(opts) {
|
|
35075
|
+
const fromName = opts?.filename ? extname3(opts.filename).replace(/^\./, "") : "";
|
|
35076
|
+
if (fromName) return fromName.toLowerCase();
|
|
35077
|
+
const mime = opts?.mimeType ?? "image/png";
|
|
35078
|
+
if (mime === "image/jpeg") return "jpg";
|
|
35079
|
+
if (mime === "image/webp") return "webp";
|
|
35080
|
+
if (mime === "image/gif") return "gif";
|
|
35081
|
+
return "png";
|
|
35073
35082
|
}
|
|
35074
|
-
|
|
35075
|
-
|
|
35076
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
35077
|
-
try {
|
|
35078
|
-
const result = await gemini.generateVideo(prompt3, {
|
|
35079
|
-
prompt: prompt3,
|
|
35080
|
-
referenceImage: options.referenceImage,
|
|
35081
|
-
duration: options.duration,
|
|
35082
|
-
aspectRatio: options.aspectRatio,
|
|
35083
|
-
model: "veo-3.1-fast-generate-preview"
|
|
35084
|
-
});
|
|
35085
|
-
if (result.status !== "failed" && result.id) {
|
|
35086
|
-
return { operationName: result.id };
|
|
35087
|
-
}
|
|
35088
|
-
const providerErr = result.error || "Veo returned failed status";
|
|
35089
|
-
if (attempt < maxRetries) {
|
|
35090
|
-
onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
35091
|
-
await sleep(RETRY_DELAY_MS);
|
|
35092
|
-
} else {
|
|
35093
|
-
console.error(source_default.dim(`
|
|
35094
|
-
[Veo error: ${providerErr}]`));
|
|
35095
|
-
}
|
|
35096
|
-
} catch (err) {
|
|
35097
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
35098
|
-
if (attempt < maxRetries) {
|
|
35099
|
-
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
35100
|
-
await sleep(RETRY_DELAY_MS);
|
|
35101
|
-
} else {
|
|
35102
|
-
console.error(source_default.dim(`
|
|
35103
|
-
[Veo error: ${errMsg}]`));
|
|
35104
|
-
}
|
|
35105
|
-
}
|
|
35106
|
-
}
|
|
35107
|
-
return null;
|
|
35083
|
+
function hmac(key, value) {
|
|
35084
|
+
return createHmac2("sha256", key).update(value).digest();
|
|
35108
35085
|
}
|
|
35109
|
-
|
|
35110
|
-
"
|
|
35111
|
-
|
|
35112
|
-
|
|
35113
|
-
|
|
35114
|
-
|
|
35115
|
-
}
|
|
35116
|
-
|
|
35117
|
-
|
|
35118
|
-
|
|
35119
|
-
|
|
35120
|
-
|
|
35121
|
-
|
|
35122
|
-
const
|
|
35123
|
-
|
|
35124
|
-
|
|
35125
|
-
|
|
35086
|
+
function sha256Hex(value) {
|
|
35087
|
+
return createHash4("sha256").update(value).digest("hex");
|
|
35088
|
+
}
|
|
35089
|
+
function awsEncode(value) {
|
|
35090
|
+
return encodeURIComponent(value).replace(
|
|
35091
|
+
/[!'()*]/g,
|
|
35092
|
+
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
|
|
35093
|
+
);
|
|
35094
|
+
}
|
|
35095
|
+
function presignS3PutUrl(params) {
|
|
35096
|
+
const now = /* @__PURE__ */ new Date();
|
|
35097
|
+
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
35098
|
+
const dateStamp = amzDate.slice(0, 8);
|
|
35099
|
+
const host = `${params.bucket}.s3.${params.region}.amazonaws.com`;
|
|
35100
|
+
const credentialScope = `${dateStamp}/${params.region}/s3/aws4_request`;
|
|
35101
|
+
const canonicalUri = `/${params.key.split("/").map(awsEncode).join("/")}`;
|
|
35102
|
+
const query = {
|
|
35103
|
+
"X-Amz-Algorithm": "AWS4-HMAC-SHA256",
|
|
35104
|
+
"X-Amz-Credential": `${params.accessKeyId}/${credentialScope}`,
|
|
35105
|
+
"X-Amz-Date": amzDate,
|
|
35106
|
+
"X-Amz-Expires": String(params.ttlSeconds),
|
|
35107
|
+
"X-Amz-SignedHeaders": "host"
|
|
35126
35108
|
};
|
|
35127
|
-
|
|
35128
|
-
|
|
35129
|
-
|
|
35130
|
-
|
|
35131
|
-
|
|
35132
|
-
|
|
35133
|
-
|
|
35134
|
-
|
|
35135
|
-
|
|
35136
|
-
|
|
35137
|
-
|
|
35138
|
-
|
|
35139
|
-
|
|
35140
|
-
|
|
35141
|
-
|
|
35142
|
-
|
|
35143
|
-
|
|
35144
|
-
|
|
35145
|
-
|
|
35146
|
-
|
|
35147
|
-
|
|
35148
|
-
|
|
35149
|
-
|
|
35150
|
-
|
|
35151
|
-
|
|
35152
|
-
|
|
35153
|
-
|
|
35154
|
-
|
|
35155
|
-
|
|
35156
|
-
|
|
35157
|
-
|
|
35158
|
-
|
|
35159
|
-
|
|
35160
|
-
|
|
35161
|
-
|
|
35162
|
-
|
|
35163
|
-
|
|
35164
|
-
|
|
35165
|
-
|
|
35166
|
-
|
|
35167
|
-
|
|
35168
|
-
|
|
35169
|
-
|
|
35170
|
-
openai: { envVar: "OPENAI_API_KEY", name: "OpenAI" },
|
|
35171
|
-
gemini: { envVar: "GOOGLE_API_KEY", name: "Google" },
|
|
35172
|
-
grok: { envVar: "XAI_API_KEY", name: "xAI" }
|
|
35173
|
-
};
|
|
35174
|
-
const info = imageKeyMap[imageProvider];
|
|
35175
|
-
if (!info) {
|
|
35176
|
-
return { ...result, error: `Invalid imageProvider: ${imageProvider}` };
|
|
35177
|
-
}
|
|
35178
|
-
imageApiKey = await getApiKey(info.envVar, info.name) ?? void 0;
|
|
35179
|
-
if (!imageApiKey) {
|
|
35180
|
-
return { ...result, error: `${info.name} API key required. Run 'vibe setup' or set ${info.envVar} in .env` };
|
|
35181
|
-
}
|
|
35109
|
+
if (params.sessionToken) {
|
|
35110
|
+
query["X-Amz-Security-Token"] = params.sessionToken;
|
|
35111
|
+
}
|
|
35112
|
+
const canonicalQuery = Object.entries(query).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${awsEncode(key)}=${awsEncode(value)}`).join("&");
|
|
35113
|
+
const canonicalRequest = [
|
|
35114
|
+
"PUT",
|
|
35115
|
+
canonicalUri,
|
|
35116
|
+
canonicalQuery,
|
|
35117
|
+
`host:${host}`,
|
|
35118
|
+
"",
|
|
35119
|
+
"host",
|
|
35120
|
+
"UNSIGNED-PAYLOAD"
|
|
35121
|
+
].join("\n");
|
|
35122
|
+
const stringToSign = [
|
|
35123
|
+
"AWS4-HMAC-SHA256",
|
|
35124
|
+
amzDate,
|
|
35125
|
+
credentialScope,
|
|
35126
|
+
sha256Hex(canonicalRequest)
|
|
35127
|
+
].join("\n");
|
|
35128
|
+
const dateKey = hmac(`AWS4${params.secretAccessKey}`, dateStamp);
|
|
35129
|
+
const regionKey = hmac(dateKey, params.region);
|
|
35130
|
+
const serviceKey = hmac(regionKey, "s3");
|
|
35131
|
+
const signingKey = hmac(serviceKey, "aws4_request");
|
|
35132
|
+
const signature = createHmac2("sha256", signingKey).update(stringToSign).digest("hex");
|
|
35133
|
+
return `https://${host}${canonicalUri}?${canonicalQuery}&X-Amz-Signature=${signature}`;
|
|
35134
|
+
}
|
|
35135
|
+
function publicS3Url(params) {
|
|
35136
|
+
if (params.publicBaseUrl) {
|
|
35137
|
+
return `${params.publicBaseUrl.replace(/\/+$/g, "")}/${params.key.split("/").map(awsEncode).join("/")}`;
|
|
35138
|
+
}
|
|
35139
|
+
return `https://${params.bucket}.s3.${params.region}.amazonaws.com/${params.key.split("/").map(awsEncode).join("/")}`;
|
|
35140
|
+
}
|
|
35141
|
+
async function resolveUploadSettings() {
|
|
35142
|
+
const config2 = await loadConfig();
|
|
35143
|
+
const provider = process.env.VIBE_UPLOAD_PROVIDER ?? config2?.upload.provider ?? "imgbb";
|
|
35144
|
+
return {
|
|
35145
|
+
provider,
|
|
35146
|
+
ttlSeconds: envNumber("VIBE_UPLOAD_TTL_SECONDS") ?? config2?.upload.ttlSeconds ?? 3600,
|
|
35147
|
+
s3: {
|
|
35148
|
+
bucket: process.env.VIBE_UPLOAD_S3_BUCKET ?? config2?.upload.s3?.bucket,
|
|
35149
|
+
region: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? config2?.upload.s3?.region,
|
|
35150
|
+
prefix: process.env.VIBE_UPLOAD_S3_PREFIX ?? config2?.upload.s3?.prefix,
|
|
35151
|
+
publicBaseUrl: process.env.VIBE_UPLOAD_PUBLIC_BASE_URL ?? config2?.upload.s3?.publicBaseUrl
|
|
35182
35152
|
}
|
|
35183
|
-
|
|
35184
|
-
|
|
35185
|
-
|
|
35186
|
-
|
|
35187
|
-
|
|
35188
|
-
|
|
35153
|
+
};
|
|
35154
|
+
}
|
|
35155
|
+
async function resolveUploadHost() {
|
|
35156
|
+
const settings = await resolveUploadSettings();
|
|
35157
|
+
if (settings.provider === "s3") {
|
|
35158
|
+
const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
|
|
35159
|
+
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
|
|
35160
|
+
const sessionToken = process.env.AWS_SESSION_TOKEN;
|
|
35161
|
+
const { bucket, region } = settings.s3;
|
|
35162
|
+
if (!accessKeyId || !secretAccessKey || !bucket || !region) {
|
|
35163
|
+
throw new Error(
|
|
35164
|
+
"S3 upload host requires AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, and VIBE_UPLOAD_S3_BUCKET."
|
|
35165
|
+
);
|
|
35189
35166
|
}
|
|
35190
|
-
|
|
35191
|
-
|
|
35192
|
-
|
|
35193
|
-
|
|
35194
|
-
|
|
35195
|
-
|
|
35196
|
-
|
|
35197
|
-
|
|
35198
|
-
|
|
35199
|
-
|
|
35200
|
-
|
|
35201
|
-
|
|
35202
|
-
|
|
35203
|
-
|
|
35167
|
+
return {
|
|
35168
|
+
provider: "s3",
|
|
35169
|
+
async uploadImage(imageBuffer, opts) {
|
|
35170
|
+
const ext = extensionFor(opts);
|
|
35171
|
+
const prefix = safePrefix(settings.s3.prefix);
|
|
35172
|
+
const key = `${prefix}/${Date.now()}-${randomUUID()}.${ext}`;
|
|
35173
|
+
const presignedUrl = presignS3PutUrl({
|
|
35174
|
+
accessKeyId,
|
|
35175
|
+
secretAccessKey,
|
|
35176
|
+
sessionToken,
|
|
35177
|
+
region,
|
|
35178
|
+
bucket,
|
|
35179
|
+
key,
|
|
35180
|
+
ttlSeconds: settings.ttlSeconds
|
|
35204
35181
|
});
|
|
35205
|
-
|
|
35206
|
-
|
|
35207
|
-
|
|
35208
|
-
|
|
35209
|
-
|
|
35210
|
-
|
|
35211
|
-
}
|
|
35212
|
-
|
|
35213
|
-
|
|
35214
|
-
options.onProgress?.(`Scene ${sceneNum}: regenerating image...`);
|
|
35215
|
-
const imageProvider = options.imageProvider || "openai";
|
|
35216
|
-
const characterDesc = segment.characterDescription || segments[0]?.characterDescription;
|
|
35217
|
-
let imagePrompt = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
|
|
35218
|
-
if (characterDesc) {
|
|
35219
|
-
imagePrompt = `${imagePrompt}
|
|
35220
|
-
|
|
35221
|
-
IMPORTANT - Character appearance must match exactly: ${characterDesc}`;
|
|
35222
|
-
}
|
|
35223
|
-
let referenceImageBuffer;
|
|
35224
|
-
const refSceneNum = options.referenceScene;
|
|
35225
|
-
if (refSceneNum && refSceneNum >= 1 && refSceneNum <= segments.length && refSceneNum !== sceneNum) {
|
|
35226
|
-
const refImagePath = resolve25(outputDir, `scene-${refSceneNum}.png`);
|
|
35227
|
-
if (existsSync14(refImagePath)) {
|
|
35228
|
-
referenceImageBuffer = await readFile13(refImagePath);
|
|
35229
|
-
}
|
|
35230
|
-
} else if (!refSceneNum) {
|
|
35231
|
-
for (let i = 1; i <= segments.length; i++) {
|
|
35232
|
-
if (i !== sceneNum) {
|
|
35233
|
-
const otherImagePath = resolve25(outputDir, `scene-${i}.png`);
|
|
35234
|
-
if (existsSync14(otherImagePath)) {
|
|
35235
|
-
referenceImageBuffer = await readFile13(otherImagePath);
|
|
35236
|
-
break;
|
|
35237
|
-
}
|
|
35238
|
-
}
|
|
35239
|
-
}
|
|
35182
|
+
const response = await fetch(presignedUrl, {
|
|
35183
|
+
method: "PUT",
|
|
35184
|
+
headers: {
|
|
35185
|
+
"content-type": opts?.mimeType ?? "application/octet-stream"
|
|
35186
|
+
},
|
|
35187
|
+
body: new Uint8Array(imageBuffer)
|
|
35188
|
+
});
|
|
35189
|
+
if (!response.ok) {
|
|
35190
|
+
throw new Error(`S3 upload failed (${response.status}): ${response.statusText}`);
|
|
35240
35191
|
}
|
|
35241
|
-
|
|
35242
|
-
|
|
35243
|
-
|
|
35244
|
-
|
|
35192
|
+
return {
|
|
35193
|
+
provider: "s3",
|
|
35194
|
+
url: publicS3Url({ region, bucket, key, publicBaseUrl: settings.s3.publicBaseUrl }),
|
|
35195
|
+
expiresAt: new Date(Date.now() + settings.ttlSeconds * 1e3).toISOString()
|
|
35245
35196
|
};
|
|
35246
|
-
let imageBuffer;
|
|
35247
|
-
let imageUrl;
|
|
35248
|
-
if (imageProvider === "openai") {
|
|
35249
|
-
const openaiImage = new OpenAIImageProvider();
|
|
35250
|
-
await openaiImage.initialize({ apiKey: imageApiKey });
|
|
35251
|
-
const imageResult = await openaiImage.generateImage(imagePrompt, {
|
|
35252
|
-
size: dalleImageSizes[options.aspectRatio || "16:9"] || "1536x1024",
|
|
35253
|
-
quality: "standard"
|
|
35254
|
-
});
|
|
35255
|
-
if (imageResult.success && imageResult.images?.[0]) {
|
|
35256
|
-
const img = imageResult.images[0];
|
|
35257
|
-
if (img.base64) imageBuffer = Buffer.from(img.base64, "base64");
|
|
35258
|
-
else if (img.url) imageUrl = img.url;
|
|
35259
|
-
}
|
|
35260
|
-
} else if (imageProvider === "gemini") {
|
|
35261
|
-
const gemini = new GeminiProvider();
|
|
35262
|
-
await gemini.initialize({ apiKey: imageApiKey });
|
|
35263
|
-
if (referenceImageBuffer) {
|
|
35264
|
-
const simplifiedVisuals = segment.visuals.split(/[,.]/).find(
|
|
35265
|
-
(part) => part.includes("standing") || part.includes("sitting") || part.includes("walking") || part.includes("lying") || part.includes("reaching") || part.includes("looking") || part.includes("working") || part.includes("coding") || part.includes("typing")
|
|
35266
|
-
) || segment.visuals.split(".")[0];
|
|
35267
|
-
const editPrompt = `Generate a new image showing the SAME SINGLE person from the reference image in a new scene.
|
|
35268
|
-
|
|
35269
|
-
REFERENCE: Look at the person in the reference image - their face, hair, build, and overall appearance.
|
|
35270
|
-
|
|
35271
|
-
NEW SCENE: ${simplifiedVisuals}
|
|
35272
|
-
|
|
35273
|
-
CRITICAL RULES:
|
|
35274
|
-
1. Show ONLY ONE person - the exact same individual from the reference image
|
|
35275
|
-
2. The person must have the IDENTICAL face, hair style, and body type
|
|
35276
|
-
3. Do NOT show multiple people or duplicate the character
|
|
35277
|
-
4. Create a single moment in time, one pose, one action
|
|
35278
|
-
5. Match the art style and quality of the reference image
|
|
35279
|
-
|
|
35280
|
-
Generate the single-person scene image now.`;
|
|
35281
|
-
const imageResult = await gemini.editImage([referenceImageBuffer], editPrompt, {
|
|
35282
|
-
aspectRatio: options.aspectRatio || "16:9"
|
|
35283
|
-
});
|
|
35284
|
-
if (imageResult.success && imageResult.images?.[0]?.base64) {
|
|
35285
|
-
imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
|
|
35286
|
-
}
|
|
35287
|
-
} else {
|
|
35288
|
-
const imageResult = await gemini.generateImage(imagePrompt, {
|
|
35289
|
-
aspectRatio: options.aspectRatio || "16:9"
|
|
35290
|
-
});
|
|
35291
|
-
if (imageResult.success && imageResult.images?.[0]?.base64) {
|
|
35292
|
-
imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
|
|
35293
|
-
}
|
|
35294
|
-
}
|
|
35295
|
-
} else if (imageProvider === "grok") {
|
|
35296
|
-
const grok = new GrokProvider();
|
|
35297
|
-
await grok.initialize({ apiKey: imageApiKey });
|
|
35298
|
-
const imageResult = await grok.generateImage(imagePrompt, {
|
|
35299
|
-
aspectRatio: options.aspectRatio || "16:9"
|
|
35300
|
-
});
|
|
35301
|
-
if (imageResult.success && imageResult.images?.[0]) {
|
|
35302
|
-
const img = imageResult.images[0];
|
|
35303
|
-
if (img.base64) imageBuffer = Buffer.from(img.base64, "base64");
|
|
35304
|
-
else if (img.url) imageUrl = img.url;
|
|
35305
|
-
}
|
|
35306
|
-
}
|
|
35307
|
-
if (imageBuffer) {
|
|
35308
|
-
await writeFile23(imagePath, imageBuffer);
|
|
35309
|
-
} else if (imageUrl) {
|
|
35310
|
-
const response = await fetch(imageUrl);
|
|
35311
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
35312
|
-
await writeFile23(imagePath, buffer);
|
|
35313
|
-
} else {
|
|
35314
|
-
sceneFailed = true;
|
|
35315
|
-
}
|
|
35316
|
-
}
|
|
35317
|
-
if (sceneFailed) {
|
|
35318
|
-
result.failedScenes.push(sceneNum);
|
|
35319
|
-
continue;
|
|
35320
35197
|
}
|
|
35321
|
-
|
|
35322
|
-
|
|
35323
|
-
|
|
35324
|
-
|
|
35325
|
-
|
|
35326
|
-
|
|
35327
|
-
|
|
35328
|
-
|
|
35329
|
-
const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
|
|
35330
|
-
if (options.generator === "grok") {
|
|
35331
|
-
const grok = new GrokProvider();
|
|
35332
|
-
await grok.initialize({ apiKey: videoApiKey });
|
|
35333
|
-
const ext = extname3(imagePath).toLowerCase().slice(1);
|
|
35334
|
-
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
35335
|
-
const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
35336
|
-
const grokDuration = Math.min(15, Math.max(1, segment.duration));
|
|
35337
|
-
const taskResult = await generateVideoWithRetryGrok(
|
|
35338
|
-
grok,
|
|
35339
|
-
segment,
|
|
35340
|
-
{
|
|
35341
|
-
duration: grokDuration,
|
|
35342
|
-
aspectRatio: options.aspectRatio || "16:9",
|
|
35343
|
-
referenceImage
|
|
35344
|
-
},
|
|
35345
|
-
maxRetries
|
|
35346
|
-
);
|
|
35347
|
-
if (taskResult) {
|
|
35348
|
-
try {
|
|
35349
|
-
const waitResult = await grok.waitForCompletion(taskResult.requestId, void 0, 3e5);
|
|
35350
|
-
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
35351
|
-
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
35352
|
-
await writeFile23(videoPath, buffer);
|
|
35353
|
-
const targetDuration = segment.duration;
|
|
35354
|
-
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
35355
|
-
if (actualVideoDuration < targetDuration - 0.1) {
|
|
35356
|
-
const extendedPath = resolve25(outputDir, `scene-${sceneNum}-extended.mp4`);
|
|
35357
|
-
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
35358
|
-
await unlink2(videoPath);
|
|
35359
|
-
await rename2(extendedPath, videoPath);
|
|
35360
|
-
}
|
|
35361
|
-
result.regeneratedScenes.push(sceneNum);
|
|
35362
|
-
} else {
|
|
35363
|
-
logSceneFailure("Grok", `scene ${sceneNum}`, waitResult);
|
|
35364
|
-
result.failedScenes.push(sceneNum);
|
|
35365
|
-
}
|
|
35366
|
-
} catch (err) {
|
|
35367
|
-
logSceneFailure("Grok", `scene ${sceneNum}`, err);
|
|
35368
|
-
result.failedScenes.push(sceneNum);
|
|
35369
|
-
}
|
|
35370
|
-
} else {
|
|
35371
|
-
result.failedScenes.push(sceneNum);
|
|
35372
|
-
}
|
|
35373
|
-
} else if (options.generator === "veo") {
|
|
35374
|
-
const veo = new GeminiProvider();
|
|
35375
|
-
await veo.initialize({ apiKey: videoApiKey });
|
|
35376
|
-
const ext = extname3(imagePath).toLowerCase().slice(1);
|
|
35377
|
-
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
35378
|
-
const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
35379
|
-
const veoDuration = segment.duration > 6 ? 8 : segment.duration > 4 ? 6 : 4;
|
|
35380
|
-
const taskResult = await generateVideoWithRetryVeo(
|
|
35381
|
-
veo,
|
|
35382
|
-
segment,
|
|
35383
|
-
{
|
|
35384
|
-
duration: veoDuration,
|
|
35385
|
-
aspectRatio: options.aspectRatio || "16:9",
|
|
35386
|
-
referenceImage
|
|
35387
|
-
},
|
|
35388
|
-
maxRetries
|
|
35389
|
-
);
|
|
35390
|
-
if (taskResult) {
|
|
35391
|
-
try {
|
|
35392
|
-
const waitResult = await veo.waitForVideoCompletion(taskResult.operationName, void 0, 3e5);
|
|
35393
|
-
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
35394
|
-
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
35395
|
-
await writeFile23(videoPath, buffer);
|
|
35396
|
-
const targetDuration = segment.duration;
|
|
35397
|
-
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
35398
|
-
if (actualVideoDuration < targetDuration - 0.1) {
|
|
35399
|
-
const extendedPath = resolve25(outputDir, `scene-${sceneNum}-extended.mp4`);
|
|
35400
|
-
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
35401
|
-
await unlink2(videoPath);
|
|
35402
|
-
await rename2(extendedPath, videoPath);
|
|
35403
|
-
}
|
|
35404
|
-
result.regeneratedScenes.push(sceneNum);
|
|
35405
|
-
} else {
|
|
35406
|
-
logSceneFailure("Veo", `scene ${sceneNum}`, waitResult);
|
|
35407
|
-
result.failedScenes.push(sceneNum);
|
|
35408
|
-
}
|
|
35409
|
-
} catch (err) {
|
|
35410
|
-
logSceneFailure("Veo", `scene ${sceneNum}`, err);
|
|
35411
|
-
result.failedScenes.push(sceneNum);
|
|
35412
|
-
}
|
|
35413
|
-
} else {
|
|
35414
|
-
result.failedScenes.push(sceneNum);
|
|
35415
|
-
}
|
|
35416
|
-
} else if (options.generator === "kling" || !options.generator) {
|
|
35417
|
-
const kling = new KlingProvider();
|
|
35418
|
-
await kling.initialize({ apiKey: videoApiKey });
|
|
35419
|
-
if (!kling.isConfigured()) {
|
|
35420
|
-
result.failedScenes.push(sceneNum);
|
|
35421
|
-
continue;
|
|
35422
|
-
}
|
|
35423
|
-
const imgbbApiKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
|
|
35424
|
-
let imageUrl;
|
|
35425
|
-
if (imgbbApiKey) {
|
|
35426
|
-
const uploadResult = await uploadToImgbb(imageBuffer, imgbbApiKey);
|
|
35427
|
-
if (uploadResult.success && uploadResult.url) {
|
|
35428
|
-
imageUrl = uploadResult.url;
|
|
35429
|
-
}
|
|
35430
|
-
}
|
|
35431
|
-
const taskResult = await generateVideoWithRetryKling(
|
|
35432
|
-
kling,
|
|
35433
|
-
segment,
|
|
35434
|
-
{
|
|
35435
|
-
duration: videoDuration,
|
|
35436
|
-
aspectRatio: options.aspectRatio || "16:9",
|
|
35437
|
-
referenceImage: imageUrl
|
|
35438
|
-
},
|
|
35439
|
-
maxRetries
|
|
35440
|
-
);
|
|
35441
|
-
if (taskResult) {
|
|
35442
|
-
try {
|
|
35443
|
-
const waitResult = await kling.waitForCompletion(taskResult.taskId, taskResult.type, void 0, 6e5);
|
|
35444
|
-
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
35445
|
-
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
35446
|
-
await writeFile23(videoPath, buffer);
|
|
35447
|
-
await extendVideoToTarget(
|
|
35448
|
-
videoPath,
|
|
35449
|
-
segment.duration,
|
|
35450
|
-
outputDir,
|
|
35451
|
-
`Scene ${sceneNum}`,
|
|
35452
|
-
{
|
|
35453
|
-
kling,
|
|
35454
|
-
videoId: waitResult.videoId,
|
|
35455
|
-
onProgress: options.onProgress
|
|
35456
|
-
}
|
|
35457
|
-
);
|
|
35458
|
-
result.regeneratedScenes.push(sceneNum);
|
|
35459
|
-
} else {
|
|
35460
|
-
logSceneFailure("Kling", `scene ${sceneNum}`, waitResult);
|
|
35461
|
-
result.failedScenes.push(sceneNum);
|
|
35462
|
-
}
|
|
35463
|
-
} catch (err) {
|
|
35464
|
-
logSceneFailure("Kling", `scene ${sceneNum}`, err);
|
|
35465
|
-
result.failedScenes.push(sceneNum);
|
|
35466
|
-
}
|
|
35467
|
-
} else {
|
|
35468
|
-
result.failedScenes.push(sceneNum);
|
|
35469
|
-
}
|
|
35470
|
-
} else {
|
|
35471
|
-
const runway = new RunwayProvider();
|
|
35472
|
-
await runway.initialize({ apiKey: videoApiKey });
|
|
35473
|
-
const ext = extname3(imagePath).toLowerCase().slice(1);
|
|
35474
|
-
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
35475
|
-
const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
35476
|
-
const aspectRatio = options.aspectRatio === "1:1" ? "16:9" : options.aspectRatio || "16:9";
|
|
35477
|
-
const taskResult = await generateVideoWithRetryRunway(
|
|
35478
|
-
runway,
|
|
35479
|
-
segment,
|
|
35480
|
-
referenceImage,
|
|
35481
|
-
{ duration: videoDuration, aspectRatio },
|
|
35482
|
-
maxRetries
|
|
35483
|
-
);
|
|
35484
|
-
if (taskResult) {
|
|
35485
|
-
try {
|
|
35486
|
-
const waitResult = await runway.waitForCompletion(taskResult.taskId, void 0, 3e5);
|
|
35487
|
-
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
35488
|
-
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
35489
|
-
await writeFile23(videoPath, buffer);
|
|
35490
|
-
const targetDuration = segment.duration;
|
|
35491
|
-
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
35492
|
-
if (actualVideoDuration < targetDuration - 0.1) {
|
|
35493
|
-
const extendedPath = resolve25(outputDir, `scene-${sceneNum}-extended.mp4`);
|
|
35494
|
-
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
35495
|
-
await unlink2(videoPath);
|
|
35496
|
-
await rename2(extendedPath, videoPath);
|
|
35497
|
-
}
|
|
35498
|
-
result.regeneratedScenes.push(sceneNum);
|
|
35499
|
-
} else {
|
|
35500
|
-
logSceneFailure("Runway", `scene ${sceneNum}`, waitResult);
|
|
35501
|
-
result.failedScenes.push(sceneNum);
|
|
35502
|
-
}
|
|
35503
|
-
} catch (err) {
|
|
35504
|
-
logSceneFailure("Runway", `scene ${sceneNum}`, err);
|
|
35505
|
-
result.failedScenes.push(sceneNum);
|
|
35506
|
-
}
|
|
35507
|
-
} else {
|
|
35508
|
-
result.failedScenes.push(sceneNum);
|
|
35509
|
-
}
|
|
35510
|
-
}
|
|
35511
|
-
} else if (!sceneFailed) {
|
|
35512
|
-
result.regeneratedScenes.push(sceneNum);
|
|
35198
|
+
};
|
|
35199
|
+
}
|
|
35200
|
+
return {
|
|
35201
|
+
provider: "imgbb",
|
|
35202
|
+
async uploadImage(imageBuffer) {
|
|
35203
|
+
const imgbbKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
|
|
35204
|
+
if (!imgbbKey) {
|
|
35205
|
+
throw new Error("IMGBB_API_KEY required for image-to-video uploads.");
|
|
35513
35206
|
}
|
|
35514
|
-
|
|
35515
|
-
|
|
35516
|
-
|
|
35517
|
-
for (const segment of segments) {
|
|
35518
|
-
segment.startTime = currentTime;
|
|
35519
|
-
currentTime += segment.duration;
|
|
35207
|
+
const result = await uploadToImgbb(imageBuffer, imgbbKey);
|
|
35208
|
+
if (!result.success || !result.url) {
|
|
35209
|
+
throw new Error(`ImgBB upload failed: ${result.error ?? "unknown error"}`);
|
|
35520
35210
|
}
|
|
35521
|
-
|
|
35522
|
-
await writeFile23(storyboardPath, serialized, "utf-8");
|
|
35211
|
+
return { provider: "imgbb", url: result.url };
|
|
35523
35212
|
}
|
|
35524
|
-
|
|
35525
|
-
return result;
|
|
35526
|
-
} catch (error) {
|
|
35527
|
-
return {
|
|
35528
|
-
...result,
|
|
35529
|
-
error: error instanceof Error ? error.message : String(error)
|
|
35530
|
-
};
|
|
35531
|
-
}
|
|
35213
|
+
};
|
|
35532
35214
|
}
|
|
35533
|
-
var
|
|
35534
|
-
|
|
35535
|
-
"src/commands/ai-script-pipeline.ts"() {
|
|
35215
|
+
var init_upload_host = __esm({
|
|
35216
|
+
"src/utils/upload-host.ts"() {
|
|
35536
35217
|
"use strict";
|
|
35537
|
-
import_yaml5 = __toESM(require_dist(), 1);
|
|
35538
|
-
init_dist2();
|
|
35539
|
-
init_api_key();
|
|
35540
35218
|
init_config();
|
|
35541
|
-
init_audio();
|
|
35542
|
-
init_ai_helpers();
|
|
35543
35219
|
init_video_utils();
|
|
35544
|
-
init_video_providers();
|
|
35545
35220
|
}
|
|
35546
35221
|
});
|
|
35547
35222
|
|
|
35548
35223
|
// src/commands/generate/video.ts
|
|
35549
|
-
import { resolve as
|
|
35550
|
-
import { readFile as
|
|
35224
|
+
import { resolve as resolve25 } from "node:path";
|
|
35225
|
+
import { readFile as readFile13, writeFile as writeFile23 } from "node:fs/promises";
|
|
35551
35226
|
function registerVideoCommand(parent) {
|
|
35552
|
-
parent.command("video").alias("vid").description("Generate video using AI (Seedance, Grok, Kling, Runway, or Veo)").argument("[prompt]", "Text prompt describing the video (interactive if omitted)").option(
|
|
35227
|
+
parent.command("video").alias("vid").description("Generate video using AI (Seedance, Grok, Kling, Runway, or Veo)").argument("[prompt]", "Text prompt describing the video (interactive if omitted)").option(
|
|
35228
|
+
"-p, --provider <provider>",
|
|
35229
|
+
"Provider: seedance (ByteDance Seedance 2.0 via fal.ai), grok, kling, runway, veo. `fal` is a deprecated v0.x alias for seedance and will be removed in 1.0."
|
|
35230
|
+
).option(
|
|
35231
|
+
"-k, --api-key <key>",
|
|
35232
|
+
"API key (or set FAL_KEY / XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)"
|
|
35233
|
+
).option("-o, --output <path>", "Output file path (downloads video)").option("-i, --image <path>", "Reference image for image-to-video").option(
|
|
35553
35234
|
"-d, --duration <sec>",
|
|
35554
35235
|
"Duration in seconds. Seedance accepts 4-15; Kling accepts 5 or 10; Veo maps to 6 or 8.",
|
|
35555
35236
|
"5"
|
|
35556
|
-
).option(
|
|
35237
|
+
).option(
|
|
35238
|
+
"-r, --ratio <ratio>",
|
|
35239
|
+
"Aspect ratio: 16:9, 9:16, or 1:1 (auto-detected from image if omitted)"
|
|
35240
|
+
).option("--seed <number>", "Random seed for reproducibility (Runway only)").option("--mode <mode>", "Generation mode: std or pro (Kling only)", "std").option(
|
|
35241
|
+
"--seedance-model <model>",
|
|
35242
|
+
"Seedance variant: quality or fast (fal.ai only)",
|
|
35243
|
+
"quality"
|
|
35244
|
+
).option("--negative <prompt>", "Negative prompt - what to avoid (Kling/Veo)").option("--resolution <res>", "Video resolution: 720p, 1080p, 4k (Veo only)").option("--last-frame <path>", "Last frame image for frame interpolation (Veo only)").option(
|
|
35245
|
+
"--ref-images <paths...>",
|
|
35246
|
+
"Reference images for character consistency (Veo 3.1 only, max 3)"
|
|
35247
|
+
).option("--person <mode>", "Person generation: allow_all, allow_adult (Veo only)").option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast (default: 3.1-fast)", "3.1-fast").option(
|
|
35248
|
+
"--runway-model <model>",
|
|
35249
|
+
"Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)",
|
|
35250
|
+
"gen4.5"
|
|
35251
|
+
).option("--no-wait", "Start generation and return task ID without waiting").option("--dry-run", "Preview parameters without executing").addHelpText(
|
|
35252
|
+
"after",
|
|
35253
|
+
`
|
|
35557
35254
|
Examples:
|
|
35558
35255
|
$ vibe generate video "dancing cat" -o cat.mp4 # Seedance when FAL_KEY is set
|
|
35559
35256
|
$ vibe gen vid "cinematic city timelapse" -o city.mp4 -p seedance # Seedance via fal.ai
|
|
35560
35257
|
$ vibe gen vid "city timelapse" -o city.mp4 -p kling # Kling
|
|
35561
35258
|
$ vibe gen vid "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
|
|
35562
35259
|
$ vibe gen vid "ocean waves" -o waves.mp4 -p veo --resolution 1080p # Veo
|
|
35563
|
-
$ vibe gen vid "sunset" -o sun.mp4 -d 10 --dry-run --json`
|
|
35260
|
+
$ vibe gen vid "sunset" -o sun.mp4 -d 10 --dry-run --json`
|
|
35261
|
+
).action(async (prompt3, options) => {
|
|
35564
35262
|
const startedAt = Date.now();
|
|
35565
35263
|
try {
|
|
35566
35264
|
if (!prompt3) {
|
|
@@ -35571,10 +35269,7 @@ Examples:
|
|
|
35571
35269
|
}
|
|
35572
35270
|
} else {
|
|
35573
35271
|
exitWithError(
|
|
35574
|
-
usageError(
|
|
35575
|
-
"Prompt argument is required.",
|
|
35576
|
-
"Usage: vibe generate video <prompt>"
|
|
35577
|
-
)
|
|
35272
|
+
usageError("Prompt argument is required.", "Usage: vibe generate video <prompt>")
|
|
35578
35273
|
);
|
|
35579
35274
|
}
|
|
35580
35275
|
}
|
|
@@ -35582,13 +35277,16 @@ Examples:
|
|
|
35582
35277
|
if (options.output) {
|
|
35583
35278
|
validateOutputPath(options.output);
|
|
35584
35279
|
}
|
|
35280
|
+
await loadProviderDefaults();
|
|
35585
35281
|
if (options.duration !== void 0) {
|
|
35586
35282
|
const d = parseFloat(options.duration);
|
|
35587
35283
|
if (!Number.isFinite(d) || d <= 0 || d > 60) {
|
|
35588
|
-
exitWithError(
|
|
35589
|
-
|
|
35590
|
-
|
|
35591
|
-
|
|
35284
|
+
exitWithError(
|
|
35285
|
+
usageError(
|
|
35286
|
+
`Invalid --duration: ${options.duration}`,
|
|
35287
|
+
"Must be a positive number \u2264 60 seconds."
|
|
35288
|
+
)
|
|
35289
|
+
);
|
|
35592
35290
|
}
|
|
35593
35291
|
}
|
|
35594
35292
|
const validProviders = ["runway", "kling", "veo", "grok", "seedance", "fal"];
|
|
@@ -35630,10 +35328,12 @@ Examples:
|
|
|
35630
35328
|
provider = resolved?.name ?? "grok";
|
|
35631
35329
|
}
|
|
35632
35330
|
let referenceImage;
|
|
35331
|
+
let referenceImageBuffer;
|
|
35332
|
+
let referenceImageMimeType;
|
|
35633
35333
|
let isImageToVideo = false;
|
|
35634
35334
|
if (options.image) {
|
|
35635
|
-
const imagePath =
|
|
35636
|
-
const imageBuffer = await
|
|
35335
|
+
const imagePath = resolve25(process.cwd(), options.image);
|
|
35336
|
+
const imageBuffer = await readFile13(imagePath);
|
|
35637
35337
|
const ext = options.image.toLowerCase().split(".").pop();
|
|
35638
35338
|
const mimeTypes2 = {
|
|
35639
35339
|
jpg: "image/jpeg",
|
|
@@ -35643,6 +35343,8 @@ Examples:
|
|
|
35643
35343
|
webp: "image/webp"
|
|
35644
35344
|
};
|
|
35645
35345
|
const mimeType = mimeTypes2[ext || "png"] || "image/png";
|
|
35346
|
+
referenceImageBuffer = imageBuffer;
|
|
35347
|
+
referenceImageMimeType = mimeType;
|
|
35646
35348
|
referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
35647
35349
|
isImageToVideo = true;
|
|
35648
35350
|
if (!options.ratio) {
|
|
@@ -35767,20 +35469,22 @@ Examples:
|
|
|
35767
35469
|
}
|
|
35768
35470
|
let klingImage = referenceImage;
|
|
35769
35471
|
if (klingImage && klingImage.startsWith("data:")) {
|
|
35770
|
-
|
|
35771
|
-
|
|
35772
|
-
|
|
35773
|
-
|
|
35774
|
-
|
|
35775
|
-
|
|
35776
|
-
|
|
35777
|
-
|
|
35778
|
-
|
|
35779
|
-
|
|
35780
|
-
|
|
35781
|
-
|
|
35782
|
-
|
|
35783
|
-
|
|
35472
|
+
try {
|
|
35473
|
+
const uploadHost = await resolveUploadHost();
|
|
35474
|
+
spinner2.text = `Uploading image via ${uploadHost.provider} for Kling...`;
|
|
35475
|
+
const upload = await uploadHost.uploadImage(referenceImageBuffer, {
|
|
35476
|
+
filename: options.image,
|
|
35477
|
+
mimeType: referenceImageMimeType
|
|
35478
|
+
});
|
|
35479
|
+
klingImage = upload.url;
|
|
35480
|
+
} catch (err) {
|
|
35481
|
+
spinner2.fail("Image upload failed");
|
|
35482
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
35483
|
+
if (message.includes("IMGBB_API_KEY")) {
|
|
35484
|
+
exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
|
|
35485
|
+
}
|
|
35486
|
+
exitWithError(apiError(message, true));
|
|
35487
|
+
}
|
|
35784
35488
|
spinner2.text = "Starting video generation...";
|
|
35785
35489
|
}
|
|
35786
35490
|
result = await kling.generateVideo(prompt3, {
|
|
@@ -35835,8 +35539,8 @@ Examples:
|
|
|
35835
35539
|
const veoDuration = parseInt(options.duration) <= 6 ? 6 : 8;
|
|
35836
35540
|
let lastFrame;
|
|
35837
35541
|
if (options.lastFrame) {
|
|
35838
|
-
const lastFramePath =
|
|
35839
|
-
const lastFrameBuffer = await
|
|
35542
|
+
const lastFramePath = resolve25(process.cwd(), options.lastFrame);
|
|
35543
|
+
const lastFrameBuffer = await readFile13(lastFramePath);
|
|
35840
35544
|
const ext = options.lastFrame.toLowerCase().split(".").pop();
|
|
35841
35545
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
|
|
35842
35546
|
lastFrame = `data:${mimeType};base64,${lastFrameBuffer.toString("base64")}`;
|
|
@@ -35845,8 +35549,8 @@ Examples:
|
|
|
35845
35549
|
if (options.refImages && options.refImages.length > 0) {
|
|
35846
35550
|
refImages = [];
|
|
35847
35551
|
for (const refPath of options.refImages.slice(0, 3)) {
|
|
35848
|
-
const absRefPath =
|
|
35849
|
-
const refBuffer = await
|
|
35552
|
+
const absRefPath = resolve25(process.cwd(), refPath);
|
|
35553
|
+
const refBuffer = await readFile13(absRefPath);
|
|
35850
35554
|
const ext = refPath.toLowerCase().split(".").pop();
|
|
35851
35555
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
|
|
35852
35556
|
refImages.push({ base64: refBuffer.toString("base64"), mimeType });
|
|
@@ -35927,20 +35631,22 @@ Examples:
|
|
|
35927
35631
|
await fal.initialize({ apiKey });
|
|
35928
35632
|
let falImage = referenceImage;
|
|
35929
35633
|
if (falImage && falImage.startsWith("data:")) {
|
|
35930
|
-
|
|
35931
|
-
|
|
35932
|
-
|
|
35933
|
-
|
|
35934
|
-
|
|
35935
|
-
|
|
35936
|
-
|
|
35937
|
-
|
|
35938
|
-
|
|
35939
|
-
|
|
35940
|
-
|
|
35941
|
-
|
|
35634
|
+
try {
|
|
35635
|
+
const uploadHost = await resolveUploadHost();
|
|
35636
|
+
spinner2.text = `Uploading image via ${uploadHost.provider} for Seedance...`;
|
|
35637
|
+
const upload = await uploadHost.uploadImage(referenceImageBuffer, {
|
|
35638
|
+
filename: options.image,
|
|
35639
|
+
mimeType: referenceImageMimeType
|
|
35640
|
+
});
|
|
35641
|
+
falImage = upload.url;
|
|
35642
|
+
} catch (err) {
|
|
35643
|
+
spinner2.fail("Image upload failed");
|
|
35644
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
35645
|
+
if (message.includes("IMGBB_API_KEY")) {
|
|
35646
|
+
exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
|
|
35647
|
+
}
|
|
35648
|
+
exitWithError(apiError(message, true));
|
|
35942
35649
|
}
|
|
35943
|
-
falImage = uploadResult.url;
|
|
35944
35650
|
}
|
|
35945
35651
|
spinner2.text = "Generating video with fal.ai Seedance 2.0 (this may take 1-3 minutes)...";
|
|
35946
35652
|
const seedanceModel = String(options.seedanceModel ?? "quality").toLowerCase();
|
|
@@ -35964,8 +35670,8 @@ Examples:
|
|
|
35964
35670
|
let outputPath;
|
|
35965
35671
|
if (options.output && finalResult.videoUrl) {
|
|
35966
35672
|
const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
|
|
35967
|
-
outputPath =
|
|
35968
|
-
await
|
|
35673
|
+
outputPath = resolve25(process.cwd(), options.output);
|
|
35674
|
+
await writeFile23(outputPath, buffer);
|
|
35969
35675
|
}
|
|
35970
35676
|
outputSuccess({
|
|
35971
35677
|
command: "generate video",
|
|
@@ -35992,14 +35698,12 @@ Examples:
|
|
|
35992
35698
|
const downloadSpinner = ora("Downloading video...").start();
|
|
35993
35699
|
try {
|
|
35994
35700
|
const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
|
|
35995
|
-
const outputPath =
|
|
35996
|
-
await
|
|
35701
|
+
const outputPath = resolve25(process.cwd(), options.output);
|
|
35702
|
+
await writeFile23(outputPath, buffer);
|
|
35997
35703
|
downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
|
|
35998
35704
|
} catch (err) {
|
|
35999
35705
|
downloadSpinner.fail(
|
|
36000
|
-
source_default.red(
|
|
36001
|
-
`Failed to download video: ${err instanceof Error ? err.message : err}`
|
|
36002
|
-
)
|
|
35706
|
+
source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
|
|
36003
35707
|
);
|
|
36004
35708
|
}
|
|
36005
35709
|
}
|
|
@@ -36017,11 +35721,10 @@ var init_video = __esm({
|
|
|
36017
35721
|
init_dist2();
|
|
36018
35722
|
init_api_key();
|
|
36019
35723
|
init_tty();
|
|
36020
|
-
init_config();
|
|
36021
35724
|
init_output();
|
|
36022
35725
|
init_validate();
|
|
36023
35726
|
init_provider_resolver();
|
|
36024
|
-
|
|
35727
|
+
init_upload_host();
|
|
36025
35728
|
init_ai_helpers();
|
|
36026
35729
|
}
|
|
36027
35730
|
});
|
|
@@ -36126,8 +35829,8 @@ Run 'vibe schema generate.<command>' for structured parameter info.
|
|
|
36126
35829
|
});
|
|
36127
35830
|
|
|
36128
35831
|
// src/commands/_shared/edit/silence-cut.ts
|
|
36129
|
-
import { existsSync as
|
|
36130
|
-
import { readFile as
|
|
35832
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
35833
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
36131
35834
|
async function detectSilencePeriods(videoPath, noiseThreshold, minDuration) {
|
|
36132
35835
|
const totalDuration = await getVideoDuration(videoPath);
|
|
36133
35836
|
const { stdout, stderr } = await execSafe(
|
|
@@ -36177,7 +35880,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
|
|
|
36177
35880
|
}
|
|
36178
35881
|
const gemini = new GeminiProvider();
|
|
36179
35882
|
await gemini.initialize({ apiKey: geminiApiKey });
|
|
36180
|
-
const videoBuffer = await
|
|
35883
|
+
const videoBuffer = await readFile14(videoPath);
|
|
36181
35884
|
const modelMap = {
|
|
36182
35885
|
flash: "gemini-3-flash-preview",
|
|
36183
35886
|
"flash-2.5": "gemini-2.5-flash",
|
|
@@ -36258,7 +35961,7 @@ async function executeSilenceCut(options) {
|
|
|
36258
35961
|
analyzeOnly = false,
|
|
36259
35962
|
useGemini = false
|
|
36260
35963
|
} = options;
|
|
36261
|
-
if (!
|
|
35964
|
+
if (!existsSync14(videoPath)) {
|
|
36262
35965
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
36263
35966
|
}
|
|
36264
35967
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36366,11 +36069,11 @@ var init_silence_cut = __esm({
|
|
|
36366
36069
|
});
|
|
36367
36070
|
|
|
36368
36071
|
// src/commands/_shared/edit/jump-cut.ts
|
|
36369
|
-
import { existsSync as
|
|
36370
|
-
import { readFile as
|
|
36072
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
36073
|
+
import { readFile as readFile15, writeFile as writeFile24, mkdir as mkdir12 } from "node:fs/promises";
|
|
36371
36074
|
import { join as join8 } from "node:path";
|
|
36372
36075
|
async function transcribeWithWords(audioPath, apiKey, language) {
|
|
36373
|
-
const audioBuffer = await
|
|
36076
|
+
const audioBuffer = await readFile15(audioPath);
|
|
36374
36077
|
const audioBlob = new Blob([audioBuffer]);
|
|
36375
36078
|
const formData = new FormData();
|
|
36376
36079
|
formData.append("file", audioBlob, "audio.wav");
|
|
@@ -36429,7 +36132,7 @@ async function executeJumpCut(options) {
|
|
|
36429
36132
|
analyzeOnly = false,
|
|
36430
36133
|
apiKey
|
|
36431
36134
|
} = options;
|
|
36432
|
-
if (!
|
|
36135
|
+
if (!existsSync15(videoPath)) {
|
|
36433
36136
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
36434
36137
|
}
|
|
36435
36138
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36526,7 +36229,7 @@ async function executeJumpCut(options) {
|
|
|
36526
36229
|
}
|
|
36527
36230
|
const concatList = segmentPaths.map((p) => `file '${p}'`).join("\n");
|
|
36528
36231
|
const listPath = join8(tmpDir, "concat.txt");
|
|
36529
|
-
await
|
|
36232
|
+
await writeFile24(listPath, concatList);
|
|
36530
36233
|
await execSafe(
|
|
36531
36234
|
"ffmpeg",
|
|
36532
36235
|
[
|
|
@@ -36715,8 +36418,8 @@ var init_helpers = __esm({
|
|
|
36715
36418
|
});
|
|
36716
36419
|
|
|
36717
36420
|
// src/commands/_shared/edit/caption.ts
|
|
36718
|
-
import { existsSync as
|
|
36719
|
-
import { readFile as
|
|
36421
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
36422
|
+
import { readFile as readFile16, writeFile as writeFile25, mkdir as mkdir13 } from "node:fs/promises";
|
|
36720
36423
|
import { dirname as dirname8, basename as basename6, extname as extname4, join as join9 } from "node:path";
|
|
36721
36424
|
function getCaptionForceStyle(style, fontSize, fontColor, position) {
|
|
36722
36425
|
const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
|
|
@@ -36745,7 +36448,7 @@ async function executeCaption(options) {
|
|
|
36745
36448
|
position = "bottom",
|
|
36746
36449
|
apiKey
|
|
36747
36450
|
} = options;
|
|
36748
|
-
if (!
|
|
36451
|
+
if (!existsSync16(videoPath)) {
|
|
36749
36452
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
36750
36453
|
}
|
|
36751
36454
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36786,7 +36489,7 @@ async function executeCaption(options) {
|
|
|
36786
36489
|
);
|
|
36787
36490
|
const whisper = new WhisperProvider();
|
|
36788
36491
|
await whisper.initialize({ apiKey: openaiKey });
|
|
36789
|
-
const audioBuffer = await
|
|
36492
|
+
const audioBuffer = await readFile16(audioPath);
|
|
36790
36493
|
const audioBlob = new Blob([audioBuffer]);
|
|
36791
36494
|
const transcriptResult = await whisper.transcribe(audioBlob, language);
|
|
36792
36495
|
if (transcriptResult.status === "failed" || !transcriptResult.segments || transcriptResult.segments.length === 0) {
|
|
@@ -36796,7 +36499,7 @@ async function executeCaption(options) {
|
|
|
36796
36499
|
};
|
|
36797
36500
|
}
|
|
36798
36501
|
const srtContent = formatSRT(transcriptResult.segments);
|
|
36799
|
-
await
|
|
36502
|
+
await writeFile25(srtPath, srtContent);
|
|
36800
36503
|
const { width, height } = await getVideoResolution(videoPath);
|
|
36801
36504
|
const fontSize = customFontSize || Math.round(height / 18);
|
|
36802
36505
|
let hasSubtitles = false;
|
|
@@ -36838,7 +36541,7 @@ async function executeCaption(options) {
|
|
|
36838
36541
|
outputDir2,
|
|
36839
36542
|
basename6(outputPath, extname4(outputPath)) + ".srt"
|
|
36840
36543
|
);
|
|
36841
|
-
await
|
|
36544
|
+
await writeFile25(outputSrtPath2, srtContent);
|
|
36842
36545
|
return { success: false, error: `${remotionErr}
|
|
36843
36546
|
SRT saved to: ${outputSrtPath2}` };
|
|
36844
36547
|
}
|
|
@@ -36877,7 +36580,7 @@ SRT saved to: ${outputSrtPath2}` };
|
|
|
36877
36580
|
outputDir2,
|
|
36878
36581
|
basename6(outputPath, extname4(outputPath)) + ".srt"
|
|
36879
36582
|
);
|
|
36880
|
-
await
|
|
36583
|
+
await writeFile25(outputSrtPath2, srtContent);
|
|
36881
36584
|
return {
|
|
36882
36585
|
success: false,
|
|
36883
36586
|
error: `${renderResult.error}
|
|
@@ -36890,7 +36593,7 @@ SRT saved to: ${outputSrtPath2}`
|
|
|
36890
36593
|
outputDir,
|
|
36891
36594
|
basename6(outputPath, extname4(outputPath)) + ".srt"
|
|
36892
36595
|
);
|
|
36893
|
-
await
|
|
36596
|
+
await writeFile25(outputSrtPath, srtContent);
|
|
36894
36597
|
return {
|
|
36895
36598
|
success: true,
|
|
36896
36599
|
outputPath,
|
|
@@ -36923,10 +36626,10 @@ var init_caption = __esm({
|
|
|
36923
36626
|
});
|
|
36924
36627
|
|
|
36925
36628
|
// src/commands/_shared/edit/noise-reduce.ts
|
|
36926
|
-
import { existsSync as
|
|
36629
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
36927
36630
|
async function executeNoiseReduce(options) {
|
|
36928
36631
|
const { inputPath, outputPath, strength = "medium", noiseFloor } = options;
|
|
36929
|
-
if (!
|
|
36632
|
+
if (!existsSync17(inputPath)) {
|
|
36930
36633
|
return { success: false, error: `File not found: ${inputPath}` };
|
|
36931
36634
|
}
|
|
36932
36635
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36983,7 +36686,7 @@ var init_noise_reduce = __esm({
|
|
|
36983
36686
|
});
|
|
36984
36687
|
|
|
36985
36688
|
// src/commands/_shared/edit/fade.ts
|
|
36986
|
-
import { existsSync as
|
|
36689
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
36987
36690
|
async function executeFade(options) {
|
|
36988
36691
|
const {
|
|
36989
36692
|
videoPath,
|
|
@@ -36993,7 +36696,7 @@ async function executeFade(options) {
|
|
|
36993
36696
|
audioOnly = false,
|
|
36994
36697
|
videoOnly = false
|
|
36995
36698
|
} = options;
|
|
36996
|
-
if (!
|
|
36699
|
+
if (!existsSync18(videoPath)) {
|
|
36997
36700
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
36998
36701
|
}
|
|
36999
36702
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37060,8 +36763,8 @@ var init_fade = __esm({
|
|
|
37060
36763
|
});
|
|
37061
36764
|
|
|
37062
36765
|
// src/commands/_shared/edit/translate-srt.ts
|
|
37063
|
-
import { existsSync as
|
|
37064
|
-
import { readFile as
|
|
36766
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
36767
|
+
import { readFile as readFile17, writeFile as writeFile26 } from "node:fs/promises";
|
|
37065
36768
|
async function executeTranslateSrt(options) {
|
|
37066
36769
|
const {
|
|
37067
36770
|
srtPath,
|
|
@@ -37071,11 +36774,11 @@ async function executeTranslateSrt(options) {
|
|
|
37071
36774
|
sourceLanguage,
|
|
37072
36775
|
apiKey
|
|
37073
36776
|
} = options;
|
|
37074
|
-
if (!
|
|
36777
|
+
if (!existsSync19(srtPath)) {
|
|
37075
36778
|
return { success: false, error: `SRT file not found: ${srtPath}` };
|
|
37076
36779
|
}
|
|
37077
36780
|
try {
|
|
37078
|
-
const srtContent = await
|
|
36781
|
+
const srtContent = await readFile17(srtPath, "utf-8");
|
|
37079
36782
|
const segments = parseSRT(srtContent);
|
|
37080
36783
|
if (segments.length === 0) {
|
|
37081
36784
|
return { success: false, error: "No subtitle segments found in SRT file" };
|
|
@@ -37165,7 +36868,7 @@ ${textsToTranslate}`;
|
|
|
37165
36868
|
}
|
|
37166
36869
|
}
|
|
37167
36870
|
const translatedSrt = formatSRT(translatedSegments);
|
|
37168
|
-
await
|
|
36871
|
+
await writeFile26(outputPath, translatedSrt);
|
|
37169
36872
|
return {
|
|
37170
36873
|
success: true,
|
|
37171
36874
|
outputPath,
|
|
@@ -37188,8 +36891,8 @@ var init_translate_srt = __esm({
|
|
|
37188
36891
|
});
|
|
37189
36892
|
|
|
37190
36893
|
// src/commands/_shared/edit/text-overlay.ts
|
|
37191
|
-
import { existsSync as
|
|
37192
|
-
import { resolve as
|
|
36894
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
36895
|
+
import { resolve as resolve26 } from "node:path";
|
|
37193
36896
|
function detectSystemFont() {
|
|
37194
36897
|
const platform3 = process.platform;
|
|
37195
36898
|
if (platform3 === "darwin") {
|
|
@@ -37199,7 +36902,7 @@ function detectSystemFont() {
|
|
|
37199
36902
|
"/Library/Fonts/Arial.ttf"
|
|
37200
36903
|
];
|
|
37201
36904
|
for (const f of candidates) {
|
|
37202
|
-
if (
|
|
36905
|
+
if (existsSync20(f)) return f;
|
|
37203
36906
|
}
|
|
37204
36907
|
} else if (platform3 === "linux") {
|
|
37205
36908
|
const candidates = [
|
|
@@ -37208,7 +36911,7 @@ function detectSystemFont() {
|
|
|
37208
36911
|
"/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
|
|
37209
36912
|
];
|
|
37210
36913
|
for (const f of candidates) {
|
|
37211
|
-
if (
|
|
36914
|
+
if (existsSync20(f)) return f;
|
|
37212
36915
|
}
|
|
37213
36916
|
} else if (platform3 === "win32") {
|
|
37214
36917
|
const candidates = [
|
|
@@ -37216,7 +36919,7 @@ function detectSystemFont() {
|
|
|
37216
36919
|
"C:\\Windows\\Fonts\\segoeui.ttf"
|
|
37217
36920
|
];
|
|
37218
36921
|
for (const f of candidates) {
|
|
37219
|
-
if (
|
|
36922
|
+
if (existsSync20(f)) return f;
|
|
37220
36923
|
}
|
|
37221
36924
|
}
|
|
37222
36925
|
return null;
|
|
@@ -37235,9 +36938,9 @@ async function applyTextOverlays(options) {
|
|
|
37235
36938
|
if (!texts || texts.length === 0) {
|
|
37236
36939
|
return { success: false, error: "No texts provided" };
|
|
37237
36940
|
}
|
|
37238
|
-
const absVideoPath =
|
|
37239
|
-
const absOutputPath =
|
|
37240
|
-
if (!
|
|
36941
|
+
const absVideoPath = resolve26(process.cwd(), videoPath);
|
|
36942
|
+
const absOutputPath = resolve26(process.cwd(), outputPath);
|
|
36943
|
+
if (!existsSync20(absVideoPath)) {
|
|
37241
36944
|
return { success: false, error: `Video not found: ${absVideoPath}` };
|
|
37242
36945
|
}
|
|
37243
36946
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37417,8 +37120,8 @@ var init_ai_edit = __esm({
|
|
|
37417
37120
|
});
|
|
37418
37121
|
|
|
37419
37122
|
// src/commands/ai-edit-cli.ts
|
|
37420
|
-
import { resolve as
|
|
37421
|
-
import { existsSync as
|
|
37123
|
+
import { resolve as resolve27, extname as extname5, basename as basename7 } from "node:path";
|
|
37124
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
37422
37125
|
function registerEditCommands(aiCommand) {
|
|
37423
37126
|
aiCommand.command("silence-cut").alias("sc").description("Remove silent segments from video (FFmpeg default, or Gemini for smart detection)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-cut.<ext>)").option("--noise <dB>", "Silence threshold in dB (default: -30)", "-30").option("-d, --min-duration <seconds>", "Minimum silence duration to cut (default: 0.5)", "0.5").option("--padding <seconds>", "Padding around non-silent segments (default: 0.1)", "0.1").option("--analyze-only", "(deprecated \u2014 use `vibe detect silence`) Only detect silence, don't cut").option("--use-gemini", "Use Gemini Video Understanding for context-aware silence detection").option("-m, --model <model>", "Gemini model (default: flash)").option("--low-res", "Low resolution mode for longer videos (Gemini only)").option("-k, --api-key <key>", "Google API key override (or set GOOGLE_API_KEY env)").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
|
|
37424
37127
|
Examples:
|
|
@@ -37434,8 +37137,8 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
|
|
|
37434
37137
|
if (options.output) {
|
|
37435
37138
|
validateOutputPath(options.output);
|
|
37436
37139
|
}
|
|
37437
|
-
const absVideoPath =
|
|
37438
|
-
if (!
|
|
37140
|
+
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
37141
|
+
if (!existsSync21(absVideoPath)) {
|
|
37439
37142
|
exitWithError(notFoundError(absVideoPath));
|
|
37440
37143
|
}
|
|
37441
37144
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37474,7 +37177,7 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
|
|
|
37474
37177
|
const spinner2 = ora(spinnerText).start();
|
|
37475
37178
|
const result = await executeSilenceCut({
|
|
37476
37179
|
videoPath: absVideoPath,
|
|
37477
|
-
outputPath:
|
|
37180
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37478
37181
|
noiseThreshold: parseFloat(options.noise),
|
|
37479
37182
|
minDuration: parseFloat(options.minDuration),
|
|
37480
37183
|
padding: parseFloat(options.padding),
|
|
@@ -37542,8 +37245,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37542
37245
|
if (options.output) {
|
|
37543
37246
|
validateOutputPath(options.output);
|
|
37544
37247
|
}
|
|
37545
|
-
const absVideoPath =
|
|
37546
|
-
if (!
|
|
37248
|
+
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
37249
|
+
if (!existsSync21(absVideoPath)) {
|
|
37547
37250
|
exitWithError(notFoundError(absVideoPath));
|
|
37548
37251
|
}
|
|
37549
37252
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37577,7 +37280,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37577
37280
|
const spinner2 = ora("Starting caption process...").start();
|
|
37578
37281
|
const result = await executeCaption({
|
|
37579
37282
|
videoPath: absVideoPath,
|
|
37580
|
-
outputPath:
|
|
37283
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37581
37284
|
style: options.style,
|
|
37582
37285
|
fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
|
|
37583
37286
|
fontColor: options.color,
|
|
@@ -37623,8 +37326,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37623
37326
|
if (options.output) {
|
|
37624
37327
|
validateOutputPath(options.output);
|
|
37625
37328
|
}
|
|
37626
|
-
const absInputPath =
|
|
37627
|
-
if (!
|
|
37329
|
+
const absInputPath = resolve27(process.cwd(), inputPath);
|
|
37330
|
+
if (!existsSync21(absInputPath)) {
|
|
37628
37331
|
exitWithError(notFoundError(absInputPath));
|
|
37629
37332
|
}
|
|
37630
37333
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37651,7 +37354,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37651
37354
|
const spinner2 = ora("Applying noise reduction...").start();
|
|
37652
37355
|
const result = await executeNoiseReduce({
|
|
37653
37356
|
inputPath: absInputPath,
|
|
37654
|
-
outputPath:
|
|
37357
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37655
37358
|
strength: options.strength,
|
|
37656
37359
|
noiseFloor: options.noiseFloor ? parseFloat(options.noiseFloor) : void 0
|
|
37657
37360
|
});
|
|
@@ -37689,8 +37392,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37689
37392
|
if (options.output) {
|
|
37690
37393
|
validateOutputPath(options.output);
|
|
37691
37394
|
}
|
|
37692
|
-
const absVideoPath =
|
|
37693
|
-
if (!
|
|
37395
|
+
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
37396
|
+
if (!existsSync21(absVideoPath)) {
|
|
37694
37397
|
exitWithError(notFoundError(absVideoPath));
|
|
37695
37398
|
}
|
|
37696
37399
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37719,7 +37422,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37719
37422
|
const spinner2 = ora("Applying fade effects...").start();
|
|
37720
37423
|
const result = await executeFade({
|
|
37721
37424
|
videoPath: absVideoPath,
|
|
37722
|
-
outputPath:
|
|
37425
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37723
37426
|
fadeIn: parseFloat(options.fadeIn),
|
|
37724
37427
|
fadeOut: parseFloat(options.fadeOut),
|
|
37725
37428
|
audioOnly: options.audioOnly || false,
|
|
@@ -37764,8 +37467,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37764
37467
|
if (!options.target) {
|
|
37765
37468
|
exitWithError(usageError("Target language required. Use -t or --target"));
|
|
37766
37469
|
}
|
|
37767
|
-
const absSrtPath =
|
|
37768
|
-
if (!
|
|
37470
|
+
const absSrtPath = resolve27(process.cwd(), srtPath);
|
|
37471
|
+
if (!existsSync21(absSrtPath)) {
|
|
37769
37472
|
exitWithError(notFoundError(absSrtPath));
|
|
37770
37473
|
}
|
|
37771
37474
|
if (options.dryRun) {
|
|
@@ -37797,7 +37500,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37797
37500
|
const spinner2 = ora(`Translating to ${options.target}...`).start();
|
|
37798
37501
|
const result = await executeTranslateSrt({
|
|
37799
37502
|
srtPath: absSrtPath,
|
|
37800
|
-
outputPath:
|
|
37503
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37801
37504
|
targetLanguage: options.target,
|
|
37802
37505
|
provider,
|
|
37803
37506
|
sourceLanguage: options.source,
|
|
@@ -37840,8 +37543,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37840
37543
|
validateOutputPath(options.output);
|
|
37841
37544
|
}
|
|
37842
37545
|
if (options.fillers) rejectControlChars(options.fillers);
|
|
37843
|
-
const absVideoPath =
|
|
37844
|
-
if (!
|
|
37546
|
+
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
37547
|
+
if (!existsSync21(absVideoPath)) {
|
|
37845
37548
|
exitWithError(notFoundError(absVideoPath));
|
|
37846
37549
|
}
|
|
37847
37550
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37876,7 +37579,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37876
37579
|
const spinner2 = ora("Transcribing with word-level timestamps...").start();
|
|
37877
37580
|
const result = await executeJumpCut({
|
|
37878
37581
|
videoPath: absVideoPath,
|
|
37879
|
-
outputPath:
|
|
37582
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37880
37583
|
fillers,
|
|
37881
37584
|
padding: parseFloat(options.padding),
|
|
37882
37585
|
language: options.language,
|
|
@@ -37941,9 +37644,9 @@ var init_ai_edit_cli = __esm({
|
|
|
37941
37644
|
});
|
|
37942
37645
|
|
|
37943
37646
|
// src/commands/_shared/execute-fill-gaps.ts
|
|
37944
|
-
import { existsSync as
|
|
37945
|
-
import { readFile as
|
|
37946
|
-
import { resolve as
|
|
37647
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
37648
|
+
import { readFile as readFile18, writeFile as writeFile27, mkdir as mkdir14, rename as renameFs } from "node:fs/promises";
|
|
37649
|
+
import { resolve as resolve28, dirname as dirname9 } from "node:path";
|
|
37947
37650
|
function detectVideoGaps(videoClips, totalDuration) {
|
|
37948
37651
|
const gaps = [];
|
|
37949
37652
|
const sortedClips = [...videoClips].sort((a, b) => a.startTime - b.startTime);
|
|
@@ -37999,7 +37702,7 @@ function analyzeGapFillability(gaps, videoClips, sources) {
|
|
|
37999
37702
|
}
|
|
38000
37703
|
async function uploadFrameToImgbb(framePath, imgbbApiKey) {
|
|
38001
37704
|
try {
|
|
38002
|
-
const frameBuffer = await
|
|
37705
|
+
const frameBuffer = await readFile18(framePath);
|
|
38003
37706
|
const frameBase64 = frameBuffer.toString("base64");
|
|
38004
37707
|
const formData = new FormData();
|
|
38005
37708
|
formData.append("key", imgbbApiKey);
|
|
@@ -38024,14 +37727,14 @@ async function executeFillGaps(options) {
|
|
|
38024
37727
|
try {
|
|
38025
37728
|
onProgress("Loading timeline...");
|
|
38026
37729
|
const filePath = await resolveTimelineFile(options.projectPath);
|
|
38027
|
-
if (!
|
|
37730
|
+
if (!existsSync22(filePath)) {
|
|
38028
37731
|
return {
|
|
38029
37732
|
success: false,
|
|
38030
37733
|
error: `Timeline file not found: ${filePath}`,
|
|
38031
37734
|
humanLines
|
|
38032
37735
|
};
|
|
38033
37736
|
}
|
|
38034
|
-
const content = await
|
|
37737
|
+
const content = await readFile18(filePath, "utf-8");
|
|
38035
37738
|
const data = JSON.parse(content);
|
|
38036
37739
|
const project = Project.fromJSON(data);
|
|
38037
37740
|
const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
|
|
@@ -38158,8 +37861,8 @@ async function executeFillGaps(options) {
|
|
|
38158
37861
|
};
|
|
38159
37862
|
}
|
|
38160
37863
|
const projectDir = dirname9(filePath);
|
|
38161
|
-
const footageDir = options.dir ?
|
|
38162
|
-
if (!
|
|
37864
|
+
const footageDir = options.dir ? resolve28(process.cwd(), options.dir) : resolve28(projectDir, "footage");
|
|
37865
|
+
if (!existsSync22(footageDir)) {
|
|
38163
37866
|
await mkdir14(footageDir, { recursive: true });
|
|
38164
37867
|
}
|
|
38165
37868
|
humanLines.push("Generating AI Videos");
|
|
@@ -38190,7 +37893,7 @@ async function executeFillGaps(options) {
|
|
|
38190
37893
|
}
|
|
38191
37894
|
onProgress("Extracting frame from preceding clip...");
|
|
38192
37895
|
const frameOffset = clipBefore.sourceStartOffset + clipBefore.duration - 0.1;
|
|
38193
|
-
const framePath =
|
|
37896
|
+
const framePath = resolve28(footageDir, `frame-${gap.start.toFixed(2)}.png`);
|
|
38194
37897
|
try {
|
|
38195
37898
|
await execSafe("ffmpeg", [
|
|
38196
37899
|
"-i",
|
|
@@ -38251,10 +37954,10 @@ async function executeFillGaps(options) {
|
|
|
38251
37954
|
continue;
|
|
38252
37955
|
}
|
|
38253
37956
|
const videoFileName = `gap-fill-${gap.start.toFixed(2)}-${gap.end.toFixed(2)}.mp4`;
|
|
38254
|
-
const videoPath =
|
|
37957
|
+
const videoPath = resolve28(footageDir, videoFileName);
|
|
38255
37958
|
onProgress("Downloading generated video...");
|
|
38256
37959
|
const videoBuffer = await downloadVideo(finalResult.videoUrl);
|
|
38257
|
-
await
|
|
37960
|
+
await writeFile27(videoPath, videoBuffer);
|
|
38258
37961
|
generatedDuration = finalResult.duration || parseInt(klingDuration);
|
|
38259
37962
|
generatedVideos.push(videoPath);
|
|
38260
37963
|
humanLines.push(` Generated: ${videoFileName} (${generatedDuration}s)`);
|
|
@@ -38263,7 +37966,7 @@ async function executeFillGaps(options) {
|
|
|
38263
37966
|
const remainingNeeded = targetDuration - generatedDuration;
|
|
38264
37967
|
const segmentDuration = remainingNeeded > 5 ? "10" : "5";
|
|
38265
37968
|
onProgress(`Generating additional ${segmentDuration}s segment...`);
|
|
38266
|
-
const lastFramePath =
|
|
37969
|
+
const lastFramePath = resolve28(
|
|
38267
37970
|
footageDir,
|
|
38268
37971
|
`frame-extend-${gap.start.toFixed(2)}-${segmentIndex}.png`
|
|
38269
37972
|
);
|
|
@@ -38319,17 +38022,17 @@ async function executeFillGaps(options) {
|
|
|
38319
38022
|
);
|
|
38320
38023
|
break;
|
|
38321
38024
|
}
|
|
38322
|
-
const segVideoPath =
|
|
38025
|
+
const segVideoPath = resolve28(
|
|
38323
38026
|
footageDir,
|
|
38324
38027
|
`gap-fill-${gap.start.toFixed(2)}-${gap.end.toFixed(2)}-seg${segmentIndex}.mp4`
|
|
38325
38028
|
);
|
|
38326
38029
|
const segVideoBuffer = await downloadVideo(segFinalResult.videoUrl);
|
|
38327
|
-
await
|
|
38328
|
-
const concatListPath =
|
|
38030
|
+
await writeFile27(segVideoPath, segVideoBuffer);
|
|
38031
|
+
const concatListPath = resolve28(footageDir, `concat-${gap.start.toFixed(2)}.txt`);
|
|
38329
38032
|
const concatList = generatedVideos.map((v) => `file '${v}'`).join("\n") + `
|
|
38330
38033
|
file '${segVideoPath}'`;
|
|
38331
|
-
await
|
|
38332
|
-
const concatOutputPath =
|
|
38034
|
+
await writeFile27(concatListPath, concatList);
|
|
38035
|
+
const concatOutputPath = resolve28(
|
|
38333
38036
|
footageDir,
|
|
38334
38037
|
`gap-fill-${gap.start.toFixed(2)}-${gap.end.toFixed(2)}-merged.mp4`
|
|
38335
38038
|
);
|
|
@@ -38385,8 +38088,8 @@ file '${segVideoPath}'`;
|
|
|
38385
38088
|
humanLines.push("");
|
|
38386
38089
|
let outputPath = filePath;
|
|
38387
38090
|
if (generatedCount > 0) {
|
|
38388
|
-
outputPath = options.output ?
|
|
38389
|
-
await
|
|
38091
|
+
outputPath = options.output ? resolve28(process.cwd(), options.output) : filePath;
|
|
38092
|
+
await writeFile27(outputPath, JSON.stringify(project.toJSON(), null, 2));
|
|
38390
38093
|
humanLines.push(`\u2714 Filled ${generatedCount} gap(s) with AI-generated video`);
|
|
38391
38094
|
humanLines.push(`Project saved: ${outputPath}`);
|
|
38392
38095
|
} else {
|
|
@@ -38482,14 +38185,14 @@ __export(edit_cmd_exports, {
|
|
|
38482
38185
|
executeSpeedRamp: () => executeSpeedRamp,
|
|
38483
38186
|
executeUpscale: () => executeUpscale
|
|
38484
38187
|
});
|
|
38485
|
-
import { resolve as
|
|
38486
|
-
import { readFile as
|
|
38188
|
+
import { resolve as resolve29, dirname as dirname10 } from "node:path";
|
|
38189
|
+
import { readFile as readFile19, writeFile as writeFile28, mkdir as mkdir15 } from "node:fs/promises";
|
|
38487
38190
|
async function executeGrade(options) {
|
|
38488
38191
|
const { videoPath, style, preset, output, analyzeOnly, apiKey } = options;
|
|
38489
38192
|
try {
|
|
38490
38193
|
if (!style && !preset) return { success: false, error: "Either style or preset is required" };
|
|
38491
38194
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
38492
|
-
const absPath =
|
|
38195
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38493
38196
|
let gradeResult;
|
|
38494
38197
|
if (preset) {
|
|
38495
38198
|
const claude = new ClaudeProvider();
|
|
@@ -38504,7 +38207,7 @@ async function executeGrade(options) {
|
|
|
38504
38207
|
if (analyzeOnly) {
|
|
38505
38208
|
return { success: true, style: preset || style, description: gradeResult.description, ffmpegFilter: gradeResult.ffmpegFilter };
|
|
38506
38209
|
}
|
|
38507
|
-
const outputPath = output ?
|
|
38210
|
+
const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, "-graded$1");
|
|
38508
38211
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", gradeResult.ffmpegFilter, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
|
|
38509
38212
|
return { success: true, outputPath, style: preset || style, description: gradeResult.description, ffmpegFilter: gradeResult.ffmpegFilter };
|
|
38510
38213
|
} catch (error) {
|
|
@@ -38519,12 +38222,12 @@ async function executeSpeedRamp(options) {
|
|
|
38519
38222
|
const claudeKey = apiKey || process.env.ANTHROPIC_API_KEY;
|
|
38520
38223
|
if (!openaiKey) return { success: false, error: "OPENAI_API_KEY required for Whisper transcription" };
|
|
38521
38224
|
if (!claudeKey) return { success: false, error: "ANTHROPIC_API_KEY required for Claude analysis" };
|
|
38522
|
-
const absPath =
|
|
38225
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38523
38226
|
const tempAudio = absPath.replace(/(\.[^.]+)$/, "-temp-audio.mp3");
|
|
38524
38227
|
await execSafe("ffmpeg", ["-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "2", tempAudio, "-y"]);
|
|
38525
38228
|
const whisper = new WhisperProvider();
|
|
38526
38229
|
await whisper.initialize({ apiKey: openaiKey });
|
|
38527
|
-
const audioBuffer = await
|
|
38230
|
+
const audioBuffer = await readFile19(tempAudio);
|
|
38528
38231
|
const transcript = await whisper.transcribe(new Blob([audioBuffer]), language);
|
|
38529
38232
|
try {
|
|
38530
38233
|
const { unlink: unlink5 } = await import("node:fs/promises");
|
|
@@ -38544,7 +38247,7 @@ async function executeSpeedRamp(options) {
|
|
|
38544
38247
|
if (speedResult.keyframes.length < 2) {
|
|
38545
38248
|
return { success: false, error: "Not enough keyframes for speed ramping" };
|
|
38546
38249
|
}
|
|
38547
|
-
const outputPath = output ?
|
|
38250
|
+
const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
|
|
38548
38251
|
const setpts = `setpts=${(1 / avgSpeed).toFixed(3)}*PTS`;
|
|
38549
38252
|
const atempo = avgSpeed >= 0.5 && avgSpeed <= 2 ? `atempo=${avgSpeed.toFixed(3)}` : "";
|
|
38550
38253
|
if (atempo) {
|
|
@@ -38561,7 +38264,7 @@ async function executeReframe(options) {
|
|
|
38561
38264
|
const { videoPath, aspect = "9:16", output, analyzeOnly } = options;
|
|
38562
38265
|
try {
|
|
38563
38266
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
38564
|
-
const absPath =
|
|
38267
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38565
38268
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
38566
38269
|
"-v",
|
|
38567
38270
|
"error",
|
|
@@ -38596,7 +38299,7 @@ async function executeReframe(options) {
|
|
|
38596
38299
|
keyframeCount: 1
|
|
38597
38300
|
};
|
|
38598
38301
|
}
|
|
38599
|
-
const outputPath = output ?
|
|
38302
|
+
const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, `-${aspect.replace(":", "x")}$1`);
|
|
38600
38303
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", `crop=${cropWidth}:${cropHeight}:${cropX}:${cropY}`, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
|
|
38601
38304
|
return { success: true, outputPath, sourceAspect: `${sourceWidth}:${sourceHeight}`, targetAspect: aspect };
|
|
38602
38305
|
} catch (error) {
|
|
@@ -38608,7 +38311,7 @@ async function executeInterpolate(options) {
|
|
|
38608
38311
|
try {
|
|
38609
38312
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
38610
38313
|
if (![2, 4, 8].includes(factor)) return { success: false, error: "Factor must be 2, 4, or 8" };
|
|
38611
|
-
const absPath =
|
|
38314
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38612
38315
|
const { stdout: fpsOut } = await execSafe("ffprobe", [
|
|
38613
38316
|
"-v",
|
|
38614
38317
|
"error",
|
|
@@ -38624,7 +38327,7 @@ async function executeInterpolate(options) {
|
|
|
38624
38327
|
const originalFps = num / (den || 1);
|
|
38625
38328
|
const targetFps = options.fps || originalFps * factor;
|
|
38626
38329
|
const mi = quality === "fast" ? "mi_mode=mci" : "mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1";
|
|
38627
|
-
const outputPath = output ?
|
|
38330
|
+
const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, `-slow${factor}x$1`);
|
|
38628
38331
|
await execSafe("ffmpeg", ["-i", absPath, "-filter:v", `minterpolate='${mi}:fps=${targetFps}',setpts=${factor}*PTS`, "-an", outputPath, "-y"], { timeout: 6e5 });
|
|
38629
38332
|
return { success: true, outputPath, originalFps, targetFps, factor };
|
|
38630
38333
|
} catch (error) {
|
|
@@ -38635,7 +38338,7 @@ async function executeUpscale(options) {
|
|
|
38635
38338
|
const { videoPath, output, scale = 2, quality = "quality" } = options;
|
|
38636
38339
|
try {
|
|
38637
38340
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
38638
|
-
const absPath =
|
|
38341
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38639
38342
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
38640
38343
|
"-v",
|
|
38641
38344
|
"error",
|
|
@@ -38651,7 +38354,7 @@ async function executeUpscale(options) {
|
|
|
38651
38354
|
const targetW = w * scale;
|
|
38652
38355
|
const targetH = h * scale;
|
|
38653
38356
|
const scaleFilter = quality === "quality" ? `scale=${targetW}:${targetH}:flags=lanczos` : `scale=${targetW}:${targetH}`;
|
|
38654
|
-
const outputPath = output ?
|
|
38357
|
+
const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, `-${scale}x$1`);
|
|
38655
38358
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", scaleFilter, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
|
|
38656
38359
|
return { success: true, outputPath, originalRes: `${w}x${h}`, targetRes: `${targetW}x${targetH}` };
|
|
38657
38360
|
} catch (error) {
|
|
@@ -38724,7 +38427,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38724
38427
|
dryRun: true,
|
|
38725
38428
|
data: {
|
|
38726
38429
|
params: {
|
|
38727
|
-
videoPath:
|
|
38430
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
38728
38431
|
style: options.style || options.preset,
|
|
38729
38432
|
analyzeOnly: options.analyzeOnly || false
|
|
38730
38433
|
}
|
|
@@ -38751,8 +38454,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38751
38454
|
}
|
|
38752
38455
|
spinner2.succeed(source_default.green("Color grade analyzed"));
|
|
38753
38456
|
if (isJsonMode()) {
|
|
38754
|
-
const absPath2 =
|
|
38755
|
-
const gradeOutputPath = options.output ?
|
|
38457
|
+
const absPath2 = resolve29(process.cwd(), videoPath);
|
|
38458
|
+
const gradeOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath2.replace(/(\.[^.]+)$/, "-graded$1");
|
|
38756
38459
|
outputSuccess({
|
|
38757
38460
|
command: "edit grade",
|
|
38758
38461
|
startedAt,
|
|
@@ -38778,8 +38481,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38778
38481
|
console.log(source_default.dim("Use without --analyze-only to apply the grade."));
|
|
38779
38482
|
return;
|
|
38780
38483
|
}
|
|
38781
|
-
const absPath =
|
|
38782
|
-
const outputPath = options.output ?
|
|
38484
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38485
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-graded$1");
|
|
38783
38486
|
spinner2.start("Applying color grade...");
|
|
38784
38487
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", gradeResult.ffmpegFilter, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
|
|
38785
38488
|
spinner2.succeed(source_default.green("Color grade applied"));
|
|
@@ -38809,7 +38512,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38809
38512
|
dryRun: true,
|
|
38810
38513
|
data: {
|
|
38811
38514
|
params: {
|
|
38812
|
-
videoPath:
|
|
38515
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
38813
38516
|
texts: options.text,
|
|
38814
38517
|
style: options.style,
|
|
38815
38518
|
fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
|
|
@@ -38822,8 +38525,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38822
38525
|
});
|
|
38823
38526
|
return;
|
|
38824
38527
|
}
|
|
38825
|
-
const absPath =
|
|
38826
|
-
const outputPath = options.output ?
|
|
38528
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38529
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-overlay$1");
|
|
38827
38530
|
const spinner2 = ora("Applying text overlays...").start();
|
|
38828
38531
|
const result = await applyTextOverlays({
|
|
38829
38532
|
videoPath: absPath,
|
|
@@ -38881,7 +38584,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38881
38584
|
dryRun: true,
|
|
38882
38585
|
data: {
|
|
38883
38586
|
params: {
|
|
38884
|
-
videoPath:
|
|
38587
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
38885
38588
|
style: options.style,
|
|
38886
38589
|
minSpeed: parseFloat(options.minSpeed),
|
|
38887
38590
|
maxSpeed: parseFloat(options.maxSpeed),
|
|
@@ -38893,7 +38596,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38893
38596
|
}
|
|
38894
38597
|
const openaiApiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI");
|
|
38895
38598
|
const claudeApiKey = await requireApiKey("ANTHROPIC_API_KEY", "Anthropic", options.apiKey);
|
|
38896
|
-
const absPath =
|
|
38599
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38897
38600
|
const spinner2 = ora("Extracting audio...").start();
|
|
38898
38601
|
const { stdout: speedRampProbe } = await execSafe("ffprobe", [
|
|
38899
38602
|
"-v",
|
|
@@ -38915,7 +38618,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38915
38618
|
spinner2.text = "Transcribing audio...";
|
|
38916
38619
|
const whisper = new WhisperProvider();
|
|
38917
38620
|
await whisper.initialize({ apiKey: openaiApiKey });
|
|
38918
|
-
const audioBuffer = await
|
|
38621
|
+
const audioBuffer = await readFile19(tempAudio);
|
|
38919
38622
|
const audioBlob = new Blob([audioBuffer]);
|
|
38920
38623
|
const transcript = await whisper.transcribe(audioBlob, options.language);
|
|
38921
38624
|
if (!transcript.segments || transcript.segments.length === 0) {
|
|
@@ -38938,7 +38641,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38938
38641
|
spinner2.succeed(source_default.green(`Found ${speedResult.keyframes.length} speed keyframes`));
|
|
38939
38642
|
if (isJsonMode()) {
|
|
38940
38643
|
const avgSpeed2 = speedResult.keyframes.reduce((sum, kf) => sum + kf.speed, 0) / speedResult.keyframes.length;
|
|
38941
|
-
const speedRampOutputPath = options.output ?
|
|
38644
|
+
const speedRampOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
|
|
38942
38645
|
outputSuccess({
|
|
38943
38646
|
command: "edit speed-ramp",
|
|
38944
38647
|
startedAt,
|
|
@@ -38967,7 +38670,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38967
38670
|
return;
|
|
38968
38671
|
}
|
|
38969
38672
|
spinner2.start("Applying speed ramps...");
|
|
38970
|
-
const outputPath = options.output ?
|
|
38673
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
|
|
38971
38674
|
const avgSpeed = speedResult.keyframes.reduce((sum, kf) => sum + kf.speed, 0) / speedResult.keyframes.length;
|
|
38972
38675
|
const setpts = `setpts=${(1 / avgSpeed).toFixed(3)}*PTS`;
|
|
38973
38676
|
const atempo = avgSpeed >= 0.5 && avgSpeed <= 2 ? `atempo=${avgSpeed.toFixed(3)}` : "";
|
|
@@ -39001,7 +38704,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39001
38704
|
dryRun: true,
|
|
39002
38705
|
data: {
|
|
39003
38706
|
params: {
|
|
39004
|
-
videoPath:
|
|
38707
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
39005
38708
|
aspect: options.aspect,
|
|
39006
38709
|
focus: options.focus,
|
|
39007
38710
|
analyzeOnly: options.analyzeOnly || false
|
|
@@ -39010,7 +38713,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39010
38713
|
});
|
|
39011
38714
|
return;
|
|
39012
38715
|
}
|
|
39013
|
-
const absPath =
|
|
38716
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
39014
38717
|
const spinner2 = ora("Analyzing video...").start();
|
|
39015
38718
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
39016
38719
|
"-v",
|
|
@@ -39048,7 +38751,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39048
38751
|
for (let i = 1; i <= numKeyframes && i <= 30; i++) {
|
|
39049
38752
|
const framePath = `${tempDir}/frame-${i.toString().padStart(4, "0")}.jpg`;
|
|
39050
38753
|
try {
|
|
39051
|
-
const frameBuffer = await
|
|
38754
|
+
const frameBuffer = await readFile19(framePath);
|
|
39052
38755
|
const frameBase64 = frameBuffer.toString("base64");
|
|
39053
38756
|
const result = await claude.analyzeFrameForReframe(frameBase64, options.aspect, {
|
|
39054
38757
|
focusMode: options.focus,
|
|
@@ -39072,7 +38775,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39072
38775
|
}
|
|
39073
38776
|
spinner2.succeed(source_default.green(`Analyzed ${cropKeyframes.length} keyframes`));
|
|
39074
38777
|
if (isJsonMode()) {
|
|
39075
|
-
const reframeOutputPath = options.output ?
|
|
38778
|
+
const reframeOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-${options.aspect.replace(":", "x")}$1`);
|
|
39076
38779
|
outputSuccess({
|
|
39077
38780
|
command: "edit reframe",
|
|
39078
38781
|
startedAt,
|
|
@@ -39107,8 +38810,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39107
38810
|
}
|
|
39108
38811
|
console.log();
|
|
39109
38812
|
if (options.keyframes) {
|
|
39110
|
-
const keyframesPath =
|
|
39111
|
-
await
|
|
38813
|
+
const keyframesPath = resolve29(process.cwd(), options.keyframes);
|
|
38814
|
+
await writeFile28(keyframesPath, JSON.stringify(cropKeyframes, null, 2));
|
|
39112
38815
|
console.log(source_default.green(`Keyframes saved to: ${keyframesPath}`));
|
|
39113
38816
|
}
|
|
39114
38817
|
if (options.analyzeOnly) {
|
|
@@ -39119,7 +38822,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39119
38822
|
const avgCropY = Math.round(cropKeyframes.reduce((sum, kf) => sum + kf.cropY, 0) / cropKeyframes.length);
|
|
39120
38823
|
const cropWidth = cropKeyframes[0]?.cropWidth || sourceWidth;
|
|
39121
38824
|
const cropHeight = cropKeyframes[0]?.cropHeight || sourceHeight;
|
|
39122
|
-
const outputPath = options.output ?
|
|
38825
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-${options.aspect.replace(":", "x")}$1`);
|
|
39123
38826
|
spinner2.start("Applying reframe...");
|
|
39124
38827
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", `crop=${cropWidth}:${cropHeight}:${avgCropX}:${avgCropY}`, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
|
|
39125
38828
|
spinner2.succeed(source_default.green("Reframe applied"));
|
|
@@ -39155,7 +38858,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39155
38858
|
dryRun: true,
|
|
39156
38859
|
data: {
|
|
39157
38860
|
params: {
|
|
39158
|
-
imagePaths: imagePaths.map((p) =>
|
|
38861
|
+
imagePaths: imagePaths.map((p) => resolve29(process.cwd(), p)),
|
|
39159
38862
|
prompt: prompt3,
|
|
39160
38863
|
provider,
|
|
39161
38864
|
model: options.model,
|
|
@@ -39176,8 +38879,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39176
38879
|
const spinner2 = ora(`Reading ${imagePaths.length} image(s)...`).start();
|
|
39177
38880
|
const imageBuffers = [];
|
|
39178
38881
|
for (const imagePath of imagePaths) {
|
|
39179
|
-
const absPath =
|
|
39180
|
-
const buffer = await
|
|
38882
|
+
const absPath = resolve29(process.cwd(), imagePath);
|
|
38883
|
+
const buffer = await readFile19(absPath);
|
|
39181
38884
|
imageBuffers.push(buffer);
|
|
39182
38885
|
}
|
|
39183
38886
|
let result;
|
|
@@ -39225,16 +38928,16 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39225
38928
|
}
|
|
39226
38929
|
spinner2.succeed(source_default.green("Image edited"));
|
|
39227
38930
|
const img = result.images[0];
|
|
39228
|
-
const outputPath =
|
|
38931
|
+
const outputPath = resolve29(process.cwd(), options.output);
|
|
39229
38932
|
const saveImage = async () => {
|
|
39230
38933
|
await mkdir15(dirname10(outputPath), { recursive: true });
|
|
39231
38934
|
if (img.base64) {
|
|
39232
38935
|
const buffer = Buffer.from(img.base64, "base64");
|
|
39233
|
-
await
|
|
38936
|
+
await writeFile28(outputPath, buffer);
|
|
39234
38937
|
} else if (img.url) {
|
|
39235
38938
|
const resp = await fetch(img.url);
|
|
39236
38939
|
const arrayBuf = await resp.arrayBuffer();
|
|
39237
|
-
await
|
|
38940
|
+
await writeFile28(outputPath, Buffer.from(arrayBuf));
|
|
39238
38941
|
}
|
|
39239
38942
|
};
|
|
39240
38943
|
const resultModel = result.model;
|
|
@@ -39267,7 +38970,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39267
38970
|
if (options.output) {
|
|
39268
38971
|
validateOutputPath(options.output);
|
|
39269
38972
|
}
|
|
39270
|
-
const absPath =
|
|
38973
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
39271
38974
|
const factor = parseInt(options.factor);
|
|
39272
38975
|
if (![2, 4, 8].includes(factor)) {
|
|
39273
38976
|
exitWithError(usageError("Factor must be 2, 4, or 8"));
|
|
@@ -39288,7 +38991,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39288
38991
|
});
|
|
39289
38992
|
return;
|
|
39290
38993
|
}
|
|
39291
|
-
const outputPath = options.output ?
|
|
38994
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-slow${factor}x$1`);
|
|
39292
38995
|
const spinner2 = ora(`Creating ${factor}x slow motion...`).start();
|
|
39293
38996
|
try {
|
|
39294
38997
|
const { stdout: fpsOut } = await execSafe("ffprobe", [
|
|
@@ -39349,7 +39052,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39349
39052
|
if (options.output) {
|
|
39350
39053
|
validateOutputPath(options.output);
|
|
39351
39054
|
}
|
|
39352
|
-
const absPath =
|
|
39055
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
39353
39056
|
const scale = parseInt(options.scale);
|
|
39354
39057
|
if (scale !== 2 && scale !== 4) {
|
|
39355
39058
|
exitWithError(usageError("Scale must be 2 or 4"));
|
|
@@ -39371,7 +39074,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39371
39074
|
return;
|
|
39372
39075
|
}
|
|
39373
39076
|
if (options.ffmpeg) {
|
|
39374
|
-
const outputPath = options.output ?
|
|
39077
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-upscaled-${scale}x$1`);
|
|
39375
39078
|
const spinner3 = ora(`Upscaling video with FFmpeg (${scale}x)...`).start();
|
|
39376
39079
|
try {
|
|
39377
39080
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
@@ -39452,9 +39155,9 @@ __export(ai_analyze_exports, {
|
|
|
39452
39155
|
executeGeminiVideo: () => executeGeminiVideo,
|
|
39453
39156
|
registerAnalyzeCommands: () => registerAnalyzeCommands
|
|
39454
39157
|
});
|
|
39455
|
-
import { readFile as
|
|
39456
|
-
import { extname as extname6, resolve as
|
|
39457
|
-
import { existsSync as
|
|
39158
|
+
import { readFile as readFile20 } from "node:fs/promises";
|
|
39159
|
+
import { extname as extname6, resolve as resolve30 } from "node:path";
|
|
39160
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
39458
39161
|
async function executeGeminiVideo(options) {
|
|
39459
39162
|
try {
|
|
39460
39163
|
const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
|
|
@@ -39472,11 +39175,11 @@ async function executeGeminiVideo(options) {
|
|
|
39472
39175
|
if (isYouTube) {
|
|
39473
39176
|
videoData = options.source;
|
|
39474
39177
|
} else {
|
|
39475
|
-
const absPath =
|
|
39476
|
-
if (!
|
|
39178
|
+
const absPath = resolve30(process.cwd(), options.source);
|
|
39179
|
+
if (!existsSync23(absPath)) {
|
|
39477
39180
|
return { success: false, error: `File not found: ${absPath}` };
|
|
39478
39181
|
}
|
|
39479
|
-
videoData = await
|
|
39182
|
+
videoData = await readFile20(absPath);
|
|
39480
39183
|
}
|
|
39481
39184
|
const gemini = new GeminiProvider();
|
|
39482
39185
|
await gemini.initialize({ apiKey });
|
|
@@ -39545,11 +39248,11 @@ async function executeAnalyze(options) {
|
|
|
39545
39248
|
}
|
|
39546
39249
|
imageBuffer = Buffer.from(await response.arrayBuffer());
|
|
39547
39250
|
} else {
|
|
39548
|
-
const absPath =
|
|
39549
|
-
if (!
|
|
39251
|
+
const absPath = resolve30(process.cwd(), source);
|
|
39252
|
+
if (!existsSync23(absPath)) {
|
|
39550
39253
|
return { success: false, error: `File not found: ${absPath}` };
|
|
39551
39254
|
}
|
|
39552
|
-
imageBuffer = await
|
|
39255
|
+
imageBuffer = await readFile20(absPath);
|
|
39553
39256
|
}
|
|
39554
39257
|
const result2 = await gemini.analyzeImage(imageBuffer, options.prompt, {
|
|
39555
39258
|
model: modelId,
|
|
@@ -39580,11 +39283,11 @@ async function executeAnalyze(options) {
|
|
|
39580
39283
|
}
|
|
39581
39284
|
videoData = Buffer.from(await response.arrayBuffer());
|
|
39582
39285
|
} else {
|
|
39583
|
-
const absPath =
|
|
39584
|
-
if (!
|
|
39286
|
+
const absPath = resolve30(process.cwd(), source);
|
|
39287
|
+
if (!existsSync23(absPath)) {
|
|
39585
39288
|
return { success: false, error: `File not found: ${absPath}` };
|
|
39586
39289
|
}
|
|
39587
|
-
videoData = await
|
|
39290
|
+
videoData = await readFile20(absPath);
|
|
39588
39291
|
}
|
|
39589
39292
|
const result = await gemini.analyzeVideo(videoData, options.prompt, {
|
|
39590
39293
|
model: modelId,
|
|
@@ -39718,9 +39421,9 @@ __export(ai_review_exports, {
|
|
|
39718
39421
|
executeReview: () => executeReview,
|
|
39719
39422
|
registerReviewCommand: () => registerReviewCommand
|
|
39720
39423
|
});
|
|
39721
|
-
import { readFile as
|
|
39722
|
-
import { resolve as
|
|
39723
|
-
import { existsSync as
|
|
39424
|
+
import { readFile as readFile21, rename as rename2 } from "node:fs/promises";
|
|
39425
|
+
import { resolve as resolve31 } from "node:path";
|
|
39426
|
+
import { existsSync as existsSync24 } from "node:fs";
|
|
39724
39427
|
function parseReviewFeedback(response) {
|
|
39725
39428
|
let cleaned = response.trim();
|
|
39726
39429
|
if (cleaned.startsWith("```json")) {
|
|
@@ -39744,8 +39447,8 @@ function parseReviewFeedback(response) {
|
|
|
39744
39447
|
}
|
|
39745
39448
|
async function executeReview(options) {
|
|
39746
39449
|
const { videoPath, storyboardPath, autoApply = false, verify = false, model = "flash" } = options;
|
|
39747
|
-
const absVideoPath =
|
|
39748
|
-
if (!
|
|
39450
|
+
const absVideoPath = resolve31(process.cwd(), videoPath);
|
|
39451
|
+
if (!existsSync24(absVideoPath)) {
|
|
39749
39452
|
return { success: false, error: `Video not found: ${absVideoPath}` };
|
|
39750
39453
|
}
|
|
39751
39454
|
const apiKey = process.env.GOOGLE_API_KEY || await getApiKey("GOOGLE_API_KEY", "Google");
|
|
@@ -39754,9 +39457,9 @@ async function executeReview(options) {
|
|
|
39754
39457
|
}
|
|
39755
39458
|
let storyboardContext = "";
|
|
39756
39459
|
if (storyboardPath) {
|
|
39757
|
-
const absStoryboardPath =
|
|
39758
|
-
if (
|
|
39759
|
-
const content = await
|
|
39460
|
+
const absStoryboardPath = resolve31(process.cwd(), storyboardPath);
|
|
39461
|
+
if (existsSync24(absStoryboardPath)) {
|
|
39462
|
+
const content = await readFile21(absStoryboardPath, "utf-8");
|
|
39760
39463
|
storyboardContext = `
|
|
39761
39464
|
|
|
39762
39465
|
Original storyboard for reference:
|
|
@@ -39789,7 +39492,7 @@ ${content}`;
|
|
|
39789
39492
|
Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFixable. Be specific and practical.${storyboardContext}`;
|
|
39790
39493
|
const gemini = new GeminiProvider();
|
|
39791
39494
|
await gemini.initialize({ apiKey });
|
|
39792
|
-
const videoData = await
|
|
39495
|
+
const videoData = await readFile21(absVideoPath);
|
|
39793
39496
|
const analysisResult = await gemini.analyzeVideo(videoData, reviewPrompt, {
|
|
39794
39497
|
model: modelId
|
|
39795
39498
|
});
|
|
@@ -39810,7 +39513,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
|
|
|
39810
39513
|
};
|
|
39811
39514
|
if (autoApply && feedback.autoFixable.length > 0) {
|
|
39812
39515
|
let currentInput = absVideoPath;
|
|
39813
|
-
const outputBase = options.outputPath ?
|
|
39516
|
+
const outputBase = options.outputPath ? resolve31(process.cwd(), options.outputPath) : absVideoPath.replace(/(\.[^.]+)$/, "-reviewed$1");
|
|
39814
39517
|
for (const fix of feedback.autoFixable) {
|
|
39815
39518
|
if (fix.type === "color_grade" && fix.ffmpegFilter) {
|
|
39816
39519
|
try {
|
|
@@ -39827,7 +39530,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
|
|
|
39827
39530
|
if (currentInput !== absVideoPath) {
|
|
39828
39531
|
const finalOutput = outputBase;
|
|
39829
39532
|
try {
|
|
39830
|
-
await
|
|
39533
|
+
await rename2(currentInput, finalOutput);
|
|
39831
39534
|
result.outputPath = finalOutput;
|
|
39832
39535
|
} catch {
|
|
39833
39536
|
result.outputPath = currentInput;
|
|
@@ -39835,7 +39538,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
|
|
|
39835
39538
|
}
|
|
39836
39539
|
}
|
|
39837
39540
|
if (verify && result.outputPath) {
|
|
39838
|
-
const verifyVideoData = await
|
|
39541
|
+
const verifyVideoData = await readFile21(result.outputPath);
|
|
39839
39542
|
const verifyResult = await gemini.analyzeVideo(
|
|
39840
39543
|
verifyVideoData,
|
|
39841
39544
|
'Rate this video overall quality on a scale of 1-10. Return ONLY a JSON object: {"score": <number>}',
|
|
@@ -39894,73 +39597,657 @@ function registerReviewCommand(aiCommand) {
|
|
|
39894
39597
|
spinner2.fail(result.error || "Video review failed");
|
|
39895
39598
|
exitWithError(apiError(result.error || "Video review failed", true));
|
|
39896
39599
|
}
|
|
39897
|
-
spinner2.succeed(source_default.green("Video review complete"));
|
|
39898
|
-
console.log();
|
|
39899
|
-
const fb = result.feedback;
|
|
39900
|
-
console.log(source_default.bold.cyan("Video Review"));
|
|
39901
|
-
console.log(source_default.dim("\u2500".repeat(60)));
|
|
39902
|
-
console.log(`Overall Score: ${source_default.bold(fb.overallScore >= 7 ? source_default.green(String(fb.overallScore)) : fb.overallScore >= 5 ? source_default.yellow(String(fb.overallScore)) : source_default.red(String(fb.overallScore)))}/10`);
|
|
39903
|
-
console.log();
|
|
39904
|
-
const categories = [
|
|
39905
|
-
["Pacing", fb.categories.pacing],
|
|
39906
|
-
["Color", fb.categories.color],
|
|
39907
|
-
["Text Readability", fb.categories.textReadability],
|
|
39908
|
-
["Audio-Visual Sync", fb.categories.audioVisualSync],
|
|
39909
|
-
["Composition", fb.categories.composition]
|
|
39910
|
-
];
|
|
39911
|
-
for (const [name, cat] of categories) {
|
|
39912
|
-
const scoreColor = cat.score >= 7 ? source_default.green : cat.score >= 5 ? source_default.yellow : source_default.red;
|
|
39913
|
-
const fixable = cat.fixable ? source_default.dim(" [fixable]") : "";
|
|
39914
|
-
console.log(` ${name.padEnd(20)} ${scoreColor(String(cat.score).padStart(2))}/10${fixable}`);
|
|
39915
|
-
if (cat.issues.length > 0) {
|
|
39916
|
-
for (const issue of cat.issues) {
|
|
39917
|
-
console.log(source_default.dim(` - ${issue}`));
|
|
39600
|
+
spinner2.succeed(source_default.green("Video review complete"));
|
|
39601
|
+
console.log();
|
|
39602
|
+
const fb = result.feedback;
|
|
39603
|
+
console.log(source_default.bold.cyan("Video Review"));
|
|
39604
|
+
console.log(source_default.dim("\u2500".repeat(60)));
|
|
39605
|
+
console.log(`Overall Score: ${source_default.bold(fb.overallScore >= 7 ? source_default.green(String(fb.overallScore)) : fb.overallScore >= 5 ? source_default.yellow(String(fb.overallScore)) : source_default.red(String(fb.overallScore)))}/10`);
|
|
39606
|
+
console.log();
|
|
39607
|
+
const categories = [
|
|
39608
|
+
["Pacing", fb.categories.pacing],
|
|
39609
|
+
["Color", fb.categories.color],
|
|
39610
|
+
["Text Readability", fb.categories.textReadability],
|
|
39611
|
+
["Audio-Visual Sync", fb.categories.audioVisualSync],
|
|
39612
|
+
["Composition", fb.categories.composition]
|
|
39613
|
+
];
|
|
39614
|
+
for (const [name, cat] of categories) {
|
|
39615
|
+
const scoreColor = cat.score >= 7 ? source_default.green : cat.score >= 5 ? source_default.yellow : source_default.red;
|
|
39616
|
+
const fixable = cat.fixable ? source_default.dim(" [fixable]") : "";
|
|
39617
|
+
console.log(` ${name.padEnd(20)} ${scoreColor(String(cat.score).padStart(2))}/10${fixable}`);
|
|
39618
|
+
if (cat.issues.length > 0) {
|
|
39619
|
+
for (const issue of cat.issues) {
|
|
39620
|
+
console.log(source_default.dim(` - ${issue}`));
|
|
39621
|
+
}
|
|
39622
|
+
}
|
|
39623
|
+
}
|
|
39624
|
+
if (result.appliedFixes && result.appliedFixes.length > 0) {
|
|
39625
|
+
console.log();
|
|
39626
|
+
console.log(source_default.bold.green("Applied Fixes:"));
|
|
39627
|
+
for (const fix of result.appliedFixes) {
|
|
39628
|
+
console.log(source_default.green(` + ${fix}`));
|
|
39629
|
+
}
|
|
39630
|
+
if (result.outputPath) {
|
|
39631
|
+
console.log(source_default.green(` Output: ${result.outputPath}`));
|
|
39632
|
+
}
|
|
39633
|
+
}
|
|
39634
|
+
if (result.verificationScore !== void 0) {
|
|
39635
|
+
console.log();
|
|
39636
|
+
console.log(source_default.bold(`Verification Score: ${result.verificationScore}/10`));
|
|
39637
|
+
}
|
|
39638
|
+
if (fb.recommendations.length > 0) {
|
|
39639
|
+
console.log();
|
|
39640
|
+
console.log(source_default.bold("Recommendations:"));
|
|
39641
|
+
for (const rec of fb.recommendations) {
|
|
39642
|
+
console.log(source_default.dim(` * ${rec}`));
|
|
39643
|
+
}
|
|
39644
|
+
}
|
|
39645
|
+
console.log();
|
|
39646
|
+
} catch (error) {
|
|
39647
|
+
exitWithError(generalError(error instanceof Error ? error.message : "Video review failed"));
|
|
39648
|
+
}
|
|
39649
|
+
});
|
|
39650
|
+
}
|
|
39651
|
+
var init_ai_review = __esm({
|
|
39652
|
+
"src/commands/ai-review.ts"() {
|
|
39653
|
+
"use strict";
|
|
39654
|
+
init_source();
|
|
39655
|
+
init_ora();
|
|
39656
|
+
init_dist2();
|
|
39657
|
+
init_api_key();
|
|
39658
|
+
init_exec_safe();
|
|
39659
|
+
init_output();
|
|
39660
|
+
init_validate();
|
|
39661
|
+
}
|
|
39662
|
+
});
|
|
39663
|
+
|
|
39664
|
+
// src/commands/_shared/video-providers.ts
|
|
39665
|
+
async function generateVideoWithRetryGrok(grok, segment, options, maxRetries, onProgress) {
|
|
39666
|
+
const prompt3 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
|
|
39667
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
39668
|
+
try {
|
|
39669
|
+
const result = await grok.generateVideo(prompt3, {
|
|
39670
|
+
prompt: prompt3,
|
|
39671
|
+
duration: options.duration,
|
|
39672
|
+
aspectRatio: options.aspectRatio,
|
|
39673
|
+
referenceImage: options.referenceImage
|
|
39674
|
+
});
|
|
39675
|
+
if (result.status !== "failed" && result.id) {
|
|
39676
|
+
return { requestId: result.id };
|
|
39677
|
+
}
|
|
39678
|
+
const providerErr = result.error || "Grok returned failed status";
|
|
39679
|
+
if (attempt < maxRetries) {
|
|
39680
|
+
onProgress?.(
|
|
39681
|
+
`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`
|
|
39682
|
+
);
|
|
39683
|
+
await sleep(RETRY_DELAY_MS);
|
|
39684
|
+
} else {
|
|
39685
|
+
console.error(source_default.dim(`
|
|
39686
|
+
[Grok error: ${providerErr}]`));
|
|
39687
|
+
}
|
|
39688
|
+
} catch (err) {
|
|
39689
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
39690
|
+
if (attempt < maxRetries) {
|
|
39691
|
+
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
39692
|
+
await sleep(RETRY_DELAY_MS);
|
|
39693
|
+
} else {
|
|
39694
|
+
console.error(source_default.dim(`
|
|
39695
|
+
[Grok error: ${errMsg}]`));
|
|
39696
|
+
}
|
|
39697
|
+
}
|
|
39698
|
+
}
|
|
39699
|
+
return null;
|
|
39700
|
+
}
|
|
39701
|
+
async function generateVideoWithRetryKling(kling, segment, options, maxRetries, onProgress) {
|
|
39702
|
+
const prompt3 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
|
|
39703
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
39704
|
+
try {
|
|
39705
|
+
const result = await kling.generateVideo(prompt3, {
|
|
39706
|
+
prompt: prompt3,
|
|
39707
|
+
// Pass reference image (base64 or URL) - KlingProvider handles v1.5 fallback for base64
|
|
39708
|
+
referenceImage: options.referenceImage,
|
|
39709
|
+
duration: options.duration,
|
|
39710
|
+
aspectRatio: options.aspectRatio,
|
|
39711
|
+
mode: "std"
|
|
39712
|
+
// std mode for faster generation
|
|
39713
|
+
});
|
|
39714
|
+
if (result.status !== "failed" && result.id) {
|
|
39715
|
+
return {
|
|
39716
|
+
taskId: result.id,
|
|
39717
|
+
type: options.referenceImage ? "image2video" : "text2video"
|
|
39718
|
+
};
|
|
39719
|
+
}
|
|
39720
|
+
const providerErr = result.error || "Kling returned failed status";
|
|
39721
|
+
if (attempt < maxRetries) {
|
|
39722
|
+
onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
39723
|
+
await sleep(RETRY_DELAY_MS);
|
|
39724
|
+
} else {
|
|
39725
|
+
console.error(source_default.dim(`
|
|
39726
|
+
[Kling error: ${providerErr}]`));
|
|
39727
|
+
}
|
|
39728
|
+
} catch (err) {
|
|
39729
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
39730
|
+
if (attempt < maxRetries) {
|
|
39731
|
+
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
39732
|
+
await sleep(RETRY_DELAY_MS);
|
|
39733
|
+
} else {
|
|
39734
|
+
console.error(source_default.dim(`
|
|
39735
|
+
[Kling error: ${errMsg}]`));
|
|
39736
|
+
}
|
|
39737
|
+
}
|
|
39738
|
+
}
|
|
39739
|
+
return null;
|
|
39740
|
+
}
|
|
39741
|
+
async function generateVideoWithRetryRunway(runway, segment, referenceImage, options, maxRetries, onProgress) {
|
|
39742
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
39743
|
+
try {
|
|
39744
|
+
const result = await runway.generateVideo(segment.visuals, {
|
|
39745
|
+
prompt: segment.visuals,
|
|
39746
|
+
referenceImage,
|
|
39747
|
+
duration: options.duration,
|
|
39748
|
+
aspectRatio: options.aspectRatio
|
|
39749
|
+
});
|
|
39750
|
+
if (result.status !== "failed" && result.id) {
|
|
39751
|
+
return { taskId: result.id };
|
|
39752
|
+
}
|
|
39753
|
+
const providerErr = result.error || "Runway returned failed status";
|
|
39754
|
+
if (attempt < maxRetries) {
|
|
39755
|
+
onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
39756
|
+
await sleep(RETRY_DELAY_MS);
|
|
39757
|
+
} else {
|
|
39758
|
+
console.error(source_default.dim(`
|
|
39759
|
+
[Runway error: ${providerErr}]`));
|
|
39760
|
+
}
|
|
39761
|
+
} catch (err) {
|
|
39762
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
39763
|
+
if (attempt < maxRetries) {
|
|
39764
|
+
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
39765
|
+
await sleep(RETRY_DELAY_MS);
|
|
39766
|
+
} else {
|
|
39767
|
+
console.error(source_default.dim(`
|
|
39768
|
+
[Runway error: ${errMsg}]`));
|
|
39769
|
+
}
|
|
39770
|
+
}
|
|
39771
|
+
}
|
|
39772
|
+
return null;
|
|
39773
|
+
}
|
|
39774
|
+
async function generateVideoWithRetryVeo(gemini, segment, options, maxRetries, onProgress) {
|
|
39775
|
+
const prompt3 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
|
|
39776
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
39777
|
+
try {
|
|
39778
|
+
const result = await gemini.generateVideo(prompt3, {
|
|
39779
|
+
prompt: prompt3,
|
|
39780
|
+
referenceImage: options.referenceImage,
|
|
39781
|
+
duration: options.duration,
|
|
39782
|
+
aspectRatio: options.aspectRatio,
|
|
39783
|
+
model: "veo-3.1-fast-generate-preview"
|
|
39784
|
+
});
|
|
39785
|
+
if (result.status !== "failed" && result.id) {
|
|
39786
|
+
return { operationName: result.id };
|
|
39787
|
+
}
|
|
39788
|
+
const providerErr = result.error || "Veo returned failed status";
|
|
39789
|
+
if (attempt < maxRetries) {
|
|
39790
|
+
onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
39791
|
+
await sleep(RETRY_DELAY_MS);
|
|
39792
|
+
} else {
|
|
39793
|
+
console.error(source_default.dim(`
|
|
39794
|
+
[Veo error: ${providerErr}]`));
|
|
39795
|
+
}
|
|
39796
|
+
} catch (err) {
|
|
39797
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
39798
|
+
if (attempt < maxRetries) {
|
|
39799
|
+
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
39800
|
+
await sleep(RETRY_DELAY_MS);
|
|
39801
|
+
} else {
|
|
39802
|
+
console.error(source_default.dim(`
|
|
39803
|
+
[Veo error: ${errMsg}]`));
|
|
39804
|
+
}
|
|
39805
|
+
}
|
|
39806
|
+
}
|
|
39807
|
+
return null;
|
|
39808
|
+
}
|
|
39809
|
+
var init_video_providers = __esm({
|
|
39810
|
+
"src/commands/_shared/video-providers.ts"() {
|
|
39811
|
+
"use strict";
|
|
39812
|
+
init_source();
|
|
39813
|
+
init_video_utils();
|
|
39814
|
+
}
|
|
39815
|
+
});
|
|
39816
|
+
|
|
39817
|
+
// src/commands/ai-script-pipeline.ts
|
|
39818
|
+
import { readFile as readFile24, writeFile as writeFile31, unlink as unlink2, rename as rename3 } from "node:fs/promises";
|
|
39819
|
+
import { resolve as resolve34, extname as extname8 } from "node:path";
|
|
39820
|
+
import { existsSync as existsSync26 } from "node:fs";
|
|
39821
|
+
async function executeRegenerateScene(options) {
|
|
39822
|
+
const result = {
|
|
39823
|
+
success: false,
|
|
39824
|
+
regeneratedScenes: [],
|
|
39825
|
+
failedScenes: []
|
|
39826
|
+
};
|
|
39827
|
+
try {
|
|
39828
|
+
const outputDir = resolve34(process.cwd(), options.projectDir);
|
|
39829
|
+
if (!existsSync26(outputDir)) {
|
|
39830
|
+
return { ...result, error: `Project directory not found: ${outputDir}` };
|
|
39831
|
+
}
|
|
39832
|
+
const yamlPath = resolve34(outputDir, "storyboard.yaml");
|
|
39833
|
+
const jsonPath = resolve34(outputDir, "storyboard.json");
|
|
39834
|
+
const storyboardPath = existsSync26(yamlPath) ? yamlPath : existsSync26(jsonPath) ? jsonPath : null;
|
|
39835
|
+
if (!storyboardPath) {
|
|
39836
|
+
return { ...result, error: `Storyboard not found in: ${outputDir} (expected storyboard.yaml or storyboard.json)` };
|
|
39837
|
+
}
|
|
39838
|
+
const storyboardContent = await readFile24(storyboardPath, "utf-8");
|
|
39839
|
+
const segments = storyboardPath.endsWith(".yaml") ? (0, import_yaml5.parse)(storyboardContent).scenes : JSON.parse(storyboardContent);
|
|
39840
|
+
for (const sceneNum of options.scenes) {
|
|
39841
|
+
if (sceneNum < 1 || sceneNum > segments.length) {
|
|
39842
|
+
return { ...result, error: `Scene ${sceneNum} does not exist. Storyboard has ${segments.length} scenes.` };
|
|
39843
|
+
}
|
|
39844
|
+
}
|
|
39845
|
+
const regenerateVideo = options.videoOnly || !options.narrationOnly && !options.imageOnly;
|
|
39846
|
+
const regenerateNarration = options.narrationOnly || !options.videoOnly && !options.imageOnly;
|
|
39847
|
+
const regenerateImage = options.imageOnly || !options.videoOnly && !options.narrationOnly;
|
|
39848
|
+
let videoApiKey;
|
|
39849
|
+
if (regenerateVideo) {
|
|
39850
|
+
const generatorKeyMap = {
|
|
39851
|
+
grok: { envVar: "XAI_API_KEY", name: "xAI (Grok)" },
|
|
39852
|
+
kling: { envVar: "KLING_API_KEY", name: "Kling" },
|
|
39853
|
+
runway: { envVar: "RUNWAY_API_SECRET", name: "Runway" },
|
|
39854
|
+
veo: { envVar: "GOOGLE_API_KEY", name: "Google (Veo)" }
|
|
39855
|
+
};
|
|
39856
|
+
const generator = options.generator || "grok";
|
|
39857
|
+
const genInfo = generatorKeyMap[generator];
|
|
39858
|
+
if (!genInfo) {
|
|
39859
|
+
return { ...result, error: `Invalid generator: ${generator}. Available: ${Object.keys(generatorKeyMap).join(", ")}` };
|
|
39860
|
+
}
|
|
39861
|
+
videoApiKey = await getApiKey(genInfo.envVar, genInfo.name) ?? void 0;
|
|
39862
|
+
if (!videoApiKey) {
|
|
39863
|
+
return { ...result, error: `${genInfo.name} API key required. Run 'vibe setup' or set ${genInfo.envVar} in .env` };
|
|
39864
|
+
}
|
|
39865
|
+
}
|
|
39866
|
+
let imageApiKey;
|
|
39867
|
+
if (regenerateImage) {
|
|
39868
|
+
const imageProvider = options.imageProvider || "openai";
|
|
39869
|
+
const imageKeyMap = {
|
|
39870
|
+
openai: { envVar: "OPENAI_API_KEY", name: "OpenAI" },
|
|
39871
|
+
gemini: { envVar: "GOOGLE_API_KEY", name: "Google" },
|
|
39872
|
+
grok: { envVar: "XAI_API_KEY", name: "xAI" }
|
|
39873
|
+
};
|
|
39874
|
+
const info = imageKeyMap[imageProvider];
|
|
39875
|
+
if (!info) {
|
|
39876
|
+
return { ...result, error: `Invalid imageProvider: ${imageProvider}` };
|
|
39877
|
+
}
|
|
39878
|
+
imageApiKey = await getApiKey(info.envVar, info.name) ?? void 0;
|
|
39879
|
+
if (!imageApiKey) {
|
|
39880
|
+
return { ...result, error: `${info.name} API key required. Run 'vibe setup' or set ${info.envVar} in .env` };
|
|
39881
|
+
}
|
|
39882
|
+
}
|
|
39883
|
+
let elevenlabsApiKey;
|
|
39884
|
+
if (regenerateNarration) {
|
|
39885
|
+
elevenlabsApiKey = await getApiKey("ELEVENLABS_API_KEY", "ElevenLabs") ?? void 0;
|
|
39886
|
+
if (!elevenlabsApiKey) {
|
|
39887
|
+
return { ...result, error: "ElevenLabs API key required. Run 'vibe setup' or set ELEVENLABS_API_KEY in .env" };
|
|
39888
|
+
}
|
|
39889
|
+
}
|
|
39890
|
+
let storyboardMutated = false;
|
|
39891
|
+
for (const sceneNum of options.scenes) {
|
|
39892
|
+
const segment = segments[sceneNum - 1];
|
|
39893
|
+
const narrationPath = resolve34(outputDir, `narration-${sceneNum}.mp3`);
|
|
39894
|
+
const imagePath = resolve34(outputDir, `scene-${sceneNum}.png`);
|
|
39895
|
+
const videoPath = resolve34(outputDir, `scene-${sceneNum}.mp4`);
|
|
39896
|
+
let sceneFailed = false;
|
|
39897
|
+
if (regenerateNarration && elevenlabsApiKey) {
|
|
39898
|
+
options.onProgress?.(`Scene ${sceneNum}: regenerating narration...`);
|
|
39899
|
+
const elevenlabs = new ElevenLabsProvider();
|
|
39900
|
+
await elevenlabs.initialize({ apiKey: elevenlabsApiKey });
|
|
39901
|
+
const narrationText = segment.narration || segment.description;
|
|
39902
|
+
const ttsResult = await elevenlabs.textToSpeech(narrationText, {
|
|
39903
|
+
voiceId: options.voice
|
|
39904
|
+
});
|
|
39905
|
+
if (ttsResult.success && ttsResult.audioBuffer) {
|
|
39906
|
+
await writeFile31(narrationPath, ttsResult.audioBuffer);
|
|
39907
|
+
segment.duration = await getAudioDuration(narrationPath);
|
|
39908
|
+
storyboardMutated = true;
|
|
39909
|
+
} else {
|
|
39910
|
+
sceneFailed = true;
|
|
39911
|
+
}
|
|
39912
|
+
}
|
|
39913
|
+
if (!sceneFailed && regenerateImage && imageApiKey) {
|
|
39914
|
+
options.onProgress?.(`Scene ${sceneNum}: regenerating image...`);
|
|
39915
|
+
const imageProvider = options.imageProvider || "openai";
|
|
39916
|
+
const characterDesc = segment.characterDescription || segments[0]?.characterDescription;
|
|
39917
|
+
let imagePrompt = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
|
|
39918
|
+
if (characterDesc) {
|
|
39919
|
+
imagePrompt = `${imagePrompt}
|
|
39920
|
+
|
|
39921
|
+
IMPORTANT - Character appearance must match exactly: ${characterDesc}`;
|
|
39922
|
+
}
|
|
39923
|
+
let referenceImageBuffer;
|
|
39924
|
+
const refSceneNum = options.referenceScene;
|
|
39925
|
+
if (refSceneNum && refSceneNum >= 1 && refSceneNum <= segments.length && refSceneNum !== sceneNum) {
|
|
39926
|
+
const refImagePath = resolve34(outputDir, `scene-${refSceneNum}.png`);
|
|
39927
|
+
if (existsSync26(refImagePath)) {
|
|
39928
|
+
referenceImageBuffer = await readFile24(refImagePath);
|
|
39929
|
+
}
|
|
39930
|
+
} else if (!refSceneNum) {
|
|
39931
|
+
for (let i = 1; i <= segments.length; i++) {
|
|
39932
|
+
if (i !== sceneNum) {
|
|
39933
|
+
const otherImagePath = resolve34(outputDir, `scene-${i}.png`);
|
|
39934
|
+
if (existsSync26(otherImagePath)) {
|
|
39935
|
+
referenceImageBuffer = await readFile24(otherImagePath);
|
|
39936
|
+
break;
|
|
39937
|
+
}
|
|
39938
|
+
}
|
|
39918
39939
|
}
|
|
39919
39940
|
}
|
|
39920
|
-
|
|
39921
|
-
|
|
39922
|
-
|
|
39923
|
-
|
|
39924
|
-
|
|
39925
|
-
|
|
39941
|
+
const dalleImageSizes = {
|
|
39942
|
+
"16:9": "1536x1024",
|
|
39943
|
+
"9:16": "1024x1536",
|
|
39944
|
+
"1:1": "1024x1024"
|
|
39945
|
+
};
|
|
39946
|
+
let imageBuffer;
|
|
39947
|
+
let imageUrl;
|
|
39948
|
+
if (imageProvider === "openai") {
|
|
39949
|
+
const openaiImage = new OpenAIImageProvider();
|
|
39950
|
+
await openaiImage.initialize({ apiKey: imageApiKey });
|
|
39951
|
+
const imageResult = await openaiImage.generateImage(imagePrompt, {
|
|
39952
|
+
size: dalleImageSizes[options.aspectRatio || "16:9"] || "1536x1024",
|
|
39953
|
+
quality: "standard"
|
|
39954
|
+
});
|
|
39955
|
+
if (imageResult.success && imageResult.images?.[0]) {
|
|
39956
|
+
const img = imageResult.images[0];
|
|
39957
|
+
if (img.base64) imageBuffer = Buffer.from(img.base64, "base64");
|
|
39958
|
+
else if (img.url) imageUrl = img.url;
|
|
39959
|
+
}
|
|
39960
|
+
} else if (imageProvider === "gemini") {
|
|
39961
|
+
const gemini = new GeminiProvider();
|
|
39962
|
+
await gemini.initialize({ apiKey: imageApiKey });
|
|
39963
|
+
if (referenceImageBuffer) {
|
|
39964
|
+
const simplifiedVisuals = segment.visuals.split(/[,.]/).find(
|
|
39965
|
+
(part) => part.includes("standing") || part.includes("sitting") || part.includes("walking") || part.includes("lying") || part.includes("reaching") || part.includes("looking") || part.includes("working") || part.includes("coding") || part.includes("typing")
|
|
39966
|
+
) || segment.visuals.split(".")[0];
|
|
39967
|
+
const editPrompt = `Generate a new image showing the SAME SINGLE person from the reference image in a new scene.
|
|
39968
|
+
|
|
39969
|
+
REFERENCE: Look at the person in the reference image - their face, hair, build, and overall appearance.
|
|
39970
|
+
|
|
39971
|
+
NEW SCENE: ${simplifiedVisuals}
|
|
39972
|
+
|
|
39973
|
+
CRITICAL RULES:
|
|
39974
|
+
1. Show ONLY ONE person - the exact same individual from the reference image
|
|
39975
|
+
2. The person must have the IDENTICAL face, hair style, and body type
|
|
39976
|
+
3. Do NOT show multiple people or duplicate the character
|
|
39977
|
+
4. Create a single moment in time, one pose, one action
|
|
39978
|
+
5. Match the art style and quality of the reference image
|
|
39979
|
+
|
|
39980
|
+
Generate the single-person scene image now.`;
|
|
39981
|
+
const imageResult = await gemini.editImage([referenceImageBuffer], editPrompt, {
|
|
39982
|
+
aspectRatio: options.aspectRatio || "16:9"
|
|
39983
|
+
});
|
|
39984
|
+
if (imageResult.success && imageResult.images?.[0]?.base64) {
|
|
39985
|
+
imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
|
|
39986
|
+
}
|
|
39987
|
+
} else {
|
|
39988
|
+
const imageResult = await gemini.generateImage(imagePrompt, {
|
|
39989
|
+
aspectRatio: options.aspectRatio || "16:9"
|
|
39990
|
+
});
|
|
39991
|
+
if (imageResult.success && imageResult.images?.[0]?.base64) {
|
|
39992
|
+
imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
|
|
39993
|
+
}
|
|
39994
|
+
}
|
|
39995
|
+
} else if (imageProvider === "grok") {
|
|
39996
|
+
const grok = new GrokProvider();
|
|
39997
|
+
await grok.initialize({ apiKey: imageApiKey });
|
|
39998
|
+
const imageResult = await grok.generateImage(imagePrompt, {
|
|
39999
|
+
aspectRatio: options.aspectRatio || "16:9"
|
|
40000
|
+
});
|
|
40001
|
+
if (imageResult.success && imageResult.images?.[0]) {
|
|
40002
|
+
const img = imageResult.images[0];
|
|
40003
|
+
if (img.base64) imageBuffer = Buffer.from(img.base64, "base64");
|
|
40004
|
+
else if (img.url) imageUrl = img.url;
|
|
40005
|
+
}
|
|
39926
40006
|
}
|
|
39927
|
-
if (
|
|
39928
|
-
|
|
40007
|
+
if (imageBuffer) {
|
|
40008
|
+
await writeFile31(imagePath, imageBuffer);
|
|
40009
|
+
} else if (imageUrl) {
|
|
40010
|
+
const response = await fetch(imageUrl);
|
|
40011
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
40012
|
+
await writeFile31(imagePath, buffer);
|
|
40013
|
+
} else {
|
|
40014
|
+
sceneFailed = true;
|
|
39929
40015
|
}
|
|
39930
40016
|
}
|
|
39931
|
-
if (
|
|
39932
|
-
|
|
39933
|
-
|
|
40017
|
+
if (sceneFailed) {
|
|
40018
|
+
result.failedScenes.push(sceneNum);
|
|
40019
|
+
continue;
|
|
39934
40020
|
}
|
|
39935
|
-
if (
|
|
39936
|
-
|
|
39937
|
-
|
|
39938
|
-
|
|
39939
|
-
|
|
40021
|
+
if (regenerateVideo && videoApiKey) {
|
|
40022
|
+
options.onProgress?.(`Scene ${sceneNum}: regenerating video (${options.generator || "grok"})...`);
|
|
40023
|
+
if (!existsSync26(imagePath)) {
|
|
40024
|
+
result.failedScenes.push(sceneNum);
|
|
40025
|
+
continue;
|
|
40026
|
+
}
|
|
40027
|
+
const imageBuffer = await readFile24(imagePath);
|
|
40028
|
+
const videoDuration = segment.duration > 5 ? 10 : 5;
|
|
40029
|
+
const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
|
|
40030
|
+
if (options.generator === "grok") {
|
|
40031
|
+
const grok = new GrokProvider();
|
|
40032
|
+
await grok.initialize({ apiKey: videoApiKey });
|
|
40033
|
+
const ext = extname8(imagePath).toLowerCase().slice(1);
|
|
40034
|
+
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
40035
|
+
const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
40036
|
+
const grokDuration = Math.min(15, Math.max(1, segment.duration));
|
|
40037
|
+
const taskResult = await generateVideoWithRetryGrok(
|
|
40038
|
+
grok,
|
|
40039
|
+
segment,
|
|
40040
|
+
{
|
|
40041
|
+
duration: grokDuration,
|
|
40042
|
+
aspectRatio: options.aspectRatio || "16:9",
|
|
40043
|
+
referenceImage
|
|
40044
|
+
},
|
|
40045
|
+
maxRetries
|
|
40046
|
+
);
|
|
40047
|
+
if (taskResult) {
|
|
40048
|
+
try {
|
|
40049
|
+
const waitResult = await grok.waitForCompletion(taskResult.requestId, void 0, 3e5);
|
|
40050
|
+
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
40051
|
+
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
40052
|
+
await writeFile31(videoPath, buffer);
|
|
40053
|
+
const targetDuration = segment.duration;
|
|
40054
|
+
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
40055
|
+
if (actualVideoDuration < targetDuration - 0.1) {
|
|
40056
|
+
const extendedPath = resolve34(outputDir, `scene-${sceneNum}-extended.mp4`);
|
|
40057
|
+
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
40058
|
+
await unlink2(videoPath);
|
|
40059
|
+
await rename3(extendedPath, videoPath);
|
|
40060
|
+
}
|
|
40061
|
+
result.regeneratedScenes.push(sceneNum);
|
|
40062
|
+
} else {
|
|
40063
|
+
logSceneFailure("Grok", `scene ${sceneNum}`, waitResult);
|
|
40064
|
+
result.failedScenes.push(sceneNum);
|
|
40065
|
+
}
|
|
40066
|
+
} catch (err) {
|
|
40067
|
+
logSceneFailure("Grok", `scene ${sceneNum}`, err);
|
|
40068
|
+
result.failedScenes.push(sceneNum);
|
|
40069
|
+
}
|
|
40070
|
+
} else {
|
|
40071
|
+
result.failedScenes.push(sceneNum);
|
|
40072
|
+
}
|
|
40073
|
+
} else if (options.generator === "veo") {
|
|
40074
|
+
const veo = new GeminiProvider();
|
|
40075
|
+
await veo.initialize({ apiKey: videoApiKey });
|
|
40076
|
+
const ext = extname8(imagePath).toLowerCase().slice(1);
|
|
40077
|
+
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
40078
|
+
const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
40079
|
+
const veoDuration = segment.duration > 6 ? 8 : segment.duration > 4 ? 6 : 4;
|
|
40080
|
+
const taskResult = await generateVideoWithRetryVeo(
|
|
40081
|
+
veo,
|
|
40082
|
+
segment,
|
|
40083
|
+
{
|
|
40084
|
+
duration: veoDuration,
|
|
40085
|
+
aspectRatio: options.aspectRatio || "16:9",
|
|
40086
|
+
referenceImage
|
|
40087
|
+
},
|
|
40088
|
+
maxRetries
|
|
40089
|
+
);
|
|
40090
|
+
if (taskResult) {
|
|
40091
|
+
try {
|
|
40092
|
+
const waitResult = await veo.waitForVideoCompletion(taskResult.operationName, void 0, 3e5);
|
|
40093
|
+
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
40094
|
+
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
40095
|
+
await writeFile31(videoPath, buffer);
|
|
40096
|
+
const targetDuration = segment.duration;
|
|
40097
|
+
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
40098
|
+
if (actualVideoDuration < targetDuration - 0.1) {
|
|
40099
|
+
const extendedPath = resolve34(outputDir, `scene-${sceneNum}-extended.mp4`);
|
|
40100
|
+
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
40101
|
+
await unlink2(videoPath);
|
|
40102
|
+
await rename3(extendedPath, videoPath);
|
|
40103
|
+
}
|
|
40104
|
+
result.regeneratedScenes.push(sceneNum);
|
|
40105
|
+
} else {
|
|
40106
|
+
logSceneFailure("Veo", `scene ${sceneNum}`, waitResult);
|
|
40107
|
+
result.failedScenes.push(sceneNum);
|
|
40108
|
+
}
|
|
40109
|
+
} catch (err) {
|
|
40110
|
+
logSceneFailure("Veo", `scene ${sceneNum}`, err);
|
|
40111
|
+
result.failedScenes.push(sceneNum);
|
|
40112
|
+
}
|
|
40113
|
+
} else {
|
|
40114
|
+
result.failedScenes.push(sceneNum);
|
|
40115
|
+
}
|
|
40116
|
+
} else if (options.generator === "kling" || !options.generator) {
|
|
40117
|
+
const kling = new KlingProvider();
|
|
40118
|
+
await kling.initialize({ apiKey: videoApiKey });
|
|
40119
|
+
if (!kling.isConfigured()) {
|
|
40120
|
+
result.failedScenes.push(sceneNum);
|
|
40121
|
+
continue;
|
|
40122
|
+
}
|
|
40123
|
+
const imgbbApiKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
|
|
40124
|
+
let imageUrl;
|
|
40125
|
+
if (imgbbApiKey) {
|
|
40126
|
+
const uploadResult = await uploadToImgbb(imageBuffer, imgbbApiKey);
|
|
40127
|
+
if (uploadResult.success && uploadResult.url) {
|
|
40128
|
+
imageUrl = uploadResult.url;
|
|
40129
|
+
}
|
|
40130
|
+
}
|
|
40131
|
+
const taskResult = await generateVideoWithRetryKling(
|
|
40132
|
+
kling,
|
|
40133
|
+
segment,
|
|
40134
|
+
{
|
|
40135
|
+
duration: videoDuration,
|
|
40136
|
+
aspectRatio: options.aspectRatio || "16:9",
|
|
40137
|
+
referenceImage: imageUrl
|
|
40138
|
+
},
|
|
40139
|
+
maxRetries
|
|
40140
|
+
);
|
|
40141
|
+
if (taskResult) {
|
|
40142
|
+
try {
|
|
40143
|
+
const waitResult = await kling.waitForCompletion(taskResult.taskId, taskResult.type, void 0, 6e5);
|
|
40144
|
+
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
40145
|
+
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
40146
|
+
await writeFile31(videoPath, buffer);
|
|
40147
|
+
await extendVideoToTarget(
|
|
40148
|
+
videoPath,
|
|
40149
|
+
segment.duration,
|
|
40150
|
+
outputDir,
|
|
40151
|
+
`Scene ${sceneNum}`,
|
|
40152
|
+
{
|
|
40153
|
+
kling,
|
|
40154
|
+
videoId: waitResult.videoId,
|
|
40155
|
+
onProgress: options.onProgress
|
|
40156
|
+
}
|
|
40157
|
+
);
|
|
40158
|
+
result.regeneratedScenes.push(sceneNum);
|
|
40159
|
+
} else {
|
|
40160
|
+
logSceneFailure("Kling", `scene ${sceneNum}`, waitResult);
|
|
40161
|
+
result.failedScenes.push(sceneNum);
|
|
40162
|
+
}
|
|
40163
|
+
} catch (err) {
|
|
40164
|
+
logSceneFailure("Kling", `scene ${sceneNum}`, err);
|
|
40165
|
+
result.failedScenes.push(sceneNum);
|
|
40166
|
+
}
|
|
40167
|
+
} else {
|
|
40168
|
+
result.failedScenes.push(sceneNum);
|
|
40169
|
+
}
|
|
40170
|
+
} else {
|
|
40171
|
+
const runway = new RunwayProvider();
|
|
40172
|
+
await runway.initialize({ apiKey: videoApiKey });
|
|
40173
|
+
const ext = extname8(imagePath).toLowerCase().slice(1);
|
|
40174
|
+
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
40175
|
+
const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
40176
|
+
const aspectRatio = options.aspectRatio === "1:1" ? "16:9" : options.aspectRatio || "16:9";
|
|
40177
|
+
const taskResult = await generateVideoWithRetryRunway(
|
|
40178
|
+
runway,
|
|
40179
|
+
segment,
|
|
40180
|
+
referenceImage,
|
|
40181
|
+
{ duration: videoDuration, aspectRatio },
|
|
40182
|
+
maxRetries
|
|
40183
|
+
);
|
|
40184
|
+
if (taskResult) {
|
|
40185
|
+
try {
|
|
40186
|
+
const waitResult = await runway.waitForCompletion(taskResult.taskId, void 0, 3e5);
|
|
40187
|
+
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
40188
|
+
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
40189
|
+
await writeFile31(videoPath, buffer);
|
|
40190
|
+
const targetDuration = segment.duration;
|
|
40191
|
+
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
40192
|
+
if (actualVideoDuration < targetDuration - 0.1) {
|
|
40193
|
+
const extendedPath = resolve34(outputDir, `scene-${sceneNum}-extended.mp4`);
|
|
40194
|
+
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
40195
|
+
await unlink2(videoPath);
|
|
40196
|
+
await rename3(extendedPath, videoPath);
|
|
40197
|
+
}
|
|
40198
|
+
result.regeneratedScenes.push(sceneNum);
|
|
40199
|
+
} else {
|
|
40200
|
+
logSceneFailure("Runway", `scene ${sceneNum}`, waitResult);
|
|
40201
|
+
result.failedScenes.push(sceneNum);
|
|
40202
|
+
}
|
|
40203
|
+
} catch (err) {
|
|
40204
|
+
logSceneFailure("Runway", `scene ${sceneNum}`, err);
|
|
40205
|
+
result.failedScenes.push(sceneNum);
|
|
40206
|
+
}
|
|
40207
|
+
} else {
|
|
40208
|
+
result.failedScenes.push(sceneNum);
|
|
40209
|
+
}
|
|
39940
40210
|
}
|
|
40211
|
+
} else if (!sceneFailed) {
|
|
40212
|
+
result.regeneratedScenes.push(sceneNum);
|
|
39941
40213
|
}
|
|
39942
|
-
console.log();
|
|
39943
|
-
} catch (error) {
|
|
39944
|
-
exitWithError(generalError(error instanceof Error ? error.message : "Video review failed"));
|
|
39945
40214
|
}
|
|
39946
|
-
|
|
40215
|
+
if (storyboardMutated) {
|
|
40216
|
+
let currentTime = 0;
|
|
40217
|
+
for (const segment of segments) {
|
|
40218
|
+
segment.startTime = currentTime;
|
|
40219
|
+
currentTime += segment.duration;
|
|
40220
|
+
}
|
|
40221
|
+
const serialized = storyboardPath.endsWith(".yaml") ? (0, import_yaml5.stringify)({ scenes: segments }, { indent: 2 }) : JSON.stringify(segments, null, 2);
|
|
40222
|
+
await writeFile31(storyboardPath, serialized, "utf-8");
|
|
40223
|
+
}
|
|
40224
|
+
result.success = result.failedScenes.length === 0;
|
|
40225
|
+
return result;
|
|
40226
|
+
} catch (error) {
|
|
40227
|
+
return {
|
|
40228
|
+
...result,
|
|
40229
|
+
error: error instanceof Error ? error.message : String(error)
|
|
40230
|
+
};
|
|
40231
|
+
}
|
|
39947
40232
|
}
|
|
39948
|
-
var
|
|
39949
|
-
|
|
40233
|
+
var import_yaml5;
|
|
40234
|
+
var init_ai_script_pipeline = __esm({
|
|
40235
|
+
"src/commands/ai-script-pipeline.ts"() {
|
|
39950
40236
|
"use strict";
|
|
39951
|
-
|
|
39952
|
-
init_ora();
|
|
40237
|
+
import_yaml5 = __toESM(require_dist(), 1);
|
|
39953
40238
|
init_dist2();
|
|
39954
40239
|
init_api_key();
|
|
39955
|
-
|
|
39956
|
-
|
|
39957
|
-
|
|
40240
|
+
init_config();
|
|
40241
|
+
init_audio();
|
|
40242
|
+
init_ai_helpers();
|
|
40243
|
+
init_video_utils();
|
|
40244
|
+
init_video_providers();
|
|
39958
40245
|
}
|
|
39959
40246
|
});
|
|
39960
40247
|
|
|
39961
40248
|
// src/commands/ai-highlights.ts
|
|
39962
40249
|
import { readFile as readFile26, writeFile as writeFile33, mkdir as mkdir16 } from "node:fs/promises";
|
|
39963
|
-
import { resolve as resolve36, dirname as dirname12, basename as basename9, extname as
|
|
40250
|
+
import { resolve as resolve36, dirname as dirname12, basename as basename9, extname as extname9 } from "node:path";
|
|
39964
40251
|
import { existsSync as existsSync28 } from "node:fs";
|
|
39965
40252
|
function filterHighlights(highlights, options) {
|
|
39966
40253
|
let filtered = highlights.filter((h) => h.confidence >= options.threshold);
|
|
@@ -40004,7 +40291,7 @@ async function executeHighlights(options) {
|
|
|
40004
40291
|
if (!existsSync28(absPath)) {
|
|
40005
40292
|
return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
|
|
40006
40293
|
}
|
|
40007
|
-
const ext =
|
|
40294
|
+
const ext = extname9(absPath).toLowerCase();
|
|
40008
40295
|
const videoExtensions = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
|
|
40009
40296
|
const isVideo = videoExtensions.includes(ext);
|
|
40010
40297
|
const targetDuration = options.duration;
|
|
@@ -40341,7 +40628,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
40341
40628
|
};
|
|
40342
40629
|
for (let i = 0; i < selectedHighlights.length; i++) {
|
|
40343
40630
|
const h = selectedHighlights[i];
|
|
40344
|
-
const baseName = basename9(absPath,
|
|
40631
|
+
const baseName = basename9(absPath, extname9(absPath));
|
|
40345
40632
|
const outputPath = resolve36(outputDir, `${baseName}-short-${i + 1}.mp4`);
|
|
40346
40633
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
40347
40634
|
"-v",
|
|
@@ -40440,7 +40727,7 @@ function registerHighlightsCommands(aiCommand) {
|
|
|
40440
40727
|
});
|
|
40441
40728
|
return;
|
|
40442
40729
|
}
|
|
40443
|
-
const ext =
|
|
40730
|
+
const ext = extname9(absPath).toLowerCase();
|
|
40444
40731
|
const videoExtensions = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
|
|
40445
40732
|
const isVideo = videoExtensions.includes(ext);
|
|
40446
40733
|
console.log();
|
|
@@ -40918,11 +41205,11 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
40918
41205
|
let outputPath;
|
|
40919
41206
|
if (shortCount === 1 && options.output) {
|
|
40920
41207
|
outputPath = resolve36(process.cwd(), options.output);
|
|
40921
|
-
if (!
|
|
41208
|
+
if (!extname9(outputPath)) {
|
|
40922
41209
|
outputPath += ".mp4";
|
|
40923
41210
|
}
|
|
40924
41211
|
} else {
|
|
40925
|
-
const baseName = basename9(absPath,
|
|
41212
|
+
const baseName = basename9(absPath, extname9(absPath));
|
|
40926
41213
|
outputPath = resolve36(outputDir, `${baseName}-short-${i + 1}.mp4`);
|
|
40927
41214
|
}
|
|
40928
41215
|
const parentDir = dirname12(outputPath);
|
|
@@ -51808,7 +52095,7 @@ var init_scene_audio_scan = __esm({
|
|
|
51808
52095
|
|
|
51809
52096
|
// src/commands/_shared/scene-audio-mux.ts
|
|
51810
52097
|
import { rename as rename4, unlink as unlink3 } from "node:fs/promises";
|
|
51811
|
-
import { resolve as resolve45, dirname as dirname15, extname as
|
|
52098
|
+
import { resolve as resolve45, dirname as dirname15, extname as extname12, basename as basename16 } from "node:path";
|
|
51812
52099
|
function buildAudioMuxFilter(audios) {
|
|
51813
52100
|
if (audios.length === 0) return null;
|
|
51814
52101
|
const labels = [];
|
|
@@ -51867,7 +52154,7 @@ async function muxAudioIntoVideo(opts) {
|
|
|
51867
52154
|
if (!filter) {
|
|
51868
52155
|
return { success: true, outputPath: opts.videoPath, audioCount: 0 };
|
|
51869
52156
|
}
|
|
51870
|
-
const ext =
|
|
52157
|
+
const ext = extname12(opts.videoPath) || `.${opts.format}`;
|
|
51871
52158
|
const tmpPath = resolve45(
|
|
51872
52159
|
dirname15(opts.videoPath),
|
|
51873
52160
|
`.${basename16(opts.videoPath, ext)}.muxing${ext}`
|
|
@@ -52525,12 +52812,22 @@ async function executeVideoGenerate(options) {
|
|
|
52525
52812
|
const key = apiKey || process.env[envKeyMap[provider] || ""];
|
|
52526
52813
|
if (!key) return { success: false, error: `${envKeyMap[provider]} required for ${provider}` };
|
|
52527
52814
|
let referenceImage;
|
|
52815
|
+
let referenceImageBuffer;
|
|
52816
|
+
let referenceImageMimeType;
|
|
52528
52817
|
if (image) {
|
|
52529
52818
|
const imagePath = resolve53(process.cwd(), image);
|
|
52530
52819
|
const imageBuffer = await readFile35(imagePath);
|
|
52531
52820
|
const ext = image.toLowerCase().split(".").pop();
|
|
52532
|
-
const mimeTypes2 = {
|
|
52821
|
+
const mimeTypes2 = {
|
|
52822
|
+
jpg: "image/jpeg",
|
|
52823
|
+
jpeg: "image/jpeg",
|
|
52824
|
+
png: "image/png",
|
|
52825
|
+
gif: "image/gif",
|
|
52826
|
+
webp: "image/webp"
|
|
52827
|
+
};
|
|
52533
52828
|
const mimeType = mimeTypes2[ext || "png"] || "image/png";
|
|
52829
|
+
referenceImageBuffer = imageBuffer;
|
|
52830
|
+
referenceImageMimeType = mimeType;
|
|
52534
52831
|
referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
52535
52832
|
}
|
|
52536
52833
|
if (provider === "seedance" || provider === "fal") {
|
|
@@ -52538,12 +52835,12 @@ async function executeVideoGenerate(options) {
|
|
|
52538
52835
|
await fal.initialize({ apiKey: key });
|
|
52539
52836
|
let falImage = referenceImage;
|
|
52540
52837
|
if (falImage && falImage.startsWith("data:")) {
|
|
52541
|
-
const
|
|
52542
|
-
|
|
52543
|
-
|
|
52544
|
-
|
|
52545
|
-
|
|
52546
|
-
falImage =
|
|
52838
|
+
const uploadHost = await resolveUploadHost();
|
|
52839
|
+
const upload = await uploadHost.uploadImage(referenceImageBuffer, {
|
|
52840
|
+
filename: image,
|
|
52841
|
+
mimeType: referenceImageMimeType
|
|
52842
|
+
});
|
|
52843
|
+
falImage = upload.url;
|
|
52547
52844
|
}
|
|
52548
52845
|
const model = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
|
|
52549
52846
|
const result = await fal.generateVideo(prompt3, {
|
|
@@ -52554,14 +52851,22 @@ async function executeVideoGenerate(options) {
|
|
|
52554
52851
|
negativePrompt: negative,
|
|
52555
52852
|
model
|
|
52556
52853
|
});
|
|
52557
|
-
if (result.status === "failed")
|
|
52854
|
+
if (result.status === "failed")
|
|
52855
|
+
return { success: false, error: result.error || "Seedance generation failed" };
|
|
52558
52856
|
let outputPath;
|
|
52559
52857
|
if (output && result.videoUrl) {
|
|
52560
52858
|
const buffer = await downloadVideo(result.videoUrl, key);
|
|
52561
52859
|
outputPath = resolve53(process.cwd(), output);
|
|
52562
52860
|
await writeFile39(outputPath, buffer);
|
|
52563
52861
|
}
|
|
52564
|
-
return {
|
|
52862
|
+
return {
|
|
52863
|
+
success: true,
|
|
52864
|
+
taskId: result.id,
|
|
52865
|
+
status: "completed",
|
|
52866
|
+
videoUrl: result.videoUrl,
|
|
52867
|
+
outputPath,
|
|
52868
|
+
provider: "seedance"
|
|
52869
|
+
};
|
|
52565
52870
|
} else if (provider === "runway") {
|
|
52566
52871
|
const runway = new RunwayProvider();
|
|
52567
52872
|
await runway.initialize({ apiKey: key });
|
|
@@ -52572,30 +52877,41 @@ async function executeVideoGenerate(options) {
|
|
|
52572
52877
|
aspectRatio: ratio,
|
|
52573
52878
|
seed
|
|
52574
52879
|
});
|
|
52575
|
-
if (result.status === "failed")
|
|
52576
|
-
|
|
52880
|
+
if (result.status === "failed")
|
|
52881
|
+
return { success: false, error: result.error || "Runway generation failed" };
|
|
52882
|
+
if (!wait)
|
|
52883
|
+
return { success: true, taskId: result.id, status: "processing", provider: "runway" };
|
|
52577
52884
|
const finalResult = await runway.waitForCompletion(result.id, () => {
|
|
52578
52885
|
}, 3e5);
|
|
52579
|
-
if (finalResult.status !== "completed")
|
|
52886
|
+
if (finalResult.status !== "completed")
|
|
52887
|
+
return { success: false, error: finalResult.error || "Runway generation failed" };
|
|
52580
52888
|
let outputPath;
|
|
52581
52889
|
if (output && finalResult.videoUrl) {
|
|
52582
52890
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52583
52891
|
outputPath = resolve53(process.cwd(), output);
|
|
52584
52892
|
await writeFile39(outputPath, buffer);
|
|
52585
52893
|
}
|
|
52586
|
-
return {
|
|
52894
|
+
return {
|
|
52895
|
+
success: true,
|
|
52896
|
+
taskId: result.id,
|
|
52897
|
+
status: "completed",
|
|
52898
|
+
videoUrl: finalResult.videoUrl,
|
|
52899
|
+
duration: finalResult.duration,
|
|
52900
|
+
outputPath,
|
|
52901
|
+
provider: "runway"
|
|
52902
|
+
};
|
|
52587
52903
|
} else if (provider === "kling") {
|
|
52588
52904
|
const kling = new KlingProvider();
|
|
52589
52905
|
await kling.initialize({ apiKey: key });
|
|
52590
52906
|
if (!kling.isConfigured()) return { success: false, error: "Invalid Kling API key format" };
|
|
52591
52907
|
let klingImage = referenceImage;
|
|
52592
52908
|
if (klingImage && klingImage.startsWith("data:")) {
|
|
52593
|
-
const
|
|
52594
|
-
|
|
52595
|
-
|
|
52596
|
-
|
|
52597
|
-
|
|
52598
|
-
klingImage =
|
|
52909
|
+
const uploadHost = await resolveUploadHost();
|
|
52910
|
+
const upload = await uploadHost.uploadImage(referenceImageBuffer, {
|
|
52911
|
+
filename: image,
|
|
52912
|
+
mimeType: referenceImageMimeType
|
|
52913
|
+
});
|
|
52914
|
+
klingImage = upload.url;
|
|
52599
52915
|
}
|
|
52600
52916
|
const result = await kling.generateVideo(prompt3, {
|
|
52601
52917
|
prompt: prompt3,
|
|
@@ -52605,23 +52921,38 @@ async function executeVideoGenerate(options) {
|
|
|
52605
52921
|
negativePrompt: negative,
|
|
52606
52922
|
mode
|
|
52607
52923
|
});
|
|
52608
|
-
if (result.status === "failed")
|
|
52924
|
+
if (result.status === "failed")
|
|
52925
|
+
return { success: false, error: result.error || "Kling generation failed" };
|
|
52609
52926
|
const taskType = referenceImage ? "image2video" : "text2video";
|
|
52610
|
-
if (!wait)
|
|
52927
|
+
if (!wait)
|
|
52928
|
+
return { success: true, taskId: result.id, status: "processing", provider: "kling" };
|
|
52611
52929
|
const finalResult = await kling.waitForCompletion(result.id, taskType, () => {
|
|
52612
52930
|
}, 6e5);
|
|
52613
|
-
if (finalResult.status !== "completed")
|
|
52931
|
+
if (finalResult.status !== "completed")
|
|
52932
|
+
return { success: false, error: finalResult.error || "Kling generation failed" };
|
|
52614
52933
|
let outputPath;
|
|
52615
52934
|
if (output && finalResult.videoUrl) {
|
|
52616
52935
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52617
52936
|
outputPath = resolve53(process.cwd(), output);
|
|
52618
52937
|
await writeFile39(outputPath, buffer);
|
|
52619
52938
|
}
|
|
52620
|
-
return {
|
|
52939
|
+
return {
|
|
52940
|
+
success: true,
|
|
52941
|
+
taskId: result.id,
|
|
52942
|
+
status: "completed",
|
|
52943
|
+
videoUrl: finalResult.videoUrl,
|
|
52944
|
+
duration: finalResult.duration,
|
|
52945
|
+
outputPath,
|
|
52946
|
+
provider: "kling"
|
|
52947
|
+
};
|
|
52621
52948
|
} else if (provider === "veo") {
|
|
52622
52949
|
const gemini = new GeminiProvider();
|
|
52623
52950
|
await gemini.initialize({ apiKey: key });
|
|
52624
|
-
const veoModelMap = {
|
|
52951
|
+
const veoModelMap = {
|
|
52952
|
+
"3.0": "veo-3.0-generate-preview",
|
|
52953
|
+
"3.1": "veo-3.1-generate-preview",
|
|
52954
|
+
"3.1-fast": "veo-3.1-fast-generate-preview"
|
|
52955
|
+
};
|
|
52625
52956
|
const model = veoModelMap[veoModel] || "veo-3.1-fast-generate-preview";
|
|
52626
52957
|
const veoDuration = duration <= 6 ? 6 : 8;
|
|
52627
52958
|
const result = await gemini.generateVideo(prompt3, {
|
|
@@ -52633,18 +52964,27 @@ async function executeVideoGenerate(options) {
|
|
|
52633
52964
|
negativePrompt: negative,
|
|
52634
52965
|
resolution
|
|
52635
52966
|
});
|
|
52636
|
-
if (result.status === "failed")
|
|
52967
|
+
if (result.status === "failed")
|
|
52968
|
+
return { success: false, error: result.error || "Veo generation failed" };
|
|
52637
52969
|
if (!wait) return { success: true, taskId: result.id, status: "processing", provider: "veo" };
|
|
52638
52970
|
const finalResult = await gemini.waitForVideoCompletion(result.id, () => {
|
|
52639
52971
|
}, 3e5);
|
|
52640
|
-
if (finalResult.status !== "completed")
|
|
52972
|
+
if (finalResult.status !== "completed")
|
|
52973
|
+
return { success: false, error: finalResult.error || "Veo generation failed" };
|
|
52641
52974
|
let outputPath;
|
|
52642
52975
|
if (output && finalResult.videoUrl) {
|
|
52643
52976
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52644
52977
|
outputPath = resolve53(process.cwd(), output);
|
|
52645
52978
|
await writeFile39(outputPath, buffer);
|
|
52646
52979
|
}
|
|
52647
|
-
return {
|
|
52980
|
+
return {
|
|
52981
|
+
success: true,
|
|
52982
|
+
taskId: result.id,
|
|
52983
|
+
status: "completed",
|
|
52984
|
+
videoUrl: finalResult.videoUrl,
|
|
52985
|
+
outputPath,
|
|
52986
|
+
provider: "veo"
|
|
52987
|
+
};
|
|
52648
52988
|
} else if (provider === "grok") {
|
|
52649
52989
|
const grok = new GrokProvider();
|
|
52650
52990
|
await grok.initialize({ apiKey: key });
|
|
@@ -52654,28 +52994,52 @@ async function executeVideoGenerate(options) {
|
|
|
52654
52994
|
duration,
|
|
52655
52995
|
aspectRatio: ratio
|
|
52656
52996
|
});
|
|
52657
|
-
if (result.status === "failed")
|
|
52658
|
-
|
|
52997
|
+
if (result.status === "failed")
|
|
52998
|
+
return { success: false, error: result.error || "Grok generation failed" };
|
|
52999
|
+
if (!wait)
|
|
53000
|
+
return { success: true, taskId: result.id, status: "processing", provider: "grok" };
|
|
52659
53001
|
const finalResult = await grok.waitForCompletion(result.id, () => {
|
|
52660
53002
|
}, 3e5);
|
|
52661
|
-
if (finalResult.status !== "completed")
|
|
53003
|
+
if (finalResult.status !== "completed")
|
|
53004
|
+
return { success: false, error: finalResult.error || "Grok generation failed" };
|
|
52662
53005
|
let outputPath;
|
|
52663
53006
|
if (output && finalResult.videoUrl) {
|
|
52664
53007
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52665
53008
|
outputPath = resolve53(process.cwd(), output);
|
|
52666
53009
|
await writeFile39(outputPath, buffer);
|
|
52667
53010
|
}
|
|
52668
|
-
return {
|
|
53011
|
+
return {
|
|
53012
|
+
success: true,
|
|
53013
|
+
taskId: result.id,
|
|
53014
|
+
status: "completed",
|
|
53015
|
+
videoUrl: finalResult.videoUrl,
|
|
53016
|
+
duration: finalResult.duration,
|
|
53017
|
+
outputPath,
|
|
53018
|
+
provider: "grok"
|
|
53019
|
+
};
|
|
52669
53020
|
}
|
|
52670
53021
|
return { success: false, error: `Unsupported provider: ${provider}` };
|
|
52671
53022
|
} catch (error) {
|
|
52672
|
-
return {
|
|
53023
|
+
return {
|
|
53024
|
+
success: false,
|
|
53025
|
+
error: `Video generation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
53026
|
+
};
|
|
52673
53027
|
}
|
|
52674
53028
|
}
|
|
52675
53029
|
async function executeVideoStatus(options) {
|
|
52676
|
-
const {
|
|
53030
|
+
const {
|
|
53031
|
+
taskId,
|
|
53032
|
+
provider = "runway",
|
|
53033
|
+
taskType = "text2video",
|
|
53034
|
+
wait = false,
|
|
53035
|
+
output,
|
|
53036
|
+
apiKey
|
|
53037
|
+
} = options;
|
|
52677
53038
|
try {
|
|
52678
|
-
const envKeyMap = {
|
|
53039
|
+
const envKeyMap = {
|
|
53040
|
+
runway: "RUNWAY_API_SECRET",
|
|
53041
|
+
kling: "KLING_API_KEY"
|
|
53042
|
+
};
|
|
52679
53043
|
const key = apiKey || process.env[envKeyMap[provider] || ""];
|
|
52680
53044
|
if (!key) return { success: false, error: `${envKeyMap[provider]} required` };
|
|
52681
53045
|
if (provider === "runway") {
|
|
@@ -52692,7 +53056,14 @@ async function executeVideoStatus(options) {
|
|
|
52692
53056
|
outputPath = resolve53(process.cwd(), output);
|
|
52693
53057
|
await writeFile39(outputPath, buffer);
|
|
52694
53058
|
}
|
|
52695
|
-
return {
|
|
53059
|
+
return {
|
|
53060
|
+
success: true,
|
|
53061
|
+
taskId,
|
|
53062
|
+
status: result.status,
|
|
53063
|
+
progress: result.progress,
|
|
53064
|
+
videoUrl: result.videoUrl,
|
|
53065
|
+
outputPath
|
|
53066
|
+
};
|
|
52696
53067
|
} else if (provider === "kling") {
|
|
52697
53068
|
const kling = new KlingProvider();
|
|
52698
53069
|
await kling.initialize({ apiKey: key });
|
|
@@ -52707,11 +53078,21 @@ async function executeVideoStatus(options) {
|
|
|
52707
53078
|
outputPath = resolve53(process.cwd(), output);
|
|
52708
53079
|
await writeFile39(outputPath, buffer);
|
|
52709
53080
|
}
|
|
52710
|
-
return {
|
|
53081
|
+
return {
|
|
53082
|
+
success: true,
|
|
53083
|
+
taskId,
|
|
53084
|
+
status: result.status,
|
|
53085
|
+
videoUrl: result.videoUrl,
|
|
53086
|
+
duration: result.duration,
|
|
53087
|
+
outputPath
|
|
53088
|
+
};
|
|
52711
53089
|
}
|
|
52712
53090
|
return { success: false, error: `Unsupported provider: ${provider}` };
|
|
52713
53091
|
} catch (error) {
|
|
52714
|
-
return {
|
|
53092
|
+
return {
|
|
53093
|
+
success: false,
|
|
53094
|
+
error: `Status check failed: ${error instanceof Error ? error.message : String(error)}`
|
|
53095
|
+
};
|
|
52715
53096
|
}
|
|
52716
53097
|
}
|
|
52717
53098
|
async function executeVideoCancel(options) {
|
|
@@ -52724,11 +53105,24 @@ async function executeVideoCancel(options) {
|
|
|
52724
53105
|
const success = await runway.cancelGeneration(taskId);
|
|
52725
53106
|
return { success };
|
|
52726
53107
|
} catch (error) {
|
|
52727
|
-
return {
|
|
53108
|
+
return {
|
|
53109
|
+
success: false,
|
|
53110
|
+
error: `Cancel failed: ${error instanceof Error ? error.message : String(error)}`
|
|
53111
|
+
};
|
|
52728
53112
|
}
|
|
52729
53113
|
}
|
|
52730
53114
|
async function executeVideoExtend(options) {
|
|
52731
|
-
const {
|
|
53115
|
+
const {
|
|
53116
|
+
videoId,
|
|
53117
|
+
provider = "kling",
|
|
53118
|
+
prompt: prompt3,
|
|
53119
|
+
duration = 5,
|
|
53120
|
+
negative,
|
|
53121
|
+
veoModel = "3.1",
|
|
53122
|
+
output,
|
|
53123
|
+
wait = true,
|
|
53124
|
+
apiKey
|
|
53125
|
+
} = options;
|
|
52732
53126
|
try {
|
|
52733
53127
|
if (provider === "kling") {
|
|
52734
53128
|
const key = apiKey || process.env.KLING_API_KEY;
|
|
@@ -52741,52 +53135,76 @@ async function executeVideoExtend(options) {
|
|
|
52741
53135
|
negativePrompt: negative,
|
|
52742
53136
|
duration: String(duration)
|
|
52743
53137
|
});
|
|
52744
|
-
if (result.status === "failed")
|
|
53138
|
+
if (result.status === "failed")
|
|
53139
|
+
return { success: false, error: result.error || "Kling extension failed" };
|
|
52745
53140
|
if (!wait) return { success: true, taskId: result.id, status: "processing" };
|
|
52746
53141
|
const finalResult = await kling.waitForExtendCompletion(result.id, () => {
|
|
52747
53142
|
}, 6e5);
|
|
52748
|
-
if (finalResult.status !== "completed")
|
|
53143
|
+
if (finalResult.status !== "completed")
|
|
53144
|
+
return { success: false, error: finalResult.error || "Kling extension failed" };
|
|
52749
53145
|
let outputPath;
|
|
52750
53146
|
if (output && finalResult.videoUrl) {
|
|
52751
53147
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52752
53148
|
outputPath = resolve53(process.cwd(), output);
|
|
52753
53149
|
await writeFile39(outputPath, buffer);
|
|
52754
53150
|
}
|
|
52755
|
-
return {
|
|
53151
|
+
return {
|
|
53152
|
+
success: true,
|
|
53153
|
+
taskId: result.id,
|
|
53154
|
+
status: "completed",
|
|
53155
|
+
videoUrl: finalResult.videoUrl,
|
|
53156
|
+
duration: finalResult.duration,
|
|
53157
|
+
outputPath
|
|
53158
|
+
};
|
|
52756
53159
|
} else if (provider === "veo") {
|
|
52757
53160
|
const key = apiKey || process.env.GOOGLE_API_KEY;
|
|
52758
53161
|
if (!key) return { success: false, error: "GOOGLE_API_KEY required" };
|
|
52759
53162
|
const gemini = new GeminiProvider();
|
|
52760
53163
|
await gemini.initialize({ apiKey: key });
|
|
52761
|
-
const veoModelMap = {
|
|
53164
|
+
const veoModelMap = {
|
|
53165
|
+
"3.0": "veo-3.0-generate-preview",
|
|
53166
|
+
"3.1": "veo-3.1-generate-preview",
|
|
53167
|
+
"3.1-fast": "veo-3.1-fast-generate-preview"
|
|
53168
|
+
};
|
|
52762
53169
|
const model = veoModelMap[veoModel] || "veo-3.1-generate-preview";
|
|
52763
53170
|
const result = await gemini.extendVideo(videoId, prompt3, {
|
|
52764
53171
|
duration,
|
|
52765
53172
|
model
|
|
52766
53173
|
});
|
|
52767
|
-
if (result.status === "failed")
|
|
53174
|
+
if (result.status === "failed")
|
|
53175
|
+
return { success: false, error: result.error || "Veo extension failed" };
|
|
52768
53176
|
if (!wait) return { success: true, taskId: result.id, status: "processing" };
|
|
52769
53177
|
const finalResult = await gemini.waitForVideoCompletion(result.id, () => {
|
|
52770
53178
|
}, 3e5);
|
|
52771
|
-
if (finalResult.status !== "completed")
|
|
53179
|
+
if (finalResult.status !== "completed")
|
|
53180
|
+
return { success: false, error: finalResult.error || "Veo extension failed" };
|
|
52772
53181
|
let outputPath;
|
|
52773
53182
|
if (output && finalResult.videoUrl) {
|
|
52774
53183
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52775
53184
|
outputPath = resolve53(process.cwd(), output);
|
|
52776
53185
|
await writeFile39(outputPath, buffer);
|
|
52777
53186
|
}
|
|
52778
|
-
return {
|
|
53187
|
+
return {
|
|
53188
|
+
success: true,
|
|
53189
|
+
taskId: result.id,
|
|
53190
|
+
status: "completed",
|
|
53191
|
+
videoUrl: finalResult.videoUrl,
|
|
53192
|
+
outputPath
|
|
53193
|
+
};
|
|
52779
53194
|
}
|
|
52780
53195
|
return { success: false, error: `Unsupported provider: ${provider}` };
|
|
52781
53196
|
} catch (error) {
|
|
52782
|
-
return {
|
|
53197
|
+
return {
|
|
53198
|
+
success: false,
|
|
53199
|
+
error: `Video extension failed: ${error instanceof Error ? error.message : String(error)}`
|
|
53200
|
+
};
|
|
52783
53201
|
}
|
|
52784
53202
|
}
|
|
52785
53203
|
var init_ai_video = __esm({
|
|
52786
53204
|
"src/commands/ai-video.ts"() {
|
|
52787
53205
|
"use strict";
|
|
52788
53206
|
init_dist2();
|
|
52789
|
-
|
|
53207
|
+
init_upload_host();
|
|
52790
53208
|
init_ai_helpers();
|
|
52791
53209
|
}
|
|
52792
53210
|
});
|
|
@@ -52800,7 +53218,7 @@ __export(ai_audio_exports, {
|
|
|
52800
53218
|
executeTranscribe: () => executeTranscribe,
|
|
52801
53219
|
executeVoiceClone: () => executeVoiceClone
|
|
52802
53220
|
});
|
|
52803
|
-
import { resolve as resolve54, dirname as dirname19, basename as basename18, extname as
|
|
53221
|
+
import { resolve as resolve54, dirname as dirname19, basename as basename18, extname as extname13 } from "node:path";
|
|
52804
53222
|
import { readFile as readFile36, writeFile as writeFile40 } from "node:fs/promises";
|
|
52805
53223
|
import { existsSync as existsSync38 } from "node:fs";
|
|
52806
53224
|
async function executeTranscribe(options) {
|
|
@@ -52897,7 +53315,7 @@ async function executeDub(options) {
|
|
|
52897
53315
|
if (!openaiKey) return { success: false, error: "OPENAI_API_KEY required for Whisper transcription" };
|
|
52898
53316
|
if (!anthropicKey) return { success: false, error: "ANTHROPIC_API_KEY required for Claude translation" };
|
|
52899
53317
|
if (!analyzeOnly && !elevenlabsKey) return { success: false, error: "ELEVENLABS_API_KEY required for TTS" };
|
|
52900
|
-
const ext =
|
|
53318
|
+
const ext = extname13(absPath).toLowerCase();
|
|
52901
53319
|
const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
|
|
52902
53320
|
let audioPath = absPath;
|
|
52903
53321
|
if (isVideo) {
|
|
@@ -52982,8 +53400,8 @@ ${segmentTexts}`,
|
|
|
52982
53400
|
}
|
|
52983
53401
|
}
|
|
52984
53402
|
const combinedBuffer = Buffer.concat(dubbedBuffers);
|
|
52985
|
-
const outputExt = isVideo ? ".mp3" :
|
|
52986
|
-
const defaultOutputPath2 = resolve54(dirname19(absPath), `${basename18(absPath,
|
|
53403
|
+
const outputExt = isVideo ? ".mp3" : extname13(absPath);
|
|
53404
|
+
const defaultOutputPath2 = resolve54(dirname19(absPath), `${basename18(absPath, extname13(absPath))}-${language}${outputExt}`);
|
|
52987
53405
|
const finalOutputPath = resolve54(process.cwd(), output || defaultOutputPath2);
|
|
52988
53406
|
await writeFile40(finalOutputPath, combinedBuffer);
|
|
52989
53407
|
if (isVideo && audioPath !== absPath) {
|
|
@@ -53020,7 +53438,7 @@ async function executeDuck(options) {
|
|
|
53020
53438
|
const absVoicePath = resolve54(process.cwd(), voicePath);
|
|
53021
53439
|
if (!existsSync38(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
|
|
53022
53440
|
if (!existsSync38(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
|
|
53023
|
-
const defaultOutput = resolve54(dirname19(absMusicPath), `${basename18(absMusicPath,
|
|
53441
|
+
const defaultOutput = resolve54(dirname19(absMusicPath), `${basename18(absMusicPath, extname13(absMusicPath))}-ducked${extname13(absMusicPath)}`);
|
|
53024
53442
|
const outputPath = resolve54(process.cwd(), output || defaultOutput);
|
|
53025
53443
|
const filter = `[1:a]asplit=2[sc][mix];[0:a][sc]sidechaincompress=threshold=${threshold}dB:ratio=${ratio}:attack=${attack}:release=${release}[ducked];[ducked][mix]amix=inputs=2:duration=longest`;
|
|
53026
53444
|
await execSafe("ffmpeg", [
|
|
@@ -59597,11 +60015,17 @@ var init_generate2 = __esm({
|
|
|
59597
60015
|
}),
|
|
59598
60016
|
async execute(args) {
|
|
59599
60017
|
const result = await executeMotion(args);
|
|
59600
|
-
if (!result.success)
|
|
60018
|
+
if (!result.success)
|
|
60019
|
+
return { success: false, error: result.error ?? "Motion generation failed" };
|
|
59601
60020
|
const out = result.compositedPath ?? result.renderedPath ?? result.codePath;
|
|
59602
60021
|
return {
|
|
59603
60022
|
success: true,
|
|
59604
|
-
data: {
|
|
60023
|
+
data: {
|
|
60024
|
+
codePath: result.codePath,
|
|
60025
|
+
renderedPath: result.renderedPath,
|
|
60026
|
+
compositedPath: result.compositedPath,
|
|
60027
|
+
componentName: result.componentName
|
|
60028
|
+
},
|
|
59605
60029
|
humanLines: [`\u2705 Motion generated \u2192 ${out}`]
|
|
59606
60030
|
};
|
|
59607
60031
|
}
|
|
@@ -59665,7 +60089,9 @@ var init_generate2 = __esm({
|
|
|
59665
60089
|
return {
|
|
59666
60090
|
success: true,
|
|
59667
60091
|
data: { outputPath: result.outputPath, provider: result.provider, duration: result.duration },
|
|
59668
|
-
humanLines: [
|
|
60092
|
+
humanLines: [
|
|
60093
|
+
`\u2705 Music${result.provider ? ` (${result.provider})` : ""} \u2192 ${result.outputPath ?? "(async)"}`
|
|
60094
|
+
]
|
|
59669
60095
|
};
|
|
59670
60096
|
}
|
|
59671
60097
|
});
|
|
@@ -59682,7 +60108,12 @@ var init_generate2 = __esm({
|
|
|
59682
60108
|
if (!result.success) return { success: false, error: result.error ?? "Music status failed" };
|
|
59683
60109
|
return {
|
|
59684
60110
|
success: true,
|
|
59685
|
-
data: {
|
|
60111
|
+
data: {
|
|
60112
|
+
taskId: result.taskId,
|
|
60113
|
+
status: result.status,
|
|
60114
|
+
audioUrl: result.audioUrl,
|
|
60115
|
+
error: result.error
|
|
60116
|
+
},
|
|
59686
60117
|
humanLines: [`Music task ${result.taskId}: ${result.status}`]
|
|
59687
60118
|
};
|
|
59688
60119
|
}
|
|
@@ -59694,7 +60125,9 @@ var init_generate2 = __esm({
|
|
|
59694
60125
|
description: "Generate an image using AI. Supports Gemini (free), OpenAI GPT Image, or Grok Imagine. Requires GOOGLE_API_KEY (Gemini), OPENAI_API_KEY (OpenAI), or XAI_API_KEY (Grok).",
|
|
59695
60126
|
schema: external_exports.object({
|
|
59696
60127
|
prompt: external_exports.string().describe("Image description prompt"),
|
|
59697
|
-
provider: external_exports.enum(["gemini", "openai", "grok"]).optional().describe(
|
|
60128
|
+
provider: external_exports.enum(["gemini", "openai", "grok"]).optional().describe(
|
|
60129
|
+
"Image provider (default: openai when OPENAI_API_KEY is configured, otherwise first configured provider)"
|
|
60130
|
+
),
|
|
59698
60131
|
output: external_exports.string().optional().describe("Output file path"),
|
|
59699
60132
|
size: external_exports.string().optional().describe("Image size for OpenAI (1024x1024, 1536x1024, 1024x1536)"),
|
|
59700
60133
|
ratio: external_exports.string().optional().describe("Aspect ratio for Gemini (1:1, 16:9, 9:16, 4:3, 3:4, etc.)"),
|
|
@@ -59704,10 +60137,16 @@ var init_generate2 = __esm({
|
|
|
59704
60137
|
}),
|
|
59705
60138
|
async execute(args) {
|
|
59706
60139
|
const result = await executeImageGenerate(args);
|
|
59707
|
-
if (!result.success)
|
|
60140
|
+
if (!result.success)
|
|
60141
|
+
return { success: false, error: result.error ?? "Image generation failed" };
|
|
59708
60142
|
return {
|
|
59709
60143
|
success: true,
|
|
59710
|
-
data: {
|
|
60144
|
+
data: {
|
|
60145
|
+
outputPath: result.outputPath,
|
|
60146
|
+
provider: result.provider,
|
|
60147
|
+
model: result.model,
|
|
60148
|
+
imageCount: result.images?.length
|
|
60149
|
+
},
|
|
59711
60150
|
humanLines: [`\u2705 Image (${result.provider}) \u2192 ${result.outputPath}`]
|
|
59712
60151
|
};
|
|
59713
60152
|
}
|
|
@@ -59729,7 +60168,9 @@ var init_generate2 = __esm({
|
|
|
59729
60168
|
return {
|
|
59730
60169
|
success: true,
|
|
59731
60170
|
data: { segmentCount: result.segmentCount, outputPath: result.outputPath },
|
|
59732
|
-
humanLines: [
|
|
60171
|
+
humanLines: [
|
|
60172
|
+
`\u2705 Storyboard: ${result.segmentCount} segments${result.outputPath ? ` \u2192 ${result.outputPath}` : ""}`
|
|
60173
|
+
]
|
|
59733
60174
|
};
|
|
59734
60175
|
}
|
|
59735
60176
|
});
|
|
@@ -59748,7 +60189,11 @@ var init_generate2 = __esm({
|
|
|
59748
60189
|
if (!result.success) return { success: false, error: result.error ?? "Background failed" };
|
|
59749
60190
|
return {
|
|
59750
60191
|
success: true,
|
|
59751
|
-
data: {
|
|
60192
|
+
data: {
|
|
60193
|
+
imageUrl: result.imageUrl,
|
|
60194
|
+
outputPath: result.outputPath,
|
|
60195
|
+
revisedPrompt: result.revisedPrompt
|
|
60196
|
+
},
|
|
59752
60197
|
humanLines: [`\u2705 Background \u2192 ${result.outputPath ?? result.imageUrl}`]
|
|
59753
60198
|
};
|
|
59754
60199
|
}
|
|
@@ -59806,7 +60251,9 @@ var init_generate2 = __esm({
|
|
|
59806
60251
|
outputPath: result.outputPath,
|
|
59807
60252
|
provider: result.provider
|
|
59808
60253
|
},
|
|
59809
|
-
humanLines: [
|
|
60254
|
+
humanLines: [
|
|
60255
|
+
`\u2705 Video (${result.provider}, ${result.status})${result.outputPath ? ` \u2192 ${result.outputPath}` : ""}`
|
|
60256
|
+
]
|
|
59810
60257
|
};
|
|
59811
60258
|
}
|
|
59812
60259
|
});
|
|
@@ -59827,8 +60274,16 @@ var init_generate2 = __esm({
|
|
|
59827
60274
|
if (!result.success) return { success: false, error: result.error ?? "Status check failed" };
|
|
59828
60275
|
return {
|
|
59829
60276
|
success: true,
|
|
59830
|
-
data: {
|
|
59831
|
-
|
|
60277
|
+
data: {
|
|
60278
|
+
taskId: result.taskId,
|
|
60279
|
+
status: result.status,
|
|
60280
|
+
progress: result.progress,
|
|
60281
|
+
videoUrl: result.videoUrl,
|
|
60282
|
+
outputPath: result.outputPath
|
|
60283
|
+
},
|
|
60284
|
+
humanLines: [
|
|
60285
|
+
`Task ${result.taskId}: ${result.status}${result.progress !== void 0 ? ` (${result.progress}%)` : ""}`
|
|
60286
|
+
]
|
|
59832
60287
|
};
|
|
59833
60288
|
}
|
|
59834
60289
|
});
|
|
@@ -59870,8 +60325,16 @@ var init_generate2 = __esm({
|
|
|
59870
60325
|
if (!result.success) return { success: false, error: result.error ?? "Extend failed" };
|
|
59871
60326
|
return {
|
|
59872
60327
|
success: true,
|
|
59873
|
-
data: {
|
|
59874
|
-
|
|
60328
|
+
data: {
|
|
60329
|
+
taskId: result.taskId,
|
|
60330
|
+
status: result.status,
|
|
60331
|
+
videoUrl: result.videoUrl,
|
|
60332
|
+
duration: result.duration,
|
|
60333
|
+
outputPath: result.outputPath
|
|
60334
|
+
},
|
|
60335
|
+
humanLines: [
|
|
60336
|
+
`\u2705 Video extended (${result.status})${result.outputPath ? ` \u2192 ${result.outputPath}` : ""}`
|
|
60337
|
+
]
|
|
59875
60338
|
};
|
|
59876
60339
|
}
|
|
59877
60340
|
});
|
|
@@ -61802,7 +62265,7 @@ var init_export2 = __esm({
|
|
|
61802
62265
|
|
|
61803
62266
|
// src/tools/manifest/agent-only.ts
|
|
61804
62267
|
import { readFile as readFile41, writeFile as writeFile48, readdir as readdir4, stat as stat6, access as access9, unlink as unlink4 } from "node:fs/promises";
|
|
61805
|
-
import { resolve as resolve63, join as join16, basename as basename22, extname as
|
|
62268
|
+
import { resolve as resolve63, join as join16, basename as basename22, extname as extname14 } from "node:path";
|
|
61806
62269
|
function matchPattern(filename, pattern) {
|
|
61807
62270
|
const regex2 = new RegExp(
|
|
61808
62271
|
"^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
|
|
@@ -61821,7 +62284,7 @@ function formatSize(bytes) {
|
|
|
61821
62284
|
return `${size.toFixed(1)}${units2[unit]}`;
|
|
61822
62285
|
}
|
|
61823
62286
|
function detectMediaType4(filePath) {
|
|
61824
|
-
const ext =
|
|
62287
|
+
const ext = extname14(filePath).toLowerCase();
|
|
61825
62288
|
if ([".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"].includes(ext)) return "video";
|
|
61826
62289
|
if ([".mp3", ".wav", ".aac", ".flac", ".ogg", ".m4a"].includes(ext)) return "audio";
|
|
61827
62290
|
if ([".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"].includes(ext)) return "image";
|
|
@@ -61848,7 +62311,7 @@ function isMediaFile2(filePath) {
|
|
|
61848
62311
|
".webp",
|
|
61849
62312
|
".bmp"
|
|
61850
62313
|
];
|
|
61851
|
-
return mediaExts.includes(
|
|
62314
|
+
return mediaExts.includes(extname14(filePath).toLowerCase());
|
|
61852
62315
|
}
|
|
61853
62316
|
async function getMediaDuration2(filePath) {
|
|
61854
62317
|
try {
|
|
@@ -61994,7 +62457,7 @@ var init_agent_only = __esm({
|
|
|
61994
62457
|
if (entry.isDirectory() && recursive) {
|
|
61995
62458
|
await scanDir(entryPath);
|
|
61996
62459
|
} else if (entry.isFile()) {
|
|
61997
|
-
const ext =
|
|
62460
|
+
const ext = extname14(entry.name).toLowerCase();
|
|
61998
62461
|
const matchesFilter = !filterExts || filterExts.includes(ext);
|
|
61999
62462
|
if (matchesFilter && isMediaFile2(entryPath)) mediaFiles.push(entryPath);
|
|
62000
62463
|
}
|
|
@@ -62962,7 +63425,7 @@ init_source();
|
|
|
62962
63425
|
// package.json
|
|
62963
63426
|
var package_default = {
|
|
62964
63427
|
name: "@vibeframe/cli",
|
|
62965
|
-
version: "0.
|
|
63428
|
+
version: "0.92.0",
|
|
62966
63429
|
description: "VibeFrame CLI - natural language video editing from the terminal",
|
|
62967
63430
|
private: false,
|
|
62968
63431
|
license: "MIT",
|
|
@@ -64148,8 +64611,8 @@ init_output();
|
|
|
64148
64611
|
init_sanitize();
|
|
64149
64612
|
init_validate();
|
|
64150
64613
|
init_cost_tier();
|
|
64151
|
-
import { readFile as
|
|
64152
|
-
import { resolve as
|
|
64614
|
+
import { readFile as readFile22, writeFile as writeFile29 } from "node:fs/promises";
|
|
64615
|
+
import { resolve as resolve32 } from "node:path";
|
|
64153
64616
|
var inspectCommand = new Command("inspect").description("Inspect media using AI (images, videos, YouTube URLs)").addHelpText(
|
|
64154
64617
|
"after",
|
|
64155
64618
|
`
|
|
@@ -64346,8 +64809,8 @@ inspectCommand.command("suggest").description("Get AI edit suggestions using Gem
|
|
|
64346
64809
|
}
|
|
64347
64810
|
const apiKey = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
|
|
64348
64811
|
const spinner2 = ora("Initializing Gemini...").start();
|
|
64349
|
-
const filePath =
|
|
64350
|
-
const content = await
|
|
64812
|
+
const filePath = resolve32(process.cwd(), projectPath);
|
|
64813
|
+
const content = await readFile22(filePath, "utf-8");
|
|
64351
64814
|
const data = JSON.parse(content);
|
|
64352
64815
|
const project = Project.fromJSON(data);
|
|
64353
64816
|
const gemini = new GeminiProvider();
|
|
@@ -64384,7 +64847,7 @@ inspectCommand.command("suggest").description("Get AI edit suggestions using Gem
|
|
|
64384
64847
|
const sug = suggestions[0];
|
|
64385
64848
|
const applied = applySuggestion(project, sug);
|
|
64386
64849
|
if (applied) {
|
|
64387
|
-
await
|
|
64850
|
+
await writeFile29(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
64388
64851
|
spinner2.succeed(source_default.green("Suggestion applied"));
|
|
64389
64852
|
} else {
|
|
64390
64853
|
spinner2.warn(source_default.yellow("Could not apply suggestion automatically"));
|
|
@@ -64409,9 +64872,9 @@ init_cost_tier();
|
|
|
64409
64872
|
init_ai_helpers();
|
|
64410
64873
|
init_output();
|
|
64411
64874
|
init_validate();
|
|
64412
|
-
import { resolve as
|
|
64413
|
-
import { readFile as
|
|
64414
|
-
import { existsSync as
|
|
64875
|
+
import { resolve as resolve33, dirname as dirname11, basename as basename8, extname as extname7 } from "node:path";
|
|
64876
|
+
import { readFile as readFile23, writeFile as writeFile30 } from "node:fs/promises";
|
|
64877
|
+
import { existsSync as existsSync25 } from "node:fs";
|
|
64415
64878
|
var audioCommand = new Command("audio").alias("au").description("Audio operations (transcribe, TTS, voice clone, ducking)").addHelpText(
|
|
64416
64879
|
"after",
|
|
64417
64880
|
`
|
|
@@ -64444,8 +64907,8 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
|
|
|
64444
64907
|
const whisper = new WhisperProvider();
|
|
64445
64908
|
await whisper.initialize({ apiKey });
|
|
64446
64909
|
spinner2.text = "Reading audio file...";
|
|
64447
|
-
const absPath =
|
|
64448
|
-
const audioBuffer = await
|
|
64910
|
+
const absPath = resolve33(process.cwd(), audioPath);
|
|
64911
|
+
const audioBuffer = await readFile23(absPath);
|
|
64449
64912
|
const audioBlob = new Blob([audioBuffer]);
|
|
64450
64913
|
spinner2.text = "Transcribing...";
|
|
64451
64914
|
const result = await whisper.transcribe(audioBlob, options.language);
|
|
@@ -64462,7 +64925,7 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
|
|
|
64462
64925
|
fullText: result.fullText,
|
|
64463
64926
|
segments: result.segments,
|
|
64464
64927
|
language: result.detectedLanguage,
|
|
64465
|
-
outputPath: options.output ?
|
|
64928
|
+
outputPath: options.output ? resolve33(process.cwd(), options.output) : void 0
|
|
64466
64929
|
}
|
|
64467
64930
|
});
|
|
64468
64931
|
return;
|
|
@@ -64482,10 +64945,10 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
|
|
|
64482
64945
|
console.log();
|
|
64483
64946
|
}
|
|
64484
64947
|
if (options.output) {
|
|
64485
|
-
const outputPath =
|
|
64948
|
+
const outputPath = resolve33(process.cwd(), options.output);
|
|
64486
64949
|
const format = detectFormat(options.output, options.format);
|
|
64487
64950
|
const content = formatTranscript(result, format);
|
|
64488
|
-
await
|
|
64951
|
+
await writeFile30(outputPath, content, "utf-8");
|
|
64489
64952
|
console.log(source_default.green(`Saved ${format.toUpperCase()} to: ${outputPath}`));
|
|
64490
64953
|
}
|
|
64491
64954
|
} catch (error) {
|
|
@@ -64543,8 +65006,8 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
|
|
|
64543
65006
|
}
|
|
64544
65007
|
const apiKey = await requireApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
|
|
64545
65008
|
const spinner2 = ora("Reading audio file...").start();
|
|
64546
|
-
const absPath =
|
|
64547
|
-
const audioBuffer = await
|
|
65009
|
+
const absPath = resolve33(process.cwd(), audioPath);
|
|
65010
|
+
const audioBuffer = await readFile23(absPath);
|
|
64548
65011
|
spinner2.text = "Isolating vocals...";
|
|
64549
65012
|
const elevenlabs = new ElevenLabsProvider();
|
|
64550
65013
|
await elevenlabs.initialize({ apiKey });
|
|
@@ -64553,8 +65016,8 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
|
|
|
64553
65016
|
spinner2.fail(result.error || "Audio isolation failed");
|
|
64554
65017
|
exitWithError(apiError(result.error || "Audio isolation failed", true));
|
|
64555
65018
|
}
|
|
64556
|
-
const outputPath =
|
|
64557
|
-
await
|
|
65019
|
+
const outputPath = resolve33(process.cwd(), options.output);
|
|
65020
|
+
await writeFile30(outputPath, result.audioBuffer);
|
|
64558
65021
|
spinner2.succeed(source_default.green("Vocals isolated"));
|
|
64559
65022
|
if (isJsonMode()) {
|
|
64560
65023
|
outputSuccess({
|
|
@@ -64614,12 +65077,12 @@ audioCommand.command("clone-voice").description("Clone a voice from audio sample
|
|
|
64614
65077
|
const spinner2 = ora("Reading audio samples...").start();
|
|
64615
65078
|
const audioBuffers = [];
|
|
64616
65079
|
for (const samplePath of samples) {
|
|
64617
|
-
const absPath =
|
|
64618
|
-
if (!
|
|
65080
|
+
const absPath = resolve33(process.cwd(), samplePath);
|
|
65081
|
+
if (!existsSync25(absPath)) {
|
|
64619
65082
|
spinner2.fail(`File not found: ${samplePath}`);
|
|
64620
65083
|
exitWithError(notFoundError(samplePath));
|
|
64621
65084
|
}
|
|
64622
|
-
const buffer = await
|
|
65085
|
+
const buffer = await readFile23(absPath);
|
|
64623
65086
|
audioBuffers.push(buffer);
|
|
64624
65087
|
}
|
|
64625
65088
|
spinner2.text = `Cloning voice from ${audioBuffers.length} sample(s)...`;
|
|
@@ -64675,8 +65138,8 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
|
|
|
64675
65138
|
if (!options.language) {
|
|
64676
65139
|
exitWithError(usageError("Target language is required. Use -l or --language"));
|
|
64677
65140
|
}
|
|
64678
|
-
const absPath =
|
|
64679
|
-
if (!
|
|
65141
|
+
const absPath = resolve33(process.cwd(), mediaPath);
|
|
65142
|
+
if (!existsSync25(absPath)) {
|
|
64680
65143
|
exitWithError(notFoundError(mediaPath));
|
|
64681
65144
|
}
|
|
64682
65145
|
const openaiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI");
|
|
@@ -64687,7 +65150,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
|
|
|
64687
65150
|
const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
|
|
64688
65151
|
let audioPath = absPath;
|
|
64689
65152
|
if (isVideo) {
|
|
64690
|
-
const tempAudioPath =
|
|
65153
|
+
const tempAudioPath = resolve33(dirname11(absPath), `temp-audio-${Date.now()}.mp3`);
|
|
64691
65154
|
try {
|
|
64692
65155
|
execSafeSync("ffmpeg", ["-i", absPath, "-vn", "-acodec", "mp3", "-y", tempAudioPath]);
|
|
64693
65156
|
audioPath = tempAudioPath;
|
|
@@ -64699,7 +65162,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
|
|
|
64699
65162
|
spinner2.text = "Transcribing audio...";
|
|
64700
65163
|
const whisper = new WhisperProvider();
|
|
64701
65164
|
await whisper.initialize({ apiKey: openaiKey });
|
|
64702
|
-
const audioBuffer = await
|
|
65165
|
+
const audioBuffer = await readFile23(audioPath);
|
|
64703
65166
|
const audioBlob = new Blob([audioBuffer]);
|
|
64704
65167
|
const transcriptResult = await whisper.transcribe(audioBlob, options.source);
|
|
64705
65168
|
if (transcriptResult.status === "failed" || !transcriptResult.segments) {
|
|
@@ -64780,7 +65243,7 @@ ${segmentTexts}`,
|
|
|
64780
65243
|
console.log();
|
|
64781
65244
|
console.log(source_default.dim("Use without --analyze-only to generate dubbed audio"));
|
|
64782
65245
|
if (options.output) {
|
|
64783
|
-
const timingPath =
|
|
65246
|
+
const timingPath = resolve33(process.cwd(), options.output);
|
|
64784
65247
|
const timingData = {
|
|
64785
65248
|
sourcePath: absPath,
|
|
64786
65249
|
sourceLanguage: transcriptResult.detectedLanguage || options.source || "auto",
|
|
@@ -64793,7 +65256,7 @@ ${segmentTexts}`,
|
|
|
64793
65256
|
translated: translatedSegments[i]?.text || s.text
|
|
64794
65257
|
}))
|
|
64795
65258
|
};
|
|
64796
|
-
await
|
|
65259
|
+
await writeFile30(timingPath, JSON.stringify(timingData, null, 2));
|
|
64797
65260
|
console.log(`Timing saved to: ${source_default.bold(timingPath)}`);
|
|
64798
65261
|
}
|
|
64799
65262
|
return;
|
|
@@ -64818,12 +65281,12 @@ ${segmentTexts}`,
|
|
|
64818
65281
|
spinner2.text = "Combining audio...";
|
|
64819
65282
|
const combinedBuffer = Buffer.concat(dubbedAudioBuffers.map((a) => a.buffer));
|
|
64820
65283
|
const outputExt = isVideo ? ".mp3" : extname7(absPath);
|
|
64821
|
-
const defaultOutputPath2 =
|
|
65284
|
+
const defaultOutputPath2 = resolve33(
|
|
64822
65285
|
dirname11(absPath),
|
|
64823
65286
|
`${basename8(absPath, extname7(absPath))}-${options.language}${outputExt}`
|
|
64824
65287
|
);
|
|
64825
|
-
const finalOutputPath =
|
|
64826
|
-
await
|
|
65288
|
+
const finalOutputPath = resolve33(process.cwd(), options.output || defaultOutputPath2);
|
|
65289
|
+
await writeFile30(finalOutputPath, combinedBuffer);
|
|
64827
65290
|
spinner2.succeed(source_default.green("Dubbing complete"));
|
|
64828
65291
|
if (isJsonMode()) {
|
|
64829
65292
|
outputSuccess({
|
|
@@ -64879,9 +65342,9 @@ audioCommand.command("duck").description("Auto-duck background music when voice
|
|
|
64879
65342
|
exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"));
|
|
64880
65343
|
}
|
|
64881
65344
|
const spinner2 = ora("Processing audio ducking...").start();
|
|
64882
|
-
const absMusic =
|
|
64883
|
-
const absVoice =
|
|
64884
|
-
const outputPath = options.output ?
|
|
65345
|
+
const absMusic = resolve33(process.cwd(), musicPath);
|
|
65346
|
+
const absVoice = resolve33(process.cwd(), options.voice);
|
|
65347
|
+
const outputPath = options.output ? resolve33(process.cwd(), options.output) : absMusic.replace(/(\.[^.]+)$/, "-ducked$1");
|
|
64885
65348
|
const thresholdDb = parseFloat(options.threshold);
|
|
64886
65349
|
const thresholdLinear = Math.pow(10, thresholdDb / 20);
|
|
64887
65350
|
const ratio = parseFloat(options.ratio);
|
|
@@ -65489,7 +65952,7 @@ init_esm();
|
|
|
65489
65952
|
init_source();
|
|
65490
65953
|
init_ora();
|
|
65491
65954
|
import { stat as stat4 } from "node:fs/promises";
|
|
65492
|
-
import { resolve as resolve39, extname as
|
|
65955
|
+
import { resolve as resolve39, extname as extname10, basename as basename12 } from "node:path";
|
|
65493
65956
|
|
|
65494
65957
|
// ../../node_modules/.pnpm/music-metadata@11.11.1/node_modules/music-metadata/lib/index.js
|
|
65495
65958
|
init_lib();
|
|
@@ -69366,7 +69829,7 @@ mediaCommand.command("info").description("Get media file information").argument(
|
|
|
69366
69829
|
const spinner2 = ora("Analyzing media...").start();
|
|
69367
69830
|
try {
|
|
69368
69831
|
const filePath = resolve39(process.cwd(), file);
|
|
69369
|
-
const ext =
|
|
69832
|
+
const ext = extname10(filePath).toLowerCase();
|
|
69370
69833
|
const fileName = basename12(filePath);
|
|
69371
69834
|
const fileStat = await stat4(filePath);
|
|
69372
69835
|
const mediaType = detectMediaType2(ext);
|
|
@@ -69508,10 +69971,10 @@ init_engine();
|
|
|
69508
69971
|
init_output();
|
|
69509
69972
|
init_cost_tier();
|
|
69510
69973
|
import { readFile as readFile27, writeFile as writeFile35, readdir as readdir2 } from "node:fs/promises";
|
|
69511
|
-
import { resolve as resolve40, basename as basename13, extname as
|
|
69974
|
+
import { resolve as resolve40, basename as basename13, extname as extname11, join as join10 } from "node:path";
|
|
69512
69975
|
var batchCommand = new Command("batch").description("Batch operations for processing multiple items");
|
|
69513
69976
|
function detectMediaType3(filePath) {
|
|
69514
|
-
const ext =
|
|
69977
|
+
const ext = extname11(filePath).toLowerCase();
|
|
69515
69978
|
const videoExts = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
|
|
69516
69979
|
const audioExts = [".mp3", ".wav", ".aac", ".flac", ".ogg", ".m4a"];
|
|
69517
69980
|
const imageExts = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"];
|
|
@@ -69521,7 +69984,7 @@ function detectMediaType3(filePath) {
|
|
|
69521
69984
|
return "video";
|
|
69522
69985
|
}
|
|
69523
69986
|
function isMediaFile(filePath) {
|
|
69524
|
-
const ext =
|
|
69987
|
+
const ext = extname11(filePath).toLowerCase();
|
|
69525
69988
|
const mediaExts = [
|
|
69526
69989
|
".mp4",
|
|
69527
69990
|
".mov",
|
|
@@ -69579,7 +70042,7 @@ batchCommand.command("import").description("Import multiple media files from a d
|
|
|
69579
70042
|
if (entry.isDirectory() && options.recursive) {
|
|
69580
70043
|
await scanDir(entryPath);
|
|
69581
70044
|
} else if (entry.isFile()) {
|
|
69582
|
-
const ext =
|
|
70045
|
+
const ext = extname11(entry.name).toLowerCase();
|
|
69583
70046
|
const matchesFilter = !filterExts || filterExts.includes(ext);
|
|
69584
70047
|
if (matchesFilter && isMediaFile(entryPath)) {
|
|
69585
70048
|
mediaFiles.push(entryPath);
|
|
@@ -70047,6 +70510,7 @@ var AI_FEATURES = [
|
|
|
70047
70510
|
desc: "generate + edit",
|
|
70048
70511
|
defaultProvider: "OpenAI gpt-image-2 (Artificial Analysis #1, since v0.56)",
|
|
70049
70512
|
alsoAvailable: "Gemini Nano Banana, Grok Imagine",
|
|
70513
|
+
keyHint: "1 key",
|
|
70050
70514
|
keys: [
|
|
70051
70515
|
{
|
|
70052
70516
|
configKey: "openai",
|
|
@@ -70101,6 +70565,7 @@ var AI_FEATURES = [
|
|
|
70101
70565
|
desc: "generate + extend",
|
|
70102
70566
|
defaultProvider: "Seedance 2.0 via fal.ai (Artificial Analysis #2 t2v + i2v, since v0.57)",
|
|
70103
70567
|
alsoAvailable: "Grok Imagine, Kling, Runway Gen-4.5, Google Veo",
|
|
70568
|
+
keyHint: "1-2 keys",
|
|
70104
70569
|
keys: [
|
|
70105
70570
|
{
|
|
70106
70571
|
configKey: "fal",
|
|
@@ -70121,6 +70586,15 @@ var AI_FEATURES = [
|
|
|
70121
70586
|
url: "https://fal.ai/dashboard/keys",
|
|
70122
70587
|
what: "ByteDance Seedance 2.0 text-to-video and image-to-video"
|
|
70123
70588
|
},
|
|
70589
|
+
extraKeys: [
|
|
70590
|
+
{
|
|
70591
|
+
configKey: "imgbb",
|
|
70592
|
+
envVar: "IMGBB_API_KEY",
|
|
70593
|
+
name: "ImgBB",
|
|
70594
|
+
url: "https://api.imgbb.com/",
|
|
70595
|
+
what: "Image hosting for Seedance image-to-video uploads"
|
|
70596
|
+
}
|
|
70597
|
+
],
|
|
70124
70598
|
defaultFor: { kind: "video", value: "seedance" }
|
|
70125
70599
|
},
|
|
70126
70600
|
{
|
|
@@ -70145,6 +70619,15 @@ var AI_FEATURES = [
|
|
|
70145
70619
|
url: "https://platform.klingai.com/",
|
|
70146
70620
|
what: "Kling video generation"
|
|
70147
70621
|
},
|
|
70622
|
+
extraKeys: [
|
|
70623
|
+
{
|
|
70624
|
+
configKey: "imgbb",
|
|
70625
|
+
envVar: "IMGBB_API_KEY",
|
|
70626
|
+
name: "ImgBB",
|
|
70627
|
+
url: "https://api.imgbb.com/",
|
|
70628
|
+
what: "Image hosting for Kling image-to-video uploads"
|
|
70629
|
+
}
|
|
70630
|
+
],
|
|
70148
70631
|
defaultFor: { kind: "video", value: "kling" }
|
|
70149
70632
|
},
|
|
70150
70633
|
{
|
|
@@ -70179,6 +70662,7 @@ var AI_FEATURES = [
|
|
|
70179
70662
|
desc: "TTS, SFX, music, voice clone",
|
|
70180
70663
|
defaultProvider: "ElevenLabs (paid, premium quality) \u2014 falls back to local Kokoro when no key (free, since v0.54)",
|
|
70181
70664
|
alsoAvailable: "Replicate MusicGen (music only)",
|
|
70665
|
+
keyHint: "0-1 key",
|
|
70182
70666
|
keys: [
|
|
70183
70667
|
{
|
|
70184
70668
|
configKey: "elevenlabs",
|
|
@@ -70224,6 +70708,7 @@ var AI_FEATURES = [
|
|
|
70224
70708
|
desc: "captions, grade, reframe, motion graphics",
|
|
70225
70709
|
defaultProvider: "Whisper (transcription) + Claude (reasoning)",
|
|
70226
70710
|
alsoAvailable: "",
|
|
70711
|
+
keyHint: "2 keys",
|
|
70227
70712
|
keys: [
|
|
70228
70713
|
{
|
|
70229
70714
|
configKey: "openai",
|
|
@@ -70257,8 +70742,11 @@ function applyProviderDefault(config2, choice) {
|
|
|
70257
70742
|
}
|
|
70258
70743
|
}
|
|
70259
70744
|
function isProviderChoiceConfigured(config2, choice) {
|
|
70260
|
-
|
|
70261
|
-
|
|
70745
|
+
const keys = [choice.key, ...getChoiceExtraKeys(config2, choice)].filter(
|
|
70746
|
+
(keyDef) => Boolean(keyDef)
|
|
70747
|
+
);
|
|
70748
|
+
for (const keyDef of keys) {
|
|
70749
|
+
const value = config2.providers[keyDef.configKey];
|
|
70262
70750
|
if (value) return true;
|
|
70263
70751
|
}
|
|
70264
70752
|
if (choice.defaultFor?.kind === "image") {
|
|
@@ -70269,6 +70757,96 @@ function isProviderChoiceConfigured(config2, choice) {
|
|
|
70269
70757
|
}
|
|
70270
70758
|
return false;
|
|
70271
70759
|
}
|
|
70760
|
+
function getConfiguredUploadProvider(config2) {
|
|
70761
|
+
return process.env.VIBE_UPLOAD_PROVIDER?.toLowerCase() === "s3" || config2.upload.provider === "s3" ? "s3" : "imgbb";
|
|
70762
|
+
}
|
|
70763
|
+
function hasVideoFeature(features) {
|
|
70764
|
+
return features.some((feature) => feature.label === "Videos");
|
|
70765
|
+
}
|
|
70766
|
+
async function promptOptionalValue(label, currentValue) {
|
|
70767
|
+
const suffix = currentValue ? source_default.dim(` [${currentValue}]`) : "";
|
|
70768
|
+
const value = await prompt2(source_default.cyan(` ${label}${suffix}: `));
|
|
70769
|
+
const trimmed = value.trim();
|
|
70770
|
+
return trimmed || currentValue;
|
|
70771
|
+
}
|
|
70772
|
+
async function configureUploadHost(config2) {
|
|
70773
|
+
console.log(source_default.dim("2b. Upload host"));
|
|
70774
|
+
console.log(source_default.bold("Where should image-to-video upload reference images?"));
|
|
70775
|
+
console.log(
|
|
70776
|
+
source_default.dim(
|
|
70777
|
+
" Seedance and Kling need a temporary HTTPS image URL when you start from a local image."
|
|
70778
|
+
)
|
|
70779
|
+
);
|
|
70780
|
+
console.log();
|
|
70781
|
+
const currentProvider = getConfiguredUploadProvider(config2);
|
|
70782
|
+
const selected = await promptSelect(
|
|
70783
|
+
source_default.cyan(" Upload host [1-3]: "),
|
|
70784
|
+
[
|
|
70785
|
+
`ImgBB ${source_default.dim("- easiest setup, third-party image hosting (default)")}`,
|
|
70786
|
+
`S3 ${source_default.dim("- your own bucket, better control/privacy")}`,
|
|
70787
|
+
`Back ${source_default.dim("(choose different features)")}`
|
|
70788
|
+
],
|
|
70789
|
+
currentProvider === "s3" ? 1 : 0,
|
|
70790
|
+
{ backIndex: 2 }
|
|
70791
|
+
);
|
|
70792
|
+
console.log();
|
|
70793
|
+
if (selected === 2) {
|
|
70794
|
+
return "back";
|
|
70795
|
+
}
|
|
70796
|
+
if (selected === 0) {
|
|
70797
|
+
config2.upload.provider = "imgbb";
|
|
70798
|
+
process.env.VIBE_UPLOAD_PROVIDER = "imgbb";
|
|
70799
|
+
console.log(source_default.dim(" ImgBB selected. Seedance/Kling setup will ask for IMGBB_API_KEY."));
|
|
70800
|
+
console.log();
|
|
70801
|
+
return "continue";
|
|
70802
|
+
}
|
|
70803
|
+
config2.upload.provider = "s3";
|
|
70804
|
+
process.env.VIBE_UPLOAD_PROVIDER = "s3";
|
|
70805
|
+
config2.upload.s3 = config2.upload.s3 ?? {};
|
|
70806
|
+
const envBucket = process.env.VIBE_UPLOAD_S3_BUCKET;
|
|
70807
|
+
const envRegion = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION;
|
|
70808
|
+
const envPrefix = process.env.VIBE_UPLOAD_S3_PREFIX;
|
|
70809
|
+
const envTtl = process.env.VIBE_UPLOAD_TTL_SECONDS;
|
|
70810
|
+
console.log(source_default.dim(" S3 selected. AWS credentials stay in your shell/.env/AWS profile."));
|
|
70811
|
+
console.log(source_default.dim(" Required at runtime: AWS credentials, region, and bucket."));
|
|
70812
|
+
console.log();
|
|
70813
|
+
const bucket = await promptOptionalValue("S3 bucket", envBucket ?? config2.upload.s3.bucket);
|
|
70814
|
+
const region = await promptOptionalValue("AWS region", envRegion ?? config2.upload.s3.region);
|
|
70815
|
+
const prefix = await promptOptionalValue(
|
|
70816
|
+
"S3 prefix",
|
|
70817
|
+
envPrefix ?? config2.upload.s3.prefix ?? "vibeframe/tmp"
|
|
70818
|
+
);
|
|
70819
|
+
const ttlValue = await promptOptionalValue(
|
|
70820
|
+
"Upload URL TTL seconds",
|
|
70821
|
+
envTtl ?? String(config2.upload.ttlSeconds ?? 3600)
|
|
70822
|
+
);
|
|
70823
|
+
if (bucket) config2.upload.s3.bucket = bucket;
|
|
70824
|
+
if (region) config2.upload.s3.region = region;
|
|
70825
|
+
if (prefix) config2.upload.s3.prefix = prefix;
|
|
70826
|
+
const ttlSeconds = ttlValue ? Number.parseInt(ttlValue, 10) : NaN;
|
|
70827
|
+
if (Number.isFinite(ttlSeconds) && ttlSeconds > 0) {
|
|
70828
|
+
config2.upload.ttlSeconds = ttlSeconds;
|
|
70829
|
+
}
|
|
70830
|
+
if (!config2.upload.s3.bucket || !config2.upload.s3.region) {
|
|
70831
|
+
console.log(
|
|
70832
|
+
source_default.yellow(
|
|
70833
|
+
" \u26A0 S3 selected, but bucket/region is incomplete. Set VIBE_UPLOAD_S3_BUCKET and AWS_REGION before image-to-video."
|
|
70834
|
+
)
|
|
70835
|
+
);
|
|
70836
|
+
} else {
|
|
70837
|
+
console.log(
|
|
70838
|
+
` ${source_default.green("\u2713")} S3 upload host configured ${source_default.dim(`(${config2.upload.s3.bucket}/${config2.upload.s3.prefix ?? "vibeframe/tmp"})`)}`
|
|
70839
|
+
);
|
|
70840
|
+
}
|
|
70841
|
+
console.log();
|
|
70842
|
+
return "continue";
|
|
70843
|
+
}
|
|
70844
|
+
function getChoiceExtraKeys(config2, choice) {
|
|
70845
|
+
if (config2.upload.provider === "s3") {
|
|
70846
|
+
return [];
|
|
70847
|
+
}
|
|
70848
|
+
return choice.extraKeys ?? [];
|
|
70849
|
+
}
|
|
70272
70850
|
function isFeatureConfigured(config2, feature) {
|
|
70273
70851
|
if (feature.providerChoices?.some((choice) => isProviderChoiceConfigured(config2, choice))) {
|
|
70274
70852
|
return true;
|
|
@@ -70378,6 +70956,7 @@ async function runNonInteractiveSetup(opts) {
|
|
|
70378
70956
|
}
|
|
70379
70957
|
async function runSetupWizard(fullSetup = false, scope = "user") {
|
|
70380
70958
|
console.log();
|
|
70959
|
+
loadEnv();
|
|
70381
70960
|
const cfgPath = getConfigPath(scope).replace(homedir5(), "~");
|
|
70382
70961
|
const scopeNote = scope === "project" ? "applies to this project only \xB7 add .vibeframe/ to .gitignore" : "applies to every project for this user";
|
|
70383
70962
|
console.log(source_default.bold.magenta("VibeFrame Setup") + source_default.bold(` \u2014 ${scope} scope`));
|
|
@@ -70520,13 +71099,16 @@ async function runAIFeaturesSetup(config2, _scope) {
|
|
|
70520
71099
|
)
|
|
70521
71100
|
);
|
|
70522
71101
|
console.log(source_default.dim(" Shortcut: enter alone picks only the highlighted feature."));
|
|
70523
|
-
console.log(
|
|
71102
|
+
console.log(
|
|
71103
|
+
source_default.dim(
|
|
71104
|
+
` Select ${source_default.bold("Back")} or press ${source_default.bold("Esc/\u2190")} to return to the setup checkpoint.`
|
|
71105
|
+
)
|
|
71106
|
+
);
|
|
70524
71107
|
console.log();
|
|
70525
71108
|
const backIndex = AI_FEATURES.length;
|
|
70526
71109
|
const featureLabels = [
|
|
70527
71110
|
...AI_FEATURES.map((f) => {
|
|
70528
|
-
const
|
|
70529
|
-
const tag = source_default.dim(`${keyCount} key${keyCount > 1 ? "s" : ""}`);
|
|
71111
|
+
const tag = source_default.dim(f.keyHint ?? `${f.keys.length} key${f.keys.length > 1 ? "s" : ""}`);
|
|
70530
71112
|
return `${f.label} ${source_default.dim(`(${f.desc})`)} ${tag}`;
|
|
70531
71113
|
}),
|
|
70532
71114
|
`Back ${source_default.dim("(return to setup checkpoint)")}`
|
|
@@ -70536,7 +71118,7 @@ async function runAIFeaturesSetup(config2, _scope) {
|
|
|
70536
71118
|
source_default.cyan(" Pick (e.g. 1,3 or 'all'): "),
|
|
70537
71119
|
featureLabels,
|
|
70538
71120
|
[...configuredFeatures, false],
|
|
70539
|
-
{ pickFocusedOnEnter: true,
|
|
71121
|
+
{ pickFocusedOnEnter: true, backIndex }
|
|
70540
71122
|
);
|
|
70541
71123
|
if (picked.includes(backIndex)) {
|
|
70542
71124
|
console.log();
|
|
@@ -70549,12 +71131,19 @@ async function runAIFeaturesSetup(config2, _scope) {
|
|
|
70549
71131
|
console.log();
|
|
70550
71132
|
return { tryCommand: "vibe --help", selectedFeatures: [] };
|
|
70551
71133
|
}
|
|
71134
|
+
if (hasVideoFeature(selectedFeatures)) {
|
|
71135
|
+
const uploadResult = await configureUploadHost(config2);
|
|
71136
|
+
if (uploadResult === "back") {
|
|
71137
|
+
console.log();
|
|
71138
|
+
continue featureSelection;
|
|
71139
|
+
}
|
|
71140
|
+
}
|
|
70552
71141
|
const plannedKeys = /* @__PURE__ */ new Map();
|
|
70553
71142
|
console.log(source_default.dim("3. Providers"));
|
|
70554
71143
|
console.log(source_default.bold("Choose providers for each selected feature"));
|
|
70555
71144
|
console.log(
|
|
70556
71145
|
source_default.dim(
|
|
70557
|
-
` Recommended default is highlighted. Press ${source_default.bold("enter")} to pick the highlighted provider,
|
|
71146
|
+
` Recommended default is highlighted. Press ${source_default.bold("enter")} to pick the highlighted provider, ${source_default.bold("space")} to choose multiple, or ${source_default.bold("Esc/\u2190")} to go back.`
|
|
70558
71147
|
)
|
|
70559
71148
|
);
|
|
70560
71149
|
console.log();
|
|
@@ -70571,7 +71160,12 @@ async function runAIFeaturesSetup(config2, _scope) {
|
|
|
70571
71160
|
(choice) => isProviderChoiceConfigured(config2, choice)
|
|
70572
71161
|
);
|
|
70573
71162
|
const providerLabels = feature.providerChoices.map((choice) => {
|
|
70574
|
-
const keyHint = choice.keyless ? source_default.dim("no key") : choice.key ? source_default.dim(
|
|
71163
|
+
const keyHint = choice.keyless ? source_default.dim("no key") : choice.key ? source_default.dim(
|
|
71164
|
+
[
|
|
71165
|
+
choice.key.envVar,
|
|
71166
|
+
...getChoiceExtraKeys(config2, choice).map((keyDef) => keyDef.envVar)
|
|
71167
|
+
].filter(Boolean).join(" + ")
|
|
71168
|
+
) : source_default.dim("no key");
|
|
70575
71169
|
const defaultHint = choice === feature.providerChoices?.[0] ? source_default.dim("recommended default") : "";
|
|
70576
71170
|
return [choice.label, defaultHint, source_default.dim(`- ${choice.desc}`), keyHint].filter(Boolean).join(" ");
|
|
70577
71171
|
});
|
|
@@ -70580,7 +71174,11 @@ async function runAIFeaturesSetup(config2, _scope) {
|
|
|
70580
71174
|
source_default.cyan(" Providers (arrow + enter picks one, space adds more): "),
|
|
70581
71175
|
[...providerLabels, `Back ${source_default.dim("(choose different features)")}`],
|
|
70582
71176
|
[...defaultSelected, false],
|
|
70583
|
-
{
|
|
71177
|
+
{
|
|
71178
|
+
pickFocusedOnEnter: true,
|
|
71179
|
+
preserveDefaultSelectionOnEnter: true,
|
|
71180
|
+
backIndex: backProviderIndex
|
|
71181
|
+
}
|
|
70584
71182
|
);
|
|
70585
71183
|
if (pickedProviders.includes(backProviderIndex)) {
|
|
70586
71184
|
console.log();
|
|
@@ -70593,6 +71191,9 @@ async function runAIFeaturesSetup(config2, _scope) {
|
|
|
70593
71191
|
}
|
|
70594
71192
|
for (const choice of selectedChoices) {
|
|
70595
71193
|
if (choice.key) addUniqueKey(keysForFeature, choice.key);
|
|
71194
|
+
for (const keyDef of getChoiceExtraKeys(config2, choice)) {
|
|
71195
|
+
addUniqueKey(keysForFeature, keyDef);
|
|
71196
|
+
}
|
|
70596
71197
|
}
|
|
70597
71198
|
if (selectedChoices.some((choice) => choice.keyless)) {
|
|
70598
71199
|
console.log(source_default.dim(" No API key needed for selected local provider(s)."));
|
|
@@ -71247,10 +71848,21 @@ OPENROUTER_API_KEY= # multiplexes any provider above
|
|
|
71247
71848
|
# \u2500\u2500 Media providers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
71248
71849
|
ELEVENLABS_API_KEY= # paid TTS / SFX / music (Kokoro is the free fallback)
|
|
71249
71850
|
FAL_KEY= # Seedance 2.0 \u2014 default video provider since v0.57
|
|
71851
|
+
IMGBB_API_KEY= # image hosting for Seedance/Kling image-to-video
|
|
71250
71852
|
RUNWAY_API_SECRET= # Runway Gen-4.5 video
|
|
71251
71853
|
KLING_API_KEY= # Kling video
|
|
71252
71854
|
REPLICATE_API_TOKEN= # MusicGen, real-esrgan, etc.
|
|
71253
|
-
|
|
71855
|
+
|
|
71856
|
+
# \u2500\u2500 Optional private upload host for image-to-video \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
71857
|
+
# Default is ImgBB. Set these only if you want temporary URLs from your own S3 bucket.
|
|
71858
|
+
# VIBE_UPLOAD_PROVIDER=s3 # imgbb | s3
|
|
71859
|
+
# VIBE_UPLOAD_TTL_SECONDS=3600
|
|
71860
|
+
# VIBE_UPLOAD_S3_BUCKET=
|
|
71861
|
+
# VIBE_UPLOAD_S3_PREFIX=vibeframe/tmp
|
|
71862
|
+
# AWS_REGION=
|
|
71863
|
+
# AWS_ACCESS_KEY_ID=
|
|
71864
|
+
# AWS_SECRET_ACCESS_KEY=
|
|
71865
|
+
# AWS_SESSION_TOKEN=
|
|
71254
71866
|
`;
|
|
71255
71867
|
}
|
|
71256
71868
|
var GITIGNORE_ADDITIONS = `
|