Files
ChunkyPanda29-ComfyUI-Polli…/pollinations_nodes.py
T

172 lines
7.2 KiB
Python

import urllib.parse
import requests
import torch
import numpy as np
from PIL import Image
import io
import json
import os
import logging
# SILENCE LOGGERS
logging.getLogger("requests").setLevel(logging.CRITICAL)
logging.getLogger("urllib3").setLevel(logging.CRITICAL)
def get_models():
current_dir = os.path.dirname(os.path.abspath(__file__))
json_path = os.path.join(current_dir, "models.json")
try:
if os.path.exists(json_path):
with open(json_path, 'r', encoding='utf-8') as f:
return json.load(f)
except: pass
return {"image": ["flux"], "video": ["wan"], "text": ["openai"], "audio": ["elevenlabs"]}
def upload_to_pollinations(image_tensor):
try:
i = 255. * image_tensor.cpu().numpy().squeeze()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
buffered = io.BytesIO()
img.save(buffered, format="PNG")
resp = requests.post("https://media.pollinations.ai/upload", files={"file": ("image.png", buffered.getvalue(), "image/png")})
if resp.status_code == 200: return resp.json().get("url")
except: pass
return None
class PollinationsImageGen:
@classmethod
def INPUT_TYPES(s):
model_data = get_models()
return {
"required": {
"prompt": ("STRING", {"multiline": True, "default": "a cat in space"}),
"model": (model_data["image"], {"default": "flux"}),
"width": ("INT", {"default": 1024, "min": 256, "max": 4096, "step": 8}),
"height": ("INT", {"default": 1024, "min": 256, "max": 4096, "step": 8}),
"seed": ("INT", {"default": 42, "min": -1, "max": 2147483647}),
},
"optional": {
"api_key": ("STRING", {"default": "", "multiline": False}),
"image_input": ("IMAGE",),
"negative_prompt": ("STRING", {"multiline": True, "default": ""})
}
}
RETURN_TYPES = ("IMAGE", "STRING")
RETURN_NAMES = ("image", "url")
FUNCTION = "generate"
CATEGORY = "Pollinations/Image"
def generate(self, prompt, model, width, height, seed, api_key="", image_input=None, negative_prompt=""):
clean_model = model.replace("💎", "").strip()
encoded_prompt = urllib.parse.quote(prompt.replace("\n", " ").strip())
url = f"https://gen.pollinations.ai/image/{encoded_prompt}?model={clean_model}&width={width}&height={height}&seed={seed}&nologo=true"
if image_input is not None:
img_url = upload_to_pollinations(image_input)
if img_url: url += f"&image={urllib.parse.quote(img_url)}"
if negative_prompt.strip(): url += f"&negative_prompt={urllib.parse.quote(negative_prompt.strip())}"
headers = {"User-Agent": "Mozilla/5.0"}
if api_key.strip(): headers["Authorization"] = f"Bearer {api_key.strip()}"
try:
r = requests.get(url, headers=headers, timeout=120)
if r.status_code == 200:
img = Image.open(io.BytesIO(r.content)).convert("RGB")
img_np = np.array(img).astype(np.float32) / 255.0
return (torch.from_numpy(img_np)[None,], url)
except: pass
return (torch.zeros((1, 512, 512, 3)), url)
class PollinationsAudioGen:
@classmethod
def INPUT_TYPES(s):
model_data = get_models()
return {
"required": {
"text": ("STRING", {"multiline": True, "default": "Hello world"}),
"model": (model_data["audio"], {"default": "elevenlabs"}),
"voice": (["alloy", "echo", "fable", "onyx", "nova", "shimmer", "sarah", "rachel", "charlie"], {"default": "sarah"}),
},
"optional": {"api_key": ("STRING", {"default": ""})}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("audio_url",)
FUNCTION = "generate"
CATEGORY = "Pollinations/Audio"
def generate(self, text, model, voice, api_key=""):
clean_model = model.replace("💎", "").strip()
encoded_text = urllib.parse.quote(text.strip())
url = f"https://gen.pollinations.ai/audio/{encoded_text}?model={clean_model}&voice={voice}"
if api_key.strip(): url += f"&key={api_key.strip()}"
return (url,)
class PollinationsVideoGen:
@classmethod
def INPUT_TYPES(s):
model_data = get_models()
return {
"required": {
"prompt": ("STRING", {"multiline": True, "default": "cinematic sunset"}),
"model": (model_data["video"], {"default": "wan"}),
},
"optional": {
"api_key": ("STRING", {"default": ""}),
"image_input": ("IMAGE",),
"duration": ("INT", {"default": 5, "min": 2, "max": 15}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("video_url",)
FUNCTION = "generate"
CATEGORY = "Pollinations/Video"
def generate(self, prompt, model, api_key="", image_input=None, duration=5):
clean_model = model.replace("💎", "").strip()
encoded_prompt = urllib.parse.quote(prompt.strip())
url = f"https://gen.pollinations.ai/video/{encoded_prompt}?model={clean_model}&duration={duration}"
if image_input is not None:
img_url = upload_to_pollinations(image_input)
if img_url: url += f"&image={urllib.parse.quote(img_url)}"
if api_key.strip(): url += f"&key={api_key.strip()}"
return (url,)
class PollinationsTextGen:
@classmethod
def INPUT_TYPES(s):
model_data = get_models()
return {
"required": {
"prompt": ("STRING", {"multiline": True, "default": "Hi!"}),
"model": (model_data["text"], {"default": "openai"}),
"system_instruction": ("STRING", {"multiline": True, "default": "You are helpful assistant."}),
},
"optional": {"api_key": ("STRING", {"default": ""})}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate"
CATEGORY = "Pollinations/Text"
def generate(self, prompt, model, system_instruction, api_key=""):
clean_model = model.replace("💎", "").strip()
url = "https://gen.pollinations.ai/v1/chat/completions"
headers = {"Content-Type": "application/json"}
if api_key.strip(): headers["Authorization"] = f"Bearer {api_key.strip()}"
payload = {"model": clean_model, "messages": [{"role": "system", "content": system_instruction}, {"role": "user", "content": prompt}]}
try:
r = requests.post(url, json=payload, headers=headers)
return (r.json()["choices"][0]["message"]["content"],)
except: return ("Error connecting to Text API",)
NODE_CLASS_MAPPINGS = {
"PollinationsImageGen": PollinationsImageGen,
"PollinationsTextGen": PollinationsTextGen,
"PollinationsVideoGen": PollinationsVideoGen,
"PollinationsAudioGen": PollinationsAudioGen
}
NODE_DISPLAY_NAME_MAPPINGS = {
"PollinationsImageGen": "🌸🖼️ Pollinations Image Gen (BYOP)",
"PollinationsTextGen": "🌸🤖 Pollinations Text Gen (BYOP)",
"PollinationsVideoGen": "🌸🎞️ Pollinations Video Gen URL (BYOP)",
"PollinationsAudioGen": "🌸🔊 Pollinations Audio Gen (BYOP)"
}