using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using TMDbLib.Objects.Find;
using TMDbLib.Objects.Search;
namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
///
/// Movie provider powered by TMDb.
///
public class TmdbMovieProvider : IRemoteMetadataProvider, IHasOrder
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager;
private readonly TmdbClientManager _tmdbClientManager;
///
/// Initializes a new instance of the class.
///
/// The .
/// The .
/// The .
public TmdbMovieProvider(
ILibraryManager libraryManager,
TmdbClientManager tmdbClientManager,
IHttpClientFactory httpClientFactory)
{
_libraryManager = libraryManager;
_tmdbClientManager = tmdbClientManager;
_httpClientFactory = httpClientFactory;
}
///
public int Order => 1;
///
public string Name => TmdbUtils.ProviderName;
///
public async Task> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken)
{
if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var id))
{
var movie = await _tmdbClientManager
.GetMovieAsync(
int.Parse(id, CultureInfo.InvariantCulture),
searchInfo.MetadataLanguage,
TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
cancellationToken)
.ConfigureAwait(false);
if (movie is not null)
{
var remoteResult = new RemoteSearchResult
{
Name = movie.Title ?? movie.OriginalTitle,
SearchProviderName = Name,
ImageUrl = _tmdbClientManager.GetPosterUrl(movie.PosterPath),
Overview = movie.Overview
};
if (movie.ReleaseDate is not 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 };
}
}
IReadOnlyList? movieResults = null;
if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out id))
{
var result = await _tmdbClientManager.FindByExternalIdAsync(
id,
FindExternalSource.Imdb,
TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
cancellationToken).ConfigureAwait(false);
movieResults = result?.MovieResults;
}
if (movieResults is null && searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out id))
{
var result = await _tmdbClientManager.FindByExternalIdAsync(
id,
FindExternalSource.TvDb,
TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
cancellationToken).ConfigureAwait(false);
movieResults = result?.MovieResults;
}
if (movieResults is null)
{
movieResults = await _tmdbClientManager
.SearchMovieAsync(searchInfo.Name, searchInfo.Year ?? 0, searchInfo.MetadataLanguage, cancellationToken)
.ConfigureAwait(false);
}
var len = movieResults.Count;
var remoteSearchResults = new RemoteSearchResult[len];
for (var i = 0; i < len; i++)
{
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[i] = remoteSearchResult;
}
return remoteSearchResults;
}
///
public async Task> 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 cleanedName = TmdbUtils.CleanName(parsedName.Name);
var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
if (searchResults.Count > 0)
{
tmdbId = searchResults[0].Id.ToString(CultureInfo.InvariantCulture);
}
}
if (string.IsNullOrEmpty(tmdbId) && !string.IsNullOrEmpty(imdbId))
{
var movieResultFromImdbId = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
if (movieResultFromImdbId?.MovieResults.Count > 0)
{
tmdbId = movieResultFromImdbId.MovieResults[0].Id.ToString(CultureInfo.InvariantCulture);
}
}
if (string.IsNullOrEmpty(tmdbId))
{
return new MetadataResult();
}
var movieResult = await _tmdbClientManager
.GetMovieAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
.ConfigureAwait(false);
if (movieResult is null)
{
return new MetadataResult();
}
var movie = new Movie
{
Name = movieResult.Title ?? movieResult.OriginalTitle,
OriginalTitle = 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
{
HasMetadata = true,
ResultLanguage = info.MetadataLanguage,
Item = movie
};
movie.SetProviderId(MetadataProvider.Tmdb, tmdbId);
movie.SetProviderId(MetadataProvider.Imdb, movieResult.ImdbId);
if (movieResult.BelongsToCollection is not 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 is not 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 is not null)
{
movie.OfficialRating = TmdbUtils.BuildParentalRating(ourRelease.Iso_3166_1, ourRelease.Certification);
}
else if (usRelease is not null)
{
movie.OfficialRating = usRelease.Certification;
}
}
movie.PremiereDate = movieResult.ReleaseDate;
movie.ProductionYear = movieResult.ReleaseDate?.Year;
if (movieResult.ProductionCompanies is not 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.Keywords?.Keywords is not null)
{
for (var i = 0; i < movieResult.Keywords.Keywords.Count; i++)
{
movie.AddTag(movieResult.Keywords.Keywords[i].Name);
}
}
if (movieResult.Credits?.Cast is not null)
{
foreach (var actor in movieResult.Credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.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 is not null)
{
var keepTypes = new[]
{
PersonType.Director,
PersonType.Writer,
PersonType.Producer
};
foreach (var person in movieResult.Credits.Crew)
{
// Normalize this
var type = TmdbUtils.MapCrewToPersonType(person);
if (!keepTypes.Contains(type, StringComparison.OrdinalIgnoreCase) &&
!keepTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
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);
}
}
if (movieResult.Videos?.Results is not null)
{
var trailers = new List();
for (var i = 0; i < movieResult.Videos.Results.Count; i++)
{
var video = movieResult.Videos.Results[i];
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
});
}
movie.RemoteTrailers = trailers;
}
return metadataResult;
}
///
public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}