A Python-based static site generator that turns iTunes XML playlist exports into a magazine-style music website, hosted on GitHub Pages.
Live example: hearmycovers.com
- Reads one or more iTunes XML playlist files
- Looks up album artwork and metadata via MusicBrainz, Deezer, and Cover Art Archive
- Generates a multi-page static website — one page per playlist, plus a searchable landing page
- Outputs clean HTML + CSS ready to push to GitHub Pages (free hosting)
- Supports a covers mode where each song is treated as a cover version, with artwork and links for the cover artist's release
- 🎨 Magazine dark theme — warm gold accents, near-black background (Mojo/Uncut energy)
- 🖼️ Smart artwork lookup — tries embedded MP3/M4A tags (APIC/covr), Deezer, Cover Art Archive, local overrides
- 🔗 Rich link pills per track — review sites (Pitchfork, BBC, Guardian, NME, etc.) and database links (AllMusic, Discogs, RYM, MusicBrainz, Spotify, YouTube, Apple Music, Deezer, Bandcamp and more)
- 🔍 Search across song title, cover artist, and original artist
- 🗂️ Sort playlists alphabetically or by date added
- ⚙️ Fully configurable via
config.ini— no hardcoded paths or settings - 🌐 Custom domain support — auto-generates
CNAMEfile on every build - 📦 MBID overrides — force specific MusicBrainz release IDs per track via a JSON sidecar
- Python 3.10+
- Required packages:
requests,mutagen
Double-click setup_env.bat.
This creates a self-contained .venv folder inside the scripts directory and installs all required packages. It uses the py launcher to find Python automatically — it is completely independent of whichever Python happens to be on your PATH.
You only need to do this once. The
.venvfolder persists between builds.
Edit the [paths] section to point at your files:
[paths]
xml_input_dir = C:\path\to\your\iTunes\playlists\
output_dir = C:\path\to\output\folder\
art_cache_dir = C:\path\to\output\folder\.art_cache\
links_cache = C:\path\to\output\folder\.links_cache.jsonSet your custom domain (or leave blank for GitHub Pages default URL):
[site]
custom_domain = yourdomain.comList your playlists oldest-first in the [playlists] section:
[playlists]
playlist_order =
my-first-playlist
my-second-playlistThe slug is the playlist name lowercased with spaces replaced by hyphens.
Export playlists from iTunes/Music: File → Library → Export Playlist… (choose XML format). Place the .xml files in your xml_input_dir.
Double-click rebuild_site.bat.
Double-click upload_site.bat — it reads output_dir from config.ini and force-pushes to your GitHub Pages repo.
itunes-playlist-website/
├── build_site.py # Site orchestrator — builds all playlists + landing page
├── playlist_generator.py # Per-playlist engine — artwork, metadata, HTML generation
├── stats.py # Statistics page generator
├── diagnose_artwork.py # Standalone artwork diagnostic tool (no side effects)
├── style.css # Magazine dark theme stylesheet
├── config.ini # All settings (paths, MusicBrainz, artwork, links, etc.)
├── requirements.txt # Python package dependencies
├── setup_env.bat # Windows: create .venv and install dependencies (run once)
├── rebuild_site.bat # Windows: build the site using .venv
├── upload_site.bat # Windows: push output folder to GitHub Pages using .venv
├── diagnose_artwork.bat # Windows: run artwork diagnostics for a playlist XML
└── itunes_playlist_site.txt # Full help manual
If some tracks are missing artwork after a build, run the diagnostic tool to see exactly what is happening for each track — without rebuilding the site or touching any cache files:
diagnose_artwork.bat "C:\path\to\your\Playlist.xml"
Or to check a single track by number:
diagnose_artwork.bat "C:\path\to\your\Playlist.xml" 3
The tool reports, per track: file found/missing, embedded APIC/covr frames (count, size, mime type), Deezer ID in tags, MusicBrainz search strategy that matched (or failed), and Cover Art Archive availability.
For any track where automatic artwork lookup fails or you want a specific image, create a sidecar JSON file next to your XML:
My Playlist_overrides.json
{
"Track Name": {
"art_url": "https://example.com/my-image.jpg"
}
}Or place an image in your art_cache_dir and reference it with "art_local": "filename.jpg".
You can also force a specific MusicBrainz release ID:
{
"Ring of Fire": {
"mbid": "c1aa4fa7-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
}When covers_mode = true in config.ini, the site is optimised for playlists of cover versions:
- Artwork is sourced for the cover artist's release, not the original
- Each card shows: Cover Artist — Album (Year)
- Original artist is shown separately and is searchable
- Link pills target the cover artist's release on each platform
See itunes_playlist_site.txt for the full manual, or run:
python build_site.py --help
Key config sections:
| Section | Purpose |
|---|---|
[paths] |
Input/output directories and cache files |
[build] |
covers_mode, force_rebuild, verbose |
[musicbrainz] |
API settings, rate limiting, search depth |
[artwork] |
Image sizes, Deezer timeout, thumbnail size |
[site] |
Title, tagline, accent colour, custom domain |
[cdn] |
Fuse.js and favicon CDN URLs |
[links] |
Which review/database sites to show; search URL templates |
[search_terms] |
Per-site query mode (title+artist vs album+artist) |
[playlists] |
Ordered list of playlist slugs (oldest first) |
- Create a GitHub repo for your output folder
- Enable GitHub Pages in repo Settings → Pages → Deploy from branch (
main) - (Optional) Add a custom domain in Pages settings and set DNS A records to:
185.199.108.153 185.199.109.153 185.199.110.153 185.199.111.153 - Set
custom_domaininconfig.ini— theCNAMEfile is written automatically on every build
| Version | Changes |
|---|---|
| V2.9.1 | Self-contained .venv workflow: setup_env.bat creates isolated Python environment; all .bat files updated to use .venv\Scripts\python.exe; requirements.txt added; diagnose_artwork.bat added |
| V2.9 | Navigation order fixed (Playlists → Search → Statistics); diagnose_artwork.py standalone diagnostic tool added |
| V2.8 | Statistics page added (By the Numbers, Top Original Artists, Songs By Decade, Most Prolific Cover Artists) |
| V2.6.1 | M4A support: embedded artwork (covr atom) and Deezer ID (freeform atom) now extracted from M4A/AAC files |
| V2.6 | Bug fixes: CSS class mismatches, unified slugify(), per-thread requests.Session, unclosed file handle |
| V2.5 | site.json fully removed; all settings in config.ini; playlist_order moved to [playlists] section |
| V2.4.1 | Bug fixes: constructed link templates, per-site search terms, search_attempts depth, thumbnail_size CSS variable |
| V2.4 | Full config.ini extraction — all settings moved out of code |
| V2.3 | PyInstaller EXE support |
| V2.2 | MP3 embedded APIC artwork extraction |
| V2.1 | Additional hyperlinked sources (Pitchfork, Discogs, RYM, etc.) |
| V2.0 | Multi-page static site; GitHub Pages deployment |
| V1.x | Single-playlist HTML poster generator |
MIT — do whatever you like with it.