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); .Returns(remoteEpisode);
Mocker.GetMock<IParsingService>() 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); .Returns(remoteEpisode.ParsedEpisodeInfo);
var client = new DownloadClientDefinition() var client = new DownloadClientDefinition()

View File

@ -39,5 +39,12 @@ namespace NzbDrone.Core.Test.ParserTests
{ {
Parser.Parser.ParseTitle(title).IsPossibleSpecialEpisode.Should().BeTrue(); 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() .Should()
.BeNull(); .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) 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) 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 // 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 // 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) if (parsedEpisodeInfo != null)
{ {

View File

@ -57,17 +57,29 @@ namespace NzbDrone.Core.Parser.Model
{ {
get 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() && return (AirDate.IsNullOrWhiteSpace() &&
SeriesTitle.IsNullOrWhiteSpace() && SeriesTitle.IsNullOrWhiteSpace() &&
(EpisodeNumbers.Length == 0 || SeasonNumber == 0) || (EpisodeNumbers.Length == 0 || SeasonNumber == 0) || !SeriesTitle.IsNullOrWhiteSpace() && Special) ||
!SeriesTitle.IsNullOrWhiteSpace() && Special); EpisodeNumbers.Length == 1 && EpisodeNumbers[0] == 0;
} }
//This prevents manually downloading a release from blowing up in mono //This prevents manually downloading a release from blowing up in mono
//TODO: Is there a better way? //TODO: Is there a better way?
private set {} 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() public override string ToString()
{ {

View File

@ -213,6 +213,16 @@ namespace NzbDrone.Core.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled) 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[] private static readonly Regex[] RejectHashedReleasesRegex = new Regex[]
{ {
// Generic match for md5 and mixed-case hashes. // Generic match for md5 and mixed-case hashes.
@ -456,7 +466,19 @@ namespace NzbDrone.Core.Parser
public static string NormalizeEpisodeTitle(string title) 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 = PunctuationRegex.Replace(title, " ");
title = DuplicateSpacesRegex.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 tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds); RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds);
List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null); 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 public class ParsingService : IParsingService
@ -63,7 +63,7 @@ namespace NzbDrone.Core.Parser
if (parsedEpisodeInfo == null || parsedEpisodeInfo.IsPossibleSpecialEpisode) if (parsedEpisodeInfo == null || parsedEpisodeInfo.IsPossibleSpecialEpisode)
{ {
var title = Path.GetFileNameWithoutExtension(filename); var title = Path.GetFileNameWithoutExtension(filename);
var specialEpisodeInfo = ParseSpecialEpisodeTitle(title, series); var specialEpisodeInfo = ParseSpecialEpisodeTitle(parsedEpisodeInfo, title, series);
if (specialEpisodeInfo != null) if (specialEpisodeInfo != null)
{ {
@ -184,18 +184,18 @@ namespace NzbDrone.Core.Parser
return GetStandardEpisodes(series, parsedEpisodeInfo, sceneSource, searchCriteria); 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 (searchCriteria != null)
{ {
if (tvdbId != 0 && tvdbId == searchCriteria.Series.TvdbId) 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) 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 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 // find special episode in series season 0
var episode = _episodeService.FindEpisodeByTitle(series.Id, 0, releaseTitle); var episode = _episodeService.FindEpisodeByTitle(series.Id, 0, releaseTitle);

View File

@ -102,11 +102,11 @@ namespace NzbDrone.Core.Tv
{ {
return _episodeRepository.GetEpisodes(seriesId, seasonNumber); 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 // 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 episodes = _episodeRepository.GetEpisodes(seriesId, seasonNumber);
var matches = episodes.Select( var matches = episodes.Select(
@ -222,4 +222,4 @@ namespace NzbDrone.Core.Tv
} }
} }
} }
} }