Browse Source

attach artwork to metadata

pull/31/head
Jason Dove 6 years ago
parent
commit
f40b3e2923
  1. 3
      ErsatzTV.Application/Images/Queries/GetImageContents.cs
  2. 27
      ErsatzTV.Application/Images/Queries/GetImageContentsHandler.cs
  3. 17
      ErsatzTV.Application/MediaCards/Mapper.cs
  4. 5
      ErsatzTV.Application/MediaSources/Commands/ScanLocalLibraryHandler.cs
  5. 6
      ErsatzTV.Application/Movies/Mapper.cs
  6. 3
      ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs
  7. 5
      ErsatzTV.Core/Domain/MediaItem/MediaItem.cs
  8. 1
      ErsatzTV.Core/Domain/MediaItem/Season.cs
  9. 13
      ErsatzTV.Core/Domain/Metadata/Artwork.cs
  10. 9
      ErsatzTV.Core/Domain/Metadata/ArtworkKind.cs
  11. 4
      ErsatzTV.Core/Domain/Metadata/EpisodeMetadata.cs
  12. 2
      ErsatzTV.Core/Domain/Metadata/Metadata.cs
  13. 9
      ErsatzTV.Core/Domain/Metadata/SeasonMetadata.cs
  14. 7
      ErsatzTV.Core/FileSystemLayout.cs
  15. 11
      ErsatzTV.Core/Interfaces/Domain/IHasAPoster.cs
  16. 1
      ErsatzTV.Core/Interfaces/Images/IImageCache.cs
  17. 2
      ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs
  18. 14
      ErsatzTV.Core/Metadata/LocalFileSystem.cs
  19. 84
      ErsatzTV.Core/Metadata/LocalFolderScanner.cs
  20. 8
      ErsatzTV.Core/Metadata/MovieFolderScanner.cs
  21. 50
      ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs
  22. 5
      ErsatzTV.Infrastructure/Data/Configurations/MediaItem/SeasonConfiguration.cs
  23. 11
      ErsatzTV.Infrastructure/Data/Configurations/Metadata/ArtworkConfiguration.cs
  24. 9
      ErsatzTV.Infrastructure/Data/Configurations/Metadata/EpisodeMetadataConfiguration.cs
  25. 9
      ErsatzTV.Infrastructure/Data/Configurations/Metadata/MovieMetadataConfiguration.cs
  26. 9
      ErsatzTV.Infrastructure/Data/Configurations/Metadata/ShowMetadataConfiguration.cs
  27. 6
      ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs
  28. 5
      ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs
  29. 21
      ErsatzTV.Infrastructure/Images/ImageCache.cs
  30. 1429
      ErsatzTV.Infrastructure/Migrations/20210227020133_Add_Artwork.Designer.cs
  31. 89
      ErsatzTV.Infrastructure/Migrations/20210227020133_Add_Artwork.cs
  32. 1496
      ErsatzTV.Infrastructure/Migrations/20210227105739_Add_SeasonMetadata.Designer.cs
  33. 80
      ErsatzTV.Infrastructure/Migrations/20210227105739_Add_SeasonMetadata.cs
  34. 147
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs
  35. 4
      ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs
  36. 4
      ErsatzTV/Controllers/IptvController.cs
  37. 4
      ErsatzTV/Controllers/PostersController.cs

3
ErsatzTV.Application/Images/Queries/GetImageContents.cs

@ -1,8 +1,9 @@ @@ -1,8 +1,9 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.Images.Queries
{
public record GetImageContents(string FileName) : IRequest<Either<BaseError, ImageViewModel>>;
public record GetImageContents(string FileName, ArtworkKind ArtworkKind, int? MaxHeight = null) : IRequest<Either<BaseError, ImageViewModel>>;
}

27
ErsatzTV.Application/Images/Queries/GetImageContentsHandler.cs

@ -3,6 +3,8 @@ using System.IO; @@ -3,6 +3,8 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Images;
using LanguageExt;
using MediatR;
using Microsoft.Extensions.Caching.Memory;
@ -13,9 +15,14 @@ namespace ErsatzTV.Application.Images.Queries @@ -13,9 +15,14 @@ namespace ErsatzTV.Application.Images.Queries
public class GetImageContentsHandler : IRequestHandler<GetImageContents, Either<BaseError, ImageViewModel>>
{
private static readonly MimeTypes MimeTypes = new();
private readonly IImageCache _imageCache;
private readonly IMemoryCache _memoryCache;
public GetImageContentsHandler(IMemoryCache memoryCache) => _memoryCache = memoryCache;
public GetImageContentsHandler(IImageCache imageCache, IMemoryCache memoryCache)
{
_imageCache = imageCache;
_memoryCache = memoryCache;
}
public async Task<Either<BaseError, ImageViewModel>> Handle(
GetImageContents request,
@ -29,8 +36,24 @@ namespace ErsatzTV.Application.Images.Queries @@ -29,8 +36,24 @@ namespace ErsatzTV.Application.Images.Queries
{
entry.SlidingExpiration = TimeSpan.FromHours(1);
string fileName = Path.Combine(FileSystemLayout.ImageCacheFolder, request.FileName);
string subfolder = request.FileName.Substring(0, 2);
string baseFolder = request.ArtworkKind switch
{
ArtworkKind.Poster => Path.Combine(FileSystemLayout.PosterCacheFolder, subfolder),
ArtworkKind.Thumbnail => Path.Combine(FileSystemLayout.ThumbnailCacheFolder, subfolder),
_ => FileSystemLayout.ImageCacheFolder
};
string fileName = Path.Combine(baseFolder, request.FileName);
byte[] contents = await File.ReadAllBytesAsync(fileName, cancellationToken);
if (request.MaxHeight.HasValue)
{
Either<BaseError, byte[]> resizeResult = await _imageCache
.ResizeImage(contents, request.MaxHeight.Value);
resizeResult.IfRight(result => contents = result);
}
MimeType mimeType = MimeTypes.GetMimeType(contents);
return new ImageViewModel(contents, mimeType.Name);
});

17
ErsatzTV.Application/MediaCards/Mapper.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
using System;
using System.Linq;
using ErsatzTV.Core.Domain;
using static LanguageExt.Prelude;
namespace ErsatzTV.Application.MediaCards
{
@ -12,7 +13,7 @@ namespace ErsatzTV.Application.MediaCards @@ -12,7 +13,7 @@ namespace ErsatzTV.Application.MediaCards
showMetadata.Title,
showMetadata.ReleaseDate?.Year.ToString(),
showMetadata.SortTitle,
null); // TODO: artwork
GetPoster(showMetadata));
internal static TelevisionSeasonCardViewModel ProjectToViewModel(Season season) =>
new(
@ -22,7 +23,7 @@ namespace ErsatzTV.Application.MediaCards @@ -22,7 +23,7 @@ namespace ErsatzTV.Application.MediaCards
GetSeasonName(season.SeasonNumber),
string.Empty,
GetSeasonName(season.SeasonNumber),
season.Poster,
season.SeasonMetadata.HeadOrNone().Map(GetPoster).IfNone(string.Empty),
season.SeasonNumber == 0 ? "S" : season.SeasonNumber.ToString());
internal static TelevisionEpisodeCardViewModel ProjectToViewModel(
@ -34,7 +35,7 @@ namespace ErsatzTV.Application.MediaCards @@ -34,7 +35,7 @@ namespace ErsatzTV.Application.MediaCards
episodeMetadata.Title,
$"Episode {episodeMetadata.Episode.EpisodeNumber}",
episodeMetadata.Episode.EpisodeNumber.ToString(),
null, // TODO: artwork
GetThumbnail(episodeMetadata),
episodeMetadata.Episode.EpisodeNumber.ToString());
internal static MovieCardViewModel ProjectToViewModel(MovieMetadata movieMetadata) =>
@ -43,7 +44,7 @@ namespace ErsatzTV.Application.MediaCards @@ -43,7 +44,7 @@ namespace ErsatzTV.Application.MediaCards
movieMetadata.Title,
movieMetadata.ReleaseDate?.Year.ToString(),
movieMetadata.SortTitle,
null); // TODO: artwork
GetPoster(movieMetadata));
internal static CollectionCardResultsViewModel
ProjectToViewModel(Collection collection) =>
@ -57,5 +58,13 @@ namespace ErsatzTV.Application.MediaCards @@ -57,5 +58,13 @@ namespace ErsatzTV.Application.MediaCards
private static string GetSeasonName(int number) =>
number == 0 ? "Specials" : $"Season {number}";
private static string GetPoster(Metadata metadata) =>
Optional(metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Poster))
.Match(a => a.Path, string.Empty);
private static string GetThumbnail(Metadata metadata) =>
Optional(metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Thumbnail))
.Match(a => a.Path, string.Empty);
}
}

5
ErsatzTV.Application/MediaSources/Commands/ScanLocalLibraryHandler.cs

@ -66,11 +66,10 @@ namespace ErsatzTV.Application.MediaSources.Commands @@ -66,11 +66,10 @@ namespace ErsatzTV.Application.MediaSources.Commands
switch (localLibrary.MediaKind)
{
case LibraryMediaKind.Movies:
// await _movieFolderScanner.ScanFolder(libraryPath, ffprobePath);
await _movieFolderScanner.ScanFolder(libraryPath, ffprobePath);
break;
case LibraryMediaKind.Shows:
// TODO: re-enable this
// await _televisionFolderScanner.ScanFolder(libraryPath, ffprobePath);
await _televisionFolderScanner.ScanFolder(libraryPath, ffprobePath);
break;
}
}

6
ErsatzTV.Application/Movies/Mapper.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using ErsatzTV.Core.Domain;
using System.Linq;
using ErsatzTV.Core.Domain;
using static LanguageExt.Prelude;
namespace ErsatzTV.Application.Movies
@ -12,7 +13,8 @@ namespace ErsatzTV.Application.Movies @@ -12,7 +13,8 @@ namespace ErsatzTV.Application.Movies
metadata.Title,
metadata.ReleaseDate?.Year.ToString(),
metadata.Plot,
movie.Poster);
Optional(metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Poster))
.Match(a => a.Path, string.Empty));
}
}
}

3
ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs

@ -54,6 +54,9 @@ namespace ErsatzTV.Core.Tests.Fakes @@ -54,6 +54,9 @@ namespace ErsatzTV.Core.Tests.Fakes
public Task<byte[]> ReadAllBytes(string path) => TestBytes.AsTask();
public Unit CopyFile(string source, string destination) =>
Unit.Default;
private static List<DirectoryInfo> Split(DirectoryInfo path)
{
var result = new List<DirectoryInfo>();

5
ErsatzTV.Core/Domain/MediaItem/MediaItem.cs

@ -1,10 +1,9 @@ @@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using ErsatzTV.Core.Interfaces.Domain;
namespace ErsatzTV.Core.Domain
{
public class MediaItem : IHasAPoster
public class MediaItem
{
public int Id { get; set; }
public MediaItemStatistics Statistics { get; set; }
@ -21,7 +20,5 @@ namespace ErsatzTV.Core.Domain @@ -21,7 +20,5 @@ namespace ErsatzTV.Core.Domain
public List<Collection> Collections { get; set; }
public List<CollectionItem> CollectionItems { get; set; }
public string Path { get; set; }
public string Poster { get; set; }
public DateTime? PosterLastWriteTime { get; set; }
}
}

1
ErsatzTV.Core/Domain/MediaItem/Season.cs

@ -9,5 +9,6 @@ namespace ErsatzTV.Core.Domain @@ -9,5 +9,6 @@ namespace ErsatzTV.Core.Domain
public Show Show { get; set; }
public List<Episode> Episodes { get; set; }
public List<SeasonMetadata> SeasonMetadata { get; set; }
}
}

13
ErsatzTV.Core/Domain/Metadata/Artwork.cs

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
using System;
namespace ErsatzTV.Core.Domain
{
public class Artwork
{
public int Id { get; set; }
public string Path { get; set; }
public ArtworkKind ArtworkKind { get; set; }
public DateTime DateAdded { get; set; }
public DateTime DateUpdated { get; set; }
}
}

9
ErsatzTV.Core/Domain/Metadata/ArtworkKind.cs

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
namespace ErsatzTV.Core.Domain
{
public enum ArtworkKind
{
Poster = 0,
Thumbnail = 1,
Logo = 2
}
}

4
ErsatzTV.Core/Domain/Metadata/EpisodeMetadata.cs

@ -1,4 +1,6 @@ @@ -1,4 +1,6 @@
namespace ErsatzTV.Core.Domain
using System.Collections.Generic;
namespace ErsatzTV.Core.Domain
{
public class EpisodeMetadata : Metadata
{

2
ErsatzTV.Core/Domain/Metadata/Metadata.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
namespace ErsatzTV.Core.Domain
{
@ -12,5 +13,6 @@ namespace ErsatzTV.Core.Domain @@ -12,5 +13,6 @@ namespace ErsatzTV.Core.Domain
public DateTime? ReleaseDate { get; set; }
public DateTime DateAdded { get; set; }
public DateTime DateUpdated { get; set; }
public List<Artwork> Artwork { get; set; }
}
}

9
ErsatzTV.Core/Domain/Metadata/SeasonMetadata.cs

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
namespace ErsatzTV.Core.Domain
{
public class SeasonMetadata : Metadata
{
public string Outline { get; set; }
public int SeasonId { get; set; }
public Season Season { get; set; }
}
}

7
ErsatzTV.Core/FileSystemLayout.cs

@ -16,7 +16,12 @@ namespace ErsatzTV.Core @@ -16,7 +16,12 @@ namespace ErsatzTV.Core
public static readonly string LogDatabasePath = Path.Combine(AppDataFolder, "logs.sqlite3");
public static readonly string ImageCacheFolder = Path.Combine(AppDataFolder, "cache", "images");
public static readonly string PlexSecretsPath = Path.Combine(AppDataFolder, "plex-secrets.json");
public static readonly string ArtworkCacheFolder = Path.Combine(AppDataFolder, "cache", "artwork");
public static readonly string PosterCacheFolder = Path.Combine(ArtworkCacheFolder, "posters");
public static readonly string ThumbnailCacheFolder = Path.Combine(ArtworkCacheFolder, "thumbnails");
}
}

11
ErsatzTV.Core/Interfaces/Domain/IHasAPoster.cs

@ -1,11 +0,0 @@ @@ -1,11 +0,0 @@
using System;
namespace ErsatzTV.Core.Interfaces.Domain
{
public interface IHasAPoster
{
string Path { get; set; }
string Poster { get; set; }
DateTime? PosterLastWriteTime { get; set; }
}
}

1
ErsatzTV.Core/Interfaces/Images/IImageCache.cs

@ -5,6 +5,7 @@ namespace ErsatzTV.Core.Interfaces.Images @@ -5,6 +5,7 @@ namespace ErsatzTV.Core.Interfaces.Images
{
public interface IImageCache
{
Task<Either<BaseError, byte[]>> ResizeImage(byte[] imageBuffer, int height);
Task<Either<BaseError, string>> ResizeAndSaveImage(byte[] imageBuffer, int? height, int? width);
Task<Either<BaseError, string>> SaveImage(byte[] imageBuffer);
}

2
ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs

@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using LanguageExt;
namespace ErsatzTV.Core.Interfaces.Metadata
{
@ -13,5 +14,6 @@ namespace ErsatzTV.Core.Interfaces.Metadata @@ -13,5 +14,6 @@ namespace ErsatzTV.Core.Interfaces.Metadata
IEnumerable<string> ListFiles(string folder);
bool FileExists(string path);
Task<byte[]> ReadAllBytes(string path);
Unit CopyFile(string source, string destination);
}
}

14
ErsatzTV.Core/Metadata/LocalFileSystem.cs

@ -4,6 +4,7 @@ using System.IO; @@ -4,6 +4,7 @@ using System.IO;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Metadata;
using LanguageExt;
using static LanguageExt.Prelude;
namespace ErsatzTV.Core.Metadata
@ -24,5 +25,18 @@ namespace ErsatzTV.Core.Metadata @@ -24,5 +25,18 @@ namespace ErsatzTV.Core.Metadata
public bool FileExists(string path) => File.Exists(path);
public Task<byte[]> ReadAllBytes(string path) => File.ReadAllBytesAsync(path);
public Unit CopyFile(string source, string destination)
{
var directory = Path.GetDirectoryName(destination);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
File.Copy(source, destination, true);
return Unit.Default;
}
}
}

84
ErsatzTV.Core/Metadata/LocalFolderScanner.cs

@ -2,9 +2,10 @@ @@ -2,9 +2,10 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Domain;
using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Interfaces.Metadata;
using LanguageExt;
@ -14,6 +15,8 @@ namespace ErsatzTV.Core.Metadata @@ -14,6 +15,8 @@ namespace ErsatzTV.Core.Metadata
{
public abstract class LocalFolderScanner
{
private static readonly SHA1CryptoServiceProvider Crypto;
public static readonly List<string> VideoFileExtensions = new()
{
".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".ogg", ".mp4",
@ -46,6 +49,8 @@ namespace ErsatzTV.Core.Metadata @@ -46,6 +49,8 @@ namespace ErsatzTV.Core.Metadata
private readonly ILocalStatisticsProvider _localStatisticsProvider;
private readonly ILogger _logger;
static LocalFolderScanner() => Crypto = new SHA1CryptoServiceProvider();
protected LocalFolderScanner(
ILocalFileSystem localFileSystem,
ILocalStatisticsProvider localStatisticsProvider,
@ -79,30 +84,63 @@ namespace ErsatzTV.Core.Metadata @@ -79,30 +84,63 @@ namespace ErsatzTV.Core.Metadata
}
}
protected async Task SavePosterToDisk<T>(
T show,
string posterPath,
Func<T, Task<bool>> update,
int height = 220) where T : IHasAPoster
protected bool RefreshArtwork(string artworkFile, Domain.Metadata metadata, ArtworkKind artworkKind)
{
byte[] originalBytes = await _localFileSystem.ReadAllBytes(posterPath);
Either<BaseError, string> maybeHash = await _imageCache.ResizeAndSaveImage(originalBytes, height, null);
await maybeHash.Match(
hash =>
{
show.Poster = hash;
show.PosterLastWriteTime = _localFileSystem.GetLastWriteTime(posterPath);
return update(show);
},
error =>
{
_logger.LogWarning("Unable to save poster to disk from {Path}: {Error}", posterPath, error.Value);
return Task.CompletedTask;
});
DateTime lastWriteTime = _localFileSystem.GetLastWriteTime(artworkFile);
Option<Artwork> maybePoster =
metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == artworkKind);
bool shouldRefresh = maybePoster.Match(
artwork => artwork.DateUpdated < lastWriteTime,
true);
if (shouldRefresh)
{
_logger.LogDebug("Refreshing {Attribute} from {Path}", artworkKind, artworkFile);
string cacheName = CopyArtworkToCache(artworkFile, artworkKind);
maybePoster.Match(
artwork =>
{
artwork.Path = cacheName;
artwork.DateUpdated = lastWriteTime;
},
() =>
{
var artwork = new Artwork
{
Path = cacheName,
DateAdded = DateTime.UtcNow,
DateUpdated = lastWriteTime,
ArtworkKind = artworkKind
};
metadata.Artwork.Add(artwork);
});
return true;
}
return false;
}
protected Task<Either<BaseError, string>> SavePosterToDisk(string posterPath, int height = 220) =>
_localFileSystem.ReadAllBytes(posterPath)
.Bind(bytes => _imageCache.ResizeAndSaveImage(bytes, height, null));
private string CopyArtworkToCache(string path, ArtworkKind artworkKind)
{
var filenameKey = $"{path}:{_localFileSystem.GetLastWriteTime(path).ToFileTimeUtc()}";
byte[] hash = Crypto.ComputeHash(Encoding.UTF8.GetBytes(filenameKey));
string hex = BitConverter.ToString(hash).Replace("-", string.Empty);
string subfolder = hex.Substring(0, 2);
string baseFolder = artworkKind switch
{
ArtworkKind.Poster => Path.Combine(FileSystemLayout.PosterCacheFolder, subfolder),
ArtworkKind.Thumbnail => Path.Combine(FileSystemLayout.ThumbnailCacheFolder, subfolder),
_ => FileSystemLayout.ImageCacheFolder
};
string target = Path.Combine(baseFolder, hex);
_localFileSystem.CopyFile(path, target);
return hex;
}
}
}

8
ErsatzTV.Core/Metadata/MovieFolderScanner.cs

@ -131,12 +131,10 @@ namespace ErsatzTV.Core.Metadata @@ -131,12 +131,10 @@ namespace ErsatzTV.Core.Metadata
await LocatePoster(movie).IfSomeAsync(
async posterFile =>
{
if (string.IsNullOrWhiteSpace(movie.Poster) ||
(movie.PosterLastWriteTime ?? DateTime.MinValue) <
_localFileSystem.GetLastWriteTime(posterFile))
MovieMetadata metadata = movie.MovieMetadata.Head();
if (RefreshArtwork(posterFile, metadata, ArtworkKind.Poster))
{
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Poster", posterFile);
await SavePosterToDisk(movie, posterFile, _movieRepository.Update, 440);
await _movieRepository.Update(movie);
}
});

50
ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@ -211,27 +212,10 @@ namespace ErsatzTV.Core.Metadata @@ -211,27 +212,10 @@ namespace ErsatzTV.Core.Metadata
await LocatePosterForShow(showFolder).IfSomeAsync(
async posterFile =>
{
if (string.IsNullOrWhiteSpace(show.Poster) ||
(show.PosterLastWriteTime ?? DateTime.MinValue) <
_localFileSystem.GetLastWriteTime(posterFile))
ShowMetadata metadata = show.ShowMetadata.Head();
if (RefreshArtwork(posterFile, metadata, ArtworkKind.Poster))
{
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Poster", posterFile);
Either<BaseError, string> maybePoster = await SavePosterToDisk(posterFile, 440);
await maybePoster.Match(
poster =>
{
show.Poster = poster;
show.PosterLastWriteTime = _localFileSystem.GetLastWriteTime(posterFile);
return _televisionRepository.Update(show);
},
error =>
{
_logger.LogWarning(
"Unable to save poster to disk from {Path}: {Error}",
posterFile,
error.Value);
return Task.CompletedTask;
});
await _televisionRepository.Update(show);
}
});
@ -250,12 +234,17 @@ namespace ErsatzTV.Core.Metadata @@ -250,12 +234,17 @@ namespace ErsatzTV.Core.Metadata
await LocatePoster(season).IfSomeAsync(
async posterFile =>
{
if (string.IsNullOrWhiteSpace(season.Poster) ||
(season.PosterLastWriteTime ?? DateTime.MinValue) <
_localFileSystem.GetLastWriteTime(posterFile))
season.SeasonMetadata ??= new List<SeasonMetadata>();
if (!season.SeasonMetadata.Any())
{
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Poster", posterFile);
await SavePosterToDisk(season, posterFile, _televisionRepository.Update, 440);
season.SeasonMetadata.Add(new SeasonMetadata { SeasonId = season.Id });
}
SeasonMetadata metadata = season.SeasonMetadata.Head();
if (RefreshArtwork(posterFile, metadata, ArtworkKind.Poster))
{
await _televisionRepository.Update(season);
}
});
@ -267,20 +256,17 @@ namespace ErsatzTV.Core.Metadata @@ -267,20 +256,17 @@ namespace ErsatzTV.Core.Metadata
}
}
private async Task<Either<BaseError, Episode>> UpdateThumbnail(
Episode episode)
private async Task<Either<BaseError, Episode>> UpdateThumbnail(Episode episode)
{
try
{
await LocateThumbnail(episode).IfSomeAsync(
async posterFile =>
{
if (string.IsNullOrWhiteSpace(episode.Poster) ||
(episode.PosterLastWriteTime ?? DateTime.MinValue) <
_localFileSystem.GetLastWriteTime(posterFile))
EpisodeMetadata metadata = episode.EpisodeMetadata.Head();
if (RefreshArtwork(posterFile, metadata, ArtworkKind.Thumbnail))
{
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Thumbnail", posterFile);
await SavePosterToDisk(episode, posterFile, _televisionRepository.Update);
await _televisionRepository.Update(episode);
}
});

5
ErsatzTV.Infrastructure/Data/Configurations/MediaItem/SeasonConfiguration.cs

@ -14,6 +14,11 @@ namespace ErsatzTV.Infrastructure.Data.Configurations @@ -14,6 +14,11 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
.WithOne(e => e.Season)
.HasForeignKey(e => e.SeasonId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasMany(s => s.SeasonMetadata)
.WithOne(s => s.Season)
.HasForeignKey(s => s.SeasonId)
.OnDelete(DeleteBehavior.Cascade);
}
}
}

11
ErsatzTV.Infrastructure/Data/Configurations/Metadata/ArtworkConfiguration.cs

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class ArtworkConfiguration : IEntityTypeConfiguration<Artwork>
{
public void Configure(EntityTypeBuilder<Artwork> builder) => builder.ToTable("Artwork");
}
}

9
ErsatzTV.Infrastructure/Data/Configurations/Metadata/EpisodeMetadataConfiguration.cs

@ -6,6 +6,13 @@ namespace ErsatzTV.Infrastructure.Data.Configurations @@ -6,6 +6,13 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class EpisodeMetadataConfiguration : IEntityTypeConfiguration<EpisodeMetadata>
{
public void Configure(EntityTypeBuilder<EpisodeMetadata> builder) => builder.ToTable("EpisodeMetadata");
public void Configure(EntityTypeBuilder<EpisodeMetadata> builder)
{
builder.ToTable("EpisodeMetadata");
builder.HasMany(em => em.Artwork)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
}
}
}

9
ErsatzTV.Infrastructure/Data/Configurations/Metadata/MovieMetadataConfiguration.cs

@ -6,6 +6,13 @@ namespace ErsatzTV.Infrastructure.Data.Configurations @@ -6,6 +6,13 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class MovieMetadataConfiguration : IEntityTypeConfiguration<MovieMetadata>
{
public void Configure(EntityTypeBuilder<MovieMetadata> builder) => builder.ToTable("MovieMetadata");
public void Configure(EntityTypeBuilder<MovieMetadata> builder)
{
builder.ToTable("MovieMetadata");
builder.HasMany(mm => mm.Artwork)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
}
}
}

9
ErsatzTV.Infrastructure/Data/Configurations/Metadata/ShowMetadataConfiguration.cs

@ -6,6 +6,13 @@ namespace ErsatzTV.Infrastructure.Data.Configurations @@ -6,6 +6,13 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class ShowMetadataConfiguration : IEntityTypeConfiguration<ShowMetadata>
{
public void Configure(EntityTypeBuilder<ShowMetadata> builder) => builder.ToTable("ShowMetadata");
public void Configure(EntityTypeBuilder<ShowMetadata> builder)
{
builder.ToTable("ShowMetadata");
builder.HasMany(sm => sm.Artwork)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
}
}
}

6
ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using Dapper;
using ErsatzTV.Core;
@ -26,14 +27,15 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -26,14 +27,15 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public Task<Option<Movie>> GetMovie(int movieId) =>
_dbContext.Movies
.Include(m => m.MovieMetadata)
.ThenInclude(m => m.Artwork)
.SingleOrDefaultAsync(m => m.Id == movieId)
.Map(Optional);
// TODO: fix this - need to add to library path, not media source
public async Task<Either<BaseError, Movie>> GetOrAdd(LibraryPath libraryPath, string path)
{
Option<Movie> maybeExisting = await _dbContext.Movies
.Include(i => i.MovieMetadata)
.ThenInclude(mm => mm.Artwork)
.Include(i => i.LibraryPath)
.SingleOrDefaultAsync(i => i.Path == path);
@ -73,6 +75,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -73,6 +75,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
LIMIT {0} OFFSET {1}",
pageSize,
(pageNumber - 1) * pageSize)
.Include(mm => mm.Artwork)
.OrderBy(mm => mm.SortTitle)
.ToListAsync();
private async Task<Either<BaseError, Movie>> AddMovie(int libraryPathId, string path)

5
ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs

@ -66,6 +66,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -66,6 +66,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
// TODO: fix split shows
_dbContext.ShowMetadata
.AsNoTracking()
.Include(sm => sm.Artwork)
.OrderBy(sm => sm.SortTitle)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
@ -96,6 +97,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -96,6 +97,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
_dbContext.Seasons
.AsNoTracking()
.Where(s => s.ShowId == televisionShowId)
.Include(s => s.SeasonMetadata)
.ThenInclude(sm => sm.Artwork)
.Include(s => s.Show)
.ThenInclude(s => s.ShowMetadata)
.OrderBy(s => s.SeasonNumber)
@ -121,6 +124,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -121,6 +124,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
_dbContext.EpisodeMetadata
.AsNoTracking()
.Filter(em => em.Episode.SeasonId == seasonId)
.Include(em => em.Artwork)
.Include(em => em.Episode)
.ThenInclude(e => e.Season)
.ThenInclude(s => s.Show)
@ -143,6 +147,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -143,6 +147,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
{
return _dbContext.Shows
.Include(s => s.ShowMetadata)
.ThenInclude(sm => sm.Artwork)
.Filter(s => s.Id == id)
.SingleOrDefaultAsync()
.Map(Optional);

21
ErsatzTV.Infrastructure/Images/ImageCache.cs

@ -17,6 +17,27 @@ namespace ErsatzTV.Infrastructure.Images @@ -17,6 +17,27 @@ namespace ErsatzTV.Infrastructure.Images
static ImageCache() => Crypto = new SHA1CryptoServiceProvider();
public async Task<Either<BaseError, byte[]>> ResizeImage(byte[] imageBuffer, int height)
{
await using var inStream = new MemoryStream(imageBuffer);
using var image = await Image.LoadAsync(inStream);
Size size = new Size { Height = height };
image.Mutate(
i => i.Resize(
new ResizeOptions
{
Mode = ResizeMode.Max,
Size = size
}));
await using var outStream = new MemoryStream();
await image.SaveAsync(outStream, new JpegEncoder { Quality = 90 });
return outStream.ToArray();
}
public async Task<Either<BaseError, string>> ResizeAndSaveImage(byte[] imageBuffer, int? height, int? width)
{
await using var inStream = new MemoryStream(imageBuffer);

1429
ErsatzTV.Infrastructure/Migrations/20210227020133_Add_Artwork.Designer.cs generated

File diff suppressed because it is too large Load Diff

89
ErsatzTV.Infrastructure/Migrations/20210227020133_Add_Artwork.cs

@ -0,0 +1,89 @@ @@ -0,0 +1,89 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Add_Artwork : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
"Poster",
"MediaItem");
migrationBuilder.DropColumn(
"PosterLastWriteTime",
"MediaItem");
migrationBuilder.CreateTable(
"Artwork",
table => new
{
Id = table.Column<int>("INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Path = table.Column<string>("TEXT", nullable: true),
ArtworkKind = table.Column<int>("INTEGER", nullable: false),
DateAdded = table.Column<DateTime>("TEXT", nullable: false),
DateUpdated = table.Column<DateTime>("TEXT", nullable: false),
EpisodeMetadataId = table.Column<int>("INTEGER", nullable: true),
MovieMetadataId = table.Column<int>("INTEGER", nullable: true),
ShowMetadataId = table.Column<int>("INTEGER", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Artwork", x => x.Id);
table.ForeignKey(
"FK_Artwork_EpisodeMetadata_EpisodeMetadataId",
x => x.EpisodeMetadataId,
"EpisodeMetadata",
"Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
"FK_Artwork_MovieMetadata_MovieMetadataId",
x => x.MovieMetadataId,
"MovieMetadata",
"Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
"FK_Artwork_ShowMetadata_ShowMetadataId",
x => x.ShowMetadataId,
"ShowMetadata",
"Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
"IX_Artwork_EpisodeMetadataId",
"Artwork",
"EpisodeMetadataId");
migrationBuilder.CreateIndex(
"IX_Artwork_MovieMetadataId",
"Artwork",
"MovieMetadataId");
migrationBuilder.CreateIndex(
"IX_Artwork_ShowMetadataId",
"Artwork",
"ShowMetadataId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
"Artwork");
migrationBuilder.AddColumn<string>(
"Poster",
"MediaItem",
"TEXT",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
"PosterLastWriteTime",
"MediaItem",
"TEXT",
nullable: true);
}
}
}

1496
ErsatzTV.Infrastructure/Migrations/20210227105739_Add_SeasonMetadata.Designer.cs generated

File diff suppressed because it is too large Load Diff

80
ErsatzTV.Infrastructure/Migrations/20210227105739_Add_SeasonMetadata.cs

@ -0,0 +1,80 @@ @@ -0,0 +1,80 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Add_SeasonMetadata : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
"SeasonMetadataId",
"Artwork",
"INTEGER",
nullable: true);
migrationBuilder.CreateTable(
"SeasonMetadata",
table => new
{
Id = table.Column<int>("INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Outline = table.Column<string>("TEXT", nullable: true),
SeasonId = table.Column<int>("INTEGER", nullable: false),
MetadataKind = table.Column<int>("INTEGER", nullable: false),
Title = table.Column<string>("TEXT", nullable: true),
OriginalTitle = table.Column<string>("TEXT", nullable: true),
SortTitle = table.Column<string>("TEXT", nullable: true),
ReleaseDate = table.Column<DateTime>("TEXT", nullable: true),
DateAdded = table.Column<DateTime>("TEXT", nullable: false),
DateUpdated = table.Column<DateTime>("TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SeasonMetadata", x => x.Id);
table.ForeignKey(
"FK_SeasonMetadata_Season_SeasonId",
x => x.SeasonId,
"Season",
"Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
"IX_Artwork_SeasonMetadataId",
"Artwork",
"SeasonMetadataId");
migrationBuilder.CreateIndex(
"IX_SeasonMetadata_SeasonId",
"SeasonMetadata",
"SeasonId");
migrationBuilder.AddForeignKey(
"FK_Artwork_SeasonMetadata_SeasonMetadataId",
"Artwork",
"SeasonMetadataId",
"SeasonMetadata",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
"FK_Artwork_SeasonMetadata_SeasonMetadataId",
"Artwork");
migrationBuilder.DropTable(
"SeasonMetadata");
migrationBuilder.DropIndex(
"IX_Artwork_SeasonMetadataId",
"Artwork");
migrationBuilder.DropColumn(
"SeasonMetadataId",
"Artwork");
}
}
}

147
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -16,6 +16,51 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -16,6 +16,51 @@ namespace ErsatzTV.Infrastructure.Migrations
modelBuilder
.HasAnnotation("ProductVersion", "5.0.3");
modelBuilder.Entity(
"ErsatzTV.Core.Domain.Artwork",
b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ArtworkKind")
.HasColumnType("INTEGER");
b.Property<DateTime>("DateAdded")
.HasColumnType("TEXT");
b.Property<DateTime>("DateUpdated")
.HasColumnType("TEXT");
b.Property<int?>("EpisodeMetadataId")
.HasColumnType("INTEGER");
b.Property<int?>("MovieMetadataId")
.HasColumnType("INTEGER");
b.Property<string>("Path")
.HasColumnType("TEXT");
b.Property<int?>("SeasonMetadataId")
.HasColumnType("INTEGER");
b.Property<int?>("ShowMetadataId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("EpisodeMetadataId");
b.HasIndex("MovieMetadataId");
b.HasIndex("SeasonMetadataId");
b.HasIndex("ShowMetadataId");
b.ToTable("Artwork");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.Channel",
b =>
@ -307,12 +352,6 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -307,12 +352,6 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<string>("Path")
.HasColumnType("TEXT");
b.Property<string>("Poster")
.HasColumnType("TEXT");
b.Property<DateTime?>("PosterLastWriteTime")
.HasColumnType("TEXT");
b.Property<int>("TelevisionEpisodeId")
.HasColumnType("INTEGER");
@ -667,6 +706,48 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -667,6 +706,48 @@ namespace ErsatzTV.Infrastructure.Migrations
b.ToTable("Resolution");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.SeasonMetadata",
b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("DateAdded")
.HasColumnType("TEXT");
b.Property<DateTime>("DateUpdated")
.HasColumnType("TEXT");
b.Property<int>("MetadataKind")
.HasColumnType("INTEGER");
b.Property<string>("OriginalTitle")
.HasColumnType("TEXT");
b.Property<string>("Outline")
.HasColumnType("TEXT");
b.Property<DateTime?>("ReleaseDate")
.HasColumnType("TEXT");
b.Property<int>("SeasonId")
.HasColumnType("INTEGER");
b.Property<string>("SortTitle")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("SeasonId");
b.ToTable("SeasonMetadata");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.ShowMetadata",
b =>
@ -880,6 +961,30 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -880,6 +961,30 @@ namespace ErsatzTV.Infrastructure.Migrations
b.ToTable("PlexMovie");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.Artwork",
b =>
{
b.HasOne("ErsatzTV.Core.Domain.EpisodeMetadata", null)
.WithMany("Artwork")
.HasForeignKey("EpisodeMetadataId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("ErsatzTV.Core.Domain.MovieMetadata", null)
.WithMany("Artwork")
.HasForeignKey("MovieMetadataId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("ErsatzTV.Core.Domain.SeasonMetadata", null)
.WithMany("Artwork")
.HasForeignKey("SeasonMetadataId");
b.HasOne("ErsatzTV.Core.Domain.ShowMetadata", null)
.WithMany("Artwork")
.HasForeignKey("ShowMetadataId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.Channel",
b =>
@ -1236,6 +1341,19 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -1236,6 +1341,19 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Navigation("ProgramSchedule");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.SeasonMetadata",
b =>
{
b.HasOne("ErsatzTV.Core.Domain.Season", "Season")
.WithMany("SeasonMetadata")
.HasForeignKey("SeasonId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Season");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.ShowMetadata",
b =>
@ -1418,6 +1536,8 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -1418,6 +1536,8 @@ namespace ErsatzTV.Infrastructure.Migrations
modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b => { b.Navigation("CollectionItems"); });
modelBuilder.Entity("ErsatzTV.Core.Domain.EpisodeMetadata", b => { b.Navigation("Artwork"); });
modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => { b.Navigation("Paths"); });
modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => { b.Navigation("MediaItems"); });
@ -1428,6 +1548,8 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -1428,6 +1548,8 @@ namespace ErsatzTV.Infrastructure.Migrations
modelBuilder.Entity("ErsatzTV.Core.Domain.MediaVersion", b => { b.Navigation("MediaFiles"); });
modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => { b.Navigation("Artwork"); });
modelBuilder.Entity(
"ErsatzTV.Core.Domain.Playout",
b =>
@ -1446,6 +1568,10 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -1446,6 +1568,10 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Navigation("Playouts");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b => { b.Navigation("Artwork"); });
modelBuilder.Entity("ErsatzTV.Core.Domain.ShowMetadata", b => { b.Navigation("Artwork"); });
modelBuilder.Entity(
"ErsatzTV.Core.Domain.Episode",
b =>
@ -1464,7 +1590,14 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -1464,7 +1590,14 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Navigation("MovieMetadata");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => { b.Navigation("Episodes"); });
modelBuilder.Entity(
"ErsatzTV.Core.Domain.Season",
b =>
{
b.Navigation("Episodes");
b.Navigation("SeasonMetadata");
});
modelBuilder.Entity(
"ErsatzTV.Core.Domain.Show",

4
ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs

@ -132,13 +132,13 @@ namespace ErsatzTV.Infrastructure.Plex @@ -132,13 +132,13 @@ namespace ErsatzTV.Infrastructure.Plex
DateAdded = DateTime.UtcNow, // TODO: actual date added?
DateUpdated = lastWriteTime
};
// TODO: artwork
var movie = new PlexMovie
{
Key = response.Key,
Poster = response.Thumb,
LastWriteTime = lastWriteTime,
PosterLastWriteTime = lastWriteTime,
MovieMetadata = new List<MovieMetadata> { metadata },
Statistics = new MediaItemStatistics
{

4
ErsatzTV/Controllers/IptvController.cs

@ -4,6 +4,7 @@ using ErsatzTV.Application.Images; @@ -4,6 +4,7 @@ using ErsatzTV.Application.Images;
using ErsatzTV.Application.Images.Queries;
using ErsatzTV.Application.Streaming.Queries;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Iptv;
using LanguageExt;
using MediatR;
@ -63,7 +64,8 @@ namespace ErsatzTV.Controllers @@ -63,7 +64,8 @@ namespace ErsatzTV.Controllers
[HttpGet("iptv/images/{fileName}")]
public async Task<IActionResult> GetImage(string fileName)
{
Either<BaseError, ImageViewModel> imageContents = await _mediator.Send(new GetImageContents(fileName));
Either<BaseError, ImageViewModel> imageContents =
await _mediator.Send(new GetImageContents(fileName, ArtworkKind.Logo));
return imageContents.Match<IActionResult>(
Left: _ => new NotFoundResult(),
Right: r => new FileContentResult(r.Contents, r.MimeType));

4
ErsatzTV/Controllers/PostersController.cs

@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
using ErsatzTV.Application.Images;
using ErsatzTV.Application.Images.Queries;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using LanguageExt;
using MediatR;
using Microsoft.AspNetCore.Mvc;
@ -19,7 +20,8 @@ namespace ErsatzTV.Controllers @@ -19,7 +20,8 @@ namespace ErsatzTV.Controllers
[HttpGet("/posters/{fileName}")]
public async Task<IActionResult> GetImage(string fileName)
{
Either<BaseError, ImageViewModel> imageContents = await _mediator.Send(new GetImageContents(fileName));
Either<BaseError, ImageViewModel> imageContents =
await _mediator.Send(new GetImageContents(fileName, ArtworkKind.Poster, 440));
return imageContents.Match<IActionResult>(
Left: _ => new NotFoundResult(),
Right: r => new FileContentResult(r.Contents, r.MimeType));

Loading…
Cancel
Save