StudiosImageProvider.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. #nullable disable
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Globalization;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Net.Http;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. using Jellyfin.Extensions;
  11. using MediaBrowser.Common.Net;
  12. using MediaBrowser.Controller.Configuration;
  13. using MediaBrowser.Controller.Entities;
  14. using MediaBrowser.Controller.Providers;
  15. using MediaBrowser.Model.Entities;
  16. using MediaBrowser.Model.IO;
  17. using MediaBrowser.Model.Providers;
  18. namespace MediaBrowser.Providers.Plugins.StudioImages
  19. {
  20. /// <summary>
  21. /// Studio image provider.
  22. /// </summary>
  23. public class StudiosImageProvider : IRemoteImageProvider
  24. {
  25. private readonly IServerConfigurationManager _config;
  26. private readonly IHttpClientFactory _httpClientFactory;
  27. private readonly IFileSystem _fileSystem;
  28. /// <summary>
  29. /// Initializes a new instance of the <see cref="StudiosImageProvider"/> class.
  30. /// </summary>
  31. /// <param name="config">The <see cref="IServerConfigurationManager"/>.</param>
  32. /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
  33. /// <param name="fileSystem">The <see cref="IFileSystem"/>.</param>
  34. public StudiosImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IFileSystem fileSystem)
  35. {
  36. _config = config;
  37. _httpClientFactory = httpClientFactory;
  38. _fileSystem = fileSystem;
  39. }
  40. /// <inheritdoc />
  41. public string Name => "Artwork Repository";
  42. /// <inheritdoc />
  43. public bool Supports(BaseItem item)
  44. {
  45. return item is Studio;
  46. }
  47. /// <inheritdoc />
  48. public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
  49. {
  50. return new ImageType[]
  51. {
  52. ImageType.Thumb
  53. };
  54. }
  55. /// <inheritdoc />
  56. public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
  57. {
  58. var thumbsPath = Path.Combine(_config.ApplicationPaths.CachePath, "imagesbyname", "remotestudiothumbs.txt");
  59. await EnsureThumbsList(thumbsPath, cancellationToken).ConfigureAwait(false);
  60. cancellationToken.ThrowIfCancellationRequested();
  61. var imageInfo = GetImage(item, thumbsPath, ImageType.Thumb, "thumb");
  62. if (imageInfo is null)
  63. {
  64. return Enumerable.Empty<RemoteImageInfo>();
  65. }
  66. return new RemoteImageInfo[]
  67. {
  68. imageInfo
  69. };
  70. }
  71. private RemoteImageInfo GetImage(BaseItem item, string filename, ImageType type, string remoteFilename)
  72. {
  73. var list = GetAvailableImages(filename);
  74. var match = FindMatch(item, list);
  75. if (!string.IsNullOrEmpty(match))
  76. {
  77. var url = GetUrl(match, remoteFilename);
  78. return new RemoteImageInfo
  79. {
  80. ProviderName = Name,
  81. Type = type,
  82. Url = url
  83. };
  84. }
  85. return null;
  86. }
  87. private string GetUrl(string image, string filename)
  88. {
  89. return string.Format(CultureInfo.InvariantCulture, "{0}/images/{1}/{2}.jpg", GetRepositoryUrl(), image, filename);
  90. }
  91. private Task EnsureThumbsList(string file, CancellationToken cancellationToken)
  92. {
  93. string url = string.Format(CultureInfo.InvariantCulture, "{0}/thumbs.txt", GetRepositoryUrl());
  94. return EnsureList(url, file, _fileSystem, cancellationToken);
  95. }
  96. /// <inheritdoc />
  97. public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
  98. {
  99. var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
  100. return httpClient.GetAsync(url, cancellationToken);
  101. }
  102. /// <summary>
  103. /// Ensures the existence of a file listing.
  104. /// </summary>
  105. /// <param name="url">The URL.</param>
  106. /// <param name="file">The file.</param>
  107. /// <param name="fileSystem">The file system.</param>
  108. /// <param name="cancellationToken">The cancellation token.</param>
  109. /// <returns>A Task to ensure existence of a file listing.</returns>
  110. public async Task EnsureList(string url, string file, IFileSystem fileSystem, CancellationToken cancellationToken)
  111. {
  112. var fileInfo = fileSystem.GetFileInfo(file);
  113. if (!fileInfo.Exists || (DateTime.UtcNow - fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays > 1)
  114. {
  115. var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
  116. Directory.CreateDirectory(Path.GetDirectoryName(file));
  117. var response = await httpClient.GetStreamAsync(url, cancellationToken).ConfigureAwait(false);
  118. await using (response.ConfigureAwait(false))
  119. {
  120. var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
  121. await using (fileStream.ConfigureAwait(false))
  122. {
  123. await response.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
  124. }
  125. }
  126. }
  127. }
  128. /// <summary>
  129. /// Get matching image for an item.
  130. /// </summary>
  131. /// <param name="item">The <see cref="BaseItem"/>.</param>
  132. /// <param name="images">The enumerable of image strings.</param>
  133. /// <returns>The matching image string.</returns>
  134. public string FindMatch(BaseItem item, IEnumerable<string> images)
  135. {
  136. var name = GetComparableName(item.Name);
  137. return images.FirstOrDefault(i => string.Equals(name, GetComparableName(i), StringComparison.OrdinalIgnoreCase));
  138. }
  139. private string GetComparableName(string name)
  140. {
  141. return name.Replace(" ", string.Empty, StringComparison.Ordinal)
  142. .Replace(".", string.Empty, StringComparison.Ordinal)
  143. .Replace("&", string.Empty, StringComparison.Ordinal)
  144. .Replace("!", string.Empty, StringComparison.Ordinal)
  145. .Replace(",", string.Empty, StringComparison.Ordinal)
  146. .Replace("/", string.Empty, StringComparison.Ordinal);
  147. }
  148. /// <summary>
  149. /// Get available image strings for a file.
  150. /// </summary>
  151. /// <param name="file">The file.</param>
  152. /// <returns>All images strings of a file.</returns>
  153. public IEnumerable<string> GetAvailableImages(string file)
  154. {
  155. using var fileStream = File.OpenRead(file);
  156. using var reader = new StreamReader(fileStream);
  157. foreach (var line in reader.ReadAllLines())
  158. {
  159. if (!string.IsNullOrWhiteSpace(line))
  160. {
  161. yield return line;
  162. }
  163. }
  164. }
  165. private string GetRepositoryUrl()
  166. => Plugin.Instance.Configuration.RepositoryUrl;
  167. }
  168. }