2014-03-12 02:11:01 +00:00
using MediaBrowser.Common.Extensions ;
2014-09-30 04:47:30 +00:00
using MediaBrowser.Controller.Channels ;
2014-03-12 02:11:01 +00:00
using MediaBrowser.Controller.Dto ;
2013-09-04 17:02:19 +00:00
using MediaBrowser.Controller.Entities ;
2013-05-25 23:52:41 +00:00
using MediaBrowser.Controller.Entities.Movies ;
using MediaBrowser.Controller.Library ;
2014-07-02 18:34:08 +00:00
using MediaBrowser.Controller.Net ;
2013-05-25 23:52:41 +00:00
using MediaBrowser.Controller.Persistence ;
2014-09-30 04:47:30 +00:00
using MediaBrowser.Model.Channels ;
2014-03-12 02:11:01 +00:00
using MediaBrowser.Model.Dto ;
using MediaBrowser.Model.Entities ;
using MediaBrowser.Model.Querying ;
2014-09-30 04:47:30 +00:00
using MoreLinq ;
2013-12-07 15:52:38 +00:00
using ServiceStack ;
2014-03-12 02:11:01 +00:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
2014-09-30 04:47:30 +00:00
using System.Threading ;
using System.Threading.Tasks ;
2013-05-25 23:52:41 +00:00
2014-03-07 15:53:23 +00:00
namespace MediaBrowser.Api.Movies
2013-05-25 23:52:41 +00:00
{
/// <summary>
/// Class GetSimilarMovies
/// </summary>
2014-03-25 21:13:55 +00:00
[Route("/Movies/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given movie.")]
2013-08-09 15:55:22 +00:00
public class GetSimilarMovies : BaseGetSimilarItemsFromItem
2013-05-25 23:52:41 +00:00
{
}
2014-03-25 21:13:55 +00:00
[Route("/Movies/Recommendations", "GET", Summary = "Gets movie recommendations")]
2014-03-12 02:11:01 +00:00
public class GetMovieRecommendations : IReturn < RecommendationDto [ ] > , IHasItemFields
{
[ApiMember(Name = "CategoryLimit", Description = "The max number of categories to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int CategoryLimit { get ; set ; }
[ApiMember(Name = "ItemLimit", Description = "The max number of items to return per category", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int ItemLimit { get ; set ; }
/// <summary>
/// Gets or sets the user id.
/// </summary>
/// <value>The user id.</value>
[ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
2015-05-29 23:51:33 +00:00
public string UserId { get ; set ; }
2014-03-12 02:11:01 +00:00
2014-05-02 02:54:33 +00:00
/// <summary>
/// Specify this to localize the search to a specific item or folder. Omit to use the root.
/// </summary>
/// <value>The parent id.</value>
[ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string ParentId { get ; set ; }
2014-09-30 04:47:30 +00:00
2014-03-12 02:11:01 +00:00
public GetMovieRecommendations ( )
{
CategoryLimit = 5 ;
ItemLimit = 8 ;
}
public string Fields { get ; set ; }
}
2013-05-25 23:52:41 +00:00
/// <summary>
/// Class MoviesService
/// </summary>
2014-07-02 18:34:08 +00:00
[Authenticated]
2013-05-25 23:52:41 +00:00
public class MoviesService : BaseApiService
{
/// <summary>
/// The _user manager
/// </summary>
private readonly IUserManager _userManager ;
/// <summary>
/// The _user data repository
/// </summary>
2013-10-02 16:08:58 +00:00
private readonly IUserDataManager _userDataRepository ;
2013-05-25 23:52:41 +00:00
/// <summary>
/// The _library manager
/// </summary>
private readonly ILibraryManager _libraryManager ;
2013-06-18 19:16:27 +00:00
private readonly IItemRepository _itemRepo ;
2013-09-04 17:02:19 +00:00
private readonly IDtoService _dtoService ;
2014-09-30 04:47:30 +00:00
private readonly IChannelManager _channelManager ;
2013-05-25 23:52:41 +00:00
/// <summary>
/// Initializes a new instance of the <see cref="MoviesService"/> class.
/// </summary>
/// <param name="userManager">The user manager.</param>
/// <param name="userDataRepository">The user data repository.</param>
/// <param name="libraryManager">The library manager.</param>
2014-09-30 04:47:30 +00:00
public MoviesService ( IUserManager userManager , IUserDataManager userDataRepository , ILibraryManager libraryManager , IItemRepository itemRepo , IDtoService dtoService , IChannelManager channelManager )
2013-05-25 23:52:41 +00:00
{
_userManager = userManager ;
_userDataRepository = userDataRepository ;
_libraryManager = libraryManager ;
2013-06-18 19:16:27 +00:00
_itemRepo = itemRepo ;
2013-09-04 17:02:19 +00:00
_dtoService = dtoService ;
2014-09-30 04:47:30 +00:00
_channelManager = channelManager ;
2013-05-25 23:52:41 +00:00
}
/// <summary>
/// Gets the specified request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
2014-09-30 04:47:30 +00:00
public async Task < object > Get ( GetSimilarMovies request )
2013-05-25 23:52:41 +00:00
{
2014-09-30 04:47:30 +00:00
var result = await GetSimilarItemsResult (
2014-03-19 04:59:45 +00:00
// Strip out secondary versions
2014-09-30 04:47:30 +00:00
request , item = > ( item is Movie ) & & ! ( ( Video ) item ) . PrimaryVersionId . HasValue ,
2014-03-19 04:59:45 +00:00
2014-09-30 04:47:30 +00:00
SimilarItemsHelper . GetSimiliarityScore ) . ConfigureAwait ( false ) ;
2013-05-25 23:52:41 +00:00
2014-02-04 04:04:19 +00:00
return ToOptimizedSerializedResultUsingCache ( result ) ;
2013-05-25 23:52:41 +00:00
}
2014-03-12 02:11:01 +00:00
2014-09-30 04:47:30 +00:00
public async Task < object > Get ( GetMovieRecommendations request )
2014-03-12 02:11:01 +00:00
{
2015-05-29 23:51:33 +00:00
var user = _userManager . GetUserById ( request . UserId ) ;
2014-03-12 02:11:01 +00:00
2015-01-25 06:34:50 +00:00
IEnumerable < BaseItem > movies = GetAllLibraryItems ( request . UserId , _userManager , _libraryManager , request . ParentId , i = > i is Movie ) ;
2014-09-30 04:47:30 +00:00
movies = _libraryManager . ReplaceVideosWithPrimaryVersions ( movies ) ;
2014-10-06 23:58:46 +00:00
var listEligibleForCategories = new List < BaseItem > ( ) ;
var listEligibleForSuggestion = new List < BaseItem > ( ) ;
2014-09-30 04:47:30 +00:00
var list = movies . ToList ( ) ;
2014-10-06 23:58:46 +00:00
listEligibleForCategories . AddRange ( list ) ;
listEligibleForSuggestion . AddRange ( list ) ;
2014-09-30 04:47:30 +00:00
if ( user . Configuration . IncludeTrailersInSuggestions )
{
var trailerResult = await _channelManager . GetAllMediaInternal ( new AllChannelMediaQuery
{
ContentTypes = new [ ] { ChannelMediaContentType . MovieExtra } ,
ExtraTypes = new [ ] { ExtraType . Trailer } ,
UserId = user . Id . ToString ( "N" )
2014-06-08 01:38:37 +00:00
2014-09-30 04:47:30 +00:00
} , CancellationToken . None ) . ConfigureAwait ( false ) ;
2014-03-12 02:11:01 +00:00
2014-10-06 23:58:46 +00:00
listEligibleForSuggestion . AddRange ( trailerResult . Items ) ;
}
2014-09-30 04:47:30 +00:00
2014-10-06 23:58:46 +00:00
listEligibleForCategories = listEligibleForCategories
. DistinctBy ( i = > i . Name , StringComparer . OrdinalIgnoreCase )
. DistinctBy ( i = > i . GetProviderId ( MetadataProviders . Imdb ) ? ? Guid . NewGuid ( ) . ToString ( ) , StringComparer . OrdinalIgnoreCase )
. ToList ( ) ;
2014-09-30 04:47:30 +00:00
2014-10-06 23:58:46 +00:00
listEligibleForSuggestion = listEligibleForSuggestion
. DistinctBy ( i = > i . Name , StringComparer . OrdinalIgnoreCase )
. DistinctBy ( i = > i . GetProviderId ( MetadataProviders . Imdb ) ? ? Guid . NewGuid ( ) . ToString ( ) , StringComparer . OrdinalIgnoreCase )
. ToList ( ) ;
2014-09-30 04:47:30 +00:00
2015-01-24 19:03:55 +00:00
var dtoOptions = GetDtoOptions ( request ) ;
2014-12-27 05:08:39 +00:00
dtoOptions . Fields = request . GetItemFields ( ) . ToList ( ) ;
var result = GetRecommendationCategories ( user , listEligibleForCategories , listEligibleForSuggestion , request . CategoryLimit , request . ItemLimit , dtoOptions ) ;
2014-03-12 02:11:01 +00:00
return ToOptimizedResult ( result ) ;
}
2015-07-08 16:10:34 +00:00
private async Task < ItemsResult > GetSimilarItemsResult ( BaseGetSimilarItemsFromItem request , Func < BaseItem , bool > includeInSearch , Func < BaseItem , List < PersonInfo > , List < PersonInfo > , BaseItem , int > getSimilarityScore )
2014-09-30 04:47:30 +00:00
{
2015-05-29 23:51:33 +00:00
var user = ! string . IsNullOrWhiteSpace ( request . UserId ) ? _userManager . GetUserById ( request . UserId ) : null ;
2014-09-30 04:47:30 +00:00
var item = string . IsNullOrEmpty ( request . Id ) ?
2015-05-29 23:51:33 +00:00
( ! string . IsNullOrWhiteSpace ( request . UserId ) ? user . RootFolder :
2014-09-30 04:47:30 +00:00
_libraryManager . RootFolder ) : _libraryManager . GetItemById ( request . Id ) ;
2015-01-25 06:34:50 +00:00
Func < BaseItem , bool > filter = i = > i . Id ! = item . Id & & includeInSearch ( i ) ;
2014-09-30 04:47:30 +00:00
var inputItems = user = = null
2015-01-25 06:34:50 +00:00
? _libraryManager . RootFolder . GetRecursiveChildren ( filter )
: user . RootFolder . GetRecursiveChildren ( user , filter ) ;
2014-09-30 04:47:30 +00:00
var list = inputItems . ToList ( ) ;
if ( item is Movie & & user ! = null & & user . Configuration . IncludeTrailersInSuggestions )
{
var trailerResult = await _channelManager . GetAllMediaInternal ( new AllChannelMediaQuery
{
ContentTypes = new [ ] { ChannelMediaContentType . MovieExtra } ,
ExtraTypes = new [ ] { ExtraType . Trailer } ,
UserId = user . Id . ToString ( "N" )
} , CancellationToken . None ) . ConfigureAwait ( false ) ;
var newTrailers = trailerResult . Items ;
list . AddRange ( newTrailers ) ;
list = list
. DistinctBy ( i = > i . Name , StringComparer . OrdinalIgnoreCase )
. DistinctBy ( i = > i . GetProviderId ( MetadataProviders . Imdb ) ? ? Guid . NewGuid ( ) . ToString ( ) , StringComparer . OrdinalIgnoreCase )
. ToList ( ) ;
}
2014-12-09 04:57:18 +00:00
if ( item is Video )
{
var imdbId = item . GetProviderId ( MetadataProviders . Imdb ) ;
// Use imdb id to try to filter duplicates of the same item
if ( ! string . IsNullOrWhiteSpace ( imdbId ) )
{
list = list
. Where ( i = > ! string . Equals ( imdbId , i . GetProviderId ( MetadataProviders . Imdb ) , StringComparison . OrdinalIgnoreCase ) )
. ToList ( ) ;
}
}
2015-06-21 03:35:22 +00:00
var items = SimilarItemsHelper . GetSimilaritems ( item , _libraryManager , list , getSimilarityScore ) . ToList ( ) ;
2014-09-30 04:47:30 +00:00
IEnumerable < BaseItem > returnItems = items ;
if ( request . Limit . HasValue )
{
returnItems = returnItems . Take ( request . Limit . Value ) ;
}
2015-01-24 19:03:55 +00:00
var dtoOptions = GetDtoOptions ( request ) ;
2014-11-30 19:01:33 +00:00
2014-09-30 04:47:30 +00:00
var result = new ItemsResult
{
2015-01-24 19:03:55 +00:00
Items = _dtoService . GetBaseItemDtos ( returnItems , dtoOptions , user ) . ToArray ( ) ,
2014-09-30 04:47:30 +00:00
TotalRecordCount = items . Count
} ;
return result ;
}
2014-12-27 05:08:39 +00:00
private IEnumerable < RecommendationDto > GetRecommendationCategories ( User user , List < BaseItem > allMoviesForCategories , List < BaseItem > allMovies , int categoryLimit , int itemLimit , DtoOptions dtoOptions )
2014-03-12 02:11:01 +00:00
{
var categories = new List < RecommendationDto > ( ) ;
2014-10-06 23:58:46 +00:00
var recentlyPlayedMovies = allMoviesForCategories
2014-03-12 02:11:01 +00:00
. Select ( i = >
{
var userdata = _userDataRepository . GetUserData ( user . Id , i . GetUserDataKey ( ) ) ;
2014-09-30 04:47:30 +00:00
return new Tuple < BaseItem , bool , DateTime > ( i , userdata . Played , userdata . LastPlayedDate ? ? DateTime . MinValue ) ;
2014-03-12 02:11:01 +00:00
} )
. Where ( i = > i . Item2 )
. OrderByDescending ( i = > i . Item3 )
. Select ( i = > i . Item1 )
. ToList ( ) ;
var excludeFromLiked = recentlyPlayedMovies . Take ( 10 ) ;
var likedMovies = allMovies
. Select ( i = >
{
var score = 0 ;
var userData = _userDataRepository . GetUserData ( user . Id , i . GetUserDataKey ( ) ) ;
if ( userData . IsFavorite )
{
score = 2 ;
}
else
{
score = userData . Likes . HasValue ? userData . Likes . Value ? 1 : - 1 : 0 ;
}
2014-09-30 04:47:30 +00:00
return new Tuple < BaseItem , int > ( i , score ) ;
2014-03-12 02:11:01 +00:00
} )
. OrderByDescending ( i = > i . Item2 )
. ThenBy ( i = > Guid . NewGuid ( ) )
. Where ( i = > i . Item2 > 0 )
. Select ( i = > i . Item1 )
. Where ( i = > ! excludeFromLiked . Contains ( i ) ) ;
var mostRecentMovies = recentlyPlayedMovies . Take ( 6 ) . ToList ( ) ;
// Get recently played directors
var recentDirectors = GetDirectors ( mostRecentMovies )
. OrderBy ( i = > Guid . NewGuid ( ) )
. ToList ( ) ;
// Get recently played actors
var recentActors = GetActors ( mostRecentMovies )
. OrderBy ( i = > Guid . NewGuid ( ) )
. ToList ( ) ;
2014-12-27 05:08:39 +00:00
var similarToRecentlyPlayed = GetSimilarTo ( user , allMovies , recentlyPlayedMovies . Take ( 7 ) . OrderBy ( i = > Guid . NewGuid ( ) ) , itemLimit , dtoOptions , RecommendationType . SimilarToRecentlyPlayed ) . GetEnumerator ( ) ;
var similarToLiked = GetSimilarTo ( user , allMovies , likedMovies , itemLimit , dtoOptions , RecommendationType . SimilarToLikedItem ) . GetEnumerator ( ) ;
2014-03-12 02:11:01 +00:00
2014-12-27 05:08:39 +00:00
var hasDirectorFromRecentlyPlayed = GetWithDirector ( user , allMovies , recentDirectors , itemLimit , dtoOptions , RecommendationType . HasDirectorFromRecentlyPlayed ) . GetEnumerator ( ) ;
var hasActorFromRecentlyPlayed = GetWithActor ( user , allMovies , recentActors , itemLimit , dtoOptions , RecommendationType . HasActorFromRecentlyPlayed ) . GetEnumerator ( ) ;
2014-03-12 02:11:01 +00:00
var categoryTypes = new List < IEnumerator < RecommendationDto > >
{
// Give this extra weight
similarToRecentlyPlayed ,
similarToRecentlyPlayed ,
// Give this extra weight
similarToLiked ,
similarToLiked ,
hasDirectorFromRecentlyPlayed ,
hasActorFromRecentlyPlayed
} ;
while ( categories . Count < categoryLimit )
{
var allEmpty = true ;
foreach ( var category in categoryTypes )
{
if ( category . MoveNext ( ) )
{
categories . Add ( category . Current ) ;
allEmpty = false ;
if ( categories . Count > = categoryLimit )
{
break ;
}
}
}
if ( allEmpty )
{
break ;
}
}
return categories . OrderBy ( i = > i . RecommendationType ) . ThenBy ( i = > Guid . NewGuid ( ) ) ;
}
2014-12-27 05:08:39 +00:00
private IEnumerable < RecommendationDto > GetWithDirector ( User user , List < BaseItem > allMovies , IEnumerable < string > directors , int itemLimit , DtoOptions dtoOptions , RecommendationType type )
2014-03-12 02:11:01 +00:00
{
var userId = user . Id ;
foreach ( var director in directors )
{
var items = allMovies
2015-06-21 03:35:22 +00:00
. Where ( i = > _libraryManager . GetPeople ( i ) . Any ( p = > string . Equals ( p . Type , PersonType . Director , StringComparison . OrdinalIgnoreCase ) & & string . Equals ( p . Name , director , StringComparison . OrdinalIgnoreCase ) ) )
2014-03-12 02:11:01 +00:00
. Take ( itemLimit )
. ToList ( ) ;
if ( items . Count > 0 )
{
yield return new RecommendationDto
{
BaselineItemName = director ,
CategoryId = director . GetMD5 ( ) . ToString ( "N" ) ,
RecommendationType = type ,
2015-01-24 19:03:55 +00:00
Items = _dtoService . GetBaseItemDtos ( items , dtoOptions , user ) . ToArray ( )
2014-03-12 02:11:01 +00:00
} ;
}
}
}
2014-12-27 05:08:39 +00:00
private IEnumerable < RecommendationDto > GetWithActor ( User user , List < BaseItem > allMovies , IEnumerable < string > names , int itemLimit , DtoOptions dtoOptions , RecommendationType type )
2014-03-12 02:11:01 +00:00
{
foreach ( var name in names )
{
2015-07-08 16:10:34 +00:00
var itemsWithActor = _libraryManager . GetItemIds ( new InternalItemsQuery
{
Person = name
} ) ;
2014-03-12 02:11:01 +00:00
var items = allMovies
2015-07-08 16:10:34 +00:00
. Where ( i = > itemsWithActor . Contains ( i . Id ) )
2014-03-12 02:11:01 +00:00
. Take ( itemLimit )
. ToList ( ) ;
if ( items . Count > 0 )
{
yield return new RecommendationDto
{
BaselineItemName = name ,
CategoryId = name . GetMD5 ( ) . ToString ( "N" ) ,
RecommendationType = type ,
2015-01-24 19:03:55 +00:00
Items = _dtoService . GetBaseItemDtos ( items , dtoOptions , user ) . ToArray ( )
2014-03-12 02:11:01 +00:00
} ;
}
}
}
2014-12-27 05:08:39 +00:00
private IEnumerable < RecommendationDto > GetSimilarTo ( User user , List < BaseItem > allMovies , IEnumerable < BaseItem > baselineItems , int itemLimit , DtoOptions dtoOptions , RecommendationType type )
2014-03-12 02:11:01 +00:00
{
foreach ( var item in baselineItems )
{
var similar = SimilarItemsHelper
2015-06-21 03:35:22 +00:00
. GetSimilaritems ( item , _libraryManager , allMovies , SimilarItemsHelper . GetSimiliarityScore )
2014-03-12 02:11:01 +00:00
. Take ( itemLimit )
. ToList ( ) ;
if ( similar . Count > 0 )
{
yield return new RecommendationDto
{
BaselineItemName = item . Name ,
CategoryId = item . Id . ToString ( "N" ) ,
RecommendationType = type ,
2015-01-24 19:03:55 +00:00
Items = _dtoService . GetBaseItemDtos ( similar , dtoOptions , user ) . ToArray ( )
2014-03-12 02:11:01 +00:00
} ;
}
}
}
private IEnumerable < string > GetActors ( IEnumerable < BaseItem > items )
{
2015-07-08 16:10:34 +00:00
var people = _libraryManager . GetPeople ( new InternalPeopleQuery
{
ExcludePersonTypes = new List < string >
{
PersonType . Director
} ,
MaxListOrder = 3
} ) ;
var itemIds = items . Select ( i = > i . Id ) . ToList ( ) ;
return people
. Where ( i = > itemIds . Contains ( i . ItemId ) )
2014-03-12 02:11:01 +00:00
. Select ( i = > i . Name )
2015-04-09 21:11:57 +00:00
. DistinctNames ( ) ;
2014-03-12 02:11:01 +00:00
}
private IEnumerable < string > GetDirectors ( IEnumerable < BaseItem > items )
{
2015-07-08 16:10:34 +00:00
var people = _libraryManager . GetPeople ( new InternalPeopleQuery
{
PersonTypes = new List < string >
{
PersonType . Director
}
} ) ;
var itemIds = items . Select ( i = > i . Id ) . ToList ( ) ;
return people
. Where ( i = > itemIds . Contains ( i . ItemId ) )
2014-03-12 02:11:01 +00:00
. Select ( i = > i . Name )
2015-04-09 21:11:57 +00:00
. DistinctNames ( ) ;
2014-03-12 02:11:01 +00:00
}
2013-05-25 23:52:41 +00:00
}
}