From 5506cb5e2588e4ac6cee34674ca92cd37e7aa3ec Mon Sep 17 00:00:00 2001 From: subin Date: Sun, 5 Jan 2025 02:09:33 +0530 Subject: [PATCH] update --- __init__.py | 3 ++ err.webp | Bin 0 -> 3916 bytes swapper.py | 30 ++++++++++++++++ utils.py | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+) create mode 100644 __init__.py create mode 100644 err.webp create mode 100644 swapper.py create mode 100644 utils.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..6b2f08e --- /dev/null +++ b/__init__.py @@ -0,0 +1,3 @@ +from .swapper import NODE_CLASS_MAPPINGS + +__all__ = ['NODE_CLASS_MAPPINGS'] \ No newline at end of file diff --git a/err.webp b/err.webp new file mode 100644 index 0000000000000000000000000000000000000000..a4aa5a0a6821f91fa6f20d5caea2d7549bdde169 GIT binary patch literal 3916 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 literal 0 HcmV?d00001 diff --git a/swapper.py b/swapper.py new file mode 100644 index 0000000..1cd9c9c --- /dev/null +++ b/swapper.py @@ -0,0 +1,30 @@ +import numpy as np +from .utils import faceswapper + +class tagger_node: + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("Output",) + FUNCTION = "swap_face" + CATEGORY = "5x00/" + + @classmethod + def IS_CHANGED(cls, **kwargs): + return float("NaN") + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "Image" : ("IMAGE", {}), + "Face" : ("IMAGE", {}), + "API_Key" : ("STRING", {}), + }, + } + + def swap_face(Image, Face, API_Key): + swapped_image = faceswapper(Image, Face, API_Key) + return (swapped_image,) + +NODE_CLASS_MAPPINGS = { + "Face Swapper" : tagger_node, +} \ No newline at end of file diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..009094a --- /dev/null +++ b/utils.py @@ -0,0 +1,98 @@ +import PIL +import numpy as np +import base64 +import requests +import json +import PIL +from io import BytesIO +import torch +import time + +def tensor_to_image(tensor): + tensor = tensor*255 + tensor = np.array(tensor, dtype=np.uint8) + if np.ndim(tensor)>3: + assert tensor.shape[0] == 1 + tensor = tensor[0] + return PIL.Image.fromarray(tensor) + +def fetch_image(url, payload, headers, timeout=3, interval=0.5): + start_time = time.time() + + while time.time() - start_time < timeout: + # Make the GET request + response = requests.request("GET", url, headers=headers, json=payload) + + if response.code == 200: + data = response.json() + + # Check if the response has the expected data (e.g., status is not "pending") + if data.get("data", {}).get("status") != "pending": + return data # Response is ready + + # Wait before the next poll + time.sleep(interval) + + # If the timeout is reached, return None or perform another action + return None + +def image_to_bs64(Image: torch.Tensor) -> str: + # Convert and resize image + pil_image = tensor_to_image(Image) + max_dimension = 512 + pil_image.thumbnail((max_dimension, max_dimension)) + buffer = BytesIO() + pil_image.save(buffer, format="JPEG") + buffer.seek(0) + base64_image = base64.b64encode(buffer.getvalue()).decode("utf-8") + buffer.close() + return base64_image + +def url_to_tensor(image_url: str) -> tuple[torch.Tensor]: + # Fetch the image data from the URL + response = requests.get(image_url) + response.raise_for_status() # Raise an error if the request fails + image_data = response.content + + # Open the image with PIL and handle EXIF orientation + image = PIL.Image.open(BytesIO(image_data)) + image = PIL.ImageOps.exif_transpose(image) + + # Convert to RGBA for potential alpha channel handling + image = image.convert("RGBA") + image_np = np.array(image).astype(np.float32) / 255.0 # Normalize + + # Split the image into RGB and Alpha channels + rgb_np, alpha_np = image_np[..., :3], image_np[..., 3] + + # Convert RGB to PyTorch tensor and ensure it's in the [N, H, W, C] format + tensor_image = torch.from_numpy(rgb_np).unsqueeze(0) # Adds N dimension + + return tensor_image + +def faceswapper(Image, Face, API_Key): + + url = "https://api.piapi.ai/api/v1/task" + + payload = json.dumps({ + "model": "Qubico/image-toolkit", + "task_type": "face-swap", + "input": { + "target_image": image_to_bs64(Image), + "swap_image": image_to_bs64(Face) + } + }) + headers = { + 'x-api-key': API_Key, + 'Content-Type': 'application/json' + } + + response = requests.request("POST", url, headers=headers, data=payload) + response_data = response.json() + task_url = url + f'/{response_data.get("data", {}).get("task_id", None)}' + + img_response = requests.request("GET", task_url, headers=headers, data='') + out_url = img_response.get("data", {}).get("image_url", None) + img_out = url_to_tensor(out_url) + + return img_out \ No newline at end of file