Migrate the TMDb providers to the TMDbLib library
This commit is contained in:
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);
|
||||
|
||||
|
|
46
MediaBrowser.Model/Extensions/EnumerableExtensions.cs
Normal file
46
MediaBrowser.Model/Extensions/EnumerableExtensions.cs
Normal file
|
@ -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);
|
||||
|
||||
if (mainResult != null)
|
||||
{
|
||||
var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
|
||||
|
||||
return GetImages(mainResult, language, tmdbImageUrl);
|
||||
}
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
return new List<RemoteImageInfo>();
|
||||
}
|
||||
var language = item.GetPreferredMetadataLanguage();
|
||||
|
||||
private IEnumerable<RemoteImageInfo> GetImages(CollectionResult obj, string language, string baseUrl)
|
||||
{
|
||||
var list = new List<RemoteImageInfo>();
|
||||
var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var images = obj.Images ?? new CollectionImages();
|
||||
|
||||
list.AddRange(GetPosters(images).Select(i => new RemoteImageInfo
|
||||
if (collection?.Images == null)
|
||||
{
|
||||
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
|
||||
}));
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
list.AddRange(GetBackdrops(images).Select(i => new RemoteImageInfo
|
||||
var remoteImages = new List<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 poster = collection.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
|
||||
});
|
||||
}
|
||||
|
||||
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
return list.OrderByDescending(i =>
|
||||
for (var i = 0; i < collection.Images.Backdrops.Count; i++)
|
||||
{
|
||||
if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
|
||||
var backdrop = collection.Images.Backdrops[i];
|
||||
remoteImages.Add(new RemoteImageInfo
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
return remoteImages.OrderByLanguageDescending(language);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the posters.
|
||||
/// </summary>
|
||||
/// <param name="images">The images.</param>
|
||||
/// <returns>IEnumerable{MovieDbProvider.Poster}.</returns>
|
||||
private IEnumerable<Poster> GetPosters(CollectionImages images)
|
||||
{
|
||||
return images.Posters ?? new List<Poster>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the backdrops.
|
||||
/// </summary>
|
||||
/// <param name="images">The images.</param>
|
||||
/// <returns>IEnumerable{MovieDbProvider.Backdrop}.</returns>
|
||||
private IEnumerable<Backdrop> GetBackdrops(CollectionImages images)
|
||||
{
|
||||
var eligibleBackdrops = images.Backdrops == null ? new List<Backdrop>() :
|
||||
images.Backdrops;
|
||||
|
||||
return eligibleBackdrops.OrderByDescending(i => i.Vote_Average)
|
||||
.ThenByDescending(i => i.Vote_Count);
|
||||
}
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
|
|
|
@ -3,270 +3,118 @@
|
|||
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 collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, searchInfo.MetadataLanguage);
|
||||
var info = _json.DeserializeFromFile<CollectionResult>(dataFilePath);
|
||||
|
||||
var images = (info.Images ?? new CollectionImages()).Posters ?? new List<Poster>();
|
||||
|
||||
var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
|
||||
if (collection == null)
|
||||
{
|
||||
return Enumerable.Empty<RemoteSearchResult>();
|
||||
}
|
||||
|
||||
var result = new RemoteSearchResult
|
||||
{
|
||||
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));
|
||||
if (collection.Images != null)
|
||||
{
|
||||
result.ImageUrl = _tmdbClientManager.GetPosterUrl(collection.PosterPath);
|
||||
}
|
||||
|
||||
result.SetProviderId(MetadataProvider.Tmdb, collection.Id.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
return new[] { result };
|
||||
}
|
||||
|
||||
return await new TmdbSearch(_logger, _json, _libraryManager).GetSearchResults(searchInfo, cancellationToken).ConfigureAwait(false);
|
||||
var collectionSearchResults = await _tmdbClientManager.SearchCollectionAsync(searchInfo.Name, language, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var collections = new List<RemoteSearchResult>();
|
||||
for (var i = 0; i < collectionSearchResults.Count; i++)
|
||||
{
|
||||
var collection = new RemoteSearchResult
|
||||
{
|
||||
Name = collectionSearchResults[i].Name,
|
||||
SearchProviderName = Name
|
||||
};
|
||||
collection.SetProviderId(MetadataProvider.Tmdb, collectionSearchResults[i].Id.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
collections.Add(collection);
|
||||
}
|
||||
|
||||
return collections;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<BoxSet>> GetMetadata(BoxSetInfo id, CancellationToken cancellationToken)
|
||||
{
|
||||
var tmdbId = id.GetProviderId(MetadataProvider.Tmdb);
|
||||
|
||||
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 (string.IsNullOrEmpty(tmdbId))
|
||||
if (tmdbId <= 0)
|
||||
{
|
||||
var searchResults = await new TmdbSearch(_logger, _json, _libraryManager).GetSearchResults(id, cancellationToken).ConfigureAwait(false);
|
||||
var searchResults = await _tmdbClientManager.SearchCollectionAsync(id.Name, language, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var searchResult = searchResults.FirstOrDefault();
|
||||
|
||||
if (searchResult != null)
|
||||
if (searchResults != null && searchResults.Count > 0)
|
||||
{
|
||||
tmdbId = searchResult.GetProviderId(MetadataProvider.Tmdb);
|
||||
tmdbId = searchResults[0].Id;
|
||||
}
|
||||
}
|
||||
|
||||
var result = new MetadataResult<BoxSet>();
|
||||
|
||||
if (!string.IsNullOrEmpty(tmdbId))
|
||||
if (tmdbId > 0)
|
||||
{
|
||||
var mainResult = await GetMovieDbResult(tmdbId, id.MetadataLanguage, cancellationToken).ConfigureAwait(false);
|
||||
var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (mainResult != null)
|
||||
if (collection != null)
|
||||
{
|
||||
var item = new BoxSet
|
||||
{
|
||||
Name = collection.Name,
|
||||
Overview = collection.Overview
|
||||
};
|
||||
|
||||
item.SetProviderId(MetadataProvider.Tmdb, collection.Id.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
result.HasMetadata = true;
|
||||
result.Item = GetItem(mainResult);
|
||||
result.Item = item;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task<CollectionResult> GetMovieDbResult(string tmdbId, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tmdbId));
|
||||
}
|
||||
|
||||
await EnsureInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, language);
|
||||
|
||||
if (!string.IsNullOrEmpty(dataFilePath))
|
||||
{
|
||||
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);
|
||||
|
||||
if (mainResult == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
|
||||
|
||||
_json.SerializeToFile(mainResult, dataFilePath);
|
||||
}
|
||||
|
||||
private async Task<CollectionResult> FetchMainResult(string id, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
var url = string.Format(CultureInfo.InvariantCulture, GetCollectionInfo3, id, TmdbUtils.ApiKey);
|
||||
|
||||
if (!string.IsNullOrEmpty(language))
|
||||
{
|
||||
url += string.Format(CultureInfo.InvariantCulture, "&language={0}", TmdbMovieProvider.NormalizeLanguage(language));
|
||||
|
||||
// Get images in english and with no language
|
||||
url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language);
|
||||
}
|
||||
|
||||
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 mainResponse = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
var mainResult = await _json.DeserializeFromStreamAsync<CollectionResult>(stream).ConfigureAwait(false);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (mainResult != null && string.IsNullOrEmpty(mainResult.Name))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
url = string.Format(CultureInfo.InvariantCulture, GetCollectionInfo3, id, TmdbUtils.ApiKey) + "&language=en";
|
||||
|
||||
if (!string.IsNullOrEmpty(language))
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
return mainResult;
|
||||
}
|
||||
|
||||
internal Task EnsureInfo(string tmdbId, string preferredMetadataLanguage, CancellationToken cancellationToken)
|
||||
{
|
||||
var path = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage);
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, 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;
|
||||
|
||||
/// <summary>
|
||||
/// The _TMDB settings task.
|
||||
/// </summary>
|
||||
private TmdbSettingsResult _tmdbSettings;
|
||||
|
||||
public TmdbMovieProvider(
|
||||
IJsonSerializer jsonSerializer,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IFileSystem fileSystem,
|
||||
IServerConfigurationManager configurationManager,
|
||||
ILogger<TmdbMovieProvider> logger,
|
||||
ILibraryManager libraryManager,
|
||||
IApplicationHost appHost)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_fileSystem = fileSystem;
|
||||
_configurationManager = configurationManager;
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
_appHost = appHost;
|
||||
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)
|
||||
internal static TmdbMovieProvider Current { get; private set; }
|
||||
|
||||
public TmdbMovieProvider(
|
||||
ILogger<TmdbMovieProvider> logger,
|
||||
ILibraryManager libraryManager,
|
||||
TmdbClientManager tmdbClientManager,
|
||||
IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
return GetMovieSearchResults(searchInfo, cancellationToken);
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
_tmdbClientManager = tmdbClientManager;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
Current = this;
|
||||
}
|
||||
|
||||
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
|
||||
var movieResult = movieResults[i];
|
||||
var remoteSearchResult = new RemoteSearchResult
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
return remoteSearchResults;
|
||||
}
|
||||
|
||||
var movie = await _tmdbClientManager
|
||||
.GetMovieAsync(tmdbId, searchInfo.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var remoteResult = new RemoteSearchResult
|
||||
{
|
||||
Name = movie.Title ?? movie.OriginalTitle,
|
||||
SearchProviderName = Name,
|
||||
ImageUrl = _tmdbClientManager.GetPosterUrl(movie.PosterPath),
|
||||
Overview = movie.Overview
|
||||
};
|
||||
|
||||
if (movie.ReleaseDate != null)
|
||||
{
|
||||
var releaseDate = movie.ReleaseDate.Value.ToUniversalTime();
|
||||
remoteResult.PremiereDate = releaseDate;
|
||||
remoteResult.ProductionYear = releaseDate.Year;
|
||||
}
|
||||
|
||||
remoteResult.SetProviderId(MetadataProvider.Tmdb, movie.Id.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(movie.ImdbId))
|
||||
{
|
||||
remoteResult.SetProviderId(MetadataProvider.Imdb, movie.ImdbId);
|
||||
}
|
||||
|
||||
return new[] { remoteResult };
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var tmdbId = info.GetProviderId(MetadataProvider.Tmdb);
|
||||
var imdbId = info.GetProviderId(MetadataProvider.Imdb);
|
||||
|
||||
if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId))
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
tmdbId = searchResults[0].Id.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
return new MetadataResult<Movie>();
|
||||
}
|
||||
|
||||
var movieResult = await _tmdbClientManager
|
||||
.GetMovieAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
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)
|
||||
{
|
||||
movie.SetProviderId(MetadataProvider.TmdbCollection, movieResult.BelongsToCollection.Id.ToString(CultureInfo.InvariantCulture));
|
||||
movie.CollectionName = movieResult.BelongsToCollection.Name;
|
||||
}
|
||||
|
||||
movie.CommunityRating = Convert.ToSingle(movieResult.VoteAverage);
|
||||
|
||||
if (movieResult.Releases?.Countries != null)
|
||||
{
|
||||
var releases = movieResult.Releases.Countries.Where(i => !string.IsNullOrWhiteSpace(i.Certification)).ToList();
|
||||
|
||||
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 (ourRelease != null)
|
||||
{
|
||||
var ratingPrefix = string.Equals(info.MetadataCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? string.Empty : info.MetadataCountryCode + "-";
|
||||
var newRating = ratingPrefix + ourRelease.Certification;
|
||||
|
||||
newRating = newRating.Replace("de-", "FSK-", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
movie.OfficialRating = newRating;
|
||||
}
|
||||
else if (usRelease != null)
|
||||
{
|
||||
movie.OfficialRating = usRelease.Certification;
|
||||
}
|
||||
}
|
||||
|
||||
movie.PremiereDate = movieResult.ReleaseDate;
|
||||
movie.ProductionYear = movieResult.ReleaseDate?.Year;
|
||||
|
||||
if (movieResult.ProductionCompanies != null)
|
||||
{
|
||||
movie.SetStudios(movieResult.ProductionCompanies.Select(c => c.Name));
|
||||
}
|
||||
|
||||
var genres = movieResult.Genres;
|
||||
|
||||
foreach (var genre in genres.Select(g => g.Name))
|
||||
{
|
||||
movie.AddGenre(genre);
|
||||
}
|
||||
|
||||
if (movieResult.Credits?.Cast != null)
|
||||
{
|
||||
// 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
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(actor.ProfilePath))
|
||||
{
|
||||
personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath);
|
||||
}
|
||||
|
||||
if (actor.Id > 0)
|
||||
{
|
||||
personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
metadataResult.AddPerson(personInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (movieResult.Credits?.Crew != null)
|
||||
{
|
||||
var keepTypes = new[]
|
||||
{
|
||||
PersonType.Director,
|
||||
PersonType.Writer,
|
||||
PersonType.Producer
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(obj.Release_Date))
|
||||
foreach (var person in movieResult.Credits.Crew)
|
||||
{
|
||||
// These dates are always in this exact format
|
||||
if (DateTime.TryParse(obj.Release_Date, _usCulture, DateTimeStyles.None, out var r))
|
||||
// Normalize this
|
||||
var type = TmdbUtils.MapCrewToPersonType(person);
|
||||
|
||||
if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase) &&
|
||||
!keepTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
remoteResult.PremiereDate = r.ToUniversalTime();
|
||||
remoteResult.ProductionYear = remoteResult.PremiereDate.Value.Year;
|
||||
continue;
|
||||
}
|
||||
|
||||
var personInfo = new PersonInfo
|
||||
{
|
||||
Name = person.Name.Trim(),
|
||||
Role = person.Job,
|
||||
Type = type
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(person.ProfilePath))
|
||||
{
|
||||
personInfo.ImageUrl = _tmdbClientManager.GetPosterUrl(person.ProfilePath);
|
||||
}
|
||||
|
||||
if (person.Id > 0)
|
||||
{
|
||||
personInfo.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
metadataResult.AddPerson(personInfo);
|
||||
}
|
||||
}
|
||||
|
||||
remoteResult.SetProviderId(MetadataProvider.Tmdb, obj.Id.ToString(_usCulture));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(obj.Imdb_Id))
|
||||
if (movieResult.Videos?.Results != null)
|
||||
{
|
||||
var trailers = new List<MediaUrl>();
|
||||
for (var i = 0; i < movieResult.Videos.Results.Count; i++)
|
||||
{
|
||||
remoteResult.SetProviderId(MetadataProvider.Imdb, obj.Imdb_Id);
|
||||
var video = movieResult.Videos.Results[0];
|
||||
if (!TmdbUtils.IsTrailerType(video))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
trailers.Add(new MediaUrl
|
||||
{
|
||||
Url = string.Format(CultureInfo.InvariantCulture, "https://www.youtube.com/watch?v={0}", video.Key),
|
||||
Name = video.Name
|
||||
});
|
||||
}
|
||||
|
||||
return new[] { remoteResult };
|
||||
movie.RemoteTrailers = trailers;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return _tmdbSettings;
|
||||
}
|
||||
|
||||
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, string.Format(CultureInfo.InvariantCulture, TmdbConfigUrl, TmdbUtils.ApiKey));
|
||||
foreach (var header in TmdbUtils.AcceptHeaders)
|
||||
{
|
||||
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the movie info.
|
||||
/// </summary>
|
||||
/// <param name="id">The id.</param>
|
||||
/// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
internal async Task DownloadMovieInfo(string id, string preferredMetadataLanguage, CancellationToken cancellationToken)
|
||||
{
|
||||
var mainResult = await FetchMainResult(id, true, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (mainResult == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dataFilePath = GetDataFilePath(id, preferredMetadataLanguage);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
|
||||
|
||||
_jsonSerializer.SerializeToFile(mainResult, dataFilePath);
|
||||
}
|
||||
|
||||
internal Task EnsureMovieInfo(string tmdbId, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tmdbId));
|
||||
}
|
||||
|
||||
var path = GetDataFilePath(tmdbId, 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 DownloadMovieInfo(tmdbId, language, cancellationToken);
|
||||
}
|
||||
|
||||
internal string GetDataFilePath(string tmdbId, string preferredLanguage)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tmdbId));
|
||||
}
|
||||
|
||||
var path = GetMovieDataPath(_configurationManager.ApplicationPaths, tmdbId);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(preferredLanguage))
|
||||
{
|
||||
preferredLanguage = "alllang";
|
||||
}
|
||||
|
||||
var filename = string.Format(CultureInfo.InvariantCulture, "all-{0}.json", preferredLanguage);
|
||||
|
||||
return Path.Combine(path, filename);
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <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.IsNullOrEmpty(language))
|
||||
{
|
||||
url += string.Format(CultureInfo.InvariantCulture, "&language={0}", NormalizeLanguage(language));
|
||||
|
||||
// Get images in english and with no language
|
||||
url += "&include_image_language=" + GetImageLanguagesParam(language);
|
||||
}
|
||||
|
||||
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 mainResponse = await GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false);
|
||||
if (mainResponse.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
await using var stream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
var mainResult = await _jsonSerializer.DeserializeFromStreamAsync<MovieResult>(stream).ConfigureAwait(false);
|
||||
|
||||
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("MovieDbProvider couldn't find meta for language " + language + ". Trying English...");
|
||||
|
||||
url = string.Format(CultureInfo.InvariantCulture, GetMovieInfo3, id, TmdbUtils.ApiKey) + "&language=en";
|
||||
|
||||
if (!string.IsNullOrEmpty(language))
|
||||
{
|
||||
// Get images in english and with no language
|
||||
url += "&include_image_language=" + GetImageLanguagesParam(language);
|
||||
}
|
||||
|
||||
using var langRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
foreach (var header in TmdbUtils.AcceptHeaders)
|
||||
{
|
||||
langRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
|
||||
}
|
||||
|
||||
using var langResponse = await GetMovieDbResponse(langRequestMessage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await using var langStream = await langResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
var langResult = await _jsonSerializer.DeserializeFromStreamAsync<MovieResult>(stream).ConfigureAwait(false);
|
||||
mainResult.Overview = langResult.Overview;
|
||||
}
|
||||
|
||||
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,80 +51,39 @@ 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);
|
||||
var personTmdbId = Convert.ToInt32(person.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||
|
||||
if (!string.IsNullOrEmpty(id))
|
||||
if (personTmdbId > 0)
|
||||
{
|
||||
await TmdbPersonProvider.Current.EnsurePersonInfo(id, cancellationToken).ConfigureAwait(false);
|
||||
var personResult = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false);
|
||||
if (personResult?.Images?.Profiles == null)
|
||||
{
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
var dataFilePath = TmdbPersonProvider.GetPersonDataFilePath(_config.ApplicationPaths, id);
|
||||
var remoteImages = new List<RemoteImageInfo>();
|
||||
var language = item.GetPreferredMetadataLanguage();
|
||||
|
||||
var result = _jsonSerializer.DeserializeFromFile<PersonResult>(dataFilePath);
|
||||
for (var i = 0; i < personResult.Images.Profiles.Count; i++)
|
||||
{
|
||||
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)
|
||||
});
|
||||
}
|
||||
|
||||
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 remoteImages.OrderByLanguageDescending(language);
|
||||
}
|
||||
|
||||
return new List<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
private IEnumerable<RemoteImageInfo> GetImages(PersonImages images, string preferredLanguage, string baseImageUrl)
|
||||
{
|
||||
var list = new List<RemoteImageInfo>();
|
||||
|
||||
if (images.Profiles != null)
|
||||
{
|
||||
list.AddRange(images.Profiles.Select(i => new RemoteImageInfo
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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 string GetLanguage(Profile profile)
|
||||
{
|
||||
return profile.Iso_639_1?.ToString();
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, 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 personTmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||
|
||||
var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
|
||||
|
||||
if (!string.IsNullOrEmpty(tmdbId))
|
||||
if (personTmdbId <= 0)
|
||||
{
|
||||
await EnsurePersonInfo(tmdbId, cancellationToken).ConfigureAwait(false);
|
||||
var personResult = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var dataFilePath = GetPersonDataFilePath(_configurationManager.ApplicationPaths, tmdbId);
|
||||
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,
|
||||
var result = new RemoteSearchResult
|
||||
{
|
||||
Name = personResult.Name,
|
||||
SearchProviderName = Name,
|
||||
Overview = personResult.Biography
|
||||
};
|
||||
|
||||
SearchProviderName = Name,
|
||||
if (personResult.Images?.Profiles != null && personResult.Images.Profiles.Count > 0)
|
||||
{
|
||||
result.ImageUrl = _tmdbClientManager.GetProfileUrl(personResult.Images.Profiles[0].FilePath);
|
||||
}
|
||||
|
||||
ImageUrl = images.Count == 0 ? null : (tmdbImageUrl + images[0].File_Path)
|
||||
};
|
||||
result.SetProviderId(MetadataProvider.Tmdb, personResult.Id.ToString(CultureInfo.InvariantCulture));
|
||||
result.SetProviderId(MetadataProvider.Imdb, personResult.ExternalIds.ImdbId);
|
||||
|
||||
result.SetProviderId(MetadataProvider.Tmdb, info.Id.ToString(_usCulture));
|
||||
result.SetProviderId(MetadataProvider.Imdb, info.Imdb_Id);
|
||||
|
||||
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 person = personSearchResult[i];
|
||||
var remoteSearchResult = new RemoteSearchResult
|
||||
{
|
||||
SearchProviderName = Name,
|
||||
Name = person.Name,
|
||||
ImageUrl = _tmdbClientManager.GetProfileUrl(person.ProfilePath)
|
||||
};
|
||||
|
||||
remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture));
|
||||
remoteSearchResults.Add(remoteSearchResult);
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
result.SetProviderId(MetadataProvider.Tmdb, i.Id.ToString(_usCulture));
|
||||
|
||||
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;
|
||||
}
|
||||
var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
var dataFilePath = GetPersonDataFilePath(_configurationManager.ApplicationPaths, tmdbId);
|
||||
|
||||
var info = _jsonSerializer.DeserializeFromFile<PersonResult>(dataFilePath);
|
||||
|
||||
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 };
|
||||
// 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 (!string.IsNullOrWhiteSpace(person.PlaceOfBirth))
|
||||
{
|
||||
item.ProductionLocations = new[] { person.PlaceOfBirth };
|
||||
}
|
||||
|
||||
item.Overview = info.Biography;
|
||||
item.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
if (DateTime.TryParseExact(info.Birthday, "yyyy-MM-dd", new CultureInfo("en-US"), DateTimeStyles.None, out var date))
|
||||
if (!string.IsNullOrEmpty(person.ImdbId))
|
||||
{
|
||||
item.PremiereDate = date.ToUniversalTime();
|
||||
}
|
||||
|
||||
if (DateTime.TryParseExact(info.Deathday, "yyyy-MM-dd", new CultureInfo("en-US"), DateTimeStyles.None, out date))
|
||||
{
|
||||
item.EndDate = date.ToUniversalTime();
|
||||
}
|
||||
|
||||
item.SetProviderId(MetadataProvider.Tmdb, info.Id.ToString(_usCulture));
|
||||
|
||||
if (!string.IsNullOrEmpty(info.Imdb_Id))
|
||||
{
|
||||
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 string Name => TmdbUtils.ProviderName;
|
||||
public TmdbEpisodeImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_tmdbClientManager = tmdbClientManager;
|
||||
}
|
||||
|
||||
// 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 episodeResult = await _tmdbClientManager
|
||||
.GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
|
||||
|
||||
list.AddRange(GetPosters(response.Images).Select(i => new RemoteImageInfo
|
||||
if (episodeResult?.Images?.Stills == null)
|
||||
{
|
||||
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
|
||||
}));
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
|
||||
var remoteImages = new List<RemoteImageInfo>();
|
||||
|
||||
return list.OrderByDescending(i =>
|
||||
for (var i = 0; i < episodeResult.Images.Stills.Count; i++)
|
||||
{
|
||||
if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
|
||||
var image = episodeResult.Images.Stills[i];
|
||||
remoteImages.Add(new RemoteImageInfo
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
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 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 episodeResult = await _tmdbClientManager
|
||||
.GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (episodeResult == null)
|
||||
{
|
||||
var response = await GetEpisodeInfo(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
result.HasMetadata = true;
|
||||
result.QueriedById = true;
|
||||
|
||||
if (!string.IsNullOrEmpty(response.Overview))
|
||||
{
|
||||
// if overview is non-empty, we can assume that localized data was returned
|
||||
result.ResultLanguage = info.MetadataLanguage;
|
||||
}
|
||||
|
||||
var item = new Episode();
|
||||
result.Item = item;
|
||||
|
||||
item.Name = info.Name;
|
||||
item.IndexNumber = info.IndexNumber;
|
||||
item.ParentIndexNumber = info.ParentIndexNumber;
|
||||
item.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));
|
||||
}
|
||||
|
||||
item.PremiereDate = response.Air_Date;
|
||||
item.ProductionYear = result.Item.PremiereDate.Value.Year;
|
||||
|
||||
item.Name = response.Name;
|
||||
item.Overview = response.Overview;
|
||||
|
||||
item.CommunityRating = (float)response.Vote_Average;
|
||||
|
||||
if (response.Videos?.Results != null)
|
||||
{
|
||||
foreach (var video in response.Videos.Results)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.ResetPeople();
|
||||
|
||||
var credits = response.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.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 });
|
||||
}
|
||||
}
|
||||
|
||||
// and the rest from crew
|
||||
if (credits.Crew != null)
|
||||
{
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
return metadataResult;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
|
||||
metadataResult.HasMetadata = true;
|
||||
metadataResult.QueriedById = true;
|
||||
|
||||
if (!string.IsNullOrEmpty(episodeResult.Overview))
|
||||
{
|
||||
if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
throw;
|
||||
// if overview is non-empty, we can assume that localized data was returned
|
||||
metadataResult.ResultLanguage = info.MetadataLanguage;
|
||||
}
|
||||
|
||||
return result;
|
||||
var item = new Episode
|
||||
{
|
||||
Name = info.Name,
|
||||
IndexNumber = info.IndexNumber,
|
||||
ParentIndexNumber = info.ParentIndexNumber,
|
||||
IndexNumberEnd = info.IndexNumberEnd
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(episodeResult.ExternalIds?.TvdbId))
|
||||
{
|
||||
item.SetProviderId(MetadataProvider.Tvdb, episodeResult.ExternalIds.TvdbId);
|
||||
}
|
||||
|
||||
item.PremiereDate = episodeResult.AirDate;
|
||||
item.ProductionYear = episodeResult.AirDate?.Year;
|
||||
|
||||
item.Name = episodeResult.Name;
|
||||
item.Overview = episodeResult.Overview;
|
||||
|
||||
item.CommunityRating = Convert.ToSingle(episodeResult.VoteAverage);
|
||||
|
||||
if (episodeResult.Videos?.Results != null)
|
||||
{
|
||||
foreach (var video in episodeResult.Videos.Results)
|
||||
{
|
||||
if (TmdbUtils.IsTrailerType(video))
|
||||
{
|
||||
var videoUrl = string.Format(CultureInfo.InvariantCulture, "http://www.youtube.com/watch?v={0}", video.Key);
|
||||
item.AddTrailerUrl(videoUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var credits = episodeResult.Credits;
|
||||
|
||||
if (credits?.Cast != null)
|
||||
{
|
||||
foreach (var actor in credits.Cast.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers))
|
||||
{
|
||||
metadataResult.AddPerson(new PersonInfo
|
||||
{
|
||||
Name = actor.Name.Trim(),
|
||||
Role = actor.Character,
|
||||
Type = PersonType.Actor,
|
||||
SortOrder = actor.Order
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (credits?.GuestStars != null)
|
||||
{
|
||||
foreach (var guest in credits.GuestStars.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers))
|
||||
{
|
||||
metadataResult.AddPerson(new PersonInfo
|
||||
{
|
||||
Name = guest.Name.Trim(),
|
||||
Role = guest.Character,
|
||||
Type = PersonType.GuestStar,
|
||||
SortOrder = guest.Order
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// and the rest from crew
|
||||
if (credits?.Crew != null)
|
||||
{
|
||||
foreach (var person in credits.Crew)
|
||||
{
|
||||
// Normalize this
|
||||
var type = TmdbUtils.MapCrewToPersonType(person);
|
||||
|
||||
if (!TmdbUtils.WantedCrewTypes.Contains(type, StringComparer.OrdinalIgnoreCase)
|
||||
&& !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
metadataResult.AddPerson(new PersonInfo
|
||||
{
|
||||
Name = person.Name.Trim(),
|
||||
Role = person.Job,
|
||||
Type = type
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 series = season?.Series;
|
||||
|
||||
var seriesId = series?.GetProviderId(MetadataProvider.Tmdb);
|
||||
var seriesTmdbId = Convert.ToInt32(series?.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||
|
||||
if (string.IsNullOrEmpty(seriesId))
|
||||
{
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
var seasonNumber = season.IndexNumber;
|
||||
|
||||
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
|
||||
if (seasonResult?.Images?.Posters == null)
|
||||
{
|
||||
Url = tmdbImageUrl + i.File_Path,
|
||||
CommunityRating = i.Vote_Average,
|
||||
VoteCount = i.Vote_Count,
|
||||
Width = i.Width,
|
||||
Height = i.Height,
|
||||
Language = TmdbMovieProvider.AdjustImageLanguage(i.Iso_639_1, language),
|
||||
ProviderName = Name,
|
||||
Type = ImageType.Primary,
|
||||
RatingType = RatingType.Score
|
||||
});
|
||||
|
||||
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
return list.OrderByDescending(i =>
|
||||
{
|
||||
if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (!isLanguageEn)
|
||||
{
|
||||
if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(i.Language))
|
||||
{
|
||||
return isLanguageEn ? 3 : 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
})
|
||||
.ThenByDescending(i => i.CommunityRating ?? 0)
|
||||
.ThenByDescending(i => i.VoteCount ?? 0);
|
||||
}
|
||||
|
||||
private async Task<List<Poster>> FetchImages(Season item, string tmdbId, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
var seasonNumber = item.IndexNumber.GetValueOrDefault();
|
||||
await TmdbSeasonProvider.Current.EnsureSeasonInfo(tmdbId, seasonNumber, language, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var path = TmdbSeasonProvider.Current.GetDataFilePath(tmdbId, seasonNumber, language);
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
return _jsonSerializer.DeserializeFromFile<Models.TV.SeasonResult>(path).Images.Posters;
|
||||
}
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
return null;
|
||||
var remoteImages = new List<RemoteImageInfo>();
|
||||
for (var i = 0; i < seasonResult.Images.Posters.Count; i++)
|
||||
{
|
||||
var image = seasonResult.Images.Posters[i];
|
||||
remoteImages.Add(new RemoteImageInfo
|
||||
{
|
||||
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 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;
|
||||
private readonly TmdbClientManager _tmdbClientManager;
|
||||
|
||||
internal static TmdbSeasonProvider Current { get; private set; }
|
||||
|
||||
public TmdbSeasonProvider(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IServerConfigurationManager configurationManager,
|
||||
IFileSystem fileSystem,
|
||||
IJsonSerializer jsonSerializer,
|
||||
ILogger<TmdbSeasonProvider> logger)
|
||||
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
|
||||
return result;
|
||||
}
|
||||
|
||||
var seasonResult = await _tmdbClientManager
|
||||
.GetSeasonAsync(Convert.ToInt32(seriesTmdbId, CultureInfo.InvariantCulture), seasonNumber.Value, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (seasonResult == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result.HasMetadata = true;
|
||||
result.Item = new Season
|
||||
{
|
||||
Name = info.Name,
|
||||
IndexNumber = seasonNumber,
|
||||
Overview = seasonResult?.Overview
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(seasonResult.ExternalIds?.TvdbId))
|
||||
{
|
||||
result.Item.SetProviderId(MetadataProvider.Tvdb, seasonResult.ExternalIds.TvdbId);
|
||||
}
|
||||
|
||||
// TODO why was this disabled?
|
||||
var credits = seasonResult.Credits;
|
||||
if (credits?.Cast != null)
|
||||
{
|
||||
var cast = credits.Cast.OrderBy(c => c.Order).Take(TmdbUtils.MaxCastMembers).ToList();
|
||||
for (var i = 0; i < cast.Count; i++)
|
||||
{
|
||||
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.AddPerson(new PersonInfo
|
||||
{
|
||||
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;
|
||||
Name = cast[i].Name.Trim(),
|
||||
Role = cast[i].Character,
|
||||
Type = PersonType.Actor,
|
||||
SortOrder = cast[i].Order
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (credits?.Crew != null)
|
||||
{
|
||||
foreach (var person in credits.Crew)
|
||||
{
|
||||
// Normalize this
|
||||
var type = TmdbUtils.MapCrewToPersonType(person);
|
||||
|
||||
if (!TmdbUtils.WantedCrewTypes.Contains(type, StringComparer.OrdinalIgnoreCase)
|
||||
&& !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result.AddPerson(new PersonInfo
|
||||
{
|
||||
Name = person.Name.Trim(),
|
||||
Role = person.Job,
|
||||
Type = type
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result.Item.PremiereDate = seasonResult.AirDate;
|
||||
result.Item.ProductionYear = seasonResult.AirDate?.Year;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeasonInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<RemoteSearchResult>>(new List<RemoteSearchResult>());
|
||||
return Task.FromResult(Enumerable.Empty<RemoteSearchResult>());
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<SeasonResult> GetSeasonInfo(
|
||||
string seriesTmdbId,
|
||||
int season,
|
||||
string preferredMetadataLanguage,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await EnsureSeasonInfo(seriesTmdbId, season, preferredMetadataLanguage, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var dataFilePath = GetDataFilePath(seriesTmdbId, season, preferredMetadataLanguage);
|
||||
|
||||
return _jsonSerializer.DeserializeFromFile<SeasonResult>(dataFilePath);
|
||||
}
|
||||
|
||||
internal Task EnsureSeasonInfo(string tmdbId, int seasonNumber, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tmdbId));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(language))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(language));
|
||||
}
|
||||
|
||||
var path = GetDataFilePath(tmdbId, seasonNumber, 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 DownloadSeasonInfo(tmdbId, seasonNumber, language, cancellationToken);
|
||||
}
|
||||
|
||||
internal string GetDataFilePath(string tmdbId, int seasonNumber, 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}-{1}.json",
|
||||
seasonNumber.ToString(CultureInfo.InvariantCulture),
|
||||
preferredLanguage);
|
||||
|
||||
return Path.Combine(path, filename);
|
||||
}
|
||||
|
||||
internal async Task DownloadSeasonInfo(string id, int seasonNumber, string preferredMetadataLanguage, 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);
|
||||
}
|
||||
|
||||
internal async Task<SeasonResult> FetchMainResult(string id, int seasonNumber, string language, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -54,107 +49,6 @@ 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);
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
|
||||
|
@ -163,16 +57,53 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
return null;
|
||||
}
|
||||
|
||||
await TmdbSeriesProvider.Current.EnsureSeriesInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
|
||||
var language = item.GetPreferredMetadataLanguage();
|
||||
|
||||
var path = TmdbSeriesProvider.Current.GetDataFilePath(tmdbId, language);
|
||||
var series = await _tmdbClientManager
|
||||
.GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(path) && File.Exists(path))
|
||||
if (series?.Images == null)
|
||||
{
|
||||
return _jsonSerializer.DeserializeFromFile<SeriesResult>(path).Images;
|
||||
return Array.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
return null;
|
||||
var remoteImages = new List<RemoteImageInfo>();
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
for (var i = 0; i < series.Images.Backdrops.Count; i++)
|
||||
{
|
||||
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 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();
|
||||
var series = await _tmdbClientManager
|
||||
.GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, searchInfo.MetadataLanguage, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
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 remoteResult = new RemoteSearchResult
|
||||
if (series != null)
|
||||
{
|
||||
Name = obj.Name,
|
||||
SearchProviderName = Name,
|
||||
ImageUrl = string.IsNullOrWhiteSpace(obj.Poster_Path) ? null : tmdbImageUrl + obj.Poster_Path
|
||||
};
|
||||
var remoteResult = MapTvShowToRemoteSearchResult(series);
|
||||
|
||||
remoteResult.SetProviderId(MetadataProvider.Tmdb, obj.Id.ToString(_usCulture));
|
||||
remoteResult.SetProviderId(MetadataProvider.Imdb, obj.External_Ids.Imdb_Id);
|
||||
|
||||
if (obj.External_Ids != null && obj.External_Ids.Tvdb_Id > 0)
|
||||
{
|
||||
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 (searchResult != null)
|
||||
if (findResult?.TvResults != null)
|
||||
{
|
||||
return new[] { searchResult };
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return await new TmdbSearch(_logger, _jsonSerializer, _libraryManager).GetSearchResults(searchInfo, cancellationToken).ConfigureAwait(false);
|
||||
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 (!string.IsNullOrEmpty(series.ExternalIds.TvdbId))
|
||||
{
|
||||
remoteResult.SetProviderId(MetadataProvider.Tvdb, series.ExternalIds.TvdbId);
|
||||
}
|
||||
}
|
||||
|
||||
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 searchResults = await _tmdbClientManager.SearchSeriesAsync(info.Name, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var searchResult = searchResults.FirstOrDefault();
|
||||
|
||||
if (searchResult != null)
|
||||
if (searchResults.Count > 0)
|
||||
{
|
||||
tmdbId = searchResult.GetProviderId(MetadataProvider.Tmdb);
|
||||
tmdbId = searchResults[0].Id.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,7 +225,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
result = await FetchMovieData(tmdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
|
||||
var tvShow = await _tmdbClientManager
|
||||
.GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
result = new MetadataResult<Series>
|
||||
{
|
||||
Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode),
|
||||
ResultLanguage = info.MetadataLanguage ?? tvShow.OriginalLanguage
|
||||
};
|
||||
|
||||
foreach (var person in GetPersons(tvShow))
|
||||
{
|
||||
result.AddPerson(person);
|
||||
}
|
||||
|
||||
result.HasMetadata = result.Item != null;
|
||||
}
|
||||
|
@ -186,99 +246,62 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||
return result;
|
||||
}
|
||||
|
||||
private async Task<MetadataResult<Series>> FetchMovieData(string tmdbId, string language, string preferredCountryCode, CancellationToken cancellationToken)
|
||||
private Series MapTvShowToSeries(TvShow seriesResult, string preferredCountryCode)
|
||||
{
|
||||
SeriesResult seriesInfo = await FetchMainResult(tmdbId, language, cancellationToken).ConfigureAwait(false);
|
||||
var series = new Series {Name = seriesResult.Name, OriginalTitle = seriesResult.OriginalName};
|
||||
|
||||
if (seriesInfo == null)
|
||||
series.SetProviderId(MetadataProvider.Tmdb, seriesResult.Id.ToString(_usCulture));
|
||||
|
||||
series.CommunityRating = Convert.ToSingle(seriesResult.VoteAverage);
|
||||
|
||||
series.Overview = seriesResult.Overview;
|
||||
|
||||
if (seriesResult.Networks != null)
|
||||
{
|
||||
return null;
|
||||
series.Studios = seriesResult.Networks.Select(i => i.Name).ToArray();
|
||||
}
|
||||
|
||||
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>
|
||||
if (seriesResult.Genres != null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
var series = seriesResult.Item;
|
||||
|
||||
series.Name = seriesInfo.Name;
|
||||
series.OriginalTitle = seriesInfo.Original_Name;
|
||||
series.SetProviderId(MetadataProvider.Tmdb, seriesInfo.Id.ToString(_usCulture));
|
||||
|
||||
string voteAvg = seriesInfo.Vote_Average.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
if (float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out float rating))
|
||||
{
|
||||
series.CommunityRating = rating;
|
||||
series.Genres = seriesResult.Genres.Select(i => i.Name).ToArray();
|
||||
}
|
||||
|
||||
series.Overview = seriesInfo.Overview;
|
||||
series.HomePageUrl = seriesResult.Homepage;
|
||||
|
||||
if (seriesInfo.Networks != null)
|
||||
{
|
||||
series.Studios = seriesInfo.Networks.Select(i => i.Name).ToArray();
|
||||
}
|
||||
series.RunTimeTicks = seriesResult.EpisodeRunTime.Select(i => TimeSpan.FromMinutes(i).Ticks).FirstOrDefault();
|
||||
|
||||
if (seriesInfo.Genres != null)
|
||||
{
|
||||
series.Genres = seriesInfo.Genres.Select(i => i.Name).ToArray();
|
||||
}
|
||||
|
||||
series.HomePageUrl = seriesInfo.Homepage;
|
||||
|
||||
series.RunTimeTicks = seriesInfo.Episode_Run_Time.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));
|
||||
}
|
||||
|
||||
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 it's recent or automatic updates are enabled, don't re-download
|
||||
if ((DateTime.UtcNow - fileInfo.LastWriteTimeUtc).TotalDays <= 2)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
yield return personInfo;
|
||||
}
|
||||
}
|
||||
|
||||
return DownloadSeriesInfo(tmdbId, language, cancellationToken);
|
||||
}
|
||||
|
||||
internal string GetDataFilePath(string tmdbId, string preferredLanguage)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tmdbId))
|
||||
if (seriesResult.Credits?.Crew != null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tmdbId));
|
||||
}
|
||||
|
||||
var path = GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
|
||||
|
||||
var filename = string.Format(CultureInfo.InvariantCulture, "series-{0}.json", preferredLanguage ?? string.Empty);
|
||||
|
||||
return Path.Combine(path, filename);
|
||||
}
|
||||
|
||||
private async Task<RemoteSearchResult> FindByExternalId(string id, string externalSource, CancellationToken cancellationToken)
|
||||
{
|
||||
var url = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
TmdbUtils.BaseTmdbApiUrl + @"3/find/{0}?api_key={1}&external_source={2}",
|
||||
id,
|
||||
TmdbUtils.ApiKey,
|
||||
externalSource);
|
||||
|
||||
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
foreach (var header in TmdbUtils.AcceptHeaders)
|
||||
{
|
||||
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
|
||||
}
|
||||
|
||||
using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
|
||||
var result = await _jsonSerializer.DeserializeFromStreamAsync<ExternalIdLookupResult>(stream).ConfigureAwait(false);
|
||||
|
||||
if (result != null && result.Tv_Results != null)
|
||||
{
|
||||
var tv = result.Tv_Results.FirstOrDefault();
|
||||
|
||||
if (tv != null)
|
||||
var keepTypes = new[]
|
||||
{
|
||||
var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
|
||||
var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
|
||||
PersonType.Director,
|
||||
PersonType.Writer,
|
||||
PersonType.Producer
|
||||
};
|
||||
|
||||
var remoteResult = new RemoteSearchResult
|
||||
foreach (var person in seriesResult.Credits.Crew)
|
||||
{
|
||||
// Normalize this
|
||||
var type = TmdbUtils.MapCrewToPersonType(person);
|
||||
|
||||
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
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return new PersonInfo
|
||||
{
|
||||
Name = person.Name.Trim(),
|
||||
Role = person.Job,
|
||||
Type = type
|
||||
};
|
||||
|
||||
remoteResult.SetProviderId(MetadataProvider.Tmdb, tv.Id.ToString(_usCulture));
|
||||
|
||||
return remoteResult;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
|
|
469
MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
Normal file
469
MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
Normal file
|
@ -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…
Reference in New Issue
Block a user