vargai 0.3.2 → 0.4.0-alpha2

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 (64) hide show
  1. package/biome.json +6 -1
  2. package/docs/index.html +1130 -0
  3. package/docs/prompting.md +326 -0
  4. package/docs/react.md +834 -0
  5. package/package.json +10 -4
  6. package/src/cli/commands/index.ts +1 -4
  7. package/src/cli/commands/render.tsx +94 -0
  8. package/src/cli/index.ts +3 -2
  9. package/src/react/cli.ts +52 -0
  10. package/src/react/elements.ts +146 -0
  11. package/src/react/examples/branching.tsx +66 -0
  12. package/src/react/examples/captions-demo.tsx +37 -0
  13. package/src/react/examples/character-video.tsx +84 -0
  14. package/src/react/examples/grid.tsx +53 -0
  15. package/src/react/examples/layouts-demo.tsx +57 -0
  16. package/src/react/examples/madi.tsx +60 -0
  17. package/src/react/examples/music-test.tsx +35 -0
  18. package/src/react/examples/onlyfans-1m/workflow.tsx +88 -0
  19. package/src/react/examples/orange-portrait.tsx +41 -0
  20. package/src/react/examples/split-element-demo.tsx +60 -0
  21. package/src/react/examples/split-layout-demo.tsx +60 -0
  22. package/src/react/examples/split.tsx +41 -0
  23. package/src/react/examples/video-grid.tsx +46 -0
  24. package/src/react/index.ts +43 -0
  25. package/src/react/layouts/grid.tsx +28 -0
  26. package/src/react/layouts/index.ts +2 -0
  27. package/src/react/layouts/split.tsx +20 -0
  28. package/src/react/react.test.ts +309 -0
  29. package/src/react/render.ts +21 -0
  30. package/src/react/renderers/animate.ts +59 -0
  31. package/src/react/renderers/captions.ts +297 -0
  32. package/src/react/renderers/clip.ts +248 -0
  33. package/src/react/renderers/context.ts +17 -0
  34. package/src/react/renderers/image.ts +109 -0
  35. package/src/react/renderers/index.ts +22 -0
  36. package/src/react/renderers/music.ts +60 -0
  37. package/src/react/renderers/packshot.ts +84 -0
  38. package/src/react/renderers/progress.ts +173 -0
  39. package/src/react/renderers/render.ts +319 -0
  40. package/src/react/renderers/slider.ts +69 -0
  41. package/src/react/renderers/speech.ts +53 -0
  42. package/src/react/renderers/split.ts +91 -0
  43. package/src/react/renderers/subtitle.ts +16 -0
  44. package/src/react/renderers/swipe.ts +75 -0
  45. package/src/react/renderers/title.ts +17 -0
  46. package/src/react/renderers/utils.ts +124 -0
  47. package/src/react/renderers/video.ts +127 -0
  48. package/src/react/runtime/jsx-dev-runtime.ts +43 -0
  49. package/src/react/runtime/jsx-runtime.ts +35 -0
  50. package/src/react/types.ts +235 -0
  51. package/src/studio/index.ts +26 -0
  52. package/src/studio/scanner.ts +102 -0
  53. package/src/studio/server.ts +554 -0
  54. package/src/studio/stages.ts +251 -0
  55. package/src/studio/step-renderer.ts +279 -0
  56. package/src/studio/types.ts +60 -0
  57. package/src/studio/ui/cache.html +303 -0
  58. package/src/studio/ui/index.html +1820 -0
  59. package/tsconfig.cli.json +8 -0
  60. package/tsconfig.json +3 -1
  61. package/bun.lock +0 -1255
  62. package/docs/plan.md +0 -66
  63. package/docs/todo.md +0 -14
  64. /package/docs/{varg-sdk.md → sdk.md} +0 -0
@@ -0,0 +1,16 @@
1
+ import type { SubtitleLayer } from "../../ai-sdk/providers/editly/types";
2
+ import type { SubtitleProps, VargElement } from "../types";
3
+ import { getTextContent } from "./utils";
4
+
5
+ export function renderSubtitle(
6
+ element: VargElement<"subtitle">,
7
+ ): SubtitleLayer {
8
+ const props = element.props as SubtitleProps;
9
+ const text = getTextContent(element.children);
10
+
11
+ return {
12
+ type: "subtitle",
13
+ text,
14
+ backgroundColor: props.backgroundColor,
15
+ };
16
+ }
@@ -0,0 +1,75 @@
1
+ import { editly } from "../../ai-sdk/providers/editly";
2
+ import type { Clip } from "../../ai-sdk/providers/editly/types";
3
+ import type { SwipeProps, VargElement } from "../types";
4
+ import type { RenderContext } from "./context";
5
+ import { renderImage } from "./image";
6
+ import { renderVideo } from "./video";
7
+
8
+ const SWIPE_TRANSITION_MAP = {
9
+ left: "slideleft",
10
+ right: "slideright",
11
+ up: "slideup",
12
+ down: "slidedown",
13
+ } as const;
14
+
15
+ export async function renderSwipe(
16
+ element: VargElement<"swipe">,
17
+ ctx: RenderContext,
18
+ ): Promise<string> {
19
+ const props = element.props as SwipeProps;
20
+ const direction = props.direction ?? "left";
21
+ const interval = props.interval ?? 3;
22
+
23
+ const childPaths: string[] = [];
24
+
25
+ for (const child of element.children) {
26
+ if (!child || typeof child !== "object" || !("type" in child)) continue;
27
+ const childElement = child as VargElement;
28
+
29
+ if (childElement.type === "image") {
30
+ const path = await renderImage(childElement as VargElement<"image">, ctx);
31
+ childPaths.push(path);
32
+ } else if (childElement.type === "video") {
33
+ const path = await renderVideo(childElement as VargElement<"video">, ctx);
34
+ childPaths.push(path);
35
+ }
36
+ }
37
+
38
+ if (childPaths.length === 0) {
39
+ throw new Error("Swipe element requires at least one image or video child");
40
+ }
41
+
42
+ if (childPaths.length === 1) {
43
+ return childPaths[0]!;
44
+ }
45
+
46
+ const transitionName = SWIPE_TRANSITION_MAP[direction];
47
+
48
+ const clips: Clip[] = childPaths.map((path, i) => {
49
+ const isVideo = path.endsWith(".mp4") || path.endsWith(".webm");
50
+ const isLast = i === childPaths.length - 1;
51
+
52
+ return {
53
+ layers: [
54
+ isVideo
55
+ ? { type: "video" as const, path, resizeMode: "cover" as const }
56
+ : { type: "image" as const, path, resizeMode: "cover" as const },
57
+ ],
58
+ duration: interval,
59
+ transition: isLast ? null : { name: transitionName, duration: 0.5 },
60
+ };
61
+ });
62
+
63
+ const outPath = `/tmp/varg-swipe-${Date.now()}.mp4`;
64
+
65
+ await editly({
66
+ outPath,
67
+ width: ctx.width,
68
+ height: ctx.height,
69
+ fps: ctx.fps,
70
+ clips,
71
+ });
72
+
73
+ ctx.tempFiles.push(outPath);
74
+ return outPath;
75
+ }
@@ -0,0 +1,17 @@
1
+ import type { TitleLayer } from "../../ai-sdk/providers/editly/types";
2
+ import type { TitleProps, VargElement } from "../types";
3
+ import { getTextContent } from "./utils";
4
+
5
+ export function renderTitle(element: VargElement<"title">): TitleLayer {
6
+ const props = element.props as TitleProps;
7
+ const text = getTextContent(element.children);
8
+
9
+ return {
10
+ type: "title",
11
+ text,
12
+ textColor: props.color,
13
+ position: props.position,
14
+ start: props.start,
15
+ stop: props.end,
16
+ };
17
+ }
@@ -0,0 +1,124 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import type { VargElement, VargNode } from "../types";
4
+
5
+ export function resolvePath(path: string): string {
6
+ if (path.startsWith("http://") || path.startsWith("https://")) {
7
+ return path;
8
+ }
9
+ return resolve(process.cwd(), path);
10
+ }
11
+
12
+ export function toFileUrl(path: string): string {
13
+ if (path.startsWith("http://") || path.startsWith("https://")) {
14
+ return path;
15
+ }
16
+ return `file://${resolvePath(path)}`;
17
+ }
18
+
19
+ type CacheKeyPart = string | number | boolean | null | undefined;
20
+
21
+ function isVargElement(v: unknown): v is VargElement {
22
+ return (
23
+ typeof v === "object" &&
24
+ v !== null &&
25
+ "type" in v &&
26
+ "props" in v &&
27
+ "children" in v
28
+ );
29
+ }
30
+
31
+ function isLocalFilePath(v: string): boolean {
32
+ if (v.startsWith("http://") || v.startsWith("https://")) return false;
33
+ if (v.startsWith("data:")) return false;
34
+ const resolved = resolvePath(v);
35
+ return existsSync(resolved);
36
+ }
37
+
38
+ function getFileFingerprint(path: string): string {
39
+ const resolved = resolvePath(path);
40
+ const stat = statSync(resolved);
41
+ return `${path}:${stat.mtimeMs}:${stat.size}`;
42
+ }
43
+
44
+ function serializeValue(v: unknown): string {
45
+ if (typeof v === "string") {
46
+ if (isLocalFilePath(v)) {
47
+ return getFileFingerprint(v);
48
+ }
49
+ return v;
50
+ }
51
+ if (v instanceof Uint8Array) {
52
+ return Buffer.from(v).toString("base64");
53
+ }
54
+ if (Array.isArray(v)) {
55
+ return `[${v.map(serializeValue).join(",")}]`;
56
+ }
57
+ if (v && typeof v === "object") {
58
+ const entries = Object.entries(v)
59
+ .map(([key, val]) => `${key}:${serializeValue(val)}`)
60
+ .join(",");
61
+ return `{${entries}}`;
62
+ }
63
+ return String(v);
64
+ }
65
+
66
+ export function computeCacheKey(element: VargElement): CacheKeyPart[] {
67
+ const key: CacheKeyPart[] = [element.type];
68
+
69
+ for (const [k, v] of Object.entries(element.props)) {
70
+ if (k === "children") continue;
71
+ if (k === "model" && v && typeof v === "object" && "modelId" in v) {
72
+ const model = v as {
73
+ provider?: string;
74
+ modelId: string;
75
+ settings?: Record<string, unknown>;
76
+ };
77
+ key.push("model", model.provider ?? "", model.modelId);
78
+ // Include model settings in cache key (e.g., higgsfield styleId, quality)
79
+ if (model.settings) {
80
+ key.push("modelSettings", serializeValue(model.settings));
81
+ }
82
+ continue;
83
+ }
84
+ if (typeof v === "string") {
85
+ if (isLocalFilePath(v)) {
86
+ key.push(k, getFileFingerprint(v));
87
+ } else {
88
+ key.push(k, v);
89
+ }
90
+ } else if (typeof v === "number" || typeof v === "boolean") {
91
+ key.push(k, v);
92
+ } else if (v === null || v === undefined) {
93
+ key.push(k, v);
94
+ } else if (v instanceof Uint8Array) {
95
+ key.push(k, Buffer.from(v).toString("base64"));
96
+ } else if (isVargElement(v)) {
97
+ key.push(k, ...computeCacheKey(v));
98
+ } else if (Array.isArray(v) || typeof v === "object") {
99
+ key.push(k, serializeValue(v));
100
+ }
101
+ }
102
+
103
+ for (const child of element.children) {
104
+ if (typeof child === "string") {
105
+ key.push("text", child);
106
+ } else if (typeof child === "number") {
107
+ key.push("num", child);
108
+ } else if (isVargElement(child)) {
109
+ key.push("child", ...computeCacheKey(child));
110
+ }
111
+ }
112
+
113
+ return key;
114
+ }
115
+
116
+ export function getTextContent(node: VargNode): string {
117
+ if (typeof node === "string") return node;
118
+ if (typeof node === "number") return String(node);
119
+ if (Array.isArray(node)) return node.map(getTextContent).join("");
120
+ if (node && typeof node === "object" && "children" in node) {
121
+ return node.children.map(getTextContent).join("");
122
+ }
123
+ return "";
124
+ }
@@ -0,0 +1,127 @@
1
+ import { File } from "../../ai-sdk/file";
2
+ import type { generateVideo } from "../../ai-sdk/generate-video";
3
+ import type {
4
+ ImageInput,
5
+ VargElement,
6
+ VideoPrompt,
7
+ VideoProps,
8
+ } from "../types";
9
+ import type { RenderContext } from "./context";
10
+ import { renderImage } from "./image";
11
+ import { addTask, completeTask, startTask } from "./progress";
12
+ import { computeCacheKey, toFileUrl } from "./utils";
13
+
14
+ async function resolveImageInput(
15
+ input: ImageInput,
16
+ ctx: RenderContext,
17
+ ): Promise<Uint8Array> {
18
+ if (input instanceof Uint8Array) {
19
+ return input;
20
+ }
21
+ if (typeof input === "string") {
22
+ const response = await fetch(toFileUrl(input));
23
+ return new Uint8Array(await response.arrayBuffer());
24
+ }
25
+ const path = await renderImage(input, ctx);
26
+ const response = await fetch(toFileUrl(path));
27
+ return new Uint8Array(await response.arrayBuffer());
28
+ }
29
+
30
+ async function resolveMediaInput(
31
+ input: Uint8Array | string | undefined,
32
+ ): Promise<Uint8Array | undefined> {
33
+ if (!input) return undefined;
34
+ if (input instanceof Uint8Array) return input;
35
+ const response = await fetch(toFileUrl(input));
36
+ return new Uint8Array(await response.arrayBuffer());
37
+ }
38
+
39
+ async function resolvePrompt(
40
+ prompt: VideoPrompt,
41
+ ctx: RenderContext,
42
+ ): Promise<
43
+ | string
44
+ | {
45
+ text?: string;
46
+ images?: Uint8Array[];
47
+ audio?: Uint8Array;
48
+ video?: Uint8Array;
49
+ }
50
+ > {
51
+ if (typeof prompt === "string") {
52
+ return prompt;
53
+ }
54
+ const [resolvedImages, resolvedAudio, resolvedVideo] = await Promise.all([
55
+ prompt.images
56
+ ? Promise.all(prompt.images.map((img) => resolveImageInput(img, ctx)))
57
+ : undefined,
58
+ resolveMediaInput(prompt.audio),
59
+ resolveMediaInput(prompt.video),
60
+ ]);
61
+ return {
62
+ text: prompt.text,
63
+ images: resolvedImages,
64
+ audio: resolvedAudio,
65
+ video: resolvedVideo,
66
+ };
67
+ }
68
+
69
+ export async function renderVideo(
70
+ element: VargElement<"video">,
71
+ ctx: RenderContext,
72
+ ): Promise<string> {
73
+ const props = element.props as VideoProps;
74
+
75
+ if (props.src && !props.prompt) {
76
+ return props.src;
77
+ }
78
+
79
+ const prompt = props.prompt;
80
+ if (!prompt) {
81
+ throw new Error("Video element requires either 'prompt' or 'src'");
82
+ }
83
+
84
+ const model = props.model;
85
+ if (!model) {
86
+ throw new Error("Video element requires 'model' prop when using prompt");
87
+ }
88
+
89
+ // Compute cache key for deduplication
90
+ const cacheKey = computeCacheKey(element);
91
+ const cacheKeyStr = JSON.stringify(cacheKey);
92
+
93
+ // Check if this element is already being rendered (deduplication)
94
+ const pendingRender = ctx.pending.get(cacheKeyStr);
95
+ if (pendingRender) {
96
+ return pendingRender;
97
+ }
98
+
99
+ // Create the render promise and store it for deduplication
100
+ const renderPromise = (async () => {
101
+ const resolvedPrompt = await resolvePrompt(prompt, ctx);
102
+
103
+ const modelId = typeof model === "string" ? model : model.modelId;
104
+ const taskId = ctx.progress
105
+ ? addTask(ctx.progress, "video", modelId)
106
+ : null;
107
+ if (taskId && ctx.progress) startTask(ctx.progress, taskId);
108
+
109
+ const { video } = await ctx.generateVideo({
110
+ model,
111
+ prompt: resolvedPrompt,
112
+ duration: 5,
113
+ cacheKey,
114
+ } as Parameters<typeof generateVideo>[0]);
115
+
116
+ if (taskId && ctx.progress) completeTask(ctx.progress, taskId);
117
+
118
+ const tempPath = await File.toTemp(video);
119
+ ctx.tempFiles.push(tempPath);
120
+
121
+ return tempPath;
122
+ })();
123
+
124
+ ctx.pending.set(cacheKeyStr, renderPromise);
125
+
126
+ return renderPromise;
127
+ }
@@ -0,0 +1,43 @@
1
+ import type { VargElement, VargNode } from "../types";
2
+
3
+ type ElementFactory = (props: Record<string, unknown>) => VargElement;
4
+
5
+ export function jsx(
6
+ type: ElementFactory,
7
+ props: Record<string, unknown> | null,
8
+ key?: string,
9
+ ): VargElement {
10
+ const finalProps = { ...props };
11
+ if (key !== undefined) {
12
+ finalProps.key = key;
13
+ }
14
+ return type(finalProps);
15
+ }
16
+
17
+ export function jsxs(
18
+ type: ElementFactory,
19
+ props: Record<string, unknown> | null,
20
+ key?: string,
21
+ ): VargElement {
22
+ return jsx(type, props, key);
23
+ }
24
+
25
+ export function jsxDEV(
26
+ type: ElementFactory,
27
+ props: Record<string, unknown> | null,
28
+ key?: string,
29
+ ): VargElement {
30
+ return jsx(type, props, key);
31
+ }
32
+
33
+ export const Fragment = ({ children }: { children?: VargNode }) => children;
34
+
35
+ export namespace JSX {
36
+ export type Element = VargElement;
37
+ // biome-ignore lint/complexity/noBannedTypes: required for JSX namespace
38
+ export type IntrinsicElements = {};
39
+ export interface ElementChildrenAttribute {
40
+ // biome-ignore lint/complexity/noBannedTypes: required for JSX namespace
41
+ children: {};
42
+ }
43
+ }
@@ -0,0 +1,35 @@
1
+ import type { VargElement, VargNode } from "../types";
2
+
3
+ type ElementFactory = (props: Record<string, unknown>) => VargElement;
4
+
5
+ export function jsx(
6
+ type: ElementFactory,
7
+ props: Record<string, unknown> | null,
8
+ key?: string,
9
+ ): VargElement {
10
+ const finalProps = { ...props };
11
+ if (key !== undefined) {
12
+ finalProps.key = key;
13
+ }
14
+ return type(finalProps);
15
+ }
16
+
17
+ export function jsxs(
18
+ type: ElementFactory,
19
+ props: Record<string, unknown> | null,
20
+ key?: string,
21
+ ): VargElement {
22
+ return jsx(type, props, key);
23
+ }
24
+
25
+ export const Fragment = ({ children }: { children?: VargNode }) => children;
26
+
27
+ export namespace JSX {
28
+ export type Element = VargElement;
29
+ // biome-ignore lint/complexity/noBannedTypes: required for JSX namespace
30
+ export type IntrinsicElements = {};
31
+ export interface ElementChildrenAttribute {
32
+ // biome-ignore lint/complexity/noBannedTypes: required for JSX namespace
33
+ children: {};
34
+ }
35
+ }
@@ -0,0 +1,235 @@
1
+ import type { ImageModelV3, SpeechModelV3 } from "@ai-sdk/provider";
2
+ import type { MusicModelV3 } from "../ai-sdk/music-model";
3
+ import type {
4
+ Position,
5
+ ResizeMode,
6
+ SizeValue,
7
+ TransitionOptions,
8
+ } from "../ai-sdk/providers/editly/types";
9
+ import type { VideoModelV3 } from "../ai-sdk/video-model";
10
+
11
+ export type VargElementType =
12
+ | "render"
13
+ | "clip"
14
+ | "overlay"
15
+ | "image"
16
+ | "video"
17
+ | "animate"
18
+ | "speech"
19
+ | "talking-head"
20
+ | "title"
21
+ | "subtitle"
22
+ | "music"
23
+ | "captions"
24
+ | "split"
25
+ | "slider"
26
+ | "swipe"
27
+ | "packshot";
28
+
29
+ export interface VargElement<T extends VargElementType = VargElementType> {
30
+ type: T;
31
+ props: Record<string, unknown>;
32
+ children: VargNode[];
33
+ }
34
+
35
+ export type VargNode =
36
+ | VargElement
37
+ | string
38
+ | number
39
+ | null
40
+ | undefined
41
+ | VargNode[];
42
+
43
+ export interface BaseProps {
44
+ key?: string | number;
45
+ }
46
+
47
+ export interface PositionProps {
48
+ left?: SizeValue;
49
+ top?: SizeValue;
50
+ width?: SizeValue;
51
+ height?: SizeValue;
52
+ }
53
+
54
+ export interface VolumeProps {
55
+ volume?: number;
56
+ }
57
+
58
+ export interface AudioProps extends VolumeProps {
59
+ keepAudio?: boolean;
60
+ }
61
+
62
+ export type TrimProps =
63
+ | { cutFrom?: number; cutTo?: number; duration?: never }
64
+ | { cutFrom?: number; cutTo?: never; duration?: number };
65
+
66
+ // Root container - sets dimensions, fps, contains clips
67
+ export interface RenderProps extends BaseProps {
68
+ width?: number;
69
+ height?: number;
70
+ fps?: number;
71
+ normalize?: boolean;
72
+ children?: VargNode;
73
+ }
74
+
75
+ export interface ClipProps extends BaseProps {
76
+ duration?: number | "auto";
77
+ transition?: TransitionOptions;
78
+ children?: VargNode;
79
+ }
80
+
81
+ export interface OverlayProps extends BaseProps, PositionProps, AudioProps {
82
+ children?: VargNode;
83
+ }
84
+
85
+ export type ImageInput = Uint8Array | string | VargElement<"image">;
86
+ export type ImagePrompt = string | { text?: string; images: ImageInput[] };
87
+
88
+ export interface ImageProps extends BaseProps, PositionProps {
89
+ prompt?: ImagePrompt;
90
+ src?: string;
91
+ model?: ImageModelV3;
92
+ aspectRatio?: `${number}:${number}`;
93
+ zoom?: "in" | "out" | "left" | "right";
94
+ resize?: ResizeMode;
95
+ position?: Position;
96
+ size?: { width: string; height: string };
97
+ removeBackground?: boolean;
98
+ }
99
+
100
+ export type VideoPrompt =
101
+ | string
102
+ | {
103
+ text?: string;
104
+ images?: ImageInput[];
105
+ audio?: Uint8Array | string;
106
+ video?: Uint8Array | string;
107
+ };
108
+
109
+ export type VideoProps = BaseProps &
110
+ PositionProps &
111
+ AudioProps &
112
+ TrimProps & {
113
+ prompt?: VideoPrompt;
114
+ src?: string;
115
+ model?: VideoModelV3;
116
+ resize?: ResizeMode;
117
+ };
118
+
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
+ export interface SpeechProps extends BaseProps, VolumeProps {
129
+ voice?: string;
130
+ model?: SpeechModelV3;
131
+ id?: string;
132
+ children?: string;
133
+ }
134
+
135
+ export interface TalkingHeadProps extends BaseProps {
136
+ character?: string;
137
+ src?: string;
138
+ voice?: string;
139
+ model?: VideoModelV3;
140
+ lipsyncModel?: VideoModelV3;
141
+ position?:
142
+ | Position
143
+ | { left?: string; right?: string; top?: string; bottom?: string };
144
+ size?: { width: string; height: string };
145
+ children?: string;
146
+ }
147
+
148
+ export interface TitleProps extends BaseProps {
149
+ position?: Position;
150
+ color?: string;
151
+ start?: number;
152
+ end?: number;
153
+ children?: string;
154
+ }
155
+
156
+ export interface SubtitleProps extends BaseProps {
157
+ backgroundColor?: string;
158
+ children?: string;
159
+ }
160
+
161
+ export type MusicProps = BaseProps &
162
+ VolumeProps &
163
+ TrimProps & {
164
+ prompt?: string;
165
+ model?: MusicModelV3;
166
+ src?: string;
167
+ loop?: boolean;
168
+ ducking?: boolean;
169
+ };
170
+
171
+ export interface CaptionsProps extends BaseProps {
172
+ src?: string | VargElement<"speech">;
173
+ srt?: string;
174
+ style?: "tiktok" | "karaoke" | "bounce" | "typewriter";
175
+ color?: string;
176
+ activeColor?: string;
177
+ fontSize?: number;
178
+ }
179
+
180
+ export interface SplitProps extends BaseProps {
181
+ direction?: "horizontal" | "vertical";
182
+ children?: VargNode;
183
+ }
184
+
185
+ export interface SliderProps extends BaseProps {
186
+ direction?: "horizontal" | "vertical";
187
+ children?: VargNode;
188
+ }
189
+
190
+ export interface SwipeProps extends BaseProps {
191
+ direction?: "left" | "right" | "up" | "down";
192
+ interval?: number;
193
+ children?: VargNode;
194
+ }
195
+
196
+ export interface PackshotProps extends BaseProps {
197
+ background?: VargElement<"image"> | string;
198
+ logo?: string;
199
+ logoPosition?: Position;
200
+ logoSize?: SizeValue;
201
+ cta?: string;
202
+ ctaPosition?: Position;
203
+ ctaColor?: string;
204
+ ctaSize?: number;
205
+ blinkCta?: boolean;
206
+ duration?: number;
207
+ }
208
+
209
+ export type RenderMode = "strict" | "default" | "preview";
210
+
211
+ export interface RenderOptions {
212
+ output?: string;
213
+ cache?: string;
214
+ quiet?: boolean;
215
+ mode?: RenderMode;
216
+ }
217
+
218
+ export interface ElementPropsMap {
219
+ render: RenderProps;
220
+ clip: ClipProps;
221
+ overlay: OverlayProps;
222
+ image: ImageProps;
223
+ video: VideoProps;
224
+ animate: AnimateProps;
225
+ speech: SpeechProps;
226
+ "talking-head": TalkingHeadProps;
227
+ title: TitleProps;
228
+ subtitle: SubtitleProps;
229
+ music: MusicProps;
230
+ captions: CaptionsProps;
231
+ split: SplitProps;
232
+ slider: SliderProps;
233
+ swipe: SwipeProps;
234
+ packshot: PackshotProps;
235
+ }
@@ -0,0 +1,26 @@
1
+ import { resolve } from "node:path";
2
+ import { createStudioServer } from "./server";
3
+
4
+ // Parse arguments: bun run studio [file.tsx] [--cache=.cache/ai]
5
+ let initialFile: string | undefined;
6
+ let cacheDir = ".cache/ai";
7
+
8
+ for (const arg of process.argv.slice(2)) {
9
+ if (arg.startsWith("--cache=")) {
10
+ cacheDir = arg.replace("--cache=", "");
11
+ } else if (arg.endsWith(".tsx") || arg.endsWith(".ts")) {
12
+ initialFile = resolve(arg);
13
+ }
14
+ }
15
+
16
+ console.log("varg studio starting...");
17
+ console.log(`cache folder: ${cacheDir}`);
18
+ if (initialFile) {
19
+ console.log(`initial file: ${initialFile}`);
20
+ }
21
+
22
+ const { port } = createStudioServer({ cacheDir, initialFile });
23
+
24
+ console.log(`\nopen http://localhost:${port}`);
25
+ console.log(" /editor - code editor");
26
+ console.log(" /cache - cache viewer");