Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 wav_to_tencent_silk(
record_wav_path, record_tecent_silk_path
duration = await audio_to_tencent_silk(
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
76 changes: 75 additions & 1 deletion astrbot/core/utils/tencent_record_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,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 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)
Expand Down Expand Up @@ -158,3 +160,75 @@ 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 Exception:
pass # 忽略清理错误