diff --git a/docs/subsonic-compatibility.md b/docs/subsonic-compatibility.md index a635796..f0829cc 100644 --- a/docs/subsonic-compatibility.md +++ b/docs/subsonic-compatibility.md @@ -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.