using System; using System.IO; using System.Net.Http; using System.Threading.Tasks; using ErsatzTV.Application.Emby; using ErsatzTV.Application.Emby.Queries; using ErsatzTV.Application.Images; using ErsatzTV.Application.Images.Queries; using ErsatzTV.Application.Jellyfin; using ErsatzTV.Application.Jellyfin.Queries; using ErsatzTV.Application.Plex; using ErsatzTV.Application.Plex.Queries; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Emby; using ErsatzTV.Core.Jellyfin; using Flurl; using LanguageExt; using MediatR; using Microsoft.AspNetCore.Mvc; namespace ErsatzTV.Controllers { [ResponseCache(Duration = 3600)] [ApiController] [ApiExplorerSettings(IgnoreApi = true)] public class PostersController : ControllerBase { private readonly IHttpClientFactory _httpClientFactory; private readonly IMediator _mediator; public PostersController(IMediator mediator, IHttpClientFactory httpClientFactory) { _mediator = mediator; _httpClientFactory = httpClientFactory; } [HttpHead("/iptv/artwork/posters/{fileName}")] [HttpGet("/iptv/artwork/posters/{fileName}")] [HttpHead("/iptv/artwork/posters/{fileName}.jpg")] [HttpGet("/iptv/artwork/posters/{fileName}.jpg")] [HttpGet("/artwork/posters/{fileName}")] public async Task GetPoster(string fileName) { Either cachedImagePath = await _mediator.Send(new GetCachedImagePath(fileName, ArtworkKind.Poster, 440)); return cachedImagePath.Match( Left: _ => new NotFoundResult(), Right: r => new PhysicalFileResult(r.FileName, r.MimeType)); } [HttpGet("/artwork/watermarks/{fileName}")] public async Task GetWatermark(string fileName) { Either cachedImagePath = await _mediator.Send(new GetCachedImagePath(fileName, ArtworkKind.Watermark)); return cachedImagePath.Match( Left: _ => new NotFoundResult(), Right: r => new PhysicalFileResult(r.FileName, r.MimeType)); } [HttpGet("/artwork/fanart/{fileName}")] public async Task GetFanArt(string fileName) { Either cachedImagePath = await _mediator.Send(new GetCachedImagePath(fileName, ArtworkKind.FanArt)); return cachedImagePath.Match( Left: _ => new NotFoundResult(), Right: r => new PhysicalFileResult(r.FileName, r.MimeType)); } [HttpHead("/iptv/artwork/posters/jellyfin/{*path}")] [HttpGet("/iptv/artwork/posters/jellyfin/{*path}")] [HttpGet("/artwork/posters/jellyfin/{*path}")] [HttpHead("/iptv/artwork/thumbnails/jellyfin/{*path}")] [HttpGet("/iptv/artwork/thumbnails/jellyfin/{*path}")] [HttpGet("/artwork/thumbnails/jellyfin/{*path}")] [HttpGet("/artwork/fanart/jellyfin/{*path}")] public Task GetJellyfin(string path) { if (Request.QueryString.HasValue) { path += Request.QueryString.Value; } return GetJellyfinArtwork(path); } [HttpHead("/iptv/artwork/posters/emby/{*path}")] [HttpGet("/iptv/artwork/posters/emby/{*path}")] [HttpGet("/artwork/posters/emby/{*path}")] [HttpHead("/iptv/artwork/thumbnails/emby/{*path}")] [HttpGet("/iptv/artwork/thumbnails/emby/{*path}")] [HttpGet("/artwork/thumbnails/emby/{*path}")] [HttpGet("/artwork/fanart/emby/{*path}")] public Task GetEmby(string path) { if (Request.QueryString.HasValue) { path += Request.QueryString.Value; } return GetEmbyArtwork(path); } [HttpHead("/iptv/artwork/posters/plex/{plexMediaSourceId}/{*path}")] [HttpGet("/iptv/artwork/posters/plex/{plexMediaSourceId}/{*path}")] [HttpGet("/artwork/posters/plex/{plexMediaSourceId}/{*path}")] public Task GetPlexPoster(int plexMediaSourceId, string path) => GetPlexArtwork( plexMediaSourceId, $"photo/:/transcode?url=/{path}&height=440&width=304&minSize=1&upscale=0"); [HttpGet("/artwork/fanart/plex/{plexMediaSourceId}/{*path}")] public Task GetPlexFanArt(int plexMediaSourceId, string path) => GetPlexArtwork( plexMediaSourceId, $"/{path}"); [HttpGet("/artwork/thumbnails/plex/{plexMediaSourceId}/{*path}")] public Task GetPlexThumbnail(int plexMediaSourceId, string path) => GetPlexArtwork( plexMediaSourceId, $"photo/:/transcode?url=/{path}&height=220&width=392&minSize=1&upscale=0"); [HttpHead("/iptv/artwork/thumbnails/{fileName}")] [HttpGet("/iptv/artwork/thumbnails/{fileName}")] [HttpHead("/iptv/artwork/thumbnails/{fileName}.jpg")] [HttpGet("/iptv/artwork/thumbnails/{fileName}.jpg")] [HttpGet("/artwork/thumbnails/{fileName}")] public async Task GetThumbnail(string fileName) { Either cachedImagePath = await _mediator.Send(new GetCachedImagePath(fileName, ArtworkKind.Thumbnail, 220)); return cachedImagePath.Match( Left: _ => new NotFoundResult(), Right: r => new PhysicalFileResult(r.FileName, r.MimeType)); } private async Task GetPlexArtwork(int plexMediaSourceId, string transcodePath) { Either connectionParameters = await _mediator.Send(new GetPlexConnectionParameters(plexMediaSourceId)); return await connectionParameters.Match>( Left: _ => new NotFoundResult().AsTask(), Right: async r => { HttpClient client = _httpClientFactory.CreateClient(); client.DefaultRequestHeaders.Add("X-Plex-Token", r.AuthToken); var fullPath = new Uri(r.Uri, transcodePath); HttpResponseMessage response = await client.GetAsync( fullPath, HttpCompletionOption.ResponseHeadersRead); Stream stream = await response.Content.ReadAsStreamAsync(); return new FileStreamResult( stream, response.Content.Headers.ContentType?.MediaType ?? "image/jpeg"); }); } private async Task GetJellyfinArtwork(string path) { Either connectionParameters = await _mediator.Send(new GetJellyfinConnectionParameters()); return await connectionParameters.Match>( Left: _ => new NotFoundResult().AsTask(), Right: async vm => { HttpClient client = _httpClientFactory.CreateClient(); Url fullPath = JellyfinUrl.ForArtwork(vm.Address, path); HttpResponseMessage response = await client.GetAsync( fullPath, HttpCompletionOption.ResponseHeadersRead); Stream stream = await response.Content.ReadAsStreamAsync(); return new FileStreamResult( stream, response.Content.Headers.ContentType?.MediaType ?? "image/jpeg"); }); } private async Task GetEmbyArtwork(string path) { Either connectionParameters = await _mediator.Send(new GetEmbyConnectionParameters()); return await connectionParameters.Match>( Left: _ => new NotFoundResult().AsTask(), Right: async vm => { HttpClient client = _httpClientFactory.CreateClient(); Url fullPath = EmbyUrl.ForArtwork(vm.Address, path); HttpResponseMessage response = await client.GetAsync( fullPath, HttpCompletionOption.ResponseHeadersRead); Stream stream = await response.Content.ReadAsStreamAsync(); return new FileStreamResult( stream, response.Content.Headers.ContentType?.MediaType ?? "image/jpeg"); }); } } }