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