Simplify image processing by removing image enhancers

This commit is contained in:
Bond_009 2020-01-21 20:26:30 +01:00
parent d4de78693f
commit ddf9b38799
13 changed files with 103 additions and 522 deletions

View File

@ -17,4 +17,16 @@
<Compile Include="..\SharedVersion.cs" /> <Compile Include="..\SharedVersion.cs" />
</ItemGroup> </ItemGroup>
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project> </Project>

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller; using MediaBrowser.Controller;
@ -11,10 +10,8 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -24,7 +21,7 @@ namespace Emby.Drawing
/// <summary> /// <summary>
/// Class ImageProcessor. /// Class ImageProcessor.
/// </summary> /// </summary>
public class ImageProcessor : IImageProcessor, IDisposable public sealed class ImageProcessor : IImageProcessor, IDisposable
{ {
// Increment this when there's a change requiring caches to be invalidated // Increment this when there's a change requiring caches to be invalidated
private const string Version = "3"; private const string Version = "3";
@ -32,28 +29,24 @@ namespace Emby.Drawing
private static readonly HashSet<string> _transparentImageTypes private static readonly HashSet<string> _transparentImageTypes
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" }; = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths; private readonly IServerApplicationPaths _appPaths;
private IImageEncoder _imageEncoder; private readonly IImageEncoder _imageEncoder;
private readonly Func<ILibraryManager> _libraryManager; private readonly Func<ILibraryManager> _libraryManager;
private readonly Func<IMediaEncoder> _mediaEncoder; private readonly Func<IMediaEncoder> _mediaEncoder;
private readonly Dictionary<string, LockInfo> _locks = new Dictionary<string, LockInfo>();
private bool _disposed = false; private bool _disposed = false;
/// <summary> /// <summary>
/// /// Initializes a new instance of the <see cref="ImageProcessor"/> class.
/// </summary> /// </summary>
/// <param name="logger"></param> /// <param name="logger">The logger.</param>
/// <param name="appPaths"></param> /// <param name="appPaths">The server application paths.</param>
/// <param name="fileSystem"></param> /// <param name="fileSystem">The filesystem.</param>
/// <param name="imageEncoder"></param> /// <param name="imageEncoder">The image encoder.</param>
/// <param name="libraryManager"></param> /// <param name="libraryManager">The library manager.</param>
/// <param name="mediaEncoder"></param> /// <param name="mediaEncoder">The media encoder.</param>
public ImageProcessor( public ImageProcessor(
ILogger<ImageProcessor> logger, ILogger<ImageProcessor> logger,
IServerApplicationPaths appPaths, IServerApplicationPaths appPaths,
@ -68,16 +61,10 @@ namespace Emby.Drawing
_libraryManager = libraryManager; _libraryManager = libraryManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_appPaths = appPaths; _appPaths = appPaths;
ImageEnhancers = Array.Empty<IImageEnhancer>();
ImageHelper.ImageProcessor = this;
} }
private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images"); private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images");
private string EnhancedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "enhanced-images");
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyCollection<string> SupportedInputFormats => public IReadOnlyCollection<string> SupportedInputFormats =>
new HashSet<string>(StringComparer.OrdinalIgnoreCase) new HashSet<string>(StringComparer.OrdinalIgnoreCase)
@ -90,9 +77,7 @@ namespace Emby.Drawing
"aiff", "aiff",
"cr2", "cr2",
"crw", "crw",
"nef",
// Remove until supported
//"nef",
"orf", "orf",
"pef", "pef",
"arw", "arw",
@ -111,19 +96,9 @@ namespace Emby.Drawing
"wbmp" "wbmp"
}; };
/// <inheritdoc />
public IReadOnlyCollection<IImageEnhancer> ImageEnhancers { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation; public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation;
/// <inheritdoc />
public IImageEncoder ImageEncoder
{
get => _imageEncoder;
set => _imageEncoder = value ?? throw new ArgumentNullException(nameof(value));
}
/// <inheritdoc /> /// <inheritdoc />
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream) public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
{ {
@ -151,6 +126,8 @@ namespace Emby.Drawing
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
} }
var libraryManager = _libraryManager();
ItemImageInfo originalImage = options.Image; ItemImageInfo originalImage = options.Image;
BaseItem item = options.Item; BaseItem item = options.Item;
@ -158,9 +135,10 @@ namespace Emby.Drawing
{ {
if (item == null) if (item == null)
{ {
item = _libraryManager().GetItemById(options.ItemId); item = libraryManager.GetItemById(options.ItemId);
} }
originalImage = await _libraryManager().ConvertImageToLocal(item, originalImage, options.ImageIndex).ConfigureAwait(false);
originalImage = await libraryManager.ConvertImageToLocal(item, originalImage, options.ImageIndex).ConfigureAwait(false);
} }
string originalImagePath = originalImage.Path; string originalImagePath = originalImage.Path;
@ -187,27 +165,6 @@ namespace Emby.Drawing
dateModified = supportedImageInfo.dateModified; dateModified = supportedImageInfo.dateModified;
bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath)); bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath));
if (options.Enhancers.Count > 0)
{
if (item == null)
{
item = _libraryManager().GetItemById(options.ItemId);
}
var tuple = await GetEnhancedImage(new ItemImageInfo
{
DateModified = dateModified,
Type = originalImage.Type,
Path = originalImagePath
}, requiresTransparency, item, options.ImageIndex, options.Enhancers, CancellationToken.None).ConfigureAwait(false);
originalImagePath = tuple.path;
dateModified = tuple.dateModified;
requiresTransparency = tuple.transparent;
// TODO: Get this info
originalImageSize = null;
}
bool autoOrient = false; bool autoOrient = false;
ImageOrientation? orientation = null; ImageOrientation? orientation = null;
if (item is Photo photo) if (item is Photo photo)
@ -240,12 +197,6 @@ namespace Emby.Drawing
ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency); ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency);
string cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer); string cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer);
CheckDisposed();
LockInfo lockInfo = GetLock(cacheFilePath);
await lockInfo.Lock.WaitAsync().ConfigureAwait(false);
try try
{ {
if (!File.Exists(cacheFilePath)) if (!File.Exists(cacheFilePath))
@ -271,10 +222,6 @@ namespace Emby.Drawing
_logger.LogError(ex, "Error encoding image"); _logger.LogError(ex, "Error encoding image");
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
} }
finally
{
ReleaseLock(cacheFilePath, lockInfo);
}
} }
private ImageFormat GetOutputFormat(IReadOnlyCollection<ImageFormat> clientSupportedFormats, bool requiresTransparency) private ImageFormat GetOutputFormat(IReadOnlyCollection<ImageFormat> clientSupportedFormats, bool requiresTransparency)
@ -306,20 +253,18 @@ namespace Emby.Drawing
} }
private string GetMimeType(ImageFormat format, string path) private string GetMimeType(ImageFormat format, string path)
{ => format switch
switch(format)
{ {
case ImageFormat.Bmp: return MimeTypes.GetMimeType("i.bmp"); ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
case ImageFormat.Gif: return MimeTypes.GetMimeType("i.gif"); ImageFormat.Gif => MimeTypes.GetMimeType("i.gif"),
case ImageFormat.Jpg: return MimeTypes.GetMimeType("i.jpg"); ImageFormat.Jpg => MimeTypes.GetMimeType("i.jpg"),
case ImageFormat.Png: return MimeTypes.GetMimeType("i.png"); ImageFormat.Png => MimeTypes.GetMimeType("i.png"),
case ImageFormat.Webp: return MimeTypes.GetMimeType("i.webp"); ImageFormat.Webp => MimeTypes.GetMimeType("i.webp"),
default: return MimeTypes.GetMimeType(path); _ => MimeTypes.GetMimeType(path)
} };
}
/// <summary> /// <summary>
/// Gets the cache file path based on a set of parameters /// Gets the cache file path based on a set of parameters.
/// </summary> /// </summary>
private string GetCacheFilePath(string originalPath, ImageDimensions outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, int? blur, string backgroundColor, string foregroundLayer) private string GetCacheFilePath(string originalPath, ImageDimensions outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, int? blur, string backgroundColor, string foregroundLayer)
{ {
@ -401,11 +346,7 @@ namespace Emby.Drawing
/// <inheritdoc /> /// <inheritdoc />
public string GetImageCacheTag(BaseItem item, ItemImageInfo image) public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
{ => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
var supportedEnhancers = GetSupportedEnhancers(item, image.Type).ToArray();
return GetImageCacheTag(item, image, supportedEnhancers);
}
/// <inheritdoc /> /// <inheritdoc />
public string GetImageCacheTag(BaseItem item, ChapterInfo chapter) public string GetImageCacheTag(BaseItem item, ChapterInfo chapter)
@ -425,26 +366,6 @@ namespace Emby.Drawing
} }
} }
/// <inheritdoc />
public string GetImageCacheTag(BaseItem item, ItemImageInfo image, IReadOnlyCollection<IImageEnhancer> imageEnhancers)
{
string originalImagePath = image.Path;
DateTime dateModified = image.DateModified;
ImageType imageType = image.Type;
// Optimization
if (imageEnhancers.Count == 0)
{
return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
// Cache name is created with supported enhancers combined with the last config change so we pick up new config changes
var cacheKeys = imageEnhancers.Select(i => i.GetConfigurationCacheKey(item, imageType)).ToList();
cacheKeys.Add(originalImagePath + dateModified.Ticks);
return string.Join("|", cacheKeys).GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
{ {
var inputFormat = Path.GetExtension(originalImagePath) var inputFormat = Path.GetExtension(originalImagePath)
@ -488,154 +409,6 @@ namespace Emby.Drawing
return (originalImagePath, dateModified); return (originalImagePath, dateModified);
} }
/// <inheritdoc />
public async Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex)
{
var enhancers = GetSupportedEnhancers(item, imageType).ToArray();
ItemImageInfo imageInfo = item.GetImageInfo(imageType, imageIndex);
bool inputImageSupportsTransparency = SupportsTransparency(imageInfo.Path);
var result = await GetEnhancedImage(imageInfo, inputImageSupportsTransparency, item, imageIndex, enhancers, CancellationToken.None);
return result.path;
}
private async Task<(string path, DateTime dateModified, bool transparent)> GetEnhancedImage(
ItemImageInfo image,
bool inputImageSupportsTransparency,
BaseItem item,
int imageIndex,
IReadOnlyCollection<IImageEnhancer> enhancers,
CancellationToken cancellationToken)
{
var originalImagePath = image.Path;
var dateModified = image.DateModified;
var imageType = image.Type;
try
{
var cacheGuid = GetImageCacheTag(item, image, enhancers);
// Enhance if we have enhancers
var enhancedImageInfo = await GetEnhancedImageInternal(originalImagePath, item, imageType, imageIndex, enhancers, cacheGuid, cancellationToken).ConfigureAwait(false);
string enhancedImagePath = enhancedImageInfo.path;
// If the path changed update dateModified
if (!string.Equals(enhancedImagePath, originalImagePath, StringComparison.OrdinalIgnoreCase))
{
var treatmentRequiresTransparency = enhancedImageInfo.transparent;
return (enhancedImagePath, _fileSystem.GetLastWriteTimeUtc(enhancedImagePath), treatmentRequiresTransparency);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error enhancing image");
}
return (originalImagePath, dateModified, inputImageSupportsTransparency);
}
/// <summary>
/// Gets the enhanced image internal.
/// </summary>
/// <param name="originalImagePath">The original image path.</param>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <param name="supportedEnhancers">The supported enhancers.</param>
/// <param name="cacheGuid">The cache unique identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;System.String&gt;.</returns>
/// <exception cref="ArgumentNullException">
/// originalImagePath
/// or
/// item
/// </exception>
private async Task<(string path, bool transparent)> GetEnhancedImageInternal(
string originalImagePath,
BaseItem item,
ImageType imageType,
int imageIndex,
IReadOnlyCollection<IImageEnhancer> supportedEnhancers,
string cacheGuid,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(originalImagePath))
{
throw new ArgumentNullException(nameof(originalImagePath));
}
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
var treatmentRequiresTransparency = false;
foreach (var enhancer in supportedEnhancers)
{
if (!treatmentRequiresTransparency)
{
treatmentRequiresTransparency = enhancer.GetEnhancedImageInfo(item, originalImagePath, imageType, imageIndex).RequiresTransparency;
}
}
// All enhanced images are saved as png to allow transparency
string cacheExtension = _imageEncoder.SupportedOutputFormats.Contains(ImageFormat.Webp) ?
".webp" :
(treatmentRequiresTransparency ? ".png" : ".jpg");
string enhancedImagePath = GetCachePath(EnhancedImageCachePath, cacheGuid + cacheExtension);
LockInfo lockInfo = GetLock(enhancedImagePath);
await lockInfo.Lock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
// Check again in case of contention
if (File.Exists(enhancedImagePath))
{
return (enhancedImagePath, treatmentRequiresTransparency);
}
Directory.CreateDirectory(Path.GetDirectoryName(enhancedImagePath));
await ExecuteImageEnhancers(supportedEnhancers, originalImagePath, enhancedImagePath, item, imageType, imageIndex).ConfigureAwait(false);
return (enhancedImagePath, treatmentRequiresTransparency);
}
finally
{
ReleaseLock(enhancedImagePath, lockInfo);
}
}
/// <summary>
/// Executes the image enhancers.
/// </summary>
/// <param name="imageEnhancers">The image enhancers.</param>
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <returns>Task{EnhancedImage}.</returns>
private static async Task ExecuteImageEnhancers(IEnumerable<IImageEnhancer> imageEnhancers, string inputPath, string outputPath, BaseItem item, ImageType imageType, int imageIndex)
{
// Run the enhancers sequentially in order of priority
foreach (var enhancer in imageEnhancers)
{
await enhancer.EnhanceImageAsync(item, inputPath, outputPath, imageType, imageIndex).ConfigureAwait(false);
// Feed the output into the next enhancer as input
inputPath = outputPath;
}
}
/// <summary> /// <summary>
/// Gets the cache path. /// Gets the cache path.
/// </summary> /// </summary>
@ -648,7 +421,7 @@ namespace Emby.Drawing
/// or /// or
/// uniqueName /// uniqueName
/// or /// or
/// fileExtension /// fileExtension.
/// </exception> /// </exception>
public string GetCachePath(string path, string uniqueName, string fileExtension) public string GetCachePath(string path, string uniqueName, string fileExtension)
{ {
@ -681,7 +454,7 @@ namespace Emby.Drawing
/// <exception cref="ArgumentNullException"> /// <exception cref="ArgumentNullException">
/// path /// path
/// or /// or
/// filename /// filename.
/// </exception> /// </exception>
public string GetCachePath(string path, string filename) public string GetCachePath(string path, string filename)
{ {
@ -689,6 +462,7 @@ namespace Emby.Drawing
{ {
throw new ArgumentNullException(nameof(path)); throw new ArgumentNullException(nameof(path));
} }
if (string.IsNullOrEmpty(filename)) if (string.IsNullOrEmpty(filename))
{ {
throw new ArgumentNullException(nameof(filename)); throw new ArgumentNullException(nameof(filename));
@ -709,75 +483,20 @@ namespace Emby.Drawing
_logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath); _logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath);
} }
/// <inheritdoc />
public IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType)
{
foreach (var i in ImageEnhancers)
{
if (i.Supports(item, imageType))
{
yield return i;
}
}
}
private class LockInfo
{
public SemaphoreSlim Lock = new SemaphoreSlim(1, 1);
public int Count = 1;
}
private LockInfo GetLock(string key)
{
lock (_locks)
{
if (_locks.TryGetValue(key, out LockInfo info))
{
info.Count++;
}
else
{
info = new LockInfo();
_locks[key] = info;
}
return info;
}
}
private void ReleaseLock(string key, LockInfo info)
{
info.Lock.Release();
lock (_locks)
{
info.Count--;
if (info.Count <= 0)
{
_locks.Remove(key);
info.Lock.Dispose();
}
}
}
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{
_disposed = true;
var disposable = _imageEncoder as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
private void CheckDisposed()
{ {
if (_disposed) if (_disposed)
{ {
throw new ObjectDisposedException(GetType().Name); return;
} }
if (_imageEncoder is IDisposable disposable)
{
disposable.Dispose();
}
_disposed = true;
} }
} }
} }

View File

@ -1074,8 +1074,6 @@ namespace Emby.Server.Implementations
GetExports<IMetadataSaver>(), GetExports<IMetadataSaver>(),
GetExports<IExternalId>()); GetExports<IExternalId>());
ImageProcessor.ImageEnhancers = GetExports<IImageEnhancer>();
LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>()); LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
SubtitleManager.AddParts(GetExports<ISubtitleProvider>()); SubtitleManager.AddParts(GetExports<ISubtitleProvider>());

View File

@ -1362,56 +1362,33 @@ namespace Emby.Server.Implementations.Dto
return null; return null;
} }
var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary).ToArray();
ImageDimensions size; ImageDimensions size;
var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio(); var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
if (defaultAspectRatio > 0) if (defaultAspectRatio > 0)
{ {
if (supportedEnhancers.Length == 0) return defaultAspectRatio;
{
return defaultAspectRatio;
}
int dummyWidth = 200;
int dummyHeight = Convert.ToInt32(dummyWidth / defaultAspectRatio);
size = new ImageDimensions(dummyWidth, dummyHeight);
} }
else
if (!imageInfo.IsLocalFile)
{ {
if (!imageInfo.IsLocalFile) return null;
}
try
{
size = _imageProcessor.GetImageDimensions(item, imageInfo);
if (size.Width <= 0 || size.Height <= 0)
{ {
return null; return null;
} }
try
{
size = _imageProcessor.GetImageDimensions(item, imageInfo);
if (size.Width <= 0 || size.Height <= 0)
{
return null;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path);
return null;
}
} }
catch (Exception ex)
foreach (var enhancer in supportedEnhancers)
{ {
try _logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path);
{ return null;
size = enhancer.GetEnhancedImageSize(item, ImageType.Primary, 0, size);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in image enhancer: {0}", enhancer.GetType().Name);
}
} }
var width = size.Width; var width = size.Width;

View File

@ -6,7 +6,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Extensions;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Globalization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SkiaSharp; using SkiaSharp;
using static Jellyfin.Drawing.Skia.SkiaHelper; using static Jellyfin.Drawing.Skia.SkiaHelper;
@ -18,27 +17,23 @@ namespace Jellyfin.Drawing.Skia
/// </summary> /// </summary>
public class SkiaEncoder : IImageEncoder public class SkiaEncoder : IImageEncoder
{ {
private readonly ILogger _logger;
private readonly IApplicationPaths _appPaths;
private readonly ILocalizationManager _localizationManager;
private static readonly HashSet<string> _transparentImageTypes private static readonly HashSet<string> _transparentImageTypes
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" }; = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
private readonly ILogger _logger;
private readonly IApplicationPaths _appPaths;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SkiaEncoder"/> class. /// Initializes a new instance of the <see cref="SkiaEncoder"/> class.
/// </summary> /// </summary>
/// <param name="logger">The application logger.</param> /// <param name="logger">The application logger.</param>
/// <param name="appPaths">The application paths.</param> /// <param name="appPaths">The application paths.</param>
/// <param name="localizationManager">The application localization manager.</param>
public SkiaEncoder( public SkiaEncoder(
ILogger<SkiaEncoder> logger, ILogger<SkiaEncoder> logger,
IApplicationPaths appPaths, IApplicationPaths appPaths)
ILocalizationManager localizationManager)
{ {
_logger = logger; _logger = logger;
_appPaths = appPaths; _appPaths = appPaths;
_localizationManager = localizationManager;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -235,9 +230,12 @@ namespace Jellyfin.Drawing.Skia
private bool RequiresSpecialCharacterHack(string path) private bool RequiresSpecialCharacterHack(string path)
{ {
if (_localizationManager.HasUnicodeCategory(path, UnicodeCategory.OtherLetter)) for (int i = 0; i < path.Length; i++)
{ {
return true; if (char.GetUnicodeCategory(path[i]) == UnicodeCategory.OtherLetter)
{
return true;
}
} }
if (HasDiacritics(path)) if (HasDiacritics(path))

View File

@ -169,7 +169,7 @@ namespace Jellyfin.Server
_loggerFactory, _loggerFactory,
options, options,
new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths), new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
new NullImageEncoder(), GetImageEncoder(appPaths),
new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()), new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()),
appConfig); appConfig);
try try
@ -193,8 +193,6 @@ namespace Jellyfin.Server
throw; throw;
} }
appHost.ImageProcessor.ImageEncoder = GetImageEncoder(appPaths, appHost.LocalizationManager);
await appHost.RunStartupTasksAsync().ConfigureAwait(false); await appHost.RunStartupTasksAsync().ConfigureAwait(false);
stopWatch.Stop(); stopWatch.Stop();
@ -494,9 +492,7 @@ namespace Jellyfin.Server
} }
} }
private static IImageEncoder GetImageEncoder( private static IImageEncoder GetImageEncoder(IApplicationPaths appPaths)
IApplicationPaths appPaths,
ILocalizationManager localizationManager)
{ {
try try
{ {
@ -505,8 +501,7 @@ namespace Jellyfin.Server
return new SkiaEncoder( return new SkiaEncoder(
_loggerFactory.CreateLogger<SkiaEncoder>(), _loggerFactory.CreateLogger<SkiaEncoder>(),
appPaths, appPaths);
localizationManager);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -24,7 +24,7 @@ using Microsoft.Net.Http.Headers;
namespace MediaBrowser.Api.Images namespace MediaBrowser.Api.Images
{ {
/// <summary> /// <summary>
/// Class GetItemImage /// Class GetItemImage.
/// </summary> /// </summary>
[Route("/Items/{Id}/Images", "GET", Summary = "Gets information about an item's images")] [Route("/Items/{Id}/Images", "GET", Summary = "Gets information about an item's images")]
[Authenticated] [Authenticated]
@ -558,21 +558,6 @@ namespace MediaBrowser.Api.Images
throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", displayText, request.Type)); throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", displayText, request.Type));
} }
IImageEnhancer[] supportedImageEnhancers;
if (_imageProcessor.ImageEnhancers.Count > 0)
{
if (item == null)
{
item = _libraryManager.GetItemById(itemId);
}
supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.GetSupportedEnhancers(item, request.Type).ToArray() : Array.Empty<IImageEnhancer>();
}
else
{
supportedImageEnhancers = Array.Empty<IImageEnhancer>();
}
bool cropwhitespace; bool cropwhitespace;
if (request.CropWhitespace.HasValue) if (request.CropWhitespace.HasValue)
{ {
@ -598,25 +583,25 @@ namespace MediaBrowser.Api.Images
{"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"} {"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"}
}; };
return GetImageResult(item, return GetImageResult(
item,
itemId, itemId,
request, request,
imageInfo, imageInfo,
cropwhitespace, cropwhitespace,
outputFormats, outputFormats,
supportedImageEnhancers,
cacheDuration, cacheDuration,
responseHeaders, responseHeaders,
isHeadRequest); isHeadRequest);
} }
private async Task<object> GetImageResult(BaseItem item, private async Task<object> GetImageResult(
BaseItem item,
Guid itemId, Guid itemId,
ImageRequest request, ImageRequest request,
ItemImageInfo image, ItemImageInfo image,
bool cropwhitespace, bool cropwhitespace,
IReadOnlyCollection<ImageFormat> supportedFormats, IReadOnlyCollection<ImageFormat> supportedFormats,
IReadOnlyCollection<IImageEnhancer> enhancers,
TimeSpan? cacheDuration, TimeSpan? cacheDuration,
IDictionary<string, string> headers, IDictionary<string, string> headers,
bool isHeadRequest) bool isHeadRequest)
@ -624,7 +609,6 @@ namespace MediaBrowser.Api.Images
var options = new ImageProcessingOptions var options = new ImageProcessingOptions
{ {
CropWhiteSpace = cropwhitespace, CropWhiteSpace = cropwhitespace,
Enhancers = enhancers,
Height = request.Height, Height = request.Height,
ImageIndex = request.Index ?? 0, ImageIndex = request.Index ?? 0,
Image = image, Image = image,

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -20,20 +19,12 @@ namespace MediaBrowser.Controller.Drawing
/// <value>The supported input formats.</value> /// <value>The supported input formats.</value>
IReadOnlyCollection<string> SupportedInputFormats { get; } IReadOnlyCollection<string> SupportedInputFormats { get; }
/// <summary>
/// Gets the image enhancers.
/// </summary>
/// <value>The image enhancers.</value>
IReadOnlyCollection<IImageEnhancer> ImageEnhancers { get; set; }
/// <summary> /// <summary>
/// Gets a value indicating whether [supports image collage creation]. /// Gets a value indicating whether [supports image collage creation].
/// </summary> /// </summary>
/// <value><c>true</c> if [supports image collage creation]; otherwise, <c>false</c>.</value> /// <value><c>true</c> if [supports image collage creation]; otherwise, <c>false</c>.</value>
bool SupportsImageCollageCreation { get; } bool SupportsImageCollageCreation { get; }
IImageEncoder ImageEncoder { get; set; }
/// <summary> /// <summary>
/// Gets the dimensions of the image. /// Gets the dimensions of the image.
/// </summary> /// </summary>
@ -58,14 +49,6 @@ namespace MediaBrowser.Controller.Drawing
/// <returns>ImageDimensions</returns> /// <returns>ImageDimensions</returns>
ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem); ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem);
/// <summary>
/// Gets the supported enhancers.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <returns>IEnumerable{IImageEnhancer}.</returns>
IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType);
/// <summary> /// <summary>
/// Gets the image cache tag. /// Gets the image cache tag.
/// </summary> /// </summary>
@ -75,15 +58,6 @@ namespace MediaBrowser.Controller.Drawing
string GetImageCacheTag(BaseItem item, ItemImageInfo image); string GetImageCacheTag(BaseItem item, ItemImageInfo image);
string GetImageCacheTag(BaseItem item, ChapterInfo info); string GetImageCacheTag(BaseItem item, ChapterInfo info);
/// <summary>
/// Gets the image cache tag.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="image">The image.</param>
/// <param name="imageEnhancers">The image enhancers.</param>
/// <returns>Guid.</returns>
string GetImageCacheTag(BaseItem item, ItemImageInfo image, IReadOnlyCollection<IImageEnhancer> imageEnhancers);
/// <summary> /// <summary>
/// Processes the image. /// Processes the image.
/// </summary> /// </summary>
@ -99,15 +73,6 @@ namespace MediaBrowser.Controller.Drawing
/// <returns>Task.</returns> /// <returns>Task.</returns>
Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options); Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options);
/// <summary>
/// Gets the enhanced image.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <returns>Task{System.String}.</returns>
Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex);
/// <summary> /// <summary>
/// Gets the supported image output formats. /// Gets the supported image output formats.
/// </summary> /// </summary>

View File

@ -19,8 +19,6 @@ namespace MediaBrowser.Controller.Drawing
return GetSizeEstimate(options); return GetSizeEstimate(options);
} }
public static IImageProcessor ImageProcessor { get; set; }
private static ImageDimensions GetSizeEstimate(ImageProcessingOptions options) private static ImageDimensions GetSizeEstimate(ImageProcessingOptions options)
{ {
if (options.Width.HasValue && options.Height.HasValue) if (options.Width.HasValue && options.Height.HasValue)

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
namespace MediaBrowser.Controller.Drawing namespace MediaBrowser.Controller.Drawing
@ -34,8 +33,6 @@ namespace MediaBrowser.Controller.Drawing
public int Quality { get; set; } public int Quality { get; set; }
public IReadOnlyCollection<IImageEnhancer> Enhancers { get; set; }
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; set; } public IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; set; }
public bool AddPlayedIndicator { get; set; } public bool AddPlayedIndicator { get; set; }

View File

@ -316,11 +316,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue) if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
{ {
var size = new ImageDimensions var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value);
{
Width = VideoStream.Width.Value,
Height = VideoStream.Height.Value
};
var newSize = DrawingUtils.Resize(size, var newSize = DrawingUtils.Resize(size,
BaseRequest.Width ?? 0, BaseRequest.Width ?? 0,
@ -346,11 +342,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue) if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
{ {
var size = new ImageDimensions var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value);
{
Width = VideoStream.Width.Value,
Height = VideoStream.Height.Value
};
var newSize = DrawingUtils.Resize(size, var newSize = DrawingUtils.Resize(size,
BaseRequest.Width ?? 0, BaseRequest.Width ?? 0,

View File

@ -1,61 +0,0 @@
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Providers
{
public interface IImageEnhancer
{
/// <summary>
/// Return true only if the given image for the given item will be enhanced by this enhancer.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <returns><c>true</c> if this enhancer will enhance the supplied image for the supplied item, <c>false</c> otherwise</returns>
bool Supports(BaseItem item, ImageType imageType);
/// <summary>
/// Gets the priority or order in which this enhancer should be run.
/// </summary>
/// <value>The priority.</value>
MetadataProviderPriority Priority { get; }
/// <summary>
/// Return a key incorporating all configuration information related to this item
/// </summary>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <returns>Cache key relating to the current state of this item and configuration</returns>
string GetConfigurationCacheKey(BaseItem item, ImageType imageType);
/// <summary>
/// Gets the size of the enhanced image.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <param name="originalImageSize">Size of the original image.</param>
/// <returns>ImageSize.</returns>
ImageDimensions GetEnhancedImageSize(BaseItem item, ImageType imageType, int imageIndex, ImageDimensions originalImageSize);
EnhancedImageInfo GetEnhancedImageInfo(BaseItem item, string inputFile, ImageType imageType, int imageIndex);
/// <summary>
/// Enhances the image async.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="inputFile">The input file.</param>
/// <param name="outputFile">The output file.</param>
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <returns>Task{Image}.</returns>
/// <exception cref="System.ArgumentNullException"></exception>
Task EnhanceImageAsync(BaseItem item, string inputFile, string outputFile, ImageType imageType, int imageIndex);
}
public class EnhancedImageInfo
{
public bool RequiresTransparency { get; set; }
}
}

View File

@ -1,36 +1,43 @@
using System.Globalization;
namespace MediaBrowser.Model.Drawing namespace MediaBrowser.Model.Drawing
{ {
/// <summary> /// <summary>
/// Struct ImageDimensions /// Struct ImageDimensions
/// </summary> /// </summary>
public struct ImageDimensions public readonly struct ImageDimensions
{ {
/// <summary> public ImageDimensions(int width, int height)
/// Gets or sets the height. {
/// </summary> Width = width;
/// <value>The height.</value> Height = height;
public int Height { get; set; } }
/// <summary> /// <summary>
/// Gets or sets the width. /// Gets the height.
/// </summary>
/// <value>The height.</value>
public int Height { get; }
/// <summary>
/// Gets the width.
/// </summary> /// </summary>
/// <value>The width.</value> /// <value>The width.</value>
public int Width { get; set; } public int Width { get; }
public bool Equals(ImageDimensions size) public bool Equals(ImageDimensions size)
{ {
return Width.Equals(size.Width) && Height.Equals(size.Height); return Width.Equals(size.Width) && Height.Equals(size.Height);
} }
/// <inheritdoc />
public override string ToString() public override string ToString()
{ {
return string.Format("{0}-{1}", Width, Height); return string.Format(
} CultureInfo.InvariantCulture,
"{0}-{1}",
public ImageDimensions(int width, int height) Width,
{ Height);
Width = width;
Height = height;
} }
} }
} }