Skip to main content

Speech-to-text with Whisper

whisper-large-v3-turbo is accessible via the same /v1/audio/transcriptions endpoint as the OpenAI Whisper API, so any code written for OpenAI works as a drop-in replacement by changing base_url. See the Whisper model page‍ for supported formats, language codes, and inference parameters.

For speaker separation, word timestamps, subtitles, or recordings longer than 30 minutes, see Speech-to-text with Cohere Transcribe‍.

Setup​

user@local $ pip install openai
user@local $ export OPENAI_API_KEY="sk-…"

For chunking long or large files (over 30 minutes or over 25 MB):

user@local $ pip install pydub
# pydub requires ffmpeg
user@local $ brew install ffmpeg # macOS
user@local $ apt-get install ffmpeg # Debian/Ubuntu

Basic transcription​

import os
from openai import OpenAI

client = OpenAI(base_url="https://llm.aihosting.mittwald.de/v1")

with open("recording.mp3", "rb") as f:
result = client.audio.transcriptions.create(
model="whisper-large-v3-turbo",
file=f,
language="en", # Always set explicitly — default is "de"
response_format="json",
temperature=0,
)

print(result.text)

Multiple languages​

files_and_languages = [
("meeting_de.mp3", "de"),
("interview_fr.wav", "fr"),
("podcast_en.ogg", "en"),
("lecture_ja.flac", "ja"),
]

for filename, lang in files_and_languages:
with open(filename, "rb") as f:
result = client.audio.transcriptions.create(
model="whisper-large-v3-turbo",
file=f,
language=lang,
response_format="json",
temperature=0,
)
print(f"[{lang}] {result.text[:120]}")

Long or large files (> 30 minutes or > 25 MB)​

The API accepts files up to 25 MB and up to 30 minutes of audio. Both limits apply independently. Which one you hit first depends on the bitrate: 25 MB hold about 52 minutes of 64 kbit/s mono mp3, but only about 27 minutes of 128 kbit/s stereo. The example below splits longer recordings at silence points with pydub, packs the pieces back into chunks of up to 25 minutes, and re-encodes each chunk as mono mp3, so every request stays below both limits and no words are cut mid-utterance:

from io import BytesIO

from pydub import AudioSegment
from pydub.silence import split_on_silence

MAX_CHUNK_MS = 25 * 60 * 1000 # Stay below the 30-minute duration limit


def transcribe_large_file(path: str, language: str = "en") -> str:
"""Transcribe a file of any size or length by splitting at silence points."""
audio = AudioSegment.from_file(path)

# Split at pauses longer than 700 ms with silence below -40 dBFS
pieces = split_on_silence(
audio,
min_silence_len=700,
silence_thresh=-40,
keep_silence=300, # Keep 300 ms of silence at each edge for context
seek_step=10, # Check every 10 ms instead of every 1 ms, about 10x faster
) or [audio]

# A passage without pauses can still exceed the limit: cut it hard
bounded: list[AudioSegment] = []
for piece in pieces:
bounded.extend(piece[i: i + MAX_CHUNK_MS] for i in range(0, len(piece), MAX_CHUNK_MS))

# Pack consecutive pieces into as few requests as possible
chunks: list[AudioSegment] = []
for piece in bounded:
if chunks and len(chunks[-1]) + len(piece) <= MAX_CHUNK_MS:
chunks[-1] += piece
else:
chunks.append(piece)

transcripts: list[str] = []
for chunk in chunks:
buf = BytesIO()
# Mono at 64 kbit/s: 25 minutes come to about 12 MB, well below 25 MB
chunk.set_channels(1).export(buf, format="mp3", bitrate="64k")
buf.seek(0)
buf.name = "chunk.mp3" # openai SDK reads the .name attribute for MIME type

result = client.audio.transcriptions.create(
model="whisper-large-v3-turbo",
file=buf,
language=language,
response_format="json",
temperature=0,
)
transcripts.append(result.text)

return " ".join(transcripts)


full_text = transcribe_large_file("long_interview.mp3", language="de")
print(full_text)

Transcription + summarisation pipeline​

Chain Whisper with a chat model to go directly from audio to a written summary:

def transcribe_and_summarise(audio_path: str, language: str = "en") -> dict:
# Step 1: transcribe
with open(audio_path, "rb") as f:
result = client.audio.transcriptions.create(
model="whisper-large-v3-turbo",
file=f,
language=language,
response_format="json",
temperature=0,
)
transcript = result.text

# Step 2: summarise
summary_resp = client.chat.completions.create(
model="Qwen3.6-35B-A3B-FP8",
messages=[
{
"role": "system",
"content": "Summarise the transcript concisely. Use bullet points for key topics.",
},
{"role": "user", "content": transcript},
],
temperature=0.7,
max_tokens=512,
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
)

return {
"transcript": transcript,
"summary": summary_resp.choices[0].message.content,
}


output = transcribe_and_summarise("team_standup.mp3", language="en")
print("Transcript:\n", output["transcript"])
print("\nSummary:\n", output["summary"])

Drop-in replacement for OpenAI​

Existing code written for the OpenAI Whisper API requires only a base_url change:

# Before (OpenAI)
client = OpenAI()

# After (mittwald AI Hosting)
client = OpenAI(base_url="https://llm.aihosting.mittwald.de/v1")

All other code — file handling, response_format, language, temperature — stays identical.

Timestamps and language detection with verbose_json​

response_format="verbose_json" returns per-segment timestamps, detected language, and total duration:

with open("recording.mp3", "rb") as f:
result = client.audio.transcriptions.create(
model="whisper-large-v3-turbo",
file=f,
language="en",
response_format="verbose_json",
temperature=0,
)

print(result.text)
print(f"Language: {result.language}")
print(f"Duration: {result.duration}s")
for seg in (result.segments or []):
print(f" [{seg.start:.1f}s–{seg.end:.1f}s] {seg.text}")

On this page