diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index 18b413964..11256dafd 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -395,7 +395,13 @@ namespace Emby.Drawing
public string GetImageBlurHash(string path)
{
var size = GetImageDimensions(path);
- if (size.Width <= 0 || size.Height <= 0)
+ return GetImageBlurHash(path, size);
+ }
+
+ ///
+ public string GetImageBlurHash(string path, ImageDimensions imageDimensions)
+ {
+ if (imageDimensions.Width <= 0 || imageDimensions.Height <= 0)
{
return string.Empty;
}
@@ -403,8 +409,8 @@ namespace Emby.Drawing
// We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance.
// One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width.
// See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components
- float xCompF = MathF.Sqrt(16.0f * size.Width / size.Height);
- float yCompF = xCompF * size.Height / size.Width;
+ float xCompF = MathF.Sqrt(16.0f * imageDimensions.Width / imageDimensions.Height);
+ float yCompF = xCompF * imageDimensions.Height / imageDimensions.Width;
int xComp = Math.Min((int)xCompF + 1, 9);
int yComp = Math.Min((int)yCompF + 1, 9);
@@ -439,47 +445,46 @@ namespace Emby.Drawing
.ToString("N", CultureInfo.InvariantCulture);
}
- private async Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
+ private Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
{
- var inputFormat = Path.GetExtension(originalImagePath)
- .TrimStart('.')
- .Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
+ var inputFormat = Path.GetExtension(originalImagePath.AsSpan()).TrimStart('.').ToString();
// These are just jpg files renamed as tbn
if (string.Equals(inputFormat, "tbn", StringComparison.OrdinalIgnoreCase))
{
- return (originalImagePath, dateModified);
+ return Task.FromResult((originalImagePath, dateModified));
}
- if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat))
- {
- try
- {
- string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
+ // TODO _mediaEncoder.ConvertImage is not implemented
+ // if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat))
+ // {
+ // try
+ // {
+ // string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
+ //
+ // string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
+ // var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
+ //
+ // var file = _fileSystem.GetFileInfo(outputPath);
+ // if (!file.Exists)
+ // {
+ // await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
+ // dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
+ // }
+ // else
+ // {
+ // dateModified = file.LastWriteTimeUtc;
+ // }
+ //
+ // originalImagePath = outputPath;
+ // }
+ // catch (Exception ex)
+ // {
+ // _logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath);
+ // }
+ // }
- string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
- var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
-
- var file = _fileSystem.GetFileInfo(outputPath);
- if (!file.Exists)
- {
- await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
- dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
- }
- else
- {
- dateModified = file.LastWriteTimeUtc;
- }
-
- originalImagePath = outputPath;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath);
- }
- }
-
- return (originalImagePath, dateModified);
+ return Task.FromResult((originalImagePath, dateModified));
}
///
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index d6754ad4a..c54945c93 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -1860,7 +1860,9 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(item));
}
- var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
+ var outdated = forceUpdate
+ ? item.ImageInfos.Where(i => i.Path != null).ToArray()
+ : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
// Skip image processing if current or live tv source
if (outdated.Length == 0 || item.SourceType != SourceType.Library)
{
@@ -1883,7 +1885,7 @@ namespace Emby.Server.Implementations.Library
_logger.LogWarning("Cannot get image index for {ImagePath}", img.Path);
continue;
}
- catch (Exception ex) when (ex is InvalidOperationException || ex is IOException)
+ catch (Exception ex) when (ex is InvalidOperationException or IOException)
{
_logger.LogWarning(ex, "Cannot fetch image from {ImagePath}", img.Path);
continue;
@@ -1895,23 +1897,24 @@ namespace Emby.Server.Implementations.Library
}
}
+ ImageDimensions size;
try
{
- ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
+ size = _imageProcessor.GetImageDimensions(item, image);
image.Width = size.Width;
image.Height = size.Height;
}
catch (Exception ex)
{
_logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path);
+ size = new ImageDimensions(0, 0);
image.Width = 0;
image.Height = 0;
- continue;
}
try
{
- image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path);
+ image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path, size);
}
catch (Exception ex)
{
diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
index 495c5b860..4de4b231d 100644
--- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
+++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
@@ -20,6 +20,7 @@
+
diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs
index 2358fe623..687528231 100644
--- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs
+++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs
@@ -10,7 +10,7 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Drawing;
using Microsoft.Extensions.Logging;
using SkiaSharp;
-using static Jellyfin.Drawing.Skia.SkiaHelper;
+using SKSvg = SkiaSharp.Extended.Svg.SKSvg;
namespace Jellyfin.Drawing.Skia
{
@@ -19,8 +19,7 @@ namespace Jellyfin.Drawing.Skia
///
public class SkiaEncoder : IImageEncoder
{
- private static readonly HashSet _transparentImageTypes
- = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
+ private static readonly HashSet _transparentImageTypes = new(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
private readonly ILogger _logger;
private readonly IApplicationPaths _appPaths;
@@ -71,7 +70,7 @@ namespace Jellyfin.Drawing.Skia
///
public IReadOnlyCollection SupportedOutputFormats
- => new HashSet() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
+ => new HashSet { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
///
/// Check if the native lib is available.
@@ -109,9 +108,7 @@ namespace Jellyfin.Drawing.Skia
}
///
- /// The path is null.
/// The path is not valid.
- /// The file at the specified path could not be used to generate a codec.
public ImageDimensions GetImageSize(string path)
{
if (!File.Exists(path))
@@ -119,12 +116,27 @@ namespace Jellyfin.Drawing.Skia
throw new FileNotFoundException("File not found", path);
}
+ var extension = Path.GetExtension(path.AsSpan());
+ if (extension.Equals(".svg", StringComparison.OrdinalIgnoreCase))
+ {
+ var svg = new SKSvg();
+ svg.Load(path);
+ return new ImageDimensions(Convert.ToInt32(svg.Picture.CullRect.Width), Convert.ToInt32(svg.Picture.CullRect.Height));
+ }
+
using var codec = SKCodec.Create(path, out SKCodecResult result);
- EnsureSuccess(result);
-
- var info = codec.Info;
-
- return new ImageDimensions(info.Width, info.Height);
+ switch (result)
+ {
+ case SKCodecResult.Success:
+ var info = codec.Info;
+ return new ImageDimensions(info.Width, info.Height);
+ case SKCodecResult.Unimplemented:
+ _logger.LogDebug("Image format not supported: {FilePath}", path);
+ return new ImageDimensions(0, 0);
+ default:
+ _logger.LogError("Unable to determine image dimensions for {FilePath}: {SkCodecResult}", path, result);
+ return new ImageDimensions(0, 0);
+ }
}
///
@@ -138,6 +150,13 @@ namespace Jellyfin.Drawing.Skia
throw new ArgumentNullException(nameof(path));
}
+ var extension = Path.GetExtension(path.AsSpan()).TrimStart('.');
+ if (!SupportedInputFormats.Contains(extension, StringComparison.OrdinalIgnoreCase))
+ {
+ _logger.LogDebug("Unable to compute blur hash due to unsupported format: {ImagePath}", path);
+ return string.Empty;
+ }
+
// Any larger than 128x128 is too slow and there's no visually discernible difference
return BlurHashEncoder.Encode(xComp, yComp, path, 128, 128);
}
@@ -378,6 +397,13 @@ namespace Jellyfin.Drawing.Skia
throw new ArgumentException("String can't be empty.", nameof(outputPath));
}
+ var inputFormat = Path.GetExtension(inputPath.AsSpan()).TrimStart('.');
+ if (!SupportedInputFormats.Contains(inputFormat, StringComparison.OrdinalIgnoreCase))
+ {
+ _logger.LogDebug("Unable to encode image due to unsupported format: {ImagePath}", inputPath);
+ return inputPath;
+ }
+
var skiaOutputFormat = GetImageFormat(outputFormat);
var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor);
diff --git a/Jellyfin.Drawing.Skia/SkiaHelper.cs b/Jellyfin.Drawing.Skia/SkiaHelper.cs
index 35dcebdab..c001c32b8 100644
--- a/Jellyfin.Drawing.Skia/SkiaHelper.cs
+++ b/Jellyfin.Drawing.Skia/SkiaHelper.cs
@@ -8,19 +8,6 @@ namespace Jellyfin.Drawing.Skia
///
public static class SkiaHelper
{
- ///
- /// Ensures the result is a success
- /// by throwing an exception when that's not the case.
- ///
- /// The result returned by Skia.
- public static void EnsureSuccess(SKCodecResult result)
- {
- if (result != SKCodecResult.Success)
- {
- throw new SkiaCodecException(result);
- }
- }
-
///
/// Gets the next valid image as a bitmap.
///
diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
index 03882a0b9..e5ce0aa21 100644
--- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs
+++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
@@ -50,6 +50,14 @@ namespace MediaBrowser.Controller.Drawing
/// BlurHash.
string GetImageBlurHash(string path);
+ ///
+ /// Gets the blurhash of the image.
+ ///
+ /// Path to the image file.
+ /// The image dimensions.
+ /// BlurHash.
+ string GetImageBlurHash(string path, ImageDimensions imageDimensions);
+
///
/// Gets the image cache tag.
///