94 lines
3.4 KiB
Python
94 lines
3.4 KiB
Python
"""Subtitle domain entities."""
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
|
|
from ..shared.value_objects import ImdbId, FilePath
|
|
from .value_objects import Language, SubtitleFormat, TimingOffset
|
|
|
|
|
|
@dataclass
|
|
class Subtitle:
|
|
"""
|
|
Subtitle entity representing a subtitle file.
|
|
|
|
Can be associated with either a movie or a TV show episode.
|
|
"""
|
|
media_imdb_id: ImdbId
|
|
language: Language
|
|
format: SubtitleFormat
|
|
file_path: FilePath
|
|
|
|
# Optional: for TV shows
|
|
season_number: Optional[int] = None
|
|
episode_number: Optional[int] = None
|
|
|
|
# Subtitle metadata
|
|
timing_offset: TimingOffset = TimingOffset(0)
|
|
hearing_impaired: bool = False
|
|
forced: bool = False # Forced subtitles (for foreign language parts)
|
|
|
|
# Source information
|
|
source: Optional[str] = None # e.g., "OpenSubtitles", "Subscene"
|
|
uploader: Optional[str] = None
|
|
download_count: Optional[int] = None
|
|
rating: Optional[float] = None
|
|
|
|
def __post_init__(self):
|
|
"""Validate subtitle entity."""
|
|
# Ensure ImdbId is actually an ImdbId instance
|
|
if not isinstance(self.media_imdb_id, ImdbId):
|
|
if isinstance(self.media_imdb_id, str):
|
|
object.__setattr__(self, 'media_imdb_id', ImdbId(self.media_imdb_id))
|
|
|
|
# Ensure Language is actually a Language instance
|
|
if not isinstance(self.language, Language):
|
|
if isinstance(self.language, str):
|
|
object.__setattr__(self, 'language', Language.from_code(self.language))
|
|
|
|
# Ensure SubtitleFormat is actually a SubtitleFormat instance
|
|
if not isinstance(self.format, SubtitleFormat):
|
|
if isinstance(self.format, str):
|
|
object.__setattr__(self, 'format', SubtitleFormat.from_extension(self.format))
|
|
|
|
# Ensure FilePath is actually a FilePath instance
|
|
if not isinstance(self.file_path, FilePath):
|
|
object.__setattr__(self, 'file_path', FilePath(self.file_path))
|
|
|
|
def is_for_movie(self) -> bool:
|
|
"""Check if this subtitle is for a movie."""
|
|
return self.season_number is None and self.episode_number is None
|
|
|
|
def is_for_episode(self) -> bool:
|
|
"""Check if this subtitle is for a TV show episode."""
|
|
return self.season_number is not None and self.episode_number is not None
|
|
|
|
def get_filename(self) -> str:
|
|
"""
|
|
Get the suggested filename for this subtitle.
|
|
|
|
Format for movies: "Movie.Title.{lang}.{format}"
|
|
Format for episodes: "S01E05.{lang}.{format}"
|
|
"""
|
|
if self.is_for_episode():
|
|
base = f"S{self.season_number:02d}E{self.episode_number:02d}"
|
|
else:
|
|
# For movies, use the file path stem
|
|
base = self.file_path.value.stem
|
|
|
|
parts = [base, self.language.value]
|
|
|
|
if self.hearing_impaired:
|
|
parts.append("hi")
|
|
if self.forced:
|
|
parts.append("forced")
|
|
|
|
return f"{'.'.join(parts)}.{self.format.value}"
|
|
|
|
def __str__(self) -> str:
|
|
if self.is_for_episode():
|
|
return f"Subtitle S{self.season_number:02d}E{self.episode_number:02d} ({self.language.value})"
|
|
return f"Subtitle ({self.language.value})"
|
|
|
|
def __repr__(self) -> str:
|
|
return f"Subtitle(media={self.media_imdb_id}, lang={self.language.value})"
|