StudiosImageProvider.cs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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 [ImageType.Thumb];
  51. }
  52. /// <inheritdoc />
  53. public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
  54. {
  55. var thumbsPath = Path.Combine(_config.ApplicationPaths.CachePath, "imagesbyname", "remotestudiothumbs.txt");
  56. await EnsureThumbsList(thumbsPath, cancellationToken).ConfigureAwait(false);
  57. cancellationToken.ThrowIfCancellationRequested();
  58. var imageInfo = GetImage(item, thumbsPath, ImageType.Thumb, "thumb");
  59. if (imageInfo is null)
  60. {
  61. return [];
  62. }
  63. return [imageInfo];
  64. }
  65. private RemoteImageInfo GetImage(BaseItem item, string filename, ImageType type, string remoteFilename)
  66. {
  67. var list = GetAvailableImages(filename);
  68. var match = FindMatch(item, list);
  69. if (!string.IsNullOrEmpty(match))
  70. {
  71. var url = GetUrl(match, remoteFilename);
  72. return new RemoteImageInfo
  73. {
  74. ProviderName = Name,
  75. Type = type,
  76. Url = url
  77. };
  78. }
  79. return null;
  80. }
  81. private string GetUrl(string image, string filename)
  82. {
  83. return string.Format(CultureInfo.InvariantCulture, "{0}/images/{1}/{2}.jpg", GetRepositoryUrl(), image, filename);
  84. }
  85. private async Task EnsureThumbsList(string file, CancellationToken cancellationToken)
  86. {
  87. string url = string.Format(CultureInfo.InvariantCulture, "{0}/thumbs.txt", GetRepositoryUrl());
  88. await EnsureList(url, file, _fileSystem, cancellationToken).ConfigureAwait(false);
  89. }
  90. /// <inheritdoc />
  91. public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
  92. {
  93. var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
  94. return httpClient.GetAsync(url, cancellationToken);
  95. }
  96. /// <summary>
  97. /// Ensures the existence of a file listing.
  98. /// </summary>
  99. /// <param name="url">The URL.</param>
  100. /// <param name="file">The file.</param>
  101. /// <param name="fileSystem">The file system.</param>
  102. /// <param name="cancellationToken">The cancellation token.</param>
  103. /// <returns>A Task to ensure existence of a file listing.</returns>
  104. public async Task EnsureList(string url, string file, IFileSystem fileSystem, CancellationToken cancellationToken)
  105. {
  106. var fileInfo = fileSystem.GetFileInfo(file);
  107. if (!fileInfo.Exists || (DateTime.UtcNow - fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays > 1)
  108. {
  109. var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
  110. Directory.CreateDirectory(Path.GetDirectoryName(file));
  111. var response = await httpClient.GetStreamAsync(url, cancellationToken).ConfigureAwait(false);
  112. await using (response.ConfigureAwait(false))
  113. {
  114. var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
  115. await using (fileStream.ConfigureAwait(false))
  116. {
  117. await response.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
  118. }
  119. }
  120. }
  121. }
  122. /// <summary>
  123. /// Get matching image for an item.
  124. /// </summary>
  125. /// <param name="item">The <see cref="BaseItem"/>.</param>
  126. /// <param name="images">The enumerable of image strings.</param>
  127. /// <returns>The matching image string.</returns>
  128. public string FindMatch(BaseItem item, IEnumerable<string> images)
  129. {
  130. var name = GetComparableName(item.Name);
  131. return images.FirstOrDefault(i => string.Equals(name, GetComparableName(i), StringComparison.OrdinalIgnoreCase));
  132. }
  133. private string GetComparableName(string name)
  134. {
  135. return name.Replace(" ", string.Empty, StringComparison.Ordinal)
  136. .Replace(".", string.Empty, StringComparison.Ordinal)
  137. .Replace("&", string.Empty, StringComparison.Ordinal)
  138. .Replace("!", string.Empty, StringComparison.Ordinal)
  139. .Replace(",", string.Empty, StringComparison.Ordinal)
  140. .Replace("/", string.Empty, StringComparison.Ordinal);
  141. }
  142. /// <summary>
  143. /// Get available image strings for a file.
  144. /// </summary>
  145. /// <param name="file">The file.</param>
  146. /// <returns>All images strings of a file.</returns>
  147. public IEnumerable<string> GetAvailableImages(string file)
  148. {
  149. using var fileStream = File.OpenRead(file);
  150. using var reader = new StreamReader(fileStream);
  151. foreach (var line in reader.ReadAllLines())
  152. {
  153. if (!string.IsNullOrWhiteSpace(line))
  154. {
  155. yield return line;
  156. }
  157. }
  158. }
  159. private string GetRepositoryUrl()
  160. => Plugin.Instance.Configuration.RepositoryUrl;
  161. }
  162. }