From 582402d45e99b521dea55a1e4d41e9bc42510f10 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sat, 27 Jul 2019 00:33:04 -0400 Subject: [PATCH] Fixed: MediaInfo Improvements, Tests --- .../FormatAudioCodecFixture.cs | 31 +++- .../FormatVideoCodecFixture.cs | 15 ++ .../FormatVideoDynamicRangeFixture.cs | 30 ++++ .../UpdateMediaInfoServiceFixture.cs | 127 +++++++++++++-- .../MediaInfo/VideoFileInfoReaderFixture.cs | 19 ++- .../NzbDrone.Core.Test.csproj | 1 + .../FileNameBuilderFixture.cs | 154 +++++++++++++++--- .../MediaFiles/MediaFileService.cs | 5 +- .../MediaInfo/UpdateMediaInfoService.cs | 70 +++++--- .../Organizer/FileNameBuilder.cs | 85 ++++++---- 10 files changed, 426 insertions(+), 111 deletions(-) create mode 100644 src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoDynamicRangeFixture.cs diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs index cfdbb02da..a0e13b2bb 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/MediaInfoFormatterTests/FormatVideoCodecFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoCodecFixture.cs index fa9d6b8a7..f6f0626d2 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoCodecFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoCodecFixture.cs @@ -78,6 +78,21 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat); } + [TestCase("MPEG-4 Visual, 20, , Intel(R) MPEG-4 encoder based on Intel(R) IPP 6.1 build 137.20[6.1.137.763]", "", "")] + public void should_warn_on_unknown_video_format(string videoFormatPack, string sceneName, string expectedFormat) + { + var split = videoFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None); + var mediaInfoModel = new MediaInfoModel + { + VideoFormat = split[0], + VideoCodecID = split[1], + VideoProfile = split[2], + VideoCodecLibrary = split[3] + }; + + MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat); + } + [Test] public void should_return_VideoFormat_by_default() { diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoDynamicRangeFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoDynamicRangeFixture.cs new file mode 100644 index 000000000..15894449f --- /dev/null +++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatVideoDynamicRangeFixture.cs @@ -0,0 +1,30 @@ +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.MediaFiles.MediaInfo; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests +{ + [TestFixture] + public class FormatVideoDynamicRangeFixture : TestBase + { + [TestCase(8, "BT.601 NTSC", "BT.709", "")] + [TestCase(10, "BT.2020", "PQ", "HDR")] + [TestCase(8, "BT.2020", "PQ", "")] + [TestCase(10, "BT.601 NTSC", "PQ", "")] + [TestCase(10, "BT.2020", "BT.709", "")] + [TestCase(10, "BT.2020", "HLG", "HDR")] + public void should_format_video_dynamic_range(int bitDepth, string colourPrimaries, string transferCharacteristics, string expectedVideoDynamicRange) + { + var mediaInfo = new MediaInfoModel + { + VideoBitDepth = bitDepth, + VideoColourPrimaries = colourPrimaries, + VideoTransferCharacteristics = transferCharacteristics, + SchemaRevision = 5 + }; + + MediaInfoFormatter.FormatVideoDynamicRange(mediaInfo).Should().Be(expectedVideoDynamicRange); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs index c41d09ba4..27333210f 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs @@ -1,5 +1,6 @@ using System.IO; using FizzWare.NBuilder; +using FluentAssertions; using Moq; using NUnit.Framework; using NzbDrone.Common.Disk; @@ -56,7 +57,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo [Test] public void should_skip_up_to_date_media_info() { - var episodeFiles = Builder.CreateListOfSize(3) + var movieFiles = Builder.CreateListOfSize(3) .All() .With(v => v.RelativePath = "media.mkv") .TheFirst(1) @@ -65,7 +66,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo Mocker.GetMock() .Setup(v => v.GetFilesByMovie(1)) - .Returns(episodeFiles); + .Returns(movieFiles); GivenFileExists(); GivenSuccessfulScan(); @@ -82,7 +83,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo [Test] public void should_skip_not_yet_date_media_info() { - var episodeFiles = Builder.CreateListOfSize(3) + var movieFiles = Builder.CreateListOfSize(3) .All() .With(v => v.RelativePath = "media.mkv") .TheFirst(1) @@ -91,7 +92,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo Mocker.GetMock() .Setup(v => v.GetFilesByMovie(1)) - .Returns(episodeFiles); + .Returns(movieFiles); GivenFileExists(); GivenSuccessfulScan(); @@ -108,7 +109,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo [Test] public void should_update_outdated_media_info() { - var episodeFiles = Builder.CreateListOfSize(3) + var movieFiles = Builder.CreateListOfSize(3) .All() .With(v => v.RelativePath = "media.mkv") .TheFirst(1) @@ -117,7 +118,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo Mocker.GetMock() .Setup(v => v.GetFilesByMovie(1)) - .Returns(episodeFiles); + .Returns(movieFiles); GivenFileExists(); GivenSuccessfulScan(); @@ -134,14 +135,14 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo [Test] public void should_ignore_missing_files() { - var episodeFiles = Builder.CreateListOfSize(2) + var movieFiles = Builder.CreateListOfSize(2) .All() .With(v => v.RelativePath = "media.mkv") .BuildList(); Mocker.GetMock() .Setup(v => v.GetFilesByMovie(1)) - .Returns(episodeFiles); + .Returns(movieFiles); GivenSuccessfulScan(); @@ -157,7 +158,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo [Test] public void should_continue_after_failure() { - var episodeFiles = Builder.CreateListOfSize(2) + var movieFiles = Builder.CreateListOfSize(2) .All() .With(v => v.RelativePath = "media.mkv") .TheFirst(1) @@ -166,7 +167,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo Mocker.GetMock() .Setup(v => v.GetFilesByMovie(1)) - .Returns(episodeFiles); + .Returns(movieFiles); GivenFileExists(); GivenSuccessfulScan(); @@ -180,5 +181,111 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo Mocker.GetMock() .Verify(v => v.Update(It.IsAny()), Times.Exactly(1)); } + + [Test] + public void should_not_update_files_if_media_info_disabled() + { + var movieFiles = Builder.CreateListOfSize(2) + .All() + .With(v => v.RelativePath = "media.mkv") + .TheFirst(1) + .With(v => v.RelativePath = "media2.mkv") + .BuildList(); + + Mocker.GetMock() + .Setup(v => v.GetFilesByMovie(1)) + .Returns(movieFiles); + + Mocker.GetMock() + .SetupGet(s => s.EnableMediaInfo) + .Returns(false); + + GivenFileExists(); + GivenSuccessfulScan(); + + Subject.Handle(new MovieScannedEvent(_movie)); + + Mocker.GetMock() + .Verify(v => v.GetMediaInfo(It.IsAny()), Times.Never()); + + Mocker.GetMock() + .Verify(v => v.Update(It.IsAny()), Times.Never()); + } + + [Test] + public void should_not_update_if_media_info_disabled() + { + var movieFile = Builder.CreateNew() + .With(v => v.RelativePath = "media.mkv") + .Build(); + + Mocker.GetMock() + .SetupGet(s => s.EnableMediaInfo) + .Returns(false); + + GivenFileExists(); + GivenSuccessfulScan(); + + Subject.Update(movieFile, _movie); + + Mocker.GetMock() + .Verify(v => v.GetMediaInfo(It.IsAny()), Times.Never()); + + Mocker.GetMock() + .Verify(v => v.Update(It.IsAny()), Times.Never()); + } + + [Test] + public void should_update_media_info() + { + var movieFile = Builder.CreateNew() + .With(v => v.RelativePath = "media.mkv") + .With(e => e.MediaInfo = new MediaInfoModel{SchemaRevision = 3}) + .Build(); + + GivenFileExists(); + GivenSuccessfulScan(); + + Subject.Update(movieFile, _movie); + + Mocker.GetMock() + .Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Once()); + + Mocker.GetMock() + .Verify(v => v.Update(movieFile), Times.Once()); + } + + [Test] + public void should_not_update_media_info_if_new_info_is_null() + { + var movieFile = Builder.CreateNew() + .With(v => v.RelativePath = "media.mkv") + .With(e => e.MediaInfo = new MediaInfoModel{SchemaRevision = 3}) + .Build(); + + GivenFileExists(); + GivenFailedScan(Path.Combine(_movie.Path, "media.mkv")); + + Subject.Update(movieFile, _movie); + + movieFile.MediaInfo.Should().NotBeNull(); + } + + [Test] + public void should_not_save_movie_file_if_new_info_is_null() + { + var movieFile = Builder.CreateNew() + .With(v => v.RelativePath = "media.mkv") + .With(e => e.MediaInfo = new MediaInfoModel{SchemaRevision = 3}) + .Build(); + + GivenFileExists(); + GivenFailedScan(Path.Combine(_movie.Path, "media.mkv")); + + Subject.Update(movieFile, _movie); + + Mocker.GetMock() + .Verify(v => v.Update(movieFile), Times.Never()); + } } } diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs index 10afb30cc..c4642733e 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/VideoFileInfoReaderFixture.cs @@ -40,20 +40,18 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo var info = Subject.GetMediaInfo(path); + info.VideoCodec.Should().BeNull(); info.VideoFormat.Should().Be("AVC"); info.VideoCodecID.Should().Be("avc1"); info.VideoProfile.Should().Be("Baseline@L2.1"); info.VideoCodecLibrary.Should().Be(""); - info.VideoMultiViewCount.Should().Be(0); - info.VideoColourPrimaries.Should().Be("BT.601 NTSC"); - info.VideoTransferCharacteristics.Should().Be("BT.709"); info.AudioFormat.Should().Be("AAC"); info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2"); + info.AudioProfile.Should().BeOneOf("", "LC"); info.AudioCodecLibrary.Should().Be(""); info.AudioBitrate.Should().Be(128000); info.AudioChannels.Should().Be(2); info.AudioLanguages.Should().Be("English"); - info.AudioAdditionalFeatures.Should().BeOneOf("", "LC"); info.Height.Should().Be(320); info.RunTime.Seconds.Should().Be(10); info.ScanType.Should().Be("Progressive"); @@ -61,6 +59,10 @@ 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().BeOneOf("", "LC"); + } [Test] @@ -77,20 +79,18 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo var info = Subject.GetMediaInfo(path); + info.VideoCodec.Should().BeNull(); info.VideoFormat.Should().Be("AVC"); info.VideoCodecID.Should().Be("avc1"); info.VideoProfile.Should().Be("Baseline@L2.1"); info.VideoCodecLibrary.Should().Be(""); - info.VideoMultiViewCount.Should().Be(0); - info.VideoColourPrimaries.Should().Be("BT.601 NTSC"); - info.VideoTransferCharacteristics.Should().Be("BT.709"); info.AudioFormat.Should().Be("AAC"); info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2"); + info.AudioProfile.Should().BeOneOf("", "LC"); info.AudioCodecLibrary.Should().Be(""); info.AudioBitrate.Should().Be(128000); info.AudioChannels.Should().Be(2); info.AudioLanguages.Should().Be("English"); - info.AudioAdditionalFeatures.Should().BeOneOf("", "LC"); info.Height.Should().Be(320); info.RunTime.Seconds.Should().Be(10); info.ScanType.Should().Be("Progressive"); @@ -98,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().BeOneOf("", "LC"); } [Test] diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 8afa60970..0e55c72a4 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -292,6 +292,7 @@ + diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs index f95e3171f..d473c9eaa 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs @@ -9,6 +9,8 @@ using NzbDrone.Core.Organizer; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Movies; +using NzbDrone.Core.MediaFiles.MediaInfo; +using Moq; namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests { @@ -305,26 +307,6 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests .Should().Be("South.Park.3D.h264.DTS"); } - [Test] - public void should_format_mediainfo_hdr_properly() - { - _namingConfig.StandardMovieFormat = "{Movie.Title}.{MEDIAINFO.HDR}.{MediaInfo.Simple}"; - - _movieFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel() - { - VideoFormat = "AVC", - VideoBitDepth = 10, - VideoColourPrimaries = "BT.2020", - VideoTransferCharacteristics = "PQ", - AudioFormat = "DTS", - AudioLanguages = "English", - Subtitles = "English/Spanish/Italian" - }; - - Subject.BuildFileName(_movie, _movieFile) - .Should().Be("South.Park.HDR.h264.DTS"); - } - [Test] public void should_remove_duplicate_non_word_characters() { @@ -506,6 +488,138 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests .Should().Be(releaseGroup); } + [TestCase("English", "")] + [TestCase("English/German", "[EN+DE]")] + public void should_format_audio_languages(string audioLanguages, string expected) + { + _movieFile.ReleaseGroup = null; + + GivenMediaInfoModel(audioLanguages: audioLanguages); + + + _namingConfig.StandardMovieFormat = "{MediaInfo AudioLanguages}"; + + + Subject.BuildFileName( _movie , _movieFile) + .Should().Be(expected); + } + + [TestCase("English", "[EN]")] + [TestCase("English/German", "[EN+DE]")] + public void should_format_audio_languages_all(string audioLanguages, string expected) + { + _movieFile.ReleaseGroup = null; + + GivenMediaInfoModel(audioLanguages: audioLanguages); + + + _namingConfig.StandardMovieFormat = "{MediaInfo AudioLanguagesAll}"; + + + Subject.BuildFileName( _movie , _movieFile) + .Should().Be(expected); + } + + [TestCase(8, "BT.601 NTSC", "BT.709", "South.Park")] + [TestCase(10, "BT.2020", "PQ", "South.Park.HDR")] + [TestCase(10, "BT.2020", "HLG", "South.Park.HDR")] + [TestCase(0, null, null, "South.Park")] + public void should_include_hdr_for_mediainfo_videodynamicrange_with_valid_properties(int bitDepth, string colourPrimaries, + string transferCharacteristics, string expectedName) + { + _namingConfig.StandardMovieFormat = + "{Movie.Title}.{MediaInfo VideoDynamicRange}"; + + GivenMediaInfoModel(videoBitDepth: bitDepth, videoColourPrimaries: colourPrimaries, videoTransferCharacteristics: transferCharacteristics); + + Subject.BuildFileName(_movie, _movieFile) + .Should().Be(expectedName); + } + + [Test] + public void should_update_media_info_if_token_configured_and_revision_is_old() + { + _namingConfig.StandardMovieFormat = + "{Movie.Title}.{MediaInfo VideoDynamicRange}"; + + GivenMediaInfoModel(schemaRevision: 3); + + Subject.BuildFileName( _movie, _movieFile); + + Mocker.GetMock().Verify(v => v.Update(_movieFile, _movie), Times.Once()); + } + + [Test] + public void should_not_update_media_info_if_no_movie_path_available() + { + _namingConfig.StandardMovieFormat = + "{Movie.Title}.{MediaInfo VideoDynamicRange}"; + + GivenMediaInfoModel(schemaRevision: 3); + _movie.Path = null; + + Subject.BuildFileName( _movie, _movieFile); + + Mocker.GetMock().Verify(v => v.Update(_movieFile, _movie), Times.Never()); + } + + [Test] + public void should_not_update_media_info_if_token_not_configured_and_revision_is_old() + { + _namingConfig.StandardMovieFormat = + "{Movie.Title}"; + + GivenMediaInfoModel(schemaRevision: 3); + + Subject.BuildFileName( _movie, _movieFile); + + Mocker.GetMock().Verify(v => v.Update(_movieFile, _movie), Times.Never()); + } + + [Test] + public void should_not_update_media_info_if_token_configured_and_revision_is_current() + { + _namingConfig.StandardMovieFormat = + "{Movie.Title}.{MediaInfo VideoDynamicRange}"; + + GivenMediaInfoModel(schemaRevision: 5); + + Subject.BuildFileName( _movie, _movieFile); + + Mocker.GetMock().Verify(v => v.Update(_movieFile, _movie), Times.Never()); + } + + [Test] + public void should_not_update_media_info_if_token_configured_and_revision_is_newer() + { + _namingConfig.StandardMovieFormat = + "{Movie.Title}.{MediaInfo VideoDynamicRange}"; + + GivenMediaInfoModel(schemaRevision: 8); + + Subject.BuildFileName(_movie, _movieFile); + + Mocker.GetMock().Verify(v => v.Update(_movieFile, _movie), Times.Never()); + } + + private void GivenMediaInfoModel(string videoCodec = "AVC", string audioCodec = "DTS", int audioChannels = 6, int videoBitDepth = 8, + string videoColourPrimaries = "", string videoTransferCharacteristics = "", string audioLanguages = "English", + string subtitles = "English/Spanish/Italian", int schemaRevision = 5) + { + _movieFile.MediaInfo = new MediaInfoModel + { + VideoCodec = videoCodec, + AudioFormat = audioCodec, + AudioChannels = audioChannels, + AudioLanguages = audioLanguages, + Subtitles = subtitles, + VideoBitDepth = videoBitDepth, + VideoColourPrimaries = videoColourPrimaries, + VideoTransferCharacteristics = videoTransferCharacteristics, + SchemaRevision = schemaRevision + }; + + } } } diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs index 18c776ab8..75a6c6d59 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs @@ -7,7 +7,6 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Movies; using NzbDrone.Core.Movies.Events; using NzbDrone.Common; -using System; namespace NzbDrone.Core.MediaFiles { @@ -28,15 +27,13 @@ namespace NzbDrone.Core.MediaFiles { private readonly IEventAggregator _eventAggregator; private readonly IMediaFileRepository _mediaFileRepository; - private readonly IMovieService _movieService; private readonly Logger _logger; - public MediaFileService(IMediaFileRepository mediaFileRepository, IMovieService movieService, + public MediaFileService(IMediaFileRepository mediaFileRepository, IEventAggregator eventAggregator, Logger logger) { _mediaFileRepository = mediaFileRepository; _eventAggregator = eventAggregator; - _movieService = movieService; _logger = logger; } diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs index 0c10a33c8..13c6d7afe 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs @@ -4,13 +4,17 @@ using NzbDrone.Common.Disk; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Movies; -using System.Collections.Generic; using System.Linq; using NzbDrone.Core.Configuration; namespace NzbDrone.Core.MediaFiles.MediaInfo { - public class UpdateMediaInfoService : IHandle + public interface IUpdateMediaInfo + { + void Update(MovieFile movieFile, Movie movie); + } + + public class UpdateMediaInfoService : IHandle, IUpdateMediaInfo { private readonly IDiskProvider _diskProvider; private readonly IMediaFileService _mediaFileService; @@ -31,28 +35,6 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo _logger = logger; } - private void UpdateMediaInfo(Movie movie, List mediaFiles) - { - foreach (var mediaFile in mediaFiles) - { - var path = Path.Combine(movie.Path, mediaFile.RelativePath); - - if (!_diskProvider.FileExists(path)) - { - _logger.Debug("Can't update MediaInfo because '{0}' does not exist", path); - continue; - } - - mediaFile.MediaInfo = _videoFileInfoReader.GetMediaInfo(path); - - if (mediaFile.MediaInfo != null) - { - _mediaFileService.Update(mediaFile); - _logger.Debug("Updated MediaInfo for '{0}'", path); - } - } - } - public void Handle(MovieScannedEvent message) { if (!_configService.EnableMediaInfo) @@ -62,9 +44,45 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo } var allMediaFiles = _mediaFileService.GetFilesByMovie(message.Movie.Id); - var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < VideoFileInfoReader.MINIMUM_MEDIA_INFO_SCHEMA_REVISION).ToList(); + var filteredMediaFiles = allMediaFiles.Where(c => + c.MediaInfo == null || + c.MediaInfo.SchemaRevision < VideoFileInfoReader.MINIMUM_MEDIA_INFO_SCHEMA_REVISION).ToList(); - UpdateMediaInfo(message.Movie, filteredMediaFiles); + foreach (var mediaFile in filteredMediaFiles) + { + UpdateMediaInfo(mediaFile, message.Movie); + } + } + + public void Update(MovieFile movieFile, Movie movie) + { + if (!_configService.EnableMediaInfo) + { + _logger.Debug("MediaInfo is disabled"); + return; + } + + UpdateMediaInfo(movieFile, movie); + } + + private void UpdateMediaInfo(MovieFile movieFile, Movie movie) + { + var path = Path.Combine(movie.Path, movieFile.RelativePath); + + if (!_diskProvider.FileExists(path)) + { + _logger.Debug("Can't update MediaInfo because '{0}' does not exist", path); + return; + } + + var updatedMediaInfo = _videoFileInfoReader.GetMediaInfo(path); + + if (updatedMediaInfo != null) + { + movieFile.MediaInfo = updatedMediaInfo; + _mediaFileService.Update(movieFile); + _logger.Debug("Updated MediaInfo for '{0}'", path); + } } } } diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 99a8a10ac..42dd8cd40 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using NLog; -using NzbDrone.Common.Cache; using NzbDrone.Common.EnsureThat; using NzbDrone.Common.Extensions; using NzbDrone.Core.MediaFiles; @@ -28,8 +27,7 @@ namespace NzbDrone.Core.Organizer { private readonly INamingConfigService _namingConfigService; private readonly IQualityDefinitionService _qualityDefinitionService; - private readonly ICached _episodeFormatCache; - private readonly ICached _absoluteEpisodeFormatCache; + private readonly IUpdateMediaInfo _mediaInfoUpdater; private readonly Logger _logger; private static readonly Regex TitleRegex = new Regex(@"\{(?[- ._\[(]*)(?(?:[a-z0-9]+)(?:(?[- ._]+)(?:[a-z0-9]+))?)(?::(?[a-z0-9]+))?(?[- ._)\]]*)\}", @@ -65,14 +63,12 @@ namespace NzbDrone.Core.Organizer public FileNameBuilder(INamingConfigService namingConfigService, IQualityDefinitionService qualityDefinitionService, - ICacheManager cacheManager, + IUpdateMediaInfo mediaInfoUpdater, Logger logger) { _namingConfigService = namingConfigService; _qualityDefinitionService = qualityDefinitionService; - //_movieFormatCache = cacheManager.GetCache(GetType(), "movieFormat"); - _episodeFormatCache = cacheManager.GetCache(GetType(), "episodeFormat"); - _absoluteEpisodeFormatCache = cacheManager.GetCache(GetType(), "absoluteEpisodeFormat"); + _mediaInfoUpdater = mediaInfoUpdater; _logger = logger; } @@ -91,6 +87,8 @@ namespace NzbDrone.Core.Organizer var pattern = namingConfig.StandardMovieFormat; var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + UpdateMediaInfoIfNeeded(pattern, movieFile, movie); + AddMovieTokens(tokenHandlers, movie); AddReleaseDateTokens(tokenHandlers, movie.Year); AddImdbIdTokens(tokenHandlers, movie.ImdbId); @@ -312,34 +310,44 @@ namespace NzbDrone.Core.Organizer tokenHandlers["{Quality Real}"] = m => qualityReal; } + private const string MediaInfoVideoDynamicRangeToken = "{MediaInfo VideoDynamicRange}"; + private static readonly IDictionary MinimumMediaInfoSchemaRevisions = + new Dictionary(FileNameBuilderTokenEqualityComparer.Instance) + { + {MediaInfoVideoDynamicRangeToken, 5} + }; + private void AddMediaInfoTokens(Dictionary> tokenHandlers, MovieFile movieFile) { - if (movieFile.MediaInfo == null) return; + if (movieFile.MediaInfo == null) + { + _logger.Trace("Media info is unavailable for {0}", movieFile); + + return; + } var sceneName = movieFile.GetSceneOrFileName(); + var videoCodec = MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, sceneName); var audioCodec = MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, sceneName); var audioChannels = MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo); + var audioLanguages = movieFile.MediaInfo.AudioLanguages ?? string.Empty; + var subtitles = movieFile.MediaInfo.Subtitles ?? string.Empty; - // Workaround until https://github.com/MediaArea/MediaInfo/issues/299 is fixed and release - if (audioCodec.EqualsIgnoreCase("DTS-X")) - { - audioChannels = audioChannels - 1 + 0.1m; - } - - var mediaInfoAudioLanguages = GetLanguagesToken(movieFile.MediaInfo.AudioLanguages); + var mediaInfoAudioLanguages = GetLanguagesToken(audioLanguages); if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace()) { mediaInfoAudioLanguages = $"[{mediaInfoAudioLanguages}]"; } + var mediaInfoAudioLanguagesAll = mediaInfoAudioLanguages; if (mediaInfoAudioLanguages == "[EN]") { mediaInfoAudioLanguages = string.Empty; } - var mediaInfoSubtitleLanguages = GetLanguagesToken(movieFile.MediaInfo.Subtitles); + var mediaInfoSubtitleLanguages = GetLanguagesToken(subtitles); if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace()) { mediaInfoSubtitleLanguages = $"[{mediaInfoSubtitleLanguages}]"; @@ -347,25 +355,11 @@ namespace NzbDrone.Core.Organizer var videoBitDepth = movieFile.MediaInfo.VideoBitDepth > 0 ? movieFile.MediaInfo.VideoBitDepth.ToString() : string.Empty; var audioChannelsFormatted = audioChannels > 0 ? - audioChannels.ToString("F1", CultureInfo.InvariantCulture) : - string.Empty; + audioChannels.ToString("F1", CultureInfo.InvariantCulture) : + string.Empty; var mediaInfo3D = movieFile.MediaInfo.VideoMultiViewCount > 1 ? "3D" : string.Empty; - var videoColourPrimaries = movieFile.MediaInfo.VideoColourPrimaries ?? string.Empty; - var videoTransferCharacteristics = movieFile.MediaInfo.VideoTransferCharacteristics ?? string.Empty; - var mediaInfoHDR = string.Empty; - - if (movieFile.MediaInfo.VideoBitDepth >= 10 && !videoColourPrimaries.IsNullOrWhiteSpace() && !videoTransferCharacteristics.IsNullOrWhiteSpace()) - { - string[] validTransferFunctions = new string[] { "PQ", "HLG" }; - - if (videoColourPrimaries.EqualsIgnoreCase("BT.2020") && validTransferFunctions.Any(videoTransferCharacteristics.Contains)) - { - mediaInfoHDR = "HDR"; - } - } - tokenHandlers["{MediaInfo Video}"] = m => videoCodec; tokenHandlers["{MediaInfo VideoCodec}"] = m => videoCodec; tokenHandlers["{MediaInfo VideoBitDepth}"] = m => videoBitDepth; @@ -377,12 +371,15 @@ namespace NzbDrone.Core.Organizer tokenHandlers["{MediaInfo AudioLanguagesAll}"] = m => mediaInfoAudioLanguagesAll; tokenHandlers["{MediaInfo SubtitleLanguages}"] = m => mediaInfoSubtitleLanguages; + tokenHandlers["{MediaInfo SubtitleLanguagesAll}"] = m => mediaInfoSubtitleLanguages; tokenHandlers["{MediaInfo 3D}"] = m => mediaInfo3D; - tokenHandlers["{MediaInfo HDR}"] = m => mediaInfoHDR; tokenHandlers["{MediaInfo Simple}"] = m => $"{videoCodec} {audioCodec}"; tokenHandlers["{MediaInfo Full}"] = m => $"{videoCodec} {audioCodec}{mediaInfoAudioLanguages} {mediaInfoSubtitleLanguages}"; + + tokenHandlers[MediaInfoVideoDynamicRangeToken] = + m => MediaInfoFormatter.FormatVideoDynamicRange(movieFile.MediaInfo); } private string GetLanguagesToken(string mediaInfoLanguages) @@ -394,7 +391,7 @@ namespace NzbDrone.Core.Organizer tokens.Add(item.Trim()); } - var cultures = System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.NeutralCultures); + var cultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); for (int i = 0; i < tokens.Count; i++) { try @@ -412,6 +409,26 @@ namespace NzbDrone.Core.Organizer return string.Join("+", tokens.Distinct()); } + private void UpdateMediaInfoIfNeeded(string pattern, MovieFile movieFile, Movie movie) + { + if (movie.Path.IsNullOrWhiteSpace()) + { + return; + } + + var schemaRevision = movieFile.MediaInfo != null ? movieFile.MediaInfo.SchemaRevision : 0; + var matches = TitleRegex.Matches(pattern); + + var shouldUpdateMediaInfo = matches.Cast() + .Select(m => MinimumMediaInfoSchemaRevisions.GetValueOrDefault(m.Value, -1)) + .Any(r => schemaRevision < r); + + if (shouldUpdateMediaInfo) + { + _mediaInfoUpdater.Update(movieFile, movie); + } + } + private string ReplaceTokens(string pattern, Dictionary> tokenHandlers, NamingConfig namingConfig) { return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenHandlers, namingConfig));