Better parsing of Chistmas Specials

This commit is contained in:
Mark McDowall 2014-12-15 10:52:16 -08:00
parent 98b4027510
commit 83e3d7145f
9 changed files with 153 additions and 369 deletions

View File

@ -187,5 +187,13 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.IsSatisfiedBy(parseResult, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_true_for_special()
{
parseResultSingle.ParsedEpisodeInfo.Special = true;
Subject.IsSatisfiedBy(parseResultSingle, null).Accepted.Should().BeTrue();
}
}
}

View File

@ -296,8 +296,8 @@
<Compile Include="SeriesStatsTests\SeriesStatisticsFixture.cs" />
<Compile Include="ThingiProvider\ProviderBaseFixture.cs" />
<Compile Include="ThingiProviderTests\NullConfigFixture.cs" />
<Compile Include="TvTests\EpisodeProviderTests\EpisodeProviderTest_GetEpisodesByParseResult.cs" />
<Compile Include="TvTests\EpisodeProviderTests\HandleEpisodeFileDeletedFixture.cs" />
<Compile Include="TvTests\EpisodeServiceTests\FindEpisodeByTitleFixture.cs" />
<Compile Include="TvTests\EpisodeServiceTests\HandleEpisodeFileDeletedFixture.cs" />
<Compile Include="TvTests\EpisodeRepositoryTests\ByAirDateFixture.cs" />
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesBetweenDatesFixture.cs" />
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesRepositoryReadFixture.cs" />

View File

@ -1,277 +0,0 @@
/*
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.TvTests.EpisodeProviderTests
{
[TestFixture]
public class EpisodeProviderTest_GetEpisodesByParseResult : ObjectDbTest
{
private IEpisodeService episodeService;
private Series fakeSeries;
private Series fakeDailySeries;
private Episode fakeEpisode;
private Episode fakeDailyEpisode;
private Episode fakeEpisode2;
[SetUp]
public void Setup()
{
fakeSeries = Builder<Series>.CreateNew().Build();
fakeDailySeries = Builder<Series>.CreateNew()
.With(c => c.SeriesType = SeriesType.Daily)
.Build();
fakeEpisode = Builder<Episode>.CreateNew()
.With(e => e.SeriesId = fakeSeries.Id)
.With(e => e.Title = "Episode (1)")
.Build();
fakeEpisode2 = Builder<Episode>.CreateNew()
.With(e => e.SeriesId = fakeSeries.Id)
.With(e => e.SeasonNumber = fakeEpisode.SeasonNumber)
.With(e => e.EpisodeNumber = fakeEpisode.EpisodeNumber + 1)
.With(e => e.Title = "Episode (2)")
.Build();
fakeDailyEpisode = Builder<Episode>.CreateNew()
.With(e => e.SeriesId = fakeSeries.Id)
.With(e => e.AirDate = DateTime.Now.Date)
.With(e => e.Title = "Daily Episode 1")
.Build();
episodeService = Mocker.Resolve<EpisodeService>();
}
[Test]
public void existing_single_episode_should_return_single_existing_episode()
{
Db.Insert(fakeEpisode);
Db.Insert(fakeSeries);
var parseResult = new EpisodeParseResult
{
Series = fakeSeries,
SeasonNumber = fakeEpisode.SeasonNumber,
EpisodeNumbers = new List<int> { fakeEpisode.EpisodeNumber }
};
var ep = episodeService.GetEpisodesByParseResult(parseResult);
ep.Should().HaveCount(1);
parseResult.EpisodeTitle.Should().Be(fakeEpisode.Title);
VerifyEpisode(ep[0], fakeEpisode);
Db<Episode>().Should().HaveCount(1);
}
[Test]
public void single_none_existing_episode_should_return_nothing_and_add_nothing()
{
var parseResult = new EpisodeParseResult
{
Series = fakeSeries,
SeasonNumber = fakeEpisode.SeasonNumber,
EpisodeNumbers = new List<int> { 10 }
};
var episode = episodeService.GetEpisodesByParseResult(parseResult);
episode.Should().BeEmpty();
Db.Fetch<Episode>().Should().HaveCount(0);
}
[Test]
public void single_none_existing_series_should_return_nothing_and_add_nothing()
{
var parseResult = new EpisodeParseResult
{
Series = fakeSeries,
SeasonNumber = 10,
EpisodeNumbers = new List<int> { 10 }
};
var episode = episodeService.GetEpisodesByParseResult(parseResult);
episode.Should().BeEmpty();
Db.Fetch<Episode>().Should().HaveCount(0);
}
[Test]
public void existing_multi_episode_should_return_all_episodes()
{
Db.Insert(fakeSeries);
Db.Insert(fakeEpisode);
Db.Insert(fakeEpisode2);
var parseResult = new EpisodeParseResult
{
Series = fakeSeries,
SeasonNumber = fakeEpisode.SeasonNumber,
EpisodeNumbers = new List<int> { fakeEpisode.EpisodeNumber, fakeEpisode2.EpisodeNumber }
};
var ep = episodeService.GetEpisodesByParseResult(parseResult);
ep.Should().HaveCount(2);
Db.Fetch<Episode>().Should().HaveCount(2);
VerifyEpisode(ep[0], fakeEpisode);
VerifyEpisode(ep[1], fakeEpisode2);
parseResult.EpisodeTitle.Should().Be("Episode");
}
[Test]
public void none_existing_multi_episode_should_not_return_or_add_anything()
{
var parseResult = new EpisodeParseResult
{
Series = fakeSeries,
SeasonNumber = fakeEpisode.SeasonNumber,
EpisodeNumbers = new List<int> { fakeEpisode.EpisodeNumber, fakeEpisode2.EpisodeNumber }
};
var ep = episodeService.GetEpisodesByParseResult(parseResult);
ep.Should().BeEmpty();
Db.Fetch<Episode>().Should().BeEmpty();
}
[Test]
public void GetEpisodeParseResult_should_return_empty_list_if_episode_list_is_null()
{
var episodes = episodeService.GetEpisodesByParseResult(new EpisodeParseResult());
episodes.Should().NotBeNull();
episodes.Should().BeEmpty();
}
[Test]
public void GetEpisodeParseResult_should_return_empty_list_if_episode_list_is_empty()
{
var episodes = episodeService.GetEpisodesByParseResult(new EpisodeParseResult { EpisodeNumbers = new List<int>() });
episodes.Should().NotBeNull();
episodes.Should().BeEmpty();
}
[Test]
public void should_return_single_episode_when_air_date_is_provided()
{
Db.Insert(fakeSeries);
Db.Insert(fakeDailyEpisode);
var episodes = episodeService.GetEpisodesByParseResult(new EpisodeParseResult { AirDate = DateTime.Today, Series = fakeDailySeries });
episodes.Should().HaveCount(1);
VerifyEpisode(episodes[0], fakeDailyEpisode);
Db.Fetch<Episode>().Should().HaveCount(1);
}
[Test]
public void should_not_add_episode_when_episode_doesnt_exist()
{
var episodes = episodeService.GetEpisodesByParseResult(new EpisodeParseResult { AirDate = DateTime.Today, Series = fakeDailySeries });
episodes.Should().HaveCount(0);
Db.Fetch<Episode>().Should().HaveCount(0);
}
[Test]
public void GetEpisodeParseResult_should_return_single_title_for_multiple_episodes()
{
Db.Insert(fakeSeries);
Db.Insert(fakeEpisode);
Db.Insert(fakeEpisode2);
var parseResult = new EpisodeParseResult
{
Series = fakeSeries,
SeasonNumber = fakeEpisode.SeasonNumber,
EpisodeNumbers = new List<int> { fakeEpisode.EpisodeNumber, fakeEpisode2.EpisodeNumber }
};
var ep = episodeService.GetEpisodesByParseResult(parseResult);
ep.Should().HaveCount(2);
Db.Fetch<Episode>().Should().HaveCount(2);
VerifyEpisode(ep[0], fakeEpisode);
VerifyEpisode(ep[1], fakeEpisode2);
parseResult.EpisodeTitle.Should().Be("Episode");
}
[Test]
public void GetEpisodeParseResult_should_return_single_title_for_single_episode()
{
Db.Insert(fakeEpisode);
Db.Insert(fakeSeries);
var parseResult = new EpisodeParseResult
{
Series = fakeSeries,
SeasonNumber = fakeEpisode.SeasonNumber,
EpisodeNumbers = new List<int> { fakeEpisode.EpisodeNumber }
};
var ep = episodeService.GetEpisodesByParseResult(parseResult);
ep.Should().HaveCount(1);
Db.Fetch<Episode>().Should().HaveCount(1);
ep.First().ShouldHave().AllPropertiesBut(e => e.Series);
parseResult.EpisodeTitle.Should().Be(fakeEpisode.Title);
}
[Test]
public void GetEpisodeParseResult_should_return_nothing_when_series_is_not_daily_but_parsed_daily()
{
Db.Insert(fakeSeries);
var parseResult = new EpisodeParseResult
{
Series = fakeSeries,
AirDate = DateTime.Today
};
var ep = episodeService.GetEpisodesByParseResult(parseResult);
ep.Should().BeEmpty();
ExceptionVerification.ExpectedWarns(1);
}
private void VerifyEpisode(Episode actual, Episode excpected)
{
actual.ShouldHave().AllProperties().But(e => e.Series).But(e => e.EpisodeFile).EqualTo(excpected);
}
}
}
*/

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.TvTests.EpisodeServiceTests
{
[TestFixture]
public class FindEpisodeByTitleFixture : CoreTest<EpisodeService>
{
private Episode _episode;
[SetUp]
public void Setup()
{
_episode = Builder<Episode>.CreateNew().Build();
}
private void GivenEpisodeTitle(string title)
{
_episode.Title = title;
Mocker.GetMock<IEpisodeRepository>()
.Setup(s => s.GetEpisodes(It.IsAny<int>(), It.IsAny<int>()))
.Returns(new List<Episode> { _episode });
}
[Test]
public void should_find_episode_by_title()
{
GivenEpisodeTitle("A Journey to the Highlands");
Subject.FindEpisodeByTitle(1, 1, "Downton.Abbey.A.Journey.To.The.Highlands.720p.BluRay.x264-aAF")
.Title
.Should()
.Be(_episode.Title);
}
}
}

View File

@ -9,7 +9,7 @@ using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.TvTests.EpisodeProviderTests
namespace NzbDrone.Core.Test.TvTests.EpisodeServiceTests
{
[TestFixture]
public class HandleEpisodeFileDeletedFixture : CoreTest<EpisodeService>

View File

@ -36,6 +36,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return Decision.Accept();
}
if (subject.ParsedEpisodeInfo.Special)
{
_logger.Debug("Special release found, skipping size check.");
return Decision.Accept();
}
var qualityDefinition = _qualityDefinitionService.Get(quality);
var minSize = qualityDefinition.MinSize.Megabytes();

View File

@ -173,7 +173,7 @@ namespace NzbDrone.Core.Parser
private static readonly Regex WordDelimiterRegex = new Regex(@"(\s|\.|,|_|-|=|\|)+", RegexOptions.Compiled);
private static readonly Regex PunctuationRegex = new Regex(@"[^\w\s]", RegexOptions.Compiled);
private static readonly Regex CommonWordRegex = new Regex(@"\b(a|an|the|and|or|of)\b\s?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex SpecialEpisodeWordRegex = new Regex(@"\b(part|special|edition)\b\s?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex SpecialEpisodeWordRegex = new Regex(@"\b(part|special|edition|christmas)\b\s?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex DuplicateSpacesRegex = new Regex(@"\s{2,}", RegexOptions.Compiled);
private static readonly Regex RequestInfoRegex = new Regex(@"\[.+?\]", RegexOptions.Compiled);
@ -393,6 +393,90 @@ namespace NzbDrone.Core.Parser
return title;
}
public static Language ParseLanguage(string title)
{
var lowerTitle = title.ToLower();
if (lowerTitle.Contains("english"))
return Language.English;
if (lowerTitle.Contains("french"))
return Language.French;
if (lowerTitle.Contains("spanish"))
return Language.Spanish;
if (lowerTitle.Contains("danish"))
return Language.Danish;
if (lowerTitle.Contains("dutch"))
return Language.Dutch;
if (lowerTitle.Contains("japanese"))
return Language.Japanese;
if (lowerTitle.Contains("cantonese"))
return Language.Cantonese;
if (lowerTitle.Contains("mandarin"))
return Language.Mandarin;
if (lowerTitle.Contains("korean"))
return Language.Korean;
if (lowerTitle.Contains("russian"))
return Language.Russian;
if (lowerTitle.Contains("polish"))
return Language.Polish;
if (lowerTitle.Contains("vietnamese"))
return Language.Vietnamese;
if (lowerTitle.Contains("swedish"))
return Language.Swedish;
if (lowerTitle.Contains("norwegian"))
return Language.Norwegian;
if (lowerTitle.Contains("nordic"))
return Language.Norwegian;
if (lowerTitle.Contains("finnish"))
return Language.Finnish;
if (lowerTitle.Contains("turkish"))
return Language.Turkish;
if (lowerTitle.Contains("portuguese"))
return Language.Portuguese;
var match = LanguageRegex.Match(title);
if (match.Groups["italian"].Captures.Cast<Capture>().Any())
return Language.Italian;
if (match.Groups["german"].Captures.Cast<Capture>().Any())
return Language.German;
if (match.Groups["flemish"].Captures.Cast<Capture>().Any())
return Language.Flemish;
if (match.Groups["greek"].Captures.Cast<Capture>().Any())
return Language.Greek;
if (match.Groups["french"].Success)
return Language.French;
if (match.Groups["russian"].Success)
return Language.Russian;
if (match.Groups["dutch"].Success)
return Language.Dutch;
return Language.English;
}
private static SeriesTitleInfo GetSeriesTitleInfo(string title)
{
var seriesTitleInfo = new SeriesTitleInfo();
@ -539,90 +623,6 @@ namespace NzbDrone.Core.Parser
return result;
}
private static Language ParseLanguage(string title)
{
var lowerTitle = title.ToLower();
if (lowerTitle.Contains("english"))
return Language.English;
if (lowerTitle.Contains("french"))
return Language.French;
if (lowerTitle.Contains("spanish"))
return Language.Spanish;
if (lowerTitle.Contains("danish"))
return Language.Danish;
if (lowerTitle.Contains("dutch"))
return Language.Dutch;
if (lowerTitle.Contains("japanese"))
return Language.Japanese;
if (lowerTitle.Contains("cantonese"))
return Language.Cantonese;
if (lowerTitle.Contains("mandarin"))
return Language.Mandarin;
if (lowerTitle.Contains("korean"))
return Language.Korean;
if (lowerTitle.Contains("russian"))
return Language.Russian;
if (lowerTitle.Contains("polish"))
return Language.Polish;
if (lowerTitle.Contains("vietnamese"))
return Language.Vietnamese;
if (lowerTitle.Contains("swedish"))
return Language.Swedish;
if (lowerTitle.Contains("norwegian"))
return Language.Norwegian;
if (lowerTitle.Contains("nordic"))
return Language.Norwegian;
if (lowerTitle.Contains("finnish"))
return Language.Finnish;
if (lowerTitle.Contains("turkish"))
return Language.Turkish;
if (lowerTitle.Contains("portuguese"))
return Language.Portuguese;
var match = LanguageRegex.Match(title);
if (match.Groups["italian"].Captures.Cast<Capture>().Any())
return Language.Italian;
if (match.Groups["german"].Captures.Cast<Capture>().Any())
return Language.German;
if (match.Groups["flemish"].Captures.Cast<Capture>().Any())
return Language.Flemish;
if (match.Groups["greek"].Captures.Cast<Capture>().Any())
return Language.Greek;
if (match.Groups["french"].Success)
return Language.French;
if (match.Groups["russian"].Success)
return Language.Russian;
if (match.Groups["dutch"].Success)
return Language.Dutch;
return Language.English;
}
private static bool ValidateBeforeParsing(string title)
{
if (title.ToLower().Contains("password") && title.ToLower().Contains("yenc"))

View File

@ -312,7 +312,7 @@ namespace NzbDrone.Core.Parser
private ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, Series series)
{
// find special episode in series season 0
var episode = _episodeService.FindEpisodeByName(series.Id, 0, title);
var episode = _episodeService.FindEpisodeByTitle(series.Id, 0, title);
if (episode != null)
{
@ -326,6 +326,8 @@ namespace NzbDrone.Core.Parser
info.FullSeason = false;
info.Quality = QualityParser.ParseQuality(title);
info.ReleaseGroup = Parser.ParseReleaseGroup(title);
info.Language = Parser.ParseLanguage(title);
info.Special = true;
_logger.Info("Found special episode {0} for title '{1}'", info, title);
return info;

View File

@ -17,7 +17,7 @@ namespace NzbDrone.Core.Tv
List<Episode> GetEpisodes(IEnumerable<Int32> ids);
Episode FindEpisode(int seriesId, int seasonNumber, int episodeNumber);
Episode FindEpisode(int seriesId, int absoluteEpisodeNumber);
Episode FindEpisodeByName(int seriesId, int seasonNumber, string episodeTitle);
Episode FindEpisodeByTitle(int seriesId, int seasonNumber, string episodeTitle);
List<Episode> FindEpisodesBySceneNumbering(int seriesId, int seasonNumber, int episodeNumber);
Episode FindEpisodeBySceneNumbering(int seriesId, int sceneAbsoluteEpisodeNumber);
Episode GetEpisode(int seriesId, String date);
@ -103,10 +103,10 @@ namespace NzbDrone.Core.Tv
return _episodeRepository.GetEpisodes(seriesId, seasonNumber);
}
public Episode FindEpisodeByName(int seriesId, int seasonNumber, string episodeTitle)
public Episode FindEpisodeByTitle(int seriesId, int seasonNumber, string episodeTitle)
{
// TODO: can replace this search mechanism with something smarter/faster/better
var search = Parser.Parser.NormalizeEpisodeTitle(episodeTitle);
var search = Parser.Parser.NormalizeEpisodeTitle(episodeTitle).Replace(".", " ");
return _episodeRepository.GetEpisodes(seriesId, seasonNumber)
.FirstOrDefault(e =>