ConvertToWebpController.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. using System.Diagnostics.CodeAnalysis;
  2. using ImageConvertService.Biz;
  3. using ImageConvertService.Biz.Models;
  4. using ImageConvertService.ExceptionHandling;
  5. using Microsoft.AspNetCore.Mvc;
  6. namespace ImageConvertService.Controllers;
  7. [Route("api/[controller]/[action]")]
  8. [ApiController]
  9. public class ConvertToWebpController(ImageConverter converter, ArchiveFileAccessor archiver, PdfImageReader pdf, ArchiveEntrySkipper filter) : Controller
  10. {
  11. [HttpPost]
  12. public IActionResult SingleFile(IFormFile file)
  13. {
  14. var ms = new MemoryStream();
  15. file.CopyTo(ms);
  16. var bytes = ms.ToArray();
  17. var webpBytes = converter.ConvertToWebp(bytes);
  18. return File(webpBytes, "image/webp", $"{file.FileName}.webp");
  19. }
  20. [HttpPost]
  21. public async Task<FileContentResult> AnyArchiveQuery(
  22. IFormFile file,
  23. [FromQuery] string? skips,
  24. [FromQuery] OutputArchiveFormat? format = OutputArchiveFormat.Fast7Z)
  25. {
  26. return await AnyArchiveInternal(file.OpenReadStream(), Request.ContentLength, file.FileName, format, skips);
  27. }
  28. [HttpPost]
  29. public async Task<FileContentResult> AnyArchiveForm(
  30. IFormFile file,
  31. [FromForm] string? skips,
  32. [FromForm] OutputArchiveFormat? format = OutputArchiveFormat.Fast7Z)
  33. {
  34. return await AnyArchiveInternal(file.OpenReadStream(), Request.ContentLength, file.FileName, format, skips);
  35. }
  36. [HttpPost]
  37. public async Task<FileContentResult> AnyArchiveStreaming(
  38. [FromQuery] string filename,
  39. [FromQuery] string? skips,
  40. [FromQuery] OutputArchiveFormat format = OutputArchiveFormat.Fast7Z)
  41. {
  42. return await AnyArchiveInternal(Request.Body, Request.ContentLength, filename, format, skips);
  43. }
  44. private async Task<FileContentResult> AnyArchiveInternal(Stream inputStream, long? contentLength, string fileName, OutputArchiveFormat? format, string? skips)
  45. {
  46. format ??= OutputArchiveFormat.Fast7Z;
  47. if (inputStream.CanSeek)
  48. {
  49. inputStream.Position = 0;
  50. return ProcessArchiveInternal(inputStream, fileName, skips, format.Value);
  51. }
  52. try
  53. {
  54. contentLength = inputStream.Length;
  55. }
  56. catch (NotSupportedException)
  57. {
  58. }
  59. if (contentLength.HasValue && contentLength.Value < 2_000_000_000)
  60. {
  61. var ms = new MemoryStream();
  62. await inputStream.CopyToAsync(ms);
  63. ms.Position = 0;
  64. return ProcessArchiveInternal(ms, fileName, skips, format.Value);
  65. }
  66. await using var tmpFileStream = new FileStream(Path.GetTempFileName(), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose);
  67. await inputStream.CopyToAsync(tmpFileStream);
  68. tmpFileStream.Position = 0;
  69. return ProcessArchiveInternal(tmpFileStream, fileName, skips, format.Value);
  70. }
  71. private FileContentResult ProcessArchiveInternal(Stream ms, string fileName, string? skips, OutputArchiveFormat format)
  72. {
  73. var (direct, convert) = filter.Filter(archiver.ReadArchive(ms), skips);
  74. var hasError = false;
  75. var pdfFiles = convert.Where(p => p.PathAndName.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase)).ToArray();
  76. if (pdfFiles.Length != 0)
  77. {
  78. foreach (var pdfFile in pdfFiles)
  79. {
  80. var pdfImagesToWebp = pdf.ReadImages(pdfFile.Content, fileTime: pdfFile.FileTimeTuple).ToList();
  81. hasError |= (!converter.ConvertToWebp(pdfImagesToWebp));
  82. var pdfToWebpEntry = new ArchiveEntry { FileTimeTuple = pdfFile.FileTimeTuple };
  83. switch (format)
  84. {
  85. case OutputArchiveFormat.Fast7Z:
  86. pdfToWebpEntry.Content = archiver.MakeArchiveFast7Z(pdfImagesToWebp);
  87. pdfToWebpEntry.PathAndName = pdfFile.PathAndName + "-webp.7z";
  88. break;
  89. case OutputArchiveFormat.FastZip:
  90. pdfToWebpEntry.Content = archiver.MakeArchiveFastZip(pdfImagesToWebp);
  91. pdfToWebpEntry.PathAndName = pdfFile.PathAndName + "-webp.zip";
  92. break;
  93. default:
  94. throw new UserFriendlyException($"Invalid {nameof(OutputArchiveFormat)}");
  95. }
  96. convert.Remove(pdfFile);
  97. direct.Add(pdfToWebpEntry);
  98. }
  99. }
  100. hasError |= (!converter.ConvertToWebp(convert));
  101. direct.AddRange(convert);
  102. return OutputAsArchive(fileName, format, direct, hasError);
  103. }
  104. private FileContentResult OutputAsArchive(string filename, OutputArchiveFormat format, List<ArchiveEntry> direct, bool withErrors)
  105. {
  106. return format switch
  107. {
  108. OutputArchiveFormat.Fast7Z => File(archiver.MakeArchiveFast7Z(direct), "application/x-7z-compressed", $"{filename}-webp{(withErrors ? "-withErrors" : "")}.7z"),
  109. OutputArchiveFormat.FastZip => File(archiver.MakeArchiveFastZip(direct), "application/zip", $"{filename}-webp{(withErrors ? "" : "-withErrors")}.zip"),
  110. _ => throw new UserFriendlyException($"Invalid {nameof(OutputArchiveFormat)}")
  111. };
  112. }
  113. public enum OutputArchiveFormat
  114. {
  115. Fast7Z,
  116. FastZip
  117. }
  118. }