diff --git a/plugins/fun_command_play/main.py b/plugins/fun_command_play/main.py index 01ff4f7..4e3a104 100644 --- a/plugins/fun_command_play/main.py +++ b/plugins/fun_command_play/main.py @@ -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":