feat: album art fallback for artists without images
When no artist image is available from any source (library store, explore cache, MB/Wikidata API), fall back to using the artist's most popular album's cover art. Uses local library data first. Applied in three contexts: 1. Search results (explore-view): - After library image seed: checks library albums by artist name - After API fetch loop: final fallback for unresolved artists - Library-only search: checks album art in the seed pass 2. Artist detail page header (explore-artist-details): - In hydrateFromCache: checks library albums after image sources - After fetchArtistImage API call: fallback if API returned nothing The album art is displayed as a circular crop in the artist avatar, which naturally looks like an artist photo — no visual distinction needed.
This commit is contained in:
@@ -818,6 +818,23 @@ export class ExploreArtistDetails extends LitElement {
|
||||
this.loadingReleases = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: use album cover art if no artist image found.
|
||||
if (!this.artistImageURL && cachedAlbums) {
|
||||
const artistName = this.artistName.toLowerCase();
|
||||
|
||||
for (const a of cachedAlbums) {
|
||||
if (a.ArtistName.toLowerCase() === artistName) {
|
||||
const art = a.CoverArtMedium || a.CoverArtSmall || a.CoverArtPath;
|
||||
|
||||
if (art) {
|
||||
this.artistImageURL = art;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchArtist(mbid: string) {
|
||||
@@ -925,7 +942,27 @@ export class ExploreArtistDetails extends LitElement {
|
||||
this.artistImageURL = url;
|
||||
}
|
||||
} catch {
|
||||
// No image available — avatar stays as initial letter.
|
||||
// No image available.
|
||||
}
|
||||
|
||||
// Fallback: album cover art if no artist image resolved.
|
||||
if (!this.artistImageURL) {
|
||||
const cachedAlbums = libraryStore.cachedAlbums;
|
||||
if (cachedAlbums) {
|
||||
const artistName = this.artistName.toLowerCase();
|
||||
|
||||
for (const a of cachedAlbums) {
|
||||
if (a.ArtistName.toLowerCase() === artistName) {
|
||||
const art = a.CoverArtMedium || a.CoverArtSmall;
|
||||
|
||||
if (art) {
|
||||
this.artistImageURL = art;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,26 @@ function extractYear(dateStr: string): string {
|
||||
return dateStr.substring(0, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find album cover art for an artist from the library store.
|
||||
* Returns the best available cover art URL, or '' if none.
|
||||
*/
|
||||
function getArtistAlbumArt(artistName: string): string {
|
||||
const cachedAlbums = libraryStore.cachedAlbums;
|
||||
if (!cachedAlbums) return '';
|
||||
|
||||
const name = artistName.toLowerCase();
|
||||
|
||||
for (const a of cachedAlbums) {
|
||||
if (a.ArtistName.toLowerCase() === name) {
|
||||
const art = a.CoverArtMedium || a.CoverArtSmall || a.CoverArtPath;
|
||||
if (art) return art;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@customElement('explore-view')
|
||||
export class ExploreView extends LitElement {
|
||||
/* ── State ── */
|
||||
@@ -606,6 +626,16 @@ export class ExploreView extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: album art for artists without images.
|
||||
for (const a of localResults.artists || []) {
|
||||
if (a.mbid && !this.artistImageCache.get(a.mbid)) {
|
||||
const albumArt = getArtistAlbumArt(a.name);
|
||||
if (albumArt) {
|
||||
this.artistImageCache.set(a.mbid, albumArt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In library-only mode, local results already have cover art
|
||||
// and artist images from the library store — no API calls needed.
|
||||
if (!exploreSettings.libraryOnly) {
|
||||
@@ -1001,6 +1031,16 @@ export class ExploreView extends LitElement {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
// Fallback: use album cover art for artists without images.
|
||||
for (const a of this.results.artists) {
|
||||
if (a.mbid && !this.artistImageCache.get(a.mbid)) {
|
||||
const albumArt = getArtistAlbumArt(a.name);
|
||||
if (albumArt) {
|
||||
this.artistImageCache.set(a.mbid, albumArt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch remaining from API (only artists not yet resolved).
|
||||
for (const a of this.results.artists) {
|
||||
if (this.artistImageCache.has(a.mbid)) continue;
|
||||
@@ -1018,6 +1058,21 @@ export class ExploreView extends LitElement {
|
||||
// No image — leave empty string.
|
||||
}
|
||||
}
|
||||
|
||||
// Final fallback: album art for artists the API couldn't resolve.
|
||||
let fallbackUpdated = false;
|
||||
|
||||
for (const a of this.results.artists) {
|
||||
if (a.mbid && !this.artistImageCache.get(a.mbid)) {
|
||||
const albumArt = getArtistAlbumArt(a.name);
|
||||
if (albumArt) {
|
||||
this.artistImageCache.set(a.mbid, albumArt);
|
||||
fallbackUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fallbackUpdated) this.requestUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user