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.
Files changed (69) hide show
  1. package/README.md +483 -61
  2. package/launch-videos/06-kawaii-fruits.tsx +93 -0
  3. package/launch-videos/07-ugc-weight-loss.tsx +132 -0
  4. package/launch-videos/08-talking-head-varg.tsx +107 -0
  5. package/launch-videos/09-girl.tsx +160 -0
  6. package/launch-videos/README.md +42 -0
  7. package/package.json +8 -4
  8. package/skills/varg-video-generation/SKILL.md +213 -0
  9. package/skills/varg-video-generation/references/templates.md +380 -0
  10. package/skills/varg-video-generation/scripts/setup.ts +265 -0
  11. package/src/ai-sdk/cache.ts +1 -1
  12. package/src/ai-sdk/middleware/wrap-image-model.ts +4 -21
  13. package/src/ai-sdk/middleware/wrap-music-model.ts +4 -16
  14. package/src/ai-sdk/middleware/wrap-video-model.ts +5 -17
  15. package/src/ai-sdk/providers/editly/index.ts +110 -53
  16. package/src/ai-sdk/providers/editly/types.ts +2 -0
  17. package/src/ai-sdk/providers/elevenlabs.ts +10 -2
  18. package/src/ai-sdk/providers/fal.ts +6 -1
  19. package/src/cli/commands/find.tsx +1 -0
  20. package/src/cli/commands/hello.ts +85 -0
  21. package/src/cli/commands/help.tsx +18 -30
  22. package/src/cli/commands/index.ts +9 -1
  23. package/src/cli/commands/init.tsx +412 -0
  24. package/src/cli/commands/list.tsx +1 -0
  25. package/src/cli/commands/render.tsx +292 -80
  26. package/src/cli/commands/run.tsx +1 -0
  27. package/src/cli/commands/studio.ts +47 -0
  28. package/src/cli/commands/which.tsx +1 -0
  29. package/src/cli/index.ts +20 -5
  30. package/src/cli/ui/components/Badge.tsx +1 -0
  31. package/src/cli/ui/components/DataTable.tsx +1 -0
  32. package/src/cli/ui/components/Header.tsx +1 -0
  33. package/src/cli/ui/components/HelpBlock.tsx +1 -0
  34. package/src/cli/ui/components/KeyValue.tsx +1 -0
  35. package/src/cli/ui/components/OptionRow.tsx +1 -0
  36. package/src/cli/ui/components/Separator.tsx +1 -0
  37. package/src/cli/ui/components/StatusBox.tsx +1 -0
  38. package/src/cli/ui/components/VargBox.tsx +1 -0
  39. package/src/cli/ui/components/VargProgress.tsx +1 -0
  40. package/src/cli/ui/components/VargSpinner.tsx +1 -0
  41. package/src/cli/ui/components/VargText.tsx +1 -0
  42. package/src/react/assets.ts +9 -0
  43. package/src/react/elements.ts +0 -5
  44. package/src/react/examples/branching.tsx +6 -4
  45. package/src/react/examples/character-video.tsx +13 -10
  46. package/src/react/examples/madi.tsx +13 -10
  47. package/src/react/examples/mcmeows.tsx +40 -0
  48. package/src/react/examples/music-defaults.tsx +24 -0
  49. package/src/react/examples/quickstart-test.tsx +97 -0
  50. package/src/react/index.ts +1 -2
  51. package/src/react/react.test.ts +10 -10
  52. package/src/react/renderers/clip.ts +13 -24
  53. package/src/react/renderers/context.ts +3 -0
  54. package/src/react/renderers/image.ts +4 -2
  55. package/src/react/renderers/index.ts +0 -1
  56. package/src/react/renderers/music.ts +3 -3
  57. package/src/react/renderers/progress.ts +1 -3
  58. package/src/react/renderers/render.ts +49 -63
  59. package/src/react/renderers/speech.ts +2 -2
  60. package/src/react/renderers/video.ts +46 -9
  61. package/src/react/types.ts +18 -14
  62. package/src/studio/stages.ts +4 -24
  63. package/src/studio/step-renderer.ts +0 -15
  64. package/test-sync-v2.ts +30 -0
  65. package/test-sync-v2.tsx +29 -0
  66. package/tsconfig.json +5 -3
  67. package/video.tsx +7 -0
  68. package/src/react/cli.ts +0 -52
  69. package/src/react/renderers/animate.ts +0 -59
@@ -9,6 +9,7 @@ import type {
9
9
  import type { RenderContext } from "./context";
10
10
  import { renderImage } from "./image";
11
11
  import { addTask, completeTask, startTask } from "./progress";
12
+ import { renderSpeech } from "./speech";
12
13
  import { computeCacheKey, toFileUrl } from "./utils";
13
14
 
14
15
  async function resolveImageInput(
@@ -27,13 +28,46 @@ async function resolveImageInput(
27
28
  return new Uint8Array(await response.arrayBuffer());
28
29
  }
29
30
 
30
- async function resolveMediaInput(
31
- input: Uint8Array | string | undefined,
31
+ async function resolveAudioInput(
32
+ input: Uint8Array | string | VargElement<"speech"> | undefined,
33
+ ctx: RenderContext,
32
34
  ): Promise<Uint8Array | undefined> {
33
35
  if (!input) return undefined;
34
36
  if (input instanceof Uint8Array) return input;
35
- const response = await fetch(toFileUrl(input));
36
- return new Uint8Array(await response.arrayBuffer());
37
+ if (typeof input === "string") {
38
+ const response = await fetch(toFileUrl(input));
39
+ return new Uint8Array(await response.arrayBuffer());
40
+ }
41
+ // It's a Speech element - render it first
42
+ if (input.type === "speech") {
43
+ const { path } = await renderSpeech(input, ctx);
44
+ const response = await fetch(toFileUrl(path));
45
+ return new Uint8Array(await response.arrayBuffer());
46
+ }
47
+ throw new Error(
48
+ `Unsupported audio input type: ${(input as VargElement).type}`,
49
+ );
50
+ }
51
+
52
+ async function resolveVideoInput(
53
+ input: Uint8Array | string | VargElement<"video"> | undefined,
54
+ ctx: RenderContext,
55
+ ): Promise<Uint8Array | undefined> {
56
+ if (!input) return undefined;
57
+ if (input instanceof Uint8Array) return input;
58
+ if (typeof input === "string") {
59
+ const response = await fetch(toFileUrl(input));
60
+ return new Uint8Array(await response.arrayBuffer());
61
+ }
62
+ // It's a Video element - render it first
63
+ if (input.type === "video") {
64
+ const path = await renderVideo(input, ctx);
65
+ const response = await fetch(toFileUrl(path));
66
+ return new Uint8Array(await response.arrayBuffer());
67
+ }
68
+ throw new Error(
69
+ `Unsupported video input type: ${(input as VargElement).type}`,
70
+ );
37
71
  }
38
72
 
39
73
  async function resolvePrompt(
@@ -55,8 +89,8 @@ async function resolvePrompt(
55
89
  prompt.images
56
90
  ? Promise.all(prompt.images.map((img) => resolveImageInput(img, ctx)))
57
91
  : undefined,
58
- resolveMediaInput(prompt.audio),
59
- resolveMediaInput(prompt.video),
92
+ resolveAudioInput(prompt.audio, ctx),
93
+ resolveVideoInput(prompt.video, ctx),
60
94
  ]);
61
95
  return {
62
96
  text: prompt.text,
@@ -81,9 +115,11 @@ export async function renderVideo(
81
115
  throw new Error("Video element requires either 'prompt' or 'src'");
82
116
  }
83
117
 
84
- const model = props.model;
118
+ const model = props.model ?? ctx.defaults?.video;
85
119
  if (!model) {
86
- throw new Error("Video element requires 'model' prop when using prompt");
120
+ throw new Error(
121
+ "Video element requires 'model' prop (or set defaults.video in render options)",
122
+ );
87
123
  }
88
124
 
89
125
  // Compute cache key for deduplication
@@ -109,7 +145,8 @@ export async function renderVideo(
109
145
  const { video } = await ctx.generateVideo({
110
146
  model,
111
147
  prompt: resolvedPrompt,
112
- duration: 5,
148
+ duration: props.duration ?? 5,
149
+ aspectRatio: props.aspectRatio,
113
150
  cacheKey,
114
151
  } as Parameters<typeof generateVideo>[0]);
115
152
 
@@ -14,7 +14,6 @@ export type VargElementType =
14
14
  | "overlay"
15
15
  | "image"
16
16
  | "video"
17
- | "animate"
18
17
  | "speech"
19
18
  | "talking-head"
20
19
  | "title"
@@ -69,12 +68,17 @@ export interface RenderProps extends BaseProps {
69
68
  height?: number;
70
69
  fps?: number;
71
70
  normalize?: boolean;
71
+ shortest?: boolean;
72
72
  children?: VargNode;
73
73
  }
74
74
 
75
75
  export interface ClipProps extends BaseProps {
76
76
  duration?: number | "auto";
77
77
  transition?: TransitionOptions;
78
+ /** Start trim point in seconds (e.g., 1 to start from 1 second) */
79
+ cutFrom?: number;
80
+ /** End trim point in seconds (e.g., 3 to end at 3 seconds) */
81
+ cutTo?: number;
78
82
  children?: VargNode;
79
83
  }
80
84
 
@@ -102,8 +106,8 @@ export type VideoPrompt =
102
106
  | {
103
107
  text?: string;
104
108
  images?: ImageInput[];
105
- audio?: Uint8Array | string;
106
- video?: Uint8Array | string;
109
+ audio?: Uint8Array | string | VargElement<"speech">;
110
+ video?: Uint8Array | string | VargElement<"video">;
107
111
  };
108
112
 
109
113
  export type VideoProps = BaseProps &
@@ -114,17 +118,9 @@ export type VideoProps = BaseProps &
114
118
  src?: string;
115
119
  model?: VideoModelV3;
116
120
  resize?: ResizeMode;
121
+ aspectRatio?: `${number}:${number}`;
117
122
  };
118
123
 
119
- // Image-to-video animation
120
- export interface AnimateProps extends BaseProps, PositionProps {
121
- image?: VargElement<"image">;
122
- src?: string;
123
- model?: VideoModelV3;
124
- motion?: string;
125
- duration?: number;
126
- }
127
-
128
124
  export interface SpeechProps extends BaseProps, VolumeProps {
129
125
  voice?: string;
130
126
  model?: SpeechModelV3;
@@ -206,13 +202,22 @@ export interface PackshotProps extends BaseProps {
206
202
  duration?: number;
207
203
  }
208
204
 
209
- export type RenderMode = "strict" | "default" | "preview";
205
+ export type RenderMode = "strict" | "preview";
206
+
207
+ export interface DefaultModels {
208
+ image?: ImageModelV3;
209
+ video?: VideoModelV3;
210
+ speech?: SpeechModelV3;
211
+ music?: MusicModelV3;
212
+ }
210
213
 
211
214
  export interface RenderOptions {
212
215
  output?: string;
213
216
  cache?: string;
214
217
  quiet?: boolean;
218
+ verbose?: boolean;
215
219
  mode?: RenderMode;
220
+ defaults?: DefaultModels;
216
221
  }
217
222
 
218
223
  export interface ElementPropsMap {
@@ -221,7 +226,6 @@ export interface ElementPropsMap {
221
226
  overlay: OverlayProps;
222
227
  image: ImageProps;
223
228
  video: VideoProps;
224
- animate: AnimateProps;
225
229
  speech: SpeechProps;
226
230
  "talking-head": TalkingHeadProps;
227
231
  title: TitleProps;
@@ -1,6 +1,6 @@
1
1
  import type { VargElement, VargNode } from "../react/types";
2
2
 
3
- export type StageType = "image" | "video" | "animate" | "speech" | "music";
3
+ export type StageType = "image" | "video" | "speech" | "music";
4
4
 
5
5
  export interface RenderStage {
6
6
  id: string;
@@ -70,11 +70,6 @@ export function extractStages(element: VargElement): ExtractedStages {
70
70
  return "video";
71
71
  }
72
72
 
73
- if (type === "animate") {
74
- const motion = props.motion;
75
- return motion ? `animate: ${motion}` : "animate";
76
- }
77
-
78
73
  if (type === "speech") {
79
74
  const text = getTextContent(element.children);
80
75
  return `speech: ${text.slice(0, 30)}${text.length > 30 ? "..." : ""}`;
@@ -120,13 +115,7 @@ export function extractStages(element: VargElement): ExtractedStages {
120
115
  const collectedDeps: string[] = [...parentDeps];
121
116
 
122
117
  // Check if this is a renderable stage
123
- const stageTypes: StageType[] = [
124
- "image",
125
- "video",
126
- "animate",
127
- "speech",
128
- "music",
129
- ];
118
+ const stageTypes: StageType[] = ["image", "video", "speech", "music"];
130
119
 
131
120
  if (stageTypes.includes(element.type as StageType)) {
132
121
  const stageType = element.type as StageType;
@@ -137,10 +126,10 @@ export function extractStages(element: VargElement): ExtractedStages {
137
126
  return [];
138
127
  }
139
128
 
140
- // For video/animate with image inputs, we need to find dependent images first
129
+ // For video with image inputs, we need to find dependent images first
141
130
  const imageDeps: string[] = [];
142
131
 
143
- if (stageType === "video" || stageType === "animate") {
132
+ if (stageType === "video") {
144
133
  // Check prompt.images for nested Image elements
145
134
  const prompt = props.prompt as { images?: VargNode[] } | undefined;
146
135
  if (prompt?.images) {
@@ -158,15 +147,6 @@ export function extractStages(element: VargElement): ExtractedStages {
158
147
  }
159
148
  }
160
149
  }
161
-
162
- // Check for image prop in animate
163
- if (stageType === "animate" && props.image) {
164
- const imgElement = props.image as VargElement;
165
- if (imgElement.type === "image") {
166
- const deps = walkTree(imgElement, currentPath, collectedDeps);
167
- imageDeps.push(...deps);
168
- }
169
- }
170
150
  }
171
151
 
172
152
  const id = generateId();
@@ -2,7 +2,6 @@ import { generateImage } from "ai";
2
2
  import { withCache } from "../ai-sdk/cache";
3
3
  import { fileCache } from "../ai-sdk/file-cache";
4
4
  import { generateVideo } from "../ai-sdk/generate-video";
5
- import { renderAnimate } from "../react/renderers/animate";
6
5
  import type { RenderContext } from "../react/renderers/context";
7
6
  import { renderImage } from "../react/renderers/image";
8
7
  import { renderMusic } from "../react/renderers/music";
@@ -124,20 +123,6 @@ export async function executeStage(
124
123
  break;
125
124
  }
126
125
 
127
- case "animate": {
128
- const path = await renderAnimate(
129
- stage.element as VargElement<"animate">,
130
- session.ctx,
131
- );
132
- result = {
133
- type: "video",
134
- path,
135
- previewUrl: `/api/step/preview/${session.id}/${stageId}`,
136
- mimeType: "video/mp4",
137
- };
138
- break;
139
- }
140
-
141
126
  case "speech": {
142
127
  const speechResult = await renderSpeech(
143
128
  stage.element as VargElement<"speech">,
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Quick sync-v2 lipsync test
3
+ * Required env: FAL_API_KEY
4
+ */
5
+
6
+ import { File, fal, generateVideo } from "./src/ai-sdk/index";
7
+
8
+ async function main() {
9
+ const videoPath = "output/extracted-videos/tyler/tyler-10.mp4";
10
+ const audioPath = "output/extracted-videos/tyler/tyler-10.mp4"; // use same video's audio for now
11
+
12
+ console.log("loading media files...");
13
+ const videoFile = File.fromPath(videoPath);
14
+ const audioFile = File.fromPath(audioPath);
15
+
16
+ console.log("lipsyncing with sync-v2...");
17
+ const { video } = await generateVideo({
18
+ model: fal.videoModel("sync-v2"),
19
+ prompt: {
20
+ video: await videoFile.data(),
21
+ audio: await audioFile.data(),
22
+ },
23
+ });
24
+
25
+ console.log(`lipsynced video: ${video.uint8Array.byteLength} bytes`);
26
+ await Bun.write("output/test-sync-v2.mp4", video.uint8Array);
27
+ console.log("done! saved to output/test-sync-v2.mp4");
28
+ }
29
+
30
+ main().catch(console.error);
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Simple sync-v2 lipsync test in React format
3
+ * Takes an existing video + audio and lipsyncs them together
4
+ *
5
+ * Run: bunx vargai render test-sync-v2.tsx
6
+ */
7
+ import { fal } from "vargai/ai";
8
+ import { Clip, Render, Video } from "vargai/react";
9
+
10
+ // Source video (existing talking head video)
11
+ const SOURCE_VIDEO = "output/extracted-videos/tyler/tyler-10.mp4";
12
+
13
+ // Source audio (pre-generated speech)
14
+ const SOURCE_AUDIO = "output/test-speech.mp3";
15
+
16
+ export default (
17
+ <Render width={1080} height={1920}>
18
+ <Clip duration={10}>
19
+ {/* Lipsync: video + audio -> sync-v2 */}
20
+ <Video
21
+ prompt={{
22
+ video: SOURCE_VIDEO,
23
+ audio: SOURCE_AUDIO,
24
+ }}
25
+ model={fal.videoModel("sync-v2")}
26
+ />
27
+ </Clip>
28
+ </Render>
29
+ );
package/tsconfig.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "module": "ESNext",
7
7
  "moduleDetection": "force",
8
8
  "jsx": "react-jsx",
9
- "jsxImportSource": "@/react/runtime",
9
+ "jsxImportSource": "vargai",
10
10
  "allowJs": true,
11
11
 
12
12
  // Bundler mode
@@ -30,10 +30,12 @@
30
30
  // Base URL for imports
31
31
  "baseUrl": ".",
32
32
  "paths": {
33
- "@/*": ["./src/*"]
33
+ "@/*": ["./src/*"],
34
+ "vargai/jsx-runtime": ["./src/react/runtime/jsx-runtime.ts"],
35
+ "vargai/jsx-dev-runtime": ["./src/react/runtime/jsx-dev-runtime.ts"]
34
36
  }
35
37
  },
36
- "include": ["src/**/*"],
38
+ "include": ["src/**/*", "launch-videos/**/*"],
37
39
  "exclude": [
38
40
  "node_modules",
39
41
  "action",
package/video.tsx ADDED
@@ -0,0 +1,7 @@
1
+ export default (
2
+ <Render>
3
+ <Clip>
4
+ <Video prompt="a cat coding" model={fal.videoModel("wan-2.5")} />
5
+ </Clip>
6
+ </Render>
7
+ );
package/src/react/cli.ts DELETED
@@ -1,52 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- import { parseArgs } from "node:util";
4
- import { render } from "./render";
5
- import type { VargElement } from "./types";
6
-
7
- const { values, positionals } = parseArgs({
8
- args: Bun.argv.slice(2),
9
- options: {
10
- output: { type: "string", short: "o" },
11
- cache: { type: "string", short: "c", default: ".cache/ai" },
12
- quiet: { type: "boolean", short: "q", default: false },
13
- },
14
- allowPositionals: true,
15
- });
16
-
17
- const [file] = positionals;
18
-
19
- if (!file) {
20
- console.error("usage: bun react/cli.ts <component.tsx> [-o output.mp4]");
21
- process.exit(1);
22
- }
23
-
24
- const resolvedPath = Bun.resolveSync(file, process.cwd());
25
- const mod = await import(resolvedPath);
26
- const component: VargElement = mod.default;
27
-
28
- if (!component || component.type !== "render") {
29
- console.error("error: default export must be a <Render> element");
30
- process.exit(1);
31
- }
32
-
33
- const outputPath =
34
- values.output ??
35
- `output/${file
36
- .replace(/\.tsx?$/, "")
37
- .split("/")
38
- .pop()}.mp4`;
39
-
40
- if (!values.quiet) {
41
- console.log(`rendering ${file} → ${outputPath}`);
42
- }
43
-
44
- const buffer = await render(component, {
45
- output: outputPath,
46
- cache: values.cache,
47
- quiet: values.quiet,
48
- });
49
-
50
- if (!values.quiet) {
51
- console.log(`done! ${buffer.byteLength} bytes → ${outputPath}`);
52
- }
@@ -1,59 +0,0 @@
1
- import { File } from "../../ai-sdk/file";
2
- import type { generateVideo } from "../../ai-sdk/generate-video";
3
- import type { AnimateProps, VargElement } from "../types";
4
- import type { RenderContext } from "./context";
5
- import { renderImage } from "./image";
6
- import { addTask, completeTask, startTask } from "./progress";
7
- import { computeCacheKey, resolvePath } from "./utils";
8
-
9
- export async function renderAnimate(
10
- element: VargElement<"animate">,
11
- ctx: RenderContext,
12
- ): Promise<string> {
13
- const props = element.props as AnimateProps;
14
-
15
- let imagePath: string;
16
- if (props.src) {
17
- imagePath = props.src;
18
- } else if (props.image) {
19
- if (props.image.type !== "image") {
20
- throw new Error(
21
- `Animate 'image' prop must be an <Image /> element, got <${props.image.type} />`,
22
- );
23
- }
24
- imagePath = await renderImage(props.image as VargElement<"image">, ctx);
25
- } else {
26
- throw new Error("Animate element requires either 'src' or 'image' prop");
27
- }
28
-
29
- const model = props.model;
30
- if (!model) {
31
- throw new Error("Animate element requires 'model' prop");
32
- }
33
-
34
- const imageData = await Bun.file(resolvePath(imagePath)).arrayBuffer();
35
- const cacheKey = computeCacheKey(element);
36
-
37
- const modelId = typeof model === "string" ? model : model.modelId;
38
- const taskId = ctx.progress
39
- ? addTask(ctx.progress, "animate", modelId)
40
- : null;
41
- if (taskId && ctx.progress) startTask(ctx.progress, taskId);
42
-
43
- const { video } = await ctx.generateVideo({
44
- model,
45
- prompt: {
46
- text: props.motion ?? "",
47
- images: [new Uint8Array(imageData)],
48
- },
49
- duration: props.duration ?? 5,
50
- cacheKey,
51
- } as Parameters<typeof generateVideo>[0]);
52
-
53
- if (taskId && ctx.progress) completeTask(ctx.progress, taskId);
54
-
55
- const tempPath = await File.toTemp(video);
56
- ctx.tempFiles.push(tempPath);
57
-
58
- return tempPath;
59
- }