init
This commit is contained in:
+104
@@ -0,0 +1,104 @@
|
||||
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]
|
||||
Reference in New Issue
Block a user