11using System ;
22using System . Collections . Generic ;
3+ using System . Globalization ;
34using System . IO ;
45using System . Linq ;
56using System . Text . RegularExpressions ;
@@ -39,31 +40,31 @@ public static class Parser
3940 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
4041
4142 //Anime - [SubGroup] Title Episode Absolute Episode Number ([SubGroup] Series Title Episode 01)
42- new Regex ( @"^(?:\[(?<subgroup>.+?)\][-_. ]?)(?<title>.+?)[-_. ](?:Episode)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?" ,
43+ new Regex ( @"^(?:\[(?<subgroup>.+?)\][-_. ]?)(?<title>.+?)[-_. ](?:Episode)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(\.\d{1,2})?( ?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?" ,
4344 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
4445
4546 //Anime - [SubGroup] Title Absolute Episode Number + Season+Episode
46- 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}[)\]])?(?:$|\.)" ,
47+ 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}[)\]])?(?:$|\.)" ,
4748 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
4849
4950 //Anime - [SubGroup] Title Season+Episode + Absolute Episode Number
50- 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}\])?(?:$|\.)" ,
51+ 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}\])?(?:$|\.)" ,
5152 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
5253
5354 //Anime - [SubGroup] Title Season+Episode
5455 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}\])?(?:$|\.)" ,
5556 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
5657
5758 //Anime - [SubGroup] Title with trailing number Absolute Episode Number
58- new Regex ( @"^\[(?<subgroup>.+?)\][-_. ]?(?<title>[^-]+?\d+?)[-_. ]+(?:[-_. ]?(?<absoluteepisode>\d{3}(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)" ,
59+ new Regex ( @"^\[(?<subgroup>.+?)\][-_. ]?(?<title>[^-]+?\d+?)[-_. ]+(?:[-_. ]?(?<absoluteepisode>\d{3}(\.\d{1,2})?( ?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)" ,
5960 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
6061
6162 //Anime - [SubGroup] Title - Absolute Episode Number
62- new Regex ( @"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)(?:[. ]-[. ](?<absoluteepisode>\d{2,3}(?!\d+|[-])))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)" ,
63+ new Regex ( @"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)(?:[. ]-[. ](?<absoluteepisode>\d{2,3}(\.\d{1,2})?( ?!\d+|[-])))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)" ,
6364 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
6465
6566 //Anime - [SubGroup] Title Absolute Episode Number
66- new Regex ( @"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)[-_. ]+\(?(?:[-_. ]?#?(?<absoluteepisode>\d{2,3}(?!\d+)))+\)?(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)" ,
67+ new Regex ( @"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)[-_. ]+\(?(?:[-_. ]?#?(?<absoluteepisode>\d{2,3}(\.\d{1,2})?( ?!\d+)))+\)?(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)" ,
6768 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
6869
6970 //Multi-episode Repeated (S01E05 - S01E06, 1x05 - 1x06, etc)
@@ -75,19 +76,19 @@ public static class Parser
7576 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
7677
7778 //Anime - Title Season EpisodeNumber + Absolute Episode Number [SubGroup]
78- 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)" ,
79+ 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)" ,
7980 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
8081
8182 //Multi-Episode with a title (S01E05E06, S01E05-06, S01E05 E06, etc) and trailing info in slashes
8283 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+)))+).+?(?:\[.+?\])(?!\\)" ,
8384 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
8485
8586 //Anime - Title Absolute Episode Number [SubGroup]
86- new Regex ( @"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{3}(?!\d+)))+(?:.+?)\[(?<subgroup>.+?)\].*?(?<hash>\[\w{8}\])?(?:$|\.)" ,
87+ new Regex ( @"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{3}(\.\d{1,2})?( ?!\d+)))+(?:.+?)\[(?<subgroup>.+?)\].*?(?<hash>\[\w{8}\])?(?:$|\.)" ,
8788 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
8889
8990 //Anime - Title Absolute Episode Number [Hash]
90- new Regex ( @"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{2,3}(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?[-_. ]+.*?(?<hash>\[\w{8}\])(?:$|\.)" ,
91+ new Regex ( @"^(?<title>.+?)(?:(?:_|-|\s|\.)+(?<absoluteepisode>\d{2,3}(\.\d{1,2})?( ?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?[-_. ]+.*?(?<hash>\[\w{8}\])(?:$|\.)" ,
9192 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
9293
9394 //Episodes with airdate AND season/episode number, capture season/epsiode only
@@ -171,11 +172,11 @@ public static class Parser
171172 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
172173
173174 // Anime - Title with season number - Absolute Episode Number (Title S01 - EP14)
174- new Regex ( @"^(?<title>.+?S\d{1,2})[-_. ]{3,}(?:EP)?(?<absoluteepisode>\d{2,3}(?!\d+|[-]))" ,
175+ new Regex ( @"^(?<title>.+?S\d{1,2})[-_. ]{3,}(?:EP)?(?<absoluteepisode>\d{2,3}(\.\d{1,2})?( ?!\d+|[-]))" ,
175176 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
176177
177178 // Anime - French titles with single episode numbers, with or without leading sub group ([RlsGroup] Title - Episode 1)
178- new Regex ( @"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)[-_. ]+?(?:Episode[-_. ]+?)(?<absoluteepisode>\d{1}(?!\d+))" ,
179+ new Regex ( @"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)[-_. ]+?(?:Episode[-_. ]+?)(?<absoluteepisode>\d{1}(\.\d{1,2})?( ?!\d+))" ,
179180 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
180181
181182 //Season only releases
@@ -230,19 +231,19 @@ public static class Parser
230231
231232 // TODO: THIS ONE
232233 //Anime - Title Absolute Episode Number (e66)
233- new Regex ( @"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:(?:_|-|\s|\.)+(?:e|ep)(?<absoluteepisode>\d{2,3}))+.*?(?<hash>\[\w{8}\])?(?:$|\.)" ,
234+ new Regex ( @"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:(?:_|-|\s|\.)+(?:e|ep)(?<absoluteepisode>\d{2,3}(\.\d{1,2})? ))+.*?(?<hash>\[\w{8}\])?(?:$|\.)" ,
234235 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
235236
236237 //Anime - Title Episode Absolute Episode Number (Series Title Episode 01)
237- new Regex ( @"^(?<title>.+?)[-_. ](?:Episode)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?" ,
238+ new Regex ( @"^(?<title>.+?)[-_. ](?:Episode)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(\.\d{1,2})?( ?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?" ,
238239 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
239240
240241 //Anime - Title Absolute Episode Number
241- new Regex ( @"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?" ,
242+ new Regex ( @"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:[-_. ]+(?<absoluteepisode>(?<!\d+)\d{2,3}(\.\d{1,2})?( ?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?" ,
242243 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
243244
244245 //Anime - Title {Absolute Episode Number}
245- new Regex ( @"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+(?<absoluteepisode>(?<!\d+)\d{2,3}(?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?" ,
246+ new Regex ( @"^(?:\[(?<subgroup>.+?)\][-_. ]?)?(?<title>.+?)(?:(?:[-_\W](?<![()\[!]))+(?<absoluteepisode>(?<!\d+)\d{2,3}(\.\d{1,2})?( ?!\d+)))+(?:_|-|\s|\.)*?(?<hash>\[.{8}\])?(?:$|\.)?" ,
246247 RegexOptions . IgnoreCase | RegexOptions . Compiled ) ,
247248
248249 //Extant, terrible multi-episode naming (extant.10708.hdtv-lol.mp4)
@@ -653,21 +654,32 @@ private static ParsedEpisodeInfo ParseMatchCollection(MatchCollection matchColle
653654
654655 if ( absoluteEpisodeCaptures . Any ( ) )
655656 {
656- var first = Convert . ToInt32 ( absoluteEpisodeCaptures . First ( ) . Value ) ;
657- var last = Convert . ToInt32 ( absoluteEpisodeCaptures . Last ( ) . Value ) ;
657+ var first = Convert . ToDecimal ( absoluteEpisodeCaptures . First ( ) . Value , CultureInfo . InvariantCulture ) ;
658+ var last = Convert . ToDecimal ( absoluteEpisodeCaptures . Last ( ) . Value , CultureInfo . InvariantCulture ) ;
658659
659660 if ( first > last )
660661 {
661662 return null ;
662663 }
663664
664- var count = last - first + 1 ;
665- result . AbsoluteEpisodeNumbers = Enumerable . Range ( first , count ) . ToArray ( ) ;
666-
667- if ( matchGroup . Groups [ "special" ] . Success )
665+ if ( ( first % 1 ) != 0 || ( last % 1 ) != 0 )
668666 {
667+ if ( absoluteEpisodeCaptures . Count != 1 )
668+ return null ; // Multiple matches not allowed for specials
669+
670+ result . SpecialAbsoluteEpisodeNumbers = new decimal [ ] { first } ;
669671 result . Special = true ;
670672 }
673+ else
674+ {
675+ var count = last - first + 1 ;
676+ result . AbsoluteEpisodeNumbers = Enumerable . Range ( ( int ) first , ( int ) count ) . ToArray ( ) ;
677+
678+ if ( matchGroup . Groups [ "special" ] . Success )
679+ {
680+ result . Special = true ;
681+ }
682+ }
671683 }
672684
673685 if ( ! episodeCaptures . Any ( ) && ! absoluteEpisodeCaptures . Any ( ) )
0 commit comments