""" phoneme stress sanity check — run BEFORE training (Phase 6). espeak-ng is piper's phonemizer. for each sampled transcript, print its IPA and flag any multisyllable word whose IPA carries no primary-stress mark 'ˈ'. wrong stress is the #1 cause of "sounds bad" — catch it here, not after GPU-days. ponytail: thin wrapper over the espeak-ng CLI. ceiling = flags MISSING stress only, not WRONG-POSITION stress (that needs an ear on the Phase 4 audio). run: python piper/check_phonemes.py [--n 30] [--meta piper/dataset/metadata.csv] requires: sudo apt install espeak-ng """ import argparse import random import shutil import subprocess import sys from pathlib import Path STRESS = "ˈ" def ipa(text: str) -> str: out = subprocess.run( ["espeak-ng", "-v", "ru", "--ipa", "-q", text], capture_output=True, text=True, ) return out.stdout.strip() def main(): if not shutil.which("espeak-ng"): sys.exit("espeak-ng not found — sudo apt install espeak-ng") ap = argparse.ArgumentParser() ap.add_argument("--meta", default="tts/piper/dataset/metadata.csv") ap.add_argument("--n", type=int, default=30) args = ap.parse_args() lines = [l for l in Path(args.meta).read_text(encoding="utf-8").splitlines() if "|" in l] sample = random.sample(lines, min(args.n, len(lines))) flagged = 0 for line in sample: _id, text = line.split("|", 1) phon = ipa(text) bad = STRESS not in phon and len(text.split()) >= 1 mark = " ⚠ NO STRESS" if bad else "" flagged += bad print(f"{text}\n → {phon}{mark}\n") print(f"[{'!' if flagged else '✓'}] {flagged}/{len(sample)} samples missing stress marks") if __name__ == "__main__": main()