mirror of
https://github.com/maxdorninger/MediaManager.git
synced 2026-04-17 21:54:00 +02:00
**Description** As explained on #322, MediaManager currently only matches torrents that represent full seasons or season packs. As a result, valid episode-based releases — commonly returned by indexers such as EZTV — are filtered out during scoring and never considered for download. Initial changes to the season parsing logic allow these torrents to be discovered. However, additional changes are required beyond season parsing to properly support single-episode imports. This PR is intended as a work-in-progress / RFC to discuss the required changes and align on the correct approach before completing the implementation. **Things planned to do** [X] Update Web UI to better display episode-level details [ ] Update TV show import logic to handle single episode files, instead of assuming full season files (to avoid integrity errors when episodes are missing) [ ] Create episode file tables to store episode-level data, similar to season files [ ] Implement fetching and downloading logic for single-episode torrents **Notes / current limitations** At the moment, the database and import logic assume one file per season per quality, which works for season packs but not for episode-based releases. These changes are intentionally not completed yet and are part of the discussion this PR aims to start. **Request for feedback** This represents a significant change in how TV content is handled in MediaManager. Before proceeding further, feedback from @maxdorninger on the overall direction and next steps would be greatly appreciated. Once aligned, the remaining tasks can be implemented incrementally. --------- Co-authored-by: Maximilian Dorninger <97409287+maxdorninger@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
184 lines
4.0 KiB
Python
184 lines
4.0 KiB
Python
import typing
|
|
import uuid
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
|
|
from media_manager.auth.schemas import UserRead
|
|
from media_manager.torrent.models import Quality
|
|
from media_manager.torrent.schemas import TorrentId, TorrentStatus
|
|
|
|
ShowId = typing.NewType("ShowId", UUID)
|
|
SeasonId = typing.NewType("SeasonId", UUID)
|
|
EpisodeId = typing.NewType("EpisodeId", UUID)
|
|
|
|
SeasonNumber = typing.NewType("SeasonNumber", int)
|
|
EpisodeNumber = typing.NewType("EpisodeNumber", int)
|
|
SeasonRequestId = typing.NewType("SeasonRequestId", UUID)
|
|
|
|
|
|
class Episode(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: EpisodeId = Field(default_factory=lambda: EpisodeId(uuid.uuid4()))
|
|
number: EpisodeNumber
|
|
external_id: int
|
|
title: str
|
|
overview: str | None = None
|
|
|
|
|
|
class Season(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: SeasonId = Field(default_factory=lambda: SeasonId(uuid.uuid4()))
|
|
number: SeasonNumber
|
|
|
|
name: str
|
|
overview: str
|
|
|
|
external_id: int
|
|
|
|
episodes: list[Episode]
|
|
|
|
|
|
class Show(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: ShowId = Field(default_factory=lambda: ShowId(uuid.uuid4()))
|
|
|
|
name: str
|
|
overview: str
|
|
year: int | None
|
|
|
|
ended: bool = False
|
|
external_id: int
|
|
metadata_provider: str
|
|
|
|
continuous_download: bool = False
|
|
library: str = "Default"
|
|
original_language: str | None = None
|
|
|
|
imdb_id: str | None = None
|
|
|
|
seasons: list[Season]
|
|
|
|
|
|
class SeasonRequestBase(BaseModel):
|
|
min_quality: Quality
|
|
wanted_quality: Quality
|
|
|
|
@model_validator(mode="after")
|
|
def ensure_wanted_quality_is_eq_or_gt_min_quality(self) -> "SeasonRequestBase":
|
|
if self.min_quality.value < self.wanted_quality.value:
|
|
msg = "wanted_quality must be equal to or lower than minimum_quality."
|
|
raise ValueError(msg)
|
|
return self
|
|
|
|
|
|
class CreateSeasonRequest(SeasonRequestBase):
|
|
season_id: SeasonId
|
|
|
|
|
|
class UpdateSeasonRequest(SeasonRequestBase):
|
|
id: SeasonRequestId
|
|
|
|
|
|
class SeasonRequest(SeasonRequestBase):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: SeasonRequestId = Field(default_factory=lambda: SeasonRequestId(uuid.uuid4()))
|
|
|
|
season_id: SeasonId
|
|
requested_by: UserRead | None = None
|
|
authorized: bool = False
|
|
authorized_by: UserRead | None = None
|
|
|
|
|
|
class RichSeasonRequest(SeasonRequest):
|
|
show: Show
|
|
season: Season
|
|
|
|
|
|
class EpisodeFile(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
episode_id: EpisodeId
|
|
quality: Quality
|
|
torrent_id: TorrentId | None
|
|
file_path_suffix: str
|
|
|
|
|
|
class PublicEpisodeFile(EpisodeFile):
|
|
downloaded: bool = False
|
|
|
|
|
|
class RichSeasonTorrent(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
torrent_id: TorrentId
|
|
torrent_title: str
|
|
status: TorrentStatus
|
|
quality: Quality
|
|
imported: bool
|
|
usenet: bool
|
|
|
|
file_path_suffix: str
|
|
seasons: list[SeasonNumber]
|
|
episodes: list[EpisodeNumber]
|
|
|
|
|
|
class RichShowTorrent(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
show_id: ShowId
|
|
name: str
|
|
year: int | None
|
|
metadata_provider: str
|
|
torrents: list[RichSeasonTorrent]
|
|
|
|
|
|
class PublicEpisode(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: EpisodeId
|
|
number: EpisodeNumber
|
|
|
|
downloaded: bool = False
|
|
title: str
|
|
overview: str | None = None
|
|
|
|
external_id: int
|
|
|
|
|
|
class PublicSeason(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: SeasonId
|
|
number: SeasonNumber
|
|
|
|
downloaded: bool = False
|
|
name: str
|
|
overview: str
|
|
|
|
external_id: int
|
|
|
|
episodes: list[PublicEpisode]
|
|
|
|
|
|
class PublicShow(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: ShowId
|
|
|
|
name: str
|
|
overview: str
|
|
year: int | None
|
|
|
|
external_id: int
|
|
metadata_provider: str
|
|
|
|
ended: bool = False
|
|
continuous_download: bool = False
|
|
library: str
|
|
|
|
seasons: list[PublicSeason]
|