Transcripts and text-based editing
Kinema's Text panel ▸ Transcript tab shows the
dialogue of the open sequence as text. Select words to mark In/Out, then
extract or lift them; remove filler words and long pauses in one step;
turn the transcript into captions. Every action is an engine command
(transcript.*), so the CLI, the control channel and MCP
agents can do the same.
Model
- A transcript belongs to a media item
(
Project::transcripts, saved in the.fcprojsince schema v9). It lists words with media-time bounds (Ticks), an optional speaker index and a confidence, plus the speaker names and the language. Because the times are media time, the transcript stays valid however the clip is trimmed, moved, sped up or reused. - The sequence transcript is derived, never stored
(
kinema_edit::transcript::sequence_words): audio tracks are read top first; a word is heard through the first enabled clip whose range covers the word's midpoint (duplicates of the same dialogue on lower tracks read once); disabled, reversed and frame-hold clips contribute nothing. - Speaker names come from the clip transcripts, so renaming "Speaker 1" in every transcript renames it across the sequence.
Commands
| Command | What it does |
|---|---|
transcript.generate |
Transcribe media items (items, else the Project
selection, else the media of the sequence's audio clips). Params:
model (default whisper-base),
language (auto = detect),
diarize, maxSpeakers. One undo step. |
transcript.set |
Store a transcript you bring (JSON: language,
speakers, words with
text/start/end/speaker);
it is sorted and made well formed. |
transcript.delete |
Remove transcripts. |
transcript.inspect |
The sequence transcript: words (index, text, sequence times, speaker, clip), paragraphs, speakers, the word at the playhead. |
transcript.search |
Word-index ranges matching a phrase (case and punctuation ignored; the last word may be a prefix). |
transcript.select |
Mark In/Out around words from..=to (frame-snapped
outward) and move the playhead there. |
transcript.extract / transcript.lift |
Extract (ripple) or lift the words' frames on the targeted tracks. |
transcript.renameSpeaker |
Rename a speaker by name (every transcript) or by index in one
item. |
transcript.removeFillers |
Ripple-delete filler words (fillers, default
um/uh/erm/…; phrases such as "you know" allowed). |
transcript.removePauses |
Ripple-delete pauses longer than minSeconds, keeping
keepSeconds of air on both sides. |
transcript.createCaptions |
Lay the words out as captions on a new caption track
(maxChars, lines, minSeconds,
maxSeconds, gapFrames). |
transcript.models /
transcript.downloadModel |
List the speech models (size, licence, installed) / download one. |
Speech recognition
Recognition goes through the Transcriber trait
(crates/speech). The built-in recogniser is OpenAI's
Whisper, run in pure Rust on candle on the CPU, with
timestamp decoding, language detection and word times from
cross-attention alignment (see the kinema_speech::whisper
module docs). Speakers are labelled by clustering per-chunk MFCC
statistics (kinema_speech::diarize); no model is
involved.
Both are optional features, off by default and never built for the web:
whisper(onkinema-speech,kinema-engine, and thekinema/kinema-cliapps, where it also enables downloads): candle inference.download(speech-downloadon the engine): HTTPS downloads with rustls + RustCrypto and the operating system's certificate verifier.
Without whisper, and with no recogniser installed,
transcript.generate and Transcribe Sequence are disabled,
with "speech-to-text is not available in this build" as the reason
(describe, command_list {"enabled_only": true}
and the menus show it); with Automatically transcribe clips on,
file.import reports the same reason as a
transcription: … entry in its errors. Without
speech-download, transcript.downloadModel is
disabled the same way. Transcripts can still be imported with
transcript.set and edited with every other command. Hosts
and tests can install any recogniser in
Session::transcriber, which enables transcription in any
build.
Models
Weights are never bundled or committed. They are
downloaded on request into
<data dir>/models/<id>/ (see
kinema_engine::autosave::default_data_dir), each file
pinned to a revision of OpenAI's Hugging Face repositories and checked
against its SHA-256:
| Id | Languages | Licence |
|---|---|---|
whisper-tiny |
multilingual | MIT (OpenAI); HF conversion Apache-2.0 |
whisper-base (default) |
multilingual | MIT (OpenAI); HF conversion Apache-2.0 |
whisper-small |
multilingual | MIT (OpenAI); HF conversion Apache-2.0 |
Testing
Unit and engine tests use a fake Transcriber
(FixedTranscriber), so CI needs no model. The end-to-end
test crates/speech/tests/whisper_model.rs (feature
whisper) runs only when weights are in
target/models/<id>/ (or
$KINEMA_MODELS_DIR) and speech samples (mono 16 kHz f32
with a reference .txt) are in
target/fixtures/speech/; it reports the word error rate and
otherwise prints SKIPPED. Measured on 2026-10-01:
whisper-tiny, English, 12.2 % WER over 797 words of the
local speech samples (LibriSpeech read speech and dialogue clips) (about
9.5 minutes for the run in a release build on an Apple-silicon laptop
CPU).
Limits
- Transcription runs synchronously inside the command (no background job or progress bar yet).
- Track items that refer to a subclip are looked up by the subclip's id, so a transcript made for the parent media is not shown through subclip clips yet.