TheMovieDB.org is now used as metadata source.

pull/24/head
Leonardo Galli 8 years ago
parent 69786b3968
commit 0715962ec5

@ -73,7 +73,7 @@ namespace NzbDrone.Api.Movie
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.Title).NotEmpty();
PostValidator.RuleFor(s => s.ImdbId).NotNull().NotEmpty().SetValidator(moviesExistsValidator);
PostValidator.RuleFor(s => s.TmdbId).NotNull().NotEmpty().SetValidator(moviesExistsValidator);
PutValidator.RuleFor(s => s.Path).IsValidPath();
}

@ -28,6 +28,7 @@ namespace NzbDrone.Api.Movie
public string Overview { get; set; }
public DateTime? InCinemas { get; set; }
public List<MediaCover> Images { get; set; }
public string Website { get; set; }
public string RemotePoster { get; set; }
public int Year { get; set; }
@ -42,6 +43,7 @@ namespace NzbDrone.Api.Movie
public DateTime? LastInfoSync { get; set; }
public string CleanTitle { get; set; }
public string ImdbId { get; set; }
public int TmdbId { get; set; }
public string TitleSlug { get; set; }
public string RootFolderPath { get; set; }
public string Certification { get; set; }
@ -50,6 +52,7 @@ namespace NzbDrone.Api.Movie
public DateTime Added { get; set; }
public AddMovieOptions AddOptions { get; set; }
public Ratings Ratings { get; set; }
public List<string> AlternativeTitles { get; set; }
//TODO: Add series statistics as a property of the series (instead of individual properties)
@ -79,7 +82,7 @@ namespace NzbDrone.Api.Movie
return new MovieResource
{
Id = model.Id,
TmdbId = model.TmdbId,
Title = model.Title,
//AlternateTitles
SortTitle = model.SortTitle,
@ -108,10 +111,12 @@ namespace NzbDrone.Api.Movie
TitleSlug = model.TitleSlug,
RootFolderPath = model.RootFolderPath,
Certification = model.Certification,
Website = model.Website,
Genres = model.Genres,
Tags = model.Tags,
Added = model.Added,
AddOptions = model.AddOptions,
AlternativeTitles = model.AlternativeTitles,
Ratings = model.Ratings
};
}
@ -123,6 +128,7 @@ namespace NzbDrone.Api.Movie
return new Core.Tv.Movie
{
Id = resource.Id,
TmdbId = resource.TmdbId,
Title = resource.Title,
//AlternateTitles
@ -151,10 +157,12 @@ namespace NzbDrone.Api.Movie
TitleSlug = resource.TitleSlug,
RootFolderPath = resource.RootFolderPath,
Certification = resource.Certification,
Website = resource.Website,
Genres = resource.Genres,
Tags = resource.Tags,
Added = resource.Added,
AddOptions = resource.AddOptions,
AlternativeTitles = resource.AlternativeTitles,
Ratings = resource.Ratings
};
}
@ -162,6 +170,7 @@ namespace NzbDrone.Api.Movie
public static Core.Tv.Movie ToModel(this MovieResource resource, Core.Tv.Movie movie)
{
movie.ImdbId = resource.ImdbId;
movie.TmdbId = resource.TmdbId;
movie.Path = resource.Path;
movie.ProfileId = resource.ProfileId;

@ -6,6 +6,7 @@ namespace NzbDrone.Common.Cloud
{
IHttpRequestBuilderFactory Services { get; }
IHttpRequestBuilderFactory SkyHookTvdb { get; }
IHttpRequestBuilderFactory TMDB { get; }
}
public class SonarrCloudRequestBuilder : ISonarrCloudRequestBuilder
@ -18,10 +19,15 @@ namespace NzbDrone.Common.Cloud
SkyHookTvdb = new HttpRequestBuilder("http://skyhook.sonarr.tv/v1/tvdb/{route}/{language}/")
.SetSegment("language", "en")
.CreateFactory();
TMDB = new HttpRequestBuilder("https://api.themoviedb.org/3/{route}/{id}{secondaryRoute}")
.AddQueryParam("api_key", "1a7373301961d03f97f853a876dd1212")
.CreateFactory();
}
public IHttpRequestBuilderFactory Services { get; private set; }
public IHttpRequestBuilderFactory SkyHookTvdb { get; private set; }
public IHttpRequestBuilderFactory TMDB { get; private set; }
}
}

@ -0,0 +1,21 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(106)]
public class add_tmdb_stuff : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Movies")
.AddColumn("TmdbId").AsInt32().WithDefaultValue(0);
Alter.Table("Movies")
.AddColumn("Website").AsString().Nullable();
Alter.Table("Movies")
.AlterColumn("ImdbId").AsString().Nullable();
Alter.Table("Movies")
.AddColumn("AlternativeTitles").AsString().Nullable();
}
}
}

@ -7,5 +7,6 @@ namespace NzbDrone.Core.MetadataSource
public interface IProvideMovieInfo
{
Movie GetMovieInfo(string ImdbId);
Movie GetMovieInfo(int TmdbId);
}
}

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
{
public class MovieSearchRoot
{
public int page { get; set; }
public MovieResult[] results { get; set; }
public int total_results { get; set; }
public int total_pages { get; set; }
}
public class MovieResult
{
public string poster_path { get; set; }
public bool adult { get; set; }
public string overview { get; set; }
public string release_date { get; set; }
public int?[] genre_ids { get; set; }
public int id { get; set; }
public string original_title { get; set; }
public string original_language { get; set; }
public string title { get; set; }
public string backdrop_path { get; set; }
public float popularity { get; set; }
public int vote_count { get; set; }
public bool video { get; set; }
public float vote_average { get; set; }
}
public class MovieResourceRoot
{
public bool adult { get; set; }
public string backdrop_path { get; set; }
public Belongs_To_Collection belongs_to_collection { get; set; }
public int budget { get; set; }
public Genre[] genres { get; set; }
public string homepage { get; set; }
public int id { get; set; }
public string imdb_id { get; set; }
public string original_language { get; set; }
public string original_title { get; set; }
public string overview { get; set; }
public float popularity { get; set; }
public string poster_path { get; set; }
public Production_Companies[] production_companies { get; set; }
public Production_Countries[] production_countries { get; set; }
public string release_date { get; set; }
public int revenue { get; set; }
public int runtime { get; set; }
public Spoken_Languages[] spoken_languages { get; set; }
public string status { get; set; }
public string tagline { get; set; }
public string title { get; set; }
public bool video { get; set; }
public float vote_average { get; set; }
public int vote_count { get; set; }
public AlternativeTitles alternative_titles { get; set; }
}
public class Belongs_To_Collection
{
public int id { get; set; }
public string name { get; set; }
public string poster_path { get; set; }
public string backdrop_path { get; set; }
}
public class Genre
{
public int id { get; set; }
public string name { get; set; }
}
public class Production_Companies
{
public string name { get; set; }
public int id { get; set; }
}
public class Production_Countries
{
public string iso_3166_1 { get; set; }
public string name { get; set; }
}
public class Spoken_Languages
{
public string iso_639_1 { get; set; }
public string name { get; set; }
}
public class AlternativeTitles
{
public List<Title> titles { get; set; }
}
public class Title
{
public string iso_3166_1 { get; set; }
public string title { get; set; }
}
}

@ -20,11 +20,13 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
private readonly Logger _logger;
private readonly IHttpRequestBuilderFactory _requestBuilder;
private readonly IHttpRequestBuilderFactory _movieBuilder;
public SkyHookProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder, Logger logger)
{
_httpClient = httpClient;
_requestBuilder = requestBuilder.SkyHookTvdb;
_movieBuilder = requestBuilder.TMDB;
_logger = logger;
}
@ -58,6 +60,65 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
return new Tuple<Series, List<Episode>>(series, episodes.ToList());
}
public Movie GetMovieInfo(int TmdbId)
{
var request = _movieBuilder.Create()
.SetSegment("route", "movie")
.SetSegment("id", TmdbId.ToString())
.SetSegment("secondaryRoute", "")
.AddQueryParam("append_to_response", "alternative_titles")
.AddQueryParam("country", "US")
.Build();
request.AllowAutoRedirect = true;
request.SuppressHttpError = true;
var response = _httpClient.Get<MovieResourceRoot>(request);
var resource = response.Resource;
var movie = new Movie();
movie.TmdbId = TmdbId;
movie.ImdbId = resource.imdb_id;
movie.Title = resource.title;
movie.TitleSlug = movie.Title.ToLower().Replace(" ", "-");
movie.CleanTitle = Parser.Parser.CleanSeriesTitle(movie.Title);
movie.Overview = resource.overview;
movie.Website = resource.homepage;
movie.InCinemas = DateTime.Parse(resource.release_date);
movie.Year = movie.InCinemas.Value.Year;
movie.Images.Add(new MediaCover.MediaCover(MediaCoverTypes.Poster, "http://image.tmdb.org/t/p/"+"w500"+resource.poster_path));//TODO: Update to load image specs from tmdb page!
movie.Images.Add(new MediaCover.MediaCover(MediaCoverTypes.Banner, "http://image.tmdb.org/t/p/" + "w1280" + resource.backdrop_path));
movie.Runtime = resource.runtime;
foreach(Title title in resource.alternative_titles.titles)
{
movie.AlternativeTitles.Add(title.title);
}
movie.Ratings = new Ratings();
movie.Ratings.Votes = resource.vote_count;
movie.Ratings.Value = (decimal)resource.vote_average;
foreach(Genre genre in resource.genres)
{
movie.Genres.Add(genre.name);
}
if (resource.status == "Released")
{
movie.Status = MovieStatusType.Released;
}
else
{
movie.Status = MovieStatusType.Announced;
}
return movie;
}
public Movie GetMovieInfo(string ImdbId)
{
var imdbRequest = new HttpRequest("http://www.omdbapi.com/?i=" + ImdbId + "&plot=full&r=json");
@ -136,11 +197,22 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
}
}
var searchTerm = lowerTitle.Replace("+", "_").Replace(" ", "_");
var searchTerm = lowerTitle.Replace("_", "+").Replace(" ", "+");
var firstChar = searchTerm.First();
var imdbRequest = new HttpRequest("https://v2.sg.media-imdb.com/suggests/" + firstChar + "/" + searchTerm + ".json");
var request = _movieBuilder.Create()
.SetSegment("route", "search")
.SetSegment("id", "movie")
.SetSegment("secondaryRoute", "")
.AddQueryParam("query", searchTerm)
.AddQueryParam("include_adult", false)
.Build();
request.AllowAutoRedirect = true;
request.SuppressHttpError = true;
/*var imdbRequest = new HttpRequest("https://v2.sg.media-imdb.com/suggests/" + firstChar + "/" + searchTerm + ".json");
var response = _httpClient.Get(imdbRequest);
@ -154,31 +226,35 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
_logger.Warn("Json object: " + json);
_logger.Warn("Crash ahead.");
_logger.Warn("Crash ahead.");*/
var response = _httpClient.Get<MovieSearchRoot>(request);
var movieResults = response.Resource.results;
var imdbMovies = new List<Movie>();
foreach (MovieResource entry in json.d)
foreach (MovieResult result in movieResults)
{
var imdbMovie = new Movie();
imdbMovie.ImdbId = entry.id;
imdbMovie.TmdbId = result.id;
try
{
imdbMovie.SortTitle = entry.l;
imdbMovie.Title = entry.l;
string titleSlug = entry.l;
imdbMovie.SortTitle = result.title;
imdbMovie.Title = result.title;
string titleSlug = result.title;
imdbMovie.TitleSlug = titleSlug.ToLower().Replace(" ", "-");
imdbMovie.Year = entry.y;
imdbMovie.Year = DateTime.Parse(result.release_date).Year;
imdbMovie.Images = new List<MediaCover.MediaCover>();
try
{
string url = (string)entry.i[0];
var imdbPoster = new MediaCover.MediaCover(MediaCoverTypes.Poster, url);
string url = result.poster_path;
var imdbPoster = new MediaCover.MediaCover(MediaCoverTypes.Poster, "http://image.tmdb.org/t/p/" + "w500" + url);
imdbMovie.Images.Add(imdbPoster);
}
catch (Exception e)
{
_logger.Debug(entry);
_logger.Debug(result);
continue;
}

@ -183,6 +183,7 @@
<Compile Include="Datastore\Migration\002_remove_tvrage_imdb_unique_constraint.cs" />
<Compile Include="Datastore\Migration\003_remove_clean_title_from_scene_mapping.cs" />
<Compile Include="Datastore\Migration\004_updated_history.cs" />
<Compile Include="Datastore\Migration\106_add_tmdb_stuff.cs" />
<Compile Include="Datastore\Migration\105_fix_history_movieId.cs" />
<Compile Include="Datastore\Migration\005_added_eventtype_to_history.cs" />
<Compile Include="Datastore\Migration\006_add_index_to_log_time.cs" />
@ -811,6 +812,7 @@
<Compile Include="MetadataSource\SkyHook\Resource\MovieResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\ShowResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\TimeOfDayResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\TMDBResources.cs" />
<Compile Include="MetadataSource\SkyHook\SkyHookProxy.cs" />
<Compile Include="MetadataSource\SearchSeriesComparer.cs" />
<Compile Include="MetadataSource\SkyHook\SkyHookException.cs" />

@ -16,8 +16,9 @@ namespace NzbDrone.Core.Tv
Genres = new List<string>();
Actors = new List<Actor>();
Tags = new HashSet<int>();
AlternativeTitles = new List<string>();
}
public int TmdbId { get; set; }
public string ImdbId { get; set; }
public string Title { get; set; }
public string CleanTitle { get; set; }
@ -30,6 +31,7 @@ namespace NzbDrone.Core.Tv
public int Runtime { get; set; }
public List<MediaCover.MediaCover> Images { get; set; }
public string TitleSlug { get; set; }
public string Website { get; set; }
public string Path { get; set; }
public int Year { get; set; }
public Ratings Ratings { get; set; }
@ -44,7 +46,7 @@ namespace NzbDrone.Core.Tv
public AddMovieOptions AddOptions { get; set; }
public LazyLoaded<MovieFile> MovieFile { get; set; }
public int MovieFileId { get; set; }
public List<string> AlternativeTitles { get; set; }
public override string ToString()
{
return string.Format("[{0}][{1}]", ImdbId, Title.NullSafe());

@ -77,7 +77,7 @@ namespace NzbDrone.Core.Tv
_logger.Info("Adding Movie {0} Path: [{1}]", newMovie, newMovie.Path);
newMovie.CleanTitle = newMovie.Title.CleanSeriesTitle();
newMovie.SortTitle = MovieTitleNormalizer.Normalize(newMovie.Title, newMovie.ImdbId);
newMovie.SortTitle = MovieTitleNormalizer.Normalize(newMovie.Title, newMovie.TmdbId);
newMovie.Added = DateTime.UtcNow;
_movieRepository.Insert(newMovie);

@ -4,16 +4,16 @@ namespace NzbDrone.Core.Tv
{
public static class MovieTitleNormalizer
{
private readonly static Dictionary<string, string> PreComputedTitles = new Dictionary<string, string>
private readonly static Dictionary<int, string> PreComputedTitles = new Dictionary<int, string>
{
{ "tt_109823457098", "a to z" },
{ 999999999, "a to z" },
};
public static string Normalize(string title, string imdbid)
public static string Normalize(string title, int tmdbid)
{
if (PreComputedTitles.ContainsKey(imdbid))
if (PreComputedTitles.ContainsKey(tmdbid))
{
return PreComputedTitles[imdbid];
return PreComputedTitles[tmdbid];
}
return Parser.Parser.NormalizeTitle(title).ToLower();

@ -51,7 +51,7 @@ namespace NzbDrone.Core.Tv
try
{
movieInfo = _movieInfo.GetMovieInfo(movie.ImdbId);
movieInfo = _movieInfo.GetMovieInfo(movie.TmdbId);
}
catch (MovieNotFoundException)
{
@ -59,10 +59,10 @@ namespace NzbDrone.Core.Tv
return;
}
if (movie.ImdbId != movieInfo.ImdbId)
if (movie.TmdbId != movieInfo.TmdbId)
{
_logger.Warn("Movie '{0}' (tvdbid {1}) was replaced with '{2}' (tvdbid {3}), because the original was a duplicate.", movie.Title, movie.ImdbId, movieInfo.Title, movieInfo.ImdbId);
movie.ImdbId = movieInfo.ImdbId;
_logger.Warn("Movie '{0}' (tvdbid {1}) was replaced with '{2}' (tvdbid {3}), because the original was a duplicate.", movie.Title, movie.TmdbId, movieInfo.Title, movieInfo.TmdbId);
movie.TmdbId = movieInfo.TmdbId;
}
movie.Title = movieInfo.Title;
@ -80,6 +80,8 @@ namespace NzbDrone.Core.Tv
movie.Genres = movieInfo.Genres;
movie.Certification = movieInfo.Certification;
movie.InCinemas = movieInfo.InCinemas;
movie.Website = movieInfo.Website;
movie.AlternativeTitles = movieInfo.AlternativeTitles;
movie.Year = movieInfo.Year;
try

@ -18,9 +18,9 @@ namespace NzbDrone.Core.Validation.Paths
{
if (context.PropertyValue == null) return true;
var imdbid = context.PropertyValue.ToString();
int tmdbId = (int)context.PropertyValue;
return (!_seriesService.GetAllMovies().Exists(s => s.ImdbId == imdbid));
return (!_seriesService.GetAllMovies().Exists(s => s.TmdbId == tmdbId));
}
}
}

@ -33,7 +33,7 @@ Handlebars.registerHelper('remotePoster', function() {
}
return new Handlebars.SafeString('<img class="series-poster placeholder-image" src="{0}">'.format(placeholder));
})
});
Handlebars.registerHelper('traktUrl', function() {
return 'http://trakt.tv/search/tvdb/' + this.tvdbId + '?id_type=show';
@ -47,6 +47,19 @@ Handlebars.registerHelper('tvdbUrl', function() {
return 'http://imdb.com/title/tt' + this.imdbId;
});
Handlebars.registerHelper('tmdbUrl', function() {
return 'https://www.themoviedb.org/movie/' + this.tmdbId;
});
Handlebars.registerHelper('homepage', function() {
return this.website;
});
Handlebars.registerHelper('alternativeTitlesString', function() {
var titles = this.alternativeTitles;
return titles.slice(0,titles.length-1).join(", ") + " and " + titles[titles.length-1];
});
Handlebars.registerHelper('inCinemas', function() {
var monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
@ -55,7 +68,7 @@ Handlebars.registerHelper('inCinemas', function() {
var year = cinemasDate.getFullYear();
var month = monthNames[cinemasDate.getMonth()];
return "In Cinemas " + month + " " + year;
})
});
Handlebars.registerHelper('tvRageUrl', function() {
return 'http://www.tvrage.com/shows/id-' + this.tvRageId;

@ -1,5 +1,5 @@
<div class="row">
<div class="col-md-9">
<div class="col-md-8">
{{profile profileId}}
{{#if network}}
@ -27,11 +27,13 @@
<span class="label label-default">Announced</span>
{{/if_eq}}
</div>
<div class="col-md-3">
<div class="col-md-4">
<span class="series-info-links">
<!--<a href="{{traktUrl}}" class="label label-info">Trakt</a>
<a href="{{tvdbUrl}}" class="label label-info">The TVDB</a>-->
<!--<a href="{{traktUrl}}" class="label label-info">Trakt</a>-->
{{#if website}}
<a href="{{homepage}}" class="label label-info">Homepage</a>
{{/if}}
<a href="{{tmdbUrl}}" class="label label-info">The Movie DB</a>
{{#if imdbId}}
<a href="{{imdbUrl}}" class="label label-info">IMDB</a>
@ -40,18 +42,12 @@
</div>
</div>
{{#if alternateTitles}}
{{#if alternativeTitles}}
<div class="row">
<div class="col-md-12">
{{#each alternateTitles}}
{{#if_eq seasonNumber compare="-1"}}
<span class="label label-default">{{title}}</span>
{{/if_eq}}
{{#if_eq sceneSeasonNumber compare="-1"}}
<span class="label label-default">{{title}}</span>
{{/if_eq}}
{{/each}}
<span class="alternative-titles">
Also known as: {{alternativeTitlesString}}.
</span>
</div>
</div>
{{/if}}

@ -274,7 +274,7 @@ module.exports = Marionette.Layout.extend({
_showBackdrop : function () {
$('body').addClass('backdrop');
var fanArt = this._getImage('fanart');
var fanArt = this._getImage('banner');
if (fanArt) {
this._backstrech = $.backstretch(fanArt);

@ -36,12 +36,18 @@
</div>
</div>
<div id="movie-info">
<ul class="nav nav-tabs" id="myTab">
<li><a href="#movie-history" class="x-movie-history">History</a></li>
<li><a href="#movie-search" class="x-movie-search">Search</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane" id="movie-history"/>
<div class="tab-pane" id="movie-search"/>
<div class="movie-tabs">
<div>
<div class="movie-tabs-card">
<ul class="nav nav-tabs" id="myTab">
<li><a href="#movie-history" class="x-movie-history">History</a></li>
<li><a href="#movie-search" class="x-movie-search">Search</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane" id="movie-history"/>
<div class="tab-pane" id="movie-search"/>
</div>
</div>
</div>
</div>
</div>

@ -8,6 +8,22 @@
max-width: 100%;
}
.movie-tabs-card {
.card;
.opacity(0.9);
margin : 30px 10px;
padding : 10px 25px;
.show-hide-episodes {
.clickable();
text-align : center;
i {
.clickable();
}
}
}
.edit-movie-modal, .delete-movie-modal {
overflow : visible;
@ -253,6 +269,12 @@
margin-bottom : 50px;
}
.alternative-titles {
font-size: 12px;
color: rgba(255, 255, 255, 180);
opacity: .75;
}
.movie-season {
.episode-number-cell {

Loading…
Cancel
Save