@vibeo/cli 0.1.1 → 0.2.0
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/dist/commands/create.d.ts +6 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +232 -130
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/list.d.ts +9 -5
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +2 -77
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/preview.d.ts +1 -4
- package/dist/commands/preview.d.ts.map +1 -1
- package/dist/commands/preview.js +1 -57
- package/dist/commands/preview.js.map +1 -1
- package/dist/commands/render.d.ts +17 -4
- package/dist/commands/render.d.ts.map +1 -1
- package/dist/commands/render.js +22 -154
- package/dist/commands/render.js.map +1 -1
- package/dist/index.js +103 -57
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/commands/create.ts +228 -134
- package/src/commands/list.ts +2 -98
- package/src/commands/preview.ts +1 -66
- package/src/commands/render.ts +26 -149
- package/src/index.ts +104 -57
package/src/commands/create.ts
CHANGED
|
@@ -1,145 +1,261 @@
|
|
|
1
|
-
import { resolve, join
|
|
2
|
-
import {
|
|
1
|
+
import { resolve, join } from "node:path";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Embedded templates
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
const TEMPLATE_BASIC = `import React from "react";
|
|
10
|
+
import {
|
|
11
|
+
Composition, Sequence, VibeoRoot,
|
|
12
|
+
useCurrentFrame, useVideoConfig, interpolate, easeInOut,
|
|
13
|
+
} from "@vibeo/core";
|
|
14
|
+
|
|
15
|
+
function TitleScene() {
|
|
16
|
+
const frame = useCurrentFrame();
|
|
17
|
+
const { width, height } = useVideoConfig();
|
|
18
|
+
const opacity = interpolate(frame, [0, 30], [0, 1], { extrapolateRight: "clamp" });
|
|
19
|
+
const y = interpolate(frame, [0, 30], [40, 0], { easing: easeInOut, extrapolateRight: "clamp" });
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div style={{ width, height, display: "flex", justifyContent: "center", alignItems: "center", background: "linear-gradient(135deg, #0f0c29, #302b63, #24243e)" }}>
|
|
23
|
+
<h1 style={{ color: "white", fontSize: 72, fontFamily: "sans-serif", opacity, transform: \`translateY(\${y}px)\` }}>Hello, Vibeo!</h1>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ContentScene() {
|
|
29
|
+
const frame = useCurrentFrame();
|
|
30
|
+
const { width, height, fps } = useVideoConfig();
|
|
31
|
+
const seconds = (frame / fps).toFixed(1);
|
|
32
|
+
const scale = interpolate(frame, [0, 20], [0.8, 1], { easing: easeInOut, extrapolateRight: "clamp" });
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div style={{ width, height, display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center", background: "#24243e" }}>
|
|
36
|
+
<div style={{ transform: \`scale(\${scale})\`, color: "white", fontSize: 48, fontFamily: "sans-serif", textAlign: "center" }}>
|
|
37
|
+
<p>Scene 2</p>
|
|
38
|
+
<p style={{ fontSize: 32, opacity: 0.7 }}>{seconds}s elapsed</p>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function MyVideo() {
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
<Sequence from={0} durationInFrames={75} name="Title"><TitleScene /></Sequence>
|
|
48
|
+
<Sequence from={75} durationInFrames={75} name="Content"><ContentScene /></Sequence>
|
|
49
|
+
</>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function Root() {
|
|
54
|
+
return (
|
|
55
|
+
<VibeoRoot>
|
|
56
|
+
<Composition id="BasicComposition" component={MyVideo} width={1920} height={1080} fps={30} durationInFrames={150} />
|
|
57
|
+
</VibeoRoot>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
const TEMPLATE_AUDIO_REACTIVE = `import React from "react";
|
|
63
|
+
import { Composition, VibeoRoot, useCurrentFrame, useVideoConfig, interpolate } from "@vibeo/core";
|
|
64
|
+
import { Audio } from "@vibeo/audio";
|
|
65
|
+
import { useAudioData } from "@vibeo/effects";
|
|
66
|
+
|
|
67
|
+
const AUDIO_SRC = "/music.mp3";
|
|
68
|
+
|
|
69
|
+
function FrequencyBars() {
|
|
70
|
+
const { width, height } = useVideoConfig();
|
|
71
|
+
const audio = useAudioData(AUDIO_SRC, { fftSize: 1024 });
|
|
72
|
+
if (!audio) return <div style={{ width, height }} />;
|
|
73
|
+
|
|
74
|
+
const barCount = 48;
|
|
75
|
+
const step = Math.floor(audio.frequencies.length / barCount);
|
|
76
|
+
const barWidth = (width * 0.8) / barCount;
|
|
77
|
+
const maxBarHeight = height * 0.6;
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div style={{ position: "absolute", bottom: 60, left: width * 0.1, display: "flex", alignItems: "flex-end", gap: 2 }}>
|
|
81
|
+
{Array.from({ length: barCount }, (_, i) => {
|
|
82
|
+
const db = audio.frequencies[i * step];
|
|
83
|
+
const normalized = Math.max(0, (db + 100) / 100);
|
|
84
|
+
const hue = interpolate(i, [0, barCount - 1], [220, 340]);
|
|
85
|
+
return (
|
|
86
|
+
<div key={i} style={{ width: barWidth - 2, height: Math.max(2, normalized * maxBarHeight), background: \`hsl(\${hue}, 80%, 60%)\`, borderRadius: 2 }} />
|
|
87
|
+
);
|
|
88
|
+
})}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function AudioViz() {
|
|
94
|
+
const frame = useCurrentFrame();
|
|
95
|
+
const { width, height, fps } = useVideoConfig();
|
|
96
|
+
const audio = useAudioData(AUDIO_SRC);
|
|
97
|
+
const hue = 240 + (audio ? audio.amplitude * 60 : 0);
|
|
98
|
+
const lightness = audio ? 8 + audio.amplitude * 12 : 8;
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div style={{ width, height, background: \`radial-gradient(ellipse at center, hsl(\${hue}, 40%, \${lightness + 5}%), hsl(\${hue}, 30%, \${lightness}%))\`, position: "relative", overflow: "hidden" }}>
|
|
102
|
+
<div style={{ position: "absolute", top: 40, left: 40, color: "white", fontFamily: "sans-serif" }}>
|
|
103
|
+
<h1 style={{ fontSize: 36, margin: 0, opacity: 0.9 }}>Audio Visualizer</h1>
|
|
104
|
+
<p style={{ fontSize: 18, margin: "8px 0 0", opacity: 0.5 }}>{(frame / fps).toFixed(1)}s</p>
|
|
105
|
+
</div>
|
|
106
|
+
<FrequencyBars />
|
|
107
|
+
<Audio src={AUDIO_SRC} volume={0.8} />
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function Root() {
|
|
113
|
+
return (
|
|
114
|
+
<VibeoRoot>
|
|
115
|
+
<Composition id="AudioReactiveViz" component={AudioViz} width={1920} height={1080} fps={30} durationInFrames={900} />
|
|
116
|
+
</VibeoRoot>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
const TEMPLATE_TRANSITIONS = `import React from "react";
|
|
122
|
+
import { Composition, Sequence, VibeoRoot, useCurrentFrame, useVideoConfig, interpolate, easeOut } from "@vibeo/core";
|
|
123
|
+
import { Transition } from "@vibeo/effects";
|
|
124
|
+
|
|
125
|
+
function ColorScene({ title, color }: { title: string; color: string }) {
|
|
126
|
+
const frame = useCurrentFrame();
|
|
127
|
+
const { width, height } = useVideoConfig();
|
|
128
|
+
const opacity = interpolate(frame, [0, 20], [0, 1], { easing: easeOut, extrapolateRight: "clamp" });
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<div style={{ width, height, background: color, display: "flex", justifyContent: "center", alignItems: "center" }}>
|
|
132
|
+
<h1 style={{ color: "white", fontSize: 80, fontFamily: "sans-serif", opacity }}>{title}</h1>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function SceneA() { return <ColorScene title="Scene One" color="linear-gradient(135deg, #667eea, #764ba2)" />; }
|
|
138
|
+
function SceneB() { return <ColorScene title="Scene Two" color="linear-gradient(135deg, #f093fb, #f5576c)" />; }
|
|
139
|
+
function SceneC() { return <ColorScene title="Scene Three" color="linear-gradient(135deg, #4facfe, #00f2fe)" />; }
|
|
23
140
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
141
|
+
function TransitionDemo() {
|
|
142
|
+
return (
|
|
143
|
+
<>
|
|
144
|
+
<Sequence from={0} durationInFrames={85}><SceneA /></Sequence>
|
|
145
|
+
<Sequence from={65} durationInFrames={20}>
|
|
146
|
+
<Transition type="fade" durationInFrames={20}><SceneA /><SceneB /></Transition>
|
|
147
|
+
</Sequence>
|
|
148
|
+
<Sequence from={85} durationInFrames={85}><SceneB /></Sequence>
|
|
149
|
+
<Sequence from={150} durationInFrames={20}>
|
|
150
|
+
<Transition type="slide" durationInFrames={20} direction="left"><SceneB /><SceneC /></Transition>
|
|
151
|
+
</Sequence>
|
|
152
|
+
<Sequence from={170} durationInFrames={70}><SceneC /></Sequence>
|
|
153
|
+
</>
|
|
154
|
+
);
|
|
27
155
|
}
|
|
28
156
|
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (arg === "--template" && next) {
|
|
37
|
-
result.template = next;
|
|
38
|
-
i++;
|
|
39
|
-
} else if (arg.startsWith("--template=")) {
|
|
40
|
-
result.template = arg.slice("--template=".length);
|
|
41
|
-
} else if (arg === "--help" || arg === "-h") {
|
|
42
|
-
printHelp();
|
|
43
|
-
process.exit(0);
|
|
44
|
-
} else if (!arg.startsWith("-") && !result.name) {
|
|
45
|
-
result.name = arg;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return result;
|
|
157
|
+
export function Root() {
|
|
158
|
+
return (
|
|
159
|
+
<VibeoRoot>
|
|
160
|
+
<Composition id="TransitionDemo" component={TransitionDemo} width={1920} height={1080} fps={30} durationInFrames={240} />
|
|
161
|
+
</VibeoRoot>
|
|
162
|
+
);
|
|
50
163
|
}
|
|
164
|
+
`;
|
|
165
|
+
|
|
166
|
+
const TEMPLATE_SUBTITLES = `import React from "react";
|
|
167
|
+
import { Composition, Sequence, VibeoRoot, useCurrentFrame, useVideoConfig, interpolate } from "@vibeo/core";
|
|
168
|
+
import { Subtitle } from "@vibeo/extras";
|
|
51
169
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
170
|
+
const SUBTITLES_SRT = \`1
|
|
171
|
+
00:00:00,500 --> 00:00:03,000
|
|
172
|
+
Welcome to the Vibeo demo.
|
|
55
173
|
|
|
56
|
-
|
|
57
|
-
|
|
174
|
+
2
|
|
175
|
+
00:00:03,500 --> 00:00:06,000
|
|
176
|
+
This shows subtitle overlays.
|
|
58
177
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
178
|
+
3
|
|
179
|
+
00:00:06,500 --> 00:00:09,000
|
|
180
|
+
Subtitles are synced to the frame timeline.
|
|
62
181
|
|
|
63
|
-
|
|
182
|
+
4
|
|
183
|
+
00:00:09,500 --> 00:00:12,000
|
|
184
|
+
You can use <b>bold</b> and <i>italic</i> text.
|
|
64
185
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
186
|
+
5
|
|
187
|
+
00:00:13,000 --> 00:00:16,000
|
|
188
|
+
The end. Thanks for watching!\`;
|
|
68
189
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
190
|
+
function SubtitleVideo() {
|
|
191
|
+
const frame = useCurrentFrame();
|
|
192
|
+
const { width, height } = useVideoConfig();
|
|
193
|
+
const hue = interpolate(frame, [0, 480], [200, 280]);
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<div style={{ width, height, position: "relative" }}>
|
|
197
|
+
<div style={{ width, height, background: \`linear-gradient(135deg, hsl(\${hue}, 50%, 15%), hsl(\${hue + 40}, 40%, 10%))\` }} />
|
|
198
|
+
<div style={{ position: "absolute", top: 0, left: 0, width, height }}>
|
|
199
|
+
<Subtitle src={SUBTITLES_SRT} format="srt" position="bottom" fontSize={36} color="white" outlineColor="black" outlineWidth={2} style={{ padding: "0 80px 60px" }} />
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
75
203
|
}
|
|
76
204
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (existsSync(candidate)) return candidate;
|
|
84
|
-
dir = resolve(dir, "..");
|
|
85
|
-
}
|
|
86
|
-
throw new Error("Could not find examples directory");
|
|
205
|
+
export function Root() {
|
|
206
|
+
return (
|
|
207
|
+
<VibeoRoot>
|
|
208
|
+
<Composition id="SubtitleOverlay" component={SubtitleVideo} width={1920} height={1080} fps={30} durationInFrames={480} />
|
|
209
|
+
</VibeoRoot>
|
|
210
|
+
);
|
|
87
211
|
}
|
|
212
|
+
`;
|
|
88
213
|
|
|
89
|
-
|
|
90
|
-
const parsed = parseArgs(args);
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
91
215
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
216
|
+
const TEMPLATES: Record<string, { description: string; source: string }> = {
|
|
217
|
+
basic: { description: "Minimal composition with text animation and two scenes", source: TEMPLATE_BASIC },
|
|
218
|
+
"audio-reactive": { description: "Audio visualization with frequency bars", source: TEMPLATE_AUDIO_REACTIVE },
|
|
219
|
+
transitions: { description: "Scene transitions (fade, slide)", source: TEMPLATE_TRANSITIONS },
|
|
220
|
+
subtitles: { description: "Video with SRT subtitle overlay", source: TEMPLATE_SUBTITLES },
|
|
221
|
+
};
|
|
97
222
|
|
|
98
|
-
|
|
99
|
-
if (!template) {
|
|
100
|
-
console.error(`Error: unknown template "${parsed.template}"`);
|
|
101
|
-
console.error(`Available: ${Object.keys(TEMPLATES).join(", ")}`);
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
223
|
+
export const TEMPLATE_NAMES = Object.keys(TEMPLATES);
|
|
104
224
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
225
|
+
export async function createProject(
|
|
226
|
+
name: string,
|
|
227
|
+
template: string,
|
|
228
|
+
): Promise<{ project: string; template: string; files: string[] }> {
|
|
229
|
+
const tmpl = TEMPLATES[template];
|
|
230
|
+
if (!tmpl) throw new Error(`Unknown template: ${template}`);
|
|
110
231
|
|
|
111
|
-
|
|
112
|
-
|
|
232
|
+
const projectDir = resolve(name);
|
|
233
|
+
if (existsSync(projectDir)) throw new Error(`Directory "${name}" already exists`);
|
|
113
234
|
|
|
114
|
-
// Create project structure
|
|
115
235
|
await mkdir(join(projectDir, "src"), { recursive: true });
|
|
116
236
|
await mkdir(join(projectDir, "public"), { recursive: true });
|
|
117
237
|
|
|
118
|
-
|
|
119
|
-
const examplesDir = findExamplesDir();
|
|
120
|
-
const exampleSrc = await readFile(join(examplesDir, template.example), "utf-8");
|
|
121
|
-
await writeFile(join(projectDir, "src", "index.tsx"), exampleSrc);
|
|
238
|
+
await writeFile(join(projectDir, "src", "index.tsx"), tmpl.source);
|
|
122
239
|
|
|
123
|
-
// Write package.json
|
|
124
240
|
const pkg = {
|
|
125
|
-
name
|
|
241
|
+
name,
|
|
126
242
|
version: "0.0.1",
|
|
127
243
|
private: true,
|
|
128
244
|
type: "module",
|
|
129
245
|
scripts: {
|
|
130
|
-
dev: "vibeo preview --entry src/index.tsx",
|
|
131
|
-
build: "vibeo render --entry src/index.tsx",
|
|
132
|
-
list: "vibeo list --entry src/index.tsx",
|
|
246
|
+
dev: "bunx @vibeo/cli preview --entry src/index.tsx",
|
|
247
|
+
build: "bunx @vibeo/cli render --entry src/index.tsx",
|
|
248
|
+
list: "bunx @vibeo/cli list --entry src/index.tsx",
|
|
133
249
|
typecheck: "bunx tsc --noEmit",
|
|
134
250
|
},
|
|
135
251
|
dependencies: {
|
|
136
|
-
"@vibeo/core": "
|
|
137
|
-
"@vibeo/audio": "
|
|
138
|
-
"@vibeo/effects": "
|
|
139
|
-
"@vibeo/extras": "
|
|
140
|
-
"@vibeo/player": "
|
|
141
|
-
"@vibeo/renderer": "
|
|
142
|
-
"@vibeo/cli": "
|
|
252
|
+
"@vibeo/core": "^0.1.0",
|
|
253
|
+
"@vibeo/audio": "^0.1.0",
|
|
254
|
+
"@vibeo/effects": "^0.1.0",
|
|
255
|
+
"@vibeo/extras": "^0.1.0",
|
|
256
|
+
"@vibeo/player": "^0.1.0",
|
|
257
|
+
"@vibeo/renderer": "^0.1.0",
|
|
258
|
+
"@vibeo/cli": "^0.1.0",
|
|
143
259
|
react: "^19.0.0",
|
|
144
260
|
"react-dom": "^19.0.0",
|
|
145
261
|
},
|
|
@@ -150,7 +266,6 @@ export async function createCommand(args: string[]): Promise<void> {
|
|
|
150
266
|
};
|
|
151
267
|
await writeFile(join(projectDir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
|
|
152
268
|
|
|
153
|
-
// Write tsconfig.json
|
|
154
269
|
const tsconfig = {
|
|
155
270
|
compilerOptions: {
|
|
156
271
|
target: "ES2022",
|
|
@@ -169,29 +284,8 @@ export async function createCommand(args: string[]): Promise<void> {
|
|
|
169
284
|
};
|
|
170
285
|
await writeFile(join(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
|
|
171
286
|
|
|
172
|
-
|
|
173
|
-
await writeFile(
|
|
174
|
-
join(projectDir, ".gitignore"),
|
|
175
|
-
`node_modules/
|
|
176
|
-
dist/
|
|
177
|
-
out/
|
|
178
|
-
*.tmp
|
|
179
|
-
.DS_Store
|
|
180
|
-
`,
|
|
181
|
-
);
|
|
287
|
+
await writeFile(join(projectDir, ".gitignore"), "node_modules/\ndist/\nout/\n*.tmp\n.DS_Store\n");
|
|
182
288
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
console.log(` ├── public/`);
|
|
186
|
-
console.log(` ├── package.json`);
|
|
187
|
-
console.log(` ├── tsconfig.json`);
|
|
188
|
-
console.log(` └── .gitignore`);
|
|
189
|
-
|
|
190
|
-
console.log(`
|
|
191
|
-
Next steps:
|
|
192
|
-
cd ${parsed.name}
|
|
193
|
-
bun install
|
|
194
|
-
bun run dev # preview in browser
|
|
195
|
-
bun run build # render to video
|
|
196
|
-
`);
|
|
289
|
+
const files = ["src/index.tsx", "package.json", "tsconfig.json", ".gitignore", "public/"];
|
|
290
|
+
return { project: name, template, files };
|
|
197
291
|
}
|
package/src/commands/list.ts
CHANGED
|
@@ -1,49 +1,6 @@
|
|
|
1
|
-
import { resolve } from "node:path";
|
|
2
1
|
import { bundle } from "@vibeo/renderer";
|
|
3
2
|
import { launchBrowser, createPage, closeBrowser } from "@vibeo/renderer";
|
|
4
3
|
|
|
5
|
-
interface ListArgs {
|
|
6
|
-
entry: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function parseArgs(args: string[]): ListArgs {
|
|
10
|
-
const result: ListArgs = {
|
|
11
|
-
entry: "",
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
for (let i = 0; i < args.length; i++) {
|
|
15
|
-
const arg = args[i]!;
|
|
16
|
-
const next = args[i + 1];
|
|
17
|
-
|
|
18
|
-
if (arg === "--entry" && next) {
|
|
19
|
-
result.entry = next;
|
|
20
|
-
i++;
|
|
21
|
-
} else if (arg.startsWith("--entry=")) {
|
|
22
|
-
result.entry = arg.slice("--entry=".length);
|
|
23
|
-
} else if (arg === "--help" || arg === "-h") {
|
|
24
|
-
printHelp();
|
|
25
|
-
process.exit(0);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return result;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function printHelp(): void {
|
|
33
|
-
console.log(`
|
|
34
|
-
vibeo list - List registered compositions
|
|
35
|
-
|
|
36
|
-
Usage:
|
|
37
|
-
vibeo list --entry <path>
|
|
38
|
-
|
|
39
|
-
Required:
|
|
40
|
-
--entry <path> Path to the root file with compositions
|
|
41
|
-
|
|
42
|
-
Options:
|
|
43
|
-
--help Show this help
|
|
44
|
-
`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
4
|
interface CompositionMeta {
|
|
48
5
|
id: string;
|
|
49
6
|
width: number;
|
|
@@ -52,21 +9,7 @@ interface CompositionMeta {
|
|
|
52
9
|
durationInFrames: number;
|
|
53
10
|
}
|
|
54
11
|
|
|
55
|
-
|
|
56
|
-
* Bundle the entry, launch a browser to evaluate it,
|
|
57
|
-
* extract registered compositions, and print a table.
|
|
58
|
-
*/
|
|
59
|
-
export async function listCommand(args: string[]): Promise<void> {
|
|
60
|
-
const parsed = parseArgs(args);
|
|
61
|
-
|
|
62
|
-
if (!parsed.entry) {
|
|
63
|
-
console.error("Error: --entry is required");
|
|
64
|
-
printHelp();
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const entry = resolve(parsed.entry);
|
|
69
|
-
|
|
12
|
+
export async function listCompositions(entry: string): Promise<CompositionMeta[]> {
|
|
70
13
|
console.log(`Bundling ${entry}...`);
|
|
71
14
|
const bundleResult = await bundle(entry);
|
|
72
15
|
|
|
@@ -75,11 +18,8 @@ export async function listCommand(args: string[]): Promise<void> {
|
|
|
75
18
|
const page = await createPage(browser, 1920, 1080);
|
|
76
19
|
|
|
77
20
|
await page.goto(bundleResult.url, { waitUntil: "networkidle" });
|
|
78
|
-
|
|
79
|
-
// Wait briefly for React to register compositions
|
|
80
21
|
await page.waitForTimeout(2000);
|
|
81
22
|
|
|
82
|
-
// Extract composition data from the page
|
|
83
23
|
const compositions = await page.evaluate(() => {
|
|
84
24
|
const win = window as typeof window & {
|
|
85
25
|
vibeo_getCompositions?: () => CompositionMeta[];
|
|
@@ -93,44 +33,8 @@ export async function listCommand(args: string[]): Promise<void> {
|
|
|
93
33
|
await page.close();
|
|
94
34
|
await closeBrowser();
|
|
95
35
|
|
|
96
|
-
|
|
97
|
-
console.log("\nNo compositions found.");
|
|
98
|
-
console.log(
|
|
99
|
-
"Make sure your entry file exports compositions via <Composition /> components.",
|
|
100
|
-
);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Print table
|
|
105
|
-
console.log("\nRegistered compositions:\n");
|
|
106
|
-
console.log(
|
|
107
|
-
padRight("ID", 25) +
|
|
108
|
-
padRight("Width", 8) +
|
|
109
|
-
padRight("Height", 8) +
|
|
110
|
-
padRight("FPS", 6) +
|
|
111
|
-
padRight("Frames", 8) +
|
|
112
|
-
"Duration",
|
|
113
|
-
);
|
|
114
|
-
console.log("-".repeat(70));
|
|
115
|
-
|
|
116
|
-
for (const comp of compositions) {
|
|
117
|
-
const duration = (comp.durationInFrames / comp.fps).toFixed(1) + "s";
|
|
118
|
-
console.log(
|
|
119
|
-
padRight(comp.id, 25) +
|
|
120
|
-
padRight(String(comp.width), 8) +
|
|
121
|
-
padRight(String(comp.height), 8) +
|
|
122
|
-
padRight(String(comp.fps), 6) +
|
|
123
|
-
padRight(String(comp.durationInFrames), 8) +
|
|
124
|
-
duration,
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
console.log();
|
|
36
|
+
return compositions;
|
|
129
37
|
} finally {
|
|
130
38
|
await bundleResult.cleanup();
|
|
131
39
|
}
|
|
132
40
|
}
|
|
133
|
-
|
|
134
|
-
function padRight(str: string, len: number): string {
|
|
135
|
-
return str.length >= len ? str : str + " ".repeat(len - str.length);
|
|
136
|
-
}
|
package/src/commands/preview.ts
CHANGED
|
@@ -1,70 +1,6 @@
|
|
|
1
|
-
import { resolve } from "node:path";
|
|
2
1
|
import { bundle } from "@vibeo/renderer";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
entry: string;
|
|
6
|
-
port: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function parseArgs(args: string[]): PreviewArgs {
|
|
10
|
-
const result: PreviewArgs = {
|
|
11
|
-
entry: "",
|
|
12
|
-
port: 3000,
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
for (let i = 0; i < args.length; i++) {
|
|
16
|
-
const arg = args[i]!;
|
|
17
|
-
const next = args[i + 1];
|
|
18
|
-
|
|
19
|
-
if (arg === "--entry" && next) {
|
|
20
|
-
result.entry = next;
|
|
21
|
-
i++;
|
|
22
|
-
} else if (arg.startsWith("--entry=")) {
|
|
23
|
-
result.entry = arg.slice("--entry=".length);
|
|
24
|
-
} else if (arg === "--port" && next) {
|
|
25
|
-
result.port = parseInt(next, 10);
|
|
26
|
-
i++;
|
|
27
|
-
} else if (arg.startsWith("--port=")) {
|
|
28
|
-
result.port = parseInt(arg.slice("--port=".length), 10);
|
|
29
|
-
} else if (arg === "--help" || arg === "-h") {
|
|
30
|
-
printHelp();
|
|
31
|
-
process.exit(0);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return result;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function printHelp(): void {
|
|
39
|
-
console.log(`
|
|
40
|
-
vibeo preview - Start a dev server with live preview
|
|
41
|
-
|
|
42
|
-
Usage:
|
|
43
|
-
vibeo preview --entry <path> [options]
|
|
44
|
-
|
|
45
|
-
Required:
|
|
46
|
-
--entry <path> Path to the root file with compositions
|
|
47
|
-
|
|
48
|
-
Options:
|
|
49
|
-
--port <number> Port for the dev server (default: 3000)
|
|
50
|
-
--help Show this help
|
|
51
|
-
`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Start a dev server hosting the Player with hot reload.
|
|
56
|
-
*/
|
|
57
|
-
export async function previewCommand(args: string[]): Promise<void> {
|
|
58
|
-
const parsed = parseArgs(args);
|
|
59
|
-
|
|
60
|
-
if (!parsed.entry) {
|
|
61
|
-
console.error("Error: --entry is required");
|
|
62
|
-
printHelp();
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const entry = resolve(parsed.entry);
|
|
67
|
-
|
|
3
|
+
export async function startPreview(entry: string, _port: number): Promise<void> {
|
|
68
4
|
console.log(`Starting preview server...`);
|
|
69
5
|
console.log(` Entry: ${entry}`);
|
|
70
6
|
|
|
@@ -73,7 +9,6 @@ export async function previewCommand(args: string[]): Promise<void> {
|
|
|
73
9
|
console.log(`\n Preview running at ${bundleResult.url}`);
|
|
74
10
|
console.log(` Press Ctrl+C to stop\n`);
|
|
75
11
|
|
|
76
|
-
// Keep the process alive until interrupted
|
|
77
12
|
const shutdown = async () => {
|
|
78
13
|
console.log("\nShutting down preview server...");
|
|
79
14
|
await bundleResult.cleanup();
|