> ## Documentation Index
> Fetch the complete documentation index at: https://docs.venice.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Narração de Artigos com Texto para Fala

> Transforme qualquer artigo da web em um arquivo de áudio narrado com o text-to-speech da Venice, abordando seleção de voz, o limite de 4096 caracteres, união de áudio sem falhas e streaming.

Fazer uma chamada ao `/audio/speech` é fácil. Narrar um artigo real é onde os problemas interessantes aparecem: o endpoint aceita no máximo 4096 caracteres por requisição, cada voz pertence a um modelo específico, os formatos de áudio diferem de modelo para modelo, e texto escrito para ser lido não se parece em nada com texto escrito para ser ouvido.

Neste tutorial trabalhamos os quatro pontos. O resultado é um script que transforma uma URL em um único arquivo de áudio:

```bash theme={"system"}
python article_to_audio.py https://docs.venice.ai/overview/privacy
```

Iremos:

1. Escolher um modelo e uma voz adequados para narração de textos longos
2. Fazer uma única requisição de fala e salvar o áudio
3. Dividir um artigo longo em pedaços que caibam no limite de caracteres
4. Unir os pedaços sintetizados em um único arquivo sem emendas audíveis
5. Reescrever o artigo em algo que valha a pena ouvir
6. Combinar as peças, e então dar uma olhada em streaming para uso interativo

## Configuração

Você precisa do Python 3.9 ou mais recente, do pacote `requests` e de uma chave de API Venice.

```bash theme={"system"}
pip install requests
export VENICE_API_KEY="your-api-key-here"
```

## 1. Escolha um modelo e uma voz

Vozes pertencem a modelos. Enviar uma voz de uma família para um modelo de outra é o erro inicial mais comum, então comece listando o que cada modelo realmente aceita:

```bash theme={"system"}
curl "https://api.venice.ai/api/v1/models?type=tts" \
  -H "Authorization: Bearer $VENICE_API_KEY" |
  jq -r '.data[] | "\(.id)  formats=\(.model_spec.supported_formats)  voices=\(.model_spec.voices | length)"'
```

```
tts-kokoro  formats=["mp3","opus","aac","flac","wav","pcm"]  voices=54
tts-qwen3-1-7b  formats=["mp3"]  voices=9
tts-xai-v1  formats=["mp3","wav","pcm"]  voices=26
tts-inworld-1-5-max  formats=["wav"]  voices=14
tts-chatterbox-hd  formats=["wav"]  voices=9
tts-orpheus  formats=["wav"]  voices=8
tts-elevenlabs-turbo-v2-5  formats=["mp3"]  voices=21
tts-minimax-speech-02-hd  formats=["mp3","pcm","flac"]  voices=15
tts-gemini-3-1-flash  formats=["mp3","opus","wav"]  voices=30
tts-gradium-v1  formats=["wav","pcm","opus"]  voices=12
```

`model_spec.voices` é a lista autoritativa de vozes de um modelo, e `supported_formats` diz quais valores de `response_format` ele aceita. Remova o `| length` da consulta para imprimir os nomes das vozes.

Vamos usar `tts-xai-v1` com a voz `eve`. Ele suporta `pcm`, o que torna a união dos pedaços simples na seção 4.

<Note>
  A velocidade de síntese varia muito mais entre modelos TTS do que a qualidade da saída, e a diferença é grande o suficiente para mudar sua arquitetura. Meça uma requisição realista contra dois ou três candidatos antes de se comprometer. Um pedaço que um modelo retorna em poucos segundos pode levar vários minutos em outro.
</Note>

## 2. Faça uma única requisição

O corpo da resposta é áudio bruto, e não JSON, então escreva os bytes diretamente em um arquivo.

<CodeGroup>
  ```python Python theme={"system"}
  import os
  from pathlib import Path

  import requests

  response = requests.post(
      "https://api.venice.ai/api/v1/audio/speech",
      headers={
          "Authorization": f"Bearer {os.environ['VENICE_API_KEY']}",
          "Content-Type": "application/json",
      },
      json={
          "model": "tts-xai-v1",
          "voice": "eve",
          "input": "Hello from Venice.",
          "response_format": "mp3",
      },
      timeout=300,
  )

  response.raise_for_status()
  Path("hello.mp3").write_bytes(response.content)
  ```

  ```javascript Node.js theme={"system"}
  import { writeFile } from "node:fs/promises";

  const response = await fetch("https://api.venice.ai/api/v1/audio/speech", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.VENICE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "tts-xai-v1",
      voice: "eve",
      input: "Hello from Venice.",
      response_format: "mp3",
    }),
  });

  if (!response.ok) {
    throw new Error(`${response.status}: ${await response.text()}`);
  }

  await writeFile("hello.mp3", Buffer.from(await response.arrayBuffer()));
  ```

  ```bash cURL theme={"system"}
  curl https://api.venice.ai/api/v1/audio/speech \
    -H "Authorization: Bearer $VENICE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "tts-xai-v1",
      "voice": "eve",
      "input": "Hello from Venice.",
      "response_format": "mp3"
    }' \
    --output hello.mp3
  ```
</CodeGroup>

Combinações incompatíveis são rejeitadas antes que qualquer áudio seja gerado, e o erro indica o que teria funcionado:

```json theme={"system"}
{"error":"Voice \"eve\" is not supported by model \"tts-kokoro\". Try using a supported voice: af_alloy, af_aoede, af_bella, af_heart, af_jadzia, ...."}
```

```json theme={"system"}
{"error":"response_format \"wav\" is not supported by model \"tts-elevenlabs-turbo-v2-5\". Supported formats: mp3."}
```

## 3. Divida o texto no limite de 4096 caracteres

O campo `input` aceita no máximo 4096 caracteres. Textos mais longos são rejeitados imediatamente, em vez de silenciosamente truncados:

```json theme={"system"}
{"error":"Invalid request parameters","issues":[{"code":"too_big","maximum":4096,"path":["input"]}]}
```

Portanto, dividimos o artigo primeiro. Dividir em fronteiras de frase importa, porque um pedaço que termina no meio de uma frase produz um tropeço audível na junção. Crie `narrate.py`:

```python theme={"system"}
from __future__ import annotations

import os
import re
import sys
import wave
from concurrent.futures import ThreadPoolExecutor

import requests

BASE_URL = "https://api.venice.ai/api/v1"
HEADERS = {
    "Authorization": f"Bearer {os.environ['VENICE_API_KEY']}",
    "Content-Type": "application/json",
}

MODEL = "tts-xai-v1"
VOICE = "eve"
SAMPLE_RATE = 24000  # tts-xai-v1 returns 24 kHz mono signed 16-bit PCM.

SENTENCE_END = re.compile(r"(?<=[.!?])\s+")


def split_into_chunks(text: str, max_chars: int = 1500) -> list[str]:
    """Split text on sentence boundaries into chunks under the 4096-character cap."""
    chunks: list[str] = []
    current = ""

    for sentence in SENTENCE_END.split(text.strip()):
        if not sentence:
            continue
        if len(sentence) > max_chars:
            raise ValueError(f"Sentence longer than {max_chars} characters: {sentence[:80]}...")
        if len(current) + len(sentence) + 1 > max_chars:
            chunks.append(current)
            current = sentence
        else:
            current = f"{current} {sentence}" if current else sentence

    if current:
        chunks.append(current)
    return chunks
```

Em um roteiro de 5362 caracteres, isso produz quatro pedaços, cada um terminando em uma frase:

```python theme={"system"}
chunks = split_into_chunks(script)
print(f"len(chunks) = {len(chunks)}")
for index, chunk in enumerate(chunks):
    print(f"chunk {index}: {len(chunk)} chars")
```

```
len(chunks) = 4
chunk 0: 1428 chars
chunk 1: 1410 chars
chunk 2: 1489 chars
chunk 3: 1015 chars
```

O `max_chars` padrão é 1500, e não algo próximo do teto de 4096, e isso é intencional. O tempo de síntese cresce com o comprimento da entrada, então pedaços menores retornam mais cedo e, como executam em paralelo, terminam o trabalho todo mais rápido. Eles também tornam as tentativas de repetição baratas quando uma requisição falha.

## 4. Una os pedaços em um único arquivo

Concatenar áudio codificado como MP3 é pouco confiável, porque cada pedaço carrega seus próprios cabeçalhos de frame. Solicitar `pcm` evita o problema por completo. PCM é composto por amostras brutas sem contêiner, então unir é apenas concatenar bytes, e o módulo `wave` da biblioteca padrão do Python escreve o cabeçalho para nós.

```python theme={"system"}
def synthesize(text: str, speed: float = 1.0) -> bytes:
    """Return raw PCM audio for one chunk."""
    response = requests.post(
        f"{BASE_URL}/audio/speech",
        headers=HEADERS,
        json={
            "model": MODEL,
            "voice": VOICE,
            "input": text,
            "response_format": "pcm",
            "speed": speed,
        },
        timeout=300,
    )
    if response.status_code != 200:
        raise RuntimeError(f"TTS failed ({response.status_code}): {response.text}")
    return response.content


def narrate(text: str, out_path: str) -> str:
    chunks = split_into_chunks(text)
    print(f"Synthesizing {len(chunks)} chunks", file=sys.stderr)

    with ThreadPoolExecutor(max_workers=4) as pool:
        audio = list(pool.map(synthesize, chunks))

    with wave.open(out_path, "wb") as output:
        output.setnchannels(1)
        output.setsampwidth(2)
        output.setframerate(SAMPLE_RATE)
        for part in audio:
            output.writeframes(part)

    seconds = sum(len(part) for part in audio) / 2 / SAMPLE_RATE
    print(f"Wrote {out_path} ({seconds:.1f}s of audio)", file=sys.stderr)
    return out_path
```

Um único pedaço retorna rapidamente em relação à quantidade de áudio que contém:

```python theme={"system"}
pcm = synthesize(chunks[0])
print(f"bytes   = {len(pcm)}")
print(f"audio   = {len(pcm) / 2 / SAMPLE_RATE:.1f}s")
```

```
bytes   = 4269376
audio   = 88.9s
```

Essa requisição levou cerca de 13 segundos para produzir 89 segundos de fala. Executar os quatro pedaços simultaneamente é o que mantém o total razoável: a narração completa abaixo levou 15 segundos de tempo real.

`ThreadPoolExecutor.map` retorna os resultados na ordem em que as entradas foram submetidas, então os pedaços chegam em ordem de leitura mesmo tendo sido sintetizados ao mesmo tempo.

<Warning>
  PCM bruto não carrega taxa de amostragem, então você precisa informar a correta ao escrever o cabeçalho WAV, e ela é específica do modelo. `tts-xai-v1` retorna 24 kHz, enquanto `tts-gradium-v1` retorna 48 kHz. Errar isso faz a narração tocar na velocidade e no tom errados.
</Warning>

Para descobrir a taxa de qualquer modelo, peça um clipe curto como `wav` e leia o cabeçalho que ele devolve:

```python theme={"system"}
import wave

response = requests.post(
    f"{BASE_URL}/audio/speech",
    headers=HEADERS,
    json={"model": MODEL, "voice": VOICE, "input": "Probe.", "response_format": "wav"},
    timeout=300,
)
response.raise_for_status()
with open("probe.wav", "wb") as handle:
    handle.write(response.content)

with wave.open("probe.wav") as probe:
    print(probe.getframerate(), probe.getnchannels(), probe.getsampwidth())
```

```
24000 1 2
```

## 5. Prepare o texto para soar bem

Markdown extraído lido em voz alta literalmente é quase impossível de escutar. URLs são o exemplo mais claro. Um modelo de fala soletra caractere por caractere, então `https://docs.venice.ai/llms.txt` sai como:

> h t t p s dois-pontos barra barra docs ponto venice ponto a i l l m s ponto t x t

Cabeçalhos, marcadores de lista, tabelas e blocos de código causam versões menores do mesmo problema. Em vez de brigar com Markdown usando expressões regulares, podemos pedir a um modelo de chat que reescreva o artigo como algo destinado a ser falado. Crie `article_to_audio.py`:

```python theme={"system"}
from __future__ import annotations

import os
import re
import sys

import requests

from narrate import narrate

BASE_URL = "https://api.venice.ai/api/v1"
HEADERS = {
    "Authorization": f"Bearer {os.environ['VENICE_API_KEY']}",
    "Content-Type": "application/json",
}

URL_PATTERN = re.compile(r"https?://\S+|www\.\S+")


def scrape(url: str) -> str:
    response = requests.post(
        f"{BASE_URL}/augment/scrape", headers=HEADERS, json={"url": url}, timeout=120
    )
    response.raise_for_status()
    return response.json()["content"]


def write_script(markdown: str, minutes: int = 6) -> str:
    response = requests.post(
        f"{BASE_URL}/chat/completions",
        headers=HEADERS,
        json={
            "model": "zai-org-glm-5-1",
            "messages": [
                {
                    "role": "system",
                    "content": (
                        "You rewrite articles as scripts to be read aloud. Output plain prose only: "
                        "no Markdown, no headings, no bullet points, no URLs, no code, no emoji. "
                        "Spell out abbreviations and numbers the way a narrator would say them. "
                        "Use short sentences with clear punctuation so speech synthesis paces well."
                    ),
                },
                {
                    "role": "user",
                    "content": f"Rewrite this article as a {minutes}-minute spoken summary.\n\n{markdown[:20000]}",
                },
            ],
            "temperature": 0.4,
        },
        timeout=300,
    )
    response.raise_for_status()
    script = response.json()["choices"][0]["message"]["content"]
    return URL_PATTERN.sub("", script).strip()
```

A substituição `URL_PATTERN` fica como rede de segurança para o link ocasional que o modelo deixe passar.

## 6. Juntando tudo

O ponto de entrada faz o scrape, escreve o roteiro, o salva e narra:

```python theme={"system"}
if __name__ == "__main__":
    url = sys.argv[1]

    print("Scraping", url, file=sys.stderr)
    markdown = scrape(url)

    print(f"Writing script from {len(markdown)} characters of Markdown", file=sys.stderr)
    script = write_script(markdown)

    with open("script.txt", "w") as handle:
        handle.write(script)

    narrate(script, "article.wav")
```

Salvar `script.txt` ao lado do áudio vale as duas linhas. Quando uma narração soa errada, o roteiro quase sempre mostra por quê, e você pode corrigir sem pagar para sintetizar de novo.

```bash theme={"system"}
python article_to_audio.py https://docs.venice.ai/overview/privacy
```

```
Scraping https://docs.venice.ai/overview/privacy
Writing script from 7582 characters of Markdown
Synthesizing 4 chunks
Wrote article.wav (355.0s of audio)
```

Pouco menos de seis minutos de narração, produzidos em cerca de quinze segundos. O roteiro agora começa com prosa em vez de elementos de navegação:

> Venice is built on a simple but powerful principle. User privacy comes first. The platform's entire architecture flows from this philosophical commitment.

<Tip>
  Para verificar uma narração sem precisar ouvi-la inteira, envie o áudio de volta por [`/audio/transcriptions`](/guides/media/speech-to-text) e compare a transcrição com `script.txt`. Transcrever os últimos vinte segundos é uma maneira rápida de confirmar que os pedaços foram unidos na ordem correta, e pega pedaços perdidos e URLs soletradas em segundos.
</Tip>

## Streaming para uso interativo

A narração em lote otimiza o tempo total. Uma interface de voz tem a prioridade oposta, que é entregar o primeiro áudio o mais rápido possível. Definir `streaming: true` retorna o corpo frase a frase à medida que é gerado, então a reprodução pode começar em cerca de um segundo em vez de esperar pelo clipe completo.

```python theme={"system"}
import time

import requests

start = time.time()
first_byte = None

with requests.post(
    "https://api.venice.ai/api/v1/audio/speech",
    headers=HEADERS,
    json={
        "model": "tts-xai-v1",
        "voice": "eve",
        "input": "Streaming returns audio while the rest is still being generated.",
        "response_format": "mp3",
        "streaming": True,
    },
    stream=True,
    timeout=300,
) as response:
    response.raise_for_status()
    with open("streamed.mp3", "wb") as audio:
        for chunk in response.iter_content(chunk_size=4096):
            if first_byte is None:
                first_byte = time.time() - start
            audio.write(chunk)

print(f"first byte: {first_byte:.2f}s   complete: {time.time() - start:.2f}s")
```

```
first byte: 0.85s   complete: 1.43s
```

Prefira `pcm` a `mp3` quando estiver alimentando a Web Audio API de um navegador ou um dispositivo de áudio diretamente, pois não precisa de etapa de decodificação.

## Opções de requisição que vale a pena conhecer

| Parâmetro     | Notas                                                                                                                                                                                                                 |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `speed`       | Aceita `0.25` a `4.0`, padrão `1.0`. Fique aproximadamente entre `0.8` e `1.3` para narração. Além disso, a entrega deixa de soar natural.                                                                            |
| `language`    | Dica opcional cuja forma aceita é específica do modelo. xAI e ElevenLabs recebem códigos ISO 639-1 como `en`, enquanto Qwen 3 e MiniMax recebem nomes completos como `English`. Valores não suportados são ignorados. |
| `prompt`      | Dica de estilo e emoção, com até 500 caracteres, atualmente respeitada apenas pelos modelos Qwen 3. Para outras famílias, a escolha da voz carrega o tom.                                                             |
| `temperature` | Faixa `0` a `2`, suportada por Qwen 3, Orpheus e Chatterbox HD. Aumente para obter mais variação entre takes.                                                                                                         |

## Erros

| Status         | Causa                                | Correção                                          |
| -------------- | ------------------------------------ | ------------------------------------------------- |
| `400`          | `input` com mais de 4096 caracteres  | Divida o texto em pedaços como na seção 3         |
| `400`          | Voz inválida para o modelo           | Use uma voz do `model_spec.voices` daquele modelo |
| `400`          | Formato não suportado pelo modelo    | Verifique `model_spec.supported_formats`          |
| `401`          | Chave ausente ou inválida            | Confirme o cabeçalho `Authorization: Bearer`      |
| `402`          | Saldo insuficiente                   | Recarregue a conta                                |
| `429`          | Limite de taxa atingido              | Reduza `max_workers` e refaça com backoff         |
| `500` ou `503` | Falha de capacidade ou de inferência | Refaça o pedaço afetado com jitter                |

Como os pedaços são independentes, uma falha custa no máximo um deles, e refazer `synthesize` para esse pedaço é sempre seguro.

## Próximos passos

Algumas extensões naturais a partir daqui:

* Faça cache do áudio por um hash do texto, voz e modelo, para que parágrafos inalterados nunca sejam sintetizados novamente.
* Substitua por uma voz clonada com [Clonagem de Voz](/guides/media/voice-cloning), para que a narração use a sua própria.
* Gere o texto de origem em vez de fazer scrape, usando [Respostas com Citações usando Web Search](/guides/tools/cited-web-answers).
* Adicione áudio de introdução ou de fundo com [Música e Efeitos Sonoros](/guides/media/music-and-sound-effects).

<CardGroup cols={2}>
  <Card title="Texto para Fala" icon="volume-2" href="/guides/media/text-to-speech">
    Referência para o endpoint de fala e seus parâmetros.
  </Card>

  <Card title="Clonagem de Voz" icon="user" href="/guides/media/voice-cloning">
    Narre com uma voz personalizada em vez de uma predefinida.
  </Card>

  <Card title="Respostas com Citações usando Web Search" icon="search" href="/guides/tools/cited-web-answers">
    Gere o texto que esta ferramenta narra.
  </Card>

  <Card title="Fala para Texto" icon="microphone" href="/guides/media/speech-to-text">
    Transcreva áudio para verificar uma narração de ponta a ponta.
  </Card>
</CardGroup>
