TmdbPersonProvider.cs 19 KB

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