diff --git a/.idea/Sonarr.iml b/.idea/Sonarr.iml index aeec84bf6..fdd47ecb3 100644 --- a/.idea/Sonarr.iml +++ b/.idea/Sonarr.iml @@ -20,6 +20,5 @@ - \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml index b8387eb1b..8ca9d74b6 100644 --- a/.idea/jsLibraryMappings.xml +++ b/.idea/jsLibraryMappings.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/libraries/Sonarr_node_modules.xml b/.idea/libraries/Sonarr_node_modules.xml deleted file mode 100644 index 4eeebc5cc..000000000 --- a/.idea/libraries/Sonarr_node_modules.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/.idea/.idea.NzbDrone/.idea/.name b/src/.idea/.idea.NzbDrone/.idea/.name new file mode 100644 index 000000000..37baec0ab --- /dev/null +++ b/src/.idea/.idea.NzbDrone/.idea/.name @@ -0,0 +1 @@ +NzbDrone \ No newline at end of file diff --git a/src/.idea/.idea.NzbDrone/.idea/contentModel.xml b/src/.idea/.idea.NzbDrone/.idea/contentModel.xml new file mode 100644 index 000000000..02cf7ea47 --- /dev/null +++ b/src/.idea/.idea.NzbDrone/.idea/contentModel.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.NzbDrone/.idea/modules.xml b/src/.idea/.idea.NzbDrone/.idea/modules.xml new file mode 100644 index 000000000..364561fe7 --- /dev/null +++ b/src/.idea/.idea.NzbDrone/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.NzbDrone/riderModule.iml b/src/.idea/.idea.NzbDrone/riderModule.iml new file mode 100644 index 000000000..c84a9c732 --- /dev/null +++ b/src/.idea/.idea.NzbDrone/riderModule.iml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NzbDrone.Api/Config/IndexerConfigResource.cs b/src/NzbDrone.Api/Config/IndexerConfigResource.cs index 39bb43b74..884e2e839 100644 --- a/src/NzbDrone.Api/Config/IndexerConfigResource.cs +++ b/src/NzbDrone.Api/Config/IndexerConfigResource.cs @@ -1,5 +1,6 @@ using NzbDrone.Api.REST; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Parser; namespace NzbDrone.Api.Config { @@ -12,6 +13,7 @@ public class IndexerConfigResource : RestResource public int AvailabilityDelay { get; set; } public bool AllowHardcodedSubs { get; set; } public string WhitelistedHardcodedSubs { get; set; } + public ParsingLeniencyType ParsingLeniency { get; set; } } public static class IndexerConfigResourceMapper @@ -27,7 +29,7 @@ public static IndexerConfigResource ToResource(IConfigService model) AvailabilityDelay = model.AvailabilityDelay, AllowHardcodedSubs = model.AllowHardcodedSubs, WhitelistedHardcodedSubs = model.WhitelistedHardcodedSubs, - + ParsingLeniency = model.ParsingLeniency, }; } } diff --git a/src/NzbDrone.Api/Movies/MovieBulkImportModule.cs b/src/NzbDrone.Api/Movies/MovieBulkImportModule.cs index 7157c4399..94985738b 100644 --- a/src/NzbDrone.Api/Movies/MovieBulkImportModule.cs +++ b/src/NzbDrone.Api/Movies/MovieBulkImportModule.cs @@ -90,7 +90,7 @@ private Response Search() return mappedMovie; } - var parsedTitle = Parser.ParseMoviePath(f.Name); + var parsedTitle = Parser.ParseMoviePath(f.Name, false); if (parsedTitle == null) { m = new Core.Tv.Movie diff --git a/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs index e9624b153..9c0e75a1a 100644 --- a/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs @@ -51,9 +51,12 @@ public class LanguageParserFixture : CoreTest [TestCase("Castle.2009.S01E14.HDTV.XviD.HUN-LOL", Language.Hungarian)] [TestCase("The Danish Girl 2015", Language.English)] [TestCase("Passengers.2016.German.DL.AC3.Dubbed.1080p.WebHD.h264.iNTERNAL-PsO", Language.German)] + [TestCase("Der.Soldat.James.German.Bluray.FuckYou.Pso.Why.cant.you.follow.scene.rules.1998", Language.German)] + [TestCase("Passengers.German.DL.AC3.Dubbed..BluRay.x264-PsO", Language.German)] + [TestCase("Valana la Legende FRENCH BluRay 720p 2016 kjhlj", Language.French)] public void should_parse_language(string postTitle, Language language) { - var result = Parser.Parser.ParseMovieTitle(postTitle); + var result = Parser.Parser.ParseMovieTitle(postTitle, true); if (result == null) { Parser.Parser.ParseTitle(postTitle).Language.Should().Be(language); diff --git a/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs index 1feea9f4d..77736d7c5 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs @@ -63,6 +63,7 @@ public void should_remove_request_info_from_title(string postTitle, string title Parser.Parser.ParseTitle(postTitle).SeriesTitle.Should().Be(title); } + //Note: This assumes extended language parser is activated [TestCase("The.Man.from.U.N.C.L.E.2015.1080p.BluRay.x264-SPARKS", "The Man from U.N.C.L.E.")] [TestCase("1941.1979.EXTENDED.720p.BluRay.X264-AMIABLE", "1941")] [TestCase("MY MOVIE (2016) [R][Action, Horror][720p.WEB-DL.AVC.8Bit.6ch.AC3].mkv", "MY MOVIE")] @@ -76,22 +77,29 @@ public void should_remove_request_info_from_title(string postTitle, string title [TestCase("A.Movie.Name.(1998)", "A Movie Name")] [TestCase("Thor: The Dark World 2013", "Thor The Dark World")] [TestCase("Resident.Evil.The.Final.Chapter.2016", "Resident Evil The Final Chapter")] + [TestCase("Der.Soldat.James.German.Bluray.FuckYou.Pso.Why.cant.you.follow.scene.rules.1998", "Der Soldat James")] + [TestCase("Passengers.German.DL.AC3.Dubbed..BluRay.x264-PsO", "Passengers")] + [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)�[XviD - Ita Ac3 - SoftSub Ita]azione, spionaggio, thriller *Prima Visione* Team mulnic Tom Cruise", "Mission Impossible Rogue Nation")] public void should_parse_movie_title(string postTitle, string title) { - Parser.Parser.ParseMovieTitle(postTitle).MovieTitle.Should().Be(title); + Parser.Parser.ParseMovieTitle(postTitle, true).MovieTitle.Should().Be(title); } [TestCase("1941.1979.EXTENDED.720p.BluRay.X264-AMIABLE", 1979)] + [TestCase("Valana la Legende FRENCH BluRay 720p 2016 kjhlj", 2016)] + [TestCase("Der.Soldat.James.German.Bluray.FuckYou.Pso.Why.cant.you.follow.scene.rules.1998", 1998)] public void should_parse_movie_year(string postTitle, int year) { - Parser.Parser.ParseMovieTitle(postTitle).Year.Should().Be(year); + Parser.Parser.ParseMovieTitle(postTitle, false).Year.Should().Be(year); } [TestCase("The Danish Girl 2015")] [TestCase("The.Danish.Girl.2015.1080p.BluRay.x264.DTS-HD.MA.5.1-RARBG")] public void should_not_parse_language_in_movie_title(string postTitle) { - Parser.Parser.ParseMovieTitle(postTitle).Language.Should().Be(Language.English); + Parser.Parser.ParseMovieTitle(postTitle, false).Language.Should().Be(Language.English); } [TestCase("Prometheus 2012 Directors Cut", "Directors Cut")] @@ -117,9 +125,9 @@ public void should_not_parse_language_in_movie_title(string postTitle) [TestCase("Prometheus Extended Directors Cut Fan Edit 2012", "Extended Directors Cut Fan Edit")] [TestCase("Prometheus Director's Cut 2012", "Director's Cut")] [TestCase("Prometheus Directors Cut 2012", "Directors Cut")] - [TestCase("Prometheus.(Extended.Theatrical.Version.IMAX).BluRay.1080p.2012.asdf", "Extended Theatrical Version IMAX")] + [TestCase("Prometheus.(Extended.Theatrical.Version.IMAX).2012.BluRay.1080p.asdf", "Extended Theatrical Version IMAX")] [TestCase("2001 A Space Odyssey Director's Cut (1968).mkv", "Director's Cut")] - [TestCase("2001: A Space Odyssey (Extended Directors Cut FanEdit) Bluray 1080p 1968", "Extended Directors Cut FanEdit")] + [TestCase("2001: A Space Odyssey (Extended Directors Cut FanEdit) 1968 Bluray 1080p", "Extended Directors Cut FanEdit")] [TestCase("A Fake Movie 2035 Directors 2012.mkv", "Directors")] [TestCase("Blade Runner Director's Cut 2049.mkv", "Director's Cut")] [TestCase("Prometheus 50th Anniversary Edition 2012.mkv", "50th Anniversary Edition")] @@ -129,7 +137,7 @@ public void should_not_parse_language_in_movie_title(string postTitle) [TestCase("Fake Movie 2016 Final Cut ", "Final Cut")] public void should_parse_edition(string postTitle, string edition) { - Parser.Parser.ParseMovieTitle(postTitle).Edition.Should().Be(edition); + Parser.Parser.ParseMovieTitle(postTitle, false).Edition.Should().Be(edition); } } } diff --git a/src/NzbDrone.Core.Test/ParserTests/RomanNumeralTests/RomanNumeralConversionFixture.cs b/src/NzbDrone.Core.Test/ParserTests/RomanNumeralTests/RomanNumeralConversionFixture.cs index 348ec221f..9b86f6811 100644 --- a/src/NzbDrone.Core.Test/ParserTests/RomanNumeralTests/RomanNumeralConversionFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/RomanNumeralTests/RomanNumeralConversionFixture.cs @@ -28,7 +28,7 @@ public void PopulateDictionaryWithProvenValues() [Test(Description = "Converts the supported range [1-3999] of Arabic to Roman numerals.")] [Order(0)] - public void should_convert_arabic_numeral_to_roman_numeral([Range(1,3999)] int arabicNumeral) + public void should_convert_arabic_numeral_to_roman_numeral([Range(1,20)] int arabicNumeral) { RomanNumeral romanNumeral = new RomanNumeral(arabicNumeral); @@ -39,7 +39,7 @@ public void should_convert_arabic_numeral_to_roman_numeral([Range(1,3999)] int a [Test] [Order(1)] - public void should_convert_roman_numeral_to_arabic_numeral([Range(1, 3999)] int arabicNumeral) + public void should_convert_roman_numeral_to_arabic_numeral([Range(1, 20)] int arabicNumeral) { RomanNumeral romanNumeral = new RomanNumeral(_arabicToRomanNumeralsMapping[arabicNumeral]); diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index f25ae165d..ad7e3430a 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -8,6 +8,7 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Messaging.Events; using NzbDrone.Common.Http.Proxy; +using NzbDrone.Core.Parser; namespace NzbDrone.Core.Configuration { @@ -211,6 +212,12 @@ public string WhitelistedHardcodedSubs set { SetValue("WhitelistedHardcodedSubs", value); } } + public ParsingLeniencyType ParsingLeniency + { + get { return GetValueEnum("ParsingLeniency", ParsingLeniencyType.Strict); } + set { SetValue("ParsingLeniency", value); } + } + public bool RemoveCompletedDownloads { get { return GetValueBoolean("RemoveCompletedDownloads", false); } diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index 08a1c0572..4292c0f89 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using NzbDrone.Core.MediaFiles; using NzbDrone.Common.Http.Proxy; +using NzbDrone.Core.Parser; namespace NzbDrone.Core.Configuration { @@ -54,6 +55,7 @@ public interface IConfigService bool AllowHardcodedSubs { get; set; } string WhitelistedHardcodedSubs { get; set; } + ParsingLeniencyType ParsingLeniency { get; set; } int NetImportSyncInterval { get; set; } string ListSyncLevel { get; set; } diff --git a/src/NzbDrone.Core/Datastore/Migration/117_update_movie_file.cs b/src/NzbDrone.Core/Datastore/Migration/117_update_movie_file.cs index ee1db828d..b4a1011a7 100644 --- a/src/NzbDrone.Core/Datastore/Migration/117_update_movie_file.cs +++ b/src/NzbDrone.Core/Datastore/Migration/117_update_movie_file.cs @@ -26,13 +26,13 @@ private void SetSortTitles(IDbConnection conn, IDbTransaction tran) var id = seriesReader.GetInt32(0); var relativePath = seriesReader.GetString(1); - var result = Parser.Parser.ParseMovieTitle(relativePath); + var result = Parser.Parser.ParseMovieTitle(relativePath, false); var edition = ""; if (result != null) { - edition = Parser.Parser.ParseMovieTitle(relativePath).Edition; + edition = Parser.Parser.ParseMovieTitle(relativePath, false).Edition; } using (IDbCommand updateCmd = conn.CreateCommand()) diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs index 910cad30b..e205e4e61 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs @@ -69,7 +69,7 @@ private IEnumerable GetMovieDecisions(List report try { - var parsedMovieInfo = Parser.Parser.ParseMovieTitle(report.Title); + var parsedMovieInfo = Parser.Parser.ParseMovieTitle(report.Title, _configService.ParsingLeniency > 0); if (parsedMovieInfo != null && !parsedMovieInfo.MovieTitle.IsNullOrWhiteSpace()) { diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs index 575c3bfbc..ff82b1688 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs @@ -3,6 +3,7 @@ using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Extensions; +using NzbDrone.Core.Configuration; using NzbDrone.Core.History; using NzbDrone.Core.Parser; @@ -18,17 +19,20 @@ public class TrackedDownloadService : ITrackedDownloadService { private readonly IParsingService _parsingService; private readonly IHistoryService _historyService; + private readonly IConfigService _config; private readonly Logger _logger; private readonly ICached _cache; public TrackedDownloadService(IParsingService parsingService, ICacheManager cacheManager, IHistoryService historyService, + IConfigService config, Logger logger) { _parsingService = parsingService; _historyService = historyService; _cache = cacheManager.GetCache(GetType()); + _config = config; _logger = logger; } @@ -56,7 +60,7 @@ public TrackedDownload TrackDownload(DownloadClientDefinition downloadClient, Do try { - var parsedMovieInfo = Parser.Parser.ParseMovieTitle(trackedDownload.DownloadItem.Title); + var parsedMovieInfo = Parser.Parser.ParseMovieTitle(trackedDownload.DownloadItem.Title, _config.ParsingLeniency > 0); var historyItems = _historyService.FindByDownloadId(downloadItem.DownloadId); if (parsedMovieInfo != null) @@ -73,7 +77,7 @@ public TrackedDownload TrackDownload(DownloadClientDefinition downloadClient, Do trackedDownload.RemoteMovie == null || trackedDownload.RemoteMovie.Movie == null) { - parsedMovieInfo = Parser.Parser.ParseMovieTitle(firstHistoryItem.SourceTitle); + parsedMovieInfo = Parser.Parser.ParseMovieTitle(firstHistoryItem.SourceTitle, _config.ParsingLeniency > 0); if (parsedMovieInfo != null) { diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs index 0367dbe19..c75020b77 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs @@ -3,6 +3,7 @@ using System.Linq; using NLog; using NzbDrone.Common.Disk; +using NzbDrone.Core.Configuration; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.Parser; @@ -29,6 +30,7 @@ public class DownloadedMovieImportService : IDownloadedMovieImportService private readonly IMakeImportDecision _importDecisionMaker; private readonly IImportApprovedMovie _importApprovedMovie; private readonly IDetectSample _detectSample; + private readonly IConfigService _config; private readonly Logger _logger; public DownloadedMovieImportService(IDiskProvider diskProvider, @@ -38,6 +40,7 @@ public DownloadedMovieImportService(IDiskProvider diskProvider, IMakeImportDecision importDecisionMaker, IImportApprovedMovie importApprovedMovie, IDetectSample detectSample, + IConfigService config, Logger logger) { _diskProvider = diskProvider; @@ -47,6 +50,7 @@ public DownloadedMovieImportService(IDiskProvider diskProvider, _importDecisionMaker = importDecisionMaker; _importApprovedMovie = importApprovedMovie; _detectSample = detectSample; + _config = config; _logger = logger; } @@ -160,7 +164,7 @@ private List ProcessFolder(DirectoryInfo directoryInfo, ImportMode } var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); - var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name); + var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name, _config.ParsingLeniency > 0); if (folderInfo != null) { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedMovie.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedMovie.cs index d15b8dacf..5187f4017 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedMovie.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedMovie.cs @@ -154,7 +154,7 @@ private string GetSceneName(DownloadClientItem downloadClientItem, LocalMovie lo { var title = Parser.Parser.RemoveFileExtension(downloadClientItem.Title); - var parsedTitle = Parser.Parser.ParseMovieTitle(title); + var parsedTitle = Parser.Parser.ParseMovieTitle(title, false); if (parsedTitle != null) { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs index 1f117b525..d3bf2e99f 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs @@ -6,6 +6,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Instrumentation.Extensions; +using NzbDrone.Core.Configuration; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; using NzbDrone.Core.Download.TrackedDownloads; @@ -38,6 +39,7 @@ public class ManualImportService : IExecute, IManualImportS private readonly ITrackedDownloadService _trackedDownloadService; private readonly IDownloadedMovieImportService _downloadedMovieImportService; private readonly IEventAggregator _eventAggregator; + private readonly IConfigService _config; private readonly Logger _logger; public ManualImportService(IDiskProvider diskProvider, @@ -53,6 +55,7 @@ public ManualImportService(IDiskProvider diskProvider, ITrackedDownloadService trackedDownloadService, IDownloadedMovieImportService downloadedMovieImportService, IEventAggregator eventAggregator, + IConfigService config, Logger logger) { _diskProvider = diskProvider; @@ -68,6 +71,7 @@ public ManualImportService(IDiskProvider diskProvider, _trackedDownloadService = trackedDownloadService; _downloadedMovieImportService = downloadedMovieImportService; _eventAggregator = eventAggregator; + _config = config; _logger = logger; } @@ -116,7 +120,7 @@ private List ProcessFolder(string folder, string downloadId) return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList(); } - var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name); + var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name, _config.ParsingLeniency > 0); var seriesFiles = _diskScanService.GetVideoFiles(folder).ToList(); var decisions = _importDecisionMaker.GetImportDecisions(seriesFiles, series, folderInfo, SceneSource(series, folder), false); @@ -282,7 +286,7 @@ public void Execute(ManualImportCommand message) var file = message.Files[i]; var movie = _movieService.GetMovie(file.MovieId); - var parsedMovieInfo = Parser.Parser.ParseMoviePath(file.Path) ?? new ParsedMovieInfo(); + var parsedMovieInfo = Parser.Parser.ParseMoviePath(file.Path, _config.ParsingLeniency > 0) ?? new ParsedMovieInfo(); var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path); var existingFile = movie.Path.IsParentPath(file.Path); diff --git a/src/NzbDrone.Core/MetadataSource/PreDB/PreDBService.cs b/src/NzbDrone.Core/MetadataSource/PreDB/PreDBService.cs index 68b3b3383..e32294add 100644 --- a/src/NzbDrone.Core/MetadataSource/PreDB/PreDBService.cs +++ b/src/NzbDrone.Core/MetadataSource/PreDB/PreDBService.cs @@ -119,7 +119,7 @@ private List FindMatchesToResults(List results) foreach (PreDBResult result in results) { - var parsedInfo = Parser.Parser.ParseMovieTitle(result.Title); + var parsedInfo = Parser.Parser.ParseMovieTitle(result.Title, true); if (parsedInfo != null) { @@ -178,7 +178,7 @@ public bool HasReleases(Movie movie) foreach (PreDBResult result in results) { - var parsed = Parser.Parser.ParseMovieTitle(result.Title); + var parsed = Parser.Parser.ParseMovieTitle(result.Title, true); if (parsed == null) { parsed = new Parser.Model.ParsedMovieInfo { MovieTitle = result.Title, Year = 0 }; diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index f6db9011f..7ad1f82be 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -414,7 +414,7 @@ public List SearchForNewMovie(string title) lowerTitle = lowerTitle.Replace(".", ""); - var parserResult = Parser.Parser.ParseMovieTitle(title, true); + var parserResult = Parser.Parser.ParseMovieTitle(title, true, true); var yearTerm = ""; diff --git a/src/NzbDrone.Core/NetImport/RSSImport/RSSImportParser.cs b/src/NzbDrone.Core/NetImport/RSSImport/RSSImportParser.cs index b874fefd5..e6dee4a25 100644 --- a/src/NzbDrone.Core/NetImport/RSSImport/RSSImportParser.cs +++ b/src/NzbDrone.Core/NetImport/RSSImport/RSSImportParser.cs @@ -141,7 +141,7 @@ protected virtual Movie ProcessItem(XElement item, Movie releaseInfo) } releaseInfo.Title = title; - var result = Parser.Parser.ParseMovieTitle(title); + var result = Parser.Parser.ParseMovieTitle(title, false);//Depreciated anyways if (result != null) { diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 49dfc097a..2b4e26eb8 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -997,6 +997,7 @@ + @@ -1381,4 +1382,4 @@ --> - + \ No newline at end of file diff --git a/src/NzbDrone.Core/Organizer/FileNameValidationService.cs b/src/NzbDrone.Core/Organizer/FileNameValidationService.cs index 697f72bbb..ef2230c45 100644 --- a/src/NzbDrone.Core/Organizer/FileNameValidationService.cs +++ b/src/NzbDrone.Core/Organizer/FileNameValidationService.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using FluentValidation.Results; +using NzbDrone.Core.Configuration; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Tv; @@ -21,7 +22,7 @@ public class FileNameValidationService : IFilenameValidationService public ValidationFailure ValidateMovieFilename(SampleResult sampleResult) { var validationFailure = new ValidationFailure("MovieFormat", ERROR_MESSAGE); - var parsedMovieInfo = Parser.Parser.ParseMovieTitle(sampleResult.FileName); + var parsedMovieInfo = Parser.Parser.ParseMovieTitle(sampleResult.FileName, false); //We are not lenient when testing naming schemes if(parsedMovieInfo == null) { diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index e9504fd96..c23b8441b 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -7,8 +7,10 @@ using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Common.Instrumentation; +using NzbDrone.Core.Configuration; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Tv; +using TinyIoC; namespace NzbDrone.Core.Parser { @@ -19,16 +21,16 @@ public static class Parser private static readonly Regex[] ReportMovieTitleRegex = new[] { //Special, Despecialized, etc. Edition Movies, e.g: Mission.Impossible.3.Special.Edition.2011 - new Regex(@"^(?(?![(\[]).+?)?(?:(?:[-_\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))))))\)?.+(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)", + new Regex(@"^(?<title>(?![(\[]).+?)?(?:(?:[-_\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))))))\)?.{1,3}(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)", 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), - + //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), - + //PassThePopcorn Torrent names: Star.Wars[PassThePopcorn] new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<year>(\[\w *\])))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled), @@ -45,6 +47,17 @@ public static class Parser //When year comes first. new Regex(@"^(?:(?:[-_\W](?<![)!]))*(?<year>(19|20)\d{2}(?!p|i|\d+|\W\d+)))+(\W+|_|$)(?<title>.+?)?$") }; + + 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), + }; + + private static readonly Regex[] ReportMovieTitleLenientRegexAfter = new Regex[] + { + + }; private static readonly Regex[] ReportTitleRegex = new[] { @@ -338,29 +351,29 @@ public static ParsedEpisodeInfo ParsePath(string path) return result; } - public static ParsedMovieInfo ParseMoviePath(string path) + public static ParsedMovieInfo ParseMoviePath(string path, bool isLenient) { var fileInfo = new FileInfo(path); - var result = ParseMovieTitle(fileInfo.Name, true); + var result = ParseMovieTitle(fileInfo.Name, isLenient, true); if (result == null) { Logger.Debug("Attempting to parse episode info using directory and file names. {0}", fileInfo.Directory.Name); - result = ParseMovieTitle(fileInfo.Directory.Name + " " + fileInfo.Name); + result = ParseMovieTitle(fileInfo.Directory.Name + " " + fileInfo.Name, isLenient); } if (result == null) { Logger.Debug("Attempting to parse episode info using directory name. {0}", fileInfo.Directory.Name); - result = ParseMovieTitle(fileInfo.Directory.Name + fileInfo.Extension); + result = ParseMovieTitle(fileInfo.Directory.Name + fileInfo.Extension, isLenient); } return result; } - public static ParsedMovieInfo ParseMovieTitle(string title, bool isDir = false) + public static ParsedMovieInfo ParseMovieTitle(string title, bool isLenient, bool isDir = false) { ParsedMovieInfo realResult = null; @@ -368,8 +381,6 @@ public static ParsedMovieInfo ParseMovieTitle(string title, bool isDir = false) { if (!ValidateBeforeParsing(title)) return null; - //title = title.Replace(" ", "."); //TODO: Determine if this breaks something. However, it shouldn't. - Logger.Debug("Parsing string '{0}'", title); if (ReversedTitleRegex.IsMatch(title)) @@ -398,6 +409,13 @@ public static ParsedMovieInfo ParseMovieTitle(string title, bool isDir = false) allRegexes.AddRange(ReportMovieTitleFolderRegex); } + if (isLenient) + { + allRegexes.InsertRange(0, ReportMovieTitleLenientRegexBefore); + + allRegexes.AddRange(ReportMovieTitleLenientRegexAfter); + } + foreach (var regex in allRegexes) { var match = regex.Matches(simpleTitle); diff --git a/src/NzbDrone.Core/Parser/ParsingLeniency.cs b/src/NzbDrone.Core/Parser/ParsingLeniency.cs new file mode 100644 index 000000000..188c6bc47 --- /dev/null +++ b/src/NzbDrone.Core/Parser/ParsingLeniency.cs @@ -0,0 +1,9 @@ +namespace NzbDrone.Core.Parser +{ + public enum ParsingLeniencyType + { + Strict = 0, + ParsingLenient = 1, + MappingLenient = 2, + } +} diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index de9fa4e05..1f27d6a0d 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -4,6 +4,7 @@ using System.Linq; using NLog; using NzbDrone.Common.Extensions; +using NzbDrone.Core.Configuration; using NzbDrone.Core.DataAugmentation.Scene; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.MediaFiles; @@ -34,6 +35,7 @@ public class ParsingService : IParsingService private readonly ISeriesService _seriesService; private readonly ISceneMappingService _sceneMappingService; private readonly IMovieService _movieService; + private readonly IConfigService _config; private readonly Logger _logger; private static HashSet<ArabicRomanNumeral> _arabicRomanNumeralMappings; @@ -42,12 +44,14 @@ public ParsingService(IEpisodeService episodeService, ISeriesService seriesService, ISceneMappingService sceneMappingService, IMovieService movieService, + IConfigService configService, Logger logger) { _episodeService = episodeService; _seriesService = seriesService; _sceneMappingService = sceneMappingService; _movieService = movieService; + _config = configService; _logger = logger; if (_arabicRomanNumeralMappings == null) @@ -127,7 +131,7 @@ public LocalMovie GetLocalMovie(string filename, Movie movie, ParsedMovieInfo fo else { - parsedMovieInfo = Parser.ParseMoviePath(filename); + parsedMovieInfo = Parser.ParseMoviePath(filename, _config.ParsingLeniency > 0); } if (parsedMovieInfo == null) @@ -172,7 +176,7 @@ public Series GetSeries(string title) public Movie GetMovie(string title) { - var parsedMovieInfo = Parser.ParseMovieTitle(title); + var parsedMovieInfo = Parser.ParseMovieTitle(title, _config.ParsingLeniency > 0); if (parsedMovieInfo == null) { diff --git a/src/NzbDrone.Core/Parser/SceneChecker.cs b/src/NzbDrone.Core/Parser/SceneChecker.cs index d53cd8960..9bc7e3890 100644 --- a/src/NzbDrone.Core/Parser/SceneChecker.cs +++ b/src/NzbDrone.Core/Parser/SceneChecker.cs @@ -9,7 +9,7 @@ public static bool IsSceneTitle(string title) if (!title.Contains(".")) return false; if (title.Contains(" ")) return false; - var parsedTitle = Parser.ParseMovieTitle(title); + var parsedTitle = Parser.ParseMovieTitle(title, false); //We are not lenient when it comes to scene checking! if (parsedTitle == null || parsedTitle.ReleaseGroup == null || diff --git a/src/UI/.idea/runConfigurations/Debug___Chrome.xml b/src/UI/.idea/runConfigurations/Debug___Chrome.xml index 47bd06dc9..61758c015 100644 --- a/src/UI/.idea/runConfigurations/Debug___Chrome.xml +++ b/src/UI/.idea/runConfigurations/Debug___Chrome.xml @@ -1,21 +1,17 @@ <component name="ProjectRunConfigurationManager"> - <configuration default="false" name="Debug - Chrome" type="JavascriptDebugType" factoryName="JavaScript Debug" singleton="true" uri="http://localhost:8989"> - <mapping url="http://localhost:8989/Calendar" local-file="$PROJECT_DIR$/Calendar" /> - <mapping url="http://localhost:8989/MainMenuView.js" local-file="$PROJECT_DIR$/MainMenuView.js" /> - <mapping url="http://localhost:8989/Settings" local-file="$PROJECT_DIR$/Settings" /> - <mapping url="http://localhost:8989/Upcoming" local-file="$PROJECT_DIR$/Upcoming" /> + <configuration default="false" name="Debug - Chrome" type="JavascriptDebugType" factoryName="JavaScript Debug" singleton="true" engineId="98ca6316-2f89-46d9-a9e5-fa9e2b0625b3" uri="http://localhost:7878"> + <mapping url="http://localhost:8989/Wanted" local-file="$PROJECT_DIR$/Wanted" /> + <mapping url="http://localhost:8989" local-file="$PROJECT_DIR$" /> <mapping url="http://localhost:8989/app.js" local-file="$PROJECT_DIR$/app.js" /> <mapping url="http://localhost:8989/Mixins" local-file="$PROJECT_DIR$/Mixins" /> - <mapping url="http://localhost:8989/Wanted" local-file="$PROJECT_DIR$/Wanted" /> <mapping url="http://localhost:8989/Quality" local-file="$PROJECT_DIR$/Quality" /> - <mapping url="http://localhost:8989/Config.js" local-file="$PROJECT_DIR$/Config.js" /> <mapping url="http://localhost:8989/Shared" local-file="$PROJECT_DIR$/Shared" /> - <mapping url="http://localhost:8989/AddSeries" local-file="$PROJECT_DIR$/AddSeries" /> - <mapping url="http://localhost:8989/HeaderView.js" local-file="$PROJECT_DIR$/HeaderView.js" /> - <mapping url="http://localhost:8989" local-file="$PROJECT_DIR$" /> - <mapping url="http://localhost:8989/Routing.js" local-file="$PROJECT_DIR$/Routing.js" /> <mapping url="http://localhost:8989/Controller.js" local-file="$PROJECT_DIR$/Controller.js" /> + <mapping url="http://localhost:8989/Calendar" local-file="$PROJECT_DIR$/Calendar" /> <mapping url="http://localhost:8989/Series" local-file="$PROJECT_DIR$/Series" /> + <mapping url="http://localhost:8989/AddSeries" local-file="$PROJECT_DIR$/AddSeries" /> + <mapping url="http://localhost:8989/Settings" local-file="$PROJECT_DIR$/Settings" /> + <mapping url="http://localhost:8989/Config.js" local-file="$PROJECT_DIR$/Config.js" /> <RunnerSettings RunnerId="JavascriptDebugRunner" /> <ConfigurationWrapper RunnerId="JavascriptDebugRunner" /> <method /> diff --git a/src/UI/.idea/runConfigurations/Debug___Firefox.xml b/src/UI/.idea/runConfigurations/Debug___Firefox.xml deleted file mode 100644 index d9e99acc3..000000000 --- a/src/UI/.idea/runConfigurations/Debug___Firefox.xml +++ /dev/null @@ -1,23 +0,0 @@ -<component name="ProjectRunConfigurationManager"> - <configuration default="false" name="Debug - Firefox" type="JavascriptDebugType" factoryName="JavaScript Debug" singleton="true" engineId="firefox" uri="http://localhost:8989"> - <mapping url="http://localhost:8989/Calendar" local-file="$PROJECT_DIR$/Calendar" /> - <mapping url="http://localhost:8989/MainMenuView.js" local-file="$PROJECT_DIR$/MainMenuView.js" /> - <mapping url="http://localhost:8989/Settings" local-file="$PROJECT_DIR$/Settings" /> - <mapping url="http://localhost:8989/Upcoming" local-file="$PROJECT_DIR$/Upcoming" /> - <mapping url="http://localhost:8989/app.js" local-file="$PROJECT_DIR$/app.js" /> - <mapping url="http://localhost:8989/Mixins" local-file="$PROJECT_DIR$/Mixins" /> - <mapping url="http://localhost:8989/Wanted" local-file="$PROJECT_DIR$/Wanted" /> - <mapping url="http://localhost:8989/Config.js" local-file="$PROJECT_DIR$/Config.js" /> - <mapping url="http://localhost:8989/Quality" local-file="$PROJECT_DIR$/Quality" /> - <mapping url="http://localhost:8989/AddSeries" local-file="$PROJECT_DIR$/AddSeries" /> - <mapping url="http://localhost:8989/Shared" local-file="$PROJECT_DIR$/Shared" /> - <mapping url="http://localhost:8989/HeaderView.js" local-file="$PROJECT_DIR$/HeaderView.js" /> - <mapping url="http://localhost:8989" local-file="$PROJECT_DIR$" /> - <mapping url="http://localhost:8989/Routing.js" local-file="$PROJECT_DIR$/Routing.js" /> - <mapping url="http://localhost:8989/Controller.js" local-file="$PROJECT_DIR$/Controller.js" /> - <mapping url="http://localhost:8989/Series" local-file="$PROJECT_DIR$/Series" /> - <RunnerSettings RunnerId="JavascriptDebugRunner" /> - <ConfigurationWrapper RunnerId="JavascriptDebugRunner" /> - <method /> - </configuration> -</component> \ No newline at end of file diff --git a/src/UI/Settings/Indexers/Options/IndexerOptionsView.js b/src/UI/Settings/Indexers/Options/IndexerOptionsView.js index 190920b79..bb859dffe 100644 --- a/src/UI/Settings/Indexers/Options/IndexerOptionsView.js +++ b/src/UI/Settings/Indexers/Options/IndexerOptionsView.js @@ -9,7 +9,8 @@ var view = Marionette.ItemView.extend({ template : 'Settings/Indexers/Options/IndexerOptionsViewTemplate', ui : { - hcwhitelist : '.x-hcwhitelist', + hcwhitelist : '.x-hcwhitelist', + leniencyTooltip : '.x-leniency-tooltip', }, onRender : function() { @@ -18,6 +19,18 @@ var view = Marionette.ItemView.extend({ allowDuplicates: true, tagClass : 'label label-success' }); + + this.templateFunction = Marionette.TemplateCache.get('Settings/Indexers/Options/LeniencyTooltipTemplate'); + var content = this.templateFunction(); + + this.ui.leniencyTooltip.popover({ + content : content, + html : true, + trigger : 'hover', + title : 'Parsing Leniency Notes', + placement : 'right', + container : this.$el + }); }, }); diff --git a/src/UI/Settings/Indexers/Options/IndexerOptionsViewTemplate.hbs b/src/UI/Settings/Indexers/Options/IndexerOptionsViewTemplate.hbs index 16736cc7d..b43975d1a 100644 --- a/src/UI/Settings/Indexers/Options/IndexerOptionsViewTemplate.hbs +++ b/src/UI/Settings/Indexers/Options/IndexerOptionsViewTemplate.hbs @@ -91,6 +91,22 @@ </div> + <div class="form-group advanced-setting"> + <label class="col-sm-3 control-label">Parser Leniency</label> + + <div class="col-sm-1 col-sm-push-2 help-inline"> + <i class="icon-sonarr-form-info leniency-tooltip x-leniency-tooltip"/> + </div> + + <div class="col-sm-2 col-sm-pull-1"> + <select class="form-control" name="parsingLeniency"> + <option value="strict">Strict</option> + <option value="parsingLenient">Lenient Parsing</option> + <option value="mappingLenient">Lenient Mapping</option> + </select> + </div> + </div> + <legend>Availability Options</legend> <div class="form-group"> <label class="col-sm-3 control-label">Availability Delay</label> diff --git a/src/UI/Settings/Indexers/Options/LeniencyTooltipTemplate.hbs b/src/UI/Settings/Indexers/Options/LeniencyTooltipTemplate.hbs new file mode 100644 index 000000000..5c4b7ffcd --- /dev/null +++ b/src/UI/Settings/Indexers/Options/LeniencyTooltipTemplate.hbs @@ -0,0 +1,7 @@ +How strict the Parser should be. (Note: Strict is strongly recommended!) +<br><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) +<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>) \ No newline at end of file