How to Extract Subtitles From a Video and Save Them as SRT
10 November 2026

How to Extract Subtitles From a Video and Save Them as SRT

Somewhere inside the video you’re holding, there’s a text track with every line of dialogue in it. Maybe it arrived with an MKV you bought, maybe it’s a caption track you added last month and never saved separately, or maybe a client sent an MP4 with subtitles embedded in the container. You need that text as a file you can edit, spell-check or translate, and retyping nine hundred lines isn’t a plan.

You can extract subtitles from a video in one FFmpeg command, and the job takes about ten seconds once you know which stream you’re pointing at. The trouble sits around that command: the track you picked isn’t the one you wanted, the output is zero bytes, or the timings come back a few milliseconds off and you can’t tell whether the tool lied.

I built a 60-second test clip for this article, 640×360 at 30 fps, with three subtitle tracks inside it (English SubRip, French SubRip and a styled ASS track) plus a second copy in MP4 where the subtitle stream is mov_text. Every command below ran against those files on FFmpeg 7.1.5, and every number is measured from that run.

What You’ll Learn

  • How to list subtitle streams with their codec and language before you extract subtitles.
  • The map syntax that picks a track by position or language, and why the absolute index fails.
  • When -c:s copy works, how to verify the output, and why cues can come back 21 ms late.

Three Kinds of Subtitle, One That Extracts Cleanly

Which type you have decides whether you can extract subtitles at all. A soft subtitle track is a separate stream holding characters plus timings, and it’s what nearly every MKV and most editor-exported MP4s carry. A bitmap track is also a stream, but it stores one small image per cue: Blu-ray PGS and DVD subpictures work this way, and FFmpeg has a PGS decoder and no PGS encoder. Burned-in subtitles are just pixels in the frames.

Type Stored as Text extraction? Codec names
Soft, text Characters and timestamps Yes, the normal job subrip, ass, mov_text, webvtt
Soft, bitmap One image per cue No, needs OCR after decoding hdmv_pgs_subtitle, dvd_subtitle, dvb_subtitle
Burned in Pixels in the frame No, no track to read it’s just video

Step 1: Find the Tracks First

Guessing costs more time than looking, and it’s the wrong way to extract subtitles. One ffprobe call lists every subtitle stream with its codec, language tag and default flag:

ffprobe -v error -select_streams s 
  -show_entries stream=index,codec_name:stream_tags=language,title 
  -show_entries stream_disposition=default,forced -of json subs.mkv

On my test file that returned three subtitle streams: 0:s:0 is subrip tagged eng with default: 1, 0:s:1 is subrip tagged fra, and 0:s:2 is the ass track, tagged eng but with no default flag, so a player won’t choose it on its own.

Two details matter when you extract subtitles. The 0:s:N numbering counts only subtitle streams, so it stays stable in a file carrying five audio tracks. The raw index in the probe output counts everything, and the same track gets a different number when extra audio is present. FFmpeg’s stream selection rules cover the rest of the map syntax. If the language tag reads und, nobody set one.

Step 2: Extract Subtitles With One Command

ffmpeg -i subs.mkv -map 0:s:0 -c:s srt english.srt

Three parts, three jobs, and it’s the whole recipe to extract subtitles once you know the track. -map 0:s:0 takes the first subtitle stream from input 0 and nothing else, which is why the output holds no video or audio. -c:s srt sets the subtitle codec and decides whether this works at all. The last argument is the destination, and 435 bytes with five cues is what came out.

For the other tracks, change the subtitle number, not the index from the probe: -map 0:s:1 gave 163 bytes with two cues, -map 0:s:2 gave the ASS track. To select by language, -map 0:s:m:language:fra produced a byte-identical file to -map 0:s:1, which is safer when you don’t control the track order. You can also extract subtitles from two tracks in one call:

ffmpeg -i subs.mkv -map 0:s:0 -c:s srt english.srt 
                  -map 0:s:1 -c:s srt french.srt

That wrote 435 and 163 bytes, the same two files as separate runs, and it’s the quickest way to extract subtitles from both languages. Leave -map out and FFmpeg picks the stream flagged default, which matched the English file byte for byte. Fine with one track, risky with five, where the default flag often marks the forced track or whichever language somebody added first. Two failures are worth recognising: with no subtitle stream in the file, -map 0:s:0 stops at exit 234 with Stream map '' matches no streams, and mapping by absolute index onto the audio stream gives exit 8, because the SRT muxer has no AAC encoder.

Step 3: Choose the Format on Purpose

Table of which subtitle codec and container combinations let you extract subtitles by copy or by transcode
Measured on FFmpeg 7.1.5: copy works only when the codec already matches the container.

Copying the stream feels safest, and it only works when the source codec already matches the destination container; the codec reference lists which subtitle codecs each muxer accepts. I ran all nine combinations: copy is the top half of the table, an explicit codec the bottom.

Source codec To .srt To .vtt To .ass
subrip, -c:s copy Works, 435 bytes Fails, 234, 0 bytes Fails, 234, 0 bytes
ass, -c:s copy Fails, 234, 0 bytes Fails, 234, 0 bytes Works, 725 bytes
mov_text, -c:s copy Fails, 234, 0 bytes Fails, 234, 0 bytes Fails, 234, 0 bytes
subrip, named codec srt, 435 bytes webvtt, 402 bytes ass, 1101 bytes
ass, named codec srt, 316 bytes webvtt, 150 bytes ass, 725 bytes
mov_text, named codec srt, 435 bytes webvtt, 402 bytes ass, 1101 bytes

The errors are specific once you know them: Unsupported subtitles codec: mov_text when you copy mov_text into SRT, and webvtt muxer supports only codec webvtt for any copy into a .vtt. All of those exit with 234 and still create the output path at zero bytes, so a script that checks whether the file exists reports success on nothing. Copy only when codec and container agree, otherwise name the codec. Either way you extract subtitles as plain text, so the transcode is string formatting with no frames involved.

Styling is what you pay when you extract subtitles into SRT. Converting the ASS track to SRT gave a cue reading <font face="DejaVu Sans" size="28" color="#ffff00"><b>{an8}<i>Italic text placed near the top</i></b></font>: the style became an HTML font tag, the italic survived as <i>, and the position override leaked into the visible text as a literal {an8}. Other overrides vanished (pos did), so a cue near the top of the frame lands wherever the player puts it. If styling matters, extract subtitles to ASS and stop there.

WebVTT has its own habits: FFmpeg drops the hours field under an hour, writing 00:02.000 --> 00:05.000 where SRT prints 00:00:02,000. That’s legal and browsers accept it, but a parser written for three colon groups will trip. HTML entities passed through untouched in both formats.

Step 4: Check What You Got

Checklist of four numbers to verify after you extract subtitles from a video file
Four checks, plus the MKV round-trip shift that makes the first cue worth measuring.

Exit code 0 means FFmpeg finished, not that the file you meant to extract subtitles into has cues. Four checks catch everything above, and they take a minute the first time: byte size (under a few hundred bytes for a feature is empty), cue count from grep -c -- '-->' english.srt, the first and last timestamps from head -2 and tail -4, and the character encoding, which should read UTF-8 with no byte-order mark. FFmpeg writes plain UTF-8 and line feeds, so any text editor’s status bar settles the last one.

Here’s the part that surprised me. The source SRT I muxed into the MKV had its first cue at 1.500 s. Extracted from that MKV, the same cue came back at 1.521 s, and all ten timestamps were exactly 21 ms later. The cues weren’t drifting; the whole track had moved. The packets explain it: the first video packet in that MKV sits at 0.021 s rather than 0.000, because the source MP4 carried a B-frame delay, and the subtitle stream was shifted with the rest of the timeline when the container was written. The same five cues from the MP4 came back identical, so the 21 ms arrives with the MKV rather than with the act of extracting subtitles. A fresh round trip gave 1.542 s.

Extracting a segment has its own clock trap. With -ss 20 before -i, the cue at 24.921 s came out at 4.921 s, because FFmpeg reset the timeline to the segment start. With -ss 20 after -i, the same cue stayed at 24.921 s, and adding -copyts to the input-side seek kept the absolute value too. Those placements move in opposite directions, so decide which timeline you want first.

How to Extract Subtitles From an MP4

MP4 can’t hold SubRip. Editors and streaming tools store text captions as mov_text, the format Apple defined, so that’s the codec in most captioned MP4s. It’s text, and you can extract subtitles from it, but doing so is the source of the most common zero-byte surprise here, since -c:s copy into an .srt fails with 234.

ffmpeg -i titles.mp4 -map 0:s:0 -c:s srt titles.srt

That produced 435 bytes and five cues, matching the MKV extraction in text and timing. If you need the track inside a container rather than a sidecar, copying into a .mov works and kept mov_text intact at 1119 bytes; only the text containers are picky. mov_text tracks often carry no title, so the language tag is the only handle you get.

What You Can’t Extract Subtitles From

Bitmap tracks are one wall: a PGS track decodes to images, and with a decoder and no encoder there’s nothing to turn into text, so OCR in a subtitle editor is the real answer. Burned-in text is the harder wall, because there’s no track to read. Check the probe first: if no stream reports codec type subtitle, nobody embedded anything.

One case looks broken and isn’t: a cue past the end of the picture still extracts, because nothing clamps cues to the video duration. I muxed a cue at 62 s into a 60 s clip and got it back intact.

Five Mistakes That Cost the Most Time

  1. -c:s copy into a container whose muxer won’t take the codec. Exit 234, zero bytes. Name the codec instead.
  2. Mapping by absolute stream index. That column counts video and audio too, so use 0:s:N when you extract subtitles.
  3. Checking the exit code and not the file. Every copy failure above still creates the output path at zero bytes, so look at the file the first time you extract subtitles from a new source.
  4. Ignoring the source encoding. A Windows-1252 SRT without -sub_charenc died at exit 69 and wrote nothing. -sub_charenc WINDOWS-1252 fixed it; ISO-8859-1 decoded the accents but dropped the em dash, since byte 0x97 is a control character in Latin-1.
  5. Expecting FFmpeg to wrap long lines. It doesn’t, my longest line came out at 65 characters, and the player wraps at display time.

Frequently Asked Questions

Does extracting subtitles touch the video?

No. The output is a subtitle file with no video stream to re-encode, so the picture and audio are never touched.

Can I extract subtitles as their own MKV?

Yes. Copying the ASS track into a fresh MKV gave a 1337-byte file whose single stream was still ass, with language tag and title intact. It’s the lossless route when styling matters.

How do I get every track out at once?

Map them into one output (-map 0:s -c:s copy all.mkv wrote 2193 bytes for three tracks) or name one output per stream. FFmpeg writes them in the order the maps appear, so check names before the run when you extract subtitles from a multi-language file.

The Short Version

Probe the file, note which subtitle stream you want and whether its codec is text or bitmap, then run one command with -map and an explicit -c:s. Use copy only when codec and container already agree. Check byte size, cue count and the first timestamp of the subtitles you extracted before trusting the result, because a failed copy still leaves a file behind and an MKV round trip can move a whole track by 21 ms.

Once the text is out, the rest gets easier. Fixing timing is a constant offset or a rescale, which the subtitle sync guide covers step by step, and the delivery format is a separate decision that SRT vs VTT vs ASS settles. If you’re creating captions rather than pulling them out, start with how to add subtitles to a video. When a probe finds no subtitle stream on a file that should have one, the path matches a video with no sound.