| | | 1 | | using Anichron.Core.Domain; |
| | | 2 | | using Anichron.Worker.Ingestion.Pipeline; |
| | | 3 | | using Anichron.Worker.Ingestion.Proxy; |
| | | 4 | | using SixLabors.ImageSharp; |
| | | 5 | | using SixLabors.ImageSharp.PixelFormats; |
| | | 6 | | using System.IO.Abstractions; |
| | | 7 | | |
| | | 8 | | namespace Anichron.Worker.Ingestion.Middlewares; |
| | | 9 | | |
| | 20 | 10 | | internal sealed partial class ImageProxyMiddleware( |
| | 20 | 11 | | IEnumerable<IImageProxyGenerator> generators, |
| | 20 | 12 | | IProxyDirectoryStrategy proxyDirectoryStrategy, |
| | 20 | 13 | | ProxyStagingWriter stagingWriter, |
| | 20 | 14 | | IFileSystem fileSystem, |
| | 20 | 15 | | ILogger<ImageProxyMiddleware> logger) : IIngestionMiddleware |
| | | 16 | | { |
| | 0 | 17 | | public int Order => IngestionOrder.ImageProxy; |
| | | 18 | | public bool CanInvoke(IngestionContext context) |
| | 3 | 19 | | => context.Item.PrimaryMediaType is MediaType.Image or MediaType.LivePhoto; |
| | | 20 | | |
| | | 21 | | public async Task InvokeAsync(IngestionContext context, IngestionDelegate next, CancellationToken ct) |
| | 17 | 22 | | { |
| | | 23 | | // Ordering guarantees ContentHashingMiddleware ran first; suppression is safe. |
| | 17 | 24 | | var proxyDirectoryName = proxyDirectoryStrategy.GetDirectory(context.Config.Id, context.ContentHash!); |
| | 17 | 25 | | var sourceBytes = await fileSystem.File.ReadAllBytesAsync(context.Item.AbsolutePath, ct); |
| | | 26 | | |
| | 17 | 27 | | await using var ms = new MemoryStream(sourceBytes); |
| | 17 | 28 | | using var image = await Image.LoadAsync<Rgba32>(ms, ct); |
| | | 29 | | |
| | 17 | 30 | | var writes = generators.Select(WriteProxyAsync).ToArray(); |
| | 17 | 31 | | await Task.WhenAll(writes); |
| | | 32 | | |
| | 16 | 33 | | Log.ProxiesGenerated(logger, writes.Length, context.Item.RelativePath); |
| | 16 | 34 | | await next(context, ct); |
| | | 35 | | |
| | | 36 | | Task WriteProxyAsync(IImageProxyGenerator generator) |
| | | 37 | | { |
| | | 38 | | return stagingWriter.WriteAsync( |
| | | 39 | | context, |
| | | 40 | | $"{proxyDirectoryName}/{generator.FileName}", |
| | | 41 | | generator.ProxyType, |
| | | 42 | | ProduceAsync, |
| | | 43 | | ct); |
| | | 44 | | |
| | | 45 | | async Task ProduceAsync(string temporaryPath, CancellationToken token) |
| | | 46 | | { |
| | | 47 | | using var clone = image.Clone(); |
| | | 48 | | var bytes = await generator.GenerateAsync(clone, token); |
| | | 49 | | await fileSystem.File.WriteAllBytesAsync(temporaryPath, bytes, token); |
| | | 50 | | } |
| | | 51 | | } |
| | 16 | 52 | | } |
| | | 53 | | |
| | | 54 | | private static partial class Log |
| | | 55 | | { |
| | | 56 | | [LoggerMessage(Level = LogLevel.Information, Message = "Generated {Count} proxy files for {RelativePath}.")] |
| | | 57 | | public static partial void ProxiesGenerated(ILogger logger, int count, string relativePath); |
| | | 58 | | } |
| | | 59 | | } |