diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index 6ab5843c15..df6539a5a6 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -221,6 +221,7 @@ namespace Emby.Dlna.Didl
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks ?? 0,
streamInfo.TargetVideoProfile,
+ streamInfo.TargetVideoRangeType,
streamInfo.TargetVideoLevel,
streamInfo.TargetFramerate ?? 0,
streamInfo.TargetPacketLength,
@@ -376,6 +377,7 @@ namespace Emby.Dlna.Didl
targetHeight,
streamInfo.TargetVideoBitDepth,
streamInfo.TargetVideoProfile,
+ streamInfo.TargetVideoRangeType,
streamInfo.TargetVideoLevel,
streamInfo.TargetFramerate ?? 0,
streamInfo.TargetPacketLength,
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index e27a8975b7..b73ce00b6f 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -561,6 +561,7 @@ namespace Emby.Dlna.PlayTo
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks ?? 0,
streamInfo.TargetVideoProfile,
+ streamInfo.TargetVideoRangeType,
streamInfo.TargetVideoLevel,
streamInfo.TargetFramerate ?? 0,
streamInfo.TargetPacketLength,
diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs
index 34dab75b82..c96ca87a56 100644
--- a/Jellyfin.Api/Helpers/StreamingHelpers.cs
+++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs
@@ -312,7 +312,7 @@ namespace Jellyfin.Api.Helpers
responseHeaders.Add(
"contentFeatures.dlna.org",
- ContentFeatureBuilder.BuildVideoHeader(profile, state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty);
+ ContentFeatureBuilder.BuildVideoHeader(profile, state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoRangeType, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty);
}
}
@@ -533,6 +533,7 @@ namespace Jellyfin.Api.Helpers
state.TargetVideoBitDepth,
state.OutputVideoBitrate,
state.TargetVideoProfile,
+ state.TargetVideoRangeType,
state.TargetVideoLevel,
state.TargetFramerate,
state.TargetPacketLength,
diff --git a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
index 462585ce35..fb4e7bd1f5 100644
--- a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
@@ -75,6 +75,12 @@ namespace MediaBrowser.Controller.MediaEncoding
/// The profile.
public string Profile { get; set; }
+ ///
+ /// Gets or sets the video range type.
+ ///
+ /// The video range type.
+ public string VideoRangeType { get; set; }
+
///
/// Gets or sets the level.
///
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 04778e6993..e0a3069e7c 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -1753,6 +1753,21 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
+ if (requestedProfiles.Length > 0)
+ {
+ if (string.IsNullOrEmpty(videoStream.VideoRangeType))
+ {
+ return false;
+ }
+
+ if (!string.IsNullOrEmpty(videoStream.VideoRangeType)
+ && !requestedRangeTypes.Contains(videoStream.VideoRangeType, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+ }
+
// Video width must fall within requested value
if (request.MaxWidth.HasValue
&& (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index 0824590f22..491662861f 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -366,6 +366,28 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ ///
+ /// Gets the target video range type.
+ ///
+ public string TargetVideoRangeType
+ {
+ get
+ {
+ if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec))
+ {
+ return VideoStream?.VideoRangeType;
+ }
+
+ var requestedRangeType = GetRequestedRangeTypes(ActualOutputVideoCodec).FirstOrDefault();
+ if (!string.IsNullOrEmpty(requestedRangeType))
+ {
+ return requestedRangeType;
+ }
+
+ return null;
+ }
+ }
+
public string TargetVideoCodecTag
{
get
@@ -579,6 +601,26 @@ namespace MediaBrowser.Controller.MediaEncoding
return Array.Empty();
}
+ public string[] GetRequestedRangeTypes(string codec)
+ {
+ if (!string.IsNullOrEmpty(BaseRequest.VideoRangeType))
+ {
+ return BaseRequest.VideoRangeType.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+
+ if (!string.IsNullOrEmpty(codec))
+ {
+ var rangetype = BaseRequest.GetOption(codec, "rangetype");
+
+ if (!string.IsNullOrEmpty(rangetype))
+ {
+ return rangetype.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+ }
+
+ return Array.Empty();
+ }
+
public string GetRequestedLevel(string codec)
{
if (!string.IsNullOrEmpty(BaseRequest.Level))
diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
index 8d03b4c0b3..5734224167 100644
--- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs
+++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
@@ -16,6 +16,7 @@ namespace MediaBrowser.Model.Dlna
int? videoBitDepth,
int? videoBitrate,
string? videoProfile,
+ string? videoRangeType,
double? videoLevel,
float? videoFramerate,
int? packetLength,
@@ -42,6 +43,8 @@ namespace MediaBrowser.Model.Dlna
return IsConditionSatisfied(condition, videoLevel);
case ProfileConditionValue.VideoProfile:
return IsConditionSatisfied(condition, videoProfile);
+ case ProfileConditionValue.VideoRangeType:
+ return IsConditionSatisfied(condition, videoRangeType);
case ProfileConditionValue.VideoCodecTag:
return IsConditionSatisfied(condition, videoCodecTag);
case ProfileConditionValue.PacketLength:
diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
index 6e129246b0..47c36494bd 100644
--- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
+++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
@@ -128,6 +128,7 @@ namespace MediaBrowser.Model.Dlna
bool isDirectStream,
long? runtimeTicks,
string videoProfile,
+ string videoRangeType,
double? videoLevel,
float? videoFramerate,
int? packetLength,
@@ -176,6 +177,7 @@ namespace MediaBrowser.Model.Dlna
bitDepth,
videoBitrate,
videoProfile,
+ videoRangeType,
videoLevel,
videoFramerate,
packetLength,
diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs
index 6170ff5bd6..79ae951708 100644
--- a/MediaBrowser.Model/Dlna/DeviceProfile.cs
+++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs
@@ -423,6 +423,7 @@ namespace MediaBrowser.Model.Dlna
/// The bit depth.
/// The video bitrate.
/// The video profile.
+ /// The video range type.
/// The video level.
/// The video framerate.
/// The packet length.
@@ -444,6 +445,7 @@ namespace MediaBrowser.Model.Dlna
int? bitDepth,
int? videoBitrate,
string videoProfile,
+ string videoRangeType,
double? videoLevel,
float? videoFramerate,
int? packetLength,
@@ -483,7 +485,7 @@ namespace MediaBrowser.Model.Dlna
var anyOff = false;
foreach (ProfileCondition c in i.Conditions)
{
- if (!ConditionProcessor.IsVideoConditionSatisfied(GetModelProfileCondition(c), width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
+ if (!ConditionProcessor.IsVideoConditionSatisfied(GetModelProfileCondition(c), width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
{
anyOff = true;
break;
diff --git a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
index eb81fde751..a32433e185 100644
--- a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
+++ b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
@@ -26,6 +26,7 @@ namespace MediaBrowser.Model.Dlna
IsAvc = 20,
IsInterlaced = 21,
AudioSampleRate = 22,
- AudioBitDepth = 23
+ AudioBitDepth = 23,
+ VideoRangeType = 24
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index fe9ff2ebe8..bfab185342 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -221,6 +221,9 @@ namespace MediaBrowser.Model.Dlna
case ProfileConditionValue.VideoProfile:
return TranscodeReason.VideoProfileNotSupported;
+ case ProfileConditionValue.VideoRangeType:
+ return TranscodeReason.VideoRangeTypeNotSupported;
+
case ProfileConditionValue.VideoTimestamp:
// TODO
return 0;
@@ -748,9 +751,9 @@ namespace MediaBrowser.Model.Dlna
var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video &&
i.ContainsAnyCodec(videoCodec, container) &&
- i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)))
+ i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)))
.Select(i =>
- i.Conditions.All(condition => ConditionProcessor.IsVideoConditionSatisfied(condition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)));
+ i.Conditions.All(condition => ConditionProcessor.IsVideoConditionSatisfied(condition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)));
// An empty appliedVideoConditions means that the codec has no conditions for the current video stream
var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied);
@@ -834,6 +837,7 @@ namespace MediaBrowser.Model.Dlna
int? videoBitrate = videoStream?.BitRate;
double? videoLevel = videoStream?.Level;
string videoProfile = videoStream?.Profile;
+ string videoRangeType = videoStream?.VideoRangeType;
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
bool? isAnamorphic = videoStream?.IsAnamorphic;
bool? isInterlaced = videoStream?.IsInterlaced;
@@ -850,7 +854,7 @@ namespace MediaBrowser.Model.Dlna
var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video &&
i.ContainsAnyCodec(videoCodec, container) &&
- i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)));
+ i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)));
var isFirstAppliedCodecProfile = true;
foreach (var i in appliedVideoConditions)
{
@@ -1081,6 +1085,7 @@ namespace MediaBrowser.Model.Dlna
int? videoBitrate = videoStream?.BitRate;
double? videoLevel = videoStream?.Level;
string videoProfile = videoStream?.Profile;
+ string videoRangeType = videoStream?.VideoRangeType;
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
bool? isAnamorphic = videoStream?.IsAnamorphic;
bool? isInterlaced = videoStream?.IsInterlaced;
@@ -1098,7 +1103,7 @@ namespace MediaBrowser.Model.Dlna
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
var checkVideoConditions = (ProfileCondition[] conditions) =>
- conditions.Where(applyCondition => !ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc));
+ conditions.Where(applyCondition => !ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc));
// Check container conditions
var containerProfileReasons = AggregateFailureConditions(
@@ -1852,6 +1857,38 @@ namespace MediaBrowser.Model.Dlna
break;
}
+ case ProfileConditionValue.VideoRangeType:
+ {
+ if (string.IsNullOrEmpty(qualifier))
+ {
+ continue;
+ }
+
+ // change from split by | to comma
+ // strip spaces to avoid having to encode
+ var values = value
+ .Split('|', StringSplitOptions.RemoveEmptyEntries);
+
+ if (condition.Condition == ProfileConditionType.Equals)
+ {
+ item.SetOption(qualifier, "rangetype", string.Join(',', values));
+ }
+ else if (condition.Condition == ProfileConditionType.EqualsAny)
+ {
+ var currentValue = item.GetOption(qualifier, "rangetype");
+ if (!string.IsNullOrEmpty(currentValue) && values.Any(value => value == currentValue))
+ {
+ item.SetOption(qualifier, "rangetype", currentValue);
+ }
+ else
+ {
+ item.SetOption(qualifier, "rangetype", string.Join(',', values));
+ }
+ }
+
+ break;
+ }
+
case ProfileConditionValue.Height:
{
if (!enableNonQualifiedConditions)
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index da089602f1..0c66351c70 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -280,6 +280,29 @@ namespace MediaBrowser.Model.Dlna
}
}
+ ///
+ /// Gets the target video range type that will be in the output stream.
+ ///
+ public string TargetVideoRangeType
+ {
+ get
+ {
+ if (IsDirectStream)
+ {
+ return TargetVideoStream?.VideoRangeType;
+ }
+
+ var targetVideoCodecs = TargetVideoCodec;
+ var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
+ if (!string.IsNullOrEmpty(videoCodec))
+ {
+ return GetOption(videoCodec, "rangetype");
+ }
+
+ return TargetVideoStream?.VideoRangeType;
+ }
+ }
+
///
/// Gets the target video codec tag.
///
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index 96b48ca52a..46a481092e 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -104,32 +104,23 @@ namespace MediaBrowser.Model.Entities
{
get
{
- if (Type != MediaStreamType.Video)
- {
- return null;
- }
-
- var colorTransfer = ColorTransfer;
+ var (videoRange, videoRangeType) = getVideoColorRange();
- if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
- || string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
- {
- return "HDR";
- }
-
- // For some Dolby Vision files, no color transfer is provided, so check the codec
-
- var codecTag = CodecTag;
+ return videoRange;
+ }
+ }
- if (string.Equals(codecTag, "dovi", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase))
- {
- return "HDR";
- }
+ ///
+ /// Gets the video range type.
+ ///
+ /// The video range type.
+ public string VideoRangeType
+ {
+ get
+ {
+ var (videoRange, videoRangeType) = getVideoColorRange();
- return "SDR";
+ return videoRangeType;
}
}
@@ -571,5 +562,39 @@ namespace MediaBrowser.Model.Entities
return true;
}
+
+ public (string VideoRange, string VideoRangeType) getVideoColorRange()
+ {
+ if (Type != MediaStreamType.Video)
+ {
+ return (null, null);
+ }
+
+ var colorTransfer = ColorTransfer;
+
+ if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase))
+ {
+ return ("HDR", "HDR10");
+ }
+
+ if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
+ {
+ return ("HDR", "HLG");
+ }
+
+ // For some Dolby Vision files, no color transfer is provided, so check the codec
+
+ var codecTag = CodecTag;
+
+ if (string.Equals(codecTag, "dovi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase))
+ {
+ return ("HDR", "DOVI");
+ }
+
+ return ("SDR", "SDR");
+ }
}
}
diff --git a/MediaBrowser.Model/Session/TranscodeReason.cs b/MediaBrowser.Model/Session/TranscodeReason.cs
index 9da9f3323b..bbdf4536b7 100644
--- a/MediaBrowser.Model/Session/TranscodeReason.cs
+++ b/MediaBrowser.Model/Session/TranscodeReason.cs
@@ -17,6 +17,7 @@ namespace MediaBrowser.Model.Session
// Video Constraints
VideoProfileNotSupported = 1 << 6,
+ VideoRangeTypeNotSupported = 1 << 24,
VideoLevelNotSupported = 1 << 7,
VideoResolutionNotSupported = 1 << 8,
VideoBitDepthNotSupported = 1 << 9,