mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-09-06 03:26:07 +02:00
Merge pull request #4450 from cvium/fix_and_mildly_improve_similar
Remove duplicate /Similar endpoints
This commit is contained in:
commit
5669d1dad2
4 changed files with 89 additions and 418 deletions
|
@ -2403,11 +2403,11 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(item.OfficialRating))
|
if (string.IsNullOrEmpty(item.OfficialRating))
|
||||||
{
|
{
|
||||||
builder.Append("((OfficialRating is null) * 10)");
|
builder.Append("(OfficialRating is null * 10)");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
builder.Append("((OfficialRating=@ItemOfficialRating) * 10)");
|
builder.Append("(OfficialRating=@ItemOfficialRating * 10)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.ProductionYear.HasValue)
|
if (item.ProductionYear.HasValue)
|
||||||
|
@ -2416,8 +2416,26 @@ namespace Emby.Server.Implementations.Data
|
||||||
builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 5 Then 5 Else 0 End )");
|
builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 5 Then 5 Else 0 End )");
|
||||||
}
|
}
|
||||||
|
|
||||||
//// genres, tags
|
// genres, tags, studios, person, year?
|
||||||
builder.Append("+ ((Select count(CleanValue) from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId)) * 10)");
|
builder.Append("+ (Select count(1) * 10 from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId))");
|
||||||
|
|
||||||
|
if (item is MusicArtist)
|
||||||
|
{
|
||||||
|
// Match albums where the artist is AlbumArtist against other albums.
|
||||||
|
// It is assumed that similar albums => similar artists.
|
||||||
|
builder.Append(
|
||||||
|
@"+ (WITH artistValues AS (
|
||||||
|
SELECT DISTINCT albumValues.CleanValue
|
||||||
|
FROM ItemValues albumValues
|
||||||
|
INNER JOIN ItemValues artistAlbums ON albumValues.ItemId = artistAlbums.ItemId
|
||||||
|
INNER JOIN TypedBaseItems artistItem ON artistAlbums.CleanValue = artistItem.CleanName AND artistAlbums.TYPE = 1 AND artistItem.Guid = @SimilarItemId
|
||||||
|
), similarArtist AS (
|
||||||
|
SELECT albumValues.ItemId
|
||||||
|
FROM ItemValues albumValues
|
||||||
|
INNER JOIN ItemValues artistAlbums ON albumValues.ItemId = artistAlbums.ItemId
|
||||||
|
INNER JOIN TypedBaseItems artistItem ON artistAlbums.CleanValue = artistItem.CleanName AND artistAlbums.TYPE = 1 AND artistItem.Guid = A.Guid
|
||||||
|
) SELECT COUNT(DISTINCT(CleanValue)) * 10 FROM ItemValues WHERE ItemId IN (SELECT ItemId FROM similarArtist) AND CleanValue IN (SELECT CleanValue FROM artistValues))");
|
||||||
|
}
|
||||||
|
|
||||||
builder.Append(") as SimilarityScore");
|
builder.Append(") as SimilarityScore");
|
||||||
|
|
||||||
|
@ -5052,7 +5070,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||||
|
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
var commandText = "select ItemId, Name, Role, PersonType, SortOrder from People";
|
var commandText = "select ItemId, Name, Role, PersonType, SortOrder from People p";
|
||||||
|
|
||||||
var whereClauses = GetPeopleWhereClauses(query, null);
|
var whereClauses = GetPeopleWhereClauses(query, null);
|
||||||
|
|
||||||
|
|
|
@ -1,135 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
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>
|
|
||||||
[Route("")]
|
|
||||||
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, Required] 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, Required] 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -681,12 +681,12 @@ namespace Jellyfin.Api.Controllers
|
||||||
/// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
|
/// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
|
||||||
/// <response code="200">Similar items returned.</response>
|
/// <response code="200">Similar items returned.</response>
|
||||||
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> containing the similar items.</returns>
|
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> containing the similar items.</returns>
|
||||||
[HttpGet("Artists/{itemId}/Similar", Name = "GetSimilarArtists2")]
|
[HttpGet("Artists/{itemId}/Similar")]
|
||||||
[HttpGet("Items/{itemId}/Similar")]
|
[HttpGet("Items/{itemId}/Similar")]
|
||||||
[HttpGet("Albums/{itemId}/Similar", Name = "GetSimilarAlbums2")]
|
[HttpGet("Albums/{itemId}/Similar")]
|
||||||
[HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows2")]
|
[HttpGet("Shows/{itemId}/Similar")]
|
||||||
[HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies2")]
|
[HttpGet("Movies/{itemId}/Similar")]
|
||||||
[HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers2")]
|
[HttpGet("Trailers/{itemId}/Similar")]
|
||||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
|
public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
|
||||||
|
@ -702,33 +702,71 @@ namespace Jellyfin.Api.Controllers
|
||||||
: _libraryManager.RootFolder)
|
: _libraryManager.RootFolder)
|
||||||
: _libraryManager.GetItemById(itemId);
|
: _libraryManager.GetItemById(itemId);
|
||||||
|
|
||||||
var program = item as IHasProgramAttributes;
|
if (item is Episode || (item is IItemByName && !(item is MusicArtist)))
|
||||||
var isMovie = item is MediaBrowser.Controller.Entities.Movies.Movie || (program != null && program.IsMovie) || item is Trailer;
|
|
||||||
if (program != null && program.IsSeries)
|
|
||||||
{
|
|
||||||
return GetSimilarItemsResult(
|
|
||||||
item,
|
|
||||||
excludeArtistIds,
|
|
||||||
userId,
|
|
||||||
limit,
|
|
||||||
fields,
|
|
||||||
new[] { nameof(Series) },
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item is MediaBrowser.Controller.Entities.TV.Episode || (item is IItemByName && !(item is MusicArtist)))
|
|
||||||
{
|
{
|
||||||
return new QueryResult<BaseItemDto>();
|
return new QueryResult<BaseItemDto>();
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetSimilarItemsResult(
|
var user = userId.HasValue && !userId.Equals(Guid.Empty)
|
||||||
item,
|
? _userManager.GetUserById(userId.Value)
|
||||||
excludeArtistIds,
|
: null;
|
||||||
userId,
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
limit,
|
.AddClientFields(Request);
|
||||||
fields,
|
|
||||||
new[] { item.GetType().Name },
|
var program = item as IHasProgramAttributes;
|
||||||
isMovie);
|
bool? isMovie = item is Movie || (program != null && program.IsMovie) || item is Trailer;
|
||||||
|
bool? isSeries = item is Series || (program != null && program.IsSeries);
|
||||||
|
|
||||||
|
var includeItemTypes = new List<string>();
|
||||||
|
if (isMovie.Value)
|
||||||
|
{
|
||||||
|
includeItemTypes.Add(nameof(Movie));
|
||||||
|
if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
|
||||||
|
{
|
||||||
|
includeItemTypes.Add(nameof(Trailer));
|
||||||
|
includeItemTypes.Add(nameof(LiveTvProgram));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isSeries.Value)
|
||||||
|
{
|
||||||
|
includeItemTypes.Add(nameof(Series));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For non series and movie types these columns are typically null
|
||||||
|
isSeries = null;
|
||||||
|
isMovie = null;
|
||||||
|
includeItemTypes.Add(item.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = new InternalItemsQuery(user)
|
||||||
|
{
|
||||||
|
Limit = limit,
|
||||||
|
IncludeItemTypes = includeItemTypes.ToArray(),
|
||||||
|
IsMovie = isMovie,
|
||||||
|
IsSeries = isSeries,
|
||||||
|
SimilarTo = item,
|
||||||
|
DtoOptions = dtoOptions,
|
||||||
|
EnableTotalRecordCount = !isMovie ?? true,
|
||||||
|
EnableGroupByMetadataKey = isMovie ?? false,
|
||||||
|
MinSimilarityScore = 2 // A remnant from album/artist scoring
|
||||||
|
};
|
||||||
|
|
||||||
|
// ExcludeArtistIds
|
||||||
|
if (!string.IsNullOrEmpty(excludeArtistIds))
|
||||||
|
{
|
||||||
|
query.ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BaseItem> itemsResult = _libraryManager.GetItemList(query);
|
||||||
|
|
||||||
|
var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
|
||||||
|
|
||||||
|
return new QueryResult<BaseItemDto>
|
||||||
|
{
|
||||||
|
Items = returnList,
|
||||||
|
TotalRecordCount = itemsResult.Count
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -881,74 +919,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueryResult<BaseItemDto> GetSimilarItemsResult(
|
|
||||||
BaseItem item,
|
|
||||||
string? excludeArtistIds,
|
|
||||||
Guid? userId,
|
|
||||||
int? limit,
|
|
||||||
ItemFields[] fields,
|
|
||||||
string[] includeItemTypes,
|
|
||||||
bool isMovie)
|
|
||||||
{
|
|
||||||
var user = userId.HasValue && !userId.Equals(Guid.Empty)
|
|
||||||
? _userManager.GetUserById(userId.Value)
|
|
||||||
: null;
|
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
|
||||||
.AddClientFields(Request);
|
|
||||||
|
|
||||||
var query = new InternalItemsQuery(user)
|
|
||||||
{
|
|
||||||
Limit = limit,
|
|
||||||
IncludeItemTypes = includeItemTypes,
|
|
||||||
IsMovie = isMovie,
|
|
||||||
SimilarTo = item,
|
|
||||||
DtoOptions = dtoOptions,
|
|
||||||
EnableTotalRecordCount = !isMovie,
|
|
||||||
EnableGroupByMetadataKey = isMovie
|
|
||||||
};
|
|
||||||
|
|
||||||
// ExcludeArtistIds
|
|
||||||
if (!string.IsNullOrEmpty(excludeArtistIds))
|
|
||||||
{
|
|
||||||
query.ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<BaseItem> itemsResult;
|
|
||||||
|
|
||||||
if (isMovie)
|
|
||||||
{
|
|
||||||
var itemTypes = new List<string> { nameof(MediaBrowser.Controller.Entities.Movies.Movie) };
|
|
||||||
if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
|
|
||||||
{
|
|
||||||
itemTypes.Add(nameof(Trailer));
|
|
||||||
itemTypes.Add(nameof(LiveTvProgram));
|
|
||||||
}
|
|
||||||
|
|
||||||
query.IncludeItemTypes = itemTypes.ToArray();
|
|
||||||
itemsResult = _libraryManager.GetArtists(query).Items.Select(i => i.Item1).ToList();
|
|
||||||
}
|
|
||||||
else if (item is MusicArtist)
|
|
||||||
{
|
|
||||||
query.IncludeItemTypes = Array.Empty<string>();
|
|
||||||
|
|
||||||
itemsResult = _libraryManager.GetArtists(query).Items.Select(i => i.Item1).ToList();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
itemsResult = _libraryManager.GetItemList(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
|
|
||||||
|
|
||||||
var result = new QueryResult<BaseItemDto>
|
|
||||||
{
|
|
||||||
Items = returnList,
|
|
||||||
TotalRecordCount = itemsResult.Count
|
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string[] GetRepresentativeItemTypes(string? contentType)
|
private static string[] GetRepresentativeItemTypes(string? contentType)
|
||||||
{
|
{
|
||||||
return contentType switch
|
return contentType switch
|
||||||
|
|
|
@ -1,182 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using MediaBrowser.Controller.Dto;
|
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
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.HasValue && !userId.Equals(Guid.Empty)
|
|
||||||
? userManager.GetUserById(userId.Value)
|
|
||||||
: 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,
|
|
||||||
ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds)
|
|
||||||
};
|
|
||||||
|
|
||||||
var inputItems = libraryManager.GetItemList(query);
|
|
||||||
|
|
||||||
var items = GetSimilaritems(item, libraryManager, inputItems, getSimilarityScore)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var returnItems = items;
|
|
||||||
|
|
||||||
if (limit.HasValue && limit < returnItems.Count)
|
|
||||||
{
|
|
||||||
returnItems = returnItems.GetRange(0, limit.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue