varg.ai-sdk 0.1.0 → 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 (236) 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 +48 -8
  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} +63 -90
  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 -227
  206. package/action/edit/SKILL.md +0 -235
  207. package/action/edit/index.ts +0 -493
  208. package/action/image/SKILL.md +0 -140
  209. package/action/image/index.ts +0 -112
  210. package/action/sync/SKILL.md +0 -136
  211. package/action/sync/index.ts +0 -187
  212. package/action/transcribe/SKILL.md +0 -179
  213. package/action/video/SKILL.md +0 -116
  214. package/action/video/index.ts +0 -135
  215. package/action/voice/SKILL.md +0 -125
  216. package/action/voice/index.ts +0 -201
  217. package/index.ts +0 -38
  218. package/lib/README.md +0 -144
  219. package/lib/ai-sdk/fal.ts +0 -106
  220. package/lib/ai-sdk/replicate.ts +0 -107
  221. package/lib/elevenlabs.ts +0 -382
  222. package/lib/fal.ts +0 -478
  223. package/lib/ffmpeg.ts +0 -467
  224. package/lib/fireworks.ts +0 -235
  225. package/lib/groq.ts +0 -246
  226. package/lib/higgsfield.ts +0 -176
  227. package/lib/remotion/SKILL.md +0 -823
  228. package/lib/remotion/cli.ts +0 -115
  229. package/lib/remotion/functions.ts +0 -283
  230. package/lib/remotion/index.ts +0 -19
  231. package/lib/remotion/templates.ts +0 -73
  232. package/lib/replicate.ts +0 -304
  233. package/output.txt +0 -1
  234. package/test-import.ts +0 -7
  235. package/test-services.ts +0 -97
  236. 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
+ }