DEV Community

Cover image for Download YouTube playlist and channel videos with yt-dlp
Bozhidar Valchev
Bozhidar Valchev

Posted on Originally published at valcheff.net on

Download YouTube playlist and channel videos with yt-dlp

YT playlist downloader is a single Python script that uses yt-dlp to download YouTube playlist videos in bulk. It handles entire channels and single videos too. It numbers the files in playlist order and remembers what it has fetched, so an interrupted run resumes where it stopped. A private or deleted video does not stop it either. The source is on GitHub under the MIT licence: YT_playlist_downloader_by_Valcheff.

Use it for material you have the right to download. That covers your own uploads, Creative Commons and public domain video, or anything the uploader permits. DRM protected streams are out of scope, and yt-dlp cannot handle them at all.

What it does

yt-dlp can already do everything below. The catch is that doing it well takes a very long command line. Most people rebuild it from the help output every time and quietly leave out the parts they forget. The script fixes the flags in place so a bulk download behaves sensibly by default.

Download YouTube playlist videos with yt-dlp: numbered files, a resume archive, unavailable items skipped
Files keep the playlist order, the archive makes re-runs resume, and an unavailable video is skipped.

Files land in playlist order

Each filename starts with its position in the playlist. A 40 part lecture series therefore stays in sequence on disk instead of sorting alphabetically by title. It also sanitises titles for Windows and caps them at 200 characters, so long names stay under the path limit.

Interrupted downloads resume

The script writes every completed video ID to an archive file in the output directory. Stop a 300 item playlist at item 180 and run the same command a week later. It continues at 181 rather than starting over. Partially written files resume at the byte level too, so a video cut off midway is not downloaded twice.

One dead video does not stop the run

The script logs and skips private, deleted and geo-blocked items, and the rest of the playlist still downloads. Without this, a single unavailable video at position 47 ends a 200 item run. That is the main reason unattended downloads fail overnight.

Requirements

  • Python 3.9 or newer
  • yt-dlp installed with its default dependency group
  • ffmpeg on PATH, for merging video with audio and for audio conversion
  • deno 2.3.0 or newer, which yt-dlp uses to solve YouTube’s JavaScript challenge
pip install -U "yt-dlp[default]"
Enter fullscreen mode Exit fullscreen mode

The quotes and the bracketed group matter. A plain pip install -U yt-dlp omits the challenge solver, which produces a misleading failure covered in the troubleshooting section.

How to download YouTube playlist videos

Start by listing the playlist. This prints the contents with numbers and durations and downloads nothing. It matters because a channel URL with 1100 videos looks identical to a playlist with 12 until you check.

python yt_playlist_dl.py "https://www.youtube.com/playlist?list=PLxxxx" --list
Enter fullscreen mode Exit fullscreen mode

Then take the first two items as a smoke test. It catches a wrong output path or a missing ffmpeg before a long run does:

python yt_playlist_dl.py "URL" -o "D:/Video" --items 1-2
Enter fullscreen mode Exit fullscreen mode

If those arrive correctly, drop the item range and download the whole playlist:

python yt_playlist_dl.py "https://www.youtube.com/playlist?list=PLxxxx" -o "D:/Video"
Enter fullscreen mode Exit fullscreen mode

Files arrive as D:/Video/<playlist name>/01 - Title [videoID].mp4 and so on. Pass --flat if you would rather skip the per-playlist subfolder.

How to download an entire YouTube channel

Point it at the channel’s videos tab. Channels list newest first. Add --reverse to make the oldest video file number one, which suits a course or a series:

python yt_playlist_dl.py "https://www.youtube.com/@channel/videos" -o "D:/Channel" --reverse
Enter fullscreen mode Exit fullscreen mode

Large channels are worth splitting into batches with --items, for example 1-100 today and 101: tomorrow. The archive file makes the split safe, because the second run skips anything the first one finished.

To keep a channel mirrored, save the URLs in a text file and re-run the same command whenever you like. It downloads only the new uploads:

python yt_playlist_dl.py -f channels.txt -o "D:/Video"
Enter fullscreen mode Exit fullscreen mode

How to download audio only

To download YouTube playlist audio without the video, add --audio-only. For podcasts, lectures and music this cuts roughly ninety percent of the transfer. The script never touches the video stream:

python yt_playlist_dl.py "URL" -o "D:/Podcasts" --audio-only --audio-format opus --thumbnail
Enter fullscreen mode Exit fullscreen mode

Choose opus rather than mp3 where your player allows it. YouTube already serves audio as opus or m4a. Requesting mp3 makes ffmpeg re-encode an already lossy stream, which adds a second round of loss. With opus you keep the original bytes. mp3 is worth it only for hardware that cannot play anything else. flac is counterproductive: it multiplies the file size without recovering detail YouTube has already discarded. The --thumbnail flag embeds the video thumbnail as cover art, which is what most music players display.

How to download videos with subtitles

python yt_playlist_dl.py "URL" -o "D:/Course" --subs --auto-subs --sub-langs en,bg --embed-subs
Enter fullscreen mode Exit fullscreen mode

Without --auto-subs you get only captions the uploader typed by hand, which for most amateur video means none at all. Adding it brings in YouTube’s machine transcript, which is useful for searching through lectures even though it reads poorly. --embed-subs writes the subtitles inside the video file instead of leaving separate .srt files next to it. That matters for players that fail to pick up external subtitle files.

Choosing quality and file size

The default ceiling is 1080p. It controls size more than quality, because 4K files are five to eight times larger than 1080p ones. The selector always takes the best format below the ceiling. A 480p source stays 480p, and the script never upscales it.

python yt_playlist_dl.py "URL" -o "D:/Video" --max-height 2160 # up to 4K
python yt_playlist_dl.py "URL" -o "D:/Video" --max-height 720 # small files
python yt_playlist_dl.py "URL" -o "D:/Video" --max-height 0 # no limit
Enter fullscreen mode Exit fullscreen mode

Output goes into an mp4 container by default, which plays on essentially everything and spares media servers from transcoding. Switch to --container mkv when you are pulling several subtitle languages or want chapters preserved, since mp4 handles both badly.

Resuming and re-running

The script records completed video IDs in .downloaded.txt inside the output directory, and every later run skips them. Interrupt with Ctrl+C at any point and run the identical command afterwards to continue.

Two consequences are worth knowing. Moving the finished files elsewhere does not clear the archive, so the script will not fetch them again. To download the same playlist again at a different quality, point -o at a new directory or pass --no-archive.

For an overnight run on a shared connection, cap the speed and slow the request rate:

python yt_playlist_dl.py "URL" -o "D:/Video" --rate-limit 3M --sleep 1 --fragments 3
Enter fullscreen mode Exit fullscreen mode

Fragments deserve a word, because they are the main speed lever. A large video arrives in hundreds of fragments and the script downloads four at a time by default. Beyond roughly eight, YouTube throttles and intermittently returns HTTP 403. The retries that follow end up slower than a lower number would have been.

All options

Option Effect
--list Print the playlist contents and download nothing
-o DIR Output directory
--flat Skip the per-playlist subfolder
--items SPEC Select items: 5-20, 3,7,12, 5:, ::2
--reverse Walk the playlist from the end, for channels that list newest first
--max-height PX Resolution ceiling, 1080 by default, 0 for unlimited
--container mp4, mkv or webm for the merged file
--audio-only Download the audio stream only
--audio-format opus, m4a, mp3, flac, wav or vorbis
--subs, --auto-subs Subtitles, optionally including machine generated ones
--embed-subs Write subtitles inside the video file
--thumbnail Embed the thumbnail as cover art
--sponsorblock Cut sponsor segments using the SponsorBlock database
--fragments N Parallel fragment downloads per video, 4 by default
--rate-limit RATE Speed ceiling such as 3M or 800K
--sleep SECONDS Pause between requests
--cookies-from-browser Use an existing browser session for your own private videos
--no-archive Ignore the resume archive and download everything again

Troubleshooting

Every download fails with “This video is not available”

The message is wrong and the video is fine. YouTube hides its stream URLs behind a JavaScript challenge. yt-dlp solves it by running an external JavaScript runtime, deno by default, over solver scripts from the separate yt-dlp-ejs package. When the solver is missing, no formats come back and yt-dlp reports the absence of formats as an unavailable video.

Installing with the dependency group shown earlier pulls the solver in and fixes it. Official GitHub release binaries bundle it already, so this only bites pip installs. Verify both halves like this:

python -c "import yt_dlp_ejs; print('solver present')"
python -m yt_dlp -v --simulate "https://www.youtube.com/watch?v=SOME_ID"
Enter fullscreen mode Exit fullscreen mode

The verbose output should report JS runtimes: deno-<version> followed by Solving JS challenges using deno. If it reports no supported runtime while deno is definitely installed, your terminal predates the installation. Its PATH is stale, so open a new one. Keep the two packages in step. yt-dlp raises the minimum solver version without announcing it and silently ignores an outdated one.

Downloads are slow or return HTTP 403

Lower --fragments to 2 or 3 and add --sleep 1. If it persists across a whole session rather than one video, YouTube is rate limiting your address. Waiting is the only real fix.

Merging fails or audio only produces nothing

ffmpeg is not on PATH. Run ffmpeg -version to confirm, and reopen the terminal if you installed it in this session.

Frequently asked questions

Can it download an entire YouTube channel?

Yes. Pass a channel videos URL such as https://www.youtube.com/@channel/videos and it walks every upload. Channels list newest first, so add –reverse for chronological order.

Can it resume an interrupted playlist download?

Yes. The script records completed video IDs in an archive file inside the output directory. Re-running the identical command skips them. Half finished files also resume at the byte level.

How many videos can it download at once?

There is no built in limit, and playlists of several hundred items are the normal case. It fetches videos one after another while the fragments inside each video download in parallel. That keeps the request rate low enough to avoid throttling.

Do I need YouTube Premium?

No. yt-dlp downloads the same streams an anonymous viewer receives. It never fetches ads, because YouTube delivers them separately from the video stream. A Premium account changes one thing: it makes the enhanced bitrate 1080p format available. yt-dlp labels that format Premium and prefers it automatically when you pass –cookies-from-browser. Everything else works identically without a subscription.

Can it download private or age restricted videos?

It can use your own browser session through –cookies-from-browser. That covers your own unlisted and private uploads and age gated material. It does not bypass any restriction; it only reuses the access you already have.

Which audio format should I choose?

opus, because YouTube already serves opus and nothing needs re-encoding. Choose mp3 only for players that cannot handle anything else.

Source

One script, one README documenting every option with the reasoning behind it, MIT licence: github.com/valcheffnet/YT_playlist_downloader_by_Valcheff. It calls yt-dlp as a Python library rather than shelling out. Progress reporting, error handling and the resume archive all run through the API.

GitHub logo valcheffnet / YT_playlist_downloader_by_Valcheff

Download YouTube playlists and channels with yt-dlp: playlist-ordered filenames, resume across runs, and per-item error tolerance

YT playlist downloader

A yt-dlp wrapper for pulling down whole YouTube playlists and channels without having to remember twenty command line flags every time.

It handles the parts you would otherwise write yourself. Filenames keep the playlist order. Anything already downloaded gets skipped on the next run. And when one item in a 200 video playlist turns out to be private, the other 199 still arrive.

Meant for material you have the right to download. Your own uploads, Creative Commons and public domain video, amateur footage, anything the uploader allows It does not touch DRM protected streams and cannot be made to.

Prerequisites

What Why Check
Python 3.9 or newer runs the script python --version
yt-dlp with the default dependency group does the actual work python -m yt_dlp --version
ffmpeg on PATH merges video and audio, converts audio, embeds subtitles ffmpeg -version
deno 2.3.0 or newer solves YouTube's JavaScript challenge
…

The post Download YouTube playlist and channel videos with yt-dlp first appeared on Valcheff Net.

Top comments (0)