From afa62e54e58b9c4a31bf1bc6ccc1e95a420a5b53 Mon Sep 17 00:00:00 2001 From: sct Date: Mon, 11 Jan 2021 07:46:55 +0000 Subject: [PATCH] feat(plex-sync): process 4k movies during sync --- server/api/plexapi.ts | 17 +++++ server/index.ts | 10 +++ server/job/plexsync/index.ts | 132 +++++++++++++++++++++++++++++------ 3 files changed, 139 insertions(+), 20 deletions(-) diff --git a/server/api/plexapi.ts b/server/api/plexapi.ts index 3dc9bb9d78..d460fe77b6 100644 --- a/server/api/plexapi.ts +++ b/server/api/plexapi.ts @@ -48,6 +48,23 @@ export interface PlexMetadata { parentIndex?: number; leafCount: number; viewedLeafCount: number; + Media: Media[]; +} + +interface Media { + id: number; + duration: number; + bitrate: number; + width: number; + height: number; + aspectRatio: number; + audioChannels: number; + audioCodec: string; + videoCodec: string; + videoResolution: string; + container: string; + videoFrameRate: string; + videoProfile: string; } interface PlexMetadataResponse { diff --git a/server/index.ts b/server/index.ts index 32b144474e..09fdf515c9 100644 --- a/server/index.ts +++ b/server/index.ts @@ -21,6 +21,7 @@ import TelegramAgent from './lib/notifications/agents/telegram'; import { getAppVersion } from './utils/appVersion'; import SlackAgent from './lib/notifications/agents/slack'; import PushoverAgent from './lib/notifications/agents/pushover'; +import PlexAPI from './api/plexapi'; const API_SPEC_PATH = path.join(__dirname, '../overseerr-api.yml'); @@ -98,6 +99,15 @@ app }; next(); }); + + server.use('/test-plex', async (req, res) => { + const plexapi = new PlexAPI({ plexToken: 'KUHExD9ggc9qhouARzJV' }); + + const response = await plexapi.getMetadata('36939'); + + res.status(200).json(response); + }); + server.use('/api/v1', routes); server.get('*', (req, res) => handle(req, res)); server.use( diff --git a/server/job/plexsync/index.ts b/server/job/plexsync/index.ts index 5fd13c212f..30fff686a4 100644 --- a/server/job/plexsync/index.ts +++ b/server/job/plexsync/index.ts @@ -45,6 +45,8 @@ class JobPlexSync { private currentLibrary: Library; private running = false; private isRecentOnly = false; + private enable4kMovie = false; + private enable4kShow = false; private asyncLock = new AsyncLock(); constructor({ isRecentOnly }: { isRecentOnly?: boolean } = {}) { @@ -86,23 +88,59 @@ class JobPlexSync { } }); + const has4k = metadata.Media.some( + (media) => media.videoResolution === '4k' + ); + const hasOtherResolution = metadata.Media.some( + (media) => media.videoResolution !== '4k' + ); + await this.asyncLock.dispatch(newMedia.tmdbId, async () => { const existing = await this.getExisting( newMedia.tmdbId, MediaType.MOVIE ); - if (existing && existing.status === MediaStatus.AVAILABLE) { - this.log(`Title exists and is already available ${metadata.title}`); - } else if (existing && existing.status !== MediaStatus.AVAILABLE) { - existing.status = MediaStatus.AVAILABLE; - mediaRepository.save(existing); - this.log( - `Request for ${metadata.title} exists. Setting status AVAILABLE`, - 'info' - ); + if (existing) { + let changedExisting = false; + + if ( + (hasOtherResolution || (!this.enable4kMovie && has4k)) && + existing.status !== MediaStatus.AVAILABLE + ) { + existing.status = MediaStatus.AVAILABLE; + changedExisting = true; + } + + if ( + has4k && + this.enable4kMovie && + existing.status4k !== MediaStatus.AVAILABLE + ) { + existing.status4k = MediaStatus.AVAILABLE; + changedExisting = true; + } + + if (changedExisting) { + await mediaRepository.save(existing); + this.log( + `Request for ${metadata.title} exists. New media types set to AVAILABLE`, + 'info' + ); + } else { + this.log( + `Title already exists and no new media types found ${metadata.title}` + ); + } } else { - newMedia.status = MediaStatus.AVAILABLE; + newMedia.status = + hasOtherResolution || (!this.enable4kMovie && has4k) + ? MediaStatus.AVAILABLE + : MediaStatus.UNKNOWN; + newMedia.status4k = + has4k && this.enable4kMovie + ? MediaStatus.AVAILABLE + : MediaStatus.UNKNOWN; newMedia.mediaType = MediaType.MOVIE; await mediaRepository.save(newMedia); this.log(`Saved ${plexitem.title}`); @@ -150,16 +188,47 @@ class JobPlexSync { const mediaRepository = getRepository(Media); await this.asyncLock.dispatch(tmdbMovieId, async () => { + const metadata = await this.plexClient.getMetadata(plexitem.ratingKey); const existing = await this.getExisting(tmdbMovieId, MediaType.MOVIE); - if (existing && existing.status === MediaStatus.AVAILABLE) { - this.log(`Title exists and is already available ${plexitem.title}`); - } else if (existing && existing.status !== MediaStatus.AVAILABLE) { - existing.status = MediaStatus.AVAILABLE; - await mediaRepository.save(existing); - this.log( - `Request for ${plexitem.title} exists. Setting status AVAILABLE`, - 'info' - ); + + const has4k = metadata.Media.some( + (media) => media.videoResolution === '4k' + ); + const hasOtherResolution = metadata.Media.some( + (media) => media.videoResolution !== '4k' + ); + + if (existing) { + let changedExisting = false; + + if ( + (hasOtherResolution || (!this.enable4kMovie && has4k)) && + existing.status !== MediaStatus.AVAILABLE + ) { + existing.status = MediaStatus.AVAILABLE; + changedExisting = true; + } + + if ( + has4k && + this.enable4kMovie && + existing.status4k !== MediaStatus.AVAILABLE + ) { + existing.status4k = MediaStatus.AVAILABLE; + changedExisting = true; + } + + if (changedExisting) { + await mediaRepository.save(existing); + this.log( + `Request for ${metadata.title} exists. New media types set to AVAILABLE`, + 'info' + ); + } else { + this.log( + `Title already exists and no new media types found ${metadata.title}` + ); + } } else { // If we have a tmdb movie guid but it didn't already exist, only then // do we request the movie from tmdb (to reduce api requests) @@ -169,7 +238,14 @@ class JobPlexSync { const newMedia = new Media(); newMedia.imdbId = tmdbMovie.external_ids.imdb_id; newMedia.tmdbId = tmdbMovie.id; - newMedia.status = MediaStatus.AVAILABLE; + newMedia.status = + hasOtherResolution || (!this.enable4kMovie && has4k) + ? MediaStatus.AVAILABLE + : MediaStatus.UNKNOWN; + newMedia.status4k = + has4k && this.enable4kMovie + ? MediaStatus.AVAILABLE + : MediaStatus.UNKNOWN; newMedia.mediaType = MediaType.MOVIE; await mediaRepository.save(newMedia); this.log(`Saved ${tmdbMovie.title}`); @@ -508,6 +584,22 @@ class JobPlexSync { (library) => library.enabled ); + this.enable4kMovie = settings.radarr.some((radarr) => radarr.is4k); + if (this.enable4kMovie) { + this.log( + 'At least one 4K Radarr server was detected, so 4K movie detection is now enabled', + 'info' + ); + } + + this.enable4kShow = settings.sonarr.some((sonarr) => sonarr.is4k); + if (this.enable4kShow) { + this.log( + 'At least one 4K Sonarr server was detected, so 4K series detection is now enabled', + 'info' + ); + } + const hasHama = await this.hasHamaAgent(); if (hasHama) { await animeList.sync();