Search Deezer (default, no API key) or Spotify (optional credentials) for tracks and albums, download audio from YouTube with yt-dlp, apply ID3 tags and artwork, then save to your browser downloads or copy into one or more Navidrome music library folders on the server.
Choose the catalog in the web UI (Catalog) or set DEFAULT_METADATA_PROVIDER in .env.
- Search Deezer or Spotify for tracks and albums
- Download from YouTube using catalog metadata; optional YouTube cookies when YouTube blocks automation
- ID3 tagging (artist, album, cover art) via the metadata service
- Download to: local (browser) or any configured Navidrome music root (multiple libraries supported β no need to run separate app instances)
- Web UI with download queue and status polling
- Background library sync (optional): scan Navidrome folders and align βalready downloadedβ state with the catalog
| Layer | Technology |
|---|---|
| Frontend | HTML, CSS, vanilla JavaScript |
| Backend | Python FastAPI |
| Catalog | Deezer (public search) or Spotify Web API (optional) |
| Audio | yt-dlp + FFmpeg |
| Tags | mutagen |
| Server library | Files copied under Navidrome music path(s); optional Navidrome API for scans |
Docker: Docker and Docker Compose
Manual: Python 3.8+ (3.11+ recommended), FFmpeg on PATH, optional Navidrome instance
Spotify in the UI requires SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET in backend/.env.
git clone https://github.com/soggy8/musikat.git
cd musikat
cp backend/env.example backend/.env
# Edit backend/.env: Navidrome path(s), optional Spotify, DEFAULT_METADATA_PROVIDER
docker-compose up -dOpen http://localhost:8000.
Mount your Navidrome music directory in docker-compose.yml (see DOCKER.md).
See SETUP.md.
cd backend
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txtInstall FFmpeg (e.g. sudo apt install ffmpeg on Debian/Ubuntu, brew install ffmpeg on macOS).
Copy backend/env.example to backend/.env and adjust.
| Variable | Description |
|---|---|
DEFAULT_METADATA_PROVIDER |
deezer (default) or spotify |
SPOTIFY_CLIENT_ID / SPOTIFY_CLIENT_SECRET |
Required if you use Spotify in the UI |
SPOTIFY_REDIRECT_URI |
OAuth redirect (default http://localhost:8000/callback) |
The app writes files on disk under paths the server is allowed to use. Navidrome should use the same folder(s) as its music library.
| Variable | Description |
|---|---|
NAVIDROME_MUSIC_PATH |
Single absolute path (default in code: /music if unset). Used when NAVIDROME_MUSIC_PATHS is not set. |
NAVIDROME_MUSIC_PATHS |
Optional. Comma- or newline-separated absolute paths. Each appears as a separate Download to target. |
NAVIDROME_MUSIC_LABELS |
Optional. Same order as NAVIDROME_MUSIC_PATHS; labels shown in the UI (defaults to folder basename). |
NAVIDROME_API_URL |
Navidrome base URL (for scans), e.g. http://localhost:4533 |
NAVIDROME_USERNAME / NAVIDROME_PASSWORD |
Optional; for triggering library scans via API |
NAVIDROME_SYNC_ENABLED |
true/false β background scan of library paths to sync βalready downloadedβ hints (default on) |
NAVIDROME_SYNC_INTERVAL_HOURS |
Between sync runs |
NAVIDROME_SYNC_INITIAL_DELAY_SEC |
Delay before first sync after startup |
Examples
Single folder (typical Docker mount):
NAVIDROME_MUSIC_PATH=/musicMultiple libraries:
NAVIDROME_MUSIC_PATHS=/data/music/rock,/data/music/classical
NAVIDROME_MUSIC_LABELS=Rock,Classical| Variable | Description |
|---|---|
DOWNLOAD_DIR |
Server temp/staging for downloads (default ./downloads) |
OUTPUT_FORMAT / AUDIO_QUALITY |
Default encode settings |
YOUTUBE_COOKIES_PATH |
Netscape cookies file for yt-dlp when YouTube blocks requests |
API_HOST / API_PORT |
Bind address |
CORS_ORIGINS |
Comma-separated allowed origins |
cd backend
uvicorn app:app --host 0.0.0.0 --port 8000 --reloadOr python app.py if your entrypoint wraps uvicorn. The UI is served from the same process (no separate frontend server).
- Open the app in the browser.
- Under Download to, choose My Downloads Folder (System) or a Navidrome path (loaded from
GET /api/navidrome/libraries/ your env). - Pick Catalog (Deezer or Spotify).
- Search tracks or albums, then download. Watch the queue for progress.
- Local: the browser saves the finished file. Navidrome: the server copies the file under the selected music root (Artist/Album layout).
- Search β Deezer or Spotify returns track/album metadata and IDs.
- Match β YouTube candidates are chosen (with optional confirmation if confidence is low).
- Download β yt-dlp fetches audio; FFmpeg converts if needed.
- Tag β Metadata service writes tags and artwork.
- Deliver β Either serve to the browser or copy into the chosen Navidrome root; optional Navidrome API notification for scanning.
| Method | Path | Notes |
|---|---|---|
| GET | /api/health |
Status, navidrome_path, navidrome_libraries, etc. |
| GET | /api/metadata/providers |
Deezer / Spotify and whether Spotify is configured |
| GET | /api/navidrome/libraries |
{ "libraries": [ { "path", "label" }, ... ] } β roots from env |
| GET | /api/formats |
Audio format and quality defaults |
| POST | /api/search |
Body: query, provider, limit |
| POST | /api/search/albums |
Album search |
| POST | /api/download |
Body includes track_id, location (local | navidrome), optional navidrome_library (absolute path; must match server config), provider, format/quality |
| POST | /api/download/album |
Album download; same location / navidrome_library pattern |
| POST | /api/reverse/download |
YouTube β metadata flow |
| GET | /api/track/{id}/exists |
Duplicate check; supports location and optional navidrome_library |
| GET | /api/download/status/{track_id} |
Job status |
Full behavior is defined in backend/app.py.
musikat/
βββ backend/
β βββ app.py
β βββ config.py
β βββ requirements.txt
β βββ env.example
β βββ static/ # app.js, styles.css
β βββ templates/ # index.html
β βββ services/ # deezer, spotify, youtube, metadata, navidrome
β βββ utils/ # file_handler, job_store, navidrome_library_sync
β βββ tests/
βββ images/
βββ Dockerfile
βββ docker-compose.yml
βββ DOCKER.md
βββ DEPLOYMENT.md
βββ SETUP.md
βββ README.md
- Try the other catalog or a more specific query (artist + title).
- Install FFmpeg and ensure it is on
PATH. - Export cookies (Netscape format) and set
YOUTUBE_COOKIES_PATH(see yt-dlp FAQ). Cookies expire; re-export if downloads start failing.
- Paths must be writable by the process running Musikat.
- For multiple roots, each path in
NAVIDROME_MUSIC_PATHSmust match what you select in the UI (server validates against the allowlist). - Check
GET /api/navidrome/librariesmatches your Docker mounts and permissions.
- Add your site origin to
CORS_ORIGINSin.env(required if the browser loads the UI from a different origin than the API, e.g. another port or domain).
If Musikat is served under a path such as https://example.com/musikat/, nginx must forward requests to the app and ideally send X-Forwarded-Prefix: /musikat so links and env match. The frontend also infers /musikat from the browser URL when that header is missing, so API calls target /musikat/api/... instead of /api/... (which would 404).
Use for personal use only. Respect copyright and the terms of Deezer, Spotify, YouTube, and your jurisdiction.
MIT β see LICENSE.
Pull requests are welcome.
- DOCKER.md β Docker deployment
- DEPLOYMENT.md β production notes
- SETUP.md β manual setup

