132 lines
5.9 KiB
Python
132 lines
5.9 KiB
Python
|
|
import os
|
|
import json
|
|
import torch
|
|
import numpy as np
|
|
from PIL import Image
|
|
import folder_paths
|
|
import execution
|
|
import time
|
|
|
|
# オリジナルのexecute関数を保持
|
|
# モジュールリロード時の無限再帰を防ぐため、executionモジュール側にバックアップを保存する
|
|
if not hasattr(execution, "original_execute_backup_for_save_prompt"):
|
|
execution.original_execute_backup_for_save_prompt = execution.execute
|
|
|
|
original_execute = execution.original_execute_backup_for_save_prompt
|
|
|
|
# ベースの保存先ディレクトリ
|
|
HOOK_BASE_DIR = os.path.join(folder_paths.get_output_directory(), "hooked_outputs")
|
|
|
|
def get_save_dir(prompt_id):
|
|
# prompt_id (実行ID) ごとにフォルダを分ける
|
|
# タイムスタンプは実行開始時ではなくノード実行時になってしまうが、prompt_id自体がユニークなIDとして使える
|
|
# 分かりやすさのためにディレクトリ名に日付などは入れず prompt_id だけにするか、
|
|
# どこかで開始時刻を取れれば良いが、単純化のため prompt_id をフォルダ名とする
|
|
save_dir = os.path.join(HOOK_BASE_DIR, prompt_id)
|
|
os.makedirs(save_dir, exist_ok=True)
|
|
return save_dir
|
|
|
|
def save_image(images, filename_prefix, save_dir):
|
|
results = []
|
|
# Tensor以外(リストなど)が来る場合もあるので再帰的に処理するか、Tensorのみ抽出
|
|
if isinstance(images, list):
|
|
for img in images:
|
|
results.extend(save_image(img, filename_prefix, save_dir))
|
|
return results
|
|
|
|
if not isinstance(images, torch.Tensor):
|
|
return []
|
|
|
|
# 画像として保存可能なTensorかチェック (B, H, W, C)
|
|
if images.ndim == 4 and images.shape[-1] in [1, 3, 4]:
|
|
for batch_idx, img_tensor in enumerate(images):
|
|
try:
|
|
img_array = 255. * img_tensor.cpu().numpy()
|
|
img = Image.fromarray(np.clip(img_array, 0, 255).astype(np.uint8))
|
|
|
|
filename = f"{filename_prefix}_{batch_idx}.png"
|
|
file_path = os.path.join(save_dir, filename)
|
|
img.save(file_path, optimize=True)
|
|
results.append(filename)
|
|
except Exception as e:
|
|
# 稀に形状が合わない等のエラーがあっても全体を止めない
|
|
print(f"[Hook Image Save Error] {e}")
|
|
|
|
return results
|
|
|
|
def serialize_value(value):
|
|
if isinstance(value, (int, float, str, bool, type(None))):
|
|
return value
|
|
elif isinstance(value, torch.Tensor):
|
|
return f"Tensor shape={value.shape} dtype={value.dtype} device={value.device}"
|
|
elif isinstance(value, list):
|
|
return [serialize_value(x) for x in value]
|
|
elif isinstance(value, dict):
|
|
return {k: serialize_value(v) for k, v in value.items()}
|
|
else:
|
|
return str(type(value))
|
|
|
|
async def new_execute(server, dynprompt, caches, current_item, extra_data, executed, prompt_id, execution_list, pending_subgraph_results, pending_async_nodes, ui_outputs):
|
|
# オリジナルの実行
|
|
result = await original_execute(server, dynprompt, caches, current_item, extra_data, executed, prompt_id, execution_list, pending_subgraph_results, pending_async_nodes, ui_outputs)
|
|
|
|
# 実行成功時のみ処理
|
|
if result[0] == execution.ExecutionResult.SUCCESS:
|
|
try:
|
|
# キャッシュから出力を取得
|
|
cached = caches.outputs.get(current_item)
|
|
if cached is not None and cached.outputs:
|
|
timestamp = int(time.time())
|
|
node_info = dynprompt.get_node(current_item)
|
|
class_type = node_info.get("class_type", "Unknown")
|
|
|
|
# 保存ディレクトリの決定
|
|
save_dir = get_save_dir(prompt_id)
|
|
|
|
# ファイル名のプレフィックス (タイムスタンプ_ノードID_タイプ)
|
|
filename_base = f"{timestamp}_{current_item}_{class_type}"
|
|
|
|
saved_files = []
|
|
outputs_summary = []
|
|
|
|
# 全ての出力を走査して保存
|
|
for out_idx, output_data in enumerate(cached.outputs):
|
|
try:
|
|
# save_dir を渡すように変更
|
|
files = save_image(output_data, f"{filename_base}_out{out_idx}", save_dir)
|
|
if files:
|
|
saved_files.extend(files)
|
|
outputs_summary.append({"index": out_idx, "type": "image", "files": files})
|
|
else:
|
|
val_str = serialize_value(output_data)
|
|
outputs_summary.append({"index": out_idx, "type": "other", "value": val_str})
|
|
except Exception as e:
|
|
outputs_summary.append({"index": out_idx, "type": "error", "error": str(e)})
|
|
|
|
# メタデータ(実行ログ)の保存
|
|
metadata = {
|
|
"node_id": current_item,
|
|
"class_type": class_type,
|
|
"prompt_id": prompt_id,
|
|
"timestamp": timestamp,
|
|
"outputs": outputs_summary,
|
|
"inputs": node_info.get("inputs"),
|
|
"workflow": dynprompt.get_original_prompt()
|
|
}
|
|
|
|
json_path = os.path.join(save_dir, f"{filename_base}_meta.json")
|
|
with open(json_path, "w", encoding="utf-8") as f:
|
|
json.dump(metadata, f, indent=4, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
print(f"[Hook Error] Failed to save output for node {current_item}: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
return result
|
|
|
|
def apply_hook():
|
|
execution.execute = new_execute
|
|
print(f"ComfyUI Execution Hook Applied: All intermediate outputs will be logged to {HOOK_BASE_DIR}/<prompt_id>/")
|