Merge pull request #7947 from nyanmisaka/video-range-condition

(cherry picked from commit f1d56aa5ce)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
pull/7845/head
Cody Robibero 2 years ago committed by Joshua Boniface
parent 7f1223016d
commit 38102499cb

@ -221,6 +221,7 @@ namespace Emby.Dlna.Didl
streamInfo.IsDirectStream, streamInfo.IsDirectStream,
streamInfo.RunTimeTicks ?? 0, streamInfo.RunTimeTicks ?? 0,
streamInfo.TargetVideoProfile, streamInfo.TargetVideoProfile,
streamInfo.TargetVideoRangeType,
streamInfo.TargetVideoLevel, streamInfo.TargetVideoLevel,
streamInfo.TargetFramerate ?? 0, streamInfo.TargetFramerate ?? 0,
streamInfo.TargetPacketLength, streamInfo.TargetPacketLength,
@ -376,6 +377,7 @@ namespace Emby.Dlna.Didl
targetHeight, targetHeight,
streamInfo.TargetVideoBitDepth, streamInfo.TargetVideoBitDepth,
streamInfo.TargetVideoProfile, streamInfo.TargetVideoProfile,
streamInfo.TargetVideoRangeType,
streamInfo.TargetVideoLevel, streamInfo.TargetVideoLevel,
streamInfo.TargetFramerate ?? 0, streamInfo.TargetFramerate ?? 0,
streamInfo.TargetPacketLength, streamInfo.TargetPacketLength,

@ -561,6 +561,7 @@ namespace Emby.Dlna.PlayTo
streamInfo.IsDirectStream, streamInfo.IsDirectStream,
streamInfo.RunTimeTicks ?? 0, streamInfo.RunTimeTicks ?? 0,
streamInfo.TargetVideoProfile, streamInfo.TargetVideoProfile,
streamInfo.TargetVideoRangeType,
streamInfo.TargetVideoLevel, streamInfo.TargetVideoLevel,
streamInfo.TargetFramerate ?? 0, streamInfo.TargetFramerate ?? 0,
streamInfo.TargetPacketLength, streamInfo.TargetPacketLength,

@ -312,7 +312,7 @@ namespace Jellyfin.Api.Helpers
responseHeaders.Add( responseHeaders.Add(
"contentFeatures.dlna.org", "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.TargetVideoBitDepth,
state.OutputVideoBitrate, state.OutputVideoBitrate,
state.TargetVideoProfile, state.TargetVideoProfile,
state.TargetVideoRangeType,
state.TargetVideoLevel, state.TargetVideoLevel,
state.TargetFramerate, state.TargetFramerate,
state.TargetPacketLength, state.TargetPacketLength,

@ -75,6 +75,12 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <value>The profile.</value> /// <value>The profile.</value>
public string Profile { get; set; } public string Profile { get; set; }
/// <summary>
/// Gets or sets the video range type.
/// </summary>
/// <value>The video range type.</value>
public string VideoRangeType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the level. /// Gets or sets the level.
/// </summary> /// </summary>

@ -1753,6 +1753,20 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
} }
var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
if (requestedProfiles.Length > 0)
{
if (string.IsNullOrEmpty(videoStream.VideoRangeType))
{
return false;
}
if (!requestedRangeTypes.Contains(videoStream.VideoRangeType, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
// Video width must fall within requested value // Video width must fall within requested value
if (request.MaxWidth.HasValue if (request.MaxWidth.HasValue
&& (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value)) && (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))

@ -366,6 +366,28 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
} }
/// <summary>
/// Gets the target video range type.
/// </summary>
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 public string TargetVideoCodecTag
{ {
get get
@ -579,6 +601,26 @@ namespace MediaBrowser.Controller.MediaEncoding
return Array.Empty<string>(); return Array.Empty<string>();
} }
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<string>();
}
public string GetRequestedLevel(string codec) public string GetRequestedLevel(string codec)
{ {
if (!string.IsNullOrEmpty(BaseRequest.Level)) if (!string.IsNullOrEmpty(BaseRequest.Level))

@ -16,6 +16,7 @@ namespace MediaBrowser.Model.Dlna
int? videoBitDepth, int? videoBitDepth,
int? videoBitrate, int? videoBitrate,
string? videoProfile, string? videoProfile,
string? videoRangeType,
double? videoLevel, double? videoLevel,
float? videoFramerate, float? videoFramerate,
int? packetLength, int? packetLength,
@ -42,6 +43,8 @@ namespace MediaBrowser.Model.Dlna
return IsConditionSatisfied(condition, videoLevel); return IsConditionSatisfied(condition, videoLevel);
case ProfileConditionValue.VideoProfile: case ProfileConditionValue.VideoProfile:
return IsConditionSatisfied(condition, videoProfile); return IsConditionSatisfied(condition, videoProfile);
case ProfileConditionValue.VideoRangeType:
return IsConditionSatisfied(condition, videoRangeType);
case ProfileConditionValue.VideoCodecTag: case ProfileConditionValue.VideoCodecTag:
return IsConditionSatisfied(condition, videoCodecTag); return IsConditionSatisfied(condition, videoCodecTag);
case ProfileConditionValue.PacketLength: case ProfileConditionValue.PacketLength:

@ -128,6 +128,7 @@ namespace MediaBrowser.Model.Dlna
bool isDirectStream, bool isDirectStream,
long? runtimeTicks, long? runtimeTicks,
string videoProfile, string videoProfile,
string videoRangeType,
double? videoLevel, double? videoLevel,
float? videoFramerate, float? videoFramerate,
int? packetLength, int? packetLength,
@ -176,6 +177,7 @@ namespace MediaBrowser.Model.Dlna
bitDepth, bitDepth,
videoBitrate, videoBitrate,
videoProfile, videoProfile,
videoRangeType,
videoLevel, videoLevel,
videoFramerate, videoFramerate,
packetLength, packetLength,

@ -423,6 +423,7 @@ namespace MediaBrowser.Model.Dlna
/// <param name="bitDepth">The bit depth.</param> /// <param name="bitDepth">The bit depth.</param>
/// <param name="videoBitrate">The video bitrate.</param> /// <param name="videoBitrate">The video bitrate.</param>
/// <param name="videoProfile">The video profile.</param> /// <param name="videoProfile">The video profile.</param>
/// <param name="videoRangeType">The video range type.</param>
/// <param name="videoLevel">The video level.</param> /// <param name="videoLevel">The video level.</param>
/// <param name="videoFramerate">The video framerate.</param> /// <param name="videoFramerate">The video framerate.</param>
/// <param name="packetLength">The packet length.</param> /// <param name="packetLength">The packet length.</param>
@ -444,6 +445,7 @@ namespace MediaBrowser.Model.Dlna
int? bitDepth, int? bitDepth,
int? videoBitrate, int? videoBitrate,
string videoProfile, string videoProfile,
string videoRangeType,
double? videoLevel, double? videoLevel,
float? videoFramerate, float? videoFramerate,
int? packetLength, int? packetLength,
@ -483,7 +485,7 @@ namespace MediaBrowser.Model.Dlna
var anyOff = false; var anyOff = false;
foreach (ProfileCondition c in i.Conditions) 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; anyOff = true;
break; break;

@ -26,6 +26,7 @@ namespace MediaBrowser.Model.Dlna
IsAvc = 20, IsAvc = 20,
IsInterlaced = 21, IsInterlaced = 21,
AudioSampleRate = 22, AudioSampleRate = 22,
AudioBitDepth = 23 AudioBitDepth = 23,
VideoRangeType = 24
} }
} }

@ -221,6 +221,9 @@ namespace MediaBrowser.Model.Dlna
case ProfileConditionValue.VideoProfile: case ProfileConditionValue.VideoProfile:
return TranscodeReason.VideoProfileNotSupported; return TranscodeReason.VideoProfileNotSupported;
case ProfileConditionValue.VideoRangeType:
return TranscodeReason.VideoRangeTypeNotSupported;
case ProfileConditionValue.VideoTimestamp: case ProfileConditionValue.VideoTimestamp:
// TODO // TODO
return 0; return 0;
@ -748,9 +751,9 @@ namespace MediaBrowser.Model.Dlna
var appliedVideoConditions = options.Profile.CodecProfiles var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video && .Where(i => i.Type == CodecType.Video &&
i.ContainsAnyCodec(videoCodec, container) && 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 => .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 // An empty appliedVideoConditions means that the codec has no conditions for the current video stream
var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied); var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied);
@ -834,6 +837,7 @@ namespace MediaBrowser.Model.Dlna
int? videoBitrate = videoStream?.BitRate; int? videoBitrate = videoStream?.BitRate;
double? videoLevel = videoStream?.Level; double? videoLevel = videoStream?.Level;
string videoProfile = videoStream?.Profile; string videoProfile = videoStream?.Profile;
string videoRangeType = videoStream?.VideoRangeType;
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
bool? isAnamorphic = videoStream?.IsAnamorphic; bool? isAnamorphic = videoStream?.IsAnamorphic;
bool? isInterlaced = videoStream?.IsInterlaced; bool? isInterlaced = videoStream?.IsInterlaced;
@ -850,7 +854,7 @@ namespace MediaBrowser.Model.Dlna
var appliedVideoConditions = options.Profile.CodecProfiles var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video && .Where(i => i.Type == CodecType.Video &&
i.ContainsAnyCodec(videoCodec, container) && 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; var isFirstAppliedCodecProfile = true;
foreach (var i in appliedVideoConditions) foreach (var i in appliedVideoConditions)
{ {
@ -1081,6 +1085,7 @@ namespace MediaBrowser.Model.Dlna
int? videoBitrate = videoStream?.BitRate; int? videoBitrate = videoStream?.BitRate;
double? videoLevel = videoStream?.Level; double? videoLevel = videoStream?.Level;
string videoProfile = videoStream?.Profile; string videoProfile = videoStream?.Profile;
string videoRangeType = videoStream?.VideoRangeType;
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
bool? isAnamorphic = videoStream?.IsAnamorphic; bool? isAnamorphic = videoStream?.IsAnamorphic;
bool? isInterlaced = videoStream?.IsInterlaced; bool? isInterlaced = videoStream?.IsInterlaced;
@ -1098,7 +1103,7 @@ namespace MediaBrowser.Model.Dlna
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video); int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
var checkVideoConditions = (ProfileCondition[] conditions) => 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 // Check container conditions
var containerProfileReasons = AggregateFailureConditions( var containerProfileReasons = AggregateFailureConditions(
@ -1852,6 +1857,38 @@ namespace MediaBrowser.Model.Dlna
break; 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 | StringSplitOptions.TrimEntries);
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(v => string.Equals(v, currentValue, StringComparison.OrdinalIgnoreCase)))
{
item.SetOption(qualifier, "rangetype", currentValue);
}
else
{
item.SetOption(qualifier, "rangetype", string.Join(',', values));
}
}
break;
}
case ProfileConditionValue.Height: case ProfileConditionValue.Height:
{ {
if (!enableNonQualifiedConditions) if (!enableNonQualifiedConditions)

@ -280,6 +280,29 @@ namespace MediaBrowser.Model.Dlna
} }
} }
/// <summary>
/// Gets the target video range type that will be in the output stream.
/// </summary>
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;
}
}
/// <summary> /// <summary>
/// Gets the target video codec tag. /// Gets the target video codec tag.
/// </summary> /// </summary>

@ -104,32 +104,23 @@ namespace MediaBrowser.Model.Entities
{ {
get get
{ {
if (Type != MediaStreamType.Video) var (videoRange, _) = GetVideoColorRange();
{
return null;
}
var colorTransfer = ColorTransfer;
if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) return videoRange;
|| 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;
if (string.Equals(codecTag, "dovi", StringComparison.OrdinalIgnoreCase) /// <summary>
|| string.Equals(codecTag, "dvh1", StringComparison.OrdinalIgnoreCase) /// Gets the video range type.
|| string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase) /// </summary>
|| string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase)) /// <value>The video range type.</value>
{ public string VideoRangeType
return "HDR"; {
} get
{
var (_, videoRangeType) = GetVideoColorRange();
return "SDR"; return videoRangeType;
} }
} }
@ -571,5 +562,39 @@ namespace MediaBrowser.Model.Entities
return true; 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");
}
} }
} }

@ -17,6 +17,7 @@ namespace MediaBrowser.Model.Session
// Video Constraints // Video Constraints
VideoProfileNotSupported = 1 << 6, VideoProfileNotSupported = 1 << 6,
VideoRangeTypeNotSupported = 1 << 24,
VideoLevelNotSupported = 1 << 7, VideoLevelNotSupported = 1 << 7,
VideoResolutionNotSupported = 1 << 8, VideoResolutionNotSupported = 1 << 8,
VideoBitDepthNotSupported = 1 << 9, VideoBitDepthNotSupported = 1 << 9,

Loading…
Cancel
Save