ForcePilot/backend/package/yuxi/channel/extensions/feishu/voice.py
Kris 5e91bb9985 feat(feishu): 新增飞书渠道完整插件实现
新增飞书渠道插件,包含基础配置、事件处理、消息收发、工具调用、权限管理等完整功能模块,支持WebSocket长连接、卡片消息、流式响应、群管理、审批通知等能力
2026-05-21 10:46:42 +08:00

67 lines
2.3 KiB
Python

from __future__ import annotations
import logging
logger = logging.getLogger(__name__)
SUPPORTED_AUDIO_FORMATS = frozenset({
"aac", "aiff", "alac", "amr", "caf", "flac",
"m4a", "mp3", "oga", "wav", "webm", "wma",
})
NATIVE_OPUS_EXTS = frozenset({".opus", ".ogg"})
FFMPEG_OPUS_CMD = (
"ffmpeg -i {input_path} -vn -ar 48000 -ac 1 -c:a libopus -b:a 64k {output_path}"
)
def is_native_opus(filename: str) -> bool:
ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else ""
return f".{ext}" in NATIVE_OPUS_EXTS
def requires_transcoding(filename: str) -> bool:
ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else ""
return ext in SUPPORTED_AUDIO_FORMATS and not is_native_opus(filename)
def build_transcode_command(input_path: str, output_path: str) -> str:
return FFMPEG_OPUS_CMD.format(input_path=input_path, output_path=output_path)
async def transcode_to_opus(
input_path: str,
output_path: str,
max_duration_secs: int = 300,
) -> dict:
import asyncio
if not requires_transcoding(input_path):
return {"success": True, "path": input_path, "transcoded": False}
cmd = build_transcode_command(input_path, output_path)
try:
proc = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await asyncio.wait_for(
proc.communicate(), timeout=max_duration_secs + 10
)
if proc.returncode == 0:
return {"success": True, "path": output_path, "transcoded": True}
else:
error_msg = stderr.decode("utf-8", errors="replace")[:500]
logger.error("FFmpeg transcoding failed: %s", error_msg)
return {"success": False, "error": error_msg, "path": None, "transcoded": False}
except asyncio.TimeoutError:
return {"success": False, "error": "Transcoding timed out", "path": None, "transcoded": False}
except FileNotFoundError:
return {"success": False, "error": "ffmpeg not found in PATH", "path": None, "transcoded": False}
except Exception:
logger.exception("Voice transcoding error")
return {"success": False, "error": "Internal error during transcoding", "path": None, "transcoded": False}