Merge pull request #17806 from jellyfin/fix/dovi-color-validation

Enforce dolby vision transfer check
This commit is contained in:
Cody Robibero
2026-09-06 07:35:32 -04:00
committed by GitHub
6 changed files with 327 additions and 12 deletions
@@ -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
};
}