TmdbPersonProvider.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. using MediaBrowser.Common.Net;
  2. using MediaBrowser.Controller.Entities;
  3. using MediaBrowser.Model.Entities;
  4. using MediaBrowser.Model.Net;
  5. using MediaBrowser.Model.Serialization;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Globalization;
  9. using System.IO;
  10. using System.Linq;
  11. using System.Net;
  12. using System.Threading;
  13. using System.Threading.Tasks;
  14. namespace MediaBrowser.Controller.Providers.Movies
  15. {
  16. /// <summary>
  17. /// Class TmdbPersonProvider
  18. /// </summary>
  19. public class TmdbPersonProvider : BaseMetadataProvider
  20. {
  21. /// <summary>
  22. /// The meta file name
  23. /// </summary>
  24. protected const string MetaFileName = "MBPerson.json";
  25. /// <summary>
  26. /// Gets the json serializer.
  27. /// </summary>
  28. /// <value>The json serializer.</value>
  29. protected IJsonSerializer JsonSerializer { get; private set; }
  30. /// <summary>
  31. /// Gets the HTTP client.
  32. /// </summary>
  33. /// <value>The HTTP client.</value>
  34. protected IHttpClient HttpClient { get; private set; }
  35. /// <summary>
  36. /// Initializes a new instance of the <see cref="MovieDbProvider" /> class.
  37. /// </summary>
  38. /// <param name="httpClient">The HTTP client.</param>
  39. /// <param name="jsonSerializer">The json serializer.</param>
  40. /// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
  41. public TmdbPersonProvider(IHttpClient httpClient, IJsonSerializer jsonSerializer)
  42. : base()
  43. {
  44. if (jsonSerializer == null)
  45. {
  46. throw new ArgumentNullException("jsonSerializer");
  47. }
  48. if (httpClient == null)
  49. {
  50. throw new ArgumentNullException("httpClient");
  51. }
  52. HttpClient = httpClient;
  53. JsonSerializer = jsonSerializer;
  54. }
  55. /// <summary>
  56. /// Supportses the specified item.
  57. /// </summary>
  58. /// <param name="item">The item.</param>
  59. /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
  60. public override bool Supports(BaseItem item)
  61. {
  62. return item is Person;
  63. }
  64. /// <summary>
  65. /// Needses the refresh internal.
  66. /// </summary>
  67. /// <param name="item">The item.</param>
  68. /// <param name="providerInfo">The provider info.</param>
  69. /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
  70. protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
  71. {
  72. //we fetch if either info or image needed and haven't already tried recently
  73. return (string.IsNullOrEmpty(item.PrimaryImagePath) || !item.ResolveArgs.ContainsMetaFileByName(MetaFileName))
  74. && DateTime.Today.Subtract(providerInfo.LastRefreshed).TotalDays > Kernel.Instance.Configuration.MetadataRefreshDays;
  75. }
  76. /// <summary>
  77. /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
  78. /// </summary>
  79. /// <param name="item">The item.</param>
  80. /// <param name="force">if set to <c>true</c> [force].</param>
  81. /// <param name="cancellationToken">The cancellation token.</param>
  82. /// <returns>Task{System.Boolean}.</returns>
  83. protected override async Task<bool> FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken)
  84. {
  85. cancellationToken.ThrowIfCancellationRequested();
  86. var person = (Person)item;
  87. var tasks = new List<Task>();
  88. var id = person.GetProviderId(MetadataProviders.Tmdb);
  89. // We don't already have an Id, need to fetch it
  90. if (string.IsNullOrEmpty(id))
  91. {
  92. id = await GetTmdbId(item, cancellationToken).ConfigureAwait(false);
  93. }
  94. cancellationToken.ThrowIfCancellationRequested();
  95. if (!string.IsNullOrEmpty(id))
  96. {
  97. //get info only if not already saved
  98. if (!item.ResolveArgs.ContainsMetaFileByName(MetaFileName))
  99. {
  100. tasks.Add(FetchInfo(person, id, cancellationToken));
  101. }
  102. //get image only if not already there
  103. if (string.IsNullOrEmpty(item.PrimaryImagePath))
  104. {
  105. tasks.Add(FetchImages(person, id, cancellationToken));
  106. }
  107. //and wait for them to complete
  108. await Task.WhenAll(tasks).ConfigureAwait(false);
  109. }
  110. else
  111. {
  112. Logger.Debug("TmdbPersonProvider Unable to obtain id for " + item.Name);
  113. }
  114. SetLastRefreshed(item, DateTime.UtcNow);
  115. return true;
  116. }
  117. /// <summary>
  118. /// Gets the priority.
  119. /// </summary>
  120. /// <value>The priority.</value>
  121. public override MetadataProviderPriority Priority
  122. {
  123. get { return MetadataProviderPriority.Second; }
  124. }
  125. /// <summary>
  126. /// Gets a value indicating whether [requires internet].
  127. /// </summary>
  128. /// <value><c>true</c> if [requires internet]; otherwise, <c>false</c>.</value>
  129. public override bool RequiresInternet
  130. {
  131. get
  132. {
  133. return true;
  134. }
  135. }
  136. /// <summary>
  137. /// Gets the TMDB id.
  138. /// </summary>
  139. /// <param name="person">The person.</param>
  140. /// <param name="cancellationToken">The cancellation token.</param>
  141. /// <returns>Task{System.String}.</returns>
  142. private async Task<string> GetTmdbId(BaseItem person, CancellationToken cancellationToken)
  143. {
  144. string url = string.Format(@"http://api.themoviedb.org/3/search/person?api_key={1}&query={0}", WebUtility.UrlEncode(person.Name), MovieDbProvider.ApiKey);
  145. PersonSearchResults searchResult = null;
  146. try
  147. {
  148. using (Stream json = await HttpClient.Get(url, Kernel.Instance.ResourcePools.MovieDb, cancellationToken).ConfigureAwait(false))
  149. {
  150. searchResult = JsonSerializer.DeserializeFromStream<PersonSearchResults>(json);
  151. }
  152. }
  153. catch (HttpException)
  154. {
  155. }
  156. return searchResult != null && searchResult.Total_Results > 0 ? searchResult.Results[0].Id.ToString() : null;
  157. }
  158. /// <summary>
  159. /// Fetches the info.
  160. /// </summary>
  161. /// <param name="person">The person.</param>
  162. /// <param name="id">The id.</param>
  163. /// <param name="cancellationToken">The cancellation token.</param>
  164. /// <returns>Task.</returns>
  165. private async Task FetchInfo(Person person, string id, CancellationToken cancellationToken)
  166. {
  167. string url = string.Format(@"http://api.themoviedb.org/3/person/{1}?api_key={0}", MovieDbProvider.ApiKey, id);
  168. PersonResult searchResult = null;
  169. try
  170. {
  171. using (Stream json = await HttpClient.Get(url, Kernel.Instance.ResourcePools.MovieDb, cancellationToken).ConfigureAwait(false))
  172. {
  173. if (json != null)
  174. {
  175. searchResult = JsonSerializer.DeserializeFromStream<PersonResult>(json);
  176. }
  177. }
  178. }
  179. catch (HttpException)
  180. {
  181. }
  182. cancellationToken.ThrowIfCancellationRequested();
  183. if (searchResult != null && searchResult.Biography != null)
  184. {
  185. ProcessInfo(person, searchResult);
  186. //save locally
  187. var memoryStream = new MemoryStream();
  188. JsonSerializer.SerializeToStream(searchResult, memoryStream);
  189. await Kernel.Instance.FileSystemManager.SaveToLibraryFilesystem(person, Path.Combine(person.MetaLocation, MetaFileName), memoryStream, cancellationToken);
  190. Logger.Debug("TmdbPersonProvider downloaded and saved information for {0}", person.Name);
  191. }
  192. }
  193. /// <summary>
  194. /// Processes the info.
  195. /// </summary>
  196. /// <param name="person">The person.</param>
  197. /// <param name="searchResult">The search result.</param>
  198. protected void ProcessInfo(Person person, PersonResult searchResult)
  199. {
  200. person.Overview = searchResult.Biography;
  201. DateTime date;
  202. if (DateTime.TryParseExact(searchResult.Birthday, "yyyy-MM-dd", new CultureInfo("en-US"), DateTimeStyles.None, out date))
  203. {
  204. person.PremiereDate = date;
  205. }
  206. person.SetProviderId(MetadataProviders.Tmdb, searchResult.Id.ToString());
  207. }
  208. /// <summary>
  209. /// Fetches the images.
  210. /// </summary>
  211. /// <param name="person">The person.</param>
  212. /// <param name="id">The id.</param>
  213. /// <param name="cancellationToken">The cancellation token.</param>
  214. /// <returns>Task.</returns>
  215. private async Task FetchImages(Person person, string id, CancellationToken cancellationToken)
  216. {
  217. string url = string.Format(@"http://api.themoviedb.org/3/person/{1}/images?api_key={0}", MovieDbProvider.ApiKey, id);
  218. PersonImages searchResult = null;
  219. try
  220. {
  221. using (Stream json = await HttpClient.Get(url, Kernel.Instance.ResourcePools.MovieDb, cancellationToken).ConfigureAwait(false))
  222. {
  223. if (json != null)
  224. {
  225. searchResult = JsonSerializer.DeserializeFromStream<PersonImages>(json);
  226. }
  227. }
  228. }
  229. catch (HttpException)
  230. {
  231. }
  232. if (searchResult != null && searchResult.Profiles.Count > 0)
  233. {
  234. //get our language
  235. var profile =
  236. searchResult.Profiles.FirstOrDefault(
  237. p =>
  238. !string.IsNullOrEmpty(p.Iso_639_1) &&
  239. p.Iso_639_1.Equals(Kernel.Instance.Configuration.PreferredMetadataLanguage,
  240. StringComparison.OrdinalIgnoreCase));
  241. if (profile == null)
  242. {
  243. //didn't find our language - try first null one
  244. profile =
  245. searchResult.Profiles.FirstOrDefault(
  246. p =>
  247. !string.IsNullOrEmpty(p.Iso_639_1) &&
  248. p.Iso_639_1.Equals(Kernel.Instance.Configuration.PreferredMetadataLanguage,
  249. StringComparison.OrdinalIgnoreCase));
  250. }
  251. if (profile == null)
  252. {
  253. //still nothing - just get first one
  254. profile = searchResult.Profiles[0];
  255. }
  256. if (profile != null)
  257. {
  258. var tmdbSettings = await Kernel.Instance.MetadataProviders.OfType<MovieDbProvider>().First().TmdbSettings.ConfigureAwait(false);
  259. var img = await DownloadAndSaveImage(person, tmdbSettings.images.base_url + Kernel.Instance.Configuration.TmdbFetchedProfileSize + profile.File_Path,
  260. "folder" + Path.GetExtension(profile.File_Path), cancellationToken).ConfigureAwait(false);
  261. if (!string.IsNullOrEmpty(img))
  262. {
  263. person.PrimaryImagePath = img;
  264. }
  265. }
  266. }
  267. }
  268. /// <summary>
  269. /// Downloads the and save image.
  270. /// </summary>
  271. /// <param name="item">The item.</param>
  272. /// <param name="source">The source.</param>
  273. /// <param name="targetName">Name of the target.</param>
  274. /// <param name="cancellationToken">The cancellation token.</param>
  275. /// <returns>Task{System.String}.</returns>
  276. private async Task<string> DownloadAndSaveImage(BaseItem item, string source, string targetName, CancellationToken cancellationToken)
  277. {
  278. if (source == null) return null;
  279. //download and save locally (if not already there)
  280. var localPath = Path.Combine(item.MetaLocation, targetName);
  281. if (!item.ResolveArgs.ContainsMetaFileByName(targetName))
  282. {
  283. using (var sourceStream = await HttpClient.GetMemoryStream(source, Kernel.Instance.ResourcePools.MovieDb, cancellationToken).ConfigureAwait(false))
  284. {
  285. await Kernel.Instance.FileSystemManager.SaveToLibraryFilesystem(item, localPath, sourceStream, cancellationToken).ConfigureAwait(false);
  286. Logger.Debug("TmdbPersonProvider downloaded and saved image for {0}", item.Name);
  287. }
  288. }
  289. return localPath;
  290. }
  291. #region Result Objects
  292. /// <summary>
  293. /// Class PersonSearchResult
  294. /// </summary>
  295. public class PersonSearchResult
  296. {
  297. /// <summary>
  298. /// Gets or sets a value indicating whether this <see cref="PersonSearchResult" /> is adult.
  299. /// </summary>
  300. /// <value><c>true</c> if adult; otherwise, <c>false</c>.</value>
  301. public bool Adult { get; set; }
  302. /// <summary>
  303. /// Gets or sets the id.
  304. /// </summary>
  305. /// <value>The id.</value>
  306. public int Id { get; set; }
  307. /// <summary>
  308. /// Gets or sets the name.
  309. /// </summary>
  310. /// <value>The name.</value>
  311. public string Name { get; set; }
  312. /// <summary>
  313. /// Gets or sets the profile_ path.
  314. /// </summary>
  315. /// <value>The profile_ path.</value>
  316. public string Profile_Path { get; set; }
  317. }
  318. /// <summary>
  319. /// Class PersonSearchResults
  320. /// </summary>
  321. public class PersonSearchResults
  322. {
  323. /// <summary>
  324. /// Gets or sets the page.
  325. /// </summary>
  326. /// <value>The page.</value>
  327. public int Page { get; set; }
  328. /// <summary>
  329. /// Gets or sets the results.
  330. /// </summary>
  331. /// <value>The results.</value>
  332. public List<PersonSearchResult> Results { get; set; }
  333. /// <summary>
  334. /// Gets or sets the total_ pages.
  335. /// </summary>
  336. /// <value>The total_ pages.</value>
  337. public int Total_Pages { get; set; }
  338. /// <summary>
  339. /// Gets or sets the total_ results.
  340. /// </summary>
  341. /// <value>The total_ results.</value>
  342. public int Total_Results { get; set; }
  343. }
  344. /// <summary>
  345. /// Class PersonResult
  346. /// </summary>
  347. public class PersonResult
  348. {
  349. /// <summary>
  350. /// Gets or sets a value indicating whether this <see cref="PersonResult" /> is adult.
  351. /// </summary>
  352. /// <value><c>true</c> if adult; otherwise, <c>false</c>.</value>
  353. public bool Adult { get; set; }
  354. /// <summary>
  355. /// Gets or sets the also_ known_ as.
  356. /// </summary>
  357. /// <value>The also_ known_ as.</value>
  358. public List<object> Also_Known_As { get; set; }
  359. /// <summary>
  360. /// Gets or sets the biography.
  361. /// </summary>
  362. /// <value>The biography.</value>
  363. public string Biography { get; set; }
  364. /// <summary>
  365. /// Gets or sets the birthday.
  366. /// </summary>
  367. /// <value>The birthday.</value>
  368. public string Birthday { get; set; }
  369. /// <summary>
  370. /// Gets or sets the deathday.
  371. /// </summary>
  372. /// <value>The deathday.</value>
  373. public string Deathday { get; set; }
  374. /// <summary>
  375. /// Gets or sets the homepage.
  376. /// </summary>
  377. /// <value>The homepage.</value>
  378. public string Homepage { get; set; }
  379. /// <summary>
  380. /// Gets or sets the id.
  381. /// </summary>
  382. /// <value>The id.</value>
  383. public int Id { get; set; }
  384. /// <summary>
  385. /// Gets or sets the name.
  386. /// </summary>
  387. /// <value>The name.</value>
  388. public string Name { get; set; }
  389. /// <summary>
  390. /// Gets or sets the place_ of_ birth.
  391. /// </summary>
  392. /// <value>The place_ of_ birth.</value>
  393. public string Place_Of_Birth { get; set; }
  394. /// <summary>
  395. /// Gets or sets the profile_ path.
  396. /// </summary>
  397. /// <value>The profile_ path.</value>
  398. public string Profile_Path { get; set; }
  399. }
  400. /// <summary>
  401. /// Class PersonProfile
  402. /// </summary>
  403. public class PersonProfile
  404. {
  405. /// <summary>
  406. /// Gets or sets the aspect_ ratio.
  407. /// </summary>
  408. /// <value>The aspect_ ratio.</value>
  409. public double Aspect_Ratio { get; set; }
  410. /// <summary>
  411. /// Gets or sets the file_ path.
  412. /// </summary>
  413. /// <value>The file_ path.</value>
  414. public string File_Path { get; set; }
  415. /// <summary>
  416. /// Gets or sets the height.
  417. /// </summary>
  418. /// <value>The height.</value>
  419. public int Height { get; set; }
  420. /// <summary>
  421. /// Gets or sets the iso_639_1.
  422. /// </summary>
  423. /// <value>The iso_639_1.</value>
  424. public string Iso_639_1 { get; set; }
  425. /// <summary>
  426. /// Gets or sets the width.
  427. /// </summary>
  428. /// <value>The width.</value>
  429. public int Width { get; set; }
  430. }
  431. /// <summary>
  432. /// Class PersonImages
  433. /// </summary>
  434. public class PersonImages
  435. {
  436. /// <summary>
  437. /// Gets or sets the id.
  438. /// </summary>
  439. /// <value>The id.</value>
  440. public int Id { get; set; }
  441. /// <summary>
  442. /// Gets or sets the profiles.
  443. /// </summary>
  444. /// <value>The profiles.</value>
  445. public List<PersonProfile> Profiles { get; set; }
  446. }
  447. #endregion
  448. }
  449. }