aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs21
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs1
-rw-r--r--Jellyfin.Api/Controllers/BrandingController.cs13
-rw-r--r--Jellyfin.Api/Controllers/ConfigurationController.cs25
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs2
-rw-r--r--Jellyfin.Server/Filters/AdditionalModelFilter.cs2
-rw-r--r--MediaBrowser.Controller/Dto/DtoOptions.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs2
-rw-r--r--MediaBrowser.Model/Branding/BrandingOptions.cs7
-rw-r--r--MediaBrowser.Model/Branding/BrandingOptionsDto.cs25
-rw-r--r--MediaBrowser.Model/Dto/BaseItemDto.cs2
-rw-r--r--MediaBrowser.Model/Dto/BaseItemPerson.cs2
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs1
-rw-r--r--MediaBrowser.Model/Extensions/ContainerHelper.cs4
-rw-r--r--MediaBrowser.Model/MediaSegments/MediaSegmentDto.cs2
-rw-r--r--MediaBrowser.Model/Querying/ItemFields.cs80
-rw-r--r--MediaBrowser.Model/Search/SearchHint.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs19
-rw-r--r--MediaBrowser.Providers/TV/SeriesMetadataService.cs2
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs2
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs4
-rw-r--r--src/Jellyfin.Drawing.Skia/SkiaEncoder.cs20
-rw-r--r--src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs18
-rw-r--r--tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs4
-rw-r--r--tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs8
-rw-r--r--tests/Jellyfin.Model.Tests/Dlna/StreamInfoTests.cs4
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json16
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json16
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",