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.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<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());
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 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]
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>()
.Setup(s => s.MatchingTerm(It.IsAny<string>(), _title))
@ -48,6 +70,13 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService
.Returns(_releaseProfiles);
}
private void GivenNamedReleaseProfile()
{
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.EnabledForTags(It.IsAny<HashSet<int>>(), It.IsAny<int>()))
.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<HashSet<int>>(), It.IsAny<int>()))
.Returns(new List<ReleaseProfile>());
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" });
}
}
}

View File

@ -22,8 +22,8 @@ namespace NzbDrone.Core.Organizer
{
public interface IBuildFileNames
{
string BuildFileName(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, 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, 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<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)
{
@ -183,12 +183,12 @@ namespace NzbDrone.Core.Organizer
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);
}
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();
@ -651,14 +651,34 @@ namespace NzbDrone.Core.Organizer
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)
{
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)

View File

@ -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<string> _preferredWords;
private static PreferredWordMatchResults _preferredWords;
public FileNameSampleService(IBuildFileNames buildFileNames)
{
@ -169,9 +170,9 @@ namespace NzbDrone.Core.Organizer
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,13 +3,14 @@ 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<string> 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<string> GetMatchingPreferredWords(Series series, string title)
public PreferredWordMatchResults GetMatchingPreferredWords(Series series, string title)
{
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)
{
var matchingPairs = new List<KeyValuePair<string, int>>();
if (!releaseProfile.IncludePreferredWhenRenaming)
{
continue;
@ -76,11 +80,34 @@ namespace NzbDrone.Core.Profiles.Releases
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>>());
}
return matchingPairs.OrderByDescending(p => p.Value)
.Select(p => p.Key)
.ToList();
profileWords[profileName].AddRange(matchingPairs);
}
allWords.AddRange(matchingPairs); // Add the "everything grouping"
}
}
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;
}
}
}