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
|
@@ -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
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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(
|
|
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
|
|
package/src/react/types.ts
CHANGED
|
@@ -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" | "
|
|
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;
|
package/src/studio/stages.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { VargElement, VargNode } from "../react/types";
|
|
2
2
|
|
|
3
|
-
export type StageType = "image" | "video" | "
|
|
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
|
|
129
|
+
// For video with image inputs, we need to find dependent images first
|
|
141
130
|
const imageDeps: string[] = [];
|
|
142
131
|
|
|
143
|
-
if (stageType === "video"
|
|
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">,
|
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
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"module": "ESNext",
|
|
7
7
|
"moduleDetection": "force",
|
|
8
8
|
"jsx": "react-jsx",
|
|
9
|
-
"jsxImportSource": "
|
|
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
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
|
-
}
|