diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index 09cf7303e7..898c673ea5 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -12,6 +12,7 @@ using Jellyfin.Api.Attributes;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos;
+using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Jellyfin.MediaEncoding.Hls.Playlist;
using MediaBrowser.Common.Configuration;
@@ -1809,7 +1810,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
{
if (EncodingHelper.IsCopyCodec(codec)
- && (string.Equals(state.VideoStream.VideoRangeType, "DOVI", StringComparison.OrdinalIgnoreCase)
+ && (state.VideoStream.VideoRangeType == VideoRangeType.DOVI
|| string.Equals(state.VideoStream.CodecTag, "dovi", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.VideoStream.CodecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.VideoStream.CodecTag, "dvhe", StringComparison.OrdinalIgnoreCase)))
diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
index 4486954c62..ce73b57541 100644
--- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
+++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Models.StreamingDtos;
+using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
@@ -211,8 +212,7 @@ public class DynamicHlsHelper
// Provide SDR HEVC entrance for backward compatibility.
if (encodingOptions.AllowHevcEncoding
&& EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
- && !string.IsNullOrEmpty(state.VideoStream.VideoRange)
- && string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
+ && state.VideoStream.VideoRange == VideoRange.HDR
&& string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
{
var requestedVideoProfiles = state.GetRequestedProfiles("hevc");
@@ -255,8 +255,7 @@ public class DynamicHlsHelper
if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
&& state.VideoStream.Level.HasValue
&& state.VideoStream.Level > 150
- && !string.IsNullOrEmpty(state.VideoStream.VideoRange)
- && string.Equals(state.VideoStream.VideoRange, "SDR", StringComparison.OrdinalIgnoreCase)
+ && state.VideoStream.VideoRange == VideoRange.SDR
&& string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
{
var playlistCodecsField = new StringBuilder();
@@ -340,17 +339,17 @@ public class DynamicHlsHelper
/// StreamState of the current stream.
private void AppendPlaylistVideoRangeField(StringBuilder builder, StreamState state)
{
- if (state.VideoStream is not null && !string.IsNullOrEmpty(state.VideoStream.VideoRange))
+ if (state.VideoStream is not null && state.VideoStream.VideoRange != VideoRange.Unknown)
{
var videoRange = state.VideoStream.VideoRange;
if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
{
- if (string.Equals(videoRange, "SDR", StringComparison.OrdinalIgnoreCase))
+ if (videoRange == VideoRange.SDR)
{
builder.Append(",VIDEO-RANGE=SDR");
}
- if (string.Equals(videoRange, "HDR", StringComparison.OrdinalIgnoreCase))
+ if (videoRange == VideoRange.HDR)
{
builder.Append(",VIDEO-RANGE=PQ");
}
diff --git a/Jellyfin.Data/Enums/VideoRange.cs b/Jellyfin.Data/Enums/VideoRange.cs
new file mode 100644
index 0000000000..5072e5ba3e
--- /dev/null
+++ b/Jellyfin.Data/Enums/VideoRange.cs
@@ -0,0 +1,22 @@
+namespace Jellyfin.Data.Enums;
+
+///
+/// An enum representing video ranges.
+///
+public enum VideoRange
+{
+ ///
+ /// Unknown video range.
+ ///
+ Unknown,
+
+ ///
+ /// SDR video range.
+ ///
+ SDR,
+
+ ///
+ /// HDR video range.
+ ///
+ HDR
+}
diff --git a/Jellyfin.Data/Enums/VideoRangeType.cs b/Jellyfin.Data/Enums/VideoRangeType.cs
new file mode 100644
index 0000000000..7ac7bc20a3
--- /dev/null
+++ b/Jellyfin.Data/Enums/VideoRangeType.cs
@@ -0,0 +1,37 @@
+namespace Jellyfin.Data.Enums;
+
+///
+/// An enum representing types of video ranges.
+///
+public enum VideoRangeType
+{
+ ///
+ /// Unknown video range type.
+ ///
+ Unknown,
+
+ ///
+ /// SDR video range type (8bit).
+ ///
+ SDR,
+
+ ///
+ /// HDR10 video range type (10bit).
+ ///
+ HDR10,
+
+ ///
+ /// HLG video range type (10bit).
+ ///
+ HLG,
+
+ ///
+ /// Dolby Vision video range type (12bit).
+ ///
+ DOVI,
+
+ ///
+ /// HDR10+ video range type (10bit to 16bit).
+ ///
+ HDR10Plus
+}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 39d53768e9..027053b13c 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -209,8 +209,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
if (string.Equals(state.VideoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
- && string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
- && string.Equals(state.VideoStream.VideoRangeType, "DOVI", StringComparison.OrdinalIgnoreCase))
+ && state.VideoStream.VideoRange == VideoRange.HDR
+ && state.VideoStream.VideoRangeType == VideoRangeType.DOVI)
{
// Only native SW decoder and HW accelerator can parse dovi rpu.
var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
@@ -221,9 +221,9 @@ namespace MediaBrowser.Controller.MediaEncoding
return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder;
}
- return string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
- && (string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase)
- || string.Equals(state.VideoStream.VideoRangeType, "HLG", StringComparison.OrdinalIgnoreCase));
+ return state.VideoStream.VideoRange == VideoRange.HDR
+ && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
+ || state.VideoStream.VideoRangeType == VideoRangeType.HLG);
}
private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
@@ -235,7 +235,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// libplacebo has partial Dolby Vision to SDR tonemapping support.
return options.EnableTonemapping
- && string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
+ && state.VideoStream.VideoRange == VideoRange.HDR
&& GetVideoColorBitDepth(state) == 10;
}
@@ -250,8 +250,8 @@ namespace MediaBrowser.Controller.MediaEncoding
// Native VPP tonemapping may come to QSV in the future.
- return string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
- && string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase);
+ return state.VideoStream.VideoRange == VideoRange.HDR
+ && state.VideoStream.VideoRangeType == VideoRangeType.HDR10;
}
///
@@ -1945,12 +1945,12 @@ namespace MediaBrowser.Controller.MediaEncoding
var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
if (requestedRangeTypes.Length > 0)
{
- if (string.IsNullOrEmpty(videoStream.VideoRangeType))
+ if (videoStream.VideoRangeType == VideoRangeType.Unknown)
{
return false;
}
- if (!requestedRangeTypes.Contains(videoStream.VideoRangeType, StringComparison.OrdinalIgnoreCase))
+ if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreCase))
{
return false;
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index a6b5416601..17813559a8 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
@@ -367,22 +368,21 @@ namespace MediaBrowser.Controller.MediaEncoding
///
/// Gets the target video range type.
///
- public string TargetVideoRangeType
+ public VideoRangeType TargetVideoRangeType
{
get
{
if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec))
{
- return VideoStream?.VideoRangeType;
+ return VideoStream?.VideoRangeType ?? VideoRangeType.Unknown;
}
- var requestedRangeType = GetRequestedRangeTypes(ActualOutputVideoCodec).FirstOrDefault();
- if (!string.IsNullOrEmpty(requestedRangeType))
+ if (Enum.TryParse(GetRequestedRangeTypes(ActualOutputVideoCodec).FirstOrDefault() ?? "Unknown", true, out VideoRangeType requestedRangeType))
{
return requestedRangeType;
}
- return null;
+ return VideoRangeType.Unknown;
}
}
diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
index f5e1a3c496..af0787990d 100644
--- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs
+++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
@@ -1,14 +1,38 @@
-#pragma warning disable CS1591
-
using System;
using System.Globalization;
+using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Model.Dlna
{
+ ///
+ /// The condition processor.
+ ///
public static class ConditionProcessor
{
+ ///
+ /// Checks if a video condition is satisfied.
+ ///
+ /// The .
+ /// The width.
+ /// The height.
+ /// The bit depth.
+ /// The bitrate.
+ /// The video profile.
+ /// The .
+ /// The video level.
+ /// The framerate.
+ /// The packet length.
+ /// The .
+ /// A value indicating whether tthe video is anamorphic.
+ /// A value indicating whether tthe video is interlaced.
+ /// The reference frames.
+ /// The number of video streams.
+ /// The number of audio streams.
+ /// The video codec tag.
+ /// A value indicating whether the video is AVC.
+ /// True if the condition is satisfied.
public static bool IsVideoConditionSatisfied(
ProfileCondition condition,
int? width,
@@ -16,7 +40,7 @@ namespace MediaBrowser.Model.Dlna
int? videoBitDepth,
int? videoBitrate,
string? videoProfile,
- string? videoRangeType,
+ VideoRangeType? videoRangeType,
double? videoLevel,
float? videoFramerate,
int? packetLength,
@@ -70,6 +94,13 @@ namespace MediaBrowser.Model.Dlna
}
}
+ ///
+ /// Checks if a image condition is satisfied.
+ ///
+ /// The .
+ /// The width.
+ /// The height.
+ /// True if the condition is satisfied.
public static bool IsImageConditionSatisfied(ProfileCondition condition, int? width, int? height)
{
switch (condition.Property)
@@ -83,6 +114,15 @@ namespace MediaBrowser.Model.Dlna
}
}
+ ///
+ /// Checks if an audio condition is satisfied.
+ ///
+ /// The .
+ /// The channel count.
+ /// The bitrate.
+ /// The sample rate.
+ /// The bit depth.
+ /// True if the condition is satisfied.
public static bool IsAudioConditionSatisfied(ProfileCondition condition, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth)
{
switch (condition.Property)
@@ -100,6 +140,17 @@ namespace MediaBrowser.Model.Dlna
}
}
+ ///
+ /// Checks if an audio condition is satisfied for a video.
+ ///
+ /// The .
+ /// The channel count.
+ /// The bitrate.
+ /// The sample rate.
+ /// The bit depth.
+ /// The profile.
+ /// A value indicating whether the audio is a secondary track.
+ /// True if the condition is satisfied.
public static bool IsVideoAudioConditionSatisfied(
ProfileCondition condition,
int? audioChannels,
@@ -281,5 +332,41 @@ namespace MediaBrowser.Model.Dlna
throw new InvalidOperationException("Unexpected ProfileConditionType: " + condition.Condition);
}
}
+
+ private static bool IsConditionSatisfied(ProfileCondition condition, VideoRangeType? currentValue)
+ {
+ if (!currentValue.HasValue || currentValue.Equals(VideoRangeType.Unknown))
+ {
+ // If the value is unknown, it satisfies if not marked as required
+ return !condition.IsRequired;
+ }
+
+ var conditionType = condition.Condition;
+ if (conditionType == ProfileConditionType.EqualsAny)
+ {
+ foreach (var singleConditionString in condition.Value.AsSpan().Split('|'))
+ {
+ if (Enum.TryParse(singleConditionString, true, out VideoRangeType conditionValue)
+ && conditionValue.Equals(currentValue))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (Enum.TryParse(condition.Value, true, out VideoRangeType expected))
+ {
+ return conditionType switch
+ {
+ ProfileConditionType.Equals => currentValue.Value == expected,
+ ProfileConditionType.NotEquals => currentValue.Value != expected,
+ _ => throw new InvalidOperationException("Unexpected ProfileConditionType: " + condition.Condition)
+ };
+ }
+
+ return false;
+ }
}
}
diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
index 1d5d0b1de3..f29022b54e 100644
--- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
+++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using Jellyfin.Data.Enums;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Model.Dlna
@@ -128,7 +129,7 @@ namespace MediaBrowser.Model.Dlna
bool isDirectStream,
long? runtimeTicks,
string videoProfile,
- string videoRangeType,
+ VideoRangeType videoRangeType,
double? videoLevel,
float? videoFramerate,
int? packetLength,
diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs
index 79ae951708..b7c23669df 100644
--- a/MediaBrowser.Model/Dlna/DeviceProfile.cs
+++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs
@@ -2,6 +2,7 @@
using System;
using System.ComponentModel;
using System.Xml.Serialization;
+using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Model.MediaInfo;
@@ -445,7 +446,7 @@ namespace MediaBrowser.Model.Dlna
int? bitDepth,
int? videoBitrate,
string videoProfile,
- string videoRangeType,
+ VideoRangeType videoRangeType,
double? videoLevel,
float? videoFramerate,
int? packetLength,
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 0a955e917f..2dbd14da4d 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using Jellyfin.Data.Enums;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
@@ -889,7 +890,7 @@ namespace MediaBrowser.Model.Dlna
int? videoBitrate = videoStream?.BitRate;
double? videoLevel = videoStream?.Level;
string? videoProfile = videoStream?.Profile;
- string? videoRangeType = videoStream?.VideoRangeType;
+ VideoRangeType? videoRangeType = videoStream?.VideoRangeType;
float videoFramerate = videoStream is null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
bool? isAnamorphic = videoStream?.IsAnamorphic;
bool? isInterlaced = videoStream?.IsInterlaced;
@@ -1144,7 +1145,7 @@ namespace MediaBrowser.Model.Dlna
int? videoBitrate = videoStream?.BitRate;
double? videoLevel = videoStream?.Level;
string? videoProfile = videoStream?.Profile;
- string? videoRangeType = videoStream?.VideoRangeType;
+ VideoRangeType? videoRangeType = videoStream?.VideoRangeType;
float videoFramerate = videoStream is null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
bool? isAnamorphic = videoStream?.IsAnamorphic;
bool? isInterlaced = videoStream?.IsInterlaced;
@@ -1932,6 +1933,10 @@ namespace MediaBrowser.Model.Dlna
{
item.SetOption(qualifier, "rangetype", string.Join(',', values));
}
+ else if (condition.Condition == ProfileConditionType.NotEquals)
+ {
+ item.SetOption(qualifier, "rangetype", string.Join(',', Enum.GetNames(typeof(VideoRangeType)).Except(values)));
+ }
else if (condition.Condition == ProfileConditionType.EqualsAny)
{
var currentValue = item.GetOption(qualifier, "rangetype");
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index a78a28e13a..00543616d1 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using Jellyfin.Data.Enums;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -281,23 +282,24 @@ namespace MediaBrowser.Model.Dlna
///
/// Gets the target video range type that will be in the output stream.
///
- public string TargetVideoRangeType
+ public VideoRangeType TargetVideoRangeType
{
get
{
if (IsDirectStream)
{
- return TargetVideoStream?.VideoRangeType;
+ return TargetVideoStream?.VideoRangeType ?? VideoRangeType.Unknown;
}
var targetVideoCodecs = TargetVideoCodec;
var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
- if (!string.IsNullOrEmpty(videoCodec))
+ if (!string.IsNullOrEmpty(videoCodec)
+ && Enum.TryParse(GetOption(videoCodec, "rangetype"), true, out VideoRangeType videoRangeType))
{
- return GetOption(videoCodec, "rangetype");
+ return videoRangeType;
}
- return TargetVideoStream?.VideoRangeType;
+ return TargetVideoStream?.VideoRangeType ?? VideoRangeType.Unknown;
}
}
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index 47341f4e17..34642b83aa 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
+using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Extensions;
@@ -148,7 +149,7 @@ namespace MediaBrowser.Model.Entities
/// Gets the video range.
///
/// The video range.
- public string VideoRange
+ public VideoRange VideoRange
{
get
{
@@ -162,7 +163,7 @@ namespace MediaBrowser.Model.Entities
/// Gets the video range type.
///
/// The video range type.
- public string VideoRangeType
+ public VideoRangeType VideoRangeType
{
get
{
@@ -306,9 +307,9 @@ namespace MediaBrowser.Model.Entities
attributes.Add(Codec.ToUpperInvariant());
}
- if (!string.IsNullOrEmpty(VideoRange))
+ if (VideoRange != VideoRange.Unknown)
{
- attributes.Add(VideoRange.ToUpperInvariant());
+ attributes.Add(VideoRange.ToString());
}
if (!string.IsNullOrEmpty(Title))
@@ -677,23 +678,23 @@ namespace MediaBrowser.Model.Entities
return true;
}
- public (string VideoRange, string VideoRangeType) GetVideoColorRange()
+ public (VideoRange VideoRange, VideoRangeType VideoRangeType) GetVideoColorRange()
{
if (Type != MediaStreamType.Video)
{
- return (null, null);
+ return (VideoRange.Unknown, VideoRangeType.Unknown);
}
var colorTransfer = ColorTransfer;
if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase))
{
- return ("HDR", "HDR10");
+ return (VideoRange.HDR, VideoRangeType.HDR10);
}
if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
{
- return ("HDR", "HLG");
+ return (VideoRange.HDR, VideoRangeType.HLG);
}
var codecTag = CodecTag;
@@ -711,10 +712,10 @@ namespace MediaBrowser.Model.Entities
|| string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase))
{
- return ("HDR", "DOVI");
+ return (VideoRange.HDR, VideoRangeType.DOVI);
}
- return ("SDR", "SDR");
+ return (VideoRange.SDR, VideoRangeType.SDR);
}
}
}