@vibeframe/cli 0.27.0 → 0.29.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/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/agent/tools/ai.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Tools - Barrel module for all AI agent tools
|
|
3
|
-
*
|
|
4
|
-
* Delegates to three sub-modules:
|
|
5
|
-
* - ai-generation.ts: Image, video, TTS, SFX, music, storyboard, motion (8 tools)
|
|
6
|
-
* - ai-pipeline.ts: Script-to-video, highlights, auto-shorts, analysis, editing, regeneration (7 tools)
|
|
7
|
-
* - ai-editing.ts: Text overlay, review, silence cut, jump cut, captions, noise reduce, fade, thumbnail, translate (9 tools)
|
|
8
|
-
*
|
|
9
|
-
* Total: 24 AI tools
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type { ToolRegistry } from "./index.js";
|
|
13
|
-
import { registerGenerationTools } from "./ai-generation.js";
|
|
14
|
-
import { registerPipelineTools } from "./ai-pipeline.js";
|
|
15
|
-
import { registerEditingTools } from "./ai-editing.js";
|
|
16
|
-
|
|
17
|
-
export function registerAITools(registry: ToolRegistry): void {
|
|
18
|
-
registerGenerationTools(registry);
|
|
19
|
-
registerPipelineTools(registry);
|
|
20
|
-
registerEditingTools(registry);
|
|
21
|
-
}
|
package/src/agent/tools/batch.ts
DELETED
|
@@ -1,429 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Batch Tools - Batch operations for processing multiple items
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { readFile, writeFile, readdir } from "node:fs/promises";
|
|
6
|
-
import { resolve, basename, extname, join } from "node:path";
|
|
7
|
-
import type { ToolRegistry, ToolHandler } from "./index.js";
|
|
8
|
-
import type { ToolDefinition, ToolResult } from "../types.js";
|
|
9
|
-
import { Project, type ProjectFile } from "../../engine/index.js";
|
|
10
|
-
import type { MediaType, EffectType } from "@vibeframe/core/timeline";
|
|
11
|
-
import { ffprobeDuration } from "../../utils/exec-safe.js";
|
|
12
|
-
|
|
13
|
-
// Helper functions
|
|
14
|
-
function detectMediaType(filePath: string): MediaType {
|
|
15
|
-
const ext = extname(filePath).toLowerCase();
|
|
16
|
-
const videoExts = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
|
|
17
|
-
const audioExts = [".mp3", ".wav", ".aac", ".flac", ".ogg", ".m4a"];
|
|
18
|
-
const imageExts = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"];
|
|
19
|
-
|
|
20
|
-
if (videoExts.includes(ext)) return "video";
|
|
21
|
-
if (audioExts.includes(ext)) return "audio";
|
|
22
|
-
if (imageExts.includes(ext)) return "image";
|
|
23
|
-
return "video";
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function isMediaFile(filePath: string): boolean {
|
|
27
|
-
const ext = extname(filePath).toLowerCase();
|
|
28
|
-
const mediaExts = [
|
|
29
|
-
".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v",
|
|
30
|
-
".mp3", ".wav", ".aac", ".flac", ".ogg", ".m4a",
|
|
31
|
-
".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp",
|
|
32
|
-
];
|
|
33
|
-
return mediaExts.includes(ext);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function getMediaDuration(filePath: string): Promise<number> {
|
|
37
|
-
try {
|
|
38
|
-
return await ffprobeDuration(filePath);
|
|
39
|
-
} catch {
|
|
40
|
-
return 0;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Tool Definitions
|
|
45
|
-
|
|
46
|
-
const batchImportDef: ToolDefinition = {
|
|
47
|
-
name: "batch_import",
|
|
48
|
-
description: "Import multiple media files from a directory into a project. Scans directory for video, audio, and image files.",
|
|
49
|
-
parameters: {
|
|
50
|
-
type: "object",
|
|
51
|
-
properties: {
|
|
52
|
-
project: {
|
|
53
|
-
type: "string",
|
|
54
|
-
description: "Project file path",
|
|
55
|
-
},
|
|
56
|
-
directory: {
|
|
57
|
-
type: "string",
|
|
58
|
-
description: "Directory containing media files to import",
|
|
59
|
-
},
|
|
60
|
-
recursive: {
|
|
61
|
-
type: "boolean",
|
|
62
|
-
description: "Search subdirectories recursively (default: false)",
|
|
63
|
-
},
|
|
64
|
-
filter: {
|
|
65
|
-
type: "string",
|
|
66
|
-
description: "Filter files by extension, comma-separated (e.g., '.mp4,.mov')",
|
|
67
|
-
},
|
|
68
|
-
imageDuration: {
|
|
69
|
-
type: "number",
|
|
70
|
-
description: "Default duration for images in seconds (default: 5)",
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
required: ["project", "directory"],
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const batchConcatDef: ToolDefinition = {
|
|
78
|
-
name: "batch_concat",
|
|
79
|
-
description: "Concatenate multiple sources into sequential clips on the timeline",
|
|
80
|
-
parameters: {
|
|
81
|
-
type: "object",
|
|
82
|
-
properties: {
|
|
83
|
-
project: {
|
|
84
|
-
type: "string",
|
|
85
|
-
description: "Project file path",
|
|
86
|
-
},
|
|
87
|
-
sourceIds: {
|
|
88
|
-
type: "array",
|
|
89
|
-
items: { type: "string", description: "Source ID" },
|
|
90
|
-
description: "Source IDs to concatenate. If empty with useAll=true, uses all sources.",
|
|
91
|
-
},
|
|
92
|
-
useAll: {
|
|
93
|
-
type: "boolean",
|
|
94
|
-
description: "Use all sources in the project (default: false)",
|
|
95
|
-
},
|
|
96
|
-
trackId: {
|
|
97
|
-
type: "string",
|
|
98
|
-
description: "Track to place clips on (auto-selects if not specified)",
|
|
99
|
-
},
|
|
100
|
-
startTime: {
|
|
101
|
-
type: "number",
|
|
102
|
-
description: "Starting time in seconds (default: 0)",
|
|
103
|
-
},
|
|
104
|
-
gap: {
|
|
105
|
-
type: "number",
|
|
106
|
-
description: "Gap between clips in seconds (default: 0)",
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
required: ["project"],
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const batchApplyEffectDef: ToolDefinition = {
|
|
114
|
-
name: "batch_apply_effect",
|
|
115
|
-
description: "Apply an effect to multiple clips at once",
|
|
116
|
-
parameters: {
|
|
117
|
-
type: "object",
|
|
118
|
-
properties: {
|
|
119
|
-
project: {
|
|
120
|
-
type: "string",
|
|
121
|
-
description: "Project file path",
|
|
122
|
-
},
|
|
123
|
-
clipIds: {
|
|
124
|
-
type: "array",
|
|
125
|
-
items: { type: "string", description: "Clip ID" },
|
|
126
|
-
description: "Clip IDs to apply effect to. If empty with useAll=true, applies to all clips.",
|
|
127
|
-
},
|
|
128
|
-
useAll: {
|
|
129
|
-
type: "boolean",
|
|
130
|
-
description: "Apply to all clips in the project (default: false)",
|
|
131
|
-
},
|
|
132
|
-
effectType: {
|
|
133
|
-
type: "string",
|
|
134
|
-
description: "Effect type to apply",
|
|
135
|
-
enum: ["fadeIn", "fadeOut", "blur", "brightness", "contrast", "saturation", "speed", "volume"],
|
|
136
|
-
},
|
|
137
|
-
duration: {
|
|
138
|
-
type: "number",
|
|
139
|
-
description: "Effect duration in seconds (default: entire clip)",
|
|
140
|
-
},
|
|
141
|
-
params: {
|
|
142
|
-
type: "object",
|
|
143
|
-
description: "Effect-specific parameters",
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
required: ["project", "effectType"],
|
|
147
|
-
},
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
// Tool Handlers
|
|
151
|
-
|
|
152
|
-
const batchImport: ToolHandler = async (args, context): Promise<ToolResult> => {
|
|
153
|
-
const projectPath = args.project as string;
|
|
154
|
-
const directory = args.directory as string;
|
|
155
|
-
const recursive = args.recursive as boolean || false;
|
|
156
|
-
const filterStr = args.filter as string | undefined;
|
|
157
|
-
const imageDuration = (args.imageDuration as number) || 5;
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
const filePath = resolve(context.workingDirectory, projectPath);
|
|
161
|
-
const content = await readFile(filePath, "utf-8");
|
|
162
|
-
const data: ProjectFile = JSON.parse(content);
|
|
163
|
-
const project = Project.fromJSON(data);
|
|
164
|
-
|
|
165
|
-
const dirPath = resolve(context.workingDirectory, directory);
|
|
166
|
-
const filterExts = filterStr
|
|
167
|
-
? filterStr.split(",").map((e) => e.trim().toLowerCase())
|
|
168
|
-
: null;
|
|
169
|
-
|
|
170
|
-
// Collect media files
|
|
171
|
-
const mediaFiles: string[] = [];
|
|
172
|
-
|
|
173
|
-
const scanDir = async (dir: string): Promise<void> => {
|
|
174
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
175
|
-
|
|
176
|
-
for (const entry of entries) {
|
|
177
|
-
const entryPath = join(dir, entry.name);
|
|
178
|
-
|
|
179
|
-
if (entry.isDirectory() && recursive) {
|
|
180
|
-
await scanDir(entryPath);
|
|
181
|
-
} else if (entry.isFile()) {
|
|
182
|
-
const ext = extname(entry.name).toLowerCase();
|
|
183
|
-
const matchesFilter = !filterExts || filterExts.includes(ext);
|
|
184
|
-
|
|
185
|
-
if (matchesFilter && isMediaFile(entryPath)) {
|
|
186
|
-
mediaFiles.push(entryPath);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
await scanDir(dirPath);
|
|
193
|
-
|
|
194
|
-
if (mediaFiles.length === 0) {
|
|
195
|
-
return {
|
|
196
|
-
toolCallId: "",
|
|
197
|
-
success: false,
|
|
198
|
-
output: "",
|
|
199
|
-
error: "No media files found in directory",
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Sort files alphabetically
|
|
204
|
-
mediaFiles.sort();
|
|
205
|
-
|
|
206
|
-
const addedSources: { id: string; name: string; type: MediaType }[] = [];
|
|
207
|
-
|
|
208
|
-
for (const mediaFile of mediaFiles) {
|
|
209
|
-
const mediaName = basename(mediaFile);
|
|
210
|
-
const mediaType = detectMediaType(mediaFile);
|
|
211
|
-
let duration = imageDuration;
|
|
212
|
-
|
|
213
|
-
// Get actual duration for video/audio
|
|
214
|
-
if (mediaType !== "image") {
|
|
215
|
-
const actualDuration = await getMediaDuration(mediaFile);
|
|
216
|
-
if (actualDuration > 0) {
|
|
217
|
-
duration = actualDuration;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const source = project.addSource({
|
|
222
|
-
name: mediaName,
|
|
223
|
-
type: mediaType,
|
|
224
|
-
url: mediaFile,
|
|
225
|
-
duration,
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
addedSources.push({ id: source.id, name: mediaName, type: mediaType });
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
await writeFile(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
232
|
-
|
|
233
|
-
// Build output
|
|
234
|
-
const output = [
|
|
235
|
-
`Imported ${addedSources.length} media files:`,
|
|
236
|
-
"",
|
|
237
|
-
...addedSources.map((s) => ` + ${s.name} (${s.type})`),
|
|
238
|
-
];
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
toolCallId: "",
|
|
242
|
-
success: true,
|
|
243
|
-
output: output.join("\n"),
|
|
244
|
-
};
|
|
245
|
-
} catch (error) {
|
|
246
|
-
return {
|
|
247
|
-
toolCallId: "",
|
|
248
|
-
success: false,
|
|
249
|
-
output: "",
|
|
250
|
-
error: `Failed to import files: ${error instanceof Error ? error.message : String(error)}`,
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
const batchConcat: ToolHandler = async (args, context): Promise<ToolResult> => {
|
|
256
|
-
const projectPath = args.project as string;
|
|
257
|
-
const sourceIds = args.sourceIds as string[] || [];
|
|
258
|
-
const useAll = args.useAll as boolean || false;
|
|
259
|
-
const trackId = args.trackId as string | undefined;
|
|
260
|
-
const startTime = (args.startTime as number) || 0;
|
|
261
|
-
const gap = (args.gap as number) || 0;
|
|
262
|
-
|
|
263
|
-
try {
|
|
264
|
-
const filePath = resolve(context.workingDirectory, projectPath);
|
|
265
|
-
const content = await readFile(filePath, "utf-8");
|
|
266
|
-
const data: ProjectFile = JSON.parse(content);
|
|
267
|
-
const project = Project.fromJSON(data);
|
|
268
|
-
|
|
269
|
-
// Get sources to concatenate
|
|
270
|
-
const sourcesToConcat = useAll
|
|
271
|
-
? project.getSources()
|
|
272
|
-
: sourceIds.map((id) => project.getSource(id)).filter(Boolean);
|
|
273
|
-
|
|
274
|
-
if (!sourcesToConcat || sourcesToConcat.length === 0) {
|
|
275
|
-
return {
|
|
276
|
-
toolCallId: "",
|
|
277
|
-
success: false,
|
|
278
|
-
output: "",
|
|
279
|
-
error: "No sources to concatenate. Provide sourceIds or use useAll=true.",
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Find or determine track
|
|
284
|
-
let targetTrackId = trackId;
|
|
285
|
-
if (!targetTrackId) {
|
|
286
|
-
// Find first matching track type
|
|
287
|
-
const firstSource = sourcesToConcat[0]!;
|
|
288
|
-
const trackType = firstSource.type === "audio" ? "audio" : "video";
|
|
289
|
-
const tracks = project.getTracksByType(trackType);
|
|
290
|
-
if (tracks.length === 0) {
|
|
291
|
-
return {
|
|
292
|
-
toolCallId: "",
|
|
293
|
-
success: false,
|
|
294
|
-
output: "",
|
|
295
|
-
error: `No ${trackType} track found. Create one first.`,
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
targetTrackId = tracks[0].id;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Create clips
|
|
302
|
-
let currentTime = startTime;
|
|
303
|
-
const createdClips: { id: string; sourceName: string; startTime: number; duration: number }[] = [];
|
|
304
|
-
|
|
305
|
-
for (const source of sourcesToConcat) {
|
|
306
|
-
if (!source) continue;
|
|
307
|
-
|
|
308
|
-
const clip = project.addClip({
|
|
309
|
-
sourceId: source.id,
|
|
310
|
-
trackId: targetTrackId,
|
|
311
|
-
startTime: currentTime,
|
|
312
|
-
duration: source.duration,
|
|
313
|
-
sourceStartOffset: 0,
|
|
314
|
-
sourceEndOffset: source.duration,
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
createdClips.push({
|
|
318
|
-
id: clip.id,
|
|
319
|
-
sourceName: source.name,
|
|
320
|
-
startTime: currentTime,
|
|
321
|
-
duration: source.duration,
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
currentTime += source.duration + gap;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
await writeFile(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
328
|
-
|
|
329
|
-
// Build output
|
|
330
|
-
const totalDuration = currentTime - gap - startTime;
|
|
331
|
-
const output = [
|
|
332
|
-
`Created ${createdClips.length} clips (total: ${totalDuration.toFixed(1)}s):`,
|
|
333
|
-
"",
|
|
334
|
-
...createdClips.map((c) => ` ${c.sourceName} @ ${c.startTime.toFixed(1)}s (${c.duration.toFixed(1)}s)`),
|
|
335
|
-
];
|
|
336
|
-
|
|
337
|
-
return {
|
|
338
|
-
toolCallId: "",
|
|
339
|
-
success: true,
|
|
340
|
-
output: output.join("\n"),
|
|
341
|
-
};
|
|
342
|
-
} catch (error) {
|
|
343
|
-
return {
|
|
344
|
-
toolCallId: "",
|
|
345
|
-
success: false,
|
|
346
|
-
output: "",
|
|
347
|
-
error: `Failed to concatenate: ${error instanceof Error ? error.message : String(error)}`,
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
const batchApplyEffect: ToolHandler = async (args, context): Promise<ToolResult> => {
|
|
353
|
-
const projectPath = args.project as string;
|
|
354
|
-
const clipIds = args.clipIds as string[] || [];
|
|
355
|
-
const useAll = args.useAll as boolean || false;
|
|
356
|
-
const effectType = args.effectType as EffectType;
|
|
357
|
-
const duration = args.duration as number | undefined;
|
|
358
|
-
const rawParams = (args.params as Record<string, unknown>) || {};
|
|
359
|
-
|
|
360
|
-
try {
|
|
361
|
-
const filePath = resolve(context.workingDirectory, projectPath);
|
|
362
|
-
const content = await readFile(filePath, "utf-8");
|
|
363
|
-
const data: ProjectFile = JSON.parse(content);
|
|
364
|
-
const project = Project.fromJSON(data);
|
|
365
|
-
|
|
366
|
-
// Get clips to apply effect to
|
|
367
|
-
const targetClips = useAll
|
|
368
|
-
? project.getClips()
|
|
369
|
-
: clipIds.map((id) => project.getClip(id)).filter(Boolean);
|
|
370
|
-
|
|
371
|
-
if (!targetClips || targetClips.length === 0) {
|
|
372
|
-
return {
|
|
373
|
-
toolCallId: "",
|
|
374
|
-
success: false,
|
|
375
|
-
output: "",
|
|
376
|
-
error: "No clips to apply effect to. Provide clipIds or use useAll=true.",
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Prepare params
|
|
381
|
-
const params: Record<string, string | number | boolean> = {};
|
|
382
|
-
for (const [key, value] of Object.entries(rawParams)) {
|
|
383
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
384
|
-
params[key] = value;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Apply effects
|
|
389
|
-
const appliedEffects: { clipId: string; effectId: string }[] = [];
|
|
390
|
-
|
|
391
|
-
for (const clip of targetClips) {
|
|
392
|
-
if (!clip) continue;
|
|
393
|
-
|
|
394
|
-
const effectDuration = duration ?? clip.duration;
|
|
395
|
-
const effect = project.addEffect(clip.id, {
|
|
396
|
-
type: effectType,
|
|
397
|
-
startTime: 0,
|
|
398
|
-
duration: effectDuration,
|
|
399
|
-
params,
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
if (effect) {
|
|
403
|
-
appliedEffects.push({ clipId: clip.id, effectId: effect.id });
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
await writeFile(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
408
|
-
|
|
409
|
-
return {
|
|
410
|
-
toolCallId: "",
|
|
411
|
-
success: true,
|
|
412
|
-
output: `Applied ${effectType} effect to ${appliedEffects.length} clips`,
|
|
413
|
-
};
|
|
414
|
-
} catch (error) {
|
|
415
|
-
return {
|
|
416
|
-
toolCallId: "",
|
|
417
|
-
success: false,
|
|
418
|
-
output: "",
|
|
419
|
-
error: `Failed to apply effect: ${error instanceof Error ? error.message : String(error)}`,
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
// Registration function
|
|
425
|
-
export function registerBatchTools(registry: ToolRegistry): void {
|
|
426
|
-
registry.register(batchImportDef, batchImport);
|
|
427
|
-
registry.register(batchConcatDef, batchConcat);
|
|
428
|
-
registry.register(batchApplyEffectDef, batchApplyEffect);
|
|
429
|
-
}
|