2026.07.02
[2026.07.02]
Added
jellyfin: Playlists API — full CRUD from the OpenAPI spec so third-party clients (Findroid, Streamyfin, Moonfin, Amcfy) can create, edit, and delete playlists through the standard surface without falling back to the Subsonic bridge;POST /PlaylistsreturnsPlaylistCreationResult,GET/POST /Playlists/{id},GET/POST/DELETE /Playlists/{id}/Items,POST /Playlists/{id}/Items/{itemId}/Move/{newIndex},GET /Playlists/{id}/Users, andDELETE /Items/{id}(which only accepts playlists — tracks/albums/artists return 403); backed by the existingrockbox-playlists::PlaylistStoreso the Subsonic and Jellyfin surfaces share state; a new virtual "Playlists"CollectionFoldershows up in/Users/{uid}/Viewsalongside Music so clients render a top-level tile;PlaylistItemIdvalues are synthesized deterministically from(playlist_id, position)so entry-id round-trips through remove/move without a mapping tablejellyfin: Favorites API — newjf_favorites (kind, native_id, favorited_at)migration covers tracks, albums, artists, and playlists; writes for tracks/albums mirror torockbox-library's existingfavouritestable so smart-playlistis_likedrules and the Subsonic bridge stay in sync, and reads take the union so likes added elsewhere still surface here;POST/DELETE /UserFavoriteItems/{id}(10.9+) and legacyPOST/DELETE /Users/{uid}/FavoriteItems/{id}both return the freshly-updatedUserItemDataDto;?IsFavorite=trueand?Filters=IsFavoriteon/Itemsroute through alist_favoritesorchestrator that honoursIncludeItemTypesacross all four kindsjellyfin: UserData API — newjf_user_data (kind, native_id, played, play_count, playback_position_ticks, last_played_at, likes, rating, updated_at)composite-PK cache backs the spec's per-item user-data fields; for tracks the store merges withrockbox-playlists::track_statson read (whicheverplay_countis higher wins, engine'slast_playedfills a null) so audio-engine counters surface on the Jellyfin side without a sync step;GET /UserItems/{id}/UserData(10.9+) and legacyGET /Users/{uid}/Items/{id}/UserDataroll upIsFavorite+ playback + rating + likes; the matchingPOSTaccepts a partialUpdateUserItemDataDtowhere unset fields preserve stored state per spec, andIsFavoriteis forwarded to the favorites store so both surfaces stay coherent; all four*_to_dtohelpers now emit real user-data fields instead of hardcoded zerosjellyfin: InstantMix API — seed-based mix generator coversGET /Items/{id}/InstantMix(generic dispatcher) plus the legacy/Songs,/Albums,/Artists,/Artists/InstantMix?id=,/Playlists, and/MusicGenres/{name}per-kind aliases; algorithm anchors the seed (or its own tracks), fills with same-artist_idmatches, then same-genre_id, then a random tail from the whole library; track seeds keep position 0 anchored through the shuffle so "play me first" behaviour is preserved; results dedup by native id and truncate toLimit(default 50)jellyfin: Lyrics API — reads a.lrc(synced) or.txt(plain) sidecar next to the audio file;GET /Audio/{id}/Lyricsreturns aLyricDto(withLyricLine.Startin 100-ns ticks and aLyricMetadatablock populated fromar/al/ti/by/offset/length/re/ve/auheader tags),POSTaccepts raw LRC / plain text OR a JSONLyricDtobody (distinguished byContent-Type— JSON re-serializes as LRC so external players can read the file),DELETEremoves sidecars idempotently,GET /Audio/{id}/RemoteSearch/LyricsandGET /Providers/Lyricsreturn empty arrays since no remote providers ship; LRC parser handles header tags, multi-timestamp expansion, and offset application; unit tests cover synced/multi-timestamp/plain parsingjellyfin: Similar API backed by Last.fm and MusicBrainz plugins —GET /Items/{id}/Similarplus legacy/Artists/{id}/Similarand/Albums/{id}/Similarroute through a plugin orchestrator that seedsartist.getsimilar/track.getsimilarfrom Last.fm and canonicalizes MBIDs through MusicBrainz before matching results back to the local library; artist seed → local artist by exact/case-insensitive name; album seed → same-artist expansion (Last.fm has no album endpoint) → their local albums; track seed → local(title, artist)lookup with the returned artist MBID cross-referenced through MB; plugins are gated onlastfm_api_key/musicbrainz_user_agent— no key means no fetches, andSimilarResultshort-circuits to empty rather than falling back to random suggestions; both plugin activations log at startupjellyfin: RemoteImage API backed by Cover Art Archive —GET /Items/{id}/RemoteImagesreturns candidate cover art (honourstype,startIndex,limit),GET /Items/{id}/RemoteImages/Providerslists active providers, andPOST /Items/{id}/RemoteImages/Download?imageUrl=&type=fetches the URL, saves under~/.config/rockbox.org/covers/using the samemd5(album_id)filename scheme the audio scanner uses, and updatesalbum.album_artin one shot; MusicBrainz search resolves(artist, album)to an MBID via a Lucene-escapedrelease-groupquery; CAA client handles both historical (small/large) and size-suffixed (250/500/1200) thumbnail shapes; track seeds fall back to their parent album so the "change cover art" action from a now-playing view works; gated on the samemusicbrainz_user_agentas the Similar pluginjellyfin: Genre Browsing API — newKIND_GENRE+remember_genreso genres get stable GUIDs;genre_to_dtopopulatesSongCount/AlbumCountfor chip UIs;GET /Genresand/MusicGenres(sorted list, honourssearchTerm/nameStartsWith/ range / pagination);GET /Genres/{name},/MusicGenres/{name}, and legacy/Users/{uid}/Genres/{name}with case-insensitive fallback;items_implgained aparentId=<genre_guid>branch (dispatches onIncludeItemTypesto return tracks / albums / artists under the genre) and anIncludeItemTypes=MusicGenreshorthand for the flat listjellyfin: Filters API —GET /Items/FiltersreturnsQueryFiltersLegacy(flat genre name list),GET /Items/Filters2returnsQueryFilters(NameGuidPairlist so chips round-trip through?genreIds=<guid>without a name→guid lookup), plus the pre-10.9GET /Users/{uid}/Items/Filtersalias; years come from distinct non-zeroalbum.year; tags and official ratings stay as empty arrays since rockbox doesn't track themjellyfin: Item Counts endpoint —GET /Items/CountsreturnsItemCountswith realSongCount/AlbumCount/ArtistCountfromrepo::*::count_filtered; unsupported kinds (movies, series, episodes, trailers, boxsets, books) stay at 0 per spec;ItemCountsums the three we surfacejellyfin: Last.fm artist enrichment — newjf_artist_enrichment (artist_id PK, mbid, bio, tags, image_url, fetched_at)cache backsOverviewandGenresonMusicArtistDTOs;LastFm::artist_infocallsartist.getInfo, cleans the trailing "Read more on Last.fm" link, extracts tags and the largest image URL;enrichment::get_artistis a cache-only read used by everyartist_to_dto(SQLite lookup — list requests stay fast),enrichment::refresh_artistenforces a 7-day TTL and upserts on demand from the detail handlers; serves stale rows on network errors so a blip doesn't blank the biojellyfin: Last.fm album enrichment — mirrors the artist flow with a newjf_album_enrichment (album_id PK, mbid, description, tags, image_url, fetched_at)cache;LastFm::album_infocallsalbum.getInfowith MBID priority over(artist, album), parses the wiki summary/content;album_to_dtoreads the cache on every call so list paths stay fast; the album detail branches initem_by_id/user_item_by_idcallrefresh_albumfirst so the returned DTO carries a hot descriptionjellyfin: home-rail routes replace the earlierempty_itemsstubs so Findroid / Streamyfin / official web actually populate their home screens —/Items/Resume+/Users/{uid}/Items/Resumereturn tracks with non-zeroPlaybackPositionTicksordered byupdated_at DESC,/UserItems/Resumeand/UserItems/Latestcover the legacy plain-array shape Findroid still uses,/Items/Suggestions+/Users/{uid}/Items/Suggestionsreturn random tracks by default (honoursIncludeItemTypes=MusicAlbum/MusicArtistandmediaType=Audio) via SQLRANDOM()so the rail refreshes each session; the generic/Itemshandler now recognizessortBy=PlayCountandsortBy=DatePlayed(plusDateLastPlayed) withsortOrder=Descending|Ascending, joiningtrackLEFT JOINtrack_statsso the "Most Played" and "Recently Played" home rails work through the standard/Itemsendpoint without dedicated handlerssettings: new optionallastfm_api_key: Option<String>andmusicbrainz_user_agent: Option<String>fields onNewGlobalSettings— the Jellyfin Similar / RemoteImage / enrichment plugins short-circuit to empty when their respective field is absent, so the plugins only activate when the corresponding credentials are present insettings.toml
Full Changelog: 2026.06.29...2026.07.02