feat: add image folder scanning and safe output filename

- scan_image_folder(): recursively scans a directory for supported
  images (png/jpg/jpeg/webp, case-insensitive), returns List[ImageAsset],
  skips and logs unsupported files, raises OSError on bad path
- make_safe_output_path(): builds <garment>_<print>.<ext> filename,
  appends _1/_2/... counter if destination already exists

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 16:58:44 +08:00
co-authored by Claude Sonnet 4.6
parent b692e3aa42
commit 2d9121e3e0
2 changed files with 76 additions and 12 deletions
+65 -1
View File
@@ -1,6 +1,7 @@
import logging import logging
import sys import sys
from pathlib import Path from pathlib import Path
from typing import List
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -8,7 +9,8 @@ SUPPORTED_IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp"}
def is_supported_image(path): 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 = get_app_dir() / "output"
d.mkdir(exist_ok=True) d.mkdir(exist_ok=True)
return d 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 : <garment_stem>_<print_stem>.<ext>
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
+11 -11
View File
@@ -160,20 +160,20 @@
任务: 任务:
- [ ] 先读取 `src/services/file_service.py` 现有内容 - [x] 先读取 `src/services/file_service.py` 现有内容
- [ ] 完善 `src/services/file_service.py` - [x] 完善 `src/services/file_service.py`
- [ ] 支持递归扫描衣服图片文件夹 - [x] 支持递归扫描衣服图片文件夹
- [ ] 支持递归扫描印花图片文件夹 - [x] 支持递归扫描印花图片文件夹
- [ ] 支持格式:PNG、JPG、JPEG、WEBP - [x] 支持格式:PNG、JPG、JPEG、WEBP
- [ ] 忽略不支持格式并记录日志 - [x] 忽略不支持格式并记录日志
- [ ] 保留来源子文件夹信息 - [x] 保留来源子文件夹信息
- [ ] 生成安全输出文件名 - [x] 生成安全输出文件名
验收: 验收:
- [ ] 可以扫描包含子文件夹的中文路径 - [x] 可以扫描包含子文件夹的中文路径
- [ ] 不修改原始素材文件 - [x] 不修改原始素材文件
- [ ] 不支持文件格式不会导致扫描失败 - [x] 不支持文件格式不会导致扫描失败
## 4. 模板服务 ## 4. 模板服务