cerevox 2.15.1 → 2.16.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/mcp/servers/prompts/image-prompt-optimizer.md +27 -2
- package/dist/mcp/servers/prompts/rules/anime-series.md +1 -0
- package/dist/mcp/servers/prompts/rules/general-video.md +1 -0
- package/dist/mcp/servers/prompts/zerocut-core.md +2 -0
- package/dist/mcp/servers/zerocut.d.ts.map +1 -1
- package/dist/mcp/servers/zerocut.js +224 -5
- package/dist/mcp/servers/zerocut.js.map +1 -1
- package/dist/timeline-editor/index.d.ts +42 -0
- package/dist/timeline-editor/index.d.ts.map +1 -0
- package/dist/timeline-editor/index.js +82 -0
- package/dist/timeline-editor/index.js.map +1 -0
- package/dist/timeline-editor/server.d.ts +137 -0
- package/dist/timeline-editor/server.d.ts.map +1 -0
- package/dist/timeline-editor/server.js +418 -0
- package/dist/timeline-editor/server.js.map +1 -0
- package/package.json +2 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
- 微距镜头 -> Macro lens shot
|
|
8
8
|
- 颗粒感 -> Grain
|
|
9
9
|
- 提升文字准确率:如果提示词中有涉及要绘制的文字符号,确保它们使用中文引号“”引用。
|
|
10
|
-
- 运用画面美学技巧时,优先使用词典中的技巧,但不要采用过度,对画面要表达的最核心内容有限采用2
|
|
10
|
+
- 运用画面美学技巧时,优先使用词典中的技巧,但不要采用过度,对画面要表达的最核心内容有限采用1、2条画面美学技巧即可。
|
|
11
11
|
|
|
12
12
|
## 有效词典
|
|
13
13
|
|
|
@@ -40,4 +40,29 @@ sisal | 亚麻绳 | 灯芯绒 | 亚麻布 | wicker | 竹编 | 棉花 | 草编 |
|
|
|
40
40
|
## 示例
|
|
41
41
|
|
|
42
42
|
- 用户输入:一个穿着华丽服装的女孩,撑伞,林荫街道,油画般的细腻笔触使画面生动美好
|
|
43
|
-
- 输出:一个女孩穿着华丽的服装,撑着遮阳伞走在林荫道上,莫奈油画风格
|
|
43
|
+
- 输出:一个女孩穿着华丽的服装,撑着遮阳伞走在林荫道上,莫奈油画风格
|
|
44
|
+
|
|
45
|
+
## 提取比喻
|
|
46
|
+
|
|
47
|
+
你需要提取出提示词中用于比喻修辞的形容或主体、姿势动作描述,例如:
|
|
48
|
+
- "如晨曦般温柔",
|
|
49
|
+
- "像流星划过夜空",
|
|
50
|
+
- "似春风拂面",
|
|
51
|
+
- "像糖果梦境一样甜美",
|
|
52
|
+
- "如光影流转的诗"
|
|
53
|
+
- "呈抱球状"
|
|
54
|
+
- "呈弓步"
|
|
55
|
+
- "站如松"
|
|
56
|
+
将它们放到metaphor_modifiers数组中输出
|
|
57
|
+
|
|
58
|
+
## 输出
|
|
59
|
+
|
|
60
|
+
输出 JSON 格式
|
|
61
|
+
|
|
62
|
+
{
|
|
63
|
+
prompt_optimized: "优化后的提示词",
|
|
64
|
+
"metaphor_modifiers": [
|
|
65
|
+
"prompt_optimized中的比喻修饰词",
|
|
66
|
+
...
|
|
67
|
+
]
|
|
68
|
+
}
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
* 音色选择:使用`search-voices`筛选配音音色,匹配场景情感
|
|
68
68
|
* 配音优先:先生成配音确定时长,再生成视频(视频时长=ceil(配音秒数),3-16秒)
|
|
69
69
|
* 时长控制:配音超16秒需简化台词或拆分场景
|
|
70
|
+
- 如简化台词,请务必先修改 story_board.json 中的 script 或 dialog 字段,确保生成语音时的一致性
|
|
70
71
|
* 视频生成策略:
|
|
71
72
|
- 默认优先使用`generate-video`生成动态视频
|
|
72
73
|
- 除非用户指定使用kenburns方案,否则仅在`generate-video`多次失败时使用`generate-video-kenburns`作为回退方案
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
* 音色选择:使用`search-voices`筛选配音音色,匹配场景情感
|
|
68
68
|
* 配音优先:先生成配音确定时长,再生成视频(视频时长=ceil(配音秒数),3-16秒)
|
|
69
69
|
* 时长控制:配音超16秒需简化台词或拆分场景
|
|
70
|
+
- 如简化台词,请务必先修改 story_board.json 中的 script 或 dialog 字段,确保生成语音时的一致性
|
|
70
71
|
* 视频生成策略:
|
|
71
72
|
- 默认优先使用`generate-video`生成动态视频
|
|
72
73
|
- 除非用户指定使用kenburns方案,否则仅在`generate-video`多次失败时使用`generate-video-kenburns`作为回退方案
|
|
@@ -285,6 +285,8 @@ projects/<id>/
|
|
|
285
285
|
|
|
286
286
|
3. 镜头自然延伸:这是一种和“一镜到底”不同的连续画面技术,当用户说“镜头自然延伸”时,你应当在生成视频时,设置saveLastFrameAs参数,将其返回的图片作为下一场景的首帧图片,不断延伸直至所有场景生成完成。如果你采用镜头自然延伸,那么你只需要设置第一个场景的start_frame,在story_board中其余场景可标记不需要start_frame,后续不断生成各场景视频,同时保存下一场景start_frame即可。
|
|
287
287
|
|
|
288
|
+
4. 手工修改草稿:在 draft_content 生成后,如果用户要求自行修改草稿,可运行 `custom-edit-draft` 工具(这个工具是本地工具,不需要先启动会话),启动服务让用户自行修改草稿。
|
|
289
|
+
|
|
288
290
|
---
|
|
289
291
|
|
|
290
292
|
## 工具优先级
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zerocut.d.ts","sourceRoot":"","sources":["../../../src/mcp/servers/zerocut.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"zerocut.d.ts","sourceRoot":"","sources":["../../../src/mcp/servers/zerocut.ts"],"names":[],"mappings":";AAukIA,wBAAsB,GAAG,kBAKxB"}
|
|
@@ -53,7 +53,51 @@ const node_path_1 = __importStar(require("node:path"));
|
|
|
53
53
|
const doubao_voices_full_1 = require("./helper/doubao_voices_full");
|
|
54
54
|
const node_fs_1 = require("node:fs");
|
|
55
55
|
const coze_1 = require("../../utils/coze");
|
|
56
|
+
const node_http_1 = __importDefault(require("node:http"));
|
|
57
|
+
const node_child_process_1 = require("node:child_process");
|
|
58
|
+
const node_util_1 = require("node:util");
|
|
59
|
+
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
56
60
|
// 错误处理工具函数
|
|
61
|
+
const handleError = (error) => {
|
|
62
|
+
if (error instanceof Error) {
|
|
63
|
+
return error.message;
|
|
64
|
+
}
|
|
65
|
+
return String(error);
|
|
66
|
+
};
|
|
67
|
+
// 检查端口是否被占用
|
|
68
|
+
const isPortInUse = async (port) => {
|
|
69
|
+
try {
|
|
70
|
+
const { stdout } = await execAsync(`lsof -ti:${port}`);
|
|
71
|
+
return stdout.trim().length > 0;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
// 停止占用端口的进程
|
|
78
|
+
const killPortProcess = async (port) => {
|
|
79
|
+
try {
|
|
80
|
+
const { stdout } = await execAsync(`lsof -ti:${port}`);
|
|
81
|
+
const pids = stdout
|
|
82
|
+
.trim()
|
|
83
|
+
.split('\n')
|
|
84
|
+
.filter(pid => pid);
|
|
85
|
+
for (const pid of pids) {
|
|
86
|
+
try {
|
|
87
|
+
await execAsync(`kill -9 ${pid}`);
|
|
88
|
+
console.log(`Killed process ${pid} on port ${port}`);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.warn(`Failed to kill process ${pid}:`, error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// 等待一下确保进程被杀死
|
|
95
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.warn(`Failed to kill processes on port ${port}:`, error);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
57
101
|
function createErrorResponse(error, operation) {
|
|
58
102
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
59
103
|
console.error(`[${operation}] Error:`, error);
|
|
@@ -841,13 +885,13 @@ server.registerTool('generate-image', {
|
|
|
841
885
|
},
|
|
842
886
|
}, async ({ prompt, sceneIndex, storyBoardFile = 'story_board.json', skipConsistencyCheck = false, size, saveToFileName, watermark, referenceImages, optimizePrompt, }) => {
|
|
843
887
|
try {
|
|
888
|
+
// 验证session状态
|
|
889
|
+
const currentSession = await validateSession('generate-image');
|
|
844
890
|
// 检查 storyboard 标志
|
|
845
891
|
if (!checkStoryboardFlag) {
|
|
846
892
|
checkStoryboardFlag = true;
|
|
847
893
|
return createErrorResponse('必须先审查生成的 story_board.json 内容,确保每个场景中的stage_atmosphere内容按照规则被正确融合到start_frame和video_prompt中,不得遗漏,检查完成后先汇报,如果有问题,应当先修改 story_board.json 内容,然后再调用 generate-image 生成图片', 'generate-image');
|
|
848
894
|
}
|
|
849
|
-
// 验证session状态
|
|
850
|
-
const currentSession = await validateSession('generate-image');
|
|
851
895
|
const validatedFileName = validateFileName(saveToFileName);
|
|
852
896
|
// 校验 prompt 与 story_board.json 中场景设定的一致性
|
|
853
897
|
if (!skipConsistencyCheck) {
|
|
@@ -888,7 +932,7 @@ server.registerTool('generate-image', {
|
|
|
888
932
|
const ai = currentSession.ai;
|
|
889
933
|
const promptOptimizer = await (0, promises_1.readFile)((0, node_path_1.resolve)(__dirname, './prompts/image-prompt-optimizer.md'), 'utf8');
|
|
890
934
|
const completion = await ai.getCompletions({
|
|
891
|
-
model: 'Doubao-Seed-1.6',
|
|
935
|
+
model: 'Doubao-Seed-1.6-flash',
|
|
892
936
|
messages: [
|
|
893
937
|
{
|
|
894
938
|
role: 'system',
|
|
@@ -900,9 +944,30 @@ server.registerTool('generate-image', {
|
|
|
900
944
|
},
|
|
901
945
|
],
|
|
902
946
|
});
|
|
903
|
-
|
|
947
|
+
let optimizedPrompt = completion.choices[0]?.message?.content.trim();
|
|
904
948
|
if (optimizedPrompt) {
|
|
905
|
-
|
|
949
|
+
if (optimizedPrompt.startsWith('```json')) {
|
|
950
|
+
// 提取 JSON 代码块中的内容
|
|
951
|
+
const jsonMatch = optimizedPrompt.match(/```json\s*([\s\S]*?)\s*```/);
|
|
952
|
+
if (jsonMatch && jsonMatch[1]) {
|
|
953
|
+
optimizedPrompt = jsonMatch[1];
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if (optimizedPrompt.startsWith('{')) {
|
|
957
|
+
try {
|
|
958
|
+
const { prompt_optimized, metaphor_modifiers } = JSON.parse(optimizedPrompt);
|
|
959
|
+
processedPrompt = `${prompt_optimized}`;
|
|
960
|
+
if (metaphor_modifiers?.length) {
|
|
961
|
+
processedPrompt += `\n\n注意:下面这些是形象比喻,并不是输出内容。\n${metaphor_modifiers}`;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
catch (ex) {
|
|
965
|
+
processedPrompt = optimizedPrompt;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
else {
|
|
969
|
+
processedPrompt = optimizedPrompt;
|
|
970
|
+
}
|
|
906
971
|
}
|
|
907
972
|
}
|
|
908
973
|
catch (error) {
|
|
@@ -3025,6 +3090,160 @@ server.registerTool('run-ffmpeg-command', {
|
|
|
3025
3090
|
return createErrorResponse(error, 'run-ffmpeg-command');
|
|
3026
3091
|
}
|
|
3027
3092
|
});
|
|
3093
|
+
server.registerTool('custom-edit-draft', {
|
|
3094
|
+
title: 'Custom Edit Draft',
|
|
3095
|
+
description: 'Launch timeline editor server for custom draft editing. Starts a local server at specified port with draft file configuration.',
|
|
3096
|
+
inputSchema: {
|
|
3097
|
+
draftContentFileName: zod_1.z
|
|
3098
|
+
.string()
|
|
3099
|
+
.optional()
|
|
3100
|
+
.default('draft_content.json')
|
|
3101
|
+
.describe('Draft content filename (default: draft_content.json)'),
|
|
3102
|
+
port: zod_1.z
|
|
3103
|
+
.number()
|
|
3104
|
+
.optional()
|
|
3105
|
+
.default(6789)
|
|
3106
|
+
.describe('Server port (default: 6789)'),
|
|
3107
|
+
},
|
|
3108
|
+
}, async ({ draftContentFileName = 'draft_content.json', port = 6789 }) => {
|
|
3109
|
+
try {
|
|
3110
|
+
// 使用项目本地目录
|
|
3111
|
+
const workDir = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir);
|
|
3112
|
+
// 检查 draft_content.json 文件是否存在
|
|
3113
|
+
const draftFilePath = (0, node_path_1.resolve)(workDir, draftContentFileName);
|
|
3114
|
+
if (!(0, node_fs_1.existsSync)(draftFilePath)) {
|
|
3115
|
+
return createErrorResponse(new Error(`Draft content file not found: ${draftContentFileName}. Please generate draft content first using the generate-draft-content tool.`), 'custom-edit-draft');
|
|
3116
|
+
}
|
|
3117
|
+
// 备份原始的 draft_content.json 文件
|
|
3118
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
3119
|
+
const backupFileName = `${draftContentFileName}.backup.${timestamp}`;
|
|
3120
|
+
const backupFilePath = (0, node_path_1.resolve)(workDir, backupFileName);
|
|
3121
|
+
try {
|
|
3122
|
+
const { copyFileSync } = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
3123
|
+
copyFileSync(draftFilePath, backupFilePath);
|
|
3124
|
+
console.log(`Draft file backed up to: ${backupFileName}`);
|
|
3125
|
+
}
|
|
3126
|
+
catch (backupError) {
|
|
3127
|
+
console.warn(`Failed to backup draft file: ${backupError}`);
|
|
3128
|
+
// 继续执行,不因备份失败而中断
|
|
3129
|
+
}
|
|
3130
|
+
// 构建启动命令参数
|
|
3131
|
+
const timelineEditorPath = (0, node_path_1.resolve)(__dirname, '../../../dist/timeline-editor/index.js');
|
|
3132
|
+
console.log(`Starting timeline editor server...`);
|
|
3133
|
+
console.log(`Working directory: ${workDir}`);
|
|
3134
|
+
console.log(`Draft file: ${draftContentFileName}`);
|
|
3135
|
+
console.log(`Server port: ${port}`);
|
|
3136
|
+
console.log(`Timeline editor path: ${timelineEditorPath}`);
|
|
3137
|
+
// 检查端口是否被占用,如果被占用则停止占用的进程
|
|
3138
|
+
if (await isPortInUse(port)) {
|
|
3139
|
+
console.log(`Port ${port} is in use, killing existing processes...`);
|
|
3140
|
+
await killPortProcess(port);
|
|
3141
|
+
}
|
|
3142
|
+
// 直接启动服务器进程
|
|
3143
|
+
const serverProcess = (0, node_child_process_1.spawn)('node', [
|
|
3144
|
+
timelineEditorPath,
|
|
3145
|
+
'--draft-file',
|
|
3146
|
+
draftContentFileName,
|
|
3147
|
+
'--project-dir',
|
|
3148
|
+
workDir,
|
|
3149
|
+
'--port',
|
|
3150
|
+
port.toString(),
|
|
3151
|
+
], {
|
|
3152
|
+
cwd: workDir,
|
|
3153
|
+
stdio: 'pipe',
|
|
3154
|
+
});
|
|
3155
|
+
const serverUrl = `http://localhost:${port}`;
|
|
3156
|
+
// 等待服务器启动 - 使用健康检查 API 轮询
|
|
3157
|
+
await new Promise((resolve, reject) => {
|
|
3158
|
+
const timeout = setTimeout(() => {
|
|
3159
|
+
reject(new Error('Server startup timeout'));
|
|
3160
|
+
}, 30000); // 增加超时时间到30秒
|
|
3161
|
+
const checkHealth = async () => {
|
|
3162
|
+
try {
|
|
3163
|
+
const healthUrl = `${serverUrl}/health`;
|
|
3164
|
+
const req = node_http_1.default.get(healthUrl, (res) => {
|
|
3165
|
+
let data = '';
|
|
3166
|
+
res.on('data', (chunk) => {
|
|
3167
|
+
data += chunk;
|
|
3168
|
+
});
|
|
3169
|
+
res.on('end', () => {
|
|
3170
|
+
try {
|
|
3171
|
+
const healthData = JSON.parse(data);
|
|
3172
|
+
if (healthData.status === 'ok') {
|
|
3173
|
+
clearTimeout(timeout);
|
|
3174
|
+
console.log('Server health check passed');
|
|
3175
|
+
resolve(void 0);
|
|
3176
|
+
}
|
|
3177
|
+
else {
|
|
3178
|
+
// 继续轮询
|
|
3179
|
+
setTimeout(checkHealth, 1000);
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
catch (parseError) {
|
|
3183
|
+
// 继续轮询
|
|
3184
|
+
setTimeout(checkHealth, 1000);
|
|
3185
|
+
}
|
|
3186
|
+
});
|
|
3187
|
+
});
|
|
3188
|
+
req.on('error', () => {
|
|
3189
|
+
// 服务器还未启动,继续轮询
|
|
3190
|
+
setTimeout(checkHealth, 1000);
|
|
3191
|
+
});
|
|
3192
|
+
req.setTimeout(2000, () => {
|
|
3193
|
+
req.destroy();
|
|
3194
|
+
// 继续轮询
|
|
3195
|
+
setTimeout(checkHealth, 1000);
|
|
3196
|
+
});
|
|
3197
|
+
}
|
|
3198
|
+
catch (error) {
|
|
3199
|
+
// 继续轮询
|
|
3200
|
+
setTimeout(checkHealth, 1000);
|
|
3201
|
+
}
|
|
3202
|
+
};
|
|
3203
|
+
serverProcess.stdout?.on('data', (data) => {
|
|
3204
|
+
console.log('Server output:', data.toString());
|
|
3205
|
+
});
|
|
3206
|
+
serverProcess.stderr?.on('data', (data) => {
|
|
3207
|
+
console.error('Server error:', data.toString());
|
|
3208
|
+
});
|
|
3209
|
+
serverProcess.on('error', (error) => {
|
|
3210
|
+
clearTimeout(timeout);
|
|
3211
|
+
reject(error);
|
|
3212
|
+
});
|
|
3213
|
+
// 开始健康检查轮询
|
|
3214
|
+
setTimeout(checkHealth, 2000); // 2秒后开始第一次检查
|
|
3215
|
+
});
|
|
3216
|
+
return {
|
|
3217
|
+
content: [
|
|
3218
|
+
{
|
|
3219
|
+
type: 'text',
|
|
3220
|
+
text: JSON.stringify({
|
|
3221
|
+
success: true,
|
|
3222
|
+
message: 'Timeline editor server started successfully',
|
|
3223
|
+
serverUrl: serverUrl,
|
|
3224
|
+
draftFile: draftContentFileName,
|
|
3225
|
+
backupFile: backupFileName,
|
|
3226
|
+
projectDirectory: workDir,
|
|
3227
|
+
port: port,
|
|
3228
|
+
pid: serverProcess.pid,
|
|
3229
|
+
instructions: [
|
|
3230
|
+
`Timeline editor is now running at: ${serverUrl}`,
|
|
3231
|
+
'Open the URL in your browser to edit the draft',
|
|
3232
|
+
'The server will automatically save changes to the draft file',
|
|
3233
|
+
`Original draft file has been backed up as: ${backupFileName}`,
|
|
3234
|
+
`Server process ID: ${serverProcess.pid}`,
|
|
3235
|
+
'The server will continue running in the background',
|
|
3236
|
+
],
|
|
3237
|
+
}, null, 2),
|
|
3238
|
+
},
|
|
3239
|
+
],
|
|
3240
|
+
};
|
|
3241
|
+
}
|
|
3242
|
+
catch (error) {
|
|
3243
|
+
console.error('Error starting timeline editor:', error);
|
|
3244
|
+
return createErrorResponse(error, 'custom-edit-draft');
|
|
3245
|
+
}
|
|
3246
|
+
});
|
|
3028
3247
|
async function run() {
|
|
3029
3248
|
// Start receiving messages on stdin and sending messages on stdout
|
|
3030
3249
|
const transport = new stdio_js_1.StdioServerTransport();
|