From b40b5234f4cb3681bd9531d76c38111057f71733 Mon Sep 17 00:00:00 2001 From: Rory& Date: Wed, 11 Mar 2026 00:58:13 +0100 Subject: [PATCH] WIP cdn-cs stuff --- .../FilesystemFileSource.cs | 4 -- .../Internal/GetImageController.cs | 67 +++++++++++-------- .../Controllers/StaticAssetController.cs | 41 ++++++++++-- .../Extensions/ImageController.cs | 2 +- .../Helpers/DefaultAvatarRenderer.cs | 24 +++++++ extra/admin-api/Spacebar.Cdn/Mimes.cs | 15 +++++ .../Properties/launchSettings.json | 3 +- .../Services/CdnStorageRebuildService.cs | 5 -- .../Services/DiscordImageResizeService.cs | 4 ++ .../Spacebar.Cdn/Spacebar.Cdn.csproj | 2 +- 10 files changed, 123 insertions(+), 44 deletions(-) create mode 100644 extra/admin-api/Spacebar.Cdn/Helpers/DefaultAvatarRenderer.cs delete mode 100644 extra/admin-api/Spacebar.Cdn/Services/CdnStorageRebuildService.cs diff --git a/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/FilesystemFileSource.cs b/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/FilesystemFileSource.cs index 3bdee1132..a3f0a7d87 100644 --- a/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/FilesystemFileSource.cs +++ b/extra/admin-api/Interop/Spacebar.Interop.Cdn.Abstractions/FilesystemFileSource.cs @@ -3,10 +3,6 @@ using ArcaneLibs; namespace Spacebar.Interop.Cdn.Abstractions; public class FilesystemFileSource(string baseUrl) : IFileSource { - private readonly StreamingHttpClient _httpClient = new() { - BaseAddress = new Uri(baseUrl) - }; - public string BaseUrl => baseUrl; public async Task Init(CancellationToken? cancellationToken = null) { diff --git a/extra/admin-api/Spacebar.Cdn/Controllers/Internal/GetImageController.cs b/extra/admin-api/Spacebar.Cdn/Controllers/Internal/GetImageController.cs index 404e6e294..f595fba97 100644 --- a/extra/admin-api/Spacebar.Cdn/Controllers/Internal/GetImageController.cs +++ b/extra/admin-api/Spacebar.Cdn/Controllers/Internal/GetImageController.cs @@ -1,3 +1,4 @@ +using ArcaneLibs; using ArcaneLibs.Collections; using ImageMagick; using Microsoft.AspNetCore.Mvc; @@ -6,7 +7,7 @@ using Spacebar.Cdn.Extensions; using Spacebar.Interop.Cdn.Abstractions; namespace Spacebar.Cdn.Controllers.Internal; -/* + [ApiController] public class IsPixelArtController(LruFileCache lfc, IFileSource fs, PixelArtDetectionService pads, DiscordImageResizeService dirs) : ControllerBase { private static readonly LruCache _isPixelArtCache = new(100_000); @@ -20,7 +21,7 @@ public class IsPixelArtController(LruFileCache lfc, IFileSource fs, PixelArtDete return pads.IsPixelArt(img); }); } - + [HttpGet("/isCartoonArt/{*_:required}")] public async Task IsCartoonArt() { return await _isPixelArtCache.GetOrAddAsync(Request.Path.ToString(), async () => { @@ -34,18 +35,19 @@ public class IsPixelArtController(LruFileCache lfc, IFileSource fs, PixelArtDete public async Task GetEdges([FromQuery] string applyMode = "pre") { return await _edgeCache.GetOrAdd(Request.Path.ToString() + Request.QueryString, async () => { var original = await fs.GetFile(Request.Path.ToString().Replace("/edges", "")); - + DiscordImageResizeParams resizeParams = new() { Size = Request.Query.ContainsKey("size") && uint.TryParse(Request.Query["size"], out uint size) ? size : null, - Quality = Request.Query.ContainsKey("quality") && Enum.TryParse(Request.Query["quality"], true, out var quality) ? quality : DiscordImageResizeQuality.High, + Quality = Request.Query.ContainsKey("quality") && Enum.TryParse(Request.Query["quality"], true, out var quality) + ? quality + : DiscordImageResizeQuality.High, KeepAspectRatio = !Request.Query.ContainsKey("keepAspectRatio") || !bool.TryParse(Request.Query["keepAspectRatio"], out bool kar) || kar, Passthrough = Request.Query.ContainsKey("passthrough") && bool.TryParse(Request.Query["passthrough"], out bool pt) && pt, Animated = Request.Query.ContainsKey("animated") && bool.TryParse(Request.Query["animated"], out bool an) && an, SpacebarAllowUpscale = Request.Query.ContainsKey("allowUpscale") && bool.TryParse(Request.Query["allowUpscale"], out bool au) && au, SpacebarOptimiseGif = Request.Query.ContainsKey("optimiseGif") && bool.TryParse(Request.Query["optimiseGif"], out bool og) && og }; - - + double radius = 1; if (Request.Query.ContainsKey("radius")) double.TryParse(Request.Query["radius"], out radius); @@ -65,27 +67,27 @@ public class IsPixelArtController(LruFileCache lfc, IFileSource fs, PixelArtDete }; }).ContinueWith(t => File(t.Result.Data, t.Result.MimeType)); } - - [HttpGet("/posterize/{*_:required}")] + + [HttpGet("/posterize/{*_:required}")] public async Task Posterize() { return await _edgeCache.GetOrAdd(Request.Path.ToString() + Request.QueryString, async () => { var original = await fs.GetFile(Request.Path.ToString().Replace("/posterize", "")); DiscordImageResizeParams resizeParams = new() { Size = Request.Query.ContainsKey("size") && uint.TryParse(Request.Query["size"], out uint size) ? size : null, - Quality = Request.Query.ContainsKey("quality") && Enum.TryParse(Request.Query["quality"], true, out var quality) ? quality : DiscordImageResizeQuality.High, + Quality = Request.Query.ContainsKey("quality") && Enum.TryParse(Request.Query["quality"], true, out var quality) + ? quality + : DiscordImageResizeQuality.High, KeepAspectRatio = !Request.Query.ContainsKey("keepAspectRatio") || !bool.TryParse(Request.Query["keepAspectRatio"], out bool kar) || kar, Passthrough = Request.Query.ContainsKey("passthrough") && bool.TryParse(Request.Query["passthrough"], out bool pt) && pt, Animated = Request.Query.ContainsKey("animated") && bool.TryParse(Request.Query["animated"], out bool an) && an, SpacebarAllowUpscale = Request.Query.ContainsKey("allowUpscale") && bool.TryParse(Request.Query["allowUpscale"], out bool au) && au }; - - + double radius = 1; if (Request.Query.ContainsKey("radius")) double.TryParse(Request.Query["radius"], out radius); var img = await original.ToMagickImageCollectionAsync(); - int inFrames = img.Count; // using var edged = pads.RenderEdges(img, radius); foreach (var frame in img) { @@ -94,9 +96,9 @@ public class IsPixelArtController(LruFileCache lfc, IFileSource fs, PixelArtDete frame.Posterize(16, DitherMethod.No); frame.ColorFuzz = new Percentage(0); } - - if(resizeParams.Size.HasValue) - img = dirs.Apply(img,resizeParams); + + if (resizeParams.Size.HasValue) + img = dirs.Apply(img, resizeParams); Console.WriteLine($"Generated edges for {Request.Path}, radius={radius}, inFrames={inFrames}, outFrames={img.Count}"); return new LruFileCache.Entry { @@ -106,36 +108,38 @@ public class IsPixelArtController(LruFileCache lfc, IFileSource fs, PixelArtDete }; }).ContinueWith(t => File(t.Result.Data, t.Result.MimeType)); } - + [HttpGet("/colorFuzz/{*_:required}")] public async Task ColorFuzz() { return await _edgeCache.GetOrAdd(Request.Path.ToString() + Request.QueryString, async () => { var original = await fs.GetFile(Request.Path.ToString().Replace("/colorFuzz", "")); DiscordImageResizeParams resizeParams = new() { - Size = Request.Query.ContainsKey("size") && uint.TryParse(Request.Query["size"], out uint size) ? size : null, - Quality = Request.Query.ContainsKey("quality") && Enum.TryParse(Request.Query["quality"], true, out var quality) ? quality : DiscordImageResizeQuality.High, + Size = Request.Query.ContainsKey("size") && uint.TryParse(Request.Query["size"], out uint size) + ? size + : null, + Quality = Request.Query.ContainsKey("quality") && Enum.TryParse(Request.Query["quality"], true, out var quality) + ? quality + : DiscordImageResizeQuality.High, KeepAspectRatio = !Request.Query.ContainsKey("keepAspectRatio") || !bool.TryParse(Request.Query["keepAspectRatio"], out bool kar) || kar, Passthrough = Request.Query.ContainsKey("passthrough") && bool.TryParse(Request.Query["passthrough"], out bool pt) && pt, Animated = Request.Query.ContainsKey("animated") && bool.TryParse(Request.Query["animated"], out bool an) && an, - SpacebarAllowUpscale = Request.Query.ContainsKey("allowUpscale") && bool.TryParse(Request.Query["allowUpscale"], out bool au) && au + SpacebarAllowUpscale = Request.Query.ContainsKey("allowUpscale") && bool.TryParse(Request.Query["allowUpscale"], out bool au) && au, }; - - + double colorFuzz = 1; if (Request.Query.ContainsKey("colorFuzz")) double.TryParse(Request.Query["colorFuzz"], out colorFuzz); var img = await original.ToMagickImageCollectionAsync(); - int inFrames = img.Count; // using var edged = pads.RenderEdges(img, radius); foreach (var frame in img) { // get major color count frame.ColorFuzz = new Percentage(colorFuzz); } - - if(resizeParams.Size.HasValue) - img = dirs.Apply(img,resizeParams); + + if (resizeParams.Size.HasValue) + img = dirs.Apply(img, resizeParams); Console.WriteLine($"Generated colorFuzz for {Request.Path}, fuzz={colorFuzz}, inFrames={inFrames}, outFrames={img.Count}"); return new LruFileCache.Entry { @@ -145,5 +149,14 @@ public class IsPixelArtController(LruFileCache lfc, IFileSource fs, PixelArtDete }; }).ContinueWith(t => File(t.Result.Data, t.Result.MimeType)); } -} -*/ \ No newline at end of file + + [HttpGet("/defaultAvatar")] + public async Task DefaultAvatar() { + var re = new RainbowEnumerator(); + var img = new MagickImageCollection(); + + var ms = new MemoryStream(); + + return new FileContentResult(ms.ToArray(), "image/gif"); + } +} \ No newline at end of file diff --git a/extra/admin-api/Spacebar.Cdn/Controllers/StaticAssetController.cs b/extra/admin-api/Spacebar.Cdn/Controllers/StaticAssetController.cs index e2ad13777..5d9d0d781 100644 --- a/extra/admin-api/Spacebar.Cdn/Controllers/StaticAssetController.cs +++ b/extra/admin-api/Spacebar.Cdn/Controllers/StaticAssetController.cs @@ -1,6 +1,6 @@ -using System.Collections.Immutable; using ArcaneLibs.Extensions.Streams; using Microsoft.AspNetCore.Mvc; +using Microsoft.OpenApi; using Spacebar.AdminApi.TestClient.Services.Services; using Spacebar.Cdn.Extensions; using Spacebar.Interop.Cdn.Abstractions; @@ -28,8 +28,39 @@ public class StaticAssetController(LruFileCache lfc, IFileSource fs, DiscordImag { "6", "1276374a404452756f3c9cc2601508a5" }, { "7", "904bf9f1b61f53ef4a3b7a893afeabe3" }, }; - // [HttpGet("/embed/avatars/{userIndex}")] - // public async Task GetDefaultUserAvatar(string userIndex) { - // - // } + + // png only + [HttpGet("/embed/avatars/{userIndex}.{ext}")] + public async Task GetDefaultUserAvatar(string userIndex, string ext) { + + var cacheKey = Request.Path + Request.QueryString; + + DiscordImageResizeParams resizeParams = GetResizeParams(); + + var entry = await lfc.GetOrAdd(cacheKey, async () => { + var original = await fs.GetFile(Request.Path); + + if (Request.Query.Any()) { + using var img = await original.ToMagickImageCollectionAsync(); + dirs.Apply(img, resizeParams); + + var outStream = new MemoryStream(); + await img.WriteAsync(outStream, img.First().Format); + outStream.Position = 0; + + return new LruFileCache.Entry() { + Data = outStream.ReadToEnd().ToArray(), + MimeType = original.MimeType + }; + } + + return new LruFileCache.Entry() { + Data = original.Stream.ReadToEnd().ToArray(), + MimeType = original.MimeType + }; + }); + + // byte array with mime type result + return new FileContentResult(entry.Data, entry.MimeType); + } } \ No newline at end of file diff --git a/extra/admin-api/Spacebar.Cdn/Extensions/ImageController.cs b/extra/admin-api/Spacebar.Cdn/Extensions/ImageController.cs index 21531e90d..133eb8e1a 100644 --- a/extra/admin-api/Spacebar.Cdn/Extensions/ImageController.cs +++ b/extra/admin-api/Spacebar.Cdn/Extensions/ImageController.cs @@ -14,7 +14,7 @@ public class ImageController : ControllerBase { Passthrough = Request.Query.ContainsKey("passthrough") && bool.TryParse(Request.Query["passthrough"], out bool pt) && pt, Animated = Request.Query.ContainsKey("animated") && bool.TryParse(Request.Query["animated"], out bool an) && an, SpacebarAllowUpscale = Request.Query.ContainsKey("allowUpscale") && bool.TryParse(Request.Query["allowUpscale"], out bool au) && au, - SpacebarOptimiseGif = Request.Query.ContainsKey("optimiseGif") && bool.TryParse(Request.Query["optimiseGif"], out bool og) && og + SpacebarOptimiseGif = Request.Query.ContainsKey("optimiseGif") && bool.TryParse(Request.Query["optimiseGif"], out bool og) && og, }; } diff --git a/extra/admin-api/Spacebar.Cdn/Helpers/DefaultAvatarRenderer.cs b/extra/admin-api/Spacebar.Cdn/Helpers/DefaultAvatarRenderer.cs new file mode 100644 index 000000000..35b478f0d --- /dev/null +++ b/extra/admin-api/Spacebar.Cdn/Helpers/DefaultAvatarRenderer.cs @@ -0,0 +1,24 @@ +using ArcaneLibs.Extensions; +using ImageMagick; + +namespace Spacebar.AdminApi.TestClient.Services.Helpers; + +public class DefaultAvatarRenderer { + // Slower at runtime, but doesnt depend on filesystem + private static string GetDefaultAvatarSvg(byte r, byte g, byte b) { + return $""" + + + + + """; + } + + public static async Task GetDefaultAvatar(byte r, byte g, byte b, MagickFormat format = MagickFormat.Png, int size = 4096) { + var img = new MagickImageCollection(GetDefaultAvatarSvg(r, g, b).AsBytes().ToArray()); + var ms = new MemoryStream(); + await img.WriteAsync(ms); + ms.Position = 0; + return ms; + } +} \ No newline at end of file diff --git a/extra/admin-api/Spacebar.Cdn/Mimes.cs b/extra/admin-api/Spacebar.Cdn/Mimes.cs index 508e9a3b6..5a2814321 100644 --- a/extra/admin-api/Spacebar.Cdn/Mimes.cs +++ b/extra/admin-api/Spacebar.Cdn/Mimes.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using ImageMagick; namespace Spacebar.AdminApi.TestClient.Services; @@ -8,6 +9,20 @@ public static class Mimes { return mime; } + public static MagickFormat GetFormatForExtension(string extension) { + extension = extension.ToLower(); + // ban some values... + if (extension == "screenshot") throw new AccessViolationException("Disallowed extension: " + extension); + + var matchingFormat = Enum.GetNames(typeof(MagickFormat)).FirstOrDefault(f => f.ToLower() == extension); + if (string.IsNullOrWhiteSpace(matchingFormat)) throw new InvalidEnumArgumentException("Unknown format: " + extension); + if (Enum.TryParse(matchingFormat, out MagickFormat fmt)) { + return fmt; + } + + throw new InvalidEnumArgumentException("Unknown format: " + extension); + } + public static string GetMime(MagickFormat fmt) => fmt switch { MagickFormat.Png => "image/png", MagickFormat.Jpeg => "image/jpeg", diff --git a/extra/admin-api/Spacebar.Cdn/Properties/launchSettings.json b/extra/admin-api/Spacebar.Cdn/Properties/launchSettings.json index b9fe4cc30..f3ffeac96 100644 --- a/extra/admin-api/Spacebar.Cdn/Properties/launchSettings.json +++ b/extra/admin-api/Spacebar.Cdn/Properties/launchSettings.json @@ -18,7 +18,8 @@ "applicationUrl": "http://localhost:5114", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Local", - "LD_LIBRARY_PATH": "/home/Rory/git/spacebar/server-master/extra/admin-api/Spacebar.Cdn/result-lib/lib/" +// "LD_LIBRARY_PATH": "/home/Rory/git/spacebar/server-master/extra/admin-api/Spacebar.Cdn/result-lib/lib/", + "STORAGE_PATH": "./files" } } } diff --git a/extra/admin-api/Spacebar.Cdn/Services/CdnStorageRebuildService.cs b/extra/admin-api/Spacebar.Cdn/Services/CdnStorageRebuildService.cs deleted file mode 100644 index 0cf0ef1ab..000000000 --- a/extra/admin-api/Spacebar.Cdn/Services/CdnStorageRebuildService.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Spacebar.AdminApi.TestClient.Services.Services; - -public class CdnFileStorageRebuildService() { - -} \ No newline at end of file diff --git a/extra/admin-api/Spacebar.Cdn/Services/DiscordImageResizeService.cs b/extra/admin-api/Spacebar.Cdn/Services/DiscordImageResizeService.cs index 2a35c4049..382845e27 100644 --- a/extra/admin-api/Spacebar.Cdn/Services/DiscordImageResizeService.cs +++ b/extra/admin-api/Spacebar.Cdn/Services/DiscordImageResizeService.cs @@ -13,6 +13,10 @@ public class DiscordImageResizeParams { public bool SpacebarAllowUpscale { get; set; } = false; public bool SpacebarOptimiseGif { get; set; } = true; + + public string ToSerializedName() { + return $"{(Animated ? "a_" : "")}{Size}px_{Quality.ToString()}_u.{SpacebarAllowUpscale}_o.{SpacebarOptimiseGif}"; + } } public enum DiscordImageResizeQuality { diff --git a/extra/admin-api/Spacebar.Cdn/Spacebar.Cdn.csproj b/extra/admin-api/Spacebar.Cdn/Spacebar.Cdn.csproj index d3ea78a61..b56ff51c3 100644 --- a/extra/admin-api/Spacebar.Cdn/Spacebar.Cdn.csproj +++ b/extra/admin-api/Spacebar.Cdn/Spacebar.Cdn.csproj @@ -9,7 +9,7 @@ - +