vargai 0.4.0-alpha3 → 0.4.0-alpha5
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/package.json +1 -1
- package/src/cli/commands/index.ts +1 -0
- package/src/cli/commands/render.tsx +43 -9
- package/src/cli/commands/studio.tsx +47 -0
- package/src/cli/index.ts +5 -1
- package/src/react/renderers/clip.ts +13 -3
- package/src/react/types.ts +4 -0
- package/src/react/cli.ts +0 -52
package/package.json
CHANGED
|
@@ -3,4 +3,5 @@ export { helpCmd, showHelp } from "./help.tsx";
|
|
|
3
3
|
export { listCmd, showListHelp } from "./list.tsx";
|
|
4
4
|
export { renderCmd } from "./render.tsx";
|
|
5
5
|
export { runCmd, showRunHelp, showTargetHelp } from "./run.tsx";
|
|
6
|
+
export { studioCmd } from "./studio.tsx";
|
|
6
7
|
export { showWhichHelp, whichCmd } from "./which.tsx";
|
|
@@ -1,7 +1,44 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
1
3
|
import { defineCommand } from "citty";
|
|
2
4
|
import { render } from "../../react/render";
|
|
3
5
|
import type { RenderMode, VargElement } from "../../react/types";
|
|
4
6
|
|
|
7
|
+
const AUTO_IMPORTS = `import { Animate, Captions, Clip, Image, Music, Overlay, Packshot, Render, Slider, Speech, Split, Subtitle, Swipe, TalkingHead, Title, Video, Grid, SplitLayout } from "vargai/react";
|
|
8
|
+
import { fal, elevenlabs, replicate } from "vargai/ai";
|
|
9
|
+
`;
|
|
10
|
+
|
|
11
|
+
async function loadComponent(filePath: string): Promise<VargElement> {
|
|
12
|
+
const resolvedPath = resolve(filePath);
|
|
13
|
+
const source = await Bun.file(resolvedPath).text();
|
|
14
|
+
|
|
15
|
+
const hasImports =
|
|
16
|
+
source.includes("from 'vargai") ||
|
|
17
|
+
source.includes('from "vargai') ||
|
|
18
|
+
source.includes("from '@vargai") ||
|
|
19
|
+
source.includes('from "@vargai');
|
|
20
|
+
|
|
21
|
+
if (hasImports) {
|
|
22
|
+
const mod = await import(resolvedPath);
|
|
23
|
+
return mod.default;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const tmpDir = ".cache/varg-render";
|
|
27
|
+
if (!existsSync(tmpDir)) {
|
|
28
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const tmpFile = `${tmpDir}/${Date.now()}.tsx`;
|
|
32
|
+
await Bun.write(tmpFile, AUTO_IMPORTS + source);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const mod = await import(resolve(tmpFile));
|
|
36
|
+
return mod.default;
|
|
37
|
+
} finally {
|
|
38
|
+
(await Bun.file(tmpFile).exists()) && (await Bun.write(tmpFile, ""));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
5
42
|
export const renderCmd = defineCommand({
|
|
6
43
|
meta: {
|
|
7
44
|
name: "render",
|
|
@@ -49,21 +86,18 @@ export const renderCmd = defineCommand({
|
|
|
49
86
|
process.exit(1);
|
|
50
87
|
}
|
|
51
88
|
|
|
52
|
-
const
|
|
53
|
-
const mod = await import(resolvedPath);
|
|
54
|
-
const component: VargElement = mod.default;
|
|
89
|
+
const component = await loadComponent(file);
|
|
55
90
|
|
|
56
91
|
if (!component || component.type !== "render") {
|
|
57
92
|
console.error("error: default export must be a <Render> element");
|
|
58
93
|
process.exit(1);
|
|
59
94
|
}
|
|
60
95
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
.pop()}.mp4`;
|
|
96
|
+
const basename = file
|
|
97
|
+
.replace(/\.tsx?$/, "")
|
|
98
|
+
.split("/")
|
|
99
|
+
.pop();
|
|
100
|
+
const outputPath = args.output ?? `output/${basename}.mp4`;
|
|
67
101
|
|
|
68
102
|
const mode: RenderMode = args.strict
|
|
69
103
|
? "strict"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { defineCommand } from "citty";
|
|
3
|
+
import { createStudioServer } from "../../studio/server";
|
|
4
|
+
|
|
5
|
+
export const studioCmd = defineCommand({
|
|
6
|
+
meta: {
|
|
7
|
+
name: "studio",
|
|
8
|
+
description: "launch varg studio - visual editor for workflows",
|
|
9
|
+
},
|
|
10
|
+
args: {
|
|
11
|
+
file: {
|
|
12
|
+
type: "positional",
|
|
13
|
+
description: "initial file to open",
|
|
14
|
+
required: false,
|
|
15
|
+
},
|
|
16
|
+
cache: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "cache directory",
|
|
19
|
+
default: ".cache/ai",
|
|
20
|
+
},
|
|
21
|
+
port: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "port to run on",
|
|
24
|
+
default: "8282",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
run: async ({ args }) => {
|
|
28
|
+
const initialFile = args.file ? resolve(args.file) : undefined;
|
|
29
|
+
const cacheDir = args.cache;
|
|
30
|
+
const port = Number.parseInt(args.port, 10);
|
|
31
|
+
|
|
32
|
+
console.log("varg studio starting...");
|
|
33
|
+
console.log(`cache folder: ${cacheDir}`);
|
|
34
|
+
if (initialFile) {
|
|
35
|
+
console.log(`initial file: ${initialFile}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const server = createStudioServer({ cacheDir, initialFile, port });
|
|
39
|
+
|
|
40
|
+
console.log(`\nopen http://localhost:${server.port}`);
|
|
41
|
+
console.log(" /editor - code editor");
|
|
42
|
+
console.log(" /cache - cache viewer");
|
|
43
|
+
|
|
44
|
+
// Keep process alive
|
|
45
|
+
await new Promise(() => {});
|
|
46
|
+
},
|
|
47
|
+
});
|
package/src/cli/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
showRunHelp,
|
|
24
24
|
showTargetHelp,
|
|
25
25
|
showWhichHelp,
|
|
26
|
+
studioCmd,
|
|
26
27
|
whichCmd,
|
|
27
28
|
} from "./commands";
|
|
28
29
|
|
|
@@ -92,15 +93,18 @@ if (hasHelp) {
|
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
const pkg = await import("../../package.json");
|
|
97
|
+
|
|
95
98
|
const main = defineCommand({
|
|
96
99
|
meta: {
|
|
97
100
|
name: "varg",
|
|
98
|
-
version:
|
|
101
|
+
version: pkg.version,
|
|
99
102
|
description: "ai video infrastructure from your terminal",
|
|
100
103
|
},
|
|
101
104
|
subCommands: {
|
|
102
105
|
run: runCmd,
|
|
103
106
|
render: renderCmd,
|
|
107
|
+
studio: studioCmd,
|
|
104
108
|
list: listCmd,
|
|
105
109
|
ls: listCmd,
|
|
106
110
|
find: findCmd,
|
|
@@ -32,9 +32,15 @@ type PendingLayer =
|
|
|
32
32
|
| { type: "sync"; layer: Layer }
|
|
33
33
|
| { type: "async"; promise: Promise<Layer> };
|
|
34
34
|
|
|
35
|
+
interface ClipLayerOptions {
|
|
36
|
+
cutFrom?: number;
|
|
37
|
+
cutTo?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
35
40
|
async function renderClipLayers(
|
|
36
41
|
children: VargNode[],
|
|
37
42
|
ctx: RenderContext,
|
|
43
|
+
clipOptions?: ClipLayerOptions,
|
|
38
44
|
): Promise<Layer[]> {
|
|
39
45
|
const pending: PendingLayer[] = [];
|
|
40
46
|
|
|
@@ -86,8 +92,9 @@ async function renderClipLayers(
|
|
|
86
92
|
type: "video",
|
|
87
93
|
path,
|
|
88
94
|
resizeMode: props.resize,
|
|
89
|
-
cutFrom
|
|
90
|
-
|
|
95
|
+
// Video-level cutFrom/cutTo take precedence over clip-level
|
|
96
|
+
cutFrom: props.cutFrom ?? clipOptions?.cutFrom,
|
|
97
|
+
cutTo: props.cutTo ?? clipOptions?.cutTo,
|
|
91
98
|
mixVolume: props.keepAudio ? (props.volume ?? 1) : 0,
|
|
92
99
|
left: props.left,
|
|
93
100
|
top: props.top,
|
|
@@ -220,7 +227,10 @@ export async function renderClip(
|
|
|
220
227
|
ctx: RenderContext,
|
|
221
228
|
): Promise<Clip> {
|
|
222
229
|
const props = element.props as ClipProps;
|
|
223
|
-
const layers = await renderClipLayers(element.children, ctx
|
|
230
|
+
const layers = await renderClipLayers(element.children, ctx, {
|
|
231
|
+
cutFrom: props.cutFrom,
|
|
232
|
+
cutTo: props.cutTo,
|
|
233
|
+
});
|
|
224
234
|
|
|
225
235
|
const isOverlayVideo = (l: Layer) =>
|
|
226
236
|
l.type === "video" &&
|
package/src/react/types.ts
CHANGED
|
@@ -75,6 +75,10 @@ export interface RenderProps extends BaseProps {
|
|
|
75
75
|
export interface ClipProps extends BaseProps {
|
|
76
76
|
duration?: number | "auto";
|
|
77
77
|
transition?: TransitionOptions;
|
|
78
|
+
/** Start trim point in seconds (e.g., 1 to start from 1 second) */
|
|
79
|
+
cutFrom?: number;
|
|
80
|
+
/** End trim point in seconds (e.g., 3 to end at 3 seconds) */
|
|
81
|
+
cutTo?: number;
|
|
78
82
|
children?: VargNode;
|
|
79
83
|
}
|
|
80
84
|
|
package/src/react/cli.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
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
|
-
}
|