API TTS Python : Préparez le texte pour la parole avec le SDK
Cette API ne renvoie pas d'audio. C'est un modèle textuel que vous appelez depuis Python avec le SDK OpenAI, et il se place devant un moteur vocal : il rédige le script, le nettoie et le découpe en phrases que votre étape de synthèse peut lire à voix haute.
Mis à jour le

Ce que cette API fait et ne fait pas
Si vous avez cherché un tutoriel tts api python, un point doit être clair avant tout code : ce service ne synthétise pas la parole. Il expose POST /v1/chat/completions et GET /v1/models pour un seul modèle de texte à poids ouverts, avec l'ID de modèle uncensored. Il n'y a pas d'endpoint audio, pas de liste de voix et aucun fichier audio dans les réponses. Il ne produit également ni image, ni vidéo, ni embedding.
Alors, où s'insère-t-il dans un projet vocal ? Dans la partie texte du pipeline. Avant qu'un moteur vocal puisse lire un script, quelqu'un doit le rédiger, développer les nombres et abréviations en mots prononcés, supprimer les balises et le découper en segments suffisamment courts pour être synthétisés sans longues pauses. Un modèle de langage effectue ce travail efficacement, et vous pouvez l'appeler depuis Python avec le même SDK que vous utilisez peut-être déjà pour d'autres services compatibles OpenAI.
Le modèle est ajusté pour répondre sans refus de contenu pour un usage adulte légal, ce qui est important pour la fiction, les dialogues de personnages et la narration où un modèle plus strict pourrait s'arrêter en plein milieu d'une scène. Une règle stricte s'applique : aucun contenu sexuel impliquant des mineurs.
Le reste de cet article construit un petit module Python en quatre étapes : un premier appel, un séparateur de phrases en streaming, un normalisateur de texte avec sortie JSON, et un wrapper de réessai qui respecte les limites de débit.
Installez le SDK et effectuez un premier appel
Vous avez besoin de Python 3.8 ou plus récent et du package officiel openai. Créez une clé sur la page Clé API. Les nouveaux comptes peuvent réclamer un crédit d'essai de 0,50 $ en un clic, sans carte bancaire.
pip install openai
Gardez la clé hors de votre code source. Exportez-la en tant que variable d'environnement et lisez-la dans le 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)
Voici toute l'intégration. Le SDK envoie Authorization: Bearer <key> pour vous. Chaque réponse inclut également l'en-tête X-Balance-USD, afin que vous puissiez lire votre crédit restant depuis la réponse brute si vous souhaitez le journaliser. La page de documentation docs liste les autres en-têtes et paramètres.
Les paramètres tels que temperature, top_p, stop, seed et les pénalités sont transmis. Pour la narration, une température entre 0,6 et 0,9 est une plage de départ raisonnable, et un seed fixe aide lorsque vous souhaitez reproduire une prise tout en ajustant les prompts.
Streamez le script et découpez-le en phrases
Les moteurs vocaux fonctionnent généralement phrase par phrase. Si vous attendez qu'un script de 900 mots soit terminé, l'auditeur attend aussi. Avec stream: true, l'API envoie des tokens via des événements envoyés par le serveur, et vous pouvez transmettre chaque phrase terminée à l'étape vocale tandis que la suite est encore en cours de rédaction.
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)
Deux détails sont faciles à manquer. Premièrement, les chiffres d'utilisation arrivent à la fin du stream, dans un fragment qui peut avoir une liste choices vide, c'est pourquoi la boucle ignore les fragments sans choices. Deuxièmement, une requête en streaming occupe son slot de concurrence jusqu'à la fin du stream. Si votre moteur vocal est lent et que vous lisez le stream de manière paresseuse, le slot reste occupé. Extrayez le texte rapidement, placez les phrases dans une file d'attente et laissez le côté audio consommer la file d'attente à son propre rythme.
L'expression régulière ci-dessus divise sur un point ou un point d'interrogation suivi d'un espace. Elle divisera incorrectement après des abréviations comme « Dr. » ou « M. », ce qui est une raison de plus de normaliser le texte d'abord, comme le montre la section suivante.
Normalisez le texte pour l'oreille
Le texte écrit et le texte parlé diffèrent. « Le Dr Lee a acheté 3 exemplaires le 3/4 pour la 2e édition » est facile à lire et mal à dire. De nombreux moteurs vocaux gèrent une partie de cela, mais les résultats varient selon le moteur, donc nettoyer le script avant qu'il n'atteigne l'étape vocale élimine une classe de surprises. Demandez au modèle de le faire avec une instruction explicite :
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."))
Utilisez une température faible pour cette étape. Vous voulez que la même entrée donne la même sortie, et la créativité ne fait que gêner. Examinez un échantillon des résultats avant de faire passer un livre entier, et décidez comment vous souhaitez gérer les éléments ambigus. La date « 3/4 » est l'exemple standard : la lisez-vous comme le 4 mars ou le 3 avril ? Mettez votre choix dans le prompt système, car le modèle ne peut pas deviner votre région.
Coût de cette étape
La normalisation est peu coûteuse. Un paragraphe de 600 tokens en entrée et environ 650 tokens en sortie coûte 600 x 0,15 $ / 1 000 000 = 0,00009 $ pour l'entrée, plus 650 x 0,50 $ / 1 000 000 = 0,000325 $ pour la sortie, soit un total de 0,000415 $. Cela représente environ 0,42 $ pour mille paragraphes. La page tarifs contient le tableau complet et les options de rechargement.
Renvoyez des segments structurés en mode JSON
Certains moteurs vocaux acceptent des paramètres par segment : une durée de pause, une vitesse de parole, une voix différente pour un personnage différent. Vous pouvez demander au modèle de produire ces instructions en JSON et de les lire en Python. Définissez response_format sur {"type": "json_object"} et décrivez également la forme dans le prompt, car le mode JSON garantit un JSON valide, pas votre schéma.
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"])
Validez ce qui est renvoyé. Vérifiez que segments existe, que chaque élément a un locuteur connu, et que pause_ms est un nombre dans une plage que vous acceptez. Si la validation échoue, réessayez une fois avec une température plus basse avant d'abandonner. Traitez la sortie du modèle comme une entrée non fiable, tout comme vous le feriez avec n'importe quel texte utilisateur.
Les pièges qui font perdre du temps
Quelques erreurs apparaissent encore et encore lorsque les gens branchent un modèle de langage devant un moteur vocal.
- Laisser le modèle ajouter des commentaires. Les modèles aiment dire « Bien sûr, voici votre script. » Cette phrase serait lue à voix haute. Terminez votre prompt système par « Retournez uniquement le texte du script » et supprimez une ligne de début si elle se termine par deux points.
- Se fier à la longueur des phrases. Certains moteurs ont du mal avec les phrases très longues. Ajoutez une règle telle que « aucune phrase ne doit dépasser 25 mots » au prompt, et appliquez-la également en Python en divisant sur les virgules lorsqu'une phrase est encore trop longue.
- Oublier la fenêtre de contexte. Le prompt et la complétion partagent 100 000 tokens. Pour un long chapitre de livre, envoyez-le par sections avec un bref résumé de la section précédente au lieu du texte complet à chaque fois. Cela maintient également la facture d'entrée basse.
- Réutiliser un client global unique entre les threads sans limite. Le client est sûr à partager, mais le service n'est pas infini. Limitez votre pool à la concurrence de votre clé.
- Coder en dur la clé. Utilisez une variable d'environnement et faites tourner la clé si elle atterrit jamais dans un dépôt.
Lorsque quelque chose semble incorrect, journalisez l'en-tête X-Request-Id avec le prompt qui l'a causé. Si vous avez besoin de demander de l'aide, cet ID est le moyen le plus rapide de pointer vers un appel spécifique, et les docs expliquent où le trouver.
Gérez les limites de débit et les réessais
Une clé complète permet 8 requêtes simultanées et 300 requêtes par minute. Une clé d'essai permet 2 requêtes simultanées et 60 par minute. Au-delà de ces limites, vous obtenez un HTTP 429, avec le code d'erreur rate_limited pour les requêtes par minute ou concurrency pour trop d'appels à la fois. En cas de surcharge temporaire, l'API répond également par 429 et envoie Retry-After: 5.
Le SDK réessaie tout seul, mais un wrapper explicite vous donne le contrôle et rend le comportement visible dans les journaux. Combinez-le avec un pool de threads dimensionné à votre clé, et vous verrez rarement un 429 :
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))
Les requêtes échouées ou refusées ne sont pas facturées, donc réessayer après une erreur ne vous coûte pas pour la tentative échouée. Une requête qui aboutit est facturée normalement, évitez donc les réessais aveugles sur les appels lents mais réussis.
Transmettez le texte à votre moteur vocal
La dernière étape se trouve en dehors de cette API. Python offre plusieurs façons de produire de l'audio à partir de texte : des modèles vocaux open-source que vous pouvez exécuter vous-même, des services vocaux de bureau intégrés à un système d'exploitation, et des API vocales hébergées par d'autres entreprises. Chacune a sa propre licence, sa qualité et son prix, vérifiez donc leur documentation et leurs conditions actuelles avant de choisir. Ce site ne fournit pas cette partie.
Une frontière propre rend votre code facile à modifier. Écrivez une fonction avec la signature speak(text: str) -> None et gardez tous les détails spécifiques au moteur à l'intérieur. Le pipeline textuel ci-dessus appelle cette fonction pour chaque phrase ou segment. Lorsque vous changez de moteur, rien d'autre ne change.
Si vous hésitez encore entre un essai et une configuration payante, les chiffres sont simples : vous payez 0,15 $ par million de tokens d'entrée et 0,50 $ par million de tokens de sortie, à partir d'un crédit prépayé qui n'expire jamais, sans abonnement. Les recharges vont de 20 $ à 500 $ et peuvent être payées par crypto (USDT sur TRC20, USDC sur Base).
Plus de guides
Questions et réponses
Cette API renvoie-t-elle des fichiers audio ?
Non. Elle ne renvoie que du texte. Servez-vous-en pour rédiger, nettoyer et structurer les scripts, puis transmettez le texte à un moteur vocal distinct.
Quel package Python dois-je utiliser ?
Le package officiel openai. Définissez base_url sur https://api.xtts.cc/v1, passez votre clé et utilisez l'ID de modèle uncensored.
Puis-je activer le streaming des réponses en Python ?
Oui. Passez stream=True et itérez sur les chunks. L'utilisation est rapportée à la fin du stream, et la requête conserve son slot de concurrence jusqu'à ce que le stream se termine.
Que se passe-t-il lorsque vous atteignez la limite de débit ?
Vous obtenez un HTTP 429 avec le code rate_limited ou concurrency. Attendez la valeur Retry-After si elle est présente et réessayez, et maintenez le nombre de workers à 8 maximum pour une clé complète ou à 2 maximum pour une clé d'essai.
Votre clé n'est qu'à un formulaire de distance
Créez un compte, copiez la clé, modifiez l'URL de base. C'est toute la configuration.