ffmpegsubtitlesvideo-captions

Can't extract subtitles from MP4? The real job is transcription.

··10 min read
Can't extract subtitles from MP4? The real job is transcription.

To extract subtitles from an MP4, first run ffprobe -v error -select_streams s -show_entries stream=index,codec_name -of csv=p=0 input.mp4. If that prints a line like 2,mov_text, pull the track out with ffmpeg -i input.mp4 -map 0:s:0 -c:s srt subs.srt and you have your SRT. If it prints nothing at all, the MP4 holds no subtitle stream, no extraction command will ever find one, and the job you actually need is transcription: run the audio through a speech-to-text pass that outputs SRT.

The empty result is the common one. Social downloads, Zoom and Loom screen recordings, and AI-generated clips from Veo or Sora almost never carry a subtitle stream. The words you can read on screen were drawn into the pixels at render time. They were never text in the container, so there's nothing to copy out.

The ffprobe check that decides which job you're doing

One command tells you which of three different operations you're facing, and it costs nothing to run. -select_streams s is the documented way to restrict ffprobe's output to subtitle streams only (ottverse), so an empty result is a real answer and not a failure.

ffprobe -v error -select_streams s \
  -show_entries stream=index,codec_name \
  -of csv=p=0 input.mp4

Three possible outputs, three different next steps:

ffprobe printsWhat you're holdingThe operation
`2,mov_text` or `2,subrip` / `ass`A real text trackStream convert to SRT
`2,hdmv_pgs_subtitle` or `2,dvd_subtitle`Timed bitmapsOCR pass
nothingNo subtitle streamTranscribe the audio

Keep -v error on. Without it ffprobe prints the full banner and format dump, and in a script that makes "no subtitle streams" look identical to "lots of output I didn't parse."

When it's a text track: convert, don't copy

A text subtitle track comes out of the container without decoding a single video frame, which is why this finishes in under a second on a two-hour file. The command maps the first subtitle stream and asks for SRT on the way out:

ffmpeg -i input.mp4 -map 0:s:0 -c:s srt subs.srt

Use -c:s srt, not -c:s copy. MP4 can carry essentially one text subtitle codec that FFmpeg will mux: mov_text, the 3GPP timed-text (tx3g) format defined in MPEG-4 Part 17. A SubRip or ASS file gets transcoded to mov_text on the way into an MP4, and for MP4 output FFmpeg's subtitle options are effectively copy or mov_text and nothing else (Wikibooks). So copy writes raw mov_text payload into a file named .srt, which is why people end up with a zero-byte or garbage SRT and assume the file is corrupt. -c:s srt runs the actual conversion. The no-re-encode details, including MKV files with four language tracks and how to pick one, are in Extract Subtitles from MKV and MP4 with FFmpeg, No Re-encoding.

When it's PGS or VobSub: you're holding pictures

PGS (Blu-ray) and VobSub (DVD) subtitles are images. Each cue is a bitmap with on/off timestamps, so turning them into SRT means reading characters off pictures. That's OCR (subtitles-ai-ocr). FFmpeg will happily extract the stream as .sup or .idx/.sub, but you still have pictures. If you try -c:s srt on a PGS stream you get Subtitle encoding currently only possible from text to text or bitmap to bitmap.

This case is rare in MP4 and common in MKV rips. MP4 itself isn't really a bitmap-subtitle container, so if you're here you're probably working with a Blu-ray remux.

When ffprobe prints nothing: the extractor was never the answer

An empty subtitle stream list means the container holds no subtitle stream, full stop (this ffprobe stream-listing gist shows the same pattern for audio and video). The matching FFmpeg error is Stream map '0:s:0' matches no streams, and that message gets misread as corruption constantly. Your file is fine. It just doesn't contain what you're asking for.

Here's where the file probably came from:

  • Downloaded with yt-dlp without the right flags. Downloading a video does not download its captions. yt-dlp needs --write-subs for a sidecar file and --embed-subs to put a track in the container (yt-dlp docs). If you grabbed the MP4 alone, the captions stayed on the platform.
  • A Zoom cloud recording. Zoom does transcribe, but it hands the text back out of band as a separate .vtt download, not inside the MP4 (University of Michigan IT). Check the recording page before you probe the file.
  • A short with auto-captions. Those captions are rendered into the picture by the editor, so the downloaded file carries no track. The visible words and the file's contents have nothing to do with each other.
  • An AI-generated clip. Veo and Sora output is encoded video and audio. There's no caption authoring step in the pipeline, so there's no track to find.

For all four, transcription from the audio is what produces the SRT you came for. It's the real job here, and the output beats what OCR would have given you, since the word timings come from the audio itself rather than from when text appeared on screen.

Burned-in text is OCR territory, and this isn't an OCR service

If the words are painted into the frames and the audio doesn't contain them (a chart label, an on-screen name card, a translation of dialogue in another language), the only way to recover them is to read the pixels. That's OCR, frame by frame, and the open-source route is a local Tesseract or PaddleOCR pipeline. videocr is a Python library that OCRs burned-in subtitles and emits timed SRT; VideOCR wraps PaddleOCR in a desktop GUI across 200+ languages. Both run locally, both need per-video tuning for crop region and confidence threshold, and both are the right tool for that job.

FFmpeg Micro does not do OCR. Saying otherwise would waste your time, and the honest boundary matters here because most people who land on "extract hardcoded subtitles" don't actually need OCR. If the burned-in text is a caption of the spoken audio (which it is for nearly every short-form video, course clip and talking-head recording), transcription beats OCR on accuracy, on timing precision, and on the cost of getting it working: no crop region to tune, no font to fight, no frame-by-frame pass over 1,800 frames per minute of video.

OCR wins in exactly one scenario: the text on screen is not in the audio. Use it there and nowhere else.

The transcription path, in code and in n8n

Transcription endpoints hand back subtitle files directly, which is the part that surprises people who have been fighting extraction commands. OpenAI's speech-to-text API accepts response_format values of srt and vtt (OpenAI docs), so one request returns the exact SRT the extractor refused to produce. You'll usually want to pull the audio out first, both to get under the 25 MB request cap and to stop paying to upload video bytes nobody reads:

ffmpeg -i input.mp4 -vn -c:a libmp3lame -b:a 128k audio.mp3

FFmpeg 8.0 added a native whisper audio filter that can emit SRT straight from the filter graph (FFmpeg filter docs). It's genuinely useful, with one catch the release coverage skips: it only exists in a build configured with --enable-whisper, and you supply a local ggml model file. No distro package ships that today, so on a stock apt install ffmpeg the filter isn't there.

That compile requirement is where this stops being runnable for most readers, and it's completely unrunnable if you're building in n8n Cloud or Make, where there's no shell and no custom binaries. More than 300 builders already run FFmpeg Micro jobs from Make or n8n for exactly that reason. If what you want is captions on the video rather than a file to archive, the Auto Captions blueprint runs the whole path: transcribe the audio, show you the text for review, burn the subtitles in. For the audio-extraction half as an API call, the extract audio recipe has the request shape, and ffprobe: how to inspect video metadata runs the same triage probe as a single POST against a URL, so you can sort a few hundred files into text-track, bitmap and no-track buckets without downloading any of them.

One billing detail that matters when you're testing on long recordings: FFmpeg Micro meters the duration of video processed, per second, with no rounding up to the minute. Trim 30 seconds out of a one-hour webinar and you're billed for 30 seconds. Failed jobs aren't billed at all, which makes the triage pass cheap to get wrong.

FAQ about extracting subtitles from MP4 files

ffprobe printed nothing for -select_streams s. Is my file broken?

An empty result from ffprobe -select_streams s means your MP4 contains no subtitle stream, and that is a valid file, not a corrupt one. Run the same command with -select_streams v or -select_streams a to confirm the file reads fine. The video and audio streams will list normally.

Why does -map 0:s:0 error with "matches no streams"?

Stream map '0:s:0' matches no streams means FFmpeg looked for a first subtitle stream and your file doesn't have one. It is not a corruption error and not a syntax error. Probe the file with -select_streams s first and branch on the result instead of assuming the map will succeed.

Can I extract the subtitle file if it's embedded into an MP4?

Yes, if the embedded subtitles are a text track. Run ffmpeg -i input.mp4 -map 0:s:0 -c:s srt subs.srt and you get a standard SRT out in about a second, with no video re-encoding. If ffprobe shows no subtitle stream, then nothing is embedded and transcription is the only way to produce an SRT.

Where did my subtitles go when I downloaded the video?

Captions live on the platform as a separate asset, so downloading the video file leaves them behind unless you ask for them. yt-dlp needs --write-subs or --embed-subs; Zoom publishes the transcript as a separate .vtt on the recording page. Auto-captions that were burned into the picture by an editor have no downloadable version at all.

Should I use OCR or transcription when the on-screen text matches the spoken audio?

Use transcription when the on-screen text matches the spoken audio. It reads word timings from the audio itself rather than inferring them from when pixels changed, needs no crop region or font tuning, and doesn't degrade on moving backgrounds or stylized caption fonts. Save OCR for text that appears on screen but is never spoken.

That one ffprobe line is the whole decision, and in the no-subtitle-stream branch it's also the end of the road for every extraction command you'll find. Sign up free if you'd rather hand that branch to an API: 200 tokens to start, about 33 minutes of video, no card. Nothing to install, nothing to keep patched, and it's callable from your code, from n8n, Make and Zapier, or from an AI agent.

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.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

Skip the command line

The Auto Captions blueprint transcribes your video and burns the captions in. You just review the transcript.

Run it (free)