vargai 0.4.0-alpha2 → 0.4.0-alpha21
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/README.md +483 -61
- package/launch-videos/06-kawaii-fruits.tsx +93 -0
- package/launch-videos/07-ugc-weight-loss.tsx +132 -0
- package/launch-videos/08-talking-head-varg.tsx +107 -0
- package/launch-videos/09-girl.tsx +160 -0
- package/launch-videos/README.md +42 -0
- package/package.json +8 -4
- package/skills/varg-video-generation/SKILL.md +213 -0
- package/skills/varg-video-generation/references/templates.md +380 -0
- package/skills/varg-video-generation/scripts/setup.ts +265 -0
- package/src/ai-sdk/cache.ts +1 -1
- package/src/ai-sdk/middleware/wrap-image-model.ts +4 -21
- package/src/ai-sdk/middleware/wrap-music-model.ts +4 -16
- package/src/ai-sdk/middleware/wrap-video-model.ts +5 -17
- package/src/ai-sdk/providers/editly/index.ts +110 -53
- package/src/ai-sdk/providers/editly/types.ts +2 -0
- package/src/ai-sdk/providers/elevenlabs.ts +10 -2
- package/src/ai-sdk/providers/fal.ts +6 -1
- package/src/cli/commands/find.tsx +1 -0
- package/src/cli/commands/hello.ts +85 -0
- package/src/cli/commands/help.tsx +18 -30
- package/src/cli/commands/index.ts +9 -1
- package/src/cli/commands/init.tsx +412 -0
- package/src/cli/commands/list.tsx +1 -0
- package/src/cli/commands/render.tsx +292 -80
- package/src/cli/commands/run.tsx +1 -0
- package/src/cli/commands/studio.ts +47 -0
- package/src/cli/commands/which.tsx +1 -0
- package/src/cli/index.ts +20 -5
- package/src/cli/ui/components/Badge.tsx +1 -0
- package/src/cli/ui/components/DataTable.tsx +1 -0
- package/src/cli/ui/components/Header.tsx +1 -0
- package/src/cli/ui/components/HelpBlock.tsx +1 -0
- package/src/cli/ui/components/KeyValue.tsx +1 -0
- package/src/cli/ui/components/OptionRow.tsx +1 -0
- package/src/cli/ui/components/Separator.tsx +1 -0
- package/src/cli/ui/components/StatusBox.tsx +1 -0
- package/src/cli/ui/components/VargBox.tsx +1 -0
- package/src/cli/ui/components/VargProgress.tsx +1 -0
- package/src/cli/ui/components/VargSpinner.tsx +1 -0
- package/src/cli/ui/components/VargText.tsx +1 -0
- package/src/react/assets.ts +9 -0
- package/src/react/elements.ts +0 -5
- package/src/react/examples/branching.tsx +6 -4
- package/src/react/examples/character-video.tsx +13 -10
- package/src/react/examples/madi.tsx +13 -10
- package/src/react/examples/mcmeows.tsx +40 -0
- package/src/react/examples/music-defaults.tsx +24 -0
- package/src/react/examples/quickstart-test.tsx +97 -0
- package/src/react/index.ts +1 -2
- package/src/react/react.test.ts +10 -10
- package/src/react/renderers/clip.ts +13 -24
- package/src/react/renderers/context.ts +3 -0
- package/src/react/renderers/image.ts +4 -2
- package/src/react/renderers/index.ts +0 -1
- package/src/react/renderers/music.ts +3 -3
- package/src/react/renderers/progress.ts +1 -3
- package/src/react/renderers/render.ts +49 -63
- package/src/react/renderers/speech.ts +2 -2
- package/src/react/renderers/video.ts +46 -9
- package/src/react/types.ts +18 -14
- package/src/studio/stages.ts +4 -24
- package/src/studio/step-renderer.ts +0 -15
- package/test-sync-v2.ts +30 -0
- package/test-sync-v2.tsx +29 -0
- package/tsconfig.json +5 -3
- package/video.tsx +7 -0
- package/src/react/cli.ts +0 -52
- package/src/react/renderers/animate.ts +0 -59
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# Video Generation Templates
|
|
2
|
+
|
|
3
|
+
Complete code templates for common video generation patterns.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Simple Slideshow](#simple-slideshow)
|
|
8
|
+
2. [Animated Video with Music](#animated-video-with-music)
|
|
9
|
+
3. [Talking Character](#talking-character)
|
|
10
|
+
4. [TikTok Multi-Clip](#tiktok-multi-clip)
|
|
11
|
+
5. [Character with Consistent Style](#character-with-consistent-style)
|
|
12
|
+
6. [Split Screen Comparison](#split-screen-comparison)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Simple Slideshow
|
|
17
|
+
|
|
18
|
+
**Requirements:** FAL_API_KEY only
|
|
19
|
+
|
|
20
|
+
Basic image slideshow with transitions and zoom effects.
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
// slideshow.tsx
|
|
24
|
+
import { render, Render, Clip, Image } from "vargai/react";
|
|
25
|
+
|
|
26
|
+
const SCENES = [
|
|
27
|
+
"sunset over ocean, cinematic golden hour",
|
|
28
|
+
"mountain peaks at dawn, misty atmosphere",
|
|
29
|
+
"city skyline at night, neon lights",
|
|
30
|
+
"forest path in autumn, fallen leaves",
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
async function main() {
|
|
34
|
+
await render(
|
|
35
|
+
<Render width={1080} height={1920}>
|
|
36
|
+
{SCENES.map((prompt, i) => (
|
|
37
|
+
<Clip
|
|
38
|
+
key={i}
|
|
39
|
+
duration={3}
|
|
40
|
+
transition={{ name: "fade", duration: 0.5 }}
|
|
41
|
+
>
|
|
42
|
+
<Image prompt={prompt} zoom="in" />
|
|
43
|
+
</Clip>
|
|
44
|
+
))}
|
|
45
|
+
</Render>,
|
|
46
|
+
{ output: "output/slideshow.mp4" }
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
console.log("Done! output/slideshow.mp4");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
main();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Run: `bun run slideshow.tsx`
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Animated Video with Music
|
|
60
|
+
|
|
61
|
+
**Requirements:** FAL_API_KEY + ELEVENLABS_API_KEY
|
|
62
|
+
|
|
63
|
+
Video with AI-generated animation and background music.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// animated-video.tsx
|
|
67
|
+
import { render, Render, Clip, Image, Animate, Music } from "vargai/react";
|
|
68
|
+
import { fal, elevenlabs } from "vargai/ai";
|
|
69
|
+
|
|
70
|
+
async function main() {
|
|
71
|
+
await render(
|
|
72
|
+
<Render width={1080} height={1920}>
|
|
73
|
+
<Music
|
|
74
|
+
prompt="upbeat electronic pop, energetic, modern tiktok vibe"
|
|
75
|
+
model={elevenlabs.musicModel()}
|
|
76
|
+
duration={10}
|
|
77
|
+
volume={0.7}
|
|
78
|
+
/>
|
|
79
|
+
|
|
80
|
+
<Clip duration={5}>
|
|
81
|
+
<Animate
|
|
82
|
+
image={Image({
|
|
83
|
+
prompt: "cute cat sitting on windowsill, golden hour lighting",
|
|
84
|
+
model: fal.imageModel("flux-schnell")
|
|
85
|
+
})}
|
|
86
|
+
motion="cat slowly turns head, blinks, tail swishes gently"
|
|
87
|
+
model={fal.videoModel("wan-2.5")}
|
|
88
|
+
duration={5}
|
|
89
|
+
/>
|
|
90
|
+
</Clip>
|
|
91
|
+
|
|
92
|
+
<Clip duration={5} transition={{ name: "fade", duration: 0.5 }}>
|
|
93
|
+
<Animate
|
|
94
|
+
image={Image({
|
|
95
|
+
prompt: "same cat stretching on windowsill, yawning"
|
|
96
|
+
})}
|
|
97
|
+
motion="cat stretches, yawns widely, settles back down"
|
|
98
|
+
model={fal.videoModel("wan-2.5")}
|
|
99
|
+
duration={5}
|
|
100
|
+
/>
|
|
101
|
+
</Clip>
|
|
102
|
+
</Render>,
|
|
103
|
+
{ output: "output/cat-video.mp4" }
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
main();
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Talking Character
|
|
113
|
+
|
|
114
|
+
**Requirements:** FAL_API_KEY + ELEVENLABS_API_KEY
|
|
115
|
+
|
|
116
|
+
Character that speaks with animated movement.
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
// talking-character.tsx
|
|
120
|
+
import { render, Render, Clip, Image, Animate, Speech } from "vargai/react";
|
|
121
|
+
import { fal, elevenlabs } from "vargai/ai";
|
|
122
|
+
|
|
123
|
+
const CHARACTER = "friendly cartoon robot, blue metallic body, expressive LED eyes, studio background";
|
|
124
|
+
|
|
125
|
+
const SCRIPT = `
|
|
126
|
+
Hello! I'm your AI assistant.
|
|
127
|
+
Today I'm going to show you something amazing.
|
|
128
|
+
Let's create some videos together!
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
async function main() {
|
|
132
|
+
await render(
|
|
133
|
+
<Render width={1080} height={1920}>
|
|
134
|
+
<Clip duration="auto">
|
|
135
|
+
<Animate
|
|
136
|
+
image={Image({
|
|
137
|
+
prompt: CHARACTER,
|
|
138
|
+
aspectRatio: "9:16",
|
|
139
|
+
model: fal.imageModel("flux-schnell")
|
|
140
|
+
})}
|
|
141
|
+
motion="robot talking naturally, subtle head movements, eyes blink occasionally, friendly gestures"
|
|
142
|
+
model={fal.videoModel("wan-2.5")}
|
|
143
|
+
/>
|
|
144
|
+
<Speech
|
|
145
|
+
voice="adam"
|
|
146
|
+
model={elevenlabs.speechModel("turbo")}
|
|
147
|
+
>
|
|
148
|
+
{SCRIPT}
|
|
149
|
+
</Speech>
|
|
150
|
+
</Clip>
|
|
151
|
+
</Render>,
|
|
152
|
+
{ output: "output/talking-robot.mp4" }
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
main();
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Available voices:** `adam`, `rachel`, `bella`, `sam`, `josh`
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## TikTok Multi-Clip
|
|
164
|
+
|
|
165
|
+
**Requirements:** FAL_API_KEY + ELEVENLABS_API_KEY
|
|
166
|
+
|
|
167
|
+
Multi-scene TikTok-style video with varied camera angles.
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
// tiktok-video.tsx
|
|
171
|
+
import { render, Render, Clip, Image, Animate, Music, Title } from "vargai/react";
|
|
172
|
+
import { fal, elevenlabs } from "vargai/ai";
|
|
173
|
+
|
|
174
|
+
const CHARACTER = "confident young woman, casual style, bright smile, modern apartment";
|
|
175
|
+
|
|
176
|
+
const SCENES = [
|
|
177
|
+
{
|
|
178
|
+
prompt: "extreme close-up face, surprised expression, wide eyes",
|
|
179
|
+
motion: "eyes widen in surprise, eyebrows raise, subtle gasp"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
prompt: "medium shot, laughing genuinely, hand on chest",
|
|
183
|
+
motion: "head tilts back slightly, genuine laugh, shoulders shake"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
prompt: "close-up, knowing smirk, raised eyebrow",
|
|
187
|
+
motion: "slow smile forms, eyebrow raises, subtle head nod"
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
prompt: "medium shot, waving at camera, bright smile",
|
|
191
|
+
motion: "waves energetically at camera, bright smile, slight bounce"
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
async function main() {
|
|
196
|
+
await render(
|
|
197
|
+
<Render width={1080} height={1920}>
|
|
198
|
+
<Music
|
|
199
|
+
prompt="trendy tiktok music, upbeat, catchy hook, viral sound"
|
|
200
|
+
model={elevenlabs.musicModel()}
|
|
201
|
+
duration={8}
|
|
202
|
+
volume={0.6}
|
|
203
|
+
/>
|
|
204
|
+
|
|
205
|
+
{SCENES.map((scene, i) => (
|
|
206
|
+
<Clip
|
|
207
|
+
key={i}
|
|
208
|
+
duration={2}
|
|
209
|
+
transition={{ name: "fade", duration: 0.3 }}
|
|
210
|
+
>
|
|
211
|
+
<Animate
|
|
212
|
+
image={Image({
|
|
213
|
+
prompt: `${CHARACTER}, ${scene.prompt}`,
|
|
214
|
+
aspectRatio: "9:16",
|
|
215
|
+
model: fal.imageModel("flux-schnell")
|
|
216
|
+
})}
|
|
217
|
+
motion={scene.motion}
|
|
218
|
+
model={fal.videoModel("wan-2.5")}
|
|
219
|
+
duration={5}
|
|
220
|
+
/>
|
|
221
|
+
{i === 0 && (
|
|
222
|
+
<Title position="bottom">POV: When it actually works</Title>
|
|
223
|
+
)}
|
|
224
|
+
</Clip>
|
|
225
|
+
))}
|
|
226
|
+
</Render>,
|
|
227
|
+
{ output: "output/tiktok-video.mp4" }
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
main();
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Character with Consistent Style
|
|
237
|
+
|
|
238
|
+
**Requirements:** FAL_API_KEY
|
|
239
|
+
|
|
240
|
+
Reuse the same character reference for visual consistency across clips.
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
// consistent-character.tsx
|
|
244
|
+
import { render, Render, Clip, Image, Animate } from "vargai/react";
|
|
245
|
+
import { fal } from "vargai/ai";
|
|
246
|
+
|
|
247
|
+
async function main() {
|
|
248
|
+
// Define character once - same reference = same generated image
|
|
249
|
+
const character = Image({
|
|
250
|
+
prompt: "cute cartoon fox, orange fur, big eyes, friendly expression",
|
|
251
|
+
model: fal.imageModel("flux-schnell"),
|
|
252
|
+
aspectRatio: "9:16"
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
await render(
|
|
256
|
+
<Render width={1080} height={1920}>
|
|
257
|
+
<Clip duration={3}>
|
|
258
|
+
<Animate
|
|
259
|
+
image={character}
|
|
260
|
+
motion="fox waves hello, tail wagging"
|
|
261
|
+
model={fal.videoModel("wan-2.5")}
|
|
262
|
+
duration={3}
|
|
263
|
+
/>
|
|
264
|
+
</Clip>
|
|
265
|
+
|
|
266
|
+
<Clip duration={3} transition={{ name: "fade", duration: 0.5 }}>
|
|
267
|
+
<Animate
|
|
268
|
+
image={character}
|
|
269
|
+
motion="fox dances happily, spinning around"
|
|
270
|
+
model={fal.videoModel("wan-2.5")}
|
|
271
|
+
duration={3}
|
|
272
|
+
/>
|
|
273
|
+
</Clip>
|
|
274
|
+
|
|
275
|
+
<Clip duration={3} transition={{ name: "fade", duration: 0.5 }}>
|
|
276
|
+
<Animate
|
|
277
|
+
image={character}
|
|
278
|
+
motion="fox blows a kiss at camera, winks"
|
|
279
|
+
model={fal.videoModel("wan-2.5")}
|
|
280
|
+
duration={3}
|
|
281
|
+
/>
|
|
282
|
+
</Clip>
|
|
283
|
+
</Render>,
|
|
284
|
+
{ output: "output/fox-video.mp4" }
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
main();
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Split Screen Comparison
|
|
294
|
+
|
|
295
|
+
**Requirements:** FAL_API_KEY
|
|
296
|
+
|
|
297
|
+
Side-by-side comparison layout.
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
// split-screen.tsx
|
|
301
|
+
import { render, Render, Clip, Image, Animate, Split, Title } from "vargai/react";
|
|
302
|
+
import { fal } from "vargai/ai";
|
|
303
|
+
|
|
304
|
+
async function main() {
|
|
305
|
+
const before = Image({
|
|
306
|
+
prompt: "messy room, clothes everywhere, unmade bed",
|
|
307
|
+
aspectRatio: "3:4"
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const after = Image({
|
|
311
|
+
prompt: "clean organized room, made bed, tidy shelves",
|
|
312
|
+
aspectRatio: "3:4"
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
await render(
|
|
316
|
+
<Render width={1080} height={1920}>
|
|
317
|
+
<Clip duration={5}>
|
|
318
|
+
<Split direction="horizontal">
|
|
319
|
+
<Animate
|
|
320
|
+
image={before}
|
|
321
|
+
motion="camera slowly pans across messy room"
|
|
322
|
+
model={fal.videoModel("wan-2.5")}
|
|
323
|
+
/>
|
|
324
|
+
<Animate
|
|
325
|
+
image={after}
|
|
326
|
+
motion="camera slowly pans across clean room"
|
|
327
|
+
model={fal.videoModel("wan-2.5")}
|
|
328
|
+
/>
|
|
329
|
+
</Split>
|
|
330
|
+
<Title position="bottom">
|
|
331
|
+
BEFORE AFTER
|
|
332
|
+
</Title>
|
|
333
|
+
</Clip>
|
|
334
|
+
</Render>,
|
|
335
|
+
{ output: "output/split-comparison.mp4" }
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
main();
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Component Reference
|
|
345
|
+
|
|
346
|
+
| Component | Props | Description |
|
|
347
|
+
|-----------|-------|-------------|
|
|
348
|
+
| `<Render>` | `width`, `height`, `fps` | Root container |
|
|
349
|
+
| `<Clip>` | `duration`, `transition` | Sequential segment |
|
|
350
|
+
| `<Image>` | `prompt`, `src`, `model`, `aspectRatio`, `zoom` | AI or static image |
|
|
351
|
+
| `<Animate>` | `image`, `motion`, `model`, `duration` | Image-to-video |
|
|
352
|
+
| `<Video>` | `src`, `prompt`, `model`, `keepAudio` | Video file or generated |
|
|
353
|
+
| `<Music>` | `prompt`, `model`, `duration`, `volume`, `loop` | Background music |
|
|
354
|
+
| `<Speech>` | `voice`, `model`, `children` | Text-to-speech |
|
|
355
|
+
| `<Title>` | `position`, `color`, `start`, `end` | Text overlay |
|
|
356
|
+
| `<Split>` | `direction` | Side-by-side layout |
|
|
357
|
+
|
|
358
|
+
## Transitions
|
|
359
|
+
|
|
360
|
+
Available transition effects:
|
|
361
|
+
|
|
362
|
+
- `fade` - Simple fade
|
|
363
|
+
- `crossfade` - Cross dissolve
|
|
364
|
+
- `wipeleft`, `wiperight`, `wipeup`, `wipedown` - Directional wipes
|
|
365
|
+
- `slideleft`, `slideright`, `slideup`, `slidedown` - Slides
|
|
366
|
+
- `cube` - 3D cube rotation
|
|
367
|
+
- `pixelize` - Pixelation effect
|
|
368
|
+
|
|
369
|
+
```tsx
|
|
370
|
+
<Clip transition={{ name: "cube", duration: 0.8 }}>
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Aspect Ratios
|
|
374
|
+
|
|
375
|
+
| Ratio | Use Case | Dimensions |
|
|
376
|
+
|-------|----------|------------|
|
|
377
|
+
| `9:16` | TikTok, Reels, Shorts | 1080x1920 |
|
|
378
|
+
| `16:9` | YouTube, Twitter | 1920x1080 |
|
|
379
|
+
| `1:1` | Instagram posts | 1080x1080 |
|
|
380
|
+
| `4:5` | Instagram portrait | 1080x1350 |
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* varg-video-generation setup script
|
|
4
|
+
*
|
|
5
|
+
* This script initializes a project for AI video generation.
|
|
6
|
+
* Can be run standalone by agents or via bunx vargai.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* bun scripts/setup.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
existsSync,
|
|
14
|
+
mkdirSync,
|
|
15
|
+
readFileSync,
|
|
16
|
+
symlinkSync,
|
|
17
|
+
writeFileSync,
|
|
18
|
+
} from "node:fs";
|
|
19
|
+
import { dirname, join } from "node:path";
|
|
20
|
+
|
|
21
|
+
const COLORS = {
|
|
22
|
+
reset: "\x1b[0m",
|
|
23
|
+
bold: "\x1b[1m",
|
|
24
|
+
dim: "\x1b[2m",
|
|
25
|
+
green: "\x1b[32m",
|
|
26
|
+
yellow: "\x1b[33m",
|
|
27
|
+
blue: "\x1b[34m",
|
|
28
|
+
red: "\x1b[31m",
|
|
29
|
+
cyan: "\x1b[36m",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const log = {
|
|
33
|
+
info: (msg: string) =>
|
|
34
|
+
console.log(`${COLORS.blue}info${COLORS.reset} ${msg}`),
|
|
35
|
+
success: (msg: string) =>
|
|
36
|
+
console.log(`${COLORS.green}done${COLORS.reset} ${msg}`),
|
|
37
|
+
warn: (msg: string) =>
|
|
38
|
+
console.log(`${COLORS.yellow}warn${COLORS.reset} ${msg}`),
|
|
39
|
+
error: (msg: string) =>
|
|
40
|
+
console.log(`${COLORS.red}error${COLORS.reset} ${msg}`),
|
|
41
|
+
step: (msg: string) =>
|
|
42
|
+
console.log(
|
|
43
|
+
`\n${COLORS.bold}${COLORS.cyan}==>${COLORS.reset} ${COLORS.bold}${msg}${COLORS.reset}`,
|
|
44
|
+
),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// .env template
|
|
48
|
+
const ENV_TEMPLATE = `# Varg AI Video Generation - API Keys
|
|
49
|
+
# Get your keys from the URLs below
|
|
50
|
+
|
|
51
|
+
# REQUIRED - Fal.ai (image & video generation)
|
|
52
|
+
# Get it: https://fal.ai/dashboard/keys
|
|
53
|
+
FAL_API_KEY=
|
|
54
|
+
|
|
55
|
+
# OPTIONAL - ElevenLabs (music & voice)
|
|
56
|
+
# Get it: https://elevenlabs.io/app/settings/api-keys
|
|
57
|
+
ELEVENLABS_API_KEY=
|
|
58
|
+
|
|
59
|
+
# OPTIONAL - Replicate (lipsync)
|
|
60
|
+
# Get it: https://replicate.com/account/api-tokens
|
|
61
|
+
REPLICATE_API_TOKEN=
|
|
62
|
+
|
|
63
|
+
# OPTIONAL - Groq (transcription)
|
|
64
|
+
# Get it: https://console.groq.com/keys
|
|
65
|
+
GROQ_API_KEY=
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
// Example video file
|
|
69
|
+
const EXAMPLE_VIDEO = `/**
|
|
70
|
+
* Example: Simple animated video
|
|
71
|
+
* Run: bun run examples/my-first-video.tsx
|
|
72
|
+
*/
|
|
73
|
+
import { render, Render, Clip, Image, Animate } from "vargai/react";
|
|
74
|
+
import { fal } from "vargai/ai";
|
|
75
|
+
|
|
76
|
+
async function main() {
|
|
77
|
+
console.log("Creating your first AI video...\\n");
|
|
78
|
+
|
|
79
|
+
await render(
|
|
80
|
+
<Render width={720} height={720}>
|
|
81
|
+
<Clip duration={3}>
|
|
82
|
+
<Animate
|
|
83
|
+
image={Image({
|
|
84
|
+
prompt: "a friendly robot waving hello, cartoon style, blue colors",
|
|
85
|
+
model: fal.imageModel("flux-schnell"),
|
|
86
|
+
aspectRatio: "1:1",
|
|
87
|
+
})}
|
|
88
|
+
motion="robot waves hello, friendly gesture"
|
|
89
|
+
model={fal.videoModel("wan-2.5")}
|
|
90
|
+
duration={3}
|
|
91
|
+
/>
|
|
92
|
+
</Clip>
|
|
93
|
+
</Render>,
|
|
94
|
+
{
|
|
95
|
+
output: "output/my-first-video.mp4",
|
|
96
|
+
cache: ".cache/ai"
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
console.log("\\nDone! Check output/my-first-video.mp4");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
main().catch(console.error);
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
async function main() {
|
|
107
|
+
console.log(`
|
|
108
|
+
${COLORS.bold}${COLORS.cyan}varg-video-generation${COLORS.reset} ${COLORS.dim}setup${COLORS.reset}
|
|
109
|
+
`);
|
|
110
|
+
|
|
111
|
+
const cwd = process.cwd();
|
|
112
|
+
|
|
113
|
+
// Step 1: Check/create directories
|
|
114
|
+
log.step("Setting up project structure");
|
|
115
|
+
|
|
116
|
+
const dirs = ["output", ".cache/ai", "examples"];
|
|
117
|
+
for (const dir of dirs) {
|
|
118
|
+
const path = join(cwd, dir);
|
|
119
|
+
if (!existsSync(path)) {
|
|
120
|
+
mkdirSync(path, { recursive: true });
|
|
121
|
+
log.success(`Created ${dir}/`);
|
|
122
|
+
} else {
|
|
123
|
+
log.info(`${dir}/ already exists`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Step 2: Check/create .env
|
|
128
|
+
log.step("Checking API keys");
|
|
129
|
+
|
|
130
|
+
const envPath = join(cwd, ".env");
|
|
131
|
+
let envContent = "";
|
|
132
|
+
let hasFalKey = false;
|
|
133
|
+
|
|
134
|
+
if (existsSync(envPath)) {
|
|
135
|
+
envContent = readFileSync(envPath, "utf8");
|
|
136
|
+
hasFalKey = /^FAL_API_KEY=.+/m.test(envContent);
|
|
137
|
+
|
|
138
|
+
if (hasFalKey) {
|
|
139
|
+
log.success("FAL_API_KEY found in .env");
|
|
140
|
+
} else {
|
|
141
|
+
log.warn("FAL_API_KEY not found in .env");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Check optional keys
|
|
145
|
+
const hasElevenLabs = /^ELEVENLABS_API_KEY=.+/m.test(envContent);
|
|
146
|
+
const hasReplicate = /^REPLICATE_API_TOKEN=.+/m.test(envContent);
|
|
147
|
+
const hasGroq = /^GROQ_API_KEY=.+/m.test(envContent);
|
|
148
|
+
|
|
149
|
+
if (hasElevenLabs)
|
|
150
|
+
log.info("ELEVENLABS_API_KEY found (music/voice enabled)");
|
|
151
|
+
if (hasReplicate) log.info("REPLICATE_API_TOKEN found (lipsync enabled)");
|
|
152
|
+
if (hasGroq) log.info("GROQ_API_KEY found (transcription enabled)");
|
|
153
|
+
|
|
154
|
+
if (!hasElevenLabs && !hasReplicate && !hasGroq) {
|
|
155
|
+
log.info("No optional keys found (basic video generation only)");
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
log.warn(".env file not found");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// If no FAL key, prompt for it
|
|
162
|
+
if (!hasFalKey) {
|
|
163
|
+
console.log(`
|
|
164
|
+
${COLORS.yellow}FAL_API_KEY is required for video generation.${COLORS.reset}
|
|
165
|
+
|
|
166
|
+
Get your free API key at: ${COLORS.cyan}https://fal.ai/dashboard/keys${COLORS.reset}
|
|
167
|
+
`);
|
|
168
|
+
|
|
169
|
+
process.stdout.write("Enter your FAL_API_KEY (or press Enter to skip): ");
|
|
170
|
+
|
|
171
|
+
const falKey = await new Promise<string>((resolve) => {
|
|
172
|
+
let input = "";
|
|
173
|
+
process.stdin.setEncoding("utf8");
|
|
174
|
+
process.stdin.once("data", (data) => {
|
|
175
|
+
input = data.toString().trim();
|
|
176
|
+
resolve(input);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (falKey) {
|
|
181
|
+
if (existsSync(envPath)) {
|
|
182
|
+
const newEnvContent = envContent.includes("FAL_API_KEY")
|
|
183
|
+
? envContent.replace(/^FAL_API_KEY=.*/m, `FAL_API_KEY=${falKey}`)
|
|
184
|
+
: `${envContent}\nFAL_API_KEY=${falKey}`;
|
|
185
|
+
writeFileSync(envPath, newEnvContent);
|
|
186
|
+
} else {
|
|
187
|
+
writeFileSync(
|
|
188
|
+
envPath,
|
|
189
|
+
ENV_TEMPLATE.replace("FAL_API_KEY=", `FAL_API_KEY=${falKey}`),
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
log.success("FAL_API_KEY saved to .env");
|
|
193
|
+
hasFalKey = true;
|
|
194
|
+
} else {
|
|
195
|
+
if (!existsSync(envPath)) {
|
|
196
|
+
writeFileSync(envPath, ENV_TEMPLATE);
|
|
197
|
+
log.info("Created .env template - add your keys manually");
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Step 3: Create example file
|
|
203
|
+
log.step("Creating example files");
|
|
204
|
+
|
|
205
|
+
const examplePath = join(cwd, "examples/my-first-video.tsx");
|
|
206
|
+
if (!existsSync(examplePath)) {
|
|
207
|
+
writeFileSync(examplePath, EXAMPLE_VIDEO);
|
|
208
|
+
log.success("Created examples/my-first-video.tsx");
|
|
209
|
+
} else {
|
|
210
|
+
log.info("examples/my-first-video.tsx already exists");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Step 4: Add to .gitignore
|
|
214
|
+
log.step("Updating .gitignore");
|
|
215
|
+
|
|
216
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
217
|
+
const gitignoreEntries = [".env", ".cache/", "output/"];
|
|
218
|
+
let gitignoreContent = existsSync(gitignorePath)
|
|
219
|
+
? readFileSync(gitignorePath, "utf8")
|
|
220
|
+
: "";
|
|
221
|
+
|
|
222
|
+
let added = false;
|
|
223
|
+
for (const entry of gitignoreEntries) {
|
|
224
|
+
if (!gitignoreContent.includes(entry)) {
|
|
225
|
+
gitignoreContent += `\n${entry}`;
|
|
226
|
+
added = true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (added) {
|
|
231
|
+
writeFileSync(gitignorePath, gitignoreContent.trim() + "\n");
|
|
232
|
+
log.success("Updated .gitignore");
|
|
233
|
+
} else {
|
|
234
|
+
log.info(".gitignore already configured");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Summary
|
|
238
|
+
console.log(`
|
|
239
|
+
${COLORS.green}${COLORS.bold}Setup complete!${COLORS.reset}
|
|
240
|
+
|
|
241
|
+
${COLORS.bold}Next steps:${COLORS.reset}
|
|
242
|
+
`);
|
|
243
|
+
|
|
244
|
+
if (!hasFalKey) {
|
|
245
|
+
console.log(` ${COLORS.yellow}1. Add FAL_API_KEY to .env${COLORS.reset}
|
|
246
|
+
Get it at: https://fal.ai/dashboard/keys
|
|
247
|
+
`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
console.log(` ${hasFalKey ? "1" : "2"}. Install vargai package:
|
|
251
|
+
${COLORS.cyan}bun add vargai${COLORS.reset}
|
|
252
|
+
|
|
253
|
+
${hasFalKey ? "2" : "3"}. Run your first video:
|
|
254
|
+
${COLORS.cyan}bun run examples/my-first-video.tsx${COLORS.reset}
|
|
255
|
+
|
|
256
|
+
${COLORS.dim}Documentation: https://github.com/vargHQ/sdk${COLORS.reset}
|
|
257
|
+
`);
|
|
258
|
+
|
|
259
|
+
process.exit(0);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
main().catch((error) => {
|
|
263
|
+
log.error(error.message);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
});
|
package/src/ai-sdk/cache.ts
CHANGED
|
@@ -15,12 +15,12 @@ export interface ImagePlaceholderFallbackOptions {
|
|
|
15
15
|
export function imagePlaceholderFallbackMiddleware(
|
|
16
16
|
options: ImagePlaceholderFallbackOptions,
|
|
17
17
|
): ImageModelV3Middleware {
|
|
18
|
-
const { mode
|
|
18
|
+
const { mode } = options;
|
|
19
19
|
|
|
20
20
|
return {
|
|
21
21
|
specificationVersion: "v3",
|
|
22
22
|
wrapGenerate: async ({ doGenerate, params, model }) => {
|
|
23
|
-
|
|
23
|
+
if (mode === "preview") {
|
|
24
24
|
const [width, height] = (params.size?.split("x").map(Number) ?? [
|
|
25
25
|
1024, 1024,
|
|
26
26
|
]) as [number, number];
|
|
@@ -42,7 +42,7 @@ export function imagePlaceholderFallbackMiddleware(
|
|
|
42
42
|
warnings: [
|
|
43
43
|
{
|
|
44
44
|
type: "other" as const,
|
|
45
|
-
message: "placeholder:
|
|
45
|
+
message: "placeholder: preview mode",
|
|
46
46
|
},
|
|
47
47
|
],
|
|
48
48
|
response: {
|
|
@@ -51,26 +51,9 @@ export function imagePlaceholderFallbackMiddleware(
|
|
|
51
51
|
headers: undefined,
|
|
52
52
|
},
|
|
53
53
|
};
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
if (mode === "preview") {
|
|
57
|
-
return createPlaceholderResult();
|
|
58
54
|
}
|
|
59
55
|
|
|
60
|
-
|
|
61
|
-
return await doGenerate();
|
|
62
|
-
} catch (e) {
|
|
63
|
-
if (mode === "strict") throw e;
|
|
64
|
-
|
|
65
|
-
const error = e instanceof Error ? e : new Error(String(e));
|
|
66
|
-
const promptText =
|
|
67
|
-
typeof params.prompt === "string"
|
|
68
|
-
? params.prompt
|
|
69
|
-
: ((params.prompt as { text?: string } | undefined)?.text ??
|
|
70
|
-
"placeholder");
|
|
71
|
-
onFallback?.(error, promptText);
|
|
72
|
-
return createPlaceholderResult();
|
|
73
|
-
}
|
|
56
|
+
return doGenerate();
|
|
74
57
|
},
|
|
75
58
|
};
|
|
76
59
|
}
|