"""Pre-Scan-Modul: Liest Disc-TOC ohne Ripping und schlägt Metadaten vor. WIEDERHERGESTELLT 22.07.2026: Beim SoC-Refactoring wurde die echte Implementierung (flache prescan.py) durch einen Stub überschattet — die Metadaten-Preview (Muss-Feature) lieferte seitdem immer „Unknown Disc". Dies ist die echte Logik, bereinigt. """ import subprocess from typing import Dict, List from clients.tmdb import TMDBClient from clients.musicbrainz import MusicBrainzClient from clients.thetvdb import TheTVDBClient from cache import get, set as cache_set from cache.keys import generate_prescan_key class PreScanResult: def __init__( self, disc_type: str = "DVD", title: str = "Unknown", year: int = None, confidence: float = 0.0, metadata: Dict = None, tracks: List[Dict] = None ): self.disc_type = disc_type self.title = title self.year = year self.confidence = confidence self.metadata = metadata or {} self.tracks = tracks or [] def to_dict(self) -> Dict: return { "disc_type": self.disc_type, "title": self.title, "year": self.year, "confidence": self.confidence, "metadata": self.metadata, "tracks": self.tracks } class PreScan: def __init__(self): self.tmdb = TMDBClient() self.musicbrainz = MusicBrainzClient() self.thetvdb = TheTVDBClient() def scan(self, device_path: str) -> PreScanResult: """Führe Pre-Scan durch.""" disc_type = self._detect_disc_type(device_path) toc = self._read_toc(device_path, disc_type) if disc_type == "CD": return self._scan_audio(device_path, toc) return self._scan_video(device_path, toc) def _detect_disc_type(self, device_path: str) -> str: """Erkenne Disc-Typ (CD/DVD/Blu-ray) über die Medien-Größe.""" try: result = subprocess.run( ["isosize", "-x", device_path], capture_output=True, text=True, timeout=5 ) if result.returncode == 0: size = int(result.stdout.strip()) if size < 700 * 1024 * 1024: return "CD" elif size < 15 * 1024 * 1024 * 1024: return "DVD" else: return "Blu-ray" except Exception: pass return "DVD" def _read_toc(self, device_path: str, disc_type: str) -> Dict: """Lese TOC (Table of Contents).""" toc = { "title": None, "year": None, "tracks": [], "duration": 0 } try: if disc_type == "CD": result = subprocess.run( ["cdparanoia", "-Q", device_path], capture_output=True, text=True, timeout=10 ) for line in result.stdout.split('\n'): if 'track' in line.lower(): parts = line.split() if len(parts) >= 4: toc["tracks"].append({ "track_number": parts[1], "title": " ".join(parts[3:]) if len(parts) > 3 else "Track", "duration": 0 }) else: # makemkvcon liest nur den TOC — KEIN Ripping result = subprocess.run( ["makemkvcon", "--minlength=300", "--progress=off", "info", device_path], capture_output=True, text=True, timeout=30 ) for line in result.stdout.split('\n'): if line.startswith('DRV:'): parts = line.split(',') if len(parts) >= 4: toc["title"] = parts[3].strip().strip('"') elif line.startswith('TINFO:'): parts = line.split(',') if len(parts) >= 5: toc["tracks"].append({ "title": parts[4].strip().strip('"') if len(parts) > 4 else "Title", "duration": int(parts[2]) if len(parts) > 2 else 0 }) except Exception as e: print(f"Pre-Scan TOC Error: {e}") return toc def _scan_audio(self, device_path: str, toc: Dict) -> PreScanResult: """Pre-Scan für Audio-CD.""" cache_key = generate_prescan_key(device_path, is_audio=True) cached = get(cache_key) if cached: return PreScanResult(**cached) confidence = 0.5 artist = None album = toc["title"] or "Unknown Album" if " - " in album: parts = album.split(" - ", 1) if len(parts) == 2: artist = parts[0] album = parts[1] if artist: artists = self.musicbrainz.search_artist(artist) if artists: confidence = 0.8 releases = self.musicbrainz.search_release(album, artist) if releases: confidence = 0.9 release_id = releases[0]["id"] release_info = self.musicbrainz.get_release_info(release_id) if release_info: for medium in release_info.get("media", []): for track in medium.get("tracks", []): toc["tracks"].append({ "track_number": track.get("position", 0), "title": track.get("title", "Unknown"), "duration": track.get("duration", 0) // 1000 }) if not artist: movies = self.tmdb.search_movie(album) if movies: confidence = 0.7 movie_details = self.tmdb.get_movie_details(movies[0]["id"]) if movie_details: album = f"Soundtrack - {movie_details.get('title', album)}" toc["title"] = album result = PreScanResult( disc_type="CD", title=album, tracks=toc["tracks"], confidence=confidence ) cache_set(cache_key, result.to_dict()) return result def _scan_video(self, device_path: str, toc: Dict) -> PreScanResult: """Pre-Scan für DVD/Blu-ray.""" cache_key = generate_prescan_key(device_path, is_audio=False) cached = get(cache_key) if cached: return PreScanResult(**cached) title = toc["title"] or "Unknown Title" confidence = 0.0 metadata = {} matched = False movies = self.tmdb.search_movie(title) if movies: for movie in movies: if movie.get("title", "").lower() == title.lower(): confidence = 0.95 movie_details = self.tmdb.get_movie_details(movie["id"]) if movie_details: metadata = { "type": "movie", "id": movie["id"], "title": movie_details.get("title", title), "year": int(movie_details.get("release_date", "0")[:4]) if movie_details.get("release_date") else None, "overview": movie_details.get("overview", ""), "poster_path": movie_details.get("poster_path", ""), "backdrop_path": movie_details.get("backdrop_path", ""), "runtime": movie_details.get("runtime", 0), "genres": [g["name"] for g in movie_details.get("genres", [])] } matched = True break if not matched: tv_shows = self.tmdb.search_tv(title) if tv_shows: for show in tv_shows: if show.get("name", "").lower() == title.lower(): confidence = 0.9 tv_details = self.tmdb.get_tv_details(show["id"]) if tv_details: metadata = { "type": "tv", "id": show["id"], "title": tv_details.get("name", title), "year": int(tv_details.get("first_air_date", "0")[:4]) if tv_details.get("first_air_date") else None, "overview": tv_details.get("overview", ""), "poster_path": tv_details.get("poster_path", ""), "backdrop_path": tv_details.get("backdrop_path", ""), "genres": [g["name"] for g in tv_details.get("genres", [])] } matched = True break if not matched: confidence = 0.3 metadata = { "type": "unknown", "title": title, "year": None } result = PreScanResult( disc_type=toc.get("disc_type", "DVD"), title=title, year=metadata.get("year"), confidence=confidence, metadata=metadata, tracks=toc.get("tracks", []) ) cache_set(cache_key, result.to_dict()) return result