AI-powered per-album pipeline: scan → local hints → MusicBrainz/Discogs/Claude resolve → cover art → interactive or auto review → tag write + rename + report. All external dependencies optional; 17/17 unit tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
59 lines
1.7 KiB
Python
59 lines
1.7 KiB
Python
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import List
|
|
|
|
from models import AlbumScan, AUDIO_EXTENSIONS, IMAGE_EXTENSIONS, TRACKLIST_EXTENSIONS
|
|
|
|
|
|
def _is_hidden(name: str) -> bool:
|
|
return name.startswith(".") or name.startswith("_")
|
|
|
|
|
|
def scan_album(album_dir: Path) -> AlbumScan:
|
|
result = AlbumScan(album_dir=album_dir)
|
|
|
|
for dirpath, dirnames, filenames in album_dir.walk() if hasattr(album_dir, "walk") else _os_walk(album_dir):
|
|
dirnames[:] = [d for d in dirnames if not _is_hidden(d)]
|
|
current = Path(dirpath) if isinstance(dirpath, str) else dirpath
|
|
|
|
for name in filenames:
|
|
if _is_hidden(name):
|
|
continue
|
|
p = current / name
|
|
ext = p.suffix.lower()
|
|
|
|
if ext in AUDIO_EXTENSIONS:
|
|
result.audio_files.append(p)
|
|
elif ext in IMAGE_EXTENSIONS:
|
|
result.image_files.append(p)
|
|
elif ext in TRACKLIST_EXTENSIONS:
|
|
result.tracklist_files.append(p)
|
|
else:
|
|
result.other_files.append(p)
|
|
|
|
result.audio_files.sort()
|
|
result.image_files.sort()
|
|
result.tracklist_files.sort()
|
|
return result
|
|
|
|
|
|
def _os_walk(album_dir: Path):
|
|
import os
|
|
return os.walk(
|
|
album_dir,
|
|
followlinks=False,
|
|
onerror=lambda e: print(f"⚠️ Scan-Fehler: {e}", file=sys.stderr),
|
|
)
|
|
|
|
|
|
def collect_album_dirs(root: Path) -> List[Path]:
|
|
dirs: List[Path] = []
|
|
try:
|
|
for item in sorted(root.iterdir()):
|
|
if item.is_dir() and not _is_hidden(item.name):
|
|
dirs.append(item)
|
|
except (PermissionError, OSError) as e:
|
|
print(f"⚠️ Lesefehler {root}: {e}", file=sys.stderr)
|
|
return dirs
|