@@ -19,21 +19,24 @@ def tensor2pil(image):
1919 image = image [0 ]
2020 return Image .fromarray (np .clip (255. * image .cpu ().numpy ().squeeze (), 0 , 255 ).astype (np .uint8 ))
2121
22- # Placeholder generation function (creates a base image, will be resized later if needed by UI)
22+ # Placeholder generation function (remains unused in the optimized path, keep for robustness?)
23+ # Note: Placeholder generation is removed from the main optimized logic below.
24+ # If an error occurs during copy, the image is simply skipped in the preview.
2325def create_placeholder (size = (128 , 128 ), text = "?" ):
2426 """Creates a simple placeholder PIL image."""
2527 img = Image .new ('RGB' , size , color = (40 , 40 , 40 ))
2628 d = ImageDraw .Draw (img )
2729 font = ImageFont .load_default () # Keep it simple for placeholders
2830 try :
2931 # Basic centering for default font
30- tw , th = d .textsize ( text , font = font ) if hasattr (d , 'textsize ' ) else (10 , 10 )
32+ tw , th = d .textbbox (( 0 , 0 ), text , font = font )[ 2 :] if hasattr (d , 'textbbox ' ) else (10 , 10 ) # Use textbbox if available
3133 d .text (((size [0 ]- tw )/ 2 , (size [1 ]- th )/ 2 ), text , font = font , fill = (180 , 180 , 180 ))
3234 except Exception as e :
3335 print (f"[PreviewHistory] Error drawing placeholder text: { e } " )
3436 d .text ((10 , 10 ), text , fill = (180 , 180 , 180 )) # Fallback position
3537 return img
3638
39+
3740class PreviewHistory :
3841 # Lock for file system operations in the target directory
3942 _dir_lock = threading .Lock ()
@@ -66,7 +69,6 @@ def INPUT_TYPES(s):
6669
6770 def execute (self , image , history_size ):
6871
69- # Use the default history folder path directly
7072 history_folder = DEFAULT_HISTORY_FOLDER
7173
7274 # Ensure target directory exists
@@ -76,36 +78,42 @@ def execute(self, image, history_size):
7678 print (f"[PreviewHistory] Created history directory: { history_folder } " )
7779 except OSError as e :
7880 print (f"[PreviewHistory] Error creating directory { history_folder } : { e } . Cannot proceed." )
79- return {"ui" : {"images" : []}}
81+ # Return empty previews if directory creation fails
82+ return {"ui" : {"images" : []}} # Return empty list
8083
8184 # --- Save New Image (if provided) ---
85+ new_image_saved_path = None
8286 if image is not None and image .nelement () > 0 :
8387 new_pil_image = tensor2pil (image )
8488 with PreviewHistory ._dir_lock : # Lock specifically for saving
8589 try :
8690 now = datetime .now ()
8791 timestamp_final = now .strftime ("%d-%m-%Y_%H-%M-%S" )
92+ # Include milliseconds for higher uniqueness, preventing rare collisions
93+ timestamp_final += f"_{ now .microsecond // 1000 :03d} "
8894 new_filename = f"history_{ timestamp_final } .png"
8995 new_path = os .path .join (history_folder , new_filename )
90- new_pil_image .save (new_path , "PNG" , compress_level = 1 )
96+ new_pil_image .save (new_path , "PNG" , compress_level = 1 ) # Faster compression
97+ new_image_saved_path = new_path # Keep track of the path if saved
9198 # print(f"[PreviewHistory] Saved new file: {new_path}") # Debug
9299 except Exception as e :
93100 print (f"[PreviewHistory] Error saving new image to '{ history_folder } ': { e } " )
94101 # Continue even if saving fails, try to show existing history
95102
96-
97- # --- Cleanup Old Files & Load Images for Preview ---
98- preview_images_pil = []
99- sorted_history_files = []
100- with PreviewHistory ._dir_lock : # Lock for listing, cleanup, and getting paths
103+ # --- Cleanup Old Files & Get List for Preview ---
104+ sorted_history_files = [] # List to hold full paths of files to preview
105+ with PreviewHistory ._dir_lock : # Lock for listing, cleanup
101106 try :
102- # Get all .png files with modification times for cleanup and loading
107+ # Get all .png files with modification times for cleanup
103108 all_files = []
104109 for filename in os .listdir (history_folder ):
105110 if filename .lower ().endswith (".png" ):
106111 full_path = os .path .join (history_folder , filename )
107112 try :
108113 if os .path .isfile (full_path ):
114+ # Use creation time if available and potentially more stable, else modification time
115+ # Note: ctime might be platform-dependent (inode change on Unix, creation on Win)
116+ # Stick to mtime for broader consistency unless ctime is specifically desired
109117 mod_time = os .path .getmtime (full_path )
110118 all_files .append ((mod_time , full_path ))
111119 except OSError :
@@ -115,89 +123,74 @@ def execute(self, image, history_size):
115123 # Sort by modification time, newest first
116124 all_files .sort (key = lambda x : x [0 ], reverse = True )
117125
126+ # Determine which files to keep based on history_size
127+ files_to_keep_info = all_files [:history_size ]
128+ files_to_remove_info = all_files [history_size :]
129+
118130 # Cleanup: Remove files exceeding the current history_size
119- if len (all_files ) > history_size :
120- files_to_remove = all_files [history_size :] # Get the oldest ones
121- # print(f"[PreviewHistory] Found {len(all_files)} files, keeping {history_size}, removing {len(files_to_remove)}.") # Debug
122- for mod_time , path_to_remove in files_to_remove :
131+ if files_to_remove_info :
132+ # print(f"[PreviewHistory] Found {len(all_files)} files, keeping {history_size}, removing {len(files_to_remove_info)}.") # Debug
133+ for mod_time , path_to_remove in files_to_remove_info :
123134 try :
124135 # print(f"[PreviewHistory] Removing old file: {path_to_remove}") # Debug
125136 os .remove (path_to_remove )
126137 except OSError as e :
127138 print (f"[PreviewHistory] Error removing old file { path_to_remove } : { e } " )
128- # Keep only the files that were not removed
129- files_to_keep = all_files [:history_size ]
130- else :
131- files_to_keep = all_files # Keep all if within limit
132139
133- # Get the paths for the files we are keeping for the preview
134- sorted_history_files = [f [1 ] for f in files_to_keep ]
140+ # Get the final list of full paths for the files we are keeping for the preview
141+ sorted_history_files = [f [1 ] for f in files_to_keep_info ]
135142
136143 except Exception as e :
137- print (f"[PreviewHistory] Error listing/cleaning files in { history_folder } for preview : { e } " )
144+ print (f"[PreviewHistory] Error listing/cleaning files in { history_folder } : { e } " )
138145 # Fall through with empty list if error during file operations
139146
140147
141- # Determine a consistent size for placeholders if needed
142- placeholder_size = (128 , 128 ) # Default
143- if sorted_history_files : # Check if we have any files paths left after potential cleanup
144- try :
145- # Try loading the first actual image (newest one)
146- first_image_path = sorted_history_files [0 ]
147- temp_img = Image .open (first_image_path )
148- placeholder_size = temp_img .size
149- temp_img .close ()
150- except Exception :
151- pass # Ignore if it fails, keep default size
152-
153- # Load images from the potentially cleaned-up list
154- for i , fp in enumerate (sorted_history_files ): # Iterate through the paths we collected
155- try :
156- img = Image .open (fp ).convert ('RGB' )
157- preview_images_pil .append (img )
158- except Exception as e :
159- print (f"[PreviewHistory] Error loading history image '{ fp } ' for preview: { e } " )
160- # Keep error placeholder for load failures, use index 'i' for label
161- preview_images_pil .append (create_placeholder (placeholder_size , f"Err { i :02d} " ))
162-
163-
164- # --- Generate Preview Data for the UI ---
148+ # --- Generate Preview Data for the UI by Copying Files ---
165149 previews = []
166150 preview_server = server .PromptServer .instance # Get server instance
167151
168- for i , pil_img in enumerate (preview_images_pil ):
169- if pil_img is None : continue # Should not happen with placeholder logic
152+ # Process the sorted list of history files to generate previews
153+ for i , history_file_path in enumerate (sorted_history_files ):
154+ if not os .path .exists (history_file_path ):
155+ print (f"[PreviewHistory] Warning: File { history_file_path } not found during preview generation (maybe removed?). Skipping." )
156+ continue
170157
171158 try :
172- # Use numpy array for saving temporary preview file
173- img_array = np .array (pil_img ).astype (np .uint8 )
174- # Define a unique prefix for each temp preview file in the batch
175- # Simplify prefix - just use the index 'i' from the preview loop
159+ # 1. Get image dimensions efficiently
160+ # Use a context manager to ensure the file handle is closed
161+ # PIL often reads headers without loading the full image data
162+ with Image .open (history_file_path ) as img :
163+ width , height = img .size
164+
165+ # 2. Determine temporary path information using dimensions
166+ # Use a distinct prefix based on the original filename hash or index to avoid collisions in temp dir
167+ # Using index 'i' is simple and effective here.
176168 filename_prefix = f"PreviewHistory_Item_{ i :02d} _"
177-
178- # Get path for temporary preview file
179- # Note: using dimensions from pil_img directly
180169 full_output_folder , fname , count , subfolder , _ = folder_paths .get_save_image_path (
181- filename_prefix , self .output_dir , pil_img . width , pil_img . height
170+ filename_prefix , self .output_dir , width , height
182171 )
183- file = f"{ fname } _{ count :05} _.png" # Temp preview is always png
172+ temp_filename = f"{ fname } _{ count :05} _.png" # Previews are always PNG
173+ temp_file_path = os .path .join (full_output_folder , temp_filename )
184174
185- # Save the image (from history dir or placeholder) to the temporary location for UI preview
186- pil_img .save (os .path .join (full_output_folder , file ), quality = 95 ) # Good quality for preview
175+ # 3. Copy the file from history folder to temp folder
176+ # shutil.copy2 preserves more metadata (like mtime), copy is slightly faster if metadata isn't needed.
177+ # Using copy should be sufficient here.
178+ shutil .copy (history_file_path , temp_file_path )
187179
188- # Append preview info for this image
180+ # 4. Append preview info for this image
189181 previews .append ({
190- "filename" : file ,
182+ "filename" : temp_filename ,
191183 "subfolder" : subfolder ,
192184 "type" : self .type
193185 })
194186
195187 except Exception as e :
196- print (f"[PreviewHistory] Error generating UI preview for image { i } : { e } " )
197- # Optionally skip this preview or add an error indicator? For now, just skip .
188+ print (f"[PreviewHistory] Error processing history image ' { history_file_path } ' for preview : { e } . Skipping this image. " )
189+ # Optionally, could create and copy a placeholder image here, but skipping is simpler/faster .
198190
199191
200192 # Return the list of preview data dictionaries
193+ # print(f"[PreviewHistory] Generated {len(previews)} previews.") # Debug
201194 return {"ui" : {"images" : previews }}
202195
203196
0 commit comments