cerevox 2.25.0 → 2.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/ai.d.ts +12 -0
- package/dist/core/ai.d.ts.map +1 -1
- package/dist/core/ai.js +64 -0
- package/dist/core/ai.js.map +1 -1
- package/dist/mcp/servers/prompts/rules/anime-series.md +6 -8
- package/dist/mcp/servers/prompts/rules/creative-ad.md +3 -4
- package/dist/mcp/servers/prompts/rules/general-video.md +6 -8
- package/dist/mcp/servers/prompts/rules/professional.md +8 -1
- package/dist/mcp/servers/prompts/rules/story-telling.md +3 -4
- package/dist/mcp/servers/zerocut.d.ts.map +1 -1
- package/dist/mcp/servers/zerocut.js +225 -63
- package/dist/mcp/servers/zerocut.js.map +1 -1
- package/dist/utils/videokit.js +4 -4
- package/dist/utils/videokit.js.map +1 -1
- package/package.json +2 -2
|
@@ -405,24 +405,9 @@ const cerevox = new index_1.default({
|
|
|
405
405
|
});
|
|
406
406
|
let session = null;
|
|
407
407
|
let projectLocalDir = '.';
|
|
408
|
-
let syncSubtitles = false;
|
|
409
408
|
let checkStoryboardFlag = false;
|
|
410
409
|
let checkAudioVideoDurationFlag = false;
|
|
411
410
|
let checkStoryboardSubtitlesFlag = false;
|
|
412
|
-
server.registerTool('zerocut-version', {
|
|
413
|
-
title: `ZeroCut Version`,
|
|
414
|
-
description: `ZeroCut: ${constants_1.VERSION}`,
|
|
415
|
-
inputSchema: {},
|
|
416
|
-
}, async () => {
|
|
417
|
-
return {
|
|
418
|
-
content: [
|
|
419
|
-
{
|
|
420
|
-
type: 'text',
|
|
421
|
-
text: constants_1.VERSION,
|
|
422
|
-
},
|
|
423
|
-
],
|
|
424
|
-
};
|
|
425
|
-
});
|
|
426
411
|
// 注册 ZeroCut 指导规范 Prompt
|
|
427
412
|
server.registerPrompt('zerocut-guideline', {
|
|
428
413
|
title: 'ZeroCut 短视频创作指导规范',
|
|
@@ -450,7 +435,7 @@ server.registerPrompt('zerocut-guideline', {
|
|
|
450
435
|
});
|
|
451
436
|
server.registerTool('retrieve-rules-context', {
|
|
452
437
|
title: 'ZeroCut Basic Rules',
|
|
453
|
-
description:
|
|
438
|
+
description: `ZeroCut-${constants_1.VERSION} Basic Rules`,
|
|
454
439
|
inputSchema: {
|
|
455
440
|
purpose: zod_1.z
|
|
456
441
|
.enum([
|
|
@@ -563,8 +548,6 @@ server.registerTool('zerocut-project-open', {
|
|
|
563
548
|
}
|
|
564
549
|
console.log('Initializing project...');
|
|
565
550
|
const workDir = await initProject(session);
|
|
566
|
-
// Initialize syncSubtitles flag
|
|
567
|
-
syncSubtitles = false;
|
|
568
551
|
projectLocalDir = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), localDir || '.');
|
|
569
552
|
const syncDir = (0, node_path_1.resolve)(projectLocalDir, 'materials');
|
|
570
553
|
try {
|
|
@@ -1454,6 +1437,24 @@ server.registerTool('edit-image', {
|
|
|
1454
1437
|
'832x1248',
|
|
1455
1438
|
'1248x832',
|
|
1456
1439
|
'1512x648',
|
|
1440
|
+
// 2K
|
|
1441
|
+
'2048x2048',
|
|
1442
|
+
'1728x2304',
|
|
1443
|
+
'2304x1728',
|
|
1444
|
+
'2560x1440',
|
|
1445
|
+
'1440x2560',
|
|
1446
|
+
'1664x2496',
|
|
1447
|
+
'2496x1664',
|
|
1448
|
+
'3024x1456',
|
|
1449
|
+
// 4K
|
|
1450
|
+
'4096x4096',
|
|
1451
|
+
'3072x4096',
|
|
1452
|
+
'4096x3072',
|
|
1453
|
+
'4096x2304',
|
|
1454
|
+
'2304x4096',
|
|
1455
|
+
'2731x4096',
|
|
1456
|
+
'4096x2731',
|
|
1457
|
+
'4096x1968',
|
|
1457
1458
|
])
|
|
1458
1459
|
.optional()
|
|
1459
1460
|
.describe('The size of the image.'),
|
|
@@ -2735,10 +2736,6 @@ server.registerTool('get-video-project-schema', {
|
|
|
2735
2736
|
inputSchema: {},
|
|
2736
2737
|
}, async () => {
|
|
2737
2738
|
try {
|
|
2738
|
-
// Check if syncSubtitles is false and show warning
|
|
2739
|
-
if (!syncSubtitles) {
|
|
2740
|
-
console.warn('Warning: syncSubtitles is false. Please call get-subtitle-contents first to sync subtitles.');
|
|
2741
|
-
}
|
|
2742
2739
|
const schemaPath = (0, node_path_1.resolve)(__dirname, '../../utils/videoproject-schema.json');
|
|
2743
2740
|
const schemaContent = await (0, promises_1.readFile)(schemaPath, 'utf-8');
|
|
2744
2741
|
const schema = JSON.parse(schemaContent);
|
|
@@ -2755,9 +2752,6 @@ server.registerTool('get-video-project-schema', {
|
|
|
2755
2752
|
* 在保证字幕文本内容与 story_board.json 中的 script(或dialog) 字段的文本内容完全一致的前提下,可根据 tts 返回的 \`captions.utterances\` 字段对字幕的显示进行优化,将过长的字幕分段显示,在 draft_content.json 中使用分段字幕,captions 的内容在 media_logs.json 中可查询到。
|
|
2756
2753
|
* 如用户未特殊指定,字幕样式(字体及大小)务必遵守输出规范
|
|
2757
2754
|
`,
|
|
2758
|
-
warning: !syncSubtitles
|
|
2759
|
-
? '⚠️ Please call get-subtitle-contents to sync subtitles before creating the draft_content.json file.'
|
|
2760
|
-
: undefined,
|
|
2761
2755
|
timestamp: new Date().toISOString(),
|
|
2762
2756
|
}),
|
|
2763
2757
|
},
|
|
@@ -2768,37 +2762,6 @@ server.registerTool('get-video-project-schema', {
|
|
|
2768
2762
|
return createErrorResponse(error, 'get-video-project-schema');
|
|
2769
2763
|
}
|
|
2770
2764
|
});
|
|
2771
|
-
server.registerTool('get-subtitle-contents', {
|
|
2772
|
-
title: 'Get Subtitle Contents',
|
|
2773
|
-
description: 'Get the subtitle contents from a story_board.json file.',
|
|
2774
|
-
inputSchema: {
|
|
2775
|
-
storyBoardFile: zod_1.z
|
|
2776
|
-
.string()
|
|
2777
|
-
.describe('The file path of the story_board.json file.'),
|
|
2778
|
-
},
|
|
2779
|
-
}, async ({ storyBoardFile }) => {
|
|
2780
|
-
syncSubtitles = true;
|
|
2781
|
-
try {
|
|
2782
|
-
const filePath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), storyBoardFile);
|
|
2783
|
-
if (!(0, node_fs_1.existsSync)(filePath)) {
|
|
2784
|
-
return createErrorResponse(`File not found: ${filePath}`, 'get-subtitle-contents');
|
|
2785
|
-
}
|
|
2786
|
-
const contents = await (0, promises_1.readFile)(filePath, 'utf-8');
|
|
2787
|
-
const data = JSON.parse(contents);
|
|
2788
|
-
const scripts = data.scenes.map((s) => s.script_translation || s.script || '');
|
|
2789
|
-
return {
|
|
2790
|
-
content: [
|
|
2791
|
-
{
|
|
2792
|
-
type: 'text',
|
|
2793
|
-
text: JSON.stringify({ scripts, from: storyBoardFile }),
|
|
2794
|
-
},
|
|
2795
|
-
],
|
|
2796
|
-
};
|
|
2797
|
-
}
|
|
2798
|
-
catch (error) {
|
|
2799
|
-
return createErrorResponse('提取台词失败,建议直接从 story_board.json 中提取(script_translation或script字段),请务必保持内容严格一致!', 'get-subtitle-contents');
|
|
2800
|
-
}
|
|
2801
|
-
});
|
|
2802
2765
|
server.registerTool('get-storyboard-schema', {
|
|
2803
2766
|
title: 'Get Storyboard Schema',
|
|
2804
2767
|
description: 'Get the complete Storyboard JSON Schema definition.',
|
|
@@ -3040,13 +3003,100 @@ server.registerTool('media-analyzer', {
|
|
|
3040
3003
|
];
|
|
3041
3004
|
}
|
|
3042
3005
|
else if (fileExtension === 'mp3') {
|
|
3043
|
-
// 音频文件 -
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3006
|
+
// 音频文件 - 生成字幕并进行分析
|
|
3007
|
+
try {
|
|
3008
|
+
// 调用AI服务生成字幕
|
|
3009
|
+
const ai = currentSession.ai;
|
|
3010
|
+
const captionsResult = await ai.voiceToCaptions({
|
|
3011
|
+
url: mediaUrl,
|
|
3012
|
+
});
|
|
3013
|
+
console.log(mediaUrl, captionsResult);
|
|
3014
|
+
if (!captionsResult || !captionsResult.utterances) {
|
|
3015
|
+
throw new Error('Failed to generate captions from audio');
|
|
3016
|
+
}
|
|
3017
|
+
// 生成字幕文件名
|
|
3018
|
+
const audioFileNameWithoutExt = mediaFileName.replace(/\.[^/.]+$/, '');
|
|
3019
|
+
const captionsFileName = `${audioFileNameWithoutExt}_captions.json`;
|
|
3020
|
+
// 保存字幕文件到本地材料目录
|
|
3021
|
+
const localDir = node_path_1.default.resolve(projectLocalDir, 'materials');
|
|
3022
|
+
if (!(0, node_fs_1.existsSync)(localDir)) {
|
|
3023
|
+
(0, node_fs_1.mkdirSync)(localDir, { recursive: true });
|
|
3024
|
+
}
|
|
3025
|
+
const captionsFilePath = node_path_1.default.join(localDir, captionsFileName);
|
|
3026
|
+
await (0, promises_1.writeFile)(captionsFilePath, JSON.stringify(captionsResult, null, 2), 'utf-8');
|
|
3027
|
+
// 提取字幕文本内容用于分析
|
|
3028
|
+
const captionsText = captionsResult.utterances
|
|
3029
|
+
.map((caption) => caption.text)
|
|
3030
|
+
.join(' ');
|
|
3031
|
+
// 构建包含字幕内容的分析提示
|
|
3032
|
+
messageContent = [
|
|
3033
|
+
{
|
|
3034
|
+
type: 'text',
|
|
3035
|
+
text: `${userPrompt}
|
|
3036
|
+
|
|
3037
|
+
音频文件:${mediaFileName}
|
|
3038
|
+
字幕内容:${captionsText}
|
|
3039
|
+
|
|
3040
|
+
请基于音频的字幕内容进行详细分析,包括:
|
|
3041
|
+
1. 内容主题和核心信息
|
|
3042
|
+
2. 语言风格和表达方式
|
|
3043
|
+
3. 情感色彩和语调特点
|
|
3044
|
+
4. 适用场景和目标受众
|
|
3045
|
+
5. 创作建议和后续应用方向
|
|
3046
|
+
|
|
3047
|
+
字幕文件已保存为:${captionsFileName}`,
|
|
3048
|
+
},
|
|
3049
|
+
];
|
|
3050
|
+
// 在返回结果中包含字幕文件信息
|
|
3051
|
+
const analysisPayload = {
|
|
3052
|
+
model: 'Doubao-Seed-1.6-flash',
|
|
3053
|
+
messages: [
|
|
3054
|
+
{
|
|
3055
|
+
role: 'system',
|
|
3056
|
+
content: systemPrompt,
|
|
3057
|
+
},
|
|
3058
|
+
{
|
|
3059
|
+
role: 'user',
|
|
3060
|
+
content: messageContent,
|
|
3061
|
+
},
|
|
3062
|
+
],
|
|
3063
|
+
};
|
|
3064
|
+
console.log(JSON.stringify(analysisPayload, null, 2));
|
|
3065
|
+
const completion = await ai.getCompletions(analysisPayload);
|
|
3066
|
+
const analysisResult = completion.choices[0]?.message?.content;
|
|
3067
|
+
if (!analysisResult) {
|
|
3068
|
+
throw new Error('No response from AI model');
|
|
3069
|
+
}
|
|
3070
|
+
return {
|
|
3071
|
+
content: [
|
|
3072
|
+
{
|
|
3073
|
+
type: 'text',
|
|
3074
|
+
text: JSON.stringify({
|
|
3075
|
+
success: true,
|
|
3076
|
+
mediaFileName,
|
|
3077
|
+
mediaType: fileExtension,
|
|
3078
|
+
analysisRequest,
|
|
3079
|
+
captionsFileName,
|
|
3080
|
+
captionsContent: captionsText,
|
|
3081
|
+
analysis: analysisResult,
|
|
3082
|
+
mediaUrl,
|
|
3083
|
+
timestamp: new Date().toISOString(),
|
|
3084
|
+
nextActionSuggest: '可根据字幕内容和分析结果进行后续创作,如生成相关视频、配音或其他素材。',
|
|
3085
|
+
}),
|
|
3086
|
+
},
|
|
3087
|
+
],
|
|
3088
|
+
};
|
|
3089
|
+
}
|
|
3090
|
+
catch (captionError) {
|
|
3091
|
+
// 如果字幕生成失败,回退到原有逻辑
|
|
3092
|
+
console.warn('Failed to generate captions:', captionError);
|
|
3093
|
+
messageContent = [
|
|
3094
|
+
{
|
|
3095
|
+
type: 'text',
|
|
3096
|
+
text: `${userPrompt}\n\n注意:这是一个音频文件 (${mediaFileName}),字幕生成失败,请根据文件名和用户需求提供分析建议。错误信息:${captionError}`,
|
|
3097
|
+
},
|
|
3098
|
+
];
|
|
3099
|
+
}
|
|
3050
3100
|
}
|
|
3051
3101
|
else {
|
|
3052
3102
|
throw new Error(`Unsupported media type: ${fileExtension}`);
|
|
@@ -3749,6 +3799,118 @@ server.registerTool('generate-video-by-ref', {
|
|
|
3749
3799
|
return createErrorResponse(error.message, 'generate-video-by-ref');
|
|
3750
3800
|
}
|
|
3751
3801
|
});
|
|
3802
|
+
server.registerTool('extend-video-duration', {
|
|
3803
|
+
title: 'Extend Video Duration',
|
|
3804
|
+
description: 'Extend any MP4 video in materials directory by 1-7 seconds using AI video extension technology.',
|
|
3805
|
+
inputSchema: {
|
|
3806
|
+
videoFileName: zod_1.z
|
|
3807
|
+
.string()
|
|
3808
|
+
.describe('The MP4 video file name in materials directory to extend.'),
|
|
3809
|
+
duration: zod_1.z
|
|
3810
|
+
.number()
|
|
3811
|
+
.min(1)
|
|
3812
|
+
.max(7)
|
|
3813
|
+
.default(3)
|
|
3814
|
+
.describe('Duration to extend the video in seconds (1-7).'),
|
|
3815
|
+
prompt: zod_1.z
|
|
3816
|
+
.string()
|
|
3817
|
+
.optional()
|
|
3818
|
+
.describe('Optional prompt to guide the video extension. If not provided, will use a default prompt.'),
|
|
3819
|
+
type: zod_1.z
|
|
3820
|
+
.enum(['turbo', 'pro'])
|
|
3821
|
+
.optional()
|
|
3822
|
+
.default('turbo')
|
|
3823
|
+
.describe('Model type for video extension. turbo is faster, pro is higher quality.'),
|
|
3824
|
+
endFrame: zod_1.z
|
|
3825
|
+
.string()
|
|
3826
|
+
.optional()
|
|
3827
|
+
.describe('Optional end frame image file name in materials directory to guide the video extension.'),
|
|
3828
|
+
saveToFileName: zod_1.z
|
|
3829
|
+
.string()
|
|
3830
|
+
.describe('The filename to save the extended video.'),
|
|
3831
|
+
},
|
|
3832
|
+
}, async ({ videoFileName, duration, prompt, type = 'turbo', endFrame, saveToFileName, }, context) => {
|
|
3833
|
+
try {
|
|
3834
|
+
await validateSession('extend-video');
|
|
3835
|
+
validateFileName(videoFileName);
|
|
3836
|
+
validateFileName(saveToFileName);
|
|
3837
|
+
if (!session) {
|
|
3838
|
+
return createErrorResponse(new Error('No active session. Please open a project first.'), 'extend-video');
|
|
3839
|
+
}
|
|
3840
|
+
// 检查视频文件为MP4格式
|
|
3841
|
+
const fileExtension = node_path_1.default.extname(videoFileName).toLowerCase();
|
|
3842
|
+
if (fileExtension !== '.mp4') {
|
|
3843
|
+
return createErrorResponse(new Error(`Only MP4 files are supported. Found: ${fileExtension}`), 'extend-video');
|
|
3844
|
+
}
|
|
3845
|
+
const videoUri = getMaterialUri(session, videoFileName);
|
|
3846
|
+
// 处理结束帧参数
|
|
3847
|
+
let endFrameUri;
|
|
3848
|
+
if (endFrame) {
|
|
3849
|
+
validateFileName(endFrame);
|
|
3850
|
+
endFrameUri = getMaterialUri(session, endFrame);
|
|
3851
|
+
}
|
|
3852
|
+
// 设置默认提示词,用中文
|
|
3853
|
+
const defaultPrompt = `延长这视频自然平滑,保持相同的风格、照明和运动模式。保持视觉一致性,确保无缝过渡。`;
|
|
3854
|
+
const finalPrompt = prompt || defaultPrompt;
|
|
3855
|
+
let progress = 0;
|
|
3856
|
+
const result = await session.ai.extendVideo({
|
|
3857
|
+
type: type,
|
|
3858
|
+
video_url: videoUri,
|
|
3859
|
+
prompt: finalPrompt,
|
|
3860
|
+
duration,
|
|
3861
|
+
end_frame: endFrameUri,
|
|
3862
|
+
onProgress: async (metaData) => {
|
|
3863
|
+
sendProgress(context, ++progress, undefined, `Extension progress: ${Math.round(progress * 100)}%`);
|
|
3864
|
+
},
|
|
3865
|
+
});
|
|
3866
|
+
// 检查结果
|
|
3867
|
+
if (!result || result.error) {
|
|
3868
|
+
throw new Error(result?.error || 'Video extension failed');
|
|
3869
|
+
}
|
|
3870
|
+
// 检查返回的视频URL
|
|
3871
|
+
if (!result.url) {
|
|
3872
|
+
throw new Error('Failed to get extended video URL');
|
|
3873
|
+
}
|
|
3874
|
+
// 保存延长后的视频
|
|
3875
|
+
await saveMaterial(session, result.url, saveToFileName);
|
|
3876
|
+
// 更新媒体日志
|
|
3877
|
+
await updateMediaLogs(session, saveToFileName, {
|
|
3878
|
+
originalVideo: videoFileName,
|
|
3879
|
+
extensionDuration: duration,
|
|
3880
|
+
prompt: finalPrompt,
|
|
3881
|
+
modelType: type,
|
|
3882
|
+
endFrame: endFrame,
|
|
3883
|
+
extendedVideoUrl: result.url,
|
|
3884
|
+
...result,
|
|
3885
|
+
}, 'video');
|
|
3886
|
+
return {
|
|
3887
|
+
content: [
|
|
3888
|
+
{
|
|
3889
|
+
type: 'text',
|
|
3890
|
+
text: JSON.stringify({
|
|
3891
|
+
success: true,
|
|
3892
|
+
message: 'Video extended successfully',
|
|
3893
|
+
originalVideo: videoFileName,
|
|
3894
|
+
extendedVideo: saveToFileName,
|
|
3895
|
+
extensionDuration: duration,
|
|
3896
|
+
prompt: finalPrompt,
|
|
3897
|
+
modelType: type,
|
|
3898
|
+
extendedVideoUrl: result.url,
|
|
3899
|
+
details: {
|
|
3900
|
+
duration: result.duration || 'Unknown',
|
|
3901
|
+
resolution: result.resolution || 'Unknown',
|
|
3902
|
+
aspectRatio: result.ratio || 'Unknown',
|
|
3903
|
+
},
|
|
3904
|
+
}, null, 2),
|
|
3905
|
+
},
|
|
3906
|
+
],
|
|
3907
|
+
};
|
|
3908
|
+
}
|
|
3909
|
+
catch (error) {
|
|
3910
|
+
console.error('Error extending video:', error);
|
|
3911
|
+
return createErrorResponse(error, 'extend-video');
|
|
3912
|
+
}
|
|
3913
|
+
});
|
|
3752
3914
|
server.registerTool('run-ffmpeg-command', {
|
|
3753
3915
|
title: 'Run FFmpeg Command',
|
|
3754
3916
|
description: 'Run ffmpeg or ffprobe commands in the sandbox environment. Only ffmpeg and ffprobe commands are allowed. The default working directory is the materials directory.',
|