diff --git a/src/NzbDrone.Core.Test/ParserTests/PathParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/PathParserFixture.cs index 8ef926316..f2c8d2a84 100644 --- a/src/NzbDrone.Core.Test/ParserTests/PathParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/PathParserFixture.cs @@ -31,6 +31,8 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase(@"C:\Test\Series\Season 1\2 Honor Thy Developer (1080p HD).m4v", 1, 2)] [TestCase(@"C:\Test\Series\Season 2 - Total Series Action\01. Total Series Action - Episode 1 - Monster Cash.mkv", 2, 1)] [TestCase(@"C:\Test\Series\Season 2\01. Total Series Action - Episode 1 - Monster Cash.mkv", 2, 1)] + [TestCase(@"C:\Test\Series\Season 1\02.04.24 - S01E01 - The Rabbit Hole", 1, 1)] + [TestCase(@"C:\Test\Series\Season 1\8 Series Rules - S01E01 - Pilot", 1, 1)] // [TestCase(@"C:\series.state.S02E04.720p.WEB-DL.DD5.1.H.264\73696S02-04.mkv", 2, 4)] //Gets treated as S01E04 (because it gets parsed as anime); 2020-01 broken test case: Expected result.EpisodeNumbers to contain 1 item(s), but found 0 public void should_parse_from_path(string path, int season, int episode) diff --git a/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs b/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs index 3f4eeee25..f1a7bdcb4 100644 --- a/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs @@ -24,6 +24,7 @@ namespace NzbDrone.Core.Parser.Model public bool IsMultiSeason { get; set; } public bool IsSeasonExtra { get; set; } public bool IsSplitEpisode { get; set; } + public bool IsMiniSeries { get; set; } public bool Special { get; set; } public string ReleaseGroup { get; set; } public string ReleaseHash { get; set; } diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 9104af5ed..53dba36e3 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -555,7 +555,7 @@ namespace NzbDrone.Core.Parser private static readonly Regex SpecialEpisodeWordRegex = new Regex(@"\b(part|special|edition|christmas)\b\s?", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex DuplicateSpacesRegex = new Regex(@"\s{2,}", RegexOptions.Compiled); private static readonly Regex SeasonFolderRegex = new Regex(@"^(?:S|Season|Saison|Series|Stagione)[-_. ]*(?(?(?(?(?(?.+)|$)", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex RequestInfoRegex = new Regex(@"^(?:\[.+?\])+", RegexOptions.Compiled); @@ -564,31 +564,40 @@ namespace NzbDrone.Core.Parser public static ParsedEpisodeInfo ParsePath(string path) { var fileInfo = new FileInfo(path); + var result = ParseTitle(fileInfo.Name); // Parse using the folder and file separately, but combine if they both parse correctly. - var episodeNumberMatch = SimpleEpisodeNumberRegex.Matches(fileInfo.Name); + var episodeNumberMatch = SimpleEpisodeNumberRegex.Match(fileInfo.Name); - if (episodeNumberMatch.Count != 0 && fileInfo.Directory?.Name != null) + if (episodeNumberMatch.Success && fileInfo.Directory?.Name != null && (result == null || result.IsMiniSeries || result.AbsoluteEpisodeNumbers.Any())) { - var parsedFileInfo = ParseMatchCollection(episodeNumberMatch, fileInfo.Name); + var seasonMatch = SeasonFolderRegex.Match(fileInfo.Directory.Name); - if (parsedFileInfo != null) + if (seasonMatch.Success && seasonMatch.Groups["season"].Success) { - var seasonMatch = SeasonFolderRegex.Match(fileInfo.Directory.Name); + var episodeCaptures = episodeNumberMatch.Groups["episode"].Captures.Cast().ToList(); + var first = ParseNumber(episodeCaptures.First().Value); + var last = ParseNumber(episodeCaptures.Last().Value); + var pathTitle = $"S{seasonMatch.Groups["season"].Value}E{first:00}"; - if (seasonMatch.Success && seasonMatch.Groups["season"].Success) + if (first != last) { - parsedFileInfo.SeasonNumber = int.Parse(seasonMatch.Groups["season"].Value); - - Logger.Debug("Episode parsed from file and folder names. {0}", parsedFileInfo); - - return parsedFileInfo; + pathTitle += $"-E{last:00}"; } + + if (episodeNumberMatch.Groups["remaining"].Success) + { + pathTitle += $" {episodeNumberMatch.Groups["remaining"].Value}"; + } + + var parsedFileInfo = ParseTitle(pathTitle); + + Logger.Debug("Episode parsed from file and folder names. {0}", parsedFileInfo); + + return parsedFileInfo; } } - var result = ParseTitle(fileInfo.Name); - if (result == null && int.TryParse(Path.GetFileNameWithoutExtension(fileInfo.Name), out var number)) { Logger.Debug("Attempting to parse episode info using directory and file names. {0}", fileInfo.Directory.Name); @@ -1107,6 +1116,7 @@ namespace NzbDrone.Core.Parser { // 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; + result.IsMiniSeries = true; } } else