| |
| """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") |
|
|
| |
| 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") |
|
|
| |
| 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") |
|
|
| |
| 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(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() |
|
|
| |
| 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() |
|
|