报错场景
nonebot+napcat在接入插件链接分享解析器重制版nonebot-plugin-resolver2(1.7.0版本)时,在解析b站视频链接时,经常会发送视频失败,并报下面这段错误
报错详细代码
03-01 02:02:43 [INFO] nonebot | Event will be handled by Matcher(type='message', module=nonebot_plugin_resolver2.matchers.bilibili, lineno=42)
[Request][2025-03-01 02:02:43,478] 激活 buvid3: [0816D84D-6AB8-8F64-5032-17A4A7083FF363384infoc] 成功
03-01 02:02:47 [INFO] nonebot | Matcher(type='message', module=nonebot_plugin_resolver2.matchers.bilibili, lineno=42) running complete
03-01 02:02:47 [ERROR] nonebot | Running Matcher(type='message', module=nonebot_plugin_resolver2.matchers.bilibili, lineno=42) failed.
Traceback (most recent call last):
File "<string>", line 17, in <module>
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot/__init__.py", line 337, in run
get_driver().run(*args, **kwargs)
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot/drivers/fastapi.py", line 187, in run
uvicorn.run(
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/uvicorn/main.py", line 579, in run
server.run()
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/uvicorn/server.py", line 66, in run
return asyncio.run(self.serve(sockets=sockets))
File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run
return runner.run(main)
File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run
return self._loop.run_until_complete(task)
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot/utils.py", line 254, in run_coro_with_shield
return await coro
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot/message.py", line 506, in check_and_run_matcher
await _run_matcher(
> File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot/message.py", line 458, in _run_matcher
await matcher.run(bot, event, state, stack, dependency_cache)
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot/internal/matcher/matcher.py", line 926, in run
await self.simple_run(bot, event, state, stack, dependency_cache)
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot/internal/matcher/matcher.py", line 863, in simple_run
await handler(
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot/dependencies/__init__.py", line 113, in __call__
return await cast(Callable[..., Awaitable[R]], self.call)(**values)
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot_plugin_resolver2/matchers/bilibili.py", line 312, in _
await bilibili.send(await get_video_seg(video_path))
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot/internal/matcher/matcher.py", line 570, in send
return await bot.send(event=event, message=_message, **kwargs)
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot/adapters/onebot/v11/bot.py", line 237, in send
return await self.__class__.send_handler(self, event, message, **kwargs)
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot/adapters/onebot/v11/bot.py", line 190, in send
return await bot.send_msg(**params)
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot/internal/adapter/bot.py", line 176, in call_api
raise exception
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot/internal/adapter/bot.py", line 128, in call_api
result = await self.adapter._call_api(self, api, **data)
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot/adapters/onebot/v11/adapter.py", line 175, in _call_api
return handle_api_result(await self._result_store.fetch(seq, timeout))
File "/root/ali-tsugu-test01/.venv/lib/python3.12/site-packages/nonebot/adapters/onebot/v11/utils.py", line 58, in handle_api_result
raise ActionFailed(**result)
nonebot.adapters.onebot.v11.exception.ActionFailed: ActionFailed(status='failed', retcode=1200, data=None, message="ENOENT: no such file or directory, stat '/root/.config/QQ/nt_qq_7****6135d56846/nt_data/Video/2025-03/Thumb/664a0871a3c5d76ed43ff4445a05fd0a_0.png'", wording="ENOENT: no such file or directory, stat '/root/.config/QQ/nt_qq_7****6135d56846/nt_data/Video/2025-03/Thumb/664a0871a3c5d76ed43ff4445a05fd0a_0.png'", echo='5')
报错分析
读代码可知,报错源于/root/.config/QQ/nt_qq_7****6135d56846/nt_data/Video/2025-03/Thumb/664a0871a3c5d76ed43ff4445a05fd0a_0.png
该路径的图片缺少,而这个图片是QQ在用户发送视频时自动生成的预览缩略图。
而我们查看同目录上一级下的Ori
文件夹,以及/root/.config/QQ/NapCat/temp
、和/root/.cache/nonebot2/nonebot_plugin_resolver2
这三处地方都可以看到,视频实际上正常下载成功了,具体代码是通过下载视频流和音频流,然后合并得到的。
合并代码位于/root/my-nonebot/.venv/lib/python3.12/site-packages/nonebot_plugin_resolver2/download/command.py
150行附近:
async def merge_av(v_path: Path, a_path: Path, output_path: Path):
"""helper function to merge video and audio
Args:
v_path (Path): video path
a_path (Path): audio path
output_path (Path): ouput path
Raises:
RuntimeError: ffmpeg未安装或命令执行失败
"""
logger.info(f"Merging {v_path.name} and {a_path.name} to {output_path.name}")
# 构建 ffmpeg 命令, localstore already path.resolve()
command = f'ffmpeg -y -i "{v_path}" -i "{a_path}" -c copy "{output_path}"'
result = await asyncio.get_event_loop().run_in_executor(
None,
lambda: subprocess.call(
command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
),
)
if result != 0:
raise RuntimeError("ffmpeg未安装或命令执行失败")
# 删除原始文件
v_path.unlink()
a_path.unlink()
然而这个合并的视频格式是 av1 编码,这是一种较新的视频编码格式,因此笔者推测QQ 可能对 av1 编码的支持不完善,导致无法正确处理或生成缩略图。
解决办法
为此,我们应该将视频格式修改为更常见的 h264 编码,修改上面的函数为:
import asyncio
import subprocess
from pathlib import Path
import logging
logger = logging.getLogger(__name__)
async def merge_av(v_path: Path, a_path: Path, output_path: Path):
"""合并视频和音频文件,并生成 QQ 兼容的 MP4 文件
Args:
v_path (Path): 视频文件路径
a_path (Path): 音频文件路径
output_path (Path): 输出文件路径
Raises:
RuntimeError: ffmpeg 未安装或命令执行失败
FileNotFoundError: 视频或音频文件不存在
PermissionError: 文件权限不足
"""
logger.info(f"Merging {v_path.name} and {a_path.name} to {output_path.name}")
# 确保输入文件存在
if not v_path.exists() or not a_path.exists():
raise FileNotFoundError("视频或音频文件不存在")
# 构建 ffmpeg 命令
# 使用 h264 视频编码和 aac 音频编码,确保 QQ 兼容
command = [
"ffmpeg",
"-y", # 覆盖输出文件
"-i", str(v_path), # 输入视频文件
"-i", str(a_path), # 输入音频文件
"-c:v", "libx264", # 使用 h264 视频编码
"-c:a", "aac", # 使用 aac 音频编码
"-strict", "experimental", # 允许使用实验性编码
"-vf", "scale=1280:720", # 限制分辨率为 1280x720(可选)
"-b:v", "1000k", # 限制视频码率为 1000k(可选)
str(output_path), # 输出文件
]
try:
# 使用 asyncio 创建子进程执行 ffmpeg 命令
process = await asyncio.create_subprocess_exec(
*command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
# 等待命令执行完成
stdout, stderr = await process.communicate()
# 检查命令返回值
if process.returncode != 0:
logger.error(f"ffmpeg 命令执行失败: {stderr.decode()}")
raise RuntimeError("ffmpeg 命令执行失败")
logger.info(f"合并成功: {output_path.name}")
# 合并成功后删除原始文件
v_path.unlink()
a_path.unlink()
logger.info(f"已删除原始文件: {v_path.name} 和 {a_path.name}")
except FileNotFoundError:
logger.error("ffmpeg 未安装")
raise RuntimeError("ffmpeg 未安装")
except PermissionError:
logger.error("文件权限不足")
raise PermissionError("文件权限不足")
except Exception as e:
logger.error(f"合并失败: {str(e)}")
raise RuntimeError(f"合并失败: {str(e)}")
然后视频便发送成功了,问题解决。
特别感谢:deepseek