Como editar vídeo com o Claude, o Codex ou qualquer agente de IA
Passe para o agente uma transcrição com o tempo exato de início e fim de cada palavra. Ele escolhe quais frases ficam, e um script curto usa esses tempos para cortar o vídeo no ffmpeg. A API do KolWrite gera essa transcrição para áudio e vídeo em 100 idiomas. As mesmas palavras ainda servem para montar as legendas do vídeo pronto.
O que o agente faz e o que não faz
Como a transcrição traz só a fala, o agente edita com base no que foi dito. Ele tira erros de início, vícios de linguagem, tomadas repetidas, desvios de assunto e pausas longas, ou separa as frases certas para montar um clipe curto. Mas ele não vê a imagem. A parte visual, como escolher qual plano entra, continua na sua mão, a menos que você passe alguns quadros do vídeo para ele analisar.
Agentes que rodam comandos no terminal (como Claude Code, Codex CLI ou um agente do Cursor) chamam a API e executam o ffmpeg por conta própria. Um chat comum, que não roda código, não faz isso sozinho, mas pode escrever os scripts para você rodar.
Prompt para colar no seu agente
Com este prompt, o agente trabalha com os números dos trechos em vez de digitar os tempos direto, o que evita que ele invente marcações que não existem. O texto também aponta para a documentação da API feita para leitura por máquina.
Edit talk.mp4 down to the parts worth keeping, using the KolWrite API for the transcript.
Read https://console.kolwrite.com/agent-instructions.md first. The key is in KOLWRITE_API_KEY.
1. Extract the audio with ffmpeg and transcribe it with model "oracle", output_format "json", word_timestamps true.
2. Print the segments as numbered lines: S<number> [start-end] text.
3. Choose the segments to keep (remove false starts, filler, repeated takes and tangents) and write
their numbers to keep.json as {"keep": [0, 2, 4]}. Do not invent timestamps; use segment numbers only.
4. Cut the video from those segments' word times with ffmpeg and write edited.mp4.
5. Make edited.srt for the edited video and attach it as a subtitle track.
Show me the list of removed segments before you cut anything.
Se quiser deixar a API configurada para todas as sessões, coloque a regra do guia da ferramenta no seu arquivo AGENTS.md ou CLAUDE.md.
Passo 1: extraia o áudio e faça a transcrição
O envio assinado tem limite de 100 MiB por arquivo. Se o vídeo for longo, separe o áudio antes de subir. Uma hora de áudio mono a 48 kbit/s dá cerca de 21 MB.
ffmpeg -i talk.mp4 -vn -ac 1 -ar 16000 -b:a 48k talk.mp3
Esta função simples sobe um arquivo local, dá a partida no trabalho e espera o resultado ficar pronto:
kolwrite.py
# kolwrite.py — three small functions around the KolWrite REST API
import os, time, requests
API = "https://app.kolwrite.com"
HEADERS = {"x-api-key": os.environ["KOLWRITE_API_KEY"]}
def upload(path):
"""Upload a local file (up to 100 MiB) and return a URL the API can read."""
name = os.path.basename(path)
form = requests.post(f"{API}/upload", headers=HEADERS, json={"filename": name}, timeout=30)
form.raise_for_status()
form = form.json()
with open(path, "rb") as media:
# No x-api-key on this request: it goes to the storage address.
sent = requests.post(form["uploadUrl"], data=form["uploadFields"],
files={"file": (name, media)}, timeout=600)
sent.raise_for_status()
return form["fileUrl"]
def transcribe(audio_url, timeout_s=3600, **options):
"""Start a job, wait for it, return the result: a dict for json, a string for srt and txt."""
body = {"audio_url": audio_url, "model": "oracle", **options}
job = requests.post(f"{API}/transcribe", headers=HEADERS, json=body, timeout=30)
job.raise_for_status()
job_id = job.json()["jobId"]
deadline = time.time() + timeout_s
while time.time() < deadline:
time.sleep(5)
reply = requests.get(f"{API}/transcribe/{job_id}", headers=HEADERS, timeout=30)
reply.raise_for_status()
reply = reply.json()
if reply["status"] == "COMPLETED":
return reply["result"]
if reply["status"] == "FAILED":
raise RuntimeError(reply.get("error", "transcription failed"))
raise TimeoutError(f"job {job_id} did not finish in {timeout_s} seconds")
O script edit.py abaixo pede a saída em JSON com os tempos de cada palavra (esses dois já são o padrão, mas deixamos explícito no código para ficar claro). A resposta vem em segments[], onde cada item traz start, end, text, um speaker opcional e words[]:
Resumo do que volta:
{
"jobId": "34c63d45-4cb5-473b-bc60-25c175e69c57",
"status": "COMPLETED",
"result": {
"language": "en",
"segments": [
{
"start": 1.0,
"end": 3.75,
"text": "Hello everyone and welcome to the show.",
"speaker": "Speaker 1",
"words": [
{ "start": 1.0, "end": 1.35, "word": "Hello" },
{ "start": 1.4, "end": 1.75, "word": "everyone" }
]
}
]
}
}
Passo 2: deixe o agente escolher o que fica
Imprima a transcrição como uma lista de linhas numeradas. É isso que o agente vai ler, o que gasta bem menos tokens do que passar o JSON bruto cheio de palavras.
view.py
# view.py — print the transcript the way an agent can read it
import json, sys
result = json.load(open(sys.argv[1]))
for number, segment in enumerate(result["segments"]):
who = f'{segment["speaker"]}: ' if segment.get("speaker") else ""
print(f'S{number} [{segment["start"]:.1f}-{segment["end"]:.1f}] {who}{segment["text"]}')
O agente devolve só os números dos trechos escolhidos:
keep.json
{"keep": [0, 2, 4]}
Antes de rodar o corte, peça para ele listar o que tirou e o motivo. Refazer um corte errado custa pouco, mas bater o olho numa lista é bem mais rápido do que assistir ao vídeo inteiro depois.
Passo 3: corte no ffmpeg usando os tempos das palavras
O script pega os números dos trechos e busca o tempo de início na primeira palavra e o de fim na última. Depois, adiciona alguns centésimos de segundo de margem, junta os trechos que estão colados e recodifica tudo de uma vez só para o corte bater no quadro exato. Ele também recalcula os tempos das palavras para a linha do tempo do vídeo novo, já deixando tudo pronto para a legenda.
cut.py
# cut.py — ranges from segment numbers, one ffmpeg pass, word times for the new timeline
import subprocess
PAD = 0.08 # seconds of room kept around every range
def ranges_from_ids(segments, keep_ids, pad=PAD):
"""Turn the segment numbers the agent chose into [start, end] ranges in seconds."""
ranges = []
for i in sorted(set(keep_ids)):
segment = segments[i]
words = segment.get("words") or []
start = words[0]["start"] if words else segment["start"]
end = words[-1]["end"] if words else segment["end"]
start, end = max(0.0, start - pad), end + pad
if ranges and start <= ranges[-1][1]:
ranges[-1][1] = max(ranges[-1][1], end)
else:
ranges.append([start, end])
return ranges
def cut(video, ranges, out):
"""Re-encode the kept ranges into one file (frame accurate)."""
parts = []
for i, (start, end) in enumerate(ranges):
parts.append(f"[0:v]trim=start={start:.3f}:end={end:.3f},setpts=PTS-STARTPTS[v{i}]")
parts.append(f"[0:a]atrim=start={start:.3f}:end={end:.3f},asetpts=PTS-STARTPTS[a{i}]")
joined = "".join(f"[v{i}][a{i}]" for i in range(len(ranges)))
parts.append(f"{joined}concat=n={len(ranges)}:v=1:a=1[v][a]")
subprocess.run(["ffmpeg", "-y", "-hide_banner", "-loglevel", "error", "-i", video,
"-filter_complex", ";".join(parts), "-map", "[v]", "-map", "[a]", out], check=True)
def remap(words, ranges):
"""Move word times onto the edited timeline, so subtitles match the cut video."""
moved, offset = [], 0.0
for start, end in ranges:
for word in words:
if word["start"] >= start and word["end"] <= end:
moved.append({**word, "start": word["start"] - start + offset,
"end": word["end"] - start + offset})
offset += end - start
return moved
edit.py
# edit.py — the whole pipeline: transcript in, edited video and subtitles out
import json
from kolwrite import upload, transcribe
from cut import ranges_from_ids, cut, remap
from srt_tools import cues_from_words, write_srt
result = transcribe(upload("talk.mp3"), output_format="json", word_timestamps=True)
with open("talk.json", "w", encoding="utf-8") as out:
json.dump(result, out, ensure_ascii=False)
# ... the agent reads talk.json (python view.py talk.json) and writes keep.json ...
keep = json.load(open("keep.json"))["keep"]
ranges = ranges_from_ids(result["segments"], keep)
cut("talk.mp4", ranges, "edited.mp4")
words = [w for segment in result["segments"] for w in segment.get("words", [])]
write_srt(cues_from_words(remap(words, ranges)), "edited.srt")
Apenas copiar os fluxos sem recodificar (-c copy) é mais rápido, mas como o corte só acontece em quadros-chave, ele pode sair alguns segundos fora do lugar. Quando a precisão do corte fizer diferença, faça a recodificação.
Passo 4: legendas para o vídeo editado
O edit.py já salvou o arquivo edited.srt usando os tempos recalculados das palavras. Para ver como as linhas são montadas, dê uma olhada no guia de SRT.
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")
Você pode gravar as legendas direto na imagem ou embutir como uma faixa separada, que dá para ligar e desligar no reprodutor:
ffmpeg -i edited.mp4 -vf "subtitles=edited.srt" -c:a copy burned.mp4
ffmpeg -i edited.mp4 -i edited.srt -c copy -c:s mov_text -metadata:s:s:0 language=eng soft.mp4
Outras edições usando os mesmos tempos por palavra
- Cortar silêncios: mantenha os blocos de fala contínua e jogue fora as pausas que passarem de um certo limite (veja como no guia de carimbos de tempo por palavra).
- Separar um clipe: busque uma frase específica na lista de palavras e recorte só aquele intervalo.
- Limpar vícios de linguagem: peça para o agente marcar os trechos que são só muleta de fala, ou faça uma busca direto nas palavras.
- Marcar capítulos e destaques: o agente aponta os números dos trechos e o script transforma isso em marcações de tempo (veja o guia de podcasts e YouTube).
- Isolar a fala de uma pessoa: ative o
include_diarizatione filtre apenas os trechos de quem você quer manter.
Quanto custa
| Trabalho | Modelo | Custo |
|---|---|---|
| Clipe de 10 minutos, transcrito uma vez | oracle | US$ 0,24 |
| Uma hora de vídeo, áudio limpo | base | US$ 0,48 |
| Uma hora de vídeo, qualidade de produção | oracle | US$ 1,44 |
| Uma hora, áudio difícil | oracle_max | US$ 3,60 |
A cobrança é feita por segundo e sem valor mínimo. Se um trecho específico ficar ruim e você quiser passar de novo em um modelo mais pesado, basta cortar só aquele pedaço do áudio antes de enviar para pagar apenas por ele. Veja a tabela em /llms/pricing.md.
Limites que você precisa conhecer
- As marcações de tempo servem bem para cortar entre uma palavra e outra, mas podem variar um pouquinho bem na ponta da palavra. A margem que o
cut.pyadiciona já resolve isso, mas vale sempre ouvir como ficaram os cortes antes de publicar. - Toda transcrição automática erra às vezes. Revise sempre os nomes próprios e números antes de publicar qualquer coisa. O KolWrite não divulga uma taxa de acerto por idioma para esta API.
- Não apague o arquivo original. Os cortes são recodificados a partir dele, e você vai precisar do bruto se resolver mudar a edição depois.
- Se a lista de intervalos ficar grande demais, o filtro gerado para o ffmpeg também fica gigante. Se passar de algumas centenas de cortes, divida o trabalho em lotes.
Perguntas e respostas
O Claude consegue editar um vídeo?
O Claude consegue editar vídeos quando pode executar comandos. Ele chama a API do KolWrite para pegar a transcrição com os tempos de cada palavra, escolhe quais frases manter e roda o ffmpeg para fazer os cortes. O Claude Code, o Codex CLI e os agentes do Cursor fazem isso, enquanto uma janela de chat comum apenas escreve os scripts.
Como corto um vídeo pela transcrição?
Faça a transcrição com tempos por palavra, escolha quais trechos vão ficar e pegue o início da primeira palavra e o fim da última em cada trecho. Com esses números em mãos, use os filtros trim e concat do ffmpeg para cortar os intervalos, do jeito que mostramos no cut.py.
Preciso de um plugin ou de um SDK?
Não. A API é HTTPS e JSON; o agente a chama com curl ou Python. No momento da escrita, não há pacote SDK nem servidor MCP publicados.
Funciona com hebraico, árabe e outros idiomas?
Para a transcrição, sim. A API aceita 100 idiomas, que ela mesma detecta ou que você define no parâmetro language. Os tempos por palavra saem dos modelos listados no guia de carimbos de tempo por palavra. Vale testar antes com uma amostra dos seus áudios e conferir a página de idiomas.
Como evitar que o agente invente carimbos de tempo?
Nunca peça para o agente devolver marcações de tempo. Entregue os trechos numerados, peça apenas os números de volta e deixe o seu código calcular os tempos reais a partir das palavras.