cerevox 4.0.0-alpha.3 → 4.0.0-alpha.30
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/core/ai.d.ts +18 -2
- package/dist/core/ai.d.ts.map +1 -1
- package/dist/core/ai.js +108 -37
- package/dist/core/ai.js.map +1 -1
- package/dist/mcp/servers/prompts/.DS_Store +0 -0
- package/dist/mcp/servers/prompts/outlines-backup.md +21 -14
- package/dist/mcp/servers/prompts/skills//344/270/200/351/224/256/346/210/220/347/211/207.md +18 -26
- package/dist/mcp/servers/prompts/skills//345/210/206/351/225/234/345/270/210.md +11 -9
- package/dist/mcp/servers/prompts/skills//347/264/240/346/235/220/345/210/233/344/275/234/357/274/210/351/200/232/347/224/250/357/274/211.md +1 -1
- package/dist/mcp/servers/prompts/skills//350/247/206/351/242/221/345/244/215/345/210/273.md +122 -0
- package/dist/mcp/servers/prompts/zerocut-core.md +25 -1
- package/dist/mcp/servers/zerocut.d.ts.map +1 -1
- package/dist/mcp/servers/zerocut.js +226 -199
- package/dist/mcp/servers/zerocut.js.map +1 -1
- package/dist/utils/videokit.d.ts +4 -4
- package/package.json +1 -1
- /package/dist/mcp/servers/prompts/{zerocut-core-web.md → zerocut-core-web-old.md} +0 -0
|
@@ -54,6 +54,7 @@ const node_fs_1 = require("node:fs");
|
|
|
54
54
|
const mp3_duration_1 = __importDefault(require("mp3-duration"));
|
|
55
55
|
const image_size_1 = __importDefault(require("image-size"));
|
|
56
56
|
const seed_1 = require("../../utils/seed");
|
|
57
|
+
const uuid_1 = require("uuid");
|
|
57
58
|
function createErrorResponse(error, operation, details) {
|
|
58
59
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
59
60
|
console.error(`[${operation}] Error:`, error);
|
|
@@ -140,6 +141,8 @@ async function saveMaterial(session, url, saveToFileName) {
|
|
|
140
141
|
const terminal = session.terminal;
|
|
141
142
|
const saveToPath = `/home/user/cerevox-zerocut/projects/${terminal.id}/materials/${saveToFileName}`;
|
|
142
143
|
const saveLocalPath = (0, node_path_1.resolve)(projectLocalDir, 'materials', saveToFileName);
|
|
144
|
+
// 确保目录存在
|
|
145
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(saveLocalPath), { recursive: true });
|
|
143
146
|
// 先下载到本地,再上传 sandbox,比直接 sandbox 更好,也以免下载超时
|
|
144
147
|
// 通过 fetch 下载到本地
|
|
145
148
|
const res = await fetch(url);
|
|
@@ -330,18 +333,18 @@ const cerevox = new index_1.default({
|
|
|
330
333
|
logLevel: 'error',
|
|
331
334
|
});
|
|
332
335
|
let session = null;
|
|
333
|
-
let projectLocalDir = process.env.ZEROCUT_PROJECT_CWD ||
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
// let checkStoryboardSubtitlesFlag = false;
|
|
336
|
+
let projectLocalDir = process.env.ZEROCUT_PROJECT_CWD ||
|
|
337
|
+
`${(0, node_path_1.resolve)(process.env.ZEROCUT_WORKSPACE_DIR, (0, uuid_1.v4)())}` ||
|
|
338
|
+
'.';
|
|
337
339
|
let closeSessionTimerId = null;
|
|
340
|
+
const CLIENT_TYPE = process.env.CLIENT_TYPE || 'trae';
|
|
338
341
|
// 注册 ZeroCut 指导规范 Prompt
|
|
339
342
|
server.registerPrompt('zerocut-guideline', {
|
|
340
343
|
title: 'ZeroCut 短视频创作指导规范',
|
|
341
344
|
description: '专业的短视频创作 Agent 指导规范,包含完整的工作流程、工具说明和质量建议',
|
|
342
345
|
}, async () => {
|
|
343
346
|
try {
|
|
344
|
-
const promptPath = (0, node_path_1.resolve)(__dirname, './prompts/zerocut-core
|
|
347
|
+
const promptPath = (0, node_path_1.resolve)(__dirname, './prompts/zerocut-core.md');
|
|
345
348
|
const promptContent = await (0, promises_1.readFile)(promptPath, 'utf-8');
|
|
346
349
|
return {
|
|
347
350
|
messages: [
|
|
@@ -362,25 +365,23 @@ server.registerPrompt('zerocut-guideline', {
|
|
|
362
365
|
});
|
|
363
366
|
server.registerTool('project-open', {
|
|
364
367
|
title: 'Open Project',
|
|
365
|
-
description: 'Launch a new Cerevox session with a Chromium browser instance and open a new project context. Supports smart file filtering to optimize
|
|
368
|
+
description: 'Launch a new Cerevox session with a Chromium browser instance and open a new project context. Supports smart file filtering to optimize resource-sync performance.',
|
|
366
369
|
inputSchema: {
|
|
367
|
-
|
|
370
|
+
projectName: zod_1.z
|
|
368
371
|
.string()
|
|
369
|
-
.
|
|
370
|
-
.default('.')
|
|
371
|
-
.describe('The path of the file to upload.'),
|
|
372
|
+
.describe('项目名,命名规则如同命名文件,可以用空格,但不得使用特殊字符(如/、:等),如果你知晓当前目录,必须用当前目录的最后一级目录名作为项目名,否则根据用户需求自定义项目名'),
|
|
372
373
|
tosFiles: zod_1.z
|
|
373
374
|
.array(zod_1.z.string())
|
|
374
375
|
.optional()
|
|
375
376
|
.default([])
|
|
376
377
|
.describe('对象存储系统中的持久化文件,不通过本地直接下载到项目目录,可选参数'),
|
|
377
|
-
|
|
378
|
+
syncAllFiles: zod_1.z
|
|
378
379
|
.boolean()
|
|
379
380
|
.optional()
|
|
380
381
|
.default(false)
|
|
381
|
-
.describe('Whether to
|
|
382
|
+
.describe('Whether to sync all resources without filtering. If true, skips the smart filtering logic.'),
|
|
382
383
|
},
|
|
383
|
-
}, async ({
|
|
384
|
+
}, async ({ projectName, syncAllFiles, tosFiles }, context) => {
|
|
384
385
|
try {
|
|
385
386
|
if (closeSessionTimerId) {
|
|
386
387
|
clearTimeout(closeSessionTimerId);
|
|
@@ -388,12 +389,9 @@ server.registerTool('project-open', {
|
|
|
388
389
|
}
|
|
389
390
|
// 检查是否已有活跃session
|
|
390
391
|
if (session) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
// } catch (closeError) {
|
|
395
|
-
// console.warn('Failed to close previous session:', closeError);
|
|
396
|
-
// }
|
|
392
|
+
if (session.projectName !== projectName) {
|
|
393
|
+
return createErrorResponse('project-open', `Another project is already open: ${session.projectName},Ask user to close it first.`);
|
|
394
|
+
}
|
|
397
395
|
const result = {
|
|
398
396
|
success: true,
|
|
399
397
|
sessionId: session.id,
|
|
@@ -424,10 +422,20 @@ server.registerTool('project-open', {
|
|
|
424
422
|
if (!session) {
|
|
425
423
|
throw new Error('Failed to create Cerevox session');
|
|
426
424
|
}
|
|
425
|
+
// 保存项目名到session
|
|
426
|
+
session.projectName = projectName;
|
|
427
427
|
console.log('Initializing project...');
|
|
428
428
|
const workDir = await initProject(session);
|
|
429
|
-
|
|
429
|
+
if (!process.env.ZEROCUT_PROJECT_CWD &&
|
|
430
|
+
!process.env.ZEROCUT_WORKSPACE_DIR) {
|
|
431
|
+
throw new Error('ZEROCUT_WORKSPACE_DIR environment variable is required');
|
|
432
|
+
}
|
|
433
|
+
projectLocalDir = process.env.ZEROCUT_PROJECT_CWD
|
|
434
|
+
? (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD, '.')
|
|
435
|
+
: (0, node_path_1.resolve)(process.env.ZEROCUT_WORKSPACE_DIR, projectName);
|
|
430
436
|
const syncDir = (0, node_path_1.resolve)(projectLocalDir, 'materials');
|
|
437
|
+
// 保证项目目录存在
|
|
438
|
+
await (0, promises_1.mkdir)(projectLocalDir, { recursive: true });
|
|
431
439
|
try {
|
|
432
440
|
await (0, promises_1.mkdir)(syncDir, { recursive: true });
|
|
433
441
|
}
|
|
@@ -439,39 +447,37 @@ server.registerTool('project-open', {
|
|
|
439
447
|
// 文件过滤逻辑
|
|
440
448
|
let filesToUpload = [];
|
|
441
449
|
let skippedFiles = [];
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
skippedFiles = filterResult.skippedFiles;
|
|
460
|
-
}
|
|
450
|
+
try {
|
|
451
|
+
materials = await listFiles(syncDir);
|
|
452
|
+
}
|
|
453
|
+
catch (listError) {
|
|
454
|
+
console.warn('Failed to list materials:', listError);
|
|
455
|
+
materials = [];
|
|
456
|
+
}
|
|
457
|
+
if (syncAllFiles) {
|
|
458
|
+
// 如果 syncAllFiles 为 true,跳过智能过滤,上传所有文件
|
|
459
|
+
filesToUpload = materials;
|
|
460
|
+
skippedFiles = [];
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
// 智能文件过滤逻辑
|
|
464
|
+
const filterResult = await filterMaterialsForUpload(materials, projectLocalDir);
|
|
465
|
+
filesToUpload = filterResult.filesToUpload;
|
|
466
|
+
skippedFiles = filterResult.skippedFiles;
|
|
461
467
|
}
|
|
462
468
|
const files = session.files;
|
|
463
469
|
let progress = 0;
|
|
464
|
-
const
|
|
470
|
+
const syncErrors = [];
|
|
465
471
|
const totalFiles = filesToUpload.length + tosFiles.length;
|
|
466
472
|
for (const material of filesToUpload) {
|
|
467
473
|
try {
|
|
468
474
|
await files.upload(material, `${workDir}/materials/${(0, node_path_1.basename)(material)}`);
|
|
469
475
|
await sendProgress(context, ++progress, totalFiles, material);
|
|
470
476
|
}
|
|
471
|
-
catch (
|
|
472
|
-
const errorMsg = `Failed to
|
|
477
|
+
catch (syncError) {
|
|
478
|
+
const errorMsg = `Failed to sync ${material}: ${syncError}`;
|
|
473
479
|
console.error(errorMsg);
|
|
474
|
-
|
|
480
|
+
syncErrors.push(errorMsg);
|
|
475
481
|
}
|
|
476
482
|
}
|
|
477
483
|
for (const tosFile of tosFiles) {
|
|
@@ -480,10 +486,10 @@ server.registerTool('project-open', {
|
|
|
480
486
|
await session.terminal.run(`wget -O ${workDir}/materials/${(0, node_path_1.basename)(url.pathname)} ${tosFile}`);
|
|
481
487
|
await sendProgress(context, ++progress, totalFiles, tosFile);
|
|
482
488
|
}
|
|
483
|
-
catch (
|
|
484
|
-
const errorMsg = `Failed to
|
|
489
|
+
catch (syncError) {
|
|
490
|
+
const errorMsg = `Failed to sync ${tosFile}: ${syncError}`;
|
|
485
491
|
console.error(errorMsg);
|
|
486
|
-
|
|
492
|
+
syncErrors.push(errorMsg);
|
|
487
493
|
}
|
|
488
494
|
}
|
|
489
495
|
const result = {
|
|
@@ -491,11 +497,12 @@ server.registerTool('project-open', {
|
|
|
491
497
|
nextActionSuggest: '检查规则上下文是否已召回,若未召回,调用 retrieve_rules 工具召回规则上下文',
|
|
492
498
|
sessionId: session.id,
|
|
493
499
|
workDir,
|
|
500
|
+
projectName,
|
|
494
501
|
projectLocalDir,
|
|
495
502
|
materials,
|
|
496
|
-
|
|
503
|
+
syncedFiles: filesToUpload.map(file => (0, node_path_1.basename)(file)),
|
|
497
504
|
skippedFiles: skippedFiles.map(file => (0, node_path_1.basename)(file)),
|
|
498
|
-
|
|
505
|
+
syncErrors: syncErrors.length > 0 ? syncErrors : undefined,
|
|
499
506
|
};
|
|
500
507
|
return {
|
|
501
508
|
content: [
|
|
@@ -521,7 +528,7 @@ server.registerTool('project-close', {
|
|
|
521
528
|
.min(0)
|
|
522
529
|
.max(20)
|
|
523
530
|
.default(5)
|
|
524
|
-
.describe('Close the session after the specified number of minutes. Default is 5 minutes.
|
|
531
|
+
.describe('Close the session after the specified number of minutes. Default is 5 minutes. 当用户主动要求关闭会话时,将该参数设置为0,否则应默认设为5'),
|
|
525
532
|
},
|
|
526
533
|
}, async ({ inMinutes }) => {
|
|
527
534
|
try {
|
|
@@ -578,9 +585,9 @@ server.registerTool('retrieve-rules-context', {
|
|
|
578
585
|
}
|
|
579
586
|
else {
|
|
580
587
|
// 当 projectRulesFile 不存在时,设置 checkStoryboardFlag 为 false
|
|
581
|
-
checkStoryboardFlag = false;
|
|
588
|
+
// checkStoryboardFlag = false;
|
|
582
589
|
// 当 projectRulesFile 不存在时,设置 checkAudioVideoDurationFlag 为 false
|
|
583
|
-
checkAudioVideoDurationFlag = false;
|
|
590
|
+
// checkAudioVideoDurationFlag = false;
|
|
584
591
|
}
|
|
585
592
|
try {
|
|
586
593
|
const ai = currentSession.ai;
|
|
@@ -636,8 +643,8 @@ server.registerTool('upload-custom-material', {
|
|
|
636
643
|
try {
|
|
637
644
|
// 验证session状态
|
|
638
645
|
const currentSession = await validateSession('upload-custom-material');
|
|
639
|
-
// 构建本地文件路径,使用
|
|
640
|
-
const validatedPath = (0, node_path_1.resolve)(
|
|
646
|
+
// 构建本地文件路径,使用 projectLocalDir
|
|
647
|
+
const validatedPath = (0, node_path_1.resolve)(projectLocalDir, 'materials', localFileName.trim());
|
|
641
648
|
// 验证本地文件存在性
|
|
642
649
|
if (!(0, node_fs_1.existsSync)(validatedPath)) {
|
|
643
650
|
throw new Error(`File not found: ${validatedPath}`);
|
|
@@ -711,7 +718,7 @@ server.registerTool('upload-custom-material', {
|
|
|
711
718
|
}
|
|
712
719
|
});
|
|
713
720
|
server.registerTool('wait-for-task-finish', {
|
|
714
|
-
title: 'Wait Workflow or VideoTask Done;只有正在运行Coze工作流或者有异步生成视频任务时才需要执行这个工具;⚠️
|
|
721
|
+
title: 'Wait Workflow or VideoTask Done;只有正在运行Coze工作流或者有异步生成视频任务时才需要执行这个工具;⚠️ 如果执行这个工具未失败只是超时,你应立即再次重新调用,以继续等待直到任务完成或失败;‼️ 有的任务执行时间需要很长,所以如果只是超时不是失败,重试多少次都是正常的,请耐心等待即可。',
|
|
715
722
|
description: 'Wait for a workflow to complete.',
|
|
716
723
|
inputSchema: {
|
|
717
724
|
taskUrl: zod_1.z
|
|
@@ -767,6 +774,32 @@ server.registerTool('wait-for-task-finish', {
|
|
|
767
774
|
catch (error) {
|
|
768
775
|
console.warn(`Failed to update media_logs.json for ${validatedFileName}:`, error);
|
|
769
776
|
}
|
|
777
|
+
if (res.data?.scenes) {
|
|
778
|
+
const { scenes, video_type, voice_type, voiceover_tone, bgm_prompt, video_model, aspect_ratio, } = res.data;
|
|
779
|
+
const seed = (0, seed_1.getRandomSeed)();
|
|
780
|
+
const orientation = aspect_ratio === '16:9' ? 'landscape' : 'portrait';
|
|
781
|
+
const storyboard = {
|
|
782
|
+
orientation,
|
|
783
|
+
video_type,
|
|
784
|
+
outline_sheet: 'outline_sheet.png',
|
|
785
|
+
bgm_prompt,
|
|
786
|
+
voice_type,
|
|
787
|
+
scenes: scenes.map((scene) => {
|
|
788
|
+
let video_prompt = scene.video_prompt;
|
|
789
|
+
if (voiceover_tone && video_prompt.includes('画外音')) {
|
|
790
|
+
video_prompt = video_prompt.replace(/画外音[::]/g, `画外音(${voiceover_tone}):`);
|
|
791
|
+
}
|
|
792
|
+
return {
|
|
793
|
+
...scene,
|
|
794
|
+
video_prompt,
|
|
795
|
+
use_video_model: video_model,
|
|
796
|
+
seed,
|
|
797
|
+
};
|
|
798
|
+
}),
|
|
799
|
+
};
|
|
800
|
+
const saveLocalPath = (0, node_path_1.resolve)(projectLocalDir, 'storyboard.json');
|
|
801
|
+
await (0, promises_1.writeFile)(saveLocalPath, JSON.stringify(storyboard, null, 2));
|
|
802
|
+
}
|
|
770
803
|
return {
|
|
771
804
|
content: [
|
|
772
805
|
{
|
|
@@ -932,7 +965,7 @@ ${roleDescriptionPrompt}
|
|
|
932
965
|
? referenceImage
|
|
933
966
|
: `./materials/${referenceImage}`;
|
|
934
967
|
// 需要得到当前项目的绝对路径
|
|
935
|
-
const imageFilePath = (0, node_path_1.resolve)(
|
|
968
|
+
const imageFilePath = (0, node_path_1.resolve)(projectLocalDir, imagePath);
|
|
936
969
|
// 读取图片文件内容
|
|
937
970
|
const imageBuffer = await (0, promises_1.readFile)(imageFilePath);
|
|
938
971
|
const fileName = (0, node_path_1.basename)(imagePath);
|
|
@@ -1026,20 +1059,6 @@ server.registerTool('generate-image', {
|
|
|
1026
1059
|
.min(1)
|
|
1027
1060
|
.optional()
|
|
1028
1061
|
.describe('分镜索引,从1开始的下标,如果非分镜对应素材,则可不传,分镜素材必传'),
|
|
1029
|
-
storyBoardFile: zod_1.z
|
|
1030
|
-
.string()
|
|
1031
|
-
.optional()
|
|
1032
|
-
.default('storyboard.json')
|
|
1033
|
-
.describe('故事板文件路径'),
|
|
1034
|
-
skipConsistencyCheck: zod_1.z
|
|
1035
|
-
.boolean()
|
|
1036
|
-
.optional()
|
|
1037
|
-
.default(false)
|
|
1038
|
-
.describe('是否跳过一致性检查,默认为false(即默认进行一致性检查)'),
|
|
1039
|
-
skipCheckWithSceneReason: zod_1.z
|
|
1040
|
-
.string()
|
|
1041
|
-
.optional()
|
|
1042
|
-
.describe('跳过校验的理由,如果skipConsistencyCheck设为true,必须要传这个参数'),
|
|
1043
1062
|
size: zod_1.z
|
|
1044
1063
|
.enum([
|
|
1045
1064
|
'1024x1024',
|
|
@@ -1124,15 +1143,16 @@ server.registerTool('generate-image', {
|
|
|
1124
1143
|
\`\`\`
|
|
1125
1144
|
`),
|
|
1126
1145
|
},
|
|
1127
|
-
}, async ({ type = 'seedream', prompt, sceneIndex,
|
|
1146
|
+
}, async ({ type = 'seedream', prompt, sceneIndex, size = '720x1280', imageCount = 1, saveToFileNames, watermark, referenceImages, }, context) => {
|
|
1128
1147
|
try {
|
|
1148
|
+
const storyBoardFile = 'storyboard.json';
|
|
1129
1149
|
// 验证session状态
|
|
1130
1150
|
const currentSession = await validateSession('generate-image');
|
|
1131
|
-
const storyBoardPath = (0, node_path_1.resolve)(
|
|
1132
|
-
const outlineSheetImagePath = (0, node_path_1.resolve)(
|
|
1151
|
+
const storyBoardPath = (0, node_path_1.resolve)(projectLocalDir, storyBoardFile);
|
|
1152
|
+
const outlineSheetImagePath = (0, node_path_1.resolve)(projectLocalDir, 'materials', 'outline_sheet.png');
|
|
1133
1153
|
const hasOutlineSheet = (0, node_fs_1.existsSync)(outlineSheetImagePath);
|
|
1134
1154
|
// 校验 prompt 与 storyboard.json 中分镜设定的一致性
|
|
1135
|
-
if (sceneIndex
|
|
1155
|
+
if (sceneIndex) {
|
|
1136
1156
|
try {
|
|
1137
1157
|
if ((0, node_fs_1.existsSync)(storyBoardPath)) {
|
|
1138
1158
|
const storyBoardContent = await (0, promises_1.readFile)(storyBoardPath, 'utf8');
|
|
@@ -1151,7 +1171,7 @@ server.registerTool('generate-image', {
|
|
|
1151
1171
|
const endFrame = scene.end_frame;
|
|
1152
1172
|
// 检查 prompt 是否严格等于 start_frame 或 end_frame
|
|
1153
1173
|
if (prompt !== startFrame && prompt !== endFrame) {
|
|
1154
|
-
return createErrorResponse('图片提示词必须严格遵照storyboard
|
|
1174
|
+
return createErrorResponse('图片提示词必须严格遵照storyboard的设定', 'generate-image');
|
|
1155
1175
|
}
|
|
1156
1176
|
if (hasOutlineSheet &&
|
|
1157
1177
|
(!referenceImages ||
|
|
@@ -1235,7 +1255,7 @@ server.registerTool('generate-image', {
|
|
|
1235
1255
|
? refImage.image
|
|
1236
1256
|
: `./materials/${refImage.image}`;
|
|
1237
1257
|
// 需要得到当前项目的绝对路径
|
|
1238
|
-
const imageFilePath = (0, node_path_1.resolve)(
|
|
1258
|
+
const imageFilePath = (0, node_path_1.resolve)(projectLocalDir, imagePath);
|
|
1239
1259
|
try {
|
|
1240
1260
|
// 直接读取本地文件
|
|
1241
1261
|
if (!(0, node_fs_1.existsSync)(imageFilePath)) {
|
|
@@ -1296,7 +1316,7 @@ ${processedPrompt}`.trim();
|
|
|
1296
1316
|
}
|
|
1297
1317
|
}
|
|
1298
1318
|
const ai = currentSession.ai;
|
|
1299
|
-
const
|
|
1319
|
+
const taskRes = await ai.generateImage({
|
|
1300
1320
|
type,
|
|
1301
1321
|
prompt: processedPrompt,
|
|
1302
1322
|
size,
|
|
@@ -1304,6 +1324,10 @@ ${processedPrompt}`.trim();
|
|
|
1304
1324
|
image: imageBase64Array,
|
|
1305
1325
|
async: true,
|
|
1306
1326
|
});
|
|
1327
|
+
const taskUrl = taskRes.taskUrl;
|
|
1328
|
+
if (!taskUrl) {
|
|
1329
|
+
return createErrorResponse(`Failed to generate image: ${taskRes.error || JSON.stringify(taskRes)}`, 'generate-image');
|
|
1330
|
+
}
|
|
1307
1331
|
let progress = 0;
|
|
1308
1332
|
const res = await ai.waitForTaskComplete({
|
|
1309
1333
|
taskUrl,
|
|
@@ -1444,7 +1468,7 @@ server.registerTool('edit-image', {
|
|
|
1444
1468
|
? sourceImageFileName
|
|
1445
1469
|
: `./materials/${sourceImageFileName}`;
|
|
1446
1470
|
// 需要得到当前项目的绝对路径
|
|
1447
|
-
const imageFilePath = (0, node_path_1.resolve)(
|
|
1471
|
+
const imageFilePath = (0, node_path_1.resolve)(projectLocalDir, imagePath);
|
|
1448
1472
|
if (!(0, node_fs_1.existsSync)(imageFilePath)) {
|
|
1449
1473
|
return createErrorResponse(`Reference image not found: ${imageFilePath}`, 'edit-image');
|
|
1450
1474
|
}
|
|
@@ -1512,9 +1536,9 @@ server.registerTool('edit-image', {
|
|
|
1512
1536
|
return createErrorResponse(error, 'edit-image');
|
|
1513
1537
|
}
|
|
1514
1538
|
});
|
|
1515
|
-
server.registerTool('generate-
|
|
1516
|
-
title: 'Generate
|
|
1517
|
-
description: `根据用户描述生成短视频的大纲;执行本工具会自动创建两个文件,一个是storyboard.json,一个是outline_sheet.png
|
|
1539
|
+
server.registerTool('generate-video-outlines', {
|
|
1540
|
+
title: 'Generate Video Outlines',
|
|
1541
|
+
description: `根据用户描述生成短视频的大纲;执行本工具会自动创建两个文件,一个是storyboard.json,一个是outline_sheet.png,前者是分镜设置,后者是分镜宫格图,其中outline_sheet.png生成于materials目录下,storyboard.json生成于materials的上级目录。`,
|
|
1518
1542
|
inputSchema: {
|
|
1519
1543
|
prompt: zod_1.z
|
|
1520
1544
|
.string()
|
|
@@ -1550,10 +1574,11 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1550
1574
|
.default('pro')
|
|
1551
1575
|
.describe('除非用户主动提出使用其他模型,否则一律用pro模型'),
|
|
1552
1576
|
},
|
|
1553
|
-
}, async ({ prompt, voiceType, language, images, orientation, model }) => {
|
|
1577
|
+
}, async ({ prompt, voiceType, language, images, orientation, model }, context) => {
|
|
1554
1578
|
try {
|
|
1555
1579
|
// 验证session状态
|
|
1556
|
-
const currentSession = await validateSession('generate-
|
|
1580
|
+
const currentSession = await validateSession('generate-video-outlines');
|
|
1581
|
+
let progress = 0;
|
|
1557
1582
|
const ai = currentSession.ai;
|
|
1558
1583
|
const res = await ai.generateShortVideoOutlines({
|
|
1559
1584
|
prompt,
|
|
@@ -1562,6 +1587,15 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1562
1587
|
images,
|
|
1563
1588
|
videoModel: model,
|
|
1564
1589
|
aspectRatio: orientation === 'portrait' ? '9:16' : '16:9',
|
|
1590
|
+
onProgress: async (metaData) => {
|
|
1591
|
+
try {
|
|
1592
|
+
await sendProgress(context, ++progress, undefined, JSON.stringify(metaData));
|
|
1593
|
+
}
|
|
1594
|
+
catch (progressError) {
|
|
1595
|
+
console.warn('Failed to send progress update:', progressError);
|
|
1596
|
+
}
|
|
1597
|
+
},
|
|
1598
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
1565
1599
|
});
|
|
1566
1600
|
if (!res) {
|
|
1567
1601
|
throw new Error('Failed to generate short video outlines: no response from AI service');
|
|
@@ -1573,7 +1607,7 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1573
1607
|
type: 'text',
|
|
1574
1608
|
text: JSON.stringify({
|
|
1575
1609
|
success: true,
|
|
1576
|
-
message: '该任务正在运行中,它是异步任务,且执行时间较长,你应立即调用工具 wait-for-task-finish 来等待任务结束,如 wait-for-task-finish 工具调用超时,你应立即再次重新调用直到任务结束。',
|
|
1610
|
+
message: '该任务正在运行中,它是异步任务,且执行时间较长,你应立即调用工具 wait-for-task-finish (saveFileName=outline_sheet.png) 来等待任务结束,如 wait-for-task-finish 工具调用超时,你应立即再次重新调用直到任务结束。',
|
|
1577
1611
|
taskUrl: res.taskUrl,
|
|
1578
1612
|
}),
|
|
1579
1613
|
},
|
|
@@ -1583,7 +1617,7 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1583
1617
|
else if (res.url) {
|
|
1584
1618
|
const url = res.url;
|
|
1585
1619
|
await saveMaterial(currentSession, url, 'outline_sheet.png');
|
|
1586
|
-
const { scenes, video_type, voice_type, voiceover_tone, bgm_prompt } = res.data || {};
|
|
1620
|
+
const { scenes, video_type, voice_type, voiceover_tone, bgm_prompt, video_model, } = res.data || {};
|
|
1587
1621
|
const seed = (0, seed_1.getRandomSeed)();
|
|
1588
1622
|
const storyboard = {
|
|
1589
1623
|
orientation,
|
|
@@ -1593,19 +1627,13 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1593
1627
|
voice_type,
|
|
1594
1628
|
scenes: scenes.map((scene) => {
|
|
1595
1629
|
let video_prompt = scene.video_prompt;
|
|
1596
|
-
if (video_prompt.includes('画外音')
|
|
1597
|
-
video_prompt.
|
|
1598
|
-
if (voiceover_tone) {
|
|
1599
|
-
video_prompt = video_prompt.replace(/画外音[::]\s*“([^”]*)”/g, `画外音(${voiceover_tone})镜头内所有角色都不言语,从远处传来广播声<广播开始>$1——</广播结束>`);
|
|
1600
|
-
}
|
|
1601
|
-
else {
|
|
1602
|
-
video_prompt = video_prompt.replace(/画外音[::]\s*“([^”]*)”/g, `镜头内所有角色都不言语,从远处传来广播声<广播开始>$1——</广播结束>`);
|
|
1603
|
-
}
|
|
1630
|
+
if (voiceover_tone && video_prompt.includes('画外音')) {
|
|
1631
|
+
video_prompt = video_prompt.replace(/画外音[::]/g, `画外音(${voiceover_tone}):`);
|
|
1604
1632
|
}
|
|
1605
1633
|
return {
|
|
1606
1634
|
...scene,
|
|
1607
1635
|
video_prompt,
|
|
1608
|
-
use_video_model:
|
|
1636
|
+
use_video_model: video_model,
|
|
1609
1637
|
seed,
|
|
1610
1638
|
};
|
|
1611
1639
|
}),
|
|
@@ -1640,7 +1668,7 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1640
1668
|
};
|
|
1641
1669
|
}
|
|
1642
1670
|
catch (error) {
|
|
1643
|
-
return createErrorResponse(error, 'generate-
|
|
1671
|
+
return createErrorResponse(error, 'generate-video-outlines');
|
|
1644
1672
|
}
|
|
1645
1673
|
});
|
|
1646
1674
|
server.registerTool('generate-music-or-mv', {
|
|
@@ -1959,26 +1987,12 @@ server.registerTool('generate-video', {
|
|
|
1959
1987
|
inputSchema: {
|
|
1960
1988
|
prompt: zod_1.z
|
|
1961
1989
|
.string()
|
|
1962
|
-
.describe('The prompt to generate.
|
|
1990
|
+
.describe('The prompt to generate. 尽量忠于用户的原始需求,除非用户明确要求协助优化,否则不要擅自发挥!'),
|
|
1963
1991
|
sceneIndex: zod_1.z
|
|
1964
1992
|
.number()
|
|
1965
1993
|
.min(1)
|
|
1966
1994
|
.optional()
|
|
1967
1995
|
.describe('分镜索引,从1开始的下标,如果非分镜对应素材,则可不传,分镜素材必传'),
|
|
1968
|
-
storyBoardFile: zod_1.z
|
|
1969
|
-
.string()
|
|
1970
|
-
.optional()
|
|
1971
|
-
.default('storyboard.json')
|
|
1972
|
-
.describe('故事板文件路径'),
|
|
1973
|
-
skipConsistencyCheck: zod_1.z
|
|
1974
|
-
.boolean()
|
|
1975
|
-
.optional()
|
|
1976
|
-
.default(false)
|
|
1977
|
-
.describe('是否跳过一致性检查,默认为false(即默认进行一致性检查)'),
|
|
1978
|
-
skipCheckWithSceneReason: zod_1.z
|
|
1979
|
-
.string()
|
|
1980
|
-
.optional()
|
|
1981
|
-
.describe('跳过校验的理由,如果skipConsistencyCheck设为true,必须要传这个参数'),
|
|
1982
1996
|
type: zod_1.z
|
|
1983
1997
|
.enum([
|
|
1984
1998
|
'pro',
|
|
@@ -2039,7 +2053,7 @@ server.registerTool('generate-video', {
|
|
|
2039
2053
|
.default(false)
|
|
2040
2054
|
.describe('Whether to optimize the prompt.'),
|
|
2041
2055
|
},
|
|
2042
|
-
}, async ({ prompt, sceneIndex,
|
|
2056
|
+
}, async ({ prompt, sceneIndex, saveToFileName, start_frame, end_frame, duration, resolution, type = 'vidu', optimizePrompt, saveLastFrameAs, mute = true, seed, }, context) => {
|
|
2043
2057
|
try {
|
|
2044
2058
|
// 验证session状态
|
|
2045
2059
|
const currentSession = await validateSession('generate-video');
|
|
@@ -2060,10 +2074,11 @@ server.registerTool('generate-video', {
|
|
|
2060
2074
|
console.warn(`zero 模型的视频仅支持 1080p 分辨率,用户指定的分辨率为 %s,已自动将 ${resolution} 转换为 1080p`, resolution);
|
|
2061
2075
|
resolution = '1080p';
|
|
2062
2076
|
}
|
|
2077
|
+
const storyBoardFile = 'storyboard.json';
|
|
2063
2078
|
// 校验 prompt 与 storyboard.json 中分镜设定的一致性
|
|
2064
|
-
if (sceneIndex
|
|
2079
|
+
if (sceneIndex) {
|
|
2065
2080
|
try {
|
|
2066
|
-
const storyBoardPath = (0, node_path_1.resolve)(
|
|
2081
|
+
const storyBoardPath = (0, node_path_1.resolve)(projectLocalDir, storyBoardFile);
|
|
2067
2082
|
if ((0, node_fs_1.existsSync)(storyBoardPath)) {
|
|
2068
2083
|
const storyBoardContent = await (0, promises_1.readFile)(storyBoardPath, 'utf8');
|
|
2069
2084
|
// 检查 storyBoard JSON 语法合法性
|
|
@@ -2079,26 +2094,26 @@ server.registerTool('generate-video', {
|
|
|
2079
2094
|
if (scene) {
|
|
2080
2095
|
const videoPrompt = scene.video_prompt;
|
|
2081
2096
|
if (videoPrompt && prompt !== videoPrompt) {
|
|
2082
|
-
return createErrorResponse('视频提示词必须严格遵照storyboard
|
|
2097
|
+
return createErrorResponse('视频提示词必须严格遵照storyboard的设定', 'generate-video');
|
|
2083
2098
|
}
|
|
2084
2099
|
if (scene.is_continuous && !end_frame) {
|
|
2085
|
-
return createErrorResponse('连续分镜必须指定end_frame
|
|
2100
|
+
return createErrorResponse('连续分镜必须指定end_frame参数', 'generate-video');
|
|
2086
2101
|
}
|
|
2087
2102
|
if (scene.video_duration != null &&
|
|
2088
2103
|
duration !== scene.video_duration) {
|
|
2089
|
-
return createErrorResponse(`视频时长必须严格遵照storyboard
|
|
2104
|
+
return createErrorResponse(`视频时长必须严格遵照storyboard的设定,storyboard 中设定的时长为 ${scene.video_duration} 秒。`, 'generate-video');
|
|
2090
2105
|
}
|
|
2091
2106
|
if (storyBoard.voice_type &&
|
|
2092
2107
|
storyBoard.voice_type !== 'slient') {
|
|
2093
2108
|
if (mute) {
|
|
2094
|
-
return createErrorResponse('有对话和旁白的分镜不能静音,请将mute设为false
|
|
2109
|
+
return createErrorResponse('有对话和旁白的分镜不能静音,请将mute设为false再重新使用工具。', 'generate-video');
|
|
2095
2110
|
}
|
|
2096
2111
|
}
|
|
2097
2112
|
// 检查 use_video_model 与 type 参数的一致性
|
|
2098
2113
|
if (scene.use_video_model &&
|
|
2099
2114
|
type &&
|
|
2100
2115
|
scene.use_video_model !== type) {
|
|
2101
|
-
return createErrorResponse(`分镜建议的视频模型(${scene.use_video_model})与传入的type参数(${type})不一致。请确保use_video_model与type
|
|
2116
|
+
return createErrorResponse(`分镜建议的视频模型(${scene.use_video_model})与传入的type参数(${type})不一致。请确保use_video_model与type参数值相同。`, 'generate-video');
|
|
2102
2117
|
}
|
|
2103
2118
|
}
|
|
2104
2119
|
else {
|
|
@@ -2232,7 +2247,7 @@ server.registerTool('generate-video', {
|
|
|
2232
2247
|
},
|
|
2233
2248
|
};
|
|
2234
2249
|
const analysisPayload = {
|
|
2235
|
-
model: 'Doubao-Seed-1.
|
|
2250
|
+
model: 'Doubao-Seed-1.8',
|
|
2236
2251
|
messages: [
|
|
2237
2252
|
{
|
|
2238
2253
|
role: 'system',
|
|
@@ -2367,7 +2382,7 @@ server.registerTool('generate-video', {
|
|
|
2367
2382
|
console.warn('Failed to send progress update:', progressError);
|
|
2368
2383
|
}
|
|
2369
2384
|
},
|
|
2370
|
-
waitForFinish: type !== 'zero',
|
|
2385
|
+
waitForFinish: type !== 'zero' && CLIENT_TYPE !== '5ire',
|
|
2371
2386
|
mute,
|
|
2372
2387
|
seed,
|
|
2373
2388
|
});
|
|
@@ -2450,38 +2465,47 @@ server.registerTool('edit-video', {
|
|
|
2450
2465
|
title: 'Edit Video',
|
|
2451
2466
|
description: `Edit video using Coze workflow,可以做以下事情:
|
|
2452
2467
|
|
|
2453
|
-
-
|
|
2468
|
+
- 编辑视频(增加、修改、删除视频中的内容),type 为 edit
|
|
2469
|
+
- 参考视频动作和特效,type 也为 edit
|
|
2454
2470
|
- 视频对口型,type 为 lipsync
|
|
2455
|
-
|
|
2471
|
+
|
|
2472
|
+
‼️ 故障排查
|
|
2473
|
+
如果出错,依次检查以下事项:
|
|
2474
|
+
|
|
2475
|
+
1. 视频是否太长(超过了8秒)或太大(超过了100M)
|
|
2476
|
+
2. 视频像素过小(小于128x128)或宽高比太悬殊(大于1:4)
|
|
2477
|
+
3. 视频格式是否是 mp4
|
|
2456
2478
|
`,
|
|
2457
2479
|
inputSchema: {
|
|
2458
2480
|
type: zod_1.z
|
|
2459
|
-
.enum(['
|
|
2481
|
+
.enum(['edit', 'lipsync'])
|
|
2482
|
+
.default('edit')
|
|
2460
2483
|
.describe('The editing type'),
|
|
2461
2484
|
video: zod_1.z.string().describe(`The video to edit
|
|
2462
2485
|
|
|
2463
|
-
- type 为
|
|
2486
|
+
- type 为 edit 时,video 为要编辑的视频
|
|
2464
2487
|
- type 为 lipsync 时,video 为要对口型的视频
|
|
2465
|
-
- type 为 imitate 时,video 为模仿动作参考视频
|
|
2466
2488
|
`),
|
|
2467
2489
|
prompt: zod_1.z.string().optional()
|
|
2468
|
-
.describe(`The editing prompt,
|
|
2490
|
+
.describe(`The editing prompt, 如实转述用户需求即可
|
|
2469
2491
|
|
|
2470
|
-
|
|
2492
|
+
要求:
|
|
2493
|
+
1)用**极简的话语**准确描述,不添加其他任何补充信息,本工具会自己优化
|
|
2494
|
+
2)video一律用“视频1”指代
|
|
2495
|
+
|
|
2496
|
+
- type 为 edit 时,prompt 为编辑指令
|
|
2471
2497
|
- type 为 lipsync 时,prompt 为空
|
|
2472
|
-
- type 为 imitate 时,prompt 为要模仿动作的内容,和 referenceImageUrl 二选一
|
|
2473
2498
|
`),
|
|
2474
|
-
|
|
2475
|
-
.describe(`The reference image
|
|
2476
|
-
- type 为
|
|
2477
|
-
- type 为 lipsync 时,
|
|
2478
|
-
- type 为 imitate 时,referenceImage 为要模仿动作的画面,和 prompt 二选一
|
|
2499
|
+
referenceImages: zod_1.z.array(zod_1.z.string()).optional()
|
|
2500
|
+
.describe(`The reference image Files for editing
|
|
2501
|
+
- type 为 edit 时,referenceImages 为参考图片(1-7张)
|
|
2502
|
+
- type 为 lipsync 时,referenceImages 为空
|
|
2479
2503
|
`),
|
|
2480
2504
|
saveToFileName: zod_1.z
|
|
2481
2505
|
.string()
|
|
2482
2506
|
.describe(`The file name to save the edited video to. 应该是mp4文件`),
|
|
2483
2507
|
},
|
|
2484
|
-
}, async ({ type, video, prompt,
|
|
2508
|
+
}, async ({ type, video, prompt, referenceImages, saveToFileName }, context) => {
|
|
2485
2509
|
try {
|
|
2486
2510
|
const currentSession = await validateSession('edit-video');
|
|
2487
2511
|
const ai = currentSession.ai;
|
|
@@ -2498,19 +2522,33 @@ server.registerTool('edit-video', {
|
|
|
2498
2522
|
}
|
|
2499
2523
|
};
|
|
2500
2524
|
let referenceImageUrl = undefined;
|
|
2501
|
-
|
|
2502
|
-
|
|
2525
|
+
let referenceImageUrls = undefined;
|
|
2526
|
+
if (referenceImages) {
|
|
2527
|
+
referenceImageUrls = referenceImages.map(fileName => getMaterialUri(currentSession, fileName));
|
|
2528
|
+
referenceImageUrl = referenceImageUrls[0];
|
|
2503
2529
|
}
|
|
2504
2530
|
const videoUrl = getMaterialUri(currentSession, video);
|
|
2505
|
-
if (type === '
|
|
2531
|
+
if (type === 'edit') {
|
|
2506
2532
|
if (!prompt) {
|
|
2507
|
-
throw new Error('prompt is required for
|
|
2533
|
+
throw new Error('prompt is required for edit type');
|
|
2508
2534
|
}
|
|
2509
|
-
res = await ai.editVideo({
|
|
2510
|
-
|
|
2535
|
+
// res = await ai.editVideo({
|
|
2536
|
+
// videoUrl,
|
|
2537
|
+
// prompt,
|
|
2538
|
+
// referenceImageUrl,
|
|
2539
|
+
// onProgress,
|
|
2540
|
+
// waitForFinish: CLIENT_TYPE !== '5ire',
|
|
2541
|
+
// });
|
|
2542
|
+
res = await ai.referencesToVideo({
|
|
2511
2543
|
prompt,
|
|
2512
|
-
|
|
2544
|
+
duration: 0,
|
|
2545
|
+
type: 'vidu-pro', //'vidu', //'pixv', // 'lite', // 'sora2',
|
|
2546
|
+
reference_images: referenceImageUrls,
|
|
2547
|
+
videos: [videoUrl],
|
|
2513
2548
|
onProgress,
|
|
2549
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
2550
|
+
// aspect_ratio: '9:16',
|
|
2551
|
+
// mute: true,
|
|
2514
2552
|
});
|
|
2515
2553
|
}
|
|
2516
2554
|
else if (type === 'lipsync') {
|
|
@@ -2525,23 +2563,7 @@ server.registerTool('edit-video', {
|
|
|
2525
2563
|
audioInMs: 0,
|
|
2526
2564
|
pad_audio: false,
|
|
2527
2565
|
onProgress,
|
|
2528
|
-
|
|
2529
|
-
}
|
|
2530
|
-
else if (type === 'imitate') {
|
|
2531
|
-
if (!prompt && !referenceImageUrl) {
|
|
2532
|
-
throw new Error('prompt or referenceImageUrl is required for imitate type');
|
|
2533
|
-
}
|
|
2534
|
-
if (prompt) {
|
|
2535
|
-
referenceImageUrl = (await ai.generateImage({
|
|
2536
|
-
prompt,
|
|
2537
|
-
type: 'banana',
|
|
2538
|
-
image: referenceImageUrl ? [referenceImageUrl] : undefined,
|
|
2539
|
-
})).url;
|
|
2540
|
-
}
|
|
2541
|
-
res = await ai.actionImitation({
|
|
2542
|
-
videoUrl,
|
|
2543
|
-
imageUrl: referenceImageUrl,
|
|
2544
|
-
onProgress,
|
|
2566
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
2545
2567
|
});
|
|
2546
2568
|
}
|
|
2547
2569
|
if (res.url) {
|
|
@@ -2750,11 +2772,15 @@ server.registerTool('audio-video-sync', {
|
|
|
2750
2772
|
.describe('The volume of video audio. 0.0 to 2.0.'),
|
|
2751
2773
|
loopAudio: zod_1.z.boolean().optional().default(true),
|
|
2752
2774
|
addSubtitles: zod_1.z.boolean().optional().default(false),
|
|
2775
|
+
subtitlesContext: zod_1.z
|
|
2776
|
+
.string()
|
|
2777
|
+
.optional()
|
|
2778
|
+
.describe('字幕的参考上下文(非必需),用于提升字幕准确性'),
|
|
2753
2779
|
saveToFileName: zod_1.z
|
|
2754
2780
|
.string()
|
|
2755
2781
|
.describe('The filename to save the audio-video-synced video. 应该是mp4文件'),
|
|
2756
2782
|
},
|
|
2757
|
-
}, async ({ videos, audio, audioInMs, audioFadeOutMs, audioVolume, videoAudioVolume, saveToFileName, loopAudio, addSubtitles, }, context) => {
|
|
2783
|
+
}, async ({ videos, audio, audioInMs, audioFadeOutMs, audioVolume, videoAudioVolume, saveToFileName, loopAudio, addSubtitles, subtitlesContext, }, context) => {
|
|
2758
2784
|
try {
|
|
2759
2785
|
// 验证session状态
|
|
2760
2786
|
const currentSession = await validateSession('audio-video-sync');
|
|
@@ -2788,6 +2814,7 @@ server.registerTool('audio-video-sync', {
|
|
|
2788
2814
|
videoAudioVolume,
|
|
2789
2815
|
loopAudio,
|
|
2790
2816
|
subtitles: addSubtitles,
|
|
2817
|
+
subtitlesContext,
|
|
2791
2818
|
});
|
|
2792
2819
|
if (result.url) {
|
|
2793
2820
|
console.log('Audio sync completed successfully');
|
|
@@ -2835,12 +2862,7 @@ server.registerTool('generate-video-by-ref', {
|
|
|
2835
2862
|
inputSchema: {
|
|
2836
2863
|
prompt: zod_1.z
|
|
2837
2864
|
.string()
|
|
2838
|
-
.describe('The prompt to generate video with or without reference images.
|
|
2839
|
-
rewritePrompt: zod_1.z
|
|
2840
|
-
.boolean()
|
|
2841
|
-
.optional()
|
|
2842
|
-
.default(true)
|
|
2843
|
-
.describe('Whether to rewrite the prompt.'),
|
|
2865
|
+
.describe('The prompt to generate video with or without reference images. 尽量忠于用户的原始需求,除非用户明确要求协助优化,否则不要擅自发挥!'),
|
|
2844
2866
|
referenceImages: zod_1.z
|
|
2845
2867
|
.array(zod_1.z.object({
|
|
2846
2868
|
name: zod_1.z
|
|
@@ -2854,18 +2876,30 @@ server.registerTool('generate-video-by-ref', {
|
|
|
2854
2876
|
.optional()
|
|
2855
2877
|
.default([])
|
|
2856
2878
|
.describe('Array of reference image objects with name, url and type. Can be empty for text-only generation.'),
|
|
2879
|
+
referenceVideo: zod_1.z
|
|
2880
|
+
.object({
|
|
2881
|
+
name: zod_1.z
|
|
2882
|
+
.string()
|
|
2883
|
+
.describe('Reference video file name in materials directory'),
|
|
2884
|
+
fileName: zod_1.z.string().describe('Reference video file name'),
|
|
2885
|
+
type: zod_1.z.enum(['video']).describe('Type of reference: video'),
|
|
2886
|
+
})
|
|
2887
|
+
.optional()
|
|
2888
|
+
.describe('Reference video file name in materials directory, 用于参考视频动作和特效,只有vidu-pro模型支持'),
|
|
2857
2889
|
duration: zod_1.z
|
|
2858
2890
|
.number()
|
|
2859
2891
|
.min(0)
|
|
2860
2892
|
.max(16)
|
|
2861
2893
|
.optional()
|
|
2862
2894
|
.default(5)
|
|
2863
|
-
.describe('The duration of the video in seconds
|
|
2895
|
+
.describe('The duration of the video in seconds.可以传0,此时会根据视频提示词内容自动确定时长'),
|
|
2864
2896
|
aspectRatio: zod_1.z
|
|
2865
2897
|
.enum(['16:9', '9:16'])
|
|
2898
|
+
.default('16:9')
|
|
2866
2899
|
.describe('The aspect ratio of the video.'),
|
|
2867
2900
|
resolution: zod_1.z
|
|
2868
2901
|
.enum(['720p', '1080p'])
|
|
2902
|
+
.default('720p')
|
|
2869
2903
|
.describe('The resolution of the video.'),
|
|
2870
2904
|
type: zod_1.z
|
|
2871
2905
|
.enum([
|
|
@@ -2875,6 +2909,7 @@ server.registerTool('generate-video-by-ref', {
|
|
|
2875
2909
|
'veo3.1',
|
|
2876
2910
|
'veo3.1-pro',
|
|
2877
2911
|
'vidu',
|
|
2912
|
+
'vidu-pro',
|
|
2878
2913
|
'vidu-uc',
|
|
2879
2914
|
'pixv',
|
|
2880
2915
|
])
|
|
@@ -2897,42 +2932,29 @@ server.registerTool('generate-video-by-ref', {
|
|
|
2897
2932
|
.min(1)
|
|
2898
2933
|
.optional()
|
|
2899
2934
|
.describe('分镜索引,从1开始的下标,如果非分镜对应素材,则可不传,分镜素材必传'),
|
|
2900
|
-
storyBoardFile: zod_1.z
|
|
2901
|
-
.string()
|
|
2902
|
-
.optional()
|
|
2903
|
-
.default('storyboard.json')
|
|
2904
|
-
.describe('故事板文件路径'),
|
|
2905
|
-
skipConsistencyCheck: zod_1.z
|
|
2906
|
-
.boolean()
|
|
2907
|
-
.optional()
|
|
2908
|
-
.default(false)
|
|
2909
|
-
.describe('是否跳过一致性检查,默认为false(即默认进行一致性检查)'),
|
|
2910
|
-
skipCheckWithSceneReason: zod_1.z
|
|
2911
|
-
.string()
|
|
2912
|
-
.optional()
|
|
2913
|
-
.describe('跳过校验的理由,如果skipConsistencyCheck设为true,必须要传这个参数'),
|
|
2914
2935
|
optimizePrompt: zod_1.z
|
|
2915
2936
|
.boolean()
|
|
2916
2937
|
.optional()
|
|
2917
2938
|
.default(false)
|
|
2918
2939
|
.describe('Whether to optimize the prompt.'),
|
|
2919
2940
|
},
|
|
2920
|
-
}, async ({ prompt,
|
|
2941
|
+
}, async ({ prompt, referenceImages, duration, aspectRatio, resolution, type = 'vidu', mute, saveToFileName, sceneIndex, optimizePrompt, seed, referenceVideo, }, context) => {
|
|
2921
2942
|
try {
|
|
2943
|
+
const storyBoardFile = 'storyboard.json';
|
|
2922
2944
|
// 验证session状态
|
|
2923
2945
|
const currentSession = await validateSession('generate-video-by-ref');
|
|
2924
|
-
const storyBoardPath = (0, node_path_1.resolve)(
|
|
2925
|
-
if (type !== 'pro' &&
|
|
2926
|
-
return createErrorResponse('
|
|
2946
|
+
const storyBoardPath = (0, node_path_1.resolve)(projectLocalDir, storyBoardFile);
|
|
2947
|
+
if (type !== 'vidu-pro' && referenceVideo) {
|
|
2948
|
+
return createErrorResponse('只有vidu-pro模型支持参考视频', 'generate-video-by-ref');
|
|
2927
2949
|
}
|
|
2928
|
-
const outlineSheetImagePath = (0, node_path_1.resolve)(
|
|
2950
|
+
const outlineSheetImagePath = (0, node_path_1.resolve)(projectLocalDir, 'materials', 'outline_sheet.png');
|
|
2929
2951
|
const hasOutlineSheet = (0, node_fs_1.existsSync)(outlineSheetImagePath);
|
|
2930
|
-
if (hasOutlineSheet && !skipConsistencyCheck) {
|
|
2931
|
-
return createErrorResponse('监测到素材中存在outline_sheet.png这张图(由outline工具生成的),应采用 generate-video 图生视频。若用户明确要用参考生视频,则跳过一致性检查。', 'generate-video');
|
|
2932
|
-
}
|
|
2933
2952
|
// 校验 prompt 与 storyboard.json 中分镜设定的一致性(如果提供了 sceneIndex)
|
|
2934
|
-
if (
|
|
2953
|
+
if (sceneIndex) {
|
|
2935
2954
|
try {
|
|
2955
|
+
if (hasOutlineSheet) {
|
|
2956
|
+
return createErrorResponse('监测到素材中存在outline_sheet.png这张图(由outline工具生成的),应采用 generate-video 图生视频。', 'generate-video-by-ref');
|
|
2957
|
+
}
|
|
2936
2958
|
if ((0, node_fs_1.existsSync)(storyBoardPath)) {
|
|
2937
2959
|
const storyBoardContent = await (0, promises_1.readFile)(storyBoardPath, 'utf8');
|
|
2938
2960
|
// 检查 storyBoard JSON 语法合法性
|
|
@@ -2948,7 +2970,7 @@ server.registerTool('generate-video-by-ref', {
|
|
|
2948
2970
|
if (scene) {
|
|
2949
2971
|
const videoPrompt = scene.video_prompt;
|
|
2950
2972
|
if (videoPrompt && prompt !== videoPrompt) {
|
|
2951
|
-
return createErrorResponse('视频提示词必须严格遵照storyboard
|
|
2973
|
+
return createErrorResponse('视频提示词必须严格遵照storyboard的设定', 'generate-video-by-ref');
|
|
2952
2974
|
}
|
|
2953
2975
|
// 检查 scene.is_continuous 是否为 true
|
|
2954
2976
|
if (scene.is_continuous === true) {
|
|
@@ -3072,7 +3094,6 @@ server.registerTool('generate-video-by-ref', {
|
|
|
3072
3094
|
console.log(`Generating video ${referenceImages.length > 0 ? `with ${referenceImages.length} reference image(s)` : 'without reference images'} using ${type} model...`);
|
|
3073
3095
|
// 处理参考图:转换为URL而不是base64
|
|
3074
3096
|
const referenceImageUrls = [];
|
|
3075
|
-
let promptPrefix = '';
|
|
3076
3097
|
for (const imageRef of referenceImages) {
|
|
3077
3098
|
// 使用 getMaterialUri 获取图片URL
|
|
3078
3099
|
const imageUrl = getMaterialUri(currentSession, imageRef.fileName);
|
|
@@ -3082,14 +3103,17 @@ server.registerTool('generate-video-by-ref', {
|
|
|
3082
3103
|
url: imageUrl,
|
|
3083
3104
|
});
|
|
3084
3105
|
console.log(`Added reference image URL: ${imageUrl} (name: ${imageRef.name}, type: ${imageRef.type})`);
|
|
3085
|
-
if (rewritePrompt) {
|
|
3086
|
-
promptPrefix += `参考“${imageRef.name}”(图${referenceImageUrls.length})${imageRef.type === 'subject' ? '主体形象' : '背景'}\n`;
|
|
3087
|
-
}
|
|
3088
|
-
}
|
|
3089
|
-
if (promptPrefix) {
|
|
3090
|
-
promptPrefix += '\n';
|
|
3091
3106
|
}
|
|
3092
|
-
const finalPrompt = `${
|
|
3107
|
+
const finalPrompt = `${prompt}`;
|
|
3108
|
+
const videos = referenceVideo
|
|
3109
|
+
? [
|
|
3110
|
+
{
|
|
3111
|
+
type: 'video',
|
|
3112
|
+
name: referenceVideo.name,
|
|
3113
|
+
url: getMaterialUri(currentSession, referenceVideo.fileName),
|
|
3114
|
+
},
|
|
3115
|
+
]
|
|
3116
|
+
: undefined;
|
|
3093
3117
|
// 调用 referencesToVideo 函数
|
|
3094
3118
|
const result = await currentSession.ai.referencesToVideo({
|
|
3095
3119
|
prompt: finalPrompt,
|
|
@@ -3100,10 +3124,12 @@ server.registerTool('generate-video-by-ref', {
|
|
|
3100
3124
|
type,
|
|
3101
3125
|
mute,
|
|
3102
3126
|
seed,
|
|
3127
|
+
videos,
|
|
3103
3128
|
onProgress: metaData => {
|
|
3104
3129
|
console.log('Video generation progress:', metaData);
|
|
3105
3130
|
sendProgress(context, metaData.progress || 0, 100, 'Generating video...');
|
|
3106
3131
|
},
|
|
3132
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
3107
3133
|
});
|
|
3108
3134
|
if (result.error) {
|
|
3109
3135
|
return createErrorResponse(result.error, 'generate-video-by-ref');
|
|
@@ -3229,6 +3255,7 @@ server.registerTool('extend-video-duration', {
|
|
|
3229
3255
|
onProgress: async (metaData) => {
|
|
3230
3256
|
sendProgress(context, ++progress, undefined, `Extension progress: ${Math.round(progress * 100)}%`);
|
|
3231
3257
|
},
|
|
3258
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
3232
3259
|
});
|
|
3233
3260
|
// 检查结果
|
|
3234
3261
|
if (!result || result.error) {
|
|
@@ -3305,7 +3332,7 @@ server.registerTool('use-template', {
|
|
|
3305
3332
|
}));
|
|
3306
3333
|
const validatedFileName = validateFileName(saveToFileName);
|
|
3307
3334
|
let completion = await ai.getCompletions({
|
|
3308
|
-
model: 'Doubao-Seed-1.
|
|
3335
|
+
model: 'Doubao-Seed-1.8',
|
|
3309
3336
|
messages: [
|
|
3310
3337
|
{
|
|
3311
3338
|
role: 'system',
|
|
@@ -3351,7 +3378,7 @@ ${user_request}
|
|
|
3351
3378
|
${JSON.stringify(materialUrls)}`;
|
|
3352
3379
|
// console.log(prompt);
|
|
3353
3380
|
completion = await ai.getCompletions({
|
|
3354
|
-
model: 'Doubao-Seed-1.
|
|
3381
|
+
model: 'Doubao-Seed-1.8',
|
|
3355
3382
|
messages: [
|
|
3356
3383
|
{
|
|
3357
3384
|
role: 'system',
|