diff options
Diffstat (limited to 'MediaBrowser.Providers/Plugins')
19 files changed, 301 insertions, 103 deletions
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs index 00bd96282c..d8cb6b4b24 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs @@ -125,7 +125,9 @@ namespace MediaBrowser.Providers.Plugins.AudioDb if (string.IsNullOrWhiteSpace(overview)) { - overview = result.strBiographyEN; + overview = string.IsNullOrWhiteSpace(result.strBiographyEN) + ? result.strBiography + : result.strBiographyEN; } item.Overview = (overview ?? string.Empty).StripHtml(); @@ -224,6 +226,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public string strTwitter { get; set; } + public string strBiography { get; set; } + public string strBiographyEN { get; set; } public string strBiographyDE { get; set; } diff --git a/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalId.cs b/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalId.cs new file mode 100644 index 0000000000..8cbd1f89a7 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalId.cs @@ -0,0 +1,23 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.ComicVine +{ + /// <inheritdoc /> + public class ComicVineExternalId : IExternalId + { + /// <inheritdoc /> + public string ProviderName => "Comic Vine"; + + /// <inheritdoc /> + public string Key => "ComicVine"; + + /// <inheritdoc /> + public ExternalIdMediaType? Type => null; + + /// <inheritdoc /> + public bool Supports(IHasProviderIds item) => item is Book; + } +} diff --git a/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalUrlProvider.cs new file mode 100644 index 0000000000..9122399179 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalUrlProvider.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Plugins.ComicVine; + +/// <inheritdoc/> +public class ComicVineExternalUrlProvider : IExternalUrlProvider +{ + /// <inheritdoc/> + public string Name => "Comic Vine"; + + /// <inheritdoc /> + public IEnumerable<string> GetExternalUrls(BaseItem item) + { + if (item.TryGetProviderId("ComicVine", out var externalId)) + { + switch (item) + { + case Person: + case Book: + yield return $"https://comicvine.gamespot.com/{externalId}"; + break; + } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/ComicVine/ComicVinePersonExternalId.cs b/MediaBrowser.Providers/Plugins/ComicVine/ComicVinePersonExternalId.cs new file mode 100644 index 0000000000..26b8e11380 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/ComicVine/ComicVinePersonExternalId.cs @@ -0,0 +1,23 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.ComicVine +{ + /// <inheritdoc /> + public class ComicVinePersonExternalId : IExternalId + { + /// <inheritdoc /> + public string ProviderName => "Comic Vine"; + + /// <inheritdoc /> + public string Key => "ComicVine"; + + /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.Person; + + /// <inheritdoc /> + public bool Supports(IHasProviderIds item) => item is Person; + } +} diff --git a/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalId.cs b/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalId.cs new file mode 100644 index 0000000000..02d3b36974 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalId.cs @@ -0,0 +1,23 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.GoogleBooks +{ + /// <inheritdoc /> + public class GoogleBooksExternalId : IExternalId + { + /// <inheritdoc /> + public string ProviderName => "Google Books"; + + /// <inheritdoc /> + public string Key => "GoogleBooks"; + + /// <inheritdoc /> + public ExternalIdMediaType? Type => null; + + /// <inheritdoc /> + public bool Supports(IHasProviderIds item) => item is Book; + } +} diff --git a/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalUrlProvider.cs new file mode 100644 index 0000000000..95047ee83e --- /dev/null +++ b/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalUrlProvider.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Plugins.GoogleBooks; + +/// <inheritdoc/> +public class GoogleBooksExternalUrlProvider : IExternalUrlProvider +{ + /// <inheritdoc /> + public string Name => "Google Books"; + + /// <inheritdoc /> + public IEnumerable<string> GetExternalUrls(BaseItem item) + { + if (item.TryGetProviderId("GoogleBooks", out var externalId)) + { + if (item is Book) + { + yield return $"https://books.google.com/books?id={externalId}"; + } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs index 1323d2604a..9df21596c5 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz; /// <summary> /// MusicBrainz artist provider. /// </summary> -public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IDisposable +public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IDisposable, IHasOrder { private readonly ILogger<MusicBrainzArtistProvider> _logger; private Query _musicBrainzQuery; @@ -42,6 +42,10 @@ public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, Ar /// <inheritdoc /> public string Name => "MusicBrainz"; + /// <inheritdoc /> + /// Runs first to populate the MusicBrainz artist ID used by downstream providers. + public int Order => 0; + private void ReloadConfig(object? sender, BasePluginConfiguration e) { var configuration = (PluginConfiguration)e; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs index 450ee2a337..3eacc4f0f0 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs @@ -33,7 +33,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Api /// <returns>The image portion of the TMDb client configuration.</returns> [HttpGet("ClientConfiguration")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task<ConfigImageTypes> TmdbClientConfiguration() + public async Task<ConfigImageTypes?> TmdbClientConfiguration() { return (await _tmdbClientManager.GetClientConfiguration().ConfigureAwait(false)).Images; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs index 02818a0e24..78be5804e3 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs @@ -75,10 +75,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets var posters = collection.Images.Posters; var backdrops = collection.Images.Backdrops; - var remoteImages = new List<RemoteImageInfo>(posters.Count + backdrops.Count); + var remoteImages = new List<RemoteImageInfo>(posters?.Count ?? 0 + backdrops?.Count ?? 0); - remoteImages.AddRange(_tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language)); - remoteImages.AddRange(_tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language)); + if (posters is not null) + { + remoteImages.AddRange(_tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language)); + } + + if (backdrops is not null) + { + remoteImages.AddRange(_tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language)); + } return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs index 34c9abae12..a7bba2d539 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs @@ -67,10 +67,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets result.SetProviderId(MetadataProvider.Tmdb, collection.Id.ToString(CultureInfo.InvariantCulture)); - return new[] { result }; + return [result]; } var collectionSearchResults = await _tmdbClientManager.SearchCollectionAsync(searchInfo.Name, language, searchInfo.MetadataCountryCode, cancellationToken).ConfigureAwait(false); + if (collectionSearchResults is null) + { + return []; + } var collections = new RemoteSearchResult[collectionSearchResults.Count]; for (var i = 0; i < collectionSearchResults.Count; i++) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs index fcc3574107..714c57d361 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs @@ -79,7 +79,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies if (movieTmdbId <= 0) { - return Enumerable.Empty<RemoteImageInfo>(); + return []; } // TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here @@ -89,17 +89,28 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies if (movie?.Images is null) { - return Enumerable.Empty<RemoteImageInfo>(); + return []; } var posters = movie.Images.Posters; var backdrops = movie.Images.Backdrops; var logos = movie.Images.Logos; - var remoteImages = new List<RemoteImageInfo>(posters.Count + backdrops.Count + logos.Count); + var remoteImages = new List<RemoteImageInfo>(posters?.Count ?? 0 + backdrops?.Count ?? 0 + logos?.Count ?? 0); - remoteImages.AddRange(_tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language)); - remoteImages.AddRange(_tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language)); - remoteImages.AddRange(_tmdbClientManager.ConvertLogosToRemoteImageInfo(logos, language)); + if (posters is not null) + { + remoteImages.AddRange(_tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language)); + } + + if (backdrops is not null) + { + remoteImages.AddRange(_tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language)); + } + + if (logos is not null) + { + remoteImages.AddRange(_tmdbClientManager.ConvertLogosToRemoteImageInfo(logos, language)); + } return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index 414a0a3c9b..ff584ba1de 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -15,6 +15,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using TMDbLib.Objects.Find; +using TMDbLib.Objects.General; using TMDbLib.Objects.Search; namespace MediaBrowser.Providers.Plugins.Tmdb.Movies @@ -84,7 +85,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies remoteResult.SetProviderId(MetadataProvider.Tmdb, movie.Id.ToString(CultureInfo.InvariantCulture)); remoteResult.TrySetProviderId(MetadataProvider.Imdb, movie.ImdbId); - return new[] { remoteResult }; + return [remoteResult]; } } @@ -118,6 +119,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies .ConfigureAwait(false); } + if (movieResults is null) + { + return []; + } + var len = movieResults.Count; var remoteSearchResults = new RemoteSearchResult[len]; for (var i = 0; i < len; i++) @@ -158,7 +164,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); - if (searchResults.Count > 0) + if (searchResults?.Count > 0) { tmdbId = searchResults[0].Id.ToString(CultureInfo.InvariantCulture); } @@ -167,7 +173,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies if (string.IsNullOrEmpty(tmdbId) && !string.IsNullOrEmpty(imdbId)) { var movieResultFromImdbId = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); - if (movieResultFromImdbId?.MovieResults.Count > 0) + if (movieResultFromImdbId?.MovieResults?.Count > 0) { tmdbId = movieResultFromImdbId.MovieResults[0].Id.ToString(CultureInfo.InvariantCulture); } @@ -193,7 +199,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies OriginalTitle = movieResult.OriginalTitle, Overview = movieResult.Overview?.Replace("\n\n", "\n", StringComparison.InvariantCulture), Tagline = movieResult.Tagline, - ProductionLocations = movieResult.ProductionCountries.Select(pc => pc.Name).ToArray() + ProductionLocations = movieResult.ProductionCountries?.Select(pc => pc.Name).ToArray() ?? Array.Empty<string>() }; var metadataResult = new MetadataResult<Movie> { @@ -218,14 +224,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies var ourRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, info.MetadataCountryCode, StringComparison.OrdinalIgnoreCase)); - if (ourRelease is not null) + if (ourRelease?.Certification is not null) { - movie.OfficialRating = TmdbUtils.BuildParentalRating(ourRelease.Iso_3166_1, ourRelease.Certification); + movie.OfficialRating = TmdbUtils.BuildParentalRating(info.MetadataCountryCode, ourRelease.Certification); } else { var usRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.OrdinalIgnoreCase)); - if (usRelease is not null) + if (usRelease?.Certification is not null) { movie.OfficialRating = usRelease.Certification; } @@ -242,16 +248,23 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies var genres = movieResult.Genres; - foreach (var genre in genres.Select(g => g.Name).Trimmed()) + if (genres is not null) { - movie.AddGenre(genre); + foreach (var genre in genres.Select(g => g.Name).Trimmed()) + { + movie.AddGenre(genre); + } } if (movieResult.Keywords?.Keywords is not null) { - for (var i = 0; i < movieResult.Keywords.Keywords.Count; i++) + foreach (var keyword in movieResult.Keywords.Keywords) { - movie.AddTag(movieResult.Keywords.Keywords[i].Name); + var name = keyword.Name; + if (!string.IsNullOrWhiteSpace(name)) + { + movie.AddTag(name); + } } } @@ -303,9 +316,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies CrewMember = crewMember, PersonType = TmdbUtils.MapCrewToPersonType(crewMember) }) - .Where(entry => - TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) || - TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)); + .Where(entry => TmdbUtils.WantedCrewKinds.Contains(entry.PersonType)); if (config.HideMissingCrewMembers) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs index 4b32d0f6bf..64ab98b262 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs @@ -56,13 +56,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People } result.SetProviderId(MetadataProvider.Tmdb, personResult.Id.ToString(CultureInfo.InvariantCulture)); - result.TrySetProviderId(MetadataProvider.Imdb, personResult.ExternalIds.ImdbId); + result.TrySetProviderId(MetadataProvider.Imdb, personResult.ExternalIds?.ImdbId); - return new[] { result }; + return [result]; } } var personSearchResult = await _tmdbClientManager.SearchPersonAsync(searchInfo.Name, cancellationToken).ConfigureAwait(false); + if (personSearchResult is null) + { + return []; + } var remoteSearchResults = new RemoteSearchResult[personSearchResult.Count]; for (var i = 0; i < personSearchResult.Count; i++) @@ -91,7 +95,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People if (personTmdbId <= 0) { var personSearchResults = await _tmdbClientManager.SearchPersonAsync(info.Name, cancellationToken).ConfigureAwait(false); - if (personSearchResults.Count > 0) + if (personSearchResults?.Count > 0) { personTmdbId = personSearchResults[0].Id; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index e30c555cb4..f0e159f098 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -275,9 +275,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV CrewMember = crewMember, PersonType = TmdbUtils.MapCrewToPersonType(crewMember) }) - .Where(entry => - TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) || - TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)); + .Where(entry => TmdbUtils.WantedCrewKinds.Contains(entry.PersonType)); if (config.HideMissingCrewMembers) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs index 1b429039e7..1eb522137d 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs @@ -76,7 +76,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV result.Item.Name = seasonResult.Name; } - result.Item.TrySetProviderId(MetadataProvider.Tvdb, seasonResult.ExternalIds.TvdbId); + result.Item.TrySetProviderId(MetadataProvider.Tvdb, seasonResult.ExternalIds?.TvdbId); // TODO why was this disabled? var credits = seasonResult.Credits; @@ -120,9 +120,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV CrewMember = crewMember, PersonType = TmdbUtils.MapCrewToPersonType(crewMember) }) - .Where(entry => - TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) || - TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)); + .Where(entry => TmdbUtils.WantedCrewKinds.Contains(entry.PersonType)); if (config.HideMissingCrewMembers) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs index 5cba84dcb3..f2e7d0c6e4 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs @@ -79,11 +79,22 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var posters = series.Images.Posters; var backdrops = series.Images.Backdrops; var logos = series.Images.Logos; - var remoteImages = new List<RemoteImageInfo>(posters.Count + backdrops.Count + logos.Count); + var remoteImages = new List<RemoteImageInfo>(posters?.Count ?? 0 + backdrops?.Count ?? 0 + logos?.Count ?? 0); - remoteImages.AddRange(_tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language)); - remoteImages.AddRange(_tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language)); - remoteImages.AddRange(_tmdbClientManager.ConvertLogosToRemoteImageInfo(logos, language)); + if (posters is not null) + { + remoteImages.AddRange(_tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language)); + } + + if (backdrops is not null) + { + remoteImages.AddRange(_tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language)); + } + + if (logos is not null) + { + remoteImages.AddRange(_tmdbClientManager.ConvertLogosToRemoteImageInfo(logos, language)); + } return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index f0828e8263..7e36c1e204 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -112,6 +112,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var tvSearchResults = await _tmdbClientManager.SearchSeriesAsync(searchInfo.Name, searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode, cancellationToken: cancellationToken) .ConfigureAwait(false); + if (tvSearchResults is null) + { + return []; + } var remoteResults = new RemoteSearchResult[tvSearchResults.Count]; for (var i = 0; i < tvSearchResults.Count; i++) @@ -141,6 +145,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV } remoteResult.PremiereDate = series.FirstAirDate?.ToUniversalTime(); + remoteResult.ProductionYear = series.FirstAirDate?.Year; return remoteResult; } @@ -157,6 +162,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV remoteResult.SetProviderId(MetadataProvider.Tmdb, series.Id.ToString(CultureInfo.InvariantCulture)); remoteResult.PremiereDate = series.FirstAirDate?.ToUniversalTime(); + remoteResult.ProductionYear = series.FirstAirDate?.Year; return remoteResult; } @@ -174,7 +180,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (string.IsNullOrEmpty(tmdbId) && info.TryGetProviderId(MetadataProvider.Imdb, out var imdbId)) { var searchResult = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); - if (searchResult?.TvResults.Count > 0) + if (searchResult?.TvResults?.Count > 0) { tmdbId = searchResult.TvResults[0].Id.ToString(CultureInfo.InvariantCulture); } @@ -183,7 +189,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (string.IsNullOrEmpty(tmdbId) && info.TryGetProviderId(MetadataProvider.Tvdb, out var tvdbId)) { var searchResult = await _tmdbClientManager.FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); - if (searchResult?.TvResults.Count > 0) + if (searchResult?.TvResults?.Count > 0) { tmdbId = searchResult.TvResults[0].Id.ToString(CultureInfo.InvariantCulture); } @@ -198,7 +204,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var cleanedName = TmdbUtils.CleanName(parsedName.Name); var searchResults = await _tmdbClientManager.SearchSeriesAsync(cleanedName, info.MetadataLanguage, info.MetadataCountryCode, info.Year ?? parsedName.Year ?? 0, cancellationToken).ConfigureAwait(false); - if (searchResults.Count > 0) + if (searchResults?.Count > 0) { tmdbId = searchResults[0].Id.ToString(CultureInfo.InvariantCulture); } @@ -262,15 +268,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (seriesResult.Keywords?.Results is not null) { - for (var i = 0; i < seriesResult.Keywords.Results.Count; i++) + foreach (var result in seriesResult.Keywords.Results) { - series.AddTag(seriesResult.Keywords.Results[i].Name); + var name = result.Name; + if (!string.IsNullOrWhiteSpace(name)) + { + series.AddTag(name); + } } } series.HomePageUrl = seriesResult.Homepage; - series.RunTimeTicks = seriesResult.EpisodeRunTime.Select(i => TimeSpan.FromMinutes(i).Ticks).FirstOrDefault(); + series.RunTimeTicks = seriesResult.EpisodeRunTime?.Select(i => TimeSpan.FromMinutes(i).Ticks).FirstOrDefault(); if (Emby.Naming.TV.TvParserHelpers.TryParseSeriesStatus(seriesResult.Status, out var seriesStatus)) { @@ -279,6 +289,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV series.EndDate = seriesResult.LastAirDate; series.PremiereDate = seriesResult.FirstAirDate; + series.ProductionYear = seriesResult.FirstAirDate?.Year; var ids = seriesResult.ExternalIds; if (ids is not null) @@ -288,21 +299,21 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV series.TrySetProviderId(MetadataProvider.Tvdb, ids.TvdbId); } - var contentRatings = seriesResult.ContentRatings.Results ?? new List<ContentRating>(); + var contentRatings = seriesResult.ContentRatings?.Results ?? new List<ContentRating>(); var ourRelease = contentRatings.FirstOrDefault(c => string.Equals(c.Iso_3166_1, preferredCountryCode, StringComparison.OrdinalIgnoreCase)); var usRelease = contentRatings.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.OrdinalIgnoreCase)); var minimumRelease = contentRatings.FirstOrDefault(); - if (ourRelease is not null) + if (ourRelease?.Rating is not null) { - series.OfficialRating = TmdbUtils.BuildParentalRating(ourRelease.Iso_3166_1, ourRelease.Rating); + series.OfficialRating = TmdbUtils.BuildParentalRating(preferredCountryCode, ourRelease.Rating); } - else if (usRelease is not null) + else if (usRelease?.Rating is not null) { series.OfficialRating = usRelease.Rating; } - else if (minimumRelease is not null) + else if (minimumRelease?.Rating is not null) { series.OfficialRating = minimumRelease.Rating; } @@ -347,7 +358,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV Role = actor.Character?.Trim() ?? string.Empty, Type = PersonKind.Actor, SortOrder = actor.Order, - ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath) + // NOTE: Null values are filtered out above + ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath!) }; if (actor.Id > 0) @@ -367,9 +379,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV CrewMember = crewMember, PersonType = TmdbUtils.MapCrewToPersonType(crewMember) }) - .Where(entry => - TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) || - TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)); + .Where(entry => TmdbUtils.WantedCrewKinds.Contains(entry.PersonType)); if (config.HideMissingCrewMembers) { @@ -390,7 +400,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV Name = crewMember.Name.Trim(), Role = crewMember.Job?.Trim() ?? string.Empty, Type = entry.PersonType, - ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath) + // NOTE: Null values are filtered out above + ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath!) }; if (crewMember.Id > 0) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index fedf345988..274db347ba 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Globalization; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Data.Enums; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; @@ -195,7 +194,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb await EnsureClientConfigAsync().ConfigureAwait(false); var series = await GetSeriesAsync(tvShowId, language, imageLanguages, countryCode, cancellationToken).ConfigureAwait(false); - var episodeGroupId = series?.EpisodeGroups.Results.Find(g => g.Type == groupType)?.Id; + var episodeGroupId = series?.EpisodeGroups?.Results?.Find(g => g.Type == groupType)?.Id; if (episodeGroupId is null) { @@ -263,7 +262,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="countryCode">The country code, ISO 3166-1.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb tv episode information or null if not found.</returns> - public async Task<TvEpisode?> GetEpisodeAsync(int tvShowId, int seasonNumber, int episodeNumber, string displayOrder, string? language, string? imageLanguages, string? countryCode, CancellationToken cancellationToken) + public async Task<TvEpisode?> GetEpisodeAsync(int tvShowId, int seasonNumber, long episodeNumber, string displayOrder, string? language, string? imageLanguages, string? countryCode, CancellationToken cancellationToken) { var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}"; if (_memoryCache.TryGetValue(key, out TvEpisode? episode)) @@ -276,9 +275,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb var group = await GetSeriesGroupAsync(tvShowId, displayOrder, language, imageLanguages, countryCode, cancellationToken).ConfigureAwait(false); if (group is not null) { - var season = group.Groups.Find(s => s.Order == seasonNumber); + var season = group.Groups?.Find(s => s.Order == seasonNumber); // Episode order starts at 0 - var ep = season?.Episodes.Find(e => e.Order == episodeNumber - 1); + var ep = season?.Episodes?.Find(e => e.Order == episodeNumber - 1); if (ep is not null) { seasonNumber = ep.SeasonNumber; @@ -382,7 +381,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="year">The year the tv show first aired.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb tv show information.</returns> - public async Task<IReadOnlyList<SearchTv>> SearchSeriesAsync(string name, string language, string? countryCode, int year = 0, CancellationToken cancellationToken = default) + public async Task<IReadOnlyList<SearchTv>?> SearchSeriesAsync(string name, string language, string? countryCode, int year = 0, CancellationToken cancellationToken = default) { var key = $"searchseries-{name}-{year.ToString(CultureInfo.InvariantCulture)}-{language}"; if (_memoryCache.TryGetValue(key, out SearchContainer<SearchTv>? series) && series is not null) @@ -396,12 +395,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb .SearchTvShowAsync(name, TmdbUtils.NormalizeLanguage(language, countryCode), includeAdult: Plugin.Instance.Configuration.IncludeAdult, firstAirDateYear: year, cancellationToken: cancellationToken) .ConfigureAwait(false); - if (searchResults.Results.Count > 0) + if (searchResults?.Results?.Count > 0) { _memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours)); } - return searchResults.Results; + return searchResults?.Results; } /// <summary> @@ -410,7 +409,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="name">The name of the person.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb person information.</returns> - public async Task<IReadOnlyList<SearchPerson>> SearchPersonAsync(string name, CancellationToken cancellationToken) + public async Task<IReadOnlyList<SearchPerson>?> SearchPersonAsync(string name, CancellationToken cancellationToken) { var key = $"searchperson-{name}"; if (_memoryCache.TryGetValue(key, out SearchContainer<SearchPerson>? person) && person is not null) @@ -424,12 +423,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb .SearchPersonAsync(name, includeAdult: Plugin.Instance.Configuration.IncludeAdult, cancellationToken: cancellationToken) .ConfigureAwait(false); - if (searchResults.Results.Count > 0) + if (searchResults?.Results?.Count > 0) { _memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours)); } - return searchResults.Results; + return searchResults?.Results; } /// <summary> @@ -439,7 +438,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="language">The movie's language.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb movie information.</returns> - public Task<IReadOnlyList<SearchMovie>> SearchMovieAsync(string name, string language, CancellationToken cancellationToken) + public Task<IReadOnlyList<SearchMovie>?> SearchMovieAsync(string name, string language, CancellationToken cancellationToken) { return SearchMovieAsync(name, 0, language, null, cancellationToken); } @@ -453,7 +452,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="countryCode">The country code, ISO 3166-1.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb movie information.</returns> - public async Task<IReadOnlyList<SearchMovie>> SearchMovieAsync(string name, int year, string language, string? countryCode, CancellationToken cancellationToken) + public async Task<IReadOnlyList<SearchMovie>?> SearchMovieAsync(string name, int year, string language, string? countryCode, CancellationToken cancellationToken) { var key = $"moviesearch-{name}-{year.ToString(CultureInfo.InvariantCulture)}-{language}"; if (_memoryCache.TryGetValue(key, out SearchContainer<SearchMovie>? movies) && movies is not null) @@ -467,12 +466,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb .SearchMovieAsync(name, TmdbUtils.NormalizeLanguage(language, countryCode), includeAdult: Plugin.Instance.Configuration.IncludeAdult, year: year, cancellationToken: cancellationToken) .ConfigureAwait(false); - if (searchResults.Results.Count > 0) + if (searchResults?.Results?.Count > 0) { _memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours)); } - return searchResults.Results; + return searchResults?.Results; } /// <summary> @@ -483,7 +482,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="countryCode">The country code, ISO 3166-1.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb collection information.</returns> - public async Task<IReadOnlyList<SearchCollection>> SearchCollectionAsync(string name, string language, string? countryCode, CancellationToken cancellationToken) + public async Task<IReadOnlyList<SearchCollection>?> SearchCollectionAsync(string name, string language, string? countryCode, CancellationToken cancellationToken) { var key = $"collectionsearch-{name}-{language}"; if (_memoryCache.TryGetValue(key, out SearchContainer<SearchCollection>? collections) && collections is not null) @@ -497,12 +496,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb .SearchCollectionAsync(name, TmdbUtils.NormalizeLanguage(language, countryCode), cancellationToken: cancellationToken) .ConfigureAwait(false); - if (searchResults.Results.Count > 0) + if (searchResults?.Results?.Count > 0) { _memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours)); } - return searchResults.Results; + return searchResults?.Results; } /// <summary> @@ -511,14 +510,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="size">The image size to fetch.</param> /// <param name="path">The relative URL of the image.</param> /// <returns>The absolute URL.</returns> - private string? GetUrl(string? size, string path) + private string? GetUrl(string? size, string? path) { if (string.IsNullOrEmpty(path)) { return null; } - return _tmDbClient.GetImageUrl(size, path, true).ToString(); + // Use "original" as default size if size is null or empty to prevent malformed URLs + var imageSize = string.IsNullOrEmpty(size) ? "original" : size; + + return _tmDbClient.GetImageUrl(imageSize, path, true).ToString(); } /// <summary> @@ -526,7 +528,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// </summary> /// <param name="posterPath">The relative URL of the poster.</param> /// <returns>The absolute URL.</returns> - public string? GetPosterUrl(string posterPath) + public string? GetPosterUrl(string? posterPath) { return GetUrl(Plugin.Instance.Configuration.PosterSize, posterPath); } @@ -536,7 +538,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// </summary> /// <param name="actorProfilePath">The relative URL of the profile image.</param> /// <returns>The absolute URL.</returns> - public string? GetProfileUrl(string actorProfilePath) + public string? GetProfileUrl(string? actorProfilePath) { return GetUrl(Plugin.Instance.Configuration.ProfileSize, actorProfilePath); } @@ -639,30 +641,44 @@ namespace MediaBrowser.Providers.Plugins.Tmdb private static void ValidatePreferences(TMDbConfig config) { var imageConfig = config.Images; + if (imageConfig is null) + { + return; + } var pluginConfig = Plugin.Instance.Configuration; - if (!imageConfig.PosterSizes.Contains(pluginConfig.PosterSize)) + if (imageConfig.PosterSizes is not null + && pluginConfig.PosterSize is not null + && !imageConfig.PosterSizes.Contains(pluginConfig.PosterSize)) { pluginConfig.PosterSize = imageConfig.PosterSizes[^1]; } - if (!imageConfig.BackdropSizes.Contains(pluginConfig.BackdropSize)) + if (imageConfig.BackdropSizes is not null + && pluginConfig.BackdropSize is not null + && !imageConfig.BackdropSizes.Contains(pluginConfig.BackdropSize)) { pluginConfig.BackdropSize = imageConfig.BackdropSizes[^1]; } - if (!imageConfig.LogoSizes.Contains(pluginConfig.LogoSize)) + if (imageConfig.LogoSizes is not null + && pluginConfig.LogoSize is not null + && !imageConfig.LogoSizes.Contains(pluginConfig.LogoSize)) { pluginConfig.LogoSize = imageConfig.LogoSizes[^1]; } - if (!imageConfig.ProfileSizes.Contains(pluginConfig.ProfileSize)) + if (imageConfig.ProfileSizes is not null + && pluginConfig.ProfileSize is not null + && !imageConfig.ProfileSizes.Contains(pluginConfig.ProfileSize)) { pluginConfig.ProfileSize = imageConfig.ProfileSizes[^1]; } - if (!imageConfig.StillSizes.Contains(pluginConfig.StillSize)) + if (imageConfig.StillSizes is not null + && pluginConfig.StillSize is not null + && !imageConfig.StillSizes.Contains(pluginConfig.StillSize)) { pluginConfig.StillSize = imageConfig.StillSizes[^1]; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index f5e59a2789..39c0497bed 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -69,19 +69,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <returns>The Jellyfin person type.</returns> public static PersonKind MapCrewToPersonType(Crew crew) { - if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase) - && crew.Job.Contains("director", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(crew.Department, "directing", StringComparison.OrdinalIgnoreCase) + && string.Equals(crew.Job, "director", StringComparison.OrdinalIgnoreCase)) { return PersonKind.Director; } - if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase) - && crew.Job.Contains("producer", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(crew.Department, "production", StringComparison.OrdinalIgnoreCase) + && string.Equals(crew.Job, "producer", StringComparison.OrdinalIgnoreCase)) { return PersonKind.Producer; } - if (crew.Department.Equals("writing", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(crew.Department, "writing", StringComparison.OrdinalIgnoreCase) + && (string.Equals(crew.Job, "writer", StringComparison.OrdinalIgnoreCase) || string.Equals(crew.Job, "screenplay", StringComparison.OrdinalIgnoreCase))) { return PersonKind.Writer; } @@ -96,9 +97,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <returns>A boolean indicating whether the video is a trailer.</returns> public static bool IsTrailerType(Video video) { - return video.Site.Equals("youtube", StringComparison.OrdinalIgnoreCase) - && (video.Type.Equals("trailer", StringComparison.OrdinalIgnoreCase) - || video.Type.Equals("teaser", StringComparison.OrdinalIgnoreCase)); + return string.Equals(video.Site, "youtube", StringComparison.OrdinalIgnoreCase) + && (string.Equals(video.Type, "trailer", StringComparison.OrdinalIgnoreCase) + || string.Equals(video.Type, "teaser", StringComparison.OrdinalIgnoreCase)); } /// <summary> @@ -116,14 +117,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb preferredLanguage = NormalizeLanguage(preferredLanguage, countryCode); languages.Add(preferredLanguage); - - if (preferredLanguage.Length == 5) // Like en-US - { - // Currently, TMDb supports 2-letter language codes only. - // They are planning to change this in the future, thus we're - // supplying both codes if we're having a 5-letter code. - languages.Add(preferredLanguage.Substring(0, 2)); - } } languages.Add("null"); @@ -184,10 +177,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="imageLanguage">The image's actual language code.</param> /// <param name="requestLanguage">The requested language code.</param> /// <returns>The language code.</returns> - public static string AdjustImageLanguage(string imageLanguage, string requestLanguage) + public static string AdjustImageLanguage(string? imageLanguage, string requestLanguage) { - if (!string.IsNullOrEmpty(imageLanguage) - && !string.IsNullOrEmpty(requestLanguage) + if (string.IsNullOrEmpty(imageLanguage)) + { + return string.Empty; + } + + if (!string.IsNullOrEmpty(requestLanguage) && requestLanguage.Length > 2 && imageLanguage.Length == 2 && requestLanguage.StartsWith(imageLanguage, StringComparison.OrdinalIgnoreCase)) |
