Add movie-keyed pool index with multi-version movies, ČSFD title bridge, subtitles and miniDLNA library scoping

This commit is contained in:
2026-09-13 17:16:48 +02:00
parent c0ad1c1553
commit c399a6aed3
26 changed files with 4635 additions and 221 deletions
+138 -16
View File
@@ -2,9 +2,12 @@
import pytest
from src.core.series import (
collect_episode_files,
episode_filename,
is_canonical_episode,
is_season_folder,
parse_episode,
sanitize_title,
scan_series,
season_folder_name,
series_folder_stem,
@@ -29,13 +32,29 @@ def test_season_folder_name():
assert season_folder_name(12) == "Season 12"
def test_episode_filename_keeps_original_name():
assert episode_filename(1, 2, "bb.pilot.hdtv", ".mkv") == "S01E02 - bb.pilot.hdtv.mkv"
# already-canonical stem → no doubled prefix
assert episode_filename(1, 2, "S01E02 - bb.pilot.hdtv", ".mkv") == \
"S01E02 - bb.pilot.hdtv.mkv"
# stem that was only the code → collapses to just the code
assert episode_filename(3, 4, "S03E04", ".mkv") == "S03E04.mkv"
def test_episode_filename_is_code_plus_series_title():
assert episode_filename(1, 2, "Most!", ".mkv") == "S01E02 - Most!.mkv"
assert episode_filename(0, 3, "Most!", ".mkv") == "S00E03 - Most!.mkv"
# release string is NOT kept — only the code + series title
assert episode_filename(12, 145, "Breaking Bad", ".mkv") == \
"S12E145 - Breaking Bad.mkv"
# empty / unusable title → just the code
assert episode_filename(3, 4, "", ".mkv") == "S03E04.mkv"
assert episode_filename(3, 4, "/", ".mkv") == "S03E04.mkv"
def test_sanitize_title():
assert sanitize_title("Most!") == "Most!"
assert sanitize_title('A: B / C D') == "A B C D"
assert sanitize_title(" trailing. ") == "trailing"
def test_is_season_folder():
assert is_season_folder("Season 01")
assert is_season_folder("Season 1")
assert is_season_folder("Specials")
assert not is_season_folder("Extras")
assert not is_season_folder("Season")
def test_series_folder_stem():
@@ -47,12 +66,15 @@ def test_series_folder_stem():
def test_is_canonical_episode(tmp_path):
folder = tmp_path / "Breaking Bad (2008) [tmdbid-1396]" / "Season 01"
folder.mkdir(parents=True)
good = folder / "S01E02 - bb.pilot.mkv"
good = folder / "S01E02 - Breaking Bad.mkv"
good.write_bytes(b"x")
bad = folder / "bb.s01e02.mkv"
bad.write_bytes(b"x")
assert is_canonical_episode(good)
assert not is_canonical_episode(bad)
wrong_body = folder / "S01E02 - old release.mkv"
wrong_body.write_bytes(b"x")
assert is_canonical_episode(good, "Breaking Bad")
assert not is_canonical_episode(bad, "Breaking Bad")
assert not is_canonical_episode(wrong_body, "Breaking Bad")
def test_scan_series(tmp_path):
@@ -73,6 +95,26 @@ def test_scan_series(tmp_path):
assert list(seasons.keys()) == [1] and len(seasons[1]) == 2
def test_collect_episode_files_walks_subfolders(tmp_path):
root = tmp_path / "Breaking.Bad.COMPLETE"
(root / "Season 01").mkdir(parents=True)
(root / "Season 02").mkdir(parents=True)
(root / "Season 01" / "BB S01E01.mkv").write_bytes(b"x")
(root / "Season 01" / "BB S01E02.mkv").write_bytes(b"x")
(root / "Season 01" / "BB S01E02.srt").write_bytes(b"x") # sidecar → skipped
(root / "Season 02" / "BB S02E01.avi").write_bytes(b"x")
(root / "readme.nfo").write_bytes(b"x") # sidecar → skipped
files = collect_episode_files(root)
assert [f.name for f in files] == [
"BB S01E01.mkv", "BB S01E02.mkv", "BB S02E01.avi"]
def test_collect_episode_files_missing_folder_is_empty(tmp_path):
assert collect_episode_files(tmp_path / "nope") == []
class TestFileManagerSeries:
@pytest.fixture
def file_manager(self, tmp_path, monkeypatch):
@@ -94,8 +136,8 @@ class TestFileManagerSeries:
assert result["imported"] == 2
base = (tmp_path / "pool" / "Seriály"
/ "Breaking Bad (2008) [tmdbid-1396]" / "Season 01")
assert (base / "S01E01 - bb.mkv").exists()
assert (base / "S01E02 - bb.mkv").exists()
assert (base / "S01E01 - Breaking Bad.mkv").exists()
assert (base / "S01E02 - Breaking Bad.mkv").exists()
def test_import_episodes_into_mixed_library(self, file_manager, tmp_path):
file_manager.set_libraries([{"name": "Hlavní", "kind": "mixed"}])
@@ -106,7 +148,7 @@ class TestFileManagerSeries:
assert result["imported"] == 1
assert (tmp_path / "pool" / "Hlavní" / "Breaking Bad (2008) [tmdbid-1396]"
/ "Season 01" / "S01E01 - bb.mkv").exists()
/ "Season 01" / "S01E01 - Breaking Bad.mkv").exists()
# a mixed library is scanned for series too
found = file_manager.load_series()
assert [s.title for s in found] == ["Breaking Bad"]
@@ -118,6 +160,45 @@ class TestFileManagerSeries:
assert result["imported"] == 0
assert result["skipped_no_code"] == ["random.mkv"]
def test_remove_series_deletes_the_folder(self, file_manager, tmp_path):
(tmp_path / "bb.S01E01.mkv").write_bytes(b"x")
(tmp_path / "bb.S02E01.mkv").write_bytes(b"x")
file_manager.import_episodes(
[tmp_path / "bb.S01E01.mkv", tmp_path / "bb.S02E01.mkv"],
"Breaking Bad", year=2008, tmdb_id=1396)
(ser,) = file_manager.load_series()
assert ser.folder.is_dir()
file_manager.remove_series(ser)
assert not ser.folder.exists()
assert file_manager.load_series() == []
def test_remove_series_refuses_folder_outside_pool(self, file_manager, tmp_path):
from src.core.series import Series
outside = tmp_path / "not-the-pool" / "Show (2020)"
outside.mkdir(parents=True)
bogus = Series(outside, "Show", "2020", None, "Seriály")
with pytest.raises(ValueError):
file_manager.remove_series(bogus)
assert outside.is_dir() # untouched
def test_import_episodes_uses_explicit_codes_override(self, file_manager, tmp_path):
uncoded = tmp_path / "pilot.mkv"
uncoded.write_bytes(b"x")
misparsed = tmp_path / "bb 2008 e07.mkv" # "2008" would parse as 20x08
misparsed.write_bytes(b"x")
result = file_manager.import_episodes(
[uncoded, misparsed], "Breaking Bad", year=2008, tmdb_id=1396,
codes={uncoded: (1, 1), misparsed: (1, 7)})
assert result["imported"] == 2
base = (tmp_path / "pool" / "Seriály"
/ "Breaking Bad (2008) [tmdbid-1396]" / "Season 01")
assert (base / "S01E01 - Breaking Bad.mkv").exists()
assert (base / "S01E07 - Breaking Bad.mkv").exists()
assert result["skipped_no_code"] == []
def test_rename_episodes_to_canonical(self, file_manager, tmp_path):
# a mis-named episode sitting in the wrong place
folder = (tmp_path / "pool" / "Seriály"
@@ -129,8 +210,49 @@ class TestFileManagerSeries:
assert len(series_list) == 1
result = file_manager.rename_episodes_to_canonical(series_list[0])
assert result["renamed"] == [("bb.1x03.HDTV.mkv", "S01E03 - bb.HDTV.mkv")]
assert (folder / "Season 01" / "S01E03 - bb.HDTV.mkv").exists()
assert result["renamed"] == [("bb.1x03.HDTV.mkv", "S01E03 - Breaking Bad.mkv")]
assert (folder / "Season 01" / "S01E03 - Breaking Bad.mkv").exists()
def test_rename_episodes_to_canonical_carries_subtitle_along(
self, file_manager, tmp_path
):
folder = (tmp_path / "pool" / "Seriály"
/ "Breaking Bad (2008) [tmdbid-1396]")
folder.mkdir(parents=True)
(folder / "bb.1x03.HDTV.mkv").write_bytes(b"x")
(folder / "bb.1x03.HDTV.cs.srt").write_bytes(b"x")
result = file_manager.rename_episodes_to_canonical(file_manager.load_series()[0])
assert result["renamed"] == [("bb.1x03.HDTV.mkv", "S01E03 - Breaking Bad.mkv")]
assert (folder / "Season 01" / "S01E03 - Breaking Bad.cs.srt").exists()
assert not (folder / "bb.1x03.HDTV.cs.srt").exists()
def test_rename_all_series_to_canonical_bulk(self, file_manager, tmp_path):
seri = tmp_path / "pool" / "Seriály"
# series A: mis-named episodes in a non-padded season folder
(seri / "Fargo (2014) [tmdbid-60622]" / "Season 1").mkdir(parents=True)
(seri / "Fargo (2014) [tmdbid-60622]" / "Season 1"
/ "fargo.1x01.1080p.WEB.mkv").write_bytes(b"x")
# series B: one good, one without a code
(seri / "Most! (2019) [tmdbid-90000]" / "Season 01").mkdir(parents=True)
(seri / "Most! (2019) [tmdbid-90000]" / "Season 01"
/ "S01E02 - Most!.mkv").write_bytes(b"x")
(seri / "Most! (2019) [tmdbid-90000]" / "Season 01"
/ "necoZvlastniho.mkv").write_bytes(b"x")
result = file_manager.rename_all_series_to_canonical()
assert result["series"] == 2
assert ("Fargo", "fargo.1x01.1080p.WEB.mkv", "S01E01 - Fargo.mkv") in result["renamed"]
assert (seri / "Fargo (2014) [tmdbid-60622]" / "Season 01"
/ "S01E01 - Fargo.mkv").exists()
# emptied non-padded season folder swept
assert not (seri / "Fargo (2014) [tmdbid-60622]" / "Season 1").exists()
# the already-canonical one is untouched, the code-less one is reported
assert ("Most!", "necoZvlastniho.mkv") in result["skipped_no_code"]
assert (seri / "Most! (2019) [tmdbid-90000]" / "Season 01"
/ "S01E02 - Most!.mkv").exists()
def test_apply_series_edits_renames_folder_and_episodes(self, file_manager, tmp_path):
folder = tmp_path / "pool" / "Seriály" / "Wrong Name"
@@ -144,5 +266,5 @@ class TestFileManagerSeries:
new_folder = (tmp_path / "pool" / "Seriály"
/ "Breaking Bad (2008) [tmdbid-1396]")
assert result["folder"] == new_folder
assert (new_folder / "Season 01" / "S01E01 - old.mkv").exists()
assert (new_folder / "Season 01" / "S01E01 - Breaking Bad.mkv").exists()
assert not folder.exists()