diff options
28 files changed, 214 insertions, 92 deletions
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 0ce967e6a..5b0fc9ef3 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -41,7 +41,6 @@ namespace Emby.Server.Implementations.Dto private readonly ILogger<DtoService> _logger; private readonly ILibraryManager _libraryManager; private readonly IUserDataManager _userDataRepository; - private readonly IItemRepository _itemRepo; private readonly IImageProcessor _imageProcessor; private readonly IProviderManager _providerManager; @@ -58,7 +57,6 @@ namespace Emby.Server.Implementations.Dto ILogger<DtoService> logger, ILibraryManager libraryManager, IUserDataManager userDataRepository, - IItemRepository itemRepo, IImageProcessor imageProcessor, IProviderManager providerManager, IRecordingsManager recordingsManager, @@ -71,7 +69,6 @@ namespace Emby.Server.Implementations.Dto _logger = logger; _libraryManager = libraryManager; _userDataRepository = userDataRepository; - _itemRepo = itemRepo; _imageProcessor = imageProcessor; _providerManager = providerManager; _recordingsManager = recordingsManager; @@ -99,11 +96,11 @@ namespace Emby.Server.Implementations.Dto if (item is LiveTvChannel tvChannel) { - (channelTuples ??= new()).Add((dto, tvChannel)); + (channelTuples ??= []).Add((dto, tvChannel)); } else if (item is LiveTvProgram) { - (programTuples ??= new()).Add((item, dto)); + (programTuples ??= []).Add((item, dto)); } if (item is IItemByName byName) @@ -590,12 +587,12 @@ namespace Emby.Server.Implementations.Dto if (dto.ImageBlurHashes is not null) { // Only add BlurHash for the person's image. - baseItemPerson.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>(); + baseItemPerson.ImageBlurHashes = []; foreach (var (imageType, blurHash) in dto.ImageBlurHashes) { if (blurHash is not null) { - baseItemPerson.ImageBlurHashes[imageType] = new Dictionary<string, string>(); + baseItemPerson.ImageBlurHashes[imageType] = []; foreach (var (imageId, blurHashValue) in blurHash) { if (string.Equals(baseItemPerson.PrimaryImageTag, imageId, StringComparison.OrdinalIgnoreCase)) @@ -674,11 +671,11 @@ namespace Emby.Server.Implementations.Dto if (!string.IsNullOrEmpty(image.BlurHash)) { - dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>(); + dto.ImageBlurHashes ??= []; if (!dto.ImageBlurHashes.TryGetValue(image.Type, out var value)) { - value = new Dictionary<string, string>(); + value = []; dto.ImageBlurHashes[image.Type] = value; } @@ -709,7 +706,7 @@ namespace Emby.Server.Implementations.Dto if (hashes.Count > 0) { - dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>(); + dto.ImageBlurHashes ??= []; dto.ImageBlurHashes[imageType] = hashes; } @@ -756,7 +753,7 @@ namespace Emby.Server.Implementations.Dto dto.AspectRatio = hasAspectRatio.AspectRatio; } - dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>(); + dto.ImageBlurHashes = []; var backdropLimit = options.GetImageLimit(ImageType.Backdrop); if (backdropLimit > 0) @@ -772,7 +769,7 @@ namespace Emby.Server.Implementations.Dto if (options.EnableImages) { - dto.ImageTags = new Dictionary<ImageType, string>(); + dto.ImageTags = []; // Prevent implicitly captured closure var currentItem = item; diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index ac3e10594..924f50286 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1808,7 +1808,6 @@ namespace Emby.Server.Implementations.Session fields.Remove(ItemFields.DateLastSaved); fields.Remove(ItemFields.DisplayPreferencesId); fields.Remove(ItemFields.Etag); - fields.Remove(ItemFields.InheritedParentalRatingValue); fields.Remove(ItemFields.ItemCounts); fields.Remove(ItemFields.MediaSourceCount); fields.Remove(ItemFields.MediaStreams); diff --git a/Jellyfin.Api/Controllers/BrandingController.cs b/Jellyfin.Api/Controllers/BrandingController.cs index 3c2c4b4db..1d948ff20 100644 --- a/Jellyfin.Api/Controllers/BrandingController.cs +++ b/Jellyfin.Api/Controllers/BrandingController.cs @@ -29,9 +29,18 @@ public class BrandingController : BaseJellyfinApiController /// <returns>An <see cref="OkResult"/> containing the branding configuration.</returns> [HttpGet("Configuration")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<BrandingOptions> GetBrandingOptions() + public ActionResult<BrandingOptionsDto> GetBrandingOptions() { - return _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding"); + var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding"); + + var brandingOptionsDto = new BrandingOptionsDto + { + LoginDisclaimer = brandingOptions.LoginDisclaimer, + CustomCss = brandingOptions.CustomCss, + SplashscreenEnabled = brandingOptions.SplashscreenEnabled + }; + + return brandingOptionsDto; } /// <summary> diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs index abe8bec2d..8dcaebf6d 100644 --- a/Jellyfin.Api/Controllers/ConfigurationController.cs +++ b/Jellyfin.Api/Controllers/ConfigurationController.cs @@ -9,6 +9,7 @@ using Jellyfin.Extensions.Json; using MediaBrowser.Common.Api; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Branding; using MediaBrowser.Model.Configuration; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -120,6 +121,30 @@ public class ConfigurationController : BaseJellyfinApiController } /// <summary> + /// Updates branding configuration. + /// </summary> + /// <param name="configuration">Branding configuration.</param> + /// <response code="204">Branding configuration updated.</response> + /// <returns>Update status.</returns> + [HttpPost("Configuration/Branding")] + [Authorize(Policy = Policies.RequiresElevation)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult UpdateBrandingConfiguration([FromBody, Required] BrandingOptionsDto configuration) + { + // Get the current branding configuration to preserve SplashscreenLocation + var currentBranding = (BrandingOptions)_configurationManager.GetConfiguration("branding"); + + // Update only the properties from BrandingOptionsDto + currentBranding.LoginDisclaimer = configuration.LoginDisclaimer; + currentBranding.CustomCss = configuration.CustomCss; + currentBranding.SplashscreenEnabled = configuration.SplashscreenEnabled; + + _configurationManager.SaveConfiguration("branding", currentBranding); + + return NoContent(); + } + + /// <summary> /// Updates the path to the media encoder. /// </summary> /// <param name="mediaEncoderPath">Media encoder path form body.</param> diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index ca8ab0ef7..51291ec62 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1753,7 +1753,7 @@ public class DynamicHlsController : BaseJellyfinApiController if (channels.HasValue && (channels.Value != 2 - || (state.AudioStream?.Channels != null && !useDownMixAlgorithm))) + || (state.AudioStream?.Channels is not null && !useDownMixAlgorithm))) { args += " -ac " + channels.Value; } diff --git a/Jellyfin.Server/Filters/AdditionalModelFilter.cs b/Jellyfin.Server/Filters/AdditionalModelFilter.cs index bf38f741c..4cd0fc231 100644 --- a/Jellyfin.Server/Filters/AdditionalModelFilter.cs +++ b/Jellyfin.Server/Filters/AdditionalModelFilter.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Server.Filters public class AdditionalModelFilter : IDocumentFilter { // Array of options that should not be visible in the api spec. - private static readonly Type[] _ignoredConfigurations = { typeof(MigrationOptions) }; + private static readonly Type[] _ignoredConfigurations = { typeof(MigrationOptions), typeof(MediaBrowser.Model.Branding.BrandingOptions) }; private readonly IServerConfigurationManager _serverConfigurationManager; /// <summary> diff --git a/MediaBrowser.Controller/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs index cb638cf90..a71cdbd62 100644 --- a/MediaBrowser.Controller/Dto/DtoOptions.cs +++ b/MediaBrowser.Controller/Dto/DtoOptions.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Dto EnableUserData = true; AddCurrentProgram = true; - Fields = allFields ? AllItemFields : Array.Empty<ItemFields>(); + Fields = allFields ? AllItemFields : []; ImageTypes = AllImageTypes; } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 207bb40d9..ed975af7f 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -5621,7 +5621,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doDeintH2645 = doDeintH264 || doDeintHevc; var doOclTonemap = IsHwTonemapAvailable(state, options); - var hasSubs = state.SubtitleStream != null && ShouldEncodeSubtitle(state); + var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; var hasAssSubs = hasSubs diff --git a/MediaBrowser.Model/Branding/BrandingOptions.cs b/MediaBrowser.Model/Branding/BrandingOptions.cs index c6580598b..5ec6b0dd4 100644 --- a/MediaBrowser.Model/Branding/BrandingOptions.cs +++ b/MediaBrowser.Model/Branding/BrandingOptions.cs @@ -1,5 +1,3 @@ -using System.Text.Json.Serialization; - namespace MediaBrowser.Model.Branding; /// <summary> @@ -27,10 +25,5 @@ public class BrandingOptions /// <summary> /// Gets or sets the splashscreen location on disk. /// </summary> - /// <remarks> - /// Not served via the API. - /// Only used to save the custom uploaded user splashscreen in the configuration file. - /// </remarks> - [JsonIgnore] public string? SplashscreenLocation { get; set; } } diff --git a/MediaBrowser.Model/Branding/BrandingOptionsDto.cs b/MediaBrowser.Model/Branding/BrandingOptionsDto.cs new file mode 100644 index 000000000..c0d8cb31c --- /dev/null +++ b/MediaBrowser.Model/Branding/BrandingOptionsDto.cs @@ -0,0 +1,25 @@ +namespace MediaBrowser.Model.Branding; + +/// <summary> +/// The branding options DTO for API use. +/// This DTO excludes SplashscreenLocation to prevent it from being updated via API. +/// </summary> +public class BrandingOptionsDto +{ + /// <summary> + /// Gets or sets the login disclaimer. + /// </summary> + /// <value>The login disclaimer.</value> + public string? LoginDisclaimer { get; set; } + + /// <summary> + /// Gets or sets the custom CSS. + /// </summary> + /// <value>The custom CSS.</value> + public string? CustomCss { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to enable the splashscreen. + /// </summary> + public bool SplashscreenEnabled { get; set; } = false; +} diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 7bfd8ca29..b38763fbf 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using Jellyfin.Data.Enums; using Jellyfin.Database.Implementations.Entities; using MediaBrowser.Model.Drawing; @@ -586,6 +587,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the type of the media. /// </summary> /// <value>The type of the media.</value> + [DefaultValue(MediaType.Unknown)] public MediaType MediaType { get; set; } /// <summary> diff --git a/MediaBrowser.Model/Dto/BaseItemPerson.cs b/MediaBrowser.Model/Dto/BaseItemPerson.cs index d3bcf492d..80e2cfb08 100644 --- a/MediaBrowser.Model/Dto/BaseItemPerson.cs +++ b/MediaBrowser.Model/Dto/BaseItemPerson.cs @@ -1,6 +1,7 @@ #nullable disable using System; using System.Collections.Generic; +using System.ComponentModel; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; using MediaBrowser.Model.Entities; @@ -34,6 +35,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the type. /// </summary> /// <value>The type.</value> + [DefaultValue(PersonKind.Unknown)] public PersonKind Type { get; set; } /// <summary> diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 400768ef3..dae3d84ae 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -157,6 +157,7 @@ namespace MediaBrowser.Model.Entities /// Gets the video range. /// </summary> /// <value>The video range.</value> + [DefaultValue(VideoRange.Unknown)] public VideoRange VideoRange { get diff --git a/MediaBrowser.Model/Extensions/ContainerHelper.cs b/MediaBrowser.Model/Extensions/ContainerHelper.cs index 39e5358ba..848cc2f62 100644 --- a/MediaBrowser.Model/Extensions/ContainerHelper.cs +++ b/MediaBrowser.Model/Extensions/ContainerHelper.cs @@ -21,7 +21,7 @@ public static class ContainerHelper public static bool ContainsContainer(string? profileContainers, string? inputContainer) { var isNegativeList = false; - if (profileContainers != null && profileContainers.StartsWith('-')) + if (profileContainers is not null && profileContainers.StartsWith('-')) { isNegativeList = true; profileContainers = profileContainers[1..]; @@ -42,7 +42,7 @@ public static class ContainerHelper public static bool ContainsContainer(string? profileContainers, ReadOnlySpan<char> inputContainer) { var isNegativeList = false; - if (profileContainers != null && profileContainers.StartsWith('-')) + if (profileContainers is not null && profileContainers.StartsWith('-')) { isNegativeList = true; profileContainers = profileContainers[1..]; diff --git a/MediaBrowser.Model/MediaSegments/MediaSegmentDto.cs b/MediaBrowser.Model/MediaSegments/MediaSegmentDto.cs index 6e5c7885c..d9129c395 100644 --- a/MediaBrowser.Model/MediaSegments/MediaSegmentDto.cs +++ b/MediaBrowser.Model/MediaSegments/MediaSegmentDto.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using Jellyfin.Database.Implementations.Enums; namespace MediaBrowser.Model.MediaSegments; @@ -21,6 +22,7 @@ public class MediaSegmentDto /// <summary> /// Gets or sets the type of content this segment defines. /// </summary> + [DefaultValue(MediaSegmentType.Unknown)] public MediaSegmentType Type { get; set; } /// <summary> diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index 6605064ad..ffecd392f 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -1,7 +1,3 @@ -#pragma warning disable CS1591 - -using System; - namespace MediaBrowser.Model.Querying { /// <summary> @@ -39,6 +35,9 @@ namespace MediaBrowser.Model.Querying /// </summary> Trickplay, + /// <summary> + /// The child count. + /// </summary> ChildCount, /// <summary> @@ -82,11 +81,6 @@ namespace MediaBrowser.Model.Querying Genres, /// <summary> - /// The home page URL. - /// </summary> - HomePageUrl, - - /// <summary> /// The item counts. /// </summary> ItemCounts, @@ -101,6 +95,9 @@ namespace MediaBrowser.Model.Querying /// </summary> MediaSources, + /// <summary> + /// The original title. + /// </summary> OriginalTitle, /// <summary> @@ -123,6 +120,9 @@ namespace MediaBrowser.Model.Querying /// </summary> People, + /// <summary> + /// Value indicating whether playback access is granted. + /// </summary> PlayAccess, /// <summary> @@ -140,6 +140,9 @@ namespace MediaBrowser.Model.Querying /// </summary> PrimaryImageAspectRatio, + /// <summary> + /// The recursive item count. + /// </summary> RecursiveItemCount, /// <summary> @@ -148,14 +151,6 @@ namespace MediaBrowser.Model.Querying Settings, /// <summary> - /// The screenshot image tags. - /// </summary> - [Obsolete("Screenshot image type is no longer used.")] - ScreenshotImageTags, - - SeriesPrimaryImage, - - /// <summary> /// The series studio. /// </summary> SeriesStudio, @@ -201,27 +196,58 @@ namespace MediaBrowser.Model.Querying SeasonUserData, /// <summary> - /// The service name. + /// The last time metadata was refreshed. /// </summary> - ServiceName, - ThemeSongIds, - ThemeVideoIds, - ExternalEtag, - PresentationUniqueKey, - InheritedParentalRatingValue, - InheritedParentalRatingSubValue, - ExternalSeriesId, - SeriesPresentationUniqueKey, DateLastRefreshed, + + /// <summary> + /// The last time metadata was saved. + /// </summary> DateLastSaved, + + /// <summary> + /// The refresh state. + /// </summary> RefreshState, + + /// <summary> + /// The channel image. + /// </summary> ChannelImage, + + /// <summary> + /// Value indicating whether media source display is enabled. + /// </summary> EnableMediaSourceDisplay, + + /// <summary> + /// The width. + /// </summary> Width, + + /// <summary> + /// The height. + /// </summary> Height, + + /// <summary> + /// The external Ids. + /// </summary> ExtraIds, + + /// <summary> + /// The local trailer count. + /// </summary> LocalTrailerCount, + + /// <summary> + /// Value indicating whether the item is HD. + /// </summary> IsHD, + + /// <summary> + /// The special feature count. + /// </summary> SpecialFeatureCount } } diff --git a/MediaBrowser.Model/Search/SearchHint.cs b/MediaBrowser.Model/Search/SearchHint.cs index 2e2979fcf..a18a813cc 100644 --- a/MediaBrowser.Model/Search/SearchHint.cs +++ b/MediaBrowser.Model/Search/SearchHint.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using Jellyfin.Data.Enums; namespace MediaBrowser.Model.Search @@ -115,6 +116,7 @@ namespace MediaBrowser.Model.Search /// Gets or sets the type of the media. /// </summary> /// <value>The type of the media.</value> + [DefaultValue(MediaType.Unknown)] public MediaType MediaType { get; set; } /// <summary> diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs index bec800c03..27e3f93a3 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs @@ -33,17 +33,18 @@ public class TmdbExternalUrlProvider : IExternalUrlProvider if (season.Series.TryGetProviderId(MetadataProvider.Tmdb, out var seriesExternalId)) { var orderString = season.Series.DisplayOrder; - if (string.IsNullOrEmpty(orderString)) + var seasonNumber = season.IndexNumber; + if (string.IsNullOrEmpty(orderString) && seasonNumber is not null) { // Default order is airdate - yield return TmdbUtils.BaseTmdbUrl + $"tv/{seriesExternalId}/season/{season.IndexNumber}"; + yield return TmdbUtils.BaseTmdbUrl + $"tv/{seriesExternalId}/season/{seasonNumber}"; } if (Enum.TryParse<TvGroupType>(season.Series.DisplayOrder, out var order)) { - if (order.Equals(TvGroupType.OriginalAirDate)) + if (order.Equals(TvGroupType.OriginalAirDate) && seasonNumber is not null) { - yield return TmdbUtils.BaseTmdbUrl + $"tv/{seriesExternalId}/season/{season.IndexNumber}"; + yield return TmdbUtils.BaseTmdbUrl + $"tv/{seriesExternalId}/season/{seasonNumber}"; } } } @@ -53,17 +54,19 @@ public class TmdbExternalUrlProvider : IExternalUrlProvider if (episode.Series.TryGetProviderId(MetadataProvider.Imdb, out seriesExternalId)) { var orderString = episode.Series.DisplayOrder; - if (string.IsNullOrEmpty(orderString)) + var seasonNumber = episode.Season?.IndexNumber; + var episodeNumber = episode.IndexNumber; + if (string.IsNullOrEmpty(orderString) && seasonNumber is not null && episodeNumber is not null) { // Default order is airdate - yield return TmdbUtils.BaseTmdbUrl + $"tv/{seriesExternalId}/season/{episode.Season.IndexNumber}/episode/{episode.IndexNumber}"; + yield return TmdbUtils.BaseTmdbUrl + $"tv/{seriesExternalId}/season/{seasonNumber}/episode/{episodeNumber}"; } if (Enum.TryParse<TvGroupType>(orderString, out var order)) { - if (order.Equals(TvGroupType.OriginalAirDate)) + if (order.Equals(TvGroupType.OriginalAirDate) && seasonNumber is not null && episodeNumber is not null) { - yield return TmdbUtils.BaseTmdbUrl + $"tv/{seriesExternalId}/season/{episode.Season.IndexNumber}/episode/{episode.IndexNumber}"; + yield return TmdbUtils.BaseTmdbUrl + $"tv/{seriesExternalId}/season/{seasonNumber}/episode/{episodeNumber}"; } } } diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index 284415dce..42d59d348 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -55,7 +55,7 @@ namespace MediaBrowser.Providers.TV foreach (var season in seasons) { - var hasUpdate = refreshOptions != null && season.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata); + var hasUpdate = refreshOptions is not null && season.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata); if (hasUpdate) { await season.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs index 2a1a14834..19b1bbe7b 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs @@ -95,7 +95,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers originalTitle.Append(" / ").Append(additionalEpisode.Item.OriginalTitle); } - if (additionalEpisode.Item.IndexNumber != null) + if (additionalEpisode.Item.IndexNumber is not null) { item.Item.IndexNumberEnd = Math.Max((int)additionalEpisode.Item.IndexNumber, item.Item.IndexNumberEnd ?? (int)additionalEpisode.Item.IndexNumber); } diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 4c8a54cc9..0217bded1 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -1020,12 +1020,12 @@ namespace MediaBrowser.XbmcMetadata.Savers protected static string SortNameOrName(BaseItem item) { - if (item == null) + if (item is null) { return string.Empty; } - if (item.SortName != null) + if (item.SortName is not null) { string trimmed = item.SortName.Trim(); if (trimmed.Length > 0) diff --git a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 99f7fa7f9..73c8c3966 100644 --- a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -557,20 +557,14 @@ public class SkiaEncoder : IImageEncoder canvas.Clear(SKColor.Parse(options.BackgroundColor)); } + using var paint = new SKPaint(); // Add blur if option is present - if (blur > 0) - { - // create image from resized bitmap to apply blur - using var paint = new SKPaint(); - using var filter = SKImageFilter.CreateBlur(blur, blur); - paint.ImageFilter = filter; - canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height), paint); - } - else - { - // draw resized bitmap onto canvas - canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height)); - } + using var filter = blur > 0 ? SKImageFilter.CreateBlur(blur, blur) : null; + paint.FilterQuality = SKFilterQuality.High; + paint.ImageFilter = filter; + + // create image from resized bitmap to apply blur + canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height), paint); // If foreground layer present then draw if (hasForegroundColor) diff --git a/src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index b0c9c0b3c..03e202e5a 100644 --- a/src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -109,15 +109,18 @@ public partial class StripCollageBuilder // resize to the same aspect as the original var backdropHeight = Math.Abs(width * backdrop.Height / backdrop.Width); - using var residedBackdrop = SkiaEncoder.ResizeImage(backdrop, new SKImageInfo(width, backdropHeight, backdrop.ColorType, backdrop.AlphaType, backdrop.ColorSpace)); + using var resizedBackdrop = SkiaEncoder.ResizeImage(backdrop, new SKImageInfo(width, backdropHeight, backdrop.ColorType, backdrop.AlphaType, backdrop.ColorSpace)); + using var paint = new SKPaint(); + paint.FilterQuality = SKFilterQuality.High; // draw the backdrop - canvas.DrawImage(residedBackdrop, 0, 0); + canvas.DrawImage(resizedBackdrop, 0, 0, paint); // draw shadow rectangle using var paintColor = new SKPaint { Color = SKColors.Black.WithAlpha(0x78), - Style = SKPaintStyle.Fill + Style = SKPaintStyle.Fill, + FilterQuality = SKFilterQuality.High }; canvas.DrawRect(0, 0, width, height, paintColor); @@ -131,7 +134,8 @@ public partial class StripCollageBuilder TextSize = 112, TextAlign = SKTextAlign.Left, Typeface = typeFace, - IsAntialias = true + IsAntialias = true, + FilterQuality = SKFilterQuality.High }; // scale down text to 90% of the width if text is larger than 95% of the width @@ -188,14 +192,16 @@ public partial class StripCollageBuilder continue; } - // Scale image. The FromBitmap creates a copy + // Scale image var imageInfo = new SKImageInfo(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType, currentBitmap.ColorSpace); using var resizeImage = SkiaEncoder.ResizeImage(currentBitmap, imageInfo); + using var paint = new SKPaint(); + paint.FilterQuality = SKFilterQuality.High; // draw this image into the strip at the next position var xPos = x * cellWidth; var yPos = y * cellHeight; - canvas.DrawImage(resizeImage, xPos, yPos); + canvas.DrawImage(resizeImage, xPos, yPos, paint); } } diff --git a/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs index a74dab5f2..e95df1635 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs +++ b/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs @@ -87,7 +87,7 @@ public class UserControllerTests Assert.Contains( Validate(userPolicy), v => v.MemberNames.Contains("PasswordResetProviderId") && - v.ErrorMessage != null && + v.ErrorMessage is not null && v.ErrorMessage.Contains("required", StringComparison.CurrentCultureIgnoreCase)); } @@ -105,7 +105,7 @@ public class UserControllerTests Assert.Contains(Validate(userPolicy), v => v.MemberNames.Contains("AuthenticationProviderId") && - v.ErrorMessage != null && + v.ErrorMessage is not null && v.ErrorMessage.Contains("required", StringComparison.CurrentCultureIgnoreCase)); } diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs index ae9edd386..2c1080ffe 100644 --- a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs +++ b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs @@ -182,6 +182,10 @@ namespace Jellyfin.Model.Tests [InlineData("Tizen3-stereo", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] [InlineData("Tizen3-stereo", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] [InlineData("Tizen3-stereo", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] + [InlineData("Tizen3-stereo", "mkv-dvhe.08-eac3-15200k", PlayMethod.DirectPlay)] + [InlineData("Tizen3-stereo", "mp4-dvh1.05-eac3-15200k", PlayMethod.Transcode, TranscodeReason.VideoRangeTypeNotSupported | TranscodeReason.AudioChannelsNotSupported, "Transcode")] + [InlineData("Tizen3-stereo", "mp4-dvhe.08-eac3-15200k", PlayMethod.DirectPlay)] + [InlineData("Tizen3-stereo", "mkv-dvhe.05-eac3-28000k", PlayMethod.Transcode, TranscodeReason.VideoBitrateNotSupported | TranscodeReason.VideoRangeTypeNotSupported | TranscodeReason.AudioChannelsNotSupported, "Transcode")] [InlineData("Tizen3-stereo", "numstreams-32", PlayMethod.DirectPlay)] [InlineData("Tizen3-stereo", "numstreams-33", PlayMethod.Transcode, TranscodeReason.StreamCountExceedsLimit, "Remux")] // Tizen 4 4K 5.1 @@ -195,6 +199,10 @@ namespace Jellyfin.Model.Tests [InlineData("Tizen4-4K-5.1", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] [InlineData("Tizen4-4K-5.1", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] [InlineData("Tizen4-4K-5.1", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] + [InlineData("Tizen4-4K-5.1", "mkv-dvhe.08-eac3-15200k", PlayMethod.DirectPlay)] + [InlineData("Tizen4-4K-5.1", "mp4-dvh1.05-eac3-15200k", PlayMethod.Transcode, TranscodeReason.VideoRangeTypeNotSupported, "Transcode")] + [InlineData("Tizen4-4K-5.1", "mp4-dvhe.08-eac3-15200k", PlayMethod.DirectPlay)] + [InlineData("Tizen4-4K-5.1", "mkv-dvhe.05-eac3-28000k", PlayMethod.Transcode, TranscodeReason.VideoRangeTypeNotSupported, "Transcode")] [InlineData("Tizen4-4K-5.1", "numstreams-32", PlayMethod.DirectPlay)] [InlineData("Tizen4-4K-5.1", "numstreams-33", PlayMethod.Transcode, TranscodeReason.StreamCountExceedsLimit, "Remux")] // WebOS 23 diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamInfoTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamInfoTests.cs index 86819de8c..8dea46806 100644 --- a/tests/Jellyfin.Model.Tests/Dlna/StreamInfoTests.cs +++ b/tests/Jellyfin.Model.Tests/Dlna/StreamInfoTests.cs @@ -31,7 +31,7 @@ public class StreamInfoTests /// <returns>An <see cref="Array"/> of <see cref="Type"/>.</returns> private static object? RandomArray(Random random, Type? elementType) { - if (elementType == null) + if (elementType is null) { return null; } @@ -148,7 +148,7 @@ public class StreamInfoTests var type = property.PropertyType; // If nullable, then set it to null, 25% of the time. - if (Nullable.GetUnderlyingType(type) != null) + if (Nullable.GetUnderlyingType(type) is not null) { if (random.Next(0, 4) == 0) { diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json index 895d13f07..9d43d2166 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json @@ -439,7 +439,14 @@ { "Condition": "EqualsAny", "Property": "VideoProfile", - "Value": "high|main|baseline|constrained baseline|high 10", + "Value": "high|main|baseline|constrained baseline", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "EqualsAny", + "Property": "VideoRangeType", + "Value": "SDR", "IsRequired": false, "$type": "ProfileCondition" }, @@ -479,6 +486,13 @@ "$type": "ProfileCondition" }, { + "Condition": "EqualsAny", + "Property": "VideoRangeType", + "Value": "SDR|DOVIWithSDR|HDR10|DOVIWithHDR10|HLG|DOVIWithHLG", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { "Condition": "LessThanEqual", "Property": "VideoLevel", "Value": "183", diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json index 345d38725..3859ef994 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json @@ -439,7 +439,14 @@ { "Condition": "EqualsAny", "Property": "VideoProfile", - "Value": "high|main|baseline|constrained baseline|high 10", + "Value": "high|main|baseline|constrained baseline", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "EqualsAny", + "Property": "VideoRangeType", + "Value": "SDR", "IsRequired": false, "$type": "ProfileCondition" }, @@ -472,6 +479,13 @@ "$type": "ProfileCondition" }, { + "Condition": "EqualsAny", + "Property": "VideoRangeType", + "Value": "SDR|DOVIWithSDR|HDR10|DOVIWithHDR10|HLG|DOVIWithHLG", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { "Condition": "LessThanEqual", "Property": "VideoLevel", "Value": "183", |
