diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs
new file mode 100644
index 000000000..46911ce93
--- /dev/null
+++ b/Jellyfin.Api/Controllers/FilterController.cs
@@ -0,0 +1,221 @@
+#nullable enable
+#pragma warning disable CA1801
+
+using System;
+using System.Linq;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Playlists;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Querying;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Jellyfin.Api.Controllers
+{
+ ///
+ /// Filters controller.
+ ///
+ [Authorize]
+ public class FilterController : BaseJellyfinApiController
+ {
+ private readonly ILibraryManager _libraryManager;
+ private readonly IUserManager _userManager;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public FilterController(ILibraryManager libraryManager, IUserManager userManager)
+ {
+ _libraryManager = libraryManager;
+ _userManager = userManager;
+ }
+
+ ///
+ /// Gets legacy query filters.
+ ///
+ /// Optional. User id.
+ /// Optional. Parent id.
+ /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.
+ /// Optional. Filter by MediaType. Allows multiple, comma delimited.
+ /// Legacy filters retrieved.
+ /// Legacy query filters.
+ [HttpGet("/Items/Filters")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult GetQueryFiltersLegacy(
+ [FromQuery] Guid? userId,
+ [FromQuery] string? parentId,
+ [FromQuery] string? includeItemTypes,
+ [FromQuery] string? mediaTypes)
+ {
+ var parentItem = string.IsNullOrEmpty(parentId)
+ ? null
+ : _libraryManager.GetItemById(parentId);
+
+ var user = userId == null || userId == Guid.Empty
+ ? null
+ : _userManager.GetUserById(userId.Value);
+
+ if (string.Equals(includeItemTypes, nameof(BoxSet), StringComparison.OrdinalIgnoreCase)
+ || string.Equals(includeItemTypes, nameof(Playlist), StringComparison.OrdinalIgnoreCase)
+ || string.Equals(includeItemTypes, nameof(Trailer), StringComparison.OrdinalIgnoreCase)
+ || string.Equals(includeItemTypes, "Program", StringComparison.OrdinalIgnoreCase))
+ {
+ parentItem = null;
+ }
+
+ var item = string.IsNullOrEmpty(parentId)
+ ? user == null
+ ? _libraryManager.RootFolder
+ : _libraryManager.GetUserRootFolder()
+ : parentItem;
+
+ var query = new InternalItemsQuery
+ {
+ User = user,
+ MediaTypes = (mediaTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),
+ IncludeItemTypes = (includeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),
+ Recursive = true,
+ EnableTotalRecordCount = false,
+ DtoOptions = new DtoOptions
+ {
+ Fields = new[] { ItemFields.Genres, ItemFields.Tags },
+ EnableImages = false,
+ EnableUserData = false
+ }
+ };
+
+ var itemList = ((Folder)item!).GetItemList(query);
+ return new QueryFiltersLegacy
+ {
+ Years = itemList.Select(i => i.ProductionYear ?? -1)
+ .Where(i => i > 0)
+ .Distinct()
+ .OrderBy(i => i)
+ .ToArray(),
+
+ Genres = itemList.SelectMany(i => i.Genres)
+ .DistinctNames()
+ .OrderBy(i => i)
+ .ToArray(),
+
+ Tags = itemList
+ .SelectMany(i => i.Tags)
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .OrderBy(i => i)
+ .ToArray(),
+
+ OfficialRatings = itemList
+ .Select(i => i.OfficialRating)
+ .Where(i => !string.IsNullOrWhiteSpace(i))
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .OrderBy(i => i)
+ .ToArray()
+ };
+ }
+
+ ///
+ /// Gets query filters.
+ ///
+ /// Optional. User id.
+ /// Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.
+ /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.
+ /// [Unused] Optional. Filter by MediaType. Allows multiple, comma delimited.
+ /// Optional. Is item airing.
+ /// Optional. Is item movie.
+ /// Optional. Is item sports.
+ /// Optional. Is item kids.
+ /// Optional. Is item news.
+ /// Optional. Is item series.
+ /// Optional. Search recursive.
+ /// Filters retrieved.
+ /// Query filters.
+ [HttpGet("/Items/Filters2")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult GetQueryFilters(
+ [FromQuery] Guid? userId,
+ [FromQuery] string? parentId,
+ [FromQuery] string? includeItemTypes,
+ [FromQuery] string? mediaTypes,
+ [FromQuery] bool? isAiring,
+ [FromQuery] bool? isMovie,
+ [FromQuery] bool? isSports,
+ [FromQuery] bool? isKids,
+ [FromQuery] bool? isNews,
+ [FromQuery] bool? isSeries,
+ [FromQuery] bool? recursive)
+ {
+ var parentItem = string.IsNullOrEmpty(parentId)
+ ? null
+ : _libraryManager.GetItemById(parentId);
+
+ var user = userId == null || userId == Guid.Empty
+ ? null
+ : _userManager.GetUserById(userId.Value);
+
+ if (string.Equals(includeItemTypes, nameof(BoxSet), StringComparison.OrdinalIgnoreCase)
+ || string.Equals(includeItemTypes, nameof(Playlist), StringComparison.OrdinalIgnoreCase)
+ || string.Equals(includeItemTypes, nameof(Trailer), StringComparison.OrdinalIgnoreCase)
+ || string.Equals(includeItemTypes, "Program", StringComparison.OrdinalIgnoreCase))
+ {
+ parentItem = null;
+ }
+
+ var filters = new QueryFilters();
+ var genreQuery = new InternalItemsQuery(user)
+ {
+ IncludeItemTypes =
+ (includeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),
+ DtoOptions = new DtoOptions
+ {
+ Fields = Array.Empty(),
+ EnableImages = false,
+ EnableUserData = false
+ },
+ IsAiring = isAiring,
+ IsMovie = isMovie,
+ IsSports = isSports,
+ IsKids = isKids,
+ IsNews = isNews,
+ IsSeries = isSeries
+ };
+
+ if ((recursive ?? true) || parentItem is UserView || parentItem is ICollectionFolder)
+ {
+ genreQuery.AncestorIds = parentItem == null ? Array.Empty() : new[] { parentItem.Id };
+ }
+ else
+ {
+ genreQuery.Parent = parentItem;
+ }
+
+ if (string.Equals(includeItemTypes, nameof(MusicAlbum), StringComparison.OrdinalIgnoreCase)
+ || string.Equals(includeItemTypes, nameof(MusicVideo), StringComparison.OrdinalIgnoreCase)
+ || string.Equals(includeItemTypes, nameof(MusicArtist), StringComparison.OrdinalIgnoreCase)
+ || string.Equals(includeItemTypes, nameof(Audio), StringComparison.OrdinalIgnoreCase))
+ {
+ filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameGuidPair
+ {
+ Name = i.Item1.Name,
+ Id = i.Item1.Id
+ }).ToArray();
+ }
+ else
+ {
+ filters.Genres = _libraryManager.GetGenres(genreQuery).Items.Select(i => new NameGuidPair
+ {
+ Name = i.Item1.Name,
+ Id = i.Item1.Id
+ }).ToArray();
+ }
+
+ return filters;
+ }
+ }
+}
diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs
deleted file mode 100644
index bd67ec41f..000000000
--- a/MediaBrowser.Api/FilterService.cs
+++ /dev/null
@@ -1,244 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Jellyfin.Data.Entities;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api
-{
- [Route("/Items/Filters", "GET", Summary = "Gets branding configuration")]
- public class GetQueryFiltersLegacy : IReturn
- {
- ///
- /// Gets or sets the user id.
- ///
- /// The user id.
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
-
- [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string ParentId { get; set; }
-
- [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string IncludeItemTypes { get; set; }
-
- [ApiMember(Name = "MediaTypes", Description = "Optional filter by MediaType. Allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string MediaTypes { get; set; }
-
- public string[] GetMediaTypes()
- {
- return (MediaTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
- }
-
- public string[] GetIncludeItemTypes()
- {
- return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
- }
- }
-
- [Route("/Items/Filters2", "GET", Summary = "Gets branding configuration")]
- public class GetQueryFilters : IReturn
- {
- ///
- /// Gets or sets the user id.
- ///
- /// The user id.
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
-
- [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string ParentId { get; set; }
-
- [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string IncludeItemTypes { get; set; }
-
- [ApiMember(Name = "MediaTypes", Description = "Optional filter by MediaType. Allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string MediaTypes { get; set; }
-
- public string[] GetMediaTypes()
- {
- return (MediaTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
- }
-
- public string[] GetIncludeItemTypes()
- {
- return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
- }
-
- public bool? IsAiring { get; set; }
- public bool? IsMovie { get; set; }
- public bool? IsSports { get; set; }
- public bool? IsKids { get; set; }
- public bool? IsNews { get; set; }
- public bool? IsSeries { get; set; }
- public bool? Recursive { get; set; }
- }
-
- [Authenticated]
- public class FilterService : BaseApiService
- {
- private readonly ILibraryManager _libraryManager;
- private readonly IUserManager _userManager;
-
- public FilterService(
- ILogger logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- ILibraryManager libraryManager,
- IUserManager userManager)
- : base(logger, serverConfigurationManager, httpResultFactory)
- {
- _libraryManager = libraryManager;
- _userManager = userManager;
- }
-
- public object Get(GetQueryFilters request)
- {
- var parentItem = string.IsNullOrEmpty(request.ParentId) ? null : _libraryManager.GetItemById(request.ParentId);
- var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
-
- if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(request.IncludeItemTypes, typeof(Trailer).Name, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(request.IncludeItemTypes, "Program", StringComparison.OrdinalIgnoreCase))
- {
- parentItem = null;
- }
-
- var filters = new QueryFilters();
-
- var genreQuery = new InternalItemsQuery(user)
- {
- IncludeItemTypes = request.GetIncludeItemTypes(),
- DtoOptions = new Controller.Dto.DtoOptions
- {
- Fields = new ItemFields[] { },
- EnableImages = false,
- EnableUserData = false
- },
- IsAiring = request.IsAiring,
- IsMovie = request.IsMovie,
- IsSports = request.IsSports,
- IsKids = request.IsKids,
- IsNews = request.IsNews,
- IsSeries = request.IsSeries
- };
-
- // Non recursive not yet supported for library folders
- if ((request.Recursive ?? true) || parentItem is UserView || parentItem is ICollectionFolder)
- {
- genreQuery.AncestorIds = parentItem == null ? Array.Empty() : new[] { parentItem.Id };
- }
- else
- {
- genreQuery.Parent = parentItem;
- }
-
- if (string.Equals(request.IncludeItemTypes, "MusicAlbum", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(request.IncludeItemTypes, "MusicVideo", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(request.IncludeItemTypes, "MusicArtist", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(request.IncludeItemTypes, "Audio", StringComparison.OrdinalIgnoreCase))
- {
- filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameGuidPair
- {
- Name = i.Item1.Name,
- Id = i.Item1.Id
-
- }).ToArray();
- }
- else
- {
- filters.Genres = _libraryManager.GetGenres(genreQuery).Items.Select(i => new NameGuidPair
- {
- Name = i.Item1.Name,
- Id = i.Item1.Id
-
- }).ToArray();
- }
-
- return ToOptimizedResult(filters);
- }
-
- public object Get(GetQueryFiltersLegacy request)
- {
- var parentItem = string.IsNullOrEmpty(request.ParentId) ? null : _libraryManager.GetItemById(request.ParentId);
- var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
-
- if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(request.IncludeItemTypes, typeof(Trailer).Name, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(request.IncludeItemTypes, "Program", StringComparison.OrdinalIgnoreCase))
- {
- parentItem = null;
- }
-
- var item = string.IsNullOrEmpty(request.ParentId) ?
- user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder() :
- parentItem;
-
- var result = ((Folder)item).GetItemList(GetItemsQuery(request, user));
-
- var filters = GetFilters(result);
-
- return ToOptimizedResult(filters);
- }
-
- private QueryFiltersLegacy GetFilters(IReadOnlyCollection items)
- {
- var result = new QueryFiltersLegacy();
-
- result.Years = items.Select(i => i.ProductionYear ?? -1)
- .Where(i => i > 0)
- .Distinct()
- .OrderBy(i => i)
- .ToArray();
-
- result.Genres = items.SelectMany(i => i.Genres)
- .DistinctNames()
- .OrderBy(i => i)
- .ToArray();
-
- result.Tags = items
- .SelectMany(i => i.Tags)
- .Distinct(StringComparer.OrdinalIgnoreCase)
- .OrderBy(i => i)
- .ToArray();
-
- result.OfficialRatings = items
- .Select(i => i.OfficialRating)
- .Where(i => !string.IsNullOrWhiteSpace(i))
- .Distinct(StringComparer.OrdinalIgnoreCase)
- .OrderBy(i => i)
- .ToArray();
-
- return result;
- }
-
- private InternalItemsQuery GetItemsQuery(GetQueryFiltersLegacy request, User user)
- {
- var query = new InternalItemsQuery
- {
- User = user,
- MediaTypes = request.GetMediaTypes(),
- IncludeItemTypes = request.GetIncludeItemTypes(),
- Recursive = true,
- EnableTotalRecordCount = false,
- DtoOptions = new Controller.Dto.DtoOptions
- {
- Fields = new[] { ItemFields.Genres, ItemFields.Tags },
- EnableImages = false,
- EnableUserData = false
- }
- };
-
- return query;
- }
- }
-}