Youtube Playlist Free Downloader Python Script May 2026

Note: This script downloads videos in the highest available resolution. Be aware that downloading copyrighted content may be against YouTube's terms of service.

download_playlist("YOUR_PLAYLIST_URL_HERE") youtube playlist free downloader python script

Critique of the Code:

Before diving into the code, let's address the "why." Pre-built websites and software exist, but they often come with: Note : This script downloads videos in the

A self-made Python script is:

from pytube import Playlist
def download_playlist(playlist_url):
    playlist = Playlist(playlist_url)
    for video_url in playlist.video_urls:
        video = pytube.YouTube(video_url)
        video.streams.get_highest_resolution().download()
if __name__ == "__main__":
    playlist_url = input("Enter the YouTube playlist URL: ")
    download_playlist(playlist_url)

Recommendation: For a serious playlist downloader, use yt-dlp. It is the industry standard for open-source video downloading. Critique of the Code: Before diving into the

Replace the stream selection line in the loop above with:

# Filter for audio only and download
stream = video.streams.filter(only_audio=True).first()
# Tip: The file will be an MP4 file containing audio. 
# You can rename the extension to .mp3 manually or use a library like 'os' to rename it.
#!/usr/bin/env python3
"""
youtube_playlist_downloader.py
Downloads all videos from a YouTube playlist using yt-dlp.
Usage:
    python youtube_playlist_downloader.py PLAYLIST_URL /path/to/output_dir
"""
import sys
import os
import time
import argparse
from yt_dlp import YoutubeDL
from yt_dlp.utils import sanitize_filename
def parse_args():
    p = argparse.ArgumentParser(description="Download all videos from a YouTube playlist.")
    p.add_argument("playlist_url", help="YouTube playlist URL")
    p.add_argument("output_dir", nargs="?", default=".", help="Directory to save videos")
    p.add_argument("--format", default="mp4", help="Container format (mp4/mkv/webm). yt-dlp will pick best video+audio.")
    p.add_argument("--sleep", type=float, default=0.5, help="Seconds to sleep between downloads")
    p.add_argument("--retries", type=int, default=3, help="Retries per video on failure")
    return p.parse_args()
def ensure_dir(path):
    os.makedirs(path, exist_ok=True)
    return os.path.abspath(path)
def build_outtmpl(output_dir):
    # Keep playlist index prefix for ordering
    return os.path.join(output_dir, "%(playlist_index)03d - %(title)s.%(ext)s")
def download_playlist(url, output_dir, fmt="mp4", sleep=0.5, retries=3):
    outtmpl = build_outtmpl(output_dir)
    ydl_opts = 
        "format": f"bestvideo[ext!=webm]+bestaudio/best",
        "outtmpl": outtmpl,
        "merge_output_format": fmt,
        "noplaylist": False,
        "ignoreerrors": True,
        "continuedl": True,
        "nooverwrites": False,
        "writesubtitles": False,
        "quiet": True,
        "progress_hooks": [progress_hook],
        # Restrict filenames to safe chars
        "restrictfilenames": False,
        "allow_unplayable_formats": False,
attempts = {}
    with YoutubeDL(ydl_opts) as ydl:
        info = ydl.extract_info(url, download=False)
        if not info:
            print("Failed to fetch playlist info.")
            return
        entries = info.get("entries") or [info]
        print(f"Found len(entries) entries in playlist.")
        for i, entry in enumerate(entries, start=1):
            if entry is None:
                print(f"[i] Skipping unavailable entry.")
                continue
            video_url = entry.get("webpage_url") or entry.get("url")
            title = entry.get("title") or f"video_i"
            index = entry.get("playlist_index") or i
            safe_title = sanitize_filename(title)
            ext = fmt
            filename = f"index:03d - safe_title.ext"
            outpath = os.path.join(output_dir, filename)
            if os.path.exists(outpath):
                print(f"[index] Already downloaded: filename")
                continue
attempt = 0
            while attempt < retries:
                attempt += 1
                try:
                    print(f"[index] Downloading (attempt/retries): title")
                    ydl.download([video_url])
                    # Small pause to be polite
                    time.sleep(sleep)
                    break
                except Exception as e:
                    print(f"[index] Error on attempt attempt: e")
                    if attempt >= retries:
                        print(f"[index] Failed after retries attempts, skipping.")
                    else:
                        time.sleep(2 ** attempt)
        print("Done.")
def progress_hook(d):
    if d.get("status") == "downloading":
        eta = d.get("eta")
        speed = d.get("speed")
        downloaded = d.get("downloaded_bytes", 0)
        total = d.get("total_bytes") or d.get("total_bytes_estimate")
        pct = ""
        if total:
            pct = f"downloaded/total*100:5.1f%"
        print(f"Downloading: d.get('filename','') pct ETA:eta speed:speed", end="\r")
    elif d.get("status") == "finished":
        print(f"\nFinished downloading: d.get('filename')")
if __name__ == "__main__":
    args = parse_args()
    out = ensure_dir(args.output_dir)
    download_playlist(args.playlist_url, out, fmt=args.format, sleep=args.sleep, retries=args.retries)
┌─────────────────┐
│  User Input     │
│  - Playlist URL │
│  - Quality      │
└────────┬────────┘
         ▼
┌─────────────────┐
│  Fetch Playlist │
│  (pytube.Playlist)│
└────────┬────────┘
         ▼
┌─────────────────┐
│ Iterate Videos  │
└────────┬────────┘
         ▼
┌─────────────────┐
│ For each video: │
│ - Get stream    │
│ - Download      │
│ - Handle errors │
└─────────────────┘