mirror of
https://github.com/Sonarr/Sonarr.git
synced 2024-12-14 11:23:42 +02:00
parent
2446c4185a
commit
2b4429f8b7
@ -148,5 +148,17 @@ public void should_parse_anime_season_packs(string postTitle, string title, int
|
|||||||
result.SeasonNumber.Should().Be(seasonNumber);
|
result.SeasonNumber.Should().Be(seasonNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase("[HorribleSubs] Goblin Slayer - 10.5 [1080p].mkv", "Goblin Slayer", 10.5)]
|
||||||
|
public void should_handle_anime_recap_numbering(string postTitle, string title, double specialEpisodeNumber)
|
||||||
|
{
|
||||||
|
var result = Parser.Parser.ParseTitle(postTitle);
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.SeriesTitle.Should().Be(title);
|
||||||
|
result.AbsoluteEpisodeNumbers.Should().BeEmpty();
|
||||||
|
result.SpecialAbsoluteEpisodeNumbers.Should().NotBeEmpty();
|
||||||
|
result.SpecialAbsoluteEpisodeNumbers.Should().BeEquivalentTo(new[] { (decimal)specialEpisodeNumber });
|
||||||
|
result.FullSeason.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ public class ParsedEpisodeInfo
|
|||||||
public int SeasonNumber { get; set; }
|
public int SeasonNumber { get; set; }
|
||||||
public int[] EpisodeNumbers { get; set; }
|
public int[] EpisodeNumbers { get; set; }
|
||||||
public int[] AbsoluteEpisodeNumbers { get; set; }
|
public int[] AbsoluteEpisodeNumbers { get; set; }
|
||||||
|
public decimal[] SpecialAbsoluteEpisodeNumbers { get; set; }
|
||||||
public string AirDate { get; set; }
|
public string AirDate { get; set; }
|
||||||
public Language Language { get; set; }
|
public Language Language { get; set; }
|
||||||
public bool FullSeason { get; set; }
|
public bool FullSeason { get; set; }
|
||||||
@ -27,6 +28,7 @@ public ParsedEpisodeInfo()
|
|||||||
{
|
{
|
||||||
EpisodeNumbers = new int[0];
|
EpisodeNumbers = new int[0];
|
||||||
AbsoluteEpisodeNumbers = new int[0];
|
AbsoluteEpisodeNumbers = new int[0];
|
||||||
|
SpecialAbsoluteEpisodeNumbers = new decimal[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsDaily
|
public bool IsDaily
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
@ -39,15 +40,15 @@ public static class Parser
|
|||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Anime - [SubGroup] Title Episode Absolute Episode Number ([SubGroup] Series Title Episode 01)
|
//Anime - [SubGroup] Title Episode Absolute Episode Number ([SubGroup] Series Title Episode 01)
|
||||||
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)(?<title>.+?)[-_. ](?:Episode)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
|
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)(?<title>.+?)[-_. ](?:Episode)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(\.\d{1,2})?(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Anime - [SubGroup] Title Absolute Episode Number + Season+Episode
|
//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+)))+).*?(?<hash>[(\[]\w{8}[)\]])?(?:$|\.)",
|
new Regex(@"^(?:\[(?<subgroup>.+?)\](?:_|-|\s|\.)?)(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+(?<absoluteepisode>\d{2,3}(\.\d{1,2})?))+(?:_|-|\s|\.)+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+).*?(?<hash>[(\[]\w{8}[)\]])?(?:$|\.)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Anime - [SubGroup] Title Season+Episode + Absolute Episode Number
|
//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+)\d{2,3}(?!\d+)))+.*?(?<hash>\[\w{8}\])?(?:$|\.)",
|
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+)\d{2,3}(\.\d{1,2})?(?!\d+)))+.*?(?<hash>\[\w{8}\])?(?:$|\.)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Anime - [SubGroup] Title Season+Episode
|
//Anime - [SubGroup] Title Season+Episode
|
||||||
@ -55,15 +56,15 @@ public static class Parser
|
|||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Anime - [SubGroup] Title with trailing number Absolute Episode Number
|
//Anime - [SubGroup] Title with trailing number Absolute Episode Number
|
||||||
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>[^-]+?\d+?)[-_. ]+(?:[-_. ]?(?<absoluteepisode>\d{3}(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
|
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>[^-]+?\d+?)[-_. ]+(?:[-_. ]?(?<absoluteepisode>\d{3}(\.\d{1,2})?(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Anime - [SubGroup] Title - Absolute Episode Number
|
//Anime - [SubGroup] Title - Absolute Episode Number
|
||||||
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)(?:[. ]-[. ](?<absoluteepisode>\d{2,3}(?!\d+|[-])))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
|
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)(?:[. ]-[. ](?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+|[-])))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Anime - [SubGroup] Title Absolute Episode Number
|
//Anime - [SubGroup] Title Absolute Episode Number
|
||||||
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)[-_. ]+\(?(?:[-_. ]?#?(?<absoluteepisode>\d{2,3}(?!\d+)))+\)?(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
|
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)[-_. ]+\(?(?:[-_. ]?#?(?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+)))+\)?(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Multi-episode Repeated (S01E05 - S01E06, 1x05 - 1x06, etc)
|
//Multi-episode Repeated (S01E05 - S01E06, 1x05 - 1x06, etc)
|
||||||
@ -75,7 +76,7 @@ public static class Parser
|
|||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Anime - Title Season EpisodeNumber + Absolute Episode Number [SubGroup]
|
//Anime - Title Season EpisodeNumber + Absolute Episode Number [SubGroup]
|
||||||
new Regex(@"^(?<title>.+?)(?:[-_\W](?<![()\[!]))+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:[ex]|\W[ex]){1,2}(?<episode>(?<!\d+)\d{2}(?!\d+)))).+?(?:[-_. ]?(?<absoluteepisode>(?<!\d+)\d{3}(?!\d+)))+.+?\[(?<subgroup>.+?)\](?:$|\.mkv)",
|
new Regex(@"^(?<title>.+?)(?:[-_\W](?<![()\[!]))+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:[ex]|\W[ex]){1,2}(?<episode>(?<!\d+)\d{2}(?!\d+)))).+?(?:[-_. ]?(?<absoluteepisode>(?<!\d+)\d{3}(\.\d{1,2})?(?!\d+)))+.+?\[(?<subgroup>.+?)\](?:$|\.mkv)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Multi-Episode with a title (S01E05E06, S01E05-06, S01E05 E06, etc) and trailing info in slashes
|
//Multi-Episode with a title (S01E05E06, S01E05-06, S01E05 E06, etc) and trailing info in slashes
|
||||||
@ -83,11 +84,11 @@ public static class Parser
|
|||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Anime - Title Absolute Episode Number [SubGroup]
|
//Anime - Title Absolute Episode Number [SubGroup]
|
||||||
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{3}(?!\d+)))+(?:.+?)\[(?<subgroup>.+?)\].*?(?<hash>\[\w{8}\])?(?:$|\.)",
|
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{3}(\.\d{1,2})?(?!\d+)))+(?:.+?)\[(?<subgroup>.+?)\].*?(?<hash>\[\w{8}\])?(?:$|\.)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Anime - Title Absolute Episode Number [Hash]
|
//Anime - Title Absolute Episode Number [Hash]
|
||||||
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{2,3}(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?[-_. ]+.*?(?<hash>\[\w{8}\])(?:$|\.)",
|
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?[-_. ]+.*?(?<hash>\[\w{8}\])(?:$|\.)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Episodes with airdate AND season/episode number, capture season/epsiode only
|
//Episodes with airdate AND season/episode number, capture season/epsiode only
|
||||||
@ -171,11 +172,11 @@ public static class Parser
|
|||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
// Anime - Title with season number - Absolute Episode Number (Title S01 - EP14)
|
// Anime - Title with season number - Absolute Episode Number (Title S01 - EP14)
|
||||||
new Regex(@"^(?<title>.+?S\d{1,2})[-_. ]{3,}(?:EP)?(?<absoluteepisode>\d{2,3}(?!\d+|[-]))",
|
new Regex(@"^(?<title>.+?S\d{1,2})[-_. ]{3,}(?:EP)?(?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+|[-]))",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
// Anime - French titles with single episode numbers, with or without leading sub group ([RlsGroup] Title - Episode 1)
|
// Anime - French titles with single episode numbers, with or without leading sub group ([RlsGroup] Title - Episode 1)
|
||||||
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)[-_. ]+?(?:Episode[-_. ]+?)(?<absoluteepisode>\d{1}(?!\d+))",
|
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)[-_. ]+?(?:Episode[-_. ]+?)(?<absoluteepisode>\d{1}(\.\d{1,2})?(?!\d+))",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Season only releases
|
//Season only releases
|
||||||
@ -230,19 +231,19 @@ public static class Parser
|
|||||||
|
|
||||||
// TODO: THIS ONE
|
// TODO: THIS ONE
|
||||||
//Anime - Title Absolute Episode Number (e66)
|
//Anime - Title Absolute Episode Number (e66)
|
||||||
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:(?:_|-|\s|\.)+(?:e|ep)(?<absoluteepisode>\d{2,3}))+.*?(?<hash>\[\w{8}\])?(?:$|\.)",
|
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:(?:_|-|\s|\.)+(?:e|ep)(?<absoluteepisode>\d{2,3}(\.\d{1,2})?))+.*?(?<hash>\[\w{8}\])?(?:$|\.)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Anime - Title Episode Absolute Episode Number (Series Title Episode 01)
|
//Anime - Title Episode Absolute Episode Number (Series Title Episode 01)
|
||||||
new Regex(@"^(?<title>.+?)[-_. ](?:Episode)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
|
new Regex(@"^(?<title>.+?)[-_. ](?:Episode)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(\.\d{1,2})?(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Anime - Title Absolute Episode Number
|
//Anime - Title Absolute Episode Number
|
||||||
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
|
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(\.\d{1,2})?(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Anime - Title {Absolute Episode Number}
|
//Anime - Title {Absolute Episode Number}
|
||||||
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
|
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+(?<absoluteepisode>(?<!\d+)\d{2,3}(\.\d{1,2})?(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
//Extant, terrible multi-episode naming (extant.10708.hdtv-lol.mp4)
|
//Extant, terrible multi-episode naming (extant.10708.hdtv-lol.mp4)
|
||||||
@ -653,21 +654,32 @@ private static ParsedEpisodeInfo ParseMatchCollection(MatchCollection matchColle
|
|||||||
|
|
||||||
if (absoluteEpisodeCaptures.Any())
|
if (absoluteEpisodeCaptures.Any())
|
||||||
{
|
{
|
||||||
var first = Convert.ToInt32(absoluteEpisodeCaptures.First().Value);
|
var first = Convert.ToDecimal(absoluteEpisodeCaptures.First().Value, CultureInfo.InvariantCulture);
|
||||||
var last = Convert.ToInt32(absoluteEpisodeCaptures.Last().Value);
|
var last = Convert.ToDecimal(absoluteEpisodeCaptures.Last().Value, CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
if (first > last)
|
if (first > last)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var count = last - first + 1;
|
if ((first % 1) != 0 || (last % 1) != 0)
|
||||||
result.AbsoluteEpisodeNumbers = Enumerable.Range(first, count).ToArray();
|
|
||||||
|
|
||||||
if (matchGroup.Groups["special"].Success)
|
|
||||||
{
|
{
|
||||||
|
if (absoluteEpisodeCaptures.Count != 1)
|
||||||
|
return null; // Multiple matches not allowed for specials
|
||||||
|
|
||||||
|
result.SpecialAbsoluteEpisodeNumbers = new decimal[] { first };
|
||||||
result.Special = true;
|
result.Special = true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var count = last - first + 1;
|
||||||
|
result.AbsoluteEpisodeNumbers = Enumerable.Range((int)first, (int)count).ToArray();
|
||||||
|
|
||||||
|
if (matchGroup.Groups["special"].Success)
|
||||||
|
{
|
||||||
|
result.Special = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!episodeCaptures.Any() && !absoluteEpisodeCaptures.Any())
|
if (!episodeCaptures.Any() && !absoluteEpisodeCaptures.Any())
|
||||||
|
Loading…
Reference in New Issue
Block a user