Transcrição ao vivo
Áudio entra em tempo real; um rascunho sai a cada 600 ms e cada frase fechada vem como final, com início e fim. Escopo stt; R$ 0,11 por hora de áudio.
wss://api.int8labs.com/v1/stt/streamAutenticação
No servidor, Authorization: Bearer int8_live_… no handshake. No navegador, ?token=int8_tmp_… na URL (um token de POST /v1/tokens). Credencial errada recebe a mensagem de erro e o fechamento 4401.
Mensagens
"start"A primeira mensagem, em até 10 s: a taxa do áudio (8000, 16000, 24000, 44100 ou 48000), encoding sempre pcm_s16le e termos-chave opcionais.
{"type":"start","sample_rate":16000,"encoding":"pcm_s16le","keyterms":["Dra. Helena Prado"]}"ready"Pode mandar o áudio. O relógio da sessão começa aqui.
{"type":"ready"}áudiobinárioO som, no ritmo da fala. Pedaços de 100 a 250 ms dão a menor latência.
PCM16 LE mono, na taxa do start, até 1 s por mensagem
"partial"Rascunho da frase em andamento, a cada 600 ms. O próximo partial ou o final do mesmo segment substitui este.
{"type":"partial","segment":3,"text":"pode confirmar a consulta de"}"final"A frase fechada (depois de 700 ms de silêncio ou 15 s de fala), com o início e o fim em milissegundos, contados do primeiro áudio da sessão.
{"type":"final","segment":3,"text":"pode confirmar a consulta de terça às catorze e trinta","start_ms":12480,"end_ms":15920}"stop"Acabou o áudio. O servidor fecha as frases que faltam e manda o done.
{"type":"stop"}"done"Fim da sessão: áudio recebido, custo total e saldo. Depois dele a conexão fecha com 1000.
{"type":"done","audio_ms":63210,"cost_brl":0.00193142,"balance_brl":87.39806858}"error"Erro, com o mesmo code do REST. Os que fecham a conexão vêm seguidos do fechamento 4000 + o status HTTP.
{"type":"error","code":"insufficient_balance","message":"Saldo insuficiente. Recarregue em console › Faturamento.","request_id":"req_01JAA4M7Q2W8E5R3T6Y9U1I0PZ"}Regras
- Todo erro fecha a conexão (4000 + o status HTTP), depois de cobrar o áudio que já entrou.
- O áudio não pode passar do relógio da sessão em mais de 10 s: quem manda um arquivo gravado de uma vez recebe
rate_limitede o fechamento 4429. Arquivo gravado vai pelo lote. - 10 s sem áudio encerram a sessão com o
done. A sessão dura até 4 horas. - O saldo e a credencial são conferidos no começo e a cada 60 s: saldo zerado fecha com 4402; chave revogada ou token vencido, com 4401. Antes do
errorchegam osfinaldo áudio já recebido (e cobrado), semdone; o áudio enviado depois disso não é recebido nem cobrado. - Com 4 frases esperando o transcritor, o servidor para de ler até a fila andar (o TCP segura o cliente). Essa espera não conta como silêncio.
Fechamentos
| Fechamento | Quando |
|---|---|
1000 | Fim normal: depois do done da transcrição ao vivo, por ociosidade ou ao passar de 4 h de sessão. |
4400 | invalid_request na transcrição ao vivo ou no modo áudio do avatar. Na voz e no modo texto do avatar, o erro de uma fala chega sem fechar a conexão. |
4401 | invalid_api_key, api_key_expired ou api_key_revoked, no começo ou no meio da sessão. Na transcrição ao vivo, os final do áudio já recebido chegam antes do erro. |
4402 | insufficient_balance: o saldo acabou. O que já foi falado ou ouvido é cobrado; na transcrição ao vivo, os final desse áudio chegam antes do erro. |
4403 | ip_not_allowed ou scope_denied. |
4413 | payload_too_large no modo áudio do avatar (fala acima de 5 min). |
4429 | rate_limited (áudio mais rápido que o tempo real) ou concurrency_limited (WebSockets demais na organização). |
4500 | internal_error. |
4503 | server_busy ou model_unavailable. |
Exemplo
import asyncioimport jsonimport osimport waveimport websockets # pip install "websockets>=14"PEDACO_MS = 100async def transcrever(caminho: str) -> None:with wave.open(caminho, "rb") as wav:assert wav.getsampwidth() == 2 and wav.getnchannels() == 1, "precisa de PCM16 mono"taxa = wav.getframerate() # 8000, 16000, 24000, 44100 ou 48000amostras = wav.readframes(wav.getnframes())async with websockets.connect("wss://api.int8labs.com/v1/stt/stream",additional_headers={"Authorization": f"Bearer {os.environ['INT8_API_KEY']}"},) as ws:await ws.send(json.dumps({"type": "start", "sample_rate": taxa, "encoding": "pcm_s16le"}))assert json.loads(await ws.recv())["type"] == "ready"async def enviar() -> None:passo = taxa * PEDACO_MS // 1000 * 2 # bytes de 100 msfor inicio in range(0, len(amostras), passo):await ws.send(amostras[inicio : inicio + passo])await asyncio.sleep(PEDACO_MS / 1000) # no ritmo da fala; mais rápido fecha com 4429await ws.send(json.dumps({"type": "stop"}))envio = asyncio.create_task(enviar())async for mensagem in ws:evento = json.loads(mensagem)if evento["type"] == "final":print(f'[{evento["start_ms"]} ms] {evento["text"]}')elif evento["type"] == "done":print("custo R$", evento["cost_brl"])elif evento["type"] == "error":print("erro:", evento["code"], evento["message"])await envioasyncio.run(transcrever("reuniao.wav"))