mirror of
https://github.com/Sonarr/Sonarr.git
synced 2024-12-16 11:37:58 +02:00
Merge branch 'regex-overhaul' into develop
This commit is contained in:
commit
e8bda2a85c
@ -120,7 +120,7 @@ public void PathParse_tests(string path, int season, int episode)
|
||||
ExceptionVerification.IgnoreWarns();
|
||||
}
|
||||
|
||||
[TestCase("[DmonHiro] The Severing Crime Edge - Cut 02 - Portrait Of Heresy [BD, 720p] [BE36E9E0]")]
|
||||
[TestCase("THIS SHOULD NEVER PARSE")]
|
||||
public void unparsable_title_should_log_warn_and_return_null(string title)
|
||||
{
|
||||
Parser.Parser.ParseTitle(title).Should().BeNull();
|
||||
@ -147,7 +147,9 @@ public void unparsable_title_should_log_warn_and_return_null(string title)
|
||||
[TestCase("Hell.on.Wheels.S02E09-E10.720p.HDTV.x264-EVOLVE", "Hell on Wheels", 2, new[] { 9, 10 })]
|
||||
[TestCase("Grey's Anatomy - 8x01_02 - Free Falling", "Grey's Anatomy", 8, new [] { 1,2 })]
|
||||
[TestCase("8x01_02 - Free Falling", "", 8, new[] { 1, 2 })]
|
||||
[TestCase("Kaamelott.S01E91-E100", "Kaamelott", 1,new[] { 91, 92, 93, 94, 95, 96, 97, 98, 99, 100 })]
|
||||
[TestCase("Kaamelott.S01E91-E100", "Kaamelott", 1, new[] { 91, 92, 93, 94, 95, 96, 97, 98, 99, 100 })]
|
||||
[TestCase("Neighbours.S29E161-E165.PDTV.x264-FQM", "Neighbours", 29, new[] { 161, 162, 163, 164, 165 })]
|
||||
[TestCase("Shortland.Street.S22E5363-E5366.HDTV.x264-FiHTV", "Shortland Street", 22, new[] { 5363, 5364, 5365, 5366 })]
|
||||
public void TitleParse_multi(string postTitle, string title, int season, int[] episodes)
|
||||
{
|
||||
var result = Parser.Parser.ParseTitle(postTitle);
|
||||
@ -174,7 +176,39 @@ public void parse_daily_episodes(string postTitle, string title, int year, int m
|
||||
result.Should().NotBeNull();
|
||||
result.SeriesTitle.Should().Be(title.CleanSeriesTitle());
|
||||
result.AirDate.Should().Be(airDate.ToString(Episode.AIR_DATE_FORMAT));
|
||||
result.EpisodeNumbers.Should().BeNull();
|
||||
result.EpisodeNumbers.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[TestCase("[SubDESU]_High_School_DxD_07_(1280x720_x264-AAC)_[6B7FD717]", "High School DxD", 7, 0, 0)]
|
||||
[TestCase("[Chihiro]_Working!!_-_06_[848x480_H.264_AAC][859EEAFA]", "Working!!", 6, 0, 0)]
|
||||
[TestCase("[Commie]_Senki_Zesshou_Symphogear_-_11_[65F220B4]", "Senki_Zesshou_Symphogear", 11, 0, 0)]
|
||||
[TestCase("[Underwater]_Rinne_no_Lagrange_-_12_(720p)_[5C7BC4F9]", "Rinne_no_Lagrange", 12, 0, 0)]
|
||||
[TestCase("[Commie]_Rinne_no_Lagrange_-_15_[E76552EA]", "Rinne_no_Lagrange", 15, 0, 0)]
|
||||
[TestCase("[HorribleSubs]_Hunter_X_Hunter_-_33_[720p]", "Hunter_X_Hunter", 33, 0, 0)]
|
||||
[TestCase("[HorribleSubs]_Fairy_Tail_-_145_[720p]", "Fairy_Tail", 145, 0, 0)]
|
||||
[TestCase("[HorribleSubs] Tonari no Kaibutsu-kun - 13 [1080p].mkv", "Tonari no Kaibutsu-kun", 13, 0, 0)]
|
||||
[TestCase("[Doremi].Yes.Pretty.Cure.5.Go.Go!.31.[1280x720].[C65D4B1F].mkv", "Yes.Pretty.Cure.5.Go.Go!", 31, 0, 0)]
|
||||
[TestCase("[K-F] One Piece 214", "One Piece", 214, 0, 0)]
|
||||
[TestCase("[K-F] One Piece S10E14 214", "One Piece", 214, 10, 14)]
|
||||
[TestCase("[K-F] One Piece 10x14 214", "One Piece", 214, 10, 14)]
|
||||
[TestCase("[K-F] One Piece 214 10x14", "One Piece", 214, 10, 14)]
|
||||
[TestCase("One Piece S10E14 214", "One Piece", 214, 10, 14)]
|
||||
[TestCase("One Piece 10x14 214", "One Piece", 214, 10, 14)]
|
||||
[TestCase("One Piece 214 10x14", "One Piece", 214, 10, 14)]
|
||||
[TestCase("214 One Piece 10x14", "One Piece", 214, 10, 14)]
|
||||
[TestCase("Bleach - 031 - The Resolution to Kill [Lunar].avi", "Bleach", 31, 0, 0)]
|
||||
[TestCase("Bleach - 031 - The Resolution to Kill [Lunar]", "Bleach", 31, 0, 0)]
|
||||
[TestCase("[ACX]Hack Sign 01 Role Play [Kosaka] [9C57891E].mkv", "Hack Sign", 1, 0, 0)]
|
||||
[TestCase("[SFW-sage] Bakuman S3 - 12 [720p][D07C91FC]", "Bakuman S3", 12, 0, 0)]
|
||||
[TestCase("ducktales_e66_time_is_money_part_one_marking_time", "DuckTales", 66, 0, 0)]
|
||||
public void parse_absolute_numbers(string postTitle, string title, int absoluteEpisodeNumber, int seasonNumber, int episodeNumber)
|
||||
{
|
||||
var result = Parser.Parser.ParseTitle(postTitle);
|
||||
result.Should().NotBeNull();
|
||||
result.AbsoluteEpisodeNumbers.First().Should().Be(absoluteEpisodeNumber);
|
||||
result.SeasonNumber.Should().Be(seasonNumber);
|
||||
result.EpisodeNumbers.FirstOrDefault().Should().Be(episodeNumber);
|
||||
result.SeriesTitle.Should().Be(title.CleanSeriesTitle());
|
||||
}
|
||||
|
||||
|
||||
|
@ -38,7 +38,8 @@ public void Setup()
|
||||
{
|
||||
SeriesTitle = _series.Title,
|
||||
SeasonNumber = 1,
|
||||
EpisodeNumbers = new[] { 1 }
|
||||
EpisodeNumbers = new[] { 1 },
|
||||
AbsoluteEpisodeNumbers = new int[0]
|
||||
};
|
||||
|
||||
_singleEpisodeSearchCriteria = new SingleEpisodeSearchCriteria
|
||||
@ -69,6 +70,11 @@ private void GivenSceneNumberingSeries()
|
||||
_series.UseSceneNumbering = true;
|
||||
}
|
||||
|
||||
private void GivenAbsoluteNumberingSeries()
|
||||
{
|
||||
_parsedEpisodeInfo.AbsoluteEpisodeNumbers = new[] { 1 };
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_get_daily_episode_episode_when_search_criteria_is_null()
|
||||
{
|
||||
@ -105,6 +111,17 @@ public void should_fallback_to_daily_episode_lookup_when_search_criteria_episode
|
||||
.Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<String>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_search_criteria_episode_when_it_matches_absolute()
|
||||
{
|
||||
GivenAbsoluteNumberingSeries();
|
||||
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria);
|
||||
|
||||
Mocker.GetMock<IEpisodeService>()
|
||||
.Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<String>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_scene_numbering_when_series_uses_scene_numbering()
|
||||
{
|
||||
|
@ -20,6 +20,7 @@ public void Setup()
|
||||
.With(e => e.SeasonNumber = 1)
|
||||
.With(e => e.SceneSeasonNumber = 2)
|
||||
.With(e => e.EpisodeNumber = 3)
|
||||
.With(e => e.AbsoluteEpisodeNumber = 3)
|
||||
.With(e => e.SceneEpisodeNumber = 4)
|
||||
.Build();
|
||||
|
||||
@ -51,5 +52,14 @@ public void should_not_find_episode_that_does_not_exist()
|
||||
.Should()
|
||||
.BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_find_episode_by_absolute_numbering()
|
||||
{
|
||||
Subject.Find(_episode.SeriesId, _episode.AbsoluteEpisodeNumber.Value)
|
||||
.Id
|
||||
.Should()
|
||||
.Be(_episode.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,27 @@ public class ParsedEpisodeInfo
|
||||
public QualityModel Quality { get; set; }
|
||||
public int SeasonNumber { get; set; }
|
||||
public int[] EpisodeNumbers { get; set; }
|
||||
public int[] AbsoluteEpisodeNumbers { get; set; }
|
||||
public String AirDate { get; set; }
|
||||
public Language Language { get; set; }
|
||||
|
||||
public bool FullSeason { get; set; }
|
||||
|
||||
public ParsedEpisodeInfo()
|
||||
{
|
||||
EpisodeNumbers = new int[0];
|
||||
AbsoluteEpisodeNumbers = new int[0];
|
||||
}
|
||||
|
||||
public bool IsDaily()
|
||||
{
|
||||
return !String.IsNullOrWhiteSpace(AirDate);
|
||||
}
|
||||
|
||||
public bool IsAbsoluteNumbering()
|
||||
{
|
||||
return AbsoluteEpisodeNumbers.Any();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string episodeString = "[Unknown Episode]";
|
||||
@ -35,10 +51,5 @@ public override string ToString()
|
||||
|
||||
return string.Format("{0} - {1} {2}", SeriesTitle, episodeString, Quality);
|
||||
}
|
||||
|
||||
public bool IsDaily()
|
||||
{
|
||||
return !String.IsNullOrWhiteSpace(AirDate);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
@ -20,6 +21,22 @@ public static class Parser
|
||||
new Regex(@"^(?<title>.+?)?\W*(?<airyear>\d{4})\W+(?<airmonth>[0-1][0-9])\W+(?<airday>[0-3][0-9])(\W+|_|$)(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Anime - Absolute Episode Number + Title + Season+Episode
|
||||
new Regex(@"^(?:(?<absoluteepisode>\d{2,3})(?:_|-|\s|\.)+)+(?<title>.+?)(?:\W|_)+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Anime - [SubGroup] Title Absolute Episode Number + Season+Episode
|
||||
new Regex(@"^(?:\[(?<subgroup>.+?)\](?:_|-|\s|\.))?(?<title>.+?)(?:(?:\W|_)+(?<absoluteepisode>\d{2,3}))+(?:_|-|\s|\.)+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Anime - [SubGroup] Title Season+Episode + Absolute Episode Number
|
||||
new Regex(@"^(?:\[(?<subgroup>.+?)\](?:_|-|\s|\.))?(?<title>.+?)(?:\W|_)+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+)(?:\s|\.)(?:(?<absoluteepisode>\d{2,3})(?:_|-|\s|\.|$)+)+",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Anime - [SubGroup] Title Absolute Episode Number
|
||||
new Regex(@"^\[(?<subgroup>.+?)\](?:_|-|\s|\.)?(?<title>.+?)(?:(?:\W|_)+(?<absoluteepisode>\d{2,}))+",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Multi-Part episodes without a title (S01E05.S01E06)
|
||||
new Regex(@"^(?:\W*S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:[ex]){1,2}(?<episode>\d{1,3}(?!\d+)))+){2,}(\W+|_|$)(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
@ -44,6 +61,10 @@ public static class Parser
|
||||
new Regex(@"^(?<title>.*?)(?:\W?S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]){1,2}(?<episode>\d{1}))+)+(\W+|_|$)(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Anime - Title Absolute Episode Number [SubGroup]
|
||||
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{2,3}))+(?:.+?)\[(?<subgroup>.+?)\](?:\.|$)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Supports 103/113 naming
|
||||
new Regex(@"^(?<title>.+?)?(?:\W?(?<season>(?<!\d+)\d{1})(?<episode>\d{2}(?!p|i|\d+)))+(\W+|_|$)(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
@ -53,7 +74,7 @@ public static class Parser
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Supports 1103/1113 naming
|
||||
new Regex(@"^(?<title>.+?)?(?:\W?(?<season>(?<!\d+|\(|\[)\d{2})(?<episode>\d{2}(?!p|i|\d+|\)|\]|\W\d+)))+(\W+|_|$)(?!\\)",
|
||||
new Regex(@"^(?<title>.+?)?(?:\W?(?<season>(?<!\d+|\(|\[|e|x)\d{2})(?<episode>(?<!e|x)\d{2}(?!p|i|\d+|\)|\]|\W\d+)))+(\W+|_|$)(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Supports Season 01 Episode 03
|
||||
@ -62,7 +83,20 @@ public static class Parser
|
||||
|
||||
//Supports Season only releases
|
||||
new Regex(@"^(?<title>.+?)\W(?:S|Season)\W?(?<season>\d{1,2}(?!\d+))(\W+|_|$)(?<extras>EXTRAS|SUBPACK)?(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled)
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//4-digit episode number
|
||||
//Episodes without a title, Single (S01E05, 1x05) AND Multi (S01E04E05, 1x04x05, etc)
|
||||
new Regex(@"^(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{4}(?!\d+|i|p)))+)(\W+|_|$)(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Episodes with a title, Single episodes (S01E05, 1x05, etc) & Multi-episode (S01E05E06, S01E05-06, S01E05 E06, etc)
|
||||
new Regex(@"^(?<title>.+?)(?:(\W|_)+S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{4}(?!\d+|i|p)))+)\W?(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Anime - Title Absolute Episode Number
|
||||
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+e(?<absoluteepisode>\d{2,3}))+",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
};
|
||||
|
||||
private static readonly Regex NormalizeRegex = new Regex(@"((^|\W|_)(a|an|the|and|or|of)($|\W|_))|\W|_|(?:(?<=[^0-9]+)|\b)(?!(?:19\d{2}|20\d{2}))\d+(?=[^0-9ip]+|\b)",
|
||||
@ -114,6 +148,7 @@ public static ParsedEpisodeInfo ParseTitle(string title)
|
||||
|
||||
if (match.Count != 0)
|
||||
{
|
||||
Debug.WriteLine(regex);
|
||||
try
|
||||
{
|
||||
var result = ParseMatchCollection(match);
|
||||
@ -226,11 +261,13 @@ private static ParsedEpisodeInfo ParseMatchCollection(MatchCollection matchColle
|
||||
{
|
||||
SeasonNumber = seasons.First(),
|
||||
EpisodeNumbers = new int[0],
|
||||
AbsoluteEpisodeNumbers = new int[0]
|
||||
};
|
||||
|
||||
foreach (Match matchGroup in matchCollection)
|
||||
{
|
||||
var episodeCaptures = matchGroup.Groups["episode"].Captures.Cast<Capture>().ToList();
|
||||
var absoluteEpisodeCaptures = matchGroup.Groups["absoluteepisode"].Captures.Cast<Capture>().ToList();
|
||||
|
||||
//Allows use to return a list of 0 episodes (We can handle that as a full season release)
|
||||
if (episodeCaptures.Any())
|
||||
@ -246,6 +283,20 @@ private static ParsedEpisodeInfo ParseMatchCollection(MatchCollection matchColle
|
||||
var count = last - first + 1;
|
||||
result.EpisodeNumbers = Enumerable.Range(first, count).ToArray();
|
||||
}
|
||||
|
||||
if (absoluteEpisodeCaptures.Any())
|
||||
{
|
||||
var first = Convert.ToInt32(absoluteEpisodeCaptures.First().Value);
|
||||
var last = Convert.ToInt32(absoluteEpisodeCaptures.Last().Value);
|
||||
|
||||
if (first > last)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var count = last - first + 1;
|
||||
result.AbsoluteEpisodeNumbers = Enumerable.Range(first, count).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
//Check to see if this is an "Extras" or "SUBPACK" release, if it is, return NULL
|
||||
@ -256,6 +307,10 @@ private static ParsedEpisodeInfo ParseMatchCollection(MatchCollection matchColle
|
||||
result.FullSeason = true;
|
||||
}
|
||||
}
|
||||
if (result.AbsoluteEpisodeNumbers.Any() && !result.EpisodeNumbers.Any())
|
||||
{
|
||||
result.SeasonNumber = 0;
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
|
@ -129,6 +129,26 @@ public List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series ser
|
||||
return result;
|
||||
}
|
||||
|
||||
if (parsedEpisodeInfo.IsAbsoluteNumbering())
|
||||
{
|
||||
foreach (var absoluteEpisodeNumber in parsedEpisodeInfo.AbsoluteEpisodeNumbers)
|
||||
{
|
||||
var episodeInfo = _episodeService.FindEpisode(series.Id, absoluteEpisodeNumber);
|
||||
|
||||
if (episodeInfo != null)
|
||||
{
|
||||
_logger.Info("Using absolute episode number {0} for: {1} - TVDB: {2}x{3:00}",
|
||||
absoluteEpisodeNumber,
|
||||
series.Title,
|
||||
episodeInfo.SeasonNumber,
|
||||
episodeInfo.EpisodeNumber);
|
||||
result.Add(episodeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (parsedEpisodeInfo.EpisodeNumbers == null)
|
||||
return result;
|
||||
|
||||
|
@ -11,6 +11,7 @@ namespace NzbDrone.Core.Tv
|
||||
public interface IEpisodeRepository : IBasicRepository<Episode>
|
||||
{
|
||||
Episode Find(int seriesId, int season, int episodeNumber);
|
||||
Episode Find(int seriesId, int absoluteEpisodeNumber);
|
||||
Episode Get(int seriesId, String date);
|
||||
Episode Find(int seriesId, String date);
|
||||
List<Episode> GetEpisodes(int seriesId);
|
||||
@ -39,6 +40,11 @@ public Episode Find(int seriesId, int season, int episodeNumber)
|
||||
return Query.SingleOrDefault(s => s.SeriesId == seriesId && s.SeasonNumber == season && s.EpisodeNumber == episodeNumber);
|
||||
}
|
||||
|
||||
public Episode Find(int seriesId, int absoluteEpisodeNumber)
|
||||
{
|
||||
return Query.SingleOrDefault(s => s.SeriesId == seriesId && s.AbsoluteEpisodeNumber == absoluteEpisodeNumber);
|
||||
}
|
||||
|
||||
public Episode Get(int seriesId, String date)
|
||||
{
|
||||
return Query.Single(s => s.SeriesId == seriesId && s.AirDate == date);
|
||||
|
@ -14,6 +14,7 @@ public interface IEpisodeService
|
||||
{
|
||||
Episode GetEpisode(int id);
|
||||
Episode FindEpisode(int seriesId, int seasonNumber, int episodeNumber, bool useScene = false);
|
||||
Episode FindEpisode(int seriesId, int absoluteEpisodeNumber);
|
||||
Episode GetEpisode(int seriesId, String date);
|
||||
Episode FindEpisode(int seriesId, String date);
|
||||
List<Episode> GetEpisodeBySeries(int seriesId);
|
||||
@ -62,6 +63,11 @@ public Episode FindEpisode(int seriesId, int seasonNumber, int episodeNumber, bo
|
||||
return _episodeRepository.Find(seriesId, seasonNumber, episodeNumber);
|
||||
}
|
||||
|
||||
public Episode FindEpisode(int seriesId, int absoluteEpisodeNumber)
|
||||
{
|
||||
return _episodeRepository.Find(seriesId, absoluteEpisodeNumber);
|
||||
}
|
||||
|
||||
public Episode GetEpisode(int seriesId, String date)
|
||||
{
|
||||
return _episodeRepository.Get(seriesId, date);
|
||||
|
Loading…
Reference in New Issue
Block a user