TvdbEpisodeProvider.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading;
  4. using System.Threading.Tasks;
  5. using MediaBrowser.Common.Net;
  6. using MediaBrowser.Controller.Entities;
  7. using MediaBrowser.Controller.Entities.TV;
  8. using MediaBrowser.Controller.Providers;
  9. using MediaBrowser.Model.Entities;
  10. using MediaBrowser.Model.Providers;
  11. using Microsoft.Extensions.Logging;
  12. using TvDbSharper;
  13. using TvDbSharper.Dto;
  14. namespace MediaBrowser.Providers.TV.TheTVDB
  15. {
  16. /// <summary>
  17. /// Class RemoteEpisodeProvider
  18. /// </summary>
  19. public class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasOrder
  20. {
  21. private readonly IHttpClient _httpClient;
  22. private readonly ILogger _logger;
  23. private readonly TvDbClientManager _tvDbClientManager;
  24. public TvdbEpisodeProvider(IHttpClient httpClient, ILogger<TvdbEpisodeProvider> logger, TvDbClientManager tvDbClientManager)
  25. {
  26. _httpClient = httpClient;
  27. _logger = logger;
  28. _tvDbClientManager = tvDbClientManager;
  29. }
  30. public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
  31. {
  32. var list = new List<RemoteSearchResult>();
  33. // The search query must either provide an episode number or date
  34. if (!searchInfo.IndexNumber.HasValue || !searchInfo.PremiereDate.HasValue)
  35. {
  36. return list;
  37. }
  38. if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds))
  39. {
  40. try
  41. {
  42. var episodeTvdbId = searchInfo.GetProviderId(MetadataProviders.Tvdb);
  43. if (string.IsNullOrEmpty(episodeTvdbId))
  44. {
  45. searchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(),
  46. out var seriesTvdbId);
  47. episodeTvdbId = await _tvDbClientManager
  48. .GetEpisodeTvdbId(searchInfo, searchInfo.MetadataLanguage, cancellationToken)
  49. .ConfigureAwait(false);
  50. if (string.IsNullOrEmpty(episodeTvdbId))
  51. {
  52. _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
  53. searchInfo.ParentIndexNumber, searchInfo.IndexNumber, seriesTvdbId);
  54. return list;
  55. }
  56. }
  57. var episodeResult = await _tvDbClientManager.GetEpisodesAsync(Convert.ToInt32(episodeTvdbId),
  58. searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
  59. var metadataResult = MapEpisodeToResult(searchInfo, episodeResult.Data);
  60. if (metadataResult.HasMetadata)
  61. {
  62. var item = metadataResult.Item;
  63. list.Add(new RemoteSearchResult
  64. {
  65. IndexNumber = item.IndexNumber,
  66. Name = item.Name,
  67. ParentIndexNumber = item.ParentIndexNumber,
  68. PremiereDate = item.PremiereDate,
  69. ProductionYear = item.ProductionYear,
  70. ProviderIds = item.ProviderIds,
  71. SearchProviderName = Name,
  72. IndexNumberEnd = item.IndexNumberEnd
  73. });
  74. }
  75. }
  76. catch (TvDbServerException e)
  77. {
  78. _logger.LogError(e, "Failed to retrieve episode with id {TvDbId}", searchInfo.IndexNumber);
  79. }
  80. }
  81. return list;
  82. }
  83. public string Name => "TheTVDB";
  84. public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken)
  85. {
  86. var result = new MetadataResult<Episode>
  87. {
  88. QueriedById = true
  89. };
  90. if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) &&
  91. (searchInfo.IndexNumber.HasValue || searchInfo.PremiereDate.HasValue))
  92. {
  93. var tvdbId = searchInfo.GetProviderId(MetadataProviders.Tvdb);
  94. try
  95. {
  96. if (string.IsNullOrEmpty(tvdbId))
  97. {
  98. tvdbId = await _tvDbClientManager
  99. .GetEpisodeTvdbId(searchInfo, searchInfo.MetadataLanguage, cancellationToken)
  100. .ConfigureAwait(false);
  101. if (string.IsNullOrEmpty(tvdbId))
  102. {
  103. _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
  104. searchInfo.ParentIndexNumber, searchInfo.IndexNumber, tvdbId);
  105. return result;
  106. }
  107. }
  108. var episodeResult = await _tvDbClientManager.GetEpisodesAsync(
  109. Convert.ToInt32(tvdbId), searchInfo.MetadataLanguage,
  110. cancellationToken).ConfigureAwait(false);
  111. result = MapEpisodeToResult(searchInfo, episodeResult.Data);
  112. }
  113. catch (TvDbServerException e)
  114. {
  115. _logger.LogError(e, "Failed to retrieve episode with id {TvDbId}", tvdbId);
  116. }
  117. }
  118. else
  119. {
  120. _logger.LogDebug("No series identity found for {EpisodeName}", searchInfo.Name);
  121. }
  122. return result;
  123. }
  124. private static MetadataResult<Episode> MapEpisodeToResult(EpisodeInfo id, EpisodeRecord episode)
  125. {
  126. var result = new MetadataResult<Episode>
  127. {
  128. HasMetadata = true,
  129. Item = new Episode
  130. {
  131. IndexNumber = id.IndexNumber,
  132. ParentIndexNumber = id.ParentIndexNumber,
  133. IndexNumberEnd = id.IndexNumberEnd,
  134. AirsBeforeEpisodeNumber = episode.AirsBeforeEpisode,
  135. AirsAfterSeasonNumber = episode.AirsAfterSeason,
  136. AirsBeforeSeasonNumber = episode.AirsBeforeSeason,
  137. Name = episode.EpisodeName,
  138. Overview = episode.Overview,
  139. CommunityRating = (float?)episode.SiteRating,
  140. }
  141. };
  142. result.ResetPeople();
  143. var item = result.Item;
  144. item.SetProviderId(MetadataProviders.Tvdb, episode.Id.ToString());
  145. item.SetProviderId(MetadataProviders.Imdb, episode.ImdbId);
  146. if (string.Equals(id.SeriesDisplayOrder, "dvd", StringComparison.OrdinalIgnoreCase))
  147. {
  148. item.IndexNumber = Convert.ToInt32(episode.DvdEpisodeNumber ?? episode.AiredEpisodeNumber);
  149. item.ParentIndexNumber = episode.DvdSeason ?? episode.AiredSeason;
  150. }
  151. else if (episode.AiredEpisodeNumber.HasValue)
  152. {
  153. item.IndexNumber = episode.AiredEpisodeNumber;
  154. }
  155. else if (episode.AiredSeason.HasValue)
  156. {
  157. item.ParentIndexNumber = episode.AiredSeason;
  158. }
  159. if (DateTime.TryParse(episode.FirstAired, out var date))
  160. {
  161. // dates from tvdb are UTC but without offset or Z
  162. item.PremiereDate = date;
  163. item.ProductionYear = date.Year;
  164. }
  165. foreach (var director in episode.Directors)
  166. {
  167. result.AddPerson(new PersonInfo
  168. {
  169. Name = director,
  170. Type = PersonType.Director
  171. });
  172. }
  173. // GuestStars is a weird list of names and roles
  174. // Example:
  175. // 1: Some Actor (Role1
  176. // 2: Role2
  177. // 3: Role3)
  178. // 4: Another Actor (Role1
  179. // ...
  180. for (var i = 0; i < episode.GuestStars.Length; ++i)
  181. {
  182. var currentActor = episode.GuestStars[i];
  183. var roleStartIndex = currentActor.IndexOf('(');
  184. if (roleStartIndex == -1)
  185. {
  186. result.AddPerson(new PersonInfo
  187. {
  188. Type = PersonType.GuestStar,
  189. Name = currentActor,
  190. Role = string.Empty
  191. });
  192. continue;
  193. }
  194. var roles = new List<string> {currentActor.Substring(roleStartIndex + 1)};
  195. // Fetch all roles
  196. for (var j = i + 1; j < episode.GuestStars.Length; ++j)
  197. {
  198. var currentRole = episode.GuestStars[j];
  199. var roleEndIndex = currentRole.IndexOf(')');
  200. if (roleEndIndex == -1)
  201. {
  202. roles.Add(currentRole);
  203. }
  204. roles.Add(currentRole.TrimEnd(')'));
  205. // Update the outer index (keep in mind it adds 1 after the iteration)
  206. i = j;
  207. break;
  208. }
  209. result.AddPerson(new PersonInfo
  210. {
  211. Type = PersonType.GuestStar,
  212. Name = currentActor.Substring(0, roleStartIndex).Trim(),
  213. Role = string.Join(", ", roles)
  214. });
  215. }
  216. foreach (var writer in episode.Writers)
  217. {
  218. result.AddPerson(new PersonInfo
  219. {
  220. Name = writer,
  221. Type = PersonType.Writer
  222. });
  223. }
  224. result.ResultLanguage = episode.Language.EpisodeName;
  225. return result;
  226. }
  227. public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
  228. {
  229. return _httpClient.GetResponse(new HttpRequestOptions
  230. {
  231. CancellationToken = cancellationToken,
  232. Url = url
  233. });
  234. }
  235. public int Order => 0;
  236. }
  237. }