Merge pull request #17806 from jellyfin/fix/dovi-color-validation
Enforce dolby vision transfer check
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Streaming;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
using IConfiguration = Microsoft.Extensions.Configuration.IConfiguration;
|
||||
|
||||
namespace Jellyfin.Controller.Tests.MediaEncoding;
|
||||
|
||||
public class EncodingHelperDoviTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null, false)]
|
||||
[InlineData("bt709", false)]
|
||||
[InlineData("unknown", false)]
|
||||
[InlineData("bt2020-10", false)]
|
||||
[InlineData("smpte2084", true)]
|
||||
[InlineData("arib-std-b67", true)]
|
||||
public void GetSwVidFilterChain_InvalidDovi_OnlyTonemapsHdrBaseLayer(string? transfer, bool tonemap)
|
||||
{
|
||||
var state = CreateState("hevc", transfer);
|
||||
var helper = CreateHelper(true);
|
||||
|
||||
var (filters, _, _) = helper.GetSwVidFilterChain(state, new EncodingOptions(), "libx264");
|
||||
var args = string.Join(',', filters);
|
||||
|
||||
Assert.Equal(VideoRangeType.DOVIInvalid, state.VideoStream.VideoRangeType);
|
||||
Assert.Equal(tonemap, args.Contains("tonemapx=", StringComparison.Ordinal));
|
||||
Assert.Contains(tonemap ? "color_trc=" + transfer : "color_trc=bt709", args, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, false)]
|
||||
[InlineData("bt709", false)]
|
||||
[InlineData("arib-std-b67", false)]
|
||||
[InlineData("smpte2084", true)]
|
||||
[InlineData("SMPTE2084", true)]
|
||||
public void IsDoviWithHdr10Bl_InvalidDovi_RequiresPq(string? transfer, bool expected)
|
||||
{
|
||||
var stream = CreateState("hevc", transfer).VideoStream;
|
||||
|
||||
Assert.True(EncodingHelper.IsDovi(stream));
|
||||
Assert.Equal(expected, EncodingHelper.IsDoviWithHdr10Bl(stream));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("hevc", null, "hevc_metadata=remove_dovi=1")]
|
||||
[InlineData("hevc", "bt709", "hevc_metadata=remove_dovi=1")]
|
||||
[InlineData("hevc", "smpte2084", "hevc_metadata=remove_dovi=1")]
|
||||
[InlineData("hevc", "arib-std-b67", "hevc_metadata=remove_dovi=1")]
|
||||
[InlineData("av1", null, "av1_metadata=remove_dovi=1")]
|
||||
[InlineData("av1", "bt709", "av1_metadata=remove_dovi=1")]
|
||||
[InlineData("av1", "smpte2084", "av1_metadata=remove_dovi=1")]
|
||||
[InlineData("av1", "arib-std-b67", "av1_metadata=remove_dovi=1")]
|
||||
public void GetBitStreamArgs_InvalidDovi_PreservesClientDependentRemoval(string codec, string? transfer, string expected)
|
||||
{
|
||||
var state = CreateState(codec, transfer);
|
||||
var helper = CreateHelper(true);
|
||||
|
||||
foreach (var (requestedRanges, removeDovi) in new[] { (null, false), ("SDR", false), ("HDR10", false), ("DOVIWithEL", false), ("DOVI", true), ("SDR,DOVI", true) })
|
||||
{
|
||||
state.BaseRequest.VideoRangeType = requestedRanges;
|
||||
|
||||
Assert.Equal(removeDovi, helper.IsDoviRemoved(state));
|
||||
if (removeDovi)
|
||||
{
|
||||
Assert.Contains(expected, helper.GetBitStreamArgs(state, MediaStreamType.Video), StringComparison.Ordinal);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(codec == "hevc" ? "-bsf:v hevc_mp4toannexb" : null, helper.GetBitStreamArgs(state, MediaStreamType.Video));
|
||||
}
|
||||
|
||||
Assert.False(CreateHelper(false).IsDoviRemoved(state));
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, true)]
|
||||
[InlineData("HDR10", true)]
|
||||
[InlineData("DOVI", false)]
|
||||
[InlineData("SDR,DOVI", false)]
|
||||
public void CanStreamCopyVideo_InvalidDovi_RequiresRemovalSupportOnlyForDoviClients(string? requestedRanges, bool copyWithoutRemovalSupport)
|
||||
{
|
||||
foreach (var codec in new[] { "hevc", "av1" })
|
||||
{
|
||||
foreach (var transfer in new[] { "bt709", "smpte2084" })
|
||||
{
|
||||
var state = CreateState(codec, transfer);
|
||||
state.BaseRequest.VideoRangeType = requestedRanges;
|
||||
|
||||
Assert.True(CreateHelper(true).CanStreamCopyVideo(state, state.VideoStream));
|
||||
Assert.Equal(copyWithoutRemovalSupport, CreateHelper(false).CanStreamCopyVideo(state, state.VideoStream));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetBitStreamArgs_ValidDovi_PreservesMetadata()
|
||||
{
|
||||
var state = CreateState("hevc", "smpte2084");
|
||||
state.VideoStream.ColorSpace = "bt2020nc";
|
||||
state.VideoStream.ColorPrimaries = "bt2020";
|
||||
state.BaseRequest.VideoRangeType = "DOVIWithEL";
|
||||
var helper = CreateHelper(true);
|
||||
|
||||
Assert.False(helper.IsDoviRemoved(state));
|
||||
Assert.Equal("-bsf:v hevc_mp4toannexb", helper.GetBitStreamArgs(state, MediaStreamType.Video));
|
||||
}
|
||||
|
||||
private static EncodingJobInfo CreateState(string codec, string? transfer)
|
||||
{
|
||||
var stream = new MediaStream
|
||||
{
|
||||
Type = MediaStreamType.Video,
|
||||
Codec = codec,
|
||||
Width = 1920,
|
||||
Height = 1080,
|
||||
BitDepth = 10,
|
||||
DvProfile = codec == "hevc" ? 7 : 10,
|
||||
DvBlSignalCompatibilityId = codec == "hevc" ? 6 : 1,
|
||||
RpuPresentFlag = 1,
|
||||
BlPresentFlag = 1,
|
||||
ColorSpace = "bt709",
|
||||
ColorPrimaries = "bt709",
|
||||
ColorTransfer = transfer
|
||||
};
|
||||
|
||||
return new EncodingJobInfo(TranscodingJobType.Hls)
|
||||
{
|
||||
VideoStream = stream,
|
||||
MediaSource = new MediaSourceInfo { Container = "mkv", MediaStreams = [stream] },
|
||||
BaseRequest = new VideoRequestDto(),
|
||||
OutputVideoCodec = "copy",
|
||||
IsVideoRequest = true,
|
||||
IsInputVideo = true
|
||||
};
|
||||
}
|
||||
|
||||
private static EncodingHelper CreateHelper(bool supportsRemoval)
|
||||
{
|
||||
var encoder = new Mock<IMediaEncoder>();
|
||||
encoder.Setup(x => x.SupportsBitStreamFilterWithOption(It.IsAny<BitStreamFilterOptionType>())).Returns(supportsRemoval);
|
||||
encoder.Setup(x => x.SupportsFilter("tonemapx")).Returns(true);
|
||||
encoder.SetupGet(x => x.EncoderVersion).Returns(new Version(8, 1));
|
||||
|
||||
return new EncodingHelper(
|
||||
Mock.Of<IApplicationPaths>(),
|
||||
encoder.Object,
|
||||
Mock.Of<ISubtitleEncoder>(),
|
||||
Mock.Of<IConfiguration>(),
|
||||
Mock.Of<IConfigurationManager>(),
|
||||
Mock.Of<IPathManager>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Model.Tests.Entities;
|
||||
|
||||
public class MediaStreamVideoRangeTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(7, 6, "smpte2084", false, VideoRangeType.DOVIWithEL)]
|
||||
[InlineData(7, 6, "smpte2084", true, VideoRangeType.DOVIWithELHDR10Plus)]
|
||||
[InlineData(8, 1, "smpte2084", false, VideoRangeType.DOVIWithHDR10)]
|
||||
[InlineData(8, 1, "smpte2084", true, VideoRangeType.DOVIWithHDR10Plus)]
|
||||
[InlineData(8, 4, "arib-std-b67", false, VideoRangeType.DOVIWithHLG)]
|
||||
[InlineData(10, 1, "smpte2084", false, VideoRangeType.DOVIWithHDR10)]
|
||||
[InlineData(10, 1, "smpte2084", true, VideoRangeType.DOVIWithHDR10Plus)]
|
||||
[InlineData(10, 4, "arib-std-b67", false, VideoRangeType.DOVIWithHLG)]
|
||||
[InlineData(8, 1, "SMPTE2084", false, VideoRangeType.DOVIWithHDR10)]
|
||||
[InlineData(8, 4, "ARIB-STD-B67", false, VideoRangeType.DOVIWithHLG)]
|
||||
public void GetVideoColorRange_ValidDovi_PreservesRangeType(
|
||||
int profile, int compatibilityId, string transfer, bool hdr10Plus, VideoRangeType expected)
|
||||
{
|
||||
var stream = CreateDovi(profile, compatibilityId, "BT2020NC", transfer, "BT2020", hdr10Plus);
|
||||
|
||||
Assert.Equal((VideoRange.HDR, expected), stream.GetVideoColorRange());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("bt709", "bt709", "bt709", VideoRange.SDR)]
|
||||
[InlineData("bt2020nc", "bt709", "bt2020", VideoRange.SDR)]
|
||||
[InlineData("bt2020nc", null, "bt2020", VideoRange.SDR)]
|
||||
[InlineData("bt2020nc", "", "bt2020", VideoRange.SDR)]
|
||||
[InlineData("bt2020nc", "unknown", "bt2020", VideoRange.SDR)]
|
||||
[InlineData("bt2020nc", "bt2020-10", "bt2020", VideoRange.SDR)]
|
||||
[InlineData(null, null, null, VideoRange.SDR)]
|
||||
[InlineData("bt709", "smpte2084", "bt2020", VideoRange.HDR)]
|
||||
[InlineData("bt2020nc", "smpte2084", "bt709", VideoRange.HDR)]
|
||||
[InlineData(null, "smpte2084", "bt2020", VideoRange.HDR)]
|
||||
[InlineData("bt2020nc", "smpte2084", null, VideoRange.HDR)]
|
||||
[InlineData("bt709", "arib-std-b67", "bt2020", VideoRange.HDR)]
|
||||
[InlineData("bt2020nc", "arib-std-b67", "bt709", VideoRange.HDR)]
|
||||
[InlineData(null, "arib-std-b67", "bt2020", VideoRange.HDR)]
|
||||
[InlineData("bt2020nc", "arib-std-b67", null, VideoRange.HDR)]
|
||||
public void GetVideoColorRange_InvalidDoviColors_UsesBaseLayerRange(
|
||||
string? space, string? transfer, string? primaries, VideoRange expected)
|
||||
{
|
||||
// Cover every HDR-compatible DV profile, including the HDR10+ variants.
|
||||
foreach (var (profile, compatibilityId) in new[] { (7, 6), (8, 1), (8, 4), (10, 1), (10, 4) })
|
||||
{
|
||||
foreach (var hdr10Plus in new[] { false, true })
|
||||
{
|
||||
var stream = CreateDovi(profile, compatibilityId, space, transfer, primaries, hdr10Plus);
|
||||
|
||||
Assert.Equal(expected, stream.VideoRange);
|
||||
Assert.Equal(VideoRangeType.DOVIInvalid, stream.VideoRangeType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(7, 6, "arib-std-b67")]
|
||||
[InlineData(8, 1, "arib-std-b67")]
|
||||
[InlineData(8, 4, "smpte2084")]
|
||||
[InlineData(10, 1, "arib-std-b67")]
|
||||
[InlineData(10, 4, "smpte2084")]
|
||||
public void GetVideoColorRange_WrongHdrTransfer_InvalidButStillHdr(int profile, int compatibilityId, string transfer)
|
||||
{
|
||||
var stream = CreateDovi(profile, compatibilityId, "bt2020nc", transfer, "bt2020", true);
|
||||
|
||||
Assert.Equal((VideoRange.HDR, VideoRangeType.DOVIInvalid), stream.GetVideoColorRange());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(5, 0, null, VideoRange.HDR, VideoRangeType.DOVI)]
|
||||
[InlineData(10, 0, null, VideoRange.HDR, VideoRangeType.DOVI)]
|
||||
[InlineData(8, 2, "bt709", VideoRange.SDR, VideoRangeType.DOVIWithSDR)]
|
||||
[InlineData(10, 2, "bt709", VideoRange.SDR, VideoRangeType.DOVIWithSDR)]
|
||||
public void GetVideoColorRange_OtherDoviProfiles_PreservesClassification(
|
||||
int profile, int compatibilityId, string? transfer, VideoRange range, VideoRangeType rangeType)
|
||||
{
|
||||
var stream = CreateDovi(profile, compatibilityId, "bt709", transfer, "bt709", false);
|
||||
|
||||
Assert.Equal((range, rangeType), stream.GetVideoColorRange());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(8, null, VideoRange.SDR)]
|
||||
[InlineData(8, "bt709", VideoRange.SDR)]
|
||||
[InlineData(8, "smpte2084", VideoRange.HDR)]
|
||||
[InlineData(10, null, VideoRange.SDR)]
|
||||
[InlineData(10, "arib-std-b67", VideoRange.HDR)]
|
||||
public void GetVideoColorRange_InvalidCompatibilityId_UsesBaseLayerRange(int profile, string? transfer, VideoRange expected)
|
||||
{
|
||||
var stream = CreateDovi(profile, 6, "bt2020nc", transfer, "bt2020", false);
|
||||
|
||||
Assert.Equal((expected, VideoRangeType.DOVIInvalid), stream.GetVideoColorRange());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("bt709", false, VideoRange.SDR, VideoRangeType.SDR)]
|
||||
[InlineData(null, false, VideoRange.SDR, VideoRangeType.SDR)]
|
||||
[InlineData("smpte2084", false, VideoRange.HDR, VideoRangeType.HDR10)]
|
||||
[InlineData("smpte2084", true, VideoRange.HDR, VideoRangeType.HDR10Plus)]
|
||||
[InlineData("arib-std-b67", false, VideoRange.HDR, VideoRangeType.HLG)]
|
||||
public void GetVideoColorRange_WithoutDovi_PreservesClassification(
|
||||
string? transfer, bool hdr10Plus, VideoRange range, VideoRangeType rangeType)
|
||||
{
|
||||
var stream = new MediaStream { Type = MediaStreamType.Video, ColorTransfer = transfer, Hdr10PlusPresentFlag = hdr10Plus };
|
||||
|
||||
Assert.Equal((range, rangeType), stream.GetVideoColorRange());
|
||||
stream.Type = MediaStreamType.Audio;
|
||||
Assert.Equal((VideoRange.Unknown, VideoRangeType.Unknown), stream.GetVideoColorRange());
|
||||
}
|
||||
|
||||
private static MediaStream CreateDovi(int profile, int compatibilityId, string? space, string? transfer, string? primaries, bool hdr10Plus)
|
||||
=> new()
|
||||
{
|
||||
Type = MediaStreamType.Video,
|
||||
DvProfile = profile,
|
||||
DvBlSignalCompatibilityId = compatibilityId,
|
||||
RpuPresentFlag = 1,
|
||||
BlPresentFlag = 1,
|
||||
ElPresentFlag = profile == 7 ? 1 : 0,
|
||||
ColorSpace = space,
|
||||
ColorTransfer = transfer,
|
||||
ColorPrimaries = primaries,
|
||||
Hdr10PlusPresentFlag = hdr10Plus
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user