"""Readers for Curator's *pre-index* metadata layouts. Before the unified pool index (`.Curator.!index`), Tagger / early Curator stored a movie's metadata in a **per-file sidecar** ``.{filename}.!tag`` (JSON) sitting next to the video. When importing a file that came from such a pool we can recover its **ČSFD link** (and title / year) from that sidecar — or from an old ``.Curator.!index`` found by walking up from the file. This is read-only recovery for the import dialog; nothing here writes. """ from __future__ import annotations import json from dataclasses import dataclass from pathlib import Path, PurePosixPath from .pool_index import INDEX_FILENAME SIDECAR_SUFFIX = ".!tag" _MAX_INDEX_WALK_UP = 4 @dataclass class LegacyMeta: """What an old sidecar / index record can still tell us about a movie.""" csfd_url: str | None = None title: str | None = None year: str | None = None # four-digit string source: str = "" # "sidecar" | "index" def _record_to_meta(record: object, source: str) -> LegacyMeta | None: """Pull the useful fields out of a stored movie record (any schema version).""" if not isinstance(record, dict): return None csfd = record.get("csfd_url") or record.get("csfd_link") # new | Tagger-era key title = record.get("title") or None meta_raw = record.get("meta") meta = meta_raw if isinstance(meta_raw, dict) else {} cache_raw = record.get("csfd_cache") cache = cache_raw if isinstance(cache_raw, dict) else {} raw_year = meta.get("year") or cache.get("year") year = ( str(raw_year).strip() if raw_year is not None and str(raw_year).strip().isdigit() else None ) if not (csfd or title or year): return None return LegacyMeta(csfd_url=csfd or None, title=title, year=year, source=source) def _from_sidecar(video_path: Path) -> LegacyMeta | None: sidecar = video_path.parent / f".{video_path.name}{SIDECAR_SUFFIX}" if not sidecar.is_file(): return None try: record = json.loads(sidecar.read_text(encoding="utf-8")) except json.JSONDecodeError, OSError, ValueError: return None return _record_to_meta(record, "sidecar") def _lookup_in_index(movies: dict, video_path: Path, index_dir: Path) -> dict | None: """Find the record for ``video_path`` in an index's ``movies`` map. Tries the exact pool-relative key first, then falls back to a unique basename match (the file may have moved since the index was written). """ try: rel = video_path.relative_to(index_dir).as_posix() if rel in movies: return movies[rel] except ValueError: pass name = video_path.name matches = [v for k, v in movies.items() if PurePosixPath(k).name == name] return matches[0] if len(matches) == 1 else None def _from_legacy_index(video_path: Path) -> LegacyMeta | None: directory = video_path.parent for _ in range(_MAX_INDEX_WALK_UP + 1): index_path = directory / INDEX_FILENAME if index_path.is_file(): try: data = json.loads(index_path.read_text(encoding="utf-8")) except json.JSONDecodeError, OSError, ValueError: return None movies = data.get("movies", data) if isinstance(data, dict) else None if not isinstance(movies, dict): return None record = _lookup_in_index(movies, video_path, directory) return _record_to_meta(record, "index") if record is not None else None if directory.parent == directory: break directory = directory.parent return None def read_legacy_meta(video_path: Path) -> LegacyMeta | None: """Recover ČSFD link / title / year from a pre-index layout, or ``None``. Order: a per-file ``.{name}.!tag`` sidecar next to the video, then a ``.Curator.!index`` found walking up (max 4 levels) from the file. """ video_path = Path(video_path) return _from_sidecar(video_path) or _from_legacy_index(video_path)