Zozoz / utmos_eval.py
im313l's picture
Upload 8 files
f07e64d verified
Raw History Blame Contribute Delete
1.67 kB
"""
UTMOS naturalness evaluator.
Matches the F5-TTS evaluation approach: SpeechMOS UTMOS22 strong predictor
loaded through torch.hub. Kept independent and lazy.
"""
import gc
import threading
from pathlib import Path
import numpy as np
import soundfile as sf
import torch
_LOCK = threading.RLock()
_PREDICTOR = None
_DEVICE = "cpu"
def _load():
global _PREDICTOR
with _LOCK:
if _PREDICTOR is not None:
return _PREDICTOR
_PREDICTOR = torch.hub.load(
"tarepan/SpeechMOS:v1.2.0",
"utmos22_strong",
trust_repo=True,
)
_PREDICTOR = _PREDICTOR.to(_DEVICE).eval()
return _PREDICTOR
def evaluate_utmos(audio_path: str) -> dict:
if not audio_path or not Path(audio_path).exists():
raise ValueError("الصوت الناتج غير موجود.")
wav, sr = sf.read(audio_path, dtype="float32", always_2d=False)
wav = np.asarray(wav, dtype=np.float32)
if wav.ndim > 1:
wav = wav.mean(axis=1)
if wav.size == 0:
raise ValueError("الصوت الناتج فارغ.")
predictor = _load()
tensor = torch.from_numpy(wav).unsqueeze(0).to(_DEVICE)
with torch.inference_mode():
score = float(predictor(tensor, int(sr))[0].detach().cpu().item())
return {
"metric": "UTMOS Naturalness",
"score": score,
"scale": "1-5",
"higher_is_better": True,
"model": "tarepan/SpeechMOS:v1.2.0 / utmos22_strong",
}
def release_utmos():
global _PREDICTOR
with _LOCK:
_PREDICTOR = None
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()