Adds subfolder inclusion and extension presets so the preview list can target nested media without loading everything. Co-authored-by: Cursor <[email protected]>
87 lines
2.4 KiB
Python
87 lines
2.4 KiB
Python
"""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)
|