diff --git a/src/renderer/features/albums/components/joined-artists.tsx b/src/renderer/features/albums/components/joined-artists.tsx
index e9c4f2e5e..6fada565d 100644
--- a/src/renderer/features/albums/components/joined-artists.tsx
+++ b/src/renderer/features/albums/components/joined-artists.tsx
@@ -96,24 +96,38 @@ export const JoinedArtists = ({
const hasArtistMatches = parts.some((part) => typeof part !== 'string');
+ // Find artists that were matched
+ const matchedArtistIds = new Set(nonOverlappingMatches.map((match) => match.artist.id));
+
+ // Find artists that are not present in the artist name
+ const unmatchedArtists = artists.filter(
+ (artist) => artist.name && !matchedArtistIds.has(artist.id),
+ );
+
// If no matches found and there are album artists, return the album artists
if (!hasArtistMatches && artists.length > 0) {
return (
{artists.map((artist, index) => (
-
+
{index > 0 && ', '}
-
- {artist.name}
-
+ {artist.id ? (
+
+ {artist.name}
+
+ ) : (
+
+ {artist.name}
+
+ )}
))}
@@ -137,21 +151,56 @@ export const JoinedArtists = ({
}
const { artist, text } = part;
+
+ if (artist.id) {
+ return (
+
+ {text}
+
+ );
+ }
return (
-
+
{text}
);
})}
+ {unmatchedArtists.length > 0 && (
+ <>
+ {', '}
+ {unmatchedArtists.map((artist, index) => (
+
+ {index > 0 && ', '}
+ {artist.id ? (
+
+ {artist.name}
+
+ ) : (
+
+ {artist.name}
+
+ )}
+
+ ))}
+ >
+ )}
);
};
diff --git a/src/shared/api/jellyfin/jellyfin-normalize.ts b/src/shared/api/jellyfin/jellyfin-normalize.ts
index ca969c39a..d6d404924 100644
--- a/src/shared/api/jellyfin/jellyfin-normalize.ts
+++ b/src/shared/api/jellyfin/jellyfin-normalize.ts
@@ -106,6 +106,33 @@ const getPlaylistImageId = (item: z.infer): nu
return null;
};
+const getArtists = (
+ item: z.infer,
+ participants?: null | Record,
+): RelatedArtist[] => {
+ if (!item?.ArtistItems?.length && !item.AlbumArtists && !participants) {
+ return [];
+ }
+
+ const result: RelatedArtist[] = [];
+
+ (item?.ArtistItems?.length ? item.ArtistItems : item.AlbumArtists)?.forEach((entry) => {
+ result.push({
+ id: entry.Id,
+ imageId: null,
+ imageUrl: null,
+ name: entry.Name,
+ userFavorite: false,
+ userRating: null,
+ });
+ });
+
+ if (participants?.['Remixer']) {
+ result.push(...participants['Remixer']);
+ }
+
+ return result;
+};
const normalizeSong = (
item: z.infer,
server: null | ServerListItem,
@@ -143,6 +170,11 @@ const normalizeSong = (
console.warn('Jellyfin song retrieved with no media sources', item);
}
+ const participants = getPeople(item);
+
+ const artists = getArtists(item, participants);
+
+ console.log('artists', artists);
return {
_itemType: LibraryItem.SONG,
_serverId: server?.id || '',
@@ -159,16 +191,7 @@ const normalizeSong = (
})),
albumId: item.AlbumId || `dummy/${item.Id}`,
artistName: item?.ArtistItems?.map((entry) => entry.Name).join(', ') || '',
- artists: (item?.ArtistItems?.length ? item.ArtistItems : item.AlbumArtists)?.map(
- (entry) => ({
- id: entry.Id,
- imageId: null,
- imageUrl: null,
- name: entry.Name,
- userFavorite: false,
- userRating: null,
- }),
- ),
+ artists,
bitDepth: null,
bitRate,
bpm: null,
@@ -210,7 +233,7 @@ const normalizeSong = (
mbzRecordingId: null,
mbzTrackId: item.ProviderIds?.MusicBrainzTrack || null,
name: item.Name,
- participants: getPeople(item),
+ participants,
path: replacePathPrefix(path || '', pathReplace, pathReplaceWith),
peak: null,
playCount: (item.UserData && item.UserData.PlayCount) || 0,
diff --git a/src/shared/api/navidrome/navidrome-normalize.ts b/src/shared/api/navidrome/navidrome-normalize.ts
index 5bdb4a6e8..43fc5cd38 100644
--- a/src/shared/api/navidrome/navidrome-normalize.ts
+++ b/src/shared/api/navidrome/navidrome-normalize.ts
@@ -57,12 +57,13 @@ const getArtists = (
) => {
let albumArtists: RelatedArtist[] | undefined;
let artists: RelatedArtist[] | undefined;
+ let remixers: RelatedArtist[] | undefined;
let participants: null | Record = null;
if (item.participants) {
participants = {};
for (const [role, list] of Object.entries(item.participants)) {
- if (role === 'albumartist' || role === 'artist') {
+ if (role === 'albumartist' || role === 'artist' || role === 'remixer') {
const roleList = list.map((item) => ({
id: item.id,
imageId: null,
@@ -74,6 +75,8 @@ const getArtists = (
if (role === 'albumartist') {
albumArtists = roleList;
+ } else if (role === 'remixer') {
+ remixers = roleList;
} else {
artists = roleList;
}
@@ -121,7 +124,7 @@ const getArtists = (
];
}
- if (artists === undefined) {
+ if (artists === undefined && remixers === undefined) {
artists = [
{
id: item.artistId,
@@ -134,7 +137,7 @@ const getArtists = (
];
}
- return { albumArtists, artists, participants };
+ return { albumArtists, artists: [...(artists || []), ...(remixers || [])], participants };
};
const normalizeSong = (
diff --git a/src/shared/api/subsonic/subsonic-normalize.ts b/src/shared/api/subsonic/subsonic-normalize.ts
index 7f28635d0..58c39628e 100644
--- a/src/shared/api/subsonic/subsonic-normalize.ts
+++ b/src/shared/api/subsonic/subsonic-normalize.ts
@@ -21,26 +21,39 @@ const getArtistList = (
artists?: typeof ssType._response.song._type.artists,
artistId?: number | string,
artistName?: string,
+ participants?: null | Record,
) => {
- return artists
- ? artists.map((item) => ({
- id: item.id.toString(),
- imageId: null,
- imageUrl: null,
- name: item.name,
- userFavorite: false,
- userRating: null,
- }))
- : [
- {
- id: artistId?.toString() || '',
- imageId: null,
- imageUrl: null,
- name: artistName || '',
- userFavorite: false,
- userRating: null,
- },
- ];
+ if (!artists && !participants) {
+ return [
+ {
+ id: artistId?.toString() || '',
+ imageId: null,
+ imageUrl: null,
+ name: artistName || '',
+ userFavorite: false,
+ userRating: null,
+ },
+ ];
+ }
+
+ const result: RelatedArtist[] = [];
+
+ artists?.forEach((item) => {
+ result.push({
+ id: item.id.toString(),
+ imageId: null,
+ imageUrl: null,
+ name: item.name,
+ userFavorite: false,
+ userRating: null,
+ });
+ });
+
+ if (participants?.['remixer']) {
+ result.push(...participants['remixer']);
+ }
+
+ return result;
};
const getParticipants = (
@@ -121,6 +134,8 @@ const normalizeSong = (
pathReplace?: string,
pathReplaceWith?: string,
): Song => {
+ const participants = getParticipants(item);
+
return {
_itemType: LibraryItem.SONG,
_serverId: server?.id || 'unknown',
@@ -130,7 +145,7 @@ const normalizeSong = (
albumArtists: getArtistList(item.albumArtists, item.artistId, item.artist),
albumId: item.albumId?.toString() || '',
artistName: item.artist || '',
- artists: getArtistList(item.artists, item.artistId, item.artist),
+ artists: getArtistList(item.artists, item.artistId, item.artist, participants),
bitDepth: item.bitDepth || null,
bitRate: item.bitRate || 0,
bpm: item.bpm || null,
@@ -164,7 +179,7 @@ const normalizeSong = (
mbzRecordingId: item.musicBrainzId || null,
mbzTrackId: null,
name: item.title,
- participants: getParticipants(item),
+ participants,
path: replacePathPrefix(item.path || '', pathReplace, pathReplaceWith),
peak:
item.replayGain && (item.replayGain.albumPeak || item.replayGain.trackPeak)