diff --git a/Kyoo/ClientApp/src/app/show-details/show-details.component.scss b/Kyoo/ClientApp/src/app/show-details/show-details.component.scss index 0e5490a3..470eef4f 100644 --- a/Kyoo/ClientApp/src/app/show-details/show-details.component.scss +++ b/Kyoo/ClientApp/src/app/show-details/show-details.component.scss @@ -15,5 +15,5 @@ .main { - background-color: var(--primary); + } diff --git a/Kyoo/InternalAPI/Crawler/Crawler.cs b/Kyoo/InternalAPI/Crawler/Crawler.cs index 961daa01..378dff8b 100644 --- a/Kyoo/InternalAPI/Crawler/Crawler.cs +++ b/Kyoo/InternalAPI/Crawler/Crawler.cs @@ -1,6 +1,7 @@ using Kyoo.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -122,6 +123,9 @@ namespace Kyoo.InternalAPI Show show = await metadataProvider.GetShowFromName(showName, showPath); showProviderIDs = show.ExternalIDs; showID = libraryManager.RegisterShow(show); + + List actors = await metadataProvider.GetPeople(show.ExternalIDs); + libraryManager.RegisterShowPeople(showID, actors); } else showProviderIDs = libraryManager.GetShowExternalIDs(showID); diff --git a/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs b/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs index 36369b1b..14726f41 100644 --- a/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs +++ b/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs @@ -8,6 +8,7 @@ namespace Kyoo.InternalAPI //Read values string GetShowExternalIDs(long showID); IEnumerable QueryShows(string selection); + List GetPeople(long showID); //Public read IEnumerable GetLibraries(); @@ -24,5 +25,7 @@ namespace Kyoo.InternalAPI long RegisterShow(Show show); long RegisterSeason(Season season); long RegisterEpisode(Episode episode); + + void RegisterShowPeople(long showID, List actors); } } diff --git a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs index 2307b81a..15ebd471 100644 --- a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs +++ b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs @@ -224,11 +224,29 @@ namespace Kyoo.InternalAPI SQLiteDataReader reader = cmd.ExecuteReader(); if (reader.Read()) - return Show.FromReader(reader); + return Show.FromReader(reader).SetPeople(this); else return null; } } + + public List GetPeople(long showID) + { + string query = "SELECT people.id, people.slug, people.name, people.imgPrimary, people.externalIDs, l.role, l.type FROM people JOIN peopleLinks l ON l.peopleID = people.id WHERE l.showID = $showID;"; + + using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection)) + { + cmd.Parameters.AddWithValue("$showID", showID); + SQLiteDataReader reader = cmd.ExecuteReader(); + + List people = new List(); + + while (reader.Read()) + people.Add(People.FromReader(reader)); + + return people; + } + } #endregion #region Check if items exists @@ -298,20 +316,20 @@ namespace Kyoo.InternalAPI string query = "INSERT INTO shows (slug, title, aliases, path, overview, genres, startYear, endYear, imgPrimary, imgThumb, imgLogo, imgBackdrop, externalIDs) VALUES($slug, $title, $aliases, $path, $overview, $genres, $startYear, $endYear, $imgPrimary, $imgThumb, $imgLogo, $imgBackdrop, $externalIDs);"; using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection)) { - cmd.Parameters.AddWithValue("slug", show.Slug); - cmd.Parameters.AddWithValue("title", show.Title); - cmd.Parameters.AddWithValue("aliases", show.GetAliases()); - cmd.Parameters.AddWithValue("path", show.Path); - cmd.Parameters.AddWithValue("overview", show.Overview); - cmd.Parameters.AddWithValue("genres", show.GetGenres()); - cmd.Parameters.AddWithValue("status", show.Status); - cmd.Parameters.AddWithValue("startYear", show.StartYear); - cmd.Parameters.AddWithValue("endYear", show.EndYear); - cmd.Parameters.AddWithValue("imgPrimary", show.ImgPrimary); - cmd.Parameters.AddWithValue("imgThumb", show.ImgThumb); - cmd.Parameters.AddWithValue("imgLogo", show.ImgLogo); - cmd.Parameters.AddWithValue("imgBackdrop", show.ImgBackdrop); - cmd.Parameters.AddWithValue("externalIDs", show.ExternalIDs); + cmd.Parameters.AddWithValue("$slug", show.Slug); + cmd.Parameters.AddWithValue("$title", show.Title); + cmd.Parameters.AddWithValue("$aliases", show.GetAliases()); + cmd.Parameters.AddWithValue("$path", show.Path); + cmd.Parameters.AddWithValue("$overview", show.Overview); + cmd.Parameters.AddWithValue("$genres", show.GetGenres()); + cmd.Parameters.AddWithValue("$status", show.Status); + cmd.Parameters.AddWithValue("$startYear", show.StartYear); + cmd.Parameters.AddWithValue("$endYear", show.EndYear); + cmd.Parameters.AddWithValue("$imgPrimary", show.ImgPrimary); + cmd.Parameters.AddWithValue("$imgThumb", show.ImgThumb); + cmd.Parameters.AddWithValue("$imgLogo", show.ImgLogo); + cmd.Parameters.AddWithValue("$imgBackdrop", show.ImgBackdrop); + cmd.Parameters.AddWithValue("$externalIDs", show.ExternalIDs); cmd.ExecuteNonQuery(); cmd.CommandText = "SELECT LAST_INSERT_ROWID()"; @@ -344,9 +362,9 @@ namespace Kyoo.InternalAPI using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection)) { cmd.Parameters.AddWithValue("$showID", episode.ShowID); - cmd.Parameters.AddWithValue("seasonID", episode.SeasonID); - cmd.Parameters.AddWithValue("episodeNumber", episode.episodeNumber); - cmd.Parameters.AddWithValue("path", episode.Path); + cmd.Parameters.AddWithValue("$seasonID", episode.SeasonID); + cmd.Parameters.AddWithValue("$episodeNumber", episode.episodeNumber); + cmd.Parameters.AddWithValue("$path", episode.Path); cmd.Parameters.AddWithValue("$title", episode.Title); cmd.Parameters.AddWithValue("$overview", episode.Overview); cmd.Parameters.AddWithValue("$releaseDate", episode.ReleaseDate); @@ -359,6 +377,38 @@ namespace Kyoo.InternalAPI return (long)cmd.ExecuteScalar(); } } + + public void RegisterShowPeople(long showID, List people) + { + string query = "INSERT INTO people (slug, name, imgPrimary, externalIDs) VALUES($slug, $name, $imgPrimary, $externalIDs);"; + string linkQuery = "INSERT INTO peopleLinks (peopleID, showID, role, type) VALUES($peopleID, $showID, $role, $type);"; + + for (int i = 0; i < people.Count; i++) + { + long peopleID; + + using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection)) + { + cmd.Parameters.AddWithValue("$slug", people[i].slug); + cmd.Parameters.AddWithValue("$name", people[i].Name); + cmd.Parameters.AddWithValue("$imgPrimary", people[i].imgPrimary); + cmd.Parameters.AddWithValue("$externalIDs", people[i].externalIDs); + cmd.ExecuteNonQuery(); + + cmd.CommandText = "SELECT LAST_INSERT_ROWID()"; + peopleID = (long)cmd.ExecuteScalar(); + } + + using (SQLiteCommand cmd = new SQLiteCommand(linkQuery, sqlConnection)) + { + cmd.Parameters.AddWithValue("$peopleID", peopleID); + cmd.Parameters.AddWithValue("$showID", showID); + cmd.Parameters.AddWithValue("$role", people[i].Role); + cmd.Parameters.AddWithValue("$type", people[i].Type); + cmd.ExecuteNonQuery(); + } + } + } #endregion } } diff --git a/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs b/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs index dcdb6325..02e18794 100644 --- a/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs +++ b/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs @@ -1,4 +1,5 @@ using Kyoo.Models; +using System.Collections.Generic; using System.Threading.Tasks; namespace Kyoo.InternalAPI @@ -9,6 +10,7 @@ namespace Kyoo.InternalAPI Task GetShowByID(string id); Task GetShowFromName(string showName, string showPath); Task GetImages(Show show); + Task> GetPeople(string id); //For the seasons Task GetSeason(string showName, long seasonNumber); diff --git a/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/ProviderTheTvDB.cs b/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/ProviderTheTvDB.cs index 03076474..51a25479 100644 --- a/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/ProviderTheTvDB.cs +++ b/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/ProviderTheTvDB.cs @@ -90,7 +90,7 @@ namespace Kyoo.InternalAPI.MetadataProvider if (token != null) { - WebRequest request = WebRequest.Create("https://api.thetvdb.com/search/series?name=" + HttpUtility.HtmlEncode(showName)); + WebRequest request = WebRequest.Create("https://api.thetvdb.com/search/series?name=" + HttpUtility.UrlEncode(showName)); request.Method = "GET"; request.Timeout = 12000; request.ContentType = "application/json"; @@ -138,7 +138,7 @@ namespace Kyoo.InternalAPI.MetadataProvider } } - return new Show() { Slug = ToSlug(showName), Title = showName }; + return new Show() { Slug = ToSlug(showName), Title = showName, Path = showPath }; } public async Task GetShowByID(string id) @@ -323,5 +323,54 @@ namespace Kyoo.InternalAPI.MetadataProvider return null; } } + + public async Task> GetPeople(string externalIDs) + { + string id = GetID(externalIDs); + + if (id == null) + return null; + + string token = await Authentificate(); + + if (token == null) + return null; + + WebRequest request = WebRequest.Create("https://api.thetvdb.com/series/" + id + "/actors"); + request.Method = "GET"; + request.Timeout = 12000; + request.ContentType = "application/json"; + request.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token); + + try + { + HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync(); + + if (response.StatusCode == HttpStatusCode.OK) + { + Stream stream = response.GetResponseStream(); + using (StreamReader reader = new StreamReader(stream)) + { + string content = await reader.ReadToEndAsync(); + stream.Close(); + response.Close(); + + dynamic data = JsonConvert.DeserializeObject(content); + return (((IEnumerable)data.data).OrderBy(x => x.sortOrder)).ToList().ConvertAll(x => { return new People(-1, ToSlug((string)x.name), (string)x.name, (string)x.role, null, "https://www.thetvdb.com/banners/" + (string)x.image, string.Format("TvDB={0}|", x.id)); }); + } + } + else + { + Debug.WriteLine("&TheTvDB Provider couldn't work for the actors of the show: " + id + ".\nError Code: " + response.StatusCode + " Message: " + response.StatusDescription); + response.Close(); + return null; + } + } + catch (WebException ex) + { + Debug.WriteLine("&TheTvDB Provider couldn't work for the actors of the show: " + id + ".\nError Code: " + ex.Status); + return null; + } + } } } diff --git a/Kyoo/InternalAPI/MetadataProvider/ProviderManager.cs b/Kyoo/InternalAPI/MetadataProvider/ProviderManager.cs index f8fb365b..1a73ab64 100644 --- a/Kyoo/InternalAPI/MetadataProvider/ProviderManager.cs +++ b/Kyoo/InternalAPI/MetadataProvider/ProviderManager.cs @@ -148,5 +148,11 @@ namespace Kyoo.InternalAPI episode.Path = episodePath; return thumbnailsManager.Validate(episode); } + + public async Task> GetPeople(string id) + { + List actors = await providers[0].GetPeople(id); + return thumbnailsManager.Validate(actors); + } } } diff --git a/Kyoo/InternalAPI/ThumbnailsManager/IThumbnailsManager.cs b/Kyoo/InternalAPI/ThumbnailsManager/IThumbnailsManager.cs index 0933994f..8207d2da 100644 --- a/Kyoo/InternalAPI/ThumbnailsManager/IThumbnailsManager.cs +++ b/Kyoo/InternalAPI/ThumbnailsManager/IThumbnailsManager.cs @@ -1,10 +1,12 @@ using Kyoo.Models; +using System.Collections.Generic; namespace Kyoo.InternalAPI.ThumbnailsManager { public interface IThumbnailsManager { Show Validate(Show show); + List Validate(List actors); Episode Validate(Episode episode); } } diff --git a/Kyoo/InternalAPI/ThumbnailsManager/ThumbnailsManager.cs b/Kyoo/InternalAPI/ThumbnailsManager/ThumbnailsManager.cs index 0080e786..7a90ec7e 100644 --- a/Kyoo/InternalAPI/ThumbnailsManager/ThumbnailsManager.cs +++ b/Kyoo/InternalAPI/ThumbnailsManager/ThumbnailsManager.cs @@ -1,5 +1,8 @@ using Kyoo.Models; +using Microsoft.Extensions.Configuration; using System; +using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Net; @@ -7,31 +10,64 @@ namespace Kyoo.InternalAPI.ThumbnailsManager { public class ThumbnailsManager : IThumbnailsManager { + private readonly IConfiguration config; + + public ThumbnailsManager(IConfiguration configuration) + { + config = configuration; + } + public Show Validate(Show show) { - string localThumb = Path.Combine(show.Path, "poster.jpg"); - if (!File.Exists(localThumb)) + if (show.ImgPrimary != null) { - using (WebClient client = new WebClient()) + string localThumb = Path.Combine(show.Path, "poster.jpg"); + if (!File.Exists(localThumb)) { - client.DownloadFileAsync(new Uri(show.ImgPrimary), localThumb); + using (WebClient client = new WebClient()) + { + client.DownloadFileAsync(new Uri(show.ImgPrimary), localThumb); + } } + show.ImgPrimary = localThumb; } - string localBackdrop = Path.Combine(show.Path, "backdrop.jpg"); - if (!File.Exists(localBackdrop)) + if(show.ImgBackdrop != null) { - using (WebClient client = new WebClient()) + string localBackdrop = Path.Combine(show.Path, "backdrop.jpg"); + if (!File.Exists(localBackdrop)) { - client.DownloadFileAsync(new Uri(show.ImgBackdrop), localBackdrop); + using (WebClient client = new WebClient()) + { + client.DownloadFileAsync(new Uri(show.ImgBackdrop), localBackdrop); + } } + show.ImgBackdrop = localBackdrop; } - show.ImgPrimary = localThumb; - show.ImgBackdrop = localBackdrop; return show; } + public List Validate(List people) + { + for (int i = 0; i < people?.Count; i++) + { + string localThumb = config.GetValue("peoplePath") + "/" + people[i].slug + ".jpg"; + if (!File.Exists(localThumb)) + { + using (WebClient client = new WebClient()) + { + Debug.WriteLine("&" + localThumb); + client.DownloadFileAsync(new Uri(people[i].imgPrimary), localThumb); + } + } + + people[i].imgPrimary = localThumb; + } + + return people; + } + public Episode Validate(Episode episode) { string localThumb = Path.ChangeExtension(episode.Path, "jpg"); diff --git a/Kyoo/Models/Episode.cs b/Kyoo/Models/Episode.cs index f7efbc89..63da37f3 100644 --- a/Kyoo/Models/Episode.cs +++ b/Kyoo/Models/Episode.cs @@ -1,22 +1,23 @@ -using System; +using Newtonsoft.Json; +using System; namespace Kyoo.Models { public class Episode { - public readonly long id; - public long ShowID; - public long SeasonID; + [JsonIgnore] public readonly long id; + [JsonIgnore] public long ShowID; + [JsonIgnore] public long SeasonID; public long episodeNumber; - public string Path; + [JsonIgnore] public string Path; public string Title; public string Overview; public DateTime ReleaseDate; public long Runtime; //This runtime variable should be in seconds (used by the video manager so we need precisions) - public string ImgPrimary; + [JsonIgnore] public string ImgPrimary; public string ExternalIDs; public long RuntimeInMinutes diff --git a/Kyoo/Models/Library.cs b/Kyoo/Models/Library.cs index c25db52b..31f072e8 100644 --- a/Kyoo/Models/Library.cs +++ b/Kyoo/Models/Library.cs @@ -1,8 +1,10 @@ -namespace Kyoo.Models +using Newtonsoft.Json; + +namespace Kyoo.Models { public struct Library { - public readonly long id; + [JsonIgnore] public readonly long id; public string Slug; public string Name; diff --git a/Kyoo/Models/People.cs b/Kyoo/Models/People.cs new file mode 100644 index 00000000..f541d8a8 --- /dev/null +++ b/Kyoo/Models/People.cs @@ -0,0 +1,38 @@ +using Newtonsoft.Json; + +namespace Kyoo.Models +{ + public class People + { + [JsonIgnore] public long id; + public string slug; + public string Name; + public string Role; //Dynamic data not stored as it in the database + public string Type; //Dynamic data not stored as it in the database ---- Null for now + [JsonIgnore] public string imgPrimary; + + public string externalIDs; + + public People(long id, string slug, string name, string role, string type, string imgPrimary, string externalIDs) + { + this.id = id; + this.slug = slug; + Name = name; + Role = role; + Type = type; + this.imgPrimary = imgPrimary; + this.externalIDs = externalIDs; + } + + public static People FromReader(System.Data.SQLite.SQLiteDataReader reader) + { + return new People((long)reader["id"], + reader["slug"] as string, + reader["name"] as string, + reader["role"] as string, + reader["type"] as string, + reader["imgPrimary"] as string, + reader["externalIDs"] as string); + } + } +} diff --git a/Kyoo/Models/Season.cs b/Kyoo/Models/Season.cs index 26da948d..0f9bdedf 100644 --- a/Kyoo/Models/Season.cs +++ b/Kyoo/Models/Season.cs @@ -1,16 +1,18 @@ -namespace Kyoo.Models +using Newtonsoft.Json; + +namespace Kyoo.Models { public class Season { - public readonly long id; - public long ShowID; + [JsonIgnore] public readonly long id; + [JsonIgnore] public long ShowID; public long seasonNumber; public string Title; public string Overview; public long? year; - public string ImgPrimary; + [JsonIgnore] public string ImgPrimary; public string ExternalIDs; public Season() { } diff --git a/Kyoo/Models/Show.cs b/Kyoo/Models/Show.cs index ec6d767b..9044f4c2 100644 --- a/Kyoo/Models/Show.cs +++ b/Kyoo/Models/Show.cs @@ -1,16 +1,18 @@ -using System.Collections.Generic; +using Kyoo.InternalAPI; +using Newtonsoft.Json; +using System.Collections.Generic; using System.Linq; namespace Kyoo.Models { public class Show { - public readonly long id = -1; + [JsonIgnore] public readonly long id = -1; public string Slug; public string Title; public IEnumerable Aliases; - public string Path; + [JsonIgnore] public string Path; public string Overview; public IEnumerable Genres; public Status? Status; @@ -18,21 +20,29 @@ namespace Kyoo.Models public long? StartYear; public long? EndYear; - public string ImgPrimary; - public string ImgThumb; - public string ImgLogo; - public string ImgBackdrop; + [JsonIgnore] public string ImgPrimary; + [JsonIgnore] public string ImgThumb; + [JsonIgnore] public string ImgLogo; + [JsonIgnore] public string ImgBackdrop; public string ExternalIDs; + public IEnumerable people; //Used in the rest API excusively. + public string GetAliases() { + if (Aliases == null) + return null; + return string.Join('|', Aliases); } public string GetGenres() { + if (Genres == null) + return null; + return string.Join('|', Genres); } @@ -98,6 +108,18 @@ namespace Kyoo.Models Path = path; return this; } + + public Show SetPeople(People[] people) + { + this.people = people; + return this; + } + + public Show SetPeople(ILibraryManager manager) + { + people = manager.GetPeople(id); + return this; + } } public enum Status { Finished, Airing } diff --git a/Kyoo/config.json b/Kyoo/config.json index d3957b02..cd93d289 100644 --- a/Kyoo/config.json +++ b/Kyoo/config.json @@ -1,5 +1,6 @@ { "databasePath": "C://Projects/database.db", + "peoplePath": "D://Videos/People", "providerPlugins": "C://Projects/Plugins/Providers", "libraryPaths": [ "D:\\Videos"