Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """Score generated speech with speechmos DNSMOS. | |
| This script intentionally keeps DNSMOS as an auxiliary non-intrusive proxy. | |
| It requires the optional `speechmos` package and is not part of the default | |
| test environment. Install it in a disposable target/venv before running. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import statistics | |
| import sys | |
| from pathlib import Path | |
| from typing import Any | |
| def parse_args() -> argparse.Namespace: | |
| parser = argparse.ArgumentParser() | |
| source = parser.add_mutually_exclusive_group(required=True) | |
| source.add_argument("--listing", help="wav_res_ref_text path") | |
| source.add_argument("--wav_dir", help="Directory containing generated wav files") | |
| parser.add_argument("--out_json", required=True) | |
| parser.add_argument("--max_items", type=int, default=0) | |
| return parser.parse_args() | |
| def load_listing(path: Path, max_items: int = 0) -> list[Path]: | |
| wavs: list[Path] = [] | |
| for line in path.read_text(encoding="utf-8").splitlines(): | |
| line = line.strip() | |
| if not line: | |
| continue | |
| wavs.append(Path(line.split("|", 1)[0])) | |
| if max_items > 0: | |
| wavs = wavs[:max_items] | |
| return wavs | |
| def load_wav_dir(path: Path, max_items: int = 0) -> list[Path]: | |
| wavs = sorted(path.glob("*.wav")) | |
| if max_items > 0: | |
| wavs = wavs[:max_items] | |
| return wavs | |
| def _mean(values: list[float]) -> float | None: | |
| return statistics.mean(values) if values else None | |
| def _std(values: list[float]) -> float | None: | |
| if len(values) <= 1: | |
| return 0.0 if values else None | |
| return statistics.stdev(values) | |
| def _as_float(value: Any) -> float: | |
| return float(value.item() if hasattr(value, "item") else value) | |
| def main() -> int: | |
| args = parse_args() | |
| if args.listing: | |
| wavs = load_listing(Path(args.listing), args.max_items) | |
| else: | |
| wavs = load_wav_dir(Path(args.wav_dir), args.max_items) | |
| print(f"[dnsmos] {len(wavs)} wavs", file=sys.stderr) | |
| try: | |
| import librosa | |
| from speechmos import dnsmos | |
| except Exception as exc: # pragma: no cover - optional dependency path | |
| raise SystemExit( | |
| "speechmos DNSMOS dependencies are missing. Install in a disposable " | |
| "environment, for example: pip install --target /tmp/speechmos_target " | |
| "speechmos==0.0.1.1 librosa==0.9.1 onnxruntime pandas tqdm" | |
| ) from exc | |
| per_utt: list[dict[str, Any]] = [] | |
| n_skip = 0 | |
| for idx, wav in enumerate(wavs, start=1): | |
| try: | |
| audio, _ = librosa.load(str(wav), sr=16_000, mono=True) | |
| # speechmos rejects samples outside [-1, 1]. Some generated wavs | |
| # contain small overshoots, so clamp at metric input time instead | |
| # of dropping the utterance. | |
| audio = audio.clip(-1.0, 1.0) | |
| scores = dnsmos.run(audio, 16_000, return_df=False, verbose=False) | |
| row = { | |
| "wav": str(wav), | |
| "ovrl_mos": _as_float(scores["ovrl_mos"]), | |
| "sig_mos": _as_float(scores["sig_mos"]), | |
| "bak_mos": _as_float(scores["bak_mos"]), | |
| "p808_mos": _as_float(scores["p808_mos"]), | |
| } | |
| per_utt.append(row) | |
| except Exception as exc: # pragma: no cover - data-dependent path | |
| n_skip += 1 | |
| print(f"[dnsmos-skip] {wav}: {exc}", file=sys.stderr) | |
| if idx % 50 == 0 or idx == len(wavs): | |
| print(f"[dnsmos] {idx}/{len(wavs)}", file=sys.stderr) | |
| values = { | |
| key: [float(row[key]) for row in per_utt] | |
| for key in ("ovrl_mos", "sig_mos", "bak_mos", "p808_mos") | |
| } | |
| out = { | |
| "n_items": len(wavs), | |
| "n_scored": len(per_utt), | |
| "n_skipped": n_skip, | |
| "metric": "speechmos_dnsmos", | |
| "sample_rate": 16_000, | |
| "max_items": args.max_items, | |
| "ovrl_mos_mean": _mean(values["ovrl_mos"]), | |
| "ovrl_mos_std": _std(values["ovrl_mos"]), | |
| "sig_mos_mean": _mean(values["sig_mos"]), | |
| "sig_mos_std": _std(values["sig_mos"]), | |
| "bak_mos_mean": _mean(values["bak_mos"]), | |
| "bak_mos_std": _std(values["bak_mos"]), | |
| "p808_mos_mean": _mean(values["p808_mos"]), | |
| "p808_mos_std": _std(values["p808_mos"]), | |
| } | |
| out_path = Path(args.out_json) | |
| out_path.parent.mkdir(parents=True, exist_ok=True) | |
| out_path.write_text(json.dumps(out, indent=2, sort_keys=True) + "\n", encoding="utf-8") | |
| (out_path.parent / f"{out_path.stem}_per_utt_dnsmos_objective.jsonl").write_text( | |
| "\n".join(json.dumps(row, sort_keys=True) for row in per_utt) + "\n", | |
| encoding="utf-8", | |
| ) | |
| print(json.dumps(out, indent=2, sort_keys=True)) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |