"""JSON-based subtitle repository implementation.""" from typing import List, Optional, Dict, Any import logging from domain.subtitles.repositories import SubtitleRepository from domain.subtitles.entities import Subtitle from domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset from domain.shared.value_objects import ImdbId, FilePath from ..memory import Memory logger = logging.getLogger(__name__) class JsonSubtitleRepository(SubtitleRepository): """ JSON-based implementation of SubtitleRepository. Stores subtitles in the memory.json file. """ def __init__(self, memory: Memory): """ Initialize repository. Args: memory: Memory instance for persistence """ self.memory = memory def save(self, subtitle: Subtitle) -> None: """Save a subtitle to the repository.""" subtitles = self._load_all() # Add new subtitle (we allow multiple subtitles for same media) subtitles.append(self._to_dict(subtitle)) # Save to memory self.memory.set('subtitles', subtitles) logger.debug(f"Saved subtitle for: {subtitle.media_imdb_id}") def find_by_media( self, media_imdb_id: ImdbId, language: Optional[Language] = None, season: Optional[int] = None, episode: Optional[int] = None ) -> List[Subtitle]: """Find subtitles for a media item.""" subtitles = self._load_all() results = [] for sub_dict in subtitles: # Filter by IMDb ID if sub_dict.get('media_imdb_id') != str(media_imdb_id): continue # Filter by language if specified if language and sub_dict.get('language') != language.value: continue # Filter by season/episode if specified if season is not None and sub_dict.get('season_number') != season: continue if episode is not None and sub_dict.get('episode_number') != episode: continue results.append(self._from_dict(sub_dict)) return results def delete(self, subtitle: Subtitle) -> bool: """Delete a subtitle from the repository.""" subtitles = self._load_all() initial_count = len(subtitles) # Filter out the subtitle (match by file path) subtitles = [ s for s in subtitles if s.get('file_path') != str(subtitle.file_path) ] if len(subtitles) < initial_count: self.memory.set('subtitles', subtitles) logger.debug(f"Deleted subtitle: {subtitle.file_path}") return True return False def _load_all(self) -> List[Dict[str, Any]]: """Load all subtitles from memory.""" return self.memory.get('subtitles', []) def _to_dict(self, subtitle: Subtitle) -> Dict[str, Any]: """Convert Subtitle entity to dict for storage.""" return { 'media_imdb_id': str(subtitle.media_imdb_id), 'language': subtitle.language.value, 'format': subtitle.format.value, 'file_path': str(subtitle.file_path), 'season_number': subtitle.season_number, 'episode_number': subtitle.episode_number, 'timing_offset': subtitle.timing_offset.milliseconds, 'hearing_impaired': subtitle.hearing_impaired, 'forced': subtitle.forced, 'source': subtitle.source, 'uploader': subtitle.uploader, 'download_count': subtitle.download_count, 'rating': subtitle.rating, } def _from_dict(self, data: Dict[str, Any]) -> Subtitle: """Convert dict from storage to Subtitle entity.""" return Subtitle( media_imdb_id=ImdbId(data['media_imdb_id']), language=Language.from_code(data['language']), format=SubtitleFormat.from_extension(data['format']), file_path=FilePath(data['file_path']), season_number=data.get('season_number'), episode_number=data.get('episode_number'), timing_offset=TimingOffset(data.get('timing_offset', 0)), hearing_impaired=data.get('hearing_impaired', False), forced=data.get('forced', False), source=data.get('source'), uploader=data.get('uploader'), download_count=data.get('download_count'), rating=data.get('rating'), )