diff --git a/Kyoo.Common/Controllers/IThumbnailsManager.cs b/Kyoo.Common/Controllers/IThumbnailsManager.cs index 465d4c62..023e566c 100644 --- a/Kyoo.Common/Controllers/IThumbnailsManager.cs +++ b/Kyoo.Common/Controllers/IThumbnailsManager.cs @@ -23,37 +23,18 @@ namespace Kyoo.Controllers /// The type of the item /// true if an image has been downloaded, false otherwise. Task DownloadImages([NotNull] T item, bool alwaysDownload = false) - where T : IResource; - + where T : IThumbnails; + /// /// Retrieve the local path of the poster of the given item. /// /// The item to retrieve the poster from. + /// The ID of the image. See for values. /// The type of the item /// If the type does not have a poster /// The path of the poster for the given resource (it might or might not exists). - Task GetPoster([NotNull] T item) - where T : IResource; - - /// - /// Retrieve the local path of the logo of the given item. - /// - /// The item to retrieve the logo from. - /// The type of the item - /// If the type does not have a logo - /// The path of the logo for the given resource (it might or might not exists). - Task GetLogo([NotNull] T item) - where T : IResource; - - /// - /// Retrieve the local path of the thumbnail of the given item. - /// - /// The item to retrieve the thumbnail from. - /// The type of the item - /// If the type does not have a thumbnail - /// The path of the thumbnail for the given resource (it might or might not exists). - Task GetThumbnail([NotNull] T item) - where T : IResource; + Task GetImagePath([NotNull] T item, int imageID) + where T : IThumbnails; } } diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index f7c5db3f..a8b796f2 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -8,7 +8,7 @@ namespace Kyoo.Models /// A class representing collections of . /// A collection can also be stored in a . /// - public class Collection : IResource, IMetadata + public class Collection : IResource, IMetadata, IThumbnails { /// public int ID { get; set; } @@ -20,14 +20,18 @@ namespace Kyoo.Models /// The name of this collection. /// public string Name { get; set; } + + /// + public Dictionary Images { get; set; } /// /// The path of this poster. /// By default, the http path for this poster is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/collection/{Slug}/poster")] public string Poster { get; set; } - + [SerializeAs("{HOST}/api/collection/{Slug}/poster")] + public string Poster => Images[Thumbnails.Poster]; + /// /// The description of this collection. /// @@ -44,7 +48,7 @@ namespace Kyoo.Models [LoadableRelation] public ICollection Libraries { get; set; } /// - public ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } #if ENABLE_INTERNAL_LINKS diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 687e88fb..ad2b79bb 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -10,7 +10,7 @@ namespace Kyoo.Models /// /// A class to represent a single show's episode. /// - public class Episode : IResource, IMetadata + public class Episode : IResource, IMetadata, IThumbnails { /// public int ID { get; set; } @@ -98,13 +98,17 @@ namespace Kyoo.Models /// The path of the video file for this episode. Any format supported by a is allowed. /// [SerializeIgnore] public string Path { get; set; } + + /// + public Dictionary Images { get; set; } /// /// The path of this episode's thumbnail. /// By default, the http path for the thumbnail is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/episodes/{Slug}/thumb")] public string Thumb { get; set; } + [SerializeAs("{HOST}/api/episodes/{Slug}/thumb")] + public string Thumb => Images[Thumbnails.Thumbnail]; /// /// The title of this episode. @@ -122,7 +126,7 @@ namespace Kyoo.Models public DateTime? ReleaseDate { get; set; } /// - public ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } /// /// The list of tracks this episode has. This lists video, audio and subtitles available. diff --git a/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs b/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs index 469d3dbb..1c937715 100644 --- a/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs +++ b/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs @@ -11,6 +11,7 @@ namespace Kyoo.Models /// /// The link to metadata providers that this show has. See for more information. /// - [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] + public ICollection ExternalIDs { get; set; } } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs b/Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs new file mode 100644 index 00000000..074751c4 --- /dev/null +++ b/Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using Kyoo.Controllers; + +namespace Kyoo.Models +{ + /// + /// An interface representing items that contains images (like posters, thumbnails, logo, banners...) + /// + public interface IThumbnails + { + /// + /// The list of images mapped to a certain index. + /// The string value should be a path supported by the . + /// + /// + /// An arbitrary index should not be used, instead use indexes from + /// + public Dictionary Images { get; set; } + + // TODO remove Posters properties add them via the json serializer for every IThumbnails + } + + /// + /// A class containing constant values for images. To be used as index of a . + /// + public static class Thumbnails + { + /// + /// A poster is a 9/16 format image with the cover of the resource. + /// + public const int Poster = 0; + + /// + /// A thumbnail is a 16/9 format image, it could ether be used as a background or as a preview but it usually + /// is not an official image. + /// + public const int Thumbnail = 1; + + /// + /// A logo is a small image representing the resource. + /// + public const int Logo = 2; + + /// + /// A video of a few minutes that tease the content. + /// + public const int Trailer = 3; + } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/People.cs b/Kyoo.Common/Models/Resources/People.cs index ff2b8ed1..5b7f6864 100644 --- a/Kyoo.Common/Models/Resources/People.cs +++ b/Kyoo.Common/Models/Resources/People.cs @@ -6,7 +6,7 @@ namespace Kyoo.Models /// /// An actor, voice actor, writer, animator, somebody who worked on a . /// - public class People : IResource, IMetadata + public class People : IResource, IMetadata, IThumbnails { /// public int ID { get; set; } @@ -19,15 +19,19 @@ namespace Kyoo.Models /// public string Name { get; set; } + /// + public Dictionary Images { get; set; } + /// /// The path of this poster. /// By default, the http path for this poster is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/people/{Slug}/poster")] public string Poster { get; set; } + [SerializeAs("{HOST}/api/people/{Slug}/poster")] + public string Poster => Images[Thumbnails.Poster]; /// - public ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } /// /// The list of roles this person has played in. See for more information. diff --git a/Kyoo.Common/Models/Resources/Provider.cs b/Kyoo.Common/Models/Resources/Provider.cs index e13a9be4..e931479a 100644 --- a/Kyoo.Common/Models/Resources/Provider.cs +++ b/Kyoo.Common/Models/Resources/Provider.cs @@ -9,7 +9,7 @@ namespace Kyoo.Models /// This class contains metadata about . /// You can have providers even if you don't have the corresponding . /// - public class Provider : IResource + public class Provider : IResource, IThumbnails { /// public int ID { get; set; } @@ -22,12 +22,16 @@ namespace Kyoo.Models /// public string Name { get; set; } + /// + public Dictionary Images { get; set; } + /// /// The path of this provider's logo. /// By default, the http path for this logo is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/providers/{Slug}/logo")] public string Logo { get; set; } + [SerializeAs("{HOST}/api/providers/{Slug}/logo")] + public string Logo => Images[Thumbnails.Logo]; /// /// The extension of the logo. This is used for http responses. @@ -61,7 +65,7 @@ namespace Kyoo.Models { Slug = Utility.ToSlug(name); Name = name; - Logo = logo; + Images[Thumbnails.Logo] = logo; } } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index b390f712..fddb7eb4 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -10,7 +10,7 @@ namespace Kyoo.Models /// /// A season of a . /// - public class Season : IResource, IMetadata + public class Season : IResource, IMetadata, IThumbnails { /// public int ID { get; set; } @@ -74,15 +74,19 @@ namespace Kyoo.Models /// public DateTime? EndDate { get; set; } + /// + public Dictionary Images { get; set; } + /// /// The path of this poster. /// By default, the http path for this poster is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] public string Poster { get; set; } + [SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] + public string Poster => Images[Thumbnails.Poster]; /// - public ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } /// /// The list of episodes that this season contains. diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index 85c5b959..a32a0169 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -11,7 +11,7 @@ namespace Kyoo.Models /// /// A series or a movie. /// - public class Show : IResource, IMetadata, IOnMerge + public class Show : IResource, IMetadata, IOnMerge, IThumbnails { /// public int ID { get; set; } @@ -44,12 +44,12 @@ namespace Kyoo.Models /// Is this show airing, not aired yet or finished? /// public Status Status { get; set; } - + /// /// An URL to a trailer. This could be any path supported by the . /// /// TODO for now, this is set to a youtube url. It should be cached and converted to a local file. - public string TrailerUrl { get; set; } + public string TrailerUrl => Images[Thumbnails.Trailer]; /// /// The date this show started airing. It can be null if this is unknown. @@ -63,26 +63,32 @@ namespace Kyoo.Models /// public DateTime? EndAir { get; set; } + /// + public Dictionary Images { get; set; } + /// /// The path of this show's poster. /// By default, the http path for this poster is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/shows/{Slug}/poster")] public string Poster { get; set; } - + [SerializeAs("{HOST}/api/shows/{Slug}/poster")] + public string Poster => Images[Thumbnails.Poster]; + /// /// The path of this show's logo. /// By default, the http path for this logo is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/shows/{Slug}/logo")] public string Logo { get; set; } - + [SerializeAs("{HOST}/api/shows/{Slug}/logo")] + public string Logo => Images[Thumbnails.Logo]; + /// /// The path of this show's backdrop. /// By default, the http path for this backdrop is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] public string Backdrop { get; set; } + [SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] + public string Backdrop => Images[Thumbnails.Thumbnail]; /// /// True if this show represent a movie, false otherwise. @@ -90,7 +96,7 @@ namespace Kyoo.Models public bool IsMovie { get; set; } /// - public ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } /// /// The ID of the Studio that made this show. diff --git a/Kyoo.Common/Models/Resources/Studio.cs b/Kyoo.Common/Models/Resources/Studio.cs index 80195317..03ec04df 100644 --- a/Kyoo.Common/Models/Resources/Studio.cs +++ b/Kyoo.Common/Models/Resources/Studio.cs @@ -25,7 +25,7 @@ namespace Kyoo.Models [LoadableRelation] public ICollection Shows { get; set; } /// - public ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } /// /// Create a new, empty, . diff --git a/Kyoo.Tests/Database/TestSample.cs b/Kyoo.Tests/Database/TestSample.cs index e0ff955f..d81612ad 100644 --- a/Kyoo.Tests/Database/TestSample.cs +++ b/Kyoo.Tests/Database/TestSample.cs @@ -41,7 +41,10 @@ namespace Kyoo.Tests Slug = "collection", Name = "Collection", Overview = "A nice collection for tests", - Poster = "Poster" + Images = + { + [Thumbnails.Poster] = "Poster" + } } }, { @@ -61,12 +64,14 @@ namespace Kyoo.Tests "In time, however, these childhood friends drifted apart, and when they became high " + "school students, they had long ceased to think of each other as friends.", Status = Status.Finished, - TrailerUrl = null, StartAir = new DateTime(2011, 1, 1), EndAir = new DateTime(2011, 1, 1), - Poster = "poster", - Logo = "logo", - Backdrop = "backdrop", + Images = + { + [Thumbnails.Poster] = "Poster", + [Thumbnails.Logo] = "Logo", + [Thumbnails.Thumbnail] = "Thumbnail" + }, IsMovie = false, Studio = null } @@ -83,7 +88,12 @@ namespace Kyoo.Tests Overview = "The first season", StartDate = new DateTime(2020, 06, 05), EndDate = new DateTime(2020, 07, 05), - Poster = "poster" + Images = + { + [Thumbnails.Poster] = "Poster", + [Thumbnails.Logo] = "Logo", + [Thumbnails.Thumbnail] = "Thumbnail" + }, } }, { @@ -98,7 +108,12 @@ namespace Kyoo.Tests EpisodeNumber = 1, AbsoluteNumber = 1, Path = "/home/kyoo/anohana-s1e1", - Thumb = "thumbnail", + Images = + { + [Thumbnails.Poster] = "Poster", + [Thumbnails.Logo] = "Logo", + [Thumbnails.Thumbnail] = "Thumbnail" + }, Title = "Episode 1", Overview = "Summary of the first episode", ReleaseDate = new DateTime(2020, 06, 05) @@ -129,7 +144,12 @@ namespace Kyoo.Tests ID = 1, Slug = "the-actor", Name = "The Actor", - Poster = "NicePoster" + Images = + { + [Thumbnails.Poster] = "Poster", + [Thumbnails.Logo] = "Logo", + [Thumbnails.Thumbnail] = "Thumbnail" + }, } }, { @@ -138,7 +158,7 @@ namespace Kyoo.Tests { ID = 1, Slug = "hyper-studio", - Name = "Hyper studio" + Name = "Hyper studio", } }, { @@ -157,7 +177,12 @@ namespace Kyoo.Tests ID = 1, Slug = "tvdb", Name = "The TVDB", - Logo = "path/tvdb.svg", + Images = + { + [Thumbnails.Poster] = "Poster", + [Thumbnails.Logo] = "path/tvdb.svg", + [Thumbnails.Thumbnail] = "Thumbnail" + }, LogoExtension = "svg" } }, @@ -257,7 +282,12 @@ namespace Kyoo.Tests EpisodeNumber = null, AbsoluteNumber = 3, Path = "/home/kyoo/anohana-3", - Thumb = "thumbnail", + Images = + { + [Thumbnails.Poster] = "Poster", + [Thumbnails.Logo] = "Logo", + [Thumbnails.Thumbnail] = "Thumbnail" + }, Title = "Episode 3", Overview = "Summary of the third absolute episode", ReleaseDate = new DateTime(2020, 06, 05) @@ -272,7 +302,12 @@ namespace Kyoo.Tests ShowSlug = "anohana", ShowID = 1, Path = "/home/kyoo/john-wick", - Thumb = "thumb", + Images = + { + [Thumbnails.Poster] = "Poster", + [Thumbnails.Logo] = "Logo", + [Thumbnails.Thumbnail] = "Thumbnail" + }, Title = "John wick", Overview = "A movie episode test", ReleaseDate = new DateTime(1595, 05, 12) diff --git a/Kyoo.TheMovieDb/Convertors.cs b/Kyoo.TheMovieDb/Convertors.cs index 0cf5fe98..b6fd4ad1 100644 --- a/Kyoo.TheMovieDb/Convertors.cs +++ b/Kyoo.TheMovieDb/Convertors.cs @@ -26,17 +26,21 @@ namespace Kyoo.TheMovieDb 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, + Images = + { + [Thumbnails.Poster] = movie.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" + : null, + [Thumbnails.Thumbnail] = movie.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" + : null, + [Thumbnails.Trailer] = 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(), + }, Genres = movie.Genres.Select(x => new Genre(x.Name)).ToArray(), Studio = !string.IsNullOrEmpty(movie.ProductionCompanies.FirstOrDefault()?.Name) ? new Studio(movie.ProductionCompanies.First().Name) @@ -72,17 +76,21 @@ namespace Kyoo.TheMovieDb 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, + Images = + { + [Thumbnails.Poster] = tv.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" + : null, + [Thumbnails.Thumbnail] = tv.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" + : null, + [Thumbnails.Trailer] = 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() + }, Genres = tv.Genres.Select(x => new Genre(x.Name)).ToArray(), Studio = !string.IsNullOrEmpty(tv.ProductionCompanies.FirstOrDefault()?.Name) ? new Studio(tv.ProductionCompanies.First().Name) @@ -117,7 +125,15 @@ namespace Kyoo.TheMovieDb { Slug = Utility.ToSlug(collection.Name), Name = collection.Name, - Poster = $"https://image.tmdb.org/t/p/original{collection.PosterPath}" + Images = + { + [Thumbnails.Poster] = collection.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{collection.PosterPath}" + : null, + [Thumbnails.Thumbnail] = collection.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{collection.BackdropPath}" + : null + } }; } @@ -136,12 +152,15 @@ namespace Kyoo.TheMovieDb 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, + Images = + { + [Thumbnails.Poster] = movie.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" + : null, + [Thumbnails.Thumbnail] = movie.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" + : null, + }, IsMovie = true, ExternalIDs = new [] { @@ -169,12 +188,15 @@ namespace Kyoo.TheMovieDb 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, + Images = + { + [Thumbnails.Poster] = tv.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" + : null, + [Thumbnails.Thumbnail] = tv.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" + : null, + }, IsMovie = true, ExternalIDs = new [] { @@ -202,7 +224,12 @@ namespace Kyoo.TheMovieDb { Slug = Utility.ToSlug(cast.Name), Name = cast.Name, - Poster = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null, + Images = + { + [Thumbnails.Poster] = cast.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" + : null + }, ExternalIDs = new[] { new MetadataID @@ -232,7 +259,12 @@ namespace Kyoo.TheMovieDb { Slug = Utility.ToSlug(cast.Name), Name = cast.Name, - Poster = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null, + Images = + { + [Thumbnails.Poster] = cast.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" + : null + }, ExternalIDs = new[] { new MetadataID @@ -262,7 +294,12 @@ namespace Kyoo.TheMovieDb { Slug = Utility.ToSlug(crew.Name), Name = crew.Name, - Poster = crew.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{crew.ProfilePath}" : null, + Images = + { + [Thumbnails.Poster] = crew.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{crew.ProfilePath}" + : null + }, ExternalIDs = new[] { new MetadataID diff --git a/Kyoo.TheMovieDb/ProviderTmdb.cs b/Kyoo.TheMovieDb/ProviderTmdb.cs index b613e5f3..bde0a748 100644 --- a/Kyoo.TheMovieDb/ProviderTmdb.cs +++ b/Kyoo.TheMovieDb/ProviderTmdb.cs @@ -29,7 +29,11 @@ namespace Kyoo.TheMovieDb Slug = "the-moviedb", Name = "TheMovieDB", LogoExtension = "svg", - Logo = "https://www.themoviedb.org/assets/2/v4/logos/v2/blue_short-8e7b30f73a4020692ccca9c88bafe5dcb6f8a62a4c6bc55cd9ba82bb2cd95f6c.svg" + Images = + { + [Thumbnails.Logo] = "https://www.themoviedb.org/assets/2/v4/logos/v2/" + + "blue_short-8e7b30f73a4020692ccca9c88bafe5dcb6f8a62a4c6bc55cd9ba82bb2cd95f6c.svg" + } }; /// diff --git a/Kyoo.TheTvdb/Convertors.cs b/Kyoo.TheTvdb/Convertors.cs index 6aad939b..b992481d 100644 --- a/Kyoo.TheTvdb/Convertors.cs +++ b/Kyoo.TheTvdb/Convertors.cs @@ -55,7 +55,12 @@ namespace Kyoo.TheTvdb Overview = result.Overview, Status = _GetStatus(result.Status), StartAir = _ParseDate(result.FirstAired), - Poster = result.Poster != null ? $"https://www.thetvdb.com{result.Poster}" : null, + Images = + { + [Thumbnails.Poster] = result.Poster != null + ? $"https://www.thetvdb.com{result.Poster}" + : null, + }, ExternalIDs = new[] { new MetadataID @@ -84,8 +89,15 @@ namespace Kyoo.TheTvdb Overview = series.Overview, Status = _GetStatus(series.Status), StartAir = _ParseDate(series.FirstAired), - Poster = series.Poster != null ? $"https://www.thetvdb.com/banners/{series.Poster}" : null, - Backdrop = series.FanArt != null ? $"https://www.thetvdb.com/banners/{series.FanArt}" : null, + Images= + { + [Thumbnails.Poster] = series.Poster != null + ? $"https://www.thetvdb.com/banners/{series.Poster}" + : null, + [Thumbnails.Thumbnail] = series.FanArt != null + ? $"https://www.thetvdb.com/banners/{series.FanArt}" + : null + }, Genres = series.Genre.Select(y => new Genre(y)).ToList(), ExternalIDs = new[] { @@ -113,7 +125,12 @@ namespace Kyoo.TheTvdb { Slug = Utility.ToSlug(actor.Name), Name = actor.Name, - Poster = actor.Image != null ? $"https://www.thetvdb.com/banners/{actor.Image}" : null, + Images = + { + [Thumbnails.Poster] = actor.Image != null + ? $"https://www.thetvdb.com/banners/{actor.Image}" + : null + }, ExternalIDs = new [] { new MetadataID @@ -144,7 +161,12 @@ namespace Kyoo.TheTvdb AbsoluteNumber = episode.AbsoluteNumber, Title = episode.EpisodeName, Overview = episode.Overview, - Thumb = episode.Filename != null ? $"https://www.thetvdb.com/banners/{episode.Filename}" : null, + Images = + { + [Thumbnails.Thumbnail] = episode.Filename != null + ? $"https://www.thetvdb.com/banners/{episode.Filename}" + : null + }, ExternalIDs = new[] { new MetadataID diff --git a/Kyoo.TheTvdb/ProviderTvdb.cs b/Kyoo.TheTvdb/ProviderTvdb.cs index 47183fb7..b2ba5683 100644 --- a/Kyoo.TheTvdb/ProviderTvdb.cs +++ b/Kyoo.TheTvdb/ProviderTvdb.cs @@ -33,7 +33,10 @@ namespace Kyoo.TheTvdb Slug = "the-tvdb", Name = "TheTVDB", LogoExtension = "png", - Logo = "https://www.thetvdb.com/images/logo.png" + Images = + { + [Thumbnails.Logo] = "https://www.thetvdb.com/images/logo.png" + } }; diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index 389891b3..61d14613 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -2,7 +2,6 @@ using System; using System.IO; using System.Threading.Tasks; -using JetBrains.Annotations; using Kyoo.Models.Options; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -55,23 +54,6 @@ namespace Kyoo.Controllers }); } - /// - public Task DownloadImages(T item, bool alwaysDownload = false) - where T : IResource - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - return item switch - { - Show show => _Validate(show, alwaysDownload), - Season season => _Validate(season, alwaysDownload), - Episode episode => _Validate(episode, alwaysDownload), - People people => _Validate(people, alwaysDownload), - Provider provider => _Validate(provider, alwaysDownload), - _ => Task.FromResult(false) - }; - } - /// /// An helper function to download an image using a . /// @@ -97,196 +79,72 @@ namespace Kyoo.Controllers return false; } } - - /// - /// Download images of a specified show. - /// - /// - /// The item to cache images. - /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// - /// true if an image has been downloaded, false otherwise. - private async Task _Validate([NotNull] Show show, bool alwaysDownload) + + /// + public async Task DownloadImages(T item, bool alwaysDownload = false) + where T : IThumbnails { - bool ret = false; - - if (show.Poster != null) - { - string posterPath = await GetPoster(show); - if (alwaysDownload || !await _files.Exists(posterPath)) - ret |= await _DownloadImage(show.Poster, posterPath, $"The poster of {show.Title}"); - } - if (show.Logo != null) - { - string logoPath = await GetLogo(show); - if (alwaysDownload || !await _files.Exists(logoPath)) - ret |= await _DownloadImage(show.Logo, logoPath, $"The logo of {show.Title}"); - } - if (show.Backdrop != null) - { - string backdropPath = await GetThumbnail(show); - if (alwaysDownload || !await _files.Exists(backdropPath)) - ret |= await _DownloadImage(show.Backdrop, backdropPath, $"The backdrop of {show.Title}"); - } + if (item == null) + throw new ArgumentNullException(nameof(item)); + string name = item is IResource res ? res.Slug : "???"; + bool ret = false; + + foreach ((int id, string image) in item.Images) + { + string localPath = await GetImagePath(item, id); + if (alwaysDownload || !await _files.Exists(localPath)) + ret |= await _DownloadImage(image, localPath, $"The image n°{id} of {name}"); + } + return ret; } - - /// - /// Download images of a specified person. - /// - /// - /// The item to cache images. - /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// - /// true if an image has been downloaded, false otherwise. - private async Task _Validate([NotNull] People people, bool alwaysDownload) - { - if (people == null) - throw new ArgumentNullException(nameof(people)); - if (people.Poster == null) - return false; - string localPath = await GetPoster(people); - if (alwaysDownload || !await _files.Exists(localPath)) - return await _DownloadImage(people.Poster, localPath, $"The profile picture of {people.Name}"); - return false; - } - - /// - /// Download images of a specified season. - /// - /// - /// The item to cache images. - /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// - /// true if an image has been downloaded, false otherwise. - private async Task _Validate([NotNull] Season season, bool alwaysDownload) - { - if (season.Poster == null) - return false; - - string localPath = await GetPoster(season); - if (alwaysDownload || !await _files.Exists(localPath)) - return await _DownloadImage(season.Poster, localPath, $"The poster of {season.Slug}"); - return false; - } - /// - /// Download images of a specified episode. - /// - /// - /// The item to cache images. - /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// - /// true if an image has been downloaded, false otherwise. - private async Task _Validate([NotNull] Episode episode, bool alwaysDownload) - { - if (episode.Thumb == null) - return false; - - string localPath = await _GetEpisodeThumb(episode); - if (alwaysDownload || !await _files.Exists(localPath)) - return await _DownloadImage(episode.Thumb, localPath, $"The thumbnail of {episode.Slug}"); - return false; - } - - /// - /// Download images of a specified provider. - /// - /// - /// The item to cache images. - /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// - /// true if an image has been downloaded, false otherwise. - private async Task _Validate([NotNull] Provider provider, bool alwaysDownload) - { - if (provider.Logo == null) - return false; - - string localPath = await GetLogo(provider); - if (alwaysDownload || !await _files.Exists(localPath)) - return await _DownloadImage(provider.Logo, localPath, $"The logo of {provider.Slug}"); - return false; - } - /// - public Task GetPoster(T item) - where T : IResource + public async Task GetImagePath(T item, int imageID) + where T : IThumbnails { if (item == null) throw new ArgumentNullException(nameof(item)); - return item switch + string imageName = imageID switch { - Show show => Task.FromResult(_files.Combine(_files.GetExtraDirectory(show), "poster.jpg")), - Season season => _GetSeasonPoster(season), - People actor => Task.FromResult(_files.Combine(_options.CurrentValue.PeoplePath, $"{actor.Slug}.jpg")), - _ => throw new NotSupportedException($"The type {typeof(T).Name} does not have a poster.") + Thumbnails.Poster => "poster.jpg", + Thumbnails.Logo => "logo.jpg", + Thumbnails.Thumbnail => "thumbnail.jpg", + _ => $"{imageID}.jpg" }; - } - - /// - /// Retrieve the path of a season's poster. - /// - /// The season to retrieve the poster from. - /// The path of the season's poster. - private async Task _GetSeasonPoster(Season season) - { - if (season.Show == null) - await _library.Value.Load(season, x => x.Show); - return _files.Combine(_files.GetExtraDirectory(season.Show), $"season-{season.SeasonNumber}.jpg"); - } - - /// - public Task GetThumbnail(T item) - where T : IResource - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - return item switch + + // TODO implement a generic way, probably need to rework IFileManager.GetExtraDirectory too. + switch (item) { - Show show => Task.FromResult(_files.Combine(_files.GetExtraDirectory(show), "backdrop.jpg")), - Episode episode => _GetEpisodeThumb(episode), - _ => throw new NotSupportedException($"The type {typeof(T).Name} does not have a thumbnail.") - }; - } - - /// - /// Get the path for an episode's thumbnail. - /// - /// The episode to retrieve the thumbnail from - /// The path of the given episode's thumbnail. - private async Task _GetEpisodeThumb(Episode episode) - { - if (episode.Show == null) - await _library.Value.Load(episode, x => x.Show); - string dir = _files.Combine(_files.GetExtraDirectory(episode.Show), "Thumbnails"); - await _files.CreateDirectory(dir); - return _files.Combine(dir, $"{Path.GetFileNameWithoutExtension(episode.Path)}.jpg"); - } - - /// - public Task GetLogo(T item) - where T : IResource - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - return Task.FromResult(item switch - { - Show show => _files.Combine(_files.GetExtraDirectory(show), "logo.png"), - Provider provider => _files.Combine(_options.CurrentValue.ProviderPath, - $"{provider.Slug}.{provider.LogoExtension}"), - _ => throw new NotSupportedException($"The type {typeof(T).Name} does not have a thumbnail.") - }); + case Show show: + return _files.Combine(_files.GetExtraDirectory(show), imageName); + + case Season season: + if (season.Show == null) + await _library.Value.Load(season, x => x.Show); + return _files.Combine( + _files.GetExtraDirectory(season.Show!), + $"season-{season.SeasonNumber}-{imageName}"); + + case Episode episode: + if (episode.Show == null) + await _library.Value.Load(episode, x => x.Show); + string dir = _files.Combine(_files.GetExtraDirectory(episode.Show!), "Thumbnails"); + await _files.CreateDirectory(dir); + return _files.Combine(dir, $"{Path.GetFileNameWithoutExtension(episode.Path)}-{imageName}"); + + case People actor: + return _files.Combine(_options.CurrentValue.PeoplePath, $"{actor.Slug}-{imageName}"); + + case Provider provider: + return _files.Combine( + _options.CurrentValue.ProviderPath, + $"{provider.Slug}-{imageName[..^4]}{provider.LogoExtension}"); + + default: + throw new NotSupportedException($"The type {typeof(T).Name} is not supported."); + } } } } diff --git a/Kyoo/Tasks/RegisterEpisode.cs b/Kyoo/Tasks/RegisterEpisode.cs index 9df8b1f7..033bfceb 100644 --- a/Kyoo/Tasks/RegisterEpisode.cs +++ b/Kyoo/Tasks/RegisterEpisode.cs @@ -163,7 +163,7 @@ namespace Kyoo.Tasks /// The type of the item /// The existing or filled item. private async Task _RegisterAndFill(T item) - where T : class, IResource + where T : class, IResource, IThumbnails { if (item == null || string.IsNullOrEmpty(item.Slug)) return null; diff --git a/Kyoo/Views/EpisodeApi.cs b/Kyoo/Views/EpisodeApi.cs index b7d83e27..beaf0273 100644 --- a/Kyoo/Views/EpisodeApi.cs +++ b/Kyoo/Views/EpisodeApi.cs @@ -195,7 +195,7 @@ namespace Kyoo.Api try { Episode episode = await _libraryManager.Get(id); - return _files.FileResult(await _thumbnails.GetThumbnail(episode)); + return _files.FileResult(await _thumbnails.GetImagePath(episode, Thumbnails.Thumbnail)); } catch (ItemNotFoundException) { @@ -210,7 +210,7 @@ namespace Kyoo.Api try { Episode episode = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbnails.GetThumbnail(episode)); + return _files.FileResult(await _thumbnails.GetImagePath(episode, Thumbnails.Thumbnail)); } catch (ItemNotFoundException) { diff --git a/Kyoo/Views/PeopleApi.cs b/Kyoo/Views/PeopleApi.cs index bb757a60..194504ff 100644 --- a/Kyoo/Views/PeopleApi.cs +++ b/Kyoo/Views/PeopleApi.cs @@ -94,7 +94,7 @@ namespace Kyoo.Api People people = await _libraryManager.GetOrDefault(id); if (people == null) return NotFound(); - return _files.FileResult(await _thumbs.GetPoster(people)); + return _files.FileResult(await _thumbs.GetImagePath(people, Thumbnails.Poster)); } [HttpGet("{slug}/poster")] @@ -103,7 +103,7 @@ namespace Kyoo.Api People people = await _libraryManager.GetOrDefault(slug); if (people == null) return NotFound(); - return _files.FileResult(await _thumbs.GetPoster(people)); + return _files.FileResult(await _thumbs.GetImagePath(people, Thumbnails.Poster)); } } } \ No newline at end of file diff --git a/Kyoo/Views/ProviderApi.cs b/Kyoo/Views/ProviderApi.cs index 026b79ef..06c06eb8 100644 --- a/Kyoo/Views/ProviderApi.cs +++ b/Kyoo/Views/ProviderApi.cs @@ -36,7 +36,7 @@ namespace Kyoo.Api Provider provider = await _libraryManager.GetOrDefault(id); if (provider == null) return NotFound(); - return _files.FileResult(await _thumbnails.GetLogo(provider)); + return _files.FileResult(await _thumbnails.GetImagePath(provider, Thumbnails.Logo)); } [HttpGet("{slug}/logo")] @@ -45,7 +45,7 @@ namespace Kyoo.Api Provider provider = await _libraryManager.GetOrDefault(slug); if (provider == null) return NotFound(); - return _files.FileResult(await _thumbnails.GetLogo(provider)); + return _files.FileResult(await _thumbnails.GetImagePath(provider, Thumbnails.Logo)); } } } \ No newline at end of file diff --git a/Kyoo/Views/SeasonApi.cs b/Kyoo/Views/SeasonApi.cs index 8d08dd0c..0241de55 100644 --- a/Kyoo/Views/SeasonApi.cs +++ b/Kyoo/Views/SeasonApi.cs @@ -151,7 +151,7 @@ namespace Kyoo.Api if (season == null) return NotFound(); await _libraryManager.Load(season, x => x.Show); - return _files.FileResult(await _thumbs.GetPoster(season)); + return _files.FileResult(await _thumbs.GetImagePath(season, Thumbnails.Poster)); } [HttpGet("{slug}/poster")] @@ -161,7 +161,7 @@ namespace Kyoo.Api if (season == null) return NotFound(); await _libraryManager.Load(season, x => x.Show); - return _files.FileResult(await _thumbs.GetPoster(season)); + return _files.FileResult(await _thumbs.GetImagePath(season, Thumbnails.Poster)); } } } \ No newline at end of file diff --git a/Kyoo/Views/ShowApi.cs b/Kyoo/Views/ShowApi.cs index 04854849..d7162792 100644 --- a/Kyoo/Views/ShowApi.cs +++ b/Kyoo/Views/ShowApi.cs @@ -417,7 +417,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbs.GetPoster(show)); + return _files.FileResult(await _thumbs.GetImagePath(show, Thumbnails.Poster)); } catch (ItemNotFoundException) { @@ -431,7 +431,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbs.GetLogo(show)); + return _files.FileResult(await _thumbs.GetImagePath(show, Thumbnails.Logo)); } catch (ItemNotFoundException) { @@ -446,7 +446,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbs.GetThumbnail(show)); + return _files.FileResult(await _thumbs.GetImagePath(show, Thumbnails.Thumbnail)); } catch (ItemNotFoundException) {