InícioBlog → Voz para texto em PHP: requisições, uploads e erros

Voz para texto em PHP: requisições, uploads e erros

Publicado em 2026-08-20 · 5 min de leitura

O PHP ainda move boa parte da web, e a tarefa de receber uma gravação e devolver texto aparece o tempo todo. Nenhuma biblioteca especial é necessária: o cURL da instalação padrão basta. Segue o código que funciona e as armadilhas de sempre.

cURL puro

<?php
$ch = curl_init("https://voicesscribe.com/v1/audio/transcriptions");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("VS_KEY")],
    CURLOPT_POSTFIELDS => [
        "model" => "whisper-1",
        "file"  => new CURLFile("call.ogg", "audio/ogg", "call.ogg"),
    ],
]);

$response = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($code !== 200) {
    throw new RuntimeException("erro $code: $response");
}
echo json_decode($response, true)["text"];

A classe CURLFile é obrigatória: se você passar o caminho como string, o PHP moderno envia isso como campo de texto comum e o serviço responde 400.

A versão com Guzzle

$client = new GuzzleHttp\Client(["base_uri" => "https://voicesscribe.com/v1/"]);

$response = $client->post("audio/transcriptions", [
    "headers"   => ["Authorization" => "Bearer " . getenv("VS_KEY")],
    "multipart" => [
        ["name" => "model", "contents" => "whisper-1"],
        ["name" => "file",  "contents" => fopen("call.ogg", "r"), "filename" => "call.ogg"],
        ["name" => "response_format", "contents" => "verbose_json"],
    ],
]);

$data = json_decode((string) $response->getBody(), true);

A chave filename é tão obrigatória aqui quanto em qualquer lugar: sem ela o servidor não determina o formato.

Receber o upload do usuário

O formulário de upload deixa o arquivo num diretório temporário, e é dali que ele segue:

$upload = $_FILES["audio"] ?? null;
if (!$upload || $upload["error"] !== UPLOAD_ERR_OK) {
    http_response_code(400);
    exit("arquivo não enviado");
}
if ($upload["size"] > 25 * 1024 * 1024) {
    http_response_code(413);
    exit("arquivo maior que 25 MB");
}
$file = new CURLFile($upload["tmp_name"], $upload["type"], $upload["name"]);

Não esqueça upload_max_filesize e post_max_size na configuração do PHP: os padrões costumam ser menores que o necessário e o arquivo nem chega ao seu código.

Processamento em lote

Um laço resolve um arquivo, mas é lento para um acervo. Uma fila é melhor: guarde as tarefas numa tabela e deixe vários trabalhadores consumirem, cada um com sua política de retentativa para falhas temporárias.

O tempo limite padrão do cURL é curto demais para gravações longas — coloque CURLOPT_TIMEOUT em pelo menos 120 segundos, senão uma hora de áudio será cortada no meio do processamento.

Teste com as suas próprias gravações. O cadastro leva um minuto e os minutos gratuitos bastam para avaliar a qualidade.

Obter uma chave de API grátis

Perguntas frequentes

Preciso de uma biblioteca específica para PHP?

Não. O cURL da instalação padrão é suficiente; o Guzzle apenas facilita envios concorrentes.

Por que recebo 400 ao enviar o arquivo?

Na maioria das vezes o arquivo foi passado como string em vez de CURLFile, ou a parte multipart não tem filename. O serviço precisa do nome para identificar o formato.

Como aumentar o limite de upload?

Por upload_max_filesize e post_max_size no php.ini. Do lado do serviço o teto é 25 MB por arquivo.

E gravações longas?

Comprima para opus e, se preciso, divida em partes, processando em paralelo e juntando o texto na ordem.

Leitura relacionada