"""Scan folders for rename candidates with optional recursion and extension filters.""" from __future__ import annotations import os import re from pathlib import Path from .pipeline import UNDO_FILENAME FILE_TYPE_PRESETS: dict[str, str] = { "All files": "", "Video": ".mkv,.mp4,.avi,.webm,.m4v,.mov", "Audio": ".mp3,.flac,.wav,.ogg,.m4a,.aac", "Images": ".jpg,.jpeg,.png,.gif,.webp,.bmp", } def parse_extension_filter(spec: str) -> frozenset[str] | None: """ Parse a user extension filter into a set of lowercase extensions with a leading dot. Returns None when empty (match all files). """ spec = spec.strip() if not spec: return None exts: set[str] = set() for part in re.split(r"[,;\s]+", spec): part = part.strip().lower() if not part: continue if part.startswith("*."): part = part[1:] if not part.startswith("."): part = "." + part exts.add(part) return frozenset(exts) if exts else None def file_extension(rel_path: str) -> str: """Return the lowercase extension (with dot) of the basename, or '' if none.""" name = Path(rel_path).name if "." in name and not name.startswith("."): return "." + name.rsplit(".", 1)[-1].lower() return "" def matches_extension(rel_path: str, extensions: frozenset[str] | None) -> bool: if extensions is None: return True return file_extension(rel_path) in extensions def list_files( base_dir: str, *, recursive: bool = False, extensions: frozenset[str] | None = None, ) -> list[str]: """Return sorted paths relative to base_dir.""" base = Path(base_dir) if not base.is_dir(): return [] found: list[str] = [] def consider(path: Path) -> None: if not path.is_file(): return if path.name.startswith("."): return if path.name == UNDO_FILENAME: return rel = path.relative_to(base).as_posix() if matches_extension(rel, extensions): found.append(rel) if recursive: for dirpath, dirnames, filenames in os.walk(base): dirnames[:] = sorted(d for d in dirnames if not d.startswith(".")) for fn in sorted(filenames): consider(Path(dirpath) / fn) else: for entry in sorted(base.iterdir(), key=lambda p: p.name.lower()): consider(entry) return sorted(found, key=str.lower)