Skip to content

Notes

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.