Adopt "Title (YYYY)" pool naming and clean Filmotéka link names
This commit is contained in:
+1
-1
@@ -271,7 +271,7 @@ class TestApplyCsfdTags:
|
||||
movie_file.csfd_cache = {"year": 1962, "rating": 75}
|
||||
ctx = movie_file.name_context()
|
||||
assert ctx["title"] == "Dr. No"
|
||||
assert ctx["year"] == 1962
|
||||
assert ctx["year"] == "1962" # coerced to str for uniform template fields
|
||||
assert ctx["rating"] == 75
|
||||
assert ctx["ext"] == movie_file.file_path.suffix
|
||||
assert ctx["filename"] == movie_file.filename
|
||||
|
||||
@@ -409,6 +409,69 @@ class TestPoolManagement:
|
||||
with pytest.raises(ValueError):
|
||||
file_manager.rename_movie(movie, " ")
|
||||
|
||||
def test_import_movie_with_year_uses_convention(self, file_manager, tmp_path):
|
||||
file_manager.set_pool_dir(tmp_path / "pool")
|
||||
source = tmp_path / "raw.mkv"
|
||||
source.write_bytes(b"x" * 10)
|
||||
|
||||
movie = file_manager.import_movie(source, "Matrix", year=1999)
|
||||
|
||||
assert movie.file_path.name == "Matrix (1999).mkv"
|
||||
assert movie.title == "Matrix" # clean title, no year
|
||||
|
||||
def test_import_movie_rejects_invalid_year(self, file_manager, tmp_path):
|
||||
file_manager.set_pool_dir(tmp_path / "pool")
|
||||
(tmp_path / "a.mkv").write_bytes(b"a")
|
||||
with pytest.raises(ValueError):
|
||||
file_manager.import_movie(tmp_path / "a.mkv", "Matrix", year="99")
|
||||
|
||||
def test_import_same_title_different_year_coexist(self, file_manager, tmp_path):
|
||||
file_manager.set_pool_dir(tmp_path / "pool")
|
||||
(tmp_path / "a.mkv").write_bytes(b"a")
|
||||
(tmp_path / "b.mkv").write_bytes(b"b")
|
||||
first = file_manager.import_movie(tmp_path / "a.mkv", "Solaris", year=1972)
|
||||
second = file_manager.import_movie(tmp_path / "b.mkv", "Solaris", year=2002)
|
||||
|
||||
assert first.file_path.name == "Solaris (1972).mkv"
|
||||
assert second.file_path.name == "Solaris (2002).mkv" # no collision/suffix
|
||||
|
||||
def test_rename_to_canonical_from_csfd_year(self, file_manager, tmp_path):
|
||||
file_manager.set_pool_dir(tmp_path / "pool")
|
||||
(tmp_path / "raw.mkv").write_bytes(b"x")
|
||||
movie = file_manager.import_movie(tmp_path / "raw.mkv", "Matrix") # plain
|
||||
assert movie.file_path.name == "Matrix.mkv"
|
||||
|
||||
movie.csfd_cache = {"year": 1999}
|
||||
renamed = file_manager.rename_to_canonical(movie)
|
||||
|
||||
assert renamed is not None
|
||||
assert movie.file_path.name == "Matrix (1999).mkv"
|
||||
assert movie.title == "Matrix"
|
||||
|
||||
def test_rename_to_canonical_skips_without_year(self, file_manager, tmp_path):
|
||||
file_manager.set_pool_dir(tmp_path / "pool")
|
||||
(tmp_path / "raw.mkv").write_bytes(b"x")
|
||||
movie = file_manager.import_movie(tmp_path / "raw.mkv", "Unknown")
|
||||
|
||||
assert file_manager.rename_to_canonical(movie) is None
|
||||
assert movie.file_path.name == "Unknown.mkv" # untouched
|
||||
|
||||
def test_rename_all_to_canonical_reports(self, file_manager, tmp_path):
|
||||
file_manager.set_pool_dir(tmp_path / "pool")
|
||||
for n in ("a.mkv", "b.mkv", "c.mkv"):
|
||||
(tmp_path / n).write_bytes(b"x")
|
||||
with_year = file_manager.import_movie(tmp_path / "a.mkv", "Matrix")
|
||||
with_year.csfd_cache = {"year": 1999}
|
||||
already = file_manager.import_movie(tmp_path / "b.mkv", "Solaris", year=1972)
|
||||
_no_year = file_manager.import_movie(tmp_path / "c.mkv", "Mystery")
|
||||
|
||||
result = file_manager.rename_all_to_canonical(file_manager.filelist)
|
||||
|
||||
assert result["renamed"] == [("Matrix.mkv", "Matrix (1999).mkv")]
|
||||
assert result["unchanged"] == 1 # Solaris (1972) already canonical
|
||||
assert result["skipped_no_year"] == ["Mystery.mkv"]
|
||||
assert already.file_path.name == "Solaris (1972).mkv"
|
||||
|
||||
def test_load_pool_movies_reads_from_index(self, file_manager, tmp_path):
|
||||
file_manager.set_pool_dir(tmp_path / "pool")
|
||||
source = tmp_path / "raw.mkv"
|
||||
|
||||
@@ -208,6 +208,78 @@ class TestHardlinkManager:
|
||||
created2, _, removed2, _ = manager.sync_structure([f], category_roots=roots)
|
||||
assert (created2, removed2) == (0, 0)
|
||||
|
||||
def test_link_name_strips_year_by_default(
|
||||
self, tmp_path, tag_manager
|
||||
):
|
||||
"""A pooled 'Title (YYYY).ext' hardlinks into the tag tree as 'Title.ext'."""
|
||||
source = tmp_path / "source"
|
||||
source.mkdir()
|
||||
(source / "Solaris (1972).mkv").write_text("x")
|
||||
f = File(source / "Solaris (1972).mkv", tag_manager)
|
||||
f.tags.clear()
|
||||
f.title = "Solaris"
|
||||
f.add_tag(Tag("žánr", "Sci-Fi"))
|
||||
|
||||
output = tmp_path / "output"
|
||||
manager = HardlinkManager(output)
|
||||
manager.create_structure_for_files([f], category_roots={"žánr": ""})
|
||||
|
||||
assert (output / "Sci-Fi" / "Solaris.mkv").exists() # year stripped
|
||||
assert not (output / "Sci-Fi" / "Solaris (1972).mkv").exists()
|
||||
|
||||
def test_link_name_keeps_year_on_collision(
|
||||
self, tmp_path, tag_manager
|
||||
):
|
||||
"""Two same-title films in one folder keep the year to disambiguate."""
|
||||
source = tmp_path / "source"
|
||||
source.mkdir()
|
||||
(source / "Solaris (1972).mkv").write_text("a")
|
||||
(source / "Solaris (2002).mkv").write_text("b")
|
||||
f1 = File(source / "Solaris (1972).mkv", tag_manager)
|
||||
f1.tags.clear()
|
||||
f1.title = "Solaris"
|
||||
f1.csfd_cache = {"year": 1972}
|
||||
f1.add_tag(Tag("žánr", "Sci-Fi"))
|
||||
f1.add_tag(Tag("rok", "1972"))
|
||||
f2 = File(source / "Solaris (2002).mkv", tag_manager)
|
||||
f2.tags.clear()
|
||||
f2.title = "Solaris"
|
||||
f2.csfd_cache = {"year": 2002}
|
||||
f2.add_tag(Tag("žánr", "Sci-Fi"))
|
||||
f2.add_tag(Tag("rok", "2002"))
|
||||
|
||||
output = tmp_path / "output"
|
||||
manager = HardlinkManager(output)
|
||||
roots = {"žánr": "", "rok": "Dle roku"}
|
||||
manager.create_structure_for_files([f1, f2], category_roots=roots)
|
||||
|
||||
# Same genre folder → clash → both keep the year
|
||||
assert (output / "Sci-Fi" / "Solaris (1972).mkv").exists()
|
||||
assert (output / "Sci-Fi" / "Solaris (2002).mkv").exists()
|
||||
assert not (output / "Sci-Fi" / "Solaris.mkv").exists()
|
||||
# Separate year folders → no clash → clean name
|
||||
assert (output / "Dle roku" / "1972" / "Solaris.mkv").exists()
|
||||
assert (output / "Dle roku" / "2002" / "Solaris.mkv").exists()
|
||||
|
||||
def test_clean_naming_cleanup_is_consistent(
|
||||
self, tmp_path, tag_manager
|
||||
):
|
||||
"""Clean-named links are stable: a second sync removes/creates nothing."""
|
||||
source = tmp_path / "source"
|
||||
source.mkdir()
|
||||
(source / "Solaris (1972).mkv").write_text("x")
|
||||
f = File(source / "Solaris (1972).mkv", tag_manager)
|
||||
f.tags.clear()
|
||||
f.title = "Solaris"
|
||||
f.add_tag(Tag("žánr", "Sci-Fi"))
|
||||
roots = {"žánr": ""}
|
||||
|
||||
output = tmp_path / "output"
|
||||
manager = HardlinkManager(output)
|
||||
manager.sync_structure([f], category_roots=roots)
|
||||
created, _, removed, _ = manager.sync_structure([f], category_roots=roots)
|
||||
assert (created, removed) == (0, 0)
|
||||
|
||||
def test_sync_removes_root_genre_folder_when_last_movie_drops_tag(
|
||||
self, temp_source_dir, temp_output_dir, tag_manager
|
||||
):
|
||||
@@ -263,8 +335,8 @@ class TestHardlinkManager:
|
||||
|
||||
# Templated name inside the collection folder
|
||||
assert (temp_output_dir / "Dle kolekce" / "James Bond" / "1962 - Dr. No.txt").exists()
|
||||
# Other categories keep the pool filename
|
||||
assert (temp_output_dir / "Akční" / "file1.txt").exists()
|
||||
# Other categories use the default clean title (no year, not the pool name)
|
||||
assert (temp_output_dir / "Akční" / "Dr. No.txt").exists()
|
||||
|
||||
def test_filename_template_cleanup_is_consistent(
|
||||
self, temp_source_dir, temp_output_dir, tag_manager
|
||||
@@ -421,30 +493,27 @@ class TestHardlinkManager:
|
||||
assert fail2 == 0
|
||||
|
||||
def test_unique_name_on_conflict(self, temp_source_dir, temp_output_dir, tag_manager):
|
||||
"""Test že při konfliktu (jiný soubor) se použije unikátní jméno"""
|
||||
# Create first file
|
||||
"""Two different movies with the same clean name (no year to tell them
|
||||
apart) in one folder get distinct links via a numeric suffix."""
|
||||
f1 = File(temp_source_dir / "file1.txt", tag_manager)
|
||||
f1.tags.clear()
|
||||
f1.add_tag(Tag("test", "tag"))
|
||||
|
||||
manager = HardlinkManager(temp_output_dir)
|
||||
manager.create_structure_for_files([f1])
|
||||
|
||||
# Create different file with same name in different location
|
||||
source2 = temp_source_dir / "subdir"
|
||||
source2.mkdir()
|
||||
(source2 / "file1.txt").write_text("different content")
|
||||
|
||||
f2 = File(source2 / "file1.txt", tag_manager)
|
||||
f2.tags.clear()
|
||||
f2.add_tag(Tag("test", "tag"))
|
||||
|
||||
# Should create file1_1.txt
|
||||
manager2 = HardlinkManager(temp_output_dir)
|
||||
success, fail = manager2.create_structure_for_files([f2])
|
||||
# One generation over both files → the plan disambiguates the clash.
|
||||
manager = HardlinkManager(temp_output_dir)
|
||||
success, fail = manager.create_structure_for_files([f1, f2])
|
||||
|
||||
assert success == 1
|
||||
assert (temp_output_dir / "test" / "tag" / "file1_1.txt").exists()
|
||||
assert success == 2 and fail == 0
|
||||
folder = temp_output_dir / "test" / "tag"
|
||||
assert (folder / "file1.txt").exists()
|
||||
assert (folder / "file1_2.txt").exists()
|
||||
|
||||
def test_czech_characters_in_tags(self, temp_source_dir, temp_output_dir, tag_manager):
|
||||
"""Test českých znaků v názvech tagů"""
|
||||
@@ -746,9 +815,11 @@ class TestEdgeCases:
|
||||
"""Test souboru se speciálními znaky v názvu"""
|
||||
source = tmp_path / "source"
|
||||
source.mkdir()
|
||||
(source / "file with spaces (2024).txt").write_text("content")
|
||||
# Parentheses that are NOT a four-digit year must survive verbatim
|
||||
# (a trailing "(YYYY)" would be stripped by the naming rule).
|
||||
(source / "file with spaces (part 2).txt").write_text("content")
|
||||
|
||||
f = File(source / "file with spaces (2024).txt", tag_manager)
|
||||
f = File(source / "file with spaces (part 2).txt", tag_manager)
|
||||
f.tags.clear()
|
||||
f.add_tag(Tag("test", "tag"))
|
||||
|
||||
@@ -759,7 +830,7 @@ class TestEdgeCases:
|
||||
success, fail = manager.create_structure_for_files([f])
|
||||
|
||||
assert success == 1
|
||||
assert (output / "test" / "tag" / "file with spaces (2024).txt").exists()
|
||||
assert (output / "test" / "tag" / "file with spaces (part 2).txt").exists()
|
||||
|
||||
def test_empty_category_filter(self, tmp_path, tag_manager):
|
||||
"""Test s prázdným seznamem kategorií"""
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
"""Tests for the pool naming convention helpers (src.core.naming)."""
|
||||
import pytest
|
||||
|
||||
from src.core.naming import (
|
||||
canonical_pool_stem,
|
||||
is_canonical_pool_stem,
|
||||
is_valid_year,
|
||||
parse_pool_stem,
|
||||
year_from_filename,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("year,ok", [
|
||||
("1968", True), (1968, True), ("968", False), ("19688", False),
|
||||
("abcd", False), ("", False), (" 1999 ", True),
|
||||
])
|
||||
def test_is_valid_year(year, ok):
|
||||
assert is_valid_year(year) is ok
|
||||
|
||||
|
||||
def test_canonical_pool_stem_builds_title_year():
|
||||
assert canonical_pool_stem("Matrix", 1999) == "Matrix (1999)"
|
||||
assert canonical_pool_stem(" Matrix ", "1999") == "Matrix (1999)"
|
||||
|
||||
|
||||
def test_canonical_pool_stem_rejects_bad_year():
|
||||
with pytest.raises(ValueError):
|
||||
canonical_pool_stem("Matrix", "99")
|
||||
|
||||
|
||||
def test_canonical_pool_stem_rejects_empty_title():
|
||||
with pytest.raises(ValueError):
|
||||
canonical_pool_stem(" ", "1999")
|
||||
|
||||
|
||||
def test_parse_pool_stem_roundtrip():
|
||||
assert parse_pool_stem("Matrix (1999)") == ("Matrix", 1999)
|
||||
# title keeps its own parentheses; only the trailing year is peeled off
|
||||
assert parse_pool_stem("Já, robot (2004)") == ("Já, robot", 2004)
|
||||
assert parse_pool_stem("Kurz (special) (2019)") == ("Kurz (special)", 2019)
|
||||
|
||||
|
||||
def test_parse_pool_stem_non_canonical():
|
||||
assert parse_pool_stem("Matrix") is None
|
||||
assert parse_pool_stem("Matrix (99)") is None
|
||||
assert parse_pool_stem("Matrix 1999") is None
|
||||
|
||||
|
||||
def test_is_canonical_pool_stem():
|
||||
assert is_canonical_pool_stem("Matrix (1999)")
|
||||
assert not is_canonical_pool_stem("Matrix")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name,year", [
|
||||
("Matrix.1999.1080p.BluRay.mkv", "1999"),
|
||||
("Šíleně smutná princezna (1968).mkv", "1968"),
|
||||
("Movie 2001 2010 remux.mkv", "2010"), # last standalone year wins
|
||||
("No Year Here.mkv", None),
|
||||
("Film 12345.mkv", None), # not a bare 4-digit year
|
||||
])
|
||||
def test_year_from_filename(name, year):
|
||||
assert year_from_filename(name) == year
|
||||
Reference in New Issue
Block a user