mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-23 14:55:20 -07:00
Added: More detailed descriptions why a movie was not able to be mapped. (#1696)
Added: Option to make mapping more lenient. This should practically allow all movies to be correctly mapped. Though it also opens the path for movies being wrongly mapped! (So it is a toggable option) Added: Improved edition parsing. Now almost all releases should have the correct edition, even ones with no year, etc.
This commit is contained in:
parent
d6cf53e12c
commit
6d033c57f4
13 changed files with 415 additions and 130 deletions
|
@ -19,6 +19,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
{
|
{
|
||||||
private List<ReleaseInfo> _reports;
|
private List<ReleaseInfo> _reports;
|
||||||
private RemoteMovie _remoteEpisode;
|
private RemoteMovie _remoteEpisode;
|
||||||
|
private MappingResult _mappingResult;
|
||||||
|
|
||||||
private Mock<IDecisionEngineSpecification> _pass1;
|
private Mock<IDecisionEngineSpecification> _pass1;
|
||||||
private Mock<IDecisionEngineSpecification> _pass2;
|
private Mock<IDecisionEngineSpecification> _pass2;
|
||||||
|
@ -50,11 +51,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
_reports = new List<ReleaseInfo> { new ReleaseInfo { Title = "Trolls.2016.720p.WEB-DL.DD5.1.H264-FGT" } };
|
_reports = new List<ReleaseInfo> { new ReleaseInfo { Title = "Trolls.2016.720p.WEB-DL.DD5.1.H264-FGT" } };
|
||||||
_remoteEpisode = new RemoteMovie {
|
_remoteEpisode = new RemoteMovie {
|
||||||
Movie = new Movie(),
|
Movie = new Movie(),
|
||||||
|
ParsedMovieInfo = new ParsedMovieInfo()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_mappingResult = new MappingResult {Movie = new Movie(), MappingResultType = MappingResultType.Success};
|
||||||
|
_mappingResult.RemoteMovie = _remoteEpisode;
|
||||||
|
|
||||||
|
|
||||||
Mocker.GetMock<IParsingService>()
|
Mocker.GetMock<IParsingService>()
|
||||||
.Setup(c => c.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), It.IsAny<SearchCriteriaBase>()))
|
.Setup(c => c.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), It.IsAny<SearchCriteriaBase>())).Returns(_mappingResult);
|
||||||
.Returns(_remoteEpisode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenSpecifications(params Mock<IDecisionEngineSpecification>[] mocks)
|
private void GivenSpecifications(params Mock<IDecisionEngineSpecification>[] mocks)
|
||||||
|
@ -121,6 +126,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
{
|
{
|
||||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||||
_reports[0].Title = "Not parsable";
|
_reports[0].Title = "Not parsable";
|
||||||
|
_mappingResult.MappingResultType = MappingResultType.NotParsable;
|
||||||
|
|
||||||
var results = Subject.GetRssDecision(_reports).ToList();
|
var results = Subject.GetRssDecision(_reports).ToList();
|
||||||
|
|
||||||
|
@ -130,7 +136,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
_pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null), Times.Never());
|
_pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null), Times.Never());
|
||||||
_pass3.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null), Times.Never());
|
_pass3.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null), Times.Never());
|
||||||
|
|
||||||
results.Should().BeEmpty();
|
results.Should().NotBeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -138,6 +144,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
{
|
{
|
||||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||||
_reports[0].Title = "1937 - Snow White and the Seven Dwarves";
|
_reports[0].Title = "1937 - Snow White and the Seven Dwarves";
|
||||||
|
_mappingResult.MappingResultType = MappingResultType.NotParsable;
|
||||||
|
|
||||||
var results = Subject.GetRssDecision(_reports).ToList();
|
var results = Subject.GetRssDecision(_reports).ToList();
|
||||||
|
|
||||||
|
@ -147,7 +154,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
_pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null), Times.Never());
|
_pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null), Times.Never());
|
||||||
_pass3.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null), Times.Never());
|
_pass3.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null), Times.Never());
|
||||||
|
|
||||||
results.Should().BeEmpty();
|
results.Should().NotBeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -156,6 +163,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||||
|
|
||||||
_remoteEpisode.Movie = null;
|
_remoteEpisode.Movie = null;
|
||||||
|
_mappingResult.MappingResultType = MappingResultType.TitleNotFound;
|
||||||
|
|
||||||
Subject.GetRssDecision(_reports);
|
Subject.GetRssDecision(_reports);
|
||||||
|
|
||||||
|
@ -249,6 +257,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||||
|
|
||||||
_remoteEpisode.Movie = null;
|
_remoteEpisode.Movie = null;
|
||||||
|
_mappingResult.MappingResultType = MappingResultType.TitleNotFound;
|
||||||
|
|
||||||
var result = Subject.GetRssDecision(_reports);
|
var result = Subject.GetRssDecision(_reports);
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
||||||
|
|
||||||
Mocker.GetMock<IParsingService>()
|
Mocker.GetMock<IParsingService>()
|
||||||
.Setup(s => s.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), (SearchCriteriaBase)null))
|
.Setup(s => s.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), (SearchCriteriaBase)null))
|
||||||
.Returns(() => CreateRemoteMovie());
|
.Returns(() => new MappingResult{RemoteMovie = CreateRemoteMovie(), MappingResultType = MappingResultType.Success});
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>()
|
Mocker.GetMock<IHttpClient>()
|
||||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||||
|
|
|
@ -50,7 +50,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
|
||||||
|
|
||||||
Mocker.GetMock<IParsingService>()
|
Mocker.GetMock<IParsingService>()
|
||||||
.Setup(s => s.Map(It.Is<ParsedMovieInfo>(i => i.MovieTitle == "A Movie"), It.IsAny<string>(), null))
|
.Setup(s => s.Map(It.Is<ParsedMovieInfo>(i => i.MovieTitle == "A Movie"), It.IsAny<string>(), null))
|
||||||
.Returns(remoteEpisode);
|
.Returns(new MappingResult{RemoteMovie = remoteEpisode});
|
||||||
|
|
||||||
var client = new DownloadClientDefinition()
|
var client = new DownloadClientDefinition()
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,6 +14,7 @@ using NzbDrone.Test.Common.Categories;
|
||||||
namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
|
namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
|
||||||
{
|
{
|
||||||
[IntegrationTest]
|
[IntegrationTest]
|
||||||
|
[Ignore("Nyaa is down!")]
|
||||||
public class IndexerIntegrationTests : CoreTest
|
public class IndexerIntegrationTests : CoreTest
|
||||||
{
|
{
|
||||||
private SingleEpisodeSearchCriteria _singleSearchCriteria;
|
private SingleEpisodeSearchCriteria _singleSearchCriteria;
|
||||||
|
|
|
@ -82,6 +82,8 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||||
[TestCase("Valana la Legende FRENCH BluRay 720p 2016 kjhlj", "Valana la Legende")]
|
[TestCase("Valana la Legende FRENCH BluRay 720p 2016 kjhlj", "Valana la Legende")]
|
||||||
[TestCase("Valana la Legende TRUEFRENCH BluRay 720p 2016 kjhlj", "Valana la Legende")]
|
[TestCase("Valana la Legende TRUEFRENCH BluRay 720p 2016 kjhlj", "Valana la Legende")]
|
||||||
[TestCase("Mission Impossible: Rogue Nation (2015)<29>[XviD - Ita Ac3 - SoftSub Ita]azione, spionaggio, thriller *Prima Visione* Team mulnic Tom Cruise", "Mission Impossible Rogue Nation")]
|
[TestCase("Mission Impossible: Rogue Nation (2015)<29>[XviD - Ita Ac3 - SoftSub Ita]azione, spionaggio, thriller *Prima Visione* Team mulnic Tom Cruise", "Mission Impossible Rogue Nation")]
|
||||||
|
[TestCase("Scary.Movie.2000.FRENCH..BluRay.-AiRLiNE", "Scary Movie")]
|
||||||
|
[TestCase("My Movie 1999 German Bluray", "My Movie")]
|
||||||
public void should_parse_movie_title(string postTitle, string title)
|
public void should_parse_movie_title(string postTitle, string title)
|
||||||
{
|
{
|
||||||
Parser.Parser.ParseMovieTitle(postTitle, true).MovieTitle.Should().Be(title);
|
Parser.Parser.ParseMovieTitle(postTitle, true).MovieTitle.Should().Be(title);
|
||||||
|
@ -135,9 +137,20 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||||
[TestCase("Movie IMAX 2012.mkv", "IMAX")]
|
[TestCase("Movie IMAX 2012.mkv", "IMAX")]
|
||||||
[TestCase("Fake Movie Final Cut 2016", "Final Cut")]
|
[TestCase("Fake Movie Final Cut 2016", "Final Cut")]
|
||||||
[TestCase("Fake Movie 2016 Final Cut ", "Final Cut")]
|
[TestCase("Fake Movie 2016 Final Cut ", "Final Cut")]
|
||||||
|
[TestCase("My Movie GERMAN Extended Cut 2016", "Extended Cut")]
|
||||||
|
[TestCase("My.Movie.GERMAN.Extended.Cut.2016", "Extended Cut")]
|
||||||
|
[TestCase("My.Movie.GERMAN.Extended.Cut", "Extended Cut")]
|
||||||
|
[TestCase("Mission Impossible: Rogue Nation 2012 Bluray", "")]
|
||||||
public void should_parse_edition(string postTitle, string edition)
|
public void should_parse_edition(string postTitle, string edition)
|
||||||
{
|
{
|
||||||
Parser.Parser.ParseMovieTitle(postTitle, false).Edition.Should().Be(edition);
|
Parser.Parser.ParseMovieTitle(postTitle, true).Edition.Should().Be(edition);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("The Lord of the Rings The Fellowship of the Ring (Extended Edition) 1080p BD25", "The Lord Of The Rings The Fellowship Of The Ring", "Extended Edition")]
|
||||||
|
[TestCase("The.Lord.of.the.Rings.The.Fellowship.of.the.Ring.(Extended.Edition).1080p.BD25", "The Lord Of The Rings The Fellowship Of The Ring", "Extended Edition")]
|
||||||
|
public void should_parse_edition_lenient_mapping(string postTitle, string foundTitle, string edition)
|
||||||
|
{
|
||||||
|
Parser.Parser.ParseMinimalMovieTitle(postTitle, foundTitle, 1290).Edition.Should().Be(edition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||||
private Movie _movie;
|
private Movie _movie;
|
||||||
private ParsedMovieInfo _parsedMovieInfo;
|
private ParsedMovieInfo _parsedMovieInfo;
|
||||||
private ParsedMovieInfo _wrongYearInfo;
|
private ParsedMovieInfo _wrongYearInfo;
|
||||||
|
private ParsedMovieInfo _wrongTitleInfo;
|
||||||
private ParsedMovieInfo _romanTitleInfo;
|
private ParsedMovieInfo _romanTitleInfo;
|
||||||
private ParsedMovieInfo _alternativeTitleInfo;
|
private ParsedMovieInfo _alternativeTitleInfo;
|
||||||
private ParsedMovieInfo _umlautInfo;
|
private ParsedMovieInfo _umlautInfo;
|
||||||
|
@ -71,6 +72,12 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||||
Year = 1900,
|
Year = 1900,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_wrongTitleInfo = new ParsedMovieInfo
|
||||||
|
{
|
||||||
|
MovieTitle = "Other Title",
|
||||||
|
Year = 2015
|
||||||
|
};
|
||||||
|
|
||||||
_alternativeTitleInfo = new ParsedMovieInfo
|
_alternativeTitleInfo = new ParsedMovieInfo
|
||||||
{
|
{
|
||||||
MovieTitle = _movie.AlternativeTitles.First(),
|
MovieTitle = _movie.AlternativeTitles.First(),
|
||||||
|
@ -139,7 +146,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||||
|
|
||||||
Subject.Map(_parsedMovieInfo, "", _movieSearchCriteria);
|
Subject.Map(_parsedMovieInfo, "", _movieSearchCriteria);
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
Mocker.GetMock<IMovieService>()
|
||||||
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never());
|
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +154,24 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||||
public void should_not_match_with_wrong_year()
|
public void should_not_match_with_wrong_year()
|
||||||
{
|
{
|
||||||
GivenMatchByMovieTitle();
|
GivenMatchByMovieTitle();
|
||||||
Subject.Map(_wrongYearInfo, "", _movieSearchCriteria).Movie.Should().BeNull();
|
Subject.Map(_wrongYearInfo, "", _movieSearchCriteria).MappingResultType.Should().Be(MappingResultType.WrongYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_match_wrong_title()
|
||||||
|
{
|
||||||
|
GivenMatchByMovieTitle();
|
||||||
|
Subject.Map(_wrongTitleInfo, "", _movieSearchCriteria).MappingResultType.Should().Be(MappingResultType.WrongTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_title_not_found_when_all_is_null()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IMovieService>()
|
||||||
|
.Setup(s => s.FindByTitle(It.IsAny<string>()))
|
||||||
|
.Returns((Movie)null);
|
||||||
|
Subject.Map(_parsedMovieInfo, "", null).MappingResultType.Should()
|
||||||
|
.Be(MappingResultType.TitleNotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
|
@ -9,6 +9,7 @@ using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
|
||||||
namespace NzbDrone.Core.DecisionEngine
|
namespace NzbDrone.Core.DecisionEngine
|
||||||
{
|
{
|
||||||
|
@ -71,50 +72,85 @@ namespace NzbDrone.Core.DecisionEngine
|
||||||
{
|
{
|
||||||
var parsedMovieInfo = Parser.Parser.ParseMovieTitle(report.Title, _configService.ParsingLeniency > 0);
|
var parsedMovieInfo = Parser.Parser.ParseMovieTitle(report.Title, _configService.ParsingLeniency > 0);
|
||||||
|
|
||||||
if (parsedMovieInfo != null && !parsedMovieInfo.MovieTitle.IsNullOrWhiteSpace())
|
MappingResult result = null;
|
||||||
{
|
|
||||||
RemoteMovie remoteMovie = _parsingService.Map(parsedMovieInfo, report.ImdbId.ToString(), searchCriteria);
|
|
||||||
remoteMovie.Release = report;
|
|
||||||
|
|
||||||
if (remoteMovie.Movie == null)
|
if (parsedMovieInfo == null || parsedMovieInfo.MovieTitle.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
decision = new DownloadDecision(remoteMovie, new Rejection("Unknown movie. Movie found does not match wanted movie."));
|
_logger.Debug("{0} could not be parsed :(.", report.Title);
|
||||||
}
|
parsedMovieInfo = new ParsedMovieInfo
|
||||||
else
|
{
|
||||||
{
|
MovieTitle = report.Title,
|
||||||
if (parsedMovieInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace())
|
Year = 1290,
|
||||||
{
|
Language = Language.Unknown,
|
||||||
remoteMovie.DownloadAllowed = true;
|
Quality = new QualityModel(),
|
||||||
if (_configService.AllowHardcodedSubs)
|
};
|
||||||
{
|
|
||||||
decision = GetDecisionForReport(remoteMovie, searchCriteria);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var whitelisted = _configService.WhitelistedHardcodedSubs.Split(',');
|
|
||||||
_logger.Debug("Testing: {0}", whitelisted);
|
|
||||||
if (whitelisted != null && whitelisted.Any(t => (parsedMovieInfo.Quality.HardcodedSubs.ToLower().Contains(t.ToLower()) && t.IsNotNullOrWhiteSpace())))
|
|
||||||
{
|
|
||||||
decision = GetDecisionForReport(remoteMovie, searchCriteria);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
decision = new DownloadDecision(remoteMovie, new Rejection("Hardcoded subs found: " + parsedMovieInfo.Quality.HardcodedSubs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
remoteMovie.DownloadAllowed = true;
|
|
||||||
decision = GetDecisionForReport(remoteMovie, searchCriteria);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
if (_configService.ParsingLeniency == ParsingLeniencyType.MappingLenient)
|
||||||
}
|
{
|
||||||
else
|
result = _parsingService.Map(parsedMovieInfo, report.ImdbId.ToString(), searchCriteria);
|
||||||
{
|
}
|
||||||
_logger.Trace("{0} could not be parsed :(.", report.Title);
|
|
||||||
}
|
if (result == null || result.MappingResultType != MappingResultType.SuccessLenientMapping)
|
||||||
|
{
|
||||||
|
result = new MappingResult {MappingResultType = MappingResultType.NotParsable};
|
||||||
|
result.Movie = null; //To ensure we have a remote movie, else null exception on next line!
|
||||||
|
result.RemoteMovie.ParsedMovieInfo = parsedMovieInfo;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Enhance Parsed Movie Info!
|
||||||
|
result.RemoteMovie.ParsedMovieInfo = Parser.Parser.ParseMinimalMovieTitle(parsedMovieInfo.MovieTitle,
|
||||||
|
result.RemoteMovie.Movie.Title, parsedMovieInfo.Year);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = _parsingService.Map(parsedMovieInfo, report.ImdbId.ToString(), searchCriteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ReleaseName = report.Title;
|
||||||
|
var remoteMovie = result.RemoteMovie;
|
||||||
|
|
||||||
|
remoteMovie.Release = report;
|
||||||
|
|
||||||
|
if (result.MappingResultType != MappingResultType.Success && result.MappingResultType != MappingResultType.SuccessLenientMapping)
|
||||||
|
{
|
||||||
|
var rejection = result.ToRejection();
|
||||||
|
remoteMovie.Movie = null; // HACK: For now!
|
||||||
|
decision = new DownloadDecision(remoteMovie, rejection);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (parsedMovieInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
remoteMovie.DownloadAllowed = true;
|
||||||
|
if (_configService.AllowHardcodedSubs)
|
||||||
|
{
|
||||||
|
decision = GetDecisionForReport(remoteMovie, searchCriteria);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var whitelisted = _configService.WhitelistedHardcodedSubs.Split(',');
|
||||||
|
_logger.Debug("Testing: {0}", whitelisted);
|
||||||
|
if (whitelisted != null && whitelisted.Any(t => (parsedMovieInfo.Quality.HardcodedSubs.ToLower().Contains(t.ToLower()) && t.IsNotNullOrWhiteSpace())))
|
||||||
|
{
|
||||||
|
decision = GetDecisionForReport(remoteMovie, searchCriteria);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
decision = new DownloadDecision(remoteMovie, new Rejection("Hardcoded subs found: " + parsedMovieInfo.Quality.HardcodedSubs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
remoteMovie.DownloadAllowed = true;
|
||||||
|
decision = GetDecisionForReport(remoteMovie, searchCriteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -65,7 +65,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
|
|
||||||
if (parsedMovieInfo != null)
|
if (parsedMovieInfo != null)
|
||||||
{
|
{
|
||||||
trackedDownload.RemoteMovie = _parsingService.Map(parsedMovieInfo, "", null);
|
trackedDownload.RemoteMovie = _parsingService.Map(parsedMovieInfo, "", null).RemoteMovie;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (historyItems.Any())
|
if (historyItems.Any())
|
||||||
|
@ -81,7 +81,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
|
|
||||||
if (parsedMovieInfo != null)
|
if (parsedMovieInfo != null)
|
||||||
{
|
{
|
||||||
trackedDownload.RemoteMovie = _parsingService.Map(parsedMovieInfo, "", null);
|
trackedDownload.RemoteMovie = _parsingService.Map(parsedMovieInfo, "", null).RemoteMovie;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,7 +185,7 @@ namespace NzbDrone.Core.MetadataSource.PreDB
|
||||||
}
|
}
|
||||||
var match = _parsingService.Map(parsed, "", new MovieSearchCriteria { Movie = movie });
|
var match = _parsingService.Map(parsed, "", new MovieSearchCriteria { Movie = movie });
|
||||||
|
|
||||||
if (match != null && match.Movie != null && match.Movie.Id == movie.Id)
|
if (match != null && match.RemoteMovie.Movie != null && match.RemoteMovie.Movie.Id == movie.Id)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,11 +25,11 @@ namespace NzbDrone.Core.Parser
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Special, Despecialized, etc. Edition Movies, e.g: Mission.Impossible.3.2011.Special.Edition //TODO: Seems to slow down parsing heavily!
|
//Special, Despecialized, etc. Edition Movies, e.g: Mission.Impossible.3.2011.Special.Edition //TODO: Seems to slow down parsing heavily!
|
||||||
new Regex(@"^(?<title>(?![(\[]).+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(19|20)\d{2}(?!p|i|(19|20)\d{2}|\]|\W(19|20)\d{2})))+(\W+|_|$)(?!\\)\(?(?<edition>(((Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Final(?=(.(Cut|Edition|Version)))|Extended|Rogue|Special|Despecialized|\d{2,3}(th)?.Anniversary)(.(Cut|Edition|Version))?(.(Extended|Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit))?|((Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit|Edition|Restored|((2|3|4)in1))))))\)?",
|
/*new Regex(@"^(?<title>(?![(\[]).+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(19|20)\d{2}(?!p|i|(19|20)\d{2}|\]|\W(19|20)\d{2})))+(\W+|_|$)(?!\\)\(?(?<edition>(((Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Final(?=(.(Cut|Edition|Version)))|Extended|Rogue|Special|Despecialized|\d{2,3}(th)?.Anniversary)(.(Cut|Edition|Version))?(.(Extended|Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit))?|((Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit|Edition|Restored|((2|3|4)in1))))))\)?",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),*/
|
||||||
|
|
||||||
//Normal movie format, e.g: Mission.Impossible.3.2011
|
//Normal movie format, e.g: Mission.Impossible.3.2011
|
||||||
new Regex(@"^(?<title>(?![(\[]).+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
new Regex(@"^(?<title>(?![(\[]).+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(19|20)\d{2}(?!p|i|(19|20)\d{2}|\]|\W(19|20)\d{2})))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//PassThePopcorn Torrent names: Star.Wars[PassThePopcorn]
|
//PassThePopcorn Torrent names: Star.Wars[PassThePopcorn]
|
||||||
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<year>(\[\w *\])))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<year>(\[\w *\])))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
@ -51,7 +51,7 @@ namespace NzbDrone.Core.Parser
|
||||||
private static readonly Regex[] ReportMovieTitleLenientRegexBefore = new[]
|
private static readonly Regex[] ReportMovieTitleLenientRegexBefore = new[]
|
||||||
{
|
{
|
||||||
//Some german or french tracker formats
|
//Some german or french tracker formats
|
||||||
new Regex(@"^(?<title>(?![(\[]).+?)((\W|_))(?:(German|French|TrueFrench))(.+?)(?=((19|20)\d{2}|$))(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+))?(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
new Regex(@"^(?<title>(?![(\[]).+?)((\W|_))(?:(?<!(19|20)\d{2}.)(German|French|TrueFrench))(.+?)(?=((19|20)\d{2}|$))(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+))?(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly Regex[] ReportMovieTitleLenientRegexAfter = new Regex[]
|
private static readonly Regex[] ReportMovieTitleLenientRegexAfter = new Regex[]
|
||||||
|
@ -321,6 +321,10 @@ namespace NzbDrone.Core.Parser
|
||||||
private static readonly Regex DuplicateSpacesRegex = new Regex(@"\s{2,}", RegexOptions.Compiled);
|
private static readonly Regex DuplicateSpacesRegex = new Regex(@"\s{2,}", RegexOptions.Compiled);
|
||||||
|
|
||||||
private static readonly Regex RequestInfoRegex = new Regex(@"\[.+?\]", RegexOptions.Compiled);
|
private static readonly Regex RequestInfoRegex = new Regex(@"\[.+?\]", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static readonly Regex ReportYearRegex = new Regex(@"^.*(?<year>(19|20)\d{2}).*$", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static readonly Regex ReportEditionRegex = new Regex(@"(?<edition>(((Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Final(?=(.(Cut|Edition|Version)))|Extended|Rogue|Special|Despecialized|\d{2,3}(th)?.Anniversary)(.(Cut|Edition|Version))?(.(Extended|Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit))?|((Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit|Edition|Restored|((2|3|4)in1))))))\)?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private static readonly string[] Numbers = new[] { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
|
private static readonly string[] Numbers = new[] { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
|
||||||
private static Dictionary<String, String> _umlautMappings = new Dictionary<string, string>
|
private static Dictionary<String, String> _umlautMappings = new Dictionary<string, string>
|
||||||
|
@ -441,6 +445,11 @@ namespace NzbDrone.Core.Parser
|
||||||
result.Quality = QualityParser.ParseQuality(title);
|
result.Quality = QualityParser.ParseQuality(title);
|
||||||
Logger.Debug("Quality parsed: {0}", result.Quality);
|
Logger.Debug("Quality parsed: {0}", result.Quality);
|
||||||
|
|
||||||
|
if (result.Edition.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
result.Edition = ParseEdition(languageTitle);
|
||||||
|
}
|
||||||
|
|
||||||
result.ReleaseGroup = ParseReleaseGroup(title);
|
result.ReleaseGroup = ParseReleaseGroup(title);
|
||||||
|
|
||||||
result.ImdbId = ParseImdbId(title);
|
result.ImdbId = ParseImdbId(title);
|
||||||
|
@ -482,6 +491,53 @@ namespace NzbDrone.Core.Parser
|
||||||
return realResult;
|
return realResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ParsedMovieInfo ParseMinimalMovieTitle(string title, string foundTitle, int foundYear)
|
||||||
|
{
|
||||||
|
var result = new ParsedMovieInfo {MovieTitle = foundTitle};
|
||||||
|
|
||||||
|
var languageTitle = Regex.Replace(title.Replace(".", " "), foundTitle, "A Movie", RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
result.Language = LanguageParser.ParseLanguage(title);
|
||||||
|
Logger.Debug("Language parsed: {0}", result.Language);
|
||||||
|
|
||||||
|
result.Quality = QualityParser.ParseQuality(title);
|
||||||
|
Logger.Debug("Quality parsed: {0}", result.Quality);
|
||||||
|
|
||||||
|
if (result.Edition.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
result.Edition = ParseEdition(languageTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ReleaseGroup = ParseReleaseGroup(title);
|
||||||
|
|
||||||
|
result.ImdbId = ParseImdbId(title);
|
||||||
|
|
||||||
|
Logger.Debug("Release Group parsed: {0}", result.ReleaseGroup);
|
||||||
|
|
||||||
|
if (foundYear > 1800)
|
||||||
|
{
|
||||||
|
result.Year = foundYear;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var match = ReportYearRegex.Match(title);
|
||||||
|
if (match.Success && match.Groups["year"].Value != null)
|
||||||
|
{
|
||||||
|
int year = 1290;
|
||||||
|
if (int.TryParse(match.Groups["year"].Value, out year))
|
||||||
|
{
|
||||||
|
result.Year = year;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Year = year;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public static string ParseImdbId(string title)
|
public static string ParseImdbId(string title)
|
||||||
{
|
{
|
||||||
var match = ReportImdbId.Match(title);
|
var match = ReportImdbId.Match(title);
|
||||||
|
@ -499,6 +555,19 @@ namespace NzbDrone.Core.Parser
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ParseEdition(string languageTitle)
|
||||||
|
{
|
||||||
|
var editionMatch = ReportEditionRegex.Match(languageTitle);
|
||||||
|
|
||||||
|
if (editionMatch.Success && editionMatch.Groups["edition"].Value != null &&
|
||||||
|
editionMatch.Groups["edition"].Value.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return editionMatch.Groups["edition"].Value.Replace(".", " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
public static ParsedEpisodeInfo ParseTitle(string title)
|
public static ParsedEpisodeInfo ParseTitle(string title)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ using NLog;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.DataAugmentation.Scene;
|
using NzbDrone.Core.DataAugmentation.Scene;
|
||||||
|
using NzbDrone.Core.DecisionEngine;
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
@ -24,7 +25,7 @@ namespace NzbDrone.Core.Parser
|
||||||
Movie GetMovie(string title);
|
Movie GetMovie(string title);
|
||||||
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);
|
||||||
RemoteMovie Map(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria = null);
|
MappingResult Map(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria = null);
|
||||||
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 title, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
||||||
}
|
}
|
||||||
|
@ -218,23 +219,18 @@ namespace NzbDrone.Core.Parser
|
||||||
return remoteEpisode;
|
return remoteEpisode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RemoteMovie Map(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria = null)
|
public MappingResult Map(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria = null)
|
||||||
{
|
{
|
||||||
var remoteMovie = new RemoteMovie
|
var result = GetMovie(parsedMovieInfo, imdbId, searchCriteria);
|
||||||
{
|
|
||||||
ParsedMovieInfo = parsedMovieInfo,
|
|
||||||
};
|
|
||||||
|
|
||||||
var movie = GetMovie(parsedMovieInfo, imdbId, searchCriteria);
|
if (result == null) {
|
||||||
|
result = new MappingResult {MappingResultType = MappingResultType.Unknown};
|
||||||
if (movie == null)
|
result.Movie = null;
|
||||||
{
|
|
||||||
return remoteMovie;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteMovie.Movie = movie;
|
result.RemoteMovie.ParsedMovieInfo = parsedMovieInfo;
|
||||||
|
|
||||||
return remoteMovie;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds)
|
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds)
|
||||||
|
@ -351,77 +347,113 @@ namespace NzbDrone.Core.Parser
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Movie GetMovie(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria)
|
private MappingResult GetMovie(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria)
|
||||||
{
|
{
|
||||||
// TODO: Answer me this: Wouldn't it be smarter to start out looking for a movie if we have an ImDb Id?
|
// TODO: Answer me this: Wouldn't it be smarter to start out looking for a movie if we have an ImDb Id?
|
||||||
|
MappingResult result = null;
|
||||||
if (!String.IsNullOrWhiteSpace(imdbId) && imdbId != "0")
|
if (!String.IsNullOrWhiteSpace(imdbId) && imdbId != "0")
|
||||||
{
|
{
|
||||||
Movie movieByImDb;
|
if (TryGetMovieByImDbId(parsedMovieInfo, imdbId, out result))
|
||||||
if (TryGetMovieByImDbId(parsedMovieInfo, imdbId, out movieByImDb))
|
|
||||||
{
|
{
|
||||||
return movieByImDb;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchCriteria != null)
|
if (searchCriteria != null)
|
||||||
{
|
{
|
||||||
Movie movieBySearchCriteria;
|
if (TryGetMovieBySearchCriteria(parsedMovieInfo, searchCriteria, out result))
|
||||||
if (TryGetMovieBySearchCriteria(parsedMovieInfo, searchCriteria, out movieBySearchCriteria))
|
|
||||||
{
|
{
|
||||||
return movieBySearchCriteria;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Movie movieByTitleAndOrYear;
|
TryGetMovieByTitleAndOrYear(parsedMovieInfo, out result);
|
||||||
if (TryGetMovieByTitleAndOrYear(parsedMovieInfo, out movieByTitleAndOrYear))
|
return result;
|
||||||
{
|
|
||||||
return movieByTitleAndOrYear;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nothing found up to here => logging that and returning null
|
// nothing found up to here => logging that and returning null
|
||||||
_logger.Debug($"No matching movie {parsedMovieInfo.MovieTitle}");
|
_logger.Debug($"No matching movie {parsedMovieInfo.MovieTitle}");
|
||||||
return null;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryGetMovieByImDbId(ParsedMovieInfo parsedMovieInfo, string imdbId, out Movie movie)
|
private bool TryGetMovieByImDbId(ParsedMovieInfo parsedMovieInfo, string imdbId, out MappingResult result)
|
||||||
{
|
{
|
||||||
movie = _movieService.FindByImdbId(imdbId);
|
var movie = _movieService.FindByImdbId(imdbId);
|
||||||
//Should fix practically all problems, where indexer is shite at adding correct imdbids to movies.
|
//Should fix practically all problems, where indexer is shite at adding correct imdbids to movies.
|
||||||
if (movie != null && parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year != movie.Year)
|
if (movie != null && parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year != movie.Year)
|
||||||
{
|
{
|
||||||
movie = null;
|
result = new MappingResult { Movie = movie, MappingResultType = MappingResultType.WrongYear};
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (movie != null) {
|
||||||
|
result = new MappingResult { Movie = movie };
|
||||||
|
} else {
|
||||||
|
result = new MappingResult { Movie = movie, MappingResultType = MappingResultType.TitleNotFound};
|
||||||
|
}
|
||||||
return movie != null;
|
return movie != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryGetMovieByTitleAndOrYear(ParsedMovieInfo parsedMovieInfo, out Movie movieByTitleAndOrYear)
|
private bool TryGetMovieByTitleAndOrYear(ParsedMovieInfo parsedMovieInfo, out MappingResult result)
|
||||||
{
|
{
|
||||||
Func<Movie, bool> isNotNull = movie => movie != null;
|
Func<Movie, bool> isNotNull = movie => movie != null;
|
||||||
|
Movie movieByTitleAndOrYear = null;
|
||||||
|
|
||||||
if (parsedMovieInfo.Year > 1800)
|
if (parsedMovieInfo.Year > 1800)
|
||||||
{
|
{
|
||||||
movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle, parsedMovieInfo.Year);
|
movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle, parsedMovieInfo.Year);
|
||||||
if (isNotNull(movieByTitleAndOrYear))
|
if (isNotNull(movieByTitleAndOrYear))
|
||||||
{
|
{
|
||||||
|
result = new MappingResult { Movie = movieByTitleAndOrYear };
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle);
|
||||||
|
if (isNotNull(movieByTitleAndOrYear))
|
||||||
|
{
|
||||||
|
result = new MappingResult { Movie = movieByTitleAndOrYear, MappingResultType = MappingResultType.WrongYear};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_config.ParsingLeniency == ParsingLeniencyType.MappingLenient)
|
||||||
|
{
|
||||||
|
movieByTitleAndOrYear = _movieService.FindByTitleInexact(parsedMovieInfo.MovieTitle, parsedMovieInfo.Year);
|
||||||
|
if (isNotNull(movieByTitleAndOrYear))
|
||||||
|
{
|
||||||
|
result = new MappingResult {Movie = movieByTitleAndOrYear, MappingResultType = MappingResultType.SuccessLenientMapping};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new MappingResult { Movie = movieByTitleAndOrYear, MappingResultType = MappingResultType.TitleNotFound};
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle);
|
movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle);
|
||||||
if (isNotNull(movieByTitleAndOrYear))
|
if (isNotNull(movieByTitleAndOrYear))
|
||||||
{
|
{
|
||||||
|
result = new MappingResult { Movie = movieByTitleAndOrYear };
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
movieByTitleAndOrYear = null;
|
|
||||||
|
if (_config.ParsingLeniency == ParsingLeniencyType.MappingLenient)
|
||||||
|
{
|
||||||
|
movieByTitleAndOrYear = _movieService.FindByTitleInexact(parsedMovieInfo.MovieTitle, null);
|
||||||
|
if (isNotNull(movieByTitleAndOrYear))
|
||||||
|
{
|
||||||
|
result = new MappingResult {Movie = movieByTitleAndOrYear, MappingResultType = MappingResultType.SuccessLenientMapping};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new MappingResult { Movie = movieByTitleAndOrYear, MappingResultType = MappingResultType.TitleNotFound};
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryGetMovieBySearchCriteria(ParsedMovieInfo parsedMovieInfo, SearchCriteriaBase searchCriteria, out Movie possibleMovie)
|
private bool TryGetMovieBySearchCriteria(ParsedMovieInfo parsedMovieInfo, SearchCriteriaBase searchCriteria, out MappingResult result)
|
||||||
{
|
{
|
||||||
possibleMovie = null;
|
Movie possibleMovie = null;
|
||||||
|
|
||||||
List<string> possibleTitles = new List<string>();
|
List<string> possibleTitles = new List<string>();
|
||||||
|
|
||||||
possibleTitles.Add(searchCriteria.Movie.CleanTitle);
|
possibleTitles.Add(searchCriteria.Movie.CleanTitle);
|
||||||
|
@ -445,7 +477,7 @@ namespace NzbDrone.Core.Parser
|
||||||
string arabicNumeral = numeralMapping.ArabicNumeralAsString;
|
string arabicNumeral = numeralMapping.ArabicNumeralAsString;
|
||||||
string romanNumeral = numeralMapping.RomanNumeralLowerCase;
|
string romanNumeral = numeralMapping.RomanNumeralLowerCase;
|
||||||
|
|
||||||
_logger.Debug(cleanTitle);
|
//_logger.Debug(cleanTitle);
|
||||||
|
|
||||||
if (title.Replace(arabicNumeral, romanNumeral) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
|
if (title.Replace(arabicNumeral, romanNumeral) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
|
||||||
{
|
{
|
||||||
|
@ -460,11 +492,42 @@ namespace NzbDrone.Core.Parser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (possibleMovie != null && (parsedMovieInfo.Year < 1800 || possibleMovie.Year == parsedMovieInfo.Year))
|
if (possibleMovie != null)
|
||||||
{
|
{
|
||||||
return true;
|
if (parsedMovieInfo.Year < 1800 || possibleMovie.Year == parsedMovieInfo.Year)
|
||||||
|
{
|
||||||
|
result = new MappingResult { Movie = possibleMovie, MappingResultType = MappingResultType.Success };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
result = new MappingResult { Movie = possibleMovie, MappingResultType = MappingResultType.WrongYear };
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
possibleMovie = null;
|
|
||||||
|
if (_config.ParsingLeniency == ParsingLeniencyType.MappingLenient)
|
||||||
|
{
|
||||||
|
if (searchCriteria.Movie.CleanTitle.Contains(cleanTitle) ||
|
||||||
|
cleanTitle.Contains(searchCriteria.Movie.CleanTitle))
|
||||||
|
{
|
||||||
|
possibleMovie = searchCriteria.Movie;
|
||||||
|
if (parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year == possibleMovie.Year)
|
||||||
|
{
|
||||||
|
result = new MappingResult {Movie = possibleMovie, MappingResultType = MappingResultType.SuccessLenientMapping};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedMovieInfo.Year < 1800)
|
||||||
|
{
|
||||||
|
result = new MappingResult { Movie = possibleMovie, MappingResultType = MappingResultType.SuccessLenientMapping };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new MappingResult { Movie = possibleMovie, MappingResultType = MappingResultType.WrongYear };
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new MappingResult { Movie = searchCriteria.Movie, MappingResultType = MappingResultType.WrongTitle };
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -693,29 +756,86 @@ namespace NzbDrone.Core.Parser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MappingException : Exception
|
|
||||||
|
public class MappingResult
|
||||||
{
|
{
|
||||||
public virtual string Reason()
|
public string Message
|
||||||
{
|
{
|
||||||
return "Parsed movie does not match wanted movie";
|
get
|
||||||
|
{
|
||||||
|
switch (MappingResultType)
|
||||||
|
{
|
||||||
|
case MappingResultType.Success:
|
||||||
|
return $"Successfully mapped release name {ReleaseName} to movie {Movie}";
|
||||||
|
break;
|
||||||
|
case MappingResultType.SuccessLenientMapping:
|
||||||
|
return $"Successfully mapped parts of the release name {ReleaseName} to movie {Movie}";
|
||||||
|
break;
|
||||||
|
case MappingResultType.NotParsable:
|
||||||
|
return $"Failed to find movie title in release name {ReleaseName}";
|
||||||
|
break;
|
||||||
|
case MappingResultType.TitleNotFound:
|
||||||
|
return $"Could not find {RemoteMovie.ParsedMovieInfo.MovieTitle}";
|
||||||
|
break;
|
||||||
|
case MappingResultType.WrongYear:
|
||||||
|
return $"Failed to map movie, expected year {RemoteMovie.Movie.Year}, but found {RemoteMovie.ParsedMovieInfo.Year}";
|
||||||
|
case MappingResultType.WrongTitle:
|
||||||
|
var comma = RemoteMovie.Movie.AlternativeTitles.Count > 0 ? ", " : "";
|
||||||
|
return
|
||||||
|
$"Failed to map movie, found title {RemoteMovie.ParsedMovieInfo.MovieTitle}, expected one of: {RemoteMovie.Movie.Title}{comma}{string.Join(", ", RemoteMovie.Movie.AlternativeTitles)}";
|
||||||
|
default:
|
||||||
|
return $"Failed to map movie for unkown reasons";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteMovie RemoteMovie;
|
||||||
|
public MappingResultType MappingResultType { get; set; }
|
||||||
|
public Movie Movie {
|
||||||
|
get {
|
||||||
|
return RemoteMovie.Movie;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
ParsedMovieInfo parsedInfo = null;
|
||||||
|
if (RemoteMovie != null)
|
||||||
|
{
|
||||||
|
parsedInfo = RemoteMovie.ParsedMovieInfo;
|
||||||
|
}
|
||||||
|
RemoteMovie = new RemoteMovie
|
||||||
|
{
|
||||||
|
Movie = value,
|
||||||
|
ParsedMovieInfo = parsedInfo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReleaseName { get; set; }
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
return string.Format(Message, RemoteMovie.Movie);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rejection ToRejection() {
|
||||||
|
switch (MappingResultType)
|
||||||
|
{
|
||||||
|
case MappingResultType.Success:
|
||||||
|
case MappingResultType.SuccessLenientMapping:
|
||||||
|
return null;
|
||||||
|
default:
|
||||||
|
return new Rejection(Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class YearDoesNotMatchException : MappingException
|
public enum MappingResultType
|
||||||
{
|
{
|
||||||
public int ExpectedYear { get; set; }
|
Unknown = -1,
|
||||||
public int? ParsedYear { get; set; }
|
Success = 0,
|
||||||
|
SuccessLenientMapping = 1,
|
||||||
override public string Reason()
|
WrongYear = 2,
|
||||||
{
|
WrongTitle = 3,
|
||||||
if (ParsedYear.HasValue && ParsedYear > 1800)
|
TitleNotFound = 4,
|
||||||
{
|
NotParsable = 5,
|
||||||
return $"Expected {ExpectedYear}, but found {ParsedYear} for year";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return "Did not find a valid year";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -28,7 +28,7 @@ namespace NzbDrone.Core.Tv
|
||||||
Movie FindByImdbId(string imdbid);
|
Movie FindByImdbId(string imdbid);
|
||||||
Movie FindByTitle(string title);
|
Movie FindByTitle(string title);
|
||||||
Movie FindByTitle(string title, int year);
|
Movie FindByTitle(string title, int year);
|
||||||
Movie FindByTitleInexact(string title);
|
Movie FindByTitleInexact(string title, int? year);
|
||||||
Movie FindByTitleSlug(string slug);
|
Movie FindByTitleSlug(string slug);
|
||||||
bool MovieExists(Movie movie);
|
bool MovieExists(Movie movie);
|
||||||
Movie GetMovieByFileId(int fileId);
|
Movie GetMovieByFileId(int fileId);
|
||||||
|
@ -238,20 +238,16 @@ namespace NzbDrone.Core.Tv
|
||||||
return _movieRepository.FindByImdbId(imdbid);
|
return _movieRepository.FindByImdbId(imdbid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Movie FindByTitleInexact(string title)
|
private List<Movie> FindByTitleInexactAll(string title)
|
||||||
{
|
{
|
||||||
// find any movie clean title within the provided release title
|
// find any movie clean title within the provided release title
|
||||||
string cleanTitle = title.CleanSeriesTitle();
|
string cleanTitle = title.CleanSeriesTitle();
|
||||||
var list = _movieRepository.All().Where(s => cleanTitle.Contains(s.CleanTitle)).ToList();
|
var list = _movieRepository.All().Where(s => cleanTitle.Contains(s.CleanTitle))
|
||||||
|
.Union(_movieRepository.All().Where(s => s.CleanTitle.Contains(cleanTitle))).ToList();
|
||||||
if (!list.Any())
|
if (!list.Any())
|
||||||
{
|
{
|
||||||
// no movie matched
|
// no movie matched
|
||||||
return null;
|
return list;
|
||||||
}
|
|
||||||
if (list.Count == 1)
|
|
||||||
{
|
|
||||||
// return the first movie if there is only one
|
|
||||||
return list.Single();
|
|
||||||
}
|
}
|
||||||
// build ordered list of movie by position in the search string
|
// build ordered list of movie by position in the search string
|
||||||
var query =
|
var query =
|
||||||
|
@ -265,21 +261,34 @@ namespace NzbDrone.Core.Tv
|
||||||
.ToList()
|
.ToList()
|
||||||
.OrderBy(s => s.position)
|
.OrderBy(s => s.position)
|
||||||
.ThenByDescending(s => s.length)
|
.ThenByDescending(s => s.length)
|
||||||
|
.Select(s => s.movie)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Movie FindByTitleInexact(string title)
|
||||||
|
{
|
||||||
|
var query = FindByTitleInexactAll(title);
|
||||||
// get the leftmost movie that is the longest
|
// get the leftmost movie that is the longest
|
||||||
// movie are usually the first thing in release title, so we select the leftmost and longest match
|
// movie are usually the first thing in release title, so we select the leftmost and longest match
|
||||||
var match = query.First().movie;
|
var match = query.First();
|
||||||
|
|
||||||
_logger.Debug("Multiple movie matched {0} from title {1}", match.Title, title);
|
_logger.Debug("Multiple movie matched {0} from title {1}", match.Title, title);
|
||||||
foreach (var entry in list)
|
foreach (var entry in query)
|
||||||
{
|
{
|
||||||
_logger.Debug("Multiple movie match candidate: {0} cleantitle: {1}", entry.Title, entry.CleanTitle);
|
_logger.Debug("Multiple movie match candidate: {0} cleantitle: {1}", entry.Title, entry.CleanTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Movie FindByTitleInexact(string title, int? year)
|
||||||
|
{
|
||||||
|
return FindByTitleInexactAll(title).FirstWithYear(year);
|
||||||
|
}
|
||||||
|
|
||||||
public Movie FindByTitle(string title, int year)
|
public Movie FindByTitle(string title, int year)
|
||||||
{
|
{
|
||||||
return _movieRepository.FindByTitle(title.CleanSeriesTitle(), year);
|
return _movieRepository.FindByTitle(title.CleanSeriesTitle(), year);
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
How strict the Parser should be. (Note: Strict is strongly recommended!)
|
<h5><b>How strict the Parser should be. (Note: Strict is strongly recommended!)</b></h5>
|
||||||
<br><br>
|
<br>
|
||||||
<b>Strict:</b> Just as before, year must immediately follow title.
|
<b>Strict:</b> Just as before, year must immediately follow title.
|
||||||
<br><br>
|
<br><br>
|
||||||
<b>Lenient Parsing:</b> Either year or language tag must immediately follow after title. (Note: May prevent Movies with language tags in title - e.g. The Danish Girl - from being parsed correctly)
|
<b>Lenient Parsing:</b> Either year or language tag must immediately follow after title. Enables releases such as 'Scary Movie German BluRay' to be parsed correctly.
|
||||||
|
<br>
|
||||||
|
<b>Note</b>: May prevent Movies with language tags in title - e.g. The Danish Girl - from being parsed correctly
|
||||||
<br><br>
|
<br><br>
|
||||||
<b>Lenient Mapping:</b> Includes Lenient Parsing. When title cannot be found Try mapping just parts of the title. (Useful when no year is present / not after title. <b>NOT IMPLEMENTED YET</b>)
|
<b>Lenient Mapping:</b> Includes Lenient Parsing. When title cannot be found, try mapping just parts of the title. Useful when no year is present / not after title.
|
||||||
|
<br>
|
||||||
|
<b>Warning!:</b> May cause unexpected mappings, e.g. Scary Movie 2 mapped to movie Scary Movie 1, etc. Use with caution.
|
Loading…
Add table
Add a link
Reference in a new issue