@open-motion/cli 0.0.1-alpha.0 → 0.0.2
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/README.md +32 -0
- package/dist/index.js +63 -9
- package/package.json +9 -5
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @open-motion/cli
|
|
2
|
+
|
|
3
|
+
The Command Line Interface for **OpenMotion** — the open-source programmatic video engine.
|
|
4
|
+
|
|
5
|
+
## 🚀 Features
|
|
6
|
+
|
|
7
|
+
- 🏗️ **Quick Init**: Scaffold new OpenMotion projects instantly.
|
|
8
|
+
- 🎥 **High-Speed Rendering**: Capture and encode videos directly from your React code.
|
|
9
|
+
- 🚀 **Parallel Execution**: Leverage multi-core CPUs for faster frame capturing.
|
|
10
|
+
- 🎛️ **Dynamic Props**: Inject external data into your videos via JSON.
|
|
11
|
+
|
|
12
|
+
## 🛠 Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g @open-motion/cli
|
|
16
|
+
# or use via npx
|
|
17
|
+
npx @open-motion/cli --help
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 📖 Usage
|
|
21
|
+
|
|
22
|
+
### Initialize a project
|
|
23
|
+
```bash
|
|
24
|
+
open-motion init my-video
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Render a video
|
|
28
|
+
```bash
|
|
29
|
+
open-motion render --url http://localhost:5173 --out out.mp4 --concurrency 4
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Learn more at the [main OpenMotion repository](https://github.com/jsongo/open-motion).
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const encoder_1 = require("@open-motion/encoder");
|
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const fs_1 = __importDefault(require("fs"));
|
|
11
11
|
const commander_1 = require("commander");
|
|
12
|
+
const cli_progress_1 = __importDefault(require("cli-progress"));
|
|
12
13
|
const runInit = async (projectName) => {
|
|
13
14
|
const targetDir = path_1.default.join(process.cwd(), projectName);
|
|
14
15
|
if (fs_1.default.existsSync(targetDir)) {
|
|
@@ -50,7 +51,7 @@ const runInit = async (projectName) => {
|
|
|
50
51
|
<head>
|
|
51
52
|
<meta charset="UTF-8" />
|
|
52
53
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
53
|
-
<title
|
|
54
|
+
<title>\${projectName}</title>
|
|
54
55
|
</head>
|
|
55
56
|
<body>
|
|
56
57
|
<div id="root"></div>
|
|
@@ -59,9 +60,17 @@ const runInit = async (projectName) => {
|
|
|
59
60
|
</html>`,
|
|
60
61
|
'vite.config.ts': `import { defineConfig } from 'vite';
|
|
61
62
|
import react from '@vitejs/plugin-react';
|
|
63
|
+
import path from 'path';
|
|
62
64
|
|
|
63
65
|
export default defineConfig({
|
|
64
66
|
plugins: [react()],
|
|
67
|
+
resolve: {
|
|
68
|
+
alias: {
|
|
69
|
+
'react': path.resolve(__dirname, 'node_modules/react'),
|
|
70
|
+
'react-dom': path.resolve(__dirname, 'node_modules/react-dom'),
|
|
71
|
+
},
|
|
72
|
+
dedupe: ['react', 'react-dom'],
|
|
73
|
+
},
|
|
65
74
|
});`,
|
|
66
75
|
'src/main.tsx': `import React from 'react';
|
|
67
76
|
import ReactDOM from 'react-dom/client';
|
|
@@ -129,9 +138,9 @@ export const App = () => {
|
|
|
129
138
|
for (const [name, content] of Object.entries(files)) {
|
|
130
139
|
fs_1.default.writeFileSync(path_1.default.join(targetDir, name), content);
|
|
131
140
|
}
|
|
132
|
-
console.log(`Success! Project
|
|
141
|
+
console.log(`Success! Project \${projectName} initialized.`);
|
|
133
142
|
console.log(`Next steps:`);
|
|
134
|
-
console.log(` cd
|
|
143
|
+
console.log(` cd \${projectName}`);
|
|
135
144
|
console.log(` npm install (or pnpm install)`);
|
|
136
145
|
console.log(` npm run dev`);
|
|
137
146
|
};
|
|
@@ -139,6 +148,7 @@ exports.runInit = runInit;
|
|
|
139
148
|
const runRender = async (options) => {
|
|
140
149
|
const tmpDir = path_1.default.join(process.cwd(), '.open-motion-tmp');
|
|
141
150
|
const inputProps = options.props ? JSON.parse(options.props) : {};
|
|
151
|
+
const startTime = Date.now();
|
|
142
152
|
console.log(`Fetching compositions from ${options.url}...`);
|
|
143
153
|
const compositions = await (0, renderer_1.getCompositions)(options.url);
|
|
144
154
|
if (compositions.length === 0) {
|
|
@@ -160,23 +170,57 @@ const runRender = async (options) => {
|
|
|
160
170
|
durationInFrames: options.duration || selectedComp.durationInFrames
|
|
161
171
|
};
|
|
162
172
|
console.log(`Rendering composition: ${selectedComp.id} (${config.width}x${config.height}, ${config.fps}fps, ${config.durationInFrames} frames)`);
|
|
173
|
+
const multibar = new cli_progress_1.default.MultiBar({
|
|
174
|
+
clearOnComplete: false,
|
|
175
|
+
hideCursor: true,
|
|
176
|
+
format: ' {bar} | {percentage}% | {value}/{total} | {task}',
|
|
177
|
+
}, cli_progress_1.default.Presets.shades_grey);
|
|
178
|
+
const renderBar = multibar.create(config.durationInFrames, 0, { task: 'Rendering' });
|
|
163
179
|
const { audioAssets } = await (0, renderer_1.renderFrames)({
|
|
164
180
|
url: options.url,
|
|
165
181
|
config,
|
|
166
182
|
outputDir: tmpDir,
|
|
167
183
|
compositionId: selectedComp.id,
|
|
168
184
|
inputProps,
|
|
169
|
-
concurrency: options.concurrency || 1
|
|
185
|
+
concurrency: options.concurrency || 1,
|
|
186
|
+
onProgress: (frame) => renderBar.update(frame)
|
|
170
187
|
});
|
|
171
|
-
|
|
172
|
-
|
|
188
|
+
renderBar.update(config.durationInFrames);
|
|
189
|
+
// Resolve audio paths to absolute file paths if they are relative URLs
|
|
190
|
+
console.log(`DEBUG: Found ${audioAssets.length} audio assets in browser.`);
|
|
191
|
+
const resolvedAudioAssets = audioAssets.map(asset => {
|
|
192
|
+
console.log(`DEBUG: Processing asset: ${asset.src}`);
|
|
193
|
+
if (asset.src.startsWith('/') && !asset.src.startsWith('//')) {
|
|
194
|
+
// Look for public folder relative to the URL if possible, but for now
|
|
195
|
+
// let's try common locations
|
|
196
|
+
const possiblePaths = [
|
|
197
|
+
path_1.default.join(process.cwd(), 'examples/demo/public', asset.src.substring(1)),
|
|
198
|
+
path_1.default.join(process.cwd(), 'public', asset.src.substring(1)),
|
|
199
|
+
];
|
|
200
|
+
for (const p of possiblePaths) {
|
|
201
|
+
console.log(`DEBUG: Checking path: ${p}`);
|
|
202
|
+
if (fs_1.default.existsSync(p)) {
|
|
203
|
+
console.log(`DEBUG: Found local file at ${p}!`);
|
|
204
|
+
return { ...asset, src: p };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return asset;
|
|
209
|
+
});
|
|
210
|
+
const encodeBar = multibar.create(100, 0, { task: 'Encoding ' });
|
|
173
211
|
await (0, encoder_1.encodeVideo)({
|
|
174
212
|
framesDir: tmpDir,
|
|
175
213
|
fps: config.fps,
|
|
176
214
|
outputFile: options.out,
|
|
177
|
-
|
|
215
|
+
audioAssets: resolvedAudioAssets,
|
|
216
|
+
onProgress: (percent) => encodeBar.update(Math.round(percent))
|
|
178
217
|
});
|
|
179
|
-
|
|
218
|
+
encodeBar.update(100);
|
|
219
|
+
multibar.stop();
|
|
220
|
+
const endTime = Date.now();
|
|
221
|
+
const durationSec = ((endTime - startTime) / 1000).toFixed(1);
|
|
222
|
+
console.log(`\nSuccess! Video rendered to ${options.out}`);
|
|
223
|
+
console.log(`Total time: ${durationSec}s`);
|
|
180
224
|
};
|
|
181
225
|
exports.runRender = runRender;
|
|
182
226
|
const main = () => {
|
|
@@ -211,7 +255,17 @@ const main = () => {
|
|
|
211
255
|
.option('--duration <number>', 'Override duration in frames', parseInt)
|
|
212
256
|
.action(async (options) => {
|
|
213
257
|
try {
|
|
214
|
-
await (0, exports.runRender)(
|
|
258
|
+
await (0, exports.runRender)({
|
|
259
|
+
url: options.url,
|
|
260
|
+
out: options.out,
|
|
261
|
+
compositionId: options.composition,
|
|
262
|
+
props: options.props,
|
|
263
|
+
concurrency: options.concurrency,
|
|
264
|
+
width: options.width,
|
|
265
|
+
height: options.height,
|
|
266
|
+
fps: options.fps,
|
|
267
|
+
duration: options.duration
|
|
268
|
+
});
|
|
215
269
|
}
|
|
216
270
|
catch (err) {
|
|
217
271
|
console.error('Render failed:', err);
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-motion/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"bin": {
|
|
5
5
|
"open-motion": "dist/bin.js"
|
|
6
6
|
},
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/
|
|
10
|
+
"url": "https://github.com/jsongo/open-motion.git",
|
|
11
11
|
"directory": "packages/cli"
|
|
12
12
|
},
|
|
13
13
|
"publishConfig": {
|
|
@@ -17,10 +17,14 @@
|
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"cli-progress": "^3.12.0",
|
|
20
21
|
"commander": "^11.0.0",
|
|
21
|
-
"@open-motion/
|
|
22
|
-
"@open-motion/
|
|
23
|
-
"@open-motion/
|
|
22
|
+
"@open-motion/encoder": "0.0.2",
|
|
23
|
+
"@open-motion/renderer": "0.0.2",
|
|
24
|
+
"@open-motion/core": "0.0.2"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/cli-progress": "^3.11.6"
|
|
24
28
|
},
|
|
25
29
|
"scripts": {
|
|
26
30
|
"build": "tsc && chmod +x dist/bin.js"
|