2020-06-02 13:07:07 +00:00
using System ;
using System.ComponentModel ;
using System.ComponentModel.DataAnnotations ;
using System.Globalization ;
using System.Linq ;
2023-02-17 22:16:08 +00:00
using Jellyfin.Api.Helpers ;
2020-11-17 03:29:46 +00:00
using Jellyfin.Api.ModelBinders ;
2021-02-11 00:09:23 +00:00
using Jellyfin.Data.Enums ;
2022-06-13 22:27:16 +00:00
using Jellyfin.Extensions ;
2020-06-02 13:07:07 +00:00
using MediaBrowser.Controller.Drawing ;
using MediaBrowser.Controller.Dto ;
using MediaBrowser.Controller.Entities ;
using MediaBrowser.Controller.Entities.Audio ;
using MediaBrowser.Controller.Entities.TV ;
using MediaBrowser.Controller.Library ;
using MediaBrowser.Controller.LiveTv ;
using MediaBrowser.Model.Entities ;
using MediaBrowser.Model.Search ;
2020-06-07 13:33:54 +00:00
using Microsoft.AspNetCore.Authorization ;
2020-06-02 13:07:07 +00:00
using Microsoft.AspNetCore.Http ;
using Microsoft.AspNetCore.Mvc ;
2023-01-31 11:18:10 +00:00
namespace Jellyfin.Api.Controllers ;
/// <summary>
/// Search controller.
/// </summary>
[Route("Search/Hints")]
2023-02-08 22:55:26 +00:00
[Authorize]
2023-01-31 11:18:10 +00:00
public class SearchController : BaseJellyfinApiController
2020-06-02 13:07:07 +00:00
{
2023-01-31 11:18:10 +00:00
private readonly ISearchEngine _searchEngine ;
private readonly ILibraryManager _libraryManager ;
private readonly IDtoService _dtoService ;
private readonly IImageProcessor _imageProcessor ;
2020-06-02 13:07:07 +00:00
/// <summary>
2023-01-31 11:18:10 +00:00
/// Initializes a new instance of the <see cref="SearchController"/> class.
2020-06-02 13:07:07 +00:00
/// </summary>
2023-01-31 11:18:10 +00:00
/// <param name="searchEngine">Instance of <see cref="ISearchEngine"/> interface.</param>
/// <param name="libraryManager">Instance of <see cref="ILibraryManager"/> interface.</param>
/// <param name="dtoService">Instance of <see cref="IDtoService"/> interface.</param>
/// <param name="imageProcessor">Instance of <see cref="IImageProcessor"/> interface.</param>
public SearchController (
ISearchEngine searchEngine ,
ILibraryManager libraryManager ,
IDtoService dtoService ,
IImageProcessor imageProcessor )
2020-06-02 13:07:07 +00:00
{
2023-01-31 11:18:10 +00:00
_searchEngine = searchEngine ;
_libraryManager = libraryManager ;
_dtoService = dtoService ;
_imageProcessor = imageProcessor ;
}
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
/// <summary>
/// Gets the search hint result.
/// </summary>
/// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <param name="userId">Optional. Supply a user id to search within a user's library or omit to search all.</param>
/// <param name="searchTerm">The search term to filter on.</param>
/// <param name="includeItemTypes">If specified, only results with the specified item types are returned. This allows multiple, comma delimited.</param>
/// <param name="excludeItemTypes">If specified, results with these item types are filtered out. This allows multiple, comma delimited.</param>
/// <param name="mediaTypes">If specified, only results with the specified media types are returned. This allows multiple, comma delimited.</param>
/// <param name="parentId">If specified, only children of the parent are returned.</param>
/// <param name="isMovie">Optional filter for movies.</param>
/// <param name="isSeries">Optional filter for series.</param>
/// <param name="isNews">Optional filter for news.</param>
/// <param name="isKids">Optional filter for kids.</param>
/// <param name="isSports">Optional filter for sports.</param>
/// <param name="includePeople">Optional filter whether to include people.</param>
/// <param name="includeMedia">Optional filter whether to include media.</param>
/// <param name="includeGenres">Optional filter whether to include genres.</param>
/// <param name="includeStudios">Optional filter whether to include studios.</param>
/// <param name="includeArtists">Optional filter whether to include artists.</param>
/// <response code="200">Search hint returned.</response>
/// <returns>An <see cref="SearchHintResult"/> with the results of the search.</returns>
[HttpGet]
[Description("Gets search hints based on a search term")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult < SearchHintResult > GetSearchHints (
[FromQuery] int? startIndex ,
[FromQuery] int? limit ,
[FromQuery] Guid ? userId ,
[FromQuery, Required] string searchTerm ,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind [ ] includeItemTypes ,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind [ ] excludeItemTypes ,
2023-05-13 18:44:31 +00:00
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType [ ] mediaTypes ,
2023-01-31 11:18:10 +00:00
[FromQuery] Guid ? parentId ,
[FromQuery] bool? isMovie ,
[FromQuery] bool? isSeries ,
[FromQuery] bool? isNews ,
[FromQuery] bool? isKids ,
[FromQuery] bool? isSports ,
[FromQuery] bool includePeople = true ,
[FromQuery] bool includeMedia = true ,
[FromQuery] bool includeGenres = true ,
[FromQuery] bool includeStudios = true ,
[FromQuery] bool includeArtists = true )
{
2023-02-17 22:16:08 +00:00
userId = RequestHelpers . GetUserId ( User , userId ) ;
2023-01-31 11:18:10 +00:00
var result = _searchEngine . GetSearchHints ( new SearchQuery
2020-06-02 13:07:07 +00:00
{
2023-01-31 11:18:10 +00:00
Limit = limit ,
SearchTerm = searchTerm ,
IncludeArtists = includeArtists ,
IncludeGenres = includeGenres ,
IncludeMedia = includeMedia ,
IncludePeople = includePeople ,
IncludeStudios = includeStudios ,
StartIndex = startIndex ,
2023-02-17 22:16:08 +00:00
UserId = userId . Value ,
2023-01-31 11:18:10 +00:00
IncludeItemTypes = includeItemTypes ,
ExcludeItemTypes = excludeItemTypes ,
MediaTypes = mediaTypes ,
ParentId = parentId ,
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
IsKids = isKids ,
IsMovie = isMovie ,
IsNews = isNews ,
IsSeries = isSeries ,
IsSports = isSports
} ) ;
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
return new SearchHintResult ( result . Items . Select ( GetSearchHintResult ) . ToArray ( ) , result . TotalRecordCount ) ;
}
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
/// <summary>
/// Gets the search hint result.
/// </summary>
/// <param name="hintInfo">The hint info.</param>
/// <returns>SearchHintResult.</returns>
private SearchHint GetSearchHintResult ( SearchHintInfo hintInfo )
{
var item = hintInfo . Item ;
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
var result = new SearchHint
2020-06-02 13:07:07 +00:00
{
2023-01-31 11:18:10 +00:00
Name = item . Name ,
IndexNumber = item . IndexNumber ,
ParentIndexNumber = item . ParentIndexNumber ,
Id = item . Id ,
Type = item . GetBaseItemKind ( ) ,
MediaType = item . MediaType ,
MatchedTerm = hintInfo . MatchedTerm ,
RunTimeTicks = item . RunTimeTicks ,
ProductionYear = item . ProductionYear ,
ChannelId = item . ChannelId ,
EndDate = item . EndDate
} ;
2020-06-02 13:07:07 +00:00
2022-08-17 18:14:07 +00:00
#pragma warning disable CS0618
2023-01-31 11:18:10 +00:00
// Kept for compatibility with older clients
result . ItemId = result . Id ;
2022-08-17 18:14:07 +00:00
#pragma warning restore CS0618
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
if ( item . IsFolder )
{
result . IsFolder = true ;
}
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
var primaryImageTag = _imageProcessor . GetImageCacheTag ( item , ImageType . Primary ) ;
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
if ( primaryImageTag is not null )
{
result . PrimaryImageTag = primaryImageTag ;
result . PrimaryImageAspectRatio = _dtoService . GetPrimaryImageAspectRatio ( item ) ;
}
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
SetThumbImageInfo ( result , item ) ;
SetBackdropImageInfo ( result , item ) ;
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
switch ( item )
{
case IHasSeries hasSeries :
result . Series = hasSeries . SeriesName ;
break ;
case LiveTvProgram program :
result . StartDate = program . StartDate ;
break ;
case Series series :
if ( series . Status . HasValue )
{
result . Status = series . Status . Value . ToString ( ) ;
}
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
break ;
case MusicAlbum album :
result . Artists = album . Artists ;
result . AlbumArtist = album . AlbumArtist ;
break ;
case Audio song :
result . AlbumArtist = song . AlbumArtists ? . FirstOrDefault ( ) ;
result . Artists = song . Artists ;
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
MusicAlbum musicAlbum = song . AlbumEntity ;
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
if ( musicAlbum is not null )
{
result . Album = musicAlbum . Name ;
result . AlbumId = musicAlbum . Id ;
}
else
{
result . Album = song . Album ;
}
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
break ;
}
2020-06-02 13:07:07 +00:00
2024-01-17 15:51:39 +00:00
if ( ! item . ChannelId . IsEmpty ( ) )
2023-01-31 11:18:10 +00:00
{
2024-04-14 14:18:36 +00:00
var channel = _libraryManager . GetItemById < BaseItem > ( item . ChannelId ) ;
2023-01-31 11:18:10 +00:00
result . ChannelName = channel ? . Name ;
2020-06-02 13:07:07 +00:00
}
2023-01-31 11:18:10 +00:00
return result ;
}
private void SetThumbImageInfo ( SearchHint hint , BaseItem item )
{
var itemWithImage = item . HasImage ( ImageType . Thumb ) ? item : null ;
if ( itemWithImage is null & & item is Episode )
2020-06-02 13:07:07 +00:00
{
2023-01-31 11:18:10 +00:00
itemWithImage = GetParentWithImage < Series > ( item , ImageType . Thumb ) ;
}
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
itemWithImage ? ? = GetParentWithImage < BaseItem > ( item , ImageType . Thumb ) ;
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
if ( itemWithImage is not null )
{
var tag = _imageProcessor . GetImageCacheTag ( itemWithImage , ImageType . Thumb ) ;
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
if ( tag is not null )
2020-06-02 13:07:07 +00:00
{
2023-01-31 11:18:10 +00:00
hint . ThumbImageTag = tag ;
hint . ThumbImageItemId = itemWithImage . Id . ToString ( "N" , CultureInfo . InvariantCulture ) ;
2020-06-02 13:07:07 +00:00
}
}
2023-01-31 11:18:10 +00:00
}
private void SetBackdropImageInfo ( SearchHint hint , BaseItem item )
{
var itemWithImage = ( item . HasImage ( ImageType . Backdrop ) ? item : null )
? ? GetParentWithImage < BaseItem > ( item , ImageType . Backdrop ) ;
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
if ( itemWithImage is not null )
2020-06-02 13:07:07 +00:00
{
2023-01-31 11:18:10 +00:00
var tag = _imageProcessor . GetImageCacheTag ( itemWithImage , ImageType . Backdrop ) ;
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
if ( tag is not null )
2020-06-02 13:07:07 +00:00
{
2023-01-31 11:18:10 +00:00
hint . BackdropImageTag = tag ;
hint . BackdropImageItemId = itemWithImage . Id . ToString ( "N" , CultureInfo . InvariantCulture ) ;
2020-06-02 13:07:07 +00:00
}
}
2023-01-31 11:18:10 +00:00
}
2020-06-02 13:07:07 +00:00
2023-01-31 11:18:10 +00:00
private T ? GetParentWithImage < T > ( BaseItem item , ImageType type )
where T : BaseItem
{
return item . GetParents ( ) . OfType < T > ( ) . FirstOrDefault ( i = > i . HasImage ( type ) ) ;
2020-06-02 13:07:07 +00:00
}
}