8.3 KiB
8.3 KiB
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. |
LOG_LEVEL |
info |
error, warn, info, or debug. Selection and export summaries are logged at info; per-track export details and conversions are logged at debug. Logs go to stdout (terminal and Docker logs). |
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). - 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), and correct the editable Title, Artist, or Album fields if needed before searching. These corrections are temporary, stay available while paging through the current load, and are used only for artwork searches; reloading tracks or refreshing the page discards them. They are never written to RadioDJ. Click Search artwork per row (or Fetch Art for Current Page for the whole page) to get artwork candidates. The final upload-icon option on each row lets you add a local PNG/JPEG/WebP image (up to 15 MB) or provide a direct image URL. Custom artwork goes through the same full-size preview and confirmation flow as search results. 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. Exports support up to 20 local uploads at a time. Confirmed artwork selections are logged with track ID and source; setLOG_LEVEL=debugfor per-track export details. - 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 successfully exported item. Exported images are always RadioDJ-compatible.jpgor.png; WebP sources (including URLs and uploads) are converted to JPEG.
- 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. Artwork logs do not include image URLs or file contents. Unsupported formats are reported as export errors rather than added to the ZIP.
Tracks without selected artwork display the bundled Album-Art.png in the web interface. It is only a visual fallback and is not selected, exported, or written to RadioDJ automatically.
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 |
JSON for URL artwork, or multipart form data with a JSON payload and uploaded artwork files → 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)
├── Album-Art.png # Web-interface fallback for tracks without selected artwork
├── 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.