The Whisper 25MB Limit Isn't a Time Limit. Re-encode First.

Your transcription job dies on a 413 the first time someone drops in a 90-minute webinar. Every tutorial answers the same way: split the file, transcribe the parts, concatenate the text. That's the right call maybe a third of the time. The other two thirds, you've added timestamp drift to a file that fit in one request.
Quick answer: The Whisper 25MB limit caps the whole HTTP request body at 26,214,400 bytes, not the audio duration, so the first fix is re-encoding, not splitting. Runningffmpeg -i input.mp4 -vn -ac 1 -ar 16000 -c:a libopus -b:a 16k speech.oggputs roughly 3.4 hours of speech under the cap in one file. If it's still too long, segment it with-f segment -segment_time 3000 -c copyand add each chunk's measured duration as a cumulative offset to the returned timestamps before you write the SRT. If you'd rather not run an encoder yourself, FFmpeg Micro does the extract and segment in one API call with no local binary.
25 MB means 26,214,400 bytes, and the multipart envelope counts
The OpenAI transcription endpoints measure mebibytes, not megabytes, and the error says so exactly:
{"error": {"message": "Maximum content size limit (26214400) exceeded (26228079 bytes read)", "type": "server_error", "param": null, "code": null}}
26,214,400 is 25 × 1024 × 1024. The second number is bytes read off the wire, including multipart boundaries, field names, and the filename. A 26,214,000-byte file still fails because the envelope pushes it over. Target 24 MB. That margin costs four minutes of audio at 32 kbps and kills a class of flaky-on-Tuesday bugs.
The cap is also why OpenAI's docs answer "longer inputs" with a PyDub snippet. PyDub decodes to raw PCM in memory before slicing, so a three-hour stereo source becomes roughly 1.9 GB of RAM. Fine on a laptop, fatal in an n8n container or a 512 MB Lambda. FFmpeg streams instead, handling the same file in a few hundred megabytes.
Re-encode before you split, because most long files never need splitting
Speech transcription doesn't need your 192 kbps stereo AAC track. Whisper resamples everything to 16 kHz mono internally, so every bit above that is budget thrown at the size cap for no accuracy gain. What 24 MB buys:
| Audio settings | Bitrate | Fits in 24 MB |
|---|---|---|
| 16 kHz mono Opus in Ogg | 16 kbps | ~3.4 hours |
| 16 kHz mono Opus in Ogg | 24 kbps | ~2.3 hours |
| 16 kHz mono MP3 | 32 kbps | ~105 minutes |
| 44.1 kHz stereo MP3 | 128 kbps | ~26 minutes |
| Source AAC copied from an MP4 | 192 kbps | ~17 minutes |
| 16 kHz mono WAV (PCM s16) | 256 kbps | ~13 minutes |
The WAV row is the trap. Local whisper.cpp builds want 16 kHz mono PCM, so people carry the habit to the API and cut their ceiling to thirteen minutes. The API takes flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, and webm. Opus in Ogg is the smallest that still transcribes cleanly.
ffmpeg -i podcast.mp4 -vn -ac 1 -ar 16000 -c:a libopus -b:a 16k speech.ogg
-vn drops the video, -ac 1 downmixes to mono, and -b:a 16k is where the savings come from. Skip -vn and you upload the H.264 track too, usually 95% of the file. If FFmpeg says the stream map matches no streams, the source has no audio track, which is a different problem.
At 16 kbps mono, expect a small word error rate penalty on accented or noisy recordings. Clean podcast audio holds up. If accuracy matters more than request count, use 24 kbps and accept two chunks.
Why `-segment_time 600 -c copy` is the wrong default
The ten-minute chunk is folklore from when everybody uploaded source-bitrate audio. Two things break when you paste it into a modern pipeline.
First, -c copy preserves the input bitrate. Aim the segment muxer at the original MP4 expecting 32 kbps chunks and you get 192 kbps chunks, so your size math is off by 6x in the dangerous direction. Re-encode first, segment second.
Second, once the audio really is 32 kbps mono, a 600-second chunk is 2.4 MB. That fires eighteen API requests at a three-hour file where two would do, and each of the seventeen seams can cut a word in half or slip the offset bookkeeping.
ffmpeg -i speech.ogg \
-f segment -segment_time 3000 -reset_timestamps 1 \
-segment_list segments.csv -segment_list_type csv \
-c copy chunk_%03d.ogg
Fifty-minute chunks at 32 kbps land near 12 MB, half the cap, with room for a long trailing segment. -reset_timestamps 1 starts each chunk at zero, which is what you want because the API returns chunk-relative timestamps anyway. -segment_list writes the boundaries:
chunk_000.ogg,0.000000,3000.060000
chunk_001.ogg,3000.060000,6000.120000
chunk_002.ogg,6000.120000,7842.384000
Those end values are not 3000.000000, and that gap is what breaks a naive offset.
The cumulative offset is where the published recipes stop
Almost every chunking guide computes the offset as chunk_index * segment_time. That's wrong in a way that only shows up at the end of a long file. The segment muxer cuts on the first encoded frame boundary at or after your requested time, so every chunk runs slightly longer than you asked. An MP3 frame at 16 kHz is 1152 samples, about 72 ms. Over six chunks that's up to 430 ms of error, all of it late, and captions visibly lag by the last twenty minutes.
Accumulate measured durations instead. They sum to the source duration exactly, so drift goes to zero.
import glob, subprocess
from openai import OpenAI
def duration(path):
out = subprocess.run(
["ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=nw=1:nk=1", path],
capture_output=True, text=True, check=True)
return float(out.stdout.strip())
client = OpenAI()
offset, words, carry = 0.0, [], ""
for path in sorted(glob.glob("chunk_*.ogg")):
with open(path, "rb") as f:
r = client.audio.transcriptions.create(
model="whisper-1",
file=f,
response_format="verbose_json",
timestamp_granularities=["word"],
prompt=carry[-800:],
)
for w in r.words:
words.append({"word": w.word,
"start": w.start + offset,
"end": w.end + offset})
carry = r.text
offset += duration(path)
Two details earn their keep. The prompt parameter carries the previous chunk's tail forward, so proper nouns survive the seam instead of being re-guessed; it takes up to 224 tokens and truncates silently past that. The model is pinned to whisper-1 on purpose: gpt-4o-transcribe and gpt-4o-mini-transcribe return json or text only, so asking them for verbose_json with timestamp granularities gets you a 400 and no timings.
The curl equivalent, if you're wiring this from n8n or Make:
curl -s https://api.openai.com/v1/audio/transcriptions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-F file=@chunk_000.ogg \
-F model=whisper-1 \
-F response_format=verbose_json \
-F "timestamp_granularities[]=word"
From there the SRT is mechanical: group words into cues, renumber from 1 across the whole file rather than per chunk, format as HH:MM:SS,mmm. If those captions go back into a video, the burn-in step has its own failure modes.
The same prep without a local FFmpeg install
Everything above assumes FFmpeg on the machine running the pipeline, and that's the assumption that breaks where this work happens. n8n disabled the Execute Command node by default in v2.0, its distroless images ship without a shell, and Lambda and Cloud Run want a layer and a timeout babysat for two commands.
| Step | Local FFmpeg | FFmpeg Micro |
|---|---|---|
| Audio out of the MP4 | Version-pinned local binary | Post the source URL |
| Downmix to 16 kbps mono | One command plus a host | Job parameter |
| Segment under 24 MB | Second command plus temp disk | Job parameter |
FFmpeg Micro takes the source URL, runs the extract and segment as a job, and hands back chunk URLs you feed straight to the transcription endpoint. No servers to run, nothing to version-pin, and the free tier covers a real podcast end to end. The offset stitch stays yours, since that's application logic, not media work.
Common pitfalls
The chunking mistakes that cost real debugging time are the quiet ones, not the ones that fail loudly:
- Splitting mid-word. A hard cut at 3000 seconds lands mid-syllable about as often as not. Run
ffmpeg -i speech.ogg -af silencedetect=n=-35dB:d=0.4 -f null -first and pick cut points from the silence list, or use a 1-second overlap and dedupe repeated words. - Not persisting per-chunk results. A 429 on chunk 7 of 9 shouldn't cost you the first six transcriptions. Write each chunk's JSON to disk keyed by filename, and keep the retry idempotent.
- Chunk order from an unsorted glob.
chunk_10.oggsorts beforechunk_2.ogg. The%03din the output pattern keeps lexical and numeric sort in agreement. - Trusting the offset without checking the tail. Spot-check a word near the end of the source against the original. If it's late by a few hundred milliseconds, you're multiplying an index instead of summing durations.
When chunking is the wrong answer entirely
Splitting a recording destroys things no offset arithmetic gets back. Speaker diarization is the main one: chunk boundaries reset speaker identity, so "Speaker 1" in chunk 3 has no relationship to "Speaker 1" in chunk 1, and stitching them needs a voice-embedding pass you don't want to build. For reliable speaker labels across a two-hour interview, a managed transcription pipeline that ingests the whole file fits better.
Chunking is also wrong when the audio isn't speech. A 16 kbps mono downmix is right for a podcast and destructive for music, multi-track recordings, or anything where the stereo field matters.
If you'd rather not call a transcription API, FFmpeg 8.0 shipped a native whisper filter that runs whisper.cpp in the filter graph and emits SRT directly, with real caveats around builds and model files. No size cap applies, because nothing is uploaded.
FAQ
What is the file size limit for the OpenAI Whisper API?
The OpenAI transcription endpoints accept request bodies up to 26,214,400 bytes, or 25 mebibytes. The limit covers the whole multipart request, not just the audio file, so target 24 MB of audio.
Can I increase the Whisper 25MB limit on a paid plan?
No plan tier raises the 25 MB cap. OpenAI Developer Community thread 566754 has been asking since 2024 and runs past thirty replies with no change; threads 267384 and 693285 repeat the request. The supported paths are re-encoding smaller or splitting into multiple requests.
How long can one audio file be before it hits the Whisper 25MB limit?
Duration depends on bitrate, not on any time limit. At 16 kbps mono Opus, roughly 3.4 hours of speech fits under 24 MB; at 32 kbps mono MP3, about 105 minutes; at a 192 kbps AAC screen-recording track, about 17 minutes.
Do Whisper timestamps line up with the original file after chunking?
Chunked Whisper transcripts return timestamps relative to each chunk, starting at zero, so they don't line up until you add a cumulative offset. Sum the measured duration of every preceding chunk with ffprobe instead of multiplying the chunk index by your requested segment time, because the segment muxer cuts on frame boundaries and each chunk runs slightly long.
How do I split audio for the Whisper API without cutting words in half?
Run FFmpeg's silencedetect filter over the audio first and place segment boundaries inside detected silences, or cut on a fixed interval with a one-second overlap and drop duplicated words where the transcripts meet. Passing the previous chunk's trailing text into the next request's prompt helps the model recover names across the seam.
Extracting the audio, downmixing it, and segmenting it under 24 MB is two FFmpeg commands plus a machine to run them on, or one API call and neither. Sign up free and run it on a real podcast to see which chunk count you end up needing.
About Javid Jamae
Founder & CEO at FFmpeg Micro
Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.
You might also like

Your AI Dubbing Workflow Isn't Broken. The Dub Just Runs Long.
An AI dubbing workflow returns a translated track longer than the video. Measure both with ffprobe, fix the drift with atempo or apad, then re-mux cleanly.

Fixed music volume is a compromise. FFmpeg audio ducking isn't.
FFmpeg audio ducking with sidechaincompress: the working filtergraph, the input order and format rules that break it, and the same mix as one API call.

FFmpeg Can't Auto Reframe Video to Vertical. Do This Instead
FFmpeg has no auto reframe video vertical mode. Four crop strategies that ship, from a fixed offset to detection-driven tracking with smoothing and deadbands.
Ready to process videos at scale?
Start using FFmpeg Micro's simple API today. No infrastructure required.
Get Started Free