Files
banban/talkingq-url/test/minimax_tts_more.py
2026-05-09 16:50:19 +08:00

263 lines
9.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import asyncio
import aiohttp
import aiofiles
import binascii
import json
import logging
from pathlib import Path
from dotenv import load_dotenv
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger("minimax_tts_test")
# 加载环境变量
env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "minimax.env")
if os.path.exists(env_path):
load_dotenv(env_path)
logger.info(f"已加载环境变量文件: {env_path}")
else:
logger.warning(f"环境变量文件不存在: {env_path}")
async def test_minimax_tts():
# 获取配置
api_key = os.getenv("MINIMAX_API_KEY", "")
group_id = os.getenv("MINIMAX_GROUP_ID", "")
base_url = os.getenv("MINIMAX_BASE_URL", "https://api.minimax.chat/v1/t2a_v2")
voice_id = os.getenv("MINIMAX_VOICE_ID", "cartoon-boy-01")
if not api_key or not group_id:
logger.error("缺少必要的配置: MINIMAX_API_KEY 或 MINIMAX_GROUP_ID")
return
# 定义中英文短语对应表
phrases_dict = {
# "upgrading": {
# "zh": "升级中,请勿断电",
# "en": "Upgrading, please do not power off"
# },
# "upgrade_success": {
# "zh": "升级成功,正在重启设备",
# "en": "Upgrade successful, restarting device"
# },
# "upgrade_failed": {
# "zh": "升级失败,正在重启设备",
# "en": "Upgrade failed, restarting device"
# },
# "low_energy": {
# "zh": "感觉没能量了,罢工",
# "en": "Feeling out of energy, going on strike"
# },
# "network_connected": {
# "zh": "连上网络开始上班了",
# "en": "Connected to network, starting work"
# },
# "network_lost": {
# "zh": "没网了,下班了",
# "en": "No network, time to get off work"
# },
# "volume_down": {
# "zh": "音量已调小一点",
# "en": "Volume turned down a bit"
# },
# "max_volume": {
# "zh": "音量已最大",
# "en": "Volume is at maximum"
# },
# "volume_up": {
# "zh": "音量已调大一点",
# "en": "Volume turned up a bit"
# },
# "wakeup": {
# "zh": "我起床了",
# "en": "I'm awake now"
# },
# "enter_network_config": {
# "zh": "进入配网",
# "en": "Entering network configuration"
# },
# "exit_network_config": {
# "zh": "退出配网",
# "en": "Exiting network configuration"
# }
# "error_card":{
# "zh": "你好,卡片有误,请使用本人卡片收听留言"
# },
# "no_message":{
# "zh": "你好,已没可读留言"
# },
# "new_message":{
# "zh": "您有新的留言请注意查收"
# }
# "test":{
# "zh": "橙子我们今天去动物园玩吧?"
# }
# "welcome":{
# "zh": "你好,小朋友,想要和你的好朋友说些什么呢?"
# }
# "tts_error":{
# "zh": "没听到你的声音喔,本轮对话结束"
# },
# "message_ok":{
# "zh": "留言已收到"
# }
# "audio_save_fail":{
# "zh": "留言已收到,但是无法发送。"
# }
# "network_ok":{
# "zh": "已连上网络,可以按键或刷卡跟我对话喔。"
# },
# "network_connect":{
# "zh": "系统启动中,正在等待网络连接。"
# },
# "have_rest":{
# "zh": "小憩一下,待会儿见。"
# }
# "bind_nfc_ready":{
# "zh": "现在开始刷卡绑定设备吧。"
# },
# "bind_nfc_finish":{
# "zh": "卡片绑定成功。"
# }
"parent_online": {
"zh": "你好,你的家长已在线请留言"
}
}
# 创建输出目录
assets_dir = os.getenv("ASSETS_DIR", "assets")
output_dir = Path(assets_dir) / (voice_id + "_tts")
output_dir.mkdir(parents=True, exist_ok=True)
logger.info(f"开始测试MiniMax TTS服务音色: {voice_id}")
# 遍历所有短语进行合成
for phrase_key, phrases in phrases_dict.items():
for lang, text in phrases.items():
await synthesize_speech(
text=text,
language=lang,
file_prefix=f"{phrase_key}_{lang}",
output_dir=output_dir,
api_key=api_key,
group_id=group_id,
base_url=base_url,
voice_id=voice_id
)
async def synthesize_speech(text, language, file_prefix, output_dir, api_key, group_id, base_url, voice_id):
"""合成单个语音文件"""
output_file = output_dir / f"{file_prefix}.mp3"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
# 语言映射
lang_mapping = {
"zh": "Chinese",
# "en": "English",
# "fr": "French",
# "de": "German",
# "es": "Spanish",
# "yue": "Chinese,Yue"
}
payload = {
"model": "speech-02-turbo",
"text": text,
"stream": True,
"voice_setting": {
"voice_id": voice_id,
"speed": 1.0,
"vol": 1.0,
"pitch": 0
},
"audio_setting": {
"sample_rate": 16000,
"bitrate": 32000,
"format": "mp3",
"channel": 1
},
"language_boost": lang_mapping.get(language, "auto")
}
url = f"{base_url}?GroupId={group_id}"
try:
logger.info(f"开始合成语音: [{language}] {text}")
audio_buffer = bytearray()
async with aiohttp.ClientSession() as session:
async with session.post(url, json=payload, headers=headers) as response:
if response.status != 200:
error_text = await response.text()
logger.error(f"MiniMax TTS请求失败: {response.status}, {error_text}")
return None
logger.info("开始接收MiniMax TTS流式响应")
line_count = 0
async for line in response.content:
line_count += 1
logger.debug(f"收到第{line_count}行原始数据: {line}")
line = line.strip() # 去除换行符
if line.startswith(b'data:'):
try:
# 解析JSON数据
json_str = line[5:].decode('utf-8').strip()
if not json_str:
logger.debug("空的JSON字符串跳过")
continue
data_json = json.loads(json_str)
if "data" in data_json and "audio" in data_json["data"]:
status = data_json["data"].get("status", 1)
audio_hex = data_json["data"]["audio"]
if status == 1: # 只处理status=1(合成中)的音频数据忽略status=2(合成结束)的汇总数据
if audio_hex:
try:
audio_binary = binascii.unhexlify(audio_hex)
audio_buffer.extend(audio_binary)
except binascii.Error as e:
logger.error(f"音频数据解码失败: {e}")
elif status == 2: # 合成结束
logger.info("MiniMax TTS流式合成完成")
break
else:
logger.warning(f"响应中没有音频数据: {data_json}")
except json.JSONDecodeError as e:
logger.error(f"JSON解析失败: {e}, 原始数据: {line}")
except Exception as e:
logger.error(f"处理MiniMax TTS流式响应出错: {str(e)}")
else:
logger.debug(f"非data行跳过: {line}")
# 检查音频缓冲区大小
if len(audio_buffer) == 0:
logger.error("音频缓冲区为空,合成可能失败")
return None
logger.info(f"音频合成完成,总大小: {len(audio_buffer)} 字节")
# 保存音频文件
async with aiofiles.open(output_file, 'wb') as f:
await f.write(audio_buffer)
logger.info(f"TTS合成成功! 保存音频到: {output_file}")
return str(output_file)
except Exception as e:
logger.error(f"MiniMax TTS合成失败: {str(e)}")
return None
if __name__ == "__main__":
asyncio.run(test_minimax_tts())