Name: "Ripuz" is a play on Qobuz — ripping music from a service that ends in uz.
Self-hosted Qobuz → MusicBrainz Picard music downloader for UNRAID (and any Linux Docker host).
Downloads your Qobuz library at a configurable quality (MP3 320 → FLAC 24-bit/192kHz), enriches tags with MusicBrainz Picard headlessly, and organises files into your music library as Artist/Album/Title.FLAC.
| Mode | What it does |
|---|---|
| Track | Download a single Qobuz track |
| Album | Download a single Qobuz album |
| Playlist | Download every track in a Qobuz playlist |
| Discography | Download an artist's full catalog from a Qobuz artist URL |
| Expand to albums | Resolve each track in a playlist to its full album, then download every album |
| Expand discographies | Resolve each artist in a playlist to their full catalog, then download everything |
| Explicit upgrade | Replace clean tracks with their explicit versions — from a playlist or by scanning your entire /music library |
All bulk modes (Discography, Expand, Explicit upgrade) are two-phase: Ripuz first resolves the full album list, shows you a plan with an estimated download size, and waits for your confirmation before downloading anything. You can cancel at any time.
Additional capabilities:
- Per-album Picard tagging (MusicBrainz lookup, headless under Xvfb)
- Configurable quality tier via the Settings UI
- Optional synced
.lrclyrics download (Plex sidecar format) - Optional prefer-explicit toggle for playlist downloads
- Disk-space floor guard — aborts a bulk job before the drive fills up
- Cross-job deduplication — two concurrent bulk jobs never download the same album twice
- HTTP Basic Auth (optional) for remote access
Qobuz no longer allows third-party password login. Extract your browser session token:
- Log in at play.qobuz.com
- Press
F12→ Application tab → Local Storage →https://play.qobuz.com - Find the
localuserkey and copy thetokenstring from the JSON value. - Paste it in Ripuz Settings.
docker run -d --name ripuz --restart unless-stopped \
-p 8080:8080 \
-v /mnt/user/appdata/ripuz/config:/config \
-v /mnt/user/appdata/ripuz/downloads:/downloads \
-v /mnt/user/data/media/music:/music \
-e PUID=99 -e PGID=100 -e UMASK=022 \
suvirp/ripuz:latestThen open http://your-server-ip:8080.
The image is published to Docker Hub (suvirp/ripuz) and GitHub Container Registry (ghcr.io/Suvir0/ripuz) on every push to main.
git clone https://github.com/Suvir0/ripuz.git
cd ripuz
docker compose up -dUse the included ripuz.xml template. Edit the volume paths:
| Container path | Suggested host path | Purpose |
|---|---|---|
/config |
/mnt/user/appdata/ripuz/config |
DB, qobuz-dl config, Picard.ini |
/downloads |
/mnt/user/appdata/ripuz/downloads |
Scratch space (fast drive recommended) |
/music |
/mnt/user/data/media/music |
Your Plex/Roon music library root |
Set PUID and PGID to match your UNRAID nobody/users (default 99/100).
docker build -t ripuz .docker pull suvirp/ripuz:latest && \
docker rm -f ripuz && \
docker run -d --name ripuz --restart unless-stopped \
-p 9999:8080 \
-v /mnt/user/appdata/ripuz/config:/config \
-v /mnt/user/data/misc:/downloads \
-v /mnt/user/data/media/music:/music \
-e PUID=99 -e PGID=100 -e UMASK=022 \
-e CONFIG_DIR=/config -e DOWNLOADS_DIR=/downloads \
-e MUSIC_DIR=/music -e APP_PORT=8080 -e LOG_LEVEL=info \
suvirp/ripuz:latest- Settings — paste your Qobuz token, set music quality and optional toggles.
- Add download — pick a mode, paste a Qobuz URL (or enable "scan my /music library" for Explicit upgrade).
- Bulk jobs — after resolving, Ripuz shows you a plan:
- Number of albums to download, estimated GB, albums already present or claimed by another job.
- Click Confirm to start downloading, or Cancel to discard.
- Jobs tab — watch live log output per job; cancel or delete finished jobs.
- Result — files land in
MUSIC_DIR/<Artist>/<Album>/<Title>.FLAC.
| Variable | Default | Description |
|---|---|---|
PUID |
99 |
User ID the app runs as (UNRAID nobody) |
PGID |
100 |
Group ID (UNRAID users) |
UMASK |
022 |
File permissions mask |
CONFIG_DIR |
/config |
Config + SQLite DB directory |
DOWNLOADS_DIR |
/downloads |
Scratch download directory |
MUSIC_DIR |
/music |
Final music library root |
APP_PORT |
8080 |
WebUI listening port |
LOG_LEVEL |
info |
Python log level (debug, info, warning, error) |
QOBUZ_QUALITY |
27 |
Startup default quality tier (overridden by Settings UI once saved) |
PICARD_TIMEOUT |
120 |
Seconds before a Picard run is killed per album |
PICARD_BIN |
picard |
Override the Picard binary path |
DISK_FLOOR_GB |
20 |
Abort a bulk download if free space on DOWNLOADS_DIR drops below this |
MAX_ALBUMS_PER_JOB |
300 |
Cap on albums a single bulk job will download |
EXPAND_MIN_ARTIST_TRACKS |
2 |
Min tracks an artist must appear on in a playlist before their full catalog is resolved |
EXPAND_JUNK_PATTERNS |
(karaoke / tribute / …) | Pipe-separated regex patterns; matching albums are excluded from expand plans |
RIPUZ_AUTH_USER |
ripuz |
Basic Auth username (only used when RIPUZ_AUTH_PASS is set) |
RIPUZ_AUTH_PASS |
(empty) | Set to enable HTTP Basic Auth; leave empty to disable |
CHOWN_MUSIC_RECURSIVE |
0 |
Set to 1 to chown /music recursively on startup |
GIT_SHA |
dev |
Injected at build time; displayed as the version in the UI |
| Value | Format |
|---|---|
5 |
MP3 320 kbps |
6 |
FLAC 16-bit / 44.1 kHz (CD quality) |
7 |
FLAC 24-bit / 96 kHz (hi-res) |
27 |
FLAC 24-bit / 192 kHz (max) |
The following settings are persisted in the DB and configurable via the Settings tab:
| Setting | Description |
|---|---|
qobuz_token |
Your Qobuz session token |
downloads_dir |
Override the scratch download path at runtime |
music_dir |
Override the music library path at runtime |
music_quality |
Quality tier (5 / 6 / 7 / 27) |
download_lyrics |
Download synced .lrc lyrics sidecars (Plex-compatible) |
prefer_explicit |
Prefer explicit versions when downloading playlists |
HTTP request
│
▼
app/main.py FastAPI app + lifespan + REST API (/api/jobs, /api/settings, /healthz)
│
▼
app/jobs.py SQLite job queue + single background worker thread
│
▼
app/pipeline.py Orchestration: download → tag → move → verify → cleanup
├── app/qobuz_client.py Qobuz API: playlist/artist/album/track resolution, explicit matching
├── app/qobuz_cli.py Subprocess wrapper for `python -m qobuz_dl dl`
├── app/picard.py Headless Picard runner (xvfb-run, per-album, best-effort)
├── app/mover.py Read mutagen tags, move files to Artist/Album/Title.FLAC
└── app/structure.py FLAC discovery, album-dir listing, empty-dir cleanup, verify
app/db.py SQLite schema: jobs, settings, album_cache; stale-job purge; cross-job dedup
app/settings_store.py Persist settings to DB; render qobuz-dl config.ini
app/config.py Env-driven paths and constants
Bulk job lifecycle:
queued → resolving → awaiting_confirm → (user clicks Confirm) → confirmed
→ downloading → tagging → verifying → done / done_with_warnings / error
Simple jobs (track, album, playlist) skip the resolve/confirm phases.
app/
main.py FastAPI app + API routes
config.py Env-driven paths and constants
db.py SQLite: jobs, settings, album_cache
settings_store.py Persist settings + render qobuz-dl config.ini
qobuz_client.py Qobuz API resolution and explicit-match logic
qobuz_cli.py Subprocess wrapper for qobuz-dl
picard.py Headless Picard runner (xvfb-run + remote commands)
pipeline.py Orchestrate download → tag → move → verify → cleanup
jobs.py SQLite job queue + background worker thread
mover.py FLAC tag reader + file mover (Artist/Album/Title.FLAC)
structure.py FLAC discovery, album-dir listing, structure verification
explicit.py Fuzzy matching to find explicit twins of clean tracks/albums
static/ index.html, app.js, style.css
picard/
Picard.ini Picard config template (tag-only; rename/move handled by mover.py)
scripts/
drain_downloads.py One-shot: move all pending downloads into the library (no re-tag)
normalize_library.py Relocate flat FLACs into Artist/Album/Title structure from tags
tests/ 329 unit tests, all mocked (no real Qobuz account or Picard needed)
Dockerfile python:3.12-slim + Picard + Xvfb + ffmpeg + gosu
entrypoint.sh PUID/PGID/UMASK drop-privileges + Picard.ini path injection
docker-compose.yml Local dev / UNRAID reference
ripuz.xml UNRAID Community-Apps template
python -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python -m pytest -vNo real Qobuz account, Picard installation, or Docker is required — all external calls are mocked.
Ripuz ships with no authentication enabled by default for trusted-LAN homelab use.
- To enable HTTP Basic Auth, set
RIPUZ_AUTH_PASS(and optionallyRIPUZ_AUTH_USER). - Do not expose port
8080to untrusted networks; use a reverse proxy, VPN, or firewall. - For stricter local file permissions on config/DB files, set
UMASK=077. - The
/healthzendpoint is always accessible without credentials (for container health checks).
| Symptom | Likely cause | Fix |
|---|---|---|
Job status done_with_warnings |
Some albums were geo-blocked, unavailable, or failed to move | Check the job log; re-run failed albums individually |
Job status error after 12 h |
Stale-job purge triggered (no status change for 12 h) | Restart the container; increase PICARD_TIMEOUT if Picard is hanging |
| Picard times out | Large album or slow MusicBrainz lookup | Increase PICARD_TIMEOUT (default 120); files are still moved using embedded tags |
| Bulk job aborted mid-way | Disk floor guard triggered | Free space on DOWNLOADS_DIR; adjust DISK_FLOOR_GB |
| Explicit upgrade finds no matches | Clean track has a different album artist or duration | Check the job log — no-match tracks are reported; use playlist mode instead of library |
UI shows version dev |
No GIT_SHA build arg was passed |
Only affects the displayed version string; not functional |
Ripuz orchestrates downloads using your own Qobuz subscription via the official authenticated API. No music is bundled or distributed with this software. Use for personal archival purposes only; this may violate Qobuz's Terms of Service. You are responsible for compliance with applicable laws and terms in your jurisdiction.
See CREDITS.md for third-party attribution and licenses.