Release v1.0.8: recursive folder scan and file type filters.
Adds subfolder inclusion and extension presets so the preview list can target nested media without loading everything. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user