using System; using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; 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; using Genre = MediaBrowser.Controller.Entities.Genre; namespace Jellyfin.Api.Controllers; /// /// The genres controller. /// [Authorize] public class GenresController : BaseJellyfinApiController { private readonly IUserManager _userManager; private readonly ILibraryManager _libraryManager; private readonly IDtoService _dtoService; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. public GenresController( IUserManager userManager, ILibraryManager libraryManager, IDtoService dtoService) { _userManager = userManager; _libraryManager = libraryManager; _dtoService = dtoService; } /// /// Gets all genres from a given item, folder, or the entire library. /// /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. /// The search term. /// Specify this to localize the search to a specific item or folder. Omit to use the root. /// Optional. Specify additional fields of information to return in the output. /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited. /// Optional filter by items that are marked as favorite, or not. /// Optional, the max number of images to return, per image type. /// Optional. The image types to include in the output. /// User id. /// Optional filter by items whose name is sorted equally or greater than a given input string. /// Optional filter by items whose name is sorted equally than a given input string. /// Optional filter by items whose name is equally or lesser than a given input string. /// Optional. Specify one or more sort orders, comma delimited. /// Sort Order - Ascending,Descending. /// Optional, include image information in output. /// Optional. Include total record count. /// Genres returned. /// An containing the queryresult of genres. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetGenres( [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isFavorite, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] Guid? userId, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { userId = RequestHelpers.GetUserId(User, userId); var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes); User? user = userId.Value.Equals(default) ? null : _userManager.GetUserById(userId.Value); var parentItem = _libraryManager.GetParentItem(parentId, userId); var query = new InternalItemsQuery(user) { ExcludeItemTypes = excludeItemTypes, IncludeItemTypes = includeItemTypes, StartIndex = startIndex, Limit = limit, IsFavorite = isFavorite, NameLessThan = nameLessThan, NameStartsWith = nameStartsWith, NameStartsWithOrGreater = nameStartsWithOrGreater, DtoOptions = dtoOptions, SearchTerm = searchTerm, EnableTotalRecordCount = enableTotalRecordCount, OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder) }; if (parentId.HasValue) { if (parentItem is Folder) { query.AncestorIds = new[] { parentId.Value }; } else { query.ItemIds = new[] { parentId.Value }; } } QueryResult<(BaseItem, ItemCounts)> result; if (parentItem is ICollectionFolder parentCollectionFolder && (parentCollectionFolder.CollectionType == CollectionType.music || parentCollectionFolder.CollectionType == CollectionType.musicvideos)) { result = _libraryManager.GetMusicGenres(query); } else { result = _libraryManager.GetGenres(query); } var shouldIncludeItemTypes = includeItemTypes.Length != 0; return RequestHelpers.CreateQueryResult(result, dtoOptions, _dtoService, shouldIncludeItemTypes, user); } /// /// Gets a genre, by name. /// /// The genre name. /// The user id. /// Genres returned. /// An containing the genre. [HttpGet("{genreName}")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); var dtoOptions = new DtoOptions() .AddClientFields(User); Genre? item; if (genreName.Contains(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase)) { item = GetItemFromSlugName(_libraryManager, genreName, dtoOptions, BaseItemKind.Genre); } else { item = _libraryManager.GetGenre(genreName); } item ??= new Genre(); var user = userId.Value.Equals(default) ? null : _userManager.GetUserById(userId.Value); return _dtoService.GetBaseItemDto(item, dtoOptions, user); } private T? GetItemFromSlugName(ILibraryManager libraryManager, string name, DtoOptions dtoOptions, BaseItemKind baseItemKind) where T : BaseItem, new() { var result = libraryManager.GetItemList(new InternalItemsQuery { Name = name.Replace(BaseItem.SlugChar, '&'), IncludeItemTypes = new[] { baseItemKind }, DtoOptions = dtoOptions }).OfType().FirstOrDefault(); result ??= libraryManager.GetItemList(new InternalItemsQuery { Name = name.Replace(BaseItem.SlugChar, '/'), IncludeItemTypes = new[] { baseItemKind }, DtoOptions = dtoOptions }).OfType().FirstOrDefault(); result ??= libraryManager.GetItemList(new InternalItemsQuery { Name = name.Replace(BaseItem.SlugChar, '?'), IncludeItemTypes = new[] { baseItemKind }, DtoOptions = dtoOptions }).OfType().FirstOrDefault(); return result; } }