Merge pull request #3441 from Ullmie02/api-albums

Move AlbumsService to Jellyfin.Api
This commit is contained in:
Patrick Barron 2020-06-25 17:30:34 +00:00 committed by GitHub
commit 7c96018410
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 315 additions and 132 deletions

View File

@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api.Controllers
{
/// <summary>
/// The albums controller.
/// </summary>
public class AlbumsController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
private readonly IDtoService _dtoService;
/// <summary>
/// Initializes a new instance of the <see cref="AlbumsController"/> class.
/// </summary>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
public AlbumsController(
IUserManager userManager,
ILibraryManager libraryManager,
IDtoService dtoService)
{
_userManager = userManager;
_libraryManager = libraryManager;
_dtoService = dtoService;
}
/// <summary>
/// Finds albums similar to a given album.
/// </summary>
/// <param name="albumId">The album id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
/// <param name="excludeArtistIds">Optional. Ids of artists to exclude.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <response code="200">Similar albums returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with similar albums.</returns>
[HttpGet("/Albums/{albumId}/Similar")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarAlbums(
[FromRoute] string albumId,
[FromQuery] Guid userId,
[FromQuery] string excludeArtistIds,
[FromQuery] int? limit)
{
var dtoOptions = new DtoOptions().AddClientFields(Request);
return SimilarItemsHelper.GetSimilarItemsResult(
dtoOptions,
_userManager,
_libraryManager,
_dtoService,
userId,
albumId,
excludeArtistIds,
limit,
new[] { typeof(MusicAlbum) },
GetAlbumSimilarityScore);
}
/// <summary>
/// Finds artists similar to a given artist.
/// </summary>
/// <param name="artistId">The artist id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
/// <param name="excludeArtistIds">Optional. Ids of artists to exclude.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <response code="200">Similar artists returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with similar artists.</returns>
[HttpGet("/Artists/{artistId}/Similar")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarArtists(
[FromRoute] string artistId,
[FromQuery] Guid userId,
[FromQuery] string excludeArtistIds,
[FromQuery] int? limit)
{
var dtoOptions = new DtoOptions().AddClientFields(Request);
return SimilarItemsHelper.GetSimilarItemsResult(
dtoOptions,
_userManager,
_libraryManager,
_dtoService,
userId,
artistId,
excludeArtistIds,
limit,
new[] { typeof(MusicArtist) },
SimilarItemsHelper.GetSimiliarityScore);
}
/// <summary>
/// Gets a similairty score of two albums.
/// </summary>
/// <param name="item1">The first item.</param>
/// <param name="item1People">The item1 people.</param>
/// <param name="allPeople">All people.</param>
/// <param name="item2">The second item.</param>
/// <returns>System.Int32.</returns>
private int GetAlbumSimilarityScore(BaseItem item1, List<PersonInfo> item1People, List<PersonInfo> allPeople, BaseItem item2)
{
var points = SimilarItemsHelper.GetSimiliarityScore(item1, item1People, allPeople, item2);
var album1 = (MusicAlbum)item1;
var album2 = (MusicAlbum)item2;
var artists1 = album1
.GetAllArtists()
.DistinctNames()
.ToList();
var artists2 = new HashSet<string>(
album2.GetAllArtists().DistinctNames(),
StringComparer.OrdinalIgnoreCase);
return points + artists1.Where(artists2.Contains).Sum(i => 5);
}
}
}

View File

@ -0,0 +1,182 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
namespace Jellyfin.Api.Helpers
{
/// <summary>
/// The similar items helper class.
/// </summary>
public static class SimilarItemsHelper
{
internal static QueryResult<BaseItemDto> GetSimilarItemsResult(
DtoOptions dtoOptions,
IUserManager userManager,
ILibraryManager libraryManager,
IDtoService dtoService,
Guid userId,
string id,
string excludeArtistIds,
int? limit,
Type[] includeTypes,
Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
{
var user = !userId.Equals(Guid.Empty) ? userManager.GetUserById(userId) : null;
var item = string.IsNullOrEmpty(id) ?
(!userId.Equals(Guid.Empty) ? libraryManager.GetUserRootFolder() :
libraryManager.RootFolder) : libraryManager.GetItemById(id);
var query = new InternalItemsQuery(user)
{
IncludeItemTypes = includeTypes.Select(i => i.Name).ToArray(),
Recursive = true,
DtoOptions = dtoOptions
};
query.ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds);
var inputItems = libraryManager.GetItemList(query);
var items = GetSimilaritems(item, libraryManager, inputItems, getSimilarityScore)
.ToList();
var returnItems = items;
if (limit.HasValue)
{
returnItems = returnItems.Take(limit.Value).ToList();
}
var dtos = dtoService.GetBaseItemDtos(returnItems, dtoOptions, user);
return new QueryResult<BaseItemDto>
{
Items = dtos,
TotalRecordCount = items.Count
};
}
/// <summary>
/// Gets the similaritems.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="inputItems">The input items.</param>
/// <param name="getSimilarityScore">The get similarity score.</param>
/// <returns>IEnumerable{BaseItem}.</returns>
private static IEnumerable<BaseItem> GetSimilaritems(
BaseItem item,
ILibraryManager libraryManager,
IEnumerable<BaseItem> inputItems,
Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
{
var itemId = item.Id;
inputItems = inputItems.Where(i => i.Id != itemId);
var itemPeople = libraryManager.GetPeople(item);
var allPeople = libraryManager.GetPeople(new InternalPeopleQuery
{
AppearsInItemId = item.Id
});
return inputItems.Select(i => new Tuple<BaseItem, int>(i, getSimilarityScore(item, itemPeople, allPeople, i)))
.Where(i => i.Item2 > 2)
.OrderByDescending(i => i.Item2)
.Select(i => i.Item1);
}
private static IEnumerable<string> GetTags(BaseItem item)
{
return item.Tags;
}
/// <summary>
/// Gets the similiarity score.
/// </summary>
/// <param name="item1">The item1.</param>
/// <param name="item1People">The item1 people.</param>
/// <param name="allPeople">All people.</param>
/// <param name="item2">The item2.</param>
/// <returns>System.Int32.</returns>
internal static int GetSimiliarityScore(BaseItem item1, List<PersonInfo> item1People, List<PersonInfo> allPeople, BaseItem item2)
{
var points = 0;
if (!string.IsNullOrEmpty(item1.OfficialRating) && string.Equals(item1.OfficialRating, item2.OfficialRating, StringComparison.OrdinalIgnoreCase))
{
points += 10;
}
// Find common genres
points += item1.Genres.Where(i => item2.Genres.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10);
// Find common tags
points += GetTags(item1).Where(i => GetTags(item2).Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10);
// Find common studios
points += item1.Studios.Where(i => item2.Studios.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 3);
var item2PeopleNames = allPeople.Where(i => i.ItemId == item2.Id)
.Select(i => i.Name)
.Where(i => !string.IsNullOrWhiteSpace(i))
.DistinctNames()
.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
points += item1People.Where(i => item2PeopleNames.ContainsKey(i.Name)).Sum(i =>
{
if (string.Equals(i.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Director, StringComparison.OrdinalIgnoreCase))
{
return 5;
}
if (string.Equals(i.Type, PersonType.Actor, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Actor, StringComparison.OrdinalIgnoreCase))
{
return 3;
}
if (string.Equals(i.Type, PersonType.Composer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Composer, StringComparison.OrdinalIgnoreCase))
{
return 3;
}
if (string.Equals(i.Type, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
{
return 3;
}
if (string.Equals(i.Type, PersonType.Writer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Writer, StringComparison.OrdinalIgnoreCase))
{
return 2;
}
return 1;
});
if (item1.ProductionYear.HasValue && item2.ProductionYear.HasValue)
{
var diff = Math.Abs(item1.ProductionYear.Value - item2.ProductionYear.Value);
// Add if they came out within the same decade
if (diff < 10)
{
points += 2;
}
// And more if within five years
if (diff < 5)
{
points += 2;
}
}
return points;
}
}
}

View File

@ -1,132 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Music
{
[Route("/Albums/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")]
public class GetSimilarAlbums : BaseGetSimilarItemsFromItem
{
}
[Route("/Artists/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")]
public class GetSimilarArtists : BaseGetSimilarItemsFromItem
{
}
[Authenticated]
public class AlbumsService : BaseApiService
{
/// <summary>
/// The _user manager
/// </summary>
private readonly IUserManager _userManager;
/// <summary>
/// The _user data repository
/// </summary>
private readonly IUserDataManager _userDataRepository;
/// <summary>
/// The _library manager
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly IItemRepository _itemRepo;
private readonly IDtoService _dtoService;
private readonly IAuthorizationContext _authContext;
public AlbumsService(
ILogger<AlbumsService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IUserManager userManager,
IUserDataManager userDataRepository,
ILibraryManager libraryManager,
IItemRepository itemRepo,
IDtoService dtoService,
IAuthorizationContext authContext)
: base(logger, serverConfigurationManager, httpResultFactory)
{
_userManager = userManager;
_userDataRepository = userDataRepository;
_libraryManager = libraryManager;
_itemRepo = itemRepo;
_dtoService = dtoService;
_authContext = authContext;
}
public object Get(GetSimilarArtists request)
{
var dtoOptions = GetDtoOptions(_authContext, request);
var result = SimilarItemsHelper.GetSimilarItemsResult(
dtoOptions,
_userManager,
_itemRepo,
_libraryManager,
_userDataRepository,
_dtoService,
request, new[] { typeof(MusicArtist) },
SimilarItemsHelper.GetSimiliarityScore);
return ToOptimizedResult(result);
}
/// <summary>
/// Gets the specified request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
public object Get(GetSimilarAlbums request)
{
var dtoOptions = GetDtoOptions(_authContext, request);
var result = SimilarItemsHelper.GetSimilarItemsResult(
dtoOptions,
_userManager,
_itemRepo,
_libraryManager,
_userDataRepository,
_dtoService,
request, new[] { typeof(MusicAlbum) },
GetAlbumSimilarityScore);
return ToOptimizedResult(result);
}
/// <summary>
/// Gets the album similarity score.
/// </summary>
/// <param name="item1">The item1.</param>
/// <param name="item1People">The item1 people.</param>
/// <param name="allPeople">All people.</param>
/// <param name="item2">The item2.</param>
/// <returns>System.Int32.</returns>
private int GetAlbumSimilarityScore(BaseItem item1, List<PersonInfo> item1People, List<PersonInfo> allPeople, BaseItem item2)
{
var points = SimilarItemsHelper.GetSimiliarityScore(item1, item1People, allPeople, item2);
var album1 = (MusicAlbum)item1;
var album2 = (MusicAlbum)item2;
var artists1 = album1
.GetAllArtists()
.DistinctNames()
.ToList();
var artists2 = new HashSet<string>(
album2.GetAllArtists().DistinctNames(),
StringComparer.OrdinalIgnoreCase);
return points + artists1.Where(artists2.Contains).Sum(i => 5);
}
}
}