From ea206f43a25700904bc9c909d6616dfe55ab8671 Mon Sep 17 00:00:00 2001 From: upscaylman <157367283+upscaylman@users.noreply.github.com> Date: Sun, 29 Mar 2026 12:42:36 +0200 Subject: [PATCH] recognize underscore and dot separators for multi-version grouping (#16465) * Add underscore and dot as multi-version file separators Extend IsEligibleForMultiVersion to recognize _ and . as valid separators between the base movie name and the version suffix. Common naming patterns like 'Movie_4K.mkv' or 'Movie.UHD.mkv' are now correctly grouped as alternate versions during library scan. * Address review: remove comment, add 3D recognition assertions --------- Co-authored-by: aimarshall615-creator --- Emby.Naming/Video/VideoListResolver.cs | 2 + .../Video/MultiVersionTests.cs | 44 ++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 4247fea0e5..a4bfb8d4a1 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -217,6 +217,8 @@ namespace Emby.Naming.Video // The CleanStringParser should have removed common keywords etc. return testFilename.IsEmpty || testFilename[0] == '-' + || testFilename[0] == '_' + || testFilename[0] == '.' || CheckMultiVersionRegex().IsMatch(testFilename); } } diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs index 6b13986957..2fb45600b1 100644 --- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Emby.Naming.Common; @@ -269,8 +270,13 @@ namespace Jellyfin.Naming.Tests.Video files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), _namingOptions).ToList(); - Assert.Equal(7, result.Count); - Assert.Empty(result[0].AlternateVersions); + Assert.Single(result); + Assert.Equal(6, result[0].AlternateVersions.Count); + + // Verify 3D recognition is preserved on alternate versions + var hsbs = result[0].AlternateVersions.First(v => v.Path.Contains("3d-hsbs", StringComparison.Ordinal)); + Assert.True(hsbs.Is3D); + Assert.Equal("hsbs", hsbs.Format3D); } [Fact] @@ -435,5 +441,39 @@ namespace Jellyfin.Naming.Tests.Video Assert.Empty(result); } + + [Fact] + public void Resolve_GivenUnderscoreSeparator_GroupsVersions() + { + var files = new[] + { + "/movies/Movie (2020)/Movie (2020)_4K.mkv", + "/movies/Movie (2020)/Movie (2020)_1080p.mkv" + }; + + var result = VideoListResolver.Resolve( + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), + _namingOptions).ToList(); + + Assert.Single(result); + Assert.Single(result[0].AlternateVersions); + } + + [Fact] + public void Resolve_GivenDotSeparator_GroupsVersions() + { + var files = new[] + { + "/movies/Movie (2020)/Movie (2020).UHD.mkv", + "/movies/Movie (2020)/Movie (2020).1080p.mkv" + }; + + var result = VideoListResolver.Resolve( + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), + _namingOptions).ToList(); + + Assert.Single(result); + Assert.Single(result[0].AlternateVersions); + } } }