Browse Source

proxy plex movie thumbnails (posters)

pull/34/head
Jason Dove 6 years ago
parent
commit
8a3e13f4ce
  1. 4
      ErsatzTV.Application/MediaCards/Queries/GetMovieCardsHandler.cs
  2. 9
      ErsatzTV.Application/Plex/Queries/GetPlexImage.cs
  3. 107
      ErsatzTV.Application/Plex/Queries/GetPlexImageHandler.cs
  4. 22
      ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs
  5. 11
      ErsatzTV/Controllers/ArtworkController.cs

4
ErsatzTV.Application/MediaCards/Queries/GetMovieCardsHandler.cs

@ -16,9 +16,7 @@ namespace ErsatzTV.Application.MediaCards.Queries @@ -16,9 +16,7 @@ namespace ErsatzTV.Application.MediaCards.Queries
public GetMovieCardsHandler(IMovieRepository movieRepository) => _movieRepository = movieRepository;
public async Task<MovieCardResultsViewModel> Handle(
GetMovieCards request,
CancellationToken cancellationToken)
public async Task<MovieCardResultsViewModel> Handle(GetMovieCards request, CancellationToken cancellationToken)
{
int count = await _movieRepository.GetMovieCount();

9
ErsatzTV.Application/Plex/Queries/GetPlexImage.cs

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
using ErsatzTV.Application.Images;
using ErsatzTV.Core;
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.Plex.Queries
{
public record GetPlexImage(int PlexMediaSourceId, string Path) : IRequest<Either<BaseError, ImageViewModel>>;
}

107
ErsatzTV.Application/Plex/Queries/GetPlexImageHandler.cs

@ -0,0 +1,107 @@ @@ -0,0 +1,107 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Application.Images;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Plex;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Plex;
using LanguageExt;
using MediatR;
using Microsoft.Extensions.Caching.Memory;
using static LanguageExt.Prelude;
namespace ErsatzTV.Application.Plex.Queries
{
public class GetPlexImageHandler : IRequestHandler<GetPlexImage, Either<BaseError, ImageViewModel>>
{
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IMemoryCache _memoryCache;
private readonly IPlexSecretStore _plexSecretStore;
public GetPlexImageHandler(
IMemoryCache memoryCache,
IMediaSourceRepository mediaSourceRepository,
IPlexSecretStore plexSecretStore)
{
_memoryCache = memoryCache;
_mediaSourceRepository = mediaSourceRepository;
_plexSecretStore = plexSecretStore;
}
public async Task<Either<BaseError, ImageViewModel>>
Handle(GetPlexImage request, CancellationToken cancellationToken)
{
if (!_memoryCache.TryGetValue(request, out ConnectionParameters parameters))
{
Either<BaseError, ConnectionParameters> maybeParameters =
await Validate(request).Map(v => v.ToEither<ConnectionParameters>());
return await maybeParameters.Match(
p =>
{
_memoryCache.Set(request, p, TimeSpan.FromHours(1));
return RequestImageFromPlex(request, p);
},
error => Left<BaseError, ImageViewModel>(error).AsTask());
}
return await RequestImageFromPlex(request, parameters);
}
private async Task<Either<BaseError, ImageViewModel>> RequestImageFromPlex(
GetPlexImage request,
ConnectionParameters connectionParameters)
{
var url = $"{connectionParameters.ActiveConnection.Uri}/{request.Path}";
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Plex-Token", connectionParameters.PlexServerAuthToken.AuthToken);
HttpResponseMessage response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
return new ImageViewModel(
await response.Content.ReadAsByteArrayAsync(),
response.Content.Headers.ContentType?.MediaType ?? "image/jpeg");
}
return BaseError.New($"Unsuccessful response from plex image request: {response.StatusCode}");
}
private Task<Validation<BaseError, ConnectionParameters>> Validate(GetPlexImage request) =>
PlexMediaSourceMustExist(request)
.BindT(MediaSourceMustHaveActiveConnection)
.BindT(MediaSourceMustHaveToken);
private Task<Validation<BaseError, PlexMediaSource>> PlexMediaSourceMustExist(GetPlexImage request) =>
_mediaSourceRepository.GetPlex(request.PlexMediaSourceId)
.Map(
v => v.ToValidation<BaseError>(
$"Plex media source {request.PlexMediaSourceId} does not exist."));
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
PlexMediaSource plexMediaSource)
{
Option<PlexConnection> maybeConnection =
plexMediaSource.Connections.SingleOrDefault(c => c.IsActive);
return maybeConnection.Map(connection => new ConnectionParameters(plexMediaSource, connection))
.ToValidation<BaseError>("Plex media source requires an active connection");
}
private async Task<Validation<BaseError, ConnectionParameters>> MediaSourceMustHaveToken(
ConnectionParameters connectionParameters)
{
Option<PlexServerAuthToken> maybeToken = await
_plexSecretStore.GetServerAuthToken(connectionParameters.PlexMediaSource.ClientIdentifier);
return maybeToken.Map(token => connectionParameters with { PlexServerAuthToken = token })
.ToValidation<BaseError>("Plex media source requires a token");
}
private record ConnectionParameters(PlexMediaSource PlexMediaSource, PlexConnection ActiveConnection)
{
public PlexServerAuthToken PlexServerAuthToken { get; set; }
}
}
}

22
ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs

@ -46,7 +46,7 @@ namespace ErsatzTV.Infrastructure.Plex @@ -46,7 +46,7 @@ namespace ErsatzTV.Infrastructure.Plex
IPlexServerApi service = RestService.For<IPlexServerApi>(connection.Uri);
return await service.GetLibrarySectionContents(library.Key, token.AuthToken)
.Map(r => r.MediaContainer.Metadata.Filter(m => m.Media.Count > 0 && m.Media[0].Part.Count > 0))
.Map(list => list.Map(ProjectToMovie).ToList());
.Map(list => list.Map(metadata => ProjectToMovie(metadata, library.MediaSourceId)).ToList());
}
catch (Exception ex)
{
@ -119,10 +119,11 @@ namespace ErsatzTV.Infrastructure.Plex @@ -119,10 +119,11 @@ namespace ErsatzTV.Infrastructure.Plex
Media = response.Media.Map(Project).ToList()
};
private static PlexMovie ProjectToMovie(PlexMetadataResponse response)
private static PlexMovie ProjectToMovie(PlexMetadataResponse response, int mediaSourceId)
{
PlexMediaResponse media = response.Media.Head();
PlexPartResponse part = media.Part.Head();
DateTime dateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime;
DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime;
var metadata = new MovieMetadata
@ -132,11 +133,24 @@ namespace ErsatzTV.Infrastructure.Plex @@ -132,11 +133,24 @@ namespace ErsatzTV.Infrastructure.Plex
ReleaseDate = DateTime.Parse(response.OriginallyAvailableAt),
Year = response.Year,
Tagline = response.Tagline,
DateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime,
DateAdded = dateAdded,
DateUpdated = lastWriteTime
};
// TODO: artwork
if (!string.IsNullOrWhiteSpace(response.Thumb))
{
var path = $"plex/{mediaSourceId}{response.Thumb}";
var artwork = new Artwork
{
ArtworkKind = ArtworkKind.Poster,
Path = path,
DateAdded = dateAdded,
DateUpdated = lastWriteTime
};
metadata.Artwork ??= new List<Artwork>();
metadata.Artwork.Add(artwork);
}
var version = new MediaVersion
{

11
ErsatzTV/Controllers/ArtworkController.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
using System.Threading.Tasks;
using ErsatzTV.Application.Images;
using ErsatzTV.Application.Images.Queries;
using ErsatzTV.Application.Plex.Queries;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using LanguageExt;
@ -27,6 +28,16 @@ namespace ErsatzTV.Controllers @@ -27,6 +28,16 @@ namespace ErsatzTV.Controllers
Right: r => new FileContentResult(r.Contents, r.MimeType));
}
[HttpGet("/artwork/posters/plex/{plexMediaSourceId}/{*path}")]
public async Task<IActionResult> GetPlexPoster(int plexMediaSourceId, string path)
{
Either<BaseError, ImageViewModel> imageContents =
await _mediator.Send(new GetPlexImage(plexMediaSourceId, path));
return imageContents.Match<IActionResult>(
Left: _ => new NotFoundResult(),
Right: r => new FileContentResult(r.Contents, r.MimeType));
}
[HttpGet("/artwork/thumbnails/{fileName}")]
public async Task<IActionResult> GetThumbnail(string fileName)
{

Loading…
Cancel
Save