cerevox 4.0.0-alpha.3 → 4.0.0-alpha.31
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 +250 -200
- 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);
|
|
@@ -320,6 +323,24 @@ async function listFiles(dir) {
|
|
|
320
323
|
const entries = await (0, promises_1.readdir)(dir, { withFileTypes: true });
|
|
321
324
|
return entries.filter(e => e.isFile()).map(e => (0, node_path_1.resolve)(dir, e.name));
|
|
322
325
|
}
|
|
326
|
+
function checkModelEnabled(model) {
|
|
327
|
+
// 检查是否是许可的模型
|
|
328
|
+
if (!process.env.ENABLED_MODELS) {
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
const enabledModels = process.env.ENABLED_MODELS.split(/\s*,\s*/g);
|
|
332
|
+
if (!enabledModels.includes(model)) {
|
|
333
|
+
throw new Error(`Type ${model} is not enabled. Use any model in ${JSON.stringify(enabledModels)} instead.`);
|
|
334
|
+
}
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
function checkSkillEnabled(skillName) {
|
|
338
|
+
if (!process.env.ENABLED_SKILLS) {
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
const enabledSkills = process.env.ENABLED_SKILLS.split(/\s*,\s*/g);
|
|
342
|
+
return enabledSkills.includes(skillName);
|
|
343
|
+
}
|
|
323
344
|
// Create an MCP server
|
|
324
345
|
const server = new mcp_js_1.McpServer({
|
|
325
346
|
name: 'Cerevox Server',
|
|
@@ -330,18 +351,18 @@ const cerevox = new index_1.default({
|
|
|
330
351
|
logLevel: 'error',
|
|
331
352
|
});
|
|
332
353
|
let session = null;
|
|
333
|
-
let projectLocalDir = process.env.ZEROCUT_PROJECT_CWD ||
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
// let checkStoryboardSubtitlesFlag = false;
|
|
354
|
+
let projectLocalDir = process.env.ZEROCUT_PROJECT_CWD ||
|
|
355
|
+
`${(0, node_path_1.resolve)(process.env.ZEROCUT_WORKSPACE_DIR, (0, uuid_1.v4)())}` ||
|
|
356
|
+
'.';
|
|
337
357
|
let closeSessionTimerId = null;
|
|
358
|
+
const CLIENT_TYPE = process.env.CLIENT_TYPE || 'trae';
|
|
338
359
|
// 注册 ZeroCut 指导规范 Prompt
|
|
339
360
|
server.registerPrompt('zerocut-guideline', {
|
|
340
361
|
title: 'ZeroCut 短视频创作指导规范',
|
|
341
362
|
description: '专业的短视频创作 Agent 指导规范,包含完整的工作流程、工具说明和质量建议',
|
|
342
363
|
}, async () => {
|
|
343
364
|
try {
|
|
344
|
-
const promptPath = (0, node_path_1.resolve)(__dirname, './prompts/zerocut-core
|
|
365
|
+
const promptPath = (0, node_path_1.resolve)(__dirname, './prompts/zerocut-core.md');
|
|
345
366
|
const promptContent = await (0, promises_1.readFile)(promptPath, 'utf-8');
|
|
346
367
|
return {
|
|
347
368
|
messages: [
|
|
@@ -362,25 +383,23 @@ server.registerPrompt('zerocut-guideline', {
|
|
|
362
383
|
});
|
|
363
384
|
server.registerTool('project-open', {
|
|
364
385
|
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
|
|
386
|
+
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
387
|
inputSchema: {
|
|
367
|
-
|
|
388
|
+
projectName: zod_1.z
|
|
368
389
|
.string()
|
|
369
|
-
.
|
|
370
|
-
.default('.')
|
|
371
|
-
.describe('The path of the file to upload.'),
|
|
390
|
+
.describe('项目名,命名规则如同命名文件,可以用空格,但不得使用特殊字符(如/、:等),如果你知晓当前目录,必须用当前目录的最后一级目录名作为项目名,否则根据用户需求自定义项目名'),
|
|
372
391
|
tosFiles: zod_1.z
|
|
373
392
|
.array(zod_1.z.string())
|
|
374
393
|
.optional()
|
|
375
394
|
.default([])
|
|
376
395
|
.describe('对象存储系统中的持久化文件,不通过本地直接下载到项目目录,可选参数'),
|
|
377
|
-
|
|
396
|
+
syncAllFiles: zod_1.z
|
|
378
397
|
.boolean()
|
|
379
398
|
.optional()
|
|
380
399
|
.default(false)
|
|
381
|
-
.describe('Whether to
|
|
400
|
+
.describe('Whether to sync all resources without filtering. If true, skips the smart filtering logic.'),
|
|
382
401
|
},
|
|
383
|
-
}, async ({
|
|
402
|
+
}, async ({ projectName, syncAllFiles, tosFiles }, context) => {
|
|
384
403
|
try {
|
|
385
404
|
if (closeSessionTimerId) {
|
|
386
405
|
clearTimeout(closeSessionTimerId);
|
|
@@ -388,12 +407,9 @@ server.registerTool('project-open', {
|
|
|
388
407
|
}
|
|
389
408
|
// 检查是否已有活跃session
|
|
390
409
|
if (session) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
// } catch (closeError) {
|
|
395
|
-
// console.warn('Failed to close previous session:', closeError);
|
|
396
|
-
// }
|
|
410
|
+
if (session.projectName !== projectName) {
|
|
411
|
+
return createErrorResponse('project-open', `Another project is already open: ${session.projectName},Ask user to close it first.`);
|
|
412
|
+
}
|
|
397
413
|
const result = {
|
|
398
414
|
success: true,
|
|
399
415
|
sessionId: session.id,
|
|
@@ -424,10 +440,20 @@ server.registerTool('project-open', {
|
|
|
424
440
|
if (!session) {
|
|
425
441
|
throw new Error('Failed to create Cerevox session');
|
|
426
442
|
}
|
|
443
|
+
// 保存项目名到session
|
|
444
|
+
session.projectName = projectName;
|
|
427
445
|
console.log('Initializing project...');
|
|
428
446
|
const workDir = await initProject(session);
|
|
429
|
-
|
|
447
|
+
if (!process.env.ZEROCUT_PROJECT_CWD &&
|
|
448
|
+
!process.env.ZEROCUT_WORKSPACE_DIR) {
|
|
449
|
+
throw new Error('ZEROCUT_WORKSPACE_DIR environment variable is required');
|
|
450
|
+
}
|
|
451
|
+
projectLocalDir = process.env.ZEROCUT_PROJECT_CWD
|
|
452
|
+
? (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD, '.')
|
|
453
|
+
: (0, node_path_1.resolve)(process.env.ZEROCUT_WORKSPACE_DIR, projectName);
|
|
430
454
|
const syncDir = (0, node_path_1.resolve)(projectLocalDir, 'materials');
|
|
455
|
+
// 保证项目目录存在
|
|
456
|
+
await (0, promises_1.mkdir)(projectLocalDir, { recursive: true });
|
|
431
457
|
try {
|
|
432
458
|
await (0, promises_1.mkdir)(syncDir, { recursive: true });
|
|
433
459
|
}
|
|
@@ -439,39 +465,37 @@ server.registerTool('project-open', {
|
|
|
439
465
|
// 文件过滤逻辑
|
|
440
466
|
let filesToUpload = [];
|
|
441
467
|
let skippedFiles = [];
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
skippedFiles = filterResult.skippedFiles;
|
|
460
|
-
}
|
|
468
|
+
try {
|
|
469
|
+
materials = await listFiles(syncDir);
|
|
470
|
+
}
|
|
471
|
+
catch (listError) {
|
|
472
|
+
console.warn('Failed to list materials:', listError);
|
|
473
|
+
materials = [];
|
|
474
|
+
}
|
|
475
|
+
if (syncAllFiles) {
|
|
476
|
+
// 如果 syncAllFiles 为 true,跳过智能过滤,上传所有文件
|
|
477
|
+
filesToUpload = materials;
|
|
478
|
+
skippedFiles = [];
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
// 智能文件过滤逻辑
|
|
482
|
+
const filterResult = await filterMaterialsForUpload(materials, projectLocalDir);
|
|
483
|
+
filesToUpload = filterResult.filesToUpload;
|
|
484
|
+
skippedFiles = filterResult.skippedFiles;
|
|
461
485
|
}
|
|
462
486
|
const files = session.files;
|
|
463
487
|
let progress = 0;
|
|
464
|
-
const
|
|
488
|
+
const syncErrors = [];
|
|
465
489
|
const totalFiles = filesToUpload.length + tosFiles.length;
|
|
466
490
|
for (const material of filesToUpload) {
|
|
467
491
|
try {
|
|
468
492
|
await files.upload(material, `${workDir}/materials/${(0, node_path_1.basename)(material)}`);
|
|
469
493
|
await sendProgress(context, ++progress, totalFiles, material);
|
|
470
494
|
}
|
|
471
|
-
catch (
|
|
472
|
-
const errorMsg = `Failed to
|
|
495
|
+
catch (syncError) {
|
|
496
|
+
const errorMsg = `Failed to sync ${material}: ${syncError}`;
|
|
473
497
|
console.error(errorMsg);
|
|
474
|
-
|
|
498
|
+
syncErrors.push(errorMsg);
|
|
475
499
|
}
|
|
476
500
|
}
|
|
477
501
|
for (const tosFile of tosFiles) {
|
|
@@ -480,10 +504,10 @@ server.registerTool('project-open', {
|
|
|
480
504
|
await session.terminal.run(`wget -O ${workDir}/materials/${(0, node_path_1.basename)(url.pathname)} ${tosFile}`);
|
|
481
505
|
await sendProgress(context, ++progress, totalFiles, tosFile);
|
|
482
506
|
}
|
|
483
|
-
catch (
|
|
484
|
-
const errorMsg = `Failed to
|
|
507
|
+
catch (syncError) {
|
|
508
|
+
const errorMsg = `Failed to sync ${tosFile}: ${syncError}`;
|
|
485
509
|
console.error(errorMsg);
|
|
486
|
-
|
|
510
|
+
syncErrors.push(errorMsg);
|
|
487
511
|
}
|
|
488
512
|
}
|
|
489
513
|
const result = {
|
|
@@ -491,11 +515,12 @@ server.registerTool('project-open', {
|
|
|
491
515
|
nextActionSuggest: '检查规则上下文是否已召回,若未召回,调用 retrieve_rules 工具召回规则上下文',
|
|
492
516
|
sessionId: session.id,
|
|
493
517
|
workDir,
|
|
518
|
+
projectName,
|
|
494
519
|
projectLocalDir,
|
|
495
520
|
materials,
|
|
496
|
-
|
|
521
|
+
syncedFiles: filesToUpload.map(file => (0, node_path_1.basename)(file)),
|
|
497
522
|
skippedFiles: skippedFiles.map(file => (0, node_path_1.basename)(file)),
|
|
498
|
-
|
|
523
|
+
syncErrors: syncErrors.length > 0 ? syncErrors : undefined,
|
|
499
524
|
};
|
|
500
525
|
return {
|
|
501
526
|
content: [
|
|
@@ -521,7 +546,7 @@ server.registerTool('project-close', {
|
|
|
521
546
|
.min(0)
|
|
522
547
|
.max(20)
|
|
523
548
|
.default(5)
|
|
524
|
-
.describe('Close the session after the specified number of minutes. Default is 5 minutes.
|
|
549
|
+
.describe('Close the session after the specified number of minutes. Default is 5 minutes. 当用户主动要求关闭会话时,将该参数设置为0,否则应默认设为5'),
|
|
525
550
|
},
|
|
526
551
|
}, async ({ inMinutes }) => {
|
|
527
552
|
try {
|
|
@@ -578,9 +603,9 @@ server.registerTool('retrieve-rules-context', {
|
|
|
578
603
|
}
|
|
579
604
|
else {
|
|
580
605
|
// 当 projectRulesFile 不存在时,设置 checkStoryboardFlag 为 false
|
|
581
|
-
checkStoryboardFlag = false;
|
|
606
|
+
// checkStoryboardFlag = false;
|
|
582
607
|
// 当 projectRulesFile 不存在时,设置 checkAudioVideoDurationFlag 为 false
|
|
583
|
-
checkAudioVideoDurationFlag = false;
|
|
608
|
+
// checkAudioVideoDurationFlag = false;
|
|
584
609
|
}
|
|
585
610
|
try {
|
|
586
611
|
const ai = currentSession.ai;
|
|
@@ -593,7 +618,8 @@ server.registerTool('retrieve-rules-context', {
|
|
|
593
618
|
await (0, promises_1.writeFile)(projectRulesFile, promptContent);
|
|
594
619
|
}
|
|
595
620
|
if (!(0, node_fs_1.existsSync)(skillsIndexFile)) {
|
|
596
|
-
const skills = rules.filter((rule) => rule.name.startsWith('skill-')
|
|
621
|
+
const skills = rules.filter((rule) => rule.name.startsWith('skill-') &&
|
|
622
|
+
checkSkillEnabled(rule.name.slice(6)));
|
|
597
623
|
const skillsList = skills.map((skill) => `## ${skill.name}
|
|
598
624
|
${skill.trigger}
|
|
599
625
|
`);
|
|
@@ -636,8 +662,8 @@ server.registerTool('upload-custom-material', {
|
|
|
636
662
|
try {
|
|
637
663
|
// 验证session状态
|
|
638
664
|
const currentSession = await validateSession('upload-custom-material');
|
|
639
|
-
// 构建本地文件路径,使用
|
|
640
|
-
const validatedPath = (0, node_path_1.resolve)(
|
|
665
|
+
// 构建本地文件路径,使用 projectLocalDir
|
|
666
|
+
const validatedPath = (0, node_path_1.resolve)(projectLocalDir, 'materials', localFileName.trim());
|
|
641
667
|
// 验证本地文件存在性
|
|
642
668
|
if (!(0, node_fs_1.existsSync)(validatedPath)) {
|
|
643
669
|
throw new Error(`File not found: ${validatedPath}`);
|
|
@@ -711,7 +737,7 @@ server.registerTool('upload-custom-material', {
|
|
|
711
737
|
}
|
|
712
738
|
});
|
|
713
739
|
server.registerTool('wait-for-task-finish', {
|
|
714
|
-
title: 'Wait Workflow or VideoTask Done;只有正在运行Coze工作流或者有异步生成视频任务时才需要执行这个工具;⚠️
|
|
740
|
+
title: 'Wait Workflow or VideoTask Done;只有正在运行Coze工作流或者有异步生成视频任务时才需要执行这个工具;⚠️ 如果执行这个工具未失败只是超时,你应立即再次重新调用,以继续等待直到任务完成或失败;‼️ 有的任务执行时间需要很长,所以如果只是超时不是失败,重试多少次都是正常的,请耐心等待即可。',
|
|
715
741
|
description: 'Wait for a workflow to complete.',
|
|
716
742
|
inputSchema: {
|
|
717
743
|
taskUrl: zod_1.z
|
|
@@ -767,6 +793,32 @@ server.registerTool('wait-for-task-finish', {
|
|
|
767
793
|
catch (error) {
|
|
768
794
|
console.warn(`Failed to update media_logs.json for ${validatedFileName}:`, error);
|
|
769
795
|
}
|
|
796
|
+
if (res.data?.scenes) {
|
|
797
|
+
const { scenes, video_type, voice_type, voiceover_tone, bgm_prompt, video_model, aspect_ratio, } = res.data;
|
|
798
|
+
const seed = (0, seed_1.getRandomSeed)();
|
|
799
|
+
const orientation = aspect_ratio === '16:9' ? 'landscape' : 'portrait';
|
|
800
|
+
const storyboard = {
|
|
801
|
+
orientation,
|
|
802
|
+
video_type,
|
|
803
|
+
outline_sheet: 'outline_sheet.png',
|
|
804
|
+
bgm_prompt,
|
|
805
|
+
voice_type,
|
|
806
|
+
scenes: scenes.map((scene) => {
|
|
807
|
+
let video_prompt = scene.video_prompt;
|
|
808
|
+
if (voiceover_tone && video_prompt.includes('画外音')) {
|
|
809
|
+
video_prompt = video_prompt.replace(/画外音[::]/g, `画外音(${voiceover_tone}):`);
|
|
810
|
+
}
|
|
811
|
+
return {
|
|
812
|
+
...scene,
|
|
813
|
+
video_prompt,
|
|
814
|
+
use_video_model: video_model,
|
|
815
|
+
seed,
|
|
816
|
+
};
|
|
817
|
+
}),
|
|
818
|
+
};
|
|
819
|
+
const saveLocalPath = (0, node_path_1.resolve)(projectLocalDir, 'storyboard.json');
|
|
820
|
+
await (0, promises_1.writeFile)(saveLocalPath, JSON.stringify(storyboard, null, 2));
|
|
821
|
+
}
|
|
770
822
|
return {
|
|
771
823
|
content: [
|
|
772
824
|
{
|
|
@@ -839,6 +891,7 @@ server.registerTool('generate-character-image', {
|
|
|
839
891
|
try {
|
|
840
892
|
// 验证session状态
|
|
841
893
|
const currentSession = await validateSession('generate-character-image');
|
|
894
|
+
checkModelEnabled(type);
|
|
842
895
|
const validatedFileName = validateFileName(saveToFileName);
|
|
843
896
|
// 根据 isTurnaround 参数生成不同的提示词和尺寸
|
|
844
897
|
let prompt;
|
|
@@ -932,7 +985,7 @@ ${roleDescriptionPrompt}
|
|
|
932
985
|
? referenceImage
|
|
933
986
|
: `./materials/${referenceImage}`;
|
|
934
987
|
// 需要得到当前项目的绝对路径
|
|
935
|
-
const imageFilePath = (0, node_path_1.resolve)(
|
|
988
|
+
const imageFilePath = (0, node_path_1.resolve)(projectLocalDir, imagePath);
|
|
936
989
|
// 读取图片文件内容
|
|
937
990
|
const imageBuffer = await (0, promises_1.readFile)(imageFilePath);
|
|
938
991
|
const fileName = (0, node_path_1.basename)(imagePath);
|
|
@@ -1026,20 +1079,6 @@ server.registerTool('generate-image', {
|
|
|
1026
1079
|
.min(1)
|
|
1027
1080
|
.optional()
|
|
1028
1081
|
.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
1082
|
size: zod_1.z
|
|
1044
1083
|
.enum([
|
|
1045
1084
|
'1024x1024',
|
|
@@ -1124,15 +1163,17 @@ server.registerTool('generate-image', {
|
|
|
1124
1163
|
\`\`\`
|
|
1125
1164
|
`),
|
|
1126
1165
|
},
|
|
1127
|
-
}, async ({ type = 'seedream', prompt, sceneIndex,
|
|
1166
|
+
}, async ({ type = 'seedream', prompt, sceneIndex, size = '720x1280', imageCount = 1, saveToFileNames, watermark, referenceImages, }, context) => {
|
|
1128
1167
|
try {
|
|
1168
|
+
const storyBoardFile = 'storyboard.json';
|
|
1129
1169
|
// 验证session状态
|
|
1130
1170
|
const currentSession = await validateSession('generate-image');
|
|
1131
|
-
|
|
1132
|
-
const
|
|
1171
|
+
checkModelEnabled(type);
|
|
1172
|
+
const storyBoardPath = (0, node_path_1.resolve)(projectLocalDir, storyBoardFile);
|
|
1173
|
+
const outlineSheetImagePath = (0, node_path_1.resolve)(projectLocalDir, 'materials', 'outline_sheet.png');
|
|
1133
1174
|
const hasOutlineSheet = (0, node_fs_1.existsSync)(outlineSheetImagePath);
|
|
1134
1175
|
// 校验 prompt 与 storyboard.json 中分镜设定的一致性
|
|
1135
|
-
if (sceneIndex
|
|
1176
|
+
if (sceneIndex) {
|
|
1136
1177
|
try {
|
|
1137
1178
|
if ((0, node_fs_1.existsSync)(storyBoardPath)) {
|
|
1138
1179
|
const storyBoardContent = await (0, promises_1.readFile)(storyBoardPath, 'utf8');
|
|
@@ -1151,7 +1192,7 @@ server.registerTool('generate-image', {
|
|
|
1151
1192
|
const endFrame = scene.end_frame;
|
|
1152
1193
|
// 检查 prompt 是否严格等于 start_frame 或 end_frame
|
|
1153
1194
|
if (prompt !== startFrame && prompt !== endFrame) {
|
|
1154
|
-
return createErrorResponse('图片提示词必须严格遵照storyboard
|
|
1195
|
+
return createErrorResponse('图片提示词必须严格遵照storyboard的设定', 'generate-image');
|
|
1155
1196
|
}
|
|
1156
1197
|
if (hasOutlineSheet &&
|
|
1157
1198
|
(!referenceImages ||
|
|
@@ -1235,7 +1276,7 @@ server.registerTool('generate-image', {
|
|
|
1235
1276
|
? refImage.image
|
|
1236
1277
|
: `./materials/${refImage.image}`;
|
|
1237
1278
|
// 需要得到当前项目的绝对路径
|
|
1238
|
-
const imageFilePath = (0, node_path_1.resolve)(
|
|
1279
|
+
const imageFilePath = (0, node_path_1.resolve)(projectLocalDir, imagePath);
|
|
1239
1280
|
try {
|
|
1240
1281
|
// 直接读取本地文件
|
|
1241
1282
|
if (!(0, node_fs_1.existsSync)(imageFilePath)) {
|
|
@@ -1296,7 +1337,7 @@ ${processedPrompt}`.trim();
|
|
|
1296
1337
|
}
|
|
1297
1338
|
}
|
|
1298
1339
|
const ai = currentSession.ai;
|
|
1299
|
-
const
|
|
1340
|
+
const taskRes = await ai.generateImage({
|
|
1300
1341
|
type,
|
|
1301
1342
|
prompt: processedPrompt,
|
|
1302
1343
|
size,
|
|
@@ -1304,6 +1345,10 @@ ${processedPrompt}`.trim();
|
|
|
1304
1345
|
image: imageBase64Array,
|
|
1305
1346
|
async: true,
|
|
1306
1347
|
});
|
|
1348
|
+
const taskUrl = taskRes.taskUrl;
|
|
1349
|
+
if (!taskUrl) {
|
|
1350
|
+
return createErrorResponse(`Failed to generate image: ${taskRes.error || JSON.stringify(taskRes)}`, 'generate-image');
|
|
1351
|
+
}
|
|
1307
1352
|
let progress = 0;
|
|
1308
1353
|
const res = await ai.waitForTaskComplete({
|
|
1309
1354
|
taskUrl,
|
|
@@ -1444,7 +1489,7 @@ server.registerTool('edit-image', {
|
|
|
1444
1489
|
? sourceImageFileName
|
|
1445
1490
|
: `./materials/${sourceImageFileName}`;
|
|
1446
1491
|
// 需要得到当前项目的绝对路径
|
|
1447
|
-
const imageFilePath = (0, node_path_1.resolve)(
|
|
1492
|
+
const imageFilePath = (0, node_path_1.resolve)(projectLocalDir, imagePath);
|
|
1448
1493
|
if (!(0, node_fs_1.existsSync)(imageFilePath)) {
|
|
1449
1494
|
return createErrorResponse(`Reference image not found: ${imageFilePath}`, 'edit-image');
|
|
1450
1495
|
}
|
|
@@ -1512,9 +1557,9 @@ server.registerTool('edit-image', {
|
|
|
1512
1557
|
return createErrorResponse(error, 'edit-image');
|
|
1513
1558
|
}
|
|
1514
1559
|
});
|
|
1515
|
-
server.registerTool('generate-
|
|
1516
|
-
title: 'Generate
|
|
1517
|
-
description: `根据用户描述生成短视频的大纲;执行本工具会自动创建两个文件,一个是storyboard.json,一个是outline_sheet.png
|
|
1560
|
+
server.registerTool('generate-video-outlines', {
|
|
1561
|
+
title: 'Generate Video Outlines',
|
|
1562
|
+
description: `根据用户描述生成短视频的大纲;执行本工具会自动创建两个文件,一个是storyboard.json,一个是outline_sheet.png,前者是分镜设置,后者是分镜宫格图,其中outline_sheet.png生成于materials目录下,storyboard.json生成于materials的上级目录。`,
|
|
1518
1563
|
inputSchema: {
|
|
1519
1564
|
prompt: zod_1.z
|
|
1520
1565
|
.string()
|
|
@@ -1550,10 +1595,11 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1550
1595
|
.default('pro')
|
|
1551
1596
|
.describe('除非用户主动提出使用其他模型,否则一律用pro模型'),
|
|
1552
1597
|
},
|
|
1553
|
-
}, async ({ prompt, voiceType, language, images, orientation, model }) => {
|
|
1598
|
+
}, async ({ prompt, voiceType, language, images, orientation, model }, context) => {
|
|
1554
1599
|
try {
|
|
1555
1600
|
// 验证session状态
|
|
1556
|
-
const currentSession = await validateSession('generate-
|
|
1601
|
+
const currentSession = await validateSession('generate-video-outlines');
|
|
1602
|
+
let progress = 0;
|
|
1557
1603
|
const ai = currentSession.ai;
|
|
1558
1604
|
const res = await ai.generateShortVideoOutlines({
|
|
1559
1605
|
prompt,
|
|
@@ -1562,6 +1608,15 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1562
1608
|
images,
|
|
1563
1609
|
videoModel: model,
|
|
1564
1610
|
aspectRatio: orientation === 'portrait' ? '9:16' : '16:9',
|
|
1611
|
+
onProgress: async (metaData) => {
|
|
1612
|
+
try {
|
|
1613
|
+
await sendProgress(context, ++progress, undefined, JSON.stringify(metaData));
|
|
1614
|
+
}
|
|
1615
|
+
catch (progressError) {
|
|
1616
|
+
console.warn('Failed to send progress update:', progressError);
|
|
1617
|
+
}
|
|
1618
|
+
},
|
|
1619
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
1565
1620
|
});
|
|
1566
1621
|
if (!res) {
|
|
1567
1622
|
throw new Error('Failed to generate short video outlines: no response from AI service');
|
|
@@ -1573,7 +1628,7 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1573
1628
|
type: 'text',
|
|
1574
1629
|
text: JSON.stringify({
|
|
1575
1630
|
success: true,
|
|
1576
|
-
message: '该任务正在运行中,它是异步任务,且执行时间较长,你应立即调用工具 wait-for-task-finish 来等待任务结束,如 wait-for-task-finish 工具调用超时,你应立即再次重新调用直到任务结束。',
|
|
1631
|
+
message: '该任务正在运行中,它是异步任务,且执行时间较长,你应立即调用工具 wait-for-task-finish (saveFileName=outline_sheet.png) 来等待任务结束,如 wait-for-task-finish 工具调用超时,你应立即再次重新调用直到任务结束。',
|
|
1577
1632
|
taskUrl: res.taskUrl,
|
|
1578
1633
|
}),
|
|
1579
1634
|
},
|
|
@@ -1583,7 +1638,7 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1583
1638
|
else if (res.url) {
|
|
1584
1639
|
const url = res.url;
|
|
1585
1640
|
await saveMaterial(currentSession, url, 'outline_sheet.png');
|
|
1586
|
-
const { scenes, video_type, voice_type, voiceover_tone, bgm_prompt } = res.data || {};
|
|
1641
|
+
const { scenes, video_type, voice_type, voiceover_tone, bgm_prompt, video_model, } = res.data || {};
|
|
1587
1642
|
const seed = (0, seed_1.getRandomSeed)();
|
|
1588
1643
|
const storyboard = {
|
|
1589
1644
|
orientation,
|
|
@@ -1593,19 +1648,13 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1593
1648
|
voice_type,
|
|
1594
1649
|
scenes: scenes.map((scene) => {
|
|
1595
1650
|
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
|
-
}
|
|
1651
|
+
if (voiceover_tone && video_prompt.includes('画外音')) {
|
|
1652
|
+
video_prompt = video_prompt.replace(/画外音[::]/g, `画外音(${voiceover_tone}):`);
|
|
1604
1653
|
}
|
|
1605
1654
|
return {
|
|
1606
1655
|
...scene,
|
|
1607
1656
|
video_prompt,
|
|
1608
|
-
use_video_model:
|
|
1657
|
+
use_video_model: video_model,
|
|
1609
1658
|
seed,
|
|
1610
1659
|
};
|
|
1611
1660
|
}),
|
|
@@ -1640,7 +1689,7 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1640
1689
|
};
|
|
1641
1690
|
}
|
|
1642
1691
|
catch (error) {
|
|
1643
|
-
return createErrorResponse(error, 'generate-
|
|
1692
|
+
return createErrorResponse(error, 'generate-video-outlines');
|
|
1644
1693
|
}
|
|
1645
1694
|
});
|
|
1646
1695
|
server.registerTool('generate-music-or-mv', {
|
|
@@ -1959,26 +2008,12 @@ server.registerTool('generate-video', {
|
|
|
1959
2008
|
inputSchema: {
|
|
1960
2009
|
prompt: zod_1.z
|
|
1961
2010
|
.string()
|
|
1962
|
-
.describe('The prompt to generate.
|
|
2011
|
+
.describe('The prompt to generate. 尽量忠于用户的原始需求,除非用户明确要求协助优化,否则不要擅自发挥!'),
|
|
1963
2012
|
sceneIndex: zod_1.z
|
|
1964
2013
|
.number()
|
|
1965
2014
|
.min(1)
|
|
1966
2015
|
.optional()
|
|
1967
2016
|
.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
2017
|
type: zod_1.z
|
|
1983
2018
|
.enum([
|
|
1984
2019
|
'pro',
|
|
@@ -2039,10 +2074,11 @@ server.registerTool('generate-video', {
|
|
|
2039
2074
|
.default(false)
|
|
2040
2075
|
.describe('Whether to optimize the prompt.'),
|
|
2041
2076
|
},
|
|
2042
|
-
}, async ({ prompt, sceneIndex,
|
|
2077
|
+
}, async ({ prompt, sceneIndex, saveToFileName, start_frame, end_frame, duration, resolution, type = 'vidu', optimizePrompt, saveLastFrameAs, mute = true, seed, }, context) => {
|
|
2043
2078
|
try {
|
|
2044
2079
|
// 验证session状态
|
|
2045
2080
|
const currentSession = await validateSession('generate-video');
|
|
2081
|
+
checkModelEnabled(type);
|
|
2046
2082
|
const isZeroModel = type.startsWith('zero');
|
|
2047
2083
|
if (!start_frame && !isZeroModel) {
|
|
2048
2084
|
return createErrorResponse('start_frame 不能为空', 'generate-video');
|
|
@@ -2060,10 +2096,11 @@ server.registerTool('generate-video', {
|
|
|
2060
2096
|
console.warn(`zero 模型的视频仅支持 1080p 分辨率,用户指定的分辨率为 %s,已自动将 ${resolution} 转换为 1080p`, resolution);
|
|
2061
2097
|
resolution = '1080p';
|
|
2062
2098
|
}
|
|
2099
|
+
const storyBoardFile = 'storyboard.json';
|
|
2063
2100
|
// 校验 prompt 与 storyboard.json 中分镜设定的一致性
|
|
2064
|
-
if (sceneIndex
|
|
2101
|
+
if (sceneIndex) {
|
|
2065
2102
|
try {
|
|
2066
|
-
const storyBoardPath = (0, node_path_1.resolve)(
|
|
2103
|
+
const storyBoardPath = (0, node_path_1.resolve)(projectLocalDir, storyBoardFile);
|
|
2067
2104
|
if ((0, node_fs_1.existsSync)(storyBoardPath)) {
|
|
2068
2105
|
const storyBoardContent = await (0, promises_1.readFile)(storyBoardPath, 'utf8');
|
|
2069
2106
|
// 检查 storyBoard JSON 语法合法性
|
|
@@ -2079,26 +2116,26 @@ server.registerTool('generate-video', {
|
|
|
2079
2116
|
if (scene) {
|
|
2080
2117
|
const videoPrompt = scene.video_prompt;
|
|
2081
2118
|
if (videoPrompt && prompt !== videoPrompt) {
|
|
2082
|
-
return createErrorResponse('视频提示词必须严格遵照storyboard
|
|
2119
|
+
return createErrorResponse('视频提示词必须严格遵照storyboard的设定', 'generate-video');
|
|
2083
2120
|
}
|
|
2084
2121
|
if (scene.is_continuous && !end_frame) {
|
|
2085
|
-
return createErrorResponse('连续分镜必须指定end_frame
|
|
2122
|
+
return createErrorResponse('连续分镜必须指定end_frame参数', 'generate-video');
|
|
2086
2123
|
}
|
|
2087
2124
|
if (scene.video_duration != null &&
|
|
2088
2125
|
duration !== scene.video_duration) {
|
|
2089
|
-
return createErrorResponse(`视频时长必须严格遵照storyboard
|
|
2126
|
+
return createErrorResponse(`视频时长必须严格遵照storyboard的设定,storyboard 中设定的时长为 ${scene.video_duration} 秒。`, 'generate-video');
|
|
2090
2127
|
}
|
|
2091
2128
|
if (storyBoard.voice_type &&
|
|
2092
2129
|
storyBoard.voice_type !== 'slient') {
|
|
2093
2130
|
if (mute) {
|
|
2094
|
-
return createErrorResponse('有对话和旁白的分镜不能静音,请将mute设为false
|
|
2131
|
+
return createErrorResponse('有对话和旁白的分镜不能静音,请将mute设为false再重新使用工具。', 'generate-video');
|
|
2095
2132
|
}
|
|
2096
2133
|
}
|
|
2097
2134
|
// 检查 use_video_model 与 type 参数的一致性
|
|
2098
2135
|
if (scene.use_video_model &&
|
|
2099
2136
|
type &&
|
|
2100
2137
|
scene.use_video_model !== type) {
|
|
2101
|
-
return createErrorResponse(`分镜建议的视频模型(${scene.use_video_model})与传入的type参数(${type})不一致。请确保use_video_model与type
|
|
2138
|
+
return createErrorResponse(`分镜建议的视频模型(${scene.use_video_model})与传入的type参数(${type})不一致。请确保use_video_model与type参数值相同。`, 'generate-video');
|
|
2102
2139
|
}
|
|
2103
2140
|
}
|
|
2104
2141
|
else {
|
|
@@ -2232,7 +2269,7 @@ server.registerTool('generate-video', {
|
|
|
2232
2269
|
},
|
|
2233
2270
|
};
|
|
2234
2271
|
const analysisPayload = {
|
|
2235
|
-
model: 'Doubao-Seed-1.
|
|
2272
|
+
model: 'Doubao-Seed-1.8',
|
|
2236
2273
|
messages: [
|
|
2237
2274
|
{
|
|
2238
2275
|
role: 'system',
|
|
@@ -2367,7 +2404,7 @@ server.registerTool('generate-video', {
|
|
|
2367
2404
|
console.warn('Failed to send progress update:', progressError);
|
|
2368
2405
|
}
|
|
2369
2406
|
},
|
|
2370
|
-
waitForFinish: type !== 'zero',
|
|
2407
|
+
waitForFinish: type !== 'zero' && CLIENT_TYPE !== '5ire',
|
|
2371
2408
|
mute,
|
|
2372
2409
|
seed,
|
|
2373
2410
|
});
|
|
@@ -2450,38 +2487,47 @@ server.registerTool('edit-video', {
|
|
|
2450
2487
|
title: 'Edit Video',
|
|
2451
2488
|
description: `Edit video using Coze workflow,可以做以下事情:
|
|
2452
2489
|
|
|
2453
|
-
-
|
|
2490
|
+
- 编辑视频(增加、修改、删除视频中的内容),type 为 edit
|
|
2491
|
+
- 参考视频动作和特效,type 也为 edit
|
|
2454
2492
|
- 视频对口型,type 为 lipsync
|
|
2455
|
-
|
|
2493
|
+
|
|
2494
|
+
‼️ 故障排查
|
|
2495
|
+
如果出错,依次检查以下事项:
|
|
2496
|
+
|
|
2497
|
+
1. 视频是否太长(超过了8秒)或太大(超过了100M)
|
|
2498
|
+
2. 视频像素过小(小于128x128)或宽高比太悬殊(大于1:4)
|
|
2499
|
+
3. 视频格式是否是 mp4
|
|
2456
2500
|
`,
|
|
2457
2501
|
inputSchema: {
|
|
2458
2502
|
type: zod_1.z
|
|
2459
|
-
.enum(['
|
|
2503
|
+
.enum(['edit', 'lipsync'])
|
|
2504
|
+
.default('edit')
|
|
2460
2505
|
.describe('The editing type'),
|
|
2461
2506
|
video: zod_1.z.string().describe(`The video to edit
|
|
2462
2507
|
|
|
2463
|
-
- type 为
|
|
2508
|
+
- type 为 edit 时,video 为要编辑的视频
|
|
2464
2509
|
- type 为 lipsync 时,video 为要对口型的视频
|
|
2465
|
-
- type 为 imitate 时,video 为模仿动作参考视频
|
|
2466
2510
|
`),
|
|
2467
2511
|
prompt: zod_1.z.string().optional()
|
|
2468
|
-
.describe(`The editing prompt,
|
|
2512
|
+
.describe(`The editing prompt, 如实转述用户需求即可
|
|
2469
2513
|
|
|
2470
|
-
|
|
2514
|
+
要求:
|
|
2515
|
+
1)用**极简的话语**准确描述,不添加其他任何补充信息,本工具会自己优化
|
|
2516
|
+
2)video一律用“视频1”指代
|
|
2517
|
+
|
|
2518
|
+
- type 为 edit 时,prompt 为编辑指令
|
|
2471
2519
|
- type 为 lipsync 时,prompt 为空
|
|
2472
|
-
- type 为 imitate 时,prompt 为要模仿动作的内容,和 referenceImageUrl 二选一
|
|
2473
2520
|
`),
|
|
2474
|
-
|
|
2475
|
-
.describe(`The reference image
|
|
2476
|
-
- type 为
|
|
2477
|
-
- type 为 lipsync 时,
|
|
2478
|
-
- type 为 imitate 时,referenceImage 为要模仿动作的画面,和 prompt 二选一
|
|
2521
|
+
referenceImages: zod_1.z.array(zod_1.z.string()).optional()
|
|
2522
|
+
.describe(`The reference image Files for editing
|
|
2523
|
+
- type 为 edit 时,referenceImages 为参考图片(1-7张)
|
|
2524
|
+
- type 为 lipsync 时,referenceImages 为空
|
|
2479
2525
|
`),
|
|
2480
2526
|
saveToFileName: zod_1.z
|
|
2481
2527
|
.string()
|
|
2482
2528
|
.describe(`The file name to save the edited video to. 应该是mp4文件`),
|
|
2483
2529
|
},
|
|
2484
|
-
}, async ({ type, video, prompt,
|
|
2530
|
+
}, async ({ type, video, prompt, referenceImages, saveToFileName }, context) => {
|
|
2485
2531
|
try {
|
|
2486
2532
|
const currentSession = await validateSession('edit-video');
|
|
2487
2533
|
const ai = currentSession.ai;
|
|
@@ -2498,19 +2544,33 @@ server.registerTool('edit-video', {
|
|
|
2498
2544
|
}
|
|
2499
2545
|
};
|
|
2500
2546
|
let referenceImageUrl = undefined;
|
|
2501
|
-
|
|
2502
|
-
|
|
2547
|
+
let referenceImageUrls = undefined;
|
|
2548
|
+
if (referenceImages) {
|
|
2549
|
+
referenceImageUrls = referenceImages.map(fileName => getMaterialUri(currentSession, fileName));
|
|
2550
|
+
referenceImageUrl = referenceImageUrls[0];
|
|
2503
2551
|
}
|
|
2504
2552
|
const videoUrl = getMaterialUri(currentSession, video);
|
|
2505
|
-
if (type === '
|
|
2553
|
+
if (type === 'edit') {
|
|
2506
2554
|
if (!prompt) {
|
|
2507
|
-
throw new Error('prompt is required for
|
|
2555
|
+
throw new Error('prompt is required for edit type');
|
|
2508
2556
|
}
|
|
2509
|
-
res = await ai.editVideo({
|
|
2510
|
-
|
|
2557
|
+
// res = await ai.editVideo({
|
|
2558
|
+
// videoUrl,
|
|
2559
|
+
// prompt,
|
|
2560
|
+
// referenceImageUrl,
|
|
2561
|
+
// onProgress,
|
|
2562
|
+
// waitForFinish: CLIENT_TYPE !== '5ire',
|
|
2563
|
+
// });
|
|
2564
|
+
res = await ai.referencesToVideo({
|
|
2511
2565
|
prompt,
|
|
2512
|
-
|
|
2566
|
+
duration: 0,
|
|
2567
|
+
type: 'vidu-pro', //'vidu', //'pixv', // 'lite', // 'sora2',
|
|
2568
|
+
reference_images: referenceImageUrls,
|
|
2569
|
+
videos: [videoUrl],
|
|
2513
2570
|
onProgress,
|
|
2571
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
2572
|
+
// aspect_ratio: '9:16',
|
|
2573
|
+
// mute: true,
|
|
2514
2574
|
});
|
|
2515
2575
|
}
|
|
2516
2576
|
else if (type === 'lipsync') {
|
|
@@ -2525,23 +2585,7 @@ server.registerTool('edit-video', {
|
|
|
2525
2585
|
audioInMs: 0,
|
|
2526
2586
|
pad_audio: false,
|
|
2527
2587
|
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,
|
|
2588
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
2545
2589
|
});
|
|
2546
2590
|
}
|
|
2547
2591
|
if (res.url) {
|
|
@@ -2750,11 +2794,15 @@ server.registerTool('audio-video-sync', {
|
|
|
2750
2794
|
.describe('The volume of video audio. 0.0 to 2.0.'),
|
|
2751
2795
|
loopAudio: zod_1.z.boolean().optional().default(true),
|
|
2752
2796
|
addSubtitles: zod_1.z.boolean().optional().default(false),
|
|
2797
|
+
subtitlesContext: zod_1.z
|
|
2798
|
+
.string()
|
|
2799
|
+
.optional()
|
|
2800
|
+
.describe('字幕的参考上下文(非必需),用于提升字幕准确性'),
|
|
2753
2801
|
saveToFileName: zod_1.z
|
|
2754
2802
|
.string()
|
|
2755
2803
|
.describe('The filename to save the audio-video-synced video. 应该是mp4文件'),
|
|
2756
2804
|
},
|
|
2757
|
-
}, async ({ videos, audio, audioInMs, audioFadeOutMs, audioVolume, videoAudioVolume, saveToFileName, loopAudio, addSubtitles, }, context) => {
|
|
2805
|
+
}, async ({ videos, audio, audioInMs, audioFadeOutMs, audioVolume, videoAudioVolume, saveToFileName, loopAudio, addSubtitles, subtitlesContext, }, context) => {
|
|
2758
2806
|
try {
|
|
2759
2807
|
// 验证session状态
|
|
2760
2808
|
const currentSession = await validateSession('audio-video-sync');
|
|
@@ -2788,6 +2836,7 @@ server.registerTool('audio-video-sync', {
|
|
|
2788
2836
|
videoAudioVolume,
|
|
2789
2837
|
loopAudio,
|
|
2790
2838
|
subtitles: addSubtitles,
|
|
2839
|
+
subtitlesContext,
|
|
2791
2840
|
});
|
|
2792
2841
|
if (result.url) {
|
|
2793
2842
|
console.log('Audio sync completed successfully');
|
|
@@ -2835,12 +2884,7 @@ server.registerTool('generate-video-by-ref', {
|
|
|
2835
2884
|
inputSchema: {
|
|
2836
2885
|
prompt: zod_1.z
|
|
2837
2886
|
.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.'),
|
|
2887
|
+
.describe('The prompt to generate video with or without reference images. 尽量忠于用户的原始需求,除非用户明确要求协助优化,否则不要擅自发挥!'),
|
|
2844
2888
|
referenceImages: zod_1.z
|
|
2845
2889
|
.array(zod_1.z.object({
|
|
2846
2890
|
name: zod_1.z
|
|
@@ -2854,18 +2898,30 @@ server.registerTool('generate-video-by-ref', {
|
|
|
2854
2898
|
.optional()
|
|
2855
2899
|
.default([])
|
|
2856
2900
|
.describe('Array of reference image objects with name, url and type. Can be empty for text-only generation.'),
|
|
2901
|
+
referenceVideo: zod_1.z
|
|
2902
|
+
.object({
|
|
2903
|
+
name: zod_1.z
|
|
2904
|
+
.string()
|
|
2905
|
+
.describe('Reference video file name in materials directory'),
|
|
2906
|
+
fileName: zod_1.z.string().describe('Reference video file name'),
|
|
2907
|
+
type: zod_1.z.enum(['video']).describe('Type of reference: video'),
|
|
2908
|
+
})
|
|
2909
|
+
.optional()
|
|
2910
|
+
.describe('Reference video file name in materials directory, 用于参考视频动作和特效,只有vidu-pro模型支持'),
|
|
2857
2911
|
duration: zod_1.z
|
|
2858
2912
|
.number()
|
|
2859
2913
|
.min(0)
|
|
2860
2914
|
.max(16)
|
|
2861
2915
|
.optional()
|
|
2862
2916
|
.default(5)
|
|
2863
|
-
.describe('The duration of the video in seconds
|
|
2917
|
+
.describe('The duration of the video in seconds.可以传0,此时会根据视频提示词内容自动确定时长'),
|
|
2864
2918
|
aspectRatio: zod_1.z
|
|
2865
2919
|
.enum(['16:9', '9:16'])
|
|
2920
|
+
.default('16:9')
|
|
2866
2921
|
.describe('The aspect ratio of the video.'),
|
|
2867
2922
|
resolution: zod_1.z
|
|
2868
2923
|
.enum(['720p', '1080p'])
|
|
2924
|
+
.default('720p')
|
|
2869
2925
|
.describe('The resolution of the video.'),
|
|
2870
2926
|
type: zod_1.z
|
|
2871
2927
|
.enum([
|
|
@@ -2875,6 +2931,7 @@ server.registerTool('generate-video-by-ref', {
|
|
|
2875
2931
|
'veo3.1',
|
|
2876
2932
|
'veo3.1-pro',
|
|
2877
2933
|
'vidu',
|
|
2934
|
+
'vidu-pro',
|
|
2878
2935
|
'vidu-uc',
|
|
2879
2936
|
'pixv',
|
|
2880
2937
|
])
|
|
@@ -2897,42 +2954,30 @@ server.registerTool('generate-video-by-ref', {
|
|
|
2897
2954
|
.min(1)
|
|
2898
2955
|
.optional()
|
|
2899
2956
|
.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
2957
|
optimizePrompt: zod_1.z
|
|
2915
2958
|
.boolean()
|
|
2916
2959
|
.optional()
|
|
2917
2960
|
.default(false)
|
|
2918
2961
|
.describe('Whether to optimize the prompt.'),
|
|
2919
2962
|
},
|
|
2920
|
-
}, async ({ prompt,
|
|
2963
|
+
}, async ({ prompt, referenceImages, duration, aspectRatio, resolution, type = 'vidu', mute, saveToFileName, sceneIndex, optimizePrompt, seed, referenceVideo, }, context) => {
|
|
2921
2964
|
try {
|
|
2965
|
+
const storyBoardFile = 'storyboard.json';
|
|
2922
2966
|
// 验证session状态
|
|
2923
2967
|
const currentSession = await validateSession('generate-video-by-ref');
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2968
|
+
checkModelEnabled(type);
|
|
2969
|
+
const storyBoardPath = (0, node_path_1.resolve)(projectLocalDir, storyBoardFile);
|
|
2970
|
+
if (type !== 'vidu-pro' && referenceVideo) {
|
|
2971
|
+
return createErrorResponse('只有vidu-pro模型支持参考视频', 'generate-video-by-ref');
|
|
2927
2972
|
}
|
|
2928
|
-
const outlineSheetImagePath = (0, node_path_1.resolve)(
|
|
2973
|
+
const outlineSheetImagePath = (0, node_path_1.resolve)(projectLocalDir, 'materials', 'outline_sheet.png');
|
|
2929
2974
|
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
2975
|
// 校验 prompt 与 storyboard.json 中分镜设定的一致性(如果提供了 sceneIndex)
|
|
2934
|
-
if (
|
|
2976
|
+
if (sceneIndex) {
|
|
2935
2977
|
try {
|
|
2978
|
+
if (hasOutlineSheet) {
|
|
2979
|
+
return createErrorResponse('监测到素材中存在outline_sheet.png这张图(由outline工具生成的),应采用 generate-video 图生视频。', 'generate-video-by-ref');
|
|
2980
|
+
}
|
|
2936
2981
|
if ((0, node_fs_1.existsSync)(storyBoardPath)) {
|
|
2937
2982
|
const storyBoardContent = await (0, promises_1.readFile)(storyBoardPath, 'utf8');
|
|
2938
2983
|
// 检查 storyBoard JSON 语法合法性
|
|
@@ -2948,7 +2993,7 @@ server.registerTool('generate-video-by-ref', {
|
|
|
2948
2993
|
if (scene) {
|
|
2949
2994
|
const videoPrompt = scene.video_prompt;
|
|
2950
2995
|
if (videoPrompt && prompt !== videoPrompt) {
|
|
2951
|
-
return createErrorResponse('视频提示词必须严格遵照storyboard
|
|
2996
|
+
return createErrorResponse('视频提示词必须严格遵照storyboard的设定', 'generate-video-by-ref');
|
|
2952
2997
|
}
|
|
2953
2998
|
// 检查 scene.is_continuous 是否为 true
|
|
2954
2999
|
if (scene.is_continuous === true) {
|
|
@@ -3072,7 +3117,6 @@ server.registerTool('generate-video-by-ref', {
|
|
|
3072
3117
|
console.log(`Generating video ${referenceImages.length > 0 ? `with ${referenceImages.length} reference image(s)` : 'without reference images'} using ${type} model...`);
|
|
3073
3118
|
// 处理参考图:转换为URL而不是base64
|
|
3074
3119
|
const referenceImageUrls = [];
|
|
3075
|
-
let promptPrefix = '';
|
|
3076
3120
|
for (const imageRef of referenceImages) {
|
|
3077
3121
|
// 使用 getMaterialUri 获取图片URL
|
|
3078
3122
|
const imageUrl = getMaterialUri(currentSession, imageRef.fileName);
|
|
@@ -3082,14 +3126,17 @@ server.registerTool('generate-video-by-ref', {
|
|
|
3082
3126
|
url: imageUrl,
|
|
3083
3127
|
});
|
|
3084
3128
|
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
3129
|
}
|
|
3092
|
-
const finalPrompt = `${
|
|
3130
|
+
const finalPrompt = `${prompt}`;
|
|
3131
|
+
const videos = referenceVideo
|
|
3132
|
+
? [
|
|
3133
|
+
{
|
|
3134
|
+
type: 'video',
|
|
3135
|
+
name: referenceVideo.name,
|
|
3136
|
+
url: getMaterialUri(currentSession, referenceVideo.fileName),
|
|
3137
|
+
},
|
|
3138
|
+
]
|
|
3139
|
+
: undefined;
|
|
3093
3140
|
// 调用 referencesToVideo 函数
|
|
3094
3141
|
const result = await currentSession.ai.referencesToVideo({
|
|
3095
3142
|
prompt: finalPrompt,
|
|
@@ -3100,10 +3147,12 @@ server.registerTool('generate-video-by-ref', {
|
|
|
3100
3147
|
type,
|
|
3101
3148
|
mute,
|
|
3102
3149
|
seed,
|
|
3150
|
+
videos,
|
|
3103
3151
|
onProgress: metaData => {
|
|
3104
3152
|
console.log('Video generation progress:', metaData);
|
|
3105
3153
|
sendProgress(context, metaData.progress || 0, 100, 'Generating video...');
|
|
3106
3154
|
},
|
|
3155
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
3107
3156
|
});
|
|
3108
3157
|
if (result.error) {
|
|
3109
3158
|
return createErrorResponse(result.error, 'generate-video-by-ref');
|
|
@@ -3229,6 +3278,7 @@ server.registerTool('extend-video-duration', {
|
|
|
3229
3278
|
onProgress: async (metaData) => {
|
|
3230
3279
|
sendProgress(context, ++progress, undefined, `Extension progress: ${Math.round(progress * 100)}%`);
|
|
3231
3280
|
},
|
|
3281
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
3232
3282
|
});
|
|
3233
3283
|
// 检查结果
|
|
3234
3284
|
if (!result || result.error) {
|
|
@@ -3305,7 +3355,7 @@ server.registerTool('use-template', {
|
|
|
3305
3355
|
}));
|
|
3306
3356
|
const validatedFileName = validateFileName(saveToFileName);
|
|
3307
3357
|
let completion = await ai.getCompletions({
|
|
3308
|
-
model: 'Doubao-Seed-1.
|
|
3358
|
+
model: 'Doubao-Seed-1.8',
|
|
3309
3359
|
messages: [
|
|
3310
3360
|
{
|
|
3311
3361
|
role: 'system',
|
|
@@ -3351,7 +3401,7 @@ ${user_request}
|
|
|
3351
3401
|
${JSON.stringify(materialUrls)}`;
|
|
3352
3402
|
// console.log(prompt);
|
|
3353
3403
|
completion = await ai.getCompletions({
|
|
3354
|
-
model: 'Doubao-Seed-1.
|
|
3404
|
+
model: 'Doubao-Seed-1.8',
|
|
3355
3405
|
messages: [
|
|
3356
3406
|
{
|
|
3357
3407
|
role: 'system',
|