From 1606ea19a8c272fa15ffd50d4a733f4418c1778f Mon Sep 17 00:00:00 2001 From: Matt Evans Date: Sun, 3 Feb 2019 16:52:37 +1100 Subject: [PATCH] New: Added support for DTS-HD MA and TrueHD Atmos in MediaInfo AudioCodec. --- .../Extensions/StringExtensions.cs | 10 ++++ .../FormatAudioCodecFixture.cs | 31 ++++++++---- .../MediaInfo/VideoFileInfoReaderFixture.cs | 6 +++ .../MediaInfo/MediaInfoFormatter.cs | 49 ++++++++++++++++--- .../MediaFiles/MediaInfo/MediaInfoModel.cs | 4 ++ .../MediaInfo/VideoFileInfoReader.cs | 12 +++-- 6 files changed, 92 insertions(+), 20 deletions(-) diff --git a/src/NzbDrone.Common/Extensions/StringExtensions.cs b/src/NzbDrone.Common/Extensions/StringExtensions.cs index ec899af34..ad5b14519 100644 --- a/src/NzbDrone.Common/Extensions/StringExtensions.cs +++ b/src/NzbDrone.Common/Extensions/StringExtensions.cs @@ -91,6 +91,11 @@ namespace NzbDrone.Common.Extensions return text.StartsWith(startsWith, StringComparison.InvariantCultureIgnoreCase); } + public static bool EndsWithIgnoreCase(this string text, string startsWith) + { + return text.EndsWith(startsWith, StringComparison.InvariantCultureIgnoreCase); + } + public static bool EqualsIgnoreCase(this string text, string equals) { return text.Equals(equals, StringComparison.InvariantCultureIgnoreCase); @@ -140,5 +145,10 @@ namespace NzbDrone.Common.Extensions { return CamelCaseRegex.Replace(input, match => " " + match.Value); } + + public static bool ContainsIgnoreCase(this IEnumerable source, string value) + { + return source.Contains(value, StringComparer.InvariantCultureIgnoreCase); + } } } diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs index e0bd875f5..15a9be09c 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs @@ -24,14 +24,26 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat); } - [TestCase("MPEG Audio, A_MPEG/L2, , ", "droned.s01e03.swedish.720p.hdtv.x264-prince", "MP2")] - [TestCase("Vorbis, A_VORBIS, , Xiph.Org libVorbis I 20101101 (Schaufenugget)", "DB Super HDTV", "Vorbis")] - [TestCase("PCM, 1, , ", "DW DVDRip XviD-idTV", "PCM")] // Dubbed most likely - [TestCase("TrueHD, A_TRUEHD, , ", "", "TrueHD")] - [TestCase("WMA, 161, , ", "Droned.wmv", "WMA")] - [TestCase("WMA, 162, Pro, ", "B.N.S04E18.720p.WEB-DL", "WMA")] - [TestCase("Opus, A_OPUS, , ", "Roadkill Ep3x11 - YouTube.webm", "Opus")] - [TestCase("mp3 , 0, , ", "climbing.mp4", "MP3")] + [TestCase("MPEG Audio, A_MPEG/L2, , , ", "droned.s01e03.swedish.720p.hdtv.x264-prince", "MP2")] + [TestCase("Vorbis, A_VORBIS, , Xiph.Org libVorbis I 20101101 (Schaufenugget), ", "DB Super HDTV", "Vorbis")] + [TestCase("PCM, 1, , , ", "DW DVDRip XviD-idTV, ", "PCM")] // Dubbed most likely + [TestCase("TrueHD, A_TRUEHD, , , ", "", "TrueHD")] + [TestCase("MLP FBA, A_TRUEHD, , , ", "TrueHD", "TrueHD")] + [TestCase("MLP FBA, A_TRUEHD, , , 16-ch", "Atmos", "TrueHD Atmos")] + [TestCase("WMA, 161, , , ", "Droned.wmv", "WMA")] + [TestCase("WMA, 162, Pro, , ", "B.N.S04E18.720p.WEB-DL", "WMA")] + [TestCase("Opus, A_OPUS, , , ", "Roadkill Ep3x11 - YouTube.webm", "Opus")] + [TestCase("mp3 , 0, , , ", "climbing.mp4", "MP3")] + [TestCase("DTS, A_DTS, , , XLL", "DTS-HD.MA", "DTS-HD MA")] + [TestCase("DTS, A_DTS, , , XLL X", "DTS-X", "DTS-X")] + [TestCase("DTS, A_DTS, , , ES XLL", "DTS-HD.MA", "DTS-HD MA")] + [TestCase("DTS, A_DTS, , , ES", "DTS-ES", "DTS-ES")] + [TestCase("DTS, A_DTS, , , ES XXCH", "DTS", "DTS-ES")] + [TestCase("DTS, A_DTS, , , XBR", "DTSHD-HRA", "DTS-HD HRA")] + [TestCase("DTS, A_DTS, , , DTS", "DTS", "DTS")] + [TestCase("E-AC-3, A_EAC3, , , JOC", "EAC3", "EAC3")] + [TestCase("E-AC-3, A_EAC3, , , ", "DD5.1", "EAC3")] + [TestCase("AC-3, A_AC3, , , ", "DD5.1", "AC3")] public void should_format_audio_format(string audioFormatPack, string sceneName, string expectedFormat) { var split = audioFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None); @@ -40,7 +52,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests AudioFormat = split[0], AudioCodecID = split[1], AudioProfile = split[2], - AudioCodecLibrary = split[3] + AudioCodecLibrary = split[3], + AudioAdditionalFeatures = split[4] }; MediaInfoFormatter.FormatAudioCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat); diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs index febeaf416..262be6d17 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs @@ -59,6 +59,9 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo info.VideoBitrate.Should().Be(193329); info.VideoFps.Should().Be(24); info.Width.Should().Be(480); + info.VideoColourPrimaries.Should().Be("BT.601 NTSC"); + info.VideoTransferCharacteristics.Should().Be("BT.709"); + info.AudioAdditionalFeatures.Should().Be(""); } @@ -95,6 +98,9 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo info.VideoBitrate.Should().Be(193329); info.VideoFps.Should().Be(24); info.Width.Should().Be(480); + info.VideoColourPrimaries.Should().Be("BT.601 NTSC"); + info.VideoTransferCharacteristics.Should().Be("BT.709"); + info.AudioAdditionalFeatures.Should().Be(""); } [Test] diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs index 4abc772c8..f3ad44e87 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs @@ -43,6 +43,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo var audioCodecID = mediaInfo.AudioCodecID ?? string.Empty; var audioProfile = mediaInfo.AudioProfile ?? string.Empty; var audioCodecLibrary = mediaInfo.AudioCodecLibrary ?? string.Empty; + var splitAdditionalFeatures = (mediaInfo.AudioAdditionalFeatures ?? string.Empty).Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries); if (audioFormat.IsNullOrWhiteSpace()) { @@ -71,6 +72,25 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo if (audioFormat.EqualsIgnoreCase("DTS")) { + if (splitAdditionalFeatures.ContainsIgnoreCase("XLL")) + { + if (splitAdditionalFeatures.ContainsIgnoreCase("X")) + { + return "DTS-X"; + } + return "DTS-HD MA"; + } + + if (splitAdditionalFeatures.ContainsIgnoreCase("ES")) + { + return "DTS-ES"; + } + + if (splitAdditionalFeatures.ContainsIgnoreCase("XBR")) + { + return "DTS-HD HRA"; + } + return "DTS"; } @@ -112,6 +132,16 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo return "TrueHD"; } + if (audioFormat.EqualsIgnoreCase("MLP FBA")) + { + if (splitAdditionalFeatures.ContainsIgnoreCase("16-ch")) + { + return "TrueHD Atmos"; + } + + return "TrueHD"; + } + if (audioFormat.EqualsIgnoreCase("Vorbis")) { return "Vorbis"; @@ -373,13 +403,16 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo .Sum(s => decimal.Parse(s.Trim(), CultureInfo.InvariantCulture)); } - - return Regex.Replace(audioChannelPositions, @"^\d+\sobjects", "", RegexOptions.Compiled | RegexOptions.IgnoreCase) - .Replace("Object Based / ", "") - .Split(new string[] { " / " }, StringSplitOptions.RemoveEmptyEntries) - .FirstOrDefault() - ?.Split('/') - .Sum(s => decimal.Parse(s, CultureInfo.InvariantCulture)); + if (audioChannelPositions.Contains("/")) + { + return Regex.Replace(audioChannelPositions, @"^\d+\sobjects", "", + RegexOptions.Compiled | RegexOptions.IgnoreCase) + .Replace("Object Based / ", "") + .Split(new string[] {" / "}, StringSplitOptions.RemoveEmptyEntries) + .FirstOrDefault() + ?.Split('/') + .Sum(s => decimal.Parse(s, CultureInfo.InvariantCulture)); + } } catch (Exception e) { @@ -401,7 +434,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo try { - return mediaInfo.AudioChannelPositionsText.ContainsIgnoreCase("LFE") ? audioChannels - 1 + 0.1m : audioChannels; + return audioChannelPositionsText.ContainsIgnoreCase("LFE") ? audioChannels - 1 + 0.1m : audioChannels; } catch (Exception e) { diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs index 03f4ef3c5..3024f6c6d 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs @@ -18,11 +18,15 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo public string VideoCodecLibrary { get; set; } public int VideoBitrate { get; set; } public int VideoBitDepth { get; set; } + public int VideoMultiViewCount { get; set; } + public string VideoColourPrimaries { get; set; } + public string VideoTransferCharacteristics { get; set; } public int Width { get; set; } public int Height { get; set; } public string AudioFormat { get; set; } public string AudioCodecID { get; set; } public string AudioCodecLibrary { get; set; } + public string AudioAdditionalFeatures { get; set; } public int AudioBitrate { get; set; } public TimeSpan RunTime { get; set; } public int AudioStreamCount { get; set; } diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs index 98e32b1f0..9841d65ce 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo private readonly Logger _logger; public const int MINIMUM_MEDIA_INFO_SCHEMA_REVISION = 3; - public const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 4; + public const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 5; public VideoFileInfoReader(IDiskProvider diskProvider, Logger logger) { @@ -94,18 +94,20 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo int audioChannels; int videoBitDepth; decimal videoFrameRate; + int videoMultiViewCount; string subtitles = mediaInfo.Get(StreamKind.General, 0, "Text_Language_List"); string scanType = mediaInfo.Get(StreamKind.Video, 0, "ScanType"); int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "Width"), out width); int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "Height"), out height); - int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitRate_Nominal"), out videoBitRate); + int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitRate"), out videoBitRate); if (videoBitRate <= 0) { - int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitRate"), out videoBitRate); + int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitRate_Nominal"), out videoBitRate); } decimal.TryParse(mediaInfo.Get(StreamKind.Video, 0, "FrameRate"), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out videoFrameRate); int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitDepth"), out videoBitDepth); + int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "MultiView_Count"), out videoMultiViewCount); //Runtime int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "PlayTime"), out videoRuntime); @@ -138,12 +140,16 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo VideoCodecLibrary = mediaInfo.Get(StreamKind.Video, 0, "Encoded_Library"), VideoBitrate = videoBitRate, VideoBitDepth = videoBitDepth, + VideoMultiViewCount = videoMultiViewCount, + VideoColourPrimaries = mediaInfo.Get(StreamKind.Video, 0, "colour_primaries"), + VideoTransferCharacteristics = mediaInfo.Get(StreamKind.Video, 0, "transfer_characteristics"), Height = height, Width = width, AudioFormat = mediaInfo.Get(StreamKind.Audio, 0, "Format"), AudioCodecID = mediaInfo.Get(StreamKind.Audio, 0, "CodecID"), AudioProfile = audioProfile, AudioCodecLibrary = mediaInfo.Get(StreamKind.Audio, 0, "Encoded_Library"), + AudioAdditionalFeatures = mediaInfo.Get(StreamKind.Audio, 0, "Format_AdditionalFeatures"), AudioBitrate = audioBitRate, RunTime = GetBestRuntime(audioRuntime, videoRuntime, generalRuntime), AudioStreamCount = streamCount,