|
|
|
#pragma warning disable CA1819
|
|
|
|
|
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
using Emby.Naming.Video;
|
|
|
|
using MediaBrowser.Model.Entities;
|
|
|
|
|
|
|
|
// ReSharper disable StringLiteralTypo
|
|
|
|
|
|
|
|
namespace Emby.Naming.Common
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// Big ugly class containing lot of different naming options that should be split and injected instead of passes everywhere.
|
|
|
|
/// </summary>
|
|
|
|
public class NamingOptions
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="NamingOptions"/> class.
|
|
|
|
/// </summary>
|
|
|
|
public NamingOptions()
|
|
|
|
{
|
|
|
|
VideoFileExtensions = new[]
|
|
|
|
{
|
|
|
|
".001",
|
|
|
|
".3g2",
|
|
|
|
".3gp",
|
|
|
|
".amv",
|
|
|
|
".asf",
|
|
|
|
".asx",
|
|
|
|
".avi",
|
|
|
|
".bin",
|
|
|
|
".bivx",
|
|
|
|
".divx",
|
|
|
|
".dv",
|
|
|
|
".dvr-ms",
|
|
|
|
".f4v",
|
|
|
|
".fli",
|
|
|
|
".flv",
|
|
|
|
".ifo",
|
|
|
|
".img",
|
|
|
|
".iso",
|
|
|
|
".m2t",
|
|
|
|
".m2ts",
|
|
|
|
".m2v",
|
|
|
|
".m4v",
|
|
|
|
".mkv",
|
|
|
|
".mk3d",
|
|
|
|
".mov",
|
|
|
|
".mp4",
|
|
|
|
".mpe",
|
|
|
|
".mpeg",
|
|
|
|
".mpg",
|
|
|
|
".mts",
|
|
|
|
".mxf",
|
|
|
|
".nrg",
|
|
|
|
".nsv",
|
|
|
|
".nuv",
|
|
|
|
".ogg",
|
|
|
|
".ogm",
|
|
|
|
".ogv",
|
|
|
|
".pva",
|
|
|
|
".qt",
|
|
|
|
".rec",
|
|
|
|
".rm",
|
|
|
|
".rmvb",
|
|
|
|
".strm",
|
|
|
|
".svq3",
|
|
|
|
".tp",
|
|
|
|
".ts",
|
|
|
|
".ty",
|
|
|
|
".viv",
|
|
|
|
".vob",
|
|
|
|
".vp3",
|
|
|
|
".webm",
|
|
|
|
".wmv",
|
|
|
|
".wtv",
|
|
|
|
".xvid"
|
|
|
|
};
|
|
|
|
|
|
|
|
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[]
|
|
|
|
{
|
|
|
|
".ass",
|
|
|
|
".mks",
|
|
|
|
".sami",
|
|
|
|
".smi",
|
|
|
|
".srt",
|
|
|
|
".ssa",
|
|
|
|
".sub",
|
|
|
|
".vtt",
|
|
|
|
};
|
|
|
|
|
|
|
|
AlbumStackingPrefixes = new[]
|
|
|
|
{
|
|
|
|
"cd",
|
|
|
|
"disc",
|
|
|
|
"disk",
|
|
|
|
"vol",
|
|
|
|
"volume"
|
|
|
|
};
|
|
|
|
|
|
|
|
AudioFileExtensions = new[]
|
|
|
|
{
|
|
|
|
".669",
|
|
|
|
".3gp",
|
|
|
|
".aa",
|
|
|
|
".aac",
|
|
|
|
".aax",
|
|
|
|
".ac3",
|
|
|
|
".act",
|
|
|
|
".adp",
|
|
|
|
".adplug",
|
|
|
|
".adx",
|
|
|
|
".afc",
|
|
|
|
".amf",
|
|
|
|
".aif",
|
|
|
|
".aiff",
|
|
|
|
".alac",
|
|
|
|
".amr",
|
|
|
|
".ape",
|
|
|
|
".ast",
|
|
|
|
".au",
|
|
|
|
".awb",
|
|
|
|
".cda",
|
|
|
|
".cue",
|
|
|
|
".dmf",
|
|
|
|
".dsf",
|
|
|
|
".dsm",
|
|
|
|
".dsp",
|
|
|
|
".dts",
|
|
|
|
".dvf",
|
|
|
|
".far",
|
|
|
|
".flac",
|
|
|
|
".gdm",
|
|
|
|
".gsm",
|
|
|
|
".gym",
|
|
|
|
".hps",
|
|
|
|
".imf",
|
|
|
|
".it",
|
|
|
|
".m15",
|
|
|
|
".m4a",
|
|
|
|
".m4b",
|
|
|
|
".mac",
|
|
|
|
".med",
|
|
|
|
".mka",
|
|
|
|
".mmf",
|
|
|
|
".mod",
|
|
|
|
".mogg",
|
|
|
|
".mp2",
|
|
|
|
".mp3",
|
|
|
|
".mpa",
|
|
|
|
".mpc",
|
|
|
|
".mpp",
|
|
|
|
".mp+",
|
|
|
|
".msv",
|
|
|
|
".nmf",
|
|
|
|
".nsf",
|
|
|
|
".nsv",
|
|
|
|
".oga",
|
|
|
|
".ogg",
|
|
|
|
".okt",
|
|
|
|
".opus",
|
|
|
|
".pls",
|
|
|
|
".ra",
|
|
|
|
".rf64",
|
|
|
|
".rm",
|
|
|
|
".s3m",
|
|
|
|
".sfx",
|
|
|
|
".shn",
|
|
|
|
".sid",
|
|
|
|
".spc",
|
|
|
|
".stm",
|
|
|
|
".strm",
|
|
|
|
".ult",
|
|
|
|
".uni",
|
|
|
|
".vox",
|
|
|
|
".wav",
|
|
|
|
".wma",
|
|
|
|
".wv",
|
|
|
|
".xm",
|
|
|
|
".xsp",
|
|
|
|
".ymf"
|
|
|
|
};
|
|
|
|
|
|
|
|
MediaFlagDelimiters = new[]
|
|
|
|
{
|
|
|
|
'.'
|
|
|
|
};
|
|
|
|
|
|
|
|
MediaForcedFlags = new[]
|
|
|
|
{
|
|
|
|
"foreign",
|
|
|
|
"forced"
|
|
|
|
};
|
|
|
|
|
|
|
|
MediaDefaultFlags = new[]
|
|
|
|
{
|
|
|
|
"default"
|
|
|
|
};
|
|
|
|
|
|
|
|
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
|
|
|
|
},
|
|
|
|
|
adjust episode path expression to allow digits in series names
The previous expression was too greedy to consume digits, because the hyphen was optional. This lead to incorrect episode numbers for certain series with digits in their names, in those cases each episode was recognized as the same episode number (the digit from the series name).
The rule, which matches most standard anime filenames, also had a lower priority than one of the Kodi rules, leading to incorrect recognition for absolute numbered episodes with triple digits and higher (first digit was used as season number, rest of digits as episode number). This also resolves one of the TODO test cases.
Additional test cases were added to ensure that both hyphens in different parts of the filename, as well as names without hyphens, still work correctly. All previous test cases still pass.
Unfortunately another TODO (EpisodeNumberTests.cs@L76, Uchuu Senkan Yamato 2199) with the same issue (digits in the series name) could not be trivially fixed in the same change, due to the significantly different formatting. Attempts to resolve this case did not work out for now.
4 years ago
|
|
|
// 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]+).*$")
|
adjust episode path expression to allow digits in series names
The previous expression was too greedy to consume digits, because the hyphen was optional. This lead to incorrect episode numbers for certain series with digits in their names, in those cases each episode was recognized as the same episode number (the digit from the series name).
The rule, which matches most standard anime filenames, also had a lower priority than one of the Kodi rules, leading to incorrect recognition for absolute numbered episodes with triple digits and higher (first digit was used as season number, rest of digits as episode number). This also resolves one of the TODO test cases.
Additional test cases were added to ensure that both hyphens in different parts of the filename, as well as names without hyphens, still work correctly. All previous test cases still pass.
Unfortunately another TODO (EpisodeNumberTests.cs@L76, Uchuu Senkan Yamato 2199) with the same issue (digits in the series name) could not be trivially fixed in the same change, due to the significantly different formatting. Attempts to resolve this case did not work out for now.
4 years ago
|
|
|
{
|
|
|
|
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.ThemeVideo,
|
|
|
|
ExtraRuleType.DirectoryName,
|
|
|
|
"backdrops",
|
|
|
|
MediaType.Video),
|
|
|
|
|
|
|
|
new ExtraRule(
|
|
|
|
ExtraType.ThemeSong,
|
|
|
|
ExtraRuleType.DirectoryName,
|
|
|
|
"theme-music",
|
|
|
|
MediaType.Audio),
|
|
|
|
|
|
|
|
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),
|
|
|
|
|
|
|
|
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.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.Unknown,
|
|
|
|
ExtraRuleType.Suffix,
|
|
|
|
"-extra",
|
|
|
|
MediaType.Video)
|
|
|
|
};
|
|
|
|
|
|
|
|
AllExtrasTypesFolderNames = VideoExtraRules
|
|
|
|
.Where(i => i.RuleType == ExtraRuleType.DirectoryName)
|
|
|
|
.ToDictionary(i => i.Token, i => i.ExtraType, StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
|
|
|
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*$"
|
|
|
|
};
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
Compile();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the folder name to extra types mapping.
|
|
|
|
/// </summary>
|
|
|
|
public Dictionary<string, ExtraType> AllExtrasTypesFolderNames { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of audio file extensions.
|
|
|
|
/// </summary>
|
|
|
|
public string[] AudioFileExtensions { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of external media flag delimiters.
|
|
|
|
/// </summary>
|
|
|
|
public char[] MediaFlagDelimiters { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of external media forced flags.
|
|
|
|
/// </summary>
|
|
|
|
public string[] MediaForcedFlags { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of external media default flags.
|
|
|
|
/// </summary>
|
|
|
|
public string[] MediaDefaultFlags { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of album stacking prefixes.
|
|
|
|
/// </summary>
|
|
|
|
public string[] AlbumStackingPrefixes { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of subtitle file extensions.
|
|
|
|
/// </summary>
|
|
|
|
public string[] SubtitleFileExtensions { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of episode regular expressions.
|
|
|
|
/// </summary>
|
|
|
|
public EpisodeExpression[] EpisodeExpressions { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of raw episode without season regular expressions strings.
|
|
|
|
/// </summary>
|
|
|
|
public string[] EpisodeWithoutSeasonExpressions { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of raw multi-part episodes regular expressions strings.
|
|
|
|
/// </summary>
|
|
|
|
public string[] EpisodeMultiPartExpressions { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of video file extensions.
|
|
|
|
/// </summary>
|
|
|
|
public string[] VideoFileExtensions { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of video stub file extensions.
|
|
|
|
/// </summary>
|
|
|
|
public string[] StubFileExtensions { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of raw audiobook parts regular expressions strings.
|
|
|
|
/// </summary>
|
|
|
|
public string[] AudioBookPartsExpressions { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of raw audiobook names regular expressions strings.
|
|
|
|
/// </summary>
|
|
|
|
public string[] AudioBookNamesExpressions { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of stub type rules.
|
|
|
|
/// </summary>
|
|
|
|
public StubTypeRule[] StubTypes { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of video flag delimiters.
|
|
|
|
/// </summary>
|
|
|
|
public char[] VideoFlagDelimiters { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of 3D Format rules.
|
|
|
|
/// </summary>
|
|
|
|
public Format3DRule[] Format3DRules { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the file stacking rules.
|
|
|
|
/// </summary>
|
|
|
|
public FileStackRule[] VideoFileStackingRules { get; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of raw clean DateTimes regular expressions strings.
|
|
|
|
/// </summary>
|
|
|
|
public string[] CleanDateTimes { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of raw clean strings regular expressions strings.
|
|
|
|
/// </summary>
|
|
|
|
public string[] CleanStrings { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of multi-episode regular expressions.
|
|
|
|
/// </summary>
|
|
|
|
public EpisodeExpression[] MultipleEpisodeExpressions { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets list of extra rules for videos.
|
|
|
|
/// </summary>
|
|
|
|
public ExtraRule[] VideoExtraRules { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets list of clean datetime regular expressions.
|
|
|
|
/// </summary>
|
|
|
|
public Regex[] CleanDateTimeRegexes { get; private set; } = Array.Empty<Regex>();
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets list of clean string regular expressions.
|
|
|
|
/// </summary>
|
|
|
|
public Regex[] CleanStringRegexes { get; private set; } = Array.Empty<Regex>();
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets list of episode without season regular expressions.
|
|
|
|
/// </summary>
|
|
|
|
public Regex[] EpisodeWithoutSeasonRegexes { get; private set; } = Array.Empty<Regex>();
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets list of multi-part episode regular expressions.
|
|
|
|
/// </summary>
|
|
|
|
public Regex[] EpisodeMultiPartRegexes { get; private set; } = Array.Empty<Regex>();
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Compiles raw regex strings into regexes.
|
|
|
|
/// </summary>
|
|
|
|
public void Compile()
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|