From 81aaf00a4cd2b3a2f8ddf67226c13bb51ea39dda Mon Sep 17 00:00:00 2001 From: Stevie Robinson Date: Tue, 10 Oct 2023 15:58:03 +0200 Subject: [PATCH] New: Add additional CleanTitle tokens and re-order options Closes #6066 --- .../MediaManagement/Naming/NamingModal.js | 9 +- .../CleanTitleTheFixture.cs | 86 +++++++++++++++++++ .../CleanTitleTheWithoutYearFixture.cs | 79 +++++++++++++++++ .../CleanTitleTheYearFixture.cs | 81 +++++++++++++++++ .../Organizer/FileNameBuilder.cs | 37 +++++++- 5 files changed, 287 insertions(+), 5 deletions(-) create mode 100644 src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheFixture.cs create mode 100644 src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheWithoutYearFixture.cs create mode 100644 src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheYearFixture.cs diff --git a/frontend/src/Settings/MediaManagement/Naming/NamingModal.js b/frontend/src/Settings/MediaManagement/Naming/NamingModal.js index 60d6a8854..509c5d940 100644 --- a/frontend/src/Settings/MediaManagement/Naming/NamingModal.js +++ b/frontend/src/Settings/MediaManagement/Naming/NamingModal.js @@ -82,13 +82,16 @@ const fileNameTokens = [ const seriesTokens = [ { token: '{Series Title}', example: 'The Series Title\'s!' }, { token: '{Series CleanTitle}', example: 'The Series Title\'s!' }, - { token: '{Series CleanTitleYear}', example: 'The Series Titles! 2010' }, + { token: '{Series TitleYear}', example: 'The Series Title\'s! (2010)' }, + { token: '{Series CleanTitleYear}', example: 'The Series Title\'s! 2010' }, + { token: '{Series TitleWithoutYear}', example: 'The Series Title\'s!' }, { token: '{Series CleanTitleWithoutYear}', example: 'The Series Title\'s!' }, { token: '{Series TitleThe}', example: 'Series Title\'s!, The' }, + { token: '{Series CleanTitleThe}', example: 'Series Title\'s!, The' }, { token: '{Series TitleTheYear}', example: 'Series Title\'s!, The (2010)' }, + { token: '{Series CleanTitleTheYear}', example: 'Series Title\'s!, The 2010' }, { token: '{Series TitleTheWithoutYear}', example: 'Series Title\'s!, The' }, - { token: '{Series TitleYear}', example: 'The Series Title\'s! (2010)' }, - { token: '{Series TitleWithoutYear}', example: 'Series Title\'s!' }, + { token: '{Series CleanTitleTheWithoutYear}', example: 'Series Title\'s!, The' }, { token: '{Series TitleFirstCharacter}', example: 'S' }, { token: '{Series Year}', example: '2010' } ]; diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheFixture.cs new file mode 100644 index 000000000..6b738f250 --- /dev/null +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheFixture.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.CustomFormats; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests +{ + [TestFixture] + public class CleanTitleTheFixture : CoreTest + { + private Series _series; + private Episode _episode; + private EpisodeFile _episodeFile; + private NamingConfig _namingConfig; + + [SetUp] + public void Setup() + { + _series = Builder + .CreateNew() + .Build(); + + _episode = 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" }; + + _namingConfig = NamingConfig.Default; + _namingConfig.RenameEpisodes = true; + + Mocker.GetMock() + .Setup(c => c.GetConfig()).Returns(_namingConfig); + + Mocker.GetMock() + .Setup(v => v.Get(Moq.It.IsAny())) + .Returns(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v)); + + Mocker.GetMock() + .Setup(v => v.All()) + .Returns(new List()); + } + + [TestCase("The Mist", "Mist, The")] + [TestCase("A Place to Call Home", "Place to Call Home, A")] + [TestCase("An Adventure in Space and Time", "Adventure in Space and Time, An")] + [TestCase("The Flash (2010)", "Flash, The 2010")] + [TestCase("A League Of Their Own (AU)", "League Of Their Own, A AU")] + [TestCase("The Fixer (ZH) (2015)", "Fixer, The ZH 2015")] + [TestCase("The Sixth Sense 2 (Thai)", "Sixth Sense 2, The Thai")] + [TestCase("The Amazing Race (Latin America)", "Amazing Race, The Latin America")] + [TestCase("The Rat Pack (A&E)", "Rat Pack, The AandE")] + [TestCase("The Climax: I (Almost) Got Away With It (2016)", "Climax I Almost Got Away With It, The 2016")] + public void should_get_expected_title_back(string title, string expected) + { + _series.Title = title; + _namingConfig.StandardEpisodeFormat = "{Series CleanTitleThe}"; + + Subject.BuildFileName(new List { _episode }, _series, _episodeFile) + .Should().Be(expected); + } + + [TestCase("A")] + [TestCase("Anne")] + [TestCase("Theodore")] + [TestCase("3%")] + public void should_not_change_title(string title) + { + _series.Title = title; + _namingConfig.StandardEpisodeFormat = "{Series CleanTitleThe}"; + + Subject.BuildFileName(new List { _episode }, _series, _episodeFile) + .Should().Be(title); + } + } +} diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheWithoutYearFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheWithoutYearFixture.cs new file mode 100644 index 000000000..104f51867 --- /dev/null +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheWithoutYearFixture.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.CustomFormats; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests +{ + [TestFixture] + public class CleanTitleTheWithoutYearFixture : CoreTest + { + private Series _series; + private Episode _episode; + private EpisodeFile _episodeFile; + private NamingConfig _namingConfig; + + [SetUp] + public void Setup() + { + _series = Builder + .CreateNew() + .Build(); + + _episode = 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" }; + + _namingConfig = NamingConfig.Default; + _namingConfig.RenameEpisodes = true; + + Mocker.GetMock() + .Setup(c => c.GetConfig()).Returns(_namingConfig); + + Mocker.GetMock() + .Setup(v => v.Get(Moq.It.IsAny())) + .Returns(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v)); + + Mocker.GetMock() + .Setup(v => v.All()) + .Returns(new List()); + } + + [TestCase("The Mist", 2018, "Mist, The")] + [TestCase("The Rat Pack (A&E)", 1999, "Rat Pack, The AandE")] + [TestCase("The Climax: I (Almost) Got Away With It (2016)", 2016, "Climax I Almost Got Away With It, The")] + [TestCase("A", 2017, "A")] + public void should_get_expected_title_back(string title, int year, string expected) + { + _series.Title = title; + _series.Year = year; + _namingConfig.StandardEpisodeFormat = "{Series CleanTitleTheWithoutYear}"; + + Subject.BuildFileName(new List { _episode }, _series, _episodeFile) + .Should().Be(expected); + } + + [Test] + public void should_not_include_0_for_year() + { + _series.Title = "The Alienist"; + _series.Year = 0; + _namingConfig.StandardEpisodeFormat = "{Series CleanTitleTheWithoutYear}"; + + Subject.BuildFileName(new List { _episode }, _series, _episodeFile) + .Should().Be("Alienist, The"); + } + } +} diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheYearFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheYearFixture.cs new file mode 100644 index 000000000..bc06d4698 --- /dev/null +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheYearFixture.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.CustomFormats; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests +{ + [TestFixture] + public class CleanTitleTheYearFixture : CoreTest + { + private Series _series; + private Episode _episode; + private EpisodeFile _episodeFile; + private NamingConfig _namingConfig; + + [SetUp] + public void Setup() + { + _series = Builder + .CreateNew() + .Build(); + + _episode = 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" }; + + _namingConfig = NamingConfig.Default; + _namingConfig.RenameEpisodes = true; + + Mocker.GetMock() + .Setup(c => c.GetConfig()).Returns(_namingConfig); + + Mocker.GetMock() + .Setup(v => v.Get(Moq.It.IsAny())) + .Returns(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v)); + + Mocker.GetMock() + .Setup(v => v.All()) + .Returns(new List()); + } + + [TestCase("The Mist", 2018, "Mist, The 2018")] + [TestCase("The Rat Pack (A&E)", 1999, "Rat Pack, The AandE 1999")] + [TestCase("The Climax: I (Almost) Got Away With It (2016)", 2016, "Climax I Almost Got Away With It, The 2016")] + [TestCase("The Climax: I (Almost) Got Away With It (2016)", 0, "Climax I Almost Got Away With It, The 2016")] + [TestCase("The Climax: I (Almost) Got Away With It", 0, "Climax I Almost Got Away With It, The")] + [TestCase("A", 2017, "A 2017")] + public void should_get_expected_title_back(string title, int year, string expected) + { + _series.Title = title; + _series.Year = year; + _namingConfig.StandardEpisodeFormat = "{Series CleanTitleTheYear}"; + + Subject.BuildFileName(new List { _episode }, _series, _episodeFile) + .Should().Be(expected); + } + + [Test] + public void should_not_include_0_for_year() + { + _series.Title = "The Alienist"; + _series.Year = 0; + _namingConfig.StandardEpisodeFormat = "{Series TitleTheYear}"; + + Subject.BuildFileName(new List { _episode }, _series, _episodeFile) + .Should().Be("Alienist, The"); + } + } +} diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 9af357cd3..65522587f 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -353,6 +353,17 @@ public static string TitleThe(string title) return TitlePrefixRegex.Replace(title, "$2, $1$3"); } + public static string CleanTitleThe(string title) + { + if (TitlePrefixRegex.IsMatch(title)) + { + var splitResult = TitlePrefixRegex.Split(title); + return $"{CleanTitle(splitResult[2]).Trim()}, {splitResult[1]}{CleanTitle(splitResult[3])}"; + } + + return CleanTitle(title); + } + public static string TitleYear(string title, int year) { // Don't use 0 for the year. @@ -370,6 +381,25 @@ public static string TitleYear(string title, int year) return $"{title} ({year})"; } + public static string CleanTitleTheYear(string title, int year) + { + // Don't use 0 for the year. + if (year == 0) + { + return CleanTitleThe(title); + } + + // Regex match incase the year in the title doesn't match the year, for whatever reason. + if (YearRegex.IsMatch(title)) + { + var splitReturn = YearRegex.Split(title); + var yearMatch = YearRegex.Match(title); + return $"{CleanTitleThe(splitReturn[0].Trim())} {yearMatch.Value[1..5]}"; + } + + return $"{CleanTitleThe(title)} {year}"; + } + public static string TitleWithoutYear(string title) { title = YearRegex.Replace(title, ""); @@ -462,13 +492,16 @@ private void AddSeriesTokens(Dictionary> tokenH { tokenHandlers["{Series Title}"] = m => series.Title; tokenHandlers["{Series CleanTitle}"] = m => CleanTitle(series.Title); + tokenHandlers["{Series TitleYear}"] = m => TitleYear(series.Title, series.Year); tokenHandlers["{Series CleanTitleYear}"] = m => CleanTitle(TitleYear(series.Title, series.Year)); + tokenHandlers["{Series TitleWithoutYear}"] = m => TitleWithoutYear(series.Title); tokenHandlers["{Series CleanTitleWithoutYear}"] = m => CleanTitle(TitleWithoutYear(series.Title)); tokenHandlers["{Series TitleThe}"] = m => TitleThe(series.Title); - tokenHandlers["{Series TitleYear}"] = m => TitleYear(series.Title, series.Year); - tokenHandlers["{Series TitleWithoutYear}"] = m => TitleWithoutYear(series.Title); + tokenHandlers["{Series CleanTitleThe}"] = m => CleanTitleThe(series.Title); tokenHandlers["{Series TitleTheYear}"] = m => TitleYear(TitleThe(series.Title), series.Year); + tokenHandlers["{Series CleanTitleTheYear}"] = m => CleanTitleTheYear(series.Title, series.Year); tokenHandlers["{Series TitleTheWithoutYear}"] = m => TitleWithoutYear(TitleThe(series.Title)); + tokenHandlers["{Series CleanTitleTheWithoutYear}"] = m => CleanTitleThe(TitleWithoutYear(series.Title)); tokenHandlers["{Series TitleFirstCharacter}"] = m => TitleFirstCharacter(TitleThe(series.Title)); tokenHandlers["{Series Year}"] = m => series.Year.ToString(); }