openai/whisper
Output writers
Active contributors: Jong Wook Kim
Purpose
Serialize a transcribe() result dict into one of five on-disk formats — plain text, WebVTT, SubRip (SRT), TSV, or JSON — and provide the small helper functions (format_timestamp, make_safe, compression_ratio, exact_div, argparse coercers) that the rest of the package leans on.
Directory layout
whisper/
└── utils.pyKey abstractions
| Symbol | Description |
|---|---|
make_safe(s) |
Replaces non-encodable characters with ? if the system encoding isn't utf-8; pass-through otherwise. Used in transcribe() when verbose=True. |
exact_div(x, y) |
assert x % y == 0 then integer division. Lets whisper/audio.py derive constants safely. |
str2bool(s), optional_int(s), optional_float(s) |
argparse coercers. str2bool only accepts "True"/"False". optional_* treat the literal "None" as None. |
compression_ratio(text) |
len(text_bytes) / len(zlib.compress(text_bytes)). Drives the fallback heuristic in transcribe(). |
format_timestamp(seconds, always_include_hours=False, decimal_marker=".") |
Formats HH:MM:SS.mmm (or MM:SS.mmm). |
get_start(segments) / get_end(segments) |
First/last word boundary across a segment list, falling back to segment-level boundaries. Used by subtitle writers. |
ResultWriter |
Base class. __call__(result, audio_path, options=None, **kwargs) writes audio_basename.<ext> to output_dir. Subclasses override write_result. |
WriteTXT, WriteVTT, WriteSRT, WriteTSV, WriteJSON |
Concrete writers for .txt, .vtt, .srt, .tsv, .json. |
SubtitlesWriter |
Shared subtitle-iteration logic for WriteVTT and WriteSRT. Supports word-level highlighting, max line width, max line count, max words per line. |
get_writer(output_format, output_dir) |
Returns either a single concrete writer or, for "all", a fan-out callable. |
How it works
graph TD
cli[whisper CLI] --> gw[get_writer]
gw -->|"txt"| txt[WriteTXT]
gw -->|"vtt"| vtt[WriteVTT]
gw -->|"srt"| srt[WriteSRT]
gw -->|"tsv"| tsv[WriteTSV]
gw -->|"json"| js[WriteJSON]
gw -->|"all"| fanout[fan-out callable]
fanout --> txt
fanout --> vtt
fanout --> srt
fanout --> tsv
fanout --> js
txt --> file1[audio.txt]
vtt --> file2[audio.vtt]
srt --> file3[audio.srt]
tsv --> file4[audio.tsv]
js --> file5[audio.json]WriteTXT
Simplest writer: prints each segment's stripped text on its own line. No timestamps.
WriteJSON
json.dump(result, file). Captures the full segment-level dict including tokens, avg_logprob, compression_ratio, no_speech_prob, and (when present) words.
WriteTSV
Tab-separated start\tend\ttext, with start/end as integer milliseconds to avoid locale-dependent decimal point parsing. Strips tabs out of the text body. Header row: start\tend\ttext.
WriteVTT and WriteSRT
Both extend SubtitlesWriter. They differ only in the file header (WEBVTT\n vs none), the timestamp format (HH:MM:SS.mmm vs HH:MM:SS,mmm — comma decimal marker for SRT), whether hours are always shown (SRT yes, VTT no), and whether each cue gets a numeric prefix (SRT yes, VTT no).
SubtitlesWriter.iterate_result has two modes:
- Segment-level (default): yields one cue per segment with the segment's
text(with-->rewritten to->to avoid breaking VTT/SRT parsers). - Word-level: triggered when
result["segments"][0]has"words". Iterates word timings, packing them into subtitle cues subject tomax_line_width,max_line_count, andmax_words_per_line. Whenhighlight_words=True, emits one cue per word so the active word can be wrapped in<u>...</u>underline tags.
The packing logic is intricate. The relevant escape valves:
- Long pauses (gap > 3 s) force a line break unless
preserve_segmentsis on (which it is when eithermax_line_countormax_line_widthisNone). - Segment breaks force a new cue when
preserve_segmentsis on. - When the cue has filled
max_line_countlines, it is yielded and a new one starts.
Argparse coercers
The CLI uses str2bool for boolean flags so --verbose True/False works as expected (instead of argparse's default truthiness behavior). optional_int / optional_float accept the literal string "None" to mean "leave unset" — used for --patience, --max_line_width, etc. where None is a meaningful default.
compression_ratio
def compression_ratio(text) -> float:
text_bytes = text.encode("utf-8")
return len(text_bytes) / len(zlib.compress(text_bytes))A repetition detector. "the the the the the" compresses well (high ratio); a real sentence compresses less. whisper/transcribe.py uses a default threshold of 2.4 to flag a segment as repetitive and trigger temperature fallback.
Integration points
- Imports from: standard library only.
- Imported by:
whisper/__init__.py(no re-exports, butwhisper.utilsis reachable);whisper/audio.py:exact_div;whisper/decoding.py:compression_ratio;whisper/transcribe.py(most of the helpers andget_writer). - Side effects: writers create files in
output_dir.
Entry points for modification
- New output format: subclass
ResultWriter, setextension, implementwrite_result. Register in thewritersdict inget_writer. - New subtitle styling: extend
SubtitlesWriter.iterate_result. The existinghighlight_wordspath is a good template. - Different timestamp format: change
format_timestampdefaults at the call site, not in the function (other callers depend on the current shape).
See also: Transcribe for how the result dict is built, and Reference: configuration for the CLI flags that pick a writer.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.