Add WebP support for media

This commit is contained in:
Pat Hartl 2026-06-22 17:05:34 -05:00
parent 9bc44500fc
commit 516465ad1a
7 changed files with 33 additions and 13 deletions

View file

@ -9,6 +9,7 @@ using ZiggyCreatures.Caching.Fusion;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Webp;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Extensions;
using LANCommander.Server.Services.Extensions;
@ -214,7 +215,7 @@ namespace LANCommander.Server.Services
image.Mutate(context => context.Resize(resizeOptions));
if (media.Type.ValueIsIn(MediaType.Icon, MediaType.Logo, MediaType.PageImage) && media.MimeType == MediaTypeNames.Image.Png && HasTransparentPixels(image))
if (media.Type.ValueIsIn(MediaType.Icon, MediaType.Logo, MediaType.PageImage) && (media.MimeType == MediaTypeNames.Image.Png || media.MimeType == MediaTypeNames.Image.Webp) && HasTransparentPixels(image))
{
await image.SaveAsPngAsync(destination);
}
@ -371,8 +372,9 @@ namespace LANCommander.Server.Services
var mime = media.MimeType?.ToLowerInvariant();
var isPng = mime == "image/png";
var isJpeg = mime == "image/jpeg" || mime == "image/jpg";
var isWebp = mime == "image/webp";
if (!isPng && !isJpeg)
if (!isPng && !isJpeg && !isWebp)
continue;
int width;
@ -391,7 +393,7 @@ namespace LANCommander.Server.Services
var oversized = options.Downscale && Math.Max(width, height) > options.MaxLongEdge;
var willConvert = isPng && options.ConvertPngToJpeg && media.Type != MediaType.Logo && media.Type != MediaType.Icon;
var willRecompress = isJpeg && options.RecompressJpeg;
var willRecompress = (isJpeg && options.RecompressJpeg) || (isWebp && options.RecompressWebp);
if (!willConvert && !oversized && !willRecompress)
continue;
@ -438,8 +440,9 @@ namespace LANCommander.Server.Services
var mime = media.MimeType?.ToLowerInvariant();
var isPng = mime == "image/png";
var isJpeg = mime == "image/jpeg" || mime == "image/jpg";
var isWebp = mime == "image/webp";
if (!isPng && !isJpeg)
if (!isPng && !isJpeg && !isWebp)
return result;
result.BeforeBytes = new FileInfo(path).Length;
@ -479,14 +482,17 @@ namespace LANCommander.Server.Services
}
var reencodeJpeg = isJpeg && (options.RecompressJpeg || changed);
var reencodeWebp = isWebp && (options.RecompressWebp || changed);
if (!convertToJpeg && !reencodeJpeg && !(isPng && changed))
if (!convertToJpeg && !reencodeJpeg && !reencodeWebp && !(isPng && changed))
return result;
var tempPath = path + ".optimizing";
if (convertToJpeg || isJpeg)
await image.SaveAsJpegAsync(tempPath, new JpegEncoder { Quality = options.JpegQuality });
else if (isWebp)
await image.SaveAsWebpAsync(tempPath, new WebpEncoder { Quality = options.WebpQuality });
else
await image.SaveAsPngAsync(tempPath);

View file

@ -10,6 +10,8 @@ namespace LANCommander.Server.Services.Models
public bool Downscale { get; set; } = true;
public int MaxLongEdge { get; set; } = 3840;
public bool RecompressJpeg { get; set; } = false;
public bool RecompressWebp { get; set; } = false;
public int WebpQuality { get; set; } = 80;
public bool StripMetadata { get; set; } = true;
}

View file

@ -6,7 +6,7 @@
@inject IOptions<Settings.Settings> Settings
@inject IMessageService MessageService
<InputFile id="@("AvatarUpload")" OnChange="(e) => UploadAvatar(e)" hidden accept=".jpg,jpeg,.png" />
<InputFile id="@("AvatarUpload")" OnChange="(e) => UploadAvatar(e)" hidden accept=".jpg,jpeg,.png,.webp" />
<label for="AvatarUpload" style="cursor: pointer;">
@if (String.IsNullOrWhiteSpace(AvatarUrl))
@ -38,7 +38,8 @@
string[] ValidMimeTypes = new string[]
{
"image/png",
"image/jpeg"
"image/jpeg",
"image/webp"
};
protected override async Task OnParametersSetAsync()

View file

@ -34,7 +34,7 @@
<div class="game-detail-preview__media-overlay game-detail-preview__media-overlay--hero">
<div class="game-detail-preview__media-overlay-actions">
@{ var bgInputId = "bg-upload-" + Guid.NewGuid(); }
<InputFile id="@bgInputId" OnChange="(e) => UploadMediaByType(e, MediaType.Background)" hidden accept=".jpg,.jpeg,.png,.gif" />
<InputFile id="@bgInputId" OnChange="(e) => UploadMediaByType(e, MediaType.Background)" hidden accept=".jpg,.jpeg,.png,.gif,.webp" />
<label class="game-detail-preview__overlay-btn" for="@bgInputId" title="Upload Background">
<Icon Type="@IconType.Outline.Upload" />
</label>
@ -71,7 +71,7 @@
<div class="game-detail-preview__media-overlay game-detail-preview__media-overlay--logo">
<div class="game-detail-preview__media-overlay-actions">
@{ var logoInputId = "logo-upload-" + Guid.NewGuid(); }
<InputFile id="@logoInputId" OnChange="(e) => UploadMediaByType(e, MediaType.Logo)" hidden accept=".jpg,.jpeg,.png,.gif" />
<InputFile id="@logoInputId" OnChange="(e) => UploadMediaByType(e, MediaType.Logo)" hidden accept=".jpg,.jpeg,.png,.gif,.webp" />
<label class="game-detail-preview__overlay-btn" for="@logoInputId" title="Upload Logo">
<Icon Type="@IconType.Outline.Upload" />
</label>
@ -106,7 +106,7 @@
<div class="game-detail-preview__media-section-header">
<span class="game-detail-preview__media-section-title">Media</span>
<div class="game-detail-preview__media-section-actions">
<InputFile id="@_ssInputId" OnChange="(e) => UploadMediaByType(e, MediaType.Screenshot)" hidden accept=".jpg,.jpeg,.png,.gif" multiple />
<InputFile id="@_ssInputId" OnChange="(e) => UploadMediaByType(e, MediaType.Screenshot)" hidden accept=".jpg,.jpeg,.png,.gif,.webp" multiple />
<InputFile id="@_vidInputId" OnChange="(e) => UploadMediaByType(e, MediaType.Video)" hidden accept=".mp4,.webm,.mkv" multiple />
<Dropdown>
@ -208,7 +208,7 @@
<div class="game-detail-preview__media-overlay game-detail-preview__media-overlay--cover">
<div class="game-detail-preview__media-overlay-actions">
@{ var coverInputId = "cover-upload-" + Guid.NewGuid(); }
<InputFile id="@coverInputId" OnChange="(e) => UploadMediaByType(e, MediaType.Cover)" hidden accept=".jpg,.jpeg,.png,.gif,.apng,.mp4,.webm,.mkv" />
<InputFile id="@coverInputId" OnChange="(e) => UploadMediaByType(e, MediaType.Cover)" hidden accept=".jpg,.jpeg,.png,.gif,.webp,.apng,.mp4,.webm,.mkv" />
<label class="game-detail-preview__overlay-btn" for="@coverInputId" title="Upload Cover">
<Icon Type="@IconType.Outline.Upload" />
</label>
@ -318,6 +318,7 @@
MediaTypeNames.Image.Png,
MediaTypeNames.Image.Jpeg,
MediaTypeNames.Image.Gif,
MediaTypeNames.Image.Webp,
"image/apng",
];
@ -331,6 +332,7 @@
MediaTypeNames.Image.Png,
MediaTypeNames.Image.Jpeg,
MediaTypeNames.Image.Gif,
MediaTypeNames.Image.Webp,
"image/apng",
"video/mp4",
"video/webm",

View file

@ -64,7 +64,7 @@
@{
var fileInputId = Guid.NewGuid();
}
<InputFile id="@fileInputId" OnChange="(e) => UploadMedia(e, row)" hidden accept=".jpg,.jpeg,.png,.gif,.pdf,.ico,.mp4,.webm,.mkv"/>
<InputFile id="@fileInputId" OnChange="(e) => UploadMedia(e, row)" hidden accept=".jpg,.jpeg,.png,.gif,.webp,.pdf,.ico,.mp4,.webm,.mkv"/>
<label class="ant-btn ant-btn-text ant-btn-icon-only" for="@fileInputId">
<Icon Type="@IconType.Outline.Upload"/>
</label>
@ -120,6 +120,7 @@
MediaTypeNames.Image.Png,
MediaTypeNames.Image.Jpeg,
MediaTypeNames.Image.Gif,
MediaTypeNames.Image.Webp,
MediaTypeNames.Application.Pdf,
MediaTypeNames.Image.Icon,
"video/mp4",

View file

@ -24,12 +24,18 @@
<FormItem Label="Recompress existing JPEGs (lossy)">
<Switch @bind-Value="@Options.RecompressJpeg" />
</FormItem>
<FormItem Label="Recompress existing WebP (lossy)">
<Switch @bind-Value="@Options.RecompressWebp" />
</FormItem>
<FormItem Label="Strip metadata (EXIF/ICC/XMP)">
<Switch @bind-Value="@Options.StripMetadata" />
</FormItem>
<FormItem Label="JPEG quality">
<AntDesign.InputNumber @bind-Value="@Options.JpegQuality" Min="1" Max="100" />
</FormItem>
<FormItem Label="WebP quality">
<AntDesign.InputNumber @bind-Value="@Options.WebpQuality" Min="1" Max="100" />
</FormItem>
<FormItem Label="Max long edge (px)">
<AntDesign.InputNumber @bind-Value="@Options.MaxLongEdge" Min="256" />
</FormItem>

View file

@ -61,7 +61,9 @@ public class SteamClient
var webAssetUri = GetWebAssetUri(appId, webAssetType);
var response = await HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, webAssetUri));
var exists = response.Content.Headers.ContentType.MediaType == MediaTypeNames.Image.Jpeg || response.Content.Headers.ContentType.MediaType == "image/png";
var exists = response.Content.Headers.ContentType.MediaType == MediaTypeNames.Image.Jpeg
|| response.Content.Headers.ContentType.MediaType == "image/png"
|| response.Content.Headers.ContentType.MediaType == MediaTypeNames.Image.Webp;
return (exists, response.Content.Headers.ContentType.MediaType);
}