diff options
Diffstat (limited to 'Jellyfin.Api')
| -rw-r--r-- | Jellyfin.Api/Controllers/BrandingController.cs | 13 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/ConfigurationController.cs | 25 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/DynamicHlsController.cs | 14 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/ImageController.cs | 3 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/ItemsController.cs | 4 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/LocalizationController.cs | 5 | ||||
| -rw-r--r-- | Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 85 | ||||
| -rw-r--r-- | Jellyfin.Api/Helpers/MediaInfoHelper.cs | 6 |
8 files changed, 111 insertions, 44 deletions
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 a351c1be1..4cac8ed67 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1419,8 +1419,9 @@ public class DynamicHlsController : BaseJellyfinApiController TranscodingJobType, cancellationTokenSource.Token) .ConfigureAwait(false); - + var mediaSourceId = state.BaseRequest.MediaSourceId; var request = new CreateMainPlaylistRequest( + mediaSourceId is null ? null : Guid.Parse(mediaSourceId), state.MediaPath, state.SegmentLength * 1000, state.RunTimeTicks ?? 0, @@ -1675,7 +1676,7 @@ public class DynamicHlsController : BaseJellyfinApiController } var audioCodec = _encodingHelper.GetAudioEncoder(state); - var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); + var bitStreamArgs = _encodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); // opus, dts, truehd and flac (in FFmpeg 5 and older) are experimental in mp4 muxer var strictArgs = string.Empty; @@ -1753,7 +1754,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; } @@ -1822,10 +1823,11 @@ public class DynamicHlsController : BaseJellyfinApiController // Clients reporting Dolby Vision capabilities with fallbacks may only support the fallback layer. // Only enable Dolby Vision remuxing if the client explicitly declares support for profiles without fallbacks. var clientSupportsDoVi = requestedRange.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIgnoreCase); - var videoIsDoVi = state.VideoStream.VideoRangeType is VideoRangeType.DOVI or VideoRangeType.DOVIWithHDR10 or VideoRangeType.DOVIWithHLG or VideoRangeType.DOVIWithSDR; + var videoIsDoVi = EncodingHelper.IsDovi(state.VideoStream); if (EncodingHelper.IsCopyCodec(codec) - && (videoIsDoVi && clientSupportsDoVi)) + && (videoIsDoVi && clientSupportsDoVi) + && !_encodingHelper.IsDoviRemoved(state)) { if (isActualOutputVideoCodecHevc) { @@ -1855,7 +1857,7 @@ public class DynamicHlsController : BaseJellyfinApiController // If h264_mp4toannexb is ever added, do not use it for live tv. if (state.VideoStream is not null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) { - string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream); + string bitStreamArgs = _encodingHelper.GetBitStreamArgs(state, MediaStreamType.Video); if (!string.IsNullOrEmpty(bitStreamArgs)) { args += " " + bitStreamArgs; diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index e7b7405ca..abda053d3 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -1727,7 +1727,8 @@ public class ImageController : BaseJellyfinApiController [FromQuery, Range(0, 100)] int quality = 90) { var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding"); - if (!brandingOptions.SplashscreenEnabled) + var isAdmin = User.IsInRole(Constants.UserRoles.Administrator); + if (!brandingOptions.SplashscreenEnabled && !isAdmin) { return NotFound(); } diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 803c2f1f7..a49128336 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -448,13 +448,13 @@ public class ItemsController : BaseJellyfinApiController // Min official rating if (!string.IsNullOrWhiteSpace(minOfficialRating)) { - query.MinParentalRating = _localization.GetRatingLevel(minOfficialRating); + query.MinParentalRating = _localization.GetRatingScore(minOfficialRating); } // Max official rating if (!string.IsNullOrWhiteSpace(maxOfficialRating)) { - query.MaxParentalRating = _localization.GetRatingLevel(maxOfficialRating); + query.MaxParentalRating = _localization.GetRatingScore(maxOfficialRating); } // Artists diff --git a/Jellyfin.Api/Controllers/LocalizationController.cs b/Jellyfin.Api/Controllers/LocalizationController.cs index f65d95c41..bbce5a9e1 100644 --- a/Jellyfin.Api/Controllers/LocalizationController.cs +++ b/Jellyfin.Api/Controllers/LocalizationController.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Jellyfin.Api.Constants; using MediaBrowser.Common.Api; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; @@ -45,7 +44,7 @@ public class LocalizationController : BaseJellyfinApiController /// <returns>An <see cref="OkResult"/> containing the list of countries.</returns> [HttpGet("Countries")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<IEnumerable<CountryInfo>> GetCountries() + public ActionResult<IReadOnlyList<CountryInfo>> GetCountries() { return Ok(_localization.GetCountries()); } @@ -57,7 +56,7 @@ public class LocalizationController : BaseJellyfinApiController /// <returns>An <see cref="OkResult"/> containing the list of parental ratings.</returns> [HttpGet("ParentalRatings")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<IEnumerable<ParentalRating>> GetParentalRatings() + public ActionResult<IReadOnlyList<ParentalRating>> GetParentalRatings() { return Ok(_localization.GetParentalRatings()); } diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index ebd0288ca..a38ad379c 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -345,13 +345,15 @@ public class DynamicHlsHelper if (videoRange == VideoRange.HDR) { - if (videoRangeType == VideoRangeType.HLG) + switch (videoRangeType) { - builder.Append(",VIDEO-RANGE=HLG"); - } - else - { - builder.Append(",VIDEO-RANGE=PQ"); + case VideoRangeType.HLG: + case VideoRangeType.DOVIWithHLG: + builder.Append(",VIDEO-RANGE=HLG"); + break; + default: + builder.Append(",VIDEO-RANGE=PQ"); + break; } } } @@ -418,36 +420,67 @@ public class DynamicHlsHelper /// <param name="state">StreamState of the current stream.</param> private void AppendPlaylistSupplementalCodecsField(StringBuilder builder, StreamState state) { - // Dolby Vision currently cannot exist when transcoding + // HDR dynamic metadata currently cannot exist when transcoding if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { return; } - var dvProfile = state.VideoStream.DvProfile; - var dvLevel = state.VideoStream.DvLevel; - var dvRangeString = state.VideoStream.VideoRangeType switch + if (EncodingHelper.IsDovi(state.VideoStream) && !_encodingHelper.IsDoviRemoved(state)) { - VideoRangeType.DOVIWithHDR10 => "db1p", - VideoRangeType.DOVIWithHLG => "db4h", - _ => string.Empty - }; + AppendDvString(); + } + else if (EncodingHelper.IsHdr10Plus(state.VideoStream) && !_encodingHelper.IsHdr10PlusRemoved(state)) + { + AppendHdr10PlusString(); + } - if (dvProfile is null || dvLevel is null || string.IsNullOrEmpty(dvRangeString)) + return; + + void AppendDvString() { - return; + var dvProfile = state.VideoStream.DvProfile; + var dvLevel = state.VideoStream.DvLevel; + var dvRangeString = state.VideoStream.VideoRangeType switch + { + VideoRangeType.DOVIWithHDR10 => "db1p", + VideoRangeType.DOVIWithHLG => "db4h", + VideoRangeType.DOVIWithHDR10Plus => "db1p", // The HDR10+ metadata would be removed if Dovi metadata is not removed + _ => string.Empty // Don't label Dovi with EL and SDR due to compatability issues, ignore invalid configurations + }; + + if (dvProfile is null || dvLevel is null || string.IsNullOrEmpty(dvRangeString)) + { + return; + } + + var dvFourCc = string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase) ? "dav1" : "dvh1"; + builder.Append(",SUPPLEMENTAL-CODECS=\"") + .Append(dvFourCc) + .Append('.') + .Append(dvProfile.Value.ToString("D2", CultureInfo.InvariantCulture)) + .Append('.') + .Append(dvLevel.Value.ToString("D2", CultureInfo.InvariantCulture)) + .Append('/') + .Append(dvRangeString) + .Append('"'); } - var dvFourCc = string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase) ? "dav1" : "dvh1"; - builder.Append(",SUPPLEMENTAL-CODECS=\"") - .Append(dvFourCc) - .Append('.') - .Append(dvProfile.Value.ToString("D2", CultureInfo.InvariantCulture)) - .Append('.') - .Append(dvLevel.Value.ToString("D2", CultureInfo.InvariantCulture)) - .Append('/') - .Append(dvRangeString) - .Append('"'); + void AppendHdr10PlusString() + { + var videoCodecLevel = GetOutputVideoCodecLevel(state); + if (string.IsNullOrEmpty(state.ActualOutputVideoCodec) || videoCodecLevel is null) + { + return; + } + + var videoCodecString = GetPlaylistVideoCodecs(state, state.ActualOutputVideoCodec, videoCodecLevel.Value); + builder.Append(",SUPPLEMENTAL-CODECS=\"") + .Append(videoCodecString) + .Append('/') + .Append("cdm4") + .Append('"'); + } } /// <summary> diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index 7b493d3fa..63c9c173b 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -290,9 +290,7 @@ public class MediaInfoHelper mediaSource.SupportsDirectPlay = false; mediaSource.SupportsDirectStream = false; - mediaSource.TranscodingUrl = streamInfo.ToUrl("-", claimsPrincipal.GetToken()).TrimStart('-'); - mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false"; - mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; + mediaSource.TranscodingUrl = streamInfo.ToUrl(null, claimsPrincipal.GetToken(), "&allowVideoStreamCopy=false&allowAudioStreamCopy=false"); mediaSource.TranscodingContainer = streamInfo.Container; mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; if (streamInfo.AlwaysBurnInSubtitleWhenTranscoding) @@ -305,7 +303,7 @@ public class MediaInfoHelper if (!mediaSource.SupportsDirectPlay && (mediaSource.SupportsTranscoding || mediaSource.SupportsDirectStream)) { streamInfo.PlayMethod = PlayMethod.Transcode; - mediaSource.TranscodingUrl = streamInfo.ToUrl("-", claimsPrincipal.GetToken()).TrimStart('-'); + mediaSource.TranscodingUrl = streamInfo.ToUrl(null, claimsPrincipal.GetToken(), null); if (!allowVideoStreamCopy) { |
