using System; using System.Collections.Generic; using System.Linq; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api.Controllers; /// /// Filters controller. /// [Route("")] [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] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); BaseItem? item = null; if (includeItemTypes.Length != 1 || !(includeItemTypes[0] == BaseItemKind.BoxSet || includeItemTypes[0] == BaseItemKind.Playlist || includeItemTypes[0] == BaseItemKind.Trailer || includeItemTypes[0] == BaseItemKind.Program)) { item = _libraryManager.GetParentItem(parentId, user?.Id); } var query = new InternalItemsQuery { User = user, MediaTypes = mediaTypes, IncludeItemTypes = includeItemTypes, Recursive = true, EnableTotalRecordCount = false, DtoOptions = new DtoOptions { Fields = new[] { ItemFields.Genres, ItemFields.Tags }, EnableImages = false, EnableUserData = false } }; if (item is not Folder folder) { return new QueryFiltersLegacy(); } var itemList = folder.GetItemList(query); var mediaStreams = GetMediaStreams(itemList, BaseItemKind.Movie, BaseItemKind.Episode).ToList(); return new QueryFiltersLegacy { Years = itemList.Select(i => i.ProductionYear ?? -1) .Where(i => i > 0) .Distinct() .Order() .ToArray(), Genres = itemList.SelectMany(i => i.Genres) .DistinctNames() .Order() .ToArray(), Tags = itemList .SelectMany(i => i.Tags) .Distinct(StringComparer.OrdinalIgnoreCase) .Order() .ToArray(), OfficialRatings = itemList .Select(i => i.OfficialRating) .Where(i => !string.IsNullOrWhiteSpace(i)) .Distinct(StringComparer.OrdinalIgnoreCase) .Order() .ToArray(), AudioLanguages = mediaStreams .Where(mediaStream => mediaStream.Type == MediaStreamType.Audio) .Select(mediaStream => string.IsNullOrWhiteSpace(mediaStream.Language) ? "und" : mediaStream.Language) .Distinct(StringComparer.OrdinalIgnoreCase) .Order() .ToArray(), SubtitleLanguages = mediaStreams .Where(mediaStream => mediaStream.Type == MediaStreamType.Subtitle) .Select(mediaStream => string.IsNullOrWhiteSpace(mediaStream.Language) ? "und" : mediaStream.Language) .Distinct(StringComparer.OrdinalIgnoreCase) .Order() .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. /// 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] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isAiring, [FromQuery] bool? isMovie, [FromQuery] bool? isSports, [FromQuery] bool? isKids, [FromQuery] bool? isNews, [FromQuery] bool? isSeries, [FromQuery] bool? recursive) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); BaseItem? parentItem = null; if (includeItemTypes.Length == 1 && (includeItemTypes[0] == BaseItemKind.BoxSet || includeItemTypes[0] == BaseItemKind.Playlist || includeItemTypes[0] == BaseItemKind.Trailer || includeItemTypes[0] == BaseItemKind.Program)) { parentItem = null; } else if (parentId.HasValue) { parentItem = _libraryManager.GetItemById(parentId.Value); } var filters = new QueryFilters(); var genreQuery = new InternalItemsQuery(user) { IncludeItemTypes = includeItemTypes, 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 is null ? Array.Empty() : new[] { parentItem.Id }; } else { genreQuery.Parent = parentItem; } if (includeItemTypes.Length == 1 && (includeItemTypes[0] == BaseItemKind.MusicAlbum || includeItemTypes[0] == BaseItemKind.MusicVideo || includeItemTypes[0] == BaseItemKind.MusicArtist || includeItemTypes[0] == BaseItemKind.Audio)) { filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameGuidPair { Name = i.Item.Name, Id = i.Item.Id }).ToArray(); } else { filters.Genres = _libraryManager.GetGenres(genreQuery).Items.Select(i => new NameGuidPair { Name = i.Item.Name, Id = i.Item.Id }).ToArray(); } return filters; } /// /// Extracts the MediaStreams from the list of BaseItems. /// If an item is a Folder, MediaStreams are searched in the list of it's child items. /// /// The list of items for which the MediaStreams should be xtracted. /// Optional list of item kinds for which the MediaStreams should be extracted. /// A list of all MediaStreams which are linked to the provided items. private IEnumerable GetMediaStreams(IEnumerable itemList, params BaseItemKind[] types) { return itemList .Where(item => item.IsFolder || types.Length == 0 || types.Contains(item.GetBaseItemKind())) .SelectMany(item => { if (item.IsFolder) { return GetMediaStreams(((Folder)item).Children, types); } else if (item is Video) { return ((Video)item).GetAllLinkedMediaStreams(); } else { return item.GetMediaStreams(); } }); } }