HomeBlog → Speech to text in Python: from one file to a working script

Speech to text in Python: from one file to a working script

Published 2026-08-05 · 6 min read

Speech-to-text in Python is four lines of code. Everything else in this guide is about the parts that are not obvious: which response format to ask for, what to do when the file is too big, and why your first script will be slower than it needs to be.

Install and first request

One dependency — the official OpenAI SDK. The API is protocol-compatible, so no special client is needed:

pip install openai
from openai import OpenAI

client = OpenAI(base_url="https://voicesscribe.com/v1", api_key="your key")

with open("audio.mp3", "rb") as f:
    result = client.audio.transcriptions.create(model="whisper-1", file=f)

print(result.text)

That is the whole thing. Keep the key in an environment variable rather than in the source:

import os
client = OpenAI(base_url="https://voicesscribe.com/v1", api_key=os.environ["VS_KEY"])

Choosing the response format

The default returns an object with a text field. Three other formats matter:

FormatWhat you getUse it when
jsonObject with .textDefault; you only need the words
verbose_jsonSegments, timings, detected languageTimestamps, chapters, quality checks
srt / vttReady subtitle file as a stringCaptions for video
textBare string, no wrapperPiping into another tool

With verbose_json the response also carries the detected language and its probability, which is the cheapest quality signal you will get:

r = client.audio.transcriptions.create(
    model="whisper-1", file=f, response_format="verbose_json",
)

for s in r.segments:
    print(f"[{s.start:6.1f}] {s.text.strip()}")

if r.language_probability < 0.6:
    print("suspicious recording — worth a manual check")

Handling errors properly

The SDK raises typed exceptions, so handling them is straightforward. The distinction that matters: some errors are worth retrying, some will fail identically forever.

from openai import APIStatusError, APIConnectionError

try:
    r = client.audio.transcriptions.create(model="whisper-1", file=f)
except APIConnectionError:
    ...                      # network — retrying makes sense
except APIStatusError as e:
    if e.status_code == 401:
        raise RuntimeError("invalid API key")
    if e.status_code == 413:
        raise RuntimeError("file over 25 MB — re-encode or split it")
    if e.status_code == 429:
        ...                  # rate limit or minutes — back off
    raise

A 400 usually means the file is empty or corrupted. Check that it opens in a player before blaming the API.

Files larger than 25 MB

The request limit is 25 MB. Two ways past it, in order of preference.

Re-encode first. Most oversized files are oversized because they are wav or high-bitrate stereo. Mono at 16 kHz is what the model uses internally anyway:

ffmpeg -i input.wav -ac 1 -ar 16000 -b:a 48k output.mp3

That turns an hour of audio into roughly 25 MB — most files stop needing to be split at all.

Then split, if still needed. Cut on pauses rather than at fixed minutes, so sentences stay intact:

ffmpeg -i long.mp3 -f segment -segment_time 1800 -c copy part%03d.mp3

Making it faster

The first script everyone writes processes files one at a time and spends almost all of its wall-clock waiting on the network. Transcription is IO-bound, so threads fix it:

from concurrent.futures import ThreadPoolExecutor

def transcribe(path):
    with open(path, "rb") as f:
        return client.audio.transcriptions.create(model="whisper-1", file=f).text

with ThreadPoolExecutor(max_workers=4) as pool:
    texts = list(pool.map(transcribe, paths))

Set max_workers from your plan's requests-per-second limit, not from the number of CPU cores. Going wider than the limit converts successful requests into 429s.

Improving accuracy on your own vocabulary

The single most underused parameter is prompt. It does not appear in the output — it biases recognition towards the words you list:

r = client.audio.transcriptions.create(
    model="whisper-1", file=f,
    prompt="Products: Kestrel, Hawknest. Speakers: Duarte, Ivanova.",
    language="en",
)

Twenty to forty terms that actually occur in your audio work well; a paragraph of general description does nothing. Adding language when the audio is single-language removes detection mistakes on short clips.

Mistakes that cost the most time

Try it on your own recordings. Sign-up takes a minute, and the free minutes are enough to judge the quality.

Get a free API key

Frequently asked questions

Do I need a special Python library?

No, the official openai package works as is — the API is protocol-compatible. Only the base URL and the key differ from the OpenAI defaults.

Which audio formats does Python need to prepare?

None specifically: ogg, opus, mp3, wav, m4a and webm are accepted as they are. Converting to mono 16 kHz mp3 is only about staying under the 25 MB request limit.

How do I get timestamps in Python?

Ask for response_format=verbose_json — the response contains segments with start and end times, plus the detected language and its probability.

Can I transcribe many files in parallel?

Yes, with a ThreadPoolExecutor. Transcription waits on the network, so threads are the right tool; size the pool below your plan's requests-per-second limit.

What should I do about files over 25 MB?

Re-encode to mono 16 kHz first — that alone fixes most cases. If the file is still too large, split it on pauses and merge the results.

Related reading