diff --git a/src/Jellyfin.Drawing/ImageProcessor.cs b/src/Jellyfin.Drawing/ImageProcessor.cs index 3c7bc394f0..b381c9ae73 100644 --- a/src/Jellyfin.Drawing/ImageProcessor.cs +++ b/src/Jellyfin.Drawing/ImageProcessor.cs @@ -19,551 +19,550 @@ using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; using Photo = MediaBrowser.Controller.Entities.Photo; -namespace Jellyfin.Drawing +namespace Jellyfin.Drawing; + +/// +/// Class ImageProcessor. +/// +public sealed class ImageProcessor : IImageProcessor, IDisposable { + // Increment this when there's a change requiring caches to be invalidated + private const char Version = '3'; + + private static readonly HashSet _transparentImageTypes + = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" }; + + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + private readonly IServerApplicationPaths _appPaths; + private readonly IImageEncoder _imageEncoder; + private readonly IMediaEncoder _mediaEncoder; + + private bool _disposed; + /// - /// Class ImageProcessor. + /// Initializes a new instance of the class. /// - public sealed class ImageProcessor : IImageProcessor, IDisposable + /// The logger. + /// The server application paths. + /// The filesystem. + /// The image encoder. + /// The media encoder. + public ImageProcessor( + ILogger logger, + IServerApplicationPaths appPaths, + IFileSystem fileSystem, + IImageEncoder imageEncoder, + IMediaEncoder mediaEncoder) { - // Increment this when there's a change requiring caches to be invalidated - private const char Version = '3'; - - private static readonly HashSet _transparentImageTypes - = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" }; - - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IServerApplicationPaths _appPaths; - private readonly IImageEncoder _imageEncoder; - private readonly IMediaEncoder _mediaEncoder; - - private bool _disposed; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The server application paths. - /// The filesystem. - /// The image encoder. - /// The media encoder. - public ImageProcessor( - ILogger logger, - IServerApplicationPaths appPaths, - IFileSystem fileSystem, - IImageEncoder imageEncoder, - IMediaEncoder mediaEncoder) - { - _logger = logger; - _fileSystem = fileSystem; - _imageEncoder = imageEncoder; - _mediaEncoder = mediaEncoder; - _appPaths = appPaths; - } + _logger = logger; + _fileSystem = fileSystem; + _imageEncoder = imageEncoder; + _mediaEncoder = mediaEncoder; + _appPaths = appPaths; + } - private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images"); + private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images"); - /// - public IReadOnlyCollection SupportedInputFormats => - new HashSet(StringComparer.OrdinalIgnoreCase) - { - "tiff", - "tif", - "jpeg", - "jpg", - "png", - "aiff", - "cr2", - "crw", - "nef", - "orf", - "pef", - "arw", - "webp", - "gif", - "bmp", - "erf", - "raf", - "rw2", - "nrw", - "dng", - "ico", - "astc", - "ktx", - "pkm", - "wbmp" - }; - - /// - public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation; - - /// - public async Task ProcessImage(ImageProcessingOptions options, Stream toStream) - { - var file = await ProcessImage(options).ConfigureAwait(false); - using (var fileStream = AsyncFile.OpenRead(file.Path)) - { - await fileStream.CopyToAsync(toStream).ConfigureAwait(false); - } + /// + public IReadOnlyCollection SupportedInputFormats => + new HashSet(StringComparer.OrdinalIgnoreCase) + { + "tiff", + "tif", + "jpeg", + "jpg", + "png", + "aiff", + "cr2", + "crw", + "nef", + "orf", + "pef", + "arw", + "webp", + "gif", + "bmp", + "erf", + "raf", + "rw2", + "nrw", + "dng", + "ico", + "astc", + "ktx", + "pkm", + "wbmp" + }; + + /// + public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation; + + /// + public async Task ProcessImage(ImageProcessingOptions options, Stream toStream) + { + var file = await ProcessImage(options).ConfigureAwait(false); + using (var fileStream = AsyncFile.OpenRead(file.Path)) + { + await fileStream.CopyToAsync(toStream).ConfigureAwait(false); } + } - /// - public IReadOnlyCollection GetSupportedImageOutputFormats() - => _imageEncoder.SupportedOutputFormats; + /// + public IReadOnlyCollection GetSupportedImageOutputFormats() + => _imageEncoder.SupportedOutputFormats; - /// - public bool SupportsTransparency(string path) - => _transparentImageTypes.Contains(Path.GetExtension(path)); + /// + public bool SupportsTransparency(string path) + => _transparentImageTypes.Contains(Path.GetExtension(path)); - /// - public async Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options) - { - ItemImageInfo originalImage = options.Image; - BaseItem item = options.Item; + /// + public async Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options) + { + ItemImageInfo originalImage = options.Image; + BaseItem item = options.Item; - string originalImagePath = originalImage.Path; - DateTime dateModified = originalImage.DateModified; - ImageDimensions? originalImageSize = null; - if (originalImage.Width > 0 && originalImage.Height > 0) - { - originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height); - } + string originalImagePath = originalImage.Path; + DateTime dateModified = originalImage.DateModified; + ImageDimensions? originalImageSize = null; + if (originalImage.Width > 0 && originalImage.Height > 0) + { + originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height); + } - var mimeType = MimeTypes.GetMimeType(originalImagePath); - if (!_imageEncoder.SupportsImageEncoding) - { - return (originalImagePath, mimeType, dateModified); - } + var mimeType = MimeTypes.GetMimeType(originalImagePath); + if (!_imageEncoder.SupportsImageEncoding) + { + return (originalImagePath, mimeType, dateModified); + } - var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false); - originalImagePath = supportedImageInfo.Path; + var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false); + originalImagePath = supportedImageInfo.Path; - // Original file doesn't exist, or original file is gif. - if (!File.Exists(originalImagePath) || string.Equals(mimeType, MediaTypeNames.Image.Gif, StringComparison.OrdinalIgnoreCase)) - { - return (originalImagePath, mimeType, dateModified); - } + // Original file doesn't exist, or original file is gif. + if (!File.Exists(originalImagePath) || string.Equals(mimeType, MediaTypeNames.Image.Gif, StringComparison.OrdinalIgnoreCase)) + { + return (originalImagePath, mimeType, dateModified); + } - dateModified = supportedImageInfo.DateModified; - bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath)); + dateModified = supportedImageInfo.DateModified; + bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath)); - bool autoOrient = false; - ImageOrientation? orientation = null; - if (item is Photo photo) + bool autoOrient = false; + ImageOrientation? orientation = null; + if (item is Photo photo) + { + if (photo.Orientation.HasValue) { - if (photo.Orientation.HasValue) - { - if (photo.Orientation.Value != ImageOrientation.TopLeft) - { - autoOrient = true; - orientation = photo.Orientation; - } - } - else + if (photo.Orientation.Value != ImageOrientation.TopLeft) { - // Orientation unknown, so do it autoOrient = true; orientation = photo.Orientation; } } - - if (options.HasDefaultOptions(originalImagePath, originalImageSize) && (!autoOrient || !options.RequiresAutoOrientation)) + else { - // Just spit out the original file if all the options are default - return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); - } - - int quality = options.Quality; - - ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency); - string cacheFilePath = GetCacheFilePath( - originalImagePath, - options.Width, - options.Height, - options.MaxWidth, - options.MaxHeight, - options.FillWidth, - options.FillHeight, - quality, - dateModified, - outputFormat, - options.AddPlayedIndicator, - options.PercentPlayed, - options.UnplayedCount, - options.Blur, - options.BackgroundColor, - options.ForegroundLayer); - - try - { - if (!File.Exists(cacheFilePath)) - { - string resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat); - - if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase)) - { - return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); - } - } - - return (cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath)); - } - catch (Exception ex) - { - // If it fails for whatever reason, return the original image - _logger.LogError(ex, "Error encoding image"); - return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); + // Orientation unknown, so do it + autoOrient = true; + orientation = photo.Orientation; } } - private ImageFormat GetOutputFormat(IReadOnlyCollection clientSupportedFormats, bool requiresTransparency) + if (options.HasDefaultOptions(originalImagePath, originalImageSize) && (!autoOrient || !options.RequiresAutoOrientation)) { - var serverFormats = GetSupportedImageOutputFormats(); - - // Client doesn't care about format, so start with webp if supported - if (serverFormats.Contains(ImageFormat.Webp) && clientSupportedFormats.Contains(ImageFormat.Webp)) - { - return ImageFormat.Webp; - } + // Just spit out the original file if all the options are default + return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); + } - // If transparency is needed and webp isn't supported, than png is the only option - if (requiresTransparency && clientSupportedFormats.Contains(ImageFormat.Png)) + int quality = options.Quality; + + ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency); + string cacheFilePath = GetCacheFilePath( + originalImagePath, + options.Width, + options.Height, + options.MaxWidth, + options.MaxHeight, + options.FillWidth, + options.FillHeight, + quality, + dateModified, + outputFormat, + options.AddPlayedIndicator, + options.PercentPlayed, + options.UnplayedCount, + options.Blur, + options.BackgroundColor, + options.ForegroundLayer); + + try + { + if (!File.Exists(cacheFilePath)) { - return ImageFormat.Png; - } + string resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat); - foreach (var format in clientSupportedFormats) - { - if (serverFormats.Contains(format)) + if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase)) { - return format; + return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } } - // We should never actually get here - return ImageFormat.Jpg; + return (cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath)); } + catch (Exception ex) + { + // If it fails for whatever reason, return the original image + _logger.LogError(ex, "Error encoding image"); + return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); + } + } - private string GetMimeType(ImageFormat format, string path) - => format switch - { - ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"), - ImageFormat.Gif => MimeTypes.GetMimeType("i.gif"), - ImageFormat.Jpg => MimeTypes.GetMimeType("i.jpg"), - ImageFormat.Png => MimeTypes.GetMimeType("i.png"), - ImageFormat.Webp => MimeTypes.GetMimeType("i.webp"), - _ => MimeTypes.GetMimeType(path) - }; - - /// - /// Gets the cache file path based on a set of parameters. - /// - private string GetCacheFilePath( - string originalPath, - int? width, - int? height, - int? maxWidth, - int? maxHeight, - int? fillWidth, - int? fillHeight, - int quality, - DateTime dateModified, - ImageFormat format, - bool addPlayedIndicator, - double percentPlayed, - int? unwatchedCount, - int? blur, - string backgroundColor, - string foregroundLayer) - { - var filename = new StringBuilder(256); - filename.Append(originalPath); - - filename.Append(",quality="); - filename.Append(quality); - - filename.Append(",datemodified="); - filename.Append(dateModified.Ticks); - - filename.Append(",f="); - filename.Append(format); - - if (width.HasValue) - { - filename.Append(",width="); - filename.Append(width.Value); - } + private ImageFormat GetOutputFormat(IReadOnlyCollection clientSupportedFormats, bool requiresTransparency) + { + var serverFormats = GetSupportedImageOutputFormats(); - if (height.HasValue) - { - filename.Append(",height="); - filename.Append(height.Value); - } + // Client doesn't care about format, so start with webp if supported + if (serverFormats.Contains(ImageFormat.Webp) && clientSupportedFormats.Contains(ImageFormat.Webp)) + { + return ImageFormat.Webp; + } - if (maxWidth.HasValue) - { - filename.Append(",maxwidth="); - filename.Append(maxWidth.Value); - } + // If transparency is needed and webp isn't supported, than png is the only option + if (requiresTransparency && clientSupportedFormats.Contains(ImageFormat.Png)) + { + return ImageFormat.Png; + } - if (maxHeight.HasValue) + foreach (var format in clientSupportedFormats) + { + if (serverFormats.Contains(format)) { - filename.Append(",maxheight="); - filename.Append(maxHeight.Value); + return format; } + } - if (fillWidth.HasValue) - { - filename.Append(",fillwidth="); - filename.Append(fillWidth.Value); - } + // We should never actually get here + return ImageFormat.Jpg; + } - if (fillHeight.HasValue) - { - filename.Append(",fillheight="); - filename.Append(fillHeight.Value); - } + private string GetMimeType(ImageFormat format, string path) + => format switch + { + ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"), + ImageFormat.Gif => MimeTypes.GetMimeType("i.gif"), + ImageFormat.Jpg => MimeTypes.GetMimeType("i.jpg"), + ImageFormat.Png => MimeTypes.GetMimeType("i.png"), + ImageFormat.Webp => MimeTypes.GetMimeType("i.webp"), + _ => MimeTypes.GetMimeType(path) + }; - if (addPlayedIndicator) - { - filename.Append(",pl=true"); - } + /// + /// Gets the cache file path based on a set of parameters. + /// + private string GetCacheFilePath( + string originalPath, + int? width, + int? height, + int? maxWidth, + int? maxHeight, + int? fillWidth, + int? fillHeight, + int quality, + DateTime dateModified, + ImageFormat format, + bool addPlayedIndicator, + double percentPlayed, + int? unwatchedCount, + int? blur, + string backgroundColor, + string foregroundLayer) + { + var filename = new StringBuilder(256); + filename.Append(originalPath); - if (percentPlayed > 0) - { - filename.Append(",p="); - filename.Append(percentPlayed); - } + filename.Append(",quality="); + filename.Append(quality); - if (unwatchedCount.HasValue) - { - filename.Append(",p="); - filename.Append(unwatchedCount.Value); - } + filename.Append(",datemodified="); + filename.Append(dateModified.Ticks); - if (blur.HasValue) - { - filename.Append(",blur="); - filename.Append(blur.Value); - } + filename.Append(",f="); + filename.Append(format); - if (!string.IsNullOrEmpty(backgroundColor)) - { - filename.Append(",b="); - filename.Append(backgroundColor); - } + if (width.HasValue) + { + filename.Append(",width="); + filename.Append(width.Value); + } - if (!string.IsNullOrEmpty(foregroundLayer)) - { - filename.Append(",fl="); - filename.Append(foregroundLayer); - } + if (height.HasValue) + { + filename.Append(",height="); + filename.Append(height.Value); + } - filename.Append(",v="); - filename.Append(Version); + if (maxWidth.HasValue) + { + filename.Append(",maxwidth="); + filename.Append(maxWidth.Value); + } - return GetCachePath(ResizedImageCachePath, filename.ToString(), "." + format.ToString().ToLowerInvariant()); + if (maxHeight.HasValue) + { + filename.Append(",maxheight="); + filename.Append(maxHeight.Value); } - /// - public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info) + if (fillWidth.HasValue) { - int width = info.Width; - int height = info.Height; + filename.Append(",fillwidth="); + filename.Append(fillWidth.Value); + } - if (height > 0 && width > 0) - { - return new ImageDimensions(width, height); - } + if (fillHeight.HasValue) + { + filename.Append(",fillheight="); + filename.Append(fillHeight.Value); + } - string path = info.Path; - _logger.LogDebug("Getting image size for item {ItemType} {Path}", item.GetType().Name, path); + if (addPlayedIndicator) + { + filename.Append(",pl=true"); + } - ImageDimensions size = GetImageDimensions(path); - info.Width = size.Width; - info.Height = size.Height; + if (percentPlayed > 0) + { + filename.Append(",p="); + filename.Append(percentPlayed); + } - return size; + if (unwatchedCount.HasValue) + { + filename.Append(",p="); + filename.Append(unwatchedCount.Value); } - /// - public ImageDimensions GetImageDimensions(string path) - => _imageEncoder.GetImageSize(path); + if (blur.HasValue) + { + filename.Append(",blur="); + filename.Append(blur.Value); + } - /// - public string GetImageBlurHash(string path) + if (!string.IsNullOrEmpty(backgroundColor)) { - var size = GetImageDimensions(path); - return GetImageBlurHash(path, size); + filename.Append(",b="); + filename.Append(backgroundColor); } - /// - public string GetImageBlurHash(string path, ImageDimensions imageDimensions) + if (!string.IsNullOrEmpty(foregroundLayer)) { - if (imageDimensions.Width <= 0 || imageDimensions.Height <= 0) - { - return string.Empty; - } + filename.Append(",fl="); + filename.Append(foregroundLayer); + } + + filename.Append(",v="); + filename.Append(Version); - // 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 * imageDimensions.Width / imageDimensions.Height); - float yCompF = xCompF * imageDimensions.Height / imageDimensions.Width; + return GetCachePath(ResizedImageCachePath, filename.ToString(), "." + format.ToString().ToLowerInvariant()); + } - int xComp = Math.Min((int)xCompF + 1, 9); - int yComp = Math.Min((int)yCompF + 1, 9); + /// + public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info) + { + int width = info.Width; + int height = info.Height; - return _imageEncoder.GetImageBlurHash(xComp, yComp, path); + if (height > 0 && width > 0) + { + return new ImageDimensions(width, height); } - /// - public string GetImageCacheTag(BaseItem item, ItemImageInfo image) - => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); + string path = info.Path; + _logger.LogDebug("Getting image size for item {ItemType} {Path}", item.GetType().Name, path); + + ImageDimensions size = GetImageDimensions(path); + info.Width = size.Width; + info.Height = size.Height; - /// - public string GetImageCacheTag(BaseItem item, ChapterInfo chapter) + return size; + } + + /// + public ImageDimensions GetImageDimensions(string path) + => _imageEncoder.GetImageSize(path); + + /// + public string GetImageBlurHash(string path) + { + var size = GetImageDimensions(path); + return GetImageBlurHash(path, size); + } + + /// + public string GetImageBlurHash(string path, ImageDimensions imageDimensions) + { + if (imageDimensions.Width <= 0 || imageDimensions.Height <= 0) { - return GetImageCacheTag(item, new ItemImageInfo - { - Path = chapter.ImagePath, - Type = ImageType.Chapter, - DateModified = chapter.ImageDateModified - }); + return string.Empty; } - /// - public string? GetImageCacheTag(User user) - { - if (user.ProfileImage is null) - { - return null; - } + // 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 * imageDimensions.Width / imageDimensions.Height); + float yCompF = xCompF * imageDimensions.Height / imageDimensions.Width; - return (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5() - .ToString("N", CultureInfo.InvariantCulture); - } + int xComp = Math.Min((int)xCompF + 1, 9); + int yComp = Math.Min((int)yCompF + 1, 9); + + return _imageEncoder.GetImageBlurHash(xComp, yComp, path); + } + + /// + public string GetImageCacheTag(BaseItem item, ItemImageInfo image) + => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); - private Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) + /// + public string GetImageCacheTag(BaseItem item, ChapterInfo chapter) + { + return GetImageCacheTag(item, new ItemImageInfo { - var inputFormat = Path.GetExtension(originalImagePath.AsSpan()).TrimStart('.').ToString(); + Path = chapter.ImagePath, + Type = ImageType.Chapter, + DateModified = chapter.ImageDateModified + }); + } - // These are just jpg files renamed as tbn - if (string.Equals(inputFormat, "tbn", StringComparison.OrdinalIgnoreCase)) - { - return Task.FromResult((originalImagePath, dateModified)); - } + /// + public string? GetImageCacheTag(User user) + { + if (user.ProfileImage is null) + { + return null; + } - // 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 (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5() + .ToString("N", CultureInfo.InvariantCulture); + } + private Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) + { + var inputFormat = Path.GetExtension(originalImagePath.AsSpan()).TrimStart('.').ToString(); + + // These are just jpg files renamed as tbn + if (string.Equals(inputFormat, "tbn", StringComparison.OrdinalIgnoreCase)) + { return Task.FromResult((originalImagePath, dateModified)); } - /// - /// Gets the cache path. - /// - /// The path. - /// Name of the unique. - /// The file extension. - /// System.String. - /// - /// path - /// or - /// uniqueName - /// or - /// fileExtension. - /// - public string GetCachePath(string path, string uniqueName, string fileExtension) - { - ArgumentException.ThrowIfNullOrEmpty(path); - ArgumentException.ThrowIfNullOrEmpty(uniqueName); - ArgumentException.ThrowIfNullOrEmpty(fileExtension); - - var filename = uniqueName.GetMD5() + fileExtension; - - return GetCachePath(path, filename); - } + // 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)); + } - /// - /// Gets the cache path. - /// - /// The path. - /// The filename. - /// System.String. - /// - /// path - /// or - /// filename. - /// - public string GetCachePath(ReadOnlySpan path, ReadOnlySpan filename) - { - if (path.IsEmpty) - { - throw new ArgumentException("Path can't be empty.", nameof(path)); - } + /// + /// Gets the cache path. + /// + /// The path. + /// Name of the unique. + /// The file extension. + /// System.String. + /// + /// path + /// or + /// uniqueName + /// or + /// fileExtension. + /// + public string GetCachePath(string path, string uniqueName, string fileExtension) + { + ArgumentException.ThrowIfNullOrEmpty(path); + ArgumentException.ThrowIfNullOrEmpty(uniqueName); + ArgumentException.ThrowIfNullOrEmpty(fileExtension); - if (filename.IsEmpty) - { - throw new ArgumentException("Filename can't be empty.", nameof(filename)); - } + var filename = uniqueName.GetMD5() + fileExtension; - var prefix = filename.Slice(0, 1); + return GetCachePath(path, filename); + } - return Path.Join(path, prefix, filename); + /// + /// Gets the cache path. + /// + /// The path. + /// The filename. + /// System.String. + /// + /// path + /// or + /// filename. + /// + public string GetCachePath(ReadOnlySpan path, ReadOnlySpan filename) + { + if (path.IsEmpty) + { + throw new ArgumentException("Path can't be empty.", nameof(path)); } - /// - public void CreateImageCollage(ImageCollageOptions options, string? libraryName) + if (filename.IsEmpty) { - _logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath); + throw new ArgumentException("Filename can't be empty.", nameof(filename)); + } - _imageEncoder.CreateImageCollage(options, libraryName); + var prefix = filename.Slice(0, 1); - _logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath); - } + return Path.Join(path, prefix, filename); + } - /// - public void Dispose() - { - if (_disposed) - { - return; - } + /// + public void CreateImageCollage(ImageCollageOptions options, string? libraryName) + { + _logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath); - if (_imageEncoder is IDisposable disposable) - { - disposable.Dispose(); - } + _imageEncoder.CreateImageCollage(options, libraryName); + + _logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath); + } + + /// + public void Dispose() + { + if (_disposed) + { + return; + } - _disposed = true; + if (_imageEncoder is IDisposable disposable) + { + disposable.Dispose(); } + + _disposed = true; } } diff --git a/src/Jellyfin.Drawing/NullImageEncoder.cs b/src/Jellyfin.Drawing/NullImageEncoder.cs index 24dda108ec..171128bed3 100644 --- a/src/Jellyfin.Drawing/NullImageEncoder.cs +++ b/src/Jellyfin.Drawing/NullImageEncoder.cs @@ -3,56 +3,55 @@ using System.Collections.Generic; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Drawing; -namespace Jellyfin.Drawing +namespace Jellyfin.Drawing; + +/// +/// A fallback implementation of . +/// +public class NullImageEncoder : IImageEncoder { - /// - /// A fallback implementation of . - /// - public class NullImageEncoder : IImageEncoder - { - /// - public IReadOnlyCollection SupportedInputFormats - => new HashSet(StringComparer.OrdinalIgnoreCase) { "png", "jpeg", "jpg" }; + /// + public IReadOnlyCollection SupportedInputFormats + => new HashSet(StringComparer.OrdinalIgnoreCase) { "png", "jpeg", "jpg" }; - /// - public IReadOnlyCollection SupportedOutputFormats + /// + public IReadOnlyCollection SupportedOutputFormats => new HashSet() { ImageFormat.Jpg, ImageFormat.Png }; - /// - public string Name => "Null Image Encoder"; - - /// - public bool SupportsImageCollageCreation => false; - - /// - public bool SupportsImageEncoding => false; - - /// - public ImageDimensions GetImageSize(string path) - => throw new NotImplementedException(); - - /// - public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat) - { - throw new NotImplementedException(); - } - - /// - public void CreateImageCollage(ImageCollageOptions options, string? libraryName) - { - throw new NotImplementedException(); - } - - /// - public void CreateSplashscreen(IReadOnlyList posters, IReadOnlyList backdrops) - { - throw new NotImplementedException(); - } - - /// - public string GetImageBlurHash(int xComp, int yComp, string path) - { - throw new NotImplementedException(); - } + /// + public string Name => "Null Image Encoder"; + + /// + public bool SupportsImageCollageCreation => false; + + /// + public bool SupportsImageEncoding => false; + + /// + public ImageDimensions GetImageSize(string path) + => throw new NotImplementedException(); + + /// + public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat) + { + throw new NotImplementedException(); + } + + /// + public void CreateImageCollage(ImageCollageOptions options, string? libraryName) + { + throw new NotImplementedException(); + } + + /// + public void CreateSplashscreen(IReadOnlyList posters, IReadOnlyList backdrops) + { + throw new NotImplementedException(); + } + + /// + public string GetImageBlurHash(int xComp, int yComp, string path) + { + throw new NotImplementedException(); } }