fix(matcher): use folder path as series context
This commit is contained in:
@@ -311,3 +311,106 @@ def test_natural_track_order_in_flat_folder(client):
|
||||
(folder / f"track{i}.mp3").write_bytes(b"")
|
||||
item = client.get("/api/import/scan").json()["items"][0]
|
||||
assert [Path(f).name for f in item["files"]] == ["track1.mp3", "track2.mp3", "track10.mp3"]
|
||||
|
||||
|
||||
def test_real_world_series_tree(client):
|
||||
"""Downloads laid out as Serie/Folgen/<Sammelordner>/<NNN - Titel>/CD/*.mp3,
|
||||
with a Cover/ folder next to the CD folder."""
|
||||
base = client.downloads / "Die Drei Fragezeichen" / "Folgen" / "3478632869 001-010"
|
||||
episodes = ["001 - Der Super - Papagei", "002 - Der Phantomsee", "003 - Der Karpatenhund"]
|
||||
for ep in episodes:
|
||||
cd = base / ep / "CD"
|
||||
cd.mkdir(parents=True)
|
||||
for i in (1, 2, 10):
|
||||
(cd / f"{i:02d} Track.mp3").write_bytes(b"")
|
||||
cover = base / ep / "Cover"
|
||||
cover.mkdir()
|
||||
(cover / "front.jpg").write_bytes(b"")
|
||||
|
||||
items = client.get("/api/import/scan").json()["items"]
|
||||
assert [i["name"] for i in items] == episodes
|
||||
for item in items:
|
||||
assert item["is_dir"] and len(item["files"]) == 3
|
||||
assert [Path(f).name for f in item["files"]] == ["01 Track.mp3", "02 Track.mp3", "10 Track.mp3"]
|
||||
assert item["rel_dir"] == "Die Drei Fragezeichen/Folgen/3478632869 001-010"
|
||||
|
||||
|
||||
def test_two_discs_next_to_cover(client):
|
||||
folder = client.downloads / "099 - Die Villa der Toten"
|
||||
for disc in ("CD 1", "CD 2"):
|
||||
(folder / disc).mkdir(parents=True)
|
||||
(folder / disc / "track.mp3").write_bytes(b"")
|
||||
(folder / "Cover").mkdir()
|
||||
(folder / "Cover" / "back.png").write_bytes(b"")
|
||||
|
||||
items = client.get("/api/import/scan").json()["items"]
|
||||
assert len(items) == 1
|
||||
assert items[0]["name"] == "099 - Die Villa der Toten"
|
||||
assert [Path(f).parent.name for f in items[0]["files"]] == ["CD 1", "CD 2"]
|
||||
|
||||
|
||||
def test_subfolder_with_own_content_is_not_a_disc(client):
|
||||
"""A real subfolder (an episode) must keep its own identity."""
|
||||
folder = client.downloads / "Sammlung"
|
||||
for ep in ("Folge 1", "Folge 2"):
|
||||
(folder / ep).mkdir(parents=True)
|
||||
for i in (1, 2):
|
||||
(folder / ep / f"t{i}.mp3").write_bytes(b"")
|
||||
items = client.get("/api/import/scan").json()["items"]
|
||||
assert sorted(i["name"] for i in items) == ["Folge 1", "Folge 2"]
|
||||
|
||||
|
||||
def test_bulk_items_creates_series_and_skips_duplicates(client):
|
||||
lib_id = client.post("/api/libraries", json={
|
||||
"name": "DDF", "media_type": "audiobook",
|
||||
"root_path": str(client.tmp_path / "ddf"),
|
||||
}).json()["id"]
|
||||
items = [{"title": f"Die drei ??? Folge {n}", "series": "Die drei ???",
|
||||
"volume": n, "external_id": f"ASIN{n}"} for n in range(1, 6)]
|
||||
|
||||
res = client.post("/api/requests/bulk-items",
|
||||
json={"library_id": lib_id, "items": items}).json()
|
||||
assert len(res["created"]) == 5 and res["skipped"] == 0
|
||||
assert res["created"][0]["media_type"] == "audiobook"
|
||||
assert res["created"][0]["volume"] == 1
|
||||
|
||||
# same call again: known external_ids are skipped, new ones still land
|
||||
items.append({"title": "Die drei ??? Folge 6", "series": "Die drei ???",
|
||||
"volume": 6, "external_id": "ASIN6"})
|
||||
res2 = client.post("/api/requests/bulk-items",
|
||||
json={"library_id": lib_id, "items": items}).json()
|
||||
assert res2["skipped"] == 5 and len(res2["created"]) == 1
|
||||
assert len(client.get("/api/requests", params={"status": "missing"}).json()) == 6
|
||||
|
||||
|
||||
def test_bulk_items_series_then_scan_matches_by_episode_number(client):
|
||||
"""The whole point: bulk-request a series, then let the scan assign folders."""
|
||||
lib_id = client.post("/api/libraries", json={
|
||||
"name": "DDF", "media_type": "audiobook",
|
||||
"root_path": str(client.tmp_path / "ddf"),
|
||||
}).json()["id"]
|
||||
titles = {1: "Die drei ??? und der Super-Papagei",
|
||||
4: "Die drei ??? und die schwarze Katze",
|
||||
8: "Die drei ??? und der grüne Geist"}
|
||||
created = client.post("/api/requests/bulk-items", json={
|
||||
"library_id": lib_id,
|
||||
"items": [{"title": t, "series": "Die drei ???", "volume": n,
|
||||
"external_id": f"ASIN{n}"} for n, t in titles.items()],
|
||||
}).json()["created"]
|
||||
by_volume = {r["volume"]: r["id"] for r in created}
|
||||
|
||||
base = client.downloads / "Die Drei Fragezeichen" / "Folgen" / "3478632869 001-010"
|
||||
folders = {1: "001 - Der Super - Papagei", 4: "004 - Die schwarzn Katze",
|
||||
8: "008 - Der gruene Geist"}
|
||||
for n, folder in folders.items():
|
||||
cd = base / folder / "CD"
|
||||
cd.mkdir(parents=True)
|
||||
(cd / "01.mp3").write_bytes(b"")
|
||||
(cd / "02.mp3").write_bytes(b"")
|
||||
|
||||
items = client.get("/api/import/scan").json()["items"]
|
||||
assert len(items) == 3
|
||||
for item in items:
|
||||
n = int(item["name"][:3])
|
||||
assert item["suggested_request_id"] == by_volume[n], item
|
||||
assert item["score"] >= 90
|
||||
|
||||
+26
-1
@@ -1,6 +1,6 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
from wordarr.importer.matcher import best_matches, normalize
|
||||
from wordarr.importer.matcher import best_matches, normalize, score
|
||||
|
||||
|
||||
def req(id, title, authors="", media_type="ebook"):
|
||||
@@ -70,3 +70,28 @@ def test_digit_inside_word_is_not_a_volume():
|
||||
r3 = req(2, "Die dr3i - Folge 3", media_type="audiobook")
|
||||
r3.volume = 3
|
||||
assert score("DiE DR3i - 05 - Der Fall", r) > score("DiE DR3i - 05 - Der Fall", r3)
|
||||
|
||||
|
||||
def test_series_in_folder_path_carries_the_match():
|
||||
"""Downloads named "001 - Titel" only match via the series in their path."""
|
||||
r = req(1, "Die drei ??? Folge 001", media_type="audiobook")
|
||||
r.volume, r.series = 1, "Die drei ???"
|
||||
item = {"path": "/d/x", "name": "001 - Der Super - Papagei",
|
||||
"rel_dir": "Die Drei Fragezeichen/Folgen/3478632869 001-010",
|
||||
"media_type": "audiobook", "is_dir": True, "files": ["/d/x/a.mp3"]}
|
||||
out = best_matches([item], [r])
|
||||
assert out[0]["suggested_request_id"] == 1
|
||||
assert out[0]["score"] >= 90
|
||||
# without the series context it stays a weak fuzzy guess
|
||||
assert score("001 - Der Super - Papagei", r) < 90
|
||||
|
||||
|
||||
def test_questionmarks_equal_fragezeichen():
|
||||
assert normalize("Die drei ???") == normalize("Die Drei Fragezeichen")
|
||||
|
||||
|
||||
def test_disc_count_is_not_an_episode_number():
|
||||
r = req(1, "Die drei ??? und der Phantomsee", media_type="audiobook")
|
||||
r.volume = 2
|
||||
# "(2 CDs)" must not satisfy the volume check of episode 2
|
||||
assert score("Die drei Fragezeichen 001 - Super-Papagei (2 CDs)", r) == 40.0
|
||||
|
||||
Reference in New Issue
Block a user