1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-11-04 10:02:40 +01:00

New: Movie Discovery/Recommendations Reworked

This commit is contained in:
Qstick 2020-06-09 01:49:44 -04:00
parent 3e3b2a7784
commit 2d59192a9e
10 changed files with 220 additions and 0 deletions

View File

@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(176)]
public class movie_recommendations : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Movies").AddColumn("Recommendations").AsString().WithDefaultValue("[]");
}
}
}

View File

@ -9,6 +9,8 @@ public interface IProvideMovieInfo
{
Movie GetMovieByImdbId(string imdbId);
Tuple<Movie, List<Credit>> GetMovieInfo(int tmdbId);
List<Movie> GetBulkMovieInfo(List<int> tmdbIds);
HashSet<int> GetChangedMovies(DateTime startTime);
}
}

View File

@ -33,5 +33,6 @@ public class MovieResource
public CollectionResource Collection { get; set; }
public string OriginalLanguage { get; set; }
public string Homepage { get; set; }
public List<RecommendationResource> Recommendations { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
{
public class RecommendationResource
{
public int TmdbId { get; set; }
public string Name { get; set; }
}
}

View File

@ -6,6 +6,7 @@
using NzbDrone.Common.Cloud;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Languages;
@ -97,6 +98,31 @@ public Tuple<Movie, List<Credit>> GetMovieInfo(int tmdbId)
return new Tuple<Movie, List<Credit>>(movie, credits.ToList());
}
public List<Movie> GetBulkMovieInfo(List<int> tmdbIds)
{
var httpRequest = _radarrMetadata.Create()
.SetSegment("route", "movie/bulk")
.Build();
httpRequest.Headers.ContentType = "application/json";
httpRequest.SetContent(tmdbIds.ToJson());
httpRequest.AllowAutoRedirect = true;
httpRequest.SuppressHttpError = true;
var httpResponse = _httpClient.Post<List<MovieResource>>(httpRequest);
if (httpResponse.HasHttpError || httpResponse.Resource.Count == 0)
{
throw new HttpException(httpRequest, httpResponse);
}
var movies = httpResponse.Resource.Select(MapMovie).ToList();
return movies;
}
public Movie GetMovieByImdbId(string imdbId)
{
var httpRequest = _radarrMetadata.Create()
@ -165,6 +191,7 @@ public Movie MapMovie(MovieResource resource)
movie.Certification = resource.Certifications.FirstOrDefault(m => m.Country == certificationCountry)?.Certification;
movie.Ratings = resource.Ratings.Select(MapRatings).FirstOrDefault() ?? new Ratings();
movie.Genres = resource.Genres;
movie.Recommendations = resource.Recommendations.Select(r => r.TmdbId).ToList();
var now = DateTime.Now;

View File

@ -16,6 +16,7 @@ public Movie()
Genres = new List<string>();
Tags = new HashSet<int>();
AlternativeTitles = new List<AlternativeTitle>();
Recommendations = new List<int>();
}
public int TmdbId { get; set; }
@ -59,6 +60,7 @@ public Movie()
public int SecondaryYearSourceId { get; set; }
public string YouTubeTrailerId { get; set; }
public string Studio { get; set; }
public List<int> Recommendations { get; set; }
public bool IsRecentMovie
{

View File

@ -43,6 +43,7 @@ public interface IMovieService
Movie UpdateMovie(Movie movie);
List<Movie> UpdateMovie(List<Movie> movie, bool useExistingRelativeFolder);
List<Movie> FilterExistingMovies(List<Movie> movies);
List<Movie> GetRecommendedMovies();
bool MoviePathExists(string folder);
void RemoveAddOptions(Movie movie);
}
@ -393,6 +394,28 @@ public List<Movie> FilterExistingMovies(List<Movie> movies)
return ret;
}
public List<Movie> GetRecommendedMovies()
{
// Get all recommended movies, plus all movies on enabled lists
var netImportMovies = new List<Movie>();
var allMovies = GetAllMovies();
// Ensure we only return distinct ids that do not exist in DB already, first 100 that are from latest movies add first
var distinctRecommendations = allMovies.OrderByDescending(x => x.Added)
.SelectMany(m => m.Recommendations.Select(c => c))
.Where(r => !allMovies.Any(m => m.TmdbId == r))
.Distinct()
.Take(100);
foreach (var recommendation in distinctRecommendations)
{
netImportMovies.Add(new Movie { TmdbId = recommendation });
}
return netImportMovies;
}
public void Handle(MovieFileAddedEvent message)
{
var movie = message.MovieFile.Movie;

View File

@ -107,6 +107,7 @@ private void RefreshMovieInfo(Movie movie)
movie.YouTubeTrailerId = movieInfo.YouTubeTrailerId;
movie.Studio = movieInfo.Studio;
movie.HasPreDBEntry = movieInfo.HasPreDBEntry;
movie.Recommendations = movieInfo.Recommendations;
try
{

View File

@ -0,0 +1,64 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Movies;
using NzbDrone.Core.NetImport.ImportExclusions;
using NzbDrone.Core.Organizer;
using Radarr.Http;
namespace NzbDrone.Api.V3.Movies
{
public class DiscoverMoviesModule : RadarrRestModule<DiscoverMoviesResource>
{
private readonly IMovieService _movieService;
private readonly IProvideMovieInfo _movieInfo;
private readonly IBuildFileNames _fileNameBuilder;
private readonly IImportExclusionsService _importExclusionService;
public DiscoverMoviesModule(IMovieService movieService, IProvideMovieInfo movieInfo, IBuildFileNames fileNameBuilder, IImportExclusionsService importExclusionsService)
: base("/movies/discover")
{
_movieService = movieService;
_movieInfo = movieInfo;
_fileNameBuilder = fileNameBuilder;
_importExclusionService = importExclusionsService;
Get("/", x => GetDiscoverMovies());
}
private object GetDiscoverMovies()
{
var results = _movieService.GetRecommendedMovies();
var mapped = new List<Movie>();
if (results.Count > 0)
{
mapped = _movieInfo.GetBulkMovieInfo(results.Select(m => m.TmdbId).ToList());
}
var realResults = new List<Movie>();
realResults.AddRange(mapped.Where(x => x != null));
return MapToResource(realResults);
}
private IEnumerable<DiscoverMoviesResource> MapToResource(IEnumerable<Movie> movies)
{
foreach (var currentMovie in movies)
{
var resource = currentMovie.ToResource(_movieService, _importExclusionService);
var poster = currentMovie.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
if (poster != null)
{
resource.RemotePoster = poster.Url;
}
resource.Folder = _fileNameBuilder.GetMovieFolder(currentMovie);
yield return resource;
}
}
}
}

View File

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Movies;
using NzbDrone.Core.NetImport.ImportExclusions;
using Radarr.Http.REST;
namespace NzbDrone.Api.V3.Movies
{
public class DiscoverMoviesResource : RestResource
{
//View Only
public string Title { get; set; }
public string SortTitle { get; set; }
public string TitleSlug { get; set; }
public MovieStatusType Status { get; set; }
public string Overview { get; set; }
public DateTime? InCinemas { get; set; }
public DateTime? PhysicalRelease { get; set; }
public List<MediaCover> Images { get; set; }
public string Website { get; set; }
public string RemotePoster { get; set; }
public int Year { get; set; }
public string YouTubeTrailerId { get; set; }
public string Studio { get; set; }
public int Runtime { get; set; }
public string ImdbId { get; set; }
public int TmdbId { get; set; }
public string Folder { get; set; }
public string Certification { get; set; }
public List<string> Genres { get; set; }
public Ratings Ratings { get; set; }
public MovieCollection Collection { get; set; }
public bool IsExcluded { get; set; }
public bool IsExisting { get; set; }
}
public static class DiscoverMoviesResourceMapper
{
public static DiscoverMoviesResource ToResource(this Movie model, IMovieService movieService, IImportExclusionsService importExclusionService)
{
if (model == null)
{
return null;
}
return new DiscoverMoviesResource
{
TmdbId = model.TmdbId,
Title = model.Title,
SortTitle = model.SortTitle,
TitleSlug = model.TitleSlug,
InCinemas = model.InCinemas,
PhysicalRelease = model.PhysicalRelease,
Status = model.Status,
Overview = model.Overview,
Images = model.Images,
Year = model.Year,
Runtime = model.Runtime,
ImdbId = model.ImdbId,
Certification = model.Certification,
Website = model.Website,
Genres = model.Genres,
Ratings = model.Ratings,
YouTubeTrailerId = model.YouTubeTrailerId,
Studio = model.Studio,
Collection = model.Collection,
IsExcluded = importExclusionService.IsMovieExcluded(model.TmdbId),
IsExisting = movieService.FindByTmdbId(model.TmdbId) != null,
};
}
}
}