Skip to content

Add album cover stacks to Artists and Album Artists views - #1563

Open
alchemyyy wants to merge 6 commits into
jeffvli:developmentfrom
alchemyyy:development
Open

Add album cover stacks to Artists and Album Artists views#1563
alchemyyy wants to merge 6 commits into
jeffvli:developmentfrom
alchemyyy:development

Conversation

@alchemyyy

Copy link
Copy Markdown

Changes:

  • Added getCoverArtValidator endpoint to Navidrome and Jellyfin controllers (Subsonic stub returns true for all)
  • New useCoverArtValidator hook creates a closure that captures server-specific placeholder detection state
  • New useArtistAlbumStack hook fetches artist albums and filters out placeholder covers

Settings:

  • toggle to enable/disable artist album cover stack

  • toggle to prefer artist cover (shows artist image if available, falls back to stack)

  • stack size setting (1-10 albums)

  • style selector (spun or staggered)

  • fitment selector (underfit, fit, overfit) with overfit size option

  • style-specific settings

    • spun: rotation angle (1-45 degrees)
    • staggered: horizontal and vertical offset values
  • all sub-settings hidden when main stack toggle is disabled

  • style-specific settings are shown only when that style is selected

Placeholder detection approach:

  • Jellyfin: Simple HEAD request returning 200 (real image) or 404 (no image).
  • Navidrome: I found a horrible way to do it. By fetching the cover art endpoint with no ID we are guaranteed to get the placeholder image. For whatever reason the placeholder images have their LastModified values set to the server startup time, which gives us something to compare against.
  • Subsonic: unimplemented.

Why the closure pattern:

The validator fetches server-specific state (the placeholder timestamp) once when the page mounts, captures it in a closure, and that closure lives for the duration of the page. When the user navigates away the component unmounts and the closure dies with it.

@vercel

vercel Bot commented Jan 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
feishin Ready Ready Preview, Comment Jul 23, 2026 8:50pm

@alchemyyy

Copy link
Copy Markdown
Author

I expect this to be pretty "raw" and in need of some work.

The styling options stuff was more or less me messing around with possibilities.

I didn't look too deeply into the Subsonic API for a way to run placeholder detections. Running image hashing "dynamically" by creating a single request for the size of placeholder the page would be requesting was one way I thought of doing it, but I'm not sure if retrieving the placeholder like I am with Navidrome is even guaranteed.

@jeffvli

jeffvli commented Jan 24, 2026

Copy link
Copy Markdown
Owner

From my testing, the artist grid works well, but the artist grid carousel is blank.

alchemyyy added 2 commits May 24, 2026 11:02
disk-backed image cache with rate-limited fetching via feishin-img:// protocol

All image URLs returned by getImageUrl() in the Jellyfin and Subsonic
controllers are wrapped into a feishin-img:// scheme, following the
existing feishin:// protocol pattern used for fonts. The main process
intercepts these requests, checks a disk cache, and only fetches from
the server on a miss. This is transparent to every existing image
consumer because the wrapping happens at the URL generation layer.

The disk cache stores images as flat files under {userData}/image-cache/,
organized into 2-character hex prefix subdirectories derived from a
SHA-256 hash of the original URL. An in-memory index tracks content
type, size, and last-accessed timestamp per entry, persisted to
index.json periodically and at shutdown. Eviction is LRU when total size
exceeds the configured limit.

Outbound fetches are governed by a token bucket rate limiter keyed per
server base URL, combined with a concurrency semaphore. Duplicate
requests for the same URL while a fetch is in-flight share the same
promise. A separate renderer-side concurrency limiter throttles the HEAD
requests used by the cover art stacking validator.

User-configurable settings:

  - Image cache enabled: toggle on/off (default on)
  - Disk cache size in MB (default 1000)
  - Rate limit burst: tokens available before throttling (default 10)
  - Rate limit refill rate: tokens per second (default 5)
  - Max concurrent requests per server (default 6)

New files:
  src/main/features/image-cache.ts
  src/renderer/utils/image-url.ts
  src/renderer/utils/rate-limited-fetch.ts
@camemb3rt

Copy link
Copy Markdown
Contributor

Came here to suggest exactly this, looking forward to it!

# Conflicts:
#	src/main/index.ts
#	src/renderer/store/settings.store.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants