This script repurposes the star ratings in Navidrome by aligning them with Deezer's track rank. It fetches the Deezer popularity rank (0β1,000,000+) for each track via the public search API and maps it to a 1β5 star rating. Higher rank = more popular on Deezer. Optionally, ratings can be graded relative to the most popular track of the artist or album (--curve), which is more meaningful for niche libraries. No API key is required β the Deezer API is completely open.
This is a fork of krestaino/sptnr by Kevin Restaino, which originally used Spotify's popularity score. This fork replaces Spotify with Deezer's public API, removing the need for OAuth, client secrets, or any authentication.
- Features
- Requirements
- Quick Start (GHCR pre-built image)
- Building from this fork
- Using Docker Compose
- Running Natively (Python)
- Usage
- Examples
- Resuming Interrupted Sessions
- Managing Docker Containers
- Mapping Deezer Rank to Navidrome Ratings
- Relative Popularity Grading (
--curve) - Estimated Processing Times
- Importance of Accurate Metadata for Track Lookup
- Logs
- CI/CD (GitHub Actions)
- Credits
- Deezer Integration: Uses the public Deezer Search API (
api.deezer.com/search) β no API key or authentication needed. - Navidrome Integration: Updates track ratings in Navidrome based on Deezer's popularity rank.
- Relative Popularity Grading:
--curve album|artistgrades each track against the most popular track of its album or artist on Deezer, instead of the absolute global rank β ideal for niche libraries. - Flexible Processing: Process specific artists, albums, or a range of artists or albums.
- Skip Existing: Optionally skip tracks that are already starred (favorited) or already rated in Navidrome, preserving your manual curation.
- Preview Mode: Run the script in preview mode to see changes without making any actual updates.
- Logging: Detailed logging of the process, both in the console and to a file.
- Docker Support: Pre-built image available on GitHub Container Registry (GHCR) with multi-arch support (amd64 + arm64).
- Docker or Python 3.x
- Access to a Navidrome server
- No API keys or external accounts required (Deezer API is public)
Compatibility Note: While this script was built with Navidrome in mind, it should theoretically work on any Subsonic server.
The easiest way to run dztnr is using the pre-built Docker image published to GitHub Container Registry on every push to main. It supports both linux/amd64 and linux/arm64.
docker run -t --rm \
-e NAV_BASE_URL=https://your_navidrome.example.com \
-e NAV_USER=your_navidrome_username \
-e NAV_PASS=your_navidrome_password \
ghcr.io/scatolo/dztnr:latestTo pin a specific version (check packages for available tags):
docker run -t --rm \
-e NAV_BASE_URL=https://your_navidrome.example.com \
-e NAV_USER=your_navidrome_username \
-e NAV_PASS=your_navidrome_password \
ghcr.io/scatolo/dztnr:1.3.0Tip: Use
--rmto auto-remove the container when it finishes. If you want persistent logs, add-v ./logs:/usr/src/app/logs.
First run? Start in preview mode with a single artist to test connectivity:
docker run -t --rm \
-e NAV_BASE_URL=https://your_navidrome.example.com \
-e NAV_USER=your_admin_user \
-e NAV_PASS=your_admin_password \
ghcr.io/scatolo/dztnr:latest -p -l 1(-p = preview, no writes; -l 1 = only the first artist)
This is a fork of krestaino/sptnr. The main changes are:
- Deezer instead of Spotify β no OAuth, no client ID/secret, no API key
- Rating based on Deezer rank instead of Spotify popularity
- Optional relative popularity grading (
--curve album|artist) - Rate limiting at 0.2s per API call
- Pre-built images published to GHCR via GitHub Actions
git clone https://github.com/scatolo/dztnr.git
cd dztnr
docker build -t dztnr-local .
docker run -t --rm \
-e NAV_BASE_URL=https://your_navidrome.example.com \
-e NAV_USER=your_navidrome_username \
-e NAV_PASS=your_navidrome_password \
dztnr-localgit clone https://github.com/scatolo/dztnr.git
cd dztnr
cp .env.example .env
# Edit .env with your Navidrome URL and credentials
pip install -r requirements.txt
python dztnr.py -p -l 1 # preview mode, first artist only-
Create
docker-compose.yml: copydocker-compose.yml.exampleand fill in your Navidrome details.Using the pre-built GHCR image (recommended):
version: "3.8" services: dztnr: container_name: dztnr image: ghcr.io/scatolo/dztnr:latest environment: - NAV_BASE_URL=https://your_navidrome.example.com - NAV_USER=your_navidrome_username - NAV_PASS=your_navidrome_password volumes: - ./logs:/usr/src/app/logs
Or build locally:
version: "3.8" services: dztnr: container_name: dztnr build: . environment: - NAV_BASE_URL=https://your_navidrome.example.com - NAV_USER=your_navidrome_username - NAV_PASS=your_navidrome_password volumes: - ./logs:/usr/src/app/logs
-
Run the script:
docker-compose run --rm dztnrWith options:
docker-compose run --rm dztnr -p -l 1
-
Clone the repository or download the necessary files (
dztnr.py,requirements.txt,.env.example). -
Install dependencies:
pip install -r requirements.txt -
Configure environment variables. Rename
.env.exampleto.envand fill in your Navidrome credentials:cp .env.example .env # Edit .env β only NAV_BASE_URL, NAV_USER, NAV_PASS are needed -
Run the script:
python dztnr.py [options]
| Flag | Long form | Description |
|---|---|---|
-p |
--preview |
Preview mode β no changes written to Navidrome |
-a |
--artist ID |
Process a specific artist by Navidrome ID (repeatable) |
-b |
--album ID |
Process a specific album by Navidrome ID (repeatable) |
-s |
--start N |
Start processing from artist at index N (0-based) |
-l |
--limit N |
Process at most N artists from the start index |
--recent N |
Process the N most recently added albums | |
-d |
--debug |
Show additional debugging information (Deezer URLs, ranks, etc.) |
--curve album |
Grade tracks relative to the album's most popular track on Deezer | |
--curve artist |
Grade tracks relative to the artist's most popular track on Deezer | |
--skip-starred |
Skip tracks already starred/favorited in Navidrome | |
--skip-rated |
Skip tracks that already have a rating (>0) in Navidrome | |
-v |
--version |
Print version and exit |
| Method | Command |
|---|---|
| GHCR image | docker run -t --rm -e NAV_BASE_URL=... -e NAV_USER=... -e NAV_PASS=... ghcr.io/scatolo/dztnr:latest [options] |
| Local Docker | docker run -t --rm -e NAV_BASE_URL=... -e NAV_USER=... -e NAV_PASS=... dztnr-local [options] |
| Docker Compose (GHCR) | docker-compose run --rm dztnr [options] |
| Python | python dztnr.py [options] |
See what would be updated without making changes:
python dztnr.py -pdocker-compose run --rm dztnr -ppython dztnr.py -a <navidrome_artist_id>docker run -t --rm -e NAV_BASE_URL=... -e NAV_USER=... -e NAV_PASS=... ghcr.io/scatolo/dztnr:latest -a <navidrome_artist_id>Start from artist #10 and process the next 5:
python dztnr.py -s 10 -l 5python dztnr.py -b <album_id_1> -b <album_id_2>Process only the albums most recently added to Navidrome:
python dztnr.py --recent 10Show additional debugging information (Deezer lookup URLs, ranks, etc.):
python dztnr.py -dThe debug flag can be combined with any other option:
python dztnr.py --recent 10 -dUse these flags to preserve your manual ratings and favorites. Both flags can be combined.
Skip tracks you've already favorited (starred):
python dztnr.py --skip-starredSkip tracks that already have a rating:
python dztnr.py --skip-ratedCombine both to only process unrated, unstarred tracks:
python dztnr.py --skip-starred --skip-ratedWith preview and artist range:
python dztnr.py -p --skip-starred --skip-rated -s 0 -l 20If the session gets interrupted (network error, machine sleep, etc.), you can resume from where you left off.
Check the log file for the last processed artist. The log entry contains the index in brackets: Artist: ARTIST_NAME (ARTIST_ID)[INDEX].
Restart with -s INDEX:
python dztnr.py -s 42docker run -t --rm -e ... ghcr.io/scatolo/dztnr:latest -s 42docker run and docker-compose run create a new container each time. Use --rm to auto-remove them on exit, or clean up manually:
docker container pruneWarning: This removes all stopped containers on your system. Check with docker ps -a first.
The script translates Deezer's popularity rank into Navidrome's 5-star rating system:
| Deezer Rank | Rating | Description |
|---|---|---|
| 0 β 9,999 | 0 | Unknown / Niche |
| 10,000 β 99,999 | 1 | Low popularity |
| 100,000 β 299,999 | 2 | Moderately popular |
| 300,000 β 599,999 | 3 | Popular |
| 600,000 β 849,999 | 4 | Very popular |
| 850,000+ | 5 | Globally known |
With
--curve, this absolute mapping is replaced by the relative grading described below.
Absolute ranks are global: in niche or low-popularity libraries, most tracks end up with 1β2 stars. With --curve, each track is graded against the most popular track of the same scope on Deezer instead of the absolute global rank, so the reference is always the artist's or album's own biggest hit:
--curve artistβ reference is the artist's most popular track on Deezer. Resolved once per artist, applied to all of their albums.--curve albumβ reference is the most popular track of the album on Deezer. Resolved once per album.
ratio = track rank / reference rank, mapped to stars as follows:
| Ratio | Rating |
|---|---|
| β₯ 0.80 | 5 |
| 0.60 β 0.79 | 4 |
| 0.40 β 0.59 | 3 |
| 0.20 β 0.39 | 2 |
| > 0 and < 0.20 | 1 |
| 0 | 0 |
The most popular track of the artist/album always gets 5 stars. Any matched track keeps at least 1 star (0 is reserved for unmatched tracks).
- Artist:
https://api.deezer.com/search/artist?q=<artist>β first match βhttps://api.deezer.com/artist/<id>/top?limit=100; the highestrankamong the top tracks is the reference. - Album:
https://api.deezer.com/search/album?q=<artist> <album>β first match βhttps://api.deezer.com/album/<id>/tracks; the highestrankamong the tracks is the reference.
These lookups add 2 extra API calls per artist (or per album) and are subject to the same 0.2s rate limiting. If the reference cannot be resolved (no match on Deezer, missing rank data), the script falls back to absolute grading for that artist/album and logs a warning.
python dztnr.py --curve artist
python dztnr.py --curve artist -a <navidrome_artist_id>
python dztnr.py --curve album -b <album_id_1> -b <album_id_2>
python dztnr.py -p --curve artist -s 0 -l 20With a time.sleep(0.2) delay between Deezer API calls:
| Library Size (Tracks) | Estimated Time |
|---|---|
| 1,000 | ~5 minutes |
| 5,000 | ~25 minutes |
| 10,000 | ~50 minutes |
| 50,000 | ~4 hours |
| 100,000 | ~8 hours |
These estimates assume a stable network connection. Actual times may vary.
The script searches Deezer using the query artist + track. Deezer's search is fairly forgiving, but accurate artist and track titles significantly improve the match rate. Tag your music library with MusicBrainz for best results.
Logs are stored in the logs/ directory. Each execution creates a new log file named deezer-rank_<timestamp>.log. Delete old logs manually if needed.
r:450000 β β
:3 | Song Title
r:<number>β Deezer rank (0β1,000,000+), or??if not foundβ β :<rating>β Navidrome star rating assigned (0β5)
When --skip-starred or --skip-rated is used:
skipping (starred): Song Title
skipping (rated β
:4): Song Title
skipping (starred, rated β
:3): Song Title
- Green β Track matched and processed
- Yellow β Track skipped (starred or already rated)
- Red β Track not found on Deezer (
??)
Colors are terminal-only and stripped from log files.
On every push to main, a GitHub Actions workflow (.github/workflows/docker-ghcr.yml) builds a multi-arch Docker image (linux/amd64 + linux/arm64) tagged with the version from the VERSION file and latest, then pushes it to GitHub Container Registry at ghcr.io/scatolo/dztnr.
Pull requests also trigger a build (amd64 only, no push) to validate the Dockerfile.
Available tags: github.com/scatolo/dztnr/pkgs/container/dztnr
This project is a fork of krestaino/sptnr by Kevin Restaino, who originally created the script to sync Spotify popularity with Navidrome ratings. This fork adapts the codebase to use Deezer's public API instead, removing the need for any authentication or API keys while preserving all the original Navidrome integration logic, CLI flags, and Docker support.
