diff options
Diffstat (limited to 'MediaBrowser.Providers/Plugins/Omdb')
4 files changed, 1016 insertions, 0 deletions
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs new file mode 100644 index 000000000..37160dd2c --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Providers; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Providers.Plugins.Omdb +{ + public class OmdbEpisodeProvider : + IRemoteMetadataProvider<Episode, EpisodeInfo>, + IHasOrder + { + private readonly IJsonSerializer _jsonSerializer; + private readonly IHttpClient _httpClient; + private readonly OmdbItemProvider _itemProvider; + private readonly IFileSystem _fileSystem; + private readonly IServerConfigurationManager _configurationManager; + private readonly IApplicationHost _appHost; + + public OmdbEpisodeProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager configurationManager) + { + _jsonSerializer = jsonSerializer; + _httpClient = httpClient; + _fileSystem = fileSystem; + _configurationManager = configurationManager; + _appHost = appHost; + _itemProvider = new OmdbItemProvider(jsonSerializer, _appHost, httpClient, logger, libraryManager, fileSystem, configurationManager); + } + + public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) + { + return _itemProvider.GetSearchResults(searchInfo, "episode", cancellationToken); + } + + public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken) + { + var result = new MetadataResult<Episode>() + { + Item = new Episode(), + QueriedById = true + }; + + // Allowing this will dramatically increase scan times + if (info.IsMissingEpisode) + { + return result; + } + + if (info.SeriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out string seriesImdbId) && !string.IsNullOrEmpty(seriesImdbId)) + { + if (info.IndexNumber.HasValue && info.ParentIndexNumber.HasValue) + { + result.HasMetadata = await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager) + .FetchEpisodeData(result, info.IndexNumber.Value, info.ParentIndexNumber.Value, info.GetProviderId(MetadataProviders.Imdb), seriesImdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); + } + } + + return result; + } + // After TheTvDb + public int Order => 1; + + public string Name => "The Open Movie Database"; + + public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken) + { + return _itemProvider.GetImageResponse(url, cancellationToken); + } + } +} diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs new file mode 100644 index 000000000..a450c2a6d --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Providers; +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Providers.Plugins.Omdb +{ + public class OmdbImageProvider : IRemoteImageProvider, IHasOrder + { + private readonly IHttpClient _httpClient; + private readonly IJsonSerializer _jsonSerializer; + private readonly IFileSystem _fileSystem; + private readonly IServerConfigurationManager _configurationManager; + private readonly IApplicationHost _appHost; + + public OmdbImageProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager) + { + _jsonSerializer = jsonSerializer; + _httpClient = httpClient; + _fileSystem = fileSystem; + _configurationManager = configurationManager; + _appHost = appHost; + } + + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) + { + return new List<ImageType> + { + ImageType.Primary + }; + } + + public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) + { + var imdbId = item.GetProviderId(MetadataProviders.Imdb); + + var list = new List<RemoteImageInfo>(); + + var provider = new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager); + + if (!string.IsNullOrWhiteSpace(imdbId)) + { + var rootObject = await provider.GetRootObject(imdbId, cancellationToken).ConfigureAwait(false); + + if (!string.IsNullOrEmpty(rootObject.Poster)) + { + if (item is Episode) + { + // img.omdbapi.com is returning 404's + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = rootObject.Poster + }); + } + else + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = string.Format("https://img.omdbapi.com/?i={0}&apikey=2c9d9507", imdbId) + }); + } + } + } + + return list; + } + + public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClient.GetResponse(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = url + }); + } + + public string Name => "The Open Movie Database"; + + public bool Supports(BaseItem item) + { + return item is Movie || item is Trailer || item is Episode; + } + // After other internet providers, because they're better + // But before fallback providers like screengrab + public int Order => 90; + } +} diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs new file mode 100644 index 000000000..3aadda5d0 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -0,0 +1,317 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Providers; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Providers.Plugins.Omdb +{ + public class OmdbItemProvider : IRemoteMetadataProvider<Series, SeriesInfo>, + IRemoteMetadataProvider<Movie, MovieInfo>, IRemoteMetadataProvider<Trailer, TrailerInfo>, IHasOrder + { + private readonly IJsonSerializer _jsonSerializer; + private readonly IHttpClient _httpClient; + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly IFileSystem _fileSystem; + private readonly IServerConfigurationManager _configurationManager; + private readonly IApplicationHost _appHost; + + public OmdbItemProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager configurationManager) + { + _jsonSerializer = jsonSerializer; + _httpClient = httpClient; + _logger = logger; + _libraryManager = libraryManager; + _fileSystem = fileSystem; + _configurationManager = configurationManager; + _appHost = appHost; + } + // After primary option + public int Order => 2; + + public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) + { + return GetSearchResults(searchInfo, "series", cancellationToken); + } + + public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken) + { + return GetSearchResults(searchInfo, "movie", cancellationToken); + } + + public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ItemLookupInfo searchInfo, string type, CancellationToken cancellationToken) + { + return GetSearchResultsInternal(searchInfo, type, true, cancellationToken); + } + + private async Task<IEnumerable<RemoteSearchResult>> GetSearchResultsInternal(ItemLookupInfo searchInfo, string type, bool isSearch, CancellationToken cancellationToken) + { + var episodeSearchInfo = searchInfo as EpisodeInfo; + + var imdbId = searchInfo.GetProviderId(MetadataProviders.Imdb); + + var urlQuery = "plot=full&r=json"; + if (type == "episode" && episodeSearchInfo != null) + { + episodeSearchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out imdbId); + } + + var name = searchInfo.Name; + var year = searchInfo.Year; + + if (!string.IsNullOrWhiteSpace(name)) + { + var parsedName = _libraryManager.ParseName(name); + var yearInName = parsedName.Year; + name = parsedName.Name; + year = year ?? yearInName; + } + + if (string.IsNullOrWhiteSpace(imdbId)) + { + if (year.HasValue) + { + urlQuery += "&y=" + year.Value.ToString(CultureInfo.InvariantCulture); + } + + // &s means search and returns a list of results as opposed to t + if (isSearch) + { + urlQuery += "&s=" + WebUtility.UrlEncode(name); + } + else + { + urlQuery += "&t=" + WebUtility.UrlEncode(name); + } + urlQuery += "&type=" + type; + } + else + { + urlQuery += "&i=" + imdbId; + isSearch = false; + } + + if (type == "episode") + { + if (searchInfo.IndexNumber.HasValue) + { + urlQuery += string.Format(CultureInfo.InvariantCulture, "&Episode={0}", searchInfo.IndexNumber); + } + if (searchInfo.ParentIndexNumber.HasValue) + { + urlQuery += string.Format(CultureInfo.InvariantCulture, "&Season={0}", searchInfo.ParentIndexNumber); + } + } + + var url = OmdbProvider.GetOmdbUrl(urlQuery, _appHost, cancellationToken); + + using (var response = await OmdbProvider.GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false)) + { + using (var stream = response.Content) + { + var resultList = new List<SearchResult>(); + + if (isSearch) + { + var searchResultList = await _jsonSerializer.DeserializeFromStreamAsync<SearchResultList>(stream).ConfigureAwait(false); + if (searchResultList != null && searchResultList.Search != null) + { + resultList.AddRange(searchResultList.Search); + } + } + else + { + var result = await _jsonSerializer.DeserializeFromStreamAsync<SearchResult>(stream).ConfigureAwait(false); + if (string.Equals(result.Response, "true", StringComparison.OrdinalIgnoreCase)) + { + resultList.Add(result); + } + } + + return resultList.Select(result => + { + var item = new RemoteSearchResult + { + IndexNumber = searchInfo.IndexNumber, + Name = result.Title, + ParentIndexNumber = searchInfo.ParentIndexNumber, + SearchProviderName = Name + }; + + if (episodeSearchInfo != null && episodeSearchInfo.IndexNumberEnd.HasValue) + { + item.IndexNumberEnd = episodeSearchInfo.IndexNumberEnd.Value; + } + + item.SetProviderId(MetadataProviders.Imdb, result.imdbID); + + if (result.Year.Length > 0 + && int.TryParse(result.Year.Substring(0, Math.Min(result.Year.Length, 4)), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedYear)) + { + item.ProductionYear = parsedYear; + } + + if (!string.IsNullOrEmpty(result.Released) + && DateTime.TryParse(result.Released, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out var released)) + { + item.PremiereDate = released; + } + + if (!string.IsNullOrWhiteSpace(result.Poster) && !string.Equals(result.Poster, "N/A", StringComparison.OrdinalIgnoreCase)) + { + item.ImageUrl = result.Poster; + } + + return item; + }); + } + } + } + + public Task<MetadataResult<Trailer>> GetMetadata(TrailerInfo info, CancellationToken cancellationToken) + { + return GetMovieResult<Trailer>(info, cancellationToken); + } + + public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(TrailerInfo searchInfo, CancellationToken cancellationToken) + { + return GetSearchResults(searchInfo, "movie", cancellationToken); + } + + public string Name => "The Open Movie Database"; + + public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken) + { + var result = new MetadataResult<Series> + { + Item = new Series(), + QueriedById = true + }; + + var imdbId = info.GetProviderId(MetadataProviders.Imdb); + if (string.IsNullOrWhiteSpace(imdbId)) + { + imdbId = await GetSeriesImdbId(info, cancellationToken).ConfigureAwait(false); + result.QueriedById = false; + } + + if (!string.IsNullOrEmpty(imdbId)) + { + result.Item.SetProviderId(MetadataProviders.Imdb, imdbId); + result.HasMetadata = true; + + await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); + } + + return result; + } + + public Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken) + { + return GetMovieResult<Movie>(info, cancellationToken); + } + + private async Task<MetadataResult<T>> GetMovieResult<T>(ItemLookupInfo info, CancellationToken cancellationToken) + where T : BaseItem, new() + { + var result = new MetadataResult<T> + { + Item = new T(), + QueriedById = true + }; + + var imdbId = info.GetProviderId(MetadataProviders.Imdb); + if (string.IsNullOrWhiteSpace(imdbId)) + { + imdbId = await GetMovieImdbId(info, cancellationToken).ConfigureAwait(false); + result.QueriedById = false; + } + + if (!string.IsNullOrEmpty(imdbId)) + { + result.Item.SetProviderId(MetadataProviders.Imdb, imdbId); + result.HasMetadata = true; + + await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); + } + + return result; + } + + private async Task<string> GetMovieImdbId(ItemLookupInfo info, CancellationToken cancellationToken) + { + var results = await GetSearchResultsInternal(info, "movie", false, cancellationToken).ConfigureAwait(false); + var first = results.FirstOrDefault(); + return first == null ? null : first.GetProviderId(MetadataProviders.Imdb); + } + + private async Task<string> GetSeriesImdbId(SeriesInfo info, CancellationToken cancellationToken) + { + var results = await GetSearchResultsInternal(info, "series", false, cancellationToken).ConfigureAwait(false); + var first = results.FirstOrDefault(); + return first == null ? null : first.GetProviderId(MetadataProviders.Imdb); + } + + public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClient.GetResponse(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = url + }); + } + + class SearchResult + { + public string Title { get; set; } + public string Year { get; set; } + public string Rated { get; set; } + public string Released { get; set; } + public string Season { get; set; } + public string Episode { get; set; } + public string Runtime { get; set; } + public string Genre { get; set; } + public string Director { get; set; } + public string Writer { get; set; } + public string Actors { get; set; } + public string Plot { get; set; } + public string Language { get; set; } + public string Country { get; set; } + public string Awards { get; set; } + public string Poster { get; set; } + public string Metascore { get; set; } + public string imdbRating { get; set; } + public string imdbVotes { get; set; } + public string imdbID { get; set; } + public string seriesID { get; set; } + public string Type { get; set; } + public string Response { get; set; } + } + + private class SearchResultList + { + /// <summary> + /// Gets or sets the results. + /// </summary> + /// <value>The results.</value> + public List<SearchResult> Search { get; set; } + } + + } +} diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs new file mode 100644 index 000000000..fbdd293ed --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -0,0 +1,521 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Providers.Plugins.Omdb +{ + public class OmdbProvider + { + private readonly IJsonSerializer _jsonSerializer; + private readonly IFileSystem _fileSystem; + private readonly IServerConfigurationManager _configurationManager; + private readonly IHttpClient _httpClient; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private readonly IApplicationHost _appHost; + + public OmdbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IApplicationHost appHost, IServerConfigurationManager configurationManager) + { + _jsonSerializer = jsonSerializer; + _httpClient = httpClient; + _fileSystem = fileSystem; + _configurationManager = configurationManager; + _appHost = appHost; + } + + public async Task Fetch<T>(MetadataResult<T> itemResult, string imdbId, string language, string country, CancellationToken cancellationToken) + where T : BaseItem + { + if (string.IsNullOrWhiteSpace(imdbId)) + { + throw new ArgumentNullException(nameof(imdbId)); + } + + var item = itemResult.Item; + + var result = await GetRootObject(imdbId, cancellationToken).ConfigureAwait(false); + + // Only take the name and rating if the user's language is set to english, since Omdb has no localization + if (string.Equals(language, "en", StringComparison.OrdinalIgnoreCase) || _configurationManager.Configuration.EnableNewOmdbSupport) + { + item.Name = result.Title; + + if (string.Equals(country, "us", StringComparison.OrdinalIgnoreCase)) + { + item.OfficialRating = result.Rated; + } + } + + if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4 + && int.TryParse(result.Year.Substring(0, 4), NumberStyles.Number, _usCulture, out var year) + && year >= 0) + { + item.ProductionYear = year; + } + + var tomatoScore = result.GetRottenTomatoScore(); + + if (tomatoScore.HasValue) + { + item.CriticRating = tomatoScore; + } + + if (!string.IsNullOrEmpty(result.imdbVotes) + && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount) + && voteCount >= 0) + { + //item.VoteCount = voteCount; + } + + if (!string.IsNullOrEmpty(result.imdbRating) + && float.TryParse(result.imdbRating, NumberStyles.Any, _usCulture, out var imdbRating) + && imdbRating >= 0) + { + item.CommunityRating = imdbRating; + } + + //if (!string.IsNullOrEmpty(result.Website)) + //{ + // item.HomePageUrl = result.Website; + //} + + if (!string.IsNullOrWhiteSpace(result.imdbID)) + { + item.SetProviderId(MetadataProviders.Imdb, result.imdbID); + } + + ParseAdditionalMetadata(itemResult, result); + } + + public async Task<bool> FetchEpisodeData<T>(MetadataResult<T> itemResult, int episodeNumber, int seasonNumber, string episodeImdbId, string seriesImdbId, string language, string country, CancellationToken cancellationToken) + where T : BaseItem + { + if (string.IsNullOrWhiteSpace(seriesImdbId)) + { + throw new ArgumentNullException(nameof(seriesImdbId)); + } + + var item = itemResult.Item; + + var seasonResult = await GetSeasonRootObject(seriesImdbId, seasonNumber, cancellationToken).ConfigureAwait(false); + + if (seasonResult == null) + { + return false; + } + + RootObject result = null; + + if (!string.IsNullOrWhiteSpace(episodeImdbId)) + { + foreach (var episode in (seasonResult.Episodes ?? new RootObject[] { })) + { + if (string.Equals(episodeImdbId, episode.imdbID, StringComparison.OrdinalIgnoreCase)) + { + result = episode; + break; + } + } + } + + // finally, search by numbers + if (result == null) + { + foreach (var episode in (seasonResult.Episodes ?? new RootObject[] { })) + { + if (episode.Episode == episodeNumber) + { + result = episode; + break; + } + } + } + + if (result == null) + { + return false; + } + + // Only take the name and rating if the user's language is set to english, since Omdb has no localization + if (string.Equals(language, "en", StringComparison.OrdinalIgnoreCase) || _configurationManager.Configuration.EnableNewOmdbSupport) + { + item.Name = result.Title; + + if (string.Equals(country, "us", StringComparison.OrdinalIgnoreCase)) + { + item.OfficialRating = result.Rated; + } + } + + if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4 + && int.TryParse(result.Year.Substring(0, 4), NumberStyles.Number, _usCulture, out var year) + && year >= 0) + { + item.ProductionYear = year; + } + + var tomatoScore = result.GetRottenTomatoScore(); + + if (tomatoScore.HasValue) + { + item.CriticRating = tomatoScore; + } + + if (!string.IsNullOrEmpty(result.imdbVotes) + && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount) + && voteCount >= 0) + { + //item.VoteCount = voteCount; + } + + if (!string.IsNullOrEmpty(result.imdbRating) + && float.TryParse(result.imdbRating, NumberStyles.Any, _usCulture, out var imdbRating) + && imdbRating >= 0) + { + item.CommunityRating = imdbRating; + } + + //if (!string.IsNullOrEmpty(result.Website)) + //{ + // item.HomePageUrl = result.Website; + //} + + if (!string.IsNullOrWhiteSpace(result.imdbID)) + { + item.SetProviderId(MetadataProviders.Imdb, result.imdbID); + } + + ParseAdditionalMetadata(itemResult, result); + + return true; + } + + internal async Task<RootObject> GetRootObject(string imdbId, CancellationToken cancellationToken) + { + var path = await EnsureItemInfo(imdbId, cancellationToken).ConfigureAwait(false); + + string resultString; + + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + using (var reader = new StreamReader(stream, new UTF8Encoding(false))) + { + resultString = reader.ReadToEnd(); + resultString = resultString.Replace("\"N/A\"", "\"\""); + } + } + + var result = _jsonSerializer.DeserializeFromString<RootObject>(resultString); + return result; + } + + internal async Task<SeasonRootObject> GetSeasonRootObject(string imdbId, int seasonId, CancellationToken cancellationToken) + { + var path = await EnsureSeasonInfo(imdbId, seasonId, cancellationToken).ConfigureAwait(false); + + string resultString; + + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + using (var reader = new StreamReader(stream, new UTF8Encoding(false))) + { + resultString = reader.ReadToEnd(); + resultString = resultString.Replace("\"N/A\"", "\"\""); + } + } + + var result = _jsonSerializer.DeserializeFromString<SeasonRootObject>(resultString); + return result; + } + + internal static bool IsValidSeries(Dictionary<string, string> seriesProviderIds) + { + if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out string id) && !string.IsNullOrEmpty(id)) + { + // This check should ideally never be necessary but we're seeing some cases of this and haven't tracked them down yet. + if (!string.IsNullOrWhiteSpace(id)) + { + return true; + } + } + + return false; + } + + public static string GetOmdbUrl(string query, IApplicationHost appHost, CancellationToken cancellationToken) + { + const string url = "https://www.omdbapi.com?apikey=2c9d9507"; + + if (string.IsNullOrWhiteSpace(query)) + { + return url; + } + return url + "&" + query; + } + + private async Task<string> EnsureItemInfo(string imdbId, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(imdbId)) + { + throw new ArgumentNullException(nameof(imdbId)); + } + + var imdbParam = imdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? imdbId : "tt" + imdbId; + + var path = GetDataFilePath(imdbParam); + + var fileInfo = _fileSystem.GetFileSystemInfo(path); + + if (fileInfo.Exists) + { + // If it's recent or automatic updates are enabled, don't re-download + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 1) + { + return path; + } + } + + var url = GetOmdbUrl(string.Format("i={0}&plot=short&tomatoes=true&r=json", imdbParam), _appHost, cancellationToken); + + using (var response = await GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false)) + { + using (var stream = response.Content) + { + var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<RootObject>(stream).ConfigureAwait(false); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + _jsonSerializer.SerializeToFile(rootObject, path); + } + } + + return path; + } + + private async Task<string> EnsureSeasonInfo(string seriesImdbId, int seasonId, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(seriesImdbId)) + { + throw new ArgumentException("The series IMDb ID was null or whitespace.", nameof(seriesImdbId)); + } + + var imdbParam = seriesImdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? seriesImdbId : "tt" + seriesImdbId; + + var path = GetSeasonFilePath(imdbParam, seasonId); + + var fileInfo = _fileSystem.GetFileSystemInfo(path); + + if (fileInfo.Exists) + { + // If it's recent or automatic updates are enabled, don't re-download + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 1) + { + return path; + } + } + + var url = GetOmdbUrl(string.Format("i={0}&season={1}&detail=full", imdbParam, seasonId), _appHost, cancellationToken); + + using (var response = await GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false)) + { + using (var stream = response.Content) + { + var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<SeasonRootObject>(stream).ConfigureAwait(false); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + _jsonSerializer.SerializeToFile(rootObject, path); + } + } + + return path; + } + + public static Task<HttpResponseInfo> GetOmdbResponse(IHttpClient httpClient, string url, CancellationToken cancellationToken) + { + return httpClient.SendAsync(new HttpRequestOptions + { + Url = url, + CancellationToken = cancellationToken, + BufferContent = true, + EnableDefaultUserAgent = true + }, HttpMethod.Get); + } + + internal string GetDataFilePath(string imdbId) + { + if (string.IsNullOrEmpty(imdbId)) + { + throw new ArgumentNullException(nameof(imdbId)); + } + + var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb"); + + var filename = string.Format("{0}.json", imdbId); + + return Path.Combine(dataPath, filename); + } + + internal string GetSeasonFilePath(string imdbId, int seasonId) + { + if (string.IsNullOrEmpty(imdbId)) + { + throw new ArgumentNullException(nameof(imdbId)); + } + + var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb"); + + var filename = string.Format("{0}_season_{1}.json", imdbId, seasonId); + + return Path.Combine(dataPath, filename); + } + + private void ParseAdditionalMetadata<T>(MetadataResult<T> itemResult, RootObject result) + where T : BaseItem + { + var item = itemResult.Item; + + var isConfiguredForEnglish = IsConfiguredForEnglish(item) || _configurationManager.Configuration.EnableNewOmdbSupport; + + // Grab series genres because imdb data is better than tvdb. Leave movies alone + // But only do it if english is the preferred language because this data will not be localized + if (isConfiguredForEnglish && !string.IsNullOrWhiteSpace(result.Genre)) + { + item.Genres = Array.Empty<string>(); + + foreach (var genre in result.Genre + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(i => i.Trim()) + .Where(i => !string.IsNullOrWhiteSpace(i))) + { + item.AddGenre(genre); + } + } + + if (isConfiguredForEnglish) + { + // Omdb is currently english only, so for other languages skip this and let secondary providers fill it in + item.Overview = result.Plot; + } + + //if (!string.IsNullOrWhiteSpace(result.Director)) + //{ + // var person = new PersonInfo + // { + // Name = result.Director.Trim(), + // Type = PersonType.Director + // }; + + // itemResult.AddPerson(person); + //} + + //if (!string.IsNullOrWhiteSpace(result.Writer)) + //{ + // var person = new PersonInfo + // { + // Name = result.Director.Trim(), + // Type = PersonType.Writer + // }; + + // itemResult.AddPerson(person); + //} + + //if (!string.IsNullOrWhiteSpace(result.Actors)) + //{ + // var actorList = result.Actors.Split(','); + // foreach (var actor in actorList) + // { + // if (!string.IsNullOrWhiteSpace(actor)) + // { + // var person = new PersonInfo + // { + // Name = actor.Trim(), + // Type = PersonType.Actor + // }; + + // itemResult.AddPerson(person); + // } + // } + //} + } + + private bool IsConfiguredForEnglish(BaseItem item) + { + var lang = item.GetPreferredMetadataLanguage(); + + // The data isn't localized and so can only be used for english users + return string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase); + } + + internal class SeasonRootObject + { + public string Title { get; set; } + public string seriesID { get; set; } + public int Season { get; set; } + public int? totalSeasons { get; set; } + public RootObject[] Episodes { get; set; } + public string Response { get; set; } + } + + internal class RootObject + { + public string Title { get; set; } + public string Year { get; set; } + public string Rated { get; set; } + public string Released { get; set; } + public string Runtime { get; set; } + public string Genre { get; set; } + public string Director { get; set; } + public string Writer { get; set; } + public string Actors { get; set; } + public string Plot { get; set; } + public string Language { get; set; } + public string Country { get; set; } + public string Awards { get; set; } + public string Poster { get; set; } + public List<OmdbRating> Ratings { get; set; } + public string Metascore { get; set; } + public string imdbRating { get; set; } + public string imdbVotes { get; set; } + public string imdbID { get; set; } + public string Type { get; set; } + public string DVD { get; set; } + public string BoxOffice { get; set; } + public string Production { get; set; } + public string Website { get; set; } + public string Response { get; set; } + public int Episode { get; set; } + + public float? GetRottenTomatoScore() + { + if (Ratings != null) + { + var rating = Ratings.FirstOrDefault(i => string.Equals(i.Source, "Rotten Tomatoes", StringComparison.OrdinalIgnoreCase)); + if (rating != null && rating.Value != null) + { + var value = rating.Value.TrimEnd('%'); + if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var score)) + { + return score; + } + } + } + return null; + } + } + public class OmdbRating + { + public string Source { get; set; } + public string Value { get; set; } + } + } +} |
