From 3fb3ee074a375d0afd4d7d81252ba535d885afb8 Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Tue, 17 Aug 2021 18:11:38 +0200 Subject: [PATCH] Remove splashscreen generation from IImageEncoder and add IImageGenerator --- Emby.Drawing/NullImageEncoder.cs | 6 - .../Controllers/SplashscreenController.cs | 105 ------------------ .../DefaultImageGenerator.cs | 83 ++++++++++++++ Jellyfin.Drawing.Skia/SkiaEncoder.cs | 7 -- Jellyfin.Drawing.Skia/SplashscreenBuilder.cs | 20 ++-- Jellyfin.Server/CoreAppHost.cs | 3 + .../Drawing/GeneratedImages.cs | 13 +++ .../Drawing/IImageEncoder.cs | 6 - .../Drawing/IImageGenerator.cs | 17 +++ .../Drawing/SplashscreenOptions.cs | 20 +--- 10 files changed, 128 insertions(+), 152 deletions(-) delete mode 100644 Jellyfin.Api/Controllers/SplashscreenController.cs create mode 100644 Jellyfin.Drawing.Skia/DefaultImageGenerator.cs create mode 100644 MediaBrowser.Controller/Drawing/GeneratedImages.cs create mode 100644 MediaBrowser.Controller/Drawing/IImageGenerator.cs diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs index ed12f6acb2..1c05aa9161 100644 --- a/Emby.Drawing/NullImageEncoder.cs +++ b/Emby.Drawing/NullImageEncoder.cs @@ -43,12 +43,6 @@ namespace Emby.Drawing throw new NotImplementedException(); } - /// - public void CreateSplashscreen(SplashscreenOptions options) - { - throw new NotImplementedException(); - } - /// public string GetImageBlurHash(int xComp, int yComp, string path) { diff --git a/Jellyfin.Api/Controllers/SplashscreenController.cs b/Jellyfin.Api/Controllers/SplashscreenController.cs deleted file mode 100644 index 48a559b281..0000000000 --- a/Jellyfin.Api/Controllers/SplashscreenController.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Jellyfin.Api.Attributes; -using Jellyfin.Data.Enums; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.Querying; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace Jellyfin.Api.Controllers -{ - /// - /// Splashscreen controller. - /// - [Route("Splashscreen")] - public class SplashscreenController : BaseJellyfinApiController - { - private readonly IImageEncoder _imageEncoder; - private readonly IItemRepository _itemRepository; - private readonly IApplicationPaths _appPaths; - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - public SplashscreenController( - IImageEncoder imageEncoder, - IItemRepository itemRepository, - IApplicationPaths applicationPaths, - ILogger logger) - { - _imageEncoder = imageEncoder; - _itemRepository = itemRepository; - _appPaths = applicationPaths; - _logger = logger; - } - - /// - /// Generates or gets the splashscreen. - /// - /// Darken the generated image. - /// Whether to regenerate the image, regardless if one already exists. - /// The splashscreen. - [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - [ProducesImageFile] - public ActionResult GetSplashscreen( - [FromQuery] bool? darken = false, - [FromQuery] bool? regenerate = false) - { - var outputPath = Path.Combine(_appPaths.DataPath, $"splashscreen-{darken}.jpg"); - - if (!System.IO.File.Exists(outputPath) || (regenerate ?? false)) - { - var posters = GetItemsWithImageType(ImageType.Primary).Select(x => x.GetImages(ImageType.Primary).First().Path).ToList(); - var landscape = GetItemsWithImageType(ImageType.Thumb).Select(x => x.GetImages(ImageType.Thumb).First().Path).ToList(); - if (landscape.Count == 0) - { - // Thumb images fit better because they include the title in the image but are not provided with TMDb. - // Using backdrops as a fallback to generate an image at all - _logger.LogDebug("No thumb images found. Using backdrops to generate splashscreen."); - landscape = GetItemsWithImageType(ImageType.Backdrop).Select(x => x.GetImages(ImageType.Backdrop).First().Path).ToList(); - } - - _imageEncoder.CreateSplashscreen(new SplashscreenOptions(posters, landscape, outputPath, darken!.Value)); - } - - return PhysicalFile(outputPath, MimeTypes.GetMimeType(outputPath)); - } - - private IReadOnlyList GetItemsWithImageType(ImageType imageType) - { - // todo make included libraries configurable - return _itemRepository.GetItemList(new InternalItemsQuery - { - CollapseBoxSetItems = false, - Recursive = true, - DtoOptions = new DtoOptions(false), - ImageTypes = new ImageType[] { imageType }, - Limit = 30, - // todo max parental rating configurable - MaxParentalRating = 10, - OrderBy = new ValueTuple[] - { - new ValueTuple(ItemSortBy.Random, SortOrder.Ascending) - }, - IncludeItemTypes = new string[] { "Movie", "Series" } - }); - } - } -} diff --git a/Jellyfin.Drawing.Skia/DefaultImageGenerator.cs b/Jellyfin.Drawing.Skia/DefaultImageGenerator.cs new file mode 100644 index 0000000000..780d0b060c --- /dev/null +++ b/Jellyfin.Drawing.Skia/DefaultImageGenerator.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Drawing.Skia +{ + /// + /// The default image generator. + /// + public class DefaultImageGenerator : IImageGenerator + { + private readonly IImageEncoder _imageEncoder; + private readonly IItemRepository _itemRepository; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public DefaultImageGenerator( + IImageEncoder imageEncoder, + IItemRepository itemRepository, + ILogger logger) + { + _imageEncoder = imageEncoder; + _itemRepository = itemRepository; + _logger = logger; + } + + /// + public GeneratedImages[] GetSupportedImages() + { + return new[] { GeneratedImages.Splashscreen }; + } + + /// + public void GenerateSplashscreen(SplashscreenOptions generationOptions) + { + var posters = GetItemsWithImageType(ImageType.Primary).Select(x => x.GetImages(ImageType.Primary).First().Path).ToList(); + var landscape = GetItemsWithImageType(ImageType.Thumb).Select(x => x.GetImages(ImageType.Thumb).First().Path).ToList(); + if (landscape.Count == 0) + { + // Thumb images fit better because they include the title in the image but are not provided with TMDb. + // Using backdrops as a fallback to generate an image at all + _logger.LogDebug("No thumb images found. Using backdrops to generate splashscreen."); + landscape = GetItemsWithImageType(ImageType.Backdrop).Select(x => x.GetImages(ImageType.Backdrop).First().Path).ToList(); + } + + var splashBuilder = new SplashscreenBuilder((SkiaEncoder)_imageEncoder); + splashBuilder.GenerateSplash(posters, landscape, generationOptions.OutputPath, generationOptions.ApplyFilter); + } + + private IReadOnlyList GetItemsWithImageType(ImageType imageType) + { + // todo make included libraries configurable + return _itemRepository.GetItemList(new InternalItemsQuery + { + CollapseBoxSetItems = false, + Recursive = true, + DtoOptions = new DtoOptions(false), + ImageTypes = new ImageType[] { imageType }, + Limit = 30, + // todo max parental rating configurable + MaxParentalRating = 10, + OrderBy = new ValueTuple[] + { + new ValueTuple(ItemSortBy.Random, SortOrder.Ascending) + }, + IncludeItemTypes = new string[] { "Movie", "Series" } + }); + } + } +} diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 16de5d7fde..6d0a5ac2b9 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -492,13 +492,6 @@ namespace Jellyfin.Drawing.Skia } } - /// - public void CreateSplashscreen(SplashscreenOptions options) - { - var splashBuilder = new SplashscreenBuilder(this); - splashBuilder.GenerateSplash(options); - } - private void DrawIndicator(SKCanvas canvas, int imageWidth, int imageHeight, ImageProcessingOptions options) { try diff --git a/Jellyfin.Drawing.Skia/SplashscreenBuilder.cs b/Jellyfin.Drawing.Skia/SplashscreenBuilder.cs index 4773464b43..7cb10bfeef 100644 --- a/Jellyfin.Drawing.Skia/SplashscreenBuilder.cs +++ b/Jellyfin.Drawing.Skia/SplashscreenBuilder.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using MediaBrowser.Controller.Drawing; using SkiaSharp; namespace Jellyfin.Drawing.Skia @@ -34,25 +33,28 @@ namespace Jellyfin.Drawing.Skia /// /// Generate a splashscreen. /// - /// The options to generate the splashscreen. - public void GenerateSplash(SplashscreenOptions options) + /// The poster paths. + /// The landscape paths. + /// The output path. + /// Whether to apply the darkening filter. + public void GenerateSplash(IReadOnlyList posters, IReadOnlyList backdrop, string outputPath, bool applyFilter) { - var wall = GenerateCollage(options.PortraitInputPaths, options.LandscapeInputPaths, options.ApplyFilter); + var wall = GenerateCollage(posters, backdrop, applyFilter); var transformed = Transform3D(wall); - using var outputStream = new SKFileWStream(options.OutputPath); + using var outputStream = new SKFileWStream(outputPath); using var pixmap = new SKPixmap(new SKImageInfo(FinalWidth, FinalHeight), transformed.GetPixels()); - pixmap.Encode(outputStream, StripCollageBuilder.GetEncodedFormat(options.OutputPath), 90); + pixmap.Encode(outputStream, StripCollageBuilder.GetEncodedFormat(outputPath), 90); } /// /// Generates a collage of posters and landscape pictures. /// - /// The poster paths. + /// The poster paths. /// The landscape paths. /// Whether to apply the darkening filter. /// The created collage as a bitmap. - private SKBitmap GenerateCollage(IReadOnlyList poster, IReadOnlyList backdrop, bool applyFilter) + private SKBitmap GenerateCollage(IReadOnlyList posters, IReadOnlyList backdrop, bool applyFilter) { _random = new Random(); @@ -80,7 +82,7 @@ namespace Jellyfin.Drawing.Skia case 0: case 2: case 3: - currentImage = SkiaHelper.GetNextValidImage(_skiaEncoder, poster, posterIndex, out int newPosterIndex); + currentImage = SkiaHelper.GetNextValidImage(_skiaEncoder, posters, posterIndex, out int newPosterIndex); posterIndex = newPosterIndex; break; default: diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 67e50b92d9..ac7ab2dee0 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -85,6 +85,9 @@ namespace Jellyfin.Server serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + // TODO search plugins + ServiceCollection.AddSingleton(); + // TODO search the assemblies instead of adding them manually? serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/MediaBrowser.Controller/Drawing/GeneratedImages.cs b/MediaBrowser.Controller/Drawing/GeneratedImages.cs new file mode 100644 index 0000000000..47b60979b2 --- /dev/null +++ b/MediaBrowser.Controller/Drawing/GeneratedImages.cs @@ -0,0 +1,13 @@ +namespace MediaBrowser.Controller.Drawing +{ + /// + /// Which generated images an supports. + /// + public enum GeneratedImages + { + /// + /// The splashscreen. + /// + Splashscreen + } +} diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index 57d73699f6..4e67cfee4f 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -74,11 +74,5 @@ namespace MediaBrowser.Controller.Drawing /// The options to use when creating the collage. /// Optional. void CreateImageCollage(ImageCollageOptions options, string? libraryName); - - /// - /// Creates a splashscreen image. - /// - /// The options to use when creating the splashscreen. - void CreateSplashscreen(SplashscreenOptions options); } } diff --git a/MediaBrowser.Controller/Drawing/IImageGenerator.cs b/MediaBrowser.Controller/Drawing/IImageGenerator.cs new file mode 100644 index 0000000000..21699c3f0b --- /dev/null +++ b/MediaBrowser.Controller/Drawing/IImageGenerator.cs @@ -0,0 +1,17 @@ +namespace MediaBrowser.Controller.Drawing +{ + public interface IImageGenerator + { + /// + /// Gets the supported generated images of the image generator. + /// + /// The supported images. + GeneratedImages[] GetSupportedImages(); + + /// + /// Generates a splashscreen. + /// + /// The options used to generate the splashscreen. + void GenerateSplashscreen(SplashscreenOptions generationOptions); + } +} diff --git a/MediaBrowser.Controller/Drawing/SplashscreenOptions.cs b/MediaBrowser.Controller/Drawing/SplashscreenOptions.cs index 0534d60b69..ba268b8eb7 100644 --- a/MediaBrowser.Controller/Drawing/SplashscreenOptions.cs +++ b/MediaBrowser.Controller/Drawing/SplashscreenOptions.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; - namespace MediaBrowser.Controller.Drawing { /// @@ -10,30 +8,14 @@ namespace MediaBrowser.Controller.Drawing /// /// Initializes a new instance of the class. /// - /// The portrait input paths. - /// The landscape input paths. /// The output path. - /// Optional. The image width. - /// Optional. The image height. /// Optional. Apply a darkening filter. - public SplashscreenOptions(IReadOnlyList portraitInputPaths, IReadOnlyList landscapeInputPaths, string outputPath, bool applyFilter = false) + public SplashscreenOptions(string outputPath, bool applyFilter = false) { - PortraitInputPaths = portraitInputPaths; - LandscapeInputPaths = landscapeInputPaths; OutputPath = outputPath; ApplyFilter = applyFilter; } - /// - /// Gets or sets the poster input paths. - /// - public IReadOnlyList PortraitInputPaths { get; set; } - - /// - /// Gets or sets the landscape input paths. - /// - public IReadOnlyList LandscapeInputPaths { get; set; } - /// /// Gets or sets the output path. ///