Transcribe Audio Locally with faster-whisper: Python CLI for TXT, SRT and VTT (2026)
Need a transcript from a meeting, interview, lecture, podcast, or screen recording—but do not want to upload the audio to a transcription website? You can run speech recognition on your own computer with faster-whisper, a reimplementation of Whisper using CTranslate2. With a small Python command-line tool, one audio file can become a plain-text transcript plus timestamped SRT and WebVTT subtitles and a JSON file for downstream processing.
This tutorial builds that tool from scratch. The workflow performs inference locally after the model has been downloaded, supports CPU-first setup, can optionally use an NVIDIA GPU, and lets you choose a language when automatic detection is not ideal. It is a practical starter pipeline—not a guarantee of perfect transcription or a claim that we benchmarked it on every operating system.
What you will build
- A repeatable Python CLI that accepts an audio or video file supported by the installed decoder.
- Four output formats: TXT, SRT, WebVTT, and JSON.
- Automatic language detection, with an optional explicit language code.
- Optional voice-activity filtering to skip long stretches without speech.
- Optional word-level timestamps for workflows that need finer alignment.
faster-whisper uses PyAV for decoding and its project documentation says a separate system FFmpeg installation is not required for ordinary transcription. FFmpeg is still useful when you need to inspect or convert troublesome media files.
Prerequisites
- Python 3.9 or newer, as stated in the faster-whisper project README.
- A few gigabytes of free disk space for the selected model and output files. Model size and memory use vary.
- An internet connection for the first model download. Later runs can reuse cached weights, provided they remain available locally.
- A sample recording you are permitted to process. Start with a short, non-sensitive file while checking the setup.
The default configuration below uses the small model with CPU INT8 inference as a reasonable starting point. If your machine is memory-constrained, try base or tiny; if you have a compatible NVIDIA CUDA environment, you can select a GPU configuration. There is no universal fastest or most accurate setting for every language, recording, and device.
Step 1: Create an isolated Python project
A virtual environment keeps this tool's dependencies separate from other Python projects. See the official Python venv documentation for platform-specific details.
mkdir local-transcriber
cd local-transcriber
python -m venv .venv
Activate it on macOS or Linux:
source .venv/bin/activate
On Windows PowerShell:
.venv\Scripts\Activate.ps1
Install the package from PyPI using the installation command documented by the project:
python -m pip install --upgrade pip
python -m pip install faster-whisper
Verify that Python can import the library:
python -c "from faster_whisper import WhisperModel; print('faster-whisper import OK')"
Expected result: faster-whisper import OK. This verifies the import, not yet that a model can run on your particular hardware.
Step 2: Create the transcription script
Create a file named transcribe.py in the project directory and paste in the following code. It writes output files into a folder you specify, and it does not upload the audio to a transcription API.
import argparse
import json
from pathlib import Path
from faster_whisper import WhisperModel
def timestamp(seconds: float, decimal: str = ",") -> str:
total_ms = max(0, int(round(seconds * 1000)))
hours, remainder = divmod(total_ms, 3_600_000)
minutes, remainder = divmod(remainder, 60_000)
secs, millis = divmod(remainder, 1_000)
return f"{hours:02}:{minutes:02}:{secs:02}{decimal}{millis:03}"
def main() -> None:
parser = argparse.ArgumentParser(
description="Transcribe audio locally with faster-whisper."
)
parser.add_argument("audio", type=Path, help="Input audio or video file")
parser.add_argument("--model", default="small",
help="Model name, for example tiny, base, small, medium, large-v3")
parser.add_argument("--language", default=None,
help="Optional language code such as en or hi; omit for auto-detection")
parser.add_argument("--device", choices=["cpu", "cuda"], default="cpu")
parser.add_argument(
"--compute-type",
choices=["int8", "float32", "float16", "int8_float16"],
default="int8",
)
parser.add_argument("--beam-size", type=int, default=5)
parser.add_argument("--no-vad", action="store_true",
help="Disable voice-activity filtering")
parser.add_argument("--word-timestamps", action="store_true",
help="Include word-level timestamps in JSON")
parser.add_argument("--output-dir", type=Path, default=Path("transcripts"))
args = parser.parse_args()
audio_path = args.audio.expanduser().resolve()
if not audio_path.is_file():
parser.error(f"Input file does not exist: {audio_path}")
if args.beam_size < 1:
parser.error("--beam-size must be at least 1")
args.output_dir.mkdir(parents=True, exist_ok=True)
model = WhisperModel(
args.model,
device=args.device,
compute_type=args.compute_type,
)
segments_iter, info = model.transcribe(
str(audio_path),
beam_size=args.beam_size,
language=args.language,
vad_filter=not args.no_vad,
word_timestamps=args.word_timestamps,
)
# faster-whisper returns a generator: iterating it performs transcription.
segments = list(segments_iter)
stem = audio_path.stem
plain_text = " ".join(segment.text.strip() for segment in segments).strip()
srt_lines = []
vtt_lines = ["WEBVTT", ""]
json_segments = []
for index, segment in enumerate(segments, start=1):
text = segment.text.strip()
start = timestamp(segment.start)
end = timestamp(segment.end)
srt_lines.extend([str(index), f"{start} --> {end}", text, ""])
vtt_lines.extend([
f"{timestamp(segment.start, '.')} --> {timestamp(segment.end, '.')}",
text,
"",
])
words = []
if segment.words:
for word in segment.words:
words.append({
"start": word.start,
"end": word.end,
"word": word.word,
"probability": getattr(word, "probability", None),
})
json_segments.append({
"index": index,
"start": segment.start,
"end": segment.end,
"text": text,
"words": words,
})
(args.output_dir / f"{stem}.txt").write_text(
plain_text + ("\n" if plain_text else ""), encoding="utf-8"
)
(args.output_dir / f"{stem}.srt").write_text(
"\n".join(srt_lines).rstrip() + ("\n" if srt_lines else ""),
encoding="utf-8",
)
(args.output_dir / f"{stem}.vtt").write_text(
"\n".join(vtt_lines).rstrip() + "\n", encoding="utf-8"
)
(args.output_dir / f"{stem}.json").write_text(
json.dumps({
"input_file": audio_path.name,
"model": args.model,
"language": info.language,
"language_probability": info.language_probability,
"segments": json_segments,
}, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
print(f"Detected language: {info.language} "
f"(probability {info.language_probability:.3f})")
print(f"Transcribed {len(segments)} segments.")
print(f"Output directory: {args.output_dir.resolve()}")
if __name__ == "__main__":
main()
Step 3: Transcribe your first recording
Put a recording in the project directory, for example meeting.mp3, then run:
python transcribe.py meeting.mp3
On the first run, the selected model may need to download before transcription starts. The script prints the detected language, its model-reported probability, the number of segments, and the output directory. It should create:
transcripts/meeting.txt— a readable transcript.transcripts/meeting.srt— numbered subtitle cues with timestamps.transcripts/meeting.vtt— WebVTT cues for compatible players and web pages.transcripts/meeting.json— language metadata and segment start/end times for scripts or applications.
These are expected outputs from the code path, not a claim that a particular sample file was executed during preparation of this article. Open the TXT file and check a few places against the original recording before relying on the transcript.
Step 4: Choose the language and model deliberately
Automatic language detection is convenient, but short clips, background music, code-switching, and noisy recordings can confuse it. If you know the spoken language, pass its language code explicitly. For example:
python transcribe.py interview.mp3 --language en
python transcribe.py interview-hi.mp3 --language hi
Try a smaller model when memory or speed is the limiting factor:
python transcribe.py meeting.mp3 --model base
A larger model may improve results for some recordings, but it also demands more compute and memory. Compare a representative sample in the language and acoustic conditions you actually care about; do not assume a model name guarantees a particular accuracy level.
Step 5: Use an NVIDIA GPU only if its runtime is configured
The project README documents GPU inference with CUDA and lists the required NVIDIA libraries. Its current guidance says recent CTranslate2 versions require CUDA 12 and cuDNN 9; older CUDA/cuDNN combinations may need a compatible version pin. Check the current project requirements before changing CUDA packages.
For a compatible NVIDIA setup, try:
python transcribe.py meeting.mp3 --device cuda --compute-type float16
If you see a missing cuBLAS/cuDNN library error, return to CPU mode first:
python transcribe.py meeting.mp3 --device cpu --compute-type int8
That separates transcription-code problems from GPU runtime problems. Do not blindly install random CUDA DLLs or mix incompatible library versions to make the error disappear.
Step 6: Generate word-level timestamps when needed
Segment timestamps are usually enough for subtitles. If a downstream editor or alignment workflow needs individual word times, enable the option:
python transcribe.py meeting.mp3 --word-timestamps
The JSON output then includes word-level entries when the model returns them. This adds work and may not be necessary for ordinary notes or meeting minutes. Word boundaries are estimates; listen to the relevant section when precise synchronization matters.
Step 7: Convert a troublesome media file with FFmpeg
faster-whisper's bundled decoding support means a separate FFmpeg install is not a prerequisite for the basic workflow. If a particular container or codec fails to decode, convert a copy to a simple WAV file using FFmpeg:
ffmpeg -i input-video.mp4 -vn -ac 1 -ar 16000 converted.wav
python transcribe.py converted.wav
-vn omits video, -ac 1 requests mono audio, and -ar 16000 sets the output sample rate to 16 kHz. Keep the original file: conversion can discard information and is not a universal fix for damaged or unsupported media.
Troubleshooting checklist
- Import error after installation: activate the project's virtual environment and run
python -m pip show faster-whisperusing the same Python interpreter that launches the script. - First run seems stuck: model weights may still be downloading or being loaded. Check network access and disk space; the first run can take longer than later runs.
- Out-of-memory or very slow inference: try
--model baseor--model tinyand CPU INT8. Close other memory-heavy applications. Performance depends on hardware, model, audio, and settings. - Incorrect language: pass
--language en,--language hi, or the appropriate code for the recording. Review code-switched speech manually. - Empty or poor transcript: confirm the file contains audible speech, check the correct audio track, and try a WAV conversion. Noise, overlap, accents, clipping, and distant microphones can degrade recognition.
- CUDA library error: check the exact CUDA/cuDNN requirements for your installed CTranslate2 version. Use CPU mode as a baseline instead of randomly downgrading system libraries.
- Subtitles look wrong: verify the player's subtitle support and compare cue times against the recording. SRT and WebVTT are different formats; use the one your target player expects.
Security, privacy, and accuracy
- Local inference is not the same as zero network activity. The first model load downloads weights from the model host. After the model is cached, the script itself makes no transcription API call, but your operating system, environment, or other software may still access the network.
- Protect the source and outputs. Audio and transcripts can contain customer data, private meetings, credentials, or personal information. Store them in an access-controlled directory and check whether that directory is synced to cloud storage or backed up elsewhere.
- Get permission. Follow applicable recording-consent, workplace, copyright, and data-retention rules before transcribing conversations or distributing subtitles.
- Review high-impact content. Names, numbers, legal terms, medical language, accents, and overlapping speakers are common places to make mistakes. Do not treat a generated transcript as a verified record without checking it.
- Check model and dependency licenses. Review the relevant model card and software licenses for your intended commercial or redistribution use.
What to build next
This CLI can become the first stage of a private audio-processing workflow: transcribe a recording, review the text, and then index approved transcripts for search. If you want to generate narration rather than recognize speech, see our related guide to LocalText2Voice for local audiobooks and podcasts. If your next step is asking questions over transcripts and other files, start with our local document Q&A assistant with Ollama and Python.
Frequently asked questions
Does faster-whisper send my audio to a cloud API?
The code in this tutorial runs the model in your Python process and does not call a hosted transcription API. The model may be downloaded on the first run, and other software on your computer can still access the network. For sensitive work, inspect the full environment and use an appropriate network policy.
Do I need FFmpeg installed?
The faster-whisper README says system FFmpeg is not required for normal use because decoding is handled through PyAV. FFmpeg is optional for manual inspection or conversion when a media file causes decoding trouble.
Can I transcribe Hindi or mixed Hindi-English speech?
You can try automatic detection or pass --language hi for Hindi. Mixed-language speech, proper nouns, and local accents may still need manual correction; test with representative audio.
Does this identify who is speaking?
No. This script produces transcription segments and optional word timestamps; it does not implement speaker diarization. Speaker attribution requires a separate workflow and should be validated carefully.
Which model should I use?
Start with small on CPU INT8, then compare base, medium, or a supported larger model against your own short test set. Choose based on quality, latency, and available memory rather than a blanket performance claim.
Official references
- faster-whisper repository and README — installation, API examples, model options, timestamps, VAD, and hardware requirements.
- OpenAI Whisper repository — original Whisper project and background.
- Python venv documentation — isolated Python environments.
- FFmpeg command-line documentation — media conversion options.
