using System.Diagnostics.CodeAnalysis; using ImageConvertService.Biz; using ImageConvertService.Biz.Models; using ImageConvertService.ExceptionHandling; using Microsoft.AspNetCore.Mvc; namespace ImageConvertService.Controllers; [Route("api/[controller]/[action]")] [ApiController] public class ConvertToWebpController(ImageConverter converter, ArchiveFileAccessor archiver, PdfImageReader pdf, ArchiveEntrySkipper filter) : Controller { [HttpPost] public IActionResult SingleFile(IFormFile file) { var ms = new MemoryStream(); file.CopyTo(ms); var bytes = ms.ToArray(); var webpBytes = converter.ConvertToWebp(bytes); return File(webpBytes, "image/webp", $"{file.FileName}.webp"); } [HttpPost] public async Task AnyArchiveQuery( IFormFile file, [FromQuery] string? skips, [FromQuery] OutputArchiveFormat? format = OutputArchiveFormat.Fast7Z) { return await AnyArchiveInternal(file.OpenReadStream(), Request.ContentLength, file.FileName, format, skips); } [HttpPost] public async Task AnyArchiveForm( IFormFile file, [FromForm] string? skips, [FromForm] OutputArchiveFormat? format = OutputArchiveFormat.Fast7Z) { return await AnyArchiveInternal(file.OpenReadStream(), Request.ContentLength, file.FileName, format, skips); } [HttpPost] public async Task AnyArchiveStreaming( [FromQuery] string filename, [FromQuery] string? skips, [FromQuery] OutputArchiveFormat format = OutputArchiveFormat.Fast7Z) { return await AnyArchiveInternal(Request.Body, Request.ContentLength, filename, format, skips); } private async Task AnyArchiveInternal(Stream inputStream, long? contentLength, string fileName, OutputArchiveFormat? format, string? skips) { format ??= OutputArchiveFormat.Fast7Z; if (inputStream.CanSeek) { inputStream.Position = 0; return ProcessArchiveInternal(inputStream, fileName, skips, format.Value); } try { contentLength = inputStream.Length; } catch (NotSupportedException) { } if (contentLength.HasValue && contentLength.Value < 2_000_000_000) { var ms = new MemoryStream(); await inputStream.CopyToAsync(ms); ms.Position = 0; return ProcessArchiveInternal(ms, fileName, skips, format.Value); } await using var tmpFileStream = new FileStream(Path.GetTempFileName(), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose); await inputStream.CopyToAsync(tmpFileStream); tmpFileStream.Position = 0; return ProcessArchiveInternal(tmpFileStream, fileName, skips, format.Value); } private FileContentResult ProcessArchiveInternal(Stream ms, string fileName, string? skips, OutputArchiveFormat format) { var (direct, convert) = filter.Filter(archiver.ReadArchive(ms), skips); var hasError = false; var pdfFiles = convert.Where(p => p.PathAndName.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase)).ToArray(); if (pdfFiles.Length != 0) { foreach (var pdfFile in pdfFiles) { var pdfImagesToWebp = pdf.ReadImages(pdfFile.Content, fileTime: pdfFile.FileTimeTuple).ToList(); hasError |= (!converter.ConvertToWebp(pdfImagesToWebp)); var pdfToWebpEntry = new ArchiveEntry { FileTimeTuple = pdfFile.FileTimeTuple }; switch (format) { case OutputArchiveFormat.Fast7Z: pdfToWebpEntry.Content = archiver.MakeArchiveFast7Z(pdfImagesToWebp); pdfToWebpEntry.PathAndName = pdfFile.PathAndName + "-webp.7z"; break; case OutputArchiveFormat.FastZip: pdfToWebpEntry.Content = archiver.MakeArchiveFastZip(pdfImagesToWebp); pdfToWebpEntry.PathAndName = pdfFile.PathAndName + "-webp.zip"; break; default: throw new UserFriendlyException($"Invalid {nameof(OutputArchiveFormat)}"); } convert.Remove(pdfFile); direct.Add(pdfToWebpEntry); } } hasError |= (!converter.ConvertToWebp(convert)); direct.AddRange(convert); return OutputAsArchive(fileName, format, direct, hasError); } private FileContentResult OutputAsArchive(string filename, OutputArchiveFormat format, List direct, bool withErrors) { return format switch { OutputArchiveFormat.Fast7Z => File(archiver.MakeArchiveFast7Z(direct), "application/x-7z-compressed", $"{filename}-webp{(withErrors ? "-withErrors" : "")}.7z"), OutputArchiveFormat.FastZip => File(archiver.MakeArchiveFastZip(direct), "application/zip", $"{filename}-webp{(withErrors ? "" : "-withErrors")}.zip"), _ => throw new UserFriendlyException($"Invalid {nameof(OutputArchiveFormat)}") }; } public enum OutputArchiveFormat { Fast7Z, FastZip } }