From d35a132279d8ba2533ba5af3460bd047029ae199 Mon Sep 17 00:00:00 2001 From: xiewoc <70128845+xiewoc@users.noreply.github.com> Date: Tue, 30 Sep 2025 20:35:21 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0audio=5Fto=5Ftencent=5Fsi?= =?UTF-8?q?lk()=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/utils/tencent_record_helper.py | 72 ++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/astrbot/core/utils/tencent_record_helper.py b/astrbot/core/utils/tencent_record_helper.py index 2c97a01ed..9730b95ac 100644 --- a/astrbot/core/utils/tencent_record_helper.py +++ b/astrbot/core/utils/tencent_record_helper.py @@ -102,7 +102,6 @@ async def convert_to_pcm_wav(input_path: str, output_path: str) -> str: else: raise RuntimeError("生成的WAV文件不存在或为空") - async def audio_to_tencent_silk_base64(audio_path: str) -> tuple[str, float]: """ 将 MP3/WAV 文件转为 Tencent Silk 并返回 base64 编码与时长(秒)。 @@ -117,7 +116,7 @@ async def audio_to_tencent_silk_base64(audio_path: str) -> tuple[str, float]: try: import pilk except ImportError as e: - raise Exception("未安装 pilk: pip install pilk") from e + raise Exception("pilk 模块未安装,请前往管理面板->控制台->安装pip库 安装 pilk 这个库") from e temp_dir = os.path.join(get_astrbot_data_path(), "temp") os.makedirs(temp_dir, exist_ok=True) @@ -158,3 +157,72 @@ async def audio_to_tencent_silk_base64(audio_path: str) -> tuple[str, float]: os.remove(wav_path) if os.path.exists(silk_path): os.remove(silk_path) + +async def audio_to_tencent_silk(audio_path: str, output_path: str) -> float: + """ + 将 MP3/WAV 文件转为 Tencent Silk 并返回时长(秒)。 + + 参数: + - audio_path: 输入音频文件路径(.mp3 或 .wav) + - output_path: 输出的音频路径-> silk + + 返回: + - duration: 音频时长(秒) + """ + try: + import pilk + except ImportError as e: + raise Exception("pilk 模块未安装,请前往管理面板->控制台->安装pip库 安装 pilk 这个库") from e + + # 确保输入文件存在 + if not os.path.exists(audio_path): + raise FileNotFoundError(f"音频文件不存在: {audio_path}") + + temp_dir = os.path.join(get_astrbot_data_path(), "temp") + os.makedirs(temp_dir, exist_ok=True) + + # 检查文件扩展名 + ext = os.path.splitext(audio_path)[1].lower() + + # 创建临时 WAV 文件 + temp_wav = tempfile.NamedTemporaryFile( + suffix=".wav", delete=False, dir=temp_dir + ).name + + wav_path = audio_path # 默认使用原文件路径 + + # 如果不是 WAV 格式,需要转换 + if ext != ".wav": + try: + await convert_to_pcm_wav(audio_path, temp_wav) + wav_path = temp_wav + except Exception as e: + # 如果转换失败,清理临时文件 + if os.path.exists(temp_wav): + os.remove(temp_wav) + raise Exception(f"音频格式转换失败: {e}") from e + + try: + with wave.open(wav_path, "rb") as wav_file: + rate = wav_file.getframerate() + + # 转换为 Silk 格式 + silk_duration = await asyncio.to_thread( + pilk.encode, wav_path, output_path, pcm_rate=rate, tencent=True + ) + + return silk_duration + + except Exception as e: + # 如果转换失败,删除可能已创建的部分输出文件 + if os.path.exists(output_path): + os.remove(output_path) + raise Exception(f"Silk 格式转换失败: {e}") from e + + finally: + # 清理临时 WAV 文件(如果是新创建的) + if wav_path != audio_path and os.path.exists(wav_path): + try: + os.remove(wav_path) + except: + pass # 忽略清理错误 From 891e3004915ea2a5cd278eda02e3e5e33a7deb5b Mon Sep 17 00:00:00 2001 From: xiewoc <70128845+xiewoc@users.noreply.github.com> Date: Tue, 30 Sep 2025 20:40:35 +0800 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20=E6=9B=B4=E6=94=B9=E4=BA=86=E5=B0=86?= =?UTF-8?q?=E9=9F=B3=E9=A2=91=E8=BD=AC=E6=8D=A2=E4=B8=BAsilk=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E7=9A=84=E6=96=B9=E6=B3=95=EF=BC=8C=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E5=8F=91=E9=80=81mp3=E7=AD=89=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E7=9A=84=E9=9F=B3=E9=A2=91=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../platform/sources/qqofficial/qqofficial_message_event.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py index 2096237ce..2dca3f92f 100644 --- a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +++ b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py @@ -6,7 +6,7 @@ import base64 import aiofiles from astrbot.core.utils.io import file_to_base64, download_image_by_url -from astrbot.core.utils.tencent_record_helper import wav_to_tencent_silk +from astrbot.core.utils.tencent_record_helper import audio_to_tencent_silk from astrbot.core.utils.astrbot_path import get_astrbot_data_path from astrbot.api.event import AstrMessageEvent, MessageChain from astrbot.api.platform import AstrBotMessage, PlatformMetadata @@ -294,7 +294,7 @@ async def _parse_to_qqofficial(message: MessageChain): temp_dir, f"{uuid.uuid4()}.silk" ) try: - duration = await wav_to_tencent_silk( + duration = await audio_to_tencent_silk( record_wav_path, record_tecent_silk_path ) if duration > 0: From e611b362fe67139e901227df1f3f8e0ef705a2b4 Mon Sep 17 00:00:00 2001 From: xiewoc <70128845+xiewoc@users.noreply.github.com> Date: Tue, 30 Sep 2025 20:46:08 +0800 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20=E6=9B=B4=E6=94=B9=E4=BA=86=E5=B0=86?= =?UTF-8?q?=E9=9F=B3=E9=A2=91=E8=BD=AC=E6=8D=A2=E4=B8=BAsilk=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E7=9A=84=E6=96=B9=E6=B3=95=EF=BC=8C=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E5=8F=91=E9=80=81mp3=E7=AD=89=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E7=9A=84=E9=9F=B3=E9=A2=91=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qqofficial/qqofficial_message_event.py | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py index 2dca3f92f..c44c258cb 100644 --- a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +++ b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py @@ -271,40 +271,49 @@ async def _parse_to_qqofficial(message: MessageChain): image_base64 = None # only one img supported image_file_path = None record_file_path = None - for i in message.chain: - if isinstance(i, Plain): - plain_text += i.text - elif isinstance(i, Image) and not image_base64: - if i.file and i.file.startswith("file:///"): - image_base64 = file_to_base64(i.file[8:]) - image_file_path = i.file[8:] - elif i.file and i.file.startswith("http"): - image_file_path = await download_image_by_url(i.file) + + for element in message.chain: + if isinstance(element, Plain): + plain_text += element.text + + elif isinstance(element, Image) and not image_base64: + if element.file and element.file.startswith("file:///"): + image_base64 = file_to_base64(element.file[8:]) + image_file_path = element.file[8:] + elif element.file and element.file.startswith("http"): + image_file_path = await download_image_by_url(element.file) image_base64 = file_to_base64(image_file_path) - elif i.file and i.file.startswith("base64://"): - image_base64 = i.file + elif element.file and element.file.startswith("base64://"): + image_base64 = element.file[9:] # 直接去掉前缀 else: - image_base64 = file_to_base64(i.file) - image_base64 = image_base64.removeprefix("base64://") - elif isinstance(i, Record): - if i.file: - record_wav_path = await i.convert_to_file_path() # wav 路径 + image_base64 = file_to_base64(element.file) + # 确保去掉 base64 前缀 + if image_base64 and image_base64.startswith("base64://"): + image_base64 = image_base64[9:] + + elif isinstance(element, Record): + if element.file: + record_wav_path = await element.convert_to_file_path() # wav 路径 temp_dir = os.path.join(get_astrbot_data_path(), "temp") - record_tecent_silk_path = os.path.join( + os.makedirs(temp_dir, exist_ok=True) # 确保目录存在 + + record_tencent_silk_path = os.path.join( temp_dir, f"{uuid.uuid4()}.silk" ) try: duration = await audio_to_tencent_silk( - record_wav_path, record_tecent_silk_path + record_wav_path, record_tencent_silk_path ) if duration > 0: - record_file_path = record_tecent_silk_path + record_file_path = record_tencent_silk_path else: record_file_path = None logger.error("转换音频格式时出错:音频时长不大于0") except Exception as e: logger.error(f"处理语音时出错: {e}") record_file_path = None + else: - logger.debug(f"qq_official 忽略 {i.type}") + logger.debug(f"qq_official 忽略 {element.type}") + return plain_text, image_base64, image_file_path, record_file_path From 37659d8dc769b8c32fc6351df858f1029ab4679b Mon Sep 17 00:00:00 2001 From: xiewoc <70128845+xiewoc@users.noreply.github.com> Date: Tue, 30 Sep 2025 21:08:40 +0800 Subject: [PATCH 4/6] =?UTF-8?q?fix:=20=E6=9B=B4=E6=94=B9=E4=BA=86=E5=B0=86?= =?UTF-8?q?=E9=9F=B3=E9=A2=91=E8=BD=AC=E6=8D=A2=E4=B8=BAsilk=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E7=9A=84=E6=96=B9=E6=B3=95=EF=BC=8C=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E5=8F=91=E9=80=81mp3=E7=AD=89=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E7=9A=84=E9=9F=B3=E9=A2=91=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sources/qqofficial/qqofficial_message_event.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py index c44c258cb..42b0b6cec 100644 --- a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +++ b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py @@ -271,11 +271,11 @@ async def _parse_to_qqofficial(message: MessageChain): image_base64 = None # only one img supported image_file_path = None record_file_path = None - + for element in message.chain: if isinstance(element, Plain): plain_text += element.text - + elif isinstance(element, Image) and not image_base64: if element.file and element.file.startswith("file:///"): image_base64 = file_to_base64(element.file[8:]) @@ -290,13 +290,13 @@ async def _parse_to_qqofficial(message: MessageChain): # 确保去掉 base64 前缀 if image_base64 and image_base64.startswith("base64://"): image_base64 = image_base64[9:] - + elif isinstance(element, Record): if element.file: record_wav_path = await element.convert_to_file_path() # wav 路径 temp_dir = os.path.join(get_astrbot_data_path(), "temp") os.makedirs(temp_dir, exist_ok=True) # 确保目录存在 - + record_tencent_silk_path = os.path.join( temp_dir, f"{uuid.uuid4()}.silk" ) @@ -312,8 +312,8 @@ async def _parse_to_qqofficial(message: MessageChain): except Exception as e: logger.error(f"处理语音时出错: {e}") record_file_path = None - + else: logger.debug(f"qq_official 忽略 {element.type}") - + return plain_text, image_base64, image_file_path, record_file_path From 1929329abd6b4a16a9f8a81686ab6e8b8ed16a10 Mon Sep 17 00:00:00 2001 From: xiewoc <70128845+xiewoc@users.noreply.github.com> Date: Tue, 30 Sep 2025 21:09:17 +0800 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0audio=5Fto=5Ftenc?= =?UTF-8?q?ent=5Fsilk()=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/utils/tencent_record_helper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/astrbot/core/utils/tencent_record_helper.py b/astrbot/core/utils/tencent_record_helper.py index 9730b95ac..df5baf573 100644 --- a/astrbot/core/utils/tencent_record_helper.py +++ b/astrbot/core/utils/tencent_record_helper.py @@ -183,14 +183,14 @@ async def audio_to_tencent_silk(audio_path: str, output_path: str) -> float: # 检查文件扩展名 ext = os.path.splitext(audio_path)[1].lower() - + # 创建临时 WAV 文件 temp_wav = tempfile.NamedTemporaryFile( suffix=".wav", delete=False, dir=temp_dir ).name wav_path = audio_path # 默认使用原文件路径 - + # 如果不是 WAV 格式,需要转换 if ext != ".wav": try: @@ -224,5 +224,5 @@ async def audio_to_tencent_silk(audio_path: str, output_path: str) -> float: if wav_path != audio_path and os.path.exists(wav_path): try: os.remove(wav_path) - except: + except Exception: pass # 忽略清理错误 From 8b33d8d305c1851ae458b56ee99f2eda14ff749f Mon Sep 17 00:00:00 2001 From: xiewoc <70128845+xiewoc@users.noreply.github.com> Date: Tue, 30 Sep 2025 21:16:57 +0800 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0audio=5Fto=5Ftenc?= =?UTF-8?q?ent=5Fsilk()=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/utils/tencent_record_helper.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/astrbot/core/utils/tencent_record_helper.py b/astrbot/core/utils/tencent_record_helper.py index df5baf573..63575d0dc 100644 --- a/astrbot/core/utils/tencent_record_helper.py +++ b/astrbot/core/utils/tencent_record_helper.py @@ -102,6 +102,7 @@ async def convert_to_pcm_wav(input_path: str, output_path: str) -> str: else: raise RuntimeError("生成的WAV文件不存在或为空") + async def audio_to_tencent_silk_base64(audio_path: str) -> tuple[str, float]: """ 将 MP3/WAV 文件转为 Tencent Silk 并返回 base64 编码与时长(秒)。 @@ -116,7 +117,9 @@ async def audio_to_tencent_silk_base64(audio_path: str) -> tuple[str, float]: try: import pilk except ImportError as e: - raise Exception("pilk 模块未安装,请前往管理面板->控制台->安装pip库 安装 pilk 这个库") from e + raise Exception( + "pilk 模块未安装,请前往管理面板->控制台->安装pip库 安装 pilk 这个库" + ) from e temp_dir = os.path.join(get_astrbot_data_path(), "temp") os.makedirs(temp_dir, exist_ok=True) @@ -158,6 +161,7 @@ async def audio_to_tencent_silk_base64(audio_path: str) -> tuple[str, float]: if os.path.exists(silk_path): os.remove(silk_path) + async def audio_to_tencent_silk(audio_path: str, output_path: str) -> float: """ 将 MP3/WAV 文件转为 Tencent Silk 并返回时长(秒)。 @@ -172,7 +176,9 @@ async def audio_to_tencent_silk(audio_path: str, output_path: str) -> float: try: import pilk except ImportError as e: - raise Exception("pilk 模块未安装,请前往管理面板->控制台->安装pip库 安装 pilk 这个库") from e + raise Exception( + "pilk 模块未安装,请前往管理面板->控制台->安装pip库 安装 pilk 这个库" + ) from e # 确保输入文件存在 if not os.path.exists(audio_path):