using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Net; 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.Model.Serialization; namespace MediaBrowser.Controller.Providers.Movies { /// /// Class TmdbPersonProvider /// public class TmdbPersonProvider : BaseMetadataProvider { /// /// The meta file name /// protected const string MetaFileName = "MBPerson.json"; /// /// Gets the json serializer. /// /// The json serializer. protected IJsonSerializer JsonSerializer { get; private set; } /// /// Initializes a new instance of the class. /// /// The json serializer. /// jsonSerializer public TmdbPersonProvider(IJsonSerializer jsonSerializer) : base() { if (jsonSerializer == null) { throw new ArgumentNullException("jsonSerializer"); } JsonSerializer = jsonSerializer; } /// /// Supportses the specified item. /// /// The item. /// true if XXXX, false otherwise public override bool Supports(BaseItem item) { return item is Person; } /// /// Needses the refresh internal. /// /// The item. /// The provider info. /// true if XXXX, false otherwise protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) { //we fetch if either info or image needed and haven't already tried recently return (string.IsNullOrEmpty(item.PrimaryImagePath) || !item.ResolveArgs.ContainsMetaFileByName(MetaFileName)) && DateTime.Today.Subtract(providerInfo.LastRefreshed).TotalDays > Kernel.Instance.Configuration.MetadataRefreshDays; } /// /// Fetches metadata and returns true or false indicating if any work that requires persistence was done /// /// The item. /// if set to true [force]. /// The cancellation token. /// Task{System.Boolean}. protected override async Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var person = (Person)item; var tasks = new List(); var id = person.GetProviderId(MetadataProviders.Tmdb); // We don't already have an Id, need to fetch it if (string.IsNullOrEmpty(id)) { id = await GetTmdbId(item, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); if (!string.IsNullOrEmpty(id)) { //get info only if not already saved if (!item.ResolveArgs.ContainsMetaFileByName(MetaFileName)) { tasks.Add(FetchInfo(person, id, cancellationToken)); } //get image only if not already there if (string.IsNullOrEmpty(item.PrimaryImagePath)) { tasks.Add(FetchImages(person, id, cancellationToken)); } //and wait for them to complete await Task.WhenAll(tasks).ConfigureAwait(false); } else { Logger.Debug("TmdbPersonProvider Unable to obtain id for " + item.Name); } SetLastRefreshed(item, DateTime.UtcNow); return true; } /// /// Gets the priority. /// /// The priority. public override MetadataProviderPriority Priority { get { return MetadataProviderPriority.Second; } } /// /// Gets a value indicating whether [requires internet]. /// /// true if [requires internet]; otherwise, false. public override bool RequiresInternet { get { return true; } } /// /// Gets the TMDB id. /// /// The person. /// The cancellation token. /// Task{System.String}. private async Task GetTmdbId(BaseItem person, CancellationToken cancellationToken) { string url = string.Format(@"http://api.themoviedb.org/3/search/person?api_key={1}&query={0}", WebUtility.UrlEncode(person.Name), MovieDbProvider.ApiKey); PersonSearchResults searchResult = null; try { using (Stream json = await Kernel.Instance.HttpManager.Get(url, Kernel.Instance.ResourcePools.MovieDb, cancellationToken).ConfigureAwait(false)) { searchResult = JsonSerializer.DeserializeFromStream(json); } } catch (HttpException) { } return searchResult != null && searchResult.Total_Results > 0 ? searchResult.Results[0].Id.ToString() : null; } /// /// Fetches the info. /// /// The person. /// The id. /// The cancellation token. /// Task. private async Task FetchInfo(Person person, string id, CancellationToken cancellationToken) { string url = string.Format(@"http://api.themoviedb.org/3/person/{1}?api_key={0}", MovieDbProvider.ApiKey, id); PersonResult searchResult = null; try { using (Stream json = await Kernel.Instance.HttpManager.Get(url, Kernel.Instance.ResourcePools.MovieDb, cancellationToken).ConfigureAwait(false)) { if (json != null) { searchResult = JsonSerializer.DeserializeFromStream(json); } } } catch (HttpException) { } cancellationToken.ThrowIfCancellationRequested(); if (searchResult != null && searchResult.Biography != null) { ProcessInfo(person, searchResult); //save locally var memoryStream = new MemoryStream(); JsonSerializer.SerializeToStream(searchResult, memoryStream); await Kernel.Instance.FileSystemManager.SaveToLibraryFilesystem(person, Path.Combine(person.MetaLocation, MetaFileName), memoryStream, cancellationToken); Logger.Debug("TmdbPersonProvider downloaded and saved information for {0}", person.Name); } } /// /// Processes the info. /// /// The person. /// The search result. protected void ProcessInfo(Person person, PersonResult searchResult) { person.Overview = searchResult.Biography; DateTime date; if (DateTime.TryParseExact(searchResult.Birthday, "yyyy-MM-dd", new CultureInfo("en-US"), DateTimeStyles.None, out date)) { person.PremiereDate = date; } person.SetProviderId(MetadataProviders.Tmdb, searchResult.Id.ToString()); } /// /// Fetches the images. /// /// The person. /// The id. /// The cancellation token. /// Task. private async Task FetchImages(Person person, string id, CancellationToken cancellationToken) { string url = string.Format(@"http://api.themoviedb.org/3/person/{1}/images?api_key={0}", MovieDbProvider.ApiKey, id); PersonImages searchResult = null; try { using (Stream json = await Kernel.Instance.HttpManager.Get(url, Kernel.Instance.ResourcePools.MovieDb, cancellationToken).ConfigureAwait(false)) { if (json != null) { searchResult = JsonSerializer.DeserializeFromStream(json); } } } catch (HttpException) { } if (searchResult != null && searchResult.Profiles.Count > 0) { //get our language var profile = searchResult.Profiles.FirstOrDefault( p => !string.IsNullOrEmpty(p.Iso_639_1) && p.Iso_639_1.Equals(Kernel.Instance.Configuration.PreferredMetadataLanguage, StringComparison.OrdinalIgnoreCase)); if (profile == null) { //didn't find our language - try first null one profile = searchResult.Profiles.FirstOrDefault( p => !string.IsNullOrEmpty(p.Iso_639_1) && p.Iso_639_1.Equals(Kernel.Instance.Configuration.PreferredMetadataLanguage, StringComparison.OrdinalIgnoreCase)); } if (profile == null) { //still nothing - just get first one profile = searchResult.Profiles[0]; } if (profile != null) { var tmdbSettings = await Kernel.Instance.MetadataProviders.OfType().First().TmdbSettings.ConfigureAwait(false); var img = await DownloadAndSaveImage(person, tmdbSettings.images.base_url + Kernel.Instance.Configuration.TmdbFetchedProfileSize + profile.File_Path, "folder" + Path.GetExtension(profile.File_Path), cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(img)) { person.PrimaryImagePath = img; } } } } /// /// Downloads the and save image. /// /// The item. /// The source. /// Name of the target. /// The cancellation token. /// Task{System.String}. private async Task DownloadAndSaveImage(BaseItem item, string source, string targetName, CancellationToken cancellationToken) { if (source == null) return null; //download and save locally (if not already there) var localPath = Path.Combine(item.MetaLocation, targetName); if (!item.ResolveArgs.ContainsMetaFileByName(targetName)) { using (var sourceStream = await Kernel.Instance.HttpManager.FetchToMemoryStream(source, Kernel.Instance.ResourcePools.MovieDb, cancellationToken).ConfigureAwait(false)) { await Kernel.Instance.FileSystemManager.SaveToLibraryFilesystem(item, localPath, sourceStream, cancellationToken).ConfigureAwait(false); Logger.Debug("TmdbPersonProvider downloaded and saved image for {0}", item.Name); } } return localPath; } #region Result Objects /// /// Class PersonSearchResult /// public class PersonSearchResult { /// /// Gets or sets a value indicating whether this is adult. /// /// true if adult; otherwise, false. public bool Adult { get; set; } /// /// Gets or sets the id. /// /// The id. public int Id { get; set; } /// /// Gets or sets the name. /// /// The name. public string Name { get; set; } /// /// Gets or sets the profile_ path. /// /// The profile_ path. public string Profile_Path { get; set; } } /// /// Class PersonSearchResults /// public class PersonSearchResults { /// /// Gets or sets the page. /// /// The page. public int Page { get; set; } /// /// Gets or sets the results. /// /// The results. public List Results { get; set; } /// /// Gets or sets the total_ pages. /// /// The total_ pages. public int Total_Pages { get; set; } /// /// Gets or sets the total_ results. /// /// The total_ results. public int Total_Results { get; set; } } /// /// Class PersonResult /// public class PersonResult { /// /// Gets or sets a value indicating whether this is adult. /// /// true if adult; otherwise, false. public bool Adult { get; set; } /// /// Gets or sets the also_ known_ as. /// /// The also_ known_ as. public List Also_Known_As { get; set; } /// /// Gets or sets the biography. /// /// The biography. public string Biography { get; set; } /// /// Gets or sets the birthday. /// /// The birthday. public string Birthday { get; set; } /// /// Gets or sets the deathday. /// /// The deathday. public string Deathday { get; set; } /// /// Gets or sets the homepage. /// /// The homepage. public string Homepage { get; set; } /// /// Gets or sets the id. /// /// The id. public int Id { get; set; } /// /// Gets or sets the name. /// /// The name. public string Name { get; set; } /// /// Gets or sets the place_ of_ birth. /// /// The place_ of_ birth. public string Place_Of_Birth { get; set; } /// /// Gets or sets the profile_ path. /// /// The profile_ path. public string Profile_Path { get; set; } } /// /// Class PersonProfile /// public class PersonProfile { /// /// Gets or sets the aspect_ ratio. /// /// The aspect_ ratio. public double Aspect_Ratio { get; set; } /// /// Gets or sets the file_ path. /// /// The file_ path. public string File_Path { get; set; } /// /// Gets or sets the height. /// /// The height. public int Height { get; set; } /// /// Gets or sets the iso_639_1. /// /// The iso_639_1. public string Iso_639_1 { get; set; } /// /// Gets or sets the width. /// /// The width. public int Width { get; set; } } /// /// Class PersonImages /// public class PersonImages { /// /// Gets or sets the id. /// /// The id. public int Id { get; set; } /// /// Gets or sets the profiles. /// /// The profiles. public List Profiles { get; set; } } #endregion } }