API TTS Python: prepara il testo per la sintesi vocale con l'SDK
Questa API non restituisce audio. È un modello testuale che chiami da Python con l'SDK OpenAI, posizionato davanti a un motore vocale: scrive il copione, lo pulisce e lo divide in frasi che il tuo stadio vocale può leggere ad alta voce.
Aggiornato

Cosa fa e cosa non fa questa API
Se hai cercato un tutorial tts api python, un punto deve essere chiaro prima di qualsiasi codice: questo servizio non sintetizza audio. Espone POST /v1/chat/completions e GET /v1/models per un modello di testo open-weight, con l'ID del modello uncensored. Non ci sono endpoint audio, nessun elenco di voci e nessun file audio in nessuna risposta. Non ha nemmeno output di immagini, video o embedding.
Quindi dove si inserisce in un progetto vocale? Nella parte testuale della pipeline. Prima che un motore vocale possa leggere un copione, qualcuno deve scriverlo, espandere numeri e abbreviazioni in parole parlate, rimuovere la formattazione e dividerlo in parti brevi da sintetizzare senza pause lunghe. Un modello linguistico fa questo lavoro bene, e puoi chiamarlo da Python con lo stesso SDK che potresti già usare per altri servizi compatibili con OpenAI.
Il modello è ottimizzato per rispondere senza rifiuti di contenuto per uso adulto lecito, il che è importante per la narrativa, i dialoghi tra personaggi e la narrazione dove un modello più restrittivo potrebbe fermarsi a metà scena. Una regola rigida si applica: nessun contenuto sessuale che coinvolga minorenni.
Il resto di questo articolo costruisce un piccolo modulo Python in quattro passaggi: una prima chiamata, uno sentence splitter in streaming, un normalizzatore di testo con output JSON e un wrapper di retry che rispetta i limiti di richieste.
Installa l'SDK e fai una prima chiamata
Hai bisogno di Python 3.8 o superiore e del pacchetto ufficiale openai. Crea una chiave nella pagina chiave API prima. I nuovi account possono richiedere un credito di prova di $0,50 lì con un clic, senza bisogno di una carta.
pip install openai
Tieni la chiave fuori dal codice sorgente. Esportala come variabile d'ambiente e leggila nel codice:
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)
Questa è l'intera integrazione. L'SDK invia Authorization: Bearer <key> per te. Ogni risposta include anche un'intestazione X-Balance-USD, così puoi leggere il tuo credito rimanente dalla risposta grezza se vuoi registrarla. La pagina documentazione elenca le altre intestazioni e i parametri.
Parametri come temperature, top_p, stop, seed e le penalità vengono passati. Per la narrazione, una temperatura tra 0,6 e 0,9 è un intervallo di partenza sensato, e un seed fisso aiuta quando vuoi riprodurre un'esecuzione mentre regoli i prompt.
Streamizza il copione e dividilo in frasi
I motori di sintesi vocale di solito funzionano frase per frase. Se aspetti che finisca un copione di 900 parole, l'ascoltatore aspetta. Con stream: true l'API invia token tramite server-sent events, e puoi passare ogni frase finita allo stadio vocale mentre il resto viene ancora scritto.
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)
Due dettagli sono facili da perdere. Primo, i numeri di utilizzo arrivano alla fine dello stream, in un chunk che può avere una lista choices vuota, motivo per cui il loop salta i chunk senza choices. Secondo, una richiesta streammata occupa il suo slot di concorrenza fino alla fine dello stream. Se il tuo motore vocale è lento e leggi lo stream in modo pigro, lo slot rimane occupato. Estrai il testo rapidamente, inserisci le frasi in una coda e lascia che il lato audio consumi la coda al suo ritmo.
La regex sopra divide su un punto o punto interrogativo seguito da spazio. Dividerebbe erroneamente dopo abbreviazioni come "Dr." o "No.", che è un'altra ragione per normalizzare il testo prima, come mostra la sezione successiva.
Normalizza il testo per l'orecchio
Il testo scritto e il testo parlato differiscono. "Dr. Lee ha comprato 3 copie il 3/4 per la 2a edizione" è facile da leggere e scomodo da dire. Molti motori vocali gestiscono parte di questo, ma i risultati variano a seconda del motore, quindi pulire il copione prima che raggiunga lo stadio vocale rimuove una classe di sorprese. Chiedi al modello di farlo con un'istruzione esplicita:
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."))
Usa una temperatura bassa per questo passaggio. Vuoi che lo stesso input produca lo stesso output, e la creatività è solo un ostacolo. Rivedi un campione di risultati prima di elaborare un intero libro, e decidi come vuoi gestire gli elementi ambigui. La data "3/4" è l'esempio standard: leggila come 4 marzo o 3 aprile? Inserisci la tua scelta nel system prompt, perché il modello non può indovinare la tua localizzazione.
Costo di questo passaggio
La normalizzazione è economica. Un paragrafo di 600 token in ingresso e circa 650 token in uscita costa 600 x $0,15 / 1.000.000 = $0,00009 per l'input, più 650 x $0,50 / 1.000.000 = $0,000325 per l'output, per un totale di $0,000415. Questo è circa $0,42 per mille paragrafi. La pagina prezzi ha la tabella completa e le opzioni di ricarica.
Restituisci segmenti strutturati con la modalità JSON
Alcuni motori vocali accettano impostazioni per segmento: una lunghezza di pausa, un tasso di parlato, una voce diversa per un personaggio diverso. Puoi far produrre al modello quelle istruzioni come JSON e leggerle in Python. Imposta response_format a {"type": "json_object"} e descrivi anche la forma nel prompt, poiché la modalità JSON garantisce JSON valido, non il tuo 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"])
Convalida ciò che viene restituito. Verifica che segments esista, che ogni elemento abbia uno speaker noto e che pause_ms sia un numero in un intervallo che accetti. Se la convalida fallisce, riprova una volta con una temperatura più bassa prima di arrenderti. Tratta l'output del modello come input non attendibile, proprio come faresti con qualsiasi testo utente.
Errori comuni che fanno perdere tempo
Alcuni errori si presentano di nuovo e di nuovo quando le persone collegano un modello linguistico davanti a un motore vocale.
- Lasciare che il modello aggiunga commenti. I modelli amano dire "Certo, ecco il tuo script." Quella frase verrebbe letta ad alta voce. Termina il tuo system prompt con "Restituisci solo il testo dello script" e rimuovi una riga iniziale se termina con i due punti.
- Fidarsi della lunghezza della frase. Alcuni motori faticano con frasi molto lunghe. Aggiungi una regola come "nessuna frase più lunga di 25 parole" al prompt, e applicala anche in Python dividendo per virgole quando una frase è ancora troppo lunga.
- Dimenticare la finestra di contesto. Prompt e completion condividono 100.000 token. Per un capitolo di libro lungo, invialo a sezioni con un breve riepilogo della sezione precedente invece del testo completo ogni volta. Questo mantiene anche il costo dell'input più basso.
- Riutilizzare un client globale unico tra thread senza un limite. Il client è sicuro da condividere, ma il servizio non è infinito. Limita il tuo pool alla concorrenza della tua chiave.
- Hard-code della chiave. Usa una variabile d'ambiente e ruota la chiave se finisce mai in un repository.
Quando qualcosa sembra sbagliato, registra l'intestazione X-Request-Id con il prompt che l'ha causata. Se hai bisogno di chiedere aiuto, quell'ID è il modo più veloce per puntare a una chiamata specifica, e la documentazione spiega dove trovarla.
Gestisci i limiti di richieste e i ritentativi
Una chiave completa permette 8 richieste parallele e 300 richieste al minuto. Una chiave di prova permette 2 parallele e 60 al minuto. Superato uno qualsiasi di questi limiti ottieni HTTP 429, con il codice errore rate_limited per le richieste al minuto o concurrency per troppe contemporaneamente. In caso di sovraccarico temporaneo l'API risponde anche con 429 e invia Retry-After: 5.
L'SDK ritenta da solo, ma un wrapper esplicito ti dà il controllo e rende il comportamento visibile nei log. Combinandolo con un pool di thread dimensionato alla tua chiave, vedrai raramente 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))
Le richieste fallite o rifiutate non vengono addebitate, quindi riprovare dopo un errore non ti costa per il tentativo fallito. Una richiesta che si completa viene fatturata normalmente, quindi evita ritentativi ciechi su chiamate lente ma riuscite.
Passa il testo al tuo motore vocale
L'ultimo passaggio vive al di fuori di questa API. Python ha diversi modi per produrre audio dal testo: modelli di sintesi vocale open-source che puoi eseguire tu stesso, servizi di sintesi vocale desktop integrati in un sistema operativo e API di sintesi vocale ospitate da altre aziende. Ognuna ha la propria licenza, qualità e prezzo, quindi controlla la loro documentazione e i termini attuali prima di scegliere. Questo sito non fornisce quella parte.
Un confine pulito mantiene il tuo codice facile da modificare. Scrivi una funzione con la firma speak(text: str) -> None e mantieni tutti i dettagli specifici del motore al suo interno. La pipeline di testo sopra chiama quella funzione per ogni frase o segmento. Quando cambi motore, nulla cambia.
Se stai ancora decidendo tra una prova e una configurazione a pagamento, i numeri sono semplici: paghi $0,15 per milione di token di input e $0,50 per milione di token di output, da credito prepagato che non scade mai, senza abbonamento. I ricarichi vanno da $20 a $500 e possono essere pagati con cripto (USDT su TRC20, USDC su Base).
Altre guide
Domande e risposte
Questa API restituisce file audio?
No. Restituisce solo testo. Usalo per scrivere, pulire e strutturare script, poi passa il testo a un motore di sintesi vocale separato.
Quale pacchetto Python dovrei usare?
Il pacchetto ufficiale openai. Imposta base_url su https://api.xtts.cc/v1, passa la tua chiave e usa l'ID del modello uncensored.
Posso ricevere le risposte in streaming in Python?
Sì. Imposta stream=True e scorri i chunk. L'utilizzo viene registrato alla fine dello streaming e la richiesta mantiene il suo slot di concorrenza fino al termine dello streaming.
Cosa succede quando superi il limite di richieste?
Ottieni HTTP 429 con il codice rate_limited o concurrency. Attendi il valore Retry-After se presente e riprova, mantenendo il numero di worker pari o inferiore a 8 con una chiave completa o 2 con una chiave di prova.
La tua chiave è a un modulo di distanza
Crea un account, copia la chiave, modifica l'URL di base. È tutta la configurazione.