diff --git a/CHANGELOG.md b/CHANGELOG.md index a31670eb..fbedbdb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Use subtitles to display errors, which fixes many edge cases of unescaped characters - Properly split song genre tags - Properly display all songs that have an identical album and title +- Fix channel logo and watermark uploads ### Added - Add song genres to search index diff --git a/ErsatzTV.Application/Images/Commands/SaveArtworkToDisk.cs b/ErsatzTV.Application/Images/Commands/SaveArtworkToDisk.cs index 653a1db4..c431b754 100644 --- a/ErsatzTV.Application/Images/Commands/SaveArtworkToDisk.cs +++ b/ErsatzTV.Application/Images/Commands/SaveArtworkToDisk.cs @@ -1,4 +1,5 @@ -using ErsatzTV.Core; +using System.IO; +using ErsatzTV.Core; using ErsatzTV.Core.Domain; using LanguageExt; using MediatR; @@ -6,5 +7,5 @@ using MediatR; namespace ErsatzTV.Application.Images.Commands { // ReSharper disable once SuggestBaseTypeForParameter - public record SaveArtworkToDisk(byte[] Buffer, ArtworkKind ArtworkKind) : IRequest>; + public record SaveArtworkToDisk(Stream Stream, ArtworkKind ArtworkKind) : IRequest>; } diff --git a/ErsatzTV.Application/Images/Commands/SaveArtworkToDiskHandler.cs b/ErsatzTV.Application/Images/Commands/SaveArtworkToDiskHandler.cs index a7dd2ab1..dc7caa57 100644 --- a/ErsatzTV.Application/Images/Commands/SaveArtworkToDiskHandler.cs +++ b/ErsatzTV.Application/Images/Commands/SaveArtworkToDiskHandler.cs @@ -14,6 +14,6 @@ namespace ErsatzTV.Application.Images.Commands public SaveArtworkToDiskHandler(IImageCache imageCache) => _imageCache = imageCache; public Task> Handle(SaveArtworkToDisk request, CancellationToken cancellationToken) => - _imageCache.SaveArtworkToCache(request.Buffer, request.ArtworkKind); + _imageCache.SaveArtworkToCache(request.Stream, request.ArtworkKind); } } diff --git a/ErsatzTV.Core/FFmpeg/TempFileCategory.cs b/ErsatzTV.Core/FFmpeg/TempFileCategory.cs index ef82c1ac..207cf903 100644 --- a/ErsatzTV.Core/FFmpeg/TempFileCategory.cs +++ b/ErsatzTV.Core/FFmpeg/TempFileCategory.cs @@ -4,6 +4,7 @@ { Subtitle = 0, SongBackground = 1, - CoverArt = 2 + CoverArt = 2, + CachedArtwork = 3 } } diff --git a/ErsatzTV.Core/Interfaces/Images/IImageCache.cs b/ErsatzTV.Core/Interfaces/Images/IImageCache.cs index 3f25ad4b..14cfb8e5 100644 --- a/ErsatzTV.Core/Interfaces/Images/IImageCache.cs +++ b/ErsatzTV.Core/Interfaces/Images/IImageCache.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.IO; +using System.Threading.Tasks; using ErsatzTV.Core.Domain; using LanguageExt; @@ -7,7 +8,7 @@ namespace ErsatzTV.Core.Interfaces.Images public interface IImageCache { Task> ResizeImage(byte[] imageBuffer, int height); - Task> SaveArtworkToCache(byte[] imageBuffer, ArtworkKind artworkKind); + Task> SaveArtworkToCache(Stream stream, ArtworkKind artworkKind); Task> CopyArtworkToCache(string path, ArtworkKind artworkKind); string GetPathForImage(string fileName, ArtworkKind artworkKind, Option maybeMaxHeight); Task IsAnimated(string fileName); diff --git a/ErsatzTV.Infrastructure/Images/ImageCache.cs b/ErsatzTV.Infrastructure/Images/ImageCache.cs index 5ea57075..f6cee1cf 100644 --- a/ErsatzTV.Infrastructure/Images/ImageCache.cs +++ b/ErsatzTV.Infrastructure/Images/ImageCache.cs @@ -5,6 +5,8 @@ using System.Text; using System.Threading.Tasks; using ErsatzTV.Core; using ErsatzTV.Core.Domain; +using ErsatzTV.Core.FFmpeg; +using ErsatzTV.Core.Interfaces.FFmpeg; using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.Core.Interfaces.Metadata; using LanguageExt; @@ -22,13 +24,19 @@ namespace ErsatzTV.Infrastructure.Images private readonly ILocalFileSystem _localFileSystem; private readonly ILogger _logger; private readonly IMemoryCache _memoryCache; + private readonly ITempFilePool _tempFilePool; static ImageCache() => Crypto = SHA1.Create(); - public ImageCache(ILocalFileSystem localFileSystem, IMemoryCache memoryCache, ILogger logger) + public ImageCache( + ILocalFileSystem localFileSystem, + IMemoryCache memoryCache, + ITempFilePool tempFilePool, + ILogger logger) { _localFileSystem = localFileSystem; _memoryCache = memoryCache; + _tempFilePool = tempFilePool; _logger = logger; } @@ -53,11 +61,14 @@ namespace ErsatzTV.Infrastructure.Images return outStream.ToArray(); } - public async Task> SaveArtworkToCache(byte[] imageBuffer, ArtworkKind artworkKind) + public async Task> SaveArtworkToCache(Stream stream, ArtworkKind artworkKind) { try { - byte[] hash = Crypto.ComputeHash(imageBuffer); + string tempFileName = _tempFilePool.GetNextTempFile(TempFileCategory.CachedArtwork); + await using var fs = new FileStream(tempFileName, FileMode.OpenOrCreate); + await stream.CopyToAsync(fs); + byte[] hash = await ComputeFileHash(tempFileName); string hex = BitConverter.ToString(hash).Replace("-", string.Empty); string subfolder = hex[..2]; string baseFolder = artworkKind switch @@ -76,7 +87,8 @@ namespace ErsatzTV.Infrastructure.Images Directory.CreateDirectory(baseFolder); } - await File.WriteAllBytesAsync(target, imageBuffer); + await _localFileSystem.CopyFile(tempFileName, target); + return hex; } catch (Exception ex) @@ -85,6 +97,14 @@ namespace ErsatzTV.Infrastructure.Images } } + private static async Task ComputeFileHash(string fileName) + { + using var md5 = MD5.Create(); + await using var fs = new FileStream(fileName, FileMode.Open); + fs.Position = 0; + return await md5.ComputeHashAsync(fs); + } + public async Task> CopyArtworkToCache(string path, ArtworkKind artworkKind) { try diff --git a/ErsatzTV/Pages/ChannelEditor.razor b/ErsatzTV/Pages/ChannelEditor.razor index 14bd4372..4a490af7 100644 --- a/ErsatzTV/Pages/ChannelEditor.razor +++ b/ErsatzTV/Pages/ChannelEditor.razor @@ -199,9 +199,8 @@ { try { - var buffer = new byte[e.File.Size]; - await e.File.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024).ReadAsync(buffer); - Either maybeCacheFileName = await _mediator.Send(new SaveArtworkToDisk(buffer, ArtworkKind.Logo)); + Either maybeCacheFileName = + await _mediator.Send(new SaveArtworkToDisk(e.File.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024), ArtworkKind.Logo)); maybeCacheFileName.Match( relativeFileName => { @@ -210,12 +209,14 @@ }, error => { + Console.WriteLine($"error saving {error}"); _snackbar.Add($"Unexpected error saving channel logo: {error.Value}", Severity.Error); _logger.LogError("Unexpected error saving channel logo: {Error}", error.Value); }); } - catch (IOException) + catch (IOException ex) { + Console.WriteLine(ex); _snackbar.Add("Channel logo exceeds maximum allowed file size of 10 MB", Severity.Error); _logger.LogError("Channel logo exceeds maximum allowed file size of 10 MB"); } diff --git a/ErsatzTV/Pages/WatermarkEditor.razor b/ErsatzTV/Pages/WatermarkEditor.razor index e1b05d28..4a9e267c 100644 --- a/ErsatzTV/Pages/WatermarkEditor.razor +++ b/ErsatzTV/Pages/WatermarkEditor.razor @@ -209,10 +209,8 @@ { try { - var buffer = new byte[e.File.Size]; - await e.File.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024).ReadAsync(buffer); Either maybeCacheFileName = await _mediator - .Send(new SaveArtworkToDisk(buffer, ArtworkKind.Watermark)); + .Send(new SaveArtworkToDisk(e.File.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024), ArtworkKind.Watermark)); maybeCacheFileName.Match( relativeFileName => {