voice-normalization / scripts /run_phase1_on_folder.py
RishiKar210's picture
Initial backend deploy
99df98c
Raw
History Blame Contribute Delete
4.72 kB
"""Run Phase 1 on every WAV in a folder, writing per-segment JSON.
Bypasses the annotation-tool DB — produces standalone JSON output for one-off
tests. Reuses the same `annotate.phase1` modules so the analysis is identical
to what the annotation tool produces.
Usage:
python scripts/run_phase1_on_folder.py SEGMENTS_DIR OUT_DIR LANGUAGE
Example:
python scripts/run_phase1_on_folder.py \\
data/hindi_emphasis_test/segments \\
data/hindi_emphasis_test/phase1 \\
hi
"""
from __future__ import annotations
import json
import logging
import sys
from pathlib import Path
# Make annotate/ importable when running this script from anywhere
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import time
from annotate.phase1.config import PIPELINE_VERSION
from annotate.phase1.emphasis import score_emphasis
from annotate.phase1.emotion import classify_emotion, offload_emotion_model
from annotate.phase1.prosody import extract_per_word_prosody
from annotate.phase1.transcribe import transcribe
log = logging.getLogger(__name__)
def process_one(wav_path: Path, language: str) -> dict:
"""Run the full Phase 1 pipeline on one WAV; return a JSON-serializable dict."""
t0 = time.time()
# 1. Transcribe (WhisperX + forced alignment).
asr = transcribe(wav_path, language=language)
# 2. Prosody per word (parselmouth F0 + energy).
windows = [(w.start, w.end) for w in asr.words]
prosody = extract_per_word_prosody(wav_path, windows)
f0_peaks = [p[0] for p in prosody]
energy_peaks = [p[1] for p in prosody]
# 3. Emphasis flags.
duration = windows[-1][1] if windows else 0.0
flags = score_emphasis(windows, f0_peaks, energy_peaks, duration)
# 4. Emotion (segment-level via emotion2vec+).
try:
emo = classify_emotion(wav_path)
emotion_obj = {
"label": emo.label,
"confidence": emo.confidence,
"scores": emo.scores,
}
except Exception as e:
emotion_obj = {"error": f"{type(e).__name__}: {e}"}
elapsed = round(time.time() - t0, 2)
return {
"segment": wav_path.name,
"language": asr.language,
"duration_seconds": round(duration, 3),
"text": asr.text,
"n_words": len(asr.words),
"n_emphasized": int(sum(flags)),
"words": [
{
"idx": i,
"text": w.text,
"start": round(w.start, 3),
"end": round(w.end, 3),
"emphasis": bool(flags[i]),
"f0_peak": round(f0_peaks[i], 1) if f0_peaks[i] is not None else None,
"energy_peak": round(energy_peaks[i], 1) if energy_peaks[i] is not None else None,
}
for i, w in enumerate(asr.words)
],
"emotion": emotion_obj,
"phase1_version": PIPELINE_VERSION,
"elapsed_seconds": elapsed,
}
def main(seg_dir: Path, out_dir: Path, language: str) -> None:
out_dir.mkdir(parents=True, exist_ok=True)
wavs = sorted(p for p in seg_dir.glob("*.wav") if not p.name.startswith("_"))
if not wavs:
print(f"No WAVs found in {seg_dir}")
return
print(f"Processing {len(wavs)} segments (language={language})...")
for wav in wavs:
try:
result = process_one(wav, language=language)
out_path = out_dir / (wav.stem + ".json")
out_path.write_text(json.dumps(result, indent=2, ensure_ascii=False), encoding="utf-8")
print(
f" {wav.name}: {result['n_words']:3d} words, "
f"{result['n_emphasized']} emphasized, "
f"emotion={result['emotion'].get('label', 'err')}, "
f"{result['elapsed_seconds']}s"
)
except Exception as e:
log.exception("phase1 failed for %s", wav)
err_path = out_dir / (wav.stem + ".error.json")
err_path.write_text(json.dumps({"error": str(e)}, indent=2), encoding="utf-8")
print(f" {wav.name}: FAILED -- {e}")
# Free emotion model GPU memory at the end
try:
offload_emotion_model()
except Exception:
pass
print(f"Done. Outputs in {out_dir}")
if __name__ == "__main__":
if len(sys.argv) != 4:
print("Usage: run_phase1_on_folder.py SEGMENTS_DIR OUT_DIR LANGUAGE")
sys.exit(1)
logging.basicConfig(level=logging.WARNING, format="%(levelname)s %(name)s: %(message)s")
for n in ("transformers", "pyannote", "pytorch_lightning", "huggingface_hub", "modelscope", "funasr", "root"):
logging.getLogger(n).setLevel(logging.ERROR)
main(Path(sys.argv[1]), Path(sys.argv[2]), sys.argv[3])