diff --git a/Jellyfin.Api/Controllers/SplashscreenController.cs b/Jellyfin.Api/Controllers/SplashscreenController.cs index 0fc9af60bb..48a559b281 100644 --- a/Jellyfin.Api/Controllers/SplashscreenController.cs +++ b/Jellyfin.Api/Controllers/SplashscreenController.cs @@ -52,8 +52,6 @@ namespace Jellyfin.Api.Controllers /// Generates or gets the splashscreen. /// /// Darken the generated image. - /// The image width. - /// The image height. /// Whether to regenerate the image, regardless if one already exists. /// The splashscreen. [HttpGet] @@ -62,11 +60,9 @@ namespace Jellyfin.Api.Controllers [ProducesImageFile] public ActionResult GetSplashscreen( [FromQuery] bool? darken = false, - [FromQuery] int? width = 1920, - [FromQuery] int? height = 1080, [FromQuery] bool? regenerate = false) { - var outputPath = Path.Combine(_appPaths.DataPath, $"splashscreen-{width}x{height}-{darken}.jpg"); + var outputPath = Path.Combine(_appPaths.DataPath, $"splashscreen-{darken}.jpg"); if (!System.IO.File.Exists(outputPath) || (regenerate ?? false)) { @@ -74,11 +70,13 @@ namespace Jellyfin.Api.Controllers 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, width!.Value, height!.Value, darken!.Value)); + _imageEncoder.CreateSplashscreen(new SplashscreenOptions(posters, landscape, outputPath, darken!.Value)); } return PhysicalFile(outputPath, MimeTypes.GetMimeType(outputPath)); @@ -86,13 +84,16 @@ namespace Jellyfin.Api.Controllers 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 = 8, + Limit = 30, + // todo max parental rating configurable + MaxParentalRating = 10, OrderBy = new ValueTuple[] { new ValueTuple(ItemSortBy.Random, SortOrder.Ascending) diff --git a/Jellyfin.Drawing.Skia/SplashscreenBuilder.cs b/Jellyfin.Drawing.Skia/SplashscreenBuilder.cs index 8b6942be0c..4773464b43 100644 --- a/Jellyfin.Drawing.Skia/SplashscreenBuilder.cs +++ b/Jellyfin.Drawing.Skia/SplashscreenBuilder.cs @@ -10,14 +10,17 @@ namespace Jellyfin.Drawing.Skia /// public class SplashscreenBuilder { + private const int FinalWidth = 1920; + private const int FinalHeight = 1080; + // generated collage resolution should be higher than the final resolution + private const int WallWidth = FinalWidth * 3; + private const int WallHeight = FinalHeight * 2; private const int Rows = 6; private const int Spacing = 20; private readonly SkiaEncoder _skiaEncoder; private Random? _random; - private int _finalWidth; - private int _finalHeight; /// /// Initializes a new instance of the class. @@ -34,13 +37,11 @@ namespace Jellyfin.Drawing.Skia /// The options to generate the splashscreen. public void GenerateSplash(SplashscreenOptions options) { - _finalWidth = options.Width; - _finalHeight = options.Height; var wall = GenerateCollage(options.PortraitInputPaths, options.LandscapeInputPaths, options.ApplyFilter); var transformed = Transform3D(wall); using var outputStream = new SKFileWStream(options.OutputPath); - using var pixmap = new SKPixmap(new SKImageInfo(_finalWidth, _finalHeight), transformed.GetPixels()); + using var pixmap = new SKPixmap(new SKImageInfo(FinalWidth, FinalHeight), transformed.GetPixels()); pixmap.Encode(outputStream, StripCollageBuilder.GetEncodedFormat(options.OutputPath), 90); } @@ -58,12 +59,11 @@ namespace Jellyfin.Drawing.Skia var posterIndex = 0; var backdropIndex = 0; - // use higher resolution than final image - var bitmap = new SKBitmap(_finalWidth * 3, _finalHeight * 2); + var bitmap = new SKBitmap(WallWidth, WallHeight); using var canvas = new SKCanvas(bitmap); canvas.Clear(SKColors.Black); - int posterHeight = _finalHeight * 2 / 6; + int posterHeight = WallHeight / 6; for (int i = 0; i < Rows; i++) { @@ -71,7 +71,7 @@ namespace Jellyfin.Drawing.Skia int currentWidthPos = i * 75; int currentHeight = i * (posterHeight + Spacing); - while (currentWidthPos < _finalWidth * 3) + while (currentWidthPos < WallWidth) { SKBitmap? currentImage; @@ -124,7 +124,7 @@ namespace Jellyfin.Drawing.Skia Color = SKColors.Black.WithAlpha(0x50), Style = SKPaintStyle.Fill }; - canvas.DrawRect(0, 0, _finalWidth * 3, _finalHeight * 2, paintColor); + canvas.DrawRect(0, 0, WallWidth, WallHeight, paintColor); } return bitmap; @@ -137,7 +137,7 @@ namespace Jellyfin.Drawing.Skia /// The transformed image. private SKBitmap Transform3D(SKBitmap input) { - var bitmap = new SKBitmap(_finalWidth, _finalHeight); + var bitmap = new SKBitmap(FinalWidth, FinalHeight); using var canvas = new SKCanvas(bitmap); canvas.Clear(SKColors.Black); var matrix = new SKMatrix diff --git a/MediaBrowser.Controller/Drawing/SplashscreenOptions.cs b/MediaBrowser.Controller/Drawing/SplashscreenOptions.cs index d70773d8f8..0534d60b69 100644 --- a/MediaBrowser.Controller/Drawing/SplashscreenOptions.cs +++ b/MediaBrowser.Controller/Drawing/SplashscreenOptions.cs @@ -16,13 +16,11 @@ namespace MediaBrowser.Controller.Drawing /// Optional. The image width. /// Optional. The image height. /// Optional. Apply a darkening filter. - public SplashscreenOptions(IReadOnlyList portraitInputPaths, IReadOnlyList landscapeInputPaths, string outputPath, int width = 1920, int height = 1080, bool applyFilter = false) + public SplashscreenOptions(IReadOnlyList portraitInputPaths, IReadOnlyList landscapeInputPaths, string outputPath, bool applyFilter = false) { PortraitInputPaths = portraitInputPaths; LandscapeInputPaths = landscapeInputPaths; OutputPath = outputPath; - Width = width; - Height = height; ApplyFilter = applyFilter; } @@ -41,16 +39,6 @@ namespace MediaBrowser.Controller.Drawing /// public string OutputPath { get; set; } - /// - /// Gets or sets the width. - /// - public int Width { get; set; } - - /// - /// Gets or sets the height. - /// - public int Height { get; set; } - /// /// Gets or sets a value indicating whether to apply a darkening filter at the end. ///