ItemImageProvider.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. using MediaBrowser.Common.Extensions;
  2. using MediaBrowser.Common.IO;
  3. using MediaBrowser.Controller.Configuration;
  4. using MediaBrowser.Controller.Entities;
  5. using MediaBrowser.Controller.Library;
  6. using MediaBrowser.Controller.Providers;
  7. using MediaBrowser.Model.Configuration;
  8. using MediaBrowser.Model.Entities;
  9. using MediaBrowser.Model.Logging;
  10. using MediaBrowser.Model.Net;
  11. using MediaBrowser.Model.Providers;
  12. using System;
  13. using System.Collections.Generic;
  14. using System.IO;
  15. using System.Linq;
  16. using System.Net;
  17. using System.Threading;
  18. using System.Threading.Tasks;
  19. namespace MediaBrowser.Providers.Manager
  20. {
  21. public class ItemImageProvider
  22. {
  23. private readonly ILogger _logger;
  24. private readonly IProviderManager _providerManager;
  25. private readonly IServerConfigurationManager _config;
  26. private readonly IFileSystem _fileSystem;
  27. public ItemImageProvider(ILogger logger, IProviderManager providerManager, IServerConfigurationManager config, IFileSystem fileSystem)
  28. {
  29. _logger = logger;
  30. _providerManager = providerManager;
  31. _config = config;
  32. _fileSystem = fileSystem;
  33. }
  34. public bool ValidateImages(IHasImages item, IEnumerable<IImageProvider> providers, IDirectoryService directoryService)
  35. {
  36. var hasChanges = item.ValidateImages(directoryService);
  37. foreach (var provider in providers.OfType<ILocalImageFileProvider>())
  38. {
  39. var images = provider.GetImages(item, directoryService);
  40. if (MergeImages(item, images))
  41. {
  42. hasChanges = true;
  43. }
  44. }
  45. return hasChanges;
  46. }
  47. public async Task<RefreshResult> RefreshImages(IHasImages item, IEnumerable<IImageProvider> imageProviders, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken)
  48. {
  49. var result = new RefreshResult { UpdateType = ItemUpdateType.None };
  50. var providers = imageProviders.ToList();
  51. var providerIds = new List<Guid>();
  52. // In order to avoid duplicates, only download these if there are none already
  53. var backdropLimit = savedOptions.GetLimit(ImageType.Backdrop);
  54. var screenshotLimit = savedOptions.GetLimit(ImageType.Screenshot);
  55. foreach (var provider in providers)
  56. {
  57. var remoteProvider = provider as IRemoteImageProvider;
  58. if (remoteProvider != null)
  59. {
  60. await RefreshFromProvider(item, remoteProvider, refreshOptions, savedOptions, backdropLimit, screenshotLimit, result, cancellationToken).ConfigureAwait(false);
  61. providerIds.Add(provider.GetType().FullName.GetMD5());
  62. continue;
  63. }
  64. var dynamicImageProvider = provider as IDynamicImageProvider;
  65. if (dynamicImageProvider != null)
  66. {
  67. await RefreshFromProvider(item, dynamicImageProvider, savedOptions, result, cancellationToken).ConfigureAwait(false);
  68. providerIds.Add(provider.GetType().FullName.GetMD5());
  69. }
  70. }
  71. result.Providers = providerIds;
  72. return result;
  73. }
  74. /// <summary>
  75. /// Refreshes from provider.
  76. /// </summary>
  77. /// <param name="item">The item.</param>
  78. /// <param name="provider">The provider.</param>
  79. /// <param name="savedOptions">The saved options.</param>
  80. /// <param name="result">The result.</param>
  81. /// <param name="cancellationToken">The cancellation token.</param>
  82. /// <returns>Task.</returns>
  83. private async Task RefreshFromProvider(IHasImages item, IDynamicImageProvider provider, MetadataOptions savedOptions, RefreshResult result, CancellationToken cancellationToken)
  84. {
  85. try
  86. {
  87. var images = provider.GetSupportedImages(item);
  88. foreach (var imageType in images)
  89. {
  90. if (!item.HasImage(imageType) && savedOptions.IsEnabled(imageType))
  91. {
  92. _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
  93. var response = await provider.GetImage(item, imageType, cancellationToken).ConfigureAwait(false);
  94. if (response.HasImage)
  95. {
  96. if (!string.IsNullOrEmpty(response.Path))
  97. {
  98. var mimeType = "image/" + Path.GetExtension(response.Path).TrimStart('.').ToLower();
  99. var stream = _fileSystem.GetFileStream(response.Path, FileMode.Open, FileAccess.Read, FileShare.Read, true);
  100. await _providerManager.SaveImage(item, stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false);
  101. }
  102. else
  103. {
  104. var mimeType = "image/" + response.Format.ToString().ToLower();
  105. await _providerManager.SaveImage(item, response.Stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false);
  106. }
  107. result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
  108. }
  109. }
  110. }
  111. }
  112. catch (OperationCanceledException)
  113. {
  114. throw;
  115. }
  116. catch (Exception ex)
  117. {
  118. result.ErrorMessage = ex.Message;
  119. result.Status = ProviderRefreshStatus.CompletedWithErrors;
  120. _logger.ErrorException("Error in {0}", ex, provider.Name);
  121. }
  122. }
  123. /// <summary>
  124. /// Image types that are only one per item
  125. /// </summary>
  126. private readonly ImageType[] _singularImages =
  127. {
  128. ImageType.Primary,
  129. ImageType.Art,
  130. ImageType.Banner,
  131. ImageType.Box,
  132. ImageType.BoxRear,
  133. ImageType.Disc,
  134. ImageType.Logo,
  135. ImageType.Menu,
  136. ImageType.Thumb
  137. };
  138. /// <summary>
  139. /// Determines if an item already contains the given images
  140. /// </summary>
  141. /// <param name="item">The item.</param>
  142. /// <param name="images">The images.</param>
  143. /// <param name="savedOptions">The saved options.</param>
  144. /// <param name="backdropLimit">The backdrop limit.</param>
  145. /// <param name="screenshotLimit">The screenshot limit.</param>
  146. /// <returns><c>true</c> if the specified item contains images; otherwise, <c>false</c>.</returns>
  147. private bool ContainsImages(IHasImages item, List<ImageType> images, MetadataOptions savedOptions, int backdropLimit, int screenshotLimit)
  148. {
  149. if (_singularImages.Any(i => images.Contains(i) && !item.HasImage(i) && savedOptions.GetLimit(i) > 0))
  150. {
  151. return false;
  152. }
  153. if (images.Contains(ImageType.Backdrop) && item.GetImages(ImageType.Backdrop).Count() < backdropLimit)
  154. {
  155. return false;
  156. }
  157. if (images.Contains(ImageType.Screenshot) && item.GetImages(ImageType.Screenshot).Count() < backdropLimit)
  158. {
  159. return false;
  160. }
  161. return true;
  162. }
  163. /// <summary>
  164. /// Refreshes from provider.
  165. /// </summary>
  166. /// <param name="item">The item.</param>
  167. /// <param name="provider">The provider.</param>
  168. /// <param name="refreshOptions">The refresh options.</param>
  169. /// <param name="savedOptions">The saved options.</param>
  170. /// <param name="backdropLimit">The backdrop limit.</param>
  171. /// <param name="screenshotLimit">The screenshot limit.</param>
  172. /// <param name="result">The result.</param>
  173. /// <param name="cancellationToken">The cancellation token.</param>
  174. /// <returns>Task.</returns>
  175. private async Task RefreshFromProvider(IHasImages item, IRemoteImageProvider provider, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, int backdropLimit, int screenshotLimit, RefreshResult result, CancellationToken cancellationToken)
  176. {
  177. try
  178. {
  179. if (ContainsImages(item, provider.GetSupportedImages(item).ToList(), savedOptions, backdropLimit, screenshotLimit))
  180. {
  181. return;
  182. }
  183. _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
  184. var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery
  185. {
  186. ProviderName = provider.Name,
  187. IncludeAllLanguages = false,
  188. IncludeDisabledProviders = false,
  189. }, cancellationToken).ConfigureAwait(false);
  190. var list = images.ToList();
  191. int minWidth;
  192. foreach (var type in _singularImages)
  193. {
  194. if (savedOptions.IsEnabled(type) && !item.HasImage(type))
  195. {
  196. minWidth = savedOptions.GetMinWidth(type);
  197. await DownloadImage(item, provider, result, list, minWidth, type, cancellationToken).ConfigureAwait(false);
  198. }
  199. }
  200. minWidth = savedOptions.GetMinWidth(ImageType.Backdrop);
  201. await DownloadBackdrops(item, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
  202. var hasScreenshots = item as IHasScreenshots;
  203. if (hasScreenshots != null)
  204. {
  205. minWidth = savedOptions.GetMinWidth(ImageType.Screenshot);
  206. await DownloadBackdrops(item, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
  207. }
  208. }
  209. catch (OperationCanceledException)
  210. {
  211. throw;
  212. }
  213. catch (Exception ex)
  214. {
  215. result.ErrorMessage = ex.Message;
  216. result.Status = ProviderRefreshStatus.CompletedWithErrors;
  217. _logger.ErrorException("Error in {0}", ex, provider.Name);
  218. }
  219. }
  220. public bool MergeImages(IHasImages item, List<LocalImageInfo> images)
  221. {
  222. var changed = false;
  223. foreach (var type in _singularImages)
  224. {
  225. var image = images.FirstOrDefault(i => i.Type == type);
  226. if (image != null)
  227. {
  228. var currentImage = item.GetImageInfo(type, 0);
  229. if (currentImage == null)
  230. {
  231. item.SetImagePath(type, image.FileInfo);
  232. changed = true;
  233. }
  234. else if (!string.Equals(currentImage.Path, image.FileInfo.FullName,
  235. StringComparison.OrdinalIgnoreCase))
  236. {
  237. item.SetImagePath(type, image.FileInfo);
  238. changed = true;
  239. }
  240. else
  241. {
  242. currentImage.DateModified = _fileSystem.GetLastWriteTimeUtc(image.FileInfo);
  243. }
  244. }
  245. }
  246. if (UpdateMultiImages(item, images, ImageType.Backdrop))
  247. {
  248. changed = true;
  249. }
  250. var hasScreenshots = item as IHasScreenshots;
  251. if (hasScreenshots != null)
  252. {
  253. if (UpdateMultiImages(item, images, ImageType.Screenshot))
  254. {
  255. changed = true;
  256. }
  257. }
  258. return changed;
  259. }
  260. private bool UpdateMultiImages(IHasImages item, List<LocalImageInfo> images, ImageType type)
  261. {
  262. var changed = false;
  263. var backdrops = images.Where(i => i.Type == type).ToList();
  264. if (backdrops.Count > 0)
  265. {
  266. var foundImages = images.Where(i => i.Type == type)
  267. .Select(i => i.FileInfo)
  268. .ToList();
  269. if (foundImages.Count > 0)
  270. {
  271. if (item.AddImages(type, foundImages))
  272. {
  273. changed = true;
  274. }
  275. }
  276. }
  277. return changed;
  278. }
  279. private async Task DownloadImage(IHasImages item, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, int minWidth, ImageType type, CancellationToken cancellationToken)
  280. {
  281. foreach (var image in images.Where(i => i.Type == type))
  282. {
  283. if (image.Width.HasValue && image.Width.Value < minWidth)
  284. {
  285. continue;
  286. }
  287. var url = image.Url;
  288. try
  289. {
  290. var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
  291. await _providerManager.SaveImage(item, response.Content, response.ContentType, type, null, cancellationToken).ConfigureAwait(false);
  292. result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
  293. break;
  294. }
  295. catch (HttpException ex)
  296. {
  297. // Sometimes providers send back bad url's. Just move to the next image
  298. if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
  299. {
  300. continue;
  301. }
  302. break;
  303. }
  304. }
  305. }
  306. private async Task DownloadBackdrops(IHasImages item, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, int minWidth, CancellationToken cancellationToken)
  307. {
  308. foreach (var image in images.Where(i => i.Type == imageType))
  309. {
  310. if (item.GetImages(imageType).Count() >= limit)
  311. {
  312. break;
  313. }
  314. if (image.Width.HasValue && image.Width.Value < minWidth)
  315. {
  316. continue;
  317. }
  318. var url = image.Url;
  319. try
  320. {
  321. var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
  322. // If there's already an image of the same size, skip it
  323. if (response.ContentLength.HasValue)
  324. {
  325. try
  326. {
  327. if (item.GetImages(imageType).Any(i => new FileInfo(i.Path).Length == response.ContentLength.Value))
  328. {
  329. response.Content.Dispose();
  330. continue;
  331. }
  332. }
  333. catch (IOException ex)
  334. {
  335. _logger.ErrorException("Error examining images", ex);
  336. }
  337. }
  338. await _providerManager.SaveImage(item, response.Content, response.ContentType, imageType, null, cancellationToken).ConfigureAwait(false);
  339. result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
  340. }
  341. catch (HttpException ex)
  342. {
  343. // Sometimes providers send back bad url's. Just move onto the next image
  344. if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
  345. {
  346. continue;
  347. }
  348. break;
  349. }
  350. }
  351. }
  352. }
  353. }