diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index f919904422..625e566600 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -1,4 +1,3 @@ -using SkiaSharp; using System; using System.Collections.Generic; using System.Globalization; @@ -18,9 +17,8 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; +using SkiaSharp; namespace Emby.Drawing { @@ -47,28 +45,27 @@ namespace Emby.Drawing private readonly ILogger _logger; private readonly IFileSystem _fileSystem; - private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationPaths _appPaths; private IImageEncoder _imageEncoder; private readonly Func _libraryManager; private readonly Func _mediaEncoder; - public ImageProcessor(ILogger logger, + public ImageProcessor( + ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem, - IJsonSerializer jsonSerializer, IImageEncoder imageEncoder, - Func libraryManager, ITimerFactory timerFactory, Func mediaEncoder) + Func libraryManager, + Func mediaEncoder) { _logger = logger; + _appPaths = appPaths; _fileSystem = fileSystem; - _jsonSerializer = jsonSerializer; _imageEncoder = imageEncoder; _libraryManager = libraryManager; _mediaEncoder = mediaEncoder; - _appPaths = appPaths; - ImageEnhancers = new IImageEnhancer[] { }; + ImageEnhancers = Array.Empty(); ImageHelper.ImageProcessor = this; } @@ -145,21 +142,19 @@ namespace Emby.Drawing return _imageEncoder.SupportedOutputFormats; } - private readonly string[] TransparentImageTypes = new string[] { ".png", ".webp", ".gif" }; + private static readonly string[] TransparentImageTypes = new string[] { ".png", ".webp", ".gif" }; public bool SupportsTransparency(string path) - { - return TransparentImageTypes.Contains(Path.GetExtension(path) ?? string.Empty); - } + => TransparentImageTypes.Contains(Path.GetExtension(path).ToLower()); - public async Task> ProcessImage(ImageProcessingOptions options) + public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } - var originalImage = options.Image; - var item = options.Item; + ItemImageInfo originalImage = options.Image; + BaseItem item = options.Item; if (!originalImage.IsLocalFile) { @@ -170,19 +165,23 @@ namespace Emby.Drawing originalImage = await _libraryManager().ConvertImageToLocal(item, originalImage, options.ImageIndex).ConfigureAwait(false); } - var originalImagePath = originalImage.Path; - var dateModified = originalImage.DateModified; - var originalImageSize = originalImage.Width > 0 && originalImage.Height > 0 ? new ImageSize(originalImage.Width, originalImage.Height) : (ImageSize?)null; + string originalImagePath = originalImage.Path; + DateTime dateModified = originalImage.DateModified; + ImageSize? originalImageSize = null; + if (originalImage.Width > 0 && originalImage.Height > 0) + { + originalImageSize = new ImageSize(originalImage.Width, originalImage.Height); + } if (!_imageEncoder.SupportsImageEncoding) { - return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); + return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false); - originalImagePath = supportedImageInfo.Item1; - dateModified = supportedImageInfo.Item2; - var requiresTransparency = TransparentImageTypes.Contains(Path.GetExtension(originalImagePath) ?? string.Empty); + originalImagePath = supportedImageInfo.path; + dateModified = supportedImageInfo.dateModified; + bool requiresTransparency = TransparentImageTypes.Contains(Path.GetExtension(originalImagePath)); if (options.Enhancers.Length > 0) { @@ -196,20 +195,18 @@ namespace Emby.Drawing DateModified = dateModified, Type = originalImage.Type, Path = originalImagePath - }, requiresTransparency, item, options.ImageIndex, options.Enhancers, CancellationToken.None).ConfigureAwait(false); - originalImagePath = tuple.Item1; - dateModified = tuple.Item2; - requiresTransparency = tuple.Item3; + originalImagePath = tuple.path; + dateModified = tuple.dateModified; + requiresTransparency = tuple.transparent; // TODO: Get this info originalImageSize = null; } - var photo = item as Photo; - var autoOrient = false; + bool autoOrient = false; ImageOrientation? orientation = null; - if (photo != null) + if (item is Photo photo) { if (photo.Orientation.HasValue) { @@ -230,7 +227,7 @@ namespace Emby.Drawing if (options.HasDefaultOptions(originalImagePath, originalImageSize) && (!autoOrient || !options.RequiresAutoOrientation)) { // Just spit out the original file if all the options are default - return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); + return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } //ImageSize? originalImageSize = GetSavedImageSize(originalImagePath, dateModified); @@ -241,15 +238,15 @@ namespace Emby.Drawing // return new ValueTuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); //} - var newSize = ImageHelper.GetNewImageSize(options, null); - var quality = options.Quality; + ImageSize newSize = ImageHelper.GetNewImageSize(options, null); + int quality = options.Quality; - var outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency); - var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer); + 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); CheckDisposed(); - var lockInfo = GetLock(cacheFilePath); + LockInfo lockInfo = GetLock(cacheFilePath); await lockInfo.Lock.WaitAsync().ConfigureAwait(false); @@ -262,17 +259,15 @@ namespace Emby.Drawing options.CropWhiteSpace = false; } - var resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat); + string resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat); if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase)) { - return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); + return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } - - return new Tuple(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath)); } - return new Tuple(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath)); + return (cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath)); } catch (ArgumentOutOfRangeException ex) { @@ -281,7 +276,7 @@ namespace Emby.Drawing _logger.LogError(ex, "Error encoding image"); #endif // Just spit out the original file if all the options are default - return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); + return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } catch (Exception ex) { @@ -289,7 +284,7 @@ namespace Emby.Drawing _logger.LogError(ex, "Error encoding image"); // Just spit out the original file if all the options are default - return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); + return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } finally { @@ -325,42 +320,17 @@ namespace Emby.Drawing return ImageFormat.Jpg; } - private void CopyFile(string src, string destination) - { - try - { - _fileSystem.CopyFile(src, destination, true); - } - catch - { - - } - } - private string GetMimeType(ImageFormat format, string path) { - if (format == ImageFormat.Bmp) + switch(format) { - return MimeTypes.GetMimeType("i.bmp"); + case ImageFormat.Bmp: return MimeTypes.GetMimeType("i.bmp"); + case ImageFormat.Gif: return MimeTypes.GetMimeType("i.gif"); + case ImageFormat.Jpg: return MimeTypes.GetMimeType("i.jpg"); + case ImageFormat.Png: return MimeTypes.GetMimeType("i.png"); + case ImageFormat.Webp: return MimeTypes.GetMimeType("i.webp"); + default: return MimeTypes.GetMimeType(path); } - if (format == ImageFormat.Gif) - { - return MimeTypes.GetMimeType("i.gif"); - } - if (format == ImageFormat.Jpg) - { - return MimeTypes.GetMimeType("i.jpg"); - } - if (format == ImageFormat.Png) - { - return MimeTypes.GetMimeType("i.png"); - } - if (format == ImageFormat.Webp) - { - return MimeTypes.GetMimeType("i.webp"); - } - - return MimeTypes.GetMimeType(path); } /// @@ -373,17 +343,12 @@ namespace Emby.Drawing /// private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, int? blur, string backgroundColor, string foregroundLayer) { - var filename = originalPath; - - filename += "width=" + outputSize.Width; - - filename += "height=" + outputSize.Height; - - filename += "quality=" + quality; - - filename += "datemodified=" + dateModified.Ticks; - - filename += "f=" + format; + var filename = originalPath + + "width=" + outputSize.Width + + "height=" + outputSize.Height + + "quality=" + quality + + "datemodified=" + dateModified.Ticks + + "f=" + format; if (addPlayedIndicator) { @@ -421,26 +386,20 @@ namespace Emby.Drawing } public ImageSize GetImageSize(BaseItem item, ItemImageInfo info) - { - return GetImageSize(item, info, true); - } + => GetImageSize(item, info, true); public ImageSize GetImageSize(BaseItem item, ItemImageInfo info, bool updateItem) { - var width = info.Width; - var height = info.Height; + int width = info.Width; + int height = info.Height; if (height > 0 && width > 0) { - return new ImageSize - { - Width = width, - Height = height - }; + return new ImageSize(width, height); } - var path = info.Path; - _logger.LogInformation("Getting image size for item {0} {1}", item.GetType().Name, path); + string path = info.Path; + _logger.LogInformation("Getting image size for item {ItemType} {Path}", item.GetType().Name, path); var size = GetImageSize(path); @@ -466,15 +425,11 @@ namespace Emby.Drawing } using (var s = new SKFileStream(path)) - using (var codec = SKCodec.Create(s)) - { - var info = codec.Info; - return new ImageSize - { - Height = info.Height, - Width = info.Width - }; - } + using (var codec = SKCodec.Create(s)) + { + var info = codec.Info; + return new ImageSize(info.Width, info.Height); + } } /// @@ -518,9 +473,9 @@ namespace Emby.Drawing /// item public string GetImageCacheTag(BaseItem item, ItemImageInfo image, IImageEnhancer[] imageEnhancers) { - var originalImagePath = image.Path; - var dateModified = image.DateModified; - var imageType = image.Type; + string originalImagePath = image.Path; + DateTime dateModified = image.DateModified; + ImageType imageType = image.Type; // Optimization if (imageEnhancers.Length == 0) @@ -532,28 +487,28 @@ namespace Emby.Drawing var cacheKeys = imageEnhancers.Select(i => i.GetConfigurationCacheKey(item, imageType)).ToList(); cacheKeys.Add(originalImagePath + dateModified.Ticks); - return string.Join("|", cacheKeys.ToArray()).GetMD5().ToString("N"); + return string.Join("|", cacheKeys).GetMD5().ToString("N"); } - private async Task> GetSupportedImage(string originalImagePath, DateTime dateModified) + private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) { - var inputFormat = (Path.GetExtension(originalImagePath) ?? string.Empty) + var inputFormat = Path.GetExtension(originalImagePath) .TrimStart('.') .Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase); // These are just jpg files renamed as tbn if (string.Equals(inputFormat, "tbn", StringComparison.OrdinalIgnoreCase)) { - return new ValueTuple(originalImagePath, dateModified); + return (originalImagePath, dateModified); } if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat, StringComparer.OrdinalIgnoreCase)) { try { - var filename = (originalImagePath + dateModified.Ticks.ToString(UsCulture)).GetMD5().ToString("N"); + string filename = (originalImagePath + dateModified.Ticks.ToString(UsCulture)).GetMD5().ToString("N"); - var cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png"; + string cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png"; var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension); var file = _fileSystem.GetFileInfo(outputPath); @@ -571,11 +526,11 @@ namespace Emby.Drawing } catch (Exception ex) { - _logger.LogError(ex, "Image conversion failed for {originalImagePath}", originalImagePath); + _logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath); } } - return new ValueTuple(originalImagePath, dateModified); + return (originalImagePath, dateModified); } /// @@ -589,16 +544,17 @@ namespace Emby.Drawing { var enhancers = GetSupportedEnhancers(item, imageType); - var imageInfo = item.GetImageInfo(imageType, imageIndex); + ItemImageInfo imageInfo = item.GetImageInfo(imageType, imageIndex); - var inputImageSupportsTransparency = SupportsTransparency(imageInfo.Path); + bool inputImageSupportsTransparency = SupportsTransparency(imageInfo.Path); var result = await GetEnhancedImage(imageInfo, inputImageSupportsTransparency, item, imageIndex, enhancers, CancellationToken.None); - return result.Item1; + return result.path; } - private async Task> GetEnhancedImage(ItemImageInfo image, + private async Task<(string path, DateTime dateModified, bool transparent)> GetEnhancedImage( + ItemImageInfo image, bool inputImageSupportsTransparency, BaseItem item, int imageIndex, @@ -616,14 +572,14 @@ namespace Emby.Drawing // Enhance if we have enhancers var enhancedImageInfo = await GetEnhancedImageInternal(originalImagePath, item, imageType, imageIndex, enhancers, cacheGuid, cancellationToken).ConfigureAwait(false); - var enhancedImagePath = enhancedImageInfo.Item1; + string enhancedImagePath = enhancedImageInfo.path; // If the path changed update dateModified if (!string.Equals(enhancedImagePath, originalImagePath, StringComparison.OrdinalIgnoreCase)) { - var treatmentRequiresTransparency = enhancedImageInfo.Item2; + var treatmentRequiresTransparency = enhancedImageInfo.transparent; - return new ValueTuple(enhancedImagePath, _fileSystem.GetLastWriteTimeUtc(enhancedImagePath), treatmentRequiresTransparency); + return (enhancedImagePath, _fileSystem.GetLastWriteTimeUtc(enhancedImagePath), treatmentRequiresTransparency); } } catch (Exception ex) @@ -631,7 +587,7 @@ namespace Emby.Drawing _logger.LogError(ex, "Error enhancing image"); } - return new ValueTuple(originalImagePath, dateModified, inputImageSupportsTransparency); + return (originalImagePath, dateModified, inputImageSupportsTransparency); } /// @@ -649,7 +605,8 @@ namespace Emby.Drawing /// or /// item /// - private async Task> GetEnhancedImageInternal(string originalImagePath, + private async Task<(string path, bool transparent)> GetEnhancedImageInternal( + string originalImagePath, BaseItem item, ImageType imageType, int imageIndex, @@ -677,13 +634,13 @@ namespace Emby.Drawing } // All enhanced images are saved as png to allow transparency - var cacheExtension = _imageEncoder.SupportedOutputFormats.Contains(ImageFormat.Webp) ? + string cacheExtension = _imageEncoder.SupportedOutputFormats.Contains(ImageFormat.Webp) ? ".webp" : (treatmentRequiresTransparency ? ".png" : ".jpg"); - var enhancedImagePath = GetCachePath(EnhancedImageCachePath, cacheGuid + cacheExtension); + string enhancedImagePath = GetCachePath(EnhancedImageCachePath, cacheGuid + cacheExtension); - var lockInfo = GetLock(enhancedImagePath); + LockInfo lockInfo = GetLock(enhancedImagePath); await lockInfo.Lock.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -692,14 +649,14 @@ namespace Emby.Drawing // Check again in case of contention if (_fileSystem.FileExists(enhancedImagePath)) { - return new ValueTuple(enhancedImagePath, treatmentRequiresTransparency); + return (enhancedImagePath, treatmentRequiresTransparency); } _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(enhancedImagePath)); await ExecuteImageEnhancers(supportedEnhancers, originalImagePath, enhancedImagePath, item, imageType, imageIndex).ConfigureAwait(false); - return new ValueTuple(enhancedImagePath, treatmentRequiresTransparency); + return (enhancedImagePath, treatmentRequiresTransparency); } finally { @@ -788,18 +745,16 @@ namespace Emby.Drawing var prefix = filename.Substring(0, 1); - path = Path.Combine(path, prefix); - - return Path.Combine(path, filename); + return Path.Combine(path, prefix, filename); } public void CreateImageCollage(ImageCollageOptions options) { - _logger.LogInformation("Creating image collage and saving to {0}", options.OutputPath); + _logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath); _imageEncoder.CreateImageCollage(options); - _logger.LogInformation("Completed creation of image collage and saved to {0}", options.OutputPath); + _logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath); } public IImageEnhancer[] GetSupportedEnhancers(BaseItem item, ImageType imageType) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 3ed31e7bd4..476c61ce3a 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1050,7 +1050,7 @@ namespace Emby.Server.Implementations private IImageProcessor GetImageProcessor() { - return new ImageProcessor(LoggerFactory.CreateLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, ImageEncoder, () => LibraryManager, TimerFactory, () => MediaEncoder); + return new ImageProcessor(LoggerFactory.CreateLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); } protected virtual FFMpegInstallInfo GetFfmpegInstallInfo() diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index d077616199..fccc60176e 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -103,7 +103,7 @@ namespace Jellyfin.Server { appHost.Init(); - appHost.ImageProcessor.ImageEncoder = getImageEncoder(_logger, fileSystem, options, () => appHost.HttpClient, appPaths, environmentInfo, appHost.LocalizationManager); + appHost.ImageProcessor.ImageEncoder = GetImageEncoder(_logger, fileSystem, options, () => appHost.HttpClient, appPaths, environmentInfo, appHost.LocalizationManager); _logger.LogInformation("Running startup tasks"); @@ -256,13 +256,12 @@ namespace Jellyfin.Server } } - public static IImageEncoder getImageEncoder( + public static IImageEncoder GetImageEncoder( ILogger logger, IFileSystem fileSystem, StartupOptions startupOptions, Func httpClient, IApplicationPaths appPaths, - IEnvironmentInfo environment, ILocalizationManager localizationManager) { try diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 2cafc9ec19..7e6e0127fb 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -82,7 +82,7 @@ namespace MediaBrowser.Controller.Drawing /// /// The options. /// Task. - Task> ProcessImage(ImageProcessingOptions options); + Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options); /// /// Gets the enhanced image.