New: Named Release Profile preferred word renaming tokens

This commit is contained in:
Alanoll 2021-04-21 17:25:41 -05:00 committed by Mark McDowall
parent 6596d0b4da
commit d4cd4a9549
7 changed files with 279 additions and 26 deletions

View File

@ -11,6 +11,7 @@ using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Organizer; using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Test.Common; using NzbDrone.Test.Common;
@ -42,7 +43,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests
.Build(); .Build();
Mocker.GetMock<IBuildFileNames>() Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.BuildFilePath(It.IsAny<List<Episode>>(), It.IsAny<Series>(), It.IsAny<EpisodeFile>(), It.IsAny<string>(), It.IsAny<NamingConfig>(), It.IsAny<List<string>>())) .Setup(s => s.BuildFilePath(It.IsAny<List<Episode>>(), It.IsAny<Series>(), It.IsAny<EpisodeFile>(), It.IsAny<string>(), It.IsAny<NamingConfig>(), It.IsAny<PreferredWordMatchResults>()))
.Returns(@"C:\Test\TV\Series\Season 01\File Name.avi".AsOsAgnostic()); .Returns(@"C:\Test\TV\Series\Season 01\File Name.avi".AsOsAgnostic());
Mocker.GetMock<IBuildFileNames>() Mocker.GetMock<IBuildFileNames>()

View File

@ -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<FileNameBuilder>
{
private Series _series;
private Episode _episode1;
private EpisodeFile _episodeFile;
private NamingConfig _namingConfig;
private PreferredWordMatchResults _preferredWords;
[SetUp]
public void Setup()
{
_series = Builder<Series>
.CreateNew()
.With(s => s.Title = "South Park")
.Build();
_namingConfig = NamingConfig.Default;
_namingConfig.RenameEpisodes = true;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(_namingConfig);
_episode1 = Builder<Episode>.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<string>() {
"x265",
"extended"
},
ByReleaseProfile = new Dictionary<string, List<string>>() {
{
"CodecProfile",
new List<string>()
{
"x265"
}
},
{
"EditionProfile",
new List<string>()
{
"extended"
}
}
}
};
Mocker.GetMock<IQualityDefinitionService>()
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
.Returns<Quality>(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<Episode> { _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<Episode> { _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<Episode> { _episode1 }, _series, _episodeFile, preferredWords: _preferredWords)
.Should().Be(expected);
}
}
}

View File

@ -15,7 +15,8 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService
{ {
private Series _series = null; private Series _series = null;
private List<ReleaseProfile> _releaseProfiles = null; private List<ReleaseProfile> _releaseProfiles = null;
private string _title = "Series.Title.S01E01.720p.HDTV.x264-Sonarr"; private List<ReleaseProfile> _namedReleaseProfiles = null;
private string _title = "Series.Title.S01E01.extended.720p.HDTV.x264-Sonarr";
[SetUp] [SetUp]
public void Setup() public void Setup()
@ -35,6 +36,27 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService
} }
}); });
_namedReleaseProfiles = new List<ReleaseProfile>();
_namedReleaseProfiles.Add(new ReleaseProfile
{
Name = "CodecProfile",
Preferred = new List<KeyValuePair<string, int>>
{
new KeyValuePair<string, int>("x264", 5),
new KeyValuePair<string, int>("x265", -10)
}
});
_namedReleaseProfiles.Add(new ReleaseProfile
{
Name = "EditionProfile",
Preferred = new List<KeyValuePair<string, int>>
{
new KeyValuePair<string, int>("extended", 5),
new KeyValuePair<string, int>("uncut", -10)
}
});
Mocker.GetMock<ITermMatcherService>() Mocker.GetMock<ITermMatcherService>()
.Setup(s => s.MatchingTerm(It.IsAny<string>(), _title)) .Setup(s => s.MatchingTerm(It.IsAny<string>(), _title))
@ -48,6 +70,13 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService
.Returns(_releaseProfiles); .Returns(_releaseProfiles);
} }
private void GivenNamedReleaseProfile()
{
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.EnabledForTags(It.IsAny<HashSet<int>>(), It.IsAny<int>()))
.Returns(_namedReleaseProfiles);
}
[Test] [Test]
public void should_return_empty_list_when_there_are_no_release_profiles() 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<HashSet<int>>(), It.IsAny<int>())) .Setup(s => s.EnabledForTags(It.IsAny<HashSet<int>>(), It.IsAny<int>()))
.Returns(new List<ReleaseProfile>()); .Returns(new List<ReleaseProfile>());
Subject.GetMatchingPreferredWords(_series, _title).Should().BeEmpty(); var matchingResults = Subject.GetMatchingPreferredWords(_series, _title);
matchingResults.All.Should().BeEmpty();
} }
[Test] [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); _releaseProfiles.First().Preferred.RemoveAt(0);
GivenReleaseProfile(); GivenReleaseProfile();
Subject.GetMatchingPreferredWords(_series, _title).Should().BeEmpty(); var matchingResults = Subject.GetMatchingPreferredWords(_series, _title);
matchingResults.All.Should().BeEmpty();
} }
[Test] [Test]
public void should_return_list_of_matching_terms() public void should_return_list_of_matching_terms_from_unnamedprofile()
{ {
GivenReleaseProfile(); 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" });
} }
} }
} }

View File

@ -22,8 +22,8 @@ namespace NzbDrone.Core.Organizer
{ {
public interface IBuildFileNames public interface IBuildFileNames
{ {
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, string extension = "", NamingConfig namingConfig = null, List<string> preferredWords = null); string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, string extension = "", NamingConfig namingConfig = null, PreferredWordMatchResults preferredWords = null);
string BuildFilePath(List<Episode> episodes, Series series, EpisodeFile episodeFile, string extension, NamingConfig namingConfig = null, List<string> preferredWords = null); string BuildFilePath(List<Episode> episodes, Series series, EpisodeFile episodeFile, string extension, NamingConfig namingConfig = null, PreferredWordMatchResults preferredWords = null);
string BuildSeasonPath(Series series, int seasonNumber); string BuildSeasonPath(Series series, int seasonNumber);
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
string GetSeriesFolder(Series series, NamingConfig namingConfig = null); string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
@ -100,7 +100,7 @@ namespace NzbDrone.Core.Organizer
_logger = logger; _logger = logger;
} }
private string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, string extension, int maxPath, NamingConfig namingConfig = null, List<string> preferredWords = null) private string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, string extension, int maxPath, NamingConfig namingConfig = null, PreferredWordMatchResults preferredWords = null)
{ {
if (namingConfig == null) if (namingConfig == null)
{ {
@ -183,12 +183,12 @@ namespace NzbDrone.Core.Organizer
return string.Join(Path.DirectorySeparatorChar.ToString(), components) + extension; return string.Join(Path.DirectorySeparatorChar.ToString(), components) + extension;
} }
public string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, string extension = "", NamingConfig namingConfig = null, List<string> preferredWords = null) public string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, string extension = "", NamingConfig namingConfig = null, PreferredWordMatchResults preferredWords = null)
{ {
return BuildFileName(episodes, series, episodeFile, extension, LongPathSupport.MaxFilePathLength, namingConfig, preferredWords); return BuildFileName(episodes, series, episodeFile, extension, LongPathSupport.MaxFilePathLength, namingConfig, preferredWords);
} }
public string BuildFilePath(List<Episode> episodes, Series series, EpisodeFile episodeFile, string extension, NamingConfig namingConfig = null, List<string> preferredWords = null) public string BuildFilePath(List<Episode> episodes, Series series, EpisodeFile episodeFile, string extension, NamingConfig namingConfig = null, PreferredWordMatchResults preferredWords = null)
{ {
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
@ -651,14 +651,34 @@ namespace NzbDrone.Core.Organizer
tokenHandlers["{TvMazeId}"] = m => series.TvMazeId > 0 ? series.TvMazeId.ToString() : string.Empty; tokenHandlers["{TvMazeId}"] = m => series.TvMazeId > 0 ? series.TvMazeId.ToString() : string.Empty;
} }
private void AddPreferredWords(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Series series, EpisodeFile episodeFile, List<string> preferredWords = null) private void AddPreferredWords(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Series series, EpisodeFile episodeFile, PreferredWordMatchResults preferredWords = null)
{ {
if (preferredWords == null) if (preferredWords == null)
{ {
preferredWords = _preferredWordService.GetMatchingPreferredWords(series, episodeFile.GetSceneOrFileName()); 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) private string GetLanguagesToken(string mediaInfoLanguages, string filter, bool skipEnglishOnly, bool quoted)

View File

@ -3,6 +3,7 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.Organizer namespace NzbDrone.Core.Organizer
{ {
@ -34,7 +35,7 @@ namespace NzbDrone.Core.Organizer
private static EpisodeFile _dailyEpisodeFile; private static EpisodeFile _dailyEpisodeFile;
private static EpisodeFile _animeEpisodeFile; private static EpisodeFile _animeEpisodeFile;
private static EpisodeFile _animeMultiEpisodeFile; private static EpisodeFile _animeMultiEpisodeFile;
private static List<string> _preferredWords; private static PreferredWordMatchResults _preferredWords;
public FileNameSampleService(IBuildFileNames buildFileNames) public FileNameSampleService(IBuildFileNames buildFileNames)
{ {
@ -169,9 +170,9 @@ namespace NzbDrone.Core.Organizer
MediaInfo = mediaInfoAnime MediaInfo = mediaInfoAnime
}; };
_preferredWords = new List<string> _preferredWords = new PreferredWordMatchResults()
{ {
"iNTERNAL" All = new List<string>() {"iNTERNAL" }
}; };
} }

View File

@ -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<string> All
{
get;
set;
}
public Dictionary<string, List<string>> ByReleaseProfile
{
get;
set;
}
public PreferredWordMatchResults()
{
All = new List<string>();
ByReleaseProfile = new Dictionary<string, List<string>>();
}
}
}

View File

@ -3,14 +3,15 @@ using NzbDrone.Core.Tv;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using System;
namespace NzbDrone.Core.Profiles.Releases namespace NzbDrone.Core.Profiles.Releases
{ {
public interface IPreferredWordService public interface IPreferredWordService
{ {
int Calculate(Series series, string title, int indexerId); int Calculate(Series series, string title, int indexerId);
List<string> GetMatchingPreferredWords(Series series, string title); PreferredWordMatchResults GetMatchingPreferredWords(Series series, string title);
} }
public class PreferredWordService : IPreferredWordService public class PreferredWordService : IPreferredWordService
{ {
@ -52,15 +53,18 @@ namespace NzbDrone.Core.Profiles.Releases
return score; return score;
} }
public List<string> GetMatchingPreferredWords(Series series, string title) public PreferredWordMatchResults GetMatchingPreferredWords(Series series, string title)
{ {
var releaseProfiles = _releaseProfileService.EnabledForTags(series.Tags, 0); var releaseProfiles = _releaseProfileService.EnabledForTags(series.Tags, 0);
var matchingPairs = new List<KeyValuePair<string, int>>(); var profileWords = new Dictionary<string, List<KeyValuePair<string, int>>>();
var allWords = new List<KeyValuePair<string, int>>();
_logger.Trace("Calculating preferred word score for '{0}'", title); _logger.Trace("Determining preferred word matches for '{0}'", title);
foreach (var releaseProfile in releaseProfiles) foreach (var releaseProfile in releaseProfiles)
{ {
var matchingPairs = new List<KeyValuePair<string, int>>();
if (!releaseProfile.IncludePreferredWhenRenaming) if (!releaseProfile.IncludePreferredWhenRenaming)
{ {
continue; continue;
@ -76,11 +80,34 @@ namespace NzbDrone.Core.Profiles.Releases
matchingPairs.Add(new KeyValuePair<string, int>(matchingTerm, preferredPair.Value)); matchingPairs.Add(new KeyValuePair<string, int>(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<KeyValuePair<string, int>>());
}
profileWords[profileName].AddRange(matchingPairs);
}
allWords.AddRange(matchingPairs); // Add the "everything grouping"
}
} }
return matchingPairs.OrderByDescending(p => p.Value) var results = new PreferredWordMatchResults()
.Select(p => p.Key) {
.ToList(); 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;
} }
} }
} }