One-way sync from a Discogs vinyl collection to Lidarr.
Records present in Discogs but absent from Lidarr are added. The sync is additive — nothing is removed automatically. A separate audit / purge workflow lets you review and selectively remove albums that are no longer in your collection. The script is safe to re-run repeatedly.
- Fetch your Discogs collection (vinyl records only).
- Resolve each record's Discogs IDs to MusicBrainz UUIDs — the IDs Lidarr requires. Results are cached on disk to avoid re-querying on subsequent runs.
- Diff the resolved IDs against your current Lidarr library.
- Add artists and albums that are missing. Artists are added before their albums.
- Python 3.11
- uv —
curl -LsSf https://astral.sh/uv/install.sh | sh - A Discogs account with a collection
- A running Lidarr instance
git clone https://github.com/youruser/discogs-lidarr-sync
cd discogs-lidarr-sync
uv syncCopy .env.example to .env and fill in your values:
cp .env.example .env| Variable | Description |
|---|---|
DISCOGS_TOKEN |
Personal access token from discogs.com/settings/developers |
DISCOGS_USERNAME |
Your Discogs username |
LIDARR_URL |
Base URL of your Lidarr instance, e.g. http://192.168.1.10:8686 |
LIDARR_API_KEY |
Found at Lidarr → Settings → General → Security → API Key |
LIDARR_ROOT_FOLDER |
Root folder path configured in Lidarr, e.g. /music |
LIDARR_QUALITY_PROFILE_ID |
Integer ID of the quality profile to use — run discogs-lidarr-sync profiles to list them |
LIDARR_METADATA_PROFILE_ID |
Integer ID of the metadata profile to use — run discogs-lidarr-sync profiles to list them |
MBZ_CACHE_PATH |
(optional) Path to the MBZ lookup cache. Default: .cache/mbz_cache.json |
SPOTIFY_CLIENT_ID |
Client ID from your Spotify developer app |
SPOTIFY_CLIENT_SECRET |
Client secret from your Spotify developer app |
SPOTIFY_REDIRECT_URI |
Must be set to http://127.0.0.1:8888/callback — add this exact URI in the Spotify app dashboard (note: 127.0.0.1, not localhost) |
SPOTIFY_PLAYLIST_ID |
(optional) Spotify playlist ID — use this if auto-creation fails with a 403 (see Spotify 403 workaround) |
SPOTIFY_TOKEN_CACHE_PATH |
(optional) Path for the cached OAuth token. Default: .cache/spotify_token |
SPOTIFY_SEARCH_CACHE_PATH |
(optional) Path for the Spotify album search cache. Default: .cache/spotify_cache.json |
uv run discogs-lidarr-sync syncOptions:
| Flag | Description |
|---|---|
--dry-run |
Show what would be added without making any changes |
--verbose / -v |
Print each item as it is processed |
--config PATH |
Path to a custom .env file (default: .env) |
A colour-coded summary table is printed at the end of every run. A timestamped JSON report is written to runs/.
uv run discogs-lidarr-sync statusFetches and displays the current Discogs vinyl count and Lidarr artist/album counts, including a count of ghost albums (see Ghost albums below). No changes are made.
uv run discogs-lidarr-sync profilesPrints a table of all quality profiles and metadata profiles configured in your Lidarr instance, with their numeric IDs. Only LIDARR_URL and LIDARR_API_KEY need to be set — run this command before filling in the profile ID variables in .env.
uv run discogs-lidarr-sync auditCompares auditable Lidarr albums against your current Discogs vinyl collection and exports a CSV of albums that have no matching Discogs record. Auditable means: monitored, or unmonitored but with files on disk. Ghost albums (unmonitored, no files) are excluded — use clean-ghosts to remove those automatically.
All rows default to action=delete. Open the CSV in a spreadsheet, change action to keep for any album you want to retain, then pass it to purge. The monitored column shows whether the album is currently monitored in Lidarr, which helps distinguish intentionally-kept unmonitored albums from stray imports.
Options:
| Flag | Description |
|---|---|
--output PATH |
Path for the output CSV. Default: audit/audit_<timestamp>.csv |
--verbose / -v |
Print each album found during the audit |
--config PATH |
Path to a custom .env file (default: .env) |
A summary table is printed at the end showing how many albums were matched, how many were exported to the CSV, and how many could not be resolved via MusicBrainz.
uv run discogs-lidarr-sync purge audit/audit_20240101T120000Z.csvReads an audit CSV produced by the audit command and deletes every album whose action column is set to delete. After album deletions, any artist that has no remaining monitored albums is also removed.
Always review the CSV carefully before running purge. Use --dry-run to confirm what will be deleted.
Options:
| Flag | Description |
|---|---|
--dry-run |
Show what would be deleted without making any changes |
--delete-files |
Also delete files from disk. Default: remove Lidarr entries only, leave files untouched |
--verbose / -v |
Print each album and artist as it is deleted |
--config PATH |
Path to a custom .env file (default: .env) |
# 1. Generate the audit CSV
uv run discogs-lidarr-sync audit
# 2. Open the CSV in a spreadsheet. Change action=keep for anything you want to retain.
# 3. Preview what will be deleted
uv run discogs-lidarr-sync purge audit/audit_<timestamp>.csv --dry-run --verbose
# 4. Execute the purge
uv run discogs-lidarr-sync purge audit/audit_<timestamp>.csv --verboseuv run discogs-lidarr-sync clean-ghostsFinds and removes every ghost album from Lidarr. Because ghost albums have never been monitored and hold no files, they can be deleted safely without a review step. Artists left with no remaining monitored or on-disk content are also removed.
Options:
| Flag | Description |
|---|---|
--dry-run |
Show what would be deleted without making any changes |
--delete-files |
Also delete files from disk. Default: remove Lidarr entries only |
--verbose / -v |
Print each album and artist as it is deleted |
--config PATH |
Path to a custom .env file (default: .env) |
uv run discogs-lidarr-sync spotify-syncFetches your Discogs vinyl collection, searches Spotify for each album, and adds all matched tracks to a playlist. The sync is additive — tracks are never removed. The playlist is created automatically if it does not exist.
First run: a browser tab opens for Spotify OAuth login. The resulting token is cached to .cache/spotify_token and silently refreshed on every subsequent run.
Options:
| Flag | Description |
|---|---|
--dry-run |
Search Spotify and show what would be added without modifying the playlist |
--rebuild |
Clear the playlist and rebuild it from scratch instead of adding incrementally |
--playlist-name NAME |
Playlist name to look up or create; has no effect when SPOTIFY_PLAYLIST_ID is set |
--verbose / -v |
Print each album as it is processed |
--config PATH |
Path to a custom .env file (default: .env) |
A colour-coded summary table is printed at the end showing how many albums were added, already present, or not found on Spotify.
- Go to the Spotify Developer Dashboard and create an app.
- Under Redirect URIs add exactly:
http://127.0.0.1:8888/callback - Copy the Client ID and Client Secret into
.env.
Spotify restricts playlist creation for apps in development mode. If spotify-sync exits with a 403 error:
- Create the playlist manually in the Spotify app.
- Open it → ··· → Share → Copy link to playlist. The URL ends with the playlist ID:
https://open.spotify.com/playlist/3cEYpjA9oz9GiPac4AsH4n - Add
SPOTIFY_PLAYLIST_ID=<id>to your.envfile. - Re-run
spotify-sync.
uv run discogs-lidarr-sync clear-cacheDeletes .cache/mbz_cache.json (or the path set in MBZ_CACHE_PATH). You will be prompted for confirmation. The cache will be rebuilt from scratch on the next sync run.
When this tool adds an artist to Lidarr it uses Lidarr's monitor="none" option, which means Lidarr immediately indexes the artist's entire MusicBrainz discography as unmonitored catalog entries. This is by design — it lets Lidarr know the artist exists without triggering a download of their whole back catalogue.
The side effect is that your Lidarr library can accumulate hundreds of these ghost albums: unmonitored entries with no files on disk, for albums you never asked Lidarr to track. They don't affect downloads but they do inflate album counts and make the library harder to read.
This tool handles them in two ways:
statusshows a ghost count so you know how many are present.clean-ghostsdeletes them all in one pass, without requiring a CSV review step. It is safe to run repeatedly.auditdeliberately excludes ghost albums from its CSV output. Including them would bury real deletion candidates under hundreds of irrelevant rows.
Discogs uses integer IDs; Lidarr requires MusicBrainz UUIDs. Resolving each record requires one or two MusicBrainz API requests, subject to a 1 request/second rate limit. A cold run over a 500-record collection takes around 8 minutes.
Results are cached in .cache/mbz_cache.json (gitignored). Subsequent runs skip already-resolved records and complete in seconds.
Failed lookups are also cached (with status: "failed") so the script does not waste API quota re-querying records that have no MusicBrainz entry.
If any vinyl records could not be matched to a MusicBrainz Release Group, they are appended to unresolved.log (tab-separated: Discogs release ID, artist, album title, status). Review this file periodically to identify records that may need manual attention in MusicBrainz or Lidarr.
# Run tests
uv run pytest
# Linter
uv run ruff check .
# Type checker
uv run mypy src/
# Record VCR cassettes for Discogs tests (requires credentials in .env)
uv run pytest tests/test_discogs_recorded.py --record-mode=all
# Record VCR cassettes for Lidarr tests (requires LIDARR_URL and LIDARR_API_KEY in .env)
uv run pytest tests/test_lidarr_recorded.py --record-mode=all
# Run live integration tests (requires full credentials in .env)
uv run pytest -m integrationPre-commit hooks run ruff and mypy on staged files automatically:
uv run pre-commit install