CodeVetter · Repo Unpack · Contributor handoff
openai/whisper
Small, flat, readable codebase (~3.7k LOC of Python). Before editing, read whisper/transcribe.py (orchestrator+CLI), whisper/decoding.py (engine), and whisper/model.py (nn defs). Style is enforced by pre-commit (black/isort/flake8) in CI. Tests need ffmpeg and network for model downloads; the fastest local check is `pytest -k 'not test_transcribe'`. Known traps: a return-instead-of-raise bug in _get_audio_features, an undeclared `regex` dependency, Whisper binding free functions as class attributes, and the deliberately convoluted seek loop in transcribe().
- Read-first files: transcribe.py (623 lines, orchestration + cli()), decoding.py (826 lines, DecodingTask/DecodingOptions/decoders/filters), model.py (352 lines, Whisper/AudioEncoder/TextDecoder), __init__.py (load_model + _MODELS), tokenizer.py (vocab + specials).
- Formatting is enforced: black + isort (profile=black, line_length=88, trailing comma, multi_line_output=3) + flake8 (--max-line-length 88, ignore E203,E501,W503,W504) via .pre-commit-config.yaml, run as the gating CI job.
- Test invocation: `pip install .[dev]` (pulls pytest+scipy) then `pytest`; ffmpeg must be on PATH; test_transcribe downloads checkpoints (~GBs) so `pytest -k 'not test_transcribe'` mirrors most of CI's fast path; CUDA tests are behind the requires_cuda marker.
- Releases: bump whisper/version.py __version__ (date-based, e.g. "20250625") and push a commit titled 'Release <version>' to main — python-publish.yml regex-matches the message, tags v<version>, builds sdist, and uploads to PyPI with TWINE secrets.
- Trap: Whisper binds transcribe/decode/detect_language as class attributes pointing at module-level functions — renaming those functions or changing their signatures breaks the model.method() API used in README examples.
- Trap: DecodingTask._verify_options rejects incompatible combos (beam_size+best_of, best_of at T=0, patience without beam_size, length_penalty outside [0,1]) — new CLI flags must respect these invariants.
- Trap: tokenizer.decode() silently drops all tokens >= timestamp_begin; use decode_with_timestamps() (renders <|1.08|>) when debugging timestamp/token issues.
- Trap: fp16 is default-on but unsupported on CPU — transcribe() downgrades to fp32 with a warning, and DecodingOptions.fp16 controls the mel cast in _get_audio_features; keep both consistent when changing dtype logic.
- Trap: binary assets (assets/*.tiktoken, assets/mel_filters.npz, normalizers/english.json) load via os.path.dirname(__file__) — restructuring the package requires updating MANIFEST.in and these loaders together.
- Safe-edit boundaries: utils.py writers and normalizers/ are low-risk leaf modules; audio.py constants (SAMPLE_RATE=16000, N_FRAMES=3000, hop=160) are load-bearing for timestamp math everywhere; the transcribe() seek loop and timing.py heuristics are the blast-radius hotspots. (inferred)