Browse Source

Naming refactoring and WIP porting of new interface repositories

JPVenson 8 months ago
parent
commit
be48cdd9e9
32 changed files with 601 additions and 367 deletions
  1. 7 6
      Emby.Server.Implementations/ApplicationHost.cs
  2. 139 0
      Emby.Server.Implementations/Data/ItemTypeLookup.cs
  3. 2 2
      Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
  4. 1 1
      Jellyfin.Data/Entities/AncestorId.cs
  5. 1 1
      Jellyfin.Data/Entities/AttachmentStreamInfo.cs
  6. 2 1
      Jellyfin.Data/Entities/BaseItemEntity.cs
  7. 20 3
      Jellyfin.Data/Entities/BaseItemProvider.cs
  8. 1 1
      Jellyfin.Data/Entities/Chapter.cs
  9. 22 1
      Jellyfin.Data/Entities/ItemValue.cs
  10. 1 1
      Jellyfin.Data/Entities/MediaStreamInfo.cs
  11. 32 2
      Jellyfin.Data/Entities/People.cs
  12. 147 247
      Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
  13. 40 16
      Jellyfin.Server.Implementations/Item/ChapterRepository.cs
  14. 1 1
      Jellyfin.Server.Implementations/Item/MediaAttachmentRepository.cs
  15. 5 5
      Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs
  16. 4 3
      Jellyfin.Server.Implementations/Item/PeopleRepository.cs
  17. 1 1
      Jellyfin.Server.Implementations/JellyfinDbContext.cs
  18. 2 2
      Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs
  19. 1 1
      Jellyfin.Server.Implementations/ModelConfiguration/BaseItemProviderConfiguration.cs
  20. 0 24
      MediaBrowser.Controller/Chapters/ChapterManager.cs
  21. 0 35
      MediaBrowser.Controller/Chapters/IChapterManager.cs
  22. 49 0
      MediaBrowser.Controller/Chapters/IChapterRepository.cs
  23. 25 0
      MediaBrowser.Controller/Drawing/IImageProcessor.cs
  24. 5 2
      MediaBrowser.Controller/Entities/BaseItem.cs
  25. 0 1
      MediaBrowser.Controller/Persistence/IItemRepository.cs
  26. 57 0
      MediaBrowser.Controller/Persistence/IItemTypeLookup.cs
  27. 1 2
      MediaBrowser.Controller/Persistence/IMediaAttachmentRepository.cs
  28. 5 2
      MediaBrowser.Controller/Persistence/IMediaStreamRepository.cs
  29. 1 2
      MediaBrowser.Controller/Persistence/IPeopleRepository.cs
  30. 2 2
      MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
  31. 2 2
      MediaBrowser.Providers/MediaInfo/ProbeProvider.cs
  32. 25 0
      src/Jellyfin.Drawing/ImageProcessor.cs

+ 7 - 6
Emby.Server.Implementations/ApplicationHost.cs

@@ -40,6 +40,7 @@ using Jellyfin.MediaEncoding.Hls.Playlist;
 using Jellyfin.Networking.Manager;
 using Jellyfin.Networking.Manager;
 using Jellyfin.Networking.Udp;
 using Jellyfin.Networking.Udp;
 using Jellyfin.Server.Implementations;
 using Jellyfin.Server.Implementations;
+using Jellyfin.Server.Implementations.Item;
 using Jellyfin.Server.Implementations.MediaSegments;
 using Jellyfin.Server.Implementations.MediaSegments;
 using MediaBrowser.Common;
 using MediaBrowser.Common;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
@@ -83,7 +84,6 @@ using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.Tasks;
 using MediaBrowser.Model.Tasks;
-using MediaBrowser.Providers.Chapters;
 using MediaBrowser.Providers.Lyric;
 using MediaBrowser.Providers.Lyric;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Plugins.Tmdb;
 using MediaBrowser.Providers.Plugins.Tmdb;
@@ -494,7 +494,12 @@ namespace Emby.Server.Implementations
 
 
             serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
             serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
 
 
-            serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
+            serviceCollection.AddSingleton<IItemRepository, BaseItemRepository>();
+            serviceCollection.AddSingleton<IPeopleRepository, PeopleRepository>();
+            serviceCollection.AddSingleton<IChapterRepository, ChapterRepository>();
+            serviceCollection.AddSingleton<IMediaAttachmentRepository, MediaAttachmentRepository>();
+            serviceCollection.AddSingleton<IMediaStreamRepository, MediaStreamRepository>();
+            serviceCollection.AddSingleton<IItemTypeLookup, ItemTypeLookup>();
 
 
             serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
             serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
             serviceCollection.AddSingleton<EncodingHelper>();
             serviceCollection.AddSingleton<EncodingHelper>();
@@ -539,8 +544,6 @@ namespace Emby.Server.Implementations
 
 
             serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
             serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
 
 
-            serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
-
             serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
             serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
 
 
             serviceCollection.AddSingleton<IAuthService, AuthService>();
             serviceCollection.AddSingleton<IAuthService, AuthService>();
@@ -578,8 +581,6 @@ namespace Emby.Server.Implementations
                 }
                 }
             }
             }
 
 
-            ((SqliteItemRepository)Resolve<IItemRepository>()).Initialize();
-
             var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>();
             var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>();
             await localizationManager.LoadAll().ConfigureAwait(false);
             await localizationManager.LoadAll().ConfigureAwait(false);
 
 

+ 139 - 0
Emby.Server.Implementations/Data/ItemTypeLookup.cs

@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Channels;
+using Emby.Server.Implementations.Playlists;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Playlists;
+using MediaBrowser.Model.Querying;
+
+namespace Jellyfin.Server.Implementations.Item;
+
+/// <summary>
+/// Provides static topic based lookups for the BaseItemKind.
+/// </summary>
+public class ItemTypeLookup : IItemTypeLookup
+{
+    /// <summary>
+    /// Gets all values of the ItemFields type.
+    /// </summary>
+    public IReadOnlyList<ItemFields> AllItemFields { get; } = Enum.GetValues<ItemFields>();
+
+    /// <summary>
+    /// Gets all BaseItemKinds that are considered Programs.
+    /// </summary>
+    public IReadOnlyList<BaseItemKind> ProgramTypes { get; } =
+    [
+            BaseItemKind.Program,
+            BaseItemKind.TvChannel,
+            BaseItemKind.LiveTvProgram,
+            BaseItemKind.LiveTvChannel
+    ];
+
+    /// <summary>
+    /// Gets all BaseItemKinds that should be excluded from parent lookup.
+    /// </summary>
+    public IReadOnlyList<BaseItemKind> ProgramExcludeParentTypes { get; } =
+    [
+            BaseItemKind.Series,
+            BaseItemKind.Season,
+            BaseItemKind.MusicAlbum,
+            BaseItemKind.MusicArtist,
+            BaseItemKind.PhotoAlbum
+    ];
+
+    /// <summary>
+    /// Gets all BaseItemKinds that are considered to be provided by services.
+    /// </summary>
+    public IReadOnlyList<BaseItemKind> ServiceTypes { get; } =
+    [
+            BaseItemKind.TvChannel,
+            BaseItemKind.LiveTvChannel
+    ];
+
+    /// <summary>
+    /// Gets all BaseItemKinds that have a StartDate.
+    /// </summary>
+    public IReadOnlyList<BaseItemKind> StartDateTypes { get; } =
+    [
+            BaseItemKind.Program,
+            BaseItemKind.LiveTvProgram
+    ];
+
+    /// <summary>
+    /// Gets all BaseItemKinds that are considered Series.
+    /// </summary>
+    public IReadOnlyList<BaseItemKind> SeriesTypes { get; } =
+    [
+            BaseItemKind.Book,
+            BaseItemKind.AudioBook,
+            BaseItemKind.Episode,
+            BaseItemKind.Season
+    ];
+
+    /// <summary>
+    /// Gets all BaseItemKinds that are not to be evaluated for Artists.
+    /// </summary>
+    public IReadOnlyList<BaseItemKind> ArtistExcludeParentTypes { get; } =
+    [
+            BaseItemKind.Series,
+            BaseItemKind.Season,
+            BaseItemKind.PhotoAlbum
+    ];
+
+    /// <summary>
+    /// Gets all BaseItemKinds that are considered Artists.
+    /// </summary>
+    public IReadOnlyList<BaseItemKind> ArtistsTypes { get; } =
+    [
+            BaseItemKind.Audio,
+            BaseItemKind.MusicAlbum,
+            BaseItemKind.MusicVideo,
+            BaseItemKind.AudioBook
+    ];
+
+    /// <summary>
+    /// Gets mapping for all BaseItemKinds and their expected serialisaition target.
+    /// </summary>
+    public IDictionary<BaseItemKind, string?> BaseItemKindNames { get; } = new Dictionary<BaseItemKind, string?>()
+    {
+        { BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName },
+        { BaseItemKind.Audio, typeof(Audio).FullName },
+        { BaseItemKind.AudioBook, typeof(AudioBook).FullName },
+        { BaseItemKind.BasePluginFolder, typeof(BasePluginFolder).FullName },
+        { BaseItemKind.Book, typeof(Book).FullName },
+        { BaseItemKind.BoxSet, typeof(BoxSet).FullName },
+        { BaseItemKind.Channel, typeof(Channel).FullName },
+        { BaseItemKind.CollectionFolder, typeof(CollectionFolder).FullName },
+        { BaseItemKind.Episode, typeof(Episode).FullName },
+        { BaseItemKind.Folder, typeof(Folder).FullName },
+        { BaseItemKind.Genre, typeof(Genre).FullName },
+        { BaseItemKind.Movie, typeof(Movie).FullName },
+        { BaseItemKind.LiveTvChannel, typeof(LiveTvChannel).FullName },
+        { BaseItemKind.LiveTvProgram, typeof(LiveTvProgram).FullName },
+        { BaseItemKind.MusicAlbum, typeof(MusicAlbum).FullName },
+        { BaseItemKind.MusicArtist, typeof(MusicArtist).FullName },
+        { BaseItemKind.MusicGenre, typeof(MusicGenre).FullName },
+        { BaseItemKind.MusicVideo, typeof(MusicVideo).FullName },
+        { BaseItemKind.Person, typeof(Person).FullName },
+        { BaseItemKind.Photo, typeof(Photo).FullName },
+        { BaseItemKind.PhotoAlbum, typeof(PhotoAlbum).FullName },
+        { BaseItemKind.Playlist, typeof(Playlist).FullName },
+        { BaseItemKind.PlaylistsFolder, typeof(PlaylistsFolder).FullName },
+        { BaseItemKind.Season, typeof(Season).FullName },
+        { BaseItemKind.Series, typeof(Series).FullName },
+        { BaseItemKind.Studio, typeof(Studio).FullName },
+        { BaseItemKind.Trailer, typeof(Trailer).FullName },
+        { BaseItemKind.TvChannel, typeof(LiveTvChannel).FullName },
+        { BaseItemKind.TvProgram, typeof(LiveTvProgram).FullName },
+        { BaseItemKind.UserRootFolder, typeof(UserRootFolder).FullName },
+        { BaseItemKind.UserView, typeof(UserView).FullName },
+        { BaseItemKind.Video, typeof(Video).FullName },
+        { BaseItemKind.Year, typeof(Year).FullName }
+    }.AsReadOnly();
+}

+ 2 - 2
Emby.Server.Implementations/MediaEncoder/EncodingManager.cs

@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.MediaEncoder
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
         private readonly ILogger<EncodingManager> _logger;
         private readonly ILogger<EncodingManager> _logger;
         private readonly IMediaEncoder _encoder;
         private readonly IMediaEncoder _encoder;
-        private readonly IChapterManager _chapterManager;
+        private readonly IChapterRepository _chapterManager;
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
 
 
         /// <summary>
         /// <summary>
@@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.MediaEncoder
             ILogger<EncodingManager> logger,
             ILogger<EncodingManager> logger,
             IFileSystem fileSystem,
             IFileSystem fileSystem,
             IMediaEncoder encoder,
             IMediaEncoder encoder,
-            IChapterManager chapterManager,
+            IChapterRepository chapterManager,
             ILibraryManager libraryManager)
             ILibraryManager libraryManager)
         {
         {
             _logger = logger;
             _logger = logger;

+ 1 - 1
Jellyfin.Data/Entities/AncestorId.cs

@@ -13,7 +13,7 @@ public class AncestorId
 
 
     public Guid ItemId { get; set; }
     public Guid ItemId { get; set; }
 
 
-    public required BaseItem Item { get; set; }
+    public required BaseItemEntity Item { get; set; }
 
 
     public string? AncestorIdText { get; set; }
     public string? AncestorIdText { get; set; }
 }
 }

+ 1 - 1
Jellyfin.Data/Entities/AttachmentStreamInfo.cs

@@ -7,7 +7,7 @@ public class AttachmentStreamInfo
 {
 {
     public required Guid ItemId { get; set; }
     public required Guid ItemId { get; set; }
 
 
-    public required BaseItem Item { get; set; }
+    public required BaseItemEntity Item { get; set; }
 
 
     public required int Index { get; set; }
     public required int Index { get; set; }
 
 

+ 2 - 1
Jellyfin.Data/Entities/BaseItem.cs → Jellyfin.Data/Entities/BaseItemEntity.cs

@@ -6,7 +6,7 @@ using System.ComponentModel.DataAnnotations.Schema;
 namespace Jellyfin.Data.Entities;
 namespace Jellyfin.Data.Entities;
 
 
 #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
 #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
-public class BaseItem
+public class BaseItemEntity
 {
 {
     [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
     [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
 
 
@@ -160,6 +160,7 @@ public class BaseItem
 
 
     public long? Size { get; set; }
     public long? Size { get; set; }
 
 
+#pragma warning disable CA2227 // Collection properties should be read only
     public ICollection<People>? Peoples { get; set; }
     public ICollection<People>? Peoples { get; set; }
 
 
     public ICollection<UserData>? UserData { get; set; }
     public ICollection<UserData>? UserData { get; set; }

+ 20 - 3
Jellyfin.Data/Entities/BaseItemProvider.cs

@@ -5,11 +5,28 @@ using System.ComponentModel.DataAnnotations.Schema;
 
 
 namespace Jellyfin.Data.Entities;
 namespace Jellyfin.Data.Entities;
 
 
+/// <summary>
+/// Represents an Key-Value relaten of an BaseItem's provider.
+/// </summary>
 public class BaseItemProvider
 public class BaseItemProvider
 {
 {
+    /// <summary>
+    /// Gets or Sets the reference ItemId.
+    /// </summary>
     public Guid ItemId { get; set; }
     public Guid ItemId { get; set; }
-    public required BaseItem Item { get; set; }
 
 
-    public string ProviderId { get; set; }
-    public string ProviderValue { get; set; }
+    /// <summary>
+    /// Gets or Sets the reference BaseItem.
+    /// </summary>
+    public required BaseItemEntity Item { get; set; }
+
+    /// <summary>
+    /// Gets or Sets the ProvidersId.
+    /// </summary>
+    public required string ProviderId { get; set; }
+
+    /// <summary>
+    /// Gets or Sets the Providers Value.
+    /// </summary>
+    public required string ProviderValue { get; set; }
 }
 }

+ 1 - 1
Jellyfin.Data/Entities/Chapter.cs

@@ -10,7 +10,7 @@ public class Chapter
 {
 {
     public Guid ItemId { get; set; }
     public Guid ItemId { get; set; }
 
 
-    public required BaseItem Item { get; set; }
+    public required BaseItemEntity Item { get; set; }
 
 
     public required int ChapterIndex { get; set; }
     public required int ChapterIndex { get; set; }
 
 

+ 22 - 1
Jellyfin.Data/Entities/ItemValue.cs

@@ -5,12 +5,33 @@ using System.ComponentModel.DataAnnotations.Schema;
 
 
 namespace Jellyfin.Data.Entities;
 namespace Jellyfin.Data.Entities;
 
 
+/// <summary>
+/// Represents an ItemValue for a BaseItem.
+/// </summary>
 public class ItemValue
 public class ItemValue
 {
 {
+    /// <summary>
+    /// Gets or Sets the reference ItemId.
+    /// </summary>
     public Guid ItemId { get; set; }
     public Guid ItemId { get; set; }
-    public required BaseItem Item { get; set; }
 
 
+    /// <summary>
+    /// Gets or Sets the referenced BaseItem.
+    /// </summary>
+    public required BaseItemEntity Item { get; set; }
+
+    /// <summary>
+    /// Gets or Sets the Type.
+    /// </summary>
     public required int Type { get; set; }
     public required int Type { get; set; }
+
+    /// <summary>
+    /// Gets or Sets the Value.
+    /// </summary>
     public required string Value { get; set; }
     public required string Value { get; set; }
+
+    /// <summary>
+    /// Gets or Sets the sanatised Value.
+    /// </summary>
     public required string CleanValue { get; set; }
     public required string CleanValue { get; set; }
 }
 }

+ 1 - 1
Jellyfin.Data/Entities/MediaStreamInfo.cs

@@ -7,7 +7,7 @@ public class MediaStreamInfo
 {
 {
     public Guid ItemId { get; set; }
     public Guid ItemId { get; set; }
 
 
-    public required BaseItem Item { get; set; }
+    public required BaseItemEntity Item { get; set; }
 
 
     public int StreamIndex { get; set; }
     public int StreamIndex { get; set; }
 
 

+ 32 - 2
Jellyfin.Data/Entities/People.cs

@@ -4,14 +4,44 @@ using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.ComponentModel.DataAnnotations.Schema;
 
 
 namespace Jellyfin.Data.Entities;
 namespace Jellyfin.Data.Entities;
+
+/// <summary>
+/// People entity.
+/// </summary>
 public class People
 public class People
 {
 {
-    public Guid ItemId { get; set; }
-    public BaseItem Item { get; set; }
+    /// <summary>
+    /// Gets or Sets The ItemId.
+    /// </summary>
+    public required Guid ItemId { get; set; }
+
+    /// <summary>
+    /// Gets or Sets Reference Item.
+    /// </summary>
+    public required BaseItemEntity Item { get; set; }
 
 
+    /// <summary>
+    /// Gets or Sets the Persons Name.
+    /// </summary>
     public required string Name { get; set; }
     public required string Name { get; set; }
+
+    /// <summary>
+    /// Gets or Sets the Role.
+    /// </summary>
     public string? Role { get; set; }
     public string? Role { get; set; }
+
+    /// <summary>
+    /// Gets or Sets the Type.
+    /// </summary>
     public string? PersonType { get; set; }
     public string? PersonType { get; set; }
+
+    /// <summary>
+    /// Gets or Sets the SortOrder.
+    /// </summary>
     public int? SortOrder { get; set; }
     public int? SortOrder { get; set; }
+
+    /// <summary>
+    /// Gets or Sets the ListOrder.
+    /// </summary>
     public int? ListOrder { get; set; }
     public int? ListOrder { get; set; }
 }
 }

+ 147 - 247
Jellyfin.Server.Implementations/Item/BaseItemManager.cs → Jellyfin.Server.Implementations/Item/BaseItemRepository.cs

@@ -24,112 +24,23 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Internal;
-using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
 using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem;
 using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem;
-using BaseItemEntity = Jellyfin.Data.Entities.BaseItem;
+using BaseItemEntity = Jellyfin.Data.Entities.BaseItemEntity;
 
 
 namespace Jellyfin.Server.Implementations.Item;
 namespace Jellyfin.Server.Implementations.Item;
 
 
 /// <summary>
 /// <summary>
 /// Handles all storage logic for BaseItems.
 /// Handles all storage logic for BaseItems.
 /// </summary>
 /// </summary>
-public sealed class BaseItemManager : IItemRepository, IDisposable
+/// <remarks>
+/// Initializes a new instance of the <see cref="BaseItemRepository"/> class.
+/// </remarks>
+/// <param name="dbProvider">The db factory.</param>
+/// <param name="appHost">The Application host.</param>
+/// <param name="itemTypeLookup">The static type lookup.</param>
+public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbProvider, IServerApplicationHost appHost, IItemTypeLookup itemTypeLookup)
+    : IItemRepository, IDisposable
 {
 {
-    private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
-    private readonly IServerApplicationHost _appHost;
-
-    private readonly ItemFields[] _allItemFields = Enum.GetValues<ItemFields>();
-
-    private static readonly BaseItemKind[] _programTypes = new[]
-    {
-            BaseItemKind.Program,
-            BaseItemKind.TvChannel,
-            BaseItemKind.LiveTvProgram,
-            BaseItemKind.LiveTvChannel
-    };
-
-    private static readonly BaseItemKind[] _programExcludeParentTypes = new[]
-    {
-            BaseItemKind.Series,
-            BaseItemKind.Season,
-            BaseItemKind.MusicAlbum,
-            BaseItemKind.MusicArtist,
-            BaseItemKind.PhotoAlbum
-    };
-
-    private static readonly BaseItemKind[] _serviceTypes = new[]
-    {
-            BaseItemKind.TvChannel,
-            BaseItemKind.LiveTvChannel
-    };
-
-    private static readonly BaseItemKind[] _startDateTypes = new[]
-    {
-            BaseItemKind.Program,
-            BaseItemKind.LiveTvProgram
-    };
-
-    private static readonly BaseItemKind[] _seriesTypes = new[]
-    {
-            BaseItemKind.Book,
-            BaseItemKind.AudioBook,
-            BaseItemKind.Episode,
-            BaseItemKind.Season
-    };
-
-    private static readonly BaseItemKind[] _artistExcludeParentTypes = new[]
-    {
-            BaseItemKind.Series,
-            BaseItemKind.Season,
-            BaseItemKind.PhotoAlbum
-    };
-
-    private static readonly BaseItemKind[] _artistsTypes = new[]
-    {
-            BaseItemKind.Audio,
-            BaseItemKind.MusicAlbum,
-            BaseItemKind.MusicVideo,
-            BaseItemKind.AudioBook
-    };
-
-    private static readonly Dictionary<BaseItemKind, string?> _baseItemKindNames = new()
-        {
-            { BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName },
-            { BaseItemKind.Audio, typeof(Audio).FullName },
-            { BaseItemKind.AudioBook, typeof(AudioBook).FullName },
-            { BaseItemKind.BasePluginFolder, typeof(BasePluginFolder).FullName },
-            { BaseItemKind.Book, typeof(Book).FullName },
-            { BaseItemKind.BoxSet, typeof(BoxSet).FullName },
-            { BaseItemKind.Channel, typeof(Channel).FullName },
-            { BaseItemKind.CollectionFolder, typeof(CollectionFolder).FullName },
-            { BaseItemKind.Episode, typeof(Episode).FullName },
-            { BaseItemKind.Folder, typeof(Folder).FullName },
-            { BaseItemKind.Genre, typeof(Genre).FullName },
-            { BaseItemKind.Movie, typeof(Movie).FullName },
-            { BaseItemKind.LiveTvChannel, typeof(LiveTvChannel).FullName },
-            { BaseItemKind.LiveTvProgram, typeof(LiveTvProgram).FullName },
-            { BaseItemKind.MusicAlbum, typeof(MusicAlbum).FullName },
-            { BaseItemKind.MusicArtist, typeof(MusicArtist).FullName },
-            { BaseItemKind.MusicGenre, typeof(MusicGenre).FullName },
-            { BaseItemKind.MusicVideo, typeof(MusicVideo).FullName },
-            { BaseItemKind.Person, typeof(Person).FullName },
-            { BaseItemKind.Photo, typeof(Photo).FullName },
-            { BaseItemKind.PhotoAlbum, typeof(PhotoAlbum).FullName },
-            { BaseItemKind.Playlist, typeof(Playlist).FullName },
-            { BaseItemKind.PlaylistsFolder, typeof(PlaylistsFolder).FullName },
-            { BaseItemKind.Season, typeof(Season).FullName },
-            { BaseItemKind.Series, typeof(Series).FullName },
-            { BaseItemKind.Studio, typeof(Studio).FullName },
-            { BaseItemKind.Trailer, typeof(Trailer).FullName },
-            { BaseItemKind.TvChannel, typeof(LiveTvChannel).FullName },
-            { BaseItemKind.TvProgram, typeof(LiveTvProgram).FullName },
-            { BaseItemKind.UserRootFolder, typeof(UserRootFolder).FullName },
-            { BaseItemKind.UserView, typeof(UserView).FullName },
-            { BaseItemKind.Video, typeof(Video).FullName },
-            { BaseItemKind.Year, typeof(Year).FullName }
-        };
-
     /// <summary>
     /// <summary>
     /// This holds all the types in the running assemblies
     /// This holds all the types in the running assemblies
     /// so that we can de-serialize properly when we don't have strong types.
     /// so that we can de-serialize properly when we don't have strong types.
@@ -137,17 +48,6 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
     private static readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>();
     private static readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>();
     private bool _disposed;
     private bool _disposed;
 
 
-    /// <summary>
-    /// Initializes a new instance of the <see cref="BaseItemManager"/> class.
-    /// </summary>
-    /// <param name="dbProvider">The db factory.</param>
-    /// <param name="appHost">The Application host.</param>
-    public BaseItemManager(IDbContextFactory<JellyfinDbContext> dbProvider, IServerApplicationHost appHost)
-    {
-        _dbProvider = dbProvider;
-        _appHost = appHost;
-    }
-
     /// <inheritdoc/>
     /// <inheritdoc/>
     public void Dispose()
     public void Dispose()
     {
     {
@@ -159,124 +59,12 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
         _disposed = true;
         _disposed = true;
     }
     }
 
 
-    private QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetItemValues(InternalItemsQuery filter, int[] itemValueTypes, string returnType)
-    {
-        ArgumentNullException.ThrowIfNull(filter);
-
-        if (!filter.Limit.HasValue)
-        {
-            filter.EnableTotalRecordCount = false;
-        }
-
-        using var context = _dbProvider.CreateDbContext();
-
-        var innerQuery = new InternalItemsQuery(filter.User)
-        {
-            ExcludeItemTypes = filter.ExcludeItemTypes,
-            IncludeItemTypes = filter.IncludeItemTypes,
-            MediaTypes = filter.MediaTypes,
-            AncestorIds = filter.AncestorIds,
-            ItemIds = filter.ItemIds,
-            TopParentIds = filter.TopParentIds,
-            ParentId = filter.ParentId,
-            IsAiring = filter.IsAiring,
-            IsMovie = filter.IsMovie,
-            IsSports = filter.IsSports,
-            IsKids = filter.IsKids,
-            IsNews = filter.IsNews,
-            IsSeries = filter.IsSeries
-        };
-        var query = TranslateQuery(context.BaseItems, context, innerQuery);
-
-        query = query.Where(e => e.Type == returnType && e.ItemValues!.Any(f => e.CleanName == f.CleanValue && itemValueTypes.Contains(f.Type)));
-
-        var outerQuery = new InternalItemsQuery(filter.User)
-        {
-            IsPlayed = filter.IsPlayed,
-            IsFavorite = filter.IsFavorite,
-            IsFavoriteOrLiked = filter.IsFavoriteOrLiked,
-            IsLiked = filter.IsLiked,
-            IsLocked = filter.IsLocked,
-            NameLessThan = filter.NameLessThan,
-            NameStartsWith = filter.NameStartsWith,
-            NameStartsWithOrGreater = filter.NameStartsWithOrGreater,
-            Tags = filter.Tags,
-            OfficialRatings = filter.OfficialRatings,
-            StudioIds = filter.StudioIds,
-            GenreIds = filter.GenreIds,
-            Genres = filter.Genres,
-            Years = filter.Years,
-            NameContains = filter.NameContains,
-            SearchTerm = filter.SearchTerm,
-            SimilarTo = filter.SimilarTo,
-            ExcludeItemIds = filter.ExcludeItemIds
-        };
-        query = TranslateQuery(query, context, outerQuery)
-            .OrderBy(e => e.PresentationUniqueKey);
-
-        if (filter.OrderBy.Count != 0
-            || filter.SimilarTo is not null
-            || !string.IsNullOrEmpty(filter.SearchTerm))
-        {
-            query = ApplyOrder(query, filter);
-        }
-        else
-        {
-            query = query.OrderBy(e => e.SortName);
-        }
-
-        if (filter.Limit.HasValue || filter.StartIndex.HasValue)
-        {
-            var offset = filter.StartIndex ?? 0;
-
-            if (offset > 0)
-            {
-                query = query.Skip(offset);
-            }
-
-            if (filter.Limit.HasValue)
-            {
-                query.Take(filter.Limit.Value);
-            }
-        }
-
-        var result = new QueryResult<(BaseItem, ItemCounts)>();
-        string countText = string.Empty;
-        if (filter.EnableTotalRecordCount)
-        {
-            result.TotalRecordCount = query.DistinctBy(e => e.PresentationUniqueKey).Count();
-        }
-
-        var resultQuery = query.Select(e => new
-        {
-            item = e,
-            itemCount = new ItemCounts()
-            {
-                SeriesCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Series),
-                EpisodeCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Episode),
-                MovieCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Movie),
-                AlbumCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.MusicAlbum),
-                ArtistCount = e.ItemValues!.Count(e => e.Type == 0 || e.Type == 1),
-                SongCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.MusicAlbum),
-                TrailerCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Trailer),
-            }
-        });
-
-        result.StartIndex = filter.StartIndex ?? 0;
-        result.Items = resultQuery.ToImmutableArray().Select(e =>
-        {
-            return (DeserialiseBaseItem(e.item), e.itemCount);
-        }).ToImmutableArray();
-
-        return result;
-    }
-
     /// <inheritdoc />
     /// <inheritdoc />
     public void DeleteItem(Guid id)
     public void DeleteItem(Guid id)
     {
     {
         ArgumentNullException.ThrowIfNull(id.IsEmpty() ? null : id);
         ArgumentNullException.ThrowIfNull(id.IsEmpty() ? null : id);
 
 
-        using var context = _dbProvider.CreateDbContext();
+        using var context = dbProvider.CreateDbContext();
         using var transaction = context.Database.BeginTransaction();
         using var transaction = context.Database.BeginTransaction();
         context.Peoples.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
         context.Peoples.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
         context.Chapters.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
         context.Chapters.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
@@ -291,7 +79,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
     /// <inheritdoc />
     /// <inheritdoc />
     public void UpdateInheritedValues()
     public void UpdateInheritedValues()
     {
     {
-        using var context = _dbProvider.CreateDbContext();
+        using var context = dbProvider.CreateDbContext();
         using var transaction = context.Database.BeginTransaction();
         using var transaction = context.Database.BeginTransaction();
 
 
         context.ItemValues.Where(e => e.Type == 6).ExecuteDelete();
         context.ItemValues.Where(e => e.Type == 6).ExecuteDelete();
@@ -324,7 +112,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
         ArgumentNullException.ThrowIfNull(filter);
         ArgumentNullException.ThrowIfNull(filter);
         PrepareFilterQuery(filter);
         PrepareFilterQuery(filter);
 
 
-        using var context = _dbProvider.CreateDbContext();
+        using var context = dbProvider.CreateDbContext();
         var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter)
         var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter)
             .DistinctBy(e => e.Id);
             .DistinctBy(e => e.Id);
 
 
@@ -352,56 +140,56 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
     /// <inheritdoc />
     /// <inheritdoc />
     public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery filter)
     public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery filter)
     {
     {
-        return GetItemValues(filter, new[] { 0, 1 }, typeof(MusicArtist).FullName!);
+        return GetItemValues(filter, [0, 1], typeof(MusicArtist).FullName!);
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
     public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery filter)
     public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery filter)
     {
     {
-        return GetItemValues(filter, new[] { 0 }, typeof(MusicArtist).FullName!);
+        return GetItemValues(filter, [0], typeof(MusicArtist).FullName!);
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
     public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery filter)
     public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery filter)
     {
     {
-        return GetItemValues(filter, new[] { 1 }, typeof(MusicArtist).FullName!);
+        return GetItemValues(filter, [1], typeof(MusicArtist).FullName!);
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
     public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery filter)
     public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery filter)
     {
     {
-        return GetItemValues(filter, new[] { 3 }, typeof(Studio).FullName!);
+        return GetItemValues(filter, [3], typeof(Studio).FullName!);
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
     public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery filter)
     public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery filter)
     {
     {
-        return GetItemValues(filter, new[] { 2 }, typeof(Genre).FullName!);
+        return GetItemValues(filter, [2], typeof(Genre).FullName!);
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
     public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery filter)
     public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery filter)
     {
     {
-        return GetItemValues(filter, new[] { 2 }, typeof(MusicGenre).FullName!);
+        return GetItemValues(filter, [2], typeof(MusicGenre).FullName!);
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
     public IReadOnlyList<string> GetStudioNames()
     public IReadOnlyList<string> GetStudioNames()
     {
     {
-        return GetItemValueNames(new[] { 3 }, Array.Empty<string>(), Array.Empty<string>());
+        return GetItemValueNames([3], Array.Empty<string>(), Array.Empty<string>());
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
     public IReadOnlyList<string> GetAllArtistNames()
     public IReadOnlyList<string> GetAllArtistNames()
     {
     {
-        return GetItemValueNames(new[] { 0, 1 }, Array.Empty<string>(), Array.Empty<string>());
+        return GetItemValueNames([0, 1], Array.Empty<string>(), Array.Empty<string>());
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
     public IReadOnlyList<string> GetMusicGenreNames()
     public IReadOnlyList<string> GetMusicGenreNames()
     {
     {
         return GetItemValueNames(
         return GetItemValueNames(
-            new[] { 2 },
+            [2],
             new string[]
             new string[]
             {
             {
                     typeof(Audio).FullName!,
                     typeof(Audio).FullName!,
@@ -416,7 +204,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
     public IReadOnlyList<string> GetGenreNames()
     public IReadOnlyList<string> GetGenreNames()
     {
     {
         return GetItemValueNames(
         return GetItemValueNames(
-            new[] { 2 },
+            [2],
             Array.Empty<string>(),
             Array.Empty<string>(),
             new string[]
             new string[]
             {
             {
@@ -443,7 +231,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
         PrepareFilterQuery(filter);
         PrepareFilterQuery(filter);
         var result = new QueryResult<BaseItemDto>();
         var result = new QueryResult<BaseItemDto>();
 
 
-        using var context = _dbProvider.CreateDbContext();
+        using var context = dbProvider.CreateDbContext();
         var dbQuery = TranslateQuery(context.BaseItems, context, filter)
         var dbQuery = TranslateQuery(context.BaseItems, context, filter)
             .DistinctBy(e => e.Id);
             .DistinctBy(e => e.Id);
         if (filter.EnableTotalRecordCount)
         if (filter.EnableTotalRecordCount)
@@ -477,7 +265,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
         ArgumentNullException.ThrowIfNull(filter);
         ArgumentNullException.ThrowIfNull(filter);
         PrepareFilterQuery(filter);
         PrepareFilterQuery(filter);
 
 
-        using var context = _dbProvider.CreateDbContext();
+        using var context = dbProvider.CreateDbContext();
         var dbQuery = TranslateQuery(context.BaseItems, context, filter)
         var dbQuery = TranslateQuery(context.BaseItems, context, filter)
             .DistinctBy(e => e.Id);
             .DistinctBy(e => e.Id);
         if (filter.Limit.HasValue || filter.StartIndex.HasValue)
         if (filter.Limit.HasValue || filter.StartIndex.HasValue)
@@ -505,7 +293,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
         // Hack for right now since we currently don't support filtering out these duplicates within a query
         // Hack for right now since we currently don't support filtering out these duplicates within a query
         PrepareFilterQuery(filter);
         PrepareFilterQuery(filter);
 
 
-        using var context = _dbProvider.CreateDbContext();
+        using var context = dbProvider.CreateDbContext();
         var dbQuery = TranslateQuery(context.BaseItems, context, filter);
         var dbQuery = TranslateQuery(context.BaseItems, context, filter);
 
 
         return dbQuery.Count();
         return dbQuery.Count();
@@ -646,7 +434,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
             var excludeTypes = filter.ExcludeItemTypes;
             var excludeTypes = filter.ExcludeItemTypes;
             if (excludeTypes.Length == 1)
             if (excludeTypes.Length == 1)
             {
             {
-                if (_baseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName))
+                if (itemTypeLookup.BaseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName))
                 {
                 {
                     baseQuery = baseQuery.Where(e => e.Type != excludeTypeName);
                     baseQuery = baseQuery.Where(e => e.Type != excludeTypeName);
                 }
                 }
@@ -656,7 +444,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
                 var excludeTypeName = new List<string>();
                 var excludeTypeName = new List<string>();
                 foreach (var excludeType in excludeTypes)
                 foreach (var excludeType in excludeTypes)
                 {
                 {
-                    if (_baseItemKindNames.TryGetValue(excludeType, out var baseItemKindName))
+                    if (itemTypeLookup.BaseItemKindNames.TryGetValue(excludeType, out var baseItemKindName))
                     {
                     {
                         excludeTypeName.Add(baseItemKindName!);
                         excludeTypeName.Add(baseItemKindName!);
                     }
                     }
@@ -667,7 +455,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
         }
         }
         else if (includeTypes.Length == 1)
         else if (includeTypes.Length == 1)
         {
         {
-            if (_baseItemKindNames.TryGetValue(includeTypes[0], out var includeTypeName))
+            if (itemTypeLookup.BaseItemKindNames.TryGetValue(includeTypes[0], out var includeTypeName))
             {
             {
                 baseQuery = baseQuery.Where(e => e.Type == includeTypeName);
                 baseQuery = baseQuery.Where(e => e.Type == includeTypeName);
             }
             }
@@ -677,7 +465,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
             var includeTypeName = new List<string>();
             var includeTypeName = new List<string>();
             foreach (var includeType in includeTypes)
             foreach (var includeType in includeTypes)
             {
             {
-                if (_baseItemKindNames.TryGetValue(includeType, out var baseItemKindName))
+                if (itemTypeLookup.BaseItemKindNames.TryGetValue(includeType, out var baseItemKindName))
                 {
                 {
                     includeTypeName.Add(baseItemKindName!);
                     includeTypeName.Add(baseItemKindName!);
                 }
                 }
@@ -1421,7 +1209,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
         ArgumentNullException.ThrowIfNull(item);
         ArgumentNullException.ThrowIfNull(item);
 
 
         var images = SerializeImages(item.ImageInfos);
         var images = SerializeImages(item.ImageInfos);
-        using var db = _dbProvider.CreateDbContext();
+        using var db = dbProvider.CreateDbContext();
 
 
         db.BaseItems
         db.BaseItems
             .Where(e => e.Id.Equals(item.Id))
             .Where(e => e.Id.Equals(item.Id))
@@ -1457,7 +1245,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
             tuples[i] = (item, ancestorIds, topParent, userdataKey, inheritedTags);
             tuples[i] = (item, ancestorIds, topParent, userdataKey, inheritedTags);
         }
         }
 
 
-        using var context = _dbProvider.CreateDbContext();
+        using var context = dbProvider.CreateDbContext();
         foreach (var item in tuples)
         foreach (var item in tuples)
         {
         {
             var entity = Map(item.Item);
             var entity = Map(item.Item);
@@ -1501,7 +1289,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
             throw new ArgumentException("Guid can't be empty", nameof(id));
             throw new ArgumentException("Guid can't be empty", nameof(id));
         }
         }
 
 
-        using var context = _dbProvider.CreateDbContext();
+        using var context = dbProvider.CreateDbContext();
         var item = context.BaseItems.FirstOrDefault(e => e.Id.Equals(id));
         var item = context.BaseItems.FirstOrDefault(e => e.Id.Equals(id));
         if (item is null)
         if (item is null)
         {
         {
@@ -1832,7 +1620,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
 
 
     private IReadOnlyList<string> GetItemValueNames(int[] itemValueTypes, IReadOnlyList<string> withItemTypes, IReadOnlyList<string> excludeItemTypes)
     private IReadOnlyList<string> GetItemValueNames(int[] itemValueTypes, IReadOnlyList<string> withItemTypes, IReadOnlyList<string> excludeItemTypes)
     {
     {
-        using var context = _dbProvider.CreateDbContext();
+        using var context = dbProvider.CreateDbContext();
 
 
         var query = context.ItemValues
         var query = context.ItemValues
             .Where(e => itemValueTypes.Contains(e.Type));
             .Where(e => itemValueTypes.Contains(e.Type));
@@ -1857,6 +1645,118 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
         return Map(baseItemEntity, dto);
         return Map(baseItemEntity, dto);
     }
     }
 
 
+    private QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetItemValues(InternalItemsQuery filter, int[] itemValueTypes, string returnType)
+    {
+        ArgumentNullException.ThrowIfNull(filter);
+
+        if (!filter.Limit.HasValue)
+        {
+            filter.EnableTotalRecordCount = false;
+        }
+
+        using var context = dbProvider.CreateDbContext();
+
+        var innerQuery = new InternalItemsQuery(filter.User)
+        {
+            ExcludeItemTypes = filter.ExcludeItemTypes,
+            IncludeItemTypes = filter.IncludeItemTypes,
+            MediaTypes = filter.MediaTypes,
+            AncestorIds = filter.AncestorIds,
+            ItemIds = filter.ItemIds,
+            TopParentIds = filter.TopParentIds,
+            ParentId = filter.ParentId,
+            IsAiring = filter.IsAiring,
+            IsMovie = filter.IsMovie,
+            IsSports = filter.IsSports,
+            IsKids = filter.IsKids,
+            IsNews = filter.IsNews,
+            IsSeries = filter.IsSeries
+        };
+        var query = TranslateQuery(context.BaseItems, context, innerQuery);
+
+        query = query.Where(e => e.Type == returnType && e.ItemValues!.Any(f => e.CleanName == f.CleanValue && itemValueTypes.Contains(f.Type)));
+
+        var outerQuery = new InternalItemsQuery(filter.User)
+        {
+            IsPlayed = filter.IsPlayed,
+            IsFavorite = filter.IsFavorite,
+            IsFavoriteOrLiked = filter.IsFavoriteOrLiked,
+            IsLiked = filter.IsLiked,
+            IsLocked = filter.IsLocked,
+            NameLessThan = filter.NameLessThan,
+            NameStartsWith = filter.NameStartsWith,
+            NameStartsWithOrGreater = filter.NameStartsWithOrGreater,
+            Tags = filter.Tags,
+            OfficialRatings = filter.OfficialRatings,
+            StudioIds = filter.StudioIds,
+            GenreIds = filter.GenreIds,
+            Genres = filter.Genres,
+            Years = filter.Years,
+            NameContains = filter.NameContains,
+            SearchTerm = filter.SearchTerm,
+            SimilarTo = filter.SimilarTo,
+            ExcludeItemIds = filter.ExcludeItemIds
+        };
+        query = TranslateQuery(query, context, outerQuery)
+            .OrderBy(e => e.PresentationUniqueKey);
+
+        if (filter.OrderBy.Count != 0
+            || filter.SimilarTo is not null
+            || !string.IsNullOrEmpty(filter.SearchTerm))
+        {
+            query = ApplyOrder(query, filter);
+        }
+        else
+        {
+            query = query.OrderBy(e => e.SortName);
+        }
+
+        if (filter.Limit.HasValue || filter.StartIndex.HasValue)
+        {
+            var offset = filter.StartIndex ?? 0;
+
+            if (offset > 0)
+            {
+                query = query.Skip(offset);
+            }
+
+            if (filter.Limit.HasValue)
+            {
+                query.Take(filter.Limit.Value);
+            }
+        }
+
+        var result = new QueryResult<(BaseItem, ItemCounts)>();
+        string countText = string.Empty;
+        if (filter.EnableTotalRecordCount)
+        {
+            result.TotalRecordCount = query.DistinctBy(e => e.PresentationUniqueKey).Count();
+        }
+
+        var resultQuery = query.Select(e => new
+        {
+            item = e,
+            itemCount = new ItemCounts()
+            {
+                SeriesCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Series),
+                EpisodeCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Episode),
+                MovieCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Movie),
+                AlbumCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.MusicAlbum),
+                ArtistCount = e.ItemValues!.Count(e => e.Type == 0 || e.Type == 1),
+                SongCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.MusicAlbum),
+                TrailerCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Trailer),
+            }
+        });
+
+        result.StartIndex = filter.StartIndex ?? 0;
+        result.Items = resultQuery.ToImmutableArray().Select(e =>
+        {
+            return (DeserialiseBaseItem(e.item), e.itemCount);
+        }).ToImmutableArray();
+
+        return result;
+    }
+
     private static void PrepareFilterQuery(InternalItemsQuery query)
     private static void PrepareFilterQuery(InternalItemsQuery query)
     {
     {
         if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
         if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
@@ -2046,12 +1946,12 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
             return null;
             return null;
         }
         }
 
 
-        return _appHost.ReverseVirtualPath(path);
+        return appHost.ReverseVirtualPath(path);
     }
     }
 
 
     private string RestorePath(string path)
     private string RestorePath(string path)
     {
     {
-        return _appHost.ExpandVirtualPath(path);
+        return appHost.ExpandVirtualPath(path);
     }
     }
 
 
     internal ItemImageInfo? ItemImageInfoFromValueString(ReadOnlySpan<char> value)
     internal ItemImageInfo? ItemImageInfoFromValueString(ReadOnlySpan<char> value)

+ 40 - 16
Jellyfin.Server.Implementations/Item/ChapterManager.cs → Jellyfin.Server.Implementations/Item/ChapterRepository.cs

@@ -14,46 +14,69 @@ namespace Jellyfin.Server.Implementations.Item;
 /// <summary>
 /// <summary>
 /// The Chapter manager.
 /// The Chapter manager.
 /// </summary>
 /// </summary>
-public class ChapterManager : IChapterManager
+public class ChapterRepository : IChapterRepository
 {
 {
     private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
     private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
     private readonly IImageProcessor _imageProcessor;
     private readonly IImageProcessor _imageProcessor;
 
 
     /// <summary>
     /// <summary>
-    /// Initializes a new instance of the <see cref="ChapterManager"/> class.
+    /// Initializes a new instance of the <see cref="ChapterRepository"/> class.
     /// </summary>
     /// </summary>
     /// <param name="dbProvider">The EFCore provider.</param>
     /// <param name="dbProvider">The EFCore provider.</param>
     /// <param name="imageProcessor">The Image Processor.</param>
     /// <param name="imageProcessor">The Image Processor.</param>
-    public ChapterManager(IDbContextFactory<JellyfinDbContext> dbProvider, IImageProcessor imageProcessor)
+    public ChapterRepository(IDbContextFactory<JellyfinDbContext> dbProvider, IImageProcessor imageProcessor)
     {
     {
         _dbProvider = dbProvider;
         _dbProvider = dbProvider;
         _imageProcessor = imageProcessor;
         _imageProcessor = imageProcessor;
     }
     }
 
 
-    /// <inheritdoc cref="IChapterManager"/>
+    /// <inheritdoc cref="IChapterRepository"/>
     public ChapterInfo? GetChapter(BaseItemDto baseItem, int index)
     public ChapterInfo? GetChapter(BaseItemDto baseItem, int index)
+    {
+        return GetChapter(baseItem.Id, index);
+    }
+
+    /// <inheritdoc cref="IChapterRepository"/>
+    public IReadOnlyList<ChapterInfo> GetChapters(BaseItemDto baseItem)
+    {
+        return GetChapters(baseItem.Id);
+    }
+
+    /// <inheritdoc cref="IChapterRepository"/>
+    public ChapterInfo? GetChapter(Guid baseItemId, int index)
     {
     {
         using var context = _dbProvider.CreateDbContext();
         using var context = _dbProvider.CreateDbContext();
-        var chapter = context.Chapters.FirstOrDefault(e => e.ItemId.Equals(baseItem.Id) && e.ChapterIndex == index);
+        var chapter = context.Chapters
+            .Select(e => new
+            {
+                chapter = e,
+                baseItemPath = e.Item.Path
+            })
+            .FirstOrDefault(e => e.chapter.ItemId.Equals(baseItemId) && e.chapter.ChapterIndex == index);
         if (chapter is not null)
         if (chapter is not null)
         {
         {
-            return Map(chapter, baseItem);
+            return Map(chapter.chapter, chapter.baseItemPath!);
         }
         }
 
 
         return null;
         return null;
     }
     }
 
 
-    /// <inheritdoc cref="IChapterManager"/>
-    public IReadOnlyList<ChapterInfo> GetChapters(BaseItemDto baseItem)
+    /// <inheritdoc cref="IChapterRepository"/>
+    public IReadOnlyList<ChapterInfo> GetChapters(Guid baseItemId)
     {
     {
         using var context = _dbProvider.CreateDbContext();
         using var context = _dbProvider.CreateDbContext();
-        return context.Chapters.Where(e => e.ItemId.Equals(baseItem.Id))
+        return context.Chapters.Where(e => e.ItemId.Equals(baseItemId))
+            .Select(e => new
+            {
+                chapter = e,
+                baseItemPath = e.Item.Path
+            })
             .ToList()
             .ToList()
-            .Select(e => Map(e, baseItem))
+            .Select(e => Map(e.chapter, e.baseItemPath!))
             .ToImmutableArray();
             .ToImmutableArray();
     }
     }
 
 
-    /// <inheritdoc cref="IChapterManager"/>
+    /// <inheritdoc cref="IChapterRepository"/>
     public void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters)
     public void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters)
     {
     {
         using var context = _dbProvider.CreateDbContext();
         using var context = _dbProvider.CreateDbContext();
@@ -80,20 +103,21 @@ public class ChapterManager : IChapterManager
             ImageDateModified = chapterInfo.ImageDateModified,
             ImageDateModified = chapterInfo.ImageDateModified,
             ImagePath = chapterInfo.ImagePath,
             ImagePath = chapterInfo.ImagePath,
             ItemId = itemId,
             ItemId = itemId,
-            Name = chapterInfo.Name
+            Name = chapterInfo.Name,
+            Item = null!
         };
         };
     }
     }
 
 
-    private ChapterInfo Map(Chapter chapterInfo, BaseItemDto baseItem)
+    private ChapterInfo Map(Chapter chapterInfo, string baseItemPath)
     {
     {
-        var info = new ChapterInfo()
+        var chapterEntity = new ChapterInfo()
         {
         {
             StartPositionTicks = chapterInfo.StartPositionTicks,
             StartPositionTicks = chapterInfo.StartPositionTicks,
             ImageDateModified = chapterInfo.ImageDateModified.GetValueOrDefault(),
             ImageDateModified = chapterInfo.ImageDateModified.GetValueOrDefault(),
             ImagePath = chapterInfo.ImagePath,
             ImagePath = chapterInfo.ImagePath,
             Name = chapterInfo.Name,
             Name = chapterInfo.Name,
         };
         };
-        info.ImageTag = _imageProcessor.GetImageCacheTag(baseItem, info);
-        return info;
+        chapterEntity.ImageTag = _imageProcessor.GetImageCacheTag(baseItemPath, chapterEntity.ImageDateModified);
+        return chapterEntity;
     }
     }
 }
 }

+ 1 - 1
Jellyfin.Server.Implementations/Item/MediaAttachmentManager.cs → Jellyfin.Server.Implementations/Item/MediaAttachmentRepository.cs

@@ -14,7 +14,7 @@ namespace Jellyfin.Server.Implementations.Item;
 /// Manager for handling Media Attachments.
 /// Manager for handling Media Attachments.
 /// </summary>
 /// </summary>
 /// <param name="dbProvider">Efcore Factory.</param>
 /// <param name="dbProvider">Efcore Factory.</param>
-public class MediaAttachmentManager(IDbContextFactory<JellyfinDbContext> dbProvider) : IMediaAttachmentManager
+public class MediaAttachmentRepository(IDbContextFactory<JellyfinDbContext> dbProvider) : IMediaAttachmentRepository
 {
 {
     /// <inheritdoc />
     /// <inheritdoc />
     public void SaveMediaAttachments(
     public void SaveMediaAttachments(

+ 5 - 5
Jellyfin.Server.Implementations/Item/MediaStreamManager.cs → Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs

@@ -13,12 +13,12 @@ using Microsoft.EntityFrameworkCore;
 namespace Jellyfin.Server.Implementations.Item;
 namespace Jellyfin.Server.Implementations.Item;
 
 
 /// <summary>
 /// <summary>
-/// Initializes a new instance of the <see cref="MediaStreamManager"/> class.
+/// Initializes a new instance of the <see cref="MediaStreamRepository"/> class.
 /// </summary>
 /// </summary>
-/// <param name="dbProvider"></param>
-/// <param name="serverApplicationHost"></param>
-/// <param name="localization"></param>
-public class MediaStreamManager(IDbContextFactory<JellyfinDbContext> dbProvider, IServerApplicationHost serverApplicationHost, ILocalizationManager localization) : IMediaStreamManager
+/// <param name="dbProvider">The EFCore db factory.</param>
+/// <param name="serverApplicationHost">The Application host.</param>
+/// <param name="localization">The Localisation Provider.</param>
+public class MediaStreamRepository(IDbContextFactory<JellyfinDbContext> dbProvider, IServerApplicationHost serverApplicationHost, ILocalizationManager localization) : IMediaStreamRepository
 {
 {
     /// <inheritdoc />
     /// <inheritdoc />
     public void SaveMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken)
     public void SaveMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken)

+ 4 - 3
Jellyfin.Server.Implementations/Item/PeopleManager.cs → Jellyfin.Server.Implementations/Item/PeopleRepository.cs

@@ -16,13 +16,13 @@ namespace Jellyfin.Server.Implementations.Item;
 /// </summary>
 /// </summary>
 /// <param name="dbProvider">Efcore Factory.</param>
 /// <param name="dbProvider">Efcore Factory.</param>
 /// <remarks>
 /// <remarks>
-/// Initializes a new instance of the <see cref="PeopleManager"/> class.
+/// Initializes a new instance of the <see cref="PeopleRepository"/> class.
 /// </remarks>
 /// </remarks>
-/// <param name="dbProvider">The EFCore Context factory.</param>
-public class PeopleManager(IDbContextFactory<JellyfinDbContext> dbProvider) : IPeopleManager
+public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider) : IPeopleRepository
 {
 {
     private readonly IDbContextFactory<JellyfinDbContext> _dbProvider = dbProvider;
     private readonly IDbContextFactory<JellyfinDbContext> _dbProvider = dbProvider;
 
 
+    /// <inheritdoc/>
     public IReadOnlyList<PersonInfo> GetPeople(InternalPeopleQuery filter)
     public IReadOnlyList<PersonInfo> GetPeople(InternalPeopleQuery filter)
     {
     {
         using var context = _dbProvider.CreateDbContext();
         using var context = _dbProvider.CreateDbContext();
@@ -37,6 +37,7 @@ public class PeopleManager(IDbContextFactory<JellyfinDbContext> dbProvider) : IP
         return dbQuery.ToList().Select(Map).ToImmutableArray();
         return dbQuery.ToList().Select(Map).ToImmutableArray();
     }
     }
 
 
+    /// <inheritdoc/>
     public IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery filter)
     public IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery filter)
     {
     {
         using var context = _dbProvider.CreateDbContext();
         using var context = _dbProvider.CreateDbContext();

+ 1 - 1
Jellyfin.Server.Implementations/JellyfinDbContext.cs

@@ -106,7 +106,7 @@ public class JellyfinDbContext : DbContext
     /// <summary>
     /// <summary>
     /// Gets the <see cref="DbSet{TEntity}"/> containing the user data.
     /// Gets the <see cref="DbSet{TEntity}"/> containing the user data.
     /// </summary>
     /// </summary>
-    public DbSet<BaseItem> BaseItems => Set<BaseItem>();
+    public DbSet<BaseItemEntity> BaseItems => Set<BaseItemEntity>();
 
 
     /// <summary>
     /// <summary>
     /// Gets the <see cref="DbSet{TEntity}"/> containing the user data.
     /// Gets the <see cref="DbSet{TEntity}"/> containing the user data.

+ 2 - 2
Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs

@@ -8,10 +8,10 @@ namespace Jellyfin.Server.Implementations.ModelConfiguration;
 /// <summary>
 /// <summary>
 /// Configuration for BaseItem.
 /// Configuration for BaseItem.
 /// </summary>
 /// </summary>
-public class BaseItemConfiguration : IEntityTypeConfiguration<BaseItem>
+public class BaseItemConfiguration : IEntityTypeConfiguration<BaseItemEntity>
 {
 {
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public void Configure(EntityTypeBuilder<BaseItem> builder)
+    public void Configure(EntityTypeBuilder<BaseItemEntity> builder)
     {
     {
         builder.HasNoKey();
         builder.HasNoKey();
         builder.HasIndex(e => e.Path);
         builder.HasIndex(e => e.Path);

+ 1 - 1
Jellyfin.Server.Implementations/ModelConfiguration/BaseItemProviderConfiguration.cs

@@ -13,7 +13,7 @@ public class BaseItemProviderConfiguration : IEntityTypeConfiguration<BaseItemPr
     /// <inheritdoc/>
     /// <inheritdoc/>
     public void Configure(EntityTypeBuilder<BaseItemProvider> builder)
     public void Configure(EntityTypeBuilder<BaseItemProvider> builder)
     {
     {
-        builder.HasNoKey();
+        builder.HasKey(e => new { e.ItemId, e.ProviderId });
         builder.HasOne(e => e.Item);
         builder.HasOne(e => e.Item);
         builder.HasIndex(e => new { e.ProviderId, e.ProviderValue, e.ItemId });
         builder.HasIndex(e => new { e.ProviderId, e.ProviderValue, e.ItemId });
     }
     }

+ 0 - 24
MediaBrowser.Controller/Chapters/ChapterManager.cs

@@ -1,24 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using MediaBrowser.Controller.Chapters;
-using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Model.Entities;
-
-namespace MediaBrowser.Providers.Chapters
-{
-    public class ChapterManager : IChapterManager
-    {
-        public ChapterManager(IDbContextFactory<JellyfinDbContext> dbProvider)
-        {
-            _itemRepo = itemRepo;
-        }
-
-        /// <inheritdoc />
-        public void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters)
-        {
-            _itemRepo.SaveChapters(itemId, chapters);
-        }
-    }
-}

+ 0 - 35
MediaBrowser.Controller/Chapters/IChapterManager.cs

@@ -1,35 +0,0 @@
-using System;
-using System.Collections.Generic;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-
-namespace MediaBrowser.Controller.Chapters
-{
-    /// <summary>
-    /// Interface IChapterManager.
-    /// </summary>
-    public interface IChapterManager
-    {
-        /// <summary>
-        /// Saves the chapters.
-        /// </summary>
-        /// <param name="itemId">The item.</param>
-        /// <param name="chapters">The set of chapters.</param>
-        void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters);
-
-        /// <summary>
-        /// Gets all chapters associated with the baseItem.
-        /// </summary>
-        /// <param name="baseItem">The baseitem.</param>
-        /// <returns>A readonly list of chapter instances.</returns>
-        IReadOnlyList<ChapterInfo> GetChapters(BaseItemDto baseItem);
-
-        /// <summary>
-        /// Gets a single chapter of a BaseItem on a specific index.
-        /// </summary>
-        /// <param name="baseItem">The baseitem.</param>
-        /// <param name="index">The index of that chapter.</param>
-        /// <returns>A chapter instance.</returns>
-        ChapterInfo? GetChapter(BaseItemDto baseItem, int index);
-    }
-}

+ 49 - 0
MediaBrowser.Controller/Chapters/IChapterRepository.cs

@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Controller.Chapters;
+
+/// <summary>
+/// Interface IChapterManager.
+/// </summary>
+public interface IChapterRepository
+{
+    /// <summary>
+    /// Saves the chapters.
+    /// </summary>
+    /// <param name="itemId">The item.</param>
+    /// <param name="chapters">The set of chapters.</param>
+    void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters);
+
+    /// <summary>
+    /// Gets all chapters associated with the baseItem.
+    /// </summary>
+    /// <param name="baseItem">The baseitem.</param>
+    /// <returns>A readonly list of chapter instances.</returns>
+    IReadOnlyList<ChapterInfo> GetChapters(BaseItemDto baseItem);
+
+    /// <summary>
+    /// Gets a single chapter of a BaseItem on a specific index.
+    /// </summary>
+    /// <param name="baseItem">The baseitem.</param>
+    /// <param name="index">The index of that chapter.</param>
+    /// <returns>A chapter instance.</returns>
+    ChapterInfo? GetChapter(BaseItemDto baseItem, int index);
+
+    /// <summary>
+    /// Gets all chapters associated with the baseItem.
+    /// </summary>
+    /// <param name="baseItemId">The BaseItems id.</param>
+    /// <returns>A readonly list of chapter instances.</returns>
+    IReadOnlyList<ChapterInfo> GetChapters(Guid baseItemId);
+
+    /// <summary>
+    /// Gets a single chapter of a BaseItem on a specific index.
+    /// </summary>
+    /// <param name="baseItemId">The BaseItems id.</param>
+    /// <param name="index">The index of that chapter.</param>
+    /// <returns>A chapter instance.</returns>
+    ChapterInfo? GetChapter(Guid baseItemId, int index);
+}

+ 25 - 0
MediaBrowser.Controller/Drawing/IImageProcessor.cs

@@ -6,6 +6,7 @@ using System.Threading.Tasks;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 
 
 namespace MediaBrowser.Controller.Drawing
 namespace MediaBrowser.Controller.Drawing
@@ -57,6 +58,22 @@ namespace MediaBrowser.Controller.Drawing
         /// <returns>BlurHash.</returns>
         /// <returns>BlurHash.</returns>
         string GetImageBlurHash(string path, ImageDimensions imageDimensions);
         string GetImageBlurHash(string path, ImageDimensions imageDimensions);
 
 
+        /// <summary>
+        /// Gets the image cache tag.
+        /// </summary>
+        /// <param name="baseItemPath">The items basePath.</param>
+        /// <param name="imageDateModified">The image last modification date.</param>
+        /// <returns>Guid.</returns>
+        string? GetImageCacheTag(string baseItemPath, DateTime imageDateModified);
+
+        /// <summary>
+        /// Gets the image cache tag.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="image">The image.</param>
+        /// <returns>Guid.</returns>
+        string? GetImageCacheTag(BaseItemDto item, ChapterInfo image);
+
         /// <summary>
         /// <summary>
         /// Gets the image cache tag.
         /// Gets the image cache tag.
         /// </summary>
         /// </summary>
@@ -65,6 +82,14 @@ namespace MediaBrowser.Controller.Drawing
         /// <returns>Guid.</returns>
         /// <returns>Guid.</returns>
         string GetImageCacheTag(BaseItem item, ItemImageInfo image);
         string GetImageCacheTag(BaseItem item, ItemImageInfo image);
 
 
+        /// <summary>
+        /// Gets the image cache tag.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="image">The image.</param>
+        /// <returns>Guid.</returns>
+        string GetImageCacheTag(BaseItemDto item, ItemImageInfo image);
+
         string? GetImageCacheTag(BaseItem item, ChapterInfo chapter);
         string? GetImageCacheTag(BaseItem item, ChapterInfo chapter);
 
 
         string? GetImageCacheTag(User user);
         string? GetImageCacheTag(User user);

+ 5 - 2
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -16,6 +16,7 @@ using Jellyfin.Data.Enums;
 using Jellyfin.Extensions;
 using Jellyfin.Extensions;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Chapters;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
@@ -479,6 +480,8 @@ namespace MediaBrowser.Controller.Entities
 
 
         public static IItemRepository ItemRepository { get; set; }
         public static IItemRepository ItemRepository { get; set; }
 
 
+        public static IChapterRepository ChapterRepository { get; set; }
+
         public static IFileSystem FileSystem { get; set; }
         public static IFileSystem FileSystem { get; set; }
 
 
         public static IUserDataManager UserDataManager { get; set; }
         public static IUserDataManager UserDataManager { get; set; }
@@ -2031,7 +2034,7 @@ namespace MediaBrowser.Controller.Entities
         {
         {
             if (imageType == ImageType.Chapter)
             if (imageType == ImageType.Chapter)
             {
             {
-                var chapter = ItemRepository.GetChapter(this, imageIndex);
+                var chapter = ChapterRepository.GetChapter(this.Id, imageIndex);
 
 
                 if (chapter is null)
                 if (chapter is null)
                 {
                 {
@@ -2081,7 +2084,7 @@ namespace MediaBrowser.Controller.Entities
 
 
             if (image.Type == ImageType.Chapter)
             if (image.Type == ImageType.Chapter)
             {
             {
-                var chapters = ItemRepository.GetChapters(this);
+                var chapters = ChapterRepository.GetChapters(this.Id);
                 for (var i = 0; i < chapters.Count; i++)
                 for (var i = 0; i < chapters.Count; i++)
                 {
                 {
                     if (chapters[i].ImagePath == image.Path)
                     if (chapters[i].ImagePath == image.Path)

+ 0 - 1
MediaBrowser.Controller/Persistence/IItemRepository.cs

@@ -52,7 +52,6 @@ public interface IItemRepository : IDisposable
     /// <returns>List&lt;Guid&gt;.</returns>
     /// <returns>List&lt;Guid&gt;.</returns>
     IReadOnlyList<Guid> GetItemIdsList(InternalItemsQuery filter);
     IReadOnlyList<Guid> GetItemIdsList(InternalItemsQuery filter);
 
 
-
     /// <summary>
     /// <summary>
     /// Gets the item list.
     /// Gets the item list.
     /// </summary>
     /// </summary>

+ 57 - 0
MediaBrowser.Controller/Persistence/IItemTypeLookup.cs

@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Model.Querying;
+
+namespace MediaBrowser.Controller.Persistence;
+
+/// <summary>
+/// Provides static lookup data for <see cref="ItemFields"/> and <see cref="BaseItemKind"/> for the domain.
+/// </summary>
+public interface IItemTypeLookup
+{
+    /// <summary>
+    /// Gets all values of the ItemFields type.
+    /// </summary>
+    public IReadOnlyList<ItemFields> AllItemFields { get; }
+
+    /// <summary>
+    /// Gets all BaseItemKinds that are considered Programs.
+    /// </summary>
+    public IReadOnlyList<BaseItemKind> ProgramTypes { get; }
+
+    /// <summary>
+    /// Gets all BaseItemKinds that should be excluded from parent lookup.
+    /// </summary>
+    public IReadOnlyList<BaseItemKind> ProgramExcludeParentTypes { get; }
+
+    /// <summary>
+    /// Gets all BaseItemKinds that are considered to be provided by services.
+    /// </summary>
+    public IReadOnlyList<BaseItemKind> ServiceTypes { get; }
+
+    /// <summary>
+    /// Gets all BaseItemKinds that have a StartDate.
+    /// </summary>
+    public IReadOnlyList<BaseItemKind> StartDateTypes { get; }
+
+    /// <summary>
+    /// Gets all BaseItemKinds that are considered Series.
+    /// </summary>
+    public IReadOnlyList<BaseItemKind> SeriesTypes { get; }
+
+    /// <summary>
+    /// Gets all BaseItemKinds that are not to be evaluated for Artists.
+    /// </summary>
+    public IReadOnlyList<BaseItemKind> ArtistExcludeParentTypes { get; }
+
+    /// <summary>
+    /// Gets all BaseItemKinds that are considered Artists.
+    /// </summary>
+    public IReadOnlyList<BaseItemKind> ArtistsTypes { get; }
+
+    /// <summary>
+    /// Gets mapping for all BaseItemKinds and their expected serialisaition target.
+    /// </summary>
+    public IDictionary<BaseItemKind, string?> BaseItemKindNames { get; }
+}

+ 1 - 2
MediaBrowser.Controller/Persistence/IMediaAttachmentManager.cs → MediaBrowser.Controller/Persistence/IMediaAttachmentRepository.cs

@@ -9,9 +9,8 @@ using MediaBrowser.Model.Entities;
 
 
 namespace MediaBrowser.Controller.Persistence;
 namespace MediaBrowser.Controller.Persistence;
 
 
-public interface IMediaAttachmentManager
+public interface IMediaAttachmentRepository
 {
 {
-
     /// <summary>
     /// <summary>
     /// Gets the media attachments.
     /// Gets the media attachments.
     /// </summary>
     /// </summary>

+ 5 - 2
MediaBrowser.Controller/Persistence/IMediaStreamManager.cs → MediaBrowser.Controller/Persistence/IMediaStreamRepository.cs

@@ -9,14 +9,17 @@ using MediaBrowser.Model.Entities;
 
 
 namespace MediaBrowser.Controller.Persistence;
 namespace MediaBrowser.Controller.Persistence;
 
 
-public interface IMediaStreamManager
+/// <summary>
+/// Provides methods for accessing MediaStreams.
+/// </summary>
+public interface IMediaStreamRepository
 {
 {
     /// <summary>
     /// <summary>
     /// Gets the media streams.
     /// Gets the media streams.
     /// </summary>
     /// </summary>
     /// <param name="filter">The query.</param>
     /// <param name="filter">The query.</param>
     /// <returns>IEnumerable{MediaStream}.</returns>
     /// <returns>IEnumerable{MediaStream}.</returns>
-    List<MediaStream> GetMediaStreams(MediaStreamQuery filter);
+    IReadOnlyList<MediaStream> GetMediaStreams(MediaStreamQuery filter);
 
 
     /// <summary>
     /// <summary>
     /// Saves the media streams.
     /// Saves the media streams.

+ 1 - 2
MediaBrowser.Controller/Persistence/IPeopleManager.cs → MediaBrowser.Controller/Persistence/IPeopleRepository.cs

@@ -8,7 +8,7 @@ using MediaBrowser.Controller.Entities;
 
 
 namespace MediaBrowser.Controller.Persistence;
 namespace MediaBrowser.Controller.Persistence;
 
 
-public interface IPeopleManager
+public interface IPeopleRepository
 {
 {
     /// <summary>
     /// <summary>
     /// Gets the people.
     /// Gets the people.
@@ -30,5 +30,4 @@ public interface IPeopleManager
     /// <param name="filter">The query.</param>
     /// <param name="filter">The query.</param>
     /// <returns>List&lt;System.String&gt;.</returns>
     /// <returns>List&lt;System.String&gt;.</returns>
     IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery filter);
     IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery filter);
-
 }
 }

+ 2 - 2
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

@@ -38,7 +38,7 @@ namespace MediaBrowser.Providers.MediaInfo
         private readonly IEncodingManager _encodingManager;
         private readonly IEncodingManager _encodingManager;
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly ISubtitleManager _subtitleManager;
         private readonly ISubtitleManager _subtitleManager;
-        private readonly IChapterManager _chapterManager;
+        private readonly IChapterRepository _chapterManager;
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
         private readonly AudioResolver _audioResolver;
         private readonly AudioResolver _audioResolver;
         private readonly SubtitleResolver _subtitleResolver;
         private readonly SubtitleResolver _subtitleResolver;
@@ -54,7 +54,7 @@ namespace MediaBrowser.Providers.MediaInfo
             IEncodingManager encodingManager,
             IEncodingManager encodingManager,
             IServerConfigurationManager config,
             IServerConfigurationManager config,
             ISubtitleManager subtitleManager,
             ISubtitleManager subtitleManager,
-            IChapterManager chapterManager,
+            IChapterRepository chapterManager,
             ILibraryManager libraryManager,
             ILibraryManager libraryManager,
             AudioResolver audioResolver,
             AudioResolver audioResolver,
             SubtitleResolver subtitleResolver)
             SubtitleResolver subtitleResolver)

+ 2 - 2
MediaBrowser.Providers/MediaInfo/ProbeProvider.cs

@@ -61,7 +61,7 @@ namespace MediaBrowser.Providers.MediaInfo
         /// <param name="encodingManager">Instance of the <see cref="IEncodingManager"/> interface.</param>
         /// <param name="encodingManager">Instance of the <see cref="IEncodingManager"/> interface.</param>
         /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
         /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
         /// <param name="subtitleManager">Instance of the <see cref="ISubtitleManager"/> interface.</param>
         /// <param name="subtitleManager">Instance of the <see cref="ISubtitleManager"/> interface.</param>
-        /// <param name="chapterManager">Instance of the <see cref="IChapterManager"/> interface.</param>
+        /// <param name="chapterManager">Instance of the <see cref="IChapterRepository"/> interface.</param>
         /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
         /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
         /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/>.</param>
         /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/>.</param>
         /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
         /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
@@ -76,7 +76,7 @@ namespace MediaBrowser.Providers.MediaInfo
             IEncodingManager encodingManager,
             IEncodingManager encodingManager,
             IServerConfigurationManager config,
             IServerConfigurationManager config,
             ISubtitleManager subtitleManager,
             ISubtitleManager subtitleManager,
-            IChapterManager chapterManager,
+            IChapterRepository chapterManager,
             ILibraryManager libraryManager,
             ILibraryManager libraryManager,
             IFileSystem fileSystem,
             IFileSystem fileSystem,
             ILoggerFactory loggerFactory,
             ILoggerFactory loggerFactory,

+ 25 - 0
src/Jellyfin.Drawing/ImageProcessor.cs

@@ -15,6 +15,7 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Net;
@@ -403,10 +404,34 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
         return _imageEncoder.GetImageBlurHash(xComp, yComp, path);
         return _imageEncoder.GetImageBlurHash(xComp, yComp, path);
     }
     }
 
 
+    /// <inheritdoc />
+    public string GetImageCacheTag(string baseItemPath, DateTime imageDateModified)
+        => (baseItemPath + imageDateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
+
     /// <inheritdoc />
     /// <inheritdoc />
     public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
     public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
         => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
         => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
 
 
+    /// <inheritdoc />
+    public string GetImageCacheTag(BaseItemDto item, ItemImageInfo image)
+        => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
+
+    /// <inheritdoc />
+    public string? GetImageCacheTag(BaseItemDto item, ChapterInfo chapter)
+    {
+        if (chapter.ImagePath is null)
+        {
+            return null;
+        }
+
+        return GetImageCacheTag(item, new ItemImageInfo
+        {
+            Path = chapter.ImagePath,
+            Type = ImageType.Chapter,
+            DateModified = chapter.ImageDateModified
+        });
+    }
+
     /// <inheritdoc />
     /// <inheritdoc />
     public string? GetImageCacheTag(BaseItem item, ChapterInfo chapter)
     public string? GetImageCacheTag(BaseItem item, ChapterInfo chapter)
     {
     {