MovieDbEpisodeImageProvider.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. using MediaBrowser.Common.IO;
  2. using MediaBrowser.Common.Net;
  3. using MediaBrowser.Controller.Configuration;
  4. using MediaBrowser.Controller.Entities;
  5. using MediaBrowser.Controller.Localization;
  6. using MediaBrowser.Controller.Providers;
  7. using MediaBrowser.Model.Dto;
  8. using MediaBrowser.Model.Entities;
  9. using MediaBrowser.Model.Providers;
  10. using MediaBrowser.Model.Serialization;
  11. using MediaBrowser.Providers.Movies;
  12. using System;
  13. using System.Collections.Generic;
  14. using System.Globalization;
  15. using System.IO;
  16. using System.Linq;
  17. using System.Threading;
  18. using System.Threading.Tasks;
  19. namespace MediaBrowser.Providers.TV
  20. {
  21. public class MovieDbEpisodeImageProvider : IRemoteImageProvider, IHasOrder
  22. {
  23. 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";
  24. private readonly IHttpClient _httpClient;
  25. private readonly IServerConfigurationManager _configurationManager;
  26. private readonly IJsonSerializer _jsonSerializer;
  27. private readonly IFileSystem _fileSystem;
  28. private readonly ILocalizationManager _localization;
  29. public MovieDbEpisodeImageProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization)
  30. {
  31. _httpClient = httpClient;
  32. _configurationManager = configurationManager;
  33. _jsonSerializer = jsonSerializer;
  34. _fileSystem = fileSystem;
  35. _localization = localization;
  36. }
  37. public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
  38. {
  39. return new List<ImageType>
  40. {
  41. ImageType.Primary
  42. };
  43. }
  44. public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken)
  45. {
  46. var episode = (Controller.Entities.TV.Episode)item;
  47. var series = episode.Series;
  48. var seriesId = series != null ? series.GetProviderId(MetadataProviders.Tmdb) : null;
  49. var list = new List<RemoteImageInfo>();
  50. if (string.IsNullOrEmpty(seriesId))
  51. {
  52. return list;
  53. }
  54. var seasonNumber = episode.ParentIndexNumber;
  55. var episodeNumber = episode.IndexNumber;
  56. if (!seasonNumber.HasValue || !episodeNumber.HasValue)
  57. {
  58. return list;
  59. }
  60. var response = await GetEpisodeInfo(seriesId, seasonNumber.Value, episodeNumber.Value,
  61. item.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false);
  62. var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
  63. var tmdbImageUrl = tmdbSettings.images.base_url + "original";
  64. list.AddRange(GetPosters(response.images).Select(i => new RemoteImageInfo
  65. {
  66. Url = tmdbImageUrl + i.file_path,
  67. CommunityRating = i.vote_average,
  68. VoteCount = i.vote_count,
  69. Width = i.width,
  70. Height = i.height,
  71. ProviderName = Name,
  72. Type = ImageType.Primary,
  73. RatingType = RatingType.Score
  74. }));
  75. var language = item.GetPreferredMetadataLanguage();
  76. var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
  77. return list.OrderByDescending(i =>
  78. {
  79. if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
  80. {
  81. return 3;
  82. }
  83. if (!isLanguageEn)
  84. {
  85. if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
  86. {
  87. return 2;
  88. }
  89. }
  90. if (string.IsNullOrEmpty(i.Language))
  91. {
  92. return isLanguageEn ? 3 : 2;
  93. }
  94. return 0;
  95. })
  96. .ThenByDescending(i => i.CommunityRating ?? 0)
  97. .ThenByDescending(i => i.VoteCount ?? 0)
  98. .ToList();
  99. }
  100. private IEnumerable<Still> GetPosters(Images images)
  101. {
  102. return images.stills ?? new List<Still>();
  103. }
  104. public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
  105. {
  106. return _httpClient.GetResponse(new HttpRequestOptions
  107. {
  108. CancellationToken = cancellationToken,
  109. Url = url,
  110. ResourcePool = MovieDbProvider.Current.MovieDbResourcePool
  111. });
  112. }
  113. public string Name
  114. {
  115. get { return "TheMovieDb"; }
  116. }
  117. public bool Supports(IHasImages item)
  118. {
  119. return item is Controller.Entities.TV.Episode;
  120. }
  121. private async Task<RootObject> GetEpisodeInfo(string seriesTmdbId, int season, int episodeNumber, string preferredMetadataLanguage,
  122. CancellationToken cancellationToken)
  123. {
  124. await EnsureEpisodeInfo(seriesTmdbId, season, episodeNumber, preferredMetadataLanguage, cancellationToken)
  125. .ConfigureAwait(false);
  126. var dataFilePath = GetDataFilePath(seriesTmdbId, season, episodeNumber, preferredMetadataLanguage);
  127. return _jsonSerializer.DeserializeFromFile<RootObject>(dataFilePath);
  128. }
  129. internal Task EnsureEpisodeInfo(string tmdbId, int seasonNumber, int episodeNumber, string language, CancellationToken cancellationToken)
  130. {
  131. if (string.IsNullOrEmpty(tmdbId))
  132. {
  133. throw new ArgumentNullException("tmdbId");
  134. }
  135. if (string.IsNullOrEmpty(language))
  136. {
  137. throw new ArgumentNullException("language");
  138. }
  139. var path = GetDataFilePath(tmdbId, seasonNumber, episodeNumber, language);
  140. var fileInfo = _fileSystem.GetFileSystemInfo(path);
  141. if (fileInfo.Exists)
  142. {
  143. // If it's recent or automatic updates are enabled, don't re-download
  144. if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3)
  145. {
  146. return Task.FromResult(true);
  147. }
  148. }
  149. return DownloadEpisodeInfo(tmdbId, seasonNumber, episodeNumber, language, cancellationToken);
  150. }
  151. internal string GetDataFilePath(string tmdbId, int seasonNumber, int episodeNumber, string preferredLanguage)
  152. {
  153. if (string.IsNullOrEmpty(tmdbId))
  154. {
  155. throw new ArgumentNullException("tmdbId");
  156. }
  157. if (string.IsNullOrEmpty(preferredLanguage))
  158. {
  159. throw new ArgumentNullException("preferredLanguage");
  160. }
  161. var path = MovieDbSeriesProvider.GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
  162. var filename = string.Format("season-{0}-episode-{1}-{2}.json",
  163. seasonNumber.ToString(CultureInfo.InvariantCulture),
  164. episodeNumber.ToString(CultureInfo.InvariantCulture),
  165. preferredLanguage);
  166. return Path.Combine(path, filename);
  167. }
  168. internal async Task DownloadEpisodeInfo(string id, int seasonNumber, int episodeNumber, string preferredMetadataLanguage, CancellationToken cancellationToken)
  169. {
  170. var mainResult = await FetchMainResult(id, seasonNumber, episodeNumber, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
  171. var dataFilePath = GetDataFilePath(id, seasonNumber, episodeNumber, preferredMetadataLanguage);
  172. Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
  173. _jsonSerializer.SerializeToFile(mainResult, dataFilePath);
  174. }
  175. internal async Task<RootObject> FetchMainResult(string id, int seasonNumber, int episodeNumber, string language, CancellationToken cancellationToken)
  176. {
  177. var url = string.Format(GetTvInfo3, id, seasonNumber.ToString(CultureInfo.InvariantCulture), episodeNumber, MovieDbProvider.ApiKey);
  178. if (!string.IsNullOrEmpty(language))
  179. {
  180. url += string.Format("&language={0}", language);
  181. }
  182. var includeImageLanguageParam = MovieDbProvider.GetImageLanguagesParam(language);
  183. // Get images in english and with no language
  184. url += "&include_image_language=" + includeImageLanguageParam;
  185. cancellationToken.ThrowIfCancellationRequested();
  186. using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
  187. {
  188. Url = url,
  189. CancellationToken = cancellationToken,
  190. AcceptHeader = MovieDbProvider.AcceptHeader
  191. }).ConfigureAwait(false))
  192. {
  193. return _jsonSerializer.DeserializeFromStream<RootObject>(json);
  194. }
  195. }
  196. public class Still
  197. {
  198. public double aspect_ratio { get; set; }
  199. public string file_path { get; set; }
  200. public int height { get; set; }
  201. public string id { get; set; }
  202. public object iso_639_1 { get; set; }
  203. public double vote_average { get; set; }
  204. public int vote_count { get; set; }
  205. public int width { get; set; }
  206. }
  207. public class Images
  208. {
  209. public List<Still> stills { get; set; }
  210. }
  211. public class ExternalIds
  212. {
  213. public string imdb_id { get; set; }
  214. public object freebase_id { get; set; }
  215. public string freebase_mid { get; set; }
  216. public int tvdb_id { get; set; }
  217. public int tvrage_id { get; set; }
  218. }
  219. public class Cast
  220. {
  221. public string character { get; set; }
  222. public string credit_id { get; set; }
  223. public int id { get; set; }
  224. public string name { get; set; }
  225. public string profile_path { get; set; }
  226. public int order { get; set; }
  227. }
  228. public class Crew
  229. {
  230. public int id { get; set; }
  231. public string credit_id { get; set; }
  232. public string name { get; set; }
  233. public string department { get; set; }
  234. public string job { get; set; }
  235. public string profile_path { get; set; }
  236. }
  237. public class GuestStar
  238. {
  239. public int id { get; set; }
  240. public string name { get; set; }
  241. public string credit_id { get; set; }
  242. public string character { get; set; }
  243. public int order { get; set; }
  244. public string profile_path { get; set; }
  245. }
  246. public class Credits
  247. {
  248. public List<Cast> cast { get; set; }
  249. public List<Crew> crew { get; set; }
  250. public List<GuestStar> guest_stars { get; set; }
  251. }
  252. public class Videos
  253. {
  254. public List<object> results { get; set; }
  255. }
  256. public class RootObject
  257. {
  258. public string air_date { get; set; }
  259. public int episode_number { get; set; }
  260. public string name { get; set; }
  261. public string overview { get; set; }
  262. public int id { get; set; }
  263. public object production_code { get; set; }
  264. public int season_number { get; set; }
  265. public string still_path { get; set; }
  266. public double vote_average { get; set; }
  267. public int vote_count { get; set; }
  268. public Images images { get; set; }
  269. public ExternalIds external_ids { get; set; }
  270. public Credits credits { get; set; }
  271. public Videos videos { get; set; }
  272. }
  273. public int Order
  274. {
  275. get
  276. {
  277. // After tvdb
  278. return 1;
  279. }
  280. }
  281. }
  282. }