瀏覽代碼

Merge pull request #7946 from cvium/svg

(cherry picked from commit 4ebe70cf6a12be4f4eae0b815a269a483ee238bb)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
Cody Robibero 3 年之前
父節點
當前提交
7f1223016d

+ 44 - 39
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);
+        }
+
+        /// <inheritdoc />
+        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);
-            }
-
-            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);
-                }
-            }
-
-            return (originalImagePath, dateModified);
+                return Task.FromResult((originalImagePath, dateModified));
+            }
+
+            // 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);
+            //     }
+            // }
+
+            return Task.FromResult((originalImagePath, dateModified));
         }
 
         /// <summary>

+ 8 - 5
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)
                 {

+ 1 - 0
Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj

@@ -20,6 +20,7 @@
     <PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.2.0" />
     <PackageReference Include="SkiaSharp" Version="2.88.1-preview.1" />
     <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1-preview.1" />
+    <PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
   </ItemGroup>
 
   <ItemGroup>

+ 37 - 11
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
     /// </summary>
     public class SkiaEncoder : IImageEncoder
     {
-        private static readonly HashSet<string> _transparentImageTypes
-            = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
+        private static readonly HashSet<string> _transparentImageTypes = new(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
 
         private readonly ILogger<SkiaEncoder> _logger;
         private readonly IApplicationPaths _appPaths;
@@ -71,7 +70,7 @@ namespace Jellyfin.Drawing.Skia
 
         /// <inheritdoc/>
         public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
-            => new HashSet<ImageFormat>() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
+            => new HashSet<ImageFormat> { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
 
         /// <summary>
         /// Check if the native lib is available.
@@ -109,9 +108,7 @@ namespace Jellyfin.Drawing.Skia
         }
 
         /// <inheritdoc />
-        /// <exception cref="ArgumentNullException">The path is null.</exception>
         /// <exception cref="FileNotFoundException">The path is not valid.</exception>
-        /// <exception cref="SkiaCodecException">The file at the specified path could not be used to generate a codec.</exception>
         public ImageDimensions GetImageSize(string path)
         {
             if (!File.Exists(path))
@@ -119,12 +116,27 @@ namespace Jellyfin.Drawing.Skia
                 throw new FileNotFoundException("File not found", path);
             }
 
-            using var codec = SKCodec.Create(path, out SKCodecResult result);
-            EnsureSuccess(result);
-
-            var info = codec.Info;
+            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));
+            }
 
-            return new ImageDimensions(info.Width, info.Height);
+            using var codec = SKCodec.Create(path, out SKCodecResult result);
+            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);
+            }
         }
 
         /// <inheritdoc />
@@ -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);

+ 0 - 13
Jellyfin.Drawing.Skia/SkiaHelper.cs

@@ -8,19 +8,6 @@ namespace Jellyfin.Drawing.Skia
     /// </summary>
     public static class SkiaHelper
     {
-        /// <summary>
-        /// Ensures the result is a success
-        /// by throwing an exception when that's not the case.
-        /// </summary>
-        /// <param name="result">The result returned by Skia.</param>
-        public static void EnsureSuccess(SKCodecResult result)
-        {
-            if (result != SKCodecResult.Success)
-            {
-                throw new SkiaCodecException(result);
-            }
-        }
-
         /// <summary>
         /// Gets the next valid image as a bitmap.
         /// </summary>

+ 8 - 0
MediaBrowser.Controller/Drawing/IImageProcessor.cs

@@ -50,6 +50,14 @@ namespace MediaBrowser.Controller.Drawing
         /// <returns>BlurHash.</returns>
         string GetImageBlurHash(string path);
 
+        /// <summary>
+        /// Gets the blurhash of the image.
+        /// </summary>
+        /// <param name="path">Path to the image file.</param>
+        /// <param name="imageDimensions">The image dimensions.</param>
+        /// <returns>BlurHash.</returns>
+        string GetImageBlurHash(string path, ImageDimensions imageDimensions);
+
         /// <summary>
         /// Gets the image cache tag.
         /// </summary>