A full-stack Next.js app for managing, analyzing, and syncing vinyl music collections with multiple music platforms. Seamlessly import your Discogs collection, enrich metadata with Apple Music, Spotify, and YouTube, perform audio analysis, and create intelligent playlists.
- Modern Frontend: Next.js 15, React 19, TypeScript, and Chakra UI v3
- Powerful Search: MeiliSearch with infinite scroll, full-text search, and advanced filtering
- Multi-Platform Integration: Discogs, Apple Music, Spotify, YouTube, and SoundCloud
- Audio Analysis: Essentia-powered BPM, key detection, and mood analysis via FastAPI microservice
- Smart Playlisting: AI-powered playlist generation using genetic algorithms
- Metadata Enrichment: Bulk editing, AI-assisted metadata completion, and track linking
- Database Management: PostgreSQL with pgvector for semantic search, backup/restore via web UI
- Collection Sync: Share and sync collections with friends
- Vector Search: Optional embedding-based similarity search using OpenAI
- Docker Compose: Full orchestration for development and production environments
dj-playlist/
├── my-collection-search/ # Main Next.js application
│ ├── src/
│ │ ├── app/ # Next.js 15 app router pages
│ │ ├── components/ # React components (SearchResults, TrackResult, etc.)
│ │ ├── hooks/ # React Query hooks and cache management
│ │ ├── lib/ # Query keys, utilities
│ │ ├── providers/ # React context providers
│ │ ├── services/ # API clients (tracks, playlists, MeiliSearch)
│ │ ├── types/ # TypeScript type definitions
│ │ └── workers/ # Background job workers
│ ├── migrations/ # PostgreSQL migration scripts (node-pg-migrate)
│ ├── public/ # Static assets
│ └── .env.example # Environment variable template
├── essentia-api/ # Python FastAPI audio analysis microservice
│ ├── app/ # FastAPI application
│ ├── requirements.txt # Python dependencies
│ └── Dockerfile # Container definition
├── audio/ # Uploaded and processed audio files (volume mount)
├── dumps/ # Database backup files (volume mount)
├── docker-compose.yml # Base Docker Compose (local builds)
├── docker-compose.dev.yml # Development overrides (hot reload, etc.)
└── docker-compose.prod.yml # Production with pre-built images (x86_64 only)
- Docker & Docker Compose (v2.x or higher)
- Node.js 20+ (for local development)
- Discogs account with collection data (required)
- API credentials (see Environment Variables section below)
-
Clone the repository:
git clone <your-repo-url> cd dj-playlist
-
Configure environment variables:
cd my-collection-search cp .env.example .envEdit
.envand add your API credentials (see "Getting API Credentials" section) -
Start services with Docker Compose:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build
This will start:
- PostgreSQL database (port 5432)
- MeiliSearch (port 7700)
- Next.js app (port 3000)
- Essentia API for audio analysis (port 8000)
-
Run database migrations:
docker compose run --rm migrate
-
Access the application:
- Frontend: http://localhost:3000
- MeiliSearch admin: http://localhost:7700
-
Import your Discogs collection:
- Navigate to the import page in the UI
- Click "Sync from Discogs"
- Your collection will be imported and indexed in MeiliSearch
There are two production deployment options depending on your platform architecture:
This option uses tagged container images from GitHub Container Registry. Ideal for Linux servers and deployment tools like Portainer.
-
Configure environment:
cd my-collection-search cp .env.example .envEdit
.envwith production credentials and strong passwords -
Start services using production compose file:
docker compose -f docker-compose.prod.yml up -d
This configuration:
- Pulls pre-built images from
ghcr.io/saegey/myapp:v1.0.78 - Includes all services: app, db, MeiliSearch, Redis, Essentia API, GA service, download worker
- Uses named volumes for persistence
- Pulls pre-built images from
-
Run migrations:
docker compose -f docker-compose.prod.yml run --rm migrate
-
Access the app: Navigate to your server's address on port 3000
Note: Currently, pre-built images are only available for x86_64/amd64 architecture. ARM64 images are not yet published to the registry.
For ARM-based systems (Apple Silicon Macs, ARM servers) where pre-built images aren't available, build locally:
-
Configure environment:
cd my-collection-search cp .env.example .envEdit
.envwith production credentials -
Build and start services:
docker compose -f docker-compose.yml up --build -d
-
Run migrations:
docker compose run --rm migrate
-
Access the app: Navigate to
http://localhost:3000
Architecture Notes:
- The main
docker-compose.ymlbuilds images locally from source - Works on any architecture (ARM64, x86_64)
- Slower initial startup due to build time
- Recommended for Mac users until ARM64 images are published
If using Portainer on a Linux server (x86_64):
- Create a new stack in Portainer
- Upload or paste the contents of
docker-compose.prod.yml - Set environment variables in Portainer's environment section:
- Add all variables from
.env.example - Portainer will inject these into the containers
- Add all variables from
- Deploy the stack
- Run migrations:
- Use Portainer's container console
- Select the
migratecontainer - Or exec into the
appcontainer:npx node-pg-migrate up
Portainer Benefits:
- Web UI for container management
- Easy log viewing and monitoring
- Automatic restarts and health checks
- Simple environment variable management
-
Start PostgreSQL and MeiliSearch (use Docker or install locally):
docker compose up -d db meili
-
Install dependencies:
cd my-collection-search npm install -
Run migrations:
npm run migrate up
-
Start the dev server:
npm run dev
-
Access: http://localhost:3000
- Migrations are managed with node-pg-migrate.
- Migration files are in
migrations/and ordered by timestamp. - To run migrations manually:
docker compose run migrate
- Backup: Use the web UI or POST
/api/backupto create a SQL dump indumps/. - Restore: Use the web UI or POST a SQL file to
/api/restore. - For a full restore, remove the DB volume first:
docker compose down -v docker compose up db # Then restore via UI or API
- Set up Discogs credentials in
.env - Navigate to the import page in the UI
- Click "Sync from Discogs" to import your collection
- Tracks are automatically indexed in MeiliSearch for instant search
- Auto-match: Automatically find Apple Music, Spotify, and YouTube links for tracks
- Bulk edit: Select multiple tracks and update fields like BPM, key, rating, tags
- AI assistance: Use OpenAI to auto-complete missing metadata fields
- Manual editing: Edit individual track details with inline forms
- Upload audio files for tracks (MP3, WAV, FLAC)
- Essentia API automatically analyzes:
- BPM (beats per minute)
- Musical key
- Danceability score
- Mood analysis (happy, sad, aggressive, relaxed)
- Analysis results are stored and searchable
- Manual: Drag and drop tracks from search results
- AI-powered: Use genetic algorithm to generate playlists based on:
- BPM matching
- Key compatibility
- Mood progression
- Genre clustering
- Full-text search: Search across title, artist, album, genres, styles
- Advanced filters: Filter by BPM range, key, star rating, tags, platform availability
- Infinite scroll: Smooth browsing through large collections
- Vector similarity: Find similar tracks using OpenAI embeddings (optional)
- Add friends by Discogs username
- Browse and search friends' collections
- Compare collections and find unique tracks
- Sync updates when friends add new records
- Audio files are processed and stored in
/audiovolume - Essentia microservice runs in its own container (
essentia-api) - Analysis results are saved to PostgreSQL and indexed in MeiliSearch
- Supports MP3, WAV, FLAC, and other common audio formats
GrooveNET supports USB DAC (Digital-to-Analog Converter) passthrough for high-quality audio playback directly from the containerized application. This feature is completely optional and disabled by default.
- USB DAC connected to your host system
- Docker host must be Linux-based (required for USB device passthrough)
- ALSA utilities installed on the host (recommended for device detection)
Run the audio detection script on your host machine (not in a container):
cd my-collection-search/scripts
./detect-audio-devices.shThis script will display:
- Available
/dev/snddevices - ALSA sound card information
- USB audio devices
- Configuration guidance
Example output:
2. ALSA Sound Cards (from /proc/asound/cards):
0 [PCH ]: HDA-Intel - HDA Intel PCH
1 [DAC ]: USB-Audio - FiiO DAC
In this example, the USB DAC is card 1.
Edit your docker-compose.prod.yml or docker-compose.yml file:
Option A - Pass All Audio Devices (Simplest):
Uncomment these lines under the app service:
devices:
- /dev/snd:/dev/snd # Pass through all audio devicesOption B - Pass Specific DAC Only (More Secure):
If your USB DAC is card 1 (check the script output):
devices:
- /dev/snd/controlC1:/dev/snd/controlC1 # Control interface for card 1
- /dev/snd/pcmC1D0p:/dev/snd/pcmC1D0p # Playback device for card 1Replace 1 with your actual card number from Step 1.
Edit your .env file:
# Enable audio playback features
ENABLE_AUDIO_PLAYBACK=true
# Set the ALSA device name
# Use 'default' for the system default device
# Or specify a card like 'hw:1,0' for card 1, device 0
AUDIO_DEVICE=hw:1,0Device name format:
default- System default audio devicehw:X,Y- Hardware device (card X, device Y)plughw:X,Y- Hardware with software conversion (more compatible)
For Docker Compose:
docker compose down
docker compose up -dFor Portainer:
- Go to Stacks → Your Stack → Editor
- Uncomment the
devices:section under theappservice - Go to Environment variables and add/update:
ENABLE_AUDIO_PLAYBACK=trueAUDIO_DEVICE=hw:1,0(adjust card number as needed)
- Click Update the stack
Run a test tone to verify the USB DAC is working:
# Test with speaker-test (generates a test tone)
docker exec -it myapp speaker-test -t wav -c 2
# Test with a specific device
docker exec -it myapp speaker-test -D hw:1,0 -t wav -c 2
# Press Ctrl+C to stop the testIf you hear audio through your USB DAC, the setup is successful!
If you're using Portainer on an Intel NUC or other Linux server:
-
Edit Stack Compose File:
- Navigate to Stacks → Your Stack → Editor
- Find the
appservice section - Uncomment the device mapping:
devices: - /dev/snd:/dev/snd
- Click Update the stack
-
Set Environment Variables:
- In the same stack editor, scroll to Environment variables
- Add or edit:
ENABLE_AUDIO_PLAYBACK=trueAUDIO_DEVICE=hw:1,0(or your device)
- Click Update the stack
-
Verify Deployment:
- Portainer will recreate the container with the new settings
- Check logs: Containers →
myapp→ Logs
No audio output:
# Check if the container can see audio devices
docker exec -it myapp ls -la /dev/snd/
# List available ALSA devices inside container
docker exec -it myapp aplay -l
# Check if the specified device exists
docker exec -it myapp cat /proc/asound/cardsPermission denied errors:
- The container user may need to be in the
audiogroup - Try passing all devices with
/dev/snd:/dev/sndinstead of specific devices - Check host permissions:
ls -la /dev/snd/
Wrong device playing audio:
- Use
AUDIO_DEVICE=hw:X,Yto specify the exact card number - Run the detection script again to verify device numbers haven't changed
- USB device numbers can change after reboot; consider using
by-idpaths
USB DAC not detected:
- Ensure the USB DAC is connected before starting containers
- Check
lsusbon the host to verify USB connection - Some DACs require specific kernel drivers or firmware
Once configured, your Next.js app can use the ENABLE_AUDIO_PLAYBACK and AUDIO_DEVICE environment variables to enable audio playback features in the UI.
Add the playback mode selector to your player UI:
import PlaybackModeSelector from '@/components/PlaybackModeSelector';
import { usePlaybackMode, useLocalPlayback } from '@/hooks/usePlaybackMode';
function MyPlayer() {
const { mode, setMode } = usePlaybackMode();
const { play, pause, stop } = useLocalPlayback();
const { currentTrack, isPlaying } = usePlaylistPlayer();
// Handle play button
const handlePlay = async () => {
if (mode === 'local-dac' && currentTrack?.local_audio_url) {
await play(currentTrack.local_audio_url);
} else {
// Use existing browser playback
browserPlay();
}
};
return (
<div>
{/* Playback mode selector */}
<PlaybackModeSelector value={mode} onChange={setMode} />
{/* Your existing player controls */}
<button onClick={handlePlay}>Play</button>
</div>
);
}For complete integration details, see docs/LOCAL_PLAYBACK.md.
- Linux only: USB device passthrough requires Linux host OS
- Not supported: macOS, Windows (WSL2 has limited support)
- Portainer: Works great on Linux servers (Intel NUC, Raspberry Pi, etc.)
- Development: May not work in
docker-compose.dev.ymlon macOS
Turn your Intel NUC with USB DAC into an AirPlay receiver! Stream audio from iOS devices, Macs, and other AirPlay sources directly to your high-quality DAC.
- Network Audio Endpoint: Your server appears as "GrooveNET Audio" in AirPlay menus
- Simultaneous Playback: Use ALSA dmix to play both AirPlay and Next.js app audio simultaneously
- Auto-Coordination: Optional hooks to pause app playback when AirPlay starts
- High Quality: Supports high sample rates and bit depths (96kHz/24-bit)
-
Enable the service in
docker-compose.prod.yml:# Uncomment the shairport-sync service block shairport-sync: image: mikebrady/shairport-sync:latest network_mode: host # Required for AirPlay discovery devices: - /dev/snd:/dev/snd
-
Configure for software mixing (recommended):
# In .env AIRPLAY_NAME=GrooveNET Audio AUDIO_DEVICE=dmix:CARD=DAC,DEV=0
-
Mount ALSA config for dmix:
# In docker-compose.prod.yml volumes: - ./config/asound.conf:/etc/asound.conf:ro
-
Start the service:
docker compose -f docker-compose.prod.yml up -d shairport-sync
-
Test: Open AirPlay menu on your iPhone/Mac and look for "GrooveNET Audio"
Simple Mode (First-Come-First-Served):
- Only one audio source at a time (AirPlay or app)
- Easiest setup, no mixing required
- See docs/AIRPLAY_RECEIVER.md
dmix Mode (Recommended):
- Both AirPlay and app can play simultaneously
- Software mixing with minimal CPU overhead
- See docs/AIRPLAY_RECEIVER.md
Coordinated Mode:
- Auto-pause app when AirPlay starts
- Managed via session hooks
- See docs/AIRPLAY_RECEIVER.md
For complete setup instructions, troubleshooting, and advanced configuration, see the AirPlay Receiver Guide.
Copy my-collection-search/.env.example to my-collection-search/.env and configure the following variables:
# Database
DATABASE_URL=postgres://djplaylist:djplaylist@localhost:5432/djplaylist
# MeiliSearch
MEILISEARCH_API_KEY=mysupersecretkey
MEILISEARCH_HOST=http://meili:7700
MEILISEARCH_EXTERNAL_HOST=http://localhost:7700
MEILI_PARENT_KEY=sample_meili_parent_key
MEILI_PARENT_KEY_UID=sample_parentkey_uid
# Discogs (Required for collection import)
DISCOGS_USER_TOKEN=your_discogs_token
DISCOGS_USERNAME=your_discogs_username
DISCOGS_FOLDER_ID=0
# Optional: Music Platform APIs
APPLE_MUSIC_TEAM_ID=your_team_id
APPLE_MUSIC_KEY_ID=your_key_id
APPLE_MUSIC_PRIVATE_KEY_PATH=./AuthKey_XXXXXXXXXX.p8
SPOTIFY_CLIENT_ID=your_spotify_client_id
SPOTIFY_CLIENT_SECRET=your_spotify_client_secret
SPOTIFY_REDIRECT_URI=http://localhost:3000/api/spotify/callback
YOUTUBE_API_KEY=your_youtube_api_key
# Optional: AI Features
OPENAI_API_KEY=your_openai_api_key
# Optional: USB DAC Audio Playback
ENABLE_AUDIO_PLAYBACK=false
AUDIO_DEVICE=defaultDiscogs is the primary source for importing your vinyl collection.
- Create a Discogs Account: Visit discogs.com and sign up
- Get Your User Token:
- Go to Settings → Developers
- Click "Generate new token"
- Copy the token to
DISCOGS_USER_TOKEN
- Find Your Username: Your username appears in your profile URL:
discogs.com/user/YOUR_USERNAME - Folder ID: Use
0for "All" or find specific folder IDs in your collection URL
Documentation: Discogs API Docs
Enables metadata enrichment, preview URLs, and Apple Music linking.
- Apple Developer Account: You need a paid Apple Developer account ($99/year)
- Sign up at developer.apple.com
- Create a MusicKit Key:
- Go to Certificates, Identifiers & Profiles
- Click "+" to create a new key
- Enable "MusicKit" checkbox
- Download the
.p8file (you can only download once!)
- Configure Environment:
APPLE_MUSIC_TEAM_ID: Found in the top-right of your Apple Developer account (10-character ID)APPLE_MUSIC_KEY_ID: The Key ID shown after creating the key (10-character ID)APPLE_MUSIC_PRIVATE_KEY_PATH: Path to your downloaded.p8file (place in project root)
Documentation: Apple Music API Setup
Enables Spotify track matching, playlist import, and new release tracking.
- Create Spotify App:
- Go to Spotify Developer Dashboard
- Click "Create app"
- Fill in app name and description
- Add redirect URI:
http://localhost:3000/api/spotify/callback
- Get Credentials:
- Click on your new app
- Copy "Client ID" to
SPOTIFY_CLIENT_ID - Click "Show Client Secret" and copy to
SPOTIFY_CLIENT_SECRET
- Set Redirect URI: Must match
SPOTIFY_REDIRECT_URIin your.env
Documentation: Spotify Web API
Enables YouTube search and video linking for tracks.
- Create Google Cloud Project:
- Go to Google Cloud Console
- Create a new project or select existing
- Enable YouTube Data API v3:
- Navigate to "APIs & Services" → "Library"
- Search for "YouTube Data API v3"
- Click "Enable"
- Create API Key:
- Go to "APIs & Services" → "Credentials"
- Click "Create Credentials" → "API Key"
- Copy the key to
YOUTUBE_API_KEY - (Optional) Restrict the key to YouTube Data API v3 for security
Documentation: YouTube Data API
Note: Free tier includes 10,000 quota units/day. Each search costs ~100 units.
Powers AI-assisted metadata completion and semantic vector search.
- Create OpenAI Account: Visit platform.openai.com
- Generate API Key:
- Go to API Keys
- Click "Create new secret key"
- Copy the key to
OPENAI_API_KEY - Important: Save immediately - you won't see it again!
- Add Billing: Add payment method in Billing settings
Documentation: OpenAI API Quickstart
Usage: The app uses GPT models for metadata enhancement and embedding models for vector search.
The project includes three Docker Compose files for different deployment scenarios:
- Builds all images locally from source
- Works on any architecture (x86_64, ARM64)
- Suitable for production on Mac/ARM systems
- Can be combined with dev overrides
- Extends base configuration with development features
- Hot reload for Next.js (volume mounts source code)
- Local development optimizations
- Usage:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
- Uses pre-built images from GitHub Container Registry
- Faster deployment (no build time)
- Includes all production services: app, db, Redis, MeiliSearch, Essentia API, GA service, download worker
- Currently x86_64/amd64 only - ARM64 images not yet published
- Ideal for Linux servers and tools like Portainer
- Usage:
docker compose -f docker-compose.prod.yml up -d
Recommendation:
- Linux servers (x86_64): Use
docker-compose.prod.ymlfor fastest deployment - Mac (Apple Silicon): Use
docker-compose.yml(local build) until ARM images are available - Development: Use
docker-compose.yml+docker-compose.dev.ymlcombined
# Check if database is running
docker compose ps db
# View database logs
docker compose logs db
# Test connection
docker compose exec db psql -U djplaylist -d djplaylist -c "SELECT 1;"# Check MeiliSearch logs
docker compose logs meili
# Verify MeiliSearch is accessible
curl http://localhost:7700/health
# Re-index all tracks (via UI or API)
# Navigate to /admin/reindex in the app- Ensure
.envfile exists inmy-collection-search/directory - Verify all required variables are set (especially
DISCOGS_USER_TOKEN) - Check that Apple Music
.p8file path is correct - Restart services after updating
.env:docker compose restart app
If ports 3000, 5432, or 7700 are already in use:
- Stop conflicting services
- Or modify ports in
docker-compose.yml(e.g.,"3001:3000")
# Check Essentia API logs
docker compose logs essentia-api
# Verify service is running
curl http://localhost:8000/health
# Check audio file permissions in ./audio/ volume- Next.js 15 (App Router)
- React 19
- TypeScript
- Chakra UI v3
- TanStack Query v5 (React Query)
- Framer Motion
- Zustand (state management)
- Next.js API Routes
- PostgreSQL 16 with pgvector extension
- MeiliSearch (search engine)
- Redis (job queuing)
- node-pg-migrate (database migrations)
- Essentia API (Python FastAPI) - Audio analysis
- Docker Compose - Orchestration
- Discogs API
- Apple Music API (MusicKit)
- Spotify Web API
- YouTube Data API v3
- OpenAI API (GPT-4 and embeddings)
Contributions are welcome! Please feel free to submit a Pull Request.
- Follow TypeScript strict mode conventions
- Use the existing query key patterns in
src/lib/queryKeys.ts - Update cache optimistically where appropriate
- Add migrations for schema changes
- Test with Docker Compose before submitting
MIT
For issues, questions, or feature requests, please open an issue on GitHub.
