From cadd1d9f561f17f53b3fd161b7eb38fdf724bbc3 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Fri, 19 Nov 2021 16:14:00 -0800 Subject: [PATCH] Add jf artist endpoints - Combined artist and artistInfo types --- src/api/api.ts | 50 ++++++----- src/api/controller.ts | 11 +-- src/api/jellyfinApi.ts | 66 ++++++++++++++- src/components/library/ArtistView.tsx | 116 +++++++++++--------------- src/components/player/PlayerBar.tsx | 5 +- src/types.ts | 9 +- 6 files changed, 162 insertions(+), 95 deletions(-) diff --git a/src/api/api.ts b/src/api/api.ts index 4efccde..b509232 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -176,10 +176,10 @@ const normalizeSong = (item: any) => { albumId: item.albumId, albumArtist: item.artist, albumArtistId: item.artistId, - artist: [{ id: item.artistId, title: item.artist }], + artist: item.artist && [{ id: item.artistId, title: item.artist }], track: item.track, year: item.year, - genre: [{ id: item.genre, title: item.genre }], + genre: item.genre && [{ id: item.genre, title: item.genre }], albumGenre: item.genre, size: item.size, contentType: item.contentType, @@ -228,21 +228,19 @@ const normalizeArtist = (item: any) => { albumCount: item.albumCount, image: getCoverArtUrl(item, legacyAuth, 350), starred: item.starred, + info: { + biography: item.biography, + externalUrl: item.lastFmUrl && [{ id: item.lastFmUrl, title: 'Last.FM' }], + imageUrl: + !item.externalImageUrl?.match('2a96cbd8b46e442fc41c2b86b821562f') && item.externalImageUrl, + similarArtist: (item.similarArtist || []).map((entry: any) => normalizeArtist(entry)), + }, type: Item.Artist, uniqueId: nanoid(), album: (item.album || []).map((entry: any) => normalizeAlbum(entry)), }; }; -const normalizeArtistInfo = (item: any) => { - return { - biography: item.biography, - lastFmUrl: item.lastFmUrl, - imageUrl: item.largeImageUrl || item.mediumImageUrl || item.smallImageUrl, - similarArtist: (item.similarArtist || []).map((entry: any) => normalizeArtist(entry)), - }; -}; - const normalizePlaylist = (item: any) => { return { id: item.id, @@ -390,7 +388,17 @@ export const getRandomSongs = async (options: { export const getArtist = async (options: { id: string }) => { const { data } = await api.get(`/getArtist`, { params: options }); - return normalizeArtist(data.artist); + const { data: infoData } = await api.get(`/getArtistInfo2`, { + params: { id: options.id, count: 8 }, + }); + + return normalizeArtist({ + ...data.artist, + biography: infoData.artistInfo2.biography, + lastFmUrl: infoData.artistInfo2.lastFmUrl, + externalImageUrl: infoData.artistInfo2.largeImageUrl, + similarArtist: infoData.artistInfo2.similarArtist, + }); }; export const getArtists = async (options: { musicFolderId?: string | number }) => { @@ -399,21 +407,21 @@ export const getArtists = async (options: { musicFolderId?: string | number }) = return (artists || []).map((entry: any) => normalizeArtist(entry)); }; -export const getArtistInfo = async (options: { id: string; count: number }) => { - const { data } = await api.get(`/getArtistInfo2`, { params: options }); - return normalizeArtistInfo(data.artistInfo2); -}; - export const getArtistSongs = async (options: { id: string }) => { const promises = []; - const artist = await getArtist({ id: options.id }); + const { data } = await api.get(`/getArtist`, { params: options }); + + console.log(`artist`, data.artist); - for (let i = 0; i < artist.album.length; i += 1) { - promises.push(getAlbum({ id: artist.album[i].id })); + for (let i = 0; i < data.artist.album.length; i += 1) { + promises.push(api.get(`/getAlbum`, { params: { id: data.artist.album[i].id } })); } const res = await Promise.all(promises); - return (_.flatten(_.map(res, 'song')) || []).map((entry: any) => normalizeSong(entry)); + + return _.flatten(res.map((entry: any) => entry.data.album.song) || []).map((entry: any) => + normalizeSong(entry) + ); }; export const startScan = async () => { diff --git a/src/api/controller.ts b/src/api/controller.ts index ab1a2cf..3afdfe2 100644 --- a/src/api/controller.ts +++ b/src/api/controller.ts @@ -8,7 +8,6 @@ import { getAlbum, getAlbums, getArtist, - getArtistInfo, getArtists, getArtistSongs, getDownloadUrl, @@ -34,6 +33,9 @@ import { updatePlaylistSongsLg, } from './api'; import { + getArtist as jfGetArtist, + getArtists as jfGetArtists, + getArtistSongs as jfGetArtistSongs, getAlbum as jfGetAlbum, getAlbums as jfGetAlbums, getPlaylist as jfGetPlaylist, @@ -49,10 +51,9 @@ const endpoints = [ { id: 'getAlbum', endpoint: { subsonic: getAlbum, jellyfin: jfGetAlbum } }, { id: 'getAlbums', endpoint: { subsonic: getAlbums, jellyfin: jfGetAlbums } }, { id: 'getRandomSongs', endpoint: { subsonic: getRandomSongs, jellyfin: undefined } }, - { id: 'getArtist', endpoint: { subsonic: getArtist, jellyfin: undefined } }, - { id: 'getArtists', endpoint: { subsonic: getArtists, jellyfin: undefined } }, - { id: 'getArtistInfo', endpoint: { subsonic: getArtistInfo, jellyfin: undefined } }, - { id: 'getArtistSongs', endpoint: { subsonic: getArtistSongs, jellyfin: undefined } }, + { id: 'getArtist', endpoint: { subsonic: getArtist, jellyfin: jfGetArtist } }, + { id: 'getArtists', endpoint: { subsonic: getArtists, jellyfin: jfGetArtists } }, + { id: 'getArtistSongs', endpoint: { subsonic: getArtistSongs, jellyfin: jfGetArtistSongs } }, { id: 'startScan', endpoint: { subsonic: startScan, jellyfin: undefined } }, { id: 'getScanStatus', endpoint: { subsonic: getScanStatus, jellyfin: undefined } }, { id: 'star', endpoint: { subsonic: star, jellyfin: undefined } }, diff --git a/src/api/jellyfinApi.ts b/src/api/jellyfinApi.ts index 6518385..161f753 100644 --- a/src/api/jellyfinApi.ts +++ b/src/api/jellyfinApi.ts @@ -75,7 +75,7 @@ const getCoverArtUrl = (item: any, size?: number) => { const normalizeItem = (item: any) => { return { - id: item.Id, + id: item.Id || item.Url, title: item.Name, }; }; @@ -134,6 +134,25 @@ const normalizeAlbum = (item: any) => { }; }; +const normalizeArtist = (item: any) => { + return { + id: item.Id, + title: item.Name, + albumCount: item.AlbumCount, + image: getCoverArtUrl(item, 350), + starred: item.UserData && item.UserData?.IsFavorite ? 'true' : undefined, + info: { + biography: item.Overview, + externalUrl: (item.ExternalUrls || []).map((entry: any) => normalizeItem(entry)), + imageUrl: undefined, + similarArtist: (item.similarArtist || []).map((entry: any) => normalizeArtist(entry)), + }, + type: Item.Artist, + uniqueId: nanoid(), + album: (item.album || []).map((entry: any) => normalizeAlbum(entry)), + }; +}; + const normalizePlaylist = (item: any) => { return { id: item.Id, @@ -248,4 +267,49 @@ export const getAlbums = async (options: { return (data.Items || []).map((entry: any) => normalizeAlbum(entry)); }; +export const getArtist = async (options: { id: string }) => { + const { data } = await jellyfinApi.get(`/users/${auth.username}/items/${options.id}`); + const { data: albumData } = await jellyfinApi.get(`/users/${auth.username}/items`, { + params: { + artistIds: options.id, + sortBy: 'SortName', + includeItemTypes: 'MusicAlbum', + recursive: true, + fields: 'AudioInfo, ParentId, Genres, DateCreated, ChildCount', + }, + }); + + return normalizeArtist({ + ...data, + album: albumData.Items, + }); +}; + +export const getArtists = async () => { + const { data } = await jellyfinApi.get(`/artists/albumartists`, { + params: { + sortBy: 'SortName', + sortOrder: 'Ascending', + recursive: true, + imageTypeLimit: 1, + }, + }); + + return (data.Items || []).map((entry: any) => normalizeArtist(entry)); +}; + +export const getArtistSongs = async (options: { id: string }) => { + const { data } = await jellyfinApi.get(`/users/${auth.username}/items`, { + params: { + artistIds: options.id, + sortBy: 'Album', + includeItemTypes: 'Audio', + recursive: true, + fields: 'Genres, DateCreated, MediaSources, UserData', + }, + }); + + return (data.Items || []).map((entry: any) => normalizeSong(entry)); +}; + // http://192.168.14.11:8096/Users/0e5716f27d7f4b48aadb4a3bd55a38e9/Items/70384e0059a925138783c7275f717859 diff --git a/src/components/library/ArtistView.tsx b/src/components/library/ArtistView.tsx index d1af098..b151b63 100644 --- a/src/components/library/ArtistView.tsx +++ b/src/components/library/ArtistView.tsx @@ -46,7 +46,7 @@ import { StyledButton, StyledPopover, StyledTag } from '../shared/styled'; import { setStatus } from '../../redux/playerSlice'; import { GradientBackground, PageHeaderSubtitleDataLine } from '../layout/styled'; import { apiController } from '../../api/controller'; -import { Server } from '../../types'; +import { Artist, GenericItem, Server } from '../../types'; const fac = new FastAverageColor(); @@ -70,18 +70,6 @@ const ArtistView = ({ ...rest }: any) => { const { isLoading, isError, data, error }: any = useQuery(['artist', artistId], () => apiController({ serverType: config.serverType, endpoint: 'getArtist', args: { id: artistId } }) ); - const { - isLoading: isLoadingAI, - isError: isErrorAI, - data: artistInfo, - error: errorAI, - }: any = useQuery(['artistInfo', artistId], () => - apiController({ - serverType: config.serverType, - endpoint: 'getArtistInfo', - args: config.serverType === Server.Subsonic ? { id: artistId, count: 8 } : null, - }) - ); const filteredData = useSearchQuery(misc.searchQuery, data?.album, [ 'title', @@ -261,18 +249,17 @@ const ArtistView = ({ ...rest }: any) => { if (!isLoading) { const img = isCached(`${misc.imageCachePath}artist_${data?.id}.jpg`) ? `${misc.imageCachePath}artist_${data?.id}.jpg` - : data?.image.includes('placeholder') - ? artistInfo?.largeImageUrl && - !artistInfo?.largeImageUrl?.match('2a96cbd8b46e442fc41c2b86b821562f') - ? artistInfo?.largeImageUrl + : data?.image?.includes('placeholder') + ? data?.info?.imageUrl + ? data?.info?.imageUrl : data?.image : data?.image; const setAvgColor = (imgUrl: string) => { if ( - data?.image.match('placeholder') || - (data?.image.match('placeholder') && - artistInfo?.largeImageUrl?.match('2a96cbd8b46e442fc41c2b86b821562f')) + data?.image?.match('placeholder') || + (data?.image?.match('placeholder') && + data?.info?.imageUrl?.match('2a96cbd8b46e442fc41c2b86b821562f')) ) { setImageAverageColor({ color: 'rgba(50, 50, 50, .4)', loaded: true }); } else { @@ -296,7 +283,7 @@ const ArtistView = ({ ...rest }: any) => { }; setAvgColor(img); } - }, [artistInfo?.largeImageUrl, data?.id, data?.image, isLoading, misc.imageCachePath]); + }, [data?.id, data?.image, data?.info, isLoading, misc.imageCachePath]); useEffect(() => { const allAlbumDurations = _.sum(_.map(data?.album, 'duration')); @@ -306,16 +293,12 @@ const ArtistView = ({ ...rest }: any) => { setArtistSongTotal(allSongCount); }, [data?.album]); - if (isLoading || isLoadingAI || imageAverageColor.loaded === false) { + if (isLoading || imageAverageColor.loaded === false) { return ; } - if (isError || isErrorAI) { - return ( - - Error: {error?.message} {errorAI?.message} - - ); + if (isError) { + return Error: {error?.message}; } return ( @@ -329,14 +312,13 @@ const ArtistView = ({ ...rest }: any) => { header={ { {artistDurationTotal} ]*>/, '') .replace('Read more on Last.fm', '')} placement="bottomStart" @@ -368,11 +350,11 @@ const ArtistView = ({ ...rest }: any) => { }} > - {artistInfo?.biography + {data?.info.biography ?.replace(/<[^>]*>/, '') .replace('Read more on Last.fm', '') ?.trim() - ? `${artistInfo?.biography + ? `${data?.info.biography ?.replace(/<[^>]*>/, '') .replace('Read more on Last.fm', '')}` : 'No artist biography found'} @@ -426,35 +408,39 @@ const ArtistView = ({ ...rest }: any) => {
Related artists
- {artistInfo.similarArtist?.map((artist: any) => ( - { - if (!rest.isModal) { - history.push(`/library/artist/${artist.id}`); - } else { - dispatch( - addModalPage({ - pageType: 'artist', - id: artist.id, - }) - ); - } - }} - > - {artist.title} - - ))} + {data.info.similarArtist && + data?.info.similarArtist.map((artist: Artist) => ( + { + if (!rest.isModal) { + history.push(`/library/artist/${artist.id}`); + } else { + dispatch( + addModalPage({ + pageType: 'artist', + id: artist.id, + }) + ); + } + }} + > + {artist.title} + + ))}

- shell.openExternal(artistInfo?.lastFmUrl)} - > - View on Last.FM - + {data.info.externalUrl && + data.info.externalUrl.map((ext: GenericItem) => ( + shell.openExternal(ext.id)} + > + {ext.title} + + ))} } > diff --git a/src/components/player/PlayerBar.tsx b/src/components/player/PlayerBar.tsx index 668090d..c021756 100644 --- a/src/components/player/PlayerBar.tsx +++ b/src/components/player/PlayerBar.tsx @@ -439,8 +439,9 @@ const PlayerBar = () => { enterable placement="topStart" text={ - playQueue[currentEntryList][playQueue.currentIndex]?.artist[0]?.title || - 'Unknown artist' + playQueue[currentEntryList][playQueue.currentIndex]?.artist + ? playQueue[currentEntryList][playQueue.currentIndex]?.artist[0]?.title + : 'Unknown artist' } >