varg.ai-sdk 0.1.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/.claude/settings.local.json +7 -0
- package/.env.example +24 -0
- package/CLAUDE.md +118 -0
- package/README.md +231 -0
- package/SKILLS.md +157 -0
- package/STRUCTURE.md +92 -0
- package/TEST_RESULTS.md +122 -0
- package/action/captions/SKILL.md +170 -0
- package/action/captions/index.ts +227 -0
- package/action/edit/SKILL.md +235 -0
- package/action/edit/index.ts +493 -0
- package/action/image/SKILL.md +140 -0
- package/action/image/index.ts +112 -0
- package/action/sync/SKILL.md +136 -0
- package/action/sync/index.ts +187 -0
- package/action/transcribe/SKILL.md +179 -0
- package/action/transcribe/index.ts +227 -0
- package/action/video/SKILL.md +116 -0
- package/action/video/index.ts +135 -0
- package/action/voice/SKILL.md +125 -0
- package/action/voice/index.ts +201 -0
- package/biome.json +33 -0
- package/index.ts +38 -0
- package/lib/README.md +144 -0
- package/lib/ai-sdk/fal.ts +106 -0
- package/lib/ai-sdk/replicate.ts +107 -0
- package/lib/elevenlabs.ts +382 -0
- package/lib/fal.ts +478 -0
- package/lib/ffmpeg.ts +467 -0
- package/lib/fireworks.ts +235 -0
- package/lib/groq.ts +246 -0
- package/lib/higgsfield.ts +176 -0
- package/lib/remotion/SKILL.md +823 -0
- package/lib/remotion/cli.ts +115 -0
- package/lib/remotion/functions.ts +283 -0
- package/lib/remotion/index.ts +19 -0
- package/lib/remotion/templates.ts +73 -0
- package/lib/replicate.ts +304 -0
- package/output.txt +1 -0
- package/package.json +35 -0
- package/pipeline/cookbooks/SKILL.md +285 -0
- package/pipeline/cookbooks/remotion-video.md +585 -0
- package/pipeline/cookbooks/round-video-character.md +337 -0
- package/pipeline/cookbooks/talking-character.md +59 -0
- package/test-import.ts +7 -0
- package/test-services.ts +97 -0
- package/tsconfig.json +29 -0
- package/utilities/s3.ts +147 -0
package/lib/fal.ts
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* fal.ai wrapper using @fal-ai/client directly
|
|
5
|
+
* for video generation and advanced features
|
|
6
|
+
*
|
|
7
|
+
* usage: bun run lib/fal.ts <command> <args>
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { fal } from "@fal-ai/client";
|
|
12
|
+
|
|
13
|
+
interface FalImageToVideoArgs {
|
|
14
|
+
prompt: string;
|
|
15
|
+
imageUrl: string; // can be url or local file path
|
|
16
|
+
modelVersion?: string;
|
|
17
|
+
duration?: 5 | 10;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* upload local file to fal storage if needed
|
|
22
|
+
* returns the url (either original or uploaded)
|
|
23
|
+
*/
|
|
24
|
+
async function ensureImageUrl(imagePathOrUrl: string): Promise<string> {
|
|
25
|
+
// if it's already a url, return it
|
|
26
|
+
if (
|
|
27
|
+
imagePathOrUrl.startsWith("http://") ||
|
|
28
|
+
imagePathOrUrl.startsWith("https://")
|
|
29
|
+
) {
|
|
30
|
+
return imagePathOrUrl;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// check if local file exists
|
|
34
|
+
if (!existsSync(imagePathOrUrl)) {
|
|
35
|
+
throw new Error(`local file not found: ${imagePathOrUrl}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(`[fal] uploading local file: ${imagePathOrUrl}`);
|
|
39
|
+
|
|
40
|
+
// read file and upload to fal
|
|
41
|
+
const file = await Bun.file(imagePathOrUrl).arrayBuffer();
|
|
42
|
+
|
|
43
|
+
const uploadedUrl = await fal.storage.upload(
|
|
44
|
+
new Blob([file], { type: "image/jpeg" }),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
console.log(`[fal] uploaded to: ${uploadedUrl}`);
|
|
48
|
+
return uploadedUrl;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface FalTextToVideoArgs {
|
|
52
|
+
prompt: string;
|
|
53
|
+
modelVersion?: string;
|
|
54
|
+
duration?: 5 | 10;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function imageToVideo(args: FalImageToVideoArgs) {
|
|
58
|
+
const modelId = `fal-ai/kling-video/${args.modelVersion || "v2.5-turbo/pro"}/image-to-video`;
|
|
59
|
+
|
|
60
|
+
console.log(`[fal] starting image-to-video: ${modelId}`);
|
|
61
|
+
console.log(`[fal] prompt: ${args.prompt}`);
|
|
62
|
+
console.log(`[fal] image: ${args.imageUrl}`);
|
|
63
|
+
|
|
64
|
+
// upload local file if needed
|
|
65
|
+
const imageUrl = await ensureImageUrl(args.imageUrl);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const result = await fal.subscribe(modelId, {
|
|
69
|
+
input: {
|
|
70
|
+
prompt: args.prompt,
|
|
71
|
+
image_url: imageUrl,
|
|
72
|
+
duration: args.duration || 5,
|
|
73
|
+
},
|
|
74
|
+
logs: true,
|
|
75
|
+
onQueueUpdate: (update: {
|
|
76
|
+
status: string;
|
|
77
|
+
logs?: Array<{ message: string }>;
|
|
78
|
+
}) => {
|
|
79
|
+
if (update.status === "IN_PROGRESS") {
|
|
80
|
+
console.log(
|
|
81
|
+
`[fal] ${update.logs?.map((l) => l.message).join(" ") || "processing..."}`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
console.log("[fal] completed!");
|
|
88
|
+
return result;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error("[fal] error:", error);
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function textToVideo(args: FalTextToVideoArgs) {
|
|
96
|
+
const modelId = `fal-ai/kling-video/${args.modelVersion || "v2.5-turbo/pro"}/text-to-video`;
|
|
97
|
+
|
|
98
|
+
console.log(`[fal] starting text-to-video: ${modelId}`);
|
|
99
|
+
console.log(`[fal] prompt: ${args.prompt}`);
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const result = await fal.subscribe(modelId, {
|
|
103
|
+
input: {
|
|
104
|
+
prompt: args.prompt,
|
|
105
|
+
duration: args.duration || 5,
|
|
106
|
+
},
|
|
107
|
+
logs: true,
|
|
108
|
+
onQueueUpdate: (update: {
|
|
109
|
+
status: string;
|
|
110
|
+
logs?: Array<{ message: string }>;
|
|
111
|
+
}) => {
|
|
112
|
+
if (update.status === "IN_PROGRESS") {
|
|
113
|
+
console.log(
|
|
114
|
+
`[fal] ${update.logs?.map((l) => l.message).join(" ") || "processing..."}`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
console.log("[fal] completed!");
|
|
121
|
+
return result;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error("[fal] error:", error);
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function generateImage(args: {
|
|
129
|
+
prompt: string;
|
|
130
|
+
model?: string;
|
|
131
|
+
imageSize?: string;
|
|
132
|
+
}) {
|
|
133
|
+
const modelId = args.model || "fal-ai/flux-pro/v1.1";
|
|
134
|
+
|
|
135
|
+
console.log(`[fal] generating image with ${modelId}`);
|
|
136
|
+
console.log(`[fal] prompt: ${args.prompt}`);
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const result = await fal.subscribe(modelId, {
|
|
140
|
+
input: {
|
|
141
|
+
prompt: args.prompt,
|
|
142
|
+
image_size: args.imageSize || "landscape_4_3",
|
|
143
|
+
},
|
|
144
|
+
logs: true,
|
|
145
|
+
onQueueUpdate: (update: {
|
|
146
|
+
status: string;
|
|
147
|
+
logs?: Array<{ message: string }>;
|
|
148
|
+
}) => {
|
|
149
|
+
if (update.status === "IN_PROGRESS") {
|
|
150
|
+
console.log(
|
|
151
|
+
`[fal] ${update.logs?.map((l) => l.message).join(" ") || "processing..."}`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
console.log("[fal] completed!");
|
|
158
|
+
return result;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error("[fal] error:", error);
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
interface FalImageToImageArgs {
|
|
166
|
+
prompt: string;
|
|
167
|
+
imageUrl: string; // can be url or local file path
|
|
168
|
+
strength?: number;
|
|
169
|
+
numInferenceSteps?: number;
|
|
170
|
+
aspectRatio?: string; // auto, 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
interface FalWan25Args {
|
|
174
|
+
prompt: string;
|
|
175
|
+
imageUrl: string; // can be url or local file path
|
|
176
|
+
audioUrl: string; // can be url or local file path
|
|
177
|
+
resolution?: "480p" | "720p" | "1080p";
|
|
178
|
+
duration?: "5" | "10";
|
|
179
|
+
negativePrompt?: string;
|
|
180
|
+
enablePromptExpansion?: boolean;
|
|
181
|
+
enableSafetyChecker?: boolean;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function imageToImage(args: FalImageToImageArgs) {
|
|
185
|
+
const modelId = "fal-ai/nano-banana-pro/edit";
|
|
186
|
+
|
|
187
|
+
console.log(`[fal] starting image-to-image: ${modelId}`);
|
|
188
|
+
console.log(`[fal] prompt: ${args.prompt}`);
|
|
189
|
+
console.log(`[fal] source image: ${args.imageUrl}`);
|
|
190
|
+
|
|
191
|
+
// upload local file if needed
|
|
192
|
+
const imageUrl = await ensureImageUrl(args.imageUrl);
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const result = await fal.subscribe(modelId, {
|
|
196
|
+
input: {
|
|
197
|
+
prompt: args.prompt,
|
|
198
|
+
image_urls: [imageUrl],
|
|
199
|
+
aspect_ratio: args.aspectRatio || "auto",
|
|
200
|
+
resolution: "2K",
|
|
201
|
+
},
|
|
202
|
+
logs: true,
|
|
203
|
+
onQueueUpdate: (update: {
|
|
204
|
+
status: string;
|
|
205
|
+
logs?: Array<{ message: string }>;
|
|
206
|
+
}) => {
|
|
207
|
+
if (update.status === "IN_PROGRESS") {
|
|
208
|
+
console.log(
|
|
209
|
+
`[fal] ${update.logs?.map((l) => l.message).join(" ") || "processing..."}`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
console.log("[fal] completed!");
|
|
216
|
+
return result;
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.error("[fal] error:", error);
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
interface FalWan25Args {
|
|
224
|
+
prompt: string;
|
|
225
|
+
imageUrl: string; // can be url or local file path
|
|
226
|
+
audioUrl: string; // can be url or local file path
|
|
227
|
+
resolution?: "480p" | "720p" | "1080p";
|
|
228
|
+
duration?: "5" | "10";
|
|
229
|
+
negativePrompt?: string;
|
|
230
|
+
enablePromptExpansion?: boolean;
|
|
231
|
+
enableSafetyChecker?: boolean;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* helper to upload audio file to fal storage if needed
|
|
236
|
+
*/
|
|
237
|
+
async function ensureAudioUrl(audioPathOrUrl: string): Promise<string> {
|
|
238
|
+
// if it's already a url, return it
|
|
239
|
+
if (
|
|
240
|
+
audioPathOrUrl.startsWith("http://") ||
|
|
241
|
+
audioPathOrUrl.startsWith("https://")
|
|
242
|
+
) {
|
|
243
|
+
return audioPathOrUrl;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// check if local file exists
|
|
247
|
+
if (!existsSync(audioPathOrUrl)) {
|
|
248
|
+
throw new Error(`local audio file not found: ${audioPathOrUrl}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log(`[fal] uploading local audio: ${audioPathOrUrl}`);
|
|
252
|
+
|
|
253
|
+
// read file and upload to fal
|
|
254
|
+
const file = await Bun.file(audioPathOrUrl).arrayBuffer();
|
|
255
|
+
|
|
256
|
+
const uploadedUrl = await fal.storage.upload(
|
|
257
|
+
new Blob([file], { type: "audio/mpeg" }),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
console.log(`[fal] uploaded audio to: ${uploadedUrl}`);
|
|
261
|
+
return uploadedUrl;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export async function wan25(args: FalWan25Args) {
|
|
265
|
+
const modelId = "fal-ai/wan-25-preview/image-to-video";
|
|
266
|
+
|
|
267
|
+
console.log(`[fal] starting wan-25: ${modelId}`);
|
|
268
|
+
console.log(`[fal] prompt: ${args.prompt}`);
|
|
269
|
+
console.log(`[fal] image: ${args.imageUrl}`);
|
|
270
|
+
console.log(`[fal] audio: ${args.audioUrl}`);
|
|
271
|
+
|
|
272
|
+
// upload local files if needed
|
|
273
|
+
const imageUrl = await ensureImageUrl(args.imageUrl);
|
|
274
|
+
const audioUrl = await ensureAudioUrl(args.audioUrl);
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
const result = await fal.subscribe(modelId, {
|
|
278
|
+
input: {
|
|
279
|
+
prompt: args.prompt,
|
|
280
|
+
image_url: imageUrl,
|
|
281
|
+
audio_url: audioUrl,
|
|
282
|
+
resolution: args.resolution || "480p",
|
|
283
|
+
duration: args.duration || "5",
|
|
284
|
+
negative_prompt:
|
|
285
|
+
args.negativePrompt ||
|
|
286
|
+
"low resolution, error, worst quality, low quality, defects",
|
|
287
|
+
enable_prompt_expansion: args.enablePromptExpansion ?? true,
|
|
288
|
+
},
|
|
289
|
+
logs: true,
|
|
290
|
+
onQueueUpdate: (update: {
|
|
291
|
+
status: string;
|
|
292
|
+
logs?: Array<{ message: string }>;
|
|
293
|
+
}) => {
|
|
294
|
+
if (update.status === "IN_PROGRESS") {
|
|
295
|
+
console.log(
|
|
296
|
+
`[fal] ${update.logs?.map((l) => l.message).join(" ") || "processing..."}`,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
console.log("[fal] completed!");
|
|
303
|
+
return result;
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error("[fal] error:", error);
|
|
306
|
+
if (error && typeof error === "object" && "body" in error) {
|
|
307
|
+
console.error(
|
|
308
|
+
"[fal] validation details:",
|
|
309
|
+
JSON.stringify(error.body, null, 2),
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// cli runner
|
|
317
|
+
if (import.meta.main) {
|
|
318
|
+
const [command, ...args] = process.argv.slice(2);
|
|
319
|
+
|
|
320
|
+
switch (command) {
|
|
321
|
+
case "image_to_video": {
|
|
322
|
+
if (!args[0] || !args[1]) {
|
|
323
|
+
console.log(`
|
|
324
|
+
usage: bun run lib/fal.ts image_to_video <prompt> <image_path_or_url> [duration]
|
|
325
|
+
|
|
326
|
+
examples:
|
|
327
|
+
bun run lib/fal.ts image_to_video "person talking" "https://image.url" 5
|
|
328
|
+
bun run lib/fal.ts image_to_video "ocean waves" "./media/beach.jpg" 10
|
|
329
|
+
`);
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
const duration = args[2];
|
|
333
|
+
if (duration && duration !== "5" && duration !== "10") {
|
|
334
|
+
console.error("duration must be 5 or 10");
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
const i2vResult = await imageToVideo({
|
|
338
|
+
prompt: args[0],
|
|
339
|
+
imageUrl: args[1],
|
|
340
|
+
duration: duration === "10" ? 10 : 5,
|
|
341
|
+
});
|
|
342
|
+
console.log(JSON.stringify(i2vResult, null, 2));
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
case "text_to_video": {
|
|
347
|
+
if (!args[0]) {
|
|
348
|
+
console.log(`
|
|
349
|
+
usage:
|
|
350
|
+
bun run lib/fal.ts text_to_video <prompt> [duration]
|
|
351
|
+
|
|
352
|
+
examples:
|
|
353
|
+
bun run lib/fal.ts text_to_video "ocean waves crashing" 5
|
|
354
|
+
`);
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
const duration = args[1];
|
|
358
|
+
if (duration && duration !== "5" && duration !== "10") {
|
|
359
|
+
console.error("duration must be 5 or 10");
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}
|
|
362
|
+
const t2vResult = await textToVideo({
|
|
363
|
+
prompt: args[0],
|
|
364
|
+
duration: duration === "10" ? 10 : 5,
|
|
365
|
+
});
|
|
366
|
+
console.log(JSON.stringify(t2vResult, null, 2));
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
case "image_to_image": {
|
|
371
|
+
if (!args[0] || !args[1]) {
|
|
372
|
+
console.log(`
|
|
373
|
+
usage: bun run lib/fal.ts image_to_image <prompt> <image_path_or_url> [aspect_ratio]
|
|
374
|
+
|
|
375
|
+
examples:
|
|
376
|
+
bun run lib/fal.ts image_to_image "woman at busy conference hall" media/friend/katia.jpg
|
|
377
|
+
bun run lib/fal.ts image_to_image "person in underground station" https://image.url 9:16
|
|
378
|
+
|
|
379
|
+
parameters:
|
|
380
|
+
aspect_ratio: auto (preserves input), 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16 (default: auto)
|
|
381
|
+
|
|
382
|
+
note: now uses nano banana pro for better quality and aspect ratio preservation
|
|
383
|
+
`);
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
const aspectRatio = args[2] || "auto";
|
|
387
|
+
const i2iResult = await imageToImage({
|
|
388
|
+
prompt: args[0],
|
|
389
|
+
imageUrl: args[1],
|
|
390
|
+
aspectRatio,
|
|
391
|
+
});
|
|
392
|
+
console.log(JSON.stringify(i2iResult, null, 2));
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
case "generate_image": {
|
|
397
|
+
if (!args[0]) {
|
|
398
|
+
console.log(`
|
|
399
|
+
usage:
|
|
400
|
+
bun run lib/fal.ts generate_image <prompt> [model] [imageSize]
|
|
401
|
+
|
|
402
|
+
examples:
|
|
403
|
+
bun run lib/fal.ts generate_image "mountain landscape" "fal-ai/flux-pro/v1.1"
|
|
404
|
+
|
|
405
|
+
available image sizes:
|
|
406
|
+
- square_hd, square, portrait_4_3, portrait_16_9
|
|
407
|
+
- landscape_4_3, landscape_16_9
|
|
408
|
+
`);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
const imgResult = await generateImage({
|
|
412
|
+
prompt: args[0],
|
|
413
|
+
model: args[1],
|
|
414
|
+
imageSize: args[2],
|
|
415
|
+
});
|
|
416
|
+
console.log(JSON.stringify(imgResult, null, 2));
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
case "wan": {
|
|
421
|
+
if (!args[0] || !args[1] || !args[2]) {
|
|
422
|
+
console.log(`
|
|
423
|
+
usage: bun run lib/fal.ts wan <image_path_or_url> <audio_path_or_url> <prompt> [duration] [resolution]
|
|
424
|
+
|
|
425
|
+
examples:
|
|
426
|
+
bun run lib/fal.ts wan media/friend/aleks/option2.jpg media/friend/aleks/voice.mp3 "selfie POV video, handheld camera" 5 480p
|
|
427
|
+
bun run lib/fal.ts wan https://image.url https://audio.url "talking video" 10 720p
|
|
428
|
+
|
|
429
|
+
parameters:
|
|
430
|
+
duration: 5 or 10 (default: 5)
|
|
431
|
+
resolution: 480p, 720p, or 1080p (default: 480p)
|
|
432
|
+
`);
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
const wanDuration = args[3];
|
|
436
|
+
if (wanDuration && wanDuration !== "5" && wanDuration !== "10") {
|
|
437
|
+
console.error("duration must be 5 or 10");
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
440
|
+
const wanResolution = args[4];
|
|
441
|
+
if (
|
|
442
|
+
wanResolution &&
|
|
443
|
+
wanResolution !== "480p" &&
|
|
444
|
+
wanResolution !== "720p" &&
|
|
445
|
+
wanResolution !== "1080p"
|
|
446
|
+
) {
|
|
447
|
+
console.error("resolution must be 480p, 720p, or 1080p");
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
const wanResult = await wan25({
|
|
451
|
+
imageUrl: args[0],
|
|
452
|
+
audioUrl: args[1],
|
|
453
|
+
prompt: args[2],
|
|
454
|
+
duration: (wanDuration as "5" | "10") || "5",
|
|
455
|
+
resolution:
|
|
456
|
+
(wanResolution as "480p" | "720p" | "1080p" | undefined) || "480p",
|
|
457
|
+
});
|
|
458
|
+
console.log(JSON.stringify(wanResult, null, 2));
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
default:
|
|
463
|
+
console.log(`
|
|
464
|
+
usage:
|
|
465
|
+
# video generation (supports local files and urls)
|
|
466
|
+
bun run lib/fal.ts image_to_video <prompt> <image_path_or_url> [duration]
|
|
467
|
+
bun run lib/fal.ts text_to_video <prompt> [duration]
|
|
468
|
+
bun run lib/fal.ts wan <image_path_or_url> <audio_path_or_url> <prompt> [duration] [resolution]
|
|
469
|
+
|
|
470
|
+
# image generation (fal client with all features)
|
|
471
|
+
bun run lib/fal.ts generate_image <prompt> [model] [imageSize]
|
|
472
|
+
bun run lib/fal.ts image_to_image <prompt> <image_path_or_url> [strength]
|
|
473
|
+
|
|
474
|
+
note: for simpler image generation, use lib/ai-sdk/fal.ts instead
|
|
475
|
+
`);
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
}
|