"""Kodi/Jellyfin sidecar writer — NFO building + the artwork-set write plan. Pure logic (XML + plan) with the filesystem injected, so it runs without disk or network. """ from __future__ import annotations import os from core.video import sidecars class FakeFS: def __init__(self, dirs=None): self.dirs = {k: list(v) for k, v in (dirs or {}).items()} self.made = [] self.saved = [] # (url, dst) — artwork self.texts = [] # (path, content) — nfo def list_dir(self, path): return self.dirs.get(str(path), []) def makedirs(self, path): self.made.append(str(path)) def save_url(self, url, dst): self.saved.append((url, dst)) def write_text(self, path, content): self.texts.append((path, content)) _MOVIE = { "title": "The Matrix", "year": "1999", "overview": "A hacker learns the truth.", "tagline": "Free your mind.", "runtime_minutes": 136, "studio": "Warner Bros.", "release_date": "1999-03-31", "rating": 8.2, "genres": ["Action", "Science Fiction"], "tmdb_id": 603, "imdb_id": "tt0133093", "poster_url": "http://img/p.jpg", "backdrop_url": "http://img/b.jpg", "logo": "http://img/l.png", "cast": [{"name": "Keanu Reeves", "character": "Neo"}, {"name": "Carrie-Anne Moss", "character": "Trinity"}], } _SHOW = { "title": "Breaking Bad", "year": "2008", "overview": "A teacher cooks.", "network": "AMC", "first_air_date": "2008-01-20", "rating": 9.5, "genres": ["Drama"], "tmdb_id": 1396, "tvdb_id": 81189, "imdb_id": "tt0903747", "poster_url": "http://img/bbp.jpg", "backdrop_url": "http://img/bbb.jpg", "logo": "http://img/bbl.png", "_seasons": [{"season_number": 1, "poster_url": "http://img/s1.jpg"}, {"season_number": 2, "poster_url": "http://img/s2.jpg"}], } # ── NFO building ────────────────────────────────────────────────────────────── def test_movie_nfo_has_the_key_fields(): xml = sidecars.nfo_movie(_MOVIE) assert xml.startswith("" in xml and "" in xml assert "The Matrix" in xml assert "1999" in xml assert "A hacker learns the truth." in xml assert "Action" in xml and "Science Fiction" in xml assert '603' in xml assert 'tt0133093' in xml assert "Keanu Reeves" in xml and "Neo" in xml def test_tvshow_nfo_uses_show_fields(): xml = sidecars.nfo_tvshow(_SHOW) assert "" in xml and "AMC" in xml # network → studio assert "2008-01-20" in xml assert '1396' in xml def test_nfo_omits_absent_fields_and_escapes(): xml = sidecars.nfo_movie({"title": "A & B ", "tmdb_id": 1}) assert "A & B <x>" in xml assert "" not in xml and "" not in xml # absent → omitted, not blank # ── write plan ──────────────────────────────────────────────────────────────── def test_plan_gates_on_settings(): none = sidecars.plan_sidecars("movie", _MOVIE, {}) # both off assert none["nfo"] is None and none["art"] == [] both = sidecars.plan_sidecars("movie", _MOVIE, {"save_artwork": True, "write_nfo": True}) assert both["nfo"][0] == "movie.nfo" names = [n for _u, n in both["art"]] assert names == ["poster.jpg", "fanart.jpg", "clearlogo.png"] def test_show_plan_includes_season_posters_and_tvshow_nfo(): plan = sidecars.plan_sidecars("episode", _SHOW, {"save_artwork": True, "write_nfo": True}) assert plan["nfo"][0] == "tvshow.nfo" names = [n for _u, n in plan["art"]] assert "season01-poster.jpg" in names and "season02-poster.jpg" in names # ── write (idempotent, best-effort) ─────────────────────────────────────────── def test_write_emits_files_and_skips_existing(): fs = FakeFS(dirs={"/lib/movies/The Matrix (1999)": ["poster.jpg"]}) # poster already there sidecars.write("/lib/movies/The Matrix (1999)", "movie", _MOVIE, {"save_artwork": True, "write_nfo": True}, fs) art = [n for _u, n in fs.saved] assert "fanart.jpg" in [os.path.basename(p) for p in art] assert "poster.jpg" not in [os.path.basename(p) for p in art] # skipped (already present) assert fs.texts and fs.texts[0][0].endswith("movie.nfo") def test_write_for_resolves_movie_folder_and_show_root(): fs = FakeFS() sidecars.write_for("/lib/movies/The Matrix (1999)/The Matrix (1999) Bluray-1080p.mkv", "movie", "http://img/p.jpg", _MOVIE, {"save_artwork": True}, fs) assert any(d.startswith("/lib/movies/The Matrix (1999)/") for _u, d in fs.saved) fs2 = FakeFS() sidecars.write_for("/lib/tv/Breaking Bad/Season 01/Breaking Bad - S01E01.mkv", "episode", "http://img/bbp.jpg", _SHOW, {"save_artwork": True}, fs2) # show-level art lands in the show ROOT, not the Season folder assert all("/Season 01/" not in d for _u, d in fs2.saved) assert any(d == os.path.join("/lib/tv", "Breaking Bad", "poster.jpg") for _u, d in fs2.saved) def test_write_for_poster_only_when_no_detail(): # detail fetch unavailable → still drops the poster from the download-row url fs = FakeFS() sidecars.write_for("/lib/movies/X (2020)/X (2020).mkv", "movie", "http://img/p.jpg", None, {"save_artwork": True}, fs) assert [os.path.basename(d) for _u, d in fs.saved] == ["poster.jpg"]