Normalize all cover art to folder.jpg (Jellyfin standard)

- Add normalize_cover_to_folder_jpg(): renames/converts any local cover
  (Front.jpg, front.webp, cover.jpg, …) to folder.jpg in-place; WebP/PNG
  are converted to JPEG via PIL
- resolve_cover() now calls normalize_cover_to_folder_jpg() automatically
  after finding a local cover, so future enrichment runs always produce folder.jpg
- One-time batch: 38 existing library covers renamed/converted to folder.jpg

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-04-29 08:41:43 +02:00
commit d06c0bbcc9

View file

@ -48,7 +48,6 @@ def _image_ok(path: Path) -> bool:
def find_local_cover(image_files: List[Path]) -> Optional[Path]: def find_local_cover(image_files: List[Path]) -> Optional[Path]:
priority = ("folder", "front", "cover", "album") priority = ("folder", "front", "cover", "album")
# Sort by priority keyword, then size descending
def key(p: Path): def key(p: Path):
name = p.name.lower() name = p.name.lower()
score = next((i for i, kw in enumerate(priority) if kw in name), len(priority)) score = next((i for i, kw in enumerate(priority) if kw in name), len(priority))
@ -61,6 +60,41 @@ def find_local_cover(image_files: List[Path]) -> Optional[Path]:
return None return None
def normalize_cover_to_folder_jpg(cover_path: Path) -> Path:
"""
Stellt sicher dass das Cover als folder.jpg (JPEG) im Album-Verzeichnis liegt.
- Ist es bereits folder.jpg unverändert zurückgeben.
- Ist es eine andere JPEG umbenennen.
- Ist es WebP oder PNG zu JPEG konvertieren, Original löschen.
Gibt den Pfad zur folder.jpg zurück.
"""
dest = cover_path.parent / "folder.jpg"
if cover_path.resolve() == dest.resolve():
return dest
suffix = cover_path.suffix.lower()
try:
if suffix in (".jpg", ".jpeg"):
cover_path.rename(dest)
elif HAS_PIL:
import io
with cover_path.open("rb") as f:
raw = f.read()
with Image.open(io.BytesIO(raw)) as img:
buf = io.BytesIO()
img.convert("RGB").save(buf, format="JPEG", quality=92)
dest.write_bytes(buf.getvalue())
cover_path.unlink()
else:
# PIL nicht verfügbar: einfach umbenennen, auch wenn es kein JPEG ist
cover_path.rename(dest)
print(f" 🖼️ Cover normalisiert → folder.jpg ({cover_path.name})")
except Exception as e:
print(f" ⚠️ Cover-Normalisierung fehlgeschlagen: {e}", file=sys.stderr)
return cover_path
return dest
def _mb_cover_url(release_mbid: str) -> Optional[str]: def _mb_cover_url(release_mbid: str) -> Optional[str]:
url = f"https://coverartarchive.org/release/{release_mbid}/front" url = f"https://coverartarchive.org/release/{release_mbid}/front"
if not HAS_REQUESTS: if not HAS_REQUESTS:
@ -195,6 +229,7 @@ def resolve_cover(
"""Returns (cover_path, source_label).""" """Returns (cover_path, source_label)."""
local = find_local_cover(image_files) local = find_local_cover(image_files)
if local: if local:
local = normalize_cover_to_folder_jpg(local)
return local, "local" return local, "local"
if release_mbid: if release_mbid: