Compare commits
3 Commits
d589a59fd1
...
dde501c27d
| Author | SHA1 | Date | |
|---|---|---|---|
| dde501c27d | |||
| d5b314f6b6 | |||
| ba8de1253e |
@@ -0,0 +1,54 @@
|
||||
"""Pure folder-scan layer for Folder Image Loader. Stdlib only — no torch."""
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tif", ".tiff"}
|
||||
|
||||
|
||||
def natural_key(s):
|
||||
return [int(t) if t.isdigit() else t.lower() for t in re.split(r"(\d+)", s)]
|
||||
|
||||
|
||||
def list_images(folder, depth=0):
|
||||
root = Path(folder)
|
||||
if not root.is_dir():
|
||||
raise NotADirectoryError(f"Not a folder: {folder}")
|
||||
root_depth = len(root.parts)
|
||||
results = []
|
||||
for dirpath, dirnames, filenames in os.walk(root):
|
||||
cur = Path(dirpath)
|
||||
rel_depth = len(cur.parts) - root_depth
|
||||
if depth >= 0 and rel_depth >= depth:
|
||||
dirnames[:] = [] # don't descend past `depth`
|
||||
if depth >= 0 and rel_depth > depth:
|
||||
continue
|
||||
for name in filenames:
|
||||
if Path(name).suffix.lower() in IMAGE_EXTS:
|
||||
results.append(str(cur / name))
|
||||
results.sort(key=lambda p: natural_key(os.path.relpath(p, root)))
|
||||
return results
|
||||
|
||||
|
||||
def resolve_index(count, index):
|
||||
if count == 0:
|
||||
raise FileNotFoundError("No images found in folder")
|
||||
if index < 0 or index >= count:
|
||||
raise IndexError(f"index {index} out of range: {count} images")
|
||||
return index
|
||||
|
||||
|
||||
def stem(image_path):
|
||||
return os.path.splitext(os.path.basename(image_path))[0]
|
||||
|
||||
|
||||
def sidecar_path(image_path):
|
||||
return os.path.splitext(image_path)[0] + ".txt"
|
||||
|
||||
|
||||
def read_sidecar(image_path):
|
||||
p = sidecar_path(image_path)
|
||||
if not os.path.isfile(p):
|
||||
return ""
|
||||
with open(p, "r", encoding="utf-8") as f:
|
||||
return f.read().rstrip("\n")
|
||||
@@ -0,0 +1,70 @@
|
||||
# tests/test_scan.py
|
||||
from gates import scan
|
||||
|
||||
def _touch(p, data=b"x"):
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
p.write_bytes(data)
|
||||
|
||||
def test_natural_sort_orders_numerically():
|
||||
items = ["img10.png", "img2.png", "img1.png"]
|
||||
assert sorted(items, key=scan.natural_key) == ["img1.png", "img2.png", "img10.png"]
|
||||
|
||||
def test_list_images_top_level_only_default(tmp_path):
|
||||
_touch(tmp_path / "a.png"); _touch(tmp_path / "b.jpg"); _touch(tmp_path / "note.txt")
|
||||
_touch(tmp_path / "sub" / "c.png")
|
||||
got = [p.split("/")[-1] for p in scan.list_images(str(tmp_path))]
|
||||
assert got == ["a.png", "b.jpg"] # depth 0: no sub/, no .txt
|
||||
|
||||
def test_list_images_depth_one(tmp_path):
|
||||
_touch(tmp_path / "a.png")
|
||||
_touch(tmp_path / "sub" / "c.png")
|
||||
_touch(tmp_path / "sub" / "deep" / "d.png")
|
||||
got = [p.split("/")[-1] for p in scan.list_images(str(tmp_path), depth=1)]
|
||||
assert got == ["a.png", "c.png"] # depth 1: include sub/, not sub/deep/
|
||||
|
||||
def test_list_images_unlimited_depth(tmp_path):
|
||||
_touch(tmp_path / "a.png"); _touch(tmp_path / "sub" / "deep" / "d.png")
|
||||
got = scan.list_images(str(tmp_path), depth=-1)
|
||||
assert len(got) == 2
|
||||
|
||||
def test_list_images_natural_sort_by_relpath(tmp_path):
|
||||
for n in ["img1.png", "img2.png", "img10.png"]:
|
||||
_touch(tmp_path / n)
|
||||
got = [p.split("/")[-1] for p in scan.list_images(str(tmp_path))]
|
||||
assert got == ["img1.png", "img2.png", "img10.png"]
|
||||
|
||||
def test_list_images_bad_path_raises(tmp_path):
|
||||
import pytest
|
||||
with pytest.raises(NotADirectoryError):
|
||||
scan.list_images(str(tmp_path / "nope"))
|
||||
|
||||
def test_resolve_index_ok():
|
||||
assert scan.resolve_index(5, 0) == 0
|
||||
assert scan.resolve_index(5, 4) == 4
|
||||
|
||||
def test_resolve_index_out_of_range_raises():
|
||||
import pytest
|
||||
with pytest.raises(IndexError):
|
||||
scan.resolve_index(5, 5)
|
||||
with pytest.raises(IndexError):
|
||||
scan.resolve_index(5, -1)
|
||||
|
||||
def test_resolve_index_empty_raises():
|
||||
import pytest
|
||||
with pytest.raises(FileNotFoundError):
|
||||
scan.resolve_index(0, 0)
|
||||
|
||||
def test_stem():
|
||||
assert scan.stem("/a/b/shot01.png") == "shot01"
|
||||
|
||||
def test_sidecar_path():
|
||||
assert scan.sidecar_path("/a/b/shot01.png") == "/a/b/shot01.txt"
|
||||
|
||||
def test_read_sidecar_present(tmp_path):
|
||||
(tmp_path / "x.png").write_bytes(b"i")
|
||||
(tmp_path / "x.txt").write_text("a caption\n", encoding="utf-8")
|
||||
assert scan.read_sidecar(str(tmp_path / "x.png")) == "a caption"
|
||||
|
||||
def test_read_sidecar_missing_returns_empty(tmp_path):
|
||||
(tmp_path / "x.png").write_bytes(b"i")
|
||||
assert scan.read_sidecar(str(tmp_path / "x.png")) == ""
|
||||
Reference in New Issue
Block a user