vibecoded Go backend + simple frontend to play music files directly from server's disk via simple html "media" element
  • Go 53.2%
  • JavaScript 34.1%
  • CSS 10.4%
  • HTML 2.3%
Find a file
Qwen Code 601bfd2a66 feat: add view-id3-tags modal for songs
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.
2026-05-09 01:42:00 +00:00
db Add Mixes section 2026-05-09 01:09:30 +00:00
indexer Add Mixes section 2026-05-09 01:09:30 +00:00
static feat: add view-id3-tags modal for songs 2026-05-09 01:42:00 +00:00
.gitignore Initial project scaffold: Go backend, frontend placeholder, SQLite, and tag indexing 2026-05-08 20:58:45 +00:00
go.mod indexer: mtime-based skip, dhowden/tag, nightly scan, debounced trigger 2026-05-08 23:56:10 +00:00
go.sum indexer: mtime-based skip, dhowden/tag, nightly scan, debounced trigger 2026-05-08 23:56:10 +00:00
main.go feat: add view-id3-tags modal for songs 2026-05-09 01:42:00 +00:00
README.md docs: add project status section to README 2026-05-09 01:21:56 +00:00

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 of Song objects ordered by artist, album, track.

  • GET /api/songs/{id} — Get a single song by its database ID.
    Returns a Song object or 404.

  • GET /api/search?q={query} — Full-text search via SQLite FTS5 across title, artist, album, and genre.
    Returns up to 200 Song objects 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 of Song objects ordered by track number.

Playlists

  • GET /api/playlists — List all playlists.
    Returns an array of Playlist objects ordered by name.

  • POST /api/playlists — Create a new playlist.
    Body: { "name": "...", "description": "...", "auto": false, "query": "" }
    Returns the created Playlist with its generated id (201 Created).

  • GET /api/playlists/{id} — Get playlist metadata. Returns a Playlist object or 404.

  • PUT /api/playlists/{id} — Update playlist metadata.
    Body: same as create. Returns the updated Playlist.

  • DELETE /api/playlists/{id} — Delete a playlist. Returns 204 No Content.

  • GET /api/playlists/{id}/songs — Get the songs inside a playlist.
    For auto playlists the result is resolved dynamically from query; for manual playlists the stored song order is returned.
    Returns an array of Song objects.

  • POST /api/playlists/{id}/songs — Add a song to a manual playlist.
    Body: { "songId": 123 }. Returns 201 Created with { "status": "added" }.

  • DELETE /api/playlists/{id}/songs/{songId} — Remove a song from a playlist. Returns 204 No Content.

Scan & Stats

  • POST /api/scan — Trigger a manual filesystem scan in the background.
    Returns 202 Accepted with { "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 the static/ 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 song path values 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.