@pickstar-2002/video-info-mcp
Version:
🎬 基于 MCP 协议的专业视频信息分析工具,支持多格式视频文件的详细信息提取和技术参数分析
319 lines • 14.2 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.VideoAnalyzer = void 0;
const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
const fs_1 = require("fs");
const path_1 = __importDefault(require("path"));
const types_1 = require("./types");
class VideoAnalyzer {
/**
* 获取视频文件的原始信息
*/
async getVideoInfo(filePath) {
return new Promise((resolve, reject) => {
fluent_ffmpeg_1.default.ffprobe(filePath, (err, metadata) => {
if (err) {
reject(new Error(`无法读取视频文件: ${err.message}`));
return;
}
try {
const validatedData = types_1.VideoInfoSchema.parse(metadata);
resolve(validatedData);
}
catch (parseError) {
reject(new Error(`视频信息解析失败: ${parseError}`));
}
});
});
}
/**
* 处理和分析视频信息
*/
async analyzeVideo(filePath, includeMetadata = true) {
// 检查文件是否存在
try {
await fs_1.promises.access(filePath);
}
catch {
throw new Error(`文件不存在: ${filePath}`);
}
const videoInfo = await this.getVideoInfo(filePath);
const fileStats = await fs_1.promises.stat(filePath);
// 分离视频流和音频流
const videoStreams = videoInfo.streams.filter(stream => stream.codec_type === 'video');
const audioStreams = videoInfo.streams.filter(stream => stream.codec_type === 'audio');
// 处理视频流信息
const processedVideoStreams = videoStreams.map(stream => ({
index: stream.index,
codec: stream.codec_name,
codecLongName: stream.codec_long_name,
profile: stream.profile,
resolution: `${stream.width}x${stream.height}`,
aspectRatio: stream.display_aspect_ratio,
pixelFormat: stream.pix_fmt,
frameRate: stream.r_frame_rate,
avgFrameRate: stream.avg_frame_rate,
bitRate: stream.bit_rate ? String(stream.bit_rate) : undefined,
maxBitRate: stream.max_bit_rate ? String(stream.max_bit_rate) : undefined,
totalFrames: stream.nb_frames ? String(stream.nb_frames) : undefined,
colorSpace: stream.color_space,
colorRange: stream.color_range
}));
// 处理音频流信息
const processedAudioStreams = audioStreams.map(stream => ({
index: stream.index,
codec: stream.codec_name,
codecLongName: stream.codec_long_name,
profile: stream.profile,
sampleRate: String(stream.sample_rate),
channels: stream.channels,
channelLayout: stream.channel_layout,
sampleFormat: stream.sample_fmt,
bitRate: stream.bit_rate ? String(stream.bit_rate) : undefined,
maxBitRate: stream.max_bit_rate ? String(stream.max_bit_rate) : undefined
}));
// 计算码率分析
const bitrateAnalysis = this.calculateBitrateAnalysis(videoInfo, fileStats.size);
// 生成技术报告
const technicalReport = this.generateTechnicalReport(processedVideoStreams, processedAudioStreams, bitrateAnalysis);
return {
filename: path_1.default.basename(filePath),
fileSize: this.formatFileSize(fileStats.size),
duration: String(videoInfo.format.duration || '未知'),
durationSeconds: parseFloat(String(videoInfo.format.duration || '0')),
format: videoInfo.format.format_name,
formatLongName: videoInfo.format.format_long_name,
videoStreams: processedVideoStreams,
audioStreams: processedAudioStreams,
bitrateAnalysis,
technicalReport
};
}
/**
* 计算码率分析
*/
calculateBitrateAnalysis(videoInfo, fileSize) {
const duration = parseFloat(String(videoInfo.format.duration || '0'));
const overallBitRate = videoInfo.format.bit_rate ? String(videoInfo.format.bit_rate) : undefined;
// 计算视频和音频码率
const videoStreams = videoInfo.streams.filter(s => s.codec_type === 'video');
const audioStreams = videoInfo.streams.filter(s => s.codec_type === 'audio');
const videoBitRate = videoStreams[0]?.bit_rate;
const audioBitRate = audioStreams[0]?.bit_rate;
// 计算最大码率
const maxBitRates = videoInfo.streams
.map(s => s.max_bit_rate ? parseInt(String(s.max_bit_rate)) : 0)
.filter(rate => rate > 0);
const maxBitRate = maxBitRates.length > 0 ? Math.max(...maxBitRates).toString() : undefined;
// 估算文件大小(基于码率)
let estimatedSize = '未知';
if (overallBitRate && duration > 0) {
const estimatedBytes = overallBitRate ? (parseInt(overallBitRate) * duration) / 8 : 0;
estimatedSize = this.formatFileSize(estimatedBytes);
}
return {
overallBitRate,
videoBitRate: videoStreams[0]?.bit_rate ? String(videoStreams[0].bit_rate) : undefined,
audioBitRate: audioStreams[0]?.bit_rate ? String(audioStreams[0].bit_rate) : undefined,
maxBitRate,
estimatedSize
};
}
/**
* 生成技术报告
*/
generateTechnicalReport(videoStreams, audioStreams, bitrateAnalysis) {
const recommendations = [];
// 视频质量评估
let videoQuality = '未知';
if (videoStreams.length > 0) {
const firstVideo = videoStreams[0];
const [width, height] = firstVideo.resolution.split('x').map(Number);
const bitRate = parseInt(firstVideo.bitRate || '0');
if (height >= 2160) {
videoQuality = '4K超高清';
}
else if (height >= 1080) {
videoQuality = '1080p高清';
if (bitRate < 5000000) {
recommendations.push('建议提高视频码率以获得更好的1080p质量');
}
}
else if (height >= 720) {
videoQuality = '720p高清';
}
else {
videoQuality = '标清';
recommendations.push('考虑提升分辨率以获得更好的观看体验');
}
}
// 音频质量评估
let audioQuality = '未知';
if (audioStreams.length > 0) {
const firstAudio = audioStreams[0];
const sampleRate = parseInt(firstAudio.sampleRate);
const bitRate = parseInt(firstAudio.bitRate || '0');
if (sampleRate >= 48000 && bitRate >= 320000) {
audioQuality = '高品质';
}
else if (sampleRate >= 44100 && bitRate >= 128000) {
audioQuality = '标准品质';
}
else {
audioQuality = '基础品质';
recommendations.push('建议提高音频码率和采样率');
}
}
// 编码建议
if (videoStreams.some(s => s.codec === 'h264')) {
recommendations.push('使用H.264编码,兼容性良好');
}
if (videoStreams.some(s => s.codec === 'hevc' || s.codec === 'h265')) {
recommendations.push('使用H.265编码,压缩效率更高但兼容性需要考虑');
}
return {
videoQuality,
audioQuality,
recommendations
};
}
/**
* 格式化文件大小
*/
formatFileSize(bytes) {
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
}
/**
* 生成不同格式的报告
*/
async generateReport(filePath, format = 'json') {
const analysis = await this.analyzeVideo(filePath);
switch (format) {
case 'json':
return JSON.stringify(analysis, null, 2);
case 'text':
return this.generateTextReport(analysis);
case 'markdown':
return this.generateMarkdownReport(analysis);
default:
return JSON.stringify(analysis, null, 2);
}
}
/**
* 生成文本格式报告
*/
generateTextReport(analysis) {
let report = '';
report += `视频信息报告\n`;
report += `${'='.repeat(50)}\n\n`;
report += `基本信息:\n`;
report += ` 文件名: ${analysis.filename}\n`;
report += ` 文件大小: ${analysis.fileSize}\n`;
report += ` 时长: ${analysis.duration}秒\n`;
report += ` 格式: ${analysis.format} (${analysis.formatLongName})\n\n`;
if (analysis.videoStreams.length > 0) {
report += `视频流信息:\n`;
analysis.videoStreams.forEach((stream, index) => {
report += ` 流 ${index + 1}:\n`;
report += ` 编码: ${stream.codec} (${stream.codecLongName})\n`;
report += ` 分辨率: ${stream.resolution}\n`;
report += ` 帧率: ${stream.frameRate}\n`;
report += ` 码率: ${stream.bitRate || '未知'}\n`;
report += ` 像素格式: ${stream.pixelFormat}\n\n`;
});
}
if (analysis.audioStreams.length > 0) {
report += `音频流信息:\n`;
analysis.audioStreams.forEach((stream, index) => {
report += ` 流 ${index + 1}:\n`;
report += ` 编码: ${stream.codec} (${stream.codecLongName})\n`;
report += ` 采样率: ${stream.sampleRate}Hz\n`;
report += ` 声道数: ${stream.channels}\n`;
report += ` 码率: ${stream.bitRate || '未知'}\n\n`;
});
}
report += `码率分析:\n`;
report += ` 总体码率: ${analysis.bitrateAnalysis.overallBitRate || '未知'}\n`;
report += ` 视频码率: ${analysis.bitrateAnalysis.videoBitRate || '未知'}\n`;
report += ` 音频码率: ${analysis.bitrateAnalysis.audioBitRate || '未知'}\n`;
report += ` 峰值码率: ${analysis.bitrateAnalysis.maxBitRate || '未知'}\n\n`;
report += `技术评估:\n`;
report += ` 视频质量: ${analysis.technicalReport.videoQuality}\n`;
report += ` 音频质量: ${analysis.technicalReport.audioQuality}\n`;
if (analysis.technicalReport.recommendations.length > 0) {
report += ` 建议:\n`;
analysis.technicalReport.recommendations.forEach(rec => {
report += ` - ${rec}\n`;
});
}
return report;
}
/**
* 生成Markdown格式报告
*/
generateMarkdownReport(analysis) {
let report = '';
report += `# 视频信息报告\n\n`;
report += `## 基本信息\n\n`;
report += `| 属性 | 值 |\n`;
report += `|------|----|\n`;
report += `| 文件名 | ${analysis.filename} |\n`;
report += `| 文件大小 | ${analysis.fileSize} |\n`;
report += `| 时长 | ${analysis.duration}秒 |\n`;
report += `| 格式 | ${analysis.format} (${analysis.formatLongName}) |\n\n`;
if (analysis.videoStreams.length > 0) {
report += `## 视频流信息\n\n`;
analysis.videoStreams.forEach((stream, index) => {
report += `### 视频流 ${index + 1}\n\n`;
report += `| 属性 | 值 |\n`;
report += `|------|----|\n`;
report += `| 编码 | ${stream.codec} (${stream.codecLongName}) |\n`;
report += `| 分辨率 | ${stream.resolution} |\n`;
report += `| 帧率 | ${stream.frameRate} |\n`;
report += `| 码率 | ${stream.bitRate || '未知'} |\n`;
report += `| 像素格式 | ${stream.pixelFormat} |\n\n`;
});
}
if (analysis.audioStreams.length > 0) {
report += `## 音频流信息\n\n`;
analysis.audioStreams.forEach((stream, index) => {
report += `### 音频流 ${index + 1}\n\n`;
report += `| 属性 | 值 |\n`;
report += `|------|----|\n`;
report += `| 编码 | ${stream.codec} (${stream.codecLongName}) |\n`;
report += `| 采样率 | ${stream.sampleRate}Hz |\n`;
report += `| 声道数 | ${stream.channels} |\n`;
report += `| 码率 | ${stream.bitRate || '未知'} |\n\n`;
});
}
report += `## 码率分析\n\n`;
report += `| 类型 | 值 |\n`;
report += `|------|----|\n`;
report += `| 总体码率 | ${analysis.bitrateAnalysis.overallBitRate || '未知'} |\n`;
report += `| 视频码率 | ${analysis.bitrateAnalysis.videoBitRate || '未知'} |\n`;
report += `| 音频码率 | ${analysis.bitrateAnalysis.audioBitRate || '未知'} |\n`;
report += `| 峰值码率 | ${analysis.bitrateAnalysis.maxBitRate || '未知'} |\n\n`;
report += `## 技术评估\n\n`;
report += `- **视频质量**: ${analysis.technicalReport.videoQuality}\n`;
report += `- **音频质量**: ${analysis.technicalReport.audioQuality}\n\n`;
if (analysis.technicalReport.recommendations.length > 0) {
report += `### 建议\n\n`;
analysis.technicalReport.recommendations.forEach(rec => {
report += `- ${rec}\n`;
});
}
return report;
}
}
exports.VideoAnalyzer = VideoAnalyzer;
//# sourceMappingURL=video-analyzer.js.map