From 13e44ce19a75dad873866e460548616aee837130 Mon Sep 17 00:00:00 2001 From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> Date: Thu, 23 Dec 2021 23:12:56 -0600 Subject: [PATCH] New: Add {MediaInfo VideoDynamicRangeType} token for renaming New: Detect HDR Type New: Display HDR Type in File Media Info Modal Based on Sonarr 7b694ea71d7f78bad5c03393c4cf6f7a28ada1cb Closes #6789 Fixes #4844 Co-authored-by: ta264 Co-authored-by: Qstick --- .../MediaManagement/Naming/NamingModal.js | 3 +- .../FormatVideoDynamicRangeTypeFixture.cs | 31 ++++++++++++++ .../MediaInfo/VideoFileInfoReaderFixture.cs | 40 +++++++++++++------ .../MediaFiles/MediaInfo/HdrFormat.cs | 5 ++- .../MediaInfo/MediaInfoFormatter.cs | 25 ++++++++++++ .../MediaInfo/VideoFileInfoReader.cs | 25 ++++++++---- .../Organizer/FileNameBuilder.cs | 6 ++- .../MovieFiles/MediaInfoResource.cs | 2 + 8 files changed, 113 insertions(+), 24 deletions(-) create mode 100644 src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoDynamicRangeTypeFixture.cs diff --git a/frontend/src/Settings/MediaManagement/Naming/NamingModal.js b/frontend/src/Settings/MediaManagement/Naming/NamingModal.js index 6586f3bf7..4725352f8 100644 --- a/frontend/src/Settings/MediaManagement/Naming/NamingModal.js +++ b/frontend/src/Settings/MediaManagement/Naming/NamingModal.js @@ -147,7 +147,8 @@ class NamingModal extends Component { { token: '{MediaInfo VideoCodec}', example: 'x264' }, { token: '{MediaInfo VideoBitDepth}', example: '10' }, - { token: '{MediaInfo VideoDynamicRange}', example: 'HDR' } + { token: '{MediaInfo VideoDynamicRange}', example: 'HDR' }, + { token: '{MediaInfo VideoDynamicRangeType}', example: 'DV HDR10' } ]; const releaseGroupTokens = [ diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoDynamicRangeTypeFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoDynamicRangeTypeFixture.cs new file mode 100644 index 000000000..3c37d0f34 --- /dev/null +++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoDynamicRangeTypeFixture.cs @@ -0,0 +1,31 @@ +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.MediaFiles.MediaInfo; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests +{ + [TestFixture] + public class FormatVideoDynamicRangeTypeFixture : TestBase + { + [TestCase(HdrFormat.None, "")] + [TestCase(HdrFormat.Hlg10, "HLG")] + [TestCase(HdrFormat.Pq10, "PQ")] + [TestCase(HdrFormat.Hdr10, "HDR10")] + [TestCase(HdrFormat.Hdr10Plus, "HDR10Plus")] + [TestCase(HdrFormat.DolbyVision, "DV")] + [TestCase(HdrFormat.DolbyVisionHdr10, "DV HDR10")] + [TestCase(HdrFormat.DolbyVisionHlg, "DV HLG")] + [TestCase(HdrFormat.DolbyVisionSdr, "DV SDR")] + public void should_format_video_dynamic_range_type(HdrFormat format, string expectedVideoDynamicRangeType) + { + var mediaInfo = new MediaInfoModel + { + VideoHdrFormat = format, + SchemaRevision = 9 + }; + + MediaInfoFormatter.FormatVideoDynamicRangeType(mediaInfo).Should().Be(expectedVideoDynamicRangeType); + } + } +} diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs index 3fb1e253e..f0826d64b 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs @@ -102,24 +102,38 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo info.VideoTransferCharacteristics.Should().Be("bt709"); } - [TestCase(8, "", "", "", HdrFormat.None)] - [TestCase(10, "", "", "", HdrFormat.None)] - [TestCase(10, "bt709", "bt709", "", HdrFormat.None)] - [TestCase(8, "bt2020", "smpte2084", "", HdrFormat.None)] - [TestCase(10, "bt2020", "bt2020-10", "", HdrFormat.Hlg10)] - [TestCase(10, "bt2020", "arib-std-b67", "", HdrFormat.Hlg10)] - [TestCase(10, "bt2020", "smpte2084", "", HdrFormat.Pq10)] - [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.SideData", HdrFormat.Pq10)] - [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.MasteringDisplayMetadata", HdrFormat.Hdr10)] - [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.ContentLightLevelMetadata", HdrFormat.Hdr10)] - [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.HdrDynamicMetadataSpmte2094", HdrFormat.Hdr10Plus)] - [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", HdrFormat.DolbyVision)] - public void should_detect_hdr_correctly(int bitDepth, string colourPrimaries, string transferFunction, string sideDataTypes, HdrFormat expected) + [TestCase(8, "", "", "", null, HdrFormat.None)] + [TestCase(10, "", "", "", null, HdrFormat.None)] + [TestCase(10, "bt709", "bt709", "", null, HdrFormat.None)] + [TestCase(8, "bt2020", "smpte2084", "", null, HdrFormat.None)] + [TestCase(10, "bt2020", "bt2020-10", "", null, HdrFormat.Hlg10)] + [TestCase(10, "bt2020", "arib-std-b67", "", null, HdrFormat.Hlg10)] + [TestCase(10, "bt2020", "smpte2084", "", null, HdrFormat.Pq10)] + [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.SideData", null, HdrFormat.Pq10)] + [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.MasteringDisplayMetadata", null, HdrFormat.Hdr10)] + [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.ContentLightLevelMetadata", null, HdrFormat.Hdr10)] + [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.HdrDynamicMetadataSpmte2094", null, HdrFormat.Hdr10Plus)] + [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", null, HdrFormat.DolbyVision)] + [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 1, HdrFormat.DolbyVisionHdr10)] + [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 2, HdrFormat.DolbyVisionSdr)] + [TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 4, HdrFormat.DolbyVisionHlg)] + public void should_detect_hdr_correctly(int bitDepth, string colourPrimaries, string transferFunction, string sideDataTypes, int? doviConfigId, HdrFormat expected) { var assembly = Assembly.GetAssembly(typeof(FFProbe)); var types = sideDataTypes.Split(",").Select(x => x.Trim()).ToList(); var sideData = types.Where(x => x.IsNotNullOrWhiteSpace()).Select(x => assembly.CreateInstance(x)).Cast().ToList(); + if (doviConfigId.HasValue) + { + sideData.ForEach(x => + { + if (x.GetType().Name == "DoviConfigurationRecordSideData") + { + ((DoviConfigurationRecordSideData)x).DvBlSignalCompatibilityId = doviConfigId.Value; + } + }); + } + var result = VideoFileInfoReader.GetHdrFormat(bitDepth, colourPrimaries, transferFunction, sideData); result.Should().Be(expected); diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/HdrFormat.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/HdrFormat.cs index de014cd6d..db109df81 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/HdrFormat.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/HdrFormat.cs @@ -7,6 +7,9 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo Hdr10, Hdr10Plus, Hlg10, - DolbyVision + DolbyVision, + DolbyVisionHdr10, + DolbyVisionSdr, + DolbyVisionHlg } } diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs index 68e68cc5b..d57f748bc 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs @@ -303,5 +303,30 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo { return mediaInfo.VideoHdrFormat != HdrFormat.None ? VideoDynamicRangeHdr : ""; } + + public static string FormatVideoDynamicRangeType(MediaInfoModel mediaInfo) + { + switch (mediaInfo.VideoHdrFormat) + { + case HdrFormat.DolbyVision: + return "DV"; + case HdrFormat.DolbyVisionHdr10: + return "DV HDR10"; + case HdrFormat.DolbyVisionHlg: + return "DV HLG"; + case HdrFormat.DolbyVisionSdr: + return "DV SDR"; + case HdrFormat.Hdr10: + return "HDR10"; + case HdrFormat.Hdr10Plus: + return "HDR10Plus"; + case HdrFormat.Hlg10: + return "HLG"; + case HdrFormat.Pq10: + return "PQ"; + } + + return ""; + } } } diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs index 113775a16..38c6b037e 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs @@ -22,7 +22,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo private readonly List _pixelFormats; public const int MINIMUM_MEDIA_INFO_SCHEMA_REVISION = 8; - public const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 8; + public const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 9; private static readonly string[] ValidHdrColourPrimaries = { "bt2020" }; private static readonly string[] HlgTransferFunctions = { "bt2020-10", "arib-std-b67" }; @@ -162,9 +162,15 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo return HdrFormat.None; } - if (TryFindSideData(sideData, nameof(DoviConfigurationRecordSideData))) + if (TryGetSideData(sideData, out var dovi)) { - return HdrFormat.DolbyVision; + return dovi.DvBlSignalCompatibilityId switch + { + 1 => HdrFormat.DolbyVisionHdr10, + 2 => HdrFormat.DolbyVisionSdr, + 4 => HdrFormat.DolbyVisionHlg, + _ => HdrFormat.DolbyVision + }; } if (!ValidHdrColourPrimaries.Contains(colorPrimaries) || !ValidHdrTransferFunctions.Contains(transferFunction)) @@ -179,13 +185,13 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo if (PqTransferFunctions.Contains(transferFunction)) { - if (TryFindSideData(sideData, nameof(HdrDynamicMetadataSpmte2094))) + if (TryGetSideData(sideData, out _)) { return HdrFormat.Hdr10Plus; } - if (TryFindSideData(sideData, nameof(MasteringDisplayMetadata)) || - TryFindSideData(sideData, nameof(ContentLightLevelMetadata))) + if (TryGetSideData(sideData, out _) || + TryGetSideData(sideData, out _)) { return HdrFormat.Hdr10; } @@ -196,9 +202,12 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo return HdrFormat.None; } - private static bool TryFindSideData(List list, string typeName) + private static bool TryGetSideData(List list, out T result) + where T : SideData { - return list?.Find(x => x.GetType().Name == typeName) != null; + result = (T)list?.FirstOrDefault(x => x.GetType().Name == typeof(T).Name); + + return result != null; } } } diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 48d649be8..5d67b4849 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -30,6 +30,7 @@ namespace NzbDrone.Core.Organizer public class FileNameBuilder : IBuildFileNames { private const string MediaInfoVideoDynamicRangeToken = "{MediaInfo VideoDynamicRange}"; + private const string MediaInfoVideoDynamicRangeTypeToken = "{MediaInfo VideoDynamicRangeType}"; private readonly INamingConfigService _namingConfigService; private readonly IQualityDefinitionService _qualityDefinitionService; @@ -353,7 +354,8 @@ namespace NzbDrone.Core.Organizer private static readonly IReadOnlyDictionary MinimumMediaInfoSchemaRevisions = new Dictionary(FileNameBuilderTokenEqualityComparer.Instance) { - { MediaInfoVideoDynamicRangeToken, 5 } + { MediaInfoVideoDynamicRangeToken, 5 }, + { MediaInfoVideoDynamicRangeTypeToken, 9 } }; private void AddMediaInfoTokens(Dictionary> tokenHandlers, MovieFile movieFile) @@ -418,6 +420,8 @@ namespace NzbDrone.Core.Organizer tokenHandlers[MediaInfoVideoDynamicRangeToken] = m => MediaInfoFormatter.FormatVideoDynamicRange(movieFile.MediaInfo); + tokenHandlers[MediaInfoVideoDynamicRangeTypeToken] = + m => MediaInfoFormatter.FormatVideoDynamicRangeType(movieFile.MediaInfo); } private void AddCustomFormats(Dictionary> tokenHandlers, Movie movie, MovieFile movieFile, List customFormats = null) diff --git a/src/Radarr.Api.V3/MovieFiles/MediaInfoResource.cs b/src/Radarr.Api.V3/MovieFiles/MediaInfoResource.cs index 0dc5f6272..42921149b 100644 --- a/src/Radarr.Api.V3/MovieFiles/MediaInfoResource.cs +++ b/src/Radarr.Api.V3/MovieFiles/MediaInfoResource.cs @@ -15,6 +15,7 @@ namespace Radarr.Api.V3.MovieFiles public int VideoBitDepth { get; set; } public int VideoBitrate { get; set; } public string VideoCodec { get; set; } + public string VideoDynamicRangeType { get; set; } public decimal VideoFps { get; set; } public string Resolution { get; set; } public string RunTime { get; set; } @@ -41,6 +42,7 @@ namespace Radarr.Api.V3.MovieFiles VideoBitDepth = model.VideoBitDepth, VideoBitrate = model.VideoBitrate, VideoCodec = MediaInfoFormatter.FormatVideoCodec(model, sceneName), + VideoDynamicRangeType = MediaInfoFormatter.FormatVideoDynamicRangeType(model), VideoFps = Math.Round(model.VideoFps, 3), Resolution = $"{model.Width}x{model.Height}", RunTime = FormatRuntime(model.RunTime),