A self-hosted web application for editing audio file metadata. Inspired by Mp3tag for Windows.
Tunewright runs as a single Docker container, serves a web UI, and operates directly on your music files via a volume mount. No database required.
- Batch tag editing — Edit title, artist, album, year, genre, track number and more across multiple files at once
- Cover art — View, add, replace, and remove embedded album artwork
- File renaming — Rename files using format strings like
%track% - %artist% - %title% - MusicBrainz lookup — Search releases, auto-fill tags, download cover art
- Spreadsheet-style grid — Virtual-scrolling file list with sortable columns and multi-select
- Tag panel — Quick-edit sidebar showing tags for selected files, with
< keep >for mixed values - Multi-user auth — First visitor creates admin account; invite others with shareable links
- Dark theme — Purpose-built UI, not a generic template
MP3, FLAC, M4A/MP4, AAC (ADTS), OGG Vorbis, Opus, Speex, WAV, AIFF, WavPack, APE, Musepack
Formats are detected from file contents, so a file with the wrong extension (for example Opus audio saved as .ogg) is still read and written correctly.
ID3v1, ID3v2.3, ID3v2.4, MP4/iTunes, Vorbis Comments, APE
services:
tunewright:
image: ghcr.io/krabhi4/tunewright:latest
ports:
- "8080:8080"
volumes:
- /path/to/your/music:/data:rw
read_only: true
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
restart: unless-stoppeddocker compose up -dOpen http://your-server:8080 in a browser. On first visit you'll be prompted to create your admin account.
docker run -d \
-p 8080:8080 \
-v /path/to/your/music:/data:rw \
ghcr.io/krabhi4/tunewright:latestAuthentication is built in and always active once an account exists.
- Start the container and open the web UI
- You'll be redirected to the setup page
- Choose a username and password — this becomes the super admin account
- You're logged in and ready to go
- Click your username in the toolbar (top-right)
- Select Manage Users
- Click Create Invite — a shareable link is generated (valid for 48 hours)
- Send the link to the person you want to invite
- They visit the link, choose a username and password, and get an admin account
| Role | Access |
|---|---|
| Super Admin | Full access + manage users (create invites, remove users) |
| Admin | Full access to all tag editing, renaming, and lookup features |
User accounts are stored in users.json (mode 0600) inside your data directory, or in TUNEWRIGHT_STATE_DIR if set, and persist across container restarts. Passwords are hashed with Argon2id.
All configuration is via environment variables.
| Variable | Default | Description |
|---|---|---|
TUNEWRIGHT_DATA_DIR |
/data |
Music directory inside the container |
TUNEWRIGHT_PORT |
8080 |
HTTP port |
TUNEWRIGHT_HOST |
127.0.0.1 (0.0.0.0 in the Docker image) |
Bind address |
TUNEWRIGHT_STATE_DIR |
data directory | Directory for users.json and mcp_auth.json; set it to keep account data out of the music library (mount a writable volume there). An existing users.json or mcp_auth.json is not moved automatically: if the data directory has one and the state directory does not, the server refuses to start until you move it |
TUNEWRIGHT_PUBLIC_URL |
unset | Public address of this server, such as https://music.example.com, used for MCP sign-in URLs and origin checks. When unset they come from the request's Host header (or the forwarded headers with TUNEWRIGHT_TRUST_PROXY) |
TUNEWRIGHT_STATIC_DIR |
/srv/static |
Frontend build directory (set by Docker) |
TUNEWRIGHT_SETUP_TOKEN |
unset | Token required to claim the first admin account |
TUNEWRIGHT_COOKIE_SECURE |
false |
Set to true when serving over HTTPS so the session cookie gets the Secure flag |
TUNEWRIGHT_TRUST_PROXY |
false |
Set to true behind a reverse proxy that sets X-Forwarded-For, so login throttling sees the real client address (the rightmost entry) instead of the proxy's. Leave it off otherwise: clients can spoof the header. While it is off, the server logs a one-time warning if a login request arrives with X-Forwarded-For. IPv6 clients are throttled per /64 prefix |
Authentication is managed through the web UI.
Until the first admin account exists, /api/v1/auth/setup is reachable without
authentication and hands the first caller super-admin. If the server binds a
non-loopback address with no TUNEWRIGHT_SETUP_TOKEN set, it generates one at
startup and prints it to the log — read it with docker compose logs and enter
it on the setup screen. Set TUNEWRIGHT_SETUP_TOKEN explicitly to choose your own.
Put TUNEWRIGHT_COOKIE_SECURE=true behind any HTTPS reverse proxy.
Rust (Axum) SvelteKit
┌─────────────────────┐ ┌──────────────────┐
│ tunewright-server │ REST API │ frontend/ │
│ routes/ │◄────────────►│ FileGrid │
│ auth middleware │ /api/v1/* │ TagPanel │
│ users.rs │ │ UserMenu │
│ │ │ PathBar │
│ tunewright-core │ │ RenameModal │
│ audio.rs (lofty) │ │ LookupModal │
│ scanner.rs │ └──────────────────┘
│ format_string.rs │
│ picture.rs │
│ rename.rs │
│ │
│ tunewright-lookup │
│ musicbrainz.rs │
└─────────────────────┘
│
┌───┴───┐
│ /data │ (volume mount)
└───────┘
- tunewright-core — Pure Rust library. Tag reading/writing via lofty, thumbnail generation via image, directory scanning, format string parser, file renaming.
- tunewright-lookup — MusicBrainz API client with rate limiting.
- tunewright-server — Axum HTTP server. Serves the SvelteKit SPA and REST API. Multi-user auth with Argon2id password hashing.
No database. Tag data lives in the audio files. User accounts live in users.json. UI state lives in the browser.
- Rust 1.89+
- Node.js 20+ with pnpm 12 (the exact version is pinned in
frontend/package.jsonpackageManager) - Docker (for container builds)
# Backend
cargo build
cargo test
# Frontend
cd frontend
pnpm install
pnpm run dev # Vite dev server with API proxy to localhost:8080
# Run backend
TUNEWRIGHT_DATA_DIR=./test-music cargo run -p tunewright-serverdocker build -t tunewright:latest .docker buildx create --name multiarch --driver docker-container --use
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t ghcr.io/krabhi4/tunewright:latest \
--push .All endpoints under /api/v1/. See the full API reference in docs/api.md.
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Health check |
| GET | /files?path=/&limit=500 |
List files in a directory |
| GET | /files/tree?depth=2 |
Directory tree |
| POST | /tags/read |
Batch read tags (fast, no audio properties) |
| POST | /tags/read-properties |
Batch read with audio properties (duration, bitrate) |
| POST | /tags/write |
Batch write tags |
| GET | /coverart?path=...&size=250 |
Get cover art thumbnail |
| DELETE | /coverart?path=... |
Remove cover art |
| POST | /rename/preview |
Preview file renames |
| POST | /rename/execute |
Execute file renames |
| GET | /lookup/musicbrainz/search?query=... |
Search MusicBrainz |
| GET | /lookup/musicbrainz/release/:mbid |
Get release details |
| POST | /auth/setup |
Create first user (super admin) |
| POST | /auth/login |
Login |
| POST | /auth/logout |
Logout |
| GET | /auth/check |
Check auth status / setup state |
| POST | /auth/register |
Register via invite token |
| POST | /auth/invites |
Create invite (super admin) |
| GET | /auth/invites |
List active invites (super admin) |
| DELETE | /auth/invites/:token |
Revoke invite (super admin) |
| GET | /auth/users |
List users (super admin) |
| DELETE | /auth/users/:id |
Remove user (super admin) |
- Backend: Rust, Axum, lofty, image, argon2, rayon, tokio
- Frontend: SvelteKit 5, TypeScript, adapter-static
- Deployment: Docker multi-stage build, Debian bookworm-slim runtime