using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models; using Kyoo.TheMovieDb.Models; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using TMDbLib.Client; using TMDbLib.Objects.Movies; using TMDbLib.Objects.Search; using TMDbLib.Objects.TvShows; namespace Kyoo.TheMovieDb { /// /// A metadata provider for TheMovieDb. /// public class TheMovieDbProvider : IMetadataProvider { /// /// The API key used to authenticate with TheMovieDb API. /// private readonly IOptions _apiKey; /// /// The logger to use in ase of issue. /// private readonly ILogger _logger; /// public Provider Provider => new() { Slug = "the-moviedb", Name = "TheMovieDB", Images = new Dictionary { [Images.Logo] = "https://www.themoviedb.org/assets/2/v4/logos/v2/" + "blue_short-8e7b30f73a4020692ccca9c88bafe5dcb6f8a62a4c6bc55cd9ba82bb2cd95f6c.svg" } }; /// /// Create a new using the given api key. /// /// The api key /// The logger to use in case of issue. public TheMovieDbProvider(IOptions apiKey, ILogger logger) { _apiKey = apiKey; _logger = logger; } /// public Task Get(T item) where T : class, IResource { return item switch { Collection collection => _GetCollection(collection) as Task, Show show => _GetShow(show) as Task, Season season => _GetSeason(season) as Task, Episode episode => _GetEpisode(episode) as Task, People person => _GetPerson(person) as Task, Studio studio => _GetStudio(studio) as Task, _ => null }; } /// /// Get a collection using it's id, if the id is not present in the collection, fallback to a name search. /// /// The collection to search for /// A collection containing metadata from TheMovieDb private async Task _GetCollection(Collection collection) { if (!collection.TryGetID(Provider.Slug, out int id)) { Collection found = (await _SearchCollections(collection.Name ?? collection.Slug)).FirstOrDefault(); if (found?.TryGetID(Provider.Slug, out id) != true) return found; } TMDbClient client = new(_apiKey.Value.ApiKey); return (await client.GetCollectionAsync(id)).ToCollection(Provider); } /// /// Get a show using it's id, if the id is not present in the show, fallback to a title search. /// /// The show to search for /// A show containing metadata from TheMovieDb private async Task _GetShow(Show show) { if (!show.TryGetID(Provider.Slug, out int id)) { Show found = (await _SearchShows(show.Title ?? show.Slug, show.StartAir?.Year)) .FirstOrDefault(x => x.IsMovie == show.IsMovie); if (found?.TryGetID(Provider.Slug, out id) != true) return found; } TMDbClient client = new(_apiKey.Value.ApiKey); if (show.IsMovie) { return (await client .GetMovieAsync(id, MovieMethods.AlternativeTitles | MovieMethods.Videos | MovieMethods.Credits)) ?.ToShow(Provider); } return (await client .GetTvShowAsync(id, TvShowMethods.AlternativeTitles | TvShowMethods.Videos | TvShowMethods.Credits)) ?.ToShow(Provider); } /// /// Get a season using it's show and it's season number. /// /// The season to retrieve metadata for. /// A season containing metadata from TheMovieDb private async Task _GetSeason(Season season) { if (season.Show == null) { _logger.LogWarning("Metadata for a season was requested but it's show is not loaded. " + "This is unsupported"); return null; } if (!season.Show.TryGetID(Provider.Slug, out int id)) return null; TMDbClient client = new(_apiKey.Value.ApiKey); return (await client.GetTvSeasonAsync(id, season.SeasonNumber)) .ToSeason(id, Provider); } /// /// Get an episode using it's show, it's season number and it's episode number. /// Absolute numbering is not supported. /// /// The episode to retrieve metadata for. /// An episode containing metadata from TheMovieDb private async Task _GetEpisode(Episode episode) { if (episode.Show == null) { _logger.LogWarning("Metadata for an episode was requested but it's show is not loaded. " + "This is unsupported"); return null; } if (!episode.Show.TryGetID(Provider.Slug, out int id) || episode.SeasonNumber == null || episode.EpisodeNumber == null) return null; TMDbClient client = new(_apiKey.Value.ApiKey); return (await client.GetTvEpisodeAsync(id, episode.SeasonNumber.Value, episode.EpisodeNumber.Value)) .ToEpisode(id, Provider); } /// /// Get a person using it's id, if the id is not present in the person, fallback to a name search. /// /// The person to search for /// A person containing metadata from TheMovieDb private async Task _GetPerson(People person) { if (!person.TryGetID(Provider.Slug, out int id)) { People found = (await _SearchPeople(person.Name ?? person.Slug)).FirstOrDefault(); if (found?.TryGetID(Provider.Slug, out id) != true) return found; } TMDbClient client = new(_apiKey.Value.ApiKey); return (await client.GetPersonAsync(id)).ToPeople(Provider); } /// /// Get a studio using it's id, if the id is not present in the studio, fallback to a name search. /// /// The studio to search for /// A studio containing metadata from TheMovieDb private async Task _GetStudio(Studio studio) { if (!studio.TryGetID(Provider.Slug, out int id)) { Studio found = (await _SearchStudios(studio.Name ?? studio.Slug)).FirstOrDefault(); if (found?.TryGetID(Provider.Slug, out id) != true) return found; } TMDbClient client = new(_apiKey.Value.ApiKey); return (await client.GetCompanyAsync(id)).ToStudio(Provider); } /// public async Task> Search(string query) where T : class, IResource { if (typeof(T) == typeof(Collection)) return (await _SearchCollections(query) as ICollection)!; if (typeof(T) == typeof(Show)) return (await _SearchShows(query) as ICollection)!; if (typeof(T) == typeof(People)) return (await _SearchPeople(query) as ICollection)!; if (typeof(T) == typeof(Studio)) return (await _SearchStudios(query) as ICollection)!; return ArraySegment.Empty; } /// /// Search for a collection using it's name as a query. /// /// The query to search for /// A list of collections containing metadata from TheMovieDb private async Task> _SearchCollections(string query) { TMDbClient client = new(_apiKey.Value.ApiKey); return (await client.SearchCollectionAsync(query)) .Results .Select(x => x.ToCollection(Provider)) .ToArray(); } /// /// Search for a show using it's name as a query. /// /// The query to search for /// The year in witch the show has aired. /// A list of shows containing metadata from TheMovieDb private async Task> _SearchShows(string query, int? year = null) { TMDbClient client = new(_apiKey.Value.ApiKey); return (await client.SearchMultiAsync(query, year: year ?? 0)) .Results .Select(x => { return x switch { SearchTv tv => tv.ToShow(Provider), SearchMovie movie => movie.ToShow(Provider), _ => null }; }) .Where(x => x != null) .ToArray(); } /// /// Search for people using there name as a query. /// /// The query to search for /// A list of people containing metadata from TheMovieDb private async Task> _SearchPeople(string query) { TMDbClient client = new(_apiKey.Value.ApiKey); return (await client.SearchPersonAsync(query)) .Results .Select(x => x.ToPeople(Provider)) .ToArray(); } /// /// Search for studios using there name as a query. /// /// The query to search for /// A list of studios containing metadata from TheMovieDb private async Task> _SearchStudios(string query) { TMDbClient client = new(_apiKey.Value.ApiKey); return (await client.SearchCompanyAsync(query)) .Results .Select(x => x.ToStudio(Provider)) .ToArray(); } } }