Merge branch 'claude/confident-bhaskara'
This commit is contained in:
+12
-2
@@ -528,7 +528,9 @@ async def scan_directory_route(request):
|
||||
None, scan_directory_for_images, directory_path
|
||||
)
|
||||
|
||||
print(f"[DirScanner] Found {stats['total']} images ({stats['with_metadata']} with metadata, {stats['skipped']} skipped)")
|
||||
from_manifest = stats.get("from_manifest", 0)
|
||||
manifest_info = f", {from_manifest} from existing manifest" if from_manifest > 0 else ""
|
||||
print(f"[DirScanner] Found {stats['total']} images ({stats['with_metadata']} with metadata, {stats['skipped']} skipped{manifest_info})")
|
||||
|
||||
# Build manifest
|
||||
manifest = {
|
||||
@@ -576,14 +578,22 @@ async def scan_directory_route(request):
|
||||
# Whitelist the directory for the view_external route
|
||||
real_dir = os.path.realpath(directory_path)
|
||||
_whitelisted_directories.add(real_dir)
|
||||
print(f"[DirScanner] Whitelisted directory: {real_dir}")
|
||||
# Also whitelist images/ subdirectory if it exists (generated sessions store images there)
|
||||
images_subdir = os.path.realpath(os.path.join(directory_path, "images"))
|
||||
if os.path.isdir(images_subdir):
|
||||
_whitelisted_directories.add(images_subdir)
|
||||
print(f"[DirScanner] Whitelisted directory: {real_dir} (+ images/)")
|
||||
else:
|
||||
print(f"[DirScanner] Whitelisted directory: {real_dir}")
|
||||
|
||||
from_manifest = stats.get("from_manifest", 0)
|
||||
return web.json_response({
|
||||
"session_name": session_name,
|
||||
"item_count": len(items),
|
||||
"with_metadata": stats["with_metadata"],
|
||||
"without_metadata": len(items) - stats["with_metadata"],
|
||||
"skipped": stats["skipped"],
|
||||
"from_manifest": from_manifest,
|
||||
})
|
||||
|
||||
except ValueError as e:
|
||||
|
||||
+113
-7
@@ -241,6 +241,83 @@ def parse_a1111_parameters(parameters_text):
|
||||
return result
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# EXISTING MANIFEST DETECTION
|
||||
# =============================================================================
|
||||
|
||||
def _extract_filename_from_url(file_url):
|
||||
"""
|
||||
Extract the image filename from a manifest item's file URL.
|
||||
Handles both generated URLs (/view?filename=xxx&...) and external URLs (/config_tester/view_external?path=...).
|
||||
"""
|
||||
if not file_url:
|
||||
return None
|
||||
|
||||
# Generated session format: /view?filename=img_xxx.webp&type=output&subfolder=...
|
||||
match = re.search(r'filename=([^&]+)', file_url)
|
||||
if match:
|
||||
return urllib.parse.unquote(match.group(1))
|
||||
|
||||
# External scan format: /config_tester/view_external?path=...
|
||||
match = re.search(r'path=([^&]+)', file_url)
|
||||
if match:
|
||||
decoded = urllib.parse.unquote(match.group(1))
|
||||
return os.path.basename(decoded)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _load_existing_manifest_items(directory_path):
|
||||
"""
|
||||
Check if a directory (or its parent) contains a manifest.json generated by this node.
|
||||
If found, returns a dict mapping image filename -> item metadata.
|
||||
|
||||
Checks:
|
||||
1. {directory_path}/manifest.json (user scanned the session root)
|
||||
2. {directory_path}/../manifest.json (user scanned the images/ subdirectory)
|
||||
|
||||
Returns:
|
||||
tuple: (items_by_filename dict, manifest_meta dict or None)
|
||||
"""
|
||||
candidates = [
|
||||
os.path.join(directory_path, "manifest.json"),
|
||||
]
|
||||
# If the user is scanning an "images" subdirectory, check the parent for manifest
|
||||
if os.path.basename(directory_path).lower() == "images":
|
||||
candidates.append(os.path.join(directory_path, "..", "manifest.json"))
|
||||
|
||||
for manifest_path in candidates:
|
||||
manifest_path = os.path.normpath(manifest_path)
|
||||
if not os.path.isfile(manifest_path):
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(manifest_path, "r") as f:
|
||||
manifest = json.load(f)
|
||||
except Exception as e:
|
||||
print(f"[DirScanner] Warning: Could not read {manifest_path}: {e}")
|
||||
continue
|
||||
|
||||
# Validate it looks like our manifest (must have "items" list)
|
||||
items_list = manifest.get("items")
|
||||
if not isinstance(items_list, list) or len(items_list) == 0:
|
||||
continue
|
||||
|
||||
# Build lookup: filename -> item data
|
||||
items_by_filename = {}
|
||||
for item in items_list:
|
||||
file_url = item.get("file", "")
|
||||
filename = _extract_filename_from_url(file_url)
|
||||
if filename:
|
||||
items_by_filename[filename] = item
|
||||
|
||||
if items_by_filename:
|
||||
print(f"[DirScanner] Found existing manifest at {manifest_path} with {len(items_by_filename)} items")
|
||||
return items_by_filename, manifest.get("meta")
|
||||
|
||||
return {}, None
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# DIRECTORY SCANNER
|
||||
# =============================================================================
|
||||
@@ -265,16 +342,26 @@ def scan_directory_for_images(directory_path, max_items=5000):
|
||||
raise ValueError(f"Directory not found: {directory_path}")
|
||||
|
||||
items = []
|
||||
stats = {"total": 0, "with_metadata": 0, "skipped": 0}
|
||||
stats = {"total": 0, "with_metadata": 0, "skipped": 0, "from_manifest": 0}
|
||||
|
||||
# Check for existing manifest.json generated by this node
|
||||
manifest_items, manifest_meta = _load_existing_manifest_items(directory_path)
|
||||
|
||||
# Collect image files, sorted by name for consistent ordering
|
||||
# Also scan images/ subdirectory if it exists (generated sessions store images there)
|
||||
image_files = []
|
||||
scan_dirs = [directory_path]
|
||||
images_subdir = os.path.join(directory_path, "images")
|
||||
if os.path.isdir(images_subdir):
|
||||
scan_dirs.append(images_subdir)
|
||||
|
||||
try:
|
||||
for entry in os.scandir(directory_path):
|
||||
if entry.is_file():
|
||||
ext = os.path.splitext(entry.name)[1].lower()
|
||||
if ext in IMAGE_EXTENSIONS:
|
||||
image_files.append(entry.path)
|
||||
for scan_dir in scan_dirs:
|
||||
for entry in os.scandir(scan_dir):
|
||||
if entry.is_file():
|
||||
ext = os.path.splitext(entry.name)[1].lower()
|
||||
if ext in IMAGE_EXTENSIONS:
|
||||
image_files.append(entry.path)
|
||||
except PermissionError:
|
||||
raise ValueError(f"Permission denied: {directory_path}")
|
||||
|
||||
@@ -285,13 +372,29 @@ def scan_directory_for_images(directory_path, max_items=5000):
|
||||
|
||||
for file_path in image_files:
|
||||
stats["total"] += 1
|
||||
filename = os.path.basename(file_path)
|
||||
|
||||
# If we have manifest data for this image, use it (much richer metadata)
|
||||
if filename in manifest_items:
|
||||
item = manifest_items[filename].copy()
|
||||
# Rewrite file URL to use the external view endpoint
|
||||
encoded_path = urllib.parse.quote(file_path, safe="")
|
||||
item["file"] = f"/config_tester/view_external?path={encoded_path}"
|
||||
item["source_file"] = filename
|
||||
# Ensure it has an ID
|
||||
if "id" not in item:
|
||||
item["id"] = int(time.time() * 100000) + random.randint(0, 1000)
|
||||
items.append(item)
|
||||
stats["from_manifest"] += 1
|
||||
continue
|
||||
|
||||
# No manifest entry — fall through to normal image processing
|
||||
try:
|
||||
item = _process_single_image(file_path, directory_path)
|
||||
if item:
|
||||
items.append(item)
|
||||
except Exception as e:
|
||||
print(f"[DirScanner] Warning: Skipping {os.path.basename(file_path)}: {e}")
|
||||
print(f"[DirScanner] Warning: Skipping {filename}: {e}")
|
||||
stats["skipped"] += 1
|
||||
continue
|
||||
|
||||
@@ -300,6 +403,9 @@ def scan_directory_for_images(directory_path, max_items=5000):
|
||||
1 for item in items if item.get("model", "Unknown") != "Unknown"
|
||||
)
|
||||
|
||||
if stats["from_manifest"] > 0:
|
||||
print(f"[DirScanner] Used existing manifest for {stats['from_manifest']}/{stats['total']} images")
|
||||
|
||||
return items, stats
|
||||
|
||||
|
||||
|
||||
@@ -346,9 +346,18 @@ async function scanDirectory() {
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
const metaInfo = result.with_metadata > 0
|
||||
? `${result.with_metadata} with metadata`
|
||||
: 'no metadata found';
|
||||
const fromManifest = result.from_manifest || 0;
|
||||
let metaInfo;
|
||||
if (fromManifest > 0) {
|
||||
metaInfo = `${fromManifest} from manifest`;
|
||||
if (result.with_metadata > fromManifest) {
|
||||
metaInfo += `, ${result.with_metadata - fromManifest} with embedded metadata`;
|
||||
}
|
||||
} else {
|
||||
metaInfo = result.with_metadata > 0
|
||||
? `${result.with_metadata} with metadata`
|
||||
: 'no metadata found';
|
||||
}
|
||||
|
||||
if (statusEl) {
|
||||
statusEl.innerText = `Found ${result.item_count} images (${metaInfo}). Loading...`;
|
||||
|
||||
Reference in New Issue
Block a user