From 235699070eac230a11a09698f051e597c84d3a1e Mon Sep 17 00:00:00 2001 From: Erik Frantz Date: Sun, 30 Nov 2025 16:28:27 -0600 Subject: [PATCH 01/10] Looks like TitleThe freaks out on a null string; use a wrapping method for {Movie CollectionThe} --- src/NzbDrone.Core/Organizer/FileNameBuilder.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index e8aa4b99e9..9e0d61dc53 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -211,6 +211,18 @@ public static string CleanTitle(string title) return title.RemoveDiacritics(); } + public static string CollectionTitleThe(string collectionTitle) + { + if (string.IsNullOrEmpty(collectionTitle)) + { + return string.Empty; + } + else + { + return TitleThe(collectionTitle); + } + } + public static string TitleThe(string title) { return TitlePrefixRegex.Replace(title, "$2, $1$3"); @@ -268,7 +280,7 @@ private void AddMovieTokens(Dictionary> tokenHa tokenHandlers["{Movie Certification}"] = m => movie.MovieMetadata.Value.Certification ?? string.Empty; tokenHandlers["{Movie Collection}"] = m => Truncate(movie.MovieMetadata.Value.CollectionTitle, m.CustomFormat) ?? string.Empty; - tokenHandlers["{Movie CollectionThe}"] = m => Truncate(TitleThe(movie.MovieMetadata.Value.CollectionTitle), m.CustomFormat) ?? string.Empty; + tokenHandlers["{Movie CollectionThe}"] = m => Truncate(CollectionTitleThe(movie.MovieMetadata.Value.CollectionTitle), m.CustomFormat) ?? string.Empty; } private string GetLanguageTitle(Movie movie, string isoCodes) From 6d8b9fc1127aca4d50e0d60f03c9aa31aa249ff8 Mon Sep 17 00:00:00 2001 From: Erik Frantz Date: Sun, 30 Nov 2025 18:09:05 -0600 Subject: [PATCH 02/10] Add in test for GetMovieFolderFixture --- .../OrganizerTests/GetMovieFolderFixture.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs index 35568c868f..58a828871f 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs @@ -1,3 +1,4 @@ +using System.IO; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Movies; @@ -32,5 +33,25 @@ public void should_use_movieFolderFormat_to_build_folder_name(string movieTitle, Subject.GetMovieFolder(movie).Should().Be(expected); } + + [TestCase("The Y-Women Collection", "The Y-Women 14", 2005, "{Movie CollectionThe}/{Movie TitleThe} ({Release Year})", "Y-Women Collection, The", "Y-Women 14, The (2005)")] + public void should_use_movieFolderFormat_and_CollectionFormat_to_build_folder_name(string collectionTitle, string movieTitle, int year, string format, string expectedCollection, string expectedTitle) + { + _namingConfig.MovieFolderFormat = format; + + var movie = new Movie + { + MovieMetadata = new MovieMetadata + { + CollectionTitle = collectionTitle, + Title = movieTitle, + Year = year, + }, + }; + + var result = Subject.GetMovieFolder(movie); + var expected = Path.Combine(expectedCollection, expectedTitle); + result.Should().Be(expected); + } } } From 1bb4922e7e41f7051b55e5415d30c3ec1cdd3991 Mon Sep 17 00:00:00 2001 From: Erik Frantz Date: Sun, 30 Nov 2025 22:35:28 -0600 Subject: [PATCH 03/10] Adding in another test --- src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs index 58a828871f..563cd52965 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs @@ -35,6 +35,7 @@ public void should_use_movieFolderFormat_to_build_folder_name(string movieTitle, } [TestCase("The Y-Women Collection", "The Y-Women 14", 2005, "{Movie CollectionThe}/{Movie TitleThe} ({Release Year})", "Y-Women Collection, The", "Y-Women 14, The (2005)")] + [TestCase("A Decade of Changes", "The First Year", 1980, "{Movie CollectionThe}/{Movie TitleThe} ({Release Year})", "Decade of Changes, A", "First Year, The (1980)")] public void should_use_movieFolderFormat_and_CollectionFormat_to_build_folder_name(string collectionTitle, string movieTitle, int year, string format, string expectedCollection, string expectedTitle) { _namingConfig.MovieFolderFormat = format; From c6159414ec0c57a96fdce48c889c420fb8bf711e Mon Sep 17 00:00:00 2001 From: Erik Frantz Date: Sun, 30 Nov 2025 22:37:30 -0600 Subject: [PATCH 04/10] Adding a test without `Movie CollectionThe` in it --- .../OrganizerTests/GetMovieFolderFixture.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs index 563cd52965..75809ed7fe 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs @@ -36,6 +36,7 @@ public void should_use_movieFolderFormat_to_build_folder_name(string movieTitle, [TestCase("The Y-Women Collection", "The Y-Women 14", 2005, "{Movie CollectionThe}/{Movie TitleThe} ({Release Year})", "Y-Women Collection, The", "Y-Women 14, The (2005)")] [TestCase("A Decade of Changes", "The First Year", 1980, "{Movie CollectionThe}/{Movie TitleThe} ({Release Year})", "Decade of Changes, A", "First Year, The (1980)")] + [TestCase(null, "Just a Movie", 1999, "{Movie Title} ({Release Year})", null, "Just a Movie (1999)")] public void should_use_movieFolderFormat_and_CollectionFormat_to_build_folder_name(string collectionTitle, string movieTitle, int year, string format, string expectedCollection, string expectedTitle) { _namingConfig.MovieFolderFormat = format; @@ -51,7 +52,9 @@ public void should_use_movieFolderFormat_and_CollectionFormat_to_build_folder_na }; var result = Subject.GetMovieFolder(movie); - var expected = Path.Combine(expectedCollection, expectedTitle); + var expected = !string.IsNullOrWhiteSpace(expectedCollection) + ? Path.Combine(expectedCollection, expectedTitle) + : expectedTitle; result.Should().Be(expected); } } From 8fedf79c79b78ef4796fc59588596435dbae6b9b Mon Sep 17 00:00:00 2001 From: Erik Frantz Date: Sun, 30 Nov 2025 22:39:10 -0600 Subject: [PATCH 05/10] Adding in a test containing `Movie CollectionThe` without a collection for the movie --- src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs index 75809ed7fe..04423c209b 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs @@ -37,6 +37,7 @@ public void should_use_movieFolderFormat_to_build_folder_name(string movieTitle, [TestCase("The Y-Women Collection", "The Y-Women 14", 2005, "{Movie CollectionThe}/{Movie TitleThe} ({Release Year})", "Y-Women Collection, The", "Y-Women 14, The (2005)")] [TestCase("A Decade of Changes", "The First Year", 1980, "{Movie CollectionThe}/{Movie TitleThe} ({Release Year})", "Decade of Changes, A", "First Year, The (1980)")] [TestCase(null, "Just a Movie", 1999, "{Movie Title} ({Release Year})", null, "Just a Movie (1999)")] + [TestCase(null, "Collectionless Slop", 1949, "{Movie CollectionThe}/{Movie TitleThe} ({Release Year})", null, "Collectionless Slop (1949)")] public void should_use_movieFolderFormat_and_CollectionFormat_to_build_folder_name(string collectionTitle, string movieTitle, int year, string format, string expectedCollection, string expectedTitle) { _namingConfig.MovieFolderFormat = format; From 24388c3c8129bd3b1ae71bfe23579aa9626cd924 Mon Sep 17 00:00:00 2001 From: Erik Frantz Date: Mon, 1 Dec 2025 10:17:38 -0600 Subject: [PATCH 06/10] Just update the `TitleThe` method per PR feedback --- src/NzbDrone.Core/Organizer/FileNameBuilder.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 9e0d61dc53..a89ce82f69 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -211,23 +211,18 @@ public static string CleanTitle(string title) return title.RemoveDiacritics(); } - public static string CollectionTitleThe(string collectionTitle) + public static string TitleThe(string title) { - if (string.IsNullOrEmpty(collectionTitle)) + if (string.IsNullOrWhiteSpace(title)) { return string.Empty; } else { - return TitleThe(collectionTitle); + return TitlePrefixRegex.Replace(title, "$2, $1$3"); } } - public static string TitleThe(string title) - { - return TitlePrefixRegex.Replace(title, "$2, $1$3"); - } - public static string CleanTitleThe(string title) { if (TitlePrefixRegex.IsMatch(title)) @@ -280,7 +275,7 @@ private void AddMovieTokens(Dictionary> tokenHa tokenHandlers["{Movie Certification}"] = m => movie.MovieMetadata.Value.Certification ?? string.Empty; tokenHandlers["{Movie Collection}"] = m => Truncate(movie.MovieMetadata.Value.CollectionTitle, m.CustomFormat) ?? string.Empty; - tokenHandlers["{Movie CollectionThe}"] = m => Truncate(CollectionTitleThe(movie.MovieMetadata.Value.CollectionTitle), m.CustomFormat) ?? string.Empty; + tokenHandlers["{Movie CollectionThe}"] = m => Truncate(TitleThe(movie.MovieMetadata.Value.CollectionTitle), m.CustomFormat) ?? string.Empty; } private string GetLanguageTitle(Movie movie, string isoCodes) From 8b0124c716f71a30dc9445282618ac0db94b5d75 Mon Sep 17 00:00:00 2001 From: Erik Frantz <39980629+BardezAnAvatar@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:35:10 -0600 Subject: [PATCH 07/10] Remove redundant else Co-authored-by: Bogdan --- src/NzbDrone.Core/Organizer/FileNameBuilder.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index a89ce82f69..b4fd2476d2 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -217,10 +217,8 @@ public static string TitleThe(string title) { return string.Empty; } - else - { - return TitlePrefixRegex.Replace(title, "$2, $1$3"); - } + + return TitlePrefixRegex.Replace(title, "$2, $1$3"); } public static string CleanTitleThe(string title) From eaa779033fc75b693bf5efc945a1d24ded3c69db Mon Sep 17 00:00:00 2001 From: Erik Frantz Date: Tue, 2 Dec 2025 22:44:50 -0600 Subject: [PATCH 08/10] New: Add missing `{Movie CleanCollectionThe}` token --- .../MediaManagement/Naming/NamingModal.tsx | 7 +- .../CleanCollectionTheFixture.cs | 91 +++++++++++++++++++ .../CleanTitleTheFixture.cs | 1 + .../Organizer/FileNameBuilder.cs | 6 ++ 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanCollectionTheFixture.cs diff --git a/frontend/src/Settings/MediaManagement/Naming/NamingModal.tsx b/frontend/src/Settings/MediaManagement/Naming/NamingModal.tsx index 44debc2a8c..7016ac4fd7 100644 --- a/frontend/src/Settings/MediaManagement/Naming/NamingModal.tsx +++ b/frontend/src/Settings/MediaManagement/Naming/NamingModal.tsx @@ -116,7 +116,12 @@ const movieTokens = [ }, { token: '{Movie CollectionThe}', - example: 'Movie Collection, The', + example: "Movie's Collection, The", + footNotes: '1', + }, + { + token: '{Movie CleanCollectionThe}', + example: 'Movies CleanCollection, The', footNotes: '1', }, { token: '{Movie Certification}', example: 'R' }, diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanCollectionTheFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanCollectionTheFixture.cs new file mode 100644 index 0000000000..1670325fb4 --- /dev/null +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanCollectionTheFixture.cs @@ -0,0 +1,91 @@ +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.Movies; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests +{ + [TestFixture] + public class CleanCollectionTheFixture : CoreTest + { + private Movie _movie; + private MovieFile _movieFile; + private NamingConfig _namingConfig; + + [SetUp] + public void Setup() + { + _movie = Builder + .CreateNew() + .With(e => e.Title = "Movie Title") + .Build(); + + _movieFile = new MovieFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "RadarrTest" }; + + _namingConfig = NamingConfig.Default; + _namingConfig.RenameMovies = 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 Badger's Collection", "Badgers Collection, The")] + [TestCase("@ The Movies Collection", "@ The Movies Collection")] // This doesn't seem right; see: FileNameBuilder.ScenifyRemoveChars, looks like it has the "at sign" in the regex + [TestCase("A Stupid/Idiotic Collection", "Stupid Idiotic Collection, A")] + [TestCase("An Astounding & Amazing Collection", "Astounding and Amazing Collection, An")] + [TestCase("The Amazing Animal-Hero's Collection (2001)", "Amazing Animal-Heros Collection, The 2001")] + [TestCase("A Different Movië (AU)", "Different Movie, A AU")] + [TestCase("The Repairër (ZH) (2015)", "Repairer, The ZH 2015")] + [TestCase("The Eighth Sensë 2 (Thai)", "Eighth Sense 2, The Thai")] + [TestCase("The Astonishing Jæg (Latin America)", "Astonishing Jaeg, The Latin America")] + [TestCase("The Hampster Pack (B&F)", "Hampster Pack, The BandF")] + [TestCase("The Gásm: I (Almost) Got Away With It (1900)", "Gasm I Almost Got Away With It, The 1900")] + [TestCase(null, "")] + public void should_get_expected_title_back(string collection, string expected) + { + SetCollectionName(_movie, collection); + _namingConfig.StandardMovieFormat = "{Movie CleanCollectionThe}"; + + Subject.BuildFileName(_movie, _movieFile) + .Should().Be(expected); + } + + [TestCase("A")] + [TestCase("Anne")] + [TestCase("Theodore")] + [TestCase("3%")] + public void should_not_change_title(string collection) + { + SetCollectionName(_movie, collection); + _namingConfig.StandardMovieFormat = "{Movie CleanCollectionThe}"; + + Subject.BuildFileName(_movie, _movieFile) + .Should().Be(collection); + } + + private void SetCollectionName(Movie movie, string collectionName) + { + var metadata = new MovieMetadata() + { + CollectionTitle = collectionName, + }; + movie.MovieMetadata = new Core.Datastore.LazyLoaded(metadata); + movie.MovieMetadata.Value.CollectionTitle = collectionName; + } + } +} diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheFixture.cs index 9a2a2b7cc0..2ce6ad1d26 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleTheFixture.cs @@ -53,6 +53,7 @@ public void Setup() [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")] + [TestCase(null, "")] public void should_get_expected_title_back(string title, string expected) { _movie.Title = title; diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index b4fd2476d2..24d69c7cb4 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -223,6 +223,11 @@ public static string TitleThe(string title) public static string CleanTitleThe(string title) { + if (string.IsNullOrWhiteSpace(title)) + { + return string.Empty; + } + if (TitlePrefixRegex.IsMatch(title)) { var splitResult = TitlePrefixRegex.Split(title); @@ -274,6 +279,7 @@ private void AddMovieTokens(Dictionary> tokenHa tokenHandlers["{Movie Certification}"] = m => movie.MovieMetadata.Value.Certification ?? string.Empty; tokenHandlers["{Movie Collection}"] = m => Truncate(movie.MovieMetadata.Value.CollectionTitle, m.CustomFormat) ?? string.Empty; tokenHandlers["{Movie CollectionThe}"] = m => Truncate(TitleThe(movie.MovieMetadata.Value.CollectionTitle), m.CustomFormat) ?? string.Empty; + tokenHandlers["{Movie CleanCollectionThe}"] = m => Truncate(CleanTitleThe(movie.MovieMetadata.Value.CollectionTitle), m.CustomFormat) ?? string.Empty; } private string GetLanguageTitle(Movie movie, string isoCodes) From b2fab11b92a90cdfb114622d0b570fa945d6e2fc Mon Sep 17 00:00:00 2001 From: Erik Frantz Date: Tue, 2 Dec 2025 22:47:42 -0600 Subject: [PATCH 09/10] Clean up the example --- frontend/src/Settings/MediaManagement/Naming/NamingModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Settings/MediaManagement/Naming/NamingModal.tsx b/frontend/src/Settings/MediaManagement/Naming/NamingModal.tsx index 7016ac4fd7..f80a2e4ba1 100644 --- a/frontend/src/Settings/MediaManagement/Naming/NamingModal.tsx +++ b/frontend/src/Settings/MediaManagement/Naming/NamingModal.tsx @@ -121,7 +121,7 @@ const movieTokens = [ }, { token: '{Movie CleanCollectionThe}', - example: 'Movies CleanCollection, The', + example: 'Movies Collection, The', footNotes: '1', }, { token: '{Movie Certification}', example: 'R' }, From 45270b706ecc5be6b090131e3ee0f33975b61682 Mon Sep 17 00:00:00 2001 From: Erik Frantz Date: Tue, 2 Dec 2025 22:52:25 -0600 Subject: [PATCH 10/10] Add a GetMovieFolder test for `Movie CleanCollectionThe` --- src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs index 04423c209b..c244da444e 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/GetMovieFolderFixture.cs @@ -35,7 +35,7 @@ public void should_use_movieFolderFormat_to_build_folder_name(string movieTitle, } [TestCase("The Y-Women Collection", "The Y-Women 14", 2005, "{Movie CollectionThe}/{Movie TitleThe} ({Release Year})", "Y-Women Collection, The", "Y-Women 14, The (2005)")] - [TestCase("A Decade of Changes", "The First Year", 1980, "{Movie CollectionThe}/{Movie TitleThe} ({Release Year})", "Decade of Changes, A", "First Year, The (1980)")] + [TestCase("A Decade's Worth of Changes", "The First Year", 1980, "{Movie CleanCollectionThe}/{Movie TitleThe} ({Release Year})", "Decades Worth of Changes, A", "First Year, The (1980)")] [TestCase(null, "Just a Movie", 1999, "{Movie Title} ({Release Year})", null, "Just a Movie (1999)")] [TestCase(null, "Collectionless Slop", 1949, "{Movie CollectionThe}/{Movie TitleThe} ({Release Year})", null, "Collectionless Slop (1949)")] public void should_use_movieFolderFormat_and_CollectionFormat_to_build_folder_name(string collectionTitle, string movieTitle, int year, string format, string expectedCollection, string expectedTitle)