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:
parent
ad10349878
commit
d0e8aef949
|
@ -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()
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
@ -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, " ");
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue