mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-07-05 21:33:02 +02:00
commit
aee3360841
|
@ -33,27 +33,29 @@ namespace Emby.Naming.Audio
|
||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
// Remove whitespace
|
// Remove whitespace
|
||||||
filename = filename.Replace("-", " ");
|
filename = filename.Replace('-', ' ');
|
||||||
filename = filename.Replace(".", " ");
|
filename = filename.Replace('.', ' ');
|
||||||
filename = filename.Replace("(", " ");
|
filename = filename.Replace('(', ' ');
|
||||||
filename = filename.Replace(")", " ");
|
filename = filename.Replace(')', ' ');
|
||||||
filename = Regex.Replace(filename, @"\s+", " ");
|
filename = Regex.Replace(filename, @"\s+", " ");
|
||||||
|
|
||||||
filename = filename.TrimStart();
|
filename = filename.TrimStart();
|
||||||
|
|
||||||
foreach (var prefix in _options.AlbumStackingPrefixes)
|
foreach (var prefix in _options.AlbumStackingPrefixes)
|
||||||
{
|
{
|
||||||
if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) == 0)
|
if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) != 0)
|
||||||
{
|
{
|
||||||
var tmp = filename.Substring(prefix.Length);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty;
|
var tmp = filename.Substring(prefix.Length);
|
||||||
|
|
||||||
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty;
|
||||||
{
|
|
||||||
result.IsMultiPart = true;
|
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
|
||||||
break;
|
{
|
||||||
}
|
result.IsMultiPart = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,13 @@ namespace Emby.Naming.Audio
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The name.</value>
|
/// <value>The name.</value>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the part.
|
/// Gets or sets the part.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The part.</value>
|
/// <value>The part.</value>
|
||||||
public string Part { get; set; }
|
public string Part { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this instance is multi part.
|
/// Gets or sets a value indicating whether this instance is multi part.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -12,35 +12,56 @@ namespace Emby.Naming.AudioBook
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The path.</value>
|
/// <value>The path.</value>
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the container.
|
/// Gets or sets the container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The container.</value>
|
/// <value>The container.</value>
|
||||||
public string Container { get; set; }
|
public string Container { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the part number.
|
/// Gets or sets the part number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The part number.</value>
|
/// <value>The part number.</value>
|
||||||
public int? PartNumber { get; set; }
|
public int? PartNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the chapter number.
|
/// Gets or sets the chapter number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The chapter number.</value>
|
/// <value>The chapter number.</value>
|
||||||
public int? ChapterNumber { get; set; }
|
public int? ChapterNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type.
|
/// Gets or sets the type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The type.</value>
|
/// <value>The type.</value>
|
||||||
public bool IsDirectory { get; set; }
|
public bool IsDirectory { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public int CompareTo(AudioBookFileInfo other)
|
public int CompareTo(AudioBookFileInfo other)
|
||||||
{
|
{
|
||||||
if (ReferenceEquals(this, other)) return 0;
|
if (ReferenceEquals(this, other))
|
||||||
if (ReferenceEquals(null, other)) return 1;
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(null, other))
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
var chapterNumberComparison = Nullable.Compare(ChapterNumber, other.ChapterNumber);
|
var chapterNumberComparison = Nullable.Compare(ChapterNumber, other.ChapterNumber);
|
||||||
if (chapterNumberComparison != 0) return chapterNumberComparison;
|
if (chapterNumberComparison != 0)
|
||||||
|
{
|
||||||
|
return chapterNumberComparison;
|
||||||
|
}
|
||||||
|
|
||||||
var partNumberComparison = Nullable.Compare(PartNumber, other.PartNumber);
|
var partNumberComparison = Nullable.Compare(PartNumber, other.PartNumber);
|
||||||
if (partNumberComparison != 0) return partNumberComparison;
|
if (partNumberComparison != 0)
|
||||||
|
{
|
||||||
|
return partNumberComparison;
|
||||||
|
}
|
||||||
|
|
||||||
return string.Compare(Path, other.Path, StringComparison.Ordinal);
|
return string.Compare(Path, other.Path, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
@ -14,14 +15,13 @@ namespace Emby.Naming.AudioBook
|
||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AudioBookFilePathParserResult Parse(string path, bool IsDirectory)
|
public AudioBookFilePathParserResult Parse(string path)
|
||||||
{
|
{
|
||||||
var result = Parse(path);
|
if (path == null)
|
||||||
return !result.Success ? new AudioBookFilePathParserResult() : result;
|
{
|
||||||
}
|
throw new ArgumentNullException(nameof(path));
|
||||||
|
}
|
||||||
|
|
||||||
private AudioBookFilePathParserResult Parse(string path)
|
|
||||||
{
|
|
||||||
var result = new AudioBookFilePathParserResult();
|
var result = new AudioBookFilePathParserResult();
|
||||||
var fileName = Path.GetFileNameWithoutExtension(path);
|
var fileName = Path.GetFileNameWithoutExtension(path);
|
||||||
foreach (var expression in _options.AudioBookPartsExpressions)
|
foreach (var expression in _options.AudioBookPartsExpressions)
|
||||||
|
@ -40,6 +40,7 @@ namespace Emby.Naming.AudioBook
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.PartNumber.HasValue)
|
if (!result.PartNumber.HasValue)
|
||||||
{
|
{
|
||||||
var value = match.Groups["part"];
|
var value = match.Groups["part"];
|
||||||
|
|
|
@ -3,7 +3,9 @@ namespace Emby.Naming.AudioBook
|
||||||
public class AudioBookFilePathParserResult
|
public class AudioBookFilePathParserResult
|
||||||
{
|
{
|
||||||
public int? PartNumber { get; set; }
|
public int? PartNumber { get; set; }
|
||||||
|
|
||||||
public int? ChapterNumber { get; set; }
|
public int? ChapterNumber { get; set; }
|
||||||
|
|
||||||
public bool Success { get; set; }
|
public bool Success { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,33 +7,40 @@ namespace Emby.Naming.AudioBook
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AudioBookInfo
|
public class AudioBookInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
public string Name { get; set; }
|
|
||||||
public int? Year { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the files.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The files.</value>
|
|
||||||
public List<AudioBookFileInfo> Files { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the extras.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The extras.</value>
|
|
||||||
public List<AudioBookFileInfo> Extras { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the alternate versions.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The alternate versions.</value>
|
|
||||||
public List<AudioBookFileInfo> AlternateVersions { get; set; }
|
|
||||||
|
|
||||||
public AudioBookInfo()
|
public AudioBookInfo()
|
||||||
{
|
{
|
||||||
Files = new List<AudioBookFileInfo>();
|
Files = new List<AudioBookFileInfo>();
|
||||||
Extras = new List<AudioBookFileInfo>();
|
Extras = new List<AudioBookFileInfo>();
|
||||||
AlternateVersions = new List<AudioBookFileInfo>();
|
AlternateVersions = new List<AudioBookFileInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name.</value>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the year.
|
||||||
|
/// </summary>
|
||||||
|
public int? Year { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the files.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The files.</value>
|
||||||
|
public List<AudioBookFileInfo> Files { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the extras.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The extras.</value>
|
||||||
|
public List<AudioBookFileInfo> Extras { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the alternate versions.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The alternate versions.</value>
|
||||||
|
public List<AudioBookFileInfo> AlternateVersions { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace Emby.Naming.AudioBook
|
||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<AudioBookInfo> Resolve(List<FileSystemMetadata> files)
|
public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files)
|
||||||
{
|
{
|
||||||
var audioBookResolver = new AudioBookResolver(_options);
|
var audioBookResolver = new AudioBookResolver(_options);
|
||||||
|
|
||||||
|
|
|
@ -24,19 +24,21 @@ namespace Emby.Naming.AudioBook
|
||||||
return Resolve(path, true);
|
return Resolve(path, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AudioBookFileInfo Resolve(string path, bool IsDirectory = false)
|
public AudioBookFileInfo Resolve(string path, bool isDirectory = false)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(path));
|
throw new ArgumentNullException(nameof(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsDirectory) // TODO
|
// TODO
|
||||||
|
if (isDirectory)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
|
|
||||||
// Check supported extensions
|
// Check supported extensions
|
||||||
if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
@ -45,8 +47,7 @@ namespace Emby.Naming.AudioBook
|
||||||
|
|
||||||
var container = extension.TrimStart('.');
|
var container = extension.TrimStart('.');
|
||||||
|
|
||||||
var parsingResult = new AudioBookFilePathParser(_options)
|
var parsingResult = new AudioBookFilePathParser(_options).Parse(path);
|
||||||
.Parse(path, IsDirectory);
|
|
||||||
|
|
||||||
return new AudioBookFileInfo
|
return new AudioBookFileInfo
|
||||||
{
|
{
|
||||||
|
@ -54,7 +55,7 @@ namespace Emby.Naming.AudioBook
|
||||||
Container = container,
|
Container = container,
|
||||||
PartNumber = parsingResult.PartNumber,
|
PartNumber = parsingResult.PartNumber,
|
||||||
ChapterNumber = parsingResult.ChapterNumber,
|
ChapterNumber = parsingResult.ChapterNumber,
|
||||||
IsDirectory = IsDirectory
|
IsDirectory = isDirectory
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,17 +6,28 @@ namespace Emby.Naming.Common
|
||||||
public class EpisodeExpression
|
public class EpisodeExpression
|
||||||
{
|
{
|
||||||
private string _expression;
|
private string _expression;
|
||||||
public string Expression { get => _expression;
|
private Regex _regex;
|
||||||
set { _expression = value; _regex = null; } }
|
|
||||||
|
public string Expression
|
||||||
|
{
|
||||||
|
get => _expression;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_expression = value;
|
||||||
|
_regex = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsByDate { get; set; }
|
public bool IsByDate { get; set; }
|
||||||
|
|
||||||
public bool IsOptimistic { get; set; }
|
public bool IsOptimistic { get; set; }
|
||||||
|
|
||||||
public bool IsNamed { get; set; }
|
public bool IsNamed { get; set; }
|
||||||
|
|
||||||
public bool SupportsAbsoluteEpisodeNumbers { get; set; }
|
public bool SupportsAbsoluteEpisodeNumbers { get; set; }
|
||||||
|
|
||||||
public string[] DateTimeFormats { get; set; }
|
public string[] DateTimeFormats { get; set; }
|
||||||
|
|
||||||
private Regex _regex;
|
|
||||||
public Regex Regex => _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled));
|
public Regex Regex => _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled));
|
||||||
|
|
||||||
public EpisodeExpression(string expression, bool byDate)
|
public EpisodeExpression(string expression, bool byDate)
|
||||||
|
|
|
@ -6,10 +6,12 @@ namespace Emby.Naming.Common
|
||||||
/// The audio
|
/// The audio
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Audio = 0,
|
Audio = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The photo
|
/// The photo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Photo = 1,
|
Photo = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The video
|
/// The video
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -8,19 +8,25 @@ namespace Emby.Naming.Common
|
||||||
public class NamingOptions
|
public class NamingOptions
|
||||||
{
|
{
|
||||||
public string[] AudioFileExtensions { get; set; }
|
public string[] AudioFileExtensions { get; set; }
|
||||||
|
|
||||||
public string[] AlbumStackingPrefixes { get; set; }
|
public string[] AlbumStackingPrefixes { get; set; }
|
||||||
|
|
||||||
public string[] SubtitleFileExtensions { get; set; }
|
public string[] SubtitleFileExtensions { get; set; }
|
||||||
|
|
||||||
public char[] SubtitleFlagDelimiters { get; set; }
|
public char[] SubtitleFlagDelimiters { get; set; }
|
||||||
|
|
||||||
public string[] SubtitleForcedFlags { get; set; }
|
public string[] SubtitleForcedFlags { get; set; }
|
||||||
|
|
||||||
public string[] SubtitleDefaultFlags { get; set; }
|
public string[] SubtitleDefaultFlags { get; set; }
|
||||||
|
|
||||||
public EpisodeExpression[] EpisodeExpressions { get; set; }
|
public EpisodeExpression[] EpisodeExpressions { get; set; }
|
||||||
|
|
||||||
public string[] EpisodeWithoutSeasonExpressions { get; set; }
|
public string[] EpisodeWithoutSeasonExpressions { get; set; }
|
||||||
|
|
||||||
public string[] EpisodeMultiPartExpressions { get; set; }
|
public string[] EpisodeMultiPartExpressions { get; set; }
|
||||||
|
|
||||||
public string[] VideoFileExtensions { get; set; }
|
public string[] VideoFileExtensions { get; set; }
|
||||||
|
|
||||||
public string[] StubFileExtensions { get; set; }
|
public string[] StubFileExtensions { get; set; }
|
||||||
|
|
||||||
public string[] AudioBookPartsExpressions { get; set; }
|
public string[] AudioBookPartsExpressions { get; set; }
|
||||||
|
@ -28,12 +34,14 @@ namespace Emby.Naming.Common
|
||||||
public StubTypeRule[] StubTypes { get; set; }
|
public StubTypeRule[] StubTypes { get; set; }
|
||||||
|
|
||||||
public char[] VideoFlagDelimiters { get; set; }
|
public char[] VideoFlagDelimiters { get; set; }
|
||||||
|
|
||||||
public Format3DRule[] Format3DRules { get; set; }
|
public Format3DRule[] Format3DRules { get; set; }
|
||||||
|
|
||||||
public string[] VideoFileStackingExpressions { get; set; }
|
public string[] VideoFileStackingExpressions { get; set; }
|
||||||
public string[] CleanDateTimes { get; set; }
|
|
||||||
public string[] CleanStrings { get; set; }
|
|
||||||
|
|
||||||
|
public string[] CleanDateTimes { get; set; }
|
||||||
|
|
||||||
|
public string[] CleanStrings { get; set; }
|
||||||
|
|
||||||
public EpisodeExpression[] MultipleEpisodeExpressions { get; set; }
|
public EpisodeExpression[] MultipleEpisodeExpressions { get; set; }
|
||||||
|
|
||||||
|
@ -41,7 +49,7 @@ namespace Emby.Naming.Common
|
||||||
|
|
||||||
public NamingOptions()
|
public NamingOptions()
|
||||||
{
|
{
|
||||||
VideoFileExtensions = new string[]
|
VideoFileExtensions = new[]
|
||||||
{
|
{
|
||||||
".m4v",
|
".m4v",
|
||||||
".3gp",
|
".3gp",
|
||||||
|
@ -106,53 +114,53 @@ namespace Emby.Naming.Common
|
||||||
{
|
{
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "dvd",
|
StubType = "dvd",
|
||||||
Token = "dvd"
|
Token = "dvd"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "hddvd",
|
StubType = "hddvd",
|
||||||
Token = "hddvd"
|
Token = "hddvd"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "bluray",
|
StubType = "bluray",
|
||||||
Token = "bluray"
|
Token = "bluray"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "bluray",
|
StubType = "bluray",
|
||||||
Token = "brrip"
|
Token = "brrip"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "bluray",
|
StubType = "bluray",
|
||||||
Token = "bd25"
|
Token = "bd25"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "bluray",
|
StubType = "bluray",
|
||||||
Token = "bd50"
|
Token = "bd50"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "vhs",
|
StubType = "vhs",
|
||||||
Token = "vhs"
|
Token = "vhs"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "tv",
|
StubType = "tv",
|
||||||
Token = "HDTV"
|
Token = "HDTV"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "tv",
|
StubType = "tv",
|
||||||
Token = "PDTV"
|
Token = "PDTV"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "tv",
|
StubType = "tv",
|
||||||
Token = "DSR"
|
Token = "DSR"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -286,7 +294,7 @@ namespace Emby.Naming.Common
|
||||||
new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"),
|
new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"),
|
||||||
new EpisodeExpression("([0-9]{4})[\\.-]([0-9]{2})[\\.-]([0-9]{2})", true)
|
new EpisodeExpression("([0-9]{4})[\\.-]([0-9]{2})[\\.-]([0-9]{2})", true)
|
||||||
{
|
{
|
||||||
DateTimeFormats = new []
|
DateTimeFormats = new[]
|
||||||
{
|
{
|
||||||
"yyyy.MM.dd",
|
"yyyy.MM.dd",
|
||||||
"yyyy-MM-dd",
|
"yyyy-MM-dd",
|
||||||
|
@ -295,7 +303,7 @@ namespace Emby.Naming.Common
|
||||||
},
|
},
|
||||||
new EpisodeExpression("([0-9]{2})[\\.-]([0-9]{2})[\\.-]([0-9]{4})", true)
|
new EpisodeExpression("([0-9]{2})[\\.-]([0-9]{2})[\\.-]([0-9]{4})", true)
|
||||||
{
|
{
|
||||||
DateTimeFormats = new []
|
DateTimeFormats = new[]
|
||||||
{
|
{
|
||||||
"dd.MM.yyyy",
|
"dd.MM.yyyy",
|
||||||
"dd-MM-yyyy",
|
"dd-MM-yyyy",
|
||||||
|
@ -348,9 +356,7 @@ namespace Emby.Naming.Common
|
||||||
},
|
},
|
||||||
|
|
||||||
// "1-12 episode title"
|
// "1-12 episode title"
|
||||||
new EpisodeExpression(@"([0-9]+)-([0-9]+)")
|
new EpisodeExpression(@"([0-9]+)-([0-9]+)"),
|
||||||
{
|
|
||||||
},
|
|
||||||
|
|
||||||
// "01 - blah.avi", "01-blah.avi"
|
// "01 - blah.avi", "01-blah.avi"
|
||||||
new EpisodeExpression(@".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\s?-\s?[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\s?-\s?[^\\\/]*$")
|
||||||
|
@ -427,7 +433,7 @@ namespace Emby.Naming.Common
|
||||||
Token = "_trailer",
|
Token = "_trailer",
|
||||||
MediaType = MediaType.Video
|
MediaType = MediaType.Video
|
||||||
},
|
},
|
||||||
new ExtraRule
|
new ExtraRule
|
||||||
{
|
{
|
||||||
ExtraType = "trailer",
|
ExtraType = "trailer",
|
||||||
RuleType = ExtraRuleType.Suffix,
|
RuleType = ExtraRuleType.Suffix,
|
||||||
|
@ -462,7 +468,7 @@ namespace Emby.Naming.Common
|
||||||
Token = "_sample",
|
Token = "_sample",
|
||||||
MediaType = MediaType.Video
|
MediaType = MediaType.Video
|
||||||
},
|
},
|
||||||
new ExtraRule
|
new ExtraRule
|
||||||
{
|
{
|
||||||
ExtraType = "sample",
|
ExtraType = "sample",
|
||||||
RuleType = ExtraRuleType.Suffix,
|
RuleType = ExtraRuleType.Suffix,
|
||||||
|
@ -476,7 +482,6 @@ namespace Emby.Naming.Common
|
||||||
Token = "theme",
|
Token = "theme",
|
||||||
MediaType = MediaType.Audio
|
MediaType = MediaType.Audio
|
||||||
},
|
},
|
||||||
|
|
||||||
new ExtraRule
|
new ExtraRule
|
||||||
{
|
{
|
||||||
ExtraType = "scene",
|
ExtraType = "scene",
|
||||||
|
@ -526,8 +531,8 @@ namespace Emby.Naming.Common
|
||||||
Token = "-short",
|
Token = "-short",
|
||||||
MediaType = MediaType.Video
|
MediaType = MediaType.Video
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Format3DRules = new[]
|
Format3DRules = new[]
|
||||||
{
|
{
|
||||||
// Kodi rules:
|
// Kodi rules:
|
||||||
|
@ -648,12 +653,10 @@ namespace Emby.Naming.Common
|
||||||
@".*(\\|\/)(?<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]?\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})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<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)
|
}.Select(i => new EpisodeExpression(i)
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
|
}).ToArray();
|
||||||
}).ToArray();
|
|
||||||
|
|
||||||
VideoFileExtensions = extensions
|
VideoFileExtensions = extensions
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
@ -18,6 +18,18 @@
|
||||||
<PackageId>Jellyfin.Naming</PackageId>
|
<PackageId>Jellyfin.Naming</PackageId>
|
||||||
<PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
|
<PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- Code analysers-->
|
||||||
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.2" />
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||||
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace Emby.Naming.Extensions
|
||||||
{
|
{
|
||||||
public static class StringExtensions
|
public static class StringExtensions
|
||||||
{
|
{
|
||||||
|
// TODO: @bond remove this when moving to netstandard2.1
|
||||||
public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
|
public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Emby.Naming
|
|
||||||
{
|
|
||||||
internal static class StringExtensions
|
|
||||||
{
|
|
||||||
public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
|
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
|
|
||||||
var previousIndex = 0;
|
|
||||||
var index = str.IndexOf(oldValue, comparison);
|
|
||||||
|
|
||||||
while (index != -1)
|
|
||||||
{
|
|
||||||
sb.Append(str.Substring(previousIndex, index - previousIndex));
|
|
||||||
sb.Append(newValue);
|
|
||||||
index += oldValue.Length;
|
|
||||||
|
|
||||||
previousIndex = index;
|
|
||||||
index = str.IndexOf(oldValue, index, comparison);
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Append(str.Substring(previousIndex));
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,16 +7,19 @@ namespace Emby.Naming.Subtitles
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The path.</value>
|
/// <value>The path.</value>
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the language.
|
/// Gets or sets the language.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The language.</value>
|
/// <value>The language.</value>
|
||||||
public string Language { get; set; }
|
public string Language { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this instance is default.
|
/// Gets or sets a value indicating whether this instance is default.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if this instance is default; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if this instance is default; otherwise, <c>false</c>.</value>
|
||||||
public bool IsDefault { get; set; }
|
public bool IsDefault { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this instance is forced.
|
/// Gets or sets a value indicating whether this instance is forced.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -7,31 +7,37 @@ namespace Emby.Naming.TV
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The path.</value>
|
/// <value>The path.</value>
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the container.
|
/// Gets or sets the container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The container.</value>
|
/// <value>The container.</value>
|
||||||
public string Container { get; set; }
|
public string Container { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name of the series.
|
/// Gets or sets the name of the series.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The name of the series.</value>
|
/// <value>The name of the series.</value>
|
||||||
public string SeriesName { get; set; }
|
public string SeriesName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the format3 d.
|
/// Gets or sets the format3 d.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The format3 d.</value>
|
/// <value>The format3 d.</value>
|
||||||
public string Format3D { get; set; }
|
public string Format3D { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether [is3 d].
|
/// Gets or sets a value indicating whether [is3 d].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value>
|
||||||
public bool Is3D { get; set; }
|
public bool Is3D { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this instance is stub.
|
/// Gets or sets a value indicating whether this instance is stub.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value>
|
||||||
public bool IsStub { get; set; }
|
public bool IsStub { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of the stub.
|
/// Gets or sets the type of the stub.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -39,12 +45,17 @@ namespace Emby.Naming.TV
|
||||||
public string StubType { get; set; }
|
public string StubType { get; set; }
|
||||||
|
|
||||||
public int? SeasonNumber { get; set; }
|
public int? SeasonNumber { get; set; }
|
||||||
|
|
||||||
public int? EpisodeNumber { get; set; }
|
public int? EpisodeNumber { get; set; }
|
||||||
|
|
||||||
public int? EndingEpsiodeNumber { get; set; }
|
public int? EndingEpsiodeNumber { get; set; }
|
||||||
|
|
||||||
public int? Year { get; set; }
|
public int? Year { get; set; }
|
||||||
|
|
||||||
public int? Month { get; set; }
|
public int? Month { get; set; }
|
||||||
|
|
||||||
public int? Day { get; set; }
|
public int? Day { get; set; }
|
||||||
|
|
||||||
public bool IsByDate { get; set; }
|
public bool IsByDate { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,12 @@ namespace Emby.Naming.TV
|
||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EpisodePathParserResult Parse(string path, bool IsDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true)
|
public EpisodePathParserResult Parse(string path, bool isDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true)
|
||||||
{
|
{
|
||||||
// Added to be able to use regex patterns which require a file extension.
|
// Added to be able to use regex patterns which require a file extension.
|
||||||
// There were no failed tests without this block, but to be safe, we can keep it until
|
// There were no failed tests without this block, but to be safe, we can keep it until
|
||||||
// the regex which require file extensions are modified so that they don't need them.
|
// the regex which require file extensions are modified so that they don't need them.
|
||||||
if (IsDirectory)
|
if (isDirectory)
|
||||||
{
|
{
|
||||||
path += ".mp4";
|
path += ".mp4";
|
||||||
}
|
}
|
||||||
|
@ -29,28 +29,20 @@ namespace Emby.Naming.TV
|
||||||
|
|
||||||
foreach (var expression in _options.EpisodeExpressions)
|
foreach (var expression in _options.EpisodeExpressions)
|
||||||
{
|
{
|
||||||
if (supportsAbsoluteNumbers.HasValue)
|
if (supportsAbsoluteNumbers.HasValue
|
||||||
|
&& expression.SupportsAbsoluteEpisodeNumbers != supportsAbsoluteNumbers.Value)
|
||||||
{
|
{
|
||||||
if (expression.SupportsAbsoluteEpisodeNumbers != supportsAbsoluteNumbers.Value)
|
continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNamed.HasValue)
|
if (isNamed.HasValue && expression.IsNamed != isNamed.Value)
|
||||||
{
|
{
|
||||||
if (expression.IsNamed != isNamed.Value)
|
continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOptimistic.HasValue)
|
if (isOptimistic.HasValue && expression.IsOptimistic != isOptimistic.Value)
|
||||||
{
|
{
|
||||||
if (expression.IsOptimistic != isOptimistic.Value)
|
continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentResult = Parse(path, expression);
|
var currentResult = Parse(path, expression);
|
||||||
|
@ -97,7 +89,8 @@ namespace Emby.Naming.TV
|
||||||
DateTime date;
|
DateTime date;
|
||||||
if (expression.DateTimeFormats.Length > 0)
|
if (expression.DateTimeFormats.Length > 0)
|
||||||
{
|
{
|
||||||
if (DateTime.TryParseExact(match.Groups[0].Value,
|
if (DateTime.TryParseExact(
|
||||||
|
match.Groups[0].Value,
|
||||||
expression.DateTimeFormats,
|
expression.DateTimeFormats,
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
DateTimeStyles.None,
|
DateTimeStyles.None,
|
||||||
|
@ -109,15 +102,12 @@ namespace Emby.Naming.TV
|
||||||
result.Success = true;
|
result.Success = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (DateTime.TryParse(match.Groups[0].Value, out date))
|
||||||
{
|
{
|
||||||
if (DateTime.TryParse(match.Groups[0].Value, out date))
|
result.Year = date.Year;
|
||||||
{
|
result.Month = date.Month;
|
||||||
result.Year = date.Year;
|
result.Day = date.Day;
|
||||||
result.Month = date.Month;
|
result.Success = true;
|
||||||
result.Day = date.Day;
|
|
||||||
result.Success = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Only consider success if date successfully parsed?
|
// TODO: Only consider success if date successfully parsed?
|
||||||
|
@ -142,7 +132,8 @@ namespace Emby.Naming.TV
|
||||||
// or a 'p' or 'i' as what you would get with a pixel resolution specification.
|
// or a 'p' or 'i' as what you would get with a pixel resolution specification.
|
||||||
// It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108
|
// It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108
|
||||||
int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length;
|
int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length;
|
||||||
if (nextIndex >= name.Length || "0123456789iIpP".IndexOf(name[nextIndex]) == -1)
|
if (nextIndex >= name.Length
|
||||||
|
|| "0123456789iIpP".IndexOf(name[nextIndex]) == -1)
|
||||||
{
|
{
|
||||||
if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
|
if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
|
||||||
{
|
{
|
||||||
|
@ -160,6 +151,7 @@ namespace Emby.Naming.TV
|
||||||
{
|
{
|
||||||
result.SeasonNumber = num;
|
result.SeasonNumber = num;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
|
if (int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
|
||||||
{
|
{
|
||||||
result.EpisodeNumber = num;
|
result.EpisodeNumber = num;
|
||||||
|
@ -171,8 +163,11 @@ namespace Emby.Naming.TV
|
||||||
// Invalidate match when the season is 200 through 1927 or above 2500
|
// Invalidate match when the season is 200 through 1927 or above 2500
|
||||||
// because it is an error unless the TV show is intentionally using false season numbers.
|
// because it is an error unless the TV show is intentionally using false season numbers.
|
||||||
// It avoids erroneous parsing of something like "Series Special (1920x1080).mkv" as being season 1920 episode 1080.
|
// It avoids erroneous parsing of something like "Series Special (1920x1080).mkv" as being season 1920 episode 1080.
|
||||||
if (result.SeasonNumber >= 200 && result.SeasonNumber < 1928 || result.SeasonNumber > 2500)
|
if ((result.SeasonNumber >= 200 && result.SeasonNumber < 1928)
|
||||||
|
|| result.SeasonNumber > 2500)
|
||||||
|
{
|
||||||
result.Success = false;
|
result.Success = false;
|
||||||
|
}
|
||||||
|
|
||||||
result.IsByDate = expression.IsByDate;
|
result.IsByDate = expression.IsByDate;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,21 @@ namespace Emby.Naming.TV
|
||||||
public class EpisodePathParserResult
|
public class EpisodePathParserResult
|
||||||
{
|
{
|
||||||
public int? SeasonNumber { get; set; }
|
public int? SeasonNumber { get; set; }
|
||||||
|
|
||||||
public int? EpisodeNumber { get; set; }
|
public int? EpisodeNumber { get; set; }
|
||||||
|
|
||||||
public int? EndingEpsiodeNumber { get; set; }
|
public int? EndingEpsiodeNumber { get; set; }
|
||||||
|
|
||||||
public string SeriesName { get; set; }
|
public string SeriesName { get; set; }
|
||||||
|
|
||||||
public bool Success { get; set; }
|
public bool Success { get; set; }
|
||||||
|
|
||||||
public bool IsByDate { get; set; }
|
public bool IsByDate { get; set; }
|
||||||
|
|
||||||
public int? Year { get; set; }
|
public int? Year { get; set; }
|
||||||
|
|
||||||
public int? Month { get; set; }
|
public int? Month { get; set; }
|
||||||
|
|
||||||
public int? Day { get; set; }
|
public int? Day { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,13 @@ namespace Emby.Naming.TV
|
||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EpisodeInfo Resolve(string path, bool IsDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true)
|
public EpisodeInfo Resolve(
|
||||||
|
string path,
|
||||||
|
bool isDirectory,
|
||||||
|
bool? isNamed = null,
|
||||||
|
bool? isOptimistic = null,
|
||||||
|
bool? supportsAbsoluteNumbers = null,
|
||||||
|
bool fillExtendedInfo = true)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
|
@ -26,7 +32,7 @@ namespace Emby.Naming.TV
|
||||||
string container = null;
|
string container = null;
|
||||||
string stubType = null;
|
string stubType = null;
|
||||||
|
|
||||||
if (!IsDirectory)
|
if (!isDirectory)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
// Check supported extensions
|
// Check supported extensions
|
||||||
|
@ -52,7 +58,7 @@ namespace Emby.Naming.TV
|
||||||
var format3DResult = new Format3DParser(_options).Parse(flags);
|
var format3DResult = new Format3DParser(_options).Parse(flags);
|
||||||
|
|
||||||
var parsingResult = new EpisodePathParser(_options)
|
var parsingResult = new EpisodePathParser(_options)
|
||||||
.Parse(path, IsDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo);
|
.Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo);
|
||||||
|
|
||||||
return new EpisodeInfo
|
return new EpisodeInfo
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,30 +3,24 @@ using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using Emby.Naming.Extensions;
|
||||||
|
|
||||||
namespace Emby.Naming.TV
|
namespace Emby.Naming.TV
|
||||||
{
|
{
|
||||||
public class SeasonPathParser
|
public class SeasonPathParser
|
||||||
{
|
{
|
||||||
private readonly NamingOptions _options;
|
|
||||||
|
|
||||||
public SeasonPathParser(NamingOptions options)
|
|
||||||
{
|
|
||||||
_options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
|
public SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
|
||||||
{
|
{
|
||||||
var result = new SeasonPathParserResult();
|
var result = new SeasonPathParserResult();
|
||||||
|
|
||||||
var seasonNumberInfo = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders);
|
var seasonNumberInfo = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders);
|
||||||
|
|
||||||
result.SeasonNumber = seasonNumberInfo.Item1;
|
result.SeasonNumber = seasonNumberInfo.seasonNumber;
|
||||||
|
|
||||||
if (result.SeasonNumber.HasValue)
|
if (result.SeasonNumber.HasValue)
|
||||||
{
|
{
|
||||||
result.Success = true;
|
result.Success = true;
|
||||||
result.IsSeasonFolder = seasonNumberInfo.Item2;
|
result.IsSeasonFolder = seasonNumberInfo.isSeasonFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -35,7 +29,7 @@ namespace Emby.Naming.TV
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A season folder must contain one of these somewhere in the name
|
/// A season folder must contain one of these somewhere in the name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly string[] SeasonFolderNames =
|
private static readonly string[] _seasonFolderNames =
|
||||||
{
|
{
|
||||||
"season",
|
"season",
|
||||||
"sæson",
|
"sæson",
|
||||||
|
@ -54,19 +48,23 @@ namespace Emby.Naming.TV
|
||||||
/// <param name="supportSpecialAliases">if set to <c>true</c> [support special aliases].</param>
|
/// <param name="supportSpecialAliases">if set to <c>true</c> [support special aliases].</param>
|
||||||
/// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param>
|
/// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param>
|
||||||
/// <returns>System.Nullable{System.Int32}.</returns>
|
/// <returns>System.Nullable{System.Int32}.</returns>
|
||||||
private Tuple<int?, bool> GetSeasonNumberFromPath(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
|
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPath(
|
||||||
|
string path,
|
||||||
|
bool supportSpecialAliases,
|
||||||
|
bool supportNumericSeasonFolders)
|
||||||
{
|
{
|
||||||
var filename = Path.GetFileName(path);
|
var filename = Path.GetFileName(path) ?? string.Empty;
|
||||||
|
|
||||||
if (supportSpecialAliases)
|
if (supportSpecialAliases)
|
||||||
{
|
{
|
||||||
if (string.Equals(filename, "specials", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(filename, "specials", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return new Tuple<int?, bool>(0, true);
|
return (0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(filename, "extras", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(filename, "extras", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return new Tuple<int?, bool>(0, true);
|
return (0, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +72,7 @@ namespace Emby.Naming.TV
|
||||||
{
|
{
|
||||||
if (int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
if (int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
||||||
{
|
{
|
||||||
return new Tuple<int?, bool>(val, true);
|
return (val, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,12 +82,12 @@ namespace Emby.Naming.TV
|
||||||
|
|
||||||
if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
||||||
{
|
{
|
||||||
return new Tuple<int?, bool>(val, true);
|
return (val, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for one of the season folder names
|
// Look for one of the season folder names
|
||||||
foreach (var name in SeasonFolderNames)
|
foreach (var name in _seasonFolderNames)
|
||||||
{
|
{
|
||||||
var index = filename.IndexOf(name, StringComparison.OrdinalIgnoreCase);
|
var index = filename.IndexOf(name, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
@ -107,10 +105,10 @@ namespace Emby.Naming.TV
|
||||||
|
|
||||||
var parts = filename.Split(new[] { '.', '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries);
|
var parts = filename.Split(new[] { '.', '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
var resultNumber = parts.Select(GetSeasonNumberFromPart).FirstOrDefault(i => i.HasValue);
|
var resultNumber = parts.Select(GetSeasonNumberFromPart).FirstOrDefault(i => i.HasValue);
|
||||||
return new Tuple<int?, bool>(resultNumber, true);
|
return (resultNumber, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int? GetSeasonNumberFromPart(string part)
|
private static int? GetSeasonNumberFromPart(string part)
|
||||||
{
|
{
|
||||||
if (part.Length < 2 || !part.StartsWith("s", StringComparison.OrdinalIgnoreCase))
|
if (part.Length < 2 || !part.StartsWith("s", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
@ -132,7 +130,7 @@ namespace Emby.Naming.TV
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <returns>System.Nullable{System.Int32}.</returns>
|
/// <returns>System.Nullable{System.Int32}.</returns>
|
||||||
private Tuple<int?, bool> GetSeasonNumberFromPathSubstring(string path)
|
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(string path)
|
||||||
{
|
{
|
||||||
var numericStart = -1;
|
var numericStart = -1;
|
||||||
var length = 0;
|
var length = 0;
|
||||||
|
@ -174,10 +172,10 @@ namespace Emby.Naming.TV
|
||||||
|
|
||||||
if (numericStart == -1)
|
if (numericStart == -1)
|
||||||
{
|
{
|
||||||
return new Tuple<int?, bool>(null, isSeasonFolder);
|
return (null, isSeasonFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Tuple<int?, bool>(int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture), isSeasonFolder);
|
return (int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture), isSeasonFolder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,13 @@ namespace Emby.Naming.TV
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The season number.</value>
|
/// <value>The season number.</value>
|
||||||
public int? SeasonNumber { get; set; }
|
public int? SeasonNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this <see cref="SeasonPathParserResult"/> is success.
|
/// Gets or sets a value indicating whether this <see cref="SeasonPathParserResult"/> is success.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if success; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if success; otherwise, <c>false</c>.</value>
|
||||||
public bool Success { get; set; }
|
public bool Success { get; set; }
|
||||||
|
|
||||||
public bool IsSeasonFolder { get; set; }
|
public bool IsSeasonFolder { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,8 @@ namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(name) ?? string.Empty;
|
var extension = Path.GetExtension(name) ?? string.Empty;
|
||||||
// Check supported extensions
|
// Check supported extensions
|
||||||
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase) &&
|
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)
|
||||||
!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
&& !_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// Dummy up a file extension because the expressions will fail without one
|
// Dummy up a file extension because the expressions will fail without one
|
||||||
// This is tricky because we can't just check Path.GetExtension for empty
|
// This is tricky because we can't just check Path.GetExtension for empty
|
||||||
|
@ -38,7 +38,6 @@ namespace Emby.Naming.Video
|
||||||
}
|
}
|
||||||
catch (ArgumentException)
|
catch (ArgumentException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = _options.CleanDateTimeRegexes.Select(i => Clean(name, i))
|
var result = _options.CleanDateTimeRegexes.Select(i => Clean(name, i))
|
||||||
|
@ -69,14 +68,15 @@ namespace Emby.Naming.Video
|
||||||
|
|
||||||
var match = expression.Match(name);
|
var match = expression.Match(name);
|
||||||
|
|
||||||
if (match.Success && match.Groups.Count == 4)
|
if (match.Success
|
||||||
|
&& match.Groups.Count == 4
|
||||||
|
&& match.Groups[1].Success
|
||||||
|
&& match.Groups[2].Success
|
||||||
|
&& int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
|
||||||
{
|
{
|
||||||
if (match.Groups[1].Success && match.Groups[2].Success && int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
|
name = match.Groups[1].Value;
|
||||||
{
|
result.Year = year;
|
||||||
name = match.Groups[1].Value;
|
result.HasChanged = true;
|
||||||
result.Year = year;
|
|
||||||
result.HasChanged = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Name = name;
|
result.Name = name;
|
||||||
|
|
|
@ -56,7 +56,6 @@ namespace Emby.Naming.Video
|
||||||
result.Rule = rule;
|
result.Rule = rule;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (rule.RuleType == ExtraRuleType.Suffix)
|
else if (rule.RuleType == ExtraRuleType.Suffix)
|
||||||
{
|
{
|
||||||
var filename = Path.GetFileNameWithoutExtension(path);
|
var filename = Path.GetFileNameWithoutExtension(path);
|
||||||
|
@ -67,7 +66,6 @@ namespace Emby.Naming.Video
|
||||||
result.Rule = rule;
|
result.Rule = rule;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (rule.RuleType == ExtraRuleType.Regex)
|
else if (rule.RuleType == ExtraRuleType.Regex)
|
||||||
{
|
{
|
||||||
var filename = Path.GetFileName(path);
|
var filename = Path.GetFileName(path);
|
||||||
|
|
|
@ -15,9 +15,9 @@ namespace Emby.Naming.Video
|
||||||
Files = new List<string>();
|
Files = new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ContainsFile(string file, bool IsDirectory)
|
public bool ContainsFile(string file, bool isDirectory)
|
||||||
{
|
{
|
||||||
if (IsDirectoryStack == IsDirectory)
|
if (IsDirectoryStack == isDirectory)
|
||||||
{
|
{
|
||||||
return Files.Contains(file, StringComparer.OrdinalIgnoreCase);
|
return Files.Contains(file, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,12 @@ namespace Emby.Naming.Video
|
||||||
|
|
||||||
public Format3DResult Parse(string path)
|
public Format3DResult Parse(string path)
|
||||||
{
|
{
|
||||||
var delimeters = _options.VideoFlagDelimiters.ToList();
|
int oldLen = _options.VideoFlagDelimiters.Length;
|
||||||
delimeters.Add(' ');
|
var delimeters = new char[oldLen + 1];
|
||||||
|
_options.VideoFlagDelimiters.CopyTo(delimeters, 0);
|
||||||
|
delimeters[oldLen] = ' ';
|
||||||
|
|
||||||
return Parse(new FlagParser(_options).GetFlags(path, delimeters.ToArray()));
|
return Parse(new FlagParser(_options).GetFlags(path, delimeters));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Format3DResult Parse(string[] videoFlags)
|
internal Format3DResult Parse(string[] videoFlags)
|
||||||
|
@ -66,8 +68,10 @@ namespace Emby.Naming.Video
|
||||||
format = flag;
|
format = flag;
|
||||||
result.Tokens.Add(rule.Token);
|
result.Tokens.Add(rule.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
foundPrefix = string.Equals(flag, rule.PreceedingToken, StringComparison.OrdinalIgnoreCase);
|
foundPrefix = string.Equals(flag, rule.PreceedingToken, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,25 +4,27 @@ namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
public class Format3DResult
|
public class Format3DResult
|
||||||
{
|
{
|
||||||
|
public Format3DResult()
|
||||||
|
{
|
||||||
|
Tokens = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether [is3 d].
|
/// Gets or sets a value indicating whether [is3 d].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value>
|
||||||
public bool Is3D { get; set; }
|
public bool Is3D { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the format3 d.
|
/// Gets or sets the format3 d.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The format3 d.</value>
|
/// <value>The format3 d.</value>
|
||||||
public string Format3D { get; set; }
|
public string Format3D { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the tokens.
|
/// Gets or sets the tokens.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The tokens.</value>
|
/// <value>The tokens.</value>
|
||||||
public List<string> Tokens { get; set; }
|
public List<string> Tokens { get; set; }
|
||||||
|
|
||||||
public Format3DResult()
|
|
||||||
{
|
|
||||||
Tokens = new List<string>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,17 +40,24 @@ namespace Emby.Naming.Video
|
||||||
var result = new StackResult();
|
var result = new StackResult();
|
||||||
foreach (var directory in files.GroupBy(file => file.IsDirectory ? file.FullName : Path.GetDirectoryName(file.FullName)))
|
foreach (var directory in files.GroupBy(file => file.IsDirectory ? file.FullName : Path.GetDirectoryName(file.FullName)))
|
||||||
{
|
{
|
||||||
var stack = new FileStack();
|
var stack = new FileStack()
|
||||||
stack.Name = Path.GetFileName(directory.Key);
|
{
|
||||||
stack.IsDirectoryStack = false;
|
Name = Path.GetFileName(directory.Key),
|
||||||
|
IsDirectoryStack = false
|
||||||
|
};
|
||||||
foreach (var file in directory)
|
foreach (var file in directory)
|
||||||
{
|
{
|
||||||
if (file.IsDirectory)
|
if (file.IsDirectory)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
stack.Files.Add(file.FullName);
|
stack.Files.Add(file.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Stacks.Add(stack);
|
result.Stacks.Add(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,16 +121,16 @@ namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
if (!string.Equals(volume1, volume2, StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(volume1, volume2, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase) &&
|
if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase)
|
||||||
string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase))
|
&& string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (stack.Files.Count == 0)
|
if (stack.Files.Count == 0)
|
||||||
{
|
{
|
||||||
stack.Name = title1 + ignore1;
|
stack.Name = title1 + ignore1;
|
||||||
stack.IsDirectoryStack = file1.IsDirectory;
|
stack.IsDirectoryStack = file1.IsDirectory;
|
||||||
//stack.Name = title1 + ignore1 + extension1;
|
|
||||||
stack.Files.Add(file1.FullName);
|
stack.Files.Add(file1.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
stack.Files.Add(file2.FullName);
|
stack.Files.Add(file2.FullName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -9,24 +9,32 @@ namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
public static StubResult ResolveFile(string path, NamingOptions options)
|
public static StubResult ResolveFile(string path, NamingOptions options)
|
||||||
{
|
{
|
||||||
var result = new StubResult();
|
if (path == null)
|
||||||
var extension = Path.GetExtension(path) ?? string.Empty;
|
|
||||||
|
|
||||||
if (options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
result.IsStub = true;
|
return default(StubResult);
|
||||||
|
}
|
||||||
|
|
||||||
path = Path.GetFileNameWithoutExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
|
|
||||||
var token = (Path.GetExtension(path) ?? string.Empty).TrimStart('.');
|
if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return default(StubResult);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var rule in options.StubTypes)
|
var result = new StubResult()
|
||||||
|
{
|
||||||
|
IsStub = true
|
||||||
|
};
|
||||||
|
|
||||||
|
path = Path.GetFileNameWithoutExtension(path);
|
||||||
|
var token = Path.GetExtension(path).TrimStart('.');
|
||||||
|
|
||||||
|
foreach (var rule in options.StubTypes)
|
||||||
|
{
|
||||||
|
if (string.Equals(rule.Token, token, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (string.Equals(rule.Token, token, StringComparison.OrdinalIgnoreCase))
|
result.StubType = rule.StubType;
|
||||||
{
|
break;
|
||||||
result.StubType = rule.StubType;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ namespace Emby.Naming.Video
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value>
|
||||||
public bool IsStub { get; set; }
|
public bool IsStub { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of the stub.
|
/// Gets or sets the type of the stub.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -7,6 +7,7 @@ namespace Emby.Naming.Video
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The token.</value>
|
/// <value>The token.</value>
|
||||||
public string Token { get; set; }
|
public string Token { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of the stub.
|
/// Gets or sets the type of the stub.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -11,56 +10,67 @@ namespace Emby.Naming.Video
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The path.</value>
|
/// <value>The path.</value>
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the container.
|
/// Gets or sets the container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The container.</value>
|
/// <value>The container.</value>
|
||||||
public string Container { get; set; }
|
public string Container { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name.
|
/// Gets or sets the name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The name.</value>
|
/// <value>The name.</value>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the year.
|
/// Gets or sets the year.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The year.</value>
|
/// <value>The year.</value>
|
||||||
public int? Year { get; set; }
|
public int? Year { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of the extra, e.g. trailer, theme song, behing the scenes, etc.
|
/// Gets or sets the type of the extra, e.g. trailer, theme song, behing the scenes, etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The type of the extra.</value>
|
/// <value>The type of the extra.</value>
|
||||||
public string ExtraType { get; set; }
|
public string ExtraType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the extra rule.
|
/// Gets or sets the extra rule.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The extra rule.</value>
|
/// <value>The extra rule.</value>
|
||||||
public ExtraRule ExtraRule { get; set; }
|
public ExtraRule ExtraRule { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the format3 d.
|
/// Gets or sets the format3 d.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The format3 d.</value>
|
/// <value>The format3 d.</value>
|
||||||
public string Format3D { get; set; }
|
public string Format3D { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether [is3 d].
|
/// Gets or sets a value indicating whether [is3 d].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value>
|
||||||
public bool Is3D { get; set; }
|
public bool Is3D { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this instance is stub.
|
/// Gets or sets a value indicating whether this instance is stub.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value>
|
||||||
public bool IsStub { get; set; }
|
public bool IsStub { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of the stub.
|
/// Gets or sets the type of the stub.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The type of the stub.</value>
|
/// <value>The type of the stub.</value>
|
||||||
public string StubType { get; set; }
|
public string StubType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type.
|
/// Gets or sets the type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The type.</value>
|
/// <value>The type.</value>
|
||||||
public bool IsDirectory { get; set; }
|
public bool IsDirectory { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the file name without extension.
|
/// Gets the file name without extension.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -12,21 +12,25 @@ namespace Emby.Naming.Video
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The name.</value>
|
/// <value>The name.</value>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the year.
|
/// Gets or sets the year.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The year.</value>
|
/// <value>The year.</value>
|
||||||
public int? Year { get; set; }
|
public int? Year { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the files.
|
/// Gets or sets the files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The files.</value>
|
/// <value>The files.</value>
|
||||||
public List<VideoFileInfo> Files { get; set; }
|
public List<VideoFileInfo> Files { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the extras.
|
/// Gets or sets the extras.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The extras.</value>
|
/// <value>The extras.</value>
|
||||||
public List<VideoFileInfo> Extras { get; set; }
|
public List<VideoFileInfo> Extras { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the alternate versions.
|
/// Gets or sets the alternate versions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -53,7 +53,7 @@ namespace Emby.Naming.Video
|
||||||
Name = stack.Name
|
Name = stack.Name
|
||||||
};
|
};
|
||||||
|
|
||||||
info.Year = info.Files.First().Year;
|
info.Year = info.Files[0].Year;
|
||||||
|
|
||||||
var extraBaseNames = new List<string>
|
var extraBaseNames = new List<string>
|
||||||
{
|
{
|
||||||
|
@ -87,7 +87,7 @@ namespace Emby.Naming.Video
|
||||||
Name = media.Name
|
Name = media.Name
|
||||||
};
|
};
|
||||||
|
|
||||||
info.Year = info.Files.First().Year;
|
info.Year = info.Files[0].Year;
|
||||||
|
|
||||||
var extras = GetExtras(remainingFiles, new List<string> { media.FileNameWithoutExtension });
|
var extras = GetExtras(remainingFiles, new List<string> { media.FileNameWithoutExtension });
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ namespace Emby.Naming.Video
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(parentPath))
|
if (!string.IsNullOrEmpty(parentPath))
|
||||||
{
|
{
|
||||||
var folderName = Path.GetFileName(Path.GetDirectoryName(videoPath));
|
var folderName = Path.GetFileName(parentPath);
|
||||||
if (!string.IsNullOrEmpty(folderName))
|
if (!string.IsNullOrEmpty(folderName))
|
||||||
{
|
{
|
||||||
var extras = GetExtras(remainingFiles, new List<string> { folderName });
|
var extras = GetExtras(remainingFiles, new List<string> { folderName });
|
||||||
|
@ -163,9 +163,7 @@ namespace Emby.Naming.Video
|
||||||
Year = i.Year
|
Year = i.Year
|
||||||
}));
|
}));
|
||||||
|
|
||||||
var orderedList = list.OrderBy(i => i.Name);
|
return list.OrderBy(i => i.Name);
|
||||||
|
|
||||||
return orderedList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<VideoInfo> GetVideosGroupedByVersion(List<VideoInfo> videos)
|
private IEnumerable<VideoInfo> GetVideosGroupedByVersion(List<VideoInfo> videos)
|
||||||
|
@ -179,23 +177,21 @@ namespace Emby.Naming.Video
|
||||||
|
|
||||||
var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path));
|
var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path));
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(folderName) && folderName.Length > 1)
|
if (!string.IsNullOrEmpty(folderName)
|
||||||
|
&& folderName.Length > 1
|
||||||
|
&& videos.All(i => i.Files.Count == 1
|
||||||
|
&& IsEligibleForMultiVersion(folderName, i.Files[0].Path))
|
||||||
|
&& HaveSameYear(videos))
|
||||||
{
|
{
|
||||||
if (videos.All(i => i.Files.Count == 1 && IsEligibleForMultiVersion(folderName, i.Files[0].Path)))
|
var ordered = videos.OrderBy(i => i.Name).ToList();
|
||||||
{
|
|
||||||
if (HaveSameYear(videos))
|
|
||||||
{
|
|
||||||
var ordered = videos.OrderBy(i => i.Name).ToList();
|
|
||||||
|
|
||||||
list.Add(ordered[0]);
|
list.Add(ordered[0]);
|
||||||
|
|
||||||
list[0].AlternateVersions = ordered.Skip(1).Select(i => i.Files[0]).ToList();
|
list[0].AlternateVersions = ordered.Skip(1).Select(i => i.Files[0]).ToList();
|
||||||
list[0].Name = folderName;
|
list[0].Name = folderName;
|
||||||
list[0].Extras.AddRange(ordered.Skip(1).SelectMany(i => i.Extras));
|
list[0].Extras.AddRange(ordered.Skip(1).SelectMany(i => i.Extras));
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return videos;
|
return videos;
|
||||||
|
@ -213,9 +209,9 @@ namespace Emby.Naming.Video
|
||||||
if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
|
if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
testFilename = testFilename.Substring(folderName.Length).Trim();
|
testFilename = testFilename.Substring(folderName.Length).Trim();
|
||||||
return string.IsNullOrEmpty(testFilename) ||
|
return string.IsNullOrEmpty(testFilename)
|
||||||
testFilename.StartsWith("-") ||
|
|| testFilename[0] == '-'
|
||||||
string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)) ;
|
|| string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -38,10 +38,11 @@ namespace Emby.Naming.Video
|
||||||
/// Resolves the specified path.
|
/// Resolves the specified path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <param name="IsDirectory">if set to <c>true</c> [is folder].</param>
|
/// <param name="isDirectory">if set to <c>true</c> [is folder].</param>
|
||||||
|
/// <param name="parseName">Whether or not the name should be parsed for info</param>
|
||||||
/// <returns>VideoFileInfo.</returns>
|
/// <returns>VideoFileInfo.</returns>
|
||||||
/// <exception cref="ArgumentNullException">path</exception>
|
/// <exception cref="ArgumentNullException">path</exception>
|
||||||
public VideoFileInfo Resolve(string path, bool IsDirectory, bool parseName = true)
|
public VideoFileInfo Resolve(string path, bool isDirectory, bool parseName = true)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
|
@ -52,9 +53,10 @@ namespace Emby.Naming.Video
|
||||||
string container = null;
|
string container = null;
|
||||||
string stubType = null;
|
string stubType = null;
|
||||||
|
|
||||||
if (!IsDirectory)
|
if (!isDirectory)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
|
|
||||||
// Check supported extensions
|
// Check supported extensions
|
||||||
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
@ -79,7 +81,7 @@ namespace Emby.Naming.Video
|
||||||
|
|
||||||
var extraResult = new ExtraResolver(_options).GetExtraInfo(path);
|
var extraResult = new ExtraResolver(_options).GetExtraInfo(path);
|
||||||
|
|
||||||
var name = IsDirectory
|
var name = isDirectory
|
||||||
? Path.GetFileName(path)
|
? Path.GetFileName(path)
|
||||||
: Path.GetFileNameWithoutExtension(path);
|
: Path.GetFileNameWithoutExtension(path);
|
||||||
|
|
||||||
|
@ -108,7 +110,7 @@ namespace Emby.Naming.Video
|
||||||
Is3D = format3DResult.Is3D,
|
Is3D = format3DResult.Is3D,
|
||||||
Format3D = format3DResult.Format3D,
|
Format3D = format3DResult.Format3D,
|
||||||
ExtraType = extraResult.ExtraType,
|
ExtraType = extraResult.ExtraType,
|
||||||
IsDirectory = IsDirectory,
|
IsDirectory = isDirectory,
|
||||||
ExtraRule = extraResult.Rule
|
ExtraRule = extraResult.Rule
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,8 +52,8 @@
|
||||||
|
|
||||||
<!-- Code analysers-->
|
<!-- Code analysers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" />
|
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.2" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
|
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -2368,7 +2368,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
public int? GetSeasonNumberFromPath(string path)
|
public int? GetSeasonNumberFromPath(string path)
|
||||||
{
|
{
|
||||||
return new SeasonPathParser(GetNamingOptions()).Parse(path, true, true).SeasonNumber;
|
return new SeasonPathParser().Parse(path, true, true).SeasonNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
|
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
|
||||||
|
|
|
@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||||
|
|
||||||
var path = args.Path;
|
var path = args.Path;
|
||||||
|
|
||||||
var seasonParserResult = new SeasonPathParser(namingOptions).Parse(path, true, true);
|
var seasonParserResult = new SeasonPathParser().Parse(path, true, true);
|
||||||
|
|
||||||
var season = new Season
|
var season = new Season
|
||||||
{
|
{
|
||||||
|
|
|
@ -194,9 +194,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||||
/// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns>
|
/// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns>
|
||||||
private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager)
|
private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions();
|
var seasonNumber = new SeasonPathParser().Parse(path, isTvContentType, isTvContentType).SeasonNumber;
|
||||||
|
|
||||||
var seasonNumber = new SeasonPathParser(namingOptions).Parse(path, isTvContentType, isTvContentType).SeasonNumber;
|
|
||||||
|
|
||||||
return seasonNumber.HasValue;
|
return seasonNumber.HasValue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<!-- We need C# 7.1 for async main-->
|
<!-- We need C# 7.1 for async main-->
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<!-- Disable documentation warnings (for now) -->
|
<!-- Disable documentation warnings (for now) -->
|
||||||
<NoWarn>SA1600;SA1601;CS1591</NoWarn>
|
<NoWarn>SA1600;SA1601;SA1629;CS1591</NoWarn>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
@ -26,8 +26,8 @@
|
||||||
|
|
||||||
<!-- Code analysers-->
|
<!-- Code analysers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" />
|
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.2" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
|
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -122,8 +122,12 @@ namespace Jellyfin.Server
|
||||||
// The default connection limit is 10 for ASP.NET hosted applications and 2 for all others.
|
// The default connection limit is 10 for ASP.NET hosted applications and 2 for all others.
|
||||||
ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
|
ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
|
||||||
|
|
||||||
|
// CA5359: Do Not Disable Certificate Validation
|
||||||
|
#pragma warning disable CA5359
|
||||||
|
|
||||||
// Allow all https requests
|
// Allow all https requests
|
||||||
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
|
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
|
||||||
|
#pragma warning restore CA5359
|
||||||
|
|
||||||
var fileSystem = new ManagedFileSystem(_loggerFactory, appPaths);
|
var fileSystem = new ManagedFileSystem(_loggerFactory, appPaths);
|
||||||
|
|
||||||
|
@ -368,7 +372,7 @@ namespace Jellyfin.Server
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(ex, "Skia not available. Will fallback to NullIMageEncoder. {0}");
|
_logger.LogInformation(ex, "Skia not available. Will fallback to NullIMageEncoder.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new NullImageEncoder();
|
return new NullImageEncoder();
|
||||||
|
|
|
@ -14,12 +14,17 @@
|
||||||
<Rule Id="SA1200" Action="None" />
|
<Rule Id="SA1200" Action="None" />
|
||||||
<!-- disable warning SA1309: Fields must not begin with an underscore -->
|
<!-- disable warning SA1309: Fields must not begin with an underscore -->
|
||||||
<Rule Id="SA1309" Action="None" />
|
<Rule Id="SA1309" Action="None" />
|
||||||
|
<!-- disable warning SA1413: Use trailing comma in multi-line initializers -->
|
||||||
|
<Rule Id="SA1413" Action="None" />
|
||||||
<!-- disable warning SA1512: Single-line comments must not be followed by blank line -->
|
<!-- disable warning SA1512: Single-line comments must not be followed by blank line -->
|
||||||
<Rule Id="SA1512" Action="None" />
|
<Rule Id="SA1512" Action="None" />
|
||||||
<!-- disable warning SA1633: The file header is missing or not located at the top of the file -->
|
<!-- disable warning SA1633: The file header is missing or not located at the top of the file -->
|
||||||
<Rule Id="SA1633" Action="None" />
|
<Rule Id="SA1633" Action="None" />
|
||||||
</Rules>
|
</Rules>
|
||||||
|
|
||||||
<Rules AnalyzerId="Microsoft.CodeAnalysis.FxCopAnalyzers" RuleNamespace="Microsoft.Design">
|
<Rules AnalyzerId="Microsoft.CodeAnalysis.FxCopAnalyzers" RuleNamespace="Microsoft.Design">
|
||||||
|
<!-- disable warning CA1031: Do not catch general exception types -->
|
||||||
|
<Rule Id="CA1031" Action="Info" />
|
||||||
<!-- disable warning CA1822: Member does not access instance data and can be marked as static -->
|
<!-- disable warning CA1822: Member does not access instance data and can be marked as static -->
|
||||||
<Rule Id="CA1822" Action="Info" />
|
<Rule Id="CA1822" Action="Info" />
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue