#pragma warning disable CS1591 using System; using System.Linq; using System.Text.RegularExpressions; using Emby.Naming.Video; using MediaBrowser.Model.Entities; namespace Emby.Naming.Common { public class NamingOptions { 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\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{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|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 // new EpisodeExpression(@".*(\\|\/)(?((?![Ss]([0-9]+)[][ ._-]*[Ee]([0-9]+))[^\\\/])*)?[Ss](?[0-9]+)[][ ._-]*[Ee](?[0-9]+)([^\\/]*)$") { IsNamed = true }, // 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)(?[\w\s]+?)\s(?\d{1,3})(-(?\d{2,3}))*[^\\\/x]*$") { IsNamed = true }, new EpisodeExpression("[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$") { SupportsAbsoluteEpisodeNumbers = true }, new EpisodeExpression(@"[\\\\/\\._ -](?(?![0-9]+[0-9][0-9])([^\\\/])*)[\\\\/\\._ -](?[0-9]+)(?[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(@".*?(\[.*?\])+.*?(?[\w\s]+?)[-\s_]+(?\d+).*$") { IsNamed = true }, new EpisodeExpression(@".*(\\|\/)[sS]?(?\d+)[xX](?\d+)[^\\\/]*$") { IsNamed = true }, new EpisodeExpression(@".*(\\|\/)[sS](?\d+)[x,X]?[eE](?\d+)[^\\\/]*$") { IsNamed = true }, new EpisodeExpression(@".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d+))[^\\\/]*$") { IsNamed = true }, new EpisodeExpression(@".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d+)[^\\\/]*$") { IsNamed = true }, // "01.avi" new EpisodeExpression(@".*[\\\/](?\d+)(-(?\d+))*\.\w+$") { IsOptimistic = true, IsNamed = true }, // "1-12 episode title" new EpisodeExpression(@"([0-9]+)-([0-9]+)"), // "01 - blah.avi", "01-blah.avi" new EpisodeExpression(@".*(\\|\/)(?\d{1,3})(-(?\d{2,3}))*\s?-\s?[^\\\/]*$") { IsOptimistic = true, IsNamed = true }, // "01.blah.avi" new EpisodeExpression(@".*(\\|\/)(?\d{1,3})(-(?\d{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(@".*[\\\/][^\\\/]* - (?\d{1,3})(-(?\d{2,3}))*[^\\\/]*$") { IsOptimistic = true, IsNamed = true }, // "01 episode title.avi" new EpisodeExpression(@"[Ss]eason[\._ ](?[0-9]+)[\\\/](?\d{1,3})([^\\\/]*)$") { IsOptimistic = true, IsNamed = true }, // "Episode 16", "Episode 16 - Title" new EpisodeExpression(@".*[\\\/][^\\\/]* (?\d{1,3})(-(?\d{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_-]?(?\d+)", // Detect specified parts, like Part 02 @"p(?:ar)?t[\s_-]?(?\d+)", // Chapter is often beginning of filename @"^(?\d+)", // Part if often ending of filename @"(?\d+)$", // Sometimes named as 0001_005 (chapter_part) @"(?\d+)_(?\d+)", // Some audiobooks are ripped from cd's, and will be named by disk number. @"dis(?:c|k)[\s_-]?(?\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]?(?\d{1,4})[xX](?\d{1,3})((-| - )\d{1,4}[eExX](?\d{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})((-| - )\d{1,4}[xX][eE](?\d{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})((-| - )?[xXeE](?\d{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})(-[xE]?[eE]?(?\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))((-| - )\d{1,4}[xXeE](?\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))((-| - )\d{1,4}[xX][eE](?\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))((-| - )?[xXeE](?\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))(-[xX]?[eE]?(?\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d{1,3})((-| - )?[xXeE](?\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d{1,3})(-[xX]?[eE]?(?\d{1,3}))+[^\\\/]*$" }.Select(i => new EpisodeExpression(i) { IsNamed = true }).ToArray(); VideoFileExtensions = extensions .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); Compile(); } public string[] AudioFileExtensions { get; set; } public string[] AlbumStackingPrefixes { get; set; } public string[] SubtitleFileExtensions { get; set; } public char[] SubtitleFlagDelimiters { get; set; } public string[] SubtitleForcedFlags { get; set; } public string[] SubtitleDefaultFlags { get; set; } public EpisodeExpression[] EpisodeExpressions { get; set; } public string[] EpisodeWithoutSeasonExpressions { get; set; } public string[] EpisodeMultiPartExpressions { get; set; } public string[] VideoFileExtensions { get; set; } public string[] StubFileExtensions { get; set; } public string[] AudioBookPartsExpressions { get; set; } public StubTypeRule[] StubTypes { get; set; } public char[] VideoFlagDelimiters { get; set; } public Format3DRule[] Format3DRules { get; set; } public string[] VideoFileStackingExpressions { get; set; } public string[] CleanDateTimes { get; set; } public string[] CleanStrings { get; set; } public EpisodeExpression[] MultipleEpisodeExpressions { get; set; } public ExtraRule[] VideoExtraRules { get; set; } public Regex[] VideoFileStackingRegexes { get; private set; } public Regex[] CleanDateTimeRegexes { get; private set; } public Regex[] CleanStringRegexes { get; private set; } public Regex[] EpisodeWithoutSeasonRegexes { get; private set; } public Regex[] EpisodeMultiPartRegexes { get; private set; } public void Compile() { VideoFileStackingRegexes = VideoFileStackingExpressions.Select(Compile).ToArray(); CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray(); CleanStringRegexes = CleanStrings.Select(Compile).ToArray(); EpisodeWithoutSeasonRegexes = EpisodeWithoutSeasonExpressions.Select(Compile).ToArray(); EpisodeMultiPartRegexes = EpisodeMultiPartExpressions.Select(Compile).ToArray(); } private Regex Compile(string exp) { return new Regex(exp, RegexOptions.IgnoreCase | RegexOptions.Compiled); } } }