Browse Source

plex scanner improvement (#1193)

* fix crash with some plex multi-episode files

* comments cleanup
pull/1195/head
Jason Dove 2 years ago committed by GitHub
parent
commit
4d84fc242b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 8
      ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs
  3. 97
      ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs
  4. 53
      ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs
  5. 2
      ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs

1
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 - Improve VAAPI encoder capability detection on newer hardware
- Fix trash page to properly display episodes with missing metadata or titles - Fix trash page to properly display episodes with missing metadata or titles
- Fix playback of content with yuv444p10le pixel format - Fix playback of content with yuv444p10le pixel format
- Fix case where some multi-episode files from Plex would crash the scanner
### Changed ### Changed
- Upgrade all docker images and windows builds to ffmpeg 6.0 - Upgrade all docker images and windows builds to ffmpeg 6.0

8
ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs

@ -486,10 +486,6 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository
version.Name = incomingVersion.Name; version.Name = incomingVersion.Name;
version.DateAdded = incomingVersion.DateAdded; 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 // media file
MediaFile file = version.MediaFiles.Head(); MediaFile file = version.MediaFiles.Head();
MediaFile incomingFile = incomingVersion.MediaFiles.Head(); MediaFile incomingFile = incomingVersion.MediaFiles.Head();
@ -502,6 +498,10 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository
file.Path = incomingFile.Path; 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( await dbContext.Connection.ExecuteAsync(
@"UPDATE MediaFile SET Path = @Path WHERE Id = @Id", @"UPDATE MediaFile SET Path = @Path WHERE Id = @Id",
new { file.Path, file.Id }); new { file.Path, file.Id });

97
ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs

@ -78,7 +78,7 @@ public class PlexServerApiClient : IPlexServerApiClient
{ {
return jsonService return jsonService
.GetLibrarySectionContents(library.Key, skip, pageSize, token.AuthToken) .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))); .Map(list => list.Map(metadata => ProjectToMovie(metadata, library.MediaSourceId)));
} }
@ -179,13 +179,14 @@ public class PlexServerApiClient : IPlexServerApiClient
Task<IEnumerable<PlexEpisode>> GetItems(IPlexServerApi xmlService, IPlexServerApi _, int skip, int pageSize) Task<IEnumerable<PlexEpisode>> GetItems(IPlexServerApi xmlService, IPlexServerApi _, int skip, int pageSize)
{ {
return xmlService.GetSeasonChildren(seasonMetadataKey, skip, pageSize, token.AuthToken) 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(r => r.Metadata.Filter(m => m.Media.Count > 0 && m.Media.Any(media => media.Part.Count > 0)))
.Map(list => list.Bind(metadata => ProjectToEpisodes(metadata, library.MediaSourceId))); .Map(list => list.Map(metadata => ProjectToEpisode(metadata, library.MediaSourceId)));
} }
return GetPagedLibraryContents(connection, CountItems, GetItems); return GetPagedLibraryContents(connection, CountItems, GetItems);
} }
// this shouldn't be called anymore
public async Task<Either<BaseError, MovieMetadata>> GetMovieMetadata( public async Task<Either<BaseError, MovieMetadata>> GetMovieMetadata(
PlexLibrary library, PlexLibrary library,
string key, string key,
@ -197,7 +198,7 @@ public class PlexServerApiClient : IPlexServerApiClient
IPlexServerApi service = XmlServiceFor(connection.Uri); IPlexServerApi service = XmlServiceFor(connection.Uri);
return await service.GetVideoMetadata(key, token.AuthToken) return await service.GetVideoMetadata(key, token.AuthToken)
.Map(Optional) .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)) .MapT(response => ProjectToMovieMetadata(response.Metadata, library.MediaSourceId))
.Map(o => o.ToEither<BaseError>("Unable to locate metadata")); .Map(o => o.ToEither<BaseError>("Unable to locate metadata"));
} }
@ -239,7 +240,7 @@ public class PlexServerApiClient : IPlexServerApiClient
Option<PlexXmlVideoMetadataResponseContainer> maybeResponse = await service Option<PlexXmlVideoMetadataResponseContainer> maybeResponse = await service
.GetVideoMetadata(key, token.AuthToken) .GetVideoMetadata(key, token.AuthToken)
.Map(Optional) .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( return maybeResponse.Match(
response => response =>
{ {
@ -268,7 +269,7 @@ public class PlexServerApiClient : IPlexServerApiClient
Option<PlexXmlVideoMetadataResponseContainer> maybeResponse = await service Option<PlexXmlVideoMetadataResponseContainer> maybeResponse = await service
.GetVideoMetadata(key, token.AuthToken) .GetVideoMetadata(key, token.AuthToken)
.Map(Optional) .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( return maybeResponse.Match(
response => response =>
{ {
@ -401,7 +402,10 @@ public class PlexServerApiClient : IPlexServerApiClient
private PlexMovie ProjectToMovie(PlexMetadataResponse response, int mediaSourceId) private PlexMovie ProjectToMovie(PlexMetadataResponse response, int mediaSourceId)
{ {
PlexMediaResponse<PlexPartResponse> media = response.Media.Head(); PlexMediaResponse<PlexPartResponse> media = response.Media
.Filter(media => media.Part.Any())
.MaxBy(media => media.Id);
PlexPartResponse part = media.Part.Head(); PlexPartResponse part = media.Part.Head();
DateTime dateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime; DateTime dateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime;
DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime; DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime;
@ -541,7 +545,10 @@ public class PlexServerApiClient : IPlexServerApiClient
private Option<MediaVersion> ProjectToMediaVersion(PlexXmlMetadataResponse response) private Option<MediaVersion> ProjectToMediaVersion(PlexXmlMetadataResponse response)
{ {
PlexMediaResponse<PlexXmlPartResponse> media = response.Media.Head(); PlexMediaResponse<PlexXmlPartResponse> media = response.Media
.Filter(media => media.Part.Any())
.MaxBy(media => media.Id);
List<PlexStreamResponse> streams = media.Part.Head().Stream; List<PlexStreamResponse> streams = media.Part.Head().Stream;
DateTime dateUpdated = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime; DateTime dateUpdated = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime;
Option<PlexStreamResponse> maybeVideoStream = streams.Find(s => s.StreamType == 1); Option<PlexStreamResponse> maybeVideoStream = streams.Find(s => s.StreamType == 1);
@ -814,52 +821,48 @@ public class PlexServerApiClient : IPlexServerApiClient
return season; return season;
} }
private IEnumerable<PlexEpisode> ProjectToEpisodes(PlexXmlMetadataResponse response, int mediaSourceId) private PlexEpisode ProjectToEpisode(PlexXmlMetadataResponse response, int mediaSourceId)
{ {
var result = new List<PlexEpisode>(); PlexMediaResponse<PlexXmlPartResponse> 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 EpisodeMetadata metadata = ProjectToEpisodeMetadata(response, mediaSourceId);
foreach (PlexMediaResponse<PlexXmlPartResponse> media in response.Media.HeadOrNone()) var version = new MediaVersion
{ {
PlexXmlPartResponse part = media.Part.Head(); Name = "Main",
DateTime dateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime; Duration = TimeSpan.FromMilliseconds(media.Duration),
DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime; Width = media.Width,
Height = media.Height,
EpisodeMetadata metadata = ProjectToEpisodeMetadata(response, mediaSourceId); DateAdded = dateAdded,
var version = new MediaVersion DateUpdated = lastWriteTime,
MediaFiles = new List<MediaFile>
{ {
Name = "Main", new PlexMediaFile
Duration = TimeSpan.FromMilliseconds(media.Duration),
Width = media.Width,
Height = media.Height,
DateAdded = dateAdded,
DateUpdated = lastWriteTime,
MediaFiles = new List<MediaFile>
{ {
new PlexMediaFile PlexId = part.Id,
{ Key = part.Key,
PlexId = part.Id, Path = part.File
Key = part.Key, }
Path = part.File },
} // specifically omit stream details
}, Streams = new List<MediaStream>()
// specifically omit stream details };
Streams = new List<MediaStream>()
};
var episode = new PlexEpisode var episode = new PlexEpisode
{ {
Key = response.Key, Key = response.Key,
Etag = _plexEtag.ForEpisode(response), Etag = _plexEtag.ForEpisode(response),
EpisodeMetadata = new List<EpisodeMetadata> { metadata }, EpisodeMetadata = new List<EpisodeMetadata> { metadata },
MediaVersions = new List<MediaVersion> { version }, MediaVersions = new List<MediaVersion> { version },
TraktListItems = new List<TraktListItem>() TraktListItems = new List<TraktListItem>()
}; };
result.Add(episode);
}
return result; return episode;
} }
private EpisodeMetadata ProjectToEpisodeMetadata(PlexMetadataResponse response, int mediaSourceId) private EpisodeMetadata ProjectToEpisodeMetadata(PlexMetadataResponse response, int mediaSourceId)

53
ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs

@ -449,49 +449,22 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
if (deepScan || result.IsAdded || MediaServerEtag(existing) != MediaServerEtag(incoming) || if (deepScan || result.IsAdded || MediaServerEtag(existing) != MediaServerEtag(incoming) ||
existing.MediaVersions.Head().Streams.Count == 0) existing.MediaVersions.Head().Streams.Count == 0)
{ {
// if (maybeMediaVersion.IsNone && _localFileSystem.FileExists(result.LocalPath)) if (maybeMediaVersion.IsNone)
// { {
// _logger.LogDebug("Refreshing {Attribute} for {Path}", "Statistics", result.LocalPath); maybeMediaVersion = await GetMediaServerStatistics(
// Either<BaseError, bool> refreshResult = connectionParameters,
// await _localStatisticsProvider.RefreshStatistics( library,
// ffmpegPath, result,
// ffprobePath, incoming);
// 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);
}
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; return result;

2
ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs

@ -107,6 +107,7 @@ public class PlexMovieLibraryScanner :
connectionParameters.Connection, connectionParameters.Connection,
connectionParameters.Token); connectionParameters.Token);
// this shouldn't be called anymore
protected override async Task<Option<MovieMetadata>> GetFullMetadata( protected override async Task<Option<MovieMetadata>> GetFullMetadata(
PlexConnectionParameters connectionParameters, PlexConnectionParameters connectionParameters,
PlexLibrary library, PlexLibrary library,
@ -133,6 +134,7 @@ public class PlexMovieLibraryScanner :
return None; return None;
} }
// this shouldn't be called anymore
protected override async Task<Option<MediaVersion>> GetMediaServerStatistics( protected override async Task<Option<MediaVersion>> GetMediaServerStatistics(
PlexConnectionParameters connectionParameters, PlexConnectionParameters connectionParameters,
PlexLibrary library, PlexLibrary library,

Loading…
Cancel
Save