Como criar legendas SRT de um vídeo com uma API
Envie o vídeo para POST /transcribe com output_format definido como srt e word_timestamps como false, espere o processamento terminar e salve o texto que voltar em um arquivo .srt. Se quiser a legenda traduzida para outro idioma, basta incluir o parâmetro translate_to. Aqui você vê como montar a requisição exata, o que vem dentro do SRT, como usar os tempos das palavras para quebrar as linhas direitinho, como converter para WebVTT e como colocar a legenda no vídeo.
A requisição
Duas chamadas: iniciar o trabalho e consultá-lo. O result de um trabalho srt concluído é uma string, não JSON. Esta versão só precisa de curl e jq:
# 1. Start the job. "word_timestamps": false gives one cue per segment.
JOB=$(curl -s https://app.kolwrite.com/transcribe \
-H "x-api-key: $KOLWRITE_API_KEY" -H "Content-Type: application/json" \
-d '{"audio_url": "https://example.com/talk.mp3", "model": "oracle", "output_format": "srt", "word_timestamps": false}' \
| jq -r .jobId)
# 2. Poll until it is done and save the result string as a .srt file.
while true; do
REPLY=$(curl -s "https://app.kolwrite.com/transcribe/$JOB" -H "x-api-key: $KOLWRITE_API_KEY")
STATUS=$(echo "$REPLY" | jq -r .status)
if [ "$STATUS" = "COMPLETED" ]; then echo "$REPLY" | jq -r .result > talk.srt; break; fi
if [ "$STATUS" = "FAILED" ]; then echo "$REPLY" | jq -r .error; break; fi
sleep 5
done
Você pode mandar um vídeo, já que a API lê a faixa de áudio. Arquivos locais passam antes por POST /upload (até 100 MiB). Se o vídeo for longo, extraia o áudio com o ffmpeg. A função auxiliar do guia de edição de vídeo faz as duas coisas em Python.
O que vem no SRT
Com "word_timestamps": false, a saída traz uma linha por trecho, igual à maioria dos arquivos de legenda:
1
00:00:01,000 --> 00:00:03,750
Hello everyone and welcome to the show.
2
00:00:05,000 --> 00:00:07,750
Today we talk about editing video from a transcript.
Por padrão, os tempos por palavra vêm ativados e a API devolve uma linha para cada palavra. Cada linha repete a frase inteira do trecho e coloca a palavra da vez numa tag <font> amarela. Isso serve para destacar a leitura em tempo real, não como legenda comum:
1
00:00:01,000 --> 00:00:01,350
<font color="yellow">Hello</font> everyone and welcome to the show.
2
00:00:01,400 --> 00:00:01,750
Hello <font color="yellow">everyone</font> and welcome to the show.
Se você quer legendas tradicionais, passe "word_timestamps": false ou monte as linhas por conta própria a partir das palavras do JSON, como mostramos abaixo.
Como montar linhas limpas a partir do tempo das palavras
Peça output_format: "json" e agrupe você mesmo as palavras: limite cada linha a 42 caracteres e 6 segundos, e quebre a linha depois de uma pausa. Assim você controla o comprimento exato, o que faz diferença quando a legenda é gravada direto na imagem do vídeo.
srt_tools.py
# srt_tools.py — neat subtitle cues from word timings
def srt_time(seconds):
ms = round(seconds * 1000)
h, ms = divmod(ms, 3_600_000)
m, ms = divmod(ms, 60_000)
s, ms = divmod(ms, 1000)
return f"{h:02}:{m:02}:{s:02},{ms:03}"
def cues_from_words(words, max_chars=42, max_seconds=6.0, max_gap=0.8):
"""Group timed words into subtitle cues: (start, end, text)."""
cues, line = [], []
for word in words:
text = word["word"].strip()
if not text:
continue
if line:
length = sum(len(w["word"]) + 1 for w in line) + len(text)
if (length > max_chars
or word["end"] - line[0]["start"] > max_seconds
or word["start"] - line[-1]["end"] > max_gap):
cues.append(line)
line = []
line.append({"start": word["start"], "end": word["end"], "word": text})
if line:
cues.append(line)
return [(c[0]["start"], c[-1]["end"], " ".join(w["word"] for w in c)) for c in cues]
def cues_from_result(result):
"""Cues for a whole JSON result; a segment without word times becomes one cue."""
cues = []
for segment in result["segments"]:
if segment.get("words"):
cues += cues_from_words(segment["words"])
elif segment["text"].strip():
cues.append((segment["start"], segment["end"], segment["text"].strip()))
return cues
def write_srt(cues, path):
with open(path, "w", encoding="utf-8") as out:
for number, (start, end, text) in enumerate(cues, 1):
out.write(f"{number}\n{srt_time(start)} --> {srt_time(end)}\n{text}\n\n")
cues_from_result(result) devolve tuplas (start, end, text) para o resultado todo e usa os tempos do trecho quando o modelo não traz tempos por palavra. Depois, write_srt(cues, "talk.srt") salva o arquivo.
Legendas traduzidas
Passe translate_to com o código do idioma e escolha um modelo Oracle (o oracle_mini é o mais barato que faz tradução). O texto já sai traduzido; mantenha word_timestamps como false para receber um trecho por linha.
{
"audio_url": "https://example.com/lecture-he.mp4",
"model": "oracle_mini",
"language": "he",
"translate_to": "en",
"output_format": "srt",
"word_timestamps": false
}
Para legendar o mesmo vídeo em vários idiomas, rode um trabalho para cada idioma de destino. Como toda tradução automática é um rascunho, revise nomes, números e termos específicos antes de publicar.
WebVTT, ASS e outros formatos
A API gera apenas json, txt ou srt. Se precisar de WebVTT para usar na tag <video> do HTML5, converta o SRT com o ffmpeg:
ffmpeg -i video.srt video.vtt
O ffmpeg faz a mesma conversão para a maioria dos formatos de legenda; basta mudar a extensão do arquivo de saída.
Grave a legenda no vídeo ou anexe como faixa
Gravada direto na imagem:
ffmpeg -i edited.mp4 -vf "subtitles=edited.srt" -c:a copy burned.mp4
Como faixa separada dentro do MP4 (quem assiste pode ligar ou desligar no player):
ffmpeg -i edited.mp4 -i edited.srt -c copy -c:s mov_text -metadata:s:s:0 language=eng soft.mp4
O YouTube Studio e a maioria dos players e editores de vídeo aceitam o arquivo .srt direto.
Legendas em hebraico e árabe
- A API entrega texto Unicode comum. Quem cuida da leitura da direita para a esquerda é o player ou o renderizador de legendas do ffmpeg, que lida bem com hebraico e árabe desde que a fonte escolhida tenha os caracteres.
- Indique uma fonte compatível com o alfabeto e confira um quadro renderizado antes de converter o vídeo inteiro. Nos nossos testes, a fonte Arial funcionou bem para hebraico, mas falhou em um glifo numa palavra em árabe; por isso, teste outra fonte quando for usar árabe.
ffmpeg -i video.mp4 -vf "subtitles=video.he.srt:force_style='FontName=Arial,FontSize=24'" -c:a copy burned.mp4
- Se a linha terminar com pontuação ou caracteres latinos (como uma marca ou um número), a ordem pode sair invertida em players que não tratam bem texto bidirecional. Faça um teste nos players que o seu público costuma usar.
Quanto custa
| Trabalho | Modelo | Custo |
|---|---|---|
| Aula de 45 minutos, com legendas | oracle | US$ 1,08 |
| A mesma aula, com legendas traduzidas | oracle_mini | US$ 0,72 |
| Uma hora de áudio limpo, com legendas | base | US$ 0,48 |
Cobramos por segundo, sem valor mínimo. Veja a tabela em /llms/pricing.md.
Perguntas e respostas
Como gero um arquivo SRT a partir de um vídeo?
Chame POST /transcribe passando a URL do vídeo, output_format: "srt" e word_timestamps: false. Acompanhe o andamento do trabalho e, quando terminar, salve a string result como um arquivo .srt.
A API devolve WebVTT?
Não, apenas json, txt ou srt. Para ter WebVTT, converta o SRT rodando ffmpeg -i video.srt video.vtt.
Por que meu SRT veio com uma linha para cada palavra?
Por padrão, a marcação de tempo por palavra vem ativa, então o SRT traz uma linha para cada palavra e destaca a que está sendo falada. Para receber uma linha por trecho, envie word_timestamps: false ou monte você mesmo as linhas usando o JSON.
Consigo gerar legendas já traduzidas?
Sim. Basta incluir translate_to com o código do idioma desejado e usar um modelo Oracle. As linhas já voltam traduzidas.
Qual a diferença entre legendas comuns e legendas para surdos e ensurdecidos?
A legenda comum só traz o que é falado (transcrito ou traduzido) para quem consegue ouvir o áudio. Já a legenda para surdos e ensurdecidos inclui também a descrição dos sons do ambiente. A API entrega o texto da fala com os tempos; se precisar descrever os sons, adicione essas marcações manualmente.