From 8aa6969aeef1a9fa9b4a273b64a2fa6af4e6d1ae Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 6 Apr 2015 18:43:29 -0700 Subject: [PATCH] Fixed: Improved special episode parsing for multiple matching titles --- .../FindEpisodeByTitleFixture.cs | 32 +++++++++++++++---- src/NzbDrone.Core/Parser/Parser.cs | 9 ++++-- src/NzbDrone.Core/Tv/EpisodeService.cs | 28 +++++++++------- 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/NzbDrone.Core.Test/TvTests/EpisodeServiceTests/FindEpisodeByTitleFixture.cs b/src/NzbDrone.Core.Test/TvTests/EpisodeServiceTests/FindEpisodeByTitleFixture.cs index b27b45e32..a3eb4cb8d 100644 --- a/src/NzbDrone.Core.Test/TvTests/EpisodeServiceTests/FindEpisodeByTitleFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/EpisodeServiceTests/FindEpisodeByTitleFixture.cs @@ -14,32 +14,50 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeServiceTests [TestFixture] public class FindEpisodeByTitleFixture : CoreTest { - private Episode _episode; + private List _episodes; [SetUp] public void Setup() { - _episode = Builder.CreateNew().Build(); + _episodes = Builder.CreateListOfSize(5) + .Build() + .ToList(); } - private void GivenEpisodeTitle(string title) + private void GivenEpisodesWithTitles(params string[] titles) { - _episode.Title = title; + for (int i = 0; i < titles.Count(); i++) + { + _episodes[i].Title = titles[i]; + } Mocker.GetMock() .Setup(s => s.GetEpisodes(It.IsAny(), It.IsAny())) - .Returns(new List { _episode }); + .Returns(_episodes); } [Test] public void should_find_episode_by_title() { - GivenEpisodeTitle("A Journey to the Highlands"); + const string expectedTitle = "A Journey to the Highlands"; + GivenEpisodesWithTitles(expectedTitle); Subject.FindEpisodeByTitle(1, 1, "Downton.Abbey.A.Journey.To.The.Highlands.720p.BluRay.x264-aAF") .Title .Should() - .Be(_episode.Title); + .Be(expectedTitle); + } + + [Test] + public void should_prefer_longer_match() + { + const string expectedTitle = "Inside The Walking Dead: Walker University"; + GivenEpisodesWithTitles("Inside The Walking Dead", expectedTitle); + + Subject.FindEpisodeByTitle(1, 1, "The.Walking.Dead.S04.Special.Inside.The.Walking.Dead.Walker.University.720p.HDTV.x264-W4F") + .Title + .Should() + .Be(expectedTitle); } } } diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index e77ca6437..9c8dbf67f 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -357,9 +357,12 @@ namespace NzbDrone.Core.Parser public static string NormalizeEpisodeTitle(string title) { - return SpecialEpisodeWordRegex.Replace(title, String.Empty) - .Trim() - .ToLower(); + title = SpecialEpisodeWordRegex.Replace(title, String.Empty); + title = PunctuationRegex.Replace(title, " "); + title = DuplicateSpacesRegex.Replace(title, " "); + + return title.Trim() + .ToLower(); } public static string NormalizeTitle(string title) diff --git a/src/NzbDrone.Core/Tv/EpisodeService.cs b/src/NzbDrone.Core/Tv/EpisodeService.cs index 991fc2f00..7c6932aa2 100644 --- a/src/NzbDrone.Core/Tv/EpisodeService.cs +++ b/src/NzbDrone.Core/Tv/EpisodeService.cs @@ -17,7 +17,7 @@ namespace NzbDrone.Core.Tv List GetEpisodes(IEnumerable ids); Episode FindEpisode(int seriesId, int seasonNumber, int episodeNumber); Episode FindEpisode(int seriesId, int absoluteEpisodeNumber); - Episode FindEpisodeByTitle(int seriesId, int seasonNumber, string episodeTitle); + Episode FindEpisodeByTitle(int seriesId, int seasonNumber, string releaseTitle); List FindEpisodesBySceneNumbering(int seriesId, int seasonNumber, int episodeNumber); Episode FindEpisodeBySceneNumbering(int seriesId, int sceneAbsoluteEpisodeNumber); Episode GetEpisode(int seriesId, String date); @@ -103,19 +103,25 @@ namespace NzbDrone.Core.Tv return _episodeRepository.GetEpisodes(seriesId, seasonNumber); } - public Episode FindEpisodeByTitle(int seriesId, int seasonNumber, string episodeTitle) + public Episode FindEpisodeByTitle(int seriesId, int seasonNumber, string releaseTitle) { // TODO: can replace this search mechanism with something smarter/faster/better - var search = Parser.Parser.NormalizeEpisodeTitle(episodeTitle).Replace(".", " "); + var normalizedReleaseTitle = Parser.Parser.NormalizeEpisodeTitle(releaseTitle).Replace(".", " "); + var episodes = _episodeRepository.GetEpisodes(seriesId, seasonNumber); - return _episodeRepository.GetEpisodes(seriesId, seasonNumber) - .FirstOrDefault(e => - { - // normalize episode title - var title = Parser.Parser.NormalizeEpisodeTitle(e.Title); - // find episode title within search string - return (title.Length > 0) && search.Contains(title); - }); + var query = episodes.Select( + episode => new + { + Position = normalizedReleaseTitle.IndexOf(Parser.Parser.NormalizeEpisodeTitle(episode.Title), StringComparison.CurrentCultureIgnoreCase), + Length = Parser.Parser.NormalizeEpisodeTitle(episode.Title).Length, + Episode = episode + }) + .Where(e => e.Episode.Title.Length > 0 && e.Position >= 0) + .OrderBy(e => e.Position) + .ThenByDescending(e => e.Length) + .ToList(); + + return query.First().Episode; } public List EpisodesWithFiles(int seriesId)