< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.FilterController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/FilterController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 152
Coverable lines: 152
Total lines: 277
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 54
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/16/2026 - 12:15:55 AM Line coverage: 0% (0/105) Branch coverage: 0% (0/44) Total lines: 2237/23/2026 - 12:16:48 AM Line coverage: 0% (0/133) Branch coverage: 0% (0/54) Total lines: 25610/29/2026 - 12:17:11 AM Line coverage: 0% (0/152) Branch coverage: 0% (0/54) Total lines: 277 5/16/2026 - 12:15:55 AM Line coverage: 0% (0/105) Branch coverage: 0% (0/44) Total lines: 2237/23/2026 - 12:16:48 AM Line coverage: 0% (0/133) Branch coverage: 0% (0/54) Total lines: 25610/29/2026 - 12:17:11 AM Line coverage: 0% (0/152) Branch coverage: 0% (0/54) Total lines: 277

Coverage delta

Coverage delta 1 -1

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
GetQueryFiltersLegacy(...)0%156120%
GetQueryFilters(...)0%1806420%

File(s)

/srv/git/jellyfin/Jellyfin.Api/Controllers/FilterController.cs

#LineLine coverage
 1using System;
 2using System.Linq;
 3using Jellyfin.Api.Helpers;
 4using Jellyfin.Api.ModelBinders;
 5using Jellyfin.Data.Enums;
 6using Jellyfin.Extensions;
 7using MediaBrowser.Controller.Dto;
 8using MediaBrowser.Controller.Entities;
 9using MediaBrowser.Controller.Library;
 10using MediaBrowser.Model.Dto;
 11using MediaBrowser.Model.Entities;
 12using MediaBrowser.Model.Globalization;
 13using MediaBrowser.Model.Querying;
 14using Microsoft.AspNetCore.Authorization;
 15using Microsoft.AspNetCore.Http;
 16using Microsoft.AspNetCore.Mvc;
 17
 18namespace Jellyfin.Api.Controllers;
 19
 20/// <summary>
 21/// Filters controller.
 22/// </summary>
 23[Route("")]
 24[Authorize]
 25public class FilterController : BaseJellyfinApiController
 26{
 27    private readonly ILibraryManager _libraryManager;
 28    private readonly IUserManager _userManager;
 29    private readonly ILocalizationManager _localization;
 30
 31    /// <summary>
 32    /// Initializes a new instance of the <see cref="FilterController"/> class.
 33    /// </summary>
 34    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 35    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 36    /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
 037    public FilterController(ILibraryManager libraryManager, IUserManager userManager, ILocalizationManager localization)
 38    {
 039        _libraryManager = libraryManager;
 040        _userManager = userManager;
 041        _localization = localization;
 042    }
 43
 44    /// <summary>
 45    /// Gets legacy query filters.
 46    /// </summary>
 47    /// <param name="userId">Optional. User id.</param>
 48    /// <param name="parentId">Optional. Parent id.</param>
 49    /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows 
 50    /// <param name="mediaTypes">Optional. Filter by MediaType. Allows multiple, comma delimited.</param>
 51    /// <response code="200">Legacy filters retrieved.</response>
 52    /// <returns>Legacy query filters.</returns>
 53    [HttpGet("Items/Filters")]
 54    [ProducesResponseType(StatusCodes.Status200OK)]
 55    public ActionResult<QueryFiltersLegacy> GetQueryFiltersLegacy(
 56        [FromQuery] Guid? userId,
 57        [FromQuery] Guid? parentId,
 58        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
 59        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes)
 60    {
 061        userId = RequestHelpers.GetUserId(User, userId);
 062        var user = userId.IsNullOrEmpty()
 063            ? null
 064            : _userManager.GetUserById(userId.Value);
 65
 066        BaseItem? item = null;
 067        if (includeItemTypes.Length != 1
 068            || !(includeItemTypes[0] == BaseItemKind.Trailer
 069                 || includeItemTypes[0] == BaseItemKind.Program))
 70        {
 071            item = _libraryManager.GetParentItem(parentId, user?.Id);
 72        }
 73
 074        if (item is not Folder folder)
 75        {
 076            return new QueryFiltersLegacy();
 77        }
 78
 079        var query = new InternalItemsQuery(user)
 080        {
 081            MediaTypes = mediaTypes,
 082            IncludeItemTypes = includeItemTypes,
 083            Recursive = true,
 084            EnableTotalRecordCount = false,
 085            AncestorIds = [folder.Id],
 086            DtoOptions = new DtoOptions
 087            {
 088                Fields = [],
 089                EnableImages = false,
 090                EnableUserData = false
 091            }
 092        };
 93
 094        return _libraryManager.GetQueryFiltersLegacy(query);
 95    }
 96
 97    /// <summary>
 98    /// Gets query filters.
 99    /// </summary>
 100    /// <param name="userId">Optional. User id.</param>
 101    /// <param name="parentId">Optional. Specify this to localize the search to a specific item or folder. Omit to use t
 102    /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows 
 103    /// <param name="isAiring">Optional. Is item airing.</param>
 104    /// <param name="isMovie">Optional. Is item movie.</param>
 105    /// <param name="isSports">Optional. Is item sports.</param>
 106    /// <param name="isKids">Optional. Is item kids.</param>
 107    /// <param name="isNews">Optional. Is item news.</param>
 108    /// <param name="isSeries">Optional. Is item series.</param>
 109    /// <param name="recursive">Optional. Search recursive.</param>
 110    /// <response code="200">Filters retrieved.</response>
 111    /// <returns>Query filters.</returns>
 112    [HttpGet("Items/Filters2")]
 113    [ProducesResponseType(StatusCodes.Status200OK)]
 114    public ActionResult<QueryFilters> GetQueryFilters(
 115        [FromQuery] Guid? userId,
 116        [FromQuery] Guid? parentId,
 117        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
 118        [FromQuery] bool? isAiring,
 119        [FromQuery] bool? isMovie,
 120        [FromQuery] bool? isSports,
 121        [FromQuery] bool? isKids,
 122        [FromQuery] bool? isNews,
 123        [FromQuery] bool? isSeries,
 124        [FromQuery] bool? recursive)
 125    {
 0126        userId = RequestHelpers.GetUserId(User, userId);
 0127        var user = userId.IsNullOrEmpty()
 0128            ? null
 0129            : _userManager.GetUserById(userId.Value);
 130
 0131        BaseItem? parentItem = null;
 0132        if (includeItemTypes.Length == 1
 0133            && (includeItemTypes[0] == BaseItemKind.Trailer
 0134                || includeItemTypes[0] == BaseItemKind.Program))
 135        {
 0136            parentItem = null;
 137        }
 0138        else if (parentId.HasValue)
 139        {
 0140            parentItem = _libraryManager.GetItemById<BaseItem>(parentId.Value);
 141        }
 142
 0143        var filters = new QueryFilters();
 0144        var genreQuery = new InternalItemsQuery(user)
 0145        {
 0146            IncludeItemTypes = includeItemTypes,
 0147            DtoOptions = new DtoOptions
 0148            {
 0149                Fields = Array.Empty<ItemFields>(),
 0150                EnableImages = false,
 0151                EnableUserData = false
 0152            },
 0153            IsAiring = isAiring,
 0154            IsMovie = isMovie,
 0155            IsSports = isSports,
 0156            IsKids = isKids,
 0157            IsNews = isNews,
 0158            IsSeries = isSeries
 0159        };
 160
 0161        var streamLanguageQuery = new InternalItemsQuery(user)
 0162        {
 0163            // It's possible that different langauges are only available on alternative versions.
 0164            // To fetch them all, owned items are included.
 0165            IncludeOwnedItems = true,
 0166            IncludeItemTypes = includeItemTypes,
 0167            DtoOptions = new DtoOptions
 0168            {
 0169                Fields = Array.Empty<ItemFields>(),
 0170                EnableImages = false,
 0171                EnableUserData = false
 0172            },
 0173            IsAiring = isAiring,
 0174            IsMovie = isMovie,
 0175            IsSports = isSports,
 0176            IsKids = isKids,
 0177            IsNews = isNews,
 0178            IsSeries = isSeries
 0179        };
 180
 0181        var tagQuery = new InternalItemsQuery(user)
 0182        {
 0183            IncludeItemTypes = includeItemTypes,
 0184            DtoOptions = new DtoOptions
 0185            {
 0186                Fields = Array.Empty<ItemFields>(),
 0187                EnableImages = false,
 0188                EnableUserData = false
 0189            },
 0190            IsAiring = isAiring,
 0191            IsMovie = isMovie,
 0192            IsSports = isSports,
 0193            IsKids = isKids,
 0194            IsNews = isNews,
 0195            IsSeries = isSeries
 0196        };
 197
 0198        if ((recursive ?? true) || parentItem is UserView || parentItem is ICollectionFolder)
 199        {
 0200            var ancestorIds = parentItem is null ? Array.Empty<Guid>() : new[] { parentItem.Id };
 0201            genreQuery.AncestorIds = ancestorIds;
 0202            streamLanguageQuery.AncestorIds = ancestorIds;
 0203            tagQuery.AncestorIds = ancestorIds;
 204        }
 205        else
 206        {
 0207            genreQuery.Parent = parentItem;
 0208            streamLanguageQuery.Parent = parentItem;
 0209            tagQuery.Parent = parentItem;
 210        }
 211
 0212        if ((includeItemTypes.Contains(BaseItemKind.Series) || includeItemTypes.Contains(BaseItemKind.Season))
 0213            && !includeItemTypes.Contains(BaseItemKind.Episode))
 214        {
 215            // streams are joined on epsiodes not shows or seasons
 0216            streamLanguageQuery.IncludeItemTypes = [.. includeItemTypes, BaseItemKind.Episode];
 217        }
 218
 0219        if (includeItemTypes.Length == 1
 0220            && (includeItemTypes[0] == BaseItemKind.MusicAlbum
 0221                || includeItemTypes[0] == BaseItemKind.MusicVideo
 0222                || includeItemTypes[0] == BaseItemKind.MusicArtist
 0223                || includeItemTypes[0] == BaseItemKind.Audio))
 224        {
 0225            filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameGuidPair
 0226            {
 0227                Name = i.Item.Name,
 0228                Id = i.Item.Id
 0229            }).ToArray();
 230        }
 231        else
 232        {
 0233            filters.Genres = _libraryManager.GetGenres(genreQuery).Items.Select(i => new NameGuidPair
 0234            {
 0235                Name = i.Item.Name,
 0236                Id = i.Item.Id
 0237            }).ToArray();
 238        }
 239
 0240        filters.Tags = _libraryManager.GetTagNames(tagQuery);
 241
 0242        if (includeItemTypes.Contains(BaseItemKind.Movie)
 0243            || includeItemTypes.Contains(BaseItemKind.Series)
 0244            || includeItemTypes.Contains(BaseItemKind.Season)
 0245            || includeItemTypes.Contains(BaseItemKind.Episode))
 246        {
 0247            filters.AudioLanguages = _libraryManager
 0248                .GetMediaStreamLanguages(MediaStreamType.Audio, streamLanguageQuery)
 0249                .Select(language =>
 0250                {
 0251                    var culture = _localization.FindLanguageInfo(language);
 0252                    return new NameValuePair
 0253                    {
 0254                        Name = culture is null ? language : $"{culture.DisplayName} ({language})",
 0255                        Value = language
 0256                    };
 0257                })
 0258                .OrderBy(l => l.Name)
 0259                .ToArray();
 0260            filters.SubtitleLanguages = _libraryManager
 0261                .GetMediaStreamLanguages(MediaStreamType.Subtitle, streamLanguageQuery)
 0262                .Select(language =>
 0263                {
 0264                    var culture = _localization.FindLanguageInfo(language);
 0265                    return new NameValuePair
 0266                    {
 0267                        Name = culture is null ? language : $"{culture.DisplayName} ({language})",
 0268                        Value = language
 0269                    };
 0270                })
 0271                .OrderBy(l => l.Name)
 0272                .ToArray();
 273        }
 274
 0275        return filters;
 276    }
 277}