Implenting a the movie db provider

This commit is contained in:
Zoe Roux 2021-07-25 23:55:55 +02:00
parent a58d240532
commit 17103548c5
9 changed files with 561 additions and 3 deletions

View File

@ -22,7 +22,7 @@
<ItemGroup>
<PackageReference Include="Autofac" Version="6.2.0" />
<PackageReference Include="JetBrains.Annotations" Version="2021.1.0" />
<PackageReference Include="JetBrains.Annotations" Version="2021.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />

View File

@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="3.0.3">
<PackageReference Include="coverlet.msbuild" Version="3.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
@ -24,7 +24,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.0.3">
<PackageReference Include="coverlet.collector" Version="3.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@ -0,0 +1,264 @@
using System.Linq;
using Kyoo.Models;
using TMDbLib.Objects.General;
using TMDbLib.Objects.Movies;
using TMDbLib.Objects.Search;
using TMDbLib.Objects.TvShows;
using Genre = Kyoo.Models.Genre;
using TvCast = TMDbLib.Objects.TvShows.Cast;
using MovieCast = TMDbLib.Objects.Movies.Cast;
namespace Kyoo.TheMovieDb
{
public static class Convertors
{
/// <summary>
/// Convert a <see cref="Movie"/> into a <see cref="Show"/>.
/// </summary>
/// <param name="movie">The movie to convert.</param>
/// <param name="provider">The provider representing TheMovieDb.</param>
/// <returns>The converted movie as a <see cref="Show"/>.</returns>
public static Show ToShow(this Movie movie, Provider provider)
{
return new()
{
Slug = Utility.ToSlug(movie.Title),
Title = movie.Title,
Aliases = movie.AlternativeTitles.Titles.Select(x => x.Title).ToArray(),
Overview = movie.Overview,
TrailerUrl = movie.Videos?.Results.Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube")
.Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault(),
Status = movie.Status == "Released" ? Status.Finished : Status.Planned,
StartAir = movie.ReleaseDate,
EndAir = movie.ReleaseDate,
Poster = movie.PosterPath != null
? $"https://image.tmdb.org/t/p/original{movie.PosterPath}"
: null,
Backdrop = movie.BackdropPath != null
? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}"
: null,
Genres = movie.Genres.Select(x => new Genre(x.Name)).ToArray(),
Studio = !string.IsNullOrEmpty(movie.ProductionCompanies.FirstOrDefault()?.Name)
? new Studio(movie.ProductionCompanies.First().Name)
: null,
IsMovie = true,
People = movie.Credits.Cast
.Select(x => x.ToPeople(provider))
.Concat(movie.Credits.Crew.Select(x => x.ToPeople(provider)))
.ToArray(),
ExternalIDs = new []
{
new MetadataID<Show>
{
Second = provider,
Link = $"https://www.themoviedb.org/movie/{movie.Id}",
DataID = movie.Id.ToString()
}
}
};
}
/// <summary>
/// Convert a <see cref="TvShow"/> to a <see cref="Show"/>.
/// </summary>
/// <param name="tv">The show to convert.</param>
/// <param name="provider">The provider representing TheMovieDb.</param>
/// <returns>A converted <see cref="TvShow"/> as a <see cref="Show"/>.</returns>
public static Show ToShow(this TvShow tv, Provider provider)
{
return new()
{
Slug = Utility.ToSlug(tv.Name),
Title = tv.Name,
Aliases = tv.AlternativeTitles.Results.Select(x => x.Title).ToArray(),
Overview = tv.Overview,
TrailerUrl = tv.Videos?.Results.Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube")
.Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault(),
Status = tv.Status == "Ended" ? Status.Finished : Status.Planned,
StartAir = tv.FirstAirDate,
EndAir = tv.LastAirDate,
Poster = tv.PosterPath != null
? $"https://image.tmdb.org/t/p/original{tv.PosterPath}"
: null,
Backdrop = tv.BackdropPath != null
? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}"
: null,
Genres = tv.Genres.Select(x => new Genre(x.Name)).ToArray(),
Studio = !string.IsNullOrEmpty(tv.ProductionCompanies.FirstOrDefault()?.Name)
? new Studio(tv.ProductionCompanies.First().Name)
: null,
IsMovie = true,
People = tv.Credits.Cast
.Select(x => x.ToPeople(provider))
.Concat(tv.Credits.Crew.Select(x => x.ToPeople(provider)))
.ToArray(),
ExternalIDs = new []
{
new MetadataID<Show>
{
Second = provider,
Link = $"https://www.themoviedb.org/movie/{tv.Id}",
DataID = tv.Id.ToString()
}
}
};
}
/// <summary>
/// Convert a <see cref="SearchMovie"/> into a <see cref="Show"/>.
/// </summary>
/// <param name="movie">The movie to convert.</param>
/// <param name="provider">The provider representing TheMovieDb.</param>
/// <returns>The converted movie as a <see cref="Show"/>.</returns>
public static Show ToShow(this SearchMovie movie, Provider provider)
{
return new()
{
Slug = Utility.ToSlug(movie.Title),
Title = movie.Title,
Overview = movie.Overview,
StartAir = movie.ReleaseDate,
EndAir = movie.ReleaseDate,
Poster = movie.PosterPath != null
? $"https://image.tmdb.org/t/p/original{movie.PosterPath}"
: null,
Backdrop = movie.BackdropPath != null
? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}"
: null,
IsMovie = true,
ExternalIDs = new []
{
new MetadataID<Show>
{
Second = provider,
Link = $"https://www.themoviedb.org/movie/{movie.Id}",
DataID = movie.Id.ToString()
}
}
};
}
/// <summary>
/// Convert a <see cref="SearchTv"/> to a <see cref="Show"/>.
/// </summary>
/// <param name="tv">The show to convert.</param>
/// <param name="provider">The provider representing TheMovieDb.</param>
/// <returns>A converted <see cref="SearchTv"/> as a <see cref="Show"/>.</returns>
public static Show ToShow(this SearchTv tv, Provider provider)
{
return new()
{
Slug = Utility.ToSlug(tv.Name),
Title = tv.Name,
Overview = tv.Overview,
StartAir = tv.FirstAirDate,
Poster = tv.PosterPath != null
? $"https://image.tmdb.org/t/p/original{tv.PosterPath}"
: null,
Backdrop = tv.BackdropPath != null
? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}"
: null,
IsMovie = true,
ExternalIDs = new []
{
new MetadataID<Show>
{
Second = provider,
Link = $"https://www.themoviedb.org/movie/{tv.Id}",
DataID = tv.Id.ToString()
}
}
};
}
/// <summary>
/// Convert a <see cref="MovieCast"/> to a <see cref="PeopleRole"/>.
/// </summary>
/// <param name="cast">An internal TheMovieDB cast.</param>
/// <param name="provider">The provider that represent TheMovieDB inside Kyoo.</param>
/// <returns>A <see cref="PeopleRole"/> representing the movie cast.</returns>
public static PeopleRole ToPeople(this MovieCast cast, Provider provider)
{
return new()
{
People = new People
{
Slug = Utility.ToSlug(cast.Name),
Name = cast.Name,
Poster = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null,
ExternalIDs = new[]
{
new MetadataID<People>
{
Second = provider,
DataID = cast.Id.ToString(),
Link = $"https://www.themoviedb.org/person/{cast.Id}"
}
}
},
Type = "Actor",
Role = cast.Character
};
}
/// <summary>
/// Convert a <see cref="TvCast"/> to a <see cref="PeopleRole"/>.
/// </summary>
/// <param name="cast">An internal TheMovieDB cast.</param>
/// <param name="provider">The provider that represent TheMovieDB inside Kyoo.</param>
/// <returns>A <see cref="PeopleRole"/> representing the movie cast.</returns>
public static PeopleRole ToPeople(this TvCast cast, Provider provider)
{
return new()
{
People = new People
{
Slug = Utility.ToSlug(cast.Name),
Name = cast.Name,
Poster = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null,
ExternalIDs = new[]
{
new MetadataID<People>
{
Second = provider,
DataID = cast.Id.ToString(),
Link = $"https://www.themoviedb.org/person/{cast.Id}"
}
}
},
Type = "Actor",
Role = cast.Character
};
}
/// <summary>
/// Convert a <see cref="Crew"/> to a <see cref="PeopleRole"/>.
/// </summary>
/// <param name="crew">An internal TheMovieDB crew member.</param>
/// <param name="provider">The provider that represent TheMovieDB inside Kyoo.</param>
/// <returns>A <see cref="PeopleRole"/> representing the movie crew.</returns>
public static PeopleRole ToPeople(this Crew crew, Provider provider)
{
return new()
{
People = new People
{
Slug = Utility.ToSlug(crew.Name),
Name = crew.Name,
Poster = crew.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{crew.ProfilePath}" : null,
ExternalIDs = new[]
{
new MetadataID<People>
{
Second = provider,
DataID = crew.Id.ToString(),
Link = $"https://www.themoviedb.org/person/{crew.Id}"
}
}
},
Type = crew.Department,
Role = crew.Job
};
}
}
}

View File

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Company>SDG</Company>
<Authors>Zoe Roux</Authors>
<RepositoryUrl>https://github.com/AnonymusRaccoon/Kyoo</RepositoryUrl>
<LangVersion>default</LangVersion>
<RootNamespace>Kyoo.TheMovieDb</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<OutputPath>../Kyoo/bin/$(Configuration)/$(TargetFramework)/plugins/the-moviedb</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<GenerateDependencyFile>false</GenerateDependencyFile>
<GenerateRuntimeConfigurationFiles>false</GenerateRuntimeConfigurationFiles>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="5.0.0" />
<PackageReference Include="TMDbLib" Version="1.8.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Kyoo.Common/Kyoo.Common.csproj">
<PrivateAssets>all</PrivateAssets>
<Private>false</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using Autofac;
using Kyoo.Controllers;
using Kyoo.Models.Attributes;
using Kyoo.TheMovieDb.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.TheMovieDb
{
/// <summary>
/// A plugin that add a <see cref="IMetadataProvider"/> for TheMovieDB.
/// </summary>
public class PluginTmdb : IPlugin
{
/// <inheritdoc />
public string Slug => "the-moviedb";
/// <inheritdoc />
public string Name => "TheMovieDb Provider";
/// <inheritdoc />
public string Description => "A metadata provider for TheMovieDB.";
/// <inheritdoc />
public ICollection<Type> Provides => new []
{
typeof(IMetadataProvider)
};
/// <inheritdoc />
public ICollection<ConditionalProvide> ConditionalProvides => ArraySegment<ConditionalProvide>.Empty;
/// <inheritdoc />
public ICollection<Type> Requires => ArraySegment<Type>.Empty;
/// <summary>
/// The configuration to use.
/// </summary>
private readonly IConfiguration _configuration;
/// <summary>
/// The configuration manager used to register typed/untyped implementations.
/// </summary>
[Injected] public IConfigurationManager ConfigurationManager { private get; set; }
/// <summary>
/// Create a new tmdb module instance and use the given configuration.
/// </summary>
/// <param name="configuration">The configuration to use</param>
public PluginTmdb(IConfiguration configuration)
{
_configuration = configuration;
}
/// <inheritdoc />
public void Configure(ContainerBuilder builder)
{
builder.RegisterProvider<TheMovieDbProvider>();
}
/// <inheritdoc />
public void Configure(IServiceCollection services, ICollection<Type> availableTypes)
{
services.Configure<TheMovieDbOptions>(_configuration.GetSection(TheMovieDbOptions.Path));
}
/// <inheritdoc />
public void ConfigureAspNet(IApplicationBuilder app)
{
ConfigurationManager.AddTyped<TheMovieDbOptions>(TheMovieDbOptions.Path);
}
}
}

View File

@ -0,0 +1,154 @@
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.Options;
using TMDbLib.Client;
using TMDbLib.Objects.Movies;
using TMDbLib.Objects.Search;
using TMDbLib.Objects.TvShows;
namespace Kyoo.TheMovieDb
{
/// <summary>
/// A metadata provider for TheMovieDb.
/// </summary>
public class TheMovieDbProvider : IMetadataProvider
{
/// <summary>
/// The API key used to authenticate with TheMovieDb API.
/// </summary>
private readonly IOptions<TheMovieDbOptions> _apiKey;
/// <inheritdoc />
public Provider Provider => new()
{
Slug = "the-moviedb",
Name = "TheMovieDB",
LogoExtension = "svg",
Logo = "https://www.themoviedb.org/assets/2/v4/logos/v2/blue_short-8e7b30f73a4020692ccca9c88bafe5dcb6f8a62a4c6bc55cd9ba82bb2cd95f6c.svg"
};
/// <summary>
/// Create a new <see cref="TheMovieDbProvider"/> using the given api key.
/// </summary>
/// <param name="apiKey">The api key</param>
public TheMovieDbProvider(IOptions<TheMovieDbOptions> apiKey)
{
_apiKey = apiKey;
}
/// <inheritdoc />
public Task<T> Get<T>(T item)
where T : class, IResource
{
return item switch
{
Show show => _GetShow(show) as Task<T>,
_ => null
};
}
/// <summary>
/// Get a show using it's id, if the id is not present in the show, fallback to a title search.
/// </summary>
/// <param name="show">The show to search for</param>
/// <returns>A show containing metadata from TheMovieDb</returns>
private async Task<Show> _GetShow(Show show)
{
if (!int.TryParse(show.GetID(Provider.Name), out int id))
return (await _SearchShows(show.Title ?? show.Slug)).FirstOrDefault();
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);
}
/// <inheritdoc />
public async Task<ICollection<T>> Search<T>(string query)
where T : class, IResource
{
if (typeof(T) == typeof(Show))
return (await _SearchShows(query) as ICollection<T>)!;
return ArraySegment<T>.Empty;
}
/// <summary>
/// Search for a show using it's name as a query.
/// </summary>
/// <param name="query">The query to search for</param>
/// <returns>A show containing metadata from TheMovieDb</returns>
private async Task<ICollection<Show>> _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();
}
// public async Task<Season> GetSeason(Show show, int seasonNumber)
// {
// string id = show?.GetID(Provider.Name);
// if (id == null)
// return await Task.FromResult<Season>(null);
// TMDbClient client = new TMDbClient(APIKey);
// TvSeason season = await client.GetTvSeasonAsync(int.Parse(id), seasonNumber);
// if (season == null)
// return null;
// return new Season(show.ID,
// seasonNumber,
// season.Name,
// season.Overview,
// season.AirDate?.Year,
// season.PosterPath != null ? "https://image.tmdb.org/t/p/original" + season.PosterPath : null,
// new[] {new MetadataID(Provider, $"{season.Id}", $"https://www.themoviedb.org/tv/{id}/season/{season.SeasonNumber}")});
// }
//
// public async Task<Episode> GetEpisode(Show show, int seasonNumber, int episodeNumber, int absoluteNumber)
// {
// if (seasonNumber == -1 || episodeNumber == -1)
// return await Task.FromResult<Episode>(null);
//
// string id = show?.GetID(Provider.Name);
// if (id == null)
// return await Task.FromResult<Episode>(null);
// TMDbClient client = new(APIKey);
// TvEpisode episode = await client.GetTvEpisodeAsync(int.Parse(id), seasonNumber, episodeNumber);
// if (episode == null)
// return null;
// return new Episode(seasonNumber, episodeNumber, absoluteNumber,
// episode.Name,
// episode.Overview,
// episode.AirDate,
// 0,
// episode.StillPath != null ? "https://image.tmdb.org/t/p/original" + episode.StillPath : null,
// new []
// {
// new MetadataID(Provider, $"{episode.Id}", $"https://www.themoviedb.org/tv/{id}/season/{episode.SeasonNumber}/episode/{episode.EpisodeNumber}")
// });
// }
}
}

View File

@ -0,0 +1,18 @@
namespace Kyoo.TheMovieDb.Models
{
/// <summary>
/// The option containing the api key for TheMovieDb.
/// </summary>
public class TheMovieDbOptions
{
/// <summary>
/// The path to get this option from the root configuration.
/// </summary>
public const string Path = "the-moviedb";
/// <summary>
/// The api key of TheMovieDb.
/// </summary>
public string ApiKey { get; set; }
}
}

View File

@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.SqLite", "Kyoo.SqLite\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.TheTvdb", "Kyoo.TheTvdb\Kyoo.TheTvdb.csproj", "{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.TheMovieDb", "Kyoo.TheMovieDb\Kyoo.TheMovieDb.csproj", "{BAB270D4-E0EA-4329-BA65-512FDAB01001}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -53,5 +55,9 @@ Global
{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Release|Any CPU.Build.0 = Release|Any CPU
{BAB270D4-E0EA-4329-BA65-512FDAB01001}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BAB270D4-E0EA-4329-BA65-512FDAB01001}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BAB270D4-E0EA-4329-BA65-512FDAB01001}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BAB270D4-E0EA-4329-BA65-512FDAB01001}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -70,5 +70,8 @@
"tvdb": {
"apiKey": "REDACTED"
},
"the-moviedb": {
"apiKey": "REDACTED"
}
}