Merge pull request #4202 from cvium/tmdblib_migration

Migrate the TMDb providers to the TMDbLib library
pull/4276/head
Joshua M. Boniface 4 years ago committed by GitHub
commit d47284d59f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

@ -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);
}
}
}

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

@ -2,6 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
@ -12,25 +13,25 @@ using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Providers; 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 namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
{ {
public class TmdbBoxSetImageProvider : IRemoteImageProvider, IHasOrder public class TmdbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
{ {
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
public TmdbBoxSetImageProvider(IHttpClientFactory httpClientFactory) public TmdbBoxSetImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
{ {
_httpClientFactory = httpClientFactory; _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) 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) 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(); return Enumerable.Empty<RemoteImageInfo>();
}
var mainResult = await TmdbBoxSetProvider.Current.GetMovieDbResult(tmdbId, null, cancellationToken).ConfigureAwait(false);
if (mainResult != null) var language = item.GetPreferredMetadataLanguage();
{
var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
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) for (var i = 0; i < collection.Images.Posters.Count; i++)
{
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
{ {
Url = baseUrl + i.File_Path, var poster = collection.Images.Posters[i];
CommunityRating = i.Vote_Average, remoteImages.Add(new RemoteImageInfo
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)) Url = _tmdbClientManager.GetPosterUrl(poster.FilePath),
{ CommunityRating = poster.VoteAverage,
return 2; 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; Url = _tmdbClientManager.GetBackdropUrl(backdrop.FilePath),
} CommunityRating = backdrop.VoteAverage,
VoteCount = backdrop.VoteCount,
return 0; Width = backdrop.Width,
}) Height = backdrop.Height,
.ThenByDescending(i => i.CommunityRating ?? 0) ProviderName = Name,
.ThenByDescending(i => i.VoteCount ?? 0); Type = ImageType.Backdrop,
} RatingType = RatingType.Score
});
/// <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;
return eligibleBackdrops.OrderByDescending(i => i.Vote_Average) return remoteImages.OrderByLanguageDescending(language);
.ThenByDescending(i => i.Vote_Count);
} }
public int Order => 0;
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);

@ -3,268 +3,116 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers; 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 namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
{ {
public class TmdbBoxSetProvider : IRemoteMetadataProvider<BoxSet, BoxSetInfo> 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 IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager; private readonly TmdbClientManager _tmdbClientManager;
public TmdbBoxSetProvider( public TmdbBoxSetProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
ILogger<TmdbBoxSetProvider> logger,
IJsonSerializer json,
IServerConfigurationManager config,
IFileSystem fileSystem,
IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager)
{ {
_logger = logger;
_json = json;
_config = config;
_fileSystem = fileSystem;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_libraryManager = libraryManager; _tmdbClientManager = tmdbClientManager;
Current = this;
} }
private readonly CultureInfo _usCulture = new CultureInfo("en-US"); public string Name => TmdbUtils.ProviderName;
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(BoxSetInfo searchInfo, CancellationToken cancellationToken) 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 collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), 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 tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); if (collection == null)
{
return Enumerable.Empty<RemoteSearchResult>();
}
var result = new RemoteSearchResult var result = new RemoteSearchResult
{ {
Name = info.Name, Name = collection.Name,
SearchProviderName = Name, SearchProviderName = Name
ImageUrl = images.Count == 0 ? null : (tmdbImageUrl + images[0].File_Path)
}; };
result.SetProviderId(MetadataProvider.Tmdb, info.Id.ToString(_usCulture)); if (collection.Images != null)
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)
{ {
result.HasMetadata = true; result.ImageUrl = _tmdbClientManager.GetPosterUrl(collection.PosterPath);
result.Item = GetItem(mainResult);
} }
}
return result; result.SetProviderId(MetadataProvider.Tmdb, collection.Id.ToString(CultureInfo.InvariantCulture));
}
internal async Task<CollectionResult> GetMovieDbResult(string tmdbId, string language, CancellationToken cancellationToken) return new[] { result };
{
if (string.IsNullOrEmpty(tmdbId))
{
throw new ArgumentNullException(nameof(tmdbId));
} }
await EnsureInfo(tmdbId, language, cancellationToken).ConfigureAwait(false); var collectionSearchResults = await _tmdbClientManager.SearchCollectionAsync(searchInfo.Name, language, cancellationToken).ConfigureAwait(false);
var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, language);
if (!string.IsNullOrEmpty(dataFilePath)) var collections = new List<RemoteSearchResult>();
for (var i = 0; i < collectionSearchResults.Count; i++)
{ {
return _json.DeserializeFromFile<CollectionResult>(dataFilePath); var collection = new RemoteSearchResult
} {
Name = collectionSearchResults[i].Name,
return null; SearchProviderName = Name
} };
collection.SetProviderId(MetadataProvider.Tmdb, collectionSearchResults[i].Id.ToString(CultureInfo.InvariantCulture));
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);
if (mainResult == null) collections.Add(collection);
{
return;
} }
var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage); return collections;
Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
_json.SerializeToFile(mainResult, dataFilePath);
} }
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); var tmdbId = Convert.ToInt32(id.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
var language = id.MetadataLanguage;
if (!string.IsNullOrEmpty(language)) // 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 if (searchResults != null && searchResults.Count > 0)
url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language); {
tmdbId = searchResults[0].Id;
}
} }
cancellationToken.ThrowIfCancellationRequested(); var result = new MetadataResult<BoxSet>();
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url); if (tmdbId > 0)
foreach (var header in TmdbUtils.AcceptHeaders)
{ {
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
}
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);
cancellationToken.ThrowIfCancellationRequested(); if (collection != null)
if (mainResult != null && string.IsNullOrEmpty(mainResult.Name))
{
if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
{ {
url = string.Format(CultureInfo.InvariantCulture, GetCollectionInfo3, id, TmdbUtils.ApiKey) + "&language=en"; var item = new BoxSet
if (!string.IsNullOrEmpty(language))
{ {
// Get images in english and with no language Name = collection.Name,
url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language); Overview = collection.Overview
} };
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);
}
}
return mainResult; item.SetProviderId(MetadataProvider.Tmdb, collection.Id.ToString(CultureInfo.InvariantCulture));
}
internal Task EnsureInfo(string tmdbId, string preferredMetadataLanguage, CancellationToken cancellationToken)
{
var path = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage);
var fileInfo = _fileSystem.GetFileSystemInfo(path); result.HasMetadata = true;
result.Item = item;
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 DownloadInfo(tmdbId, preferredMetadataLanguage, cancellationToken); return result;
}
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;
} }
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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();
}
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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();
}
}
}
}

@ -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);
}
}
}

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

@ -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);
}
}
}

@ -3,26 +3,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.Linq;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers; 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 namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{ {
@ -31,365 +22,273 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
/// </summary> /// </summary>
public class TmdbMovieProvider : IRemoteMetadataProvider<Movie, MovieInfo>, IHasOrder 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 IHttpClientFactory _httpClientFactory;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly ILogger<TmdbMovieProvider> _logger;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IApplicationHost _appHost; private readonly TmdbClientManager _tmdbClientManager;
/// <summary>
/// The _TMDB settings task.
/// </summary>
private TmdbSettingsResult _tmdbSettings;
public TmdbMovieProvider( public TmdbMovieProvider(
IJsonSerializer jsonSerializer,
IHttpClientFactory httpClientFactory,
IFileSystem fileSystem,
IServerConfigurationManager configurationManager,
ILogger<TmdbMovieProvider> logger,
ILibraryManager libraryManager, ILibraryManager libraryManager,
IApplicationHost appHost) TmdbClientManager tmdbClientManager,
IHttpClientFactory httpClientFactory)
{ {
_jsonSerializer = jsonSerializer;
_httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_logger = logger;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_appHost = appHost; _tmdbClientManager = tmdbClientManager;
Current = this; _httpClientFactory = httpClientFactory;
} }
internal static TmdbMovieProvider Current { get; private set; }
/// <inheritdoc />
public string Name => TmdbUtils.ProviderName; public string Name => TmdbUtils.ProviderName;
/// <inheritdoc /> /// <inheritdoc />
public int Order => 1; public int Order => 1;
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken)
{ {
return GetMovieSearchResults(searchInfo, cancellationToken); var tmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
}
public async Task<IEnumerable<RemoteSearchResult>> GetMovieSearchResults(ItemLookupInfo searchInfo, CancellationToken cancellationToken)
{
var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb);
if (!string.IsNullOrEmpty(tmdbId)) if (tmdbId == 0)
{ {
cancellationToken.ThrowIfCancellationRequested(); var movieResults = await _tmdbClientManager
.SearchMovieAsync(searchInfo.Name, searchInfo.MetadataLanguage, cancellationToken)
await EnsureMovieInfo(tmdbId, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); .ConfigureAwait(false);
var remoteSearchResults = new List<RemoteSearchResult>();
var dataFilePath = GetDataFilePath(tmdbId, searchInfo.MetadataLanguage); for (var i = 0; i < movieResults.Count; i++)
var obj = _jsonSerializer.DeserializeFromFile<MovieResult>(dataFilePath);
var tmdbSettings = await GetTmdbSettings(cancellationToken).ConfigureAwait(false);
var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
var remoteResult = new RemoteSearchResult
{ {
Name = obj.GetTitle(), var movieResult = movieResults[i];
SearchProviderName = Name, var remoteSearchResult = new RemoteSearchResult
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))
{ {
remoteResult.PremiereDate = r.ToUniversalTime(); Name = movieResult.Title ?? movieResult.OriginalTitle,
remoteResult.ProductionYear = remoteResult.PremiereDate.Value.Year; ImageUrl = _tmdbClientManager.GetPosterUrl(movieResult.PosterPath),
} Overview = movieResult.Overview,
} SearchProviderName = Name
};
remoteResult.SetProviderId(MetadataProvider.Tmdb, obj.Id.ToString(_usCulture));
var releaseDate = movieResult.ReleaseDate?.ToUniversalTime();
if (!string.IsNullOrWhiteSpace(obj.Imdb_Id)) remoteSearchResult.PremiereDate = releaseDate;
{ remoteSearchResult.ProductionYear = releaseDate?.Year;
remoteResult.SetProviderId(MetadataProvider.Imdb, obj.Imdb_Id);
remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, movieResult.Id.ToString(CultureInfo.InvariantCulture));
remoteSearchResults.Add(remoteSearchResult);
} }
return new[] { remoteResult }; return remoteSearchResults;
} }
return await new TmdbSearch(_logger, _jsonSerializer, _libraryManager).GetMovieSearchResults(searchInfo, cancellationToken).ConfigureAwait(false); var movie = await _tmdbClientManager
} .GetMovieAsync(tmdbId, searchInfo.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage), cancellationToken)
.ConfigureAwait(false);
public Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
{
return GetItemMetadata<Movie>(info, cancellationToken);
}
public Task<MetadataResult<T>> GetItemMetadata<T>(ItemLookupInfo id, CancellationToken cancellationToken) var remoteResult = new RemoteSearchResult
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)
{ {
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)); if (movie.ReleaseDate != null)
foreach (var header in TmdbUtils.AcceptHeaders)
{ {
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); remoteResult.SetProviderId(MetadataProvider.Tmdb, movie.Id.ToString(CultureInfo.InvariantCulture));
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) if (!string.IsNullOrWhiteSpace(movie.ImdbId))
{
var dataPath = Path.Combine(appPaths.CachePath, "tmdb-movies2");
return dataPath;
}
/// <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)
{ {
return; remoteResult.SetProviderId(MetadataProvider.Imdb, movie.ImdbId);
} }
var dataFilePath = GetDataFilePath(id, preferredMetadataLanguage); return new[] { remoteResult };
Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
_jsonSerializer.SerializeToFile(mainResult, dataFilePath);
} }
internal Task EnsureMovieInfo(string tmdbId, string language, CancellationToken cancellationToken) public async Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
{ {
if (string.IsNullOrEmpty(tmdbId)) var tmdbId = info.GetProviderId(MetadataProvider.Tmdb);
{ var imdbId = info.GetProviderId(MetadataProvider.Imdb);
throw new ArgumentNullException(nameof(tmdbId));
}
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 // ParseName is required here.
if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) // 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)) 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>
{ {
preferredLanguage = "alllang"; HasMetadata = true,
ResultLanguage = info.MetadataLanguage,
Item = movie
};
movie.SetProviderId(MetadataProvider.Tmdb, tmdbId);
movie.SetProviderId(MetadataProvider.Imdb, movieResult.ImdbId);
if (movieResult.BelongsToCollection != null)
{
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 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));
var languages = new List<string>();
if (!string.IsNullOrEmpty(preferredLanguage)) if (ourRelease != null)
{ {
preferredLanguage = NormalizeLanguage(preferredLanguage); 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 movie.OfficialRating = usRelease.Certification;
// 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"); 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) foreach (var genre in genres.Select(g => g.Name))
{
if (!string.IsNullOrEmpty(language))
{ {
// They require this to be uppercase movie.AddGenre(genre);
// 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) if (movieResult.Keywords?.Keywords != null)
{
for (var i = 0; i < movieResult.Keywords.Keywords.Count; i++)
{ {
language = parts[0] + "-" + parts[1].ToUpperInvariant(); movie.AddTag(movieResult.Keywords.Keywords[i].Name);
} }
} }
return language; if (movieResult.Credits?.Cast != null)
}
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; // TODO configurable
} foreach (var actor in movieResult.Credits.Cast.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers))
{
return imageLanguage; var personInfo = new PersonInfo
} {
Name = actor.Name.Trim(),
Role = actor.Character,
Type = PersonType.Actor,
SortOrder = actor.Order
};
/// <summary> if (!string.IsNullOrWhiteSpace(actor.ProfilePath))
/// Fetches the main result. {
/// </summary> personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath);
/// <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.IsNullOrEmpty(language)) if (actor.Id > 0)
{ {
url += string.Format(CultureInfo.InvariantCulture, "&language={0}", NormalizeLanguage(language)); personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture));
}
// Get images in english and with no language metadataResult.AddPerson(personInfo);
url += "&include_image_language=" + GetImageLanguagesParam(language); }
} }
cancellationToken.ThrowIfCancellationRequested(); if (movieResult.Credits?.Crew != null)
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
foreach (var header in TmdbUtils.AcceptHeaders)
{ {
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); foreach (var person in movieResult.Credits.Crew)
if (mainResponse.StatusCode == HttpStatusCode.NotFound) {
{ // Normalize this
return null; var type = TmdbUtils.MapCrewToPersonType(person);
}
await using var stream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase) &&
var mainResult = await _jsonSerializer.DeserializeFromStreamAsync<MovieResult>(stream).ConfigureAwait(false); !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 (!string.IsNullOrWhiteSpace(person.ProfilePath))
if (mainResult != null && {
string.IsNullOrEmpty(mainResult.Overview) && personInfo.ImageUrl = _tmdbClientManager.GetPosterUrl(person.ProfilePath);
!string.IsNullOrEmpty(language) && }
!string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
{
_logger.LogInformation("MovieDbProvider couldn't find meta for language " + language + ". Trying English...");
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)) metadataResult.AddPerson(personInfo);
{
// Get images in english and with no language
url += "&include_image_language=" + GetImageLanguagesParam(language);
} }
}
using var langRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); if (movieResult.Videos?.Results != null)
foreach (var header in TmdbUtils.AcceptHeaders) {
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); movie.RemoteTrailers = trailers;
var langResult = await _jsonSerializer.DeserializeFromStreamAsync<MovieResult>(stream).ConfigureAwait(false);
mainResult.Overview = langResult.Overview;
} }
return mainResult; return metadataResult;
}
/// <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);
} }
/// <inheritdoc /> /// <inheritdoc />

@ -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();
}
}
}

@ -1,9 +0,0 @@
#pragma warning disable CS1591
namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
internal class TmdbSettingsResult
{
public TmdbImageSettings images { get; set; }
}
}

@ -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();
}
}
}

@ -2,40 +2,33 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Providers; 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 namespace MediaBrowser.Providers.Plugins.Tmdb.People
{ {
public class TmdbPersonImageProvider : IRemoteImageProvider, IHasOrder public class TmdbPersonImageProvider : IRemoteImageProvider, IHasOrder
{ {
private readonly IServerConfigurationManager _config;
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClientFactory _httpClientFactory; 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; _httpClientFactory = httpClientFactory;
_tmdbClientManager = tmdbClientManager;
} }
public static string ProviderName => TmdbUtils.ProviderName;
/// <inheritdoc /> /// <inheritdoc />
public string Name => ProviderName; public string Name => TmdbUtils.ProviderName;
/// <inheritdoc /> /// <inheritdoc />
public int Order => 0; public int Order => 0;
@ -56,78 +49,37 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{ {
var person = (Person)item; var person = (Person)item;
var id = person.GetProviderId(MetadataProvider.Tmdb); var personTmdbId = Convert.ToInt32(person.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
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>();
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, return Enumerable.Empty<RemoteImageInfo>();
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;
} }
if (!isLanguageEn) var remoteImages = new List<RemoteImageInfo>();
{ var language = item.GetPreferredMetadataLanguage();
if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
{
return 2;
}
}
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; return remoteImages.OrderByLanguageDescending(language);
}) }
.ThenByDescending(i => i.CommunityRating ?? 0)
.ThenByDescending(i => i.VoteCount ?? 0);
}
private string GetLanguage(Profile profile) return Enumerable.Empty<RemoteImageInfo>();
{
return profile.Iso_639_1?.ToString();
} }
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)

@ -3,198 +3,130 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Providers; 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 namespace MediaBrowser.Providers.Plugins.Tmdb.People
{ {
public class TmdbPersonProvider : IRemoteMetadataProvider<Person, PersonLookupInfo> 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 IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
public TmdbPersonProvider( public TmdbPersonProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
IFileSystem fileSystem,
IServerConfigurationManager configurationManager,
IJsonSerializer jsonSerializer,
IHttpClientFactory httpClientFactory)
{ {
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_jsonSerializer = jsonSerializer;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
Current = this; _tmdbClientManager = tmdbClientManager;
} }
internal static TmdbPersonProvider Current { get; private set; }
public string Name => TmdbUtils.ProviderName; public string Name => TmdbUtils.ProviderName;
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken)
{ {
var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb); var personTmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
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); if (personResult != null)
var info = _jsonSerializer.DeserializeFromFile<PersonResult>(dataFilePath);
IReadOnlyList<Profile> images = info.Images?.Profiles ?? Array.Empty<Profile>();
var result = new RemoteSearchResult
{ {
Name = info.Name, var result = new RemoteSearchResult
{
SearchProviderName = Name, 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.Tmdb, personResult.Id.ToString(CultureInfo.InvariantCulture));
result.SetProviderId(MetadataProvider.Imdb, info.Imdb_Id); result.SetProviderId(MetadataProvider.Imdb, personResult.ExternalIds.ImdbId);
return new[] { result }; return new[] { result };
}
} }
// TODO why? Because of the old rate limit?
if (searchInfo.IsAutomated) if (searchInfo.IsAutomated)
{ {
// Don't hammer moviedb searching by name // Don't hammer moviedb searching by name
return Array.Empty<RemoteSearchResult>(); return Enumerable.Empty<RemoteSearchResult>();
} }
var url = string.Format( var personSearchResult = await _tmdbClientManager.SearchPersonAsync(searchInfo.Name, cancellationToken).ConfigureAwait(false);
CultureInfo.InvariantCulture,
TmdbUtils.BaseTmdbApiUrl + @"3/search/person?api_key={1}&query={0}",
WebUtility.UrlEncode(searchInfo.Name),
TmdbUtils.ApiKey);
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url); var remoteSearchResults = new List<RemoteSearchResult>();
foreach (var header in TmdbUtils.AcceptHeaders) for (var i = 0; i < personSearchResult.Count; i++)
{ {
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); var person = personSearchResult[i];
} var remoteSearchResult = new RemoteSearchResult
{
var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false); SearchProviderName = Name,
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); Name = person.Name,
ImageUrl = _tmdbClientManager.GetProfileUrl(person.ProfilePath)
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
};
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) 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 // 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>(); var result = new MetadataResult<Person>();
if (!string.IsNullOrEmpty(tmdbId)) if (personTmdbId > 0)
{ {
try var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false);
{
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 item = new Person();
result.HasMetadata = true; result.HasMetadata = true;
// Take name from incoming info, don't rename the person var item = new 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))
{ {
item.ProductionLocations = new string[] { info.Place_Of_Birth }; // Take name from incoming info, don't rename the person
} // TODO: This should go in PersonMetadataService, not each person provider
Name = id.Name,
item.Overview = info.Biography; HomePageUrl = person.Homepage,
Overview = person.Biography,
if (DateTime.TryParseExact(info.Birthday, "yyyy-MM-dd", new CultureInfo("en-US"), DateTimeStyles.None, out var date)) PremiereDate = person.Birthday?.ToUniversalTime(),
{ EndDate = person.Deathday?.ToUniversalTime()
item.PremiereDate = date.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; result.HasMetadata = true;
@ -204,65 +136,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
return result; 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) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);

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

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

@ -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);
}
}
}

@ -2,7 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
@ -13,29 +13,25 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Providers; 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 namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{ {
public class TmdbSeasonImageProvider : IRemoteImageProvider, IHasOrder public class TmdbSeasonImageProvider : IRemoteImageProvider, IHasOrder
{ {
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
public TmdbSeasonImageProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory) public TmdbSeasonImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
{ {
_jsonSerializer = jsonSerializer;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_tmdbClientManager = tmdbClientManager;
} }
public int Order => 1; public int Order => 1;
public string Name => ProviderName; public string Name => TmdbUtils.ProviderName;
public static string ProviderName => TmdbUtils.ProviderName;
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) 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) public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{ {
var season = (Season)item; var season = (Season)item;
var series = season.Series; var series = season?.Series;
var seriesId = series?.GetProviderId(MetadataProvider.Tmdb);
if (string.IsNullOrEmpty(seriesId))
{
return Enumerable.Empty<RemoteImageInfo>();
}
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>(); return Enumerable.Empty<RemoteImageInfo>();
} }
var language = item.GetPreferredMetadataLanguage(); 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); if (seasonResult?.Images?.Posters == null)
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 (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase)) return Enumerable.Empty<RemoteImageInfo>();
{ }
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);
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) public IEnumerable<ImageType> GetSupportedImages(BaseItem item)

@ -3,53 +3,28 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.Linq;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Providers; 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 namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{ {
public class TmdbSeasonProvider : IRemoteMetadataProvider<Season, SeasonInfo> 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 IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _configurationManager; private readonly TmdbClientManager _tmdbClientManager;
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem; public TmdbSeasonProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
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)
{ {
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_configurationManager = configurationManager; _tmdbClientManager = tmdbClientManager;
_fileSystem = fileSystem;
_jsonSerializer = jsonSerializer;
_logger = logger;
Current = this;
} }
public string Name => TmdbUtils.ProviderName; public string Name => TmdbUtils.ProviderName;
@ -62,180 +37,86 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var seasonNumber = info.IndexNumber; var seasonNumber = info.IndexNumber;
if (!string.IsNullOrWhiteSpace(seriesTmdbId) && seasonNumber.HasValue) if (string.IsNullOrWhiteSpace(seriesTmdbId) || !seasonNumber.HasValue)
{ {
try return result;
{
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; var seasonResult = await _tmdbClientManager
} .GetSeasonAsync(Convert.ToInt32(seriesTmdbId, CultureInfo.InvariantCulture), seasonNumber.Value, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
.ConfigureAwait(false);
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);
}
internal Task EnsureSeasonInfo(string tmdbId, int seasonNumber, string language, CancellationToken cancellationToken) if (seasonResult == null)
{
if (string.IsNullOrEmpty(tmdbId))
{ {
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); if (!string.IsNullOrEmpty(seasonResult.ExternalIds?.TvdbId))
{
var fileInfo = _fileSystem.GetFileSystemInfo(path); 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 var cast = credits.Cast.OrderBy(c => c.Order).Take(TmdbUtils.MaxCastMembers).ToList();
if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) 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); if (credits?.Crew != null)
}
internal string GetDataFilePath(string tmdbId, int seasonNumber, string preferredLanguage)
{
if (string.IsNullOrEmpty(tmdbId))
{ {
throw new ArgumentNullException(nameof(tmdbId)); foreach (var person in credits.Crew)
} {
// Normalize this
var type = TmdbUtils.MapCrewToPersonType(person);
if (string.IsNullOrEmpty(preferredLanguage)) if (!TmdbUtils.WantedCrewTypes.Contains(type, StringComparer.OrdinalIgnoreCase)
{ && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
throw new ArgumentNullException(nameof(preferredLanguage)); {
} 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( result.Item.PremiereDate = seasonResult.AirDate;
CultureInfo.InvariantCulture, result.Item.ProductionYear = seasonResult.AirDate?.Year;
"season-{0}-{1}.json",
seasonNumber.ToString(CultureInfo.InvariantCulture),
preferredLanguage);
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); return Task.FromResult(Enumerable.Empty<RemoteSearchResult>());
var dataFilePath = GetDataFilePath(id, seasonNumber, preferredMetadataLanguage);
Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
_jsonSerializer.SerializeToFile(mainResult, dataFilePath);
} }
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( return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
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);
} }
} }
} }

@ -2,7 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
@ -13,28 +13,23 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Providers; 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 namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{ {
public class TmdbSeriesImageProvider : IRemoteImageProvider, IHasOrder public class TmdbSeriesImageProvider : IRemoteImageProvider, IHasOrder
{ {
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
public TmdbSeriesImageProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory) public TmdbSeriesImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
{ {
_jsonSerializer = jsonSerializer;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_tmdbClientManager = tmdbClientManager;
} }
public string Name => ProviderName; public string Name => TmdbUtils.ProviderName;
public static string ProviderName => TmdbUtils.ProviderName;
// After tvdb and fanart // After tvdb and fanart
public int Order => 2; public int Order => 2;
@ -55,124 +50,60 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{ {
var list = new List<RemoteImageInfo>(); var tmdbId = item.GetProviderId(MetadataProvider.Tmdb);
var results = await FetchImages(item, null, cancellationToken).ConfigureAwait(false);
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(); var language = item.GetPreferredMetadataLanguage();
list.AddRange(GetPosters(results).Select(i => new RemoteImageInfo var series = await _tmdbClientManager
{ .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken)
Url = tmdbImageUrl + i.File_Path, .ConfigureAwait(false);
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);
}
/// <summary> if (series?.Images == null)
/// 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))
{ {
return null; return Enumerable.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) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)

@ -3,107 +3,80 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers; using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization; using TMDbLib.Objects.Find;
using MediaBrowser.Providers.Plugins.Tmdb.Models.Search; using TMDbLib.Objects.Search;
using MediaBrowser.Providers.Plugins.Tmdb.Models.TV; using TMDbLib.Objects.TvShows;
using MediaBrowser.Providers.Plugins.Tmdb.Movies;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Plugins.Tmdb.TV namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{ {
public class TmdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder 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 IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager; private readonly TmdbClientManager _tmdbClientManager;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public TmdbSeriesProvider( public TmdbSeriesProvider(
IJsonSerializer jsonSerializer,
IServerConfigurationManager configurationManager,
ILogger<TmdbSeriesProvider> logger,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager) TmdbClientManager tmdbClientManager)
{ {
_jsonSerializer = jsonSerializer;
_configurationManager = configurationManager;
_logger = logger;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_libraryManager = libraryManager; _tmdbClientManager = tmdbClientManager;
Current = this; Current = this;
} }
internal static TmdbSeriesProvider Current { get; private set; }
public string Name => TmdbUtils.ProviderName; public string Name => TmdbUtils.ProviderName;
// After TheTVDB // After TheTVDB
public int Order => 1; public int Order => 1;
internal static TmdbSeriesProvider Current { get; private set; }
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
{ {
var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb); var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb);
if (!string.IsNullOrEmpty(tmdbId)) if (!string.IsNullOrEmpty(tmdbId))
{ {
cancellationToken.ThrowIfCancellationRequested(); var series = await _tmdbClientManager
.GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, searchInfo.MetadataLanguage, cancellationToken)
await EnsureSeriesInfo(tmdbId, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); .ConfigureAwait(false);
var dataFilePath = GetDataFilePath(tmdbId, searchInfo.MetadataLanguage);
var obj = _jsonSerializer.DeserializeFromFile<SeriesResult>(dataFilePath);
var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); if (series != null)
var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
var remoteResult = new RemoteSearchResult
{ {
Name = obj.Name, var remoteResult = MapTvShowToRemoteSearchResult(series);
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);
if (obj.External_Ids != null && obj.External_Ids.Tvdb_Id > 0) return new[] { remoteResult };
{
remoteResult.SetProviderId(MetadataProvider.Tvdb, obj.External_Ids.Tvdb_Id.Value.ToString(_usCulture));
} }
return new[] { remoteResult };
} }
var imdbId = searchInfo.GetProviderId(MetadataProvider.Imdb); var imdbId = searchInfo.GetProviderId(MetadataProvider.Imdb);
if (!string.IsNullOrEmpty(imdbId)) 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 +84,79 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (!string.IsNullOrEmpty(tvdbId)) 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(CultureInfo.InvariantCulture));
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(CultureInfo.InvariantCulture));
remoteResult.PremiereDate = series.FirstAirDate?.ToUniversalTime();
return remoteResult;
} }
public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken) public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken)
@ -137,11 +174,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (!string.IsNullOrEmpty(imdbId)) 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) if (searchResult != null)
{ {
tmdbId = searchResult.GetProviderId(MetadataProvider.Tmdb); tmdbId = searchResult.TvResults.FirstOrDefault()?.Id.ToString(CultureInfo.InvariantCulture);
} }
} }
} }
@ -152,11 +189,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (!string.IsNullOrEmpty(tvdbId)) 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) if (searchResult != null)
{ {
tmdbId = searchResult.GetProviderId(MetadataProvider.Tmdb); tmdbId = searchResult.TvResults.FirstOrDefault()?.Id.ToString(CultureInfo.InvariantCulture);
} }
} }
} }
@ -164,13 +201,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (string.IsNullOrEmpty(tmdbId)) if (string.IsNullOrEmpty(tmdbId))
{ {
result.QueriedById = false; result.QueriedById = false;
var searchResults = await new TmdbSearch(_logger, _jsonSerializer, _libraryManager).GetSearchResults(info, cancellationToken).ConfigureAwait(false); var searchResults = await _tmdbClientManager.SearchSeriesAsync(info.Name, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
var searchResult = searchResults.FirstOrDefault();
if (searchResult != null) if (searchResults.Count > 0)
{ {
tmdbId = searchResult.GetProviderId(MetadataProvider.Tmdb); tmdbId = searchResults[0].Id.ToString(CultureInfo.InvariantCulture);
} }
} }
@ -178,107 +213,91 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
result = await FetchMovieData(tmdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); var tvShow = await _tmdbClientManager
.GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
result.HasMetadata = result.Item != null; .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) foreach (var person in GetPersons(tvShow))
{ {
SeriesResult seriesInfo = await FetchMainResult(tmdbId, language, cancellationToken).ConfigureAwait(false); result.AddPerson(person);
}
if (seriesInfo == null) result.HasMetadata = result.Item != null;
{
return 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; 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.SetProviderId(MetadataProvider.Tmdb, seriesResult.Id.ToString(CultureInfo.InvariantCulture));
series.Name = seriesInfo.Name; series.CommunityRating = Convert.ToSingle(seriesResult.VoteAverage);
series.OriginalTitle = seriesInfo.Original_Name;
series.SetProviderId(MetadataProvider.Tmdb, seriesInfo.Id.ToString(_usCulture));
string voteAvg = seriesInfo.Vote_Average.ToString(CultureInfo.InvariantCulture); series.Overview = seriesResult.Overview;
if (float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out float rating)) if (seriesResult.Networks != null)
{ {
series.CommunityRating = rating; series.Studios = seriesResult.Networks.Select(i => i.Name).ToArray();
} }
series.Overview = seriesInfo.Overview; if (seriesResult.Genres != null)
if (seriesInfo.Networks != null)
{ {
series.Studios = seriesInfo.Networks.Select(i => i.Name).ToArray(); series.Genres = seriesResult.Genres.Select(i => i.Name).ToArray();
} }
if (seriesInfo.Genres != null) if (seriesResult.Keywords?.Results != null)
{ {
series.Genres = seriesInfo.Genres.Select(i => i.Name).ToArray(); for (var i = 0; i < seriesResult.Keywords.Results.Count; i++)
{
series.AddTag(seriesResult.Keywords.Results[i].Name);
}
} }
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.Status = SeriesStatus.Ended;
series.EndDate = seriesInfo.Last_Air_Date; series.EndDate = seriesResult.LastAirDate;
} }
else else
{ {
series.Status = SeriesStatus.Continuing; 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 (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 ourRelease = contentRatings.FirstOrDefault(c => string.Equals(c.Iso_3166_1, preferredCountryCode, StringComparison.OrdinalIgnoreCase));
var usRelease = contentRatings.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.OrdinalIgnoreCase)); var usRelease = contentRatings.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.OrdinalIgnoreCase));
@ -297,254 +316,72 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
series.OfficialRating = minimumRelease.Rating; 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) if (TmdbUtils.IsTrailerType(video))
|| video.Type.Equals("clip", StringComparison.OrdinalIgnoreCase))
&& video.Site.Equals("youtube", StringComparison.OrdinalIgnoreCase))
{ {
series.AddTrailerUrl($"http://www.youtube.com/watch?v={video.Key}"); series.AddTrailerUrl($"http://www.youtube.com/watch?v={video.Key}");
} }
} }
} }
seriesResult.ResetPeople(); return series;
var tmdbImageUrl = settings.images.GetImageUrl("original"); }
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
{
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[] var personInfo = new PersonInfo
{ {
PersonType.Director, Name = actor.Name.Trim(),
PersonType.Writer, Role = actor.Character,
PersonType.Producer Type = PersonType.Actor,
SortOrder = actor.Order,
ImageUrl = _tmdbClientManager.GetPosterUrl(actor.ProfilePath)
}; };
foreach (var person in seriesInfo.Credits.Crew) if (actor.Id > 0)
{ {
// Normalize this personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture));
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
});
} }
}
}
}
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) yield return personInfo;
{
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));
}
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); if (seriesResult.Credits?.Crew != null)
var fileInfo = new FileInfo(path);
if (fileInfo.Exists)
{ {
// If it's recent or automatic updates are enabled, don't re-download var keepTypes = new[]
if ((DateTime.UtcNow - fileInfo.LastWriteTimeUtc).TotalDays <= 2)
{ {
return Task.CompletedTask; PersonType.Director,
} PersonType.Writer,
} PersonType.Producer
};
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();
if (tv != null) foreach (var person in seriesResult.Credits.Crew)
{ {
var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); // Normalize this
var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); 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, continue;
SearchProviderName = Name, }
ImageUrl = string.IsNullOrWhiteSpace(tv.Poster_Path)
? null
: tmdbImageUrl + tv.Poster_Path
};
remoteResult.SetProviderId(MetadataProvider.Tmdb, tv.Id.ToString(_usCulture));
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) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)

@ -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);
movie = await _tmDbClient.GetMovieAsync(
tmdbId,
TmdbUtils.NormalizeLanguage(language),
imageLanguages,
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);
collection = await _tmDbClient.GetCollectionAsync(
tmdbId,
TmdbUtils.NormalizeLanguage(language),
imageLanguages,
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);
series = await _tmDbClient.GetTvShowAsync(
tmdbId,
language: TmdbUtils.NormalizeLanguage(language),
includeImageLanguage: imageLanguages,
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);
season = await _tmDbClient.GetTvSeasonAsync(
tvShowId,
seasonNumber,
language: TmdbUtils.NormalizeLanguage(language),
includeImageLanguage: imageLanguages,
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);
episode = await _tmDbClient.GetTvEpisodeAsync(
tvShowId,
seasonNumber,
episodeNumber,
language: TmdbUtils.NormalizeLanguage(language),
includeImageLanguage: imageLanguages,
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);
result = await _tmDbClient.FindAsync(
source,
externalId,
TmdbUtils.NormalizeLanguage(language),
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;
}
}
}

@ -1,7 +1,9 @@
#nullable enable
using System; using System;
using System.Net.Mime; using System.Collections.Generic;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Providers.Plugins.Tmdb.Models.General; using TMDbLib.Objects.General;
namespace MediaBrowser.Providers.Plugins.Tmdb namespace MediaBrowser.Providers.Plugins.Tmdb
{ {
@ -15,11 +17,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// </summary> /// </summary>
public const string BaseTmdbUrl = "https://www.themoviedb.org/"; public const string BaseTmdbUrl = "https://www.themoviedb.org/";
/// <summary>
/// URL of the TMDB API instance to use.
/// </summary>
public const string BaseTmdbApiUrl = "https://api.themoviedb.org/";
/// <summary> /// <summary>
/// Name of the provider. /// Name of the provider.
/// </summary> /// </summary>
@ -31,9 +28,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
public const string ApiKey = "4219e299c89411838049ab0dab19ebd5"; public const string ApiKey = "4219e299c89411838049ab0dab19ebd5";
/// <summary> /// <summary>
/// Value of the Accept header for requests to the provider. /// Maximum number of cast members to pull.
/// </summary>
public const int MaxCastMembers = 15;
/// <summary>
/// The crew types to keep.
/// </summary> /// </summary>
public static readonly string[] AcceptHeaders = { MediaTypeNames.Application.Json, "image/*" }; public static readonly string[] WantedCrewTypes =
{
PersonType.Director,
PersonType.Writer,
PersonType.Producer
};
/// <summary> /// <summary>
/// Maps the TMDB provided roles for crew members to Jellyfin roles. /// Maps the TMDB provided roles for crew members to Jellyfin roles.
@ -59,7 +66,98 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
return PersonType.Writer; return PersonType.Writer;
} }
return null; return string.Empty;
}
/// <summary>
/// Determines whether a video is a trailer.
/// </summary>
/// <param name="video">The TMDb video.</param>
/// <returns>A boolean indicating whether the video is a trailer.</returns>
public static bool IsTrailerType(Video video)
{
return video.Site.Equals("youtube", StringComparison.OrdinalIgnoreCase)
&& (!video.Type.Equals("trailer", StringComparison.OrdinalIgnoreCase)
|| !video.Type.Equals("teaser", StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Normalizes a language string for use with TMDb's include image language parameter.
/// </summary>
/// <param name="preferredLanguage">The preferred language as either a 2 letter code with or without country code.</param>
/// <returns>The comma separated language string.</returns>
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);
}
/// <summary>
/// Normalizes a language string for use with TMDb's language parameter.
/// </summary>
/// <param name="language">The language code.</param>
/// <returns>The normalized language code.</returns>
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;
}
/// <summary>
/// Adjusts the image's language code preferring the 5 letter language code eg. en-US.
/// </summary>
/// <param name="imageLanguage">The image's actual language code.</param>
/// <param name="requestLanguage">The requested language code.</param>
/// <returns>The language code.</returns>
public static string AdjustImageLanguage(string imageLanguage, string requestLanguage)
{
if (!string.IsNullOrEmpty(imageLanguage)
&& !string.IsNullOrEmpty(requestLanguage)
&& requestLanguage.Length > 2
&& imageLanguage.Length == 2
&& requestLanguage.StartsWith(imageLanguage, StringComparison.OrdinalIgnoreCase))
{
return requestLanguage;
}
return imageLanguage;
} }
} }
} }

@ -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);
}
}
}
Loading…
Cancel
Save