# INSTRUMENT-AUSNAHME: Victor-Go — Holmes/Watson v3: D+F Settings, kein SSML, langer Tail
"""
Wie v2, aber:
  - Holmes: stability=0.95, similarity=0.30, style=0.0, speaker_boost=False, KEIN SSML
  - Watson: stability variiert, similarity=0.42, speaker_boost=False, KEIN SSML
  - TAIL_MS = 600ms + 80ms fade-out (kein abgehackter Atmer)
  - Output: watson_demo_clips/watson_v3.mp3
"""
import json, base64, time, subprocess, urllib.request, urllib.error
from pathlib import Path
from pydub import AudioSegment, effects

API_KEY = "sk_cac6e576af419777a078b639869a5b3f36ae04eca65d6e37"
CLIPS   = Path("/Users/victorholland/Vibe Coding/dispatcher/cockpit/watson_demo_clips")
LOG     = Path("/tmp/watson_v3_gen.log")
MODEL   = "eleven_multilingual_v2"
WATSON  = "T080SqoEEtD27526TTTa"
HOLMES  = "kMg8CgFduU4YUWmbRBx1"
CLIPS.mkdir(exist_ok=True)
LOG.write_text("")

LEAD_MS = 30
TAIL_MS = 600
FADE_OUT_MS = 80

def log(msg):
    print(msg, flush=True)
    with LOG.open("a") as f:
        f.write(msg + "\n")

def vs_holmes(stability=0.95, similarity=0.30, style=0.0):
    return {"stability": stability, "similarity_boost": similarity,
            "style": style, "use_speaker_boost": False}

def vs_watson(stability=0.50, similarity=0.42, style=0.28):
    return {"stability": stability, "similarity_boost": similarity,
            "style": style, "use_speaker_boost": False}

DIALOG = [
    ("d01","holmes",HOLMES, vs_holmes(),
     "Watson. Ich habe ein kleines Rätsel für Sie. Erschrecken Sie bitte nicht."),
    ("d02","watson",WATSON, vs_watson(0.52,0.42,0.20),
     "Holmes, es ist halb zehn. Ich sitze in meinem Sessel. Sagen Sie mir nicht, dass das Mathematik wird."),
    ("d03","holmes",HOLMES, vs_holmes(),
     "Mathematik. Emil schaut zu. Bemühen Sie sich."),
    ("d04","watson",WATSON, vs_watson(0.48,0.42,0.24),
     "Na wunderbar. Was für ein herrlicher Abend."),
    ("d05","holmes",HOLMES, vs_holmes(),
     "Mrs. Hudson verlangt für unser Zimmer in der Baker Street zwei Shilling Grundgebühr — plus einen halben Shilling pro Nacht. Wieviel zahlen wir nach zehn Nächten?"),
    ("d06","watson",WATSON, vs_watson(0.50,0.42,0.22),
     "Zwei Shilling Grundgebühr... plus zehn mal ein halber... das sind fünf... macht sieben Shilling."),
    ("d07","holmes",HOLMES, vs_holmes(),
     "Richtig. Das nennt man eine lineare Funktion. y gleich m mal x plus b. Die Grundgebühr ist b — der Preis pro Nacht ist m. Einfach, nicht wahr?"),
    ("d08","watson",WATSON, vs_watson(0.55,0.42,0.18),
     "Erschreckend einfach, ja."),
    ("d09","holmes",HOLMES, vs_holmes(),
     "Fast alles im Leben ist erschreckend einfach, Watson. Man muss nur hinschauen."),
    ("d10","watson",WATSON, vs_watson(0.42,0.41,0.30),
     "Und wenn ich nach zehn Shilling insgesamt frage — nach wievielen Nächten erreichen wir die?"),
    ("d11","holmes",HOLMES, vs_holmes(),
     "Das ist die richtige Frage. x gleich zehn minus zwei, geteilt durch null Komma fünf. Macht sechzehn Nächte."),
    ("d12","watson",WATSON, vs_watson(0.54,0.42,0.20),
     "Das stimmt. Ich habe es im Kopf nachgerechnet."),
    ("d13","holmes",HOLMES, vs_holmes(),
     "Ich weiß. Ich habe es an Ihren Lippen abgelesen."),
    ("d14","holmes",HOLMES, vs_holmes(),
     "Kommen wir zu Pythagoras."),
    ("d15","watson",WATSON, vs_watson(0.38,0.41,0.40),
     "Schon der Name macht mir Bauchweh."),
    ("d16","holmes",HOLMES, vs_holmes(),
     "Watson. Ein Einbrecher — rein hypothetisch natürlich — steht drei Meter von einer vier Meter hohen Wand entfernt. Wie lang muss seine Leiter sein?"),
    ("d17","watson",WATSON, vs_watson(0.32,0.41,0.50),
     "Ah — das ist Pythagoras! Drei hoch zwei ist neun, vier hoch zwei sechzehn... zusammen fünfundzwanzig... Wurzel fünf! Fünf Meter Leiter!"),
    ("d18","holmes",HOLMES, vs_holmes(0.92,0.28,0.0),
     "Korrekt. Ich bin... beeindruckt."),
    ("d19","watson",WATSON, vs_watson(0.36,0.41,0.44),
     "Sie sind nie beeindruckt."),
    ("d20","holmes",HOLMES, vs_holmes(),
     "Ich sagte, ich bin beeindruckt. Nicht, dass ich es zeige."),
    ("d21","watson",WATSON, vs_watson(0.28,0.41,0.55),
     "Holmes — war das eben ein Kompliment?"),
    ("d22","holmes",HOLMES, vs_holmes(),
     "Es war eine Beobachtung. Fahren Sie fort."),
    ("d23","watson",WATSON, vs_watson(0.54,0.43,0.20),
     "Der Satz des Pythagoras: a im Quadrat plus b im Quadrat gleich c im Quadrat. c ist immer die Hypotenuse — die längste Seite, die dem rechten Winkel gegenüberliegt."),
    ("d24","holmes",HOLMES, vs_holmes(),
     "Und das gilt ausschließlich bei rechtwinkligen Dreiecken. Das ist entscheidend."),
    ("d25","watson",WATSON, vs_watson(0.40,0.41,0.35),
     "Was ist, wenn der Einbrecher keinen rechten Winkel hat?"),
    ("d26","holmes",HOLMES, vs_holmes(),
     "Dann ist er ein schlechter Einbrecher."),
    ("d27","holmes",HOLMES, vs_holmes(),
     "Jetzt kommt das Schwere. Gleichungssysteme und Textaufgaben."),
    ("d28","watson",WATSON, vs_watson(0.50,0.42,0.25),
     "Hier bin ich — offen gestanden — tatsächlich schwach."),
    ("d29","holmes",HOLMES, vs_holmes(),
     "Das weiß ich. Es ist Ihr Kryptonit, Watson."),
    ("d30","watson",WATSON, vs_watson(0.44,0.41,0.32),
     "Mein was?"),
    ("d31","holmes",HOLMES, vs_holmes(),
     "Aufgabe: Ich kaufe zwei Würste und ein Bier für sieben Pence. Drei Würste und zwei Bier kosten elf Pence. Was kostet eine Wurst — was kostet ein Bier?"),
    ("d32","watson",WATSON, vs_watson(0.38,0.41,0.42),
     "Hmm... wenn zwei Würste und ein Bier sieben Pence sind... dann könnte eine Wurst... drei... nein, zwei... ich verliere völlig den Faden."),
    ("d33","holmes",HOLMES, vs_holmes(),
     "Stopp. Das ist der klassische Fehler. Sie rechnen, bevor Sie denken. Erst Variablen."),
    ("d34","watson",WATSON, vs_watson(0.52,0.42,0.22),
     "Also... w für Wurst, b für Bier?"),
    ("d35","holmes",HOLMES, vs_holmes(),
     "Gut. Erste Gleichung — direkt aus dem Text."),
    ("d36","watson",WATSON, vs_watson(0.54,0.43,0.20),
     "Zwei w plus b gleich sieben."),
    ("d37","holmes",HOLMES, vs_holmes(),
     "Zweite?"),
    ("d38","watson",WATSON, vs_watson(0.52,0.42,0.22),
     "Drei w plus zwei b gleich elf."),
    ("d39","holmes",HOLMES, vs_holmes(),
     "Ausgezeichnet. Jetzt: multiplizieren Sie die erste Gleichung mit zwei."),
    ("d40","watson",WATSON, vs_watson(0.55,0.43,0.20),
     "Vier w plus zwei b gleich vierzehn."),
    ("d41","holmes",HOLMES, vs_holmes(),
     "Und subtrahieren Sie davon die zweite Gleichung."),
    ("d42","watson",WATSON, vs_watson(0.36,0.41,0.45),
     "Vier w minus drei w... das ist ein w. Und vierzehn minus elf ist drei. Also... eine Wurst kostet drei Pence?"),
    ("d43","holmes",HOLMES, vs_holmes(0.92,0.28,0.0),
     "Korrekt."),
    ("d44","watson",WATSON, vs_watson(0.34,0.41,0.48),
     "Und dann: b gleich sieben minus sechs... ein Pence für ein Bier! Holmes — ich hab's!"),
    ("d45","holmes",HOLMES, vs_holmes(0.90,0.28,0.0),
     "Watson. Das. War. Korrekt."),
    ("d46","watson",WATSON, vs_watson(0.52,0.42,0.22),
     "Das war... das war gar nicht so schrecklich."),
    ("d47","holmes",HOLMES, vs_holmes(),
     "Es war überhaupt nicht schrecklich. Die Methode: erst Text lesen, Variablen benennen, Gleichungen aufschreiben — dann, und erst dann, rechnen."),
    ("d48","watson",WATSON, vs_watson(0.58,0.43,0.18),
     "Erst Text. Dann Variablen. Dann Gleichungen. Dann rechnen. Immer in dieser Reihenfolge."),
    ("d49","holmes",HOLMES, vs_holmes(),
     "Genau so. Und nicht anders."),
    ("d50","watson",WATSON, vs_watson(0.60,0.44,0.16),
     "Also, Emil — das war's für heute Abend. Lineare Funktionen, Pythagoras, Gleichungssysteme mit Textaufgaben. Ganz schön viel für einen einzigen Abend in der Baker Street."),
    ("d51","holmes",HOLMES, vs_holmes(0.92,0.30,0.0),
     "Und übrigens, Emil — machen Sie sich keine Sorgen wegen morgen. Sie sind ja nun nicht auf den Kopf gefallen."),
    ("d52","watson",WATSON, vs_watson(0.64,0.44,0.14),
     "Gehen Sie in diese Prüfung, junger Mann, mit dem Kopf oben. Wir glauben an Sie."),
    ("d53","holmes",HOLMES, vs_holmes(),
     "Watson hat recht. Was ich — ungern, aber ehrlich — gar nicht so selten zugeben muss."),
    ("d54","watson",WATSON, vs_watson(0.35,0.41,0.45),
     "Holmes!"),
    ("d55","holmes",HOLMES, vs_holmes(0.92,0.28,0.0),
     "In mathematischen Dingen jedenfalls."),
]

PAUSE_AFTER = {
    "d04": 500, "d09": 400, "d13": 600,
    "d14": 900,
    "d17": 700, "d18": 800, "d22": 500,
    "d26": 1000,
    "d29": 400, "d33": 600, "d35": 300,
    "d37": 200, "d39": 400, "d41": 500,
    "d43": 700, "d45": 800, "d46": 500,
    "d49": 1000,
    "d51": 700, "d53": 500, "d54": 300,
}

def get_speech_bounds(ts_path, audio_len_ms):
    try:
        alignment = json.loads(ts_path.read_text())
        chars  = alignment.get("characters", [])
        starts = alignment.get("character_start_times_seconds", [])
        ends   = alignment.get("character_end_times_seconds", [])
        if not chars:
            raise ValueError("leer")
        in_tag, real = False, []
        for c, s, e in zip(chars, starts, ends):
            if c == '<': in_tag = True
            if not in_tag: real.append((c, s, e))
            if c == '>': in_tag = False
        if not real:
            raise ValueError("keine Sprachzeichen")
        return int(real[0][1] * 1000), int(real[-1][2] * 1000)
    except Exception:
        return 30, audio_len_ms - 30

log("=== Watson v3 — Generierung (D+F Settings, kein SSML) ===")
errors = 0
texts = [row[4] for row in DIALOG]

for i, (did, sp, voice_id, vs, text) in enumerate(DIALOG):
    out_mp3 = CLIPS / f"{did}_{sp}.mp3"
    out_ts  = CLIPS / f"{did}_{sp}.timestamps.json"

    prev_text = texts[i-1] if i > 0 else ""
    next_text = texts[i+1] if i < len(DIALOG)-1 else ""

    payload = {"text": text, "model_id": MODEL, "voice_settings": vs}
    if prev_text:
        payload["previous_text"] = prev_text
    if next_text:
        payload["next_text"] = next_text

    req = urllib.request.Request(
        f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}/with-timestamps",
        data=json.dumps(payload).encode(),
        headers={"xi-api-key": API_KEY, "Content-Type": "application/json",
                 "Accept": "application/json"},
        method="POST"
    )
    try:
        with urllib.request.urlopen(req, timeout=60) as r:
            resp = json.loads(r.read())
        out_mp3.write_bytes(base64.b64decode(resp["audio_base64"]))
        out_ts.write_text(json.dumps(resp["alignment"], ensure_ascii=False))
        dur = float(subprocess.check_output(
            ["ffprobe","-v","quiet","-show_entries","format=duration","-of","csv=p=0",str(out_mp3)],
            text=True).strip())
        log(f"  ✓ {did} ({sp:8s})  {dur:.2f}s")
    except urllib.error.HTTPError as e:
        log(f"  ✗ {did} HTTP {e.code}: {e.read()[:150]}")
        errors += 1
    except Exception as e:
        log(f"  ✗ {did}: {e}")
        errors += 1
    time.sleep(0.4)

log(f"\n{len(DIALOG)-errors}/{len(DIALOG)} Clips OK")

# ─── Mix ─────────────────────────────────────────────────────────────────────
log("\n=== Mix ===")
trimmed = []
for did, sp, *_ in DIALOG:
    mp3 = CLIPS / f"{did}_{sp}.mp3"
    ts  = CLIPS / f"{did}_{sp}.timestamps.json"
    if not mp3.exists():
        log(f"  ✗ {did} fehlt")
        continue
    raw  = AudioSegment.from_mp3(str(mp3))
    norm = effects.normalize(raw, headroom=1.0)
    s_ms, e_ms = get_speech_bounds(ts, len(norm))
    cut_start = max(0, s_ms - LEAD_MS)
    cut_end   = min(len(norm), e_ms + TAIL_MS)
    if cut_end - cut_start < 200:
        cut_start, cut_end = 0, len(norm)
    clipped = norm[cut_start:cut_end].fade_out(FADE_OUT_MS)
    trimmed.append((did, clipped))

if not trimmed:
    raise SystemExit("Keine Clips!")

result = trimmed[0][1]
for i in range(1, len(trimmed)):
    did_prev = trimmed[i-1][0]
    _, seg   = trimmed[i]
    pause_ms = PAUSE_AFTER.get(did_prev, 0)
    if pause_ms > 0:
        result = result + AudioSegment.silent(duration=pause_ms, frame_rate=44100)
    result = result.append(seg, crossfade=0)

OUT = CLIPS / "watson_v3.mp3"
log(f"\nGesamt: {len(result)/1000:.1f}s — Exportiere...")
result.export(str(OUT), format="mp3", bitrate="320k",
              tags={"title":"Holmes & Watson — Mathe für Emil v3",
                    "artist":"Watson Demo", "album":"Baker Street Hörspiel"})
log(f"Fertig: {OUT.stat().st_size//1024} KB")
print(f"\n✓ {OUT.name}  {OUT.stat().st_size//1024} KB  {len(result)/1000:.1f}s")
