diff --git a/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs index 5652f3b6c..cebd69bff 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs @@ -177,6 +177,38 @@ namespace NzbDrone.Core.Test.ParserTests result.EpisodeNumbers.Should().BeNull(); } + [TestCase("[SubDESU]_High_School_DxD_07_(1280x720_x264-AAC)_[6B7FD717]", "High School DxD", 7, 0, 0)] + [TestCase("[Chihiro]_Working!!_-_06_[848x480_H.264_AAC][859EEAFA]", "Working!!", 6, 0, 0)] + [TestCase("[Commie]_Senki_Zesshou_Symphogear_-_11_[65F220B4]", "Senki_Zesshou_Symphogear", 11, 0, 0)] + [TestCase("[Underwater]_Rinne_no_Lagrange_-_12_(720p)_[5C7BC4F9]", "Rinne_no_Lagrange", 12, 0, 0)] + [TestCase("[Commie]_Rinne_no_Lagrange_-_15_[E76552EA]", "Rinne_no_Lagrange", 15, 0, 0)] + [TestCase("[HorribleSubs]_Hunter_X_Hunter_-_33_[720p]", "Hunter_X_Hunter", 33, 0, 0)] + [TestCase("[HorribleSubs]_Fairy_Tail_-_145_[720p]", "Fairy_Tail", 145, 0, 0)] + [TestCase("[HorribleSubs] Tonari no Kaibutsu-kun - 13 [1080p].mkv", "Tonari no Kaibutsu-kun", 13, 0, 0)] + [TestCase("[Doremi].Yes.Pretty.Cure.5.Go.Go!.31.[1280x720].[C65D4B1F].mkv", "Yes.Pretty.Cure.5.Go.Go!", 31, 0, 0)] + [TestCase("[K-F] One Piece 214", "One Piece", 214, 0, 0)] + [TestCase("[K-F] One Piece S10E14 214", "One Piece", 214, 10, 14)] + [TestCase("[K-F] One Piece 10x14 214", "One Piece", 214, 10, 14)] + [TestCase("[K-F] One Piece 214 10x14", "One Piece", 214, 10, 14)] + [TestCase("One Piece S10E14 214", "One Piece", 214, 10, 14)] + [TestCase("One Piece 10x14 214", "One Piece", 214, 10, 14)] + [TestCase("One Piece 214 10x14", "One Piece", 214, 10, 14)] + [TestCase("214 One Piece 10x14", "One Piece", 214, 10, 14)] + [TestCase("Bleach - 031 - The Resolution to Kill [Lunar].avi", "Bleach", 31, 0, 0)] + [TestCase("Bleach - 031 - The Resolution to Kill [Lunar]", "Bleach", 31, 0, 0)] + [TestCase("[ACX]Hack Sign 01 Role Play [Kosaka] [9C57891E].mkv", "Hack Sign", 1, 0, 0)] + [TestCase("[SFW-sage] Bakuman S3 - 12 [720p][D07C91FC]", "Bakuman S3", 12, 0, 0)] + [TestCase("ducktales_e66_time_is_money_part_one_marking_time", "DuckTales", 66, 0, 0)] + public void parse_absolute_numbers(string postTitle, string title, int absoluteEpisodeNumber, int seasonNumber, int episodeNumber) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Should().NotBeNull(); + result.AbsoluteEpisodeNumbers.First().Should().Be(absoluteEpisodeNumber); + result.SeasonNumber.Should().Be(seasonNumber); + result.EpisodeNumbers.FirstOrDefault().Should().Be(episodeNumber); + result.SeriesTitle.Should().Be(title.CleanSeriesTitle()); + } + [TestCase("Conan {year} {month} {day} Emma Roberts HDTV XviD BFF")] [TestCase("The Tonight Show With Jay Leno {year} {month} {day} 1080i HDTV DD5 1 MPEG2 TrollHD")] diff --git a/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs b/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs index 14fd53a80..48e9ec577 100644 --- a/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs @@ -11,6 +11,7 @@ namespace NzbDrone.Core.Parser.Model public QualityModel Quality { get; set; } public int SeasonNumber { get; set; } public int[] EpisodeNumbers { get; set; } + public int[] AbsoluteEpisodeNumbers { get; set; } public String AirDate { get; set; } public Language Language { get; set; } diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 9122245ab..419baa5c0 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -16,6 +16,26 @@ namespace NzbDrone.Core.Parser private static readonly Regex[] ReportTitleRegex = new[] { + //Anime - Absolute Episode Number + Title + Season+Episode + new Regex(@"^(?:(?\d{2,3})(?:_|-|\s|\.)+)+(?.+?)(?:_|-|\s|\.)+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Anime - [SubGroup] Title Absolute Episode Number + Season+Episode + new Regex(@"^(?:\[(?<subgroup>.+?)\](?:_|-|\s|\.))?(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{2,3}))+(?:_|-|\s|\.)+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Anime - [SubGroup] Title Season+Episode + Absolute Episode Number + new Regex(@"^(?:\[(?<subgroup>.+?)\](?:_|-|\s|\.))?(?<title>.+?)(?:_|-|\s|\.)+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+)(?:_|\s|\.)(?:(?<absoluteepisode>\d{2,3})(?:_|-|\s|\.|$)+)+", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Anime - [SubGroup] Title Absolute Episode Number + new Regex(@"^\[(?<subgroup>.+?)\](?:_|-|\s|\.)?(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{2,}))+", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Anime - Title Absolute Episode Number [SubGroup] + new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{2,3}))+(?:.+?)\[(?<subgroup>.+?)\](?:\.|$)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + //Episodes with airdate new Regex(@"^(?<title>.+?)?\W*(?<airyear>\d{4})\W+(?<airmonth>[0-1][0-9])\W+(?<airday>[0-3][0-9])(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled), @@ -226,25 +246,45 @@ namespace NzbDrone.Core.Parser { SeasonNumber = seasons.First(), EpisodeNumbers = new int[0], + AbsoluteEpisodeNumbers = new int[0] }; foreach (Match matchGroup in matchCollection) { var episodeCaptures = matchGroup.Groups["episode"].Captures.Cast<Capture>().ToList(); + var absoluteEpisodeCaptures = matchGroup.Groups["absoluteepisode"].Captures.Cast<Capture>().ToList(); //Allows use to return a list of 0 episodes (We can handle that as a full season release) - if (episodeCaptures.Any()) + var eps = episodeCaptures.Any(); + var epsAbs = absoluteEpisodeCaptures.Any(); + if (eps || epsAbs) { - var first = Convert.ToInt32(episodeCaptures.First().Value); - var last = Convert.ToInt32(episodeCaptures.Last().Value); - - if (first > last) + if (eps) { - return null; + var first = Convert.ToInt32(episodeCaptures.First().Value); + var last = Convert.ToInt32(episodeCaptures.Last().Value); + + if (first > last) + { + return null; + } + + var count = last - first + 1; + result.EpisodeNumbers = Enumerable.Range(first, count).ToArray(); } + if (epsAbs) + { + var first = Convert.ToInt32(absoluteEpisodeCaptures.First().Value); + var last = Convert.ToInt32(absoluteEpisodeCaptures.Last().Value); - var count = last - first + 1; - result.EpisodeNumbers = Enumerable.Range(first, count).ToArray(); + if (first > last) + { + return null; + } + + var count = last - first + 1; + result.AbsoluteEpisodeNumbers = Enumerable.Range(first, count).ToArray(); + } } else { @@ -256,6 +296,10 @@ namespace NzbDrone.Core.Parser result.FullSeason = true; } } + if (result.AbsoluteEpisodeNumbers.Any() && !result.EpisodeNumbers.Any()) + { + result.SeasonNumber = 0; + } } else