Files
banban/talkingq-url/test/volcano_tts_more.py
2026-03-24 15:04:36 +08:00

201 lines
8.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 base64
from pathlib import Path
from pydub import AudioSegment
import io
import logging
from dotenv import load_dotenv
import shutil
import uuid
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger("volcano_tts_test")
def check_dependencies():
dependencies = ["ffmpeg", "ffprobe"]
missing = []
for dep in dependencies:
if not shutil.which(dep):
missing.append(dep)
if missing:
logger.error(f"缺少必要依赖: {', '.join(missing)}")
logger.error(
"请安装缺失的依赖项。在Ubuntu上可以使用: sudo apt-get install ffmpeg"
)
return False
return True
env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "volcano.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_volcano_tts():
if not check_dependencies():
return
api_access_token = os.getenv("VOLCANO_ACCESS_TOKEN", "")
appid = os.getenv("VOLCANO_APP_ID", "")
cluster = os.getenv("VOLCANO_CLUSTER", "volcano_tts")
base_url = os.getenv(
"VOLCANO_TTS_BASE_URL", "https://openspeech.bytedance.com/api/v1/tts"
)
role_name = os.getenv("ROLE_NAME", "Dundun Chicken")
voice_type = os.getenv("VOICE_TYPE", "S_NkHcFJam1")
speed_ratio = float(os.getenv("SPEED_RATIO", "1.0"))
phrases = [
"It's being upgraded. Please don't cut off the power.",
"Enter the network configuration mode.",
"Exit the network configuration mode.",
"I'll speak louder.",
"I'll speak softer.",
"There's no internet connection. It's time to get off work.",
f"Hello , I'm {role_name}",
"Connected to the network.",
"I'm out of power.",
"The upgrade was successful.",
"There seem to be some minor issues with the upgrade. Let's try it again.",
"It's already at the maximum volume.",
"You're so talkative.",
]
file_prefixes = [
"upgrading",
"enter_network_config",
"exit_network_config",
"volume_up",
"volume_down",
"network_lost",
"wakeup",
"network_connected",
"low_energy",
"upgrade_success",
"upgrade_failed",
"max_volume",
"too_talkative"
]
assets_dir = os.getenv("ASSETS_DIR", "assets")
output_dir = Path(assets_dir) / "tts_audio"
output_dir.mkdir(parents=True, exist_ok=True)
if not api_access_token or not appid:
logger.error("缺少必要的配置: VOLCANO_ACCESS_TOKEN 或 VOLCANO_APP_ID")
return
logger.info(f"开始测试火山引擎TTS服务角色: {role_name}, 音色: {voice_type}")
for phrase, file_prefix in zip(phrases, file_prefixes):
output_file_prefix = str(output_dir / file_prefix)
unique_reqid = str(uuid.uuid4())
payload = {
"app": {
"appid": appid,
"token": "access_token",
"cluster": cluster,
},
"user": {"uid": "test_user"},
"audio": {
"voice_type": voice_type,
"encoding": "mp3",
"speed_ratio": float(speed_ratio),
},
"request": {"reqid": unique_reqid, "text": phrase, "operation": "query"},
}
headers = {
"Authorization": f"Bearer;{api_access_token}",
"Content-Type": "application/json",
}
logger.info(f"准备发送请求,文本内容: {phrase}, 请求ID: {unique_reqid}")
try:
async with aiohttp.ClientSession() as session:
async with session.post(
base_url, headers=headers, json=payload
) as response:
logid = response.headers.get('X-Tt-Logid', 'unknown')
logger.debug(f"服务端返回Logid: {logid}")
if response.status == 200:
resp_json = await response.json()
if "code" in resp_json:
code = resp_json["code"]
if code == 3000: # 成功状态码
audio_base64 = resp_json.get("data")
if audio_base64:
audio_data = base64.b64decode(audio_base64)
duration_ms = int(
resp_json.get("addition", {}).get("duration", "0")
)
duration = duration_ms / 1000 # 转换为秒
output_file = f"{output_file_prefix}.mp3"
audio_stream = io.BytesIO(audio_data)
sound = AudioSegment.from_file(
audio_stream, format="mp3"
)
sound = (
sound.set_frame_rate(16000)
.set_sample_width(2)
.set_channels(1)
)
sound.export(output_file, format="mp3", bitrate="16k")
logger.info(f"TTS合成成功! 音频时长: {duration}")
logger.info(f"保存音频到: {output_file}")
else:
error_msg = "音频数据不存在"
logger.error(f"TTS合成失败: {error_msg}")
else:
error_code = resp_json.get('code')
error_msg = resp_json.get('message', '未知错误')
error_info = get_error_description(error_code, error_msg)
logger.error(f"TTS合成失败: {error_info} (LogID: {logid})")
else:
logger.error(f"返回数据格式异常缺少code字段: {resp_json}")
else:
error_text = await response.text()
logger.error(
f"TTS合成失败状态码: {response.status}, 错误: {error_text} (LogID: {logid})"
)
except Exception as e:
logger.error(f"TTS请求出错: {str(e)}")
await asyncio.sleep(1)
def get_error_description(code, message):
"""根据错误码返回详细的错误描述"""
error_descriptions = {
3001: "无效的请求,请检查参数",
3003: "并发超限,请降低请求频率或增购并发",
3005: "后端服务忙,请稍后重试",
3006: "服务中断,请求已完成/失败之后相同reqid再次请求",
3010: "文本长度超限,请减少文本长度",
3011: "无效文本,请检查文本内容",
3030: "处理超时,请重试或检查文本",
3031: "处理错误,后端出现异常",
3032: "等待获取音频超时,请重试",
3040: "后端链路连接错误,请重试",
3050: "音色不存在请检查voice_type参数"
}
if "quota exceeded for types: xxxxxxxxx_lifetime" in message:
return "试用版用量用完,需开通正式版才能继续使用"
elif "quota exceeded for types: concurrency" in message:
return "并发超过限定值,需减少并发调用或增购并发"
elif "Init Engine Instance failed" in message:
return "voice_type或cluster参数错误"
elif "illegal input text" in message:
return "文本无效,无可合成的有效内容"
elif "requested grant not found" in message:
return "鉴权失败请检查appid和token是否正确"
elif "access denied" in message:
return "未拥有当前音色授权,请在控制台购买该音色"
return f"错误码: {code}, 错误信息: {message} - {error_descriptions.get(code, '未知错误')}"
if __name__ == "__main__":
asyncio.run(test_volcano_tts())