enable plex for movies (#72)

* re-enable plex, temp force secure connections

* add plex fanart

* synchronize genre from plex

* fix plex library sync

* improve stream error handling

* synchronize plex artwork

* use switch instead of button

* prioritize local connections for insecure plex sources

* sign out of plex

* better plex sign in/out

* code cleanup

* fix plex movie aspect ratio and scan type
This commit is contained in:
Jason Dove
2021-03-13 21:04:54 +00:00
committed by GitHub
parent a13f964200
commit aa938baec8
35 changed files with 602 additions and 132 deletions
@@ -0,0 +1,9 @@
namespace ErsatzTV.Core.Errors
{
public class PlayoutItemDoesNotExistOnDisk : BaseError
{
public PlayoutItemDoesNotExistOnDisk(string path) : base($"Playout item does not exist on disk\n{path}")
{
}
}
}
@@ -0,0 +1,9 @@
namespace ErsatzTV.Core.Errors
{
public class UnableToLocatePlayoutItem : BaseError
{
public UnableToLocatePlayoutItem() : base("Unable to locate playout item")
{
}
}
}
+2 -2
View File
@@ -84,7 +84,7 @@ namespace ErsatzTV.Core.FFmpeg
.Build();
}
public Process ForOfflineImage(string ffmpegPath, Channel channel, Option<TimeSpan> duration)
public Process ForError(string ffmpegPath, Channel channel, Option<TimeSpan> duration, string errorMessage)
{
FFmpegPlaybackSettings playbackSettings =
_playbackSettingsCalculator.CalculateErrorSettings(channel.FFmpegProfile);
@@ -99,7 +99,7 @@ namespace ErsatzTV.Core.FFmpeg
.WithLoopedImage("Resources/background.png")
.WithLibavfilter()
.WithInput("anullsrc")
.WithErrorText(desiredResolution, "Channel is Offline")
.WithErrorText(desiredResolution, errorMessage)
.WithPixfmt("yuv420p")
.WithPlaybackArgs(playbackSettings)
.WithMetadata(channel)
@@ -5,8 +5,12 @@ namespace ErsatzTV.Core.Interfaces.Locking
public interface IEntityLocker
{
event EventHandler OnLibraryChanged;
event EventHandler OnPlexChanged;
bool LockLibrary(int libraryId);
bool UnlockLibrary(int libraryId);
bool IsLibraryLocked(int libraryId);
bool LockPlex();
bool UnlockPlex();
bool IsPlexLocked();
}
}
@@ -10,8 +10,8 @@ namespace ErsatzTV.Core.Interfaces.Plex
Task<string> GetClientIdentifier();
Task<List<PlexUserAuthToken>> GetUserAuthTokens();
Task<Unit> UpsertUserAuthToken(PlexUserAuthToken userAuthToken);
Task<List<PlexServerAuthToken>> GetServerAuthTokens();
Task<Option<PlexServerAuthToken>> GetServerAuthToken(string clientIdentifier);
Task<Unit> UpsertServerAuthToken(PlexServerAuthToken serverAuthToken);
Task<Unit> DeleteAll();
}
}
@@ -16,5 +16,10 @@ namespace ErsatzTV.Core.Interfaces.Plex
PlexLibrary library,
PlexConnection connection,
PlexServerAuthToken token);
Task<Either<BaseError, MediaVersion>> GetStatistics(
PlexMovie movie,
PlexConnection connection,
PlexServerAuthToken token);
}
}
@@ -22,7 +22,8 @@ namespace ErsatzTV.Core.Interfaces.Repositories
Task Update(LocalMediaSource localMediaSource);
Task Update(PlexMediaSource plexMediaSource);
Task Update(PlexLibrary plexMediaSourceLibrary);
Task Delete(int id);
Task Delete(int mediaSourceId);
Task<Unit> DeleteAllPlex();
Task DisablePlexLibrarySync(List<int> libraryIds);
Task EnablePlexLibrarySync(IEnumerable<int> libraryIds);
}
+114 -13
View File
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Plex;
@@ -38,19 +39,26 @@ namespace ErsatzTV.Core.Plex
await entries.Match(
async movieEntries =>
{
foreach (PlexMovie entry in movieEntries)
foreach (PlexMovie incoming in movieEntries)
{
// TODO: optimize dbcontext use here, do we need tracking? can we make partial updates with dapper?
// TODO: figure out how to rebuild playlists
Either<BaseError, PlexMovie> maybeMovie = await _movieRepository
.GetOrAdd(plexMediaSourceLibrary, entry)
.BindT(UpdateIfNeeded);
.GetOrAdd(plexMediaSourceLibrary, incoming)
.BindT(existing => UpdateStatistics(existing, incoming, connection, token))
.BindT(existing => UpdateMetadata(existing, incoming))
.BindT(existing => UpdateArtwork(existing, incoming));
maybeMovie.IfLeft(
error => _logger.LogWarning(
"Error processing plex movie at {Key}: {Error}",
entry.Key,
error.Value));
await maybeMovie.Match(
async movie => await _movieRepository.Update(movie),
error =>
{
_logger.LogWarning(
"Error processing plex movie at {Key}: {Error}",
incoming.Key,
error.Value);
return Task.CompletedTask;
});
}
},
error =>
@@ -68,10 +76,103 @@ namespace ErsatzTV.Core.Plex
return Unit.Default;
}
private Task<Either<BaseError, PlexMovie>> UpdateIfNeeded(PlexMovie plexMovie) =>
// .BindT(movie => UpdateStatistics(movie, ffprobePath).MapT(_ => movie))
// .BindT(UpdateMetadata)
// .BindT(UpdatePoster);
Right<BaseError, PlexMovie>(plexMovie).AsTask();
private async Task<Either<BaseError, PlexMovie>> UpdateStatistics(
PlexMovie existing,
PlexMovie incoming,
PlexConnection connection,
PlexServerAuthToken token)
{
MediaVersion existingVersion = existing.MediaVersions.Head();
MediaVersion incomingVersion = incoming.MediaVersions.Head();
if (incomingVersion.DateUpdated > existingVersion.DateUpdated ||
string.IsNullOrWhiteSpace(existingVersion.SampleAspectRatio))
{
Either<BaseError, MediaVersion> maybeStatistics =
await _plexServerApiClient.GetStatistics(incoming, connection, token);
maybeStatistics.IfRight(
mediaVersion =>
{
existingVersion.SampleAspectRatio = mediaVersion.SampleAspectRatio ?? "1:1";
existingVersion.VideoScanKind = mediaVersion.VideoScanKind;
existingVersion.DateUpdated = incomingVersion.DateUpdated;
});
}
return Right<BaseError, PlexMovie>(existing);
}
private Task<Either<BaseError, PlexMovie>> UpdateMetadata(PlexMovie existing, PlexMovie incoming)
{
MovieMetadata existingMetadata = existing.MovieMetadata.Head();
MovieMetadata incomingMetadata = incoming.MovieMetadata.Head();
if (incomingMetadata.DateUpdated > existingMetadata.DateUpdated)
{
foreach (Genre genre in existingMetadata.Genres
.Filter(g => incomingMetadata.Genres.All(g2 => g2.Name != g.Name))
.ToList())
{
existingMetadata.Genres.Remove(genre);
}
foreach (Genre genre in incomingMetadata.Genres
.Filter(g => existingMetadata.Genres.All(g2 => g2.Name != g.Name))
.ToList())
{
existingMetadata.Genres.Add(genre);
}
}
return Right<BaseError, PlexMovie>(existing).AsTask();
}
private Task<Either<BaseError, PlexMovie>> UpdateArtwork(PlexMovie existing, PlexMovie incoming)
{
MovieMetadata existingMetadata = existing.MovieMetadata.Head();
MovieMetadata incomingMetadata = incoming.MovieMetadata.Head();
if (incomingMetadata.DateUpdated > existingMetadata.DateUpdated)
{
UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.Poster);
UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.FanArt);
}
return Right<BaseError, PlexMovie>(existing).AsTask();
}
private void UpdateArtworkIfNeeded(
MovieMetadata existingMetadata,
MovieMetadata incomingMetadata,
ArtworkKind artworkKind)
{
Option<Artwork> maybeIncomingArtwork = Optional(incomingMetadata.Artwork).Flatten()
.Find(a => a.ArtworkKind == artworkKind);
maybeIncomingArtwork.Match(
incomingArtwork =>
{
Option<Artwork> maybeExistingArtwork = Optional(existingMetadata.Artwork).Flatten()
.Find(a => a.ArtworkKind == artworkKind);
maybeExistingArtwork.Match(
existingArtwork =>
{
existingArtwork.Path = incomingArtwork.Path;
existingArtwork.DateUpdated = incomingArtwork.DateUpdated;
},
() =>
{
existingMetadata.Artwork ??= new List<Artwork>();
existingMetadata.Artwork.Add(incomingArtwork);
});
},
() =>
{
existingMetadata.Artwork ??= new List<Artwork>();
existingMetadata.Artwork.RemoveAll(a => a.ArtworkKind == artworkKind);
});
}
}
}