@vibeframe/cli 0.91.9 → 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 +1552 -959
- 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;
|
|
@@ -33353,29 +33399,61 @@ var init_video_extend = __esm({
|
|
|
33353
33399
|
});
|
|
33354
33400
|
|
|
33355
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
|
+
}
|
|
33356
33433
|
function resolveProvider(category) {
|
|
33357
33434
|
const candidates = getProvidersFor(category);
|
|
33358
33435
|
if (candidates.length === 0) return null;
|
|
33359
33436
|
if (configDefaults?.[category]) {
|
|
33360
33437
|
const preferred = candidates.find((c) => c.name === configDefaults[category]);
|
|
33361
|
-
if (preferred && (preferred
|
|
33438
|
+
if (preferred && hasCandidateKey(preferred)) {
|
|
33362
33439
|
return { name: preferred.name, label: preferred.label };
|
|
33363
33440
|
}
|
|
33364
33441
|
}
|
|
33365
33442
|
for (const candidate of candidates) {
|
|
33366
|
-
if (
|
|
33443
|
+
if (hasCandidateKey(candidate)) {
|
|
33367
33444
|
return { name: candidate.name, label: candidate.label };
|
|
33368
33445
|
}
|
|
33369
33446
|
}
|
|
33370
33447
|
return null;
|
|
33371
33448
|
}
|
|
33372
|
-
var configDefaults;
|
|
33449
|
+
var configDefaults, configProviderKeys;
|
|
33373
33450
|
var init_provider_resolver = __esm({
|
|
33374
33451
|
"src/utils/provider-resolver.ts"() {
|
|
33375
33452
|
"use strict";
|
|
33376
33453
|
init_dist2();
|
|
33377
33454
|
init_api_key();
|
|
33378
33455
|
configDefaults = null;
|
|
33456
|
+
configProviderKeys = null;
|
|
33379
33457
|
}
|
|
33380
33458
|
});
|
|
33381
33459
|
|
|
@@ -33412,13 +33490,26 @@ import { resolve as resolve23, dirname as dirname7 } from "node:path";
|
|
|
33412
33490
|
import { fileURLToPath } from "node:url";
|
|
33413
33491
|
import { writeFile as writeFile21, mkdir as mkdir11 } from "node:fs/promises";
|
|
33414
33492
|
function registerImageCommand(parent) {
|
|
33415
|
-
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
|
+
`
|
|
33416
33506
|
Examples:
|
|
33417
33507
|
$ vibe generate image "a sunset over the ocean" -o sunset.png
|
|
33418
33508
|
$ vibe gen img "logo design" -o logo.png -p openai
|
|
33419
33509
|
$ vibe gen img "landscape photo" -o wide.png -r 16:9
|
|
33420
33510
|
$ vibe gen img "portrait" -o portrait.png -p gemini -m pro
|
|
33421
|
-
$ vibe gen img "product shot" --dry-run --json`
|
|
33511
|
+
$ vibe gen img "product shot" --dry-run --json`
|
|
33512
|
+
).action(async (prompt3, options) => {
|
|
33422
33513
|
const startedAt = Date.now();
|
|
33423
33514
|
try {
|
|
33424
33515
|
if (!prompt3) {
|
|
@@ -33429,10 +33520,7 @@ Examples:
|
|
|
33429
33520
|
}
|
|
33430
33521
|
} else {
|
|
33431
33522
|
exitWithError(
|
|
33432
|
-
usageError(
|
|
33433
|
-
"Prompt argument is required.",
|
|
33434
|
-
"Usage: vibe generate image <prompt>"
|
|
33435
|
-
)
|
|
33523
|
+
usageError("Prompt argument is required.", "Usage: vibe generate image <prompt>")
|
|
33436
33524
|
);
|
|
33437
33525
|
}
|
|
33438
33526
|
}
|
|
@@ -33440,13 +33528,16 @@ Examples:
|
|
|
33440
33528
|
if (options.output) {
|
|
33441
33529
|
validateOutputPath(options.output);
|
|
33442
33530
|
}
|
|
33531
|
+
await loadProviderDefaults();
|
|
33443
33532
|
if (options.count !== void 0) {
|
|
33444
33533
|
const n = parseInt(options.count, 10);
|
|
33445
33534
|
if (!Number.isFinite(n) || n < 1 || n > 10) {
|
|
33446
|
-
exitWithError(
|
|
33447
|
-
|
|
33448
|
-
|
|
33449
|
-
|
|
33535
|
+
exitWithError(
|
|
33536
|
+
usageError(
|
|
33537
|
+
`Invalid --count: ${options.count}`,
|
|
33538
|
+
"Must be an integer between 1 and 10."
|
|
33539
|
+
)
|
|
33540
|
+
);
|
|
33450
33541
|
}
|
|
33451
33542
|
}
|
|
33452
33543
|
const imageRegistry = getProvidersFor("image");
|
|
@@ -33511,11 +33602,9 @@ Examples:
|
|
|
33511
33602
|
const apiKey = await requireApiKey(envKey, providerName, options.apiKey);
|
|
33512
33603
|
const spinner2 = ora(`Generating image with ${providerName}...`).start();
|
|
33513
33604
|
if (provider === "openai") {
|
|
33514
|
-
const { result, modelLabel } = await executeOpenAIImageGenerate(
|
|
33515
|
-
|
|
33516
|
-
|
|
33517
|
-
{ apiKey }
|
|
33518
|
-
);
|
|
33605
|
+
const { result, modelLabel } = await executeOpenAIImageGenerate(prompt3, options, {
|
|
33606
|
+
apiKey
|
|
33607
|
+
});
|
|
33519
33608
|
if (!result.success || !result.images) {
|
|
33520
33609
|
spinner2.fail(result.error || "Image generation failed");
|
|
33521
33610
|
exitWithError(apiError(result.error || "Image generation failed", true));
|
|
@@ -33737,9 +33826,7 @@ Examples:
|
|
|
33737
33826
|
spinner2.fail(result.error || "Image generation failed");
|
|
33738
33827
|
exitWithError(apiError(result.error || "Image generation failed", true));
|
|
33739
33828
|
}
|
|
33740
|
-
spinner2.succeed(
|
|
33741
|
-
source_default.green(`Generated ${result.images.length} image(s) with xAI Grok`)
|
|
33742
|
-
);
|
|
33829
|
+
spinner2.succeed(source_default.green(`Generated ${result.images.length} image(s) with xAI Grok`));
|
|
33743
33830
|
if (isJsonMode()) {
|
|
33744
33831
|
const outputPath = options.output ? resolve23(process.cwd(), options.output) : void 0;
|
|
33745
33832
|
if (outputPath && result.images.length > 0) {
|
|
@@ -34972,606 +35059,206 @@ var init_video_utils = __esm({
|
|
|
34972
35059
|
}
|
|
34973
35060
|
});
|
|
34974
35061
|
|
|
34975
|
-
// src/
|
|
34976
|
-
|
|
34977
|
-
|
|
34978
|
-
|
|
34979
|
-
|
|
34980
|
-
|
|
34981
|
-
|
|
34982
|
-
|
|
34983
|
-
aspectRatio: options.aspectRatio,
|
|
34984
|
-
referenceImage: options.referenceImage
|
|
34985
|
-
});
|
|
34986
|
-
if (result.status !== "failed" && result.id) {
|
|
34987
|
-
return { requestId: result.id };
|
|
34988
|
-
}
|
|
34989
|
-
const providerErr = result.error || "Grok returned failed status";
|
|
34990
|
-
if (attempt < maxRetries) {
|
|
34991
|
-
onProgress?.(
|
|
34992
|
-
`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`
|
|
34993
|
-
);
|
|
34994
|
-
await sleep(RETRY_DELAY_MS);
|
|
34995
|
-
} else {
|
|
34996
|
-
console.error(source_default.dim(`
|
|
34997
|
-
[Grok error: ${providerErr}]`));
|
|
34998
|
-
}
|
|
34999
|
-
} catch (err) {
|
|
35000
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
35001
|
-
if (attempt < maxRetries) {
|
|
35002
|
-
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
35003
|
-
await sleep(RETRY_DELAY_MS);
|
|
35004
|
-
} else {
|
|
35005
|
-
console.error(source_default.dim(`
|
|
35006
|
-
[Grok error: ${errMsg}]`));
|
|
35007
|
-
}
|
|
35008
|
-
}
|
|
35009
|
-
}
|
|
35010
|
-
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;
|
|
35011
35070
|
}
|
|
35012
|
-
|
|
35013
|
-
|
|
35014
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
35015
|
-
try {
|
|
35016
|
-
const result = await kling.generateVideo(prompt3, {
|
|
35017
|
-
prompt: prompt3,
|
|
35018
|
-
// Pass reference image (base64 or URL) - KlingProvider handles v1.5 fallback for base64
|
|
35019
|
-
referenceImage: options.referenceImage,
|
|
35020
|
-
duration: options.duration,
|
|
35021
|
-
aspectRatio: options.aspectRatio,
|
|
35022
|
-
mode: "std"
|
|
35023
|
-
// std mode for faster generation
|
|
35024
|
-
});
|
|
35025
|
-
if (result.status !== "failed" && result.id) {
|
|
35026
|
-
return {
|
|
35027
|
-
taskId: result.id,
|
|
35028
|
-
type: options.referenceImage ? "image2video" : "text2video"
|
|
35029
|
-
};
|
|
35030
|
-
}
|
|
35031
|
-
const providerErr = result.error || "Kling returned failed status";
|
|
35032
|
-
if (attempt < maxRetries) {
|
|
35033
|
-
onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
35034
|
-
await sleep(RETRY_DELAY_MS);
|
|
35035
|
-
} else {
|
|
35036
|
-
console.error(source_default.dim(`
|
|
35037
|
-
[Kling error: ${providerErr}]`));
|
|
35038
|
-
}
|
|
35039
|
-
} catch (err) {
|
|
35040
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
35041
|
-
if (attempt < maxRetries) {
|
|
35042
|
-
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
35043
|
-
await sleep(RETRY_DELAY_MS);
|
|
35044
|
-
} else {
|
|
35045
|
-
console.error(source_default.dim(`
|
|
35046
|
-
[Kling error: ${errMsg}]`));
|
|
35047
|
-
}
|
|
35048
|
-
}
|
|
35049
|
-
}
|
|
35050
|
-
return null;
|
|
35071
|
+
function safePrefix(prefix) {
|
|
35072
|
+
return (prefix ?? "vibeframe/tmp").replace(/^\/+|\/+$/g, "");
|
|
35051
35073
|
}
|
|
35052
|
-
|
|
35053
|
-
|
|
35054
|
-
|
|
35055
|
-
|
|
35056
|
-
|
|
35057
|
-
|
|
35058
|
-
|
|
35059
|
-
|
|
35060
|
-
});
|
|
35061
|
-
if (result.status !== "failed" && result.id) {
|
|
35062
|
-
return { taskId: result.id };
|
|
35063
|
-
}
|
|
35064
|
-
const providerErr = result.error || "Runway returned failed status";
|
|
35065
|
-
if (attempt < maxRetries) {
|
|
35066
|
-
onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
35067
|
-
await sleep(RETRY_DELAY_MS);
|
|
35068
|
-
} else {
|
|
35069
|
-
console.error(source_default.dim(`
|
|
35070
|
-
[Runway error: ${providerErr}]`));
|
|
35071
|
-
}
|
|
35072
|
-
} catch (err) {
|
|
35073
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
35074
|
-
if (attempt < maxRetries) {
|
|
35075
|
-
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
35076
|
-
await sleep(RETRY_DELAY_MS);
|
|
35077
|
-
} else {
|
|
35078
|
-
console.error(source_default.dim(`
|
|
35079
|
-
[Runway error: ${errMsg}]`));
|
|
35080
|
-
}
|
|
35081
|
-
}
|
|
35082
|
-
}
|
|
35083
|
-
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";
|
|
35084
35082
|
}
|
|
35085
|
-
|
|
35086
|
-
|
|
35087
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
35088
|
-
try {
|
|
35089
|
-
const result = await gemini.generateVideo(prompt3, {
|
|
35090
|
-
prompt: prompt3,
|
|
35091
|
-
referenceImage: options.referenceImage,
|
|
35092
|
-
duration: options.duration,
|
|
35093
|
-
aspectRatio: options.aspectRatio,
|
|
35094
|
-
model: "veo-3.1-fast-generate-preview"
|
|
35095
|
-
});
|
|
35096
|
-
if (result.status !== "failed" && result.id) {
|
|
35097
|
-
return { operationName: result.id };
|
|
35098
|
-
}
|
|
35099
|
-
const providerErr = result.error || "Veo returned failed status";
|
|
35100
|
-
if (attempt < maxRetries) {
|
|
35101
|
-
onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
35102
|
-
await sleep(RETRY_DELAY_MS);
|
|
35103
|
-
} else {
|
|
35104
|
-
console.error(source_default.dim(`
|
|
35105
|
-
[Veo error: ${providerErr}]`));
|
|
35106
|
-
}
|
|
35107
|
-
} catch (err) {
|
|
35108
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
35109
|
-
if (attempt < maxRetries) {
|
|
35110
|
-
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
35111
|
-
await sleep(RETRY_DELAY_MS);
|
|
35112
|
-
} else {
|
|
35113
|
-
console.error(source_default.dim(`
|
|
35114
|
-
[Veo error: ${errMsg}]`));
|
|
35115
|
-
}
|
|
35116
|
-
}
|
|
35117
|
-
}
|
|
35118
|
-
return null;
|
|
35083
|
+
function hmac(key, value) {
|
|
35084
|
+
return createHmac2("sha256", key).update(value).digest();
|
|
35119
35085
|
}
|
|
35120
|
-
|
|
35121
|
-
"
|
|
35122
|
-
|
|
35123
|
-
|
|
35124
|
-
|
|
35125
|
-
|
|
35126
|
-
}
|
|
35127
|
-
|
|
35128
|
-
|
|
35129
|
-
|
|
35130
|
-
|
|
35131
|
-
|
|
35132
|
-
|
|
35133
|
-
const
|
|
35134
|
-
|
|
35135
|
-
|
|
35136
|
-
|
|
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"
|
|
35137
35108
|
};
|
|
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
|
-
|
|
35171
|
-
|
|
35172
|
-
|
|
35173
|
-
|
|
35174
|
-
|
|
35175
|
-
|
|
35176
|
-
|
|
35177
|
-
|
|
35178
|
-
|
|
35179
|
-
|
|
35180
|
-
|
|
35181
|
-
openai: { envVar: "OPENAI_API_KEY", name: "OpenAI" },
|
|
35182
|
-
gemini: { envVar: "GOOGLE_API_KEY", name: "Google" },
|
|
35183
|
-
grok: { envVar: "XAI_API_KEY", name: "xAI" }
|
|
35184
|
-
};
|
|
35185
|
-
const info = imageKeyMap[imageProvider];
|
|
35186
|
-
if (!info) {
|
|
35187
|
-
return { ...result, error: `Invalid imageProvider: ${imageProvider}` };
|
|
35188
|
-
}
|
|
35189
|
-
imageApiKey = await getApiKey(info.envVar, info.name) ?? void 0;
|
|
35190
|
-
if (!imageApiKey) {
|
|
35191
|
-
return { ...result, error: `${info.name} API key required. Run 'vibe setup' or set ${info.envVar} in .env` };
|
|
35192
|
-
}
|
|
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
|
|
35193
35152
|
}
|
|
35194
|
-
|
|
35195
|
-
|
|
35196
|
-
|
|
35197
|
-
|
|
35198
|
-
|
|
35199
|
-
|
|
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
|
+
);
|
|
35200
35166
|
}
|
|
35201
|
-
|
|
35202
|
-
|
|
35203
|
-
|
|
35204
|
-
|
|
35205
|
-
|
|
35206
|
-
|
|
35207
|
-
|
|
35208
|
-
|
|
35209
|
-
|
|
35210
|
-
|
|
35211
|
-
|
|
35212
|
-
|
|
35213
|
-
|
|
35214
|
-
|
|
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
|
|
35215
35181
|
});
|
|
35216
|
-
|
|
35217
|
-
|
|
35218
|
-
|
|
35219
|
-
|
|
35220
|
-
|
|
35221
|
-
|
|
35222
|
-
}
|
|
35223
|
-
|
|
35224
|
-
|
|
35225
|
-
options.onProgress?.(`Scene ${sceneNum}: regenerating image...`);
|
|
35226
|
-
const imageProvider = options.imageProvider || "openai";
|
|
35227
|
-
const characterDesc = segment.characterDescription || segments[0]?.characterDescription;
|
|
35228
|
-
let imagePrompt = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
|
|
35229
|
-
if (characterDesc) {
|
|
35230
|
-
imagePrompt = `${imagePrompt}
|
|
35231
|
-
|
|
35232
|
-
IMPORTANT - Character appearance must match exactly: ${characterDesc}`;
|
|
35233
|
-
}
|
|
35234
|
-
let referenceImageBuffer;
|
|
35235
|
-
const refSceneNum = options.referenceScene;
|
|
35236
|
-
if (refSceneNum && refSceneNum >= 1 && refSceneNum <= segments.length && refSceneNum !== sceneNum) {
|
|
35237
|
-
const refImagePath = resolve25(outputDir, `scene-${refSceneNum}.png`);
|
|
35238
|
-
if (existsSync14(refImagePath)) {
|
|
35239
|
-
referenceImageBuffer = await readFile13(refImagePath);
|
|
35240
|
-
}
|
|
35241
|
-
} else if (!refSceneNum) {
|
|
35242
|
-
for (let i = 1; i <= segments.length; i++) {
|
|
35243
|
-
if (i !== sceneNum) {
|
|
35244
|
-
const otherImagePath = resolve25(outputDir, `scene-${i}.png`);
|
|
35245
|
-
if (existsSync14(otherImagePath)) {
|
|
35246
|
-
referenceImageBuffer = await readFile13(otherImagePath);
|
|
35247
|
-
break;
|
|
35248
|
-
}
|
|
35249
|
-
}
|
|
35250
|
-
}
|
|
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}`);
|
|
35251
35191
|
}
|
|
35252
|
-
|
|
35253
|
-
|
|
35254
|
-
|
|
35255
|
-
|
|
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()
|
|
35256
35196
|
};
|
|
35257
|
-
let imageBuffer;
|
|
35258
|
-
let imageUrl;
|
|
35259
|
-
if (imageProvider === "openai") {
|
|
35260
|
-
const openaiImage = new OpenAIImageProvider();
|
|
35261
|
-
await openaiImage.initialize({ apiKey: imageApiKey });
|
|
35262
|
-
const imageResult = await openaiImage.generateImage(imagePrompt, {
|
|
35263
|
-
size: dalleImageSizes[options.aspectRatio || "16:9"] || "1536x1024",
|
|
35264
|
-
quality: "standard"
|
|
35265
|
-
});
|
|
35266
|
-
if (imageResult.success && imageResult.images?.[0]) {
|
|
35267
|
-
const img = imageResult.images[0];
|
|
35268
|
-
if (img.base64) imageBuffer = Buffer.from(img.base64, "base64");
|
|
35269
|
-
else if (img.url) imageUrl = img.url;
|
|
35270
|
-
}
|
|
35271
|
-
} else if (imageProvider === "gemini") {
|
|
35272
|
-
const gemini = new GeminiProvider();
|
|
35273
|
-
await gemini.initialize({ apiKey: imageApiKey });
|
|
35274
|
-
if (referenceImageBuffer) {
|
|
35275
|
-
const simplifiedVisuals = segment.visuals.split(/[,.]/).find(
|
|
35276
|
-
(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")
|
|
35277
|
-
) || segment.visuals.split(".")[0];
|
|
35278
|
-
const editPrompt = `Generate a new image showing the SAME SINGLE person from the reference image in a new scene.
|
|
35279
|
-
|
|
35280
|
-
REFERENCE: Look at the person in the reference image - their face, hair, build, and overall appearance.
|
|
35281
|
-
|
|
35282
|
-
NEW SCENE: ${simplifiedVisuals}
|
|
35283
|
-
|
|
35284
|
-
CRITICAL RULES:
|
|
35285
|
-
1. Show ONLY ONE person - the exact same individual from the reference image
|
|
35286
|
-
2. The person must have the IDENTICAL face, hair style, and body type
|
|
35287
|
-
3. Do NOT show multiple people or duplicate the character
|
|
35288
|
-
4. Create a single moment in time, one pose, one action
|
|
35289
|
-
5. Match the art style and quality of the reference image
|
|
35290
|
-
|
|
35291
|
-
Generate the single-person scene image now.`;
|
|
35292
|
-
const imageResult = await gemini.editImage([referenceImageBuffer], editPrompt, {
|
|
35293
|
-
aspectRatio: options.aspectRatio || "16:9"
|
|
35294
|
-
});
|
|
35295
|
-
if (imageResult.success && imageResult.images?.[0]?.base64) {
|
|
35296
|
-
imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
|
|
35297
|
-
}
|
|
35298
|
-
} else {
|
|
35299
|
-
const imageResult = await gemini.generateImage(imagePrompt, {
|
|
35300
|
-
aspectRatio: options.aspectRatio || "16:9"
|
|
35301
|
-
});
|
|
35302
|
-
if (imageResult.success && imageResult.images?.[0]?.base64) {
|
|
35303
|
-
imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
|
|
35304
|
-
}
|
|
35305
|
-
}
|
|
35306
|
-
} else if (imageProvider === "grok") {
|
|
35307
|
-
const grok = new GrokProvider();
|
|
35308
|
-
await grok.initialize({ apiKey: imageApiKey });
|
|
35309
|
-
const imageResult = await grok.generateImage(imagePrompt, {
|
|
35310
|
-
aspectRatio: options.aspectRatio || "16:9"
|
|
35311
|
-
});
|
|
35312
|
-
if (imageResult.success && imageResult.images?.[0]) {
|
|
35313
|
-
const img = imageResult.images[0];
|
|
35314
|
-
if (img.base64) imageBuffer = Buffer.from(img.base64, "base64");
|
|
35315
|
-
else if (img.url) imageUrl = img.url;
|
|
35316
|
-
}
|
|
35317
|
-
}
|
|
35318
|
-
if (imageBuffer) {
|
|
35319
|
-
await writeFile23(imagePath, imageBuffer);
|
|
35320
|
-
} else if (imageUrl) {
|
|
35321
|
-
const response = await fetch(imageUrl);
|
|
35322
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
35323
|
-
await writeFile23(imagePath, buffer);
|
|
35324
|
-
} else {
|
|
35325
|
-
sceneFailed = true;
|
|
35326
|
-
}
|
|
35327
|
-
}
|
|
35328
|
-
if (sceneFailed) {
|
|
35329
|
-
result.failedScenes.push(sceneNum);
|
|
35330
|
-
continue;
|
|
35331
35197
|
}
|
|
35332
|
-
|
|
35333
|
-
|
|
35334
|
-
|
|
35335
|
-
|
|
35336
|
-
|
|
35337
|
-
|
|
35338
|
-
|
|
35339
|
-
|
|
35340
|
-
const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
|
|
35341
|
-
if (options.generator === "grok") {
|
|
35342
|
-
const grok = new GrokProvider();
|
|
35343
|
-
await grok.initialize({ apiKey: videoApiKey });
|
|
35344
|
-
const ext = extname3(imagePath).toLowerCase().slice(1);
|
|
35345
|
-
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
35346
|
-
const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
35347
|
-
const grokDuration = Math.min(15, Math.max(1, segment.duration));
|
|
35348
|
-
const taskResult = await generateVideoWithRetryGrok(
|
|
35349
|
-
grok,
|
|
35350
|
-
segment,
|
|
35351
|
-
{
|
|
35352
|
-
duration: grokDuration,
|
|
35353
|
-
aspectRatio: options.aspectRatio || "16:9",
|
|
35354
|
-
referenceImage
|
|
35355
|
-
},
|
|
35356
|
-
maxRetries
|
|
35357
|
-
);
|
|
35358
|
-
if (taskResult) {
|
|
35359
|
-
try {
|
|
35360
|
-
const waitResult = await grok.waitForCompletion(taskResult.requestId, void 0, 3e5);
|
|
35361
|
-
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
35362
|
-
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
35363
|
-
await writeFile23(videoPath, buffer);
|
|
35364
|
-
const targetDuration = segment.duration;
|
|
35365
|
-
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
35366
|
-
if (actualVideoDuration < targetDuration - 0.1) {
|
|
35367
|
-
const extendedPath = resolve25(outputDir, `scene-${sceneNum}-extended.mp4`);
|
|
35368
|
-
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
35369
|
-
await unlink2(videoPath);
|
|
35370
|
-
await rename2(extendedPath, videoPath);
|
|
35371
|
-
}
|
|
35372
|
-
result.regeneratedScenes.push(sceneNum);
|
|
35373
|
-
} else {
|
|
35374
|
-
logSceneFailure("Grok", `scene ${sceneNum}`, waitResult);
|
|
35375
|
-
result.failedScenes.push(sceneNum);
|
|
35376
|
-
}
|
|
35377
|
-
} catch (err) {
|
|
35378
|
-
logSceneFailure("Grok", `scene ${sceneNum}`, err);
|
|
35379
|
-
result.failedScenes.push(sceneNum);
|
|
35380
|
-
}
|
|
35381
|
-
} else {
|
|
35382
|
-
result.failedScenes.push(sceneNum);
|
|
35383
|
-
}
|
|
35384
|
-
} else if (options.generator === "veo") {
|
|
35385
|
-
const veo = new GeminiProvider();
|
|
35386
|
-
await veo.initialize({ apiKey: videoApiKey });
|
|
35387
|
-
const ext = extname3(imagePath).toLowerCase().slice(1);
|
|
35388
|
-
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
35389
|
-
const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
35390
|
-
const veoDuration = segment.duration > 6 ? 8 : segment.duration > 4 ? 6 : 4;
|
|
35391
|
-
const taskResult = await generateVideoWithRetryVeo(
|
|
35392
|
-
veo,
|
|
35393
|
-
segment,
|
|
35394
|
-
{
|
|
35395
|
-
duration: veoDuration,
|
|
35396
|
-
aspectRatio: options.aspectRatio || "16:9",
|
|
35397
|
-
referenceImage
|
|
35398
|
-
},
|
|
35399
|
-
maxRetries
|
|
35400
|
-
);
|
|
35401
|
-
if (taskResult) {
|
|
35402
|
-
try {
|
|
35403
|
-
const waitResult = await veo.waitForVideoCompletion(taskResult.operationName, void 0, 3e5);
|
|
35404
|
-
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
35405
|
-
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
35406
|
-
await writeFile23(videoPath, buffer);
|
|
35407
|
-
const targetDuration = segment.duration;
|
|
35408
|
-
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
35409
|
-
if (actualVideoDuration < targetDuration - 0.1) {
|
|
35410
|
-
const extendedPath = resolve25(outputDir, `scene-${sceneNum}-extended.mp4`);
|
|
35411
|
-
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
35412
|
-
await unlink2(videoPath);
|
|
35413
|
-
await rename2(extendedPath, videoPath);
|
|
35414
|
-
}
|
|
35415
|
-
result.regeneratedScenes.push(sceneNum);
|
|
35416
|
-
} else {
|
|
35417
|
-
logSceneFailure("Veo", `scene ${sceneNum}`, waitResult);
|
|
35418
|
-
result.failedScenes.push(sceneNum);
|
|
35419
|
-
}
|
|
35420
|
-
} catch (err) {
|
|
35421
|
-
logSceneFailure("Veo", `scene ${sceneNum}`, err);
|
|
35422
|
-
result.failedScenes.push(sceneNum);
|
|
35423
|
-
}
|
|
35424
|
-
} else {
|
|
35425
|
-
result.failedScenes.push(sceneNum);
|
|
35426
|
-
}
|
|
35427
|
-
} else if (options.generator === "kling" || !options.generator) {
|
|
35428
|
-
const kling = new KlingProvider();
|
|
35429
|
-
await kling.initialize({ apiKey: videoApiKey });
|
|
35430
|
-
if (!kling.isConfigured()) {
|
|
35431
|
-
result.failedScenes.push(sceneNum);
|
|
35432
|
-
continue;
|
|
35433
|
-
}
|
|
35434
|
-
const imgbbApiKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
|
|
35435
|
-
let imageUrl;
|
|
35436
|
-
if (imgbbApiKey) {
|
|
35437
|
-
const uploadResult = await uploadToImgbb(imageBuffer, imgbbApiKey);
|
|
35438
|
-
if (uploadResult.success && uploadResult.url) {
|
|
35439
|
-
imageUrl = uploadResult.url;
|
|
35440
|
-
}
|
|
35441
|
-
}
|
|
35442
|
-
const taskResult = await generateVideoWithRetryKling(
|
|
35443
|
-
kling,
|
|
35444
|
-
segment,
|
|
35445
|
-
{
|
|
35446
|
-
duration: videoDuration,
|
|
35447
|
-
aspectRatio: options.aspectRatio || "16:9",
|
|
35448
|
-
referenceImage: imageUrl
|
|
35449
|
-
},
|
|
35450
|
-
maxRetries
|
|
35451
|
-
);
|
|
35452
|
-
if (taskResult) {
|
|
35453
|
-
try {
|
|
35454
|
-
const waitResult = await kling.waitForCompletion(taskResult.taskId, taskResult.type, void 0, 6e5);
|
|
35455
|
-
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
35456
|
-
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
35457
|
-
await writeFile23(videoPath, buffer);
|
|
35458
|
-
await extendVideoToTarget(
|
|
35459
|
-
videoPath,
|
|
35460
|
-
segment.duration,
|
|
35461
|
-
outputDir,
|
|
35462
|
-
`Scene ${sceneNum}`,
|
|
35463
|
-
{
|
|
35464
|
-
kling,
|
|
35465
|
-
videoId: waitResult.videoId,
|
|
35466
|
-
onProgress: options.onProgress
|
|
35467
|
-
}
|
|
35468
|
-
);
|
|
35469
|
-
result.regeneratedScenes.push(sceneNum);
|
|
35470
|
-
} else {
|
|
35471
|
-
logSceneFailure("Kling", `scene ${sceneNum}`, waitResult);
|
|
35472
|
-
result.failedScenes.push(sceneNum);
|
|
35473
|
-
}
|
|
35474
|
-
} catch (err) {
|
|
35475
|
-
logSceneFailure("Kling", `scene ${sceneNum}`, err);
|
|
35476
|
-
result.failedScenes.push(sceneNum);
|
|
35477
|
-
}
|
|
35478
|
-
} else {
|
|
35479
|
-
result.failedScenes.push(sceneNum);
|
|
35480
|
-
}
|
|
35481
|
-
} else {
|
|
35482
|
-
const runway = new RunwayProvider();
|
|
35483
|
-
await runway.initialize({ apiKey: videoApiKey });
|
|
35484
|
-
const ext = extname3(imagePath).toLowerCase().slice(1);
|
|
35485
|
-
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
35486
|
-
const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
35487
|
-
const aspectRatio = options.aspectRatio === "1:1" ? "16:9" : options.aspectRatio || "16:9";
|
|
35488
|
-
const taskResult = await generateVideoWithRetryRunway(
|
|
35489
|
-
runway,
|
|
35490
|
-
segment,
|
|
35491
|
-
referenceImage,
|
|
35492
|
-
{ duration: videoDuration, aspectRatio },
|
|
35493
|
-
maxRetries
|
|
35494
|
-
);
|
|
35495
|
-
if (taskResult) {
|
|
35496
|
-
try {
|
|
35497
|
-
const waitResult = await runway.waitForCompletion(taskResult.taskId, void 0, 3e5);
|
|
35498
|
-
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
35499
|
-
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
35500
|
-
await writeFile23(videoPath, buffer);
|
|
35501
|
-
const targetDuration = segment.duration;
|
|
35502
|
-
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
35503
|
-
if (actualVideoDuration < targetDuration - 0.1) {
|
|
35504
|
-
const extendedPath = resolve25(outputDir, `scene-${sceneNum}-extended.mp4`);
|
|
35505
|
-
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
35506
|
-
await unlink2(videoPath);
|
|
35507
|
-
await rename2(extendedPath, videoPath);
|
|
35508
|
-
}
|
|
35509
|
-
result.regeneratedScenes.push(sceneNum);
|
|
35510
|
-
} else {
|
|
35511
|
-
logSceneFailure("Runway", `scene ${sceneNum}`, waitResult);
|
|
35512
|
-
result.failedScenes.push(sceneNum);
|
|
35513
|
-
}
|
|
35514
|
-
} catch (err) {
|
|
35515
|
-
logSceneFailure("Runway", `scene ${sceneNum}`, err);
|
|
35516
|
-
result.failedScenes.push(sceneNum);
|
|
35517
|
-
}
|
|
35518
|
-
} else {
|
|
35519
|
-
result.failedScenes.push(sceneNum);
|
|
35520
|
-
}
|
|
35521
|
-
}
|
|
35522
|
-
} else if (!sceneFailed) {
|
|
35523
|
-
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.");
|
|
35524
35206
|
}
|
|
35525
|
-
|
|
35526
|
-
|
|
35527
|
-
|
|
35528
|
-
for (const segment of segments) {
|
|
35529
|
-
segment.startTime = currentTime;
|
|
35530
|
-
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"}`);
|
|
35531
35210
|
}
|
|
35532
|
-
|
|
35533
|
-
await writeFile23(storyboardPath, serialized, "utf-8");
|
|
35211
|
+
return { provider: "imgbb", url: result.url };
|
|
35534
35212
|
}
|
|
35535
|
-
|
|
35536
|
-
return result;
|
|
35537
|
-
} catch (error) {
|
|
35538
|
-
return {
|
|
35539
|
-
...result,
|
|
35540
|
-
error: error instanceof Error ? error.message : String(error)
|
|
35541
|
-
};
|
|
35542
|
-
}
|
|
35213
|
+
};
|
|
35543
35214
|
}
|
|
35544
|
-
var
|
|
35545
|
-
|
|
35546
|
-
"src/commands/ai-script-pipeline.ts"() {
|
|
35215
|
+
var init_upload_host = __esm({
|
|
35216
|
+
"src/utils/upload-host.ts"() {
|
|
35547
35217
|
"use strict";
|
|
35548
|
-
import_yaml5 = __toESM(require_dist(), 1);
|
|
35549
|
-
init_dist2();
|
|
35550
|
-
init_api_key();
|
|
35551
35218
|
init_config();
|
|
35552
|
-
init_audio();
|
|
35553
|
-
init_ai_helpers();
|
|
35554
35219
|
init_video_utils();
|
|
35555
|
-
init_video_providers();
|
|
35556
35220
|
}
|
|
35557
35221
|
});
|
|
35558
35222
|
|
|
35559
35223
|
// src/commands/generate/video.ts
|
|
35560
|
-
import { resolve as
|
|
35561
|
-
import { readFile as
|
|
35224
|
+
import { resolve as resolve25 } from "node:path";
|
|
35225
|
+
import { readFile as readFile13, writeFile as writeFile23 } from "node:fs/promises";
|
|
35562
35226
|
function registerVideoCommand(parent) {
|
|
35563
|
-
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(
|
|
35564
35234
|
"-d, --duration <sec>",
|
|
35565
35235
|
"Duration in seconds. Seedance accepts 4-15; Kling accepts 5 or 10; Veo maps to 6 or 8.",
|
|
35566
35236
|
"5"
|
|
35567
|
-
).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
|
+
`
|
|
35568
35254
|
Examples:
|
|
35569
35255
|
$ vibe generate video "dancing cat" -o cat.mp4 # Seedance when FAL_KEY is set
|
|
35570
35256
|
$ vibe gen vid "cinematic city timelapse" -o city.mp4 -p seedance # Seedance via fal.ai
|
|
35571
35257
|
$ vibe gen vid "city timelapse" -o city.mp4 -p kling # Kling
|
|
35572
35258
|
$ vibe gen vid "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
|
|
35573
35259
|
$ vibe gen vid "ocean waves" -o waves.mp4 -p veo --resolution 1080p # Veo
|
|
35574
|
-
$ 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) => {
|
|
35575
35262
|
const startedAt = Date.now();
|
|
35576
35263
|
try {
|
|
35577
35264
|
if (!prompt3) {
|
|
@@ -35582,10 +35269,7 @@ Examples:
|
|
|
35582
35269
|
}
|
|
35583
35270
|
} else {
|
|
35584
35271
|
exitWithError(
|
|
35585
|
-
usageError(
|
|
35586
|
-
"Prompt argument is required.",
|
|
35587
|
-
"Usage: vibe generate video <prompt>"
|
|
35588
|
-
)
|
|
35272
|
+
usageError("Prompt argument is required.", "Usage: vibe generate video <prompt>")
|
|
35589
35273
|
);
|
|
35590
35274
|
}
|
|
35591
35275
|
}
|
|
@@ -35593,13 +35277,16 @@ Examples:
|
|
|
35593
35277
|
if (options.output) {
|
|
35594
35278
|
validateOutputPath(options.output);
|
|
35595
35279
|
}
|
|
35280
|
+
await loadProviderDefaults();
|
|
35596
35281
|
if (options.duration !== void 0) {
|
|
35597
35282
|
const d = parseFloat(options.duration);
|
|
35598
35283
|
if (!Number.isFinite(d) || d <= 0 || d > 60) {
|
|
35599
|
-
exitWithError(
|
|
35600
|
-
|
|
35601
|
-
|
|
35602
|
-
|
|
35284
|
+
exitWithError(
|
|
35285
|
+
usageError(
|
|
35286
|
+
`Invalid --duration: ${options.duration}`,
|
|
35287
|
+
"Must be a positive number \u2264 60 seconds."
|
|
35288
|
+
)
|
|
35289
|
+
);
|
|
35603
35290
|
}
|
|
35604
35291
|
}
|
|
35605
35292
|
const validProviders = ["runway", "kling", "veo", "grok", "seedance", "fal"];
|
|
@@ -35641,10 +35328,12 @@ Examples:
|
|
|
35641
35328
|
provider = resolved?.name ?? "grok";
|
|
35642
35329
|
}
|
|
35643
35330
|
let referenceImage;
|
|
35331
|
+
let referenceImageBuffer;
|
|
35332
|
+
let referenceImageMimeType;
|
|
35644
35333
|
let isImageToVideo = false;
|
|
35645
35334
|
if (options.image) {
|
|
35646
|
-
const imagePath =
|
|
35647
|
-
const imageBuffer = await
|
|
35335
|
+
const imagePath = resolve25(process.cwd(), options.image);
|
|
35336
|
+
const imageBuffer = await readFile13(imagePath);
|
|
35648
35337
|
const ext = options.image.toLowerCase().split(".").pop();
|
|
35649
35338
|
const mimeTypes2 = {
|
|
35650
35339
|
jpg: "image/jpeg",
|
|
@@ -35654,6 +35343,8 @@ Examples:
|
|
|
35654
35343
|
webp: "image/webp"
|
|
35655
35344
|
};
|
|
35656
35345
|
const mimeType = mimeTypes2[ext || "png"] || "image/png";
|
|
35346
|
+
referenceImageBuffer = imageBuffer;
|
|
35347
|
+
referenceImageMimeType = mimeType;
|
|
35657
35348
|
referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
35658
35349
|
isImageToVideo = true;
|
|
35659
35350
|
if (!options.ratio) {
|
|
@@ -35778,20 +35469,22 @@ Examples:
|
|
|
35778
35469
|
}
|
|
35779
35470
|
let klingImage = referenceImage;
|
|
35780
35471
|
if (klingImage && klingImage.startsWith("data:")) {
|
|
35781
|
-
|
|
35782
|
-
|
|
35783
|
-
|
|
35784
|
-
|
|
35785
|
-
|
|
35786
|
-
|
|
35787
|
-
|
|
35788
|
-
|
|
35789
|
-
|
|
35790
|
-
|
|
35791
|
-
|
|
35792
|
-
|
|
35793
|
-
|
|
35794
|
-
|
|
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
|
+
}
|
|
35795
35488
|
spinner2.text = "Starting video generation...";
|
|
35796
35489
|
}
|
|
35797
35490
|
result = await kling.generateVideo(prompt3, {
|
|
@@ -35846,8 +35539,8 @@ Examples:
|
|
|
35846
35539
|
const veoDuration = parseInt(options.duration) <= 6 ? 6 : 8;
|
|
35847
35540
|
let lastFrame;
|
|
35848
35541
|
if (options.lastFrame) {
|
|
35849
|
-
const lastFramePath =
|
|
35850
|
-
const lastFrameBuffer = await
|
|
35542
|
+
const lastFramePath = resolve25(process.cwd(), options.lastFrame);
|
|
35543
|
+
const lastFrameBuffer = await readFile13(lastFramePath);
|
|
35851
35544
|
const ext = options.lastFrame.toLowerCase().split(".").pop();
|
|
35852
35545
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
|
|
35853
35546
|
lastFrame = `data:${mimeType};base64,${lastFrameBuffer.toString("base64")}`;
|
|
@@ -35856,8 +35549,8 @@ Examples:
|
|
|
35856
35549
|
if (options.refImages && options.refImages.length > 0) {
|
|
35857
35550
|
refImages = [];
|
|
35858
35551
|
for (const refPath of options.refImages.slice(0, 3)) {
|
|
35859
|
-
const absRefPath =
|
|
35860
|
-
const refBuffer = await
|
|
35552
|
+
const absRefPath = resolve25(process.cwd(), refPath);
|
|
35553
|
+
const refBuffer = await readFile13(absRefPath);
|
|
35861
35554
|
const ext = refPath.toLowerCase().split(".").pop();
|
|
35862
35555
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
|
|
35863
35556
|
refImages.push({ base64: refBuffer.toString("base64"), mimeType });
|
|
@@ -35938,20 +35631,22 @@ Examples:
|
|
|
35938
35631
|
await fal.initialize({ apiKey });
|
|
35939
35632
|
let falImage = referenceImage;
|
|
35940
35633
|
if (falImage && falImage.startsWith("data:")) {
|
|
35941
|
-
|
|
35942
|
-
|
|
35943
|
-
|
|
35944
|
-
|
|
35945
|
-
|
|
35946
|
-
|
|
35947
|
-
|
|
35948
|
-
|
|
35949
|
-
|
|
35950
|
-
|
|
35951
|
-
|
|
35952
|
-
|
|
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));
|
|
35953
35649
|
}
|
|
35954
|
-
falImage = uploadResult.url;
|
|
35955
35650
|
}
|
|
35956
35651
|
spinner2.text = "Generating video with fal.ai Seedance 2.0 (this may take 1-3 minutes)...";
|
|
35957
35652
|
const seedanceModel = String(options.seedanceModel ?? "quality").toLowerCase();
|
|
@@ -35975,8 +35670,8 @@ Examples:
|
|
|
35975
35670
|
let outputPath;
|
|
35976
35671
|
if (options.output && finalResult.videoUrl) {
|
|
35977
35672
|
const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
|
|
35978
|
-
outputPath =
|
|
35979
|
-
await
|
|
35673
|
+
outputPath = resolve25(process.cwd(), options.output);
|
|
35674
|
+
await writeFile23(outputPath, buffer);
|
|
35980
35675
|
}
|
|
35981
35676
|
outputSuccess({
|
|
35982
35677
|
command: "generate video",
|
|
@@ -36003,14 +35698,12 @@ Examples:
|
|
|
36003
35698
|
const downloadSpinner = ora("Downloading video...").start();
|
|
36004
35699
|
try {
|
|
36005
35700
|
const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
|
|
36006
|
-
const outputPath =
|
|
36007
|
-
await
|
|
35701
|
+
const outputPath = resolve25(process.cwd(), options.output);
|
|
35702
|
+
await writeFile23(outputPath, buffer);
|
|
36008
35703
|
downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
|
|
36009
35704
|
} catch (err) {
|
|
36010
35705
|
downloadSpinner.fail(
|
|
36011
|
-
source_default.red(
|
|
36012
|
-
`Failed to download video: ${err instanceof Error ? err.message : err}`
|
|
36013
|
-
)
|
|
35706
|
+
source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
|
|
36014
35707
|
);
|
|
36015
35708
|
}
|
|
36016
35709
|
}
|
|
@@ -36028,11 +35721,10 @@ var init_video = __esm({
|
|
|
36028
35721
|
init_dist2();
|
|
36029
35722
|
init_api_key();
|
|
36030
35723
|
init_tty();
|
|
36031
|
-
init_config();
|
|
36032
35724
|
init_output();
|
|
36033
35725
|
init_validate();
|
|
36034
35726
|
init_provider_resolver();
|
|
36035
|
-
|
|
35727
|
+
init_upload_host();
|
|
36036
35728
|
init_ai_helpers();
|
|
36037
35729
|
}
|
|
36038
35730
|
});
|
|
@@ -36137,8 +35829,8 @@ Run 'vibe schema generate.<command>' for structured parameter info.
|
|
|
36137
35829
|
});
|
|
36138
35830
|
|
|
36139
35831
|
// src/commands/_shared/edit/silence-cut.ts
|
|
36140
|
-
import { existsSync as
|
|
36141
|
-
import { readFile as
|
|
35832
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
35833
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
36142
35834
|
async function detectSilencePeriods(videoPath, noiseThreshold, minDuration) {
|
|
36143
35835
|
const totalDuration = await getVideoDuration(videoPath);
|
|
36144
35836
|
const { stdout, stderr } = await execSafe(
|
|
@@ -36188,7 +35880,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
|
|
|
36188
35880
|
}
|
|
36189
35881
|
const gemini = new GeminiProvider();
|
|
36190
35882
|
await gemini.initialize({ apiKey: geminiApiKey });
|
|
36191
|
-
const videoBuffer = await
|
|
35883
|
+
const videoBuffer = await readFile14(videoPath);
|
|
36192
35884
|
const modelMap = {
|
|
36193
35885
|
flash: "gemini-3-flash-preview",
|
|
36194
35886
|
"flash-2.5": "gemini-2.5-flash",
|
|
@@ -36269,7 +35961,7 @@ async function executeSilenceCut(options) {
|
|
|
36269
35961
|
analyzeOnly = false,
|
|
36270
35962
|
useGemini = false
|
|
36271
35963
|
} = options;
|
|
36272
|
-
if (!
|
|
35964
|
+
if (!existsSync14(videoPath)) {
|
|
36273
35965
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
36274
35966
|
}
|
|
36275
35967
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36377,11 +36069,11 @@ var init_silence_cut = __esm({
|
|
|
36377
36069
|
});
|
|
36378
36070
|
|
|
36379
36071
|
// src/commands/_shared/edit/jump-cut.ts
|
|
36380
|
-
import { existsSync as
|
|
36381
|
-
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";
|
|
36382
36074
|
import { join as join8 } from "node:path";
|
|
36383
36075
|
async function transcribeWithWords(audioPath, apiKey, language) {
|
|
36384
|
-
const audioBuffer = await
|
|
36076
|
+
const audioBuffer = await readFile15(audioPath);
|
|
36385
36077
|
const audioBlob = new Blob([audioBuffer]);
|
|
36386
36078
|
const formData = new FormData();
|
|
36387
36079
|
formData.append("file", audioBlob, "audio.wav");
|
|
@@ -36440,7 +36132,7 @@ async function executeJumpCut(options) {
|
|
|
36440
36132
|
analyzeOnly = false,
|
|
36441
36133
|
apiKey
|
|
36442
36134
|
} = options;
|
|
36443
|
-
if (!
|
|
36135
|
+
if (!existsSync15(videoPath)) {
|
|
36444
36136
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
36445
36137
|
}
|
|
36446
36138
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36537,7 +36229,7 @@ async function executeJumpCut(options) {
|
|
|
36537
36229
|
}
|
|
36538
36230
|
const concatList = segmentPaths.map((p) => `file '${p}'`).join("\n");
|
|
36539
36231
|
const listPath = join8(tmpDir, "concat.txt");
|
|
36540
|
-
await
|
|
36232
|
+
await writeFile24(listPath, concatList);
|
|
36541
36233
|
await execSafe(
|
|
36542
36234
|
"ffmpeg",
|
|
36543
36235
|
[
|
|
@@ -36726,8 +36418,8 @@ var init_helpers = __esm({
|
|
|
36726
36418
|
});
|
|
36727
36419
|
|
|
36728
36420
|
// src/commands/_shared/edit/caption.ts
|
|
36729
|
-
import { existsSync as
|
|
36730
|
-
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";
|
|
36731
36423
|
import { dirname as dirname8, basename as basename6, extname as extname4, join as join9 } from "node:path";
|
|
36732
36424
|
function getCaptionForceStyle(style, fontSize, fontColor, position) {
|
|
36733
36425
|
const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
|
|
@@ -36756,7 +36448,7 @@ async function executeCaption(options) {
|
|
|
36756
36448
|
position = "bottom",
|
|
36757
36449
|
apiKey
|
|
36758
36450
|
} = options;
|
|
36759
|
-
if (!
|
|
36451
|
+
if (!existsSync16(videoPath)) {
|
|
36760
36452
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
36761
36453
|
}
|
|
36762
36454
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36797,7 +36489,7 @@ async function executeCaption(options) {
|
|
|
36797
36489
|
);
|
|
36798
36490
|
const whisper = new WhisperProvider();
|
|
36799
36491
|
await whisper.initialize({ apiKey: openaiKey });
|
|
36800
|
-
const audioBuffer = await
|
|
36492
|
+
const audioBuffer = await readFile16(audioPath);
|
|
36801
36493
|
const audioBlob = new Blob([audioBuffer]);
|
|
36802
36494
|
const transcriptResult = await whisper.transcribe(audioBlob, language);
|
|
36803
36495
|
if (transcriptResult.status === "failed" || !transcriptResult.segments || transcriptResult.segments.length === 0) {
|
|
@@ -36807,7 +36499,7 @@ async function executeCaption(options) {
|
|
|
36807
36499
|
};
|
|
36808
36500
|
}
|
|
36809
36501
|
const srtContent = formatSRT(transcriptResult.segments);
|
|
36810
|
-
await
|
|
36502
|
+
await writeFile25(srtPath, srtContent);
|
|
36811
36503
|
const { width, height } = await getVideoResolution(videoPath);
|
|
36812
36504
|
const fontSize = customFontSize || Math.round(height / 18);
|
|
36813
36505
|
let hasSubtitles = false;
|
|
@@ -36849,7 +36541,7 @@ async function executeCaption(options) {
|
|
|
36849
36541
|
outputDir2,
|
|
36850
36542
|
basename6(outputPath, extname4(outputPath)) + ".srt"
|
|
36851
36543
|
);
|
|
36852
|
-
await
|
|
36544
|
+
await writeFile25(outputSrtPath2, srtContent);
|
|
36853
36545
|
return { success: false, error: `${remotionErr}
|
|
36854
36546
|
SRT saved to: ${outputSrtPath2}` };
|
|
36855
36547
|
}
|
|
@@ -36888,7 +36580,7 @@ SRT saved to: ${outputSrtPath2}` };
|
|
|
36888
36580
|
outputDir2,
|
|
36889
36581
|
basename6(outputPath, extname4(outputPath)) + ".srt"
|
|
36890
36582
|
);
|
|
36891
|
-
await
|
|
36583
|
+
await writeFile25(outputSrtPath2, srtContent);
|
|
36892
36584
|
return {
|
|
36893
36585
|
success: false,
|
|
36894
36586
|
error: `${renderResult.error}
|
|
@@ -36901,7 +36593,7 @@ SRT saved to: ${outputSrtPath2}`
|
|
|
36901
36593
|
outputDir,
|
|
36902
36594
|
basename6(outputPath, extname4(outputPath)) + ".srt"
|
|
36903
36595
|
);
|
|
36904
|
-
await
|
|
36596
|
+
await writeFile25(outputSrtPath, srtContent);
|
|
36905
36597
|
return {
|
|
36906
36598
|
success: true,
|
|
36907
36599
|
outputPath,
|
|
@@ -36934,10 +36626,10 @@ var init_caption = __esm({
|
|
|
36934
36626
|
});
|
|
36935
36627
|
|
|
36936
36628
|
// src/commands/_shared/edit/noise-reduce.ts
|
|
36937
|
-
import { existsSync as
|
|
36629
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
36938
36630
|
async function executeNoiseReduce(options) {
|
|
36939
36631
|
const { inputPath, outputPath, strength = "medium", noiseFloor } = options;
|
|
36940
|
-
if (!
|
|
36632
|
+
if (!existsSync17(inputPath)) {
|
|
36941
36633
|
return { success: false, error: `File not found: ${inputPath}` };
|
|
36942
36634
|
}
|
|
36943
36635
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36994,7 +36686,7 @@ var init_noise_reduce = __esm({
|
|
|
36994
36686
|
});
|
|
36995
36687
|
|
|
36996
36688
|
// src/commands/_shared/edit/fade.ts
|
|
36997
|
-
import { existsSync as
|
|
36689
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
36998
36690
|
async function executeFade(options) {
|
|
36999
36691
|
const {
|
|
37000
36692
|
videoPath,
|
|
@@ -37004,7 +36696,7 @@ async function executeFade(options) {
|
|
|
37004
36696
|
audioOnly = false,
|
|
37005
36697
|
videoOnly = false
|
|
37006
36698
|
} = options;
|
|
37007
|
-
if (!
|
|
36699
|
+
if (!existsSync18(videoPath)) {
|
|
37008
36700
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
37009
36701
|
}
|
|
37010
36702
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37071,8 +36763,8 @@ var init_fade = __esm({
|
|
|
37071
36763
|
});
|
|
37072
36764
|
|
|
37073
36765
|
// src/commands/_shared/edit/translate-srt.ts
|
|
37074
|
-
import { existsSync as
|
|
37075
|
-
import { readFile as
|
|
36766
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
36767
|
+
import { readFile as readFile17, writeFile as writeFile26 } from "node:fs/promises";
|
|
37076
36768
|
async function executeTranslateSrt(options) {
|
|
37077
36769
|
const {
|
|
37078
36770
|
srtPath,
|
|
@@ -37082,11 +36774,11 @@ async function executeTranslateSrt(options) {
|
|
|
37082
36774
|
sourceLanguage,
|
|
37083
36775
|
apiKey
|
|
37084
36776
|
} = options;
|
|
37085
|
-
if (!
|
|
36777
|
+
if (!existsSync19(srtPath)) {
|
|
37086
36778
|
return { success: false, error: `SRT file not found: ${srtPath}` };
|
|
37087
36779
|
}
|
|
37088
36780
|
try {
|
|
37089
|
-
const srtContent = await
|
|
36781
|
+
const srtContent = await readFile17(srtPath, "utf-8");
|
|
37090
36782
|
const segments = parseSRT(srtContent);
|
|
37091
36783
|
if (segments.length === 0) {
|
|
37092
36784
|
return { success: false, error: "No subtitle segments found in SRT file" };
|
|
@@ -37176,7 +36868,7 @@ ${textsToTranslate}`;
|
|
|
37176
36868
|
}
|
|
37177
36869
|
}
|
|
37178
36870
|
const translatedSrt = formatSRT(translatedSegments);
|
|
37179
|
-
await
|
|
36871
|
+
await writeFile26(outputPath, translatedSrt);
|
|
37180
36872
|
return {
|
|
37181
36873
|
success: true,
|
|
37182
36874
|
outputPath,
|
|
@@ -37199,8 +36891,8 @@ var init_translate_srt = __esm({
|
|
|
37199
36891
|
});
|
|
37200
36892
|
|
|
37201
36893
|
// src/commands/_shared/edit/text-overlay.ts
|
|
37202
|
-
import { existsSync as
|
|
37203
|
-
import { resolve as
|
|
36894
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
36895
|
+
import { resolve as resolve26 } from "node:path";
|
|
37204
36896
|
function detectSystemFont() {
|
|
37205
36897
|
const platform3 = process.platform;
|
|
37206
36898
|
if (platform3 === "darwin") {
|
|
@@ -37210,7 +36902,7 @@ function detectSystemFont() {
|
|
|
37210
36902
|
"/Library/Fonts/Arial.ttf"
|
|
37211
36903
|
];
|
|
37212
36904
|
for (const f of candidates) {
|
|
37213
|
-
if (
|
|
36905
|
+
if (existsSync20(f)) return f;
|
|
37214
36906
|
}
|
|
37215
36907
|
} else if (platform3 === "linux") {
|
|
37216
36908
|
const candidates = [
|
|
@@ -37219,7 +36911,7 @@ function detectSystemFont() {
|
|
|
37219
36911
|
"/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
|
|
37220
36912
|
];
|
|
37221
36913
|
for (const f of candidates) {
|
|
37222
|
-
if (
|
|
36914
|
+
if (existsSync20(f)) return f;
|
|
37223
36915
|
}
|
|
37224
36916
|
} else if (platform3 === "win32") {
|
|
37225
36917
|
const candidates = [
|
|
@@ -37227,7 +36919,7 @@ function detectSystemFont() {
|
|
|
37227
36919
|
"C:\\Windows\\Fonts\\segoeui.ttf"
|
|
37228
36920
|
];
|
|
37229
36921
|
for (const f of candidates) {
|
|
37230
|
-
if (
|
|
36922
|
+
if (existsSync20(f)) return f;
|
|
37231
36923
|
}
|
|
37232
36924
|
}
|
|
37233
36925
|
return null;
|
|
@@ -37246,9 +36938,9 @@ async function applyTextOverlays(options) {
|
|
|
37246
36938
|
if (!texts || texts.length === 0) {
|
|
37247
36939
|
return { success: false, error: "No texts provided" };
|
|
37248
36940
|
}
|
|
37249
|
-
const absVideoPath =
|
|
37250
|
-
const absOutputPath =
|
|
37251
|
-
if (!
|
|
36941
|
+
const absVideoPath = resolve26(process.cwd(), videoPath);
|
|
36942
|
+
const absOutputPath = resolve26(process.cwd(), outputPath);
|
|
36943
|
+
if (!existsSync20(absVideoPath)) {
|
|
37252
36944
|
return { success: false, error: `Video not found: ${absVideoPath}` };
|
|
37253
36945
|
}
|
|
37254
36946
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37428,8 +37120,8 @@ var init_ai_edit = __esm({
|
|
|
37428
37120
|
});
|
|
37429
37121
|
|
|
37430
37122
|
// src/commands/ai-edit-cli.ts
|
|
37431
|
-
import { resolve as
|
|
37432
|
-
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";
|
|
37433
37125
|
function registerEditCommands(aiCommand) {
|
|
37434
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", `
|
|
37435
37127
|
Examples:
|
|
@@ -37445,8 +37137,8 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
|
|
|
37445
37137
|
if (options.output) {
|
|
37446
37138
|
validateOutputPath(options.output);
|
|
37447
37139
|
}
|
|
37448
|
-
const absVideoPath =
|
|
37449
|
-
if (!
|
|
37140
|
+
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
37141
|
+
if (!existsSync21(absVideoPath)) {
|
|
37450
37142
|
exitWithError(notFoundError(absVideoPath));
|
|
37451
37143
|
}
|
|
37452
37144
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37485,7 +37177,7 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
|
|
|
37485
37177
|
const spinner2 = ora(spinnerText).start();
|
|
37486
37178
|
const result = await executeSilenceCut({
|
|
37487
37179
|
videoPath: absVideoPath,
|
|
37488
|
-
outputPath:
|
|
37180
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37489
37181
|
noiseThreshold: parseFloat(options.noise),
|
|
37490
37182
|
minDuration: parseFloat(options.minDuration),
|
|
37491
37183
|
padding: parseFloat(options.padding),
|
|
@@ -37553,8 +37245,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37553
37245
|
if (options.output) {
|
|
37554
37246
|
validateOutputPath(options.output);
|
|
37555
37247
|
}
|
|
37556
|
-
const absVideoPath =
|
|
37557
|
-
if (!
|
|
37248
|
+
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
37249
|
+
if (!existsSync21(absVideoPath)) {
|
|
37558
37250
|
exitWithError(notFoundError(absVideoPath));
|
|
37559
37251
|
}
|
|
37560
37252
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37588,7 +37280,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37588
37280
|
const spinner2 = ora("Starting caption process...").start();
|
|
37589
37281
|
const result = await executeCaption({
|
|
37590
37282
|
videoPath: absVideoPath,
|
|
37591
|
-
outputPath:
|
|
37283
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37592
37284
|
style: options.style,
|
|
37593
37285
|
fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
|
|
37594
37286
|
fontColor: options.color,
|
|
@@ -37634,8 +37326,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37634
37326
|
if (options.output) {
|
|
37635
37327
|
validateOutputPath(options.output);
|
|
37636
37328
|
}
|
|
37637
|
-
const absInputPath =
|
|
37638
|
-
if (!
|
|
37329
|
+
const absInputPath = resolve27(process.cwd(), inputPath);
|
|
37330
|
+
if (!existsSync21(absInputPath)) {
|
|
37639
37331
|
exitWithError(notFoundError(absInputPath));
|
|
37640
37332
|
}
|
|
37641
37333
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37662,7 +37354,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37662
37354
|
const spinner2 = ora("Applying noise reduction...").start();
|
|
37663
37355
|
const result = await executeNoiseReduce({
|
|
37664
37356
|
inputPath: absInputPath,
|
|
37665
|
-
outputPath:
|
|
37357
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37666
37358
|
strength: options.strength,
|
|
37667
37359
|
noiseFloor: options.noiseFloor ? parseFloat(options.noiseFloor) : void 0
|
|
37668
37360
|
});
|
|
@@ -37700,8 +37392,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37700
37392
|
if (options.output) {
|
|
37701
37393
|
validateOutputPath(options.output);
|
|
37702
37394
|
}
|
|
37703
|
-
const absVideoPath =
|
|
37704
|
-
if (!
|
|
37395
|
+
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
37396
|
+
if (!existsSync21(absVideoPath)) {
|
|
37705
37397
|
exitWithError(notFoundError(absVideoPath));
|
|
37706
37398
|
}
|
|
37707
37399
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37730,7 +37422,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37730
37422
|
const spinner2 = ora("Applying fade effects...").start();
|
|
37731
37423
|
const result = await executeFade({
|
|
37732
37424
|
videoPath: absVideoPath,
|
|
37733
|
-
outputPath:
|
|
37425
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37734
37426
|
fadeIn: parseFloat(options.fadeIn),
|
|
37735
37427
|
fadeOut: parseFloat(options.fadeOut),
|
|
37736
37428
|
audioOnly: options.audioOnly || false,
|
|
@@ -37775,8 +37467,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37775
37467
|
if (!options.target) {
|
|
37776
37468
|
exitWithError(usageError("Target language required. Use -t or --target"));
|
|
37777
37469
|
}
|
|
37778
|
-
const absSrtPath =
|
|
37779
|
-
if (!
|
|
37470
|
+
const absSrtPath = resolve27(process.cwd(), srtPath);
|
|
37471
|
+
if (!existsSync21(absSrtPath)) {
|
|
37780
37472
|
exitWithError(notFoundError(absSrtPath));
|
|
37781
37473
|
}
|
|
37782
37474
|
if (options.dryRun) {
|
|
@@ -37808,7 +37500,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37808
37500
|
const spinner2 = ora(`Translating to ${options.target}...`).start();
|
|
37809
37501
|
const result = await executeTranslateSrt({
|
|
37810
37502
|
srtPath: absSrtPath,
|
|
37811
|
-
outputPath:
|
|
37503
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37812
37504
|
targetLanguage: options.target,
|
|
37813
37505
|
provider,
|
|
37814
37506
|
sourceLanguage: options.source,
|
|
@@ -37851,8 +37543,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37851
37543
|
validateOutputPath(options.output);
|
|
37852
37544
|
}
|
|
37853
37545
|
if (options.fillers) rejectControlChars(options.fillers);
|
|
37854
|
-
const absVideoPath =
|
|
37855
|
-
if (!
|
|
37546
|
+
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
37547
|
+
if (!existsSync21(absVideoPath)) {
|
|
37856
37548
|
exitWithError(notFoundError(absVideoPath));
|
|
37857
37549
|
}
|
|
37858
37550
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37887,7 +37579,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37887
37579
|
const spinner2 = ora("Transcribing with word-level timestamps...").start();
|
|
37888
37580
|
const result = await executeJumpCut({
|
|
37889
37581
|
videoPath: absVideoPath,
|
|
37890
|
-
outputPath:
|
|
37582
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37891
37583
|
fillers,
|
|
37892
37584
|
padding: parseFloat(options.padding),
|
|
37893
37585
|
language: options.language,
|
|
@@ -37952,9 +37644,9 @@ var init_ai_edit_cli = __esm({
|
|
|
37952
37644
|
});
|
|
37953
37645
|
|
|
37954
37646
|
// src/commands/_shared/execute-fill-gaps.ts
|
|
37955
|
-
import { existsSync as
|
|
37956
|
-
import { readFile as
|
|
37957
|
-
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";
|
|
37958
37650
|
function detectVideoGaps(videoClips, totalDuration) {
|
|
37959
37651
|
const gaps = [];
|
|
37960
37652
|
const sortedClips = [...videoClips].sort((a, b) => a.startTime - b.startTime);
|
|
@@ -38010,7 +37702,7 @@ function analyzeGapFillability(gaps, videoClips, sources) {
|
|
|
38010
37702
|
}
|
|
38011
37703
|
async function uploadFrameToImgbb(framePath, imgbbApiKey) {
|
|
38012
37704
|
try {
|
|
38013
|
-
const frameBuffer = await
|
|
37705
|
+
const frameBuffer = await readFile18(framePath);
|
|
38014
37706
|
const frameBase64 = frameBuffer.toString("base64");
|
|
38015
37707
|
const formData = new FormData();
|
|
38016
37708
|
formData.append("key", imgbbApiKey);
|
|
@@ -38035,14 +37727,14 @@ async function executeFillGaps(options) {
|
|
|
38035
37727
|
try {
|
|
38036
37728
|
onProgress("Loading timeline...");
|
|
38037
37729
|
const filePath = await resolveTimelineFile(options.projectPath);
|
|
38038
|
-
if (!
|
|
37730
|
+
if (!existsSync22(filePath)) {
|
|
38039
37731
|
return {
|
|
38040
37732
|
success: false,
|
|
38041
37733
|
error: `Timeline file not found: ${filePath}`,
|
|
38042
37734
|
humanLines
|
|
38043
37735
|
};
|
|
38044
37736
|
}
|
|
38045
|
-
const content = await
|
|
37737
|
+
const content = await readFile18(filePath, "utf-8");
|
|
38046
37738
|
const data = JSON.parse(content);
|
|
38047
37739
|
const project = Project.fromJSON(data);
|
|
38048
37740
|
const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
|
|
@@ -38169,8 +37861,8 @@ async function executeFillGaps(options) {
|
|
|
38169
37861
|
};
|
|
38170
37862
|
}
|
|
38171
37863
|
const projectDir = dirname9(filePath);
|
|
38172
|
-
const footageDir = options.dir ?
|
|
38173
|
-
if (!
|
|
37864
|
+
const footageDir = options.dir ? resolve28(process.cwd(), options.dir) : resolve28(projectDir, "footage");
|
|
37865
|
+
if (!existsSync22(footageDir)) {
|
|
38174
37866
|
await mkdir14(footageDir, { recursive: true });
|
|
38175
37867
|
}
|
|
38176
37868
|
humanLines.push("Generating AI Videos");
|
|
@@ -38201,7 +37893,7 @@ async function executeFillGaps(options) {
|
|
|
38201
37893
|
}
|
|
38202
37894
|
onProgress("Extracting frame from preceding clip...");
|
|
38203
37895
|
const frameOffset = clipBefore.sourceStartOffset + clipBefore.duration - 0.1;
|
|
38204
|
-
const framePath =
|
|
37896
|
+
const framePath = resolve28(footageDir, `frame-${gap.start.toFixed(2)}.png`);
|
|
38205
37897
|
try {
|
|
38206
37898
|
await execSafe("ffmpeg", [
|
|
38207
37899
|
"-i",
|
|
@@ -38262,10 +37954,10 @@ async function executeFillGaps(options) {
|
|
|
38262
37954
|
continue;
|
|
38263
37955
|
}
|
|
38264
37956
|
const videoFileName = `gap-fill-${gap.start.toFixed(2)}-${gap.end.toFixed(2)}.mp4`;
|
|
38265
|
-
const videoPath =
|
|
37957
|
+
const videoPath = resolve28(footageDir, videoFileName);
|
|
38266
37958
|
onProgress("Downloading generated video...");
|
|
38267
37959
|
const videoBuffer = await downloadVideo(finalResult.videoUrl);
|
|
38268
|
-
await
|
|
37960
|
+
await writeFile27(videoPath, videoBuffer);
|
|
38269
37961
|
generatedDuration = finalResult.duration || parseInt(klingDuration);
|
|
38270
37962
|
generatedVideos.push(videoPath);
|
|
38271
37963
|
humanLines.push(` Generated: ${videoFileName} (${generatedDuration}s)`);
|
|
@@ -38274,7 +37966,7 @@ async function executeFillGaps(options) {
|
|
|
38274
37966
|
const remainingNeeded = targetDuration - generatedDuration;
|
|
38275
37967
|
const segmentDuration = remainingNeeded > 5 ? "10" : "5";
|
|
38276
37968
|
onProgress(`Generating additional ${segmentDuration}s segment...`);
|
|
38277
|
-
const lastFramePath =
|
|
37969
|
+
const lastFramePath = resolve28(
|
|
38278
37970
|
footageDir,
|
|
38279
37971
|
`frame-extend-${gap.start.toFixed(2)}-${segmentIndex}.png`
|
|
38280
37972
|
);
|
|
@@ -38330,17 +38022,17 @@ async function executeFillGaps(options) {
|
|
|
38330
38022
|
);
|
|
38331
38023
|
break;
|
|
38332
38024
|
}
|
|
38333
|
-
const segVideoPath =
|
|
38025
|
+
const segVideoPath = resolve28(
|
|
38334
38026
|
footageDir,
|
|
38335
38027
|
`gap-fill-${gap.start.toFixed(2)}-${gap.end.toFixed(2)}-seg${segmentIndex}.mp4`
|
|
38336
38028
|
);
|
|
38337
38029
|
const segVideoBuffer = await downloadVideo(segFinalResult.videoUrl);
|
|
38338
|
-
await
|
|
38339
|
-
const concatListPath =
|
|
38030
|
+
await writeFile27(segVideoPath, segVideoBuffer);
|
|
38031
|
+
const concatListPath = resolve28(footageDir, `concat-${gap.start.toFixed(2)}.txt`);
|
|
38340
38032
|
const concatList = generatedVideos.map((v) => `file '${v}'`).join("\n") + `
|
|
38341
38033
|
file '${segVideoPath}'`;
|
|
38342
|
-
await
|
|
38343
|
-
const concatOutputPath =
|
|
38034
|
+
await writeFile27(concatListPath, concatList);
|
|
38035
|
+
const concatOutputPath = resolve28(
|
|
38344
38036
|
footageDir,
|
|
38345
38037
|
`gap-fill-${gap.start.toFixed(2)}-${gap.end.toFixed(2)}-merged.mp4`
|
|
38346
38038
|
);
|
|
@@ -38396,8 +38088,8 @@ file '${segVideoPath}'`;
|
|
|
38396
38088
|
humanLines.push("");
|
|
38397
38089
|
let outputPath = filePath;
|
|
38398
38090
|
if (generatedCount > 0) {
|
|
38399
|
-
outputPath = options.output ?
|
|
38400
|
-
await
|
|
38091
|
+
outputPath = options.output ? resolve28(process.cwd(), options.output) : filePath;
|
|
38092
|
+
await writeFile27(outputPath, JSON.stringify(project.toJSON(), null, 2));
|
|
38401
38093
|
humanLines.push(`\u2714 Filled ${generatedCount} gap(s) with AI-generated video`);
|
|
38402
38094
|
humanLines.push(`Project saved: ${outputPath}`);
|
|
38403
38095
|
} else {
|
|
@@ -38493,14 +38185,14 @@ __export(edit_cmd_exports, {
|
|
|
38493
38185
|
executeSpeedRamp: () => executeSpeedRamp,
|
|
38494
38186
|
executeUpscale: () => executeUpscale
|
|
38495
38187
|
});
|
|
38496
|
-
import { resolve as
|
|
38497
|
-
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";
|
|
38498
38190
|
async function executeGrade(options) {
|
|
38499
38191
|
const { videoPath, style, preset, output, analyzeOnly, apiKey } = options;
|
|
38500
38192
|
try {
|
|
38501
38193
|
if (!style && !preset) return { success: false, error: "Either style or preset is required" };
|
|
38502
38194
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
38503
|
-
const absPath =
|
|
38195
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38504
38196
|
let gradeResult;
|
|
38505
38197
|
if (preset) {
|
|
38506
38198
|
const claude = new ClaudeProvider();
|
|
@@ -38515,7 +38207,7 @@ async function executeGrade(options) {
|
|
|
38515
38207
|
if (analyzeOnly) {
|
|
38516
38208
|
return { success: true, style: preset || style, description: gradeResult.description, ffmpegFilter: gradeResult.ffmpegFilter };
|
|
38517
38209
|
}
|
|
38518
|
-
const outputPath = output ?
|
|
38210
|
+
const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, "-graded$1");
|
|
38519
38211
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", gradeResult.ffmpegFilter, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
|
|
38520
38212
|
return { success: true, outputPath, style: preset || style, description: gradeResult.description, ffmpegFilter: gradeResult.ffmpegFilter };
|
|
38521
38213
|
} catch (error) {
|
|
@@ -38530,12 +38222,12 @@ async function executeSpeedRamp(options) {
|
|
|
38530
38222
|
const claudeKey = apiKey || process.env.ANTHROPIC_API_KEY;
|
|
38531
38223
|
if (!openaiKey) return { success: false, error: "OPENAI_API_KEY required for Whisper transcription" };
|
|
38532
38224
|
if (!claudeKey) return { success: false, error: "ANTHROPIC_API_KEY required for Claude analysis" };
|
|
38533
|
-
const absPath =
|
|
38225
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38534
38226
|
const tempAudio = absPath.replace(/(\.[^.]+)$/, "-temp-audio.mp3");
|
|
38535
38227
|
await execSafe("ffmpeg", ["-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "2", tempAudio, "-y"]);
|
|
38536
38228
|
const whisper = new WhisperProvider();
|
|
38537
38229
|
await whisper.initialize({ apiKey: openaiKey });
|
|
38538
|
-
const audioBuffer = await
|
|
38230
|
+
const audioBuffer = await readFile19(tempAudio);
|
|
38539
38231
|
const transcript = await whisper.transcribe(new Blob([audioBuffer]), language);
|
|
38540
38232
|
try {
|
|
38541
38233
|
const { unlink: unlink5 } = await import("node:fs/promises");
|
|
@@ -38555,7 +38247,7 @@ async function executeSpeedRamp(options) {
|
|
|
38555
38247
|
if (speedResult.keyframes.length < 2) {
|
|
38556
38248
|
return { success: false, error: "Not enough keyframes for speed ramping" };
|
|
38557
38249
|
}
|
|
38558
|
-
const outputPath = output ?
|
|
38250
|
+
const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
|
|
38559
38251
|
const setpts = `setpts=${(1 / avgSpeed).toFixed(3)}*PTS`;
|
|
38560
38252
|
const atempo = avgSpeed >= 0.5 && avgSpeed <= 2 ? `atempo=${avgSpeed.toFixed(3)}` : "";
|
|
38561
38253
|
if (atempo) {
|
|
@@ -38572,7 +38264,7 @@ async function executeReframe(options) {
|
|
|
38572
38264
|
const { videoPath, aspect = "9:16", output, analyzeOnly } = options;
|
|
38573
38265
|
try {
|
|
38574
38266
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
38575
|
-
const absPath =
|
|
38267
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38576
38268
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
38577
38269
|
"-v",
|
|
38578
38270
|
"error",
|
|
@@ -38607,7 +38299,7 @@ async function executeReframe(options) {
|
|
|
38607
38299
|
keyframeCount: 1
|
|
38608
38300
|
};
|
|
38609
38301
|
}
|
|
38610
|
-
const outputPath = output ?
|
|
38302
|
+
const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, `-${aspect.replace(":", "x")}$1`);
|
|
38611
38303
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", `crop=${cropWidth}:${cropHeight}:${cropX}:${cropY}`, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
|
|
38612
38304
|
return { success: true, outputPath, sourceAspect: `${sourceWidth}:${sourceHeight}`, targetAspect: aspect };
|
|
38613
38305
|
} catch (error) {
|
|
@@ -38619,7 +38311,7 @@ async function executeInterpolate(options) {
|
|
|
38619
38311
|
try {
|
|
38620
38312
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
38621
38313
|
if (![2, 4, 8].includes(factor)) return { success: false, error: "Factor must be 2, 4, or 8" };
|
|
38622
|
-
const absPath =
|
|
38314
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38623
38315
|
const { stdout: fpsOut } = await execSafe("ffprobe", [
|
|
38624
38316
|
"-v",
|
|
38625
38317
|
"error",
|
|
@@ -38635,7 +38327,7 @@ async function executeInterpolate(options) {
|
|
|
38635
38327
|
const originalFps = num / (den || 1);
|
|
38636
38328
|
const targetFps = options.fps || originalFps * factor;
|
|
38637
38329
|
const mi = quality === "fast" ? "mi_mode=mci" : "mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1";
|
|
38638
|
-
const outputPath = output ?
|
|
38330
|
+
const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, `-slow${factor}x$1`);
|
|
38639
38331
|
await execSafe("ffmpeg", ["-i", absPath, "-filter:v", `minterpolate='${mi}:fps=${targetFps}',setpts=${factor}*PTS`, "-an", outputPath, "-y"], { timeout: 6e5 });
|
|
38640
38332
|
return { success: true, outputPath, originalFps, targetFps, factor };
|
|
38641
38333
|
} catch (error) {
|
|
@@ -38646,7 +38338,7 @@ async function executeUpscale(options) {
|
|
|
38646
38338
|
const { videoPath, output, scale = 2, quality = "quality" } = options;
|
|
38647
38339
|
try {
|
|
38648
38340
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
38649
|
-
const absPath =
|
|
38341
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38650
38342
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
38651
38343
|
"-v",
|
|
38652
38344
|
"error",
|
|
@@ -38662,7 +38354,7 @@ async function executeUpscale(options) {
|
|
|
38662
38354
|
const targetW = w * scale;
|
|
38663
38355
|
const targetH = h * scale;
|
|
38664
38356
|
const scaleFilter = quality === "quality" ? `scale=${targetW}:${targetH}:flags=lanczos` : `scale=${targetW}:${targetH}`;
|
|
38665
|
-
const outputPath = output ?
|
|
38357
|
+
const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, `-${scale}x$1`);
|
|
38666
38358
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", scaleFilter, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
|
|
38667
38359
|
return { success: true, outputPath, originalRes: `${w}x${h}`, targetRes: `${targetW}x${targetH}` };
|
|
38668
38360
|
} catch (error) {
|
|
@@ -38735,7 +38427,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38735
38427
|
dryRun: true,
|
|
38736
38428
|
data: {
|
|
38737
38429
|
params: {
|
|
38738
|
-
videoPath:
|
|
38430
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
38739
38431
|
style: options.style || options.preset,
|
|
38740
38432
|
analyzeOnly: options.analyzeOnly || false
|
|
38741
38433
|
}
|
|
@@ -38762,8 +38454,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38762
38454
|
}
|
|
38763
38455
|
spinner2.succeed(source_default.green("Color grade analyzed"));
|
|
38764
38456
|
if (isJsonMode()) {
|
|
38765
|
-
const absPath2 =
|
|
38766
|
-
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");
|
|
38767
38459
|
outputSuccess({
|
|
38768
38460
|
command: "edit grade",
|
|
38769
38461
|
startedAt,
|
|
@@ -38789,8 +38481,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38789
38481
|
console.log(source_default.dim("Use without --analyze-only to apply the grade."));
|
|
38790
38482
|
return;
|
|
38791
38483
|
}
|
|
38792
|
-
const absPath =
|
|
38793
|
-
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");
|
|
38794
38486
|
spinner2.start("Applying color grade...");
|
|
38795
38487
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", gradeResult.ffmpegFilter, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
|
|
38796
38488
|
spinner2.succeed(source_default.green("Color grade applied"));
|
|
@@ -38820,7 +38512,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38820
38512
|
dryRun: true,
|
|
38821
38513
|
data: {
|
|
38822
38514
|
params: {
|
|
38823
|
-
videoPath:
|
|
38515
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
38824
38516
|
texts: options.text,
|
|
38825
38517
|
style: options.style,
|
|
38826
38518
|
fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
|
|
@@ -38833,8 +38525,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38833
38525
|
});
|
|
38834
38526
|
return;
|
|
38835
38527
|
}
|
|
38836
|
-
const absPath =
|
|
38837
|
-
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");
|
|
38838
38530
|
const spinner2 = ora("Applying text overlays...").start();
|
|
38839
38531
|
const result = await applyTextOverlays({
|
|
38840
38532
|
videoPath: absPath,
|
|
@@ -38892,7 +38584,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38892
38584
|
dryRun: true,
|
|
38893
38585
|
data: {
|
|
38894
38586
|
params: {
|
|
38895
|
-
videoPath:
|
|
38587
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
38896
38588
|
style: options.style,
|
|
38897
38589
|
minSpeed: parseFloat(options.minSpeed),
|
|
38898
38590
|
maxSpeed: parseFloat(options.maxSpeed),
|
|
@@ -38904,7 +38596,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38904
38596
|
}
|
|
38905
38597
|
const openaiApiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI");
|
|
38906
38598
|
const claudeApiKey = await requireApiKey("ANTHROPIC_API_KEY", "Anthropic", options.apiKey);
|
|
38907
|
-
const absPath =
|
|
38599
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38908
38600
|
const spinner2 = ora("Extracting audio...").start();
|
|
38909
38601
|
const { stdout: speedRampProbe } = await execSafe("ffprobe", [
|
|
38910
38602
|
"-v",
|
|
@@ -38926,7 +38618,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38926
38618
|
spinner2.text = "Transcribing audio...";
|
|
38927
38619
|
const whisper = new WhisperProvider();
|
|
38928
38620
|
await whisper.initialize({ apiKey: openaiApiKey });
|
|
38929
|
-
const audioBuffer = await
|
|
38621
|
+
const audioBuffer = await readFile19(tempAudio);
|
|
38930
38622
|
const audioBlob = new Blob([audioBuffer]);
|
|
38931
38623
|
const transcript = await whisper.transcribe(audioBlob, options.language);
|
|
38932
38624
|
if (!transcript.segments || transcript.segments.length === 0) {
|
|
@@ -38949,7 +38641,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38949
38641
|
spinner2.succeed(source_default.green(`Found ${speedResult.keyframes.length} speed keyframes`));
|
|
38950
38642
|
if (isJsonMode()) {
|
|
38951
38643
|
const avgSpeed2 = speedResult.keyframes.reduce((sum, kf) => sum + kf.speed, 0) / speedResult.keyframes.length;
|
|
38952
|
-
const speedRampOutputPath = options.output ?
|
|
38644
|
+
const speedRampOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
|
|
38953
38645
|
outputSuccess({
|
|
38954
38646
|
command: "edit speed-ramp",
|
|
38955
38647
|
startedAt,
|
|
@@ -38978,7 +38670,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38978
38670
|
return;
|
|
38979
38671
|
}
|
|
38980
38672
|
spinner2.start("Applying speed ramps...");
|
|
38981
|
-
const outputPath = options.output ?
|
|
38673
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
|
|
38982
38674
|
const avgSpeed = speedResult.keyframes.reduce((sum, kf) => sum + kf.speed, 0) / speedResult.keyframes.length;
|
|
38983
38675
|
const setpts = `setpts=${(1 / avgSpeed).toFixed(3)}*PTS`;
|
|
38984
38676
|
const atempo = avgSpeed >= 0.5 && avgSpeed <= 2 ? `atempo=${avgSpeed.toFixed(3)}` : "";
|
|
@@ -39012,7 +38704,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39012
38704
|
dryRun: true,
|
|
39013
38705
|
data: {
|
|
39014
38706
|
params: {
|
|
39015
|
-
videoPath:
|
|
38707
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
39016
38708
|
aspect: options.aspect,
|
|
39017
38709
|
focus: options.focus,
|
|
39018
38710
|
analyzeOnly: options.analyzeOnly || false
|
|
@@ -39021,7 +38713,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39021
38713
|
});
|
|
39022
38714
|
return;
|
|
39023
38715
|
}
|
|
39024
|
-
const absPath =
|
|
38716
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
39025
38717
|
const spinner2 = ora("Analyzing video...").start();
|
|
39026
38718
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
39027
38719
|
"-v",
|
|
@@ -39059,7 +38751,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39059
38751
|
for (let i = 1; i <= numKeyframes && i <= 30; i++) {
|
|
39060
38752
|
const framePath = `${tempDir}/frame-${i.toString().padStart(4, "0")}.jpg`;
|
|
39061
38753
|
try {
|
|
39062
|
-
const frameBuffer = await
|
|
38754
|
+
const frameBuffer = await readFile19(framePath);
|
|
39063
38755
|
const frameBase64 = frameBuffer.toString("base64");
|
|
39064
38756
|
const result = await claude.analyzeFrameForReframe(frameBase64, options.aspect, {
|
|
39065
38757
|
focusMode: options.focus,
|
|
@@ -39083,7 +38775,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39083
38775
|
}
|
|
39084
38776
|
spinner2.succeed(source_default.green(`Analyzed ${cropKeyframes.length} keyframes`));
|
|
39085
38777
|
if (isJsonMode()) {
|
|
39086
|
-
const reframeOutputPath = options.output ?
|
|
38778
|
+
const reframeOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-${options.aspect.replace(":", "x")}$1`);
|
|
39087
38779
|
outputSuccess({
|
|
39088
38780
|
command: "edit reframe",
|
|
39089
38781
|
startedAt,
|
|
@@ -39118,8 +38810,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39118
38810
|
}
|
|
39119
38811
|
console.log();
|
|
39120
38812
|
if (options.keyframes) {
|
|
39121
|
-
const keyframesPath =
|
|
39122
|
-
await
|
|
38813
|
+
const keyframesPath = resolve29(process.cwd(), options.keyframes);
|
|
38814
|
+
await writeFile28(keyframesPath, JSON.stringify(cropKeyframes, null, 2));
|
|
39123
38815
|
console.log(source_default.green(`Keyframes saved to: ${keyframesPath}`));
|
|
39124
38816
|
}
|
|
39125
38817
|
if (options.analyzeOnly) {
|
|
@@ -39130,7 +38822,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39130
38822
|
const avgCropY = Math.round(cropKeyframes.reduce((sum, kf) => sum + kf.cropY, 0) / cropKeyframes.length);
|
|
39131
38823
|
const cropWidth = cropKeyframes[0]?.cropWidth || sourceWidth;
|
|
39132
38824
|
const cropHeight = cropKeyframes[0]?.cropHeight || sourceHeight;
|
|
39133
|
-
const outputPath = options.output ?
|
|
38825
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-${options.aspect.replace(":", "x")}$1`);
|
|
39134
38826
|
spinner2.start("Applying reframe...");
|
|
39135
38827
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", `crop=${cropWidth}:${cropHeight}:${avgCropX}:${avgCropY}`, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
|
|
39136
38828
|
spinner2.succeed(source_default.green("Reframe applied"));
|
|
@@ -39166,7 +38858,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39166
38858
|
dryRun: true,
|
|
39167
38859
|
data: {
|
|
39168
38860
|
params: {
|
|
39169
|
-
imagePaths: imagePaths.map((p) =>
|
|
38861
|
+
imagePaths: imagePaths.map((p) => resolve29(process.cwd(), p)),
|
|
39170
38862
|
prompt: prompt3,
|
|
39171
38863
|
provider,
|
|
39172
38864
|
model: options.model,
|
|
@@ -39187,8 +38879,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39187
38879
|
const spinner2 = ora(`Reading ${imagePaths.length} image(s)...`).start();
|
|
39188
38880
|
const imageBuffers = [];
|
|
39189
38881
|
for (const imagePath of imagePaths) {
|
|
39190
|
-
const absPath =
|
|
39191
|
-
const buffer = await
|
|
38882
|
+
const absPath = resolve29(process.cwd(), imagePath);
|
|
38883
|
+
const buffer = await readFile19(absPath);
|
|
39192
38884
|
imageBuffers.push(buffer);
|
|
39193
38885
|
}
|
|
39194
38886
|
let result;
|
|
@@ -39236,16 +38928,16 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39236
38928
|
}
|
|
39237
38929
|
spinner2.succeed(source_default.green("Image edited"));
|
|
39238
38930
|
const img = result.images[0];
|
|
39239
|
-
const outputPath =
|
|
38931
|
+
const outputPath = resolve29(process.cwd(), options.output);
|
|
39240
38932
|
const saveImage = async () => {
|
|
39241
38933
|
await mkdir15(dirname10(outputPath), { recursive: true });
|
|
39242
38934
|
if (img.base64) {
|
|
39243
38935
|
const buffer = Buffer.from(img.base64, "base64");
|
|
39244
|
-
await
|
|
38936
|
+
await writeFile28(outputPath, buffer);
|
|
39245
38937
|
} else if (img.url) {
|
|
39246
38938
|
const resp = await fetch(img.url);
|
|
39247
38939
|
const arrayBuf = await resp.arrayBuffer();
|
|
39248
|
-
await
|
|
38940
|
+
await writeFile28(outputPath, Buffer.from(arrayBuf));
|
|
39249
38941
|
}
|
|
39250
38942
|
};
|
|
39251
38943
|
const resultModel = result.model;
|
|
@@ -39278,7 +38970,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39278
38970
|
if (options.output) {
|
|
39279
38971
|
validateOutputPath(options.output);
|
|
39280
38972
|
}
|
|
39281
|
-
const absPath =
|
|
38973
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
39282
38974
|
const factor = parseInt(options.factor);
|
|
39283
38975
|
if (![2, 4, 8].includes(factor)) {
|
|
39284
38976
|
exitWithError(usageError("Factor must be 2, 4, or 8"));
|
|
@@ -39299,7 +38991,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39299
38991
|
});
|
|
39300
38992
|
return;
|
|
39301
38993
|
}
|
|
39302
|
-
const outputPath = options.output ?
|
|
38994
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-slow${factor}x$1`);
|
|
39303
38995
|
const spinner2 = ora(`Creating ${factor}x slow motion...`).start();
|
|
39304
38996
|
try {
|
|
39305
38997
|
const { stdout: fpsOut } = await execSafe("ffprobe", [
|
|
@@ -39360,7 +39052,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39360
39052
|
if (options.output) {
|
|
39361
39053
|
validateOutputPath(options.output);
|
|
39362
39054
|
}
|
|
39363
|
-
const absPath =
|
|
39055
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
39364
39056
|
const scale = parseInt(options.scale);
|
|
39365
39057
|
if (scale !== 2 && scale !== 4) {
|
|
39366
39058
|
exitWithError(usageError("Scale must be 2 or 4"));
|
|
@@ -39382,7 +39074,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39382
39074
|
return;
|
|
39383
39075
|
}
|
|
39384
39076
|
if (options.ffmpeg) {
|
|
39385
|
-
const outputPath = options.output ?
|
|
39077
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-upscaled-${scale}x$1`);
|
|
39386
39078
|
const spinner3 = ora(`Upscaling video with FFmpeg (${scale}x)...`).start();
|
|
39387
39079
|
try {
|
|
39388
39080
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
@@ -39463,9 +39155,9 @@ __export(ai_analyze_exports, {
|
|
|
39463
39155
|
executeGeminiVideo: () => executeGeminiVideo,
|
|
39464
39156
|
registerAnalyzeCommands: () => registerAnalyzeCommands
|
|
39465
39157
|
});
|
|
39466
|
-
import { readFile as
|
|
39467
|
-
import { extname as extname6, resolve as
|
|
39468
|
-
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";
|
|
39469
39161
|
async function executeGeminiVideo(options) {
|
|
39470
39162
|
try {
|
|
39471
39163
|
const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
|
|
@@ -39483,11 +39175,11 @@ async function executeGeminiVideo(options) {
|
|
|
39483
39175
|
if (isYouTube) {
|
|
39484
39176
|
videoData = options.source;
|
|
39485
39177
|
} else {
|
|
39486
|
-
const absPath =
|
|
39487
|
-
if (!
|
|
39178
|
+
const absPath = resolve30(process.cwd(), options.source);
|
|
39179
|
+
if (!existsSync23(absPath)) {
|
|
39488
39180
|
return { success: false, error: `File not found: ${absPath}` };
|
|
39489
39181
|
}
|
|
39490
|
-
videoData = await
|
|
39182
|
+
videoData = await readFile20(absPath);
|
|
39491
39183
|
}
|
|
39492
39184
|
const gemini = new GeminiProvider();
|
|
39493
39185
|
await gemini.initialize({ apiKey });
|
|
@@ -39556,11 +39248,11 @@ async function executeAnalyze(options) {
|
|
|
39556
39248
|
}
|
|
39557
39249
|
imageBuffer = Buffer.from(await response.arrayBuffer());
|
|
39558
39250
|
} else {
|
|
39559
|
-
const absPath =
|
|
39560
|
-
if (!
|
|
39251
|
+
const absPath = resolve30(process.cwd(), source);
|
|
39252
|
+
if (!existsSync23(absPath)) {
|
|
39561
39253
|
return { success: false, error: `File not found: ${absPath}` };
|
|
39562
39254
|
}
|
|
39563
|
-
imageBuffer = await
|
|
39255
|
+
imageBuffer = await readFile20(absPath);
|
|
39564
39256
|
}
|
|
39565
39257
|
const result2 = await gemini.analyzeImage(imageBuffer, options.prompt, {
|
|
39566
39258
|
model: modelId,
|
|
@@ -39591,11 +39283,11 @@ async function executeAnalyze(options) {
|
|
|
39591
39283
|
}
|
|
39592
39284
|
videoData = Buffer.from(await response.arrayBuffer());
|
|
39593
39285
|
} else {
|
|
39594
|
-
const absPath =
|
|
39595
|
-
if (!
|
|
39286
|
+
const absPath = resolve30(process.cwd(), source);
|
|
39287
|
+
if (!existsSync23(absPath)) {
|
|
39596
39288
|
return { success: false, error: `File not found: ${absPath}` };
|
|
39597
39289
|
}
|
|
39598
|
-
videoData = await
|
|
39290
|
+
videoData = await readFile20(absPath);
|
|
39599
39291
|
}
|
|
39600
39292
|
const result = await gemini.analyzeVideo(videoData, options.prompt, {
|
|
39601
39293
|
model: modelId,
|
|
@@ -39729,9 +39421,9 @@ __export(ai_review_exports, {
|
|
|
39729
39421
|
executeReview: () => executeReview,
|
|
39730
39422
|
registerReviewCommand: () => registerReviewCommand
|
|
39731
39423
|
});
|
|
39732
|
-
import { readFile as
|
|
39733
|
-
import { resolve as
|
|
39734
|
-
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";
|
|
39735
39427
|
function parseReviewFeedback(response) {
|
|
39736
39428
|
let cleaned = response.trim();
|
|
39737
39429
|
if (cleaned.startsWith("```json")) {
|
|
@@ -39755,8 +39447,8 @@ function parseReviewFeedback(response) {
|
|
|
39755
39447
|
}
|
|
39756
39448
|
async function executeReview(options) {
|
|
39757
39449
|
const { videoPath, storyboardPath, autoApply = false, verify = false, model = "flash" } = options;
|
|
39758
|
-
const absVideoPath =
|
|
39759
|
-
if (!
|
|
39450
|
+
const absVideoPath = resolve31(process.cwd(), videoPath);
|
|
39451
|
+
if (!existsSync24(absVideoPath)) {
|
|
39760
39452
|
return { success: false, error: `Video not found: ${absVideoPath}` };
|
|
39761
39453
|
}
|
|
39762
39454
|
const apiKey = process.env.GOOGLE_API_KEY || await getApiKey("GOOGLE_API_KEY", "Google");
|
|
@@ -39765,9 +39457,9 @@ async function executeReview(options) {
|
|
|
39765
39457
|
}
|
|
39766
39458
|
let storyboardContext = "";
|
|
39767
39459
|
if (storyboardPath) {
|
|
39768
|
-
const absStoryboardPath =
|
|
39769
|
-
if (
|
|
39770
|
-
const content = await
|
|
39460
|
+
const absStoryboardPath = resolve31(process.cwd(), storyboardPath);
|
|
39461
|
+
if (existsSync24(absStoryboardPath)) {
|
|
39462
|
+
const content = await readFile21(absStoryboardPath, "utf-8");
|
|
39771
39463
|
storyboardContext = `
|
|
39772
39464
|
|
|
39773
39465
|
Original storyboard for reference:
|
|
@@ -39800,7 +39492,7 @@ ${content}`;
|
|
|
39800
39492
|
Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFixable. Be specific and practical.${storyboardContext}`;
|
|
39801
39493
|
const gemini = new GeminiProvider();
|
|
39802
39494
|
await gemini.initialize({ apiKey });
|
|
39803
|
-
const videoData = await
|
|
39495
|
+
const videoData = await readFile21(absVideoPath);
|
|
39804
39496
|
const analysisResult = await gemini.analyzeVideo(videoData, reviewPrompt, {
|
|
39805
39497
|
model: modelId
|
|
39806
39498
|
});
|
|
@@ -39821,7 +39513,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
|
|
|
39821
39513
|
};
|
|
39822
39514
|
if (autoApply && feedback.autoFixable.length > 0) {
|
|
39823
39515
|
let currentInput = absVideoPath;
|
|
39824
|
-
const outputBase = options.outputPath ?
|
|
39516
|
+
const outputBase = options.outputPath ? resolve31(process.cwd(), options.outputPath) : absVideoPath.replace(/(\.[^.]+)$/, "-reviewed$1");
|
|
39825
39517
|
for (const fix of feedback.autoFixable) {
|
|
39826
39518
|
if (fix.type === "color_grade" && fix.ffmpegFilter) {
|
|
39827
39519
|
try {
|
|
@@ -39838,7 +39530,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
|
|
|
39838
39530
|
if (currentInput !== absVideoPath) {
|
|
39839
39531
|
const finalOutput = outputBase;
|
|
39840
39532
|
try {
|
|
39841
|
-
await
|
|
39533
|
+
await rename2(currentInput, finalOutput);
|
|
39842
39534
|
result.outputPath = finalOutput;
|
|
39843
39535
|
} catch {
|
|
39844
39536
|
result.outputPath = currentInput;
|
|
@@ -39846,7 +39538,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
|
|
|
39846
39538
|
}
|
|
39847
39539
|
}
|
|
39848
39540
|
if (verify && result.outputPath) {
|
|
39849
|
-
const verifyVideoData = await
|
|
39541
|
+
const verifyVideoData = await readFile21(result.outputPath);
|
|
39850
39542
|
const verifyResult = await gemini.analyzeVideo(
|
|
39851
39543
|
verifyVideoData,
|
|
39852
39544
|
'Rate this video overall quality on a scale of 1-10. Return ONLY a JSON object: {"score": <number>}',
|
|
@@ -39905,73 +39597,657 @@ function registerReviewCommand(aiCommand) {
|
|
|
39905
39597
|
spinner2.fail(result.error || "Video review failed");
|
|
39906
39598
|
exitWithError(apiError(result.error || "Video review failed", true));
|
|
39907
39599
|
}
|
|
39908
|
-
spinner2.succeed(source_default.green("Video review complete"));
|
|
39909
|
-
console.log();
|
|
39910
|
-
const fb = result.feedback;
|
|
39911
|
-
console.log(source_default.bold.cyan("Video Review"));
|
|
39912
|
-
console.log(source_default.dim("\u2500".repeat(60)));
|
|
39913
|
-
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`);
|
|
39914
|
-
console.log();
|
|
39915
|
-
const categories = [
|
|
39916
|
-
["Pacing", fb.categories.pacing],
|
|
39917
|
-
["Color", fb.categories.color],
|
|
39918
|
-
["Text Readability", fb.categories.textReadability],
|
|
39919
|
-
["Audio-Visual Sync", fb.categories.audioVisualSync],
|
|
39920
|
-
["Composition", fb.categories.composition]
|
|
39921
|
-
];
|
|
39922
|
-
for (const [name, cat] of categories) {
|
|
39923
|
-
const scoreColor = cat.score >= 7 ? source_default.green : cat.score >= 5 ? source_default.yellow : source_default.red;
|
|
39924
|
-
const fixable = cat.fixable ? source_default.dim(" [fixable]") : "";
|
|
39925
|
-
console.log(` ${name.padEnd(20)} ${scoreColor(String(cat.score).padStart(2))}/10${fixable}`);
|
|
39926
|
-
if (cat.issues.length > 0) {
|
|
39927
|
-
for (const issue of cat.issues) {
|
|
39928
|
-
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
|
+
}
|
|
39929
39939
|
}
|
|
39930
39940
|
}
|
|
39931
|
-
|
|
39932
|
-
|
|
39933
|
-
|
|
39934
|
-
|
|
39935
|
-
|
|
39936
|
-
|
|
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
|
+
}
|
|
39937
40006
|
}
|
|
39938
|
-
if (
|
|
39939
|
-
|
|
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;
|
|
39940
40015
|
}
|
|
39941
40016
|
}
|
|
39942
|
-
if (
|
|
39943
|
-
|
|
39944
|
-
|
|
40017
|
+
if (sceneFailed) {
|
|
40018
|
+
result.failedScenes.push(sceneNum);
|
|
40019
|
+
continue;
|
|
39945
40020
|
}
|
|
39946
|
-
if (
|
|
39947
|
-
|
|
39948
|
-
|
|
39949
|
-
|
|
39950
|
-
|
|
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
|
+
}
|
|
39951
40210
|
}
|
|
40211
|
+
} else if (!sceneFailed) {
|
|
40212
|
+
result.regeneratedScenes.push(sceneNum);
|
|
39952
40213
|
}
|
|
39953
|
-
console.log();
|
|
39954
|
-
} catch (error) {
|
|
39955
|
-
exitWithError(generalError(error instanceof Error ? error.message : "Video review failed"));
|
|
39956
40214
|
}
|
|
39957
|
-
|
|
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
|
+
}
|
|
39958
40232
|
}
|
|
39959
|
-
var
|
|
39960
|
-
|
|
40233
|
+
var import_yaml5;
|
|
40234
|
+
var init_ai_script_pipeline = __esm({
|
|
40235
|
+
"src/commands/ai-script-pipeline.ts"() {
|
|
39961
40236
|
"use strict";
|
|
39962
|
-
|
|
39963
|
-
init_ora();
|
|
40237
|
+
import_yaml5 = __toESM(require_dist(), 1);
|
|
39964
40238
|
init_dist2();
|
|
39965
40239
|
init_api_key();
|
|
39966
|
-
|
|
39967
|
-
|
|
39968
|
-
|
|
40240
|
+
init_config();
|
|
40241
|
+
init_audio();
|
|
40242
|
+
init_ai_helpers();
|
|
40243
|
+
init_video_utils();
|
|
40244
|
+
init_video_providers();
|
|
39969
40245
|
}
|
|
39970
40246
|
});
|
|
39971
40247
|
|
|
39972
40248
|
// src/commands/ai-highlights.ts
|
|
39973
40249
|
import { readFile as readFile26, writeFile as writeFile33, mkdir as mkdir16 } from "node:fs/promises";
|
|
39974
|
-
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";
|
|
39975
40251
|
import { existsSync as existsSync28 } from "node:fs";
|
|
39976
40252
|
function filterHighlights(highlights, options) {
|
|
39977
40253
|
let filtered = highlights.filter((h) => h.confidence >= options.threshold);
|
|
@@ -40015,7 +40291,7 @@ async function executeHighlights(options) {
|
|
|
40015
40291
|
if (!existsSync28(absPath)) {
|
|
40016
40292
|
return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
|
|
40017
40293
|
}
|
|
40018
|
-
const ext =
|
|
40294
|
+
const ext = extname9(absPath).toLowerCase();
|
|
40019
40295
|
const videoExtensions = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
|
|
40020
40296
|
const isVideo = videoExtensions.includes(ext);
|
|
40021
40297
|
const targetDuration = options.duration;
|
|
@@ -40352,7 +40628,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
40352
40628
|
};
|
|
40353
40629
|
for (let i = 0; i < selectedHighlights.length; i++) {
|
|
40354
40630
|
const h = selectedHighlights[i];
|
|
40355
|
-
const baseName = basename9(absPath,
|
|
40631
|
+
const baseName = basename9(absPath, extname9(absPath));
|
|
40356
40632
|
const outputPath = resolve36(outputDir, `${baseName}-short-${i + 1}.mp4`);
|
|
40357
40633
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
40358
40634
|
"-v",
|
|
@@ -40451,7 +40727,7 @@ function registerHighlightsCommands(aiCommand) {
|
|
|
40451
40727
|
});
|
|
40452
40728
|
return;
|
|
40453
40729
|
}
|
|
40454
|
-
const ext =
|
|
40730
|
+
const ext = extname9(absPath).toLowerCase();
|
|
40455
40731
|
const videoExtensions = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
|
|
40456
40732
|
const isVideo = videoExtensions.includes(ext);
|
|
40457
40733
|
console.log();
|
|
@@ -40929,11 +41205,11 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
40929
41205
|
let outputPath;
|
|
40930
41206
|
if (shortCount === 1 && options.output) {
|
|
40931
41207
|
outputPath = resolve36(process.cwd(), options.output);
|
|
40932
|
-
if (!
|
|
41208
|
+
if (!extname9(outputPath)) {
|
|
40933
41209
|
outputPath += ".mp4";
|
|
40934
41210
|
}
|
|
40935
41211
|
} else {
|
|
40936
|
-
const baseName = basename9(absPath,
|
|
41212
|
+
const baseName = basename9(absPath, extname9(absPath));
|
|
40937
41213
|
outputPath = resolve36(outputDir, `${baseName}-short-${i + 1}.mp4`);
|
|
40938
41214
|
}
|
|
40939
41215
|
const parentDir = dirname12(outputPath);
|
|
@@ -51819,7 +52095,7 @@ var init_scene_audio_scan = __esm({
|
|
|
51819
52095
|
|
|
51820
52096
|
// src/commands/_shared/scene-audio-mux.ts
|
|
51821
52097
|
import { rename as rename4, unlink as unlink3 } from "node:fs/promises";
|
|
51822
|
-
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";
|
|
51823
52099
|
function buildAudioMuxFilter(audios) {
|
|
51824
52100
|
if (audios.length === 0) return null;
|
|
51825
52101
|
const labels = [];
|
|
@@ -51878,7 +52154,7 @@ async function muxAudioIntoVideo(opts) {
|
|
|
51878
52154
|
if (!filter) {
|
|
51879
52155
|
return { success: true, outputPath: opts.videoPath, audioCount: 0 };
|
|
51880
52156
|
}
|
|
51881
|
-
const ext =
|
|
52157
|
+
const ext = extname12(opts.videoPath) || `.${opts.format}`;
|
|
51882
52158
|
const tmpPath = resolve45(
|
|
51883
52159
|
dirname15(opts.videoPath),
|
|
51884
52160
|
`.${basename16(opts.videoPath, ext)}.muxing${ext}`
|
|
@@ -52536,12 +52812,22 @@ async function executeVideoGenerate(options) {
|
|
|
52536
52812
|
const key = apiKey || process.env[envKeyMap[provider] || ""];
|
|
52537
52813
|
if (!key) return { success: false, error: `${envKeyMap[provider]} required for ${provider}` };
|
|
52538
52814
|
let referenceImage;
|
|
52815
|
+
let referenceImageBuffer;
|
|
52816
|
+
let referenceImageMimeType;
|
|
52539
52817
|
if (image) {
|
|
52540
52818
|
const imagePath = resolve53(process.cwd(), image);
|
|
52541
52819
|
const imageBuffer = await readFile35(imagePath);
|
|
52542
52820
|
const ext = image.toLowerCase().split(".").pop();
|
|
52543
|
-
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
|
+
};
|
|
52544
52828
|
const mimeType = mimeTypes2[ext || "png"] || "image/png";
|
|
52829
|
+
referenceImageBuffer = imageBuffer;
|
|
52830
|
+
referenceImageMimeType = mimeType;
|
|
52545
52831
|
referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
52546
52832
|
}
|
|
52547
52833
|
if (provider === "seedance" || provider === "fal") {
|
|
@@ -52549,12 +52835,12 @@ async function executeVideoGenerate(options) {
|
|
|
52549
52835
|
await fal.initialize({ apiKey: key });
|
|
52550
52836
|
let falImage = referenceImage;
|
|
52551
52837
|
if (falImage && falImage.startsWith("data:")) {
|
|
52552
|
-
const
|
|
52553
|
-
|
|
52554
|
-
|
|
52555
|
-
|
|
52556
|
-
|
|
52557
|
-
falImage =
|
|
52838
|
+
const uploadHost = await resolveUploadHost();
|
|
52839
|
+
const upload = await uploadHost.uploadImage(referenceImageBuffer, {
|
|
52840
|
+
filename: image,
|
|
52841
|
+
mimeType: referenceImageMimeType
|
|
52842
|
+
});
|
|
52843
|
+
falImage = upload.url;
|
|
52558
52844
|
}
|
|
52559
52845
|
const model = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
|
|
52560
52846
|
const result = await fal.generateVideo(prompt3, {
|
|
@@ -52565,14 +52851,22 @@ async function executeVideoGenerate(options) {
|
|
|
52565
52851
|
negativePrompt: negative,
|
|
52566
52852
|
model
|
|
52567
52853
|
});
|
|
52568
|
-
if (result.status === "failed")
|
|
52854
|
+
if (result.status === "failed")
|
|
52855
|
+
return { success: false, error: result.error || "Seedance generation failed" };
|
|
52569
52856
|
let outputPath;
|
|
52570
52857
|
if (output && result.videoUrl) {
|
|
52571
52858
|
const buffer = await downloadVideo(result.videoUrl, key);
|
|
52572
52859
|
outputPath = resolve53(process.cwd(), output);
|
|
52573
52860
|
await writeFile39(outputPath, buffer);
|
|
52574
52861
|
}
|
|
52575
|
-
return {
|
|
52862
|
+
return {
|
|
52863
|
+
success: true,
|
|
52864
|
+
taskId: result.id,
|
|
52865
|
+
status: "completed",
|
|
52866
|
+
videoUrl: result.videoUrl,
|
|
52867
|
+
outputPath,
|
|
52868
|
+
provider: "seedance"
|
|
52869
|
+
};
|
|
52576
52870
|
} else if (provider === "runway") {
|
|
52577
52871
|
const runway = new RunwayProvider();
|
|
52578
52872
|
await runway.initialize({ apiKey: key });
|
|
@@ -52583,30 +52877,41 @@ async function executeVideoGenerate(options) {
|
|
|
52583
52877
|
aspectRatio: ratio,
|
|
52584
52878
|
seed
|
|
52585
52879
|
});
|
|
52586
|
-
if (result.status === "failed")
|
|
52587
|
-
|
|
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" };
|
|
52588
52884
|
const finalResult = await runway.waitForCompletion(result.id, () => {
|
|
52589
52885
|
}, 3e5);
|
|
52590
|
-
if (finalResult.status !== "completed")
|
|
52886
|
+
if (finalResult.status !== "completed")
|
|
52887
|
+
return { success: false, error: finalResult.error || "Runway generation failed" };
|
|
52591
52888
|
let outputPath;
|
|
52592
52889
|
if (output && finalResult.videoUrl) {
|
|
52593
52890
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52594
52891
|
outputPath = resolve53(process.cwd(), output);
|
|
52595
52892
|
await writeFile39(outputPath, buffer);
|
|
52596
52893
|
}
|
|
52597
|
-
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
|
+
};
|
|
52598
52903
|
} else if (provider === "kling") {
|
|
52599
52904
|
const kling = new KlingProvider();
|
|
52600
52905
|
await kling.initialize({ apiKey: key });
|
|
52601
52906
|
if (!kling.isConfigured()) return { success: false, error: "Invalid Kling API key format" };
|
|
52602
52907
|
let klingImage = referenceImage;
|
|
52603
52908
|
if (klingImage && klingImage.startsWith("data:")) {
|
|
52604
|
-
const
|
|
52605
|
-
|
|
52606
|
-
|
|
52607
|
-
|
|
52608
|
-
|
|
52609
|
-
klingImage =
|
|
52909
|
+
const uploadHost = await resolveUploadHost();
|
|
52910
|
+
const upload = await uploadHost.uploadImage(referenceImageBuffer, {
|
|
52911
|
+
filename: image,
|
|
52912
|
+
mimeType: referenceImageMimeType
|
|
52913
|
+
});
|
|
52914
|
+
klingImage = upload.url;
|
|
52610
52915
|
}
|
|
52611
52916
|
const result = await kling.generateVideo(prompt3, {
|
|
52612
52917
|
prompt: prompt3,
|
|
@@ -52616,23 +52921,38 @@ async function executeVideoGenerate(options) {
|
|
|
52616
52921
|
negativePrompt: negative,
|
|
52617
52922
|
mode
|
|
52618
52923
|
});
|
|
52619
|
-
if (result.status === "failed")
|
|
52924
|
+
if (result.status === "failed")
|
|
52925
|
+
return { success: false, error: result.error || "Kling generation failed" };
|
|
52620
52926
|
const taskType = referenceImage ? "image2video" : "text2video";
|
|
52621
|
-
if (!wait)
|
|
52927
|
+
if (!wait)
|
|
52928
|
+
return { success: true, taskId: result.id, status: "processing", provider: "kling" };
|
|
52622
52929
|
const finalResult = await kling.waitForCompletion(result.id, taskType, () => {
|
|
52623
52930
|
}, 6e5);
|
|
52624
|
-
if (finalResult.status !== "completed")
|
|
52931
|
+
if (finalResult.status !== "completed")
|
|
52932
|
+
return { success: false, error: finalResult.error || "Kling generation failed" };
|
|
52625
52933
|
let outputPath;
|
|
52626
52934
|
if (output && finalResult.videoUrl) {
|
|
52627
52935
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52628
52936
|
outputPath = resolve53(process.cwd(), output);
|
|
52629
52937
|
await writeFile39(outputPath, buffer);
|
|
52630
52938
|
}
|
|
52631
|
-
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
|
+
};
|
|
52632
52948
|
} else if (provider === "veo") {
|
|
52633
52949
|
const gemini = new GeminiProvider();
|
|
52634
52950
|
await gemini.initialize({ apiKey: key });
|
|
52635
|
-
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
|
+
};
|
|
52636
52956
|
const model = veoModelMap[veoModel] || "veo-3.1-fast-generate-preview";
|
|
52637
52957
|
const veoDuration = duration <= 6 ? 6 : 8;
|
|
52638
52958
|
const result = await gemini.generateVideo(prompt3, {
|
|
@@ -52644,18 +52964,27 @@ async function executeVideoGenerate(options) {
|
|
|
52644
52964
|
negativePrompt: negative,
|
|
52645
52965
|
resolution
|
|
52646
52966
|
});
|
|
52647
|
-
if (result.status === "failed")
|
|
52967
|
+
if (result.status === "failed")
|
|
52968
|
+
return { success: false, error: result.error || "Veo generation failed" };
|
|
52648
52969
|
if (!wait) return { success: true, taskId: result.id, status: "processing", provider: "veo" };
|
|
52649
52970
|
const finalResult = await gemini.waitForVideoCompletion(result.id, () => {
|
|
52650
52971
|
}, 3e5);
|
|
52651
|
-
if (finalResult.status !== "completed")
|
|
52972
|
+
if (finalResult.status !== "completed")
|
|
52973
|
+
return { success: false, error: finalResult.error || "Veo generation failed" };
|
|
52652
52974
|
let outputPath;
|
|
52653
52975
|
if (output && finalResult.videoUrl) {
|
|
52654
52976
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52655
52977
|
outputPath = resolve53(process.cwd(), output);
|
|
52656
52978
|
await writeFile39(outputPath, buffer);
|
|
52657
52979
|
}
|
|
52658
|
-
return {
|
|
52980
|
+
return {
|
|
52981
|
+
success: true,
|
|
52982
|
+
taskId: result.id,
|
|
52983
|
+
status: "completed",
|
|
52984
|
+
videoUrl: finalResult.videoUrl,
|
|
52985
|
+
outputPath,
|
|
52986
|
+
provider: "veo"
|
|
52987
|
+
};
|
|
52659
52988
|
} else if (provider === "grok") {
|
|
52660
52989
|
const grok = new GrokProvider();
|
|
52661
52990
|
await grok.initialize({ apiKey: key });
|
|
@@ -52665,28 +52994,52 @@ async function executeVideoGenerate(options) {
|
|
|
52665
52994
|
duration,
|
|
52666
52995
|
aspectRatio: ratio
|
|
52667
52996
|
});
|
|
52668
|
-
if (result.status === "failed")
|
|
52669
|
-
|
|
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" };
|
|
52670
53001
|
const finalResult = await grok.waitForCompletion(result.id, () => {
|
|
52671
53002
|
}, 3e5);
|
|
52672
|
-
if (finalResult.status !== "completed")
|
|
53003
|
+
if (finalResult.status !== "completed")
|
|
53004
|
+
return { success: false, error: finalResult.error || "Grok generation failed" };
|
|
52673
53005
|
let outputPath;
|
|
52674
53006
|
if (output && finalResult.videoUrl) {
|
|
52675
53007
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52676
53008
|
outputPath = resolve53(process.cwd(), output);
|
|
52677
53009
|
await writeFile39(outputPath, buffer);
|
|
52678
53010
|
}
|
|
52679
|
-
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
|
+
};
|
|
52680
53020
|
}
|
|
52681
53021
|
return { success: false, error: `Unsupported provider: ${provider}` };
|
|
52682
53022
|
} catch (error) {
|
|
52683
|
-
return {
|
|
53023
|
+
return {
|
|
53024
|
+
success: false,
|
|
53025
|
+
error: `Video generation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
53026
|
+
};
|
|
52684
53027
|
}
|
|
52685
53028
|
}
|
|
52686
53029
|
async function executeVideoStatus(options) {
|
|
52687
|
-
const {
|
|
53030
|
+
const {
|
|
53031
|
+
taskId,
|
|
53032
|
+
provider = "runway",
|
|
53033
|
+
taskType = "text2video",
|
|
53034
|
+
wait = false,
|
|
53035
|
+
output,
|
|
53036
|
+
apiKey
|
|
53037
|
+
} = options;
|
|
52688
53038
|
try {
|
|
52689
|
-
const envKeyMap = {
|
|
53039
|
+
const envKeyMap = {
|
|
53040
|
+
runway: "RUNWAY_API_SECRET",
|
|
53041
|
+
kling: "KLING_API_KEY"
|
|
53042
|
+
};
|
|
52690
53043
|
const key = apiKey || process.env[envKeyMap[provider] || ""];
|
|
52691
53044
|
if (!key) return { success: false, error: `${envKeyMap[provider]} required` };
|
|
52692
53045
|
if (provider === "runway") {
|
|
@@ -52703,7 +53056,14 @@ async function executeVideoStatus(options) {
|
|
|
52703
53056
|
outputPath = resolve53(process.cwd(), output);
|
|
52704
53057
|
await writeFile39(outputPath, buffer);
|
|
52705
53058
|
}
|
|
52706
|
-
return {
|
|
53059
|
+
return {
|
|
53060
|
+
success: true,
|
|
53061
|
+
taskId,
|
|
53062
|
+
status: result.status,
|
|
53063
|
+
progress: result.progress,
|
|
53064
|
+
videoUrl: result.videoUrl,
|
|
53065
|
+
outputPath
|
|
53066
|
+
};
|
|
52707
53067
|
} else if (provider === "kling") {
|
|
52708
53068
|
const kling = new KlingProvider();
|
|
52709
53069
|
await kling.initialize({ apiKey: key });
|
|
@@ -52718,11 +53078,21 @@ async function executeVideoStatus(options) {
|
|
|
52718
53078
|
outputPath = resolve53(process.cwd(), output);
|
|
52719
53079
|
await writeFile39(outputPath, buffer);
|
|
52720
53080
|
}
|
|
52721
|
-
return {
|
|
53081
|
+
return {
|
|
53082
|
+
success: true,
|
|
53083
|
+
taskId,
|
|
53084
|
+
status: result.status,
|
|
53085
|
+
videoUrl: result.videoUrl,
|
|
53086
|
+
duration: result.duration,
|
|
53087
|
+
outputPath
|
|
53088
|
+
};
|
|
52722
53089
|
}
|
|
52723
53090
|
return { success: false, error: `Unsupported provider: ${provider}` };
|
|
52724
53091
|
} catch (error) {
|
|
52725
|
-
return {
|
|
53092
|
+
return {
|
|
53093
|
+
success: false,
|
|
53094
|
+
error: `Status check failed: ${error instanceof Error ? error.message : String(error)}`
|
|
53095
|
+
};
|
|
52726
53096
|
}
|
|
52727
53097
|
}
|
|
52728
53098
|
async function executeVideoCancel(options) {
|
|
@@ -52735,11 +53105,24 @@ async function executeVideoCancel(options) {
|
|
|
52735
53105
|
const success = await runway.cancelGeneration(taskId);
|
|
52736
53106
|
return { success };
|
|
52737
53107
|
} catch (error) {
|
|
52738
|
-
return {
|
|
53108
|
+
return {
|
|
53109
|
+
success: false,
|
|
53110
|
+
error: `Cancel failed: ${error instanceof Error ? error.message : String(error)}`
|
|
53111
|
+
};
|
|
52739
53112
|
}
|
|
52740
53113
|
}
|
|
52741
53114
|
async function executeVideoExtend(options) {
|
|
52742
|
-
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;
|
|
52743
53126
|
try {
|
|
52744
53127
|
if (provider === "kling") {
|
|
52745
53128
|
const key = apiKey || process.env.KLING_API_KEY;
|
|
@@ -52752,52 +53135,76 @@ async function executeVideoExtend(options) {
|
|
|
52752
53135
|
negativePrompt: negative,
|
|
52753
53136
|
duration: String(duration)
|
|
52754
53137
|
});
|
|
52755
|
-
if (result.status === "failed")
|
|
53138
|
+
if (result.status === "failed")
|
|
53139
|
+
return { success: false, error: result.error || "Kling extension failed" };
|
|
52756
53140
|
if (!wait) return { success: true, taskId: result.id, status: "processing" };
|
|
52757
53141
|
const finalResult = await kling.waitForExtendCompletion(result.id, () => {
|
|
52758
53142
|
}, 6e5);
|
|
52759
|
-
if (finalResult.status !== "completed")
|
|
53143
|
+
if (finalResult.status !== "completed")
|
|
53144
|
+
return { success: false, error: finalResult.error || "Kling extension failed" };
|
|
52760
53145
|
let outputPath;
|
|
52761
53146
|
if (output && finalResult.videoUrl) {
|
|
52762
53147
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52763
53148
|
outputPath = resolve53(process.cwd(), output);
|
|
52764
53149
|
await writeFile39(outputPath, buffer);
|
|
52765
53150
|
}
|
|
52766
|
-
return {
|
|
53151
|
+
return {
|
|
53152
|
+
success: true,
|
|
53153
|
+
taskId: result.id,
|
|
53154
|
+
status: "completed",
|
|
53155
|
+
videoUrl: finalResult.videoUrl,
|
|
53156
|
+
duration: finalResult.duration,
|
|
53157
|
+
outputPath
|
|
53158
|
+
};
|
|
52767
53159
|
} else if (provider === "veo") {
|
|
52768
53160
|
const key = apiKey || process.env.GOOGLE_API_KEY;
|
|
52769
53161
|
if (!key) return { success: false, error: "GOOGLE_API_KEY required" };
|
|
52770
53162
|
const gemini = new GeminiProvider();
|
|
52771
53163
|
await gemini.initialize({ apiKey: key });
|
|
52772
|
-
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
|
+
};
|
|
52773
53169
|
const model = veoModelMap[veoModel] || "veo-3.1-generate-preview";
|
|
52774
53170
|
const result = await gemini.extendVideo(videoId, prompt3, {
|
|
52775
53171
|
duration,
|
|
52776
53172
|
model
|
|
52777
53173
|
});
|
|
52778
|
-
if (result.status === "failed")
|
|
53174
|
+
if (result.status === "failed")
|
|
53175
|
+
return { success: false, error: result.error || "Veo extension failed" };
|
|
52779
53176
|
if (!wait) return { success: true, taskId: result.id, status: "processing" };
|
|
52780
53177
|
const finalResult = await gemini.waitForVideoCompletion(result.id, () => {
|
|
52781
53178
|
}, 3e5);
|
|
52782
|
-
if (finalResult.status !== "completed")
|
|
53179
|
+
if (finalResult.status !== "completed")
|
|
53180
|
+
return { success: false, error: finalResult.error || "Veo extension failed" };
|
|
52783
53181
|
let outputPath;
|
|
52784
53182
|
if (output && finalResult.videoUrl) {
|
|
52785
53183
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52786
53184
|
outputPath = resolve53(process.cwd(), output);
|
|
52787
53185
|
await writeFile39(outputPath, buffer);
|
|
52788
53186
|
}
|
|
52789
|
-
return {
|
|
53187
|
+
return {
|
|
53188
|
+
success: true,
|
|
53189
|
+
taskId: result.id,
|
|
53190
|
+
status: "completed",
|
|
53191
|
+
videoUrl: finalResult.videoUrl,
|
|
53192
|
+
outputPath
|
|
53193
|
+
};
|
|
52790
53194
|
}
|
|
52791
53195
|
return { success: false, error: `Unsupported provider: ${provider}` };
|
|
52792
53196
|
} catch (error) {
|
|
52793
|
-
return {
|
|
53197
|
+
return {
|
|
53198
|
+
success: false,
|
|
53199
|
+
error: `Video extension failed: ${error instanceof Error ? error.message : String(error)}`
|
|
53200
|
+
};
|
|
52794
53201
|
}
|
|
52795
53202
|
}
|
|
52796
53203
|
var init_ai_video = __esm({
|
|
52797
53204
|
"src/commands/ai-video.ts"() {
|
|
52798
53205
|
"use strict";
|
|
52799
53206
|
init_dist2();
|
|
52800
|
-
|
|
53207
|
+
init_upload_host();
|
|
52801
53208
|
init_ai_helpers();
|
|
52802
53209
|
}
|
|
52803
53210
|
});
|
|
@@ -52811,7 +53218,7 @@ __export(ai_audio_exports, {
|
|
|
52811
53218
|
executeTranscribe: () => executeTranscribe,
|
|
52812
53219
|
executeVoiceClone: () => executeVoiceClone
|
|
52813
53220
|
});
|
|
52814
|
-
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";
|
|
52815
53222
|
import { readFile as readFile36, writeFile as writeFile40 } from "node:fs/promises";
|
|
52816
53223
|
import { existsSync as existsSync38 } from "node:fs";
|
|
52817
53224
|
async function executeTranscribe(options) {
|
|
@@ -52908,7 +53315,7 @@ async function executeDub(options) {
|
|
|
52908
53315
|
if (!openaiKey) return { success: false, error: "OPENAI_API_KEY required for Whisper transcription" };
|
|
52909
53316
|
if (!anthropicKey) return { success: false, error: "ANTHROPIC_API_KEY required for Claude translation" };
|
|
52910
53317
|
if (!analyzeOnly && !elevenlabsKey) return { success: false, error: "ELEVENLABS_API_KEY required for TTS" };
|
|
52911
|
-
const ext =
|
|
53318
|
+
const ext = extname13(absPath).toLowerCase();
|
|
52912
53319
|
const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
|
|
52913
53320
|
let audioPath = absPath;
|
|
52914
53321
|
if (isVideo) {
|
|
@@ -52993,8 +53400,8 @@ ${segmentTexts}`,
|
|
|
52993
53400
|
}
|
|
52994
53401
|
}
|
|
52995
53402
|
const combinedBuffer = Buffer.concat(dubbedBuffers);
|
|
52996
|
-
const outputExt = isVideo ? ".mp3" :
|
|
52997
|
-
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}`);
|
|
52998
53405
|
const finalOutputPath = resolve54(process.cwd(), output || defaultOutputPath2);
|
|
52999
53406
|
await writeFile40(finalOutputPath, combinedBuffer);
|
|
53000
53407
|
if (isVideo && audioPath !== absPath) {
|
|
@@ -53031,7 +53438,7 @@ async function executeDuck(options) {
|
|
|
53031
53438
|
const absVoicePath = resolve54(process.cwd(), voicePath);
|
|
53032
53439
|
if (!existsSync38(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
|
|
53033
53440
|
if (!existsSync38(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
|
|
53034
|
-
const defaultOutput = resolve54(dirname19(absMusicPath), `${basename18(absMusicPath,
|
|
53441
|
+
const defaultOutput = resolve54(dirname19(absMusicPath), `${basename18(absMusicPath, extname13(absMusicPath))}-ducked${extname13(absMusicPath)}`);
|
|
53035
53442
|
const outputPath = resolve54(process.cwd(), output || defaultOutput);
|
|
53036
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`;
|
|
53037
53444
|
await execSafe("ffmpeg", [
|
|
@@ -59608,11 +60015,17 @@ var init_generate2 = __esm({
|
|
|
59608
60015
|
}),
|
|
59609
60016
|
async execute(args) {
|
|
59610
60017
|
const result = await executeMotion(args);
|
|
59611
|
-
if (!result.success)
|
|
60018
|
+
if (!result.success)
|
|
60019
|
+
return { success: false, error: result.error ?? "Motion generation failed" };
|
|
59612
60020
|
const out = result.compositedPath ?? result.renderedPath ?? result.codePath;
|
|
59613
60021
|
return {
|
|
59614
60022
|
success: true,
|
|
59615
|
-
data: {
|
|
60023
|
+
data: {
|
|
60024
|
+
codePath: result.codePath,
|
|
60025
|
+
renderedPath: result.renderedPath,
|
|
60026
|
+
compositedPath: result.compositedPath,
|
|
60027
|
+
componentName: result.componentName
|
|
60028
|
+
},
|
|
59616
60029
|
humanLines: [`\u2705 Motion generated \u2192 ${out}`]
|
|
59617
60030
|
};
|
|
59618
60031
|
}
|
|
@@ -59676,7 +60089,9 @@ var init_generate2 = __esm({
|
|
|
59676
60089
|
return {
|
|
59677
60090
|
success: true,
|
|
59678
60091
|
data: { outputPath: result.outputPath, provider: result.provider, duration: result.duration },
|
|
59679
|
-
humanLines: [
|
|
60092
|
+
humanLines: [
|
|
60093
|
+
`\u2705 Music${result.provider ? ` (${result.provider})` : ""} \u2192 ${result.outputPath ?? "(async)"}`
|
|
60094
|
+
]
|
|
59680
60095
|
};
|
|
59681
60096
|
}
|
|
59682
60097
|
});
|
|
@@ -59693,7 +60108,12 @@ var init_generate2 = __esm({
|
|
|
59693
60108
|
if (!result.success) return { success: false, error: result.error ?? "Music status failed" };
|
|
59694
60109
|
return {
|
|
59695
60110
|
success: true,
|
|
59696
|
-
data: {
|
|
60111
|
+
data: {
|
|
60112
|
+
taskId: result.taskId,
|
|
60113
|
+
status: result.status,
|
|
60114
|
+
audioUrl: result.audioUrl,
|
|
60115
|
+
error: result.error
|
|
60116
|
+
},
|
|
59697
60117
|
humanLines: [`Music task ${result.taskId}: ${result.status}`]
|
|
59698
60118
|
};
|
|
59699
60119
|
}
|
|
@@ -59705,7 +60125,9 @@ var init_generate2 = __esm({
|
|
|
59705
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).",
|
|
59706
60126
|
schema: external_exports.object({
|
|
59707
60127
|
prompt: external_exports.string().describe("Image description prompt"),
|
|
59708
|
-
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
|
+
),
|
|
59709
60131
|
output: external_exports.string().optional().describe("Output file path"),
|
|
59710
60132
|
size: external_exports.string().optional().describe("Image size for OpenAI (1024x1024, 1536x1024, 1024x1536)"),
|
|
59711
60133
|
ratio: external_exports.string().optional().describe("Aspect ratio for Gemini (1:1, 16:9, 9:16, 4:3, 3:4, etc.)"),
|
|
@@ -59715,10 +60137,16 @@ var init_generate2 = __esm({
|
|
|
59715
60137
|
}),
|
|
59716
60138
|
async execute(args) {
|
|
59717
60139
|
const result = await executeImageGenerate(args);
|
|
59718
|
-
if (!result.success)
|
|
60140
|
+
if (!result.success)
|
|
60141
|
+
return { success: false, error: result.error ?? "Image generation failed" };
|
|
59719
60142
|
return {
|
|
59720
60143
|
success: true,
|
|
59721
|
-
data: {
|
|
60144
|
+
data: {
|
|
60145
|
+
outputPath: result.outputPath,
|
|
60146
|
+
provider: result.provider,
|
|
60147
|
+
model: result.model,
|
|
60148
|
+
imageCount: result.images?.length
|
|
60149
|
+
},
|
|
59722
60150
|
humanLines: [`\u2705 Image (${result.provider}) \u2192 ${result.outputPath}`]
|
|
59723
60151
|
};
|
|
59724
60152
|
}
|
|
@@ -59740,7 +60168,9 @@ var init_generate2 = __esm({
|
|
|
59740
60168
|
return {
|
|
59741
60169
|
success: true,
|
|
59742
60170
|
data: { segmentCount: result.segmentCount, outputPath: result.outputPath },
|
|
59743
|
-
humanLines: [
|
|
60171
|
+
humanLines: [
|
|
60172
|
+
`\u2705 Storyboard: ${result.segmentCount} segments${result.outputPath ? ` \u2192 ${result.outputPath}` : ""}`
|
|
60173
|
+
]
|
|
59744
60174
|
};
|
|
59745
60175
|
}
|
|
59746
60176
|
});
|
|
@@ -59759,7 +60189,11 @@ var init_generate2 = __esm({
|
|
|
59759
60189
|
if (!result.success) return { success: false, error: result.error ?? "Background failed" };
|
|
59760
60190
|
return {
|
|
59761
60191
|
success: true,
|
|
59762
|
-
data: {
|
|
60192
|
+
data: {
|
|
60193
|
+
imageUrl: result.imageUrl,
|
|
60194
|
+
outputPath: result.outputPath,
|
|
60195
|
+
revisedPrompt: result.revisedPrompt
|
|
60196
|
+
},
|
|
59763
60197
|
humanLines: [`\u2705 Background \u2192 ${result.outputPath ?? result.imageUrl}`]
|
|
59764
60198
|
};
|
|
59765
60199
|
}
|
|
@@ -59817,7 +60251,9 @@ var init_generate2 = __esm({
|
|
|
59817
60251
|
outputPath: result.outputPath,
|
|
59818
60252
|
provider: result.provider
|
|
59819
60253
|
},
|
|
59820
|
-
humanLines: [
|
|
60254
|
+
humanLines: [
|
|
60255
|
+
`\u2705 Video (${result.provider}, ${result.status})${result.outputPath ? ` \u2192 ${result.outputPath}` : ""}`
|
|
60256
|
+
]
|
|
59821
60257
|
};
|
|
59822
60258
|
}
|
|
59823
60259
|
});
|
|
@@ -59838,8 +60274,16 @@ var init_generate2 = __esm({
|
|
|
59838
60274
|
if (!result.success) return { success: false, error: result.error ?? "Status check failed" };
|
|
59839
60275
|
return {
|
|
59840
60276
|
success: true,
|
|
59841
|
-
data: {
|
|
59842
|
-
|
|
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
|
+
]
|
|
59843
60287
|
};
|
|
59844
60288
|
}
|
|
59845
60289
|
});
|
|
@@ -59881,8 +60325,16 @@ var init_generate2 = __esm({
|
|
|
59881
60325
|
if (!result.success) return { success: false, error: result.error ?? "Extend failed" };
|
|
59882
60326
|
return {
|
|
59883
60327
|
success: true,
|
|
59884
|
-
data: {
|
|
59885
|
-
|
|
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
|
+
]
|
|
59886
60338
|
};
|
|
59887
60339
|
}
|
|
59888
60340
|
});
|
|
@@ -61813,7 +62265,7 @@ var init_export2 = __esm({
|
|
|
61813
62265
|
|
|
61814
62266
|
// src/tools/manifest/agent-only.ts
|
|
61815
62267
|
import { readFile as readFile41, writeFile as writeFile48, readdir as readdir4, stat as stat6, access as access9, unlink as unlink4 } from "node:fs/promises";
|
|
61816
|
-
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";
|
|
61817
62269
|
function matchPattern(filename, pattern) {
|
|
61818
62270
|
const regex2 = new RegExp(
|
|
61819
62271
|
"^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
|
|
@@ -61832,7 +62284,7 @@ function formatSize(bytes) {
|
|
|
61832
62284
|
return `${size.toFixed(1)}${units2[unit]}`;
|
|
61833
62285
|
}
|
|
61834
62286
|
function detectMediaType4(filePath) {
|
|
61835
|
-
const ext =
|
|
62287
|
+
const ext = extname14(filePath).toLowerCase();
|
|
61836
62288
|
if ([".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"].includes(ext)) return "video";
|
|
61837
62289
|
if ([".mp3", ".wav", ".aac", ".flac", ".ogg", ".m4a"].includes(ext)) return "audio";
|
|
61838
62290
|
if ([".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"].includes(ext)) return "image";
|
|
@@ -61859,7 +62311,7 @@ function isMediaFile2(filePath) {
|
|
|
61859
62311
|
".webp",
|
|
61860
62312
|
".bmp"
|
|
61861
62313
|
];
|
|
61862
|
-
return mediaExts.includes(
|
|
62314
|
+
return mediaExts.includes(extname14(filePath).toLowerCase());
|
|
61863
62315
|
}
|
|
61864
62316
|
async function getMediaDuration2(filePath) {
|
|
61865
62317
|
try {
|
|
@@ -62005,7 +62457,7 @@ var init_agent_only = __esm({
|
|
|
62005
62457
|
if (entry.isDirectory() && recursive) {
|
|
62006
62458
|
await scanDir(entryPath);
|
|
62007
62459
|
} else if (entry.isFile()) {
|
|
62008
|
-
const ext =
|
|
62460
|
+
const ext = extname14(entry.name).toLowerCase();
|
|
62009
62461
|
const matchesFilter = !filterExts || filterExts.includes(ext);
|
|
62010
62462
|
if (matchesFilter && isMediaFile2(entryPath)) mediaFiles.push(entryPath);
|
|
62011
62463
|
}
|
|
@@ -62973,7 +63425,7 @@ init_source();
|
|
|
62973
63425
|
// package.json
|
|
62974
63426
|
var package_default = {
|
|
62975
63427
|
name: "@vibeframe/cli",
|
|
62976
|
-
version: "0.
|
|
63428
|
+
version: "0.92.0",
|
|
62977
63429
|
description: "VibeFrame CLI - natural language video editing from the terminal",
|
|
62978
63430
|
private: false,
|
|
62979
63431
|
license: "MIT",
|
|
@@ -64159,8 +64611,8 @@ init_output();
|
|
|
64159
64611
|
init_sanitize();
|
|
64160
64612
|
init_validate();
|
|
64161
64613
|
init_cost_tier();
|
|
64162
|
-
import { readFile as
|
|
64163
|
-
import { resolve as
|
|
64614
|
+
import { readFile as readFile22, writeFile as writeFile29 } from "node:fs/promises";
|
|
64615
|
+
import { resolve as resolve32 } from "node:path";
|
|
64164
64616
|
var inspectCommand = new Command("inspect").description("Inspect media using AI (images, videos, YouTube URLs)").addHelpText(
|
|
64165
64617
|
"after",
|
|
64166
64618
|
`
|
|
@@ -64357,8 +64809,8 @@ inspectCommand.command("suggest").description("Get AI edit suggestions using Gem
|
|
|
64357
64809
|
}
|
|
64358
64810
|
const apiKey = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
|
|
64359
64811
|
const spinner2 = ora("Initializing Gemini...").start();
|
|
64360
|
-
const filePath =
|
|
64361
|
-
const content = await
|
|
64812
|
+
const filePath = resolve32(process.cwd(), projectPath);
|
|
64813
|
+
const content = await readFile22(filePath, "utf-8");
|
|
64362
64814
|
const data = JSON.parse(content);
|
|
64363
64815
|
const project = Project.fromJSON(data);
|
|
64364
64816
|
const gemini = new GeminiProvider();
|
|
@@ -64395,7 +64847,7 @@ inspectCommand.command("suggest").description("Get AI edit suggestions using Gem
|
|
|
64395
64847
|
const sug = suggestions[0];
|
|
64396
64848
|
const applied = applySuggestion(project, sug);
|
|
64397
64849
|
if (applied) {
|
|
64398
|
-
await
|
|
64850
|
+
await writeFile29(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
64399
64851
|
spinner2.succeed(source_default.green("Suggestion applied"));
|
|
64400
64852
|
} else {
|
|
64401
64853
|
spinner2.warn(source_default.yellow("Could not apply suggestion automatically"));
|
|
@@ -64420,9 +64872,9 @@ init_cost_tier();
|
|
|
64420
64872
|
init_ai_helpers();
|
|
64421
64873
|
init_output();
|
|
64422
64874
|
init_validate();
|
|
64423
|
-
import { resolve as
|
|
64424
|
-
import { readFile as
|
|
64425
|
-
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";
|
|
64426
64878
|
var audioCommand = new Command("audio").alias("au").description("Audio operations (transcribe, TTS, voice clone, ducking)").addHelpText(
|
|
64427
64879
|
"after",
|
|
64428
64880
|
`
|
|
@@ -64455,8 +64907,8 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
|
|
|
64455
64907
|
const whisper = new WhisperProvider();
|
|
64456
64908
|
await whisper.initialize({ apiKey });
|
|
64457
64909
|
spinner2.text = "Reading audio file...";
|
|
64458
|
-
const absPath =
|
|
64459
|
-
const audioBuffer = await
|
|
64910
|
+
const absPath = resolve33(process.cwd(), audioPath);
|
|
64911
|
+
const audioBuffer = await readFile23(absPath);
|
|
64460
64912
|
const audioBlob = new Blob([audioBuffer]);
|
|
64461
64913
|
spinner2.text = "Transcribing...";
|
|
64462
64914
|
const result = await whisper.transcribe(audioBlob, options.language);
|
|
@@ -64473,7 +64925,7 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
|
|
|
64473
64925
|
fullText: result.fullText,
|
|
64474
64926
|
segments: result.segments,
|
|
64475
64927
|
language: result.detectedLanguage,
|
|
64476
|
-
outputPath: options.output ?
|
|
64928
|
+
outputPath: options.output ? resolve33(process.cwd(), options.output) : void 0
|
|
64477
64929
|
}
|
|
64478
64930
|
});
|
|
64479
64931
|
return;
|
|
@@ -64493,10 +64945,10 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
|
|
|
64493
64945
|
console.log();
|
|
64494
64946
|
}
|
|
64495
64947
|
if (options.output) {
|
|
64496
|
-
const outputPath =
|
|
64948
|
+
const outputPath = resolve33(process.cwd(), options.output);
|
|
64497
64949
|
const format = detectFormat(options.output, options.format);
|
|
64498
64950
|
const content = formatTranscript(result, format);
|
|
64499
|
-
await
|
|
64951
|
+
await writeFile30(outputPath, content, "utf-8");
|
|
64500
64952
|
console.log(source_default.green(`Saved ${format.toUpperCase()} to: ${outputPath}`));
|
|
64501
64953
|
}
|
|
64502
64954
|
} catch (error) {
|
|
@@ -64554,8 +65006,8 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
|
|
|
64554
65006
|
}
|
|
64555
65007
|
const apiKey = await requireApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
|
|
64556
65008
|
const spinner2 = ora("Reading audio file...").start();
|
|
64557
|
-
const absPath =
|
|
64558
|
-
const audioBuffer = await
|
|
65009
|
+
const absPath = resolve33(process.cwd(), audioPath);
|
|
65010
|
+
const audioBuffer = await readFile23(absPath);
|
|
64559
65011
|
spinner2.text = "Isolating vocals...";
|
|
64560
65012
|
const elevenlabs = new ElevenLabsProvider();
|
|
64561
65013
|
await elevenlabs.initialize({ apiKey });
|
|
@@ -64564,8 +65016,8 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
|
|
|
64564
65016
|
spinner2.fail(result.error || "Audio isolation failed");
|
|
64565
65017
|
exitWithError(apiError(result.error || "Audio isolation failed", true));
|
|
64566
65018
|
}
|
|
64567
|
-
const outputPath =
|
|
64568
|
-
await
|
|
65019
|
+
const outputPath = resolve33(process.cwd(), options.output);
|
|
65020
|
+
await writeFile30(outputPath, result.audioBuffer);
|
|
64569
65021
|
spinner2.succeed(source_default.green("Vocals isolated"));
|
|
64570
65022
|
if (isJsonMode()) {
|
|
64571
65023
|
outputSuccess({
|
|
@@ -64625,12 +65077,12 @@ audioCommand.command("clone-voice").description("Clone a voice from audio sample
|
|
|
64625
65077
|
const spinner2 = ora("Reading audio samples...").start();
|
|
64626
65078
|
const audioBuffers = [];
|
|
64627
65079
|
for (const samplePath of samples) {
|
|
64628
|
-
const absPath =
|
|
64629
|
-
if (!
|
|
65080
|
+
const absPath = resolve33(process.cwd(), samplePath);
|
|
65081
|
+
if (!existsSync25(absPath)) {
|
|
64630
65082
|
spinner2.fail(`File not found: ${samplePath}`);
|
|
64631
65083
|
exitWithError(notFoundError(samplePath));
|
|
64632
65084
|
}
|
|
64633
|
-
const buffer = await
|
|
65085
|
+
const buffer = await readFile23(absPath);
|
|
64634
65086
|
audioBuffers.push(buffer);
|
|
64635
65087
|
}
|
|
64636
65088
|
spinner2.text = `Cloning voice from ${audioBuffers.length} sample(s)...`;
|
|
@@ -64686,8 +65138,8 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
|
|
|
64686
65138
|
if (!options.language) {
|
|
64687
65139
|
exitWithError(usageError("Target language is required. Use -l or --language"));
|
|
64688
65140
|
}
|
|
64689
|
-
const absPath =
|
|
64690
|
-
if (!
|
|
65141
|
+
const absPath = resolve33(process.cwd(), mediaPath);
|
|
65142
|
+
if (!existsSync25(absPath)) {
|
|
64691
65143
|
exitWithError(notFoundError(mediaPath));
|
|
64692
65144
|
}
|
|
64693
65145
|
const openaiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI");
|
|
@@ -64698,7 +65150,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
|
|
|
64698
65150
|
const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
|
|
64699
65151
|
let audioPath = absPath;
|
|
64700
65152
|
if (isVideo) {
|
|
64701
|
-
const tempAudioPath =
|
|
65153
|
+
const tempAudioPath = resolve33(dirname11(absPath), `temp-audio-${Date.now()}.mp3`);
|
|
64702
65154
|
try {
|
|
64703
65155
|
execSafeSync("ffmpeg", ["-i", absPath, "-vn", "-acodec", "mp3", "-y", tempAudioPath]);
|
|
64704
65156
|
audioPath = tempAudioPath;
|
|
@@ -64710,7 +65162,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
|
|
|
64710
65162
|
spinner2.text = "Transcribing audio...";
|
|
64711
65163
|
const whisper = new WhisperProvider();
|
|
64712
65164
|
await whisper.initialize({ apiKey: openaiKey });
|
|
64713
|
-
const audioBuffer = await
|
|
65165
|
+
const audioBuffer = await readFile23(audioPath);
|
|
64714
65166
|
const audioBlob = new Blob([audioBuffer]);
|
|
64715
65167
|
const transcriptResult = await whisper.transcribe(audioBlob, options.source);
|
|
64716
65168
|
if (transcriptResult.status === "failed" || !transcriptResult.segments) {
|
|
@@ -64791,7 +65243,7 @@ ${segmentTexts}`,
|
|
|
64791
65243
|
console.log();
|
|
64792
65244
|
console.log(source_default.dim("Use without --analyze-only to generate dubbed audio"));
|
|
64793
65245
|
if (options.output) {
|
|
64794
|
-
const timingPath =
|
|
65246
|
+
const timingPath = resolve33(process.cwd(), options.output);
|
|
64795
65247
|
const timingData = {
|
|
64796
65248
|
sourcePath: absPath,
|
|
64797
65249
|
sourceLanguage: transcriptResult.detectedLanguage || options.source || "auto",
|
|
@@ -64804,7 +65256,7 @@ ${segmentTexts}`,
|
|
|
64804
65256
|
translated: translatedSegments[i]?.text || s.text
|
|
64805
65257
|
}))
|
|
64806
65258
|
};
|
|
64807
|
-
await
|
|
65259
|
+
await writeFile30(timingPath, JSON.stringify(timingData, null, 2));
|
|
64808
65260
|
console.log(`Timing saved to: ${source_default.bold(timingPath)}`);
|
|
64809
65261
|
}
|
|
64810
65262
|
return;
|
|
@@ -64829,12 +65281,12 @@ ${segmentTexts}`,
|
|
|
64829
65281
|
spinner2.text = "Combining audio...";
|
|
64830
65282
|
const combinedBuffer = Buffer.concat(dubbedAudioBuffers.map((a) => a.buffer));
|
|
64831
65283
|
const outputExt = isVideo ? ".mp3" : extname7(absPath);
|
|
64832
|
-
const defaultOutputPath2 =
|
|
65284
|
+
const defaultOutputPath2 = resolve33(
|
|
64833
65285
|
dirname11(absPath),
|
|
64834
65286
|
`${basename8(absPath, extname7(absPath))}-${options.language}${outputExt}`
|
|
64835
65287
|
);
|
|
64836
|
-
const finalOutputPath =
|
|
64837
|
-
await
|
|
65288
|
+
const finalOutputPath = resolve33(process.cwd(), options.output || defaultOutputPath2);
|
|
65289
|
+
await writeFile30(finalOutputPath, combinedBuffer);
|
|
64838
65290
|
spinner2.succeed(source_default.green("Dubbing complete"));
|
|
64839
65291
|
if (isJsonMode()) {
|
|
64840
65292
|
outputSuccess({
|
|
@@ -64890,9 +65342,9 @@ audioCommand.command("duck").description("Auto-duck background music when voice
|
|
|
64890
65342
|
exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"));
|
|
64891
65343
|
}
|
|
64892
65344
|
const spinner2 = ora("Processing audio ducking...").start();
|
|
64893
|
-
const absMusic =
|
|
64894
|
-
const absVoice =
|
|
64895
|
-
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");
|
|
64896
65348
|
const thresholdDb = parseFloat(options.threshold);
|
|
64897
65349
|
const thresholdLinear = Math.pow(10, thresholdDb / 20);
|
|
64898
65350
|
const ratio = parseFloat(options.ratio);
|
|
@@ -65500,7 +65952,7 @@ init_esm();
|
|
|
65500
65952
|
init_source();
|
|
65501
65953
|
init_ora();
|
|
65502
65954
|
import { stat as stat4 } from "node:fs/promises";
|
|
65503
|
-
import { resolve as resolve39, extname as
|
|
65955
|
+
import { resolve as resolve39, extname as extname10, basename as basename12 } from "node:path";
|
|
65504
65956
|
|
|
65505
65957
|
// ../../node_modules/.pnpm/music-metadata@11.11.1/node_modules/music-metadata/lib/index.js
|
|
65506
65958
|
init_lib();
|
|
@@ -69377,7 +69829,7 @@ mediaCommand.command("info").description("Get media file information").argument(
|
|
|
69377
69829
|
const spinner2 = ora("Analyzing media...").start();
|
|
69378
69830
|
try {
|
|
69379
69831
|
const filePath = resolve39(process.cwd(), file);
|
|
69380
|
-
const ext =
|
|
69832
|
+
const ext = extname10(filePath).toLowerCase();
|
|
69381
69833
|
const fileName = basename12(filePath);
|
|
69382
69834
|
const fileStat = await stat4(filePath);
|
|
69383
69835
|
const mediaType = detectMediaType2(ext);
|
|
@@ -69519,10 +69971,10 @@ init_engine();
|
|
|
69519
69971
|
init_output();
|
|
69520
69972
|
init_cost_tier();
|
|
69521
69973
|
import { readFile as readFile27, writeFile as writeFile35, readdir as readdir2 } from "node:fs/promises";
|
|
69522
|
-
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";
|
|
69523
69975
|
var batchCommand = new Command("batch").description("Batch operations for processing multiple items");
|
|
69524
69976
|
function detectMediaType3(filePath) {
|
|
69525
|
-
const ext =
|
|
69977
|
+
const ext = extname11(filePath).toLowerCase();
|
|
69526
69978
|
const videoExts = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
|
|
69527
69979
|
const audioExts = [".mp3", ".wav", ".aac", ".flac", ".ogg", ".m4a"];
|
|
69528
69980
|
const imageExts = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"];
|
|
@@ -69532,7 +69984,7 @@ function detectMediaType3(filePath) {
|
|
|
69532
69984
|
return "video";
|
|
69533
69985
|
}
|
|
69534
69986
|
function isMediaFile(filePath) {
|
|
69535
|
-
const ext =
|
|
69987
|
+
const ext = extname11(filePath).toLowerCase();
|
|
69536
69988
|
const mediaExts = [
|
|
69537
69989
|
".mp4",
|
|
69538
69990
|
".mov",
|
|
@@ -69590,7 +70042,7 @@ batchCommand.command("import").description("Import multiple media files from a d
|
|
|
69590
70042
|
if (entry.isDirectory() && options.recursive) {
|
|
69591
70043
|
await scanDir(entryPath);
|
|
69592
70044
|
} else if (entry.isFile()) {
|
|
69593
|
-
const ext =
|
|
70045
|
+
const ext = extname11(entry.name).toLowerCase();
|
|
69594
70046
|
const matchesFilter = !filterExts || filterExts.includes(ext);
|
|
69595
70047
|
if (matchesFilter && isMediaFile(entryPath)) {
|
|
69596
70048
|
mediaFiles.push(entryPath);
|
|
@@ -70058,6 +70510,7 @@ var AI_FEATURES = [
|
|
|
70058
70510
|
desc: "generate + edit",
|
|
70059
70511
|
defaultProvider: "OpenAI gpt-image-2 (Artificial Analysis #1, since v0.56)",
|
|
70060
70512
|
alsoAvailable: "Gemini Nano Banana, Grok Imagine",
|
|
70513
|
+
keyHint: "1 key",
|
|
70061
70514
|
keys: [
|
|
70062
70515
|
{
|
|
70063
70516
|
configKey: "openai",
|
|
@@ -70112,6 +70565,7 @@ var AI_FEATURES = [
|
|
|
70112
70565
|
desc: "generate + extend",
|
|
70113
70566
|
defaultProvider: "Seedance 2.0 via fal.ai (Artificial Analysis #2 t2v + i2v, since v0.57)",
|
|
70114
70567
|
alsoAvailable: "Grok Imagine, Kling, Runway Gen-4.5, Google Veo",
|
|
70568
|
+
keyHint: "1-2 keys",
|
|
70115
70569
|
keys: [
|
|
70116
70570
|
{
|
|
70117
70571
|
configKey: "fal",
|
|
@@ -70132,6 +70586,15 @@ var AI_FEATURES = [
|
|
|
70132
70586
|
url: "https://fal.ai/dashboard/keys",
|
|
70133
70587
|
what: "ByteDance Seedance 2.0 text-to-video and image-to-video"
|
|
70134
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
|
+
],
|
|
70135
70598
|
defaultFor: { kind: "video", value: "seedance" }
|
|
70136
70599
|
},
|
|
70137
70600
|
{
|
|
@@ -70156,6 +70619,15 @@ var AI_FEATURES = [
|
|
|
70156
70619
|
url: "https://platform.klingai.com/",
|
|
70157
70620
|
what: "Kling video generation"
|
|
70158
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
|
+
],
|
|
70159
70631
|
defaultFor: { kind: "video", value: "kling" }
|
|
70160
70632
|
},
|
|
70161
70633
|
{
|
|
@@ -70190,6 +70662,7 @@ var AI_FEATURES = [
|
|
|
70190
70662
|
desc: "TTS, SFX, music, voice clone",
|
|
70191
70663
|
defaultProvider: "ElevenLabs (paid, premium quality) \u2014 falls back to local Kokoro when no key (free, since v0.54)",
|
|
70192
70664
|
alsoAvailable: "Replicate MusicGen (music only)",
|
|
70665
|
+
keyHint: "0-1 key",
|
|
70193
70666
|
keys: [
|
|
70194
70667
|
{
|
|
70195
70668
|
configKey: "elevenlabs",
|
|
@@ -70235,6 +70708,7 @@ var AI_FEATURES = [
|
|
|
70235
70708
|
desc: "captions, grade, reframe, motion graphics",
|
|
70236
70709
|
defaultProvider: "Whisper (transcription) + Claude (reasoning)",
|
|
70237
70710
|
alsoAvailable: "",
|
|
70711
|
+
keyHint: "2 keys",
|
|
70238
70712
|
keys: [
|
|
70239
70713
|
{
|
|
70240
70714
|
configKey: "openai",
|
|
@@ -70268,8 +70742,11 @@ function applyProviderDefault(config2, choice) {
|
|
|
70268
70742
|
}
|
|
70269
70743
|
}
|
|
70270
70744
|
function isProviderChoiceConfigured(config2, choice) {
|
|
70271
|
-
|
|
70272
|
-
|
|
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];
|
|
70273
70750
|
if (value) return true;
|
|
70274
70751
|
}
|
|
70275
70752
|
if (choice.defaultFor?.kind === "image") {
|
|
@@ -70280,6 +70757,96 @@ function isProviderChoiceConfigured(config2, choice) {
|
|
|
70280
70757
|
}
|
|
70281
70758
|
return false;
|
|
70282
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
|
+
}
|
|
70283
70850
|
function isFeatureConfigured(config2, feature) {
|
|
70284
70851
|
if (feature.providerChoices?.some((choice) => isProviderChoiceConfigured(config2, choice))) {
|
|
70285
70852
|
return true;
|
|
@@ -70389,6 +70956,7 @@ async function runNonInteractiveSetup(opts) {
|
|
|
70389
70956
|
}
|
|
70390
70957
|
async function runSetupWizard(fullSetup = false, scope = "user") {
|
|
70391
70958
|
console.log();
|
|
70959
|
+
loadEnv();
|
|
70392
70960
|
const cfgPath = getConfigPath(scope).replace(homedir5(), "~");
|
|
70393
70961
|
const scopeNote = scope === "project" ? "applies to this project only \xB7 add .vibeframe/ to .gitignore" : "applies to every project for this user";
|
|
70394
70962
|
console.log(source_default.bold.magenta("VibeFrame Setup") + source_default.bold(` \u2014 ${scope} scope`));
|
|
@@ -70540,8 +71108,7 @@ async function runAIFeaturesSetup(config2, _scope) {
|
|
|
70540
71108
|
const backIndex = AI_FEATURES.length;
|
|
70541
71109
|
const featureLabels = [
|
|
70542
71110
|
...AI_FEATURES.map((f) => {
|
|
70543
|
-
const
|
|
70544
|
-
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" : ""}`);
|
|
70545
71112
|
return `${f.label} ${source_default.dim(`(${f.desc})`)} ${tag}`;
|
|
70546
71113
|
}),
|
|
70547
71114
|
`Back ${source_default.dim("(return to setup checkpoint)")}`
|
|
@@ -70564,6 +71131,13 @@ async function runAIFeaturesSetup(config2, _scope) {
|
|
|
70564
71131
|
console.log();
|
|
70565
71132
|
return { tryCommand: "vibe --help", selectedFeatures: [] };
|
|
70566
71133
|
}
|
|
71134
|
+
if (hasVideoFeature(selectedFeatures)) {
|
|
71135
|
+
const uploadResult = await configureUploadHost(config2);
|
|
71136
|
+
if (uploadResult === "back") {
|
|
71137
|
+
console.log();
|
|
71138
|
+
continue featureSelection;
|
|
71139
|
+
}
|
|
71140
|
+
}
|
|
70567
71141
|
const plannedKeys = /* @__PURE__ */ new Map();
|
|
70568
71142
|
console.log(source_default.dim("3. Providers"));
|
|
70569
71143
|
console.log(source_default.bold("Choose providers for each selected feature"));
|
|
@@ -70586,7 +71160,12 @@ async function runAIFeaturesSetup(config2, _scope) {
|
|
|
70586
71160
|
(choice) => isProviderChoiceConfigured(config2, choice)
|
|
70587
71161
|
);
|
|
70588
71162
|
const providerLabels = feature.providerChoices.map((choice) => {
|
|
70589
|
-
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");
|
|
70590
71169
|
const defaultHint = choice === feature.providerChoices?.[0] ? source_default.dim("recommended default") : "";
|
|
70591
71170
|
return [choice.label, defaultHint, source_default.dim(`- ${choice.desc}`), keyHint].filter(Boolean).join(" ");
|
|
70592
71171
|
});
|
|
@@ -70612,6 +71191,9 @@ async function runAIFeaturesSetup(config2, _scope) {
|
|
|
70612
71191
|
}
|
|
70613
71192
|
for (const choice of selectedChoices) {
|
|
70614
71193
|
if (choice.key) addUniqueKey(keysForFeature, choice.key);
|
|
71194
|
+
for (const keyDef of getChoiceExtraKeys(config2, choice)) {
|
|
71195
|
+
addUniqueKey(keysForFeature, keyDef);
|
|
71196
|
+
}
|
|
70615
71197
|
}
|
|
70616
71198
|
if (selectedChoices.some((choice) => choice.keyless)) {
|
|
70617
71199
|
console.log(source_default.dim(" No API key needed for selected local provider(s)."));
|
|
@@ -71266,10 +71848,21 @@ OPENROUTER_API_KEY= # multiplexes any provider above
|
|
|
71266
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
|
|
71267
71849
|
ELEVENLABS_API_KEY= # paid TTS / SFX / music (Kokoro is the free fallback)
|
|
71268
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
|
|
71269
71852
|
RUNWAY_API_SECRET= # Runway Gen-4.5 video
|
|
71270
71853
|
KLING_API_KEY= # Kling video
|
|
71271
71854
|
REPLICATE_API_TOKEN= # MusicGen, real-esrgan, etc.
|
|
71272
|
-
|
|
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=
|
|
71273
71866
|
`;
|
|
71274
71867
|
}
|
|
71275
71868
|
var GITIGNORE_ADDITIONS = `
|