diff --git a/src/services/file_service.py b/src/services/file_service.py index e8c25cb..fe8fcd5 100644 --- a/src/services/file_service.py +++ b/src/services/file_service.py @@ -1,6 +1,7 @@ import logging import sys from pathlib import Path +from typing import List logger = logging.getLogger(__name__) @@ -8,7 +9,8 @@ SUPPORTED_IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp"} def is_supported_image(path): - return path.suffix.lower() in SUPPORTED_IMAGE_EXTENSIONS + """Return True if path has a supported image extension (case-insensitive).""" + return Path(path).suffix.lower() in SUPPORTED_IMAGE_EXTENSIONS # --------------------------------------------------------------------------- @@ -49,3 +51,65 @@ def get_output_dir(): 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 diff --git a/tasks.md b/tasks.md index 18631b5..2155c9b 100644 --- a/tasks.md +++ b/tasks.md @@ -160,20 +160,20 @@ 任务: -- [ ] 先读取 `src/services/file_service.py` 现有内容 -- [ ] 完善 `src/services/file_service.py` -- [ ] 支持递归扫描衣服图片文件夹 -- [ ] 支持递归扫描印花图片文件夹 -- [ ] 支持格式:PNG、JPG、JPEG、WEBP -- [ ] 忽略不支持格式并记录日志 -- [ ] 保留来源子文件夹信息 -- [ ] 生成安全输出文件名 +- [x] 先读取 `src/services/file_service.py` 现有内容 +- [x] 完善 `src/services/file_service.py` +- [x] 支持递归扫描衣服图片文件夹 +- [x] 支持递归扫描印花图片文件夹 +- [x] 支持格式:PNG、JPG、JPEG、WEBP +- [x] 忽略不支持格式并记录日志 +- [x] 保留来源子文件夹信息 +- [x] 生成安全输出文件名 验收: -- [ ] 可以扫描包含子文件夹的中文路径 -- [ ] 不修改原始素材文件 -- [ ] 不支持文件格式不会导致扫描失败 +- [x] 可以扫描包含子文件夹的中文路径 +- [x] 不修改原始素材文件 +- [x] 不支持文件格式不会导致扫描失败 ## 4. 模板服务