Using an AI assistant? Paste the block below into Claude Code, Codex, Cursor, or any LLM tool.
LLM Quick Reference
music-dl — local-first Tidal music manager. Downloads lossless/hi-res tracks,
manages a local library (any drive or NAS), plays everything in a browser GUI.
macOS, Linux, and Windows 10/11. Free and open-source.
INSTALL (macOS/Linux):
curl -fsSL https://raw.githubusercontent.com/alfdav/music-dl/master/scripts/install.sh | bash
INSTALL (Windows 10/11):
irm https://raw.githubusercontent.com/alfdav/music-dl/master/scripts/install.ps1 | iex
INSTALL (Headless/NAS):
curl -fsSL https://raw.githubusercontent.com/alfdav/music-dl/master/scripts/install-docker.sh | bash
INTERNAL LATEST (macOS):
curl -fsSL https://raw.githubusercontent.com/alfdav/music-dl/master/scripts/install-macos-local.sh | bash
INTERNAL LATEST (Windows 10/11):
irm https://raw.githubusercontent.com/alfdav/music-dl/master/scripts/install-windows-local.ps1 | iex
DEV: cd tidaldl-py && uv sync && music-dl gui # http://localhost:8765
TEST: cd tidaldl-py && uv run --extra test pytest
BUILD: cd tidaldl-py && uv sync && uv pip install pyinstaller && bun install && bunx tauri build --bundles dmg
STACK: Python 3.12+, FastAPI, vanilla JS, Tauri v2, Bun/discord.js for the optional bot.
REPO: monorepo — Python app under tidaldl-py/, Discord bot under apps/discord-bot/.
KEY PATHS:
DESIGN.md — agent-readable design tokens and visual identity contract
tidaldl-py/docs/design-system.md — detailed UI component/layout/animation rules
tidal_dl/gui/static/{app.js,style.css,index.html} — frontend (no framework)
tidal_dl/gui/__init__.py — FastAPI app factory
tidal_dl/gui/api/ — all API routes
tidal_dl/gui/security.py — CSRF, path validation, host validation
src-tauri/src/lib.rs — Tauri sidecar spawn + health poll
apps/discord-bot/ — optional private Discord voice bot
RULES:
- Audio: direct <audio src="..."> only. NO Web Audio API. Non-negotiable.
- Design: read DESIGN.md before UI work; keep it aligned with design-system.md and style.css.
- Security: localhost-only, CSRF on writes, path validation on file ops.
- Tooling: uv over pip, bun over npm.
A local-first music manager that connects to your Tidal account. Search the catalog, download tracks in lossless or hi-res quality, browse your local collection, and play everything directly in the browser. Your files, your NAS, your rules.
A setup wizard walks you through Tidal login and library configuration on first launch — no config files to edit.
The GUI can also start and recover the Tidal OAuth flow itself from the browser. Use music-dl login only if you want to authenticate from the terminal for CLI-first workflows.
Using an AI coding agent? Expand the LLM Quick Reference at the top and paste it into your agent.
Copy this into Terminal:
curl -fsSL https://raw.githubusercontent.com/alfdav/music-dl/master/scripts/install.sh | bashWhat it does:
- macOS Apple Silicon: downloads the latest
.dmg, verifies the GitHub release checksum, installs to/Applications, strips quarantine, then opensmusic-dl.app. - Linux x86_64: downloads the latest
.AppImage, verifies the GitHub release checksum, installs it as~/.local/bin/music-dl.
If macOS reports a DMG mount failure, rerun this current command first. The installer keeps progress output separate from the verified DMG path passed to hdiutil.
Copy this into PowerShell:
irm https://raw.githubusercontent.com/alfdav/music-dl/master/scripts/install.ps1 | iexDownloads the latest unsigned .msi, verifies the GitHub release checksum, then starts the Windows installer. SmartScreen warnings are expected for early unsigned builds. WSL is not required.
Copy this into Terminal:
curl -fsSL https://raw.githubusercontent.com/alfdav/music-dl/master/scripts/install-docker.sh | bashBuilds and starts the Docker Compose GUI at http://localhost:8765. Use this for Linux servers, NAS boxes, or machines where you do not want desktop packaging.
If you prefer to build locally, copy this into Terminal:
curl -fsSL https://raw.githubusercontent.com/alfdav/music-dl/master/scripts/install-macos-local.sh | bashOn success, it installs music-dl.app to /Applications/music-dl.app. Requires Xcode Command Line Tools, Rust, uv, and Bun.
Use these on our own machines when master has newer commits than the latest GitHub release and we do not want to cut binaries.
No local build tools, rolling edge channel:
curl -fsSL https://raw.githubusercontent.com/alfdav/music-dl/master/scripts/install.sh | MUSIC_DL_RELEASE_TAG=edge bash$env:MUSIC_DL_RELEASE_TAG = "edge"
irm https://raw.githubusercontent.com/alfdav/music-dl/master/scripts/install.ps1 | iex
Remove-Item Env:MUSIC_DL_RELEASE_TAGThese install the latest rolling edge artifact. Edge builds are produced automatically from master, replace the previous edge release assets, and point the app updater at the same edge manifest.
Build locally from source:
curl -fsSL https://raw.githubusercontent.com/alfdav/music-dl/master/scripts/install-macos-local.sh | bashWindows 10/11:
irm https://raw.githubusercontent.com/alfdav/music-dl/master/scripts/install-windows-local.ps1 | iexBoth installers clone or refresh the source checkout, build locally, and install the app. They require normal build tools. Source installers use SSH Git by default (git@github.com:alfdav/music-dl.git), so your machine needs GitHub SSH access.
See Building the Desktop App for the full prerequisite list and platform-specific commands. The short version for macOS:
cd tidaldl-py
uv sync && uv pip install pyinstaller
bun install
bunx tauri build --bundles dmg
# Output: src-tauri/target/release/bundle/dmg/- macOS/Linux desktop: rerun the same
install.shcommand. - Windows: rerun the same PowerShell command and follow the MSI installer.
- Headless/Docker: rerun the same
install-docker.shcommand. - macOS source build: rerun the same
install-macos-local.shcommand.
These same install one-liners appear in every release's notes. Canonical source:
docs/release/install-instructions.md— edit there and both README and release notes stay in sync.
Requires Python 3.12+ and ffmpeg.
uv tool install --from git+https://github.com/alfdav/music-dl.git#subdirectory=tidaldl-py music-dl
music-dl guiYour browser opens automatically. The wizard handles the rest.
- Library browser — your local collection organized by artist or album with page-sized/cached loading, a dedicated Recently Added category, album art, quality badges (24-bit, lossless, MQA), and instant search
- Home dashboard — recent additions, recently played, top artists, genres, repeat listening stats, and Continue Listening resume
- Tidal search & download — search the full Tidal catalog, see which tracks you already own, download what you're missing
- Quality upgrades — re-download existing tracks at higher quality without duplicates
- Duplicate cleanup — ISRC-based deduplication finds exact copies across your collection
- In-browser playback — play anything in your library, bit-perfect to your DAC, with persisted queue, volume, repeat/shuffle preferences, keyboard shortcuts, and queue actions
- Waveform visualizer — pre-computed amplitude data drives a ripple animation from the playhead, zero audio post-processing
- Playlist sync — point it at a Tidal playlist and it downloads only the tracks you don't have
- Favorites — mark tracks you love, access them from one place
- Local lyrics — synced
.lrcsidecars and embedded tag fallback, rendered in the player with no network lookups. Seetidaldl-py/docs/local-lyrics.md. - Setup wizard — first-run experience that walks you through Tidal login and library paths
- Discord bot (optional) — single-user, single-guild companion that streams and downloads from your library over Discord voice. Configure it and manage the bot service from the GUI's DJAI view, then use the auto-posted Discord remote panel for search, playlists, playback controls, and repeat. See
apps/discord-bot/README.mdandtidaldl-py/docs/bot-onboarding.md.
The GUI is the main experience, but everything works from the terminal too:
music-dl gui # launch the web UI
music-dl dl <URL> # download a track, album, or playlist
music-dl dl <URL> <URL> ... # download multiple URLs
music-dl dl --list urls.txt # download URLs from a file, one per line
music-dl dl <URL> --output ~/x # one-off output directory override
music-dl cfg # view/edit settings
music-dl login # authenticate with Tidal from the terminal
music-dl logout # clear stored Tidal credentials
music-dl sync # sync library database
music-dl import <file> # import a playlist from CSV/JSON
music-dl isrc-tag <path> # write ISRC tags to local audio files
music-dl source show # inspect Hi-Fi API/OAuth download source settings
music-dl scan add <PATH> # add and scan a local library directory
music-dl dl_fav tracks --since 2026-01-01 # download favorite tracks incrementally
music-dl gui --setup-bot # terminal fallback for Discord bot onboardingRun music-dl --help for the full list.
Settings are managed from the in-app Settings page. The config file lives at ~/.config/music-dl/settings.json.
| Setting | Default | What it does |
|---|---|---|
download_base_path |
~/download |
Where downloaded files go |
scan_paths |
"" |
Comma-separated local library roots |
quality_audio |
HI_RES_LOSSLESS |
Preferred audio quality |
skip_existing |
true |
Skip tracks you already have |
skip_duplicate_isrc |
true |
Skip tracks with matching ISRC codes |
download_source |
hifi_api |
Preferred stream source |
download_source_fallback |
true |
Fall back to OAuth when the preferred source fails |
graph TD
CLI["CLI · Typer<br/><code>cli.py</code>"] --> Core
GUI["GUI · FastAPI<br/><code>gui/</code>"] --> Core
Bot["Discord bot · Bun/discord.js<br/><code>apps/discord-bot</code>"] --> BotAPI["Bot API<br/><code>/api/bot/*</code>"]
BotAPI --> Core
Core["config.py<br/>Settings · Tidal"] --> DB["library_db.py<br/>SQLite + WAL"]
Core --> DL["download.py<br/>Download class"]
Tidal["Tidal API<br/>tidalapi"] --> DL
DL --> Tag["mutagen<br/>tagging"]
CLI, GUI, and the optional bot share the same backend core. CLI and GUI use the same singletons (Settings, Tidal, LibraryDB). The Discord bot stays thin: slash commands, queue state, and Discord voice transport live in Bun; source resolution, playable URLs, downloads, and auth stay in music-dl. The <audio> element plays files directly from source — no Web Audio API, no processing.
For deep dives, see:
- Backend Reference — API routes, DB schema, download pipeline, middleware, security model
- DESIGN.md — agent-readable design tokens and visual identity contract
- Design System — detailed UI component patterns, layout, and animation rules
- Docker Guide — detailed Docker usage, mounts, CLI commands, headless/cron
| Variable | Default | What it does |
|---|---|---|
MUSIC_DL_CONFIG_DIR |
~/.config/music-dl |
Config/credentials directory |
MUSIC_DL_BIND_ALL |
(unset) | Set to 1 to bind server to 0.0.0.0 (Docker sets this automatically) |
MUSIC_DL_HOST |
127.0.0.1 |
Docker compose host binding. Set to 0.0.0.0 for LAN access |
MUSIC_DL_PORT |
8765 |
Docker compose port mapping |
MUSIC_DL_CONFIG |
~/.config/music-dl |
Docker compose config volume source |
MUSIC_DL_DOWNLOADS |
~/Music |
Docker compose downloads volume source |
MUSIC_DL_BOT_ENV_PATH |
<config-dir>/discord-bot.env |
Optional Discord bot env-file override |
MUSIC_DL_BOT_TOKEN_PATH |
<config-dir>/bot-shared-token |
Optional backend shared-token file override |
MUSIC_DL_BOT_PATH |
auto-detected repo path | Optional path to apps/discord-bot for music-dl gui --setup-bot |
MUSIC_DL_BOT_TOKEN |
(unset) | Optional env override for bot/backend bearer auth |
git clone git@github.com:alfdav/music-dl.git
cd music-dl/tidaldl-py
uv sync
music-dl guiRun the Python test suite:
uv run --extra test pytestRun the Discord bot checks:
cd apps/discord-bot
bun test
bun run typecheckRun the release smoke coverage from the repository root:
uv run --project tidaldl-py --extra test pytest \
tidaldl-py/tests/test_gui_command.py \
tidaldl-py/tests/test_gui_api.py \
tidaldl-py/tests/test_setup.py \
tidaldl-py/tests/test_token_refresh.py \
tidaldl-py/tests/test_public_branding.py \
tidaldl-py/tests/test_packaging.py
uv build --project tidaldl-py
docker build -f docker/Dockerfile -t music-dl .Prerequisites: Rust, Bun, Python 3.12+, and platform-specific dependencies.
macOS:
# Xcode CLI tools (if not installed)
xcode-select --installLinux (Ubuntu/Debian):
sudo apt install libwebkit2gtk-4.1-dev libayatana-appindicator3-dev \
librsvg2-dev patchelf libgtk-3-dev ffmpegWindows 10/11:
- WebView2 Runtime (normally already installed on Windows 10/11)
- Microsoft C++ Build Tools / Visual Studio Build Tools
- WiX requirements used by Tauri MSI builds
Build:
cd tidaldl-py
uv sync && uv pip install pyinstaller
bun install
# Linux:
bunx tauri build # outputs .AppImage + .deb
# macOS (produces .app + .dmg):
bunx tauri build --bundles dmg
# Output: src-tauri/target/release/bundle/The build process: PyInstaller compiles the Python backend into a standalone sidecar binary → Tauri wraps it with a native window → outputs .app/.dmg (macOS), .AppImage/.deb (Linux), or .msi (Windows).
For Windows local builds, build and rename the PyInstaller sidecar before running Tauri, then use the CI config override so Tauri does not run the default Unix beforeBuildCommand:
cd tidaldl-py
uv sync --extra build
bun install
$TargetTriple = rustc --print host-tuple
uv run pyinstaller --clean --distpath src-tauri/binaries --workpath build/pyinstaller --noconfirm build/pyinstaller/music-dl-server.spec
Move-Item -Force "src-tauri/binaries/music-dl-server.exe" "src-tauri/binaries/music-dl-server-$TargetTriple.exe"
bunx tauri build --target $TargetTriple --bundles msi --config src-tauri/tauri.ci.conf.jsonThe one-command internal Windows source installer runs that same flow:
irm https://raw.githubusercontent.com/alfdav/music-dl/master/scripts/install-windows-local.ps1 | iexThe desktop app and browser mode share the same local web UI. Tauri starts or reuses the localhost daemon, then opens the same route the browser would use. Desktop protocol links such as music-dl://open#search open supported internal views in the app.
Linux, macOS, and Windows releases are published via GitHub Actions. The macOS app is not notarized (no Apple Developer ID). The scripts/install.sh one-liner verifies the GitHub release checksum and strips the quarantine xattr so Gatekeeper doesn't fire. If you download a DMG through Safari instead, macOS will set the quarantine bit and you'll need a one-time right-click → Open bypass on first launch. Windows MSI builds are unsigned, so SmartScreen may warn on first install.
Windows smoke test before marking a release supported:
- Install the MSI.
- Launch
music-dl. - Complete or recover Tidal authentication.
- Choose a local library/download path.
- Search for one track.
- Download one track.
- Play that track.
- Quit and reopen the app.
- Confirm settings, auth, and library state persist.
See CONTRIBUTING.md for the full development workflow.
The GUI binds to localhost only — it is not accessible from other machines. CSRF protection is enabled for all write operations. The Docker image runs as a non-root user (UID 1000) and binds to localhost on the host side by default.
Do not expose port 8765 to untrusted networks without adding your own authentication layer.
Apache-2.0. See LICENSE.
Personal project for educational purposes and private use. Not affiliated with or endorsed by TIDAL. A valid TIDAL subscription is required. Downloaded files are for personal offline use in accordance with your subscription terms. You are responsible for compliance with applicable laws and TIDAL's Terms of Service.
Built on yaronzz/Tidal-Media-Downloader and tidal-dl-ng. Powered by tidalapi, mutagen, FastAPI, Rich, and Typer.


