This commit is contained in:
rainlizard
2025-05-01 16:53:32 +10:00
parent 7b288e637a
commit f25c674b65
+56 -63
View File
@@ -19,7 +19,9 @@ def tensor2pil(image):
image = image[0]
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
# Placeholder generation function (creates a base image, will be resized later if needed by UI)
# Placeholder generation function (remains unused in the optimized path, keep for robustness?)
# Note: Placeholder generation is removed from the main optimized logic below.
# If an error occurs during copy, the image is simply skipped in the preview.
def create_placeholder(size=(128, 128), text="?"):
"""Creates a simple placeholder PIL image."""
img = Image.new('RGB', size, color = (40, 40, 40))
@@ -27,13 +29,14 @@ def create_placeholder(size=(128, 128), text="?"):
font = ImageFont.load_default() # Keep it simple for placeholders
try:
# Basic centering for default font
tw, th = d.textsize(text, font=font) if hasattr(d, 'textsize') else (10, 10)
tw, th = d.textbbox((0,0), text, font=font)[2:] if hasattr(d, 'textbbox') else (10, 10) # Use textbbox if available
d.text(((size[0]-tw)/2, (size[1]-th)/2), text, font=font, fill=(180, 180, 180))
except Exception as e:
print(f"[PreviewHistory] Error drawing placeholder text: {e}")
d.text((10, 10), text, fill=(180, 180, 180)) # Fallback position
return img
class PreviewHistory:
# Lock for file system operations in the target directory
_dir_lock = threading.Lock()
@@ -66,7 +69,6 @@ class PreviewHistory:
def execute(self, image, history_size):
# Use the default history folder path directly
history_folder = DEFAULT_HISTORY_FOLDER
# Ensure target directory exists
@@ -76,36 +78,42 @@ class PreviewHistory:
print(f"[PreviewHistory] Created history directory: {history_folder}")
except OSError as e:
print(f"[PreviewHistory] Error creating directory {history_folder}: {e}. Cannot proceed.")
return {"ui": {"images": []}}
# Return empty previews if directory creation fails
return {"ui": {"images": []}} # Return empty list
# --- Save New Image (if provided) ---
new_image_saved_path = None
if image is not None and image.nelement() > 0:
new_pil_image = tensor2pil(image)
with PreviewHistory._dir_lock: # Lock specifically for saving
try:
now = datetime.now()
timestamp_final = now.strftime("%d-%m-%Y_%H-%M-%S")
# Include milliseconds for higher uniqueness, preventing rare collisions
timestamp_final += f"_{now.microsecond // 1000:03d}"
new_filename = f"history_{timestamp_final}.png"
new_path = os.path.join(history_folder, new_filename)
new_pil_image.save(new_path, "PNG", compress_level=1)
new_pil_image.save(new_path, "PNG", compress_level=1) # Faster compression
new_image_saved_path = new_path # Keep track of the path if saved
# print(f"[PreviewHistory] Saved new file: {new_path}") # Debug
except Exception as e:
print(f"[PreviewHistory] Error saving new image to '{history_folder}': {e}")
# Continue even if saving fails, try to show existing history
# --- Cleanup Old Files & Load Images for Preview ---
preview_images_pil = []
sorted_history_files = []
with PreviewHistory._dir_lock: # Lock for listing, cleanup, and getting paths
# --- Cleanup Old Files & Get List for Preview ---
sorted_history_files = [] # List to hold full paths of files to preview
with PreviewHistory._dir_lock: # Lock for listing, cleanup
try:
# Get all .png files with modification times for cleanup and loading
# Get all .png files with modification times for cleanup
all_files = []
for filename in os.listdir(history_folder):
if filename.lower().endswith(".png"):
full_path = os.path.join(history_folder, filename)
try:
if os.path.isfile(full_path):
# Use creation time if available and potentially more stable, else modification time
# Note: ctime might be platform-dependent (inode change on Unix, creation on Win)
# Stick to mtime for broader consistency unless ctime is specifically desired
mod_time = os.path.getmtime(full_path)
all_files.append((mod_time, full_path))
except OSError:
@@ -115,89 +123,74 @@ class PreviewHistory:
# Sort by modification time, newest first
all_files.sort(key=lambda x: x[0], reverse=True)
# Determine which files to keep based on history_size
files_to_keep_info = all_files[:history_size]
files_to_remove_info = all_files[history_size:]
# Cleanup: Remove files exceeding the current history_size
if len(all_files) > history_size:
files_to_remove = all_files[history_size:] # Get the oldest ones
# print(f"[PreviewHistory] Found {len(all_files)} files, keeping {history_size}, removing {len(files_to_remove)}.") # Debug
for mod_time, path_to_remove in files_to_remove:
if files_to_remove_info:
# print(f"[PreviewHistory] Found {len(all_files)} files, keeping {history_size}, removing {len(files_to_remove_info)}.") # Debug
for mod_time, path_to_remove in files_to_remove_info:
try:
# print(f"[PreviewHistory] Removing old file: {path_to_remove}") # Debug
os.remove(path_to_remove)
except OSError as e:
print(f"[PreviewHistory] Error removing old file {path_to_remove}: {e}")
# Keep only the files that were not removed
files_to_keep = all_files[:history_size]
else:
files_to_keep = all_files # Keep all if within limit
# Get the paths for the files we are keeping for the preview
sorted_history_files = [f[1] for f in files_to_keep]
# Get the final list of full paths for the files we are keeping for the preview
sorted_history_files = [f[1] for f in files_to_keep_info]
except Exception as e:
print(f"[PreviewHistory] Error listing/cleaning files in {history_folder} for preview: {e}")
print(f"[PreviewHistory] Error listing/cleaning files in {history_folder}: {e}")
# Fall through with empty list if error during file operations
# Determine a consistent size for placeholders if needed
placeholder_size = (128, 128) # Default
if sorted_history_files: # Check if we have any files paths left after potential cleanup
try:
# Try loading the first actual image (newest one)
first_image_path = sorted_history_files[0]
temp_img = Image.open(first_image_path)
placeholder_size = temp_img.size
temp_img.close()
except Exception:
pass # Ignore if it fails, keep default size
# Load images from the potentially cleaned-up list
for i, fp in enumerate(sorted_history_files): # Iterate through the paths we collected
try:
img = Image.open(fp).convert('RGB')
preview_images_pil.append(img)
except Exception as e:
print(f"[PreviewHistory] Error loading history image '{fp}' for preview: {e}")
# Keep error placeholder for load failures, use index 'i' for label
preview_images_pil.append(create_placeholder(placeholder_size, f"Err {i:02d}"))
# --- Generate Preview Data for the UI ---
# --- Generate Preview Data for the UI by Copying Files ---
previews = []
preview_server = server.PromptServer.instance # Get server instance
for i, pil_img in enumerate(preview_images_pil):
if pil_img is None: continue # Should not happen with placeholder logic
# Process the sorted list of history files to generate previews
for i, history_file_path in enumerate(sorted_history_files):
if not os.path.exists(history_file_path):
print(f"[PreviewHistory] Warning: File {history_file_path} not found during preview generation (maybe removed?). Skipping.")
continue
try:
# Use numpy array for saving temporary preview file
img_array = np.array(pil_img).astype(np.uint8)
# Define a unique prefix for each temp preview file in the batch
# Simplify prefix - just use the index 'i' from the preview loop
# 1. Get image dimensions efficiently
# Use a context manager to ensure the file handle is closed
# PIL often reads headers without loading the full image data
with Image.open(history_file_path) as img:
width, height = img.size
# 2. Determine temporary path information using dimensions
# Use a distinct prefix based on the original filename hash or index to avoid collisions in temp dir
# Using index 'i' is simple and effective here.
filename_prefix = f"PreviewHistory_Item_{i:02d}_"
# Get path for temporary preview file
# Note: using dimensions from pil_img directly
full_output_folder, fname, count, subfolder, _ = folder_paths.get_save_image_path(
filename_prefix, self.output_dir, pil_img.width, pil_img.height
filename_prefix, self.output_dir, width, height
)
file = f"{fname}_{count:05}_.png" # Temp preview is always png
temp_filename = f"{fname}_{count:05}_.png" # Previews are always PNG
temp_file_path = os.path.join(full_output_folder, temp_filename)
# Save the image (from history dir or placeholder) to the temporary location for UI preview
pil_img.save(os.path.join(full_output_folder, file), quality=95) # Good quality for preview
# 3. Copy the file from history folder to temp folder
# shutil.copy2 preserves more metadata (like mtime), copy is slightly faster if metadata isn't needed.
# Using copy should be sufficient here.
shutil.copy(history_file_path, temp_file_path)
# Append preview info for this image
# 4. Append preview info for this image
previews.append({
"filename": file,
"filename": temp_filename,
"subfolder": subfolder,
"type": self.type
})
except Exception as e:
print(f"[PreviewHistory] Error generating UI preview for image {i}: {e}")
# Optionally skip this preview or add an error indicator? For now, just skip.
print(f"[PreviewHistory] Error processing history image '{history_file_path}' for preview: {e}. Skipping this image.")
# Optionally, could create and copy a placeholder image here, but skipping is simpler/faster.
# Return the list of preview data dictionaries
# print(f"[PreviewHistory] Generated {len(previews)} previews.") # Debug
return {"ui": {"images": previews}}