From 4d84fc242b23ccb466d624b88148bf317773ed6d Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Fri, 3 Mar 2023 06:08:42 -0600 Subject: [PATCH] plex scanner improvement (#1193) * fix crash with some plex multi-episode files * comments cleanup --- CHANGELOG.md | 1 + .../Repositories/PlexTelevisionRepository.cs | 8 +- .../Plex/PlexServerApiClient.cs | 97 ++++++++++--------- .../MediaServerMovieLibraryScanner.cs | 53 +++------- .../Core/Plex/PlexMovieLibraryScanner.cs | 2 + 5 files changed, 70 insertions(+), 91 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02106420..c4460319 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Improve VAAPI encoder capability detection on newer hardware - Fix trash page to properly display episodes with missing metadata or titles - Fix playback of content with yuv444p10le pixel format +- Fix case where some multi-episode files from Plex would crash the scanner ### Changed - Upgrade all docker images and windows builds to ffmpeg 6.0 diff --git a/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs index 2eea7bd2..7d661537 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs @@ -486,10 +486,6 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository version.Name = incomingVersion.Name; version.DateAdded = incomingVersion.DateAdded; - await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaVersion SET Name = @Name, DateAdded = @DateAdded WHERE Id = @Id", - new { version.Name, version.DateAdded, version.Id }); - // media file MediaFile file = version.MediaFiles.Head(); MediaFile incomingFile = incomingVersion.MediaFiles.Head(); @@ -502,6 +498,10 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository file.Path = incomingFile.Path; + await dbContext.Connection.ExecuteAsync( + @"UPDATE MediaVersion SET Name = @Name, DateAdded = @DateAdded WHERE Id = @Id", + new { version.Name, version.DateAdded, version.Id }); + await dbContext.Connection.ExecuteAsync( @"UPDATE MediaFile SET Path = @Path WHERE Id = @Id", new { file.Path, file.Id }); diff --git a/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs b/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs index cf0facf6..a2f888a4 100644 --- a/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs +++ b/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs @@ -78,7 +78,7 @@ public class PlexServerApiClient : IPlexServerApiClient { return jsonService .GetLibrarySectionContents(library.Key, skip, pageSize, token.AuthToken) - .Map(r => r.MediaContainer.Metadata.Filter(m => m.Media.Count > 0 && m.Media[0].Part.Count > 0)) + .Map(r => r.MediaContainer.Metadata.Filter(m => m.Media.Count > 0 && m.Media.Any(media => media.Part.Count > 0))) .Map(list => list.Map(metadata => ProjectToMovie(metadata, library.MediaSourceId))); } @@ -179,13 +179,14 @@ public class PlexServerApiClient : IPlexServerApiClient Task> GetItems(IPlexServerApi xmlService, IPlexServerApi _, int skip, int pageSize) { return xmlService.GetSeasonChildren(seasonMetadataKey, skip, pageSize, token.AuthToken) - .Map(r => r.Metadata.Filter(m => m.Media.Count > 0 && m.Media[0].Part.Count > 0)) - .Map(list => list.Bind(metadata => ProjectToEpisodes(metadata, library.MediaSourceId))); + .Map(r => r.Metadata.Filter(m => m.Media.Count > 0 && m.Media.Any(media => media.Part.Count > 0))) + .Map(list => list.Map(metadata => ProjectToEpisode(metadata, library.MediaSourceId))); } return GetPagedLibraryContents(connection, CountItems, GetItems); } + // this shouldn't be called anymore public async Task> GetMovieMetadata( PlexLibrary library, string key, @@ -197,7 +198,7 @@ public class PlexServerApiClient : IPlexServerApiClient IPlexServerApi service = XmlServiceFor(connection.Uri); return await service.GetVideoMetadata(key, token.AuthToken) .Map(Optional) - .Map(r => r.Filter(m => m.Metadata.Media.Count > 0 && m.Metadata.Media[0].Part.Count > 0)) + .Map(r => r.Filter(m => m.Metadata.Media.Count > 0 && m.Metadata.Media.Any(media => media.Part.Count > 0))) .MapT(response => ProjectToMovieMetadata(response.Metadata, library.MediaSourceId)) .Map(o => o.ToEither("Unable to locate metadata")); } @@ -239,7 +240,7 @@ public class PlexServerApiClient : IPlexServerApiClient Option maybeResponse = await service .GetVideoMetadata(key, token.AuthToken) .Map(Optional) - .Map(r => r.Filter(m => m.Metadata.Media.Count > 0 && m.Metadata.Media[0].Part.Count > 0)); + .Map(r => r.Filter(m => m.Metadata.Media.Count > 0 && m.Metadata.Media.Any(media => media.Part.Count > 0))); return maybeResponse.Match( response => { @@ -268,7 +269,7 @@ public class PlexServerApiClient : IPlexServerApiClient Option maybeResponse = await service .GetVideoMetadata(key, token.AuthToken) .Map(Optional) - .Map(r => r.Filter(m => m.Metadata.Media.Count > 0 && m.Metadata.Media[0].Part.Count > 0)); + .Map(r => r.Filter(m => m.Metadata.Media.Count > 0 && m.Metadata.Media.Any(media => media.Part.Count > 0))); return maybeResponse.Match( response => { @@ -401,7 +402,10 @@ public class PlexServerApiClient : IPlexServerApiClient private PlexMovie ProjectToMovie(PlexMetadataResponse response, int mediaSourceId) { - PlexMediaResponse media = response.Media.Head(); + PlexMediaResponse media = response.Media + .Filter(media => media.Part.Any()) + .MaxBy(media => media.Id); + PlexPartResponse part = media.Part.Head(); DateTime dateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime; DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime; @@ -541,7 +545,10 @@ public class PlexServerApiClient : IPlexServerApiClient private Option ProjectToMediaVersion(PlexXmlMetadataResponse response) { - PlexMediaResponse media = response.Media.Head(); + PlexMediaResponse media = response.Media + .Filter(media => media.Part.Any()) + .MaxBy(media => media.Id); + List streams = media.Part.Head().Stream; DateTime dateUpdated = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime; Option maybeVideoStream = streams.Find(s => s.StreamType == 1); @@ -814,52 +821,48 @@ public class PlexServerApiClient : IPlexServerApiClient return season; } - private IEnumerable ProjectToEpisodes(PlexXmlMetadataResponse response, int mediaSourceId) + private PlexEpisode ProjectToEpisode(PlexXmlMetadataResponse response, int mediaSourceId) { - var result = new List(); + PlexMediaResponse media = response.Media + .Filter(media => media.Part.Any()) + .MaxBy(media => media.Id); + + PlexXmlPartResponse part = media.Part.Head(); + DateTime dateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime; + DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime; - // TODO: actually use all media records - foreach (PlexMediaResponse media in response.Media.HeadOrNone()) + EpisodeMetadata metadata = ProjectToEpisodeMetadata(response, mediaSourceId); + var version = new MediaVersion { - PlexXmlPartResponse part = media.Part.Head(); - DateTime dateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime; - DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime; - - EpisodeMetadata metadata = ProjectToEpisodeMetadata(response, mediaSourceId); - var version = new MediaVersion + Name = "Main", + Duration = TimeSpan.FromMilliseconds(media.Duration), + Width = media.Width, + Height = media.Height, + DateAdded = dateAdded, + DateUpdated = lastWriteTime, + MediaFiles = new List { - Name = "Main", - Duration = TimeSpan.FromMilliseconds(media.Duration), - Width = media.Width, - Height = media.Height, - DateAdded = dateAdded, - DateUpdated = lastWriteTime, - MediaFiles = new List + new PlexMediaFile { - new PlexMediaFile - { - PlexId = part.Id, - Key = part.Key, - Path = part.File - } - }, - // specifically omit stream details - Streams = new List() - }; + PlexId = part.Id, + Key = part.Key, + Path = part.File + } + }, + // specifically omit stream details + Streams = new List() + }; - var episode = new PlexEpisode - { - Key = response.Key, - Etag = _plexEtag.ForEpisode(response), - EpisodeMetadata = new List { metadata }, - MediaVersions = new List { version }, - TraktListItems = new List() - }; - - result.Add(episode); - } + var episode = new PlexEpisode + { + Key = response.Key, + Etag = _plexEtag.ForEpisode(response), + EpisodeMetadata = new List { metadata }, + MediaVersions = new List { version }, + TraktListItems = new List() + }; - return result; + return episode; } private EpisodeMetadata ProjectToEpisodeMetadata(PlexMetadataResponse response, int mediaSourceId) diff --git a/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs b/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs index b08efbc2..b99d8b81 100644 --- a/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs @@ -449,49 +449,22 @@ public abstract class MediaServerMovieLibraryScanner refreshResult = - // await _localStatisticsProvider.RefreshStatistics( - // ffmpegPath, - // ffprobePath, - // existing, - // result.LocalPath); - // - // foreach (BaseError error in refreshResult.LeftToSeq()) - // { - // _logger.LogWarning( - // "Unable to refresh {Attribute} for media item {Path}. Error: {Error}", - // "Statistics", - // result.LocalPath, - // error.Value); - // } - // - // foreach (bool _ in refreshResult.RightToSeq()) - // { - // result.IsUpdated = true; - // } - // } - // else - // { - if (maybeMediaVersion.IsNone) - { - maybeMediaVersion = await GetMediaServerStatistics( - connectionParameters, - library, - result, - incoming); - } + if (maybeMediaVersion.IsNone) + { + maybeMediaVersion = await GetMediaServerStatistics( + connectionParameters, + library, + result, + incoming); + } - foreach (MediaVersion mediaVersion in maybeMediaVersion) + foreach (MediaVersion mediaVersion in maybeMediaVersion) + { + if (await _metadataRepository.UpdateStatistics(result.Item, mediaVersion)) { - if (await _metadataRepository.UpdateStatistics(result.Item, mediaVersion)) - { - result.IsUpdated = true; - } + result.IsUpdated = true; } - // } + } } return result; diff --git a/ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs b/ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs index b73719d7..453c5374 100644 --- a/ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs @@ -107,6 +107,7 @@ public class PlexMovieLibraryScanner : connectionParameters.Connection, connectionParameters.Token); + // this shouldn't be called anymore protected override async Task> GetFullMetadata( PlexConnectionParameters connectionParameters, PlexLibrary library, @@ -133,6 +134,7 @@ public class PlexMovieLibraryScanner : return None; } + // this shouldn't be called anymore protected override async Task> GetMediaServerStatistics( PlexConnectionParameters connectionParameters, PlexLibrary library,