from __future__ import annotations import re import sys from pathlib import Path from typing import List from models import AlbumScan, AUDIO_EXTENSIONS, IMAGE_EXTENSIONS, TRACKLIST_EXTENSIONS, PLAYLIST_EXTENSIONS _DISC_DIR_RE = re.compile(r"(?i)^(?:cd|disc|disk|side)[_ \-]*\d{1,2}$") def _is_hidden(name: str) -> bool: return name.startswith(".") or name.startswith("_") def _is_disc_dir(name: str) -> bool: """True für Ordner wie 'CD1', 'Disc 2', 'Side A', 'Disk_1'.""" return bool(_DISC_DIR_RE.match(name)) def scan_album(album_dir: Path) -> AlbumScan: """ Scannt ein Album-Verzeichnis. Rekursions-Regel: - Hat das Album-Verzeichnis selbst Audio-Dateien → kein Abstieg in Unterordner (Einzelscheibe; Sub-Ordner wie Artworks, Scans, irrtümliche Kopien werden ignoriert). - Hat der Root KEINE Audio-Dateien → Abstieg nur in Disc-Unterordner (CD1, Disc 2 …). """ result = AlbumScan(album_dir=album_dir) # Erst nur die Wurzel-Ebene scannen, um zu entscheiden ob rekursiert wird root_has_audio = any( (album_dir / name).suffix.lower() in AUDIO_EXTENSIONS for name in _listdir(album_dir) if not _is_hidden(name) ) if root_has_audio: # Nur Root-Ebene — keine Unterordner _scan_dir(album_dir, album_dir, result, recurse=False) else: # Kein Audio an der Wurzel → Multi-CD: nur Disc-Unterordner _scan_dir(album_dir, album_dir, result, recurse=True) result.audio_files.sort() result.image_files.sort() result.tracklist_files.sort() result.playlist_files.sort() return result def _listdir(path: Path) -> List[str]: try: return [e.name for e in path.iterdir()] except (PermissionError, OSError) as e: print(f"⚠️ Scan-Fehler: {e}", file=sys.stderr) return [] def _scan_dir(current: Path, album_dir: Path, result: AlbumScan, recurse: bool) -> None: try: entries = sorted(current.iterdir()) except (PermissionError, OSError) as e: print(f"⚠️ Scan-Fehler {current}: {e}", file=sys.stderr) return for entry in entries: name = entry.name if _is_hidden(name): continue if entry.is_dir(): if recurse and _is_disc_dir(name): _scan_dir(entry, album_dir, result, recurse=True) # Andere Unterordner (Artworks, irrtümliche Kopien…) werden übersprungen elif entry.is_file(): ext = entry.suffix.lower() if ext in AUDIO_EXTENSIONS: result.audio_files.append(entry) elif ext in IMAGE_EXTENSIONS: result.image_files.append(entry) elif ext in TRACKLIST_EXTENSIONS: result.tracklist_files.append(entry) elif ext in PLAYLIST_EXTENSIONS: result.playlist_files.append(entry) else: result.other_files.append(entry) 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