| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- /**
- * minimax-key2 TTS 测试脚本
- * 使用 minimax-key2 配置发送 2 个 "你好" TTS 请求
- * 流程: 创建任务 → 轮询状态 → 下载音频
- */
- import * as fs from 'fs';
- import * as path from 'path';
- import { fileURLToPath } from 'url';
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
- // ============================================================
- // minimax-key2 配置 (来自 server/src/config/models.json)
- // ============================================================
- const CONFIG = {
- apiKey: 'sk-cp-BOVwOolqOEmHycg8MzDX_UgQ_bDEK3snjPUFuwolUCb_TxMxhhlAfYhTT0_tqRvEz5K56xpl-aOoJtj97MuAY4UIA-PCQLVetUCY56i8LFimNuQqrrq5xWA',
- ttsApiPath: 'https://api.minimaxi.com',
- model: 'speech-2.8-hd',
- voice: 'audiobook_female_1', // Cherry
- };
- const OUTPUT_DIR = path.resolve(__dirname, '../test-results/minimax-key2-tts');
- const TEXT = '你好';
- // ============================================================
- // TAR 解包工具:从 tar buffer 中提取 MP3
- // ============================================================
- function extractMp3FromTar(buffer: Buffer): Buffer | null {
- let offset = 0;
- while (offset < buffer.length - 512) {
- const nameSlice = buffer.subarray(offset, offset + 100);
- const nameEnd = nameSlice.indexOf(0);
- const filename = nameEnd >= 0 ? Buffer.from(nameSlice.subarray(0, nameEnd)).toString() : nameSlice.toString();
- if (!filename || filename.trim().length === 0) break;
- const sizeStr = Buffer.from(buffer.subarray(offset + 124, offset + 135)).toString().trim();
- const fileSize = parseInt(sizeStr, 8);
- const contentOffset = offset + 512;
- if (filename.endsWith('.mp3')) {
- console.log(` 📦 从 tar 中提取 ${filename} (${fileSize} bytes)`);
- return Buffer.from(buffer.subarray(contentOffset, contentOffset + fileSize));
- }
- const paddedSize = Math.ceil(fileSize / 512) * 512;
- offset = contentOffset + paddedSize;
- }
- return null;
- }
- // ============================================================
- // 单个 TTS 请求完整流程
- // ============================================================
- async function ttsRequest(text: string, requestId: number): Promise<void> {
- const tag = `[请求#${requestId}]`;
- console.log(`\n${'='.repeat(60)}`);
- console.log(`${tag} 开始 TTS 合成: "${text}"`);
- console.log(`${'='.repeat(60)}`);
- // ---- 步骤 1: 创建异步任务 ----
- console.log(`${tag} 📤 步骤1: 创建异步任务...`);
- const createRes = await fetch(`${CONFIG.ttsApiPath}/v1/t2a_async_v2`, {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${CONFIG.apiKey}`,
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- model: CONFIG.model,
- text,
- voice_setting: {
- voice_id: CONFIG.voice,
- speed: 1,
- vol: 1,
- pitch: 1,
- },
- audio_setting: {
- audio_sample_rate: 32000,
- bitrate: 128000,
- format: 'mp3',
- channel: 1,
- },
- }),
- });
- const createData: any = await createRes.json();
- console.log(`${tag} 响应:`, JSON.stringify(createData, null, 2));
- if (createData.base_resp?.status_code !== 0) {
- throw new Error(`${tag} 创建任务失败: ${createData.base_resp?.status_msg || JSON.stringify(createData)}`);
- }
- const task_id = String(createData.task_id);
- const task_token = createData.task_token;
- console.log(`${tag} ✅ 任务已创建, task_id: ${task_id}`);
- // ---- 步骤 2: 轮询任务状态 ----
- console.log(`${tag} 🔄 步骤2: 轮询中...`);
- const maxPollMs = 2 * 60 * 1000; // 最多 2 分钟
- const pollInterval = 3000;
- const startTime = Date.now();
- let file_id: string | null = null;
- while (Date.now() - startTime < maxPollMs) {
- const queryRes = await fetch(
- `${CONFIG.ttsApiPath}/v1/query/t2a_async_query_v2?task_id=${task_id}&task_token=${task_token}`,
- { headers: { 'Authorization': `Bearer ${CONFIG.apiKey}` } }
- );
- const queryData: any = await queryRes.json();
- const status = queryData.status;
- process.stdout.write(`\r${tag} 状态: ${status} (${Math.round((Date.now() - startTime) / 1000)}s)`);
- if (status === 'Success') {
- file_id = queryData.file_id ? String(queryData.file_id) : null;
- console.log(`\n${tag} ✅ 任务完成, file_id: ${file_id}`);
- break;
- }
- if (status === 'Failed') {
- throw new Error(`${tag} 任务失败: ${queryData.status_msg || '未知错误'}`);
- }
- await new Promise(r => setTimeout(r, pollInterval));
- }
- if (!file_id) throw new Error(`${tag} 轮询超时`);
- // ---- 步骤 3: 下载音频 ----
- console.log(`${tag} ⬇️ 步骤3: 下载音频...`);
- const downloadRes = await fetch(
- `${CONFIG.ttsApiPath}/v1/files/retrieve_content?file_id=${file_id}`,
- { headers: { 'Authorization': `Bearer ${CONFIG.apiKey}` } }
- );
- const arrayBuffer = await downloadRes.arrayBuffer();
- const buffer = Buffer.from(arrayBuffer);
- const mp3Data = extractMp3FromTar(buffer);
- if (!mp3Data) throw new Error(`${tag} tar 中未找到 MP3 文件`);
- if (!fs.existsSync(OUTPUT_DIR)) fs.mkdirSync(OUTPUT_DIR, { recursive: true });
- const outputPath = path.join(OUTPUT_DIR, `output-${requestId}.mp3`);
- fs.writeFileSync(outputPath, mp3Data);
- console.log(`${tag} ✅ 音频已保存: ${outputPath} (${mp3Data.length} bytes)`);
- }
- // ============================================================
- // 主流程:同时发送 2 个 TTS 请求
- // ============================================================
- async function main() {
- console.log('🚀 MiniMax Key2 TTS 测试 - 并行 2 个请求');
- console.log(`📝 文本: "${TEXT}"`);
- console.log(`🎤 音色: ${CONFIG.voice}`);
- console.log(`🤖 模型: ${CONFIG.model}`);
- console.log(`📁 输出: ${OUTPUT_DIR}`);
- const startTime = Date.now();
- try {
- await Promise.all([
- ttsRequest(TEXT, 1),
- ttsRequest(TEXT, 2),
- ]);
- const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
- console.log(`\n${'='.repeat(60)}`);
- console.log(`🎉 全部完成! 耗时: ${elapsed}s`);
- console.log(`📂 输出目录: ${OUTPUT_DIR}`);
- } catch (err: any) {
- console.error(`\n❌ 测试失败:`, err.message);
- process.exit(1);
- }
- }
- main();
|