aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShadowghost <Ghost_of_Stone@web.de>2026-09-06 08:04:34 +0200
committerShadowghost <Ghost_of_Stone@web.de>2026-09-06 08:04:34 +0200
commit79a6ac6d82499814ca2547bdd3872d24c2f9b08d (patch)
tree64a5259fbe44106ea42c5f243726319aa41fc239
parent006809f02a41064b8c7ca64ce7391c9296ac1074 (diff)
Don't read episode markers in episode titles as a multi-episode range
-rw-r--r--Emby.Naming/TV/EpisodePathParser.cs6
-rw-r--r--MediaBrowser.Providers/TV/EpisodeMetadataService.cs12
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs3
-rw-r--r--tests/Jellyfin.Providers.Tests/TV/EpisodeMetadataServiceTests.cs11
4 files changed, 18 insertions, 14 deletions
diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs
index 0c737964b4..f06aa909ba 100644
--- a/Emby.Naming/TV/EpisodePathParser.cs
+++ b/Emby.Naming/TV/EpisodePathParser.cs
@@ -158,7 +158,9 @@ namespace Emby.Naming.TV
if (nextIndex >= name.Length
|| !"0123456789iIpP".Contains(name[nextIndex], StringComparison.Ordinal))
{
- if (int.TryParse(endingNumberGroup.ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
+ // A range cannot end before it starts, so a lower number belongs to the episode title rather than to a range.
+ if (int.TryParse(endingNumberGroup.ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out num)
+ && num >= result.EpisodeNumber)
{
result.EndingEpisodeNumber = num;
}
@@ -226,7 +228,7 @@ namespace Emby.Naming.TV
info.SeriesName = result.SeriesName;
}
- if (!info.EndingEpisodeNumber.HasValue && info.EpisodeNumber.HasValue)
+ if (!info.EndingEpisodeNumber.HasValue && result.EndingEpisodeNumber >= info.EpisodeNumber)
{
info.EndingEpisodeNumber = result.EndingEpisodeNumber;
}
diff --git a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
index 4ec6afe45b..f662ac2367 100644
--- a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
+++ b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
@@ -44,18 +44,16 @@ public class EpisodeMetadataService : MetadataService<Episode, EpisodeInfo>
{
var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType);
- // An episode cannot end before it starts. Providers and nfo files occasionally report the range
- // transposed, which makes clients render the episode number backwards. Both numbers describe the
- // same set of episodes either way, so restore their order instead of dropping the range.
- if (item.IndexNumber.HasValue && item.IndexNumberEnd < item.IndexNumber)
+ // An episode cannot end before it starts.
+ if (item.IndexNumberEnd < item.IndexNumber)
{
Logger.LogWarning(
- "Correcting reversed episode range {IndexNumber}-{IndexNumberEnd} for {Path}",
- item.IndexNumber,
+ "Discarding episode range end {IndexNumberEnd} preceding episode number {IndexNumber} for {Path}",
item.IndexNumberEnd,
+ item.IndexNumber,
item.Path);
- (item.IndexNumber, item.IndexNumberEnd) = (item.IndexNumberEnd, item.IndexNumber);
+ item.IndexNumberEnd = null;
updatedType |= ItemUpdateType.MetadataImport;
}
else if (item.IndexNumberEnd.HasValue && !item.IndexNumber.HasValue)
diff --git a/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs
index 7e708c681d..4236749423 100644
--- a/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs
@@ -74,6 +74,9 @@ namespace Jellyfin.Naming.Tests.TV
[InlineData("Season 5/S05E23 11-59 [HDTV-1080p][x265 AC3].mkv", null)]
[InlineData("Season 5/S05E23 11-59 [HDTV-1080p][HEVC AC3].mkv", null)]
[InlineData("Season 1/S01E01 1-23-45 [Bluray-1080p][AV1 Opus].mkv", null)]
+ // Episode markers in the episode title must not be read as an episode range
+ [InlineData("Season 03/Star Trek Enterprise (2001) - S03E21 - E2 (1080p BluRay x265).mkv", null)]
+ [InlineData("Season 02/Series Name (2001) - S02E10 - E5 [WEBRip-1080p].mkv", null)]
public void TestGetEndingEpisodeNumberFromFile(string filename, int? endingEpisodeNumber)
{
var result = _episodePathParser.Parse(filename, false);
diff --git a/tests/Jellyfin.Providers.Tests/TV/EpisodeMetadataServiceTests.cs b/tests/Jellyfin.Providers.Tests/TV/EpisodeMetadataServiceTests.cs
index 24fd6a58e9..ea762256db 100644
--- a/tests/Jellyfin.Providers.Tests/TV/EpisodeMetadataServiceTests.cs
+++ b/tests/Jellyfin.Providers.Tests/TV/EpisodeMetadataServiceTests.cs
@@ -104,9 +104,10 @@ public sealed class EpisodeMetadataServiceTests : IDisposable
}
[Theory]
- [InlineData(2, 1)] // e.g. an nfo with its episodedetails blocks in descending order
+ [InlineData(2, 1)]
[InlineData(22, 21)]
- public void BeforeSave_ReversedEpisodeRange_RestoresOrder(int indexNumber, int indexNumberEnd)
+ [InlineData(21, 2)] // e.g. "Series - S03E21 - E2 (1080p BluRay x265).mkv", where "E2" is the episode title
+ public void BeforeSave_ReversedEpisodeRange_ClearsIndexNumberEnd(int indexNumber, int indexNumberEnd)
{
var item = new Episode
{
@@ -116,9 +117,9 @@ public sealed class EpisodeMetadataServiceTests : IDisposable
var updateType = _service.BeforeSave(item);
- // The range still covers the same episodes, it is just no longer transposed
- Assert.Equal(indexNumberEnd, item.IndexNumber);
- Assert.Equal(indexNumber, item.IndexNumberEnd);
+ // The episode number identifies the item, so it is kept and the impossible range is dropped
+ Assert.Equal(indexNumber, item.IndexNumber);
+ Assert.Null(item.IndexNumberEnd);
Assert.True(updateType.HasFlag(ItemUpdateType.MetadataImport));
}