修复响应指令语音发送格式误判导致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)
|
self._put_media_bytes_to_cache(cache_key, payload)
|
||||||
return 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:
|
def start(self) -> bool:
|
||||||
self.status = PluginStatus.RUNNING
|
self.status = PluginStatus.RUNNING
|
||||||
self.LOG.info(f"[{self.name}] 插件已启动")
|
self.LOG.info(f"[{self.name}] 插件已启动")
|
||||||
@@ -425,10 +489,13 @@ class FunCommandPlayPlugin(MessagePluginInterface):
|
|||||||
voice_format = str(action.get("format", "") or "").strip().lower()
|
voice_format = str(action.get("format", "") or "").strip().lower()
|
||||||
voice_bytes = self._load_media_bytes(media_kind="voice", media_path=voice_path)
|
voice_bytes = self._load_media_bytes(media_kind="voice", media_path=voice_path)
|
||||||
if voice_bytes:
|
if voice_bytes:
|
||||||
if not voice_format:
|
await self._send_voice_with_fallback(
|
||||||
suffix = Path(voice_path).suffix.lower()
|
bot=bot,
|
||||||
voice_format = "wav" if suffix == ".wav" else "mp3"
|
target_id=target_id,
|
||||||
await bot.send_voice_message(target_id, voice_bytes, voice_format)
|
voice_bytes=voice_bytes,
|
||||||
|
voice_path=voice_path,
|
||||||
|
configured_format=voice_format,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if action_type == "video":
|
if action_type == "video":
|
||||||
|
|||||||
Reference in New Issue
Block a user