@@ -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
0 commit comments