75 lines
2.0 KiB
Python
75 lines
2.0 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import logging
|
||
|
|
import subprocess
|
||
|
|
import tempfile
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
TARGET_SAMPLE_RATE = 48000
|
||
|
|
TARGET_BITRATE = "64k"
|
||
|
|
TARGET_FORMAT = "ogg"
|
||
|
|
TARGET_CODEC = "libopus"
|
||
|
|
|
||
|
|
|
||
|
|
def check_ffmpeg_available() -> bool:
|
||
|
|
try:
|
||
|
|
result = subprocess.run(["ffmpeg", "-version"], capture_output=True, timeout=5)
|
||
|
|
return result.returncode == 0
|
||
|
|
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||
|
|
return False
|
||
|
|
|
||
|
|
|
||
|
|
def transcode_to_ogg_opus(
|
||
|
|
input_data: bytes,
|
||
|
|
input_format: str = "mp3",
|
||
|
|
sample_rate: int = TARGET_SAMPLE_RATE,
|
||
|
|
bitrate: str = TARGET_BITRATE,
|
||
|
|
) -> bytes | None:
|
||
|
|
if not check_ffmpeg_available():
|
||
|
|
logger.warning("[FeishuAudio] FFmpeg not available, skipping transcode")
|
||
|
|
return None
|
||
|
|
|
||
|
|
with tempfile.NamedTemporaryFile(suffix=f".{input_format}", delete=False) as infile:
|
||
|
|
infile.write(input_data)
|
||
|
|
input_path = infile.name
|
||
|
|
|
||
|
|
output_path = input_path + f".{TARGET_FORMAT}"
|
||
|
|
|
||
|
|
try:
|
||
|
|
cmd = [
|
||
|
|
"ffmpeg",
|
||
|
|
"-y",
|
||
|
|
"-i",
|
||
|
|
input_path,
|
||
|
|
"-ar",
|
||
|
|
str(sample_rate),
|
||
|
|
"-b:a",
|
||
|
|
bitrate,
|
||
|
|
"-c:a",
|
||
|
|
TARGET_CODEC,
|
||
|
|
output_path,
|
||
|
|
]
|
||
|
|
result = subprocess.run(cmd, capture_output=True, timeout=60)
|
||
|
|
if result.returncode != 0:
|
||
|
|
logger.error(
|
||
|
|
"[FeishuAudio] FFmpeg transcode failed: %s",
|
||
|
|
result.stderr.decode(errors="replace")[:300],
|
||
|
|
)
|
||
|
|
return None
|
||
|
|
|
||
|
|
output_bytes = Path(output_path).read_bytes()
|
||
|
|
logger.info(
|
||
|
|
"[FeishuAudio] Transcode complete: %d -> %d bytes",
|
||
|
|
len(input_data),
|
||
|
|
len(output_bytes),
|
||
|
|
)
|
||
|
|
return output_bytes
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("[FeishuAudio] Transcode error: %s", e)
|
||
|
|
return None
|
||
|
|
finally:
|
||
|
|
Path(input_path).unlink(missing_ok=True)
|
||
|
|
Path(output_path).unlink(missing_ok=True)
|