aboutsummaryrefslogtreecommitdiff
path: root/Emby.Naming
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Naming')
-rw-r--r--Emby.Naming/Common/NamingOptions.cs6
-rw-r--r--Emby.Naming/TV/SeriesPathParser.cs20
-rw-r--r--Emby.Naming/TV/SeriesResolver.cs13
-rw-r--r--Emby.Naming/Video/ExtraRuleResolver.cs4
-rw-r--r--Emby.Naming/Video/VideoListResolver.cs42
5 files changed, 59 insertions, 26 deletions
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index 9ecefb4c7b..58d6e012f9 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -543,6 +543,12 @@ namespace Emby.Naming.Common
MediaType.Video),
new ExtraRule(
+ ExtraType.Sample,
+ ExtraRuleType.DirectoryName,
+ "sample",
+ MediaType.Video),
+
+ new ExtraRule(
ExtraType.Short,
ExtraRuleType.DirectoryName,
"shorts",
diff --git a/Emby.Naming/TV/SeriesPathParser.cs b/Emby.Naming/TV/SeriesPathParser.cs
index 94b4b5c823..1dd7a55aaf 100644
--- a/Emby.Naming/TV/SeriesPathParser.cs
+++ b/Emby.Naming/TV/SeriesPathParser.cs
@@ -20,6 +20,14 @@ namespace Emby.Naming.TV
foreach (var expression in options.EpisodeExpressions)
{
+ // Optimistic expressions (bare numbers, "01.blah", etc.) are only meant for
+ // episode parsing and produce false series names on release folder names like
+ // "Silo.S03.1080p.WEB-DL..." (e.g. reading "264" as S02E64). Skip them here.
+ if (expression.IsOptimistic)
+ {
+ continue;
+ }
+
var currentResult = Parse(path, expression);
if (currentResult.Success)
{
@@ -49,8 +57,18 @@ namespace Emby.Naming.TV
{
if (expression.IsNamed)
{
+ // Reject implausible season numbers (e.g. resolutions like 1280x720
+ // read as S1280E720), mirroring EpisodePathParser.
+ var seasonNumberGroup = match.Groups["seasonnumber"];
+ if (seasonNumberGroup.Success
+ && int.TryParse(seasonNumberGroup.ValueSpan, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var seasonNumber)
+ && ((seasonNumber >= 200 && seasonNumber < 1928) || seasonNumber > 2500))
+ {
+ return result;
+ }
+
result.SeriesName = match.Groups["seriesname"].Value;
- result.Success = !string.IsNullOrEmpty(result.SeriesName) && !match.Groups["seasonnumber"].ValueSpan.IsEmpty;
+ result.Success = !string.IsNullOrEmpty(result.SeriesName) && !seasonNumberGroup.ValueSpan.IsEmpty;
}
}
diff --git a/Emby.Naming/TV/SeriesResolver.cs b/Emby.Naming/TV/SeriesResolver.cs
index 733e2418c2..ce42cb69fb 100644
--- a/Emby.Naming/TV/SeriesResolver.cs
+++ b/Emby.Naming/TV/SeriesResolver.cs
@@ -10,11 +10,14 @@ namespace Emby.Naming.TV
public static partial class SeriesResolver
{
/// <summary>
- /// Regex that matches strings of at least 2 characters separated by a dot or underscore.
- /// Used for removing separators between words, i.e turns "The_show" into "The show" while
- /// preserving names like "S.H.O.W".
+ /// Regex that matches a run of dots or underscores that separates two words, where a word is
+ /// at least 2 characters long. Used for removing separators between words, i.e turns
+ /// "The_show" into "The show" while preserving acronyms like "S.H.O.W", whose single letters
+ /// are a word on neither side. Whitespace bounds a word too, so the dot in
+ /// "Marvel's Agents of S.H.I.E.L.D." is read against the "S" beside it rather than against
+ /// the whole run of words before it.
/// </summary>
- [GeneratedRegex(@"((?<a>[^\._]{2,})[\._]*)|([\._](?<b>[^\._]{2,}))")]
+ [GeneratedRegex(@"(?<=[^\s\._]{2})[\._]+|[\._]+(?=[^\s\._]{2})")]
private static partial Regex SeriesNameRegex();
/// <summary>
@@ -60,7 +63,7 @@ namespace Emby.Naming.TV
if (!string.IsNullOrEmpty(seriesName))
{
- seriesName = SeriesNameRegex().Replace(seriesName, "${a} ${b}").Trim();
+ seriesName = SeriesNameRegex().Replace(seriesName, " ").Trim();
}
return new SeriesInfo(path)
diff --git a/Emby.Naming/Video/ExtraRuleResolver.cs b/Emby.Naming/Video/ExtraRuleResolver.cs
index 2e0caa612f..7b0ef411e9 100644
--- a/Emby.Naming/Video/ExtraRuleResolver.cs
+++ b/Emby.Naming/Video/ExtraRuleResolver.cs
@@ -30,7 +30,7 @@ namespace Emby.Naming.Video
ReadOnlySpan<char> pathSpan = path.AsSpan();
ReadOnlySpan<char> fileName = Path.GetFileName(pathSpan);
ReadOnlySpan<char> fileNameWithoutExtension = Path.GetFileNameWithoutExtension(pathSpan);
- // Trim the digits from the end of the filename so we can recognize things like -trailer2
+ // Trim the digits from the end of the filename so we can recognize things like -trailer2 or sample1
ReadOnlySpan<char> trimmedFileNameWithoutExtension = fileNameWithoutExtension.TrimEnd(_digits);
ReadOnlySpan<char> directoryName = Path.GetFileName(Path.GetDirectoryName(pathSpan));
string fullDirectory = Path.GetDirectoryName(pathSpan).ToString();
@@ -45,7 +45,7 @@ namespace Emby.Naming.Video
bool isMatch = rule.RuleType switch
{
- ExtraRuleType.Filename => fileNameWithoutExtension.Equals(rule.Token, StringComparison.OrdinalIgnoreCase),
+ ExtraRuleType.Filename => trimmedFileNameWithoutExtension.Equals(rule.Token, StringComparison.OrdinalIgnoreCase),
ExtraRuleType.Suffix => trimmedFileNameWithoutExtension.EndsWith(rule.Token, StringComparison.OrdinalIgnoreCase),
ExtraRuleType.Regex => Regex.IsMatch(fileName, rule.Token, RegexOptions.IgnoreCase | RegexOptions.Compiled),
ExtraRuleType.DirectoryName => directoryName.Equals(rule.Token, StringComparison.OrdinalIgnoreCase)
diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index 29330b132d..e16562774d 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -217,24 +217,7 @@ namespace Emby.Naming.Video
for (var i = 0; i < videos.Count; i++)
{
var video = videos[i];
- var episodeResult = _episodePathParser.Parse(video.Files[0].Path, false);
- string? key = null;
- if (episodeResult.Success)
- {
- if (episodeResult.IsByDate
- && episodeResult.Year.HasValue
- && episodeResult.Month.HasValue
- && episodeResult.Day.HasValue)
- {
- key = FormattableString.Invariant(
- $"D{episodeResult.Year.Value}{episodeResult.Month.Value:D2}{episodeResult.Day.Value:D2}");
- }
- else if (episodeResult.EpisodeNumber.HasValue)
- {
- key = FormattableString.Invariant(
- $"S{episodeResult.SeasonNumber ?? 0}E{episodeResult.EpisodeNumber.Value}");
- }
- }
+ var key = GetEpisodeVersionKey(video.Files[0].Path);
if (key is null)
{
@@ -265,6 +248,29 @@ namespace Emby.Naming.Video
return result;
}
+ private string? GetEpisodeVersionKey(string path)
+ {
+ // Optimistic expressions are guesses, so they are not consulted here: merging is destructive,
+ // a file collapsed into the alternate versions of another one is no longer an episode of its own.
+ var episodeResult = _episodePathParser.Parse(path, false, isOptimistic: false, fillExtendedInfo: false);
+ if (!episodeResult.Success)
+ {
+ return null;
+ }
+
+ if (episodeResult.IsByDate)
+ {
+ return episodeResult.Year.HasValue && episodeResult.Month.HasValue && episodeResult.Day.HasValue
+ ? FormattableString.Invariant(
+ $"D{episodeResult.Year.Value}{episodeResult.Month.Value:D2}{episodeResult.Day.Value:D2}")
+ : null;
+ }
+
+ return episodeResult.SeasonNumber.HasValue && episodeResult.EpisodeNumber.HasValue
+ ? FormattableString.Invariant($"S{episodeResult.SeasonNumber.Value}E{episodeResult.EpisodeNumber.Value}")
+ : null;
+ }
+
private static VideoInfo OrganizeAlternateVersions(
List<VideoInfo> videos,
VideoInfo? primaryOverride = null,