Come montare un video con Claude, Codex o qualsiasi agente IA

Passa all’agente una trascrizione con l’inizio e la fine esatti di ogni parola. L’agente sceglie quali frasi tenere e un breve script usa quei tempi per tagliare il video con ffmpeg. L’API di KolWrite genera questa trascrizione da audio e video in 100 lingue, e con le stesse parole crei anche i sottotitoli del montaggio finale.

Ottieni una chiave API Guida rapida Lingue supportate

Ultimo aggiornamento: · Markdown

Cosa può fare l'agente e cosa no

Nella trascrizione c'è solo il parlato, quindi l'agente lavora su quello che viene detto. Può eliminare false partenze, intercalari, ciak ripetuti, divagazioni e lunghe pause, oppure isolare le frasi giuste per un clip breve. Non vede però il video: decidere quale inquadratura mostrare spetta a te, a meno che tu non gli passi anche dei fotogrammi da analizzare.

Un agente in grado di eseguire comandi nel terminale (come Claude Code, Codex CLI o l'agente di Cursor) può chiamare direttamente l'API e lanciare ffmpeg in autonomia. Una normale chat che non esegue codice non può farlo da sola, ma può comunque scrivere gli script al posto tuo.

Il prompt da incollare nell'agente

Con questo prompt l'agente lavora sui numeri dei passaggi anziché scrivere direttamente i timestamp, evitando così che si inventi i tempi. Il testo rimanda poi alla documentazione dell'API pensata per le macchine.

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 vuoi che l'agente conosca già l'API in ogni sessione, copia la regola che trovi nella guida allo strumento dentro AGENTS.md o CLAUDE.md.

Passo 1: estrai l'audio e avvia la trascrizione

Il caricamento firmato ha un limite di 100 MiB, quindi se il video è lungo conviene prima estrarre l’audio. Un’ora di audio mono a 48 kbit/s occupa circa 21 MB.

ffmpeg -i talk.mp4 -vn -ac 1 -ar 16000 -b:a 48k talk.mp3

Questa breve funzione carica un file locale, fa partire la trascrizione e aspetta il risultato:

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")

Lo script edit.py qui sotto richiede l'output in JSON con i timestamp per parola (sono già le impostazioni predefinite, ma le abbiamo esplicitate per chiarezza). La risposta restituisce l'array segments[], dove ogni elemento contiene start, end, text, il campo facoltativo speaker e words[]:

Ecco un estratto della risposta:

{
  "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: fai scegliere all'agente cosa tenere

Formatta la trascrizione come un elenco di righe numerate. L'agente leggerà questo testo, consumando molti meno token rispetto all'intero JSON con i dettagli di ogni singola parola.

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"]}')

A quel punto l'agente ti risponderà indicando solo i numeri dei passaggi da salvare:

keep.json

{"keep": [0, 2, 4]}

Prima di procedere con il taglio, chiedi all'agente di elencare le parti scartate spiegando il motivo. Rifare un taglio sbagliato costa poco, ma dare un'occhiata a una lista ti porta via molto meno tempo che riguardare tutto il video montato.

Passo 3: taglia il video con ffmpeg usando i tempi delle parole

Lo script recupera l'inizio della prima parola e la fine dell'ultima per ciascun passaggio scelto, aggiunge qualche centesimo di secondo di margine, accorpa i segmenti consecutivi e ricodifica il tutto in un solo passaggio per avere tagli precisi al fotogramma. Inoltre, ricalcola i tempi delle parole in base alla nuova durata del video, così sono già pronti per i sottotitoli.

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")

Copiare i flussi audio e video senza ricodificare (-c copy) richiede meno tempo, ma permette di tagliare solo in corrispondenza dei keyframe, con il rischio di sbagliare il punto di taglio anche di qualche secondo. Se ti serve precisione, ricodifica il file.

Passo 4: aggiungi i sottotitoli al video montato

Lo script edit.py ha già generato il file edited.srt usando i nuovi tempi delle parole. Se vuoi capire come vengono formattate le righe, trovi i dettagli nella guida all'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")

Ora puoi imprimere i sottotitoli direttamente sui fotogrammi del video oppure aggiungerli come traccia separata attivabile dal player:

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

Altre operazioni possibili con gli stessi tempi delle parole

  • Accorciare i silenzi: mantieni i blocchi di parlato continuo e taglia le pause che superano una certa durata (trovi i dettagli nella guida ai timestamp per parola).
  • Estrarre una clip: cerca una frase specifica dentro l'elenco delle parole e ritaglia l'intervallo corrispondente.
  • Rimuovere gli intercalari: fai individuare all'agente i segmenti composti solo da parole riempitive, oppure cercali direttamente tu tra le singole parole.
  • Creare capitoli e momenti salienti: l'agente seleziona i numeri dei passaggi rilevanti e lo script li trasforma in timestamp (vedi la guida per podcast e YouTube).
  • Escludere gli interventi di un interlocutore: attiva include_diarization e conserva solo i passaggi di chi ti interessa tenere.

Quanto costa

LavoroModelloCosto
Clip di 10 minuti, trascritto una sola voltaoracle0,24 US$
Un'ora di video, audio pulitobase0,48 US$
Un'ora di video, qualità di produzioneoracle1,44 US$
Un'ora di video, audio complessooracle_max3,60 US$

La tariffazione è al secondo e senza soglia minima: se un singolo passaggio viene male e vuoi rielaborarlo con un modello più avanzato, ti basta ritagliare quel pezzetto di audio per pagare solo quei secondi. Trovi tutti i prezzi in /llms/pricing.md.

Limiti da tenere presenti

  • I timestamp sono abbastanza precisi per tagliare nello spazio tra due parole, ma possono avere un leggero scarto proprio sull'attacco o sulla coda della singola parola; il margine aggiunto da cut.py serve proprio a compensare questo effetto. Riascolta comunque i punti di giunzione prima di pubblicare il video.
  • Le trascrizioni automatiche non sono infallibili. Verifica sempre nomi propri e numeri prima di andare online; per questa API KolWrite non dichiara percentuali di accuratezza divise per lingua.
  • Non buttare il file originale. Ogni montaggio viene ricodificato partendo dal sorgente, che ti tornerà utile se in futuro vorrai modificare i tagli.
  • Se hai una lista lunghissima di segmenti da tenere, la stringa del filtro ffmpeg diventerà altrettanto enorme. Se superi qualche centinaio di tagli, ti conviene dividere il lavoro in più blocchi.

Domande e risposte

Claude può montare un video?

Sì, a patto che possa eseguire comandi sul tuo sistema: chiama l'API di KolWrite per avere la trascrizione con i tempi di ogni parola, sceglie le frasi da salvare e lancia ffmpeg per fare i tagli. Strumenti come Claude Code, Codex CLI e gli agenti di Cursor lo fanno direttamente, mentre una normale interfaccia di chat può solo prepararti gli script da eseguire.

Come taglio un video partendo dalla sua trascrizione?

Genera la trascrizione con i timestamp per parola e scegli quali passaggi tenere. Per ciascuno, prendi l’inizio della prima parola e la fine dell’ultima, poi taglia e unisci quegli intervalli con i filtri trim e concat di ffmpeg, come mostrato in cut.py.

Serve un plugin o un SDK?

No. L’API è HTTPS e JSON; l’agente la chiama con curl o Python. Al momento della scrittura non sono pubblicati né un pacchetto SDK né un server MCP.

Funziona con ebraico, arabo e altre lingue?

Per la trascrizione sì: l'API gestisce 100 lingue, che può riconoscere da sola oppure ricevere tramite il parametro language. I tempi delle singole parole vengono calcolati dai modelli indicati nella guida ai timestamp per parola. Ti consigliamo di fare prima un test con un estratto del tuo audio e di dare un'occhiata alla pagina delle lingue.

Come evito che l'agente inventi i timestamp?

Non fargli mai scrivere i tempi. Passagli un elenco di frasi numerate, fatti rispondere solo con i numeri da tenere e lascia che sia il tuo codice a ricavare i tempi esatti dai timestamp delle parole.