2021-10-26 09:49:01 -04:00
|
|
|
#nullable disable
|
|
|
|
|
2021-07-22 20:33:19 -04:00
|
|
|
#pragma warning disable CS159, SA1300
|
2020-06-19 14:24:13 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
using System;
|
2016-06-22 21:27:25 -04:00
|
|
|
using System.Collections.Generic;
|
2021-11-19 09:32:07 -05:00
|
|
|
using System.Diagnostics.CodeAnalysis;
|
2014-02-03 15:51:28 -05:00
|
|
|
using System.Globalization;
|
2016-06-03 17:46:39 -04:00
|
|
|
using System.IO;
|
2014-02-03 15:51:28 -05:00
|
|
|
using System.Linq;
|
2019-06-14 12:38:14 -04:00
|
|
|
using System.Net.Http;
|
2021-11-19 09:32:07 -05:00
|
|
|
using System.Net.Http.Json;
|
2020-12-23 07:06:29 -05:00
|
|
|
using System.Text.Json;
|
2014-02-03 15:51:28 -05:00
|
|
|
using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
2023-03-25 13:52:02 -04:00
|
|
|
using Jellyfin.Data.Enums;
|
2021-06-19 12:02:33 -04:00
|
|
|
using Jellyfin.Extensions.Json;
|
2020-08-31 13:05:21 -04:00
|
|
|
using MediaBrowser.Common.Net;
|
2019-01-13 14:26:31 -05:00
|
|
|
using MediaBrowser.Controller.Configuration;
|
|
|
|
using MediaBrowser.Controller.Entities;
|
|
|
|
using MediaBrowser.Controller.Providers;
|
|
|
|
using MediaBrowser.Model.Entities;
|
|
|
|
using MediaBrowser.Model.IO;
|
2014-02-03 15:51:28 -05:00
|
|
|
|
2020-03-09 10:36:02 -04:00
|
|
|
namespace MediaBrowser.Providers.Plugins.Omdb
|
2014-02-03 15:51:28 -05:00
|
|
|
{
|
2021-07-22 20:33:19 -04:00
|
|
|
/// <summary>Provider for OMDB service.</summary>
|
2014-02-03 15:51:28 -05:00
|
|
|
public class OmdbProvider
|
|
|
|
{
|
2016-06-03 20:23:44 -04:00
|
|
|
private readonly IFileSystem _fileSystem;
|
|
|
|
private readonly IServerConfigurationManager _configurationManager;
|
2020-08-17 15:10:02 -04:00
|
|
|
private readonly IHttpClientFactory _httpClientFactory;
|
2020-12-25 13:37:38 -05:00
|
|
|
private readonly JsonSerializerOptions _jsonOptions;
|
2014-02-03 15:51:28 -05:00
|
|
|
|
2021-07-22 20:33:19 -04:00
|
|
|
/// <summary>Initializes a new instance of the <see cref="OmdbProvider"/> class.</summary>
|
|
|
|
/// <param name="httpClientFactory">HttpClientFactory to use for calls to OMDB service.</param>
|
|
|
|
/// <param name="fileSystem">IFileSystem to use for store OMDB data.</param>
|
|
|
|
/// <param name="configurationManager">IServerConfigurationManager to use.</param>
|
2021-11-16 06:24:17 -05:00
|
|
|
public OmdbProvider(IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
|
2014-02-03 15:51:28 -05:00
|
|
|
{
|
2020-08-17 15:10:02 -04:00
|
|
|
_httpClientFactory = httpClientFactory;
|
2016-06-03 20:23:44 -04:00
|
|
|
_fileSystem = fileSystem;
|
|
|
|
_configurationManager = configurationManager;
|
2020-12-25 13:37:38 -05:00
|
|
|
|
2021-03-08 23:57:38 -05:00
|
|
|
_jsonOptions = new JsonSerializerOptions(JsonDefaults.Options);
|
2021-11-19 09:32:07 -05:00
|
|
|
// These converters need to take priority
|
|
|
|
_jsonOptions.Converters.Insert(0, new JsonOmdbNotAvailableStringConverter());
|
|
|
|
_jsonOptions.Converters.Insert(0, new JsonOmdbNotAvailableInt32Converter());
|
2014-02-03 15:51:28 -05:00
|
|
|
}
|
|
|
|
|
2021-07-22 20:33:19 -04:00
|
|
|
/// <summary>Fetches data from OMDB service.</summary>
|
|
|
|
/// <param name="itemResult">Metadata about media item.</param>
|
|
|
|
/// <param name="imdbId">IMDB ID for media.</param>
|
|
|
|
/// <param name="language">Media language.</param>
|
|
|
|
/// <param name="country">Country of origin.</param>
|
|
|
|
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
|
|
|
|
/// <typeparam name="T">The first generic type parameter.</typeparam>
|
|
|
|
/// <returns>Returns a Task object that can be awaited.</returns>
|
2016-07-01 16:03:36 -04:00
|
|
|
public async Task Fetch<T>(MetadataResult<T> itemResult, string imdbId, string language, string country, CancellationToken cancellationToken)
|
2016-11-15 14:42:43 -05:00
|
|
|
where T : BaseItem
|
2014-02-03 15:51:28 -05:00
|
|
|
{
|
2015-05-28 19:37:43 -04:00
|
|
|
if (string.IsNullOrWhiteSpace(imdbId))
|
|
|
|
{
|
2019-01-06 15:50:43 -05:00
|
|
|
throw new ArgumentNullException(nameof(imdbId));
|
2015-05-28 19:37:43 -04:00
|
|
|
}
|
|
|
|
|
2019-01-13 15:37:13 -05:00
|
|
|
var item = itemResult.Item;
|
2016-07-01 16:03:36 -04:00
|
|
|
|
2016-12-23 14:35:05 -05:00
|
|
|
var result = await GetRootObject(imdbId, cancellationToken).ConfigureAwait(false);
|
2014-02-03 15:51:28 -05:00
|
|
|
|
2021-11-19 09:32:07 -05:00
|
|
|
var isEnglishRequested = IsConfiguredForEnglish(item, language);
|
2020-11-18 08:46:14 -05:00
|
|
|
// Only take the name and rating if the user's language is set to English, since Omdb has no localization
|
2021-11-19 09:32:07 -05:00
|
|
|
if (isEnglishRequested)
|
2016-11-15 14:42:43 -05:00
|
|
|
{
|
|
|
|
item.Name = result.Title;
|
2015-08-19 12:43:23 -04:00
|
|
|
|
2016-11-15 14:42:43 -05:00
|
|
|
if (string.Equals(country, "us", StringComparison.OrdinalIgnoreCase))
|
|
|
|
{
|
|
|
|
item.OfficialRating = result.Rated;
|
2015-07-06 10:20:23 -04:00
|
|
|
}
|
2016-11-15 14:42:43 -05:00
|
|
|
}
|
2015-05-30 10:32:18 -04:00
|
|
|
|
2021-11-19 09:32:07 -05:00
|
|
|
if (TryParseYear(result.Year, out var year))
|
2016-11-15 14:42:43 -05:00
|
|
|
{
|
|
|
|
item.ProductionYear = year;
|
|
|
|
}
|
2015-05-30 10:32:18 -04:00
|
|
|
|
2017-04-12 13:09:12 -04:00
|
|
|
var tomatoScore = result.GetRottenTomatoScore();
|
2014-02-03 15:51:28 -05:00
|
|
|
|
2017-04-12 13:09:12 -04:00
|
|
|
if (tomatoScore.HasValue)
|
2016-10-17 12:35:29 -04:00
|
|
|
{
|
2017-04-12 13:09:12 -04:00
|
|
|
item.CriticRating = tomatoScore;
|
2016-10-17 12:35:29 -04:00
|
|
|
}
|
2014-02-03 15:51:28 -05:00
|
|
|
|
2016-11-15 14:42:43 -05:00
|
|
|
if (!string.IsNullOrEmpty(result.imdbVotes)
|
2021-09-26 10:14:36 -04:00
|
|
|
&& int.TryParse(result.imdbVotes, NumberStyles.Number, CultureInfo.InvariantCulture, out var voteCount)
|
2016-11-15 14:42:43 -05:00
|
|
|
&& voteCount >= 0)
|
|
|
|
{
|
2020-06-14 05:11:11 -04:00
|
|
|
// item.VoteCount = voteCount;
|
2016-11-15 14:42:43 -05:00
|
|
|
}
|
2014-02-03 15:51:28 -05:00
|
|
|
|
2023-02-19 10:52:29 -05:00
|
|
|
if (float.TryParse(result.imdbRating, CultureInfo.InvariantCulture, out var imdbRating)
|
2016-11-15 14:42:43 -05:00
|
|
|
&& imdbRating >= 0)
|
|
|
|
{
|
|
|
|
item.CommunityRating = imdbRating;
|
|
|
|
}
|
2014-02-03 15:51:28 -05:00
|
|
|
|
2020-05-31 02:15:34 -04:00
|
|
|
if (!string.IsNullOrEmpty(result.Website))
|
|
|
|
{
|
|
|
|
item.HomePageUrl = result.Website;
|
|
|
|
}
|
2014-02-03 15:51:28 -05:00
|
|
|
|
2016-11-15 14:42:43 -05:00
|
|
|
if (!string.IsNullOrWhiteSpace(result.imdbID))
|
|
|
|
{
|
2020-06-06 15:17:49 -04:00
|
|
|
item.SetProviderId(MetadataProvider.Imdb, result.imdbID);
|
2016-11-15 14:42:43 -05:00
|
|
|
}
|
2015-05-30 10:32:18 -04:00
|
|
|
|
2021-11-19 09:32:07 -05:00
|
|
|
ParseAdditionalMetadata(itemResult, result, isEnglishRequested);
|
2016-06-03 20:23:44 -04:00
|
|
|
}
|
|
|
|
|
2021-07-22 20:33:19 -04:00
|
|
|
/// <summary>Gets data about an episode.</summary>
|
|
|
|
/// <param name="itemResult">Metadata about episode.</param>
|
|
|
|
/// <param name="episodeNumber">Episode number.</param>
|
|
|
|
/// <param name="seasonNumber">Season number.</param>
|
|
|
|
/// <param name="episodeImdbId">Episode ID.</param>
|
|
|
|
/// <param name="seriesImdbId">Season ID.</param>
|
|
|
|
/// <param name="language">Episode language.</param>
|
|
|
|
/// <param name="country">Country of origin.</param>
|
|
|
|
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
|
|
|
|
/// <typeparam name="T">The first generic type parameter.</typeparam>
|
|
|
|
/// <returns>Whether operation was successful.</returns>
|
2017-05-03 17:53:33 -04:00
|
|
|
public async Task<bool> FetchEpisodeData<T>(MetadataResult<T> itemResult, int episodeNumber, int seasonNumber, string episodeImdbId, string seriesImdbId, string language, string country, CancellationToken cancellationToken)
|
2016-07-01 16:03:36 -04:00
|
|
|
where T : BaseItem
|
2016-06-22 21:27:25 -04:00
|
|
|
{
|
2017-05-03 17:53:33 -04:00
|
|
|
if (string.IsNullOrWhiteSpace(seriesImdbId))
|
2016-06-22 21:27:25 -04:00
|
|
|
{
|
2019-01-06 15:50:43 -05:00
|
|
|
throw new ArgumentNullException(nameof(seriesImdbId));
|
2016-06-22 21:27:25 -04:00
|
|
|
}
|
|
|
|
|
2019-01-13 15:37:13 -05:00
|
|
|
var item = itemResult.Item;
|
2016-07-01 16:03:36 -04:00
|
|
|
|
2017-05-03 17:53:33 -04:00
|
|
|
var seasonResult = await GetSeasonRootObject(seriesImdbId, seasonNumber, cancellationToken).ConfigureAwait(false);
|
2016-10-22 10:51:19 -04:00
|
|
|
|
2022-12-05 09:00:20 -05:00
|
|
|
if (seasonResult?.Episodes is null)
|
2016-10-22 10:51:19 -04:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2016-06-22 21:27:25 -04:00
|
|
|
|
|
|
|
RootObject result = null;
|
|
|
|
|
2017-05-03 17:53:33 -04:00
|
|
|
if (!string.IsNullOrWhiteSpace(episodeImdbId))
|
|
|
|
{
|
2020-05-31 02:15:34 -04:00
|
|
|
foreach (var episode in seasonResult.Episodes)
|
2017-05-03 17:53:33 -04:00
|
|
|
{
|
|
|
|
if (string.Equals(episodeImdbId, episode.imdbID, StringComparison.OrdinalIgnoreCase))
|
|
|
|
{
|
|
|
|
result = episode;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// finally, search by numbers
|
2022-12-05 09:00:20 -05:00
|
|
|
if (result is null)
|
2016-06-22 21:27:25 -04:00
|
|
|
{
|
2020-05-31 02:15:34 -04:00
|
|
|
foreach (var episode in seasonResult.Episodes)
|
2016-06-22 21:27:25 -04:00
|
|
|
{
|
2017-05-03 17:53:33 -04:00
|
|
|
if (episode.Episode == episodeNumber)
|
|
|
|
{
|
|
|
|
result = episode;
|
|
|
|
break;
|
|
|
|
}
|
2016-06-22 21:27:25 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-05 09:00:20 -05:00
|
|
|
if (result is null)
|
2016-06-22 21:27:25 -04:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-11-19 09:32:07 -05:00
|
|
|
var isEnglishRequested = IsConfiguredForEnglish(item, language);
|
2020-11-18 08:46:14 -05:00
|
|
|
// Only take the name and rating if the user's language is set to English, since Omdb has no localization
|
2021-11-19 09:32:07 -05:00
|
|
|
if (isEnglishRequested)
|
2016-06-22 21:27:25 -04:00
|
|
|
{
|
|
|
|
item.Name = result.Title;
|
|
|
|
|
|
|
|
if (string.Equals(country, "us", StringComparison.OrdinalIgnoreCase))
|
|
|
|
{
|
|
|
|
item.OfficialRating = result.Rated;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-19 09:32:07 -05:00
|
|
|
if (TryParseYear(result.Year, out var year))
|
2016-06-22 21:27:25 -04:00
|
|
|
{
|
|
|
|
item.ProductionYear = year;
|
|
|
|
}
|
|
|
|
|
2017-04-12 13:09:12 -04:00
|
|
|
var tomatoScore = result.GetRottenTomatoScore();
|
2016-06-22 21:27:25 -04:00
|
|
|
|
2017-04-12 13:09:12 -04:00
|
|
|
if (tomatoScore.HasValue)
|
2016-10-17 12:35:29 -04:00
|
|
|
{
|
2017-04-12 13:09:12 -04:00
|
|
|
item.CriticRating = tomatoScore;
|
2016-06-22 21:27:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(result.imdbVotes)
|
2021-09-26 10:14:36 -04:00
|
|
|
&& int.TryParse(result.imdbVotes, NumberStyles.Number, CultureInfo.InvariantCulture, out var voteCount)
|
2016-06-22 21:27:25 -04:00
|
|
|
&& voteCount >= 0)
|
|
|
|
{
|
2020-06-14 05:11:11 -04:00
|
|
|
// item.VoteCount = voteCount;
|
2016-06-22 21:27:25 -04:00
|
|
|
}
|
|
|
|
|
2023-02-19 10:52:29 -05:00
|
|
|
if (float.TryParse(result.imdbRating, CultureInfo.InvariantCulture, out var imdbRating)
|
2016-06-22 21:27:25 -04:00
|
|
|
&& imdbRating >= 0)
|
|
|
|
{
|
|
|
|
item.CommunityRating = imdbRating;
|
|
|
|
}
|
|
|
|
|
2020-05-31 02:15:34 -04:00
|
|
|
if (!string.IsNullOrEmpty(result.Website))
|
|
|
|
{
|
|
|
|
item.HomePageUrl = result.Website;
|
|
|
|
}
|
2016-06-22 21:27:25 -04:00
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(result.imdbID))
|
|
|
|
{
|
2020-06-06 15:17:49 -04:00
|
|
|
item.SetProviderId(MetadataProvider.Imdb, result.imdbID);
|
2016-06-22 21:27:25 -04:00
|
|
|
}
|
|
|
|
|
2021-11-19 09:32:07 -05:00
|
|
|
ParseAdditionalMetadata(itemResult, result, isEnglishRequested);
|
2016-06-22 21:27:25 -04:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-06-03 21:08:27 -04:00
|
|
|
internal async Task<RootObject> GetRootObject(string imdbId, CancellationToken cancellationToken)
|
|
|
|
{
|
2016-12-23 14:35:05 -05:00
|
|
|
var path = await EnsureItemInfo(imdbId, cancellationToken).ConfigureAwait(false);
|
2023-01-11 04:36:18 -05:00
|
|
|
var stream = AsyncFile.OpenRead(path);
|
|
|
|
await using (stream.ConfigureAwait(false))
|
|
|
|
{
|
|
|
|
return await JsonSerializer.DeserializeAsync<RootObject>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
|
|
|
}
|
2016-06-03 21:08:27 -04:00
|
|
|
}
|
|
|
|
|
2016-06-22 21:27:25 -04:00
|
|
|
internal async Task<SeasonRootObject> GetSeasonRootObject(string imdbId, int seasonId, CancellationToken cancellationToken)
|
|
|
|
{
|
2016-12-23 14:35:05 -05:00
|
|
|
var path = await EnsureSeasonInfo(imdbId, seasonId, cancellationToken).ConfigureAwait(false);
|
2023-01-11 04:36:18 -05:00
|
|
|
var stream = AsyncFile.OpenRead(path);
|
|
|
|
await using (stream.ConfigureAwait(false))
|
|
|
|
{
|
|
|
|
return await JsonSerializer.DeserializeAsync<SeasonRootObject>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
|
|
|
}
|
2016-06-22 21:27:25 -04:00
|
|
|
}
|
|
|
|
|
2021-07-22 20:33:19 -04:00
|
|
|
/// <summary>Gets OMDB URL.</summary>
|
|
|
|
/// <param name="query">Appends query string to URL.</param>
|
|
|
|
/// <returns>OMDB URL with optional query string.</returns>
|
2020-08-07 13:26:28 -04:00
|
|
|
public static string GetOmdbUrl(string query)
|
2017-02-13 16:03:41 -05:00
|
|
|
{
|
2020-08-07 13:26:28 -04:00
|
|
|
const string Url = "https://www.omdbapi.com?apikey=2c9d9507";
|
2018-09-12 13:26:21 -04:00
|
|
|
|
2019-01-03 13:51:18 -05:00
|
|
|
if (string.IsNullOrWhiteSpace(query))
|
2018-09-12 13:26:21 -04:00
|
|
|
{
|
2020-08-07 13:26:28 -04:00
|
|
|
return Url;
|
2018-09-12 13:26:21 -04:00
|
|
|
}
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2020-08-07 13:26:28 -04:00
|
|
|
return Url + "&" + query;
|
2017-02-13 16:03:41 -05:00
|
|
|
}
|
|
|
|
|
2021-11-19 09:32:07 -05:00
|
|
|
/// <summary>
|
|
|
|
/// Extract the year from a string.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="input">The input string.</param>
|
|
|
|
/// <param name="year">The year.</param>
|
|
|
|
/// <returns>A value indicating whether the input could successfully be parsed as a year.</returns>
|
|
|
|
public static bool TryParseYear(string input, [NotNullWhen(true)] out int? year)
|
|
|
|
{
|
|
|
|
if (string.IsNullOrEmpty(input))
|
|
|
|
{
|
|
|
|
year = 0;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (int.TryParse(input.AsSpan(0, 4), NumberStyles.Number, CultureInfo.InvariantCulture, out var result))
|
|
|
|
{
|
|
|
|
year = result;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
year = 0;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-06-03 21:08:27 -04:00
|
|
|
private async Task<string> EnsureItemInfo(string imdbId, CancellationToken cancellationToken)
|
2016-06-03 20:23:44 -04:00
|
|
|
{
|
|
|
|
if (string.IsNullOrWhiteSpace(imdbId))
|
|
|
|
{
|
2019-01-06 15:50:43 -05:00
|
|
|
throw new ArgumentNullException(nameof(imdbId));
|
2016-06-03 20:23:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2018-09-12 13:26:21 -04:00
|
|
|
if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 1)
|
2016-06-03 20:23:44 -04:00
|
|
|
{
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
}
|
2021-01-12 10:27:38 -05:00
|
|
|
else
|
|
|
|
{
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
|
|
|
}
|
2016-06-03 20:23:44 -04:00
|
|
|
|
2020-08-07 13:26:28 -04:00
|
|
|
var url = GetOmdbUrl(
|
|
|
|
string.Format(
|
|
|
|
CultureInfo.InvariantCulture,
|
|
|
|
"i={0}&plot=short&tomatoes=true&r=json",
|
|
|
|
imdbParam));
|
2016-06-03 20:23:44 -04:00
|
|
|
|
2021-11-19 09:32:07 -05:00
|
|
|
var rootObject = await _httpClientFactory.CreateClient(NamedClient.Default).GetFromJsonAsync<RootObject>(url, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
2023-01-11 04:36:18 -05:00
|
|
|
FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
|
|
|
|
await using (jsonFileStream.ConfigureAwait(false))
|
|
|
|
{
|
|
|
|
await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
|
|
|
}
|
2016-06-03 20:23:44 -04:00
|
|
|
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2016-06-22 21:27:25 -04:00
|
|
|
private async Task<string> EnsureSeasonInfo(string seriesImdbId, int seasonId, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
if (string.IsNullOrWhiteSpace(seriesImdbId))
|
|
|
|
{
|
2019-01-12 15:41:08 -05:00
|
|
|
throw new ArgumentException("The series IMDb ID was null or whitespace.", nameof(seriesImdbId));
|
2016-06-22 21:27:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2018-09-12 13:26:21 -04:00
|
|
|
if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 1)
|
2016-06-22 21:27:25 -04:00
|
|
|
{
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
}
|
2021-01-12 10:27:38 -05:00
|
|
|
else
|
|
|
|
{
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
|
|
|
}
|
2016-06-22 21:27:25 -04:00
|
|
|
|
2020-08-07 13:26:28 -04:00
|
|
|
var url = GetOmdbUrl(
|
|
|
|
string.Format(
|
|
|
|
CultureInfo.InvariantCulture,
|
|
|
|
"i={0}&season={1}&detail=full",
|
|
|
|
imdbParam,
|
|
|
|
seasonId));
|
2016-06-22 21:27:25 -04:00
|
|
|
|
2021-11-19 09:32:07 -05:00
|
|
|
var rootObject = await _httpClientFactory.CreateClient(NamedClient.Default).GetFromJsonAsync<SeasonRootObject>(url, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
2023-01-11 04:36:18 -05:00
|
|
|
FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
|
|
|
|
await using (jsonFileStream.ConfigureAwait(false))
|
|
|
|
{
|
|
|
|
await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
|
|
|
}
|
2016-06-22 21:27:25 -04:00
|
|
|
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2016-06-03 20:23:44 -04:00
|
|
|
internal string GetDataFilePath(string imdbId)
|
|
|
|
{
|
2022-10-13 13:08:00 -04:00
|
|
|
ArgumentException.ThrowIfNullOrEmpty(imdbId);
|
2016-06-03 20:23:44 -04:00
|
|
|
|
|
|
|
var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb");
|
|
|
|
|
2020-08-07 13:26:28 -04:00
|
|
|
var filename = string.Format(CultureInfo.InvariantCulture, "{0}.json", imdbId);
|
2016-06-03 20:23:44 -04:00
|
|
|
|
|
|
|
return Path.Combine(dataPath, filename);
|
2014-02-03 15:51:28 -05:00
|
|
|
}
|
|
|
|
|
2016-06-22 21:27:25 -04:00
|
|
|
internal string GetSeasonFilePath(string imdbId, int seasonId)
|
|
|
|
{
|
2022-10-13 13:08:00 -04:00
|
|
|
ArgumentException.ThrowIfNullOrEmpty(imdbId);
|
2016-06-22 21:27:25 -04:00
|
|
|
|
|
|
|
var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb");
|
|
|
|
|
2020-08-07 13:26:28 -04:00
|
|
|
var filename = string.Format(CultureInfo.InvariantCulture, "{0}_season_{1}.json", imdbId, seasonId);
|
2016-06-22 21:27:25 -04:00
|
|
|
|
|
|
|
return Path.Combine(dataPath, filename);
|
|
|
|
}
|
|
|
|
|
2021-11-19 09:32:07 -05:00
|
|
|
private static void ParseAdditionalMetadata<T>(MetadataResult<T> itemResult, RootObject result, bool isEnglishRequested)
|
2016-07-01 16:03:36 -04:00
|
|
|
where T : BaseItem
|
2014-02-03 15:51:28 -05:00
|
|
|
{
|
2019-01-13 15:37:13 -05:00
|
|
|
var item = itemResult.Item;
|
2016-07-01 16:03:36 -04:00
|
|
|
|
2020-05-31 02:15:34 -04:00
|
|
|
// Grab series genres because IMDb data is better than TVDB. Leave movies alone
|
2020-11-18 08:46:14 -05:00
|
|
|
// But only do it if English is the preferred language because this data will not be localized
|
2021-11-19 09:32:07 -05:00
|
|
|
if (isEnglishRequested && !string.IsNullOrWhiteSpace(result.Genre))
|
2014-02-03 15:51:28 -05:00
|
|
|
{
|
2018-09-12 13:26:21 -04:00
|
|
|
item.Genres = Array.Empty<string>();
|
2014-02-03 15:51:28 -05:00
|
|
|
|
2021-11-19 09:32:07 -05:00
|
|
|
foreach (var genre in result.Genre.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
2014-02-03 15:51:28 -05:00
|
|
|
{
|
|
|
|
item.AddGenre(genre);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-31 23:08:42 -04:00
|
|
|
item.Overview = result.Plot;
|
2016-07-01 16:03:36 -04:00
|
|
|
|
2020-05-31 02:15:34 -04:00
|
|
|
if (!Plugin.Instance.Configuration.CastAndCrew)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(result.Director))
|
|
|
|
{
|
|
|
|
var person = new PersonInfo
|
|
|
|
{
|
Sort embedded collections in Nfo files
Because the Nfo files emit the collections as they are in-memory, the
files are not stable in format, genres, tags, albums, people, etc. are emitted in random orders. Add ordering of the collections when emitting the Nfo files so the file remains stable (unchanged) when underlying media information doesn't change.
In the process of this, it became clear that most of the providers and probes don't trim the strings like people's names, genre names, etc. so did a pass of Trim cleanup too.
Specific ordering: (alphabetical/numeric ascending after trimming blanks and defaulting to zero for missing numbers)
BaseItem: Directors, Writers, Trailers (by Url), Production Locations, Genres, Studios, Tags, Custom Provider Data (by key), Linked Children (by Path>LibraryItemId), Backdrop Images (by path), Actors (by SortOrder>Name)
AlbumNfo: Artists, Album Artists, Tracks (by ParentIndexNumber>IndexNumber>Name)
ArtistNfo: Albums (by Production Year>SortName>Name)
MovieNfo: Artists
Fix Debug build lint
Fix CI debug build lint issue.
Fix review issues
Fixed debug-build lint issues.
Emits the `disc` number to NFO for tracks with a non-zero ParentIndexNumber and only emit `position` if non-zero.
Removed the exception filtering I put in for testing.
Don't emit actors for MusicAlbums or MusicArtists
Swap from String.Trimmed() to ?.Trim()
Addressing PR feedback
Can't use ReadOnlySpan in an async method
Removed now-unused namespace
2023-05-15 01:38:27 -04:00
|
|
|
Name = result.Director.Trim(),
|
2023-03-25 13:52:02 -04:00
|
|
|
Type = PersonKind.Director
|
2020-05-31 02:15:34 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
itemResult.AddPerson(person);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(result.Writer))
|
|
|
|
{
|
|
|
|
var person = new PersonInfo
|
|
|
|
{
|
Sort embedded collections in Nfo files
Because the Nfo files emit the collections as they are in-memory, the
files are not stable in format, genres, tags, albums, people, etc. are emitted in random orders. Add ordering of the collections when emitting the Nfo files so the file remains stable (unchanged) when underlying media information doesn't change.
In the process of this, it became clear that most of the providers and probes don't trim the strings like people's names, genre names, etc. so did a pass of Trim cleanup too.
Specific ordering: (alphabetical/numeric ascending after trimming blanks and defaulting to zero for missing numbers)
BaseItem: Directors, Writers, Trailers (by Url), Production Locations, Genres, Studios, Tags, Custom Provider Data (by key), Linked Children (by Path>LibraryItemId), Backdrop Images (by path), Actors (by SortOrder>Name)
AlbumNfo: Artists, Album Artists, Tracks (by ParentIndexNumber>IndexNumber>Name)
ArtistNfo: Albums (by Production Year>SortName>Name)
MovieNfo: Artists
Fix Debug build lint
Fix CI debug build lint issue.
Fix review issues
Fixed debug-build lint issues.
Emits the `disc` number to NFO for tracks with a non-zero ParentIndexNumber and only emit `position` if non-zero.
Removed the exception filtering I put in for testing.
Don't emit actors for MusicAlbums or MusicArtists
Swap from String.Trimmed() to ?.Trim()
Addressing PR feedback
Can't use ReadOnlySpan in an async method
Removed now-unused namespace
2023-05-15 01:38:27 -04:00
|
|
|
Name = result.Writer.Trim(),
|
2023-03-25 13:52:02 -04:00
|
|
|
Type = PersonKind.Writer
|
2020-05-31 02:15:34 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
itemResult.AddPerson(person);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(result.Actors))
|
|
|
|
{
|
2021-11-19 09:32:07 -05:00
|
|
|
var actorList = result.Actors.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
2020-05-31 02:15:34 -04:00
|
|
|
foreach (var actor in actorList)
|
|
|
|
{
|
2021-11-19 09:32:07 -05:00
|
|
|
if (string.IsNullOrWhiteSpace(actor))
|
2020-05-31 02:15:34 -04:00
|
|
|
{
|
2021-11-19 09:32:07 -05:00
|
|
|
continue;
|
2020-05-31 02:15:34 -04:00
|
|
|
}
|
2021-11-19 09:32:07 -05:00
|
|
|
|
|
|
|
var person = new PersonInfo
|
|
|
|
{
|
Sort embedded collections in Nfo files
Because the Nfo files emit the collections as they are in-memory, the
files are not stable in format, genres, tags, albums, people, etc. are emitted in random orders. Add ordering of the collections when emitting the Nfo files so the file remains stable (unchanged) when underlying media information doesn't change.
In the process of this, it became clear that most of the providers and probes don't trim the strings like people's names, genre names, etc. so did a pass of Trim cleanup too.
Specific ordering: (alphabetical/numeric ascending after trimming blanks and defaulting to zero for missing numbers)
BaseItem: Directors, Writers, Trailers (by Url), Production Locations, Genres, Studios, Tags, Custom Provider Data (by key), Linked Children (by Path>LibraryItemId), Backdrop Images (by path), Actors (by SortOrder>Name)
AlbumNfo: Artists, Album Artists, Tracks (by ParentIndexNumber>IndexNumber>Name)
ArtistNfo: Albums (by Production Year>SortName>Name)
MovieNfo: Artists
Fix Debug build lint
Fix CI debug build lint issue.
Fix review issues
Fixed debug-build lint issues.
Emits the `disc` number to NFO for tracks with a non-zero ParentIndexNumber and only emit `position` if non-zero.
Removed the exception filtering I put in for testing.
Don't emit actors for MusicAlbums or MusicArtists
Swap from String.Trimmed() to ?.Trim()
Addressing PR feedback
Can't use ReadOnlySpan in an async method
Removed now-unused namespace
2023-05-15 01:38:27 -04:00
|
|
|
Name = actor.Trim(),
|
2023-03-25 13:52:02 -04:00
|
|
|
Type = PersonKind.Actor
|
2021-11-19 09:32:07 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
itemResult.AddPerson(person);
|
2020-05-31 02:15:34 -04:00
|
|
|
}
|
|
|
|
}
|
2014-02-03 15:51:28 -05:00
|
|
|
}
|
|
|
|
|
2021-11-19 09:32:07 -05:00
|
|
|
private static bool IsConfiguredForEnglish(BaseItem item, string language)
|
2014-02-03 15:51:28 -05:00
|
|
|
{
|
2021-11-19 09:32:07 -05:00
|
|
|
if (string.IsNullOrEmpty(language))
|
|
|
|
{
|
|
|
|
language = item.GetPreferredMetadataLanguage();
|
|
|
|
}
|
2014-02-03 15:51:28 -05:00
|
|
|
|
2020-11-18 08:46:14 -05:00
|
|
|
// The data isn't localized and so can only be used for English users
|
2021-11-19 09:32:07 -05:00
|
|
|
return string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
|
2014-02-03 15:51:28 -05:00
|
|
|
}
|
|
|
|
|
2020-12-28 17:41:46 -05:00
|
|
|
internal class SeasonRootObject
|
2016-06-22 21:27:25 -04:00
|
|
|
{
|
|
|
|
public string Title { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2016-06-22 21:27:25 -04:00
|
|
|
public string seriesID { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2020-12-24 10:10:30 -05:00
|
|
|
public int? Season { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2016-06-22 21:27:25 -04:00
|
|
|
public int? totalSeasons { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2016-06-22 21:27:25 -04:00
|
|
|
public RootObject[] Episodes { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2016-06-22 21:27:25 -04:00
|
|
|
public string Response { get; set; }
|
|
|
|
}
|
|
|
|
|
2020-12-28 17:41:46 -05:00
|
|
|
internal class RootObject
|
2014-02-03 15:51:28 -05:00
|
|
|
{
|
|
|
|
public string Title { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string Year { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string Rated { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string Released { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string Runtime { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string Genre { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string Director { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string Writer { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string Actors { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string Plot { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2017-04-12 13:09:12 -04:00
|
|
|
public string Language { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2017-04-12 13:09:12 -04:00
|
|
|
public string Country { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2017-04-12 13:09:12 -04:00
|
|
|
public string Awards { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string Poster { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2017-04-12 13:09:12 -04:00
|
|
|
public List<OmdbRating> Ratings { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2017-04-12 13:09:12 -04:00
|
|
|
public string Metascore { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string imdbRating { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string imdbVotes { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string imdbID { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string Type { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string DVD { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string BoxOffice { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string Production { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string Website { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2014-02-03 15:51:28 -05:00
|
|
|
public string Response { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2020-12-24 10:10:30 -05:00
|
|
|
public int? Episode { get; set; }
|
2014-02-03 15:51:28 -05:00
|
|
|
|
2017-04-12 13:09:12 -04:00
|
|
|
public float? GetRottenTomatoScore()
|
|
|
|
{
|
2022-12-05 09:01:13 -05:00
|
|
|
if (Ratings is not null)
|
2017-04-12 13:09:12 -04:00
|
|
|
{
|
|
|
|
var rating = Ratings.FirstOrDefault(i => string.Equals(i.Source, "Rotten Tomatoes", StringComparison.OrdinalIgnoreCase));
|
2022-12-05 09:01:13 -05:00
|
|
|
if (rating?.Value is not null)
|
2017-04-12 13:09:12 -04:00
|
|
|
{
|
|
|
|
var value = rating.Value.TrimEnd('%');
|
2023-02-19 10:52:29 -05:00
|
|
|
if (float.TryParse(value, CultureInfo.InvariantCulture, out var score))
|
2017-04-12 13:09:12 -04:00
|
|
|
{
|
|
|
|
return score;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2017-04-12 13:09:12 -04:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2021-07-22 23:16:38 -04:00
|
|
|
#pragma warning disable CA1034
|
2021-07-22 20:33:19 -04:00
|
|
|
/// <summary>Describes OMDB rating.</summary>
|
2017-04-12 13:09:12 -04:00
|
|
|
public class OmdbRating
|
|
|
|
{
|
2021-07-22 20:33:19 -04:00
|
|
|
/// <summary>Gets or sets rating source.</summary>
|
2017-04-12 13:09:12 -04:00
|
|
|
public string Source { get; set; }
|
2020-05-31 02:15:34 -04:00
|
|
|
|
2021-07-22 20:33:19 -04:00
|
|
|
/// <summary>Gets or sets rating value.</summary>
|
2017-04-12 13:09:12 -04:00
|
|
|
public string Value { get; set; }
|
2014-02-03 15:51:28 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|