Fix more warnings

pull/1366/head
Bond-009 5 years ago
parent 2aed2d164b
commit a6f9ceedd8

@ -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;
}
var tmp = filename.Substring(prefix.Length);
tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty; tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty;
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
{ {
result.IsMultiPart = true; result.IsMultiPart = true;
break; 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
{ {
public AudioBookInfo()
{
Files = new List<AudioBookFileInfo>();
Extras = new List<AudioBookFileInfo>();
AlternateVersions = new List<AudioBookFileInfo>();
}
/// <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>
/// Gets or sets the year.
/// </summary>
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<AudioBookFileInfo> Files { get; set; } public List<AudioBookFileInfo> 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<AudioBookFileInfo> Extras { get; set; } public List<AudioBookFileInfo> Extras { get; set; }
/// <summary> /// <summary>
/// Gets or sets the alternate versions. /// Gets or sets the alternate versions.
/// </summary> /// </summary>
/// <value>The alternate versions.</value> /// <value>The alternate versions.</value>
public List<AudioBookFileInfo> AlternateVersions { get; set; } public List<AudioBookFileInfo> AlternateVersions { get; set; }
public AudioBookInfo()
{
Files = new List<AudioBookFileInfo>();
Extras = new List<AudioBookFileInfo>();
AlternateVersions = new List<AudioBookFileInfo>();
}
} }
} }

@ -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[] CleanDateTimes { get; set; }
public string[] CleanStrings { 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,22 @@
<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>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</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,17 +102,15 @@ 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?
result.Success = true; result.Success = true;
} }
@ -142,7 +133,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 +152,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 +164,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; {
return default(StubResult);
}
var extension = Path.GetExtension(path);
if (options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{ {
result.IsStub = true; return default(StubResult);
}
path = Path.GetFileNameWithoutExtension(path); var result = new StubResult()
{
IsStub = true
};
var token = (Path.GetExtension(path) ?? string.Empty).TrimStart('.'); path = Path.GetFileNameWithoutExtension(path);
var token = Path.GetExtension(path).TrimStart('.');
foreach (var rule in options.StubTypes) 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…
Cancel
Save