Raymxnd's picture
Torch weights recovered from the ONNX export, with the parity evidence
3a490d9 verified
Raw History Blame Contribute Delete
3.13 kB
#!/usr/bin/env python3
"""Detect speech in an audio file and write the regions out as WebVTT.
python example.py speech.wav
python example.py speech.mp3 --dtype fp16 --device cpu -o regions.vtt
The VTT is cues covering the detected speech, with no text -- useful for
eyeballing the VAD's decisions in any media player, and as the segmentation
step in front of an ASR model.
"""
from __future__ import annotations
import argparse
from pathlib import Path
import torch
from vad_torch import TorchVadRunner, DEFAULT_WEIGHTS, SAMPLE_RATE
def fmt_vtt_time(seconds: float) -> str:
h = int(seconds // 3600)
m = int((seconds % 3600) // 60)
s = seconds % 60
return f"{h:02d}:{m:02d}:{s:06.3f}"
def main() -> int:
p = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument("audio", type=Path)
p.add_argument("-o", "--out", type=Path, default=None,
help="output .vtt (default: alongside the input)")
p.add_argument("--weights", type=Path, default=DEFAULT_WEIGHTS)
p.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
p.add_argument("--dtype", default="fp32", choices=["fp32", "fp16"],
help="fp32 (default) reproduces the ONNX reference exactly; "
"fp16 is ~2x faster and moves a few boundaries")
p.add_argument("--batch-size", type=int, default=8)
p.add_argument("--threshold", type=float, default=0.4,
help="in LOGIT space, not probability (default 0.4 ~= 0.60)")
a = p.parse_args()
if not a.audio.exists():
raise SystemExit(f"no such file: {a.audio}")
try:
import librosa
except ImportError:
raise SystemExit("this example needs librosa: pip install librosa")
print(f"loading {a.audio.name}")
audio, _ = librosa.load(str(a.audio), sr=SAMPLE_RATE, mono=True)
duration = len(audio) / SAMPLE_RATE
print(f" {duration:.1f}s at {SAMPLE_RATE} Hz mono")
dt = {"fp32": torch.float32, "fp16": torch.float16}[a.dtype]
if a.dtype == "fp16" and a.device == "cpu":
raise SystemExit("fp16 on CPU is not supported; use --dtype fp32")
runner = TorchVadRunner(weights=a.weights, device=a.device, dtype=dt,
batch_size=a.batch_size)
print(f" model on {a.device}, {a.dtype}")
intervals = runner.speech_intervals(audio, threshold=a.threshold)
speech = sum(e - s for s, e in intervals)
print(f"\n{len(intervals)} speech regions, {speech:.1f}s of {duration:.1f}s "
f"({speech / max(duration, 1e-9) * 100:.1f}% active)")
out = a.out or a.audio.with_suffix(".vtt")
lines = ["WEBVTT", ""]
for i, (start, end) in enumerate(intervals, 1):
lines.append(str(i))
lines.append(f"{fmt_vtt_time(start)} --> {fmt_vtt_time(end)}")
lines.append(f"[speech {i}]")
lines.append("")
out.write_text("\n".join(lines), encoding="utf-8")
print(f"-> {out}")
return 0
if __name__ == "__main__":
raise SystemExit(main())