Opus/M4A-Cover-Embedding, cover.py-Tests und OCR-Tests
- tagger.py: embed_cover() unterstützt jetzt .opus (Vorbis-Comment METADATA_BLOCK_PICTURE) und .m4a (MP4Cover); imports ergänzt - test_tagger.py: 2 neue Tests für Opus/M4A; minimale Audio-Fixtures als base64-Konstanten (176 B Opus, 856 B M4A) - test_cover.py: TestPrepareCover (5 Tests) und TestCopyCovers (6 Tests) für prepare_cover() und copy_covers() - test_ocr.py: 13 Tests für run_ocr(), _detect_and_fix_rotation() und ocr_images(); Tesseract via subprocess.run gemockt 144 Tests, 0 Fehler Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
cfc2a2018e
commit
795be8609a
4 changed files with 343 additions and 1 deletions
|
|
@ -2,7 +2,18 @@
|
|||
|
||||
from pathlib import Path
|
||||
|
||||
from musiksammlung.cover import find_cover
|
||||
from PIL import Image
|
||||
|
||||
from musiksammlung.cover import copy_covers, find_cover, prepare_cover
|
||||
|
||||
|
||||
def _make_image(path: Path, size: tuple[int, int] = (100, 100), mode: str = "RGB") -> Path:
|
||||
img = Image.new(mode, size, color=(200, 100, 50))
|
||||
fmt = "PNG" if path.suffix.lower() == ".png" else "JPEG"
|
||||
if mode == "RGBA" and fmt == "JPEG":
|
||||
img = img.convert("RGB")
|
||||
img.save(str(path), fmt)
|
||||
return path
|
||||
|
||||
|
||||
class TestFindCover:
|
||||
|
|
@ -41,3 +52,70 @@ class TestFindCover:
|
|||
(tmp_path / "cover.jpg").touch()
|
||||
(tmp_path / "back.jpg").touch()
|
||||
assert find_cover(tmp_path, "front") is None
|
||||
|
||||
|
||||
class TestPrepareCover:
|
||||
def test_creates_jpeg_output(self, tmp_path: Path) -> None:
|
||||
src = _make_image(tmp_path / "src.jpg")
|
||||
dst = tmp_path / "out.jpg"
|
||||
prepare_cover(src, dst)
|
||||
assert dst.exists()
|
||||
assert Image.open(dst).format == "JPEG"
|
||||
|
||||
def test_scales_down_large_image(self, tmp_path: Path) -> None:
|
||||
src = _make_image(tmp_path / "src.jpg", size=(2000, 2000))
|
||||
dst = tmp_path / "out.jpg"
|
||||
prepare_cover(src, dst, max_size=1200)
|
||||
assert max(Image.open(dst).size) <= 1200
|
||||
|
||||
def test_does_not_upscale_small_image(self, tmp_path: Path) -> None:
|
||||
src = _make_image(tmp_path / "src.jpg", size=(300, 300))
|
||||
dst = tmp_path / "out.jpg"
|
||||
prepare_cover(src, dst, max_size=1200)
|
||||
assert max(Image.open(dst).size) <= 300
|
||||
|
||||
def test_converts_rgba_to_rgb(self, tmp_path: Path) -> None:
|
||||
src = tmp_path / "src.png"
|
||||
Image.new("RGBA", (100, 100), (255, 0, 0, 128)).save(str(src), "PNG")
|
||||
dst = tmp_path / "out.jpg"
|
||||
prepare_cover(src, dst)
|
||||
assert Image.open(dst).mode == "RGB"
|
||||
|
||||
def test_creates_parent_directory(self, tmp_path: Path) -> None:
|
||||
src = _make_image(tmp_path / "src.jpg")
|
||||
dst = tmp_path / "subdir" / "nested" / "out.jpg"
|
||||
prepare_cover(src, dst)
|
||||
assert dst.exists()
|
||||
|
||||
|
||||
class TestCopyCovers:
|
||||
def test_copies_front_cover(self, tmp_path: Path) -> None:
|
||||
src = _make_image(tmp_path / "src.jpg")
|
||||
copy_covers(src, None, tmp_path)
|
||||
assert (tmp_path / "frontcover.jpg").exists()
|
||||
|
||||
def test_copies_back_cover(self, tmp_path: Path) -> None:
|
||||
src = _make_image(tmp_path / "src.jpg")
|
||||
copy_covers(None, src, tmp_path)
|
||||
assert (tmp_path / "backcover.jpg").exists()
|
||||
|
||||
def test_copies_both_covers(self, tmp_path: Path) -> None:
|
||||
front = _make_image(tmp_path / "front.jpg")
|
||||
back = _make_image(tmp_path / "back.jpg")
|
||||
copy_covers(front, back, tmp_path)
|
||||
assert (tmp_path / "frontcover.jpg").exists()
|
||||
assert (tmp_path / "backcover.jpg").exists()
|
||||
|
||||
def test_skips_nonexistent_front(self, tmp_path: Path) -> None:
|
||||
copy_covers(tmp_path / "nope.jpg", None, tmp_path)
|
||||
assert not (tmp_path / "frontcover.jpg").exists()
|
||||
|
||||
def test_skips_nonexistent_back(self, tmp_path: Path) -> None:
|
||||
copy_covers(None, tmp_path / "nope.jpg", tmp_path)
|
||||
assert not (tmp_path / "backcover.jpg").exists()
|
||||
|
||||
def test_existing_frontcover_not_overwritten_when_no_source(self, tmp_path: Path) -> None:
|
||||
existing = _make_image(tmp_path / "frontcover.jpg")
|
||||
original_mtime = existing.stat().st_mtime
|
||||
copy_covers(None, None, tmp_path)
|
||||
assert (tmp_path / "frontcover.jpg").stat().st_mtime == original_mtime
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue