From dbe545628f3e6124f55ea17bad54d5bb3caf99e4 Mon Sep 17 00:00:00 2001 From: Andre Mainka Date: Thu, 29 Feb 2024 14:53:46 +0100 Subject: [PATCH] Make Tvdb Client usage more resilient and robust (#112) * catch JsonException * refactor duplicate code and catch JsonException --- Jellyfin.Plugin.Tvdb/CollectionExtensions.cs | 24 +++ .../Providers/TvdbEpisodeImageProvider.cs | 24 +-- .../Providers/TvdbSeasonImageProvider.cs | 199 +++++++++--------- .../Providers/TvdbSeriesImageProvider.cs | 185 ++++++++-------- .../Providers/TvdbSeriesProvider.cs | 21 +- Jellyfin.Plugin.Tvdb/TvdbSdkExtensions.cs | 144 ++++++++++++- Jellyfin.Plugin.Tvdb/TvdbUtils.cs | 82 -------- 7 files changed, 372 insertions(+), 307 deletions(-) create mode 100644 Jellyfin.Plugin.Tvdb/CollectionExtensions.cs diff --git a/Jellyfin.Plugin.Tvdb/CollectionExtensions.cs b/Jellyfin.Plugin.Tvdb/CollectionExtensions.cs new file mode 100644 index 0000000..6ba6970 --- /dev/null +++ b/Jellyfin.Plugin.Tvdb/CollectionExtensions.cs @@ -0,0 +1,24 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Jellyfin.Plugin.Tvdb; + +internal static class CollectionExtensions +{ + /// + /// Adds the if it is not . + /// + /// Data type of the collection items. + /// Input . + /// Item to add. + /// The input collection. + public static ICollection AddIfNotNull(this ICollection collection, T? item) + { + if (item != null) + { + collection.Add(item); + } + + return collection; + } +} diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbEpisodeImageProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbEpisodeImageProvider.cs index a7563f2..878738d 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbEpisodeImageProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbEpisodeImageProvider.cs @@ -4,14 +4,15 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; + using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; + using Microsoft.Extensions.Logging; -using Tvdb.Sdk; namespace Jellyfin.Plugin.Tvdb.Providers { @@ -94,11 +95,7 @@ await _tvdbClientManager .GetEpisodesAsync(Convert.ToInt32(episodeTvdbId, CultureInfo.InvariantCulture), language, cancellationToken) .ConfigureAwait(false); - var image = GetImageInfo(episodeResult); - if (image != null) - { - imageResult.Add(image); - } + imageResult.AddIfNotNull(episodeResult.CreateImageInfo(Name)); } catch (Exception e) { @@ -114,20 +111,5 @@ public Task GetImageResponse(string url, CancellationToken { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(new Uri(url), cancellationToken); } - - private RemoteImageInfo? GetImageInfo(EpisodeExtendedRecord episode) - { - if (string.IsNullOrEmpty(episode.Image)) - { - return null; - } - - return new RemoteImageInfo - { - ProviderName = Name, - Url = episode.Image, - Type = ImageType.Primary - }; - } } } diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbSeasonImageProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbSeasonImageProvider.cs index d5a86db..7a04702 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbSeasonImageProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbSeasonImageProvider.cs @@ -3,135 +3,132 @@ using System.Globalization; using System.Linq; using System.Net.Http; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; + using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Providers; + using Microsoft.Extensions.Logging; + using Tvdb.Sdk; -using RatingType = MediaBrowser.Model.Dto.RatingType; -namespace Jellyfin.Plugin.Tvdb.Providers +namespace Jellyfin.Plugin.Tvdb.Providers; + +/// +/// Tvdb season image provider. +/// +public class TvdbSeasonImageProvider : IRemoteImageProvider { + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private readonly TvdbClientManager _tvdbClientManager; + /// - /// Tvdb season image provider. + /// Initializes a new instance of the class. /// - public class TvdbSeasonImageProvider : IRemoteImageProvider + /// Instance of the interface. + /// Instance of the interface. + /// Instance of . + public TvdbSeasonImageProvider( + IHttpClientFactory httpClientFactory, + ILogger logger, + TvdbClientManager tvdbClientManager) { - private readonly IHttpClientFactory _httpClientFactory; - private readonly ILogger _logger; - private readonly TvdbClientManager _tvdbClientManager; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// Instance of the interface. - /// Instance of . - public TvdbSeasonImageProvider(IHttpClientFactory httpClientFactory, ILogger logger, TvdbClientManager tvdbClientManager) - { - _httpClientFactory = httpClientFactory; - _logger = logger; - _tvdbClientManager = tvdbClientManager; - } + _httpClientFactory = httpClientFactory; + _logger = logger; + _tvdbClientManager = tvdbClientManager; + } + + /// + public string Name => TvdbPlugin.ProviderName; + + /// + public bool Supports(BaseItem item) + { + return item is Season; + } + + /// + public IEnumerable GetSupportedImages(BaseItem item) + { + yield return ImageType.Primary; + yield return ImageType.Banner; + yield return ImageType.Backdrop; + } - /// - public string Name => TvdbPlugin.ProviderName; + /// + public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + { + var season = (Season)item; + var series = season.Series; - /// - public bool Supports(BaseItem item) + if (series == null || !season.IndexNumber.HasValue || !TvdbSeriesProvider.IsValidSeries(series.ProviderIds)) { - return item is Season; + return Enumerable.Empty(); } - /// - public IEnumerable GetSupportedImages(BaseItem item) + var languages = await _tvdbClientManager.GetLanguagesAsync(cancellationToken) + .ConfigureAwait(false); + var languageLookup = languages + .ToDictionary(l => l.Id, StringComparer.OrdinalIgnoreCase); + + var artworkTypes = await _tvdbClientManager.GetArtworkTypeAsync(cancellationToken) + .ConfigureAwait(false); + var seasonArtworkTypeLookup = artworkTypes + .Where(t => string.Equals(t.RecordType, "season", StringComparison.OrdinalIgnoreCase)) + .ToDictionary(t => t.Id); + + var seriesTvdbId = Convert.ToInt32(series.GetProviderId(TvdbPlugin.ProviderId), CultureInfo.InvariantCulture); + var seasonNumber = season.IndexNumber.Value; + + var seasonArtworks = await GetSeasonArtworks(seriesTvdbId, seasonNumber, cancellationToken) + .ConfigureAwait(false); + + var remoteImages = new List(); + foreach (var artwork in seasonArtworks) { - yield return ImageType.Primary; - yield return ImageType.Banner; - yield return ImageType.Backdrop; + var artworkType = seasonArtworkTypeLookup.GetValueOrDefault(artwork.Type); + var imageType = artworkType.GetImageType(); + var artworkLanguage = artwork.Language is null ? null : languageLookup.GetValueOrDefault(artwork.Language); + + // only add if valid RemoteImageInfo + remoteImages.AddIfNotNull(artwork.CreateImageInfo(Name, imageType, artworkLanguage)); } - /// - public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + return remoteImages.OrderByLanguageDescending(item.GetPreferredMetadataLanguage()); + } + + private async Task> GetSeasonArtworks(int seriesTvdbId, int seasonNumber, CancellationToken cancellationToken) + { + try { - var season = (Season)item; - var series = season.Series; - - if (series == null || !season.IndexNumber.HasValue || !TvdbSeriesProvider.IsValidSeries(series.ProviderIds)) - { - return Enumerable.Empty(); - } - - var tvdbId = Convert.ToInt32(series.GetProviderId(TvdbPlugin.ProviderId), CultureInfo.InvariantCulture); - var seasonNumber = season.IndexNumber.Value; - var language = item.GetPreferredMetadataLanguage(); - var remoteImages = new List(); - var seriesInfo = await _tvdbClientManager.GetSeriesExtendedByIdAsync(tvdbId, language, cancellationToken, small: true).ConfigureAwait(false); + var seriesInfo = await _tvdbClientManager.GetSeriesExtendedByIdAsync(seriesTvdbId, string.Empty, cancellationToken, small: true) + .ConfigureAwait(false); var seasonTvdbId = seriesInfo.Seasons.FirstOrDefault(s => s.Number == seasonNumber)?.Id; - var seasonInfo = await _tvdbClientManager.GetSeasonByIdAsync(Convert.ToInt32(seasonTvdbId, CultureInfo.InvariantCulture), language, cancellationToken).ConfigureAwait(false); - var seasonImages = seasonInfo.Artwork; - var languages = _tvdbClientManager.GetLanguagesAsync(CancellationToken.None).Result; - var artworkTypes = _tvdbClientManager.GetArtworkTypeAsync(CancellationToken.None).Result; - - foreach (var image in seasonImages) - { - ImageType type; - // Checks if valid image type, if not, skip - try - { - type = TvdbUtils.GetImageTypeFromKeyType(artworkTypes.FirstOrDefault(x => x.Id == image.Type && string.Equals(x.RecordType, "season", StringComparison.OrdinalIgnoreCase))?.Name); - } - catch (Exception) - { - continue; - } - - var imageInfo = new RemoteImageInfo - { - RatingType = RatingType.Score, - Url = image.Image, - Width = Convert.ToInt32(image.Width, CultureInfo.InvariantCulture), - Height = Convert.ToInt32(image.Height, CultureInfo.InvariantCulture), - Type = type, - ProviderName = Name, - ThumbnailUrl = image.Thumbnail - }; - - // Tvdb uses 3 letter code for language (prob ISO 639-2) - var artworkLanguage = languages.FirstOrDefault(lang => string.Equals(lang.Id, image.Language, StringComparison.OrdinalIgnoreCase))?.Id; - if (!string.IsNullOrEmpty(artworkLanguage)) - { - imageInfo.Language = TvdbUtils.NormalizeLanguageToJellyfin(artworkLanguage)?.ToLowerInvariant(); - } - - remoteImages.Add(imageInfo); - } - - return remoteImages.OrderByDescending(i => - { - if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase)) - { - return 2; - } - else if (!string.IsNullOrEmpty(i.Language)) - { - return 1; - } - - return 0; - }); + var seasonInfo = await _tvdbClientManager.GetSeasonByIdAsync(seasonTvdbId ?? 0, string.Empty, cancellationToken) + .ConfigureAwait(false); + return seasonInfo.Artwork; } - - /// - public Task GetImageResponse(string url, CancellationToken cancellationToken) + catch (Exception ex) when ( + (ex is SeriesException seriesEx && seriesEx.InnerException is JsonException) + || (ex is SeasonsException seasonEx && seasonEx.InnerException is JsonException)) { - return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(new Uri(url), cancellationToken); + _logger.LogError(ex, "Failed to retrieve season images for series {TvDbId}", seriesTvdbId); + return Array.Empty(); } } + + /// + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(new Uri(url), cancellationToken); + } } diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesImageProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesImageProvider.cs index 6565df1..1ffa445 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesImageProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesImageProvider.cs @@ -3,126 +3,121 @@ using System.Globalization; using System.Linq; using System.Net.Http; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; + using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Providers; + using Microsoft.Extensions.Logging; + using Tvdb.Sdk; -using RatingType = MediaBrowser.Model.Dto.RatingType; -using Series = MediaBrowser.Controller.Entities.TV.Series; -namespace Jellyfin.Plugin.Tvdb.Providers +namespace Jellyfin.Plugin.Tvdb.Providers; + +/// +/// Tvdb series image provider. +/// +public class TvdbSeriesImageProvider : IRemoteImageProvider { + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private readonly TvdbClientManager _tvdbClientManager; + /// - /// Tvdb series image provider. + /// Initializes a new instance of the class. /// - public class TvdbSeriesImageProvider : IRemoteImageProvider + /// Instance of the interface. + /// Instance of the interface. + /// Instance of . + public TvdbSeriesImageProvider( + IHttpClientFactory httpClientFactory, + ILogger logger, + TvdbClientManager tvdbClientManager) { - private readonly IHttpClientFactory _httpClientFactory; - private readonly ILogger _logger; - private readonly TvdbClientManager _tvdbClientManager; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// Instance of the interface. - /// Instance of . - public TvdbSeriesImageProvider(IHttpClientFactory httpClientFactory, ILogger logger, TvdbClientManager tvdbClientManager) - { - _httpClientFactory = httpClientFactory; - _logger = logger; - _tvdbClientManager = tvdbClientManager; - } + _httpClientFactory = httpClientFactory; + _logger = logger; + _tvdbClientManager = tvdbClientManager; + } + + /// + public string Name => TvdbPlugin.ProviderName; + + /// + public bool Supports(BaseItem item) + { + return item is Series; + } - /// - public string Name => TvdbPlugin.ProviderName; + /// + public IEnumerable GetSupportedImages(BaseItem item) + { + yield return ImageType.Primary; + yield return ImageType.Banner; + yield return ImageType.Backdrop; + } - /// - public bool Supports(BaseItem item) + /// + public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + { + if (!TvdbSeriesProvider.IsValidSeries(item.ProviderIds)) { - return item is Series; + return Enumerable.Empty(); } - /// - public IEnumerable GetSupportedImages(BaseItem item) + var languages = await _tvdbClientManager.GetLanguagesAsync(cancellationToken) + .ConfigureAwait(false); + var languageLookup = languages + .ToDictionary(l => l.Id, StringComparer.OrdinalIgnoreCase); + + var artworkTypes = await _tvdbClientManager.GetArtworkTypeAsync(cancellationToken) + .ConfigureAwait(false); + var seriesArtworkTypeLookup = artworkTypes + .Where(t => string.Equals(t.RecordType, "series", StringComparison.OrdinalIgnoreCase)) + .ToDictionary(t => t.Id); + + var seriesTvdbId = Convert.ToInt32(item.GetProviderId(TvdbPlugin.ProviderId), CultureInfo.InvariantCulture); + var seriesArtworks = await GetSeriesArtworks(seriesTvdbId, cancellationToken) + .ConfigureAwait(false); + + var remoteImages = new List(); + foreach (var artwork in seriesArtworks) { - yield return ImageType.Primary; - yield return ImageType.Banner; - yield return ImageType.Backdrop; + var artworkType = seriesArtworkTypeLookup.GetValueOrDefault(artwork.Type); + var imageType = artworkType.GetImageType(); + var artworkLanguage = artwork.Language is null ? null : languageLookup.GetValueOrDefault(artwork.Language); + + // only add if valid RemoteImageInfo + remoteImages.AddIfNotNull(artwork.CreateImageInfo(Name, imageType, artworkLanguage)); } - /// - public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + return remoteImages.OrderByLanguageDescending(item.GetPreferredMetadataLanguage()); + } + + private async Task> GetSeriesArtworks(int seriesTvdbId, CancellationToken cancellationToken) + { + try { - if (!TvdbSeriesProvider.IsValidSeries(item.ProviderIds)) - { - return Enumerable.Empty(); - } - - var language = item.GetPreferredMetadataLanguage(); - var remoteImages = new List(); - var tvdbId = Convert.ToInt32(item.GetProviderId(TvdbPlugin.ProviderId), CultureInfo.InvariantCulture); - var seriesInfo = await _tvdbClientManager.GetSeriesImagesAsync(tvdbId, language, cancellationToken).ConfigureAwait(false); - var seriesImages = seriesInfo.Artworks; - var languages = _tvdbClientManager.GetLanguagesAsync(CancellationToken.None).Result; - var artworkTypes = _tvdbClientManager.GetArtworkTypeAsync(CancellationToken.None).Result; - foreach (var image in seriesImages) - { - ImageType type; - // Checks if valid image type, if not, skip - try - { - type = TvdbUtils.GetImageTypeFromKeyType(artworkTypes.FirstOrDefault(x => x.Id == image.Type && string.Equals(x.RecordType, "series", StringComparison.OrdinalIgnoreCase))?.Name); - } - catch (Exception) - { - continue; - } - - var imageInfo = new RemoteImageInfo - { - RatingType = RatingType.Score, - Url = image.Image, - Width = Convert.ToInt32(image.Width, CultureInfo.InvariantCulture), - Height = Convert.ToInt32(image.Height, CultureInfo.InvariantCulture), - Type = type, - ProviderName = Name, - ThumbnailUrl = image.Thumbnail - }; - // TVDb uses 3 character language - var imageLanguage = languages.FirstOrDefault(lang => string.Equals(lang.Id, image.Language, StringComparison.OrdinalIgnoreCase))?.Id; - if (!string.IsNullOrEmpty(imageLanguage)) - { - imageInfo.Language = TvdbUtils.NormalizeLanguageToJellyfin(imageLanguage)?.ToLowerInvariant(); - } - - remoteImages.Add(imageInfo); - } - - return remoteImages.OrderByDescending(i => - { - if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase)) - { - return 2; - } - else if (!string.IsNullOrEmpty(i.Language)) - { - return 1; - } - - return 0; - }); + var seriesInfo = await _tvdbClientManager.GetSeriesImagesAsync(seriesTvdbId, string.Empty, cancellationToken) + .ConfigureAwait(false); + return seriesInfo.Artworks; } - - /// - public Task GetImageResponse(string url, CancellationToken cancellationToken) + catch (SeriesException ex) when (ex.InnerException is JsonException) { - return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(new Uri(url), cancellationToken); + _logger.LogError(ex, "Failed to retrieve series images for {TvDbId}", seriesTvdbId); + return Array.Empty(); } } + + /// + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(new Uri(url), cancellationToken); + } } diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesProvider.cs index 286da3f..4bab013 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesProvider.cs @@ -4,18 +4,19 @@ using System.Linq; using System.Net.Http; using System.Text; +using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using Microsoft.Extensions.Logging; using Tvdb.Sdk; -using Series = MediaBrowser.Controller.Entities.TV.Series; namespace Jellyfin.Plugin.Tvdb.Providers { @@ -266,15 +267,23 @@ await _tvdbClientManager } } - private async Task GetSeriesByRemoteId(string id, string language, string seriesName, CancellationToken cancellationToken) + private async Task GetSeriesByRemoteId(string remoteId, string language, string seriesName, CancellationToken cancellationToken) { - var result = await _tvdbClientManager.GetSeriesByRemoteIdAsync(id, language, cancellationToken) - .ConfigureAwait(false); - var resultData = result; + IReadOnlyList resultData; + try + { + resultData = await _tvdbClientManager.GetSeriesByRemoteIdAsync(remoteId, language, cancellationToken) + .ConfigureAwait(false); + } + catch (SearchException ex) when (ex.InnerException is JsonException) + { + _logger.LogError(ex, "Failed to retrieve series with {RemoteId}", remoteId); + return null; + } if (resultData is null || resultData.Count == 0 || resultData[0] is null || resultData[0].Series is null || resultData[0].Series.Id.HasValue == false) { - _logger.LogWarning("TvdbSearch: No series found for id: {0}", id); + _logger.LogWarning("TvdbSearch: No series found for remote id: {RemoteId}", remoteId); return null; } diff --git a/Jellyfin.Plugin.Tvdb/TvdbSdkExtensions.cs b/Jellyfin.Plugin.Tvdb/TvdbSdkExtensions.cs index 6a44b05..75f5fa9 100644 --- a/Jellyfin.Plugin.Tvdb/TvdbSdkExtensions.cs +++ b/Jellyfin.Plugin.Tvdb/TvdbSdkExtensions.cs @@ -1,7 +1,13 @@ +using System; +using System.Globalization; using System.Linq; using Jellyfin.Extensions; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + using Tvdb.Sdk; namespace Jellyfin.Plugin.Tvdb; @@ -21,7 +27,7 @@ public static class TvdbSdkExtensions { return translations? .NameTranslations? - .FirstOrDefault(translation => TvdbUtils.MatchLanguage(language, translation.Language))? + .FirstOrDefault(translation => IsMatch(translation, language))? .Name; } @@ -35,7 +41,141 @@ public static class TvdbSdkExtensions { return translations? .OverviewTranslations? - .FirstOrDefault(translation => TvdbUtils.MatchLanguage(language, translation.Language))? + .FirstOrDefault(translation => IsMatch(translation, language))? .Overview; } + + private static bool IsMatch(this Translation translation, string? language) + { + if (string.IsNullOrWhiteSpace(language)) + { + return false; + } + + language = language?.ToLowerInvariant() switch + { + "zh-tw" => "zh", // Unique case for zh-TW + "pt-br" => "pt", // Unique case for pt-BR0 + _ => language, + }; + + // try to find a match (ISO 639-2) + return TvdbCultureInfo.GetCultureInfo(language!)? + .ThreeLetterISOLanguageNames? + .Contains(translation.Language, StringComparer.OrdinalIgnoreCase) + ?? false; + } + + /// + /// Normalize to jellyfin format. + /// + /// TVDb uses 3 character language. + /// The . + /// Normalized language. + private static string? NormalizeToJellyfin(this Language? language) + { + return language?.Id?.ToLowerInvariant() switch + { + "zhtw" => "zh-TW", // Unique case for zhtw + "pt" => "pt-BR", // Unique case for pt + var tvdbLang when tvdbLang is { } => TvdbCultureInfo.GetCultureInfo(tvdbLang)?.TwoLetterISOLanguageName, // to (ISO 639-1) + _ => null, + }; + } + + /// + /// Get from . + /// + /// A . + /// or if type is unknown.0 + public static ImageType? GetImageType(this ArtworkType? artworkType) + { + return artworkType?.Name?.ToLowerInvariant() switch + { + "poster" => ImageType.Primary, + "banner" => ImageType.Banner, + "background" => ImageType.Backdrop, + "clearlogo" => ImageType.Logo, + _ => null, + }; + } + + /// + /// Creates a from an . + /// + /// The . + /// The provider name. + /// A , or null if does not contain image information. + public static RemoteImageInfo? CreateImageInfo(this EpisodeExtendedRecord episodeRecord, string providerName) + { + if (string.IsNullOrEmpty(episodeRecord.Image)) + { + return null; + } + + return new RemoteImageInfo + { + ProviderName = providerName, + Url = episodeRecord.Image, + Type = ImageType.Primary + }; + } + + /// + /// Creates a from an . + /// + /// The . + /// The provider name. + /// The . + /// The . + /// A , or null if is . + public static RemoteImageInfo? CreateImageInfo(this ArtworkExtendedRecord artworkRecord, string providerName, ImageType? type, Language? language) + { + return CreateRemoteImageInfo( + artworkRecord.Image, + artworkRecord.Thumbnail, + (artworkRecord.Width, artworkRecord.Height), + providerName, + type, + language); + } + + /// + /// Creates a from an . + /// + /// The . + /// The provider name. + /// The . + /// The . + /// A , or null if is . + public static RemoteImageInfo? CreateImageInfo(this ArtworkBaseRecord artworkRecord, string providerName, ImageType? type, Language? language) + { + return CreateRemoteImageInfo( + artworkRecord.Image, + artworkRecord.Thumbnail, + (artworkRecord.Width, artworkRecord.Height), + providerName, + type, + language); + } + + private static RemoteImageInfo? CreateRemoteImageInfo(string imageUrl, string thumbnailUrl, (long Width, long Height) imageDimension, string providerName, ImageType? type, Language? language) + { + if (type is null) + { + return null; + } + + return new RemoteImageInfo + { + RatingType = RatingType.Score, + Url = imageUrl, + Width = Convert.ToInt32(imageDimension.Width, CultureInfo.InvariantCulture), + Height = Convert.ToInt32(imageDimension.Height, CultureInfo.InvariantCulture), + Type = type.Value, + Language = language.NormalizeToJellyfin()?.ToLowerInvariant(), + ProviderName = providerName, + ThumbnailUrl = thumbnailUrl + }; + } } diff --git a/Jellyfin.Plugin.Tvdb/TvdbUtils.cs b/Jellyfin.Plugin.Tvdb/TvdbUtils.cs index fea2cc3..2f4866e 100644 --- a/Jellyfin.Plugin.Tvdb/TvdbUtils.cs +++ b/Jellyfin.Plugin.Tvdb/TvdbUtils.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Model.Entities; using Tvdb.Sdk; namespace Jellyfin.Plugin.Tvdb @@ -17,86 +15,6 @@ public static class TvdbUtils /// public const string TvdbBaseUrl = "https://www.thetvdb.com/"; - /// - /// Get image type from key type. - /// - /// Key type. - /// Image type. - /// Unknown key type. - public static ImageType GetImageTypeFromKeyType(string? keyType) - { - if (!string.IsNullOrEmpty(keyType)) - { - switch (keyType.ToLowerInvariant()) - { - case "poster": return ImageType.Primary; - case "banner": return ImageType.Banner; - case "background": return ImageType.Backdrop; - case "clearlogo": return ImageType.Logo; - default: throw new ArgumentException($"Invalid or unknown keytype: {keyType}", nameof(keyType)); - } - } - - throw new ArgumentException("Null keytype"); - } - - /// - /// Try to find a match for input language. - /// - /// input Language. - /// TVDB data language. - /// Normalized language. - public static bool MatchLanguage(string? language, string tvdbLanguage) - { - if (string.IsNullOrWhiteSpace(language)) - { - return false; - } - - // Unique case for zh-TW - if (string.Equals(language, "zh-TW", StringComparison.OrdinalIgnoreCase)) - { - language = "zhtw"; - } - - // Unique case for pt-BR - if (string.Equals(language, "pt-br", StringComparison.OrdinalIgnoreCase)) - { - language = "pt"; - } - - // try to find a match (ISO 639-2) - return TvdbCultureInfo.GetCultureInfo(language)?.ThreeLetterISOLanguageNames?.Contains(tvdbLanguage, StringComparer.OrdinalIgnoreCase) ?? false; - } - - /// - /// Normalize language to jellyfin format. - /// - /// Language. - /// Normalized language. - public static string? NormalizeLanguageToJellyfin(string? language) - { - if (string.IsNullOrWhiteSpace(language)) - { - return null; - } - - // Unique case for zhtw - if (string.Equals(language, "zhtw", StringComparison.OrdinalIgnoreCase)) - { - return "zh-TW"; - } - - // Unique case for pt - if (string.Equals(language, "pt", StringComparison.OrdinalIgnoreCase)) - { - return "pt-BR"; - } - - // to (ISO 639-1) - return TvdbCultureInfo.GetCultureInfo(language)?.TwoLetterISOLanguageName; - } - /// /// Converts SeriesAirsDays to DayOfWeek array. ///