|
|
|
|
@@ -78,197 +78,6 @@ namespace NzbDrone.Core.Parser
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private static readonly Regex[] ReportTitleRegex = new[]
|
|
|
|
|
{
|
|
|
|
|
//Anime - Absolute Episode Number + Title + Season+Episode
|
|
|
|
|
//Todo: This currently breaks series that start with numbers
|
|
|
|
|
// 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),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Multi-Part episodes without a title (S01E05.S01E06)
|
|
|
|
|
new Regex(@"^(?:\W*S?(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:(?:[ex]){1,2}(?<episode>\d{1,3}(?!\d+)))+){2,}",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Episodes without a title, Single (S01E05, 1x05) AND Multi (S01E04E05, 1x04x05, etc)
|
|
|
|
|
new Regex(@"^(?:S?(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+)))+)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//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}\])?(?:$|\.)?",
|
|
|
|
|
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+)))+).*?(?<hash>[(\[]\w{8}[)\]])?(?:$|\.)",
|
|
|
|
|
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+)\d{2,3}(?!\d+)))+.*?(?<hash>\[\w{8}\])?(?:$|\.)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Anime - [SubGroup] Title Season+Episode
|
|
|
|
|
new Regex(@"^(?:\[(?<subgroup>.+?)\](?:_|-|\s|\.)?)(?<title>.+?)(?:[-_\W](?<![()\[!]))+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+)(?:\s|\.).*?(?<hash>\[\w{8}\])?(?:$|\.)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//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)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Anime - [SubGroup] Title - Absolute Episode Number
|
|
|
|
|
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)(?:[. ]-[. ](?<absoluteepisode>\d{2,3}(?!\d+|[-])))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Anime - [SubGroup] Title Absolute Episode Number
|
|
|
|
|
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)[-_. ]+\(?(?:[-_. ]?(?<absoluteepisode>\d{2,3}(?!\d+)))+\)?(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//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)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Anime - Title Absolute Episode Number [SubGroup]
|
|
|
|
|
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{3}(?!\d+)))+(?:.+?)\[(?<subgroup>.+?)\].*?(?<hash>\[\w{8}\])?(?:$|\.)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Anime - Title Absolute Episode Number [Hash]
|
|
|
|
|
new Regex(@"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{2,3}(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?[-_. ]+.*?(?<hash>\[\w{8}\])(?:$|\.)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Episodes with airdate AND season/episode number, capture season/epsiode only
|
|
|
|
|
new Regex(@"^(?<title>.+?)?\W*(?<airdate>\d{4}\W+[0-1][0-9]\W+[0-3][0-9])(?!\W+[0-3][0-9])[-_. ](?:s?(?<season>(?<!\d+)(?:\d{1,2})(?!\d+)))(?:[ex](?<episode>(?<!\d+)(?:\d{1,3})(?!\d+)))",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Episodes with airdate AND season/episode number
|
|
|
|
|
new Regex(@"^(?<title>.+?)?\W*(?<airyear>\d{4})\W+(?<airmonth>[0-1][0-9])\W+(?<airday>[0-3][0-9])(?!\W+[0-3][0-9]).+?(?:s?(?<season>(?<!\d+)(?:\d{1,2})(?!\d+)))(?:[ex](?<episode>(?<!\d+)(?:\d{1,3})(?!\d+)))",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Multi-episode Repeated (S01E05 - S01E06, 1x05 - 1x06, etc)
|
|
|
|
|
new Regex(@"^(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+S?(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:(?:[ex]|[-_. ]e){1,2}(?<episode>\d{1,3}(?!\d+)))+){2,}",
|
|
|
|
|
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{2,3}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+)))*)\W?(?!\\)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Episodes with a title, 4 digit season number, Single episodes (S2016E05, etc) & Multi-episode (S2016E05E06, S2016E05-06, S2016E05 E06, etc)
|
|
|
|
|
new Regex(@"^(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+S(?<season>(?<!\d+)(?:\d{4})(?!\d+))(?:e|\We|_){1,2}(?<episode>\d{2,3}(?!\d+))(?:(?:\-|e|\We|_){1,2}(?<episode>\d{2,3}(?!\d+)))*)\W?(?!\\)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Episodes with a title, 4 digit season number, Single episodes (2016x05, etc) & Multi-episode (2016x05x06, 2016x05-06, 2016x05 x06, etc)
|
|
|
|
|
new Regex(@"^(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+(?<season>(?<!\d+)(?:\d{4})(?!\d+))(?:x|\Wx|_){1,2}(?<episode>\d{2,3}(?!\d+))(?:(?:\-|x|\Wx|_){1,2}(?<episode>\d{2,3}(?!\d+)))*)\W?(?!\\)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Mini-Series with year in title, treated as season 1, episodes are labelled as Part01, Part 01, Part.1
|
|
|
|
|
new Regex(@"^(?<title>.+?\d{4})(?:\W+(?:(?:Part\W?|e)(?<episode>\d{1,2}(?!\d+)))+)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Mini-Series, treated as season 1, episodes are labelled as Part01, Part 01, Part.1
|
|
|
|
|
new Regex(@"^(?<title>.+?)(?:\W+(?:(?:Part\W?|(?<!\d+\W+)e)(?<episode>\d{1,2}(?!\d+)))+)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Mini-Series, treated as season 1, episodes are labelled as Part One/Two/Three/...Nine, Part.One, Part_One
|
|
|
|
|
new Regex(@"^(?<title>.+?)(?:\W+(?:Part[-._ ](?<episode>One|Two|Three|Four|Five|Six|Seven|Eight|Nine)(?>[-._ ])))",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Mini-Series, treated as season 1, episodes are labelled as XofY
|
|
|
|
|
new Regex(@"^(?<title>.+?)(?:\W+(?:(?<episode>(?<!\d+)\d{1,2}(?!\d+))of\d+)+)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Supports Season 01 Episode 03
|
|
|
|
|
new Regex(@"(?:.*(?:\""|^))(?<title>.*?)(?:[-_\W](?<![()\[]))+(?:\W?Season\W?)(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:\W|_)+(?:Episode\W)(?:[-_. ]?(?<episode>(?<!\d+)\d{1,2}(?!\d+)))+",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Multi-episode release with no space between series title and season (S01E11E12)
|
|
|
|
|
new Regex(@"(?:.*(?:^))(?<title>.*?)(?:\W?|_)S(?<season>(?<!\d+)\d{2}(?!\d+))(?:E(?<episode>(?<!\d+)\d{2}(?!\d+)))+",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Multi-episode with single episode numbers (S6.E1-E2, S6.E1E2, S6E1E2, etc)
|
|
|
|
|
new Regex(@"^(?<title>.+?)[-_. ]S(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:[-_. ]?[ex]?(?<episode>(?<!\d+)\d{1,2}(?!\d+)))+",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Single episode season or episode S1E1 or S1-E1
|
|
|
|
|
new Regex(@"(?:.*(?:\""|^))(?<title>.*?)(?:\W?|_)S(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:\W|_)?E(?<episode>(?<!\d+)\d{1,2}(?!\d+))",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//3 digit season S010E05
|
|
|
|
|
new Regex(@"(?:.*(?:\""|^))(?<title>.*?)(?:\W?|_)S(?<season>(?<!\d+)\d{3}(?!\d+))(?:\W|_)?E(?<episode>(?<!\d+)\d{1,2}(?!\d+))",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//5 digit episode number with a title
|
|
|
|
|
new Regex(@"^(?:(?<title>.+?)(?:_|-|\s|\.)+)(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+)))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>(?<!\d+)\d{5}(?!\d+)))",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//5 digit multi-episode with a title
|
|
|
|
|
new Regex(@"^(?:(?<title>.+?)(?:_|-|\s|\.)+)(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+)))(?:(?:[-_. ]{1,3}ep){1,2}(?<episode>(?<!\d+)\d{5}(?!\d+)))+",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
// Separated season and episode numbers S01 - E01
|
|
|
|
|
new Regex(@"^(?<title>.+?)(?:_|-|\s|\.)+S(?<season>\d{2}(?!\d+))(\W-\W)E(?<episode>(?<!\d+)\d{2}(?!\d+))(?!\\)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Season only releases
|
|
|
|
|
new Regex(@"^(?<title>.+?)\W(?:S|Season)\W?(?<season>\d{1,2}(?!\d+))(\W+|_|$)(?<extras>EXTRAS|SUBPACK)?(?!\\)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//4 digit season only releases
|
|
|
|
|
new Regex(@"^(?<title>.+?)\W(?:S|Season)\W?(?<season>\d{4}(?!\d+))(\W+|_|$)(?<extras>EXTRAS|SUBPACK)?(?!\\)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Episodes with a title and season/episode in square brackets
|
|
|
|
|
new Regex(@"^(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+\[S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>(?<!\d+)\d{2}(?!\d+|i|p)))+\])\W?(?!\\)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Supports 103/113 naming
|
|
|
|
|
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))+(?<season>(?<!\d+)[1-9])(?<episode>[1-9][0-9]|[0][1-9])(?![a-z]|\d+))+",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Episodes with airdate
|
|
|
|
|
new Regex(@"^(?<title>.+?)?\W*(?<airyear>\d{4})\W+(?<airmonth>[0-1][0-9])\W+(?<airday>[0-3][0-9])(?!\W+[0-3][0-9])",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Supports 1103/1113 naming
|
|
|
|
|
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),
|
|
|
|
|
|
|
|
|
|
//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),
|
|
|
|
|
|
|
|
|
|
//4 digit episode number
|
|
|
|
|
//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),
|
|
|
|
|
|
|
|
|
|
//Episodes with single digit episode number (S01E1, S01E5E6, etc)
|
|
|
|
|
new Regex(@"^(?<title>.*?)(?:(?:[-_\W](?<![()\[!]))+S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]){1,2}(?<episode>\d{1}))+)+(\W+|_|$)(?!\\)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//iTunes Season 1\05 Title (Quality).ext
|
|
|
|
|
new Regex(@"^(?:Season(?:_|-|\s|\.)(?<season>(?<!\d+)\d{1,2}(?!\d+)))(?:_|-|\s|\.)(?<episode>(?<!\d+)\d{1,2}(?!\d+))",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Anime - Title Absolute Episode Number (e66)
|
|
|
|
|
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:(?:_|-|\s|\.)+(?:e|ep)(?<absoluteepisode>\d{2,3}))+.*?(?<hash>\[\w{8}\])?(?:$|\.)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Anime - Title Episode Absolute Episode Number (Series Title Episode 01)
|
|
|
|
|
new Regex(@"^(?<title>.+?)[-_. ](?:Episode)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Anime - Title Absolute Episode Number
|
|
|
|
|
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Anime - Title {Absolute Episode Number}
|
|
|
|
|
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
|
|
|
|
|
|
|
|
//Extant, terrible multi-episode naming (extant.10708.hdtv-lol.mp4)
|
|
|
|
|
new Regex(@"^(?<title>.+?)[-_. ](?<season>[0]?\d?)(?:(?<episode>\d{2}){2}(?!\d+))[-_. ]",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private static readonly Regex[] RejectHashedReleasesRegex = new Regex[]
|
|
|
|
|
{
|
|
|
|
|
// Generic match for md5 and mixed-case hashes.
|
|
|
|
|
@@ -328,9 +137,6 @@ namespace NzbDrone.Core.Parser
|
|
|
|
|
private static readonly Regex AnimeReleaseGroupRegex = new Regex(@"^(?:\[(?<subgroup>(?!\s).+?(?<!\s))\](?:_|-|\s|\.)?)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
|
|
|
|
|
|
|
|
|
private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?<italian>\b(?:ita|italian)\b)|(?<german>german\b|videomann)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_)(?:FR|VOSTFR)(?:\W|_))|(?<russian>\brus\b)|(?<dutch>nl\W?subs?)|(?<hungarian>\b(?:HUNDUB|HUN)\b)|(?<spanish>\b(?:español|castellano)\b)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
|
|
|
|
|
|
|
|
|
private static readonly Regex YearInTitleRegex = new Regex(@"^(?<title>.+?)(?:\W|_)?(?<year>\d{4})",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
|
|
|
|
|
|
|
|
|
@@ -346,59 +152,20 @@ namespace NzbDrone.Core.Parser
|
|
|
|
|
|
|
|
|
|
public static ParsedTrackInfo ParseMusicPath(string path)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
var fileInfo = new FileInfo(path);
|
|
|
|
|
var file = TagLib.File.Create(path);
|
|
|
|
|
var trackNumber = file.Tag.Track;
|
|
|
|
|
var trackTitle = file.Tag.Title;
|
|
|
|
|
|
|
|
|
|
var artist = file.Tag.FirstAlbumArtist;
|
|
|
|
|
var result = new ParsedTrackInfo{ };
|
|
|
|
|
|
|
|
|
|
if (artist.IsNullOrWhiteSpace())
|
|
|
|
|
if (MediaFiles.MediaFileExtensions.Extensions.Contains(fileInfo.Extension))
|
|
|
|
|
{
|
|
|
|
|
artist = file.Tag.FirstPerformer;
|
|
|
|
|
result = ParseAudioTags(path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var artistTitleInfo = new ArtistTitleInfo
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Title = artist,
|
|
|
|
|
Year = (int)file.Tag.Year
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var temp = new int[1];
|
|
|
|
|
temp[0] = (int)trackNumber;
|
|
|
|
|
var result = new ParsedTrackInfo
|
|
|
|
|
{
|
|
|
|
|
Language = Language.English, //TODO Parse from Tag/Mediainfo
|
|
|
|
|
AlbumTitle = file.Tag.Album,
|
|
|
|
|
ArtistTitle = artist,
|
|
|
|
|
ArtistMBId = file.Tag.MusicBrainzArtistId,
|
|
|
|
|
AlbumMBId = file.Tag.MusicBrainzReleaseId,
|
|
|
|
|
TrackMBId = file.Tag.MusicBrainzReleaseType,
|
|
|
|
|
TrackNumbers = temp,
|
|
|
|
|
ArtistTitleInfo = artistTitleInfo,
|
|
|
|
|
Title = trackTitle
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
foreach (TagLib.ICodec codec in file.Properties.Codecs)
|
|
|
|
|
{
|
|
|
|
|
TagLib.IAudioCodec acodec = codec as TagLib.IAudioCodec;
|
|
|
|
|
TagLib.IVideoCodec vcodec = codec as TagLib.IVideoCodec;
|
|
|
|
|
|
|
|
|
|
if (acodec != null && (acodec.MediaTypes & TagLib.MediaTypes.Audio) != TagLib.MediaTypes.None)
|
|
|
|
|
{
|
|
|
|
|
Logger.Debug("Audio Properties : " + acodec.Description);
|
|
|
|
|
Logger.Debug("Bitrate: " + acodec.AudioBitrate);
|
|
|
|
|
Logger.Debug("SampleRate: " + acodec.AudioSampleRate);
|
|
|
|
|
Logger.Debug("Channels: " + acodec.AudioChannels + "\n");
|
|
|
|
|
|
|
|
|
|
result.Quality = QualityParser.ParseQuality(acodec.Description, acodec.AudioBitrate, acodec.AudioSampleRate);
|
|
|
|
|
Logger.Debug("Quality parsed: {0}", result.Quality);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Check if it is common that we might need to fallback to parser to gather details
|
|
|
|
|
//var result = ParseMusicTitle(fileInfo.Name);
|
|
|
|
|
|
|
|
|
|
@@ -632,17 +399,6 @@ namespace NzbDrone.Core.Parser
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string CleanSeriesTitle(this string title)
|
|
|
|
|
{
|
|
|
|
|
long number = 0;
|
|
|
|
|
|
|
|
|
|
//If Title only contains numbers return it as is.
|
|
|
|
|
if (long.TryParse(title, out number))
|
|
|
|
|
return title;
|
|
|
|
|
|
|
|
|
|
return NormalizeRegex.Replace(title, string.Empty).ToLower().RemoveAccent();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string CleanArtistName(this string name)
|
|
|
|
|
{
|
|
|
|
|
long number = 0;
|
|
|
|
|
@@ -722,97 +478,59 @@ namespace NzbDrone.Core.Parser
|
|
|
|
|
return title;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Language ParseLanguage(string title)
|
|
|
|
|
private static ParsedTrackInfo ParseAudioTags(string path)
|
|
|
|
|
{
|
|
|
|
|
var lowerTitle = title.ToLower();
|
|
|
|
|
var fileInfo = new FileInfo(path);
|
|
|
|
|
var file = TagLib.File.Create(path);
|
|
|
|
|
var trackNumber = file.Tag.Track;
|
|
|
|
|
var trackTitle = file.Tag.Title;
|
|
|
|
|
|
|
|
|
|
if (lowerTitle.Contains("english"))
|
|
|
|
|
return Language.English;
|
|
|
|
|
var artist = file.Tag.FirstAlbumArtist;
|
|
|
|
|
|
|
|
|
|
if (lowerTitle.Contains("french"))
|
|
|
|
|
return Language.French;
|
|
|
|
|
if (artist.IsNullOrWhiteSpace())
|
|
|
|
|
{
|
|
|
|
|
artist = file.Tag.FirstPerformer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (lowerTitle.Contains("spanish"))
|
|
|
|
|
return Language.Spanish;
|
|
|
|
|
var artistTitleInfo = new ArtistTitleInfo
|
|
|
|
|
{
|
|
|
|
|
Title = artist,
|
|
|
|
|
Year = (int)file.Tag.Year
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (lowerTitle.Contains("danish"))
|
|
|
|
|
return Language.Danish;
|
|
|
|
|
var temp = new int[1];
|
|
|
|
|
temp[0] = (int)trackNumber;
|
|
|
|
|
var result = new ParsedTrackInfo
|
|
|
|
|
{
|
|
|
|
|
Language = Language.English, //TODO Parse from Tag/Mediainfo
|
|
|
|
|
AlbumTitle = file.Tag.Album,
|
|
|
|
|
ArtistTitle = artist,
|
|
|
|
|
ArtistMBId = file.Tag.MusicBrainzArtistId,
|
|
|
|
|
AlbumMBId = file.Tag.MusicBrainzReleaseId,
|
|
|
|
|
TrackMBId = file.Tag.MusicBrainzReleaseType,
|
|
|
|
|
TrackNumbers = temp,
|
|
|
|
|
ArtistTitleInfo = artistTitleInfo,
|
|
|
|
|
Title = trackTitle
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (lowerTitle.Contains("dutch"))
|
|
|
|
|
return Language.Dutch;
|
|
|
|
|
foreach (TagLib.ICodec codec in file.Properties.Codecs)
|
|
|
|
|
{
|
|
|
|
|
TagLib.IAudioCodec acodec = codec as TagLib.IAudioCodec;
|
|
|
|
|
TagLib.IVideoCodec vcodec = codec as TagLib.IVideoCodec;
|
|
|
|
|
|
|
|
|
|
if (lowerTitle.Contains("japanese"))
|
|
|
|
|
return Language.Japanese;
|
|
|
|
|
if (acodec != null && (acodec.MediaTypes & TagLib.MediaTypes.Audio) != TagLib.MediaTypes.None)
|
|
|
|
|
{
|
|
|
|
|
Logger.Debug("Audio Properties : " + acodec.Description);
|
|
|
|
|
Logger.Debug("Bitrate: " + acodec.AudioBitrate);
|
|
|
|
|
Logger.Debug("SampleRate: " + acodec.AudioSampleRate);
|
|
|
|
|
Logger.Debug("Channels: " + acodec.AudioChannels + "\n");
|
|
|
|
|
|
|
|
|
|
if (lowerTitle.Contains("cantonese"))
|
|
|
|
|
return Language.Cantonese;
|
|
|
|
|
result.Quality = QualityParser.ParseQuality(acodec.Description, acodec.AudioBitrate, acodec.AudioSampleRate);
|
|
|
|
|
Logger.Debug("Quality parsed: {0}", result.Quality);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
if (lowerTitle.Contains("hungarian"))
|
|
|
|
|
return Language.Hungarian;
|
|
|
|
|
|
|
|
|
|
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["spanish"].Captures.Cast<Capture>().Any())
|
|
|
|
|
return Language.Spanish;
|
|
|
|
|
|
|
|
|
|
if (match.Groups["french"].Success)
|
|
|
|
|
return Language.French;
|
|
|
|
|
|
|
|
|
|
if (match.Groups["russian"].Success)
|
|
|
|
|
return Language.Russian;
|
|
|
|
|
|
|
|
|
|
if (match.Groups["dutch"].Success)
|
|
|
|
|
return Language.Dutch;
|
|
|
|
|
|
|
|
|
|
if (match.Groups["hungarian"].Success)
|
|
|
|
|
return Language.Hungarian;
|
|
|
|
|
|
|
|
|
|
return Language.English;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static ParsedTrackInfo ParseMatchMusicCollection(MatchCollection matchCollection)
|
|
|
|
|
@@ -898,7 +616,7 @@ namespace NzbDrone.Core.Parser
|
|
|
|
|
|
|
|
|
|
if (parseResult == null)
|
|
|
|
|
{
|
|
|
|
|
return CleanSeriesTitle(title);
|
|
|
|
|
return CleanArtistName(title);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return parseResult.ArtistName;
|
|
|
|
|
|