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.
- 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 +43 -10
- 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} +58 -68
- 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 -169
- package/action/edit/SKILL.md +0 -235
- package/action/edit/index.ts +0 -437
- package/action/image/SKILL.md +0 -140
- package/action/image/index.ts +0 -105
- package/action/sync/SKILL.md +0 -136
- package/action/sync/index.ts +0 -145
- package/action/transcribe/SKILL.md +0 -179
- package/action/video/SKILL.md +0 -116
- package/action/video/index.ts +0 -125
- package/action/voice/SKILL.md +0 -125
- package/action/voice/index.ts +0 -136
- package/cli/commands/find.ts +0 -58
- package/cli/commands/help.ts +0 -70
- package/cli/commands/list.ts +0 -49
- package/cli/commands/run.ts +0 -237
- package/cli/commands/which.ts +0 -66
- package/cli/discover.ts +0 -66
- package/cli/index.ts +0 -33
- package/cli/runner.ts +0 -65
- package/cli/types.ts +0 -49
- package/cli/ui.ts +0 -185
- package/index.ts +0 -75
- 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 -467
- 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,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replicate provider for video and image generation
|
|
3
|
+
* Supports Minimax, Kling, Luma, Flux, and other models
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import Replicate from "replicate";
|
|
7
|
+
import type { JobStatusUpdate, ProviderConfig } from "../core/schema/types";
|
|
8
|
+
import { BaseProvider } from "./base";
|
|
9
|
+
|
|
10
|
+
export class ReplicateProvider extends BaseProvider {
|
|
11
|
+
readonly name = "replicate";
|
|
12
|
+
private client: Replicate;
|
|
13
|
+
|
|
14
|
+
constructor(config?: ProviderConfig) {
|
|
15
|
+
super(config);
|
|
16
|
+
this.client = new Replicate({
|
|
17
|
+
auth: config?.apiKey || process.env.REPLICATE_API_TOKEN || "",
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async submit(
|
|
22
|
+
model: string,
|
|
23
|
+
inputs: Record<string, unknown>,
|
|
24
|
+
_config?: ProviderConfig,
|
|
25
|
+
): Promise<string> {
|
|
26
|
+
// Transform inputs for provider-specific field names
|
|
27
|
+
const transformedInputs = this.transformInputs(model, inputs);
|
|
28
|
+
|
|
29
|
+
const prediction = await this.client.predictions.create({
|
|
30
|
+
model: model as `${string}/${string}`,
|
|
31
|
+
input: transformedInputs,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
console.log(`[replicate] job submitted: ${prediction.id}`);
|
|
35
|
+
return prediction.id;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Transform inputs for provider-specific field names
|
|
40
|
+
*/
|
|
41
|
+
private transformInputs(
|
|
42
|
+
model: string,
|
|
43
|
+
inputs: Record<string, unknown>,
|
|
44
|
+
): Record<string, unknown> {
|
|
45
|
+
// Nano Banana Pro: Replicate uses 'image_input' instead of 'image_urls'
|
|
46
|
+
if (model === "google/nano-banana-pro" && inputs.image_urls) {
|
|
47
|
+
const { image_urls, ...rest } = inputs;
|
|
48
|
+
return {
|
|
49
|
+
...rest,
|
|
50
|
+
image_input: image_urls,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return inputs;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async getStatus(jobId: string): Promise<JobStatusUpdate> {
|
|
57
|
+
const prediction = await this.client.predictions.get(jobId);
|
|
58
|
+
|
|
59
|
+
const statusMap: Record<string, JobStatusUpdate["status"]> = {
|
|
60
|
+
starting: "queued",
|
|
61
|
+
processing: "processing",
|
|
62
|
+
succeeded: "completed",
|
|
63
|
+
failed: "failed",
|
|
64
|
+
canceled: "cancelled",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
status: statusMap[prediction.status] ?? "processing",
|
|
69
|
+
output: prediction.output,
|
|
70
|
+
error:
|
|
71
|
+
typeof prediction.error === "string" ? prediction.error : undefined,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async getResult(jobId: string): Promise<unknown> {
|
|
76
|
+
const prediction = await this.client.predictions.get(jobId);
|
|
77
|
+
return prediction.output;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
override async cancel(jobId: string): Promise<void> {
|
|
81
|
+
await this.client.predictions.cancel(jobId);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// High-level convenience methods
|
|
86
|
+
// ============================================================================
|
|
87
|
+
|
|
88
|
+
async runModel(model: string, input: Record<string, unknown>) {
|
|
89
|
+
console.log(`[replicate] running ${model}...`);
|
|
90
|
+
|
|
91
|
+
const output = await this.client.run(model as `${string}/${string}`, {
|
|
92
|
+
input,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
console.log(`[replicate] completed`);
|
|
96
|
+
return output;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async runVideo(options: { model: string; input: Record<string, unknown> }) {
|
|
100
|
+
return this.runModel(options.model, options.input);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async runImage(options: { model: string; input: Record<string, unknown> }) {
|
|
104
|
+
return this.runModel(options.model, options.input);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async listPredictions() {
|
|
108
|
+
return this.client.predictions.list();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async getPrediction(id: string) {
|
|
112
|
+
return this.client.predictions.get(id);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Popular models registry
|
|
117
|
+
export const MODELS = {
|
|
118
|
+
VIDEO: {
|
|
119
|
+
MINIMAX: "minimax/video-01",
|
|
120
|
+
KLING: "fofr/kling-v1.5",
|
|
121
|
+
LUMA: "fofr/ltx-video",
|
|
122
|
+
RUNWAY_GEN3: "replicate/runway-gen3-turbo",
|
|
123
|
+
WAN_2_5: "wan-video/wan-2.5-i2v",
|
|
124
|
+
},
|
|
125
|
+
IMAGE: {
|
|
126
|
+
FLUX_PRO: "black-forest-labs/flux-1.1-pro",
|
|
127
|
+
FLUX_DEV: "black-forest-labs/flux-dev",
|
|
128
|
+
FLUX_SCHNELL: "black-forest-labs/flux-schnell",
|
|
129
|
+
STABLE_DIFFUSION: "stability-ai/sdxl",
|
|
130
|
+
NANO_BANANA_PRO: "google/nano-banana-pro",
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Export singleton instance
|
|
135
|
+
export const replicateProvider = new ReplicateProvider();
|
|
136
|
+
|
|
137
|
+
// Re-export convenience functions for backward compatibility
|
|
138
|
+
export const runModel = (model: string, input: Record<string, unknown>) =>
|
|
139
|
+
replicateProvider.runModel(model, input);
|
|
140
|
+
export const runVideo = (options: {
|
|
141
|
+
model: string;
|
|
142
|
+
input: Record<string, unknown>;
|
|
143
|
+
}) => replicateProvider.runVideo(options);
|
|
144
|
+
export const runImage = (options: {
|
|
145
|
+
model: string;
|
|
146
|
+
input: Record<string, unknown>;
|
|
147
|
+
}) => replicateProvider.runImage(options);
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage provider for Cloudflare R2 / S3 compatible storage
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
GetObjectCommand,
|
|
7
|
+
PutObjectCommand,
|
|
8
|
+
S3Client,
|
|
9
|
+
} from "@aws-sdk/client-s3";
|
|
10
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
11
|
+
import type { JobStatusUpdate, ProviderConfig } from "../core/schema/types";
|
|
12
|
+
import { BaseProvider } from "./base";
|
|
13
|
+
|
|
14
|
+
export interface StorageConfig extends ProviderConfig {
|
|
15
|
+
endpoint?: string;
|
|
16
|
+
accessKeyId?: string;
|
|
17
|
+
secretAccessKey?: string;
|
|
18
|
+
bucket?: string;
|
|
19
|
+
publicUrl?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class StorageProvider extends BaseProvider {
|
|
23
|
+
readonly name = "storage";
|
|
24
|
+
private client: S3Client;
|
|
25
|
+
private bucket: string;
|
|
26
|
+
private publicUrl: string;
|
|
27
|
+
|
|
28
|
+
constructor(config?: StorageConfig) {
|
|
29
|
+
super(config);
|
|
30
|
+
|
|
31
|
+
this.client = new S3Client({
|
|
32
|
+
region: "auto",
|
|
33
|
+
endpoint: config?.endpoint || process.env.CLOUDFLARE_R2_API_URL,
|
|
34
|
+
credentials: {
|
|
35
|
+
accessKeyId:
|
|
36
|
+
config?.accessKeyId || process.env.CLOUDFLARE_ACCESS_KEY_ID || "",
|
|
37
|
+
secretAccessKey:
|
|
38
|
+
config?.secretAccessKey || process.env.CLOUDFLARE_ACCESS_SECRET || "",
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
this.bucket = config?.bucket || process.env.CLOUDFLARE_R2_BUCKET || "m";
|
|
43
|
+
this.publicUrl = config?.publicUrl || "https://s3.varg.ai";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async submit(
|
|
47
|
+
_model: string,
|
|
48
|
+
_inputs: Record<string, unknown>,
|
|
49
|
+
_config?: ProviderConfig,
|
|
50
|
+
): Promise<string> {
|
|
51
|
+
const jobId = `storage_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
52
|
+
return jobId;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async getStatus(_jobId: string): Promise<JobStatusUpdate> {
|
|
56
|
+
return { status: "completed" };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async getResult(_jobId: string): Promise<unknown> {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// Storage Operations
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Upload a file from local path to storage
|
|
69
|
+
*/
|
|
70
|
+
async uploadLocalFile(filePath: string, objectKey: string): Promise<string> {
|
|
71
|
+
console.log(`[storage] uploading ${filePath} to ${objectKey}`);
|
|
72
|
+
|
|
73
|
+
const file = Bun.file(filePath);
|
|
74
|
+
const buffer = await file.arrayBuffer();
|
|
75
|
+
|
|
76
|
+
await this.client.send(
|
|
77
|
+
new PutObjectCommand({
|
|
78
|
+
Bucket: this.bucket,
|
|
79
|
+
Key: objectKey,
|
|
80
|
+
Body: new Uint8Array(buffer),
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return this.getPublicUrl(objectKey);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Upload content from a URL to storage
|
|
89
|
+
*/
|
|
90
|
+
async uploadFromUrl(url: string, objectKey: string): Promise<string> {
|
|
91
|
+
console.log(`[storage] uploading from URL to ${objectKey}`);
|
|
92
|
+
|
|
93
|
+
const response = await fetch(url);
|
|
94
|
+
const buffer = await response.arrayBuffer();
|
|
95
|
+
|
|
96
|
+
await this.client.send(
|
|
97
|
+
new PutObjectCommand({
|
|
98
|
+
Bucket: this.bucket,
|
|
99
|
+
Key: objectKey,
|
|
100
|
+
Body: new Uint8Array(buffer),
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return this.getPublicUrl(objectKey);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Upload raw buffer to storage
|
|
109
|
+
*/
|
|
110
|
+
async uploadBuffer(
|
|
111
|
+
buffer: ArrayBuffer | Uint8Array,
|
|
112
|
+
objectKey: string,
|
|
113
|
+
contentType?: string,
|
|
114
|
+
): Promise<string> {
|
|
115
|
+
console.log(`[storage] uploading buffer to ${objectKey}`);
|
|
116
|
+
|
|
117
|
+
await this.client.send(
|
|
118
|
+
new PutObjectCommand({
|
|
119
|
+
Bucket: this.bucket,
|
|
120
|
+
Key: objectKey,
|
|
121
|
+
Body: buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer),
|
|
122
|
+
ContentType: contentType,
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
return this.getPublicUrl(objectKey);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Generate a presigned URL for temporary access
|
|
131
|
+
*/
|
|
132
|
+
async generatePresignedUrl(
|
|
133
|
+
objectKey: string,
|
|
134
|
+
expiresIn = 3600,
|
|
135
|
+
): Promise<string> {
|
|
136
|
+
const command = new GetObjectCommand({
|
|
137
|
+
Bucket: this.bucket,
|
|
138
|
+
Key: objectKey,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return getSignedUrl(this.client, command, { expiresIn });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get the public URL for an object
|
|
146
|
+
*/
|
|
147
|
+
getPublicUrl(objectKey: string): string {
|
|
148
|
+
const endpoint = process.env.CLOUDFLARE_R2_API_URL || "";
|
|
149
|
+
|
|
150
|
+
if (endpoint.includes("localhost")) {
|
|
151
|
+
return `${endpoint}/${objectKey}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return `${this.publicUrl}/${objectKey}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Download a file from storage
|
|
159
|
+
*/
|
|
160
|
+
async downloadToFile(objectKey: string, outputPath: string): Promise<string> {
|
|
161
|
+
console.log(`[storage] downloading ${objectKey} to ${outputPath}`);
|
|
162
|
+
|
|
163
|
+
const command = new GetObjectCommand({
|
|
164
|
+
Bucket: this.bucket,
|
|
165
|
+
Key: objectKey,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const response = await this.client.send(command);
|
|
169
|
+
const body = response.Body;
|
|
170
|
+
|
|
171
|
+
if (!body) {
|
|
172
|
+
throw new Error("Empty response body");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Convert stream to buffer
|
|
176
|
+
const chunks: Uint8Array[] = [];
|
|
177
|
+
// @ts-expect-error - body is a readable stream
|
|
178
|
+
for await (const chunk of body) {
|
|
179
|
+
chunks.push(chunk);
|
|
180
|
+
}
|
|
181
|
+
const buffer = Buffer.concat(chunks);
|
|
182
|
+
|
|
183
|
+
await Bun.write(outputPath, buffer);
|
|
184
|
+
console.log(`[storage] saved to ${outputPath}`);
|
|
185
|
+
|
|
186
|
+
return outputPath;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Export singleton instance
|
|
191
|
+
export const storageProvider = new StorageProvider();
|
|
192
|
+
|
|
193
|
+
// Re-export convenience functions for backward compatibility
|
|
194
|
+
export const uploadFile = (filePath: string, objectKey: string) =>
|
|
195
|
+
storageProvider.uploadLocalFile(filePath, objectKey);
|
|
196
|
+
export const uploadFromUrl = (url: string, objectKey: string) =>
|
|
197
|
+
storageProvider.uploadFromUrl(url, objectKey);
|
|
198
|
+
export const uploadBuffer = (
|
|
199
|
+
buffer: ArrayBuffer | Uint8Array,
|
|
200
|
+
objectKey: string,
|
|
201
|
+
contentType?: string,
|
|
202
|
+
) => storageProvider.uploadBuffer(buffer, objectKey, contentType);
|
|
203
|
+
export const generatePresignedUrl = (objectKey: string, expiresIn?: number) =>
|
|
204
|
+
storageProvider.generatePresignedUrl(objectKey, expiresIn);
|
|
205
|
+
export const getPublicUrl = (objectKey: string) =>
|
|
206
|
+
storageProvider.getPublicUrl(objectKey);
|
package/src/react/cli.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { render } from "./render";
|
|
5
|
+
import type { VargElement } from "./types";
|
|
6
|
+
|
|
7
|
+
const { values, positionals } = parseArgs({
|
|
8
|
+
args: Bun.argv.slice(2),
|
|
9
|
+
options: {
|
|
10
|
+
output: { type: "string", short: "o" },
|
|
11
|
+
cache: { type: "string", short: "c", default: ".cache/ai" },
|
|
12
|
+
quiet: { type: "boolean", short: "q", default: false },
|
|
13
|
+
},
|
|
14
|
+
allowPositionals: true,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const [file] = positionals;
|
|
18
|
+
|
|
19
|
+
if (!file) {
|
|
20
|
+
console.error("usage: bun react/cli.ts <component.tsx> [-o output.mp4]");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const resolvedPath = Bun.resolveSync(file, process.cwd());
|
|
25
|
+
const mod = await import(resolvedPath);
|
|
26
|
+
const component: VargElement = mod.default;
|
|
27
|
+
|
|
28
|
+
if (!component || component.type !== "render") {
|
|
29
|
+
console.error("error: default export must be a <Render> element");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const outputPath =
|
|
34
|
+
values.output ??
|
|
35
|
+
`output/${file
|
|
36
|
+
.replace(/\.tsx?$/, "")
|
|
37
|
+
.split("/")
|
|
38
|
+
.pop()}.mp4`;
|
|
39
|
+
|
|
40
|
+
if (!values.quiet) {
|
|
41
|
+
console.log(`rendering ${file} → ${outputPath}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const buffer = await render(component, {
|
|
45
|
+
output: outputPath,
|
|
46
|
+
cache: values.cache,
|
|
47
|
+
quiet: values.quiet,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (!values.quiet) {
|
|
51
|
+
console.log(`done! ${buffer.byteLength} bytes → ${outputPath}`);
|
|
52
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnimateProps,
|
|
3
|
+
CaptionsProps,
|
|
4
|
+
ClipProps,
|
|
5
|
+
ImageProps,
|
|
6
|
+
MusicProps,
|
|
7
|
+
OverlayProps,
|
|
8
|
+
PackshotProps,
|
|
9
|
+
RenderProps,
|
|
10
|
+
SliderProps,
|
|
11
|
+
SpeechProps,
|
|
12
|
+
SplitProps,
|
|
13
|
+
SubtitleProps,
|
|
14
|
+
SwipeProps,
|
|
15
|
+
TalkingHeadProps,
|
|
16
|
+
TitleProps,
|
|
17
|
+
VargElement,
|
|
18
|
+
VargNode,
|
|
19
|
+
VideoProps,
|
|
20
|
+
} from "./types";
|
|
21
|
+
|
|
22
|
+
function normalizeChildren(children: unknown): VargNode[] {
|
|
23
|
+
if (children === null || children === undefined) return [];
|
|
24
|
+
if (Array.isArray(children))
|
|
25
|
+
return children.flat().flatMap(normalizeChildren);
|
|
26
|
+
return [children as VargNode];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function createElement<T extends VargElement["type"]>(
|
|
30
|
+
type: T,
|
|
31
|
+
props: Record<string, unknown>,
|
|
32
|
+
children: unknown,
|
|
33
|
+
): VargElement<T> {
|
|
34
|
+
const { children: _, ...restProps } = props;
|
|
35
|
+
return {
|
|
36
|
+
type,
|
|
37
|
+
props: restProps,
|
|
38
|
+
children: normalizeChildren(children ?? props.children),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function Render(props: RenderProps): VargElement<"render"> {
|
|
43
|
+
return createElement(
|
|
44
|
+
"render",
|
|
45
|
+
props as Record<string, unknown>,
|
|
46
|
+
props.children,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function Clip(props: ClipProps): VargElement<"clip"> {
|
|
51
|
+
return createElement(
|
|
52
|
+
"clip",
|
|
53
|
+
props as Record<string, unknown>,
|
|
54
|
+
props.children,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function Overlay(props: OverlayProps): VargElement<"overlay"> {
|
|
59
|
+
return createElement(
|
|
60
|
+
"overlay",
|
|
61
|
+
props as Record<string, unknown>,
|
|
62
|
+
props.children,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function Image(props: ImageProps): VargElement<"image"> {
|
|
67
|
+
return createElement("image", props as Record<string, unknown>, undefined);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function Video(props: VideoProps): VargElement<"video"> {
|
|
71
|
+
return createElement("video", props as Record<string, unknown>, undefined);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function Animate(props: AnimateProps): VargElement<"animate"> {
|
|
75
|
+
return createElement("animate", props as Record<string, unknown>, undefined);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function Speech(props: SpeechProps): VargElement<"speech"> {
|
|
79
|
+
return createElement(
|
|
80
|
+
"speech",
|
|
81
|
+
props as Record<string, unknown>,
|
|
82
|
+
props.children,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function TalkingHead(
|
|
87
|
+
props: TalkingHeadProps,
|
|
88
|
+
): VargElement<"talking-head"> {
|
|
89
|
+
return createElement(
|
|
90
|
+
"talking-head",
|
|
91
|
+
props as Record<string, unknown>,
|
|
92
|
+
props.children,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function Title(props: TitleProps): VargElement<"title"> {
|
|
97
|
+
return createElement(
|
|
98
|
+
"title",
|
|
99
|
+
props as Record<string, unknown>,
|
|
100
|
+
props.children,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function Subtitle(props: SubtitleProps): VargElement<"subtitle"> {
|
|
105
|
+
return createElement(
|
|
106
|
+
"subtitle",
|
|
107
|
+
props as Record<string, unknown>,
|
|
108
|
+
props.children,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function Music(props: MusicProps): VargElement<"music"> {
|
|
113
|
+
return createElement("music", props as Record<string, unknown>, undefined);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function Captions(props: CaptionsProps): VargElement<"captions"> {
|
|
117
|
+
return createElement("captions", props as Record<string, unknown>, undefined);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function Split(props: SplitProps): VargElement<"split"> {
|
|
121
|
+
return createElement(
|
|
122
|
+
"split",
|
|
123
|
+
props as Record<string, unknown>,
|
|
124
|
+
props.children,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function Slider(props: SliderProps): VargElement<"slider"> {
|
|
129
|
+
return createElement(
|
|
130
|
+
"slider",
|
|
131
|
+
props as Record<string, unknown>,
|
|
132
|
+
props.children,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function Swipe(props: SwipeProps): VargElement<"swipe"> {
|
|
137
|
+
return createElement(
|
|
138
|
+
"swipe",
|
|
139
|
+
props as Record<string, unknown>,
|
|
140
|
+
props.children,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function Packshot(props: PackshotProps): VargElement<"packshot"> {
|
|
145
|
+
return createElement("packshot", props as Record<string, unknown>, undefined);
|
|
146
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { elevenlabs } from "../../ai-sdk/providers/elevenlabs-provider";
|
|
2
|
+
import { fal } from "../../ai-sdk/providers/fal-provider";
|
|
3
|
+
import { Animate, Clip, Image, Render, Speech, Title } from "..";
|
|
4
|
+
|
|
5
|
+
// Non-linear tree: multiple clips with independent branches
|
|
6
|
+
// Clip 1: TalkingHead (Image -> Animate + Speech)
|
|
7
|
+
// Clip 2: Split comparison (2 independent Images)
|
|
8
|
+
// Clip 3: Product shot with music
|
|
9
|
+
|
|
10
|
+
const character = Image({
|
|
11
|
+
prompt:
|
|
12
|
+
"friendly tech reviewer, young man with glasses, studio lighting, professional headshot",
|
|
13
|
+
model: fal.imageModel("flux-schnell"),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const _productBefore = Image({
|
|
17
|
+
prompt:
|
|
18
|
+
"old smartphone, cracked screen, slow, outdated design, on white background",
|
|
19
|
+
model: fal.imageModel("flux-schnell"),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const _productAfter = Image({
|
|
23
|
+
prompt:
|
|
24
|
+
"sleek new smartphone, edge-to-edge display, premium design, on white background",
|
|
25
|
+
model: fal.imageModel("flux-schnell"),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const packshot = Image({
|
|
29
|
+
prompt:
|
|
30
|
+
"smartphone floating with gradient background, product photography, premium feel",
|
|
31
|
+
model: fal.imageModel("flux-schnell"),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export default (
|
|
35
|
+
<Render width={1080} height={1920}>
|
|
36
|
+
{/* Clip 1: Talking head intro */}
|
|
37
|
+
<Clip duration={5}>
|
|
38
|
+
<Animate
|
|
39
|
+
image={character}
|
|
40
|
+
model={fal.videoModel("wan-2.5")}
|
|
41
|
+
motion="talking naturally, slight head movements, friendly expression"
|
|
42
|
+
/>
|
|
43
|
+
<Speech voice="adam" model={elevenlabs.speechModel("turbo")}>
|
|
44
|
+
Hey everyone! Today we're looking at the biggest smartphone upgrade of
|
|
45
|
+
the year.
|
|
46
|
+
</Speech>
|
|
47
|
+
</Clip>
|
|
48
|
+
|
|
49
|
+
{/* Clip 2: Before/after comparison - branches into 2 images */}
|
|
50
|
+
<Clip duration={4} transition={{ name: "fade", duration: 0.5 }}>
|
|
51
|
+
<Image
|
|
52
|
+
prompt="split screen comparison layout"
|
|
53
|
+
model={fal.imageModel("flux-schnell")}
|
|
54
|
+
/>
|
|
55
|
+
<Title position="top">Before vs After</Title>
|
|
56
|
+
</Clip>
|
|
57
|
+
|
|
58
|
+
{/* Clip 3: Product packshot */}
|
|
59
|
+
<Clip duration={3} transition={{ name: "fade", duration: 0.5 }}>
|
|
60
|
+
{packshot}
|
|
61
|
+
<Title position="bottom" color="#ffffff">
|
|
62
|
+
Available Now
|
|
63
|
+
</Title>
|
|
64
|
+
</Clip>
|
|
65
|
+
</Render>
|
|
66
|
+
);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { elevenlabs } from "../../ai-sdk/providers/elevenlabs-provider";
|
|
2
|
+
import { Captions, Clip, Image, Render, render, Speech } from "..";
|
|
3
|
+
|
|
4
|
+
async function main() {
|
|
5
|
+
const speech = Speech({
|
|
6
|
+
model: elevenlabs.speechModel("eleven_multilingual_v2"),
|
|
7
|
+
voice: "adam",
|
|
8
|
+
children:
|
|
9
|
+
"Hello world! This is a test of the captions system with word level timestamps.",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const video = (
|
|
13
|
+
<Render width={1080} height={1920}>
|
|
14
|
+
<Clip duration={5}>
|
|
15
|
+
<Image src="media/cyberpunk-street.png" resize="contain" />
|
|
16
|
+
</Clip>
|
|
17
|
+
<Clip duration={5}>
|
|
18
|
+
<Image src="media/cyberpunk-street.png" resize="cover" />
|
|
19
|
+
</Clip>
|
|
20
|
+
<Clip duration={5}>
|
|
21
|
+
<Image src="media/cyberpunk-street.png" />
|
|
22
|
+
</Clip>
|
|
23
|
+
<Captions src={speech} style="tiktok" />
|
|
24
|
+
</Render>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
console.log("rendering captions demo with speech transcription...\n");
|
|
28
|
+
|
|
29
|
+
await render(video, {
|
|
30
|
+
output: "output/captions-demo.mp4",
|
|
31
|
+
cache: ".cache/ai",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
console.log("\ndone! check output/captions-demo.mp4");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
main().catch(console.error);
|