Add movie-keyed pool index with multi-version movies, ČSFD title bridge, subtitles and miniDLNA library scoping
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
"""Tests for the movie object and the pool layout it derives (src.core.movie)."""
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from src.core.movie import (
|
||||
DEFAULT_VERSION_LABEL,
|
||||
MediaFile,
|
||||
Movie,
|
||||
identity_key,
|
||||
media_filename,
|
||||
normalize_3d_label,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("label,expected", [
|
||||
("HSBS", "HSBS"), ("hsbs", "HSBS"), ("3D HSBS", "HSBS"),
|
||||
("3d.hsbs", "HSBS"), ("3D_FTAB", "FTAB"), ("3d-mvc", "MVC"),
|
||||
("4K", None), ("Director's Cut", None), ("", None), (None, None),
|
||||
])
|
||||
def test_normalize_3d_label(label, expected):
|
||||
assert normalize_3d_label(label) == expected
|
||||
|
||||
|
||||
class TestMediaFilename:
|
||||
def test_no_label_is_the_bare_stem(self):
|
||||
assert media_filename("Matrix (1999)", "", ".mkv", grouped=False) == \
|
||||
"Matrix (1999).mkv"
|
||||
|
||||
def test_3d_label_uses_jellyfins_documented_form(self):
|
||||
assert media_filename("Avatar (2009)", "3D HSBS", ".mkv", grouped=False) == \
|
||||
"Avatar (2009).3D.HSBS.mkv"
|
||||
# same spelling grouped — "." is a valid separator after the folder name
|
||||
assert media_filename("Avatar (2009)", "hsbs", ".mkv", grouped=True) == \
|
||||
"Avatar (2009).3D.HSBS.mkv"
|
||||
|
||||
def test_plain_label_uses_dash_form(self):
|
||||
assert media_filename("Avatar (2009)", "4K", ".mkv", grouped=True) == \
|
||||
"Avatar (2009) - 4K.mkv"
|
||||
|
||||
def test_grouped_empty_label_gets_a_default(self):
|
||||
assert media_filename("Avatar (2009)", "", ".mkv", grouped=True) == \
|
||||
f"Avatar (2009) - {DEFAULT_VERSION_LABEL}.mkv"
|
||||
|
||||
|
||||
class TestIdentityKey:
|
||||
def test_tmdb_id_wins(self):
|
||||
assert identity_key(603, "Matrix", "1999") == "tmdb-603"
|
||||
assert identity_key("603", "cokoli", None) == "tmdb-603"
|
||||
|
||||
def test_slug_fallback_without_id(self):
|
||||
assert identity_key(None, "Matrix", "1999") == "name-matrix-1999"
|
||||
|
||||
def test_slug_folds_diacritics(self):
|
||||
assert identity_key(None, "Pelíšky", "1999") == "name-pelisky-1999"
|
||||
|
||||
def test_slug_without_year(self):
|
||||
assert identity_key(None, "Nějaký film", None) == "name-nejaky-film"
|
||||
|
||||
|
||||
def _movie(**kwargs) -> Movie:
|
||||
base = dict(key="tmdb-19995", title="Avatar", year="2009", tmdb_id=19995,
|
||||
library="Filmy")
|
||||
base.update(kwargs)
|
||||
return Movie(**base)
|
||||
|
||||
|
||||
class TestMovieShape:
|
||||
def test_stem_is_the_canonical_form(self):
|
||||
assert _movie().stem == "Avatar (2009) [tmdbid-19995]"
|
||||
|
||||
def test_stem_falls_back_to_title_without_year(self):
|
||||
assert _movie(year=None, tmdb_id=None).stem == "Avatar"
|
||||
|
||||
def test_single_video_is_not_grouped(self):
|
||||
m = _movie(media=[MediaFile(Path("/pool/Filmy/x.mkv"))])
|
||||
assert m.is_grouped is False
|
||||
|
||||
def test_two_videos_are_grouped(self):
|
||||
m = _movie(media=[MediaFile(Path("/a.mkv"), "3D HSBS"),
|
||||
MediaFile(Path("/b.mkv"), "Výchozí")])
|
||||
assert m.is_grouped is True
|
||||
|
||||
def test_primary_prefers_the_plain_version(self):
|
||||
m = _movie(media=[MediaFile(Path("/a.mkv"), "3D HSBS"),
|
||||
MediaFile(Path("/b.mkv"), "")])
|
||||
assert m.primary.label == ""
|
||||
|
||||
|
||||
class TestDesiredLayout:
|
||||
def test_one_video_stays_flat_in_the_library(self, tmp_path):
|
||||
m = _movie(media=[MediaFile(tmp_path / "kdekoli.mkv", "")])
|
||||
(media, target), = m.desired_layout(tmp_path)
|
||||
assert target == tmp_path / "Filmy" / "Avatar (2009) [tmdbid-19995].mkv"
|
||||
|
||||
def test_one_3d_video_keeps_the_3d_tag_flat(self, tmp_path):
|
||||
m = _movie(media=[MediaFile(tmp_path / "x.mkv", "3D HSBS")])
|
||||
(_m, target), = m.desired_layout(tmp_path)
|
||||
assert target.name == "Avatar (2009) [tmdbid-19995].3D.HSBS.mkv"
|
||||
assert target.parent == tmp_path / "Filmy"
|
||||
|
||||
def test_two_videos_move_into_the_movie_folder(self, tmp_path):
|
||||
m = _movie(media=[MediaFile(tmp_path / "a.mkv", ""),
|
||||
MediaFile(tmp_path / "b.mkv", "3D HSBS")])
|
||||
targets = {mf.label: path for mf, path in m.desired_layout(tmp_path)}
|
||||
folder = tmp_path / "Filmy" / "Avatar (2009) [tmdbid-19995]"
|
||||
assert targets[""] == folder / \
|
||||
f"Avatar (2009) [tmdbid-19995] - {DEFAULT_VERSION_LABEL}.mkv"
|
||||
assert targets["3D HSBS"] == folder / "Avatar (2009) [tmdbid-19995].3D.HSBS.mkv"
|
||||
|
||||
def test_layout_follows_a_retitle(self, tmp_path):
|
||||
"""Renaming the movie is just a different desired layout — the thing
|
||||
that used to be impossible for a grouped movie."""
|
||||
m = _movie(title="Avatar: Nová verze",
|
||||
media=[MediaFile(tmp_path / "a.mkv", "A"),
|
||||
MediaFile(tmp_path / "b.mkv", "B")])
|
||||
folders = {path.parent for _mf, path in m.desired_layout(tmp_path)}
|
||||
assert folders == {
|
||||
tmp_path / "Filmy" / "Avatar: Nová verze (2009) [tmdbid-19995]"}
|
||||
|
||||
def test_layout_follows_a_library_move(self, tmp_path):
|
||||
m = _movie(library="Kids", media=[MediaFile(tmp_path / "a.mkv", "")])
|
||||
(_m, target), = m.desired_layout(tmp_path)
|
||||
assert target.parent == tmp_path / "Kids"
|
||||
|
||||
|
||||
class TestRecordRoundTrip:
|
||||
def test_to_and_from_record(self, tmp_path):
|
||||
m = _movie(
|
||||
media=[MediaFile(tmp_path / "Filmy" / "a.mkv", "3D HSBS")],
|
||||
csfd_url="https://www.csfd.cz/film/1/", date="2026-01-01",
|
||||
attributes={"collection_sort": "01"}, meta={"year": "2009"})
|
||||
|
||||
record = m.to_record(tmp_path)
|
||||
assert record["media"] == [
|
||||
{"role": "video", "label": "3D HSBS", "file": "Filmy/a.mkv"}]
|
||||
|
||||
back = Movie.from_record("tmdb-19995", record, tmp_path)
|
||||
assert back.title == "Avatar"
|
||||
assert back.year == "2009"
|
||||
assert back.tmdb_id == 19995
|
||||
assert back.library == "Filmy"
|
||||
assert back.csfd_url == "https://www.csfd.cz/film/1/"
|
||||
assert back.attributes == {"collection_sort": "01"}
|
||||
assert back.meta == {"year": "2009"}
|
||||
assert back.media[0].path == tmp_path / "Filmy" / "a.mkv"
|
||||
assert back.media[0].format_3d == "HSBS"
|
||||
|
||||
def test_from_record_builds_tags_via_tagmanager(self, tmp_path):
|
||||
from src.core.tag_manager import TagManager
|
||||
record = {"title": "Avatar", "media": [], "tags": ["Žánr/Sci-Fi"]}
|
||||
|
||||
back = Movie.from_record("tmdb-19995", record, tmp_path, TagManager())
|
||||
|
||||
assert {t.full_path for t in back.tags} == {"Žánr/Sci-Fi"}
|
||||
Reference in New Issue
Block a user