cocccck / custom_nodes /ComfyUI-Roop /roop_custom_nodes.py
bbbboiwow's picture
Upload folder using huggingface_hub
edb09f2 verified
Raw
History Blame Contribute Delete
13.3 kB
# roop_custom_nodes.py
import os
import subprocess
import glob
from PIL import Image
import numpy as np
import torch
import hmac
import hashlib
import requests
# Helper: save tensor image
def save_tensor_as_image(tensor, path):
np_img = (tensor[0].cpu().numpy() * 255).astype(np.uint8)
if np_img.shape[0] == 3: # If format is CHW
np_img = np.transpose(np_img, (1, 2, 0)) # Convert to HWC for PIL
img = Image.fromarray(np_img)
img.save(path)
# Helper: load image as tensor
def load_image_as_tensor(path):
img = Image.open(path).convert("RGB")
np_img = np.array(img).astype(np.float32) / 255.0
return torch.from_numpy(np_img).unsqueeze(0)
def get_unique_filename(path):
base, ext = os.path.splitext(path)
counter = 1
new_path = path
while os.path.exists(new_path):
new_path = f"{base}_{counter}{ext}"
counter += 1
return new_path
def send_webhook_image(webhook_url, webhook_secret, output_path, extra_data=None):
if not webhook_url:
return
with open(output_path, 'rb') as f:
image_data = f.read()
# GitHub-style HMAC signature
signature = 'sha256=' + hmac.new(
webhook_secret.encode('utf-8'),
image_data,
hashlib.sha256
).hexdigest()
headers = {
'X-Hub-Signature-256': signature
}
files = {
'file': ('swapped.png', image_data, 'image/png')
}
data = extra_data or {}
try:
resp = requests.post(webhook_url, headers=headers, files=files, data=data)
resp.raise_for_status()
print(f"[Webhook] Sent successfully to {webhook_url}")
except Exception as e:
print(f"[Webhook Error] {e}")
class RoopFaceSwap:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"source_image": ("IMAGE",),
"target_image": ("IMAGE",),
"roop_dir": ("STRING", {"default": "/content/roop"}),
"output_name": ("STRING", {"default": "roop_output.png"}),
"many_faces": ("BOOLEAN", {"default": False})
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("swapped_image",)
FUNCTION = "run"
CATEGORY = "Roop/Basic"
def run(self, source_image, target_image, roop_dir, output_name, many_faces):
temp_dir = os.path.join(roop_dir, "temp_io")
os.makedirs(temp_dir, exist_ok=True)
source_path = os.path.join(temp_dir, "source.png")
target_path = os.path.join(temp_dir, "target.png")
output_path = get_unique_filename(os.path.join(temp_dir, output_name))
save_tensor_as_image(source_image, source_path)
save_tensor_as_image(target_image, target_path)
cmd = [
"python", "run.py",
"-s", source_path,
"-t", target_path,
"-o", output_path,
"--execution-provider", "cuda",
"--frame-processor", "face_swapper"
]
if many_faces:
cmd.append("--many-faces")
subprocess.run(cmd, check=True, cwd=roop_dir)
if not os.path.exists(output_path):
print(f"[Warning] Roop did not produce output: {output_path}")
blank = torch.zeros_like(target_image)
return (blank,)
return (load_image_as_tensor(output_path),)
class RoopFaceSwapWithEnhancer:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"source_image": ("IMAGE",),
"target_image": ("IMAGE",),
"roop_dir": ("STRING", {"default": "/content/roop"}),
"output_name": ("STRING", {"default": "roop_output.png"}),
"many_faces": ("BOOLEAN", {"default": False})
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("swapped_image",)
FUNCTION = "run"
CATEGORY = "Roop/Enhanced"
def run(self, source_image, target_image, roop_dir, output_name, many_faces):
temp_dir = os.path.join(roop_dir, "temp_io")
os.makedirs(temp_dir, exist_ok=True)
source_path = os.path.join(temp_dir, "source.png")
target_path = os.path.join(temp_dir, "target.png")
output_path = get_unique_filename(os.path.join(temp_dir, output_name))
save_tensor_as_image(source_image, source_path)
save_tensor_as_image(target_image, target_path)
cmd = [
"python", "run.py",
"-s", source_path,
"-t", target_path,
"-o", output_path,
"--execution-provider", "cuda",
"--frame-processor", "face_swapper", "face_enhancer"
]
if many_faces:
cmd.append("--many-faces")
subprocess.run(cmd, check=True, cwd=roop_dir)
if not os.path.exists(output_path):
print(f"[Warning] Roop did not produce output: {output_path}")
blank = torch.zeros_like(target_image)
return (blank,)
return (load_image_as_tensor(output_path),)
class RoopBatchFaceSwap:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"source_image": ("IMAGE",),
"input_dir": ("STRING", {"default": "/input/images"}),
"output_dir": ("STRING", {"default": "/output/images"}),
"roop_dir": ("STRING", {"default": "/content/roop"}),
"use_enhancer": ("BOOLEAN", {"default": False}),
"many_faces": ("BOOLEAN", {"default": False})
}
}
RETURN_TYPES = ()
RETURN_NAMES = ()
FUNCTION = "run"
CATEGORY = "Roop/Batch"
def run(self, source_image, input_dir, output_dir, roop_dir, use_enhancer, many_faces):
os.makedirs(output_dir, exist_ok=True)
temp_dir = os.path.join(roop_dir, "temp_io")
os.makedirs(temp_dir, exist_ok=True)
source_path = os.path.join(temp_dir, "source.png")
save_tensor_as_image(source_image, source_path)
image_paths = glob.glob(os.path.join(input_dir, "*.jpg")) + \
glob.glob(os.path.join(input_dir, "*.png"))
for img_path in image_paths:
target_name = os.path.basename(img_path)
target_path = os.path.join(temp_dir, "target.png")
output_path = os.path.join(output_dir, f"out_{target_name}")
Image.open(img_path).save(target_path)
cmd = [
"python", "run.py",
"-s", source_path,
"-t", target_path,
"-o", output_path,
"--execution-provider", "cuda",
"--frame-processor", "face_swapper"
]
if use_enhancer:
cmd[-1] += " face_enhancer"
if many_faces:
cmd.append("--many-faces")
subprocess.run(cmd, check=True, cwd=roop_dir)
if not os.path.exists(output_path):
print(f"[Skipped] NSFW or error: {img_path} -> No output generated.")
continue
return ()
class RoopFaceSwapVideo:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"source_image": ("IMAGE",),
"target_video_path": ("STRING", {"default": "/path/to/video.mp4"}),
"roop_dir": ("STRING", {"default": "/content/roop"}),
"output_name": ("STRING", {"default": "swapped_video.mp4"}),
"many_faces": ("BOOLEAN", {"default": False})
}
}
RETURN_TYPES = ()
RETURN_NAMES = ()
FUNCTION = "run"
CATEGORY = "Roop/Video"
def run(self, source_image, target_video_path, roop_dir, output_name, many_faces):
temp_dir = os.path.join(roop_dir, "temp_io")
os.makedirs(temp_dir, exist_ok=True)
source_path = os.path.join(temp_dir, "source.png")
output_path = get_unique_filename(os.path.join(temp_dir, output_name))
save_tensor_as_image(source_image, source_path)
cmd = [
"python", "run.py",
"-s", source_path,
"-t", target_video_path,
"-o", output_path,
"--keep-fps",
"--keep-frames",
"--execution-provider", "cuda",
"--frame-processor", "face_swapper"
]
if many_faces:
cmd.append("--many-faces")
subprocess.run(cmd, check=True, cwd=roop_dir)
if not os.path.exists(output_path):
print(f"[Warning] Roop did not produce video output: {output_path}")
return ()
class RoopSendWebhookImage:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image_tensor": ("IMAGE",),
"filename": ("STRING", {"default": "output.png"}),
"webhook_url": ("STRING", {"default": ""}),
"webhook_secret": ("STRING", {"default": ""}),
"enable_webhook": ("BOOLEAN", {"default": True}),
"roop_dir": ("STRING", {"default": "/content/roop"})
}
}
RETURN_TYPES = ()
RETURN_NAMES = ()
FUNCTION = "run"
CATEGORY = "Roop/Webhook"
def run(self, image_tensor, filename, webhook_url, webhook_secret, enable_webhook, roop_dir):
if not enable_webhook or not webhook_url:
print("[WebhookImage] Disabled or URL not set — skipping.")
return ()
temp_dir = os.path.join(roop_dir, "temp_io")
os.makedirs(temp_dir, exist_ok=True)
output_path = os.path.join(temp_dir, filename)
save_tensor_as_image(image_tensor, output_path)
try:
with open(output_path, 'rb') as f:
file_data = f.read()
headers = {}
if webhook_secret:
signature = 'sha256=' + hmac.new(
webhook_secret.encode('utf-8'),
file_data,
hashlib.sha256
).hexdigest()
headers['X-Hub-Signature-256'] = signature
files = {
'file': (filename, file_data, 'image/png')
}
resp = requests.post(webhook_url, headers=headers, files=files)
resp.raise_for_status()
print(f"[WebhookImage] Sent image: {filename}{webhook_url}")
except Exception as e:
print(f"[WebhookImage Error] {e}")
return ()
class RoopSendWebhookFile:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"file_path": ("STRING",),
"filename": ("STRING", {"default": "output.mp4"}),
"webhook_url": ("STRING", {"default": ""}),
"webhook_secret": ("STRING", {"default": ""}),
"enable_webhook": ("BOOLEAN", {"default": True})
}
}
RETURN_TYPES = ()
RETURN_NAMES = ()
FUNCTION = "run"
CATEGORY = "Roop/Webhook"
def run(self, file_path, filename, webhook_url, webhook_secret, enable_webhook):
if not enable_webhook or not webhook_url:
print("[WebhookFile] Disabled or URL not set — skipping.")
return ()
if not os.path.exists(file_path):
print(f"[WebhookFile] File does not exist: {file_path}")
return ()
try:
with open(file_path, 'rb') as f:
file_data = f.read()
headers = {}
if webhook_secret:
signature = 'sha256=' + hmac.new(
webhook_secret.encode('utf-8'),
file_data,
hashlib.sha256
).hexdigest()
headers['X-Hub-Signature-256'] = signature
content_type = "video/mp4" if filename.endswith(".mp4") else "application/octet-stream"
files = {
'file': (filename, file_data, content_type)
}
resp = requests.post(webhook_url, headers=headers, files=files)
resp.raise_for_status()
print(f"[WebhookFile] Sent file: {filename}{webhook_url}")
except Exception as e:
print(f"[WebhookFile Error] {e}")
return ()
# Register with ComfyUI
NODE_CLASS_MAPPINGS = {
"RoopFaceSwap": RoopFaceSwap,
"RoopFaceSwapWithEnhancer": RoopFaceSwapWithEnhancer,
"RoopBatchFaceSwap": RoopBatchFaceSwap,
"RoopFaceSwapVideo": RoopFaceSwapVideo,
"RoopSendWebhookImage": RoopSendWebhookImage,
"RoopSendWebhookFile": RoopSendWebhookFile,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RoopFaceSwap": "Roop Face Swap (Image)",
"RoopFaceSwapWithEnhancer": "Roop Face Swap + Enhancer",
"RoopBatchFaceSwap": "Roop Batch Image Folder",
"RoopFaceSwapVideo": "Roop Face Swap (Video)",
"RoopSendWebhookImage": "Roop Webhook: Image Tensor",
"RoopSendWebhookFile": "Roop Webhook: File Path",
}