修复响应指令语音发送格式误判导致ffmpeg解码失败
1. 语音发送逻辑改为优先按文件后缀推断格式,参考message_push.py的稳定实现。 2. 新增语音发送兜底重试机制:首选格式失败后自动尝试mp3/wav/amr。 3. 增加详细日志,便于排查配置格式与文件真实格式不一致问题。
This commit is contained in:
@@ -208,6 +208,70 @@ class FunCommandPlayPlugin(MessagePluginInterface):
|
||||
self._put_media_bytes_to_cache(cache_key, payload)
|
||||
return payload
|
||||
|
||||
@staticmethod
|
||||
def _infer_voice_format_by_path(voice_path: str, configured_format: str) -> str:
|
||||
"""根据语音文件路径推断发送格式。
|
||||
|
||||
设计说明:
|
||||
1. 参考 message_push.py 的既有稳定逻辑:优先按文件后缀判断格式。
|
||||
2. 若后缀可识别(wav/mp3/amr),直接使用后缀,避免“配置格式与真实文件不一致”导致解码失败。
|
||||
3. 若后缀不可识别,再回退到配置值;配置也无效时最终默认 mp3。
|
||||
"""
|
||||
suffix = Path(str(voice_path or "")).suffix.lower().strip()
|
||||
if suffix == ".wav":
|
||||
return "wav"
|
||||
if suffix == ".amr":
|
||||
return "amr"
|
||||
if suffix == ".mp3":
|
||||
return "mp3"
|
||||
|
||||
normalized_cfg = str(configured_format or "").strip().lower()
|
||||
if normalized_cfg in {"wav", "mp3", "amr"}:
|
||||
return normalized_cfg
|
||||
return "mp3"
|
||||
|
||||
async def _send_voice_with_fallback(
|
||||
self,
|
||||
bot: WechatAPIClient,
|
||||
target_id: str,
|
||||
voice_bytes: bytes,
|
||||
voice_path: str,
|
||||
configured_format: str,
|
||||
) -> None:
|
||||
"""发送语音并进行格式兜底重试。
|
||||
|
||||
背景:
|
||||
- 在线上规则维护中,常见问题是“文件实际是 wav,但配置里误写 mp3”,
|
||||
这会触发 ffmpeg 解码失败(Header missing)。
|
||||
|
||||
策略:
|
||||
1. 首次发送:使用“后缀优先”推断格式(与 message_push.py 保持一致)。
|
||||
2. 失败后自动尝试其它格式(mp3/wav/amr),提高容错性,降低人工维护成本。
|
||||
"""
|
||||
first_try = self._infer_voice_format_by_path(voice_path=voice_path, configured_format=configured_format)
|
||||
candidates: List[str] = [first_try]
|
||||
for fallback in ("mp3", "wav", "amr"):
|
||||
if fallback not in candidates:
|
||||
candidates.append(fallback)
|
||||
|
||||
last_error: Optional[Exception] = None
|
||||
for fmt in candidates:
|
||||
try:
|
||||
await bot.send_voice_message(target_id, voice_bytes, fmt)
|
||||
if fmt != first_try:
|
||||
self.LOG.warning(
|
||||
f"[{self.name}] 语音发送触发格式兜底重试成功: path={voice_path}, first={first_try}, final={fmt}"
|
||||
)
|
||||
return
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
self.LOG.warning(
|
||||
f"[{self.name}] 语音发送失败,尝试下一个格式: path={voice_path}, format={fmt}, error={e}"
|
||||
)
|
||||
|
||||
if last_error:
|
||||
raise last_error
|
||||
|
||||
def start(self) -> bool:
|
||||
self.status = PluginStatus.RUNNING
|
||||
self.LOG.info(f"[{self.name}] 插件已启动")
|
||||
@@ -425,10 +489,13 @@ class FunCommandPlayPlugin(MessagePluginInterface):
|
||||
voice_format = str(action.get("format", "") or "").strip().lower()
|
||||
voice_bytes = self._load_media_bytes(media_kind="voice", media_path=voice_path)
|
||||
if voice_bytes:
|
||||
if not voice_format:
|
||||
suffix = Path(voice_path).suffix.lower()
|
||||
voice_format = "wav" if suffix == ".wav" else "mp3"
|
||||
await bot.send_voice_message(target_id, voice_bytes, voice_format)
|
||||
await self._send_voice_with_fallback(
|
||||
bot=bot,
|
||||
target_id=target_id,
|
||||
voice_bytes=voice_bytes,
|
||||
voice_path=voice_path,
|
||||
configured_format=voice_format,
|
||||
)
|
||||
return
|
||||
|
||||
if action_type == "video":
|
||||
|
||||
Reference in New Issue
Block a user