Migrate the TMDb providers to the TMDbLib library

pull/4202/head
cvium 4 years ago
parent c0be770681
commit e9524f89d6

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

@ -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="OptimizedPriorityQueue" Version="4.2.0" />
<PackageReference Include="PlaylistsNET" Version="1.1.2" />
<PackageReference Include="TMDbLib" Version="1.7.1-alpha" />
<PackageReference Include="TvDbSharper" Version="3.2.2" />
</ItemGroup>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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