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/studio/ui/index.html
CHANGED
|
@@ -763,8 +763,8 @@
|
|
|
763
763
|
<script src="https://unpkg.com/drawflow@0.0.60/dist/drawflow.min.js"></script>
|
|
764
764
|
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js"></script>
|
|
765
765
|
<script>
|
|
766
|
-
const DEFAULT_CODE = `import { fal } from "
|
|
767
|
-
import { Clip, Image, Render, Video } from "
|
|
766
|
+
const DEFAULT_CODE = `import { fal } from "vargai/ai";
|
|
767
|
+
import { Clip, Image, Render, Video } from "vargai/react";
|
|
768
768
|
|
|
769
769
|
export default (
|
|
770
770
|
<Render width={1080} height={1920}>
|
package/src/tests/all.test.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Run with: bun run src/tests/all.test.ts
|
|
6
6
|
*
|
|
7
7
|
* Note: Most tests require API keys to be set in environment variables:
|
|
8
|
-
* - FAL_KEY
|
|
8
|
+
* - FAL_API_KEY (or FAL_KEY)
|
|
9
9
|
* - REPLICATE_API_TOKEN
|
|
10
10
|
* - ELEVENLABS_API_KEY
|
|
11
11
|
* - GROQ_API_KEY
|
|
@@ -318,7 +318,7 @@ await test(
|
|
|
318
318
|
}
|
|
319
319
|
console.log(` Generated: ${result.data.images[0].url}`);
|
|
320
320
|
},
|
|
321
|
-
!hasApiKey("FAL_KEY"),
|
|
321
|
+
!hasApiKey(["FAL_API_KEY", "FAL_KEY"]),
|
|
322
322
|
);
|
|
323
323
|
|
|
324
324
|
await test(
|
|
@@ -334,7 +334,7 @@ await test(
|
|
|
334
334
|
}
|
|
335
335
|
console.log(` Generated: ${result.data.video.url}`);
|
|
336
336
|
},
|
|
337
|
-
!hasApiKey("FAL_KEY"),
|
|
337
|
+
!hasApiKey(["FAL_API_KEY", "FAL_KEY"]),
|
|
338
338
|
);
|
|
339
339
|
|
|
340
340
|
// Replicate tests
|
|
@@ -455,7 +455,7 @@ await test(
|
|
|
455
455
|
}
|
|
456
456
|
console.log(` Output: ${JSON.stringify(result.output).slice(0, 100)}...`);
|
|
457
457
|
},
|
|
458
|
-
!hasApiKey("FAL_KEY"),
|
|
458
|
+
!hasApiKey(["FAL_API_KEY", "FAL_KEY"]),
|
|
459
459
|
);
|
|
460
460
|
|
|
461
461
|
await test(
|
package/src/tests/index.ts
CHANGED
|
@@ -20,7 +20,7 @@ Available test files:
|
|
|
20
20
|
bun run src/tests/all.test.ts
|
|
21
21
|
Comprehensive tests including live API calls.
|
|
22
22
|
Requires API keys set in environment variables:
|
|
23
|
-
- FAL_KEY
|
|
23
|
+
- FAL_API_KEY (or FAL_KEY)
|
|
24
24
|
- REPLICATE_API_TOKEN
|
|
25
25
|
- ELEVENLABS_API_KEY
|
|
26
26
|
- GROQ_API_KEY
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Clip, Render, render, Video } from "./src/react";
|
|
2
|
+
import { Grid, Slot } from "./src/react/layouts";
|
|
3
|
+
|
|
4
|
+
const video1 = <Video src="media/fitness-demo.mp4" />;
|
|
5
|
+
const video2 = <Video src="media/kangaroo-scene.mp4" />;
|
|
6
|
+
|
|
7
|
+
await render(
|
|
8
|
+
<Render width={1080} height={1920}>
|
|
9
|
+
<Clip duration={3}>
|
|
10
|
+
<Grid columns={1} rows={2}>
|
|
11
|
+
<Slot class="fit-cover pos-top">{video1}</Slot>
|
|
12
|
+
<Slot class="fit-cover pos-bottom">{video2}</Slot>
|
|
13
|
+
</Grid>
|
|
14
|
+
</Clip>
|
|
15
|
+
</Render>,
|
|
16
|
+
{ output: "output/test-slot-grid.mp4" },
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
console.log("done: output/test-slot-grid.mp4");
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Clip, Render, render, Video } from "./src/react";
|
|
2
|
+
import { Slot, Split } from "./src/react/layouts";
|
|
3
|
+
|
|
4
|
+
const video1 = <Video src="media/fitness-demo.mp4" />;
|
|
5
|
+
const video2 = <Video src="media/kangaroo-scene.mp4" />;
|
|
6
|
+
|
|
7
|
+
const positions = [
|
|
8
|
+
["pos-top", "pos-top"],
|
|
9
|
+
["pos-bottom", "pos-bottom"],
|
|
10
|
+
["pos-center", "pos-center"],
|
|
11
|
+
["pos-top", "pos-bottom"],
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
for (const [pos1, pos2] of positions) {
|
|
15
|
+
const outFile = `output/test-slot-userland-${pos1}-${pos2}.mp4`;
|
|
16
|
+
|
|
17
|
+
await render(
|
|
18
|
+
<Render width={1080} height={1920}>
|
|
19
|
+
<Clip duration={3}>
|
|
20
|
+
<Split direction="vertical">
|
|
21
|
+
<Slot class={`fit-cover ${pos1}`}>{video1}</Slot>
|
|
22
|
+
<Slot class={`fit-cover ${pos2}`}>{video2}</Slot>
|
|
23
|
+
</Split>
|
|
24
|
+
</Clip>
|
|
25
|
+
</Render>,
|
|
26
|
+
{ output: outFile },
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
console.log(`done: ${outFile}`);
|
|
30
|
+
}
|
package/test-sync-v2.ts
ADDED
|
@@ -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);
|
package/test-sync-v2.tsx
ADDED
|
@@ -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
package/video.tsx
ADDED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { $ } from "bun";
|
|
2
|
-
import type { VideoInfo } from "./types";
|
|
3
|
-
|
|
4
|
-
const FFMPEG_COMMON_ARGS = ["-hide_banner", "-loglevel", "error"];
|
|
5
|
-
|
|
6
|
-
export async function ffmpeg(
|
|
7
|
-
args: string[],
|
|
8
|
-
options?: { stdin?: "pipe" | "ignore"; stdout?: "pipe" | "inherit" },
|
|
9
|
-
): Promise<{ stdout: Buffer; exitCode: number }> {
|
|
10
|
-
const proc = Bun.spawn(["ffmpeg", ...FFMPEG_COMMON_ARGS, ...args], {
|
|
11
|
-
stdin: options?.stdin ?? "ignore",
|
|
12
|
-
stdout: options?.stdout === "inherit" ? "inherit" : "pipe",
|
|
13
|
-
stderr: "inherit",
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const stdout =
|
|
17
|
-
options?.stdout === "inherit"
|
|
18
|
-
? Buffer.alloc(0)
|
|
19
|
-
: Buffer.from(await new Response(proc.stdout).arrayBuffer());
|
|
20
|
-
const exitCode = await proc.exited;
|
|
21
|
-
|
|
22
|
-
return { stdout, exitCode };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function ffprobe(path: string): Promise<VideoInfo> {
|
|
26
|
-
const result =
|
|
27
|
-
await $`ffprobe -v error -show_entries stream=width,height,r_frame_rate,codec_type -show_entries format=duration -of json ${path}`.json();
|
|
28
|
-
|
|
29
|
-
const videoStream = result.streams?.find(
|
|
30
|
-
(s: { codec_type: string }) => s.codec_type === "video",
|
|
31
|
-
);
|
|
32
|
-
const duration = parseFloat(result.format?.duration ?? "0");
|
|
33
|
-
|
|
34
|
-
let fps: number | undefined;
|
|
35
|
-
const framerateStr: string | undefined = videoStream?.r_frame_rate;
|
|
36
|
-
if (framerateStr) {
|
|
37
|
-
const parts = framerateStr.split("/").map(Number);
|
|
38
|
-
const num = parts[0];
|
|
39
|
-
const den = parts[1];
|
|
40
|
-
if (den && den > 0 && num) fps = num / den;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
duration,
|
|
45
|
-
width: videoStream?.width,
|
|
46
|
-
height: videoStream?.height,
|
|
47
|
-
fps,
|
|
48
|
-
framerateStr,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function readDuration(path: string): Promise<number> {
|
|
53
|
-
const result =
|
|
54
|
-
await $`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 ${path}`.text();
|
|
55
|
-
return parseFloat(result.trim());
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function multipleOf2(n: number): number {
|
|
59
|
-
return Math.round(n / 2) * 2;
|
|
60
|
-
}
|
|
@@ -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
|
-
}
|
|
File without changes
|