New: Consider all scene SxxE00 releases Specials as well.

* New: Consider all scene SxxE00 releases Specials as well.

* Cleanup special episode titles and handle E00 specials with existing scenemappings.

* Moved handling of scene mapped E00 to central function.
This commit is contained in:
Taloth 2018-01-04 21:49:16 +01:00 committed by GitHub
parent ad10349878
commit d0e8aef949
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 94 additions and 18 deletions

View File

@ -104,7 +104,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
.Returns(remoteEpisode);
Mocker.GetMock<IParsingService>()
.Setup(s => s.ParseSpecialEpisodeTitle(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Setup(s => s.ParseSpecialEpisodeTitle(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(remoteEpisode.ParsedEpisodeInfo);
var client = new DownloadClientDefinition()

View File

@ -39,5 +39,12 @@ namespace NzbDrone.Core.Test.ParserTests
{
Parser.Parser.ParseTitle(title).IsPossibleSpecialEpisode.Should().BeTrue();
}
[TestCase("Dr.S11E00.A.Christmas.Carol.Special.720p.HDTV-FieldOfView")]
public void IsPossibleSpecialEpisode_should_be_true_if_e00_special(string title)
{
Parser.Parser.ParseTitle(title).IsPossibleSpecialEpisode.Should().BeTrue();
}
}
}

View File

@ -67,5 +67,31 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeServiceTests
.Should()
.BeNull();
}
[Test]
public void should_handle_e00_specials()
{
const string expectedTitle = "Inside The Walking Dead: Walker University";
GivenEpisodesWithTitles("Inside The Walking Dead", expectedTitle, "Inside The Walking Dead Walker University 2");
Subject.FindEpisodeByTitle(1, 1, "The.Walking.Dead.S04E00.Inside.The.Walking.Dead.Walker.University.720p.HDTV.x264-W4F")
.Title
.Should()
.Be(expectedTitle);
}
[TestCase("Dead.Man.Walking.S04E00.Inside.The.Walking.Dead.Walker.University.720p.HDTV.x264-W4F", "Inside The Walking Dead: Walker University", new[] { "Inside The Walking Dead", "Inside The Walking Dead Walker University 2" })]
[TestCase("Who.1999.S11E00.Twice.Upon.A.Time.1080p.AMZN.WEB-DL.DDP5.1.H.264-NTb", "Twice Upon A Time", new[] { "Last Christmas" })]
[TestCase("Who.1999.S11E00.Twice.Upon.A.Time.Christmas.Special.720p.HDTV.x264-FoV", "Twice Upon A Time", new[] { "Last Christmas" })]
[TestCase("Who.1999.S10E00.Christmas.Special.The.Return.Of.Doctor.Mysterio.1080p.BluRay.x264-OUIJA", "The Return Of Doctor Mysterio", new[] { "Doctor Mysterio" })]
public void should_handle_special(string releaseTitle, string expectedTitle, string[] rejectedTitles)
{
GivenEpisodesWithTitles(rejectedTitles.Concat(new[] { expectedTitle }).ToArray());
var episode = Subject.FindEpisodeByTitle(1, 0, releaseTitle);
episode.Should().NotBeNull();
episode.Title.Should().Be(expectedTitle);
}
}
}

View File

@ -65,7 +65,7 @@ namespace NzbDrone.Core.DecisionEngine
if (parsedEpisodeInfo == null || parsedEpisodeInfo.IsPossibleSpecialEpisode)
{
var specialEpisodeInfo = _parsingService.ParseSpecialEpisodeTitle(report.Title, report.TvdbId, report.TvRageId, searchCriteria);
var specialEpisodeInfo = _parsingService.ParseSpecialEpisodeTitle(parsedEpisodeInfo, report.Title, report.TvdbId, report.TvRageId, searchCriteria);
if (specialEpisodeInfo != null)
{

View File

@ -78,7 +78,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
{
// Try parsing the original source title and if that fails, try parsing it as a special
// TODO: Pass the TVDB ID and TVRage IDs in as well so we have a better chance for finding the item
parsedEpisodeInfo = Parser.Parser.ParseTitle(firstHistoryItem.SourceTitle) ?? _parsingService.ParseSpecialEpisodeTitle(firstHistoryItem.SourceTitle, 0, 0);
parsedEpisodeInfo = Parser.Parser.ParseTitle(firstHistoryItem.SourceTitle) ?? _parsingService.ParseSpecialEpisodeTitle(parsedEpisodeInfo, firstHistoryItem.SourceTitle, 0, 0);
if (parsedEpisodeInfo != null)
{

View File

@ -57,17 +57,29 @@ namespace NzbDrone.Core.Parser.Model
{
get
{
// if we don't have eny episode numbers we are likely a special episode and need to do a search by episode title
// if we don't have any episode numbers we are likely a special episode and need to do a search by episode title
return (AirDate.IsNullOrWhiteSpace() &&
SeriesTitle.IsNullOrWhiteSpace() &&
(EpisodeNumbers.Length == 0 || SeasonNumber == 0) ||
!SeriesTitle.IsNullOrWhiteSpace() && Special);
(EpisodeNumbers.Length == 0 || SeasonNumber == 0) || !SeriesTitle.IsNullOrWhiteSpace() && Special) ||
EpisodeNumbers.Length == 1 && EpisodeNumbers[0] == 0;
}
//This prevents manually downloading a release from blowing up in mono
//TODO: Is there a better way?
private set {}
}
public bool IsPossibleSceneSeasonSpecial
{
get
{
// SxxE00 episodes
return SeasonNumber != 0 && EpisodeNumbers.Length == 1 && EpisodeNumbers[0] == 0;
}
//This prevents manually downloading a release from blowing up in mono
//TODO: Is there a better way?
private set { }
}
public override string ToString()
{

View File

@ -213,6 +213,16 @@ namespace NzbDrone.Core.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled)
};
private static readonly Regex[] SpecialEpisodeTitleRegex = new Regex[]
{
new Regex(@"\.S\d+E00\.(?<episodetitle>.+?)(?:\.(?:720p|1080p|HDTV|WEB|WEBRip|WEB-DL)\.|$)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
new Regex(@"\.S\d+\.Special\.(?<episodetitle>.+?)(?:\.(?:720p|1080p|HDTV|WEB|WEBRip|WEB-DL)\.|$)",
RegexOptions.IgnoreCase | RegexOptions.Compiled)
};
private static readonly Regex[] RejectHashedReleasesRegex = new Regex[]
{
// Generic match for md5 and mixed-case hashes.
@ -456,7 +466,19 @@ namespace NzbDrone.Core.Parser
public static string NormalizeEpisodeTitle(string title)
{
title = SpecialEpisodeWordRegex.Replace(title, string.Empty);
var match = SpecialEpisodeTitleRegex
.Select(v => v.Match(title))
.Where(v => v.Success)
.FirstOrDefault();
if (match != null)
{
title = match.Groups["episodetitle"].Value;
}
// Disabled, Until we run into specific testcases for the removal of these words.
//title = SpecialEpisodeWordRegex.Replace(title, string.Empty);
title = PunctuationRegex.Replace(title, " ");
title = DuplicateSpacesRegex.Replace(title, " ");

View File

@ -19,7 +19,7 @@ namespace NzbDrone.Core.Parser
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds);
List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null);
ParsedEpisodeInfo ParseSpecialEpisodeTitle(string releaseTitle, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
ParsedEpisodeInfo ParseSpecialEpisodeTitle(ParsedEpisodeInfo parsedEpisodeInfo, string releaseTitle, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
}
public class ParsingService : IParsingService
@ -63,7 +63,7 @@ namespace NzbDrone.Core.Parser
if (parsedEpisodeInfo == null || parsedEpisodeInfo.IsPossibleSpecialEpisode)
{
var title = Path.GetFileNameWithoutExtension(filename);
var specialEpisodeInfo = ParseSpecialEpisodeTitle(title, series);
var specialEpisodeInfo = ParseSpecialEpisodeTitle(parsedEpisodeInfo, title, series);
if (specialEpisodeInfo != null)
{
@ -184,18 +184,18 @@ namespace NzbDrone.Core.Parser
return GetStandardEpisodes(series, parsedEpisodeInfo, sceneSource, searchCriteria);
}
public ParsedEpisodeInfo ParseSpecialEpisodeTitle(string releaseTitle, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null)
public ParsedEpisodeInfo ParseSpecialEpisodeTitle(ParsedEpisodeInfo parsedEpisodeInfo, string releaseTitle, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null)
{
if (searchCriteria != null)
{
if (tvdbId != 0 && tvdbId == searchCriteria.Series.TvdbId)
{
return ParseSpecialEpisodeTitle(releaseTitle, searchCriteria.Series);
return ParseSpecialEpisodeTitle(parsedEpisodeInfo, releaseTitle, searchCriteria.Series);
}
if (tvRageId != 0 && tvRageId == searchCriteria.Series.TvRageId)
{
return ParseSpecialEpisodeTitle(releaseTitle, searchCriteria.Series);
return ParseSpecialEpisodeTitle(parsedEpisodeInfo, releaseTitle, searchCriteria.Series);
}
}
@ -222,11 +222,20 @@ namespace NzbDrone.Core.Parser
return null;
}
return ParseSpecialEpisodeTitle(releaseTitle, series);
return ParseSpecialEpisodeTitle(parsedEpisodeInfo, releaseTitle, series);
}
private ParsedEpisodeInfo ParseSpecialEpisodeTitle(string releaseTitle, Series series)
private ParsedEpisodeInfo ParseSpecialEpisodeTitle(ParsedEpisodeInfo parsedEpisodeInfo, string releaseTitle, Series series)
{
// SxxE00 episodes are sometimes mapped via TheXEM, don't use episode title parsing in that case.
if (parsedEpisodeInfo != null && parsedEpisodeInfo.IsPossibleSceneSeasonSpecial && series.UseSceneNumbering)
{
if (_episodeService.FindEpisodesBySceneNumbering(series.Id, parsedEpisodeInfo.SeasonNumber, 0).Any())
{
return parsedEpisodeInfo;
}
}
// find special episode in series season 0
var episode = _episodeService.FindEpisodeByTitle(series.Id, 0, releaseTitle);

View File

@ -102,11 +102,11 @@ namespace NzbDrone.Core.Tv
{
return _episodeRepository.GetEpisodes(seriesId, seasonNumber);
}
public Episode FindEpisodeByTitle(int seriesId, int seasonNumber, string releaseTitle)
public Episode FindEpisodeByTitle(int seriesId, int seasonNumber, string releaseTitle)
{
// TODO: can replace this search mechanism with something smarter/faster/better
var normalizedReleaseTitle = Parser.Parser.NormalizeEpisodeTitle(releaseTitle).Replace(".", " ");
var normalizedReleaseTitle = Parser.Parser.NormalizeEpisodeTitle(releaseTitle);
var episodes = _episodeRepository.GetEpisodes(seriesId, seasonNumber);
var matches = episodes.Select(
@ -222,4 +222,4 @@ namespace NzbDrone.Core.Tv
}
}
}
}
}