diff options
| -rw-r--r-- | Controllers/MediaController.cs | 40 | ||||
| -rw-r--r-- | Server.csproj | 4 | ||||
| -rw-r--r-- | Services/ConfigService.cs | 14 | ||||
| -rw-r--r-- | Services/MediaService.cs | 55 |
4 files changed, 92 insertions, 21 deletions
diff --git a/Controllers/MediaController.cs b/Controllers/MediaController.cs index 2c015f7..cd6916f 100644 --- a/Controllers/MediaController.cs +++ b/Controllers/MediaController.cs @@ -8,18 +8,26 @@ namespace HyperBooru.Controllers; [ApiController] [Route("/media")] public class MediaController : Controller { - private IMediaService mediaService; - private IConfigService config; - private HBContext db; + private IHttpContextAccessor httpContextAccessor; + private IMediaService mediaService; + private IConfigService config; + private HBContext db; + + private readonly string[] FormatPriority = [ + "image/webp", + "image/png" + ]; public MediaController( + IHttpContextAccessor httpContextAccessor, IMediaService mediaService, IConfigService config, HBContext db) { - this.mediaService = mediaService; - this.config = config; - this.db = db; + this.httpContextAccessor = httpContextAccessor; + this.mediaService = mediaService; + this.config = config; + this.db = db; } [HttpGet("{mediaId}")] @@ -30,9 +38,25 @@ public class MediaController : Controller { if(media is null) return NotFound(); - var fs = System.IO.File.OpenRead(mediaService.GetPath(media)); + // Check if the requested media item is a HEIC image and if it is, convert it + // otherwise, return the original file content, unaltered + if(media.CurrentUploadedFile!.MimeType == "image/heic") { + // If the media needs to be converted, check the HTTP request for allowed + // media formats, and convert to the best available format or WebP otherwise + var allowedTypes = httpContextAccessor + .HttpContext? + .Request + .GetTypedHeaders().Accept.Select(h => h.MediaType.ToString()) ?? Array.Empty<string>(); + + var format = FormatPriority.FirstOrDefault(f => allowedTypes.Contains(f)) ?? "image/webp"; - return new FileStreamResult(fs, media.CurrentUploadedFile!.MimeType); + var fs = mediaService.GetConverted(media, format); + + return new FileStreamResult(fs, format); + } else { + var fs = System.IO.File.OpenRead(mediaService.GetPath(media)); + return new FileStreamResult(fs, media.CurrentUploadedFile!.MimeType); + } } [HttpGet("thumb/{mediaId}")] diff --git a/Server.csproj b/Server.csproj index 8e85fd2..1a48326 100644 --- a/Server.csproj +++ b/Server.csproj @@ -6,9 +6,9 @@ <ImplicitUsings>enable</ImplicitUsings> <AssemblyName>HyperBooru</AssemblyName> <RootNamespace>HyperBooru</RootNamespace> - <AssemblyVersion>0.4.0.0</AssemblyVersion> + <AssemblyVersion>0.5.0.0</AssemblyVersion> <FileVersion>$(AssemblyVersion)</FileVersion> - <Version>0.4-alpha</Version> + <Version>0.5-alpha</Version> <UserSecretsId>2907567f-4640-4581-8f4d-0977952d26bd</UserSecretsId> </PropertyGroup> diff --git a/Services/ConfigService.cs b/Services/ConfigService.cs index 8460fd0..d2d1a06 100644 --- a/Services/ConfigService.cs +++ b/Services/ConfigService.cs @@ -1,11 +1,12 @@ namespace HyperBooru.Services; public interface IConfigService { - public string DataPath { get; } - public string DbConnectionString { get; } - public string MediaBasePath { get; } - public string ThumbnailBasePath { get; } - public bool EnableOcr { get; } + public string DataPath { get; } + public string DbConnectionString { get; } + public string MediaBasePath { get; } + public string ThumbnailBasePath { get; } + public string ConvertedMediaBasePath { get; } + public bool EnableOcr { get; } } public class ConfigService : IConfigService { @@ -47,6 +48,9 @@ public class ConfigService : IConfigService { public string ThumbnailBasePath => Path.Join(DataPath, "thumb"); + public string ConvertedMediaBasePath => + Path.Join(DataPath, "converted"); + public bool EnableOcr => bool.TryParse(config["DisableOcr"], out bool x) ? !x : true; diff --git a/Services/MediaService.cs b/Services/MediaService.cs index a5803f9..2f7eac6 100644 --- a/Services/MediaService.cs +++ b/Services/MediaService.cs @@ -1,4 +1,5 @@ using ImageMagick; +using ImageMagick.Formats; using Microsoft.EntityFrameworkCore; using MimeDetective; using MimeDetective.Definitions; @@ -31,14 +32,21 @@ public interface IMediaService { public void DeleteThumbnails(Media media); public Stream GetThumbnail(Guid media, int? width, int? height); public Stream GetThumbnail(Media media, int? width, int? height); + public Stream GetConverted(Guid mediaId, string mimeType = "image/png"); + public Stream GetConverted(Media media, string mimeType = "image/png"); public string GetPath(Guid media); - public string GetPath(Guid media, int? width, int? height); public string GetPath(Media media); - public string GetPath(Media media, int? width, int? height); } public class MediaService : IMediaService { + private readonly Dictionary<string,MagickFormat> FormatMap = new() { + ["image/jpeg"] = MagickFormat.Jpeg, + ["image/jpg"] = MagickFormat.Jpg, + ["image/png"] = MagickFormat.Png, + ["image/webp"] = MagickFormat.WebP + }; + private IDbContextFactory<HBContext> dbFactory; private IConfigService config; @@ -267,7 +275,7 @@ public class MediaService : IMediaService { "Both width and height cannot be null!", mediaId); - var thumbPath = GetPath(mediaId, width, height); + var thumbPath = GetThumbnailPath(mediaId, width, height); if(File.Exists(thumbPath)) return System.IO.File.OpenRead(thumbPath); @@ -288,9 +296,30 @@ public class MediaService : IMediaService { return System.IO.File.OpenRead(thumbPath); } + public Stream GetConverted(Guid mediaId, string mimeType) { + if(!FormatMap.TryGetValue(mimeType, out var format)) + throw new MediaException($"Cannot convert to unknown format ({mimeType})", mediaId); + + var convertedPath = GetConvertedPath(mediaId, mimeType); + + if(File.Exists(convertedPath)) + return System.IO.File.OpenRead(convertedPath); + + if(!File.Exists(GetPath(mediaId))) + throw new ObjectNotFoundException(mediaId); + + using var image = new MagickImage(GetPath(mediaId)); + image.Write(convertedPath, format); + + return System.IO.File.OpenRead(convertedPath); + } + public Stream GetThumbnail(Media media, int? width, int? height) => GetThumbnail(media.Guid, width, height); + public Stream GetConverted(Media media, string mimeType) => + GetConverted(media.Guid, mimeType); + public string GetPath(Guid mediaId) { var fileInfo = new FileInfo( Path.Join( @@ -303,7 +332,7 @@ public class MediaService : IMediaService { return fileInfo.FullName; } - public string GetPath(Guid mediaId, int? width, int? height) { + public string GetThumbnailPath(Guid mediaId, int? width, int? height) { if(width is null && height is null) throw new ThumbnailException( "Both width and height cannot be null!", @@ -319,11 +348,25 @@ public class MediaService : IMediaService { return fileInfo.FullName; } + public string GetConvertedPath(Guid mediaId, string mimeType) { + var fileInfo = new FileInfo(Path.Join( + config.ConvertedMediaBasePath, + mediaId.ToString().Substring(0, 2), + mediaId.ToString().Substring(2, 2), + $"{mediaId.ToString()}-{mimeType.Split('/')[1]}")); + + Directory.CreateDirectory(fileInfo.Directory!.FullName); + return fileInfo.FullName; + } + public string GetPath(Media media) => GetPath(media.Guid); - public string GetPath(Media media, int? width, int? height) => - GetPath(media.Guid, width, height); + public string GetThumbnailPath(Media media, int? width, int? height) => + GetThumbnailPath(media.Guid, width, height); + + public string GetConvertedPath(Media media, string mimeType) => + GetConvertedPath(media.Guid, mimeType); private int GetUploadedFileHash(UploadedFile uf) => ( uf.CreateTime, |
