from __future__ import annotations import base64 import wave from array import array from pathlib import Path def wav_to_float32_base64(path: Path, target_rate: int = 16000) -> tuple[str, float]: """Return mono float32 PCM expected by the MiniCPM-o Comni APIs. ``audioop`` was removed from the standard library in Python 3.13, so it is imported lazily here: only the optional MiniCPM-o voice path needs it. The default text backend never calls this function, keeping the app importable on modern Python without the ``audioop-lts`` backport installed. """ try: import audioop # stdlib <=3.12; provided by the audioop-lts backport on 3.13+ except ModuleNotFoundError as exc: # pragma: no cover - exercised only on 3.13+ without backport raise RuntimeError( "Audio/voice features require the 'audioop' module. On Python 3.13+ " "install the backport with: pip install audioop-lts" ) from exc with wave.open(str(path), "rb") as source: channels = source.getnchannels() sample_width = source.getsampwidth() source_rate = source.getframerate() pcm = source.readframes(source.getnframes()) if channels > 1: pcm = audioop.tomono(pcm, sample_width, 0.5, 0.5) if sample_width != 2: pcm = audioop.lin2lin(pcm, sample_width, 2) if source_rate != target_rate: pcm, _ = audioop.ratecv(pcm, 2, 1, source_rate, target_rate, None) int_samples = array("h") int_samples.frombytes(pcm) float_samples = array("f", (sample / 32768.0 for sample in int_samples)) duration = len(float_samples) / target_rate return base64.b64encode(float_samples.tobytes()).decode("ascii"), duration