Version: 5.4 | Last Updated: 21 June 2026
Note: This script is a convenience wrapper around yt-dlp. It does not replace yt-dlp — it provides pre-configured settings for batch downloading with retry logic, logging, and media server support (Plex, Kodi, Jellyfin). yt-dlp must be installed separately.
Language / Language:
- English script:
yt_download_en.py(default) - Russian script:
yt_download_ru.py
Automated YouTube video downloader (yt-dlp) with intelligent error handling, DNS recovery, auto-restart, playlist progress tracking, NFO generation for Plex/Kodi, and detailed logging for mass downloads.
- 🧠 Intelligent Error Handling — 20+ error types classified into categories (skip, retry, pause, fatal)
- 🌐 DNS Recovery System — Automatic detection and waiting for DNS restoration
- 🔄 Auto-Restart on Critical Errors — Script automatically restarts after failures
- 📋 Playlist Support — Automatic subfolder creation per playlist/channel with progress tracking
- 📁 NFO Generation — Creates .nfo files for Plex/Kodi with metadata from .info.json
- 📊 Detailed Logging — Rotating logs with 10MB auto-rotation (keeps 5 backups)
- 🎯 Resume Support — Continue from where you left off via archive tracking
- 🍪 Cookie Support — Access age-restricted content with Firefox cookies
- 🎨 Colored Output — Real-time progress with color-coded status
- ⏱️ Adaptive Delays — 5-60 second intervals to avoid rate limits
- 📦 MP4 Optimization — Automatic metadata and thumbnail embedding
- 🔁 Rate Limit Detection — Automatic pauses from 30 seconds to 1 hour
- 🛡️ Stable Connection — Optimized for unstable networks (1 concurrent fragment)
- Purpose
- Quick Start
- Key Features
- System Requirements
- Installing Dependencies
- Usage
- File Structure
- Script Configuration
- Operating Logic
- Handled Errors
- NFO File Generation
- Troubleshooting
- FAQ
- Additional Information
- License
- Useful Links
The script is designed for reliable downloading of large video collections (hundreds and thousands of files, including playlists) in a "set and forget" mode. It automatically handles typical issues: DNS failures, YouTube rate limits, network errors, unavailable videos, with the ability to resume from where it left off. Includes playlist progress tracking with automatic subfolder organization, NFO generation for media servers, and automatic restart on critical errors.
# 1. Install dependencies
uv pip install yt-dlp colorama
# Or with pip: pip install yt-dlp colorama
winget install ffmpeg
# 2. Create links.txt with YouTube URLs
echo https://www.youtube.com/watch?v=dQw4w9WgXcQ > links.txt
# 3. Run the script
python yt_download_en.py- Classification of 20+ error types into categories: skip, retry, pause, fatal
- Automatic detection of DNS failures with recovery waiting
- Handling of HTTP 403/429/400/404/410, bot detection, geo-blocks, copyright, private videos
- Adaptive pauses depending on error type (from 30 seconds to 1 hour)
- Automatic detection of DNS resolution failures
- Smart waiting for DNS restoration (up to 10 minutes)
- Tracking consecutive DNS errors to prevent infinite loops
- Early DNS availability check before script start
- Automatic restart on critical errors (up to 3 consecutive failures)
- Graceful handling of network interruptions
- Resume from interruption point
- Automatic detection of playlist and channel URLs
- Dedicated subfolder per playlist/channel:
downloads/<playlist_title>/ - Progress tracking: shows total, downloaded, and remaining video counts
- Extended timeout of 120 minutes for playlist processing
- Automatic generation of .nfo files for Plex/Kodi from .info.json
- Metadata extraction: title, uploader, description, upload date
- Compatible format with media server requirements
- Up to 3 attempts per video with progressive delays
- Automatic skipping of irreversible errors (deleted/paid/private videos)
- Built-in hang protection (timeout 60 minutes per single video, 120 minutes per playlist)
- Using cookies from Firefox to access age-restricted videos
- Stable single-fragment downloading (1 thread) for unstable connections
- Adaptive delays of 5-60 seconds between videos to avoid blocks
- Automatic embedding of metadata and thumbnails into MP4
- Separate thumbnail saving as JPG files
- Single log file
download.logwith timestamps of all events - Automatic log rotation at 10 MB (keeps 5 backup copies)
- Colored console output with download progress bar (via colorama)
- Detailed statistics: successful/skipped/errors, total and average time
- Uses yt-dlp's built-in mechanism to track downloaded videos by ID
- Automatic skipping of already downloaded files on restart
- Saving list of failed downloads to
failed_links.txtfor retry
- Windows: 10/11 or Windows Server 2016+
- Python: 3.8 or higher → Download Python
- PowerShell: 5.1 or higher (built into Windows)
- yt-dlp: latest version → GitHub yt-dlp
- ffmpeg: for merging video/audio formats → Download ffmpeg
# Install yt-dlp
pip install -U yt-dlp
# Install colorama for colored output (optional but recommended)
pip install coloramaThere are several ways to install ffmpeg in PowerShell on Windows.
Winget is built into Windows 10/11, so this is the easiest method:
winget install ffmpegIf you have Chocolatey installed, run in PowerShell with administrator rights:
choco install ffmpegScoop installs programs in a user directory without cluttering system folders:
scoop install ffmpegIf you prefer more control over the installation process:
-
Open PowerShell with administrator rights
-
Download and install ffmpeg:
# Download ffmpeg
Invoke-WebRequest -Uri "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip" -OutFile "ffmpeg.zip"
# Extract to C:\
Expand-Archive -Path "ffmpeg.zip" -DestinationPath "C:\"
# Rename folder
$ffmpegFolder = Get-ChildItem -Path "C:\" -Filter "ffmpeg-*" -Directory
Rename-Item -Path $ffmpegFolder.FullName -NewName "ffmpeg"- Add ffmpeg to PATH:
$envPath = [Environment]::GetEnvironmentVariable("PATH", "Machine")
[Environment]::SetEnvironmentVariable("PATH", $envPath + ";C:\ffmpeg\bin", "Machine")- Verify installation by opening a new terminal:
ffmpeg -versionAfter installation, the following commands will be available: ffmpeg, ffplay, ffprobe.
Create a file links.txt in the script folder, add one YouTube URL per line:
https://www.youtube.com/watch?v=dQw4w9WgXcQ
https://www.youtube.com/watch?v=9bZkp7q19f0
https://www.youtube.com/watch?v=h4Bq69HfR0Y&list=RDh4Bq69HfR0Y&start_radio=1&pp=ygUMa2VybWl0IGRhbmNloAcB0gcJCXwKAYcqIYzv
python yt_download_en.py # or yt_download_ru.py for RussianThe script will automatically process all links with error handling. Progress is displayed in the console with color highlighting:
- 🟢 Green — successful download
- 🔵 Blue — already downloaded previously
- 🟡 Yellow — warnings and retries
- 🔴 Red — errors
- 🟣 Purple — merging process
- 🌐 Cyan — DNS recovery process
If necessary, interrupt the script with Ctrl+C — progress will be saved in download_archive.txt, and you can continue later from the same place. The script also has auto-restart capability for critical errors.
Some YouTube content requires authentication:
- Private playlists (Watch Later, Liked Videos, custom playlists)
- Age-restricted content (18+ videos)
- Members-only videos
- Subscriber content
The script supports three methods for authentication via cookies.
yt-dlp automatically extracts cookies from your browser.
[cookies]
mode = "browser"
browser = "firefox"Supported browsers: firefox, chrome, chromium, edge, opera, brave, safari.
Make sure you are logged into YouTube in the browser before running the script.
A standalone cookies.txt file is more reliable than automatic browser extraction. It works regardless of whether the browser is open or closed.
Steps:
-
Install a browser extension to export cookies:
- Chrome/Edge: Get cookies.txt LOCALLY
- Firefox: cookies.txt
-
Log in to YouTube in your browser.
-
Click the extension icon → Export → save as
cookies.txt. -
Place
cookies.txtin the script directory (or set a custom path in config). -
Configure in
config.toml:
[cookies]
mode = "file"
cookies_file = "cookies.txt"Export cookies from the command line for use with Method 2:
yt-dlp --cookies-from-browser chrome --cookies cookies.txt "https://www.youtube.com/..."Then follow Method 2 steps 4-5.
[cookies]
# "browser" — auto-extract from browser (default)
# "file" — use cookies.txt file
# "off" — no cookies (public videos only)
mode = "browser"
browser = "firefox"
cookies_file = "cookies.txt"After running, the script will create the following files:
| File | Description |
|---|---|
download.log |
Main log with timestamps of all events and errors |
download.log.1 - .5 |
Backup log copies (created during rotation) |
download_archive.txt |
yt-dlp service file with IDs of successfully downloaded videos |
failed_links.txt |
List of URLs of failed downloads for retry |
downloads/ |
Main folder for all downloaded files |
downloads/*.mp4 |
Single video files in format Title [ID].mp4 |
downloads/<playlist>/ |
Subfolder per playlist or channel |
downloads/<playlist>/*.mp4 |
Playlist video files in format Title [ID].mp4 |
*.info.json |
Metadata files with video information (alongside videos) |
*.nfo |
Media server metadata files for Plex/Kodi (alongside videos) |
*.jpg |
Thumbnail images in JPG format (alongside videos) |
In the setup_logger() function:
logger = setup_logger(
log_file,
max_bytes=10*1024*1024, # Maximum log size in bytes (default 10 MB)
backup_count=5 # Number of backup copies (default 5)
)Configuring log size and count:
max_bytes— file size at which rotation occurs (e.g.,20*1024*1024for 20 MB)backup_count— number of old logs to keep (e.g.,3for 3 copies)
In the download_youtube_videos() function:
max_attempts = 3 # Number of attempts per video (default 3)In the download loop:
timeout_seconds = 7200 if is_playlist else 3600 # 120 min for playlists, 60 min for single videosIn the cmd array:
'--sleep-requests', '5', # Delay between API requests (seconds)
'--sleep-interval', '20', # Minimum delay between videos (seconds)
'--max-sleep-interval', '60', # Maximum delay between videos (seconds)
'--socket-timeout', '60', # Socket timeout (seconds)Delay configuration recommendations:
- For unstable connections:
20-60seconds (current values) - With frequent rate limits: increase to
30-120seconds
'--retries', '15', # General retry attempts
'--fragment-retries', '15', # Fragment retry attempts
'--extractor-retries', '8', # Extractor retry attempts
'--file-access-retries', '5', # File access retry attempts
'--concurrent-fragments', '1', # Number of parallel threads (1 for stability)
'--buffer-size', '16K', # Buffer size (16K optimal for most cases)# Playlists — saved to a dedicated subfolder:
'%(playlist_title,uploader,channel).100s/%(title).200s [%(id)s].%(ext)s'
# Single videos — saved directly to downloads/ root:
'%(title).200s [%(id)s].%(ext)s'Available variables:
%(title)s— video title (truncated to 200 characters)%(id)s— video ID%(playlist_title)s— playlist title (used for subfolder name)%(uploader)s— channel author%(upload_date)s— upload date
Full list: yt-dlp Output Template
'--cookies-from-browser', 'firefox', # Browser for cookies (firefox only in this config)'--remote-components', 'ejs:github', # Use remote extractor components from GitHub- ✅ Initialization: Checks yt-dlp, ffmpeg, and DNS availability
- 📄 Link Processing: Reads
links.txtfile (ignores commented lines with#) - 📋 Playlist Check: For playlist URLs, retrieves progress (total/downloaded/remaining videos)
- 🌐 DNS Monitoring: Continuously checks DNS resolution throughout process
- 🎬 Video Download: For each URL, launches yt-dlp with optimized parameters
- 👁️ Real-time Monitoring: Tracks output, recognizes and classifies errors
- 🔄 Error Handling: On error, decides: retry, skip, pause, or wait for DNS
- 📁 NFO Generation: Creates .nfo files for media servers after successful download
- 📝 Logging: Records all events with timestamps in
download.log(with auto-rotation) - 💾 Archiving: Saves IDs of successfully downloaded videos in
download_archive.txt - 🔁 Auto-Restart: Automatically restarts on critical errors (max 3 attempts)
- 📊 Statistics: Outputs detailed stats and list of failed downloads
- Detection: Script monitors for "failed to resolve" or "getaddrinfo failed" errors
- Counting: Tracks consecutive DNS errors (max 20 before critical action)
- Recovery Attempt: Pauses and waits for DNS restoration (up to 10 minutes)
- Resumption: Continues downloading once DNS is restored
- Fallback: If DNS not restored, script can be restarted manually
After each successful download, the script:
- Locates the
.info.jsonfile created by yt-dlp - Extracts metadata: title, video ID, uploader, description, upload date
- Applies XML escaping to all text fields (safe for
&,<,>in titles) - Creates a
.nfofile with structured XML for Plex/Kodi/Jellyfin - Saves it alongside the video file with the same base name
- Skips if
.nfoalready exists (idempotent)
The script automatically generates .nfo files compatible with Plex, Kodi, and other media servers. These files contain structured metadata that helps media organizers properly catalog your videos.
- Video Title
- Uploader/Channel name
- YouTube Video ID
- Video Description
- Upload Date (formatted as YYYY-MM-DD HH:MM:SSZ)
- Year (for media server sorting)
- Source identifier ("YouTube")
- XML declaration header (
<?xml version="1.0" encoding="UTF-8"?>) - XML-escaped text fields (safe for special characters like
&,<,>)
- Created in the same directory as the downloaded video
- Same base filename as the video (e.g.,
Video Title [ABC123].nfo) - Automatically generated from the
.info.jsonfile created by yt-dlp
- NFO generation is controlled by
generate_nfo = trueinconfig.toml[downloads]section - Set
generate_nfo = falseto disable NFO file creation
- Plex/Kodi/Jellyfin Compatibility: Media servers automatically read and display metadata
- Organized Library: Proper sorting by date, channel, and title
- Searchable Content: Descriptions and metadata become searchable in your media library
- Automatic Thumbnails: Media servers can use the embedded or separate thumbnail
The script automatically handles the following error types:
- DNS resolution failures (30-second pause, wait for recovery)
- HTTP 403 (access/cookie issues) - 10-minute pause
- HTTP 400 (outdated yt-dlp version)
- Connection timeouts (30-second pause)
- Network errors (60-second pause)
- Bot detection (5-minute pause)
- Age-restricted content (retry with cookies)
- YouTube rate limit (1-hour pause)
- HTTP 429 (30-minute pause)
- HTTP 404/410 (video deleted)
- Private videos / Members-only
- Geo-blocking
- Copyright takedown
- Payment required
- Scheduled premieres
- Video unavailable
- Disk full
- No folder access permissions
- ffmpeg not found
- Command parameter errors (exit code 2)
Cause: yt-dlp is not installed or not in PATH
Solution:
# Verify installation
where yt-dlp
# Reinstall if needed
pip install -U yt-dlpCause: ffmpeg is not installed or not in PATH
Solution:
# Install via winget
winget install ffmpeg
# Restart PowerShell to reload PATH
# Verify installation
ffmpeg -versionCause: Internet connection issues or DNS server problems
Solution:
- Script automatically pauses and waits for DNS recovery (up to 10 minutes)
- Check your internet connection
- Try flushing DNS:
ipconfig /flushdns - Consider changing DNS servers to Google (8.8.8.8) or Cloudflare (1.1.1.1)
Cause: Too many requests to YouTube
Solution:
- Script automatically pauses for 30 minutes
- Increase delays in configuration: modify
--sleep-intervaland--max-sleep-interval - Use cookies from an authorized browser account
Cause: Access denied (often for age-restricted videos)
Solution:
- Ensure cookies are properly exported from Firefox
- Log into YouTube in Firefox before running the script
- Check that
--cookies-from-browser firefoxis working
Cause: Network issues or YouTube throttling
Solution:
- Script has built-in timeout: 60 minutes per single video, 120 minutes per playlist
- If it hangs repeatedly, check your internet connection stability
- The single concurrent fragment (--concurrent-fragments 1) is already optimized for unstable connections
Cause: Video is deleted, private, or geo-blocked
Solution:
- Script automatically skips these videos
- Check
failed_links.txtfor list of failed URLs - For geo-blocked videos, consider using a VPN
Cause: .info.json files missing or corrupted
Solution:
- Ensure
--write-info-jsonis in the yt-dlp command (it is by default) - Check that video downloads complete successfully
- Verify disk space is available
Q: Can I download age-restricted videos?
A: Yes, the script uses cookies from Firefox via --cookies-from-browser firefox. Make sure you're logged into YouTube in Firefox before running the script.
Q: How do I resume interrupted downloads?
A: Just re-run the script. It automatically skips downloaded videos via download_archive.txt. The script also has auto-restart capability for crashes.
Q: What video quality does the script download?
A: Automatically selects the best available quality using -f bestvideo+bestaudio/best, then remuxes to MP4. This typically gives 1080p or higher when available.
Q: How much disk space do I need?
A: Full HD (1080p) videos typically range from 500 MB to 2 GB per video. Ensure you have sufficient free space for your download list plus extra for logs and metadata files.
Q: Can I download entire playlists?
A: Yes, just paste the playlist URL in links.txt. The script automatically detects playlists, creates a dedicated subfolder, and tracks download progress.
Q: Why does the script pause for an hour sometimes?
A: When YouTube rate limiting is detected, the script automatically pauses for 1 hour to avoid account blocking.
Q: Can I change the browser for cookies?
A: Yes, modify the --cookies-from-browser parameter in the cmd array. Supported: firefox, chrome, chromium, edge, opera, brave, safari.
Q: What are .nfo files and do I need them?
A: .nfo files are metadata files for media servers like Plex and Kodi. They're automatically generated and help organize your video library. You can delete them if not using a media server.
Q: How does DNS recovery work?
A: When DNS errors are detected, the script pauses, checks DNS availability every 60 seconds, and resumes automatically when DNS is restored (up to 10 minutes wait).
Q: Can I run multiple instances simultaneously?
A: Not recommended, as both instances would write to the same log and archive files, causing conflicts. Use separate directories for parallel downloads.
Q: How do I update yt-dlp?
A: Run pip install -U yt-dlp regularly. YouTube frequently changes its API, so keeping yt-dlp updated is important for reliability.
- Account security: Use moderate delays between videos to avoid YouTube account blocking
- Disk space: Ensure sufficient free space (Full HD video takes ~500 MB - 2 GB)
- Age restrictions: For 18+ videos, cookies from a browser with an authorized YouTube account are required
- Updates: Regularly update yt-dlp:
pip install -U yt-dlp - DNS issues: The script is resilient to temporary DNS failures but may need manual intervention for prolonged outages
- Logs: Check
download.logfor detailed error information if downloads fail - Thumbnails: Separate JPG thumbnails are saved alongside videos for media server use
The following files are created at runtime and are excluded from the repository via .gitignore:
| File | Description |
|---|---|
links.txt |
Your YouTube URLs, one per line (create this file manually) |
downloads/ |
All downloaded videos, organized by playlist |
download.log |
Rotating log (10 MB max, 5 backups) |
download_archive.txt |
Tracks downloaded video IDs for resume support |
failed_links.txt |
List of URLs that failed to download |
# 1. Install dependencies
uv pip install yt-dlp colorama
# Or with pip: pip install yt-dlp colorama
# 2. Create your links file
echo https://www.youtube.com/watch?v=EXAMPLE > links.txt
# 3. Run the script
python yt_download_en.py # or yt_download_ru.py for RussianNote: The script requires Firefox cookies (--cookies-from-browser firefox) for age-restricted and private content. Make sure you are logged into YouTube in Firefox before running.
The script is distributed freely. Use at your discretion.