TTS-API Python: Text für die Sprachsynthese mit dem SDK vorbereiten
Diese API gibt keine Audio-Dateien zurück. Es ist ein Textmodell, das du mit dem OpenAI SDK in Python aufrufst und das vor einer Sprach-Engine geschaltet wird: Es schreibt das Skript, bereinigt es und teilt es in Sätze auf, die deine Sprach-Engine vorlesen kann.
Aktualisiert

Was diese API kann und nicht kann
Wenn du nach einem TTS-API-Python-Tutorial gesucht hast, muss vor dem Code eines klar sein: Dieser Dienst generiert keine Sprache. Er stellt POST /v1/chat/completions und GET /v1/models für ein Open-Weight-Textmodell mit der Modell-ID uncensored bereit. Es gibt keinen Audio-Endpunkt, keine Sprachliste und keine Audiodatei in der Antwort. Auch keine Bild-, Video- oder Embedding-Ausgabe.
Wo passt es also in ein Sprachprojekt? In den Textteil der Pipeline. Bevor eine Sprach-Engine ein Skript vorlesen kann, muss jemand es schreiben, Zahlen und Abkürzungen in gesprochene Wörter umwandeln, Markup entfernen und es in kurze Stücke schneiden, die sich ohne lange Pausen synthetisieren lassen. Ein Sprachmodell erledigt diese Arbeit gut, und du kannst es mit demselben SDK aufrufen, das du möglicherweise bereits für andere OpenAI-kompatible Dienste nutzt.
Das Modell ist darauf abgestimmt, bei rechtmäßiger Nutzung durch Erwachsene ohne Inhaltsfilter zu antworten, was für Fiktion, Charakterdialoge und Erzählungen wichtig ist, in denen ein strengeres Modell mitten in einer Szene abbrechen könnte. Eine harte Regel gilt: Keine sexuellen Inhalte mit Minderjährigen.
Der Rest dieses Artikels erstellt ein kleines Python-Modul in vier Schritten: ein erster Aufruf, ein Streaming-Sentenz-Splitter, ein Text-Normalisierer mit JSON-Ausgabe und ein Retry-Wrapper, der die Ratenlimits respektiert.
SDK installieren und ersten Aufruf durchführen
Du benötigst Python 3.8 oder neuer und das offizielle openai-Paket. Erstelle zuerst einen Schlüssel auf der API-Schlüssel-Seite. Neue Konten können dort mit einem Klick $0,50 Testguthaben beanspruchen, dafür wird keine Karte benötigt.
pip install openai
Halte den Schlüssel aus deinem Quellcode. Exportiere ihn als Umgebungsvariable und lese ihn im Code:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.xtts.cc/v1",
api_key=os.environ["XTTS_API_KEY"],
)
resp = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": "You write short narration scripts. Plain sentences only."},
{"role": "user", "content": "Write a 60-word intro for a podcast about night trains."},
],
temperature=0.8,
)
script = resp.choices[0].message.content
print(script)
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)
Das ist die gesamte Integration. Das SDK sendet Authorization: Bearer <key> für dich. Jede Antwort enthält auch den X-Balance-USD-Header, damit du dein verbliebenes Guthaben aus der Rohantwort lesen kannst. Die Dokumentationsseite listet die anderen Header und Parameter auf.
Parameter wie temperature, top_p, stop, seed und die Penalties werden durchgereicht. Für die Erzählung ist eine Temperatur zwischen 0,6 und 0,9 ein sinnvoller Startbereich, und ein fester seed hilft, wenn du eine Aufnahme reproduzieren möchtest, während du Prompts optimierst.
Skript streamen und in Sätze zerlegen
Sprach-Engines arbeiten normalerweise Satz für Satz. Wenn du auf das Ende eines 900-Wörter-Skripts wartest, wartet der Hörer auch. Mit stream: true sendet die API Token über Server-Sent Events, und du kannst jeden fertigen Satz an die Voice-Stage übergeben, während der Rest noch geschrieben wird.
import os
import re
from openai import OpenAI
client = OpenAI(base_url="https://api.xtts.cc/v1", api_key=os.environ["XTTS_API_KEY"])
SENTENCE_END = re.compile(r"(?<=[.?])\s+")
def stream_sentences(topic):
stream = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": "Write spoken narration. One idea per sentence."},
{"role": "user", "content": topic},
],
stream=True,
)
buffer = ""
for chunk in stream:
if not chunk.choices:
continue # the final chunk can carry usage only
buffer += chunk.choices[0].delta.content or ""
parts = SENTENCE_END.split(buffer)
for sentence in parts[:-1]:
yield sentence.strip()
buffer = parts[-1]
if buffer.strip():
yield buffer.strip()
for line in stream_sentences("Explain how a tide table is read."):
print(line) # replace with: your_voice_engine.say(line)
Zwei Details sind leicht zu übersehen. Erstens kommen die Nutzungszahlen am Ende des Streams in einem Chunk, der eine leere choices-Liste haben kann, weshalb die Schleife Chunks ohne Choices überspringt. Zweitens hält eine gestreamte Anfrage ihren Concurrency-Slot bis zum Stream-Ende. Wenn deine Voice-Engine langsam ist und du den Stream faul ausliest, bleibt der Slot belegt. Hole den Text schnell heraus, lege Sätze in eine Warteschlange und lass die Audio-Seite die Warteschlange in ihrem eigenen Tempo abarbeiten.
Der obige reguläre Ausdruck teilt an einem Punkt oder Fragezeichen gefolgt von Whitespace. Er teilt fälschlicherweise nach Abkürzungen wie „Dr.“ oder „Nr.“, was ein weiterer Grund ist, den Text zuerst zu normalisieren, wie der nächste Abschnitt zeigt.
Text für das Ohr normalisieren
Geschriebener Text und gesprochener Text unterscheiden sich. „Dr. Lee kaufte 3 Exemplare am 3/4 für die 2. Ausgabe“ ist leicht zu lesen und schwer auszusprechen. Viele Sprach-Engines verarbeiten einige dieser Fälle, aber die Ergebnisse variieren je nach Engine. Das Bereinigen des Skripts vor der Voice-Stage entfernt eine Kategorie von Überraschungen. Bitte das Modell, dies mit einer expliziten Anweisung zu tun:
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.xtts.cc/v1", api_key=os.environ["XTTS_API_KEY"])
NORMALIZE = (
"Rewrite the user's text so it can be read aloud. "
"Spell out numbers, currency, dates and abbreviations as words. "
"Remove markdown, URLs and emoji. Keep the meaning and the order. "
"Return only the rewritten text."
)
def normalize(text):
r = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": NORMALIZE},
{"role": "user", "content": text},
],
temperature=0.2,
)
return r.choices[0].message.content.strip()
print(normalize("Dr. Lee bought 3 copies on 3/4 for the 2nd edition."))
Verwende für diesen Schritt eine niedrige Temperatur. Du willst, dass dieselbe Eingabe dieselbe Ausgabe liefert, und Kreativität steht nur im Weg. Überprüfe eine Stichprobe der Ergebnisse, bevor du ein ganzes Buch damit verarbeitest, und entscheide, wie du mehrdeutige Elemente behandeln möchtest. Das Datum „3/4“ ist das Standardbeispiel: Lies es als 4. März oder 3. April? Trage deine Wahl in den System-Prompt ein, da das Modell deine Locale nicht erraten kann.
Kosten dieses Schritts
Normalisierung ist günstig. Ein 600-Token-Absatz mit 600 Tokens Input und ca. 650 Tokens Output kostet 600 x $0,15 / 1.000.000 = $0,00009 für Input, plus 650 x $0,50 / 1.000.000 = $0,000325 für Output, insgesamt $0,000415. Das sind ca. $0,42 für tausend Absätze. Die Preisseite enthält die vollständige Tabelle und Optionen zum Aufladen.
Strukturierte Segmente mit JSON-Modus zurückgeben
Einige Sprach-Engine akzeptieren Einstellungen pro Segment: eine Pausenlänge, eine Sprechgeschwindigkeit, eine andere Stimme für einen anderen Charakter. Du kannst das Modell diese Anweisungen als JSON produzieren lassen und sie in Python lesen. Setze response_format auf {"type": "json_object"} und beschreibe auch die Form im Prompt, da JSON-Modus gültiges JSON garantiert, nicht dein Schema.
import json
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.xtts.cc/v1", api_key=os.environ["XTTS_API_KEY"])
prompt = (
"Split this scene into segments. Return JSON like "
'{"segments": [{"speaker": "narrator", "text": "...", "pause_ms": 300}]}. '
"Speakers are narrator, mara, or tom."
)
r = client.chat.completions.create(
model="uncensored",
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": prompt},
{"role": "user", "content": "Mara opens the door. Tom says he is late again. She sighs and lets him in."},
],
temperature=0.3,
)
data = json.loads(r.choices[0].message.content)
for seg in data["segments"]:
print(seg["speaker"], seg["pause_ms"], seg["text"])
Validiere die Rückgabe. Prüfe, ob segments existiert, ob jeder Eintrag einen bekannten Sprecher hat und ob pause_ms eine Zahl in einem von dir akzeptierten Bereich ist. Bei einem Validierungsfehler wiederhole einmal mit einer niedrigeren Temperatur, bevor du aufgibst. Behandle Modellausgaben als nicht vertrauenswürdige Eingabe, genau wie jeden Benutzertext.
Fallstricke, die Zeit kosten
Einige Fehler tauchen immer wieder auf, wenn Leute ein Sprachmodell vor eine Sprach-Engine schalten.
- Dem Modell Kommentare hinzufügen lassen. Modelle sagen gerne „Sicher, hier ist dein Skript.“ Dieser Satz würde vorgelesen. Beende deinen System-Prompt mit „Gib nur den Skripttext zurück“ und entferne eine führende Zeile, wenn sie mit einem Doppelpunkt endet.
- Der Satzlänge vertrauen. Einige Engines haben Probleme mit sehr langen Sätzen. Füge eine Regel wie „Kein Satz länger als 25 Wörter“ zum Prompt hinzu und setze sie in Python auch durch Aufteilung bei Kommas durch, wenn ein Satz immer noch zu lang ist.
- Das Kontextfenster vergessen. Prompt und Completion teilen sich 100.000 Token. Sende ein langes Buchkapitel in Abschnitten mit einer kurzen Zusammenfassung des vorherigen Abschnitts anstelle des vollen Textes. Das hält die Input-Kosten auch niedrig.
- Einen globalen Client ohne Limit in Threads wiederverwenden. Der Client ist sicher teilbar, der Dienst nicht unbegrenzt. Begrenze deinen Pool auf die Konkurrenz deines Keys.
- Den Key hard-coden. Verwende eine Umgebungsvariable und rotiere den Key, falls er jemals in einem Repository landet.
Wenn etwas falsch aussieht, logge den X-Request-Id-Header zusammen mit dem Prompt, der ihn ausgelöst hat. Wenn du Hilfe brauchst, ist diese ID der schnellste Weg, um genau eine bestimmte Anfrage zu identifizieren, und die Dokumentation erklärt, wo du sie findest.
Ratenlimits und Wiederholungen handhaben
Ein voller Schlüssel erlaubt 8 parallele Anfragen und 300 Anfragen pro Minute. Ein Test-Schlüssel erlaubt 2 parallele und 60 pro Minute. Wird das Limit überschritten, erhältst du HTTP 429 mit dem Fehlercode rate_limited für Anfragen pro Minute oder concurrency für zu viele gleichzeitig. Bei temporärer Überlastung antwortet die API ebenfalls mit 429 und sendet Retry-After: 5.
Das SDK wiederholt automatisch, aber ein expliziter Wrapper gibt dir die Kontrolle und macht das Verhalten in Logs sichtbar. Kombiniere ihn mit einem Thread-Pool, der auf deinen Key abgestimmt ist, und du wirst ein 429 fast gar nicht sehen:
import os
import time
from concurrent.futures import ThreadPoolExecutor
from openai import OpenAI, RateLimitError
client = OpenAI(
base_url="https://api.xtts.cc/v1",
api_key=os.environ["XTTS_API_KEY"],
max_retries=0,
)
def ask(prompt, tries=5):
for attempt in range(tries):
try:
r = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": prompt}],
)
return r.choices[0].message.content
except RateLimitError as err:
wait = int(err.response.headers.get("Retry-After", 5))
time.sleep(wait * (attempt + 1))
raise RuntimeError("still rate limited after retries")
prompts = ["Narrate a sunrise in two sentences."] * 20
with ThreadPoolExecutor(max_workers=8) as pool: # use 2 on a trial key
results = list(pool.map(ask, prompts))
print(len(results))
Fehlgeschlagene oder verweigerte Anfragen werden nicht berechnet, daher kostet dich ein Retry nach einem Fehler nicht für den fehlgeschlagenen Versuch. Eine abgeschlossene Anfrage wird normal abgerechnet, daher vermeide blinde Wiederholungen bei langsamen, aber erfolgreichen Aufrufen.
Den Text an deine Sprach-Engine übergeben
Der letzte Schritt liegt außerhalb dieser API. Python hat mehrere Möglichkeiten, Audio aus Text zu erzeugen: Open-Source-Sprachmodelle, die du selbst ausführen kannst, Desktop-Sprachdienste, die in einem Betriebssystem integriert sind, und gehostete Sprach-APIs anderer Unternehmen. Jede hat ihre eigene Lizenz, Qualität und ihren Preis, also prüfe ihre aktuelle Dokumentation und Nutzungsbedingungen, bevor du dich entscheidest. Diese Website liefert diesen Teil nicht.
Eine saubere Grenze hält deinen Code einfach änderbar. Schreibe eine Funktion mit der Signatur speak(text: str) -> None und halte alle Engine-spezifischen Details darin. Die Text-Pipeline oben ruft diese Funktion für jeden Satz oder Segment auf. Wenn du die Engine wechselst, ändert sich nichts anderes.
Wenn du dich noch zwischen einem Trial und einem bezahlten Setup entscheidest, sind die Zahlen einfach: Du zahlst $0,15 pro Million Input-Token und $0,50 pro Million Output-Token aus Prepaid-Guthaben, das verfällt nie, ohne Abo. Aufladungen reichen von $20 bis $500 und können mit Krypto (USDT auf TRC20, USDC auf Base) bezahlt werden.
Weitere Anleitungen
Fragen und Antworten
Gibt diese API Audiodateien zurück?
Nein. Es gibt nur Text zurück. Nutze es, um Skripte zu schreiben, zu bereinigen und zu strukturieren, und übergebe den Text dann an eine separate Sprach-Engine.
Welches Python-Paket soll ich verwenden?
Das offizielle openai-Paket. Setze base_url auf https://api.xtts.cc/v1, gib deinen Schlüssel ein und verwende die Modell-ID uncensored.
Kann ich Antworten in Python streamen?
Ja. Übergebe stream=True und iteriere über die Chunks. Die Nutzung wird am Ende des Streams gemeldet, und die Anfrage hält ihren Concurrency-Slot, bis der Stream abgeschlossen ist.
Was passiert, wenn ich das Ratenlimit treffe?
Du erhältst HTTP 429 mit dem Code rate_limited oder concurrency. Warte den Retry-After-Wert ab, wenn vorhanden, und wiederhole die Anfrage. Halte die Anzahl der Worker bei einem vollständigen Schlüssel bei oder unter 8, bei einem Trial-Schlüssel bei oder unter 2.
Dein Schlüssel ist nur ein Formular entfernt
Erstelle ein Konto, kopiere den Schlüssel, ändere die Basis-URL. Das ist die gesamte Einrichtung.