Don't read episode markers in episode titles as a multi-episode range

This commit is contained in:
Shadowghost
2026-09-06 08:04:34 +02:00
parent 006809f02a
commit 79a6ac6d82
4 changed files with 18 additions and 14 deletions
+4 -2
View File
@@ -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;
}
@@ -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)
@@ -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);
@@ -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));
}