minimax-key2-tts-test.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /**
  2. * minimax-key2 TTS 测试脚本
  3. * 使用 minimax-key2 配置发送 2 个 "你好" TTS 请求
  4. * 流程: 创建任务 → 轮询状态 → 下载音频
  5. */
  6. import * as fs from 'fs';
  7. import * as path from 'path';
  8. import { fileURLToPath } from 'url';
  9. const __dirname = path.dirname(fileURLToPath(import.meta.url));
  10. // ============================================================
  11. // minimax-key2 配置 (来自 server/src/config/models.json)
  12. // ============================================================
  13. const CONFIG = {
  14. apiKey: 'sk-cp-BOVwOolqOEmHycg8MzDX_UgQ_bDEK3snjPUFuwolUCb_TxMxhhlAfYhTT0_tqRvEz5K56xpl-aOoJtj97MuAY4UIA-PCQLVetUCY56i8LFimNuQqrrq5xWA',
  15. ttsApiPath: 'https://api.minimaxi.com',
  16. model: 'speech-2.8-hd',
  17. voice: 'audiobook_female_1', // Cherry
  18. };
  19. const OUTPUT_DIR = path.resolve(__dirname, '../test-results/minimax-key2-tts');
  20. const TEXT = '你好';
  21. // ============================================================
  22. // TAR 解包工具:从 tar buffer 中提取 MP3
  23. // ============================================================
  24. function extractMp3FromTar(buffer: Buffer): Buffer | null {
  25. let offset = 0;
  26. while (offset < buffer.length - 512) {
  27. const nameSlice = buffer.subarray(offset, offset + 100);
  28. const nameEnd = nameSlice.indexOf(0);
  29. const filename = nameEnd >= 0 ? Buffer.from(nameSlice.subarray(0, nameEnd)).toString() : nameSlice.toString();
  30. if (!filename || filename.trim().length === 0) break;
  31. const sizeStr = Buffer.from(buffer.subarray(offset + 124, offset + 135)).toString().trim();
  32. const fileSize = parseInt(sizeStr, 8);
  33. const contentOffset = offset + 512;
  34. if (filename.endsWith('.mp3')) {
  35. console.log(` 📦 从 tar 中提取 ${filename} (${fileSize} bytes)`);
  36. return Buffer.from(buffer.subarray(contentOffset, contentOffset + fileSize));
  37. }
  38. const paddedSize = Math.ceil(fileSize / 512) * 512;
  39. offset = contentOffset + paddedSize;
  40. }
  41. return null;
  42. }
  43. // ============================================================
  44. // 单个 TTS 请求完整流程
  45. // ============================================================
  46. async function ttsRequest(text: string, requestId: number): Promise<void> {
  47. const tag = `[请求#${requestId}]`;
  48. console.log(`\n${'='.repeat(60)}`);
  49. console.log(`${tag} 开始 TTS 合成: "${text}"`);
  50. console.log(`${'='.repeat(60)}`);
  51. // ---- 步骤 1: 创建异步任务 ----
  52. console.log(`${tag} 📤 步骤1: 创建异步任务...`);
  53. const createRes = await fetch(`${CONFIG.ttsApiPath}/v1/t2a_async_v2`, {
  54. method: 'POST',
  55. headers: {
  56. 'Authorization': `Bearer ${CONFIG.apiKey}`,
  57. 'Content-Type': 'application/json',
  58. },
  59. body: JSON.stringify({
  60. model: CONFIG.model,
  61. text,
  62. voice_setting: {
  63. voice_id: CONFIG.voice,
  64. speed: 1,
  65. vol: 1,
  66. pitch: 1,
  67. },
  68. audio_setting: {
  69. audio_sample_rate: 32000,
  70. bitrate: 128000,
  71. format: 'mp3',
  72. channel: 1,
  73. },
  74. }),
  75. });
  76. const createData: any = await createRes.json();
  77. console.log(`${tag} 响应:`, JSON.stringify(createData, null, 2));
  78. if (createData.base_resp?.status_code !== 0) {
  79. throw new Error(`${tag} 创建任务失败: ${createData.base_resp?.status_msg || JSON.stringify(createData)}`);
  80. }
  81. const task_id = String(createData.task_id);
  82. const task_token = createData.task_token;
  83. console.log(`${tag} ✅ 任务已创建, task_id: ${task_id}`);
  84. // ---- 步骤 2: 轮询任务状态 ----
  85. console.log(`${tag} 🔄 步骤2: 轮询中...`);
  86. const maxPollMs = 2 * 60 * 1000; // 最多 2 分钟
  87. const pollInterval = 3000;
  88. const startTime = Date.now();
  89. let file_id: string | null = null;
  90. while (Date.now() - startTime < maxPollMs) {
  91. const queryRes = await fetch(
  92. `${CONFIG.ttsApiPath}/v1/query/t2a_async_query_v2?task_id=${task_id}&task_token=${task_token}`,
  93. { headers: { 'Authorization': `Bearer ${CONFIG.apiKey}` } }
  94. );
  95. const queryData: any = await queryRes.json();
  96. const status = queryData.status;
  97. process.stdout.write(`\r${tag} 状态: ${status} (${Math.round((Date.now() - startTime) / 1000)}s)`);
  98. if (status === 'Success') {
  99. file_id = queryData.file_id ? String(queryData.file_id) : null;
  100. console.log(`\n${tag} ✅ 任务完成, file_id: ${file_id}`);
  101. break;
  102. }
  103. if (status === 'Failed') {
  104. throw new Error(`${tag} 任务失败: ${queryData.status_msg || '未知错误'}`);
  105. }
  106. await new Promise(r => setTimeout(r, pollInterval));
  107. }
  108. if (!file_id) throw new Error(`${tag} 轮询超时`);
  109. // ---- 步骤 3: 下载音频 ----
  110. console.log(`${tag} ⬇️ 步骤3: 下载音频...`);
  111. const downloadRes = await fetch(
  112. `${CONFIG.ttsApiPath}/v1/files/retrieve_content?file_id=${file_id}`,
  113. { headers: { 'Authorization': `Bearer ${CONFIG.apiKey}` } }
  114. );
  115. const arrayBuffer = await downloadRes.arrayBuffer();
  116. const buffer = Buffer.from(arrayBuffer);
  117. const mp3Data = extractMp3FromTar(buffer);
  118. if (!mp3Data) throw new Error(`${tag} tar 中未找到 MP3 文件`);
  119. if (!fs.existsSync(OUTPUT_DIR)) fs.mkdirSync(OUTPUT_DIR, { recursive: true });
  120. const outputPath = path.join(OUTPUT_DIR, `output-${requestId}.mp3`);
  121. fs.writeFileSync(outputPath, mp3Data);
  122. console.log(`${tag} ✅ 音频已保存: ${outputPath} (${mp3Data.length} bytes)`);
  123. }
  124. // ============================================================
  125. // 主流程:同时发送 2 个 TTS 请求
  126. // ============================================================
  127. async function main() {
  128. console.log('🚀 MiniMax Key2 TTS 测试 - 并行 2 个请求');
  129. console.log(`📝 文本: "${TEXT}"`);
  130. console.log(`🎤 音色: ${CONFIG.voice}`);
  131. console.log(`🤖 模型: ${CONFIG.model}`);
  132. console.log(`📁 输出: ${OUTPUT_DIR}`);
  133. const startTime = Date.now();
  134. try {
  135. await Promise.all([
  136. ttsRequest(TEXT, 1),
  137. ttsRequest(TEXT, 2),
  138. ]);
  139. const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
  140. console.log(`\n${'='.repeat(60)}`);
  141. console.log(`🎉 全部完成! 耗时: ${elapsed}s`);
  142. console.log(`📂 输出目录: ${OUTPUT_DIR}`);
  143. } catch (err: any) {
  144. console.error(`\n❌ 测试失败:`, err.message);
  145. process.exit(1);
  146. }
  147. }
  148. main();