aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api')
-rw-r--r--Jellyfin.Api/Controllers/AudioController.cs24
-rw-r--r--Jellyfin.Api/Controllers/BrandingController.cs13
-rw-r--r--Jellyfin.Api/Controllers/ConfigurationController.cs25
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs90
-rw-r--r--Jellyfin.Api/Controllers/ImageController.cs3
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs4
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs4
-rw-r--r--Jellyfin.Api/Controllers/LocalizationController.cs5
-rw-r--r--Jellyfin.Api/Controllers/PlaystateController.cs3
-rw-r--r--Jellyfin.Api/Controllers/StartupController.cs5
-rw-r--r--Jellyfin.Api/Controllers/SyncPlayController.cs28
-rw-r--r--Jellyfin.Api/Controllers/SystemController.cs14
-rw-r--r--Jellyfin.Api/Controllers/UniversalAudioController.cs4
-rw-r--r--Jellyfin.Api/Controllers/VideosController.cs24
-rw-r--r--Jellyfin.Api/Helpers/DynamicHlsHelper.cs85
-rw-r--r--Jellyfin.Api/Helpers/MediaInfoHelper.cs13
-rw-r--r--Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs5
-rw-r--r--Jellyfin.Api/Models/SystemInfoDtos/FolderStorageDto.cs46
-rw-r--r--Jellyfin.Api/Models/SystemInfoDtos/LibraryStorageDto.cs37
-rw-r--r--Jellyfin.Api/Models/SystemInfoDtos/SystemStorageDto.cs67
20 files changed, 382 insertions, 117 deletions
diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs
index 272b4034e..e334e1264 100644
--- a/Jellyfin.Api/Controllers/AudioController.cs
+++ b/Jellyfin.Api/Controllers/AudioController.cs
@@ -92,18 +92,18 @@ public class AudioController : BaseJellyfinApiController
[ProducesAudioFile]
public async Task<ActionResult> GetAudioStream(
[FromRoute, Required] Guid itemId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? container,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -114,7 +114,7 @@ public class AudioController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -133,8 +133,8 @@ public class AudioController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -259,18 +259,18 @@ public class AudioController : BaseJellyfinApiController
[ProducesAudioFile]
public async Task<ActionResult> GetAudioStreamByContainer(
[FromRoute, Required] Guid itemId,
- [FromRoute, Required] string container,
+ [FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -281,7 +281,7 @@ public class AudioController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -300,8 +300,8 @@ public class AudioController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
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..4cac8ed67 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -166,18 +166,18 @@ public class DynamicHlsController : BaseJellyfinApiController
[ProducesPlaylistFile]
public async Task<ActionResult> GetLiveHlsStream(
[FromRoute, Required] Guid itemId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? container,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -188,7 +188,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -207,8 +207,8 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -415,12 +415,12 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery, Required] string mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -431,7 +431,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -452,8 +452,8 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -591,12 +591,12 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery, Required] string mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -608,7 +608,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -627,8 +627,8 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -761,12 +761,12 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -777,7 +777,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -798,8 +798,8 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -933,12 +933,12 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -950,7 +950,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -969,8 +969,8 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -1106,7 +1106,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId,
[FromRoute, Required] string playlistId,
[FromRoute, Required] int segmentId,
- [FromRoute, Required] string container,
+ [FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
[FromQuery, Required] long runtimeTicks,
[FromQuery, Required] long actualSegmentLengthTicks,
[FromQuery] bool? @static,
@@ -1114,12 +1114,12 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -1130,7 +1130,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -1151,8 +1151,8 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -1291,7 +1291,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId,
[FromRoute, Required] string playlistId,
[FromRoute, Required] int segmentId,
- [FromRoute, Required] string container,
+ [FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
[FromQuery, Required] long runtimeTicks,
[FromQuery, Required] long actualSegmentLengthTicks,
[FromQuery] bool? @static,
@@ -1299,12 +1299,12 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -1316,7 +1316,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -1335,8 +1335,8 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -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/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 5461d12fa..10f1789ad 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -1190,7 +1190,9 @@ public class LiveTvController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesVideoFile]
- public ActionResult GetLiveStreamFile([FromRoute, Required] string streamId, [FromRoute, Required] string container)
+ public ActionResult GetLiveStreamFile(
+ [FromRoute, Required] string streamId,
+ [FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container)
{
var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfoByUniqueId(streamId);
if (liveStreamInfo is null)
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/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs
index 1577b4594..ade0906b3 100644
--- a/Jellyfin.Api/Controllers/PlaystateController.cs
+++ b/Jellyfin.Api/Controllers/PlaystateController.cs
@@ -272,6 +272,7 @@ public class PlaystateController : BaseJellyfinApiController
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("PlayingItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
+ [Obsolete("This endpoint is obsolete. Use ReportPlaybackStart instead")]
public async Task<ActionResult> OnPlaybackStart(
[FromRoute, Required] Guid itemId,
[FromQuery] string? mediaSourceId,
@@ -350,6 +351,7 @@ public class PlaystateController : BaseJellyfinApiController
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("PlayingItems/{itemId}/Progress")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
+ [Obsolete("This endpoint is obsolete. Use ReportPlaybackProgress instead")]
public async Task<ActionResult> OnPlaybackProgress(
[FromRoute, Required] Guid itemId,
[FromQuery] string? mediaSourceId,
@@ -438,6 +440,7 @@ public class PlaystateController : BaseJellyfinApiController
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("PlayingItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
+ [Obsolete("This endpoint is obsolete. Use ReportPlaybackStop instead")]
public async Task<ActionResult> OnPlaybackStopped(
[FromRoute, Required] Guid itemId,
[FromQuery] string? mediaSourceId,
diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs
index a6bc84311..09f20558f 100644
--- a/Jellyfin.Api/Controllers/StartupController.cs
+++ b/Jellyfin.Api/Controllers/StartupController.cs
@@ -58,6 +58,7 @@ public class StartupController : BaseJellyfinApiController
{
return new StartupConfigurationDto
{
+ ServerName = _config.Configuration.ServerName,
UICulture = _config.Configuration.UICulture,
MetadataCountryCode = _config.Configuration.MetadataCountryCode,
PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage
@@ -74,6 +75,7 @@ public class StartupController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult UpdateInitialConfiguration([FromBody, Required] StartupConfigurationDto startupConfiguration)
{
+ _config.Configuration.ServerName = startupConfiguration.ServerName ?? string.Empty;
_config.Configuration.UICulture = startupConfiguration.UICulture ?? string.Empty;
_config.Configuration.MetadataCountryCode = startupConfiguration.MetadataCountryCode ?? string.Empty;
_config.Configuration.PreferredMetadataLanguage = startupConfiguration.PreferredMetadataLanguage ?? string.Empty;
@@ -112,8 +114,7 @@ public class StartupController : BaseJellyfinApiController
var user = _userManager.Users.First();
return new StartupUserDto
{
- Name = user.Username,
- Password = user.Password
+ Name = user.Username
};
}
diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs
index 383978197..fbab2a784 100644
--- a/Jellyfin.Api/Controllers/SyncPlayController.cs
+++ b/Jellyfin.Api/Controllers/SyncPlayController.cs
@@ -1,9 +1,9 @@
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.SyncPlayDtos;
using MediaBrowser.Common.Api;
@@ -50,17 +50,16 @@ public class SyncPlayController : BaseJellyfinApiController
/// </summary>
/// <param name="requestData">The settings of the new group.</param>
/// <response code="204">New group created.</response>
- /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
+ /// <returns>An <see cref="GroupInfoDto"/> for the created group.</returns>
[HttpPost("New")]
- [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.SyncPlayCreateGroup)]
- public async Task<ActionResult> SyncPlayCreateGroup(
+ public async Task<ActionResult<GroupInfoDto>> SyncPlayCreateGroup(
[FromBody, Required] NewGroupRequestDto requestData)
{
var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new NewGroupRequest(requestData.GroupName);
- _syncPlayManager.NewGroup(currentSession, syncPlayRequest, CancellationToken.None);
- return NoContent();
+ return Ok(_syncPlayManager.NewGroup(currentSession, syncPlayRequest, CancellationToken.None));
}
/// <summary>
@@ -113,6 +112,23 @@ public class SyncPlayController : BaseJellyfinApiController
}
/// <summary>
+ /// Gets a SyncPlay group by id.
+ /// </summary>
+ /// <param name="id">The id of the group.</param>
+ /// <response code="200">Group returned.</response>
+ /// <returns>An <see cref="GroupInfoDto"/> for the requested group.</returns>
+ [HttpGet("{id:guid}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [Authorize(Policy = Policies.SyncPlayJoinGroup)]
+ public async Task<ActionResult<GroupInfoDto>> SyncPlayGetGroup([FromRoute] Guid id)
+ {
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
+ var group = _syncPlayManager.GetGroup(currentSession, id);
+ return group == null ? NotFound() : Ok(group);
+ }
+
+ /// <summary>
/// Request to set new playlist in SyncPlay group.
/// </summary>
/// <param name="requestData">The new playlist to play in the group.</param>
diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs
index 0ee11c070..07a1f7650 100644
--- a/Jellyfin.Api/Controllers/SystemController.cs
+++ b/Jellyfin.Api/Controllers/SystemController.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Mime;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
+using Jellyfin.Api.Models.SystemInfoDtos;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
@@ -72,6 +73,19 @@ public class SystemController : BaseJellyfinApiController
=> _systemManager.GetSystemInfo(Request);
/// <summary>
+ /// Gets information about the server.
+ /// </summary>
+ /// <response code="200">Information retrieved.</response>
+ /// <response code="403">User does not have permission to retrieve information.</response>
+ /// <returns>A <see cref="SystemInfo"/> with info about the system.</returns>
+ [HttpGet("Info/Storage")]
+ [Authorize(Policy = Policies.RequiresElevation)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ public ActionResult<SystemStorageDto> GetSystemStorage()
+ => Ok(SystemStorageDto.FromSystemStorageInfo(_systemManager.GetSystemStorageInfo()));
+
+ /// <summary>
/// Gets public information about the server.
/// </summary>
/// <response code="200">Information retrieved.</response>
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index a5b5fde62..fd6334703 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -102,13 +102,13 @@ public class UniversalAudioController : BaseJellyfinApiController
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
[FromQuery] Guid? userId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] int? maxAudioChannels,
[FromQuery] int? transcodingAudioChannels,
[FromQuery] int? maxStreamingBitrate,
[FromQuery] int? audioBitRate,
[FromQuery] long? startTimeTicks,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? transcodingContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? transcodingContainer,
[FromQuery] MediaStreamProtocol? transcodingProtocol,
[FromQuery] int? maxAudioSampleRate,
[FromQuery] int? maxAudioBitDepth,
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index 6f18c1603..97f3239bb 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -315,18 +315,18 @@ public class VideosController : BaseJellyfinApiController
[ProducesVideoFile]
public async Task<ActionResult> GetVideoStream(
[FromRoute, Required] Guid itemId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? container,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -337,7 +337,7 @@ public class VideosController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -358,8 +358,8 @@ public class VideosController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -556,18 +556,18 @@ public class VideosController : BaseJellyfinApiController
[ProducesVideoFile]
public Task<ActionResult> GetVideoStreamByContainer(
[FromRoute, Required] Guid itemId,
- [FromRoute, Required] string container,
+ [FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
[FromQuery] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -578,7 +578,7 @@ public class VideosController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -599,8 +599,8 @@ public class VideosController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
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..454d3f08e 100644
--- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs
+++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
@@ -129,6 +129,13 @@ public class MediaInfoHelper
var mediaSourcesClone = JsonSerializer.Deserialize<MediaSourceInfo[]>(JsonSerializer.SerializeToUtf8Bytes(mediaSources));
if (mediaSourcesClone is not null)
{
+ // Carry over the default audio index source.
+ // This field is not intended to be exposed to API clients, but it is used internally by the server
+ for (int i = 0; i < mediaSourcesClone.Length && i < mediaSources.Length; i++)
+ {
+ mediaSourcesClone[i].DefaultAudioIndexSource = mediaSources[i].DefaultAudioIndexSource;
+ }
+
result.MediaSources = mediaSourcesClone;
}
@@ -290,9 +297,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 +310,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)
{
diff --git a/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs b/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs
index 402707819..1ba23339d 100644
--- a/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs
+++ b/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs
@@ -6,6 +6,11 @@ namespace Jellyfin.Api.Models.StartupDtos;
public class StartupConfigurationDto
{
/// <summary>
+ /// Gets or sets the server name.
+ /// </summary>
+ public string? ServerName { get; set; }
+
+ /// <summary>
/// Gets or sets UI language culture.
/// </summary>
public string? UICulture { get; set; }
diff --git a/Jellyfin.Api/Models/SystemInfoDtos/FolderStorageDto.cs b/Jellyfin.Api/Models/SystemInfoDtos/FolderStorageDto.cs
new file mode 100644
index 000000000..00a965898
--- /dev/null
+++ b/Jellyfin.Api/Models/SystemInfoDtos/FolderStorageDto.cs
@@ -0,0 +1,46 @@
+using MediaBrowser.Model.System;
+
+namespace Jellyfin.Api.Models.SystemInfoDtos;
+
+/// <summary>
+/// Contains information about a specific folder.
+/// </summary>
+public record FolderStorageDto
+{
+ /// <summary>
+ /// Gets the path of the folder in question.
+ /// </summary>
+ public required string Path { get; init; }
+
+ /// <summary>
+ /// Gets the free space of the underlying storage device of the <see cref="Path"/>.
+ /// </summary>
+ public long FreeSpace { get; init; }
+
+ /// <summary>
+ /// Gets the used space of the underlying storage device of the <see cref="Path"/>.
+ /// </summary>
+ public long UsedSpace { get; init; }
+
+ /// <summary>
+ /// Gets the kind of storage device of the <see cref="Path"/>.
+ /// </summary>
+ public string? StorageType { get; init; }
+
+ /// <summary>
+ /// Gets the Device Identifier.
+ /// </summary>
+ public string? DeviceId { get; init; }
+
+ internal static FolderStorageDto FromFolderStorageInfo(FolderStorageInfo model)
+ {
+ return new()
+ {
+ Path = model.Path,
+ FreeSpace = model.FreeSpace,
+ UsedSpace = model.UsedSpace,
+ StorageType = model.StorageType,
+ DeviceId = model.DeviceId
+ };
+ }
+}
diff --git a/Jellyfin.Api/Models/SystemInfoDtos/LibraryStorageDto.cs b/Jellyfin.Api/Models/SystemInfoDtos/LibraryStorageDto.cs
new file mode 100644
index 000000000..c138324d2
--- /dev/null
+++ b/Jellyfin.Api/Models/SystemInfoDtos/LibraryStorageDto.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using MediaBrowser.Model.System;
+
+namespace Jellyfin.Api.Models.SystemInfoDtos;
+
+/// <summary>
+/// Contains informations about a libraries storage informations.
+/// </summary>
+public record LibraryStorageDto
+{
+ /// <summary>
+ /// Gets or sets the Library Id.
+ /// </summary>
+ public required Guid Id { get; set; }
+
+ /// <summary>
+ /// Gets or sets the name of the library.
+ /// </summary>
+ public required string Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the storage informations about the folders used in a library.
+ /// </summary>
+ public required IReadOnlyCollection<FolderStorageDto> Folders { get; set; }
+
+ internal static LibraryStorageDto FromLibraryStorageModel(LibraryStorageInfo model)
+ {
+ return new()
+ {
+ Id = model.Id,
+ Name = model.Name,
+ Folders = model.Folders.Select(FolderStorageDto.FromFolderStorageInfo).ToArray()
+ };
+ }
+}
diff --git a/Jellyfin.Api/Models/SystemInfoDtos/SystemStorageDto.cs b/Jellyfin.Api/Models/SystemInfoDtos/SystemStorageDto.cs
new file mode 100644
index 000000000..a09042439
--- /dev/null
+++ b/Jellyfin.Api/Models/SystemInfoDtos/SystemStorageDto.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using MediaBrowser.Model.System;
+
+namespace Jellyfin.Api.Models.SystemInfoDtos;
+
+/// <summary>
+/// Contains informations about the systems storage.
+/// </summary>
+public record SystemStorageDto
+{
+ /// <summary>
+ /// Gets or sets the Storage information of the program data folder.
+ /// </summary>
+ public required FolderStorageDto ProgramDataFolder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Storage information of the web UI resources folder.
+ /// </summary>
+ public required FolderStorageDto WebFolder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Storage information of the folder where images are cached.
+ /// </summary>
+ public required FolderStorageDto ImageCacheFolder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Storage information of the cache folder.
+ /// </summary>
+ public required FolderStorageDto CacheFolder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Storage information of the folder where logfiles are saved to.
+ /// </summary>
+ public required FolderStorageDto LogFolder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Storage information of the folder where metadata is stored.
+ /// </summary>
+ public required FolderStorageDto InternalMetadataFolder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Storage information of the transcoding cache.
+ /// </summary>
+ public required FolderStorageDto TranscodingTempFolder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the storage informations of all libraries.
+ /// </summary>
+ public required IReadOnlyCollection<LibraryStorageDto> Libraries { get; set; }
+
+ internal static SystemStorageDto FromSystemStorageInfo(SystemStorageInfo model)
+ {
+ return new SystemStorageDto()
+ {
+ ProgramDataFolder = FolderStorageDto.FromFolderStorageInfo(model.ProgramDataFolder),
+ WebFolder = FolderStorageDto.FromFolderStorageInfo(model.WebFolder),
+ ImageCacheFolder = FolderStorageDto.FromFolderStorageInfo(model.ImageCacheFolder),
+ CacheFolder = FolderStorageDto.FromFolderStorageInfo(model.CacheFolder),
+ LogFolder = FolderStorageDto.FromFolderStorageInfo(model.LogFolder),
+ InternalMetadataFolder = FolderStorageDto.FromFolderStorageInfo(model.InternalMetadataFolder),
+ TranscodingTempFolder = FolderStorageDto.FromFolderStorageInfo(model.TranscodingTempFolder),
+ Libraries = model.Libraries.Select(LibraryStorageDto.FromLibraryStorageModel).ToArray()
+ };
+ }
+}