aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api')
-rw-r--r--Jellyfin.Api/Controllers/BrandingController.cs13
-rw-r--r--Jellyfin.Api/Controllers/ConfigurationController.cs25
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs14
-rw-r--r--Jellyfin.Api/Controllers/ImageController.cs3
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs4
-rw-r--r--Jellyfin.Api/Controllers/LocalizationController.cs5
-rw-r--r--Jellyfin.Api/Helpers/DynamicHlsHelper.cs85
-rw-r--r--Jellyfin.Api/Helpers/MediaInfoHelper.cs6
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)
{