vargai 0.4.0-alpha47 → 0.4.0-alpha49

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.
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Grok Imagine Video - AI SDK Provider Test
3
+ *
4
+ * Run with: bun run examples/grok-imagine-ai-sdk.tsx
5
+ *
6
+ * Tests the Grok Imagine Video model via the AI-SDK interface
7
+ */
8
+
9
+ import { writeFile } from "node:fs/promises";
10
+ import { join } from "node:path";
11
+ import { fal } from "../src/ai-sdk/providers/fal";
12
+
13
+ async function testGrokTextToVideo() {
14
+ console.log("\n=== Grok Text-to-Video (AI-SDK) ===\n");
15
+
16
+ const model = fal.videoModel("grok-imagine");
17
+
18
+ const result = await model.doGenerate({
19
+ prompt:
20
+ "A futuristic city at night with neon lights reflecting on wet streets, flying cars passing by, cyberpunk aesthetic",
21
+ n: 1,
22
+ duration: 6,
23
+ aspectRatio: "16:9",
24
+ resolution: undefined,
25
+ fps: undefined,
26
+ seed: undefined,
27
+ files: undefined,
28
+ providerOptions: {
29
+ fal: {
30
+ resolution: "720p",
31
+ },
32
+ },
33
+ });
34
+
35
+ console.log("Warnings:", result.warnings);
36
+ console.log("Response:", {
37
+ timestamp: result.response.timestamp,
38
+ modelId: result.response.modelId,
39
+ });
40
+
41
+ // Save the video
42
+ const outputPath = join(import.meta.dir, "../output/grok-t2v-test.mp4");
43
+ await writeFile(outputPath, result.videos[0]!);
44
+ console.log(`Video saved to: ${outputPath}`);
45
+
46
+ return outputPath;
47
+ }
48
+
49
+ async function testGrokImageToVideo() {
50
+ console.log("\n=== Grok Image-to-Video (AI-SDK) ===\n");
51
+
52
+ const model = fal.videoModel("grok-imagine");
53
+
54
+ // Fetch a sample image
55
+ const imageUrl =
56
+ "https://v3b.fal.media/files/b/0a8b90e0/BFLE9VDlZqsryU-UA3BoD_image_004.png";
57
+ const imageResponse = await fetch(imageUrl);
58
+ const imageBuffer = new Uint8Array(await imageResponse.arrayBuffer());
59
+
60
+ const result = await model.doGenerate({
61
+ prompt:
62
+ "The knight slowly raises their sword, preparing for battle, dramatic lighting",
63
+ n: 1,
64
+ duration: 6,
65
+ aspectRatio: "16:9",
66
+ resolution: undefined,
67
+ fps: undefined,
68
+ seed: undefined,
69
+ files: [
70
+ {
71
+ type: "file",
72
+ data: imageBuffer,
73
+ mediaType: "image/png",
74
+ },
75
+ ],
76
+ providerOptions: {
77
+ fal: {
78
+ resolution: "720p",
79
+ },
80
+ },
81
+ });
82
+
83
+ console.log("Warnings:", result.warnings);
84
+ console.log("Response:", {
85
+ timestamp: result.response.timestamp,
86
+ modelId: result.response.modelId,
87
+ });
88
+
89
+ // Save the video
90
+ const outputPath = join(import.meta.dir, "../output/grok-i2v-test.mp4");
91
+ await writeFile(outputPath, result.videos[0]!);
92
+ console.log(`Video saved to: ${outputPath}`);
93
+
94
+ return outputPath;
95
+ }
96
+
97
+ async function testGrokEditVideo() {
98
+ console.log("\n=== Grok Edit Video (AI-SDK) ===\n");
99
+
100
+ const model = fal.videoModel("grok-imagine-edit");
101
+
102
+ // Use a sample video
103
+ const videoUrl =
104
+ "https://v3b.fal.media/files/b/0a8b9112/V5Z_NIPE3ppMDWivNo6_q_video_019.mp4";
105
+ const videoResponse = await fetch(videoUrl);
106
+ const videoBuffer = new Uint8Array(await videoResponse.arrayBuffer());
107
+
108
+ const result = await model.doGenerate({
109
+ prompt:
110
+ "Transform the scene into a watercolor painting style with soft pastel colors",
111
+ n: 1,
112
+ duration: undefined,
113
+ aspectRatio: undefined,
114
+ resolution: undefined,
115
+ fps: undefined,
116
+ seed: undefined,
117
+ files: [
118
+ {
119
+ type: "file",
120
+ data: videoBuffer,
121
+ mediaType: "video/mp4",
122
+ },
123
+ ],
124
+ providerOptions: {
125
+ fal: {
126
+ resolution: "720p",
127
+ },
128
+ },
129
+ });
130
+
131
+ console.log("Warnings:", result.warnings);
132
+ console.log("Response:", {
133
+ timestamp: result.response.timestamp,
134
+ modelId: result.response.modelId,
135
+ });
136
+
137
+ // Save the video
138
+ const outputPath = join(import.meta.dir, "../output/grok-edit-test.mp4");
139
+ await writeFile(outputPath, result.videos[0]!);
140
+ console.log(`Video saved to: ${outputPath}`);
141
+
142
+ return outputPath;
143
+ }
144
+
145
+ // Main execution
146
+ async function main() {
147
+ const args = process.argv.slice(2);
148
+ const mode = args[0] || "t2v";
149
+
150
+ console.log("Grok Imagine Video - AI SDK Test");
151
+ console.log("=================================");
152
+ console.log(`Mode: ${mode}`);
153
+
154
+ // Ensure output directory exists
155
+ const { mkdir } = await import("node:fs/promises");
156
+ await mkdir(join(import.meta.dir, "../output"), { recursive: true });
157
+
158
+ try {
159
+ switch (mode) {
160
+ case "t2v":
161
+ await testGrokTextToVideo();
162
+ break;
163
+
164
+ case "i2v":
165
+ await testGrokImageToVideo();
166
+ break;
167
+
168
+ case "edit":
169
+ await testGrokEditVideo();
170
+ break;
171
+
172
+ case "all":
173
+ await testGrokTextToVideo();
174
+ await testGrokImageToVideo();
175
+ await testGrokEditVideo();
176
+ break;
177
+
178
+ default:
179
+ console.log(`
180
+ Usage: bun run examples/grok-imagine-ai-sdk.tsx [mode]
181
+
182
+ Modes:
183
+ t2v Text-to-Video generation
184
+ i2v Image-to-Video generation
185
+ edit Video editing
186
+ all Run all tests
187
+
188
+ Examples:
189
+ bun run examples/grok-imagine-ai-sdk.tsx t2v
190
+ bun run examples/grok-imagine-ai-sdk.tsx all
191
+ `);
192
+ }
193
+
194
+ console.log("\nTest completed!");
195
+ } catch (error) {
196
+ console.error("\nTest failed:", error);
197
+ process.exit(1);
198
+ }
199
+ }
200
+
201
+ main();
package/package.json CHANGED
@@ -69,7 +69,7 @@
69
69
  "sharp": "^0.34.5",
70
70
  "zod": "^4.2.1"
71
71
  },
72
- "version": "0.4.0-alpha47",
72
+ "version": "0.4.0-alpha49",
73
73
  "exports": {
74
74
  ".": "./src/index.ts",
75
75
  "./ai": "./src/ai-sdk/index.ts",
@@ -45,28 +45,46 @@ function createMemoryCache(): CacheStorage {
45
45
  }
46
46
 
47
47
  // TODO: allow passing CacheStorage via providerOptions.fal.cacheStorage for proper serverless support
48
- function isFilesystemWritable(): boolean {
49
- try {
50
- const testPath = `.cache/.write-test-${Date.now()}`;
51
- Bun.spawnSync(["mkdir", "-p", ".cache"]);
52
- const result = Bun.spawnSync(["touch", testPath]);
53
- if (result.exitCode === 0) {
54
- Bun.spawnSync(["rm", testPath]);
55
- return true;
56
- }
57
- return false;
58
- } catch {
59
- return false;
60
- }
61
- }
62
-
63
- const USE_FILE_CACHE = isFilesystemWritable();
64
-
65
48
  function createFalCache(name: string): CacheStorage {
66
- if (!USE_FILE_CACHE) {
67
- return createMemoryCache();
68
- }
69
- return fileCache({ dir: `.cache/${name}` });
49
+ let cache: CacheStorage | null = null;
50
+ let useFallback = false;
51
+ const fallback = createMemoryCache();
52
+
53
+ const getCache = () => {
54
+ if (useFallback) return fallback;
55
+ if (!cache) cache = fileCache({ dir: `.cache/${name}` });
56
+ return cache;
57
+ };
58
+
59
+ return {
60
+ async get(key) {
61
+ if (useFallback) return fallback.get(key);
62
+ try {
63
+ return await getCache().get(key);
64
+ } catch {
65
+ useFallback = true;
66
+ return fallback.get(key);
67
+ }
68
+ },
69
+ async set(key, value, ttl) {
70
+ if (useFallback) return fallback.set(key, value, ttl);
71
+ try {
72
+ await getCache().set(key, value, ttl);
73
+ } catch {
74
+ useFallback = true;
75
+ await fallback.set(key, value, ttl);
76
+ }
77
+ },
78
+ async delete(key) {
79
+ if (useFallback) return fallback.delete(key);
80
+ try {
81
+ await getCache().delete(key);
82
+ } catch {
83
+ useFallback = true;
84
+ await fallback.delete(key);
85
+ }
86
+ },
87
+ };
70
88
  }
71
89
 
72
90
  const pendingStorage = createFalCache("fal-pending");
@@ -69,7 +69,11 @@ export async function burnCaptions(
69
69
  const { video, assPath, outputPath = "output.mp4", verbose } = options;
70
70
  const captions: FFmpegOutput = { type: "file", path: assPath };
71
71
 
72
- const isCloud = options.backend !== undefined;
72
+ // Resolve backend first so we can check if it's cloud or local
73
+ // TODO: This is a hack - we should abstract backend capabilities (e.g., supportsLocalPaths)
74
+ // instead of checking the name directly. For now, we assume "local" is the only local backend.
75
+ const backend = options.backend ?? localBackend;
76
+ const isCloud = backend.name !== "local";
73
77
 
74
78
  const videoInput = await resolveInputPathMaybeUpload(video, {
75
79
  shouldUpload: isCloud,
@@ -78,14 +82,16 @@ export async function burnCaptions(
78
82
  shouldUpload: isCloud,
79
83
  });
80
84
 
81
- const backend = options.backend ?? localBackend;
82
-
83
- // FFmpeg filter syntax requires escaping backslashes and colons
84
- const escapedAssPath = assInput.replace(/\\/g, "\\\\").replace(/:/g, "\\:");
85
+ // For cloud backends (Rendi): pass raw URL so replaceWithPlaceholders() can match
86
+ // and replace with {{in_X}} placeholder. Rendi downloads inputs and provides local paths.
87
+ // For local backend: escape for FFmpeg filter syntax (backslashes and colons)
88
+ const subtitlesPath = isCloud
89
+ ? assInput
90
+ : assInput.replace(/\\/g, "\\\\").replace(/:/g, "\\:");
85
91
 
86
92
  const result = await backend.run({
87
93
  inputs: [videoInput, assInput],
88
- videoFilter: `subtitles=${escapedAssPath}`,
94
+ videoFilter: `subtitles=${subtitlesPath}`,
89
95
  outputArgs: ["-crf", "18", "-preset", "fast", "-c:a", "copy"],
90
96
  outputPath,
91
97
  verbose,