Open-Source Wikis

/

Whisper

/

Systems

/

Output writers

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.py

Key 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 to max_line_width, max_line_count, and max_words_per_line. When highlight_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_segments is on (which it is when either max_line_count or max_line_width is None).
  • Segment breaks force a new cue when preserve_segments is on.
  • When the cue has filled max_line_count lines, 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, but whisper.utils is reachable); whisper/audio.py:exact_div; whisper/decoding.py:compression_ratio; whisper/transcribe.py (most of the helpers and get_writer).
  • Side effects: writers create files in output_dir.

Entry points for modification

  • New output format: subclass ResultWriter, set extension, implement write_result. Register in the writers dict in get_writer.
  • New subtitle styling: extend SubtitlesWriter.iterate_result. The existing highlight_words path is a good template.
  • Different timestamp format: change format_timestamp defaults 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.

Output writers – Whisper wiki | Factory