import logging import sys from pathlib import Path from typing import List logger = logging.getLogger(__name__) SUPPORTED_IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp"} def is_supported_image(path): """Return True if path has a supported image extension (case-insensitive).""" return Path(path).suffix.lower() in SUPPORTED_IMAGE_EXTENSIONS # --------------------------------------------------------------------------- # Path helpers # --------------------------------------------------------------------------- def get_app_dir(): """Return the application root directory as a Path. PyInstaller onedir: directory that contains the .exe. Development: project root (three levels above this file: src/services/file_service.py -> src/services -> src -> project root). """ if getattr(sys, "frozen", False): return Path(sys.executable).resolve().parent return Path(__file__).resolve().parent.parent.parent def get_resource_path(relative_path): """Return absolute Path for a resource file. Development resources live under src/resources/. In a PyInstaller onedir build, resources are copied next to the executable under resources/. """ if getattr(sys, "frozen", False): return get_app_dir() / "resources" / relative_path return get_app_dir() / "src" / "resources" / relative_path def get_config_path(relative_path): """Return absolute Path for a file under /config/.""" return get_app_dir() / "config" / relative_path def get_log_dir(): """Return /logs/ as a Path, creating the directory if absent.""" d = get_app_dir() / "logs" d.mkdir(exist_ok=True) return d def get_output_dir(): """Return /output/ as a Path, creating the directory if absent.""" d = get_app_dir() / "output" d.mkdir(exist_ok=True) return d # --------------------------------------------------------------------------- # 文件夹扫描 # --------------------------------------------------------------------------- def scan_image_folder(folder) -> List: """Recursively scan *folder* for supported images. Returns a list of ImageAsset objects (selected=True by default). Skips unsupported files and logs them at DEBUG level. Raises OSError if *folder* does not exist or is not a directory. """ from core.models import ImageAsset folder = Path(folder) if not folder.exists(): raise OSError("Folder not found: {}".format(folder)) if not folder.is_dir(): raise OSError("Path is not a directory: {}".format(folder)) assets = [] skipped_count = 0 for entry in sorted(folder.rglob("*")): if not entry.is_file(): continue if is_supported_image(entry): assets.append(ImageAsset(path=entry)) else: skipped_count += 1 logger.debug("Skipped unsupported file: %s", entry) if skipped_count: logger.info( "Skipped %d unsupported file(s) in %s", skipped_count, folder ) logger.info("Found %d image(s) in %s", len(assets), folder) return assets # --------------------------------------------------------------------------- # 安全输出文件名 # --------------------------------------------------------------------------- def make_safe_output_path(output_dir, garment_path, print_path, output_format="PNG"): """Return an output Path that will not overwrite an existing file. Filename pattern : _. If that path exists, appends _1, _2, ... until a free slot is found. """ output_dir = Path(output_dir) ext = ".png" if output_format.upper() == "PNG" else ".jpg" stem = "{}_{}".format(Path(garment_path).stem, Path(print_path).stem) candidate = output_dir / (stem + ext) counter = 1 while candidate.exists(): candidate = output_dir / ("{}_{}{}".format(stem, counter, ext)) counter += 1 return candidate