release: v1.5.10 — Symfonium compat, popular tracks, MusicBrainz mirror support

- fix(subsonic): map #text to value in JSON responses for Symfonium (#126)
- fix(subsonic): add getBookmarks.view empty stub for Symfonium (#126)
- fix(artist): show 10 popular tracks instead of 5 (#91)
- feat(musicbrainz): configurable base URL via MUSICBRAINZ_BASE_URL env var (#63)
This commit is contained in:
Your Name
2026-02-27 13:08:52 -06:00
parent 51c761715c
commit 376ae0590a
8 changed files with 27 additions and 9 deletions
+8
View File
@@ -91,6 +91,14 @@ VERSION=latest
# For split containers (docker-compose.yml), simply don't start the audio-analyzer-clap service.
# DISABLE_CLAP=true
# ==============================================================================
# OPTIONAL: MusicBrainz Configuration
# ==============================================================================
# Custom MusicBrainz API base URL (default: https://musicbrainz.org/ws/2)
# Useful for self-hosted MusicBrainz mirrors to avoid rate limiting
# MUSICBRAINZ_BASE_URL=https://musicbrainz.org/ws/2
# ==============================================================================
# OPTIONAL: Lidarr Webhook Configuration
# ==============================================================================
+5 -1
View File
@@ -5,7 +5,7 @@ All notable changes to Kima will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.5.9] - 2026-02-27
## [1.5.10] - 2026-02-27
### Added
@@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Enrichment pipeline: "Reset Vibe Embeddings" incomplete**: `reRunVibeEmbeddingsOnly()` reset `vibeAnalysisStatus` but did not delete existing `track_embeddings` rows, so the re-queue query (which uses LEFT JOIN) silently skipped tracks that already had embeddings. Now deletes all embeddings first for full regeneration.
- **Feature detection: CLAP reported available when disabled**: When `DISABLE_CLAP=true` was set, `checkCLAP()` skipped the file-existence check but still fell through to heartbeat and data checks. If old embeddings existed in the database, it returned `true`, causing the vibe sweep to queue jobs that no CLAP worker would ever process. Now returns `false` immediately when disabled.
- **docker-compose.server.yml healthcheck using removed tool**: Healthcheck used `wget` which is removed from the production image during security hardening. Changed to `node /app/healthcheck.js` to match docker-compose.prod.yml.
- **#126 Subsonic JSON `getGenres.view` breaking Symfonium**: Genre responses used `#text` for the genre name in JSON output -- correct for XML but violates the Subsonic JSON convention which uses `value`. Symfonium's strict JSON parser rejected the response. Fixed `stripAttrPrefix()` to map `#text` to `value` in all JSON responses.
- **#126 Subsonic `getBookmarks.view` not implemented**: Symfonium calls `getBookmarks.view` during sync and expects a valid response with a `bookmarks` key. The endpoint hit the catch-all "not implemented" handler, returning an error without the required key. Added an empty stub returning `{ bookmarks: {} }`.
- **#91 Artist page only showing 5 popular tracks**: Frontend sliced popular tracks to 5 even though the backend returned 10. Now displays all 10.
- **#63 MusicBrainz base URL hardcoded**: MusicBrainz API URL was hardcoded, preventing use of self-hosted mirrors. Now configurable via `MUSICBRAINZ_BASE_URL` environment variable (defaults to `https://musicbrainz.org/ws/2`).
## [1.5.8] - 2026-02-26
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "kima-backend",
"version": "1.5.9",
"version": "1.5.10",
"description": "Kima backend API server",
"license": "GPL-3.0",
"repository": {
+6
View File
@@ -58,6 +58,12 @@ subsonicRouter.all("/getOpenSubsonicExtensions.view", (req: Request, res: Respon
});
});
// Stubs for endpoints not yet fully implemented.
// Return valid empty responses so strict clients (e.g. Symfonium) don't error.
subsonicRouter.all("/getBookmarks.view", (req: Request, res: Response) => {
subsonicOk(req, res, { bookmarks: {} });
});
subsonicRouter.use(libraryRouter);
subsonicRouter.use(playbackRouter);
subsonicRouter.use(searchRouter);
+1 -1
View File
@@ -8,7 +8,7 @@ class MusicBrainzService {
constructor() {
this.client = axios.create({
baseURL: "https://musicbrainz.org/ws/2",
baseURL: process.env.MUSICBRAINZ_BASE_URL || "https://musicbrainz.org/ws/2",
timeout: 10000,
headers: {
"User-Agent":
+4 -4
View File
@@ -19,10 +19,10 @@ function stripAttrPrefix(obj: unknown): unknown {
if (Array.isArray(obj)) return obj.map(stripAttrPrefix);
if (obj !== null && typeof obj === "object") {
return Object.fromEntries(
Object.entries(obj as Record<string, unknown>).map(([k, v]) => [
k.startsWith("@_") ? k.slice(2) : k,
stripAttrPrefix(v),
])
Object.entries(obj as Record<string, unknown>).map(([k, v]) => {
const key = k === "#text" ? "value" : k.startsWith("@_") ? k.slice(2) : k;
return [key, stripAttrPrefix(v)];
})
);
}
return obj;
@@ -36,7 +36,7 @@ export const PopularTracks: React.FC<PopularTracksProps> = ({
<section>
<SectionHeader color="tracks" title="Popular" />
<div data-tv-section="tracks">
{tracks.slice(0, 5).map((track, index) => {
{tracks.slice(0, 10).map((track, index) => {
const isPlaying = currentTrackId === track.id;
const isPreviewPlaying =
previewTrack === track.id && previewPlaying;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "kima-frontend",
"version": "1.5.9",
"version": "1.5.10",
"description": "Kima web frontend",
"license": "GPL-3.0",
"repository": {