* include cache header on artwork responses * rework movie page to include fan art * full width app bar * dark mode * cleanup * fix placeholder color
99 lines
3.9 KiB
C#
99 lines
3.9 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using ErsatzTV.Core;
|
|
using ErsatzTV.Core.Domain;
|
|
using ErsatzTV.Core.Interfaces.Images;
|
|
using ErsatzTV.Core.Interfaces.Metadata;
|
|
using LanguageExt;
|
|
using SixLabors.ImageSharp;
|
|
using SixLabors.ImageSharp.Formats.Jpeg;
|
|
using SixLabors.ImageSharp.Processing;
|
|
|
|
namespace ErsatzTV.Infrastructure.Images
|
|
{
|
|
public class ImageCache : IImageCache
|
|
{
|
|
private static readonly SHA1CryptoServiceProvider Crypto;
|
|
private readonly ILocalFileSystem _localFileSystem;
|
|
|
|
static ImageCache() => Crypto = new SHA1CryptoServiceProvider();
|
|
|
|
public ImageCache(ILocalFileSystem localFileSystem) => _localFileSystem = localFileSystem;
|
|
|
|
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);
|
|
|
|
var 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>> SaveArtworkToCache(byte[] imageBuffer, ArtworkKind artworkKind)
|
|
{
|
|
try
|
|
{
|
|
byte[] hash = Crypto.ComputeHash(imageBuffer);
|
|
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),
|
|
ArtworkKind.Logo => Path.Combine(FileSystemLayout.LogoCacheFolder, subfolder),
|
|
ArtworkKind.FanArt => Path.Combine(FileSystemLayout.FanArtCacheFolder, subfolder),
|
|
_ => FileSystemLayout.LegacyImageCacheFolder
|
|
};
|
|
string target = Path.Combine(baseFolder, hex);
|
|
|
|
if (!Directory.Exists(baseFolder))
|
|
{
|
|
Directory.CreateDirectory(baseFolder);
|
|
}
|
|
|
|
await File.WriteAllBytesAsync(target, imageBuffer);
|
|
return hex;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BaseError.New(ex.Message);
|
|
}
|
|
}
|
|
|
|
public 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),
|
|
ArtworkKind.Logo => Path.Combine(FileSystemLayout.LogoCacheFolder, subfolder),
|
|
ArtworkKind.FanArt => Path.Combine(FileSystemLayout.FanArtCacheFolder, subfolder),
|
|
_ => FileSystemLayout.LegacyImageCacheFolder
|
|
};
|
|
string target = Path.Combine(baseFolder, hex);
|
|
_localFileSystem.CopyFile(path, target);
|
|
|
|
return hex;
|
|
}
|
|
}
|
|
}
|