111 lines
4.0 KiB
Python
111 lines
4.0 KiB
Python
"""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)
|