新增大量WhatsApp适配器相关代码,包括账号管理、会话处理、消息收发、验证授权、媒体处理、互动命令、审批流程、健康检测等完整功能模块,搭建基础的Baileys协议WhatsApp接入能力
86 lines
2.5 KiB
Python
86 lines
2.5 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import os
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
|
|
class Transcoder:
|
|
def __init__(self, ffmpeg_path: str = "ffmpeg"):
|
|
self._ffmpeg = ffmpeg_path
|
|
|
|
async def to_ogg_opus(self, input_path: str, sample_rate: int = 16000) -> str:
|
|
output_path = input_path + ".ogg"
|
|
|
|
cmd = [
|
|
self._ffmpeg,
|
|
"-y",
|
|
"-i",
|
|
input_path,
|
|
"-c:a",
|
|
"libopus",
|
|
"-b:a",
|
|
"24k",
|
|
"-ar",
|
|
str(sample_rate),
|
|
"-ac",
|
|
"1",
|
|
"-vbr",
|
|
"on",
|
|
"-application",
|
|
"voip",
|
|
"-frame_duration",
|
|
"20",
|
|
output_path,
|
|
]
|
|
|
|
try:
|
|
process = await asyncio.create_subprocess_exec(
|
|
*cmd,
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE,
|
|
)
|
|
stdout, stderr = await process.communicate()
|
|
if process.returncode != 0:
|
|
err_msg = stderr.decode()[:500] if stderr else "unknown error"
|
|
raise RuntimeError(f"FFmpeg transcode failed (exit {process.returncode}): {err_msg}")
|
|
logger.debug(f"Transcoded {input_path} → {output_path}")
|
|
return output_path
|
|
except FileNotFoundError:
|
|
raise RuntimeError(f"FFmpeg not found at '{self._ffmpeg}'. Install FFmpeg to use TTS voice notes.")
|
|
|
|
async def wav_to_ogg_opus(self, wav_data: bytes) -> bytes:
|
|
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
|
|
f.write(wav_data)
|
|
tmp_in = f.name
|
|
|
|
try:
|
|
tmp_out = await self.to_ogg_opus(tmp_in)
|
|
try:
|
|
return Path(tmp_out).read_bytes()
|
|
finally:
|
|
if os.path.exists(tmp_out):
|
|
os.unlink(tmp_out)
|
|
finally:
|
|
if os.path.exists(tmp_in):
|
|
os.unlink(tmp_in)
|
|
|
|
async def check_available(self) -> bool:
|
|
try:
|
|
proc = await asyncio.create_subprocess_exec(
|
|
self._ffmpeg,
|
|
"-version",
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE,
|
|
)
|
|
await proc.communicate()
|
|
return proc.returncode == 0
|
|
except FileNotFoundError:
|
|
return False
|
|
|
|
|
|
transcoder = Transcoder()
|