b9a2cc584bdeda7fdc545939a8a22148f08bb8cb
This is the start upload I figured this is a way for me to easily tweak with radio DJ and get the album art
RDJ Art Manager 🎵
A production-ready, open-source web app to find, review, and apply missing album artwork for a RadioDJ MySQL database.
- 🔍 Free artwork lookup — iTunes Search API first, MusicBrainz + Cover Art Archive fallback. No API keys, no credit cards.
- 🗂 Multi-user with roles — embedded SQLite (better-sqlite3) stores accounts and per-user RadioDJ connection profiles. First registered user becomes the admin.
- 🔐 Secure by default — scrypt password hashing, AES-256-GCM encrypted DB passwords, HttpOnly session cookies, rate-limited auth endpoints.
- 🎛 Spreadsheet-style review grid — preview artwork candidates per track, pick the right cover, filter, auto-search.
- 📦 ZIP export — images renamed to the base audio filename (
01 Track.mp3→01 Track.jpg), plus anexport-report.jsonmanifest. - 🗄 Optional DB update — sets
songs.imagein the RadioDJ database to the new file name. - 🐳 Docker — one
docker compose upwith./datapersisted.
Quick start
Local (Node.js ≥ 20)
npm install
npm start
# open http://localhost:3000
Docker
docker compose up -d --build
# open http://localhost:3000
SQLite data is persisted in ./data (mapped to /app/data in the container).
Configuration
| Env var | Default | Description |
|---|---|---|
PORT |
3000 |
HTTP port. |
DATA_DIR |
./data |
Where the SQLite database lives. |
APP_SECRET |
(auto) | Set this in production (min 16 chars). Encrypts stored RadioDJ DB passwords. If unset, a random secret is generated at data/.app-secret. Changing it makes stored DB passwords unreadable. |
COOKIE_SECURE |
0 |
Set to 1 when serving over HTTPS so session cookies get the Secure flag. |
Usage
- Register — the first account becomes the
admin. - Open Profiles and add a RadioDJ MySQL connection (host, port, database, user, password). Use Test to verify connectivity — it also reports how many tracks are missing artwork.
- Pick the station in the header dropdown and click Load tracks missing artwork. The query matches tracks where
song_type = 0andimageisNULL, empty, or a RadioDJ default placeholder (%no_cover%,%no_image%,%no-cover%— case-insensitive). Placeholder rows are flagged in the grid with an amber “placeholder art” badge. - Pick a primary art source in the header (Auto chains iTunes → Deezer → MusicBrainz, or pin a single API), use the grid pagination bar (page size 10/25/30/50/all, prev/next, numbered pages), then click Search artwork per row (or Fetch Art for Current Page for the whole page) to get artwork candidates. Hover a thumbnail to see its source (native tooltip), click it to open the full-size preview overlay (title, artist, source, “View Original Image”, and ✓ Confirm & Select This Art — close with backdrop click or
Esc). A row only becomes exportable once you tick its checkbox; fetching art never checks the box for you. - Click Download ZIP & Update Database:
- Uncheck Update RadioDJ DB to only download the ZIP.
- When checked, the app runs
UPDATE songs SET image = '<new file name>' WHERE id = <song id>for every exported item.
- Copy the images from the ZIP into the folder configured in RadioDJ under Options → Options → Album Art, so RadioDJ picks them up.
Note: RadioDJ's
songs.imagecolumn stores just the artwork file name (relative to its album-art folder). That is exactly what this app writes.
Admin
The admin header button opens the administration panel:
- List all users and change roles (
user⇄admin). You can't change your own role or demote the last admin. - Copy (clone) all station profiles from one user to another — encrypted passwords carry over.
API overview
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/register |
Register (first user becomes admin). |
| POST | /api/auth/login / logout |
Session auth (HttpOnly cookie). |
| GET | /api/auth/me |
Current session user. |
| GET/POST | /api/profiles |
List / create RadioDJ connection profiles. |
| PUT/DELETE | /api/profiles/:id |
Update (blank password keeps stored one) / delete. |
| POST | /api/profiles/:id/test |
Test MySQL connectivity + count missing artwork. |
| GET | /api/tracks?profileId=&page=&limit= |
Tracks missing artwork (song_type=0 and image NULL/empty or a no_cover/no_image/no-cover placeholder); limit=all disables paging. Returns {tracks,totalTracks,page,totalPages}. |
| POST | /api/artwork/search |
{artist,title,album,source} — source auto chains iTunes → Deezer → MusicBrainz/CAA; or itunes/deezer/musicbrainz only. |
| POST | /api/export |
{profileId,updateDb,items[]} → ZIP download + optional DB update. |
| GET | /api/admin/users |
(admin) List users. |
| PUT | /api/admin/users/:id/role |
(admin) Change role. |
| POST | /api/admin/clone-profiles |
(admin) Copy all profiles between users. |
| GET | /api/health |
Health check (used by Docker HEALTHCHECK). |
Project layout
├── server.js # Express backend: auth, profiles, MySQL pools, artwork search, ZIP export
├── public/index.html # Single-page Tailwind dashboard (vanilla JS)
├── data/ # SQLite storage (git-ignored, volume-mapped in Docker)
├── Dockerfile
├── docker-compose.yml
├── smoke-test.ps1 # Optional API smoke test (run while the server is up)
└── package.json
Security notes
- Passwords: scrypt (N=16384) + constant-time comparison.
- RadioDJ DB passwords: AES-256-GCM encrypted at rest; never returned by the API.
- Sessions: random 256-bit tokens in SQLite, HttpOnly + SameSite=Lax cookies, 7-day expiry.
- Auth endpoints are rate-limited; all API routes require authentication; admin routes require the
adminrole. - SQL access is parameterized; the RadioDJ connection user only needs
SELECT/UPDATEon thesongstable.
License
MIT — see LICENSE.
Languages
HTML
51.4%
JavaScript
41.8%
PowerShell
5.5%
Dockerfile
1.3%