varg.ai-sdk 0.1.1 → 0.4.0-alpha.1

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 (246) hide show
  1. package/.claude/settings.local.json +1 -1
  2. package/.env.example +3 -0
  3. package/.github/workflows/ci.yml +23 -0
  4. package/.husky/README.md +102 -0
  5. package/.husky/commit-msg +6 -0
  6. package/.husky/pre-commit +9 -0
  7. package/.husky/pre-push +6 -0
  8. package/.size-limit.json +8 -0
  9. package/.test-hooks.ts +5 -0
  10. package/CLAUDE.md +10 -3
  11. package/CONTRIBUTING.md +150 -0
  12. package/LICENSE.md +53 -0
  13. package/README.md +56 -209
  14. package/SKILLS.md +26 -10
  15. package/biome.json +7 -1
  16. package/bun.lock +1286 -0
  17. package/commitlint.config.js +22 -0
  18. package/docs/index.html +1130 -0
  19. package/docs/prompting.md +326 -0
  20. package/docs/react.md +834 -0
  21. package/docs/sdk.md +812 -0
  22. package/ffmpeg/CLAUDE.md +68 -0
  23. package/package.json +43 -10
  24. package/pipeline/cookbooks/scripts/animate-frames-parallel.ts +84 -0
  25. package/pipeline/cookbooks/scripts/combine-scenes.sh +53 -0
  26. package/pipeline/cookbooks/scripts/generate-frames-parallel.ts +99 -0
  27. package/pipeline/cookbooks/scripts/still-to-video.sh +37 -0
  28. package/pipeline/cookbooks/text-to-tiktok.md +669 -0
  29. package/pipeline/cookbooks/trendwatching.md +156 -0
  30. package/plan.md +281 -0
  31. package/scripts/.gitkeep +0 -0
  32. package/src/ai-sdk/cache.ts +142 -0
  33. package/src/ai-sdk/examples/cached-generation.ts +53 -0
  34. package/src/ai-sdk/examples/duet-scene-4.ts +53 -0
  35. package/src/ai-sdk/examples/duet-scene-5-audio.ts +32 -0
  36. package/src/ai-sdk/examples/duet-video.ts +56 -0
  37. package/src/ai-sdk/examples/editly-composition.ts +63 -0
  38. package/src/ai-sdk/examples/editly-test.ts +57 -0
  39. package/src/ai-sdk/examples/editly-video-test.ts +52 -0
  40. package/src/ai-sdk/examples/fal-lipsync.ts +43 -0
  41. package/src/ai-sdk/examples/higgsfield-image.ts +61 -0
  42. package/src/ai-sdk/examples/music-generation.ts +19 -0
  43. package/src/ai-sdk/examples/openai-sora.ts +34 -0
  44. package/src/ai-sdk/examples/replicate-bg-removal.ts +52 -0
  45. package/src/ai-sdk/examples/simpsons-scene.ts +61 -0
  46. package/src/ai-sdk/examples/talking-lion.ts +55 -0
  47. package/src/ai-sdk/examples/video-generation.ts +39 -0
  48. package/src/ai-sdk/examples/workflow-animated-girl.ts +104 -0
  49. package/src/ai-sdk/examples/workflow-before-after.ts +114 -0
  50. package/src/ai-sdk/examples/workflow-character-grid.ts +112 -0
  51. package/src/ai-sdk/examples/workflow-slideshow.ts +161 -0
  52. package/src/ai-sdk/file-cache.ts +112 -0
  53. package/src/ai-sdk/file.ts +238 -0
  54. package/src/ai-sdk/generate-element.ts +92 -0
  55. package/src/ai-sdk/generate-music.ts +46 -0
  56. package/src/ai-sdk/generate-video.ts +165 -0
  57. package/src/ai-sdk/index.ts +72 -0
  58. package/src/ai-sdk/music-model.ts +110 -0
  59. package/src/ai-sdk/providers/editly/editly.test.ts +1108 -0
  60. package/src/ai-sdk/providers/editly/ffmpeg.ts +60 -0
  61. package/src/ai-sdk/providers/editly/index.ts +817 -0
  62. package/src/ai-sdk/providers/editly/layers.ts +776 -0
  63. package/src/ai-sdk/providers/editly/plan.md +144 -0
  64. package/src/ai-sdk/providers/editly/types.ts +328 -0
  65. package/src/ai-sdk/providers/elevenlabs-provider.ts +255 -0
  66. package/src/ai-sdk/providers/fal-provider.ts +512 -0
  67. package/src/ai-sdk/providers/higgsfield.ts +379 -0
  68. package/src/ai-sdk/providers/openai.ts +251 -0
  69. package/src/ai-sdk/providers/replicate.ts +16 -0
  70. package/src/ai-sdk/video-model.ts +185 -0
  71. package/src/cli/commands/find.tsx +137 -0
  72. package/src/cli/commands/help.tsx +85 -0
  73. package/src/cli/commands/index.ts +6 -0
  74. package/src/cli/commands/list.tsx +238 -0
  75. package/src/cli/commands/render.tsx +71 -0
  76. package/src/cli/commands/run.tsx +511 -0
  77. package/src/cli/commands/which.tsx +253 -0
  78. package/src/cli/index.ts +114 -0
  79. package/src/cli/quiet.ts +44 -0
  80. package/src/cli/types.ts +32 -0
  81. package/src/cli/ui/components/Badge.tsx +29 -0
  82. package/src/cli/ui/components/DataTable.tsx +51 -0
  83. package/src/cli/ui/components/Header.tsx +23 -0
  84. package/src/cli/ui/components/HelpBlock.tsx +44 -0
  85. package/src/cli/ui/components/KeyValue.tsx +33 -0
  86. package/src/cli/ui/components/OptionRow.tsx +81 -0
  87. package/src/cli/ui/components/Separator.tsx +23 -0
  88. package/src/cli/ui/components/StatusBox.tsx +108 -0
  89. package/src/cli/ui/components/VargBox.tsx +51 -0
  90. package/src/cli/ui/components/VargProgress.tsx +36 -0
  91. package/src/cli/ui/components/VargSpinner.tsx +34 -0
  92. package/src/cli/ui/components/VargText.tsx +56 -0
  93. package/src/cli/ui/components/index.ts +19 -0
  94. package/src/cli/ui/index.ts +12 -0
  95. package/src/cli/ui/render.ts +35 -0
  96. package/src/cli/ui/theme.ts +63 -0
  97. package/src/cli/utils.ts +78 -0
  98. package/src/core/executor/executor.ts +201 -0
  99. package/src/core/executor/index.ts +13 -0
  100. package/src/core/executor/job.ts +214 -0
  101. package/src/core/executor/pipeline.ts +222 -0
  102. package/src/core/index.ts +11 -0
  103. package/src/core/registry/index.ts +9 -0
  104. package/src/core/registry/loader.ts +149 -0
  105. package/src/core/registry/registry.ts +221 -0
  106. package/src/core/registry/resolver.ts +206 -0
  107. package/src/core/schema/helpers.ts +134 -0
  108. package/src/core/schema/index.ts +8 -0
  109. package/src/core/schema/shared.ts +102 -0
  110. package/src/core/schema/types.ts +279 -0
  111. package/src/core/schema/validator.ts +92 -0
  112. package/src/definitions/actions/captions.ts +261 -0
  113. package/src/definitions/actions/edit.ts +298 -0
  114. package/src/definitions/actions/image.ts +125 -0
  115. package/src/definitions/actions/index.ts +114 -0
  116. package/src/definitions/actions/music.ts +205 -0
  117. package/src/definitions/actions/sync.ts +128 -0
  118. package/{action/transcribe/index.ts → src/definitions/actions/transcribe.ts} +58 -68
  119. package/src/definitions/actions/upload.ts +111 -0
  120. package/src/definitions/actions/video.ts +163 -0
  121. package/src/definitions/actions/voice.ts +119 -0
  122. package/src/definitions/index.ts +23 -0
  123. package/src/definitions/models/elevenlabs.ts +50 -0
  124. package/src/definitions/models/flux.ts +56 -0
  125. package/src/definitions/models/index.ts +36 -0
  126. package/src/definitions/models/kling.ts +56 -0
  127. package/src/definitions/models/llama.ts +54 -0
  128. package/src/definitions/models/nano-banana-pro.ts +102 -0
  129. package/src/definitions/models/sonauto.ts +68 -0
  130. package/src/definitions/models/soul.ts +65 -0
  131. package/src/definitions/models/wan.ts +54 -0
  132. package/src/definitions/models/whisper.ts +44 -0
  133. package/src/definitions/skills/index.ts +12 -0
  134. package/src/definitions/skills/talking-character.ts +87 -0
  135. package/src/definitions/skills/text-to-tiktok.ts +97 -0
  136. package/src/index.ts +118 -0
  137. package/src/providers/apify.ts +269 -0
  138. package/src/providers/base.ts +264 -0
  139. package/src/providers/elevenlabs.ts +217 -0
  140. package/src/providers/fal.ts +392 -0
  141. package/src/providers/ffmpeg.ts +544 -0
  142. package/src/providers/fireworks.ts +193 -0
  143. package/src/providers/groq.ts +149 -0
  144. package/src/providers/higgsfield.ts +145 -0
  145. package/src/providers/index.ts +143 -0
  146. package/src/providers/replicate.ts +147 -0
  147. package/src/providers/storage.ts +206 -0
  148. package/src/react/cli.ts +52 -0
  149. package/src/react/elements.ts +146 -0
  150. package/src/react/examples/branching.tsx +66 -0
  151. package/src/react/examples/captions-demo.tsx +37 -0
  152. package/src/react/examples/character-video.tsx +84 -0
  153. package/src/react/examples/grid.tsx +53 -0
  154. package/src/react/examples/layouts-demo.tsx +57 -0
  155. package/src/react/examples/madi.tsx +60 -0
  156. package/src/react/examples/music-test.tsx +35 -0
  157. package/src/react/examples/onlyfans-1m/workflow.tsx +88 -0
  158. package/src/react/examples/orange-portrait.tsx +41 -0
  159. package/src/react/examples/split-element-demo.tsx +60 -0
  160. package/src/react/examples/split-layout-demo.tsx +60 -0
  161. package/src/react/examples/split.tsx +41 -0
  162. package/src/react/examples/video-grid.tsx +46 -0
  163. package/src/react/index.ts +43 -0
  164. package/src/react/layouts/grid.tsx +28 -0
  165. package/src/react/layouts/index.ts +2 -0
  166. package/src/react/layouts/split.tsx +20 -0
  167. package/src/react/react.test.ts +309 -0
  168. package/src/react/render.ts +21 -0
  169. package/src/react/renderers/animate.ts +59 -0
  170. package/src/react/renderers/captions.ts +297 -0
  171. package/src/react/renderers/clip.ts +248 -0
  172. package/src/react/renderers/context.ts +17 -0
  173. package/src/react/renderers/image.ts +109 -0
  174. package/src/react/renderers/index.ts +22 -0
  175. package/src/react/renderers/music.ts +60 -0
  176. package/src/react/renderers/packshot.ts +84 -0
  177. package/src/react/renderers/progress.ts +173 -0
  178. package/src/react/renderers/render.ts +243 -0
  179. package/src/react/renderers/slider.ts +69 -0
  180. package/src/react/renderers/speech.ts +53 -0
  181. package/src/react/renderers/split.ts +91 -0
  182. package/src/react/renderers/subtitle.ts +16 -0
  183. package/src/react/renderers/swipe.ts +75 -0
  184. package/src/react/renderers/title.ts +17 -0
  185. package/src/react/renderers/utils.ts +124 -0
  186. package/src/react/renderers/video.ts +127 -0
  187. package/src/react/runtime/jsx-dev-runtime.ts +43 -0
  188. package/src/react/runtime/jsx-runtime.ts +35 -0
  189. package/src/react/types.ts +232 -0
  190. package/src/studio/index.ts +26 -0
  191. package/src/studio/scanner.ts +102 -0
  192. package/src/studio/server.ts +554 -0
  193. package/src/studio/stages.ts +251 -0
  194. package/src/studio/step-renderer.ts +279 -0
  195. package/src/studio/types.ts +60 -0
  196. package/src/studio/ui/cache.html +303 -0
  197. package/src/studio/ui/index.html +1820 -0
  198. package/src/tests/all.test.ts +509 -0
  199. package/src/tests/index.ts +33 -0
  200. package/src/tests/unit.test.ts +403 -0
  201. package/tsconfig.cli.json +8 -0
  202. package/tsconfig.json +21 -3
  203. package/TEST_RESULTS.md +0 -122
  204. package/action/captions/SKILL.md +0 -170
  205. package/action/captions/index.ts +0 -169
  206. package/action/edit/SKILL.md +0 -235
  207. package/action/edit/index.ts +0 -437
  208. package/action/image/SKILL.md +0 -140
  209. package/action/image/index.ts +0 -105
  210. package/action/sync/SKILL.md +0 -136
  211. package/action/sync/index.ts +0 -145
  212. package/action/transcribe/SKILL.md +0 -179
  213. package/action/video/SKILL.md +0 -116
  214. package/action/video/index.ts +0 -125
  215. package/action/voice/SKILL.md +0 -125
  216. package/action/voice/index.ts +0 -136
  217. package/cli/commands/find.ts +0 -58
  218. package/cli/commands/help.ts +0 -70
  219. package/cli/commands/list.ts +0 -49
  220. package/cli/commands/run.ts +0 -237
  221. package/cli/commands/which.ts +0 -66
  222. package/cli/discover.ts +0 -66
  223. package/cli/index.ts +0 -33
  224. package/cli/runner.ts +0 -65
  225. package/cli/types.ts +0 -49
  226. package/cli/ui.ts +0 -185
  227. package/index.ts +0 -75
  228. package/lib/README.md +0 -144
  229. package/lib/ai-sdk/fal.ts +0 -106
  230. package/lib/ai-sdk/replicate.ts +0 -107
  231. package/lib/elevenlabs.ts +0 -382
  232. package/lib/fal.ts +0 -467
  233. package/lib/ffmpeg.ts +0 -467
  234. package/lib/fireworks.ts +0 -235
  235. package/lib/groq.ts +0 -246
  236. package/lib/higgsfield.ts +0 -176
  237. package/lib/remotion/SKILL.md +0 -823
  238. package/lib/remotion/cli.ts +0 -115
  239. package/lib/remotion/functions.ts +0 -283
  240. package/lib/remotion/index.ts +0 -19
  241. package/lib/remotion/templates.ts +0 -73
  242. package/lib/replicate.ts +0 -304
  243. package/output.txt +0 -1
  244. package/test-import.ts +0 -7
  245. package/test-services.ts +0 -97
  246. package/utilities/s3.ts +0 -147
@@ -0,0 +1,28 @@
1
+ import type { VargElement } from "../types";
2
+
3
+ export const Grid = ({
4
+ columns,
5
+ rows,
6
+ children,
7
+ resize = "contain",
8
+ }: {
9
+ columns?: number;
10
+ rows?: number;
11
+ children: VargElement[];
12
+ resize?: "cover" | "contain" | "stretch";
13
+ }) => {
14
+ const cols = columns ?? children.length;
15
+ const rowCount = rows ?? Math.ceil(children.length / cols);
16
+ const positioned = children.map((el, i) => ({
17
+ ...el,
18
+ props: {
19
+ ...el.props,
20
+ left: `${((i % cols) / cols) * 100}%`,
21
+ top: `${(Math.floor(i / cols) / rowCount) * 100}%`,
22
+ width: `${(1 / cols) * 100}%`,
23
+ height: `${(1 / rowCount) * 100}%`,
24
+ resize,
25
+ },
26
+ }));
27
+ return <>{positioned}</>;
28
+ };
@@ -0,0 +1,2 @@
1
+ export { Grid } from "./grid";
2
+ export { SplitLayout } from "./split";
@@ -0,0 +1,20 @@
1
+ import type { VargElement } from "../types";
2
+ import { Grid } from "./grid";
3
+
4
+ export const SplitLayout = ({
5
+ left,
6
+ right,
7
+ direction = "horizontal",
8
+ }: {
9
+ left: VargElement;
10
+ right: VargElement;
11
+ direction?: "horizontal" | "vertical";
12
+ }) => (
13
+ <Grid
14
+ columns={direction === "horizontal" ? 2 : 1}
15
+ rows={direction === "vertical" ? 2 : 1}
16
+ >
17
+ {left}
18
+ {right}
19
+ </Grid>
20
+ );
@@ -0,0 +1,309 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { existsSync, unlinkSync } from "node:fs";
3
+ import { fal } from "../ai-sdk/providers/fal-provider";
4
+ import {
5
+ Animate,
6
+ Captions,
7
+ Clip,
8
+ Image,
9
+ Packshot,
10
+ Render,
11
+ render,
12
+ Slider,
13
+ Split,
14
+ Swipe,
15
+ Title,
16
+ Video,
17
+ } from "./index";
18
+
19
+ describe("varg-react elements", () => {
20
+ test("Render creates correct element structure", () => {
21
+ const element = Render({
22
+ width: 1280,
23
+ height: 720,
24
+ fps: 30,
25
+ children: [],
26
+ });
27
+
28
+ expect(element.type).toBe("render");
29
+ expect(element.props.width).toBe(1280);
30
+ expect(element.props.height).toBe(720);
31
+ expect(element.props.fps).toBe(30);
32
+ });
33
+
34
+ test("Video creates correct element structure", () => {
35
+ const element = Video({
36
+ prompt: "ocean waves",
37
+ model: fal.videoModel("wan-2.5"),
38
+ });
39
+
40
+ expect(element.type).toBe("video");
41
+ expect(element.props.prompt).toBe("ocean waves");
42
+ });
43
+
44
+ test("Clip creates correct element structure", () => {
45
+ const element = Clip({
46
+ duration: 5,
47
+ transition: { name: "fade", duration: 0.5 },
48
+ children: [],
49
+ });
50
+
51
+ expect(element.type).toBe("clip");
52
+ expect(element.props.duration).toBe(5);
53
+ expect(element.props.transition).toEqual({ name: "fade", duration: 0.5 });
54
+ });
55
+
56
+ test("Image creates correct element structure", () => {
57
+ const element = Image({
58
+ prompt: "fat tiger on couch",
59
+ model: fal.imageModel("flux-schnell"),
60
+ aspectRatio: "16:9",
61
+ zoom: "in",
62
+ });
63
+
64
+ expect(element.type).toBe("image");
65
+ expect(element.props.prompt).toBe("fat tiger on couch");
66
+ expect(element.props.aspectRatio).toBe("16:9");
67
+ expect(element.props.zoom).toBe("in");
68
+ });
69
+
70
+ test("Title creates correct element with text children", () => {
71
+ const element = Title({
72
+ position: "bottom",
73
+ color: "#ffffff",
74
+ children: "I'M IN DANGER",
75
+ });
76
+
77
+ expect(element.type).toBe("title");
78
+ expect(element.props.position).toBe("bottom");
79
+ expect(element.props.color).toBe("#ffffff");
80
+ expect(element.children).toContain("I'M IN DANGER");
81
+ });
82
+
83
+ test("Animate creates correct element with nested image", () => {
84
+ const image = Image({ prompt: "luigi in wheelchair" });
85
+ const element = Animate({
86
+ image,
87
+ model: fal.videoModel("wan-2.5"),
88
+ motion: "wheels spinning fast",
89
+ duration: 5,
90
+ });
91
+
92
+ expect(element.type).toBe("animate");
93
+ expect(element.props.image).toBe(image);
94
+ expect(element.props.motion).toBe("wheels spinning fast");
95
+ expect(element.props.duration).toBe(5);
96
+ });
97
+
98
+ test("nested composition builds correct tree", () => {
99
+ const root = Render({
100
+ width: 1080,
101
+ height: 1920,
102
+ children: [
103
+ Clip({
104
+ duration: 5,
105
+ children: [
106
+ Image({
107
+ prompt: "ralph wiggum",
108
+ model: fal.imageModel("flux-schnell"),
109
+ }),
110
+ Title({ children: "HELLO" }),
111
+ ],
112
+ }),
113
+ Clip({
114
+ duration: 3,
115
+ transition: { name: "fade", duration: 0.3 },
116
+ children: [
117
+ Image({
118
+ prompt: "fat tiger",
119
+ model: fal.imageModel("flux-schnell"),
120
+ }),
121
+ ],
122
+ }),
123
+ ],
124
+ });
125
+
126
+ expect(root.type).toBe("render");
127
+ expect(root.children.length).toBe(2);
128
+
129
+ const clip1 = root.children[0] as ReturnType<typeof Clip>;
130
+ expect(clip1.type).toBe("clip");
131
+ expect(clip1.children.length).toBe(2);
132
+
133
+ const clip2 = root.children[1] as ReturnType<typeof Clip>;
134
+ expect(clip2.type).toBe("clip");
135
+ expect(clip2.props.transition).toEqual({ name: "fade", duration: 0.3 });
136
+ });
137
+ });
138
+
139
+ describe("varg-react render", () => {
140
+ test("render throws on non-render root", async () => {
141
+ const clip = Clip({ duration: 5, children: [] });
142
+
143
+ expect(render(clip)).rejects.toThrow("Root element must be <Render>");
144
+ });
145
+
146
+ test("render requires model prop for image with prompt", async () => {
147
+ const root = Render({
148
+ width: 720,
149
+ height: 720,
150
+ children: [
151
+ Clip({
152
+ duration: 3,
153
+ children: [Image({ prompt: "test image without model" })],
154
+ }),
155
+ ],
156
+ });
157
+
158
+ expect(render(root)).rejects.toThrow("model");
159
+ });
160
+ });
161
+
162
+ describe("layout renderers", () => {
163
+ const testImage1 = "media/cyberpunk-street.png";
164
+ const testImage2 = "media/fal-coffee-shop.png";
165
+ const outPath = "output/layout-test.mp4";
166
+
167
+ test("Split renders side-by-side images", async () => {
168
+ const root = Render({
169
+ width: 1280,
170
+ height: 720,
171
+ children: [
172
+ Clip({
173
+ duration: 2,
174
+ children: [
175
+ Split({
176
+ direction: "horizontal",
177
+ children: [
178
+ Image({ src: testImage1 }),
179
+ Image({ src: testImage2 }),
180
+ ],
181
+ }),
182
+ ],
183
+ }),
184
+ ],
185
+ });
186
+
187
+ const result = await render(root, { output: outPath, quiet: true });
188
+ expect(result).toBeInstanceOf(Uint8Array);
189
+ expect(result.length).toBeGreaterThan(0);
190
+ expect(existsSync(outPath)).toBe(true);
191
+ unlinkSync(outPath);
192
+ });
193
+
194
+ test(
195
+ "Slider renders with slide transitions",
196
+ async () => {
197
+ const root = Render({
198
+ width: 1280,
199
+ height: 720,
200
+ children: [
201
+ Clip({
202
+ duration: 4,
203
+ children: [
204
+ Slider({
205
+ direction: "horizontal",
206
+ children: [
207
+ Image({ src: testImage1 }),
208
+ Image({ src: testImage2 }),
209
+ ],
210
+ }),
211
+ ],
212
+ }),
213
+ ],
214
+ });
215
+
216
+ const result = await render(root, { output: outPath, quiet: true });
217
+ expect(result).toBeInstanceOf(Uint8Array);
218
+ expect(existsSync(outPath)).toBe(true);
219
+ unlinkSync(outPath);
220
+ },
221
+ { timeout: 30000 },
222
+ );
223
+
224
+ test(
225
+ "Swipe renders with swipe animation",
226
+ async () => {
227
+ const root = Render({
228
+ width: 1280,
229
+ height: 720,
230
+ children: [
231
+ Clip({
232
+ duration: 4,
233
+ children: [
234
+ Swipe({
235
+ direction: "left",
236
+ interval: 2,
237
+ children: [
238
+ Image({ src: testImage1 }),
239
+ Image({ src: testImage2 }),
240
+ ],
241
+ }),
242
+ ],
243
+ }),
244
+ ],
245
+ });
246
+
247
+ const result = await render(root, { output: outPath, quiet: true });
248
+ expect(result).toBeInstanceOf(Uint8Array);
249
+ expect(existsSync(outPath)).toBe(true);
250
+ unlinkSync(outPath);
251
+ },
252
+ { timeout: 30000 },
253
+ );
254
+
255
+ test("Packshot renders end card with logo and cta", async () => {
256
+ const root = Render({
257
+ width: 1280,
258
+ height: 720,
259
+ children: [
260
+ Clip({
261
+ duration: 3,
262
+ children: [
263
+ Packshot({
264
+ background: "#1a1a2e",
265
+ logo: testImage1,
266
+ logoPosition: "center",
267
+ logoSize: "40%",
268
+ cta: "Subscribe Now!",
269
+ ctaPosition: "bottom",
270
+ ctaColor: "#FFD700",
271
+ duration: 3,
272
+ }),
273
+ ],
274
+ }),
275
+ ],
276
+ });
277
+
278
+ const result = await render(root, { output: outPath, quiet: true });
279
+ expect(result).toBeInstanceOf(Uint8Array);
280
+ expect(existsSync(outPath)).toBe(true);
281
+ unlinkSync(outPath);
282
+ });
283
+
284
+ test(
285
+ "Captions burns subtitles from SRT file",
286
+ async () => {
287
+ const root = Render({
288
+ width: 1280,
289
+ height: 720,
290
+ children: [
291
+ Clip({
292
+ duration: 3,
293
+ children: [Image({ src: testImage1 })],
294
+ }),
295
+ Captions({
296
+ srt: "media/dora-test.srt",
297
+ style: "tiktok",
298
+ }),
299
+ ],
300
+ });
301
+
302
+ const result = await render(root, { output: outPath, quiet: true });
303
+ expect(result).toBeInstanceOf(Uint8Array);
304
+ expect(existsSync(outPath)).toBe(true);
305
+ unlinkSync(outPath);
306
+ },
307
+ { timeout: 30000 },
308
+ );
309
+ });
@@ -0,0 +1,21 @@
1
+ import { renderRoot } from "./renderers";
2
+ import type { RenderOptions, VargElement } from "./types";
3
+
4
+ export async function render(
5
+ element: VargElement,
6
+ options: RenderOptions = {},
7
+ ): Promise<Uint8Array> {
8
+ if (element.type !== "render") {
9
+ throw new Error("Root element must be <Render>");
10
+ }
11
+
12
+ return renderRoot(element as VargElement<"render">, options);
13
+ }
14
+
15
+ export const renderStream = {
16
+ async *stream(element: VargElement, options: RenderOptions = {}) {
17
+ yield { type: "start", progress: 0 };
18
+ const result = await render(element, options);
19
+ yield { type: "complete", progress: 100, result };
20
+ },
21
+ };
@@ -0,0 +1,59 @@
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
+ }