From 379fa002288032097ff3222c7484136f10ab69d2 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 7 Jul 2014 10:23:08 -0400 Subject: [PATCH] support episode images from tmdb --- .../MediaBrowser.Providers.csproj | 1 + .../TV/MovieDbEpisodeImageProvider.cs | 347 ++++++++++++++++++ .../TV/MovieDbSeasonProvider.cs | 5 +- MediaBrowser.Providers/TV/TvExternalIds.cs | 23 ++ 4 files changed, 373 insertions(+), 3 deletions(-) create mode 100644 MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 406c5e2e53..fe22d4c99b 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -163,6 +163,7 @@ + diff --git a/MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs new file mode 100644 index 0000000000..7979711ec0 --- /dev/null +++ b/MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs @@ -0,0 +1,347 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Providers.Movies; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.TV +{ + public class MovieDbEpisodeImageProvider : IRemoteImageProvider, IHasOrder + { + private const string GetTvInfo3 = @"http://api.themoviedb.org/3/tv/{0}/season/{1}/episode/{2}?api_key={3}&append_to_response=images,external_ids,credits,videos"; + private readonly IHttpClient _httpClient; + private readonly IServerConfigurationManager _configurationManager; + private readonly IJsonSerializer _jsonSerializer; + private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; + + public MovieDbEpisodeImageProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization) + { + _httpClient = httpClient; + _configurationManager = configurationManager; + _jsonSerializer = jsonSerializer; + _fileSystem = fileSystem; + _localization = localization; + } + + public IEnumerable GetSupportedImages(IHasImages item) + { + return new List + { + ImageType.Primary + }; + } + + public async Task> GetImages(IHasImages item, CancellationToken cancellationToken) + { + var episode = (Controller.Entities.TV.Episode)item; + var series = episode.Series; + + var seriesId = series != null ? series.GetProviderId(MetadataProviders.Tmdb) : null; + + var list = new List(); + + if (string.IsNullOrEmpty(seriesId)) + { + return list; + } + + var seasonNumber = episode.ParentIndexNumber; + var episodeNumber = episode.IndexNumber; + + if (!seasonNumber.HasValue || !episodeNumber.HasValue) + { + return list; + } + + var response = await GetEpisodeInfo(seriesId, seasonNumber.Value, episodeNumber.Value, + item.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false); + + var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); + + var tmdbImageUrl = tmdbSettings.images.base_url + "original"; + + list.AddRange(GetPosters(response.images).Select(i => new RemoteImageInfo + { + Url = tmdbImageUrl + i.file_path, + CommunityRating = i.vote_average, + VoteCount = i.vote_count, + Width = i.width, + Height = i.height, + ProviderName = Name, + Type = ImageType.Primary, + RatingType = RatingType.Score + })); + + var language = item.GetPreferredMetadataLanguage(); + + var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase); + + return list.OrderByDescending(i => + { + if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase)) + { + return 3; + } + if (!isLanguageEn) + { + if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase)) + { + return 2; + } + } + if (string.IsNullOrEmpty(i.Language)) + { + return isLanguageEn ? 3 : 2; + } + return 0; + }) + .ThenByDescending(i => i.CommunityRating ?? 0) + .ThenByDescending(i => i.VoteCount ?? 0) + .ToList(); + + } + + private IEnumerable GetPosters(Images images) + { + return images.stills ?? new List(); + } + + + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClient.GetResponse(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = url, + ResourcePool = MovieDbProvider.Current.MovieDbResourcePool + }); + } + + public string Name + { + get { return "TheMovieDb"; } + } + + public bool Supports(IHasImages item) + { + return item is Controller.Entities.TV.Episode; + } + + private async Task GetEpisodeInfo(string seriesTmdbId, int season, int episodeNumber, string preferredMetadataLanguage, + CancellationToken cancellationToken) + { + await EnsureEpisodeInfo(seriesTmdbId, season, episodeNumber, preferredMetadataLanguage, cancellationToken) + .ConfigureAwait(false); + + var dataFilePath = GetDataFilePath(seriesTmdbId, season, episodeNumber, preferredMetadataLanguage); + + return _jsonSerializer.DeserializeFromFile(dataFilePath); + } + + internal Task EnsureEpisodeInfo(string tmdbId, int seasonNumber, int episodeNumber, string language, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(tmdbId)) + { + throw new ArgumentNullException("tmdbId"); + } + if (string.IsNullOrEmpty(language)) + { + throw new ArgumentNullException("language"); + } + + var path = GetDataFilePath(tmdbId, seasonNumber, episodeNumber, language); + + var fileInfo = _fileSystem.GetFileSystemInfo(path); + + if (fileInfo.Exists) + { + // If it's recent or automatic updates are enabled, don't re-download + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3) + { + return Task.FromResult(true); + } + } + + return DownloadEpisodeInfo(tmdbId, seasonNumber, episodeNumber, language, cancellationToken); + } + + internal string GetDataFilePath(string tmdbId, int seasonNumber, int episodeNumber, string preferredLanguage) + { + if (string.IsNullOrEmpty(tmdbId)) + { + throw new ArgumentNullException("tmdbId"); + } + if (string.IsNullOrEmpty(preferredLanguage)) + { + throw new ArgumentNullException("preferredLanguage"); + } + + var path = MovieDbSeriesProvider.GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId); + + var filename = string.Format("season-{0}-episode-{1}-{2}.json", + seasonNumber.ToString(CultureInfo.InvariantCulture), + episodeNumber.ToString(CultureInfo.InvariantCulture), + preferredLanguage); + + return Path.Combine(path, filename); + } + + internal async Task DownloadEpisodeInfo(string id, int seasonNumber, int episodeNumber, string preferredMetadataLanguage, CancellationToken cancellationToken) + { + var mainResult = await FetchMainResult(id, seasonNumber, episodeNumber, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + + var dataFilePath = GetDataFilePath(id, seasonNumber, episodeNumber, preferredMetadataLanguage); + + Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath)); + _jsonSerializer.SerializeToFile(mainResult, dataFilePath); + } + + internal async Task FetchMainResult(string id, int seasonNumber, int episodeNumber, string language, CancellationToken cancellationToken) + { + var url = string.Format(GetTvInfo3, id, seasonNumber.ToString(CultureInfo.InvariantCulture), episodeNumber, MovieDbProvider.ApiKey); + + var imageLanguages = _localization.GetCultures() + .Select(i => i.TwoLetterISOLanguageName) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + imageLanguages.Add("null"); + + if (!string.IsNullOrEmpty(language)) + { + // If preferred language isn't english, get those images too + if (imageLanguages.Contains(language, StringComparer.OrdinalIgnoreCase)) + { + imageLanguages.Add(language); + } + + url += string.Format("&language={0}", language); + } + + // Get images in english and with no language + url += "&include_image_language=" + string.Join(",", imageLanguages.ToArray()); + + cancellationToken.ThrowIfCancellationRequested(); + + using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions + { + Url = url, + CancellationToken = cancellationToken, + AcceptHeader = MovieDbProvider.AcceptHeader + + }).ConfigureAwait(false)) + { + return _jsonSerializer.DeserializeFromStream(json); + } + } + + public class Still + { + public double aspect_ratio { get; set; } + public string file_path { get; set; } + public int height { get; set; } + public string id { get; set; } + public object iso_639_1 { get; set; } + public double vote_average { get; set; } + public int vote_count { get; set; } + public int width { get; set; } + } + + public class Images + { + public List stills { get; set; } + } + + public class ExternalIds + { + public string imdb_id { get; set; } + public object freebase_id { get; set; } + public string freebase_mid { get; set; } + public int tvdb_id { get; set; } + public int tvrage_id { get; set; } + } + + public class Cast + { + public string character { get; set; } + public string credit_id { get; set; } + public int id { get; set; } + public string name { get; set; } + public string profile_path { get; set; } + public int order { get; set; } + } + + public class Crew + { + public int id { get; set; } + public string credit_id { get; set; } + public string name { get; set; } + public string department { get; set; } + public string job { get; set; } + public string profile_path { get; set; } + } + + public class GuestStar + { + public int id { get; set; } + public string name { get; set; } + public string credit_id { get; set; } + public string character { get; set; } + public int order { get; set; } + public string profile_path { get; set; } + } + + public class Credits + { + public List cast { get; set; } + public List crew { get; set; } + public List guest_stars { get; set; } + } + + public class Videos + { + public List results { get; set; } + } + + public class RootObject + { + public string air_date { get; set; } + public int episode_number { get; set; } + public string name { get; set; } + public string overview { get; set; } + public int id { get; set; } + public object production_code { get; set; } + public int season_number { get; set; } + public string still_path { get; set; } + public double vote_average { get; set; } + public int vote_count { get; set; } + public Images images { get; set; } + public ExternalIds external_ids { get; set; } + public Credits credits { get; set; } + public Videos videos { get; set; } + } + + public int Order + { + get + { + // After tvdb + return 1; + } + } + } +} diff --git a/MediaBrowser.Providers/TV/MovieDbSeasonProvider.cs b/MediaBrowser.Providers/TV/MovieDbSeasonProvider.cs index 1950ae0ce3..7748615a73 100644 --- a/MediaBrowser.Providers/TV/MovieDbSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/MovieDbSeasonProvider.cs @@ -1,7 +1,6 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Providers; @@ -108,7 +107,7 @@ namespace MediaBrowser.Providers.TV private async Task GetSeasonInfo(string seriesTmdbId, int season, string preferredMetadataLanguage, CancellationToken cancellationToken) { - await EnsureSeriesInfo(seriesTmdbId, season, preferredMetadataLanguage, cancellationToken) + await EnsureSeasonInfo(seriesTmdbId, season, preferredMetadataLanguage, cancellationToken) .ConfigureAwait(false); var dataFilePath = GetDataFilePath(seriesTmdbId, season, preferredMetadataLanguage); @@ -117,7 +116,7 @@ namespace MediaBrowser.Providers.TV } private readonly Task _cachedTask = Task.FromResult(true); - internal Task EnsureSeriesInfo(string tmdbId, int seasonNumber, string language, CancellationToken cancellationToken) + internal Task EnsureSeasonInfo(string tmdbId, int seasonNumber, string language, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(tmdbId)) { diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs index 60f1321573..82baae2507 100644 --- a/MediaBrowser.Providers/TV/TvExternalIds.cs +++ b/MediaBrowser.Providers/TV/TvExternalIds.cs @@ -51,6 +51,29 @@ namespace MediaBrowser.Providers.TV } } + public class TvdbSeasonExternalId : IExternalId + { + public string Name + { + get { return "TheTVDB"; } + } + + public string Key + { + get { return MetadataProviders.Tvdb.ToString(); } + } + + public string UrlFormatString + { + get { return null; } + } + + public bool Supports(IHasProviderIds item) + { + return item is Season; + } + } + public class TvdbEpisodeExternalId : IExternalId { public string Name