< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.SearchController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/SearchController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 99
Coverable lines: 99
Total lines: 267
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 44
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 3/6/2026 - 12:14:09 AM Line coverage: 0% (0/98) Branch coverage: 0% (0/44) Total lines: 2666/8/2026 - 12:16:15 AM Line coverage: 0% (0/99) Branch coverage: 0% (0/44) Total lines: 267 3/6/2026 - 12:14:09 AM Line coverage: 0% (0/98) Branch coverage: 0% (0/44) Total lines: 2666/8/2026 - 12:16:15 AM Line coverage: 0% (0/99) Branch coverage: 0% (0/44) Total lines: 267

Coverage delta

Coverage delta 1 -1

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
GetSearchHints()100%210%
GetSearchHintResult(...)0%600240%
SetThumbImageInfo(...)0%156120%
SetBackdropImageInfo(...)0%7280%
GetParentWithImage(...)100%210%

File(s)

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

#LineLine coverage
 1using System;
 2using System.ComponentModel;
 3using System.ComponentModel.DataAnnotations;
 4using System.Globalization;
 5using System.Linq;
 6using System.Threading.Tasks;
 7using Jellyfin.Api.Helpers;
 8using Jellyfin.Api.ModelBinders;
 9using Jellyfin.Data.Enums;
 10using Jellyfin.Extensions;
 11using MediaBrowser.Controller.Drawing;
 12using MediaBrowser.Controller.Dto;
 13using MediaBrowser.Controller.Entities;
 14using MediaBrowser.Controller.Entities.Audio;
 15using MediaBrowser.Controller.Entities.TV;
 16using MediaBrowser.Controller.Library;
 17using MediaBrowser.Controller.LiveTv;
 18using MediaBrowser.Model.Entities;
 19using MediaBrowser.Model.Search;
 20using Microsoft.AspNetCore.Authorization;
 21using Microsoft.AspNetCore.Http;
 22using Microsoft.AspNetCore.Mvc;
 23
 24namespace Jellyfin.Api.Controllers;
 25
 26/// <summary>
 27/// Search controller.
 28/// </summary>
 29[Route("Search/Hints")]
 30[Authorize]
 31public class SearchController : BaseJellyfinApiController
 32{
 33    private readonly ISearchManager _searchManager;
 34    private readonly ILibraryManager _libraryManager;
 35    private readonly IDtoService _dtoService;
 36    private readonly IImageProcessor _imageProcessor;
 37
 38    /// <summary>
 39    /// Initializes a new instance of the <see cref="SearchController"/> class.
 40    /// </summary>
 41    /// <param name="searchManager">Instance of <see cref="ISearchManager"/> interface.</param>
 42    /// <param name="libraryManager">Instance of <see cref="ILibraryManager"/> interface.</param>
 43    /// <param name="dtoService">Instance of <see cref="IDtoService"/> interface.</param>
 44    /// <param name="imageProcessor">Instance of <see cref="IImageProcessor"/> interface.</param>
 045    public SearchController(
 046        ISearchManager searchManager,
 047        ILibraryManager libraryManager,
 048        IDtoService dtoService,
 049        IImageProcessor imageProcessor)
 50    {
 051        _searchManager = searchManager;
 052        _libraryManager = libraryManager;
 053        _dtoService = dtoService;
 054        _imageProcessor = imageProcessor;
 055    }
 56
 57    /// <summary>
 58    /// Gets the search hint result.
 59    /// </summary>
 60    /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped fr
 61    /// <param name="limit">Optional. The maximum number of records to return.</param>
 62    /// <param name="userId">Optional. Supply a user id to search within a user's library or omit to search all.</param>
 63    /// <param name="searchTerm">The search term to filter on.</param>
 64    /// <param name="includeItemTypes">If specified, only results with the specified item types are returned. This allow
 65    /// <param name="excludeItemTypes">If specified, results with these item types are filtered out. This allows multipl
 66    /// <param name="mediaTypes">If specified, only results with the specified media types are returned. This allows mul
 67    /// <param name="parentId">If specified, only children of the parent are returned.</param>
 68    /// <param name="isMovie">Optional filter for movies.</param>
 69    /// <param name="isSeries">Optional filter for series.</param>
 70    /// <param name="isNews">Optional filter for news.</param>
 71    /// <param name="isKids">Optional filter for kids.</param>
 72    /// <param name="isSports">Optional filter for sports.</param>
 73    /// <param name="includePeople">Optional filter whether to include people.</param>
 74    /// <param name="includeMedia">Optional filter whether to include media.</param>
 75    /// <param name="includeGenres">Optional filter whether to include genres.</param>
 76    /// <param name="includeStudios">Optional filter whether to include studios.</param>
 77    /// <param name="includeArtists">Optional filter whether to include artists.</param>
 78    /// <response code="200">Search hint returned.</response>
 79    /// <returns>An <see cref="SearchHintResult"/> with the results of the search.</returns>
 80    [HttpGet]
 81    [Description("Gets search hints based on a search term")]
 82    [ProducesResponseType(StatusCodes.Status200OK)]
 83    public async Task<ActionResult<SearchHintResult>> GetSearchHints(
 84        [FromQuery] int? startIndex,
 85        [FromQuery] int? limit,
 86        [FromQuery] Guid? userId,
 87        [FromQuery, Required] string searchTerm,
 88        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
 89        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
 90        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
 91        [FromQuery] Guid? parentId,
 92        [FromQuery] bool? isMovie,
 93        [FromQuery] bool? isSeries,
 94        [FromQuery] bool? isNews,
 95        [FromQuery] bool? isKids,
 96        [FromQuery] bool? isSports,
 97        [FromQuery] bool includePeople = true,
 98        [FromQuery] bool includeMedia = true,
 99        [FromQuery] bool includeGenres = true,
 100        [FromQuery] bool includeStudios = true,
 101        [FromQuery] bool includeArtists = true)
 102    {
 0103        userId = RequestHelpers.GetUserId(User, userId);
 0104        var result = await _searchManager.GetSearchHintsAsync(new SearchQuery
 0105        {
 0106            Limit = limit,
 0107            SearchTerm = searchTerm,
 0108            IncludeArtists = includeArtists,
 0109            IncludeGenres = includeGenres,
 0110            IncludeMedia = includeMedia,
 0111            IncludePeople = includePeople,
 0112            IncludeStudios = includeStudios,
 0113            StartIndex = startIndex,
 0114            UserId = userId.Value,
 0115            IncludeItemTypes = includeItemTypes,
 0116            ExcludeItemTypes = excludeItemTypes,
 0117            MediaTypes = mediaTypes,
 0118            ParentId = parentId,
 0119
 0120            IsKids = isKids,
 0121            IsMovie = isMovie,
 0122            IsNews = isNews,
 0123            IsSeries = isSeries,
 0124            IsSports = isSports
 0125        }).ConfigureAwait(false);
 126
 0127        return new SearchHintResult(result.Items.Select(GetSearchHintResult).ToArray(), result.TotalRecordCount);
 0128    }
 129
 130    /// <summary>
 131    /// Gets the search hint result.
 132    /// </summary>
 133    /// <param name="hintInfo">The hint info.</param>
 134    /// <returns>SearchHintResult.</returns>
 135    private SearchHint GetSearchHintResult(SearchHintInfo hintInfo)
 136    {
 0137        var item = hintInfo.Item;
 138
 0139        var result = new SearchHint
 0140        {
 0141            Name = item.Name,
 0142            IndexNumber = item.IndexNumber,
 0143            ParentIndexNumber = item.ParentIndexNumber,
 0144            Id = item.Id,
 0145            Type = item.GetBaseItemKind(),
 0146            MediaType = item.MediaType,
 0147            MatchedTerm = hintInfo.MatchedTerm,
 0148            RunTimeTicks = item.RunTimeTicks,
 0149            ProductionYear = item.ProductionYear,
 0150            ChannelId = item.ChannelId,
 0151            EndDate = item.EndDate
 0152        };
 153
 154#pragma warning disable CS0618
 155        // Kept for compatibility with older clients
 0156        result.ItemId = result.Id;
 157#pragma warning restore CS0618
 158
 0159        if (item.IsFolder)
 160        {
 0161            result.IsFolder = true;
 162        }
 163
 0164        var primaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary);
 165
 0166        if (primaryImageTag is not null)
 167        {
 0168            result.PrimaryImageTag = primaryImageTag;
 0169            result.PrimaryImageAspectRatio = _dtoService.GetPrimaryImageAspectRatio(item);
 170        }
 171
 0172        SetThumbImageInfo(result, item);
 0173        SetBackdropImageInfo(result, item);
 174
 175        switch (item)
 176        {
 177            case IHasSeries hasSeries:
 0178                result.Series = hasSeries.SeriesName;
 0179                break;
 180            case LiveTvProgram program:
 0181                result.StartDate = program.StartDate;
 0182                break;
 183            case Series series:
 0184                if (series.Status.HasValue)
 185                {
 0186                    result.Status = series.Status.Value.ToString();
 187                }
 188
 0189                break;
 190            case MusicAlbum album:
 0191                result.Artists = album.Artists;
 0192                result.AlbumArtist = album.AlbumArtist;
 0193                break;
 194            case Audio song:
 0195                result.AlbumArtist = song.AlbumArtists?.FirstOrDefault();
 0196                result.Artists = song.Artists;
 197
 0198                MusicAlbum musicAlbum = song.AlbumEntity;
 199
 0200                if (musicAlbum is not null)
 201                {
 0202                    result.Album = musicAlbum.Name;
 0203                    result.AlbumId = musicAlbum.Id;
 204                }
 205                else
 206                {
 0207                    result.Album = song.Album;
 208                }
 209
 210                break;
 211        }
 212
 0213        if (!item.ChannelId.IsEmpty())
 214        {
 0215            var channel = _libraryManager.GetItemById<BaseItem>(item.ChannelId);
 0216            result.ChannelName = channel?.Name;
 217        }
 218
 0219        return result;
 220    }
 221
 222    private void SetThumbImageInfo(SearchHint hint, BaseItem item)
 223    {
 0224        var itemWithImage = item.HasImage(ImageType.Thumb) ? item : null;
 225
 0226        if (itemWithImage is null && item is Episode)
 227        {
 0228            itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb);
 229        }
 230
 0231        itemWithImage ??= GetParentWithImage<BaseItem>(item, ImageType.Thumb);
 232
 0233        if (itemWithImage is not null)
 234        {
 0235            var tag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Thumb);
 236
 0237            if (tag is not null)
 238            {
 0239                hint.ThumbImageTag = tag;
 0240                hint.ThumbImageItemId = itemWithImage.Id.ToString("N", CultureInfo.InvariantCulture);
 241            }
 242        }
 0243    }
 244
 245    private void SetBackdropImageInfo(SearchHint hint, BaseItem item)
 246    {
 0247        var itemWithImage = (item.HasImage(ImageType.Backdrop) ? item : null)
 0248            ?? GetParentWithImage<BaseItem>(item, ImageType.Backdrop);
 249
 0250        if (itemWithImage is not null)
 251        {
 0252            var tag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Backdrop);
 253
 0254            if (tag is not null)
 255            {
 0256                hint.BackdropImageTag = tag;
 0257                hint.BackdropImageItemId = itemWithImage.Id.ToString("N", CultureInfo.InvariantCulture);
 258            }
 259        }
 0260    }
 261
 262    private T? GetParentWithImage<T>(BaseItem item, ImageType type)
 263        where T : BaseItem
 264    {
 0265        return item.GetParents().OfType<T>().FirstOrDefault(i => i.HasImage(type));
 266    }
 267}