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.
Files changed (48) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/.env.example +24 -0
  3. package/CLAUDE.md +118 -0
  4. package/README.md +231 -0
  5. package/SKILLS.md +157 -0
  6. package/STRUCTURE.md +92 -0
  7. package/TEST_RESULTS.md +122 -0
  8. package/action/captions/SKILL.md +170 -0
  9. package/action/captions/index.ts +227 -0
  10. package/action/edit/SKILL.md +235 -0
  11. package/action/edit/index.ts +493 -0
  12. package/action/image/SKILL.md +140 -0
  13. package/action/image/index.ts +112 -0
  14. package/action/sync/SKILL.md +136 -0
  15. package/action/sync/index.ts +187 -0
  16. package/action/transcribe/SKILL.md +179 -0
  17. package/action/transcribe/index.ts +227 -0
  18. package/action/video/SKILL.md +116 -0
  19. package/action/video/index.ts +135 -0
  20. package/action/voice/SKILL.md +125 -0
  21. package/action/voice/index.ts +201 -0
  22. package/biome.json +33 -0
  23. package/index.ts +38 -0
  24. package/lib/README.md +144 -0
  25. package/lib/ai-sdk/fal.ts +106 -0
  26. package/lib/ai-sdk/replicate.ts +107 -0
  27. package/lib/elevenlabs.ts +382 -0
  28. package/lib/fal.ts +478 -0
  29. package/lib/ffmpeg.ts +467 -0
  30. package/lib/fireworks.ts +235 -0
  31. package/lib/groq.ts +246 -0
  32. package/lib/higgsfield.ts +176 -0
  33. package/lib/remotion/SKILL.md +823 -0
  34. package/lib/remotion/cli.ts +115 -0
  35. package/lib/remotion/functions.ts +283 -0
  36. package/lib/remotion/index.ts +19 -0
  37. package/lib/remotion/templates.ts +73 -0
  38. package/lib/replicate.ts +304 -0
  39. package/output.txt +1 -0
  40. package/package.json +35 -0
  41. package/pipeline/cookbooks/SKILL.md +285 -0
  42. package/pipeline/cookbooks/remotion-video.md +585 -0
  43. package/pipeline/cookbooks/round-video-character.md +337 -0
  44. package/pipeline/cookbooks/talking-character.md +59 -0
  45. package/test-import.ts +7 -0
  46. package/test-services.ts +97 -0
  47. package/tsconfig.json +29 -0
  48. 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
+ }