speech-artifact-detectors / inference_example.py
christoph
Add model card metadata, inference example script, and enhanced README
9fba0fd
Raw
History Blame Contribute Delete
4.76 kB
#!/usr/bin/env python3
"""Inference example for Speech Artifact Detectors.
Demonstrates loading models from HuggingFace Hub and scoring audio files.
Usage:
# Score a single file with all 10 detectors
python inference_example.py audio.wav
# Score a directory of files
python inference_example.py /path/to/audio/ --ext wav
# Only TTS/vocoder detectors
python inference_example.py audio.wav --category tts_artifact
# Only augmentation detectors on CPU
python inference_example.py audio.wav --category augmentation --device cpu
# Custom threshold for flagging artifacts
python inference_example.py audio.wav --threshold 0.3
Requirements:
pip install torch torchaudio soundfile numpy huggingface_hub
"""
import argparse
import glob
import os
import sys
import time
import numpy as np
import torch
def main():
parser = argparse.ArgumentParser(
description="Score audio files for speech artifacts",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("input", help="Audio file or directory to score")
parser.add_argument("--ext", default="wav",
help="File extension when input is a directory (default: wav)")
parser.add_argument("--device", default=None,
help="Device: cuda, cpu, or auto-detect (default)")
parser.add_argument("--category", choices=["tts_artifact", "augmentation"],
help="Load only models from this category")
parser.add_argument("--threshold", type=float, default=0.5,
help="Score threshold for flagging artifacts (default: 0.5)")
parser.add_argument("--batch-size", type=int, default=16,
help="Batch size for inference (default: 16)")
args = parser.parse_args()
device = args.device or ("cuda" if torch.cuda.is_available() else "cpu")
# ── Load models from HuggingFace Hub ─────────────────────────────────
from speech_artifact_detector import load_from_hub
print(f"Loading models on {device}...")
categories = [args.category] if args.category else None
t0 = time.time()
models = load_from_hub(device=device, categories=categories)
print(f" Loaded {len(models)} models in {time.time() - t0:.1f}s: "
f"{', '.join(models.keys())}\n")
# ── Collect input files ──────────────────────────────────────────────
if os.path.isdir(args.input):
files = sorted(glob.glob(os.path.join(args.input, f"*.{args.ext}")))
if not files:
print(f"No .{args.ext} files found in {args.input}")
sys.exit(1)
elif os.path.isfile(args.input):
files = [args.input]
else:
print(f"File not found: {args.input}")
sys.exit(1)
print(f"Scoring {len(files)} file(s)...\n")
# ── Score each file ──────────────────────────────────────────────────
from speech_artifact_detector import score_file_all
model_names = list(models.keys())
all_results = []
for fpath in files:
fname = os.path.basename(fpath)
scores = score_file_all(models, fpath, device)
mean_score = np.mean(list(scores.values()))
verdict = "ARTIFACT" if mean_score >= args.threshold else "clean"
all_results.append({"file": fname, "scores": scores,
"mean": mean_score, "verdict": verdict})
# Print per-file results
print(f"{'=' * 60}")
print(f" {fname} β†’ {verdict} (mean={mean_score:.4f})")
print(f"{'=' * 60}")
for name in model_names:
s = scores[name]
bar = "#" * int(s * 30) + "." * (30 - int(s * 30))
flag = " <<<" if s >= args.threshold else ""
print(f" {name:>25s}: {s:.4f} [{bar}]{flag}")
print()
# ── Summary ──────────────────────────────────────────────────────────
if len(files) > 1:
n_artifact = sum(1 for r in all_results if r["verdict"] == "ARTIFACT")
print(f"\nSummary: {n_artifact}/{len(files)} files flagged as ARTIFACT "
f"(threshold={args.threshold})")
print(f"\nPer-detector averages across all files:")
for name in model_names:
avg = np.mean([r["scores"][name] for r in all_results])
print(f" {name:>25s}: {avg:.4f}")
if __name__ == "__main__":
main()