Browse Source

Migrate the TMDb providers to the TMDbLib library

cvium 4 years ago
parent
commit
e9524f89d6
73 changed files with 1570 additions and 3822 deletions
  1. 2 0
      Emby.Server.Implementations/ApplicationHost.cs
  2. 46 0
      MediaBrowser.Model/Extensions/EnumerableExtensions.cs
  3. 1 0
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  4. 46 97
      MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
  5. 51 203
      MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
  6. 0 14
      MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs
  7. 0 23
      MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs
  8. 0 17
      MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs
  9. 0 21
      MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs
  10. 0 19
      MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs
  11. 0 17
      MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs
  12. 0 11
      MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs
  13. 0 13
      MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs
  14. 0 11
      MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs
  15. 0 11
      MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keywords.cs
  16. 0 21
      MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs
  17. 0 17
      MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs
  18. 0 23
      MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs
  19. 0 11
      MediaBrowser.Providers/Plugins/Tmdb/Models/General/StillImages.cs
  20. 0 23
      MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs
  21. 0 11
      MediaBrowser.Providers/Plugins/Tmdb/Models/General/Videos.cs
  22. 0 15
      MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/BelongsToCollection.cs
  23. 0 19
      MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Cast.cs
  24. 0 14
      MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Casts.cs
  25. 0 15
      MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Country.cs
  26. 0 80
      MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs
  27. 0 11
      MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCompany.cs
  28. 0 11
      MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCountry.cs
  29. 0 11
      MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Releases.cs
  30. 0 11
      MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/SpokenLanguage.cs
  31. 0 11
      MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Trailers.cs
  32. 0 13
      MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Youtube.cs
  33. 0 12
      MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonImages.cs
  34. 0 38
      MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonResult.cs
  35. 0 11
      MediaBrowser.Providers/Plugins/Tmdb/Models/Search/ExternalIdLookupResult.cs
  36. 0 78
      MediaBrowser.Providers/Plugins/Tmdb/Models/Search/MovieResult.cs
  37. 0 31
      MediaBrowser.Providers/Plugins/Tmdb/Models/Search/PersonSearchResult.cs
  38. 0 33
      MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TmdbSearchResult.cs
  39. 0 25
      MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TvResult.cs
  40. 0 19
      MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Cast.cs
  41. 0 11
      MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRating.cs
  42. 0 11
      MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRatings.cs
  43. 0 13
      MediaBrowser.Providers/Plugins/Tmdb/Models/TV/CreatedBy.cs
  44. 0 14
      MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Credits.cs
  45. 0 23
      MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Episode.cs
  46. 0 16
      MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeCredits.cs
  47. 0 38
      MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeResult.cs
  48. 0 19
      MediaBrowser.Providers/Plugins/Tmdb/Models/TV/GuestStar.cs
  49. 0 11
      MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Network.cs
  50. 0 17
      MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Season.cs
  51. 0 12
      MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonImages.cs
  52. 0 33
      MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonResult.cs
  53. 0 71
      MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeriesResult.cs
  54. 0 309
      MediaBrowser.Providers/Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs
  55. 0 212
      MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs
  56. 0 22
      MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageSettings.cs
  57. 1 2
      MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs
  58. 128 0
      MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs
  59. 179 279
      MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
  60. 0 302
      MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs
  61. 0 34
      MediaBrowser.Providers/Plugins/Tmdb/Music/TmdbMusicVideoProvider.cs
  62. 27 73
      MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
  63. 60 184
      MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
  64. 41 71
      MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
  65. 117 118
      MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
  66. 0 156
      MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs
  67. 30 76
      MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
  68. 61 180
      MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
  69. 45 114
      MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
  70. 177 336
      MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
  71. 469 0
      MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
  72. 89 1
      MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
  73. 0 43
      MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs

+ 2 - 0
Emby.Server.Implementations/ApplicationHost.cs

@@ -97,6 +97,7 @@ using MediaBrowser.Model.Tasks;
 using MediaBrowser.Providers.Chapters;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Plugins.TheTvdb;
+using MediaBrowser.Providers.Plugins.Tmdb;
 using MediaBrowser.Providers.Subtitles;
 using MediaBrowser.XbmcMetadata.Providers;
 using Microsoft.AspNetCore.Mvc;
@@ -537,6 +538,7 @@ namespace Emby.Server.Implementations
 
             ServiceCollection.AddSingleton(_fileSystemManager);
             ServiceCollection.AddSingleton<TvdbClientManager>();
+            ServiceCollection.AddSingleton<TmdbClientManager>();
 
             ServiceCollection.AddSingleton(_networkManager);
 

+ 46 - 0
MediaBrowser.Model/Extensions/EnumerableExtensions.cs

@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using MediaBrowser.Model.Providers;
+
+namespace MediaBrowser.Model.Extensions
+{
+    /// <summary>
+    /// Extension methods for <see cref="IEnumerable{T}"/>.
+    /// </summary>
+    public static class EnumerableExtensions
+    {
+        /// <summary>
+        /// Orders <see cref="RemoteImageInfo"/> by requested language in descending order, prioritizing "en" over other non-matches.
+        /// </summary>
+        /// <param name="remoteImageInfos">The remote image infos.</param>
+        /// <param name="requestedLanguage">The requested language for the images.</param>
+        /// <returns>The ordered remote image infos.</returns>
+        public static IEnumerable<RemoteImageInfo> OrderByLanguageDescending(this IEnumerable<RemoteImageInfo> remoteImageInfos, string requestedLanguage)
+        {
+            var isRequestedLanguageEn = string.Equals(requestedLanguage, "en", StringComparison.OrdinalIgnoreCase);
+
+            return remoteImageInfos.OrderByDescending(i =>
+                {
+                    if (string.Equals(requestedLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
+                    {
+                        return 3;
+                    }
+
+                    if (!isRequestedLanguageEn && string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
+                    {
+                        return 2;
+                    }
+
+                    if (string.IsNullOrEmpty(i.Language))
+                    {
+                        return isRequestedLanguageEn ? 3 : 2;
+                    }
+
+                    return 0;
+                })
+                .ThenByDescending(i => i.CommunityRating ?? 0)
+                .ThenByDescending(i => i.VoteCount ?? 0);
+        }
+    }
+}

+ 1 - 0
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -21,6 +21,7 @@
     <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.8" />
     <PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
     <PackageReference Include="PlaylistsNET" Version="1.1.2" />
+    <PackageReference Include="TMDbLib" Version="1.7.1-alpha" />
     <PackageReference Include="TvDbSharper" Version="3.2.2" />
   </ItemGroup>
 

+ 46 - 97
MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs

@@ -2,6 +2,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Linq;
 using System.Net.Http;
 using System.Threading;
@@ -12,25 +13,25 @@ using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.Collections;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-using MediaBrowser.Providers.Plugins.Tmdb.Movies;
 
 namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
 {
     public class TmdbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
     {
         private readonly IHttpClientFactory _httpClientFactory;
+        private readonly TmdbClientManager _tmdbClientManager;
 
-        public TmdbBoxSetImageProvider(IHttpClientFactory httpClientFactory)
+        public TmdbBoxSetImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
         {
             _httpClientFactory = httpClientFactory;
+            _tmdbClientManager = tmdbClientManager;
         }
 
-        public string Name => ProviderName;
+        public string Name => TmdbUtils.ProviderName;
 
-        public static string ProviderName => TmdbUtils.ProviderName;
+        public int Order => 0;
 
         public bool Supports(BaseItem item)
         {
@@ -48,112 +49,60 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
 
         public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
         {
-            var tmdbId = item.GetProviderId(MetadataProvider.Tmdb);
+            var tmdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
 
-            if (!string.IsNullOrEmpty(tmdbId))
+            if (tmdbId <= 0)
             {
-                var language = item.GetPreferredMetadataLanguage();
-
-                var mainResult = await TmdbBoxSetProvider.Current.GetMovieDbResult(tmdbId, null, cancellationToken).ConfigureAwait(false);
+                return Enumerable.Empty<RemoteImageInfo>();
+            }
 
-                if (mainResult != null)
-                {
-                    var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
+            var language = item.GetPreferredMetadataLanguage();
 
-                    var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
+            var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
 
-                    return GetImages(mainResult, language, tmdbImageUrl);
-                }
+            if (collection?.Images == null)
+            {
+                return Enumerable.Empty<RemoteImageInfo>();
             }
 
-            return new List<RemoteImageInfo>();
-        }
+            var remoteImages = new List<RemoteImageInfo>();
 
-        private IEnumerable<RemoteImageInfo> GetImages(CollectionResult obj, string language, string baseUrl)
-        {
-            var list = new List<RemoteImageInfo>();
-
-            var images = obj.Images ?? new CollectionImages();
-
-            list.AddRange(GetPosters(images).Select(i => new RemoteImageInfo
-            {
-                Url = baseUrl + i.File_Path,
-                CommunityRating = i.Vote_Average,
-                VoteCount = i.Vote_Count,
-                Width = i.Width,
-                Height = i.Height,
-                Language = TmdbMovieProvider.AdjustImageLanguage(i.Iso_639_1, language),
-                ProviderName = Name,
-                Type = ImageType.Primary,
-                RatingType = RatingType.Score
-            }));
-
-            list.AddRange(GetBackdrops(images).Select(i => new RemoteImageInfo
+            for (var i = 0; i < collection.Images.Posters.Count; i++)
             {
-                Url = baseUrl + i.File_Path,
-                CommunityRating = i.Vote_Average,
-                VoteCount = i.Vote_Count,
-                Width = i.Width,
-                Height = i.Height,
-                ProviderName = Name,
-                Type = ImageType.Backdrop,
-                RatingType = RatingType.Score
-            }));
-
-            var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
-
-            return list.OrderByDescending(i =>
-            {
-                if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
-                {
-                    return 3;
-                }
-
-                if (!isLanguageEn)
+                var poster = collection.Images.Posters[i];
+                remoteImages.Add(new RemoteImageInfo
                 {
-                    if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
-                    {
-                        return 2;
-                    }
-                }
+                    Url = _tmdbClientManager.GetPosterUrl(poster.FilePath),
+                    CommunityRating = poster.VoteAverage,
+                    VoteCount = poster.VoteCount,
+                    Width = poster.Width,
+                    Height = poster.Height,
+                    Language = TmdbUtils.AdjustImageLanguage(poster.Iso_639_1, language),
+                    ProviderName = Name,
+                    Type = ImageType.Primary,
+                    RatingType = RatingType.Score
+                });
+            }
 
-                if (string.IsNullOrEmpty(i.Language))
+            for (var i = 0; i < collection.Images.Backdrops.Count; i++)
+            {
+                var backdrop = collection.Images.Backdrops[i];
+                remoteImages.Add(new RemoteImageInfo
                 {
-                    return isLanguageEn ? 3 : 2;
-                }
-
-                return 0;
-            })
-                .ThenByDescending(i => i.CommunityRating ?? 0)
-                .ThenByDescending(i => i.VoteCount ?? 0);
-        }
-
-        /// <summary>
-        /// Gets the posters.
-        /// </summary>
-        /// <param name="images">The images.</param>
-        /// <returns>IEnumerable{MovieDbProvider.Poster}.</returns>
-        private IEnumerable<Poster> GetPosters(CollectionImages images)
-        {
-            return images.Posters ?? new List<Poster>();
-        }
-
-        /// <summary>
-        /// Gets the backdrops.
-        /// </summary>
-        /// <param name="images">The images.</param>
-        /// <returns>IEnumerable{MovieDbProvider.Backdrop}.</returns>
-        private IEnumerable<Backdrop> GetBackdrops(CollectionImages images)
-        {
-            var eligibleBackdrops = images.Backdrops == null ? new List<Backdrop>() :
-                images.Backdrops;
+                    Url = _tmdbClientManager.GetBackdropUrl(backdrop.FilePath),
+                    CommunityRating = backdrop.VoteAverage,
+                    VoteCount = backdrop.VoteCount,
+                    Width = backdrop.Width,
+                    Height = backdrop.Height,
+                    ProviderName = Name,
+                    Type = ImageType.Backdrop,
+                    RatingType = RatingType.Score
+                });
+            }
 
-            return eligibleBackdrops.OrderByDescending(i => i.Vote_Average)
-                .ThenByDescending(i => i.Vote_Count);
+            return remoteImages.OrderByLanguageDescending(language);
         }
 
-        public int Order => 0;
-
         public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
         {
             return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);

+ 51 - 203
MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs

@@ -3,268 +3,116 @@
 using System;
 using System.Collections.Generic;
 using System.Globalization;
-using System.IO;
 using System.Linq;
 using System.Net.Http;
-using System.Net.Http.Headers;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.Collections;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-using MediaBrowser.Providers.Plugins.Tmdb.Movies;
-using Microsoft.Extensions.Logging;
 
 namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
 {
     public class TmdbBoxSetProvider : IRemoteMetadataProvider<BoxSet, BoxSetInfo>
     {
-        private const string GetCollectionInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/collection/{0}?api_key={1}&append_to_response=images";
-
-        internal static TmdbBoxSetProvider Current;
-
-        private readonly ILogger<TmdbBoxSetProvider> _logger;
-        private readonly IJsonSerializer _json;
-        private readonly IServerConfigurationManager _config;
-        private readonly IFileSystem _fileSystem;
         private readonly IHttpClientFactory _httpClientFactory;
-        private readonly ILibraryManager _libraryManager;
+        private readonly TmdbClientManager _tmdbClientManager;
 
-        public TmdbBoxSetProvider(
-            ILogger<TmdbBoxSetProvider> logger,
-            IJsonSerializer json,
-            IServerConfigurationManager config,
-            IFileSystem fileSystem,
-            IHttpClientFactory httpClientFactory,
-            ILibraryManager libraryManager)
+        public TmdbBoxSetProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
         {
-            _logger = logger;
-            _json = json;
-            _config = config;
-            _fileSystem = fileSystem;
             _httpClientFactory = httpClientFactory;
-            _libraryManager = libraryManager;
-            Current = this;
+            _tmdbClientManager = tmdbClientManager;
         }
 
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+        public string Name => TmdbUtils.ProviderName;
 
         public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(BoxSetInfo searchInfo, CancellationToken cancellationToken)
         {
-            var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb);
+            var tmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
+            var language = searchInfo.MetadataLanguage;
 
-            if (!string.IsNullOrEmpty(tmdbId))
+            if (tmdbId > 0)
             {
-                await EnsureInfo(tmdbId, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
-
-                var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, searchInfo.MetadataLanguage);
-                var info = _json.DeserializeFromFile<CollectionResult>(dataFilePath);
-
-                var images = (info.Images ?? new CollectionImages()).Posters ?? new List<Poster>();
-
-                var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
+                var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
 
-                var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
+                if (collection == null)
+                {
+                    return Enumerable.Empty<RemoteSearchResult>();
+                }
 
                 var result = new RemoteSearchResult
                 {
-                    Name = info.Name,
-                    SearchProviderName = Name,
-                    ImageUrl = images.Count == 0 ? null : (tmdbImageUrl + images[0].File_Path)
+                    Name = collection.Name,
+                    SearchProviderName = Name
                 };
 
-                result.SetProviderId(MetadataProvider.Tmdb, info.Id.ToString(_usCulture));
-
-                return new[] { result };
-            }
-
-            return await new TmdbSearch(_logger, _json, _libraryManager).GetSearchResults(searchInfo, cancellationToken).ConfigureAwait(false);
-        }
-
-        public async Task<MetadataResult<BoxSet>> GetMetadata(BoxSetInfo id, CancellationToken cancellationToken)
-        {
-            var tmdbId = id.GetProviderId(MetadataProvider.Tmdb);
-
-            // We don't already have an Id, need to fetch it
-            if (string.IsNullOrEmpty(tmdbId))
-            {
-                var searchResults = await new TmdbSearch(_logger, _json, _libraryManager).GetSearchResults(id, cancellationToken).ConfigureAwait(false);
-
-                var searchResult = searchResults.FirstOrDefault();
-
-                if (searchResult != null)
-                {
-                    tmdbId = searchResult.GetProviderId(MetadataProvider.Tmdb);
-                }
-            }
-
-            var result = new MetadataResult<BoxSet>();
-
-            if (!string.IsNullOrEmpty(tmdbId))
-            {
-                var mainResult = await GetMovieDbResult(tmdbId, id.MetadataLanguage, cancellationToken).ConfigureAwait(false);
-
-                if (mainResult != null)
+                if (collection.Images != null)
                 {
-                    result.HasMetadata = true;
-                    result.Item = GetItem(mainResult);
+                    result.ImageUrl = _tmdbClientManager.GetPosterUrl(collection.PosterPath);
                 }
-            }
 
-            return result;
-        }
+                result.SetProviderId(MetadataProvider.Tmdb, collection.Id.ToString(CultureInfo.InvariantCulture));
 
-        internal async Task<CollectionResult> GetMovieDbResult(string tmdbId, string language, CancellationToken cancellationToken)
-        {
-            if (string.IsNullOrEmpty(tmdbId))
-            {
-                throw new ArgumentNullException(nameof(tmdbId));
+                return new[] { result };
             }
 
-            await EnsureInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
-
-            var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, language);
+            var collectionSearchResults = await _tmdbClientManager.SearchCollectionAsync(searchInfo.Name, language, cancellationToken).ConfigureAwait(false);
 
-            if (!string.IsNullOrEmpty(dataFilePath))
+            var collections = new List<RemoteSearchResult>();
+            for (var i = 0; i < collectionSearchResults.Count; i++)
             {
-                return _json.DeserializeFromFile<CollectionResult>(dataFilePath);
-            }
-
-            return null;
-        }
-
-        private BoxSet GetItem(CollectionResult obj)
-        {
-            var item = new BoxSet
-            {
-                Name = obj.Name,
-                Overview = obj.Overview
-            };
-
-            item.SetProviderId(MetadataProvider.Tmdb, obj.Id.ToString(_usCulture));
-
-            return item;
-        }
-
-        private async Task DownloadInfo(string tmdbId, string preferredMetadataLanguage, CancellationToken cancellationToken)
-        {
-            var mainResult = await FetchMainResult(tmdbId, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
+                var collection = new RemoteSearchResult
+                {
+                    Name = collectionSearchResults[i].Name,
+                    SearchProviderName = Name
+                };
+                collection.SetProviderId(MetadataProvider.Tmdb, collectionSearchResults[i].Id.ToString(CultureInfo.InvariantCulture));
 
-            if (mainResult == null)
-            {
-                return;
+                collections.Add(collection);
             }
 
-            var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage);
-
-            Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
-
-            _json.SerializeToFile(mainResult, dataFilePath);
+            return collections;
         }
 
-        private async Task<CollectionResult> FetchMainResult(string id, string language, CancellationToken cancellationToken)
+        public async Task<MetadataResult<BoxSet>> GetMetadata(BoxSetInfo id, CancellationToken cancellationToken)
         {
-            var url = string.Format(CultureInfo.InvariantCulture, GetCollectionInfo3, id, TmdbUtils.ApiKey);
-
-            if (!string.IsNullOrEmpty(language))
+            var tmdbId = Convert.ToInt32(id.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
+            var language = id.MetadataLanguage;
+            // We don't already have an Id, need to fetch it
+            if (tmdbId <= 0)
             {
-                url += string.Format(CultureInfo.InvariantCulture, "&language={0}", TmdbMovieProvider.NormalizeLanguage(language));
+                var searchResults = await _tmdbClientManager.SearchCollectionAsync(id.Name, language, cancellationToken).ConfigureAwait(false);
 
-                // Get images in english and with no language
-                url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language);
+                if (searchResults != null && searchResults.Count > 0)
+                {
+                    tmdbId = searchResults[0].Id;
+                }
             }
 
-            cancellationToken.ThrowIfCancellationRequested();
+            var result = new MetadataResult<BoxSet>();
 
-            using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
-            foreach (var header in TmdbUtils.AcceptHeaders)
+            if (tmdbId > 0)
             {
-                requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
-            }
-
-            using var mainResponse = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false);
-            await using var stream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
-            var mainResult = await _json.DeserializeFromStreamAsync<CollectionResult>(stream).ConfigureAwait(false);
+                var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
 
-            cancellationToken.ThrowIfCancellationRequested();
-
-            if (mainResult != null && string.IsNullOrEmpty(mainResult.Name))
-            {
-                if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
+                if (collection != null)
                 {
-                    url = string.Format(CultureInfo.InvariantCulture, GetCollectionInfo3, id, TmdbUtils.ApiKey) + "&language=en";
-
-                    if (!string.IsNullOrEmpty(language))
+                    var item = new BoxSet
                     {
-                        // Get images in english and with no language
-                        url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language);
-                    }
-
-                    using var langRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
-                    foreach (var header in TmdbUtils.AcceptHeaders)
-                    {
-                        langRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
-                    }
-
-                    await using var langStream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
-                    mainResult = await _json.DeserializeFromStreamAsync<CollectionResult>(langStream).ConfigureAwait(false);
-                }
-            }
+                        Name = collection.Name,
+                        Overview = collection.Overview
+                    };
 
-            return mainResult;
-        }
-
-        internal Task EnsureInfo(string tmdbId, string preferredMetadataLanguage, CancellationToken cancellationToken)
-        {
-            var path = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage);
+                    item.SetProviderId(MetadataProvider.Tmdb, collection.Id.ToString(CultureInfo.InvariantCulture));
 
-            var fileInfo = _fileSystem.GetFileSystemInfo(path);
-
-            if (fileInfo.Exists)
-            {
-                // If it's recent or automatic updates are enabled, don't re-download
-                if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
-                {
-                    return Task.CompletedTask;
+                    result.HasMetadata = true;
+                    result.Item = item;
                 }
             }
 
-            return DownloadInfo(tmdbId, preferredMetadataLanguage, cancellationToken);
-        }
-
-        public string Name => TmdbUtils.ProviderName;
-
-        private static string GetDataFilePath(IApplicationPaths appPaths, string tmdbId, string preferredLanguage)
-        {
-            var path = GetDataPath(appPaths, tmdbId);
-
-            var filename = string.Format(CultureInfo.InvariantCulture, "all-{0}.json", preferredLanguage ?? string.Empty);
-
-            return Path.Combine(path, filename);
-        }
-
-        private static string GetDataPath(IApplicationPaths appPaths, string tmdbId)
-        {
-            var dataPath = GetCollectionsDataPath(appPaths);
-
-            return Path.Combine(dataPath, tmdbId);
-        }
-
-        private static string GetCollectionsDataPath(IApplicationPaths appPaths)
-        {
-            var dataPath = Path.Combine(appPaths.CachePath, "tmdb-collections");
-
-            return dataPath;
+            return result;
         }
 
         public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)

+ 0 - 14
MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs

@@ -1,14 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections
-{
-    public class CollectionImages
-    {
-        public List<Backdrop> Backdrops { get; set; }
-
-        public List<Poster> Posters { get; set; }
-    }
-}

+ 0 - 23
MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs

@@ -1,23 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections
-{
-    public class CollectionResult
-    {
-        public int Id { get; set; }
-
-        public string Name { get; set; }
-
-        public string Overview { get; set; }
-
-        public string Poster_Path { get; set; }
-
-        public string Backdrop_Path { get; set; }
-
-        public List<Part> Parts { get; set; }
-
-        public CollectionImages Images { get; set; }
-    }
-}

+ 0 - 17
MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs

@@ -1,17 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections
-{
-    public class Part
-    {
-        public string Title { get; set; }
-
-        public int Id { get; set; }
-
-        public string Release_Date { get; set; }
-
-        public string Poster_Path { get; set; }
-
-        public string Backdrop_Path { get; set; }
-    }
-}

+ 0 - 21
MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs

@@ -1,21 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
-{
-    public class Backdrop
-    {
-        public double Aspect_Ratio { get; set; }
-
-        public string File_Path { get; set; }
-
-        public int Height { get; set; }
-
-        public string Iso_639_1 { get; set; }
-
-        public double Vote_Average { get; set; }
-
-        public int Vote_Count { get; set; }
-
-        public int Width { get; set; }
-    }
-}

+ 0 - 19
MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs

@@ -1,19 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
-{
-    public class Crew
-    {
-        public int Id { get; set; }
-
-        public string Credit_Id { get; set; }
-
-        public string Name { get; set; }
-
-        public string Department { get; set; }
-
-        public string Job { get; set; }
-
-        public string Profile_Path { get; set; }
-    }
-}

+ 0 - 17
MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs

@@ -1,17 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
-{
-    public class ExternalIds
-    {
-        public string Imdb_Id { get; set; }
-
-        public object Freebase_Id { get; set; }
-
-        public string Freebase_Mid { get; set; }
-
-        public int? Tvdb_Id { get; set; }
-
-        public int? Tvrage_Id { get; set; }
-    }
-}

+ 0 - 11
MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs

@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
-{
-    public class Genre
-    {
-        public int Id { get; set; }
-
-        public string Name { get; set; }
-    }
-}

+ 0 - 13
MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs

@@ -1,13 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
-{
-    public class Images
-    {
-        public List<Backdrop> Backdrops { get; set; }
-
-        public List<Poster> Posters { get; set; }
-    }
-}

+ 0 - 11
MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs

@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
-{
-    public class Keyword
-    {
-        public int Id { get; set; }
-
-        public string Name { get; set; }
-    }
-}

+ 0 - 11
MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keywords.cs

@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
-{
-    public class Keywords
-    {
-        public List<Keyword> Results { get; set; }
-    }
-}

+ 0 - 21
MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs

@@ -1,21 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
-{
-    public class Poster
-    {
-        public double Aspect_Ratio { get; set; }
-
-        public string File_Path { get; set; }
-
-        public int Height { get; set; }
-
-        public string Iso_639_1 { get; set; }
-
-        public double Vote_Average { get; set; }
-
-        public int Vote_Count { get; set; }
-
-        public int Width { get; set; }
-    }
-}

+ 0 - 17
MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs

@@ -1,17 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
-{
-    public class Profile
-    {
-        public string File_Path { get; set; }
-
-        public int Width { get; set; }
-
-        public int Height { get; set; }
-
-        public object Iso_639_1 { get; set; }
-
-        public double Aspect_Ratio { get; set; }
-    }
-}

+ 0 - 23
MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs

@@ -1,23 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
-{
-    public class Still
-    {
-        public double Aspect_Ratio { get; set; }
-
-        public string File_Path { get; set; }
-
-        public int Height { get; set; }
-
-        public string Id { get; set; }
-
-        public string Iso_639_1 { get; set; }
-
-        public double Vote_Average { get; set; }
-
-        public int Vote_Count { get; set; }
-
-        public int Width { get; set; }
-    }
-}

+ 0 - 11
MediaBrowser.Providers/Plugins/Tmdb/Models/General/StillImages.cs

@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
-{
-    public class StillImages
-    {
-        public List<Still> Stills { get; set; }
-    }
-}

+ 0 - 23
MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs

@@ -1,23 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
-{
-    public class Video
-    {
-        public string Id { get; set; }
-
-        public string Iso_639_1 { get; set; }
-
-        public string Iso_3166_1 { get; set; }
-
-        public string Key { get; set; }
-
-        public string Name { get; set; }
-
-        public string Site { get; set; }
-
-        public string Size { get; set; }
-
-        public string Type { get; set; }
-    }
-}

+ 0 - 11
MediaBrowser.Providers/Plugins/Tmdb/Models/General/Videos.cs

@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
-{
-    public class Videos
-    {
-        public IReadOnlyList<Video> Results { get; set; }
-    }
-}

+ 0 - 15
MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/BelongsToCollection.cs

@@ -1,15 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
-{
-    public class BelongsToCollection
-    {
-        public int Id { get; set; }
-
-        public string Name { get; set; }
-
-        public string Poster_Path { get; set; }
-
-        public string Backdrop_Path { get; set; }
-    }
-}

+ 0 - 19
MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Cast.cs

@@ -1,19 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
-{
-    public class Cast
-    {
-        public int Id { get; set; }
-
-        public string Name { get; set; }
-
-        public string Character { get; set; }
-
-        public int Order { get; set; }
-
-        public int Cast_Id { get; set; }
-
-        public string Profile_Path { get; set; }
-    }
-}

+ 0 - 14
MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Casts.cs

@@ -1,14 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
-{
-    public class Casts
-    {
-        public List<Cast> Cast { get; set; }
-
-        public List<Crew> Crew { get; set; }
-    }
-}

+ 0 - 15
MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Country.cs

@@ -1,15 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
-{
-    public class Country
-    {
-        public string Iso_3166_1 { get; set; }
-
-        public string Certification { get; set; }
-
-        public DateTime Release_Date { get; set; }
-    }
-}

+ 0 - 80
MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs

@@ -1,80 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
-{
-    public class MovieResult
-    {
-        public bool Adult { get; set; }
-
-        public string Backdrop_Path { get; set; }
-
-        public BelongsToCollection Belongs_To_Collection { get; set; }
-
-        public long Budget { get; set; }
-
-        public List<Genre> Genres { get; set; }
-
-        public string Homepage { get; set; }
-
-        public int Id { get; set; }
-
-        public string Imdb_Id { get; set; }
-
-        public string Original_Title { get; set; }
-
-        public string Original_Name { get; set; }
-
-        public string Overview { get; set; }
-
-        public double Popularity { get; set; }
-
-        public string Poster_Path { get; set; }
-
-        public List<ProductionCompany> Production_Companies { get; set; }
-
-        public List<ProductionCountry> Production_Countries { get; set; }
-
-        public string Release_Date { get; set; }
-
-        public long Revenue { get; set; }
-
-        public int Runtime { get; set; }
-
-        public List<SpokenLanguage> Spoken_Languages { get; set; }
-
-        public string Status { get; set; }
-
-        public string Tagline { get; set; }
-
-        public string Title { get; set; }
-
-        public string Name { get; set; }
-
-        public double Vote_Average { get; set; }
-
-        public int Vote_Count { get; set; }
-
-        public Casts Casts { get; set; }
-
-        public Releases Releases { get; set; }
-
-        public Images Images { get; set; }
-
-        public Keywords Keywords { get; set; }
-
-        public Trailers Trailers { get; set; }
-
-        public string GetOriginalTitle()
-        {
-            return Original_Name ?? Original_Title;
-        }
-
-        public string GetTitle()
-        {
-            return Name ?? Title ?? GetOriginalTitle();
-        }
-    }
-}

+ 0 - 11
MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCompany.cs

@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
-{
-    public class ProductionCompany
-    {
-        public string Name { get; set; }
-
-        public int Id { get; set; }
-    }
-}

+ 0 - 11
MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCountry.cs

@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
-{
-    public class ProductionCountry
-    {
-        public string Iso_3166_1 { get; set; }
-
-        public string Name { get; set; }
-    }
-}

+ 0 - 11
MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Releases.cs

@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
-{
-    public class Releases
-    {
-        public List<Country> Countries { get; set; }
-    }
-}

+ 0 - 11
MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/SpokenLanguage.cs

@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
-{
-    public class SpokenLanguage
-    {
-        public string Iso_639_1 { get; set; }
-
-        public string Name { get; set; }
-    }
-}

+ 0 - 11
MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Trailers.cs

@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
-{
-    public class Trailers
-    {
-        public IReadOnlyList<Youtube> Youtube { get; set; }
-    }
-}

+ 0 - 13
MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Youtube.cs

@@ -1,13 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
-{
-    public class Youtube
-    {
-        public string Name { get; set; }
-
-        public string Size { get; set; }
-
-        public string Source { get; set; }
-    }
-}

+ 0 - 12
MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonImages.cs

@@ -1,12 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.People
-{
-    public class PersonImages
-    {
-        public IReadOnlyList<Profile> Profiles { get; set; }
-    }
-}

+ 0 - 38
MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonResult.cs

@@ -1,38 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.People
-{
-    public class PersonResult
-    {
-        public bool Adult { get; set; }
-
-        public List<string> Also_Known_As { get; set; }
-
-        public string Biography { get; set; }
-
-        public string Birthday { get; set; }
-
-        public string Deathday { get; set; }
-
-        public string Homepage { get; set; }
-
-        public int Id { get; set; }
-
-        public string Imdb_Id { get; set; }
-
-        public string Name { get; set; }
-
-        public string Place_Of_Birth { get; set; }
-
-        public double Popularity { get; set; }
-
-        public string Profile_Path { get; set; }
-
-        public PersonImages Images { get; set; }
-
-        public ExternalIds External_Ids { get; set; }
-    }
-}

+ 0 - 11
MediaBrowser.Providers/Plugins/Tmdb/Models/Search/ExternalIdLookupResult.cs

@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
-{
-    public class ExternalIdLookupResult
-    {
-        public List<TvResult> Tv_Results { get; set; }
-    }
-}

+ 0 - 78
MediaBrowser.Providers/Plugins/Tmdb/Models/Search/MovieResult.cs

@@ -1,78 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
-{
-    public class MovieResult
-    {
-        /// <summary>
-        /// Gets or sets a value indicating whether this <see cref="MovieResult" /> is adult.
-        /// </summary>
-        /// <value><c>true</c> if adult; otherwise, <c>false</c>.</value>
-        public bool Adult { get; set; }
-
-        /// <summary>
-        /// Gets or sets the backdrop_path.
-        /// </summary>
-        /// <value>The backdrop_path.</value>
-        public string Backdrop_Path { get; set; }
-
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        public int Id { get; set; }
-
-        /// <summary>
-        /// Gets or sets the original_title.
-        /// </summary>
-        /// <value>The original_title.</value>
-        public string Original_Title { get; set; }
-
-        /// <summary>
-        /// Gets or sets the original_name.
-        /// </summary>
-        /// <value>The original_name.</value>
-        public string Original_Name { get; set; }
-
-        /// <summary>
-        /// Gets or sets the release_date.
-        /// </summary>
-        /// <value>The release_date.</value>
-        public string Release_Date { get; set; }
-
-        /// <summary>
-        /// Gets or sets the poster_path.
-        /// </summary>
-        /// <value>The poster_path.</value>
-        public string Poster_Path { get; set; }
-
-        /// <summary>
-        /// Gets or sets the popularity.
-        /// </summary>
-        /// <value>The popularity.</value>
-        public double Popularity { get; set; }
-
-        /// <summary>
-        /// Gets or sets the title.
-        /// </summary>
-        /// <value>The title.</value>
-        public string Title { get; set; }
-
-        /// <summary>
-        /// Gets or sets the vote_average.
-        /// </summary>
-        /// <value>The vote_average.</value>
-        public double Vote_Average { get; set; }
-
-        /// <summary>
-        /// For collection search results.
-        /// </summary>
-        public string Name { get; set; }
-
-        /// <summary>
-        /// Gets or sets the vote_count.
-        /// </summary>
-        /// <value>The vote_count.</value>
-        public int Vote_Count { get; set; }
-    }
-}

+ 0 - 31
MediaBrowser.Providers/Plugins/Tmdb/Models/Search/PersonSearchResult.cs

@@ -1,31 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
-{
-    public class PersonSearchResult
-    {
-        /// <summary>
-        /// Gets or sets a value indicating whether this <see cref="PersonSearchResult" /> is adult.
-        /// </summary>
-        /// <value><c>true</c> if adult; otherwise, <c>false</c>.</value>
-        public bool Adult { get; set; }
-
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        public int Id { get; set; }
-
-        /// <summary>
-        /// Gets or sets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        public string Name { get; set; }
-
-        /// <summary>
-        /// Gets or sets the profile_ path.
-        /// </summary>
-        /// <value>The profile_ path.</value>
-        public string Profile_Path { get; set; }
-    }
-}

+ 0 - 33
MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TmdbSearchResult.cs

@@ -1,33 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
-{
-    public class TmdbSearchResult<T>
-    {
-        /// <summary>
-        /// Gets or sets the page.
-        /// </summary>
-        /// <value>The page.</value>
-        public int Page { get; set; }
-
-        /// <summary>
-        /// Gets or sets the results.
-        /// </summary>
-        /// <value>The results.</value>
-        public List<T> Results { get; set; }
-
-        /// <summary>
-        /// Gets or sets the total_pages.
-        /// </summary>
-        /// <value>The total_pages.</value>
-        public int Total_Pages { get; set; }
-
-        /// <summary>
-        /// Gets or sets the total_results.
-        /// </summary>
-        /// <value>The total_results.</value>
-        public int Total_Results { get; set; }
-    }
-}

+ 0 - 25
MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TvResult.cs

@@ -1,25 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
-{
-    public class TvResult
-    {
-        public string Backdrop_Path { get; set; }
-
-        public string First_Air_Date { get; set; }
-
-        public int Id { get; set; }
-
-        public string Original_Name { get; set; }
-
-        public string Poster_Path { get; set; }
-
-        public double Popularity { get; set; }
-
-        public string Name { get; set; }
-
-        public double Vote_Average { get; set; }
-
-        public int Vote_Count { get; set; }
-    }
-}

+ 0 - 19
MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Cast.cs

@@ -1,19 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
-{
-    public class Cast
-    {
-        public string Character { get; set; }
-
-        public string Credit_Id { get; set; }
-
-        public int Id { get; set; }
-
-        public string Name { get; set; }
-
-        public string Profile_Path { get; set; }
-
-        public int Order { get; set; }
-    }
-}

+ 0 - 11
MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRating.cs

@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
-{
-    public class ContentRating
-    {
-        public string Iso_3166_1 { get; set; }
-
-        public string Rating { get; set; }
-    }
-}

+ 0 - 11
MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRatings.cs

@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
-{
-    public class ContentRatings
-    {
-        public List<ContentRating> Results { get; set; }
-    }
-}

+ 0 - 13
MediaBrowser.Providers/Plugins/Tmdb/Models/TV/CreatedBy.cs

@@ -1,13 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
-{
-    public class CreatedBy
-    {
-        public int Id { get; set; }
-
-        public string Name { get; set; }
-
-        public string Profile_Path { get; set; }
-    }
-}

+ 0 - 14
MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Credits.cs

@@ -1,14 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
-{
-    public class Credits
-    {
-        public List<Cast> Cast { get; set; }
-
-        public List<Crew> Crew { get; set; }
-    }
-}

+ 0 - 23
MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Episode.cs

@@ -1,23 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
-{
-    public class Episode
-    {
-        public string Air_Date { get; set; }
-
-        public int Episode_Number { get; set; }
-
-        public int Id { get; set; }
-
-        public string Name { get; set; }
-
-        public string Overview { get; set; }
-
-        public string Still_Path { get; set; }
-
-        public double Vote_Average { get; set; }
-
-        public int Vote_Count { get; set; }
-    }
-}

+ 0 - 16
MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeCredits.cs

@@ -1,16 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
-{
-    public class EpisodeCredits
-    {
-        public List<Cast> Cast { get; set; }
-
-        public List<Crew> Crew { get; set; }
-
-        public List<GuestStar> Guest_Stars { get; set; }
-    }
-}

+ 0 - 38
MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeResult.cs

@@ -1,38 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
-{
-    public class EpisodeResult
-    {
-        public DateTime Air_Date { get; set; }
-
-        public int Episode_Number { get; set; }
-
-        public string Name { get; set; }
-
-        public string Overview { get; set; }
-
-        public int Id { get; set; }
-
-        public object Production_Code { get; set; }
-
-        public int Season_Number { get; set; }
-
-        public string Still_Path { get; set; }
-
-        public double Vote_Average { get; set; }
-
-        public int Vote_Count { get; set; }
-
-        public StillImages Images { get; set; }
-
-        public ExternalIds External_Ids { get; set; }
-
-        public EpisodeCredits Credits { get; set; }
-
-        public Tmdb.Models.General.Videos Videos { get; set; }
-    }
-}

+ 0 - 19
MediaBrowser.Providers/Plugins/Tmdb/Models/TV/GuestStar.cs

@@ -1,19 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
-{
-    public class GuestStar
-    {
-        public int Id { get; set; }
-
-        public string Name { get; set; }
-
-        public string Credit_Id { get; set; }
-
-        public string Character { get; set; }
-
-        public int Order { get; set; }
-
-        public string Profile_Path { get; set; }
-    }
-}

+ 0 - 11
MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Network.cs

@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
-{
-    public class Network
-    {
-        public int Id { get; set; }
-
-        public string Name { get; set; }
-    }
-}

+ 0 - 17
MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Season.cs

@@ -1,17 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
-{
-    public class Season
-    {
-        public string Air_Date { get; set; }
-
-        public int Episode_Count { get; set; }
-
-        public int Id { get; set; }
-
-        public string Poster_Path { get; set; }
-
-        public int Season_Number { get; set; }
-    }
-}

+ 0 - 12
MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonImages.cs

@@ -1,12 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
-{
-    public class SeasonImages
-    {
-        public List<Poster> Posters { get; set; }
-    }
-}

+ 0 - 33
MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonResult.cs

@@ -1,33 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
-{
-    public class SeasonResult
-    {
-        public DateTime Air_Date { get; set; }
-
-        public List<Episode> Episodes { get; set; }
-
-        public string Name { get; set; }
-
-        public string Overview { get; set; }
-
-        public int Id { get; set; }
-
-        public string Poster_Path { get; set; }
-
-        public int Season_Number { get; set; }
-
-        public Credits Credits { get; set; }
-
-        public SeasonImages Images { get; set; }
-
-        public ExternalIds External_Ids { get; set; }
-
-        public General.Videos Videos { get; set; }
-    }
-}

+ 0 - 71
MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeriesResult.cs

@@ -1,71 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
-{
-    public class SeriesResult
-    {
-        public string Backdrop_Path { get; set; }
-
-        public List<CreatedBy> Created_By { get; set; }
-
-        public List<int> Episode_Run_Time { get; set; }
-
-        public DateTime First_Air_Date { get; set; }
-
-        public List<Genre> Genres { get; set; }
-
-        public string Homepage { get; set; }
-
-        public int Id { get; set; }
-
-        public bool In_Production { get; set; }
-
-        public List<string> Languages { get; set; }
-
-        public DateTime Last_Air_Date { get; set; }
-
-        public string Name { get; set; }
-
-        public List<Network> Networks { get; set; }
-
-        public int Number_Of_Episodes { get; set; }
-
-        public int Number_Of_Seasons { get; set; }
-
-        public string Original_Name { get; set; }
-
-        public List<string> Origin_Country { get; set; }
-
-        public string Overview { get; set; }
-
-        public string Popularity { get; set; }
-
-        public string Poster_Path { get; set; }
-
-        public List<Season> Seasons { get; set; }
-
-        public string Status { get; set; }
-
-        public double Vote_Average { get; set; }
-
-        public int Vote_Count { get; set; }
-
-        public Credits Credits { get; set; }
-
-        public Images Images { get; set; }
-
-        public Keywords Keywords { get; set; }
-
-        public ExternalIds External_Ids { get; set; }
-
-        public General.Videos Videos { get; set; }
-
-        public ContentRatings Content_Ratings { get; set; }
-
-        public string ResultLanguage { get; set; }
-    }
-}

+ 0 - 309
MediaBrowser.Providers/Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs

@@ -1,309 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.Movies;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
-{
-    public class GenericTmdbMovieInfo<T>
-        where T : BaseItem, new()
-    {
-        private readonly ILogger _logger;
-        private readonly IJsonSerializer _jsonSerializer;
-        private readonly ILibraryManager _libraryManager;
-        private readonly IFileSystem _fileSystem;
-
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
-        public GenericTmdbMovieInfo(ILogger logger, IJsonSerializer jsonSerializer, ILibraryManager libraryManager, IFileSystem fileSystem)
-        {
-            _logger = logger;
-            _jsonSerializer = jsonSerializer;
-            _libraryManager = libraryManager;
-            _fileSystem = fileSystem;
-        }
-
-        public async Task<MetadataResult<T>> GetMetadata(ItemLookupInfo itemId, CancellationToken cancellationToken)
-        {
-            var tmdbId = itemId.GetProviderId(MetadataProvider.Tmdb);
-            var imdbId = itemId.GetProviderId(MetadataProvider.Imdb);
-
-            // Don't search for music video id's because it is very easy to misidentify.
-            if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId) && typeof(T) != typeof(MusicVideo))
-            {
-                var searchResults = await new TmdbSearch(_logger, _jsonSerializer, _libraryManager).GetMovieSearchResults(itemId, cancellationToken).ConfigureAwait(false);
-
-                var searchResult = searchResults.FirstOrDefault();
-
-                if (searchResult != null)
-                {
-                    tmdbId = searchResult.GetProviderId(MetadataProvider.Tmdb);
-                }
-            }
-
-            if (!string.IsNullOrEmpty(tmdbId) || !string.IsNullOrEmpty(imdbId))
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                return await FetchMovieData(tmdbId, imdbId, itemId.MetadataLanguage, itemId.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
-            }
-
-            return new MetadataResult<T>();
-        }
-
-        /// <summary>
-        /// Fetches the movie data.
-        /// </summary>
-        /// <param name="tmdbId">The TMDB identifier.</param>
-        /// <param name="imdbId">The imdb identifier.</param>
-        /// <param name="language">The language.</param>
-        /// <param name="preferredCountryCode">The preferred country code.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{`0}.</returns>
-        private async Task<MetadataResult<T>> FetchMovieData(string tmdbId, string imdbId, string language, string preferredCountryCode, CancellationToken cancellationToken)
-        {
-            var item = new MetadataResult<T>
-            {
-                Item = new T()
-            };
-
-            string dataFilePath = null;
-            MovieResult movieInfo = null;
-
-            // Id could be ImdbId or TmdbId
-            if (string.IsNullOrEmpty(tmdbId))
-            {
-                movieInfo = await TmdbMovieProvider.Current.FetchMainResult(imdbId, false, language, cancellationToken).ConfigureAwait(false);
-                if (movieInfo != null)
-                {
-                    tmdbId = movieInfo.Id.ToString(_usCulture);
-
-                    dataFilePath = TmdbMovieProvider.Current.GetDataFilePath(tmdbId, language);
-                    Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
-                    _jsonSerializer.SerializeToFile(movieInfo, dataFilePath);
-                }
-            }
-
-            if (!string.IsNullOrWhiteSpace(tmdbId))
-            {
-                await TmdbMovieProvider.Current.EnsureMovieInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
-
-                dataFilePath = dataFilePath ?? TmdbMovieProvider.Current.GetDataFilePath(tmdbId, language);
-                movieInfo = movieInfo ?? _jsonSerializer.DeserializeFromFile<MovieResult>(dataFilePath);
-
-                var settings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
-
-                ProcessMainInfo(item, settings, preferredCountryCode, movieInfo);
-                item.HasMetadata = true;
-            }
-
-            return item;
-        }
-
-        /// <summary>
-        /// Processes the main info.
-        /// </summary>
-        /// <param name="resultItem">The result item.</param>
-        /// <param name="settings">The settings.</param>
-        /// <param name="preferredCountryCode">The preferred country code.</param>
-        /// <param name="movieData">The movie data.</param>
-        private void ProcessMainInfo(MetadataResult<T> resultItem, TmdbSettingsResult settings, string preferredCountryCode, MovieResult movieData)
-        {
-            var movie = resultItem.Item;
-
-            movie.Name = movieData.GetTitle() ?? movie.Name;
-
-            movie.OriginalTitle = movieData.GetOriginalTitle();
-
-            movie.Overview = string.IsNullOrWhiteSpace(movieData.Overview) ? null : WebUtility.HtmlDecode(movieData.Overview);
-            movie.Overview = movie.Overview != null ? movie.Overview.Replace("\n\n", "\n") : null;
-
-            // movie.HomePageUrl = movieData.homepage;
-
-            if (!string.IsNullOrEmpty(movieData.Tagline))
-            {
-                movie.Tagline = movieData.Tagline;
-            }
-
-            if (movieData.Production_Countries != null)
-            {
-                movie.ProductionLocations = movieData
-                    .Production_Countries
-                    .Select(i => i.Name)
-                    .ToArray();
-            }
-
-            movie.SetProviderId(MetadataProvider.Tmdb, movieData.Id.ToString(_usCulture));
-            movie.SetProviderId(MetadataProvider.Imdb, movieData.Imdb_Id);
-
-            if (movieData.Belongs_To_Collection != null)
-            {
-                movie.SetProviderId(MetadataProvider.TmdbCollection,
-                                    movieData.Belongs_To_Collection.Id.ToString(CultureInfo.InvariantCulture));
-
-                if (movie is Movie movieItem)
-                {
-                    movieItem.CollectionName = movieData.Belongs_To_Collection.Name;
-                }
-            }
-
-            string voteAvg = movieData.Vote_Average.ToString(CultureInfo.InvariantCulture);
-
-            if (float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var rating))
-            {
-                movie.CommunityRating = rating;
-            }
-
-            // movie.VoteCount = movieData.vote_count;
-
-            if (movieData.Releases != null && movieData.Releases.Countries != null)
-            {
-                var releases = movieData.Releases.Countries.Where(i => !string.IsNullOrWhiteSpace(i.Certification)).ToList();
-
-                var ourRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, preferredCountryCode, StringComparison.OrdinalIgnoreCase));
-                var usRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.OrdinalIgnoreCase));
-
-                if (ourRelease != null)
-                {
-                    var ratingPrefix = string.Equals(preferredCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? "" : preferredCountryCode + "-";
-                    var newRating = ratingPrefix + ourRelease.Certification;
-
-                    newRating = newRating.Replace("de-", "FSK-", StringComparison.OrdinalIgnoreCase);
-
-                    movie.OfficialRating = newRating;
-                }
-                else if (usRelease != null)
-                {
-                    movie.OfficialRating = usRelease.Certification;
-                }
-            }
-
-            if (!string.IsNullOrWhiteSpace(movieData.Release_Date))
-            {
-                // These dates are always in this exact format
-                if (DateTime.TryParse(movieData.Release_Date, _usCulture, DateTimeStyles.None, out var r))
-                {
-                    movie.PremiereDate = r.ToUniversalTime();
-                    movie.ProductionYear = movie.PremiereDate.Value.Year;
-                }
-            }
-
-            // studios
-            if (movieData.Production_Companies != null)
-            {
-                movie.SetStudios(movieData.Production_Companies.Select(c => c.Name));
-            }
-
-            // genres
-            // Movies get this from imdb
-            var genres = movieData.Genres ?? new List<Tmdb.Models.General.Genre>();
-
-            foreach (var genre in genres.Select(g => g.Name))
-            {
-                movie.AddGenre(genre);
-            }
-
-            resultItem.ResetPeople();
-            var tmdbImageUrl = settings.images.GetImageUrl("original");
-
-            // Actors, Directors, Writers - all in People
-            // actors come from cast
-            if (movieData.Casts != null && movieData.Casts.Cast != null)
-            {
-                foreach (var actor in movieData.Casts.Cast.OrderBy(a => a.Order))
-                {
-                    var personInfo = new PersonInfo
-                    {
-                        Name = actor.Name.Trim(),
-                        Role = actor.Character,
-                        Type = PersonType.Actor,
-                        SortOrder = actor.Order
-                    };
-
-                    if (!string.IsNullOrWhiteSpace(actor.Profile_Path))
-                    {
-                        personInfo.ImageUrl = tmdbImageUrl + actor.Profile_Path;
-                    }
-
-                    if (actor.Id > 0)
-                    {
-                        personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture));
-                    }
-
-                    resultItem.AddPerson(personInfo);
-                }
-            }
-
-            // and the rest from crew
-            if (movieData.Casts?.Crew != null)
-            {
-                var keepTypes = new[]
-                {
-                    PersonType.Director,
-                    PersonType.Writer,
-                    PersonType.Producer
-                };
-
-                foreach (var person in movieData.Casts.Crew)
-                {
-                    // Normalize this
-                    var type = TmdbUtils.MapCrewToPersonType(person);
-
-                    if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase) &&
-                        !keepTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
-                    {
-                        continue;
-                    }
-
-                    var personInfo = new PersonInfo
-                    {
-                        Name = person.Name.Trim(),
-                        Role = person.Job,
-                        Type = type
-                    };
-
-                    if (!string.IsNullOrWhiteSpace(person.Profile_Path))
-                    {
-                        personInfo.ImageUrl = tmdbImageUrl + person.Profile_Path;
-                    }
-
-                    if (person.Id > 0)
-                    {
-                        personInfo.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture));
-                    }
-
-                    resultItem.AddPerson(personInfo);
-                }
-            }
-
-            // if (movieData.keywords != null && movieData.keywords.keywords != null)
-            //{
-            //    movie.Keywords = movieData.keywords.keywords.Select(i => i.name).ToList();
-            //}
-
-            if (movieData.Trailers != null && movieData.Trailers.Youtube != null)
-            {
-                movie.RemoteTrailers = movieData.Trailers.Youtube.Select(i => new MediaUrl
-                {
-                    Url = string.Format(CultureInfo.InvariantCulture, "https://www.youtube.com/watch?v={0}", i.Source),
-                    Name = i.Name
-                }).ToArray();
-            }
-        }
-    }
-}

+ 0 - 212
MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs

@@ -1,212 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.Movies;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
-{
-    public class TmdbImageProvider : IRemoteImageProvider, IHasOrder
-    {
-        private readonly IJsonSerializer _jsonSerializer;
-        private readonly IHttpClientFactory _httpClientFactory;
-        private readonly IFileSystem _fileSystem;
-
-        public TmdbImageProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory, IFileSystem fileSystem)
-        {
-            _jsonSerializer = jsonSerializer;
-            _httpClientFactory = httpClientFactory;
-            _fileSystem = fileSystem;
-        }
-
-        public string Name => ProviderName;
-
-        public static string ProviderName => TmdbUtils.ProviderName;
-
-        /// <inheritdoc />
-        public int Order => 0;
-
-        public bool Supports(BaseItem item)
-        {
-            return item is Movie || item is MusicVideo || item is Trailer;
-        }
-
-        public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
-        {
-            return new List<ImageType>
-            {
-                ImageType.Primary,
-                ImageType.Backdrop
-            };
-        }
-
-        public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
-        {
-            var list = new List<RemoteImageInfo>();
-
-            var language = item.GetPreferredMetadataLanguage();
-
-            var results = await FetchImages(item, null, _jsonSerializer, cancellationToken).ConfigureAwait(false);
-
-            if (results == null)
-            {
-                return list;
-            }
-
-            var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
-
-            var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
-
-            var supportedImages = GetSupportedImages(item).ToList();
-
-            if (supportedImages.Contains(ImageType.Primary))
-            {
-                list.AddRange(GetPosters(results).Select(i => new RemoteImageInfo
-                {
-                    Url = tmdbImageUrl + i.File_Path,
-                    CommunityRating = i.Vote_Average,
-                    VoteCount = i.Vote_Count,
-                    Width = i.Width,
-                    Height = i.Height,
-                    Language = TmdbMovieProvider.AdjustImageLanguage(i.Iso_639_1, language),
-                    ProviderName = Name,
-                    Type = ImageType.Primary,
-                    RatingType = RatingType.Score
-                }));
-            }
-
-            if (supportedImages.Contains(ImageType.Backdrop))
-            {
-                list.AddRange(GetBackdrops(results).Select(i => new RemoteImageInfo
-                {
-                    Url = tmdbImageUrl + i.File_Path,
-                    CommunityRating = i.Vote_Average,
-                    VoteCount = i.Vote_Count,
-                    Width = i.Width,
-                    Height = i.Height,
-                    ProviderName = Name,
-                    Type = ImageType.Backdrop,
-                    RatingType = RatingType.Score
-                }));
-            }
-
-            var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
-
-            return list.OrderByDescending(i =>
-            {
-                if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
-                {
-                    return 3;
-                }
-
-                if (!isLanguageEn)
-                {
-                    if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
-                    {
-                        return 2;
-                    }
-                }
-
-                if (string.IsNullOrEmpty(i.Language))
-                {
-                    return isLanguageEn ? 3 : 2;
-                }
-
-                return 0;
-            })
-                .ThenByDescending(i => i.CommunityRating ?? 0)
-                .ThenByDescending(i => i.VoteCount ?? 0);
-        }
-
-        /// <summary>
-        /// Gets the posters.
-        /// </summary>
-        /// <param name="images">The images.</param>
-        /// <returns>IEnumerable{MovieDbProvider.Poster}.</returns>
-        private IEnumerable<Poster> GetPosters(Images images)
-        {
-            return images.Posters ?? new List<Poster>();
-        }
-
-        /// <summary>
-        /// Gets the backdrops.
-        /// </summary>
-        /// <param name="images">The images.</param>
-        /// <returns>IEnumerable{MovieDbProvider.Backdrop}.</returns>
-        private IEnumerable<Backdrop> GetBackdrops(Images images)
-        {
-            var eligibleBackdrops = images.Backdrops == null ? new List<Backdrop>() :
-                images.Backdrops;
-
-            return eligibleBackdrops.OrderByDescending(i => i.Vote_Average)
-                .ThenByDescending(i => i.Vote_Count);
-        }
-
-        /// <summary>
-        /// Fetches the images.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="language">The language.</param>
-        /// <param name="jsonSerializer">The json serializer.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{MovieImages}.</returns>
-        private async Task<Images> FetchImages(BaseItem item, string language, IJsonSerializer jsonSerializer, CancellationToken cancellationToken)
-        {
-            var tmdbId = item.GetProviderId(MetadataProvider.Tmdb);
-
-            if (string.IsNullOrWhiteSpace(tmdbId))
-            {
-                var imdbId = item.GetProviderId(MetadataProvider.Imdb);
-                if (!string.IsNullOrWhiteSpace(imdbId))
-                {
-                    var movieInfo = await TmdbMovieProvider.Current.FetchMainResult(imdbId, false, language, cancellationToken).ConfigureAwait(false);
-                    if (movieInfo != null)
-                    {
-                        tmdbId = movieInfo.Id.ToString(CultureInfo.InvariantCulture);
-                    }
-                }
-            }
-
-            if (string.IsNullOrWhiteSpace(tmdbId))
-            {
-                return null;
-            }
-
-            await TmdbMovieProvider.Current.EnsureMovieInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
-
-            var path = TmdbMovieProvider.Current.GetDataFilePath(tmdbId, language);
-
-            if (!string.IsNullOrEmpty(path))
-            {
-                var fileInfo = _fileSystem.GetFileInfo(path);
-
-                if (fileInfo.Exists)
-                {
-                    return jsonSerializer.DeserializeFromFile<MovieResult>(path).Images;
-                }
-            }
-
-            return null;
-        }
-
-        public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
-        {
-            return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
-        }
-    }
-}

+ 0 - 22
MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageSettings.cs

@@ -1,22 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
-{
-    internal class TmdbImageSettings
-    {
-        public IReadOnlyList<string> backdrop_sizes { get; set; }
-
-        public string secure_base_url { get; set; }
-
-        public IReadOnlyList<string> poster_sizes { get; set; }
-
-        public IReadOnlyList<string> profile_sizes { get; set; }
-
-        public string GetImageUrl(string image)
-        {
-            return secure_base_url + image;
-        }
-    }
-}

+ 1 - 2
MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs

@@ -1,4 +1,3 @@
-using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Providers;
@@ -33,7 +32,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
                 return true;
             }
 
-            return item is Movie || item is MusicVideo || item is Trailer;
+            return item is Movie;
         }
     }
 }

+ 128 - 0
MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs

@@ -0,0 +1,128 @@
+#pragma warning disable CS1591
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Extensions;
+using MediaBrowser.Model.Providers;
+using TMDbLib.Objects.Find;
+
+namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
+{
+    public class TmdbMovieImageProvider : IRemoteImageProvider, IHasOrder
+    {
+        private readonly IHttpClientFactory _httpClientFactory;
+        private readonly TmdbClientManager _tmdbClientManager;
+
+        public TmdbMovieImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
+        {
+            _httpClientFactory = httpClientFactory;
+            _tmdbClientManager = tmdbClientManager;
+        }
+
+        public int Order => 0;
+
+        public string Name => TmdbUtils.ProviderName;
+
+        public bool Supports(BaseItem item)
+        {
+            return item is Movie || item is Trailer;
+        }
+
+        public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
+        {
+            return new List<ImageType>
+            {
+                ImageType.Primary,
+                ImageType.Backdrop
+            };
+        }
+
+        public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
+        {
+            var language = item.GetPreferredMetadataLanguage();
+
+            var movieTmdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
+            if (movieTmdbId <= 0)
+            {
+                var movieImdbId = item.GetProviderId(MetadataProvider.Imdb);
+                if (string.IsNullOrEmpty(movieImdbId))
+                {
+                    return Enumerable.Empty<RemoteImageInfo>();
+                }
+
+                var movieResult = await _tmdbClientManager.FindByExternalIdAsync(movieImdbId, FindExternalSource.Imdb, language, cancellationToken).ConfigureAwait(false);
+                if (movieResult?.MovieResults != null && movieResult.MovieResults.Count > 0)
+                {
+                    movieTmdbId = movieResult.MovieResults[0].Id;
+                }
+            }
+
+            if (movieTmdbId <= 0)
+            {
+                return Enumerable.Empty<RemoteImageInfo>();
+            }
+
+            var movie = await _tmdbClientManager
+                .GetMovieAsync(movieTmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken)
+                .ConfigureAwait(false);
+
+            if (movie?.Images == null)
+            {
+                return Enumerable.Empty<RemoteImageInfo>();
+            }
+
+            var remoteImages = new List<RemoteImageInfo>();
+
+            for (var i = 0; i < movie.Images.Posters.Count; i++)
+            {
+                var poster = movie.Images.Posters[i];
+                remoteImages.Add(new RemoteImageInfo
+                {
+                    Url = _tmdbClientManager.GetPosterUrl(poster.FilePath),
+                    CommunityRating = poster.VoteAverage,
+                    VoteCount = poster.VoteCount,
+                    Width = poster.Width,
+                    Height = poster.Height,
+                    Language = TmdbUtils.AdjustImageLanguage(poster.Iso_639_1, language),
+                    ProviderName = Name,
+                    Type = ImageType.Primary,
+                    RatingType = RatingType.Score
+                });
+            }
+
+            for (var i = 0; i < movie.Images.Backdrops.Count; i++)
+            {
+                var backdrop = movie.Images.Backdrops[i];
+                remoteImages.Add(new RemoteImageInfo
+                {
+                    Url = _tmdbClientManager.GetPosterUrl(backdrop.FilePath),
+                    CommunityRating = backdrop.VoteAverage,
+                    VoteCount = backdrop.VoteCount,
+                    Width = backdrop.Width,
+                    Height = backdrop.Height,
+                    ProviderName = Name,
+                    Type = ImageType.Backdrop,
+                    RatingType = RatingType.Score
+                });
+            }
+
+            return remoteImages.OrderByLanguageDescending(language);
+        }
+
+        public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
+        {
+            return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
+        }
+    }
+}

+ 179 - 279
MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs

@@ -3,25 +3,19 @@
 using System;
 using System.Collections.Generic;
 using System.Globalization;
-using System.IO;
+using System.Linq;
 using System.Net;
 using System.Net.Http;
 using System.Net.Http.Headers;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Common;
-using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.Movies;
 using Microsoft.Extensions.Logging;
 
 namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
@@ -31,365 +25,271 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
     /// </summary>
     public class TmdbMovieProvider : IRemoteMetadataProvider<Movie, MovieInfo>, IHasOrder
     {
-        private const string TmdbConfigUrl = TmdbUtils.BaseTmdbApiUrl + "3/configuration?api_key={0}";
-        private const string GetMovieInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/movie/{0}?api_key={1}&append_to_response=casts,releases,images,keywords,trailers";
-
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
-        private readonly IJsonSerializer _jsonSerializer;
         private readonly IHttpClientFactory _httpClientFactory;
-        private readonly IFileSystem _fileSystem;
-        private readonly IServerConfigurationManager _configurationManager;
         private readonly ILogger<TmdbMovieProvider> _logger;
         private readonly ILibraryManager _libraryManager;
-        private readonly IApplicationHost _appHost;
+        private readonly TmdbClientManager _tmdbClientManager;
+
+        public string Name => TmdbUtils.ProviderName;
 
-        /// <summary>
-        /// The _TMDB settings task.
-        /// </summary>
-        private TmdbSettingsResult _tmdbSettings;
+        /// <inheritdoc />
+        public int Order => 1;
+
+        internal static TmdbMovieProvider Current { get; private set; }
 
         public TmdbMovieProvider(
-            IJsonSerializer jsonSerializer,
-            IHttpClientFactory httpClientFactory,
-            IFileSystem fileSystem,
-            IServerConfigurationManager configurationManager,
             ILogger<TmdbMovieProvider> logger,
             ILibraryManager libraryManager,
-            IApplicationHost appHost)
+            TmdbClientManager tmdbClientManager,
+            IHttpClientFactory httpClientFactory)
         {
-            _jsonSerializer = jsonSerializer;
-            _httpClientFactory = httpClientFactory;
-            _fileSystem = fileSystem;
-            _configurationManager = configurationManager;
             _logger = logger;
             _libraryManager = libraryManager;
-            _appHost = appHost;
+            _tmdbClientManager = tmdbClientManager;
+            _httpClientFactory = httpClientFactory;
             Current = this;
         }
 
-        internal static TmdbMovieProvider Current { get; private set; }
-
-        /// <inheritdoc />
-        public string Name => TmdbUtils.ProviderName;
-
-        /// <inheritdoc />
-        public int Order => 1;
-
-        public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken)
-        {
-            return GetMovieSearchResults(searchInfo, cancellationToken);
-        }
-
-        public async Task<IEnumerable<RemoteSearchResult>> GetMovieSearchResults(ItemLookupInfo searchInfo, CancellationToken cancellationToken)
+        public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken)
         {
-            var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb);
+            var tmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
 
-            if (!string.IsNullOrEmpty(tmdbId))
+            if (tmdbId == 0)
             {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                await EnsureMovieInfo(tmdbId, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
-
-                var dataFilePath = GetDataFilePath(tmdbId, searchInfo.MetadataLanguage);
-
-                var obj = _jsonSerializer.DeserializeFromFile<MovieResult>(dataFilePath);
-
-                var tmdbSettings = await GetTmdbSettings(cancellationToken).ConfigureAwait(false);
-
-                var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
-
-                var remoteResult = new RemoteSearchResult
+                var movieResults = await _tmdbClientManager
+                    .SearchMovieAsync(searchInfo.Name, searchInfo.MetadataLanguage, cancellationToken)
+                    .ConfigureAwait(false);
+                var remoteSearchResults = new List<RemoteSearchResult>();
+                for (var i = 0; i < movieResults.Count; i++)
                 {
-                    Name = obj.GetTitle(),
-                    SearchProviderName = Name,
-                    ImageUrl = string.IsNullOrWhiteSpace(obj.Poster_Path) ? null : tmdbImageUrl + obj.Poster_Path
-                };
-
-                if (!string.IsNullOrWhiteSpace(obj.Release_Date))
-                {
-                    // These dates are always in this exact format
-                    if (DateTime.TryParse(obj.Release_Date, _usCulture, DateTimeStyles.None, out var r))
+                    var movieResult = movieResults[i];
+                    var remoteSearchResult = new RemoteSearchResult
                     {
-                        remoteResult.PremiereDate = r.ToUniversalTime();
-                        remoteResult.ProductionYear = remoteResult.PremiereDate.Value.Year;
-                    }
+                        Name = movieResult.Title ?? movieResult.OriginalTitle,
+                        ImageUrl = _tmdbClientManager.GetPosterUrl(movieResult.PosterPath),
+                        Overview = movieResult.Overview,
+                        SearchProviderName = Name
+                    };
+
+                    var releaseDate = movieResult.ReleaseDate?.ToUniversalTime();
+                    remoteSearchResult.PremiereDate = releaseDate;
+                    remoteSearchResult.ProductionYear = releaseDate?.Year;
+
+                    remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, movieResult.Id.ToString(CultureInfo.InvariantCulture));
+                    remoteSearchResults.Add(remoteSearchResult);
                 }
 
-                remoteResult.SetProviderId(MetadataProvider.Tmdb, obj.Id.ToString(_usCulture));
-
-                if (!string.IsNullOrWhiteSpace(obj.Imdb_Id))
-                {
-                    remoteResult.SetProviderId(MetadataProvider.Imdb, obj.Imdb_Id);
-                }
-
-                return new[] { remoteResult };
+                return remoteSearchResults;
             }
 
-            return await new TmdbSearch(_logger, _jsonSerializer, _libraryManager).GetMovieSearchResults(searchInfo, cancellationToken).ConfigureAwait(false);
-        }
-
-        public Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
-        {
-            return GetItemMetadata<Movie>(info, cancellationToken);
-        }
+            var movie = await _tmdbClientManager
+                .GetMovieAsync(tmdbId, searchInfo.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage), cancellationToken)
+                .ConfigureAwait(false);
 
-        public Task<MetadataResult<T>> GetItemMetadata<T>(ItemLookupInfo id, CancellationToken cancellationToken)
-            where T : BaseItem, new()
-        {
-            var movieDb = new GenericTmdbMovieInfo<T>(_logger, _jsonSerializer, _libraryManager, _fileSystem);
-
-            return movieDb.GetMetadata(id, cancellationToken);
-        }
-
-        /// <summary>
-        /// Gets the TMDB settings.
-        /// </summary>
-        /// <returns>Task{TmdbSettingsResult}.</returns>
-        internal async Task<TmdbSettingsResult> GetTmdbSettings(CancellationToken cancellationToken)
-        {
-            if (_tmdbSettings != null)
+            var remoteResult = new RemoteSearchResult
             {
-                return _tmdbSettings;
-            }
+                Name = movie.Title ?? movie.OriginalTitle,
+                SearchProviderName = Name,
+                ImageUrl = _tmdbClientManager.GetPosterUrl(movie.PosterPath),
+                Overview = movie.Overview
+            };
 
-            using var requestMessage = new HttpRequestMessage(HttpMethod.Get, string.Format(CultureInfo.InvariantCulture, TmdbConfigUrl, TmdbUtils.ApiKey));
-            foreach (var header in TmdbUtils.AcceptHeaders)
+            if (movie.ReleaseDate != null)
             {
-                requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
+                var releaseDate = movie.ReleaseDate.Value.ToUniversalTime();
+                remoteResult.PremiereDate = releaseDate;
+                remoteResult.ProductionYear = releaseDate.Year;
             }
 
-            using var response = await GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false);
-            await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
-            _tmdbSettings = await _jsonSerializer.DeserializeFromStreamAsync<TmdbSettingsResult>(stream).ConfigureAwait(false);
-            return _tmdbSettings;
-        }
-
-        /// <summary>
-        /// Gets the movie data path.
-        /// </summary>
-        /// <param name="appPaths">The app paths.</param>
-        /// <param name="tmdbId">The TMDB id.</param>
-        /// <returns>System.String.</returns>
-        internal static string GetMovieDataPath(IApplicationPaths appPaths, string tmdbId)
-        {
-            var dataPath = GetMoviesDataPath(appPaths);
-
-            return Path.Combine(dataPath, tmdbId);
-        }
-
-        internal static string GetMoviesDataPath(IApplicationPaths appPaths)
-        {
-            var dataPath = Path.Combine(appPaths.CachePath, "tmdb-movies2");
-
-            return dataPath;
-        }
+            remoteResult.SetProviderId(MetadataProvider.Tmdb, movie.Id.ToString(CultureInfo.InvariantCulture));
 
-        /// <summary>
-        /// Downloads the movie info.
-        /// </summary>
-        /// <param name="id">The id.</param>
-        /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        internal async Task DownloadMovieInfo(string id, string preferredMetadataLanguage, CancellationToken cancellationToken)
-        {
-            var mainResult = await FetchMainResult(id, true, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
-
-            if (mainResult == null)
+            if (!string.IsNullOrWhiteSpace(movie.ImdbId))
             {
-                return;
+                remoteResult.SetProviderId(MetadataProvider.Imdb, movie.ImdbId);
             }
 
-            var dataFilePath = GetDataFilePath(id, preferredMetadataLanguage);
-
-            Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
-
-            _jsonSerializer.SerializeToFile(mainResult, dataFilePath);
+            return new[] { remoteResult };
         }
 
-        internal Task EnsureMovieInfo(string tmdbId, string language, CancellationToken cancellationToken)
+        public async Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
         {
-            if (string.IsNullOrEmpty(tmdbId))
-            {
-                throw new ArgumentNullException(nameof(tmdbId));
-            }
+            var tmdbId = info.GetProviderId(MetadataProvider.Tmdb);
+            var imdbId = info.GetProviderId(MetadataProvider.Imdb);
 
-            var path = GetDataFilePath(tmdbId, language);
-
-            var fileInfo = _fileSystem.GetFileSystemInfo(path);
-
-            if (fileInfo.Exists)
+            if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId))
             {
-                // If it's recent or automatic updates are enabled, don't re-download
-                if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
+                // ParseName is required here.
+                // Caller provides the filename with extension stripped and NOT the parsed filename
+                var parsedName = _libraryManager.ParseName(info.Name);
+                var searchResults = await _tmdbClientManager.SearchMovieAsync(parsedName.Name, parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+
+                if (searchResults.Count > 0)
                 {
-                    return Task.CompletedTask;
+                    tmdbId = searchResults[0].Id.ToString(CultureInfo.InvariantCulture);
                 }
             }
 
-            return DownloadMovieInfo(tmdbId, language, cancellationToken);
-        }
-
-        internal string GetDataFilePath(string tmdbId, string preferredLanguage)
-        {
             if (string.IsNullOrEmpty(tmdbId))
             {
-                throw new ArgumentNullException(nameof(tmdbId));
+                return new MetadataResult<Movie>();
             }
 
-            var path = GetMovieDataPath(_configurationManager.ApplicationPaths, tmdbId);
+            var movieResult = await _tmdbClientManager
+                .GetMovieAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
+                .ConfigureAwait(false);
 
-            if (string.IsNullOrWhiteSpace(preferredLanguage))
+            var movie = new Movie
+            {
+                Name = movieResult.Title ?? movieResult.OriginalTitle,
+                Overview = movieResult.Overview?.Replace("\n\n", "\n", StringComparison.InvariantCulture),
+                Tagline = movieResult.Tagline,
+                ProductionLocations = movieResult.ProductionCountries.Select(pc => pc.Name).ToArray()
+            };
+            var metadataResult = new MetadataResult<Movie>
+            {
+                HasMetadata = true,
+                ResultLanguage = info.MetadataLanguage,
+                Item = movie
+            };
+
+            movie.SetProviderId(MetadataProvider.Tmdb, tmdbId);
+            movie.SetProviderId(MetadataProvider.Imdb, movieResult.ImdbId);
+            if (movieResult.BelongsToCollection != null)
             {
-                preferredLanguage = "alllang";
+                movie.SetProviderId(MetadataProvider.TmdbCollection, movieResult.BelongsToCollection.Id.ToString(CultureInfo.InvariantCulture));
+                movie.CollectionName = movieResult.BelongsToCollection.Name;
             }
 
-            var filename = string.Format(CultureInfo.InvariantCulture, "all-{0}.json", preferredLanguage);
+            movie.CommunityRating = Convert.ToSingle(movieResult.VoteAverage);
 
-            return Path.Combine(path, filename);
-        }
+            if (movieResult.Releases?.Countries != null)
+            {
+                var releases = movieResult.Releases.Countries.Where(i => !string.IsNullOrWhiteSpace(i.Certification)).ToList();
 
-        public static string GetImageLanguagesParam(string preferredLanguage)
-        {
-            var languages = new List<string>();
+                var ourRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, info.MetadataCountryCode, StringComparison.OrdinalIgnoreCase));
+                var usRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.OrdinalIgnoreCase));
 
-            if (!string.IsNullOrEmpty(preferredLanguage))
-            {
-                preferredLanguage = NormalizeLanguage(preferredLanguage);
+                if (ourRelease != null)
+                {
+                    var ratingPrefix = string.Equals(info.MetadataCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? string.Empty : info.MetadataCountryCode + "-";
+                    var newRating = ratingPrefix + ourRelease.Certification;
 
-                languages.Add(preferredLanguage);
+                    newRating = newRating.Replace("de-", "FSK-", StringComparison.OrdinalIgnoreCase);
 
-                if (preferredLanguage.Length == 5) // like en-US
+                    movie.OfficialRating = newRating;
+                }
+                else if (usRelease != null)
                 {
-                    // Currenty, 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));
+                    movie.OfficialRating = usRelease.Certification;
                 }
             }
 
-            languages.Add("null");
+            movie.PremiereDate = movieResult.ReleaseDate;
+            movie.ProductionYear = movieResult.ReleaseDate?.Year;
 
-            if (!string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase))
+            if (movieResult.ProductionCompanies != null)
             {
-                languages.Add("en");
+                movie.SetStudios(movieResult.ProductionCompanies.Select(c => c.Name));
             }
 
-            return string.Join(',', languages);
-        }
+            var genres = movieResult.Genres;
 
-        public static string NormalizeLanguage(string language)
-        {
-            if (!string.IsNullOrEmpty(language))
+            foreach (var genre in genres.Select(g => g.Name))
             {
-                // They require this to be uppercase
-                // Everything after the hyphen must be written in uppercase due to a way TMDB wrote their api.
-                // See here: https://www.themoviedb.org/talk/5119221d760ee36c642af4ad?page=3#56e372a0c3a3685a9e0019ab
-                var parts = language.Split('-');
-
-                if (parts.Length == 2)
-                {
-                    language = parts[0] + "-" + parts[1].ToUpperInvariant();
-                }
+                movie.AddGenre(genre);
             }
 
-            return language;
-        }
-
-        public static string AdjustImageLanguage(string imageLanguage, string requestLanguage)
-        {
-            if (!string.IsNullOrEmpty(imageLanguage)
-                && !string.IsNullOrEmpty(requestLanguage)
-                && requestLanguage.Length > 2
-                && imageLanguage.Length == 2
-                && requestLanguage.StartsWith(imageLanguage, StringComparison.OrdinalIgnoreCase))
+            if (movieResult.Credits?.Cast != null)
             {
-                return requestLanguage;
-            }
-
-            return imageLanguage;
-        }
+                // TODO configurable
+                foreach (var actor in movieResult.Credits.Cast.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers))
+                {
+                    var personInfo = new PersonInfo
+                    {
+                        Name = actor.Name.Trim(),
+                        Role = actor.Character,
+                        Type = PersonType.Actor,
+                        SortOrder = actor.Order
+                    };
 
-        /// <summary>
-        /// Fetches the main result.
-        /// </summary>
-        /// <param name="id">The id.</param>
-        /// <param name="isTmdbId">if set to <c>true</c> [is TMDB identifier].</param>
-        /// <param name="language">The language.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{CompleteMovieData}.</returns>
-        internal async Task<MovieResult> FetchMainResult(string id, bool isTmdbId, string language, CancellationToken cancellationToken)
-        {
-            var url = string.Format(CultureInfo.InvariantCulture, GetMovieInfo3, id, TmdbUtils.ApiKey);
+                    if (!string.IsNullOrWhiteSpace(actor.ProfilePath))
+                    {
+                        personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath);
+                    }
 
-            if (!string.IsNullOrEmpty(language))
-            {
-                url += string.Format(CultureInfo.InvariantCulture, "&language={0}", NormalizeLanguage(language));
+                    if (actor.Id > 0)
+                    {
+                        personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture));
+                    }
 
-                // Get images in english and with no language
-                url += "&include_image_language=" + GetImageLanguagesParam(language);
+                    metadataResult.AddPerson(personInfo);
+                }
             }
 
-            cancellationToken.ThrowIfCancellationRequested();
-
-            using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
-            foreach (var header in TmdbUtils.AcceptHeaders)
+            if (movieResult.Credits?.Crew != null)
             {
-                requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
-            }
+                var keepTypes = new[]
+                {
+                    PersonType.Director,
+                    PersonType.Writer,
+                    PersonType.Producer
+                };
 
-            using var mainResponse = await GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false);
-            if (mainResponse.StatusCode == HttpStatusCode.NotFound)
-            {
-                return null;
-            }
+                foreach (var person in movieResult.Credits.Crew)
+                {
+                    // Normalize this
+                    var type = TmdbUtils.MapCrewToPersonType(person);
 
-            await using var stream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
-            var mainResult = await _jsonSerializer.DeserializeFromStreamAsync<MovieResult>(stream).ConfigureAwait(false);
+                    if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase) &&
+                        !keepTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+                    {
+                        continue;
+                    }
 
-            cancellationToken.ThrowIfCancellationRequested();
+                    var personInfo = new PersonInfo
+                    {
+                        Name = person.Name.Trim(),
+                        Role = person.Job,
+                        Type = type
+                    };
 
-            // If the language preference isn't english, then have the overview fallback to english if it's blank
-            if (mainResult != null &&
-                string.IsNullOrEmpty(mainResult.Overview) &&
-                !string.IsNullOrEmpty(language) &&
-                !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
-            {
-                _logger.LogInformation("MovieDbProvider couldn't find meta for language " + language + ". Trying English...");
+                    if (!string.IsNullOrWhiteSpace(person.ProfilePath))
+                    {
+                        personInfo.ImageUrl = _tmdbClientManager.GetPosterUrl(person.ProfilePath);
+                    }
 
-                url = string.Format(CultureInfo.InvariantCulture, GetMovieInfo3, id, TmdbUtils.ApiKey) + "&language=en";
+                    if (person.Id > 0)
+                    {
+                        personInfo.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture));
+                    }
 
-                if (!string.IsNullOrEmpty(language))
-                {
-                    // Get images in english and with no language
-                    url += "&include_image_language=" + GetImageLanguagesParam(language);
+                    metadataResult.AddPerson(personInfo);
                 }
+            }
+
 
-                using var langRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
-                foreach (var header in TmdbUtils.AcceptHeaders)
+            if (movieResult.Videos?.Results != null)
+            {
+                var trailers = new List<MediaUrl>();
+                for (var i = 0; i < movieResult.Videos.Results.Count; i++)
                 {
-                    langRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
-                }
+                    var video = movieResult.Videos.Results[0];
+                    if (!TmdbUtils.IsTrailerType(video))
+                    {
+                        continue;
+                    }
 
-                using var langResponse = await GetMovieDbResponse(langRequestMessage, cancellationToken).ConfigureAwait(false);
+                    trailers.Add(new MediaUrl
+                    {
+                        Url = string.Format(CultureInfo.InvariantCulture, "https://www.youtube.com/watch?v={0}", video.Key),
+                        Name = video.Name
+                    });
+                }
 
-                await using var langStream = await langResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
-                var langResult = await _jsonSerializer.DeserializeFromStreamAsync<MovieResult>(stream).ConfigureAwait(false);
-                mainResult.Overview = langResult.Overview;
+                movie.RemoteTrailers = trailers;
             }
 
-            return mainResult;
-        }
-
-        /// <summary>
-        /// Gets the movie db response.
-        /// </summary>
-        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
-        internal Task<HttpResponseMessage> GetMovieDbResponse(HttpRequestMessage message, CancellationToken cancellationToken = default)
-        {
-            message.Headers.UserAgent.ParseAdd(_appHost.ApplicationUserAgent);
-            return _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(message, cancellationToken);
+            return metadataResult;
         }
 
         /// <inheritdoc />

+ 0 - 302
MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs

@@ -1,302 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.Search;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
-{
-    public class TmdbSearch
-    {
-        private const string SearchUrl = TmdbUtils.BaseTmdbApiUrl + @"3/search/{3}?api_key={1}&query={0}&language={2}";
-        private const string SearchUrlTvWithYear = TmdbUtils.BaseTmdbApiUrl + @"3/search/tv?api_key={1}&query={0}&language={2}&first_air_date_year={3}";
-        private const string SearchUrlMovieWithYear = TmdbUtils.BaseTmdbApiUrl + @"3/search/movie?api_key={1}&query={0}&language={2}&primary_release_year={3}";
-
-        private static readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
-        private static readonly Regex _cleanEnclosed = new Regex(@"\p{Ps}.*\p{Pe}", RegexOptions.Compiled);
-        private static readonly Regex _cleanNonWord = new Regex(@"[\W_]+", RegexOptions.Compiled);
-        private static readonly Regex _cleanStopWords = new Regex(
-            @"\b( # Start at word boundary
-            19[0-9]{2}|20[0-9]{2}| # 1900-2099
-            S[0-9]{2}| # Season
-            E[0-9]{2}| # Episode
-            (2160|1080|720|576|480)[ip]?| # Resolution
-            [xh]?264| # Encoding
-            (web|dvd|bd|hdtv|hd)rip| # *Rip
-            web|hdtv|mp4|bluray|ktr|dl|single|imageset|internal|doku|dubbed|retail|xxx|flac
-            ).* # Match rest of string",
-            RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase);
-
-        private readonly ILogger _logger;
-        private readonly IJsonSerializer _json;
-        private readonly ILibraryManager _libraryManager;
-
-        public TmdbSearch(ILogger logger, IJsonSerializer json, ILibraryManager libraryManager)
-        {
-            _logger = logger;
-            _json = json;
-            _libraryManager = libraryManager;
-        }
-
-        public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo idInfo, CancellationToken cancellationToken)
-        {
-            return GetSearchResults(idInfo, "tv", cancellationToken);
-        }
-
-        public Task<IEnumerable<RemoteSearchResult>> GetMovieSearchResults(ItemLookupInfo idInfo, CancellationToken cancellationToken)
-        {
-            return GetSearchResults(idInfo, "movie", cancellationToken);
-        }
-
-        public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(BoxSetInfo idInfo, CancellationToken cancellationToken)
-        {
-            return GetSearchResults(idInfo, "collection", cancellationToken);
-        }
-
-        private async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ItemLookupInfo idInfo, string searchType, CancellationToken cancellationToken)
-        {
-            var name = idInfo.Name;
-            var year = idInfo.Year;
-
-            if (string.IsNullOrWhiteSpace(name))
-            {
-                return new List<RemoteSearchResult>();
-            }
-
-            var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
-
-            var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
-
-            // ParseName is required here.
-            // Caller provides the filename with extension stripped and NOT the parsed filename
-            var parsedName = _libraryManager.ParseName(name);
-            var yearInName = parsedName.Year;
-            name = parsedName.Name;
-            year ??= yearInName;
-
-            var language = idInfo.MetadataLanguage.ToLowerInvariant();
-
-            // Replace sequences of non-word characters with space
-            // TMDB expects a space separated list of words make sure that is the case
-            name = _cleanNonWord.Replace(name, " ").Trim();
-
-            _logger.LogInformation("TmdbSearch: Finding id for item: {0} ({1})", name, year);
-            var results = await GetSearchResults(name, searchType, year, language, tmdbImageUrl, cancellationToken).ConfigureAwait(false);
-
-            if (results.Count == 0)
-            {
-                // try in english if wasn't before
-                if (!string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
-                {
-                    results = await GetSearchResults(name, searchType, year, "en", tmdbImageUrl, cancellationToken).ConfigureAwait(false);
-                }
-            }
-
-            // TODO: retrying alternatives should be done outside the search
-            // provider so that the retry logic can be common for all search
-            // providers
-            if (results.Count == 0)
-            {
-                var name2 = parsedName.Name;
-
-                // Remove things enclosed in []{}() etc
-                name2 = _cleanEnclosed.Replace(name2, string.Empty);
-
-                // Replace sequences of non-word characters with space
-                name2 = _cleanNonWord.Replace(name2, " ");
-
-                // Clean based on common stop words / tokens
-                name2 = _cleanStopWords.Replace(name2, string.Empty);
-
-                // Trim whitespace
-                name2 = name2.Trim();
-
-                // Search again if the new name is different
-                if (!string.Equals(name2, name, StringComparison.Ordinal) && !string.IsNullOrWhiteSpace(name2))
-                {
-                    _logger.LogInformation("TmdbSearch: Finding id for item: {0} ({1})", name2, year);
-                    results = await GetSearchResults(name2, searchType, year, language, tmdbImageUrl, cancellationToken).ConfigureAwait(false);
-
-                    if (results.Count == 0 && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
-                    {
-                        // one more time, in english
-                        results = await GetSearchResults(name2, searchType, year, "en", tmdbImageUrl, cancellationToken).ConfigureAwait(false);
-                    }
-                }
-            }
-
-            return results.Where(i =>
-            {
-                if (year.HasValue && i.ProductionYear.HasValue)
-                {
-                    // Allow one year tolerance
-                    return Math.Abs(year.Value - i.ProductionYear.Value) <= 1;
-                }
-
-                return true;
-            });
-        }
-
-        private Task<List<RemoteSearchResult>> GetSearchResults(string name, string type, int? year, string language, string baseImageUrl, CancellationToken cancellationToken)
-        {
-            switch (type)
-            {
-                case "tv":
-                    return GetSearchResultsTv(name, year, language, baseImageUrl, cancellationToken);
-                default:
-                    return GetSearchResultsGeneric(name, type, year, language, baseImageUrl, cancellationToken);
-            }
-        }
-
-        private async Task<List<RemoteSearchResult>> GetSearchResultsGeneric(string name, string type, int? year, string language, string baseImageUrl, CancellationToken cancellationToken)
-        {
-            if (string.IsNullOrWhiteSpace(name))
-            {
-                throw new ArgumentException("String can't be null or empty.", nameof(name));
-            }
-
-            string url3;
-            if (year != null && string.Equals(type, "movie", StringComparison.OrdinalIgnoreCase))
-            {
-                url3 = string.Format(
-                    CultureInfo.InvariantCulture,
-                    SearchUrlMovieWithYear,
-                    WebUtility.UrlEncode(name),
-                    TmdbUtils.ApiKey,
-                    language,
-                    year);
-            }
-            else
-            {
-                url3 = string.Format(
-                    CultureInfo.InvariantCulture,
-                    SearchUrl,
-                    WebUtility.UrlEncode(name),
-                    TmdbUtils.ApiKey,
-                    language,
-                    type);
-            }
-
-            using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url3);
-            foreach (var header in TmdbUtils.AcceptHeaders)
-            {
-                requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
-            }
-
-            using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false);
-            await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
-            var searchResults = await _json.DeserializeFromStreamAsync<TmdbSearchResult<MovieResult>>(stream).ConfigureAwait(false);
-
-            var results = searchResults.Results ?? new List<MovieResult>();
-
-            return results
-                .Select(i =>
-                {
-                    var remoteResult = new RemoteSearchResult
-                    {
-                        SearchProviderName = TmdbMovieProvider.Current.Name,
-                        Name = i.Title ?? i.Name ?? i.Original_Title,
-                        ImageUrl = string.IsNullOrWhiteSpace(i.Poster_Path) ? null : baseImageUrl + i.Poster_Path
-                    };
-
-                    if (!string.IsNullOrWhiteSpace(i.Release_Date))
-                    {
-                        // These dates are always in this exact format
-                        if (DateTime.TryParseExact(i.Release_Date, "yyyy-MM-dd", _usCulture, DateTimeStyles.None, out var r))
-                        {
-                            remoteResult.PremiereDate = r.ToUniversalTime();
-                            remoteResult.ProductionYear = remoteResult.PremiereDate.Value.Year;
-                        }
-                    }
-
-                    remoteResult.SetProviderId(MetadataProvider.Tmdb, i.Id.ToString(_usCulture));
-
-                    return remoteResult;
-                })
-                .ToList();
-        }
-
-        private async Task<List<RemoteSearchResult>> GetSearchResultsTv(string name, int? year, string language, string baseImageUrl, CancellationToken cancellationToken)
-        {
-            if (string.IsNullOrWhiteSpace(name))
-            {
-                throw new ArgumentException("String can't be null or empty.", nameof(name));
-            }
-
-            string url3;
-            if (year == null)
-            {
-                url3 = string.Format(
-                CultureInfo.InvariantCulture,
-                SearchUrl,
-                WebUtility.UrlEncode(name),
-                TmdbUtils.ApiKey,
-                language,
-                "tv");
-            }
-            else
-            {
-                url3 = string.Format(
-                    CultureInfo.InvariantCulture,
-                    SearchUrlTvWithYear,
-                    WebUtility.UrlEncode(name),
-                    TmdbUtils.ApiKey,
-                    language,
-                    year);
-            }
-
-            using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url3);
-            foreach (var header in TmdbUtils.AcceptHeaders)
-            {
-                requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
-            }
-
-            using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false);
-            await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
-            var searchResults = await _json.DeserializeFromStreamAsync<TmdbSearchResult<TvResult>>(stream).ConfigureAwait(false);
-
-            var results = searchResults.Results ?? new List<TvResult>();
-
-            return results
-                .Select(i =>
-                {
-                    var remoteResult = new RemoteSearchResult
-                    {
-                        SearchProviderName = TmdbMovieProvider.Current.Name,
-                        Name = i.Name ?? i.Original_Name,
-                        ImageUrl = string.IsNullOrWhiteSpace(i.Poster_Path) ? null : baseImageUrl + i.Poster_Path
-                    };
-
-                    if (!string.IsNullOrWhiteSpace(i.First_Air_Date))
-                    {
-                        // These dates are always in this exact format
-                        if (DateTime.TryParseExact(i.First_Air_Date, "yyyy-MM-dd", _usCulture, DateTimeStyles.None, out var r))
-                        {
-                            remoteResult.PremiereDate = r.ToUniversalTime();
-                            remoteResult.ProductionYear = remoteResult.PremiereDate.Value.Year;
-                        }
-                    }
-
-                    remoteResult.SetProviderId(MetadataProvider.Tmdb, i.Id.ToString(_usCulture));
-
-                    return remoteResult;
-                })
-                .ToList();
-        }
-    }
-}

+ 0 - 34
MediaBrowser.Providers/Plugins/Tmdb/Music/TmdbMusicVideoProvider.cs

@@ -1,34 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.Tmdb.Movies;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Music
-{
-    public class TmdbMusicVideoProvider : IRemoteMetadataProvider<MusicVideo, MusicVideoInfo>
-    {
-        public string Name => TmdbMovieProvider.Current.Name;
-
-        public Task<MetadataResult<MusicVideo>> GetMetadata(MusicVideoInfo info, CancellationToken cancellationToken)
-        {
-            return TmdbMovieProvider.Current.GetItemMetadata<MusicVideo>(info, cancellationToken);
-        }
-
-        public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MusicVideoInfo searchInfo, CancellationToken cancellationToken)
-        {
-            return Task.FromResult((IEnumerable<RemoteSearchResult>)new List<RemoteSearchResult>());
-        }
-
-        public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
-        {
-            throw new NotImplementedException();
-        }
-    }
-}

+ 27 - 73
MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs

@@ -2,6 +2,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Linq;
 using System.Net.Http;
 using System.Threading;
@@ -11,31 +12,25 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.People;
-using MediaBrowser.Providers.Plugins.Tmdb.Movies;
 
 namespace MediaBrowser.Providers.Plugins.Tmdb.People
 {
     public class TmdbPersonImageProvider : IRemoteImageProvider, IHasOrder
     {
-        private readonly IServerConfigurationManager _config;
-        private readonly IJsonSerializer _jsonSerializer;
         private readonly IHttpClientFactory _httpClientFactory;
+        private readonly TmdbClientManager _tmdbClientManager;
 
-        public TmdbPersonImageProvider(IServerConfigurationManager config, IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory)
+        public TmdbPersonImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
         {
-            _config = config;
-            _jsonSerializer = jsonSerializer;
             _httpClientFactory = httpClientFactory;
+            _tmdbClientManager = tmdbClientManager;
         }
 
-        public static string ProviderName => TmdbUtils.ProviderName;
-
         /// <inheritdoc />
-        public string Name => ProviderName;
+        public string Name => TmdbUtils.ProviderName;
 
         /// <inheritdoc />
         public int Order => 0;
@@ -56,78 +51,37 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
         public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
         {
             var person = (Person)item;
-            var id = person.GetProviderId(MetadataProvider.Tmdb);
-
-            if (!string.IsNullOrEmpty(id))
-            {
-                await TmdbPersonProvider.Current.EnsurePersonInfo(id, cancellationToken).ConfigureAwait(false);
-
-                var dataFilePath = TmdbPersonProvider.GetPersonDataFilePath(_config.ApplicationPaths, id);
-
-                var result = _jsonSerializer.DeserializeFromFile<PersonResult>(dataFilePath);
-
-                var images = result.Images ?? new PersonImages();
-
-                var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
-
-                var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
-
-                return GetImages(images, item.GetPreferredMetadataLanguage(), tmdbImageUrl);
-            }
-
-            return new List<RemoteImageInfo>();
-        }
-
-        private IEnumerable<RemoteImageInfo> GetImages(PersonImages images, string preferredLanguage, string baseImageUrl)
-        {
-            var list = new List<RemoteImageInfo>();
+            var personTmdbId = Convert.ToInt32(person.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
 
-            if (images.Profiles != null)
+            if (personTmdbId > 0)
             {
-                list.AddRange(images.Profiles.Select(i => new RemoteImageInfo
+                var personResult = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false);
+                if (personResult?.Images?.Profiles == null)
                 {
-                    ProviderName = Name,
-                    Type = ImageType.Primary,
-                    Width = i.Width,
-                    Height = i.Height,
-                    Language = GetLanguage(i),
-                    Url = baseImageUrl + i.File_Path
-                }));
-            }
-
-            var language = preferredLanguage;
-
-            var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
-
-            return list.OrderByDescending(i =>
-            {
-                if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
-                {
-                    return 3;
+                    return Enumerable.Empty<RemoteImageInfo>();
                 }
 
-                if (!isLanguageEn)
-                {
-                    if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
-                    {
-                        return 2;
-                    }
-                }
+                var remoteImages = new List<RemoteImageInfo>();
+                var language = item.GetPreferredMetadataLanguage();
 
-                if (string.IsNullOrEmpty(i.Language))
+                for (var i = 0; i < personResult.Images.Profiles.Count; i++)
                 {
-                    return isLanguageEn ? 3 : 2;
+                    var image = personResult.Images.Profiles[i];
+                    remoteImages.Add(new RemoteImageInfo
+                    {
+                        ProviderName = Name,
+                        Type = ImageType.Primary,
+                        Width = image.Width,
+                        Height = image.Height,
+                        Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, language),
+                        Url = _tmdbClientManager.GetProfileUrl(image.FilePath)
+                    });
                 }
 
-                return 0;
-            })
-                .ThenByDescending(i => i.CommunityRating ?? 0)
-                .ThenByDescending(i => i.VoteCount ?? 0);
-        }
+                return remoteImages.OrderByLanguageDescending(language);
+            }
 
-        private string GetLanguage(Profile profile)
-        {
-            return profile.Iso_639_1?.ToString();
+            return new List<RemoteImageInfo>();
         }
 
         public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)

+ 60 - 184
MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs

@@ -3,198 +3,133 @@
 using System;
 using System.Collections.Generic;
 using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Net;
 using System.Net.Http;
-using System.Net.Http.Headers;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.People;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.Search;
-using MediaBrowser.Providers.Plugins.Tmdb.Movies;
 using Microsoft.Extensions.Logging;
 
 namespace MediaBrowser.Providers.Plugins.Tmdb.People
 {
     public class TmdbPersonProvider : IRemoteMetadataProvider<Person, PersonLookupInfo>
     {
-        private const string DataFileName = "info.json";
-
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
-        private readonly IJsonSerializer _jsonSerializer;
-        private readonly IFileSystem _fileSystem;
-        private readonly IServerConfigurationManager _configurationManager;
         private readonly IHttpClientFactory _httpClientFactory;
+        private readonly TmdbClientManager _tmdbClientManager;
 
-        public TmdbPersonProvider(
-            IFileSystem fileSystem,
-            IServerConfigurationManager configurationManager,
-            IJsonSerializer jsonSerializer,
-            IHttpClientFactory httpClientFactory)
+        public TmdbPersonProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
         {
-            _fileSystem = fileSystem;
-            _configurationManager = configurationManager;
-            _jsonSerializer = jsonSerializer;
             _httpClientFactory = httpClientFactory;
-            Current = this;
+            _tmdbClientManager = tmdbClientManager;
         }
 
-        internal static TmdbPersonProvider Current { get; private set; }
-
         public string Name => TmdbUtils.ProviderName;
 
         public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken)
         {
-            var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb);
-
-            var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
-
-            var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
+            var personTmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
 
-            if (!string.IsNullOrEmpty(tmdbId))
+            if (personTmdbId <= 0)
             {
-                await EnsurePersonInfo(tmdbId, cancellationToken).ConfigureAwait(false);
+                var personResult = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false);
 
-                var dataFilePath = GetPersonDataFilePath(_configurationManager.ApplicationPaths, tmdbId);
-                var info = _jsonSerializer.DeserializeFromFile<PersonResult>(dataFilePath);
-
-                IReadOnlyList<Profile> images = info.Images?.Profiles ?? Array.Empty<Profile>();
-
-                var result = new RemoteSearchResult
+                if (personResult != null)
                 {
-                    Name = info.Name,
-
-                    SearchProviderName = Name,
+                    var result = new RemoteSearchResult
+                    {
+                        Name = personResult.Name,
+                        SearchProviderName = Name,
+                        Overview = personResult.Biography
+                    };
 
-                    ImageUrl = images.Count == 0 ? null : (tmdbImageUrl + images[0].File_Path)
-                };
+                    if (personResult.Images?.Profiles != null && personResult.Images.Profiles.Count > 0)
+                    {
+                        result.ImageUrl = _tmdbClientManager.GetProfileUrl(personResult.Images.Profiles[0].FilePath);
+                    }
 
-                result.SetProviderId(MetadataProvider.Tmdb, info.Id.ToString(_usCulture));
-                result.SetProviderId(MetadataProvider.Imdb, info.Imdb_Id);
+                    result.SetProviderId(MetadataProvider.Tmdb, personResult.Id.ToString(CultureInfo.InvariantCulture));
+                    result.SetProviderId(MetadataProvider.Imdb, personResult.ExternalIds.ImdbId);
 
-                return new[] { result };
+                    return new[] { result };
+                }
             }
 
+            // TODO why? Because of the old rate limit?
             if (searchInfo.IsAutomated)
             {
                 // Don't hammer moviedb searching by name
-                return Array.Empty<RemoteSearchResult>();
+                return new List<RemoteSearchResult>();
             }
 
-            var url = string.Format(
-                CultureInfo.InvariantCulture,
-                TmdbUtils.BaseTmdbApiUrl + @"3/search/person?api_key={1}&query={0}",
-                WebUtility.UrlEncode(searchInfo.Name),
-                TmdbUtils.ApiKey);
+            var personSearchResult = await _tmdbClientManager.SearchPersonAsync(searchInfo.Name, cancellationToken).ConfigureAwait(false);
 
-            using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
-            foreach (var header in TmdbUtils.AcceptHeaders)
+            var remoteSearchResults = new List<RemoteSearchResult>();
+            for (var i = 0; i < personSearchResult.Count; i++)
             {
-                requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
-            }
-
-            var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false);
-            await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
-
-            var result2 = await _jsonSerializer.DeserializeFromStreamAsync<TmdbSearchResult<PersonSearchResult>>(stream).ConfigureAwait(false)
-                         ?? new TmdbSearchResult<PersonSearchResult>();
-
-            return result2.Results.Select(i => GetSearchResult(i, tmdbImageUrl));
-        }
-
-        private RemoteSearchResult GetSearchResult(PersonSearchResult i, string baseImageUrl)
-        {
-            var result = new RemoteSearchResult
-            {
-                SearchProviderName = Name,
-
-                Name = i.Name,
-
-                ImageUrl = string.IsNullOrEmpty(i.Profile_Path) ? null : baseImageUrl + i.Profile_Path
-            };
+                var person = personSearchResult[i];
+                var remoteSearchResult = new RemoteSearchResult
+                {
+                    SearchProviderName = Name,
+                    Name = person.Name,
+                    ImageUrl = _tmdbClientManager.GetProfileUrl(person.ProfilePath)
+                };
 
-            result.SetProviderId(MetadataProvider.Tmdb, i.Id.ToString(_usCulture));
+                remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture));
+                remoteSearchResults.Add(remoteSearchResult);
+            }
 
-            return result;
+            return remoteSearchResults;
         }
 
         public async Task<MetadataResult<Person>> GetMetadata(PersonLookupInfo id, CancellationToken cancellationToken)
         {
-            var tmdbId = id.GetProviderId(MetadataProvider.Tmdb);
+            var personTmdbId = Convert.ToInt32(id.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
 
             // We don't already have an Id, need to fetch it
-            if (string.IsNullOrEmpty(tmdbId))
+            if (personTmdbId <= 0)
             {
-                tmdbId = await GetTmdbId(id, cancellationToken).ConfigureAwait(false);
+                var personSearchResults = await _tmdbClientManager.SearchPersonAsync(id.Name, cancellationToken).ConfigureAwait(false);
+                if (personSearchResults.Count > 0)
+                {
+                    personTmdbId = personSearchResults[0].Id;
+                }
             }
 
             var result = new MetadataResult<Person>();
 
-            if (!string.IsNullOrEmpty(tmdbId))
+            if (personTmdbId > 0)
             {
-                try
-                {
-                    await EnsurePersonInfo(tmdbId, cancellationToken).ConfigureAwait(false);
-                }
-                catch (HttpException ex)
-                {
-                    if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
-                    {
-                        return result;
-                    }
-
-                    throw;
-                }
-
-                var dataFilePath = GetPersonDataFilePath(_configurationManager.ApplicationPaths, tmdbId);
-
-                var info = _jsonSerializer.DeserializeFromFile<PersonResult>(dataFilePath);
+                var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false);
 
-                var item = new Person();
                 result.HasMetadata = true;
 
-                // Take name from incoming info, don't rename the person
-                // TODO: This should go in PersonMetadataService, not each person provider
-                item.Name = id.Name;
-
-                // item.HomePageUrl = info.homepage;
-
-                if (!string.IsNullOrWhiteSpace(info.Place_Of_Birth))
+                var item = new Person
                 {
-                    item.ProductionLocations = new string[] { info.Place_Of_Birth };
-                }
-
-                item.Overview = info.Biography;
-
-                if (DateTime.TryParseExact(info.Birthday, "yyyy-MM-dd", new CultureInfo("en-US"), DateTimeStyles.None, out var date))
-                {
-                    item.PremiereDate = date.ToUniversalTime();
-                }
+                    // Take name from incoming info, don't rename the person
+                    // TODO: This should go in PersonMetadataService, not each person provider
+                    Name = id.Name,
+                    HomePageUrl = person.Homepage,
+                    Overview = person.Biography,
+                    PremiereDate = person.Birthday?.ToUniversalTime(),
+                    EndDate = person.Deathday?.ToUniversalTime()
+                };
 
-                if (DateTime.TryParseExact(info.Deathday, "yyyy-MM-dd", new CultureInfo("en-US"), DateTimeStyles.None, out date))
+                if (!string.IsNullOrWhiteSpace(person.PlaceOfBirth))
                 {
-                    item.EndDate = date.ToUniversalTime();
+                    item.ProductionLocations = new[] { person.PlaceOfBirth };
                 }
 
-                item.SetProviderId(MetadataProvider.Tmdb, info.Id.ToString(_usCulture));
+                item.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture));
 
-                if (!string.IsNullOrEmpty(info.Imdb_Id))
+                if (!string.IsNullOrEmpty(person.ImdbId))
                 {
-                    item.SetProviderId(MetadataProvider.Imdb, info.Imdb_Id);
+                    item.SetProviderId(MetadataProvider.Imdb, person.ImdbId);
                 }
 
                 result.HasMetadata = true;
@@ -204,65 +139,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
             return result;
         }
 
-        /// <summary>
-        /// Gets the TMDB id.
-        /// </summary>
-        /// <param name="info">The information.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.String}.</returns>
-        private async Task<string> GetTmdbId(PersonLookupInfo info, CancellationToken cancellationToken)
-        {
-            var results = await GetSearchResults(info, cancellationToken).ConfigureAwait(false);
-
-            return results.Select(i => i.GetProviderId(MetadataProvider.Tmdb)).FirstOrDefault();
-        }
-
-        internal async Task EnsurePersonInfo(string id, CancellationToken cancellationToken)
-        {
-            var dataFilePath = GetPersonDataFilePath(_configurationManager.ApplicationPaths, id);
-
-            var fileInfo = _fileSystem.GetFileSystemInfo(dataFilePath);
-
-            if (fileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
-            {
-                return;
-            }
-
-            var url = string.Format(
-                CultureInfo.InvariantCulture,
-                TmdbUtils.BaseTmdbApiUrl + @"3/person/{1}?api_key={0}&append_to_response=credits,images,external_ids",
-                TmdbUtils.ApiKey,
-                id);
-
-            using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
-            foreach (var header in TmdbUtils.AcceptHeaders)
-            {
-                requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
-            }
-
-            using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false);
-            Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
-            await using var fs = new FileStream(dataFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
-            await response.Content.CopyToAsync(fs).ConfigureAwait(false);
-        }
-
-        private static string GetPersonDataPath(IApplicationPaths appPaths, string tmdbId)
-        {
-            var letter = tmdbId.GetMD5().ToString().AsSpan().Slice(0, 1);
-
-            return Path.Join(GetPersonsDataPath(appPaths), letter, tmdbId);
-        }
-
-        internal static string GetPersonDataFilePath(IApplicationPaths appPaths, string tmdbId)
-        {
-            return Path.Combine(GetPersonDataPath(appPaths, tmdbId), DataFileName);
-        }
-
-        private static string GetPersonsDataPath(IApplicationPaths appPaths)
-        {
-            return Path.Combine(appPaths.CachePath, "tmdb-people");
-        }
-
         public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
         {
             return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);

+ 41 - 71
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs

@@ -2,40 +2,37 @@
 
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Linq;
 using System.Net.Http;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-using MediaBrowser.Providers.Plugins.Tmdb.Movies;
-using Microsoft.Extensions.Logging;
 
 namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 {
-    public class TmdbEpisodeImageProvider :
-            TmdbEpisodeProviderBase,
-            IRemoteImageProvider,
-            IHasOrder
+    public class TmdbEpisodeImageProvider : IRemoteImageProvider, IHasOrder
     {
-        public TmdbEpisodeImageProvider(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory)
-            : base(httpClientFactory, configurationManager, jsonSerializer, fileSystem, localization, loggerFactory)
+        private readonly IHttpClientFactory _httpClientFactory;
+        private readonly TmdbClientManager _tmdbClientManager;
+
+        public TmdbEpisodeImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
         {
+            _httpClientFactory = httpClientFactory;
+            _tmdbClientManager = tmdbClientManager;
         }
 
-        public string Name => TmdbUtils.ProviderName;
-
         // After TheTvDb
         public int Order => 1;
 
+        public string Name => TmdbUtils.ProviderName;
+
         public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
         {
             return new List<ImageType>
@@ -49,13 +46,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
             var episode = (Controller.Entities.TV.Episode)item;
             var series = episode.Series;
 
-            var seriesId = series?.GetProviderId(MetadataProvider.Tmdb);
+            var seriesTmdbId = Convert.ToInt32(series?.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
 
-            var list = new List<RemoteImageInfo>();
-
-            if (string.IsNullOrEmpty(seriesId))
+            if (seriesTmdbId <= 0)
             {
-                return list;
+                return Enumerable.Empty<RemoteImageInfo>();
             }
 
             var seasonNumber = episode.ParentIndexNumber;
@@ -63,76 +58,51 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 
             if (!seasonNumber.HasValue || !episodeNumber.HasValue)
             {
-                return list;
+                return Enumerable.Empty<RemoteImageInfo>();
             }
 
             var language = item.GetPreferredMetadataLanguage();
 
-            var response = await GetEpisodeInfo(
-                seriesId,
-                seasonNumber.Value,
-                episodeNumber.Value,
-                language,
-                cancellationToken).ConfigureAwait(false);
-
-            var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
+            var episodeResult = await _tmdbClientManager
+                .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken)
+                .ConfigureAwait(false);
 
-            var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
-
-            list.AddRange(GetPosters(response.Images).Select(i => new RemoteImageInfo
-            {
-                Url = tmdbImageUrl + i.File_Path,
-                CommunityRating = i.Vote_Average,
-                VoteCount = i.Vote_Count,
-                Width = i.Width,
-                Height = i.Height,
-                Language = TmdbMovieProvider.AdjustImageLanguage(i.Iso_639_1, language),
-                ProviderName = Name,
-                Type = ImageType.Primary,
-                RatingType = RatingType.Score
-            }));
-
-            var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
-
-            return list.OrderByDescending(i =>
+            if (episodeResult?.Images?.Stills == null)
             {
-                if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
-                {
-                    return 3;
-                }
+                return Enumerable.Empty<RemoteImageInfo>();
+            }
 
-                if (!isLanguageEn)
-                {
-                    if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
-                    {
-                        return 2;
-                    }
-                }
+            var remoteImages = new List<RemoteImageInfo>();
 
-                if (string.IsNullOrEmpty(i.Language))
+            for (var i = 0; i < episodeResult.Images.Stills.Count; i++)
+            {
+                var image = episodeResult.Images.Stills[i];
+                remoteImages.Add(new RemoteImageInfo
                 {
-                    return isLanguageEn ? 3 : 2;
-                }
-
-                return 0;
-            })
-                .ThenByDescending(i => i.CommunityRating ?? 0)
-                .ThenByDescending(i => i.VoteCount ?? 0);
-        }
+                    Url = _tmdbClientManager.GetStillUrl(image.FilePath),
+                    CommunityRating = image.VoteAverage,
+                    VoteCount = image.VoteCount,
+                    Width = image.Width,
+                    Height = image.Height,
+                    Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, language),
+                    ProviderName = Name,
+                    Type = ImageType.Primary,
+                    RatingType = RatingType.Score
+                });
+            }
 
-        private IEnumerable<Still> GetPosters(StillImages images)
-        {
-            return images.Stills ?? new List<Still>();
+            return remoteImages.OrderByLanguageDescending(language);
         }
 
         public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
         {
-            return GetResponse(url, cancellationToken);
+            return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
         }
 
         public bool Supports(BaseItem item)
         {
             return item is Controller.Entities.TV.Episode;
         }
+
     }
 }

+ 117 - 118
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs

@@ -4,10 +4,10 @@ using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
-using System.Net;
 using System.Net.Http;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
@@ -15,21 +15,21 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Serialization;
 using Microsoft.Extensions.Logging;
 
 namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 {
-    public class TmdbEpisodeProvider :
-            TmdbEpisodeProviderBase,
-            IRemoteMetadataProvider<Episode, EpisodeInfo>,
-            IHasOrder
+    public class TmdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasOrder
     {
-        public TmdbEpisodeProvider(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory)
-            : base(httpClientFactory, configurationManager, jsonSerializer, fileSystem, localization, loggerFactory)
+        private readonly IHttpClientFactory _httpClientFactory;
+        private readonly TmdbClientManager _tmdbClientManager;
+
+        public TmdbEpisodeProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
         {
+            _httpClientFactory = httpClientFactory;
+            _tmdbClientManager = tmdbClientManager;
         }
 
         // After TheTvDb
@@ -39,51 +39,54 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 
         public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
         {
-            var list = new List<RemoteSearchResult>();
-
             // The search query must either provide an episode number or date
             if (!searchInfo.IndexNumber.HasValue || !searchInfo.ParentIndexNumber.HasValue)
             {
-                return list;
+                return Enumerable.Empty<RemoteSearchResult>();
             }
 
-            var metadataResult = await GetMetadata(searchInfo, cancellationToken).ConfigureAwait(false);
+            var metadataResult = await GetMetadata(searchInfo, cancellationToken);
 
-            if (metadataResult.HasMetadata)
+            if (!metadataResult.HasMetadata)
             {
-                var item = metadataResult.Item;
-
-                list.Add(new RemoteSearchResult
-                {
-                    IndexNumber = item.IndexNumber,
-                    Name = item.Name,
-                    ParentIndexNumber = item.ParentIndexNumber,
-                    PremiereDate = item.PremiereDate,
-                    ProductionYear = item.ProductionYear,
-                    ProviderIds = item.ProviderIds,
-                    SearchProviderName = Name,
-                    IndexNumberEnd = item.IndexNumberEnd
-                });
+                return Enumerable.Empty<RemoteSearchResult>();
             }
 
+            var list = new List<RemoteSearchResult>();
+
+            var item = metadataResult.Item;
+
+            list.Add(new RemoteSearchResult
+            {
+                IndexNumber = item.IndexNumber,
+                Name = item.Name,
+                ParentIndexNumber = item.ParentIndexNumber,
+                PremiereDate = item.PremiereDate,
+                ProductionYear = item.ProductionYear,
+                ProviderIds = item.ProviderIds,
+                SearchProviderName = Name,
+                IndexNumberEnd = item.IndexNumberEnd
+            });
+
             return list;
         }
 
         public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken)
         {
-            var result = new MetadataResult<Episode>();
+            var metadataResult = new MetadataResult<Episode>();
 
             // Allowing this will dramatically increase scan times
             if (info.IsMissingEpisode)
             {
-                return result;
+                return metadataResult;
             }
 
-            info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string seriesTmdbId);
+            info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string tmdbId);
 
-            if (string.IsNullOrEmpty(seriesTmdbId))
+            var seriesTmdbId = Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture);
+            if (seriesTmdbId <= 0)
             {
-                return result;
+                return metadataResult;
             }
 
             var seasonNumber = info.ParentIndexNumber;
@@ -91,125 +94,121 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 
             if (!seasonNumber.HasValue || !episodeNumber.HasValue)
             {
-                return result;
+                return metadataResult;
             }
 
-            try
-            {
-                var response = await GetEpisodeInfo(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+            var episodeResult = await _tmdbClientManager
+                .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
+                .ConfigureAwait(false);
 
-                result.HasMetadata = true;
-                result.QueriedById = true;
+            if (episodeResult == null)
+            {
+                return metadataResult;
+            }
 
-                if (!string.IsNullOrEmpty(response.Overview))
-                {
-                    // if overview is non-empty, we can assume that localized data was returned
-                    result.ResultLanguage = info.MetadataLanguage;
-                }
+            metadataResult.HasMetadata = true;
+            metadataResult.QueriedById = true;
 
-                var item = new Episode();
-                result.Item = item;
+            if (!string.IsNullOrEmpty(episodeResult.Overview))
+            {
+                // if overview is non-empty, we can assume that localized data was returned
+                metadataResult.ResultLanguage = info.MetadataLanguage;
+            }
 
-                item.Name = info.Name;
-                item.IndexNumber = info.IndexNumber;
-                item.ParentIndexNumber = info.ParentIndexNumber;
-                item.IndexNumberEnd = info.IndexNumberEnd;
+            var item = new Episode
+            {
+                Name = info.Name,
+                IndexNumber = info.IndexNumber,
+                ParentIndexNumber = info.ParentIndexNumber,
+                IndexNumberEnd = info.IndexNumberEnd
+            };
 
-                if (response.External_Ids != null && response.External_Ids.Tvdb_Id > 0)
-                {
-                    item.SetProviderId(MetadataProvider.Tvdb, response.External_Ids.Tvdb_Id.Value.ToString(CultureInfo.InvariantCulture));
-                }
+            if (!string.IsNullOrEmpty(episodeResult.ExternalIds?.TvdbId))
+            {
+                item.SetProviderId(MetadataProvider.Tvdb, episodeResult.ExternalIds.TvdbId);
+            }
 
-                item.PremiereDate = response.Air_Date;
-                item.ProductionYear = result.Item.PremiereDate.Value.Year;
+            item.PremiereDate = episodeResult.AirDate;
+            item.ProductionYear = episodeResult.AirDate?.Year;
 
-                item.Name = response.Name;
-                item.Overview = response.Overview;
+            item.Name = episodeResult.Name;
+            item.Overview = episodeResult.Overview;
 
-                item.CommunityRating = (float)response.Vote_Average;
+            item.CommunityRating = Convert.ToSingle(episodeResult.VoteAverage);
 
-                if (response.Videos?.Results != null)
+            if (episodeResult.Videos?.Results != null)
+            {
+                foreach (var video in episodeResult.Videos.Results)
                 {
-                    foreach (var video in response.Videos.Results)
+                    if (TmdbUtils.IsTrailerType(video))
                     {
-                        if (video.Type.Equals("trailer", System.StringComparison.OrdinalIgnoreCase)
-                            || video.Type.Equals("clip", System.StringComparison.OrdinalIgnoreCase))
-                        {
-                            if (video.Site.Equals("youtube", System.StringComparison.OrdinalIgnoreCase))
-                            {
-                                var videoUrl = string.Format(CultureInfo.InvariantCulture, "http://www.youtube.com/watch?v={0}", video.Key);
-                                item.AddTrailerUrl(videoUrl);
-                            }
-                        }
+                        var videoUrl = string.Format(CultureInfo.InvariantCulture, "http://www.youtube.com/watch?v={0}", video.Key);
+                        item.AddTrailerUrl(videoUrl);
                     }
                 }
+            }
 
-                result.ResetPeople();
+            var credits = episodeResult.Credits;
 
-                var credits = response.Credits;
-                if (credits != null)
+            if (credits?.Cast != null)
+            {
+                foreach (var actor in credits.Cast.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers))
                 {
-                    // Actors, Directors, Writers - all in People
-                    // actors come from cast
-                    if (credits.Cast != null)
+                    metadataResult.AddPerson(new PersonInfo
                     {
-                        foreach (var actor in credits.Cast.OrderBy(a => a.Order))
-                        {
-                            result.AddPerson(new PersonInfo { Name = actor.Name.Trim(), Role = actor.Character, Type = PersonType.Actor, SortOrder = actor.Order });
-                        }
-                    }
-
-                    // guest stars
-                    if (credits.Guest_Stars != null)
-                    {
-                        foreach (var guest in credits.Guest_Stars.OrderBy(a => a.Order))
-                        {
-                            result.AddPerson(new PersonInfo { Name = guest.Name.Trim(), Role = guest.Character, Type = PersonType.GuestStar, SortOrder = guest.Order });
-                        }
-                    }
+                        Name = actor.Name.Trim(),
+                        Role = actor.Character,
+                        Type = PersonType.Actor,
+                        SortOrder = actor.Order
+                    });
+                }
+            }
 
-                    // and the rest from crew
-                    if (credits.Crew != null)
+            if (credits?.GuestStars != null)
+            {
+                foreach (var guest in credits.GuestStars.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers))
+                {
+                    metadataResult.AddPerson(new PersonInfo
                     {
-                        var keepTypes = new[]
-                        {
-                            PersonType.Director,
-                            PersonType.Writer,
-                            PersonType.Producer
-                        };
-
-                        foreach (var person in credits.Crew)
-                        {
-                            // Normalize this
-                            var type = TmdbUtils.MapCrewToPersonType(person);
-
-                            if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase) &&
-                                !keepTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
-                            {
-                                continue;
-                            }
-
-                            result.AddPerson(new PersonInfo { Name = person.Name.Trim(), Role = person.Job, Type = type });
-                        }
-                    }
+                        Name = guest.Name.Trim(),
+                        Role = guest.Character,
+                        Type = PersonType.GuestStar,
+                        SortOrder = guest.Order
+                    });
                 }
             }
-            catch (HttpException ex)
+
+            // and the rest from crew
+            if (credits?.Crew != null)
             {
-                if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
+                foreach (var person in credits.Crew)
                 {
-                    return result;
-                }
+                    // Normalize this
+                    var type = TmdbUtils.MapCrewToPersonType(person);
+
+                    if (!TmdbUtils.WantedCrewTypes.Contains(type, StringComparer.OrdinalIgnoreCase)
+                        && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+                    {
+                        continue;
+                    }
 
-                throw;
+                    metadataResult.AddPerson(new PersonInfo
+                    {
+                        Name = person.Name.Trim(),
+                        Role = person.Job,
+                        Type = type
+                    });
+                }
             }
 
-            return result;
+            metadataResult.Item = item;
+
+            return metadataResult;
         }
 
         public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
         {
-            return GetResponse(url, cancellationToken);
+            return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
         }
     }
 }

+ 0 - 156
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs

@@ -1,156 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Globalization;
-using System.IO;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.TV;
-using MediaBrowser.Providers.Plugins.Tmdb.Movies;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.TV
-{
-    public abstract class TmdbEpisodeProviderBase
-    {
-        private const string EpisodeUrlPattern = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}/season/{1}/episode/{2}?api_key={3}&append_to_response=images,external_ids,credits,videos";
-
-        private readonly IHttpClientFactory _httpClientFactory;
-        private readonly IServerConfigurationManager _configurationManager;
-        private readonly IJsonSerializer _jsonSerializer;
-        private readonly IFileSystem _fileSystem;
-        private readonly ILogger<TmdbEpisodeProviderBase> _logger;
-
-        protected TmdbEpisodeProviderBase(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory)
-        {
-            _httpClientFactory = httpClientFactory;
-            _configurationManager = configurationManager;
-            _jsonSerializer = jsonSerializer;
-            _fileSystem = fileSystem;
-            _logger = loggerFactory.CreateLogger<TmdbEpisodeProviderBase>();
-        }
-
-        protected ILogger Logger => _logger;
-
-        protected async Task<EpisodeResult> GetEpisodeInfo(
-            string seriesTmdbId,
-            int season,
-            int episodeNumber,
-            string preferredMetadataLanguage,
-            CancellationToken cancellationToken)
-        {
-            await EnsureEpisodeInfo(seriesTmdbId, season, episodeNumber, preferredMetadataLanguage, cancellationToken)
-                    .ConfigureAwait(false);
-
-            var dataFilePath = GetDataFilePath(seriesTmdbId, season, episodeNumber, preferredMetadataLanguage);
-
-            return _jsonSerializer.DeserializeFromFile<EpisodeResult>(dataFilePath);
-        }
-
-        internal Task EnsureEpisodeInfo(string tmdbId, int seasonNumber, int episodeNumber, string language, CancellationToken cancellationToken)
-        {
-            if (string.IsNullOrEmpty(tmdbId))
-            {
-                throw new ArgumentNullException(nameof(tmdbId));
-            }
-
-            if (string.IsNullOrEmpty(language))
-            {
-                throw new ArgumentNullException(nameof(language));
-            }
-
-            var path = GetDataFilePath(tmdbId, seasonNumber, episodeNumber, language);
-
-            var fileInfo = _fileSystem.GetFileSystemInfo(path);
-
-            if (fileInfo.Exists)
-            {
-                // If it's recent or automatic updates are enabled, don't re-download
-                if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
-                {
-                    return Task.CompletedTask;
-                }
-            }
-
-            return DownloadEpisodeInfo(tmdbId, seasonNumber, episodeNumber, language, cancellationToken);
-        }
-
-        internal string GetDataFilePath(string tmdbId, int seasonNumber, int episodeNumber, string preferredLanguage)
-        {
-            if (string.IsNullOrEmpty(tmdbId))
-            {
-                throw new ArgumentNullException(nameof(tmdbId));
-            }
-
-            if (string.IsNullOrEmpty(preferredLanguage))
-            {
-                throw new ArgumentNullException(nameof(preferredLanguage));
-            }
-
-            var path = TmdbSeriesProvider.GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
-
-            var filename = string.Format(
-                CultureInfo.InvariantCulture,
-                "season-{0}-episode-{1}-{2}.json",
-                seasonNumber.ToString(CultureInfo.InvariantCulture),
-                episodeNumber.ToString(CultureInfo.InvariantCulture),
-                preferredLanguage);
-
-            return Path.Combine(path, filename);
-        }
-
-        internal async Task DownloadEpisodeInfo(string id, int seasonNumber, int episodeNumber, string preferredMetadataLanguage, CancellationToken cancellationToken)
-        {
-            var mainResult = await FetchMainResult(EpisodeUrlPattern, id, seasonNumber, episodeNumber, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
-
-            var dataFilePath = GetDataFilePath(id, seasonNumber, episodeNumber, preferredMetadataLanguage);
-
-            Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
-            _jsonSerializer.SerializeToFile(mainResult, dataFilePath);
-        }
-
-        internal async Task<EpisodeResult> FetchMainResult(string urlPattern, string id, int seasonNumber, int episodeNumber, string language, CancellationToken cancellationToken)
-        {
-            var url = string.Format(
-                CultureInfo.InvariantCulture,
-                urlPattern,
-                id,
-                seasonNumber.ToString(CultureInfo.InvariantCulture),
-                episodeNumber,
-                TmdbUtils.ApiKey);
-
-            if (!string.IsNullOrEmpty(language))
-            {
-                url += string.Format(CultureInfo.InvariantCulture, "&language={0}", language);
-            }
-
-            var includeImageLanguageParam = TmdbMovieProvider.GetImageLanguagesParam(language);
-            // Get images in english and with no language
-            url += "&include_image_language=" + includeImageLanguageParam;
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
-            foreach (var header in TmdbUtils.AcceptHeaders)
-            {
-                requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
-            }
-
-            using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false);
-            await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
-            return await _jsonSerializer.DeserializeFromStreamAsync<EpisodeResult>(stream).ConfigureAwait(false);
-        }
-
-        protected Task<HttpResponseMessage> GetResponse(string url, CancellationToken cancellationToken)
-        {
-            return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
-        }
-    }
-}

+ 30 - 76
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs

@@ -2,7 +2,7 @@
 
 using System;
 using System.Collections.Generic;
-using System.IO;
+using System.Globalization;
 using System.Linq;
 using System.Net.Http;
 using System.Threading;
@@ -13,29 +13,25 @@ using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-using MediaBrowser.Providers.Plugins.Tmdb.Movies;
 
 namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 {
     public class TmdbSeasonImageProvider : IRemoteImageProvider, IHasOrder
     {
-        private readonly IJsonSerializer _jsonSerializer;
         private readonly IHttpClientFactory _httpClientFactory;
+        private readonly TmdbClientManager _tmdbClientManager;
 
-        public TmdbSeasonImageProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory)
+        public TmdbSeasonImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
         {
-            _jsonSerializer = jsonSerializer;
             _httpClientFactory = httpClientFactory;
+            _tmdbClientManager = tmdbClientManager;
         }
 
         public int Order => 1;
 
-        public string Name => ProviderName;
-
-        public static string ProviderName => TmdbUtils.ProviderName;
+        public string Name => TmdbUtils.ProviderName;
 
         public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
         {
@@ -45,87 +41,45 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
         public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
         {
             var season = (Season)item;
-            var series = season.Series;
-
-            var seriesId = series?.GetProviderId(MetadataProvider.Tmdb);
-
-            if (string.IsNullOrEmpty(seriesId))
-            {
-                return Enumerable.Empty<RemoteImageInfo>();
-            }
+            var series = season?.Series;
 
-            var seasonNumber = season.IndexNumber;
+            var seriesTmdbId = Convert.ToInt32(series?.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
 
-            if (!seasonNumber.HasValue)
+            if (seriesTmdbId <= 0 || season?.IndexNumber != null)
             {
                 return Enumerable.Empty<RemoteImageInfo>();
             }
 
             var language = item.GetPreferredMetadataLanguage();
 
-            var results = await FetchImages(season, seriesId, language, cancellationToken).ConfigureAwait(false);
+            var seasonResult = await _tmdbClientManager
+                .GetSeasonAsync(seriesTmdbId, season.IndexNumber.Value, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken)
+                .ConfigureAwait(false);
 
-            var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
-
-            var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
-
-            var list = results.Select(i => new RemoteImageInfo
-            {
-                Url = tmdbImageUrl + i.File_Path,
-                CommunityRating = i.Vote_Average,
-                VoteCount = i.Vote_Count,
-                Width = i.Width,
-                Height = i.Height,
-                Language = TmdbMovieProvider.AdjustImageLanguage(i.Iso_639_1, language),
-                ProviderName = Name,
-                Type = ImageType.Primary,
-                RatingType = RatingType.Score
-            });
-
-            var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
-
-            return list.OrderByDescending(i =>
+            if (seasonResult?.Images?.Posters == null)
             {
-                if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
-                {
-                    return 3;
-                }
-
-                if (!isLanguageEn)
-                {
-                    if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
-                    {
-                        return 2;
-                    }
-                }
-
-                if (string.IsNullOrEmpty(i.Language))
-                {
-                    return isLanguageEn ? 3 : 2;
-                }
-
-                return 0;
-            })
-                .ThenByDescending(i => i.CommunityRating ?? 0)
-                .ThenByDescending(i => i.VoteCount ?? 0);
-        }
-
-        private async Task<List<Poster>> FetchImages(Season item, string tmdbId, string language, CancellationToken cancellationToken)
-        {
-            var seasonNumber = item.IndexNumber.GetValueOrDefault();
-            await TmdbSeasonProvider.Current.EnsureSeasonInfo(tmdbId, seasonNumber, language, cancellationToken).ConfigureAwait(false);
-
-            var path = TmdbSeasonProvider.Current.GetDataFilePath(tmdbId, seasonNumber, language);
+                return Enumerable.Empty<RemoteImageInfo>();
+            }
 
-            if (!string.IsNullOrEmpty(path))
+            var remoteImages = new List<RemoteImageInfo>();
+            for (var i = 0; i < seasonResult.Images.Posters.Count; i++)
             {
-                if (File.Exists(path))
+                var image = seasonResult.Images.Posters[i];
+                remoteImages.Add(new RemoteImageInfo
                 {
-                    return _jsonSerializer.DeserializeFromFile<Models.TV.SeasonResult>(path).Images.Posters;
-                }
+                    Url = _tmdbClientManager.GetPosterUrl(image.FilePath),
+                    CommunityRating = image.VoteAverage,
+                    VoteCount = image.VoteCount,
+                    Width = image.Width,
+                    Height = image.Height,
+                    Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, language),
+                    ProviderName = Name,
+                    Type = ImageType.Primary,
+                    RatingType = RatingType.Score
+                });
             }
 
-            return null;
+            return remoteImages.OrderByLanguageDescending(language);
         }
 
         public IEnumerable<ImageType> GetSupportedImages(BaseItem item)

+ 61 - 180
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs

@@ -3,53 +3,28 @@
 using System;
 using System.Collections.Generic;
 using System.Globalization;
-using System.IO;
-using System.Net;
+using System.Linq;
 using System.Net.Http;
-using System.Net.Http.Headers;
 using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.TV;
-using MediaBrowser.Providers.Plugins.Tmdb.Movies;
-using Microsoft.Extensions.Logging;
-using Season = MediaBrowser.Controller.Entities.TV.Season;
 
 namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 {
     public class TmdbSeasonProvider : IRemoteMetadataProvider<Season, SeasonInfo>
     {
-        private const string GetTvInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}/season/{1}?api_key={2}&append_to_response=images,keywords,external_ids,credits,videos";
-
         private readonly IHttpClientFactory _httpClientFactory;
-        private readonly IServerConfigurationManager _configurationManager;
-        private readonly IJsonSerializer _jsonSerializer;
-        private readonly IFileSystem _fileSystem;
-        private readonly ILogger<TmdbSeasonProvider> _logger;
-
-        internal static TmdbSeasonProvider Current { get; private set; }
-
-        public TmdbSeasonProvider(
-            IHttpClientFactory httpClientFactory,
-            IServerConfigurationManager configurationManager,
-            IFileSystem fileSystem,
-            IJsonSerializer jsonSerializer,
-            ILogger<TmdbSeasonProvider> logger)
+        private readonly TmdbClientManager _tmdbClientManager;
+
+        public TmdbSeasonProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
         {
             _httpClientFactory = httpClientFactory;
-            _configurationManager = configurationManager;
-            _fileSystem = fileSystem;
-            _jsonSerializer = jsonSerializer;
-            _logger = logger;
-            Current = this;
+            _tmdbClientManager = tmdbClientManager;
         }
 
         public string Name => TmdbUtils.ProviderName;
@@ -62,180 +37,86 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 
             var seasonNumber = info.IndexNumber;
 
-            if (!string.IsNullOrWhiteSpace(seriesTmdbId) && seasonNumber.HasValue)
+            if (string.IsNullOrWhiteSpace(seriesTmdbId) || !seasonNumber.HasValue)
             {
-                try
-                {
-                    var seasonInfo = await GetSeasonInfo(seriesTmdbId, seasonNumber.Value, info.MetadataLanguage, cancellationToken)
-                      .ConfigureAwait(false);
-
-                    result.HasMetadata = true;
-                    result.Item = new Season();
-
-                    // Don't use moviedb season names for now until if/when we have field-level configuration
-                    // result.Item.Name = seasonInfo.name;
-
-                    result.Item.Name = info.Name;
-
-                    result.Item.IndexNumber = seasonNumber;
-
-                    result.Item.Overview = seasonInfo.Overview;
-
-                    if (seasonInfo.External_Ids != null && seasonInfo.External_Ids.Tvdb_Id > 0)
-                    {
-                        result.Item.SetProviderId(MetadataProvider.Tvdb, seasonInfo.External_Ids.Tvdb_Id.Value.ToString(CultureInfo.InvariantCulture));
-                    }
-
-                    var credits = seasonInfo.Credits;
-                    if (credits != null)
-                    {
-                        // Actors, Directors, Writers - all in People
-                        // actors come from cast
-                        if (credits.Cast != null)
-                        {
-                            // foreach (var actor in credits.cast.OrderBy(a => a.order)) result.Item.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor, SortOrder = actor.order });
-                        }
-
-                        // and the rest from crew
-                        if (credits.Crew != null)
-                        {
-                            // foreach (var person in credits.crew) result.Item.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department });
-                        }
-                    }
-
-                    result.Item.PremiereDate = seasonInfo.Air_Date;
-                    result.Item.ProductionYear = result.Item.PremiereDate.Value.Year;
-                }
-                catch (HttpException ex)
-                {
-                    _logger.LogError(ex, "No metadata found for {0}", seasonNumber.Value);
-
-                    if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
-                    {
-                        return result;
-                    }
-
-                    throw;
-                }
+                return result;
             }
 
-            return result;
-        }
-
-        public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeasonInfo searchInfo, CancellationToken cancellationToken)
-        {
-            return Task.FromResult<IEnumerable<RemoteSearchResult>>(new List<RemoteSearchResult>());
-        }
-
-        public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
-        {
-            return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
-        }
-
-        private async Task<SeasonResult> GetSeasonInfo(
-            string seriesTmdbId,
-            int season,
-            string preferredMetadataLanguage,
-            CancellationToken cancellationToken)
-        {
-            await EnsureSeasonInfo(seriesTmdbId, season, preferredMetadataLanguage, cancellationToken)
-                    .ConfigureAwait(false);
-
-            var dataFilePath = GetDataFilePath(seriesTmdbId, season, preferredMetadataLanguage);
-
-            return _jsonSerializer.DeserializeFromFile<SeasonResult>(dataFilePath);
-        }
+            var seasonResult = await _tmdbClientManager
+                .GetSeasonAsync(Convert.ToInt32(seriesTmdbId, CultureInfo.InvariantCulture), seasonNumber.Value, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
+                .ConfigureAwait(false);
 
-        internal Task EnsureSeasonInfo(string tmdbId, int seasonNumber, string language, CancellationToken cancellationToken)
-        {
-            if (string.IsNullOrEmpty(tmdbId))
+            if (seasonResult == null)
             {
-                throw new ArgumentNullException(nameof(tmdbId));
+                return result;
             }
 
-            if (string.IsNullOrEmpty(language))
+            result.HasMetadata = true;
+            result.Item = new Season
             {
-                throw new ArgumentNullException(nameof(language));
-            }
+                Name = info.Name,
+                IndexNumber = seasonNumber,
+                Overview = seasonResult?.Overview
+            };
 
-            var path = GetDataFilePath(tmdbId, seasonNumber, language);
-
-            var fileInfo = _fileSystem.GetFileSystemInfo(path);
+            if (!string.IsNullOrEmpty(seasonResult.ExternalIds?.TvdbId))
+            {
+                result.Item.SetProviderId(MetadataProvider.Tvdb, seasonResult.ExternalIds.TvdbId);
+            }
 
-            if (fileInfo.Exists)
+            // TODO why was this disabled?
+            var credits = seasonResult.Credits;
+            if (credits?.Cast != null)
             {
-                // If it's recent or automatic updates are enabled, don't re-download
-                if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
+                var cast = credits.Cast.OrderBy(c => c.Order).Take(TmdbUtils.MaxCastMembers).ToList();
+                for (var i = 0; i < cast.Count; i++)
                 {
-                    return Task.CompletedTask;
+                    result.AddPerson(new PersonInfo
+                    {
+                        Name = cast[i].Name.Trim(),
+                        Role = cast[i].Character,
+                        Type = PersonType.Actor,
+                        SortOrder = cast[i].Order
+                    });
                 }
             }
 
-            return DownloadSeasonInfo(tmdbId, seasonNumber, language, cancellationToken);
-        }
-
-        internal string GetDataFilePath(string tmdbId, int seasonNumber, string preferredLanguage)
-        {
-            if (string.IsNullOrEmpty(tmdbId))
+            if (credits?.Crew != null)
             {
-                throw new ArgumentNullException(nameof(tmdbId));
-            }
+                foreach (var person in credits.Crew)
+                {
+                    // Normalize this
+                    var type = TmdbUtils.MapCrewToPersonType(person);
 
-            if (string.IsNullOrEmpty(preferredLanguage))
-            {
-                throw new ArgumentNullException(nameof(preferredLanguage));
-            }
+                    if (!TmdbUtils.WantedCrewTypes.Contains(type, StringComparer.OrdinalIgnoreCase)
+                        && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+                    {
+                        continue;
+                    }
 
-            var path = TmdbSeriesProvider.GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
+                    result.AddPerson(new PersonInfo
+                    {
+                        Name = person.Name.Trim(),
+                        Role = person.Job,
+                        Type = type
+                    });
+                }
+            }
 
-            var filename = string.Format(
-                CultureInfo.InvariantCulture,
-                "season-{0}-{1}.json",
-                seasonNumber.ToString(CultureInfo.InvariantCulture),
-                preferredLanguage);
+            result.Item.PremiereDate = seasonResult.AirDate;
+            result.Item.ProductionYear = seasonResult.AirDate?.Year;
 
-            return Path.Combine(path, filename);
+            return result;
         }
 
-        internal async Task DownloadSeasonInfo(string id, int seasonNumber, string preferredMetadataLanguage, CancellationToken cancellationToken)
+        public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeasonInfo searchInfo, CancellationToken cancellationToken)
         {
-            var mainResult = await FetchMainResult(id, seasonNumber, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
-
-            var dataFilePath = GetDataFilePath(id, seasonNumber, preferredMetadataLanguage);
-
-            Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
-            _jsonSerializer.SerializeToFile(mainResult, dataFilePath);
+            return Task.FromResult(Enumerable.Empty<RemoteSearchResult>());
         }
 
-        internal async Task<SeasonResult> FetchMainResult(string id, int seasonNumber, string language, CancellationToken cancellationToken)
+        public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
         {
-            var url = string.Format(
-                CultureInfo.InvariantCulture,
-                GetTvInfo3,
-                id,
-                seasonNumber.ToString(CultureInfo.InvariantCulture),
-                TmdbUtils.ApiKey);
-
-            if (!string.IsNullOrEmpty(language))
-            {
-                url += string.Format(CultureInfo.InvariantCulture, "&language={0}", TmdbMovieProvider.NormalizeLanguage(language));
-            }
-
-            var includeImageLanguageParam = TmdbMovieProvider.GetImageLanguagesParam(language);
-            // Get images in english and with no language
-            url += "&include_image_language=" + includeImageLanguageParam;
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
-            foreach (var header in TmdbUtils.AcceptHeaders)
-            {
-                requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
-            }
-
-            using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false);
-            await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
-            return await _jsonSerializer.DeserializeFromStreamAsync<SeasonResult>(stream).ConfigureAwait(false);
+            return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
         }
     }
 }

+ 45 - 114
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs

@@ -2,7 +2,7 @@
 
 using System;
 using System.Collections.Generic;
-using System.IO;
+using System.Globalization;
 using System.Linq;
 using System.Net.Http;
 using System.Threading;
@@ -13,28 +13,23 @@ using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.TV;
-using MediaBrowser.Providers.Plugins.Tmdb.Movies;
 
 namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 {
     public class TmdbSeriesImageProvider : IRemoteImageProvider, IHasOrder
     {
-        private readonly IJsonSerializer _jsonSerializer;
         private readonly IHttpClientFactory _httpClientFactory;
+        private readonly TmdbClientManager _tmdbClientManager;
 
-        public TmdbSeriesImageProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory)
+        public TmdbSeriesImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
         {
-            _jsonSerializer = jsonSerializer;
             _httpClientFactory = httpClientFactory;
+            _tmdbClientManager = tmdbClientManager;
         }
 
-        public string Name => ProviderName;
-
-        public static string ProviderName => TmdbUtils.ProviderName;
+        public string Name => TmdbUtils.ProviderName;
 
         // After tvdb and fanart
         public int Order => 2;
@@ -55,124 +50,60 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 
         public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
         {
-            var list = new List<RemoteImageInfo>();
-
-            var results = await FetchImages(item, null, cancellationToken).ConfigureAwait(false);
+            var tmdbId = item.GetProviderId(MetadataProvider.Tmdb);
 
-            if (results == null)
+            if (string.IsNullOrEmpty(tmdbId))
             {
-                return list;
+                return null;
             }
 
-            var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
-
-            var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
-
             var language = item.GetPreferredMetadataLanguage();
 
-            list.AddRange(GetPosters(results).Select(i => new RemoteImageInfo
-            {
-                Url = tmdbImageUrl + i.File_Path,
-                CommunityRating = i.Vote_Average,
-                VoteCount = i.Vote_Count,
-                Width = i.Width,
-                Height = i.Height,
-                Language = TmdbMovieProvider.AdjustImageLanguage(i.Iso_639_1, language),
-                ProviderName = Name,
-                Type = ImageType.Primary,
-                RatingType = RatingType.Score
-            }));
-
-            list.AddRange(GetBackdrops(results).Select(i => new RemoteImageInfo
-            {
-                Url = tmdbImageUrl + i.File_Path,
-                CommunityRating = i.Vote_Average,
-                VoteCount = i.Vote_Count,
-                Width = i.Width,
-                Height = i.Height,
-                ProviderName = Name,
-                Type = ImageType.Backdrop,
-                RatingType = RatingType.Score
-            }));
-
-            var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
-
-            return list.OrderByDescending(i =>
-            {
-                if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
-                {
-                    return 3;
-                }
-
-                if (!isLanguageEn)
-                {
-                    if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
-                    {
-                        return 2;
-                    }
-                }
-
-                if (string.IsNullOrEmpty(i.Language))
-                {
-                    return isLanguageEn ? 3 : 2;
-                }
-
-                return 0;
-            })
-                .ThenByDescending(i => i.CommunityRating ?? 0)
-                .ThenByDescending(i => i.VoteCount ?? 0);
-        }
+            var series = await _tmdbClientManager
+                .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken)
+                .ConfigureAwait(false);
 
-        /// <summary>
-        /// Gets the posters.
-        /// </summary>
-        /// <param name="images">The images.</param>
-        private IEnumerable<Poster> GetPosters(Images images)
-        {
-            return images.Posters ?? new List<Poster>();
-        }
-
-        /// <summary>
-        /// Gets the backdrops.
-        /// </summary>
-        /// <param name="images">The images.</param>
-        private IEnumerable<Backdrop> GetBackdrops(Images images)
-        {
-            var eligibleBackdrops = images.Backdrops ?? new List<Backdrop>();
-
-            return eligibleBackdrops.OrderByDescending(i => i.Vote_Average)
-                .ThenByDescending(i => i.Vote_Count);
-        }
-
-        /// <summary>
-        /// Fetches the images.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="language">The language.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{MovieImages}.</returns>
-        private async Task<Images> FetchImages(
-            BaseItem item,
-            string language,
-            CancellationToken cancellationToken)
-        {
-            var tmdbId = item.GetProviderId(MetadataProvider.Tmdb);
-
-            if (string.IsNullOrEmpty(tmdbId))
+            if (series?.Images == null)
             {
-                return null;
+                return Array.Empty<RemoteImageInfo>();
             }
 
-            await TmdbSeriesProvider.Current.EnsureSeriesInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
+            var remoteImages = new List<RemoteImageInfo>();
 
-            var path = TmdbSeriesProvider.Current.GetDataFilePath(tmdbId, language);
+            for (var i = 0; i < series.Images.Posters.Count; i++)
+            {
+                var poster = series.Images.Posters[i];
+                remoteImages.Add(new RemoteImageInfo
+                {
+                    Url = _tmdbClientManager.GetPosterUrl(poster.FilePath),
+                    CommunityRating = poster.VoteAverage,
+                    VoteCount = poster.VoteCount,
+                    Width = poster.Width,
+                    Height = poster.Height,
+                    Language = TmdbUtils.AdjustImageLanguage(poster.Iso_639_1, language),
+                    ProviderName = Name,
+                    Type = ImageType.Primary,
+                    RatingType = RatingType.Score
+                });
+            }
 
-            if (!string.IsNullOrEmpty(path) && File.Exists(path))
+            for (var i = 0; i < series.Images.Backdrops.Count; i++)
             {
-                return _jsonSerializer.DeserializeFromFile<SeriesResult>(path).Images;
+                var backdrop = series.Images.Backdrops[i];
+                remoteImages.Add(new RemoteImageInfo
+                {
+                    Url = _tmdbClientManager.GetBackdropUrl(backdrop.FilePath),
+                    CommunityRating = backdrop.VoteAverage,
+                    VoteCount = backdrop.VoteCount,
+                    Width = backdrop.Width,
+                    Height = backdrop.Height,
+                    ProviderName = Name,
+                    Type = ImageType.Backdrop,
+                    RatingType = RatingType.Score
+                });
             }
 
-            return null;
+            return remoteImages.OrderByLanguageDescending(language);
         }
 
         public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)

+ 177 - 336
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs

@@ -1,6 +1,7 @@
 #pragma warning disable CS1591
 
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
@@ -9,7 +10,6 @@ using System.Net.Http;
 using System.Net.Http.Headers;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
@@ -17,93 +17,78 @@ using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.Search;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.TV;
 using MediaBrowser.Providers.Plugins.Tmdb.Movies;
 using Microsoft.Extensions.Logging;
+using TMDbLib.Objects.Find;
+using TMDbLib.Objects.Search;
+using TMDbLib.Objects.TvShows;
 
 namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 {
     public class TmdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder
     {
-        private const string GetTvInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}?api_key={1}&append_to_response=credits,images,keywords,external_ids,videos,content_ratings";
-
-        private readonly IJsonSerializer _jsonSerializer;
-        private readonly IServerConfigurationManager _configurationManager;
-        private readonly ILogger<TmdbSeriesProvider> _logger;
         private readonly IHttpClientFactory _httpClientFactory;
-        private readonly ILibraryManager _libraryManager;
+        private readonly TmdbClientManager _tmdbClientManager;
 
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
 
         public TmdbSeriesProvider(
-            IJsonSerializer jsonSerializer,
-            IServerConfigurationManager configurationManager,
-            ILogger<TmdbSeriesProvider> logger,
             IHttpClientFactory httpClientFactory,
-            ILibraryManager libraryManager)
+            TmdbClientManager tmdbClientManager)
         {
-            _jsonSerializer = jsonSerializer;
-            _configurationManager = configurationManager;
-            _logger = logger;
             _httpClientFactory = httpClientFactory;
-            _libraryManager = libraryManager;
+            _tmdbClientManager = tmdbClientManager;
             Current = this;
         }
 
-        internal static TmdbSeriesProvider Current { get; private set; }
-
         public string Name => TmdbUtils.ProviderName;
 
         // After TheTVDB
         public int Order => 1;
 
+        internal static TmdbSeriesProvider Current { get; private set; }
+
         public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
         {
             var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb);
 
             if (!string.IsNullOrEmpty(tmdbId))
             {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                await EnsureSeriesInfo(tmdbId, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
-
-                var dataFilePath = GetDataFilePath(tmdbId, searchInfo.MetadataLanguage);
-
-                var obj = _jsonSerializer.DeserializeFromFile<SeriesResult>(dataFilePath);
-
-                var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
-                var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
+                var series = await _tmdbClientManager
+                    .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, searchInfo.MetadataLanguage, cancellationToken)
+                    .ConfigureAwait(false);
 
-                var remoteResult = new RemoteSearchResult
+                if (series != null)
                 {
-                    Name = obj.Name,
-                    SearchProviderName = Name,
-                    ImageUrl = string.IsNullOrWhiteSpace(obj.Poster_Path) ? null : tmdbImageUrl + obj.Poster_Path
-                };
-
-                remoteResult.SetProviderId(MetadataProvider.Tmdb, obj.Id.ToString(_usCulture));
-                remoteResult.SetProviderId(MetadataProvider.Imdb, obj.External_Ids.Imdb_Id);
+                    var remoteResult = MapTvShowToRemoteSearchResult(series);
 
-                if (obj.External_Ids != null && obj.External_Ids.Tvdb_Id > 0)
-                {
-                    remoteResult.SetProviderId(MetadataProvider.Tvdb, obj.External_Ids.Tvdb_Id.Value.ToString(_usCulture));
+                    return new[] { remoteResult };
                 }
-
-                return new[] { remoteResult };
             }
 
             var imdbId = searchInfo.GetProviderId(MetadataProvider.Imdb);
 
             if (!string.IsNullOrEmpty(imdbId))
             {
-                var searchResult = await FindByExternalId(imdbId, "imdb_id", cancellationToken).ConfigureAwait(false);
+                var findResult = await _tmdbClientManager
+                    .FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, searchInfo.MetadataLanguage, cancellationToken)
+                    .ConfigureAwait(false);
 
-                if (searchResult != null)
+                if (findResult?.TvResults != null)
                 {
-                    return new[] { searchResult };
+                    var imdbIdResults = new List<RemoteSearchResult>();
+                    for (var i = 0; i < findResult.TvResults.Count; i++)
+                    {
+                        var remoteResult = MapSearchTvToRemoteSearchResult(findResult.TvResults[i]);
+                        remoteResult.SetProviderId(MetadataProvider.Imdb, imdbId);
+                        imdbIdResults.Add(remoteResult);
+                    }
+
+                    return imdbIdResults;
                 }
             }
 
@@ -111,15 +96,79 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 
             if (!string.IsNullOrEmpty(tvdbId))
             {
-                var searchResult = await FindByExternalId(tvdbId, "tvdb_id", cancellationToken).ConfigureAwait(false);
+                var findResult = await _tmdbClientManager
+                    .FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, searchInfo.MetadataLanguage, cancellationToken)
+                    .ConfigureAwait(false);
+
+                if (findResult?.TvResults != null)
+                {
+                    var tvIdResults = new List<RemoteSearchResult>();
+                    for (var i = 0; i < findResult.TvResults.Count; i++)
+                    {
+                        var remoteResult = MapSearchTvToRemoteSearchResult(findResult.TvResults[i]);
+                        remoteResult.SetProviderId(MetadataProvider.Tvdb, tvdbId);
+                        tvIdResults.Add(remoteResult);
+                    }
+
+                    return tvIdResults;
+                }
+            }
+
+            var tvSearchResults = await _tmdbClientManager.SearchSeriesAsync(searchInfo.Name, searchInfo.MetadataLanguage, cancellationToken)
+                .ConfigureAwait(false);
+
+            var remoteResults = new List<RemoteSearchResult>();
+            for (var i = 0; i < tvSearchResults.Count; i++)
+            {
+                remoteResults.Add(MapSearchTvToRemoteSearchResult(tvSearchResults[i]));
+            }
+
+            return remoteResults;
+        }
+
+        private RemoteSearchResult MapTvShowToRemoteSearchResult(TvShow series)
+        {
+            var remoteResult = new RemoteSearchResult
+            {
+                Name = series.Name ?? series.OriginalName,
+                SearchProviderName = Name,
+                ImageUrl = _tmdbClientManager.GetPosterUrl(series.PosterPath),
+                Overview = series.Overview
+            };
+
+            remoteResult.SetProviderId(MetadataProvider.Tmdb, series.Id.ToString(_usCulture));
+            if (series.ExternalIds != null)
+            {
+                if (!string.IsNullOrEmpty(series.ExternalIds.ImdbId))
+                {
+                    remoteResult.SetProviderId(MetadataProvider.Imdb, series.ExternalIds.ImdbId);
+                }
 
-                if (searchResult != null)
+                if (!string.IsNullOrEmpty(series.ExternalIds.TvdbId))
                 {
-                    return new[] { searchResult };
+                    remoteResult.SetProviderId(MetadataProvider.Tvdb, series.ExternalIds.TvdbId);
                 }
             }
 
-            return await new TmdbSearch(_logger, _jsonSerializer, _libraryManager).GetSearchResults(searchInfo, cancellationToken).ConfigureAwait(false);
+            remoteResult.PremiereDate = series.FirstAirDate?.ToUniversalTime();
+
+            return remoteResult;
+        }
+
+        private RemoteSearchResult MapSearchTvToRemoteSearchResult(SearchTv series)
+        {
+            var remoteResult = new RemoteSearchResult
+            {
+                Name = series.Name ?? series.OriginalName,
+                SearchProviderName = Name,
+                ImageUrl = _tmdbClientManager.GetPosterUrl(series.PosterPath),
+                Overview = series.Overview
+            };
+
+            remoteResult.SetProviderId(MetadataProvider.Tmdb, series.Id.ToString(_usCulture));
+            remoteResult.PremiereDate = series.FirstAirDate?.ToUniversalTime();
+
+            return remoteResult;
         }
 
         public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken)
@@ -137,11 +186,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 
                 if (!string.IsNullOrEmpty(imdbId))
                 {
-                    var searchResult = await FindByExternalId(imdbId, "imdb_id", cancellationToken).ConfigureAwait(false);
+                    var searchResult = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
 
                     if (searchResult != null)
                     {
-                        tmdbId = searchResult.GetProviderId(MetadataProvider.Tmdb);
+                        tmdbId = searchResult.TvResults.FirstOrDefault()?.Id.ToString(CultureInfo.InvariantCulture);
                     }
                 }
             }
@@ -152,11 +201,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 
                 if (!string.IsNullOrEmpty(tvdbId))
                 {
-                    var searchResult = await FindByExternalId(tvdbId, "tvdb_id", cancellationToken).ConfigureAwait(false);
+                    var searchResult = await _tmdbClientManager.FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
 
                     if (searchResult != null)
                     {
-                        tmdbId = searchResult.GetProviderId(MetadataProvider.Tmdb);
+                        tmdbId = searchResult.TvResults.FirstOrDefault()?.Id.ToString(CultureInfo.InvariantCulture);
                     }
                 }
             }
@@ -164,13 +213,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
             if (string.IsNullOrEmpty(tmdbId))
             {
                 result.QueriedById = false;
-                var searchResults = await new TmdbSearch(_logger, _jsonSerializer, _libraryManager).GetSearchResults(info, cancellationToken).ConfigureAwait(false);
-
-                var searchResult = searchResults.FirstOrDefault();
+                var searchResults = await _tmdbClientManager.SearchSeriesAsync(info.Name, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
 
-                if (searchResult != null)
+                if (searchResults.Count > 0)
                 {
-                    tmdbId = searchResult.GetProviderId(MetadataProvider.Tmdb);
+                    tmdbId = searchResults[0].Id.ToString(CultureInfo.InvariantCulture);
                 }
             }
 
@@ -178,107 +225,83 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
             {
                 cancellationToken.ThrowIfCancellationRequested();
 
-                result = await FetchMovieData(tmdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
-
-                result.HasMetadata = result.Item != null;
-            }
+                var tvShow = await _tmdbClientManager
+                    .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
+                    .ConfigureAwait(false);
 
-            return result;
-        }
+                result = new MetadataResult<Series>
+                {
+                    Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode),
+                    ResultLanguage = info.MetadataLanguage ?? tvShow.OriginalLanguage
+                };
 
-        private async Task<MetadataResult<Series>> FetchMovieData(string tmdbId, string language, string preferredCountryCode, CancellationToken cancellationToken)
-        {
-            SeriesResult seriesInfo = await FetchMainResult(tmdbId, language, cancellationToken).ConfigureAwait(false);
+                foreach (var person in GetPersons(tvShow))
+                {
+                    result.AddPerson(person);
+                }
 
-            if (seriesInfo == null)
-            {
-                return null;
+                result.HasMetadata = result.Item != null;
             }
 
-            tmdbId = seriesInfo.Id.ToString(_usCulture);
-
-            string dataFilePath = GetDataFilePath(tmdbId, language);
-            Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
-            _jsonSerializer.SerializeToFile(seriesInfo, dataFilePath);
-
-            await EnsureSeriesInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
-
-            var result = new MetadataResult<Series>
-            {
-                Item = new Series(),
-                ResultLanguage = seriesInfo.ResultLanguage
-            };
-
-            var settings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
-
-            ProcessMainInfo(result, seriesInfo, preferredCountryCode, settings);
-
             return result;
         }
 
-        private void ProcessMainInfo(MetadataResult<Series> seriesResult, SeriesResult seriesInfo, string preferredCountryCode, TmdbSettingsResult settings)
+        private Series MapTvShowToSeries(TvShow seriesResult, string preferredCountryCode)
         {
-            var series = seriesResult.Item;
+            var series = new Series {Name = seriesResult.Name, OriginalTitle = seriesResult.OriginalName};
 
-            series.Name = seriesInfo.Name;
-            series.OriginalTitle = seriesInfo.Original_Name;
-            series.SetProviderId(MetadataProvider.Tmdb, seriesInfo.Id.ToString(_usCulture));
+            series.SetProviderId(MetadataProvider.Tmdb, seriesResult.Id.ToString(_usCulture));
 
-            string voteAvg = seriesInfo.Vote_Average.ToString(CultureInfo.InvariantCulture);
+            series.CommunityRating = Convert.ToSingle(seriesResult.VoteAverage);
 
-            if (float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out float rating))
-            {
-                series.CommunityRating = rating;
-            }
+            series.Overview = seriesResult.Overview;
 
-            series.Overview = seriesInfo.Overview;
-
-            if (seriesInfo.Networks != null)
+            if (seriesResult.Networks != null)
             {
-                series.Studios = seriesInfo.Networks.Select(i => i.Name).ToArray();
+                series.Studios = seriesResult.Networks.Select(i => i.Name).ToArray();
             }
 
-            if (seriesInfo.Genres != null)
+            if (seriesResult.Genres != null)
             {
-                series.Genres = seriesInfo.Genres.Select(i => i.Name).ToArray();
+                series.Genres = seriesResult.Genres.Select(i => i.Name).ToArray();
             }
 
-            series.HomePageUrl = seriesInfo.Homepage;
+            series.HomePageUrl = seriesResult.Homepage;
 
-            series.RunTimeTicks = seriesInfo.Episode_Run_Time.Select(i => TimeSpan.FromMinutes(i).Ticks).FirstOrDefault();
+            series.RunTimeTicks = seriesResult.EpisodeRunTime.Select(i => TimeSpan.FromMinutes(i).Ticks).FirstOrDefault();
 
-            if (string.Equals(seriesInfo.Status, "Ended", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(seriesResult.Status, "Ended", StringComparison.OrdinalIgnoreCase))
             {
                 series.Status = SeriesStatus.Ended;
-                series.EndDate = seriesInfo.Last_Air_Date;
+                series.EndDate = seriesResult.LastAirDate;
             }
             else
             {
                 series.Status = SeriesStatus.Continuing;
             }
 
-            series.PremiereDate = seriesInfo.First_Air_Date;
+            series.PremiereDate = seriesResult.FirstAirDate;
 
-            var ids = seriesInfo.External_Ids;
+            var ids = seriesResult.ExternalIds;
             if (ids != null)
             {
-                if (!string.IsNullOrWhiteSpace(ids.Imdb_Id))
+                if (!string.IsNullOrWhiteSpace(ids.ImdbId))
                 {
-                    series.SetProviderId(MetadataProvider.Imdb, ids.Imdb_Id);
+                    series.SetProviderId(MetadataProvider.Imdb, ids.ImdbId);
                 }
 
-                if (ids.Tvrage_Id > 0)
+                if (!string.IsNullOrEmpty(ids.TvrageId))
                 {
-                    series.SetProviderId(MetadataProvider.TvRage, ids.Tvrage_Id.Value.ToString(_usCulture));
+                    series.SetProviderId(MetadataProvider.TvRage, ids.TvrageId);
                 }
 
-                if (ids.Tvdb_Id > 0)
+                if (!string.IsNullOrEmpty(ids.TvdbId))
                 {
-                    series.SetProviderId(MetadataProvider.Tvdb, ids.Tvdb_Id.Value.ToString(_usCulture));
+                    series.SetProviderId(MetadataProvider.Tvdb, ids.TvdbId);
                 }
             }
 
-            var contentRatings = (seriesInfo.Content_Ratings ?? new 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));
@@ -297,254 +320,72 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
                 series.OfficialRating = minimumRelease.Rating;
             }
 
-            if (seriesInfo.Videos != null && seriesInfo.Videos.Results != null)
+            if (seriesResult.Videos?.Results != null)
             {
-                foreach (var video in seriesInfo.Videos.Results)
+                foreach (var video in seriesResult.Videos.Results)
                 {
-                    if ((video.Type.Equals("trailer", StringComparison.OrdinalIgnoreCase)
-                        || video.Type.Equals("clip", StringComparison.OrdinalIgnoreCase))
-                        && video.Site.Equals("youtube", StringComparison.OrdinalIgnoreCase))
+                    if (TmdbUtils.IsTrailerType(video))
                     {
                         series.AddTrailerUrl($"http://www.youtube.com/watch?v={video.Key}");
                     }
                 }
             }
 
-            seriesResult.ResetPeople();
-            var tmdbImageUrl = settings.images.GetImageUrl("original");
+            return series;
+        }
 
-            if (seriesInfo.Credits != null)
+        private IEnumerable<PersonInfo> GetPersons(TvShow seriesResult)
+        {
+            if (seriesResult.Credits?.Cast != null)
             {
-                if (seriesInfo.Credits.Cast != null)
+                foreach (var actor in seriesResult.Credits.Cast.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers))
                 {
-                    foreach (var actor in seriesInfo.Credits.Cast.OrderBy(a => a.Order))
+                    var personInfo = new PersonInfo
                     {
-                        var personInfo = new PersonInfo
-                        {
-                            Name = actor.Name.Trim(),
-                            Role = actor.Character,
-                            Type = PersonType.Actor,
-                            SortOrder = actor.Order
-                        };
-
-                        if (!string.IsNullOrWhiteSpace(actor.Profile_Path))
-                        {
-                            personInfo.ImageUrl = tmdbImageUrl + actor.Profile_Path;
-                        }
-
-                        if (actor.Id > 0)
-                        {
-                            personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture));
-                        }
-
-                        seriesResult.AddPerson(personInfo);
-                    }
-                }
-
-                if (seriesInfo.Credits.Crew != null)
-                {
-                    var keepTypes = new[]
-                    {
-                        PersonType.Director,
-                        PersonType.Writer,
-                        PersonType.Producer
+                        Name = actor.Name.Trim(),
+                        Role = actor.Character,
+                        Type = PersonType.Actor,
+                        SortOrder = actor.Order,
+                        ImageUrl = _tmdbClientManager.GetPosterUrl(actor.ProfilePath)
                     };
 
-                    foreach (var person in seriesInfo.Credits.Crew)
+                    if (actor.Id > 0)
                     {
-                        // Normalize this
-                        var type = TmdbUtils.MapCrewToPersonType(person);
-
-                        if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase)
-                            && !keepTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
-                        {
-                            continue;
-                        }
-
-                        seriesResult.AddPerson(new PersonInfo
-                        {
-                            Name = person.Name.Trim(),
-                            Role = person.Job,
-                            Type = type
-                        });
+                        personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture));
                     }
-                }
-            }
-        }
-
-        internal static string GetSeriesDataPath(IApplicationPaths appPaths, string tmdbId)
-        {
-            var dataPath = GetSeriesDataPath(appPaths);
-
-            return Path.Combine(dataPath, tmdbId);
-        }
-
-        internal static string GetSeriesDataPath(IApplicationPaths appPaths)
-        {
-            var dataPath = Path.Combine(appPaths.CachePath, "tmdb-tv");
-
-            return dataPath;
-        }
-
-        internal async Task DownloadSeriesInfo(string id, string preferredMetadataLanguage, CancellationToken cancellationToken)
-        {
-            SeriesResult mainResult = await FetchMainResult(id, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
-
-            if (mainResult == null)
-            {
-                return;
-            }
-
-            var dataFilePath = GetDataFilePath(id, preferredMetadataLanguage);
-
-            Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
-
-            _jsonSerializer.SerializeToFile(mainResult, dataFilePath);
-        }
-
-        internal async Task<SeriesResult> FetchMainResult(string id, string language, CancellationToken cancellationToken)
-        {
-            var url = string.Format(CultureInfo.InvariantCulture, GetTvInfo3, id, TmdbUtils.ApiKey);
-
-            if (!string.IsNullOrEmpty(language))
-            {
-                url += "&language=" + TmdbMovieProvider.NormalizeLanguage(language)
-                    + "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language); // Get images in english and with no language
-            }
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            using var mainRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
-            foreach (var header in TmdbUtils.AcceptHeaders)
-            {
-                mainRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
-            }
-
-            using var mainResponse = await TmdbMovieProvider.Current.GetMovieDbResponse(mainRequestMessage, cancellationToken).ConfigureAwait(false);
-            await using var mainStream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
-            var mainResult = await _jsonSerializer.DeserializeFromStreamAsync<SeriesResult>(mainStream).ConfigureAwait(false);
-
-            if (!string.IsNullOrEmpty(language))
-            {
-                mainResult.ResultLanguage = language;
-            }
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            // If the language preference isn't english, then have the overview fallback to english if it's blank
-            if (mainResult != null &&
-                string.IsNullOrEmpty(mainResult.Overview) &&
-                !string.IsNullOrEmpty(language) &&
-                !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
-            {
-                _logger.LogInformation("MovieDbSeriesProvider couldn't find meta for language {Language}. Trying English...", language);
-
-                url = string.Format(CultureInfo.InvariantCulture, GetTvInfo3, id, TmdbUtils.ApiKey) + "&language=en";
-
-                if (!string.IsNullOrEmpty(language))
-                {
-                    // Get images in english and with no language
-                    url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language);
-                }
 
-                using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
-                foreach (var header in TmdbUtils.AcceptHeaders)
-                {
-                    mainRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
+                    yield return personInfo;
                 }
-
-                using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false);
-                await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
-                var englishResult = await _jsonSerializer.DeserializeFromStreamAsync<SeriesResult>(stream).ConfigureAwait(false);
-
-                mainResult.Overview = englishResult.Overview;
-                mainResult.ResultLanguage = "en";
-            }
-
-            return mainResult;
-        }
-
-        internal Task EnsureSeriesInfo(string tmdbId, string language, CancellationToken cancellationToken)
-        {
-            if (string.IsNullOrEmpty(tmdbId))
-            {
-                throw new ArgumentNullException(nameof(tmdbId));
             }
 
-            var path = GetDataFilePath(tmdbId, language);
-
-            var fileInfo = new FileInfo(path);
-            if (fileInfo.Exists)
+            if (seriesResult.Credits?.Crew != null)
             {
-                // If it's recent or automatic updates are enabled, don't re-download
-                if ((DateTime.UtcNow - fileInfo.LastWriteTimeUtc).TotalDays <= 2)
+                var keepTypes = new[]
                 {
-                    return Task.CompletedTask;
-                }
-            }
-
-            return DownloadSeriesInfo(tmdbId, language, cancellationToken);
-        }
-
-        internal string GetDataFilePath(string tmdbId, string preferredLanguage)
-        {
-            if (string.IsNullOrEmpty(tmdbId))
-            {
-                throw new ArgumentNullException(nameof(tmdbId));
-            }
-
-            var path = GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
-
-            var filename = string.Format(CultureInfo.InvariantCulture, "series-{0}.json", preferredLanguage ?? string.Empty);
-
-            return Path.Combine(path, filename);
-        }
-
-        private async Task<RemoteSearchResult> FindByExternalId(string id, string externalSource, CancellationToken cancellationToken)
-        {
-            var url = string.Format(
-                CultureInfo.InvariantCulture,
-                TmdbUtils.BaseTmdbApiUrl + @"3/find/{0}?api_key={1}&external_source={2}",
-                id,
-                TmdbUtils.ApiKey,
-                externalSource);
-
-            using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
-            foreach (var header in TmdbUtils.AcceptHeaders)
-            {
-                requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
-            }
-
-            using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false);
-            await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
-
-            var result = await _jsonSerializer.DeserializeFromStreamAsync<ExternalIdLookupResult>(stream).ConfigureAwait(false);
-
-            if (result != null && result.Tv_Results != null)
-            {
-                var tv = result.Tv_Results.FirstOrDefault();
+                    PersonType.Director,
+                    PersonType.Writer,
+                    PersonType.Producer
+                };
 
-                if (tv != null)
+                foreach (var person in seriesResult.Credits.Crew)
                 {
-                    var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
-                    var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
+                    // Normalize this
+                    var type = TmdbUtils.MapCrewToPersonType(person);
 
-                    var remoteResult = new RemoteSearchResult
+                    if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase)
+                        && !keepTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
                     {
-                        Name = tv.Name,
-                        SearchProviderName = Name,
-                        ImageUrl = string.IsNullOrWhiteSpace(tv.Poster_Path)
-                            ? null
-                            : tmdbImageUrl + tv.Poster_Path
-                    };
-
-                    remoteResult.SetProviderId(MetadataProvider.Tmdb, tv.Id.ToString(_usCulture));
+                        continue;
+                    }
 
-                    return remoteResult;
+                    yield return new PersonInfo
+                    {
+                        Name = person.Name.Trim(),
+                        Role = person.Job,
+                        Type = type
+                    };
                 }
             }
-
-            return null;
         }
 
         public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)

+ 469 - 0
MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs

@@ -0,0 +1,469 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Memory;
+using TMDbLib.Client;
+using TMDbLib.Objects.Collections;
+using TMDbLib.Objects.Find;
+using TMDbLib.Objects.General;
+using TMDbLib.Objects.Movies;
+using TMDbLib.Objects.People;
+using TMDbLib.Objects.Search;
+using TMDbLib.Objects.TvShows;
+
+namespace MediaBrowser.Providers.Plugins.Tmdb
+{
+    /// <summary>
+    /// Manager class for abstracting the TMDb API client library.
+    /// </summary>
+    public class TmdbClientManager
+    {
+        private const int CacheDurationInHours = 1;
+
+        private readonly IMemoryCache _memoryCache;
+        private readonly TMDbClient _tmDbClient;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="TmdbClientManager"/> class.
+        /// </summary>
+        /// <param name="memoryCache">An instance of <see cref="IMemoryCache"/>.</param>
+        public TmdbClientManager(IMemoryCache memoryCache)
+        {
+            _memoryCache = memoryCache;
+            _tmDbClient = new TMDbClient(TmdbUtils.ApiKey);
+            // Not really interested in NotFoundException
+            _tmDbClient.ThrowApiExceptions = false;
+        }
+
+        /// <summary>
+        /// Gets a movie from the TMDb API based on its TMDb id.
+        /// </summary>
+        /// <param name="tmdbId">The movie's TMDb id.</param>
+        /// <param name="language">The movie's language.</param>
+        /// <param name="imageLanguages">A comma-separated list of image languages.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>The TMDb movie or null if not found.</returns>
+        public async Task<Movie> GetMovieAsync(int tmdbId, string language, string imageLanguages, CancellationToken cancellationToken)
+        {
+            var key = $"movie-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
+            if (_memoryCache.TryGetValue(key, out Movie movie))
+            {
+                return movie;
+            }
+
+            await EnsureClientConfigAsync().ConfigureAwait(false);
+
+            // TODO include image language
+            movie = await _tmDbClient.GetMovieAsync(
+                tmdbId,
+                TmdbUtils.NormalizeLanguage(language),
+                MovieMethods.Credits | MovieMethods.Releases | MovieMethods.Images | MovieMethods.Keywords | MovieMethods.Videos,
+                cancellationToken).ConfigureAwait(false);
+
+            if (movie != null)
+            {
+                _memoryCache.Set(key, movie, TimeSpan.FromHours(CacheDurationInHours));
+            }
+
+            return movie;
+        }
+
+        /// <summary>
+        /// Gets a collection from the TMDb API based on its TMDb id.
+        /// </summary>
+        /// <param name="tmdbId">The collection's TMDb id.</param>
+        /// <param name="language">The collection's language.</param>
+        /// <param name="imageLanguages">A comma-separated list of image languages.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>The TMDb collection or null if not found.</returns>
+        public async Task<Collection> GetCollectionAsync(int tmdbId, string language, string imageLanguages, CancellationToken cancellationToken)
+        {
+            var key = $"collection-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
+            if (_memoryCache.TryGetValue(key, out Collection collection))
+            {
+                return collection;
+            }
+
+            await EnsureClientConfigAsync().ConfigureAwait(false);
+
+            // TODO include image language
+            collection = await _tmDbClient.GetCollectionAsync(
+                tmdbId,
+                TmdbUtils.NormalizeLanguage(language),
+                CollectionMethods.Images,
+                cancellationToken).ConfigureAwait(false);
+
+            if (collection != null)
+            {
+                _memoryCache.Set(key, collection, TimeSpan.FromHours(CacheDurationInHours));
+            }
+
+            return collection;
+        }
+
+        /// <summary>
+        /// Gets a tv show from the TMDb API based on its TMDb id.
+        /// </summary>
+        /// <param name="tmdbId">The tv show's TMDb id.</param>
+        /// <param name="language">The tv show's language.</param>
+        /// <param name="imageLanguages">A comma-separated list of image languages.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>The TMDb tv show information or null if not found.</returns>
+        public async Task<TvShow> GetSeriesAsync(int tmdbId, string language, string imageLanguages, CancellationToken cancellationToken)
+        {
+            var key = $"series-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
+            if (_memoryCache.TryGetValue(key, out TvShow series))
+            {
+                return series;
+            }
+
+            await EnsureClientConfigAsync().ConfigureAwait(false);
+
+            // TODO include image language
+            series = await _tmDbClient.GetTvShowAsync(
+                tmdbId,
+                language: TmdbUtils.NormalizeLanguage(language),
+                extraMethods: TvShowMethods.Credits | TvShowMethods.Images | TvShowMethods.Keywords | TvShowMethods.ExternalIds | TvShowMethods.Videos | TvShowMethods.ContentRatings,
+                cancellationToken: cancellationToken).ConfigureAwait(false);
+
+            if (series != null)
+            {
+                _memoryCache.Set(key, series, TimeSpan.FromHours(CacheDurationInHours));
+            }
+
+            return series;
+        }
+
+        /// <summary>
+        /// Gets a tv season from the TMDb API based on the tv show's TMDb id.
+        /// </summary>
+        /// <param name="tvShowId">The tv season's TMDb id.</param>
+        /// <param name="seasonNumber">The season number.</param>
+        /// <param name="language">The tv season's language.</param>
+        /// <param name="imageLanguages">A comma-separated list of image languages.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>The TMDb tv season information or null if not found.</returns>
+        public async Task<TvSeason> GetSeasonAsync(int tvShowId, int seasonNumber, string language, string imageLanguages, CancellationToken cancellationToken)
+        {
+            var key = $"season-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}-{language}";
+            if (_memoryCache.TryGetValue(key, out TvSeason season))
+            {
+                return season;
+            }
+
+            await EnsureClientConfigAsync().ConfigureAwait(false);
+
+            // TODO include image language
+            season = await _tmDbClient.GetTvSeasonAsync(
+                tvShowId,
+                seasonNumber,
+                language: TmdbUtils.NormalizeLanguage(language),
+                extraMethods: TvSeasonMethods.Credits | TvSeasonMethods.Images | TvSeasonMethods.ExternalIds | TvSeasonMethods.Videos,
+                cancellationToken: cancellationToken).ConfigureAwait(false);
+
+            if (season != null)
+            {
+                _memoryCache.Set(key, season, TimeSpan.FromHours(CacheDurationInHours));
+            }
+
+            return season;
+        }
+
+        /// <summary>
+        /// Gets a movie from the TMDb API based on the tv show's TMDb id.
+        /// </summary>
+        /// <param name="tvShowId">The tv show's TMDb id.</param>
+        /// <param name="seasonNumber">The season number.</param>
+        /// <param name="episodeNumber">The episode number.</param>
+        /// <param name="language">The episode's language.</param>
+        /// <param name="imageLanguages">A comma-separated list of image languages.</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 language, string imageLanguages, CancellationToken cancellationToken)
+        {
+            var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{language}";
+            if (_memoryCache.TryGetValue(key, out TvEpisode episode))
+            {
+                return episode;
+            }
+
+            await EnsureClientConfigAsync().ConfigureAwait(false);
+
+            // TODO include image language
+            episode = await _tmDbClient.GetTvEpisodeAsync(
+                tvShowId,
+                seasonNumber,
+                episodeNumber,
+                language: TmdbUtils.NormalizeLanguage(language),
+                extraMethods: TvEpisodeMethods.Credits | TvEpisodeMethods.Images | TvEpisodeMethods.ExternalIds | TvEpisodeMethods.Videos,
+                cancellationToken: cancellationToken).ConfigureAwait(false);
+
+            if (episode != null)
+            {
+                _memoryCache.Set(key, episode, TimeSpan.FromHours(CacheDurationInHours));
+            }
+
+            return episode;
+        }
+
+        /// <summary>
+        /// Gets a person eg. cast or crew member from the TMDb API based on its TMDb id.
+        /// </summary>
+        /// <param name="personTmdbId">The person's TMDb id.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>The TMDb person information or null if not found.</returns>
+        public async Task<Person> GetPersonAsync(int personTmdbId, CancellationToken cancellationToken)
+        {
+            var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}";
+            if (_memoryCache.TryGetValue(key, out Person person))
+            {
+                return person;
+            }
+
+            await EnsureClientConfigAsync().ConfigureAwait(false);
+
+            person = await _tmDbClient.GetPersonAsync(
+                personTmdbId,
+                PersonMethods.TvCredits | PersonMethods.MovieCredits | PersonMethods.Images | PersonMethods.ExternalIds,
+                cancellationToken).ConfigureAwait(false);
+
+            if (person != null)
+            {
+                _memoryCache.Set(key, person, TimeSpan.FromHours(CacheDurationInHours));
+            }
+
+            return person;
+        }
+
+        /// <summary>
+        /// Gets an item from the TMDb API based on its id from an external service eg. IMDb id, TvDb id.
+        /// </summary>
+        /// <param name="externalId">The item's external id.</param>
+        /// <param name="source">The source of the id eg. IMDb.</param>
+        /// <param name="language">The item's language.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>The TMDb item or null if not found.</returns>
+        public async Task<FindContainer> FindByExternalIdAsync(
+            string externalId,
+            FindExternalSource source,
+            string language,
+            CancellationToken cancellationToken)
+        {
+            var key = $"find-{source.ToString()}-{externalId.ToString(CultureInfo.InvariantCulture)}-{language}";
+            if (_memoryCache.TryGetValue(key, out FindContainer result))
+            {
+                return result;
+            }
+
+            await EnsureClientConfigAsync().ConfigureAwait(false);
+
+            // TODO language
+            result = await _tmDbClient.FindAsync(
+                source,
+                externalId,
+                cancellationToken).ConfigureAwait(false);
+
+            if (result != null)
+            {
+                _memoryCache.Set(key, result, TimeSpan.FromHours(CacheDurationInHours));
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// Searches for a tv show using the TMDb API based on its name.
+        /// </summary>
+        /// <param name="name">The name of the tv show.</param>
+        /// <param name="language">The tv show's language.</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, CancellationToken cancellationToken)
+        {
+            var key = $"searchseries-{name}-{language}";
+            if (_memoryCache.TryGetValue(key, out SearchContainer<SearchTv> series))
+            {
+                return series.Results;
+            }
+
+            await EnsureClientConfigAsync().ConfigureAwait(false);
+
+            var searchResults = await _tmDbClient
+                .SearchTvShowAsync(name, TmdbUtils.NormalizeLanguage(language), cancellationToken: cancellationToken)
+                .ConfigureAwait(false);
+
+            if (searchResults.Results.Count > 0)
+            {
+                _memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours));
+            }
+
+            return searchResults.Results;
+        }
+
+        /// <summary>
+        /// Searches for a person based on their name using the TMDb API.
+        /// </summary>
+        /// <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)
+        {
+            var key = $"searchperson-{name}";
+            if (_memoryCache.TryGetValue(key, out SearchContainer<SearchPerson> person))
+            {
+                return person.Results;
+            }
+
+            await EnsureClientConfigAsync().ConfigureAwait(false);
+
+            var searchResults = await _tmDbClient
+                .SearchPersonAsync(name, cancellationToken: cancellationToken)
+                .ConfigureAwait(false);
+
+            if (searchResults.Results.Count > 0)
+            {
+                _memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours));
+            }
+
+            return searchResults.Results;
+        }
+
+        /// <summary>
+        /// Searches for a movie based on its name using the TMDb API.
+        /// </summary>
+        /// <param name="name">The name of the movie.</param>
+        /// <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)
+        {
+            return SearchMovieAsync(name, 0, language, cancellationToken);
+        }
+
+        /// <summary>
+        /// Searches for a movie based on its name using the TMDb API.
+        /// </summary>
+        /// <param name="name">The name of the movie.</param>
+        /// <param name="year">The release year of the movie.</param>
+        /// <param name="language">The movie's language.</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, CancellationToken cancellationToken)
+        {
+            var key = $"moviesearch-{name}-{year.ToString(CultureInfo.InvariantCulture)}-{language}";
+            if (_memoryCache.TryGetValue(key, out SearchContainer<SearchMovie> movies))
+            {
+                return movies.Results;
+            }
+
+            await EnsureClientConfigAsync().ConfigureAwait(false);
+
+            var searchResults = await _tmDbClient
+                .SearchMovieAsync(name, TmdbUtils.NormalizeLanguage(language), year: year, cancellationToken: cancellationToken)
+                .ConfigureAwait(false);
+
+            if (searchResults.Results.Count > 0)
+            {
+                _memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours));
+            }
+
+            return searchResults.Results;
+        }
+
+        /// <summary>
+        /// Searches for a collection based on its name using the TMDb API.
+        /// </summary>
+        /// <param name="name">The name of the collection.</param>
+        /// <param name="language">The collection's language.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>The TMDb collection information.</returns>
+        public async Task<IReadOnlyList<SearchCollection>> SearchCollectionAsync(string name, string language, CancellationToken cancellationToken)
+        {
+            var key = $"collectionsearch-{name}-{language}";
+            if (_memoryCache.TryGetValue(key, out SearchContainer<SearchCollection> collections))
+            {
+                return collections.Results;
+            }
+
+            await EnsureClientConfigAsync().ConfigureAwait(false);
+
+            var searchResults = await _tmDbClient
+                .SearchCollectionAsync(name, TmdbUtils.NormalizeLanguage(language), cancellationToken: cancellationToken)
+                .ConfigureAwait(false);
+
+            if (searchResults.Results.Count > 0)
+            {
+                _memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours));
+            }
+
+            return searchResults.Results;
+        }
+
+        /// <summary>
+        /// Gets the absolute URL of the poster.
+        /// </summary>
+        /// <param name="posterPath">The relative URL of the poster.</param>
+        /// <returns>The absolute URL.</returns>
+        public string GetPosterUrl(string posterPath)
+        {
+            if (string.IsNullOrEmpty(posterPath))
+            {
+                return null;
+            }
+
+            return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.PosterSizes[^1], posterPath).ToString();
+        }
+
+        /// <summary>
+        /// Gets the absolute URL of the backdrop image.
+        /// </summary>
+        /// <param name="posterPath">The relative URL of the backdrop image.</param>
+        /// <returns>The absolute URL.</returns>
+        public string GetBackdropUrl(string posterPath)
+        {
+            if (string.IsNullOrEmpty(posterPath))
+            {
+                return null;
+            }
+
+            return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.BackdropSizes[^1], posterPath).ToString();
+        }
+
+        /// <summary>
+        /// Gets the absolute URL of the profile image.
+        /// </summary>
+        /// <param name="actorProfilePath">The relative URL of the profile image.</param>
+        /// <returns>The absolute URL.</returns>
+        public string GetProfileUrl(string actorProfilePath)
+        {
+            if (string.IsNullOrEmpty(actorProfilePath))
+            {
+                return null;
+            }
+
+            return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.ProfileSizes[^1], actorProfilePath).ToString();
+        }
+
+        /// <summary>
+        /// Gets the absolute URL of the still image.
+        /// </summary>
+        /// <param name="filePath">The relative URL of the still image.</param>
+        /// <returns>The absolute URL.</returns>
+        public string GetStillUrl(string filePath)
+        {
+            if (string.IsNullOrEmpty(filePath))
+            {
+                return null;
+            }
+
+            return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.StillSizes[^1], filePath).ToString();
+        }
+
+        private Task EnsureClientConfigAsync()
+        {
+            return !_tmDbClient.HasConfig ? _tmDbClient.GetConfigAsync() : Task.CompletedTask;
+        }
+    }
+}

+ 89 - 1
MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs

@@ -1,7 +1,10 @@
+#nullable enable
+
 using System;
+using System.Collections.Generic;
 using System.Net.Mime;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
+using TMDbLib.Objects.General;
 
 namespace MediaBrowser.Providers.Plugins.Tmdb
 {
@@ -30,11 +33,26 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
         /// </summary>
         public const string ApiKey = "4219e299c89411838049ab0dab19ebd5";
 
+        /// <summary>
+        /// Maximum number of cast members to pull.
+        /// </summary>
+        public const int MaxCastMembers = 15;
+
         /// <summary>
         /// Value of the Accept header for requests to the provider.
         /// </summary>
         public static readonly string[] AcceptHeaders = { MediaTypeNames.Application.Json, "image/*" };
 
+        /// <summary>
+        /// The crew types to keep.
+        /// </summary>
+        public static readonly string[] WantedCrewTypes =
+        {
+            PersonType.Director,
+            PersonType.Writer,
+            PersonType.Producer
+        };
+
         /// <summary>
         /// Maps the TMDB provided roles for crew members to Jellyfin roles.
         /// </summary>
@@ -61,5 +79,75 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
 
             return null;
         }
+
+        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));
+        }
+
+        public static string GetImageLanguagesParam(string preferredLanguage)
+        {
+            var languages = new List<string>();
+
+            if (!string.IsNullOrEmpty(preferredLanguage))
+            {
+                preferredLanguage = NormalizeLanguage(preferredLanguage);
+
+                languages.Add(preferredLanguage);
+
+                if (preferredLanguage.Length == 5) // like en-US
+                {
+                    // Currenty, 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");
+
+            if (!string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase))
+            {
+                languages.Add("en");
+            }
+
+            return string.Join(',', languages);
+        }
+
+        public static string NormalizeLanguage(string language)
+        {
+            if (string.IsNullOrEmpty(language))
+            {
+                return language;
+            }
+
+            // They require this to be uppercase
+            // Everything after the hyphen must be written in uppercase due to a way TMDB wrote their api.
+            // See here: https://www.themoviedb.org/talk/5119221d760ee36c642af4ad?page=3#56e372a0c3a3685a9e0019ab
+            var parts = language.Split('-');
+
+            if (parts.Length == 2)
+            {
+                language = parts[0] + "-" + parts[1].ToUpperInvariant();
+            }
+
+            return language;
+        }
+
+        public static string AdjustImageLanguage(string imageLanguage, string requestLanguage)
+        {
+            if (!string.IsNullOrEmpty(imageLanguage)
+                && !string.IsNullOrEmpty(requestLanguage)
+                && requestLanguage.Length > 2
+                && imageLanguage.Length == 2
+                && requestLanguage.StartsWith(imageLanguage, StringComparison.OrdinalIgnoreCase))
+            {
+                return requestLanguage;
+            }
+
+            return imageLanguage;
+        }
     }
 }

+ 0 - 43
MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs

@@ -1,43 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.Tmdb.Movies;
-
-namespace MediaBrowser.Providers.Plugins.Tmdb.Trailers
-{
-    public class TmdbTrailerProvider : IHasOrder, IRemoteMetadataProvider<Trailer, TrailerInfo>
-    {
-        private readonly IHttpClientFactory _httpClientFactory;
-
-        public TmdbTrailerProvider(IHttpClientFactory httpClientFactory)
-        {
-            _httpClientFactory = httpClientFactory;
-        }
-
-        public string Name => TmdbMovieProvider.Current.Name;
-
-        public int Order => 0;
-
-        public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(TrailerInfo searchInfo, CancellationToken cancellationToken)
-        {
-            return TmdbMovieProvider.Current.GetMovieSearchResults(searchInfo, cancellationToken);
-        }
-
-        public Task<MetadataResult<Trailer>> GetMetadata(TrailerInfo info, CancellationToken cancellationToken)
-        {
-            return TmdbMovieProvider.Current.GetItemMetadata<Trailer>(info, cancellationToken);
-        }
-
-        public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
-        {
-            return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
-        }
-    }
-}