File size: 4,764 Bytes
9fba0fd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#!/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()