From 160baa02fd9a2e7b4e2b3dcc28769274d0588413 Mon Sep 17 00:00:00 2001
From: Joe Rogers <1337joe@gmail.com>
Date: Mon, 6 Mar 2023 22:18:26 -0500
Subject: [PATCH 1/4] Remove some BaseItem references to make ItemResolveArgs
more usable for testing.
---
.../Library/LibraryManager.cs | 2 +-
.../Entities/AggregateFolder.cs | 2 +-
.../Entities/CollectionFolder.cs | 2 +-
.../Library/ItemResolveArgs.cs | 17 +++++++----------
.../Library/EpisodeResolverTest.cs | 6 ++++--
.../Library/MovieResolverTests.cs | 3 ++-
6 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 66bd68ddd0..9fa4d3599f 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -537,7 +537,7 @@ namespace Emby.Server.Implementations.Library
collectionType = GetContentTypeOverride(fullPath, true);
}
- var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService)
+ var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService, this)
{
Parent = parent,
FileInfo = fileInfo,
diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs
index 08c622cde0..aacb38607e 100644
--- a/MediaBrowser.Controller/Entities/AggregateFolder.cs
+++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs
@@ -120,7 +120,7 @@ namespace MediaBrowser.Controller.Entities
var path = ContainingFolderPath;
- var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService)
+ var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService, LibraryManager)
{
FileInfo = FileSystem.GetDirectoryInfo(path)
};
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index 5ac619d8f5..ff17797179 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -288,7 +288,7 @@ namespace MediaBrowser.Controller.Entities
{
var path = ContainingFolderPath;
- var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService)
+ var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService, LibraryManager)
{
FileInfo = FileSystem.GetDirectoryInfo(path),
Parent = GetParent() as Folder,
diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs
index 01986d3032..730d89356d 100644
--- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs
+++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs
@@ -23,6 +23,7 @@ namespace MediaBrowser.Controller.Library
///
private readonly IServerApplicationPaths _appPaths;
+ private readonly ILibraryManager _libraryManager;
private LibraryOptions _libraryOptions;
///
@@ -30,10 +31,12 @@ namespace MediaBrowser.Controller.Library
///
/// The app paths.
/// The directory service.
- public ItemResolveArgs(IServerApplicationPaths appPaths, IDirectoryService directoryService)
+ /// The library manager.
+ public ItemResolveArgs(IServerApplicationPaths appPaths, IDirectoryService directoryService, ILibraryManager libraryManager)
{
_appPaths = appPaths;
DirectoryService = directoryService;
+ _libraryManager = libraryManager;
}
// TODO remove dependencies as properties, they should be injected where it makes sense
@@ -47,7 +50,7 @@ namespace MediaBrowser.Controller.Library
public LibraryOptions LibraryOptions
{
- get => _libraryOptions ??= Parent is null ? new LibraryOptions() : BaseItem.LibraryManager.GetLibraryOptions(Parent);
+ get => _libraryOptions ??= Parent is null ? new LibraryOptions() : _libraryManager.GetLibraryOptions(Parent);
set => _libraryOptions = value;
}
@@ -231,21 +234,15 @@ namespace MediaBrowser.Controller.Library
///
/// Gets the configured content type for the path.
///
- ///
- /// This is subject to future refactoring as it relies on a static property in BaseItem.
- ///
/// The configured content type.
public string GetConfiguredContentType()
{
- return BaseItem.LibraryManager.GetConfiguredContentType(Path);
+ return _libraryManager.GetConfiguredContentType(Path);
}
///
/// Gets the file system children that do not hit the ignore file check.
///
- ///
- /// This is subject to future refactoring as it relies on a static property in BaseItem.
- ///
/// The file system children that are not ignored.
public IEnumerable GetActualFileSystemChildren()
{
@@ -253,7 +250,7 @@ namespace MediaBrowser.Controller.Library
for (var i = 0; i < numberOfChildren; i++)
{
var child = FileSystemChildren[i];
- if (BaseItem.LibraryManager.IgnoreFile(child, Parent))
+ if (_libraryManager.IgnoreFile(child, Parent))
{
continue;
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
index 286ba04059..65f19a051a 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
@@ -25,7 +25,8 @@ namespace Jellyfin.Server.Implementations.Tests.Library
var episodeResolver = new EpisodeResolver(Mock.Of>(), _namingOptions);
var itemResolveArgs = new ItemResolveArgs(
Mock.Of(),
- Mock.Of())
+ Mock.Of(),
+ null)
{
Parent = parent,
CollectionType = CollectionType.TvShows,
@@ -48,7 +49,8 @@ namespace Jellyfin.Server.Implementations.Tests.Library
var episodeResolver = new EpisodeResolverMock(Mock.Of>(), _namingOptions);
var itemResolveArgs = new ItemResolveArgs(
Mock.Of(),
- Mock.Of())
+ Mock.Of(),
+ null)
{
Parent = series,
CollectionType = CollectionType.TvShows,
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs
index efc3ac0c2a..f96bd6138e 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs
@@ -21,7 +21,8 @@ public class MovieResolverTests
var movieResolver = new MovieResolver(Mock.Of(), Mock.Of>(), _namingOptions);
var itemResolveArgs = new ItemResolveArgs(
Mock.Of(),
- Mock.Of())
+ Mock.Of(),
+ null)
{
Parent = null,
FileInfo = new FileSystemMetadata
From 1c3a97bf6adadf4e3b22177e1e965691637d0426 Mon Sep 17 00:00:00 2001
From: Joe Rogers <1337joe@gmail.com>
Date: Mon, 6 Mar 2023 23:00:55 -0500
Subject: [PATCH 2/4] Inject IDirectoryService where needed instead of passing
it through ItemResolveArgs
---
.../Library/LibraryManager.cs | 8 +++++---
.../Resolvers/Audio/MusicAlbumResolver.cs | 7 +++++--
.../Resolvers/Audio/MusicArtistResolver.cs | 15 +++++++++------
.../Library/Resolvers/BaseVideoResolver.cs | 7 +++++--
.../Library/Resolvers/ExtraResolver.cs | 8 +++++---
.../Library/Resolvers/GenericVideoResolver.cs | 6 ++++--
.../Library/Resolvers/Movies/MovieResolver.cs | 13 +++++++------
.../Library/Resolvers/PhotoResolver.cs | 18 ++++++++++++++----
.../Library/Resolvers/TV/EpisodeResolver.cs | 6 ++++--
.../Entities/AggregateFolder.cs | 2 +-
.../Entities/CollectionFolder.cs | 2 +-
.../Library/ItemResolveArgs.cs | 10 ++--------
.../Library/EpisodeResolverTest.cs | 8 +++-----
.../Library/MovieResolverTests.cs | 3 +--
14 files changed, 66 insertions(+), 47 deletions(-)
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 9fa4d3599f..e5c520ca2b 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -113,6 +113,7 @@ namespace Emby.Server.Implementations.Library
/// The image processor.
/// The memory cache.
/// The naming options.
+ /// The directory service.
public LibraryManager(
IServerApplicationHost appHost,
ILoggerFactory loggerFactory,
@@ -128,7 +129,8 @@ namespace Emby.Server.Implementations.Library
IItemRepository itemRepository,
IImageProcessor imageProcessor,
IMemoryCache memoryCache,
- NamingOptions namingOptions)
+ NamingOptions namingOptions,
+ IDirectoryService directoryService)
{
_appHost = appHost;
_logger = loggerFactory.CreateLogger();
@@ -146,7 +148,7 @@ namespace Emby.Server.Implementations.Library
_memoryCache = memoryCache;
_namingOptions = namingOptions;
- _extraResolver = new ExtraResolver(loggerFactory.CreateLogger(), namingOptions);
+ _extraResolver = new ExtraResolver(loggerFactory.CreateLogger(), namingOptions, directoryService);
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
@@ -537,7 +539,7 @@ namespace Emby.Server.Implementations.Library
collectionType = GetContentTypeOverride(fullPath, true);
}
- var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService, this)
+ var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, this)
{
Parent = parent,
FileInfo = fileInfo,
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
index a922e36855..bbc70701cb 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
@@ -25,16 +25,19 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
private readonly ILogger _logger;
private readonly NamingOptions _namingOptions;
+ private readonly IDirectoryService _directoryService;
///
/// Initializes a new instance of the class.
///
/// The logger.
/// The naming options.
- public MusicAlbumResolver(ILogger logger, NamingOptions namingOptions)
+ /// The directory service.
+ public MusicAlbumResolver(ILogger logger, NamingOptions namingOptions, IDirectoryService directoryService)
{
_logger = logger;
_namingOptions = namingOptions;
+ _directoryService = directoryService;
}
///
@@ -109,7 +112,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
// If args contains music it's a music album
- if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService))
+ if (ContainsMusic(args.FileSystemChildren, true, _directoryService))
{
return true;
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
index 2538c2b5b4..c858dc53d9 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Emby.Naming.Common;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;
@@ -18,19 +19,23 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
public class MusicArtistResolver : ItemResolver
{
private readonly ILogger _logger;
- private NamingOptions _namingOptions;
+ private readonly NamingOptions _namingOptions;
+ private readonly IDirectoryService _directoryService;
///
/// Initializes a new instance of the class.
///
/// Instance of the interface.
/// The .
+ /// The directory service.
public MusicArtistResolver(
ILogger logger,
- NamingOptions namingOptions)
+ NamingOptions namingOptions,
+ IDirectoryService directoryService)
{
_logger = logger;
_namingOptions = namingOptions;
+ _directoryService = directoryService;
}
///
@@ -78,9 +83,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return null;
}
- var directoryService = args.DirectoryService;
-
- var albumResolver = new MusicAlbumResolver(_logger, _namingOptions);
+ var albumResolver = new MusicAlbumResolver(_logger, _namingOptions, _directoryService);
var directories = args.FileSystemChildren.Where(i => i.IsDirectory);
@@ -97,7 +100,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
// If we contain a music album assume we are an artist folder
- if (albumResolver.IsMusicAlbum(fileSystemInfo.FullName, directoryService))
+ if (albumResolver.IsMusicAlbum(fileSystemInfo.FullName, _directoryService))
{
// Stop once we see a music album
state.Stop();
diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
index e8615e7db7..9b133bef48 100644
--- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
@@ -25,14 +25,17 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
private readonly ILogger _logger;
- protected BaseVideoResolver(ILogger logger, NamingOptions namingOptions)
+ protected BaseVideoResolver(ILogger logger, NamingOptions namingOptions, IDirectoryService directoryService)
{
_logger = logger;
NamingOptions = namingOptions;
+ DirectoryService = directoryService;
}
protected NamingOptions NamingOptions { get; }
+ protected IDirectoryService DirectoryService { get; }
+
///
/// Resolves the specified args.
///
@@ -65,7 +68,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
var filename = child.Name;
if (child.IsDirectory)
{
- if (IsDvdDirectory(child.FullName, filename, args.DirectoryService))
+ if (IsDvdDirectory(child.FullName, filename, DirectoryService))
{
videoType = VideoType.Dvd;
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs
index 30c52e19d3..0b255f673b 100644
--- a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs
@@ -4,6 +4,7 @@ using System.IO;
using Emby.Naming.Common;
using Emby.Naming.Video;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;
@@ -25,11 +26,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
///
/// The logger.
/// An instance of .
- public ExtraResolver(ILogger logger, NamingOptions namingOptions)
+ /// The directory service.
+ public ExtraResolver(ILogger logger, NamingOptions namingOptions, IDirectoryService directoryService)
{
_namingOptions = namingOptions;
- _trailerResolvers = new IItemResolver[] { new GenericVideoResolver(logger, namingOptions) };
- _videoResolvers = new IItemResolver[] { new GenericVideoResolver
/// The app paths.
- /// The directory service.
/// The library manager.
- public ItemResolveArgs(IServerApplicationPaths appPaths, IDirectoryService directoryService, ILibraryManager libraryManager)
+ public ItemResolveArgs(IServerApplicationPaths appPaths, ILibraryManager libraryManager)
{
_appPaths = appPaths;
- DirectoryService = directoryService;
_libraryManager = libraryManager;
}
- // TODO remove dependencies as properties, they should be injected where it makes sense
- public IDirectoryService DirectoryService { get; }
-
///
/// Gets or sets the file system children.
///
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
index 65f19a051a..6d0ed7bbba 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
@@ -22,10 +22,9 @@ namespace Jellyfin.Server.Implementations.Tests.Library
{
var parent = new Folder { Name = "extras" };
- var episodeResolver = new EpisodeResolver(Mock.Of>(), _namingOptions);
+ var episodeResolver = new EpisodeResolver(Mock.Of>(), _namingOptions, Mock.Of());
var itemResolveArgs = new ItemResolveArgs(
Mock.Of(),
- Mock.Of(),
null)
{
Parent = parent,
@@ -46,10 +45,9 @@ namespace Jellyfin.Server.Implementations.Tests.Library
// Have to create a mock because of moq proxies not being castable to a concrete implementation
// https://github.com/jellyfin/jellyfin/blob/ab0cff8556403e123642dc9717ba778329554634/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs#L48
- var episodeResolver = new EpisodeResolverMock(Mock.Of>(), _namingOptions);
+ var episodeResolver = new EpisodeResolverMock(Mock.Of>(), _namingOptions, Mock.Of());
var itemResolveArgs = new ItemResolveArgs(
Mock.Of(),
- Mock.Of(),
null)
{
Parent = series,
@@ -64,7 +62,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library
private sealed class EpisodeResolverMock : EpisodeResolver
{
- public EpisodeResolverMock(ILogger logger, NamingOptions namingOptions) : base(logger, namingOptions)
+ public EpisodeResolverMock(ILogger logger, NamingOptions namingOptions, IDirectoryService directoryService) : base(logger, namingOptions, directoryService)
{
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs
index f96bd6138e..aed584355c 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs
@@ -18,10 +18,9 @@ public class MovieResolverTests
[Fact]
public void Resolve_GivenLocalAlternateVersion_ResolvesToVideo()
{
- var movieResolver = new MovieResolver(Mock.Of(), Mock.Of>(), _namingOptions);
+ var movieResolver = new MovieResolver(Mock.Of(), Mock.Of>(), _namingOptions, Mock.Of());
var itemResolveArgs = new ItemResolveArgs(
Mock.Of(),
- Mock.Of(),
null)
{
Parent = null,
From 18b8efa7e0c532ce51a0d72a6d8690f8f4a3f302 Mon Sep 17 00:00:00 2001
From: Joe Rogers <1337joe@gmail.com>
Date: Mon, 6 Mar 2023 23:22:37 -0500
Subject: [PATCH 3/4] Add tests for audio book resolving
---
.../Library/AudioResolverTests.cs | 73 +++++++++++++++++++
1 file changed, 73 insertions(+)
create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs
new file mode 100644
index 0000000000..ca5829f10a
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs
@@ -0,0 +1,73 @@
+using System.Linq;
+using Emby.Naming.Common;
+using Emby.Server.Implementations.Library.Resolvers.Audio;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.IO;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Library;
+
+public class AudioResolverTests
+{
+ private static readonly NamingOptions _namingOptions = new();
+
+ [Theory]
+ [InlineData("words.mp3")] // single non-tagged file
+ [InlineData("chapter 01.mp3")]
+ [InlineData("part 1.mp3")]
+ [InlineData("chapter 01.mp3", "non-media.txt")]
+ [InlineData("title.mp3", "title.epub")]
+ [InlineData("01.mp3", "subdirectory/")] // single media file with sub-directory - note that this will hide any contents in the subdirectory
+ public void Resolve_AudiobookDirectory_SingleResult(params string[] children)
+ {
+ var resolved = TestResolveChildren("/parent/title", children);
+ Assert.NotNull(resolved);
+ }
+
+ [Theory]
+ /* Results that can't be displayed as an audio book. */
+ [InlineData] // no contents
+ [InlineData("subdirectory/")]
+ [InlineData("non-media.txt")]
+ /* Names don't indicate parts of a single book. */
+ [InlineData("Name.mp3", "Another Name.mp3")]
+ /* Results that are an audio book but not currently navigable as such (multiple chapters and/or parts). */
+ [InlineData("01.mp3", "02.mp3")]
+ [InlineData("chapter 01.mp3", "chapter 02.mp3")]
+ [InlineData("part 1.mp3", "part 2.mp3")]
+ [InlineData("chapter 01 part 01.mp3", "chapter 01 part 02.mp3")]
+ /* Mismatched chapters, parts, and named files. */
+ [InlineData("chapter 01.mp3", "part 2.mp3")]
+ public void Resolve_AudiobookDirectory_NoResult(params string[] children)
+ {
+ var resolved = TestResolveChildren("/parent/book title", children);
+ Assert.Null(resolved);
+ }
+
+ private Audio? TestResolveChildren(string parent, string[] children)
+ {
+ var childrenMetadata = children.Select(name => new FileSystemMetadata
+ {
+ FullName = parent + "/" + name,
+ IsDirectory = name.EndsWith('/')
+ }).ToArray();
+
+ var resolver = new AudioResolver(_namingOptions);
+ var itemResolveArgs = new ItemResolveArgs(
+ null,
+ Mock.Of())
+ {
+ CollectionType = "books",
+ FileInfo = new FileSystemMetadata
+ {
+ FullName = parent,
+ IsDirectory = true
+ },
+ FileSystemChildren = childrenMetadata
+ };
+
+ return resolver.Resolve(itemResolveArgs);
+ }
+}
From 361fff3a0c1b2e5c14e991d53f5736e909b889b6 Mon Sep 17 00:00:00 2001
From: Joe Rogers <1337joe@gmail.com>
Date: Mon, 6 Mar 2023 23:27:21 -0500
Subject: [PATCH 4/4] Fix cases where multiple files are resolved as a single
book
---
.../Library/Resolvers/Audio/AudioResolver.cs | 3 ++-
.../Library/AudioResolverTests.cs | 3 +++
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
index 69e9057984..a74f824752 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
@@ -192,7 +192,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
continue;
}
- if (resolvedItem.Files.Count == 0)
+ // Until multi-part books are handled letting files stack hides them from browsing in the client
+ if (resolvedItem.Files.Count == 0 || resolvedItem.Extras.Count > 0 || resolvedItem.AlternateVersions.Count > 0)
{
continue;
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs
index ca5829f10a..d136c1bc68 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs
@@ -40,6 +40,9 @@ public class AudioResolverTests
[InlineData("chapter 01 part 01.mp3", "chapter 01 part 02.mp3")]
/* Mismatched chapters, parts, and named files. */
[InlineData("chapter 01.mp3", "part 2.mp3")]
+ [InlineData("book title.mp3", "chapter name.mp3")] // "book title" resolves as alternate version of book based on directory name
+ [InlineData("01 Content.mp3", "01 Credits.mp3")] // resolves as alternate versions of chapter 1
+ [InlineData("Chapter Name.mp3", "Part 1.mp3")]
public void Resolve_AudiobookDirectory_NoResult(params string[] children)
{
var resolved = TestResolveChildren("/parent/book title", children);