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:
@@ -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")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user