From d4cd4a9549ef41983caf3ff713cb9e3f6970e8a0 Mon Sep 17 00:00:00 2001 From: Alanoll Date: Wed, 21 Apr 2021 17:25:41 -0500 Subject: [PATCH] New: Named Release Profile preferred word renaming tokens --- .../MoveEpisodeFileFixture.cs | 3 +- .../PreferredWordsFixture.cs | 112 ++++++++++++++++++ .../GetMatchingPreferredWordsFixture.cs | 75 +++++++++++- .../Organizer/FileNameBuilder.cs | 34 ++++-- .../Organizer/FileNameSampleService.cs | 7 +- .../Releases/PreferredWordMatchResults.cs | 29 +++++ .../Profiles/Releases/PreferredWordService.cs | 45 +++++-- 7 files changed, 279 insertions(+), 26 deletions(-) create mode 100644 src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/PreferredWordsFixture.cs create mode 100644 src/NzbDrone.Core/Profiles/Releases/PreferredWordMatchResults.cs diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeFileMovingServiceTests/MoveEpisodeFileFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeFileMovingServiceTests/MoveEpisodeFileFixture.cs index 298e524a9..360b122ea 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeFileMovingServiceTests/MoveEpisodeFileFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeFileMovingServiceTests/MoveEpisodeFileFixture.cs @@ -11,6 +11,7 @@ using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles.Releases; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; @@ -42,7 +43,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests .Build(); Mocker.GetMock() - .Setup(s => s.BuildFilePath(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .Setup(s => s.BuildFilePath(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(@"C:\Test\TV\Series\Season 01\File Name.avi".AsOsAgnostic()); Mocker.GetMock() diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/PreferredWordsFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/PreferredWordsFixture.cs new file mode 100644 index 000000000..64287d854 --- /dev/null +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/PreferredWordsFixture.cs @@ -0,0 +1,112 @@ +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Profiles.Releases; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests +{ + [TestFixture] + + public class PreferredWordsFixture : CoreTest + { + private Series _series; + private Episode _episode1; + private EpisodeFile _episodeFile; + private NamingConfig _namingConfig; + + private PreferredWordMatchResults _preferredWords; + + [SetUp] + public void Setup() + { + _series = Builder + .CreateNew() + .With(s => s.Title = "South Park") + .Build(); + + + _namingConfig = NamingConfig.Default; + _namingConfig.RenameEpisodes = true; + + + Mocker.GetMock() + .Setup(c => c.GetConfig()).Returns(_namingConfig); + + _episode1 = Builder.CreateNew() + .With(e => e.Title = "City Sushi") + .With(e => e.SeasonNumber = 15) + .With(e => e.EpisodeNumber = 6) + .With(e => e.AbsoluteEpisodeNumber = 100) + .Build(); + + _episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "SonarrTest" }; + + _preferredWords = new PreferredWordMatchResults() { + All = new List() { + "x265", + "extended" + }, + ByReleaseProfile = new Dictionary>() { + { + "CodecProfile", + new List() + { + "x265" + } + }, + { + "EditionProfile", + new List() + { + "extended" + } + } + } + }; + + Mocker.GetMock() + .Setup(v => v.Get(Moq.It.IsAny())) + .Returns(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v)); + } + + [TestCase("{Preferred Words}", "x265 extended")] + [TestCase("{Preferred Words:CodecProfile}", "x265")] + [TestCase("{Preferred Words:EditionProfile}", "extended")] + [TestCase("{Preferred Words:CodecProfile} - {PreferredWords:EditionProfile}", "x265 - extended")] + public void should_replace_PreferredWords(string format, string expected) + { + _namingConfig.StandardEpisodeFormat = format; + + Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile, preferredWords: _preferredWords) + .Should().Be(expected); + } + + [TestCase("{Preferred Words:}", "{Preferred Words:}")] + public void should_not_replace_PreferredWords(string format, string expected) + { + _namingConfig.StandardEpisodeFormat = format; + + Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile, preferredWords: _preferredWords) + .Should().Be(expected); + } + + [TestCase("{Preferred Words:NonexistentProfile}", "")] + public void should_replace_PreferredWords_with_empty_string(string format, string expected) + { + _namingConfig.StandardEpisodeFormat = format; + + Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile, preferredWords: _preferredWords) + .Should().Be(expected); + } + } +} diff --git a/src/NzbDrone.Core.Test/Profiles/Releases/PreferredWordService/GetMatchingPreferredWordsFixture.cs b/src/NzbDrone.Core.Test/Profiles/Releases/PreferredWordService/GetMatchingPreferredWordsFixture.cs index d60713b5a..624eb18dd 100644 --- a/src/NzbDrone.Core.Test/Profiles/Releases/PreferredWordService/GetMatchingPreferredWordsFixture.cs +++ b/src/NzbDrone.Core.Test/Profiles/Releases/PreferredWordService/GetMatchingPreferredWordsFixture.cs @@ -15,7 +15,8 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService { private Series _series = null; private List _releaseProfiles = null; - private string _title = "Series.Title.S01E01.720p.HDTV.x264-Sonarr"; + private List _namedReleaseProfiles = null; + private string _title = "Series.Title.S01E01.extended.720p.HDTV.x264-Sonarr"; [SetUp] public void Setup() @@ -35,6 +36,27 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService } }); + _namedReleaseProfiles = new List(); + + _namedReleaseProfiles.Add(new ReleaseProfile + { + Name = "CodecProfile", + Preferred = new List> + { + new KeyValuePair("x264", 5), + new KeyValuePair("x265", -10) + } + }); + _namedReleaseProfiles.Add(new ReleaseProfile + { + Name = "EditionProfile", + Preferred = new List> + { + new KeyValuePair("extended", 5), + new KeyValuePair("uncut", -10) + } + }); + Mocker.GetMock() .Setup(s => s.MatchingTerm(It.IsAny(), _title)) @@ -48,6 +70,13 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService .Returns(_releaseProfiles); } + private void GivenNamedReleaseProfile() + { + Mocker.GetMock() + .Setup(s => s.EnabledForTags(It.IsAny>(), It.IsAny())) + .Returns(_namedReleaseProfiles); + } + [Test] public void should_return_empty_list_when_there_are_no_release_profiles() { @@ -55,24 +84,58 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService .Setup(s => s.EnabledForTags(It.IsAny>(), It.IsAny())) .Returns(new List()); - Subject.GetMatchingPreferredWords(_series, _title).Should().BeEmpty(); + var matchingResults = Subject.GetMatchingPreferredWords(_series, _title); + matchingResults.All.Should().BeEmpty(); } [Test] - public void should_return_empty_list_when_there_are_no_matching_preferred_words() + public void should_return_empty_list_when_there_are_no_matching_preferred_words_from_unnamedprofile() { _releaseProfiles.First().Preferred.RemoveAt(0); GivenReleaseProfile(); - Subject.GetMatchingPreferredWords(_series, _title).Should().BeEmpty(); + var matchingResults = Subject.GetMatchingPreferredWords(_series, _title); + matchingResults.All.Should().BeEmpty(); } [Test] - public void should_return_list_of_matching_terms() + public void should_return_list_of_matching_terms_from_unnamedprofile() { GivenReleaseProfile(); - Subject.GetMatchingPreferredWords(_series, _title).Should().Contain(new[] {"x264"}); + var matchingResults = Subject.GetMatchingPreferredWords(_series, _title); + matchingResults.All.Should().Equal(new[] { "x264" }); + } + + [Test] + public void should_return_empty_list_when_there_are_no_matching_preferred_words_from_namedprofiles() + { + _namedReleaseProfiles.First().Preferred.RemoveAt(0); + _namedReleaseProfiles.Skip(1).First().Preferred.RemoveAt(0); + + GivenNamedReleaseProfile(); + + var matchingResults = Subject.GetMatchingPreferredWords(_series, _title); + matchingResults.All.Should().BeEmpty(); + } + + [Test] + public void should_return_list_of_matching_terms_from_multiple_namedprofiles() + { + GivenNamedReleaseProfile(); + + var matchingResults = Subject.GetMatchingPreferredWords(_series, _title); + matchingResults.ByReleaseProfile.Should().ContainKey("CodecProfile").WhichValue.Should().Equal(new[] { "x264" }); + matchingResults.ByReleaseProfile.Should().ContainKey("EditionProfile").WhichValue.Should().Equal(new[] { "extended" }); + } + + [Test] + public void should_return_list_of_matching_terms_from_multiple_namedprofiles_all() + { + GivenNamedReleaseProfile(); + + var matchingResults = Subject.GetMatchingPreferredWords(_series, _title); + matchingResults.All.Should().Equal(new[] { "x264", "extended" }); } } } diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 2ed329f71..79d959582 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -22,8 +22,8 @@ namespace NzbDrone.Core.Organizer { public interface IBuildFileNames { - string BuildFileName(List episodes, Series series, EpisodeFile episodeFile, string extension = "", NamingConfig namingConfig = null, List preferredWords = null); - string BuildFilePath(List episodes, Series series, EpisodeFile episodeFile, string extension, NamingConfig namingConfig = null, List preferredWords = null); + string BuildFileName(List episodes, Series series, EpisodeFile episodeFile, string extension = "", NamingConfig namingConfig = null, PreferredWordMatchResults preferredWords = null); + string BuildFilePath(List episodes, Series series, EpisodeFile episodeFile, string extension, NamingConfig namingConfig = null, PreferredWordMatchResults preferredWords = null); string BuildSeasonPath(Series series, int seasonNumber); BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); string GetSeriesFolder(Series series, NamingConfig namingConfig = null); @@ -100,7 +100,7 @@ namespace NzbDrone.Core.Organizer _logger = logger; } - private string BuildFileName(List episodes, Series series, EpisodeFile episodeFile, string extension, int maxPath, NamingConfig namingConfig = null, List preferredWords = null) + private string BuildFileName(List episodes, Series series, EpisodeFile episodeFile, string extension, int maxPath, NamingConfig namingConfig = null, PreferredWordMatchResults preferredWords = null) { if (namingConfig == null) { @@ -183,12 +183,12 @@ namespace NzbDrone.Core.Organizer return string.Join(Path.DirectorySeparatorChar.ToString(), components) + extension; } - public string BuildFileName(List episodes, Series series, EpisodeFile episodeFile, string extension = "", NamingConfig namingConfig = null, List preferredWords = null) + public string BuildFileName(List episodes, Series series, EpisodeFile episodeFile, string extension = "", NamingConfig namingConfig = null, PreferredWordMatchResults preferredWords = null) { return BuildFileName(episodes, series, episodeFile, extension, LongPathSupport.MaxFilePathLength, namingConfig, preferredWords); } - public string BuildFilePath(List episodes, Series series, EpisodeFile episodeFile, string extension, NamingConfig namingConfig = null, List preferredWords = null) + public string BuildFilePath(List episodes, Series series, EpisodeFile episodeFile, string extension, NamingConfig namingConfig = null, PreferredWordMatchResults preferredWords = null) { Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); @@ -651,14 +651,34 @@ namespace NzbDrone.Core.Organizer tokenHandlers["{TvMazeId}"] = m => series.TvMazeId > 0 ? series.TvMazeId.ToString() : string.Empty; } - private void AddPreferredWords(Dictionary> tokenHandlers, Series series, EpisodeFile episodeFile, List preferredWords = null) + private void AddPreferredWords(Dictionary> tokenHandlers, Series series, EpisodeFile episodeFile, PreferredWordMatchResults preferredWords = null) { if (preferredWords == null) { preferredWords = _preferredWordService.GetMatchingPreferredWords(series, episodeFile.GetSceneOrFileName()); } - tokenHandlers["{Preferred Words}"] = m => string.Join(" ", preferredWords); + tokenHandlers["{Preferred Words}"] = m => { + + var profileName = ""; + + if (m.CustomFormat != null) + { + profileName = m.CustomFormat.Trim(); + } + + if (profileName.IsNullOrWhiteSpace()) + { + return string.Join(" ", preferredWords.All); + } + + if (preferredWords.ByReleaseProfile.TryGetValue(profileName, out var profilePreferredWords)) + { + return string.Join(" ", profilePreferredWords); + } + + return string.Empty; + }; } private string GetLanguagesToken(string mediaInfoLanguages, string filter, bool skipEnglishOnly, bool quoted) diff --git a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs index cb7e4fc71..4c663b28e 100644 --- a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs +++ b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs @@ -3,6 +3,7 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.MediaFiles.MediaInfo; +using NzbDrone.Core.Profiles.Releases; namespace NzbDrone.Core.Organizer { @@ -34,7 +35,7 @@ namespace NzbDrone.Core.Organizer private static EpisodeFile _dailyEpisodeFile; private static EpisodeFile _animeEpisodeFile; private static EpisodeFile _animeMultiEpisodeFile; - private static List _preferredWords; + private static PreferredWordMatchResults _preferredWords; public FileNameSampleService(IBuildFileNames buildFileNames) { @@ -169,9 +170,9 @@ namespace NzbDrone.Core.Organizer MediaInfo = mediaInfoAnime }; - _preferredWords = new List + _preferredWords = new PreferredWordMatchResults() { - "iNTERNAL" + All = new List() {"iNTERNAL" } }; } diff --git a/src/NzbDrone.Core/Profiles/Releases/PreferredWordMatchResults.cs b/src/NzbDrone.Core/Profiles/Releases/PreferredWordMatchResults.cs new file mode 100644 index 000000000..3a9444b65 --- /dev/null +++ b/src/NzbDrone.Core/Profiles/Releases/PreferredWordMatchResults.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NzbDrone.Core.Profiles.Releases +{ + public class PreferredWordMatchResults + { + public List All + { + get; + set; + } + + public Dictionary> ByReleaseProfile + { + get; + set; + } + + public PreferredWordMatchResults() + { + All = new List(); + ByReleaseProfile = new Dictionary>(); + } + } +} diff --git a/src/NzbDrone.Core/Profiles/Releases/PreferredWordService.cs b/src/NzbDrone.Core/Profiles/Releases/PreferredWordService.cs index bee590510..5b1b51bdc 100644 --- a/src/NzbDrone.Core/Profiles/Releases/PreferredWordService.cs +++ b/src/NzbDrone.Core/Profiles/Releases/PreferredWordService.cs @@ -3,14 +3,15 @@ using NzbDrone.Core.Tv; using System.Collections.Generic; using System.Linq; using NzbDrone.Common.Extensions; +using System; namespace NzbDrone.Core.Profiles.Releases { public interface IPreferredWordService { int Calculate(Series series, string title, int indexerId); - List GetMatchingPreferredWords(Series series, string title); - } + PreferredWordMatchResults GetMatchingPreferredWords(Series series, string title); + } public class PreferredWordService : IPreferredWordService { @@ -52,15 +53,18 @@ namespace NzbDrone.Core.Profiles.Releases return score; } - public List GetMatchingPreferredWords(Series series, string title) + public PreferredWordMatchResults GetMatchingPreferredWords(Series series, string title) { var releaseProfiles = _releaseProfileService.EnabledForTags(series.Tags, 0); - var matchingPairs = new List>(); + var profileWords = new Dictionary>>(); + var allWords = new List>(); - _logger.Trace("Calculating preferred word score for '{0}'", title); + _logger.Trace("Determining preferred word matches for '{0}'", title); foreach (var releaseProfile in releaseProfiles) { + var matchingPairs = new List>(); + if (!releaseProfile.IncludePreferredWhenRenaming) { continue; @@ -76,11 +80,34 @@ namespace NzbDrone.Core.Profiles.Releases matchingPairs.Add(new KeyValuePair(matchingTerm, preferredPair.Value)); } } + + if (matchingPairs.Count > 0) + { + if (releaseProfile.Name.IsNotNullOrWhiteSpace()) + { + var profileName = releaseProfile.Name.Trim(); + + if (!profileWords.ContainsKey(profileName)) + { + profileWords.Add(profileName, new List>()); + } + + profileWords[profileName].AddRange(matchingPairs); + } + + allWords.AddRange(matchingPairs); // Add the "everything grouping" + } } - return matchingPairs.OrderByDescending(p => p.Value) - .Select(p => p.Key) - .ToList(); + var results = new PreferredWordMatchResults() + { + All = allWords.OrderByDescending(m => m.Value).Select(m => m.Key).ToList(), + ByReleaseProfile = profileWords.ToDictionary(item => item.Key, item => item.Value.OrderByDescending(m => m.Value).Select(m => m.Key).ToList()) + }; + + _logger.Trace("Determined preferred word matches for '{0}'. Count {1}", title, allWords.Count); + + return results; } - } + } }