@vibeframe/cli 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +8 -15
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/ai-analyze.d.ts.map +1 -1
- package/dist/commands/ai-analyze.js +9 -16
- package/dist/commands/ai-analyze.js.map +1 -1
- package/dist/commands/ai-audio.d.ts.map +1 -1
- package/dist/commands/ai-audio.js +129 -111
- package/dist/commands/ai-audio.js.map +1 -1
- package/dist/commands/ai-broll.d.ts.map +1 -1
- package/dist/commands/ai-broll.js +38 -23
- package/dist/commands/ai-broll.js.map +1 -1
- package/dist/commands/ai-edit-cli.d.ts.map +1 -1
- package/dist/commands/ai-edit-cli.js +53 -65
- package/dist/commands/ai-edit-cli.js.map +1 -1
- package/dist/commands/ai-fill-gaps.d.ts.map +1 -1
- package/dist/commands/ai-fill-gaps.js +13 -17
- package/dist/commands/ai-fill-gaps.js.map +1 -1
- package/dist/commands/ai-highlights.d.ts.map +1 -1
- package/dist/commands/ai-highlights.js +87 -60
- package/dist/commands/ai-highlights.js.map +1 -1
- package/dist/commands/ai-image.d.ts.map +1 -1
- package/dist/commands/ai-image.js +75 -60
- package/dist/commands/ai-image.js.map +1 -1
- package/dist/commands/ai-motion.d.ts.map +1 -1
- package/dist/commands/ai-motion.js +30 -5
- package/dist/commands/ai-motion.js.map +1 -1
- package/dist/commands/ai-narrate.d.ts.map +1 -1
- package/dist/commands/ai-narrate.js +19 -16
- package/dist/commands/ai-narrate.js.map +1 -1
- package/dist/commands/ai-review.d.ts.map +1 -1
- package/dist/commands/ai-review.js +24 -5
- package/dist/commands/ai-review.js.map +1 -1
- package/dist/commands/ai-script-pipeline-cli.d.ts.map +1 -1
- package/dist/commands/ai-script-pipeline-cli.js +114 -88
- package/dist/commands/ai-script-pipeline-cli.js.map +1 -1
- package/dist/commands/ai-script-pipeline.d.ts +12 -2
- package/dist/commands/ai-script-pipeline.d.ts.map +1 -1
- package/dist/commands/ai-script-pipeline.js +113 -27
- package/dist/commands/ai-script-pipeline.js.map +1 -1
- package/dist/commands/ai-suggest-edit.d.ts.map +1 -1
- package/dist/commands/ai-suggest-edit.js +16 -21
- package/dist/commands/ai-suggest-edit.js.map +1 -1
- package/dist/commands/ai-video-fx.d.ts.map +1 -1
- package/dist/commands/ai-video-fx.js +72 -71
- package/dist/commands/ai-video-fx.js.map +1 -1
- package/dist/commands/ai-video.d.ts.map +1 -1
- package/dist/commands/ai-video.js +99 -90
- package/dist/commands/ai-video.js.map +1 -1
- package/dist/commands/ai-viral.d.ts.map +1 -1
- package/dist/commands/ai-viral.js +12 -24
- package/dist/commands/ai-viral.js.map +1 -1
- package/dist/commands/ai-visual-fx.d.ts.map +1 -1
- package/dist/commands/ai-visual-fx.js +76 -60
- package/dist/commands/ai-visual-fx.js.map +1 -1
- package/dist/commands/analyze.js +4 -4
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/audio.js +44 -44
- package/dist/commands/audio.js.map +1 -1
- package/dist/commands/batch.d.ts.map +1 -1
- package/dist/commands/batch.js +92 -39
- package/dist/commands/batch.js.map +1 -1
- package/dist/commands/detect.d.ts.map +1 -1
- package/dist/commands/detect.js +62 -11
- package/dist/commands/detect.js.map +1 -1
- package/dist/commands/edit-cmd.js +60 -64
- package/dist/commands/edit-cmd.js.map +1 -1
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +137 -90
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/generate.js +125 -128
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/media.d.ts.map +1 -1
- package/dist/commands/media.js +7 -9
- package/dist/commands/media.js.map +1 -1
- package/dist/commands/output.js +2 -2
- package/dist/commands/output.js.map +1 -1
- package/dist/commands/pipeline.d.ts.map +1 -1
- package/dist/commands/pipeline.js +21 -27
- package/dist/commands/pipeline.js.map +1 -1
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +42 -9
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/schema.d.ts.map +1 -1
- package/dist/commands/schema.js +10 -16
- package/dist/commands/schema.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +248 -234
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/timeline.d.ts.map +1 -1
- package/dist/commands/timeline.js +185 -59
- package/dist/commands/timeline.js.map +1 -1
- package/dist/commands/validate.d.ts +3 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +9 -2
- package/dist/commands/validate.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +95 -17
- package/dist/index.js.map +1 -1
- package/dist/utils/api-key.d.ts.map +1 -1
- package/dist/utils/api-key.js +4 -2
- package/dist/utils/api-key.js.map +1 -1
- package/dist/utils/first-run.d.ts.map +1 -1
- package/dist/utils/first-run.js +5 -6
- package/dist/utils/first-run.js.map +1 -1
- package/dist/utils/tty.d.ts +1 -1
- package/dist/utils/tty.d.ts.map +1 -1
- package/dist/utils/tty.js +62 -3
- package/dist/utils/tty.js.map +1 -1
- package/package.json +3 -3
|
@@ -21,6 +21,8 @@ import { getApiKey } from '../utils/api-key.js';
|
|
|
21
21
|
import { execSafe, execSafeSync, commandExists } from '../utils/exec-safe.js';
|
|
22
22
|
import { detectFormat, formatTranscript } from '../utils/subtitle.js';
|
|
23
23
|
import { formatTime } from './ai-helpers.js';
|
|
24
|
+
import { exitWithError, authError, notFoundError, apiError, usageError, generalError, outputResult } from './output.js';
|
|
25
|
+
import { validateOutputPath } from "./validate.js";
|
|
24
26
|
function _registerAudioCommands(aiCommand) {
|
|
25
27
|
aiCommand
|
|
26
28
|
.command("transcribe")
|
|
@@ -30,12 +32,19 @@ function _registerAudioCommands(aiCommand) {
|
|
|
30
32
|
.option("-l, --language <lang>", "Language code (e.g., en, ko)")
|
|
31
33
|
.option("-o, --output <path>", "Output file path")
|
|
32
34
|
.option("-f, --format <format>", "Output format: json, srt, vtt (auto-detected from extension)")
|
|
35
|
+
.option("--dry-run", "Preview parameters without executing")
|
|
33
36
|
.action(async (audioPath, options) => {
|
|
34
37
|
try {
|
|
38
|
+
if (options.output) {
|
|
39
|
+
validateOutputPath(options.output);
|
|
40
|
+
}
|
|
41
|
+
if (options.dryRun) {
|
|
42
|
+
outputResult({ dryRun: true, command: "ai transcribe", params: { audio: audioPath, output: options.output, language: options.language, format: options.format } });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
35
45
|
const apiKey = await getApiKey("OPENAI_API_KEY", "OpenAI", options.apiKey);
|
|
36
46
|
if (!apiKey) {
|
|
37
|
-
|
|
38
|
-
process.exit(1);
|
|
47
|
+
exitWithError(authError("OPENAI_API_KEY", "OpenAI"));
|
|
39
48
|
}
|
|
40
49
|
const spinner = ora("Initializing Whisper...").start();
|
|
41
50
|
const whisper = new WhisperProvider();
|
|
@@ -47,8 +56,8 @@ function _registerAudioCommands(aiCommand) {
|
|
|
47
56
|
spinner.text = "Transcribing...";
|
|
48
57
|
const result = await whisper.transcribe(audioBlob, options.language);
|
|
49
58
|
if (result.status === "failed") {
|
|
50
|
-
spinner.fail(
|
|
51
|
-
|
|
59
|
+
spinner.fail("Transcription failed");
|
|
60
|
+
exitWithError(apiError(`Transcription failed: ${result.error}`, true));
|
|
52
61
|
}
|
|
53
62
|
spinner.succeed(chalk.green("Transcription complete"));
|
|
54
63
|
console.log();
|
|
@@ -74,9 +83,7 @@ function _registerAudioCommands(aiCommand) {
|
|
|
74
83
|
}
|
|
75
84
|
}
|
|
76
85
|
catch (error) {
|
|
77
|
-
|
|
78
|
-
console.error(error);
|
|
79
|
-
process.exit(1);
|
|
86
|
+
exitWithError(apiError(`Transcription failed: ${error instanceof Error ? error.message : String(error)}`, true));
|
|
80
87
|
}
|
|
81
88
|
});
|
|
82
89
|
aiCommand
|
|
@@ -87,12 +94,19 @@ function _registerAudioCommands(aiCommand) {
|
|
|
87
94
|
.option("-o, --output <path>", "Output audio file path", "output.mp3")
|
|
88
95
|
.option("-v, --voice <id>", "Voice ID (default: Rachel)", "21m00Tcm4TlvDq8ikWAM")
|
|
89
96
|
.option("--list-voices", "List available voices")
|
|
97
|
+
.option("--dry-run", "Preview parameters without executing")
|
|
90
98
|
.action(async (text, options) => {
|
|
91
99
|
try {
|
|
100
|
+
if (options.output) {
|
|
101
|
+
validateOutputPath(options.output);
|
|
102
|
+
}
|
|
103
|
+
if (options.dryRun) {
|
|
104
|
+
outputResult({ dryRun: true, command: "ai tts", params: { text, output: options.output, voice: options.voice } });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
92
107
|
const apiKey = await getApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
|
|
93
108
|
if (!apiKey) {
|
|
94
|
-
|
|
95
|
-
process.exit(1);
|
|
109
|
+
exitWithError(authError("ELEVENLABS_API_KEY", "ElevenLabs"));
|
|
96
110
|
}
|
|
97
111
|
const elevenlabs = new ElevenLabsProvider();
|
|
98
112
|
await elevenlabs.initialize({ apiKey });
|
|
@@ -123,8 +137,8 @@ function _registerAudioCommands(aiCommand) {
|
|
|
123
137
|
voiceId: options.voice,
|
|
124
138
|
});
|
|
125
139
|
if (!result.success || !result.audioBuffer) {
|
|
126
|
-
spinner.fail(
|
|
127
|
-
|
|
140
|
+
spinner.fail("TTS generation failed");
|
|
141
|
+
exitWithError(apiError(result.error || "TTS generation failed", true));
|
|
128
142
|
}
|
|
129
143
|
const outputPath = resolve(process.cwd(), options.output);
|
|
130
144
|
await writeFile(outputPath, result.audioBuffer);
|
|
@@ -135,9 +149,7 @@ function _registerAudioCommands(aiCommand) {
|
|
|
135
149
|
console.log();
|
|
136
150
|
}
|
|
137
151
|
catch (error) {
|
|
138
|
-
|
|
139
|
-
console.error(error);
|
|
140
|
-
process.exit(1);
|
|
152
|
+
exitWithError(apiError(`TTS generation failed: ${error instanceof Error ? error.message : String(error)}`, true));
|
|
141
153
|
}
|
|
142
154
|
});
|
|
143
155
|
aiCommand
|
|
@@ -148,8 +160,7 @@ function _registerAudioCommands(aiCommand) {
|
|
|
148
160
|
try {
|
|
149
161
|
const apiKey = await getApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
|
|
150
162
|
if (!apiKey) {
|
|
151
|
-
|
|
152
|
-
process.exit(1);
|
|
163
|
+
exitWithError(authError("ELEVENLABS_API_KEY", "ElevenLabs"));
|
|
153
164
|
}
|
|
154
165
|
const spinner = ora("Fetching voices...").start();
|
|
155
166
|
const elevenlabs = new ElevenLabsProvider();
|
|
@@ -167,9 +178,7 @@ function _registerAudioCommands(aiCommand) {
|
|
|
167
178
|
console.log();
|
|
168
179
|
}
|
|
169
180
|
catch (error) {
|
|
170
|
-
|
|
171
|
-
console.error(error);
|
|
172
|
-
process.exit(1);
|
|
181
|
+
exitWithError(apiError(`Failed to fetch voices: ${error instanceof Error ? error.message : String(error)}`, true));
|
|
173
182
|
}
|
|
174
183
|
});
|
|
175
184
|
aiCommand
|
|
@@ -180,12 +189,19 @@ function _registerAudioCommands(aiCommand) {
|
|
|
180
189
|
.option("-o, --output <path>", "Output audio file path", "sound-effect.mp3")
|
|
181
190
|
.option("-d, --duration <seconds>", "Duration in seconds (0.5-22, default: auto)")
|
|
182
191
|
.option("--prompt-influence <value>", "Prompt influence (0-1, default: 0.3)")
|
|
192
|
+
.option("--dry-run", "Preview parameters without executing")
|
|
183
193
|
.action(async (prompt, options) => {
|
|
184
194
|
try {
|
|
195
|
+
if (options.output) {
|
|
196
|
+
validateOutputPath(options.output);
|
|
197
|
+
}
|
|
198
|
+
if (options.dryRun) {
|
|
199
|
+
outputResult({ dryRun: true, command: "ai sfx", params: { prompt, output: options.output, duration: options.duration, promptInfluence: options.promptInfluence } });
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
185
202
|
const apiKey = await getApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
|
|
186
203
|
if (!apiKey) {
|
|
187
|
-
|
|
188
|
-
process.exit(1);
|
|
204
|
+
exitWithError(authError("ELEVENLABS_API_KEY", "ElevenLabs"));
|
|
189
205
|
}
|
|
190
206
|
const spinner = ora("Generating sound effect...").start();
|
|
191
207
|
const elevenlabs = new ElevenLabsProvider();
|
|
@@ -195,8 +211,8 @@ function _registerAudioCommands(aiCommand) {
|
|
|
195
211
|
promptInfluence: options.promptInfluence ? parseFloat(options.promptInfluence) : undefined,
|
|
196
212
|
});
|
|
197
213
|
if (!result.success || !result.audioBuffer) {
|
|
198
|
-
spinner.fail(
|
|
199
|
-
|
|
214
|
+
spinner.fail("Sound effect generation failed");
|
|
215
|
+
exitWithError(apiError(result.error || "Sound effect generation failed", true));
|
|
200
216
|
}
|
|
201
217
|
const outputPath = resolve(process.cwd(), options.output);
|
|
202
218
|
await writeFile(outputPath, result.audioBuffer);
|
|
@@ -205,9 +221,7 @@ function _registerAudioCommands(aiCommand) {
|
|
|
205
221
|
console.log();
|
|
206
222
|
}
|
|
207
223
|
catch (error) {
|
|
208
|
-
|
|
209
|
-
console.error(error);
|
|
210
|
-
process.exit(1);
|
|
224
|
+
exitWithError(apiError(`Sound effect generation failed: ${error instanceof Error ? error.message : String(error)}`, true));
|
|
211
225
|
}
|
|
212
226
|
});
|
|
213
227
|
aiCommand
|
|
@@ -216,12 +230,19 @@ function _registerAudioCommands(aiCommand) {
|
|
|
216
230
|
.argument("<audio>", "Input audio file path")
|
|
217
231
|
.option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)")
|
|
218
232
|
.option("-o, --output <path>", "Output audio file path", "vocals.mp3")
|
|
233
|
+
.option("--dry-run", "Preview parameters without executing")
|
|
219
234
|
.action(async (audioPath, options) => {
|
|
220
235
|
try {
|
|
236
|
+
if (options.output) {
|
|
237
|
+
validateOutputPath(options.output);
|
|
238
|
+
}
|
|
239
|
+
if (options.dryRun) {
|
|
240
|
+
outputResult({ dryRun: true, command: "ai isolate", params: { audio: audioPath, output: options.output } });
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
221
243
|
const apiKey = await getApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
|
|
222
244
|
if (!apiKey) {
|
|
223
|
-
|
|
224
|
-
process.exit(1);
|
|
245
|
+
exitWithError(authError("ELEVENLABS_API_KEY", "ElevenLabs"));
|
|
225
246
|
}
|
|
226
247
|
const spinner = ora("Reading audio file...").start();
|
|
227
248
|
const absPath = resolve(process.cwd(), audioPath);
|
|
@@ -231,8 +252,8 @@ function _registerAudioCommands(aiCommand) {
|
|
|
231
252
|
await elevenlabs.initialize({ apiKey });
|
|
232
253
|
const result = await elevenlabs.isolateVocals(audioBuffer);
|
|
233
254
|
if (!result.success || !result.audioBuffer) {
|
|
234
|
-
spinner.fail(
|
|
235
|
-
|
|
255
|
+
spinner.fail("Audio isolation failed");
|
|
256
|
+
exitWithError(apiError(result.error || "Audio isolation failed", true));
|
|
236
257
|
}
|
|
237
258
|
const outputPath = resolve(process.cwd(), options.output);
|
|
238
259
|
await writeFile(outputPath, result.audioBuffer);
|
|
@@ -241,9 +262,7 @@ function _registerAudioCommands(aiCommand) {
|
|
|
241
262
|
console.log();
|
|
242
263
|
}
|
|
243
264
|
catch (error) {
|
|
244
|
-
|
|
245
|
-
console.error(error);
|
|
246
|
-
process.exit(1);
|
|
265
|
+
exitWithError(apiError(`Audio isolation failed: ${error instanceof Error ? error.message : String(error)}`, true));
|
|
247
266
|
}
|
|
248
267
|
});
|
|
249
268
|
aiCommand
|
|
@@ -256,12 +275,16 @@ function _registerAudioCommands(aiCommand) {
|
|
|
256
275
|
.option("--labels <json>", "Labels as JSON (e.g., '{\"accent\": \"american\"}')")
|
|
257
276
|
.option("--remove-noise", "Remove background noise from samples")
|
|
258
277
|
.option("--list", "List all available voices")
|
|
278
|
+
.option("--dry-run", "Preview parameters without executing")
|
|
259
279
|
.action(async (samples, options) => {
|
|
260
280
|
try {
|
|
281
|
+
if (options.dryRun) {
|
|
282
|
+
outputResult({ dryRun: true, command: "ai voice-clone", params: { samples, name: options.name, description: options.description, removeNoise: options.removeNoise } });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
261
285
|
const apiKey = await getApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
|
|
262
286
|
if (!apiKey) {
|
|
263
|
-
|
|
264
|
-
process.exit(1);
|
|
287
|
+
exitWithError(authError("ELEVENLABS_API_KEY", "ElevenLabs"));
|
|
265
288
|
}
|
|
266
289
|
const elevenlabs = new ElevenLabsProvider();
|
|
267
290
|
await elevenlabs.initialize({ apiKey });
|
|
@@ -286,20 +309,18 @@ function _registerAudioCommands(aiCommand) {
|
|
|
286
309
|
}
|
|
287
310
|
// Clone voice mode
|
|
288
311
|
if (!options.name) {
|
|
289
|
-
|
|
290
|
-
process.exit(1);
|
|
312
|
+
exitWithError(usageError("Voice name is required. Use --name <name>"));
|
|
291
313
|
}
|
|
292
314
|
if (!samples || samples.length === 0) {
|
|
293
|
-
|
|
294
|
-
process.exit(1);
|
|
315
|
+
exitWithError(usageError("At least one audio sample is required"));
|
|
295
316
|
}
|
|
296
317
|
const spinner = ora("Reading audio samples...").start();
|
|
297
318
|
const audioBuffers = [];
|
|
298
319
|
for (const samplePath of samples) {
|
|
299
320
|
const absPath = resolve(process.cwd(), samplePath);
|
|
300
321
|
if (!existsSync(absPath)) {
|
|
301
|
-
spinner.fail(
|
|
302
|
-
|
|
322
|
+
spinner.fail("File not found");
|
|
323
|
+
exitWithError(notFoundError(samplePath));
|
|
303
324
|
}
|
|
304
325
|
const buffer = await readFile(absPath);
|
|
305
326
|
audioBuffers.push(buffer);
|
|
@@ -313,8 +334,8 @@ function _registerAudioCommands(aiCommand) {
|
|
|
313
334
|
removeBackgroundNoise: options.removeNoise,
|
|
314
335
|
});
|
|
315
336
|
if (!result.success) {
|
|
316
|
-
spinner.fail(
|
|
317
|
-
|
|
337
|
+
spinner.fail("Voice cloning failed");
|
|
338
|
+
exitWithError(apiError(result.error || "Voice cloning failed", true));
|
|
318
339
|
}
|
|
319
340
|
spinner.succeed(chalk.green("Voice cloned successfully"));
|
|
320
341
|
console.log();
|
|
@@ -328,9 +349,7 @@ function _registerAudioCommands(aiCommand) {
|
|
|
328
349
|
console.log();
|
|
329
350
|
}
|
|
330
351
|
catch (error) {
|
|
331
|
-
|
|
332
|
-
console.error(error);
|
|
333
|
-
process.exit(1);
|
|
352
|
+
exitWithError(apiError(`Voice cloning failed: ${error instanceof Error ? error.message : String(error)}`, true));
|
|
334
353
|
}
|
|
335
354
|
});
|
|
336
355
|
aiCommand
|
|
@@ -343,12 +362,19 @@ function _registerAudioCommands(aiCommand) {
|
|
|
343
362
|
.option("--model <model>", "Model variant: large, stereo-large, melody-large, stereo-melody-large", "stereo-large")
|
|
344
363
|
.option("-o, --output <path>", "Output audio file path", "music.mp3")
|
|
345
364
|
.option("--no-wait", "Don't wait for generation to complete (async mode)")
|
|
365
|
+
.option("--dry-run", "Preview parameters without executing")
|
|
346
366
|
.action(async (prompt, options) => {
|
|
347
367
|
try {
|
|
368
|
+
if (options.output) {
|
|
369
|
+
validateOutputPath(options.output);
|
|
370
|
+
}
|
|
371
|
+
if (options.dryRun) {
|
|
372
|
+
outputResult({ dryRun: true, command: "ai music", params: { prompt, output: options.output, duration: options.duration, model: options.model, melody: options.melody } });
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
348
375
|
const apiKey = await getApiKey("REPLICATE_API_TOKEN", "Replicate", options.apiKey);
|
|
349
376
|
if (!apiKey) {
|
|
350
|
-
|
|
351
|
-
process.exit(1);
|
|
377
|
+
exitWithError(authError("REPLICATE_API_TOKEN", "Replicate"));
|
|
352
378
|
}
|
|
353
379
|
const replicate = new ReplicateProvider();
|
|
354
380
|
await replicate.initialize({ apiKey });
|
|
@@ -360,14 +386,12 @@ function _registerAudioCommands(aiCommand) {
|
|
|
360
386
|
spinner.text = "Uploading melody reference...";
|
|
361
387
|
const absPath = resolve(process.cwd(), options.melody);
|
|
362
388
|
if (!existsSync(absPath)) {
|
|
363
|
-
spinner.fail(
|
|
364
|
-
|
|
389
|
+
spinner.fail("Melody file not found");
|
|
390
|
+
exitWithError(notFoundError(options.melody));
|
|
365
391
|
}
|
|
366
392
|
// For Replicate, we need a publicly accessible URL
|
|
367
393
|
// In practice, users would need to host the file or use a data URL
|
|
368
|
-
|
|
369
|
-
console.log(chalk.yellow("Please upload your melody file and provide the URL"));
|
|
370
|
-
process.exit(1);
|
|
394
|
+
exitWithError(usageError("Melody conditioning requires a publicly accessible URL. Please upload your melody file and provide the URL."));
|
|
371
395
|
}
|
|
372
396
|
const result = await replicate.generateMusic(prompt, {
|
|
373
397
|
duration,
|
|
@@ -375,8 +399,8 @@ function _registerAudioCommands(aiCommand) {
|
|
|
375
399
|
melodyUrl,
|
|
376
400
|
});
|
|
377
401
|
if (!result.success || !result.taskId) {
|
|
378
|
-
spinner.fail(
|
|
379
|
-
|
|
402
|
+
spinner.fail("Music generation failed");
|
|
403
|
+
exitWithError(apiError(result.error || "Music generation failed", true));
|
|
380
404
|
}
|
|
381
405
|
if (!options.wait) {
|
|
382
406
|
spinner.succeed(chalk.green("Music generation started"));
|
|
@@ -388,14 +412,14 @@ function _registerAudioCommands(aiCommand) {
|
|
|
388
412
|
spinner.text = "Generating music (this may take a few minutes)...";
|
|
389
413
|
const finalResult = await replicate.waitForMusic(result.taskId);
|
|
390
414
|
if (!finalResult.success || !finalResult.audioUrl) {
|
|
391
|
-
spinner.fail(
|
|
392
|
-
|
|
415
|
+
spinner.fail("Music generation failed");
|
|
416
|
+
exitWithError(apiError(finalResult.error || "Music generation failed", true));
|
|
393
417
|
}
|
|
394
418
|
spinner.text = "Downloading generated audio...";
|
|
395
419
|
const response = await fetch(finalResult.audioUrl);
|
|
396
420
|
if (!response.ok) {
|
|
397
|
-
spinner.fail(
|
|
398
|
-
|
|
421
|
+
spinner.fail("Failed to download generated audio");
|
|
422
|
+
exitWithError(apiError("Failed to download generated audio", true));
|
|
399
423
|
}
|
|
400
424
|
const audioBuffer = Buffer.from(await response.arrayBuffer());
|
|
401
425
|
const outputPath = resolve(process.cwd(), options.output);
|
|
@@ -408,9 +432,7 @@ function _registerAudioCommands(aiCommand) {
|
|
|
408
432
|
console.log();
|
|
409
433
|
}
|
|
410
434
|
catch (error) {
|
|
411
|
-
|
|
412
|
-
console.error(error);
|
|
413
|
-
process.exit(1);
|
|
435
|
+
exitWithError(apiError(`Music generation failed: ${error instanceof Error ? error.message : String(error)}`, true));
|
|
414
436
|
}
|
|
415
437
|
});
|
|
416
438
|
aiCommand
|
|
@@ -422,8 +444,7 @@ function _registerAudioCommands(aiCommand) {
|
|
|
422
444
|
try {
|
|
423
445
|
const apiKey = await getApiKey("REPLICATE_API_TOKEN", "Replicate", options.apiKey);
|
|
424
446
|
if (!apiKey) {
|
|
425
|
-
|
|
426
|
-
process.exit(1);
|
|
447
|
+
exitWithError(authError("REPLICATE_API_TOKEN", "Replicate"));
|
|
427
448
|
}
|
|
428
449
|
const replicate = new ReplicateProvider();
|
|
429
450
|
await replicate.initialize({ apiKey });
|
|
@@ -446,9 +467,7 @@ function _registerAudioCommands(aiCommand) {
|
|
|
446
467
|
console.log();
|
|
447
468
|
}
|
|
448
469
|
catch (error) {
|
|
449
|
-
|
|
450
|
-
console.error(error);
|
|
451
|
-
process.exit(1);
|
|
470
|
+
exitWithError(apiError(`Failed to get music status: ${error instanceof Error ? error.message : String(error)}`, true));
|
|
452
471
|
}
|
|
453
472
|
});
|
|
454
473
|
aiCommand
|
|
@@ -462,12 +481,19 @@ function _registerAudioCommands(aiCommand) {
|
|
|
462
481
|
.option("--no-denoise", "Disable noise reduction")
|
|
463
482
|
.option("--enhance", "Enable audio enhancement")
|
|
464
483
|
.option("--noise-floor <dB>", "FFmpeg noise floor threshold", "-30")
|
|
484
|
+
.option("--dry-run", "Preview parameters without executing")
|
|
465
485
|
.action(async (audioPath, options) => {
|
|
466
486
|
try {
|
|
487
|
+
if (options.output) {
|
|
488
|
+
validateOutputPath(options.output);
|
|
489
|
+
}
|
|
467
490
|
const absPath = resolve(process.cwd(), audioPath);
|
|
468
491
|
if (!existsSync(absPath)) {
|
|
469
|
-
|
|
470
|
-
|
|
492
|
+
exitWithError(notFoundError(audioPath));
|
|
493
|
+
}
|
|
494
|
+
if (options.dryRun) {
|
|
495
|
+
outputResult({ dryRun: true, command: "ai audio-restore", params: { audio: audioPath, output: options.output, ffmpeg: options.ffmpeg, denoise: options.denoise, enhance: options.enhance, noiseFloor: options.noiseFloor } });
|
|
496
|
+
return;
|
|
471
497
|
}
|
|
472
498
|
// Default output path
|
|
473
499
|
const ext = extname(audioPath);
|
|
@@ -500,36 +526,24 @@ function _registerAudioCommands(aiCommand) {
|
|
|
500
526
|
console.log();
|
|
501
527
|
}
|
|
502
528
|
catch (error) {
|
|
503
|
-
spinner.fail(
|
|
504
|
-
|
|
505
|
-
console.error(chalk.dim(error.message));
|
|
506
|
-
}
|
|
507
|
-
process.exit(1);
|
|
529
|
+
spinner.fail("FFmpeg restoration failed");
|
|
530
|
+
exitWithError(generalError(`FFmpeg restoration failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
508
531
|
}
|
|
509
532
|
return;
|
|
510
533
|
}
|
|
511
534
|
// Replicate AI mode
|
|
512
535
|
const apiKey = await getApiKey("REPLICATE_API_TOKEN", "Replicate", options.apiKey);
|
|
513
536
|
if (!apiKey) {
|
|
514
|
-
|
|
515
|
-
console.error(chalk.dim("Or use --ffmpeg for free FFmpeg-based restoration"));
|
|
516
|
-
process.exit(1);
|
|
537
|
+
exitWithError(authError("REPLICATE_API_TOKEN", "Replicate"));
|
|
517
538
|
}
|
|
518
539
|
const replicate = new ReplicateProvider();
|
|
519
540
|
await replicate.initialize({ apiKey });
|
|
520
541
|
// For Replicate, we need a publicly accessible URL
|
|
521
542
|
// This is a limitation - users need to upload their file first
|
|
522
|
-
|
|
523
|
-
console.log(chalk.yellow("For local files, use --ffmpeg for free local processing"));
|
|
524
|
-
console.log();
|
|
525
|
-
console.log(chalk.dim("Example with FFmpeg:"));
|
|
526
|
-
console.log(chalk.dim(` pnpm vibe ai audio-restore ${audioPath} --ffmpeg`));
|
|
527
|
-
process.exit(1);
|
|
543
|
+
exitWithError(usageError("Replicate requires a publicly accessible audio URL. For local files, use --ffmpeg for free local processing.", `pnpm vibe ai audio-restore ${audioPath} --ffmpeg`));
|
|
528
544
|
}
|
|
529
545
|
catch (error) {
|
|
530
|
-
|
|
531
|
-
console.error(error);
|
|
532
|
-
process.exit(1);
|
|
546
|
+
exitWithError(apiError(`Audio restoration failed: ${error instanceof Error ? error.message : String(error)}`, true));
|
|
533
547
|
}
|
|
534
548
|
});
|
|
535
549
|
aiCommand
|
|
@@ -541,33 +555,35 @@ function _registerAudioCommands(aiCommand) {
|
|
|
541
555
|
.option("-v, --voice <id>", "ElevenLabs voice ID for output")
|
|
542
556
|
.option("--analyze-only", "Only analyze and show timing, don't generate audio")
|
|
543
557
|
.option("-o, --output <path>", "Output file path")
|
|
558
|
+
.option("--dry-run", "Preview parameters without executing")
|
|
544
559
|
.action(async (mediaPath, options) => {
|
|
545
560
|
try {
|
|
561
|
+
if (options.output) {
|
|
562
|
+
validateOutputPath(options.output);
|
|
563
|
+
}
|
|
546
564
|
if (!options.language) {
|
|
547
|
-
|
|
548
|
-
|
|
565
|
+
exitWithError(usageError("Target language is required. Use -l or --language"));
|
|
566
|
+
}
|
|
567
|
+
if (options.dryRun) {
|
|
568
|
+
outputResult({ dryRun: true, command: "ai dub", params: { media: mediaPath, output: options.output, language: options.language, source: options.source, voice: options.voice, analyzeOnly: options.analyzeOnly } });
|
|
569
|
+
return;
|
|
549
570
|
}
|
|
550
571
|
const absPath = resolve(process.cwd(), mediaPath);
|
|
551
572
|
if (!existsSync(absPath)) {
|
|
552
|
-
|
|
553
|
-
process.exit(1);
|
|
573
|
+
exitWithError(notFoundError(mediaPath));
|
|
554
574
|
}
|
|
555
575
|
// Check required API keys
|
|
556
576
|
const openaiKey = await getApiKey("OPENAI_API_KEY", "OpenAI", undefined);
|
|
557
577
|
const anthropicKey = await getApiKey("ANTHROPIC_API_KEY", "Anthropic", undefined);
|
|
558
578
|
const elevenlabsKey = await getApiKey("ELEVENLABS_API_KEY", "ElevenLabs", undefined);
|
|
559
579
|
if (!openaiKey) {
|
|
560
|
-
|
|
561
|
-
process.exit(1);
|
|
580
|
+
exitWithError(authError("OPENAI_API_KEY", "OpenAI"));
|
|
562
581
|
}
|
|
563
582
|
if (!anthropicKey) {
|
|
564
|
-
|
|
565
|
-
process.exit(1);
|
|
583
|
+
exitWithError(authError("ANTHROPIC_API_KEY", "Anthropic"));
|
|
566
584
|
}
|
|
567
585
|
if (!options.analyzeOnly && !elevenlabsKey) {
|
|
568
|
-
|
|
569
|
-
console.error(chalk.dim("Or use --analyze-only to preview timing without generating audio"));
|
|
570
|
-
process.exit(1);
|
|
586
|
+
exitWithError(authError("ELEVENLABS_API_KEY", "ElevenLabs"));
|
|
571
587
|
}
|
|
572
588
|
const spinner = ora("Extracting audio...").start();
|
|
573
589
|
// Check if input is video
|
|
@@ -582,8 +598,8 @@ function _registerAudioCommands(aiCommand) {
|
|
|
582
598
|
audioPath = tempAudioPath;
|
|
583
599
|
}
|
|
584
600
|
catch (error) {
|
|
585
|
-
spinner.fail(
|
|
586
|
-
|
|
601
|
+
spinner.fail("Failed to extract audio from video");
|
|
602
|
+
exitWithError(generalError(`Failed to extract audio from video: ${error instanceof Error ? error.message : String(error)}`));
|
|
587
603
|
}
|
|
588
604
|
}
|
|
589
605
|
// Step 2: Transcribe with Whisper
|
|
@@ -594,8 +610,8 @@ function _registerAudioCommands(aiCommand) {
|
|
|
594
610
|
const audioBlob = new Blob([audioBuffer]);
|
|
595
611
|
const transcriptResult = await whisper.transcribe(audioBlob, options.source);
|
|
596
612
|
if (transcriptResult.status === "failed" || !transcriptResult.segments) {
|
|
597
|
-
spinner.fail(
|
|
598
|
-
|
|
613
|
+
spinner.fail("Transcription failed");
|
|
614
|
+
exitWithError(apiError(`Transcription failed: ${transcriptResult.error}`, true));
|
|
599
615
|
}
|
|
600
616
|
// Step 3: Translate with Claude
|
|
601
617
|
spinner.text = "Translating...";
|
|
@@ -733,9 +749,7 @@ function _registerAudioCommands(aiCommand) {
|
|
|
733
749
|
}
|
|
734
750
|
}
|
|
735
751
|
catch (error) {
|
|
736
|
-
|
|
737
|
-
console.error(error);
|
|
738
|
-
process.exit(1);
|
|
752
|
+
exitWithError(apiError(`Dubbing failed: ${error instanceof Error ? error.message : String(error)}`, true));
|
|
739
753
|
}
|
|
740
754
|
});
|
|
741
755
|
// ============================================
|
|
@@ -752,16 +766,22 @@ function _registerAudioCommands(aiCommand) {
|
|
|
752
766
|
.option("-r, --ratio <ratio>", "Compression ratio", "3")
|
|
753
767
|
.option("-a, --attack <ms>", "Attack time in ms", "20")
|
|
754
768
|
.option("-l, --release <ms>", "Release time in ms", "200")
|
|
769
|
+
.option("--dry-run", "Preview parameters without executing")
|
|
755
770
|
.action(async (musicPath, options) => {
|
|
756
771
|
try {
|
|
772
|
+
if (options.output) {
|
|
773
|
+
validateOutputPath(options.output);
|
|
774
|
+
}
|
|
757
775
|
if (!options.voice) {
|
|
758
|
-
|
|
759
|
-
|
|
776
|
+
exitWithError(usageError("Voice track required. Use --voice <path>"));
|
|
777
|
+
}
|
|
778
|
+
if (options.dryRun) {
|
|
779
|
+
outputResult({ dryRun: true, command: "ai duck", params: { music: musicPath, output: options.output, voice: options.voice, threshold: options.threshold, ratio: options.ratio, attack: options.attack, release: options.release } });
|
|
780
|
+
return;
|
|
760
781
|
}
|
|
761
782
|
// Check FFmpeg availability
|
|
762
783
|
if (!commandExists("ffmpeg")) {
|
|
763
|
-
|
|
764
|
-
process.exit(1);
|
|
784
|
+
exitWithError(generalError("FFmpeg not found. Please install FFmpeg."));
|
|
765
785
|
}
|
|
766
786
|
const spinner = ora("Processing audio ducking...").start();
|
|
767
787
|
const absMusic = resolve(process.cwd(), musicPath);
|
|
@@ -791,9 +811,7 @@ function _registerAudioCommands(aiCommand) {
|
|
|
791
811
|
console.log();
|
|
792
812
|
}
|
|
793
813
|
catch (error) {
|
|
794
|
-
|
|
795
|
-
console.error(error);
|
|
796
|
-
process.exit(1);
|
|
814
|
+
exitWithError(generalError(`Audio ducking failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
797
815
|
}
|
|
798
816
|
});
|
|
799
817
|
// AI Color Grading
|