Browse Source

Migrate VideoRange and VideoRangeType to Enum

Shadowghost 1 year ago
parent
commit
20a4509991

+ 2 - 1
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)))

+ 6 - 7
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
     /// <param name="state">StreamState of the current stream.</param>
     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");
                 }

+ 22 - 0
Jellyfin.Data/Enums/VideoRange.cs

@@ -0,0 +1,22 @@
+namespace Jellyfin.Data.Enums;
+
+/// <summary>
+/// An enum representing video ranges.
+/// </summary>
+public enum VideoRange
+{
+    /// <summary>
+    /// Unknown video range.
+    /// </summary>
+    Unknown,
+
+    /// <summary>
+    /// SDR video range.
+    /// </summary>
+    SDR,
+
+    /// <summary>
+    /// HDR video range.
+    /// </summary>
+    HDR
+}

+ 37 - 0
Jellyfin.Data/Enums/VideoRangeType.cs

@@ -0,0 +1,37 @@
+namespace Jellyfin.Data.Enums;
+
+/// <summary>
+/// An enum representing types of video ranges.
+/// </summary>
+public enum VideoRangeType
+{
+    /// <summary>
+    /// Unknown video range type.
+    /// </summary>
+    Unknown,
+
+    /// <summary>
+    /// SDR video range type (8bit).
+    /// </summary>
+    SDR,
+
+    /// <summary>
+    /// HDR10 video range type (10bit).
+    /// </summary>
+    HDR10,
+
+    /// <summary>
+    /// HLG video range type (10bit).
+    /// </summary>
+    HLG,
+
+    /// <summary>
+    /// Dolby Vision video range type (12bit).
+    /// </summary>
+    DOVI,
+
+    /// <summary>
+    /// HDR10+ video range type (10bit to 16bit).
+    /// </summary>
+    HDR10Plus
+}

+ 10 - 10
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;
         }
 
         /// <summary>
@@ -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;
                 }

+ 5 - 5
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
         /// <summary>
         /// Gets the target video range type.
         /// </summary>
-        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;
             }
         }
 

+ 90 - 3
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
 {
+    /// <summary>
+    /// The condition processor.
+    /// </summary>
     public static class ConditionProcessor
     {
+        /// <summary>
+        /// Checks if a video condition is satisfied.
+        /// </summary>
+        /// <param name="condition">The <see cref="ProfileCondition"/>.</param>
+        /// <param name="width">The width.</param>
+        /// <param name="height">The height.</param>
+        /// <param name="videoBitDepth">The bit depth.</param>
+        /// <param name="videoBitrate">The bitrate.</param>
+        /// <param name="videoProfile">The video profile.</param>
+        /// <param name="videoRangeType">The <see cref="VideoRangeType"/>.</param>
+        /// <param name="videoLevel">The video level.</param>
+        /// <param name="videoFramerate">The framerate.</param>
+        /// <param name="packetLength">The packet length.</param>
+        /// <param name="timestamp">The <see cref="TransportStreamTimestamp"/>.</param>
+        /// <param name="isAnamorphic">A value indicating whether tthe video is anamorphic.</param>
+        /// <param name="isInterlaced">A value indicating whether tthe video is interlaced.</param>
+        /// <param name="refFrames">The reference frames.</param>
+        /// <param name="numVideoStreams">The number of video streams.</param>
+        /// <param name="numAudioStreams">The number of audio streams.</param>
+        /// <param name="videoCodecTag">The video codec tag.</param>
+        /// <param name="isAvc">A value indicating whether the video is AVC.</param>
+        /// <returns><b>True</b> if the condition is satisfied.</returns>
         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
             }
         }
 
+        /// <summary>
+        /// Checks if a image condition is satisfied.
+        /// </summary>
+        /// <param name="condition">The <see cref="ProfileCondition"/>.</param>
+        /// <param name="width">The width.</param>
+        /// <param name="height">The height.</param>
+        /// <returns><b>True</b> if the condition is satisfied.</returns>
         public static bool IsImageConditionSatisfied(ProfileCondition condition, int? width, int? height)
         {
             switch (condition.Property)
@@ -83,6 +114,15 @@ namespace MediaBrowser.Model.Dlna
             }
         }
 
+        /// <summary>
+        /// Checks if an audio condition is satisfied.
+        /// </summary>
+        /// <param name="condition">The <see cref="ProfileCondition"/>.</param>
+        /// <param name="audioChannels">The channel count.</param>
+        /// <param name="audioBitrate">The bitrate.</param>
+        /// <param name="audioSampleRate">The sample rate.</param>
+        /// <param name="audioBitDepth">The bit depth.</param>
+        /// <returns><b>True</b> if the condition is satisfied.</returns>
         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
             }
         }
 
+        /// <summary>
+        /// Checks if an audio condition is satisfied for a video.
+        /// </summary>
+        /// <param name="condition">The <see cref="ProfileCondition"/>.</param>
+        /// <param name="audioChannels">The channel count.</param>
+        /// <param name="audioBitrate">The bitrate.</param>
+        /// <param name="audioSampleRate">The sample rate.</param>
+        /// <param name="audioBitDepth">The bit depth.</param>
+        /// <param name="audioProfile">The profile.</param>
+        /// <param name="isSecondaryTrack">A value indicating whether the audio is a secondary track.</param>
+        /// <returns><b>True</b> if the condition is satisfied.</returns>
         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;
+        }
     }
 }

+ 2 - 1
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,

+ 2 - 1
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,

+ 7 - 2
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");

+ 7 - 5
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
         /// <summary>
         /// Gets the target video range type that will be in the output stream.
         /// </summary>
-        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;
             }
         }
 

+ 11 - 10
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.
         /// </summary>
         /// <value>The video range.</value>
-        public string VideoRange
+        public VideoRange VideoRange
         {
             get
             {
@@ -162,7 +163,7 @@ namespace MediaBrowser.Model.Entities
         /// Gets the video range type.
         /// </summary>
         /// <value>The video range type.</value>
-        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);
         }
     }
 }