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", LogoExtension = "svg", Images = new Dictionary { [Thumbnails.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, _ => null }; } /// /// Get a collection using it's id, if the id is not present in the collection, fallback to a name search. /// /// The show to collection 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)).FirstOrDefault(); 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 || !season.Show.TryGetID(Provider.Slug, out int id)) { _logger.LogWarning("Metadata for a season was requested but it's show is not loaded. " + "This is unsupported"); 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 || !episode.Show.TryGetID(Provider.Slug, out int id)) { _logger.LogWarning("Metadata for a season was requested but it's show is not loaded. " + "This is unsupported"); return null; } if (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); } /// 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 /// A list of shows containing metadata from TheMovieDb private async Task> _SearchShows(string query) { TMDbClient client = new(_apiKey.Value.ApiKey); return (await client.SearchMultiAsync(query)) .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(); } } }