MovieDbSeasonProvider.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. using MediaBrowser.Common.Net;
  2. using MediaBrowser.Controller.Configuration;
  3. using MediaBrowser.Controller.Entities.TV;
  4. using MediaBrowser.Controller.Localization;
  5. using MediaBrowser.Controller.Providers;
  6. using MediaBrowser.Model.Entities;
  7. using MediaBrowser.Model.Logging;
  8. using MediaBrowser.Model.Net;
  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.Net;
  17. using System.Threading;
  18. using System.Threading.Tasks;
  19. using CommonIO;
  20. namespace MediaBrowser.Providers.TV
  21. {
  22. public class MovieDbSeasonProvider : IRemoteMetadataProvider<Season, SeasonInfo>
  23. {
  24. private const string GetTvInfo3 = @"https://api.themoviedb.org/3/tv/{0}/season/{1}?api_key={2}&append_to_response=images,keywords,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. private readonly ILogger _logger;
  31. public MovieDbSeasonProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer, ILogManager logManager)
  32. {
  33. _httpClient = httpClient;
  34. _configurationManager = configurationManager;
  35. _fileSystem = fileSystem;
  36. _localization = localization;
  37. _jsonSerializer = jsonSerializer;
  38. _logger = logManager.GetLogger(GetType().Name);
  39. }
  40. public async Task<MetadataResult<Season>> GetMetadata(SeasonInfo info, CancellationToken cancellationToken)
  41. {
  42. var result = new MetadataResult<Season>();
  43. string seriesTmdbId;
  44. info.SeriesProviderIds.TryGetValue(MetadataProviders.Tmdb.ToString(), out seriesTmdbId);
  45. var seasonNumber = info.IndexNumber;
  46. if (!string.IsNullOrWhiteSpace(seriesTmdbId) && seasonNumber.HasValue)
  47. {
  48. try
  49. {
  50. var seasonInfo = await GetSeasonInfo(seriesTmdbId, seasonNumber.Value, info.MetadataLanguage, cancellationToken)
  51. .ConfigureAwait(false);
  52. result.HasMetadata = true;
  53. result.Item = new Season();
  54. result.Item.Name = seasonInfo.name;
  55. result.Item.IndexNumber = seasonNumber;
  56. result.Item.Overview = seasonInfo.overview;
  57. if (seasonInfo.external_ids.tvdb_id > 0)
  58. {
  59. result.Item.SetProviderId(MetadataProviders.Tvdb, seasonInfo.external_ids.tvdb_id.ToString(CultureInfo.InvariantCulture));
  60. }
  61. var credits = seasonInfo.credits;
  62. if (credits != null)
  63. {
  64. //Actors, Directors, Writers - all in People
  65. //actors come from cast
  66. if (credits.cast != null)
  67. {
  68. //foreach (var actor in credits.cast.OrderBy(a => a.order)) result.Item.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor, SortOrder = actor.order });
  69. }
  70. //and the rest from crew
  71. if (credits.crew != null)
  72. {
  73. //foreach (var person in credits.crew) result.Item.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department });
  74. }
  75. }
  76. result.Item.PremiereDate = seasonInfo.air_date;
  77. result.Item.ProductionYear = result.Item.PremiereDate.Value.Year;
  78. }
  79. catch (HttpException ex)
  80. {
  81. _logger.Error("No metadata found for {0}", seasonNumber.Value);
  82. if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
  83. {
  84. return result;
  85. }
  86. throw;
  87. }
  88. }
  89. return result;
  90. }
  91. public string Name
  92. {
  93. get { return "TheMovieDb"; }
  94. }
  95. public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeasonInfo searchInfo, CancellationToken cancellationToken)
  96. {
  97. return Task.FromResult<IEnumerable<RemoteSearchResult>>(new List<RemoteSearchResult>());
  98. }
  99. public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
  100. {
  101. return _httpClient.GetResponse(new HttpRequestOptions
  102. {
  103. CancellationToken = cancellationToken,
  104. Url = url,
  105. ResourcePool = MovieDbProvider.Current.MovieDbResourcePool
  106. });
  107. }
  108. private async Task<RootObject> GetSeasonInfo(string seriesTmdbId, int season, string preferredMetadataLanguage,
  109. CancellationToken cancellationToken)
  110. {
  111. await EnsureSeasonInfo(seriesTmdbId, season, preferredMetadataLanguage, cancellationToken)
  112. .ConfigureAwait(false);
  113. var dataFilePath = GetDataFilePath(seriesTmdbId, season, preferredMetadataLanguage);
  114. return _jsonSerializer.DeserializeFromFile<RootObject>(dataFilePath);
  115. }
  116. private readonly Task _cachedTask = Task.FromResult(true);
  117. internal Task EnsureSeasonInfo(string tmdbId, int seasonNumber, string language, CancellationToken cancellationToken)
  118. {
  119. if (string.IsNullOrEmpty(tmdbId))
  120. {
  121. throw new ArgumentNullException("tmdbId");
  122. }
  123. if (string.IsNullOrEmpty(language))
  124. {
  125. throw new ArgumentNullException("language");
  126. }
  127. var path = GetDataFilePath(tmdbId, seasonNumber, language);
  128. var fileInfo = _fileSystem.GetFileSystemInfo(path);
  129. if (fileInfo.Exists)
  130. {
  131. // If it's recent or automatic updates are enabled, don't re-download
  132. if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3)
  133. {
  134. return _cachedTask;
  135. }
  136. }
  137. return DownloadSeasonInfo(tmdbId, seasonNumber, language, cancellationToken);
  138. }
  139. internal string GetDataFilePath(string tmdbId, int seasonNumber, string preferredLanguage)
  140. {
  141. if (string.IsNullOrEmpty(tmdbId))
  142. {
  143. throw new ArgumentNullException("tmdbId");
  144. }
  145. if (string.IsNullOrEmpty(preferredLanguage))
  146. {
  147. throw new ArgumentNullException("preferredLanguage");
  148. }
  149. var path = MovieDbSeriesProvider.GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
  150. var filename = string.Format("season-{0}-{1}.json",
  151. seasonNumber.ToString(CultureInfo.InvariantCulture),
  152. preferredLanguage);
  153. return Path.Combine(path, filename);
  154. }
  155. internal async Task DownloadSeasonInfo(string id, int seasonNumber, string preferredMetadataLanguage, CancellationToken cancellationToken)
  156. {
  157. var mainResult = await FetchMainResult(id, seasonNumber, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
  158. var dataFilePath = GetDataFilePath(id, seasonNumber, preferredMetadataLanguage);
  159. _fileSystem.CreateDirectory(Path.GetDirectoryName(dataFilePath));
  160. _jsonSerializer.SerializeToFile(mainResult, dataFilePath);
  161. }
  162. internal async Task<RootObject> FetchMainResult(string id, int seasonNumber, string language, CancellationToken cancellationToken)
  163. {
  164. var url = string.Format(GetTvInfo3, id, seasonNumber.ToString(CultureInfo.InvariantCulture), MovieDbProvider.ApiKey);
  165. if (!string.IsNullOrEmpty(language))
  166. {
  167. url += string.Format("&language={0}", MovieDbProvider.NormalizeLanguage(language));
  168. }
  169. var includeImageLanguageParam = MovieDbProvider.GetImageLanguagesParam(language);
  170. // Get images in english and with no language
  171. url += "&include_image_language=" + includeImageLanguageParam;
  172. cancellationToken.ThrowIfCancellationRequested();
  173. using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
  174. {
  175. Url = url,
  176. CancellationToken = cancellationToken,
  177. AcceptHeader = MovieDbProvider.AcceptHeader
  178. }).ConfigureAwait(false))
  179. {
  180. return _jsonSerializer.DeserializeFromStream<RootObject>(json);
  181. }
  182. }
  183. public class Episode
  184. {
  185. public string air_date { get; set; }
  186. public int episode_number { get; set; }
  187. public int id { get; set; }
  188. public string name { get; set; }
  189. public string overview { get; set; }
  190. public string still_path { get; set; }
  191. public double vote_average { get; set; }
  192. public int vote_count { get; set; }
  193. }
  194. public class Cast
  195. {
  196. public string character { get; set; }
  197. public string credit_id { get; set; }
  198. public int id { get; set; }
  199. public string name { get; set; }
  200. public string profile_path { get; set; }
  201. public int order { get; set; }
  202. }
  203. public class Crew
  204. {
  205. public string credit_id { get; set; }
  206. public string department { get; set; }
  207. public int id { get; set; }
  208. public string name { get; set; }
  209. public string job { get; set; }
  210. public string profile_path { get; set; }
  211. }
  212. public class Credits
  213. {
  214. public List<Cast> cast { get; set; }
  215. public List<Crew> crew { get; set; }
  216. }
  217. public class Poster
  218. {
  219. public double aspect_ratio { get; set; }
  220. public string file_path { get; set; }
  221. public int height { get; set; }
  222. public string id { get; set; }
  223. public string iso_639_1 { get; set; }
  224. public double vote_average { get; set; }
  225. public int vote_count { get; set; }
  226. public int width { get; set; }
  227. }
  228. public class Images
  229. {
  230. public List<Poster> posters { get; set; }
  231. }
  232. public class ExternalIds
  233. {
  234. public string freebase_id { get; set; }
  235. public string freebase_mid { get; set; }
  236. public int tvdb_id { get; set; }
  237. public object tvrage_id { get; set; }
  238. }
  239. public class Videos
  240. {
  241. public List<object> results { get; set; }
  242. }
  243. public class RootObject
  244. {
  245. public DateTime air_date { get; set; }
  246. public List<Episode> episodes { get; set; }
  247. public string name { get; set; }
  248. public string overview { get; set; }
  249. public int id { get; set; }
  250. public string poster_path { get; set; }
  251. public int season_number { get; set; }
  252. public Credits credits { get; set; }
  253. public Images images { get; set; }
  254. public ExternalIds external_ids { get; set; }
  255. public Videos videos { get; set; }
  256. }
  257. }
  258. }