diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs index 64393669b3..c12fc21130 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs @@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks progress.Report(0); - _imageGenerator.GenerateSplashscreen(Path.Combine(_applicationPaths.DataPath, "splashscreen.webp")); + _imageGenerator.Generate(GeneratedImageType.Splashscreen, Path.Combine(_applicationPaths.DataPath, "splashscreen.webp")); return ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken); } diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 24059cddd3..6d34ca7708 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -1705,18 +1706,18 @@ namespace Jellyfin.Api.Controllers /// /// Generates or gets the splashscreen. /// - /// Optional. Supply the cache tag from the item object to receive strong caching headers. + /// Supply the cache tag from the item object to receive strong caching headers. /// Determines the output format of the image - original,gif,jpg,png. /// The maximum image width to return. /// The maximum image height to return. /// The fixed image width to return. /// The fixed image height to return. - /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. /// Width of box to fill. /// Height of box to fill. - /// Optional. Blur image. - /// Optional. Apply a background color for transparent images. - /// Optional. Apply a foreground layer on top of the image. + /// Blur image. + /// Apply a background color for transparent images. + /// Apply a foreground layer on top of the image. + /// Quality setting, from 0-100. /// Splashscreen returned successfully. /// The splashscreen. [HttpGet("Branding/Splashscreen")] @@ -1729,12 +1730,12 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? maxHeight, [FromQuery] int? width, [FromQuery] int? height, - [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, [FromQuery] int? blur, [FromQuery] string? backgroundColor, - [FromQuery] string? foregroundLayer) + [FromQuery] string? foregroundLayer, + [FromQuery, Range(0, 100)] int quality = 90) { string splashscreenPath; var brandingOptions = _serverConfigurationManager.GetConfiguration("branding"); @@ -1746,9 +1747,9 @@ namespace Jellyfin.Api.Controllers { splashscreenPath = Path.Combine(_appPaths.DataPath, "splashscreen.webp"); - if (!System.IO.File.Exists(splashscreenPath) && _imageGenerator.GetSupportedImages().Contains(GeneratedImages.Splashscreen)) + if (!System.IO.File.Exists(splashscreenPath) && _imageGenerator.GetSupportedImages().Contains(GeneratedImageType.Splashscreen)) { - _imageGenerator.GenerateSplashscreen(splashscreenPath); + _imageGenerator.Generate(GeneratedImageType.Splashscreen, splashscreenPath); } } @@ -1771,18 +1772,20 @@ namespace Jellyfin.Api.Controllers MaxWidth = maxWidth, FillHeight = fillHeight, FillWidth = fillWidth, - Quality = quality ?? 100, + Quality = quality, Width = width, Blur = blur, BackgroundColor = backgroundColor, ForegroundLayer = foregroundLayer, SupportedOutputFormats = outputFormats }; + return await GetImageResult( - options, - cacheDuration, - new Dictionary(), - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)); + options, + cacheDuration, + ImmutableDictionary.Empty, + Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + .ConfigureAwait(false); } /// @@ -1815,7 +1818,6 @@ namespace Jellyfin.Api.Controllers brandingOptions.SplashscreenLocation = filePath; _serverConfigurationManager.SaveConfiguration("branding", brandingOptions); - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . await using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) { await memoryStream.CopyToAsync(fs, CancellationToken.None).ConfigureAwait(false); diff --git a/Jellyfin.Drawing.Skia/DefaultImageGenerator.cs b/Jellyfin.Drawing.Skia/DefaultImageGenerator.cs index 4d029d9069..e102b8f49a 100644 --- a/Jellyfin.Drawing.Skia/DefaultImageGenerator.cs +++ b/Jellyfin.Drawing.Skia/DefaultImageGenerator.cs @@ -10,74 +10,73 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; -namespace Jellyfin.Drawing.Skia +namespace Jellyfin.Drawing.Skia; + +/// +/// The default image generator. +/// +public class DefaultImageGenerator : IImageGenerator { + private readonly IImageEncoder _imageEncoder; + private readonly IItemRepository _itemRepository; + private readonly ILogger _logger; + /// - /// The default image generator. + /// Initializes a new instance of the class. /// - public class DefaultImageGenerator : IImageGenerator + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public DefaultImageGenerator( + IImageEncoder imageEncoder, + IItemRepository itemRepository, + ILogger logger) { - private readonly IImageEncoder _imageEncoder; - private readonly IItemRepository _itemRepository; - private readonly ILogger _logger; + _imageEncoder = imageEncoder; + _itemRepository = itemRepository; + _logger = 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 IReadOnlyList GetSupportedImages() + { + return new[] { GeneratedImageType.Splashscreen }; + } - /// - public GeneratedImages[] GetSupportedImages() + /// + public void Generate(GeneratedImageType imageTypeType, string outputPath) + { + 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) { - return new[] { GeneratedImages.Splashscreen }; + // 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(); } - /// - public void GenerateSplashscreen(string outputPath) - { - 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, outputPath); - } + var splashBuilder = new SplashscreenBuilder((SkiaEncoder)_imageEncoder); + splashBuilder.GenerateSplash(posters, landscape, outputPath); + } - private IReadOnlyList GetItemsWithImageType(ImageType imageType) + private IReadOnlyList GetItemsWithImageType(ImageType imageType) + { + // todo make included libraries configurable + return _itemRepository.GetItemList(new InternalItemsQuery { - // todo make included libraries configurable - return _itemRepository.GetItemList(new InternalItemsQuery + CollapseBoxSetItems = false, + Recursive = true, + DtoOptions = new DtoOptions(false), + ImageTypes = new[] { imageType }, + Limit = 30, + // todo max parental rating configurable + MaxParentalRating = 10, + OrderBy = new ValueTuple[] { - 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" } - }); - } + new(ItemSortBy.Random, SortOrder.Ascending) + }, + IncludeItemTypes = new[] { BaseItemKind.Movie, BaseItemKind.Series } + }); } } diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index ac7ab2dee0..b2f9a518a4 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -86,7 +86,7 @@ namespace Jellyfin.Server serviceCollection.AddSingleton(); // TODO search plugins - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); // TODO search the assemblies instead of adding them manually? serviceCollection.AddSingleton(); diff --git a/MediaBrowser.Controller/Drawing/GeneratedImageType.cs b/MediaBrowser.Controller/Drawing/GeneratedImageType.cs new file mode 100644 index 0000000000..a8db86d4f6 --- /dev/null +++ b/MediaBrowser.Controller/Drawing/GeneratedImageType.cs @@ -0,0 +1,12 @@ +namespace MediaBrowser.Controller.Drawing; + +/// +/// Which generated image type the supports. +/// +public enum GeneratedImageType +{ + /// + /// The splashscreen. + /// + Splashscreen = 0 +} diff --git a/MediaBrowser.Controller/Drawing/GeneratedImages.cs b/MediaBrowser.Controller/Drawing/GeneratedImages.cs deleted file mode 100644 index 47b60979b2..0000000000 --- a/MediaBrowser.Controller/Drawing/GeneratedImages.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MediaBrowser.Controller.Drawing -{ - /// - /// Which generated images an supports. - /// - public enum GeneratedImages - { - /// - /// The splashscreen. - /// - Splashscreen - } -} diff --git a/MediaBrowser.Controller/Drawing/IImageGenerator.cs b/MediaBrowser.Controller/Drawing/IImageGenerator.cs index f7e7f83b2e..773db02cb9 100644 --- a/MediaBrowser.Controller/Drawing/IImageGenerator.cs +++ b/MediaBrowser.Controller/Drawing/IImageGenerator.cs @@ -1,20 +1,22 @@ -namespace MediaBrowser.Controller.Drawing +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Drawing; + +/// +/// Interface for an image generator. +/// +public interface IImageGenerator { /// - /// Interface for an image generator. + /// Gets the supported generated images of the image generator. /// - public interface IImageGenerator - { - /// - /// Gets the supported generated images of the image generator. - /// - /// The supported images. - GeneratedImages[] GetSupportedImages(); + /// The supported generated image types. + IReadOnlyList GetSupportedImages(); - /// - /// Generates a splashscreen. - /// - /// The path where the splashscreen should be saved. - void GenerateSplashscreen(string outputPath); - } + /// + /// Generates a splashscreen. + /// + /// The image to generate. + /// The path where the splashscreen should be saved. + void Generate(GeneratedImageType imageTypeType, string outputPath); } diff --git a/MediaBrowser.Model/Branding/BrandingOptions.cs b/MediaBrowser.Model/Branding/BrandingOptions.cs index 56e5a87152..01db708856 100644 --- a/MediaBrowser.Model/Branding/BrandingOptions.cs +++ b/MediaBrowser.Model/Branding/BrandingOptions.cs @@ -1,38 +1,38 @@ using System.Text.Json.Serialization; using System.Xml.Serialization; -#pragma warning disable CS1591 +namespace MediaBrowser.Model.Branding; -namespace MediaBrowser.Model.Branding +/// +/// The branding options. +/// +public class BrandingOptions { - public class BrandingOptions - { - /// - /// Gets or sets the login disclaimer. - /// - /// The login disclaimer. - public string? LoginDisclaimer { get; set; } + /// + /// Gets or sets the login disclaimer. + /// + /// The login disclaimer. + public string? LoginDisclaimer { get; set; } - /// - /// Gets or sets the custom CSS. - /// - /// The custom CSS. - public string? CustomCss { get; set; } + /// + /// Gets or sets the custom CSS. + /// + /// The custom CSS. + public string? CustomCss { get; set; } - /// - /// Gets or sets the splashscreen location on disk. - /// - /// - /// Not served via the API. - /// Only used to save the custom uploaded user splashscreen in the configuration file. - /// - [JsonIgnore] - public string? SplashscreenLocation { get; set; } + /// + /// Gets or sets the splashscreen location on disk. + /// + /// + /// Not served via the API. + /// Only used to save the custom uploaded user splashscreen in the configuration file. + /// + [JsonIgnore] + public string? SplashscreenLocation { get; set; } - /// - /// Gets the splashscreen url. - /// - [XmlIgnore] - public string? SplashscreenUrl => "/Branding/Splashscreen"; - } + /// + /// Gets the splashscreen url. + /// + [XmlIgnore] + public string SplashscreenUrl => "/Branding/Splashscreen"; }