Reworking images

This commit is contained in:
Zoe Roux 2021-07-27 00:11:06 +02:00
parent 2812c9cacf
commit 9777551b78
22 changed files with 326 additions and 310 deletions

View File

@ -23,37 +23,18 @@ namespace Kyoo.Controllers
/// <typeparam name="T">The type of the item</typeparam>
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
Task<bool> DownloadImages<T>([NotNull] T item, bool alwaysDownload = false)
where T : IResource;
where T : IThumbnails;
/// <summary>
/// Retrieve the local path of the poster of the given item.
/// </summary>
/// <param name="item">The item to retrieve the poster from.</param>
/// <param name="imageID">The ID of the image. See <see cref="Thumbnails"/> for values.</param>
/// <typeparam name="T">The type of the item</typeparam>
/// <exception cref="NotSupportedException">If the type does not have a poster</exception>
/// <returns>The path of the poster for the given resource (it might or might not exists).</returns>
Task<string> GetPoster<T>([NotNull] T item)
where T : IResource;
/// <summary>
/// Retrieve the local path of the logo of the given item.
/// </summary>
/// <param name="item">The item to retrieve the logo from.</param>
/// <typeparam name="T">The type of the item</typeparam>
/// <exception cref="NotSupportedException">If the type does not have a logo</exception>
/// <returns>The path of the logo for the given resource (it might or might not exists).</returns>
Task<string> GetLogo<T>([NotNull] T item)
where T : IResource;
/// <summary>
/// Retrieve the local path of the thumbnail of the given item.
/// </summary>
/// <param name="item">The item to retrieve the thumbnail from.</param>
/// <typeparam name="T">The type of the item</typeparam>
/// <exception cref="NotSupportedException">If the type does not have a thumbnail</exception>
/// <returns>The path of the thumbnail for the given resource (it might or might not exists).</returns>
Task<string> GetThumbnail<T>([NotNull] T item)
where T : IResource;
Task<string> GetImagePath<T>([NotNull] T item, int imageID)
where T : IThumbnails;
}
}

View File

@ -8,7 +8,7 @@ namespace Kyoo.Models
/// A class representing collections of <see cref="Show"/>.
/// A collection can also be stored in a <see cref="Library"/>.
/// </summary>
public class Collection : IResource, IMetadata
public class Collection : IResource, IMetadata, IThumbnails
{
/// <inheritdoc />
public int ID { get; set; }
@ -20,14 +20,18 @@ namespace Kyoo.Models
/// The name of this collection.
/// </summary>
public string Name { get; set; }
/// <inheritdoc />
public Dictionary<int, string> Images { get; set; }
/// <summary>
/// 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.
/// </summary>
[SerializeAs("{HOST}/api/collection/{Slug}/poster")] public string Poster { get; set; }
[SerializeAs("{HOST}/api/collection/{Slug}/poster")]
public string Poster => Images[Thumbnails.Poster];
/// <summary>
/// The description of this collection.
/// </summary>
@ -44,7 +48,7 @@ namespace Kyoo.Models
[LoadableRelation] public ICollection<Library> Libraries { get; set; }
/// <inheritdoc />
public ICollection<MetadataID> ExternalIDs { get; set; }
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
#if ENABLE_INTERNAL_LINKS

View File

@ -10,7 +10,7 @@ namespace Kyoo.Models
/// <summary>
/// A class to represent a single show's episode.
/// </summary>
public class Episode : IResource, IMetadata
public class Episode : IResource, IMetadata, IThumbnails
{
/// <inheritdoc />
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 <see cref="IFileSystem"/> is allowed.
/// </summary>
[SerializeIgnore] public string Path { get; set; }
/// <inheritdoc />
public Dictionary<int, string> Images { get; set; }
/// <summary>
/// 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.
/// </summary>
[SerializeAs("{HOST}/api/episodes/{Slug}/thumb")] public string Thumb { get; set; }
[SerializeAs("{HOST}/api/episodes/{Slug}/thumb")]
public string Thumb => Images[Thumbnails.Thumbnail];
/// <summary>
/// The title of this episode.
@ -122,7 +126,7 @@ namespace Kyoo.Models
public DateTime? ReleaseDate { get; set; }
/// <inheritdoc />
public ICollection<MetadataID> ExternalIDs { get; set; }
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
/// <summary>
/// The list of tracks this episode has. This lists video, audio and subtitles available.

View File

@ -11,6 +11,7 @@ namespace Kyoo.Models
/// <summary>
/// The link to metadata providers that this show has. See <see cref="MetadataID"/> for more information.
/// </summary>
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
[EditableRelation] [LoadableRelation]
public ICollection<MetadataID> ExternalIDs { get; set; }
}
}

View File

@ -0,0 +1,49 @@
using System.Collections.Generic;
using Kyoo.Controllers;
namespace Kyoo.Models
{
/// <summary>
/// An interface representing items that contains images (like posters, thumbnails, logo, banners...)
/// </summary>
public interface IThumbnails
{
/// <summary>
/// The list of images mapped to a certain index.
/// The string value should be a path supported by the <see cref="IFileSystem"/>.
/// </summary>
/// <remarks>
/// An arbitrary index should not be used, instead use indexes from <see cref="Thumbnails"/>
/// </remarks>
public Dictionary<int, string> Images { get; set; }
// TODO remove Posters properties add them via the json serializer for every IThumbnails
}
/// <summary>
/// A class containing constant values for images. To be used as index of a <see cref="IThumbnails.Images"/>.
/// </summary>
public static class Thumbnails
{
/// <summary>
/// A poster is a 9/16 format image with the cover of the resource.
/// </summary>
public const int Poster = 0;
/// <summary>
/// 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.
/// </summary>
public const int Thumbnail = 1;
/// <summary>
/// A logo is a small image representing the resource.
/// </summary>
public const int Logo = 2;
/// <summary>
/// A video of a few minutes that tease the content.
/// </summary>
public const int Trailer = 3;
}
}

View File

@ -6,7 +6,7 @@ namespace Kyoo.Models
/// <summary>
/// An actor, voice actor, writer, animator, somebody who worked on a <see cref="Show"/>.
/// </summary>
public class People : IResource, IMetadata
public class People : IResource, IMetadata, IThumbnails
{
/// <inheritdoc />
public int ID { get; set; }
@ -19,15 +19,19 @@ namespace Kyoo.Models
/// </summary>
public string Name { get; set; }
/// <inheritdoc />
public Dictionary<int, string> Images { get; set; }
/// <summary>
/// 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.
/// </summary>
[SerializeAs("{HOST}/api/people/{Slug}/poster")] public string Poster { get; set; }
[SerializeAs("{HOST}/api/people/{Slug}/poster")]
public string Poster => Images[Thumbnails.Poster];
/// <inheritdoc />
public ICollection<MetadataID> ExternalIDs { get; set; }
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
/// <summary>
/// The list of roles this person has played in. See <see cref="PeopleRole"/> for more information.

View File

@ -9,7 +9,7 @@ namespace Kyoo.Models
/// This class contains metadata about <see cref="IMetadataProvider"/>.
/// You can have providers even if you don't have the corresponding <see cref="IMetadataProvider"/>.
/// </summary>
public class Provider : IResource
public class Provider : IResource, IThumbnails
{
/// <inheritdoc />
public int ID { get; set; }
@ -22,12 +22,16 @@ namespace Kyoo.Models
/// </summary>
public string Name { get; set; }
/// <inheritdoc />
public Dictionary<int, string> Images { get; set; }
/// <summary>
/// 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.
/// </summary>
[SerializeAs("{HOST}/api/providers/{Slug}/logo")] public string Logo { get; set; }
[SerializeAs("{HOST}/api/providers/{Slug}/logo")]
public string Logo => Images[Thumbnails.Logo];
/// <summary>
/// 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;
}
}
}

View File

@ -10,7 +10,7 @@ namespace Kyoo.Models
/// <summary>
/// A season of a <see cref="Show"/>.
/// </summary>
public class Season : IResource, IMetadata
public class Season : IResource, IMetadata, IThumbnails
{
/// <inheritdoc />
public int ID { get; set; }
@ -74,15 +74,19 @@ namespace Kyoo.Models
/// </summary>
public DateTime? EndDate { get; set; }
/// <inheritdoc />
public Dictionary<int, string> Images { get; set; }
/// <summary>
/// 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.
/// </summary>
[SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] public string Poster { get; set; }
[SerializeAs("{HOST}/api/seasons/{Slug}/thumb")]
public string Poster => Images[Thumbnails.Poster];
/// <inheritdoc />
public ICollection<MetadataID> ExternalIDs { get; set; }
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
/// <summary>
/// The list of episodes that this season contains.

View File

@ -11,7 +11,7 @@ namespace Kyoo.Models
/// <summary>
/// A series or a movie.
/// </summary>
public class Show : IResource, IMetadata, IOnMerge
public class Show : IResource, IMetadata, IOnMerge, IThumbnails
{
/// <inheritdoc />
public int ID { get; set; }
@ -44,12 +44,12 @@ namespace Kyoo.Models
/// Is this show airing, not aired yet or finished?
/// </summary>
public Status Status { get; set; }
/// <summary>
/// An URL to a trailer. This could be any path supported by the <see cref="IFileSystem"/>.
/// </summary>
/// 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];
/// <summary>
/// The date this show started airing. It can be null if this is unknown.
@ -63,26 +63,32 @@ namespace Kyoo.Models
/// </summary>
public DateTime? EndAir { get; set; }
/// <inheritdoc />
public Dictionary<int, string> Images { get; set; }
/// <summary>
/// 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.
/// </summary>
[SerializeAs("{HOST}/api/shows/{Slug}/poster")] public string Poster { get; set; }
[SerializeAs("{HOST}/api/shows/{Slug}/poster")]
public string Poster => Images[Thumbnails.Poster];
/// <summary>
/// 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.
/// </summary>
[SerializeAs("{HOST}/api/shows/{Slug}/logo")] public string Logo { get; set; }
[SerializeAs("{HOST}/api/shows/{Slug}/logo")]
public string Logo => Images[Thumbnails.Logo];
/// <summary>
/// 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.
/// </summary>
[SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] public string Backdrop { get; set; }
[SerializeAs("{HOST}/api/shows/{Slug}/backdrop")]
public string Backdrop => Images[Thumbnails.Thumbnail];
/// <summary>
/// True if this show represent a movie, false otherwise.
@ -90,7 +96,7 @@ namespace Kyoo.Models
public bool IsMovie { get; set; }
/// <inheritdoc />
public ICollection<MetadataID> ExternalIDs { get; set; }
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
/// <summary>
/// The ID of the Studio that made this show.

View File

@ -25,7 +25,7 @@ namespace Kyoo.Models
[LoadableRelation] public ICollection<Show> Shows { get; set; }
/// <inheritdoc />
public ICollection<MetadataID> ExternalIDs { get; set; }
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
/// <summary>
/// Create a new, empty, <see cref="Studio"/>.

View File

@ -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)

View File

@ -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

View File

@ -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"
}
};
/// <summary>

View File

@ -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

View File

@ -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"
}
};

View File

@ -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
});
}
/// <inheritdoc />
public Task<bool> DownloadImages<T>(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)
};
}
/// <summary>
/// An helper function to download an image using a <see cref="LocalFileSystem"/>.
/// </summary>
@ -97,196 +79,72 @@ namespace Kyoo.Controllers
return false;
}
}
/// <summary>
/// Download images of a specified show.
/// </summary>
/// <param name="show">
/// The item to cache images.
/// </param>
/// <param name="alwaysDownload">
/// <c>true</c> if images should be downloaded even if they already exists locally, <c>false</c> otherwise.
/// </param>
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
private async Task<bool> _Validate([NotNull] Show show, bool alwaysDownload)
/// <inheritdoc />
public async Task<bool> DownloadImages<T>(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;
}
/// <summary>
/// Download images of a specified person.
/// </summary>
/// <param name="people">
/// The item to cache images.
/// </param>
/// <param name="alwaysDownload">
/// <c>true</c> if images should be downloaded even if they already exists locally, <c>false</c> otherwise.
/// </param>
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
private async Task<bool> _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;
}
/// <summary>
/// Download images of a specified season.
/// </summary>
/// <param name="season">
/// The item to cache images.
/// </param>
/// <param name="alwaysDownload">
/// <c>true</c> if images should be downloaded even if they already exists locally, <c>false</c> otherwise.
/// </param>
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
private async Task<bool> _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;
}
/// <summary>
/// Download images of a specified episode.
/// </summary>
/// <param name="episode">
/// The item to cache images.
/// </param>
/// <param name="alwaysDownload">
/// <c>true</c> if images should be downloaded even if they already exists locally, <c>false</c> otherwise.
/// </param>
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
private async Task<bool> _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;
}
/// <summary>
/// Download images of a specified provider.
/// </summary>
/// <param name="provider">
/// The item to cache images.
/// </param>
/// <param name="alwaysDownload">
/// <c>true</c> if images should be downloaded even if they already exists locally, <c>false</c> otherwise.
/// </param>
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
private async Task<bool> _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;
}
/// <inheritdoc />
public Task<string> GetPoster<T>(T item)
where T : IResource
public async Task<string> GetImagePath<T>(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"
};
}
/// <summary>
/// Retrieve the path of a season's poster.
/// </summary>
/// <param name="season">The season to retrieve the poster from.</param>
/// <returns>The path of the season's poster.</returns>
private async Task<string> _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");
}
/// <inheritdoc />
public Task<string> GetThumbnail<T>(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.")
};
}
/// <summary>
/// Get the path for an episode's thumbnail.
/// </summary>
/// <param name="episode">The episode to retrieve the thumbnail from</param>
/// <returns>The path of the given episode's thumbnail.</returns>
private async Task<string> _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");
}
/// <inheritdoc />
public Task<string> GetLogo<T>(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.");
}
}
}
}

View File

@ -163,7 +163,7 @@ namespace Kyoo.Tasks
/// <typeparam name="T">The type of the item</typeparam>
/// <returns>The existing or filled item.</returns>
private async Task<T> _RegisterAndFill<T>(T item)
where T : class, IResource
where T : class, IResource, IThumbnails
{
if (item == null || string.IsNullOrEmpty(item.Slug))
return null;

View File

@ -195,7 +195,7 @@ namespace Kyoo.Api
try
{
Episode episode = await _libraryManager.Get<Episode>(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<Episode>(slug);
return _files.FileResult(await _thumbnails.GetThumbnail(episode));
return _files.FileResult(await _thumbnails.GetImagePath(episode, Thumbnails.Thumbnail));
}
catch (ItemNotFoundException)
{

View File

@ -94,7 +94,7 @@ namespace Kyoo.Api
People people = await _libraryManager.GetOrDefault<People>(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<People>(slug);
if (people == null)
return NotFound();
return _files.FileResult(await _thumbs.GetPoster(people));
return _files.FileResult(await _thumbs.GetImagePath(people, Thumbnails.Poster));
}
}
}

View File

@ -36,7 +36,7 @@ namespace Kyoo.Api
Provider provider = await _libraryManager.GetOrDefault<Provider>(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<Provider>(slug);
if (provider == null)
return NotFound();
return _files.FileResult(await _thumbnails.GetLogo(provider));
return _files.FileResult(await _thumbnails.GetImagePath(provider, Thumbnails.Logo));
}
}
}

View File

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

View File

@ -417,7 +417,7 @@ namespace Kyoo.Api
try
{
Show show = await _libraryManager.Get<Show>(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<Show>(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<Show>(slug);
return _files.FileResult(await _thumbs.GetThumbnail(show));
return _files.FileResult(await _thumbs.GetImagePath(show, Thumbnails.Thumbnail));
}
catch (ItemNotFoundException)
{