diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-03-11 22:11:01 -0400 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-03-11 22:11:01 -0400 |
| commit | 6825cad56221775dfdfc88bbf260cd1399fa4313 (patch) | |
| tree | 34a36db33bb1042a82957d06423577fc2d20705f /MediaBrowser.Api | |
| parent | 38e5e32b42a2eb217457cbd42e8c26b46d401b23 (diff) | |
move collections back under movies. add movie suggestions page.
Diffstat (limited to 'MediaBrowser.Api')
| -rw-r--r-- | MediaBrowser.Api/Movies/MoviesService.cs | 244 | ||||
| -rw-r--r-- | MediaBrowser.Api/SimilarItemsHelper.cs | 10 | ||||
| -rw-r--r-- | MediaBrowser.Api/TvShowsService.cs | 15 |
3 files changed, 257 insertions, 12 deletions
diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 5d97d13e15..667a86fe6a 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -1,9 +1,16 @@ -using MediaBrowser.Controller.Dto; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; using ServiceStack; +using System; +using System.Collections.Generic; +using System.Linq; namespace MediaBrowser.Api.Movies { @@ -23,6 +30,32 @@ namespace MediaBrowser.Api.Movies } } + [Route("/Movies/Recommendations", "GET")] + [Api(Description = "Gets movie recommendations")] + 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")] + public Guid? UserId { get; set; } + + public GetMovieRecommendations() + { + CategoryLimit = 5; + ItemLimit = 8; + } + + public string Fields { get; set; } + } + /// <summary> /// Class MoviesService /// </summary> @@ -78,5 +111,214 @@ namespace MediaBrowser.Api.Movies return ToOptimizedSerializedResultUsingCache(result); } + + public object Get(GetMovieRecommendations request) + { + var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; + + var folder = user.RootFolder; + var movies = folder.RecursiveChildren.OfType<Movie>().ToList(); + + var result = GetRecommendationCategories(user, movies, request.CategoryLimit, request.ItemLimit, request.GetItemFields().ToList()); + + return ToOptimizedResult(result); + } + + private IEnumerable<RecommendationDto> GetRecommendationCategories(User user, List<Movie> allMovies, int categoryLimit, int itemLimit, List<ItemFields> fields) + { + var categories = new List<RecommendationDto>(); + + var recentlyPlayedMovies = allMovies + .Select(i => + { + var userdata = _userDataRepository.GetUserData(user.Id, i.GetUserDataKey()); + return new Tuple<Movie, bool, DateTime>(i, userdata.Played, userdata.LastPlayedDate ?? DateTime.MinValue); + }) + .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; + } + + return new Tuple<Movie, int>(i, score); + }) + .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(); + + var similarToRecentlyPlayed = GetSimilarTo(user, allMovies, recentlyPlayedMovies.Take(10).OrderBy(i => Guid.NewGuid()), itemLimit, fields, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator(); + var similarToLiked = GetSimilarTo(user, allMovies, likedMovies, itemLimit, fields, RecommendationType.SimilarToLikedItem).GetEnumerator(); + + var hasDirectorFromRecentlyPlayed = GetWithDirector(user, allMovies, recentDirectors, itemLimit, fields, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator(); + var hasActorFromRecentlyPlayed = GetWithActor(user, allMovies, recentActors, itemLimit, fields, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator(); + + 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; + } + } + + //// Get the lead actor for all movies + //var allActors = GetActors(allMovies) + // .ToList(); + + //foreach (var actor in recentActors) + //{ + + //} + + return categories.OrderBy(i => i.RecommendationType).ThenBy(i => Guid.NewGuid()); + } + + private IEnumerable<RecommendationDto> GetWithDirector(User user, List<Movie> allMovies, IEnumerable<string> directors, int itemLimit, List<ItemFields> fields, RecommendationType type) + { + var userId = user.Id; + + foreach (var director in directors) + { + var items = allMovies + .Where(i => !_userDataRepository.GetUserData(userId, i.GetUserDataKey()).Played && i.People.Any(p => string.Equals(p.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) && string.Equals(p.Name, director, StringComparison.OrdinalIgnoreCase))) + .Take(itemLimit) + .ToList(); + + if (items.Count > 0) + { + yield return new RecommendationDto + { + BaselineItemName = director, + CategoryId = director.GetMD5().ToString("N"), + RecommendationType = type, + Items = items.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray() + }; + } + } + } + + private IEnumerable<RecommendationDto> GetWithActor(User user, List<Movie> allMovies, IEnumerable<string> names, int itemLimit, List<ItemFields> fields, RecommendationType type) + { + var userId = user.Id; + + foreach (var name in names) + { + var items = allMovies + .Where(i => !_userDataRepository.GetUserData(userId, i.GetUserDataKey()).Played && i.People.Any(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase))) + .Take(itemLimit) + .ToList(); + + if (items.Count > 0) + { + yield return new RecommendationDto + { + BaselineItemName = name, + CategoryId = name.GetMD5().ToString("N"), + RecommendationType = type, + Items = items.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray() + }; + } + } + } + + private IEnumerable<RecommendationDto> GetSimilarTo(User user, List<Movie> allMovies, IEnumerable<Movie> baselineItems, int itemLimit, List<ItemFields> fields, RecommendationType type) + { + var userId = user.Id; + + foreach (var item in baselineItems) + { + var similar = SimilarItemsHelper + .GetSimilaritems(item, allMovies, SimilarItemsHelper.GetSimiliarityScore) + .Where(i => !_userDataRepository.GetUserData(userId, i.GetUserDataKey()).Played) + .Take(itemLimit) + .ToList(); + + if (similar.Count > 0) + { + yield return new RecommendationDto + { + BaselineItemName = item.Name, + CategoryId = item.Id.ToString("N"), + RecommendationType = type, + Items = similar.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray() + }; + } + } + } + + private IEnumerable<string> GetActors(IEnumerable<BaseItem> items) + { + // Get the two leading actors for all movies + return items + .SelectMany(i => i.People.Where(p => !string.Equals(PersonType.Director, p.Type, StringComparison.OrdinalIgnoreCase)).Take(2)) + .Select(i => i.Name) + .Distinct(StringComparer.OrdinalIgnoreCase); + } + + private IEnumerable<string> GetDirectors(IEnumerable<BaseItem> items) + { + return items + .Select(i => i.People.FirstOrDefault(p => string.Equals(PersonType.Director, p.Type, StringComparison.OrdinalIgnoreCase))) + .Where(i => i != null) + .Select(i => i.Name) + .Distinct(StringComparer.OrdinalIgnoreCase); + } } } diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index d1dc801bcd..abc4ece65b 100644 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -73,7 +73,7 @@ namespace MediaBrowser.Api var item = string.IsNullOrEmpty(request.Id) ? (request.UserId.HasValue ? user.RootFolder : - (Folder)libraryManager.RootFolder) : dtoService.GetItemByDtoId(request.Id, request.UserId); + libraryManager.RootFolder) : dtoService.GetItemByDtoId(request.Id, request.UserId); var fields = request.GetItemFields().ToList(); @@ -81,7 +81,7 @@ namespace MediaBrowser.Api ? libraryManager.RootFolder.GetRecursiveChildren(i => i.Id != item.Id) : user.RootFolder.GetRecursiveChildren(user, i => i.Id != item.Id); - var items = GetSimilaritems(item, inputItems, includeInSearch, getSimilarityScore) + var items = GetSimilaritems(item, inputItems.Where(includeInSearch), getSimilarityScore) .ToList(); IEnumerable<BaseItem> returnItems = items; @@ -106,12 +106,12 @@ namespace MediaBrowser.Api /// </summary> /// <param name="item">The item.</param> /// <param name="inputItems">The input items.</param> - /// <param name="includeInSearch">The include in search.</param> /// <param name="getSimilarityScore">The get similarity score.</param> /// <returns>IEnumerable{BaseItem}.</returns> - internal static IEnumerable<BaseItem> GetSimilaritems(BaseItem item, IEnumerable<BaseItem> inputItems, Func<BaseItem, bool> includeInSearch, Func<BaseItem, BaseItem, int> getSimilarityScore) + internal static IEnumerable<BaseItem> GetSimilaritems(BaseItem item, IEnumerable<BaseItem> inputItems, Func<BaseItem, BaseItem, int> getSimilarityScore) { - inputItems = inputItems.Where(includeInSearch); + var itemId = item.Id; + inputItems = inputItems.Where(i => i.Id != itemId); return inputItems.Select(i => new Tuple<BaseItem, int>(i, getSimilarityScore(item, i))) .Where(i => i.Item2 > 2) diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 198e1c3839..9e58c9f53d 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -300,11 +300,13 @@ namespace MediaBrowser.Api return FilterSeries(request, series) .AsParallel() - .Select(i => GetNextUp(i, currentUser, request).Item1) - .Where(i => i != null) + .Select(i => GetNextUp(i, currentUser)) + .Where(i => i.Item1 != null) .OrderByDescending(i => { - var seriesUserData = _userDataManager.GetUserData(user.Id, i.Series.GetUserDataKey()); + var episode = i.Item1; + + var seriesUserData = _userDataManager.GetUserData(user.Id, episode.Series.GetUserDataKey()); if (seriesUserData.IsFavorite) { @@ -318,7 +320,9 @@ namespace MediaBrowser.Api return 0; }) - .ThenByDescending(i => i.PremiereDate ?? DateTime.MinValue); + .ThenByDescending(i =>i.Item2) + .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue) + .Select(i => i.Item1); } /// <summary> @@ -326,9 +330,8 @@ namespace MediaBrowser.Api /// </summary> /// <param name="series">The series.</param> /// <param name="user">The user.</param> - /// <param name="request">The request.</param> /// <returns>Task{Episode}.</returns> - private Tuple<Episode, DateTime> GetNextUp(Series series, User user, GetNextUpEpisodes request) + private Tuple<Episode, DateTime> GetNextUp(Series series, User user) { // Get them in display order, then reverse var allEpisodes = series.GetSeasons(user, true, true) |
