#!/usr/bin/env node /** * 验证培训包中的语音克隆能力 * 检查项: * 1. 语音服务模块是否可加载 * 2. ffmpeg/wx-voice 依赖是否可用 * 3. Fmode 后端连通性 */ const path = require('path'); const fs = require('fs'); const TRAINING_EXE = path.join(__dirname, '../dist/qiwei-training/qiwei-workbench.exe'); const VOICE_CHECK_MARKERS = [ 'VoiceCloneService', 'indextts2', // Fmode 后端接口路径 'ffmpeg', 'wx-voice', 'SILK', ]; async function checkExeExists() { if (!fs.existsSync(TRAINING_EXE)) { console.error(`❌ 培训包 exe 不存在: ${TRAINING_EXE}`); console.error(' 请先运行: node scripts/build-training-package.mjs'); process.exit(1); } const sizeMB = (fs.statSync(TRAINING_EXE).size / (1024 * 1024)).toFixed(1); console.log(`✅ 培训包 exe 已存在: ${sizeMB} MB`); } async function checkVoiceModuleInSource() { const voiceServicePath = path.join(__dirname, '../mcp/src/core/voice-clone-service.js'); if (!fs.existsSync(voiceServicePath)) { console.error('❌ 源码中缺少 voice-clone-service.js'); process.exit(1); } const content = fs.readFileSync(voiceServicePath, 'utf8'); const missing = VOICE_CHECK_MARKERS.filter(marker => !content.includes(marker)); if (missing.length > 0) { console.error(`❌ 语音服务模块缺少关键组件: ${missing.join(', ')}`); process.exit(1); } console.log('✅ 源码语音服务模块完整'); } async function checkVoiceDependencies() { const packageJson = require('../package.json'); const requiredDeps = [ '@ffmpeg-installer/ffmpeg', '@ffprobe-installer/ffprobe', '@binsee/wx-voice', ]; const missing = requiredDeps.filter(dep => !packageJson.dependencies[dep] && !packageJson.optionalDependencies[dep] ); if (missing.length > 0) { console.error(`❌ package.json 缺少语音依赖: ${missing.join(', ')}`); process.exit(1); } console.log('✅ package.json 语音依赖已声明'); } async function checkFmodeBackendEndpoint() { const voiceServicePath = path.join(__dirname, '../mcp/src/core/voice-clone-service.js'); const content = fs.readFileSync(voiceServicePath, 'utf8'); const endpoints = [ 'https://server.fmode.cn/api/voice/indextts2', 'http://127.0.0.1:7338/api/voice/indextts2', ]; const hasEndpoint = endpoints.some(ep => content.includes(ep)); if (!hasEndpoint) { console.error('❌ 语音服务未配置 Fmode 后端地址'); process.exit(1); } console.log('✅ Fmode 语音后端地址已配置'); } async function checkREADMEVoiceSection() { const readme = path.join(__dirname, '../dist/qiwei-training/README.md'); if (!fs.existsSync(readme)) { console.error('❌ 培训包 README.md 不存在'); process.exit(1); } const content = fs.readFileSync(readme, 'utf8'); if (!content.includes('## 语音克隆功能')) { console.error('❌ README.md 缺少语音克隆说明'); process.exit(1); } console.log('✅ README.md 包含语音克隆文档'); } async function main() { console.log('开始验证培训包语音能力...\n'); await checkExeExists(); await checkVoiceModuleInSource(); await checkVoiceDependencies(); await checkFmodeBackendEndpoint(); await checkREADMEVoiceSection(); console.log('\n✅ 所有检查通过!培训包已包含完整语音克隆能力。'); console.log('\n下一步测试:'); console.log(' 1. 拷贝 dist/qiwei-training/ 到测试机'); console.log(' 2. 配置 .env.local 中的 QIWEI_AUTH_TOKEN'); console.log(' 3. 运行 qiwei-workbench.exe'); console.log(' 4. 在工作台中录制参考音频并测试合成'); } main().catch(err => { console.error('验证失败:', err.message); process.exit(1); });