diff --git a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs index b60378af..61e0867f 100644 --- a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs +++ b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs @@ -99,6 +99,11 @@ namespace Kyoo.Abstractions.Models /// public int Rating { get; set; } + /// + /// How long is this movie? (in minutes) + /// + public int? Runtime { get; set; } + /// /// The date this show started airing. It can be null if this is unknown. /// diff --git a/back/src/Kyoo.Abstractions/Models/News.cs b/back/src/Kyoo.Abstractions/Models/News.cs index 8216068a..ca79c732 100644 --- a/back/src/Kyoo.Abstractions/Models/News.cs +++ b/back/src/Kyoo.Abstractions/Models/News.cs @@ -93,7 +93,12 @@ namespace Kyoo.Abstractions.Models /// /// How well this item is rated? (from 0 to 100). /// - public int Rating { get; set; } + public int? Rating { get; set; } + + /// + /// How long is this movie or episode? (in minutes) + /// + public int Runtime { get; set; } /// /// The date this movie aired. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs index 6bb64121..ea696d57 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -133,6 +133,11 @@ namespace Kyoo.Abstractions.Models /// public string? Overview { get; set; } + /// + /// How long is this episode? (in minutes) + /// + public int Runtime { get; set; } + /// /// The release date of this episode. It can be null if unknown. /// diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs index 7ff7c4b2..c5fcbb88 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs @@ -82,6 +82,11 @@ namespace Kyoo.Abstractions.Models /// public int Rating { get; set; } + /// + /// How long is this movie? (in minutes) + /// + public int Runtime { get; set; } + /// /// The date this movie aired. /// diff --git a/back/src/Kyoo.Meilisearch/MeilisearchModule.cs b/back/src/Kyoo.Meilisearch/MeilisearchModule.cs index 9f983d39..1228b002 100644 --- a/back/src/Kyoo.Meilisearch/MeilisearchModule.cs +++ b/back/src/Kyoo.Meilisearch/MeilisearchModule.cs @@ -61,6 +61,7 @@ namespace Kyoo.Meiliseach CamelCase.ConvertName(nameof(LibraryItem.AirDate)), CamelCase.ConvertName(nameof(LibraryItem.AddedDate)), CamelCase.ConvertName(nameof(LibraryItem.Rating)), + CamelCase.ConvertName(nameof(LibraryItem.Runtime)), }, DisplayedAttributes = new[] { diff --git a/back/src/Kyoo.Postgresql/Migrations/20231031212819_rating.Designer.cs b/back/src/Kyoo.Postgresql/Migrations/20231031212819_rating.Designer.cs index fbda3775..fe21bd2c 100644 --- a/back/src/Kyoo.Postgresql/Migrations/20231031212819_rating.Designer.cs +++ b/back/src/Kyoo.Postgresql/Migrations/20231031212819_rating.Designer.cs @@ -121,6 +121,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("release_date"); + b.Property("Runtime") + .HasColumnType("integer") + .HasColumnName("runtime"); + b.Property("SeasonId") .HasColumnType("integer") .HasColumnName("season_id"); @@ -212,10 +216,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("path"); b.Property("Rating") - .IsRequired() .HasColumnType("integer") .HasColumnName("rating"); + b.Property("Runtime") + .HasColumnType("integer") + .HasColumnName("runtime"); + b.Property("Slug") .IsRequired() .HasMaxLength(256) @@ -298,10 +305,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("path"); b.Property("Rating") - .IsRequired() .HasColumnType("integer") .HasColumnName("rating"); + b.Property("Runtime") + .HasColumnType("integer") + .HasColumnName("runtime"); + b.Property("Slug") .IsRequired() .HasMaxLength(256) @@ -402,10 +412,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("path"); b.Property("Rating") - .IsRequired() .HasColumnType("integer") .HasColumnName("rating"); + b.Property("Runtime") + .HasColumnType("integer") + .HasColumnName("runtime"); + b.Property("SeasonNumber") .HasColumnType("integer") .HasColumnName("season_number"); @@ -628,7 +641,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("overview"); b.Property("Rating") - .IsRequired() .HasColumnType("integer") .HasColumnName("rating"); diff --git a/back/src/Kyoo.Postgresql/Migrations/20231031212819_rating.cs b/back/src/Kyoo.Postgresql/Migrations/20231031212819_rating.cs index 75e9d5f9..cd75a744 100644 --- a/back/src/Kyoo.Postgresql/Migrations/20231031212819_rating.cs +++ b/back/src/Kyoo.Postgresql/Migrations/20231031212819_rating.cs @@ -34,21 +34,21 @@ namespace Kyoo.Postgresql.Migrations s.id, s.slug, s.name, s.tagline, s.aliases, s.overview, s.tags, s.genres, s.status, s.start_air, s.end_air, s.poster_source, s.poster_blurhash, s.thumbnail_source, s.thumbnail_blurhash, s.logo_source, s.logo_blurhash, s.trailer, s.external_id, s.start_air AS air_date, NULL as path, - 'show'::item_kind AS kind, s.added_date, s.rating + 'show'::item_kind AS kind, s.added_date, s.rating, NULL AS runtime FROM shows AS s UNION ALL SELECT -m.id, m.slug, m.name, m.tagline, m.aliases, m.overview, m.tags, m.genres, m.status, m.air_date as start_air, m.air_date as end_air, m.poster_source, m.poster_blurhash, m.thumbnail_source, m.thumbnail_blurhash, m.logo_source, m.logo_blurhash, m.trailer, m.external_id, m.air_date, m.path, - 'movie'::item_kind AS kind, m.added_date, m.rating + 'movie'::item_kind AS kind, m.added_date, m.rating, m.runtime FROM movies AS m UNION ALL SELECT c.id + 10000 AS id, c.slug, c.name, NULL as tagline, NULL as alises, c.overview, NULL AS tags, NULL AS genres, 'unknown'::status AS status, NULL AS start_air, NULL AS end_air, c.poster_source, c.poster_blurhash, c.thumbnail_source, c.thumbnail_blurhash, c.logo_source, c.logo_blurhash, NULL as trailer, c.external_id, NULL AS air_date, NULL as path, - 'collection'::item_kind AS kind, c.added_date, NULL as rating + 'collection'::item_kind AS kind, c.added_date, NULL AS rating, NULL AS runtime FROM collections AS c "); @@ -62,7 +62,7 @@ namespace Kyoo.Postgresql.Migrations 'episode'::news_kind AS kind, e.added_date, s.id AS show_id, s.slug AS show_slug, s.name AS show_name, s.poster_source AS show_poster_source, s.poster_blurhash AS show_poster_blurhash, s.thumbnail_source AS show_thumbnail_source, s.thumbnail_blurhash AS show_thumbnail_blurhash, s.logo_source AS show_logo_source, s.logo_blurhash AS show_logo_blurhash, - NULL as rating + NULL as rating, e.runtime FROM episodes AS e LEFT JOIN shows AS s ON e.show_id = s.id UNION ALL @@ -72,7 +72,7 @@ namespace Kyoo.Postgresql.Migrations m.logo_source, m.logo_blurhash, m.trailer, m.external_id, NULL AS season_number, NULL AS episode_number, NULL as absolute_number, 'movie'::news_kind AS kind, m.added_date, NULL AS show_id, NULL AS show_slug, NULL AS show_name, NULL AS show_poster_source, NULL AS show_poster_blurhash, NULL AS show_thumbnail_source, NULL AS show_thumbnail_blurhash, - NULL AS show_logo_source, NULL AS show_logo_blurhash, m.rating + NULL AS show_logo_source, NULL AS show_logo_blurhash, m.rating, m.runtime FROM movies AS m "); } @@ -96,6 +96,20 @@ namespace Kyoo.Postgresql.Migrations type: "integer", nullable: false); + migrationBuilder.AddColumn( + name: "runtime", + table: "movies", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "runtime", + table: "episodes", + type: "integer", + nullable: false, + defaultValue: 0); + CreateItemView(migrationBuilder); } @@ -114,6 +128,14 @@ namespace Kyoo.Postgresql.Migrations name: "rating", table: "movies"); + migrationBuilder.DropColumn( + name: "runtime", + table: "movies"); + + migrationBuilder.DropColumn( + name: "runtime", + table: "episodes"); + AddedDate.CreateItemView(migrationBuilder); } } diff --git a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs index c8961480..88a40765 100644 --- a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs +++ b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using System.Collections.Generic; using Kyoo.Abstractions.Models; @@ -118,6 +118,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("release_date"); + b.Property("Runtime") + .HasColumnType("integer") + .HasColumnName("runtime"); + b.Property("SeasonId") .HasColumnType("integer") .HasColumnName("season_id"); @@ -209,10 +213,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("path"); b.Property("Rating") - .IsRequired() .HasColumnType("integer") .HasColumnName("rating"); + b.Property("Runtime") + .HasColumnType("integer") + .HasColumnName("runtime"); + b.Property("Slug") .IsRequired() .HasMaxLength(256) @@ -295,10 +302,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("path"); b.Property("Rating") - .IsRequired() .HasColumnType("integer") .HasColumnName("rating"); + b.Property("Runtime") + .HasColumnType("integer") + .HasColumnName("runtime"); + b.Property("Slug") .IsRequired() .HasMaxLength(256) @@ -399,10 +409,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("path"); b.Property("Rating") - .IsRequired() .HasColumnType("integer") .HasColumnName("rating"); + b.Property("Runtime") + .HasColumnType("integer") + .HasColumnName("runtime"); + b.Property("SeasonNumber") .HasColumnType("integer") .HasColumnName("season_number"); @@ -625,7 +638,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("overview"); b.Property("Rating") - .IsRequired() .HasColumnType("integer") .HasColumnName("rating"); diff --git a/scanner/providers/implementations/themoviedatabase.py b/scanner/providers/implementations/themoviedatabase.py index 43352005..cca36fdc 100644 --- a/scanner/providers/implementations/themoviedatabase.py +++ b/scanner/providers/implementations/themoviedatabase.py @@ -139,6 +139,7 @@ class TheMovieDatabase(Provider): if movie["status"] == "Released" else MovieStatus.PLANNED, rating=round(float(movie["vote_average"]) * 10), + runtime=int(movie["runtime"]), studios=[self.to_studio(x) for x in movie["production_companies"]], genres=[ self.genre_map[x["id"]] @@ -423,6 +424,7 @@ class TheMovieDatabase(Provider): season_number=episode["season_number"], episode_number=episode["episode_number"], absolute_number=absolute, + runtime=int(episode["runtime"]), release_date=datetime.strptime(episode["air_date"], "%Y-%m-%d").date() if episode["air_date"] else None, diff --git a/scanner/providers/types/episode.py b/scanner/providers/types/episode.py index f46eb488..0d30002f 100644 --- a/scanner/providers/types/episode.py +++ b/scanner/providers/types/episode.py @@ -26,6 +26,7 @@ class Episode: season_number: Optional[int] episode_number: Optional[int] absolute_number: Optional[int] + runtime: int release_date: Optional[date | int] thumbnail: Optional[str] external_id: dict[str, MetadataID] diff --git a/scanner/providers/types/movie.py b/scanner/providers/types/movie.py index 76afc089..7f1e991e 100644 --- a/scanner/providers/types/movie.py +++ b/scanner/providers/types/movie.py @@ -30,18 +30,19 @@ class MovieTranslation: @dataclass class Movie: - original_language: Optional[str] = None - aliases: list[str] = field(default_factory=list) - air_date: Optional[date | int] = None - status: Status = Status.UNKNOWN - rating: int = None - path: Optional[str] = None - studios: list[Studio] = field(default_factory=list) - genres: list[Genre] = field(default_factory=list) + original_language: Optional[str] + aliases: list[str] + air_date: Optional[date | int] + status: Status + rating: int + runtime: int + studios: list[Studio] + genres: list[Genre] # TODO: handle staff # staff: list[Staff] - external_id: dict[str, MetadataID] = field(default_factory=dict) + external_id: dict[str, MetadataID] + path: Optional[str] = None translations: dict[str, MovieTranslation] = field(default_factory=dict) def to_kyoo(self):