OmdbProvider.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. using CommonIO;
  2. using MediaBrowser.Common.Configuration;
  3. using MediaBrowser.Common.Net;
  4. using MediaBrowser.Controller.Configuration;
  5. using MediaBrowser.Controller.Entities;
  6. using MediaBrowser.Model.Entities;
  7. using MediaBrowser.Model.Serialization;
  8. using System;
  9. using System.Globalization;
  10. using System.IO;
  11. using System.Linq;
  12. using System.Net;
  13. using System.Text;
  14. using System.Threading;
  15. using System.Threading.Tasks;
  16. namespace MediaBrowser.Providers.Omdb
  17. {
  18. public class OmdbProvider
  19. {
  20. internal static readonly SemaphoreSlim ResourcePool = new SemaphoreSlim(1, 1);
  21. private readonly IJsonSerializer _jsonSerializer;
  22. private readonly IFileSystem _fileSystem;
  23. private readonly IServerConfigurationManager _configurationManager;
  24. private readonly IHttpClient _httpClient;
  25. private readonly CultureInfo _usCulture = new CultureInfo("en-US");
  26. public static OmdbProvider Current;
  27. public OmdbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
  28. {
  29. _jsonSerializer = jsonSerializer;
  30. _httpClient = httpClient;
  31. _fileSystem = fileSystem;
  32. _configurationManager = configurationManager;
  33. Current = this;
  34. }
  35. public async Task Fetch(BaseItem item, string imdbId, string language, string country, CancellationToken cancellationToken)
  36. {
  37. if (string.IsNullOrWhiteSpace(imdbId))
  38. {
  39. throw new ArgumentNullException("imdbId");
  40. }
  41. var path = await EnsureItemInfo(imdbId, cancellationToken);
  42. string resultString;
  43. using (Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 131072))
  44. {
  45. using (var reader = new StreamReader(stream, new UTF8Encoding(false)))
  46. {
  47. resultString = reader.ReadToEnd();
  48. resultString = resultString.Replace("\"N/A\"", "\"\"");
  49. }
  50. }
  51. var result = _jsonSerializer.DeserializeFromString<RootObject>(resultString);
  52. // Only take the name and rating if the user's language is set to english, since Omdb has no localization
  53. if (string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
  54. {
  55. item.Name = result.Title;
  56. if (string.Equals(country, "us", StringComparison.OrdinalIgnoreCase))
  57. {
  58. item.OfficialRating = result.Rated;
  59. }
  60. }
  61. int year;
  62. if (!string.IsNullOrEmpty(result.Year)
  63. && int.TryParse(result.Year, NumberStyles.Number, _usCulture, out year)
  64. && year >= 0)
  65. {
  66. item.ProductionYear = year;
  67. }
  68. var hasCriticRating = item as IHasCriticRating;
  69. if (hasCriticRating != null)
  70. {
  71. // Seeing some bogus RT data on omdb for series, so filter it out here
  72. // RT doesn't even have tv series
  73. int tomatoMeter;
  74. if (!string.IsNullOrEmpty(result.tomatoMeter)
  75. && int.TryParse(result.tomatoMeter, NumberStyles.Integer, _usCulture, out tomatoMeter)
  76. && tomatoMeter >= 0)
  77. {
  78. hasCriticRating.CriticRating = tomatoMeter;
  79. }
  80. if (!string.IsNullOrEmpty(result.tomatoConsensus)
  81. && !string.Equals(result.tomatoConsensus, "No consensus yet.", StringComparison.OrdinalIgnoreCase))
  82. {
  83. hasCriticRating.CriticRatingSummary = WebUtility.HtmlDecode(result.tomatoConsensus);
  84. }
  85. }
  86. int voteCount;
  87. if (!string.IsNullOrEmpty(result.imdbVotes)
  88. && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out voteCount)
  89. && voteCount >= 0)
  90. {
  91. item.VoteCount = voteCount;
  92. }
  93. float imdbRating;
  94. if (!string.IsNullOrEmpty(result.imdbRating)
  95. && float.TryParse(result.imdbRating, NumberStyles.Any, _usCulture, out imdbRating)
  96. && imdbRating >= 0)
  97. {
  98. item.CommunityRating = imdbRating;
  99. }
  100. if (!string.IsNullOrEmpty(result.Website))
  101. {
  102. item.HomePageUrl = result.Website;
  103. }
  104. if (!string.IsNullOrWhiteSpace(result.imdbID))
  105. {
  106. item.SetProviderId(MetadataProviders.Imdb, result.imdbID);
  107. }
  108. ParseAdditionalMetadata(item, result);
  109. }
  110. internal async Task<string> EnsureItemInfo(string imdbId, CancellationToken cancellationToken)
  111. {
  112. if (string.IsNullOrWhiteSpace(imdbId))
  113. {
  114. throw new ArgumentNullException("imdbId");
  115. }
  116. var imdbParam = imdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? imdbId : "tt" + imdbId;
  117. var path = GetDataFilePath(imdbParam);
  118. var fileInfo = _fileSystem.GetFileSystemInfo(path);
  119. if (fileInfo.Exists)
  120. {
  121. // If it's recent or automatic updates are enabled, don't re-download
  122. if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3)
  123. {
  124. return path;
  125. }
  126. }
  127. var url = string.Format("https://www.omdbapi.com/?i={0}&tomatoes=true", imdbParam);
  128. using (var stream = await _httpClient.Get(new HttpRequestOptions
  129. {
  130. Url = url,
  131. ResourcePool = ResourcePool,
  132. CancellationToken = cancellationToken
  133. }).ConfigureAwait(false))
  134. {
  135. var rootObject = _jsonSerializer.DeserializeFromStream<RootObject>(stream);
  136. _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
  137. _jsonSerializer.SerializeToFile(rootObject, path);
  138. }
  139. return path;
  140. }
  141. internal string GetDataFilePath(string imdbId)
  142. {
  143. if (string.IsNullOrEmpty(imdbId))
  144. {
  145. throw new ArgumentNullException("imdbId");
  146. }
  147. var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb");
  148. var filename = string.Format("{0}.json", imdbId);
  149. return Path.Combine(dataPath, filename);
  150. }
  151. private void ParseAdditionalMetadata(BaseItem item, RootObject result)
  152. {
  153. // Grab series genres because imdb data is better than tvdb. Leave movies alone
  154. // But only do it if english is the preferred language because this data will not be localized
  155. if (ShouldFetchGenres(item) &&
  156. !string.IsNullOrWhiteSpace(result.Genre))
  157. {
  158. item.Genres.Clear();
  159. foreach (var genre in result.Genre
  160. .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
  161. .Select(i => i.Trim())
  162. .Where(i => !string.IsNullOrWhiteSpace(i)))
  163. {
  164. item.AddGenre(genre);
  165. }
  166. }
  167. var hasMetascore = item as IHasMetascore;
  168. if (hasMetascore != null)
  169. {
  170. float metascore;
  171. if (!string.IsNullOrEmpty(result.Metascore) && float.TryParse(result.Metascore, NumberStyles.Any, _usCulture, out metascore) && metascore >= 0)
  172. {
  173. hasMetascore.Metascore = metascore;
  174. }
  175. }
  176. var hasAwards = item as IHasAwards;
  177. if (hasAwards != null && !string.IsNullOrEmpty(result.Awards))
  178. {
  179. hasAwards.AwardSummary = WebUtility.HtmlDecode(result.Awards);
  180. }
  181. var hasShortOverview = item as IHasShortOverview;
  182. if (hasShortOverview != null)
  183. {
  184. // Imdb plots are usually pretty short
  185. hasShortOverview.ShortOverview = result.Plot;
  186. }
  187. }
  188. private bool ShouldFetchGenres(BaseItem item)
  189. {
  190. var lang = item.GetPreferredMetadataLanguage();
  191. // The data isn't localized and so can only be used for english users
  192. return string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase);
  193. }
  194. private class RootObject
  195. {
  196. public string Title { get; set; }
  197. public string Year { get; set; }
  198. public string Rated { get; set; }
  199. public string Released { get; set; }
  200. public string Runtime { get; set; }
  201. public string Genre { get; set; }
  202. public string Director { get; set; }
  203. public string Writer { get; set; }
  204. public string Actors { get; set; }
  205. public string Plot { get; set; }
  206. public string Poster { get; set; }
  207. public string imdbRating { get; set; }
  208. public string imdbVotes { get; set; }
  209. public string imdbID { get; set; }
  210. public string Type { get; set; }
  211. public string tomatoMeter { get; set; }
  212. public string tomatoImage { get; set; }
  213. public string tomatoRating { get; set; }
  214. public string tomatoReviews { get; set; }
  215. public string tomatoFresh { get; set; }
  216. public string tomatoRotten { get; set; }
  217. public string tomatoConsensus { get; set; }
  218. public string tomatoUserMeter { get; set; }
  219. public string tomatoUserRating { get; set; }
  220. public string tomatoUserReviews { get; set; }
  221. public string DVD { get; set; }
  222. public string BoxOffice { get; set; }
  223. public string Production { get; set; }
  224. public string Website { get; set; }
  225. public string Response { get; set; }
  226. public string Language { get; set; }
  227. public string Country { get; set; }
  228. public string Awards { get; set; }
  229. public string Metascore { get; set; }
  230. }
  231. }
  232. }