#!/usr/bin/env python3
"""speak.py — deutsche Sprachausgabe über Piper (lokal, offline, kostenlos).

Warum Piper und nicht die Windows-Sprachausgabe: auf diesem Rechner ist KEINE
deutsche SAPI-/OneCore-Stimme installiert (nur David/Zira/Mark, alle en-US) —
deutscher Text käme mit englischer Aussprache heraus. Piper bringt eine eigene
deutsche Stimme mit, läuft komplett lokal (passt zum Rest des Setups: WireGuard-
only, Secrets in der ini, keine Cloud-Abhängigkeit) und kostet nichts.

Aufruf:
    python tools/speak.py "Der Text"
    echo "Der Text" | python tools/speak.py
    python tools/speak.py --datei meldung.txt

Rückgabe 0 = gesprochen, 1 = Fehler (fehlt Piper/Modell → Hinweis auf stderr).
Fail-safe gedacht: ein Aufrufer im Bot soll bei fehlender Sprachausgabe
weiterlaufen, nicht abstürzen.
"""
from __future__ import annotations

import argparse
import os
import subprocess
import sys
import tempfile

HERE = os.path.dirname(os.path.abspath(__file__))


def _finde(name: str, *starts: str) -> str:
    """Erste Datei `name` unterhalb der Startpfade. ⚠ Das Piper-ZIP entpackt eine
    Ebene tiefer als erwartet (`tools/piper/piper/piper.exe`) — fest verdrahtete
    Pfade brechen deshalb, sobald jemand anders entpackt oder das Layout sich mit
    einer neuen Version ändert. Suchen ist hier robuster als raten."""
    for s in starts:
        if os.path.isfile(s):
            return s
        for wurzel, _dirs, dateien in os.walk(s):
            if name in dateien:
                return os.path.join(wurzel, name)
    return ""


PIPER = _finde("piper.exe", os.path.join(HERE, "piper"))
MODELL = _finde("de_DE-thorsten-medium.onnx", os.path.join(HERE, "piper"))


def sprich(text: str, behalten: str | None = None) -> int:
    text = (text or "").strip()
    if not text:
        return 0
    if not PIPER or not MODELL:
        print(f"Piper oder Stimme nicht gefunden unter {os.path.join(HERE, 'piper')}\n"
              f"  piper.exe: {PIPER or 'FEHLT'}\n  Stimme:    {MODELL or 'FEHLT'}",
              file=sys.stderr)
        return 1
    wav = behalten or os.path.join(tempfile.gettempdir(), "speak_out.wav")
    try:
        subprocess.run([PIPER, "--model", MODELL, "--output_file", wav],
                       input=text.encode("utf-8"),
                       stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
                       check=True, timeout=120)
    except Exception as e:
        print(f"Piper-Aufruf fehlgeschlagen: {e}", file=sys.stderr)
        return 1
    if behalten:                       # nur erzeugen, nicht abspielen
        return 0
    # Abspielen über den .NET-SoundPlayer — kein zusätzliches Paket nötig.
    ps = (f"(New-Object Media.SoundPlayer '{wav}').PlaySync()")
    try:
        subprocess.run(["powershell", "-NoProfile", "-Command", ps],
                       stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
                       check=True, timeout=300)
    except Exception as e:
        print(f"Wiedergabe fehlgeschlagen: {e}", file=sys.stderr)
        return 1
    return 0


def main() -> None:
    p = argparse.ArgumentParser(description="Deutsche Sprachausgabe via Piper")
    p.add_argument("text", nargs="*", help="zu sprechender Text")
    p.add_argument("--datei", help="Text aus Datei lesen")
    p.add_argument("--wav", help="nur WAV erzeugen, nicht abspielen")
    a = p.parse_args()
    if a.datei:
        with open(a.datei, encoding="utf-8") as f:
            txt = f.read()
    elif a.text:
        txt = " ".join(a.text)
    else:
        txt = sys.stdin.read()          # Pipe-Betrieb
    sys.exit(sprich(txt, a.wav))


if __name__ == "__main__":
    main()
