From 58d2c94eb079864a05163de27cd7828d95fd1a5b Mon Sep 17 00:00:00 2001 From: panko Date: Mon, 7 Sep 2026 20:41:21 +0200 Subject: [PATCH 1/3] Skip optimistic expressions when parsing series names Optimistic episode expressions (bare numbers) are only meant for episode parsing. When SeriesPathParser uses them, release folder names like Silo.S03.1080p.WEB-DL... get a false series name by reading 264 as S02E64. Signed-off-by: panko --- Emby.Naming/TV/SeriesPathParser.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) 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; } } From 83d2b87ebb392f7988a2b6057a0a567f81ad6f82 Mon Sep 17 00:00:00 2001 From: panko Date: Sat, 12 Sep 2026 18:07:16 +0200 Subject: [PATCH 2/3] Add regression tests for series names from release folders Signed-off-by: panko --- .../TV/ReleaseNameParsingTests.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/Jellyfin.Naming.Tests/TV/ReleaseNameParsingTests.cs diff --git a/tests/Jellyfin.Naming.Tests/TV/ReleaseNameParsingTests.cs b/tests/Jellyfin.Naming.Tests/TV/ReleaseNameParsingTests.cs new file mode 100644 index 0000000000..453f19ed52 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/TV/ReleaseNameParsingTests.cs @@ -0,0 +1,37 @@ +using Emby.Naming.Common; +using Emby.Naming.TV; +using Xunit; + +namespace Jellyfin.Naming.Tests.TV +{ + /// + /// Regression tests for series name extraction from release folder names. + /// All titles, groups and person names below are fictional. + /// + public class ReleaseNameParsingTests + { + private static readonly NamingOptions _namingOptions = new(); + + [Theory] + // Optimistic episode expressions must not produce series names. + [InlineData("Bunker.S03.1080p.PULSAR.WEB-DL.DDP5.1.Atmos.H.264-showWEB", "Bunker")] + [InlineData("Outer.Colony.S01.1080p.NOVA.WEB-DL.DDP5.1.H.264.HUN.ENG-QUASAR", "Outer Colony")] + public void SeriesResolver_ExtractsNameFromReleaseFolder(string folderName, string expectedName) + { + var result = SeriesResolver.Resolve(_namingOptions, "/media/" + folderName); + + Assert.Equal(expectedName, result.Name); + } + + [Theory] + // Resolution patterns (1280x720 etc.) must not parse as season/episode. + [InlineData("/media/Jujutsu Kaisen (BD_1280x720)")] + [InlineData("/media/Show.1920x1080.BluRay")] + public void SeriesPathParser_ResolutionPatternIsNotASeries(string path) + { + var result = SeriesPathParser.Parse(_namingOptions, path); + + Assert.False(result.Success); + } + } +} From f02164771d4ce18fa93e4c30c69c264ecbae76ae Mon Sep 17 00:00:00 2001 From: panko Date: Tue, 15 Sep 2026 14:05:33 +0200 Subject: [PATCH 3/3] Trim release tests into existing suites and cover NNNNxNNNN Signed-off-by: panko --- .../TV/ReleaseNameParsingTests.cs | 37 ------------------- .../TV/SeriesPathParserTest.cs | 10 +++++ .../TV/SeriesResolverTests.cs | 2 + 3 files changed, 12 insertions(+), 37 deletions(-) delete mode 100644 tests/Jellyfin.Naming.Tests/TV/ReleaseNameParsingTests.cs diff --git a/tests/Jellyfin.Naming.Tests/TV/ReleaseNameParsingTests.cs b/tests/Jellyfin.Naming.Tests/TV/ReleaseNameParsingTests.cs deleted file mode 100644 index 453f19ed52..0000000000 --- a/tests/Jellyfin.Naming.Tests/TV/ReleaseNameParsingTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Emby.Naming.Common; -using Emby.Naming.TV; -using Xunit; - -namespace Jellyfin.Naming.Tests.TV -{ - /// - /// Regression tests for series name extraction from release folder names. - /// All titles, groups and person names below are fictional. - /// - public class ReleaseNameParsingTests - { - private static readonly NamingOptions _namingOptions = new(); - - [Theory] - // Optimistic episode expressions must not produce series names. - [InlineData("Bunker.S03.1080p.PULSAR.WEB-DL.DDP5.1.Atmos.H.264-showWEB", "Bunker")] - [InlineData("Outer.Colony.S01.1080p.NOVA.WEB-DL.DDP5.1.H.264.HUN.ENG-QUASAR", "Outer Colony")] - public void SeriesResolver_ExtractsNameFromReleaseFolder(string folderName, string expectedName) - { - var result = SeriesResolver.Resolve(_namingOptions, "/media/" + folderName); - - Assert.Equal(expectedName, result.Name); - } - - [Theory] - // Resolution patterns (1280x720 etc.) must not parse as season/episode. - [InlineData("/media/Jujutsu Kaisen (BD_1280x720)")] - [InlineData("/media/Show.1920x1080.BluRay")] - public void SeriesPathParser_ResolutionPatternIsNotASeries(string path) - { - var result = SeriesPathParser.Parse(_namingOptions, path); - - Assert.False(result.Success); - } - } -} diff --git a/tests/Jellyfin.Naming.Tests/TV/SeriesPathParserTest.cs b/tests/Jellyfin.Naming.Tests/TV/SeriesPathParserTest.cs index e6b0409db7..4e1de22790 100644 --- a/tests/Jellyfin.Naming.Tests/TV/SeriesPathParserTest.cs +++ b/tests/Jellyfin.Naming.Tests/TV/SeriesPathParserTest.cs @@ -25,5 +25,15 @@ namespace Jellyfin.Naming.Tests.TV Assert.Equal(name, res.SeriesName); Assert.True(res.Success); } + + [Theory] + [InlineData("/media/Jujutsu Kaisen (BD_1280x720)")] + [InlineData("/media/Show.1920x1080.BluRay")] + public void SeriesPathParser_ResolutionPatternIsNotASeries(string path) + { + var res = SeriesPathParser.Parse(_namingOptions, path); + + Assert.False(res.Success); + } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/SeriesResolverTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeriesResolverTests.cs index 023c6cb2fa..0c44595441 100644 --- a/tests/Jellyfin.Naming.Tests/TV/SeriesResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/SeriesResolverTests.cs @@ -25,6 +25,8 @@ namespace Jellyfin.Naming.Tests.TV [InlineData("Marvel's.Agents.of.S.H.I.E.L.D.", "Marvel's Agents of S.H.I.E.L.D.")] [InlineData("The.Show.S.H.O.W", "The Show S.H.O.W")] [InlineData("/some/path/Dawson's Creek", "Dawson's Creek")] + [InlineData("/media/Bunker.S03.1080p.PULSAR.WEB-DL.DDP5.1.Atmos.H.264-showWEB", "Bunker")] + [InlineData("/media/Outer.Colony.S01.1080p.NOVA.WEB-DL.DDP5.1.H.264.HUN.ENG-QUASAR", "Outer Colony")] public void SeriesResolverResolveTest(string path, string name) { var res = SeriesResolver.Resolve(_namingOptions, path);