From 40b9aa3a4cbaa7a55491b14c1beaf85201418f86 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 14 May 2025 19:15:13 +0800 Subject: [PATCH 1/5] style: format code --- astrbot/core/config/default.py | 65 +++++++++++++++---- .../core/pipeline/result_decorate/stage.py | 23 ++++--- 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 56bdd5bac..46654d873 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -211,9 +211,9 @@ CONFIG_METADATA_2 = { }, "items": { "kf_name": { - "description": "微信客服账号名", - "type": "string", - "hint": "可选。微信客服账号名(不是 ID)。可在 https://kf.weixin.qq.com/kf/frame#/accounts 获取" + "description": "微信客服账号名", + "type": "string", + "hint": "可选。微信客服账号名(不是 ID)。可在 https://kf.weixin.qq.com/kf/frame#/accounts 获取", }, "telegram_token": { "description": "Bot Token", @@ -798,46 +798,87 @@ CONFIG_METADATA_2 = { "azure_tts_rate": "1", "azure_tts_volume": "100", "azure_tts_subscription_key": "", - "azure_tts_region": "eastus" + "azure_tts_region": "eastus", }, }, "items": { "azure_tts_voice": { "type": "string", "description": "音色设置", - "hint": "API 音色" + "hint": "API 音色", }, "azure_tts_style": { "type": "string", "description": "风格设置", - "hint": "声音特定的讲话风格。 可以表达快乐、同情和平静等情绪。" + "hint": "声音特定的讲话风格。 可以表达快乐、同情和平静等情绪。", }, "azure_tts_role": { "type": "string", "description": "模仿设置(可选)", "hint": "讲话角色扮演。 声音可以模仿不同的年龄和性别,但声音名称不会更改。 例如,男性语音可以提高音调和改变语调来模拟女性语音,但语音名称不会更改。 如果角色缺失或不受声音的支持,则会忽略此属性。", - "options": ["Boy","Girl","YoungAdultFemale","YoungAdultMale","OlderAdultFemale","OlderAdultMale","SeniorFemale","SeniorMale","禁用"] + "options": [ + "Boy", + "Girl", + "YoungAdultFemale", + "YoungAdultMale", + "OlderAdultFemale", + "OlderAdultMale", + "SeniorFemale", + "SeniorMale", + "禁用", + ], }, "azure_tts_rate": { "type": "string", "description": "语速设置", - "hint": "指示文本的讲出速率。可在字词或句子层面应用语速。 速率变化应为原始音频的 0.5 到 2 倍。" + "hint": "指示文本的讲出速率。可在字词或句子层面应用语速。 速率变化应为原始音频的 0.5 到 2 倍。", }, "azure_tts_volume": { "type": "string", "description": "语音音量设置", - "hint": "指示语音的音量级别。 可在句子层面应用音量的变化。以从 0.0 到 100.0(从最安静到最大声,例如 75)的数字表示。 默认值为 100.0。" + "hint": "指示语音的音量级别。 可在句子层面应用音量的变化。以从 0.0 到 100.0(从最安静到最大声,例如 75)的数字表示。 默认值为 100.0。", }, "azure_tts_region": { "type": "string", "description": "API 地区", "hint": "Azure_TTS 处理数据所在区域,具体参考 https://learn.microsoft.com/zh-cn/azure/ai-services/speech-service/regions", - "options": ["southafricanorth", "eastasia", "southeastasia", "australiaeast", "centralindia", "japaneast", "japanwest", "koreacentral", "canadacentral", "northeurope", "westeurope", "francecentral", "germanywestcentral", "norwayeast", "swedencentral", "switzerlandnorth", "switzerlandwest", "uksouth", "uaenorth", "brazilsouth", "qatarcentral", "centralus", "eastus", "eastus2", "northcentralus", "southcentralus", "westcentralus", "westus", "westus2", "westus3"] + "options": [ + "southafricanorth", + "eastasia", + "southeastasia", + "australiaeast", + "centralindia", + "japaneast", + "japanwest", + "koreacentral", + "canadacentral", + "northeurope", + "westeurope", + "francecentral", + "germanywestcentral", + "norwayeast", + "swedencentral", + "switzerlandnorth", + "switzerlandwest", + "uksouth", + "uaenorth", + "brazilsouth", + "qatarcentral", + "centralus", + "eastus", + "eastus2", + "northcentralus", + "southcentralus", + "westcentralus", + "westus", + "westus2", + "westus3", + ], }, "azure_tts_subscription_key": { "type": "string", "description": "服务订阅密钥", - "hint": "Azure_TTS 服务的订阅密钥(注意不是令牌)" + "hint": "Azure_TTS 服务的订阅密钥(注意不是令牌)", }, "dashscope_tts_voice": { "description": "语音合成模型", @@ -1382,7 +1423,7 @@ CONFIG_METADATA_2 = { "description": "对外可达的回调接口地址", "type": "string", "obvious_hint": True, - "hint": "外部服务可能会通过 AstrBot 生成的回调链接(如文件下载链接)访问 AstrBot 后端。由于 AstrBot 无法自动判断部署环境中对外可达的主机地址(host),因此需要通过此配置项显式指定 “外部服务如何访问 AstrBot” 的地址。如 http://localhost:6185,https://example.com 等。" + "hint": "外部服务可能会通过 AstrBot 生成的回调链接(如文件下载链接)访问 AstrBot 后端。由于 AstrBot 无法自动判断部署环境中对外可达的主机地址(host),因此需要通过此配置项显式指定 “外部服务如何访问 AstrBot” 的地址。如 http://localhost:6185,https://example.com 等。", }, "log_level": { "description": "控制台日志级别", diff --git a/astrbot/core/pipeline/result_decorate/stage.py b/astrbot/core/pipeline/result_decorate/stage.py index 55abdfb0b..bf877cf4a 100644 --- a/astrbot/core/pipeline/result_decorate/stage.py +++ b/astrbot/core/pipeline/result_decorate/stage.py @@ -1,17 +1,18 @@ -import time import re +import time import traceback -from typing import Union, AsyncGenerator -from ..stage import Stage, register_stage, registered_stages -from ..context import PipelineContext -from astrbot.core.platform.astr_message_event import AstrMessageEvent +from typing import AsyncGenerator, Union + +from astrbot.core import html_renderer, logger +from astrbot.core.message.components import At, File, Image, Node, Plain, Record, Reply from astrbot.core.message.message_event_result import ResultContentType +from astrbot.core.platform.astr_message_event import AstrMessageEvent from astrbot.core.platform.message_type import MessageType -from astrbot.core import logger -from astrbot.core.message.components import Plain, Image, At, Reply, Record, File, Node -from astrbot.core import html_renderer -from astrbot.core.star.star_handler import star_handlers_registry, EventType from astrbot.core.star.star import star_map +from astrbot.core.star.star_handler import EventType, star_handlers_registry + +from ..context import PipelineContext +from ..stage import Stage, register_stage, registered_stages @register_stage @@ -184,7 +185,9 @@ class ResultDecorateStage(Stage): new_chain.append( Record(file=audio_path, url=audio_path) ) - if(self.ctx.astrbot_config["provider_tts_settings"]["dual_output"]): + if self.ctx.astrbot_config["provider_tts_settings"][ + "dual_output" + ]: new_chain.append(comp) else: logger.error( From d88683f4981cd311c7db1af28fc3902e663391be Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 14 May 2025 19:17:28 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat(tts):=20=E5=A2=9E=E5=8A=A0=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=96=87=E4=BB=B6=E6=9C=8D=E5=8A=A1=E6=8F=90=E4=BE=9B?= =?UTF-8?q?=20TTS=20=E8=AF=AD=E9=9F=B3=E6=96=87=E4=BB=B6=E7=9A=84=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 6 +++ .../core/pipeline/result_decorate/stage.py | 48 +++++++++++++------ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 46654d873..d9d604d68 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -66,6 +66,7 @@ DEFAULT_CONFIG = { "enable": False, "provider_id": "", "dual_output": False, + "use_file_service": False, }, "provider_ltm_settings": { "group_icl_enable": False, @@ -1307,6 +1308,11 @@ CONFIG_METADATA_2 = { "hint": "启用后,Bot 将同时输出语音和文字消息。", "obvious_hint": True, }, + "use_file_service": { + "description": "使用文件服务提供 TTS 语音文件", + "type": "bool", + "hint": "启用后,如已配置 callback_api_base ,将会使用文件服务提供TTS语音文件", + }, }, }, "provider_ltm_settings": { diff --git a/astrbot/core/pipeline/result_decorate/stage.py b/astrbot/core/pipeline/result_decorate/stage.py index bf877cf4a..920e9204a 100644 --- a/astrbot/core/pipeline/result_decorate/stage.py +++ b/astrbot/core/pipeline/result_decorate/stage.py @@ -3,7 +3,7 @@ import time import traceback from typing import AsyncGenerator, Union -from astrbot.core import html_renderer, logger +from astrbot.core import html_renderer, logger, file_token_service from astrbot.core.message.components import At, File, Image, Node, Plain, Record, Reply from astrbot.core.message.message_event_result import ResultContentType from astrbot.core.platform.astr_message_event import AstrMessageEvent @@ -178,23 +178,43 @@ class ResultDecorateStage(Stage): for comp in result.chain: if isinstance(comp, Plain) and len(comp.text) > 1: try: - logger.info("TTS 请求: " + comp.text) + logger.info(f"TTS 请求: {comp.text}") audio_path = await tts_provider.get_audio(comp.text) - logger.info("TTS 结果: " + audio_path) - if audio_path: - new_chain.append( - Record(file=audio_path, url=audio_path) - ) - if self.ctx.astrbot_config["provider_tts_settings"][ - "dual_output" - ]: - new_chain.append(comp) - else: + logger.info(f"TTS 结果: {audio_path}") + if not audio_path: logger.error( - f"由于 TTS 音频文件没找到,消息段转语音失败: {comp.text}" + f"由于 TTS 音频文件未找到,消息段转语音失败: {comp.text}" ) new_chain.append(comp) - except BaseException: + continue + + use_file_service = self.ctx.astrbot_config[ + "provider_tts_settings" + ]["use_file_service"] + callback_api_base = self.ctx.astrbot_config[ + "callback_api_base" + ] + dual_output = self.ctx.astrbot_config[ + "provider_tts_settings" + ]["dual_output"] + + url = None + if use_file_service and callback_api_base: + token = await file_token_service.register_file( + audio_path + ) + url = f"{callback_api_base}/api/file/{token}" + logger.debug(f"已注册:{url}") + + new_chain.append( + Record( + file=url if url else audio_path, + url=url if url else audio_path, + ) + ) + if dual_output: + new_chain.append(comp) + except Exception: logger.error(traceback.format_exc()) logger.error("TTS 失败,使用文本发送。") new_chain.append(comp) From 75c3d8abbd0900a19a6091ba67617ac956b442ef Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 14 May 2025 19:18:36 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat(t2i):=20=E4=B8=BA=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E8=BD=AC=E5=9B=BE=E5=83=8F=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6=E6=9C=8D=E5=8A=A1=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 6 ++++++ astrbot/core/pipeline/result_decorate/stage.py | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index d9d604d68..8712ff72b 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -92,6 +92,7 @@ DEFAULT_CONFIG = { "t2i_word_threshold": 150, "t2i_strategy": "remote", "t2i_endpoint": "", + "t2i_use_file_service": False, "http_proxy": "", "dashboard": { "enable": True, @@ -1448,6 +1449,11 @@ CONFIG_METADATA_2 = { "type": "string", "hint": "当 t2i_strategy 为 remote 时生效。为空时使用 AstrBot API 服务", }, + "t2i_use_file_service": { + "description": "本地文本转图像使用文件服务提供文件", + "type": "bool", + "hint": "当 t2i_strategy 为 local 并且配置 callback_api_base 时生效。是否使用文件服务提供文件。", + }, "pip_install_arg": { "description": "pip 安装参数", "type": "string", diff --git a/astrbot/core/pipeline/result_decorate/stage.py b/astrbot/core/pipeline/result_decorate/stage.py index 920e9204a..fd1134edd 100644 --- a/astrbot/core/pipeline/result_decorate/stage.py +++ b/astrbot/core/pipeline/result_decorate/stage.py @@ -248,6 +248,14 @@ class ResultDecorateStage(Stage): if url: if url.startswith("http"): result.chain = [Image.fromURL(url)] + elif ( + self.ctx.astrbot_config["t2i_use_file_service"] + and self.ctx.astrbot_config["callback_api_base"] + ): + token = await file_token_service.register_file(url) + url = f"{self.ctx.astrbot_config['callback_api_base']}/api/file/{token}" + logger.debug(f"已注册:{url}") + result.chain = [Image.fromURL(url)] else: result.chain = [Image.fromFileSystem(url)] From e6981290bc5e73731f52cf4b80e13f69ae87ed10 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 14 May 2025 20:05:38 +0800 Subject: [PATCH 4/5] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20Record=20?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E7=9A=84=E6=96=87=E4=BB=B6=E5=92=8C=20URL=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E8=B5=8B=E5=80=BC=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/pipeline/result_decorate/stage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/astrbot/core/pipeline/result_decorate/stage.py b/astrbot/core/pipeline/result_decorate/stage.py index fd1134edd..9adaf2da6 100644 --- a/astrbot/core/pipeline/result_decorate/stage.py +++ b/astrbot/core/pipeline/result_decorate/stage.py @@ -208,8 +208,8 @@ class ResultDecorateStage(Stage): new_chain.append( Record( - file=url if url else audio_path, - url=url if url else audio_path, + file=url or audio_path, + url=url or audio_path, ) ) if dual_output: From c5ccc1a08402973cb3312c8a58c74b9185be1037 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Thu, 15 May 2025 09:50:27 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat(Video):=20=E5=A2=9E=E5=8A=A0=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E6=B6=88=E6=81=AF=E7=BB=84=E4=BB=B6=E7=9A=84=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E8=BD=AC=E6=8D=A2=E5=92=8C=E6=B3=A8=E5=86=8C=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/message/components.py | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/astrbot/core/message/components.py b/astrbot/core/message/components.py index a7474cb77..97b712d0b 100644 --- a/astrbot/core/message/components.py +++ b/astrbot/core/message/components.py @@ -250,6 +250,51 @@ class Video(BaseMessageComponent): return Video(file=url, **_) raise Exception("not a valid url") + async def convert_to_file_path(self) -> str: + """将这个视频统一转换为本地文件路径。这个方法避免了手动判断视频数据类型,直接返回视频数据的本地路径(如果是网络 URL,则会自动进行下载)。 + + Returns: + str: 视频的本地路径,以绝对路径表示。 + """ + url = self.file + if url and url.startswith("file:///"): + return url[8:] + elif url and url.startswith("http"): + download_dir = os.path.join(get_astrbot_data_path(), "temp") + video_file_path = os.path.join(download_dir, f"{uuid.uuid4().hex}") + await download_file(url, video_file_path) + if os.path.exists(video_file_path): + return os.path.abspath(video_file_path) + else: + raise Exception(f"download failed: {url}") + elif os.path.exists(url): + return os.path.abspath(url) + else: + raise Exception(f"not a valid file: {url}") + + async def register_to_file_service(self): + """ + 将视频注册到文件服务。 + + Returns: + str: 注册后的URL + + Raises: + Exception: 如果未配置 callback_api_base + """ + callback_host = astrbot_config.get("callback_api_base") + + if not callback_host: + raise Exception("未配置 callback_api_base,文件服务不可用") + + file_path = await self.convert_to_file_path() + + token = await file_token_service.register_file(file_path) + + logger.debug(f"已注册:{callback_host}/api/file/{token}") + + return f"{callback_host}/api/file/{token}" + class At(BaseMessageComponent): type: ComponentType = "At"