From 9405ba78711351514d7bb5875c8a9acaf4fbc9fe Mon Sep 17 00:00:00 2001 From: Zhalslar Date: Mon, 16 Jun 2025 13:45:50 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat:=E6=96=B0=E5=A2=9EGPT=5FSoVIS=E9=80=82?= =?UTF-8?q?=E9=85=8D=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 166 +++++++++++++++++- astrbot/core/provider/manager.py | 4 + .../provider/sources/gsv_selfhosted_source.py | 106 +++++++++++ 3 files changed, 270 insertions(+), 6 deletions(-) create mode 100644 astrbot/core/provider/sources/gsv_selfhosted_source.py diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 32dd1b4543..27d28dc821 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -25,8 +25,8 @@ "id_whitelist_log": True, "wl_ignore_admin_on_group": True, "wl_ignore_admin_on_friend": True, - "reply_with_mention": False, - "reply_with_quote": False, + "reply_with_mention": 0.0, + "reply_with_quote": 0.0, "path_mapping": [], "segmented_reply": { "enable": False, @@ -466,13 +466,13 @@ }, "reply_with_mention": { "description": "回复时 @ 发送者", - "type": "bool", - "hint": "启用后,机器人回复消息时会 @ 发送者。实际效果以具体的平台适配器为准。", + "type": "float", + "hint": "启用后,机器人回复消息时会 @ 发送者。0.0-1.0 之间的概率值,0.0 表示从不,1.0 表示总是。实际效果以具体的平台适配器为准。", }, "reply_with_quote": { "description": "回复时引用消息", - "type": "bool", - "hint": "启用后,机器人回复消息时会引用原消息。实际效果以具体的平台适配器为准。", + "type": "float", + "hint": "启用后,机器人回复消息时会引用原消息。0.0-1.0 之间的概率值,0.0 表示从不,1.0 表示总是。实际效果以具体的平台适配器为准。", }, "path_mapping": { "description": "路径映射", @@ -800,6 +800,36 @@ "edge-tts-voice": "zh-CN-XiaoxiaoNeural", "timeout": 20, }, + "GSV TTS(本地加载)": { + "id": "gsv_tts", + "enable": False, + "type": "gsv_tts_selfhost", + "provider_type": "text_to_speech", + "api_base": "http://127.0.0.1:9880", + "gpt_weights_path": "", + "sovits_weights_path": "", + "gsv_default_parms": { + "gsv_ref_audio_path": "", + "gsv_prompt_text": "", + "gsv_prompt_lang": "zh", + "gsv_aux_ref_audio_paths": "", + "gsv_text_lang": "zh", + "gsv_top_k": 5, + "gsv_top_p": 1.0, + "gsv_temperature": 1.0, + "gsv_text_split_method": "cut3", + "gsv_batch_size": 1, + "gsv_batch_threshold": 0.75, + "gsv_split_bucket": True, + "gsv_speed_factor": 1, + "gsv_fragment_interval": 0.3, + "gsv_streaming_mode": False, + "gsv_seed": -1, + "gsv_parallel_infer": True, + "gsv_repetition_penalty": 1.35, + "gsv_media_type": "wav", + }, + }, "GSVI TTS(API)": { "id": "gsvi_tts", "type": "gsvi_tts_api", @@ -901,6 +931,130 @@ }, }, "items": { + "gpt_weights_path": { + "description": "GPT模型文件路径", + "type": "string", + "hint": "即“.ckpt”后缀的文件,请使用绝对路径,路径两端不要带双引号,不填则默认用GPT_SoVITS内置的SoVITS模型(建议直接在GPT_SoVITS中改默认模型)", + "obvious_hint": True, + }, + "sovits_weights_path": { + "description": "SoVITS模型文件路径", + "type": "string", + "hint": "即“.pth”后缀的文件,请使用绝对路径,路径两端不要带双引号,不填则默认用GPT_SoVITS内置的SoVITS模型(建议直接在GPT_SoVITS中改默认模型)", + "obvious_hint": True, + }, + "gsv_default_parms": { + "description": "GPT_SoVITS默认参数", + "hint": "参考音频文件路径、参考音频文本必填,其他参数根据个人爱好自行填写", + "type": "object", + "items": { + "gsv_ref_audio_path": { + "description": "参考音频文件路径", + "type": "string", + "hint": "必填!请使用绝对路径!路径两端不要带双引号!", + "obvious_hint": True, + }, + "gsv_prompt_text": { + "description": "参考音频文本", + "type": "string", + "hint": "必填!请填写参考音频讲述的文本", + "obvious_hint": True, + }, + "gsv_prompt_lang": { + "description": "参考音频文本语言", + "type": "string", + "hint": "请填写参考音频讲述的文本的语言,默认为中文", + }, + "gsv_aux_ref_audio_paths": { + "description": "辅助参考音频文件路径", + "type": "string", + "hint": "辅助参考音频文件,可不填", + }, + "gsv_text_lang": { + "description": "文本语言", + "type": "string", + "hint": "默认为中文", + }, + "gsv_top_k": { + "description": "生成语音的多样性", + "type": "int", + "hint": "", + }, + "gsv_top_p": { + "description": "核采样的阈值", + "type": "float", + "hint": "", + }, + "gsv_temperature": { + "description": "生成语音的随机性", + "type": "float", + "hint": "", + }, + "gsv_text_split_method": { + "description": "切分文本的方法", + "type": "string", + "hint": "可选值: `cut0`:不切分 `cut1`:四句一切 `cut2`:50字一切 `cut3`:按中文句号切 `cut4`:按英文句号切 `cut5`:按标点符号切", + "options": [ + "cut0", + "cut1", + "cut2", + "cut3", + "cut4", + "cut5", + ], + }, + "gsv_batch_size": { + "description": "批处理大小", + "type": "int", + "hint": "", + }, + "gsv_batch_threshold": { + "description": "批处理阈值", + "type": "float", + "hint": "", + }, + "gsv_split_bucket": { + "description": "将文本分割成桶以便并行处理", + "type": "bool", + "hint": "", + }, + "gsv_speed_factor": { + "description": "语音播放速度", + "type": "float", + "hint": "1为原始语速", + }, + "gsv_fragment_interval": { + "description": "语音片段之间的间隔时间", + "type": "float", + "hint": "", + }, + "gsv_streaming_mode": { + "description": "启用流模式", + "type": "bool", + "hint": "", + }, + "gsv_seed": { + "description": "随机种子", + "type": "int", + "hint": "用于结果的可重复性", + }, + "gsv_parallel_infer": { + "description": "并行执行推理", + "type": "bool", + "hint": "", + }, + "gsv_repetition_penalty": { + "description": "重复惩罚因子", + "type": "float", + "hint": "", + }, + "gsv_media_type": { + "description": "输出媒体的类型", + "type": "string", + "hint": "建议用wav", + }, + }, + }, "embedding_dimensions": { "description": "嵌入维度", "type": "int", diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py index b11a3361a9..382f469fef 100644 --- a/astrbot/core/provider/manager.py +++ b/astrbot/core/provider/manager.py @@ -225,6 +225,10 @@ async def load_provider(self, provider_config: dict): from .sources.edge_tts_source import ( ProviderEdgeTTS as ProviderEdgeTTS, ) + case "gsv_tts_selfhost": + from .sources.gsv_selfhosted_source import ( + ProviderGSVTTS as ProviderGSVTTS, + ) case "gsvi_tts_api": from .sources.gsvi_tts_source import ( ProviderGSVITTS as ProviderGSVITTS, diff --git a/astrbot/core/provider/sources/gsv_selfhosted_source.py b/astrbot/core/provider/sources/gsv_selfhosted_source.py new file mode 100644 index 0000000000..dbde29f0d5 --- /dev/null +++ b/astrbot/core/provider/sources/gsv_selfhosted_source.py @@ -0,0 +1,106 @@ + +import asyncio +import os +import uuid + +import aiohttp +from ..provider import TTSProvider +from ..entities import ProviderType +from ..register import register_provider_adapter +from astrbot import logger +from astrbot.core.utils.astrbot_path import get_astrbot_data_path + + +@register_provider_adapter( + provider_type_name="gsv_tts_selfhost", + desc=" GPT-SoVITS TTS(本地加载)", + provider_type=ProviderType.TEXT_TO_SPEECH, +) +class ProviderGSVTTS(TTSProvider): + def __init__( + self, + provider_config: dict, + provider_settings: dict, + ) -> None: + super().__init__(provider_config, provider_settings) + # 基础URL + self.api_base = provider_config.get("api_base", "http://127.0.0.1:9880") + if self.api_base.endswith("/"): + self.api_base = self.api_base[:-1] + + # 模型文件路径 + self.gpt_weights_path: str = provider_config.get("gpt_weights_path", "") + self.sovits_weights_path: str = provider_config.get("sovits_weights_path", "") + asyncio.create_task(self._set_model_weights()) + + # 默认参数 + raw_params = provider_config.get("gsv_default_parms", {}) + self.default_params: dict = { + key.removeprefix("gsv_"): str(value).lower() + for key, value in raw_params.items() + } + + # 情绪预设 + self.emotions = provider_config.get("emotions", {}) + + async def _make_request( + self, + endpoint: str, + params=None, + ) -> str | bytes: + """通用的异步请求方法""" + async with aiohttp.ClientSession() as session: + async with session.request("GET", endpoint, params=params) as response: + if response.status != 200: + return await response.text() + else: + return await response.read() + + async def _set_model_weights(self): + """设置模型""" + try: + # 设置 GPT 模型 + if self.gpt_weights_path: + gpt_endpoint = f"{self.api_base}/set_gpt_weights" + gpt_params = {"weights_path": self.gpt_weights_path} + if await self._make_request(endpoint=gpt_endpoint, params=gpt_params): + logger.info(f"成功设置 GPT 模型路径:{self.gpt_weights_path}") + else: + logger.info("GPT 模型路径未配置,将使用GPT_SoVITS内置的GPT模型") + + # 设置 SoVITS 模型 + if self.sovits_weights_path: + sovits_endpoint = f"{self.api_base}/set_sovits_weights" + sovits_params = {"weights_path": self.sovits_weights_path} + if await self._make_request( + endpoint=sovits_endpoint, params=sovits_params + ): + logger.info(f"成功设置 SoVITS 模型路径:{self.sovits_weights_path}") + else: + logger.info("SoVITS 模型路径未配置,将使用GPT_SoVITS内置的SoVITS模型") + except aiohttp.ClientError as e: + logger.error(f"设置模型路径时发生错误:{e}") + except Exception as e: + logger.error(f"发生未知错误:{e}") + + async def get_audio(self, text: str) -> str: + """实现 TTS 核心方法,根据文本内容自动切换情绪""" + endpoint = f"{self.api_base}/tts" + + params = self.default_params.copy() + params["text"] = text + + temp_dir = os.path.join(get_astrbot_data_path(), "temp") + os.makedirs(temp_dir, exist_ok=True) + path = os.path.join(temp_dir, f"gsvi_tts_{uuid.uuid4()}.wav") + + logger.debug(f"正在调用GSV语音合成接口,参数:{params}") + + result = await self._make_request(endpoint, params) + if isinstance(result, bytes): + with open(path, "wb") as f: + f.write(result) + return path + else: + raise Exception(f"GSVI TTS API 请求失败: {result}") + From 825e3dbcf53b9bc9a0b830724f06fa4743370d55 Mon Sep 17 00:00:00 2001 From: Zhalslar Date: Tue, 17 Jun 2025 09:44:09 +0800 Subject: [PATCH 2/6] Update default.py --- astrbot/core/config/default.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 27d28dc821..8eb9c0e970 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -466,13 +466,13 @@ }, "reply_with_mention": { "description": "回复时 @ 发送者", - "type": "float", - "hint": "启用后,机器人回复消息时会 @ 发送者。0.0-1.0 之间的概率值,0.0 表示从不,1.0 表示总是。实际效果以具体的平台适配器为准。", + "type": "bool", + "hint": "启用后,机器人回复消息时会 @ 发送者。实际效果以具体的平台适配器为准。", }, "reply_with_quote": { "description": "回复时引用消息", - "type": "float", - "hint": "启用后,机器人回复消息时会引用原消息。0.0-1.0 之间的概率值,0.0 表示从不,1.0 表示总是。实际效果以具体的平台适配器为准。", + "type": "bool", + "hint": "启用后,机器人回复消息时会引用原消息。实际效果以具体的平台适配器为准。", }, "path_mapping": { "description": "路径映射", From 14c29f07bd0c6552083cad17e342e022bb9d95f4 Mon Sep 17 00:00:00 2001 From: Zhalslar Date: Tue, 17 Jun 2025 10:55:35 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 5 +- .../provider/sources/gsv_selfhosted_source.py | 133 ++++++++++++------ 2 files changed, 90 insertions(+), 48 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 8eb9c0e970..61f9115117 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -25,8 +25,8 @@ "id_whitelist_log": True, "wl_ignore_admin_on_group": True, "wl_ignore_admin_on_friend": True, - "reply_with_mention": 0.0, - "reply_with_quote": 0.0, + "reply_with_mention": False, + "reply_with_quote": False, "path_mapping": [], "segmented_reply": { "enable": False, @@ -808,6 +808,7 @@ "api_base": "http://127.0.0.1:9880", "gpt_weights_path": "", "sovits_weights_path": "", + "timeout": 60, "gsv_default_parms": { "gsv_ref_audio_path": "", "gsv_prompt_text": "", diff --git a/astrbot/core/provider/sources/gsv_selfhosted_source.py b/astrbot/core/provider/sources/gsv_selfhosted_source.py index dbde29f0d5..ff0083a1a6 100644 --- a/astrbot/core/provider/sources/gsv_selfhosted_source.py +++ b/astrbot/core/provider/sources/gsv_selfhosted_source.py @@ -23,78 +23,101 @@ def __init__( provider_settings: dict, ) -> None: super().__init__(provider_config, provider_settings) - # 基础URL - self.api_base = provider_config.get("api_base", "http://127.0.0.1:9880") - if self.api_base.endswith("/"): - self.api_base = self.api_base[:-1] - # 模型文件路径 + self.api_base = provider_config.get("api_base", "http://127.0.0.1:9880").rstrip( + "/" + ) self.gpt_weights_path: str = provider_config.get("gpt_weights_path", "") self.sovits_weights_path: str = provider_config.get("sovits_weights_path", "") - asyncio.create_task(self._set_model_weights()) - # 默认参数 - raw_params = provider_config.get("gsv_default_parms", {}) + # TTS 请求的默认参数,移除前缀gsv_ self.default_params: dict = { key.removeprefix("gsv_"): str(value).lower() - for key, value in raw_params.items() + for key, value in provider_config.get("gsv_default_parms", {}).items() } - - # 情绪预设 - self.emotions = provider_config.get("emotions", {}) - - async def _make_request( - self, - endpoint: str, - params=None, - ) -> str | bytes: - """通用的异步请求方法""" - async with aiohttp.ClientSession() as session: - async with session.request("GET", endpoint, params=params) as response: - if response.status != 200: - return await response.text() - else: + self.timeout = provider_config.get("timeout", 60) + self._session = aiohttp.ClientSession( + timeout=aiohttp.ClientTimeout(total=self.timeout) + ) + asyncio.create_task(self._async_init()).add_done_callback( + self._handle_init_exception + ) + + async def _async_init(self): + await self._set_model_weights() + + def _handle_init_exception(self, task: asyncio.Task): + if task.exception(): + logger.error(f"[GSV TTS] 初始化失败:{task.exception()}") + + def get_session(self) -> aiohttp.ClientSession: + if not self._session or self._session.closed: + raise RuntimeError("[GSV TTS] Provider HTTP session is not ready or closed.") + return self._session + + async def _make_request(self, endpoint: str, params=None, retries: int = 3) -> bytes | None: + """发起请求""" + for attempt in range(retries): + logger.debug(f"[GSV TTS] 请求地址:{endpoint},参数:{params}") + try: + async with self.get_session().get(endpoint, params=params) as response: + if response.status != 200: + error_text = await response.text() + raise Exception( + f"[GSV TTS] Request to {endpoint} failed with status {response.status}: {error_text}" + ) return await response.read() + except Exception as e: + if attempt < retries - 1: + logger.warning( + f"[GSV TTS] 请求 {endpoint} 第 {attempt + 1} 次失败:{e},重试中..." + ) + await asyncio.sleep(1) + else: + logger.error(f"[GSV TTS] 请求 {endpoint} 最终失败:{e}") + raise async def _set_model_weights(self): - """设置模型""" + """设置模型路径""" try: - # 设置 GPT 模型 if self.gpt_weights_path: - gpt_endpoint = f"{self.api_base}/set_gpt_weights" - gpt_params = {"weights_path": self.gpt_weights_path} - if await self._make_request(endpoint=gpt_endpoint, params=gpt_params): - logger.info(f"成功设置 GPT 模型路径:{self.gpt_weights_path}") + await self._make_request( + f"{self.api_base}/set_gpt_weights", + {"weights_path": self.gpt_weights_path}, + ) + logger.info(f"[GSV TTS] 成功设置 GPT 模型路径:{self.gpt_weights_path}") else: - logger.info("GPT 模型路径未配置,将使用GPT_SoVITS内置的GPT模型") + logger.info("[GSV TTS] GPT 模型路径未配置,将使用内置 GPT 模型") - # 设置 SoVITS 模型 if self.sovits_weights_path: - sovits_endpoint = f"{self.api_base}/set_sovits_weights" - sovits_params = {"weights_path": self.sovits_weights_path} - if await self._make_request( - endpoint=sovits_endpoint, params=sovits_params - ): - logger.info(f"成功设置 SoVITS 模型路径:{self.sovits_weights_path}") + await self._make_request( + f"{self.api_base}/set_sovits_weights", + {"weights_path": self.sovits_weights_path}, + ) + logger.info( + f"[GSV TTS] 成功设置 SoVITS 模型路径:{self.sovits_weights_path}" + ) else: - logger.info("SoVITS 模型路径未配置,将使用GPT_SoVITS内置的SoVITS模型") + logger.info("[GSV TTS] SoVITS 模型路径未配置,将使用内置 SoVITS 模型") except aiohttp.ClientError as e: - logger.error(f"设置模型路径时发生错误:{e}") + logger.error(f"[GSV TTS] 设置模型路径时发生网络错误:{e}") except Exception as e: - logger.error(f"发生未知错误:{e}") + logger.error(f"[GSV TTS] 设置模型路径时发生未知错误:{e}") async def get_audio(self, text: str) -> str: """实现 TTS 核心方法,根据文本内容自动切换情绪""" + if not text: + raise ValueError("[GSV TTS] TTS 文本不能为空") + endpoint = f"{self.api_base}/tts" - params = self.default_params.copy() - params["text"] = text + params = self.build_synthesis_params(text) temp_dir = os.path.join(get_astrbot_data_path(), "temp") os.makedirs(temp_dir, exist_ok=True) - path = os.path.join(temp_dir, f"gsvi_tts_{uuid.uuid4()}.wav") + path = os.path.join(temp_dir, f"gsv_tts_{uuid.uuid4().hex}.wav") - logger.debug(f"正在调用GSV语音合成接口,参数:{params}") + logger.debug(f"[GSV TTS] 正在调用语音合成接口,参数:{params}") result = await self._make_request(endpoint, params) if isinstance(result, bytes): @@ -102,5 +125,23 @@ async def get_audio(self, text: str) -> str: f.write(result) return path else: - raise Exception(f"GSVI TTS API 请求失败: {result}") + raise Exception(f"[GSV TTS] 合成失败,输入文本:{text},错误信息:{result}") + + def build_synthesis_params(self, text: str) -> dict: + """ + 构建语音合成所需的参数字典。 + + 当前仅包含默认参数 + 文本,未来可在此基础上动态添加如情绪、角色等语义控制字段。 + """ + params = self.default_params.copy() + params["text"] = text + # TODO: 在此处添加情绪分析,例如 params["emotion"] = detect_emotion(text) + return params + + async def shutdown(self): + if self._session and not self._session.closed: + await self._session.close() + def __del__(self): + if hasattr(self, "_session") and self._session and not self._session.closed: + logger.warning("[GSV TTS] ProviderGSVTTS 已被销毁但 session 未关闭,请确保调用 shutdown()") From b251ee93225b8177661b84e7978c2023f31a0a64 Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Wed, 18 Jun 2025 23:45:59 +0800 Subject: [PATCH 4/6] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E7=A9=BA?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- astrbot/core/provider/sources/gsv_selfhosted_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astrbot/core/provider/sources/gsv_selfhosted_source.py b/astrbot/core/provider/sources/gsv_selfhosted_source.py index ff0083a1a6..88f68e9532 100644 --- a/astrbot/core/provider/sources/gsv_selfhosted_source.py +++ b/astrbot/core/provider/sources/gsv_selfhosted_source.py @@ -106,7 +106,7 @@ async def _set_model_weights(self): async def get_audio(self, text: str) -> str: """实现 TTS 核心方法,根据文本内容自动切换情绪""" - if not text: + if not text.strip(): raise ValueError("[GSV TTS] TTS 文本不能为空") endpoint = f"{self.api_base}/tts" From 17893931514bd2b45d55d42f7b6bfb6ba08d1fce Mon Sep 17 00:00:00 2001 From: Zhalslar Date: Thu, 19 Jun 2025 00:52:03 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E6=8F=90=E4=BE=9Binitialize=E5=92=8Ctermin?= =?UTF-8?q?ate=E6=96=B9=E6=B3=95=E5=AF=B9=E6=8E=A5=E4=B8=8A=E6=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../provider/sources/gsv_selfhosted_source.py | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/astrbot/core/provider/sources/gsv_selfhosted_source.py b/astrbot/core/provider/sources/gsv_selfhosted_source.py index ff0083a1a6..d6379e244c 100644 --- a/astrbot/core/provider/sources/gsv_selfhosted_source.py +++ b/astrbot/core/provider/sources/gsv_selfhosted_source.py @@ -36,19 +36,20 @@ def __init__( for key, value in provider_config.get("gsv_default_parms", {}).items() } self.timeout = provider_config.get("timeout", 60) + self._session: aiohttp.ClientSession | None = None + + + async def initialize(self): + """异步初始化:在 ProviderManager 中被调用""" self._session = aiohttp.ClientSession( timeout=aiohttp.ClientTimeout(total=self.timeout) ) - asyncio.create_task(self._async_init()).add_done_callback( - self._handle_init_exception - ) - - async def _async_init(self): - await self._set_model_weights() - - def _handle_init_exception(self, task: asyncio.Task): - if task.exception(): - logger.error(f"[GSV TTS] 初始化失败:{task.exception()}") + try: + await self._set_model_weights() + logger.info("[GSV TTS] 初始化完成") + except Exception as e: + logger.error(f"[GSV TTS] 初始化失败:{e}") + raise def get_session(self) -> aiohttp.ClientSession: if not self._session or self._session.closed: @@ -138,10 +139,10 @@ def build_synthesis_params(self, text: str) -> dict: # TODO: 在此处添加情绪分析,例如 params["emotion"] = detect_emotion(text) return params - async def shutdown(self): + async def terminate(self): + """终止释放资源:在 ProviderManager 中被调用""" if self._session and not self._session.closed: await self._session.close() + logger.info("[GSV TTS] Session 已关闭") + - def __del__(self): - if hasattr(self, "_session") and self._session and not self._session.closed: - logger.warning("[GSV TTS] ProviderGSVTTS 已被销毁但 session 未关闭,请确保调用 shutdown()") From dc62c1f8d4444df9f7bebf65e98231d63c77d684 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 21 Jun 2025 23:56:06 +0800 Subject: [PATCH 6/6] style: code format --- .../core/provider/sources/gsv_selfhosted_source.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/astrbot/core/provider/sources/gsv_selfhosted_source.py b/astrbot/core/provider/sources/gsv_selfhosted_source.py index e3449d9217..6c4d872a9b 100644 --- a/astrbot/core/provider/sources/gsv_selfhosted_source.py +++ b/astrbot/core/provider/sources/gsv_selfhosted_source.py @@ -1,4 +1,3 @@ - import asyncio import os import uuid @@ -13,7 +12,7 @@ @register_provider_adapter( provider_type_name="gsv_tts_selfhost", - desc=" GPT-SoVITS TTS(本地加载)", + desc="GPT-SoVITS TTS(本地加载)", provider_type=ProviderType.TEXT_TO_SPEECH, ) class ProviderGSVTTS(TTSProvider): @@ -38,7 +37,6 @@ def __init__( self.timeout = provider_config.get("timeout", 60) self._session: aiohttp.ClientSession | None = None - async def initialize(self): """异步初始化:在 ProviderManager 中被调用""" self._session = aiohttp.ClientSession( @@ -53,10 +51,14 @@ async def initialize(self): def get_session(self) -> aiohttp.ClientSession: if not self._session or self._session.closed: - raise RuntimeError("[GSV TTS] Provider HTTP session is not ready or closed.") + raise RuntimeError( + "[GSV TTS] Provider HTTP session is not ready or closed." + ) return self._session - async def _make_request(self, endpoint: str, params=None, retries: int = 3) -> bytes | None: + async def _make_request( + self, endpoint: str, params=None, retries: int = 3 + ) -> bytes | None: """发起请求""" for attempt in range(retries): logger.debug(f"[GSV TTS] 请求地址:{endpoint},参数:{params}") @@ -144,5 +146,3 @@ async def terminate(self): if self._session and not self._session.closed: await self._session.close() logger.info("[GSV TTS] Session 已关闭") - -