Paste a playlist link. Get a tagged music folder.
An interactive CLI that pulls YouTube and YouTube Music playlists through yt-dlp and FFmpeg into resumable, cached M4A downloads with embedded album art.
Download any YouTube playlist or custom Excel song list as high-quality M4A tracks — with embedded album art, metadata tags, and a ready-to-play .m3u playlist file. Works with YouTube, YouTube Music, private playlists, and YouTube Premium content.
streamlist has two modes: Download and Make edits.
- Downloads the best available audio stream for each track
- Re-encodes to M4A (AAC 256k) using the best encoder available on your machine
- Asks how to handle non-square thumbnails: keep as-is, center crop, smart crop, or padded blur
- Embeds the video thumbnail as album art at original quality (converts png/webp to JPEG only if needed)
- Writes tags: title, artist, album, album artist, track number
- Generates a
.m3uplaylist file so any music player can load the full collection - Resume & sync — skips tracks already downloaded; re-running only fetches what's missing
- Per-playlist cache — instant re-run skipping with zero network calls for cached tracks
- Auto-retry — failed tracks are never cached, so the next run retries them automatically
- Cache rebuild — if a folder has existing files but no cache (older download), the cache is rebuilt from filenames before proceeding
- Fix cover art — re-download thumbnails for an existing playlist and re-embed, without touching the audio. Four styles to choose from:
- Center crop — crops to the middle square of the image
- Smart crop — entropy-based crop that finds the most visually interesting region
- Padded blur — places the original image centered on a blurred, zoomed background (Spotify-style)
- Restore original — re-embeds the raw YouTube thumbnail with no transform
- Rename album / album artist — rewrite the album name and album artist tags across all tracks in a folder, independently of the folder name. Audio is never touched.
Output structure:
My Playlist/
Track One.m4a
Track Two.m4a
Track Three.m4a
My Playlist.m3u
streamlist_cache.json
Python 3.8+
Install dependencies:
pip install yt-dlp openpyxl tqdm Pillow mutagenFFmpeg (required for encoding):
# macOS
brew install ffmpeg
# Ubuntu / Debian
sudo apt install ffmpeg
# Windows
# Download from https://ffmpeg.org/download.htmlpython streamlist.pyThe script will ask which mode you want:
- Download — walks you through cookies, source (URL or Excel), and playlist name
- Make edits — prompts for a folder path and edit type
Passing --url or --excel skips the top-level menu and goes straight to download.
python streamlist.py --url "https://music.youtube.com/playlist?list=..."python streamlist.py --excel my_songs.xlsx --name "My Playlist"python streamlist.py
# → Make edits → Fix cover art → enter folder path → choose crop styleOr point directly to the folder:
python streamlist.py --out "~/Music/My Playlist"
# → Make edits → Fix cover art → choose crop stylepython streamlist.py \
--url "https://youtube.com/playlist?list=..." \
--name "Summer Mix" \
--out ~/Music \
--browser safari \
--no-notification| Flag | Description |
|---|---|
--url |
YouTube or YouTube Music playlist / video URL |
--excel |
Path to .xlsx file with track list |
--name |
Override the playlist / output folder name |
--out |
Output directory (default: current directory) |
--browser |
Browser to read cookies from: safari, chrome, firefox, edge, brave, opera |
--no-notification |
Disable the completion sound |
Create a .xlsx file with these columns. Only url is required.
| url | title | artist |
|---|---|---|
| https://youtube.com/watch?v=... | Get Lucky | Daft Punk |
| https://music.youtube.com/watch?v=... | Blinding Lights | The Weeknd |
| https://youtube.com/watch?v=... | (fetched from YouTube if blank) |
A sample_playlist.xlsx template is included in this repo.
streamlist is designed to be run repeatedly against the same playlist without re-downloading anything.
After each successful download, the track's metadata is written to streamlist_cache.json inside the playlist folder:
{
"https://www.youtube.com/watch?v=abc123": {
"title": "Get Lucky",
"artist": "Daft Punk",
"filename": "Get Lucky.m4a",
"duration": 248
}
}All URLs are normalized to www.youtube.com before being used as cache keys, so the same track is always recognized whether the playlist returns a youtube.com or music.youtube.com URL.
On re-runs:
- Cache hit → track is skipped instantly with zero network calls
- Cache miss → track is fetched and downloaded as normal
- Failed tracks → never written to cache, so the next run retries them automatically
Just re-run the same command. Existing tracks are skipped; only new ones are downloaded. The .m3u is always rewritten to reflect the full current playlist.
fix_cache.py fixes mismatches between streamlist_cache.json and the actual files on disk. Run it whenever the cache and filenames have drifted out of sync.
python fix_cache.py /path/to/playlist/folderWhen run, it prompts for any title suffixes you want stripped:
Enter suffix(es) to strip from track titles (e.g. '. [indie playlist]').
Press Enter with no input when done.
Strip suffix (or Enter to skip): . [indie playlist]
Strip suffix (or Enter to skip):
Just press Enter immediately to skip suffix stripping and only do the rename/cache sync.
You can also pass suffixes directly as flags:
python fix_cache.py /path/to/folder --strip ". [indie playlist]" --strip " (official audio)"What it does:
- Normalizes
music.youtube.com→www.youtube.comcache keys, collapsing duplicates (keeps the cleaner/shorter title) - Strips
01 -style number prefixes from filenames (legacy format migration) - Strips any suffixes you specify from track titles in the cache and filenames on disk
- If you already manually renamed files on disk, updates the cache to match without touching the files
- Disambiguates tracks that share the same title by appending the artist name:
Song Title (Artist Name).m4a
If a playlist folder already has .m4a files but no streamlist_cache.json (e.g. downloaded with an older version of streamlist), the cache is automatically rebuilt from the existing filenames before proceeding:
No cache found but 28 existing file(s) detected — rebuilding cache from filenames...
✓ Rebuilt cache for 28 track(s).
At the end of every run:
✓ Done! 3 downloaded | 28 skipped | 1 failed
Some tracks in YouTube Music playlists have a separate video ID that YouTube blocks from all third-party downloaders — even with valid login cookies and a Premium subscription. These are recordings that YouTube Music licenses exclusively and deliberately restricts at the API level. yt-dlp receives a hard "Video unavailable" response with no workaround.
How to identify them: the error will be:
✗ Failed: ERROR: [youtube] <video_id>: Video unavailable. This video is not available
even after the cookie retry attempt.
Workarounds:
- Search for the same song on regular YouTube — a different, downloadable upload almost always exists. Add that URL to your Excel file instead.
- Add the file manually (from another source) and add a dummy entry to
streamlist_cache.jsonso streamlist stops retrying it:"https://music.youtube.com/watch?v=<video_id>": { "title": "Song Name", "artist": "Artist Name", "filename": "Song Name.m4a", "duration": 0 }
Videos with a YouTube age restriction may fail even with cookies if the account has not verified its age in the browser session.
Videos restricted to specific countries will fail if your IP address is outside the allowed region. A VPN set to the appropriate country before running the script is the only workaround.
streamlist supports Premium-only and private/unlisted playlists by reading cookies directly from your browser session.
Setup for macOS (Safari / Chrome):
- Make sure you're logged into YouTube in your browser
- Go to System Settings → Privacy & Security → Full Disk Access
- Add Terminal (or VS Code if running from there)
- Run the script and choose your browser when prompted
macOS needs Full Disk Access to let yt-dlp read the browser's cookie database.
Windows: Use chrome or firefox — Safari is not available on Windows.
- Encoder detection — tests
aac_at(Apple hardware) →libfdk_aac→aac(FFmpeg native) and uses the best available - Cache check — loads
streamlist_cache.json; rebuilds it from existing files if missing - Input parsing — fetches playlist metadata via yt-dlp (no download yet) or reads your Excel file
- Per-track pipeline (inside a temp folder that auto-cleans):
- Skips instantly if URL is in cache and file exists
- Downloads best audio stream (opus/AAC/webm, whatever YouTube offers)
- Downloads thumbnail → embeds as-is if JPEG, converts only if png/webp
- Re-encodes to M4A with embedded art and tags
- Writes entry to cache immediately after success
- M3U generation — rewrites the portable playlist file with relative paths
| Platform | Status |
|---|---|
| macOS | ✓ Full support (hardware encoder on Apple Silicon / Intel) |
| Linux | ✓ Full support |
| Windows | ✓ Works (use chrome or firefox for cookies; Safari not available) |
MIT
