Replaced loop.py and image.py, and updated video.py to fix the 'reverse cache' issue by injecting the new index directly into the ComfyUI API JSON payload. Added large debug banners in Spanish, PNG keyframe extraction, and progressive MP4 saving.
112 lines
3.6 KiB
Python
112 lines
3.6 KiB
Python
import os
|
|
import time
|
|
import random
|
|
import numpy as np
|
|
import torch
|
|
from PIL import Image
|
|
import folder_paths
|
|
from . import register_node
|
|
|
|
global_session_image = None
|
|
|
|
def tensor_to_temp_image(tensor_image, prefix="session_img"):
|
|
temp_dir = folder_paths.get_temp_directory()
|
|
filename = f"{prefix}_{random.randint(10000, 99999)}.png"
|
|
filepath = os.path.join(temp_dir, filename)
|
|
img_array = 255. * tensor_image[0].cpu().numpy()
|
|
img = Image.fromarray(np.clip(img_array, 0, 255).astype(np.uint8))
|
|
img.save(filepath)
|
|
return {"filename": filename, "subfolder": "", "type": "temp"}
|
|
|
|
@register_node
|
|
class SessionImageReceiver:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"initial_image": ("IMAGE",),
|
|
"current_loop_index": ("INT", {"default": 0, "min": 0, "max": 10000}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
RETURN_NAMES = ("current_image",)
|
|
OUTPUT_NODE = True
|
|
FUNCTION = "get_image"
|
|
CATEGORY = "🔁 Sequential Batcher/Image"
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, **kwargs):
|
|
return time.time()
|
|
|
|
def get_image(self, initial_image, current_loop_index):
|
|
global global_session_image
|
|
loop_idx = current_loop_index[0] if isinstance(current_loop_index, list) else current_loop_index
|
|
is_first = (loop_idx == 0)
|
|
|
|
print(f"\n{'='*50}")
|
|
print(f"📥 [DEBUG] NODO: Image Receiver")
|
|
print(f" -> Ciclo actual detectado: {loop_idx}")
|
|
|
|
if is_first or global_session_image is None:
|
|
global_session_image = initial_image.clone().cpu()
|
|
print(f" -> 🆕 Iniciando sesión con la imagen ORIGINAL.")
|
|
selected = initial_image
|
|
else:
|
|
print(f" -> ♻️ Usando el fotograma rescatado de la RAM.")
|
|
selected = global_session_image
|
|
|
|
print(f" -> 🖼️ Tensor shape: {selected.shape}")
|
|
print(f"{'='*50}\n")
|
|
|
|
ui_image = tensor_to_temp_image(selected, "receiver")
|
|
return {"ui": {"images": [ui_image]}, "result": (selected, )}
|
|
|
|
@register_node
|
|
class SessionImageSender:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"generated_images": ("IMAGE",),
|
|
"current_loop_index": ("INT", {"default": 0, "min": 0, "max": 10000}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ()
|
|
RETURN_NAMES = ()
|
|
OUTPUT_NODE = True
|
|
FUNCTION = "set_image"
|
|
CATEGORY = "🔁 Sequential Batcher/Image"
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, **kwargs):
|
|
return time.time()
|
|
|
|
def set_image(self, generated_images, current_loop_index):
|
|
global global_session_image
|
|
loop_idx = current_loop_index[0] if isinstance(current_loop_index, list) else current_loop_index
|
|
|
|
print(f"\n{'='*50}")
|
|
print(f"📤 [DEBUG] NODO: Image Sender")
|
|
print(f" -> Ciclo actual: {loop_idx} | Frames recibidos: {generated_images.shape[0]}")
|
|
|
|
last_frame = generated_images[-1:].clone().cpu()
|
|
global_session_image = last_frame
|
|
|
|
# Guardar disco progresivo
|
|
out_dir = folder_paths.get_output_directory()
|
|
filename = f"keyframe_{loop_idx:03d}.png"
|
|
filepath = os.path.join(out_dir, filename)
|
|
|
|
img_array = 255. * last_frame[0].numpy()
|
|
img = Image.fromarray(np.clip(img_array, 0, 255).astype(np.uint8))
|
|
img.save(filepath)
|
|
|
|
print(f" -> 💾 Keyframe guardado físicamente: {filename}")
|
|
print(f" -> 🧠 RAM asegurada para el próximo ciclo.")
|
|
print(f"{'='*50}\n")
|
|
|
|
ui_image = tensor_to_temp_image(last_frame, "sender")
|
|
return {"ui": {"images": [ui_image]}}
|