update nodes for hyprlab api
This commit is contained in:
@@ -6,6 +6,7 @@ from .nodes4tuzi import (
|
||||
LoadVideoFromUrlVHS,
|
||||
LoadVideoFromUrlComfyIO,
|
||||
)
|
||||
from .nodes4hypr import HyprLab_Image_API_Node
|
||||
|
||||
# Exporting the node classes for ComfyUI to discover
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
@@ -31,6 +32,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"reg_tuzi_chat_response": RegTuziChatResponse,
|
||||
"load_video_from_url": LoadVideoFromUrlVHS,
|
||||
"load_video_from_url_comfy_core": LoadVideoFromUrlComfyIO,
|
||||
"hyprlab_image_api_node": HyprLab_Image_API_Node,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -56,4 +58,5 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"BillBum_Modified_LLM_ForceStream_Mode": "LLM StreamResponse Node (Old)",
|
||||
"BillBum_Modified_GPTImage1_API_Node": "Custom GPTImage1 API Node",
|
||||
"BillBum_Modified_Flux_API_with_imgInput": "Custom Flux API Node",
|
||||
"hyprlab_image_api_node": "HyprLab ImageGen API Node",
|
||||
}
|
||||
+1
-1
@@ -161,7 +161,7 @@ class Input_Text:
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"multiline": True},),
|
||||
"text": ("STRING", {"dynamicPrompts": True, "multiline": True},),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
import torch
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import io
|
||||
import base64
|
||||
import requests
|
||||
import random
|
||||
import tenacity
|
||||
import math
|
||||
from comfy.utils import common_upscale
|
||||
|
||||
|
||||
## ====== Utility Functions ======
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
def pil2tensor(image):
|
||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
def downscale_input(image):
|
||||
|
||||
samples = image.movedim(-1,1)
|
||||
total = int(1536 * 1024)
|
||||
scale_by = math.sqrt(total / (samples.shape[3] * samples.shape[2]))
|
||||
if scale_by >= 1:
|
||||
return image
|
||||
width = round(samples.shape[3] * scale_by)
|
||||
height = round(samples.shape[2] * scale_by)
|
||||
s = common_upscale(samples, width, height, "lanczos", "disabled")
|
||||
s = s.movedim(1,-1)
|
||||
return s
|
||||
|
||||
|
||||
## ====== HyprLab API Nodes ======
|
||||
class HyprLab_Image_API_Node:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"prompt": ("STRING", {"forceInput": True, "dynamicPrompts": True, "tooltip": "The main text prompt"}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"model": ("STRING", {"default": "nano-banana-pro"}),
|
||||
"api_url": ("STRING", {"multiline": False, "default": "https://api.hyprlab.io/v1/images/generations"}),
|
||||
"api_key": ("STRING", {"multiline": False, "default": "YOUR_API_KEY_HERE"}),
|
||||
"resolution": (["1K", "2K", "4K"], {"default": "1K"}),
|
||||
"aspect_ratio": ([
|
||||
"match_input_image", "1:1", "9:16", "16:9", "3:4",
|
||||
"4:3", "3:2", "2:3", "5:4", "4:5", "21:9"
|
||||
], {"default": "1:1"}),
|
||||
},
|
||||
"optional": {
|
||||
"image_input": ("IMAGE", {"default": None, "tooltip": "Optional input images to guide generation"}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("IMAGE",)
|
||||
FUNCTION = "generate_image"
|
||||
CATEGORY = "BillBum_API/Image_API"
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _encode_images_to_base64(images):
|
||||
if images is None:
|
||||
return []
|
||||
if images.dim() == 3:
|
||||
images = images.unsqueeze(0)
|
||||
|
||||
images = downscale_input(images)
|
||||
encoded_images = []
|
||||
for idx in range(images.shape[0]):
|
||||
tensor_image = images[idx].clamp(0.0, 1.0)
|
||||
|
||||
pil_image = tensor2pil(tensor_image)
|
||||
buffer = io.BytesIO()
|
||||
pil_image.save(buffer, format="PNG")
|
||||
encoded = base64.b64encode(buffer.getvalue()).decode("utf-8")
|
||||
|
||||
encoded_images.append(f"data:image/png;base64,{encoded}")
|
||||
return encoded_images
|
||||
|
||||
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1.25, min=5, max=30), stop=tenacity.stop_after_attempt(3))
|
||||
def generate_image(self, prompt, seed, model, api_url, api_key, resolution, aspect_ratio, image_input=None):
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {api_key}"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"model": model,
|
||||
"prompt": prompt,
|
||||
"resolution": resolution,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"output_format": "png",
|
||||
"response_format": "b64_json",
|
||||
"seed": seed
|
||||
}
|
||||
|
||||
if image_input is not None:
|
||||
encoded_imgs = self._encode_images_to_base64(image_input)
|
||||
if encoded_imgs:
|
||||
payload["image_input"] = encoded_imgs
|
||||
|
||||
try:
|
||||
response = requests.post(api_url, headers=headers, json=payload, timeout=60)
|
||||
response.raise_for_status()
|
||||
response_data = response.json()
|
||||
|
||||
images_output = []
|
||||
|
||||
data_list = response_data.get("data", [])
|
||||
if not data_list and "b64_json" in response_data:
|
||||
data_list = [response_data]
|
||||
|
||||
for item in data_list:
|
||||
b64_str = item.get("b64_json")
|
||||
if b64_str:
|
||||
img_data = base64.b64decode(b64_str)
|
||||
img = Image.open(io.BytesIO(img_data))
|
||||
|
||||
if img.mode != "RGBA":
|
||||
img = img.convert("RGBA")
|
||||
|
||||
images_output.append(pil2tensor(img))
|
||||
|
||||
if not images_output:
|
||||
print(f"API Response: {response_data}")
|
||||
raise ValueError("API did not return any valid images.")
|
||||
|
||||
return (torch.cat(images_output, dim=0),)
|
||||
|
||||
except Exception as e:
|
||||
if isinstance(e, requests.exceptions.RequestException) and e.response is not None:
|
||||
print(f"API Error Response: {e.response.text}")
|
||||
raise ValueError(f"HyprBanana API Error: {e}")
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui_billbum_api_nodes"
|
||||
description = "API call node for Third-party platforms both official and local. Support VLMs LLMs Dalle3 Flux-Pro(Support kontext, banana etc... now!! and new Support gpt-image-1!!). And some little tools: img to b64 url, b64 url to img, b64 url to b64 data, reg text to word and ',' only, etc."
|
||||
version = "1.1.6"
|
||||
version = "1.1.7"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = ["tenacity", "openai", "pillow", "requests", "torch", "numpy", "tiktoken"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user