fd-speech-demo / scripts /score_speechmos_dnsmos.py
voidful's picture
Add SR-FD four-step comparison demo
10bebcd verified
Raw
History Blame Contribute Delete
4.82 kB
#!/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())