add banbanmini backend

This commit is contained in:
HycJack
2026-03-24 15:04:36 +08:00
parent 0d0f995dc2
commit 7510ca6df1
197 changed files with 13008 additions and 0 deletions

View File

@@ -0,0 +1,202 @@
import os
import uuid
import aiohttp
import aiofiles
import binascii
import json
from typing import Optional, List, Any, AsyncGenerator
from config import settings
from interfaces.tts import TTS
from implementations.base_service import BaseService
from utils.logger import session_logger
from services.tts_audio_cleaner import TTSAudioCleaner
class MiniMaxTTS(TTS, BaseService):
def __init__(self, selected_role=None):
BaseService.__init__(self, "tts", selected_role)
self.api_key = settings.minimax_api_key
self.group_id = settings.minimax_group_id
self.base_url = settings.minimax_base_url
self.connected = False
self.device_id = "unknown"
self.session_id = "unknown"
self.voice_id = "cartoon-boy-01" # 默认音色
if selected_role and "minimax_voice_id" in selected_role:
self.voice_id = selected_role["minimax_voice_id"]
self.model = "speech-02-turbo"
self.selected_role = selected_role
async def connect(self):
"""连接到MiniMax服务"""
self.connected = True
session_logger.info(
self.device_id,
self.session_id,
f"MiniMax TTS服务已连接使用音色: {self.voice_id}"
)
async def close(self):
"""关闭MiniMax服务连接"""
self.connected = False
async def tts(
self,
text: str,
output_file_prefix: Optional[str] = None,
tts_format: str = "mp3",
selected_role: dict = None,
session: Optional[Any] = None,
language: str = None,
) -> Optional[List[str]]:
"""
将文本转换为语音
返回值为包含音频URL的列表
"""
if selected_role:
self.selected_role = selected_role
if "minimax_voice_id" in selected_role:
self.voice_id = selected_role["minimax_voice_id"]
if session and hasattr(session, "device_id") and session.device_id:
self.device_id = session.device_id
if session and hasattr(session, "session_id") and session.session_id:
self.session_id = session.session_id
unique_id = f"{self.session_id}_{uuid.uuid4().hex[:8]}"
if output_file_prefix:
output_file = f"{output_file_prefix}.{tts_format}"
else:
await TTSAudioCleaner.prepare_output_directory()
tts_dir = os.path.join(settings.assets_dir, "tts_audio")
output_file = os.path.join(tts_dir, f"{unique_id}.{tts_format}")
try:
session_logger.info(
self.device_id,
self.session_id,
f"开始MiniMax流式语音合成文本长度: {len(text)},输出文件: {output_file}"
)
urls = await self._process_stream(text, output_file, language)
return urls if urls else None
except Exception as e:
session_logger.error(
self.device_id,
self.session_id,
f"MiniMax TTS合成失败: {str(e)}",
exc_info=True
)
return None
async def _process_stream(self, text: str, output_file: str, language: str = None) -> Optional[List[str]]:
"""使用流式方式处理文本并返回URL"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": self.model,
"text": text,
"stream": True,
"voice_setting": {
"voice_id": self.voice_id,
"speed": 1.0,
"vol": 2.0,
"pitch": 0
},
"audio_setting": {
"sample_rate": 16000,
"bitrate": 32000,
"format": "mp3",
"channel": 1
}
}
if language:
lang_mapping = {
"zh": "Chinese",
"en": "English",
"fr": "French",
"de": "German",
"es": "Spanish",
"yue": "Chinese,Yue"
}
payload["language_boost"] = lang_mapping.get(language, "auto")
else:
payload["language_boost"] = "auto"
url = f"{self.base_url}?GroupId={self.group_id}"
try:
session_logger.info(
self.device_id,
self.session_id,
f"发送MiniMax TTS流式请求: {url}"
)
audio_buffer = bytearray()
async with aiohttp.ClientSession() as client_session:
async with client_session.post(url, json=payload, headers=headers) as response:
if response.status != 200:
error_text = await response.text()
session_logger.error(
self.device_id,
self.session_id,
f"MiniMax TTS请求失败: {response.status}, {error_text}"
)
return None
session_logger.info(
self.device_id,
self.session_id,
"开始接收MiniMax TTS流式响应"
)
async for line in response.content:
if line.startswith(b'data:'):
try:
data_json = json.loads(line[5:])
if "data" in data_json and "audio" in data_json["data"]:
status = data_json["data"].get("status", 1)
if status == 1: # 只处理status=1(合成中)的音频数据忽略status=2(合成结束)的汇总数据
audio_hex = data_json["data"]["audio"]
audio_binary = binascii.unhexlify(audio_hex)
audio_buffer.extend(audio_binary)
if status == 2: # 合成结束
session_logger.info(
self.device_id,
self.session_id,
"MiniMax TTS流式合成完成"
)
except Exception as e:
session_logger.error(
self.device_id,
self.session_id,
f"处理MiniMax TTS流式响应出错: {str(e)}",
exc_info=True
)
async with aiofiles.open(output_file, 'wb') as f:
await f.write(audio_buffer)
relative_path = os.path.relpath(output_file, settings.assets_dir)
url = f"assets/{relative_path}"
session_logger.info(
self.device_id,
self.session_id,
f"MiniMax TTS流式合成完成生成URL: {url}"
)
return [url]
except Exception as e:
session_logger.error(
self.device_id,
self.session_id,
f"MiniMax TTS流式处理失败: {str(e)}",
exc_info=True
)
return None