From d16f68bb14588ba9869a5a74e8f71dfc4af2856a Mon Sep 17 00:00:00 2001 From: dkanada Date: Mon, 9 Mar 2020 23:36:02 +0900 Subject: move omdb providers --- MediaBrowser.Providers/Omdb/OmdbImageProvider.cs | 99 ---- MediaBrowser.Providers/Omdb/OmdbItemProvider.cs | 317 ------------- MediaBrowser.Providers/Omdb/OmdbProvider.cs | 521 --------------------- .../Plugins/Omdb/OmdbEpisodeProvider.cs | 79 ++++ .../Plugins/Omdb/OmdbImageProvider.cs | 99 ++++ .../Plugins/Omdb/OmdbItemProvider.cs | 317 +++++++++++++ .../Plugins/Omdb/OmdbProvider.cs | 521 +++++++++++++++++++++ .../TV/Omdb/OmdbEpisodeProvider.cs | 80 ---- 8 files changed, 1016 insertions(+), 1017 deletions(-) delete mode 100644 MediaBrowser.Providers/Omdb/OmdbImageProvider.cs delete mode 100644 MediaBrowser.Providers/Omdb/OmdbItemProvider.cs delete mode 100644 MediaBrowser.Providers/Omdb/OmdbProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs delete mode 100644 MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs diff --git a/MediaBrowser.Providers/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Omdb/OmdbImageProvider.cs deleted file mode 100644 index 1e0bcacf2..000000000 --- a/MediaBrowser.Providers/Omdb/OmdbImageProvider.cs +++ /dev/null @@ -1,99 +0,0 @@ -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.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 GetSupportedImages(BaseItem item) - { - return new List - { - ImageType.Primary - }; - } - - public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) - { - var imdbId = item.GetProviderId(MetadataProviders.Imdb); - - var list = new List(); - - 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 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/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs deleted file mode 100644 index 44b9dcca1..000000000 --- a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs +++ /dev/null @@ -1,317 +0,0 @@ -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.Omdb -{ - public class OmdbItemProvider : IRemoteMetadataProvider, - IRemoteMetadataProvider, IRemoteMetadataProvider, 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> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) - { - return GetSearchResults(searchInfo, "series", cancellationToken); - } - - public Task> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken) - { - return GetSearchResults(searchInfo, "movie", cancellationToken); - } - - public Task> GetSearchResults(ItemLookupInfo searchInfo, string type, CancellationToken cancellationToken) - { - return GetSearchResultsInternal(searchInfo, type, true, cancellationToken); - } - - private async Task> 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(); - - if (isSearch) - { - var searchResultList = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false); - if (searchResultList != null && searchResultList.Search != null) - { - resultList.AddRange(searchResultList.Search); - } - } - else - { - var result = await _jsonSerializer.DeserializeFromStreamAsync(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> GetMetadata(TrailerInfo info, CancellationToken cancellationToken) - { - return GetMovieResult(info, cancellationToken); - } - - public Task> GetSearchResults(TrailerInfo searchInfo, CancellationToken cancellationToken) - { - return GetSearchResults(searchInfo, "movie", cancellationToken); - } - - public string Name => "The Open Movie Database"; - - public async Task> GetMetadata(SeriesInfo info, CancellationToken cancellationToken) - { - var result = new MetadataResult - { - 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> GetMetadata(MovieInfo info, CancellationToken cancellationToken) - { - return GetMovieResult(info, cancellationToken); - } - - private async Task> GetMovieResult(ItemLookupInfo info, CancellationToken cancellationToken) - where T : BaseItem, new() - { - var result = new MetadataResult - { - 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 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 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 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 - { - /// - /// Gets or sets the results. - /// - /// The results. - public List Search { get; set; } - } - - } -} diff --git a/MediaBrowser.Providers/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Omdb/OmdbProvider.cs deleted file mode 100644 index fbf6ae135..000000000 --- a/MediaBrowser.Providers/Omdb/OmdbProvider.cs +++ /dev/null @@ -1,521 +0,0 @@ -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.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(MetadataResult 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 FetchEpisodeData(MetadataResult 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 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(resultString); - return result; - } - - internal async Task 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(resultString); - return result; - } - - internal static bool IsValidSeries(Dictionary 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 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(stream).ConfigureAwait(false); - Directory.CreateDirectory(Path.GetDirectoryName(path)); - _jsonSerializer.SerializeToFile(rootObject, path); - } - } - - return path; - } - - private async Task 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(stream).ConfigureAwait(false); - Directory.CreateDirectory(Path.GetDirectoryName(path)); - _jsonSerializer.SerializeToFile(rootObject, path); - } - } - - return path; - } - - public static Task 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(MetadataResult 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(); - - 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 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; } - } - } -} 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, + 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> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) + { + return _itemProvider.GetSearchResults(searchInfo, "episode", cancellationToken); + } + + public async Task> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken) + { + var result = new MetadataResult() + { + 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 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 GetSupportedImages(BaseItem item) + { + return new List + { + ImageType.Primary + }; + } + + public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + { + var imdbId = item.GetProviderId(MetadataProviders.Imdb); + + var list = new List(); + + 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 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, + IRemoteMetadataProvider, IRemoteMetadataProvider, 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> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) + { + return GetSearchResults(searchInfo, "series", cancellationToken); + } + + public Task> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken) + { + return GetSearchResults(searchInfo, "movie", cancellationToken); + } + + public Task> GetSearchResults(ItemLookupInfo searchInfo, string type, CancellationToken cancellationToken) + { + return GetSearchResultsInternal(searchInfo, type, true, cancellationToken); + } + + private async Task> 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(); + + if (isSearch) + { + var searchResultList = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false); + if (searchResultList != null && searchResultList.Search != null) + { + resultList.AddRange(searchResultList.Search); + } + } + else + { + var result = await _jsonSerializer.DeserializeFromStreamAsync(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> GetMetadata(TrailerInfo info, CancellationToken cancellationToken) + { + return GetMovieResult(info, cancellationToken); + } + + public Task> GetSearchResults(TrailerInfo searchInfo, CancellationToken cancellationToken) + { + return GetSearchResults(searchInfo, "movie", cancellationToken); + } + + public string Name => "The Open Movie Database"; + + public async Task> GetMetadata(SeriesInfo info, CancellationToken cancellationToken) + { + var result = new MetadataResult + { + 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> GetMetadata(MovieInfo info, CancellationToken cancellationToken) + { + return GetMovieResult(info, cancellationToken); + } + + private async Task> GetMovieResult(ItemLookupInfo info, CancellationToken cancellationToken) + where T : BaseItem, new() + { + var result = new MetadataResult + { + 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 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 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 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 + { + /// + /// Gets or sets the results. + /// + /// The results. + public List 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(MetadataResult 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 FetchEpisodeData(MetadataResult 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 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(resultString); + return result; + } + + internal async Task 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(resultString); + return result; + } + + internal static bool IsValidSeries(Dictionary 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 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(stream).ConfigureAwait(false); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + _jsonSerializer.SerializeToFile(rootObject, path); + } + } + + return path; + } + + private async Task 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(stream).ConfigureAwait(false); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + _jsonSerializer.SerializeToFile(rootObject, path); + } + } + + return path; + } + + public static Task 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(MetadataResult 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(); + + 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 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; } + } + } +} diff --git a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs deleted file mode 100644 index dee3030af..000000000 --- a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs +++ /dev/null @@ -1,80 +0,0 @@ -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 MediaBrowser.Providers.Omdb; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Providers.TV.Omdb -{ - public class OmdbEpisodeProvider : - IRemoteMetadataProvider, - 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> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) - { - return _itemProvider.GetSearchResults(searchInfo, "episode", cancellationToken); - } - - public async Task> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken) - { - var result = new MetadataResult() - { - 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 GetImageResponse(string url, CancellationToken cancellationToken) - { - return _itemProvider.GetImageResponse(url, cancellationToken); - } - } -} -- cgit v1.2.3