Skip to content

Commit d7ed07a

Browse files
committed
Merge branch 'claude/confident-bhaskara'
2 parents 3dc9464 + a9a022b commit d7ed07a

3 files changed

Lines changed: 137 additions & 12 deletions

File tree

__init__.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,9 @@ async def scan_directory_route(request):
528528
None, scan_directory_for_images, directory_path
529529
)
530530

531-
print(f"[DirScanner] Found {stats['total']} images ({stats['with_metadata']} with metadata, {stats['skipped']} skipped)")
531+
from_manifest = stats.get("from_manifest", 0)
532+
manifest_info = f", {from_manifest} from existing manifest" if from_manifest > 0 else ""
533+
print(f"[DirScanner] Found {stats['total']} images ({stats['with_metadata']} with metadata, {stats['skipped']} skipped{manifest_info})")
532534

533535
# Build manifest
534536
manifest = {
@@ -576,14 +578,22 @@ async def scan_directory_route(request):
576578
# Whitelist the directory for the view_external route
577579
real_dir = os.path.realpath(directory_path)
578580
_whitelisted_directories.add(real_dir)
579-
print(f"[DirScanner] Whitelisted directory: {real_dir}")
581+
# Also whitelist images/ subdirectory if it exists (generated sessions store images there)
582+
images_subdir = os.path.realpath(os.path.join(directory_path, "images"))
583+
if os.path.isdir(images_subdir):
584+
_whitelisted_directories.add(images_subdir)
585+
print(f"[DirScanner] Whitelisted directory: {real_dir} (+ images/)")
586+
else:
587+
print(f"[DirScanner] Whitelisted directory: {real_dir}")
580588

589+
from_manifest = stats.get("from_manifest", 0)
581590
return web.json_response({
582591
"session_name": session_name,
583592
"item_count": len(items),
584593
"with_metadata": stats["with_metadata"],
585594
"without_metadata": len(items) - stats["with_metadata"],
586595
"skipped": stats["skipped"],
596+
"from_manifest": from_manifest,
587597
})
588598

589599
except ValueError as e:

directory_scanner.py

Lines changed: 113 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,83 @@ def parse_a1111_parameters(parameters_text):
241241
return result
242242

243243

244+
# =============================================================================
245+
# EXISTING MANIFEST DETECTION
246+
# =============================================================================
247+
248+
def _extract_filename_from_url(file_url):
249+
"""
250+
Extract the image filename from a manifest item's file URL.
251+
Handles both generated URLs (/view?filename=xxx&...) and external URLs (/config_tester/view_external?path=...).
252+
"""
253+
if not file_url:
254+
return None
255+
256+
# Generated session format: /view?filename=img_xxx.webp&type=output&subfolder=...
257+
match = re.search(r'filename=([^&]+)', file_url)
258+
if match:
259+
return urllib.parse.unquote(match.group(1))
260+
261+
# External scan format: /config_tester/view_external?path=...
262+
match = re.search(r'path=([^&]+)', file_url)
263+
if match:
264+
decoded = urllib.parse.unquote(match.group(1))
265+
return os.path.basename(decoded)
266+
267+
return None
268+
269+
270+
def _load_existing_manifest_items(directory_path):
271+
"""
272+
Check if a directory (or its parent) contains a manifest.json generated by this node.
273+
If found, returns a dict mapping image filename -> item metadata.
274+
275+
Checks:
276+
1. {directory_path}/manifest.json (user scanned the session root)
277+
2. {directory_path}/../manifest.json (user scanned the images/ subdirectory)
278+
279+
Returns:
280+
tuple: (items_by_filename dict, manifest_meta dict or None)
281+
"""
282+
candidates = [
283+
os.path.join(directory_path, "manifest.json"),
284+
]
285+
# If the user is scanning an "images" subdirectory, check the parent for manifest
286+
if os.path.basename(directory_path).lower() == "images":
287+
candidates.append(os.path.join(directory_path, "..", "manifest.json"))
288+
289+
for manifest_path in candidates:
290+
manifest_path = os.path.normpath(manifest_path)
291+
if not os.path.isfile(manifest_path):
292+
continue
293+
294+
try:
295+
with open(manifest_path, "r") as f:
296+
manifest = json.load(f)
297+
except Exception as e:
298+
print(f"[DirScanner] Warning: Could not read {manifest_path}: {e}")
299+
continue
300+
301+
# Validate it looks like our manifest (must have "items" list)
302+
items_list = manifest.get("items")
303+
if not isinstance(items_list, list) or len(items_list) == 0:
304+
continue
305+
306+
# Build lookup: filename -> item data
307+
items_by_filename = {}
308+
for item in items_list:
309+
file_url = item.get("file", "")
310+
filename = _extract_filename_from_url(file_url)
311+
if filename:
312+
items_by_filename[filename] = item
313+
314+
if items_by_filename:
315+
print(f"[DirScanner] Found existing manifest at {manifest_path} with {len(items_by_filename)} items")
316+
return items_by_filename, manifest.get("meta")
317+
318+
return {}, None
319+
320+
244321
# =============================================================================
245322
# DIRECTORY SCANNER
246323
# =============================================================================
@@ -265,16 +342,26 @@ def scan_directory_for_images(directory_path, max_items=5000):
265342
raise ValueError(f"Directory not found: {directory_path}")
266343

267344
items = []
268-
stats = {"total": 0, "with_metadata": 0, "skipped": 0}
345+
stats = {"total": 0, "with_metadata": 0, "skipped": 0, "from_manifest": 0}
346+
347+
# Check for existing manifest.json generated by this node
348+
manifest_items, manifest_meta = _load_existing_manifest_items(directory_path)
269349

270350
# Collect image files, sorted by name for consistent ordering
351+
# Also scan images/ subdirectory if it exists (generated sessions store images there)
271352
image_files = []
353+
scan_dirs = [directory_path]
354+
images_subdir = os.path.join(directory_path, "images")
355+
if os.path.isdir(images_subdir):
356+
scan_dirs.append(images_subdir)
357+
272358
try:
273-
for entry in os.scandir(directory_path):
274-
if entry.is_file():
275-
ext = os.path.splitext(entry.name)[1].lower()
276-
if ext in IMAGE_EXTENSIONS:
277-
image_files.append(entry.path)
359+
for scan_dir in scan_dirs:
360+
for entry in os.scandir(scan_dir):
361+
if entry.is_file():
362+
ext = os.path.splitext(entry.name)[1].lower()
363+
if ext in IMAGE_EXTENSIONS:
364+
image_files.append(entry.path)
278365
except PermissionError:
279366
raise ValueError(f"Permission denied: {directory_path}")
280367

@@ -285,13 +372,29 @@ def scan_directory_for_images(directory_path, max_items=5000):
285372

286373
for file_path in image_files:
287374
stats["total"] += 1
375+
filename = os.path.basename(file_path)
376+
377+
# If we have manifest data for this image, use it (much richer metadata)
378+
if filename in manifest_items:
379+
item = manifest_items[filename].copy()
380+
# Rewrite file URL to use the external view endpoint
381+
encoded_path = urllib.parse.quote(file_path, safe="")
382+
item["file"] = f"/config_tester/view_external?path={encoded_path}"
383+
item["source_file"] = filename
384+
# Ensure it has an ID
385+
if "id" not in item:
386+
item["id"] = int(time.time() * 100000) + random.randint(0, 1000)
387+
items.append(item)
388+
stats["from_manifest"] += 1
389+
continue
288390

391+
# No manifest entry — fall through to normal image processing
289392
try:
290393
item = _process_single_image(file_path, directory_path)
291394
if item:
292395
items.append(item)
293396
except Exception as e:
294-
print(f"[DirScanner] Warning: Skipping {os.path.basename(file_path)}: {e}")
397+
print(f"[DirScanner] Warning: Skipping {filename}: {e}")
295398
stats["skipped"] += 1
296399
continue
297400

@@ -300,6 +403,9 @@ def scan_directory_for_images(directory_path, max_items=5000):
300403
1 for item in items if item.get("model", "Unknown") != "Unknown"
301404
)
302405

406+
if stats["from_manifest"] > 0:
407+
print(f"[DirScanner] Used existing manifest for {stats['from_manifest']}/{stats['total']} images")
408+
303409
return items, stats
304410

305411

resources/logic_utils.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,18 @@ async function scanDirectory() {
346346
const result = await response.json();
347347

348348
if (response.ok) {
349-
const metaInfo = result.with_metadata > 0
350-
? `${result.with_metadata} with metadata`
351-
: 'no metadata found';
349+
const fromManifest = result.from_manifest || 0;
350+
let metaInfo;
351+
if (fromManifest > 0) {
352+
metaInfo = `${fromManifest} from manifest`;
353+
if (result.with_metadata > fromManifest) {
354+
metaInfo += `, ${result.with_metadata - fromManifest} with embedded metadata`;
355+
}
356+
} else {
357+
metaInfo = result.with_metadata > 0
358+
? `${result.with_metadata} with metadata`
359+
: 'no metadata found';
360+
}
352361

353362
if (statusEl) {
354363
statusEl.innerText = `Found ${result.item_count} images (${metaInfo}). Loading...`;

0 commit comments

Comments
 (0)