diff options
| author | Michael McElroy <44167199+mcmcelro@users.noreply.github.com> | 2025-03-27 20:18:19 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-03-27 18:18:19 -0600 |
| commit | f02190c394ad300c00dec4cc4f3b9d42ac6aaca2 (patch) | |
| tree | 0e258e8396a8161844207b2681f34b15b6d9c35e | |
| parent | 88ceaa39b0347c7b7626d38a48baa64923c66eeb (diff) | |
Fix for Issue #12142: Fix ExtraRuleResolver filtering out top level folders (#12170)
* Fix ExtraRuleResolver to stop filtering out libraries where the name of the base folder matches an 'videos extras' rule with an ExtraRuleType of DirectoryName
Currently the ExtraRuleResolver code doesn't know anything about the root folder of the current library. As a result, when we're attempting to add items in a library where the root folder has a name with a match in Emby.Naming.Common.NamingOptions.VideoExtraRules, the entire library is being ignored as a Video Extras folder.
Need to pass in the root folder of the current library to compare to the path of the current item being evaluated, and if we match the current item's folder to the root folder, then we ignore the ExtraRules with a type of DirectoryName and we continue to scan deeper in the library. Filters still apply to subfolders within the library itself.
* Update CONTRIBUTORS.md
* Update Emby.Naming/Video/ExtraRuleResolver.cs
* Update ExtraTests.cs
Add tests for this fix.
Also add missing tests in TestKodiExtras, TestExpandedExtras, and TestSample, and expanded TestDirectories into TestDirectoriesAudioExtras and TestDirectoriesVideoExtras. There were no checks for the theme-music folder name previously.
* Update ExtraTests.cs
Removed unnecessary "using System"
* In MediaBrowser.Model, upgrade System.Text.Json from 8.0.3 (vulnerable - high risk) to 8.0.4
* Update ExtraTests.cs
Remove empty lines in usings
* Revert "In MediaBrowser.Model, upgrade System.Text.Json from 8.0.3 (vulnerable - high risk) to 8.0.4"
| -rw-r--r-- | CONTRIBUTORS.md | 1 | ||||
| -rw-r--r-- | Emby.Naming/Video/ExtraRuleResolver.cs | 7 | ||||
| -rw-r--r-- | Emby.Naming/Video/VideoListResolver.cs | 5 | ||||
| -rw-r--r-- | Emby.Naming/Video/VideoResolver.cs | 15 | ||||
| -rw-r--r-- | Emby.Server.Implementations/Library/LibraryManager.cs | 2 | ||||
| -rw-r--r-- | Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs | 4 | ||||
| -rw-r--r-- | Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs | 4 | ||||
| -rw-r--r-- | tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs | 62 |
8 files changed, 84 insertions, 16 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ae1a2fd71..0dcce1ea1 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -269,3 +269,4 @@ - [Robert Lützner](https://github.com/rluetzner) - [Nathan McCrina](https://github.com/nfmccrina) - [Martin Reuter](https://github.com/reuterma24) + - [Michael McElroy](https://github.com/mcmcelro) diff --git a/Emby.Naming/Video/ExtraRuleResolver.cs b/Emby.Naming/Video/ExtraRuleResolver.cs index 3219472ef..528906589 100644 --- a/Emby.Naming/Video/ExtraRuleResolver.cs +++ b/Emby.Naming/Video/ExtraRuleResolver.cs @@ -18,8 +18,9 @@ namespace Emby.Naming.Video /// </summary> /// <param name="path">Path to file.</param> /// <param name="namingOptions">The naming options.</param> + /// <param name="libraryRoot">Top-level folder for the containing library.</param> /// <returns>Returns <see cref="ExtraResult"/> object.</returns> - public static ExtraResult GetExtraInfo(string path, NamingOptions namingOptions) + public static ExtraResult GetExtraInfo(string path, NamingOptions namingOptions, string? libraryRoot = "") { var result = new ExtraResult(); @@ -69,7 +70,9 @@ namespace Emby.Naming.Video else if (rule.RuleType == ExtraRuleType.DirectoryName) { var directoryName = Path.GetFileName(Path.GetDirectoryName(pathSpan)); - if (directoryName.Equals(rule.Token, StringComparison.OrdinalIgnoreCase)) + string fullDirectory = Path.GetDirectoryName(pathSpan).ToString(); + if (directoryName.Equals(rule.Token, StringComparison.OrdinalIgnoreCase) + && !string.Equals(fullDirectory, libraryRoot, StringComparison.OrdinalIgnoreCase)) { result.ExtraType = rule.ExtraType; result.Rule = rule; diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 12bc22a6a..a3134f3f6 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -27,8 +27,9 @@ namespace Emby.Naming.Video /// <param name="namingOptions">The naming options.</param> /// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param> /// <param name="parseName">Whether to parse the name or use the filename.</param> + /// <param name="libraryRoot">Top-level folder for the containing library.</param> /// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns> - public static IReadOnlyList<VideoInfo> Resolve(IReadOnlyList<VideoFileInfo> videoInfos, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true) + public static IReadOnlyList<VideoInfo> Resolve(IReadOnlyList<VideoFileInfo> videoInfos, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true, string? libraryRoot = "") { // Filter out all extras, otherwise they could cause stacks to not be resolved // See the unit test TestStackedWithTrailer @@ -65,7 +66,7 @@ namespace Emby.Naming.Video { var info = new VideoInfo(stack.Name) { - Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions, parseName)) + Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions, parseName, libraryRoot)) .OfType<VideoFileInfo>() .ToList() }; diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index db5bfdbf9..afbf6f8fa 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -17,10 +17,11 @@ namespace Emby.Naming.Video /// <param name="path">The path.</param> /// <param name="namingOptions">The naming options.</param> /// <param name="parseName">Whether to parse the name or use the filename.</param> + /// <param name="libraryRoot">Top-level folder for the containing library.</param> /// <returns>VideoFileInfo.</returns> - public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions, bool parseName = true) + public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions, bool parseName = true, string? libraryRoot = "") { - return Resolve(path, true, namingOptions, parseName); + return Resolve(path, true, namingOptions, parseName, libraryRoot); } /// <summary> @@ -28,10 +29,11 @@ namespace Emby.Naming.Video /// </summary> /// <param name="path">The path.</param> /// <param name="namingOptions">The naming options.</param> + /// <param name="libraryRoot">Top-level folder for the containing library.</param> /// <returns>VideoFileInfo.</returns> - public static VideoFileInfo? ResolveFile(string? path, NamingOptions namingOptions) + public static VideoFileInfo? ResolveFile(string? path, NamingOptions namingOptions, string? libraryRoot = "") { - return Resolve(path, false, namingOptions); + return Resolve(path, false, namingOptions, libraryRoot: libraryRoot); } /// <summary> @@ -41,9 +43,10 @@ namespace Emby.Naming.Video /// <param name="isDirectory">if set to <c>true</c> [is folder].</param> /// <param name="namingOptions">The naming options.</param> /// <param name="parseName">Whether or not the name should be parsed for info.</param> + /// <param name="libraryRoot">Top-level folder for the containing library.</param> /// <returns>VideoFileInfo.</returns> /// <exception cref="ArgumentNullException"><c>path</c> is <c>null</c>.</exception> - public static VideoFileInfo? Resolve(string? path, bool isDirectory, NamingOptions namingOptions, bool parseName = true) + public static VideoFileInfo? Resolve(string? path, bool isDirectory, NamingOptions namingOptions, bool parseName = true, string? libraryRoot = "") { if (string.IsNullOrEmpty(path)) { @@ -75,7 +78,7 @@ namespace Emby.Naming.Video var format3DResult = Format3DParser.Parse(path, namingOptions); - var extraResult = ExtraRuleResolver.GetExtraInfo(path, namingOptions); + var extraResult = ExtraRuleResolver.GetExtraInfo(path, namingOptions, libraryRoot); var name = Path.GetFileNameWithoutExtension(path); diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 62f1f3d3a..c8026960d 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2709,7 +2709,7 @@ namespace Emby.Server.Implementations.Library public IEnumerable<BaseItem> FindExtras(BaseItem owner, IReadOnlyList<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) { - var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions); + var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions, libraryRoot: owner.ContainingFolderPath); if (ownerVideoInfo is null) { yield break; diff --git a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs index b4791b945..b9f9f2972 100644 --- a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs @@ -54,9 +54,9 @@ namespace Emby.Server.Implementations.Library.Resolvers _ => _videoResolvers }; - public bool TryGetExtraTypeForOwner(string path, VideoFileInfo ownerVideoFileInfo, [NotNullWhen(true)] out ExtraType? extraType) + public bool TryGetExtraTypeForOwner(string path, VideoFileInfo ownerVideoFileInfo, [NotNullWhen(true)] out ExtraType? extraType, string? libraryRoot = "") { - var extraResult = GetExtraInfo(path, _namingOptions); + var extraResult = GetExtraInfo(path, _namingOptions, libraryRoot); if (extraResult.ExtraType is null) { extraType = null; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 4debe722b..f1aeb1340 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -270,11 +270,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies } var videoInfos = files - .Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, NamingOptions, parseName)) + .Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, NamingOptions, parseName, parent.ContainingFolderPath)) .Where(f => f is not null) .ToList(); - var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, supportMultiEditions, parseName); + var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, supportMultiEditions, parseName, parent.ContainingFolderPath); var result = new MultiItemResolverResult { diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs index 2c33ab492..51eb99f49 100644 --- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs @@ -2,6 +2,7 @@ using Emby.Naming.Common; using Emby.Naming.Video; using MediaBrowser.Model.Entities; using Xunit; + using MediaType = Emby.Naming.Common.MediaType; namespace Jellyfin.Naming.Tests.Video @@ -20,6 +21,9 @@ namespace Jellyfin.Naming.Tests.Video { Test("trailer.mp4", ExtraType.Trailer); Test("300-trailer.mp4", ExtraType.Trailer); + Test("300.trailer.mp4", ExtraType.Trailer); + Test("300_trailer.mp4", ExtraType.Trailer); + Test("300 trailer.mp4", ExtraType.Trailer); Test("theme.mp3", ExtraType.ThemeSong); } @@ -43,6 +47,19 @@ namespace Jellyfin.Naming.Tests.Video Test("300-deletedscene.mp4", ExtraType.DeletedScene); Test("300-interview.mp4", ExtraType.Interview); Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes); + Test("300-featurette.mp4", ExtraType.Featurette); + Test("300-short.mp4", ExtraType.Short); + Test("300-extra.mp4", ExtraType.Unknown); + Test("300-other.mp4", ExtraType.Unknown); + } + + [Theory] + [InlineData(ExtraType.ThemeSong, "theme-music")] + public void TestDirectoriesAudioExtras(ExtraType type, string dirName) + { + Test(dirName + "/300.mp3", type); + Test("300/" + dirName + "/something.mp3", type); + Test("/data/something/Movies/300/" + dirName + "/whoknows.mp3", type); } [Theory] @@ -52,11 +69,14 @@ namespace Jellyfin.Naming.Tests.Video [InlineData(ExtraType.Scene, "scenes")] [InlineData(ExtraType.Sample, "samples")] [InlineData(ExtraType.Short, "shorts")] + [InlineData(ExtraType.Trailer, "trailers")] [InlineData(ExtraType.Featurette, "featurettes")] [InlineData(ExtraType.Clip, "clips")] [InlineData(ExtraType.ThemeVideo, "backdrops")] + [InlineData(ExtraType.Unknown, "extra")] [InlineData(ExtraType.Unknown, "extras")] - public void TestDirectories(ExtraType type, string dirName) + [InlineData(ExtraType.Unknown, "other")] + public void TestDirectoriesVideoExtras(ExtraType type, string dirName) { Test(dirName + "/300.mp4", type); Test("300/" + dirName + "/something.mkv", type); @@ -75,10 +95,44 @@ namespace Jellyfin.Naming.Tests.Video Test("/data/something/Movies/" + dirName + "/" + dirName + ".mp4", null); } + [Theory] + [InlineData(ExtraType.ThemeSong, "theme-music")] + public void TestTopLevelDirectoriesWithAudioExtraNames(ExtraType typicalType, string dirName) + { + string libraryRoot = "/data/something/" + dirName; + TestWithLibraryRoot(libraryRoot + "/300.mp3", libraryRoot, null); + TestWithLibraryRoot(libraryRoot + "/300/" + dirName + "/something.mp3", libraryRoot, typicalType); + } + + [Theory] + [InlineData(ExtraType.Trailer, "trailers")] + [InlineData(ExtraType.ThemeVideo, "backdrops")] + [InlineData(ExtraType.BehindTheScenes, "behind the scenes")] + [InlineData(ExtraType.DeletedScene, "deleted scenes")] + [InlineData(ExtraType.Interview, "interviews")] + [InlineData(ExtraType.Scene, "scenes")] + [InlineData(ExtraType.Sample, "samples")] + [InlineData(ExtraType.Short, "shorts")] + [InlineData(ExtraType.Featurette, "featurettes")] + [InlineData(ExtraType.Unknown, "extras")] + [InlineData(ExtraType.Unknown, "extra")] + [InlineData(ExtraType.Unknown, "other")] + [InlineData(ExtraType.Clip, "clips")] + public void TestTopLevelDirectoriesWithVideoExtraNames(ExtraType typicalType, string dirName) + { + string libraryRoot = "/data/something/" + dirName; + TestWithLibraryRoot(libraryRoot + "/300.mp4", libraryRoot, null); + TestWithLibraryRoot(libraryRoot + "/300/" + dirName + "/something.mkv", libraryRoot, typicalType); + } + [Fact] public void TestSample() { + Test("sample.mp4", ExtraType.Sample); Test("300-sample.mp4", ExtraType.Sample); + Test("300.sample.mp4", ExtraType.Sample); + Test("300_sample.mp4", ExtraType.Sample); + Test("300 sample.mp4", ExtraType.Sample); } private void Test(string input, ExtraType? expectedType) @@ -88,6 +142,12 @@ namespace Jellyfin.Naming.Tests.Video Assert.Equal(expectedType, extraType); } + private void TestWithLibraryRoot(string input, string libraryRoot, ExtraType? expectedType) + { + var extraType = ExtraRuleResolver.GetExtraInfo(input, _videoOptions, libraryRoot).ExtraType; + Assert.Equal(expectedType, extraType); + } + [Fact] public void TestExtraInfo_InvalidRuleType() { |
