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();
}
}
}