@idk500/video-vision-mcp 1.2.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/LICENSE +22 -0
- package/README.md +136 -0
- package/dist/frame-extractor.d.ts +28 -0
- package/dist/frame-extractor.js +246 -0
- package/dist/hunyuan-client.d.ts +95 -0
- package/dist/hunyuan-client.js +319 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +813 -0
- package/dist/video-processor.d.ts +68 -0
- package/dist/video-processor.js +478 -0
- package/package.json +67 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { VideoInfo } from './frame-extractor.js';
|
|
2
|
+
import { HunyuanClient } from './hunyuan-client.js';
|
|
3
|
+
export interface VideoAnalysisOptions {
|
|
4
|
+
prompt?: string;
|
|
5
|
+
maxFrames?: number;
|
|
6
|
+
strategy?: 'uniform' | 'keyframe' | 'scene_change';
|
|
7
|
+
hunyuanClient?: HunyuanClient;
|
|
8
|
+
cleanup?: boolean;
|
|
9
|
+
secretId?: string;
|
|
10
|
+
secretKey?: string;
|
|
11
|
+
region?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface VideoAnalysisResult {
|
|
14
|
+
summary: string;
|
|
15
|
+
}
|
|
16
|
+
export interface VideoScriptOptions {
|
|
17
|
+
prompt?: string;
|
|
18
|
+
maxFrames?: number;
|
|
19
|
+
strategy?: 'uniform' | 'keyframe' | 'scene_change';
|
|
20
|
+
hunyuanClient?: HunyuanClient;
|
|
21
|
+
cleanup?: boolean;
|
|
22
|
+
secretId?: string;
|
|
23
|
+
secretKey?: string;
|
|
24
|
+
region?: string;
|
|
25
|
+
scriptType?: 'commercial' | 'documentary' | 'tutorial' | 'narrative' | 'custom';
|
|
26
|
+
targetDuration?: number;
|
|
27
|
+
targetAudience?: string;
|
|
28
|
+
style?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface VideoScriptResult {
|
|
31
|
+
script: string;
|
|
32
|
+
videoAnalysis: string;
|
|
33
|
+
usage: {
|
|
34
|
+
analysisTokens: number;
|
|
35
|
+
scriptTokens: number;
|
|
36
|
+
totalTokens: number;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface ImageScriptOptions {
|
|
40
|
+
prompt?: string;
|
|
41
|
+
hunyuanClient?: HunyuanClient;
|
|
42
|
+
secretId?: string;
|
|
43
|
+
secretKey?: string;
|
|
44
|
+
region?: string;
|
|
45
|
+
scriptType?: 'commercial' | 'documentary' | 'tutorial' | 'narrative' | 'custom';
|
|
46
|
+
targetDuration?: number;
|
|
47
|
+
targetAudience?: string;
|
|
48
|
+
style?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface ImageScriptResult {
|
|
51
|
+
script: string;
|
|
52
|
+
imageAnalysis: string;
|
|
53
|
+
usage: {
|
|
54
|
+
analysisTokens: number;
|
|
55
|
+
scriptTokens: number;
|
|
56
|
+
totalTokens: number;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export declare class VideoProcessor {
|
|
60
|
+
private frameExtractor;
|
|
61
|
+
constructor();
|
|
62
|
+
getVideoInfo(videoPath: string): Promise<VideoInfo>;
|
|
63
|
+
analyzeVideo(videoPath: string, options: VideoAnalysisOptions): Promise<VideoAnalysisResult>;
|
|
64
|
+
generateVideoScript(videoPath: string, options: VideoScriptOptions): Promise<VideoScriptResult>;
|
|
65
|
+
private buildScriptPrompt;
|
|
66
|
+
generateImageScript(imagePaths: string[], options: ImageScriptOptions): Promise<ImageScriptResult>;
|
|
67
|
+
private buildImageScriptPrompt;
|
|
68
|
+
}
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { FrameExtractor } from './frame-extractor.js';
|
|
2
|
+
import { HunyuanClient } from './hunyuan-client.js';
|
|
3
|
+
export class VideoProcessor {
|
|
4
|
+
frameExtractor;
|
|
5
|
+
constructor() {
|
|
6
|
+
this.frameExtractor = new FrameExtractor();
|
|
7
|
+
}
|
|
8
|
+
async getVideoInfo(videoPath) {
|
|
9
|
+
try {
|
|
10
|
+
// 检查文件是否存在
|
|
11
|
+
const fs = await import('fs/promises');
|
|
12
|
+
try {
|
|
13
|
+
await fs.access(videoPath);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error(`视频文件不存在或无法访问: ${videoPath}`);
|
|
17
|
+
}
|
|
18
|
+
console.error(`获取视频信息: ${videoPath}`);
|
|
19
|
+
return this.frameExtractor.getVideoInfo(videoPath);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error instanceof Error) {
|
|
23
|
+
throw new Error(`获取视频信息失败: ${error.message}`);
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`获取视频信息失败: ${String(error)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async analyzeVideo(videoPath, options) {
|
|
29
|
+
const { prompt = '请基于这些视频关键帧,用100-200字简洁描述视频的主要内容、场景、人物和动作,不需要逐帧分析。', maxFrames = 5, strategy = 'keyframe', cleanup = true, secretId, secretKey, region, } = options;
|
|
30
|
+
try {
|
|
31
|
+
// 参数验证
|
|
32
|
+
if (!videoPath) {
|
|
33
|
+
throw new Error('视频路径参数是必需的');
|
|
34
|
+
}
|
|
35
|
+
// 检查文件是否存在
|
|
36
|
+
const fs = await import('fs/promises');
|
|
37
|
+
try {
|
|
38
|
+
await fs.access(videoPath);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
throw new Error(`视频文件不存在或无法访问: ${videoPath}`);
|
|
42
|
+
}
|
|
43
|
+
// 创建或使用提供的 HunyuanClient
|
|
44
|
+
let hunyuanClient = options.hunyuanClient;
|
|
45
|
+
if (!hunyuanClient) {
|
|
46
|
+
if (!secretId) {
|
|
47
|
+
throw new Error('视觉模型 API Key 缺失:请通过环境变量 VISION_API_KEY 或参数 secretId 提供');
|
|
48
|
+
}
|
|
49
|
+
hunyuanClient = new HunyuanClient({
|
|
50
|
+
secretId,
|
|
51
|
+
secretKey,
|
|
52
|
+
region: region || 'ap-beijing',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
else if (secretId) {
|
|
56
|
+
hunyuanClient.setCredentials(secretId, secretKey);
|
|
57
|
+
}
|
|
58
|
+
console.error(`开始视频分析: ${videoPath}`);
|
|
59
|
+
// 提取关键帧
|
|
60
|
+
const extractionOptions = {
|
|
61
|
+
maxFrames: Math.min(maxFrames, 4), // 限制帧数以控制成本
|
|
62
|
+
strategy,
|
|
63
|
+
quality: 85,
|
|
64
|
+
};
|
|
65
|
+
console.error(`开始提取视频帧...`);
|
|
66
|
+
const framePaths = await this.frameExtractor.extractFrames(videoPath, extractionOptions);
|
|
67
|
+
console.error(`成功提取 ${framePaths.length} 个帧`);
|
|
68
|
+
if (framePaths.length === 0) {
|
|
69
|
+
throw new Error('无法从视频中提取任何帧');
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
// 构建简洁的提示词
|
|
73
|
+
const summaryPrompt = `请基于这${framePaths.length}张视频关键帧,用100-200字简洁描述视频内容,包括:主要场景、人物、动作和整体内容。请用一段连贯的文字总结,不要逐帧分析。`;
|
|
74
|
+
console.error(`开始分析视频内容...`);
|
|
75
|
+
// 使用单次请求分析多张图片
|
|
76
|
+
const batchResult = await hunyuanClient.analyzeImagesInSingleRequest(framePaths, summaryPrompt);
|
|
77
|
+
console.error(`视频分析完成`);
|
|
78
|
+
return {
|
|
79
|
+
summary: batchResult.content,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
// 清理临时帧文件
|
|
84
|
+
if (cleanup) {
|
|
85
|
+
console.error('清理临时帧文件...');
|
|
86
|
+
await this.frameExtractor.cleanupFrames(framePaths);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.error(`视频分析失败:`, error);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async generateVideoScript(videoPath, options) {
|
|
96
|
+
const { prompt, maxFrames = 5, strategy = 'keyframe', cleanup = true, secretId, secretKey, region, scriptType = 'commercial', targetDuration, targetAudience = '一般观众', style = '专业、吸引人', } = options;
|
|
97
|
+
try {
|
|
98
|
+
// 参数验证
|
|
99
|
+
if (!videoPath) {
|
|
100
|
+
throw new Error('视频路径参数是必需的');
|
|
101
|
+
}
|
|
102
|
+
// 检查文件是否存在
|
|
103
|
+
const fs = await import('fs/promises');
|
|
104
|
+
try {
|
|
105
|
+
await fs.access(videoPath);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
throw new Error(`视频文件不存在或无法访问: ${videoPath}`);
|
|
109
|
+
}
|
|
110
|
+
// 创建或使用提供的 HunyuanClient
|
|
111
|
+
let hunyuanClient = options.hunyuanClient;
|
|
112
|
+
if (!hunyuanClient) {
|
|
113
|
+
if (!secretId) {
|
|
114
|
+
throw new Error('视觉模型 API Key 缺失:请通过环境变量 VISION_API_KEY 或参数 secretId 提供');
|
|
115
|
+
}
|
|
116
|
+
hunyuanClient = new HunyuanClient({
|
|
117
|
+
secretId,
|
|
118
|
+
secretKey,
|
|
119
|
+
region: region || 'ap-beijing',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
else if (secretId) {
|
|
123
|
+
hunyuanClient.setCredentials(secretId, secretKey);
|
|
124
|
+
}
|
|
125
|
+
console.error(`开始视频脚本生成: ${videoPath}`);
|
|
126
|
+
// 第一步:分析视频内容
|
|
127
|
+
const extractionOptions = {
|
|
128
|
+
maxFrames: Math.min(maxFrames, 4),
|
|
129
|
+
strategy,
|
|
130
|
+
quality: 85,
|
|
131
|
+
};
|
|
132
|
+
console.error(`开始提取视频帧...`);
|
|
133
|
+
const framePaths = await this.frameExtractor.extractFrames(videoPath, extractionOptions);
|
|
134
|
+
console.error(`成功提取 ${framePaths.length} 个帧`);
|
|
135
|
+
if (framePaths.length === 0) {
|
|
136
|
+
throw new Error('无法从视频中提取任何帧');
|
|
137
|
+
}
|
|
138
|
+
let analysisResult;
|
|
139
|
+
let scriptResult;
|
|
140
|
+
try {
|
|
141
|
+
// 构建视频分析提示词
|
|
142
|
+
const analysisPrompt = `请基于这${framePaths.length}张视频关键帧,详细分析视频内容,包括:
|
|
143
|
+
1. 主要场景和环境描述
|
|
144
|
+
2. 人物角色和动作
|
|
145
|
+
3. 物品道具和布景
|
|
146
|
+
4. 拍摄角度和构图
|
|
147
|
+
5. 色彩和光线效果
|
|
148
|
+
6. 整体氛围和情绪
|
|
149
|
+
7. 故事情节或内容主题
|
|
150
|
+
|
|
151
|
+
请用专业的影视制作术语进行描述,为后续的拍摄脚本创作提供详细的参考信息。`;
|
|
152
|
+
console.error(`开始分析视频内容...`);
|
|
153
|
+
analysisResult = await hunyuanClient.analyzeImagesInSingleRequest(framePaths, analysisPrompt);
|
|
154
|
+
console.error(`视频内容分析完成`);
|
|
155
|
+
// 第二步:基于分析结果生成拍摄脚本
|
|
156
|
+
const scriptPrompt = this.buildScriptPrompt(analysisResult.content, {
|
|
157
|
+
scriptType,
|
|
158
|
+
targetDuration,
|
|
159
|
+
targetAudience,
|
|
160
|
+
style,
|
|
161
|
+
customPrompt: prompt,
|
|
162
|
+
});
|
|
163
|
+
console.error(`开始生成拍摄脚本...`);
|
|
164
|
+
scriptResult = await hunyuanClient.generateText(scriptPrompt, 'hunyuan-lite');
|
|
165
|
+
console.error(`拍摄脚本生成完成`);
|
|
166
|
+
return {
|
|
167
|
+
script: scriptResult.content,
|
|
168
|
+
videoAnalysis: analysisResult.content,
|
|
169
|
+
usage: {
|
|
170
|
+
analysisTokens: analysisResult.usage.totalTokens,
|
|
171
|
+
scriptTokens: scriptResult.usage.totalTokens,
|
|
172
|
+
totalTokens: analysisResult.usage.totalTokens + scriptResult.usage.totalTokens,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
// 清理临时帧文件
|
|
178
|
+
if (cleanup) {
|
|
179
|
+
console.error('清理临时帧文件...');
|
|
180
|
+
await this.frameExtractor.cleanupFrames(framePaths);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
console.error(`视频脚本生成失败:`, error);
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
buildScriptPrompt(videoAnalysis, options) {
|
|
190
|
+
const { scriptType, targetDuration, targetAudience, style, customPrompt } = options;
|
|
191
|
+
let basePrompt = `基于以下视频内容分析,创作一个专业的拍摄脚本:
|
|
192
|
+
|
|
193
|
+
【视频内容分析】
|
|
194
|
+
${videoAnalysis}
|
|
195
|
+
|
|
196
|
+
【脚本要求】`;
|
|
197
|
+
// 根据脚本类型添加特定要求
|
|
198
|
+
switch (scriptType) {
|
|
199
|
+
case 'commercial':
|
|
200
|
+
basePrompt += `
|
|
201
|
+
- 脚本类型:商业广告脚本
|
|
202
|
+
- 重点突出产品特色和卖点
|
|
203
|
+
- 包含吸引人的开头、清晰的产品展示、强有力的行动号召
|
|
204
|
+
- 语言简洁有力,节奏紧凑`;
|
|
205
|
+
break;
|
|
206
|
+
case 'documentary':
|
|
207
|
+
basePrompt += `
|
|
208
|
+
- 脚本类型:纪录片脚本
|
|
209
|
+
- 注重真实性和客观性
|
|
210
|
+
- 包含背景介绍、事实陈述、深度分析
|
|
211
|
+
- 语言专业严谨,逻辑清晰`;
|
|
212
|
+
break;
|
|
213
|
+
case 'tutorial':
|
|
214
|
+
basePrompt += `
|
|
215
|
+
- 脚本类型:教学视频脚本
|
|
216
|
+
- 步骤清晰,易于理解和跟随
|
|
217
|
+
- 包含引言、分步教学、总结回顾
|
|
218
|
+
- 语言通俗易懂,重点突出`;
|
|
219
|
+
break;
|
|
220
|
+
case 'narrative':
|
|
221
|
+
basePrompt += `
|
|
222
|
+
- 脚本类型:叙事视频脚本
|
|
223
|
+
- 注重故事性和情感表达
|
|
224
|
+
- 包含起承转合的完整故事结构
|
|
225
|
+
- 语言生动形象,富有感染力`;
|
|
226
|
+
break;
|
|
227
|
+
default:
|
|
228
|
+
basePrompt += `
|
|
229
|
+
- 脚本类型:${scriptType}
|
|
230
|
+
- 根据视频内容特点制作合适的脚本`;
|
|
231
|
+
}
|
|
232
|
+
basePrompt += `
|
|
233
|
+
- 目标受众:${targetAudience}
|
|
234
|
+
- 拍摄风格:${style}`;
|
|
235
|
+
if (targetDuration) {
|
|
236
|
+
basePrompt += `
|
|
237
|
+
- 目标时长:约${targetDuration}秒`;
|
|
238
|
+
}
|
|
239
|
+
if (customPrompt) {
|
|
240
|
+
basePrompt += `
|
|
241
|
+
- 特殊要求:${customPrompt}`;
|
|
242
|
+
}
|
|
243
|
+
basePrompt += `
|
|
244
|
+
|
|
245
|
+
【脚本格式】
|
|
246
|
+
请按照以下格式输出专业拍摄脚本:
|
|
247
|
+
|
|
248
|
+
## 视频标题
|
|
249
|
+
[根据内容生成吸引人的标题]
|
|
250
|
+
|
|
251
|
+
## 脚本概述
|
|
252
|
+
[简要说明视频主题和目标]
|
|
253
|
+
|
|
254
|
+
## 分镜脚本
|
|
255
|
+
|
|
256
|
+
### 镜头1:[镜头描述]
|
|
257
|
+
- **时长**:[预估时长]
|
|
258
|
+
- **景别**:[特写/中景/全景等]
|
|
259
|
+
- **机位**:[拍摄角度和位置]
|
|
260
|
+
- **内容**:[具体拍摄内容]
|
|
261
|
+
- **台词/解说**:[如有]
|
|
262
|
+
- **音效/配乐**:[建议的音效]
|
|
263
|
+
|
|
264
|
+
### 镜头2:[镜头描述]
|
|
265
|
+
[按相同格式继续...]
|
|
266
|
+
|
|
267
|
+
## 制作要点
|
|
268
|
+
- [关键拍摄技巧]
|
|
269
|
+
- [后期制作建议]
|
|
270
|
+
- [注意事项]
|
|
271
|
+
|
|
272
|
+
## 预期效果
|
|
273
|
+
[描述最终视频的预期效果和观众反应]
|
|
274
|
+
|
|
275
|
+
请确保脚本专业、实用,能够指导实际的视频拍摄制作。`;
|
|
276
|
+
return basePrompt;
|
|
277
|
+
}
|
|
278
|
+
async generateImageScript(imagePaths, options) {
|
|
279
|
+
const { prompt, secretId, secretKey, region, scriptType = 'commercial', targetDuration, targetAudience = '一般观众', style = '专业、吸引人', } = options;
|
|
280
|
+
try {
|
|
281
|
+
// 参数验证
|
|
282
|
+
if (!imagePaths || imagePaths.length === 0) {
|
|
283
|
+
throw new Error('图片路径数组参数是必需的,且不能为空');
|
|
284
|
+
}
|
|
285
|
+
// 检查所有图片文件是否存在
|
|
286
|
+
const fs = await import('fs/promises');
|
|
287
|
+
const invalidPaths = [];
|
|
288
|
+
for (const imagePath of imagePaths) {
|
|
289
|
+
try {
|
|
290
|
+
await fs.access(imagePath);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
invalidPaths.push(imagePath);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (invalidPaths.length > 0) {
|
|
297
|
+
throw new Error('以下图片文件不存在或无法访问:\
|
|
298
|
+
' + invalidPaths.join('\
|
|
299
|
+
'));
|
|
300
|
+
}
|
|
301
|
+
// 创建或使用提供的 HunyuanClient
|
|
302
|
+
let hunyuanClient = options.hunyuanClient;
|
|
303
|
+
if (!hunyuanClient) {
|
|
304
|
+
if (!secretId) {
|
|
305
|
+
throw new Error('视觉模型 API Key 缺失:请通过环境变量 VISION_API_KEY 或参数 secretId 提供');
|
|
306
|
+
}
|
|
307
|
+
hunyuanClient = new HunyuanClient({
|
|
308
|
+
secretId,
|
|
309
|
+
secretKey,
|
|
310
|
+
region: region || 'ap-beijing',
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
else if (secretId) {
|
|
314
|
+
hunyuanClient.setCredentials(secretId, secretKey);
|
|
315
|
+
}
|
|
316
|
+
console.error('开始基于图片生成拍摄脚本,共 ' + imagePaths.length + ' 张图片');
|
|
317
|
+
// 第一步:批量分析图片内容
|
|
318
|
+
console.error('开始分析图片内容...');
|
|
319
|
+
// 构建图片分析提示词
|
|
320
|
+
const analysisPrompt = '请基于这些图片,详细分析其内容,包括:\
|
|
321
|
+
1. 主要场景和环境描述\
|
|
322
|
+
2. 人物角色、表情和动作\
|
|
323
|
+
3. 物品道具和布景细节\
|
|
324
|
+
4. 构图、角度和视觉效果\
|
|
325
|
+
5. 色彩搭配和光线效果\
|
|
326
|
+
6. 整体氛围和情绪表达\
|
|
327
|
+
7. 故事情节或主题内容\
|
|
328
|
+
\
|
|
329
|
+
请用专业的影视制作术语进行描述,为后续的拍摄脚本创作提供详细的参考信息。如果图片之间有关联性,请说明它们的逻辑关系和故事连贯性。';
|
|
330
|
+
// 使用批量分析功能
|
|
331
|
+
const analysisResults = await hunyuanClient.analyzeImageBatch(imagePaths, analysisPrompt);
|
|
332
|
+
// 合并所有分析结果
|
|
333
|
+
const successfulAnalyses = analysisResults.filter(result => !result.content.startsWith('❌'));
|
|
334
|
+
const failedCount = analysisResults.length - successfulAnalyses.length;
|
|
335
|
+
if (successfulAnalyses.length === 0) {
|
|
336
|
+
throw new Error('所有图片分析都失败了,无法生成脚本');
|
|
337
|
+
}
|
|
338
|
+
if (failedCount > 0) {
|
|
339
|
+
console.error('警告:' + failedCount + ' 张图片分析失败,将基于 ' + successfulAnalyses.length + ' 张成功分析的图片生成脚本');
|
|
340
|
+
}
|
|
341
|
+
// 合并分析结果
|
|
342
|
+
const combinedAnalysis = successfulAnalyses
|
|
343
|
+
.map((result, index) => '【图片 ' + (index + 1) + '】\
|
|
344
|
+
' + result.content)
|
|
345
|
+
.join('\
|
|
346
|
+
\
|
|
347
|
+
');
|
|
348
|
+
const totalAnalysisTokens = analysisResults.reduce((sum, result) => sum + result.usage.totalTokens, 0);
|
|
349
|
+
console.error('图片内容分析完成 - 成功: ' + successfulAnalyses.length + '/' + imagePaths.length + ', Token使用: ' + totalAnalysisTokens);
|
|
350
|
+
// 第二步:基于分析结果生成拍摄脚本
|
|
351
|
+
const scriptPrompt = this.buildImageScriptPrompt(combinedAnalysis, {
|
|
352
|
+
scriptType,
|
|
353
|
+
targetDuration,
|
|
354
|
+
targetAudience,
|
|
355
|
+
style,
|
|
356
|
+
customPrompt: prompt,
|
|
357
|
+
imageCount: successfulAnalyses.length,
|
|
358
|
+
});
|
|
359
|
+
console.error(`开始生成拍摄脚本...`);
|
|
360
|
+
const scriptResult = await hunyuanClient.generateText(scriptPrompt, 'hunyuan-lite');
|
|
361
|
+
console.error(`拍摄脚本生成完成`);
|
|
362
|
+
return {
|
|
363
|
+
script: scriptResult.content,
|
|
364
|
+
imageAnalysis: combinedAnalysis,
|
|
365
|
+
usage: {
|
|
366
|
+
analysisTokens: totalAnalysisTokens,
|
|
367
|
+
scriptTokens: scriptResult.usage.totalTokens,
|
|
368
|
+
totalTokens: totalAnalysisTokens + scriptResult.usage.totalTokens,
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
console.error(`基于图片的脚本生成失败:`, error);
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
buildImageScriptPrompt(imageAnalysis, options) {
|
|
378
|
+
const { scriptType, targetDuration, targetAudience, style, customPrompt, imageCount } = options;
|
|
379
|
+
let basePrompt = `基于以下 ${imageCount} 张图片的内容分析,创作一个专业的拍摄脚本:
|
|
380
|
+
|
|
381
|
+
【图片内容分析】
|
|
382
|
+
${imageAnalysis}
|
|
383
|
+
|
|
384
|
+
【脚本要求】`;
|
|
385
|
+
// 根据脚本类型添加特定要求
|
|
386
|
+
switch (scriptType) {
|
|
387
|
+
case 'commercial':
|
|
388
|
+
basePrompt += `
|
|
389
|
+
- 脚本类型:商业广告脚本
|
|
390
|
+
- 重点突出产品特色和卖点
|
|
391
|
+
- 包含吸引人的开头、清晰的产品展示、强有力的行动号召
|
|
392
|
+
- 语言简洁有力,节奏紧凑
|
|
393
|
+
- 充分利用图片中的视觉元素增强广告效果`;
|
|
394
|
+
break;
|
|
395
|
+
case 'documentary':
|
|
396
|
+
basePrompt += `
|
|
397
|
+
- 脚本类型:纪录片脚本
|
|
398
|
+
- 注重真实性和客观性
|
|
399
|
+
- 包含背景介绍、事实陈述、深度分析
|
|
400
|
+
- 语言专业严谨,逻辑清晰
|
|
401
|
+
- 基于图片内容构建真实的故事线`;
|
|
402
|
+
break;
|
|
403
|
+
case 'tutorial':
|
|
404
|
+
basePrompt += `
|
|
405
|
+
- 脚本类型:教学视频脚本
|
|
406
|
+
- 步骤清晰,易于理解和跟随
|
|
407
|
+
- 包含引言、分步教学、总结回顾
|
|
408
|
+
- 语言通俗易懂,重点突出
|
|
409
|
+
- 利用图片中的元素作为教学示例`;
|
|
410
|
+
break;
|
|
411
|
+
case 'narrative':
|
|
412
|
+
basePrompt += `
|
|
413
|
+
- 脚本类型:叙事视频脚本
|
|
414
|
+
- 注重故事性和情感表达
|
|
415
|
+
- 包含起承转合的完整故事结构
|
|
416
|
+
- 语言生动形象,富有感染力
|
|
417
|
+
- 将图片内容串联成连贯的故事情节`;
|
|
418
|
+
break;
|
|
419
|
+
default:
|
|
420
|
+
basePrompt += `
|
|
421
|
+
- 脚本类型:${scriptType}
|
|
422
|
+
- 根据图片内容特点制作合适的脚本`;
|
|
423
|
+
}
|
|
424
|
+
basePrompt += `
|
|
425
|
+
- 目标受众:${targetAudience}
|
|
426
|
+
- 拍摄风格:${style}
|
|
427
|
+
- 图片数量:${imageCount} 张`;
|
|
428
|
+
if (targetDuration) {
|
|
429
|
+
basePrompt += `
|
|
430
|
+
- 目标时长:约${targetDuration}秒`;
|
|
431
|
+
}
|
|
432
|
+
if (customPrompt) {
|
|
433
|
+
basePrompt += `
|
|
434
|
+
- 特殊要求:${customPrompt}`;
|
|
435
|
+
}
|
|
436
|
+
basePrompt += `
|
|
437
|
+
|
|
438
|
+
【脚本格式】
|
|
439
|
+
请按照以下格式输出专业拍摄脚本:
|
|
440
|
+
|
|
441
|
+
## 视频标题
|
|
442
|
+
[根据图片内容生成吸引人的标题]
|
|
443
|
+
|
|
444
|
+
## 脚本概述
|
|
445
|
+
[简要说明视频主题和目标,以及如何利用现有图片素材]
|
|
446
|
+
|
|
447
|
+
## 分镜脚本
|
|
448
|
+
|
|
449
|
+
### 镜头1:[镜头描述]
|
|
450
|
+
- **时长**:[预估时长]
|
|
451
|
+
- **景别**:[特写/中景/全景等]
|
|
452
|
+
- **机位**:[拍摄角度和位置]
|
|
453
|
+
- **内容**:[具体拍摄内容,可参考图片中的元素]
|
|
454
|
+
- **参考图片**:[如适用,指出参考了哪张图片的哪些元素]
|
|
455
|
+
- **台词/解说**:[如有]
|
|
456
|
+
- **音效/配乐**:[建议的音效]
|
|
457
|
+
|
|
458
|
+
### 镜头2:[镜头描述]
|
|
459
|
+
[按相同格式继续...]
|
|
460
|
+
|
|
461
|
+
## 图片素材利用建议
|
|
462
|
+
- [说明如何在拍摄中参考和利用现有图片]
|
|
463
|
+
- [图片中的哪些元素可以直接使用或重新拍摄]
|
|
464
|
+
- [如何保持与图片风格的一致性]
|
|
465
|
+
|
|
466
|
+
## 制作要点
|
|
467
|
+
- [关键拍摄技巧]
|
|
468
|
+
- [后期制作建议]
|
|
469
|
+
- [注意事项]
|
|
470
|
+
- [与图片素材的结合方式]
|
|
471
|
+
|
|
472
|
+
## 预期效果
|
|
473
|
+
[描述最终视频的预期效果和观众反应]
|
|
474
|
+
|
|
475
|
+
请确保脚本专业、实用,能够指导实际的视频拍摄制作,并充分利用现有的图片素材。`;
|
|
476
|
+
return basePrompt;
|
|
477
|
+
}
|
|
478
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@idk500/video-vision-mcp",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Video MCP server with AI vision analysis using OpenAI-compatible endpoints (default: Zhipu Bigmodel glm-4.6v-flash)",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"dev": "tsx src/index.ts",
|
|
11
|
+
"lint": "eslint src/**/*.ts",
|
|
12
|
+
"lint:fix": "eslint src/**/*.ts --fix",
|
|
13
|
+
"format": "prettier --write src/**/*.ts",
|
|
14
|
+
"example": "tsx src/examples.ts",
|
|
15
|
+
"test:local": "tsx src/local-test.ts",
|
|
16
|
+
"test:video": "tsx test/video-analysis-test.ts",
|
|
17
|
+
"test:auth": "tsx test/auth-test.ts",
|
|
18
|
+
"test:script": "tsx test/script-generation-test.ts",
|
|
19
|
+
"setup": "node start.js",
|
|
20
|
+
"clean": "rm -rf dist temp_frames temp_test_frames"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mcp",
|
|
24
|
+
"video",
|
|
25
|
+
"vision",
|
|
26
|
+
"ai",
|
|
27
|
+
"storyboard",
|
|
28
|
+
"frame-extraction",
|
|
29
|
+
"bigmodel",
|
|
30
|
+
"glm-4.6v-flash"
|
|
31
|
+
],
|
|
32
|
+
"author": "pickstar-2002 & idk500",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/idk500/video-capture-script-mcp.git"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/idk500/video-capture-script-mcp#readme",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/idk500/video-capture-script-mcp/issues"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE"
|
|
46
|
+
],
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
49
|
+
"fluent-ffmpeg": "^2.1.2",
|
|
50
|
+
"node-fetch": "^3.3.0",
|
|
51
|
+
"dotenv": "^16.0.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^20.0.0",
|
|
55
|
+
"@types/fluent-ffmpeg": "^2.1.24",
|
|
56
|
+
"typescript": "^5.0.0",
|
|
57
|
+
"tsx": "^4.0.0",
|
|
58
|
+
"eslint": "^8.0.0",
|
|
59
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
60
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
61
|
+
"prettier": "^3.0.0"
|
|
62
|
+
},
|
|
63
|
+
"bin": {
|
|
64
|
+
"video-mcp": "dist/index.js"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|