@vibeframe/cli 0.27.0 → 0.30.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/LICENSE +21 -0
- package/dist/agent/adapters/index.d.ts +1 -0
- package/dist/agent/adapters/index.d.ts.map +1 -1
- package/dist/agent/adapters/index.js +5 -0
- package/dist/agent/adapters/index.js.map +1 -1
- package/dist/agent/adapters/openrouter.d.ts +16 -0
- package/dist/agent/adapters/openrouter.d.ts.map +1 -0
- package/dist/agent/adapters/openrouter.js +100 -0
- package/dist/agent/adapters/openrouter.js.map +1 -0
- package/dist/agent/types.d.ts +1 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +3 -1
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/ai-edit-cli.d.ts.map +1 -1
- package/dist/commands/ai-edit-cli.js +18 -0
- package/dist/commands/ai-edit-cli.js.map +1 -1
- package/dist/commands/generate.js +14 -0
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/schema.d.ts +1 -0
- package/dist/commands/schema.d.ts.map +1 -1
- package/dist/commands/schema.js +122 -21
- package/dist/commands/schema.js.map +1 -1
- package/dist/commands/setup.js +5 -2
- package/dist/commands/setup.js.map +1 -1
- package/dist/config/schema.d.ts +2 -1
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +2 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/index.js +0 -0
- package/package.json +16 -12
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-lint.log +0 -21
- package/.turbo/turbo-test.log +0 -689
- package/src/agent/adapters/claude.ts +0 -143
- package/src/agent/adapters/gemini.ts +0 -159
- package/src/agent/adapters/index.ts +0 -61
- package/src/agent/adapters/ollama.ts +0 -231
- package/src/agent/adapters/openai.ts +0 -116
- package/src/agent/adapters/xai.ts +0 -119
- package/src/agent/index.ts +0 -251
- package/src/agent/memory/index.ts +0 -151
- package/src/agent/prompts/system.ts +0 -106
- package/src/agent/tools/ai-editing.ts +0 -845
- package/src/agent/tools/ai-generation.ts +0 -1073
- package/src/agent/tools/ai-pipeline.ts +0 -1055
- package/src/agent/tools/ai.ts +0 -21
- package/src/agent/tools/batch.ts +0 -429
- package/src/agent/tools/e2e.test.ts +0 -545
- package/src/agent/tools/export.ts +0 -184
- package/src/agent/tools/filesystem.ts +0 -237
- package/src/agent/tools/index.ts +0 -150
- package/src/agent/tools/integration.test.ts +0 -775
- package/src/agent/tools/media.ts +0 -697
- package/src/agent/tools/project.ts +0 -313
- package/src/agent/tools/timeline.ts +0 -951
- package/src/agent/types.ts +0 -68
- package/src/commands/agent.ts +0 -340
- package/src/commands/ai-analyze.ts +0 -429
- package/src/commands/ai-animated-caption.ts +0 -390
- package/src/commands/ai-audio.ts +0 -941
- package/src/commands/ai-broll.ts +0 -490
- package/src/commands/ai-edit-cli.ts +0 -658
- package/src/commands/ai-edit.ts +0 -1542
- package/src/commands/ai-fill-gaps.ts +0 -566
- package/src/commands/ai-helpers.ts +0 -65
- package/src/commands/ai-highlights.ts +0 -1303
- package/src/commands/ai-image.ts +0 -761
- package/src/commands/ai-motion.ts +0 -347
- package/src/commands/ai-narrate.ts +0 -451
- package/src/commands/ai-review.ts +0 -309
- package/src/commands/ai-script-pipeline-cli.ts +0 -1710
- package/src/commands/ai-script-pipeline.ts +0 -1365
- package/src/commands/ai-suggest-edit.ts +0 -264
- package/src/commands/ai-video-fx.ts +0 -445
- package/src/commands/ai-video.ts +0 -915
- package/src/commands/ai-viral.ts +0 -595
- package/src/commands/ai-visual-fx.ts +0 -601
- package/src/commands/ai.test.ts +0 -627
- package/src/commands/ai.ts +0 -307
- package/src/commands/analyze.ts +0 -282
- package/src/commands/audio.ts +0 -644
- package/src/commands/batch.test.ts +0 -279
- package/src/commands/batch.ts +0 -440
- package/src/commands/detect.ts +0 -329
- package/src/commands/doctor.ts +0 -237
- package/src/commands/edit-cmd.ts +0 -1014
- package/src/commands/export.ts +0 -918
- package/src/commands/generate.ts +0 -2146
- package/src/commands/media.ts +0 -177
- package/src/commands/output.ts +0 -142
- package/src/commands/pipeline.ts +0 -398
- package/src/commands/project.test.ts +0 -127
- package/src/commands/project.ts +0 -149
- package/src/commands/sanitize.ts +0 -60
- package/src/commands/schema.ts +0 -130
- package/src/commands/setup.ts +0 -509
- package/src/commands/timeline.test.ts +0 -499
- package/src/commands/timeline.ts +0 -529
- package/src/commands/validate.ts +0 -77
- package/src/config/config.test.ts +0 -197
- package/src/config/index.ts +0 -125
- package/src/config/schema.ts +0 -82
- package/src/engine/index.ts +0 -2
- package/src/engine/project.test.ts +0 -702
- package/src/engine/project.ts +0 -439
- package/src/index.ts +0 -146
- package/src/utils/api-key.test.ts +0 -41
- package/src/utils/api-key.ts +0 -247
- package/src/utils/audio.ts +0 -83
- package/src/utils/exec-safe.ts +0 -75
- package/src/utils/first-run.ts +0 -52
- package/src/utils/provider-resolver.ts +0 -56
- package/src/utils/remotion.ts +0 -951
- package/src/utils/subtitle.test.ts +0 -227
- package/src/utils/subtitle.ts +0 -169
- package/src/utils/tty.ts +0 -196
- package/tsconfig.json +0 -20
package/src/commands/batch.ts
DELETED
|
@@ -1,440 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { readFile, writeFile, readdir } from "node:fs/promises";
|
|
3
|
-
import { resolve, basename, extname, join } from "node:path";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import ora from "ora";
|
|
6
|
-
import { Project, type ProjectFile } from "../engine/index.js";
|
|
7
|
-
import type { MediaType, EffectType } from "@vibeframe/core/timeline";
|
|
8
|
-
|
|
9
|
-
export const batchCommand = new Command("batch")
|
|
10
|
-
.description("Batch operations for processing multiple items");
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Detect media type from file extension
|
|
14
|
-
*/
|
|
15
|
-
function detectMediaType(filePath: string): MediaType {
|
|
16
|
-
const ext = extname(filePath).toLowerCase();
|
|
17
|
-
const videoExts = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
|
|
18
|
-
const audioExts = [".mp3", ".wav", ".aac", ".flac", ".ogg", ".m4a"];
|
|
19
|
-
const imageExts = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"];
|
|
20
|
-
|
|
21
|
-
if (videoExts.includes(ext)) return "video";
|
|
22
|
-
if (audioExts.includes(ext)) return "audio";
|
|
23
|
-
if (imageExts.includes(ext)) return "image";
|
|
24
|
-
return "video"; // Default to video
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Check if file is a media file
|
|
29
|
-
*/
|
|
30
|
-
function isMediaFile(filePath: string): boolean {
|
|
31
|
-
const ext = extname(filePath).toLowerCase();
|
|
32
|
-
const mediaExts = [
|
|
33
|
-
".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v",
|
|
34
|
-
".mp3", ".wav", ".aac", ".flac", ".ogg", ".m4a",
|
|
35
|
-
".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp",
|
|
36
|
-
];
|
|
37
|
-
return mediaExts.includes(ext);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ============ batch import ============
|
|
41
|
-
|
|
42
|
-
batchCommand
|
|
43
|
-
.command("import")
|
|
44
|
-
.description("Import multiple media files from a directory")
|
|
45
|
-
.argument("<project>", "Project file path")
|
|
46
|
-
.argument("<directory>", "Directory containing media files")
|
|
47
|
-
.option("-r, --recursive", "Search subdirectories", false)
|
|
48
|
-
.option("-d, --duration <seconds>", "Default duration for images", "5")
|
|
49
|
-
.option("--filter <pattern>", "Filter files by extension (e.g., '.mp4,.mov')")
|
|
50
|
-
.action(async (projectPath: string, directory: string, options) => {
|
|
51
|
-
const spinner = ora("Scanning directory...").start();
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const filePath = resolve(process.cwd(), projectPath);
|
|
55
|
-
const content = await readFile(filePath, "utf-8");
|
|
56
|
-
const data: ProjectFile = JSON.parse(content);
|
|
57
|
-
const project = Project.fromJSON(data);
|
|
58
|
-
|
|
59
|
-
const dirPath = resolve(process.cwd(), directory);
|
|
60
|
-
const filterExts = options.filter
|
|
61
|
-
? options.filter.split(",").map((e: string) => e.trim().toLowerCase())
|
|
62
|
-
: null;
|
|
63
|
-
|
|
64
|
-
// Collect media files
|
|
65
|
-
const mediaFiles: string[] = [];
|
|
66
|
-
|
|
67
|
-
const scanDir = async (dir: string): Promise<void> => {
|
|
68
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
69
|
-
|
|
70
|
-
for (const entry of entries) {
|
|
71
|
-
const entryPath = join(dir, entry.name);
|
|
72
|
-
|
|
73
|
-
if (entry.isDirectory() && options.recursive) {
|
|
74
|
-
await scanDir(entryPath);
|
|
75
|
-
} else if (entry.isFile()) {
|
|
76
|
-
const ext = extname(entry.name).toLowerCase();
|
|
77
|
-
const matchesFilter = !filterExts || filterExts.includes(ext);
|
|
78
|
-
|
|
79
|
-
if (matchesFilter && isMediaFile(entryPath)) {
|
|
80
|
-
mediaFiles.push(entryPath);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
await scanDir(dirPath);
|
|
87
|
-
|
|
88
|
-
if (mediaFiles.length === 0) {
|
|
89
|
-
spinner.fail(chalk.red("No media files found in directory"));
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Sort files alphabetically
|
|
94
|
-
mediaFiles.sort();
|
|
95
|
-
|
|
96
|
-
spinner.text = `Importing ${mediaFiles.length} files...`;
|
|
97
|
-
|
|
98
|
-
const addedSources: string[] = [];
|
|
99
|
-
const defaultDuration = parseFloat(options.duration);
|
|
100
|
-
|
|
101
|
-
for (const mediaFile of mediaFiles) {
|
|
102
|
-
const mediaName = basename(mediaFile);
|
|
103
|
-
const mediaType = detectMediaType(mediaFile);
|
|
104
|
-
const duration = mediaType === "image" ? defaultDuration : 0;
|
|
105
|
-
|
|
106
|
-
const source = project.addSource({
|
|
107
|
-
name: mediaName,
|
|
108
|
-
type: mediaType,
|
|
109
|
-
url: mediaFile,
|
|
110
|
-
duration,
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
addedSources.push(source.id);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
await writeFile(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
117
|
-
|
|
118
|
-
spinner.succeed(chalk.green(`Imported ${addedSources.length} media files`));
|
|
119
|
-
console.log();
|
|
120
|
-
|
|
121
|
-
for (const file of mediaFiles) {
|
|
122
|
-
console.log(chalk.dim(" +"), basename(file));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
console.log();
|
|
126
|
-
} catch (error) {
|
|
127
|
-
spinner.fail(chalk.red("Import failed"));
|
|
128
|
-
if (error instanceof Error) {
|
|
129
|
-
console.error(chalk.red(error.message));
|
|
130
|
-
}
|
|
131
|
-
process.exit(1);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// ============ batch concat ============
|
|
136
|
-
|
|
137
|
-
batchCommand
|
|
138
|
-
.command("concat")
|
|
139
|
-
.description("Concatenate multiple sources into sequential clips")
|
|
140
|
-
.argument("<project>", "Project file path")
|
|
141
|
-
.argument("[source-ids...]", "Source IDs to concatenate (or --all)")
|
|
142
|
-
.option("--all", "Concatenate all sources in order", false)
|
|
143
|
-
.option("--track <track-id>", "Track to place clips on")
|
|
144
|
-
.option("--start <seconds>", "Starting time", "0")
|
|
145
|
-
.option("--gap <seconds>", "Gap between clips", "0")
|
|
146
|
-
.action(async (projectPath: string, sourceIds: string[], options) => {
|
|
147
|
-
const spinner = ora("Creating clips...").start();
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
const filePath = resolve(process.cwd(), projectPath);
|
|
151
|
-
const content = await readFile(filePath, "utf-8");
|
|
152
|
-
const data: ProjectFile = JSON.parse(content);
|
|
153
|
-
const project = Project.fromJSON(data);
|
|
154
|
-
|
|
155
|
-
const sources = project.getSources();
|
|
156
|
-
|
|
157
|
-
if (sources.length === 0) {
|
|
158
|
-
spinner.fail(chalk.red("No sources in project"));
|
|
159
|
-
process.exit(1);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Get sources to concatenate
|
|
163
|
-
let selectedSources = sources;
|
|
164
|
-
if (!options.all && sourceIds.length > 0) {
|
|
165
|
-
selectedSources = sourceIds
|
|
166
|
-
.map((id) => sources.find((s) => s.id === id))
|
|
167
|
-
.filter((s): s is NonNullable<typeof s> => s !== undefined);
|
|
168
|
-
|
|
169
|
-
if (selectedSources.length === 0) {
|
|
170
|
-
spinner.fail(chalk.red("No matching sources found"));
|
|
171
|
-
process.exit(1);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Determine track
|
|
176
|
-
const tracks = project.getTracks();
|
|
177
|
-
let trackId = options.track;
|
|
178
|
-
|
|
179
|
-
if (!trackId) {
|
|
180
|
-
// Use first video track for video, first audio track for audio
|
|
181
|
-
const firstSource = selectedSources[0];
|
|
182
|
-
const matchingTrack = tracks.find((t) => t.type === firstSource.type);
|
|
183
|
-
trackId = matchingTrack?.id || tracks[0].id;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const startTime = parseFloat(options.start);
|
|
187
|
-
const gap = parseFloat(options.gap);
|
|
188
|
-
let currentTime = startTime;
|
|
189
|
-
const addedClips: string[] = [];
|
|
190
|
-
|
|
191
|
-
for (const source of selectedSources) {
|
|
192
|
-
const clip = project.addClip({
|
|
193
|
-
sourceId: source.id,
|
|
194
|
-
trackId,
|
|
195
|
-
startTime: currentTime,
|
|
196
|
-
duration: source.duration,
|
|
197
|
-
sourceStartOffset: 0,
|
|
198
|
-
sourceEndOffset: source.duration,
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
addedClips.push(clip.id);
|
|
202
|
-
currentTime += source.duration + gap;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
await writeFile(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
206
|
-
|
|
207
|
-
spinner.succeed(chalk.green(`Created ${addedClips.length} clips`));
|
|
208
|
-
console.log();
|
|
209
|
-
console.log(chalk.dim(" Total duration:"), `${currentTime - gap}s`);
|
|
210
|
-
console.log(chalk.dim(" Track:"), trackId);
|
|
211
|
-
|
|
212
|
-
for (let i = 0; i < selectedSources.length; i++) {
|
|
213
|
-
console.log(chalk.dim(` ${i + 1}.`), selectedSources[i].name);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
console.log();
|
|
217
|
-
} catch (error) {
|
|
218
|
-
spinner.fail(chalk.red("Concat failed"));
|
|
219
|
-
if (error instanceof Error) {
|
|
220
|
-
console.error(chalk.red(error.message));
|
|
221
|
-
}
|
|
222
|
-
process.exit(1);
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// ============ batch apply-effect ============
|
|
227
|
-
|
|
228
|
-
batchCommand
|
|
229
|
-
.command("apply-effect")
|
|
230
|
-
.description("Apply an effect to multiple clips")
|
|
231
|
-
.argument("<project>", "Project file path")
|
|
232
|
-
.argument("<effect-type>", "Effect type (fadeIn, fadeOut, blur, etc.)")
|
|
233
|
-
.argument("[clip-ids...]", "Clip IDs to apply effect to (or --all)")
|
|
234
|
-
.option("--all", "Apply to all clips", false)
|
|
235
|
-
.option("-d, --duration <seconds>", "Effect duration", "1")
|
|
236
|
-
.option("-s, --start <seconds>", "Effect start time (relative to clip)", "0")
|
|
237
|
-
.option("--intensity <value>", "Effect intensity (0-1)", "1")
|
|
238
|
-
.action(
|
|
239
|
-
async (
|
|
240
|
-
projectPath: string,
|
|
241
|
-
effectType: string,
|
|
242
|
-
clipIds: string[],
|
|
243
|
-
options
|
|
244
|
-
) => {
|
|
245
|
-
const spinner = ora("Applying effects...").start();
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
const filePath = resolve(process.cwd(), projectPath);
|
|
249
|
-
const content = await readFile(filePath, "utf-8");
|
|
250
|
-
const data: ProjectFile = JSON.parse(content);
|
|
251
|
-
const project = Project.fromJSON(data);
|
|
252
|
-
|
|
253
|
-
const clips = project.getClips();
|
|
254
|
-
|
|
255
|
-
if (clips.length === 0) {
|
|
256
|
-
spinner.fail(chalk.red("No clips in project"));
|
|
257
|
-
process.exit(1);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Get clips to apply effect to
|
|
261
|
-
let selectedClips = clips;
|
|
262
|
-
if (!options.all && clipIds.length > 0) {
|
|
263
|
-
selectedClips = clipIds
|
|
264
|
-
.map((id) => clips.find((c) => c.id === id))
|
|
265
|
-
.filter((c): c is NonNullable<typeof c> => c !== undefined);
|
|
266
|
-
|
|
267
|
-
if (selectedClips.length === 0) {
|
|
268
|
-
spinner.fail(chalk.red("No matching clips found"));
|
|
269
|
-
process.exit(1);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const duration = parseFloat(options.duration);
|
|
274
|
-
const startTime = parseFloat(options.start);
|
|
275
|
-
const intensity = parseFloat(options.intensity);
|
|
276
|
-
let appliedCount = 0;
|
|
277
|
-
|
|
278
|
-
for (const clip of selectedClips) {
|
|
279
|
-
const effect = project.addEffect(clip.id, {
|
|
280
|
-
type: effectType as EffectType,
|
|
281
|
-
startTime,
|
|
282
|
-
duration,
|
|
283
|
-
params: { intensity },
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
if (effect) {
|
|
287
|
-
appliedCount++;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
await writeFile(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
292
|
-
|
|
293
|
-
spinner.succeed(
|
|
294
|
-
chalk.green(`Applied ${effectType} to ${appliedCount} clips`)
|
|
295
|
-
);
|
|
296
|
-
console.log();
|
|
297
|
-
console.log(chalk.dim(" Effect:"), effectType);
|
|
298
|
-
console.log(chalk.dim(" Duration:"), `${duration}s`);
|
|
299
|
-
console.log(chalk.dim(" Intensity:"), intensity);
|
|
300
|
-
console.log();
|
|
301
|
-
} catch (error) {
|
|
302
|
-
spinner.fail(chalk.red("Apply effect failed"));
|
|
303
|
-
if (error instanceof Error) {
|
|
304
|
-
console.error(chalk.red(error.message));
|
|
305
|
-
}
|
|
306
|
-
process.exit(1);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
// ============ batch remove-clips ============
|
|
312
|
-
|
|
313
|
-
batchCommand
|
|
314
|
-
.command("remove-clips")
|
|
315
|
-
.description("Remove multiple clips from the timeline")
|
|
316
|
-
.argument("<project>", "Project file path")
|
|
317
|
-
.argument("[clip-ids...]", "Clip IDs to remove")
|
|
318
|
-
.option("--all", "Remove all clips", false)
|
|
319
|
-
.option("--track <track-id>", "Remove clips from specific track only")
|
|
320
|
-
.action(async (projectPath: string, clipIds: string[], options) => {
|
|
321
|
-
const spinner = ora("Removing clips...").start();
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
const filePath = resolve(process.cwd(), projectPath);
|
|
325
|
-
const content = await readFile(filePath, "utf-8");
|
|
326
|
-
const data: ProjectFile = JSON.parse(content);
|
|
327
|
-
const project = Project.fromJSON(data);
|
|
328
|
-
|
|
329
|
-
const clips = project.getClips();
|
|
330
|
-
|
|
331
|
-
if (clips.length === 0) {
|
|
332
|
-
spinner.fail(chalk.red("No clips in project"));
|
|
333
|
-
process.exit(1);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Get clips to remove
|
|
337
|
-
let clipsToRemove = clips;
|
|
338
|
-
|
|
339
|
-
if (options.track) {
|
|
340
|
-
clipsToRemove = clips.filter((c) => c.trackId === options.track);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (!options.all && clipIds.length > 0) {
|
|
344
|
-
clipsToRemove = clipIds
|
|
345
|
-
.map((id) => clips.find((c) => c.id === id))
|
|
346
|
-
.filter((c): c is NonNullable<typeof c> => c !== undefined);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (clipsToRemove.length === 0) {
|
|
350
|
-
spinner.fail(chalk.red("No matching clips found"));
|
|
351
|
-
process.exit(1);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
let removedCount = 0;
|
|
355
|
-
for (const clip of clipsToRemove) {
|
|
356
|
-
if (project.removeClip(clip.id)) {
|
|
357
|
-
removedCount++;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
await writeFile(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
362
|
-
|
|
363
|
-
spinner.succeed(chalk.green(`Removed ${removedCount} clips`));
|
|
364
|
-
console.log();
|
|
365
|
-
} catch (error) {
|
|
366
|
-
spinner.fail(chalk.red("Remove clips failed"));
|
|
367
|
-
if (error instanceof Error) {
|
|
368
|
-
console.error(chalk.red(error.message));
|
|
369
|
-
}
|
|
370
|
-
process.exit(1);
|
|
371
|
-
}
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
// ============ batch info ============
|
|
375
|
-
|
|
376
|
-
batchCommand
|
|
377
|
-
.command("info")
|
|
378
|
-
.description("Show batch processing statistics")
|
|
379
|
-
.argument("<project>", "Project file path")
|
|
380
|
-
.action(async (projectPath: string) => {
|
|
381
|
-
const spinner = ora("Loading project...").start();
|
|
382
|
-
|
|
383
|
-
try {
|
|
384
|
-
const filePath = resolve(process.cwd(), projectPath);
|
|
385
|
-
const content = await readFile(filePath, "utf-8");
|
|
386
|
-
const data: ProjectFile = JSON.parse(content);
|
|
387
|
-
const project = Project.fromJSON(data);
|
|
388
|
-
|
|
389
|
-
const sources = project.getSources();
|
|
390
|
-
const clips = project.getClips();
|
|
391
|
-
const tracks = project.getTracks();
|
|
392
|
-
const summary = project.getSummary();
|
|
393
|
-
|
|
394
|
-
spinner.stop();
|
|
395
|
-
|
|
396
|
-
console.log(chalk.bold("\nProject Statistics\n"));
|
|
397
|
-
|
|
398
|
-
// Sources breakdown
|
|
399
|
-
const videoSources = sources.filter((s) => s.type === "video").length;
|
|
400
|
-
const audioSources = sources.filter((s) => s.type === "audio").length;
|
|
401
|
-
const imageSources = sources.filter((s) => s.type === "image").length;
|
|
402
|
-
|
|
403
|
-
console.log(chalk.cyan("Sources:"), sources.length);
|
|
404
|
-
if (videoSources > 0) console.log(chalk.dim(" Video:"), videoSources);
|
|
405
|
-
if (audioSources > 0) console.log(chalk.dim(" Audio:"), audioSources);
|
|
406
|
-
if (imageSources > 0) console.log(chalk.dim(" Image:"), imageSources);
|
|
407
|
-
|
|
408
|
-
// Clips breakdown
|
|
409
|
-
const clipsPerTrack = tracks.map((t) => ({
|
|
410
|
-
track: t.name,
|
|
411
|
-
count: clips.filter((c) => c.trackId === t.id).length,
|
|
412
|
-
}));
|
|
413
|
-
|
|
414
|
-
console.log(chalk.cyan("\nClips:"), clips.length);
|
|
415
|
-
for (const { track, count } of clipsPerTrack) {
|
|
416
|
-
if (count > 0) console.log(chalk.dim(` ${track}:`), count);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Effects count
|
|
420
|
-
const totalEffects = clips.reduce((sum, c) => sum + c.effects.length, 0);
|
|
421
|
-
if (totalEffects > 0) {
|
|
422
|
-
console.log(chalk.cyan("\nEffects:"), totalEffects);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// Timeline info
|
|
426
|
-
console.log(chalk.cyan("\nTimeline:"));
|
|
427
|
-
console.log(chalk.dim(" Duration:"), `${summary.duration.toFixed(1)}s`);
|
|
428
|
-
console.log(chalk.dim(" Tracks:"), tracks.length);
|
|
429
|
-
console.log(chalk.dim(" Frame rate:"), `${summary.frameRate} fps`);
|
|
430
|
-
console.log(chalk.dim(" Aspect ratio:"), summary.aspectRatio);
|
|
431
|
-
|
|
432
|
-
console.log();
|
|
433
|
-
} catch (error) {
|
|
434
|
-
spinner.fail(chalk.red("Failed to load project"));
|
|
435
|
-
if (error instanceof Error) {
|
|
436
|
-
console.error(chalk.red(error.message));
|
|
437
|
-
}
|
|
438
|
-
process.exit(1);
|
|
439
|
-
}
|
|
440
|
-
});
|