- Go 53.2%
- JavaScript 34.1%
- CSS 10.4%
- HTML 2.3%
Adds a 'Tags' button on every song row that opens a modal showing
all metadata parsed from the actual audio file via dhowden/tag.
New endpoint: GET /api/songs/{id}/tags
- Returns filePath, format, fileType, all parsed fields, picture info,
and raw underlying tags.
Frontend:
- Tags button in song table (next to play button)
- Modal with sections: File path, Format, Tags, Raw Tags
- File path is absolute for easy copy-paste into CLI tools like
id3v2, eyeD3, metaflac, etc.
No editing is supported — this is purely inspection/diagnostic.
|
||
|---|---|---|
| db | ||
| indexer | ||
| static | ||
| .gitignore | ||
| go.mod | ||
| go.sum | ||
| main.go | ||
| README.md | ||
Status
- Songs can be played.
- Artists: "Play all songs of this artist" works.
- Mixes: A separate directory where you can have a DJ's directory containing their mixes.
Music Player — Project Spec
A personal, self-hosted music player web app. Music files (primarily MP3) are stored on disk organized as /musicdir/Artist/Album/Song.mp3. A SQLite database holds song metadata (parsed from ID3 tags) and playlist definitions, which can be either auto-generated from metadata or manually curated. An indexer/scanner component keeps the database in sync with the filesystem.
The backend is written in Go using the standard library HTTP server, and handles API endpoints for metadata queries, search, and playlist logic. It does not serve music files directly. Music files are served by Caddy (which handles range requests, partial content, and caching automatically), with the music directory mounted at /music/*. The Go API returns /music/... URLs for each song, which the frontend passes directly to an HTML element for playback. The entire stack sits behind an Authelia instance — no auth logic lives in the app itself.
The guiding principle throughout is simplicity: sqlite over postgres, over a JS audio library, Caddy for file serving over reimplementing it in Go, filesystem as the source of truth for audio data.
How to build
This project uses modernc.org/sqlite, a Go-native SQLite driver. No C compiler or CGO setup is needed.
go build .
Or run directly:
go run . --music ./music --db ./music.db
API Reference
All endpoints return JSON. Errors use the shape { "error": "message" } with an appropriate HTTP status code.
Songs
-
GET /api/songs— List all songs, paginated.
Query params:limit(default 500, capped at 1000),offset.
Returns an array ofSongobjects ordered by artist, album, track. -
GET /api/songs/{id}— Get a single song by its database ID.
Returns aSongobject or404. -
GET /api/search?q={query}— Full-text search via SQLite FTS5 across title, artist, album, and genre.
Returns up to 200Songobjects ranked by relevance.
Artists & Albums
-
GET /api/artists— List every distinct artist in the library.
Returns["Artist A", "Artist B", ...]. -
GET /api/artists/{artist}/albums— List albums for a given artist, ordered by year then album title.
Returns["Album A", "Album B", ...]. -
GET /api/artists/{artist}/albums/{album}/songs— List songs on a specific album.
Returns an array ofSongobjects ordered by track number.
Playlists
-
GET /api/playlists— List all playlists.
Returns an array ofPlaylistobjects ordered by name. -
POST /api/playlists— Create a new playlist.
Body:{ "name": "...", "description": "...", "auto": false, "query": "" }
Returns the createdPlaylistwith its generatedid(201 Created). -
GET /api/playlists/{id}— Get playlist metadata. Returns aPlaylistobject or404. -
PUT /api/playlists/{id}— Update playlist metadata.
Body: same as create. Returns the updatedPlaylist. -
DELETE /api/playlists/{id}— Delete a playlist. Returns204 No Content. -
GET /api/playlists/{id}/songs— Get the songs inside a playlist.
For auto playlists the result is resolved dynamically fromquery; for manual playlists the stored song order is returned.
Returns an array ofSongobjects. -
POST /api/playlists/{id}/songs— Add a song to a manual playlist.
Body:{ "songId": 123 }. Returns201 Createdwith{ "status": "added" }. -
DELETE /api/playlists/{id}/songs/{songId}— Remove a song from a playlist. Returns204 No Content.
Scan & Stats
-
POST /api/scan— Trigger a manual filesystem scan in the background.
Returns202 Acceptedwith{ "status": "scanning" }. -
GET /api/stats— Quick library counts.
Returns{ "total": 1234, "artists": 42, "albums": 67 }.
Static Files
GET /— The Go server serves the frontend static files (index.html,app.js,style.css) from thestatic/directory.
Audio File Serving
Music files are NOT served by the Go backend.
MP3s are exposed as plain static files by Caddy (or another reverse proxy) mounted at/music/*. The API returns songpathvalues such as/music/Artist/Album/Song.mp3, which the frontend passes directly to an<audio>element. Caddy handles range requests, partial content, and caching automatically.
Response Shapes
Song
{
"id": 1,
"title": "Song Title",
"artist": "Artist Name",
"album": "Album Name",
"year": 2020,
"track": 1,
"genre": "Rock",
"path": "/music/Artist/Album/Song.mp3",
"duration": 180
}
Playlist
{
"id": 1,
"name": "My Playlist",
"description": "A cool playlist",
"auto": false,
"query": "artist:Radiohead album:OK Computer"
}
Auto-playlist query supports a small grammar: artist:X, album:Y, year:Z, genre:G, or free text which hits the FTS5 index.