diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-09-06 08:04:34 +0200 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-09-06 08:04:34 +0200 |
| commit | 79a6ac6d82499814ca2547bdd3872d24c2f9b08d (patch) | |
| tree | 64a5259fbe44106ea42c5f243726319aa41fc239 | |
| parent | 006809f02a41064b8c7ca64ce7391c9296ac1074 (diff) | |
Don't read episode markers in episode titles as a multi-episode range
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)); } |
