Music Stack (Navidrome + AudioMuse-AI)¶
Self-hosted music streaming with AI-powered playlist generation and discovery.
Architecture¶
┌─────────────────────────────────────────────────────────────┐
│ Mobile Apps │
│ (Symfonium, Amperfy, play:Sub) │
└─────────────────┬───────────────────────────────────────────┘
│ Subsonic API
▼
┌─────────────────────────────────────────────────────────────┐
│ NAVIDROME │
│ URL: https://music.bogocat.com │
│ Internal: 10.89.97.50:4533 │
│ • Music streaming server │
│ • Subsonic API for mobile apps │
│ • Web UI for browser playback │
└─────────────────┬───────────────────────────────────────────┘
│
┌───────────┼───────────┬────────────────────┐
▼ │ ▼ ▼ Scrobbles
┌──────────────┐ │ ┌──────────────────┐ ┌──────────────────┐
│ Music Files │ │ │ AudioMuse-AI │ │ MULTI-SCROBBLER │
│ /mnt/media/ │ │ │ :8765 │ │ :9078 │
│ music/ │ │ │ • AI playlists │ │ • Scrobble hub │
│ ↑ │ │ │ • Sonic analysis │ │ • Fan-out to │
│ Lidarr │ │ │ • Music map │ │ multiple dests │
│ downloads │ │ └──────────────────┘ └────────┬─────────┘
└──────────────┘ │ │
│ ┌────────────┴────────────┐
│ ▼ ▼
│ ┌──────────────────┐ ┌──────────────────┐
│ │ MALOJA │ │ LISTENBRAINZ │
│ │ :42010 │ │ (public profile) │
│ │ • Listening stats│ │ • celentano │
│ │ • Charts/trends │ └────────┬─────────┘
│ │ • Wrapped-style │ │
│ └──────────────────┘ │
▼ │
┌─────────────────────────────────────────────────────────────┐│
│ Music Discovery Service ││
│ • Reads listening history from ListenBrainz ◄──────────────┘│
│ • Queries Last.fm API for similar artists │
│ • Adds new artists to Lidarr automatically │
│ • Sends Discord notifications │
│ • Runs weekly via cron (Sunday 3am) │
└─────────────────────────────────────────────────────────────┘
Components¶
Navidrome¶
Location: arr-stack VM (10.89.97.50)
Port: 4533
External URL: https://music.bogocat.com
Config: /opt/arr-stack/configs/navidrome/
Lightweight music server implementing Subsonic/OpenSubsonic API. Handles: - Library scanning and metadata - User authentication - Streaming to clients - Playlist management
AudioMuse-AI¶
Location: arr-stack VM (10.89.97.50)
Port: 8765
Config: /opt/arr-stack/configs/audiomuse/
AI-powered music analysis and playlist generation: - Flask app - Web UI and API (port 8765) - RQ Worker - Background analysis jobs (GPU) - Redis - Task queue - PostgreSQL - Analysis data storage
Features: - Sonic fingerprinting and similarity - CLAP embeddings for semantic search - AI playlist naming via local LLM - Music map visualization
Multi-Scrobbler (Scrobble Hub)¶
Location: arr-stack VM (10.89.97.50)
Port: 9078
Config: /opt/arr-stack/configs/multi-scrobbler/config.json
Docs: https://foxxmd.github.io/multi-scrobbler/
Receives scrobbles from Navidrome and forwards to multiple destinations: - ListenBrainz - Public profile for music discovery service - Maloja - Local stats dashboard
Why multi-scrobbler? Navidrome only supports one ListenBrainz endpoint. Maloja doesn't forward to ListenBrainz. Multi-scrobbler acts as a hub to fan out scrobbles to both services.
Navidrome Integration:
1. Environment: ND_LISTENBRAINZ_BASEURL=http://multi-scrobbler:9078/1/
2. In Navidrome Settings → Personal → ListenBrainz Token: navidrome-scrobble-token
Configuration (config.json):
{
"clients": [
{
"name": "ListenBrainz",
"type": "listenbrainz",
"data": {
"token": "<listenbrainz-token>",
"username": "celentano"
}
},
{
"name": "Maloja",
"type": "maloja",
"data": {
"url": "http://maloja:42010",
"apiKey": "<maloja-api-key>"
}
}
]
}
Environment Variables:
- LZE_TOKEN - Token that Navidrome uses to authenticate (set to navidrome-scrobble-token)
Maloja (Listening Stats)¶
Location: arr-stack VM (10.89.97.50)
Port: 42010
URL: http://10.89.97.50:42010
Config: /opt/arr-stack/configs/maloja/
Self-hosted music scrobble database for personal listening statistics: - Charts and trends over time - Top artists/albums/tracks - Spotify Wrapped-style summaries
Credentials:
- Admin user: admin
- API key: See /opt/arr-stack/configs/maloja/apikeys.yml
Note: Scrobbles come from multi-scrobbler, not directly from Navidrome.
Music Discovery Service¶
Location: /root/tower-fleet/services/music-discovery/
Schedule: Weekly (Sunday 3am via cron)
Automatic music discovery that adds new artists based on your listening habits:
- Fetches your top artists from ListenBrainz (what you actually listen to)
- Queries Last.fm API for similar artists
- Filters out artists already in library
- Adds recommendations to Lidarr
- Lidarr downloads from your indexers
Requirements: - Last.fm API key (free): https://www.last.fm/api/account/create - ListenBrainz account (free): https://listenbrainz.org - Scrobbling enabled in Navidrome
Configuration:
# /opt/music-discovery/.env
LASTFM_API_KEY=your_api_key
LISTENBRAINZ_USER=your_username
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... # Optional
Discord Notifications: When enabled, sends a summary after each run: - Artists added to Lidarr - Artists already in library - Recommendations based on listening history
Cron schedule:
# Runs every Sunday at 3am
0 3 * * 0 cd /opt/music-discovery && docker compose run --rm music-discovery >> /var/log/music-discovery.log 2>&1
Run manually:
ssh root@10.89.97.50 'cd /opt/music-discovery && docker compose run --rm music-discovery --dry-run' # Test
ssh root@10.89.97.50 'cd /opt/music-discovery && docker compose run --rm music-discovery' # Add artists
Check logs:
Tuning (in docker-compose.yml):
- MAX_RECOMMENDATIONS_PER_RUN=20 - Artists to add per run
- SIMILAR_ARTISTS_PER_SEED=10 - Similar artists to fetch per seed
- TOP_ARTISTS_TO_SEED=10 - Seeds from listening history
- MIN_MATCH_SCORE=0.1 - Last.fm similarity threshold (0.0-1.0)
Infrastructure¶
Docker Compose Services¶
All services run on arr-stack VM in /opt/arr-stack/docker-compose.yml:
# Music streaming
navidrome:
image: deluan/navidrome:latest
ports: ["4533:4533"]
environment:
- ND_LISTENBRAINZ_BASEURL=http://multi-scrobbler:9078/1/
volumes:
- /opt/arr-stack/configs/navidrome:/data
- /mnt/media/music:/music:ro
# AI analysis
audiomuse:
image: ghcr.io/neptunehub/audiomuse-ai:latest
ports: ["8765:8000"]
environment:
- MEDIASERVER_TYPE=navidrome
- NAVIDROME_URL=http://navidrome:4533
- NAVIDROME_USER=root
- NAVIDROME_PASSWORD=<password>
- AI_MODEL_PROVIDER=OPENAI
- OPENAI_SERVER_URL=http://10.89.97.100:8080/v1/chat/completions
- OPENAI_MODEL_NAME=Qwen2.5-Coder-7B-Instruct-Q4_K_M.gguf
- OPENAI_API_KEY=no-key-needed
deploy:
resources:
reservations:
devices:
- driver: nvidia
capabilities: [gpu, compute]
audiomuse-worker:
image: ghcr.io/neptunehub/audiomuse-ai:latest
environment:
- SERVICE_TYPE=worker
# ... same env vars as audiomuse
audiomuse-redis:
image: redis:alpine
audiomuse-db:
image: postgres:15-alpine
# Scrobble hub - forwards to multiple destinations
multi-scrobbler:
image: foxxmd/multi-scrobbler:latest
ports: ["9078:9078"]
environment:
- LZE_TOKEN=navidrome-scrobble-token
- TZ=America/New_York
volumes:
- /opt/arr-stack/configs/multi-scrobbler:/config
# Listening stats dashboard
maloja:
image: krateng/maloja:latest
ports: ["42010:42010"]
environment:
- MALOJA_SKIP_SETUP=yes
volumes:
- /opt/arr-stack/configs/maloja:/data
Network Access¶
| Service | Internal | External |
|---|---|---|
| Navidrome | 10.89.97.50:4533 | https://music.bogocat.com |
| AudioMuse | 10.89.97.50:8765 | Internal only |
| Multi-Scrobbler | 10.89.97.50:9078 | Internal only |
| Maloja | 10.89.97.50:42010 | Internal only |
Caddy config (VPS /etc/caddy/Caddyfile):
music.bogocat.com {
reverse_proxy 10.89.97.50:4533 {
header_up X-Forwarded-Host music.bogocat.com
header_up X-Forwarded-Proto https
}
}
GPU Acceleration¶
AudioMuse uses the Quadro M2000 on arr-stack VM for: - Audio feature extraction - CLAP embedding generation - ONNX model inference
GPU is shared with Tdarr and ErsatzTV.
Mobile Clients¶
Symfonium (Android - Recommended)¶
- Add Server → Subsonic
- Server URL:
https://music.bogocat.com - Username/Password: Navidrome credentials
Settings for best performance: - Enable gapless playback - Enable track preloading - Set buffer to 30+ seconds - Enable cache (2-4 GB)
iOS Options¶
| App | CarPlay | Offline | Notes |
|---|---|---|---|
| Amperfy | Yes | Yes | Free, full-featured |
| play:Sub | Yes | Yes | Paid, polished UI |
| Substreamer | Yes | Yes | Cross-platform |
All use same connection settings as Symfonium.
LLM Integration¶
AudioMuse uses local LLM for intelligent playlist naming.
Provider: llama.cpp on Windows PC (10.89.97.100:8080) Model: Qwen2.5-Coder-7B-Instruct-Q4_K_M
Configuration:
AI_MODEL_PROVIDER=OPENAI
OPENAI_SERVER_URL=http://10.89.97.100:8080/v1/chat/completions
OPENAI_MODEL_NAME=Qwen2.5-Coder-7B-Instruct-Q4_K_M.gguf
OPENAI_API_KEY=no-key-needed
See also: Local LLM Setup
Operations¶
Check Status¶
# Container status
ssh root@10.89.97.50 'docker ps --filter "name=navidrome" --filter "name=audiomuse" --filter "name=maloja" --filter "name=multi-scrobbler"'
# Navidrome logs
ssh root@10.89.97.50 'docker logs navidrome --tail 50'
# AudioMuse analysis progress
ssh root@10.89.97.50 'docker logs audiomuse-worker --tail 50'
# Multi-scrobbler (check scrobble forwarding)
ssh root@10.89.97.50 'docker logs multi-scrobbler --tail 50'
# Maloja stats
ssh root@10.89.97.50 'docker logs maloja --tail 50'
Restart Services¶
ssh root@10.89.97.50 'cd /opt/arr-stack && docker compose restart navidrome'
ssh root@10.89.97.50 'cd /opt/arr-stack && docker compose restart audiomuse audiomuse-worker'
ssh root@10.89.97.50 'cd /opt/arr-stack && docker compose restart multi-scrobbler'
ssh root@10.89.97.50 'cd /opt/arr-stack && docker compose restart maloja'
Verify Scrobbling¶
# Test multi-scrobbler token validation
ssh root@10.89.97.50 'curl -s "http://localhost:9078/1/validate-token" -H "Authorization: Token navidrome-scrobble-token"'
# Should return: {"code":200,"message":"Token valid.","valid":true,"user_name":"Multi-Scrobbler"}
# Watch for incoming scrobbles
ssh root@10.89.97.50 'docker logs multi-scrobbler --since 5m 2>&1 | grep -iE "scrobble|submit"'
Trigger Library Rescan¶
Navidrome: Settings → Scan (or wait for hourly auto-scan)
AudioMuse: Go to http://10.89.97.50:8765, set "Number of Recent Albums" and click Analyze
API Testing¶
# Test Subsonic API
curl -s "https://music.bogocat.com/rest/ping.view?u=USER&p=PASS&v=1.16.1&c=test&f=json"
# Test streaming latency
time curl -s -o /dev/null "https://music.bogocat.com/rest/stream.view?u=USER&p=PASS&v=1.16.1&c=test&id=SONG_ID" -r 0-65535
Initial Setup¶
First-Time Analysis¶
- Go to http://10.89.97.50:8765
- Set "Number of Recent Albums" to
0(analyze all) - Click Analyze button
- Monitor progress in worker logs
Initial analysis time depends on library size: - ~245 artists: 80-100 hours - GPU accelerated but CPU-bound on feature extraction
After Analysis Completes¶
Features available: - Instant Playlist - Natural language playlist creation - Music Map - Visual sonic exploration - Similar Songs - Find tracks by sound - CLAP Search - Semantic search ("chill lo-fi beats") - Song Alchemy - Blend characteristics of multiple songs
Troubleshooting¶
Slow Mobile Playback¶
-
Check API latency:
Should be <200ms -
If API is fast, issue is client settings:
- Enable preloading
- Increase buffer size
- Enable caching
AudioMuse Not Syncing¶
Check MEDIASERVER_TYPE is set:
ssh root@10.89.97.50 'docker exec audiomuse env | grep MEDIASERVER'
# Should show: MEDIASERVER_TYPE=navidrome
Check worker logs for errors:
GPU Not Used¶
Verify GPU access:
Check container has GPU reservation in docker-compose.yml.
Scrobbles Not Appearing¶
-
Check Navidrome is pointing to multi-scrobbler:
-
Test token validation:
-
Check multi-scrobbler clients are initialized:
-
If Navidrome gives 401 error when linking:
- Ensure
LZE_TOKENenv var matches what you enter in Navidrome UI - Recreate container:
docker compose up -d navidrome --force-recreate
Library Analysis (January 2026)¶
Current Storage¶
| Format | Files | Size | Avg/File |
|---|---|---|---|
| FLAC | 1,910 | 68 GB | 36 MB |
| MP3 | 13,939 | 130 GB | 9.5 MB |
| Other | 42 | ~200 MB | - |
| Total | 15,891 | 198 GB | - |
Transcoding Considerations¶
If all FLAC → MP3 320: - FLAC files would shrink: 68 GB → ~18 GB - Total library: 198 GB → ~148 GB - Savings: ~50 GB (25%)
Recommendation: Keep FLAC
Reasons: 1. Storage isn't constrained (9.1 TB free, music is 2.2%) 2. 50 GB savings = 0.5% of free space 3. FLAC → MP3 is irreversible (lossy) 4. Navidrome transcodes on-the-fly for mobile clients 5. AudioMuse analysis benefits from lossless source 6. Future use cases (home speakers, DAC) benefit from lossless
For mobile bandwidth concerns: Configure per-user transcoding in Navidrome settings or client app (Symfonium → Max streaming bitrate).
Top Artists by Size¶
| Artist | Size | Notes |
|---|---|---|
| Joni Mitchell | 11 GB | High-res FLAC discography |
| Fall Out Boy | 5.1 GB | |
| Sufjan Stevens | 4.7 GB | |
| Elton John | 4.5 GB | |
| The Beatles | 4.1 GB |