Skip to content

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

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:

  1. Fetches your top artists from ListenBrainz (what you actually listen to)
  2. Queries Last.fm API for similar artists
  3. Filters out artists already in library
  4. Adds recommendations to Lidarr
  5. 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:

ssh root@10.89.97.50 'tail -100 /var/log/music-discovery.log'

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

  1. Add Server → Subsonic
  2. Server URL: https://music.bogocat.com
  3. 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

  1. Go to http://10.89.97.50:8765
  2. Set "Number of Recent Albums" to 0 (analyze all)
  3. Click Analyze button
  4. 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

  1. Check API latency:

    time curl -s "https://music.bogocat.com/rest/ping.view?..."
    
    Should be <200ms

  2. If API is fast, issue is client settings:

  3. Enable preloading
  4. Increase buffer size
  5. 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:

ssh root@10.89.97.50 'docker logs audiomuse-worker 2>&1 | grep -i error'

GPU Not Used

Verify GPU access:

ssh root@10.89.97.50 'docker exec audiomuse nvidia-smi'

Check container has GPU reservation in docker-compose.yml.

Scrobbles Not Appearing

  1. Check Navidrome is pointing to multi-scrobbler:

    ssh root@10.89.97.50 'docker exec navidrome env | grep LISTENBRAINZ'
    # Should show: ND_LISTENBRAINZ_BASEURL=http://multi-scrobbler:9078/1/
    

  2. Test token validation:

    ssh root@10.89.97.50 'curl -s "http://localhost:9078/1/validate-token" -H "Authorization: Token navidrome-scrobble-token"'
    

  3. Check multi-scrobbler clients are initialized:

    ssh root@10.89.97.50 'docker logs multi-scrobbler 2>&1 | grep "Fully Initialized"'
    # Should show both ListenBrainz and Maloja initialized
    

  4. If Navidrome gives 401 error when linking:

  5. Ensure LZE_TOKEN env var matches what you enter in Navidrome UI
  6. 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