Initial implementation of Music Metadata Enricher
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>
This commit is contained in:
parent
b273052f68
commit
f7cf520dbe
8 changed files with 1748 additions and 0 deletions
59
scanner.py
Normal file
59
scanner.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue