TmdbClientManager.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using Jellyfin.Data.Enums;
  7. using MediaBrowser.Model.Dto;
  8. using MediaBrowser.Model.Entities;
  9. using MediaBrowser.Model.Providers;
  10. using Microsoft.Extensions.Caching.Memory;
  11. using TMDbLib.Client;
  12. using TMDbLib.Objects.Collections;
  13. using TMDbLib.Objects.Find;
  14. using TMDbLib.Objects.General;
  15. using TMDbLib.Objects.Movies;
  16. using TMDbLib.Objects.People;
  17. using TMDbLib.Objects.Search;
  18. using TMDbLib.Objects.TvShows;
  19. namespace MediaBrowser.Providers.Plugins.Tmdb
  20. {
  21. /// <summary>
  22. /// Manager class for abstracting the TMDb API client library.
  23. /// </summary>
  24. public class TmdbClientManager : IDisposable
  25. {
  26. private const int CacheDurationInHours = 1;
  27. private readonly IMemoryCache _memoryCache;
  28. private readonly TMDbClient _tmDbClient;
  29. /// <summary>
  30. /// Initializes a new instance of the <see cref="TmdbClientManager"/> class.
  31. /// </summary>
  32. /// <param name="memoryCache">An instance of <see cref="IMemoryCache"/>.</param>
  33. public TmdbClientManager(IMemoryCache memoryCache)
  34. {
  35. _memoryCache = memoryCache;
  36. var apiKey = Plugin.Instance.Configuration.TmdbApiKey;
  37. apiKey = string.IsNullOrEmpty(apiKey) ? TmdbUtils.ApiKey : apiKey;
  38. _tmDbClient = new TMDbClient(apiKey);
  39. // Not really interested in NotFoundException
  40. _tmDbClient.ThrowApiExceptions = false;
  41. }
  42. /// <summary>
  43. /// Gets a movie from the TMDb API based on its TMDb id.
  44. /// </summary>
  45. /// <param name="tmdbId">The movie's TMDb id.</param>
  46. /// <param name="language">The movie's language.</param>
  47. /// <param name="imageLanguages">A comma-separated list of image languages.</param>
  48. /// <param name="cancellationToken">The cancellation token.</param>
  49. /// <returns>The TMDb movie or null if not found.</returns>
  50. public async Task<Movie?> GetMovieAsync(int tmdbId, string? language, string? imageLanguages, CancellationToken cancellationToken)
  51. {
  52. var key = $"movie-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
  53. if (_memoryCache.TryGetValue(key, out Movie? movie))
  54. {
  55. return movie;
  56. }
  57. await EnsureClientConfigAsync().ConfigureAwait(false);
  58. var extraMethods = MovieMethods.Credits | MovieMethods.Releases | MovieMethods.Images | MovieMethods.Videos;
  59. if (!(Plugin.Instance?.Configuration.ExcludeTagsMovies).GetValueOrDefault())
  60. {
  61. extraMethods |= MovieMethods.Keywords;
  62. }
  63. movie = await _tmDbClient.GetMovieAsync(
  64. tmdbId,
  65. TmdbUtils.NormalizeLanguage(language),
  66. imageLanguages,
  67. extraMethods,
  68. cancellationToken).ConfigureAwait(false);
  69. if (movie is not null)
  70. {
  71. _memoryCache.Set(key, movie, TimeSpan.FromHours(CacheDurationInHours));
  72. }
  73. return movie;
  74. }
  75. /// <summary>
  76. /// Gets a collection from the TMDb API based on its TMDb id.
  77. /// </summary>
  78. /// <param name="tmdbId">The collection's TMDb id.</param>
  79. /// <param name="language">The collection's language.</param>
  80. /// <param name="imageLanguages">A comma-separated list of image languages.</param>
  81. /// <param name="cancellationToken">The cancellation token.</param>
  82. /// <returns>The TMDb collection or null if not found.</returns>
  83. public async Task<Collection?> GetCollectionAsync(int tmdbId, string? language, string? imageLanguages, CancellationToken cancellationToken)
  84. {
  85. var key = $"collection-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
  86. if (_memoryCache.TryGetValue(key, out Collection? collection))
  87. {
  88. return collection;
  89. }
  90. await EnsureClientConfigAsync().ConfigureAwait(false);
  91. collection = await _tmDbClient.GetCollectionAsync(
  92. tmdbId,
  93. TmdbUtils.NormalizeLanguage(language),
  94. imageLanguages,
  95. CollectionMethods.Images,
  96. cancellationToken).ConfigureAwait(false);
  97. if (collection is not null)
  98. {
  99. _memoryCache.Set(key, collection, TimeSpan.FromHours(CacheDurationInHours));
  100. }
  101. return collection;
  102. }
  103. /// <summary>
  104. /// Gets a tv show from the TMDb API based on its TMDb id.
  105. /// </summary>
  106. /// <param name="tmdbId">The tv show's TMDb id.</param>
  107. /// <param name="language">The tv show's language.</param>
  108. /// <param name="imageLanguages">A comma-separated list of image languages.</param>
  109. /// <param name="cancellationToken">The cancellation token.</param>
  110. /// <returns>The TMDb tv show information or null if not found.</returns>
  111. public async Task<TvShow?> GetSeriesAsync(int tmdbId, string? language, string? imageLanguages, CancellationToken cancellationToken)
  112. {
  113. var key = $"series-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
  114. if (_memoryCache.TryGetValue(key, out TvShow? series))
  115. {
  116. return series;
  117. }
  118. await EnsureClientConfigAsync().ConfigureAwait(false);
  119. var extraMethods = TvShowMethods.Credits | TvShowMethods.Images | TvShowMethods.ExternalIds | TvShowMethods.Videos | TvShowMethods.ContentRatings | TvShowMethods.EpisodeGroups;
  120. if (!(Plugin.Instance?.Configuration.ExcludeTagsSeries).GetValueOrDefault())
  121. {
  122. extraMethods |= TvShowMethods.Keywords;
  123. }
  124. series = await _tmDbClient.GetTvShowAsync(
  125. tmdbId,
  126. language: TmdbUtils.NormalizeLanguage(language),
  127. includeImageLanguage: imageLanguages,
  128. extraMethods: extraMethods,
  129. cancellationToken: cancellationToken).ConfigureAwait(false);
  130. if (series is not null)
  131. {
  132. _memoryCache.Set(key, series, TimeSpan.FromHours(CacheDurationInHours));
  133. }
  134. return series;
  135. }
  136. /// <summary>
  137. /// Gets a tv show episode group from the TMDb API based on the show id and the display order.
  138. /// </summary>
  139. /// <param name="tvShowId">The tv show's TMDb id.</param>
  140. /// <param name="displayOrder">The display order.</param>
  141. /// <param name="language">The tv show's language.</param>
  142. /// <param name="imageLanguages">A comma-separated list of image languages.</param>
  143. /// <param name="cancellationToken">The cancellation token.</param>
  144. /// <returns>The TMDb tv show episode group information or null if not found.</returns>
  145. private async Task<TvGroupCollection?> GetSeriesGroupAsync(int tvShowId, string displayOrder, string? language, string? imageLanguages, CancellationToken cancellationToken)
  146. {
  147. TvGroupType? groupType =
  148. string.Equals(displayOrder, "originalAirDate", StringComparison.Ordinal) ? TvGroupType.OriginalAirDate :
  149. string.Equals(displayOrder, "absolute", StringComparison.Ordinal) ? TvGroupType.Absolute :
  150. string.Equals(displayOrder, "dvd", StringComparison.Ordinal) ? TvGroupType.DVD :
  151. string.Equals(displayOrder, "digital", StringComparison.Ordinal) ? TvGroupType.Digital :
  152. string.Equals(displayOrder, "storyArc", StringComparison.Ordinal) ? TvGroupType.StoryArc :
  153. string.Equals(displayOrder, "production", StringComparison.Ordinal) ? TvGroupType.Production :
  154. string.Equals(displayOrder, "tv", StringComparison.Ordinal) ? TvGroupType.TV :
  155. null;
  156. if (groupType is null)
  157. {
  158. return null;
  159. }
  160. var key = $"group-{tvShowId.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}";
  161. if (_memoryCache.TryGetValue(key, out TvGroupCollection? group))
  162. {
  163. return group;
  164. }
  165. await EnsureClientConfigAsync().ConfigureAwait(false);
  166. var series = await GetSeriesAsync(tvShowId, language, imageLanguages, cancellationToken).ConfigureAwait(false);
  167. var episodeGroupId = series?.EpisodeGroups.Results.Find(g => g.Type == groupType)?.Id;
  168. if (episodeGroupId is null)
  169. {
  170. return null;
  171. }
  172. group = await _tmDbClient.GetTvEpisodeGroupsAsync(
  173. episodeGroupId,
  174. language: TmdbUtils.NormalizeLanguage(language),
  175. cancellationToken: cancellationToken).ConfigureAwait(false);
  176. if (group is not null)
  177. {
  178. _memoryCache.Set(key, group, TimeSpan.FromHours(CacheDurationInHours));
  179. }
  180. return group;
  181. }
  182. /// <summary>
  183. /// Gets a tv season from the TMDb API based on the tv show's TMDb id.
  184. /// </summary>
  185. /// <param name="tvShowId">The tv season's TMDb id.</param>
  186. /// <param name="seasonNumber">The season number.</param>
  187. /// <param name="language">The tv season's language.</param>
  188. /// <param name="imageLanguages">A comma-separated list of image languages.</param>
  189. /// <param name="cancellationToken">The cancellation token.</param>
  190. /// <returns>The TMDb tv season information or null if not found.</returns>
  191. public async Task<TvSeason?> GetSeasonAsync(int tvShowId, int seasonNumber, string? language, string? imageLanguages, CancellationToken cancellationToken)
  192. {
  193. var key = $"season-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}-{language}";
  194. if (_memoryCache.TryGetValue(key, out TvSeason? season))
  195. {
  196. return season;
  197. }
  198. await EnsureClientConfigAsync().ConfigureAwait(false);
  199. season = await _tmDbClient.GetTvSeasonAsync(
  200. tvShowId,
  201. seasonNumber,
  202. language: TmdbUtils.NormalizeLanguage(language),
  203. includeImageLanguage: imageLanguages,
  204. extraMethods: TvSeasonMethods.Credits | TvSeasonMethods.Images | TvSeasonMethods.ExternalIds | TvSeasonMethods.Videos,
  205. cancellationToken: cancellationToken).ConfigureAwait(false);
  206. if (season is not null)
  207. {
  208. _memoryCache.Set(key, season, TimeSpan.FromHours(CacheDurationInHours));
  209. }
  210. return season;
  211. }
  212. /// <summary>
  213. /// Gets a movie from the TMDb API based on the tv show's TMDb id.
  214. /// </summary>
  215. /// <param name="tvShowId">The tv show's TMDb id.</param>
  216. /// <param name="seasonNumber">The season number.</param>
  217. /// <param name="episodeNumber">The episode number.</param>
  218. /// <param name="displayOrder">The display order.</param>
  219. /// <param name="language">The episode's language.</param>
  220. /// <param name="imageLanguages">A comma-separated list of image languages.</param>
  221. /// <param name="cancellationToken">The cancellation token.</param>
  222. /// <returns>The TMDb tv episode information or null if not found.</returns>
  223. public async Task<TvEpisode?> GetEpisodeAsync(int tvShowId, int seasonNumber, int episodeNumber, string displayOrder, string? language, string? imageLanguages, CancellationToken cancellationToken)
  224. {
  225. var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}";
  226. if (_memoryCache.TryGetValue(key, out TvEpisode? episode))
  227. {
  228. return episode;
  229. }
  230. await EnsureClientConfigAsync().ConfigureAwait(false);
  231. var group = await GetSeriesGroupAsync(tvShowId, displayOrder, language, imageLanguages, cancellationToken).ConfigureAwait(false);
  232. if (group is not null)
  233. {
  234. var season = group.Groups.Find(s => s.Order == seasonNumber);
  235. // Episode order starts at 0
  236. var ep = season?.Episodes.Find(e => e.Order == episodeNumber - 1);
  237. if (ep is not null)
  238. {
  239. seasonNumber = ep.SeasonNumber;
  240. episodeNumber = ep.EpisodeNumber;
  241. }
  242. }
  243. episode = await _tmDbClient.GetTvEpisodeAsync(
  244. tvShowId,
  245. seasonNumber,
  246. episodeNumber,
  247. language: TmdbUtils.NormalizeLanguage(language),
  248. includeImageLanguage: imageLanguages,
  249. extraMethods: TvEpisodeMethods.Credits | TvEpisodeMethods.Images | TvEpisodeMethods.ExternalIds | TvEpisodeMethods.Videos,
  250. cancellationToken: cancellationToken).ConfigureAwait(false);
  251. if (episode is not null)
  252. {
  253. _memoryCache.Set(key, episode, TimeSpan.FromHours(CacheDurationInHours));
  254. }
  255. return episode;
  256. }
  257. /// <summary>
  258. /// Gets a person eg. cast or crew member from the TMDb API based on its TMDb id.
  259. /// </summary>
  260. /// <param name="personTmdbId">The person's TMDb id.</param>
  261. /// <param name="language">The episode's language.</param>
  262. /// <param name="cancellationToken">The cancellation token.</param>
  263. /// <returns>The TMDb person information or null if not found.</returns>
  264. public async Task<Person?> GetPersonAsync(int personTmdbId, string language, CancellationToken cancellationToken)
  265. {
  266. var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
  267. if (_memoryCache.TryGetValue(key, out Person? person))
  268. {
  269. return person;
  270. }
  271. await EnsureClientConfigAsync().ConfigureAwait(false);
  272. person = await _tmDbClient.GetPersonAsync(
  273. personTmdbId,
  274. TmdbUtils.NormalizeLanguage(language),
  275. PersonMethods.TvCredits | PersonMethods.MovieCredits | PersonMethods.Images | PersonMethods.ExternalIds,
  276. cancellationToken).ConfigureAwait(false);
  277. if (person is not null)
  278. {
  279. _memoryCache.Set(key, person, TimeSpan.FromHours(CacheDurationInHours));
  280. }
  281. return person;
  282. }
  283. /// <summary>
  284. /// Gets an item from the TMDb API based on its id from an external service eg. IMDb id, TvDb id.
  285. /// </summary>
  286. /// <param name="externalId">The item's external id.</param>
  287. /// <param name="source">The source of the id eg. IMDb.</param>
  288. /// <param name="language">The item's language.</param>
  289. /// <param name="cancellationToken">The cancellation token.</param>
  290. /// <returns>The TMDb item or null if not found.</returns>
  291. public async Task<FindContainer?> FindByExternalIdAsync(
  292. string externalId,
  293. FindExternalSource source,
  294. string language,
  295. CancellationToken cancellationToken)
  296. {
  297. var key = $"find-{source.ToString()}-{externalId.ToString(CultureInfo.InvariantCulture)}-{language}";
  298. if (_memoryCache.TryGetValue(key, out FindContainer? result))
  299. {
  300. return result;
  301. }
  302. await EnsureClientConfigAsync().ConfigureAwait(false);
  303. result = await _tmDbClient.FindAsync(
  304. source,
  305. externalId,
  306. TmdbUtils.NormalizeLanguage(language),
  307. cancellationToken).ConfigureAwait(false);
  308. if (result is not null)
  309. {
  310. _memoryCache.Set(key, result, TimeSpan.FromHours(CacheDurationInHours));
  311. }
  312. return result;
  313. }
  314. /// <summary>
  315. /// Searches for a tv show using the TMDb API based on its name.
  316. /// </summary>
  317. /// <param name="name">The name of the tv show.</param>
  318. /// <param name="language">The tv show's language.</param>
  319. /// <param name="year">The year the tv show first aired.</param>
  320. /// <param name="cancellationToken">The cancellation token.</param>
  321. /// <returns>The TMDb tv show information.</returns>
  322. public async Task<IReadOnlyList<SearchTv>> SearchSeriesAsync(string name, string language, int year = 0, CancellationToken cancellationToken = default)
  323. {
  324. var key = $"searchseries-{name}-{language}";
  325. if (_memoryCache.TryGetValue(key, out SearchContainer<SearchTv>? series) && series is not null)
  326. {
  327. return series.Results;
  328. }
  329. await EnsureClientConfigAsync().ConfigureAwait(false);
  330. var searchResults = await _tmDbClient
  331. .SearchTvShowAsync(name, TmdbUtils.NormalizeLanguage(language), includeAdult: Plugin.Instance.Configuration.IncludeAdult, firstAirDateYear: year, cancellationToken: cancellationToken)
  332. .ConfigureAwait(false);
  333. if (searchResults.Results.Count > 0)
  334. {
  335. _memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours));
  336. }
  337. return searchResults.Results;
  338. }
  339. /// <summary>
  340. /// Searches for a person based on their name using the TMDb API.
  341. /// </summary>
  342. /// <param name="name">The name of the person.</param>
  343. /// <param name="cancellationToken">The cancellation token.</param>
  344. /// <returns>The TMDb person information.</returns>
  345. public async Task<IReadOnlyList<SearchPerson>> SearchPersonAsync(string name, CancellationToken cancellationToken)
  346. {
  347. var key = $"searchperson-{name}";
  348. if (_memoryCache.TryGetValue(key, out SearchContainer<SearchPerson>? person) && person is not null)
  349. {
  350. return person.Results;
  351. }
  352. await EnsureClientConfigAsync().ConfigureAwait(false);
  353. var searchResults = await _tmDbClient
  354. .SearchPersonAsync(name, includeAdult: Plugin.Instance.Configuration.IncludeAdult, cancellationToken: cancellationToken)
  355. .ConfigureAwait(false);
  356. if (searchResults.Results.Count > 0)
  357. {
  358. _memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours));
  359. }
  360. return searchResults.Results;
  361. }
  362. /// <summary>
  363. /// Searches for a movie based on its name using the TMDb API.
  364. /// </summary>
  365. /// <param name="name">The name of the movie.</param>
  366. /// <param name="language">The movie's language.</param>
  367. /// <param name="cancellationToken">The cancellation token.</param>
  368. /// <returns>The TMDb movie information.</returns>
  369. public Task<IReadOnlyList<SearchMovie>> SearchMovieAsync(string name, string language, CancellationToken cancellationToken)
  370. {
  371. return SearchMovieAsync(name, 0, language, cancellationToken);
  372. }
  373. /// <summary>
  374. /// Searches for a movie based on its name using the TMDb API.
  375. /// </summary>
  376. /// <param name="name">The name of the movie.</param>
  377. /// <param name="year">The release year of the movie.</param>
  378. /// <param name="language">The movie's language.</param>
  379. /// <param name="cancellationToken">The cancellation token.</param>
  380. /// <returns>The TMDb movie information.</returns>
  381. public async Task<IReadOnlyList<SearchMovie>> SearchMovieAsync(string name, int year, string language, CancellationToken cancellationToken)
  382. {
  383. var key = $"moviesearch-{name}-{year.ToString(CultureInfo.InvariantCulture)}-{language}";
  384. if (_memoryCache.TryGetValue(key, out SearchContainer<SearchMovie>? movies) && movies is not null)
  385. {
  386. return movies.Results;
  387. }
  388. await EnsureClientConfigAsync().ConfigureAwait(false);
  389. var searchResults = await _tmDbClient
  390. .SearchMovieAsync(name, TmdbUtils.NormalizeLanguage(language), includeAdult: Plugin.Instance.Configuration.IncludeAdult, year: year, cancellationToken: cancellationToken)
  391. .ConfigureAwait(false);
  392. if (searchResults.Results.Count > 0)
  393. {
  394. _memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours));
  395. }
  396. return searchResults.Results;
  397. }
  398. /// <summary>
  399. /// Searches for a collection based on its name using the TMDb API.
  400. /// </summary>
  401. /// <param name="name">The name of the collection.</param>
  402. /// <param name="language">The collection's language.</param>
  403. /// <param name="cancellationToken">The cancellation token.</param>
  404. /// <returns>The TMDb collection information.</returns>
  405. public async Task<IReadOnlyList<SearchCollection>> SearchCollectionAsync(string name, string language, CancellationToken cancellationToken)
  406. {
  407. var key = $"collectionsearch-{name}-{language}";
  408. if (_memoryCache.TryGetValue(key, out SearchContainer<SearchCollection>? collections) && collections is not null)
  409. {
  410. return collections.Results;
  411. }
  412. await EnsureClientConfigAsync().ConfigureAwait(false);
  413. var searchResults = await _tmDbClient
  414. .SearchCollectionAsync(name, TmdbUtils.NormalizeLanguage(language), cancellationToken: cancellationToken)
  415. .ConfigureAwait(false);
  416. if (searchResults.Results.Count > 0)
  417. {
  418. _memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours));
  419. }
  420. return searchResults.Results;
  421. }
  422. /// <summary>
  423. /// Handles bad path checking and builds the absolute url.
  424. /// </summary>
  425. /// <param name="size">The image size to fetch.</param>
  426. /// <param name="path">The relative URL of the image.</param>
  427. /// <returns>The absolute URL.</returns>
  428. private string? GetUrl(string? size, string path)
  429. {
  430. if (string.IsNullOrEmpty(path))
  431. {
  432. return null;
  433. }
  434. return _tmDbClient.GetImageUrl(size, path, true).ToString();
  435. }
  436. /// <summary>
  437. /// Gets the absolute URL of the poster.
  438. /// </summary>
  439. /// <param name="posterPath">The relative URL of the poster.</param>
  440. /// <returns>The absolute URL.</returns>
  441. public string? GetPosterUrl(string posterPath)
  442. {
  443. return GetUrl(Plugin.Instance.Configuration.PosterSize, posterPath);
  444. }
  445. /// <summary>
  446. /// Gets the absolute URL of the profile image.
  447. /// </summary>
  448. /// <param name="actorProfilePath">The relative URL of the profile image.</param>
  449. /// <returns>The absolute URL.</returns>
  450. public string? GetProfileUrl(string actorProfilePath)
  451. {
  452. return GetUrl(Plugin.Instance.Configuration.ProfileSize, actorProfilePath);
  453. }
  454. /// <summary>
  455. /// Converts poster <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s.
  456. /// </summary>
  457. /// <param name="images">The input images.</param>
  458. /// <param name="requestLanguage">The requested language.</param>
  459. /// <returns>The remote images.</returns>
  460. public IEnumerable<RemoteImageInfo> ConvertPostersToRemoteImageInfo(IReadOnlyList<ImageData> images, string requestLanguage)
  461. => ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.PosterSize, ImageType.Primary, requestLanguage);
  462. /// <summary>
  463. /// Converts backdrop <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s.
  464. /// </summary>
  465. /// <param name="images">The input images.</param>
  466. /// <param name="requestLanguage">The requested language.</param>
  467. /// <returns>The remote images.</returns>
  468. public IEnumerable<RemoteImageInfo> ConvertBackdropsToRemoteImageInfo(IReadOnlyList<ImageData> images, string requestLanguage)
  469. => ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.BackdropSize, ImageType.Backdrop, requestLanguage);
  470. /// <summary>
  471. /// Converts logo <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s.
  472. /// </summary>
  473. /// <param name="images">The input images.</param>
  474. /// <param name="requestLanguage">The requested language.</param>
  475. /// <returns>The remote images.</returns>
  476. public IEnumerable<RemoteImageInfo> ConvertLogosToRemoteImageInfo(IReadOnlyList<ImageData> images, string requestLanguage)
  477. => ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.LogoSize, ImageType.Logo, requestLanguage);
  478. /// <summary>
  479. /// Converts profile <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s.
  480. /// </summary>
  481. /// <param name="images">The input images.</param>
  482. /// <param name="requestLanguage">The requested language.</param>
  483. /// <returns>The remote images.</returns>
  484. public IEnumerable<RemoteImageInfo> ConvertProfilesToRemoteImageInfo(IReadOnlyList<ImageData> images, string requestLanguage)
  485. => ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.ProfileSize, ImageType.Primary, requestLanguage);
  486. /// <summary>
  487. /// Converts still <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s.
  488. /// </summary>
  489. /// <param name="images">The input images.</param>
  490. /// <param name="requestLanguage">The requested language.</param>
  491. /// <returns>The remote images.</returns>
  492. public IEnumerable<RemoteImageInfo> ConvertStillsToRemoteImageInfo(IReadOnlyList<ImageData> images, string requestLanguage)
  493. => ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.StillSize, ImageType.Primary, requestLanguage);
  494. /// <summary>
  495. /// Converts <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s.
  496. /// </summary>
  497. /// <param name="images">The input images.</param>
  498. /// <param name="size">The size of the image to fetch.</param>
  499. /// <param name="type">The type of the image.</param>
  500. /// <param name="requestLanguage">The requested language.</param>
  501. /// <returns>The remote images.</returns>
  502. private IEnumerable<RemoteImageInfo> ConvertToRemoteImageInfo(IReadOnlyList<ImageData> images, string? size, ImageType type, string requestLanguage)
  503. {
  504. // sizes provided are for original resolution, don't store them when downloading scaled images
  505. var scaleImage = !string.Equals(size, "original", StringComparison.OrdinalIgnoreCase);
  506. for (var i = 0; i < images.Count; i++)
  507. {
  508. var image = images[i];
  509. yield return new RemoteImageInfo
  510. {
  511. Url = GetUrl(size, image.FilePath),
  512. CommunityRating = image.VoteAverage,
  513. VoteCount = image.VoteCount,
  514. Width = scaleImage ? null : image.Width,
  515. Height = scaleImage ? null : image.Height,
  516. Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, requestLanguage),
  517. ProviderName = TmdbUtils.ProviderName,
  518. Type = type,
  519. RatingType = RatingType.Score
  520. };
  521. }
  522. }
  523. private async Task EnsureClientConfigAsync()
  524. {
  525. if (!_tmDbClient.HasConfig)
  526. {
  527. var config = await _tmDbClient.GetConfigAsync().ConfigureAwait(false);
  528. ValidatePreferences(config);
  529. }
  530. }
  531. private static void ValidatePreferences(TMDbConfig config)
  532. {
  533. var imageConfig = config.Images;
  534. var pluginConfig = Plugin.Instance.Configuration;
  535. if (!imageConfig.PosterSizes.Contains(pluginConfig.PosterSize))
  536. {
  537. pluginConfig.PosterSize = imageConfig.PosterSizes[^1];
  538. }
  539. if (!imageConfig.BackdropSizes.Contains(pluginConfig.BackdropSize))
  540. {
  541. pluginConfig.BackdropSize = imageConfig.BackdropSizes[^1];
  542. }
  543. if (!imageConfig.LogoSizes.Contains(pluginConfig.LogoSize))
  544. {
  545. pluginConfig.LogoSize = imageConfig.LogoSizes[^1];
  546. }
  547. if (!imageConfig.ProfileSizes.Contains(pluginConfig.ProfileSize))
  548. {
  549. pluginConfig.ProfileSize = imageConfig.ProfileSizes[^1];
  550. }
  551. if (!imageConfig.StillSizes.Contains(pluginConfig.StillSize))
  552. {
  553. pluginConfig.StillSize = imageConfig.StillSizes[^1];
  554. }
  555. }
  556. /// <summary>
  557. /// Gets the <see cref="TMDbClient"/> configuration.
  558. /// </summary>
  559. /// <returns>The configuration.</returns>
  560. public async Task<TMDbConfig> GetClientConfiguration()
  561. {
  562. await EnsureClientConfigAsync().ConfigureAwait(false);
  563. return _tmDbClient.Config;
  564. }
  565. /// <inheritdoc />
  566. public void Dispose()
  567. {
  568. Dispose(true);
  569. GC.SuppressFinalize(this);
  570. }
  571. /// <summary>
  572. /// Releases unmanaged and - optionally - managed resources.
  573. /// </summary>
  574. /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  575. protected virtual void Dispose(bool disposing)
  576. {
  577. if (disposing)
  578. {
  579. _memoryCache?.Dispose();
  580. _tmDbClient?.Dispose();
  581. }
  582. }
  583. }
  584. }