cerevox 2.43.1 → 3.0.0-alpha.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.
Files changed (39) hide show
  1. package/dist/core/ai.d.ts +28 -3
  2. package/dist/core/ai.d.ts.map +1 -1
  3. package/dist/core/ai.js +45 -16
  4. package/dist/core/ai.js.map +1 -1
  5. package/dist/mcp/servers/prompts/actions/storyboard_optimization.md +5 -5
  6. package/dist/{utils/videoproject-schema.json → mcp/servers/prompts/draft_content-schema.json} +3 -3
  7. package/dist/mcp/servers/prompts/rules/creative-ad.md +6 -6
  8. package/dist/mcp/servers/prompts/rules/expert.md +25 -25
  9. package/dist/mcp/servers/prompts/rules/freeform.md +2 -3
  10. package/dist/mcp/servers/prompts/rules/general-video.md +10 -10
  11. package/dist/mcp/servers/prompts/rules/material-creation.md +2 -2
  12. package/dist/mcp/servers/prompts/rules/music-video.md +3 -3
  13. package/dist/mcp/servers/prompts/rules/stage-play.md +4 -4
  14. package/dist/mcp/servers/prompts/rules/story-telling.md +8 -8
  15. package/dist/mcp/servers/prompts/skills/storyboard/storyboard-optimization-skill.md +5 -5
  16. package/dist/mcp/servers/prompts/skills/video/continuity-techniques.md +1 -1
  17. package/dist/mcp/servers/prompts/skills/workflows/general-video.md +10 -10
  18. package/dist/mcp/servers/prompts/skills/workflows/music-video.md +3 -3
  19. package/dist/mcp/servers/prompts/zerocut-core.md +26 -27
  20. package/dist/mcp/servers/zerocut.d.ts.map +1 -1
  21. package/dist/mcp/servers/zerocut.js +397 -521
  22. package/dist/mcp/servers/zerocut.js.map +1 -1
  23. package/dist/utils/coze.d.ts +1 -0
  24. package/dist/utils/coze.d.ts.map +1 -1
  25. package/dist/utils/coze.js +19 -0
  26. package/dist/utils/coze.js.map +1 -1
  27. package/package.json +2 -2
  28. package/dist/timeline-editor/index.d.ts +0 -42
  29. package/dist/timeline-editor/index.d.ts.map +0 -1
  30. package/dist/timeline-editor/index.js +0 -82
  31. package/dist/timeline-editor/index.js.map +0 -1
  32. package/dist/timeline-editor/public/app.js +0 -2086
  33. package/dist/timeline-editor/public/index.html +0 -141
  34. package/dist/timeline-editor/public/style.css +0 -695
  35. package/dist/timeline-editor/server.d.ts +0 -137
  36. package/dist/timeline-editor/server.d.ts.map +0 -1
  37. package/dist/timeline-editor/server.js +0 -418
  38. package/dist/timeline-editor/server.js.map +0 -1
  39. /package/dist/{utils → mcp/servers/prompts}/storyboard-schema.json +0 -0
@@ -53,85 +53,8 @@ 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_child_process_1 = require("node:child_process");
57
- const node_util_1 = require("node:util");
58
- const os = __importStar(require("node:os"));
59
56
  const mp3_duration_1 = __importDefault(require("mp3-duration"));
60
57
  const image_size_1 = __importDefault(require("image-size"));
61
- const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
62
- // 错误处理工具函数
63
- const handleError = (error) => {
64
- if (error instanceof Error) {
65
- return error.message;
66
- }
67
- return String(error);
68
- };
69
- // 检查端口是否被占用 - 跨平台版本
70
- const isPortInUse = async (port) => {
71
- try {
72
- const platform = os.platform();
73
- let command;
74
- if (platform === 'win32') {
75
- // Windows 使用 netstat
76
- command = `netstat -ano | findstr :${port}`;
77
- }
78
- else {
79
- // Unix/Linux/macOS 使用 lsof
80
- command = `lsof -ti:${port}`;
81
- }
82
- const { stdout } = await execAsync(command);
83
- return stdout.trim().length > 0;
84
- }
85
- catch {
86
- return false;
87
- }
88
- };
89
- // 停止占用端口的进程 - 跨平台版本
90
- const killPortProcess = async (port) => {
91
- try {
92
- const platform = os.platform();
93
- if (platform === 'win32') {
94
- // Windows 版本
95
- const { stdout } = await execAsync(`netstat -ano | findstr :${port}`);
96
- const lines = stdout.trim().split('\n');
97
- for (const line of lines) {
98
- const parts = line.trim().split(/\s+/);
99
- const pid = parts[parts.length - 1];
100
- if (pid && /^\d+$/.test(pid)) {
101
- try {
102
- await execAsync(`taskkill /F /PID ${pid}`);
103
- console.log(`Killed process ${pid} on port ${port}`);
104
- }
105
- catch (error) {
106
- console.warn(`Failed to kill process ${pid}:`, error);
107
- }
108
- }
109
- }
110
- }
111
- else {
112
- // Unix/Linux/macOS 版本
113
- const { stdout } = await execAsync(`lsof -ti:${port}`);
114
- const pids = stdout
115
- .trim()
116
- .split('\n')
117
- .filter(pid => pid);
118
- for (const pid of pids) {
119
- try {
120
- await execAsync(`kill -9 ${pid}`);
121
- console.log(`Killed process ${pid} on port ${port}`);
122
- }
123
- catch (error) {
124
- console.warn(`Failed to kill process ${pid}:`, error);
125
- }
126
- }
127
- }
128
- // 等待一下确保进程被杀死
129
- await new Promise(resolve => setTimeout(resolve, 1000));
130
- }
131
- catch (error) {
132
- console.warn(`Failed to kill processes on port ${port}:`, error);
133
- }
134
- };
135
58
  function createErrorResponse(error, operation) {
136
59
  const errorMessage = error instanceof Error ? error.message : String(error);
137
60
  console.error(`[${operation}] Error:`, error);
@@ -159,6 +82,10 @@ async function validateSession(operation) {
159
82
  session = null;
160
83
  throw new Error(`Session not initialized. Please call 'project-open' first before using ${operation}.`);
161
84
  }
85
+ const projectRulesFile = (0, node_path_1.resolve)(projectLocalDir, '.trae', 'rules', `project_rules.md`);
86
+ if (!(0, node_fs_1.existsSync)(projectRulesFile)) {
87
+ throw new Error(`Project rules file not found: ${projectRulesFile}. Please call 'retrieve-rules-context' first.`);
88
+ }
162
89
  return session;
163
90
  }
164
91
  // 文件名验证
@@ -173,20 +100,6 @@ function validateFileName(fileName) {
173
100
  }
174
101
  return fileName.trim();
175
102
  }
176
- // 图片文件验证
177
- function validateImageFile(localPath) {
178
- if (!localPath || localPath.trim() === '') {
179
- throw new Error('Local path cannot be empty');
180
- }
181
- const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'];
182
- const extension = localPath
183
- .toLowerCase()
184
- .substring(localPath.lastIndexOf('.'));
185
- if (!allowedExtensions.includes(extension)) {
186
- throw new Error(`Unsupported image format: ${extension}. Allowed formats: ${allowedExtensions.join(', ')}`);
187
- }
188
- return localPath.trim();
189
- }
190
103
  function getMaterialUri(session, fileName) {
191
104
  return session.sandbox.getUrl(`/zerocut/${session.terminal.id}/materials/${(0, node_path_1.basename)(fileName)}`);
192
105
  }
@@ -211,7 +124,7 @@ async function initProject(session) {
211
124
  // 创建素材目录和成品目录 materials、output
212
125
  await (await terminal.run('mkdir materials output')).end();
213
126
  // 文件包括(大部分不需要此时创建)
214
- // story_board.json 故事版
127
+ // storyboard.json 故事版
215
128
  // draft_content.json 草稿内容,Agent在创作过程中,会根据项目规范,自动生成和修改该文件
216
129
  return workDir;
217
130
  }
@@ -692,6 +605,7 @@ server.registerTool('project-open', {
692
605
  }
693
606
  const result = {
694
607
  success: true,
608
+ nextActionSuggest: '检查规则上下文是否已召回,若未召回,调用 retrieve_rules 工具召回规则上下文',
695
609
  sessionId: session.id,
696
610
  workDir,
697
611
  projectLocalDir,
@@ -1158,7 +1072,7 @@ server.registerTool('generate-image', {
1158
1072
  type: zod_1.z.enum(['banana', 'seedream']).optional().default('seedream'),
1159
1073
  prompt: zod_1.z
1160
1074
  .string()
1161
- .describe('The prompt to generate. 一般要严格对应 story_board 中当前场景的 start_frame 或 end_frame 中的字段描述'),
1075
+ .describe('The prompt to generate. 一般要严格对应 storyboard 中当前场景的 start_frame 或 end_frame 中的字段描述'),
1162
1076
  sceneIndex: zod_1.z
1163
1077
  .number()
1164
1078
  .min(1)
@@ -1167,7 +1081,7 @@ server.registerTool('generate-image', {
1167
1081
  storyBoardFile: zod_1.z
1168
1082
  .string()
1169
1083
  .optional()
1170
- .default('story_board.json')
1084
+ .default('storyboard.json')
1171
1085
  .describe('故事板文件路径'),
1172
1086
  skipConsistencyCheck: zod_1.z
1173
1087
  .boolean()
@@ -1252,7 +1166,7 @@ server.registerTool('generate-image', {
1252
1166
  \`\`\`
1253
1167
  `),
1254
1168
  },
1255
- }, async ({ type = 'seedream', prompt, sceneIndex, storyBoardFile = 'story_board.json', skipConsistencyCheck = false, size = '720x1280', saveToFileName, watermark, referenceImages, optimizePrompt, }) => {
1169
+ }, async ({ type = 'seedream', prompt, sceneIndex, storyBoardFile = 'storyboard.json', skipConsistencyCheck = false, size = '720x1280', saveToFileName, watermark, referenceImages, optimizePrompt, }) => {
1256
1170
  try {
1257
1171
  // 验证session状态
1258
1172
  const currentSession = await validateSession('generate-image');
@@ -1260,10 +1174,10 @@ server.registerTool('generate-image', {
1260
1174
  // 检查 storyboard 标志
1261
1175
  if (!checkStoryboardFlag && (0, node_fs_1.existsSync)(storyBoardPath)) {
1262
1176
  checkStoryboardFlag = true;
1263
- return createErrorResponse('必须先审查生成的 story_board.json 内容,确保每个场景中的stage_atmosphere内容按照规则被正确融合到start_frame和video_prompt中,不得遗漏,检查完成后先汇报,如果有问题,应当先修改 story_board.json 内容,然后再调用 generate-image 生成图片。注意修改 story_board 内容时,仅修改相应字段的字符串值,不要破坏JSON格式!', 'generate-image');
1177
+ return createErrorResponse('必须先审查生成的 storyboard.json 内容,确保每个场景中的stage_atmosphere内容按照规则被正确融合到start_frame和video_prompt中,不得遗漏,检查完成后先汇报,如果有问题,应当先修改 storyboard.json 内容,然后再调用 generate-image 生成图片。注意修改 storyboard 内容时,仅修改相应字段的字符串值,不要破坏JSON格式!', 'generate-image');
1264
1178
  }
1265
1179
  const validatedFileName = validateFileName(saveToFileName);
1266
- // 校验 prompt 与 story_board.json 中场景设定的一致性
1180
+ // 校验 prompt 与 storyboard.json 中场景设定的一致性
1267
1181
  if (sceneIndex && !skipConsistencyCheck) {
1268
1182
  try {
1269
1183
  if ((0, node_fs_1.existsSync)(storyBoardPath)) {
@@ -1283,9 +1197,9 @@ server.registerTool('generate-image', {
1283
1197
  const endFrame = scene.end_frame;
1284
1198
  // 检查 prompt 是否严格等于 start_frame 或 end_frame
1285
1199
  if (prompt !== startFrame && prompt !== endFrame) {
1286
- return createErrorResponse('图片提示词必须严格遵照story_board的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-image');
1200
+ return createErrorResponse('图片提示词必须严格遵照storyboard的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-image');
1287
1201
  }
1288
- // 校验 size 参数与 story_board 的 orientation 属性一致性
1202
+ // 校验 size 参数与 storyboard 的 orientation 属性一致性
1289
1203
  if (size && storyBoard.orientation) {
1290
1204
  const isLandscapeSize = [
1291
1205
  '1152x864',
@@ -1330,7 +1244,7 @@ server.registerTool('generate-image', {
1330
1244
  }
1331
1245
  }
1332
1246
  else {
1333
- console.warn(`Scene index ${sceneIndex} not found in story_board.json`);
1247
+ console.warn(`Scene index ${sceneIndex} not found in storyboard.json`);
1334
1248
  }
1335
1249
  }
1336
1250
  }
@@ -1340,7 +1254,7 @@ server.registerTool('generate-image', {
1340
1254
  }
1341
1255
  catch (error) {
1342
1256
  console.error('Failed to validate prompt with story board:', error);
1343
- // 如果读取或解析 story_board.json 失败,继续执行但记录警告
1257
+ // 如果读取或解析 storyboard.json 失败,继续执行但记录警告
1344
1258
  }
1345
1259
  }
1346
1260
  // 检查并替换英文单引号包裹的中文内容为中文双引号
@@ -1647,7 +1561,7 @@ server.registerTool('generate-video', {
1647
1561
  inputSchema: {
1648
1562
  prompt: zod_1.z
1649
1563
  .string()
1650
- .describe('The prompt to generate. 一般要严格对应 story_board 中当前场景的 video_prompt 字段描述'),
1564
+ .describe('The prompt to generate. 一般要严格对应 storyboard 中当前场景的 video_prompt 字段描述'),
1651
1565
  sceneIndex: zod_1.z
1652
1566
  .number()
1653
1567
  .min(1)
@@ -1656,7 +1570,7 @@ server.registerTool('generate-video', {
1656
1570
  storyBoardFile: zod_1.z
1657
1571
  .string()
1658
1572
  .optional()
1659
- .default('story_board.json')
1573
+ .default('storyboard.json')
1660
1574
  .describe('故事板文件路径'),
1661
1575
  skipConsistencyCheck: zod_1.z
1662
1576
  .boolean()
@@ -1691,7 +1605,7 @@ server.registerTool('generate-video', {
1691
1605
  .describe('The image file name of the start frame.'),
1692
1606
  duration: zod_1.z
1693
1607
  .number()
1694
- .min(2)
1608
+ .min(1)
1695
1609
  .max(23)
1696
1610
  .describe('The duration of the video. 一般与 tts 配音音频时长向上取整秒(ceil)一致,或者与 timeline_analysis 中确定的歌曲片段时长一致'),
1697
1611
  end_frame: zod_1.z
@@ -1718,7 +1632,7 @@ server.registerTool('generate-video', {
1718
1632
  .default(false)
1719
1633
  .describe('Whether to optimize the prompt.'),
1720
1634
  },
1721
- }, async ({ prompt, sceneIndex, storyBoardFile = 'story_board.json', skipConsistencyCheck = false, saveToFileName, start_frame, end_frame, duration, watermark, resolution, type, optimizePrompt, saveLastFrameAs, }, context) => {
1635
+ }, async ({ prompt, sceneIndex, storyBoardFile = 'storyboard.json', skipConsistencyCheck = false, saveToFileName, start_frame, end_frame, duration, watermark, resolution, type, optimizePrompt, saveLastFrameAs, }, context) => {
1722
1636
  try {
1723
1637
  // 验证session状态
1724
1638
  const currentSession = await validateSession('generate-video');
@@ -1739,7 +1653,7 @@ server.registerTool('generate-video', {
1739
1653
  console.warn(`zero 模型的视频仅支持 1080p 分辨率,用户指定的分辨率为 %s,已自动将 ${resolution} 转换为 1080p`, resolution);
1740
1654
  resolution = '1080p';
1741
1655
  }
1742
- // 校验 prompt 与 story_board.json 中场景设定的一致性以及视频时长与 timeline_analysis.json 中 proposed_video_scenes 的匹配
1656
+ // 校验 prompt 与 storyboard.json 中场景设定的一致性以及视频时长与 timeline_analysis.json 中 proposed_video_scenes 的匹配
1743
1657
  if (sceneIndex && !skipConsistencyCheck) {
1744
1658
  try {
1745
1659
  const storyBoardPath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, storyBoardFile);
@@ -1758,7 +1672,7 @@ server.registerTool('generate-video', {
1758
1672
  if (scene) {
1759
1673
  const videoPrompt = scene.video_prompt;
1760
1674
  if (videoPrompt && prompt !== videoPrompt) {
1761
- return createErrorResponse('视频提示词必须严格遵照story_board的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-video');
1675
+ return createErrorResponse('视频提示词必须严格遵照storyboard的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-video');
1762
1676
  }
1763
1677
  if (scene.is_continuous && !end_frame) {
1764
1678
  return createErrorResponse('连续场景必须指定end_frame参数,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-video');
@@ -1771,7 +1685,7 @@ server.registerTool('generate-video', {
1771
1685
  }
1772
1686
  }
1773
1687
  else {
1774
- console.warn(`Scene index ${sceneIndex} not found in story_board.json`);
1688
+ console.warn(`Scene index ${sceneIndex} not found in storyboard.json`);
1775
1689
  }
1776
1690
  }
1777
1691
  }
@@ -1781,7 +1695,7 @@ server.registerTool('generate-video', {
1781
1695
  }
1782
1696
  catch (error) {
1783
1697
  console.error('Failed to validate prompt with story board:', error);
1784
- // 如果读取或解析 story_board.json 失败,继续执行但记录警告
1698
+ // 如果读取或解析 storyboard.json 失败,继续执行但记录警告
1785
1699
  }
1786
1700
  // 校验视频时长与 timeline_analysis.json 中 proposed_video_scenes 的匹配
1787
1701
  try {
@@ -1818,10 +1732,10 @@ server.registerTool('generate-video', {
1818
1732
  if (!checkAudioVideoDurationFlag) {
1819
1733
  checkAudioVideoDurationFlag = true;
1820
1734
  if (scene.audio_mode === 'vo_sync') {
1821
- return createErrorResponse('请先自我检查 media_logs 中的音频时长,确保 story_board 中视频时长为音频时长向上取整 即 ceil(音频时长),然后再按照正确的视频时长创建视频', 'generate-video');
1735
+ return createErrorResponse('请先自我检查 media_logs 中的音频时长,确保 storyboard 中视频时长为音频时长向上取整 即 ceil(音频时长),然后再按照正确的视频时长创建视频', 'generate-video');
1822
1736
  }
1823
1737
  else if (scene.audio_mode === 'dialogue') {
1824
- return createErrorResponse('请先自我检查 media_logs 中的音频时长,确保 story_board 中视频时长**不小于**音频时长向上取整 即 ceil(音频时长),然后再按照正确的视频时长创建视频', 'generate-video');
1738
+ return createErrorResponse('请先自我检查 media_logs 中的音频时长,确保 storyboard 中视频时长**不小于**音频时长向上取整 即 ceil(音频时长),然后再按照正确的视频时长创建视频', 'generate-video');
1825
1739
  }
1826
1740
  }
1827
1741
  }
@@ -1946,10 +1860,10 @@ server.registerTool('generate-video', {
1946
1860
  type: 'string',
1947
1861
  description: 'kenburns 视频 宽x高',
1948
1862
  },
1949
- },
1950
- duration: {
1951
- type: 'number',
1952
- description: 'kenburns 视频 时长',
1863
+ duration: {
1864
+ type: 'number',
1865
+ description: 'kenburns 视频 时长',
1866
+ },
1953
1867
  },
1954
1868
  required: ['camera_motion', 'size', 'duration'],
1955
1869
  },
@@ -2091,7 +2005,7 @@ server.registerTool('generate-video', {
2091
2005
  console.warn('Failed to send progress update:', progressError);
2092
2006
  }
2093
2007
  },
2094
- waitForFinish: type !== 'zero', // zero 模型由于时间比较长,因此要异步查询而不是等待
2008
+ waitForFinish: true,
2095
2009
  });
2096
2010
  if (!res) {
2097
2011
  throw new Error('Failed to generate video: no response from AI service');
@@ -2139,7 +2053,7 @@ server.registerTool('generate-video', {
2139
2053
  type: 'text',
2140
2054
  text: JSON.stringify({
2141
2055
  success: true,
2142
- message: '该视频生成任务已提交,它是异步任务,且执行时间较长,你应立即调用工具 wait-for-task-finish 来等待任务结束,如该工具调用超时,你应立即再次重新调用直到任务结束。',
2056
+ message: '该视频生成任务正在运行中,它是异步任务,且执行时间较长,你应立即调用工具 wait-for-task-finish 来等待任务结束,如该工具调用超时,你应立即再次重新调用直到任务结束。',
2143
2057
  taskUrl: res.taskUrl,
2144
2058
  }),
2145
2059
  },
@@ -2194,6 +2108,7 @@ server.registerTool('wait-for-task-finish', {
2194
2108
  console.warn('Failed to send progress update:', progressError);
2195
2109
  }
2196
2110
  },
2111
+ timeout: 300000,
2197
2112
  });
2198
2113
  if (res.url) {
2199
2114
  const uri = await saveMaterial(currentSession, res.url, saveToFileName);
@@ -2423,7 +2338,7 @@ server.registerTool('generate-scene-tts', {
2423
2338
  storyBoardFile: zod_1.z
2424
2339
  .string()
2425
2340
  .optional()
2426
- .default('story_board.json')
2341
+ .default('storyboard.json')
2427
2342
  .describe('故事板文件路径'),
2428
2343
  skipConsistencyCheck: zod_1.z
2429
2344
  .boolean()
@@ -2478,7 +2393,9 @@ server.registerTool('generate-scene-tts', {
2478
2393
  'ASMR',
2479
2394
  ])
2480
2395
  .optional(),
2481
- voiceID: zod_1.z.string().describe(`适合作为视频配音的音色ID`),
2396
+ voiceID: zod_1.z
2397
+ .string()
2398
+ .describe(`适合作为视频配音的音色ID,除非用户指定,否则你必须已通过 search_voice 工具检查确定该音色确实是存在的。`),
2482
2399
  explicit_language: zod_1.z.enum(['zh', 'en', 'ja']).optional().default('zh'),
2483
2400
  },
2484
2401
  }, async ({ text, sceneIndex, storyBoardFile, skipConsistencyCheck, voiceID, saveToFileName, speed, pitch, volume, emotion, explicit_language, }) => {
@@ -2489,7 +2406,7 @@ server.registerTool('generate-scene-tts', {
2489
2406
  const finalSpeed = speed ?? 1;
2490
2407
  volume = volume ?? 1;
2491
2408
  let scene = null;
2492
- // 校验 text 与 story_board.json 中场景设定的一致性
2409
+ // 校验 text 与 storyboard.json 中场景设定的一致性
2493
2410
  if (sceneIndex && !skipConsistencyCheck) {
2494
2411
  try {
2495
2412
  const storyBoardPath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, storyBoardFile);
@@ -2524,11 +2441,11 @@ server.registerTool('generate-scene-tts', {
2524
2441
  }
2525
2442
  }
2526
2443
  if (!isValidText) {
2527
- return createErrorResponse('配音文本必须严格遵照story_board的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-scene-tts');
2444
+ return createErrorResponse('配音文本必须严格遵照storyboard的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-scene-tts');
2528
2445
  }
2529
2446
  }
2530
2447
  else {
2531
- console.warn(`Scene index ${sceneIndex} not found in story_board.json`);
2448
+ console.warn(`Scene index ${sceneIndex} not found in storyboard.json`);
2532
2449
  }
2533
2450
  }
2534
2451
  }
@@ -2538,11 +2455,14 @@ server.registerTool('generate-scene-tts', {
2538
2455
  }
2539
2456
  catch (error) {
2540
2457
  console.error('Failed to validate text with story board:', error);
2541
- // 如果读取或解析 story_board.json 失败,继续执行但记录警告
2458
+ // 如果读取或解析 storyboard.json 失败,继续执行但记录警告
2542
2459
  }
2543
2460
  }
2544
2461
  console.log(`Generating TTS with voice: ${voiceID}, speed: ${finalSpeed}, text: ${text.substring(0, 100)}...`);
2545
2462
  const ai = currentSession.ai;
2463
+ if (voiceID.startsWith('BV0')) {
2464
+ throw new Error(`BV0* 系列音色已弃用,你必须已通过 search_voice 工具检查确定该音色确实是存在的。`);
2465
+ }
2546
2466
  const type = voiceID.startsWith('zh_') ||
2547
2467
  voiceID.startsWith('en_') ||
2548
2468
  voiceID.startsWith('multi_') ||
@@ -2592,7 +2512,7 @@ server.registerTool('generate-scene-tts', {
2592
2512
  console.log('TTS generated successfully, saving to materials...');
2593
2513
  const { url, duration, ...opts } = res;
2594
2514
  if (!skipConsistencyCheck && duration > 16) {
2595
- return createErrorResponse('TTS duration exceeds 16 seconds, 建议调整文本长度、提升语速或拆分场景...,⚠️如简化文本内容或拆分文本,需要立即更新 story_board 以保持内容同步!如仍要生成,可设置 skipConsistencyCheck 为 true,跳过一致性检查。', 'generate-scene-tts');
2515
+ return createErrorResponse('TTS duration exceeds 16 seconds, 建议调整文本长度、提升语速或拆分场景...,⚠️如简化文本内容或拆分文本,需要立即更新 storyboard 以保持内容同步!如仍要生成,可设置 skipConsistencyCheck 为 true,跳过一致性检查。', 'generate-scene-tts');
2596
2516
  }
2597
2517
  if (!duration) {
2598
2518
  return createErrorResponse('TTS duration not returned from AI service', 'generate-scene-tts');
@@ -2678,8 +2598,8 @@ server.registerTool('compile-and-run', {
2678
2598
  checkStoryboardSubtitlesFlag = true;
2679
2599
  return createErrorResponse(`请先对 draft_content 进行以下一致性检查:
2680
2600
 
2681
- 1. 检查字幕文字内容是否与 story_board 中各个场景的 script 或 dialog 内容完全一致(⚠️ 允许字幕分段展示,只要最终文本保持一致就行)
2682
- 2. 检查视频 resolution 设定是否与 story_board 的 orientation 设置一致,默认 720p 情况下视频尺寸应为横屏 1280x720,竖屏 720x1280,若视频为 1080p 则尺寸应分别为横屏 1920x1080 和竖屏 1080x1920,切勿设反
2601
+ 1. 检查字幕文字内容是否与 storyboard 中各个场景的 script 或 dialog 内容完全一致(⚠️ 允许字幕分段展示,只要最终文本保持一致就行)
2602
+ 2. 检查视频 resolution 设定是否与 storyboard 的 orientation 设置一致,默认 720p 情况下视频尺寸应为横屏 1280x720,竖屏 720x1280,若视频为 1080p 则尺寸应分别为横屏 1920x1080 和竖屏 1080x1920,切勿设反
2683
2603
  3. 除非用户明确表示不要背景音乐,否则应检查是否有生成并配置了 BGM,若无,则生成 BGM 并将其加入素材和轨道配置
2684
2604
 
2685
2605
  以上检查任何一项有问题,先修复 draft_content 使其符合要求后再进行合成`, 'compile-and-run');
@@ -2837,47 +2757,26 @@ server.registerTool('compile-and-run', {
2837
2757
  return createErrorResponse(error, 'compile-and-run');
2838
2758
  }
2839
2759
  });
2840
- server.registerTool('get-video-project-schema', {
2841
- title: 'Get VideoProject Schema',
2842
- description: 'Get the complete VideoProject JSON Schema definition. Should use this schema to validate draft_content JSON files.',
2843
- inputSchema: {},
2844
- }, async () => {
2760
+ server.registerTool('get-schema', {
2761
+ title: 'Get Storyboard Schema or Draft Content Schema',
2762
+ description: 'Get the complete Storyboard or Draft Content JSON Schema definition. Use this schema to validate storyboard.json or draft_content.json files.',
2763
+ inputSchema: {
2764
+ type: zod_1.z.enum(['storyboard', 'draft_content']),
2765
+ },
2766
+ }, async ({ type }) => {
2845
2767
  try {
2846
- const schemaPath = (0, node_path_1.resolve)(__dirname, '../../utils/videoproject-schema.json');
2768
+ const schemaPath = (0, node_path_1.resolve)(__dirname, `./prompts/${type}-schema.json`);
2847
2769
  const schemaContent = await (0, promises_1.readFile)(schemaPath, 'utf-8');
2848
2770
  const schema = JSON.parse(schemaContent);
2849
- return {
2850
- content: [
2851
- {
2852
- type: 'text',
2853
- text: JSON.stringify({
2854
- success: true,
2855
- schema,
2856
- important_guidelines: `⚠️ 生成文件时请严格遵守输出规范,字幕文本内容必须与 story_board.json 中的 script(或dialog) 字段的文本内容完全一致。
2771
+ let important_guidelines = '';
2772
+ if (type === 'draft_content') {
2773
+ important_guidelines = `⚠️ 生成文件时请严格遵守输出规范,字幕文本内容必须与 storyboard.json 中的 script(或dialog) 字段的文本内容完全一致。
2857
2774
 
2858
2775
  ** 字幕优化 **
2859
- * 在保证字幕文本内容与 story_board.json 中的 script(或dialog) 字段的文本内容完全一致的前提下,可根据 tts 返回的 \`captions.utterances\` 字段对字幕的显示进行优化,将过长的字幕分段显示,在 draft_content.json 中使用分段字幕,captions 的内容在 media_logs.json 中可查询到。
2776
+ * 在保证字幕文本内容与 storyboard.json 中的 script(或dialog) 字段的文本内容完全一致的前提下,可根据 tts 返回的 \`captions.utterances\` 字段对字幕的显示进行优化,将过长的字幕分段显示,在 draft_content.json 中使用分段字幕,captions 的内容在 media_logs.json 中可查询到。
2860
2777
  * 如用户未特殊指定,字幕样式(字体及大小)务必遵守输出规范
2861
- `,
2862
- timestamp: new Date().toISOString(),
2863
- }),
2864
- },
2865
- ],
2866
- };
2867
- }
2868
- catch (error) {
2869
- return createErrorResponse(error, 'get-video-project-schema');
2870
- }
2871
- });
2872
- server.registerTool('get-storyboard-schema', {
2873
- title: 'Get Storyboard Schema',
2874
- description: 'Get the complete Storyboard JSON Schema definition.',
2875
- inputSchema: {},
2876
- }, async () => {
2877
- try {
2878
- const schemaPath = (0, node_path_1.resolve)(__dirname, '../../utils/storyboard-schema.json');
2879
- const schemaContent = await (0, promises_1.readFile)(schemaPath, 'utf-8');
2880
- const schema = JSON.parse(schemaContent);
2778
+ `;
2779
+ }
2881
2780
  return {
2882
2781
  content: [
2883
2782
  {
@@ -2885,6 +2784,7 @@ server.registerTool('get-storyboard-schema', {
2885
2784
  text: JSON.stringify({
2886
2785
  success: true,
2887
2786
  schema,
2787
+ important_guidelines,
2888
2788
  timestamp: new Date().toISOString(),
2889
2789
  }),
2890
2790
  },
@@ -2892,7 +2792,7 @@ server.registerTool('get-storyboard-schema', {
2892
2792
  };
2893
2793
  }
2894
2794
  catch (error) {
2895
- return createErrorResponse(error, 'get-storyboard-schema');
2795
+ return createErrorResponse(error, 'get-schema');
2896
2796
  }
2897
2797
  });
2898
2798
  server.registerTool('do-storyboard-optimization', {
@@ -2912,7 +2812,7 @@ server.registerTool('do-storyboard-optimization', {
2912
2812
  text: JSON.stringify({
2913
2813
  content: {
2914
2814
  guideline: storyboardOptimizationGuidelines,
2915
- action: '你应当根据guideline优化story_board.json',
2815
+ action: '你应当根据guideline优化storyboard.json',
2916
2816
  },
2917
2817
  }),
2918
2818
  },
@@ -3296,180 +3196,206 @@ server.registerTool('media-analyzer', {
3296
3196
  return createErrorResponse(error, 'media-analyzer');
3297
3197
  }
3298
3198
  });
3299
- server.registerTool('image-aligner', {
3300
- title: 'Image Aligner',
3301
- description: 'Analyze image quality and alignment with prompt using AI Image Quality Inspector.',
3302
- inputSchema: {
3303
- imageFileName: zod_1.z
3304
- .string()
3305
- .describe('The image file name in materials directory to analyze.'),
3306
- sceneIndex: zod_1.z.number().min(1).describe('场景索引,从1开始的下标'),
3307
- storyBoardFile: zod_1.z
3308
- .string()
3309
- .optional()
3310
- .default('story_board.json')
3311
- .describe('故事板文件路径'),
3312
- imagePrompt: zod_1.z
3313
- .string()
3314
- .optional()
3315
- .describe('可选的图片提示词,如果提供则覆盖story_board中的提示词'),
3316
- customPrompt: zod_1.z
3317
- .string()
3318
- .optional()
3319
- .describe('可选的额外用户要求,用于补充图片质量评估的特定需求'),
3320
- },
3321
- }, async ({ imageFileName, sceneIndex, storyBoardFile = 'story_board.json', imagePrompt, customPrompt, }) => {
3322
- try {
3323
- const currentSession = await validateSession('image-aligner');
3324
- // 验证图片文件
3325
- validateImageFile(imageFileName);
3326
- // 获取图片 URL
3327
- const imageUrl = getMaterialUri(currentSession, imageFileName);
3328
- // 确定要使用的提示词
3329
- let finalPrompt = imagePrompt;
3330
- // 如果没有提供imagePrompt,则从story_board中获取
3331
- if (!imagePrompt) {
3332
- try {
3333
- const storyBoardPath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, storyBoardFile);
3334
- if ((0, node_fs_1.existsSync)(storyBoardPath)) {
3335
- const storyBoardContent = await (0, promises_1.readFile)(storyBoardPath, 'utf8');
3336
- const storyBoard = JSON.parse(storyBoardContent);
3337
- if (storyBoard.scenes && Array.isArray(storyBoard.scenes)) {
3338
- const scene = storyBoard.scenes[sceneIndex - 1]; // sceneIndex 从1开始,数组从0开始
3339
- if (scene) {
3340
- // 根据文件名判断优先级:若end_frame存在且imageFileName包含"_end"则优先取end_frame,否则取start_frame
3341
- if (scene.end_frame && imageFileName.includes('_end')) {
3342
- finalPrompt = scene.end_frame;
3343
- }
3344
- else {
3345
- finalPrompt = scene.start_frame || scene.end_frame;
3346
- }
3347
- if (!finalPrompt) {
3348
- return createErrorResponse(`场景 ${sceneIndex} 中未找到 start_frame 或 end_frame 提示词`, 'image-aligner');
3349
- }
3350
- }
3351
- else {
3352
- return createErrorResponse(`在 ${storyBoardFile} 中未找到场景索引 ${sceneIndex}`, 'image-aligner');
3353
- }
3354
- }
3355
- else {
3356
- return createErrorResponse(`${storyBoardFile} 文件格式不正确,缺少 scenes 数组`, 'image-aligner');
3357
- }
3358
- }
3359
- else {
3360
- return createErrorResponse(`故事板文件不存在: ${storyBoardPath}`, 'image-aligner');
3361
- }
3362
- }
3363
- catch (error) {
3364
- return createErrorResponse(`读取或解析故事板文件失败: ${error}`, 'image-aligner');
3365
- }
3366
- }
3367
- // 如果仍然没有提示词,返回错误
3368
- if (!finalPrompt) {
3369
- return createErrorResponse('未提供 imagePrompt 且无法从故事板中获取提示词', 'image-aligner');
3370
- }
3371
- // 读取图片质量检查指南
3372
- const alignerGuidelinePath = (0, node_path_1.resolve)(__dirname, './prompts/reasonings/image_aligner.md');
3373
- let alignerGuideline = '';
3374
- try {
3375
- alignerGuideline = await (0, promises_1.readFile)(alignerGuidelinePath, 'utf8');
3376
- }
3377
- catch (error) {
3378
- console.warn('无法读取图片质量检查指南:', error);
3379
- alignerGuideline =
3380
- '请对图片质量进行评估,包括构图、色彩、清晰度等方面。';
3381
- }
3382
- // 构建系统提示
3383
- const systemPrompt = `你是一个专业的AI图片质量检查员。请根据以下指南对图片进行评估:
3384
-
3385
- ${alignerGuideline}
3386
-
3387
- 请严格按照指南中的JSON格式返回评估结果。`;
3388
- // 构建用户提示
3389
- const userPrompt = `请对这张图片进行质量评估。
3390
-
3391
- 原始提示词:${finalPrompt}${customPrompt
3392
- ? `
3393
-
3394
- 额外要求:${customPrompt}`
3395
- : ''}
3396
-
3397
- 请按照指南要求,返回包含评分、问题列表和优化建议的JSON格式结果。`;
3398
- // 调用AI模型进行图片质量评估
3399
- const ai = currentSession.ai;
3400
- const completion = await ai.getCompletions({
3401
- model: 'Doubao-Seed-1.6',
3402
- messages: [
3403
- {
3404
- role: 'system',
3405
- content: systemPrompt,
3406
- },
3407
- {
3408
- role: 'user',
3409
- content: [
3410
- {
3411
- type: 'image_url',
3412
- image_url: {
3413
- url: imageUrl,
3414
- },
3415
- },
3416
- {
3417
- type: 'text',
3418
- text: userPrompt,
3419
- },
3420
- ],
3421
- },
3422
- ],
3423
- });
3424
- const result = completion.choices[0]?.message?.content;
3425
- if (!result) {
3426
- throw new Error('No response from AI model');
3427
- }
3428
- // 解析AI响应
3429
- let alignmentResult;
3430
- try {
3431
- // 尝试从响应中提取JSON
3432
- const jsonMatch = result.match(/```json\s*([\s\S]*?)\s*```/) ||
3433
- result.match(/\{[\s\S]*\}/);
3434
- if (jsonMatch) {
3435
- alignmentResult = JSON.parse(jsonMatch[1] || jsonMatch[0]);
3436
- }
3437
- else {
3438
- // 如果没有找到JSON格式,尝试直接解析整个响应
3439
- alignmentResult = JSON.parse(result);
3440
- }
3441
- }
3442
- catch (error) {
3443
- // 如果解析失败,返回原始响应
3444
- alignmentResult = {
3445
- error: 'JSON解析失败',
3446
- raw_response: result,
3447
- };
3448
- }
3449
- return {
3450
- content: [
3451
- {
3452
- type: 'text',
3453
- text: JSON.stringify({
3454
- success: true,
3455
- imageFileName,
3456
- sceneIndex,
3457
- storyBoardFile,
3458
- imagePrompt: finalPrompt,
3459
- customPrompt,
3460
- promptSource: imagePrompt ? 'manual_override' : 'story_board',
3461
- analysis: alignmentResult,
3462
- imageUrl,
3463
- nextActionSuggest: '可根据分析结果调整提示词,修改story_board后,重新生成图片。',
3464
- }),
3465
- },
3466
- ],
3467
- };
3468
- }
3469
- catch (error) {
3470
- return createErrorResponse(error, 'image-aligner');
3471
- }
3472
- });
3199
+ // server.registerTool(
3200
+ // 'image-aligner',
3201
+ // {
3202
+ // title: 'Image Aligner',
3203
+ // description:
3204
+ // 'Analyze image quality and alignment with prompt using AI Image Quality Inspector.',
3205
+ // inputSchema: {
3206
+ // imageFileName: z
3207
+ // .string()
3208
+ // .describe('The image file name in materials directory to analyze.'),
3209
+ // sceneIndex: z.number().min(1).describe('场景索引,从1开始的下标'),
3210
+ // storyBoardFile: z
3211
+ // .string()
3212
+ // .optional()
3213
+ // .default('storyboard.json')
3214
+ // .describe('故事板文件路径'),
3215
+ // imagePrompt: z
3216
+ // .string()
3217
+ // .optional()
3218
+ // .describe('可选的图片提示词,如果提供则覆盖storyboard中的提示词'),
3219
+ // customPrompt: z
3220
+ // .string()
3221
+ // .optional()
3222
+ // .describe('可选的额外用户要求,用于补充图片质量评估的特定需求'),
3223
+ // },
3224
+ // },
3225
+ // async ({
3226
+ // imageFileName,
3227
+ // sceneIndex,
3228
+ // storyBoardFile = 'storyboard.json',
3229
+ // imagePrompt,
3230
+ // customPrompt,
3231
+ // }) => {
3232
+ // try {
3233
+ // const currentSession = await validateSession('image-aligner');
3234
+ // // 验证图片文件
3235
+ // validateImageFile(imageFileName);
3236
+ // // 获取图片 URL
3237
+ // const imageUrl = getMaterialUri(currentSession, imageFileName);
3238
+ // // 确定要使用的提示词
3239
+ // let finalPrompt = imagePrompt;
3240
+ // // 如果没有提供imagePrompt,则从storyboard中获取
3241
+ // if (!imagePrompt) {
3242
+ // try {
3243
+ // const storyBoardPath = resolve(
3244
+ // process.env.ZEROCUT_PROJECT_CWD || process.cwd(),
3245
+ // projectLocalDir,
3246
+ // storyBoardFile
3247
+ // );
3248
+ // if (existsSync(storyBoardPath)) {
3249
+ // const storyBoardContent = await readFile(storyBoardPath, 'utf8');
3250
+ // const storyBoard = JSON.parse(storyBoardContent);
3251
+ // if (storyBoard.scenes && Array.isArray(storyBoard.scenes)) {
3252
+ // const scene = storyBoard.scenes[sceneIndex - 1]; // sceneIndex 从1开始,数组从0开始
3253
+ // if (scene) {
3254
+ // // 根据文件名判断优先级:若end_frame存在且imageFileName包含"_end"则优先取end_frame,否则取start_frame
3255
+ // if (scene.end_frame && imageFileName.includes('_end')) {
3256
+ // finalPrompt = scene.end_frame;
3257
+ // } else {
3258
+ // finalPrompt = scene.start_frame || scene.end_frame;
3259
+ // }
3260
+ // if (!finalPrompt) {
3261
+ // return createErrorResponse(
3262
+ // `场景 ${sceneIndex} 中未找到 start_frame 或 end_frame 提示词`,
3263
+ // 'image-aligner'
3264
+ // );
3265
+ // }
3266
+ // } else {
3267
+ // return createErrorResponse(
3268
+ // `在 ${storyBoardFile} 中未找到场景索引 ${sceneIndex}`,
3269
+ // 'image-aligner'
3270
+ // );
3271
+ // }
3272
+ // } else {
3273
+ // return createErrorResponse(
3274
+ // `${storyBoardFile} 文件格式不正确,缺少 scenes 数组`,
3275
+ // 'image-aligner'
3276
+ // );
3277
+ // }
3278
+ // } else {
3279
+ // return createErrorResponse(
3280
+ // `故事板文件不存在: ${storyBoardPath}`,
3281
+ // 'image-aligner'
3282
+ // );
3283
+ // }
3284
+ // } catch (error) {
3285
+ // return createErrorResponse(
3286
+ // `读取或解析故事板文件失败: ${error}`,
3287
+ // 'image-aligner'
3288
+ // );
3289
+ // }
3290
+ // }
3291
+ // // 如果仍然没有提示词,返回错误
3292
+ // if (!finalPrompt) {
3293
+ // return createErrorResponse(
3294
+ // '未提供 imagePrompt 且无法从故事板中获取提示词',
3295
+ // 'image-aligner'
3296
+ // );
3297
+ // }
3298
+ // // 读取图片质量检查指南
3299
+ // const alignerGuidelinePath = resolve(
3300
+ // __dirname,
3301
+ // './prompts/reasonings/image_aligner.md'
3302
+ // );
3303
+ // let alignerGuideline = '';
3304
+ // try {
3305
+ // alignerGuideline = await readFile(alignerGuidelinePath, 'utf8');
3306
+ // } catch (error) {
3307
+ // console.warn('无法读取图片质量检查指南:', error);
3308
+ // alignerGuideline =
3309
+ // '请对图片质量进行评估,包括构图、色彩、清晰度等方面。';
3310
+ // }
3311
+ // // 构建系统提示
3312
+ // const systemPrompt = `你是一个专业的AI图片质量检查员。请根据以下指南对图片进行评估:
3313
+ // ${alignerGuideline}
3314
+ // 请严格按照指南中的JSON格式返回评估结果。`;
3315
+ // // 构建用户提示
3316
+ // const userPrompt = `请对这张图片进行质量评估。
3317
+ // 原始提示词:${finalPrompt}${
3318
+ // customPrompt
3319
+ // ? `
3320
+ // 额外要求:${customPrompt}`
3321
+ // : ''
3322
+ // }
3323
+ // 请按照指南要求,返回包含评分、问题列表和优化建议的JSON格式结果。`;
3324
+ // // 调用AI模型进行图片质量评估
3325
+ // const ai = currentSession.ai;
3326
+ // const completion = await ai.getCompletions({
3327
+ // model: 'Doubao-Seed-1.6',
3328
+ // messages: [
3329
+ // {
3330
+ // role: 'system',
3331
+ // content: systemPrompt,
3332
+ // },
3333
+ // {
3334
+ // role: 'user',
3335
+ // content: [
3336
+ // {
3337
+ // type: 'image_url',
3338
+ // image_url: {
3339
+ // url: imageUrl,
3340
+ // },
3341
+ // },
3342
+ // {
3343
+ // type: 'text',
3344
+ // text: userPrompt,
3345
+ // },
3346
+ // ],
3347
+ // },
3348
+ // ],
3349
+ // });
3350
+ // const result = completion.choices[0]?.message?.content;
3351
+ // if (!result) {
3352
+ // throw new Error('No response from AI model');
3353
+ // }
3354
+ // // 解析AI响应
3355
+ // let alignmentResult;
3356
+ // try {
3357
+ // // 尝试从响应中提取JSON
3358
+ // const jsonMatch =
3359
+ // result.match(/```json\s*([\s\S]*?)\s*```/) ||
3360
+ // result.match(/\{[\s\S]*\}/);
3361
+ // if (jsonMatch) {
3362
+ // alignmentResult = JSON.parse(jsonMatch[1] || jsonMatch[0]);
3363
+ // } else {
3364
+ // // 如果没有找到JSON格式,尝试直接解析整个响应
3365
+ // alignmentResult = JSON.parse(result);
3366
+ // }
3367
+ // } catch (error) {
3368
+ // // 如果解析失败,返回原始响应
3369
+ // alignmentResult = {
3370
+ // error: 'JSON解析失败',
3371
+ // raw_response: result,
3372
+ // };
3373
+ // }
3374
+ // return {
3375
+ // content: [
3376
+ // {
3377
+ // type: 'text',
3378
+ // text: JSON.stringify({
3379
+ // success: true,
3380
+ // imageFileName,
3381
+ // sceneIndex,
3382
+ // storyBoardFile,
3383
+ // imagePrompt: finalPrompt,
3384
+ // customPrompt,
3385
+ // promptSource: imagePrompt ? 'manual_override' : 'storyboard',
3386
+ // analysis: alignmentResult,
3387
+ // imageUrl,
3388
+ // nextActionSuggest:
3389
+ // '可根据分析结果调整提示词,修改storyboard后,重新生成图片。',
3390
+ // }),
3391
+ // },
3392
+ // ],
3393
+ // };
3394
+ // } catch (error) {
3395
+ // return createErrorResponse(error, 'image-aligner');
3396
+ // }
3397
+ // }
3398
+ // );
3473
3399
  server.registerTool('audio-video-sync', {
3474
3400
  title: 'Audio Video Sync',
3475
3401
  description: 'Generate audio-video-synced video by matching video with audio. 还可以对口型。',
@@ -3640,7 +3566,7 @@ server.registerTool('generate-video-by-ref', {
3640
3566
  .describe('Array of reference image objects with name, url and type. Can be empty for text-only generation.'),
3641
3567
  duration: zod_1.z
3642
3568
  .number()
3643
- .min(2)
3569
+ .min(1)
3644
3570
  .max(16)
3645
3571
  .optional()
3646
3572
  .default(5)
@@ -3671,7 +3597,7 @@ server.registerTool('generate-video-by-ref', {
3671
3597
  storyBoardFile: zod_1.z
3672
3598
  .string()
3673
3599
  .optional()
3674
- .default('story_board.json')
3600
+ .default('storyboard.json')
3675
3601
  .describe('故事板文件路径'),
3676
3602
  skipConsistencyCheck: zod_1.z
3677
3603
  .boolean()
@@ -3696,17 +3622,17 @@ server.registerTool('generate-video-by-ref', {
3696
3622
  // 检查 storyboard 标志
3697
3623
  if (!checkStoryboardFlag && (0, node_fs_1.existsSync)(storyBoardPath)) {
3698
3624
  checkStoryboardFlag = true;
3699
- return createErrorResponse(`必须先审查生成的 story_board.json 内容,按照如下步骤:
3625
+ return createErrorResponse(`必须先审查生成的 storyboard.json 内容,按照如下步骤:
3700
3626
 
3701
3627
  1. 确保每个场景中的stage_atmosphere内容按照规则被正确融合到video_prompt中,不得遗漏
3702
3628
  2. 如有main_characters设定且包含了reference_image,或有reference_objects,需确保video_prompt描述已包含该场景相关main_characters和所有reference_objects中的物品或背景,并确保参考图具体内容已经在video_prompt中有明确描述,如果没有,可忽略。
3703
3629
  3. 如有配音,先自我检查 media_logs 中的查音频时长,确保以匹配音频时长来生成视频
3704
3630
 
3705
- 检查完上述问题后先汇报,如果有需要,应当先修改 story_board.json 内容,然后再调用 generate-video-by-ref 生成视频。注意修改 story_board 内容时,仅修改相应字段的字符串值,不要破坏JSON格式!
3631
+ 检查完上述问题后先汇报,如果有需要,应当先修改 storyboard.json 内容,然后再调用 generate-video-by-ref 生成视频。注意修改 storyboard 内容时,仅修改相应字段的字符串值,不要破坏JSON格式!
3706
3632
 
3707
3633
  再次调用 generate-video-by-ref 时,如需要参考图,要确保referenceImages使用正确(main_characters中的reference_image作为参考人物,reference_objects中的image作为参考物品或参考背景)`, 'generate-image');
3708
3634
  }
3709
- // 校验 prompt 与 story_board.json 中场景设定的一致性(如果提供了 sceneIndex)
3635
+ // 校验 prompt 与 storyboard.json 中场景设定的一致性(如果提供了 sceneIndex)
3710
3636
  if (!skipConsistencyCheck && sceneIndex) {
3711
3637
  try {
3712
3638
  if ((0, node_fs_1.existsSync)(storyBoardPath)) {
@@ -3724,7 +3650,7 @@ server.registerTool('generate-video-by-ref', {
3724
3650
  if (scene) {
3725
3651
  const videoPrompt = scene.video_prompt;
3726
3652
  if (videoPrompt && prompt !== videoPrompt) {
3727
- return createErrorResponse('视频提示词必须严格遵照story_board的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-video-by-ref');
3653
+ return createErrorResponse('视频提示词必须严格遵照storyboard的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-video-by-ref');
3728
3654
  }
3729
3655
  // 检查 scene.is_continuous 是否为 true
3730
3656
  if (scene.is_continuous === true) {
@@ -3791,7 +3717,7 @@ server.registerTool('generate-video-by-ref', {
3791
3717
  }
3792
3718
  }
3793
3719
  else {
3794
- console.warn(`Scene index ${sceneIndex} not found in story_board.json`);
3720
+ console.warn(`Scene index ${sceneIndex} not found in storyboard.json`);
3795
3721
  }
3796
3722
  }
3797
3723
  }
@@ -3801,7 +3727,7 @@ server.registerTool('generate-video-by-ref', {
3801
3727
  }
3802
3728
  catch (error) {
3803
3729
  console.error('Failed to validate prompt with story board:', error);
3804
- // 如果读取或解析 story_board.json 失败,继续执行但记录警告
3730
+ // 如果读取或解析 storyboard.json 失败,继续执行但记录警告
3805
3731
  }
3806
3732
  }
3807
3733
  const validatedFileName = validateFileName(saveToFileName);
@@ -4026,6 +3952,132 @@ server.registerTool('extend-video-duration', {
4026
3952
  return createErrorResponse(error, 'extend-video');
4027
3953
  }
4028
3954
  });
3955
+ server.registerTool('generate-video-by-template', {
3956
+ title: 'Generate Video by Template',
3957
+ description: 'Generate a video based on a template. The template must be a valid JSON string.',
3958
+ inputSchema: {
3959
+ purpose: zod_1.z
3960
+ .string()
3961
+ .describe('The prompt to generate the video. 自动根据意图匹配模板'),
3962
+ text_prompts: zod_1.z
3963
+ .array(zod_1.z.string().describe('Text prompt for the template to build video.'))
3964
+ .optional()
3965
+ .describe('Optional text prompts to use in the template.'),
3966
+ materials: zod_1.z
3967
+ .array(zod_1.z.string().describe('Material file name in materials directory.'))
3968
+ .optional()
3969
+ .describe('Optional materials to use in the template.'),
3970
+ saveToFileName: zod_1.z
3971
+ .string()
3972
+ .describe('The filename to save the generated video.'),
3973
+ },
3974
+ }, async ({ purpose, text_prompts, saveToFileName, materials }) => {
3975
+ try {
3976
+ const templates = {
3977
+ '7569583728302817331': '宠物唱歌',
3978
+ '7569605825011367976': '万圣节宠物弹吉他',
3979
+ };
3980
+ const currentSession = await validateSession('generate-video-by-template');
3981
+ const validatedFileName = validateFileName(saveToFileName);
3982
+ const ai = currentSession.ai;
3983
+ let completion = await ai.getCompletions({
3984
+ model: 'Doubao-Seed-1.6-flash',
3985
+ messages: [
3986
+ {
3987
+ role: 'system',
3988
+ content: `你根据用户需求,从以下模板中选择一个匹配的模板,返回模板ID:\n\n${JSON.stringify(templates)}\n\n**约束**:只输出模板ID,不需要其他解释。`,
3989
+ },
3990
+ {
3991
+ role: 'user',
3992
+ content: `用户意图:${purpose}`,
3993
+ },
3994
+ ],
3995
+ });
3996
+ const templateId = completion.choices[0]?.message?.content.trim();
3997
+ if (!templateId) {
3998
+ throw new Error('Failed to get template ID');
3999
+ }
4000
+ const workflowInfo = await ai.getCozeWorkflowInfo(templateId);
4001
+ const materialUrls = materials?.map(material => getMaterialUri(currentSession, material));
4002
+ const schema = {
4003
+ name: 'workflow_parameters',
4004
+ schema: {
4005
+ type: 'object',
4006
+ properties: {
4007
+ parameters: {
4008
+ type: 'object',
4009
+ description: 'The parameters for the workflow.',
4010
+ },
4011
+ },
4012
+ required: ['parameters'],
4013
+ },
4014
+ };
4015
+ const prompt = `你根据模板工作流输入 schema 和 prompt、materials 生成一个 JSON 字符串,作为模板工作流的参数。
4016
+
4017
+ ## **工作流输入 schema**:
4018
+ ${JSON.stringify(workflowInfo.data.workflow_detail.description)}
4019
+
4020
+ ## **prompt**:
4021
+ ${text_prompts}
4022
+
4023
+ ## **materials**:
4024
+ ${JSON.stringify(materialUrls)}`;
4025
+ completion = await ai.getCompletions({
4026
+ model: 'Doubao-Seed-1.6-flash',
4027
+ messages: [
4028
+ {
4029
+ role: 'system',
4030
+ content: prompt,
4031
+ },
4032
+ {
4033
+ role: 'user',
4034
+ content: `生成模板调用的正确parameters参数`,
4035
+ },
4036
+ ],
4037
+ response_format: {
4038
+ type: 'json_schema',
4039
+ json_schema: schema,
4040
+ },
4041
+ });
4042
+ const parameters = completion.choices[0]?.message?.content.trim();
4043
+ if (!parameters) {
4044
+ throw new Error('Failed to get parameters');
4045
+ }
4046
+ console.log(parameters);
4047
+ const result = await ai.runCozeWorkflow(templateId, JSON.parse(parameters).parameters);
4048
+ if (result.url) {
4049
+ // 保存到项目材料目录
4050
+ const uri = await saveMaterial(currentSession, result.url, validatedFileName);
4051
+ return {
4052
+ content: [
4053
+ {
4054
+ type: 'text',
4055
+ text: JSON.stringify({
4056
+ success: true,
4057
+ parameters,
4058
+ uri,
4059
+ }),
4060
+ },
4061
+ ],
4062
+ };
4063
+ }
4064
+ return {
4065
+ content: [
4066
+ {
4067
+ type: 'text',
4068
+ text: JSON.stringify({
4069
+ success: false,
4070
+ result,
4071
+ parameters,
4072
+ }),
4073
+ },
4074
+ ],
4075
+ };
4076
+ }
4077
+ catch (error) {
4078
+ return createErrorResponse(error, 'generate-video-by-template');
4079
+ }
4080
+ });
4029
4081
  server.registerTool('run-ffmpeg-command', {
4030
4082
  title: 'Run FFmpeg Command',
4031
4083
  description: 'Run ffmpeg or ffprobe commands in the sandbox environment. Only ffmpeg and ffprobe commands are allowed. The default working directory is the materials directory.',
@@ -4269,182 +4321,6 @@ server.registerTool('build-capcat-draft', {
4269
4321
  return createErrorResponse(error, 'build-capcat-draft');
4270
4322
  }
4271
4323
  });
4272
- // server.registerTool(
4273
- // 'custom-edit-draft',
4274
- // {
4275
- // title: 'Custom Edit Draft',
4276
- // description:
4277
- // 'Launch timeline editor server for custom draft editing. Starts a local server at specified port with draft file configuration.',
4278
- // inputSchema: {
4279
- // draftContentFileName: z
4280
- // .string()
4281
- // .optional()
4282
- // .default('draft_content.json')
4283
- // .describe('Draft content filename (default: draft_content.json)'),
4284
- // port: z
4285
- // .number()
4286
- // .optional()
4287
- // .default(6789)
4288
- // .describe('Server port (default: 6789)'),
4289
- // },
4290
- // },
4291
- // async ({ draftContentFileName = 'draft_content.json', port = 6789 }) => {
4292
- // try {
4293
- // // 使用项目本地目录
4294
- // const workDir = resolve(
4295
- // process.env.ZEROCUT_PROJECT_CWD || process.cwd(),
4296
- // projectLocalDir
4297
- // );
4298
- // // 检查 draft_content.json 文件是否存在
4299
- // const draftFilePath = resolve(workDir, draftContentFileName);
4300
- // if (!existsSync(draftFilePath)) {
4301
- // return createErrorResponse(
4302
- // new Error(
4303
- // `Draft content file not found: ${draftContentFileName}. Please generate draft content first using the generate-draft-content tool.`
4304
- // ),
4305
- // 'custom-edit-draft'
4306
- // );
4307
- // }
4308
- // // 备份原始的 draft_content.json 文件
4309
- // const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
4310
- // const backupFileName = `${draftContentFileName}.backup.${timestamp}`;
4311
- // const backupFilePath = resolve(workDir, backupFileName);
4312
- // try {
4313
- // const { copyFileSync } = await import('fs');
4314
- // copyFileSync(draftFilePath, backupFilePath);
4315
- // console.log(`Draft file backed up to: ${backupFileName}`);
4316
- // } catch (backupError) {
4317
- // console.warn(`Failed to backup draft file: ${backupError}`);
4318
- // // 继续执行,不因备份失败而中断
4319
- // }
4320
- // // 构建启动命令参数
4321
- // const timelineEditorPath = resolve(
4322
- // __dirname,
4323
- // '../../../dist/timeline-editor/index.js'
4324
- // );
4325
- // console.log(`Starting timeline editor server...`);
4326
- // console.log(`Working directory: ${workDir}`);
4327
- // console.log(`Draft file: ${draftContentFileName}`);
4328
- // console.log(`Server port: ${port}`);
4329
- // console.log(`Timeline editor path: ${timelineEditorPath}`);
4330
- // // 检查端口是否被占用,如果被占用则停止占用的进程
4331
- // if (await isPortInUse(port)) {
4332
- // console.log(`Port ${port} is in use, killing existing processes...`);
4333
- // await killPortProcess(port);
4334
- // }
4335
- // // 直接启动服务器进程
4336
- // const platform = os.platform();
4337
- // const nodeCommand = platform === 'win32' ? 'node.exe' : 'node';
4338
- // const serverProcess = spawn(
4339
- // nodeCommand,
4340
- // [
4341
- // timelineEditorPath,
4342
- // '--draft-file',
4343
- // draftContentFileName,
4344
- // '--project-dir',
4345
- // workDir,
4346
- // '--port',
4347
- // port.toString(),
4348
- // ],
4349
- // {
4350
- // cwd: workDir,
4351
- // stdio: 'pipe',
4352
- // shell: platform === 'win32', // 在 Windows 下使用 shell
4353
- // }
4354
- // );
4355
- // const serverUrl = `http://localhost:${port}`;
4356
- // // 等待服务器启动 - 使用健康检查 API 轮询
4357
- // await new Promise((resolve, reject) => {
4358
- // const timeout = setTimeout(() => {
4359
- // reject(new Error('Server startup timeout'));
4360
- // }, 30000); // 增加超时时间到30秒
4361
- // const checkHealth = async () => {
4362
- // try {
4363
- // const healthUrl = `${serverUrl}/health`;
4364
- // const req = http.get(healthUrl, (res: any) => {
4365
- // let data = '';
4366
- // res.on('data', (chunk: any) => {
4367
- // data += chunk;
4368
- // });
4369
- // res.on('end', () => {
4370
- // try {
4371
- // const healthData = JSON.parse(data);
4372
- // if (healthData.status === 'ok') {
4373
- // clearTimeout(timeout);
4374
- // console.log('Server health check passed');
4375
- // resolve(void 0);
4376
- // } else {
4377
- // // 继续轮询
4378
- // setTimeout(checkHealth, 1000);
4379
- // }
4380
- // } catch (parseError) {
4381
- // // 继续轮询
4382
- // setTimeout(checkHealth, 1000);
4383
- // }
4384
- // });
4385
- // });
4386
- // req.on('error', () => {
4387
- // // 服务器还未启动,继续轮询
4388
- // setTimeout(checkHealth, 1000);
4389
- // });
4390
- // req.setTimeout(2000, () => {
4391
- // req.destroy();
4392
- // // 继续轮询
4393
- // setTimeout(checkHealth, 1000);
4394
- // });
4395
- // } catch (error) {
4396
- // // 继续轮询
4397
- // setTimeout(checkHealth, 1000);
4398
- // }
4399
- // };
4400
- // serverProcess.stdout?.on('data', (data: any) => {
4401
- // console.log('Server output:', data.toString());
4402
- // });
4403
- // serverProcess.stderr?.on('data', (data: any) => {
4404
- // console.error('Server error:', data.toString());
4405
- // });
4406
- // serverProcess.on('error', (error: any) => {
4407
- // clearTimeout(timeout);
4408
- // reject(error);
4409
- // });
4410
- // // 开始健康检查轮询
4411
- // setTimeout(checkHealth, 2000); // 2秒后开始第一次检查
4412
- // });
4413
- // return {
4414
- // content: [
4415
- // {
4416
- // type: 'text',
4417
- // text: JSON.stringify(
4418
- // {
4419
- // success: true,
4420
- // message: 'Timeline editor server started successfully',
4421
- // serverUrl: serverUrl,
4422
- // draftFile: draftContentFileName,
4423
- // backupFile: backupFileName,
4424
- // projectDirectory: workDir,
4425
- // port: port,
4426
- // pid: serverProcess.pid,
4427
- // instructions: [
4428
- // `Timeline editor is now running at: ${serverUrl}`,
4429
- // 'Open the URL in your browser to edit the draft',
4430
- // 'The server will automatically save changes to the draft file',
4431
- // `Original draft file has been backed up as: ${backupFileName}`,
4432
- // `Server process ID: ${serverProcess.pid}`,
4433
- // 'The server will continue running in the background',
4434
- // ],
4435
- // },
4436
- // null,
4437
- // 2
4438
- // ),
4439
- // },
4440
- // ],
4441
- // };
4442
- // } catch (error) {
4443
- // console.error('Error starting timeline editor:', error);
4444
- // return createErrorResponse(error, 'custom-edit-draft');
4445
- // }
4446
- // }
4447
- // );
4448
4324
  async function run() {
4449
4325
  // Start receiving messages on stdin and sending messages on stdout
4450
4326
  const transport = new stdio_js_1.StdioServerTransport();