mirror of
https://github.com/Radarr/Radarr.git
synced 2024-09-11 12:02:35 +02: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
@ -19,6 +19,7 @@ public class DownloadDecisionMakerFixture : CoreTest<DownloadDecisionMaker>
|
||||
{
|
||||
private List<ReleaseInfo> _reports;
|
||||
private RemoteMovie _remoteEpisode;
|
||||
private MappingResult _mappingResult;
|
||||
|
||||
private Mock<IDecisionEngineSpecification> _pass1;
|
||||
private Mock<IDecisionEngineSpecification> _pass2;
|
||||
@ -50,11 +51,15 @@ public void Setup()
|
||||
_reports = new List<ReleaseInfo> { new ReleaseInfo { Title = "Trolls.2016.720p.WEB-DL.DD5.1.H264-FGT" } };
|
||||
_remoteEpisode = new RemoteMovie {
|
||||
Movie = new Movie(),
|
||||
ParsedMovieInfo = new ParsedMovieInfo()
|
||||
};
|
||||
|
||||
_mappingResult = new MappingResult {Movie = new Movie(), MappingResultType = MappingResultType.Success};
|
||||
_mappingResult.RemoteMovie = _remoteEpisode;
|
||||
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(c => c.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), It.IsAny<SearchCriteriaBase>()))
|
||||
.Returns(_remoteEpisode);
|
||||
.Setup(c => c.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), It.IsAny<SearchCriteriaBase>())).Returns(_mappingResult);
|
||||
}
|
||||
|
||||
private void GivenSpecifications(params Mock<IDecisionEngineSpecification>[] mocks)
|
||||
@ -121,6 +126,7 @@ public void should_not_attempt_to_map_episode_if_not_parsable()
|
||||
{
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
_reports[0].Title = "Not parsable";
|
||||
_mappingResult.MappingResultType = MappingResultType.NotParsable;
|
||||
|
||||
var results = Subject.GetRssDecision(_reports).ToList();
|
||||
|
||||
@ -130,7 +136,7 @@ public void should_not_attempt_to_map_episode_if_not_parsable()
|
||||
_pass2.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]
|
||||
@ -138,6 +144,7 @@ public void should_not_attempt_to_map_episode_series_title_is_blank()
|
||||
{
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
_reports[0].Title = "1937 - Snow White and the Seven Dwarves";
|
||||
_mappingResult.MappingResultType = MappingResultType.NotParsable;
|
||||
|
||||
var results = Subject.GetRssDecision(_reports).ToList();
|
||||
|
||||
@ -147,7 +154,7 @@ public void should_not_attempt_to_map_episode_series_title_is_blank()
|
||||
_pass2.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]
|
||||
@ -156,6 +163,7 @@ public void should_not_attempt_to_make_decision_if_series_is_unknown()
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
|
||||
_remoteEpisode.Movie = null;
|
||||
_mappingResult.MappingResultType = MappingResultType.TitleNotFound;
|
||||
|
||||
Subject.GetRssDecision(_reports);
|
||||
|
||||
@ -249,6 +257,7 @@ public void should_not_allow_download_if_series_is_unknown()
|
||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||
|
||||
_remoteEpisode.Movie = null;
|
||||
_mappingResult.MappingResultType = MappingResultType.TitleNotFound;
|
||||
|
||||
var result = Subject.GetRssDecision(_reports);
|
||||
|
||||
|
@ -31,7 +31,7 @@ public void SetupBase()
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.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>()
|
||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||
|
@ -50,7 +50,7 @@ public void should_track_downloads_using_the_source_title_if_it_cannot_be_found_
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.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()
|
||||
{
|
||||
|
@ -14,6 +14,7 @@
|
||||
namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
|
||||
{
|
||||
[IntegrationTest]
|
||||
[Ignore("Nyaa is down!")]
|
||||
public class IndexerIntegrationTests : CoreTest
|
||||
{
|
||||
private SingleEpisodeSearchCriteria _singleSearchCriteria;
|
||||
|
@ -82,6 +82,8 @@ public void should_remove_request_info_from_title(string postTitle, string title
|
||||
[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("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)
|
||||
{
|
||||
Parser.Parser.ParseMovieTitle(postTitle, true).MovieTitle.Should().Be(title);
|
||||
@ -135,9 +137,20 @@ public void should_not_parse_language_in_movie_title(string postTitle)
|
||||
[TestCase("Movie IMAX 2012.mkv", "IMAX")]
|
||||
[TestCase("Fake Movie Final Cut 2016", "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)
|
||||
{
|
||||
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 @@ public class MapFixture : TestBase<ParsingService>
|
||||
private Movie _movie;
|
||||
private ParsedMovieInfo _parsedMovieInfo;
|
||||
private ParsedMovieInfo _wrongYearInfo;
|
||||
private ParsedMovieInfo _wrongTitleInfo;
|
||||
private ParsedMovieInfo _romanTitleInfo;
|
||||
private ParsedMovieInfo _alternativeTitleInfo;
|
||||
private ParsedMovieInfo _umlautInfo;
|
||||
@ -71,6 +72,12 @@ public void Setup()
|
||||
Year = 1900,
|
||||
};
|
||||
|
||||
_wrongTitleInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitle = "Other Title",
|
||||
Year = 2015
|
||||
};
|
||||
|
||||
_alternativeTitleInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitle = _movie.AlternativeTitles.First(),
|
||||
@ -139,7 +146,7 @@ public void should_use_search_criteria_movie_title()
|
||||
|
||||
Subject.Map(_parsedMovieInfo, "", _movieSearchCriteria);
|
||||
|
||||
Mocker.GetMock<ISeriesService>()
|
||||
Mocker.GetMock<IMovieService>()
|
||||
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
@ -147,7 +154,24 @@ public void should_use_search_criteria_movie_title()
|
||||
public void should_not_match_with_wrong_year()
|
||||
{
|
||||
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]
|
||||
|
@ -9,6 +9,7 @@
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
@ -71,50 +72,85 @@ private IEnumerable<DownloadDecision> GetMovieDecisions(List<ReleaseInfo> report
|
||||
{
|
||||
var parsedMovieInfo = Parser.Parser.ParseMovieTitle(report.Title, _configService.ParsingLeniency > 0);
|
||||
|
||||
if (parsedMovieInfo != null && !parsedMovieInfo.MovieTitle.IsNullOrWhiteSpace())
|
||||
{
|
||||
RemoteMovie remoteMovie = _parsingService.Map(parsedMovieInfo, report.ImdbId.ToString(), searchCriteria);
|
||||
remoteMovie.Release = report;
|
||||
MappingResult result = null;
|
||||
|
||||
if (remoteMovie.Movie == null)
|
||||
{
|
||||
decision = new DownloadDecision(remoteMovie, new Rejection("Unknown movie. Movie found does not match wanted movie."));
|
||||
}
|
||||
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);
|
||||
}
|
||||
if (parsedMovieInfo == null || parsedMovieInfo.MovieTitle.IsNullOrWhiteSpace())
|
||||
{
|
||||
_logger.Debug("{0} could not be parsed :(.", report.Title);
|
||||
parsedMovieInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitle = report.Title,
|
||||
Year = 1290,
|
||||
Language = Language.Unknown,
|
||||
Quality = new QualityModel(),
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Trace("{0} could not be parsed :(.", report.Title);
|
||||
}
|
||||
if (_configService.ParsingLeniency == ParsingLeniencyType.MappingLenient)
|
||||
{
|
||||
result = _parsingService.Map(parsedMovieInfo, report.ImdbId.ToString(), searchCriteria);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -65,7 +65,7 @@ public TrackedDownload TrackDownload(DownloadClientDefinition downloadClient, Do
|
||||
|
||||
if (parsedMovieInfo != null)
|
||||
{
|
||||
trackedDownload.RemoteMovie = _parsingService.Map(parsedMovieInfo, "", null);
|
||||
trackedDownload.RemoteMovie = _parsingService.Map(parsedMovieInfo, "", null).RemoteMovie;
|
||||
}
|
||||
|
||||
if (historyItems.Any())
|
||||
@ -81,7 +81,7 @@ public TrackedDownload TrackDownload(DownloadClientDefinition downloadClient, Do
|
||||
|
||||
if (parsedMovieInfo != null)
|
||||
{
|
||||
trackedDownload.RemoteMovie = _parsingService.Map(parsedMovieInfo, "", null);
|
||||
trackedDownload.RemoteMovie = _parsingService.Map(parsedMovieInfo, "", null).RemoteMovie;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ public bool HasReleases(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;
|
||||
}
|
||||
|
@ -25,11 +25,11 @@ public static class Parser
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//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))))))\)?",
|
||||
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+|_|$)(?!\\)\(?(?<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),*/
|
||||
|
||||
//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]
|
||||
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<year>(\[\w *\])))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
@ -51,7 +51,7 @@ public static class Parser
|
||||
private static readonly Regex[] ReportMovieTitleLenientRegexBefore = new[]
|
||||
{
|
||||
//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[]
|
||||
@ -321,6 +321,10 @@ public static class Parser
|
||||
private static readonly Regex DuplicateSpacesRegex = new Regex(@"\s{2,}", 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 Dictionary<String, String> _umlautMappings = new Dictionary<string, string>
|
||||
@ -441,6 +445,11 @@ public static ParsedMovieInfo ParseMovieTitle(string title, bool isLenient, bool
|
||||
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);
|
||||
@ -482,6 +491,53 @@ public static ParsedMovieInfo ParseMovieTitle(string title, bool isLenient, bool
|
||||
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)
|
||||
{
|
||||
var match = ReportImdbId.Match(title);
|
||||
@ -499,6 +555,19 @@ public static string ParseImdbId(string title)
|
||||
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)
|
||||
{
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.DataAugmentation.Scene;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@ -24,7 +25,7 @@ public interface IParsingService
|
||||
Movie GetMovie(string title);
|
||||
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
||||
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);
|
||||
ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
||||
}
|
||||
@ -218,23 +219,18 @@ public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tv
|
||||
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
|
||||
{
|
||||
ParsedMovieInfo = parsedMovieInfo,
|
||||
};
|
||||
var result = GetMovie(parsedMovieInfo, imdbId, searchCriteria);
|
||||
|
||||
var movie = GetMovie(parsedMovieInfo, imdbId, searchCriteria);
|
||||
|
||||
if (movie == null)
|
||||
{
|
||||
return remoteMovie;
|
||||
if (result == null) {
|
||||
result = new MappingResult {MappingResultType = MappingResultType.Unknown};
|
||||
result.Movie = null;
|
||||
}
|
||||
|
||||
remoteMovie.Movie = movie;
|
||||
result.RemoteMovie.ParsedMovieInfo = parsedMovieInfo;
|
||||
|
||||
return remoteMovie;
|
||||
return result;
|
||||
}
|
||||
|
||||
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds)
|
||||
@ -351,77 +347,113 @@ private ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, Series series)
|
||||
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?
|
||||
MappingResult result = null;
|
||||
if (!String.IsNullOrWhiteSpace(imdbId) && imdbId != "0")
|
||||
{
|
||||
Movie movieByImDb;
|
||||
if (TryGetMovieByImDbId(parsedMovieInfo, imdbId, out movieByImDb))
|
||||
if (TryGetMovieByImDbId(parsedMovieInfo, imdbId, out result))
|
||||
{
|
||||
return movieByImDb;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
Movie movieBySearchCriteria;
|
||||
if (TryGetMovieBySearchCriteria(parsedMovieInfo, searchCriteria, out movieBySearchCriteria))
|
||||
if (TryGetMovieBySearchCriteria(parsedMovieInfo, searchCriteria, out result))
|
||||
{
|
||||
return movieBySearchCriteria;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Movie movieByTitleAndOrYear;
|
||||
if (TryGetMovieByTitleAndOrYear(parsedMovieInfo, out movieByTitleAndOrYear))
|
||||
{
|
||||
return movieByTitleAndOrYear;
|
||||
}
|
||||
TryGetMovieByTitleAndOrYear(parsedMovieInfo, out result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// nothing found up to here => logging that and returning null
|
||||
_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.
|
||||
if (movie != null && parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year != movie.Year)
|
||||
{
|
||||
movie = null;
|
||||
result = new MappingResult { Movie = movie, MappingResultType = MappingResultType.WrongYear};
|
||||
return false;
|
||||
}
|
||||
if (movie != null) {
|
||||
result = new MappingResult { Movie = movie };
|
||||
} else {
|
||||
result = new MappingResult { Movie = movie, MappingResultType = MappingResultType.TitleNotFound};
|
||||
}
|
||||
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;
|
||||
Movie movieByTitleAndOrYear = null;
|
||||
|
||||
if (parsedMovieInfo.Year > 1800)
|
||||
{
|
||||
movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle, parsedMovieInfo.Year);
|
||||
if (isNotNull(movieByTitleAndOrYear))
|
||||
{
|
||||
result = new MappingResult { Movie = movieByTitleAndOrYear };
|
||||
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;
|
||||
}
|
||||
|
||||
movieByTitleAndOrYear = _movieService.FindByTitle(parsedMovieInfo.MovieTitle);
|
||||
if (isNotNull(movieByTitleAndOrYear))
|
||||
{
|
||||
result = new MappingResult { Movie = movieByTitleAndOrYear };
|
||||
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;
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
possibleTitles.Add(searchCriteria.Movie.CleanTitle);
|
||||
@ -445,7 +477,7 @@ private bool TryGetMovieBySearchCriteria(ParsedMovieInfo parsedMovieInfo, Search
|
||||
string arabicNumeral = numeralMapping.ArabicNumeralAsString;
|
||||
string romanNumeral = numeralMapping.RomanNumeralLowerCase;
|
||||
|
||||
_logger.Debug(cleanTitle);
|
||||
//_logger.Debug(cleanTitle);
|
||||
|
||||
if (title.Replace(arabicNumeral, romanNumeral) == parsedMovieInfo.MovieTitle.CleanSeriesTitle())
|
||||
{
|
||||
@ -460,11 +492,42 @@ private bool TryGetMovieBySearchCriteria(ParsedMovieInfo parsedMovieInfo, Search
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -693,29 +756,86 @@ private List<Episode> GetStandardEpisodes(Series series, ParsedEpisodeInfo parse
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
public int? ParsedYear { get; set; }
|
||||
|
||||
override public string Reason()
|
||||
{
|
||||
if (ParsedYear.HasValue && ParsedYear > 1800)
|
||||
{
|
||||
return $"Expected {ExpectedYear}, but found {ParsedYear} for year";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Did not find a valid year";
|
||||
}
|
||||
}
|
||||
Unknown = -1,
|
||||
Success = 0,
|
||||
SuccessLenientMapping = 1,
|
||||
WrongYear = 2,
|
||||
WrongTitle = 3,
|
||||
TitleNotFound = 4,
|
||||
NotParsable = 5,
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ public interface IMovieService
|
||||
Movie FindByImdbId(string imdbid);
|
||||
Movie FindByTitle(string title);
|
||||
Movie FindByTitle(string title, int year);
|
||||
Movie FindByTitleInexact(string title);
|
||||
Movie FindByTitleInexact(string title, int? year);
|
||||
Movie FindByTitleSlug(string slug);
|
||||
bool MovieExists(Movie movie);
|
||||
Movie GetMovieByFileId(int fileId);
|
||||
@ -238,20 +238,16 @@ public Movie FindByImdbId(string 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
|
||||
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())
|
||||
{
|
||||
// no movie matched
|
||||
return null;
|
||||
}
|
||||
if (list.Count == 1)
|
||||
{
|
||||
// return the first movie if there is only one
|
||||
return list.Single();
|
||||
return list;
|
||||
}
|
||||
// build ordered list of movie by position in the search string
|
||||
var query =
|
||||
@ -265,21 +261,34 @@ public Movie FindByTitleInexact(string title)
|
||||
.ToList()
|
||||
.OrderBy(s => s.position)
|
||||
.ThenByDescending(s => s.length)
|
||||
.Select(s => s.movie)
|
||||
.ToList();
|
||||
|
||||
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
public Movie FindByTitleInexact(string title)
|
||||
{
|
||||
var query = FindByTitleInexactAll(title);
|
||||
// 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
|
||||
var match = query.First().movie;
|
||||
var match = query.First();
|
||||
|
||||
_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);
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
public Movie FindByTitleInexact(string title, int? year)
|
||||
{
|
||||
return FindByTitleInexactAll(title).FirstWithYear(year);
|
||||
}
|
||||
|
||||
public Movie FindByTitle(string title, int year)
|
||||
{
|
||||
return _movieRepository.FindByTitle(title.CleanSeriesTitle(), year);
|
||||
|
@ -1,7 +1,11 @@
|
||||
How strict the Parser should be. (Note: Strict is strongly recommended!)
|
||||
<br><br>
|
||||
<h5><b>How strict the Parser should be. (Note: Strict is strongly recommended!)</b></h5>
|
||||
<br>
|
||||
<b>Strict:</b> Just as before, year must immediately follow title.
|
||||
<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>
|
||||
<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…
Reference in New Issue
Block a user