github Chevron7Locked/kima-hub v1.12.0-rc1
Kima v1.12.0-rc1

pre-release4 hours ago

Caution

Upgrading from 1.11 to 1.12 redoes your library's enrichment

Kima now hears your songs through LAION-CLAP's music model (see Changed), so on first start 1.12 removes the sound vectors 1.11 made and builds them again in the background. Until it finishes, similar tracks, the vibe map, search by sound, and the mood stations and mixes fill in as songs are done; the What's New window, the vibe page and the radio page show how far along it is. 1.12 also analyses every song again in the background: the analysis now reads the middle 90 seconds of each song and maps valence and arousal differently (see Changed), so each song is analysed once more and then given its new sound vector. On a copy of a 9,176-song library with a GPU, the whole upgrade took 1 hour 55 minutes: about 50 minutes of analysis, 25 of sound vectors and moods, and 40 of similarity and the map. Without a GPU it takes longer; the music model alone takes about 1.8 seconds a song per processor thread. Everything else stays: music, playlists, plays, likes, edits, identification and artists.

Upgrading from 1.10: read 1.11.0 below first: its one-time wipe and re-run of enrichment is still needed. 1.12 then starts its own re-analysis and new sound vectors by itself, so they need no re-run by hand.

Upgrading from Lidify: go through 1.11 first. The all-in-one image no longer renames a database or user named lidify to kima at first start (1.11's image still does), so an install that goes straight from Lidify to 1.12 does not carry its old database over.

Added

  • Moods that come from listening. Every song is scored on 22 moods (21 in use; "night" is each person's own late-night listening instead) by models learned from thousands of songs people have tagged with each mood (Deezer, MTG-Jamendo and others; all 22 beat chance on held-out songs), read from the same music-model sound vectors that match songs. A song counts as a mood when it scores 1.25 standard deviations above its own library's average, one line for mood tags, mixes, stations and the vibe map's mood view. Moods are a new enrichment step after sound vectors, with its own re-run; the analyzer no longer writes mood tags.
  • 14 mood stations. Calm, Energy, Intense, Happy, Melancholy, Focus: Calm, Focus: Energy, Feel-good, Late Night, Romantic, Road Trip, Nostalgic, Epic and Wildcard (GET /api/library/radio/stations). A station plays every song that fits, ordered so one artist doesn't run together. Late Night starts from the songs this person plays between 22:00 and 04:00 in their own time zone; Wildcard plays the songs no mood claims. The Summer, 3AM Thoughts and Friday Night stations are gone.
  • Daily mixes are recipes. Each mix is a blend of moods and readings (services/mixes/recipes.json); a mix draws 20 songs that fit, evenly across the recipe, fresh each day and per person, so everyone's picks differ from 1.11's. No more than two songs come from one artist; if that leaves fewer than 8 songs, it allows three, then no limit. Recipes that lean familiar favour songs like the listener's 200 most-played of the last year, through the matching engine. Midnight Drive and Late Night come from the person's own night plays.
  • Podcasts, browsed like a podcast app. Discovery comes from Apple Podcasts' own charts and categories instead of keyword searches (the old "top podcasts" was a search for the word "podcast"). The podcasts page leads with episodes in progress across every show and each followed show's newest unplayed episode, then "for you" rows from the categories of the shows you follow, the top shows, and a row of top shows for every other category (each opening its whole chart), loaded as you scroll. "More like this" ranks shows by how many of a show's categories they share. Search covers shows on Apple Podcasts and the episodes of shows you follow. A show you haven't followed shows its description, categories and newest 20 episodes with summaries, and plays its first episode (usually the one where the hosts explain the show), streamed from the feed and not stored. Deezer is no longer used for podcasts, and the old keyword recommender and its table are gone. Operators choose the chart country in Settings → Podcasts (default United States). New endpoints for clients: GET /api/podcasts/categories, /charts?category=, /for-you, /listening, /preview/:itunesId/snippet; podcast responses add categories.
  • Per-step enrichment reruns. Settings → Library Enrichment now has a step picker that re-runs from identification, artists, audio analysis, sound vectors, moods, or similarity — each clears itself and what genuinely depends on it (re-running artists keeps analysis and vectors; identification starts from scratch). Every step but moods also redoes similarity and the vibe map, since the formula reads artist genres, analysis and sound vectors and never the moods; a moods re-run leaves the map as it is. The full wipe-and-rerun is unchanged.
  • Update pings. Every 24 hours Kima checks GitHub's releases API for newer versions. When one is found, an amber dot appears on the version number in the app; tapping it opens the release's What's New window. The check can be turned off in Settings → Storage.
  • A permanent changelog. Every release's note and items stack newest first at /changelog and are never overwritten by the next version. Closing the release window marks it read, not done: anything a release still needs from you stays listed there and closes itself when the server sees it happen. The version number opens unread news, otherwise the changelog.
  • The library follows your disk. A track whose file two consecutive scans cannot find is removed, with everything attached to it (plays, likes, playlist entries), and its title, artist and album are written to a removal log first. No timer: the next scan confirms. If 10 or more tracks and over 10% of the library vanish at once, nothing is removed — that is a dropped drive, not a deletion. Admins can read the log at GET /api/library/removals. A song replaced by another copy on the same album (an mp3 upgraded to flac, say) hands its plays, likes, playlist places and bookmarks to the copy before it goes. An empty music folder (a drive that isn't mounted) marks nothing missing. Songs already flagged missing before upgrading are removed by the first scan after it. To keep the old behaviour (flag missing files, never remove them), set RECONCILE_MODE=flag.
  • An ordered play queue for iOS. GET /api/library/artists/:id/play-tracks returns only the fields a queue needs (id, title, track/disc number, duration, album cover, artist name) in canonical server order — effective year desc, title asc, disc/track within album. Only songs Kima can stream are included. The albums are the artist's library albums, not Discover albums; the endpoint returns them in its own canonical order, which the client consumes as-given, without the artist's full discography payload.
  • Untagged albums get their real MusicBrainz id. An album whose files carry no MusicBrainz album id is now identified once from all its songs' AcoustID answers: the album more than half of its songs agree on, under its own title (edition words like "Deluxe" aside, but "Live" kept apart), becomes its id. It then gets Cover Art Archive covers, Lidarr ownership, Discover's "already owned" check and its year, label and genres, and its songs take their details from one album request. Library albums only; generic titles ("Greatest Hits") and albums the votes can't settle are left alone, and a file's own album tag always replaces an id chosen this way. On a 9,151-song test library, 331 of 489 untagged albums would be identified; with the tags of 426 tagged albums hidden, the vote named 373 of the 374 it decided exactly as their tags do.
  • Five more daily mixes. Sleep, Summer, Epic, Dreamy and Nostalgic join the daily rotation, and every mood mix is 20 songs (it was 10).
  • A "Let people sign up" switch in Settings → User Management turns open registration on or off; before, only the API could change it.
  • A link to phone pairing. Settings → Native Apps links the /device page where the phone app is paired by QR code or code; the page's only link had been deleted.
  • Home stations and artist radio refill as they play, without repeating a song, and carry on after the queue has ended; a home station starts over once it has played everything.
  • The player is a named "now playing" region, so a screen reader can jump straight to it.

Changed

  • The public vibe feed is rebuilt as soon as the library changes. It was rebuilt only when someone asked for it, so the first request after a change got the copy from before it (on a feed nobody had read for days, a days-old one). With the feed on, Kima now checks every 30 seconds and rebuilds when anything in it changed.
  • Pages full of covers no longer run into the image limit. The server allowed 500 picture requests a minute from one address; a page shows 50 to 80, so a person clicking quickly through a few pages saw blank covers for the rest of the minute. The limit is now 3,000 a minute.
  • Release radar in the sidebar. The page of new and upcoming albums from the artists you follow is in the rail and the phone menu; nothing linked to it before.
  • Analysis drops seven unused mood readings. The audio analyzer no longer computes MusiCNN's seven raw mood scores (happy, sad, relaxed, aggressive, party, acoustic, electronic) and the database no longer stores them: moods come from the learned mood step, and nothing read these. Analysis does a little less work per song.
  • Similar tracks always use the matching formula. An install without the CLAP analyzer no longer falls back to a second, hand-weighted way of finding similar tracks; it shows none.
  • The CLAP analyzer is ONNX only. The PyTorch code path it carried, which no image could run, is gone.
  • The queue lives in the right-hand panel. The separate queue page is gone; the panel's queue tab now moves, removes and clears songs as the page did, and an old link to the page opens home. The panel's open button works from the keyboard.
  • The right-hand panel is two halves: listening and activity. Listening holds the playing song, then what is up next, its lyrics or its artist; activity holds everything Kima is doing, merged newest first with downloads running at the top, or just alerts, downloads, imports or history. The icon row with red badges, the numbered rows and the capitals are gone, the phone's activity overlay and the vibe page's sheet read the same way, and closed, the panel is a thin handle like the library's.
  • The sidebar's mixes show their covers. Each mix tile draws its covers instead of a flat colour, each cover once and fitted to how many there are (one fills the tile, two sit side by side, three are one tall and two small, four fill a grid), and the mix and playlist page headers use the same tile; refreshing the mixes moved to the section header, so it is there even with no mixes, and a mix list that fails to load offers to try again. The rail's icons name themselves on hover and keyboard focus, and the sidebar's quieter text is easier to read.
  • Matching uses the music-tuned sound model Kima was meant to use. Similar tracks, recommendations, Alchemy, Song Path, the vibe map, text search and the similar-artists re-rank now hear your songs through LAION-CLAP's music model, the one 1.10 ran, instead of the general-purpose CLAP model 1.11 switched to. Why: on every vibe test in the study it did better than the general model or the same, never worse. Songs placed next to each other share their rated mood more often, the feel read from the sound is closer to people's ratings, and the vibe map keeps more of each song's true neighbours. It still runs on ONNX Runtime without PyTorch, from five 10-second samples spread across each song. The feel layer was retrained for it on the same rated songs, and the vocal, acoustic and mood readings were refitted; the feel and genre weights are unchanged. Upgrading: the update removes the sound vectors the old model made and queues every song for a new one, the same work as Settings → Library Enrichment → re-run from sound vectors (use that to start it again by hand). Songs leave matching until they are done, and a fresh vibe map is laid out once they all are. The music model takes about 1.8 seconds a song on one processor thread, so about 30 minutes per 1,000 songs per sound-vector worker, roughly twice the general model's time. The image is about 165 MB larger: the music model's files are 782 MB against the general model's 619 MB.
  • Valence and arousal from audio analysis no longer pile up at the ends. The analyzer stretched its mood models' answers and cut them off at the ends of the scale, so about one song in five read as exactly the calmest or the most intense there is. It now maps them with a fit to people's ratings of thousands of songs, which agrees with those ratings far better on music it was not fitted on, and leaves no song at an end. The mood mixes and mood stations no longer use fixed cut-offs on these readings: they are recipes over the learned moods (see Added), where a song counts at 1.25 standard deviations above its own library's average. Only three mixes still filter on a reading directly: Vocal Detox plays songs whose instrumentalness is 0.75 or more, Minor Key Mondays plays minor-key songs whose arousal is at or above the library's median, and Tempo Flow plays only songs whose tempo the analyzer is sure of (a confidence of at least 2, now stored for every song). Upgrading: every song is analysed again in the background after the update (the analyzer takes about 10 seconds a song on one processor core), then given its new sound vector.
  • Turning the GPU switches off keeps the GPU download. It used to delete the roughly 3.4 GB runtime when both switches went off, so switching back on meant another 2 GB download. Now it stays on disk and does nothing until a switch is on again (a download still running when the last switch goes off is stopped). To free the space, stop Kima and delete the download by hand; the GPU guide (docs/gpu.md) says where it is for each install type.
  • Audio analysis and sound vectors take turns. Sound vectors start once audio analysis has no songs left, instead of running beside it, so the two heavy models never share the processor and the GPU. New songs are analysed first, then given vectors. Songs added during a vector run wait for the songs CLAP is working on, then go first. An analyzer that has stopped holds nothing back. Admins who want both at once can turn on "Run audio analysis and sound vectors at the same time" in Settings, under Library Enrichment, or beside the wipe-and-re-run button in the release window; it warns about the load first. GET/PUT /api/analysis/together.
  • Songs are identified by their sound first. A file whose tags don't say which recording it is now goes to AcoustID (audio fingerprints) before any MusicBrainz search, ten songs per request, and each reply is kept so no song is asked twice. Its ISRCs and genres then come from one MusicBrainz request per album (the album AcoustID lists under your album's title, or the album your tags name) instead of one request per song; songs AcoustID doesn't recognise, or matches only by length and not by title, still get the MusicBrainz name search first.
  • Your shows can be sorted by latest episode. The podcasts page's "episodes" sort ordered shows by how many episodes they have; it is now "latest episode", the show that published most recently first. GET /api/podcasts gives each show's latestEpisodeAt.
  • "More like this" on a show you haven't followed ranks shows by the categories they share, as it does for a followed show, instead of listing its first category's chart. New endpoint: GET /api/podcasts/preview/:itunesId/similar.
  • The release window says "What's new" everywhere. In wide mode the back button used to read "{sender}'s note" (confusing — it navigates to the overview, not a note). Now it says "What's new" on every screen size.
  • Open item highlight is more visible. The background went from 0.07 to 0.13 with a subtle inset ring, so it clearly differs from the item hover state.
  • Downloading is operator-only by default. Downloads cost money, use peer networks and fill the disk, so members can no longer start them unless the operator turns on Member Downloads in Settings → Download Preferences. Deleting stays operator-only either way, and the server enforces it at every download entry point; members keep browsing and searching. The weekly Discover run follows the same rule. The app shows download buttons only to people allowed to use them, and a member's playlist import keeps the songs already in the library and downloads nothing.
  • Audio analysis listens to the middle of each song. It reads the middle 90 seconds instead of the last 90, so a fade-out, a silent tail or a hidden track after minutes of silence no longer stands in for the song (one track read as near silence because 84 of its last 90 seconds were silent). Tempo, key, energy, danceability and the analysis half of each song's valence and arousal come from this window; the sound vectors behind matching, moods and the vibe map sample the whole song. Songs analysed before 1.12 are analysed again by the update itself (see Upgrading), so every song's readings come from the middle 90 seconds.
  • Playlists can flip to reverse order. A toggle next to shuffle reverses the list — the display order, the queue built from it, and it's remembered per playlist. Stored order and drag-to-reorder are untouched: dragging in the reversed view lands where it looks, not mirrored. (?order=rev works on the API too.)
  • Random Discovery plays ten artists. It drew five random albums, so with the per-artist limit it fell to about nine songs, and when most albums were one artist's, that artist filled the mix (18 of 20 on one library). It now takes the first album by each of ten artists, in the day's order, and two songs from each. A library with fewer than ten artists has no Random Discovery.
  • At most two songs per artist in more mixes. The mood mixes' limit (2, then 3, then none while fewer than 8 songs remain) now also holds for decade, genre, Random Discovery, More Like, Vocal Detox and Minor Key Mondays mixes; Your Top 20 and Rediscover have none. A decade or genre mix is offered only when its artists can fill 8 songs at that limit (on one library the 1980s mix had been 17 of 20 songs by one artist).
  • Minor Key Mondays and Tempo Flow changed. Minor Key Mondays plays minor-key songs whose arousal is at or above the library's median, instead of energy of 0.45 or more. Tempo Flow uses only songs whose tempo the analyzer is sure of, since an unsure tempo put a song in the wrong band.
  • The container follows your time zone. The all-in-one image forced UTC, so the TZ you set never applied. The daily mixes, Discover Weekly's Monday 05:00 run and the nightly clean-up now follow it; with TZ unset the container stays on UTC, and the Compose files default it to UTC.
  • One music folder for scans and downloads. Scans read the folder set at startup while Soulseek, imports, retries and Lidarr read the saved one, so a folder changed in Settings took downloads somewhere scans never looked. Everything now uses the folder in use. A folder saved in Settings applies at once; it is refused if it does not exist, if MUSIC_PATH fixes the folder (the field says so and is locked), or if it does not hold most of the library's songs, since scans drop songs they keep not finding. The transcode cache size also applies when saved, with no restart.
  • Discover Weekly starts each week clean. The weekly run cleared last week's suggestions but left Discover downloads no suggestion named, Discover albums nothing covers, failed Discover downloads and Lidarr's Discover-tagged artists to pile up. It now clears them all, as the manual clear does. An artist that another listener's current week or a running download still holds is left as it is, tag included. The manual clear no longer deletes every album-less artist in the library (the daily check does that). A listener's new Discover settings default to a playlist of 10 songs (it was 40).
  • Podcast covers come from the feed. Shows and episodes give the feed's image, loaded through Kima's image proxy; the cover cache is gone. Covers had been served from files never refreshed since 1.11, and an install without those files answered every podcast cover with a server error. Links saved in a player before 1.12 redirect to the proxy.
  • A release-radar download never joins Discover's. The radar kept its own check for "already downloading", which counted Discover jobs, so tapping an album Discover was fetching joined the Discover job; and two taps at once made the second fail. It now uses the same check as every other download, and two taps make one job.

Removed

  • Unused server endpoints. Nine API routes that neither the web app nor the iOS app calls: enrich-now for one artist or album (and the per-user enrichment service behind it), a podcast episode's cache status, recent releases, the Spotify import session log, the Spotify credentials test, the vibe status counter, similar tracks under recommendations, and the admin mood-bucket backfill.
  • More routes nothing calls. GET /api/plays (recording a play is unchanged), POST /api/spotify/parse, GET /api/releases/upcoming, the seed-based GET /api/recommendations and /api/recommendations/albums, the non-streaming form of the artist preview, GET /api/downloads (the list of download jobs; the web app and the iOS app read active downloads from the notifications), GET and PATCH /api/downloads/:id, /api/discover/generate/status, the /api/discover/exclusions routes, GET /api/library/cover-art-colors, GET /api/analysis/track/:id, GET /api/browse/playlists/featured (both apps read the same charts from /api/browse/all; a playlist id that is not a number now answers 404), POST /api/playlists/:id/pending/reconcile (every library scan already adds waiting songs that have arrived, for every playlist) and POST /api/onboarding/enrichment.
  • Offline downloads. The /api/offline routes and the "user cache size" slider in Settings are gone: neither the web app nor the iOS app used the routes, and the slider only set a limit for them. The server panel's cache size now counts the transcode cache alone.
  • The "Auto enrich metadata" switch. It changed only which settings rows showed; those rows now always show. The stored Spotify client id and secret are gone too: playlist import reads public playlists and needs neither. The download-path setting is gone: it was saved but never read, because Lidarr and Soulseek download into the music folder.
  • NZBGet in the server and services compose files. docker-compose.server.yml and docker-compose.services.yml now run SABnzbd (web page on port 8085, settings in a sabnzbd_config volume), as docker-compose.yml already did. Someone who keeps an old copy of either file keeps their NZBGet container and its settings.
  • The Lidify database rename, and SESSION_SECRET in the all-in-one compose files. See Upgrading for the first. The image keeps its own session secret in /data/secrets, so docker-compose.prod.yml and docker-compose.server.yml no longer pass one (the split docker-compose.yml still does).
  • The custom mood mix generator, which nothing called since mood mixes became recipes.
  • MAX_ANALYZE_SECONDS. Analysis always reads the middle 90 seconds of each song, so the setting is gone from .env.example and the image; a value you set is ignored.
  • The PODCAST_DEBUG switch. It only added detail lines to the podcast stream's debug log, which showed only when debug logging was already on; it is gone from docker-compose.yml too.
  • The bulk remove-songs endpoint DELETE /api/playlists/:id/items, which nothing called (removing songs from a playlist works as before), and enrichmentSettings in GET /api/auth/me's answer.

Fixed

  • Discover songs stay in Discover. Discover Weekly's suggestions are yours to try, not your library, but they leaked almost everywhere. They no longer play on any radio station or mood station, appear in any mix, mood page, search result, artist page, similar-track list, song path, blend or sound search, show as stars on the vibe map, or count toward recommendations and "more like this". The collection's Refine panel now means what it says: "in your library" shows library albums only (it also listed Discover albums an old ownership record still pointed at), "recommended" shows your own current Discover albums (it showed every listener's), and "all" shows both. Discover songs are no longer identified, analysed, given sound vectors or mood scores, and no longer count in your library's averages or on the enrichment page. Liking a song moves its whole album into your library and starts its enrichment straight away; unliking moves it back to Discover. Genre stations play your library only.
  • Your own albums are no longer filed as Discover. Kima filed an album as Discover when its title matched any Discover download, by any artist, or when Discover had ever suggested its artist; a daily check did the same to albums already in your library; and later scans marked Discover albums as yours. So albums you got yourself (often more by an artist Discover introduced you to) sat in Discover, and Discover albums showed up in your library. Now one rule decides, everywhere: an album is Discover only when a Discover download asked for exactly that album (the same release, or exactly the same artist and title). A suggestion alone says nothing, and an album you liked or asked for yourself is never Discover. On upgrade, Kima sorts the albums once. An album filed as Discover stays there if a Discover download asked for it, or, when no record says anything about it, if Lidarr holds its files under an artist carrying the Discover tag Kima puts on artists in Lidarr; every other one moves to your library. Albums in your library that a Discover download or a tagged Lidarr artist accounts for go back to Discover, unless you made them yours: a song of the album is liked or in a playlist, the album was edited, a like brought it in, its songs have 3 or more plays across 3 or more different songs, you own another album by that artist, or the library holds a second copy of the album. With Lidarr set up but unreachable, the sort waits for the next start.
  • Genre stations no longer split one genre by its spelling (#235). "Hip Hop", "hip-hop", "HipHop" and the like (any case, spaces, hyphens and dashes, underscores, slashes, dots, apostrophes, an accent typed as its own mark) are one station, named by the spelling most of its songs carry, and it plays every spelling. A genre station named only with punctuation plays nothing instead of the whole library, and a % in a genre name is a letter, not a wildcard. The genre list is also faster: about 0.7 s instead of 1.0 s on 200,000 songs, and 0.8 s instead of 2.1 s right after a large scan.
  • The collection's refine controls fit the screen (#216). The Refine panel ran past the bottom of the page at laptop, tablet and phone sizes, so sort and page size sat under the player bar until you scrolled. Wider than a phone there is no panel now: show, sort and page size sit on the toolbar line, with Reset beside them once something differs from the default. On a phone the Refine button opens them as a sheet from the bottom: sort first, the filter as a three-way switch, each setting a radio group (arrow keys, Home and End), and the button tells screen readers what is changed. On a TV the controls and Shuffle are a row of their own, between the tabs and the albums, with sort and page size as switches rather than dropdowns; before, the remote could reach neither.
  • The collection's mood band says what it shows. The thin band under the collection's title is split by each mood's share of your library. The songs that stand out on no single mood were a plain grey stretch; they are now a blend of the five mood colours. Hovering the band, or tapping it on a phone, says what makes a song a mood: scoring 1.25 standard deviations or more above your library's average for it.
  • The Fire TV remote's Back button works. Fire TV sends Back as its own key, which Kima ignored; on the TV layout it now does what Escape does: closes what's open, then returns to the tab row.
  • Podcast search counts against Apple's limit like Kima's other Apple requests. It called Apple directly, outside the queue that keeps Kima within Apple's limit; it now goes through it, ahead of background work, and still gives up after 5 seconds, waiting included.
  • A podcast download cut off by the host no longer leaves a half file in the cache. Kima deleted the partial file while it was still being closed, so it could reappear; it now waits for the file to close first.
  • On a TV remote, a row that loads in above you no longer moves you onto another row. The remote reads where the focus really is, remembers each row's column by the row itself, and starts a row at its first card from its "show all" link.
  • A setting saved in the old encrypted format can no longer come back as garbage. Values stored before 1.10's encryption change had no tamper check, so read with the wrong key they came back as random bytes about one time in 256 instead of being refused; and a value saved under the oldest key form was missed when the current key happened to pass. Kima now accepts only text, tries both key forms, and otherwise asks for the setting to be re-entered.
  • Songs of one album no longer fail when they save the same new genre at the same moment. Two songs finishing together both tried to create the genre, and one failed with a duplicate-name error until its retry.
  • Two songs of one album asked about at once share one MusicBrainz tracklist request instead of each making its own.
  • Playing music no longer leaves a "not found" error in the browser's console once per session. The player sent a report the server had no use for on most installs; the server now tells the player up front whether to send it.
  • Song previews play again. Every 30-second preview (in search, browse playlists, and album and artist pages for songs you don't own) answered "not found": Deezer stopped honouring the search Kima used to find them. Previews are now found with a plain search, matched by artist and title with or without the credits Deezer adds ("feat. …", "Radio Edit"), and a well-known song buried under covers in that search is found among its artist's own popular songs.
  • Artist photos and related artists come from the right artist. Two Deezer lookups took the first artist with the name you asked for, which can be an obscure namesake ranked above the real one; they now take the most-followed artist of that name, as the artist page's popular songs already did.
  • Members can no longer start downloads through a playlist import while downloads are operator-only. Importing a playlist without choosing albums still searched Soulseek for every song not in the library and downloaded it; it now does that only for someone allowed to download, and an import queued before an admin switched member downloads off no longer downloads either. Songs a member's import can't match stay on the playlist's pending list for an operator to retry.
  • Playlists stay current on all your open devices. Your other open devices used to see only a reorder as it happened (and a finished import adding its playlist to their list). Now every change reaches them as it happens: creating, renaming, making public or reversing a playlist; adding, removing or reordering its songs, or removing a song still being looked for; hiding or deleting it; saving a mix or a What's-new mood mix; a Subsonic app's changes; playlist imports (Spotify, Deezer, M3U) and the songs they find later; and songs leaving the library (deleting a song, album or artist, clearing Discover, or a scan removing songs whose files are gone). A device showing a playlist deleted elsewhere closes it, including when it only learns of the delete because the playlist answers "not found". When the live connection drops and comes back, the app refreshes what that connection keeps current (playlists, notifications, downloads, albums, enrichment progress, recently added and your imports); before, it refreshed only notifications and downloads. Someone else viewing your public playlist doesn't see your changes live; they show up when that person's page loads it again.
  • Quick edits to a playlist no longer undo each other on screen. Each answered edit fetched the playlist while later ones were still on their way, so those briefly vanished; and when several edits in a row failed with no connection, the playlist could be left showing one of them as done. Now the playlist is fetched once the last edit is answered, and put back as it was only if none of them went through.
  • Saving a mix as a playlist works when some of its songs have left the library. The save failed and left an empty playlist behind, which then blocked a retry as "already saved". Songs no longer in the library are left out; if none are left, the save says so and makes no playlist.
  • A Subsonic app's playlist change naming a song that doesn't exist changes nothing. A rename sent with it was applied before the song failed, and creating a playlist with such a song left an empty playlist. The whole request is now refused with "not found".
  • TV mode's Back works on every page, and only when nothing else on the page uses it. Back (the Escape key) returned to the tab row only on pages built of TV rows, and only with a card focused; on an artist or album page, or once the focus was lost, it did nothing. It now returns to the tab row from anywhere in the page. And Back that closed something (the sleep timer, a playlist rename, the What's new window, the sidebar's menu or new-playlist box, the library filter panel, podcast search results, an open hour on the listening clock, the stats page's shortcut list) or left the TV search box also jumped to the tab row; now it only does the one thing.
  • A song whose fingerprint was retried asks AcoustID once it has one. A song whose audio fingerprint failed could only be identified by name, and when retrying it from the failures screen then gave it a fingerprint, its identification stayed closed as "no match" without ever asking AcoustID. It now asks as soon as the fingerprint is stored. A retagged file is also read again before its songs are asked about in a batch, and a batch answer that has expired is asked again.
  • Checking a show for new episodes says what went wrong. A feed that is gone answers "not found" and one that doesn't answer (or answers with a web page) "try again shortly", instead of a server error.
  • A show's categories are read from its feed correctly. Categories written with single quotes were missed, and a category tag inside an episode was taken as the show's.
  • Saving podcast progress without a length keeps the length already saved, instead of setting it to zero.
  • Category rows on the iOS podcasts screen ask Apple only for real categories, each once, so an unknown or repeated id no longer costs a request to Apple.
  • The podcast search box no longer claims to be a keyboard-navigable list. It told screen readers to expect arrow-key movement it never had; its results are now plain buttons, reached with Tab.
  • A What's new choice that fails to save says so in the notification tray, instead of quietly switching back.
  • The changelog page shows what you just did. Answering or running something from the release window now updates /changelog straight away, and the page reads the server again on every visit, so an item that finished meanwhile (a re-run of the library) shows as done.
  • Subsonic apps that search with search2 get results again. Kima answered search2 under the old search label, so apps that read searchResult2 found nothing.
  • A list page asked for a negative or garbled starting point answers normally instead of with a server error: albums, artists, songs, the removal log, enrichment failures and Subsonic album lists. A Subsonic album list asked for a negative size no longer counts back from the end.
  • Covers whose host can't be reached say "try again shortly" instead of a server error, including a connection that drops twice in a row, and a host name that doesn't exist is no longer reported as a refused address. A host that answers with its own error or "too busy" (Cover Art Archive does, now and then) is treated the same way: the cover is tried again next time instead of being taken, and remembered for an hour, as having no picture.
  • A long queue no longer slows every page. The queue list in the side panel and the vibe page's sheet drew every song in the queue, so a queue of a few thousand songs put hundreds of thousands of elements on every page while the panel was open. It now draws only the songs in view.
  • A cover whose stored file is gone shows on the first load. When an album's cover file had been removed (the cover cache cleared, say), Kima found the cover again and sent the browser off to the picture's own host, which the web app's security rules forbid its background helper to follow, so the cover stayed blank until the next visit. Kima now fetches the found cover itself and serves it, as it does for every other picture.
  • An idle audio analyzer no longer logs an error every few seconds. Waiting for work on an empty queue ended in a "Timeout reading from socket" error and a reconnect, over and over, because the Redis library the image now ships gives up on a reply after 5 seconds, exactly as long as the analyzer waits for a job.
  • A new queue limit applies as soon as it is saved, instead of up to a minute later.
  • A link to an artist that exists nowhere says "Artist not found". It showed an empty artist page named after the link. A name MusicBrainz does not know still gets its page when Deezer has its songs, a picture or similar artists.
  • When Deezer's request limit is reached, Kima waits and asks again. Deezer answers a quota hit as an ordinary reply, so it was taken as a success and the lookup simply failed; and a failed request no longer leaves Browse's featured playlists empty for a day. Several lookups of the same artist's popular songs at once now share one request. And any outside service (MusicBrainz, Deezer, Apple and the rest) that answers again after a burst of "too many requests" is used again at once, instead of every request waiting out the rest of a minute.
  • Seeking reaches the end of a song whose file is longer than its tagged length, as the progress bar already showed.
  • The player's song title is no longer a page heading. The full-screen player added a second top-level heading over the page behind it, and the three players marked the title three different ways.
  • The last podcast or audiobook position is saved when you close or leave the page. The save sent as the page closed was cancelled by the browser; it is now sent so the browser finishes it.
  • A podcast episode or audiobook you listen to the end stays finished. At the end of a file the browser reports a pause just before the end, and the player saved that pause as "not finished" alongside the "finished" save; whichever arrived last won, so finished episodes sat in "continue listening" at 100% or more. Pausing, closing the page or switching away after the end no longer saves over it either.
  • The next episode plays when one ends, and the player's next button works for podcasts. "Next" was the next row of the show's page (newest first, so the older episode), and only when playback started there; resuming from "continue listening" just stopped. It is now the show's next episode by release date that you haven't finished, wherever playback started. New endpoint: GET /api/podcasts/:podcastId/episodes/:episodeId/next.
  • An episode listened past 95% counts as finished. It leaves "continue listening" and "next" skips it; replaying it from early on makes it unfinished again. The 95% is measured against the audio's own length as the player reads it, never the feed's listed length (which runs minutes short of audio with ads stitched in). Episodes saved past their listed end before this are marked finished once, when the server upgrades.
  • Unfollowing a show forgets that one of its episodes was playing, so the player no longer comes back to an episode of a show you've left.
  • Leaving a page in the middle of a save no longer reports an error. An answer cut off as the page closed was logged as "failed to save podcast progress" even though the server had already saved it.
  • The enrichment bars reach 100% when every song that's there is done. Songs whose files are gone (an album replaced by a better copy, say) held every bar short of 100; they're now left out and shown as "files missing" beside the count. The bar called "Mood Tags" is "Song Identification": it always counted which recording each song is (AcoustID and MusicBrainz), not moods, which come from audio analysis.
  • A show whose feed couldn't be read once stops being listed as failing the next time its feed is read. A moment without network kept every show it touched on the enrichment failures list for good, and the message said the feed couldn't be parsed when it couldn't be reached.
  • Changing an album's MusicBrainz id no longer drops its ownership protection. The album editor's id change relabelled the album's "owned" record as coming from enrichment, which could let the nightly integrity check move a library album into Discover; it also created ownership where there was none and left Discover exclusions and compilation ownership behind. Every record keyed on the old id now moves with it, keeping its source.
  • Podcast episode lengths. Ad networks stitch ads into episodes when they're downloaded, so the length a feed lists runs short (Dungeons and Daddies episodes measured 7–10% longer than listed). Kima now measures each cached episode's real length and stores it, and the first play, before the episode is cached, gets the real size from the host instead of the size the feed declares (which the browser turned into a length off by over half an hour). The player's clock no longer freezes at the listed length, and seeking reaches the real end. A malformed progress save answers 400 (or 404 for an unknown episode) instead of 500.
  • Podcast downloads judged by what the host sends. A download from a host that states no file size was measured against the size the feed declares, which can belong to another file, so complete episodes were thrown away three times and never cached. And a connection dropped in the first moments of a download left the episode marked as downloading until the server restarted. Downloads are now judged only by the host's own size, and a stored episode's size is the size of the file Kima holds.
  • Touch-screen hover-reveal controls on artist and album pages now respond to taps. Before, the controls that appear on hover were stuck off on touch screens (8 places fixed: play/edit/queue/lyrics on album and artist pages).
  • Preview stream range requests (the artist and playlist preview-stream endpoints) now forward Range headers through the shared preview proxy (proxyPreviewStream), and are tested against a local upstream server that answers with a real 206.
  • Subsonic search3 with an empty query enumerates the library (same as getAlbumList2) instead of returning an empty response.
  • Lidarr's webhook check reports Kima's version. It answered "unknown" in the published image, which starts the server with node rather than npm and so never set the variable it read.
  • Collection-page delete controls no longer show to non-admins (the backend already enforced it; the UI now gates on the signed-in user's role, which it previously ignored).
  • The analyzer refuses impossible loudness readings. A measurement outside −70…0.5 LUFS, or a range past 60 LU, is a meter or decoder failure, not music — one once reached the database as +2868 LUFS. It is now stored as no measurement, with a warning naming the file. Quiet real audio passes untouched.
  • prisma migrate dev works again. The 1.11 play-heard migration read Prisma's own migration table without checking it exists, so replaying the history onto a fresh database (migrate dev's shadow database, or a raw SQL replay) failed there. migrate deploy was unaffected. The full history now applies clean to an empty database.
  • A song whose file is back is no longer missing. A track flagged missing by one scan kept the flag after its file was found again (a file moved to another disk and linked back, for example), and could then be removed. The flag now clears whenever a scan finds the file.
  • Following a show twice, or one whose feed moved, works. Following looked for a stored show by feed address only, so a show Apple had moved to a new feed address, a double click, or two people following the same new show at once failed with a server error. It now finds the show by its feed or its Apple id and adds the follow once.
  • Podcast failures say what happened. An unknown category, a show Apple doesn't know, or a host refusing an episode answers "not found"; an Apple outage answers "try again"; and a bad reply from Apple is no longer remembered as "no shows" for hours. Following a feed says whether nothing is there, the host is not answering, or what it sent is not a feed.
  • Leaving or reopening a page no longer reports errors. Requests the browser cancels when you leave a page were logged as failures, and a page brought back from the background on a phone could stop reporting real errors at all.
  • The page after onboarding no longer starts a second Audiobookshelf sync (or logs errors when Audiobookshelf is off).
  • Moving a song in a playlist shows at once, reversed or not, instead of after the page refreshes.
  • Episode artwork shows on every show page. Episodes with their own art were drawn straight from the feed's host, which the app's security policy blocks for most hosts, so their art was missing (and errors were logged). It now loads through Kima, like every other image.
  • Escape in a confirmation closes only the confirmation, not the window behind it, and focus goes back to the button that opened it.
  • A show you just followed appears in search right away, and one you removed disappears.
  • Names in any script match. Titles and names written without English letters (Japanese, Hindi, Thai, Cyrillic and so on) were treated as blank in several places, so different songs matched each other: Spotify import took any such song for any other (asked for 道 it matched 花束を君に as exact), Soulseek's album download paired such tracks with whatever file came first, and Discover merged different Hindi and Thai names. Every comparison now keeps the letters of every script; a title with no letters or digits at all matches only itself. Search by name and genre works the same way, and Subsonic apps' genre filters match the web app's.
  • Artist and album keys for names in other scripts are repaired. The July 2026 upgrade computed them wrongly on a database that sees no letters outside English: a name in another script was keyed by its own id, and the scanner then filed a second copy whenever a file arrived without a release-group id. Once per install, before the workers start, Kima recomputes every key and sort name the scanner's way. Where two rows want one key the older keeps it and the other is logged, not merged.
  • Deleting an artist deletes only its own files. It removed the top folder of each of its songs' paths and any folder named after it, which took other artists' files with it when music was filed by genre, in a shared compilations folder, or under an artist whose name matched another folder. It now deletes the artist's song files, then each folder that held them only when what is left is leftovers (pictures, .nfo, .cue, .lrc, .log, system files); any other file, a link or an unreadable entry keeps the folder, and nothing outside the music folder is touched.
  • Subsonic apps list only songs that can play. Album pages, album lists, starred songs and album search counted or left out songs whose file is missing or unreadable inconsistently; an album's song count, length and song list now hold only playable songs, and an album with none is not listed.
  • An edited song title shows everywhere. The player bar, the full-screen and overlay players, the lock screen, the queue, the now-playing panel and every Subsonic reply showed the file's title after the title was edited in Kima; they now show the edit.
  • C# and the other sharp spellings count as their flats in key similarity and in the Key Journey mix.
  • A liked Discover album untags every artist of that name, and a liked artist's downloads are kept whatever the spelling. Finding the Lidarr artist by lowercased name took the first match and left a namesake tagged, and the weekly clear kept a liked artist's downloads only when the names matched lowercased. An unavailable suggestion now leaves the list only for the same release or the same artist and title, not for any album whose artist and title merely contain them ("Alive" stood for "Live"). A running Discover download shields only the album it names.
  • One album spelled two ways notifies once. Duplicate download notices were caught only when artist and album matched lowercased; accents and apostrophes now fold.
  • The Discover page no longer shows a stale "0 of 0" progress, and shows the day the server's week starts on whatever the browser's time zone; a browser west of the server showed the Sunday before.
  • A re-run no longer dies on a database deadlock. A re-run, a similarity build, a map write and the analyzers locked the same rows in different orders, so Postgres could kill a re-run after its queues were emptied, or leave the old map standing. They now lock in one order.
  • An unsupported audio file names its codec in the failure list in Settings (UNSUPPORTED::dts), instead of unknown.
  • The artist play queue skips rows it cannot stream. play-tracks now serves only library-owned albums with uncorrupt files still on disk — the same rule the search doors use — so a failed or still-pending download can no longer ride into an iOS queue as a 404. (Live example on the test stack: the endpoint served 413 rows where 410 were playable.)
  • Similar artists you own are found whatever their spelling. The artist page looked each one up by its plain lowercase name, so "Sigur Rós" or "Hall & Oates" showed as not in your library, with no picture.
  • Popular songs join the ones you own whatever their apostrophes. An artist page showed Deezer's "Evil's Twin" as a preview beside your own "Evil’s Twin".
  • Recently added fills its row. It read only the 20 newest albums, so one artist adding many left the row short.
  • Subsonic apps get one consistent answer for what a song streams as. Apps were sometimes told a song would play one way and then got another: a 128 kbps cap could give a 192 kbps MP3, and stream.view ignored format=mp3. A song now plays as it is when it fits the app's format and cap, else as the highest MP3 within the cap; a cap is never exceeded, and a cap of 320 means a 320 kbps MP3 of a lossless file rather than the file itself.
  • A song an import finds by its title alone keeps its place in the playlist. It landed out of order, or took a waiting song's place and failed the whole import.
  • A failed playlist import shows one row in the tray, not one from the import and one from the worker.
  • Retrying a failed podcast checks its feed again. The retry button took the failure for a song's, found no such song and cleared it without the feed ever being tried. It now makes the feed due for the next refresh, and the failure clears when that refresh works.
  • Editing a deleted artist, album or song answers "not found" instead of a server error.
  • The metadata editor opens above the page. Opened from an artist or album page, its Reset, Cancel and Save buttons were clipped under the song list. Resetting metadata and clearing Discover now ask in Kima's own dialog, not the browser's pop-up.
  • A picture's address is used as sent. Cover, shared-cover and audiobook-series addresses were decoded twice, so %25 named another file and a series called "100% Hits" answered with a server error.
  • A failed first scan no longer says "Ready to Go!"; it says once that the sync did not finish.
  • The right-hand panel pushes the page as it opens and closes, instead of the page snapping over in one step.
  • The enrichment speed slider no longer shows a made-up rate. Its "~N artists/min" was six to ten times the slider value and measured nothing.
  • A song waiting on a rate limit names the source that limited it, and waits on that source's pause, not another's.
  • A new GPU download is checked with its own CUDA libraries. The check after installing used the running install's, so a first install was checked with none and a refresh against the old ones.
  • Changing the analyzer's worker count and changing it back no longer leaves the first change waiting, and the analyzer reconnects to its database after five errors in a row, not after one error following a quiet spell.
  • Saving settings no longer writes a .env file. It rewrote ../.env with Lidarr, Audiobookshelf, Soulseek and cache values, which nothing in the image read, and set LIDARR_ENABLED=false on any save without Lidarr fields; in a checkout run from source it edited the project's own .env.
  • Turning Lidarr or Audiobookshelf off during first-run setup is saved even when no settings are stored yet; before, Lidarr stayed on.

Security

  • Redirects are checked at every hop. Kima checked only the first address of a feed, episode, cover or link it fetched; a redirect could then send it to an address inside your network (a cloud metadata service, say), which any signed-in member could read back through Kima. Every redirect is now checked, internal addresses are recognised however they are written (IPv6 forms of IPv4 addresses included), and a name that resolves to an internal address is refused when Kima connects.
  • Signing out clears what the app remembered. The next person to sign in on the same tab could see the last person's cached answers (playlists, shows, search results) for a few minutes. The app now forgets them on sign-out.
  • A show's Apple id can't be claimed by another feed. Following stored whatever Apple id came with the request, so a member could tie a real show's id to their own feed, and others following that show by its id got the impostor. An id is now kept only when Apple confirms it belongs to that feed.
  • Audiobookshelf is asked only for covers and books. The cover route passed any address it was given to Audiobookshelf with Kima's API key and returned the answer, so any signed-in listener could read other Audiobookshelf API pages through it; a book id such as ..%2Fusers%3F could aim stream, detail and progress requests elsewhere in Audiobookshelf the same way. Only a book's cover is fetched now, and a book id is letters, digits, _ and - only.
  • The first-run setup status checks sign-in as every other route does. It took refresh tokens, stream tickets and tokens revoked by a password change as a sign-in.
  • Search no longer shows other people's shows. Podcast and episode search read every show on the server, so a member saw the shows other users follow. Results now come only from the shows you follow.
  • transformers bumped from 4.57.6 to 5.10.4 in both services/audio-analyzer-clap/requirements.txt and the all-in-one image's runtime pip install. Fixes CVE-2026-4372, CVE-2026-5241 and CVE-2026-9856 (model-loading RCE class). The gate asserts bit-identical output (mel filter bank, window, mel spectrograms, 512-d audio and text embeddings, token ids — all identical on the shipped fixtures). The sha-gated ONNX export stage keeps its own pin on 4.57.6 (the export is torch-based and would not load a newer release under the same condition). The security nightly's "Dependency & Secret Scan" now reports zero HIGH/CRITICAL findings.
  • Next.js 16.4.1 closes two critical flaws that let anyone, signed in or not, run code on the server: one in image optimisation (GHSA-2464-fhr4-w9xw), one in the use cache path (GHSA-673q-mjm6-r5xr), plus a denial of service (CVE-2026-94482).
  • Node 24.22.1 carries the fix for Node2Shell (CVE-2026-111497), remote code execution through malformed Brotli data, which Node's built-in fetch decodes; the image's system Brotli is Debian's patched build. The backend and frontend Dockerfiles that the compose files build move from Node 20 (end of life, never to be fixed) to Node 24.
  • Dependencies: DOMPurify 3.4.17 (a critical sanitiser bypass), body-parser 1.20.9, multer 2.5.0, follow-redirects 1.16.2, undici 7.30.1, zod 4 (an unbounded-array denial of service; validation errors now list zod 4's issue objects under details), and OpenSSL 3.0.22-1~deb12u2 in the image.
  • The image is built from the project's own files only. Test-run traces (which can hold sign-in tokens), local assistant folders and macOS metadata files are kept out of the Docker build context.
  • Resetting edited metadata needs an admin. Editing an artist, album or song's metadata required one, but resetting it only required a sign-in, so any member could wipe an admin's edits. The web edit button shows only to admins too.
  • Soulseek connect and disconnect need an admin, since they act on the server's one connection, shared by every listener. A downloaded file also can no longer land outside the music folder: the file-name cleanup let a name of exactly .. through, so an artist and album named .. (from a playlist being imported) wrote outside it. One rule now applies to every download path — characters filesystems refuse become _, a name that is only dots becomes _, everything else is kept ("...And You Will Know Us" keeps its dots) — and the result is checked to be inside the music folder.
  • The image no longer shares one SSL private key across pulls. Debian's ssl-cert generated a key pair while the image built, so every pull shared it; Kima's Postgres has SSL off and never read it, so the pair is deleted. The CLAP analyzer's Python bullmq moves to 3.4.5, which pins msgpack 1.2.3, the fix for GHSA-j586-36cw-2gc2.

Quick start

docker run -d \
  --name kima \
  -p 3030:3030 \
  -v /path/to/your/music:/music \
  -v kima_data:/data \
  chevron7locked/kima:v1.12.0-rc1

Then open http://localhost:3030 and create your account. See the README for full documentation.

Full Changelog: v1.11.0...v1.12.0-rc1

Don't miss a new kima-hub release

NewReleases is sending notifications on new releases.