From a9a022b66281a8234670a7131e3ef28da51e28ad Mon Sep 17 00:00:00 2001 From: JasonHoku Date: Sun, 15 Feb 2026 02:58:05 -0800 Subject: [PATCH] Read Manifest From Loaded Session Directories --- __init__.py | 14 ++++- directory_scanner.py | 120 ++++++++++++++++++++++++++++++++++++--- resources/logic_utils.js | 15 ++++- 3 files changed, 137 insertions(+), 12 deletions(-) diff --git a/__init__.py b/__init__.py index a6a03d0..51e7eb8 100644 --- a/__init__.py +++ b/__init__.py @@ -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: diff --git a/directory_scanner.py b/directory_scanner.py index d9bc6c0..6caf126 100644 --- a/directory_scanner.py +++ b/directory_scanner.py @@ -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 diff --git a/resources/logic_utils.js b/resources/logic_utils.js index 1588b09..da8adab 100644 --- a/resources/logic_utils.js +++ b/resources/logic_utils.js @@ -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...`;