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
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export const DEFAULT_WORKSPACE_DIR = path.join(os.homedir(), ".video-maker");
|
|
4
|
+
export const DEFAULT_EXPORT_DIR = path.join(os.homedir(), "Downloads", "video-maker");
|
|
5
|
+
export const PROJECTS_DIR = "projects";
|
|
6
|
+
export function workspaceDir() {
|
|
7
|
+
return process.env.VIDEO_MAKER_WORKSPACE ?? DEFAULT_WORKSPACE_DIR;
|
|
8
|
+
}
|
|
9
|
+
export function exportDir() {
|
|
10
|
+
return process.env.VIDEO_MAKER_EXPORT_DIR ? resolveUserPath(process.env.VIDEO_MAKER_EXPORT_DIR) : DEFAULT_EXPORT_DIR;
|
|
11
|
+
}
|
|
12
|
+
export function resolveUserPath(input) {
|
|
13
|
+
if (input === "~")
|
|
14
|
+
return os.homedir();
|
|
15
|
+
if (input.startsWith("~/"))
|
|
16
|
+
return path.join(os.homedir(), input.slice(2));
|
|
17
|
+
return path.resolve(input);
|
|
18
|
+
}
|
|
19
|
+
export function projectsDir() {
|
|
20
|
+
return path.join(workspaceDir(), PROJECTS_DIR);
|
|
21
|
+
}
|
|
22
|
+
export function projectDir(projectId) {
|
|
23
|
+
assertSafeProjectId(projectId);
|
|
24
|
+
return path.join(projectsDir(), projectId);
|
|
25
|
+
}
|
|
26
|
+
export function toProjectPath(projectId, relativePath) {
|
|
27
|
+
assertProjectRelativePath(relativePath);
|
|
28
|
+
const root = projectDir(projectId);
|
|
29
|
+
const resolved = path.resolve(root, relativePath);
|
|
30
|
+
if (!resolved.startsWith(root + path.sep) && resolved !== root) {
|
|
31
|
+
throw new Error(`Path escapes project directory: ${relativePath}`);
|
|
32
|
+
}
|
|
33
|
+
return resolved;
|
|
34
|
+
}
|
|
35
|
+
export function assertSafeProjectId(projectId) {
|
|
36
|
+
if (!/^[a-z0-9][a-z0-9_-]{5,63}$/i.test(projectId)) {
|
|
37
|
+
throw new Error(`Invalid projectId: ${projectId}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function assertProjectRelativePath(relativePath) {
|
|
41
|
+
if (path.isAbsolute(relativePath)) {
|
|
42
|
+
throw new Error(`Project paths must be relative: ${relativePath}`);
|
|
43
|
+
}
|
|
44
|
+
if (relativePath.includes("\0")) {
|
|
45
|
+
throw new Error("Project paths cannot contain null bytes");
|
|
46
|
+
}
|
|
47
|
+
const normalized = path.normalize(relativePath);
|
|
48
|
+
if (normalized === "." || normalized.startsWith("..") || normalized.includes(`${path.sep}..${path.sep}`)) {
|
|
49
|
+
throw new Error(`Project path escapes project directory: ${relativePath}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function makeProjectId(date = new Date()) {
|
|
53
|
+
const stamp = date.toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
54
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
55
|
+
return `vid_${stamp}_${random}`;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/core/paths.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
export declare const sceneSchema: z.ZodObject<{
|
|
3
|
+
id: z.ZodString;
|
|
4
|
+
durationSec: z.ZodNumber;
|
|
5
|
+
narration: z.ZodString;
|
|
6
|
+
subtitle: z.ZodString;
|
|
7
|
+
visualPrompt: z.ZodString;
|
|
8
|
+
assetPath: z.ZodString;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export declare const videoPlanSchema: z.ZodObject<{
|
|
11
|
+
title: z.ZodString;
|
|
12
|
+
aspectRatio: z.ZodDefault<z.ZodLiteral<"9:16">>;
|
|
13
|
+
language: z.ZodDefault<z.ZodLiteral<"zh-CN">>;
|
|
14
|
+
style: z.ZodDefault<z.ZodString>;
|
|
15
|
+
scenes: z.ZodArray<z.ZodObject<{
|
|
16
|
+
id: z.ZodString;
|
|
17
|
+
durationSec: z.ZodNumber;
|
|
18
|
+
narration: z.ZodString;
|
|
19
|
+
subtitle: z.ZodString;
|
|
20
|
+
visualPrompt: z.ZodString;
|
|
21
|
+
assetPath: z.ZodString;
|
|
22
|
+
}, z.core.$strip>>;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
export type Scene = z.infer<typeof sceneSchema>;
|
|
25
|
+
export type VideoPlan = z.infer<typeof videoPlanSchema>;
|
|
26
|
+
export declare const videoPlanJsonSchema: {
|
|
27
|
+
readonly type: "object";
|
|
28
|
+
readonly required: readonly ["title", "aspectRatio", "language", "style", "scenes"];
|
|
29
|
+
readonly additionalProperties: false;
|
|
30
|
+
readonly properties: {
|
|
31
|
+
readonly title: {
|
|
32
|
+
readonly type: "string";
|
|
33
|
+
readonly minLength: 1;
|
|
34
|
+
readonly maxLength: 100;
|
|
35
|
+
};
|
|
36
|
+
readonly aspectRatio: {
|
|
37
|
+
readonly const: "9:16";
|
|
38
|
+
};
|
|
39
|
+
readonly language: {
|
|
40
|
+
readonly const: "zh-CN";
|
|
41
|
+
};
|
|
42
|
+
readonly style: {
|
|
43
|
+
readonly type: "string";
|
|
44
|
+
readonly minLength: 1;
|
|
45
|
+
readonly maxLength: 80;
|
|
46
|
+
};
|
|
47
|
+
readonly scenes: {
|
|
48
|
+
readonly type: "array";
|
|
49
|
+
readonly minItems: 1;
|
|
50
|
+
readonly maxItems: 20;
|
|
51
|
+
readonly items: {
|
|
52
|
+
readonly type: "object";
|
|
53
|
+
readonly required: readonly ["id", "durationSec", "narration", "subtitle", "visualPrompt", "assetPath"];
|
|
54
|
+
readonly additionalProperties: false;
|
|
55
|
+
readonly properties: {
|
|
56
|
+
readonly id: {
|
|
57
|
+
readonly type: "string";
|
|
58
|
+
readonly pattern: "^scene_[0-9]{3,}$";
|
|
59
|
+
};
|
|
60
|
+
readonly durationSec: {
|
|
61
|
+
readonly type: "number";
|
|
62
|
+
readonly minimum: 4;
|
|
63
|
+
readonly maximum: 12;
|
|
64
|
+
};
|
|
65
|
+
readonly narration: {
|
|
66
|
+
readonly type: "string";
|
|
67
|
+
readonly minLength: 1;
|
|
68
|
+
readonly maxLength: 800;
|
|
69
|
+
};
|
|
70
|
+
readonly subtitle: {
|
|
71
|
+
readonly type: "string";
|
|
72
|
+
readonly minLength: 1;
|
|
73
|
+
readonly maxLength: 120;
|
|
74
|
+
};
|
|
75
|
+
readonly visualPrompt: {
|
|
76
|
+
readonly type: "string";
|
|
77
|
+
readonly minLength: 8;
|
|
78
|
+
readonly maxLength: 1200;
|
|
79
|
+
};
|
|
80
|
+
readonly assetPath: {
|
|
81
|
+
readonly type: "string";
|
|
82
|
+
readonly pattern: "^assets/[a-zA-Z0-9_.-]+\\.(png|jpg|jpeg|webp)$";
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
export declare function parseVideoPlan(input: unknown): VideoPlan;
|
|
90
|
+
export declare function totalDurationSec(plan: VideoPlan): number;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import * as z from "zod/v4";
|
|
3
|
+
const projectAssetPath = z.string().superRefine((value, ctx) => {
|
|
4
|
+
if (path.isAbsolute(value)) {
|
|
5
|
+
ctx.addIssue({ code: "custom", message: "assetPath must be project-relative" });
|
|
6
|
+
}
|
|
7
|
+
if (!/^assets\/[a-zA-Z0-9_.-]+\.(png|jpg|jpeg|webp)$/.test(value)) {
|
|
8
|
+
ctx.addIssue({
|
|
9
|
+
code: "custom",
|
|
10
|
+
message: "assetPath must look like assets/scene_001.png, .jpg, .jpeg, or .webp"
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
if (value.includes("..")) {
|
|
14
|
+
ctx.addIssue({ code: "custom", message: "assetPath cannot contain .." });
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
export const sceneSchema = z.object({
|
|
18
|
+
id: z.string().regex(/^scene_[0-9]{3,}$/),
|
|
19
|
+
durationSec: z.number().min(4).max(12),
|
|
20
|
+
narration: z.string().min(1).max(800),
|
|
21
|
+
subtitle: z.string().min(1).max(120),
|
|
22
|
+
visualPrompt: z.string().min(8).max(1200),
|
|
23
|
+
assetPath: projectAssetPath
|
|
24
|
+
});
|
|
25
|
+
export const videoPlanSchema = z.object({
|
|
26
|
+
title: z.string().min(1).max(100),
|
|
27
|
+
aspectRatio: z.literal("9:16").default("9:16"),
|
|
28
|
+
language: z.literal("zh-CN").default("zh-CN"),
|
|
29
|
+
style: z.string().min(1).max(80).default("short_video_explainer"),
|
|
30
|
+
scenes: z.array(sceneSchema).min(1).max(20)
|
|
31
|
+
}).superRefine((plan, ctx) => {
|
|
32
|
+
const totalDuration = plan.scenes.reduce((sum, scene) => sum + scene.durationSec, 0);
|
|
33
|
+
if (totalDuration < 30 || totalDuration > 90) {
|
|
34
|
+
ctx.addIssue({
|
|
35
|
+
code: "custom",
|
|
36
|
+
path: ["scenes"],
|
|
37
|
+
message: `total duration must be between 30 and 90 seconds; got ${totalDuration}`
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const ids = new Set();
|
|
41
|
+
const paths = new Set();
|
|
42
|
+
for (const scene of plan.scenes) {
|
|
43
|
+
if (ids.has(scene.id)) {
|
|
44
|
+
ctx.addIssue({ code: "custom", path: ["scenes"], message: `duplicate scene id: ${scene.id}` });
|
|
45
|
+
}
|
|
46
|
+
if (paths.has(scene.assetPath)) {
|
|
47
|
+
ctx.addIssue({ code: "custom", path: ["scenes"], message: `duplicate assetPath: ${scene.assetPath}` });
|
|
48
|
+
}
|
|
49
|
+
ids.add(scene.id);
|
|
50
|
+
paths.add(scene.assetPath);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
export const videoPlanJsonSchema = {
|
|
54
|
+
type: "object",
|
|
55
|
+
required: ["title", "aspectRatio", "language", "style", "scenes"],
|
|
56
|
+
additionalProperties: false,
|
|
57
|
+
properties: {
|
|
58
|
+
title: { type: "string", minLength: 1, maxLength: 100 },
|
|
59
|
+
aspectRatio: { const: "9:16" },
|
|
60
|
+
language: { const: "zh-CN" },
|
|
61
|
+
style: { type: "string", minLength: 1, maxLength: 80 },
|
|
62
|
+
scenes: {
|
|
63
|
+
type: "array",
|
|
64
|
+
minItems: 1,
|
|
65
|
+
maxItems: 20,
|
|
66
|
+
items: {
|
|
67
|
+
type: "object",
|
|
68
|
+
required: ["id", "durationSec", "narration", "subtitle", "visualPrompt", "assetPath"],
|
|
69
|
+
additionalProperties: false,
|
|
70
|
+
properties: {
|
|
71
|
+
id: { type: "string", pattern: "^scene_[0-9]{3,}$" },
|
|
72
|
+
durationSec: { type: "number", minimum: 4, maximum: 12 },
|
|
73
|
+
narration: { type: "string", minLength: 1, maxLength: 800 },
|
|
74
|
+
subtitle: { type: "string", minLength: 1, maxLength: 120 },
|
|
75
|
+
visualPrompt: { type: "string", minLength: 8, maxLength: 1200 },
|
|
76
|
+
assetPath: {
|
|
77
|
+
type: "string",
|
|
78
|
+
pattern: "^assets/[a-zA-Z0-9_.-]+\\.(png|jpg|jpeg|webp)$"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
export function parseVideoPlan(input) {
|
|
86
|
+
return videoPlanSchema.parse(input);
|
|
87
|
+
}
|
|
88
|
+
export function totalDurationSec(plan) {
|
|
89
|
+
return plan.scenes.reduce((sum, scene) => sum + scene.durationSec, 0);
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=plan-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-schema.js","sourceRoot":"","sources":["../../src/core/plan-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type VideoPlan } from "./plan-schema.js";
|
|
2
|
+
export interface VideoProjectManifest {
|
|
3
|
+
projectId: string;
|
|
4
|
+
prompt: string;
|
|
5
|
+
createdAt: string;
|
|
6
|
+
updatedAt: string;
|
|
7
|
+
status: "created" | "planned" | "assets_ready" | "audio_ready" | "rendered";
|
|
8
|
+
options: {
|
|
9
|
+
aspectRatio: "9:16";
|
|
10
|
+
width: 1080;
|
|
11
|
+
height: 1920;
|
|
12
|
+
language: "zh-CN";
|
|
13
|
+
};
|
|
14
|
+
planPath?: string;
|
|
15
|
+
audioPath?: string;
|
|
16
|
+
subtitlePath?: string;
|
|
17
|
+
outputPath?: string;
|
|
18
|
+
exportedPath?: string;
|
|
19
|
+
exportedAt?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface CreateProjectOptions {
|
|
22
|
+
projectId?: string;
|
|
23
|
+
aspectRatio?: "9:16";
|
|
24
|
+
language?: "zh-CN";
|
|
25
|
+
}
|
|
26
|
+
export declare function createVideoProject(prompt: string, options?: CreateProjectOptions): Promise<{
|
|
27
|
+
projectId: string;
|
|
28
|
+
projectDir: string;
|
|
29
|
+
manifest: VideoProjectManifest;
|
|
30
|
+
}>;
|
|
31
|
+
export declare function ensureWorkspace(): Promise<string>;
|
|
32
|
+
export declare function readManifest(projectId: string): Promise<VideoProjectManifest>;
|
|
33
|
+
export declare function writeManifest(projectId: string, manifest: VideoProjectManifest): Promise<void>;
|
|
34
|
+
export declare function saveVideoPlan(projectId: string, input: unknown): Promise<{
|
|
35
|
+
projectId: string;
|
|
36
|
+
plan: VideoPlan;
|
|
37
|
+
totalDurationSec: number;
|
|
38
|
+
planPath: string;
|
|
39
|
+
}>;
|
|
40
|
+
export declare function readVideoPlan(projectId: string): Promise<VideoPlan>;
|
|
41
|
+
export declare function markStatus(projectId: string, status: VideoProjectManifest["status"], updates?: Partial<VideoProjectManifest>): Promise<VideoProjectManifest>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { videoPlanSchema, totalDurationSec } from "./plan-schema.js";
|
|
4
|
+
import { makeProjectId, projectDir, projectsDir, toProjectPath } from "./paths.js";
|
|
5
|
+
const MANIFEST_FILE = "manifest.json";
|
|
6
|
+
const PLAN_FILE = "video_plan.json";
|
|
7
|
+
export async function createVideoProject(prompt, options = {}) {
|
|
8
|
+
const projectId = options.projectId ?? makeProjectId();
|
|
9
|
+
const root = projectDir(projectId);
|
|
10
|
+
await fs.mkdir(path.join(root, "assets"), { recursive: true });
|
|
11
|
+
await fs.mkdir(path.join(root, "audio"), { recursive: true });
|
|
12
|
+
await fs.mkdir(path.join(root, "output"), { recursive: true });
|
|
13
|
+
const now = new Date().toISOString();
|
|
14
|
+
const manifest = {
|
|
15
|
+
projectId,
|
|
16
|
+
prompt,
|
|
17
|
+
createdAt: now,
|
|
18
|
+
updatedAt: now,
|
|
19
|
+
status: "created",
|
|
20
|
+
options: {
|
|
21
|
+
aspectRatio: "9:16",
|
|
22
|
+
width: 1080,
|
|
23
|
+
height: 1920,
|
|
24
|
+
language: "zh-CN"
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
await writeManifest(projectId, manifest);
|
|
28
|
+
return { projectId, projectDir: root, manifest };
|
|
29
|
+
}
|
|
30
|
+
export async function ensureWorkspace() {
|
|
31
|
+
const root = projectsDir();
|
|
32
|
+
await fs.mkdir(root, { recursive: true });
|
|
33
|
+
return root;
|
|
34
|
+
}
|
|
35
|
+
export async function readManifest(projectId) {
|
|
36
|
+
const content = await fs.readFile(toProjectPath(projectId, MANIFEST_FILE), "utf8");
|
|
37
|
+
return JSON.parse(content);
|
|
38
|
+
}
|
|
39
|
+
export async function writeManifest(projectId, manifest) {
|
|
40
|
+
manifest.updatedAt = new Date().toISOString();
|
|
41
|
+
await fs.writeFile(toProjectPath(projectId, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
42
|
+
}
|
|
43
|
+
export async function saveVideoPlan(projectId, input) {
|
|
44
|
+
const plan = videoPlanSchema.parse(input);
|
|
45
|
+
const manifest = await readManifest(projectId);
|
|
46
|
+
const planPath = PLAN_FILE;
|
|
47
|
+
await fs.writeFile(toProjectPath(projectId, planPath), `${JSON.stringify(plan, null, 2)}\n`);
|
|
48
|
+
manifest.status = "planned";
|
|
49
|
+
manifest.planPath = planPath;
|
|
50
|
+
await writeManifest(projectId, manifest);
|
|
51
|
+
return {
|
|
52
|
+
projectId,
|
|
53
|
+
plan,
|
|
54
|
+
totalDurationSec: totalDurationSec(plan),
|
|
55
|
+
planPath: toProjectPath(projectId, planPath)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export async function readVideoPlan(projectId) {
|
|
59
|
+
const manifest = await readManifest(projectId);
|
|
60
|
+
if (!manifest.planPath) {
|
|
61
|
+
throw new Error(`Project ${projectId} has no saved video plan`);
|
|
62
|
+
}
|
|
63
|
+
const content = await fs.readFile(toProjectPath(projectId, manifest.planPath), "utf8");
|
|
64
|
+
return videoPlanSchema.parse(JSON.parse(content));
|
|
65
|
+
}
|
|
66
|
+
export async function markStatus(projectId, status, updates = {}) {
|
|
67
|
+
const manifest = await readManifest(projectId);
|
|
68
|
+
const next = { ...manifest, ...updates, status };
|
|
69
|
+
await writeManifest(projectId, next);
|
|
70
|
+
return next;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/core/project.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ExportVideoResult } from "./export.js";
|
|
2
|
+
export interface RenderResult {
|
|
3
|
+
projectId: string;
|
|
4
|
+
outputPath: string;
|
|
5
|
+
absoluteOutputPath: string;
|
|
6
|
+
subtitlePath: string;
|
|
7
|
+
manifestPath: string;
|
|
8
|
+
export?: ExportVideoResult;
|
|
9
|
+
}
|
|
10
|
+
export declare function renderProjectVideo(projectId: string, options?: {
|
|
11
|
+
outputDir?: string;
|
|
12
|
+
}): Promise<RenderResult>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execa } from "execa";
|
|
4
|
+
import { resolveBinary } from "./env.js";
|
|
5
|
+
import { exportProjectVideo } from "./export.js";
|
|
6
|
+
import { verifyAssets } from "./assets.js";
|
|
7
|
+
import { markStatus, readManifest, readVideoPlan } from "./project.js";
|
|
8
|
+
import { toProjectPath } from "./paths.js";
|
|
9
|
+
import { writeSrt } from "./subtitles.js";
|
|
10
|
+
function escapeConcatPath(filePath) {
|
|
11
|
+
return filePath.replace(/'/g, "'\\''");
|
|
12
|
+
}
|
|
13
|
+
function escapeSubtitlePath(filePath) {
|
|
14
|
+
return filePath.replace(/\\/g, "/").replace(/:/g, "\\:");
|
|
15
|
+
}
|
|
16
|
+
export async function renderProjectVideo(projectId, options = {}) {
|
|
17
|
+
const ffmpeg = await resolveBinary("ffmpeg", ["-version"]);
|
|
18
|
+
if (!ffmpeg.installed || !ffmpeg.path) {
|
|
19
|
+
throw new Error(`ffmpeg is not available. Install it with: ${ffmpeg.installCommand}`);
|
|
20
|
+
}
|
|
21
|
+
const assetCheck = await verifyAssets(projectId);
|
|
22
|
+
if (!assetCheck.ok) {
|
|
23
|
+
throw new Error(`Cannot render until all assets are valid: ${JSON.stringify(assetCheck.assets)}`);
|
|
24
|
+
}
|
|
25
|
+
const plan = await readVideoPlan(projectId);
|
|
26
|
+
const manifest = await readManifest(projectId);
|
|
27
|
+
if (!manifest.audioPath) {
|
|
28
|
+
throw new Error("Project audio is missing. Run generate_audio before render_video.");
|
|
29
|
+
}
|
|
30
|
+
const subtitlePath = await writeSrt(projectId, plan);
|
|
31
|
+
const segmentDir = toProjectPath(projectId, "output/segments");
|
|
32
|
+
await fs.mkdir(segmentDir, { recursive: true });
|
|
33
|
+
const segmentPaths = [];
|
|
34
|
+
for (const [index, scene] of plan.scenes.entries()) {
|
|
35
|
+
const inputImage = toProjectPath(projectId, scene.assetPath);
|
|
36
|
+
const segmentPath = path.join(segmentDir, `${String(index + 1).padStart(3, "0")}_${scene.id}.mp4`);
|
|
37
|
+
const frames = Math.max(1, Math.round(scene.durationSec * 30));
|
|
38
|
+
const vf = [
|
|
39
|
+
"scale=1080:1920:force_original_aspect_ratio=increase",
|
|
40
|
+
"crop=1080:1920",
|
|
41
|
+
`zoompan=z='min(zoom+0.0008,1.06)':d=${frames}:s=1080x1920:fps=30`,
|
|
42
|
+
"format=yuv420p"
|
|
43
|
+
].join(",");
|
|
44
|
+
await execa(ffmpeg.path, [
|
|
45
|
+
"-y",
|
|
46
|
+
"-loop", "1",
|
|
47
|
+
"-i", inputImage,
|
|
48
|
+
"-t", String(scene.durationSec),
|
|
49
|
+
"-vf", vf,
|
|
50
|
+
"-r", "30",
|
|
51
|
+
"-an",
|
|
52
|
+
"-c:v", "libx264",
|
|
53
|
+
"-pix_fmt", "yuv420p",
|
|
54
|
+
segmentPath
|
|
55
|
+
]);
|
|
56
|
+
segmentPaths.push(segmentPath);
|
|
57
|
+
}
|
|
58
|
+
const concatFile = toProjectPath(projectId, "output/segments.txt");
|
|
59
|
+
await fs.writeFile(concatFile, segmentPaths.map((file) => `file '${escapeConcatPath(file)}'`).join("\n"));
|
|
60
|
+
const stitchedPath = toProjectPath(projectId, "output/video_silent.mp4");
|
|
61
|
+
await execa(ffmpeg.path, [
|
|
62
|
+
"-y",
|
|
63
|
+
"-f", "concat",
|
|
64
|
+
"-safe", "0",
|
|
65
|
+
"-i", concatFile,
|
|
66
|
+
"-c", "copy",
|
|
67
|
+
stitchedPath
|
|
68
|
+
]);
|
|
69
|
+
const outputPath = "output/final.mp4";
|
|
70
|
+
const absoluteOutputPath = toProjectPath(projectId, outputPath);
|
|
71
|
+
const absoluteSubtitlePath = toProjectPath(projectId, subtitlePath);
|
|
72
|
+
const audioPath = toProjectPath(projectId, manifest.audioPath);
|
|
73
|
+
await execa(ffmpeg.path, [
|
|
74
|
+
"-y",
|
|
75
|
+
"-i", stitchedPath,
|
|
76
|
+
"-i", audioPath,
|
|
77
|
+
"-vf", `subtitles='${escapeSubtitlePath(absoluteSubtitlePath)}':force_style='Fontsize=12,Alignment=2,MarginV=90'`,
|
|
78
|
+
"-c:v", "libx264",
|
|
79
|
+
"-pix_fmt", "yuv420p",
|
|
80
|
+
"-c:a", "aac",
|
|
81
|
+
"-shortest",
|
|
82
|
+
absoluteOutputPath
|
|
83
|
+
]);
|
|
84
|
+
await markStatus(projectId, "rendered", {
|
|
85
|
+
subtitlePath,
|
|
86
|
+
outputPath
|
|
87
|
+
});
|
|
88
|
+
const renderResult = {
|
|
89
|
+
projectId,
|
|
90
|
+
outputPath,
|
|
91
|
+
absoluteOutputPath,
|
|
92
|
+
subtitlePath,
|
|
93
|
+
manifestPath: toProjectPath(projectId, "manifest.json")
|
|
94
|
+
};
|
|
95
|
+
if (options.outputDir) {
|
|
96
|
+
renderResult.export = await exportProjectVideo(projectId, options.outputDir);
|
|
97
|
+
}
|
|
98
|
+
return renderResult;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/core/render.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { toProjectPath } from "./paths.js";
|
|
3
|
+
export function formatSrtTimestamp(seconds) {
|
|
4
|
+
const ms = Math.max(0, Math.round(seconds * 1000));
|
|
5
|
+
const hours = Math.floor(ms / 3_600_000);
|
|
6
|
+
const minutes = Math.floor((ms % 3_600_000) / 60_000);
|
|
7
|
+
const secs = Math.floor((ms % 60_000) / 1000);
|
|
8
|
+
const millis = ms % 1000;
|
|
9
|
+
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")},${String(millis).padStart(3, "0")}`;
|
|
10
|
+
}
|
|
11
|
+
export function buildSrt(plan) {
|
|
12
|
+
let cursor = 0;
|
|
13
|
+
return plan.scenes.map((scene, index) => {
|
|
14
|
+
const start = cursor;
|
|
15
|
+
const end = cursor + scene.durationSec;
|
|
16
|
+
cursor = end;
|
|
17
|
+
return [
|
|
18
|
+
String(index + 1),
|
|
19
|
+
`${formatSrtTimestamp(start)} --> ${formatSrtTimestamp(end)}`,
|
|
20
|
+
scene.subtitle,
|
|
21
|
+
""
|
|
22
|
+
].join("\n");
|
|
23
|
+
}).join("\n");
|
|
24
|
+
}
|
|
25
|
+
export async function writeSrt(projectId, plan) {
|
|
26
|
+
const relativePath = "output/subtitles.srt";
|
|
27
|
+
await fs.writeFile(toProjectPath(projectId, relativePath), buildSrt(plan), "utf8");
|
|
28
|
+
return relativePath;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=subtitles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subtitles.js","sourceRoot":"","sources":["../../src/core/subtitles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAElC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,SAAS,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,CAAC;IACzB,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AACrJ,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAe;IACtC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACtC,MAAM,KAAK,GAAG,MAAM,CAAC;QACrB,MAAM,GAAG,GAAG,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC;QACvC,MAAM,GAAG,GAAG,CAAC;QACb,OAAO;YACL,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC;YACjB,GAAG,kBAAkB,CAAC,KAAK,CAAC,QAAQ,kBAAkB,CAAC,GAAG,CAAC,EAAE;YAC7D,KAAK,CAAC,QAAQ;YACd,EAAE;SACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,SAAiB,EAAE,IAAe;IAC/D,MAAM,YAAY,GAAG,sBAAsB,CAAC;IAC5C,MAAM,EAAE,CAAC,SAAS,CAAC,aAAa,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IACnF,OAAO,YAAY,CAAC;AACtB,CAAC"}
|