Download YouTube subtitles as SRT, without the video
The command is three flags. The part nobody mentions is that auto-generated captions come out with every line duplicated — and which format avoids it instead of cleaning up afterwards.
You want the text, not the video. A transcript to search, subtitles for a file you already have, or a talk you would rather read in four minutes than watch in forty.
yt-dlp --skip-download --write-auto-subs --sub-langs en --convert-subs srt "URL"
That works, and if you stop there you will get a file with every line printed two or three times. Here is why, and the better command.
First, see what exists
Two different things get called subtitles, and they behave differently:
yt-dlp --list-subs "URL"
- Subtitles — uploaded by the creator. Punctuated, correctly spelled, properly timed. Rare outside professional channels.
- Automatic captions — speech recognition. Available on almost everything, and the source of the duplication problem below.
The automatic list is enormous, because YouTube offers machine translation of
the machine transcription into every language it knows. Entries like
fr-en mean “French, translated from English”. For a transcript, you want the
original language, not a translation of it: two rounds of machine error
compound in ways that are hard to spot and impossible to trust.
Creator subtitles, when they exist
yt-dlp --skip-download --write-subs --sub-langs en --convert-subs srt "URL"
--write-subs is real subtitles only. It fails when there are none rather than
quietly falling back, which is the behaviour you want — you want to know which
kind you got.
Nothing more to do here. These files are clean.
Automatic captions, and the duplication
yt-dlp --skip-download --write-auto-subs --sub-langs en --convert-subs srt "URL"
Open the result and you find:
1
00:00:19,560 --> 00:00:21,950
Hear that?
2
00:00:21,950 --> 00:00:21,960
Hear that?
3
00:00:21,960 --> 00:00:24,830
Hear that? That's nothing.
Not a bug in yt-dlp. YouTube’s automatic captions scroll: on screen, a line appears, then a second line appears beneath it while the first is still there, then the first scrolls off. To describe that, the caption track repeats the visible text on every change, with 10-millisecond filler cues in between.
Fine as a live display. As a document, it means each sentence appears repeatedly, and a 40-minute talk produces a 315-cue file where 158 cues would do.
The fix is a different source format, not a cleanup script
The default download is WebVTT, which is what carries the scrolling. YouTube
also serves the same captions as srv1, a much simpler format that has no
scrolling in it at all — each line once, with a start and a duration.
yt-dlp --skip-download --write-auto-subs --sub-langs en \
--sub-format srv1 "URL"
Checked on 2026-07-31, on the same video: 315 duplicated cues from the
default, 158 clean ones from srv1. Same words, none repeated.
One catch: --convert-subs srt cannot read srv1 — FFmpeg rejects it with
Invalid data found when processing input. It is a dozen lines of XML, so
convert it directly:
#!/usr/bin/env python3
"""Convert YouTube's srv1 captions to clean SRT. Usage: srv1-to-srt.py file.srv1"""
import html, sys, xml.etree.ElementTree as ET
def stamp(seconds):
ms = round(seconds * 1000)
h, ms = divmod(ms, 3600000)
m, ms = divmod(ms, 60000)
s, ms = divmod(ms, 1000)
return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
def main(path):
root = ET.parse(path).getroot()
n = 0
for node in root.findall("text"):
text = html.unescape(html.unescape(node.text or "")).strip()
if not text:
continue
start = float(node.get("start", 0))
end = start + float(node.get("dur", 0))
n += 1
print(f"{n}\n{stamp(start)} --> {stamp(end)}\n{text}\n")
main(sys.argv[1])
python3 srv1-to-srt.py video.en.srv1 > video.srt
The double html.unescape is not a mistake: YouTube escapes the text twice, so
an apostrophe arrives as ' and one pass leaves you with '.
If you would rather not run a script
ttml also avoids the duplication and does convert:
yt-dlp --skip-download --write-auto-subs --sub-langs en \
--sub-format ttml --convert-subs srt "URL"
The cost is <font color="white" size=".72c"> wrapped around every line, which
players ignore and text editors do not. Strip them if the file is for reading:
sed -i '' 's/<[^>]*>//g' video.en.srt
Just the words, no timings
For pasting into notes, or feeding to something else:
yt-dlp --skip-download --write-auto-subs --sub-langs en --sub-format srv1 \
-o "%(title)s.%(ext)s" "URL"
python3 -c "
import html, sys, xml.etree.ElementTree as ET
root = ET.parse(sys.argv[1]).getroot()
print(' '.join(html.unescape(html.unescape(t.text or '')).strip()
for t in root.findall('text') if (t.text or '').strip()))
" video.en.srv1 > transcript.txt
Expect no punctuation and mangled proper nouns. Automatic captions are a transcription, not a document, and the errors cluster exactly on the names and technical terms you were probably searching for.
Subtitles inside the video file
To watch rather than read, embed them as a track — selectable in the player, no second file to keep alongside:
yt-dlp --embed-subs --sub-langs en \
-f "bestvideo[vcodec^=avc1]+bestaudio[ext=m4a]" "URL"
Add --write-auto-subs to fall back to automatic captions when there are no
real ones. Embedding needs FFmpeg — if it is missing you get the video without
the track and only a warning, the same failure mode as
the silent-file problem.
Every language a video offers
yt-dlp --skip-download --write-subs --sub-langs all --convert-subs srt "URL"
Use --write-subs and not --write-auto-subs for this. With automatic
captions, all means every machine translation YouTube can generate — hundreds
of files, all of them a translation of a transcription.
A whole playlist or channel
yt-dlp --skip-download --write-auto-subs --sub-langs en --sub-format srv1 \
--download-archive subs.txt \
-o "%(playlist_index)03d - %(title)s.%(ext)s" \
--sleep-requests 1 \
"PLAYLIST_URL"
Subtitle files are tiny, so this is fast — but it is still one request per video, and a few hundred of those in a row is how you meet 429 Too Many Requests. Keep the sleep.
The short version
| You want | Command |
|---|---|
| Creator subtitles | --write-subs --sub-langs en --convert-subs srt |
| Auto captions, clean | --write-auto-subs --sub-langs en --sub-format srv1 + the script |
| Auto captions, no script | --sub-format ttml --convert-subs srt, then strip tags |
| See what exists | --list-subs |
| Inside the video | --embed-subs |
| Text only | srv1, then join the <text> nodes |
The one thing to carry away: --sub-format srv1. The duplication everyone
cleans up afterwards is avoidable at the source, and nearly every guide on this
topic hands you the duplicated file without mentioning it.