From d21c18f65742d3370a94bd7a3f0e94ff710e7354 Mon Sep 17 00:00:00 2001 From: YOO_koishi <2358181935@qq.com> Date: Wed, 14 May 2025 22:43:40 +0800 Subject: [PATCH 1/4] change defualt.py --- astrbot/core/config/default.py | 11 ++++ .../core/provider/sources/volcengine_tts.py | 65 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 astrbot/core/provider/sources/volcengine_tts.py diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 5e06c19db..5eb0495a6 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -747,6 +747,17 @@ CONFIG_METADATA_2 = { "dashscope_tts_voice": "loongstella", "timeout": "20", }, + "火山引擎_TTS(API)": { + "id": "volcengine_tts", + "type": "volcengine_tts", + "enable": False, + "api_key": "", + "appid": "", + "cluster": "", + "voice_type": "xiaoyun", + "api_base": "https://openspeech.bytedance.com/api/v1/tts", + "timeout": "20", + }, }, "items": { "dashscope_tts_voice": { diff --git a/astrbot/core/provider/sources/volcengine_tts.py b/astrbot/core/provider/sources/volcengine_tts.py new file mode 100644 index 000000000..76d08f078 --- /dev/null +++ b/astrbot/core/provider/sources/volcengine_tts.py @@ -0,0 +1,65 @@ +import uuid +import base64 +import json +import requests +from ..provider import TTSProvider +from ..entities import ProviderType +from ..register import register_provider_adapter + +@register_provider_adapter( + "volcengine_tts", "火山引擎 TTS", provider_type=ProviderType.TEXT_TO_SPEECH +) +class ProviderVolcengineTTS(TTSProvider): + def __init__(self, provider_config: dict, provider_settings: dict) -> None: + super().__init__(provider_config, provider_settings) + self.api_key = provider_config.get("api_key", "") + self.appid = provider_config.get("appid", "") + self.cluster = provider_config.get("cluster", "") + self.voice_type = provider_config.get("voice_type", "xiaoyun") + self.api_base = provider_config.get("api_base", "https://openspeech.bytedance.com/api/v1/tts") + self.timeout = provider_config.get("timeout", "20") + + def _build_request_payload(self, text: str) -> dict: + return { + "app": { + "appid": self.appid, + "token": self.api_key, + "cluster": self.cluster + }, + "user": { + "uid": str(uuid.uuid4()) + }, + "audio": { + "voice_type": self.voice_type, + "encoding": "mp3", + "speed_ratio": 1.0, + "volume_ratio": 1.0, + "pitch_ratio": 1.0, + }, + "request": { + "reqid": str(uuid.uuid4()), + "text": text, + "text_type": "plain", + "operation": "query", + "with_frontend": 1, + "frontend_type": "unitTson" + } + } + + def get_audio(self, text: str) -> str: + headers = {"Authorization": f"Bearer {self.api_key}"} + payload = self._build_request_payload(text) + response = requests.post(self.api_base, json=payload, headers=headers, timeout=self.timeout) + + if response.status_code == 200: + resp_data = response.json() + if "data" in resp_data: + audio_data = base64.b64decode(resp_data["data"]) + file_path = f"data/temp/volcengine_tts_{uuid.uuid4()}.mp3" + with open(file_path, "wb") as audio_file: + audio_file.write(audio_data) + return file_path + else: + raise Exception(f"火山引擎 TTS API 返回错误: {resp_data}") + else: + raise Exception(f"火山引擎 TTS API 请求失败: {response.status_code}, {response.text}") \ No newline at end of file From db13a602742b2a2fe0d215f80cc6fe1fb5686359 Mon Sep 17 00:00:00 2001 From: YOO_koishi <2358181935@qq.com> Date: Sun, 18 May 2025 03:18:36 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=A8=20feat:=20add-volcengine-tts-supp?= =?UTF-8?q?ort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 5 +- astrbot/core/provider/manager.py | 4 + .../core/provider/sources/volcengine_tts.py | 87 +++++++++++++++---- 3 files changed, 75 insertions(+), 21 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index dd50b110f..526280130 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -803,13 +803,14 @@ CONFIG_METADATA_2 = { "火山引擎_TTS(API)": { "id": "volcengine_tts", "type": "volcengine_tts", + "provider_type": "text_to_speech", "enable": False, "api_key": "", "appid": "", "cluster": "", - "voice_type": "xiaoyun", + "voice_type": "", "api_base": "https://openspeech.bytedance.com/api/v1/tts", - "timeout": "20", + "timeout": 20, }, }, "items": { diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py index e61fbf925..80269752d 100644 --- a/astrbot/core/provider/manager.py +++ b/astrbot/core/provider/manager.py @@ -206,6 +206,10 @@ class ProviderManager: from .sources.azure_tts_source import ( AzureTTSProvider as AzureTTSProvider, ) + case "volcengine_tts": + from .sources.volcengine_tts import ( + ProviderVolcengineTTS as ProviderVolcengineTTS, + ) except (ImportError, ModuleNotFoundError) as e: logger.critical( f"加载 {provider_config['type']}({provider_config['id']}) 提供商适配器失败:{e}。可能是因为有未安装的依赖。" diff --git a/astrbot/core/provider/sources/volcengine_tts.py b/astrbot/core/provider/sources/volcengine_tts.py index 76d08f078..90680f42b 100644 --- a/astrbot/core/provider/sources/volcengine_tts.py +++ b/astrbot/core/provider/sources/volcengine_tts.py @@ -1,6 +1,10 @@ import uuid import base64 import json +import os +import traceback +import asyncio +import aiohttp import requests from ..provider import TTSProvider from ..entities import ProviderType @@ -16,8 +20,11 @@ class ProviderVolcengineTTS(TTSProvider): self.appid = provider_config.get("appid", "") self.cluster = provider_config.get("cluster", "") self.voice_type = provider_config.get("voice_type", "xiaoyun") - self.api_base = provider_config.get("api_base", "https://openspeech.bytedance.com/api/v1/tts") - self.timeout = provider_config.get("timeout", "20") + + host = "openspeech.bytedance.com" + self.api_base = provider_config.get("api_base", f"https://{host}/api/v1/tts") + + self.timeout = provider_config.get("timeout", 20) def _build_request_payload(self, text: str) -> dict: return { @@ -27,7 +34,7 @@ class ProviderVolcengineTTS(TTSProvider): "cluster": self.cluster }, "user": { - "uid": str(uuid.uuid4()) + "uid": str(uuid.uuid4()) }, "audio": { "voice_type": self.voice_type, @@ -46,20 +53,62 @@ class ProviderVolcengineTTS(TTSProvider): } } - def get_audio(self, text: str) -> str: - headers = {"Authorization": f"Bearer {self.api_key}"} + async def get_audio(self, text: str) -> str: + """异步方法获取语音文件路径""" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer;{self.api_key}" + } + payload = self._build_request_payload(text) - response = requests.post(self.api_base, json=payload, headers=headers, timeout=self.timeout) - - if response.status_code == 200: - resp_data = response.json() - if "data" in resp_data: - audio_data = base64.b64decode(resp_data["data"]) - file_path = f"data/temp/volcengine_tts_{uuid.uuid4()}.mp3" - with open(file_path, "wb") as audio_file: - audio_file.write(audio_data) - return file_path - else: - raise Exception(f"火山引擎 TTS API 返回错误: {resp_data}") - else: - raise Exception(f"火山引擎 TTS API 请求失败: {response.status_code}, {response.text}") \ No newline at end of file + + # 打印请求信息以便调试 + print(f"请求 URL: {self.api_base}") + print(f"请求头: {headers}") + print(f"请求体: {json.dumps(payload, ensure_ascii=False)[:100]}...") + + try: + # 使用 aiohttp 进行异步请求 + async with aiohttp.ClientSession() as session: + async with session.post( + self.api_base, + data=json.dumps(payload), # 使用 data 而不是 json 参数 + headers=headers, + timeout=self.timeout + ) as response: + print(f"响应状态码: {response.status}") + + # 获取响应内容 + response_text = await response.text() + print(f"响应内容: {response_text[:200]}...") + + if response.status == 200: + resp_data = json.loads(response_text) + + if "data" in resp_data: + audio_data = base64.b64decode(resp_data["data"]) + + # 确保目录存在 + os.makedirs("data/temp", exist_ok=True) + + file_path = f"data/temp/volcengine_tts_{uuid.uuid4()}.mp3" + + # 使用线程运行I/O操作,避免阻塞 + loop = asyncio.get_running_loop() + await loop.run_in_executor( + None, + lambda: open(file_path, "wb").write(audio_data) + ) + + return file_path + else: + error_msg = resp_data.get("message", "未知错误") + raise Exception(f"火山引擎 TTS API 返回错误: {error_msg}") + else: + raise Exception(f"火山引擎 TTS API 请求失败: {response.status}, {response_text}") + + except Exception as e: + # 添加更详细的异常捕获 + error_details = traceback.format_exc() + print(f"火山引擎 TTS 异常详情: {error_details}") + raise Exception(f"火山引擎 TTS 异常: {str(e)}") \ No newline at end of file From bf306a2f01d86891293134138c7da3a63ba195bd Mon Sep 17 00:00:00 2001 From: YOO_koishi <2358181935@qq.com> Date: Mon, 19 May 2025 16:16:25 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=A9=B9fix:=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0logger=E5=87=BD=E6=95=B0,=E6=B7=BB=E5=8A=A0sp?= =?UTF-8?q?eed=5Fratio=E9=80=89=E9=A1=B9,=E4=B8=BA=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E9=80=89=E9=A1=B9=E6=B7=BB=E5=8A=A0description?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 25 +++++++++++-- .../core/provider/sources/volcengine_tts.py | 35 ++++++++----------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index a47e9a4c9..cf8e68862 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -848,13 +848,34 @@ CONFIG_METADATA_2 = { "enable": False, "api_key": "", "appid": "", - "cluster": "", - "voice_type": "", + "volcengine_cluster": "", + "volcengine_voice_type": "", + "volcengine_speed_ratio": 1.0, "api_base": "https://openspeech.bytedance.com/api/v1/tts", "timeout": 20, }, }, "items": { + "volcengine_cluster": { + "type": "string", + "description": "火山引擎集群", + "hint": "可选volcano_icl或volcano_icl_concurr" + }, + "volcengine_voice_type": { + "type": "string", + "description": "火山引擎音色", + "hint": "输入S_开头的声音id(SpeakerId)" + }, + "volcengine_speed_ratio": { + "type": "float", + "description": "语速设置", + "hint": "语速设置,范围为 0.2 到 3.0,默认值为 1.0" + }, + "volcengine_volume_ratio": { + "type": "float", + "description": "音量设置", + "hint": "音量设置,范围为 0.0 到 2.0,默认值为 1.0" + }, "azure_tts_voice": { "type": "string", "description": "音色设置", diff --git a/astrbot/core/provider/sources/volcengine_tts.py b/astrbot/core/provider/sources/volcengine_tts.py index 90680f42b..e713345b4 100644 --- a/astrbot/core/provider/sources/volcengine_tts.py +++ b/astrbot/core/provider/sources/volcengine_tts.py @@ -9,6 +9,7 @@ import requests from ..provider import TTSProvider from ..entities import ProviderType from ..register import register_provider_adapter +from astrbot import logger @register_provider_adapter( "volcengine_tts", "火山引擎 TTS", provider_type=ProviderType.TEXT_TO_SPEECH @@ -18,12 +19,10 @@ class ProviderVolcengineTTS(TTSProvider): super().__init__(provider_config, provider_settings) self.api_key = provider_config.get("api_key", "") self.appid = provider_config.get("appid", "") - self.cluster = provider_config.get("cluster", "") - self.voice_type = provider_config.get("voice_type", "xiaoyun") - - host = "openspeech.bytedance.com" - self.api_base = provider_config.get("api_base", f"https://{host}/api/v1/tts") - + self.cluster = provider_config.get("volcengine_cluster", "") + self.voice_type = provider_config.get("volcengine_voice_type", "") + self.speed_ratio = provider_config.get("volcengine_speed_ratio", 1.0) + self.api_base = provider_config.get("api_base", f"https://openspeech.bytedance.com/api/v1/tts") self.timeout = provider_config.get("timeout", 20) def _build_request_payload(self, text: str) -> dict: @@ -39,7 +38,7 @@ class ProviderVolcengineTTS(TTSProvider): "audio": { "voice_type": self.voice_type, "encoding": "mp3", - "speed_ratio": 1.0, + "speed_ratio": self.speed_ratio, "volume_ratio": 1.0, "pitch_ratio": 1.0, }, @@ -57,30 +56,27 @@ class ProviderVolcengineTTS(TTSProvider): """异步方法获取语音文件路径""" headers = { "Content-Type": "application/json", - "Authorization": f"Bearer;{self.api_key}" + "Authorization": f"Bearer; {self.api_key}" } payload = self._build_request_payload(text) - # 打印请求信息以便调试 - print(f"请求 URL: {self.api_base}") - print(f"请求头: {headers}") - print(f"请求体: {json.dumps(payload, ensure_ascii=False)[:100]}...") + logger.info(f"请求头: {headers}") + logger.info(f"请求 URL: {self.api_base}") + logger.info(f"请求体: {json.dumps(payload, ensure_ascii=False)[:100]}...") try: - # 使用 aiohttp 进行异步请求 async with aiohttp.ClientSession() as session: async with session.post( self.api_base, - data=json.dumps(payload), # 使用 data 而不是 json 参数 + data=json.dumps(payload), headers=headers, timeout=self.timeout ) as response: - print(f"响应状态码: {response.status}") + logger.info(f"响应状态码: {response.status}") - # 获取响应内容 response_text = await response.text() - print(f"响应内容: {response_text[:200]}...") + logger.info(f"响应内容: {response_text[:200]}...") if response.status == 200: resp_data = json.loads(response_text) @@ -88,12 +84,10 @@ class ProviderVolcengineTTS(TTSProvider): if "data" in resp_data: audio_data = base64.b64decode(resp_data["data"]) - # 确保目录存在 os.makedirs("data/temp", exist_ok=True) file_path = f"data/temp/volcengine_tts_{uuid.uuid4()}.mp3" - # 使用线程运行I/O操作,避免阻塞 loop = asyncio.get_running_loop() await loop.run_in_executor( None, @@ -108,7 +102,6 @@ class ProviderVolcengineTTS(TTSProvider): raise Exception(f"火山引擎 TTS API 请求失败: {response.status}, {response_text}") except Exception as e: - # 添加更详细的异常捕获 error_details = traceback.format_exc() - print(f"火山引擎 TTS 异常详情: {error_details}") + logger.info(f"火山引擎 TTS 异常详情: {error_details}") raise Exception(f"火山引擎 TTS 异常: {str(e)}") \ No newline at end of file From 56bf5d38a12e6e1fe2d2068c562d49b55edb38b8 Mon Sep 17 00:00:00 2001 From: YOO_koishi <2358181935@qq.com> Date: Tue, 20 May 2025 13:51:11 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=94=A7fix:=20=E4=BF=AE=E6=94=B9logger?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E7=AD=89=E7=BA=A7=E4=B8=BAdebug=E7=BA=A7?= =?UTF-8?q?=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/provider/sources/volcengine_tts.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/astrbot/core/provider/sources/volcengine_tts.py b/astrbot/core/provider/sources/volcengine_tts.py index e713345b4..dca0196b1 100644 --- a/astrbot/core/provider/sources/volcengine_tts.py +++ b/astrbot/core/provider/sources/volcengine_tts.py @@ -61,9 +61,9 @@ class ProviderVolcengineTTS(TTSProvider): payload = self._build_request_payload(text) - logger.info(f"请求头: {headers}") - logger.info(f"请求 URL: {self.api_base}") - logger.info(f"请求体: {json.dumps(payload, ensure_ascii=False)[:100]}...") + logger.debug(f"请求头: {headers}") + logger.debug(f"请求 URL: {self.api_base}") + logger.debug(f"请求体: {json.dumps(payload, ensure_ascii=False)[:100]}...") try: async with aiohttp.ClientSession() as session: @@ -73,10 +73,10 @@ class ProviderVolcengineTTS(TTSProvider): headers=headers, timeout=self.timeout ) as response: - logger.info(f"响应状态码: {response.status}") + logger.debug(f"响应状态码: {response.status}") response_text = await response.text() - logger.info(f"响应内容: {response_text[:200]}...") + logger.debug(f"响应内容: {response_text[:200]}...") if response.status == 200: resp_data = json.loads(response_text) @@ -103,5 +103,5 @@ class ProviderVolcengineTTS(TTSProvider): except Exception as e: error_details = traceback.format_exc() - logger.info(f"火山引擎 TTS 异常详情: {error_details}") + logger.debug(f"火山引擎 TTS 异常详情: {error_details}") raise Exception(f"火山引擎 TTS 异常: {str(e)}") \ No newline at end of file