A full-stack tool for managing a DJ music library — download, tag, deduplicate, and organize tracks automatically.
djtoolkit uses aioslsk to download from Soulseek directly (embedded Python client, no external service), matches tracks from Spotify playlists, enriches metadata, fingerprints for deduplication, and moves finalized files into a clean library folder.
The system has two modes:
- CLI — run pipeline steps directly via
makecommands, backed by Supabase (PostgreSQL) - Web — Next.js frontend deployed on Vercel with Supabase (PostgreSQL + Auth), plus a local agent daemon that processes jobs in the background
| Requirement | Notes |
|---|---|
| macOS (recommended) | Tested on macOS with Apple Silicon. Linux works. Windows untested. |
| Homebrew | macOS package manager — install it first |
| Python 3.11+ | brew install python |
| uv | curl -LsSf https://astral.sh/uv/install.sh | sh |
| Soulseek account | Free account at slsknet.org — needed for downloading |
Chromaprint (fpcalc) |
brew install chromaprint — needed for fingerprinting |
| Supabase project | PostgreSQL + Auth — set SUPABASE_PROJECT_URL, SUPABASE_SERVICE_ROLE_KEY, DJTOOLKIT_USER_ID in .env |
| Soulseek credentials | Username in djtoolkit.toml [soulseek], password in .env as SOULSEEK_PASSWORD |
| Requirement | Notes |
|---|---|
| Node.js 20+ | brew install node |
| npm | Comes with Node.js |
| Supabase project | Auth + PostgreSQL — configure in web/.env.local |
| Key | Used for | How to get |
|---|---|---|
| AcoustID | Fingerprint → MusicBrainz lookup (deduplication) | Free at acoustid.org/login |
| Spotify Client ID + Secret | Spotify metadata enrichment + cover art | Create an app at developer.spotify.com |
| Last.fm API key | lastfm cover art source |
Free at last.fm/api/account/create |
These go in .env (see .env.example). The three default cover art sources — Cover Art Archive, iTunes, and Deezer — require no API keys.
# 1. Clone and install
git clone https://github.com/youruser/djtoolkit.git
cd djtoolkit
make install
# 2. Configure
make init # copies djtoolkit.toml.example → djtoolkit.toml and .env.example → .env
# edit djtoolkit.toml — set your paths (downloads_dir, library_dir, etc.)
# edit .env — fill in API keys (AcoustID, Spotify, Last.fm as needed)
# 3. Add your Soulseek credentials
# edit djtoolkit.toml → set username under [soulseek]
# edit .env → set SOULSEEK_PASSWORD
# 4. Run the pipeline (Flow 1 — from Exportify CSV)
make import-csv CSV=~/Downloads/my_playlist.csv
make download
make fingerprint
make apply-metadata
make move-to-libraryThat's it — your tracks are tagged and in ~/Music/DJ/library.
Import a playlist exported from Exportify, download each track via Soulseek, fingerprint to remove duplicates, write clean tags, and move to your library.
import-csv → download → fingerprint → apply-metadata → move-to-library
See docs/flows.md for a full step-by-step walkthrough.
Scan a local folder of already-downloaded audio files, enrich metadata from an Exportify CSV or audio analysis, write tags, and move to the library.
import-folder → metadata apply --source → move-to-library
See docs/flows.md for details.
The web frontend is a Next.js app in web/ with Supabase Auth, deployed to Vercel. API route handlers live in web/app/api/.
cd web && npm install && npm run dev # Next.js at http://localhost:3000Pages: import, catalog, pipeline, agents, settings.
The agent is a single Python binary that handles CLI commands, a background daemon, and a system tray icon. It polls the cloud API for jobs (download, fingerprint, metadata, cover art) and executes them locally — your files never leave your machine.
# Homebrew (recommended)
brew tap yenkz/djtoolkit && brew install djtoolkit
# Or one-line installer
curl -fsSL https://raw.githubusercontent.com/yenkz/djtoolkit/main/install.sh | bashDownload the .msi from the latest release and run the installer.
djtoolkit setup # opens browser to connect your account (recommended)
djtoolkit setup --terminal # interactive terminal wizarddjtoolkit agent tray # system tray + daemon (recommended)
djtoolkit agent run # headless daemon only
djtoolkit agent install # install as launchd (macOS) / Windows Service
djtoolkit agent status # check daemon statusmake import-csv CSV=path/to.csv # import Exportify playlist
make download # download via Soulseek
make fingerprint # deduplicate with Chromaprint
make apply-metadata # write tags + normalize filenames
make move-to-library # move tagged files into library_dir (requires metadata_written=1)
make move-to-library MODE=imported # move all available tracks (skips metadata_written check)
make import-folder DIR=path/ # scan existing folder (Flow 2)
make import-trackid URL=https://... # identify track from YouTube URL
djtoolkit metadata apply --source spotify --csv path/to.csv # enrich DB + write tags in one step
djtoolkit metadata apply --source audio-analysis # BPM / key / loudness via librosa
make enrich ARGS='--spotify x.csv' # enrich DB only (no file writes)
make fetch-cover-art # fetch + embed cover art
uv run djtoolkit db status # track counts + processing flags
uv run djtoolkit db reset-downloading # reset stuck downloads → candidate
uv run djtoolkit db purge-failed # delete all failed tracksSee docs/flows.md for the full pipeline reference.
djtoolkit/
├── djtoolkit/
│ ├── __main__.py # Typer CLI entry point
│ ├── config.py # loads djtoolkit.toml via tomllib + dotenv loader
│ ├── adapters/
│ │ ├── base.py # abstract adapter interface
│ │ ├── supabase.py # SupabaseAdapter — sole data access layer
│ │ ├── traktor.py # Traktor NML import/export
│ │ └── rekordbox.py # Rekordbox XML import/export
│ ├── models/
│ │ ├── track.py # Track dataclass (to_db_row / from_db_row)
│ │ └── camelot.py # Camelot wheel key conversion
│ ├── agent/ # local agent daemon (job polling + execution)
│ │ ├── daemon.py # async event loop
│ │ ├── runner.py # job dispatcher
│ │ ├── executor.py # job executor
│ │ ├── client.py # HTTP client for cloud API
│ │ ├── keychain.py # credential storage
│ │ ├── launchd.py # macOS launchd integration
│ │ └── jobs/ # download, fingerprint, metadata, cover_art
│ ├── db/
│ │ ├── supabase_client.py # get_client() — reads env vars, returns supabase.Client
│ │ ├── pg_schema.sql # PostgreSQL schema reference
│ │ └── rls.sql # Row-Level Security policies
│ ├── importers/
│ │ ├── exportify.py # Flow 1: Exportify CSV → candidate tracks
│ │ ├── folder.py # Flow 2: scan audio files → available tracks
│ │ └── trackid.py # Flow 3: YouTube mix → identified tracks
│ ├── downloader/
│ │ └── aioslsk_client.py # embedded Soulseek client
│ ├── fingerprint/
│ │ └── chromaprint.py # fpcalc wrapper + AcoustID lookup
│ ├── metadata/
│ │ └── writer.py # mutagen tag writer + filename normalizer
│ ├── enrichment/
│ │ ├── spotify.py # Exportify CSV metadata enrichment
│ │ └── audio_analysis.py # librosa BPM/key/loudness
│ ├── coverart/
│ │ └── art.py # cover art fetcher + embedder
│ ├── library/
│ │ └── mover.py # move tagged files to library_dir
│ └── utils/
│ └── search_string.py # Soulseek search query builder
├── web/ # Next.js frontend (deployed to Vercel)
│ ├── app/ # App Router (TypeScript)
│ │ ├── api/ # API route handlers
│ │ ├── login/ # login page
│ │ ├── auth/callback/ # OAuth callback
│ │ └── (app)/ # authenticated pages
│ │ ├── import/ # import flows
│ │ ├── catalog/ # library browse
│ │ ├── pipeline/ # pipeline status
│ │ ├── agents/ # agent management
│ │ └── settings/ # settings
│ ├── components/ # React components
│ └── lib/ # Supabase client + utilities
├── supabase/ # Supabase project config + migrations
├── tests/
├── packaging/ # PyInstaller config (macOS + Windows)
├── Makefile
└── pyproject.toml
Copy djtoolkit.toml.example to djtoolkit.toml and edit it. Secrets (SOULSEEK_PASSWORD, ACOUSTID_API_KEY) go in .env.
See docs/configuration.md for a full reference.
- Web: Auto-deployed to Vercel on push to master
- Database: Supabase (hosted PostgreSQL + Auth + Realtime)
- Agent: Runs locally on your machine, polls Supabase for jobs
| Doc | Contents |
|---|---|
| docs/flows.md | Pipeline walkthroughs (Flow 1, 2 & 3) |
| docs/configuration.md | Full djtoolkit.toml reference |
| ARCHITECTURE.md | System design & component overview |
Personal project — not yet licensed for public distribution.