"""Diagnostic: why don't [laughs]/[crying] audio tags render? Answers three questions with measurements, not theories: 1. Is `eleven_v3` actually available to this account? (If not, the API may be serving a v2 model that ignores audio tags entirely.) 2. Does voice_settings suppress tags? v3 documents stability as discrete 0.0/0.5/1.0, and higher stability = less responsive to audio tags. We currently send 0.40-0.50 + style + use_speaker_boost. 3. Does tag POSITION (head vs tail) or tag CHOICE ([laughs] vs [laughs harder]) decide whether it fires? Method: synthesize the same sentence under N configs and measure duration. A rendered laugh/sob ADDS audible time. Control (no tag) is the baseline; any config whose duration ~= control did not render its tag. Run: ELEVENLABS_API_KEY=sk_... /bin/python scripts/_tts_probe.py Outputs mp3s to scripts/_probe_out/ so you can listen to confirm. """ import os import subprocess import sys from pathlib import Path OUT = Path(__file__).resolve().parent / "_probe_out" VOICE = "TbMNBJ27fH2U0VgpSNko" # joy-female, same voice the laughter clips use SAD_VOICE = "m3yAHyFEFKtbCIM5n7GF" # sadness-female LAUGH_BASE = "가끔은 밥 차리는 게 하기 싫을 때가 있잖아요" SOB_BASE = "점수가 반이나 잘렸어" CUR = {"stability": 0.45, "similarity_boost": 0.75, "style": 0.30, "use_speaker_boost": True} CREATIVE = {"stability": 0.0, "similarity_boost": 0.75} # v3 "Creative": most tag-responsive NATURAL = {"stability": 0.5, "similarity_boost": 0.75} # v3 "Natural" # (label, text, voice, voice_settings, model) PROBES = [ ("01_control_notag", f"{LAUGH_BASE}.", VOICE, CUR, "eleven_v3"), ("02_head_laughs_cur", f". [laughs] {LAUGH_BASE}.", VOICE, CUR, "eleven_v3"), ("03_head_laughs_creative", f". [laughs] {LAUGH_BASE}.", VOICE, CREATIVE, "eleven_v3"), ("04_head_laughsharder_cur", f". [laughs harder] {LAUGH_BASE}.", VOICE, CUR, "eleven_v3"), ("05_tail_laughs_cur", f"{LAUGH_BASE} [laughs].", VOICE, CUR, "eleven_v3"), ("06_tail_laughs_creative", f"{LAUGH_BASE} [laughs].", VOICE, CREATIVE, "eleven_v3"), ("07_head_laughs_natural", f". [laughs] {LAUGH_BASE}.", VOICE, NATURAL, "eleven_v3"), ("08_head_laughs_v2", f". [laughs] {LAUGH_BASE}.", VOICE, CUR, "eleven_multilingual_v2"), ("09_sob_control", f"{SOB_BASE}.", SAD_VOICE, CUR, "eleven_v3"), ("10_sob_tail_cur", f"{SOB_BASE} [crying].", SAD_VOICE, CUR, "eleven_v3"), ("11_sob_tail_creative", f"{SOB_BASE} [crying].", SAD_VOICE, CREATIVE, "eleven_v3"), ("12_sob_double_tag", f". [sorrowful] {SOB_BASE} [crying].", SAD_VOICE, CUR, "eleven_v3"), ] def dur(p: Path) -> float: try: r = subprocess.run( ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", str(p)], capture_output=True, text=True, check=True) return float(r.stdout.strip()) except Exception: return float("nan") def main(): key = os.getenv("ELEVENLABS_API_KEY") if not key: sys.exit("ELEVENLABS_API_KEY not set") from elevenlabs.client import ElevenLabs client = ElevenLabs(api_key=key) print("=" * 72) print("Q1: which models does this account actually have?") print("=" * 72) try: for m in client.models.list(): mid = getattr(m, "model_id", "?") if "v3" in mid.lower() or "multilingual" in mid.lower() or "turbo" in mid.lower(): print(f" {mid:34s} tts={getattr(m,'can_do_text_to_speech','?')} " f"name={getattr(m,'name','?')}") except Exception as e: print(f" [models.list failed] {e}") OUT.mkdir(exist_ok=True) print() print("=" * 72) print("Q2/Q3: does the tag add audible time? (control = no tag)") print("=" * 72) results = {} for label, text, voice, vs, model in PROBES: p = OUT / f"{label}.mp3" try: audio = client.text_to_speech.convert( voice_id=voice, text=text, model_id=model, output_format="mp3_44100_128", voice_settings=vs) with p.open("wb") as f: for c in audio: f.write(c) d = dur(p) results[label] = d print(f" {label:28s} {d:6.2f}s model={model:24s} stab={vs.get('stability')}") except Exception as e: print(f" {label:28s} FAILED: {str(e)[:90]}") print() print("=" * 72) print("VERDICT (delta vs control; a rendered laugh/sob should add >0.4s)") print("=" * 72) for ctrl, group in (("01_control_notag", [k for k in results if k.startswith(("02", "03", "04", "05", "06", "07", "08"))]), ("09_sob_control", [k for k in results if k.startswith(("10", "11", "12"))])): if ctrl not in results: continue base = results[ctrl] print(f"\n control {ctrl} = {base:.2f}s") for k in sorted(group): d = results[k] - base verdict = "TAG FIRED" if d > 0.4 else "no effect" print(f" {k:28s} {results[k]:6.2f}s {d:+6.2f}s {verdict}") print(f"\nListen to the mp3s in {OUT} to confirm.") if __name__ == "__main__": main()