Update docs/subsonic-compatibility.md with the OpenSubsonic extension matrix
Documents every currently-registered OpenSubsonic extension (apiKeyAuthentication, formPost, getPodcastEpisode, songLyrics, indexBasedQueue, playbackReport, topSongsByArtistId, transcodeOffset, transcoding, sonicSimilarity), what backs each one, and what it's tested against — cross-referencing the extensions-registry regression guard that fails the build if an advertised extension's handler ever goes missing. Also updates the getArtistInfo/getAlbumInfo/ getSimilarSongs rows and error-code list for Phase 3 changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014r98AtcBbevpscWLsfwVvv
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
# Subsonic API compatibility
|
||||
|
||||
VibeSonic implements the **complete original Subsonic REST API through
|
||||
version 1.16.1** as of Phase 2. Every endpoint below is backed by real
|
||||
data from VibeSonic's own database/scanner/streaming/transcoding/jukebox
|
||||
subsystems — none of them are stub handlers that return an empty
|
||||
successful response.
|
||||
|
||||
OpenSubsonic extensions are **not yet implemented** (planned for Phase
|
||||
3), except where the underlying architecture already needed to make a
|
||||
choice (e.g. opaque string IDs almost everywhere) that happens to also
|
||||
suit OpenSubsonic later.
|
||||
version 1.16.1** (Phase 2) plus a growing set of **OpenSubsonic
|
||||
extensions** (Phase 3). Every endpoint below is backed by real data
|
||||
from VibeSonic's own database/scanner/streaming/transcoding/jukebox/
|
||||
audio-analysis subsystems — none of them are stub handlers that return
|
||||
an empty successful response. `getOpenSubsonicExtensions` only ever
|
||||
advertises an extension once its handler and tests exist and pass; see
|
||||
[OpenSubsonic extensions](#opensubsonic-extensions) below for the full
|
||||
list and what backs each one.
|
||||
|
||||
## How to read this table
|
||||
|
||||
@@ -56,11 +55,11 @@ suit OpenSubsonic later.
|
||||
| `getSong` | 1.8.0 | ✅ | — | ✅ | ✅ | ✅ (folder restriction) | |
|
||||
| `getVideos` | 1.8.0 | ✅ | — | ✅ | — | — | Real: lists media files the scanner tagged `kind=video`. |
|
||||
| `getVideoInfo` | 1.14.0 | ⚠️ | — | — | — | — | Reports one default audio track (VibeSonic doesn't probe multi-audio-track containers yet) and real sidecar `.srt`/`.vtt`/`.ass` captions found next to the file. |
|
||||
| `getArtistInfo` | 1.11.0 | ⚠️ | — | — | — | — | Honestly returns empty biography/images/similar-artists — VibeSonic has no external metadata provider in Phase 2. Not a fake success: it's an accurate "we don't have this" response. |
|
||||
| `getArtistInfo2` | 1.11.0 | ⚠️ | — | — | — | — | Same as above, ID3 form. |
|
||||
| `getAlbumInfo` | 1.14.0 | ⚠️ | — | — | — | — | Same reasoning as `getArtistInfo`. |
|
||||
| `getAlbumInfo2` | 1.14.0 | ⚠️ | — | — | — | — | Same. |
|
||||
| `getSimilarSongs` | 1.11.0 | ✅ | — | — | — | — | Real, local genre-overlap heuristic (song/album/artist seed), not fabricated data. |
|
||||
| `getArtistInfo` | 1.11.0 | ✅ | — | ✅ | — | — | Local MusicBrainz ID always returned; biography (MusicBrainz disambiguation) filled in only when `VIBESONIC_ENABLE_EXTERNAL_METADATA=true` and the artist has a tagged MBID — see [OpenSubsonic extensions](#opensubsonic-extensions). |
|
||||
| `getArtistInfo2` | 1.11.0 | ✅ | — | ✅ | — | — | Same as above, ID3 form. |
|
||||
| `getAlbumInfo` | 1.14.0 | ✅ | — | ✅ | — | — | Local MusicBrainz ID always returned; cover-art image URLs filled in from the Cover Art Archive under the same conditions as `getArtistInfo`. |
|
||||
| `getAlbumInfo2` | 1.14.0 | ✅ | — | ✅ | — | — | Same. |
|
||||
| `getSimilarSongs` | 1.11.0 | ✅ | — | — | — | — | Genre-overlap candidate set (song/album/artist seed), re-ranked by real sonic-feature distance when the seed song has been audio-analyzed. Not the same as `getSonicSimilarTracks` (pure sonic search) — see below. |
|
||||
| `getSimilarSongs2` | 1.11.0 | ✅ | — | — | — | — | ID3 (artist-seeded) form. |
|
||||
| `getTopSongs` | 1.13.0 | ✅ | — | ✅ | — | — | Ranked by real aggregate play count across all users. |
|
||||
|
||||
@@ -103,7 +102,7 @@ suit OpenSubsonic later.
|
||||
| `hls` | 1.8.0 | ✅ | n/a | n/a | — | ✅ (stream role) | Real ffmpeg HLS segmenting, cached per (source, bitrate); segment URLs re-embed the caller's auth params. |
|
||||
| `getCaptions` | 1.14.0 | ✅ | n/a | n/a | — | — | Serves real sidecar subtitle files. |
|
||||
| `getCoverArt` | 1.0.0 | ✅ | n/a | n/a | — | ✅ (coverArt role) | Embedded or folder art, resized and cached on disk. |
|
||||
| `getLyrics` | 1.2.0 | ✅ | ✅ | — | — | — | Real embedded lyrics extracted at scan time; empty (not fabricated) if none found. |
|
||||
| `getLyrics` | 1.2.0 | ✅ | ✅ | — | — | — | Title/artist search over the same structured lyrics storage `getLyricsBySongId` reads; empty (not fabricated) if none found. |
|
||||
| `getAvatar` | 1.8.0 | ⚠️ | — | — | — | — | Honestly 404s — VibeSonic has no user-avatar feature in Phase 1/2. |
|
||||
|
||||
## Transcoding
|
||||
@@ -232,6 +231,52 @@ enforced server-side (`internal/subsonic/folders.go`).
|
||||
|
||||
---
|
||||
|
||||
## OpenSubsonic extensions
|
||||
|
||||
VibeSonic advertises these via `getOpenSubsonicExtensions`
|
||||
(`internal/subsonic/extensions.go`); `TestGetOpenSubsonicExtensionsNeverStubbed`
|
||||
in `internal/subsonic/opensubsonic_core_test.go` fails the build if an
|
||||
extension is ever registered without its handler actually existing, so
|
||||
this list can't drift ahead of what's real.
|
||||
|
||||
| Extension | Versions | Endpoint(s) | Tested | Notes |
|
||||
|---|---|---|---|---|
|
||||
| `apiKeyAuthentication` | 1 | `tokenInfo`, `apiKey=` param on every endpoint | ✅ | Per-user named keys with plaintext shown once, SHA-256 hash stored, creation/last-used tracking, revocation (self or admin). `apiKey` conflicting with `u` returns error 43; an invalid/revoked key returns error 44. Managed at Settings → API Keys; admins see/revoke everyone's keys at `/admin/api-keys`. |
|
||||
| `formPost` | 1 | (framework-wide) | ✅ | `application/x-www-form-urlencoded` POST supported on every endpoint via `net/http`'s `ParseForm`; a key present in both the POST body and the query string resolves to the **body** value (verified empirically and regression-tested). |
|
||||
| `getPodcastEpisode` | 1 | `getPodcastEpisode` | ✅ | Real lookup against the Phase 2 podcast subsystem; 404s honestly for an unknown episode. |
|
||||
| `songLyrics` | 1, 2 | `getLyricsBySongId` | ✅ | Structured lyrics (multiple entries per song, multiple languages) from embedded tags and sidecar `.lrc`/Enhanced LRC files, stored with UTF-8 byte offsets computed once at index time. `enhanced=true` additionally returns `cueLine`/`cue` word-level timing — but only for entries actually parsed from a source with real word timing (Enhanced LRC); plain and line-only-synced lyrics never get fabricated cues. |
|
||||
| `indexBasedQueue` | 1 | `getPlayQueueByIndex`, `savePlayQueueByIndex` | ✅ | Same `play_queues` storage as the classic queue, but exposes `currentIndex` directly instead of resolving it to a song ID — correct even when the queue repeats the same song at multiple positions (regression-tested). |
|
||||
| `playbackReport` | 1 | `reportPlayback` | ✅ | Persists user/media/position/state/client/timestamp (`playback_reports` table); `state=stopped` also clears now-playing and (unless `ignoreScrobble`) logs playback history. Never leaks one user's playback state to another. |
|
||||
| `topSongsByArtistId` | 1 | `getTopSongs` (`id` param) | ✅ | `id` (canonical internal artist ID) takes priority over the legacy `artist` name param when both are given. |
|
||||
| `transcodeOffset` | 1 | `stream` (`timeOffset` param) | ✅ | Forces a real transcode with an actual ffmpeg `-ss` seek — never decode-from-zero — verified by asserting the response isn't a range-servable direct passthrough. |
|
||||
| `transcoding` | 1 | `getTranscodeDecision`, `getTranscodeStream` | ✅ | Real capability matching: compares the source file's actual scanned format/channels against the client's `directPlayProfiles`; falls back to searching `transcodingProfiles` for a codec VibeSonic's ffmpeg pipeline can actually encode (mp3/opus/aac) that also satisfies any `codecProfile` limitations the client marked required. `canDirectPlay`/`canTranscode` are never set true without an actual match. `getTranscodeStream` takes the opaque `transcodeParams` token verbatim and streams real transcoded audio with real seek support. |
|
||||
| `sonicSimilarity` | 1 | `getSonicSimilarTracks`, `findSonicPath` | ✅ | Backed by `internal/audiofeatures` (real ffmpeg PCM decode + a hand-written FFT for tempo/loudness/spectral-shape/chroma, computed by the scanner as part of each scan) and `internal/similarity` (an in-process, z-score-normalized nearest-neighbor index — no external vector database). A song that hasn't been analyzed yet simply doesn't appear in results; `findSonicPath` walks real feature-space geometry to a real intermediate song at each hop, never an invented one. |
|
||||
|
||||
### OpenSubsonic response/model additions
|
||||
|
||||
- `subsonic-response`: `type`, `serverVersion`, `openSubsonic` — always
|
||||
populated (`internal/subsonic/model.go`).
|
||||
- `error.helpUrl` — set on API-key-related errors (43, 44), pointing at
|
||||
the Settings API Keys page.
|
||||
- `Child`/other entity fields VibeSonic already populated correctly
|
||||
before Phase 3 (`replayGain*`, `musicBrainzId`, disc/track totals,
|
||||
etc.) are unchanged; nothing new was added here beyond what's listed
|
||||
in the extension table above (structured lyrics, sonic-match
|
||||
entries, transcode-decision/stream-details, index-based play queue).
|
||||
|
||||
### Optional external metadata (MusicBrainz / Cover Art Archive)
|
||||
|
||||
Not an OpenSubsonic extension itself, but part of Phase 3's metadata
|
||||
work: `getArtistInfo(2)`/`getAlbumInfo(2)` fetch a real MusicBrainz
|
||||
artist disambiguation or Cover Art Archive front-cover image when
|
||||
`VIBESONIC_ENABLE_EXTERNAL_METADATA=true` **and** the entity already
|
||||
carries a locally-tagged MusicBrainz ID, caching results in
|
||||
`external_metadata_cache` for 30 days. VibeSonic works completely
|
||||
without Internet access: the flag defaults off, and any lookup
|
||||
failure/timeout silently falls back to local-only fields rather than
|
||||
erroring the request. See `internal/metadataenrich` and
|
||||
`internal/subsonic/enrich.go`.
|
||||
|
||||
## Authentication
|
||||
|
||||
Both original Subsonic authentication modes are implemented in
|
||||
@@ -265,17 +310,21 @@ are accepted, since the protocol is additive.
|
||||
All defined in `internal/subsonic/errors.go`: 0 (generic), 10 (missing
|
||||
parameter), 20 (client must upgrade), 30 (server must upgrade, reserved
|
||||
— VibeSonic never needs to require a newer client), 40 (wrong
|
||||
credentials), 41 (token auth unsupported for this account), 50
|
||||
(unauthorized for the operation), 60 (license/trial — never returned),
|
||||
70 (not found).
|
||||
credentials), 41 (token auth unsupported for this account), 42 (auth
|
||||
mechanism not supported), 43 (conflicting auth mechanisms — e.g. both
|
||||
`apiKey` and `u` given), 44 (invalid/revoked API key), 50 (unauthorized
|
||||
for the operation), 60 (license/trial — never returned), 70 (not
|
||||
found).
|
||||
|
||||
## Web UI
|
||||
|
||||
Newly exposed through the Bootstrap UI in Phase 2: Podcasts (`/podcasts`,
|
||||
admin at `/admin/podcasts`), Internet Radio (`/radio`), Shares
|
||||
(`/shares`, plus the public `/share/{token}` page), Bookmarks
|
||||
(`/bookmarks`), and per-user Subsonic permission editing
|
||||
(`/admin/users/{id}/permissions`). The admin Jukebox page
|
||||
(`/bookmarks`), per-user Subsonic permission editing
|
||||
(`/admin/users/{id}/permissions`), and API key management (Settings →
|
||||
API Keys for any user; `/admin/api-keys` for admins to view/revoke
|
||||
everyone's). The admin Jukebox page
|
||||
(`/admin/jukebox`) shows live status and emergency stop/clear; building
|
||||
a jukebox playlist is done from a Subsonic client via `jukeboxControl`,
|
||||
which is the actual protocol surface for that feature.
|
||||
|
||||
Reference in New Issue
Block a user