A focused audio transcription service built with Python 3.12.10, FastAPI, and Faster-Whisper. Upload a WAV or MP3 file and receive a structured JSON response containing the full transcription text plus per-segment timestamps. Long recordings are automatically split into 30-second chunks and reassembled with correct global timestamps.
- Requirements
- Architecture Overview
- Installation
- Configuration
- Running the Service
- API Reference
- Model Selection Guide
- Project Structure
- Troubleshooting
| Requirement | Version |
|---|---|
| Operating System | Windows 10 / Windows 11 |
| Python | 3.12.10 |
| FFmpeg | 4.x or newer |
| pip | latest |
app/
├── main.py # FastAPI app factory, lifespan, health check
├── config/
│ └── settings.py # All configuration via Pydantic BaseSettings + .env
├── api/
│ └── routes/
│ └── transcription.py # POST /transcribe endpoint
├── audio/
│ └── processor.py # Validation, format conversion, chunking (pydub + FFmpeg)
├── transcription/
│ └── service.py # Faster-Whisper inference and chunk orchestration
├── models/
│ └── schemas.py # Pydantic request/response schemas
└── utils/
└── file_utils.py # Temporary file helpers
Client ──(MP3 / WAV)──► POST /transcribe
│
Validate file extension, MIME type, size
│
Save to Windows temp directory
│
Convert to 16 kHz mono WAV ◄── pydub + FFmpeg
│
Measure total duration
│
Split into 30-second chunks
│
Transcribe each chunk ◄── Faster-Whisper
│
Offset timestamps + merge all segments
│
Delete all temp files
│
JSON response ──► Client
Download and install Python 3.12.10 from the official website:
https://www.python.org/downloads/release/python-31210/
During installation:
- Check "Add Python to PATH"
- Check "Install pip"
Verify after installation:
python --version
# Expected: Python 3.12.10
pip --version
# Expected: pip 24.x.x from ...FFmpeg is required by pydub for audio decoding and encoding. It must be installed and accessible on your Windows PATH.
winget (recommended, built into Windows 10/11):
winget install --id Gyan.FFmpeg -e --source wingetOR
Manually download:
- Download the full build from https://www.gyan.dev/ffmpeg/builds/
- Extract the ZIP to
C:\ffmpeg - Add
C:\ffmpeg\binto your Windows System PATH:- Open Start → search "Environment Variables"
- Click "Environment Variables"
- Under System variables, select Path → click Edit
- Click New → enter
C:\ffmpeg\bin - Click OK on all dialogs
After any installation method — close and reopen PowerShell, then verify:
ffmpeg -versionYou must see version output similar to:
ffmpeg version 8.1.2-full_build-www.gyan.dev Copyright (c) 2000-2026 the FFmpeg developers
If ffmpeg -version fails, FFmpeg is not on PATH — see
Troubleshooting.
git clone https://github.com/SyntaxilitY/Transcription-speech-to-text-Pipeline-Flask-Python.git
cd Transcription-speech-to-text-Pipeline-Flask-Python# Create virtual environment
python -m venv .venv
# Activate it
.venv\Scripts\activateYour prompt will change to show (.venv) when the environment is active.
pip install --upgrade pip
pip install -r requirements.txtThis installs all required packages including FastAPI, Faster-Whisper, pydub, and Uvicorn.
copy .env.example .envOpen .env in any text editor and configure it. See
Configuration for all available options.
All settings are managed through the .env file in the project root.
# ── Application ────────────────────────────────────────────────────────────
APP_NAME=Transcription Pipeline
APP_VERSION=1.0.0
# Set to true during development to enable debug logging and auto-reload
APP_DEBUG=false
# ── Server ─────────────────────────────────────────────────────────────────
HOST=0.0.0.0
PORT=8000
# ── Faster-Whisper Model ───────────────────────────────────────────────────
# Model size controls the trade-off between speed and accuracy.
# Options: tiny | base | small | medium | large | large-v2 | large-v3
# Recommendation for Windows CPU: base or small
WHISPER_MODEL_SIZE=base
# Device to run inference on.
# Options: cpu | cuda
# Use cpu unless you have an NVIDIA GPU with CUDA installed.
WHISPER_DEVICE=cpu
# Compute type (quantization).
# Use float32 on Windows CPU — int8 can cause the server to hang on Windows.
# Options: float32 | float16 | int8_float16 | int8
WHISPER_COMPUTE_TYPE=float32
# Language of the audio.
# Leave blank for automatic detection (recommended).
# Set to a language code to force a specific language: en | fr | de | es | etc.
WHISPER_LANGUAGE=
# Beam size for decoding. Higher values improve accuracy but are slower.
WHISPER_BEAM_SIZE=5
# ── Audio Processing ───────────────────────────────────────────────────────
# Duration of each audio chunk in milliseconds.
# Long files are split into chunks of this size before transcription.
CHUNK_DURATION_MS=30000
# Target sample rate in Hz. Whisper requires 16000 — do not change this.
TARGET_SAMPLE_RATE=16000
# Maximum allowed upload size in bytes. Default is 500 MB.
MAX_FILE_SIZE_BYTES=524288000
# ── FFmpeg ─────────────────────────────────────────────────────────────────
# Leave blank if FFmpeg is on your Windows PATH.
# Set this to the full path of ffmpeg.exe if the app cannot find it.
#
# To find your FFmpeg path, run in PowerShell:
# where ffmpeg
#
# Then paste the result here. Example:
# FFMPEG_PATH=C:\Users\YourName\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-8.1.2-full_build\bin\ffmpeg.exe
FFMPEG_PATH=
# ── Temporary Files ────────────────────────────────────────────────────────
# Directory for temporary audio files created during processing.
# Leave blank to use the Windows default:
# C:\Users\<YourName>\AppData\Local\Temp\transcription_pipeline
# Do NOT set this to a Unix-style path like /tmp — it does not exist on Windows.
TEMP_DIR=| Setting | Detail |
|---|---|
WHISPER_COMPUTE_TYPE |
Always use float32 on Windows CPU. Setting int8 causes the server to hang silently on Windows during startup. |
WHISPER_LANGUAGE |
Leave the value completely blank for auto-detection. Do not wrap it in quotes. |
FFMPEG_PATH |
Only needed if ffmpeg -version works in one terminal but the app reports FFmpeg not found. |
TEMP_DIR |
Leave blank. The app resolves the correct Windows temp path automatically using Python's tempfile.gettempdir(). |
Important: Always run the service from the same PowerShell window where
ffmpeg -version works. Do not switch terminals between verifying FFmpeg and
starting the app.
# Make sure your virtual environment is active
.venv\Scripts\activate
# Run with auto-reload (set APP_DEBUG=true in .env first)
python -m app.main.venv\Scripts\activate
uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 1Why
--workers 1? The Faster-Whisper model is loaded into memory once at startup and shared across all requests. Using multiple workers would cause each worker process to load its own copy of the model, multiplying memory usage with no benefit on a single-model setup.
INFO | === Transcription Pipeline starting up ===
INFO | FFmpeg configured → C:\...\ffmpeg.exe
INFO | FFprobe configured → C:\...\ffprobe.exe
INFO | Loading TranscriptionService — this may take 10-30 seconds...
INFO | Loading Faster-Whisper model: size=base device=cpu compute_type=float32
INFO | Faster-Whisper model loaded successfully.
INFO | TranscriptionService ready.
INFO | === Startup complete. Ready to accept requests. ===
INFO | Application startup complete.
INFO | Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
The first run downloads the Whisper model from HuggingFace (~145 MB for
base). Subsequent starts use the locally cached model and are much faster.
Open a second PowerShell window and run:
curl http://localhost:8000/healthExpected response:
{
"status": 200,
"code": "HTTP_200_OK",
"message": "Service is healthy.",
"data": {
"service": "Transcription Pipeline",
"version": "1.0.0",
"model": "base",
"device": "cpu"
}
}With the service running, open either of these URLs in your browser:
- Swagger UI → http://localhost:8000/docs
- ReDoc → http://localhost:8000/redoc
Transcribe an uploaded audio file. Returns the full transcription text and per-segment timestamps.
| Field | Type | Required | Description |
|---|---|---|---|
file |
binary (multipart/form-data) | ✅ | WAV or MP3 audio file, maximum 500 MB |
| Format | Accepted MIME Types |
|---|---|
| WAV | audio/wav, audio/x-wav |
| MP3 | audio/mpeg, audio/mp3 |
curl -X POST http://localhost:8000/transcribe `
-F "file=@C:\path\to\your\recording.mp3"import httpx
with open(r"C:\path\to\your\recording.mp3", "rb") as f:
response = httpx.post(
"http://localhost:8000/transcribe",
files={"file": ("recording.mp3", f, "audio/mpeg")},
)
data = response.json()
print(data["data"]["full_text"])
for segment in data["data"]["segments"]:
print(f"[{segment['start']:.2f}s → {segment['end']:.2f}s] {segment['text']}"){
"status": 200,
"code": "HTTP_200_OK",
"message": "Audio transcribed successfully.",
"data": {
"filename": "recording.mp3",
"duration_seconds": 125.48,
"language": "en",
"language_probability": 0.9987,
"full_text": "Hello, this is a sample transcription of the uploaded audio file.",
"segments": [
{
"segment_id": 0,
"start": 0.0,
"end": 3.48,
"text": "Hello, this is a sample transcription",
"confidence": 0.9241
},
{
"segment_id": 1,
"start": 3.48,
"end": 6.92,
"text": "of the uploaded audio file.",
"confidence": 0.9108
}
],
"chunk_count": 5
}
}| Field | Type | Description |
|---|---|---|
filename |
string | Original uploaded filename |
duration_seconds |
float | Total audio duration in seconds |
language |
string | Detected or forced language as ISO 639-1 code (e.g. en) |
language_probability |
float | Confidence of language detection, range 0.0 – 1.0 |
full_text |
string | Complete transcription as a single concatenated string |
segments |
array | List of timed transcription segments |
segments[].segment_id |
integer | Zero-based index of this segment |
segments[].start |
float | Segment start time in seconds from the beginning of the file |
segments[].end |
float | Segment end time in seconds from the beginning of the file |
segments[].text |
string | Transcribed text for this segment |
segments[].confidence |
float | Transcription confidence score, range 0.0 – 1.0 |
chunk_count |
integer | Number of 30-second chunks the file was divided into |
| HTTP Status | Code | Cause |
|---|---|---|
400 |
HTTP_400_BAD_REQUEST |
Uploaded file is empty |
413 |
HTTP_413_REQUEST_ENTITY_TOO_LARGE |
File exceeds 500 MB |
422 |
HTTP_422_UNPROCESSABLE_ENTITY |
Unsupported format or undecodable audio |
500 |
HTTP_500_INTERNAL_SERVER_ERROR |
Unexpected server-side error |
Lightweight liveness check. Confirms the service is running and returns the active model configuration.
curl http://localhost:8000/healthThe model is set via WHISPER_MODEL_SIZE in .env. It is downloaded
automatically on first run and cached at:
C:\Users\<YourName>\.cache\huggingface\hub
| Model | Disk Size | RAM Usage | Speed on CPU | Accuracy |
|---|---|---|---|---|
tiny |
~75 MB | ~125 MB | Very fast | Low |
base |
~145 MB | ~210 MB | Fast | Good — recommended default |
small |
~465 MB | ~600 MB | Moderate | Better |
medium |
~1.5 GB | ~2 GB | Slow | High |
large-v3 |
~3 GB | ~4 GB | Very slow | Best — GPU recommended |
For Windows CPU with limited RAM, base is the best starting point.
Switch to small if accuracy is more important than speed.
transcription-pipeline/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI application, lifespan, health check
│ ├── config/
│ │ ├── __init__.py
│ │ └── settings.py # Pydantic BaseSettings — reads from .env
│ ├── api/
│ │ ├── __init__.py
│ │ └── routes/
│ │ ├── __init__.py
│ │ └── transcription.py # POST /transcribe route handler
│ ├── audio/
│ │ ├── __init__.py
│ │ └── processor.py # Audio validation, FFmpeg setup, conversion, chunking
│ ├── transcription/
│ │ ├── __init__.py
│ │ └── service.py # Faster-Whisper model lifecycle and inference pipeline
│ ├── models/
│ │ ├── __init__.py
│ │ └── schemas.py # Pydantic request and response schemas
│ └── utils/
│ ├── __init__.py
│ └── file_utils.py # Temp file creation, cleanup helpers
├── .env # Your local configuration (do not commit)
├── .env.example # Configuration template
├── requirements.txt # All Python dependencies with pinned versions
├── Dockerfile # Container build instructions
└── README.md # This file
pydub cannot find FFmpeg. This warning appears at startup and will cause
transcription to fail with [WinError 2].
Step 1 — Find the exact path of your FFmpeg installation:
where ffmpegStep 2 — Set FFMPEG_PATH in .env to that exact path:
FFMPEG_PATH=C:\Users\YourName\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-8.1.2-full_build\bin\ffmpeg.exeStep 3 — Restart the service.
Same cause as above. FFmpeg is installed but the terminal running the service
cannot locate it. Set FFMPEG_PATH in .env as shown above.
WHISPER_LANGUAGE in .env is being read as an empty string "" instead
of None. This happens when the line is present but has no value.
The setting is already handled defensively in the code, but if you see this
error ensure your .env has:
# Option 1 — auto-detect (recommended)
WHISPER_LANGUAGE=
# Option 2 — force a specific language
WHISPER_LANGUAGE=enDo not add quotes around the value.
Cause: WHISPER_COMPUTE_TYPE=int8 in .env. On Windows CPU, int8
causes CTranslate2 to hang silently during model initialisation.
Fix — change to float32 in .env:
WHISPER_COMPUTE_TYPE=float32Restart the service. Startup should complete within 30 seconds.
The service was started with the wrong command.
# Wrong
python app/main.py
# Correct — always use the -m flag from the project root
python -m app.mainThe server did not complete startup. Check the PowerShell window running the service for error messages. Common causes and fixes:
| Cause | Fix |
|---|---|
| FFmpeg not found | Set FFMPEG_PATH in .env |
int8 compute type hanging |
Set WHISPER_COMPUTE_TYPE=float32 in .env |
| Port 8000 already in use | Change PORT in .env or stop the process using port 8000 |
| Virtual environment not activated | Run .venv\Scripts\activate first |
Check if port 8000 is already in use:
netstat -ano | findstr :8000If a process is listed, stop it or change PORT=8001 in .env.
The model is cached by HuggingFace Hub after the first download. If it re-downloads every time, the cache directory may be missing or unwritable.
Check the cache location:
# Open the HuggingFace cache directory in Explorer
explorer "$env:USERPROFILE\.cache\huggingface\hub"If the directory is empty or missing, the download will happen again on the next startup. This is normal for a first run only.
Expected behaviour on CPU. Options to improve speed:
- Use a smaller model — change
WHISPER_MODEL_SIZE=tinyin.env - Reduce
WHISPER_BEAM_SIZE=1in.envfor fastest decoding - If you have an NVIDIA GPU, set
WHISPER_DEVICE=cudaandWHISPER_COMPUTE_TYPE=float16in.env