Files
Shaman 82c306ff69 init
2026-10-01 07:38:01 +03:00

105 lines
2.5 KiB
Python

import shutil
import struct
import subprocess
from io import BytesIO
def _find_ffmpeg(configured: str = "ffmpeg") -> str:
if shutil.which(configured):
return configured
try:
from imageio_ffmpeg import get_ffmpeg_exe
return get_ffmpeg_exe()
except Exception:
raise RuntimeError(
"ffmpeg not found: install it on PATH, set runtime.ffmpeg_path "
"in config.json, or `pip install imageio-ffmpeg`"
) from None
_SUPPORTED_FORMATS = {"mp3", "wav", "pcm", "opus", "aac", "flac"}
_CONTENT_TYPES = {
"mp3": "audio/mpeg",
"wav": "audio/wav",
"pcm": "audio/pcm",
"opus": "audio/opus",
"aac": "audio/aac",
"flac": "audio/flac",
}
_FFMPEG_CODECS = {
"mp3": "libmp3lame",
"opus": "libopus",
"aac": "aac",
"flac": "flac",
}
def pcm_to_wav(pcm: bytes, sample_rate: int = 24000) -> bytes:
data_size = len(pcm)
byte_rate = sample_rate * 2
header = struct.pack(
"<4sI4s4sI4sI",
b"RIFF",
36 + data_size,
b"WAVE",
b"fmt ",
16,
b"data",
data_size,
)
fmt = struct.pack("<HHIIHH", 1, 1, sample_rate, byte_rate, 2, 16)
return header[:20] + fmt + header[20:] + pcm
def pcm_to_raw(pcm: bytes) -> bytes:
return pcm
def pcm_to_ffmpeg(
pcm: bytes, fmt: str, sample_rate: int = 24000, ffmpeg_path: str = "ffmpeg"
) -> bytes:
codec = _FFMPEG_CODECS.get(fmt)
if codec is None:
raise ValueError(f"Unsupported ffmpeg format: {fmt}")
cmd = [
ffmpeg_path, "-y",
"-f", "s16le",
"-ar", str(sample_rate),
"-ac", "1",
"-i", "pipe:0",
"-f", _ffprobe_fmt(fmt),
"-codec:a", codec,
"pipe:1",
]
proc = subprocess.run(
cmd,
input=pcm,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
if proc.returncode != 0:
raise RuntimeError(f"ffmpeg failed: {proc.stderr.decode(errors='replace')}")
return proc.stdout
def _ffprobe_fmt(fmt: str) -> str:
return {"mp3": "mp3", "opus": "opus", "aac": "adts", "flac": "flac"}.get(fmt, fmt)
def encode(pcm: bytes, fmt: str, sample_rate: int = 24000, ffmpeg_path: str = "ffmpeg") -> tuple[bytes, str]:
if fmt not in _SUPPORTED_FORMATS:
raise ValueError(f"Unsupported response_format: {fmt}")
if fmt == "wav":
data = pcm_to_wav(pcm, sample_rate)
elif fmt == "pcm":
data = pcm
else:
data = pcm_to_ffmpeg(pcm, fmt, sample_rate, _find_ffmpeg(ffmpeg_path))
return data, _CONTENT_TYPES[fmt]