MovieDbEpisodeImageProvider.cs 12 KB

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