diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index d3e042abaa..c39162250a 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -259,5 +259,11 @@ namespace MediaBrowser.Model.Configuration
///
/// The chapter image resolution.
public ImageResolution ChapterImageResolution { get; set; } = ImageResolution.MatchSource;
+
+ ///
+ /// Gets or sets the limit for parallel image encoding.
+ ///
+ /// The limit for parallel image encoding.
+ public int ParallelImageEncodingLimit { get; set; } = 0;
}
}
diff --git a/src/Jellyfin.Drawing/ImageProcessor.cs b/src/Jellyfin.Drawing/ImageProcessor.cs
index b381c9ae73..353a27b254 100644
--- a/src/Jellyfin.Drawing/ImageProcessor.cs
+++ b/src/Jellyfin.Drawing/ImageProcessor.cs
@@ -5,10 +5,12 @@ using System.IO;
using System.Linq;
using System.Net.Mime;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.MediaEncoding;
@@ -38,6 +40,8 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
private readonly IImageEncoder _imageEncoder;
private readonly IMediaEncoder _mediaEncoder;
+ private readonly SemaphoreSlim _parallelEncodingLimit;
+
private bool _disposed;
///
@@ -48,18 +52,28 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
/// The filesystem.
/// The image encoder.
/// The media encoder.
+ /// The configuration.
public ImageProcessor(
ILogger logger,
IServerApplicationPaths appPaths,
IFileSystem fileSystem,
IImageEncoder imageEncoder,
- IMediaEncoder mediaEncoder)
+ IMediaEncoder mediaEncoder,
+ IServerConfigurationManager config)
{
_logger = logger;
_fileSystem = fileSystem;
_imageEncoder = imageEncoder;
_mediaEncoder = mediaEncoder;
_appPaths = appPaths;
+
+ var semaphoreCount = config.Configuration.ParallelImageEncodingLimit;
+ if (semaphoreCount < 1)
+ {
+ semaphoreCount = 2 * Environment.ProcessorCount;
+ }
+
+ _parallelEncodingLimit = new(semaphoreCount, semaphoreCount);
}
private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images");
@@ -199,7 +213,18 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
{
if (!File.Exists(cacheFilePath))
{
- string resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat);
+ // Limit number of parallel (more precisely: concurrent) image encodings to prevent a high memory usage
+ await _parallelEncodingLimit.WaitAsync().ConfigureAwait(false);
+
+ string resultPath;
+ try
+ {
+ resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat);
+ }
+ finally
+ {
+ _parallelEncodingLimit.Release();
+ }
if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase))
{
@@ -563,6 +588,8 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
disposable.Dispose();
}
+ _parallelEncodingLimit?.Dispose();
+
_disposed = true;
}
}