video-maker-mcp 0.1.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/README.md +143 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +74 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/install.d.ts +15 -0
- package/dist/cli/install.js +59 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/cli/load-env.d.ts +1 -0
- package/dist/cli/load-env.js +11 -0
- package/dist/cli/load-env.js.map +1 -0
- package/dist/core/assets.d.ts +28 -0
- package/dist/core/assets.js +50 -0
- package/dist/core/assets.js.map +1 -0
- package/dist/core/env.d.ts +27 -0
- package/dist/core/env.js +89 -0
- package/dist/core/env.js.map +1 -0
- package/dist/core/export.d.ts +11 -0
- package/dist/core/export.js +39 -0
- package/dist/core/export.js.map +1 -0
- package/dist/core/paths.d.ts +12 -0
- package/dist/core/paths.js +57 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/plan-schema.d.ts +90 -0
- package/dist/core/plan-schema.js +91 -0
- package/dist/core/plan-schema.js.map +1 -0
- package/dist/core/project.d.ts +41 -0
- package/dist/core/project.js +72 -0
- package/dist/core/project.js.map +1 -0
- package/dist/core/render.d.ts +12 -0
- package/dist/core/render.js +100 -0
- package/dist/core/render.js.map +1 -0
- package/dist/core/subtitles.d.ts +4 -0
- package/dist/core/subtitles.js +30 -0
- package/dist/core/subtitles.js.map +1 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.js +120 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/providers/tts/volcengine.d.ts +19 -0
- package/dist/providers/tts/volcengine.js +209 -0
- package/dist/providers/tts/volcengine.js.map +1 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# video-maker-mcp
|
|
2
|
+
|
|
3
|
+
Local MCP storyboard video renderer for Codex-driven short video generation.
|
|
4
|
+
|
|
5
|
+
`video-maker-mcp` does not run its own LLM and does not generate images itself. Codex writes the script and storyboard, uses its host image generation capability to create scene images, then this MCP server validates assets, generates Doubao/Volcengine narration, and renders a 9:16 MP4 with ffmpeg.
|
|
6
|
+
|
|
7
|
+
## Install With Codex
|
|
8
|
+
|
|
9
|
+
Paste this into Codex:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
请帮我安装 video-maker-mcp。执行:
|
|
13
|
+
npx -y video-maker-mcp@latest install --host codex
|
|
14
|
+
|
|
15
|
+
如果提示缺少 ffmpeg,请按提示安装 ffmpeg,然后重新运行:
|
|
16
|
+
npx -y video-maker-mcp@latest doctor
|
|
17
|
+
|
|
18
|
+
最后确认 Codex 能看到 video-maker MCP server。
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Manual command:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx -y video-maker-mcp@latest install --host codex
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The installer configures Codex with:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
codex mcp add video-maker -- npx -y video-maker-mcp@latest serve
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
It does not silently install system packages and does not bundle `ffmpeg-static`.
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
- Node.js 20+
|
|
38
|
+
- Codex CLI
|
|
39
|
+
- ffmpeg on PATH
|
|
40
|
+
- Doubao/Volcengine TTS credentials
|
|
41
|
+
|
|
42
|
+
ffmpeg install hints:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# macOS
|
|
46
|
+
brew install ffmpeg
|
|
47
|
+
|
|
48
|
+
# Ubuntu/Debian
|
|
49
|
+
sudo apt update && sudo apt install -y ffmpeg
|
|
50
|
+
|
|
51
|
+
# Windows
|
|
52
|
+
winget install Gyan.FFmpeg
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## TTS Environment
|
|
56
|
+
|
|
57
|
+
Set:
|
|
58
|
+
|
|
59
|
+
```env
|
|
60
|
+
VIDEO_MAKER_TTS_API_KEY=your-api-key
|
|
61
|
+
VIDEO_MAKER_TTS_VOICE_ID=zh_female_vv_uranus_bigtts
|
|
62
|
+
VIDEO_MAKER_TTS_RESOURCE_ID=seed-tts-2.0
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
For local development, put these values in a project-root `.env` file. The CLI loads `.env` automatically. To load another env file, set `VIDEO_MAKER_ENV_FILE=/path/to/file`.
|
|
66
|
+
|
|
67
|
+
The default TTS integration uses the current Doubao/Volcengine V3 HTTP unidirectional API with `X-Api-Key`. This matches the new console API Key page.
|
|
68
|
+
|
|
69
|
+
Legacy V1 `AppID + Access Token` is still accepted as a fallback when `VIDEO_MAKER_TTS_API_KEY` is not set, but new accounts should use `VIDEO_MAKER_TTS_API_KEY`.
|
|
70
|
+
|
|
71
|
+
Optional:
|
|
72
|
+
|
|
73
|
+
```env
|
|
74
|
+
VIDEO_MAKER_TTS_ENDPOINT=https://openspeech.bytedance.com/api/v3/tts/unidirectional
|
|
75
|
+
VIDEO_MAKER_WORKSPACE=$HOME/.video-maker
|
|
76
|
+
VIDEO_MAKER_EXPORT_DIR=$HOME/Downloads/video-maker
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Quick TTS smoke test:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm run build
|
|
83
|
+
node dist/cli/index.js tts-test --text "你好,这是一段豆包语音测试。"
|
|
84
|
+
open ~/Downloads/video-maker/doubao-tts-test.mp3
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`tts-test --out` accepts either a directory or an `.mp3` file:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
node dist/cli/index.js tts-test --out ~/Downloads/video-maker
|
|
91
|
+
node dist/cli/index.js tts-test --out ~/Downloads/my-voice-test.mp3
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Codex Workflow
|
|
95
|
+
|
|
96
|
+
Once installed, ask Codex:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
用 video-maker 把下面文稿生成一个 60 秒竖屏中文解说视频。请先检查环境,然后创建项目,生成分镜计划,生成每个分镜图片,验证素材,生成语音,最后渲染 mp4,并把最终文件导出到 ~/Downloads/video-maker。
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The MCP workflow is:
|
|
103
|
+
|
|
104
|
+
1. `check_environment`
|
|
105
|
+
2. `create_video_project`
|
|
106
|
+
3. `get_video_plan_schema`
|
|
107
|
+
4. `save_video_plan`
|
|
108
|
+
5. `list_required_assets`
|
|
109
|
+
6. Codex generates images into the exact returned paths
|
|
110
|
+
7. `verify_assets`
|
|
111
|
+
8. `generate_audio`
|
|
112
|
+
9. `render_video`
|
|
113
|
+
10. Optional: `export_video`
|
|
114
|
+
|
|
115
|
+
## Output
|
|
116
|
+
|
|
117
|
+
Projects are stored under:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
~/.video-maker/projects/<projectId>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Final files:
|
|
124
|
+
|
|
125
|
+
- `output/final.mp4`
|
|
126
|
+
- `output/subtitles.srt`
|
|
127
|
+
- `manifest.json`
|
|
128
|
+
- `video_plan.json`
|
|
129
|
+
|
|
130
|
+
For user-facing delivery, ask Codex to pass `outputDir` to `render_video` or call `export_video` after rendering. If no export directory is provided, `export_video` copies files to:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
~/Downloads/video-maker/<projectId>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Local Development
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npm install
|
|
140
|
+
npm run typecheck
|
|
141
|
+
npm run build
|
|
142
|
+
node dist/cli/index.js doctor
|
|
143
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { serveMcp } from "../mcp/server.js";
|
|
6
|
+
import { installCodex, runDoctor } from "./install.js";
|
|
7
|
+
import { synthesizeVolcengineSpeech } from "../providers/tts/volcengine.js";
|
|
8
|
+
import { loadCliEnv } from "./load-env.js";
|
|
9
|
+
import { DEFAULT_EXPORT_DIR, resolveUserPath } from "../core/paths.js";
|
|
10
|
+
loadCliEnv();
|
|
11
|
+
const program = new Command();
|
|
12
|
+
function defaultTtsTestPath() {
|
|
13
|
+
return path.join(DEFAULT_EXPORT_DIR, "doubao-tts-test.mp3");
|
|
14
|
+
}
|
|
15
|
+
async function resolveOutputPath(output) {
|
|
16
|
+
const resolved = resolveUserPath(output);
|
|
17
|
+
if (path.extname(resolved).toLowerCase() === ".mp3") {
|
|
18
|
+
await fs.mkdir(path.dirname(resolved), { recursive: true });
|
|
19
|
+
return resolved;
|
|
20
|
+
}
|
|
21
|
+
await fs.mkdir(resolved, { recursive: true });
|
|
22
|
+
return path.join(resolved, "doubao-tts-test.mp3");
|
|
23
|
+
}
|
|
24
|
+
program
|
|
25
|
+
.name("video-maker-mcp")
|
|
26
|
+
.description("Local MCP storyboard video renderer for Codex.")
|
|
27
|
+
.version("0.1.0");
|
|
28
|
+
program
|
|
29
|
+
.command("serve")
|
|
30
|
+
.description("Start the stdio MCP server.")
|
|
31
|
+
.action(async () => {
|
|
32
|
+
await serveMcp();
|
|
33
|
+
});
|
|
34
|
+
program
|
|
35
|
+
.command("doctor")
|
|
36
|
+
.description("Check local dependencies and configuration.")
|
|
37
|
+
.action(async () => {
|
|
38
|
+
const result = await runDoctor();
|
|
39
|
+
console.log(JSON.stringify(result, null, 2));
|
|
40
|
+
if (!result.ok)
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
});
|
|
43
|
+
program
|
|
44
|
+
.command("install")
|
|
45
|
+
.description("Configure video-maker-mcp for a supported host.")
|
|
46
|
+
.option("--host <host>", "Host to configure; v1 supports codex", "codex")
|
|
47
|
+
.option("--package-spec <spec>", "npm package spec used in Codex MCP config", "video-maker-mcp@latest")
|
|
48
|
+
.action(async (opts) => {
|
|
49
|
+
if (opts.host !== "codex") {
|
|
50
|
+
console.error("v1 only supports --host codex");
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const result = await installCodex({ host: "codex", packageSpec: opts.packageSpec });
|
|
55
|
+
console.log(JSON.stringify(result, null, 2));
|
|
56
|
+
if (!result.ok)
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
});
|
|
59
|
+
program
|
|
60
|
+
.command("tts-test")
|
|
61
|
+
.description("Generate a short MP3 with the configured Doubao/Volcengine TTS credentials.")
|
|
62
|
+
.option("--text <text>", "Text to synthesize", "你好,这是一段 video-maker-mcp 的豆包语音合成本地测试。")
|
|
63
|
+
.option("--out <path>", "Output MP3 path or directory", defaultTtsTestPath())
|
|
64
|
+
.action(async (opts) => {
|
|
65
|
+
const outputPath = await resolveOutputPath(opts.out);
|
|
66
|
+
const audio = await synthesizeVolcengineSpeech(opts.text);
|
|
67
|
+
await fs.writeFile(outputPath, audio);
|
|
68
|
+
console.log(JSON.stringify({ ok: true, outputPath, bytes: audio.length }, null, 2));
|
|
69
|
+
});
|
|
70
|
+
program.parseAsync(process.argv).catch((error) => {
|
|
71
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
72
|
+
process.exitCode = 1;
|
|
73
|
+
});
|
|
74
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { checkEnvironment } from "../core/env.js";
|
|
2
|
+
export interface InstallOptions {
|
|
3
|
+
host: "codex";
|
|
4
|
+
packageSpec?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function runDoctor(): Promise<{
|
|
7
|
+
ok: boolean;
|
|
8
|
+
environment: Awaited<ReturnType<typeof checkEnvironment>>;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function installCodex(options: InstallOptions): Promise<{
|
|
11
|
+
ok: boolean;
|
|
12
|
+
configured: boolean;
|
|
13
|
+
message: string;
|
|
14
|
+
environment: Awaited<ReturnType<typeof checkEnvironment>>;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { execa } from "execa";
|
|
2
|
+
import { checkEnvironment, ffmpegInstallCommand } from "../core/env.js";
|
|
3
|
+
import { ensureWorkspace } from "../core/project.js";
|
|
4
|
+
export async function runDoctor() {
|
|
5
|
+
await ensureWorkspace();
|
|
6
|
+
const environment = await checkEnvironment();
|
|
7
|
+
return { ok: environment.ok, environment };
|
|
8
|
+
}
|
|
9
|
+
export async function installCodex(options) {
|
|
10
|
+
if (options.host !== "codex") {
|
|
11
|
+
throw new Error("v1 only supports --host codex");
|
|
12
|
+
}
|
|
13
|
+
await ensureWorkspace();
|
|
14
|
+
const environment = await checkEnvironment();
|
|
15
|
+
if (!environment.ffmpeg.installed) {
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
configured: false,
|
|
19
|
+
environment,
|
|
20
|
+
message: `ffmpeg is required before configuring video-maker-mcp. Install it with: ${environment.ffmpeg.installCommand ?? ffmpegInstallCommand()}`
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (!environment.codex.installed) {
|
|
24
|
+
return {
|
|
25
|
+
ok: false,
|
|
26
|
+
configured: false,
|
|
27
|
+
environment,
|
|
28
|
+
message: "Codex CLI was not found on PATH. Install Codex first, then rerun this installer."
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const already = await execa("codex", ["mcp", "get", "video-maker"], { reject: false });
|
|
32
|
+
if (already.exitCode === 0) {
|
|
33
|
+
return {
|
|
34
|
+
ok: true,
|
|
35
|
+
configured: false,
|
|
36
|
+
environment,
|
|
37
|
+
message: "Codex MCP server 'video-maker' is already configured."
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const packageSpec = options.packageSpec ?? "video-maker-mcp@latest";
|
|
41
|
+
const add = await execa("codex", [
|
|
42
|
+
"mcp", "add", "video-maker", "--",
|
|
43
|
+
"npx", "-y", packageSpec, "serve"
|
|
44
|
+
], { reject: false });
|
|
45
|
+
if (add.exitCode !== 0) {
|
|
46
|
+
throw new Error(`codex mcp add failed:\n${add.stdout}\n${add.stderr}`);
|
|
47
|
+
}
|
|
48
|
+
const smoke = await execa("codex", ["mcp", "get", "video-maker"], { reject: false });
|
|
49
|
+
if (smoke.exitCode !== 0) {
|
|
50
|
+
throw new Error(`codex mcp get video-maker failed after install:\n${smoke.stdout}\n${smoke.stderr}`);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
ok: true,
|
|
54
|
+
configured: true,
|
|
55
|
+
environment,
|
|
56
|
+
message: "Configured Codex MCP server 'video-maker'. Restart active Codex sessions to load it."
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/cli/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAOrD,MAAM,CAAC,KAAK,UAAU,SAAS;IAI7B,MAAM,eAAe,EAAE,CAAC;IACxB,MAAM,WAAW,GAAG,MAAM,gBAAgB,EAAE,CAAC;IAC7C,OAAO,EAAE,EAAE,EAAE,WAAW,CAAC,EAAE,EAAE,WAAW,EAAE,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAuB;IAMxD,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACnD,CAAC;IACD,MAAM,eAAe,EAAE,CAAC;IACxB,MAAM,WAAW,GAAG,MAAM,gBAAgB,EAAE,CAAC;IAC7C,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QAClC,OAAO;YACL,EAAE,EAAE,KAAK;YACT,UAAU,EAAE,KAAK;YACjB,WAAW;YACX,OAAO,EAAE,2EAA2E,WAAW,CAAC,MAAM,CAAC,cAAc,IAAI,oBAAoB,EAAE,EAAE;SAClJ,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;QACjC,OAAO;YACL,EAAE,EAAE,KAAK;YACT,UAAU,EAAE,KAAK;YACjB,WAAW;YACX,OAAO,EAAE,kFAAkF;SAC5F,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACvF,IAAI,OAAO,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO;YACL,EAAE,EAAE,IAAI;YACR,UAAU,EAAE,KAAK;YACjB,WAAW;YACX,OAAO,EAAE,uDAAuD;SACjE,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,wBAAwB,CAAC;IACpE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE;QAC/B,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI;QACjC,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO;KAClC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACtB,IAAI,GAAG,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IACzE,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACrF,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,oDAAoD,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACvG,CAAC;IACD,OAAO;QACL,EAAE,EAAE,IAAI;QACR,UAAU,EAAE,IAAI;QAChB,WAAW;QACX,OAAO,EAAE,sFAAsF;KAChG,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function loadCliEnv(): string | undefined;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import dotenv from "dotenv";
|
|
4
|
+
export function loadCliEnv() {
|
|
5
|
+
const envFile = process.env.VIDEO_MAKER_ENV_FILE ?? path.join(process.cwd(), ".env");
|
|
6
|
+
if (!fs.existsSync(envFile))
|
|
7
|
+
return undefined;
|
|
8
|
+
dotenv.config({ path: envFile, quiet: true });
|
|
9
|
+
return envFile;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=load-env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-env.js","sourceRoot":"","sources":["../../src/cli/load-env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,MAAM,UAAU,UAAU;IACxB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;IACrF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9C,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface RequiredAsset {
|
|
2
|
+
sceneId: string;
|
|
3
|
+
assetPath: string;
|
|
4
|
+
absolutePath: string;
|
|
5
|
+
prompt: string;
|
|
6
|
+
durationSec: number;
|
|
7
|
+
}
|
|
8
|
+
export interface AssetIssue {
|
|
9
|
+
sceneId: string;
|
|
10
|
+
assetPath: string;
|
|
11
|
+
absolutePath: string;
|
|
12
|
+
ok: false;
|
|
13
|
+
error: string;
|
|
14
|
+
}
|
|
15
|
+
export interface VerifiedAsset {
|
|
16
|
+
sceneId: string;
|
|
17
|
+
assetPath: string;
|
|
18
|
+
absolutePath: string;
|
|
19
|
+
ok: true;
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
format?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function listRequiredAssets(projectId: string): Promise<RequiredAsset[]>;
|
|
25
|
+
export declare function verifyAssets(projectId: string): Promise<{
|
|
26
|
+
ok: boolean;
|
|
27
|
+
assets: Array<VerifiedAsset | AssetIssue>;
|
|
28
|
+
}>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import sharp from "sharp";
|
|
3
|
+
import { readVideoPlan, markStatus } from "./project.js";
|
|
4
|
+
import { toProjectPath } from "./paths.js";
|
|
5
|
+
export async function listRequiredAssets(projectId) {
|
|
6
|
+
const plan = await readVideoPlan(projectId);
|
|
7
|
+
return plan.scenes.map((scene) => ({
|
|
8
|
+
sceneId: scene.id,
|
|
9
|
+
assetPath: scene.assetPath,
|
|
10
|
+
absolutePath: toProjectPath(projectId, scene.assetPath),
|
|
11
|
+
prompt: scene.visualPrompt,
|
|
12
|
+
durationSec: scene.durationSec
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
export async function verifyAssets(projectId) {
|
|
16
|
+
const required = await listRequiredAssets(projectId);
|
|
17
|
+
const assets = await Promise.all(required.map(async (asset) => {
|
|
18
|
+
try {
|
|
19
|
+
await fs.access(asset.absolutePath);
|
|
20
|
+
const metadata = await sharp(asset.absolutePath).metadata();
|
|
21
|
+
if (!metadata.width || !metadata.height) {
|
|
22
|
+
throw new Error("Image dimensions could not be read");
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
sceneId: asset.sceneId,
|
|
26
|
+
assetPath: asset.assetPath,
|
|
27
|
+
absolutePath: asset.absolutePath,
|
|
28
|
+
ok: true,
|
|
29
|
+
width: metadata.width,
|
|
30
|
+
height: metadata.height,
|
|
31
|
+
format: metadata.format
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
return {
|
|
36
|
+
sceneId: asset.sceneId,
|
|
37
|
+
assetPath: asset.assetPath,
|
|
38
|
+
absolutePath: asset.absolutePath,
|
|
39
|
+
ok: false,
|
|
40
|
+
error: error instanceof Error ? error.message : String(error)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}));
|
|
44
|
+
const ok = assets.every((asset) => asset.ok);
|
|
45
|
+
if (ok) {
|
|
46
|
+
await markStatus(projectId, "assets_ready");
|
|
47
|
+
}
|
|
48
|
+
return { ok, assets };
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.js","sourceRoot":"","sources":["../../src/core/assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA4B3C,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,SAAiB;IACxD,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,SAAS,CAAC,CAAC;IAC5C,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACjC,OAAO,EAAE,KAAK,CAAC,EAAE;QACjB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,YAAY,EAAE,aAAa,CAAC,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC;QACvD,MAAM,EAAE,KAAK,CAAC,YAAY;QAC1B,WAAW,EAAE,KAAK,CAAC,WAAW;KAC/B,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,SAAiB;IAIlD,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,SAAS,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAuC,EAAE;QACjG,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YACpC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAC;YAC5D,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;gBACxC,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;YACxD,CAAC;YACD,OAAO;gBACL,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,YAAY,EAAE,KAAK,CAAC,YAAY;gBAChC,EAAE,EAAE,IAAI;gBACR,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,MAAM,EAAE,QAAQ,CAAC,MAAM;aACxB,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,YAAY,EAAE,KAAK,CAAC,YAAY;gBAChC,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC,CAAC;IACJ,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC7C,IAAI,EAAE,EAAE,CAAC;QACP,MAAM,UAAU,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface BinaryCheck {
|
|
2
|
+
installed: boolean;
|
|
3
|
+
path?: string;
|
|
4
|
+
version?: string;
|
|
5
|
+
installCommand?: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface TtsCheck {
|
|
9
|
+
configured: boolean;
|
|
10
|
+
missingEnv: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface EnvironmentCheck {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
node: {
|
|
15
|
+
version: string;
|
|
16
|
+
ok: boolean;
|
|
17
|
+
};
|
|
18
|
+
ffmpeg: BinaryCheck;
|
|
19
|
+
codex: BinaryCheck;
|
|
20
|
+
tts: TtsCheck;
|
|
21
|
+
workspaceDir: string;
|
|
22
|
+
defaultExportDir: string;
|
|
23
|
+
nextActions: string[];
|
|
24
|
+
}
|
|
25
|
+
export declare function ffmpegInstallCommand(platform?: NodeJS.Platform): string;
|
|
26
|
+
export declare function resolveBinary(name: string, args?: string[]): Promise<BinaryCheck>;
|
|
27
|
+
export declare function checkEnvironment(): Promise<EnvironmentCheck>;
|
package/dist/core/env.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import { execa } from "execa";
|
|
3
|
+
import which from "which";
|
|
4
|
+
import { exportDir, workspaceDir } from "./paths.js";
|
|
5
|
+
function missingTtsEnv(env = process.env) {
|
|
6
|
+
const missing = [];
|
|
7
|
+
if (!env.VIDEO_MAKER_TTS_VOICE_ID)
|
|
8
|
+
missing.push("VIDEO_MAKER_TTS_VOICE_ID");
|
|
9
|
+
if (env.VIDEO_MAKER_TTS_API_KEY)
|
|
10
|
+
return missing;
|
|
11
|
+
if (env.VIDEO_MAKER_TTS_APP_ID && (env.VIDEO_MAKER_TTS_TOKEN || env.VIDEO_MAKER_TTS_ACCESS_TOKEN || env.VOLCENGINE_SECRET_ACCESS_KEY)) {
|
|
12
|
+
return missing;
|
|
13
|
+
}
|
|
14
|
+
missing.push("VIDEO_MAKER_TTS_API_KEY");
|
|
15
|
+
return missing;
|
|
16
|
+
}
|
|
17
|
+
export function ffmpegInstallCommand(platform = process.platform) {
|
|
18
|
+
if (platform === "darwin")
|
|
19
|
+
return "brew install ffmpeg";
|
|
20
|
+
if (platform === "win32")
|
|
21
|
+
return "winget install Gyan.FFmpeg";
|
|
22
|
+
if (platform === "linux") {
|
|
23
|
+
const distro = os.release().toLowerCase();
|
|
24
|
+
if (distro.includes("arch"))
|
|
25
|
+
return "sudo pacman -S ffmpeg";
|
|
26
|
+
if (distro.includes("fedora"))
|
|
27
|
+
return "sudo dnf install -y ffmpeg";
|
|
28
|
+
return "sudo apt update && sudo apt install -y ffmpeg";
|
|
29
|
+
}
|
|
30
|
+
return "Install ffmpeg from https://ffmpeg.org/download.html and ensure ffmpeg is on PATH";
|
|
31
|
+
}
|
|
32
|
+
export async function resolveBinary(name, args = ["--version"]) {
|
|
33
|
+
try {
|
|
34
|
+
const binaryPath = await which(name);
|
|
35
|
+
const result = await execa(binaryPath, args, { reject: false });
|
|
36
|
+
const version = `${result.stdout}\n${result.stderr}`.trim().split("\n")[0];
|
|
37
|
+
return {
|
|
38
|
+
installed: result.exitCode === 0,
|
|
39
|
+
path: binaryPath,
|
|
40
|
+
version,
|
|
41
|
+
error: result.exitCode === 0 ? undefined : `${name} exited with ${result.exitCode}`
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return {
|
|
46
|
+
installed: false,
|
|
47
|
+
installCommand: name === "ffmpeg" ? ffmpegInstallCommand() : undefined,
|
|
48
|
+
error: error instanceof Error ? error.message : String(error)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export async function checkEnvironment() {
|
|
53
|
+
const nodeMajor = Number(process.versions.node.split(".")[0]);
|
|
54
|
+
const [ffmpeg, codex] = await Promise.all([
|
|
55
|
+
resolveBinary("ffmpeg", ["-version"]),
|
|
56
|
+
resolveBinary("codex", ["--version"])
|
|
57
|
+
]);
|
|
58
|
+
const missingEnv = missingTtsEnv();
|
|
59
|
+
const nextActions = [];
|
|
60
|
+
if (!ffmpeg.installed) {
|
|
61
|
+
nextActions.push(`Install ffmpeg: ${ffmpeg.installCommand ?? ffmpegInstallCommand()}`);
|
|
62
|
+
}
|
|
63
|
+
if (!codex.installed) {
|
|
64
|
+
nextActions.push("Install Codex CLI before using the Codex installer integration.");
|
|
65
|
+
}
|
|
66
|
+
if (missingEnv.length > 0) {
|
|
67
|
+
nextActions.push(`Set TTS environment variables: ${missingEnv.join(", ")}`);
|
|
68
|
+
}
|
|
69
|
+
if (nodeMajor < 20) {
|
|
70
|
+
nextActions.push("Install Node.js 20 or newer.");
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
ok: nodeMajor >= 20 && ffmpeg.installed && missingEnv.length === 0,
|
|
74
|
+
node: {
|
|
75
|
+
version: process.versions.node,
|
|
76
|
+
ok: nodeMajor >= 20
|
|
77
|
+
},
|
|
78
|
+
ffmpeg,
|
|
79
|
+
codex,
|
|
80
|
+
tts: {
|
|
81
|
+
configured: missingEnv.length === 0,
|
|
82
|
+
missingEnv
|
|
83
|
+
},
|
|
84
|
+
workspaceDir: workspaceDir(),
|
|
85
|
+
defaultExportDir: exportDir(),
|
|
86
|
+
nextActions
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/core/env.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ExportVideoResult {
|
|
2
|
+
projectId: string;
|
|
3
|
+
exportDir: string;
|
|
4
|
+
files: {
|
|
5
|
+
video: string;
|
|
6
|
+
subtitles?: string;
|
|
7
|
+
manifest: string;
|
|
8
|
+
plan?: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export declare function exportProjectVideo(projectId: string, outputDir?: string): Promise<ExportVideoResult>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { exportDir, resolveUserPath, toProjectPath } from "./paths.js";
|
|
4
|
+
import { markStatus, readManifest } from "./project.js";
|
|
5
|
+
export async function exportProjectVideo(projectId, outputDir) {
|
|
6
|
+
const manifest = await readManifest(projectId);
|
|
7
|
+
if (!manifest.outputPath) {
|
|
8
|
+
throw new Error("Project video is missing. Run render_video before export_video.");
|
|
9
|
+
}
|
|
10
|
+
const destinationDir = outputDir ? resolveUserPath(outputDir) : path.join(exportDir(), projectId);
|
|
11
|
+
await fs.mkdir(destinationDir, { recursive: true });
|
|
12
|
+
const videoPath = path.join(destinationDir, "final.mp4");
|
|
13
|
+
const manifestPath = path.join(destinationDir, "manifest.json");
|
|
14
|
+
const subtitlePath = manifest.subtitlePath ? path.join(destinationDir, "subtitles.srt") : undefined;
|
|
15
|
+
const planPath = manifest.planPath ? path.join(destinationDir, "video_plan.json") : undefined;
|
|
16
|
+
await markStatus(projectId, "rendered", {
|
|
17
|
+
exportedPath: videoPath,
|
|
18
|
+
exportedAt: new Date().toISOString()
|
|
19
|
+
});
|
|
20
|
+
await fs.copyFile(toProjectPath(projectId, manifest.outputPath), videoPath);
|
|
21
|
+
await fs.copyFile(toProjectPath(projectId, "manifest.json"), manifestPath);
|
|
22
|
+
if (manifest.subtitlePath && subtitlePath) {
|
|
23
|
+
await fs.copyFile(toProjectPath(projectId, manifest.subtitlePath), subtitlePath);
|
|
24
|
+
}
|
|
25
|
+
if (manifest.planPath && planPath) {
|
|
26
|
+
await fs.copyFile(toProjectPath(projectId, manifest.planPath), planPath);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
projectId,
|
|
30
|
+
exportDir: destinationDir,
|
|
31
|
+
files: {
|
|
32
|
+
video: videoPath,
|
|
33
|
+
subtitles: subtitlePath,
|
|
34
|
+
manifest: manifestPath,
|
|
35
|
+
plan: planPath
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/core/export.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAaxD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,SAAiB,EAAE,SAAkB;IAC5E,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;IACrF,CAAC;IAED,MAAM,cAAc,GAAG,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,CAAC,CAAC;IAClG,MAAM,EAAE,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEpD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IACzD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC;IAChE,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACpG,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE9F,MAAM,UAAU,CAAC,SAAS,EAAE,UAAU,EAAE;QACtC,YAAY,EAAE,SAAS;QACvB,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACrC,CAAC,CAAC;IAEH,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,CAAC,CAAC;IAC5E,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,eAAe,CAAC,EAAE,YAAY,CAAC,CAAC;IAC3E,IAAI,QAAQ,CAAC,YAAY,IAAI,YAAY,EAAE,CAAC;QAC1C,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC,CAAC;IACnF,CAAC;IACD,IAAI,QAAQ,CAAC,QAAQ,IAAI,QAAQ,EAAE,CAAC;QAClC,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC3E,CAAC;IAED,OAAO;QACL,SAAS;QACT,SAAS,EAAE,cAAc;QACzB,KAAK,EAAE;YACL,KAAK,EAAE,SAAS;YAChB,SAAS,EAAE,YAAY;YACvB,QAAQ,EAAE,YAAY;YACtB,IAAI,EAAE,QAAQ;SACf;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const DEFAULT_WORKSPACE_DIR: string;
|
|
2
|
+
export declare const DEFAULT_EXPORT_DIR: string;
|
|
3
|
+
export declare const PROJECTS_DIR = "projects";
|
|
4
|
+
export declare function workspaceDir(): string;
|
|
5
|
+
export declare function exportDir(): string;
|
|
6
|
+
export declare function resolveUserPath(input: string): string;
|
|
7
|
+
export declare function projectsDir(): string;
|
|
8
|
+
export declare function projectDir(projectId: string): string;
|
|
9
|
+
export declare function toProjectPath(projectId: string, relativePath: string): string;
|
|
10
|
+
export declare function assertSafeProjectId(projectId: string): void;
|
|
11
|
+
export declare function assertProjectRelativePath(relativePath: string): void;
|
|
12
|
+
export declare function makeProjectId(date?: Date): string;
|