233 lines
7.8 KiB
Python
233 lines
7.8 KiB
Python
"""
|
|
Data models and enums for Serien-Checker database
|
|
"""
|
|
|
|
from enum import Enum
|
|
from typing import Optional, Union
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
|
|
|
|
def _safe_parse_datetime(value: Union[str, datetime, None]) -> Optional[datetime]:
|
|
"""
|
|
Safely parse a datetime value that might be a string, datetime object, or None
|
|
|
|
Args:
|
|
value: String ISO format, datetime object, or None
|
|
|
|
Returns:
|
|
datetime object or None
|
|
"""
|
|
if value is None:
|
|
return None
|
|
if isinstance(value, datetime):
|
|
return value
|
|
if isinstance(value, str):
|
|
return datetime.fromisoformat(value)
|
|
return None
|
|
|
|
|
|
class SeasonType(Enum):
|
|
"""Types of seasons"""
|
|
NORMAL = "normal"
|
|
SPECIALS = "specials"
|
|
EXTRAS = "extras"
|
|
BEST_OF = "best_of"
|
|
YEAR_BASED = "year_based"
|
|
|
|
|
|
class DatePreference(Enum):
|
|
"""Preferred date types for episode releases"""
|
|
DE_FIRST = "de_first" # Deutsche Erstausstrahlung (frühestes deutsches Datum)
|
|
DE_TV = "de_tv" # Deutsche TV-Premiere
|
|
DE_STREAMING = "de_streaming" # Deutsche Streaming-Premiere
|
|
DE_HOME_MEDIA = "de_home_media" # Deutsche Home-Media-Premiere
|
|
DE_SYNC = "de_sync" # Deutsche Synchronfassung
|
|
ORIGINAL = "original" # Erstausstrahlung (Original)
|
|
|
|
|
|
@dataclass
|
|
class Series:
|
|
"""Represents a TV series"""
|
|
id: Optional[int]
|
|
title: str
|
|
url: str
|
|
date_preference: DatePreference
|
|
last_updated: Optional[datetime] = None
|
|
|
|
@classmethod
|
|
def from_row(cls, row: tuple) -> 'Series':
|
|
"""Create Series from database row"""
|
|
return cls(
|
|
id=row[0],
|
|
title=row[1],
|
|
url=row[2],
|
|
date_preference=DatePreference(row[3]),
|
|
last_updated=datetime.fromisoformat(row[4]) if row[4] else None
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class Season:
|
|
"""Represents a season of a series"""
|
|
id: Optional[int]
|
|
series_id: int
|
|
name: str
|
|
season_type: SeasonType
|
|
sort_order: int
|
|
|
|
@classmethod
|
|
def from_row(cls, row: tuple) -> 'Season':
|
|
"""Create Season from database row"""
|
|
return cls(
|
|
id=row[0],
|
|
series_id=row[1],
|
|
name=row[2],
|
|
season_type=SeasonType(row[3]),
|
|
sort_order=row[4]
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class Episode:
|
|
"""Represents an episode"""
|
|
id: Optional[int]
|
|
season_id: int
|
|
episode_code: str # e.g., "01", "01a", "01b"
|
|
title: str
|
|
|
|
# Episode number (overall series number from fernsehserien.de)
|
|
episode_number: Optional[int] = None
|
|
|
|
# Episode ID from fernsehserien.de (e.g., "1828679")
|
|
episode_id: Optional[str] = None
|
|
|
|
# All available air dates
|
|
date_de_tv: Optional[datetime] = None
|
|
date_de_streaming: Optional[datetime] = None
|
|
date_de_home_media: Optional[datetime] = None
|
|
date_de_sync: Optional[datetime] = None
|
|
date_original: Optional[datetime] = None
|
|
|
|
# Comparison date (based on series preference)
|
|
comparison_date: Optional[datetime] = None
|
|
|
|
@classmethod
|
|
def from_row(cls, row: tuple) -> 'Episode':
|
|
"""Create Episode from database row"""
|
|
# Handle old (9/10/11 fields) and new (12 fields) schema
|
|
if len(row) >= 12:
|
|
# New schema with episode_number, episode_id, and date_de_home_media
|
|
return cls(
|
|
id=row[0],
|
|
season_id=row[1],
|
|
episode_number=row[2],
|
|
episode_code=row[3],
|
|
title=row[4],
|
|
episode_id=row[5],
|
|
date_de_tv=_safe_parse_datetime(row[6]),
|
|
date_de_streaming=_safe_parse_datetime(row[7]),
|
|
date_de_home_media=_safe_parse_datetime(row[8]),
|
|
date_de_sync=_safe_parse_datetime(row[9]),
|
|
date_original=_safe_parse_datetime(row[10]),
|
|
comparison_date=_safe_parse_datetime(row[11])
|
|
)
|
|
elif len(row) >= 11:
|
|
# Schema with episode_number and date_de_home_media but no episode_id
|
|
return cls(
|
|
id=row[0],
|
|
season_id=row[1],
|
|
episode_number=row[2],
|
|
episode_code=row[3],
|
|
title=row[4],
|
|
episode_id=None,
|
|
date_de_tv=_safe_parse_datetime(row[5]),
|
|
date_de_streaming=_safe_parse_datetime(row[6]),
|
|
date_de_home_media=_safe_parse_datetime(row[7]),
|
|
date_de_sync=_safe_parse_datetime(row[8]),
|
|
date_original=_safe_parse_datetime(row[9]),
|
|
comparison_date=_safe_parse_datetime(row[10])
|
|
)
|
|
elif len(row) >= 10:
|
|
# Schema with episode_number but without date_de_home_media and episode_id
|
|
return cls(
|
|
id=row[0],
|
|
season_id=row[1],
|
|
episode_number=row[2],
|
|
episode_code=row[3],
|
|
title=row[4],
|
|
episode_id=None,
|
|
date_de_tv=_safe_parse_datetime(row[5]),
|
|
date_de_streaming=_safe_parse_datetime(row[6]),
|
|
date_de_home_media=None,
|
|
date_de_sync=_safe_parse_datetime(row[7]),
|
|
date_original=_safe_parse_datetime(row[8]),
|
|
comparison_date=_safe_parse_datetime(row[9])
|
|
)
|
|
else:
|
|
# Old schema without episode_number, episode_id, and date_de_home_media
|
|
return cls(
|
|
id=row[0],
|
|
season_id=row[1],
|
|
episode_number=None,
|
|
episode_code=row[2],
|
|
title=row[3],
|
|
episode_id=None,
|
|
date_de_tv=_safe_parse_datetime(row[4]),
|
|
date_de_streaming=_safe_parse_datetime(row[5]),
|
|
date_de_home_media=None,
|
|
date_de_sync=_safe_parse_datetime(row[6]),
|
|
date_original=_safe_parse_datetime(row[7]),
|
|
comparison_date=_safe_parse_datetime(row[8])
|
|
)
|
|
|
|
def calculate_comparison_date(self, preference: DatePreference) -> Optional[datetime]:
|
|
"""
|
|
Calculate the comparison date based on preference with fallback logic
|
|
"""
|
|
# Special case: DE_FIRST = earliest German date
|
|
if preference == DatePreference.DE_FIRST:
|
|
german_dates = [
|
|
d for d in [self.date_de_tv, self.date_de_streaming, self.date_de_home_media, self.date_de_sync]
|
|
if d is not None
|
|
]
|
|
if german_dates:
|
|
return min(german_dates)
|
|
# No fallback - return None if no German date available
|
|
return None
|
|
|
|
# Priority order based on preference
|
|
# For German preferences: only use German dates, no fallback to original
|
|
# For ORIGINAL preference: only use original date
|
|
# Priority for German dates: TV → Streaming → Home-Media → Sync
|
|
priority_map = {
|
|
DatePreference.DE_TV: [self.date_de_tv, self.date_de_streaming, self.date_de_home_media, self.date_de_sync],
|
|
DatePreference.DE_STREAMING: [self.date_de_streaming, self.date_de_tv, self.date_de_home_media, self.date_de_sync],
|
|
DatePreference.DE_HOME_MEDIA: [self.date_de_home_media, self.date_de_streaming, self.date_de_tv, self.date_de_sync],
|
|
DatePreference.DE_SYNC: [self.date_de_sync, self.date_de_home_media, self.date_de_streaming, self.date_de_tv],
|
|
DatePreference.ORIGINAL: [self.date_original]
|
|
}
|
|
|
|
dates = priority_map.get(preference, [])
|
|
for date in dates:
|
|
if date is not None:
|
|
return date
|
|
return None
|
|
|
|
|
|
@dataclass
|
|
class Settings:
|
|
"""Application settings"""
|
|
id: Optional[int]
|
|
key: str
|
|
value: str
|
|
|
|
@classmethod
|
|
def from_row(cls, row: tuple) -> 'Settings':
|
|
"""Create Settings from database row"""
|
|
return cls(
|
|
id=row[0],
|
|
key=row[1],
|
|
value=row[2]
|
|
)
|