From 165e3dbae8c9ba478d4aac4bd1b8514f54f0dfec Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 29 Oct 2023 16:27:30 -0700 Subject: [PATCH] New: Parsing for titles with multiple translated titles separated by '/' Closes #6137 --- .../ParserTests/MultiEpisodeParserFixture.cs | 1 + .../ParserTests/ParserFixture.cs | 7 +++++++ .../ParserTests/SingleEpisodeParserFixture.cs | 1 + src/NzbDrone.Core/Parser/Parser.cs | 15 ++++++++++++--- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Core.Test/ParserTests/MultiEpisodeParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/MultiEpisodeParserFixture.cs index 3fbd06f34..6af9a1733 100644 --- a/src/NzbDrone.Core.Test/ParserTests/MultiEpisodeParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/MultiEpisodeParserFixture.cs @@ -75,6 +75,7 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("13 Series Se.1 afl.2-3-4 [VTM]", "13 Series", 1, new[] { 2, 3, 4 })] [TestCase("Series T Se.3 afl.3 en 4", "Series T", 3, new[] { 3, 4 })] [TestCase("Series Title (S15E06-08) City Sushi", "Series Title", 15, new[] { 6, 7, 8 })] + [TestCase("Босх: Спадок (S2E1-4) / Series: Legacy (S2E1-4) (2023) WEB-DL 1080p Ukr/Eng | sub Eng", "Series: Legacy", 2, new[] { 1, 2, 3, 4 })] // [TestCase("", "", , new [] { })] public void should_parse_multiple_episodes(string postTitle, string title, int season, int[] episodes) diff --git a/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs index 91cf0e477..db1a61d3f 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs @@ -92,5 +92,12 @@ namespace NzbDrone.Core.Test.ParserTests var result = Parser.Parser.ParseTitle(path); result.ReleaseTitle.Should().Be(releaseTitle); } + + [TestCase("Босх: Спадок (S2E1) / Series: Legacy (S2E1) (2023) WEB-DL 1080p Ukr/Eng | sub Eng", "Босх: Спадок", "Series: Legacy")] + public void should_parse_multiple_series_titles(string postTitle, params string[] titles) + { + var seriesTitleInfo = Parser.Parser.ParseTitle(postTitle).SeriesTitleInfo; + seriesTitleInfo.AllTitles.Should().BeEquivalentTo(titles); + } } } diff --git a/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs index c7f4eb389..245fee1a6 100644 --- a/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs @@ -164,6 +164,7 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("Series Title [HDTV][Cap.104](website.com).avi", "Series Title", 1, 4)] [TestCase("Series Title [HDTV][Cap.402](website.com).avi", "Series Title", 4, 2)] [TestCase("Series Title [HDTV 720p][Cap.101](website.com).mkv", "Series Title", 1, 1)] + [TestCase("Босх: Спадок (S2E1) / Series: Legacy (S2E1) (2023) WEB-DL 1080p Ukr/Eng | sub Eng", "Series: Legacy", 2, 1)] // [TestCase("", "", 0, 0)] public void should_parse_single_episode(string postTitle, string title, int seasonNumber, int episodeNumber) diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 27f98b91c..8136f0f89 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -202,7 +202,7 @@ namespace NzbDrone.Core.Parser new Regex(@"^(?.+?)(?:\W+S(?<season>(?<!\d+)(?:\d{1,2})(?!\d+))\W+(?:(?:(?:Part|Vol)\W?|(?<!\d+\W+)e)(?<seasonpart>\d{1,2}(?!\d+)))+)", RegexOptions.IgnoreCase | RegexOptions.Compiled), - // Anime - 4 digit absolute episode number + // Anime - 4 digit absolute episode number new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)(?<title>.+?)[-_. ]+?(?<absoluteepisode>\d{4}(\.\d{1,2})?(?!\d+))", RegexOptions.IgnoreCase | RegexOptions.Compiled), @@ -254,6 +254,11 @@ namespace NzbDrone.Core.Parser new Regex(@"^(?<title>.+?)[-_. ]S(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:[-_. ]?[ex]?(?<episode>(?<!\d+)\d{1,2}(?!\d+)))+", RegexOptions.IgnoreCase | RegexOptions.Compiled), + // TODO: Before this + // Single or multi episode releases with multiple titles, each followed by season and episode numbers in brackets + new Regex(@"^(?<title>.*?)[ ._]\(S(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:\W|_)?E?[ ._]?(?<episode>(?<!\d+)\d{1,2}(?!\d+))(?:-(?<episode>(?<!\d+)\d{1,2}(?!\d+)))?\)(?:[ ._]\/[ ._])(?<title>.*?)[ ._]\(", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + // Single episode season or episode S1E1 or S1-E1 or S1.Ep1 or S01.Ep.01 new Regex(@"(?:.*(?:\""|^))(?<title>.*?)(?:\W?|_)S(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:\W|_)?Ep?[ ._]?(?<episode>(?<!\d+)\d{1,2}(?!\d+))", RegexOptions.IgnoreCase | RegexOptions.Compiled), @@ -883,7 +888,7 @@ namespace NzbDrone.Core.Parser return title; } - private static SeriesTitleInfo GetSeriesTitleInfo(string title) + private static SeriesTitleInfo GetSeriesTitleInfo(string title, MatchCollection matchCollection) { var seriesTitleInfo = new SeriesTitleInfo(); seriesTitleInfo.Title = title; @@ -906,6 +911,10 @@ namespace NzbDrone.Core.Parser { seriesTitleInfo.AllTitles = matchComponents.Groups["title"].Captures.OfType<Capture>().Select(v => v.Value).ToArray(); } + else if (matchCollection[0].Groups["title"].Captures.Count > 1) + { + seriesTitleInfo.AllTitles = matchCollection[0].Groups["title"].Captures.Select(c => c.Value.Replace('.', ' ').Replace('_', ' ')).ToArray(); + } return seriesTitleInfo; } @@ -1112,7 +1121,7 @@ namespace NzbDrone.Core.Parser } result.SeriesTitle = seriesName; - result.SeriesTitleInfo = GetSeriesTitleInfo(result.SeriesTitle); + result.SeriesTitleInfo = GetSeriesTitleInfo(result.SeriesTitle, matchCollection); Logger.Debug("Episode Parsed. {0}", result);