audio.py
Language Python2.5 KB74 lines
"""ffmpeg: any audio or video in, 16 kHz mono out.Uses the system ffmpeg when there is one, else the static binary that theimageio-ffmpeg package ships, so nothing needs installing system-wide."""from __future__ import annotationsimport shutilimport subprocessimport wavefrom pathlib import PathAUDIO_EXTS = {".mp3", ".m4a", ".wav", ".aac", ".flac", ".ogg", ".opus", ".wma", ".aiff", ".aif",".mp4", ".mov", ".m4v", ".mkv", ".webm", ".avi"}class AudioError(RuntimeError):passdef ffmpeg_path() -> str:found = shutil.which("ffmpeg")if found:return foundtry:import imageio_ffmpegreturn imageio_ffmpeg.get_ffmpeg_exe()except Exception as e: # pragma: no cover - depends on the machineraise AudioError("ffmpeg not found: install it, or `pip install imageio-ffmpeg`.") from edef _run(args: list[str]) -> None:cmd = [ffmpeg_path(), "-nostdin", "-hide_banner", "-loglevel", "error", "-y", *args]p = subprocess.run(cmd, capture_output=True, text=True)if p.returncode != 0:msg = (p.stderr or "").strip().splitlines()raise AudioError(f"ffmpeg could not read this file: {msg[-1] if msg else 'unknown error'}")def to_wav16k(src: Path, dst: Path) -> Path:"""Decode to 16 kHz mono 16-bit WAV: what the local models expect."""_run(["-i", str(src), "-vn", "-ac", "1", "-ar", "16000", "-c:a", "pcm_s16le", str(dst)])return dstdef to_flac16k(src: Path, dst: Path) -> Path:"""16 kHz mono FLAC for upload: lossless, and about half the size of WAV."""_run(["-i", str(src), "-vn", "-ac", "1", "-ar", "16000", "-c:a", "flac", str(dst)])return dstdef wav_duration(path: Path) -> float:with wave.open(str(path), "rb") as w:return w.getnframes() / float(w.getframerate())def hms(seconds: float) -> str:s = int(round(seconds))return f"{s // 3600}:{s // 60 % 60:02d}:{s % 60:02d}"def load_wav16k(path: Path):"""The 16 kHz mono WAV as float32 samples in [-1, 1], the array WhisperX takes.Read directly rather than through whisperx.load_audio, which shells out toan `ffmpeg` on PATH and so would bring back the system dependency."""import numpy as npwith wave.open(str(path), "rb") as w:if w.getframerate() != 16000 or w.getnchannels() != 1 or w.getsampwidth() != 2:raise AudioError(f"{path.name}: expected 16 kHz mono 16-bit WAV")frames = w.readframes(w.getnframes())return np.frombuffer(frames, dtype="<i2").astype(np.float32) / 32768.0