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.
- package/.claude/settings.local.json +1 -1
- package/.env.example +3 -0
- package/.github/workflows/ci.yml +23 -0
- package/.husky/README.md +102 -0
- package/.husky/commit-msg +6 -0
- package/.husky/pre-commit +9 -0
- package/.husky/pre-push +6 -0
- package/.size-limit.json +8 -0
- package/.test-hooks.ts +5 -0
- package/CLAUDE.md +10 -3
- package/CONTRIBUTING.md +150 -0
- package/LICENSE.md +53 -0
- package/README.md +56 -209
- package/SKILLS.md +26 -10
- package/biome.json +7 -1
- package/bun.lock +1286 -0
- package/commitlint.config.js +22 -0
- package/docs/index.html +1130 -0
- package/docs/prompting.md +326 -0
- package/docs/react.md +834 -0
- package/docs/sdk.md +812 -0
- package/ffmpeg/CLAUDE.md +68 -0
- package/package.json +48 -8
- package/pipeline/cookbooks/scripts/animate-frames-parallel.ts +84 -0
- package/pipeline/cookbooks/scripts/combine-scenes.sh +53 -0
- package/pipeline/cookbooks/scripts/generate-frames-parallel.ts +99 -0
- package/pipeline/cookbooks/scripts/still-to-video.sh +37 -0
- package/pipeline/cookbooks/text-to-tiktok.md +669 -0
- package/pipeline/cookbooks/trendwatching.md +156 -0
- package/plan.md +281 -0
- package/scripts/.gitkeep +0 -0
- package/src/ai-sdk/cache.ts +142 -0
- package/src/ai-sdk/examples/cached-generation.ts +53 -0
- package/src/ai-sdk/examples/duet-scene-4.ts +53 -0
- package/src/ai-sdk/examples/duet-scene-5-audio.ts +32 -0
- package/src/ai-sdk/examples/duet-video.ts +56 -0
- package/src/ai-sdk/examples/editly-composition.ts +63 -0
- package/src/ai-sdk/examples/editly-test.ts +57 -0
- package/src/ai-sdk/examples/editly-video-test.ts +52 -0
- package/src/ai-sdk/examples/fal-lipsync.ts +43 -0
- package/src/ai-sdk/examples/higgsfield-image.ts +61 -0
- package/src/ai-sdk/examples/music-generation.ts +19 -0
- package/src/ai-sdk/examples/openai-sora.ts +34 -0
- package/src/ai-sdk/examples/replicate-bg-removal.ts +52 -0
- package/src/ai-sdk/examples/simpsons-scene.ts +61 -0
- package/src/ai-sdk/examples/talking-lion.ts +55 -0
- package/src/ai-sdk/examples/video-generation.ts +39 -0
- package/src/ai-sdk/examples/workflow-animated-girl.ts +104 -0
- package/src/ai-sdk/examples/workflow-before-after.ts +114 -0
- package/src/ai-sdk/examples/workflow-character-grid.ts +112 -0
- package/src/ai-sdk/examples/workflow-slideshow.ts +161 -0
- package/src/ai-sdk/file-cache.ts +112 -0
- package/src/ai-sdk/file.ts +238 -0
- package/src/ai-sdk/generate-element.ts +92 -0
- package/src/ai-sdk/generate-music.ts +46 -0
- package/src/ai-sdk/generate-video.ts +165 -0
- package/src/ai-sdk/index.ts +72 -0
- package/src/ai-sdk/music-model.ts +110 -0
- package/src/ai-sdk/providers/editly/editly.test.ts +1108 -0
- package/src/ai-sdk/providers/editly/ffmpeg.ts +60 -0
- package/src/ai-sdk/providers/editly/index.ts +817 -0
- package/src/ai-sdk/providers/editly/layers.ts +776 -0
- package/src/ai-sdk/providers/editly/plan.md +144 -0
- package/src/ai-sdk/providers/editly/types.ts +328 -0
- package/src/ai-sdk/providers/elevenlabs-provider.ts +255 -0
- package/src/ai-sdk/providers/fal-provider.ts +512 -0
- package/src/ai-sdk/providers/higgsfield.ts +379 -0
- package/src/ai-sdk/providers/openai.ts +251 -0
- package/src/ai-sdk/providers/replicate.ts +16 -0
- package/src/ai-sdk/video-model.ts +185 -0
- package/src/cli/commands/find.tsx +137 -0
- package/src/cli/commands/help.tsx +85 -0
- package/src/cli/commands/index.ts +6 -0
- package/src/cli/commands/list.tsx +238 -0
- package/src/cli/commands/render.tsx +71 -0
- package/src/cli/commands/run.tsx +511 -0
- package/src/cli/commands/which.tsx +253 -0
- package/src/cli/index.ts +114 -0
- package/src/cli/quiet.ts +44 -0
- package/src/cli/types.ts +32 -0
- package/src/cli/ui/components/Badge.tsx +29 -0
- package/src/cli/ui/components/DataTable.tsx +51 -0
- package/src/cli/ui/components/Header.tsx +23 -0
- package/src/cli/ui/components/HelpBlock.tsx +44 -0
- package/src/cli/ui/components/KeyValue.tsx +33 -0
- package/src/cli/ui/components/OptionRow.tsx +81 -0
- package/src/cli/ui/components/Separator.tsx +23 -0
- package/src/cli/ui/components/StatusBox.tsx +108 -0
- package/src/cli/ui/components/VargBox.tsx +51 -0
- package/src/cli/ui/components/VargProgress.tsx +36 -0
- package/src/cli/ui/components/VargSpinner.tsx +34 -0
- package/src/cli/ui/components/VargText.tsx +56 -0
- package/src/cli/ui/components/index.ts +19 -0
- package/src/cli/ui/index.ts +12 -0
- package/src/cli/ui/render.ts +35 -0
- package/src/cli/ui/theme.ts +63 -0
- package/src/cli/utils.ts +78 -0
- package/src/core/executor/executor.ts +201 -0
- package/src/core/executor/index.ts +13 -0
- package/src/core/executor/job.ts +214 -0
- package/src/core/executor/pipeline.ts +222 -0
- package/src/core/index.ts +11 -0
- package/src/core/registry/index.ts +9 -0
- package/src/core/registry/loader.ts +149 -0
- package/src/core/registry/registry.ts +221 -0
- package/src/core/registry/resolver.ts +206 -0
- package/src/core/schema/helpers.ts +134 -0
- package/src/core/schema/index.ts +8 -0
- package/src/core/schema/shared.ts +102 -0
- package/src/core/schema/types.ts +279 -0
- package/src/core/schema/validator.ts +92 -0
- package/src/definitions/actions/captions.ts +261 -0
- package/src/definitions/actions/edit.ts +298 -0
- package/src/definitions/actions/image.ts +125 -0
- package/src/definitions/actions/index.ts +114 -0
- package/src/definitions/actions/music.ts +205 -0
- package/src/definitions/actions/sync.ts +128 -0
- package/{action/transcribe/index.ts → src/definitions/actions/transcribe.ts} +63 -90
- package/src/definitions/actions/upload.ts +111 -0
- package/src/definitions/actions/video.ts +163 -0
- package/src/definitions/actions/voice.ts +119 -0
- package/src/definitions/index.ts +23 -0
- package/src/definitions/models/elevenlabs.ts +50 -0
- package/src/definitions/models/flux.ts +56 -0
- package/src/definitions/models/index.ts +36 -0
- package/src/definitions/models/kling.ts +56 -0
- package/src/definitions/models/llama.ts +54 -0
- package/src/definitions/models/nano-banana-pro.ts +102 -0
- package/src/definitions/models/sonauto.ts +68 -0
- package/src/definitions/models/soul.ts +65 -0
- package/src/definitions/models/wan.ts +54 -0
- package/src/definitions/models/whisper.ts +44 -0
- package/src/definitions/skills/index.ts +12 -0
- package/src/definitions/skills/talking-character.ts +87 -0
- package/src/definitions/skills/text-to-tiktok.ts +97 -0
- package/src/index.ts +118 -0
- package/src/providers/apify.ts +269 -0
- package/src/providers/base.ts +264 -0
- package/src/providers/elevenlabs.ts +217 -0
- package/src/providers/fal.ts +392 -0
- package/src/providers/ffmpeg.ts +544 -0
- package/src/providers/fireworks.ts +193 -0
- package/src/providers/groq.ts +149 -0
- package/src/providers/higgsfield.ts +145 -0
- package/src/providers/index.ts +143 -0
- package/src/providers/replicate.ts +147 -0
- package/src/providers/storage.ts +206 -0
- package/src/react/cli.ts +52 -0
- package/src/react/elements.ts +146 -0
- package/src/react/examples/branching.tsx +66 -0
- package/src/react/examples/captions-demo.tsx +37 -0
- package/src/react/examples/character-video.tsx +84 -0
- package/src/react/examples/grid.tsx +53 -0
- package/src/react/examples/layouts-demo.tsx +57 -0
- package/src/react/examples/madi.tsx +60 -0
- package/src/react/examples/music-test.tsx +35 -0
- package/src/react/examples/onlyfans-1m/workflow.tsx +88 -0
- package/src/react/examples/orange-portrait.tsx +41 -0
- package/src/react/examples/split-element-demo.tsx +60 -0
- package/src/react/examples/split-layout-demo.tsx +60 -0
- package/src/react/examples/split.tsx +41 -0
- package/src/react/examples/video-grid.tsx +46 -0
- package/src/react/index.ts +43 -0
- package/src/react/layouts/grid.tsx +28 -0
- package/src/react/layouts/index.ts +2 -0
- package/src/react/layouts/split.tsx +20 -0
- package/src/react/react.test.ts +309 -0
- package/src/react/render.ts +21 -0
- package/src/react/renderers/animate.ts +59 -0
- package/src/react/renderers/captions.ts +297 -0
- package/src/react/renderers/clip.ts +248 -0
- package/src/react/renderers/context.ts +17 -0
- package/src/react/renderers/image.ts +109 -0
- package/src/react/renderers/index.ts +22 -0
- package/src/react/renderers/music.ts +60 -0
- package/src/react/renderers/packshot.ts +84 -0
- package/src/react/renderers/progress.ts +173 -0
- package/src/react/renderers/render.ts +243 -0
- package/src/react/renderers/slider.ts +69 -0
- package/src/react/renderers/speech.ts +53 -0
- package/src/react/renderers/split.ts +91 -0
- package/src/react/renderers/subtitle.ts +16 -0
- package/src/react/renderers/swipe.ts +75 -0
- package/src/react/renderers/title.ts +17 -0
- package/src/react/renderers/utils.ts +124 -0
- package/src/react/renderers/video.ts +127 -0
- package/src/react/runtime/jsx-dev-runtime.ts +43 -0
- package/src/react/runtime/jsx-runtime.ts +35 -0
- package/src/react/types.ts +232 -0
- package/src/studio/index.ts +26 -0
- package/src/studio/scanner.ts +102 -0
- package/src/studio/server.ts +554 -0
- package/src/studio/stages.ts +251 -0
- package/src/studio/step-renderer.ts +279 -0
- package/src/studio/types.ts +60 -0
- package/src/studio/ui/cache.html +303 -0
- package/src/studio/ui/index.html +1820 -0
- package/src/tests/all.test.ts +509 -0
- package/src/tests/index.ts +33 -0
- package/src/tests/unit.test.ts +403 -0
- package/tsconfig.cli.json +8 -0
- package/tsconfig.json +21 -3
- package/TEST_RESULTS.md +0 -122
- package/action/captions/SKILL.md +0 -170
- package/action/captions/index.ts +0 -227
- package/action/edit/SKILL.md +0 -235
- package/action/edit/index.ts +0 -493
- package/action/image/SKILL.md +0 -140
- package/action/image/index.ts +0 -112
- package/action/sync/SKILL.md +0 -136
- package/action/sync/index.ts +0 -187
- package/action/transcribe/SKILL.md +0 -179
- package/action/video/SKILL.md +0 -116
- package/action/video/index.ts +0 -135
- package/action/voice/SKILL.md +0 -125
- package/action/voice/index.ts +0 -201
- package/index.ts +0 -38
- package/lib/README.md +0 -144
- package/lib/ai-sdk/fal.ts +0 -106
- package/lib/ai-sdk/replicate.ts +0 -107
- package/lib/elevenlabs.ts +0 -382
- package/lib/fal.ts +0 -478
- package/lib/ffmpeg.ts +0 -467
- package/lib/fireworks.ts +0 -235
- package/lib/groq.ts +0 -246
- package/lib/higgsfield.ts +0 -176
- package/lib/remotion/SKILL.md +0 -823
- package/lib/remotion/cli.ts +0 -115
- package/lib/remotion/functions.ts +0 -283
- package/lib/remotion/index.ts +0 -19
- package/lib/remotion/templates.ts +0 -73
- package/lib/replicate.ts +0 -304
- package/output.txt +0 -1
- package/test-import.ts +0 -7
- package/test-services.ts +0 -97
- package/utilities/s3.ts +0 -147
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { editly } from "../../ai-sdk/providers/editly";
|
|
2
|
+
import type { Clip, Layer } from "../../ai-sdk/providers/editly/types";
|
|
3
|
+
import type { SplitProps, VargElement } from "../types";
|
|
4
|
+
import type { RenderContext } from "./context";
|
|
5
|
+
import { renderImage } from "./image";
|
|
6
|
+
import { renderVideo } from "./video";
|
|
7
|
+
|
|
8
|
+
export async function renderSplit(
|
|
9
|
+
element: VargElement<"split">,
|
|
10
|
+
ctx: RenderContext,
|
|
11
|
+
): Promise<string> {
|
|
12
|
+
const props = element.props as SplitProps;
|
|
13
|
+
const direction = props.direction ?? "horizontal";
|
|
14
|
+
|
|
15
|
+
const childPaths: string[] = [];
|
|
16
|
+
|
|
17
|
+
for (const child of element.children) {
|
|
18
|
+
if (!child || typeof child !== "object" || !("type" in child)) continue;
|
|
19
|
+
const childElement = child as VargElement;
|
|
20
|
+
|
|
21
|
+
if (childElement.type === "image") {
|
|
22
|
+
const path = await renderImage(childElement as VargElement<"image">, ctx);
|
|
23
|
+
childPaths.push(path);
|
|
24
|
+
} else if (childElement.type === "video") {
|
|
25
|
+
const path = await renderVideo(childElement as VargElement<"video">, ctx);
|
|
26
|
+
childPaths.push(path);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (childPaths.length === 0) {
|
|
31
|
+
throw new Error("Split element requires at least one image or video child");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (childPaths.length === 1) {
|
|
35
|
+
return childPaths[0]!;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const numChildren = childPaths.length;
|
|
39
|
+
const cellWidth =
|
|
40
|
+
direction === "horizontal"
|
|
41
|
+
? Math.floor(ctx.width / numChildren)
|
|
42
|
+
: ctx.width;
|
|
43
|
+
const cellHeight =
|
|
44
|
+
direction === "vertical"
|
|
45
|
+
? Math.floor(ctx.height / numChildren)
|
|
46
|
+
: ctx.height;
|
|
47
|
+
|
|
48
|
+
const layers: Layer[] = childPaths.map((path, i) => {
|
|
49
|
+
const isVideo = path.endsWith(".mp4") || path.endsWith(".webm");
|
|
50
|
+
const left = direction === "horizontal" ? cellWidth * i : 0;
|
|
51
|
+
const top = direction === "vertical" ? cellHeight * i : 0;
|
|
52
|
+
|
|
53
|
+
if (isVideo) {
|
|
54
|
+
return {
|
|
55
|
+
type: "video" as const,
|
|
56
|
+
path,
|
|
57
|
+
left,
|
|
58
|
+
top,
|
|
59
|
+
width: cellWidth,
|
|
60
|
+
height: cellHeight,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
type: "image-overlay" as const,
|
|
65
|
+
path,
|
|
66
|
+
position: { x: left, y: top },
|
|
67
|
+
width: cellWidth,
|
|
68
|
+
height: cellHeight,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
layers.unshift({ type: "fill-color" as const, color: "#000000" });
|
|
73
|
+
|
|
74
|
+
const clip: Clip = {
|
|
75
|
+
layers,
|
|
76
|
+
duration: 5,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const outPath = `/tmp/varg-split-${Date.now()}.mp4`;
|
|
80
|
+
|
|
81
|
+
await editly({
|
|
82
|
+
outPath,
|
|
83
|
+
width: ctx.width,
|
|
84
|
+
height: ctx.height,
|
|
85
|
+
fps: ctx.fps,
|
|
86
|
+
clips: [clip],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
ctx.tempFiles.push(outPath);
|
|
90
|
+
return outPath;
|
|
91
|
+
}
|
|
@@ -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
|
+
}
|