Speech to Text in Python with faster-whisper: Transcribe Audio and Make Subtitles (2026)
Turning speech into text used to mean paying for a cloud API or fighting with old speech recognition libraries. In 2026 the easiest high-quality option is faster-whisper: a reimplementation of OpenAI's open-source Whisper model on top of the CTranslate2 inference engine. It runs completely offline, works on an ordinary laptop CPU, understands about 100 languages and is several times faster than the original openai-whisper package while using less memory. In this tutorial we transcribe a meeting recording with timestamps, write a ready-to-upload SRT subtitle file, flag low-confidence words with word-level timestamps, detect the language of a Spanish clip and translate it straight into English. Everything was tested on Python 3.13.5 with faster-whisper 1.2.1, ctranslate2 4.8.4 and PyAV 18.1.0, and every output block below is the real output of the scripts shown.
Why faster-whisper?
- Private and free: audio never leaves your machine, and there is no per-minute bill. That matters for meetings, interviews, customer calls and medical or legal recordings.
- Fast on CPU: with 8-bit (
int8) quantization thebasemodel transcribed our 19-second clip in about 1.2 seconds on a CPU. On an NVIDIA GPU usedevice="cuda"andcompute_type="float16". - No FFmpeg install needed: audio is decoded with PyAV, which bundles the FFmpeg libraries, so MP3, WAV, M4A, OGG and the audio track of MP4 videos all work out of the box.
- Same models as Whisper:
tiny,base,small,medium,large-v3and the fastturbomodel (large-v3-turbo) are downloaded automatically from Hugging Face the first time you use them and cached afterwards.
Install
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install faster-whisper==1.2.1 "av<19"
Important 2026 gotcha: a plain pip install faster-whisper currently pulls PyAV 19, and transcription then fails with TypeError: open() got an unexpected keyword argument 'metadata_errors', because PyAV 19 removed that argument and faster-whisper 1.2.1 still passes it. We confirmed that PyAV 17 and 18.1.0 work and 19.0.0 does not, so pin "av<19" until a newer faster-whisper release fixes it. The first run downloads the model (about 145 MB for base); you may see a harmless warning about unauthenticated Hugging Face requests, which you can silence by setting an HF_TOKEN.
Step 1: Transcribe an audio file with timestamps
Our test file meeting.mp3 is a 19-second weekly update. WhisperModel loads the model once; transcribe() returns a generator of segments plus an info object with the detected language and duration. vad_filter=True enables the built-in Silero voice activity detector, which skips silence and reduces the "hallucinated" text Whisper can produce on quiet parts of long recordings.
import time
from faster_whisper import WhisperModel
# "base" is about 145 MB and runs well on a laptop CPU; int8 keeps memory low
model = WhisperModel("base", device="cpu", compute_type="int8")
start = time.perf_counter()
segments, info = model.transcribe("meeting.mp3", beam_size=5, vad_filter=True)
print(f"language: {info.language} (probability {info.language_probability:.2f})")
print(f"audio duration: {info.duration:.1f}s")
for seg in segments: # segments is a generator: decoding happens here
print(f"[{seg.start:5.2f}s -> {seg.end:5.2f}s] {seg.text.strip()}")
print(f"transcribed in {time.perf_counter() - start:.1f}s on CPU")
Real output
$ python transcribe.py
language: en (probability 1.00)
audio duration: 19.3s
[ 0.00s -> 6.64s] Good morning, team. Here is the weekly update. Sales grew 12% compared to last week,
[ 6.64s -> 12.64s] and we shipped version 2.4 of the mobile app on Tuesday. The main risk is the delayed payment
[12.64s -> 18.00s] integration, which should be ready by Friday. Please send your questions to Sarah before
[18.00s -> 18.96s] the next meeting.
transcribed in 1.2s on CPU
Notice that Whisper did more than recognise words: it added punctuation and wrote "twelve percent" as 12% and "version two point four" as 2.4. Also note that nothing is decoded until you loop over segments. If you need a list, call list(segments), otherwise the work happens lazily inside your for loop.
Step 2: Generate SRT subtitles for YouTube or a video player
Each segment has start and end times in seconds, which is exactly what a subtitle file needs. SRT uses the format HH:MM:SS,mmm, so we convert seconds with divmod and write numbered blocks.
from faster_whisper import WhisperModel
def srt_time(t: float) -> str:
ms = round(t * 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}"
model = WhisperModel("base", device="cpu", compute_type="int8")
segments, _ = model.transcribe("meeting.mp3", vad_filter=True)
with open("meeting.srt", "w", encoding="utf-8") as f:
for i, seg in enumerate(segments, start=1):
f.write(f"{i}\n{srt_time(seg.start)} --> {srt_time(seg.end)}\n{seg.text.strip()}\n\n")
print(open("meeting.srt", encoding="utf-8").read().strip())
$ python make_srt.py
1
00:00:00,000 --> 00:00:06,640
Good morning, team. Here is the weekly update. Sales grew 12% compared to last week,
2
00:00:06,640 --> 00:00:12,640
and we shipped version 2.4 of the mobile app on Tuesday. The main risk is the delayed payment
3
00:00:12,640 --> 00:00:18,000
integration, which should be ready by Friday. Please send your questions to Sarah before
4
00:00:18,000 --> 00:00:18,960
the next meeting.
Upload meeting.srt to YouTube Studio (Subtitles, Upload file, With timing) or load it in VLC next to the video. For a video file, pass the .mp4 straight to transcribe(): PyAV extracts the audio track for you.
Step 3: Word-level timestamps and confidence scores
Set word_timestamps=True and every segment gets a words list with the start time, end time and probability of each word. This is useful for karaoke-style captions, for jumping to the exact moment a word was said and for flagging words a human should double-check before publishing a transcript.
from faster_whisper import WhisperModel
model = WhisperModel("base", device="cpu", compute_type="int8")
segments, _ = model.transcribe("meeting.mp3", word_timestamps=True)
words = [w for seg in segments for w in seg.words]
print("first words:", " | ".join(f"{w.word.strip()} {w.start:.2f}s" for w in words[:5]))
low = [w for w in words if w.probability < 0.80]
print(f"{len(words)} words, {len(low)} below 0.80 confidence - check these:")
for w in low:
print(f" {w.start:5.2f}s {w.word.strip():<10} p={w.probability:.2f}")
$ python words.py
first words: Good 0.00s | morning, 0.34s | team. 0.92s | Here 1.72s | is 1.96s
52 words, 1 below 0.80 confidence - check these:
12.54s integration, p=0.76
Only one of 52 words fell below 80% confidence. In a long interview, printing this short list lets an editor review a handful of words instead of re-listening to the whole recording.
Step 4: Detect the language and translate to English
Whisper is multilingual. Without a language argument it listens to the first 30 seconds and guesses the language. With task="translate" it outputs English text directly, whatever language was spoken.
from faster_whisper import WhisperModel
model = WhisperModel("base", device="cpu", compute_type="int8")
# 1) automatic language detection + transcription in the original language
segments, info = model.transcribe("spanish.mp3")
print(f"detected: {info.language} ({info.language_probability:.2f})")
print("es:", " ".join(s.text.strip() for s in segments))
# 2) task="translate" makes Whisper output English text directly
segments, _ = model.transcribe("spanish.mp3", task="translate")
print("en:", " ".join(s.text.strip() for s in segments))
$ python translate.py
detected: es (0.91)
es: Hola a todos. El nuevo producto estará disponible en tiendas a partir del lunes y el precio será de 20 euros.
en: Hello everyone! The new product will be available from Monday and the price will be 20 euros.
The Spanish clip was detected as es with 91% probability, transcribed accurately (with "veinte" written as 20) and translated into natural English. If you already know the language, pass language="es": it skips detection and avoids wrong guesses on very short clips.
Step 5: Faster transcription of long files with batching
For podcasts and hour-long meetings, BatchedInferencePipeline splits the audio into speech chunks and decodes several of them at once, which gives a big speed-up, especially on a GPU.
from faster_whisper import WhisperModel, BatchedInferencePipeline
model = WhisperModel("base", device="cpu", compute_type="int8")
batched = BatchedInferencePipeline(model=model)
segments, info = batched.transcribe("meeting.mp3", batch_size=8)
print(" ".join(s.text.strip() for s in segments))
$ python batched.py
Good morning team. Here is the weekly update. Sales grew 12% compared to last week, and we shipped version 2.4 of the mobile app on Tuesday. The main risk is the delayed payment integration, which should be ready by Friday. Please send your questions to Sarah before the next meeting.
Which model should you choose?
- tiny / base: fastest, fine for clear speech, quick drafts and low-power machines. The English-only versions (
base.en,small.en) are slightly more accurate for English. - small / medium: a good balance for accents, noisy audio and non-English languages.
- large-v3: the most accurate, best on a GPU. turbo (large-v3-turbo) is much faster than large-v3 with similar accuracy for transcription, but it is not trained for
task="translate", so use another model for translation. - On CPU always try
compute_type="int8"first; on GPU usefloat16orint8_float16. Setcpu_threadsto the number of physical cores if transcription feels slow.
Common errors and fixes
- TypeError ... 'metadata_errors': PyAV 19 is installed. Run
pip install "av<19". - Could not load library libcudnn / libcublas: GPU mode needs CUDA 12 and cuDNN 9 libraries. Install them (for example the
nvidia-cublas-cu12andnvidia-cudnn-cu12pip packages) or usedevice="cpu". - Repeated or invented sentences on silence: keep
vad_filter=True, and considercondition_on_previous_text=Falsefor long, noisy files. - Wrong language detected: pass
language="en"(or the correct code) explicitly.
Where to use this next
- Wrap
transcribe()in a web page with an upload button and a text box using Gradio, and you have your own private transcription app in about 15 lines. - Before sending a long transcript to an LLM for a summary, count its tokens with tiktoken so it fits the context window and your budget.
- Combine transcripts with documents converted by MarkItDown to build one searchable Markdown knowledge base for RAG.
Conclusion
With faster-whisper, speech to text in Python is a few lines of code: load a WhisperModel, call transcribe() and loop over the segments. On a normal CPU you get punctuated text with timestamps in about a second for a short clip, SRT subtitles with a tiny helper function, word-level confidence for review, automatic language detection and translation to English, all offline and free. Just remember to pin av<19 with faster-whisper 1.2.1, and pick the model size that fits your hardware and accuracy needs.