diff --git a/Jellyfin.Plugin.Tvdb/ProviderIdsExtensions.cs b/Jellyfin.Plugin.Tvdb/ProviderIdsExtensions.cs new file mode 100644 index 0000000..bed1a30 --- /dev/null +++ b/Jellyfin.Plugin.Tvdb/ProviderIdsExtensions.cs @@ -0,0 +1,114 @@ +using System; +using System.Globalization; + +using MediaBrowser.Model.Entities; + +namespace Jellyfin.Plugin.Tvdb; + +internal static class ProviderIdsExtensions +{ + /// + /// Check whether an item includes an entry for any supported provider IDs. + /// + /// The . + /// True, if contains any supported provider IDs. + internal static bool IsSupported(this IHasProviderIds? item) + { + return HasProviderId(item, MetadataProvider.Tvdb) + || HasProviderId(item, MetadataProvider.Imdb) + || HasProviderId(item, MetadataProvider.Zap2It); + } + + /// + /// Get the TvDB id stored within the item. + /// + /// The item to get the TvDB id from. + /// The Id, or 0. + public static int GetTvdbId(this IHasProviderIds item) + => Convert.ToInt32(item.GetProviderId(TvdbPlugin.ProviderId), CultureInfo.InvariantCulture); + + /// + public static void SetTvdbId(this IHasProviderIds item, long? value) + => SetTvdbId(item, value.HasValue && value > 0 ? value.Value.ToString(CultureInfo.InvariantCulture) : null); + + /// + /// Set the TvDB id in the item, if provided is not or white space. + /// + /// >The to set the TvDB id. + /// TvDB id to set. + /// if value was set. + public static bool SetTvdbId(this IHasProviderIds item, string? value) + => SetProviderIdIfHasValue(item, TvdbPlugin.ProviderId, value); + + /// + public static bool SetProviderIdIfHasValue(this IHasProviderIds item, MetadataProvider provider, string? value) + => SetProviderIdIfHasValue(item, provider.ToString(), value); + + /// + /// Set the provider id in the item, if provided is not or white space. + /// + /// >The to set the TvDB id. + /// Provider name. + /// Provider id to set. + /// if value was set. + public static bool SetProviderIdIfHasValue(this IHasProviderIds item, string name, string? value) + { + if (!HasValue(value)) + { + return false; + } + + item.SetProviderId(name, value); + return true; + } + + /// + /// Checks whether the item has TvDB Id stored. + /// + /// The item. + /// True, if item has TvDB Id stored. + public static bool HasTvdbId(this IHasProviderIds? item) + => HasTvdbId(item, out var value); + + /// + public static bool HasTvdbId(this IHasProviderIds? item, out string? value) + => HasProviderId(item, TvdbPlugin.ProviderId, out value); + + /// + public static bool HasProviderId(this IHasProviderIds? item, MetadataProvider provider) + => HasProviderId(item, provider, out var value); + + /// + public static bool HasProviderId(this IHasProviderIds? item, MetadataProvider provider, out string? value) + => HasProviderId(item, provider.ToString(), out value); + + /// + /// Checks whether the item has provider id stored. + /// + /// The item. + /// Provider. + /// True, if item has provider id stored. + public static bool HasProviderId(this IHasProviderIds? item, string name) + => HasProviderId(item, name, out var value); + + /// + /// Checks whether the item has provider id stored. + /// + /// The item. + /// Provider. + /// The current provider id value. + /// True, if item has provider id stored. + public static bool HasProviderId(this IHasProviderIds? item, string name, out string? value) + { + value = null; + var result = item is { } + && item.TryGetProviderId(name, out value) + && HasValue(value); + + value = result ? value : null; + return result; + } + + private static bool HasValue(string? value) + => !string.IsNullOrWhiteSpace(value); +} diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbEpisodeImageProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbEpisodeImageProvider.cs index 878738d..edaccb6 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbEpisodeImageProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbEpisodeImageProvider.cs @@ -58,7 +58,7 @@ public async Task> GetImages(BaseItem item, Cancell var series = episode.Series; var imageResult = new List(); var language = item.GetPreferredMetadataLanguage(); - if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds)) + if (series.IsSupported()) { // Process images try @@ -85,7 +85,7 @@ public async Task> GetImages(BaseItem item, Cancell "Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}:{Name}", episode.ParentIndexNumber, episode.IndexNumber, - series.GetProviderId(TvdbPlugin.ProviderId), + series.GetTvdbId(), series.Name); return imageResult; } @@ -99,7 +99,7 @@ await _tvdbClientManager } catch (Exception e) { - _logger.LogError(e, "Failed to retrieve episode images for series {TvDbId}:{Name}", series.GetProviderId(TvdbPlugin.ProviderId), series.Name); + _logger.LogError(e, "Failed to retrieve episode images for series {TvDbId}:{Name}", series.GetTvdbId(), series.Name); } } diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbEpisodeProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbEpisodeProvider.cs index 73621b2..c09f2ce 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbEpisodeProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbEpisodeProvider.cs @@ -50,7 +50,7 @@ public async Task> GetSearchResults(EpisodeInfo // Either an episode number or date must be provided; and the dictionary of provider ids must be valid if ((searchInfo.IndexNumber == null && searchInfo.PremiereDate == null) - || !TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds)) + || !searchInfo.IsSupported()) { return list; } @@ -82,32 +82,26 @@ public async Task> GetSearchResults(EpisodeInfo /// public async Task> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken) { - var result = new MetadataResult - { - QueriedById = true - }; - - if (TvdbSeriesProvider.IsValidSeries(info.SeriesProviderIds) && - (info.IndexNumber.HasValue || info.PremiereDate.HasValue)) + if ((info.IndexNumber == null && info.PremiereDate == null) + || !info.IsSupported()) { - // Check for multiple episodes per file, if not run one query. - if (info.IndexNumberEnd.HasValue) + _logger.LogDebug("No series identity found for {EpisodeName}", info.Name); + return new MetadataResult { - _logger.LogDebug("Multiple episodes found in {Path}", info.Path); + QueriedById = true + }; + } - result = await GetCombinedEpisode(info, cancellationToken).ConfigureAwait(false); - } - else - { - result = await GetEpisode(info, cancellationToken).ConfigureAwait(false); - } + // Check for multiple episodes per file, if not run one query. + if (info.IndexNumberEnd.HasValue) + { + _logger.LogDebug("Multiple episodes found in {Path}", info.Path); + return await GetCombinedEpisode(info, cancellationToken).ConfigureAwait(false); } else { - _logger.LogDebug("No series identity found for {EpisodeName}", info.Name); + return await GetEpisode(info, cancellationToken).ConfigureAwait(false); } - - return result; } private async Task> GetCombinedEpisode(EpisodeInfo info, CancellationToken cancellationToken) @@ -159,7 +153,6 @@ private async Task> GetEpisode(EpisodeInfo searchInfo, C QueriedById = true }; - var seriesTvdbId = searchInfo.SeriesProviderIds.FirstOrDefault(x => x.Key == TvdbPlugin.ProviderId).Value; string? episodeTvdbId = null; try { @@ -172,7 +165,7 @@ private async Task> GetEpisode(EpisodeInfo searchInfo, C "Episode S{Season:00}E{Episode:00} not found for series {SeriesTvdbId}:{Name}", searchInfo.ParentIndexNumber, searchInfo.IndexNumber, - seriesTvdbId, + searchInfo.GetTvdbId(), searchInfo.Name); return result; } @@ -190,7 +183,7 @@ private async Task> GetEpisode(EpisodeInfo searchInfo, C e, "Failed to retrieve episode with id {EpisodeTvDbId}, series id {SeriesTvdbId}:{Name}", episodeTvdbId, - seriesTvdbId, + searchInfo.GetTvdbId(), searchInfo.Name); } @@ -220,12 +213,9 @@ private static MetadataResult MapEpisodeToResult(EpisodeInfo id, Episod result.ResetPeople(); var item = result.Item; - item.SetProviderId(TvdbPlugin.ProviderId, episode.Id.GetValueOrDefault().ToString(CultureInfo.InvariantCulture)); + item.SetTvdbId(episode.Id); var imdbID = episode.RemoteIds.FirstOrDefault(x => string.Equals(x.SourceName, "IMDB", StringComparison.OrdinalIgnoreCase))?.Id; - if (!string.IsNullOrEmpty(imdbID)) - { - item.SetProviderId(MetadataProvider.Imdb, imdbID); - } + item.SetProviderIdIfHasValue(MetadataProvider.Imdb, imdbID); if (string.Equals(id.SeriesDisplayOrder, "dvd", StringComparison.OrdinalIgnoreCase)) { diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbMissingEpisodeProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbMissingEpisodeProvider.cs index 9b468cf..d3886cd 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbMissingEpisodeProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbMissingEpisodeProvider.cs @@ -102,7 +102,8 @@ private static bool EpisodeExists(EpisodeBaseRecord episodeRecord, IReadOnlyList private static bool EpisodeEquals(Episode episode, EpisodeBaseRecord otherEpisodeRecord) { - return episode.ContainsEpisodeNumber(otherEpisodeRecord.Number.GetValueOrDefault()) + return otherEpisodeRecord.Number.HasValue + && episode.ContainsEpisodeNumber(otherEpisodeRecord.Number.Value) && episode.ParentIndexNumber == otherEpisodeRecord.SeasonNumber; } @@ -155,13 +156,13 @@ private void OnProviderManagerRefreshComplete(object? sender, GenericEventArgs(); @@ -191,7 +192,8 @@ private async Task HandleSeries(Series series) var allEpisodes = await GetAllEpisodes(tvdbId, series.GetPreferredMetadataLanguage()).ConfigureAwait(false); var allSeasons = allEpisodes - .Select(ep => ep.SeasonNumber.GetValueOrDefault()) + .Where(ep => ep.SeasonNumber.HasValue) + .Select(ep => ep.SeasonNumber!.Value) .Distinct() .ToList(); @@ -202,14 +204,14 @@ private async Task HandleSeries(Series series) private async Task HandleSeason(Season season) { - if (season.Series == null - || !season.Series.TryGetProviderId(MetadataProvider.Tvdb.ToString(), out var tvdbIdTxt)) + var series = season.Series; + if (series.HasTvdbId()) { _logger.LogDebug("No TVDB Id available."); return; } - var tvdbId = Convert.ToInt32(tvdbIdTxt, CultureInfo.InvariantCulture); + var tvdbId = series.GetTvdbId(); var allEpisodes = await GetAllEpisodes(tvdbId, season.GetPreferredMetadataLanguage()) .ConfigureAwait(false); @@ -321,14 +323,13 @@ private void OnLibraryManagerItemRemoved(object? sender, ItemChangeEventArgs ite } else if (itemChangeEventArgs.Item is Episode episode) { - if (episode.Series == null - || !episode.Series.TryGetProviderId(MetadataProvider.Tvdb.ToString(), out var tvdbIdTxt)) + if (!episode.Series.HasTvdbId()) { _logger.LogDebug("No TVDB Id available."); return; } - var tvdbId = Convert.ToInt32(tvdbIdTxt, CultureInfo.InvariantCulture); + var tvdbId = episode.Series.GetTvdbId(); var episodeRecords = GetAllEpisodes(tvdbId, episode.GetPreferredMetadataLanguage()).GetAwaiter().GetResult(); @@ -385,7 +386,8 @@ private void AddMissingEpisodes( var episodeRecord = allEpisodeRecords[i]; // skip if it exists already - if (existingEpisodes.TryGetValue(episodeRecord.SeasonNumber.GetValueOrDefault(), out var episodes) + if (episodeRecord.SeasonNumber.HasValue + && existingEpisodes.TryGetValue(episodeRecord.SeasonNumber.Value, out var episodes) && EpisodeExists(episodeRecord, episodes)) { _logger.LogDebug("{MethodName}: Skip, already existing S{Season:00}E{Episode:00}", nameof(AddMissingEpisodes), episodeRecord.SeasonNumber, episodeRecord.Number); @@ -435,7 +437,7 @@ private Season AddVirtualSeason(int season, Series series) private void AddVirtualEpisode(EpisodeBaseRecord? episode, Season? season) { - if (episode == null || season == null) + if (episode?.SeasonNumber == null || season == null) { return; } @@ -447,7 +449,7 @@ private void AddVirtualEpisode(EpisodeBaseRecord? episode, Season? season) IndexNumber = episode.Number, ParentIndexNumber = episode.SeasonNumber, Id = _libraryManager.GetNewItemId( - season.Series.Id + episode.SeasonNumber.GetValueOrDefault().ToString(CultureInfo.InvariantCulture) + "Episode " + episode.Number, + $"{season.Series.Id}{episode.SeasonNumber}Episode {episode.Number}", typeof(Episode)), IsVirtualItem = true, SeasonId = season.Id, @@ -467,7 +469,7 @@ private void AddVirtualEpisode(EpisodeBaseRecord? episode, Season? season) } newEpisode.PresentationUniqueKey = newEpisode.GetPresentationUniqueKey(); - newEpisode.SetProviderId(MetadataProvider.Tvdb, episode.Id.GetValueOrDefault().ToString(CultureInfo.InvariantCulture)); + newEpisode.SetTvdbId(episode.Id); _logger.LogDebug( "Creating virtual episode {SeriesName} S{Season:00}E{Episode:00}", diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbPersonImageProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbPersonImageProvider.cs index 480e640..6becb20 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbPersonImageProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbPersonImageProvider.cs @@ -72,7 +72,7 @@ public async Task> GetImages(BaseItem item, Cancell EnableImages = false } }).Cast() - .Where(i => TvdbSeriesProvider.IsValidSeries(i.ProviderIds)) + .Where(i => i.IsSupported()) .ToList(); var infos = (await Task.WhenAll(seriesWithPerson.Select(async i => @@ -93,7 +93,7 @@ public Task GetImageResponse(string url, CancellationToken private async Task GetImageFromSeriesData(Series series, string personName, CancellationToken cancellationToken) { - var tvdbId = Convert.ToInt32(series.GetProviderId(TvdbPlugin.ProviderId), CultureInfo.InvariantCulture); + var tvdbId = series.GetTvdbId(); try { diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbSeasonImageProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbSeasonImageProvider.cs index 7a04702..4d37006 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbSeasonImageProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbSeasonImageProvider.cs @@ -69,7 +69,7 @@ public async Task> GetImages(BaseItem item, Cancell var season = (Season)item; var series = season.Series; - if (series == null || !season.IndexNumber.HasValue || !TvdbSeriesProvider.IsValidSeries(series.ProviderIds)) + if (!series.IsSupported() || season.IndexNumber is null) { return Enumerable.Empty(); } @@ -83,9 +83,10 @@ public async Task> GetImages(BaseItem item, Cancell .ConfigureAwait(false); var seasonArtworkTypeLookup = artworkTypes .Where(t => string.Equals(t.RecordType, "season", StringComparison.OrdinalIgnoreCase)) - .ToDictionary(t => t.Id); + .Where(t => t.Id.HasValue) + .ToDictionary(t => t.Id!.Value); - var seriesTvdbId = Convert.ToInt32(series.GetProviderId(TvdbPlugin.ProviderId), CultureInfo.InvariantCulture); + var seriesTvdbId = series.GetTvdbId(); var seasonNumber = season.IndexNumber.Value; var seasonArtworks = await GetSeasonArtworks(seriesTvdbId, seasonNumber, cancellationToken) @@ -94,7 +95,7 @@ public async Task> GetImages(BaseItem item, Cancell var remoteImages = new List(); foreach (var artwork in seasonArtworks) { - var artworkType = seasonArtworkTypeLookup.GetValueOrDefault(artwork.Type); + var artworkType = artwork.Type is null ? null : seasonArtworkTypeLookup.GetValueOrDefault(artwork.Type!.Value); var imageType = artworkType.GetImageType(); var artworkLanguage = artwork.Language is null ? null : languageLookup.GetValueOrDefault(artwork.Language); diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesImageProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesImageProvider.cs index 1ffa445..1e39591 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesImageProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesImageProvider.cs @@ -66,7 +66,7 @@ public IEnumerable GetSupportedImages(BaseItem item) /// public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) { - if (!TvdbSeriesProvider.IsValidSeries(item.ProviderIds)) + if (!item.IsSupported()) { return Enumerable.Empty(); } @@ -80,16 +80,17 @@ public async Task> GetImages(BaseItem item, Cancell .ConfigureAwait(false); var seriesArtworkTypeLookup = artworkTypes .Where(t => string.Equals(t.RecordType, "series", StringComparison.OrdinalIgnoreCase)) - .ToDictionary(t => t.Id); + .Where(t => t.Id.HasValue) + .ToDictionary(t => t.Id!.Value); - var seriesTvdbId = Convert.ToInt32(item.GetProviderId(TvdbPlugin.ProviderId), CultureInfo.InvariantCulture); + var seriesTvdbId = item.GetTvdbId(); var seriesArtworks = await GetSeriesArtworks(seriesTvdbId, cancellationToken) .ConfigureAwait(false); var remoteImages = new List(); foreach (var artwork in seriesArtworks) { - var artworkType = seriesArtworkTypeLookup.GetValueOrDefault(artwork.Type); + var artworkType = artwork.Type is null ? null : seriesArtworkTypeLookup.GetValueOrDefault(artwork.Type!.Value); var imageType = artworkType.GetImageType(); var artworkLanguage = artwork.Language is null ? null : languageLookup.GetValueOrDefault(artwork.Language); diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesProvider.cs index 4bab013..821f837 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbSeriesProvider.cs @@ -52,7 +52,7 @@ public TvdbSeriesProvider(IHttpClientFactory httpClientFactory, ILogger public async Task> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) { - if (IsValidSeries(searchInfo.ProviderIds)) + if (searchInfo.IsSupported()) { return await FetchSeriesSearchResult(searchInfo, cancellationToken).ConfigureAwait(false); } @@ -68,7 +68,7 @@ public async Task> GetMetadata(SeriesInfo info, Cancellat QueriedById = true, }; - if (!IsValidSeries(info.ProviderIds)) + if (!info.IsSupported()) { result.QueriedById = false; await Identify(info).ConfigureAwait(false); @@ -76,7 +76,7 @@ public async Task> GetMetadata(SeriesInfo info, Cancellat cancellationToken.ThrowIfCancellationRequested(); - if (IsValidSeries(info.ProviderIds)) + if (info.IsSupported()) { result.Item = new Series(); result.HasMetadata = true; @@ -94,65 +94,48 @@ public Task GetImageResponse(string url, CancellationToken return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(new Uri(url), cancellationToken); } - /// - /// Check whether a dictionary of provider IDs includes an entry for a valid TV metadata provider. - /// - /// The provider IDs to check. - /// True, if the series contains a valid TV provider ID, otherwise false. - internal static bool IsValidSeries(Dictionary ids) - { - return (ids.TryGetValue(MetadataProvider.Tvdb.ToString(), out var tvdbId) && !string.IsNullOrEmpty(tvdbId)) - || (ids.TryGetValue(MetadataProvider.Imdb.ToString(), out var imdbId) && !string.IsNullOrEmpty(imdbId)) - || (ids.TryGetValue(MetadataProvider.Zap2It.ToString(), out var zap2ItId) && !string.IsNullOrEmpty(zap2ItId)); - } - private async Task> FetchSeriesSearchResult(SeriesInfo seriesInfo, CancellationToken cancellationToken) { - var tvdbId = seriesInfo.GetProviderId(MetadataProvider.Tvdb); - if (string.IsNullOrEmpty(tvdbId)) + async Task TryGetTvdbIdWithRemoteId(MetadataProvider metadataProvider) { - var imdbId = seriesInfo.GetProviderId(MetadataProvider.Imdb); - if (!string.IsNullOrEmpty(imdbId)) + var id = seriesInfo.GetProviderId(metadataProvider); + if (string.IsNullOrEmpty(id)) { - tvdbId = await GetSeriesByRemoteId( - imdbId, - seriesInfo.MetadataLanguage, - seriesInfo.Name, - cancellationToken).ConfigureAwait(false); + return null; } + + return await GetSeriesByRemoteId( + id, + seriesInfo.MetadataLanguage, + seriesInfo.Name, + cancellationToken).ConfigureAwait(false); } - if (string.IsNullOrEmpty(tvdbId)) + int? tvdbId; + if (seriesInfo.HasTvdbId()) { - var zap2ItId = seriesInfo.GetProviderId(MetadataProvider.Zap2It); - if (!string.IsNullOrEmpty(zap2ItId)) - { - tvdbId = await GetSeriesByRemoteId( - zap2ItId, - seriesInfo.MetadataLanguage, - seriesInfo.Name, - cancellationToken).ConfigureAwait(false); - } + tvdbId = seriesInfo.GetTvdbId(); + } + else + { + var tvdbIdTxt = await TryGetTvdbIdWithRemoteId(MetadataProvider.Imdb).ConfigureAwait(false) + ?? await TryGetTvdbIdWithRemoteId(MetadataProvider.Zap2It).ConfigureAwait(false) + ?? await TryGetTvdbIdWithRemoteId(MetadataProvider.Tmdb).ConfigureAwait(false); + + tvdbId = tvdbIdTxt is null ? null : Convert.ToInt32(tvdbIdTxt, CultureInfo.InvariantCulture); } - if (string.IsNullOrEmpty(tvdbId)) + if (!tvdbId.HasValue) { - var tmdbId = seriesInfo.GetProviderId(MetadataProvider.Tmdb); - if (!string.IsNullOrEmpty(tmdbId)) - { - tvdbId = await GetSeriesByRemoteId( - tmdbId, - seriesInfo.MetadataLanguage, - seriesInfo.Name, - cancellationToken).ConfigureAwait(false); - } + _logger.LogWarning("No valid tvdb id found for series {TvdbId}:{SeriesName}", tvdbId, seriesInfo.Name); + return Array.Empty(); } try { var seriesResult = await _tvdbClientManager - .GetSeriesExtendedByIdAsync(Convert.ToInt32(tvdbId, CultureInfo.InvariantCulture), seriesInfo.MetadataLanguage, cancellationToken, small: true) + .GetSeriesExtendedByIdAsync(tvdbId.Value, seriesInfo.MetadataLanguage, cancellationToken, small: true) .ConfigureAwait(false); return new[] { MapSeriesToRemoteSearchResult(seriesResult) }; } @@ -181,67 +164,64 @@ private RemoteSearchResult MapSeriesToRemoteSearchResult(SeriesExtendedRecord se } var imdbID = series.RemoteIds.FirstOrDefault(x => x.SourceName == "IMDB")?.Id; - if (!string.IsNullOrEmpty(imdbID)) - { - remoteResult.SetProviderId(MetadataProvider.Imdb, imdbID); - } - - remoteResult.SetProviderId(MetadataProvider.Tvdb, series.Id.GetValueOrDefault().ToString(CultureInfo.InvariantCulture)); + remoteResult.SetProviderIdIfHasValue(MetadataProvider.Imdb, imdbID); + remoteResult.SetTvdbId(series.Id); return remoteResult; } private async Task FetchSeriesMetadata( MetadataResult result, - SeriesInfo info, + SeriesInfo seriesInfo, CancellationToken cancellationToken) { - string metadataLanguage = info.MetadataLanguage; - Dictionary seriesProviderIds = info.ProviderIds; - var series = result.Item; + var seriesMetadata = result.Item; + async Task TryGetTvdbIdWithRemoteId(string id) + { + return await GetSeriesByRemoteId( + id, + seriesInfo.MetadataLanguage, + seriesInfo.Name, + cancellationToken).ConfigureAwait(false); + } - if (seriesProviderIds.TryGetValue(TvdbPlugin.ProviderId, out var tvdbId) && !string.IsNullOrEmpty(tvdbId)) + if (seriesInfo.HasTvdbId(out var tvdbIdTxt)) { - series.SetProviderId(TvdbPlugin.ProviderId, tvdbId); + seriesMetadata.SetTvdbId(tvdbIdTxt); } - if (seriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out var imdbId) && !string.IsNullOrEmpty(imdbId)) + if (seriesInfo.HasProviderId(MetadataProvider.Imdb, out var imdbId)) { - series.SetProviderId(MetadataProvider.Imdb, imdbId); - tvdbId = await GetSeriesByRemoteId( - imdbId, - metadataLanguage, - info.Name, - cancellationToken).ConfigureAwait(false); + seriesMetadata.SetProviderId(MetadataProvider.Imdb, imdbId!); + tvdbIdTxt ??= await TryGetTvdbIdWithRemoteId(imdbId!).ConfigureAwait(false); } - if (seriesProviderIds.TryGetValue(MetadataProvider.Zap2It.ToString(), out var zap2It) && !string.IsNullOrEmpty(zap2It)) + if (seriesInfo.HasProviderId(MetadataProvider.Zap2It, out var zap2It)) { - series.SetProviderId(MetadataProvider.Zap2It, zap2It); - tvdbId = await GetSeriesByRemoteId( - zap2It, - metadataLanguage, - info.Name, - cancellationToken).ConfigureAwait(false); + seriesMetadata.SetProviderId(MetadataProvider.Zap2It, zap2It!); + tvdbIdTxt ??= await TryGetTvdbIdWithRemoteId(zap2It!).ConfigureAwait(false); } - if (seriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out var tmdbId) && !string.IsNullOrEmpty(tmdbId)) + if (seriesInfo.HasProviderId(MetadataProvider.Tmdb, out var tmdbId)) { - series.SetProviderId(MetadataProvider.Tmdb, tmdbId); - tvdbId = await GetSeriesByRemoteId( - tmdbId, - metadataLanguage, - info.Name, - cancellationToken).ConfigureAwait(false); + seriesMetadata.SetProviderId(MetadataProvider.Tmdb, tmdbId!); + tvdbIdTxt ??= await TryGetTvdbIdWithRemoteId(tmdbId!).ConfigureAwait(false); + } + + if (string.IsNullOrWhiteSpace(tvdbIdTxt)) + { + _logger.LogWarning("No valid tvdb id found for series {TvdbId}:{SeriesName}", tvdbIdTxt, seriesInfo.Name); + return; } + var tvdbId = Convert.ToInt32(tvdbIdTxt, CultureInfo.InvariantCulture); try { var seriesResult = await _tvdbClientManager - .GetSeriesExtendedByIdAsync(Convert.ToInt32(tvdbId, CultureInfo.InvariantCulture), metadataLanguage, cancellationToken, Meta4.Translations, false) + .GetSeriesExtendedByIdAsync(tvdbId, seriesInfo.MetadataLanguage, cancellationToken, Meta4.Translations, false) .ConfigureAwait(false); - MapSeriesToResult(result, seriesResult, info); + MapSeriesToResult(result, seriesResult, seriesInfo); result.ResetPeople(); @@ -257,12 +237,12 @@ await _tvdbClientManager } else { - _logger.LogError("Failed to retrieve actors for series {TvdbId}:{SeriesName}", tvdbId, info.Name); + _logger.LogError("Failed to retrieve actors for series {TvdbId}:{SeriesName}", tvdbId, seriesInfo.Name); } } catch (Exception e) { - _logger.LogError(e, "Failed to retrieve series with id {TvdbId}:{SeriesName}", tvdbId, info.Name); + _logger.LogError(e, "Failed to retrieve series with id {TvdbId}:{SeriesName}", tvdbId, seriesInfo.Name); return; } } @@ -281,13 +261,13 @@ await _tvdbClientManager return null; } - if (resultData is null || resultData.Count == 0 || resultData[0] is null || resultData[0].Series is null || resultData[0].Series.Id.HasValue == false) + if (resultData is null || resultData.Count == 0 || resultData[0]?.Series?.Id is null) { _logger.LogWarning("TvdbSearch: No series found for remote id: {RemoteId}", remoteId); return null; } - return resultData[0].Series.Id.GetValueOrDefault().ToString(CultureInfo.InvariantCulture); + return resultData[0].Series.Id?.ToString(CultureInfo.InvariantCulture); } /// @@ -367,32 +347,22 @@ private async Task> FindSeriesInternal(string name, str var seriesResult = await _tvdbClientManager.GetSeriesExtendedByIdAsync(Convert.ToInt32(seriesSearchResult.Tvdb_id, CultureInfo.InvariantCulture), language, cancellationToken, small: true) .ConfigureAwait(false); + var imdbId = seriesResult.RemoteIds.FirstOrDefault(x => string.Equals(x.SourceName, "IMDB", StringComparison.OrdinalIgnoreCase))?.Id.ToString(); - if (!string.IsNullOrEmpty(imdbId)) - { - remoteSearchResult.SetProviderId(MetadataProvider.Imdb, imdbId); - } + remoteSearchResult.SetProviderIdIfHasValue(MetadataProvider.Imdb, imdbId); var zap2ItId = seriesResult.RemoteIds.FirstOrDefault(x => string.Equals(x.SourceName, "Zap2It", StringComparison.OrdinalIgnoreCase))?.Id.ToString(); - - if (!string.IsNullOrEmpty(zap2ItId)) - { - remoteSearchResult.SetProviderId(MetadataProvider.Zap2It, zap2ItId); - } + remoteSearchResult.SetProviderIdIfHasValue(MetadataProvider.Zap2It, zap2ItId); var tmdbId = seriesResult.RemoteIds.FirstOrDefault(x => string.Equals(x.SourceName, "TheMovieDB.com", StringComparison.OrdinalIgnoreCase))?.Id.ToString(); - - if (!string.IsNullOrEmpty(tmdbId)) - { - remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, tmdbId); - } + remoteSearchResult.SetProviderIdIfHasValue(MetadataProvider.Tmdb, tmdbId); } catch (Exception e) { _logger.LogError(e, "Unable to retrieve series with id {TvdbId}:{SeriesName}", seriesSearchResult.Tvdb_id, seriesSearchResult.Name); } - remoteSearchResult.SetProviderId(TvdbPlugin.ProviderId, seriesSearchResult.Tvdb_id); + remoteSearchResult.SetTvdbId(seriesSearchResult.Tvdb_id); list.Add(new Tuple, RemoteSearchResult>(tvdbTitles, remoteSearchResult)); } @@ -450,7 +420,7 @@ private static void MapActorsToResult(MetadataResult result, IEnumerable private async Task Identify(SeriesInfo info) { - if (!string.IsNullOrWhiteSpace(info.GetProviderId(TvdbPlugin.ProviderId))) + if (info.HasTvdbId()) { return; } @@ -459,21 +429,16 @@ private async Task Identify(SeriesInfo info) .ConfigureAwait(false); var entry = remoteSearchResults.FirstOrDefault(); - - if (entry != null) + if (entry.HasTvdbId(out var tvdbId)) { - var id = entry.GetProviderId(TvdbPlugin.ProviderId); - if (!string.IsNullOrEmpty(id)) - { - info.SetProviderId(TvdbPlugin.ProviderId, id); - } + info.SetTvdbId(tvdbId); } } private static void MapSeriesToResult(MetadataResult result, SeriesExtendedRecord tvdbSeries, SeriesInfo info) { Series series = result.Item; - series.SetProviderId(TvdbPlugin.ProviderId, tvdbSeries.Id.GetValueOrDefault().ToString(CultureInfo.InvariantCulture)); + series.SetTvdbId(tvdbSeries.Id); // Tvdb uses 3 letter code for language (prob ISO 639-2) // Reverts to OriginalName if no translation is found series.Name = tvdbSeries.Translations.GetTranslatedNamedOrDefault(info.MetadataLanguage) ?? tvdbSeries.Name; @@ -485,23 +450,15 @@ private static void MapSeriesToResult(MetadataResult result, SeriesExten // series.CommunityRating = (float?)tvdbSeries.SiteRating; // Attempts to default to USA if not found series.OfficialRating = tvdbSeries.ContentRatings.FirstOrDefault(x => string.Equals(x.Country, TvdbCultureInfo.GetCountryInfo(info.MetadataCountryCode)?.ThreeLetterISORegionName, StringComparison.OrdinalIgnoreCase))?.Name ?? tvdbSeries.ContentRatings.FirstOrDefault(x => string.Equals(x.Country, "usa", StringComparison.OrdinalIgnoreCase))?.Name; + var imdbId = tvdbSeries.RemoteIds.FirstOrDefault(x => string.Equals(x.SourceName, "IMDB", StringComparison.OrdinalIgnoreCase))?.Id.ToString(); - var zap2ItId = tvdbSeries.RemoteIds.FirstOrDefault(x => string.Equals(x.SourceName, "Zap2It", StringComparison.OrdinalIgnoreCase))?.Id.ToString(); - var tmdbId = tvdbSeries.RemoteIds.FirstOrDefault(x => string.Equals(x.SourceName, "TheMovieDB.com", StringComparison.OrdinalIgnoreCase))?.Id.ToString(); - if (!string.IsNullOrEmpty(imdbId)) - { - series.SetProviderId(MetadataProvider.Imdb, imdbId); - } + series.SetProviderIdIfHasValue(MetadataProvider.Imdb, imdbId); - if (!string.IsNullOrEmpty(zap2ItId)) - { - series.SetProviderId(MetadataProvider.Zap2It, zap2ItId); - } + var zap2ItId = tvdbSeries.RemoteIds.FirstOrDefault(x => string.Equals(x.SourceName, "Zap2It", StringComparison.OrdinalIgnoreCase))?.Id.ToString(); + series.SetProviderIdIfHasValue(MetadataProvider.Zap2It, zap2ItId); - if (!string.IsNullOrEmpty(tmdbId)) - { - series.SetProviderId(MetadataProvider.Tmdb, tmdbId); - } + var tmdbId = tvdbSeries.RemoteIds.FirstOrDefault(x => string.Equals(x.SourceName, "TheMovieDB.com", StringComparison.OrdinalIgnoreCase))?.Id.ToString(); + series.SetProviderIdIfHasValue(MetadataProvider.Tmdb, tmdbId); if (Enum.TryParse(tvdbSeries.Status.Name, true, out SeriesStatus seriesStatus)) { diff --git a/Jellyfin.Plugin.Tvdb/TvdbClientManager.cs b/Jellyfin.Plugin.Tvdb/TvdbClientManager.cs index afe2b1f..93266dc 100644 --- a/Jellyfin.Plugin.Tvdb/TvdbClientManager.cs +++ b/Jellyfin.Plugin.Tvdb/TvdbClientManager.cs @@ -321,7 +321,6 @@ public async Task> GetArtworkTypeAsync(CancellationTo { var seriesClient = _serviceProvider.GetRequiredService(); await LoginAsync().ConfigureAwait(false); - searchInfo.SeriesProviderIds.TryGetValue(TvdbPlugin.ProviderId, out var seriesTvdbId); int? episodeNumber = null; int? seasonNumber = null; string? airDate = null; @@ -368,27 +367,27 @@ public async Task> GetArtworkTypeAsync(CancellationTo { case "dvd": case "absolute": - seriesResponse = await seriesClient.GetSeriesEpisodesAsync(page: 0, id: Convert.ToInt32(seriesTvdbId, CultureInfo.InvariantCulture), season_type: searchInfo.SeriesDisplayOrder, season: seasonNumber, episodeNumber: episodeNumber, airDate: airDate, cancellationToken: cancellationToken).ConfigureAwait(false); + seriesResponse = await seriesClient.GetSeriesEpisodesAsync(page: 0, id: searchInfo.GetTvdbId(), season_type: searchInfo.SeriesDisplayOrder, season: seasonNumber, episodeNumber: episodeNumber, airDate: airDate, cancellationToken: cancellationToken).ConfigureAwait(false); break; default: - seriesResponse = await seriesClient.GetSeriesEpisodesAsync(page: 0, id: Convert.ToInt32(seriesTvdbId, CultureInfo.InvariantCulture), season_type: "default", season: seasonNumber, episodeNumber: episodeNumber, airDate: airDate, cancellationToken: cancellationToken).ConfigureAwait(false); + seriesResponse = await seriesClient.GetSeriesEpisodesAsync(page: 0, id: searchInfo.GetTvdbId(), season_type: "default", season: seasonNumber, episodeNumber: episodeNumber, airDate: airDate, cancellationToken: cancellationToken).ConfigureAwait(false); break; } } else // when special use default order { - seriesResponse = await seriesClient.GetSeriesEpisodesAsync(page: 0, id: Convert.ToInt32(seriesTvdbId, CultureInfo.InvariantCulture), season_type: "default", season: seasonNumber, episodeNumber: episodeNumber, airDate: airDate, cancellationToken: cancellationToken).ConfigureAwait(false); + seriesResponse = await seriesClient.GetSeriesEpisodesAsync(page: 0, id: searchInfo.GetTvdbId(), season_type: "default", season: seasonNumber, episodeNumber: episodeNumber, airDate: airDate, cancellationToken: cancellationToken).ConfigureAwait(false); } Data2 seriesData = seriesResponse.Data; - if (seriesData == null || seriesData.Episodes == null || seriesData.Episodes.Count == 0 || seriesData.Episodes[0].Id.HasValue == false) + if (seriesData?.Episodes == null || seriesData.Episodes.Count == 0) { return null; } else { - return seriesData.Episodes[0].Id.GetValueOrDefault().ToString(CultureInfo.InvariantCulture); + return seriesData.Episodes[0].Id?.ToString(CultureInfo.InvariantCulture); } } diff --git a/Jellyfin.Plugin.Tvdb/TvdbSdkExtensions.cs b/Jellyfin.Plugin.Tvdb/TvdbSdkExtensions.cs index 75f5fa9..5d32e00 100644 --- a/Jellyfin.Plugin.Tvdb/TvdbSdkExtensions.cs +++ b/Jellyfin.Plugin.Tvdb/TvdbSdkExtensions.cs @@ -159,7 +159,7 @@ private static bool IsMatch(this Translation translation, string? language) language); } - private static RemoteImageInfo? CreateRemoteImageInfo(string imageUrl, string thumbnailUrl, (long Width, long Height) imageDimension, string providerName, ImageType? type, Language? language) + private static RemoteImageInfo? CreateRemoteImageInfo(string imageUrl, string thumbnailUrl, (long? Width, long? Height) imageDimension, string providerName, ImageType? type, Language? language) { if (type is null) {