vargai 0.4.0-alpha4 → 0.4.0-alpha40
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/.env.example +6 -0
- package/README.md +483 -61
- package/assets/fonts/TikTokSans-Bold.ttf +0 -0
- package/examples/grok-imagine-test.tsx +155 -0
- 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 +10 -4
- package/pipeline/cookbooks/round-video-character.md +1 -1
- package/skills/varg-video-generation/SKILL.md +224 -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 -3
- package/src/ai-sdk/examples/google-image.ts +62 -0
- package/src/ai-sdk/index.ts +10 -0
- 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/CONTRIBUTING.md +457 -0
- package/src/ai-sdk/providers/editly/backends/index.ts +8 -0
- package/src/ai-sdk/providers/editly/backends/local.ts +94 -0
- package/src/ai-sdk/providers/editly/backends/types.ts +74 -0
- package/src/ai-sdk/providers/editly/editly.test.ts +49 -1
- package/src/ai-sdk/providers/editly/index.ts +164 -80
- package/src/ai-sdk/providers/editly/layers.ts +58 -6
- package/src/ai-sdk/providers/editly/rendi/editly-with-rendi-backend.test.ts +335 -0
- package/src/ai-sdk/providers/editly/rendi/index.ts +289 -0
- package/src/ai-sdk/providers/editly/rendi/rendi.test.ts +35 -0
- package/src/ai-sdk/providers/editly/types.ts +30 -0
- package/src/ai-sdk/providers/elevenlabs.ts +10 -2
- package/src/ai-sdk/providers/fal.test.ts +214 -0
- package/src/ai-sdk/providers/fal.ts +435 -40
- package/src/ai-sdk/providers/google.ts +423 -0
- package/src/ai-sdk/providers/together.ts +191 -0
- package/src/cli/commands/find.tsx +1 -0
- package/src/cli/commands/frame.tsx +616 -0
- package/src/cli/commands/hello.ts +85 -0
- package/src/cli/commands/help.tsx +18 -30
- package/src/cli/commands/index.ts +11 -2
- package/src/cli/commands/init.tsx +570 -0
- package/src/cli/commands/list.tsx +1 -0
- package/src/cli/commands/render.tsx +322 -76
- package/src/cli/commands/run.tsx +1 -0
- package/src/cli/commands/storyboard.tsx +1714 -0
- package/src/cli/commands/which.tsx +1 -0
- package/src/cli/index.ts +23 -4
- 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/definitions/actions/grok-edit.ts +133 -0
- package/src/definitions/actions/index.ts +16 -0
- package/src/definitions/actions/qwen-angles.ts +218 -0
- package/src/index.ts +1 -0
- package/src/providers/fal.ts +196 -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/local-files-test.tsx +19 -0
- package/src/react/examples/ltx2-test.tsx +25 -0
- 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 +101 -0
- package/src/react/examples/qwen-angles-test.tsx +72 -0
- package/src/react/index.ts +3 -3
- package/src/react/layouts/grid.tsx +1 -1
- package/src/react/layouts/index.ts +2 -1
- package/src/react/layouts/slot.tsx +85 -0
- package/src/react/layouts/split.tsx +18 -0
- package/src/react/react.test.ts +60 -11
- package/src/react/renderers/burn-captions.ts +95 -0
- package/src/react/renderers/cache.test.ts +182 -0
- package/src/react/renderers/captions.ts +25 -6
- package/src/react/renderers/clip.ts +56 -25
- package/src/react/renderers/context.ts +5 -2
- package/src/react/renderers/image.ts +5 -2
- package/src/react/renderers/index.ts +0 -1
- package/src/react/renderers/music.ts +8 -3
- package/src/react/renderers/packshot/blinking-button.ts +413 -0
- package/src/react/renderers/packshot.ts +170 -8
- package/src/react/renderers/progress.ts +4 -3
- package/src/react/renderers/render.ts +127 -71
- package/src/react/renderers/speech.ts +2 -2
- package/src/react/renderers/split.ts +34 -13
- package/src/react/renderers/utils.test.ts +80 -0
- package/src/react/renderers/utils.ts +37 -1
- package/src/react/renderers/video.ts +47 -9
- package/src/react/types.ts +70 -17
- package/src/studio/stages.ts +40 -39
- package/src/studio/step-renderer.ts +14 -24
- package/src/studio/ui/index.html +2 -2
- package/src/tests/all.test.ts +4 -4
- package/src/tests/index.ts +1 -1
- package/test-slot-grid.tsx +19 -0
- package/test-slot-userland.tsx +30 -0
- package/test-sync-v2.ts +30 -0
- package/test-sync-v2.tsx +29 -0
- package/tsconfig.json +1 -1
- package/video.tsx +7 -0
- package/src/ai-sdk/providers/editly/ffmpeg.ts +0 -60
- package/src/react/renderers/animate.ts +0 -59
- /package/src/cli/commands/{studio.tsx → studio.ts} +0 -0
package/src/providers/fal.ts
CHANGED
|
@@ -7,6 +7,11 @@ import { fal } from "@fal-ai/client";
|
|
|
7
7
|
import type { JobStatusUpdate, ProviderConfig } from "../core/schema/types";
|
|
8
8
|
import { BaseProvider, ensureUrl } from "./base";
|
|
9
9
|
|
|
10
|
+
const falApiKey = process.env.FAL_API_KEY ?? process.env.FAL_KEY;
|
|
11
|
+
if (falApiKey) {
|
|
12
|
+
fal.config({ credentials: falApiKey });
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
export class FalProvider extends BaseProvider {
|
|
11
16
|
readonly name = "fal";
|
|
12
17
|
|
|
@@ -369,6 +374,197 @@ export class FalProvider extends BaseProvider {
|
|
|
369
374
|
console.log("[fal] completed!");
|
|
370
375
|
return result;
|
|
371
376
|
}
|
|
377
|
+
|
|
378
|
+
// ============================================================================
|
|
379
|
+
// Grok Imagine Video methods (xAI)
|
|
380
|
+
// ============================================================================
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Generate video from text using Grok Imagine Video
|
|
384
|
+
* Supports 1-15 second videos at 480p or 720p resolution
|
|
385
|
+
*/
|
|
386
|
+
async grokTextToVideo(args: {
|
|
387
|
+
prompt: string;
|
|
388
|
+
duration?: number;
|
|
389
|
+
aspectRatio?: "16:9" | "4:3" | "3:2" | "1:1" | "2:3" | "3:4" | "9:16";
|
|
390
|
+
resolution?: "480p" | "720p";
|
|
391
|
+
}) {
|
|
392
|
+
const modelId = "xai/grok-imagine-video/text-to-video";
|
|
393
|
+
|
|
394
|
+
console.log(`[fal] starting grok text-to-video: ${modelId}`);
|
|
395
|
+
console.log(`[fal] prompt: ${args.prompt}`);
|
|
396
|
+
|
|
397
|
+
const result = await fal.subscribe(modelId, {
|
|
398
|
+
input: {
|
|
399
|
+
prompt: args.prompt,
|
|
400
|
+
duration: args.duration ?? 6,
|
|
401
|
+
aspect_ratio: args.aspectRatio ?? "16:9",
|
|
402
|
+
resolution: args.resolution ?? "720p",
|
|
403
|
+
},
|
|
404
|
+
logs: true,
|
|
405
|
+
onQueueUpdate: (update) => {
|
|
406
|
+
if (update.status === "IN_PROGRESS") {
|
|
407
|
+
console.log(
|
|
408
|
+
`[fal] ${update.logs?.map((l) => l.message).join(" ") || "processing..."}`,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
console.log("[fal] completed!");
|
|
415
|
+
return result;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Generate video from image using Grok Imagine Video
|
|
420
|
+
* Supports 1-15 second videos at 480p or 720p resolution
|
|
421
|
+
*/
|
|
422
|
+
async grokImageToVideo(args: {
|
|
423
|
+
prompt: string;
|
|
424
|
+
imageUrl: string;
|
|
425
|
+
duration?: number;
|
|
426
|
+
aspectRatio?:
|
|
427
|
+
| "auto"
|
|
428
|
+
| "16:9"
|
|
429
|
+
| "4:3"
|
|
430
|
+
| "3:2"
|
|
431
|
+
| "1:1"
|
|
432
|
+
| "2:3"
|
|
433
|
+
| "3:4"
|
|
434
|
+
| "9:16";
|
|
435
|
+
resolution?: "480p" | "720p";
|
|
436
|
+
}) {
|
|
437
|
+
const modelId = "xai/grok-imagine-video/image-to-video";
|
|
438
|
+
|
|
439
|
+
console.log(`[fal] starting grok image-to-video: ${modelId}`);
|
|
440
|
+
console.log(`[fal] prompt: ${args.prompt}`);
|
|
441
|
+
|
|
442
|
+
const imageUrl = await ensureUrl(args.imageUrl, (buffer) =>
|
|
443
|
+
this.uploadFile(buffer),
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
const result = await fal.subscribe(modelId, {
|
|
447
|
+
input: {
|
|
448
|
+
prompt: args.prompt,
|
|
449
|
+
image_url: imageUrl,
|
|
450
|
+
duration: args.duration ?? 6,
|
|
451
|
+
aspect_ratio: args.aspectRatio ?? "auto",
|
|
452
|
+
resolution: args.resolution ?? "720p",
|
|
453
|
+
},
|
|
454
|
+
logs: true,
|
|
455
|
+
onQueueUpdate: (update) => {
|
|
456
|
+
if (update.status === "IN_PROGRESS") {
|
|
457
|
+
console.log(
|
|
458
|
+
`[fal] ${update.logs?.map((l) => l.message).join(" ") || "processing..."}`,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
console.log("[fal] completed!");
|
|
465
|
+
return result;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Edit video using Grok Imagine Video
|
|
470
|
+
* Video will be resized to max 854x480 and truncated to 8 seconds
|
|
471
|
+
*/
|
|
472
|
+
async grokEditVideo(args: {
|
|
473
|
+
prompt: string;
|
|
474
|
+
videoUrl: string;
|
|
475
|
+
resolution?: "auto" | "480p" | "720p";
|
|
476
|
+
}) {
|
|
477
|
+
const modelId = "xai/grok-imagine-video/edit-video";
|
|
478
|
+
|
|
479
|
+
console.log(`[fal] starting grok edit-video: ${modelId}`);
|
|
480
|
+
console.log(`[fal] prompt: ${args.prompt}`);
|
|
481
|
+
|
|
482
|
+
const videoUrl = await ensureUrl(args.videoUrl, (buffer) =>
|
|
483
|
+
this.uploadFile(buffer),
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
const result = await fal.subscribe(modelId, {
|
|
487
|
+
input: {
|
|
488
|
+
prompt: args.prompt,
|
|
489
|
+
video_url: videoUrl,
|
|
490
|
+
resolution: args.resolution ?? "auto",
|
|
491
|
+
},
|
|
492
|
+
logs: true,
|
|
493
|
+
onQueueUpdate: (update) => {
|
|
494
|
+
if (update.status === "IN_PROGRESS") {
|
|
495
|
+
console.log(
|
|
496
|
+
`[fal] ${update.logs?.map((l) => l.message).join(" ") || "processing..."}`,
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
console.log("[fal] completed!");
|
|
503
|
+
return result;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// ============================================================================
|
|
507
|
+
// Qwen Image Edit 2511 Multiple Angles
|
|
508
|
+
// ============================================================================
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Adjust camera angle of an image using Qwen Image Edit 2511 Multiple Angles
|
|
512
|
+
* Generates same scene from different angles (azimuth/elevation)
|
|
513
|
+
*/
|
|
514
|
+
async qwenMultipleAngles(args: {
|
|
515
|
+
imageUrl: string;
|
|
516
|
+
horizontalAngle?: number;
|
|
517
|
+
verticalAngle?: number;
|
|
518
|
+
zoom?: number;
|
|
519
|
+
additionalPrompt?: string;
|
|
520
|
+
loraScale?: number;
|
|
521
|
+
imageSize?: string | { width: number; height: number };
|
|
522
|
+
guidanceScale?: number;
|
|
523
|
+
numInferenceSteps?: number;
|
|
524
|
+
acceleration?: "none" | "regular";
|
|
525
|
+
negativePrompt?: string;
|
|
526
|
+
seed?: number;
|
|
527
|
+
outputFormat?: "png" | "jpeg" | "webp";
|
|
528
|
+
numImages?: number;
|
|
529
|
+
}) {
|
|
530
|
+
const modelId = "fal-ai/qwen-image-edit-2511-multiple-angles";
|
|
531
|
+
|
|
532
|
+
console.log(`[fal] starting qwen multiple angles: ${modelId}`);
|
|
533
|
+
|
|
534
|
+
const imageUrl = await ensureUrl(args.imageUrl, (buffer) =>
|
|
535
|
+
this.uploadFile(buffer),
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
const result = await fal.subscribe(modelId, {
|
|
539
|
+
input: {
|
|
540
|
+
image_urls: [imageUrl],
|
|
541
|
+
horizontal_angle: args.horizontalAngle ?? 0,
|
|
542
|
+
vertical_angle: args.verticalAngle ?? 0,
|
|
543
|
+
zoom: args.zoom ?? 5,
|
|
544
|
+
additional_prompt: args.additionalPrompt,
|
|
545
|
+
lora_scale: args.loraScale ?? 1,
|
|
546
|
+
image_size: args.imageSize,
|
|
547
|
+
guidance_scale: args.guidanceScale ?? 4.5,
|
|
548
|
+
num_inference_steps: args.numInferenceSteps ?? 28,
|
|
549
|
+
acceleration: args.acceleration ?? "regular",
|
|
550
|
+
negative_prompt: args.negativePrompt ?? "",
|
|
551
|
+
seed: args.seed,
|
|
552
|
+
output_format: args.outputFormat ?? "png",
|
|
553
|
+
num_images: args.numImages ?? 1,
|
|
554
|
+
},
|
|
555
|
+
logs: true,
|
|
556
|
+
onQueueUpdate: (update) => {
|
|
557
|
+
if (update.status === "IN_PROGRESS") {
|
|
558
|
+
console.log(
|
|
559
|
+
`[fal] ${update.logs?.map((l) => l.message).join(" ") || "processing..."}`,
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
console.log("[fal] completed!");
|
|
566
|
+
return result;
|
|
567
|
+
}
|
|
372
568
|
}
|
|
373
569
|
|
|
374
570
|
// Export singleton instance
|
package/src/react/elements.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
AnimateProps,
|
|
3
2
|
CaptionsProps,
|
|
4
3
|
ClipProps,
|
|
5
4
|
ImageProps,
|
|
@@ -71,10 +70,6 @@ export function Video(props: VideoProps): VargElement<"video"> {
|
|
|
71
70
|
return createElement("video", props as Record<string, unknown>, undefined);
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
export function Animate(props: AnimateProps): VargElement<"animate"> {
|
|
75
|
-
return createElement("animate", props as Record<string, unknown>, undefined);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
73
|
export function Speech(props: SpeechProps): VargElement<"speech"> {
|
|
79
74
|
return createElement(
|
|
80
75
|
"speech",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { elevenlabs } from "../../ai-sdk/providers/elevenlabs";
|
|
2
2
|
import { fal } from "../../ai-sdk/providers/fal";
|
|
3
|
-
import {
|
|
3
|
+
import { Clip, Image, Render, Speech, Title, Video } from "..";
|
|
4
4
|
|
|
5
5
|
// Non-linear tree: multiple clips with independent branches
|
|
6
6
|
// Clip 1: TalkingHead (Image -> Animate + Speech)
|
|
@@ -35,10 +35,12 @@ export default (
|
|
|
35
35
|
<Render width={1080} height={1920}>
|
|
36
36
|
{/* Clip 1: Talking head intro */}
|
|
37
37
|
<Clip duration={5}>
|
|
38
|
-
<
|
|
39
|
-
|
|
38
|
+
<Video
|
|
39
|
+
prompt={{
|
|
40
|
+
text: "talking naturally, slight head movements, friendly expression",
|
|
41
|
+
images: [character],
|
|
42
|
+
}}
|
|
40
43
|
model={fal.videoModel("wan-2.5")}
|
|
41
|
-
motion="talking naturally, slight head movements, friendly expression"
|
|
42
44
|
/>
|
|
43
45
|
<Speech voice="adam" model={elevenlabs.speechModel("turbo")}>
|
|
44
46
|
Hey everyone! Today we're looking at the biggest smartphone upgrade of
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { elevenlabs } from "../../ai-sdk/providers/elevenlabs";
|
|
2
2
|
import { fal } from "../../ai-sdk/providers/fal";
|
|
3
|
-
import {
|
|
3
|
+
import { Clip, Image, Music, Render, render, Video } from "..";
|
|
4
4
|
|
|
5
5
|
const MADI_REF =
|
|
6
6
|
"https://s3.varg.ai/fellowers/madi/character_shots/madi_shot_03_closeup.png";
|
|
@@ -54,16 +54,19 @@ async function main() {
|
|
|
54
54
|
|
|
55
55
|
{SCENES.map((scene) => (
|
|
56
56
|
<Clip key={scene.prompt} duration={2}>
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
<Video
|
|
58
|
+
prompt={{
|
|
59
|
+
text: scene.motion,
|
|
60
|
+
images: [
|
|
61
|
+
Image({
|
|
62
|
+
prompt: { text: scene.prompt, images: [MADI_REF] },
|
|
63
|
+
model: fal.imageModel("nano-banana-pro/edit"),
|
|
64
|
+
aspectRatio: "9:16",
|
|
65
|
+
resize: "cover",
|
|
66
|
+
}),
|
|
67
|
+
],
|
|
68
|
+
}}
|
|
65
69
|
model={fal.videoModel("wan-2.5")}
|
|
66
|
-
duration={5}
|
|
67
70
|
/>
|
|
68
71
|
</Clip>
|
|
69
72
|
))}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { fal } from "../../ai-sdk/providers/fal";
|
|
2
|
+
import { Clip, Image, Render, Video } from "..";
|
|
3
|
+
|
|
4
|
+
export default (
|
|
5
|
+
<Render width={1080} height={1920}>
|
|
6
|
+
<Clip duration={3}>
|
|
7
|
+
<Image src="media/cyberpunk-street.png" />
|
|
8
|
+
</Clip>
|
|
9
|
+
<Clip duration={3}>
|
|
10
|
+
<Video
|
|
11
|
+
prompt={{
|
|
12
|
+
text: "camera pans across the scene",
|
|
13
|
+
images: [Image({ src: "media/fal-coffee-shop.png" })],
|
|
14
|
+
}}
|
|
15
|
+
model={fal.videoModel("kling-v2.5")}
|
|
16
|
+
/>
|
|
17
|
+
</Clip>
|
|
18
|
+
</Render>
|
|
19
|
+
);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { fal } from "../../ai-sdk/providers/fal";
|
|
2
|
+
import { Clip, Render, Video } from "..";
|
|
3
|
+
|
|
4
|
+
export default (
|
|
5
|
+
<Render width={1248} height={704}>
|
|
6
|
+
<Clip>
|
|
7
|
+
<Video
|
|
8
|
+
prompt={{
|
|
9
|
+
text: "Camera slowly dollies in toward her face, city lights flicker",
|
|
10
|
+
images: [
|
|
11
|
+
"https://storage.googleapis.com/falserverless/example_inputs/ltxv-2-i2v-input.jpg",
|
|
12
|
+
],
|
|
13
|
+
}}
|
|
14
|
+
model={fal.videoModel("ltx-2-19b-distilled")}
|
|
15
|
+
keepAudio
|
|
16
|
+
providerOptions={{
|
|
17
|
+
fal: {
|
|
18
|
+
generate_audio: true,
|
|
19
|
+
camera_lora: "dolly_in",
|
|
20
|
+
},
|
|
21
|
+
}}
|
|
22
|
+
/>
|
|
23
|
+
</Clip>
|
|
24
|
+
</Render>
|
|
25
|
+
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { elevenlabs } from "../../ai-sdk/providers/elevenlabs";
|
|
2
2
|
import { fal } from "../../ai-sdk/providers/fal";
|
|
3
|
-
import {
|
|
3
|
+
import { Clip, Image, Music, Render, Video } from "..";
|
|
4
4
|
|
|
5
5
|
const MADI_REF =
|
|
6
6
|
"https://s3.varg.ai/fellowers/madi/character_shots/madi_shot_03_closeup.png";
|
|
@@ -43,16 +43,19 @@ export default (
|
|
|
43
43
|
|
|
44
44
|
{SCENES.map((scene) => (
|
|
45
45
|
<Clip key={scene.prompt} duration={2}>
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
<Video
|
|
47
|
+
prompt={{
|
|
48
|
+
text: scene.motion,
|
|
49
|
+
images: [
|
|
50
|
+
Image({
|
|
51
|
+
prompt: { text: scene.prompt, images: [MADI_REF] },
|
|
52
|
+
model: fal.imageModel("nano-banana-pro/edit"),
|
|
53
|
+
aspectRatio: "9:16",
|
|
54
|
+
resize: "cover",
|
|
55
|
+
}),
|
|
56
|
+
],
|
|
57
|
+
}}
|
|
54
58
|
model={fal.videoModel("wan-2.5")}
|
|
55
|
-
duration={5}
|
|
56
59
|
/>
|
|
57
60
|
</Clip>
|
|
58
61
|
))}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { elevenlabs } from "../../ai-sdk/providers/elevenlabs";
|
|
2
|
+
import { fal } from "../../ai-sdk/providers/fal";
|
|
3
|
+
import { Clip, Music, Render, render, Title, Video } from "..";
|
|
4
|
+
|
|
5
|
+
export default (
|
|
6
|
+
<Render>
|
|
7
|
+
<Clip duration={4}>
|
|
8
|
+
<Video prompt="A sophisticated tabby cat wearing a tailored tiny business suit and small round glasses, standing upright at a McDonald's counter. One paw raised pointing assertively at the illuminated menu board above. The cat has an extremely serious, concentrated expression with furrowed brows. Cinematic lighting with warm McDonald's interior ambiance, shallow depth of field focusing on the cat's determined face, photorealistic fur texture with fine detail. Professional business atmosphere meets fast food chaos." />
|
|
9
|
+
</Clip>
|
|
10
|
+
|
|
11
|
+
<Clip duration={4}>
|
|
12
|
+
<Video prompt="Absolute mayhem - four to five cats in a chaotic pile fight. Orange tabby, black and white tuxedo cat, calico with patches, and fluffy gray cat all scrambling, paws flailing, tumbling over each other in exaggerated cartoon-style motion. They're all desperately reaching for a single perfect golden McDonald's french fry sitting on a red plastic tray in the center. Wide-eyed expressions, mouths open mid-meow, dynamic motion blur, comedic timing. Fast food restaurant background slightly blurred. Playful, over-the-top energy." />
|
|
13
|
+
</Clip>
|
|
14
|
+
|
|
15
|
+
<Clip duration={3}>
|
|
16
|
+
<Video prompt="Proud orange tabby cat wearing an official McDonald's crew member visor and name tag, standing upright behind a restaurant register counter. Front paws crossed confidently across chest, chin lifted with a smug, self-satisfied expression. Perfect posture, professional demeanor. Bright McDonald's interior lighting, red and yellow color scheme in background. The cat radiates 'employee of the month' energy. Crisp, clean, professional fast food aesthetic." />
|
|
17
|
+
<Title position="bottom">McMeow's: NOW HIRING</Title>
|
|
18
|
+
</Clip>
|
|
19
|
+
|
|
20
|
+
<Music
|
|
21
|
+
model={elevenlabs.musicModel()}
|
|
22
|
+
prompt="playful upbeat comedy music with quirky pizzicato strings and light percussion, funny corporate training video vibes"
|
|
23
|
+
duration={11}
|
|
24
|
+
/>
|
|
25
|
+
</Render>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
async function main() {
|
|
29
|
+
const component = await import("./mcmeows.tsx").then((m) => m.default);
|
|
30
|
+
await render(component, {
|
|
31
|
+
output: "output/mcmeows.mp4",
|
|
32
|
+
cache: ".cache/ai",
|
|
33
|
+
verbose: true,
|
|
34
|
+
defaults: {
|
|
35
|
+
video: fal.videoModel("wan-2.5"),
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { elevenlabs } from "../../ai-sdk/providers/elevenlabs";
|
|
2
|
+
import { Clip, Image, Music, Render, render } from "..";
|
|
3
|
+
|
|
4
|
+
export default (
|
|
5
|
+
<Render width={1920} height={1080}>
|
|
6
|
+
<Clip duration={5}>
|
|
7
|
+
<Image src="media/cyberpunk-street.png" />
|
|
8
|
+
</Clip>
|
|
9
|
+
<Music prompt="calm ambient electronic music" duration={5} />
|
|
10
|
+
</Render>
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
const component = await import("./music-defaults.tsx").then((m) => m.default);
|
|
15
|
+
await render(component, {
|
|
16
|
+
output: "output/music-defaults.mp4",
|
|
17
|
+
defaults: {
|
|
18
|
+
music: elevenlabs.musicModel(),
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
console.log("done! check output/music-defaults.mp4");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quickstart Test - Verification script for video generation setup
|
|
3
|
+
*
|
|
4
|
+
* This minimal test confirms your FAL_API_KEY is working correctly.
|
|
5
|
+
* It generates a simple 3-second animated image.
|
|
6
|
+
*
|
|
7
|
+
* Run: bun run src/react/examples/quickstart-test.tsx
|
|
8
|
+
* Output: output/quickstart-test.mp4
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { fal } from "../../ai-sdk/providers/fal";
|
|
12
|
+
import { Clip, Image, Render, render, Video } from "..";
|
|
13
|
+
|
|
14
|
+
const quickstartVideo = (
|
|
15
|
+
<Render width={720} height={720}>
|
|
16
|
+
<Clip duration={3}>
|
|
17
|
+
<Video
|
|
18
|
+
prompt={{
|
|
19
|
+
text: "robot waves hello, friendly gesture, slight head tilt",
|
|
20
|
+
images: [
|
|
21
|
+
Image({
|
|
22
|
+
prompt:
|
|
23
|
+
"a friendly robot waving hello, simple cartoon style, blue and white colors, clean background",
|
|
24
|
+
model: fal.imageModel("flux-schnell"),
|
|
25
|
+
aspectRatio: "1:1",
|
|
26
|
+
}),
|
|
27
|
+
],
|
|
28
|
+
}}
|
|
29
|
+
model={fal.videoModel("wan-2.5")}
|
|
30
|
+
/>
|
|
31
|
+
</Clip>
|
|
32
|
+
</Render>
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export default quickstartVideo;
|
|
36
|
+
|
|
37
|
+
async function main() {
|
|
38
|
+
console.log("=== Varg Video Generation - Setup Verification ===\n");
|
|
39
|
+
|
|
40
|
+
// Check for FAL_API_KEY
|
|
41
|
+
if (!process.env.FAL_API_KEY) {
|
|
42
|
+
console.error("ERROR: FAL_API_KEY not found in environment");
|
|
43
|
+
console.error("\nTo fix this:");
|
|
44
|
+
console.error("1. Get an API key at: https://fal.ai/dashboard/keys");
|
|
45
|
+
console.error("2. Add to .env file: FAL_API_KEY=fal_xxxxx");
|
|
46
|
+
console.error("3. Run this test again");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log("FAL_API_KEY found");
|
|
51
|
+
|
|
52
|
+
// Check for optional keys
|
|
53
|
+
const hasElevenLabs = !!process.env.ELEVENLABS_API_KEY;
|
|
54
|
+
const hasReplicate = !!process.env.REPLICATE_API_TOKEN;
|
|
55
|
+
const hasGroq = !!process.env.GROQ_API_KEY;
|
|
56
|
+
|
|
57
|
+
console.log("\nOptional keys:");
|
|
58
|
+
console.log(
|
|
59
|
+
` ELEVENLABS_API_KEY: ${hasElevenLabs ? "found" : "not found (music/voice unavailable)"}`,
|
|
60
|
+
);
|
|
61
|
+
console.log(
|
|
62
|
+
` REPLICATE_API_TOKEN: ${hasReplicate ? "found" : "not found (lipsync unavailable)"}`,
|
|
63
|
+
);
|
|
64
|
+
console.log(
|
|
65
|
+
` GROQ_API_KEY: ${hasGroq ? "found" : "not found (transcription unavailable)"}`,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
console.log("\n--- Running verification test ---\n");
|
|
69
|
+
console.log("Generating a simple 3-second animation...");
|
|
70
|
+
console.log("This may take 30-60 seconds on first run.\n");
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const buffer = await render(quickstartVideo, {
|
|
74
|
+
output: "output/quickstart-test.mp4",
|
|
75
|
+
cache: ".cache/ai",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
console.log("\n=== SUCCESS ===");
|
|
79
|
+
console.log(
|
|
80
|
+
`Output: output/quickstart-test.mp4 (${(buffer.byteLength / 1024 / 1024).toFixed(2)} MB)`,
|
|
81
|
+
);
|
|
82
|
+
console.log("\nYour setup is working! You can now:");
|
|
83
|
+
console.log("1. Try the templates in .claude/skills/video-generation.md");
|
|
84
|
+
console.log(
|
|
85
|
+
"2. Run existing examples: bun run src/react/examples/madi.tsx",
|
|
86
|
+
);
|
|
87
|
+
console.log("3. Create your own videos using the React engine");
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error("\n=== VERIFICATION FAILED ===");
|
|
90
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
91
|
+
console.error("\nCommon fixes:");
|
|
92
|
+
console.error("- Check FAL_API_KEY is correct (no extra spaces)");
|
|
93
|
+
console.error("- Ensure you have credits at fal.ai");
|
|
94
|
+
console.error("- Try running: bun install");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (import.meta.main) {
|
|
100
|
+
main();
|
|
101
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qwen Image Edit 2511 Multiple Angles Test
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates camera angle adjustment using Qwen model.
|
|
5
|
+
* Generates the same scene from different perspectives (azimuth/elevation).
|
|
6
|
+
*
|
|
7
|
+
* Run with: bun run src/cli/index.ts render src/react/examples/qwen-angles-test.tsx
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { fal } from "../../ai-sdk/providers/fal";
|
|
11
|
+
import { Clip, Image, Render } from "..";
|
|
12
|
+
|
|
13
|
+
// Source image - replace with your own image URL or local path
|
|
14
|
+
const sourceImage =
|
|
15
|
+
"https://v3b.fal.media/files/b/0a8973cb/qUbVwDCcMlvX4drBGYB1H.png";
|
|
16
|
+
|
|
17
|
+
export default (
|
|
18
|
+
<Render width={1024} height={1024}>
|
|
19
|
+
{/* Original image - front view */}
|
|
20
|
+
<Clip duration={2}>
|
|
21
|
+
<Image src={sourceImage} resize="cover" />
|
|
22
|
+
</Clip>
|
|
23
|
+
|
|
24
|
+
{/* Right side view (90 degrees horizontal) */}
|
|
25
|
+
<Clip duration={2}>
|
|
26
|
+
<Image
|
|
27
|
+
prompt={{ text: "", images: [sourceImage] }}
|
|
28
|
+
model={fal.imageModel("qwen-angles")}
|
|
29
|
+
aspectRatio="1:1"
|
|
30
|
+
providerOptions={{
|
|
31
|
+
fal: {
|
|
32
|
+
horizontal_angle: 90,
|
|
33
|
+
vertical_angle: 0,
|
|
34
|
+
zoom: 5,
|
|
35
|
+
},
|
|
36
|
+
}}
|
|
37
|
+
/>
|
|
38
|
+
</Clip>
|
|
39
|
+
|
|
40
|
+
{/* Bird's eye view (60 degrees vertical) */}
|
|
41
|
+
<Clip duration={2}>
|
|
42
|
+
<Image
|
|
43
|
+
prompt={{ text: "", images: [sourceImage] }}
|
|
44
|
+
model={fal.imageModel("qwen-angles")}
|
|
45
|
+
aspectRatio="1:1"
|
|
46
|
+
providerOptions={{
|
|
47
|
+
fal: {
|
|
48
|
+
horizontal_angle: 0,
|
|
49
|
+
vertical_angle: 60,
|
|
50
|
+
zoom: 5,
|
|
51
|
+
},
|
|
52
|
+
}}
|
|
53
|
+
/>
|
|
54
|
+
</Clip>
|
|
55
|
+
|
|
56
|
+
{/* Close-up from 45 degree angle */}
|
|
57
|
+
<Clip duration={2}>
|
|
58
|
+
<Image
|
|
59
|
+
prompt={{ text: "", images: [sourceImage] }}
|
|
60
|
+
model={fal.imageModel("qwen-angles")}
|
|
61
|
+
aspectRatio="1:1"
|
|
62
|
+
providerOptions={{
|
|
63
|
+
fal: {
|
|
64
|
+
horizontal_angle: 45,
|
|
65
|
+
vertical_angle: 30,
|
|
66
|
+
zoom: 8,
|
|
67
|
+
},
|
|
68
|
+
}}
|
|
69
|
+
/>
|
|
70
|
+
</Clip>
|
|
71
|
+
</Render>
|
|
72
|
+
);
|
package/src/react/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
export type { CacheStorage } from "../ai-sdk/cache";
|
|
1
2
|
export type { SizeValue } from "../ai-sdk/providers/editly/types";
|
|
3
|
+
export { assets } from "./assets";
|
|
2
4
|
export {
|
|
3
|
-
Animate,
|
|
4
5
|
Captions,
|
|
5
6
|
Clip,
|
|
6
7
|
Image,
|
|
@@ -17,10 +18,9 @@ export {
|
|
|
17
18
|
Title,
|
|
18
19
|
Video,
|
|
19
20
|
} from "./elements";
|
|
20
|
-
export { Grid, SplitLayout } from "./layouts";
|
|
21
|
+
export { Grid, Slot, SplitLayout } from "./layouts";
|
|
21
22
|
export { render, renderStream } from "./render";
|
|
22
23
|
export type {
|
|
23
|
-
AnimateProps,
|
|
24
24
|
CaptionsProps,
|
|
25
25
|
ClipProps,
|
|
26
26
|
ImageProps,
|
|
@@ -21,7 +21,7 @@ export const Grid = ({
|
|
|
21
21
|
top: `${(Math.floor(i / cols) / rowCount) * 100}%`,
|
|
22
22
|
width: `${(1 / cols) * 100}%`,
|
|
23
23
|
height: `${(1 / rowCount) * 100}%`,
|
|
24
|
-
resize,
|
|
24
|
+
resize: (el.props as Record<string, unknown>).resize ?? resize,
|
|
25
25
|
},
|
|
26
26
|
}));
|
|
27
27
|
return <>{positioned}</>;
|