FanArtMovieProvider.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. using MediaBrowser.Common.Configuration;
  2. using MediaBrowser.Common.Extensions;
  3. using MediaBrowser.Common.IO;
  4. using MediaBrowser.Common.Net;
  5. using MediaBrowser.Controller.Configuration;
  6. using MediaBrowser.Controller.Entities;
  7. using MediaBrowser.Controller.Entities.Movies;
  8. using MediaBrowser.Model.Entities;
  9. using MediaBrowser.Model.Logging;
  10. using System;
  11. using System.Globalization;
  12. using System.IO;
  13. using System.Linq;
  14. using System.Threading;
  15. using System.Threading.Tasks;
  16. using System.Xml;
  17. namespace MediaBrowser.Controller.Providers.Movies
  18. {
  19. /// <summary>
  20. /// Class FanArtMovieProvider
  21. /// </summary>
  22. class FanArtMovieProvider : FanartBaseProvider
  23. {
  24. /// <summary>
  25. /// Gets the HTTP client.
  26. /// </summary>
  27. /// <value>The HTTP client.</value>
  28. protected IHttpClient HttpClient { get; private set; }
  29. /// <summary>
  30. /// The _provider manager
  31. /// </summary>
  32. private readonly IProviderManager _providerManager;
  33. /// <summary>
  34. /// The us culture
  35. /// </summary>
  36. private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
  37. internal static FanArtMovieProvider Current { get; private set; }
  38. /// <summary>
  39. /// Initializes a new instance of the <see cref="FanArtMovieProvider" /> class.
  40. /// </summary>
  41. /// <param name="httpClient">The HTTP client.</param>
  42. /// <param name="logManager">The log manager.</param>
  43. /// <param name="configurationManager">The configuration manager.</param>
  44. /// <param name="providerManager">The provider manager.</param>
  45. /// <exception cref="System.ArgumentNullException">httpClient</exception>
  46. public FanArtMovieProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
  47. : base(logManager, configurationManager)
  48. {
  49. if (httpClient == null)
  50. {
  51. throw new ArgumentNullException("httpClient");
  52. }
  53. HttpClient = httpClient;
  54. _providerManager = providerManager;
  55. Current = this;
  56. }
  57. /// <summary>
  58. /// Gets a value indicating whether [refresh on version change].
  59. /// </summary>
  60. /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
  61. protected override bool RefreshOnVersionChange
  62. {
  63. get
  64. {
  65. return true;
  66. }
  67. }
  68. /// <summary>
  69. /// Gets the provider version.
  70. /// </summary>
  71. /// <value>The provider version.</value>
  72. protected override string ProviderVersion
  73. {
  74. get
  75. {
  76. return "13";
  77. }
  78. }
  79. /// <summary>
  80. /// The fan art base URL
  81. /// </summary>
  82. protected string FanArtBaseUrl = "http://api.fanart.tv/webservice/movie/{0}/{1}/xml/all/1/1";
  83. /// <summary>
  84. /// Supportses the specified item.
  85. /// </summary>
  86. /// <param name="item">The item.</param>
  87. /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
  88. public override bool Supports(BaseItem item)
  89. {
  90. var trailer = item as Trailer;
  91. if (trailer != null)
  92. {
  93. return !trailer.IsLocalTrailer;
  94. }
  95. return item is Movie || item is BoxSet || item is MusicVideo;
  96. }
  97. /// <summary>
  98. /// Needses the refresh internal.
  99. /// </summary>
  100. /// <param name="item">The item.</param>
  101. /// <param name="providerInfo">The provider info.</param>
  102. /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
  103. protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
  104. {
  105. if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb)))
  106. {
  107. return false;
  108. }
  109. if (!ConfigurationManager.Configuration.DownloadMovieImages.Art &&
  110. !ConfigurationManager.Configuration.DownloadMovieImages.Logo &&
  111. !ConfigurationManager.Configuration.DownloadMovieImages.Disc &&
  112. !ConfigurationManager.Configuration.DownloadMovieImages.Backdrops &&
  113. !ConfigurationManager.Configuration.DownloadMovieImages.Banner &&
  114. !ConfigurationManager.Configuration.DownloadMovieImages.Thumb)
  115. {
  116. return false;
  117. }
  118. // Refresh if tmdb id has changed
  119. if (providerInfo.Data != GetComparisonData(item.GetProviderId(MetadataProviders.Tmdb)))
  120. {
  121. return true;
  122. }
  123. return base.NeedsRefreshInternal(item, providerInfo);
  124. }
  125. /// <summary>
  126. /// Gets the comparison data.
  127. /// </summary>
  128. /// <param name="id">The id.</param>
  129. /// <returns>Guid.</returns>
  130. private Guid GetComparisonData(string id)
  131. {
  132. if (!string.IsNullOrEmpty(id))
  133. {
  134. // Process images
  135. var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, id);
  136. var files = new DirectoryInfo(path)
  137. .EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly)
  138. .Select(i => i.FullName + i.LastWriteTimeUtc.Ticks)
  139. .ToArray();
  140. if (files.Length > 0)
  141. {
  142. return string.Join(string.Empty, files).GetMD5();
  143. }
  144. }
  145. return Guid.Empty;
  146. }
  147. /// <summary>
  148. /// Gets the movie data path.
  149. /// </summary>
  150. /// <param name="appPaths">The app paths.</param>
  151. /// <param name="tmdbId">The TMDB id.</param>
  152. /// <returns>System.String.</returns>
  153. internal static string GetMovieDataPath(IApplicationPaths appPaths, string tmdbId)
  154. {
  155. var dataPath = Path.Combine(GetMoviesDataPath(appPaths), tmdbId);
  156. if (!Directory.Exists(dataPath))
  157. {
  158. Directory.CreateDirectory(dataPath);
  159. }
  160. return dataPath;
  161. }
  162. /// <summary>
  163. /// Gets the movie data path.
  164. /// </summary>
  165. /// <param name="appPaths">The app paths.</param>
  166. /// <returns>System.String.</returns>
  167. internal static string GetMoviesDataPath(IApplicationPaths appPaths)
  168. {
  169. var dataPath = Path.Combine(appPaths.DataPath, "fanart-movies");
  170. if (!Directory.Exists(dataPath))
  171. {
  172. Directory.CreateDirectory(dataPath);
  173. }
  174. return dataPath;
  175. }
  176. /// <summary>
  177. /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
  178. /// </summary>
  179. /// <param name="item">The item.</param>
  180. /// <param name="force">if set to <c>true</c> [force].</param>
  181. /// <param name="cancellationToken">The cancellation token.</param>
  182. /// <returns>Task{System.Boolean}.</returns>
  183. public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken)
  184. {
  185. cancellationToken.ThrowIfCancellationRequested();
  186. BaseProviderInfo data;
  187. if (!item.ProviderData.TryGetValue(Id, out data))
  188. {
  189. data = new BaseProviderInfo();
  190. item.ProviderData[Id] = data;
  191. }
  192. var movieId = item.GetProviderId(MetadataProviders.Tmdb);
  193. var movieDataPath = GetMovieDataPath(ConfigurationManager.ApplicationPaths, movieId);
  194. var xmlPath = Path.Combine(movieDataPath, "fanart.xml");
  195. // Only download the xml if it doesn't already exist. The prescan task will take care of getting updates
  196. if (!File.Exists(xmlPath))
  197. {
  198. await DownloadMovieXml(movieDataPath, movieId, cancellationToken).ConfigureAwait(false);
  199. }
  200. if (File.Exists(xmlPath))
  201. {
  202. await FetchFromXml(item, xmlPath, cancellationToken).ConfigureAwait(false);
  203. }
  204. data.Data = GetComparisonData(item.GetProviderId(MetadataProviders.Tmdb));
  205. SetLastRefreshed(item, DateTime.UtcNow);
  206. return true;
  207. }
  208. /// <summary>
  209. /// Downloads the movie XML.
  210. /// </summary>
  211. /// <param name="movieDataPath">The movie data path.</param>
  212. /// <param name="tmdbId">The TMDB id.</param>
  213. /// <param name="cancellationToken">The cancellation token.</param>
  214. /// <returns>Task.</returns>
  215. internal async Task DownloadMovieXml(string movieDataPath, string tmdbId, CancellationToken cancellationToken)
  216. {
  217. cancellationToken.ThrowIfCancellationRequested();
  218. string url = string.Format(FanArtBaseUrl, ApiKey, tmdbId);
  219. var xmlPath = Path.Combine(movieDataPath, "fanart.xml");
  220. using (var response = await HttpClient.Get(new HttpRequestOptions
  221. {
  222. Url = url,
  223. ResourcePool = FanArtResourcePool,
  224. CancellationToken = cancellationToken
  225. }).ConfigureAwait(false))
  226. {
  227. using (var xmlFileStream = new FileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
  228. {
  229. await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
  230. }
  231. }
  232. }
  233. /// <summary>
  234. /// Fetches from XML.
  235. /// </summary>
  236. /// <param name="item">The item.</param>
  237. /// <param name="xmlFilePath">The XML file path.</param>
  238. /// <param name="cancellationToken">The cancellation token.</param>
  239. /// <returns>Task.</returns>
  240. private async Task FetchFromXml(BaseItem item, string xmlFilePath, CancellationToken cancellationToken)
  241. {
  242. var doc = new XmlDocument();
  243. doc.Load(xmlFilePath);
  244. var language = ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower();
  245. cancellationToken.ThrowIfCancellationRequested();
  246. var saveLocal = ConfigurationManager.Configuration.SaveLocalMeta &&
  247. item.LocationType == LocationType.FileSystem;
  248. string path;
  249. var hd = ConfigurationManager.Configuration.DownloadHDFanArt ? "hd" : "";
  250. if (ConfigurationManager.Configuration.DownloadMovieImages.Logo && !item.HasImage(ImageType.Logo))
  251. {
  252. var node =
  253. doc.SelectSingleNode("//fanart/movie/movielogos/" + hd + "movielogo[@lang = \"" + language + "\"]/@url") ??
  254. doc.SelectSingleNode("//fanart/movie/movielogos/movielogo[@lang = \"" + language + "\"]/@url");
  255. if (node == null && language != "en")
  256. {
  257. //maybe just couldn't find language - try just first one
  258. node = doc.SelectSingleNode("//fanart/movie/movielogos/" + hd + "movielogo/@url");
  259. }
  260. path = node != null ? node.Value : null;
  261. if (!string.IsNullOrEmpty(path))
  262. {
  263. item.SetImage(ImageType.Logo, await _providerManager.DownloadAndSaveImage(item, path, LogoFile, saveLocal, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
  264. }
  265. }
  266. cancellationToken.ThrowIfCancellationRequested();
  267. if (ConfigurationManager.Configuration.DownloadMovieImages.Art && !item.HasImage(ImageType.Art))
  268. {
  269. var node =
  270. doc.SelectSingleNode("//fanart/movie/moviearts/" + hd + "movieart[@lang = \"" + language + "\"]/@url") ??
  271. doc.SelectSingleNode("//fanart/movie/moviearts/" + hd + "movieart/@url") ??
  272. doc.SelectSingleNode("//fanart/movie/moviearts/movieart[@lang = \"" + language + "\"]/@url") ??
  273. doc.SelectSingleNode("//fanart/movie/moviearts/movieart/@url");
  274. path = node != null ? node.Value : null;
  275. if (!string.IsNullOrEmpty(path))
  276. {
  277. item.SetImage(ImageType.Art, await _providerManager.DownloadAndSaveImage(item, path, ArtFile, saveLocal, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
  278. }
  279. }
  280. cancellationToken.ThrowIfCancellationRequested();
  281. if (ConfigurationManager.Configuration.DownloadMovieImages.Disc && !item.HasImage(ImageType.Disc))
  282. {
  283. var node = doc.SelectSingleNode("//fanart/movie/moviediscs/moviedisc[@lang = \"" + language + "\"]/@url") ??
  284. doc.SelectSingleNode("//fanart/movie/moviediscs/moviedisc/@url");
  285. path = node != null ? node.Value : null;
  286. if (!string.IsNullOrEmpty(path))
  287. {
  288. item.SetImage(ImageType.Disc, await _providerManager.DownloadAndSaveImage(item, path, DiscFile, saveLocal, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
  289. }
  290. }
  291. cancellationToken.ThrowIfCancellationRequested();
  292. if (ConfigurationManager.Configuration.DownloadMovieImages.Banner && !item.HasImage(ImageType.Banner))
  293. {
  294. var node = doc.SelectSingleNode("//fanart/movie/moviebanners/moviebanner[@lang = \"" + language + "\"]/@url") ??
  295. doc.SelectSingleNode("//fanart/movie/moviebanners/moviebanner/@url");
  296. path = node != null ? node.Value : null;
  297. if (!string.IsNullOrEmpty(path))
  298. {
  299. item.SetImage(ImageType.Banner, await _providerManager.DownloadAndSaveImage(item, path, BannerFile, saveLocal, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
  300. }
  301. }
  302. cancellationToken.ThrowIfCancellationRequested();
  303. if (ConfigurationManager.Configuration.DownloadMovieImages.Thumb && !item.HasImage(ImageType.Thumb))
  304. {
  305. var node = doc.SelectSingleNode("//fanart/movie/moviethumbs/moviethumb[@lang = \"" + language + "\"]/@url") ??
  306. doc.SelectSingleNode("//fanart/movie/moviethumbs/moviethumb/@url");
  307. path = node != null ? node.Value : null;
  308. if (!string.IsNullOrEmpty(path))
  309. {
  310. item.SetImage(ImageType.Thumb, await _providerManager.DownloadAndSaveImage(item, path, ThumbFile, saveLocal, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
  311. }
  312. }
  313. if (ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && item.BackdropImagePaths.Count < ConfigurationManager.Configuration.MaxBackdrops)
  314. {
  315. var nodes = doc.SelectNodes("//fanart/movie/moviebackgrounds//@url");
  316. if (nodes != null)
  317. {
  318. var numBackdrops = item.BackdropImagePaths.Count;
  319. foreach (XmlNode node in nodes)
  320. {
  321. path = node.Value;
  322. if (!string.IsNullOrEmpty(path))
  323. {
  324. item.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(item, path, ("backdrop" + (numBackdrops > 0 ? numBackdrops.ToString(UsCulture) : "") + ".jpg"), saveLocal, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
  325. numBackdrops++;
  326. if (item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops) break;
  327. }
  328. }
  329. }
  330. }
  331. }
  332. }
  333. }