From d4167d716973efbbdeab6bce135ad4fc09216a29 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 17 Mar 2021 17:14:54 -0700 Subject: [PATCH] New: Support for using parsed season number for some anime releases without aliases Closes #4377 --- .../AbsoluteEpisodeNumberParserFixture.cs | 1 + .../ParsingServiceTests/GetEpisodesFixture.cs | 20 ++++++ src/NzbDrone.Core/Parser/Parser.cs | 67 ++++++++++--------- src/NzbDrone.Core/Parser/ParsingService.cs | 10 +++ 4 files changed, 67 insertions(+), 31 deletions(-) diff --git a/src/NzbDrone.Core.Test/ParserTests/AbsoluteEpisodeNumberParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/AbsoluteEpisodeNumberParserFixture.cs index 18bdf4663..448b1bdd4 100644 --- a/src/NzbDrone.Core.Test/ParserTests/AbsoluteEpisodeNumberParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/AbsoluteEpisodeNumberParserFixture.cs @@ -102,6 +102,7 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("[abc] Adventure Series: 30 [Web][MKV][h264][720p][AAC 2.0][abc]", "Adventure Series:", 30, 0, 0)] [TestCase("[XKsub] Series Title S2 [05][HEVC-10bit 1080p AAC][CHS&CHT&JPN]", "Series Title S2", 5, 0, 0)] [TestCase("[Cheetah-Raws] Super Long Anime - 1000 (YTV 1280x720 x264 AAC)", "Super Long Anime", 1000, 0, 0)] + [TestCase("[DameDesuYo] ReZero kara Hajimeru Isekai Seikatsu (Season 2) - 33 (1280x720 10bit EAC3) [42A12A76].mkv", "ReZero kara Hajimeru Isekai Seikatsu", 33, 2, 0)] //[TestCase("", "", 0, 0, 0)] public void should_parse_absolute_numbers(string postTitle, string title, int absoluteEpisodeNumber, int seasonNumber, int episodeNumber) { diff --git a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs index b86edceac..dee242030 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs @@ -268,6 +268,26 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests .Verify(v => v.FindEpisode(It.IsAny(), seasonNumber, It.IsAny()), Times.Never()); } + [TestCase(2)] + [TestCase(20)] + public void should_find_episode_by_parsed_season_and_absolute_episode_number_when_season_number_is_2_or_higher(int seasonNumber) + { + GivenAbsoluteNumberingSeries(); + _parsedEpisodeInfo.SeasonNumber = seasonNumber; + + Mocker.GetMock() + .Setup(s => s.FindEpisodesBySceneNumbering(It.IsAny(), seasonNumber, It.IsAny())) + .Returns(new List { _episodes.First() }); + + Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null); + + Mocker.GetMock() + .Verify(v => v.FindEpisodesBySceneNumbering(It.IsAny(), seasonNumber, It.IsAny()), Times.Once()); + + Mocker.GetMock() + .Verify(v => v.FindEpisode(It.IsAny(), seasonNumber, It.IsAny()), Times.Never()); + } + [TestCase(0)] [TestCase(1)] [TestCase(2)] diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 3932daf70..6689bc7bd 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -72,6 +72,10 @@ namespace NzbDrone.Core.Parser //Anime - [SubGroup] Title with trailing number Absolute Episode Number - Batch separated with tilde new Regex(@"^\[(?.+?)\][-_. ]?(?[^-]+?)(?:(?<![-_. ]|\b[0]\d+) - )[-_. ]?(?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+))~(?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+))(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)", RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Anime - [SubGroup] Title with season number in brackets Absolute Episode Number + new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>[^-]+?)[_. ]+?\(Season[_. ](?<season>\d+)\)[-_. ]+?(?:[-_. ]?(?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), //Anime - [SubGroup] Title with trailing number Absolute Episode Number new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>[^-]+?)(?:(?<![-_. ]|\b[0]\d+) - )(?:[-_. ]?(?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)", @@ -748,36 +752,12 @@ namespace NzbDrone.Core.Parser if (airYear < 1900) { - var seasons = new List<int>(); - - foreach (Capture seasonCapture in matchCollection[0].Groups["season"].Captures) - { - int parsedSeason; - if (int.TryParse(seasonCapture.Value, out parsedSeason)) - { - seasons.Add(parsedSeason); - - lastSeasonEpisodeStringIndex = Math.Max(lastSeasonEpisodeStringIndex, seasonCapture.EndIndex()); - } - } - - //If no season was found it should be treated as a mini series and season 1 - if (seasons.Count == 0) seasons.Add(1); - result = new ParsedEpisodeInfo - { - ReleaseTitle = releaseTitle, - SeasonNumber = seasons.First(), - EpisodeNumbers = new int[0], - AbsoluteEpisodeNumbers = new int[0] - }; - - - //If more than 1 season was parsed set IsMultiSeason to true so it can be rejected later - if (seasons.Distinct().Count() > 1) - { - result.IsMultiSeason = true; - } + { + ReleaseTitle = releaseTitle, + EpisodeNumbers = new int[0], + AbsoluteEpisodeNumbers = new int[0] + }; foreach (Match matchGroup in matchCollection) { @@ -860,9 +840,34 @@ namespace NzbDrone.Core.Parser } } - if (result.AbsoluteEpisodeNumbers.Any() && !result.EpisodeNumbers.Any()) + var seasons = new List<int>(); + + foreach (Capture seasonCapture in matchCollection[0].Groups["season"].Captures) + { + int parsedSeason; + if (int.TryParse(seasonCapture.Value, out parsedSeason)) + { + seasons.Add(parsedSeason); + + lastSeasonEpisodeStringIndex = Math.Max(lastSeasonEpisodeStringIndex, seasonCapture.EndIndex()); + } + } + + //If more than 1 season was parsed set IsMultiSeason to true so it can be rejected later + if (seasons.Distinct().Count() > 1) + { + result.IsMultiSeason = true; + } + + if (seasons.Any()) + { + // If at least one season was parsed use the first season as the season + result.SeasonNumber = seasons.First(); + } + else if (!result.AbsoluteEpisodeNumbers.Any() && result.EpisodeNumbers.Any()) { - result.SeasonNumber = 0; + // If no season was found and it's not an absolute only release it should be treated as a mini series and season 1 + result.SeasonNumber = 1; } } diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index 0078031d4..b63ecf02c 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -465,6 +465,16 @@ namespace NzbDrone.Core.Parser episodes.AddIfNotNull(episode); } } + else if (parsedEpisodeInfo.SeasonNumber > 1) + { + episodes = _episodeService.FindEpisodesBySceneNumbering(series.Id, parsedEpisodeInfo.SeasonNumber, absoluteEpisodeNumber); + + if (episodes.Empty()) + { + var episode = _episodeService.FindEpisode(series.Id, sceneSeasonNumber.Value, absoluteEpisodeNumber); + episodes.AddIfNotNull(episode); + } + } else { episodes = _episodeService.FindEpisodesBySceneNumbering(series.Id, absoluteEpisodeNumber);