@vibeframe/cli 0.91.9 → 0.93.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/CONTEXT.md +1 -1
- package/dist/index.js +1624 -1000
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -14270,6 +14270,9 @@ function getProvidersFor(kind) {
|
|
|
14270
14270
|
function getProviderEnvVars() {
|
|
14271
14271
|
return Object.fromEntries([...apiKeyRegistry.values()].map((k) => [k.configKey, k.envVar]));
|
|
14272
14272
|
}
|
|
14273
|
+
function getProviderEnvAliases() {
|
|
14274
|
+
return Object.fromEntries([...apiKeyRegistry.values()].filter((k) => k.envAliases && k.envAliases.length > 0).map((k) => [k.envVar, k.envAliases ?? []]));
|
|
14275
|
+
}
|
|
14273
14276
|
function getCommandKeyMap() {
|
|
14274
14277
|
const map = {};
|
|
14275
14278
|
for (const p of providerRegistry2.values()) {
|
|
@@ -14377,7 +14380,8 @@ var init_api_keys = __esm({
|
|
|
14377
14380
|
});
|
|
14378
14381
|
defineApiKey({
|
|
14379
14382
|
configKey: "fal",
|
|
14380
|
-
envVar: "
|
|
14383
|
+
envVar: "FAL_API_KEY",
|
|
14384
|
+
envAliases: ["FAL_KEY"],
|
|
14381
14385
|
label: "fal.ai",
|
|
14382
14386
|
showInSetup: true,
|
|
14383
14387
|
setupDescription: "Seedance 2.0 video gen ($$, default since v0.57)",
|
|
@@ -14394,7 +14398,7 @@ var init_api_keys = __esm({
|
|
|
14394
14398
|
label: "xAI",
|
|
14395
14399
|
showInSetup: true,
|
|
14396
14400
|
setupDescription: "Grok \u2014 video gen with audio ($$), image ($), Agent",
|
|
14397
|
-
envExampleComment: "xAI API Key (Grok video generation \u2014 fallback when no
|
|
14401
|
+
envExampleComment: "xAI API Key (Grok video generation \u2014 fallback when no FAL_API_KEY)",
|
|
14398
14402
|
envExampleUrl: "https://console.x.ai/",
|
|
14399
14403
|
keyFormat: { prefix: /^xai-/, example: "xai-..." }
|
|
14400
14404
|
});
|
|
@@ -14443,8 +14447,8 @@ var init_api_keys = __esm({
|
|
|
14443
14447
|
configKey: "imgbb",
|
|
14444
14448
|
envVar: "IMGBB_API_KEY",
|
|
14445
14449
|
label: "ImgBB",
|
|
14446
|
-
showInSetup:
|
|
14447
|
-
|
|
14450
|
+
showInSetup: true,
|
|
14451
|
+
setupDescription: "image hosting for Seedance/Kling image-to-video uploads",
|
|
14448
14452
|
envExampleComment: "ImgBB API Key (image hosting \u2014 used by Kling and Seedance for image-to-video uploads)",
|
|
14449
14453
|
envExampleUrl: "https://api.imgbb.com/",
|
|
14450
14454
|
// ImgBB issues 32-character lowercase hex keys. Soft-warn if a pasted
|
|
@@ -14453,9 +14457,7 @@ var init_api_keys = __esm({
|
|
|
14453
14457
|
keyFormat: { prefix: /^[a-f0-9]{32}$/, example: "32-char hex" },
|
|
14454
14458
|
// ImgBB has no provider class (envvar-only); doctor still shows what it
|
|
14455
14459
|
// unlocks at the apiKey level.
|
|
14456
|
-
commandsUnlocked: [
|
|
14457
|
-
"generate video -p kling/seedance (image-to-video upload host)"
|
|
14458
|
-
]
|
|
14460
|
+
commandsUnlocked: ["generate video -p kling/seedance (image-to-video upload host)"]
|
|
14459
14461
|
});
|
|
14460
14462
|
defineProvider({
|
|
14461
14463
|
id: "openrouter",
|
|
@@ -24453,7 +24455,7 @@ var init_FalProvider = __esm({
|
|
|
24453
24455
|
return {
|
|
24454
24456
|
id: "",
|
|
24455
24457
|
status: "failed",
|
|
24456
|
-
error: "fal.ai API key not configured. Set
|
|
24458
|
+
error: "fal.ai API key not configured. Set FAL_API_KEY in .env."
|
|
24457
24459
|
};
|
|
24458
24460
|
}
|
|
24459
24461
|
const variant = options?.model ?? DEFAULT_VARIANT;
|
|
@@ -25230,6 +25232,7 @@ __export(dist_exports2, {
|
|
|
25230
25232
|
getCommandKeyMap: () => getCommandKeyMap,
|
|
25231
25233
|
getDisplayLabelForApiKey: () => getDisplayLabelForApiKey,
|
|
25232
25234
|
getKeyFormat: () => getKeyFormat,
|
|
25235
|
+
getProviderEnvAliases: () => getProviderEnvAliases,
|
|
25233
25236
|
getProviderEnvVars: () => getProviderEnvVars,
|
|
25234
25237
|
getProvidersFor: () => getProvidersFor,
|
|
25235
25238
|
getSetupProviders: () => getSetupProviders,
|
|
@@ -25677,12 +25680,19 @@ function createDefaultConfig() {
|
|
|
25677
25680
|
aspectRatio: "16:9",
|
|
25678
25681
|
exportQuality: "standard"
|
|
25679
25682
|
},
|
|
25683
|
+
upload: {
|
|
25684
|
+
provider: "imgbb",
|
|
25685
|
+
ttlSeconds: 3600,
|
|
25686
|
+
s3: {
|
|
25687
|
+
prefix: "vibeframe/tmp"
|
|
25688
|
+
}
|
|
25689
|
+
},
|
|
25680
25690
|
repl: {
|
|
25681
25691
|
autoSave: true
|
|
25682
25692
|
}
|
|
25683
25693
|
};
|
|
25684
25694
|
}
|
|
25685
|
-
var PROVIDER_NAMES, PROVIDER_ENV_VARS;
|
|
25695
|
+
var PROVIDER_NAMES, PROVIDER_ENV_VARS, PROVIDER_ENV_ALIASES;
|
|
25686
25696
|
var init_schema = __esm({
|
|
25687
25697
|
"src/config/schema.ts"() {
|
|
25688
25698
|
"use strict";
|
|
@@ -25696,10 +25706,32 @@ var init_schema = __esm({
|
|
|
25696
25706
|
openrouter: "OpenRouter"
|
|
25697
25707
|
};
|
|
25698
25708
|
PROVIDER_ENV_VARS = getProviderEnvVars();
|
|
25709
|
+
PROVIDER_ENV_ALIASES = getProviderEnvAliases();
|
|
25699
25710
|
}
|
|
25700
25711
|
});
|
|
25701
25712
|
|
|
25702
25713
|
// src/config/index.ts
|
|
25714
|
+
var config_exports = {};
|
|
25715
|
+
__export(config_exports, {
|
|
25716
|
+
CONFIG_DIR: () => CONFIG_DIR,
|
|
25717
|
+
CONFIG_PATH: () => CONFIG_PATH,
|
|
25718
|
+
PROVIDER_ENV_ALIASES: () => PROVIDER_ENV_ALIASES,
|
|
25719
|
+
PROVIDER_ENV_VARS: () => PROVIDER_ENV_VARS,
|
|
25720
|
+
PROVIDER_NAMES: () => PROVIDER_NAMES,
|
|
25721
|
+
USER_CONFIG_DIR: () => USER_CONFIG_DIR,
|
|
25722
|
+
USER_CONFIG_PATH: () => USER_CONFIG_PATH,
|
|
25723
|
+
createDefaultConfig: () => createDefaultConfig,
|
|
25724
|
+
getActiveScope: () => getActiveScope,
|
|
25725
|
+
getApiKeyFromConfig: () => getApiKeyFromConfig,
|
|
25726
|
+
getConfigDir: () => getConfigDir,
|
|
25727
|
+
getConfigPath: () => getConfigPath,
|
|
25728
|
+
getProjectConfigDir: () => getProjectConfigDir,
|
|
25729
|
+
getProjectConfigPath: () => getProjectConfigPath,
|
|
25730
|
+
isConfigured: () => isConfigured,
|
|
25731
|
+
loadConfig: () => loadConfig,
|
|
25732
|
+
saveConfig: () => saveConfig,
|
|
25733
|
+
updateProviderKey: () => updateProviderKey
|
|
25734
|
+
});
|
|
25703
25735
|
import { resolve as resolve4 } from "node:path";
|
|
25704
25736
|
import { homedir } from "node:os";
|
|
25705
25737
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, access as access2 } from "node:fs/promises";
|
|
@@ -25734,6 +25766,11 @@ function applyDefaults(parsed) {
|
|
|
25734
25766
|
llm: { ...defaults.llm, ...parsed.llm },
|
|
25735
25767
|
providers: { ...defaults.providers, ...parsed.providers },
|
|
25736
25768
|
defaults: { ...defaults.defaults, ...parsed.defaults },
|
|
25769
|
+
upload: {
|
|
25770
|
+
...defaults.upload,
|
|
25771
|
+
...parsed.upload,
|
|
25772
|
+
s3: { ...defaults.upload.s3, ...parsed.upload?.s3 }
|
|
25773
|
+
},
|
|
25737
25774
|
repl: { ...defaults.repl, ...parsed.repl }
|
|
25738
25775
|
};
|
|
25739
25776
|
}
|
|
@@ -25761,6 +25798,11 @@ async function loadConfig(options = {}) {
|
|
|
25761
25798
|
llm: { ...user.llm, ...project2.llm },
|
|
25762
25799
|
providers: { ...user.providers, ...project2.providers },
|
|
25763
25800
|
defaults: { ...user.defaults, ...project2.defaults },
|
|
25801
|
+
upload: {
|
|
25802
|
+
...user.upload,
|
|
25803
|
+
...project2.upload,
|
|
25804
|
+
s3: { ...user.upload.s3, ...project2.upload.s3 }
|
|
25805
|
+
},
|
|
25764
25806
|
repl: { ...user.repl, ...project2.repl }
|
|
25765
25807
|
};
|
|
25766
25808
|
}
|
|
@@ -25788,7 +25830,7 @@ async function isConfigured() {
|
|
|
25788
25830
|
return true;
|
|
25789
25831
|
}
|
|
25790
25832
|
const envVar = PROVIDER_ENV_VARS[providerKey];
|
|
25791
|
-
if (envVar &&
|
|
25833
|
+
if (envVar && getEnvValue(envVar)) {
|
|
25792
25834
|
return true;
|
|
25793
25835
|
}
|
|
25794
25836
|
return false;
|
|
@@ -25799,10 +25841,20 @@ async function getApiKeyFromConfig(providerKey) {
|
|
|
25799
25841
|
return config2.providers[providerKey];
|
|
25800
25842
|
}
|
|
25801
25843
|
const envVar = PROVIDER_ENV_VARS[providerKey];
|
|
25802
|
-
if (envVar) return
|
|
25844
|
+
if (envVar) return getEnvValue(envVar);
|
|
25803
25845
|
return void 0;
|
|
25804
25846
|
}
|
|
25805
|
-
|
|
25847
|
+
function getEnvValue(envVar) {
|
|
25848
|
+
return process.env[envVar] || PROVIDER_ENV_ALIASES[envVar]?.map((alias) => process.env[alias]).find(Boolean);
|
|
25849
|
+
}
|
|
25850
|
+
async function updateProviderKey(providerKey, apiKey, options = {}) {
|
|
25851
|
+
const scope = options.scope ?? await getActiveScope(options.cwd);
|
|
25852
|
+
let config2 = await loadConfig({ scope, cwd: options.cwd });
|
|
25853
|
+
if (!config2) config2 = createDefaultConfig();
|
|
25854
|
+
config2.providers[providerKey] = apiKey;
|
|
25855
|
+
await saveConfig(config2, { scope, cwd: options.cwd });
|
|
25856
|
+
}
|
|
25857
|
+
var import_yaml, USER_CONFIG_DIR, USER_CONFIG_PATH, CONFIG_DIR, CONFIG_PATH;
|
|
25806
25858
|
var init_config = __esm({
|
|
25807
25859
|
"src/config/index.ts"() {
|
|
25808
25860
|
"use strict";
|
|
@@ -25811,6 +25863,7 @@ var init_config = __esm({
|
|
|
25811
25863
|
init_schema();
|
|
25812
25864
|
USER_CONFIG_DIR = resolve4(homedir(), ".vibeframe");
|
|
25813
25865
|
USER_CONFIG_PATH = resolve4(USER_CONFIG_DIR, "config.yaml");
|
|
25866
|
+
CONFIG_DIR = USER_CONFIG_DIR;
|
|
25814
25867
|
CONFIG_PATH = USER_CONFIG_PATH;
|
|
25815
25868
|
}
|
|
25816
25869
|
});
|
|
@@ -25819,6 +25872,12 @@ var init_config = __esm({
|
|
|
25819
25872
|
import { createInterface } from "node:readline";
|
|
25820
25873
|
import { readFile as readFile3, writeFile as writeFile3, access as access3 } from "node:fs/promises";
|
|
25821
25874
|
import { resolve as resolve5 } from "node:path";
|
|
25875
|
+
function providerKeyForEnvVar(envVar) {
|
|
25876
|
+
return PROVIDER_KEY_BY_ENV_VAR[envVar];
|
|
25877
|
+
}
|
|
25878
|
+
function getEnvValue2(envVar) {
|
|
25879
|
+
return process.env[envVar] || PROVIDER_ENV_ALIASES[envVar]?.map((alias) => process.env[alias]).find(Boolean);
|
|
25880
|
+
}
|
|
25822
25881
|
function loadEnv() {
|
|
25823
25882
|
(0, import_dotenv.config)({ path: resolve5(process.cwd(), ".env"), debug: false, quiet: true });
|
|
25824
25883
|
const monorepoRoot = findMonorepoRoot();
|
|
@@ -25881,19 +25940,7 @@ async function getApiKey(envVar, providerName, optionValue) {
|
|
|
25881
25940
|
if (optionValue) {
|
|
25882
25941
|
return optionValue;
|
|
25883
25942
|
}
|
|
25884
|
-
const
|
|
25885
|
-
ANTHROPIC_API_KEY: "anthropic",
|
|
25886
|
-
OPENAI_API_KEY: "openai",
|
|
25887
|
-
GOOGLE_API_KEY: "google",
|
|
25888
|
-
XAI_API_KEY: "xai",
|
|
25889
|
-
ELEVENLABS_API_KEY: "elevenlabs",
|
|
25890
|
-
RUNWAY_API_SECRET: "runway",
|
|
25891
|
-
KLING_API_KEY: "kling",
|
|
25892
|
-
OPENROUTER_API_KEY: "openrouter",
|
|
25893
|
-
IMGBB_API_KEY: "imgbb",
|
|
25894
|
-
REPLICATE_API_TOKEN: "replicate"
|
|
25895
|
-
};
|
|
25896
|
-
const providerKey = providerKeyMap[envVar];
|
|
25943
|
+
const providerKey = providerKeyForEnvVar(envVar);
|
|
25897
25944
|
if (providerKey) {
|
|
25898
25945
|
const configKey = await getApiKeyFromConfig(providerKey);
|
|
25899
25946
|
if (configKey) {
|
|
@@ -25901,7 +25948,7 @@ async function getApiKey(envVar, providerName, optionValue) {
|
|
|
25901
25948
|
}
|
|
25902
25949
|
}
|
|
25903
25950
|
loadEnv();
|
|
25904
|
-
const envValue =
|
|
25951
|
+
const envValue = getEnvValue2(envVar);
|
|
25905
25952
|
if (envValue) {
|
|
25906
25953
|
return envValue;
|
|
25907
25954
|
}
|
|
@@ -25910,7 +25957,9 @@ async function getApiKey(envVar, providerName, optionValue) {
|
|
|
25910
25957
|
}
|
|
25911
25958
|
console.log();
|
|
25912
25959
|
console.log(source_default.yellow(`${providerName} API key not found.`));
|
|
25913
|
-
console.log(
|
|
25960
|
+
console.log(
|
|
25961
|
+
source_default.dim(`Set ${envVar} in .env (current directory), run 'vibe setup', or enter below.`)
|
|
25962
|
+
);
|
|
25914
25963
|
console.log();
|
|
25915
25964
|
const apiKey = await prompt(source_default.cyan(`Enter ${providerName} API key: `), true);
|
|
25916
25965
|
if (!apiKey || apiKey.trim() === "") {
|
|
@@ -25925,7 +25974,7 @@ async function getApiKey(envVar, providerName, optionValue) {
|
|
|
25925
25974
|
}
|
|
25926
25975
|
function hasApiKey(envVar) {
|
|
25927
25976
|
loadEnv();
|
|
25928
|
-
return !!
|
|
25977
|
+
return !!getEnvValue2(envVar);
|
|
25929
25978
|
}
|
|
25930
25979
|
async function requireApiKey(envVar, providerName, cliOverride) {
|
|
25931
25980
|
const key = await getApiKey(envVar, providerName, cliOverride);
|
|
@@ -25954,22 +26003,39 @@ async function saveApiKeyToEnv(envVar, apiKey) {
|
|
|
25954
26003
|
}
|
|
25955
26004
|
await writeFile3(envPath, content, "utf-8");
|
|
25956
26005
|
}
|
|
25957
|
-
var import_dotenv, API_KEY_URLS, ApiKeyError;
|
|
26006
|
+
var import_dotenv, PROVIDER_KEY_BY_ENV_VAR, API_KEY_URLS, ApiKeyError;
|
|
25958
26007
|
var init_api_key = __esm({
|
|
25959
26008
|
"src/utils/api-key.ts"() {
|
|
25960
26009
|
"use strict";
|
|
25961
26010
|
import_dotenv = __toESM(require_main(), 1);
|
|
25962
26011
|
init_source();
|
|
25963
26012
|
init_config();
|
|
26013
|
+
PROVIDER_KEY_BY_ENV_VAR = {
|
|
26014
|
+
ANTHROPIC_API_KEY: "anthropic",
|
|
26015
|
+
OPENAI_API_KEY: "openai",
|
|
26016
|
+
GOOGLE_API_KEY: "google",
|
|
26017
|
+
XAI_API_KEY: "xai",
|
|
26018
|
+
ELEVENLABS_API_KEY: "elevenlabs",
|
|
26019
|
+
FAL_API_KEY: "fal",
|
|
26020
|
+
FAL_KEY: "fal",
|
|
26021
|
+
RUNWAY_API_SECRET: "runway",
|
|
26022
|
+
KLING_API_KEY: "kling",
|
|
26023
|
+
OPENROUTER_API_KEY: "openrouter",
|
|
26024
|
+
IMGBB_API_KEY: "imgbb",
|
|
26025
|
+
REPLICATE_API_TOKEN: "replicate"
|
|
26026
|
+
};
|
|
25964
26027
|
API_KEY_URLS = {
|
|
25965
26028
|
GOOGLE_API_KEY: "https://aistudio.google.com/apikey",
|
|
25966
26029
|
OPENAI_API_KEY: "https://platform.openai.com/api-keys",
|
|
25967
26030
|
ANTHROPIC_API_KEY: "https://console.anthropic.com/settings/keys",
|
|
25968
26031
|
XAI_API_KEY: "https://console.x.ai",
|
|
25969
26032
|
ELEVENLABS_API_KEY: "https://elevenlabs.io/app/settings/api-keys",
|
|
26033
|
+
FAL_API_KEY: "https://fal.ai/dashboard/keys",
|
|
26034
|
+
FAL_KEY: "https://fal.ai/dashboard/keys",
|
|
25970
26035
|
RUNWAY_API_SECRET: "https://app.runwayml.com/settings/api-keys",
|
|
25971
26036
|
KLING_API_KEY: "https://klingai.com/dev",
|
|
25972
|
-
REPLICATE_API_TOKEN: "https://replicate.com/account/api-tokens"
|
|
26037
|
+
REPLICATE_API_TOKEN: "https://replicate.com/account/api-tokens",
|
|
26038
|
+
IMGBB_API_KEY: "https://api.imgbb.com/"
|
|
25973
26039
|
};
|
|
25974
26040
|
ApiKeyError = class extends Error {
|
|
25975
26041
|
envVar;
|
|
@@ -33353,29 +33419,61 @@ var init_video_extend = __esm({
|
|
|
33353
33419
|
});
|
|
33354
33420
|
|
|
33355
33421
|
// src/utils/provider-resolver.ts
|
|
33422
|
+
function loadProviderDefaultsFromConfig(config2) {
|
|
33423
|
+
configDefaults = {};
|
|
33424
|
+
if (config2?.defaults.imageProvider) configDefaults.image = config2.defaults.imageProvider;
|
|
33425
|
+
if (config2?.defaults.videoProvider) configDefaults.video = config2.defaults.videoProvider;
|
|
33426
|
+
configProviderKeys = new Set(
|
|
33427
|
+
Object.entries(config2?.providers ?? {}).filter(([, value]) => Boolean(value)).map(([key]) => key)
|
|
33428
|
+
);
|
|
33429
|
+
}
|
|
33430
|
+
async function loadProviderDefaults() {
|
|
33431
|
+
try {
|
|
33432
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
33433
|
+
const config2 = await loadConfig2();
|
|
33434
|
+
loadProviderDefaultsFromConfig(config2);
|
|
33435
|
+
} catch {
|
|
33436
|
+
configDefaults = null;
|
|
33437
|
+
configProviderKeys = null;
|
|
33438
|
+
}
|
|
33439
|
+
}
|
|
33440
|
+
function providerKeyForEnvVar2(envVar) {
|
|
33441
|
+
const envVars = getProviderEnvVars();
|
|
33442
|
+
for (const [providerKey, candidateEnvVar] of Object.entries(envVars)) {
|
|
33443
|
+
if (candidateEnvVar === envVar) return providerKey;
|
|
33444
|
+
}
|
|
33445
|
+
return null;
|
|
33446
|
+
}
|
|
33447
|
+
function hasCandidateKey(candidate) {
|
|
33448
|
+
if (candidate.envVar === null) return true;
|
|
33449
|
+
if (hasApiKey(candidate.envVar)) return true;
|
|
33450
|
+
const providerKey = providerKeyForEnvVar2(candidate.envVar);
|
|
33451
|
+
return providerKey ? Boolean(configProviderKeys?.has(providerKey)) : false;
|
|
33452
|
+
}
|
|
33356
33453
|
function resolveProvider(category) {
|
|
33357
33454
|
const candidates = getProvidersFor(category);
|
|
33358
33455
|
if (candidates.length === 0) return null;
|
|
33359
33456
|
if (configDefaults?.[category]) {
|
|
33360
33457
|
const preferred = candidates.find((c) => c.name === configDefaults[category]);
|
|
33361
|
-
if (preferred && (preferred
|
|
33458
|
+
if (preferred && hasCandidateKey(preferred)) {
|
|
33362
33459
|
return { name: preferred.name, label: preferred.label };
|
|
33363
33460
|
}
|
|
33364
33461
|
}
|
|
33365
33462
|
for (const candidate of candidates) {
|
|
33366
|
-
if (
|
|
33463
|
+
if (hasCandidateKey(candidate)) {
|
|
33367
33464
|
return { name: candidate.name, label: candidate.label };
|
|
33368
33465
|
}
|
|
33369
33466
|
}
|
|
33370
33467
|
return null;
|
|
33371
33468
|
}
|
|
33372
|
-
var configDefaults;
|
|
33469
|
+
var configDefaults, configProviderKeys;
|
|
33373
33470
|
var init_provider_resolver = __esm({
|
|
33374
33471
|
"src/utils/provider-resolver.ts"() {
|
|
33375
33472
|
"use strict";
|
|
33376
33473
|
init_dist2();
|
|
33377
33474
|
init_api_key();
|
|
33378
33475
|
configDefaults = null;
|
|
33476
|
+
configProviderKeys = null;
|
|
33379
33477
|
}
|
|
33380
33478
|
});
|
|
33381
33479
|
|
|
@@ -33412,13 +33510,26 @@ import { resolve as resolve23, dirname as dirname7 } from "node:path";
|
|
|
33412
33510
|
import { fileURLToPath } from "node:url";
|
|
33413
33511
|
import { writeFile as writeFile21, mkdir as mkdir11 } from "node:fs/promises";
|
|
33414
33512
|
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(
|
|
33513
|
+
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(
|
|
33514
|
+
"-p, --provider <provider>",
|
|
33515
|
+
"Provider: openai (default when OPENAI_API_KEY set), gemini, grok, runway"
|
|
33516
|
+
).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(
|
|
33517
|
+
"-r, --ratio <ratio>",
|
|
33518
|
+
"Aspect ratio (gemini: 1:1, 1:4, 1:8, 4:1, 8:1, 16:9, 9:16, 3:4, 4:3, etc.)",
|
|
33519
|
+
"1:1"
|
|
33520
|
+
).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(
|
|
33521
|
+
"-m, --model <model>",
|
|
33522
|
+
"Model. Gemini: flash, 3.1-flash, latest, pro. OpenAI: 1.5 (default), 2 (gpt-image-2)"
|
|
33523
|
+
).option("--dry-run", "Preview parameters without executing").addHelpText(
|
|
33524
|
+
"after",
|
|
33525
|
+
`
|
|
33416
33526
|
Examples:
|
|
33417
33527
|
$ vibe generate image "a sunset over the ocean" -o sunset.png
|
|
33418
33528
|
$ vibe gen img "logo design" -o logo.png -p openai
|
|
33419
33529
|
$ vibe gen img "landscape photo" -o wide.png -r 16:9
|
|
33420
33530
|
$ vibe gen img "portrait" -o portrait.png -p gemini -m pro
|
|
33421
|
-
$ vibe gen img "product shot" --dry-run --json`
|
|
33531
|
+
$ vibe gen img "product shot" --dry-run --json`
|
|
33532
|
+
).action(async (prompt3, options) => {
|
|
33422
33533
|
const startedAt = Date.now();
|
|
33423
33534
|
try {
|
|
33424
33535
|
if (!prompt3) {
|
|
@@ -33429,10 +33540,7 @@ Examples:
|
|
|
33429
33540
|
}
|
|
33430
33541
|
} else {
|
|
33431
33542
|
exitWithError(
|
|
33432
|
-
usageError(
|
|
33433
|
-
"Prompt argument is required.",
|
|
33434
|
-
"Usage: vibe generate image <prompt>"
|
|
33435
|
-
)
|
|
33543
|
+
usageError("Prompt argument is required.", "Usage: vibe generate image <prompt>")
|
|
33436
33544
|
);
|
|
33437
33545
|
}
|
|
33438
33546
|
}
|
|
@@ -33440,13 +33548,16 @@ Examples:
|
|
|
33440
33548
|
if (options.output) {
|
|
33441
33549
|
validateOutputPath(options.output);
|
|
33442
33550
|
}
|
|
33551
|
+
await loadProviderDefaults();
|
|
33443
33552
|
if (options.count !== void 0) {
|
|
33444
33553
|
const n = parseInt(options.count, 10);
|
|
33445
33554
|
if (!Number.isFinite(n) || n < 1 || n > 10) {
|
|
33446
|
-
exitWithError(
|
|
33447
|
-
|
|
33448
|
-
|
|
33449
|
-
|
|
33555
|
+
exitWithError(
|
|
33556
|
+
usageError(
|
|
33557
|
+
`Invalid --count: ${options.count}`,
|
|
33558
|
+
"Must be an integer between 1 and 10."
|
|
33559
|
+
)
|
|
33560
|
+
);
|
|
33450
33561
|
}
|
|
33451
33562
|
}
|
|
33452
33563
|
const imageRegistry = getProvidersFor("image");
|
|
@@ -33511,11 +33622,9 @@ Examples:
|
|
|
33511
33622
|
const apiKey = await requireApiKey(envKey, providerName, options.apiKey);
|
|
33512
33623
|
const spinner2 = ora(`Generating image with ${providerName}...`).start();
|
|
33513
33624
|
if (provider === "openai") {
|
|
33514
|
-
const { result, modelLabel } = await executeOpenAIImageGenerate(
|
|
33515
|
-
|
|
33516
|
-
|
|
33517
|
-
{ apiKey }
|
|
33518
|
-
);
|
|
33625
|
+
const { result, modelLabel } = await executeOpenAIImageGenerate(prompt3, options, {
|
|
33626
|
+
apiKey
|
|
33627
|
+
});
|
|
33519
33628
|
if (!result.success || !result.images) {
|
|
33520
33629
|
spinner2.fail(result.error || "Image generation failed");
|
|
33521
33630
|
exitWithError(apiError(result.error || "Image generation failed", true));
|
|
@@ -33737,9 +33846,7 @@ Examples:
|
|
|
33737
33846
|
spinner2.fail(result.error || "Image generation failed");
|
|
33738
33847
|
exitWithError(apiError(result.error || "Image generation failed", true));
|
|
33739
33848
|
}
|
|
33740
|
-
spinner2.succeed(
|
|
33741
|
-
source_default.green(`Generated ${result.images.length} image(s) with xAI Grok`)
|
|
33742
|
-
);
|
|
33849
|
+
spinner2.succeed(source_default.green(`Generated ${result.images.length} image(s) with xAI Grok`));
|
|
33743
33850
|
if (isJsonMode()) {
|
|
33744
33851
|
const outputPath = options.output ? resolve23(process.cwd(), options.output) : void 0;
|
|
33745
33852
|
if (outputPath && result.images.length > 0) {
|
|
@@ -34972,606 +35079,206 @@ var init_video_utils = __esm({
|
|
|
34972
35079
|
}
|
|
34973
35080
|
});
|
|
34974
35081
|
|
|
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;
|
|
35082
|
+
// src/utils/upload-host.ts
|
|
35083
|
+
import { createHash as createHash4, createHmac as createHmac2, randomUUID } from "node:crypto";
|
|
35084
|
+
import { extname as extname3 } from "node:path";
|
|
35085
|
+
function envNumber(name) {
|
|
35086
|
+
const value = process.env[name];
|
|
35087
|
+
if (!value) return void 0;
|
|
35088
|
+
const n = Number.parseInt(value, 10);
|
|
35089
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
35011
35090
|
}
|
|
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;
|
|
35091
|
+
function safePrefix(prefix) {
|
|
35092
|
+
return (prefix ?? "vibeframe/tmp").replace(/^\/+|\/+$/g, "");
|
|
35051
35093
|
}
|
|
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;
|
|
35094
|
+
function extensionFor(opts) {
|
|
35095
|
+
const fromName = opts?.filename ? extname3(opts.filename).replace(/^\./, "") : "";
|
|
35096
|
+
if (fromName) return fromName.toLowerCase();
|
|
35097
|
+
const mime = opts?.mimeType ?? "image/png";
|
|
35098
|
+
if (mime === "image/jpeg") return "jpg";
|
|
35099
|
+
if (mime === "image/webp") return "webp";
|
|
35100
|
+
if (mime === "image/gif") return "gif";
|
|
35101
|
+
return "png";
|
|
35084
35102
|
}
|
|
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;
|
|
35103
|
+
function hmac(key, value) {
|
|
35104
|
+
return createHmac2("sha256", key).update(value).digest();
|
|
35119
35105
|
}
|
|
35120
|
-
|
|
35121
|
-
"
|
|
35122
|
-
|
|
35123
|
-
|
|
35124
|
-
|
|
35125
|
-
|
|
35126
|
-
}
|
|
35127
|
-
|
|
35128
|
-
|
|
35129
|
-
|
|
35130
|
-
|
|
35131
|
-
|
|
35132
|
-
|
|
35133
|
-
const
|
|
35134
|
-
|
|
35135
|
-
|
|
35136
|
-
|
|
35106
|
+
function sha256Hex(value) {
|
|
35107
|
+
return createHash4("sha256").update(value).digest("hex");
|
|
35108
|
+
}
|
|
35109
|
+
function awsEncode(value) {
|
|
35110
|
+
return encodeURIComponent(value).replace(
|
|
35111
|
+
/[!'()*]/g,
|
|
35112
|
+
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
|
|
35113
|
+
);
|
|
35114
|
+
}
|
|
35115
|
+
function presignS3PutUrl(params) {
|
|
35116
|
+
const now = /* @__PURE__ */ new Date();
|
|
35117
|
+
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
35118
|
+
const dateStamp = amzDate.slice(0, 8);
|
|
35119
|
+
const host = `${params.bucket}.s3.${params.region}.amazonaws.com`;
|
|
35120
|
+
const credentialScope = `${dateStamp}/${params.region}/s3/aws4_request`;
|
|
35121
|
+
const canonicalUri = `/${params.key.split("/").map(awsEncode).join("/")}`;
|
|
35122
|
+
const query = {
|
|
35123
|
+
"X-Amz-Algorithm": "AWS4-HMAC-SHA256",
|
|
35124
|
+
"X-Amz-Credential": `${params.accessKeyId}/${credentialScope}`,
|
|
35125
|
+
"X-Amz-Date": amzDate,
|
|
35126
|
+
"X-Amz-Expires": String(params.ttlSeconds),
|
|
35127
|
+
"X-Amz-SignedHeaders": "host"
|
|
35137
35128
|
};
|
|
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
|
-
}
|
|
35129
|
+
if (params.sessionToken) {
|
|
35130
|
+
query["X-Amz-Security-Token"] = params.sessionToken;
|
|
35131
|
+
}
|
|
35132
|
+
const canonicalQuery = Object.entries(query).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${awsEncode(key)}=${awsEncode(value)}`).join("&");
|
|
35133
|
+
const canonicalRequest = [
|
|
35134
|
+
"PUT",
|
|
35135
|
+
canonicalUri,
|
|
35136
|
+
canonicalQuery,
|
|
35137
|
+
`host:${host}`,
|
|
35138
|
+
"",
|
|
35139
|
+
"host",
|
|
35140
|
+
"UNSIGNED-PAYLOAD"
|
|
35141
|
+
].join("\n");
|
|
35142
|
+
const stringToSign = [
|
|
35143
|
+
"AWS4-HMAC-SHA256",
|
|
35144
|
+
amzDate,
|
|
35145
|
+
credentialScope,
|
|
35146
|
+
sha256Hex(canonicalRequest)
|
|
35147
|
+
].join("\n");
|
|
35148
|
+
const dateKey = hmac(`AWS4${params.secretAccessKey}`, dateStamp);
|
|
35149
|
+
const regionKey = hmac(dateKey, params.region);
|
|
35150
|
+
const serviceKey = hmac(regionKey, "s3");
|
|
35151
|
+
const signingKey = hmac(serviceKey, "aws4_request");
|
|
35152
|
+
const signature = createHmac2("sha256", signingKey).update(stringToSign).digest("hex");
|
|
35153
|
+
return `https://${host}${canonicalUri}?${canonicalQuery}&X-Amz-Signature=${signature}`;
|
|
35154
|
+
}
|
|
35155
|
+
function publicS3Url(params) {
|
|
35156
|
+
if (params.publicBaseUrl) {
|
|
35157
|
+
return `${params.publicBaseUrl.replace(/\/+$/g, "")}/${params.key.split("/").map(awsEncode).join("/")}`;
|
|
35158
|
+
}
|
|
35159
|
+
return `https://${params.bucket}.s3.${params.region}.amazonaws.com/${params.key.split("/").map(awsEncode).join("/")}`;
|
|
35160
|
+
}
|
|
35161
|
+
async function resolveUploadSettings() {
|
|
35162
|
+
const config2 = await loadConfig();
|
|
35163
|
+
const provider = process.env.VIBE_UPLOAD_PROVIDER ?? config2?.upload.provider ?? "imgbb";
|
|
35164
|
+
return {
|
|
35165
|
+
provider,
|
|
35166
|
+
ttlSeconds: envNumber("VIBE_UPLOAD_TTL_SECONDS") ?? config2?.upload.ttlSeconds ?? 3600,
|
|
35167
|
+
s3: {
|
|
35168
|
+
bucket: process.env.VIBE_UPLOAD_S3_BUCKET ?? config2?.upload.s3?.bucket,
|
|
35169
|
+
region: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? config2?.upload.s3?.region,
|
|
35170
|
+
prefix: process.env.VIBE_UPLOAD_S3_PREFIX ?? config2?.upload.s3?.prefix,
|
|
35171
|
+
publicBaseUrl: process.env.VIBE_UPLOAD_PUBLIC_BASE_URL ?? config2?.upload.s3?.publicBaseUrl
|
|
35193
35172
|
}
|
|
35194
|
-
|
|
35195
|
-
|
|
35196
|
-
|
|
35197
|
-
|
|
35198
|
-
|
|
35199
|
-
|
|
35173
|
+
};
|
|
35174
|
+
}
|
|
35175
|
+
async function resolveUploadHost() {
|
|
35176
|
+
const settings = await resolveUploadSettings();
|
|
35177
|
+
if (settings.provider === "s3") {
|
|
35178
|
+
const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
|
|
35179
|
+
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
|
|
35180
|
+
const sessionToken = process.env.AWS_SESSION_TOKEN;
|
|
35181
|
+
const { bucket, region } = settings.s3;
|
|
35182
|
+
if (!accessKeyId || !secretAccessKey || !bucket || !region) {
|
|
35183
|
+
throw new Error(
|
|
35184
|
+
"S3 upload host requires AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, and VIBE_UPLOAD_S3_BUCKET."
|
|
35185
|
+
);
|
|
35200
35186
|
}
|
|
35201
|
-
|
|
35202
|
-
|
|
35203
|
-
|
|
35204
|
-
|
|
35205
|
-
|
|
35206
|
-
|
|
35207
|
-
|
|
35208
|
-
|
|
35209
|
-
|
|
35210
|
-
|
|
35211
|
-
|
|
35212
|
-
|
|
35213
|
-
|
|
35214
|
-
|
|
35187
|
+
return {
|
|
35188
|
+
provider: "s3",
|
|
35189
|
+
async uploadImage(imageBuffer, opts) {
|
|
35190
|
+
const ext = extensionFor(opts);
|
|
35191
|
+
const prefix = safePrefix(settings.s3.prefix);
|
|
35192
|
+
const key = `${prefix}/${Date.now()}-${randomUUID()}.${ext}`;
|
|
35193
|
+
const presignedUrl = presignS3PutUrl({
|
|
35194
|
+
accessKeyId,
|
|
35195
|
+
secretAccessKey,
|
|
35196
|
+
sessionToken,
|
|
35197
|
+
region,
|
|
35198
|
+
bucket,
|
|
35199
|
+
key,
|
|
35200
|
+
ttlSeconds: settings.ttlSeconds
|
|
35215
35201
|
});
|
|
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
|
-
}
|
|
35202
|
+
const response = await fetch(presignedUrl, {
|
|
35203
|
+
method: "PUT",
|
|
35204
|
+
headers: {
|
|
35205
|
+
"content-type": opts?.mimeType ?? "application/octet-stream"
|
|
35206
|
+
},
|
|
35207
|
+
body: new Uint8Array(imageBuffer)
|
|
35208
|
+
});
|
|
35209
|
+
if (!response.ok) {
|
|
35210
|
+
throw new Error(`S3 upload failed (${response.status}): ${response.statusText}`);
|
|
35251
35211
|
}
|
|
35252
|
-
|
|
35253
|
-
|
|
35254
|
-
|
|
35255
|
-
|
|
35212
|
+
return {
|
|
35213
|
+
provider: "s3",
|
|
35214
|
+
url: publicS3Url({ region, bucket, key, publicBaseUrl: settings.s3.publicBaseUrl }),
|
|
35215
|
+
expiresAt: new Date(Date.now() + settings.ttlSeconds * 1e3).toISOString()
|
|
35256
35216
|
};
|
|
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
35217
|
}
|
|
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);
|
|
35218
|
+
};
|
|
35219
|
+
}
|
|
35220
|
+
return {
|
|
35221
|
+
provider: "imgbb",
|
|
35222
|
+
async uploadImage(imageBuffer) {
|
|
35223
|
+
const imgbbKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
|
|
35224
|
+
if (!imgbbKey) {
|
|
35225
|
+
throw new Error("IMGBB_API_KEY required for image-to-video uploads.");
|
|
35524
35226
|
}
|
|
35525
|
-
|
|
35526
|
-
|
|
35527
|
-
|
|
35528
|
-
for (const segment of segments) {
|
|
35529
|
-
segment.startTime = currentTime;
|
|
35530
|
-
currentTime += segment.duration;
|
|
35227
|
+
const result = await uploadToImgbb(imageBuffer, imgbbKey);
|
|
35228
|
+
if (!result.success || !result.url) {
|
|
35229
|
+
throw new Error(`ImgBB upload failed: ${result.error ?? "unknown error"}`);
|
|
35531
35230
|
}
|
|
35532
|
-
|
|
35533
|
-
await writeFile23(storyboardPath, serialized, "utf-8");
|
|
35231
|
+
return { provider: "imgbb", url: result.url };
|
|
35534
35232
|
}
|
|
35535
|
-
|
|
35536
|
-
return result;
|
|
35537
|
-
} catch (error) {
|
|
35538
|
-
return {
|
|
35539
|
-
...result,
|
|
35540
|
-
error: error instanceof Error ? error.message : String(error)
|
|
35541
|
-
};
|
|
35542
|
-
}
|
|
35233
|
+
};
|
|
35543
35234
|
}
|
|
35544
|
-
var
|
|
35545
|
-
|
|
35546
|
-
"src/commands/ai-script-pipeline.ts"() {
|
|
35235
|
+
var init_upload_host = __esm({
|
|
35236
|
+
"src/utils/upload-host.ts"() {
|
|
35547
35237
|
"use strict";
|
|
35548
|
-
import_yaml5 = __toESM(require_dist(), 1);
|
|
35549
|
-
init_dist2();
|
|
35550
|
-
init_api_key();
|
|
35551
35238
|
init_config();
|
|
35552
|
-
init_audio();
|
|
35553
|
-
init_ai_helpers();
|
|
35554
35239
|
init_video_utils();
|
|
35555
|
-
init_video_providers();
|
|
35556
35240
|
}
|
|
35557
35241
|
});
|
|
35558
35242
|
|
|
35559
35243
|
// src/commands/generate/video.ts
|
|
35560
|
-
import { resolve as
|
|
35561
|
-
import { readFile as
|
|
35244
|
+
import { resolve as resolve25 } from "node:path";
|
|
35245
|
+
import { readFile as readFile13, writeFile as writeFile23 } from "node:fs/promises";
|
|
35562
35246
|
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(
|
|
35247
|
+
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(
|
|
35248
|
+
"-p, --provider <provider>",
|
|
35249
|
+
"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."
|
|
35250
|
+
).option(
|
|
35251
|
+
"-k, --api-key <key>",
|
|
35252
|
+
"API key (or set FAL_API_KEY / XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)"
|
|
35253
|
+
).option("-o, --output <path>", "Output file path (downloads video)").option("-i, --image <path>", "Reference image for image-to-video").option(
|
|
35564
35254
|
"-d, --duration <sec>",
|
|
35565
35255
|
"Duration in seconds. Seedance accepts 4-15; Kling accepts 5 or 10; Veo maps to 6 or 8.",
|
|
35566
35256
|
"5"
|
|
35567
|
-
).option(
|
|
35257
|
+
).option(
|
|
35258
|
+
"-r, --ratio <ratio>",
|
|
35259
|
+
"Aspect ratio: 16:9, 9:16, or 1:1 (auto-detected from image if omitted)"
|
|
35260
|
+
).option("--seed <number>", "Random seed for reproducibility (Runway only)").option("--mode <mode>", "Generation mode: std or pro (Kling only)", "std").option(
|
|
35261
|
+
"--seedance-model <model>",
|
|
35262
|
+
"Seedance variant: quality or fast (fal.ai only)",
|
|
35263
|
+
"quality"
|
|
35264
|
+
).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(
|
|
35265
|
+
"--ref-images <paths...>",
|
|
35266
|
+
"Reference images for character consistency (Veo 3.1 only, max 3)"
|
|
35267
|
+
).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(
|
|
35268
|
+
"--runway-model <model>",
|
|
35269
|
+
"Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)",
|
|
35270
|
+
"gen4.5"
|
|
35271
|
+
).option("--no-wait", "Start generation and return task ID without waiting").option("--dry-run", "Preview parameters without executing").addHelpText(
|
|
35272
|
+
"after",
|
|
35273
|
+
`
|
|
35568
35274
|
Examples:
|
|
35569
|
-
$ vibe generate video "dancing cat" -o cat.mp4 # Seedance when
|
|
35275
|
+
$ vibe generate video "dancing cat" -o cat.mp4 # Seedance when FAL_API_KEY is set
|
|
35570
35276
|
$ vibe gen vid "cinematic city timelapse" -o city.mp4 -p seedance # Seedance via fal.ai
|
|
35571
35277
|
$ vibe gen vid "city timelapse" -o city.mp4 -p kling # Kling
|
|
35572
35278
|
$ vibe gen vid "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
|
|
35573
35279
|
$ 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`
|
|
35280
|
+
$ vibe gen vid "sunset" -o sun.mp4 -d 10 --dry-run --json`
|
|
35281
|
+
).action(async (prompt3, options) => {
|
|
35575
35282
|
const startedAt = Date.now();
|
|
35576
35283
|
try {
|
|
35577
35284
|
if (!prompt3) {
|
|
@@ -35582,10 +35289,7 @@ Examples:
|
|
|
35582
35289
|
}
|
|
35583
35290
|
} else {
|
|
35584
35291
|
exitWithError(
|
|
35585
|
-
usageError(
|
|
35586
|
-
"Prompt argument is required.",
|
|
35587
|
-
"Usage: vibe generate video <prompt>"
|
|
35588
|
-
)
|
|
35292
|
+
usageError("Prompt argument is required.", "Usage: vibe generate video <prompt>")
|
|
35589
35293
|
);
|
|
35590
35294
|
}
|
|
35591
35295
|
}
|
|
@@ -35593,13 +35297,16 @@ Examples:
|
|
|
35593
35297
|
if (options.output) {
|
|
35594
35298
|
validateOutputPath(options.output);
|
|
35595
35299
|
}
|
|
35300
|
+
await loadProviderDefaults();
|
|
35596
35301
|
if (options.duration !== void 0) {
|
|
35597
35302
|
const d = parseFloat(options.duration);
|
|
35598
35303
|
if (!Number.isFinite(d) || d <= 0 || d > 60) {
|
|
35599
|
-
exitWithError(
|
|
35600
|
-
|
|
35601
|
-
|
|
35602
|
-
|
|
35304
|
+
exitWithError(
|
|
35305
|
+
usageError(
|
|
35306
|
+
`Invalid --duration: ${options.duration}`,
|
|
35307
|
+
"Must be a positive number \u2264 60 seconds."
|
|
35308
|
+
)
|
|
35309
|
+
);
|
|
35603
35310
|
}
|
|
35604
35311
|
}
|
|
35605
35312
|
const validProviders = ["runway", "kling", "veo", "grok", "seedance", "fal"];
|
|
@@ -35608,7 +35315,7 @@ Examples:
|
|
|
35608
35315
|
veo: "GOOGLE_API_KEY",
|
|
35609
35316
|
kling: "KLING_API_KEY",
|
|
35610
35317
|
runway: "RUNWAY_API_SECRET",
|
|
35611
|
-
seedance: "
|
|
35318
|
+
seedance: "FAL_API_KEY"
|
|
35612
35319
|
};
|
|
35613
35320
|
let provider;
|
|
35614
35321
|
if (options.provider) {
|
|
@@ -35641,10 +35348,12 @@ Examples:
|
|
|
35641
35348
|
provider = resolved?.name ?? "grok";
|
|
35642
35349
|
}
|
|
35643
35350
|
let referenceImage;
|
|
35351
|
+
let referenceImageBuffer;
|
|
35352
|
+
let referenceImageMimeType;
|
|
35644
35353
|
let isImageToVideo = false;
|
|
35645
35354
|
if (options.image) {
|
|
35646
|
-
const imagePath =
|
|
35647
|
-
const imageBuffer = await
|
|
35355
|
+
const imagePath = resolve25(process.cwd(), options.image);
|
|
35356
|
+
const imageBuffer = await readFile13(imagePath);
|
|
35648
35357
|
const ext = options.image.toLowerCase().split(".").pop();
|
|
35649
35358
|
const mimeTypes2 = {
|
|
35650
35359
|
jpg: "image/jpeg",
|
|
@@ -35654,6 +35363,8 @@ Examples:
|
|
|
35654
35363
|
webp: "image/webp"
|
|
35655
35364
|
};
|
|
35656
35365
|
const mimeType = mimeTypes2[ext || "png"] || "image/png";
|
|
35366
|
+
referenceImageBuffer = imageBuffer;
|
|
35367
|
+
referenceImageMimeType = mimeType;
|
|
35657
35368
|
referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
35658
35369
|
isImageToVideo = true;
|
|
35659
35370
|
if (!options.ratio) {
|
|
@@ -35707,7 +35418,7 @@ Examples:
|
|
|
35707
35418
|
kling: "KLING_API_KEY",
|
|
35708
35419
|
veo: "GOOGLE_API_KEY",
|
|
35709
35420
|
grok: "XAI_API_KEY",
|
|
35710
|
-
seedance: "
|
|
35421
|
+
seedance: "FAL_API_KEY"
|
|
35711
35422
|
};
|
|
35712
35423
|
const providerNameMap = {
|
|
35713
35424
|
runway: "Runway",
|
|
@@ -35778,20 +35489,22 @@ Examples:
|
|
|
35778
35489
|
}
|
|
35779
35490
|
let klingImage = referenceImage;
|
|
35780
35491
|
if (klingImage && klingImage.startsWith("data:")) {
|
|
35781
|
-
|
|
35782
|
-
|
|
35783
|
-
|
|
35784
|
-
|
|
35785
|
-
|
|
35786
|
-
|
|
35787
|
-
|
|
35788
|
-
|
|
35789
|
-
|
|
35790
|
-
|
|
35791
|
-
|
|
35792
|
-
|
|
35793
|
-
|
|
35794
|
-
|
|
35492
|
+
try {
|
|
35493
|
+
const uploadHost = await resolveUploadHost();
|
|
35494
|
+
spinner2.text = `Uploading image via ${uploadHost.provider} for Kling...`;
|
|
35495
|
+
const upload = await uploadHost.uploadImage(referenceImageBuffer, {
|
|
35496
|
+
filename: options.image,
|
|
35497
|
+
mimeType: referenceImageMimeType
|
|
35498
|
+
});
|
|
35499
|
+
klingImage = upload.url;
|
|
35500
|
+
} catch (err) {
|
|
35501
|
+
spinner2.fail("Image upload failed");
|
|
35502
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
35503
|
+
if (message.includes("IMGBB_API_KEY")) {
|
|
35504
|
+
exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
|
|
35505
|
+
}
|
|
35506
|
+
exitWithError(apiError(message, true));
|
|
35507
|
+
}
|
|
35795
35508
|
spinner2.text = "Starting video generation...";
|
|
35796
35509
|
}
|
|
35797
35510
|
result = await kling.generateVideo(prompt3, {
|
|
@@ -35846,8 +35559,8 @@ Examples:
|
|
|
35846
35559
|
const veoDuration = parseInt(options.duration) <= 6 ? 6 : 8;
|
|
35847
35560
|
let lastFrame;
|
|
35848
35561
|
if (options.lastFrame) {
|
|
35849
|
-
const lastFramePath =
|
|
35850
|
-
const lastFrameBuffer = await
|
|
35562
|
+
const lastFramePath = resolve25(process.cwd(), options.lastFrame);
|
|
35563
|
+
const lastFrameBuffer = await readFile13(lastFramePath);
|
|
35851
35564
|
const ext = options.lastFrame.toLowerCase().split(".").pop();
|
|
35852
35565
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
|
|
35853
35566
|
lastFrame = `data:${mimeType};base64,${lastFrameBuffer.toString("base64")}`;
|
|
@@ -35856,8 +35569,8 @@ Examples:
|
|
|
35856
35569
|
if (options.refImages && options.refImages.length > 0) {
|
|
35857
35570
|
refImages = [];
|
|
35858
35571
|
for (const refPath of options.refImages.slice(0, 3)) {
|
|
35859
|
-
const absRefPath =
|
|
35860
|
-
const refBuffer = await
|
|
35572
|
+
const absRefPath = resolve25(process.cwd(), refPath);
|
|
35573
|
+
const refBuffer = await readFile13(absRefPath);
|
|
35861
35574
|
const ext = refPath.toLowerCase().split(".").pop();
|
|
35862
35575
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
|
|
35863
35576
|
refImages.push({ base64: refBuffer.toString("base64"), mimeType });
|
|
@@ -35938,20 +35651,22 @@ Examples:
|
|
|
35938
35651
|
await fal.initialize({ apiKey });
|
|
35939
35652
|
let falImage = referenceImage;
|
|
35940
35653
|
if (falImage && falImage.startsWith("data:")) {
|
|
35941
|
-
|
|
35942
|
-
|
|
35943
|
-
|
|
35944
|
-
|
|
35945
|
-
|
|
35946
|
-
|
|
35947
|
-
|
|
35948
|
-
|
|
35949
|
-
|
|
35950
|
-
|
|
35951
|
-
|
|
35952
|
-
|
|
35654
|
+
try {
|
|
35655
|
+
const uploadHost = await resolveUploadHost();
|
|
35656
|
+
spinner2.text = `Uploading image via ${uploadHost.provider} for Seedance...`;
|
|
35657
|
+
const upload = await uploadHost.uploadImage(referenceImageBuffer, {
|
|
35658
|
+
filename: options.image,
|
|
35659
|
+
mimeType: referenceImageMimeType
|
|
35660
|
+
});
|
|
35661
|
+
falImage = upload.url;
|
|
35662
|
+
} catch (err) {
|
|
35663
|
+
spinner2.fail("Image upload failed");
|
|
35664
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
35665
|
+
if (message.includes("IMGBB_API_KEY")) {
|
|
35666
|
+
exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
|
|
35667
|
+
}
|
|
35668
|
+
exitWithError(apiError(message, true));
|
|
35953
35669
|
}
|
|
35954
|
-
falImage = uploadResult.url;
|
|
35955
35670
|
}
|
|
35956
35671
|
spinner2.text = "Generating video with fal.ai Seedance 2.0 (this may take 1-3 minutes)...";
|
|
35957
35672
|
const seedanceModel = String(options.seedanceModel ?? "quality").toLowerCase();
|
|
@@ -35975,8 +35690,8 @@ Examples:
|
|
|
35975
35690
|
let outputPath;
|
|
35976
35691
|
if (options.output && finalResult.videoUrl) {
|
|
35977
35692
|
const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
|
|
35978
|
-
outputPath =
|
|
35979
|
-
await
|
|
35693
|
+
outputPath = resolve25(process.cwd(), options.output);
|
|
35694
|
+
await writeFile23(outputPath, buffer);
|
|
35980
35695
|
}
|
|
35981
35696
|
outputSuccess({
|
|
35982
35697
|
command: "generate video",
|
|
@@ -36003,14 +35718,12 @@ Examples:
|
|
|
36003
35718
|
const downloadSpinner = ora("Downloading video...").start();
|
|
36004
35719
|
try {
|
|
36005
35720
|
const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
|
|
36006
|
-
const outputPath =
|
|
36007
|
-
await
|
|
35721
|
+
const outputPath = resolve25(process.cwd(), options.output);
|
|
35722
|
+
await writeFile23(outputPath, buffer);
|
|
36008
35723
|
downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
|
|
36009
35724
|
} catch (err) {
|
|
36010
35725
|
downloadSpinner.fail(
|
|
36011
|
-
source_default.red(
|
|
36012
|
-
`Failed to download video: ${err instanceof Error ? err.message : err}`
|
|
36013
|
-
)
|
|
35726
|
+
source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
|
|
36014
35727
|
);
|
|
36015
35728
|
}
|
|
36016
35729
|
}
|
|
@@ -36028,11 +35741,10 @@ var init_video = __esm({
|
|
|
36028
35741
|
init_dist2();
|
|
36029
35742
|
init_api_key();
|
|
36030
35743
|
init_tty();
|
|
36031
|
-
init_config();
|
|
36032
35744
|
init_output();
|
|
36033
35745
|
init_validate();
|
|
36034
35746
|
init_provider_resolver();
|
|
36035
|
-
|
|
35747
|
+
init_upload_host();
|
|
36036
35748
|
init_ai_helpers();
|
|
36037
35749
|
}
|
|
36038
35750
|
});
|
|
@@ -36085,7 +35797,7 @@ var init_generate = __esm({
|
|
|
36085
35797
|
Examples:
|
|
36086
35798
|
$ vibe generate image "a sunset over the ocean" -o sunset.png
|
|
36087
35799
|
$ vibe generate image "logo design" -o logo.png -p openai
|
|
36088
|
-
$ vibe generate video "dancing cat" -o cat.mp4 # Seedance when
|
|
35800
|
+
$ vibe generate video "dancing cat" -o cat.mp4 # Seedance when FAL_API_KEY is set
|
|
36089
35801
|
$ vibe generate video "city timelapse" -o city.mp4 -p seedance # Seedance via fal.ai
|
|
36090
35802
|
$ vibe generate video "city timelapse" -o city.mp4 -p kling # Kling
|
|
36091
35803
|
$ vibe generate video "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
|
|
@@ -36096,7 +35808,7 @@ Examples:
|
|
|
36096
35808
|
API Keys (per provider):
|
|
36097
35809
|
GOOGLE_API_KEY Image (default), Veo video
|
|
36098
35810
|
OPENAI_API_KEY Image (-p openai)
|
|
36099
|
-
|
|
35811
|
+
FAL_API_KEY Seedance video (-p seedance, default video)
|
|
36100
35812
|
XAI_API_KEY Grok image/video
|
|
36101
35813
|
KLING_API_KEY Kling video (-p kling)
|
|
36102
35814
|
RUNWAY_API_SECRET Runway video (-p runway)
|
|
@@ -36137,8 +35849,8 @@ Run 'vibe schema generate.<command>' for structured parameter info.
|
|
|
36137
35849
|
});
|
|
36138
35850
|
|
|
36139
35851
|
// src/commands/_shared/edit/silence-cut.ts
|
|
36140
|
-
import { existsSync as
|
|
36141
|
-
import { readFile as
|
|
35852
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
35853
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
36142
35854
|
async function detectSilencePeriods(videoPath, noiseThreshold, minDuration) {
|
|
36143
35855
|
const totalDuration = await getVideoDuration(videoPath);
|
|
36144
35856
|
const { stdout, stderr } = await execSafe(
|
|
@@ -36188,7 +35900,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
|
|
|
36188
35900
|
}
|
|
36189
35901
|
const gemini = new GeminiProvider();
|
|
36190
35902
|
await gemini.initialize({ apiKey: geminiApiKey });
|
|
36191
|
-
const videoBuffer = await
|
|
35903
|
+
const videoBuffer = await readFile14(videoPath);
|
|
36192
35904
|
const modelMap = {
|
|
36193
35905
|
flash: "gemini-3-flash-preview",
|
|
36194
35906
|
"flash-2.5": "gemini-2.5-flash",
|
|
@@ -36269,7 +35981,7 @@ async function executeSilenceCut(options) {
|
|
|
36269
35981
|
analyzeOnly = false,
|
|
36270
35982
|
useGemini = false
|
|
36271
35983
|
} = options;
|
|
36272
|
-
if (!
|
|
35984
|
+
if (!existsSync14(videoPath)) {
|
|
36273
35985
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
36274
35986
|
}
|
|
36275
35987
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36377,11 +36089,11 @@ var init_silence_cut = __esm({
|
|
|
36377
36089
|
});
|
|
36378
36090
|
|
|
36379
36091
|
// src/commands/_shared/edit/jump-cut.ts
|
|
36380
|
-
import { existsSync as
|
|
36381
|
-
import { readFile as
|
|
36092
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
36093
|
+
import { readFile as readFile15, writeFile as writeFile24, mkdir as mkdir12 } from "node:fs/promises";
|
|
36382
36094
|
import { join as join8 } from "node:path";
|
|
36383
36095
|
async function transcribeWithWords(audioPath, apiKey, language) {
|
|
36384
|
-
const audioBuffer = await
|
|
36096
|
+
const audioBuffer = await readFile15(audioPath);
|
|
36385
36097
|
const audioBlob = new Blob([audioBuffer]);
|
|
36386
36098
|
const formData = new FormData();
|
|
36387
36099
|
formData.append("file", audioBlob, "audio.wav");
|
|
@@ -36440,7 +36152,7 @@ async function executeJumpCut(options) {
|
|
|
36440
36152
|
analyzeOnly = false,
|
|
36441
36153
|
apiKey
|
|
36442
36154
|
} = options;
|
|
36443
|
-
if (!
|
|
36155
|
+
if (!existsSync15(videoPath)) {
|
|
36444
36156
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
36445
36157
|
}
|
|
36446
36158
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36537,7 +36249,7 @@ async function executeJumpCut(options) {
|
|
|
36537
36249
|
}
|
|
36538
36250
|
const concatList = segmentPaths.map((p) => `file '${p}'`).join("\n");
|
|
36539
36251
|
const listPath = join8(tmpDir, "concat.txt");
|
|
36540
|
-
await
|
|
36252
|
+
await writeFile24(listPath, concatList);
|
|
36541
36253
|
await execSafe(
|
|
36542
36254
|
"ffmpeg",
|
|
36543
36255
|
[
|
|
@@ -36726,8 +36438,8 @@ var init_helpers = __esm({
|
|
|
36726
36438
|
});
|
|
36727
36439
|
|
|
36728
36440
|
// src/commands/_shared/edit/caption.ts
|
|
36729
|
-
import { existsSync as
|
|
36730
|
-
import { readFile as
|
|
36441
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
36442
|
+
import { readFile as readFile16, writeFile as writeFile25, mkdir as mkdir13 } from "node:fs/promises";
|
|
36731
36443
|
import { dirname as dirname8, basename as basename6, extname as extname4, join as join9 } from "node:path";
|
|
36732
36444
|
function getCaptionForceStyle(style, fontSize, fontColor, position) {
|
|
36733
36445
|
const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
|
|
@@ -36756,7 +36468,7 @@ async function executeCaption(options) {
|
|
|
36756
36468
|
position = "bottom",
|
|
36757
36469
|
apiKey
|
|
36758
36470
|
} = options;
|
|
36759
|
-
if (!
|
|
36471
|
+
if (!existsSync16(videoPath)) {
|
|
36760
36472
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
36761
36473
|
}
|
|
36762
36474
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36797,7 +36509,7 @@ async function executeCaption(options) {
|
|
|
36797
36509
|
);
|
|
36798
36510
|
const whisper = new WhisperProvider();
|
|
36799
36511
|
await whisper.initialize({ apiKey: openaiKey });
|
|
36800
|
-
const audioBuffer = await
|
|
36512
|
+
const audioBuffer = await readFile16(audioPath);
|
|
36801
36513
|
const audioBlob = new Blob([audioBuffer]);
|
|
36802
36514
|
const transcriptResult = await whisper.transcribe(audioBlob, language);
|
|
36803
36515
|
if (transcriptResult.status === "failed" || !transcriptResult.segments || transcriptResult.segments.length === 0) {
|
|
@@ -36807,7 +36519,7 @@ async function executeCaption(options) {
|
|
|
36807
36519
|
};
|
|
36808
36520
|
}
|
|
36809
36521
|
const srtContent = formatSRT(transcriptResult.segments);
|
|
36810
|
-
await
|
|
36522
|
+
await writeFile25(srtPath, srtContent);
|
|
36811
36523
|
const { width, height } = await getVideoResolution(videoPath);
|
|
36812
36524
|
const fontSize = customFontSize || Math.round(height / 18);
|
|
36813
36525
|
let hasSubtitles = false;
|
|
@@ -36849,7 +36561,7 @@ async function executeCaption(options) {
|
|
|
36849
36561
|
outputDir2,
|
|
36850
36562
|
basename6(outputPath, extname4(outputPath)) + ".srt"
|
|
36851
36563
|
);
|
|
36852
|
-
await
|
|
36564
|
+
await writeFile25(outputSrtPath2, srtContent);
|
|
36853
36565
|
return { success: false, error: `${remotionErr}
|
|
36854
36566
|
SRT saved to: ${outputSrtPath2}` };
|
|
36855
36567
|
}
|
|
@@ -36888,7 +36600,7 @@ SRT saved to: ${outputSrtPath2}` };
|
|
|
36888
36600
|
outputDir2,
|
|
36889
36601
|
basename6(outputPath, extname4(outputPath)) + ".srt"
|
|
36890
36602
|
);
|
|
36891
|
-
await
|
|
36603
|
+
await writeFile25(outputSrtPath2, srtContent);
|
|
36892
36604
|
return {
|
|
36893
36605
|
success: false,
|
|
36894
36606
|
error: `${renderResult.error}
|
|
@@ -36901,7 +36613,7 @@ SRT saved to: ${outputSrtPath2}`
|
|
|
36901
36613
|
outputDir,
|
|
36902
36614
|
basename6(outputPath, extname4(outputPath)) + ".srt"
|
|
36903
36615
|
);
|
|
36904
|
-
await
|
|
36616
|
+
await writeFile25(outputSrtPath, srtContent);
|
|
36905
36617
|
return {
|
|
36906
36618
|
success: true,
|
|
36907
36619
|
outputPath,
|
|
@@ -36934,10 +36646,10 @@ var init_caption = __esm({
|
|
|
36934
36646
|
});
|
|
36935
36647
|
|
|
36936
36648
|
// src/commands/_shared/edit/noise-reduce.ts
|
|
36937
|
-
import { existsSync as
|
|
36649
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
36938
36650
|
async function executeNoiseReduce(options) {
|
|
36939
36651
|
const { inputPath, outputPath, strength = "medium", noiseFloor } = options;
|
|
36940
|
-
if (!
|
|
36652
|
+
if (!existsSync17(inputPath)) {
|
|
36941
36653
|
return { success: false, error: `File not found: ${inputPath}` };
|
|
36942
36654
|
}
|
|
36943
36655
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36994,7 +36706,7 @@ var init_noise_reduce = __esm({
|
|
|
36994
36706
|
});
|
|
36995
36707
|
|
|
36996
36708
|
// src/commands/_shared/edit/fade.ts
|
|
36997
|
-
import { existsSync as
|
|
36709
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
36998
36710
|
async function executeFade(options) {
|
|
36999
36711
|
const {
|
|
37000
36712
|
videoPath,
|
|
@@ -37004,7 +36716,7 @@ async function executeFade(options) {
|
|
|
37004
36716
|
audioOnly = false,
|
|
37005
36717
|
videoOnly = false
|
|
37006
36718
|
} = options;
|
|
37007
|
-
if (!
|
|
36719
|
+
if (!existsSync18(videoPath)) {
|
|
37008
36720
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
37009
36721
|
}
|
|
37010
36722
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37071,8 +36783,8 @@ var init_fade = __esm({
|
|
|
37071
36783
|
});
|
|
37072
36784
|
|
|
37073
36785
|
// src/commands/_shared/edit/translate-srt.ts
|
|
37074
|
-
import { existsSync as
|
|
37075
|
-
import { readFile as
|
|
36786
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
36787
|
+
import { readFile as readFile17, writeFile as writeFile26 } from "node:fs/promises";
|
|
37076
36788
|
async function executeTranslateSrt(options) {
|
|
37077
36789
|
const {
|
|
37078
36790
|
srtPath,
|
|
@@ -37082,11 +36794,11 @@ async function executeTranslateSrt(options) {
|
|
|
37082
36794
|
sourceLanguage,
|
|
37083
36795
|
apiKey
|
|
37084
36796
|
} = options;
|
|
37085
|
-
if (!
|
|
36797
|
+
if (!existsSync19(srtPath)) {
|
|
37086
36798
|
return { success: false, error: `SRT file not found: ${srtPath}` };
|
|
37087
36799
|
}
|
|
37088
36800
|
try {
|
|
37089
|
-
const srtContent = await
|
|
36801
|
+
const srtContent = await readFile17(srtPath, "utf-8");
|
|
37090
36802
|
const segments = parseSRT(srtContent);
|
|
37091
36803
|
if (segments.length === 0) {
|
|
37092
36804
|
return { success: false, error: "No subtitle segments found in SRT file" };
|
|
@@ -37176,7 +36888,7 @@ ${textsToTranslate}`;
|
|
|
37176
36888
|
}
|
|
37177
36889
|
}
|
|
37178
36890
|
const translatedSrt = formatSRT(translatedSegments);
|
|
37179
|
-
await
|
|
36891
|
+
await writeFile26(outputPath, translatedSrt);
|
|
37180
36892
|
return {
|
|
37181
36893
|
success: true,
|
|
37182
36894
|
outputPath,
|
|
@@ -37199,8 +36911,8 @@ var init_translate_srt = __esm({
|
|
|
37199
36911
|
});
|
|
37200
36912
|
|
|
37201
36913
|
// src/commands/_shared/edit/text-overlay.ts
|
|
37202
|
-
import { existsSync as
|
|
37203
|
-
import { resolve as
|
|
36914
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
36915
|
+
import { resolve as resolve26 } from "node:path";
|
|
37204
36916
|
function detectSystemFont() {
|
|
37205
36917
|
const platform3 = process.platform;
|
|
37206
36918
|
if (platform3 === "darwin") {
|
|
@@ -37210,7 +36922,7 @@ function detectSystemFont() {
|
|
|
37210
36922
|
"/Library/Fonts/Arial.ttf"
|
|
37211
36923
|
];
|
|
37212
36924
|
for (const f of candidates) {
|
|
37213
|
-
if (
|
|
36925
|
+
if (existsSync20(f)) return f;
|
|
37214
36926
|
}
|
|
37215
36927
|
} else if (platform3 === "linux") {
|
|
37216
36928
|
const candidates = [
|
|
@@ -37219,7 +36931,7 @@ function detectSystemFont() {
|
|
|
37219
36931
|
"/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
|
|
37220
36932
|
];
|
|
37221
36933
|
for (const f of candidates) {
|
|
37222
|
-
if (
|
|
36934
|
+
if (existsSync20(f)) return f;
|
|
37223
36935
|
}
|
|
37224
36936
|
} else if (platform3 === "win32") {
|
|
37225
36937
|
const candidates = [
|
|
@@ -37227,7 +36939,7 @@ function detectSystemFont() {
|
|
|
37227
36939
|
"C:\\Windows\\Fonts\\segoeui.ttf"
|
|
37228
36940
|
];
|
|
37229
36941
|
for (const f of candidates) {
|
|
37230
|
-
if (
|
|
36942
|
+
if (existsSync20(f)) return f;
|
|
37231
36943
|
}
|
|
37232
36944
|
}
|
|
37233
36945
|
return null;
|
|
@@ -37246,9 +36958,9 @@ async function applyTextOverlays(options) {
|
|
|
37246
36958
|
if (!texts || texts.length === 0) {
|
|
37247
36959
|
return { success: false, error: "No texts provided" };
|
|
37248
36960
|
}
|
|
37249
|
-
const absVideoPath =
|
|
37250
|
-
const absOutputPath =
|
|
37251
|
-
if (!
|
|
36961
|
+
const absVideoPath = resolve26(process.cwd(), videoPath);
|
|
36962
|
+
const absOutputPath = resolve26(process.cwd(), outputPath);
|
|
36963
|
+
if (!existsSync20(absVideoPath)) {
|
|
37252
36964
|
return { success: false, error: `Video not found: ${absVideoPath}` };
|
|
37253
36965
|
}
|
|
37254
36966
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37428,8 +37140,8 @@ var init_ai_edit = __esm({
|
|
|
37428
37140
|
});
|
|
37429
37141
|
|
|
37430
37142
|
// src/commands/ai-edit-cli.ts
|
|
37431
|
-
import { resolve as
|
|
37432
|
-
import { existsSync as
|
|
37143
|
+
import { resolve as resolve27, extname as extname5, basename as basename7 } from "node:path";
|
|
37144
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
37433
37145
|
function registerEditCommands(aiCommand) {
|
|
37434
37146
|
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
37147
|
Examples:
|
|
@@ -37445,8 +37157,8 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
|
|
|
37445
37157
|
if (options.output) {
|
|
37446
37158
|
validateOutputPath(options.output);
|
|
37447
37159
|
}
|
|
37448
|
-
const absVideoPath =
|
|
37449
|
-
if (!
|
|
37160
|
+
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
37161
|
+
if (!existsSync21(absVideoPath)) {
|
|
37450
37162
|
exitWithError(notFoundError(absVideoPath));
|
|
37451
37163
|
}
|
|
37452
37164
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37485,7 +37197,7 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
|
|
|
37485
37197
|
const spinner2 = ora(spinnerText).start();
|
|
37486
37198
|
const result = await executeSilenceCut({
|
|
37487
37199
|
videoPath: absVideoPath,
|
|
37488
|
-
outputPath:
|
|
37200
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37489
37201
|
noiseThreshold: parseFloat(options.noise),
|
|
37490
37202
|
minDuration: parseFloat(options.minDuration),
|
|
37491
37203
|
padding: parseFloat(options.padding),
|
|
@@ -37553,8 +37265,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37553
37265
|
if (options.output) {
|
|
37554
37266
|
validateOutputPath(options.output);
|
|
37555
37267
|
}
|
|
37556
|
-
const absVideoPath =
|
|
37557
|
-
if (!
|
|
37268
|
+
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
37269
|
+
if (!existsSync21(absVideoPath)) {
|
|
37558
37270
|
exitWithError(notFoundError(absVideoPath));
|
|
37559
37271
|
}
|
|
37560
37272
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37588,7 +37300,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37588
37300
|
const spinner2 = ora("Starting caption process...").start();
|
|
37589
37301
|
const result = await executeCaption({
|
|
37590
37302
|
videoPath: absVideoPath,
|
|
37591
|
-
outputPath:
|
|
37303
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37592
37304
|
style: options.style,
|
|
37593
37305
|
fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
|
|
37594
37306
|
fontColor: options.color,
|
|
@@ -37634,8 +37346,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37634
37346
|
if (options.output) {
|
|
37635
37347
|
validateOutputPath(options.output);
|
|
37636
37348
|
}
|
|
37637
|
-
const absInputPath =
|
|
37638
|
-
if (!
|
|
37349
|
+
const absInputPath = resolve27(process.cwd(), inputPath);
|
|
37350
|
+
if (!existsSync21(absInputPath)) {
|
|
37639
37351
|
exitWithError(notFoundError(absInputPath));
|
|
37640
37352
|
}
|
|
37641
37353
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37662,7 +37374,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37662
37374
|
const spinner2 = ora("Applying noise reduction...").start();
|
|
37663
37375
|
const result = await executeNoiseReduce({
|
|
37664
37376
|
inputPath: absInputPath,
|
|
37665
|
-
outputPath:
|
|
37377
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37666
37378
|
strength: options.strength,
|
|
37667
37379
|
noiseFloor: options.noiseFloor ? parseFloat(options.noiseFloor) : void 0
|
|
37668
37380
|
});
|
|
@@ -37700,8 +37412,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37700
37412
|
if (options.output) {
|
|
37701
37413
|
validateOutputPath(options.output);
|
|
37702
37414
|
}
|
|
37703
|
-
const absVideoPath =
|
|
37704
|
-
if (!
|
|
37415
|
+
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
37416
|
+
if (!existsSync21(absVideoPath)) {
|
|
37705
37417
|
exitWithError(notFoundError(absVideoPath));
|
|
37706
37418
|
}
|
|
37707
37419
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37730,7 +37442,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37730
37442
|
const spinner2 = ora("Applying fade effects...").start();
|
|
37731
37443
|
const result = await executeFade({
|
|
37732
37444
|
videoPath: absVideoPath,
|
|
37733
|
-
outputPath:
|
|
37445
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37734
37446
|
fadeIn: parseFloat(options.fadeIn),
|
|
37735
37447
|
fadeOut: parseFloat(options.fadeOut),
|
|
37736
37448
|
audioOnly: options.audioOnly || false,
|
|
@@ -37775,8 +37487,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37775
37487
|
if (!options.target) {
|
|
37776
37488
|
exitWithError(usageError("Target language required. Use -t or --target"));
|
|
37777
37489
|
}
|
|
37778
|
-
const absSrtPath =
|
|
37779
|
-
if (!
|
|
37490
|
+
const absSrtPath = resolve27(process.cwd(), srtPath);
|
|
37491
|
+
if (!existsSync21(absSrtPath)) {
|
|
37780
37492
|
exitWithError(notFoundError(absSrtPath));
|
|
37781
37493
|
}
|
|
37782
37494
|
if (options.dryRun) {
|
|
@@ -37808,7 +37520,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37808
37520
|
const spinner2 = ora(`Translating to ${options.target}...`).start();
|
|
37809
37521
|
const result = await executeTranslateSrt({
|
|
37810
37522
|
srtPath: absSrtPath,
|
|
37811
|
-
outputPath:
|
|
37523
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37812
37524
|
targetLanguage: options.target,
|
|
37813
37525
|
provider,
|
|
37814
37526
|
sourceLanguage: options.source,
|
|
@@ -37851,8 +37563,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37851
37563
|
validateOutputPath(options.output);
|
|
37852
37564
|
}
|
|
37853
37565
|
if (options.fillers) rejectControlChars(options.fillers);
|
|
37854
|
-
const absVideoPath =
|
|
37855
|
-
if (!
|
|
37566
|
+
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
37567
|
+
if (!existsSync21(absVideoPath)) {
|
|
37856
37568
|
exitWithError(notFoundError(absVideoPath));
|
|
37857
37569
|
}
|
|
37858
37570
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37887,7 +37599,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37887
37599
|
const spinner2 = ora("Transcribing with word-level timestamps...").start();
|
|
37888
37600
|
const result = await executeJumpCut({
|
|
37889
37601
|
videoPath: absVideoPath,
|
|
37890
|
-
outputPath:
|
|
37602
|
+
outputPath: resolve27(process.cwd(), outputPath),
|
|
37891
37603
|
fillers,
|
|
37892
37604
|
padding: parseFloat(options.padding),
|
|
37893
37605
|
language: options.language,
|
|
@@ -37952,9 +37664,9 @@ var init_ai_edit_cli = __esm({
|
|
|
37952
37664
|
});
|
|
37953
37665
|
|
|
37954
37666
|
// src/commands/_shared/execute-fill-gaps.ts
|
|
37955
|
-
import { existsSync as
|
|
37956
|
-
import { readFile as
|
|
37957
|
-
import { resolve as
|
|
37667
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
37668
|
+
import { readFile as readFile18, writeFile as writeFile27, mkdir as mkdir14, rename as renameFs } from "node:fs/promises";
|
|
37669
|
+
import { resolve as resolve28, dirname as dirname9 } from "node:path";
|
|
37958
37670
|
function detectVideoGaps(videoClips, totalDuration) {
|
|
37959
37671
|
const gaps = [];
|
|
37960
37672
|
const sortedClips = [...videoClips].sort((a, b) => a.startTime - b.startTime);
|
|
@@ -38010,7 +37722,7 @@ function analyzeGapFillability(gaps, videoClips, sources) {
|
|
|
38010
37722
|
}
|
|
38011
37723
|
async function uploadFrameToImgbb(framePath, imgbbApiKey) {
|
|
38012
37724
|
try {
|
|
38013
|
-
const frameBuffer = await
|
|
37725
|
+
const frameBuffer = await readFile18(framePath);
|
|
38014
37726
|
const frameBase64 = frameBuffer.toString("base64");
|
|
38015
37727
|
const formData = new FormData();
|
|
38016
37728
|
formData.append("key", imgbbApiKey);
|
|
@@ -38035,14 +37747,14 @@ async function executeFillGaps(options) {
|
|
|
38035
37747
|
try {
|
|
38036
37748
|
onProgress("Loading timeline...");
|
|
38037
37749
|
const filePath = await resolveTimelineFile(options.projectPath);
|
|
38038
|
-
if (!
|
|
37750
|
+
if (!existsSync22(filePath)) {
|
|
38039
37751
|
return {
|
|
38040
37752
|
success: false,
|
|
38041
37753
|
error: `Timeline file not found: ${filePath}`,
|
|
38042
37754
|
humanLines
|
|
38043
37755
|
};
|
|
38044
37756
|
}
|
|
38045
|
-
const content = await
|
|
37757
|
+
const content = await readFile18(filePath, "utf-8");
|
|
38046
37758
|
const data = JSON.parse(content);
|
|
38047
37759
|
const project = Project.fromJSON(data);
|
|
38048
37760
|
const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
|
|
@@ -38169,8 +37881,8 @@ async function executeFillGaps(options) {
|
|
|
38169
37881
|
};
|
|
38170
37882
|
}
|
|
38171
37883
|
const projectDir = dirname9(filePath);
|
|
38172
|
-
const footageDir = options.dir ?
|
|
38173
|
-
if (!
|
|
37884
|
+
const footageDir = options.dir ? resolve28(process.cwd(), options.dir) : resolve28(projectDir, "footage");
|
|
37885
|
+
if (!existsSync22(footageDir)) {
|
|
38174
37886
|
await mkdir14(footageDir, { recursive: true });
|
|
38175
37887
|
}
|
|
38176
37888
|
humanLines.push("Generating AI Videos");
|
|
@@ -38201,7 +37913,7 @@ async function executeFillGaps(options) {
|
|
|
38201
37913
|
}
|
|
38202
37914
|
onProgress("Extracting frame from preceding clip...");
|
|
38203
37915
|
const frameOffset = clipBefore.sourceStartOffset + clipBefore.duration - 0.1;
|
|
38204
|
-
const framePath =
|
|
37916
|
+
const framePath = resolve28(footageDir, `frame-${gap.start.toFixed(2)}.png`);
|
|
38205
37917
|
try {
|
|
38206
37918
|
await execSafe("ffmpeg", [
|
|
38207
37919
|
"-i",
|
|
@@ -38262,10 +37974,10 @@ async function executeFillGaps(options) {
|
|
|
38262
37974
|
continue;
|
|
38263
37975
|
}
|
|
38264
37976
|
const videoFileName = `gap-fill-${gap.start.toFixed(2)}-${gap.end.toFixed(2)}.mp4`;
|
|
38265
|
-
const videoPath =
|
|
37977
|
+
const videoPath = resolve28(footageDir, videoFileName);
|
|
38266
37978
|
onProgress("Downloading generated video...");
|
|
38267
37979
|
const videoBuffer = await downloadVideo(finalResult.videoUrl);
|
|
38268
|
-
await
|
|
37980
|
+
await writeFile27(videoPath, videoBuffer);
|
|
38269
37981
|
generatedDuration = finalResult.duration || parseInt(klingDuration);
|
|
38270
37982
|
generatedVideos.push(videoPath);
|
|
38271
37983
|
humanLines.push(` Generated: ${videoFileName} (${generatedDuration}s)`);
|
|
@@ -38274,7 +37986,7 @@ async function executeFillGaps(options) {
|
|
|
38274
37986
|
const remainingNeeded = targetDuration - generatedDuration;
|
|
38275
37987
|
const segmentDuration = remainingNeeded > 5 ? "10" : "5";
|
|
38276
37988
|
onProgress(`Generating additional ${segmentDuration}s segment...`);
|
|
38277
|
-
const lastFramePath =
|
|
37989
|
+
const lastFramePath = resolve28(
|
|
38278
37990
|
footageDir,
|
|
38279
37991
|
`frame-extend-${gap.start.toFixed(2)}-${segmentIndex}.png`
|
|
38280
37992
|
);
|
|
@@ -38330,17 +38042,17 @@ async function executeFillGaps(options) {
|
|
|
38330
38042
|
);
|
|
38331
38043
|
break;
|
|
38332
38044
|
}
|
|
38333
|
-
const segVideoPath =
|
|
38045
|
+
const segVideoPath = resolve28(
|
|
38334
38046
|
footageDir,
|
|
38335
38047
|
`gap-fill-${gap.start.toFixed(2)}-${gap.end.toFixed(2)}-seg${segmentIndex}.mp4`
|
|
38336
38048
|
);
|
|
38337
38049
|
const segVideoBuffer = await downloadVideo(segFinalResult.videoUrl);
|
|
38338
|
-
await
|
|
38339
|
-
const concatListPath =
|
|
38050
|
+
await writeFile27(segVideoPath, segVideoBuffer);
|
|
38051
|
+
const concatListPath = resolve28(footageDir, `concat-${gap.start.toFixed(2)}.txt`);
|
|
38340
38052
|
const concatList = generatedVideos.map((v) => `file '${v}'`).join("\n") + `
|
|
38341
38053
|
file '${segVideoPath}'`;
|
|
38342
|
-
await
|
|
38343
|
-
const concatOutputPath =
|
|
38054
|
+
await writeFile27(concatListPath, concatList);
|
|
38055
|
+
const concatOutputPath = resolve28(
|
|
38344
38056
|
footageDir,
|
|
38345
38057
|
`gap-fill-${gap.start.toFixed(2)}-${gap.end.toFixed(2)}-merged.mp4`
|
|
38346
38058
|
);
|
|
@@ -38396,8 +38108,8 @@ file '${segVideoPath}'`;
|
|
|
38396
38108
|
humanLines.push("");
|
|
38397
38109
|
let outputPath = filePath;
|
|
38398
38110
|
if (generatedCount > 0) {
|
|
38399
|
-
outputPath = options.output ?
|
|
38400
|
-
await
|
|
38111
|
+
outputPath = options.output ? resolve28(process.cwd(), options.output) : filePath;
|
|
38112
|
+
await writeFile27(outputPath, JSON.stringify(project.toJSON(), null, 2));
|
|
38401
38113
|
humanLines.push(`\u2714 Filled ${generatedCount} gap(s) with AI-generated video`);
|
|
38402
38114
|
humanLines.push(`Project saved: ${outputPath}`);
|
|
38403
38115
|
} else {
|
|
@@ -38493,14 +38205,14 @@ __export(edit_cmd_exports, {
|
|
|
38493
38205
|
executeSpeedRamp: () => executeSpeedRamp,
|
|
38494
38206
|
executeUpscale: () => executeUpscale
|
|
38495
38207
|
});
|
|
38496
|
-
import { resolve as
|
|
38497
|
-
import { readFile as
|
|
38208
|
+
import { resolve as resolve29, dirname as dirname10 } from "node:path";
|
|
38209
|
+
import { readFile as readFile19, writeFile as writeFile28, mkdir as mkdir15 } from "node:fs/promises";
|
|
38498
38210
|
async function executeGrade(options) {
|
|
38499
38211
|
const { videoPath, style, preset, output, analyzeOnly, apiKey } = options;
|
|
38500
38212
|
try {
|
|
38501
38213
|
if (!style && !preset) return { success: false, error: "Either style or preset is required" };
|
|
38502
38214
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
38503
|
-
const absPath =
|
|
38215
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38504
38216
|
let gradeResult;
|
|
38505
38217
|
if (preset) {
|
|
38506
38218
|
const claude = new ClaudeProvider();
|
|
@@ -38515,7 +38227,7 @@ async function executeGrade(options) {
|
|
|
38515
38227
|
if (analyzeOnly) {
|
|
38516
38228
|
return { success: true, style: preset || style, description: gradeResult.description, ffmpegFilter: gradeResult.ffmpegFilter };
|
|
38517
38229
|
}
|
|
38518
|
-
const outputPath = output ?
|
|
38230
|
+
const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, "-graded$1");
|
|
38519
38231
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", gradeResult.ffmpegFilter, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
|
|
38520
38232
|
return { success: true, outputPath, style: preset || style, description: gradeResult.description, ffmpegFilter: gradeResult.ffmpegFilter };
|
|
38521
38233
|
} catch (error) {
|
|
@@ -38530,12 +38242,12 @@ async function executeSpeedRamp(options) {
|
|
|
38530
38242
|
const claudeKey = apiKey || process.env.ANTHROPIC_API_KEY;
|
|
38531
38243
|
if (!openaiKey) return { success: false, error: "OPENAI_API_KEY required for Whisper transcription" };
|
|
38532
38244
|
if (!claudeKey) return { success: false, error: "ANTHROPIC_API_KEY required for Claude analysis" };
|
|
38533
|
-
const absPath =
|
|
38245
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38534
38246
|
const tempAudio = absPath.replace(/(\.[^.]+)$/, "-temp-audio.mp3");
|
|
38535
38247
|
await execSafe("ffmpeg", ["-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "2", tempAudio, "-y"]);
|
|
38536
38248
|
const whisper = new WhisperProvider();
|
|
38537
38249
|
await whisper.initialize({ apiKey: openaiKey });
|
|
38538
|
-
const audioBuffer = await
|
|
38250
|
+
const audioBuffer = await readFile19(tempAudio);
|
|
38539
38251
|
const transcript = await whisper.transcribe(new Blob([audioBuffer]), language);
|
|
38540
38252
|
try {
|
|
38541
38253
|
const { unlink: unlink5 } = await import("node:fs/promises");
|
|
@@ -38555,7 +38267,7 @@ async function executeSpeedRamp(options) {
|
|
|
38555
38267
|
if (speedResult.keyframes.length < 2) {
|
|
38556
38268
|
return { success: false, error: "Not enough keyframes for speed ramping" };
|
|
38557
38269
|
}
|
|
38558
|
-
const outputPath = output ?
|
|
38270
|
+
const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
|
|
38559
38271
|
const setpts = `setpts=${(1 / avgSpeed).toFixed(3)}*PTS`;
|
|
38560
38272
|
const atempo = avgSpeed >= 0.5 && avgSpeed <= 2 ? `atempo=${avgSpeed.toFixed(3)}` : "";
|
|
38561
38273
|
if (atempo) {
|
|
@@ -38572,7 +38284,7 @@ async function executeReframe(options) {
|
|
|
38572
38284
|
const { videoPath, aspect = "9:16", output, analyzeOnly } = options;
|
|
38573
38285
|
try {
|
|
38574
38286
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
38575
|
-
const absPath =
|
|
38287
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38576
38288
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
38577
38289
|
"-v",
|
|
38578
38290
|
"error",
|
|
@@ -38607,7 +38319,7 @@ async function executeReframe(options) {
|
|
|
38607
38319
|
keyframeCount: 1
|
|
38608
38320
|
};
|
|
38609
38321
|
}
|
|
38610
|
-
const outputPath = output ?
|
|
38322
|
+
const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, `-${aspect.replace(":", "x")}$1`);
|
|
38611
38323
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", `crop=${cropWidth}:${cropHeight}:${cropX}:${cropY}`, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
|
|
38612
38324
|
return { success: true, outputPath, sourceAspect: `${sourceWidth}:${sourceHeight}`, targetAspect: aspect };
|
|
38613
38325
|
} catch (error) {
|
|
@@ -38619,7 +38331,7 @@ async function executeInterpolate(options) {
|
|
|
38619
38331
|
try {
|
|
38620
38332
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
38621
38333
|
if (![2, 4, 8].includes(factor)) return { success: false, error: "Factor must be 2, 4, or 8" };
|
|
38622
|
-
const absPath =
|
|
38334
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38623
38335
|
const { stdout: fpsOut } = await execSafe("ffprobe", [
|
|
38624
38336
|
"-v",
|
|
38625
38337
|
"error",
|
|
@@ -38635,7 +38347,7 @@ async function executeInterpolate(options) {
|
|
|
38635
38347
|
const originalFps = num / (den || 1);
|
|
38636
38348
|
const targetFps = options.fps || originalFps * factor;
|
|
38637
38349
|
const mi = quality === "fast" ? "mi_mode=mci" : "mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1";
|
|
38638
|
-
const outputPath = output ?
|
|
38350
|
+
const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, `-slow${factor}x$1`);
|
|
38639
38351
|
await execSafe("ffmpeg", ["-i", absPath, "-filter:v", `minterpolate='${mi}:fps=${targetFps}',setpts=${factor}*PTS`, "-an", outputPath, "-y"], { timeout: 6e5 });
|
|
38640
38352
|
return { success: true, outputPath, originalFps, targetFps, factor };
|
|
38641
38353
|
} catch (error) {
|
|
@@ -38646,7 +38358,7 @@ async function executeUpscale(options) {
|
|
|
38646
38358
|
const { videoPath, output, scale = 2, quality = "quality" } = options;
|
|
38647
38359
|
try {
|
|
38648
38360
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
38649
|
-
const absPath =
|
|
38361
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38650
38362
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
38651
38363
|
"-v",
|
|
38652
38364
|
"error",
|
|
@@ -38662,7 +38374,7 @@ async function executeUpscale(options) {
|
|
|
38662
38374
|
const targetW = w * scale;
|
|
38663
38375
|
const targetH = h * scale;
|
|
38664
38376
|
const scaleFilter = quality === "quality" ? `scale=${targetW}:${targetH}:flags=lanczos` : `scale=${targetW}:${targetH}`;
|
|
38665
|
-
const outputPath = output ?
|
|
38377
|
+
const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, `-${scale}x$1`);
|
|
38666
38378
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", scaleFilter, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
|
|
38667
38379
|
return { success: true, outputPath, originalRes: `${w}x${h}`, targetRes: `${targetW}x${targetH}` };
|
|
38668
38380
|
} catch (error) {
|
|
@@ -38735,7 +38447,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38735
38447
|
dryRun: true,
|
|
38736
38448
|
data: {
|
|
38737
38449
|
params: {
|
|
38738
|
-
videoPath:
|
|
38450
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
38739
38451
|
style: options.style || options.preset,
|
|
38740
38452
|
analyzeOnly: options.analyzeOnly || false
|
|
38741
38453
|
}
|
|
@@ -38762,8 +38474,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38762
38474
|
}
|
|
38763
38475
|
spinner2.succeed(source_default.green("Color grade analyzed"));
|
|
38764
38476
|
if (isJsonMode()) {
|
|
38765
|
-
const absPath2 =
|
|
38766
|
-
const gradeOutputPath = options.output ?
|
|
38477
|
+
const absPath2 = resolve29(process.cwd(), videoPath);
|
|
38478
|
+
const gradeOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath2.replace(/(\.[^.]+)$/, "-graded$1");
|
|
38767
38479
|
outputSuccess({
|
|
38768
38480
|
command: "edit grade",
|
|
38769
38481
|
startedAt,
|
|
@@ -38789,8 +38501,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38789
38501
|
console.log(source_default.dim("Use without --analyze-only to apply the grade."));
|
|
38790
38502
|
return;
|
|
38791
38503
|
}
|
|
38792
|
-
const absPath =
|
|
38793
|
-
const outputPath = options.output ?
|
|
38504
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38505
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-graded$1");
|
|
38794
38506
|
spinner2.start("Applying color grade...");
|
|
38795
38507
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", gradeResult.ffmpegFilter, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
|
|
38796
38508
|
spinner2.succeed(source_default.green("Color grade applied"));
|
|
@@ -38820,7 +38532,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38820
38532
|
dryRun: true,
|
|
38821
38533
|
data: {
|
|
38822
38534
|
params: {
|
|
38823
|
-
videoPath:
|
|
38535
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
38824
38536
|
texts: options.text,
|
|
38825
38537
|
style: options.style,
|
|
38826
38538
|
fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
|
|
@@ -38833,8 +38545,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38833
38545
|
});
|
|
38834
38546
|
return;
|
|
38835
38547
|
}
|
|
38836
|
-
const absPath =
|
|
38837
|
-
const outputPath = options.output ?
|
|
38548
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38549
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-overlay$1");
|
|
38838
38550
|
const spinner2 = ora("Applying text overlays...").start();
|
|
38839
38551
|
const result = await applyTextOverlays({
|
|
38840
38552
|
videoPath: absPath,
|
|
@@ -38892,7 +38604,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38892
38604
|
dryRun: true,
|
|
38893
38605
|
data: {
|
|
38894
38606
|
params: {
|
|
38895
|
-
videoPath:
|
|
38607
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
38896
38608
|
style: options.style,
|
|
38897
38609
|
minSpeed: parseFloat(options.minSpeed),
|
|
38898
38610
|
maxSpeed: parseFloat(options.maxSpeed),
|
|
@@ -38904,7 +38616,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38904
38616
|
}
|
|
38905
38617
|
const openaiApiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI");
|
|
38906
38618
|
const claudeApiKey = await requireApiKey("ANTHROPIC_API_KEY", "Anthropic", options.apiKey);
|
|
38907
|
-
const absPath =
|
|
38619
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
38908
38620
|
const spinner2 = ora("Extracting audio...").start();
|
|
38909
38621
|
const { stdout: speedRampProbe } = await execSafe("ffprobe", [
|
|
38910
38622
|
"-v",
|
|
@@ -38926,7 +38638,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38926
38638
|
spinner2.text = "Transcribing audio...";
|
|
38927
38639
|
const whisper = new WhisperProvider();
|
|
38928
38640
|
await whisper.initialize({ apiKey: openaiApiKey });
|
|
38929
|
-
const audioBuffer = await
|
|
38641
|
+
const audioBuffer = await readFile19(tempAudio);
|
|
38930
38642
|
const audioBlob = new Blob([audioBuffer]);
|
|
38931
38643
|
const transcript = await whisper.transcribe(audioBlob, options.language);
|
|
38932
38644
|
if (!transcript.segments || transcript.segments.length === 0) {
|
|
@@ -38949,7 +38661,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38949
38661
|
spinner2.succeed(source_default.green(`Found ${speedResult.keyframes.length} speed keyframes`));
|
|
38950
38662
|
if (isJsonMode()) {
|
|
38951
38663
|
const avgSpeed2 = speedResult.keyframes.reduce((sum, kf) => sum + kf.speed, 0) / speedResult.keyframes.length;
|
|
38952
|
-
const speedRampOutputPath = options.output ?
|
|
38664
|
+
const speedRampOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
|
|
38953
38665
|
outputSuccess({
|
|
38954
38666
|
command: "edit speed-ramp",
|
|
38955
38667
|
startedAt,
|
|
@@ -38978,7 +38690,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38978
38690
|
return;
|
|
38979
38691
|
}
|
|
38980
38692
|
spinner2.start("Applying speed ramps...");
|
|
38981
|
-
const outputPath = options.output ?
|
|
38693
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
|
|
38982
38694
|
const avgSpeed = speedResult.keyframes.reduce((sum, kf) => sum + kf.speed, 0) / speedResult.keyframes.length;
|
|
38983
38695
|
const setpts = `setpts=${(1 / avgSpeed).toFixed(3)}*PTS`;
|
|
38984
38696
|
const atempo = avgSpeed >= 0.5 && avgSpeed <= 2 ? `atempo=${avgSpeed.toFixed(3)}` : "";
|
|
@@ -39012,7 +38724,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39012
38724
|
dryRun: true,
|
|
39013
38725
|
data: {
|
|
39014
38726
|
params: {
|
|
39015
|
-
videoPath:
|
|
38727
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
39016
38728
|
aspect: options.aspect,
|
|
39017
38729
|
focus: options.focus,
|
|
39018
38730
|
analyzeOnly: options.analyzeOnly || false
|
|
@@ -39021,7 +38733,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39021
38733
|
});
|
|
39022
38734
|
return;
|
|
39023
38735
|
}
|
|
39024
|
-
const absPath =
|
|
38736
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
39025
38737
|
const spinner2 = ora("Analyzing video...").start();
|
|
39026
38738
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
39027
38739
|
"-v",
|
|
@@ -39059,7 +38771,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39059
38771
|
for (let i = 1; i <= numKeyframes && i <= 30; i++) {
|
|
39060
38772
|
const framePath = `${tempDir}/frame-${i.toString().padStart(4, "0")}.jpg`;
|
|
39061
38773
|
try {
|
|
39062
|
-
const frameBuffer = await
|
|
38774
|
+
const frameBuffer = await readFile19(framePath);
|
|
39063
38775
|
const frameBase64 = frameBuffer.toString("base64");
|
|
39064
38776
|
const result = await claude.analyzeFrameForReframe(frameBase64, options.aspect, {
|
|
39065
38777
|
focusMode: options.focus,
|
|
@@ -39083,7 +38795,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39083
38795
|
}
|
|
39084
38796
|
spinner2.succeed(source_default.green(`Analyzed ${cropKeyframes.length} keyframes`));
|
|
39085
38797
|
if (isJsonMode()) {
|
|
39086
|
-
const reframeOutputPath = options.output ?
|
|
38798
|
+
const reframeOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-${options.aspect.replace(":", "x")}$1`);
|
|
39087
38799
|
outputSuccess({
|
|
39088
38800
|
command: "edit reframe",
|
|
39089
38801
|
startedAt,
|
|
@@ -39118,8 +38830,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39118
38830
|
}
|
|
39119
38831
|
console.log();
|
|
39120
38832
|
if (options.keyframes) {
|
|
39121
|
-
const keyframesPath =
|
|
39122
|
-
await
|
|
38833
|
+
const keyframesPath = resolve29(process.cwd(), options.keyframes);
|
|
38834
|
+
await writeFile28(keyframesPath, JSON.stringify(cropKeyframes, null, 2));
|
|
39123
38835
|
console.log(source_default.green(`Keyframes saved to: ${keyframesPath}`));
|
|
39124
38836
|
}
|
|
39125
38837
|
if (options.analyzeOnly) {
|
|
@@ -39130,7 +38842,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39130
38842
|
const avgCropY = Math.round(cropKeyframes.reduce((sum, kf) => sum + kf.cropY, 0) / cropKeyframes.length);
|
|
39131
38843
|
const cropWidth = cropKeyframes[0]?.cropWidth || sourceWidth;
|
|
39132
38844
|
const cropHeight = cropKeyframes[0]?.cropHeight || sourceHeight;
|
|
39133
|
-
const outputPath = options.output ?
|
|
38845
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-${options.aspect.replace(":", "x")}$1`);
|
|
39134
38846
|
spinner2.start("Applying reframe...");
|
|
39135
38847
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", `crop=${cropWidth}:${cropHeight}:${avgCropX}:${avgCropY}`, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
|
|
39136
38848
|
spinner2.succeed(source_default.green("Reframe applied"));
|
|
@@ -39166,7 +38878,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39166
38878
|
dryRun: true,
|
|
39167
38879
|
data: {
|
|
39168
38880
|
params: {
|
|
39169
|
-
imagePaths: imagePaths.map((p) =>
|
|
38881
|
+
imagePaths: imagePaths.map((p) => resolve29(process.cwd(), p)),
|
|
39170
38882
|
prompt: prompt3,
|
|
39171
38883
|
provider,
|
|
39172
38884
|
model: options.model,
|
|
@@ -39187,8 +38899,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39187
38899
|
const spinner2 = ora(`Reading ${imagePaths.length} image(s)...`).start();
|
|
39188
38900
|
const imageBuffers = [];
|
|
39189
38901
|
for (const imagePath of imagePaths) {
|
|
39190
|
-
const absPath =
|
|
39191
|
-
const buffer = await
|
|
38902
|
+
const absPath = resolve29(process.cwd(), imagePath);
|
|
38903
|
+
const buffer = await readFile19(absPath);
|
|
39192
38904
|
imageBuffers.push(buffer);
|
|
39193
38905
|
}
|
|
39194
38906
|
let result;
|
|
@@ -39236,16 +38948,16 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39236
38948
|
}
|
|
39237
38949
|
spinner2.succeed(source_default.green("Image edited"));
|
|
39238
38950
|
const img = result.images[0];
|
|
39239
|
-
const outputPath =
|
|
38951
|
+
const outputPath = resolve29(process.cwd(), options.output);
|
|
39240
38952
|
const saveImage = async () => {
|
|
39241
38953
|
await mkdir15(dirname10(outputPath), { recursive: true });
|
|
39242
38954
|
if (img.base64) {
|
|
39243
38955
|
const buffer = Buffer.from(img.base64, "base64");
|
|
39244
|
-
await
|
|
38956
|
+
await writeFile28(outputPath, buffer);
|
|
39245
38957
|
} else if (img.url) {
|
|
39246
38958
|
const resp = await fetch(img.url);
|
|
39247
38959
|
const arrayBuf = await resp.arrayBuffer();
|
|
39248
|
-
await
|
|
38960
|
+
await writeFile28(outputPath, Buffer.from(arrayBuf));
|
|
39249
38961
|
}
|
|
39250
38962
|
};
|
|
39251
38963
|
const resultModel = result.model;
|
|
@@ -39278,7 +38990,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39278
38990
|
if (options.output) {
|
|
39279
38991
|
validateOutputPath(options.output);
|
|
39280
38992
|
}
|
|
39281
|
-
const absPath =
|
|
38993
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
39282
38994
|
const factor = parseInt(options.factor);
|
|
39283
38995
|
if (![2, 4, 8].includes(factor)) {
|
|
39284
38996
|
exitWithError(usageError("Factor must be 2, 4, or 8"));
|
|
@@ -39299,7 +39011,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39299
39011
|
});
|
|
39300
39012
|
return;
|
|
39301
39013
|
}
|
|
39302
|
-
const outputPath = options.output ?
|
|
39014
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-slow${factor}x$1`);
|
|
39303
39015
|
const spinner2 = ora(`Creating ${factor}x slow motion...`).start();
|
|
39304
39016
|
try {
|
|
39305
39017
|
const { stdout: fpsOut } = await execSafe("ffprobe", [
|
|
@@ -39360,7 +39072,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39360
39072
|
if (options.output) {
|
|
39361
39073
|
validateOutputPath(options.output);
|
|
39362
39074
|
}
|
|
39363
|
-
const absPath =
|
|
39075
|
+
const absPath = resolve29(process.cwd(), videoPath);
|
|
39364
39076
|
const scale = parseInt(options.scale);
|
|
39365
39077
|
if (scale !== 2 && scale !== 4) {
|
|
39366
39078
|
exitWithError(usageError("Scale must be 2 or 4"));
|
|
@@ -39382,7 +39094,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39382
39094
|
return;
|
|
39383
39095
|
}
|
|
39384
39096
|
if (options.ffmpeg) {
|
|
39385
|
-
const outputPath = options.output ?
|
|
39097
|
+
const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-upscaled-${scale}x$1`);
|
|
39386
39098
|
const spinner3 = ora(`Upscaling video with FFmpeg (${scale}x)...`).start();
|
|
39387
39099
|
try {
|
|
39388
39100
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
@@ -39463,9 +39175,9 @@ __export(ai_analyze_exports, {
|
|
|
39463
39175
|
executeGeminiVideo: () => executeGeminiVideo,
|
|
39464
39176
|
registerAnalyzeCommands: () => registerAnalyzeCommands
|
|
39465
39177
|
});
|
|
39466
|
-
import { readFile as
|
|
39467
|
-
import { extname as extname6, resolve as
|
|
39468
|
-
import { existsSync as
|
|
39178
|
+
import { readFile as readFile20 } from "node:fs/promises";
|
|
39179
|
+
import { extname as extname6, resolve as resolve30 } from "node:path";
|
|
39180
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
39469
39181
|
async function executeGeminiVideo(options) {
|
|
39470
39182
|
try {
|
|
39471
39183
|
const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
|
|
@@ -39483,11 +39195,11 @@ async function executeGeminiVideo(options) {
|
|
|
39483
39195
|
if (isYouTube) {
|
|
39484
39196
|
videoData = options.source;
|
|
39485
39197
|
} else {
|
|
39486
|
-
const absPath =
|
|
39487
|
-
if (!
|
|
39198
|
+
const absPath = resolve30(process.cwd(), options.source);
|
|
39199
|
+
if (!existsSync23(absPath)) {
|
|
39488
39200
|
return { success: false, error: `File not found: ${absPath}` };
|
|
39489
39201
|
}
|
|
39490
|
-
videoData = await
|
|
39202
|
+
videoData = await readFile20(absPath);
|
|
39491
39203
|
}
|
|
39492
39204
|
const gemini = new GeminiProvider();
|
|
39493
39205
|
await gemini.initialize({ apiKey });
|
|
@@ -39556,11 +39268,11 @@ async function executeAnalyze(options) {
|
|
|
39556
39268
|
}
|
|
39557
39269
|
imageBuffer = Buffer.from(await response.arrayBuffer());
|
|
39558
39270
|
} else {
|
|
39559
|
-
const absPath =
|
|
39560
|
-
if (!
|
|
39271
|
+
const absPath = resolve30(process.cwd(), source);
|
|
39272
|
+
if (!existsSync23(absPath)) {
|
|
39561
39273
|
return { success: false, error: `File not found: ${absPath}` };
|
|
39562
39274
|
}
|
|
39563
|
-
imageBuffer = await
|
|
39275
|
+
imageBuffer = await readFile20(absPath);
|
|
39564
39276
|
}
|
|
39565
39277
|
const result2 = await gemini.analyzeImage(imageBuffer, options.prompt, {
|
|
39566
39278
|
model: modelId,
|
|
@@ -39591,11 +39303,11 @@ async function executeAnalyze(options) {
|
|
|
39591
39303
|
}
|
|
39592
39304
|
videoData = Buffer.from(await response.arrayBuffer());
|
|
39593
39305
|
} else {
|
|
39594
|
-
const absPath =
|
|
39595
|
-
if (!
|
|
39306
|
+
const absPath = resolve30(process.cwd(), source);
|
|
39307
|
+
if (!existsSync23(absPath)) {
|
|
39596
39308
|
return { success: false, error: `File not found: ${absPath}` };
|
|
39597
39309
|
}
|
|
39598
|
-
videoData = await
|
|
39310
|
+
videoData = await readFile20(absPath);
|
|
39599
39311
|
}
|
|
39600
39312
|
const result = await gemini.analyzeVideo(videoData, options.prompt, {
|
|
39601
39313
|
model: modelId,
|
|
@@ -39729,9 +39441,9 @@ __export(ai_review_exports, {
|
|
|
39729
39441
|
executeReview: () => executeReview,
|
|
39730
39442
|
registerReviewCommand: () => registerReviewCommand
|
|
39731
39443
|
});
|
|
39732
|
-
import { readFile as
|
|
39733
|
-
import { resolve as
|
|
39734
|
-
import { existsSync as
|
|
39444
|
+
import { readFile as readFile21, rename as rename2 } from "node:fs/promises";
|
|
39445
|
+
import { resolve as resolve31 } from "node:path";
|
|
39446
|
+
import { existsSync as existsSync24 } from "node:fs";
|
|
39735
39447
|
function parseReviewFeedback(response) {
|
|
39736
39448
|
let cleaned = response.trim();
|
|
39737
39449
|
if (cleaned.startsWith("```json")) {
|
|
@@ -39755,8 +39467,8 @@ function parseReviewFeedback(response) {
|
|
|
39755
39467
|
}
|
|
39756
39468
|
async function executeReview(options) {
|
|
39757
39469
|
const { videoPath, storyboardPath, autoApply = false, verify = false, model = "flash" } = options;
|
|
39758
|
-
const absVideoPath =
|
|
39759
|
-
if (!
|
|
39470
|
+
const absVideoPath = resolve31(process.cwd(), videoPath);
|
|
39471
|
+
if (!existsSync24(absVideoPath)) {
|
|
39760
39472
|
return { success: false, error: `Video not found: ${absVideoPath}` };
|
|
39761
39473
|
}
|
|
39762
39474
|
const apiKey = process.env.GOOGLE_API_KEY || await getApiKey("GOOGLE_API_KEY", "Google");
|
|
@@ -39765,9 +39477,9 @@ async function executeReview(options) {
|
|
|
39765
39477
|
}
|
|
39766
39478
|
let storyboardContext = "";
|
|
39767
39479
|
if (storyboardPath) {
|
|
39768
|
-
const absStoryboardPath =
|
|
39769
|
-
if (
|
|
39770
|
-
const content = await
|
|
39480
|
+
const absStoryboardPath = resolve31(process.cwd(), storyboardPath);
|
|
39481
|
+
if (existsSync24(absStoryboardPath)) {
|
|
39482
|
+
const content = await readFile21(absStoryboardPath, "utf-8");
|
|
39771
39483
|
storyboardContext = `
|
|
39772
39484
|
|
|
39773
39485
|
Original storyboard for reference:
|
|
@@ -39800,7 +39512,7 @@ ${content}`;
|
|
|
39800
39512
|
Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFixable. Be specific and practical.${storyboardContext}`;
|
|
39801
39513
|
const gemini = new GeminiProvider();
|
|
39802
39514
|
await gemini.initialize({ apiKey });
|
|
39803
|
-
const videoData = await
|
|
39515
|
+
const videoData = await readFile21(absVideoPath);
|
|
39804
39516
|
const analysisResult = await gemini.analyzeVideo(videoData, reviewPrompt, {
|
|
39805
39517
|
model: modelId
|
|
39806
39518
|
});
|
|
@@ -39821,7 +39533,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
|
|
|
39821
39533
|
};
|
|
39822
39534
|
if (autoApply && feedback.autoFixable.length > 0) {
|
|
39823
39535
|
let currentInput = absVideoPath;
|
|
39824
|
-
const outputBase = options.outputPath ?
|
|
39536
|
+
const outputBase = options.outputPath ? resolve31(process.cwd(), options.outputPath) : absVideoPath.replace(/(\.[^.]+)$/, "-reviewed$1");
|
|
39825
39537
|
for (const fix of feedback.autoFixable) {
|
|
39826
39538
|
if (fix.type === "color_grade" && fix.ffmpegFilter) {
|
|
39827
39539
|
try {
|
|
@@ -39838,7 +39550,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
|
|
|
39838
39550
|
if (currentInput !== absVideoPath) {
|
|
39839
39551
|
const finalOutput = outputBase;
|
|
39840
39552
|
try {
|
|
39841
|
-
await
|
|
39553
|
+
await rename2(currentInput, finalOutput);
|
|
39842
39554
|
result.outputPath = finalOutput;
|
|
39843
39555
|
} catch {
|
|
39844
39556
|
result.outputPath = currentInput;
|
|
@@ -39846,7 +39558,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
|
|
|
39846
39558
|
}
|
|
39847
39559
|
}
|
|
39848
39560
|
if (verify && result.outputPath) {
|
|
39849
|
-
const verifyVideoData = await
|
|
39561
|
+
const verifyVideoData = await readFile21(result.outputPath);
|
|
39850
39562
|
const verifyResult = await gemini.analyzeVideo(
|
|
39851
39563
|
verifyVideoData,
|
|
39852
39564
|
'Rate this video overall quality on a scale of 1-10. Return ONLY a JSON object: {"score": <number>}',
|
|
@@ -39905,73 +39617,657 @@ function registerReviewCommand(aiCommand) {
|
|
|
39905
39617
|
spinner2.fail(result.error || "Video review failed");
|
|
39906
39618
|
exitWithError(apiError(result.error || "Video review failed", true));
|
|
39907
39619
|
}
|
|
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}`));
|
|
39620
|
+
spinner2.succeed(source_default.green("Video review complete"));
|
|
39621
|
+
console.log();
|
|
39622
|
+
const fb = result.feedback;
|
|
39623
|
+
console.log(source_default.bold.cyan("Video Review"));
|
|
39624
|
+
console.log(source_default.dim("\u2500".repeat(60)));
|
|
39625
|
+
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`);
|
|
39626
|
+
console.log();
|
|
39627
|
+
const categories = [
|
|
39628
|
+
["Pacing", fb.categories.pacing],
|
|
39629
|
+
["Color", fb.categories.color],
|
|
39630
|
+
["Text Readability", fb.categories.textReadability],
|
|
39631
|
+
["Audio-Visual Sync", fb.categories.audioVisualSync],
|
|
39632
|
+
["Composition", fb.categories.composition]
|
|
39633
|
+
];
|
|
39634
|
+
for (const [name, cat] of categories) {
|
|
39635
|
+
const scoreColor = cat.score >= 7 ? source_default.green : cat.score >= 5 ? source_default.yellow : source_default.red;
|
|
39636
|
+
const fixable = cat.fixable ? source_default.dim(" [fixable]") : "";
|
|
39637
|
+
console.log(` ${name.padEnd(20)} ${scoreColor(String(cat.score).padStart(2))}/10${fixable}`);
|
|
39638
|
+
if (cat.issues.length > 0) {
|
|
39639
|
+
for (const issue of cat.issues) {
|
|
39640
|
+
console.log(source_default.dim(` - ${issue}`));
|
|
39641
|
+
}
|
|
39642
|
+
}
|
|
39643
|
+
}
|
|
39644
|
+
if (result.appliedFixes && result.appliedFixes.length > 0) {
|
|
39645
|
+
console.log();
|
|
39646
|
+
console.log(source_default.bold.green("Applied Fixes:"));
|
|
39647
|
+
for (const fix of result.appliedFixes) {
|
|
39648
|
+
console.log(source_default.green(` + ${fix}`));
|
|
39649
|
+
}
|
|
39650
|
+
if (result.outputPath) {
|
|
39651
|
+
console.log(source_default.green(` Output: ${result.outputPath}`));
|
|
39652
|
+
}
|
|
39653
|
+
}
|
|
39654
|
+
if (result.verificationScore !== void 0) {
|
|
39655
|
+
console.log();
|
|
39656
|
+
console.log(source_default.bold(`Verification Score: ${result.verificationScore}/10`));
|
|
39657
|
+
}
|
|
39658
|
+
if (fb.recommendations.length > 0) {
|
|
39659
|
+
console.log();
|
|
39660
|
+
console.log(source_default.bold("Recommendations:"));
|
|
39661
|
+
for (const rec of fb.recommendations) {
|
|
39662
|
+
console.log(source_default.dim(` * ${rec}`));
|
|
39663
|
+
}
|
|
39664
|
+
}
|
|
39665
|
+
console.log();
|
|
39666
|
+
} catch (error) {
|
|
39667
|
+
exitWithError(generalError(error instanceof Error ? error.message : "Video review failed"));
|
|
39668
|
+
}
|
|
39669
|
+
});
|
|
39670
|
+
}
|
|
39671
|
+
var init_ai_review = __esm({
|
|
39672
|
+
"src/commands/ai-review.ts"() {
|
|
39673
|
+
"use strict";
|
|
39674
|
+
init_source();
|
|
39675
|
+
init_ora();
|
|
39676
|
+
init_dist2();
|
|
39677
|
+
init_api_key();
|
|
39678
|
+
init_exec_safe();
|
|
39679
|
+
init_output();
|
|
39680
|
+
init_validate();
|
|
39681
|
+
}
|
|
39682
|
+
});
|
|
39683
|
+
|
|
39684
|
+
// src/commands/_shared/video-providers.ts
|
|
39685
|
+
async function generateVideoWithRetryGrok(grok, segment, options, maxRetries, onProgress) {
|
|
39686
|
+
const prompt3 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
|
|
39687
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
39688
|
+
try {
|
|
39689
|
+
const result = await grok.generateVideo(prompt3, {
|
|
39690
|
+
prompt: prompt3,
|
|
39691
|
+
duration: options.duration,
|
|
39692
|
+
aspectRatio: options.aspectRatio,
|
|
39693
|
+
referenceImage: options.referenceImage
|
|
39694
|
+
});
|
|
39695
|
+
if (result.status !== "failed" && result.id) {
|
|
39696
|
+
return { requestId: result.id };
|
|
39697
|
+
}
|
|
39698
|
+
const providerErr = result.error || "Grok returned failed status";
|
|
39699
|
+
if (attempt < maxRetries) {
|
|
39700
|
+
onProgress?.(
|
|
39701
|
+
`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`
|
|
39702
|
+
);
|
|
39703
|
+
await sleep(RETRY_DELAY_MS);
|
|
39704
|
+
} else {
|
|
39705
|
+
console.error(source_default.dim(`
|
|
39706
|
+
[Grok error: ${providerErr}]`));
|
|
39707
|
+
}
|
|
39708
|
+
} catch (err) {
|
|
39709
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
39710
|
+
if (attempt < maxRetries) {
|
|
39711
|
+
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
39712
|
+
await sleep(RETRY_DELAY_MS);
|
|
39713
|
+
} else {
|
|
39714
|
+
console.error(source_default.dim(`
|
|
39715
|
+
[Grok error: ${errMsg}]`));
|
|
39716
|
+
}
|
|
39717
|
+
}
|
|
39718
|
+
}
|
|
39719
|
+
return null;
|
|
39720
|
+
}
|
|
39721
|
+
async function generateVideoWithRetryKling(kling, segment, options, maxRetries, onProgress) {
|
|
39722
|
+
const prompt3 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
|
|
39723
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
39724
|
+
try {
|
|
39725
|
+
const result = await kling.generateVideo(prompt3, {
|
|
39726
|
+
prompt: prompt3,
|
|
39727
|
+
// Pass reference image (base64 or URL) - KlingProvider handles v1.5 fallback for base64
|
|
39728
|
+
referenceImage: options.referenceImage,
|
|
39729
|
+
duration: options.duration,
|
|
39730
|
+
aspectRatio: options.aspectRatio,
|
|
39731
|
+
mode: "std"
|
|
39732
|
+
// std mode for faster generation
|
|
39733
|
+
});
|
|
39734
|
+
if (result.status !== "failed" && result.id) {
|
|
39735
|
+
return {
|
|
39736
|
+
taskId: result.id,
|
|
39737
|
+
type: options.referenceImage ? "image2video" : "text2video"
|
|
39738
|
+
};
|
|
39739
|
+
}
|
|
39740
|
+
const providerErr = result.error || "Kling returned failed status";
|
|
39741
|
+
if (attempt < maxRetries) {
|
|
39742
|
+
onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
39743
|
+
await sleep(RETRY_DELAY_MS);
|
|
39744
|
+
} else {
|
|
39745
|
+
console.error(source_default.dim(`
|
|
39746
|
+
[Kling error: ${providerErr}]`));
|
|
39747
|
+
}
|
|
39748
|
+
} catch (err) {
|
|
39749
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
39750
|
+
if (attempt < maxRetries) {
|
|
39751
|
+
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
39752
|
+
await sleep(RETRY_DELAY_MS);
|
|
39753
|
+
} else {
|
|
39754
|
+
console.error(source_default.dim(`
|
|
39755
|
+
[Kling error: ${errMsg}]`));
|
|
39756
|
+
}
|
|
39757
|
+
}
|
|
39758
|
+
}
|
|
39759
|
+
return null;
|
|
39760
|
+
}
|
|
39761
|
+
async function generateVideoWithRetryRunway(runway, segment, referenceImage, options, maxRetries, onProgress) {
|
|
39762
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
39763
|
+
try {
|
|
39764
|
+
const result = await runway.generateVideo(segment.visuals, {
|
|
39765
|
+
prompt: segment.visuals,
|
|
39766
|
+
referenceImage,
|
|
39767
|
+
duration: options.duration,
|
|
39768
|
+
aspectRatio: options.aspectRatio
|
|
39769
|
+
});
|
|
39770
|
+
if (result.status !== "failed" && result.id) {
|
|
39771
|
+
return { taskId: result.id };
|
|
39772
|
+
}
|
|
39773
|
+
const providerErr = result.error || "Runway returned failed status";
|
|
39774
|
+
if (attempt < maxRetries) {
|
|
39775
|
+
onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
39776
|
+
await sleep(RETRY_DELAY_MS);
|
|
39777
|
+
} else {
|
|
39778
|
+
console.error(source_default.dim(`
|
|
39779
|
+
[Runway error: ${providerErr}]`));
|
|
39780
|
+
}
|
|
39781
|
+
} catch (err) {
|
|
39782
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
39783
|
+
if (attempt < maxRetries) {
|
|
39784
|
+
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
39785
|
+
await sleep(RETRY_DELAY_MS);
|
|
39786
|
+
} else {
|
|
39787
|
+
console.error(source_default.dim(`
|
|
39788
|
+
[Runway error: ${errMsg}]`));
|
|
39789
|
+
}
|
|
39790
|
+
}
|
|
39791
|
+
}
|
|
39792
|
+
return null;
|
|
39793
|
+
}
|
|
39794
|
+
async function generateVideoWithRetryVeo(gemini, segment, options, maxRetries, onProgress) {
|
|
39795
|
+
const prompt3 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
|
|
39796
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
39797
|
+
try {
|
|
39798
|
+
const result = await gemini.generateVideo(prompt3, {
|
|
39799
|
+
prompt: prompt3,
|
|
39800
|
+
referenceImage: options.referenceImage,
|
|
39801
|
+
duration: options.duration,
|
|
39802
|
+
aspectRatio: options.aspectRatio,
|
|
39803
|
+
model: "veo-3.1-fast-generate-preview"
|
|
39804
|
+
});
|
|
39805
|
+
if (result.status !== "failed" && result.id) {
|
|
39806
|
+
return { operationName: result.id };
|
|
39807
|
+
}
|
|
39808
|
+
const providerErr = result.error || "Veo returned failed status";
|
|
39809
|
+
if (attempt < maxRetries) {
|
|
39810
|
+
onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
39811
|
+
await sleep(RETRY_DELAY_MS);
|
|
39812
|
+
} else {
|
|
39813
|
+
console.error(source_default.dim(`
|
|
39814
|
+
[Veo error: ${providerErr}]`));
|
|
39815
|
+
}
|
|
39816
|
+
} catch (err) {
|
|
39817
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
39818
|
+
if (attempt < maxRetries) {
|
|
39819
|
+
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
39820
|
+
await sleep(RETRY_DELAY_MS);
|
|
39821
|
+
} else {
|
|
39822
|
+
console.error(source_default.dim(`
|
|
39823
|
+
[Veo error: ${errMsg}]`));
|
|
39824
|
+
}
|
|
39825
|
+
}
|
|
39826
|
+
}
|
|
39827
|
+
return null;
|
|
39828
|
+
}
|
|
39829
|
+
var init_video_providers = __esm({
|
|
39830
|
+
"src/commands/_shared/video-providers.ts"() {
|
|
39831
|
+
"use strict";
|
|
39832
|
+
init_source();
|
|
39833
|
+
init_video_utils();
|
|
39834
|
+
}
|
|
39835
|
+
});
|
|
39836
|
+
|
|
39837
|
+
// src/commands/ai-script-pipeline.ts
|
|
39838
|
+
import { readFile as readFile24, writeFile as writeFile31, unlink as unlink2, rename as rename3 } from "node:fs/promises";
|
|
39839
|
+
import { resolve as resolve34, extname as extname8 } from "node:path";
|
|
39840
|
+
import { existsSync as existsSync26 } from "node:fs";
|
|
39841
|
+
async function executeRegenerateScene(options) {
|
|
39842
|
+
const result = {
|
|
39843
|
+
success: false,
|
|
39844
|
+
regeneratedScenes: [],
|
|
39845
|
+
failedScenes: []
|
|
39846
|
+
};
|
|
39847
|
+
try {
|
|
39848
|
+
const outputDir = resolve34(process.cwd(), options.projectDir);
|
|
39849
|
+
if (!existsSync26(outputDir)) {
|
|
39850
|
+
return { ...result, error: `Project directory not found: ${outputDir}` };
|
|
39851
|
+
}
|
|
39852
|
+
const yamlPath = resolve34(outputDir, "storyboard.yaml");
|
|
39853
|
+
const jsonPath = resolve34(outputDir, "storyboard.json");
|
|
39854
|
+
const storyboardPath = existsSync26(yamlPath) ? yamlPath : existsSync26(jsonPath) ? jsonPath : null;
|
|
39855
|
+
if (!storyboardPath) {
|
|
39856
|
+
return { ...result, error: `Storyboard not found in: ${outputDir} (expected storyboard.yaml or storyboard.json)` };
|
|
39857
|
+
}
|
|
39858
|
+
const storyboardContent = await readFile24(storyboardPath, "utf-8");
|
|
39859
|
+
const segments = storyboardPath.endsWith(".yaml") ? (0, import_yaml5.parse)(storyboardContent).scenes : JSON.parse(storyboardContent);
|
|
39860
|
+
for (const sceneNum of options.scenes) {
|
|
39861
|
+
if (sceneNum < 1 || sceneNum > segments.length) {
|
|
39862
|
+
return { ...result, error: `Scene ${sceneNum} does not exist. Storyboard has ${segments.length} scenes.` };
|
|
39863
|
+
}
|
|
39864
|
+
}
|
|
39865
|
+
const regenerateVideo = options.videoOnly || !options.narrationOnly && !options.imageOnly;
|
|
39866
|
+
const regenerateNarration = options.narrationOnly || !options.videoOnly && !options.imageOnly;
|
|
39867
|
+
const regenerateImage = options.imageOnly || !options.videoOnly && !options.narrationOnly;
|
|
39868
|
+
let videoApiKey;
|
|
39869
|
+
if (regenerateVideo) {
|
|
39870
|
+
const generatorKeyMap = {
|
|
39871
|
+
grok: { envVar: "XAI_API_KEY", name: "xAI (Grok)" },
|
|
39872
|
+
kling: { envVar: "KLING_API_KEY", name: "Kling" },
|
|
39873
|
+
runway: { envVar: "RUNWAY_API_SECRET", name: "Runway" },
|
|
39874
|
+
veo: { envVar: "GOOGLE_API_KEY", name: "Google (Veo)" }
|
|
39875
|
+
};
|
|
39876
|
+
const generator = options.generator || "grok";
|
|
39877
|
+
const genInfo = generatorKeyMap[generator];
|
|
39878
|
+
if (!genInfo) {
|
|
39879
|
+
return { ...result, error: `Invalid generator: ${generator}. Available: ${Object.keys(generatorKeyMap).join(", ")}` };
|
|
39880
|
+
}
|
|
39881
|
+
videoApiKey = await getApiKey(genInfo.envVar, genInfo.name) ?? void 0;
|
|
39882
|
+
if (!videoApiKey) {
|
|
39883
|
+
return { ...result, error: `${genInfo.name} API key required. Run 'vibe setup' or set ${genInfo.envVar} in .env` };
|
|
39884
|
+
}
|
|
39885
|
+
}
|
|
39886
|
+
let imageApiKey;
|
|
39887
|
+
if (regenerateImage) {
|
|
39888
|
+
const imageProvider = options.imageProvider || "openai";
|
|
39889
|
+
const imageKeyMap = {
|
|
39890
|
+
openai: { envVar: "OPENAI_API_KEY", name: "OpenAI" },
|
|
39891
|
+
gemini: { envVar: "GOOGLE_API_KEY", name: "Google" },
|
|
39892
|
+
grok: { envVar: "XAI_API_KEY", name: "xAI" }
|
|
39893
|
+
};
|
|
39894
|
+
const info = imageKeyMap[imageProvider];
|
|
39895
|
+
if (!info) {
|
|
39896
|
+
return { ...result, error: `Invalid imageProvider: ${imageProvider}` };
|
|
39897
|
+
}
|
|
39898
|
+
imageApiKey = await getApiKey(info.envVar, info.name) ?? void 0;
|
|
39899
|
+
if (!imageApiKey) {
|
|
39900
|
+
return { ...result, error: `${info.name} API key required. Run 'vibe setup' or set ${info.envVar} in .env` };
|
|
39901
|
+
}
|
|
39902
|
+
}
|
|
39903
|
+
let elevenlabsApiKey;
|
|
39904
|
+
if (regenerateNarration) {
|
|
39905
|
+
elevenlabsApiKey = await getApiKey("ELEVENLABS_API_KEY", "ElevenLabs") ?? void 0;
|
|
39906
|
+
if (!elevenlabsApiKey) {
|
|
39907
|
+
return { ...result, error: "ElevenLabs API key required. Run 'vibe setup' or set ELEVENLABS_API_KEY in .env" };
|
|
39908
|
+
}
|
|
39909
|
+
}
|
|
39910
|
+
let storyboardMutated = false;
|
|
39911
|
+
for (const sceneNum of options.scenes) {
|
|
39912
|
+
const segment = segments[sceneNum - 1];
|
|
39913
|
+
const narrationPath = resolve34(outputDir, `narration-${sceneNum}.mp3`);
|
|
39914
|
+
const imagePath = resolve34(outputDir, `scene-${sceneNum}.png`);
|
|
39915
|
+
const videoPath = resolve34(outputDir, `scene-${sceneNum}.mp4`);
|
|
39916
|
+
let sceneFailed = false;
|
|
39917
|
+
if (regenerateNarration && elevenlabsApiKey) {
|
|
39918
|
+
options.onProgress?.(`Scene ${sceneNum}: regenerating narration...`);
|
|
39919
|
+
const elevenlabs = new ElevenLabsProvider();
|
|
39920
|
+
await elevenlabs.initialize({ apiKey: elevenlabsApiKey });
|
|
39921
|
+
const narrationText = segment.narration || segment.description;
|
|
39922
|
+
const ttsResult = await elevenlabs.textToSpeech(narrationText, {
|
|
39923
|
+
voiceId: options.voice
|
|
39924
|
+
});
|
|
39925
|
+
if (ttsResult.success && ttsResult.audioBuffer) {
|
|
39926
|
+
await writeFile31(narrationPath, ttsResult.audioBuffer);
|
|
39927
|
+
segment.duration = await getAudioDuration(narrationPath);
|
|
39928
|
+
storyboardMutated = true;
|
|
39929
|
+
} else {
|
|
39930
|
+
sceneFailed = true;
|
|
39931
|
+
}
|
|
39932
|
+
}
|
|
39933
|
+
if (!sceneFailed && regenerateImage && imageApiKey) {
|
|
39934
|
+
options.onProgress?.(`Scene ${sceneNum}: regenerating image...`);
|
|
39935
|
+
const imageProvider = options.imageProvider || "openai";
|
|
39936
|
+
const characterDesc = segment.characterDescription || segments[0]?.characterDescription;
|
|
39937
|
+
let imagePrompt = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
|
|
39938
|
+
if (characterDesc) {
|
|
39939
|
+
imagePrompt = `${imagePrompt}
|
|
39940
|
+
|
|
39941
|
+
IMPORTANT - Character appearance must match exactly: ${characterDesc}`;
|
|
39942
|
+
}
|
|
39943
|
+
let referenceImageBuffer;
|
|
39944
|
+
const refSceneNum = options.referenceScene;
|
|
39945
|
+
if (refSceneNum && refSceneNum >= 1 && refSceneNum <= segments.length && refSceneNum !== sceneNum) {
|
|
39946
|
+
const refImagePath = resolve34(outputDir, `scene-${refSceneNum}.png`);
|
|
39947
|
+
if (existsSync26(refImagePath)) {
|
|
39948
|
+
referenceImageBuffer = await readFile24(refImagePath);
|
|
39949
|
+
}
|
|
39950
|
+
} else if (!refSceneNum) {
|
|
39951
|
+
for (let i = 1; i <= segments.length; i++) {
|
|
39952
|
+
if (i !== sceneNum) {
|
|
39953
|
+
const otherImagePath = resolve34(outputDir, `scene-${i}.png`);
|
|
39954
|
+
if (existsSync26(otherImagePath)) {
|
|
39955
|
+
referenceImageBuffer = await readFile24(otherImagePath);
|
|
39956
|
+
break;
|
|
39957
|
+
}
|
|
39958
|
+
}
|
|
39929
39959
|
}
|
|
39930
39960
|
}
|
|
39931
|
-
|
|
39932
|
-
|
|
39933
|
-
|
|
39934
|
-
|
|
39935
|
-
|
|
39936
|
-
|
|
39961
|
+
const dalleImageSizes = {
|
|
39962
|
+
"16:9": "1536x1024",
|
|
39963
|
+
"9:16": "1024x1536",
|
|
39964
|
+
"1:1": "1024x1024"
|
|
39965
|
+
};
|
|
39966
|
+
let imageBuffer;
|
|
39967
|
+
let imageUrl;
|
|
39968
|
+
if (imageProvider === "openai") {
|
|
39969
|
+
const openaiImage = new OpenAIImageProvider();
|
|
39970
|
+
await openaiImage.initialize({ apiKey: imageApiKey });
|
|
39971
|
+
const imageResult = await openaiImage.generateImage(imagePrompt, {
|
|
39972
|
+
size: dalleImageSizes[options.aspectRatio || "16:9"] || "1536x1024",
|
|
39973
|
+
quality: "standard"
|
|
39974
|
+
});
|
|
39975
|
+
if (imageResult.success && imageResult.images?.[0]) {
|
|
39976
|
+
const img = imageResult.images[0];
|
|
39977
|
+
if (img.base64) imageBuffer = Buffer.from(img.base64, "base64");
|
|
39978
|
+
else if (img.url) imageUrl = img.url;
|
|
39979
|
+
}
|
|
39980
|
+
} else if (imageProvider === "gemini") {
|
|
39981
|
+
const gemini = new GeminiProvider();
|
|
39982
|
+
await gemini.initialize({ apiKey: imageApiKey });
|
|
39983
|
+
if (referenceImageBuffer) {
|
|
39984
|
+
const simplifiedVisuals = segment.visuals.split(/[,.]/).find(
|
|
39985
|
+
(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")
|
|
39986
|
+
) || segment.visuals.split(".")[0];
|
|
39987
|
+
const editPrompt = `Generate a new image showing the SAME SINGLE person from the reference image in a new scene.
|
|
39988
|
+
|
|
39989
|
+
REFERENCE: Look at the person in the reference image - their face, hair, build, and overall appearance.
|
|
39990
|
+
|
|
39991
|
+
NEW SCENE: ${simplifiedVisuals}
|
|
39992
|
+
|
|
39993
|
+
CRITICAL RULES:
|
|
39994
|
+
1. Show ONLY ONE person - the exact same individual from the reference image
|
|
39995
|
+
2. The person must have the IDENTICAL face, hair style, and body type
|
|
39996
|
+
3. Do NOT show multiple people or duplicate the character
|
|
39997
|
+
4. Create a single moment in time, one pose, one action
|
|
39998
|
+
5. Match the art style and quality of the reference image
|
|
39999
|
+
|
|
40000
|
+
Generate the single-person scene image now.`;
|
|
40001
|
+
const imageResult = await gemini.editImage([referenceImageBuffer], editPrompt, {
|
|
40002
|
+
aspectRatio: options.aspectRatio || "16:9"
|
|
40003
|
+
});
|
|
40004
|
+
if (imageResult.success && imageResult.images?.[0]?.base64) {
|
|
40005
|
+
imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
|
|
40006
|
+
}
|
|
40007
|
+
} else {
|
|
40008
|
+
const imageResult = await gemini.generateImage(imagePrompt, {
|
|
40009
|
+
aspectRatio: options.aspectRatio || "16:9"
|
|
40010
|
+
});
|
|
40011
|
+
if (imageResult.success && imageResult.images?.[0]?.base64) {
|
|
40012
|
+
imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
|
|
40013
|
+
}
|
|
40014
|
+
}
|
|
40015
|
+
} else if (imageProvider === "grok") {
|
|
40016
|
+
const grok = new GrokProvider();
|
|
40017
|
+
await grok.initialize({ apiKey: imageApiKey });
|
|
40018
|
+
const imageResult = await grok.generateImage(imagePrompt, {
|
|
40019
|
+
aspectRatio: options.aspectRatio || "16:9"
|
|
40020
|
+
});
|
|
40021
|
+
if (imageResult.success && imageResult.images?.[0]) {
|
|
40022
|
+
const img = imageResult.images[0];
|
|
40023
|
+
if (img.base64) imageBuffer = Buffer.from(img.base64, "base64");
|
|
40024
|
+
else if (img.url) imageUrl = img.url;
|
|
40025
|
+
}
|
|
39937
40026
|
}
|
|
39938
|
-
if (
|
|
39939
|
-
|
|
40027
|
+
if (imageBuffer) {
|
|
40028
|
+
await writeFile31(imagePath, imageBuffer);
|
|
40029
|
+
} else if (imageUrl) {
|
|
40030
|
+
const response = await fetch(imageUrl);
|
|
40031
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
40032
|
+
await writeFile31(imagePath, buffer);
|
|
40033
|
+
} else {
|
|
40034
|
+
sceneFailed = true;
|
|
39940
40035
|
}
|
|
39941
40036
|
}
|
|
39942
|
-
if (
|
|
39943
|
-
|
|
39944
|
-
|
|
40037
|
+
if (sceneFailed) {
|
|
40038
|
+
result.failedScenes.push(sceneNum);
|
|
40039
|
+
continue;
|
|
39945
40040
|
}
|
|
39946
|
-
if (
|
|
39947
|
-
|
|
39948
|
-
|
|
39949
|
-
|
|
39950
|
-
|
|
40041
|
+
if (regenerateVideo && videoApiKey) {
|
|
40042
|
+
options.onProgress?.(`Scene ${sceneNum}: regenerating video (${options.generator || "grok"})...`);
|
|
40043
|
+
if (!existsSync26(imagePath)) {
|
|
40044
|
+
result.failedScenes.push(sceneNum);
|
|
40045
|
+
continue;
|
|
40046
|
+
}
|
|
40047
|
+
const imageBuffer = await readFile24(imagePath);
|
|
40048
|
+
const videoDuration = segment.duration > 5 ? 10 : 5;
|
|
40049
|
+
const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
|
|
40050
|
+
if (options.generator === "grok") {
|
|
40051
|
+
const grok = new GrokProvider();
|
|
40052
|
+
await grok.initialize({ apiKey: videoApiKey });
|
|
40053
|
+
const ext = extname8(imagePath).toLowerCase().slice(1);
|
|
40054
|
+
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
40055
|
+
const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
40056
|
+
const grokDuration = Math.min(15, Math.max(1, segment.duration));
|
|
40057
|
+
const taskResult = await generateVideoWithRetryGrok(
|
|
40058
|
+
grok,
|
|
40059
|
+
segment,
|
|
40060
|
+
{
|
|
40061
|
+
duration: grokDuration,
|
|
40062
|
+
aspectRatio: options.aspectRatio || "16:9",
|
|
40063
|
+
referenceImage
|
|
40064
|
+
},
|
|
40065
|
+
maxRetries
|
|
40066
|
+
);
|
|
40067
|
+
if (taskResult) {
|
|
40068
|
+
try {
|
|
40069
|
+
const waitResult = await grok.waitForCompletion(taskResult.requestId, void 0, 3e5);
|
|
40070
|
+
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
40071
|
+
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
40072
|
+
await writeFile31(videoPath, buffer);
|
|
40073
|
+
const targetDuration = segment.duration;
|
|
40074
|
+
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
40075
|
+
if (actualVideoDuration < targetDuration - 0.1) {
|
|
40076
|
+
const extendedPath = resolve34(outputDir, `scene-${sceneNum}-extended.mp4`);
|
|
40077
|
+
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
40078
|
+
await unlink2(videoPath);
|
|
40079
|
+
await rename3(extendedPath, videoPath);
|
|
40080
|
+
}
|
|
40081
|
+
result.regeneratedScenes.push(sceneNum);
|
|
40082
|
+
} else {
|
|
40083
|
+
logSceneFailure("Grok", `scene ${sceneNum}`, waitResult);
|
|
40084
|
+
result.failedScenes.push(sceneNum);
|
|
40085
|
+
}
|
|
40086
|
+
} catch (err) {
|
|
40087
|
+
logSceneFailure("Grok", `scene ${sceneNum}`, err);
|
|
40088
|
+
result.failedScenes.push(sceneNum);
|
|
40089
|
+
}
|
|
40090
|
+
} else {
|
|
40091
|
+
result.failedScenes.push(sceneNum);
|
|
40092
|
+
}
|
|
40093
|
+
} else if (options.generator === "veo") {
|
|
40094
|
+
const veo = new GeminiProvider();
|
|
40095
|
+
await veo.initialize({ apiKey: videoApiKey });
|
|
40096
|
+
const ext = extname8(imagePath).toLowerCase().slice(1);
|
|
40097
|
+
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
40098
|
+
const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
40099
|
+
const veoDuration = segment.duration > 6 ? 8 : segment.duration > 4 ? 6 : 4;
|
|
40100
|
+
const taskResult = await generateVideoWithRetryVeo(
|
|
40101
|
+
veo,
|
|
40102
|
+
segment,
|
|
40103
|
+
{
|
|
40104
|
+
duration: veoDuration,
|
|
40105
|
+
aspectRatio: options.aspectRatio || "16:9",
|
|
40106
|
+
referenceImage
|
|
40107
|
+
},
|
|
40108
|
+
maxRetries
|
|
40109
|
+
);
|
|
40110
|
+
if (taskResult) {
|
|
40111
|
+
try {
|
|
40112
|
+
const waitResult = await veo.waitForVideoCompletion(taskResult.operationName, void 0, 3e5);
|
|
40113
|
+
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
40114
|
+
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
40115
|
+
await writeFile31(videoPath, buffer);
|
|
40116
|
+
const targetDuration = segment.duration;
|
|
40117
|
+
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
40118
|
+
if (actualVideoDuration < targetDuration - 0.1) {
|
|
40119
|
+
const extendedPath = resolve34(outputDir, `scene-${sceneNum}-extended.mp4`);
|
|
40120
|
+
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
40121
|
+
await unlink2(videoPath);
|
|
40122
|
+
await rename3(extendedPath, videoPath);
|
|
40123
|
+
}
|
|
40124
|
+
result.regeneratedScenes.push(sceneNum);
|
|
40125
|
+
} else {
|
|
40126
|
+
logSceneFailure("Veo", `scene ${sceneNum}`, waitResult);
|
|
40127
|
+
result.failedScenes.push(sceneNum);
|
|
40128
|
+
}
|
|
40129
|
+
} catch (err) {
|
|
40130
|
+
logSceneFailure("Veo", `scene ${sceneNum}`, err);
|
|
40131
|
+
result.failedScenes.push(sceneNum);
|
|
40132
|
+
}
|
|
40133
|
+
} else {
|
|
40134
|
+
result.failedScenes.push(sceneNum);
|
|
40135
|
+
}
|
|
40136
|
+
} else if (options.generator === "kling" || !options.generator) {
|
|
40137
|
+
const kling = new KlingProvider();
|
|
40138
|
+
await kling.initialize({ apiKey: videoApiKey });
|
|
40139
|
+
if (!kling.isConfigured()) {
|
|
40140
|
+
result.failedScenes.push(sceneNum);
|
|
40141
|
+
continue;
|
|
40142
|
+
}
|
|
40143
|
+
const imgbbApiKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
|
|
40144
|
+
let imageUrl;
|
|
40145
|
+
if (imgbbApiKey) {
|
|
40146
|
+
const uploadResult = await uploadToImgbb(imageBuffer, imgbbApiKey);
|
|
40147
|
+
if (uploadResult.success && uploadResult.url) {
|
|
40148
|
+
imageUrl = uploadResult.url;
|
|
40149
|
+
}
|
|
40150
|
+
}
|
|
40151
|
+
const taskResult = await generateVideoWithRetryKling(
|
|
40152
|
+
kling,
|
|
40153
|
+
segment,
|
|
40154
|
+
{
|
|
40155
|
+
duration: videoDuration,
|
|
40156
|
+
aspectRatio: options.aspectRatio || "16:9",
|
|
40157
|
+
referenceImage: imageUrl
|
|
40158
|
+
},
|
|
40159
|
+
maxRetries
|
|
40160
|
+
);
|
|
40161
|
+
if (taskResult) {
|
|
40162
|
+
try {
|
|
40163
|
+
const waitResult = await kling.waitForCompletion(taskResult.taskId, taskResult.type, void 0, 6e5);
|
|
40164
|
+
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
40165
|
+
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
40166
|
+
await writeFile31(videoPath, buffer);
|
|
40167
|
+
await extendVideoToTarget(
|
|
40168
|
+
videoPath,
|
|
40169
|
+
segment.duration,
|
|
40170
|
+
outputDir,
|
|
40171
|
+
`Scene ${sceneNum}`,
|
|
40172
|
+
{
|
|
40173
|
+
kling,
|
|
40174
|
+
videoId: waitResult.videoId,
|
|
40175
|
+
onProgress: options.onProgress
|
|
40176
|
+
}
|
|
40177
|
+
);
|
|
40178
|
+
result.regeneratedScenes.push(sceneNum);
|
|
40179
|
+
} else {
|
|
40180
|
+
logSceneFailure("Kling", `scene ${sceneNum}`, waitResult);
|
|
40181
|
+
result.failedScenes.push(sceneNum);
|
|
40182
|
+
}
|
|
40183
|
+
} catch (err) {
|
|
40184
|
+
logSceneFailure("Kling", `scene ${sceneNum}`, err);
|
|
40185
|
+
result.failedScenes.push(sceneNum);
|
|
40186
|
+
}
|
|
40187
|
+
} else {
|
|
40188
|
+
result.failedScenes.push(sceneNum);
|
|
40189
|
+
}
|
|
40190
|
+
} else {
|
|
40191
|
+
const runway = new RunwayProvider();
|
|
40192
|
+
await runway.initialize({ apiKey: videoApiKey });
|
|
40193
|
+
const ext = extname8(imagePath).toLowerCase().slice(1);
|
|
40194
|
+
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
40195
|
+
const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
40196
|
+
const aspectRatio = options.aspectRatio === "1:1" ? "16:9" : options.aspectRatio || "16:9";
|
|
40197
|
+
const taskResult = await generateVideoWithRetryRunway(
|
|
40198
|
+
runway,
|
|
40199
|
+
segment,
|
|
40200
|
+
referenceImage,
|
|
40201
|
+
{ duration: videoDuration, aspectRatio },
|
|
40202
|
+
maxRetries
|
|
40203
|
+
);
|
|
40204
|
+
if (taskResult) {
|
|
40205
|
+
try {
|
|
40206
|
+
const waitResult = await runway.waitForCompletion(taskResult.taskId, void 0, 3e5);
|
|
40207
|
+
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
40208
|
+
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
40209
|
+
await writeFile31(videoPath, buffer);
|
|
40210
|
+
const targetDuration = segment.duration;
|
|
40211
|
+
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
40212
|
+
if (actualVideoDuration < targetDuration - 0.1) {
|
|
40213
|
+
const extendedPath = resolve34(outputDir, `scene-${sceneNum}-extended.mp4`);
|
|
40214
|
+
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
40215
|
+
await unlink2(videoPath);
|
|
40216
|
+
await rename3(extendedPath, videoPath);
|
|
40217
|
+
}
|
|
40218
|
+
result.regeneratedScenes.push(sceneNum);
|
|
40219
|
+
} else {
|
|
40220
|
+
logSceneFailure("Runway", `scene ${sceneNum}`, waitResult);
|
|
40221
|
+
result.failedScenes.push(sceneNum);
|
|
40222
|
+
}
|
|
40223
|
+
} catch (err) {
|
|
40224
|
+
logSceneFailure("Runway", `scene ${sceneNum}`, err);
|
|
40225
|
+
result.failedScenes.push(sceneNum);
|
|
40226
|
+
}
|
|
40227
|
+
} else {
|
|
40228
|
+
result.failedScenes.push(sceneNum);
|
|
40229
|
+
}
|
|
39951
40230
|
}
|
|
40231
|
+
} else if (!sceneFailed) {
|
|
40232
|
+
result.regeneratedScenes.push(sceneNum);
|
|
39952
40233
|
}
|
|
39953
|
-
console.log();
|
|
39954
|
-
} catch (error) {
|
|
39955
|
-
exitWithError(generalError(error instanceof Error ? error.message : "Video review failed"));
|
|
39956
40234
|
}
|
|
39957
|
-
|
|
40235
|
+
if (storyboardMutated) {
|
|
40236
|
+
let currentTime = 0;
|
|
40237
|
+
for (const segment of segments) {
|
|
40238
|
+
segment.startTime = currentTime;
|
|
40239
|
+
currentTime += segment.duration;
|
|
40240
|
+
}
|
|
40241
|
+
const serialized = storyboardPath.endsWith(".yaml") ? (0, import_yaml5.stringify)({ scenes: segments }, { indent: 2 }) : JSON.stringify(segments, null, 2);
|
|
40242
|
+
await writeFile31(storyboardPath, serialized, "utf-8");
|
|
40243
|
+
}
|
|
40244
|
+
result.success = result.failedScenes.length === 0;
|
|
40245
|
+
return result;
|
|
40246
|
+
} catch (error) {
|
|
40247
|
+
return {
|
|
40248
|
+
...result,
|
|
40249
|
+
error: error instanceof Error ? error.message : String(error)
|
|
40250
|
+
};
|
|
40251
|
+
}
|
|
39958
40252
|
}
|
|
39959
|
-
var
|
|
39960
|
-
|
|
40253
|
+
var import_yaml5;
|
|
40254
|
+
var init_ai_script_pipeline = __esm({
|
|
40255
|
+
"src/commands/ai-script-pipeline.ts"() {
|
|
39961
40256
|
"use strict";
|
|
39962
|
-
|
|
39963
|
-
init_ora();
|
|
40257
|
+
import_yaml5 = __toESM(require_dist(), 1);
|
|
39964
40258
|
init_dist2();
|
|
39965
40259
|
init_api_key();
|
|
39966
|
-
|
|
39967
|
-
|
|
39968
|
-
|
|
40260
|
+
init_config();
|
|
40261
|
+
init_audio();
|
|
40262
|
+
init_ai_helpers();
|
|
40263
|
+
init_video_utils();
|
|
40264
|
+
init_video_providers();
|
|
39969
40265
|
}
|
|
39970
40266
|
});
|
|
39971
40267
|
|
|
39972
40268
|
// src/commands/ai-highlights.ts
|
|
39973
40269
|
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
|
|
40270
|
+
import { resolve as resolve36, dirname as dirname12, basename as basename9, extname as extname9 } from "node:path";
|
|
39975
40271
|
import { existsSync as existsSync28 } from "node:fs";
|
|
39976
40272
|
function filterHighlights(highlights, options) {
|
|
39977
40273
|
let filtered = highlights.filter((h) => h.confidence >= options.threshold);
|
|
@@ -40015,7 +40311,7 @@ async function executeHighlights(options) {
|
|
|
40015
40311
|
if (!existsSync28(absPath)) {
|
|
40016
40312
|
return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
|
|
40017
40313
|
}
|
|
40018
|
-
const ext =
|
|
40314
|
+
const ext = extname9(absPath).toLowerCase();
|
|
40019
40315
|
const videoExtensions = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
|
|
40020
40316
|
const isVideo = videoExtensions.includes(ext);
|
|
40021
40317
|
const targetDuration = options.duration;
|
|
@@ -40352,7 +40648,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
40352
40648
|
};
|
|
40353
40649
|
for (let i = 0; i < selectedHighlights.length; i++) {
|
|
40354
40650
|
const h = selectedHighlights[i];
|
|
40355
|
-
const baseName = basename9(absPath,
|
|
40651
|
+
const baseName = basename9(absPath, extname9(absPath));
|
|
40356
40652
|
const outputPath = resolve36(outputDir, `${baseName}-short-${i + 1}.mp4`);
|
|
40357
40653
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
40358
40654
|
"-v",
|
|
@@ -40451,7 +40747,7 @@ function registerHighlightsCommands(aiCommand) {
|
|
|
40451
40747
|
});
|
|
40452
40748
|
return;
|
|
40453
40749
|
}
|
|
40454
|
-
const ext =
|
|
40750
|
+
const ext = extname9(absPath).toLowerCase();
|
|
40455
40751
|
const videoExtensions = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
|
|
40456
40752
|
const isVideo = videoExtensions.includes(ext);
|
|
40457
40753
|
console.log();
|
|
@@ -40929,11 +41225,11 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
40929
41225
|
let outputPath;
|
|
40930
41226
|
if (shortCount === 1 && options.output) {
|
|
40931
41227
|
outputPath = resolve36(process.cwd(), options.output);
|
|
40932
|
-
if (!
|
|
41228
|
+
if (!extname9(outputPath)) {
|
|
40933
41229
|
outputPath += ".mp4";
|
|
40934
41230
|
}
|
|
40935
41231
|
} else {
|
|
40936
|
-
const baseName = basename9(absPath,
|
|
41232
|
+
const baseName = basename9(absPath, extname9(absPath));
|
|
40937
41233
|
outputPath = resolve36(outputDir, `${baseName}-short-${i + 1}.mp4`);
|
|
40938
41234
|
}
|
|
40939
41235
|
const parentDir = dirname12(outputPath);
|
|
@@ -51819,7 +52115,7 @@ var init_scene_audio_scan = __esm({
|
|
|
51819
52115
|
|
|
51820
52116
|
// src/commands/_shared/scene-audio-mux.ts
|
|
51821
52117
|
import { rename as rename4, unlink as unlink3 } from "node:fs/promises";
|
|
51822
|
-
import { resolve as resolve45, dirname as dirname15, extname as
|
|
52118
|
+
import { resolve as resolve45, dirname as dirname15, extname as extname12, basename as basename16 } from "node:path";
|
|
51823
52119
|
function buildAudioMuxFilter(audios) {
|
|
51824
52120
|
if (audios.length === 0) return null;
|
|
51825
52121
|
const labels = [];
|
|
@@ -51878,7 +52174,7 @@ async function muxAudioIntoVideo(opts) {
|
|
|
51878
52174
|
if (!filter) {
|
|
51879
52175
|
return { success: true, outputPath: opts.videoPath, audioCount: 0 };
|
|
51880
52176
|
}
|
|
51881
|
-
const ext =
|
|
52177
|
+
const ext = extname12(opts.videoPath) || `.${opts.format}`;
|
|
51882
52178
|
const tmpPath = resolve45(
|
|
51883
52179
|
dirname15(opts.videoPath),
|
|
51884
52180
|
`.${basename16(opts.videoPath, ext)}.muxing${ext}`
|
|
@@ -52530,18 +52826,29 @@ async function executeVideoGenerate(options) {
|
|
|
52530
52826
|
runway: "RUNWAY_API_SECRET",
|
|
52531
52827
|
kling: "KLING_API_KEY",
|
|
52532
52828
|
veo: "GOOGLE_API_KEY",
|
|
52533
|
-
seedance: "
|
|
52534
|
-
fal: "
|
|
52829
|
+
seedance: "FAL_API_KEY",
|
|
52830
|
+
fal: "FAL_API_KEY"
|
|
52535
52831
|
};
|
|
52536
|
-
const
|
|
52832
|
+
const envKey = envKeyMap[provider] || "";
|
|
52833
|
+
const key = apiKey || (hasApiKey(envKey) ? process.env[envKey] || (envKey === "FAL_API_KEY" ? process.env.FAL_KEY : void 0) : void 0);
|
|
52537
52834
|
if (!key) return { success: false, error: `${envKeyMap[provider]} required for ${provider}` };
|
|
52538
52835
|
let referenceImage;
|
|
52836
|
+
let referenceImageBuffer;
|
|
52837
|
+
let referenceImageMimeType;
|
|
52539
52838
|
if (image) {
|
|
52540
52839
|
const imagePath = resolve53(process.cwd(), image);
|
|
52541
52840
|
const imageBuffer = await readFile35(imagePath);
|
|
52542
52841
|
const ext = image.toLowerCase().split(".").pop();
|
|
52543
|
-
const mimeTypes2 = {
|
|
52842
|
+
const mimeTypes2 = {
|
|
52843
|
+
jpg: "image/jpeg",
|
|
52844
|
+
jpeg: "image/jpeg",
|
|
52845
|
+
png: "image/png",
|
|
52846
|
+
gif: "image/gif",
|
|
52847
|
+
webp: "image/webp"
|
|
52848
|
+
};
|
|
52544
52849
|
const mimeType = mimeTypes2[ext || "png"] || "image/png";
|
|
52850
|
+
referenceImageBuffer = imageBuffer;
|
|
52851
|
+
referenceImageMimeType = mimeType;
|
|
52545
52852
|
referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
52546
52853
|
}
|
|
52547
52854
|
if (provider === "seedance" || provider === "fal") {
|
|
@@ -52549,12 +52856,12 @@ async function executeVideoGenerate(options) {
|
|
|
52549
52856
|
await fal.initialize({ apiKey: key });
|
|
52550
52857
|
let falImage = referenceImage;
|
|
52551
52858
|
if (falImage && falImage.startsWith("data:")) {
|
|
52552
|
-
const
|
|
52553
|
-
|
|
52554
|
-
|
|
52555
|
-
|
|
52556
|
-
|
|
52557
|
-
falImage =
|
|
52859
|
+
const uploadHost = await resolveUploadHost();
|
|
52860
|
+
const upload = await uploadHost.uploadImage(referenceImageBuffer, {
|
|
52861
|
+
filename: image,
|
|
52862
|
+
mimeType: referenceImageMimeType
|
|
52863
|
+
});
|
|
52864
|
+
falImage = upload.url;
|
|
52558
52865
|
}
|
|
52559
52866
|
const model = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
|
|
52560
52867
|
const result = await fal.generateVideo(prompt3, {
|
|
@@ -52565,14 +52872,22 @@ async function executeVideoGenerate(options) {
|
|
|
52565
52872
|
negativePrompt: negative,
|
|
52566
52873
|
model
|
|
52567
52874
|
});
|
|
52568
|
-
if (result.status === "failed")
|
|
52875
|
+
if (result.status === "failed")
|
|
52876
|
+
return { success: false, error: result.error || "Seedance generation failed" };
|
|
52569
52877
|
let outputPath;
|
|
52570
52878
|
if (output && result.videoUrl) {
|
|
52571
52879
|
const buffer = await downloadVideo(result.videoUrl, key);
|
|
52572
52880
|
outputPath = resolve53(process.cwd(), output);
|
|
52573
52881
|
await writeFile39(outputPath, buffer);
|
|
52574
52882
|
}
|
|
52575
|
-
return {
|
|
52883
|
+
return {
|
|
52884
|
+
success: true,
|
|
52885
|
+
taskId: result.id,
|
|
52886
|
+
status: "completed",
|
|
52887
|
+
videoUrl: result.videoUrl,
|
|
52888
|
+
outputPath,
|
|
52889
|
+
provider: "seedance"
|
|
52890
|
+
};
|
|
52576
52891
|
} else if (provider === "runway") {
|
|
52577
52892
|
const runway = new RunwayProvider();
|
|
52578
52893
|
await runway.initialize({ apiKey: key });
|
|
@@ -52583,30 +52898,41 @@ async function executeVideoGenerate(options) {
|
|
|
52583
52898
|
aspectRatio: ratio,
|
|
52584
52899
|
seed
|
|
52585
52900
|
});
|
|
52586
|
-
if (result.status === "failed")
|
|
52587
|
-
|
|
52901
|
+
if (result.status === "failed")
|
|
52902
|
+
return { success: false, error: result.error || "Runway generation failed" };
|
|
52903
|
+
if (!wait)
|
|
52904
|
+
return { success: true, taskId: result.id, status: "processing", provider: "runway" };
|
|
52588
52905
|
const finalResult = await runway.waitForCompletion(result.id, () => {
|
|
52589
52906
|
}, 3e5);
|
|
52590
|
-
if (finalResult.status !== "completed")
|
|
52907
|
+
if (finalResult.status !== "completed")
|
|
52908
|
+
return { success: false, error: finalResult.error || "Runway generation failed" };
|
|
52591
52909
|
let outputPath;
|
|
52592
52910
|
if (output && finalResult.videoUrl) {
|
|
52593
52911
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52594
52912
|
outputPath = resolve53(process.cwd(), output);
|
|
52595
52913
|
await writeFile39(outputPath, buffer);
|
|
52596
52914
|
}
|
|
52597
|
-
return {
|
|
52915
|
+
return {
|
|
52916
|
+
success: true,
|
|
52917
|
+
taskId: result.id,
|
|
52918
|
+
status: "completed",
|
|
52919
|
+
videoUrl: finalResult.videoUrl,
|
|
52920
|
+
duration: finalResult.duration,
|
|
52921
|
+
outputPath,
|
|
52922
|
+
provider: "runway"
|
|
52923
|
+
};
|
|
52598
52924
|
} else if (provider === "kling") {
|
|
52599
52925
|
const kling = new KlingProvider();
|
|
52600
52926
|
await kling.initialize({ apiKey: key });
|
|
52601
52927
|
if (!kling.isConfigured()) return { success: false, error: "Invalid Kling API key format" };
|
|
52602
52928
|
let klingImage = referenceImage;
|
|
52603
52929
|
if (klingImage && klingImage.startsWith("data:")) {
|
|
52604
|
-
const
|
|
52605
|
-
|
|
52606
|
-
|
|
52607
|
-
|
|
52608
|
-
|
|
52609
|
-
klingImage =
|
|
52930
|
+
const uploadHost = await resolveUploadHost();
|
|
52931
|
+
const upload = await uploadHost.uploadImage(referenceImageBuffer, {
|
|
52932
|
+
filename: image,
|
|
52933
|
+
mimeType: referenceImageMimeType
|
|
52934
|
+
});
|
|
52935
|
+
klingImage = upload.url;
|
|
52610
52936
|
}
|
|
52611
52937
|
const result = await kling.generateVideo(prompt3, {
|
|
52612
52938
|
prompt: prompt3,
|
|
@@ -52616,23 +52942,38 @@ async function executeVideoGenerate(options) {
|
|
|
52616
52942
|
negativePrompt: negative,
|
|
52617
52943
|
mode
|
|
52618
52944
|
});
|
|
52619
|
-
if (result.status === "failed")
|
|
52945
|
+
if (result.status === "failed")
|
|
52946
|
+
return { success: false, error: result.error || "Kling generation failed" };
|
|
52620
52947
|
const taskType = referenceImage ? "image2video" : "text2video";
|
|
52621
|
-
if (!wait)
|
|
52948
|
+
if (!wait)
|
|
52949
|
+
return { success: true, taskId: result.id, status: "processing", provider: "kling" };
|
|
52622
52950
|
const finalResult = await kling.waitForCompletion(result.id, taskType, () => {
|
|
52623
52951
|
}, 6e5);
|
|
52624
|
-
if (finalResult.status !== "completed")
|
|
52952
|
+
if (finalResult.status !== "completed")
|
|
52953
|
+
return { success: false, error: finalResult.error || "Kling generation failed" };
|
|
52625
52954
|
let outputPath;
|
|
52626
52955
|
if (output && finalResult.videoUrl) {
|
|
52627
52956
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52628
52957
|
outputPath = resolve53(process.cwd(), output);
|
|
52629
52958
|
await writeFile39(outputPath, buffer);
|
|
52630
52959
|
}
|
|
52631
|
-
return {
|
|
52960
|
+
return {
|
|
52961
|
+
success: true,
|
|
52962
|
+
taskId: result.id,
|
|
52963
|
+
status: "completed",
|
|
52964
|
+
videoUrl: finalResult.videoUrl,
|
|
52965
|
+
duration: finalResult.duration,
|
|
52966
|
+
outputPath,
|
|
52967
|
+
provider: "kling"
|
|
52968
|
+
};
|
|
52632
52969
|
} else if (provider === "veo") {
|
|
52633
52970
|
const gemini = new GeminiProvider();
|
|
52634
52971
|
await gemini.initialize({ apiKey: key });
|
|
52635
|
-
const veoModelMap = {
|
|
52972
|
+
const veoModelMap = {
|
|
52973
|
+
"3.0": "veo-3.0-generate-preview",
|
|
52974
|
+
"3.1": "veo-3.1-generate-preview",
|
|
52975
|
+
"3.1-fast": "veo-3.1-fast-generate-preview"
|
|
52976
|
+
};
|
|
52636
52977
|
const model = veoModelMap[veoModel] || "veo-3.1-fast-generate-preview";
|
|
52637
52978
|
const veoDuration = duration <= 6 ? 6 : 8;
|
|
52638
52979
|
const result = await gemini.generateVideo(prompt3, {
|
|
@@ -52644,18 +52985,27 @@ async function executeVideoGenerate(options) {
|
|
|
52644
52985
|
negativePrompt: negative,
|
|
52645
52986
|
resolution
|
|
52646
52987
|
});
|
|
52647
|
-
if (result.status === "failed")
|
|
52988
|
+
if (result.status === "failed")
|
|
52989
|
+
return { success: false, error: result.error || "Veo generation failed" };
|
|
52648
52990
|
if (!wait) return { success: true, taskId: result.id, status: "processing", provider: "veo" };
|
|
52649
52991
|
const finalResult = await gemini.waitForVideoCompletion(result.id, () => {
|
|
52650
52992
|
}, 3e5);
|
|
52651
|
-
if (finalResult.status !== "completed")
|
|
52993
|
+
if (finalResult.status !== "completed")
|
|
52994
|
+
return { success: false, error: finalResult.error || "Veo generation failed" };
|
|
52652
52995
|
let outputPath;
|
|
52653
52996
|
if (output && finalResult.videoUrl) {
|
|
52654
52997
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52655
52998
|
outputPath = resolve53(process.cwd(), output);
|
|
52656
52999
|
await writeFile39(outputPath, buffer);
|
|
52657
53000
|
}
|
|
52658
|
-
return {
|
|
53001
|
+
return {
|
|
53002
|
+
success: true,
|
|
53003
|
+
taskId: result.id,
|
|
53004
|
+
status: "completed",
|
|
53005
|
+
videoUrl: finalResult.videoUrl,
|
|
53006
|
+
outputPath,
|
|
53007
|
+
provider: "veo"
|
|
53008
|
+
};
|
|
52659
53009
|
} else if (provider === "grok") {
|
|
52660
53010
|
const grok = new GrokProvider();
|
|
52661
53011
|
await grok.initialize({ apiKey: key });
|
|
@@ -52665,28 +53015,52 @@ async function executeVideoGenerate(options) {
|
|
|
52665
53015
|
duration,
|
|
52666
53016
|
aspectRatio: ratio
|
|
52667
53017
|
});
|
|
52668
|
-
if (result.status === "failed")
|
|
52669
|
-
|
|
53018
|
+
if (result.status === "failed")
|
|
53019
|
+
return { success: false, error: result.error || "Grok generation failed" };
|
|
53020
|
+
if (!wait)
|
|
53021
|
+
return { success: true, taskId: result.id, status: "processing", provider: "grok" };
|
|
52670
53022
|
const finalResult = await grok.waitForCompletion(result.id, () => {
|
|
52671
53023
|
}, 3e5);
|
|
52672
|
-
if (finalResult.status !== "completed")
|
|
53024
|
+
if (finalResult.status !== "completed")
|
|
53025
|
+
return { success: false, error: finalResult.error || "Grok generation failed" };
|
|
52673
53026
|
let outputPath;
|
|
52674
53027
|
if (output && finalResult.videoUrl) {
|
|
52675
53028
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52676
53029
|
outputPath = resolve53(process.cwd(), output);
|
|
52677
53030
|
await writeFile39(outputPath, buffer);
|
|
52678
53031
|
}
|
|
52679
|
-
return {
|
|
53032
|
+
return {
|
|
53033
|
+
success: true,
|
|
53034
|
+
taskId: result.id,
|
|
53035
|
+
status: "completed",
|
|
53036
|
+
videoUrl: finalResult.videoUrl,
|
|
53037
|
+
duration: finalResult.duration,
|
|
53038
|
+
outputPath,
|
|
53039
|
+
provider: "grok"
|
|
53040
|
+
};
|
|
52680
53041
|
}
|
|
52681
53042
|
return { success: false, error: `Unsupported provider: ${provider}` };
|
|
52682
53043
|
} catch (error) {
|
|
52683
|
-
return {
|
|
53044
|
+
return {
|
|
53045
|
+
success: false,
|
|
53046
|
+
error: `Video generation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
53047
|
+
};
|
|
52684
53048
|
}
|
|
52685
53049
|
}
|
|
52686
53050
|
async function executeVideoStatus(options) {
|
|
52687
|
-
const {
|
|
53051
|
+
const {
|
|
53052
|
+
taskId,
|
|
53053
|
+
provider = "runway",
|
|
53054
|
+
taskType = "text2video",
|
|
53055
|
+
wait = false,
|
|
53056
|
+
output,
|
|
53057
|
+
apiKey
|
|
53058
|
+
} = options;
|
|
52688
53059
|
try {
|
|
52689
|
-
const envKeyMap = {
|
|
53060
|
+
const envKeyMap = {
|
|
53061
|
+
runway: "RUNWAY_API_SECRET",
|
|
53062
|
+
kling: "KLING_API_KEY"
|
|
53063
|
+
};
|
|
52690
53064
|
const key = apiKey || process.env[envKeyMap[provider] || ""];
|
|
52691
53065
|
if (!key) return { success: false, error: `${envKeyMap[provider]} required` };
|
|
52692
53066
|
if (provider === "runway") {
|
|
@@ -52703,7 +53077,14 @@ async function executeVideoStatus(options) {
|
|
|
52703
53077
|
outputPath = resolve53(process.cwd(), output);
|
|
52704
53078
|
await writeFile39(outputPath, buffer);
|
|
52705
53079
|
}
|
|
52706
|
-
return {
|
|
53080
|
+
return {
|
|
53081
|
+
success: true,
|
|
53082
|
+
taskId,
|
|
53083
|
+
status: result.status,
|
|
53084
|
+
progress: result.progress,
|
|
53085
|
+
videoUrl: result.videoUrl,
|
|
53086
|
+
outputPath
|
|
53087
|
+
};
|
|
52707
53088
|
} else if (provider === "kling") {
|
|
52708
53089
|
const kling = new KlingProvider();
|
|
52709
53090
|
await kling.initialize({ apiKey: key });
|
|
@@ -52718,11 +53099,21 @@ async function executeVideoStatus(options) {
|
|
|
52718
53099
|
outputPath = resolve53(process.cwd(), output);
|
|
52719
53100
|
await writeFile39(outputPath, buffer);
|
|
52720
53101
|
}
|
|
52721
|
-
return {
|
|
53102
|
+
return {
|
|
53103
|
+
success: true,
|
|
53104
|
+
taskId,
|
|
53105
|
+
status: result.status,
|
|
53106
|
+
videoUrl: result.videoUrl,
|
|
53107
|
+
duration: result.duration,
|
|
53108
|
+
outputPath
|
|
53109
|
+
};
|
|
52722
53110
|
}
|
|
52723
53111
|
return { success: false, error: `Unsupported provider: ${provider}` };
|
|
52724
53112
|
} catch (error) {
|
|
52725
|
-
return {
|
|
53113
|
+
return {
|
|
53114
|
+
success: false,
|
|
53115
|
+
error: `Status check failed: ${error instanceof Error ? error.message : String(error)}`
|
|
53116
|
+
};
|
|
52726
53117
|
}
|
|
52727
53118
|
}
|
|
52728
53119
|
async function executeVideoCancel(options) {
|
|
@@ -52735,11 +53126,24 @@ async function executeVideoCancel(options) {
|
|
|
52735
53126
|
const success = await runway.cancelGeneration(taskId);
|
|
52736
53127
|
return { success };
|
|
52737
53128
|
} catch (error) {
|
|
52738
|
-
return {
|
|
53129
|
+
return {
|
|
53130
|
+
success: false,
|
|
53131
|
+
error: `Cancel failed: ${error instanceof Error ? error.message : String(error)}`
|
|
53132
|
+
};
|
|
52739
53133
|
}
|
|
52740
53134
|
}
|
|
52741
53135
|
async function executeVideoExtend(options) {
|
|
52742
|
-
const {
|
|
53136
|
+
const {
|
|
53137
|
+
videoId,
|
|
53138
|
+
provider = "kling",
|
|
53139
|
+
prompt: prompt3,
|
|
53140
|
+
duration = 5,
|
|
53141
|
+
negative,
|
|
53142
|
+
veoModel = "3.1",
|
|
53143
|
+
output,
|
|
53144
|
+
wait = true,
|
|
53145
|
+
apiKey
|
|
53146
|
+
} = options;
|
|
52743
53147
|
try {
|
|
52744
53148
|
if (provider === "kling") {
|
|
52745
53149
|
const key = apiKey || process.env.KLING_API_KEY;
|
|
@@ -52752,53 +53156,78 @@ async function executeVideoExtend(options) {
|
|
|
52752
53156
|
negativePrompt: negative,
|
|
52753
53157
|
duration: String(duration)
|
|
52754
53158
|
});
|
|
52755
|
-
if (result.status === "failed")
|
|
53159
|
+
if (result.status === "failed")
|
|
53160
|
+
return { success: false, error: result.error || "Kling extension failed" };
|
|
52756
53161
|
if (!wait) return { success: true, taskId: result.id, status: "processing" };
|
|
52757
53162
|
const finalResult = await kling.waitForExtendCompletion(result.id, () => {
|
|
52758
53163
|
}, 6e5);
|
|
52759
|
-
if (finalResult.status !== "completed")
|
|
53164
|
+
if (finalResult.status !== "completed")
|
|
53165
|
+
return { success: false, error: finalResult.error || "Kling extension failed" };
|
|
52760
53166
|
let outputPath;
|
|
52761
53167
|
if (output && finalResult.videoUrl) {
|
|
52762
53168
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52763
53169
|
outputPath = resolve53(process.cwd(), output);
|
|
52764
53170
|
await writeFile39(outputPath, buffer);
|
|
52765
53171
|
}
|
|
52766
|
-
return {
|
|
53172
|
+
return {
|
|
53173
|
+
success: true,
|
|
53174
|
+
taskId: result.id,
|
|
53175
|
+
status: "completed",
|
|
53176
|
+
videoUrl: finalResult.videoUrl,
|
|
53177
|
+
duration: finalResult.duration,
|
|
53178
|
+
outputPath
|
|
53179
|
+
};
|
|
52767
53180
|
} else if (provider === "veo") {
|
|
52768
53181
|
const key = apiKey || process.env.GOOGLE_API_KEY;
|
|
52769
53182
|
if (!key) return { success: false, error: "GOOGLE_API_KEY required" };
|
|
52770
53183
|
const gemini = new GeminiProvider();
|
|
52771
53184
|
await gemini.initialize({ apiKey: key });
|
|
52772
|
-
const veoModelMap = {
|
|
53185
|
+
const veoModelMap = {
|
|
53186
|
+
"3.0": "veo-3.0-generate-preview",
|
|
53187
|
+
"3.1": "veo-3.1-generate-preview",
|
|
53188
|
+
"3.1-fast": "veo-3.1-fast-generate-preview"
|
|
53189
|
+
};
|
|
52773
53190
|
const model = veoModelMap[veoModel] || "veo-3.1-generate-preview";
|
|
52774
53191
|
const result = await gemini.extendVideo(videoId, prompt3, {
|
|
52775
53192
|
duration,
|
|
52776
53193
|
model
|
|
52777
53194
|
});
|
|
52778
|
-
if (result.status === "failed")
|
|
53195
|
+
if (result.status === "failed")
|
|
53196
|
+
return { success: false, error: result.error || "Veo extension failed" };
|
|
52779
53197
|
if (!wait) return { success: true, taskId: result.id, status: "processing" };
|
|
52780
53198
|
const finalResult = await gemini.waitForVideoCompletion(result.id, () => {
|
|
52781
53199
|
}, 3e5);
|
|
52782
|
-
if (finalResult.status !== "completed")
|
|
53200
|
+
if (finalResult.status !== "completed")
|
|
53201
|
+
return { success: false, error: finalResult.error || "Veo extension failed" };
|
|
52783
53202
|
let outputPath;
|
|
52784
53203
|
if (output && finalResult.videoUrl) {
|
|
52785
53204
|
const buffer = await downloadVideo(finalResult.videoUrl, key);
|
|
52786
53205
|
outputPath = resolve53(process.cwd(), output);
|
|
52787
53206
|
await writeFile39(outputPath, buffer);
|
|
52788
53207
|
}
|
|
52789
|
-
return {
|
|
53208
|
+
return {
|
|
53209
|
+
success: true,
|
|
53210
|
+
taskId: result.id,
|
|
53211
|
+
status: "completed",
|
|
53212
|
+
videoUrl: finalResult.videoUrl,
|
|
53213
|
+
outputPath
|
|
53214
|
+
};
|
|
52790
53215
|
}
|
|
52791
53216
|
return { success: false, error: `Unsupported provider: ${provider}` };
|
|
52792
53217
|
} catch (error) {
|
|
52793
|
-
return {
|
|
53218
|
+
return {
|
|
53219
|
+
success: false,
|
|
53220
|
+
error: `Video extension failed: ${error instanceof Error ? error.message : String(error)}`
|
|
53221
|
+
};
|
|
52794
53222
|
}
|
|
52795
53223
|
}
|
|
52796
53224
|
var init_ai_video = __esm({
|
|
52797
53225
|
"src/commands/ai-video.ts"() {
|
|
52798
53226
|
"use strict";
|
|
52799
53227
|
init_dist2();
|
|
52800
|
-
|
|
53228
|
+
init_upload_host();
|
|
52801
53229
|
init_ai_helpers();
|
|
53230
|
+
init_api_key();
|
|
52802
53231
|
}
|
|
52803
53232
|
});
|
|
52804
53233
|
|
|
@@ -52811,7 +53240,7 @@ __export(ai_audio_exports, {
|
|
|
52811
53240
|
executeTranscribe: () => executeTranscribe,
|
|
52812
53241
|
executeVoiceClone: () => executeVoiceClone
|
|
52813
53242
|
});
|
|
52814
|
-
import { resolve as resolve54, dirname as dirname19, basename as basename18, extname as
|
|
53243
|
+
import { resolve as resolve54, dirname as dirname19, basename as basename18, extname as extname13 } from "node:path";
|
|
52815
53244
|
import { readFile as readFile36, writeFile as writeFile40 } from "node:fs/promises";
|
|
52816
53245
|
import { existsSync as existsSync38 } from "node:fs";
|
|
52817
53246
|
async function executeTranscribe(options) {
|
|
@@ -52908,7 +53337,7 @@ async function executeDub(options) {
|
|
|
52908
53337
|
if (!openaiKey) return { success: false, error: "OPENAI_API_KEY required for Whisper transcription" };
|
|
52909
53338
|
if (!anthropicKey) return { success: false, error: "ANTHROPIC_API_KEY required for Claude translation" };
|
|
52910
53339
|
if (!analyzeOnly && !elevenlabsKey) return { success: false, error: "ELEVENLABS_API_KEY required for TTS" };
|
|
52911
|
-
const ext =
|
|
53340
|
+
const ext = extname13(absPath).toLowerCase();
|
|
52912
53341
|
const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
|
|
52913
53342
|
let audioPath = absPath;
|
|
52914
53343
|
if (isVideo) {
|
|
@@ -52993,8 +53422,8 @@ ${segmentTexts}`,
|
|
|
52993
53422
|
}
|
|
52994
53423
|
}
|
|
52995
53424
|
const combinedBuffer = Buffer.concat(dubbedBuffers);
|
|
52996
|
-
const outputExt = isVideo ? ".mp3" :
|
|
52997
|
-
const defaultOutputPath2 = resolve54(dirname19(absPath), `${basename18(absPath,
|
|
53425
|
+
const outputExt = isVideo ? ".mp3" : extname13(absPath);
|
|
53426
|
+
const defaultOutputPath2 = resolve54(dirname19(absPath), `${basename18(absPath, extname13(absPath))}-${language}${outputExt}`);
|
|
52998
53427
|
const finalOutputPath = resolve54(process.cwd(), output || defaultOutputPath2);
|
|
52999
53428
|
await writeFile40(finalOutputPath, combinedBuffer);
|
|
53000
53429
|
if (isVideo && audioPath !== absPath) {
|
|
@@ -53031,7 +53460,7 @@ async function executeDuck(options) {
|
|
|
53031
53460
|
const absVoicePath = resolve54(process.cwd(), voicePath);
|
|
53032
53461
|
if (!existsSync38(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
|
|
53033
53462
|
if (!existsSync38(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
|
|
53034
|
-
const defaultOutput = resolve54(dirname19(absMusicPath), `${basename18(absMusicPath,
|
|
53463
|
+
const defaultOutput = resolve54(dirname19(absMusicPath), `${basename18(absMusicPath, extname13(absMusicPath))}-ducked${extname13(absMusicPath)}`);
|
|
53035
53464
|
const outputPath = resolve54(process.cwd(), output || defaultOutput);
|
|
53036
53465
|
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
53466
|
await execSafe("ffmpeg", [
|
|
@@ -59608,11 +60037,17 @@ var init_generate2 = __esm({
|
|
|
59608
60037
|
}),
|
|
59609
60038
|
async execute(args) {
|
|
59610
60039
|
const result = await executeMotion(args);
|
|
59611
|
-
if (!result.success)
|
|
60040
|
+
if (!result.success)
|
|
60041
|
+
return { success: false, error: result.error ?? "Motion generation failed" };
|
|
59612
60042
|
const out = result.compositedPath ?? result.renderedPath ?? result.codePath;
|
|
59613
60043
|
return {
|
|
59614
60044
|
success: true,
|
|
59615
|
-
data: {
|
|
60045
|
+
data: {
|
|
60046
|
+
codePath: result.codePath,
|
|
60047
|
+
renderedPath: result.renderedPath,
|
|
60048
|
+
compositedPath: result.compositedPath,
|
|
60049
|
+
componentName: result.componentName
|
|
60050
|
+
},
|
|
59616
60051
|
humanLines: [`\u2705 Motion generated \u2192 ${out}`]
|
|
59617
60052
|
};
|
|
59618
60053
|
}
|
|
@@ -59676,7 +60111,9 @@ var init_generate2 = __esm({
|
|
|
59676
60111
|
return {
|
|
59677
60112
|
success: true,
|
|
59678
60113
|
data: { outputPath: result.outputPath, provider: result.provider, duration: result.duration },
|
|
59679
|
-
humanLines: [
|
|
60114
|
+
humanLines: [
|
|
60115
|
+
`\u2705 Music${result.provider ? ` (${result.provider})` : ""} \u2192 ${result.outputPath ?? "(async)"}`
|
|
60116
|
+
]
|
|
59680
60117
|
};
|
|
59681
60118
|
}
|
|
59682
60119
|
});
|
|
@@ -59693,7 +60130,12 @@ var init_generate2 = __esm({
|
|
|
59693
60130
|
if (!result.success) return { success: false, error: result.error ?? "Music status failed" };
|
|
59694
60131
|
return {
|
|
59695
60132
|
success: true,
|
|
59696
|
-
data: {
|
|
60133
|
+
data: {
|
|
60134
|
+
taskId: result.taskId,
|
|
60135
|
+
status: result.status,
|
|
60136
|
+
audioUrl: result.audioUrl,
|
|
60137
|
+
error: result.error
|
|
60138
|
+
},
|
|
59697
60139
|
humanLines: [`Music task ${result.taskId}: ${result.status}`]
|
|
59698
60140
|
};
|
|
59699
60141
|
}
|
|
@@ -59705,7 +60147,9 @@ var init_generate2 = __esm({
|
|
|
59705
60147
|
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
60148
|
schema: external_exports.object({
|
|
59707
60149
|
prompt: external_exports.string().describe("Image description prompt"),
|
|
59708
|
-
provider: external_exports.enum(["gemini", "openai", "grok"]).optional().describe(
|
|
60150
|
+
provider: external_exports.enum(["gemini", "openai", "grok"]).optional().describe(
|
|
60151
|
+
"Image provider (default: openai when OPENAI_API_KEY is configured, otherwise first configured provider)"
|
|
60152
|
+
),
|
|
59709
60153
|
output: external_exports.string().optional().describe("Output file path"),
|
|
59710
60154
|
size: external_exports.string().optional().describe("Image size for OpenAI (1024x1024, 1536x1024, 1024x1536)"),
|
|
59711
60155
|
ratio: external_exports.string().optional().describe("Aspect ratio for Gemini (1:1, 16:9, 9:16, 4:3, 3:4, etc.)"),
|
|
@@ -59715,10 +60159,16 @@ var init_generate2 = __esm({
|
|
|
59715
60159
|
}),
|
|
59716
60160
|
async execute(args) {
|
|
59717
60161
|
const result = await executeImageGenerate(args);
|
|
59718
|
-
if (!result.success)
|
|
60162
|
+
if (!result.success)
|
|
60163
|
+
return { success: false, error: result.error ?? "Image generation failed" };
|
|
59719
60164
|
return {
|
|
59720
60165
|
success: true,
|
|
59721
|
-
data: {
|
|
60166
|
+
data: {
|
|
60167
|
+
outputPath: result.outputPath,
|
|
60168
|
+
provider: result.provider,
|
|
60169
|
+
model: result.model,
|
|
60170
|
+
imageCount: result.images?.length
|
|
60171
|
+
},
|
|
59722
60172
|
humanLines: [`\u2705 Image (${result.provider}) \u2192 ${result.outputPath}`]
|
|
59723
60173
|
};
|
|
59724
60174
|
}
|
|
@@ -59740,7 +60190,9 @@ var init_generate2 = __esm({
|
|
|
59740
60190
|
return {
|
|
59741
60191
|
success: true,
|
|
59742
60192
|
data: { segmentCount: result.segmentCount, outputPath: result.outputPath },
|
|
59743
|
-
humanLines: [
|
|
60193
|
+
humanLines: [
|
|
60194
|
+
`\u2705 Storyboard: ${result.segmentCount} segments${result.outputPath ? ` \u2192 ${result.outputPath}` : ""}`
|
|
60195
|
+
]
|
|
59744
60196
|
};
|
|
59745
60197
|
}
|
|
59746
60198
|
});
|
|
@@ -59759,7 +60211,11 @@ var init_generate2 = __esm({
|
|
|
59759
60211
|
if (!result.success) return { success: false, error: result.error ?? "Background failed" };
|
|
59760
60212
|
return {
|
|
59761
60213
|
success: true,
|
|
59762
|
-
data: {
|
|
60214
|
+
data: {
|
|
60215
|
+
imageUrl: result.imageUrl,
|
|
60216
|
+
outputPath: result.outputPath,
|
|
60217
|
+
revisedPrompt: result.revisedPrompt
|
|
60218
|
+
},
|
|
59763
60219
|
humanLines: [`\u2705 Background \u2192 ${result.outputPath ?? result.imageUrl}`]
|
|
59764
60220
|
};
|
|
59765
60221
|
}
|
|
@@ -59817,7 +60273,9 @@ var init_generate2 = __esm({
|
|
|
59817
60273
|
outputPath: result.outputPath,
|
|
59818
60274
|
provider: result.provider
|
|
59819
60275
|
},
|
|
59820
|
-
humanLines: [
|
|
60276
|
+
humanLines: [
|
|
60277
|
+
`\u2705 Video (${result.provider}, ${result.status})${result.outputPath ? ` \u2192 ${result.outputPath}` : ""}`
|
|
60278
|
+
]
|
|
59821
60279
|
};
|
|
59822
60280
|
}
|
|
59823
60281
|
});
|
|
@@ -59838,8 +60296,16 @@ var init_generate2 = __esm({
|
|
|
59838
60296
|
if (!result.success) return { success: false, error: result.error ?? "Status check failed" };
|
|
59839
60297
|
return {
|
|
59840
60298
|
success: true,
|
|
59841
|
-
data: {
|
|
59842
|
-
|
|
60299
|
+
data: {
|
|
60300
|
+
taskId: result.taskId,
|
|
60301
|
+
status: result.status,
|
|
60302
|
+
progress: result.progress,
|
|
60303
|
+
videoUrl: result.videoUrl,
|
|
60304
|
+
outputPath: result.outputPath
|
|
60305
|
+
},
|
|
60306
|
+
humanLines: [
|
|
60307
|
+
`Task ${result.taskId}: ${result.status}${result.progress !== void 0 ? ` (${result.progress}%)` : ""}`
|
|
60308
|
+
]
|
|
59843
60309
|
};
|
|
59844
60310
|
}
|
|
59845
60311
|
});
|
|
@@ -59881,8 +60347,16 @@ var init_generate2 = __esm({
|
|
|
59881
60347
|
if (!result.success) return { success: false, error: result.error ?? "Extend failed" };
|
|
59882
60348
|
return {
|
|
59883
60349
|
success: true,
|
|
59884
|
-
data: {
|
|
59885
|
-
|
|
60350
|
+
data: {
|
|
60351
|
+
taskId: result.taskId,
|
|
60352
|
+
status: result.status,
|
|
60353
|
+
videoUrl: result.videoUrl,
|
|
60354
|
+
duration: result.duration,
|
|
60355
|
+
outputPath: result.outputPath
|
|
60356
|
+
},
|
|
60357
|
+
humanLines: [
|
|
60358
|
+
`\u2705 Video extended (${result.status})${result.outputPath ? ` \u2192 ${result.outputPath}` : ""}`
|
|
60359
|
+
]
|
|
59886
60360
|
};
|
|
59887
60361
|
}
|
|
59888
60362
|
});
|
|
@@ -61813,7 +62287,7 @@ var init_export2 = __esm({
|
|
|
61813
62287
|
|
|
61814
62288
|
// src/tools/manifest/agent-only.ts
|
|
61815
62289
|
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
|
|
62290
|
+
import { resolve as resolve63, join as join16, basename as basename22, extname as extname14 } from "node:path";
|
|
61817
62291
|
function matchPattern(filename, pattern) {
|
|
61818
62292
|
const regex2 = new RegExp(
|
|
61819
62293
|
"^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
|
|
@@ -61832,7 +62306,7 @@ function formatSize(bytes) {
|
|
|
61832
62306
|
return `${size.toFixed(1)}${units2[unit]}`;
|
|
61833
62307
|
}
|
|
61834
62308
|
function detectMediaType4(filePath) {
|
|
61835
|
-
const ext =
|
|
62309
|
+
const ext = extname14(filePath).toLowerCase();
|
|
61836
62310
|
if ([".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"].includes(ext)) return "video";
|
|
61837
62311
|
if ([".mp3", ".wav", ".aac", ".flac", ".ogg", ".m4a"].includes(ext)) return "audio";
|
|
61838
62312
|
if ([".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"].includes(ext)) return "image";
|
|
@@ -61859,7 +62333,7 @@ function isMediaFile2(filePath) {
|
|
|
61859
62333
|
".webp",
|
|
61860
62334
|
".bmp"
|
|
61861
62335
|
];
|
|
61862
|
-
return mediaExts.includes(
|
|
62336
|
+
return mediaExts.includes(extname14(filePath).toLowerCase());
|
|
61863
62337
|
}
|
|
61864
62338
|
async function getMediaDuration2(filePath) {
|
|
61865
62339
|
try {
|
|
@@ -62005,7 +62479,7 @@ var init_agent_only = __esm({
|
|
|
62005
62479
|
if (entry.isDirectory() && recursive) {
|
|
62006
62480
|
await scanDir(entryPath);
|
|
62007
62481
|
} else if (entry.isFile()) {
|
|
62008
|
-
const ext =
|
|
62482
|
+
const ext = extname14(entry.name).toLowerCase();
|
|
62009
62483
|
const matchesFilter = !filterExts || filterExts.includes(ext);
|
|
62010
62484
|
if (matchesFilter && isMediaFile2(entryPath)) mediaFiles.push(entryPath);
|
|
62011
62485
|
}
|
|
@@ -62973,7 +63447,7 @@ init_source();
|
|
|
62973
63447
|
// package.json
|
|
62974
63448
|
var package_default = {
|
|
62975
63449
|
name: "@vibeframe/cli",
|
|
62976
|
-
version: "0.
|
|
63450
|
+
version: "0.93.0",
|
|
62977
63451
|
description: "VibeFrame CLI - natural language video editing from the terminal",
|
|
62978
63452
|
private: false,
|
|
62979
63453
|
license: "MIT",
|
|
@@ -64159,8 +64633,8 @@ init_output();
|
|
|
64159
64633
|
init_sanitize();
|
|
64160
64634
|
init_validate();
|
|
64161
64635
|
init_cost_tier();
|
|
64162
|
-
import { readFile as
|
|
64163
|
-
import { resolve as
|
|
64636
|
+
import { readFile as readFile22, writeFile as writeFile29 } from "node:fs/promises";
|
|
64637
|
+
import { resolve as resolve32 } from "node:path";
|
|
64164
64638
|
var inspectCommand = new Command("inspect").description("Inspect media using AI (images, videos, YouTube URLs)").addHelpText(
|
|
64165
64639
|
"after",
|
|
64166
64640
|
`
|
|
@@ -64357,8 +64831,8 @@ inspectCommand.command("suggest").description("Get AI edit suggestions using Gem
|
|
|
64357
64831
|
}
|
|
64358
64832
|
const apiKey = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
|
|
64359
64833
|
const spinner2 = ora("Initializing Gemini...").start();
|
|
64360
|
-
const filePath =
|
|
64361
|
-
const content = await
|
|
64834
|
+
const filePath = resolve32(process.cwd(), projectPath);
|
|
64835
|
+
const content = await readFile22(filePath, "utf-8");
|
|
64362
64836
|
const data = JSON.parse(content);
|
|
64363
64837
|
const project = Project.fromJSON(data);
|
|
64364
64838
|
const gemini = new GeminiProvider();
|
|
@@ -64395,7 +64869,7 @@ inspectCommand.command("suggest").description("Get AI edit suggestions using Gem
|
|
|
64395
64869
|
const sug = suggestions[0];
|
|
64396
64870
|
const applied = applySuggestion(project, sug);
|
|
64397
64871
|
if (applied) {
|
|
64398
|
-
await
|
|
64872
|
+
await writeFile29(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
64399
64873
|
spinner2.succeed(source_default.green("Suggestion applied"));
|
|
64400
64874
|
} else {
|
|
64401
64875
|
spinner2.warn(source_default.yellow("Could not apply suggestion automatically"));
|
|
@@ -64420,9 +64894,9 @@ init_cost_tier();
|
|
|
64420
64894
|
init_ai_helpers();
|
|
64421
64895
|
init_output();
|
|
64422
64896
|
init_validate();
|
|
64423
|
-
import { resolve as
|
|
64424
|
-
import { readFile as
|
|
64425
|
-
import { existsSync as
|
|
64897
|
+
import { resolve as resolve33, dirname as dirname11, basename as basename8, extname as extname7 } from "node:path";
|
|
64898
|
+
import { readFile as readFile23, writeFile as writeFile30 } from "node:fs/promises";
|
|
64899
|
+
import { existsSync as existsSync25 } from "node:fs";
|
|
64426
64900
|
var audioCommand = new Command("audio").alias("au").description("Audio operations (transcribe, TTS, voice clone, ducking)").addHelpText(
|
|
64427
64901
|
"after",
|
|
64428
64902
|
`
|
|
@@ -64455,8 +64929,8 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
|
|
|
64455
64929
|
const whisper = new WhisperProvider();
|
|
64456
64930
|
await whisper.initialize({ apiKey });
|
|
64457
64931
|
spinner2.text = "Reading audio file...";
|
|
64458
|
-
const absPath =
|
|
64459
|
-
const audioBuffer = await
|
|
64932
|
+
const absPath = resolve33(process.cwd(), audioPath);
|
|
64933
|
+
const audioBuffer = await readFile23(absPath);
|
|
64460
64934
|
const audioBlob = new Blob([audioBuffer]);
|
|
64461
64935
|
spinner2.text = "Transcribing...";
|
|
64462
64936
|
const result = await whisper.transcribe(audioBlob, options.language);
|
|
@@ -64473,7 +64947,7 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
|
|
|
64473
64947
|
fullText: result.fullText,
|
|
64474
64948
|
segments: result.segments,
|
|
64475
64949
|
language: result.detectedLanguage,
|
|
64476
|
-
outputPath: options.output ?
|
|
64950
|
+
outputPath: options.output ? resolve33(process.cwd(), options.output) : void 0
|
|
64477
64951
|
}
|
|
64478
64952
|
});
|
|
64479
64953
|
return;
|
|
@@ -64493,10 +64967,10 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
|
|
|
64493
64967
|
console.log();
|
|
64494
64968
|
}
|
|
64495
64969
|
if (options.output) {
|
|
64496
|
-
const outputPath =
|
|
64970
|
+
const outputPath = resolve33(process.cwd(), options.output);
|
|
64497
64971
|
const format = detectFormat(options.output, options.format);
|
|
64498
64972
|
const content = formatTranscript(result, format);
|
|
64499
|
-
await
|
|
64973
|
+
await writeFile30(outputPath, content, "utf-8");
|
|
64500
64974
|
console.log(source_default.green(`Saved ${format.toUpperCase()} to: ${outputPath}`));
|
|
64501
64975
|
}
|
|
64502
64976
|
} catch (error) {
|
|
@@ -64554,8 +65028,8 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
|
|
|
64554
65028
|
}
|
|
64555
65029
|
const apiKey = await requireApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
|
|
64556
65030
|
const spinner2 = ora("Reading audio file...").start();
|
|
64557
|
-
const absPath =
|
|
64558
|
-
const audioBuffer = await
|
|
65031
|
+
const absPath = resolve33(process.cwd(), audioPath);
|
|
65032
|
+
const audioBuffer = await readFile23(absPath);
|
|
64559
65033
|
spinner2.text = "Isolating vocals...";
|
|
64560
65034
|
const elevenlabs = new ElevenLabsProvider();
|
|
64561
65035
|
await elevenlabs.initialize({ apiKey });
|
|
@@ -64564,8 +65038,8 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
|
|
|
64564
65038
|
spinner2.fail(result.error || "Audio isolation failed");
|
|
64565
65039
|
exitWithError(apiError(result.error || "Audio isolation failed", true));
|
|
64566
65040
|
}
|
|
64567
|
-
const outputPath =
|
|
64568
|
-
await
|
|
65041
|
+
const outputPath = resolve33(process.cwd(), options.output);
|
|
65042
|
+
await writeFile30(outputPath, result.audioBuffer);
|
|
64569
65043
|
spinner2.succeed(source_default.green("Vocals isolated"));
|
|
64570
65044
|
if (isJsonMode()) {
|
|
64571
65045
|
outputSuccess({
|
|
@@ -64625,12 +65099,12 @@ audioCommand.command("clone-voice").description("Clone a voice from audio sample
|
|
|
64625
65099
|
const spinner2 = ora("Reading audio samples...").start();
|
|
64626
65100
|
const audioBuffers = [];
|
|
64627
65101
|
for (const samplePath of samples) {
|
|
64628
|
-
const absPath =
|
|
64629
|
-
if (!
|
|
65102
|
+
const absPath = resolve33(process.cwd(), samplePath);
|
|
65103
|
+
if (!existsSync25(absPath)) {
|
|
64630
65104
|
spinner2.fail(`File not found: ${samplePath}`);
|
|
64631
65105
|
exitWithError(notFoundError(samplePath));
|
|
64632
65106
|
}
|
|
64633
|
-
const buffer = await
|
|
65107
|
+
const buffer = await readFile23(absPath);
|
|
64634
65108
|
audioBuffers.push(buffer);
|
|
64635
65109
|
}
|
|
64636
65110
|
spinner2.text = `Cloning voice from ${audioBuffers.length} sample(s)...`;
|
|
@@ -64686,8 +65160,8 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
|
|
|
64686
65160
|
if (!options.language) {
|
|
64687
65161
|
exitWithError(usageError("Target language is required. Use -l or --language"));
|
|
64688
65162
|
}
|
|
64689
|
-
const absPath =
|
|
64690
|
-
if (!
|
|
65163
|
+
const absPath = resolve33(process.cwd(), mediaPath);
|
|
65164
|
+
if (!existsSync25(absPath)) {
|
|
64691
65165
|
exitWithError(notFoundError(mediaPath));
|
|
64692
65166
|
}
|
|
64693
65167
|
const openaiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI");
|
|
@@ -64698,7 +65172,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
|
|
|
64698
65172
|
const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
|
|
64699
65173
|
let audioPath = absPath;
|
|
64700
65174
|
if (isVideo) {
|
|
64701
|
-
const tempAudioPath =
|
|
65175
|
+
const tempAudioPath = resolve33(dirname11(absPath), `temp-audio-${Date.now()}.mp3`);
|
|
64702
65176
|
try {
|
|
64703
65177
|
execSafeSync("ffmpeg", ["-i", absPath, "-vn", "-acodec", "mp3", "-y", tempAudioPath]);
|
|
64704
65178
|
audioPath = tempAudioPath;
|
|
@@ -64710,7 +65184,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
|
|
|
64710
65184
|
spinner2.text = "Transcribing audio...";
|
|
64711
65185
|
const whisper = new WhisperProvider();
|
|
64712
65186
|
await whisper.initialize({ apiKey: openaiKey });
|
|
64713
|
-
const audioBuffer = await
|
|
65187
|
+
const audioBuffer = await readFile23(audioPath);
|
|
64714
65188
|
const audioBlob = new Blob([audioBuffer]);
|
|
64715
65189
|
const transcriptResult = await whisper.transcribe(audioBlob, options.source);
|
|
64716
65190
|
if (transcriptResult.status === "failed" || !transcriptResult.segments) {
|
|
@@ -64791,7 +65265,7 @@ ${segmentTexts}`,
|
|
|
64791
65265
|
console.log();
|
|
64792
65266
|
console.log(source_default.dim("Use without --analyze-only to generate dubbed audio"));
|
|
64793
65267
|
if (options.output) {
|
|
64794
|
-
const timingPath =
|
|
65268
|
+
const timingPath = resolve33(process.cwd(), options.output);
|
|
64795
65269
|
const timingData = {
|
|
64796
65270
|
sourcePath: absPath,
|
|
64797
65271
|
sourceLanguage: transcriptResult.detectedLanguage || options.source || "auto",
|
|
@@ -64804,7 +65278,7 @@ ${segmentTexts}`,
|
|
|
64804
65278
|
translated: translatedSegments[i]?.text || s.text
|
|
64805
65279
|
}))
|
|
64806
65280
|
};
|
|
64807
|
-
await
|
|
65281
|
+
await writeFile30(timingPath, JSON.stringify(timingData, null, 2));
|
|
64808
65282
|
console.log(`Timing saved to: ${source_default.bold(timingPath)}`);
|
|
64809
65283
|
}
|
|
64810
65284
|
return;
|
|
@@ -64829,12 +65303,12 @@ ${segmentTexts}`,
|
|
|
64829
65303
|
spinner2.text = "Combining audio...";
|
|
64830
65304
|
const combinedBuffer = Buffer.concat(dubbedAudioBuffers.map((a) => a.buffer));
|
|
64831
65305
|
const outputExt = isVideo ? ".mp3" : extname7(absPath);
|
|
64832
|
-
const defaultOutputPath2 =
|
|
65306
|
+
const defaultOutputPath2 = resolve33(
|
|
64833
65307
|
dirname11(absPath),
|
|
64834
65308
|
`${basename8(absPath, extname7(absPath))}-${options.language}${outputExt}`
|
|
64835
65309
|
);
|
|
64836
|
-
const finalOutputPath =
|
|
64837
|
-
await
|
|
65310
|
+
const finalOutputPath = resolve33(process.cwd(), options.output || defaultOutputPath2);
|
|
65311
|
+
await writeFile30(finalOutputPath, combinedBuffer);
|
|
64838
65312
|
spinner2.succeed(source_default.green("Dubbing complete"));
|
|
64839
65313
|
if (isJsonMode()) {
|
|
64840
65314
|
outputSuccess({
|
|
@@ -64890,9 +65364,9 @@ audioCommand.command("duck").description("Auto-duck background music when voice
|
|
|
64890
65364
|
exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"));
|
|
64891
65365
|
}
|
|
64892
65366
|
const spinner2 = ora("Processing audio ducking...").start();
|
|
64893
|
-
const absMusic =
|
|
64894
|
-
const absVoice =
|
|
64895
|
-
const outputPath = options.output ?
|
|
65367
|
+
const absMusic = resolve33(process.cwd(), musicPath);
|
|
65368
|
+
const absVoice = resolve33(process.cwd(), options.voice);
|
|
65369
|
+
const outputPath = options.output ? resolve33(process.cwd(), options.output) : absMusic.replace(/(\.[^.]+)$/, "-ducked$1");
|
|
64896
65370
|
const thresholdDb = parseFloat(options.threshold);
|
|
64897
65371
|
const thresholdLinear = Math.pow(10, thresholdDb / 20);
|
|
64898
65372
|
const ratio = parseFloat(options.ratio);
|
|
@@ -65500,7 +65974,7 @@ init_esm();
|
|
|
65500
65974
|
init_source();
|
|
65501
65975
|
init_ora();
|
|
65502
65976
|
import { stat as stat4 } from "node:fs/promises";
|
|
65503
|
-
import { resolve as resolve39, extname as
|
|
65977
|
+
import { resolve as resolve39, extname as extname10, basename as basename12 } from "node:path";
|
|
65504
65978
|
|
|
65505
65979
|
// ../../node_modules/.pnpm/music-metadata@11.11.1/node_modules/music-metadata/lib/index.js
|
|
65506
65980
|
init_lib();
|
|
@@ -69377,7 +69851,7 @@ mediaCommand.command("info").description("Get media file information").argument(
|
|
|
69377
69851
|
const spinner2 = ora("Analyzing media...").start();
|
|
69378
69852
|
try {
|
|
69379
69853
|
const filePath = resolve39(process.cwd(), file);
|
|
69380
|
-
const ext =
|
|
69854
|
+
const ext = extname10(filePath).toLowerCase();
|
|
69381
69855
|
const fileName = basename12(filePath);
|
|
69382
69856
|
const fileStat = await stat4(filePath);
|
|
69383
69857
|
const mediaType = detectMediaType2(ext);
|
|
@@ -69519,10 +69993,10 @@ init_engine();
|
|
|
69519
69993
|
init_output();
|
|
69520
69994
|
init_cost_tier();
|
|
69521
69995
|
import { readFile as readFile27, writeFile as writeFile35, readdir as readdir2 } from "node:fs/promises";
|
|
69522
|
-
import { resolve as resolve40, basename as basename13, extname as
|
|
69996
|
+
import { resolve as resolve40, basename as basename13, extname as extname11, join as join10 } from "node:path";
|
|
69523
69997
|
var batchCommand = new Command("batch").description("Batch operations for processing multiple items");
|
|
69524
69998
|
function detectMediaType3(filePath) {
|
|
69525
|
-
const ext =
|
|
69999
|
+
const ext = extname11(filePath).toLowerCase();
|
|
69526
70000
|
const videoExts = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
|
|
69527
70001
|
const audioExts = [".mp3", ".wav", ".aac", ".flac", ".ogg", ".m4a"];
|
|
69528
70002
|
const imageExts = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"];
|
|
@@ -69532,7 +70006,7 @@ function detectMediaType3(filePath) {
|
|
|
69532
70006
|
return "video";
|
|
69533
70007
|
}
|
|
69534
70008
|
function isMediaFile(filePath) {
|
|
69535
|
-
const ext =
|
|
70009
|
+
const ext = extname11(filePath).toLowerCase();
|
|
69536
70010
|
const mediaExts = [
|
|
69537
70011
|
".mp4",
|
|
69538
70012
|
".mov",
|
|
@@ -69590,7 +70064,7 @@ batchCommand.command("import").description("Import multiple media files from a d
|
|
|
69590
70064
|
if (entry.isDirectory() && options.recursive) {
|
|
69591
70065
|
await scanDir(entryPath);
|
|
69592
70066
|
} else if (entry.isFile()) {
|
|
69593
|
-
const ext =
|
|
70067
|
+
const ext = extname11(entry.name).toLowerCase();
|
|
69594
70068
|
const matchesFilter = !filterExts || filterExts.includes(ext);
|
|
69595
70069
|
if (matchesFilter && isMediaFile(entryPath)) {
|
|
69596
70070
|
mediaFiles.push(entryPath);
|
|
@@ -69971,6 +70445,13 @@ var VALID_LLM_PROVIDERS = [
|
|
|
69971
70445
|
"openrouter"
|
|
69972
70446
|
];
|
|
69973
70447
|
var VALID_SCOPES = ["user", "project"];
|
|
70448
|
+
function envValueFor(meta) {
|
|
70449
|
+
return process.env[meta.envVar] || meta.envAliases?.map((alias) => process.env[alias]).find(Boolean);
|
|
70450
|
+
}
|
|
70451
|
+
function envValueForName(envVar) {
|
|
70452
|
+
if (envVar === "FAL_API_KEY") return process.env.FAL_API_KEY || process.env.FAL_KEY;
|
|
70453
|
+
return process.env[envVar];
|
|
70454
|
+
}
|
|
69974
70455
|
function parseScope(raw) {
|
|
69975
70456
|
const v = String(raw ?? "user");
|
|
69976
70457
|
if (!VALID_SCOPES.includes(v)) {
|
|
@@ -70058,6 +70539,7 @@ var AI_FEATURES = [
|
|
|
70058
70539
|
desc: "generate + edit",
|
|
70059
70540
|
defaultProvider: "OpenAI gpt-image-2 (Artificial Analysis #1, since v0.56)",
|
|
70060
70541
|
alsoAvailable: "Gemini Nano Banana, Grok Imagine",
|
|
70542
|
+
keyHint: "1 key",
|
|
70061
70543
|
keys: [
|
|
70062
70544
|
{
|
|
70063
70545
|
configKey: "openai",
|
|
@@ -70112,10 +70594,11 @@ var AI_FEATURES = [
|
|
|
70112
70594
|
desc: "generate + extend",
|
|
70113
70595
|
defaultProvider: "Seedance 2.0 via fal.ai (Artificial Analysis #2 t2v + i2v, since v0.57)",
|
|
70114
70596
|
alsoAvailable: "Grok Imagine, Kling, Runway Gen-4.5, Google Veo",
|
|
70597
|
+
keyHint: "1-2 keys",
|
|
70115
70598
|
keys: [
|
|
70116
70599
|
{
|
|
70117
70600
|
configKey: "fal",
|
|
70118
|
-
envVar: "
|
|
70601
|
+
envVar: "FAL_API_KEY",
|
|
70119
70602
|
name: "Seedance 2.0 (via fal.ai)",
|
|
70120
70603
|
url: "https://fal.ai/dashboard/keys",
|
|
70121
70604
|
what: "ByteDance Seedance 2.0 text-to-video and image-to-video"
|
|
@@ -70127,11 +70610,20 @@ var AI_FEATURES = [
|
|
|
70127
70610
|
desc: "recommended default, text-to-video + image-to-video",
|
|
70128
70611
|
key: {
|
|
70129
70612
|
configKey: "fal",
|
|
70130
|
-
envVar: "
|
|
70613
|
+
envVar: "FAL_API_KEY",
|
|
70131
70614
|
name: "Seedance 2.0 (via fal.ai)",
|
|
70132
70615
|
url: "https://fal.ai/dashboard/keys",
|
|
70133
70616
|
what: "ByteDance Seedance 2.0 text-to-video and image-to-video"
|
|
70134
70617
|
},
|
|
70618
|
+
extraKeys: [
|
|
70619
|
+
{
|
|
70620
|
+
configKey: "imgbb",
|
|
70621
|
+
envVar: "IMGBB_API_KEY",
|
|
70622
|
+
name: "ImgBB",
|
|
70623
|
+
url: "https://api.imgbb.com/",
|
|
70624
|
+
what: "Image hosting for Seedance image-to-video uploads"
|
|
70625
|
+
}
|
|
70626
|
+
],
|
|
70135
70627
|
defaultFor: { kind: "video", value: "seedance" }
|
|
70136
70628
|
},
|
|
70137
70629
|
{
|
|
@@ -70156,6 +70648,15 @@ var AI_FEATURES = [
|
|
|
70156
70648
|
url: "https://platform.klingai.com/",
|
|
70157
70649
|
what: "Kling video generation"
|
|
70158
70650
|
},
|
|
70651
|
+
extraKeys: [
|
|
70652
|
+
{
|
|
70653
|
+
configKey: "imgbb",
|
|
70654
|
+
envVar: "IMGBB_API_KEY",
|
|
70655
|
+
name: "ImgBB",
|
|
70656
|
+
url: "https://api.imgbb.com/",
|
|
70657
|
+
what: "Image hosting for Kling image-to-video uploads"
|
|
70658
|
+
}
|
|
70659
|
+
],
|
|
70159
70660
|
defaultFor: { kind: "video", value: "kling" }
|
|
70160
70661
|
},
|
|
70161
70662
|
{
|
|
@@ -70190,6 +70691,7 @@ var AI_FEATURES = [
|
|
|
70190
70691
|
desc: "TTS, SFX, music, voice clone",
|
|
70191
70692
|
defaultProvider: "ElevenLabs (paid, premium quality) \u2014 falls back to local Kokoro when no key (free, since v0.54)",
|
|
70192
70693
|
alsoAvailable: "Replicate MusicGen (music only)",
|
|
70694
|
+
keyHint: "0-1 key",
|
|
70193
70695
|
keys: [
|
|
70194
70696
|
{
|
|
70195
70697
|
configKey: "elevenlabs",
|
|
@@ -70235,6 +70737,7 @@ var AI_FEATURES = [
|
|
|
70235
70737
|
desc: "captions, grade, reframe, motion graphics",
|
|
70236
70738
|
defaultProvider: "Whisper (transcription) + Claude (reasoning)",
|
|
70237
70739
|
alsoAvailable: "",
|
|
70740
|
+
keyHint: "2 keys",
|
|
70238
70741
|
keys: [
|
|
70239
70742
|
{
|
|
70240
70743
|
configKey: "openai",
|
|
@@ -70268,8 +70771,11 @@ function applyProviderDefault(config2, choice) {
|
|
|
70268
70771
|
}
|
|
70269
70772
|
}
|
|
70270
70773
|
function isProviderChoiceConfigured(config2, choice) {
|
|
70271
|
-
|
|
70272
|
-
|
|
70774
|
+
const keys = [choice.key, ...getChoiceExtraKeys(config2, choice)].filter(
|
|
70775
|
+
(keyDef) => Boolean(keyDef)
|
|
70776
|
+
);
|
|
70777
|
+
for (const keyDef of keys) {
|
|
70778
|
+
const value = config2.providers[keyDef.configKey];
|
|
70273
70779
|
if (value) return true;
|
|
70274
70780
|
}
|
|
70275
70781
|
if (choice.defaultFor?.kind === "image") {
|
|
@@ -70280,6 +70786,96 @@ function isProviderChoiceConfigured(config2, choice) {
|
|
|
70280
70786
|
}
|
|
70281
70787
|
return false;
|
|
70282
70788
|
}
|
|
70789
|
+
function getConfiguredUploadProvider(config2) {
|
|
70790
|
+
return process.env.VIBE_UPLOAD_PROVIDER?.toLowerCase() === "s3" || config2.upload.provider === "s3" ? "s3" : "imgbb";
|
|
70791
|
+
}
|
|
70792
|
+
function hasVideoFeature(features) {
|
|
70793
|
+
return features.some((feature) => feature.label === "Videos");
|
|
70794
|
+
}
|
|
70795
|
+
async function promptOptionalValue(label, currentValue) {
|
|
70796
|
+
const suffix = currentValue ? source_default.dim(` [${currentValue}]`) : "";
|
|
70797
|
+
const value = await prompt2(source_default.cyan(` ${label}${suffix}: `));
|
|
70798
|
+
const trimmed = value.trim();
|
|
70799
|
+
return trimmed || currentValue;
|
|
70800
|
+
}
|
|
70801
|
+
async function configureUploadHost(config2) {
|
|
70802
|
+
console.log(source_default.dim("2b. Upload host"));
|
|
70803
|
+
console.log(source_default.bold("Where should image-to-video upload reference images?"));
|
|
70804
|
+
console.log(
|
|
70805
|
+
source_default.dim(
|
|
70806
|
+
" Seedance and Kling need a temporary HTTPS image URL when you start from a local image."
|
|
70807
|
+
)
|
|
70808
|
+
);
|
|
70809
|
+
console.log();
|
|
70810
|
+
const currentProvider = getConfiguredUploadProvider(config2);
|
|
70811
|
+
const selected = await promptSelect(
|
|
70812
|
+
source_default.cyan(" Upload host [1-3]: "),
|
|
70813
|
+
[
|
|
70814
|
+
`ImgBB ${source_default.dim("- easiest setup, third-party image hosting (default)")}`,
|
|
70815
|
+
`S3 ${source_default.dim("- your own bucket, better control/privacy")}`,
|
|
70816
|
+
`Back ${source_default.dim("(choose different features)")}`
|
|
70817
|
+
],
|
|
70818
|
+
currentProvider === "s3" ? 1 : 0,
|
|
70819
|
+
{ backIndex: 2 }
|
|
70820
|
+
);
|
|
70821
|
+
console.log();
|
|
70822
|
+
if (selected === 2) {
|
|
70823
|
+
return "back";
|
|
70824
|
+
}
|
|
70825
|
+
if (selected === 0) {
|
|
70826
|
+
config2.upload.provider = "imgbb";
|
|
70827
|
+
process.env.VIBE_UPLOAD_PROVIDER = "imgbb";
|
|
70828
|
+
console.log(source_default.dim(" ImgBB selected. Seedance/Kling setup will ask for IMGBB_API_KEY."));
|
|
70829
|
+
console.log();
|
|
70830
|
+
return "continue";
|
|
70831
|
+
}
|
|
70832
|
+
config2.upload.provider = "s3";
|
|
70833
|
+
process.env.VIBE_UPLOAD_PROVIDER = "s3";
|
|
70834
|
+
config2.upload.s3 = config2.upload.s3 ?? {};
|
|
70835
|
+
const envBucket = process.env.VIBE_UPLOAD_S3_BUCKET;
|
|
70836
|
+
const envRegion = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION;
|
|
70837
|
+
const envPrefix = process.env.VIBE_UPLOAD_S3_PREFIX;
|
|
70838
|
+
const envTtl = process.env.VIBE_UPLOAD_TTL_SECONDS;
|
|
70839
|
+
console.log(source_default.dim(" S3 selected. AWS credentials stay in your shell/.env/AWS profile."));
|
|
70840
|
+
console.log(source_default.dim(" Required at runtime: AWS credentials, region, and bucket."));
|
|
70841
|
+
console.log();
|
|
70842
|
+
const bucket = await promptOptionalValue("S3 bucket", envBucket ?? config2.upload.s3.bucket);
|
|
70843
|
+
const region = await promptOptionalValue("AWS region", envRegion ?? config2.upload.s3.region);
|
|
70844
|
+
const prefix = await promptOptionalValue(
|
|
70845
|
+
"S3 prefix",
|
|
70846
|
+
envPrefix ?? config2.upload.s3.prefix ?? "vibeframe/tmp"
|
|
70847
|
+
);
|
|
70848
|
+
const ttlValue = await promptOptionalValue(
|
|
70849
|
+
"Upload URL TTL seconds",
|
|
70850
|
+
envTtl ?? String(config2.upload.ttlSeconds ?? 3600)
|
|
70851
|
+
);
|
|
70852
|
+
if (bucket) config2.upload.s3.bucket = bucket;
|
|
70853
|
+
if (region) config2.upload.s3.region = region;
|
|
70854
|
+
if (prefix) config2.upload.s3.prefix = prefix;
|
|
70855
|
+
const ttlSeconds = ttlValue ? Number.parseInt(ttlValue, 10) : NaN;
|
|
70856
|
+
if (Number.isFinite(ttlSeconds) && ttlSeconds > 0) {
|
|
70857
|
+
config2.upload.ttlSeconds = ttlSeconds;
|
|
70858
|
+
}
|
|
70859
|
+
if (!config2.upload.s3.bucket || !config2.upload.s3.region) {
|
|
70860
|
+
console.log(
|
|
70861
|
+
source_default.yellow(
|
|
70862
|
+
" \u26A0 S3 selected, but bucket/region is incomplete. Set VIBE_UPLOAD_S3_BUCKET and AWS_REGION before image-to-video."
|
|
70863
|
+
)
|
|
70864
|
+
);
|
|
70865
|
+
} else {
|
|
70866
|
+
console.log(
|
|
70867
|
+
` ${source_default.green("\u2713")} S3 upload host configured ${source_default.dim(`(${config2.upload.s3.bucket}/${config2.upload.s3.prefix ?? "vibeframe/tmp"})`)}`
|
|
70868
|
+
);
|
|
70869
|
+
}
|
|
70870
|
+
console.log();
|
|
70871
|
+
return "continue";
|
|
70872
|
+
}
|
|
70873
|
+
function getChoiceExtraKeys(config2, choice) {
|
|
70874
|
+
if (config2.upload.provider === "s3") {
|
|
70875
|
+
return [];
|
|
70876
|
+
}
|
|
70877
|
+
return choice.extraKeys ?? [];
|
|
70878
|
+
}
|
|
70283
70879
|
function isFeatureConfigured(config2, feature) {
|
|
70284
70880
|
if (feature.providerChoices?.some((choice) => isProviderChoiceConfigured(config2, choice))) {
|
|
70285
70881
|
return true;
|
|
@@ -70323,7 +70919,7 @@ async function runNonInteractiveSetup(opts) {
|
|
|
70323
70919
|
if (opts.importEnv) {
|
|
70324
70920
|
loadEnv();
|
|
70325
70921
|
for (const meta of getAllApiKeys()) {
|
|
70326
|
-
const envValue =
|
|
70922
|
+
const envValue = envValueFor(meta);
|
|
70327
70923
|
if (!envValue) continue;
|
|
70328
70924
|
const existing = config2.providers[meta.configKey];
|
|
70329
70925
|
if (existing === envValue) continue;
|
|
@@ -70389,6 +70985,7 @@ async function runNonInteractiveSetup(opts) {
|
|
|
70389
70985
|
}
|
|
70390
70986
|
async function runSetupWizard(fullSetup = false, scope = "user") {
|
|
70391
70987
|
console.log();
|
|
70988
|
+
loadEnv();
|
|
70392
70989
|
const cfgPath = getConfigPath(scope).replace(homedir5(), "~");
|
|
70393
70990
|
const scopeNote = scope === "project" ? "applies to this project only \xB7 add .vibeframe/ to .gitignore" : "applies to every project for this user";
|
|
70394
70991
|
console.log(source_default.bold.magenta("VibeFrame Setup") + source_default.bold(` \u2014 ${scope} scope`));
|
|
@@ -70452,7 +71049,7 @@ async function runSetupWizard(fullSetup = false, scope = "user") {
|
|
|
70452
71049
|
},
|
|
70453
71050
|
{
|
|
70454
71051
|
configKey: "fal",
|
|
70455
|
-
envVar: "
|
|
71052
|
+
envVar: "FAL_API_KEY",
|
|
70456
71053
|
name: "fal.ai",
|
|
70457
71054
|
url: "https://fal.ai/dashboard/keys",
|
|
70458
71055
|
what: "Seedance 2.0 \u2014 video generation, default since v0.57"
|
|
@@ -70540,8 +71137,7 @@ async function runAIFeaturesSetup(config2, _scope) {
|
|
|
70540
71137
|
const backIndex = AI_FEATURES.length;
|
|
70541
71138
|
const featureLabels = [
|
|
70542
71139
|
...AI_FEATURES.map((f) => {
|
|
70543
|
-
const
|
|
70544
|
-
const tag = source_default.dim(`${keyCount} key${keyCount > 1 ? "s" : ""}`);
|
|
71140
|
+
const tag = source_default.dim(f.keyHint ?? `${f.keys.length} key${f.keys.length > 1 ? "s" : ""}`);
|
|
70545
71141
|
return `${f.label} ${source_default.dim(`(${f.desc})`)} ${tag}`;
|
|
70546
71142
|
}),
|
|
70547
71143
|
`Back ${source_default.dim("(return to setup checkpoint)")}`
|
|
@@ -70564,6 +71160,13 @@ async function runAIFeaturesSetup(config2, _scope) {
|
|
|
70564
71160
|
console.log();
|
|
70565
71161
|
return { tryCommand: "vibe --help", selectedFeatures: [] };
|
|
70566
71162
|
}
|
|
71163
|
+
if (hasVideoFeature(selectedFeatures)) {
|
|
71164
|
+
const uploadResult = await configureUploadHost(config2);
|
|
71165
|
+
if (uploadResult === "back") {
|
|
71166
|
+
console.log();
|
|
71167
|
+
continue featureSelection;
|
|
71168
|
+
}
|
|
71169
|
+
}
|
|
70567
71170
|
const plannedKeys = /* @__PURE__ */ new Map();
|
|
70568
71171
|
console.log(source_default.dim("3. Providers"));
|
|
70569
71172
|
console.log(source_default.bold("Choose providers for each selected feature"));
|
|
@@ -70586,7 +71189,12 @@ async function runAIFeaturesSetup(config2, _scope) {
|
|
|
70586
71189
|
(choice) => isProviderChoiceConfigured(config2, choice)
|
|
70587
71190
|
);
|
|
70588
71191
|
const providerLabels = feature.providerChoices.map((choice) => {
|
|
70589
|
-
const keyHint = choice.keyless ? source_default.dim("no key") : choice.key ? source_default.dim(
|
|
71192
|
+
const keyHint = choice.keyless ? source_default.dim("no key") : choice.key ? source_default.dim(
|
|
71193
|
+
[
|
|
71194
|
+
choice.key.envVar,
|
|
71195
|
+
...getChoiceExtraKeys(config2, choice).map((keyDef) => keyDef.envVar)
|
|
71196
|
+
].filter(Boolean).join(" + ")
|
|
71197
|
+
) : source_default.dim("no key");
|
|
70590
71198
|
const defaultHint = choice === feature.providerChoices?.[0] ? source_default.dim("recommended default") : "";
|
|
70591
71199
|
return [choice.label, defaultHint, source_default.dim(`- ${choice.desc}`), keyHint].filter(Boolean).join(" ");
|
|
70592
71200
|
});
|
|
@@ -70612,6 +71220,9 @@ async function runAIFeaturesSetup(config2, _scope) {
|
|
|
70612
71220
|
}
|
|
70613
71221
|
for (const choice of selectedChoices) {
|
|
70614
71222
|
if (choice.key) addUniqueKey(keysForFeature, choice.key);
|
|
71223
|
+
for (const keyDef of getChoiceExtraKeys(config2, choice)) {
|
|
71224
|
+
addUniqueKey(keysForFeature, keyDef);
|
|
71225
|
+
}
|
|
70615
71226
|
}
|
|
70616
71227
|
if (selectedChoices.some((choice) => choice.keyless)) {
|
|
70617
71228
|
console.log(source_default.dim(" No API key needed for selected local provider(s)."));
|
|
@@ -70649,7 +71260,7 @@ async function runAIFeaturesSetup(config2, _scope) {
|
|
|
70649
71260
|
}
|
|
70650
71261
|
loadEnv();
|
|
70651
71262
|
const configValue = config2.providers[keyDef.configKey];
|
|
70652
|
-
const envValue =
|
|
71263
|
+
const envValue = envValueForName(keyDef.envVar);
|
|
70653
71264
|
if (configValue || envValue) {
|
|
70654
71265
|
const value = configValue || envValue;
|
|
70655
71266
|
const source = configValue ? "config" : ".env";
|
|
@@ -70697,7 +71308,7 @@ async function collectKeys(config2, keys) {
|
|
|
70697
71308
|
loadEnv();
|
|
70698
71309
|
for (const keyDef of keys) {
|
|
70699
71310
|
const configValue = config2.providers[keyDef.configKey];
|
|
70700
|
-
const envValue =
|
|
71311
|
+
const envValue = envValueForName(keyDef.envVar);
|
|
70701
71312
|
if (configValue) {
|
|
70702
71313
|
console.log(
|
|
70703
71314
|
` ${source_default.green("\u2713")} ${keyDef.name.padEnd(14)} ${maskApiKey(configValue)} ${source_default.dim("(config)")}`
|
|
@@ -70958,7 +71569,7 @@ async function showConfig(opts = { verbose: false }) {
|
|
|
70958
71569
|
{ key: "openai", name: "OpenAI", env: "OPENAI_API_KEY" },
|
|
70959
71570
|
{ key: "google", name: "Google", env: "GOOGLE_API_KEY" },
|
|
70960
71571
|
{ key: "xai", name: "xAI", env: "XAI_API_KEY" },
|
|
70961
|
-
{ key: "fal", name: "fal.ai", env: "
|
|
71572
|
+
{ key: "fal", name: "fal.ai", env: "FAL_API_KEY" },
|
|
70962
71573
|
{ key: "elevenlabs", name: "ElevenLabs", env: "ELEVENLABS_API_KEY" },
|
|
70963
71574
|
{ key: "runway", name: "Runway", env: "RUNWAY_API_SECRET" },
|
|
70964
71575
|
{ key: "kling", name: "Kling", env: "KLING_API_KEY" },
|
|
@@ -70969,7 +71580,7 @@ async function showConfig(opts = { verbose: false }) {
|
|
|
70969
71580
|
let unsetCount = 0;
|
|
70970
71581
|
for (const p of providerKeys) {
|
|
70971
71582
|
const configValue = config2?.providers[p.key];
|
|
70972
|
-
const dotenvValue = dotenvKeys[p.env];
|
|
71583
|
+
const dotenvValue = dotenvKeys[p.env] || (p.env === "FAL_API_KEY" ? dotenvKeys.FAL_KEY : void 0);
|
|
70973
71584
|
if (configValue || dotenvValue) {
|
|
70974
71585
|
const sources = [];
|
|
70975
71586
|
if (configValue) sources.push("config");
|
|
@@ -71265,11 +71876,22 @@ OPENROUTER_API_KEY= # multiplexes any provider above
|
|
|
71265
71876
|
|
|
71266
71877
|
# \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
71878
|
ELEVENLABS_API_KEY= # paid TTS / SFX / music (Kokoro is the free fallback)
|
|
71268
|
-
|
|
71879
|
+
FAL_API_KEY= # Seedance 2.0 \u2014 default video provider since v0.57
|
|
71880
|
+
IMGBB_API_KEY= # image hosting for Seedance/Kling image-to-video
|
|
71269
71881
|
RUNWAY_API_SECRET= # Runway Gen-4.5 video
|
|
71270
71882
|
KLING_API_KEY= # Kling video
|
|
71271
71883
|
REPLICATE_API_TOKEN= # MusicGen, real-esrgan, etc.
|
|
71272
|
-
|
|
71884
|
+
|
|
71885
|
+
# \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
|
|
71886
|
+
# Default is ImgBB. Set these only if you want temporary URLs from your own S3 bucket.
|
|
71887
|
+
# VIBE_UPLOAD_PROVIDER=s3 # imgbb | s3
|
|
71888
|
+
# VIBE_UPLOAD_TTL_SECONDS=3600
|
|
71889
|
+
# VIBE_UPLOAD_S3_BUCKET=
|
|
71890
|
+
# VIBE_UPLOAD_S3_PREFIX=vibeframe/tmp
|
|
71891
|
+
# AWS_REGION=
|
|
71892
|
+
# AWS_ACCESS_KEY_ID=
|
|
71893
|
+
# AWS_SECRET_ACCESS_KEY=
|
|
71894
|
+
# AWS_SESSION_TOKEN=
|
|
71273
71895
|
`;
|
|
71274
71896
|
}
|
|
71275
71897
|
var GITIGNORE_ADDITIONS = `
|
|
@@ -72129,7 +72751,9 @@ async function runDiagnostics() {
|
|
|
72129
72751
|
let readyCount = FREE_COMMANDS.length;
|
|
72130
72752
|
let totalCount = FREE_COMMANDS.length;
|
|
72131
72753
|
for (const [envVar, commands] of Object.entries(COMMAND_KEY_MAP)) {
|
|
72132
|
-
const configured =
|
|
72754
|
+
const configured = Boolean(
|
|
72755
|
+
process.env[envVar] || PROVIDER_ENV_ALIASES[envVar]?.map((alias) => process.env[alias]).find(Boolean)
|
|
72756
|
+
);
|
|
72133
72757
|
const providerName = Object.entries(PROVIDER_ENV_VARS).find(([, v]) => v === envVar)?.[0] ?? envVar;
|
|
72134
72758
|
providers[providerName] = { envVar, configured, commands };
|
|
72135
72759
|
totalCount += commands.length;
|
|
@@ -72418,7 +73042,7 @@ async function runLiveKeyTests(results) {
|
|
|
72418
73042
|
}
|
|
72419
73043
|
const { testKey: testKey2 } = await Promise.resolve().then(() => (init_key_live_test(), key_live_test_exports));
|
|
72420
73044
|
for (const [name, info] of configured) {
|
|
72421
|
-
const value = process.env[info.envVar];
|
|
73045
|
+
const value = process.env[info.envVar] || PROVIDER_ENV_ALIASES[info.envVar]?.map((alias) => process.env[alias]).find(Boolean);
|
|
72422
73046
|
if (!value) continue;
|
|
72423
73047
|
process.stdout.write(` ${name.padEnd(12)} `);
|
|
72424
73048
|
const result = await testKey2(name, value);
|