diff --git a/server/api/musicbrainz/index.ts b/server/api/musicbrainz/index.ts index 00cf68d9..0d836dd5 100644 --- a/server/api/musicbrainz/index.ts +++ b/server/api/musicbrainz/index.ts @@ -455,73 +455,144 @@ class MusicBrainz extends BaseNodeBrainz { } }; - public getArtist = (artistId: string): mbArtist => { + public getArtist = (artistId: string): Promise => { try { - const rawData = this.artist(artistId, { - inc: 'tags+recordings+releases+release-groups+works', + return new Promise((resolve, reject) => { + this.artist( + artistId, + { + inc: 'tags+recordings+releases+release-groups+works', + }, + (error, data) => { + if (error) { + reject(error); + } else { + const results = convertArtist(data as Artist); + resolve(results); + } + } + ); }); - const artist: mbArtist = convertArtist(rawData); - return artist; } catch (e) { - throw new Error( - `[MusicBrainz] Failed to fetch artist details: ${e.message}` - ); + logger.error('Failed to get artist', { + label: 'MusicBrainz', + message: e.message, + }); + return new Promise((resolve) => resolve({} as mbArtist)); } }; - public getRecording = (recordingId: string): mbRecording => { + public getRecording = (recordingId: string): Promise => { try { - const rawData = this.recording(recordingId, { - inc: 'tags+artists+releases', + return new Promise((resolve, reject) => { + this.recording( + recordingId, + { + inc: 'tags+artists+releases', + }, + (error, data) => { + if (error) { + reject(error); + } else { + const results = convertRecording(data as Recording); + resolve(results); + } + } + ); }); - const recording: mbRecording = convertRecording(rawData); - return recording; } catch (e) { - throw new Error( - `[MusicBrainz] Failed to fetch recording details: ${e.message}` - ); + logger.error('Failed to get recording', { + label: 'MusicBrainz', + message: e.message, + }); + return new Promise((resolve) => resolve({} as mbRecording)); } }; - public getReleaseGroup(releaseGroupId: string): mbReleaseGroup { + public getReleaseGroup = ( + releaseGroupId: string + ): Promise => { try { - const rawData = this.releaseGroup(releaseGroupId, { - inc: 'tags+artists+releases', + return new Promise((resolve, reject) => { + this.releaseGroup( + releaseGroupId, + { + inc: 'tags+artists+releases', + }, + (error, data) => { + if (error) { + reject(error); + } else { + const results = convertReleaseGroup(data as Group); + resolve(results); + } + } + ); }); - const releaseGroup: mbReleaseGroup = convertReleaseGroup(rawData); - return releaseGroup; } catch (e) { - throw new Error( - `[MusicBrainz] Failed to fetch release group details: ${e.message}` + logger.error('Failed to get release-group', { + label: 'MusicBrainz', + message: e.message, + }); + return new Promise((resolve) => + resolve({} as mbReleaseGroup) ); } - } + }; - public getRelease(releaseId: string): mbRelease { + public getRelease = (releaseId: string): Promise => { try { - const rawData = this.release(releaseId, { - inc: 'tags+artists+recordings', + return new Promise((resolve, reject) => { + this.release( + releaseId, + { + inc: 'tags+artists+recordings', + }, + (error, data) => { + if (error) { + reject(error); + } else { + const results = convertRelease(data as Release); + resolve(results); + } + } + ); }); - const release: mbRelease = convertRelease(rawData); - return release; } catch (e) { - throw new Error( - `[MusicBrainz] Failed to fetch release details: ${e.message}` - ); + logger.error('Failed to get release', { + label: 'MusicBrainz', + message: e.message, + }); + return new Promise((resolve) => resolve({} as mbRelease)); } - } + }; - public getWork(workId: string): mbWork { + public getWork = (workId: string): Promise => { try { - const rawData = this.work(workId, { inc: 'tags+artist-rels' }); - const work: mbWork = convertWork(rawData); - return work; + return new Promise((resolve, reject) => { + this.work( + workId, + { + inc: 'tags+artist-rels', + }, + (error, data) => { + if (error) { + reject(error); + } else { + const results = convertWork(data as Work); + resolve(results); + } + } + ); + }); } catch (e) { - throw new Error( - `[MusicBrainz] Failed to fetch work details: ${e.message}` - ); + logger.error('Failed to get work', { + label: 'MusicBrainz', + message: e.message, + }); + return new Promise((resolve) => resolve({} as mbWork)); } - } + }; } export default MusicBrainz; diff --git a/server/api/plexapi.ts b/server/api/plexapi.ts index f6b8f3cb..3766668c 100644 --- a/server/api/plexapi.ts +++ b/server/api/plexapi.ts @@ -16,7 +16,7 @@ export interface PlexLibraryItem { Guid?: { id: string; }[]; - type: 'movie' | 'show' | 'season' | 'episode'; + type: 'movie' | 'show' | 'season' | 'episode' | 'artist' | 'album' | 'track'; Media: Media[]; } @@ -28,7 +28,7 @@ interface PlexLibraryResponse { } export interface PlexLibrary { - type: 'show' | 'movie'; + type: 'show' | 'movie' | 'artist'; key: string; title: string; agent: string; @@ -44,7 +44,7 @@ export interface PlexMetadata { ratingKey: string; parentRatingKey?: string; guid: string; - type: 'movie' | 'show' | 'season'; + type: 'movie' | 'show' | 'season' | 'episode' | 'artist' | 'album' | 'track'; title: string; Guid: { id: string; @@ -152,7 +152,10 @@ class PlexAPI { const newLibraries: Library[] = libraries // Remove libraries that are not movie or show .filter( - (library) => library.type === 'movie' || library.type === 'show' + (library) => + library.type === 'movie' || + library.type === 'show' || + library.type === 'artist' ) // Remove libraries that do not have a metadata agent set (usually personal video libraries) .filter((library) => library.agent !== 'com.plexapp.agents.none') @@ -227,12 +230,12 @@ class PlexAPI { options: { addedAt: number } = { addedAt: Date.now() - 1000 * 60 * 60, }, - mediaType: 'movie' | 'show' + mediaType: 'movie' | 'show' | 'artist' ): Promise { const response = await this.plexClient.query({ - uri: `/library/sections/${id}/all?type=${ - mediaType === 'show' ? '4' : '1' - }&sort=addedAt%3Adesc&addedAt>>=${Math.floor(options.addedAt / 1000)}`, + uri: `/library/sections/${id}/all?type=${mediaType}&sort=addedAt%3Adesc&addedAt>>=${Math.floor( + options.addedAt / 1000 + )}`, extraHeaders: { 'X-Plex-Container-Start': `0`, 'X-Plex-Container-Size': `500`, diff --git a/server/api/plextv.ts b/server/api/plextv.ts index ccb1be66..c79aa953 100644 --- a/server/api/plextv.ts +++ b/server/api/plextv.ts @@ -110,10 +110,14 @@ interface MetadataResponse { MediaContainer: { Metadata: { ratingKey: string; - type: 'movie' | 'show'; + type: 'movie' | 'show' | 'season' | 'episode' | 'artist' | 'album'; title: string; Guid: { - id: `imdb://tt${number}` | `tmdb://${number}` | `tvdb://${number}`; + id: + | `imdb://tt${number}` + | `tmdb://${number}` + | `tvdb://${number}` + | `mbid://${string}`; }[]; }[]; }; @@ -121,9 +125,10 @@ interface MetadataResponse { export interface PlexWatchlistItem { ratingKey: string; - tmdbId: number; + tmdbId?: number; tvdbId?: number; - type: 'movie' | 'show' | 'music'; + musicBrainzId?: string; + type: 'movie' | 'show' | 'season' | 'episode' | 'artist' | 'album'; title: string; } @@ -299,6 +304,9 @@ class PlexTvAPI extends ExternalAPI { const tvdbString = metadata.Guid.find((guid) => guid.id.startsWith('tvdb') ); + const musicBrainzString = metadata.Guid.find((guid) => + guid.id.startsWith('mbid') + ); return { ratingKey: metadata.ratingKey, @@ -308,6 +316,9 @@ class PlexTvAPI extends ExternalAPI { tvdbId: tvdbString ? Number(tvdbString.id.split('//')[1]) : undefined, + musicBrainzId: musicBrainzString + ? musicBrainzString.id.split('//')[1] + : undefined, title: metadata.title, type: metadata.type, }; @@ -315,7 +326,11 @@ class PlexTvAPI extends ExternalAPI { ) ); - const filteredList = watchlistDetails.filter((detail) => detail.tmdbId); + const filteredList = watchlistDetails.filter((detail) => + ['movie', 'show'].includes(detail.type) + ? detail.tmdbId + : detail.musicBrainzId + ); return { offset, diff --git a/server/interfaces/api/discoverInterfaces.ts b/server/interfaces/api/discoverInterfaces.ts index 89cb7426..cfe24f23 100644 --- a/server/interfaces/api/discoverInterfaces.ts +++ b/server/interfaces/api/discoverInterfaces.ts @@ -6,8 +6,9 @@ export interface GenreSliderItem { export interface WatchlistItem { ratingKey: string; - tmdbId: number; - mediaType: 'movie' | 'tv'; + tmdbId?: number; + musicBrainzId?: string; + mediaType: 'movie' | 'tv' | 'music'; title: string; } diff --git a/server/lib/availabilitySync.ts b/server/lib/availabilitySync.ts index 0a16302c..8dbf10be 100644 --- a/server/lib/availabilitySync.ts +++ b/server/lib/availabilitySync.ts @@ -286,16 +286,20 @@ class AvailabilitySync { id: media.id, }) .andWhere( - `(request.is4k = :is4k AND media.${ - is4k ? 'status4k' : 'status' - } IN (:...mediaStatus))`, - { - mediaStatus: [ - MediaStatus.AVAILABLE, - MediaStatus.PARTIALLY_AVAILABLE, - ], - is4k: is4k, - } + ['show', 'movie'].includes(media.mediaType) + ? `(request.is4k = :is4k AND media.${ + is4k ? 'status4k' : 'status' + } IN (:...mediaStatus))` + : '', + ['show', 'movie'].includes(media.mediaType) + ? { + mediaStatus: [ + MediaStatus.AVAILABLE, + MediaStatus.PARTIALLY_AVAILABLE, + ], + is4k: is4k, + } + : {} ) .getMany(); diff --git a/server/lib/scanners/baseScanner.ts b/server/lib/scanners/baseScanner.ts index f0f3db7e..bfd28289 100644 --- a/server/lib/scanners/baseScanner.ts +++ b/server/lib/scanners/baseScanner.ts @@ -24,7 +24,8 @@ export interface RunnableScanner { } export interface MediaIds { - tmdbId: number; + tmdbId?: number; + mbId?: string; imdbId?: string; tvdbId?: number; isHama?: boolean; @@ -79,13 +80,19 @@ class BaseScanner { this.updateRate = updateRate ?? UPDATE_RATE; } - private async getExisting(tmdbId: number, mediaType: MediaType) { + private async getExisting(id: number | string, mediaType: MediaType) { const mediaRepository = getRepository(Media); - const existing = await mediaRepository.findOne({ - where: { tmdbId: tmdbId, mediaType }, - }); - + let existing: Media | null; + if (mediaType === MediaType.MOVIE || mediaType === MediaType.TV) { + existing = await mediaRepository.findOne({ + where: { tmdbId: id as number, mediaType }, + }); + } else { + existing = await mediaRepository.findOne({ + where: { mbId: id as string, mediaType }, + }); + } return existing; } @@ -110,8 +117,8 @@ class BaseScanner { if (existing) { let changedExisting = false; - if (existing[is4k ? 'status4k' : 'status'] !== MediaStatus.AVAILABLE) { - existing[is4k ? 'status4k' : 'status'] = processing + if (existing['status'] !== MediaStatus.AVAILABLE) { + existing['status'] = processing ? MediaStatus.PROCESSING : MediaStatus.AVAILABLE; if (mediaAddedAt) { @@ -125,29 +132,21 @@ class BaseScanner { changedExisting = true; } - if ( - ratingKey && - existing[is4k ? 'ratingKey4k' : 'ratingKey'] !== ratingKey - ) { - existing[is4k ? 'ratingKey4k' : 'ratingKey'] = ratingKey; + if (ratingKey && existing['ratingKey'] !== ratingKey) { + existing['ratingKey'] = ratingKey; changedExisting = true; } - if ( - serviceId !== undefined && - existing[is4k ? 'serviceId4k' : 'serviceId'] !== serviceId - ) { - existing[is4k ? 'serviceId4k' : 'serviceId'] = serviceId; + if (serviceId !== undefined && existing['serviceId'] !== serviceId) { + existing['serviceId'] = serviceId; changedExisting = true; } if ( externalServiceId !== undefined && - existing[is4k ? 'externalServiceId4k' : 'externalServiceId'] !== - externalServiceId + existing['externalServiceId'] !== externalServiceId ) { - existing[is4k ? 'externalServiceId4k' : 'externalServiceId'] = - externalServiceId; + existing['externalServiceId'] = externalServiceId; changedExisting = true; } @@ -384,12 +383,11 @@ class BaseScanner { } if (serviceId !== undefined) { - media[is4k ? 'serviceId4k' : 'serviceId'] = serviceId; + media['serviceId'] = serviceId; } if (externalServiceId !== undefined) { - media[is4k ? 'externalServiceId4k' : 'externalServiceId'] = - externalServiceId; + media['externalServiceId'] = externalServiceId; } if (externalServiceSlug !== undefined) { @@ -505,6 +503,93 @@ class BaseScanner { }); } + protected async processArtist( + mbId: string, + { + mediaAddedAt, + ratingKey, + serviceId, + externalServiceId, + processing = false, + title = 'Unknown Title', + }: ProcessOptions = {} + ): Promise { + const mediaRepository = getRepository(Media); + + await this.asyncLock.dispatch(mbId, async () => { + const existing = await this.getExisting(mbId, MediaType.MUSIC); + + if (existing) { + let changedExisting = false; + + if (existing['status'] !== MediaStatus.AVAILABLE) { + existing['status'] = processing + ? MediaStatus.PROCESSING + : MediaStatus.AVAILABLE; + if (mediaAddedAt) { + existing.mediaAddedAt = mediaAddedAt; + } + changedExisting = true; + } + + if (!changedExisting && !existing.mediaAddedAt && mediaAddedAt) { + existing.mediaAddedAt = mediaAddedAt; + changedExisting = true; + } + + if (ratingKey && existing['ratingKey'] !== ratingKey) { + existing['ratingKey'] = ratingKey; + changedExisting = true; + } + + if (serviceId !== undefined && existing['serviceId'] !== serviceId) { + existing['serviceId'] = serviceId; + changedExisting = true; + } + + if ( + externalServiceId !== undefined && + existing['externalServiceId'] !== externalServiceId + ) { + existing['externalServiceId'] = externalServiceId; + changedExisting = true; + } + + if (changedExisting) { + await mediaRepository.save(existing); + this.log( + `Media for ${title} exists. Changes were detected and the title will be updated.`, + 'info' + ); + } else { + this.log(`Title already exists and no changes detected for ${title}`); + } + } else { + const newMedia = new Media(); + newMedia.mbId = mbId; + + newMedia.status = !processing + ? MediaStatus.AVAILABLE + : processing + ? MediaStatus.PROCESSING + : MediaStatus.UNKNOWN; + newMedia.mediaType = MediaType.MUSIC; + newMedia.serviceId = serviceId; + newMedia.externalServiceId = externalServiceId; + + if (mediaAddedAt) { + newMedia.mediaAddedAt = mediaAddedAt; + } + + if (ratingKey) { + newMedia.ratingKey = ratingKey; + } + await mediaRepository.save(newMedia); + this.log(`Saved new media: ${title}`); + } + }); + } + /** * Call startRun from child class whenever a run is starting to * ensure required values are set diff --git a/server/lib/scanners/plex/index.ts b/server/lib/scanners/plex/index.ts index f074872b..f1465380 100644 --- a/server/lib/scanners/plex/index.ts +++ b/server/lib/scanners/plex/index.ts @@ -19,6 +19,7 @@ import { uniqWith } from 'lodash'; const imdbRegex = new RegExp(/imdb:\/\/(tt[0-9]+)/); const tmdbRegex = new RegExp(/tmdb:\/\/([0-9]+)/); const tvdbRegex = new RegExp(/tvdb:\/\/([0-9]+)/); +const mbRegex = new RegExp(/mbid:\/\/([0-9a-f-]+)/); const tmdbShowRegex = new RegExp(/themoviedb:\/\/([0-9]+)/); const plexRegex = new RegExp(/plex:\/\//); // Hama agent uses ASS naming, see details here: @@ -209,6 +210,8 @@ class PlexScanner plexitem.type === 'season' ) { await this.processPlexShow(plexitem); + } else if (plexitem.type === 'artist') { + await this.processPlexArtist(plexitem); } } catch (e) { this.log('Failed to process Plex media', 'error', { @@ -224,13 +227,18 @@ class PlexScanner const has4k = plexitem.Media.some( (media) => media.videoResolution === '4k' ); - - await this.processMovie(mediaIds.tmdbId, { - is4k: has4k && this.enable4kMovie, - mediaAddedAt: new Date(plexitem.addedAt * 1000), - ratingKey: plexitem.ratingKey, - title: plexitem.title, - }); + if (mediaIds.tmdbId) { + await this.processMovie(mediaIds.tmdbId, { + is4k: has4k && this.enable4kMovie, + mediaAddedAt: new Date(plexitem.addedAt * 1000), + ratingKey: plexitem.ratingKey, + title: plexitem.title, + }); + } else { + this.log('No TMDB ID found for movie', 'warn', { + title: plexitem.title, + }); + } } private async processPlexMovieByTmdbId( @@ -273,7 +281,9 @@ class PlexScanner await this.processHamaSpecials(metadata, mediaIds.tvdbId); } - const tvShow = await this.tmdb.getTvShow({ tvId: mediaIds.tmdbId }); + const tvShow = await this.tmdb.getTvShow({ + tvId: mediaIds.tmdbId as number, + }); const seasons = tvShow.seasons; const processableSeasons: ProcessableSeason[] = []; @@ -322,7 +332,7 @@ class PlexScanner if (mediaIds.tvdbId) { await this.processShow( - mediaIds.tmdbId, + mediaIds.tmdbId as number, mediaIds.tvdbId ?? tvShow.external_ids.tvdb_id, processableSeasons, { @@ -334,6 +344,21 @@ class PlexScanner } } + private async processPlexArtist(plexitem: PlexLibraryItem) { + const mediaIds = await this.getMediaIds(plexitem); + if (mediaIds.mbId) { + await this.processArtist(mediaIds.mbId, { + mediaAddedAt: new Date(plexitem.addedAt * 1000), + ratingKey: plexitem.ratingKey, + title: plexitem.title, + }); + } else { + this.log('No MusicBrainz ID found for artist', 'warn', { + title: plexitem.title, + }); + } + } + private async getMediaIds(plexitem: PlexLibraryItem): Promise { let mediaIds: Partial = {}; // Check if item is using new plex movie/tv agent @@ -372,6 +397,8 @@ class PlexScanner } else if (ref.id.match(tvdbRegex)) { const tvdbMatch = ref.id.match(tvdbRegex)?.[1]; mediaIds.tvdbId = Number(tvdbMatch); + } else if (ref.id.match(mbRegex)) { + mediaIds.mbId = ref.id.match(mbRegex)?.[1] ?? undefined; } }); @@ -487,10 +514,16 @@ class PlexScanner } } } + // Check for MusicBrainz + } else if (plexitem.guid.match(mbRegex)) { + const mbMatch = plexitem.guid.match(mbRegex); + if (mbMatch) { + mediaIds.mbId = mbMatch[1]; + } } - if (!mediaIds.tmdbId) { - throw new Error('Unable to find TMDB ID'); + if (!mediaIds.tmdbId && !mediaIds.mbId) { + throw new Error('Unable to find either a TMDB ID or a MB ID'); } // We check above if we have the TMDB ID, so we can safely assert the type below diff --git a/server/lib/settings.ts b/server/lib/settings.ts index f31bf22b..55734982 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -9,7 +9,7 @@ export interface Library { id: string; name: string; enabled: boolean; - type: 'show' | 'movie'; + type: 'show' | 'movie' | 'artist'; lastScan?: number; } diff --git a/server/lib/watchlistsync.ts b/server/lib/watchlistsync.ts index 2d623610..4b6d1127 100644 --- a/server/lib/watchlistsync.ts +++ b/server/lib/watchlistsync.ts @@ -10,6 +10,11 @@ import { RequestPermissionError, } from '@server/entity/MediaRequest'; import { User } from '@server/entity/User'; +import type { + MusicRequestBody, + TvRequestBody, + VideoRequestBody, +} from '@server/interfaces/api/requestInterfaces'; import logger from '@server/logger'; import { Permission } from './permissions'; @@ -66,7 +71,8 @@ class WatchlistSync { const response = await plexTvApi.getWatchlist({ size: 200 }); const mediaItems = await Media.getRelatedMedia( - response.items.map((i) => i.tmdbId) + response.items.map((i) => i.tmdbId) as number[], + response.items.map((i) => i.musicBrainzId) as string[] ); const unavailableItems = response.items.filter( @@ -114,13 +120,17 @@ class WatchlistSync { await MediaRequest.request( { - mediaId: mediaItem.tmdbId, + mediaId: mediaItem.tmdbId ?? mediaItem.musicBrainzId, mediaType: - mediaItem.type === 'show' ? MediaType.TV : MediaType.MOVIE, + mediaItem.type === 'show' + ? MediaType.TV + : mediaItem.type === 'movie' + ? MediaType.MOVIE + : MediaType.MUSIC, seasons: mediaItem.type === 'show' ? 'all' : undefined, - tvdbId: mediaItem.tvdbId, - is4k: false, - }, + tvdbId: mediaItem.tvdbId ?? undefined, + is4k: ['movie', 'show'].includes(mediaItem.type) ? false : false, + } as MusicRequestBody | TvRequestBody | VideoRequestBody, user, { isAutoRequest: true } ); diff --git a/server/routes/discover.ts b/server/routes/discover.ts index e1ee25a9..0138df13 100644 --- a/server/routes/discover.ts +++ b/server/routes/discover.ts @@ -847,6 +847,7 @@ discoverRoutes.get, WatchlistResponse>( title: item.title, mediaType: item.type === 'show' ? 'tv' : 'movie', tmdbId: item.tmdbId, + musicBrainzId: item.musicBrainzId, })), }); } diff --git a/server/types/nodebrainz.d.ts b/server/types/nodebrainz.d.ts index 3232d5bb..13fa1400 100644 --- a/server/types/nodebrainz.d.ts +++ b/server/types/nodebrainz.d.ts @@ -29,11 +29,31 @@ declare module 'nodebrainz' { } export default class BaseNodeBrainz { constructor(options: { userAgent: string }); - artist(artistId: string, { inc }: { inc: string }): Artist; - recording(recordingId: string, { inc }: { inc: string }): Recording; - release(releaseId: string, { inc }: { inc: string }): Release; - releaseGroup(releaseGroupId: string, { inc }: { inc: string }): Group; - work(workId: string, { inc }: { inc: string }): Work; + artist( + artistId: string, + { inc }: { inc: string }, + callback: (err: Error, data: Artist) => void + ): Promise; + recording( + recordingId: string, + { inc }: { inc: string }, + callback: (err: Error, data: Recording) => void + ): Promise; + release( + releaseId: string, + { inc }: { inc: string }, + callback: (err: Error, data: Release) => void + ): Promise; + releaseGroup( + releaseGroupId: string, + { inc }: { inc: string }, + callback: (err: Error, data: Group) => void + ): Promise; + work( + workId: string, + { inc }: { inc: string }, + callback: (err: Error, data: Work) => void + ): Promise; search( type: string, search: SearchOptions, diff --git a/src/components/Discover/RecentlyAddedSlider/index.tsx b/src/components/Discover/RecentlyAddedSlider/index.tsx index 078f86ba..0255be71 100644 --- a/src/components/Discover/RecentlyAddedSlider/index.tsx +++ b/src/components/Discover/RecentlyAddedSlider/index.tsx @@ -1,12 +1,16 @@ import Slider from '@app/components/Slider'; +import TitleCard from '@app/components/TitleCard'; import TmdbTitleCard from '@app/components/TitleCard/TmdbTitleCard'; import { Permission, useUser } from '@app/hooks/useUser'; import type { MediaResultsResponse } from '@server/interfaces/api/mediaInterfaces'; import { defineMessages, useIntl } from 'react-intl'; import useSWR from 'swr'; +//import MusicBrainz from '@server/api/musicbrainz'; + const messages = defineMessages({ recentlyAdded: 'Recently Added', + recentlyAddedMusic: 'Recently Added Music', }); const RecentlyAddedSlider = () => { @@ -26,6 +30,21 @@ const RecentlyAddedSlider = () => { return null; } + const videoMedias = (media?.results ?? []).filter((item) => ["movie", "tv"].includes(item.mediaType)) + const musicMedias = (media?.results ?? []).filter((item) => !["movie", "tv"].includes(item.mediaType)) + + //const musicBrainz = new MusicBrainz(); + //const artistNames = musicMedias.map(async (item) => {return item.mbId ? (await musicBrainz.getArtist(item.mbId)).name: "Unknown"}); + + const musicItems = musicMedias.map((item) => ( + + )); + return ( <>
@@ -36,16 +55,27 @@ const RecentlyAddedSlider = () => { ( - ( + ))} /> +
+
+ {intl.formatMessage(messages.recentlyAddedMusic)} +
+
+ + ); };