public NamingOptions() { VideoFileExtensions = new[] { ".m4v", ".3gp", ".nsv", ".ts", ".ty", ".strm", ".rm", ".rmvb", ".ifo", ".mov", ".qt", ".divx", ".xvid", ".bivx", ".vob", ".nrg", ".img", ".iso", ".pva", ".wmv", ".asf", ".asx", ".ogm", ".m2v", ".avi", ".bin", ".dvr-ms", ".mpg", ".mpeg", ".mp4", ".mkv", ".avc", ".vp3", ".svq3", ".nuv", ".viv", ".dv", ".fli", ".flv", ".001", ".tp" }; VideoFlagDelimiters = new[] { '(', ')', '-', '.', '_', '[', ']' }; StubFileExtensions = new[] { ".disc" }; StubTypes = new[] { new StubTypeRule { StubType = "dvd", Token = "dvd" }, new StubTypeRule { StubType = "hddvd", Token = "hddvd" }, new StubTypeRule { StubType = "bluray", Token = "bluray" }, new StubTypeRule { StubType = "bluray", Token = "brrip" }, new StubTypeRule { StubType = "bluray", Token = "bd25" }, new StubTypeRule { StubType = "bluray", Token = "bd50" }, new StubTypeRule { StubType = "vhs", Token = "vhs" }, new StubTypeRule { StubType = "tv", Token = "HDTV" }, new StubTypeRule { StubType = "tv", Token = "PDTV" }, new StubTypeRule { StubType = "tv", Token = "DSR" } }; VideoFileStackingExpressions = new[] { "(.*?)([ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(.*?)(\\.[^.]+)$", "(.*?)([ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(.*?)(\\.[^.]+)$", "(.*?)([ ._-]*[a-d])(.*?)(\\.[^.]+)$" }; CleanDateTimes = new[] { @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*", @"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*" }; CleanStrings = new[] { @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)", @"(\[.*\])" }; SubtitleFileExtensions = new[] { ".srt", ".ssa", ".ass", ".sub" }; SubtitleFlagDelimiters = new[] { '.' }; SubtitleForcedFlags = new[] { "foreign", "forced" }; SubtitleDefaultFlags = new[] { "default" }; AlbumStackingPrefixes = new[] { "disc", "cd", "disk", "vol", "volume" }; AudioFileExtensions = new[] { ".nsv", ".m4a", ".flac", ".aac", ".strm", ".pls", ".rm", ".mpa", ".wav", ".wma", ".ogg", ".opus", ".mp3", ".mp2", ".mod", ".amf", ".669", ".dmf", ".dsm", ".far", ".gdm", ".imf", ".it", ".m15", ".med", ".okt", ".s3m", ".stm", ".sfx", ".ult", ".uni", ".xm", ".sid", ".ac3", ".dts", ".cue", ".aif", ".aiff", ".ape", ".mac", ".mpc", ".mp+", ".mpp", ".shn", ".wv", ".nsf", ".spc", ".gym", ".adplug", ".adx", ".dsp", ".adp", ".ymf", ".ast", ".afc", ".hps", ".xsp", ".acc", ".m4b", ".oga", ".dsf", ".mka" }; EpisodeExpressions = new[] { // *** Begin Kodi Standard Naming // <!-- foo.s01.e01, foo.s01_e01, S01E02 foo, S01 - E02 --> new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![Ss]([0-9]+)[][ ._-]*[Ee]([0-9]+))[^\\\/])*)?[Ss](?<seasonnumber>[0-9]+)[][ ._-]*[Ee](?<epnumber>[0-9]+)([^\\/]*)$") { IsNamed = true }, // <!-- foo.ep01, foo.EP_01 --> new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"), new EpisodeExpression("([0-9]{4})[\\.-]([0-9]{2})[\\.-]([0-9]{2})", true) { DateTimeFormats = new[] { "yyyy.MM.dd", "yyyy-MM-dd", "yyyy_MM_dd" } }, new EpisodeExpression("([0-9]{2})[\\.-]([0-9]{2})[\\.-]([0-9]{4})", true) { DateTimeFormats = new[] { "dd.MM.yyyy", "dd-MM-yyyy", "dd_MM_yyyy" } }, // This isn't a Kodi naming rule, but the expression below causes false positives, // so we make sure this one gets tested first. // "Foo Bar 889" new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/x]*$") { IsNamed = true }, new EpisodeExpression("[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$") { SupportsAbsoluteEpisodeNumbers = true }, new EpisodeExpression(@"[\\\\/\\._ -](?<seriesname>(?![0-9]+[0-9][0-9])([^\\\/])*)[\\\\/\\._ -](?<seasonnumber>[0-9]+)(?<epnumber>[0-9][0-9](?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([\\._ -][^\\\\/]*)$") { IsOptimistic = true, IsNamed = true, SupportsAbsoluteEpisodeNumbers = false }, new EpisodeExpression("[\\/._ -]p(?:ar)?t[_. -]()([ivx]+|[0-9]+)([._ -][^\\/]*)$") { SupportsAbsoluteEpisodeNumbers = true }, // *** End Kodi Standard Naming // [bar] Foo - 1 [baz] new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>[0-9]+).*$") { IsNamed = true }, new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]+)[xX](?<epnumber>[0-9]+)[^\\\/]*$") { IsNamed = true }, new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>[0-9]+)[x,X]?[eE](?<epnumber>[0-9]+)[^\\\/]*$") { IsNamed = true }, new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]+))[^\\\/]*$") { IsNamed = true }, new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]+)[^\\\/]*$") { IsNamed = true }, // "01.avi" new EpisodeExpression(@".*[\\\/](?<epnumber>[0-9]+)(-(?<endingepnumber>[0-9]+))*\.\w+$") { IsOptimistic = true, IsNamed = true }, // "1-12 episode title" new EpisodeExpression(@"([0-9]+)-([0-9]+)"), // "01 - blah.avi", "01-blah.avi" new EpisodeExpression(@".*(\\|\/)(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*\s?-\s?[^\\\/]*$") { IsOptimistic = true, IsNamed = true }, // "01.blah.avi" new EpisodeExpression(@".*(\\|\/)(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*\.[^\\\/]+$") { IsOptimistic = true, IsNamed = true }, // "blah - 01.avi", "blah 2 - 01.avi", "blah - 01 blah.avi", "blah 2 - 01 blah", "blah - 01 - blah.avi", "blah 2 - 01 - blah" new EpisodeExpression(@".*[\\\/][^\\\/]* - (?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/]*$") { IsOptimistic = true, IsNamed = true }, // "01 episode title.avi" new EpisodeExpression(@"[Ss]eason[\._ ](?<seasonnumber>[0-9]+)[\\\/](?<epnumber>[0-9]{1,3})([^\\\/]*)$") { IsOptimistic = true, IsNamed = true }, // "Episode 16", "Episode 16 - Title" new EpisodeExpression(@".*[\\\/][^\\\/]* (?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/]*$") { IsOptimistic = true, IsNamed = true } }; EpisodeWithoutSeasonExpressions = new[] { @"[/\._ \-]()([0-9]+)(-[0-9]+)?" }; EpisodeMultiPartExpressions = new[] { @"^[-_ex]+([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)" }; VideoExtraRules = new[] { new ExtraRule { ExtraType = ExtraType.Trailer, RuleType = ExtraRuleType.Filename, Token = "trailer", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.Trailer, RuleType = ExtraRuleType.Suffix, Token = "-trailer", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.Trailer, RuleType = ExtraRuleType.Suffix, Token = ".trailer", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.Trailer, RuleType = ExtraRuleType.Suffix, Token = "_trailer", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.Trailer, RuleType = ExtraRuleType.Suffix, Token = " trailer", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.Sample, RuleType = ExtraRuleType.Filename, Token = "sample", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.Sample, RuleType = ExtraRuleType.Suffix, Token = "-sample", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.Sample, RuleType = ExtraRuleType.Suffix, Token = ".sample", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.Sample, RuleType = ExtraRuleType.Suffix, Token = "_sample", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.Sample, RuleType = ExtraRuleType.Suffix, Token = " sample", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.ThemeSong, RuleType = ExtraRuleType.Filename, Token = "theme", MediaType = MediaType.Audio }, new ExtraRule { ExtraType = ExtraType.Scene, RuleType = ExtraRuleType.Suffix, Token = "-scene", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.Clip, RuleType = ExtraRuleType.Suffix, Token = "-clip", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.Interview, RuleType = ExtraRuleType.Suffix, Token = "-interview", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.BehindTheScenes, RuleType = ExtraRuleType.Suffix, Token = "-behindthescenes", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.DeletedScene, RuleType = ExtraRuleType.Suffix, Token = "-deleted", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.Clip, RuleType = ExtraRuleType.Suffix, Token = "-featurette", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.Clip, RuleType = ExtraRuleType.Suffix, Token = "-short", MediaType = MediaType.Video }, new ExtraRule { ExtraType = ExtraType.BehindTheScenes, RuleType = ExtraRuleType.DirectoryName, Token = "behind the scenes", MediaType = MediaType.Video, }, new ExtraRule { ExtraType = ExtraType.DeletedScene, RuleType = ExtraRuleType.DirectoryName, Token = "deleted scenes", MediaType = MediaType.Video, }, new ExtraRule { ExtraType = ExtraType.Interview, RuleType = ExtraRuleType.DirectoryName, Token = "interviews", MediaType = MediaType.Video, }, new ExtraRule { ExtraType = ExtraType.Scene, RuleType = ExtraRuleType.DirectoryName, Token = "scenes", MediaType = MediaType.Video, }, new ExtraRule { ExtraType = ExtraType.Sample, RuleType = ExtraRuleType.DirectoryName, Token = "samples", MediaType = MediaType.Video, }, new ExtraRule { ExtraType = ExtraType.Clip, RuleType = ExtraRuleType.DirectoryName, Token = "shorts", MediaType = MediaType.Video, }, new ExtraRule { ExtraType = ExtraType.Clip, RuleType = ExtraRuleType.DirectoryName, Token = "featurettes", MediaType = MediaType.Video, }, new ExtraRule { ExtraType = ExtraType.Unknown, RuleType = ExtraRuleType.DirectoryName, Token = "extras", MediaType = MediaType.Video, }, }; Format3DRules = new[] { // Kodi rules: new Format3DRule { PreceedingToken = "3d", Token = "hsbs" }, new Format3DRule { PreceedingToken = "3d", Token = "sbs" }, new Format3DRule { PreceedingToken = "3d", Token = "htab" }, new Format3DRule { PreceedingToken = "3d", Token = "tab" }, // Media Browser rules: new Format3DRule { Token = "fsbs" }, new Format3DRule { Token = "hsbs" }, new Format3DRule { Token = "sbs" }, new Format3DRule { Token = "ftab" }, new Format3DRule { Token = "htab" }, new Format3DRule { Token = "tab" }, new Format3DRule { Token = "sbs3d" }, new Format3DRule { Token = "mvc" } }; AudioBookPartsExpressions = new[] { // Detect specified chapters, like CH 01 @"ch(?:apter)?[\s_-]?(?<chapter>[0-9]+)", // Detect specified parts, like Part 02 @"p(?:ar)?t[\s_-]?(?<part>[0-9]+)", // Chapter is often beginning of filename "^(?<chapter>[0-9]+)", // Part if often ending of filename "(?<part>[0-9]+)$", // Sometimes named as 0001_005 (chapter_part) "(?<chapter>[0-9]+)_(?<part>[0-9]+)", // Some audiobooks are ripped from cd's, and will be named by disk number. @"dis(?:c|k)[\s_-]?(?<chapter>[0-9]+)" }; var extensions = VideoFileExtensions.ToList(); extensions.AddRange(new[] { ".mkv", ".m2t", ".m2ts", ".img", ".iso", ".mk3d", ".ts", ".rmvb", ".mov", ".avi", ".mpg", ".mpeg", ".wmv", ".mp4", ".divx", ".dvr-ms", ".wtv", ".ogm", ".ogv", ".asf", ".m4v", ".flv", ".f4v", ".3gp", ".webm", ".mts", ".m2v", ".rec", ".mxf" }); MultipleEpisodeExpressions = new string[] { @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[xX][eE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})(-[xE]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )[0-9]{1,4}[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )[0-9]{1,4}[xX][eE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))(-[xX]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]{1,3})((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]{1,3})(-[xX]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$" }.Select(i => new EpisodeExpression(i) { IsNamed = true }).ToArray(); VideoFileExtensions = extensions .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); Compile(); }
/// <summary> /// Initializes a new instance of the <see cref="NamingOptions"/> class. /// </summary> public NamingOptions() { VideoFileExtensions = new[] { ".m4v", ".3gp", ".nsv", ".ts", ".ty", ".strm", ".rm", ".rmvb", ".ifo", ".mov", ".qt", ".divx", ".xvid", ".bivx", ".vob", ".nrg", ".img", ".iso", ".pva", ".wmv", ".asf", ".asx", ".ogm", ".m2v", ".avi", ".bin", ".dvr-ms", ".mpg", ".mpeg", ".mp4", ".mkv", ".avc", ".vp3", ".svq3", ".nuv", ".viv", ".dv", ".fli", ".flv", ".001", ".tp" }; VideoFlagDelimiters = new[] { '(', ')', '-', '.', '_', '[', ']' }; StubFileExtensions = new[] { ".disc" }; StubTypes = new[] { new StubTypeRule( stubType: "dvd", token: "dvd"), new StubTypeRule( stubType: "hddvd", token: "hddvd"), new StubTypeRule( stubType: "bluray", token: "bluray"), new StubTypeRule( stubType: "bluray", token: "brrip"), new StubTypeRule( stubType: "bluray", token: "bd25"), new StubTypeRule( stubType: "bluray", token: "bd50"), new StubTypeRule( stubType: "vhs", token: "vhs"), new StubTypeRule( stubType: "tv", token: "HDTV"), new StubTypeRule( stubType: "tv", token: "PDTV"), new StubTypeRule( stubType: "tv", token: "DSR") }; VideoFileStackingRules = new[] { new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[0-9]+)[\)\]]?(?:\.[^.]+)?$", true), new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[a-d])[\)\]]?(?:\.[^.]+)?$", false), new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]?)(?<number>[a-d])(?:\.[^.]+)?$", false) }; CleanDateTimes = new[] { @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*", @"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*" }; CleanStrings = new[] { @"^\s*(?<cleaned>.+?)[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|blu-ray|x264|x265|h264|h265|xvid|xvidvd|xxx|www.www|AAC|DTS|\[.*\])([ _\,\.\(\)\[\]\-]|$)", @"^(?<cleaned>.+?)(\[.*\])", @"^\s*(?<cleaned>.+?)\WE[0-9]+(-|~)E?[0-9]+(\W|$)", @"^\s*\[[^\]]+\](?!\.\w+$)\s*(?<cleaned>.+)", @"^\s*(?<cleaned>.+?)\s+-\s+[0-9]+\s*$" }; SubtitleFileExtensions = new[] { ".srt", ".ssa", ".ass", ".sub" }; SubtitleFlagDelimiters = new[] { '.' }; SubtitleForcedFlags = new[] { "foreign", "forced" }; SubtitleDefaultFlags = new[] { "default" }; AlbumStackingPrefixes = new[] { "disc", "cd", "disk", "vol", "volume" }; AudioFileExtensions = new[] { ".nsv", ".m4a", ".flac", ".aac", ".strm", ".pls", ".rm", ".mpa", ".wav", ".wma", ".ogg", ".opus", ".mp3", ".mp2", ".mod", ".amf", ".669", ".dmf", ".dsm", ".far", ".gdm", ".imf", ".it", ".m15", ".med", ".okt", ".s3m", ".stm", ".sfx", ".ult", ".uni", ".xm", ".sid", ".ac3", ".dts", ".cue", ".aif", ".aiff", ".ape", ".mac", ".mpc", ".mp+", ".mpp", ".shn", ".wv", ".nsf", ".spc", ".gym", ".adplug", ".adx", ".dsp", ".adp", ".ymf", ".ast", ".afc", ".hps", ".xsp", ".acc", ".m4b", ".oga", ".dsf", ".mka" }; EpisodeExpressions = new[] { // *** Begin Kodi Standard Naming // <!-- foo.s01.e01, foo.s01_e01, S01E02 foo, S01 - E02 --> new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![Ss]([0-9]+)[][ ._-]*[Ee]([0-9]+))[^\\\/])*)?[Ss](?<seasonnumber>[0-9]+)[][ ._-]*[Ee](?<epnumber>[0-9]+)([^\\/]*)$") { IsNamed = true }, // <!-- foo.ep01, foo.EP_01 --> new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"), // <!-- foo.E01., foo.e01. --> new EpisodeExpression(@"[^\\/]*?()\.?[Ee]([0-9]+)\.([^\\/]*)$"), new EpisodeExpression("(?<year>[0-9]{4})[\\.-](?<month>[0-9]{2})[\\.-](?<day>[0-9]{2})", true) { DateTimeFormats = new[] { "yyyy.MM.dd", "yyyy-MM-dd", "yyyy_MM_dd" } }, new EpisodeExpression(@"(?<day>[0-9]{2})[.-](?<month>[0-9]{2})[.-](?<year>[0-9]{4})", true) { DateTimeFormats = new[] { "dd.MM.yyyy", "dd-MM-yyyy", "dd_MM_yyyy" } }, // This isn't a Kodi naming rule, but the expression below causes false positives, // so we make sure this one gets tested first. // "Foo Bar 889" new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/x]*$") { IsNamed = true }, new EpisodeExpression(@"[\\\/\._ \[\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\.[1-9])(?![0-9]))?)([^\\\/]*)$") { SupportsAbsoluteEpisodeNumbers = true }, // Not a Kodi rule as well, but below rule also causes false positives for triple-digit episode names // [bar] Foo - 1 [baz] special case of below expression to prevent false positives with digits in the series name new EpisodeExpression(@".*[\\\/]?.*?(\[.*?\])+.*?(?<seriesname>[-\w\s]+?)[\s_]*-[\s_]*(?<epnumber>[0-9]+).*$") { IsNamed = true }, // /server/anything_102.mp4 // /server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv // /server/anything_1996.11.14.mp4 new EpisodeExpression(@"[\\/._ -](?<seriesname>(?![0-9]+[0-9][0-9])([^\\\/_])*)[\\\/._ -](?<seasonnumber>[0-9]+)(?<epnumber>[0-9][0-9](?:(?:[a-i]|\.[1-9])(?![0-9]))?)([._ -][^\\\/]*)$") { IsOptimistic = true, IsNamed = true, SupportsAbsoluteEpisodeNumbers = false }, new EpisodeExpression("[\\/._ -]p(?:ar)?t[_. -]()([ivx]+|[0-9]+)([._ -][^\\/]*)$") { SupportsAbsoluteEpisodeNumbers = true }, // *** End Kodi Standard Naming // "Episode 16", "Episode 16 - Title" new EpisodeExpression(@"[Ee]pisode (?<epnumber>[0-9]+)(-(?<endingepnumber>[0-9]+))?[^\\\/]*$") { IsNamed = true }, new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]+)[xX](?<epnumber>[0-9]+)[^\\\/]*$") { IsNamed = true }, new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>[0-9]+)[x,X]?[eE](?<epnumber>[0-9]+)[^\\\/]*$") { IsNamed = true }, new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]+))[^\\\/]*$") { IsNamed = true }, new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]+)[^\\\/]*$") { IsNamed = true }, // "01.avi" new EpisodeExpression(@".*[\\\/](?<epnumber>[0-9]+)(-(?<endingepnumber>[0-9]+))*\.\w+$") { IsOptimistic = true, IsNamed = true }, // "1-12 episode title" new EpisodeExpression(@"([0-9]+)-([0-9]+)"), // "01 - blah.avi", "01-blah.avi" new EpisodeExpression(@".*(\\|\/)(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*\s?-\s?[^\\\/]*$") { IsOptimistic = true, IsNamed = true }, // "01.blah.avi" new EpisodeExpression(@".*(\\|\/)(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*\.[^\\\/]+$") { IsOptimistic = true, IsNamed = true }, // "blah - 01.avi", "blah 2 - 01.avi", "blah - 01 blah.avi", "blah 2 - 01 blah", "blah - 01 - blah.avi", "blah 2 - 01 - blah" new EpisodeExpression(@".*[\\\/][^\\\/]* - (?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/]*$") { IsOptimistic = true, IsNamed = true }, // "01 episode title.avi" new EpisodeExpression(@"[Ss]eason[\._ ](?<seasonnumber>[0-9]+)[\\\/](?<epnumber>[0-9]{1,3})([^\\\/]*)$") { IsOptimistic = true, IsNamed = true }, // Series and season only expression // "the show/season 1", "the show/s01" new EpisodeExpression(@"(.*(\\|\/))*(?<seriesname>.+)\/[Ss](eason)?[\. _\-]*(?<seasonnumber>[0-9]+)") { IsNamed = true }, // Series and season only expression // "the show S01", "the show season 1" new EpisodeExpression(@"(.*(\\|\/))*(?<seriesname>.+)[\. _\-]+[sS](eason)?[\. _\-]*(?<seasonnumber>[0-9]+)") { IsNamed = true }, }; EpisodeWithoutSeasonExpressions = new[] { @"[/\._ \-]()([0-9]+)(-[0-9]+)?" }; EpisodeMultiPartExpressions = new[] { @"^[-_ex]+([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)" }; VideoExtraRules = new[] { new ExtraRule( ExtraType.Trailer, ExtraRuleType.DirectoryName, "trailers", MediaType.Video), new ExtraRule( ExtraType.Trailer, ExtraRuleType.Filename, "trailer", MediaType.Video), new ExtraRule( ExtraType.Trailer, ExtraRuleType.Suffix, "-trailer", MediaType.Video), new ExtraRule( ExtraType.Trailer, ExtraRuleType.Suffix, ".trailer", MediaType.Video), new ExtraRule( ExtraType.Trailer, ExtraRuleType.Suffix, "_trailer", MediaType.Video), new ExtraRule( ExtraType.Trailer, ExtraRuleType.Suffix, " trailer", MediaType.Video), new ExtraRule( ExtraType.Sample, ExtraRuleType.Filename, "sample", MediaType.Video), new ExtraRule( ExtraType.Sample, ExtraRuleType.Suffix, "-sample", MediaType.Video), new ExtraRule( ExtraType.Sample, ExtraRuleType.Suffix, ".sample", MediaType.Video), new ExtraRule( ExtraType.Sample, ExtraRuleType.Suffix, "_sample", MediaType.Video), new ExtraRule( ExtraType.Sample, ExtraRuleType.Suffix, " sample", MediaType.Video), new ExtraRule( ExtraType.ThemeVideo, ExtraRuleType.DirectoryName, "backdrops", MediaType.Video), new ExtraRule( ExtraType.ThemeSong, ExtraRuleType.Filename, "theme", MediaType.Audio), new ExtraRule( ExtraType.ThemeSong, ExtraRuleType.DirectoryName, "theme-music", MediaType.Audio), new ExtraRule( ExtraType.Scene, ExtraRuleType.Suffix, "-scene", MediaType.Video), new ExtraRule( ExtraType.Clip, ExtraRuleType.Suffix, "-clip", MediaType.Video), new ExtraRule( ExtraType.Interview, ExtraRuleType.Suffix, "-interview", MediaType.Video), new ExtraRule( ExtraType.BehindTheScenes, ExtraRuleType.Suffix, "-behindthescenes", MediaType.Video), new ExtraRule( ExtraType.DeletedScene, ExtraRuleType.Suffix, "-deleted", MediaType.Video), new ExtraRule( ExtraType.DeletedScene, ExtraRuleType.Suffix, "-deletedscene", MediaType.Video), new ExtraRule( ExtraType.Clip, ExtraRuleType.Suffix, "-featurette", MediaType.Video), new ExtraRule( ExtraType.Clip, ExtraRuleType.Suffix, "-short", MediaType.Video), new ExtraRule( ExtraType.BehindTheScenes, ExtraRuleType.DirectoryName, "behind the scenes", MediaType.Video), new ExtraRule( ExtraType.DeletedScene, ExtraRuleType.DirectoryName, "deleted scenes", MediaType.Video), new ExtraRule( ExtraType.Interview, ExtraRuleType.DirectoryName, "interviews", MediaType.Video), new ExtraRule( ExtraType.Scene, ExtraRuleType.DirectoryName, "scenes", MediaType.Video), new ExtraRule( ExtraType.Sample, ExtraRuleType.DirectoryName, "samples", MediaType.Video), new ExtraRule( ExtraType.Clip, ExtraRuleType.DirectoryName, "shorts", MediaType.Video), new ExtraRule( ExtraType.Clip, ExtraRuleType.DirectoryName, "featurettes", MediaType.Video), new ExtraRule( ExtraType.Unknown, ExtraRuleType.DirectoryName, "extras", MediaType.Video) }; Format3DRules = new[] { // Kodi rules: new Format3DRule( precedingToken: "3d", token: "hsbs"), new Format3DRule( precedingToken: "3d", token: "sbs"), new Format3DRule( precedingToken: "3d", token: "htab"), new Format3DRule( precedingToken: "3d", token: "tab"), // Media Browser rules: new Format3DRule("fsbs"), new Format3DRule("hsbs"), new Format3DRule("sbs"), new Format3DRule("ftab"), new Format3DRule("htab"), new Format3DRule("tab"), new Format3DRule("sbs3d"), new Format3DRule("mvc") }; AudioBookPartsExpressions = new[] { // Detect specified chapters, like CH 01 @"ch(?:apter)?[\s_-]?(?<chapter>[0-9]+)", // Detect specified parts, like Part 02 @"p(?:ar)?t[\s_-]?(?<part>[0-9]+)", // Chapter is often beginning of filename "^(?<chapter>[0-9]+)", // Part if often ending of filename @"(?<!ch(?:apter) )(?<part>[0-9]+)$", // Sometimes named as 0001_005 (chapter_part) "(?<chapter>[0-9]+)_(?<part>[0-9]+)", // Some audiobooks are ripped from cd's, and will be named by disk number. @"dis(?:c|k)[\s_-]?(?<chapter>[0-9]+)" }; AudioBookNamesExpressions = new[] { // Detect year usually in brackets after name Batman (2020) @"^(?<name>.+?)\s*\(\s*(?<year>[0-9]{4})\s*\)\s*$", @"^\s*(?<name>[^ ].*?)\s*$" }; var extensions = VideoFileExtensions.ToList(); extensions.AddRange(new[] { ".mkv", ".m2t", ".m2ts", ".img", ".iso", ".mk3d", ".ts", ".rmvb", ".mov", ".avi", ".mpg", ".mpeg", ".wmv", ".mp4", ".divx", ".dvr-ms", ".wtv", ".ogm", ".ogv", ".asf", ".m4v", ".flv", ".f4v", ".3gp", ".webm", ".mts", ".m2v", ".rec", ".mxf" }); MultipleEpisodeExpressions = new[] { @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[xX][eE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})(-[xE]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )[0-9]{1,4}[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )[0-9]{1,4}[xX][eE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))(-[xX]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]{1,3})((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]{1,3})(-[xX]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$" }.Select(i => new EpisodeExpression(i) { IsNamed = true }).ToArray(); VideoFileExtensions = extensions .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); AllExtrasTypesFolderNames = new Dictionary <string, ExtraType>(StringComparer.OrdinalIgnoreCase) { ["trailers"] = ExtraType.Trailer, ["theme-music"] = ExtraType.ThemeSong, ["backdrops"] = ExtraType.ThemeVideo, ["extras"] = ExtraType.Unknown, ["behind the scenes"] = ExtraType.BehindTheScenes, ["deleted scenes"] = ExtraType.DeletedScene, ["interviews"] = ExtraType.Interview, ["scenes"] = ExtraType.Scene, ["samples"] = ExtraType.Sample, ["shorts"] = ExtraType.Clip, ["featurettes"] = ExtraType.Clip }; Compile(); }
/// <summary> /// Initializes a new instance of the <see cref="NamingOptions"/> class. /// </summary> public NamingOptions() { VideoFileExtensions = new[] { ".m4v", ".3gp", ".nsv", ".ts", ".ty", ".strm", ".rm", ".rmvb", ".ifo", ".mov", ".qt", ".divx", ".xvid", ".bivx", ".vob", ".nrg", ".img", ".iso", ".pva", ".wmv", ".asf", ".asx", ".ogm", ".m2v", ".avi", ".bin", ".dvr-ms", ".mpg", ".mpeg", ".mp4", ".mkv", ".avc", ".vp3", ".svq3", ".nuv", ".viv", ".dv", ".fli", ".flv", ".001", ".tp" }; VideoFlagDelimiters = new[] { '(', ')', '-', '.', '_', '[', ']' }; StubFileExtensions = new[] { ".disc" }; StubTypes = new[] { new StubTypeRule( stubType: "dvd", token: "dvd"), new StubTypeRule( stubType: "hddvd", token: "hddvd"), new StubTypeRule( stubType: "bluray", token: "bluray"), new StubTypeRule( stubType: "bluray", token: "brrip"), new StubTypeRule( stubType: "bluray", token: "bd25"), new StubTypeRule( stubType: "bluray", token: "bd50"), new StubTypeRule( stubType: "vhs", token: "vhs"), new StubTypeRule( stubType: "tv", token: "HDTV"), new StubTypeRule( stubType: "tv", token: "PDTV"), new StubTypeRule( stubType: "tv", token: "DSR") }; VideoFileStackingExpressions = new[] { "(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(?<ignore>.*?)(?<extension>\\.[^.]+)$", "(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$", "(?<title>.*?)(?<volume>[ ._-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$" }; CleanDateTimes = new[] { @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*", @"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*" }; CleanStrings = new[] { @"^(?:(?<header>\[雪飘工作室]\[|\[FLsnow]\[|\[Nekomoe\ kissaten]\[|\[LKSUB]\[|\[UHA-WINGS]\[|\[BDRIP]\[|\[YG&Neo\.sub]\[|\[KTXP]\[)(?<seriesname>[^[\]【]+)|(?:(?<seriesname>\d+[ .][^[\]【]+)\.bhd)|(?:【[^】]*】|\[[^\]]*])*(?<seriesname>[^[\]【]+?)(?:第[一二三四五六七八九1-9]季[ ]?)?(?: - \d+)?(?:\(480P.*)?(?:【[^】]*】|\[[^\]]*])*$|^\[(?<seriesname>[^\]]*)])" }; SubtitleFileExtensions = new[] { ".srt", ".ssa", ".ass", ".sub" }; SubtitleFlagDelimiters = new[] { '.' }; SubtitleForcedFlags = new[] { "foreign", "forced" }; SubtitleDefaultFlags = new[] { "default" }; AlbumStackingPrefixes = new[] { "disc", "cd", "disk", "vol", "volume" }; AudioFileExtensions = new[] { ".nsv", ".m4a", ".flac", ".aac", ".strm", ".pls", ".rm", ".mpa", ".wav", ".wma", ".ogg", ".opus", ".mp3", ".mp2", ".mod", ".amf", ".669", ".dmf", ".dsm", ".far", ".gdm", ".imf", ".it", ".m15", ".med", ".okt", ".s3m", ".stm", ".sfx", ".ult", ".uni", ".xm", ".sid", ".ac3", ".dts", ".cue", ".aif", ".aiff", ".ape", ".mac", ".mpc", ".mp+", ".mpp", ".shn", ".wv", ".nsf", ".spc", ".gym", ".adplug", ".adx", ".dsp", ".adp", ".ymf", ".ast", ".afc", ".hps", ".xsp", ".acc", ".m4b", ".oga", ".dsf", ".mka" }; EpisodeExpressions = new[] { // 用.fsx拼接编写的针对TV目录,适配各种中文格式的超长正则表达式 new EpisodeExpression(@"^(?:.*[\\/][[【](?<header>Nekomoe kissaten|LKSUB|UHA-WINGS|YG&Neo.sub|KTXP|動畫瘋|HYSUB|UHA-WINGS|Nekomoe kissaten&VCB-Studio|BDRIP)[\]】]\[?\ ?(?<seriesname>.+?)\ ?(?:第(?<seasonnumber>[一二三四五六七八九十1-9])季[ ]?|[Ss](?<seasonnumber>[1-9]))?(?<specialSeason>\[特別篇])?(?:\[年齡限制版])?(?:\ (?<epnumber>[0-9.]+)\ |]?\[(?<epnumber>[0-9.]+)])?(?:\[[^\]]*])*\.[A-z1-9]+|(?<seriesname>.+?)[\\/]\k<seriesname>\ (?<epnumber>[0-9.]+)\.[A-z1-9]+|(?<specialSeason>OVA|OAD|SP|特别篇|特別篇|【?剧场版】?)?(?<seriesname>.*?)\ ?(?:TV|MV|1080P|720P)?(?:第(?<seasonnumber>[一二三四五六七八九十1-9])季[ ]?|(?<seasonnumber>[1-9])|(?<seasonnumber>[Ⅰ-Ⅹ]))?(?<specialSeason>OVA|OAD|SP|特别篇|特別篇|【?剧场版】?)?(?:\[[^\]]*])*(?:\d{4})?[\\/](?:【漫锋网】)?(?:TV|MV|1080P|720P)?[\ _]?(?:第(?<seasonnumber>[一二三四五六七八九十1-9])季[ ]?)?(?:(?:(?:\[.*?])?\[)?(?:(?<specialSeason>OVA|OAD|SP|特别篇|特別篇|【?剧场版】?)|OP-ED|PV-CF|MV|AD|\ The\ Movie\ 简体).*?)?(?:(?<=[\\/ ])Menu)?(?:(?<=[\\/ ]|EP|OVA|OAD|SP|特别篇|特別篇|【?剧场版】?|[-_\u4e00-\u9fa5])(?<epnumber>[0-9.]+))?(?:\[[^\]]*])*(?:.*?\.bhd)?(?:\..*?)?(?:\ \(.*?)?\.[A-z1-9]+|(?<specialSeason>OVA|OAD|SP|特别篇|特別篇|【?剧场版】?)?.*?\ ?(?:TV|MV|1080P|720P)?(?:第(?<seasonnumber>[一二三四五六七八九十1-9])季[ ]?|(?<seasonnumber>[1-9])|(?<seasonnumber>[Ⅰ-Ⅹ]))?(?<specialSeason>OVA|OAD|SP|特别篇|特別篇|【?剧场版】?)?(?:\[[^\]]*])*(?:\d{4})?[\\/](?:【漫锋网】)?(?<seriesname>.*?)(?:TV|MV|1080P|720P)?[\ _]?(?:第(?<seasonnumber>[一二三四五六七八九十1-9])季[ ]?)?(?:(?:(?:\[.*?])?\[)?(?:(?<specialSeason>OVA|OAD|SP|特别篇|特別篇|【?剧场版】?)|OP-ED|PV-CF|MV|AD|\ The\ Movie\ 简体).*?)?(?:(?<=[\\/ ])Menu)?(?:(?<=[\\/ ]|EP|OVA|OAD|SP|特别篇|特別篇|【?剧场版】?|[-_\u4e00-\u9fa5])(?<epnumber>[0-9.]+))?(?:\[[^\]]*])*(?:.*?\.bhd)?(?:\..*?)?(?:\ \(.*?)?\.[A-z1-9]+)$") { IsNamed = true }, // *** Begin Kodi Standard Naming // <!-- foo.s01.e01, foo.s01_e01, S01E02 foo, S01 - E02 --> new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![Ss]([0-9]+)[][ ._-]*[Ee]([0-9]+))[^\\\/])*)?[Ss](?<seasonnumber>[0-9]+)[][ ._-]*[Ee](?<epnumber>[0-9]+)([^\\/]*)$") { IsNamed = true }, // <!-- foo.ep01, foo.EP_01 --> new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"), new EpisodeExpression("(?<year>[0-9]{4})[\\.-](?<month>[0-9]{2})[\\.-](?<day>[0-9]{2})", true) { DateTimeFormats = new[] { "yyyy.MM.dd", "yyyy-MM-dd", "yyyy_MM_dd" } }, new EpisodeExpression(@"(?<day>[0-9]{2})[.-](?<month>[0-9]{2})[.-](?<year>[0-9]{4})", true) { DateTimeFormats = new[] { "dd.MM.yyyy", "dd-MM-yyyy", "dd_MM_yyyy" } }, // This isn't a Kodi naming rule, but the expression below causes false positives, // so we make sure this one gets tested first. // "Foo Bar 889" new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/x]*$") { IsNamed = true }, new EpisodeExpression("[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$") { SupportsAbsoluteEpisodeNumbers = true }, // Not a Kodi rule as well, but below rule also causes false positives for triple-digit episode names // [bar] Foo - 1 [baz] special case of below expression to prevent false positives with digits in the series name new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[\s_]*-[\s_]*(?<epnumber>[0-9]+).*$") { IsNamed = true }, // /server/anything_102.mp4 // /server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv // /server/anything_1996.11.14.mp4 new EpisodeExpression(@"[\\/._ -](?<seriesname>(?![0-9]+[0-9][0-9])([^\\\/_])*)[\\\/._ -](?<seasonnumber>[0-9]+)(?<epnumber>[0-9][0-9](?:(?:[a-i]|\.[1-9])(?![0-9]))?)([._ -][^\\\/]*)$") { IsOptimistic = true, IsNamed = true, SupportsAbsoluteEpisodeNumbers = false }, new EpisodeExpression("[\\/._ -]p(?:ar)?t[_. -]()([ivx]+|[0-9]+)([._ -][^\\/]*)$") { SupportsAbsoluteEpisodeNumbers = true }, // *** End Kodi Standard Naming new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]+)[xX](?<epnumber>[0-9]+)[^\\\/]*$") { IsNamed = true }, new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>[0-9]+)[x,X]?[eE](?<epnumber>[0-9]+)[^\\\/]*$") { IsNamed = true }, new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]+))[^\\\/]*$") { IsNamed = true }, new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]+)[^\\\/]*$") { IsNamed = true }, // "01.avi" new EpisodeExpression(@".*[\\\/](?<epnumber>[0-9]+)(-(?<endingepnumber>[0-9]+))*\.\w+$") { IsOptimistic = true, IsNamed = true }, // "1-12 episode title" new EpisodeExpression(@"([0-9]+)-([0-9]+)"), // "01 - blah.avi", "01-blah.avi" new EpisodeExpression(@".*(\\|\/)(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*\s?-\s?[^\\\/]*$") { IsOptimistic = true, IsNamed = true }, // "01.blah.avi" new EpisodeExpression(@".*(\\|\/)(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*\.[^\\\/]+$") { IsOptimistic = true, IsNamed = true }, // "blah - 01.avi", "blah 2 - 01.avi", "blah - 01 blah.avi", "blah 2 - 01 blah", "blah - 01 - blah.avi", "blah 2 - 01 - blah" new EpisodeExpression(@".*[\\\/][^\\\/]* - (?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/]*$") { IsOptimistic = true, IsNamed = true }, // "01 episode title.avi" new EpisodeExpression(@"[Ss]eason[\._ ](?<seasonnumber>[0-9]+)[\\\/](?<epnumber>[0-9]{1,3})([^\\\/]*)$") { IsOptimistic = true, IsNamed = true }, // "Episode 16", "Episode 16 - Title" new EpisodeExpression(@".*[\\\/][^\\\/]* (?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/]*$") { IsOptimistic = true, IsNamed = true } }; EpisodeWithoutSeasonExpressions = new[] { @"[/\._ \-]()([0-9]+)(-[0-9]+)?" }; EpisodeMultiPartExpressions = new[] { @"^[-_ex]+([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)" }; VideoExtraRules = new[] { new ExtraRule( ExtraType.Trailer, ExtraRuleType.Filename, "trailer", MediaType.Video), new ExtraRule( ExtraType.Trailer, ExtraRuleType.Suffix, "-trailer", MediaType.Video), new ExtraRule( ExtraType.Trailer, ExtraRuleType.Suffix, ".trailer", MediaType.Video), new ExtraRule( ExtraType.Trailer, ExtraRuleType.Suffix, "_trailer", MediaType.Video), new ExtraRule( ExtraType.Trailer, ExtraRuleType.Suffix, " trailer", MediaType.Video), new ExtraRule( ExtraType.Sample, ExtraRuleType.Filename, "sample", MediaType.Video), new ExtraRule( ExtraType.Sample, ExtraRuleType.Suffix, "-sample", MediaType.Video), new ExtraRule( ExtraType.Sample, ExtraRuleType.Suffix, ".sample", MediaType.Video), new ExtraRule( ExtraType.Sample, ExtraRuleType.Suffix, "_sample", MediaType.Video), new ExtraRule( ExtraType.Sample, ExtraRuleType.Suffix, " sample", MediaType.Video), new ExtraRule( ExtraType.ThemeSong, ExtraRuleType.Filename, "theme", MediaType.Audio), new ExtraRule( ExtraType.Scene, ExtraRuleType.Suffix, "-scene", MediaType.Video), new ExtraRule( ExtraType.Clip, ExtraRuleType.Suffix, "-clip", MediaType.Video), new ExtraRule( ExtraType.Interview, ExtraRuleType.Suffix, "-interview", MediaType.Video), new ExtraRule( ExtraType.BehindTheScenes, ExtraRuleType.Suffix, "-behindthescenes", MediaType.Video), new ExtraRule( ExtraType.DeletedScene, ExtraRuleType.Suffix, "-deleted", MediaType.Video), new ExtraRule( ExtraType.Clip, ExtraRuleType.Suffix, "-featurette", MediaType.Video), new ExtraRule( ExtraType.Clip, ExtraRuleType.Suffix, "-short", MediaType.Video), new ExtraRule( ExtraType.BehindTheScenes, ExtraRuleType.DirectoryName, "behind the scenes", MediaType.Video), new ExtraRule( ExtraType.DeletedScene, ExtraRuleType.DirectoryName, "deleted scenes", MediaType.Video), new ExtraRule( ExtraType.Interview, ExtraRuleType.DirectoryName, "interviews", MediaType.Video), new ExtraRule( ExtraType.Scene, ExtraRuleType.DirectoryName, "scenes", MediaType.Video), new ExtraRule( ExtraType.Sample, ExtraRuleType.DirectoryName, "samples", MediaType.Video), new ExtraRule( ExtraType.Clip, ExtraRuleType.DirectoryName, "shorts", MediaType.Video), new ExtraRule( ExtraType.Clip, ExtraRuleType.DirectoryName, "featurettes", MediaType.Video), new ExtraRule( ExtraType.Unknown, ExtraRuleType.DirectoryName, "extras", MediaType.Video), }; Format3DRules = new[] { // Kodi rules: new Format3DRule( precedingToken: "3d", token: "hsbs"), new Format3DRule( precedingToken: "3d", token: "sbs"), new Format3DRule( precedingToken: "3d", token: "htab"), new Format3DRule( precedingToken: "3d", token: "tab"), // Media Browser rules: new Format3DRule("fsbs"), new Format3DRule("hsbs"), new Format3DRule("sbs"), new Format3DRule("ftab"), new Format3DRule("htab"), new Format3DRule("tab"), new Format3DRule("sbs3d"), new Format3DRule("mvc") }; AudioBookPartsExpressions = new[] { // Detect specified chapters, like CH 01 @"ch(?:apter)?[\s_-]?(?<chapter>[0-9]+)", // Detect specified parts, like Part 02 @"p(?:ar)?t[\s_-]?(?<part>[0-9]+)", // Chapter is often beginning of filename "^(?<chapter>[0-9]+)", // Part if often ending of filename @"(?<!ch(?:apter) )(?<part>[0-9]+)$", // Sometimes named as 0001_005 (chapter_part) "(?<chapter>[0-9]+)_(?<part>[0-9]+)", // Some audiobooks are ripped from cd's, and will be named by disk number. @"dis(?:c|k)[\s_-]?(?<chapter>[0-9]+)" }; AudioBookNamesExpressions = new[] { // Detect year usually in brackets after name Batman (2020) @"^(?<name>.+?)\s*\(\s*(?<year>[0-9]{4})\s*\)\s*$", @"^\s*(?<name>[^ ].*?)\s*$" }; var extensions = VideoFileExtensions.ToList(); extensions.AddRange(new[] { ".mkv", ".m2t", ".m2ts", ".img", ".iso", ".mk3d", ".ts", ".rmvb", ".mov", ".avi", ".mpg", ".mpeg", ".wmv", ".mp4", ".divx", ".dvr-ms", ".wtv", ".ogm", ".ogv", ".asf", ".m4v", ".flv", ".f4v", ".3gp", ".webm", ".mts", ".m2v", ".rec", ".mxf" }); MultipleEpisodeExpressions = new[] { @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[xX][eE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})(-[xE]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )[0-9]{1,4}[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )[0-9]{1,4}[xX][eE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))(-[xX]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]{1,3})((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]{1,3})(-[xX]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$" }.Select(i => new EpisodeExpression(i) { IsNamed = true }).ToArray(); VideoFileExtensions = extensions .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); Compile(); }
public ExtendedNamingOptions() { var extensions = VideoFileExtensions.ToList(); extensions.AddRange(new[] { ".mkv", ".m2t", ".m2ts", ".img", ".iso", ".mk3d", ".ts", ".rmvb", ".mov", ".avi", ".mpg", ".mpeg", ".wmv", ".mp4", ".divx", ".dvr-ms", ".wtv", ".ogm", ".ogv", ".asf", ".m4v", ".flv", ".f4v", ".3gp", ".webm", ".mts", ".m2v", ".rec" }); // Problematic. Can always become configurable if needed. extensions.Remove(".dat"); extensions.Remove(".wpl"); extensions.Remove(".m3u"); VideoFileExtensions = extensions .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); VideoExtraRules.AddRange(new List <ExtraRule> { new ExtraRule { ExtraType = "scene", RuleType = ExtraRuleType.Suffix, Token = "-scene", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "clip", RuleType = ExtraRuleType.Suffix, Token = "-clip", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "interview", RuleType = ExtraRuleType.Suffix, Token = "-interview", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "behindthescenes", RuleType = ExtraRuleType.Suffix, Token = "-behindthescenes", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "deletedscene", RuleType = ExtraRuleType.Suffix, Token = "-deleted", MediaType = MediaType.Video } }); Format3DRules.AddRange(new List <Format3DRule> { // Media Browser rules: new Format3DRule { Token = "fsbs" }, new Format3DRule { Token = "hsbs" }, new Format3DRule { Token = "sbs" }, new Format3DRule { Token = "ftab" }, new Format3DRule { Token = "htab" }, new Format3DRule { Token = "tab" }, new Format3DRule { Token = "sbs3d" }, new Format3DRule { Token = "mvc" } }); }
public NamingOptions() { VideoFileExtensions = new[] { ".m4v", ".3gp", ".nsv", ".ts", ".ty", ".strm", ".rm", ".rmvb", ".ifo", ".mov", ".qt", ".divx", ".xvid", ".bivx", ".vob", ".nrg", ".img", ".iso", ".pva", ".wmv", ".asf", ".asx", ".ogm", ".m2v", ".avi", ".bin", ".dvr-ms", ".mpg", ".mpeg", ".mp4", ".mkv", ".avc", ".vp3", ".svq3", ".nuv", ".viv", ".dv", ".fli", ".flv", ".001", ".tp" }; VideoFlagDelimiters = new[] { '(', ')', '-', '.', '_', '[', ']' }; StubFileExtensions = new[] { ".disc" }; StubTypes = new[] { new StubTypeRule { StubType = "dvd", Token = "dvd" }, new StubTypeRule { StubType = "hddvd", Token = "hddvd" }, new StubTypeRule { StubType = "bluray", Token = "bluray" }, new StubTypeRule { StubType = "bluray", Token = "brrip" }, new StubTypeRule { StubType = "bluray", Token = "bd25" }, new StubTypeRule { StubType = "bluray", Token = "bd50" }, new StubTypeRule { StubType = "vhs", Token = "vhs" }, new StubTypeRule { StubType = "tv", Token = "HDTV" }, new StubTypeRule { StubType = "tv", Token = "PDTV" }, new StubTypeRule { StubType = "tv", Token = "DSR" } }; VideoFileStackingExpressions = new[] { "(.*?)([ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(.*?)(\\.[^.]+)$", "(.*?)([ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(.*?)(\\.[^.]+)$", "(.*?)([ ._-]*[a-d])(.*?)(\\.[^.]+)$" }; CleanDateTimes = new[] { @"(.+[^ _\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9][0-9]|20[0-1][0-9])([ _\,\.\(\)\[\]\-][^0-9]|$)" }; CleanStrings = new[] { @"[ _\,\.\(\)\[\]\-](ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)", @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|\[.*\])([ _\,\.\(\)\[\]\-]|$)", @"(\[.*\])" }; SubtitleFileExtensions = new[] { ".srt", ".ssa", ".ass", ".sub" }; SubtitleFlagDelimiters = new[] { '.' }; SubtitleForcedFlags = new[] { "foreign", "forced" }; SubtitleDefaultFlags = new[] { "default" }; AlbumStackingPrefixes = new[] { "disc", "cd", "disk", "vol", "volume" }; AudioFileExtensions = new[] { ".nsv", ".m4a", ".flac", ".aac", ".strm", ".pls", ".rm", ".mpa", ".wav", ".wma", ".ogg", ".opus", ".mp3", ".mp2", ".mod", ".amf", ".669", ".dmf", ".dsm", ".far", ".gdm", ".imf", ".it", ".m15", ".med", ".okt", ".s3m", ".stm", ".sfx", ".ult", ".uni", ".xm", ".sid", ".ac3", ".dts", ".cue", ".aif", ".aiff", ".ape", ".mac", ".mpc", ".mp+", ".mpp", ".shn", ".wv", ".nsf", ".spc", ".gym", ".adplug", ".adx", ".dsp", ".adp", ".ymf", ".ast", ".afc", ".hps", ".xsp", ".acc", ".m4b", ".oga", ".dsf", ".mka" }; // Notice that these expressions need to match the whole path without the extension EpisodeExpressions = new[] { // *** Start Kodi Standard Naming // sname.s01.e01, sname.s01_e01, S01E02 sname, S01 - E02 new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![Ss]([0-9]+)[][ ._-]*[Ee]([0-9]+))[^\\\/])*)?[Ss](?<seasonnumber>[0-9]+)[][ ._-]*[Ee](?<epnumber>[0-9]+)([^\\/]*)$") { IsNamed = true }, // foo.ep01, foo.EP_01 new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"), // 0001-01-01, 0001 01 01 [is this useless because they do not match the paths?] new EpisodeExpression("([0-9]{4})[\\.-]([0-9]{2})[\\.-]([0-9]{2})", true) { DateTimeFormats = new[] { "yyyy.MM.dd", "yyyy-MM-dd", "yyyy_MM_dd" } }, // 01-01-0001 [is this useless because they do not match the paths?] new EpisodeExpression("([0-9]{2})[\\.-]([0-9]{2})[\\.-]([0-9]{4})", true) { DateTimeFormats = new[] { "dd.MM.yyyy", "dd-MM-yyyy", "dd_MM_yyyy" } }, // 01x01 foo new EpisodeExpression("[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$") { SupportsAbsoluteEpisodeNumbers = true }, // sname0001/0000101 [last two numbers are episodes, the rest is season] {also why is it matched weirdly after the series name?} new EpisodeExpression(@"[\\\\/\\._ -](?<seriesname>(?![0-9]+[0-9][0-9])([^\\\/])*)[\\\\/\\._ -](?<seasonnumber>[0-9]+)(?<epnumber>[0-9][0-9](?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([\\._ -][^\\\\/]*)$") { IsOptimistic = true, IsNamed = true, SupportsAbsoluteEpisodeNumbers = false }, // what is this? are we matching the letter a and r? and afterwards some ivx? new EpisodeExpression("[\\/._ -]p(?:ar)?t[_. -]()([ivx]+|[0-9]+)([._ -][^\\/]*)$") { SupportsAbsoluteEpisodeNumbers = true }, // *** End Kodi Standard Naming // S0001E001 (or less leading zeroes and lower case) new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})[^\\\/]*$") { IsNamed = true }, // S0001xE001 (or less leading zeroes and lower case) new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>\d{1,4})[x,X]?[eE](?<epnumber>\d{1,3})[^\\\/]*$") { IsNamed = true }, // sname S0001X001/S0001X001 new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))[^\\\/]*$") { IsNamed = true }, // foo/S0001.E001 (or x instead of .) new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})[^\\\/]*$") { IsNamed = true }, // "01" new EpisodeExpression(@".*[\\\/](?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\.\w+$") { IsOptimistic = true, IsNamed = true }, // "1-12 episode title" new EpisodeExpression(@"([0-9]+)-([0-9]+)"), // "01 - foo", "01-foo.avi" new EpisodeExpression(@".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\s?-\s?[^\\\/]*$") { IsOptimistic = true, IsNamed = true }, // "01.foo.avi" new EpisodeExpression(@".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\.[^\\\/]+$") { IsOptimistic = true, IsNamed = true }, // "foo - 01", "foo 2 - 01", "foo - 01 foo", "foo 2 - 01 foo", "foo - 01 - foo", "foo 2 - 01 - bar" new EpisodeExpression(@".*[\\\/][^\\\/]* - (?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$") { IsOptimistic = true, IsNamed = true }, // "01 episode title.avi" new EpisodeExpression(@"[Ss]eason[\._ ](?<seasonnumber>[0-9]+)[\\\/](?<epnumber>\d{1,3})([^\\\/]*)$") { IsOptimistic = true, IsNamed = true }, // "Episode 16", "Episode 16 - Title" new EpisodeExpression(@".*[\\\/][^\\\/]* (?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$") { IsOptimistic = true, IsNamed = true }, // "[*] Name 01 [*] new EpisodeExpression(@".*?\[.*?\].*?(?<seriesname>\S+?(\s.+?)*?)[-\s]+(?<epnumber>[0-9]+).*\[.*?\]$") { IsNamed = true } }; EpisodeWithoutSeasonExpressions = new[] { @"[/\._ \-]()([0-9]+)(-[0-9]+)?" }; EpisodeMultiPartExpressions = new[] { @"^[-_ex]+([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)" }; VideoExtraRules = new[] { new ExtraRule { ExtraType = "trailer", RuleType = ExtraRuleType.Filename, Token = "trailer", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "trailer", RuleType = ExtraRuleType.Suffix, Token = "-trailer", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "trailer", RuleType = ExtraRuleType.Suffix, Token = ".trailer", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "trailer", RuleType = ExtraRuleType.Suffix, Token = "_trailer", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "trailer", RuleType = ExtraRuleType.Suffix, Token = " trailer", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "sample", RuleType = ExtraRuleType.Filename, Token = "sample", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "sample", RuleType = ExtraRuleType.Suffix, Token = "-sample", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "sample", RuleType = ExtraRuleType.Suffix, Token = ".sample", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "sample", RuleType = ExtraRuleType.Suffix, Token = "_sample", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "sample", RuleType = ExtraRuleType.Suffix, Token = " sample", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "themesong", RuleType = ExtraRuleType.Filename, Token = "theme", MediaType = MediaType.Audio }, new ExtraRule { ExtraType = "scene", RuleType = ExtraRuleType.Suffix, Token = "-scene", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "clip", RuleType = ExtraRuleType.Suffix, Token = "-clip", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "interview", RuleType = ExtraRuleType.Suffix, Token = "-interview", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "behindthescenes", RuleType = ExtraRuleType.Suffix, Token = "-behindthescenes", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "deletedscene", RuleType = ExtraRuleType.Suffix, Token = "-deleted", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "featurette", RuleType = ExtraRuleType.Suffix, Token = "-featurette", MediaType = MediaType.Video }, new ExtraRule { ExtraType = "short", RuleType = ExtraRuleType.Suffix, Token = "-short", MediaType = MediaType.Video } }; Format3DRules = new[] { // Kodi rules: new Format3DRule { PreceedingToken = "3d", Token = "hsbs" }, new Format3DRule { PreceedingToken = "3d", Token = "sbs" }, new Format3DRule { PreceedingToken = "3d", Token = "htab" }, new Format3DRule { PreceedingToken = "3d", Token = "tab" }, // Media Browser rules: new Format3DRule { Token = "fsbs" }, new Format3DRule { Token = "hsbs" }, new Format3DRule { Token = "sbs" }, new Format3DRule { Token = "ftab" }, new Format3DRule { Token = "htab" }, new Format3DRule { Token = "tab" }, new Format3DRule { Token = "sbs3d" }, new Format3DRule { Token = "mvc" } }; AudioBookPartsExpressions = new[] { // Detect specified chapters, like CH 01 @"ch(?:apter)?[\s_-]?(?<chapter>\d+)", // Detect specified parts, like Part 02 @"p(?:ar)?t[\s_-]?(?<part>\d+)", // Chapter is often beginning of filename @"^(?<chapter>\d+)", // Part if often ending of filename @"(?<part>\d+)$", // Sometimes named as 0001_005 (chapter_part) @"(?<chapter>\d+)_(?<part>\d+)", // Some audiobooks are ripped from cd's, and will be named by disk number. @"dis(?:c|k)[\s_-]?(?<chapter>\d+)" }; var extensions = VideoFileExtensions.ToList(); extensions.AddRange(new[] { ".mkv", ".m2t", ".m2ts", ".img", ".iso", ".mk3d", ".ts", ".rmvb", ".mov", ".avi", ".mpg", ".mpeg", ".wmv", ".mp4", ".divx", ".dvr-ms", ".wtv", ".ogm", ".ogv", ".asf", ".m4v", ".flv", ".f4v", ".3gp", ".webm", ".mts", ".m2v", ".rec", ".mxf" }); MultipleEpisodeExpressions = new string[] { @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[eExX](?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})(-[xE]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$" }.Select(i => new EpisodeExpression(i) { IsNamed = true }).ToArray(); VideoFileExtensions = extensions .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); Compile(); }