From 8677a17c3e326a538dab7c3863791e0087d178b8 Mon Sep 17 00:00:00 2001 From: SenorSmartyPants Date: Fri, 24 Jun 2022 17:43:08 -0500 Subject: [PATCH 1/5] Non greedy regex, parse the first year, not the last --- Emby.Naming/Common/NamingOptions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 2bd089ed8f..0cbcc0e4b4 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -146,8 +146,8 @@ namespace Emby.Naming.Common CleanDateTimes = new[] { - @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*", - @"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*" + @"(.+?[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*", + @"(.+?[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*" }; CleanStrings = new[] From 9016fec892239d3cb921bcc8ad03fe7bbc841b60 Mon Sep 17 00:00:00 2001 From: SenorSmartyPants Date: Mon, 13 Mar 2023 16:31:55 -0500 Subject: [PATCH 2/5] Make sure episodes in series folder don't group as multiversion Test for episodes in series folder Turn on multiple versions for episodes Support for multiversion episodes in mixed folders Update 2 failing test cases. These were for passing for unofficially supported filenames. Dashes with no spaces, which is not how JF docs say multiversion files are supposed to be named. Fix possible null fix null take 2 Don't ParseName when calling ResolveVideos ParseName drops everything after year in a filename. This results in episode information being dropped if year is present. Update tests to set ParseName=false Additional test with Mixed folder with Year in filename Added case when calculating displayname for versions for mixed folders. Add StringComparison.Ordinal to LastIndexOf Was generating an error in recent build attempts. Clean the episode filename to set the grouper value This allows files like Name (2020) - S01E01 [BluRay-480p x264][AC3 2.0] - [ORIGINAL].mkv Name (2020) - S01E01 [BluRay-1080p x264][AC3 5.1]- [Remaster].mkv to be grouped on 'Name (2020) - S01E01' Fix false positive merging Only do cleanstring or " -" index cleaning, not both. Compatiblity fix when stacking episodes and multiple versions are present Fix linting problems --- Emby.Naming/Common/NamingOptions.cs | 17 ++ Emby.Naming/Video/VideoListResolver.cs | 134 +++++++++-- .../Library/Resolvers/Movies/MovieResolver.cs | 4 +- MediaBrowser.Controller/Entities/BaseItem.cs | 10 +- .../Video/MultiVersionTests.cs | 215 ++++++++++++++++-- .../Video/VideoListResolverTests.cs | 144 ++++++++++-- 6 files changed, 460 insertions(+), 64 deletions(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 0cbcc0e4b4..1e953848e8 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -726,6 +726,12 @@ namespace Emby.Naming.Common @"^\s*(?[^ ].*?)\s*$" }; + VideoVersionExpressions = new[] + { + // get filename before final space-dash-space + @"^(?.*?)(?:\s-\s(?!.*\s-\s)(.*))?$" + }; + MultipleEpisodeExpressions = new[] { @".*(\\|\/)[sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?[0-9]{1,3}))+[^\\\/]*$", @@ -846,6 +852,11 @@ namespace Emby.Naming.Common /// public string[] CleanStrings { get; set; } + /// + /// Gets or sets list of raw clean strings regular expressions strings. + /// + public string[] VideoVersionExpressions { get; set; } + /// /// Gets or sets list of multi-episode regular expressions. /// @@ -866,6 +877,11 @@ namespace Emby.Naming.Common /// public Regex[] CleanStringRegexes { get; private set; } = Array.Empty(); + /// + /// Gets list of video version regular expressions. + /// + public Regex[] VideoVersionRegexes { get; private set; } = Array.Empty(); + /// /// Compiles raw regex strings into regexes. /// @@ -873,6 +889,7 @@ namespace Emby.Naming.Common { CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray(); CleanStringRegexes = CleanStrings.Select(Compile).ToArray(); + VideoVersionRegexes = VideoVersionExpressions.Select(Compile).ToArray(); } private Regex Compile(string exp) diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 51f29cf088..6812645c15 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -4,7 +4,9 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using Emby.Naming.Common; +using Emby.Naming.TV; using Jellyfin.Extensions; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; namespace Emby.Naming.Video @@ -25,10 +27,16 @@ namespace Emby.Naming.Video /// /// List of related video files. /// The naming options. + /// Collection type of videos being resolved. /// Indication we should consider multi-versions of content. /// Whether to parse the name or use the filename. /// Returns enumerable of which groups files together when related. - public static IReadOnlyList Resolve(IReadOnlyList videoInfos, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true) + public static IReadOnlyList Resolve( + IReadOnlyList videoInfos, + NamingOptions namingOptions, + string collectionType, + bool supportMultiVersion = true, + bool parseName = true) { // Filter out all extras, otherwise they could cause stacks to not be resolved // See the unit test TestStackedWithTrailer @@ -79,12 +87,19 @@ namespace Emby.Naming.Video var info = new VideoInfo(media.Name) { Files = new[] { media } }; info.Year = info.Files[0].Year; + if (info.Year is null) + { + // parse name for year info. Episodes don't get parsed up to this point for year info + var info2 = VideoResolver.Resolve(media.Path, media.IsDirectory, namingOptions, parseName); + info.Year = info2?.Year; + } + list.Add(info); } if (supportMultiVersion) { - list = GetVideosGroupedByVersion(list, namingOptions); + list = GetVideosGroupedByVersion(list, namingOptions, collectionType); } // Whatever files are left, just add them @@ -98,7 +113,7 @@ namespace Emby.Naming.Video return list; } - private static List GetVideosGroupedByVersion(List videos, NamingOptions namingOptions) + private static List GetVideosGroupedByVersion(List videos, NamingOptions namingOptions, string collectionType) { if (videos.Count == 0) { @@ -112,6 +127,8 @@ namespace Emby.Naming.Video return videos; } + var mergeable = new List(); + var notMergeable = new List(); // Cannot use Span inside local functions and delegates thus we cannot use LINQ here nor merge with the above [if] VideoInfo? primary = null; for (var i = 0; i < videos.Count; i++) @@ -122,9 +139,14 @@ namespace Emby.Naming.Video continue; } - if (!IsEligibleForMultiVersion(folderName, video.Files[0].FileNameWithoutExtension, namingOptions)) + // don't merge stacked episodes + if (video.Files.Count() == 1 && IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions, collectionType)) + { + mergeable.Add(video); + } + else { - return videos; + notMergeable.Add(video); } if (folderName.Equals(video.Files[0].FileNameWithoutExtension, StringComparison.Ordinal)) @@ -133,35 +155,59 @@ namespace Emby.Naming.Video } } - if (videos.Count > 1) + var list = new List(); + if (collectionType.Equals(CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) + { + var groupedList = mergeable.GroupBy(x => EpisodeGrouper(x.Files[0].Path, namingOptions, collectionType)); + foreach (var grouping in groupedList) + { + list.Add(OrganizeAlternateVersions(grouping.ToList(), grouping.Key.AsSpan(), primary)); + } + } + else if (mergeable.Count() > 0) { - var groups = videos.GroupBy(x => ResolutionRegex().IsMatch(x.Files[0].FileNameWithoutExtension)).ToList(); - videos.Clear(); + list.Add(OrganizeAlternateVersions(mergeable, folderName, primary)); + } + + // add non mergeables back in + list.AddRange(notMergeable); + list.Sort((x, y) => string.Compare(x.Name, y.Name, StringComparison.Ordinal)); + + return list; + } + + private static VideoInfo OrganizeAlternateVersions(List grouping, ReadOnlySpan name, VideoInfo? primary) + { + VideoInfo? groupPrimary = null; + if (primary is not null && grouping.Contains(primary)) + { + groupPrimary = primary; + } + + var alternateVersions = new List(); + if (grouping.Count() > 1) + { + // groups resolution based into one, and all other names + var groups = grouping.GroupBy(x => ResolutionRegex().IsMatch(x.Files[0].FileNameWithoutExtension)); foreach (var group in groups) { if (group.Key) { - videos.InsertRange(0, group.OrderByDescending(x => x.Files[0].FileNameWithoutExtension.ToString(), new AlphanumericComparator())); + alternateVersions.InsertRange(0, group.OrderByDescending(x => x.Files[0].FileNameWithoutExtension.ToString(), new AlphanumericComparator())); } else { - videos.AddRange(group.OrderBy(x => x.Files[0].FileNameWithoutExtension.ToString(), new AlphanumericComparator())); + alternateVersions.AddRange(group.OrderBy(x => x.Files[0].FileNameWithoutExtension.ToString(), new AlphanumericComparator())); } } } - primary ??= videos[0]; - videos.Remove(primary); - - var list = new List - { - primary - }; - - list[0].AlternateVersions = videos.Select(x => x.Files[0]).ToArray(); - list[0].Name = folderName.ToString(); + groupPrimary ??= alternateVersions.FirstOrDefault() ?? grouping.First(); + alternateVersions.Remove(groupPrimary); + groupPrimary.AlternateVersions = alternateVersions.Select(x => x.Files[0]).ToArray(); - return list; + groupPrimary.Name = name.ToString(); + return groupPrimary; } private static bool HaveSameYear(IReadOnlyList videos) @@ -183,8 +229,16 @@ namespace Emby.Naming.Video return true; } - private static bool IsEligibleForMultiVersion(ReadOnlySpan folderName, ReadOnlySpan testFilename, NamingOptions namingOptions) + private static bool IsEligibleForMultiVersion(ReadOnlySpan folderName, ReadOnlySpan testFilePath, NamingOptions namingOptions, ReadOnlySpan collectionType) { + var testFilename = Path.GetFileNameWithoutExtension(testFilePath); + + if (collectionType.Equals(CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) + { + // episodes are always eligible to be grouped + return true; + } + if (!testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) { return false; @@ -207,5 +261,41 @@ namespace Emby.Naming.Video || testFilename[0] == '-' || CheckMultiVersionRegex().IsMatch(testFilename); } + + private static string EpisodeGrouper(string testFilePath, NamingOptions namingOptions, ReadOnlySpan collectionType) + { + // grouper for tv shows/episodes should be everything before space-dash-space + var resolver = new EpisodeResolver(namingOptions); + EpisodeInfo? episodeInfo = resolver.Resolve(testFilePath, false); + ReadOnlySpan seriesName = episodeInfo!.SeriesName; + + var filename = Path.GetFileNameWithoutExtension(testFilePath); + // start with grouping by filename + string g = filename; + for (var i = 0; i < namingOptions.VideoVersionRegexes.Length; i++) + { + var rule = namingOptions.VideoVersionRegexes[i]; + var match = rule.Match(filename); + if (!match.Success) + { + continue; + } + + g = match.Groups["filename"].Value; + // clean the filename + if (VideoResolver.TryCleanString(g, namingOptions, out string newName)) + { + g = newName; + } + + // never group episodes under series name + if (MemoryExtensions.Equals(g.AsSpan(), seriesName, StringComparison.OrdinalIgnoreCase)) + { + g = filename; + } + } + + return g; + } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 0b65bf921e..5c0ab4bcf4 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -229,7 +229,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) { - return ResolveVideos(parent, files, false, collectionType, true); + return ResolveVideos(parent, files, true, collectionType, false); } return null; @@ -275,7 +275,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies .Where(f => f is not null) .ToList(); - var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, supportMultiEditions, parseName); + var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, collectionType, supportMultiEditions, parseName); var result = new MultiItemResolverResult { diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 9f3e8eec96..b552a2b0f8 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1179,10 +1179,18 @@ namespace MediaBrowser.Controller.Entities { if (HasLocalAlternateVersions) { - var displayName = System.IO.Path.GetFileNameWithoutExtension(path) + var fileName = System.IO.Path.GetFileNameWithoutExtension(path); + var displayName = fileName .Replace(System.IO.Path.GetFileName(ContainingFolderPath), string.Empty, StringComparison.OrdinalIgnoreCase) .TrimStart(new char[] { ' ', '-' }); + if (fileName == displayName) + { + // file does not start with parent folder name. This must be an episode in a mixed directory + // get string after last dash - this is the version name + displayName = fileName.Substring(fileName.LastIndexOf('-') + 1).TrimStart(new char[] { ' ', '-' }); + } + if (!string.IsNullOrEmpty(displayName)) { terms.Add(displayName); diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs index 294f11ee74..a9019fa3ff 100644 --- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs @@ -1,7 +1,9 @@ +using System; using System.Collections.Generic; using System.Linq; using Emby.Naming.Common; using Emby.Naming.Video; +using MediaBrowser.Model.Entities; using Xunit; namespace Jellyfin.Naming.Tests.Video @@ -23,7 +25,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Single(result.Where(v => v.ExtraType is null)); Assert.Single(result.Where(v => v.ExtraType is not null)); @@ -42,7 +45,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Single(result.Where(v => v.ExtraType is null)); Assert.Single(result.Where(v => v.ExtraType is not null)); @@ -60,7 +64,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Single(result); Assert.Single(result[0].AlternateVersions); @@ -82,7 +87,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(7, result.Count); Assert.Empty(result[0].AlternateVersions); @@ -105,7 +111,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Single(result); Assert.Equal(7, result[0].AlternateVersions.Count); @@ -129,7 +136,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(9, result.Count); Assert.Empty(result[0].AlternateVersions); @@ -149,7 +157,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(5, result.Count); Assert.Empty(result[0].AlternateVersions); @@ -171,7 +180,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(5, result.Count); Assert.Empty(result[0].AlternateVersions); @@ -193,7 +203,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Single(result); Assert.Equal("/movies/Iron Man/Iron Man.mkv", result[0].Files[0].Path); @@ -222,7 +233,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Single(result); Assert.Equal("/movies/Iron Man/Iron Man.mkv", result[0].Files[0].Path); @@ -246,7 +258,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(2, result.Count); } @@ -267,7 +280,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(7, result.Count); Assert.Empty(result[0].AlternateVersions); @@ -289,7 +303,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(5, result.Count); Assert.Empty(result[0].AlternateVersions); @@ -306,7 +321,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies); Assert.Single(result); Assert.Single(result[0].AlternateVersions); @@ -323,7 +339,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies); Assert.Single(result); Assert.Single(result[0].AlternateVersions); @@ -344,7 +361,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies); Assert.Single(result); Assert.Equal("/movies/X-Men Apocalypse (2016)/X-Men Apocalypse (2016).mkv", result[0].Files[0].Path); @@ -367,7 +385,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies); Assert.Single(result); Assert.Single(result[0].AlternateVersions); @@ -384,7 +403,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies); Assert.Equal(2, result.Count); } @@ -392,9 +412,166 @@ namespace Jellyfin.Naming.Tests.Video [Fact] public void TestEmptyList() { - var result = VideoListResolver.Resolve(new List(), _namingOptions).ToList(); + var result = VideoListResolver.Resolve(new List(), _namingOptions, string.Empty).ToList(); Assert.Empty(result); } + + [Fact] + public void TestMultiVersionEpisodeDontCollapse() + { + // Test for false positive + + var files = new[] + { + @"/TV/Dexter/Dexter - S01E01 - One.mkv", + @"/TV/Dexter/Dexter - S01E02 - Two.mkv", + @"/TV/Dexter/Dexter - S01E03 - Three.mkv", + @"/TV/Dexter/Dexter - S01E04 - Four.mkv", + @"/TV/Dexter/Dexter - S01E05 - Five.mkv", + }; + + var result = VideoListResolver.Resolve( + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType().ToList(), + _namingOptions, + CollectionType.TvShows); + + Assert.Equal(5, result.Count); + Assert.Empty(result[0].AlternateVersions); + } + + [Fact] + public void TestMultiVersionEpisodeDontCollapse2() + { + // Test for false positive + + var files = new[] + { + @"/TV/Dexter/Dexter - S01E01 One.mkv", + @"/TV/Dexter/Dexter - S01E02 Two.mkv", + @"/TV/Dexter/Dexter - S01E03 Three.mkv", + @"/TV/Dexter/Dexter - S01E04 Four.mkv", + @"/TV/Dexter/Dexter - S01E05 Five.mkv", + @"/TV/Star Trek- Picard/Season 3/Star Trek - Picard 3x01 [WEBDL-720p Proper x264][EAC3 5.1] - Part One - The Next Generation.mkv", + @"/TV/Star Trek- Picard/Season 3/Star Trek - Picard 3x02 [WEBDL-720p Proper x264][EAC3 5.1] - Part Two - Disengage.mkv", + @"/TV/Star Trek- Picard/Season 3/Star Trek - Picard 3x03 [WEBDL-720p x264][EAC3 5.1] - Part Three - Seventeen Seconds.mkv", + }; + + var result = VideoListResolver.Resolve( + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType().ToList(), + _namingOptions, + CollectionType.TvShows); + + Assert.Equal(8, result.Count); + Assert.Empty(result[0].AlternateVersions); + } + + [Fact] + public void TestMultiVersionEpisode() + { + var files = new[] + { + @"/TV/Dexter/Dexter - S01E01/Dexter - S01E01 - One.mkv", + @"/TV/Dexter/Dexter - S01E01/Dexter - S01E01 - Two.mkv", + }; + + var result = VideoListResolver.Resolve( + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType().ToList(), + _namingOptions, + CollectionType.TvShows); + + Assert.Single(result); + Assert.Single(result[0].AlternateVersions); + } + + [Fact] + public void TestMultiVersionEpisodeMixedSeriesFolder() + { + var files = new[] + { + @"/TV/Dexter/Dexter - S01E01.mkv", + @"/TV/Dexter/Dexter - S01E01 - Unaired.mkv", + @"/TV/Dexter/Dexter - S01E02 - Two.mkv", + @"/TV/Dexter/Dexter - S01E03 - Three.mkv", + @"/TV/Dexter/Dexter S02E01 - Ia.mkv", + @"/TV/Dexter/Dexter S02E01 - I.mkv", + @"/TV/Dexter/Dexter - S02E02.mkv", + }; + + var result = VideoListResolver.Resolve( + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType().ToList(), + _namingOptions, + CollectionType.TvShows); + + Assert.Equal(5, result.Count); + Assert.Single(result[0].AlternateVersions); + Assert.Empty(result[1].AlternateVersions); + Assert.Empty(result[2].AlternateVersions); + Assert.Empty(result[3].AlternateVersions); + + var s02e01 = result.FirstOrDefault(x => string.Equals(x.Name, "Dexter S02E01", StringComparison.Ordinal)); + Assert.NotNull(s02e01); + Assert.Single(s02e01!.AlternateVersions); + } + + [Fact] + public void TestMultiVersionEpisodeMixedSeasonFolder() + { + var files = new[] + { + @"/TV/Dexter/Season 2/Dexter - S02E01 - Ia.mkv", + @"/TV/Dexter/Season 2/Dexter - S02E01 - I.mkv", + @"/TV/Dexter/Season 2/Dexter - S02E02.mkv", + }; + + var result = VideoListResolver.Resolve( + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType().ToList(), + _namingOptions, + CollectionType.TvShows); + + Assert.Equal(2, result.Count); + Assert.Single(result[0].AlternateVersions); + Assert.Empty(result[1].AlternateVersions); + } + + [Fact] + public void TestMultiVersionEpisodeMixedSeasonFolderWithYear() + { + var files = new[] + { + @"/TV/Name (2020)/Season 1/Name (2020) - S01E01 - [ORIGINAL].mkv", + @"/TV/Name (2020)/Season 1/Name (2020) - S01E01 - [VERSION].mkv", + @"/TV/Name (2020)/Season 1/Name (2020) - S01E02 - [ORIGINAL].mkv", + }; + + var result = VideoListResolver.Resolve( + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType().ToList(), + _namingOptions, + CollectionType.TvShows); + + Assert.Equal(2, result.Count); + Assert.Single(result[0].AlternateVersions); + Assert.Empty(result[1].AlternateVersions); + } + + [Fact] + public void TestMultiVersionEpisodeMixedSeasonFolderWithYearAndDirtyNames() + { + var files = new[] + { + @"/TV/Name (2020)/Season 1/Name (2020) - S01E01 [BluRay-480p x264][AC3 2.0] - [ORIGINAL].mkv", + @"/TV/Name (2020)/Season 1/Name (2020) - S01E01 [BluRay-1080p x264][AC3 5.1] - [Remaster].mkv", + @"/TV/Name (2020)/Season 1/Name (2020) - S01E02 - [ORIGINAL].mkv", + }; + + var result = VideoListResolver.Resolve( + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType().ToList(), + _namingOptions, + CollectionType.TvShows); + + Assert.Equal(2, result.Count); + Assert.Single(result[0].AlternateVersions); + Assert.Empty(result[1].AlternateVersions); + } } } diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs index 0316377d49..eff57721b7 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs @@ -42,7 +42,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(11, result.Count); var batman = result.FirstOrDefault(x => string.Equals(x.Name, "Batman", StringComparison.Ordinal)); @@ -65,6 +66,90 @@ namespace Jellyfin.Naming.Tests.Video Assert.Equal(ExtraType.Trailer, result[10].ExtraType); } + [Fact] + public void TestTVStackAndVersions() + { + // No stacking here because there is no part/disc/etc + var files = new[] + { + @"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) - s01e01 CD1.avi", + @"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) - s01e01 CD2.avi", + @"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) - s01e02 - The First Cut is the Deepest.avi", + @"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) - s01e03.mp4", + @"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) - s01e04 - Aired Version.mp4", + @"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) - s01e04 - Uncensored Version.mp4" + }; + + var result = VideoListResolver.Resolve( + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType().ToList(), + _namingOptions, + CollectionType.TvShows).ToList(); + + Assert.Equal(4, result.Count); + + var s01e01 = result.FirstOrDefault(x => string.Equals(x.Name, "Grey's Anatomy (2005) - s01e01", StringComparison.Ordinal)); + Assert.NotNull(s01e01); + Assert.Equal(2, s01e01!.Files.Count); + + var s01e04 = result.FirstOrDefault(x => string.Equals(x.Name, "Grey's Anatomy (2005) - s01e04", StringComparison.Ordinal)); + Assert.NotNull(s01e04); + Assert.Equal(1, s01e04!.AlternateVersions.Count); + } + + [Fact] + public void TestTVStackAndVersionsNoFirstDash() + { + // No stacking here because there is no part/disc/etc + var files = new[] + { + @"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) s01e01 - pt1.avi", + @"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) s01e01 - pt2.avi", + @"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) s01e02 - The First Cut is the Deepest.avi", + @"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) s01e03.mp4", + @"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) s01e04 - Aired Version.mp4", + @"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) s01e04 - Uncensored Version.mp4" + }; + + var result = VideoListResolver.Resolve( + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType().ToList(), + _namingOptions, + CollectionType.TvShows).ToList(); + + Assert.Equal(4, result.Count); + + var s01e01 = result.FirstOrDefault(x => string.Equals(x.Name, "Grey's Anatomy (2005) s01e01", StringComparison.Ordinal)); + Assert.NotNull(s01e01); + Assert.Equal(2, s01e01!.Files.Count); + + var s01e04 = result.FirstOrDefault(x => string.Equals(x.Name, "Grey's Anatomy (2005) s01e04", StringComparison.Ordinal)); + Assert.NotNull(s01e04); + Assert.Equal(1, s01e04!.AlternateVersions.Count); + } + + [Fact] + public void TestTVStack() + { + // No stacking here because there is no part/disc/etc + var files = new[] + { + @"/TV/Doctor Who/Season 21/Doctor Who 21x11 - Resurrection of the Daleks - Part 1.mkv", + @"/TV/Doctor Who/Season 21/Doctor Who 21x11 - Resurrection of the Daleks - Part 2.mkv", + @"/TV/Doctor Who/Season 21/Doctor Who 21x12 - Resurrection of the Daleks - Part 3.mkv", + @"/TV/Doctor Who/Season 21/Doctor Who 21x12 - Resurrection of the Daleks - Part 4.mkv" + }; + + var result = VideoListResolver.Resolve( + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType().ToList(), + _namingOptions, + CollectionType.TvShows).ToList(); + + Assert.Equal(2, result.Count); + + var s21e12 = result.FirstOrDefault(x => string.Equals(x.Name, "Doctor Who 21x12 - Resurrection of the Daleks", StringComparison.Ordinal)); + Assert.NotNull(s21e12); + Assert.Equal(2, s21e12!.Files.Count); + } + [Fact] public void TestWithMetadata() { @@ -76,7 +161,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Single(result); } @@ -92,7 +178,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(2, result.Count); Assert.False(result[0].ExtraType.HasValue); @@ -110,7 +197,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(2, result.Count); Assert.False(result[0].ExtraType.HasValue); @@ -129,7 +217,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(3, result.Count); Assert.False(result[0].ExtraType.HasValue); @@ -149,7 +238,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(3, result.Count); Assert.False(result[0].ExtraType.HasValue); @@ -168,7 +258,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(2, result.Count); Assert.False(result[0].ExtraType.HasValue); @@ -190,7 +281,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + string.Empty).ToList(); Assert.Equal(5, result.Count); } @@ -206,7 +298,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, true, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Single(result); } @@ -223,7 +316,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, true, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(2, result.Count); } @@ -241,7 +335,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(3, result.Count); Assert.False(result[0].ExtraType.HasValue); @@ -262,7 +357,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(4, result.Count); Assert.False(result[0].ExtraType.HasValue); @@ -284,7 +380,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + string.Empty).ToList(); Assert.Equal(2, result.Count); } @@ -299,7 +396,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Single(result); } @@ -314,7 +412,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Single(result); } @@ -330,7 +429,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); // The result should contain two individual movies // Version grouping should not work here, because the files are not in a directory with the name 'Four Sisters and a Wedding' @@ -348,7 +448,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(2, result.Count); } @@ -364,7 +465,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(2, result.Count); Assert.False(result[0].ExtraType.HasValue); @@ -382,7 +484,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(2, result.Count); Assert.False(result[0].ExtraType.HasValue); @@ -400,7 +503,8 @@ namespace Jellyfin.Naming.Tests.Video var result = VideoListResolver.Resolve( files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType().ToList(), - _namingOptions).ToList(); + _namingOptions, + CollectionType.Movies).ToList(); Assert.Equal(2, result.Count); Assert.False(result[0].ExtraType.HasValue); From 09e5c3ba6988bb9fc13b197d282f8647c727708b Mon Sep 17 00:00:00 2001 From: SenorSmartyPants Date: Tue, 14 Mar 2023 16:39:22 -0500 Subject: [PATCH 3/5] Code review: Count() -> Count and comments capitalization --- Emby.Naming/Common/NamingOptions.cs | 2 +- Emby.Naming/Video/VideoListResolver.cs | 22 +++++++++---------- MediaBrowser.Controller/Entities/BaseItem.cs | 4 ++-- .../Video/VideoListResolverTests.cs | 3 --- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 1e953848e8..e62da6e8e0 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -728,7 +728,7 @@ namespace Emby.Naming.Common VideoVersionExpressions = new[] { - // get filename before final space-dash-space + // Get filename before final space-dash-space @"^(?.*?)(?:\s-\s(?!.*\s-\s)(.*))?$" }; diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 6812645c15..e95fd25742 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -89,7 +89,7 @@ namespace Emby.Naming.Video info.Year = info.Files[0].Year; if (info.Year is null) { - // parse name for year info. Episodes don't get parsed up to this point for year info + // Parse name for year info. Episodes don't get parsed up to this point for year info. var info2 = VideoResolver.Resolve(media.Path, media.IsDirectory, namingOptions, parseName); info.Year = info2?.Year; } @@ -139,8 +139,8 @@ namespace Emby.Naming.Video continue; } - // don't merge stacked episodes - if (video.Files.Count() == 1 && IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions, collectionType)) + // Don't merge stacked episodes + if (video.Files.Count == 1 && IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions, collectionType)) { mergeable.Add(video); } @@ -164,12 +164,12 @@ namespace Emby.Naming.Video list.Add(OrganizeAlternateVersions(grouping.ToList(), grouping.Key.AsSpan(), primary)); } } - else if (mergeable.Count() > 0) + else if (mergeable.Count > 0) { list.Add(OrganizeAlternateVersions(mergeable, folderName, primary)); } - // add non mergeables back in + // Add non mergeables back in list.AddRange(notMergeable); list.Sort((x, y) => string.Compare(x.Name, y.Name, StringComparison.Ordinal)); @@ -185,7 +185,7 @@ namespace Emby.Naming.Video } var alternateVersions = new List(); - if (grouping.Count() > 1) + if (grouping.Count > 1) { // groups resolution based into one, and all other names var groups = grouping.GroupBy(x => ResolutionRegex().IsMatch(x.Files[0].FileNameWithoutExtension)); @@ -235,7 +235,7 @@ namespace Emby.Naming.Video if (collectionType.Equals(CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) { - // episodes are always eligible to be grouped + // Episodes are always eligible to be grouped return true; } @@ -264,13 +264,13 @@ namespace Emby.Naming.Video private static string EpisodeGrouper(string testFilePath, NamingOptions namingOptions, ReadOnlySpan collectionType) { - // grouper for tv shows/episodes should be everything before space-dash-space + // Grouper for tv shows/episodes should be everything before space-dash-space var resolver = new EpisodeResolver(namingOptions); EpisodeInfo? episodeInfo = resolver.Resolve(testFilePath, false); ReadOnlySpan seriesName = episodeInfo!.SeriesName; var filename = Path.GetFileNameWithoutExtension(testFilePath); - // start with grouping by filename + // Start with grouping by filename string g = filename; for (var i = 0; i < namingOptions.VideoVersionRegexes.Length; i++) { @@ -282,13 +282,13 @@ namespace Emby.Naming.Video } g = match.Groups["filename"].Value; - // clean the filename + // Clean the filename if (VideoResolver.TryCleanString(g, namingOptions, out string newName)) { g = newName; } - // never group episodes under series name + // Never group episodes under series name if (MemoryExtensions.Equals(g.AsSpan(), seriesName, StringComparison.OrdinalIgnoreCase)) { g = filename; diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index b552a2b0f8..11bb1b2da2 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1186,8 +1186,8 @@ namespace MediaBrowser.Controller.Entities if (fileName == displayName) { - // file does not start with parent folder name. This must be an episode in a mixed directory - // get string after last dash - this is the version name + // File does not start with parent folder name. This must be an episode in a mixed directory + // Get string after last dash - this is the version name displayName = fileName.Substring(fileName.LastIndexOf('-') + 1).TrimStart(new char[] { ' ', '-' }); } diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs index eff57721b7..3a8be1447d 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs @@ -69,7 +69,6 @@ namespace Jellyfin.Naming.Tests.Video [Fact] public void TestTVStackAndVersions() { - // No stacking here because there is no part/disc/etc var files = new[] { @"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) - s01e01 CD1.avi", @@ -99,7 +98,6 @@ namespace Jellyfin.Naming.Tests.Video [Fact] public void TestTVStackAndVersionsNoFirstDash() { - // No stacking here because there is no part/disc/etc var files = new[] { @"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) s01e01 - pt1.avi", @@ -129,7 +127,6 @@ namespace Jellyfin.Naming.Tests.Video [Fact] public void TestTVStack() { - // No stacking here because there is no part/disc/etc var files = new[] { @"/TV/Doctor Who/Season 21/Doctor Who 21x11 - Resurrection of the Daleks - Part 1.mkv", From 48176f5d80201f1946aa5d60fab8d785b576c06f Mon Sep 17 00:00:00 2001 From: SenorSmartyPants Date: Sat, 18 Mar 2023 13:25:22 -0500 Subject: [PATCH 4/5] version[ABCD] test --- .../Video/MultiVersionTests.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs index a9019fa3ff..d451f1303a 100644 --- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs @@ -573,5 +573,25 @@ namespace Jellyfin.Naming.Tests.Video Assert.Single(result[0].AlternateVersions); Assert.Empty(result[1].AlternateVersions); } + + [Fact] + public void TestMultiVersionEpisodeABCD() + { + var files = new[] + { + @"/TV/SeriesName/SeriesName- S01E01 - EpisodeTitle - [VersionA].mkv", + @"/TV/SeriesName/SeriesName- S01E01 - EpisodeTitle - [VersionB].mkv", + @"/TV/SeriesName/SeriesName- S01E01 - EpisodeTitle - VersionC.mkv", + @"/TV/SeriesName/SeriesName- S01E01 - EpisodeTitle - VersionD.mkv" + }; + + var result = VideoListResolver.Resolve( + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType().ToList(), + _namingOptions, + CollectionType.TvShows); + + Assert.Single(result); + Assert.Equal(3, result[0].AlternateVersions.Count); + } } } From 8f3a4ca3a6a7d90a708f1e67a7349287cc570041 Mon Sep 17 00:00:00 2001 From: SenorSmartyPants Date: Sun, 24 Sep 2023 15:46:21 -0500 Subject: [PATCH 5/5] Convert Equals 1 to Single tests --- tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs index 3a8be1447d..bb074ab0e6 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs @@ -92,7 +92,7 @@ namespace Jellyfin.Naming.Tests.Video var s01e04 = result.FirstOrDefault(x => string.Equals(x.Name, "Grey's Anatomy (2005) - s01e04", StringComparison.Ordinal)); Assert.NotNull(s01e04); - Assert.Equal(1, s01e04!.AlternateVersions.Count); + Assert.Single(s01e04!.AlternateVersions); } [Fact] @@ -121,7 +121,7 @@ namespace Jellyfin.Naming.Tests.Video var s01e04 = result.FirstOrDefault(x => string.Equals(x.Name, "Grey's Anatomy (2005) s01e04", StringComparison.Ordinal)); Assert.NotNull(s01e04); - Assert.Equal(1, s01e04!.AlternateVersions.Count); + Assert.Single(s01e04!.AlternateVersions); } [Fact]