cerevox 4.0.0-alpha.8 → 4.0.0-beta.1
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 +27 -13
- package/dist/core/ai.d.ts.map +1 -1
- package/dist/core/ai.js +143 -127
- 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 +9 -4
- package/dist/mcp/servers/prompts/skills//344/270/200/351/224/256/346/210/220/347/211/207.md +6 -5
- package/dist/mcp/servers/prompts/skills//345/210/206/351/225/234/345/270/210.md +11 -3
- 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 +290 -281
- 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);
|
|
@@ -322,6 +323,24 @@ async function listFiles(dir) {
|
|
|
322
323
|
const entries = await (0, promises_1.readdir)(dir, { withFileTypes: true });
|
|
323
324
|
return entries.filter(e => e.isFile()).map(e => (0, node_path_1.resolve)(dir, e.name));
|
|
324
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
|
+
}
|
|
325
344
|
// Create an MCP server
|
|
326
345
|
const server = new mcp_js_1.McpServer({
|
|
327
346
|
name: 'Cerevox Server',
|
|
@@ -332,18 +351,18 @@ const cerevox = new index_1.default({
|
|
|
332
351
|
logLevel: 'error',
|
|
333
352
|
});
|
|
334
353
|
let session = null;
|
|
335
|
-
let projectLocalDir = process.env.ZEROCUT_PROJECT_CWD ||
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
// 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
|
+
'.';
|
|
339
357
|
let closeSessionTimerId = null;
|
|
358
|
+
const CLIENT_TYPE = process.env.CLIENT_TYPE || 'trae';
|
|
340
359
|
// 注册 ZeroCut 指导规范 Prompt
|
|
341
360
|
server.registerPrompt('zerocut-guideline', {
|
|
342
361
|
title: 'ZeroCut 短视频创作指导规范',
|
|
343
362
|
description: '专业的短视频创作 Agent 指导规范,包含完整的工作流程、工具说明和质量建议',
|
|
344
363
|
}, async () => {
|
|
345
364
|
try {
|
|
346
|
-
const promptPath = (0, node_path_1.resolve)(__dirname, './prompts/zerocut-core
|
|
365
|
+
const promptPath = (0, node_path_1.resolve)(__dirname, './prompts/zerocut-core.md');
|
|
347
366
|
const promptContent = await (0, promises_1.readFile)(promptPath, 'utf-8');
|
|
348
367
|
return {
|
|
349
368
|
messages: [
|
|
@@ -364,25 +383,23 @@ server.registerPrompt('zerocut-guideline', {
|
|
|
364
383
|
});
|
|
365
384
|
server.registerTool('project-open', {
|
|
366
385
|
title: 'Open Project',
|
|
367
|
-
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.',
|
|
368
387
|
inputSchema: {
|
|
369
|
-
|
|
388
|
+
projectName: zod_1.z
|
|
370
389
|
.string()
|
|
371
|
-
.
|
|
372
|
-
.default('.')
|
|
373
|
-
.describe('The path of the file to upload.'),
|
|
390
|
+
.describe('项目名,命名规则如同命名文件,可以用空格,但不得使用特殊字符(如/、:等),如果你知晓当前目录,必须用当前目录的最后一级目录名作为项目名,否则根据用户需求自定义项目名'),
|
|
374
391
|
tosFiles: zod_1.z
|
|
375
392
|
.array(zod_1.z.string())
|
|
376
393
|
.optional()
|
|
377
394
|
.default([])
|
|
378
395
|
.describe('对象存储系统中的持久化文件,不通过本地直接下载到项目目录,可选参数'),
|
|
379
|
-
|
|
396
|
+
syncAllFiles: zod_1.z
|
|
380
397
|
.boolean()
|
|
381
398
|
.optional()
|
|
382
399
|
.default(false)
|
|
383
|
-
.describe('Whether to
|
|
400
|
+
.describe('Whether to sync all resources without filtering. If true, skips the smart filtering logic.'),
|
|
384
401
|
},
|
|
385
|
-
}, async ({
|
|
402
|
+
}, async ({ projectName, syncAllFiles, tosFiles }, context) => {
|
|
386
403
|
try {
|
|
387
404
|
if (closeSessionTimerId) {
|
|
388
405
|
clearTimeout(closeSessionTimerId);
|
|
@@ -390,12 +407,9 @@ server.registerTool('project-open', {
|
|
|
390
407
|
}
|
|
391
408
|
// 检查是否已有活跃session
|
|
392
409
|
if (session) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
// } catch (closeError) {
|
|
397
|
-
// console.warn('Failed to close previous session:', closeError);
|
|
398
|
-
// }
|
|
410
|
+
if (session.projectName !== projectName) {
|
|
411
|
+
return createErrorResponse('project-open', `Another project is already open: ${session.projectName},Ask user to close it first.`);
|
|
412
|
+
}
|
|
399
413
|
const result = {
|
|
400
414
|
success: true,
|
|
401
415
|
sessionId: session.id,
|
|
@@ -426,10 +440,20 @@ server.registerTool('project-open', {
|
|
|
426
440
|
if (!session) {
|
|
427
441
|
throw new Error('Failed to create Cerevox session');
|
|
428
442
|
}
|
|
443
|
+
// 保存项目名到session
|
|
444
|
+
session.projectName = projectName;
|
|
429
445
|
console.log('Initializing project...');
|
|
430
446
|
const workDir = await initProject(session);
|
|
431
|
-
|
|
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);
|
|
432
454
|
const syncDir = (0, node_path_1.resolve)(projectLocalDir, 'materials');
|
|
455
|
+
// 保证项目目录存在
|
|
456
|
+
await (0, promises_1.mkdir)(projectLocalDir, { recursive: true });
|
|
433
457
|
try {
|
|
434
458
|
await (0, promises_1.mkdir)(syncDir, { recursive: true });
|
|
435
459
|
}
|
|
@@ -441,39 +465,37 @@ server.registerTool('project-open', {
|
|
|
441
465
|
// 文件过滤逻辑
|
|
442
466
|
let filesToUpload = [];
|
|
443
467
|
let skippedFiles = [];
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
skippedFiles = filterResult.skippedFiles;
|
|
462
|
-
}
|
|
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;
|
|
463
485
|
}
|
|
464
486
|
const files = session.files;
|
|
465
487
|
let progress = 0;
|
|
466
|
-
const
|
|
488
|
+
const syncErrors = [];
|
|
467
489
|
const totalFiles = filesToUpload.length + tosFiles.length;
|
|
468
490
|
for (const material of filesToUpload) {
|
|
469
491
|
try {
|
|
470
492
|
await files.upload(material, `${workDir}/materials/${(0, node_path_1.basename)(material)}`);
|
|
471
493
|
await sendProgress(context, ++progress, totalFiles, material);
|
|
472
494
|
}
|
|
473
|
-
catch (
|
|
474
|
-
const errorMsg = `Failed to
|
|
495
|
+
catch (syncError) {
|
|
496
|
+
const errorMsg = `Failed to sync ${material}: ${syncError}`;
|
|
475
497
|
console.error(errorMsg);
|
|
476
|
-
|
|
498
|
+
syncErrors.push(errorMsg);
|
|
477
499
|
}
|
|
478
500
|
}
|
|
479
501
|
for (const tosFile of tosFiles) {
|
|
@@ -482,10 +504,10 @@ server.registerTool('project-open', {
|
|
|
482
504
|
await session.terminal.run(`wget -O ${workDir}/materials/${(0, node_path_1.basename)(url.pathname)} ${tosFile}`);
|
|
483
505
|
await sendProgress(context, ++progress, totalFiles, tosFile);
|
|
484
506
|
}
|
|
485
|
-
catch (
|
|
486
|
-
const errorMsg = `Failed to
|
|
507
|
+
catch (syncError) {
|
|
508
|
+
const errorMsg = `Failed to sync ${tosFile}: ${syncError}`;
|
|
487
509
|
console.error(errorMsg);
|
|
488
|
-
|
|
510
|
+
syncErrors.push(errorMsg);
|
|
489
511
|
}
|
|
490
512
|
}
|
|
491
513
|
const result = {
|
|
@@ -493,11 +515,12 @@ server.registerTool('project-open', {
|
|
|
493
515
|
nextActionSuggest: '检查规则上下文是否已召回,若未召回,调用 retrieve_rules 工具召回规则上下文',
|
|
494
516
|
sessionId: session.id,
|
|
495
517
|
workDir,
|
|
518
|
+
projectName,
|
|
496
519
|
projectLocalDir,
|
|
497
520
|
materials,
|
|
498
|
-
|
|
521
|
+
syncedFiles: filesToUpload.map(file => (0, node_path_1.basename)(file)),
|
|
499
522
|
skippedFiles: skippedFiles.map(file => (0, node_path_1.basename)(file)),
|
|
500
|
-
|
|
523
|
+
syncErrors: syncErrors.length > 0 ? syncErrors : undefined,
|
|
501
524
|
};
|
|
502
525
|
return {
|
|
503
526
|
content: [
|
|
@@ -523,7 +546,7 @@ server.registerTool('project-close', {
|
|
|
523
546
|
.min(0)
|
|
524
547
|
.max(20)
|
|
525
548
|
.default(5)
|
|
526
|
-
.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'),
|
|
527
550
|
},
|
|
528
551
|
}, async ({ inMinutes }) => {
|
|
529
552
|
try {
|
|
@@ -580,9 +603,9 @@ server.registerTool('retrieve-rules-context', {
|
|
|
580
603
|
}
|
|
581
604
|
else {
|
|
582
605
|
// 当 projectRulesFile 不存在时,设置 checkStoryboardFlag 为 false
|
|
583
|
-
checkStoryboardFlag = false;
|
|
606
|
+
// checkStoryboardFlag = false;
|
|
584
607
|
// 当 projectRulesFile 不存在时,设置 checkAudioVideoDurationFlag 为 false
|
|
585
|
-
checkAudioVideoDurationFlag = false;
|
|
608
|
+
// checkAudioVideoDurationFlag = false;
|
|
586
609
|
}
|
|
587
610
|
try {
|
|
588
611
|
const ai = currentSession.ai;
|
|
@@ -595,7 +618,8 @@ server.registerTool('retrieve-rules-context', {
|
|
|
595
618
|
await (0, promises_1.writeFile)(projectRulesFile, promptContent);
|
|
596
619
|
}
|
|
597
620
|
if (!(0, node_fs_1.existsSync)(skillsIndexFile)) {
|
|
598
|
-
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)));
|
|
599
623
|
const skillsList = skills.map((skill) => `## ${skill.name}
|
|
600
624
|
${skill.trigger}
|
|
601
625
|
`);
|
|
@@ -638,8 +662,8 @@ server.registerTool('upload-custom-material', {
|
|
|
638
662
|
try {
|
|
639
663
|
// 验证session状态
|
|
640
664
|
const currentSession = await validateSession('upload-custom-material');
|
|
641
|
-
// 构建本地文件路径,使用
|
|
642
|
-
const validatedPath = (0, node_path_1.resolve)(
|
|
665
|
+
// 构建本地文件路径,使用 projectLocalDir
|
|
666
|
+
const validatedPath = (0, node_path_1.resolve)(projectLocalDir, 'materials', localFileName.trim());
|
|
643
667
|
// 验证本地文件存在性
|
|
644
668
|
if (!(0, node_fs_1.existsSync)(validatedPath)) {
|
|
645
669
|
throw new Error(`File not found: ${validatedPath}`);
|
|
@@ -713,7 +737,7 @@ server.registerTool('upload-custom-material', {
|
|
|
713
737
|
}
|
|
714
738
|
});
|
|
715
739
|
server.registerTool('wait-for-task-finish', {
|
|
716
|
-
title: 'Wait Workflow or VideoTask Done;只有正在运行Coze工作流或者有异步生成视频任务时才需要执行这个工具;⚠️
|
|
740
|
+
title: 'Wait Workflow or VideoTask Done;只有正在运行Coze工作流或者有异步生成视频任务时才需要执行这个工具;⚠️ 如果执行这个工具未失败只是超时,你应立即再次重新调用,以继续等待直到任务完成或失败;‼️ 有的任务执行时间需要很长,所以如果只是超时不是失败,重试多少次都是正常的,请耐心等待即可。',
|
|
717
741
|
description: 'Wait for a workflow to complete.',
|
|
718
742
|
inputSchema: {
|
|
719
743
|
taskUrl: zod_1.z
|
|
@@ -769,6 +793,32 @@ server.registerTool('wait-for-task-finish', {
|
|
|
769
793
|
catch (error) {
|
|
770
794
|
console.warn(`Failed to update media_logs.json for ${validatedFileName}:`, error);
|
|
771
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
|
+
}
|
|
772
822
|
return {
|
|
773
823
|
content: [
|
|
774
824
|
{
|
|
@@ -841,6 +891,7 @@ server.registerTool('generate-character-image', {
|
|
|
841
891
|
try {
|
|
842
892
|
// 验证session状态
|
|
843
893
|
const currentSession = await validateSession('generate-character-image');
|
|
894
|
+
checkModelEnabled(type);
|
|
844
895
|
const validatedFileName = validateFileName(saveToFileName);
|
|
845
896
|
// 根据 isTurnaround 参数生成不同的提示词和尺寸
|
|
846
897
|
let prompt;
|
|
@@ -934,7 +985,7 @@ ${roleDescriptionPrompt}
|
|
|
934
985
|
? referenceImage
|
|
935
986
|
: `./materials/${referenceImage}`;
|
|
936
987
|
// 需要得到当前项目的绝对路径
|
|
937
|
-
const imageFilePath = (0, node_path_1.resolve)(
|
|
988
|
+
const imageFilePath = (0, node_path_1.resolve)(projectLocalDir, imagePath);
|
|
938
989
|
// 读取图片文件内容
|
|
939
990
|
const imageBuffer = await (0, promises_1.readFile)(imageFilePath);
|
|
940
991
|
const fileName = (0, node_path_1.basename)(imagePath);
|
|
@@ -1007,7 +1058,7 @@ ${roleDescriptionPrompt}
|
|
|
1007
1058
|
});
|
|
1008
1059
|
server.registerTool('generate-image', {
|
|
1009
1060
|
title: 'Generate Image',
|
|
1010
|
-
description:
|
|
1061
|
+
description: `生成图片`,
|
|
1011
1062
|
inputSchema: {
|
|
1012
1063
|
type: zod_1.z
|
|
1013
1064
|
.enum([
|
|
@@ -1028,11 +1079,6 @@ server.registerTool('generate-image', {
|
|
|
1028
1079
|
.min(1)
|
|
1029
1080
|
.optional()
|
|
1030
1081
|
.describe('分镜索引,从1开始的下标,如果非分镜对应素材,则可不传,分镜素材必传'),
|
|
1031
|
-
storyBoardFile: zod_1.z
|
|
1032
|
-
.string()
|
|
1033
|
-
.optional()
|
|
1034
|
-
.default('storyboard.json')
|
|
1035
|
-
.describe('故事板文件路径'),
|
|
1036
1082
|
size: zod_1.z
|
|
1037
1083
|
.enum([
|
|
1038
1084
|
'1024x1024',
|
|
@@ -1117,12 +1163,14 @@ server.registerTool('generate-image', {
|
|
|
1117
1163
|
\`\`\`
|
|
1118
1164
|
`),
|
|
1119
1165
|
},
|
|
1120
|
-
}, async ({ type = 'seedream', prompt, sceneIndex,
|
|
1166
|
+
}, async ({ type = 'seedream', prompt, sceneIndex, size = '720x1280', imageCount = 1, saveToFileNames, watermark, referenceImages, }, context) => {
|
|
1121
1167
|
try {
|
|
1168
|
+
const storyBoardFile = 'storyboard.json';
|
|
1122
1169
|
// 验证session状态
|
|
1123
1170
|
const currentSession = await validateSession('generate-image');
|
|
1124
|
-
|
|
1125
|
-
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');
|
|
1126
1174
|
const hasOutlineSheet = (0, node_fs_1.existsSync)(outlineSheetImagePath);
|
|
1127
1175
|
// 校验 prompt 与 storyboard.json 中分镜设定的一致性
|
|
1128
1176
|
if (sceneIndex) {
|
|
@@ -1228,7 +1276,7 @@ server.registerTool('generate-image', {
|
|
|
1228
1276
|
? refImage.image
|
|
1229
1277
|
: `./materials/${refImage.image}`;
|
|
1230
1278
|
// 需要得到当前项目的绝对路径
|
|
1231
|
-
const imageFilePath = (0, node_path_1.resolve)(
|
|
1279
|
+
const imageFilePath = (0, node_path_1.resolve)(projectLocalDir, imagePath);
|
|
1232
1280
|
try {
|
|
1233
1281
|
// 直接读取本地文件
|
|
1234
1282
|
if (!(0, node_fs_1.existsSync)(imageFilePath)) {
|
|
@@ -1289,17 +1337,13 @@ ${processedPrompt}`.trim();
|
|
|
1289
1337
|
}
|
|
1290
1338
|
}
|
|
1291
1339
|
const ai = currentSession.ai;
|
|
1292
|
-
|
|
1340
|
+
let progress = 0;
|
|
1341
|
+
const res = await ai.generateImage({
|
|
1293
1342
|
type,
|
|
1294
1343
|
prompt: processedPrompt,
|
|
1295
1344
|
size,
|
|
1296
1345
|
watermark,
|
|
1297
1346
|
image: imageBase64Array,
|
|
1298
|
-
async: true,
|
|
1299
|
-
});
|
|
1300
|
-
let progress = 0;
|
|
1301
|
-
const res = await ai.waitForTaskComplete({
|
|
1302
|
-
taskUrl,
|
|
1303
1347
|
onProgress: async (metaData) => {
|
|
1304
1348
|
try {
|
|
1305
1349
|
await sendProgress(context, ++progress, undefined, JSON.stringify(metaData));
|
|
@@ -1312,7 +1356,21 @@ ${processedPrompt}`.trim();
|
|
|
1312
1356
|
if (!res) {
|
|
1313
1357
|
throw new Error('Failed to generate image: no response from AI service');
|
|
1314
1358
|
}
|
|
1315
|
-
if (res.
|
|
1359
|
+
if (res.taskUrl) {
|
|
1360
|
+
return {
|
|
1361
|
+
content: [
|
|
1362
|
+
{
|
|
1363
|
+
type: 'text',
|
|
1364
|
+
text: JSON.stringify({
|
|
1365
|
+
success: true,
|
|
1366
|
+
message: '该任务正在运行中,它是异步任务,且执行时间较长,你应立即调用工具 wait-for-task-finish (saveFileName=outline_sheet.png) 来等待任务结束,如 wait-for-task-finish 工具调用超时,你应立即再次重新调用直到任务结束。',
|
|
1367
|
+
taskUrl: res.taskUrl,
|
|
1368
|
+
}),
|
|
1369
|
+
},
|
|
1370
|
+
],
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
else if (res.urls && res.urls.length > 0) {
|
|
1316
1374
|
console.log('Image generated successfully, saving to materials...');
|
|
1317
1375
|
let uris = [];
|
|
1318
1376
|
if (res.urls.length === 1 && res.urls[0]) {
|
|
@@ -1380,7 +1438,7 @@ server.registerTool('edit-image', {
|
|
|
1380
1438
|
type: zod_1.z
|
|
1381
1439
|
.enum(['banana-pro', 'banana', 'seedream', 'seedream-pro'])
|
|
1382
1440
|
.optional()
|
|
1383
|
-
.default('seedream')
|
|
1441
|
+
.default('seedream-pro')
|
|
1384
1442
|
.describe('The type of image model to use.'),
|
|
1385
1443
|
sourceImageFileName: zod_1.z.string().describe('The source image file name.'),
|
|
1386
1444
|
saveToFileName: zod_1.z
|
|
@@ -1437,7 +1495,7 @@ server.registerTool('edit-image', {
|
|
|
1437
1495
|
? sourceImageFileName
|
|
1438
1496
|
: `./materials/${sourceImageFileName}`;
|
|
1439
1497
|
// 需要得到当前项目的绝对路径
|
|
1440
|
-
const imageFilePath = (0, node_path_1.resolve)(
|
|
1498
|
+
const imageFilePath = (0, node_path_1.resolve)(projectLocalDir, imagePath);
|
|
1441
1499
|
if (!(0, node_fs_1.existsSync)(imageFilePath)) {
|
|
1442
1500
|
return createErrorResponse(`Reference image not found: ${imageFilePath}`, 'edit-image');
|
|
1443
1501
|
}
|
|
@@ -1505,9 +1563,9 @@ server.registerTool('edit-image', {
|
|
|
1505
1563
|
return createErrorResponse(error, 'edit-image');
|
|
1506
1564
|
}
|
|
1507
1565
|
});
|
|
1508
|
-
server.registerTool('generate-
|
|
1509
|
-
title: 'Generate
|
|
1510
|
-
description: `根据用户描述生成短视频的大纲;执行本工具会自动创建两个文件,一个是storyboard.json,一个是outline_sheet.png
|
|
1566
|
+
server.registerTool('generate-video-outlines', {
|
|
1567
|
+
title: 'Generate Video Outlines',
|
|
1568
|
+
description: `根据用户描述生成短视频的大纲;执行本工具会自动创建两个文件,一个是storyboard.json,一个是outline_sheet.png,前者是分镜设置,后者是分镜宫格图,其中outline_sheet.png生成于materials目录下,storyboard.json生成于materials的上级目录。`,
|
|
1511
1569
|
inputSchema: {
|
|
1512
1570
|
prompt: zod_1.z
|
|
1513
1571
|
.string()
|
|
@@ -1534,6 +1592,7 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1534
1592
|
'vidu-turbo',
|
|
1535
1593
|
'vidu-pro',
|
|
1536
1594
|
'kling',
|
|
1595
|
+
'wan',
|
|
1537
1596
|
'pixv',
|
|
1538
1597
|
'veo3.1',
|
|
1539
1598
|
'veo3.1-pro',
|
|
@@ -1543,10 +1602,11 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1543
1602
|
.default('pro')
|
|
1544
1603
|
.describe('除非用户主动提出使用其他模型,否则一律用pro模型'),
|
|
1545
1604
|
},
|
|
1546
|
-
}, async ({ prompt, voiceType, language, images, orientation, model }) => {
|
|
1605
|
+
}, async ({ prompt, voiceType, language, images, orientation, model }, context) => {
|
|
1547
1606
|
try {
|
|
1548
1607
|
// 验证session状态
|
|
1549
|
-
const currentSession = await validateSession('generate-
|
|
1608
|
+
const currentSession = await validateSession('generate-video-outlines');
|
|
1609
|
+
let progress = 0;
|
|
1550
1610
|
const ai = currentSession.ai;
|
|
1551
1611
|
const res = await ai.generateShortVideoOutlines({
|
|
1552
1612
|
prompt,
|
|
@@ -1555,6 +1615,15 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1555
1615
|
images,
|
|
1556
1616
|
videoModel: model,
|
|
1557
1617
|
aspectRatio: orientation === 'portrait' ? '9:16' : '16:9',
|
|
1618
|
+
onProgress: async (metaData) => {
|
|
1619
|
+
try {
|
|
1620
|
+
await sendProgress(context, ++progress, undefined, JSON.stringify(metaData));
|
|
1621
|
+
}
|
|
1622
|
+
catch (progressError) {
|
|
1623
|
+
console.warn('Failed to send progress update:', progressError);
|
|
1624
|
+
}
|
|
1625
|
+
},
|
|
1626
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
1558
1627
|
});
|
|
1559
1628
|
if (!res) {
|
|
1560
1629
|
throw new Error('Failed to generate short video outlines: no response from AI service');
|
|
@@ -1566,7 +1635,7 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1566
1635
|
type: 'text',
|
|
1567
1636
|
text: JSON.stringify({
|
|
1568
1637
|
success: true,
|
|
1569
|
-
message: '该任务正在运行中,它是异步任务,且执行时间较长,你应立即调用工具 wait-for-task-finish 来等待任务结束,如 wait-for-task-finish 工具调用超时,你应立即再次重新调用直到任务结束。',
|
|
1638
|
+
message: '该任务正在运行中,它是异步任务,且执行时间较长,你应立即调用工具 wait-for-task-finish (saveFileName=outline_sheet.png) 来等待任务结束,如 wait-for-task-finish 工具调用超时,你应立即再次重新调用直到任务结束。',
|
|
1570
1639
|
taskUrl: res.taskUrl,
|
|
1571
1640
|
}),
|
|
1572
1641
|
},
|
|
@@ -1576,7 +1645,7 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1576
1645
|
else if (res.url) {
|
|
1577
1646
|
const url = res.url;
|
|
1578
1647
|
await saveMaterial(currentSession, url, 'outline_sheet.png');
|
|
1579
|
-
const { scenes, video_type, voice_type, voiceover_tone, bgm_prompt } = res.data || {};
|
|
1648
|
+
const { scenes, video_type, voice_type, voiceover_tone, bgm_prompt, video_model, } = res.data || {};
|
|
1580
1649
|
const seed = (0, seed_1.getRandomSeed)();
|
|
1581
1650
|
const storyboard = {
|
|
1582
1651
|
orientation,
|
|
@@ -1586,19 +1655,13 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1586
1655
|
voice_type,
|
|
1587
1656
|
scenes: scenes.map((scene) => {
|
|
1588
1657
|
let video_prompt = scene.video_prompt;
|
|
1589
|
-
if (video_prompt.includes('画外音')
|
|
1590
|
-
video_prompt.
|
|
1591
|
-
if (voiceover_tone) {
|
|
1592
|
-
video_prompt = video_prompt.replace(/画外音[::]\s*“([^”]*)”/g, `画外音(${voiceover_tone})镜头内所有角色都不言语,从远处(镜头外)传来广播声<广播开始>$1——</广播结束>`);
|
|
1593
|
-
}
|
|
1594
|
-
else {
|
|
1595
|
-
video_prompt = video_prompt.replace(/画外音[::]\s*“([^”]*)”/g, `镜头内所有角色都不言语,从远处(镜头外)传来广播声<广播开始>$1——</广播结束>`);
|
|
1596
|
-
}
|
|
1658
|
+
if (voiceover_tone && video_prompt.includes('画外音')) {
|
|
1659
|
+
video_prompt = video_prompt.replace(/画外音[::]/g, `画外音(${voiceover_tone}):`);
|
|
1597
1660
|
}
|
|
1598
1661
|
return {
|
|
1599
1662
|
...scene,
|
|
1600
1663
|
video_prompt,
|
|
1601
|
-
use_video_model:
|
|
1664
|
+
use_video_model: video_model,
|
|
1602
1665
|
seed,
|
|
1603
1666
|
};
|
|
1604
1667
|
}),
|
|
@@ -1633,7 +1696,7 @@ server.registerTool('generate-short-video-outlines', {
|
|
|
1633
1696
|
};
|
|
1634
1697
|
}
|
|
1635
1698
|
catch (error) {
|
|
1636
|
-
return createErrorResponse(error, 'generate-
|
|
1699
|
+
return createErrorResponse(error, 'generate-video-outlines');
|
|
1637
1700
|
}
|
|
1638
1701
|
});
|
|
1639
1702
|
server.registerTool('generate-music-or-mv', {
|
|
@@ -1950,17 +2013,14 @@ server.registerTool('generate-video', {
|
|
|
1950
2013
|
title: 'Generate Video',
|
|
1951
2014
|
description: `图生视频和首尾帧生视频工具`,
|
|
1952
2015
|
inputSchema: {
|
|
1953
|
-
prompt: zod_1.z
|
|
2016
|
+
prompt: zod_1.z
|
|
2017
|
+
.string()
|
|
2018
|
+
.describe('The prompt to generate. 尽量忠于用户的原始需求,除非用户明确要求协助优化,否则不要擅自发挥!'),
|
|
1954
2019
|
sceneIndex: zod_1.z
|
|
1955
2020
|
.number()
|
|
1956
2021
|
.min(1)
|
|
1957
2022
|
.optional()
|
|
1958
2023
|
.describe('分镜索引,从1开始的下标,如果非分镜对应素材,则可不传,分镜素材必传'),
|
|
1959
|
-
storyBoardFile: zod_1.z
|
|
1960
|
-
.string()
|
|
1961
|
-
.optional()
|
|
1962
|
-
.default('storyboard.json')
|
|
1963
|
-
.describe('故事板文件路径'),
|
|
1964
2024
|
type: zod_1.z
|
|
1965
2025
|
.enum([
|
|
1966
2026
|
'pro',
|
|
@@ -2021,10 +2081,11 @@ server.registerTool('generate-video', {
|
|
|
2021
2081
|
.default(false)
|
|
2022
2082
|
.describe('Whether to optimize the prompt.'),
|
|
2023
2083
|
},
|
|
2024
|
-
}, async ({ prompt, sceneIndex,
|
|
2084
|
+
}, async ({ prompt, sceneIndex, saveToFileName, start_frame, end_frame, duration, resolution, type = 'vidu', optimizePrompt, saveLastFrameAs, mute = true, seed, }, context) => {
|
|
2025
2085
|
try {
|
|
2026
2086
|
// 验证session状态
|
|
2027
2087
|
const currentSession = await validateSession('generate-video');
|
|
2088
|
+
checkModelEnabled(type);
|
|
2028
2089
|
const isZeroModel = type.startsWith('zero');
|
|
2029
2090
|
if (!start_frame && !isZeroModel) {
|
|
2030
2091
|
return createErrorResponse('start_frame 不能为空', 'generate-video');
|
|
@@ -2042,10 +2103,11 @@ server.registerTool('generate-video', {
|
|
|
2042
2103
|
console.warn(`zero 模型的视频仅支持 1080p 分辨率,用户指定的分辨率为 %s,已自动将 ${resolution} 转换为 1080p`, resolution);
|
|
2043
2104
|
resolution = '1080p';
|
|
2044
2105
|
}
|
|
2106
|
+
const storyBoardFile = 'storyboard.json';
|
|
2045
2107
|
// 校验 prompt 与 storyboard.json 中分镜设定的一致性
|
|
2046
2108
|
if (sceneIndex) {
|
|
2047
2109
|
try {
|
|
2048
|
-
const storyBoardPath = (0, node_path_1.resolve)(
|
|
2110
|
+
const storyBoardPath = (0, node_path_1.resolve)(projectLocalDir, storyBoardFile);
|
|
2049
2111
|
if ((0, node_fs_1.existsSync)(storyBoardPath)) {
|
|
2050
2112
|
const storyBoardContent = await (0, promises_1.readFile)(storyBoardPath, 'utf8');
|
|
2051
2113
|
// 检查 storyBoard JSON 语法合法性
|
|
@@ -2068,7 +2130,7 @@ server.registerTool('generate-video', {
|
|
|
2068
2130
|
}
|
|
2069
2131
|
if (scene.video_duration != null &&
|
|
2070
2132
|
duration !== scene.video_duration) {
|
|
2071
|
-
return createErrorResponse(`视频时长必须严格遵照storyboard
|
|
2133
|
+
return createErrorResponse(`视频时长必须严格遵照storyboard的设定,storyboard 中设定的时长为 ${scene.video_duration} 秒。`, 'generate-video');
|
|
2072
2134
|
}
|
|
2073
2135
|
if (storyBoard.voice_type &&
|
|
2074
2136
|
storyBoard.voice_type !== 'slient') {
|
|
@@ -2214,7 +2276,7 @@ server.registerTool('generate-video', {
|
|
|
2214
2276
|
},
|
|
2215
2277
|
};
|
|
2216
2278
|
const analysisPayload = {
|
|
2217
|
-
model: 'Doubao-Seed-1.
|
|
2279
|
+
model: 'Doubao-Seed-1.8',
|
|
2218
2280
|
messages: [
|
|
2219
2281
|
{
|
|
2220
2282
|
role: 'system',
|
|
@@ -2349,7 +2411,7 @@ server.registerTool('generate-video', {
|
|
|
2349
2411
|
console.warn('Failed to send progress update:', progressError);
|
|
2350
2412
|
}
|
|
2351
2413
|
},
|
|
2352
|
-
waitForFinish: type !== 'zero',
|
|
2414
|
+
waitForFinish: type !== 'zero' && CLIENT_TYPE !== '5ire',
|
|
2353
2415
|
mute,
|
|
2354
2416
|
seed,
|
|
2355
2417
|
});
|
|
@@ -2432,44 +2494,83 @@ server.registerTool('edit-video', {
|
|
|
2432
2494
|
title: 'Edit Video',
|
|
2433
2495
|
description: `Edit video using Coze workflow,可以做以下事情:
|
|
2434
2496
|
|
|
2435
|
-
-
|
|
2497
|
+
- 编辑视频(增加、修改、删除视频中的内容),type 为 edit
|
|
2498
|
+
- 参考视频动作和特效,type 也为 edit
|
|
2436
2499
|
- 视频对口型,type 为 lipsync
|
|
2437
|
-
-
|
|
2500
|
+
- 视频延长 1~7 秒,type 为 extend
|
|
2501
|
+
- 视频超清,type 为 upscale
|
|
2502
|
+
|
|
2503
|
+
‼️ 故障排查
|
|
2504
|
+
如果出错,依次检查以下事项:
|
|
2505
|
+
|
|
2506
|
+
1. 视频是否太长(超过了8秒)或太大(超过了100M)
|
|
2507
|
+
2. 视频像素过小(小于128x128)或宽高比太悬殊(大于1:4)
|
|
2508
|
+
3. 视频格式是否是 mp4
|
|
2438
2509
|
`,
|
|
2439
2510
|
inputSchema: {
|
|
2440
2511
|
type: zod_1.z
|
|
2441
|
-
.enum(['
|
|
2512
|
+
.enum(['edit', 'lipsync', 'extend', 'upscale'])
|
|
2513
|
+
.default('edit')
|
|
2442
2514
|
.describe('The editing type'),
|
|
2443
2515
|
video: zod_1.z.string().describe(`The video to edit
|
|
2444
2516
|
|
|
2445
|
-
- type 为
|
|
2517
|
+
- type 为 edit 时,video 为要编辑的视频
|
|
2446
2518
|
- type 为 lipsync 时,video 为要对口型的视频
|
|
2447
|
-
- type 为
|
|
2519
|
+
- type 为 extend 时,video 为要延长的视频
|
|
2520
|
+
- type 为 upscale 时,video 为要转为超清的视频
|
|
2521
|
+
`),
|
|
2522
|
+
duration: zod_1.z
|
|
2523
|
+
.number()
|
|
2524
|
+
.min(0)
|
|
2525
|
+
.max(7)
|
|
2526
|
+
.optional()
|
|
2527
|
+
.describe(`The duration of the edited video in seconds. 0 表示与原视频相同。编辑视频时如用户未指定,默认为 0。延长视频时,最小1,最大7,默认值5。
|
|
2528
|
+
|
|
2529
|
+
- type 为 lipsync 或 upscale 时,忽略 duration 参数
|
|
2530
|
+
`),
|
|
2531
|
+
resolution: zod_1.z.enum(['720p', '1080p', '2K', '4K']).optional()
|
|
2532
|
+
.describe(`延长视频和超清视频时的分辨率(可选)
|
|
2533
|
+
|
|
2534
|
+
- type 为 edit 或 upscale 时,忽略 resolution 参数
|
|
2535
|
+
- type 为 extend 时,不传为 720p
|
|
2536
|
+
- type 为 upscale 时,不传为 1080p
|
|
2448
2537
|
`),
|
|
2449
2538
|
prompt: zod_1.z.string().optional()
|
|
2450
|
-
.describe(`The editing prompt,
|
|
2539
|
+
.describe(`The editing prompt, 如实转述用户需求即可
|
|
2540
|
+
|
|
2541
|
+
要求:
|
|
2542
|
+
1)用**极简的话语**准确描述,不添加其他任何补充信息,本工具会自己优化
|
|
2543
|
+
2)video一律用“视频1”指代
|
|
2451
2544
|
|
|
2452
|
-
- type 为
|
|
2545
|
+
- type 为 edit 时,prompt 为编辑指令
|
|
2453
2546
|
- type 为 lipsync 时,prompt 为空
|
|
2454
|
-
- type 为
|
|
2547
|
+
- type 为 extend 时,prompt 为延长提示词或空
|
|
2548
|
+
- type 为 upscale 时,prompt 为空
|
|
2455
2549
|
`),
|
|
2456
|
-
|
|
2457
|
-
.describe(`The reference image
|
|
2458
|
-
- type 为
|
|
2459
|
-
- type 为 lipsync 时,
|
|
2460
|
-
- type 为
|
|
2550
|
+
referenceImages: zod_1.z.array(zod_1.z.string()).optional()
|
|
2551
|
+
.describe(`The reference image Files for editing
|
|
2552
|
+
- type 为 edit 时,referenceImages 为参考图片(1-7张)
|
|
2553
|
+
- type 为 lipsync 时,referenceImages 为空
|
|
2554
|
+
- type 为 extend 时,referenceImages 为空或单张图,表示延长到尾帧的参考图像
|
|
2555
|
+
- type 为 upscale 时,referenceImages 为空
|
|
2461
2556
|
`),
|
|
2462
2557
|
saveToFileName: zod_1.z
|
|
2463
2558
|
.string()
|
|
2464
2559
|
.describe(`The file name to save the edited video to. 应该是mp4文件`),
|
|
2465
2560
|
},
|
|
2466
|
-
}, async ({ type, video, prompt,
|
|
2561
|
+
}, async ({ type, video, duration = 0, resolution, prompt, referenceImages, saveToFileName, }, context) => {
|
|
2467
2562
|
try {
|
|
2468
2563
|
const currentSession = await validateSession('edit-video');
|
|
2469
2564
|
const ai = currentSession.ai;
|
|
2470
2565
|
const validatedFileName = validateFileName(saveToFileName);
|
|
2471
2566
|
let res = {};
|
|
2472
2567
|
let progress = 0;
|
|
2568
|
+
if (type === 'extend' && (resolution === '2K' || resolution === '4K')) {
|
|
2569
|
+
throw new Error('Extend type only supports 720p resolution or 1080p resolution');
|
|
2570
|
+
}
|
|
2571
|
+
if (type === 'upscale' && resolution === '720p') {
|
|
2572
|
+
throw new Error('720p resolution is not supported for upscale type');
|
|
2573
|
+
}
|
|
2473
2574
|
const onProgress = async (metaData) => {
|
|
2474
2575
|
console.log('Replace video progress:', metaData);
|
|
2475
2576
|
try {
|
|
@@ -2479,20 +2580,25 @@ server.registerTool('edit-video', {
|
|
|
2479
2580
|
console.warn('Failed to send progress update:', progressError);
|
|
2480
2581
|
}
|
|
2481
2582
|
};
|
|
2482
|
-
let
|
|
2483
|
-
if (
|
|
2484
|
-
|
|
2583
|
+
let referenceImageUrls = undefined;
|
|
2584
|
+
if (referenceImages) {
|
|
2585
|
+
referenceImageUrls = referenceImages.map(fileName => getMaterialUri(currentSession, fileName));
|
|
2485
2586
|
}
|
|
2486
2587
|
const videoUrl = getMaterialUri(currentSession, video);
|
|
2487
|
-
if (type === '
|
|
2588
|
+
if (type === 'edit') {
|
|
2488
2589
|
if (!prompt) {
|
|
2489
|
-
throw new Error('prompt is required for
|
|
2590
|
+
throw new Error('prompt is required for edit type');
|
|
2490
2591
|
}
|
|
2491
|
-
res = await ai.
|
|
2492
|
-
videoUrl,
|
|
2592
|
+
res = await ai.referencesToVideo({
|
|
2493
2593
|
prompt,
|
|
2494
|
-
|
|
2594
|
+
duration,
|
|
2595
|
+
type: 'vidu-pro', //'vidu', //'pixv', // 'lite', // 'sora2',
|
|
2596
|
+
reference_images: referenceImageUrls,
|
|
2597
|
+
videos: [videoUrl],
|
|
2495
2598
|
onProgress,
|
|
2599
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
2600
|
+
// aspect_ratio: '9:16',
|
|
2601
|
+
// mute: true,
|
|
2496
2602
|
});
|
|
2497
2603
|
}
|
|
2498
2604
|
else if (type === 'lipsync') {
|
|
@@ -2501,29 +2607,31 @@ server.registerTool('edit-video', {
|
|
|
2501
2607
|
videoUrl,
|
|
2502
2608
|
});
|
|
2503
2609
|
res = await ai.lipSync({
|
|
2504
|
-
type: 'pixv',
|
|
2505
2610
|
videoUrl,
|
|
2506
2611
|
audioUrl,
|
|
2507
2612
|
audioInMs: 0,
|
|
2508
2613
|
pad_audio: false,
|
|
2509
2614
|
onProgress,
|
|
2615
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
2510
2616
|
});
|
|
2511
2617
|
}
|
|
2512
|
-
else if (type === '
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
type: 'banana',
|
|
2520
|
-
image: referenceImageUrl ? [referenceImageUrl] : undefined,
|
|
2521
|
-
})).url;
|
|
2522
|
-
}
|
|
2523
|
-
res = await ai.actionImitation({
|
|
2524
|
-
videoUrl,
|
|
2525
|
-
imageUrl: referenceImageUrl,
|
|
2618
|
+
else if (type === 'extend') {
|
|
2619
|
+
res = await ai.extendVideo({
|
|
2620
|
+
video_url: videoUrl,
|
|
2621
|
+
resolution: resolution || '720p',
|
|
2622
|
+
prompt,
|
|
2623
|
+
duration: duration || 5,
|
|
2624
|
+
end_frame: referenceImageUrls?.[0],
|
|
2526
2625
|
onProgress,
|
|
2626
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
else if (type === 'upscale') {
|
|
2630
|
+
res = await ai.upscaleVideo({
|
|
2631
|
+
video_url: videoUrl,
|
|
2632
|
+
resolution,
|
|
2633
|
+
onProgress,
|
|
2634
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
2527
2635
|
});
|
|
2528
2636
|
}
|
|
2529
2637
|
if (res.url) {
|
|
@@ -2732,11 +2840,15 @@ server.registerTool('audio-video-sync', {
|
|
|
2732
2840
|
.describe('The volume of video audio. 0.0 to 2.0.'),
|
|
2733
2841
|
loopAudio: zod_1.z.boolean().optional().default(true),
|
|
2734
2842
|
addSubtitles: zod_1.z.boolean().optional().default(false),
|
|
2843
|
+
subtitlesContext: zod_1.z
|
|
2844
|
+
.string()
|
|
2845
|
+
.optional()
|
|
2846
|
+
.describe('字幕的参考上下文(非必需),用于提升字幕准确性'),
|
|
2735
2847
|
saveToFileName: zod_1.z
|
|
2736
2848
|
.string()
|
|
2737
2849
|
.describe('The filename to save the audio-video-synced video. 应该是mp4文件'),
|
|
2738
2850
|
},
|
|
2739
|
-
}, async ({ videos, audio, audioInMs, audioFadeOutMs, audioVolume, videoAudioVolume, saveToFileName, loopAudio, addSubtitles, }, context) => {
|
|
2851
|
+
}, async ({ videos, audio, audioInMs, audioFadeOutMs, audioVolume, videoAudioVolume, saveToFileName, loopAudio, addSubtitles, subtitlesContext, }, context) => {
|
|
2740
2852
|
try {
|
|
2741
2853
|
// 验证session状态
|
|
2742
2854
|
const currentSession = await validateSession('audio-video-sync');
|
|
@@ -2770,6 +2882,7 @@ server.registerTool('audio-video-sync', {
|
|
|
2770
2882
|
videoAudioVolume,
|
|
2771
2883
|
loopAudio,
|
|
2772
2884
|
subtitles: addSubtitles,
|
|
2885
|
+
subtitlesContext,
|
|
2773
2886
|
});
|
|
2774
2887
|
if (result.url) {
|
|
2775
2888
|
console.log('Audio sync completed successfully');
|
|
@@ -2817,12 +2930,7 @@ server.registerTool('generate-video-by-ref', {
|
|
|
2817
2930
|
inputSchema: {
|
|
2818
2931
|
prompt: zod_1.z
|
|
2819
2932
|
.string()
|
|
2820
|
-
.describe('The prompt to generate video with or without reference images. '),
|
|
2821
|
-
rewritePrompt: zod_1.z
|
|
2822
|
-
.boolean()
|
|
2823
|
-
.optional()
|
|
2824
|
-
.default(true)
|
|
2825
|
-
.describe('Whether to rewrite the prompt.'),
|
|
2933
|
+
.describe('The prompt to generate video with or without reference images. 尽量忠于用户的原始需求,除非用户明确要求协助优化,否则不要擅自发挥!'),
|
|
2826
2934
|
referenceImages: zod_1.z
|
|
2827
2935
|
.array(zod_1.z.object({
|
|
2828
2936
|
name: zod_1.z
|
|
@@ -2836,6 +2944,16 @@ server.registerTool('generate-video-by-ref', {
|
|
|
2836
2944
|
.optional()
|
|
2837
2945
|
.default([])
|
|
2838
2946
|
.describe('Array of reference image objects with name, url and type. Can be empty for text-only generation.'),
|
|
2947
|
+
referenceVideo: zod_1.z
|
|
2948
|
+
.object({
|
|
2949
|
+
name: zod_1.z
|
|
2950
|
+
.string()
|
|
2951
|
+
.describe('Reference video file name in materials directory'),
|
|
2952
|
+
fileName: zod_1.z.string().describe('Reference video file name'),
|
|
2953
|
+
type: zod_1.z.enum(['video']).describe('Type of reference: video'),
|
|
2954
|
+
})
|
|
2955
|
+
.optional()
|
|
2956
|
+
.describe('Reference video file name in materials directory, 用于参考视频动作和特效,只有vidu-pro模型支持'),
|
|
2839
2957
|
duration: zod_1.z
|
|
2840
2958
|
.number()
|
|
2841
2959
|
.min(0)
|
|
@@ -2845,9 +2963,11 @@ server.registerTool('generate-video-by-ref', {
|
|
|
2845
2963
|
.describe('The duration of the video in seconds.可以传0,此时会根据视频提示词内容自动确定时长'),
|
|
2846
2964
|
aspectRatio: zod_1.z
|
|
2847
2965
|
.enum(['16:9', '9:16'])
|
|
2966
|
+
.default('16:9')
|
|
2848
2967
|
.describe('The aspect ratio of the video.'),
|
|
2849
2968
|
resolution: zod_1.z
|
|
2850
2969
|
.enum(['720p', '1080p'])
|
|
2970
|
+
.default('720p')
|
|
2851
2971
|
.describe('The resolution of the video.'),
|
|
2852
2972
|
type: zod_1.z
|
|
2853
2973
|
.enum([
|
|
@@ -2857,6 +2977,7 @@ server.registerTool('generate-video-by-ref', {
|
|
|
2857
2977
|
'veo3.1',
|
|
2858
2978
|
'veo3.1-pro',
|
|
2859
2979
|
'vidu',
|
|
2980
|
+
'vidu-pro',
|
|
2860
2981
|
'vidu-uc',
|
|
2861
2982
|
'pixv',
|
|
2862
2983
|
])
|
|
@@ -2879,23 +3000,23 @@ server.registerTool('generate-video-by-ref', {
|
|
|
2879
3000
|
.min(1)
|
|
2880
3001
|
.optional()
|
|
2881
3002
|
.describe('分镜索引,从1开始的下标,如果非分镜对应素材,则可不传,分镜素材必传'),
|
|
2882
|
-
storyBoardFile: zod_1.z
|
|
2883
|
-
.string()
|
|
2884
|
-
.optional()
|
|
2885
|
-
.default('storyboard.json')
|
|
2886
|
-
.describe('故事板文件路径'),
|
|
2887
3003
|
optimizePrompt: zod_1.z
|
|
2888
3004
|
.boolean()
|
|
2889
3005
|
.optional()
|
|
2890
3006
|
.default(false)
|
|
2891
3007
|
.describe('Whether to optimize the prompt.'),
|
|
2892
3008
|
},
|
|
2893
|
-
}, async ({ prompt,
|
|
3009
|
+
}, async ({ prompt, referenceImages, duration, aspectRatio, resolution, type = 'vidu', mute, saveToFileName, sceneIndex, optimizePrompt, seed, referenceVideo, }, context) => {
|
|
2894
3010
|
try {
|
|
3011
|
+
const storyBoardFile = 'storyboard.json';
|
|
2895
3012
|
// 验证session状态
|
|
2896
3013
|
const currentSession = await validateSession('generate-video-by-ref');
|
|
2897
|
-
|
|
2898
|
-
const
|
|
3014
|
+
checkModelEnabled(type);
|
|
3015
|
+
const storyBoardPath = (0, node_path_1.resolve)(projectLocalDir, storyBoardFile);
|
|
3016
|
+
if (type !== 'vidu-pro' && referenceVideo) {
|
|
3017
|
+
return createErrorResponse('只有vidu-pro模型支持参考视频', 'generate-video-by-ref');
|
|
3018
|
+
}
|
|
3019
|
+
const outlineSheetImagePath = (0, node_path_1.resolve)(projectLocalDir, 'materials', 'outline_sheet.png');
|
|
2899
3020
|
const hasOutlineSheet = (0, node_fs_1.existsSync)(outlineSheetImagePath);
|
|
2900
3021
|
// 校验 prompt 与 storyboard.json 中分镜设定的一致性(如果提供了 sceneIndex)
|
|
2901
3022
|
if (sceneIndex) {
|
|
@@ -3042,7 +3163,6 @@ server.registerTool('generate-video-by-ref', {
|
|
|
3042
3163
|
console.log(`Generating video ${referenceImages.length > 0 ? `with ${referenceImages.length} reference image(s)` : 'without reference images'} using ${type} model...`);
|
|
3043
3164
|
// 处理参考图:转换为URL而不是base64
|
|
3044
3165
|
const referenceImageUrls = [];
|
|
3045
|
-
let promptPrefix = '';
|
|
3046
3166
|
for (const imageRef of referenceImages) {
|
|
3047
3167
|
// 使用 getMaterialUri 获取图片URL
|
|
3048
3168
|
const imageUrl = getMaterialUri(currentSession, imageRef.fileName);
|
|
@@ -3052,14 +3172,17 @@ server.registerTool('generate-video-by-ref', {
|
|
|
3052
3172
|
url: imageUrl,
|
|
3053
3173
|
});
|
|
3054
3174
|
console.log(`Added reference image URL: ${imageUrl} (name: ${imageRef.name}, type: ${imageRef.type})`);
|
|
3055
|
-
if (rewritePrompt) {
|
|
3056
|
-
promptPrefix += `参考“${imageRef.name}”(图${referenceImageUrls.length})${imageRef.type === 'subject' ? '主体形象' : '背景'}\n`;
|
|
3057
|
-
}
|
|
3058
|
-
}
|
|
3059
|
-
if (promptPrefix) {
|
|
3060
|
-
promptPrefix += '\n';
|
|
3061
3175
|
}
|
|
3062
|
-
const finalPrompt = `${
|
|
3176
|
+
const finalPrompt = `${prompt}`;
|
|
3177
|
+
const videos = referenceVideo
|
|
3178
|
+
? [
|
|
3179
|
+
{
|
|
3180
|
+
type: 'video',
|
|
3181
|
+
name: referenceVideo.name,
|
|
3182
|
+
url: getMaterialUri(currentSession, referenceVideo.fileName),
|
|
3183
|
+
},
|
|
3184
|
+
]
|
|
3185
|
+
: undefined;
|
|
3063
3186
|
// 调用 referencesToVideo 函数
|
|
3064
3187
|
const result = await currentSession.ai.referencesToVideo({
|
|
3065
3188
|
prompt: finalPrompt,
|
|
@@ -3070,10 +3193,12 @@ server.registerTool('generate-video-by-ref', {
|
|
|
3070
3193
|
type,
|
|
3071
3194
|
mute,
|
|
3072
3195
|
seed,
|
|
3196
|
+
videos,
|
|
3073
3197
|
onProgress: metaData => {
|
|
3074
3198
|
console.log('Video generation progress:', metaData);
|
|
3075
3199
|
sendProgress(context, metaData.progress || 0, 100, 'Generating video...');
|
|
3076
3200
|
},
|
|
3201
|
+
waitForFinish: CLIENT_TYPE !== '5ire',
|
|
3077
3202
|
});
|
|
3078
3203
|
if (result.error) {
|
|
3079
3204
|
return createErrorResponse(result.error, 'generate-video-by-ref');
|
|
@@ -3132,122 +3257,6 @@ server.registerTool('generate-video-by-ref', {
|
|
|
3132
3257
|
return createErrorResponse(error.message, 'generate-video-by-ref');
|
|
3133
3258
|
}
|
|
3134
3259
|
});
|
|
3135
|
-
server.registerTool('extend-video-duration', {
|
|
3136
|
-
title: 'Extend Video Duration',
|
|
3137
|
-
description: 'Extend any MP4 video in materials directory by 1-7 seconds using AI video extension technology.',
|
|
3138
|
-
inputSchema: {
|
|
3139
|
-
videoFileName: zod_1.z
|
|
3140
|
-
.string()
|
|
3141
|
-
.describe('The MP4 video file name in materials directory to extend.'),
|
|
3142
|
-
duration: zod_1.z
|
|
3143
|
-
.number()
|
|
3144
|
-
.min(1)
|
|
3145
|
-
.max(7)
|
|
3146
|
-
.default(3)
|
|
3147
|
-
.describe('Duration to extend the video in seconds (1-7).'),
|
|
3148
|
-
resolution: zod_1.z
|
|
3149
|
-
.enum(['720p', '1080p'])
|
|
3150
|
-
.default('720p')
|
|
3151
|
-
.describe('The resolution of the video.'),
|
|
3152
|
-
prompt: zod_1.z
|
|
3153
|
-
.string()
|
|
3154
|
-
.optional()
|
|
3155
|
-
.describe('Optional prompt to guide the video extension. If not provided, will use a default prompt.'),
|
|
3156
|
-
type: zod_1.z
|
|
3157
|
-
.enum(['turbo', 'pro'])
|
|
3158
|
-
.default('turbo')
|
|
3159
|
-
.describe('Model type for video extension. turbo is faster, pro is higher quality.'),
|
|
3160
|
-
endFrame: zod_1.z
|
|
3161
|
-
.string()
|
|
3162
|
-
.optional()
|
|
3163
|
-
.describe('Optional end frame image file name in materials directory to guide the video extension.'),
|
|
3164
|
-
saveToFileName: zod_1.z
|
|
3165
|
-
.string()
|
|
3166
|
-
.describe('The filename to save the extended video. 应该是mp4文件'),
|
|
3167
|
-
},
|
|
3168
|
-
}, async ({ videoFileName, duration, resolution, prompt, type = 'turbo', endFrame, saveToFileName, }, context) => {
|
|
3169
|
-
try {
|
|
3170
|
-
await validateSession('extend-video');
|
|
3171
|
-
validateFileName(videoFileName);
|
|
3172
|
-
validateFileName(saveToFileName);
|
|
3173
|
-
if (!session) {
|
|
3174
|
-
return createErrorResponse(new Error('No active session. Please open a project first.'), 'extend-video');
|
|
3175
|
-
}
|
|
3176
|
-
// 检查视频文件为MP4格式
|
|
3177
|
-
const fileExtension = node_path_1.default.extname(videoFileName).toLowerCase();
|
|
3178
|
-
if (fileExtension !== '.mp4') {
|
|
3179
|
-
return createErrorResponse(new Error(`Only MP4 files are supported. Found: ${fileExtension}`), 'extend-video');
|
|
3180
|
-
}
|
|
3181
|
-
const videoUri = getMaterialUri(session, videoFileName);
|
|
3182
|
-
// 处理结束帧参数
|
|
3183
|
-
let endFrameUri;
|
|
3184
|
-
if (endFrame) {
|
|
3185
|
-
validateFileName(endFrame);
|
|
3186
|
-
endFrameUri = getMaterialUri(session, endFrame);
|
|
3187
|
-
}
|
|
3188
|
-
// 设置默认提示词,用中文
|
|
3189
|
-
const defaultPrompt = `延长这视频自然平滑,保持相同的风格、照明和运动模式。保持视觉一致性,确保无缝过渡。`;
|
|
3190
|
-
const finalPrompt = prompt || defaultPrompt;
|
|
3191
|
-
let progress = 0;
|
|
3192
|
-
const result = await session.ai.extendVideo({
|
|
3193
|
-
type: type,
|
|
3194
|
-
video_url: videoUri,
|
|
3195
|
-
prompt: finalPrompt,
|
|
3196
|
-
duration,
|
|
3197
|
-
resolution,
|
|
3198
|
-
end_frame: endFrameUri,
|
|
3199
|
-
onProgress: async (metaData) => {
|
|
3200
|
-
sendProgress(context, ++progress, undefined, `Extension progress: ${Math.round(progress * 100)}%`);
|
|
3201
|
-
},
|
|
3202
|
-
});
|
|
3203
|
-
// 检查结果
|
|
3204
|
-
if (!result || result.error) {
|
|
3205
|
-
throw new Error(result?.error || 'Video extension failed');
|
|
3206
|
-
}
|
|
3207
|
-
// 检查返回的视频URL
|
|
3208
|
-
if (!result.url) {
|
|
3209
|
-
throw new Error('Failed to get extended video URL');
|
|
3210
|
-
}
|
|
3211
|
-
// 保存延长后的视频
|
|
3212
|
-
await saveMaterial(session, result.url, saveToFileName);
|
|
3213
|
-
// 更新媒体日志
|
|
3214
|
-
await updateMediaLogs(session, saveToFileName, {
|
|
3215
|
-
originalVideo: videoFileName,
|
|
3216
|
-
extensionDuration: duration,
|
|
3217
|
-
prompt: finalPrompt,
|
|
3218
|
-
modelType: type,
|
|
3219
|
-
endFrame: endFrame,
|
|
3220
|
-
extendedVideoUrl: result.url,
|
|
3221
|
-
...result,
|
|
3222
|
-
}, 'video');
|
|
3223
|
-
return {
|
|
3224
|
-
content: [
|
|
3225
|
-
{
|
|
3226
|
-
type: 'text',
|
|
3227
|
-
text: JSON.stringify({
|
|
3228
|
-
success: true,
|
|
3229
|
-
message: 'Video extended successfully',
|
|
3230
|
-
originalVideo: videoFileName,
|
|
3231
|
-
extendedVideo: saveToFileName,
|
|
3232
|
-
extensionDuration: duration,
|
|
3233
|
-
prompt: finalPrompt,
|
|
3234
|
-
modelType: type,
|
|
3235
|
-
extendedVideoUrl: result.url,
|
|
3236
|
-
details: {
|
|
3237
|
-
duration: result.duration || 'Unknown',
|
|
3238
|
-
resolution: result.resolution || 'Unknown',
|
|
3239
|
-
aspectRatio: result.ratio || 'Unknown',
|
|
3240
|
-
},
|
|
3241
|
-
}, null, 2),
|
|
3242
|
-
},
|
|
3243
|
-
],
|
|
3244
|
-
};
|
|
3245
|
-
}
|
|
3246
|
-
catch (error) {
|
|
3247
|
-
console.error('Error extending video:', error);
|
|
3248
|
-
return createErrorResponse(error, 'extend-video');
|
|
3249
|
-
}
|
|
3250
|
-
});
|
|
3251
3260
|
server.registerTool('use-template', {
|
|
3252
3261
|
title: 'Use Template',
|
|
3253
3262
|
description: 'Find a template that matches the user request, and use it to generate a new material.',
|
|
@@ -3275,7 +3284,7 @@ server.registerTool('use-template', {
|
|
|
3275
3284
|
}));
|
|
3276
3285
|
const validatedFileName = validateFileName(saveToFileName);
|
|
3277
3286
|
let completion = await ai.getCompletions({
|
|
3278
|
-
model: 'Doubao-Seed-1.
|
|
3287
|
+
model: 'Doubao-Seed-1.8',
|
|
3279
3288
|
messages: [
|
|
3280
3289
|
{
|
|
3281
3290
|
role: 'system',
|
|
@@ -3321,7 +3330,7 @@ ${user_request}
|
|
|
3321
3330
|
${JSON.stringify(materialUrls)}`;
|
|
3322
3331
|
// console.log(prompt);
|
|
3323
3332
|
completion = await ai.getCompletions({
|
|
3324
|
-
model: 'Doubao-Seed-1.
|
|
3333
|
+
model: 'Doubao-Seed-1.8',
|
|
3325
3334
|
messages: [
|
|
3326
3335
|
{
|
|
3327
3336
|
role: 'system',
|