Add files via upload
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# Pollinations Premium Models
|
||||
|
||||
## Overview
|
||||
Premium models require authentication and tier-based access from Pollinations.ai. These models offer enhanced capabilities but need API tokens.
|
||||
|
||||
## Available Premium Models
|
||||
|
||||
### 🔑 Premium Image Generation
|
||||
- **gptimage**: Advanced image generation (requires "flower tier")
|
||||
- **kontext**: Image-to-image editing and enhancement (requires "seed tier")
|
||||
|
||||
### 🔑 Premium Text-to-Speech (Private Mode Only)
|
||||
- **openai-audio**: OpenAI GPT-4o Mini Audio Preview (works anonymously, private mode requires "seed tier")
|
||||
- **hypnosis-tracy**: Hypnosis Tracy voice model (works anonymously, private mode requires "seed tier")
|
||||
|
||||
## How to Get Access
|
||||
|
||||
1. **Visit Authentication Page**: https://auth.pollinations.ai
|
||||
2. **Create Account**: Sign up or log in
|
||||
3. **Request Tier Access**:
|
||||
- For gptimage: Request "flower tier" access
|
||||
- For kontext: Request "seed tier" access
|
||||
- For text-to-speech private mode: Request "seed tier" access
|
||||
4. **Get API Token**: Copy your API token from the dashboard
|
||||
|
||||
## Using Premium Nodes
|
||||
|
||||
### Premium Image Generator 🔑
|
||||
- **Node**: `PollinationsPremiumImageGen`
|
||||
- **Required**: API token, prompt, model selection
|
||||
- **Models**: gptimage, kontext
|
||||
- **Features**: Enhanced prompting, higher quality output
|
||||
|
||||
### Premium Image Editor 🔑
|
||||
- **Node**: `PollinationsPremiumImageEdit`
|
||||
- **Required**: Input image, API token, prompt, model selection
|
||||
- **Models**: gptimage, kontext
|
||||
- **Features**: Advanced image-to-image editing
|
||||
|
||||
### Text-to-Speech (Private Mode) 🔑
|
||||
- **Node**: `PollinationsTextToSpeech`
|
||||
- **Required**: Text input, authentication only for private mode
|
||||
- **Models**: openai-audio, hypnosis-tracy
|
||||
- **Features**: High-quality voice synthesis with multiple voice options
|
||||
- **Note**: Works without authentication by default, private mode requires authentication
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
1. **Install Nodes**: The premium nodes are automatically loaded with the main plugin
|
||||
2. **Get API Token**: Follow the authentication process above
|
||||
3. **Add Token**: Enter your API token in the node's "api_token" field
|
||||
4. **Select Model**: Choose the appropriate premium model
|
||||
5. **Generate**: Create your content with enhanced capabilities
|
||||
|
||||
## Error Messages
|
||||
|
||||
- **"API token required"**: You need to enter a valid API token
|
||||
- **"Authentication failed"**: Check your API token is correct
|
||||
- **"Requires higher tier access"**: Your account needs tier upgrade
|
||||
- **"tier access"**: Model requires specific tier level
|
||||
|
||||
## Free vs Premium
|
||||
|
||||
### Free Models (No Token Required)
|
||||
- **flux**: Fast, good quality image generation
|
||||
- **turbo**: Ultra-fast image generation
|
||||
|
||||
### Premium Models (Authentication Required)
|
||||
- **gptimage**: Enhanced image generation with better prompt understanding
|
||||
- **kontext**: Advanced image editing and enhancement capabilities
|
||||
- **openai-audio (private mode)**: High-quality text-to-speech with multiple voices
|
||||
- **hypnosis-tracy (private mode)**: Alternative text-to-speech model
|
||||
|
||||
## Support
|
||||
|
||||
For authentication issues or tier access requests:
|
||||
- Visit: https://auth.pollinations.ai
|
||||
- Contact Pollinations support for tier upgrades
|
||||
- Check your account dashboard for token management
|
||||
|
||||
## Notes
|
||||
|
||||
- Premium models have higher quality but require authentication
|
||||
- Free models work without any setup
|
||||
- API tokens are account-specific and should be kept secure
|
||||
- Different models require different tier levels
|
||||
+113
-120
@@ -13,92 +13,47 @@ from pydub import AudioSegment
|
||||
import io
|
||||
import folder_paths
|
||||
|
||||
# Updated based on https://image.pollinations.ai/models
|
||||
DEFAULT_IMAGE_MODELS = ["flux", "turbo"]
|
||||
# Top models from https://text.pollinations.ai/models (first few models)
|
||||
DEFAULT_TEXT_MODELS = ["openai", "openai-fast", "openai-large", "qwen-coder", "llama", "mistral"]
|
||||
|
||||
MODELS_CACHE = {"models": [], "last_update": 0}
|
||||
TEXT_MODELS_CACHE = {"model_info": [], "last_update": 0}
|
||||
TEXT_GENERATION_MODELS = [
|
||||
"openai", # OpenAI GPT-4o Mini
|
||||
"mistral", # Mistral Small 3.1 24B
|
||||
"llamascout", # Llama 4 Scout 17B
|
||||
"openai-fast", # OpenAI GPT-4.1 Nano
|
||||
"openai-reasoning", # OpenAI O3
|
||||
"phi", # Phi-4 Mini Instruct
|
||||
"qwen-coder", # Qwen 2.5 Coder 32B
|
||||
"bidara", # NASA BIDARA
|
||||
"midijourney" # MIDIjourney
|
||||
]
|
||||
|
||||
def get_available_models():
|
||||
"""Get available image models from API with caching"""
|
||||
current_time = time.time()
|
||||
|
||||
if current_time - MODELS_CACHE["last_update"] > 3600 or not MODELS_CACHE["models"]:
|
||||
try:
|
||||
response = requests.get("https://image.pollinations.ai/models", timeout=10)
|
||||
if response.status_code == 200:
|
||||
models_data = response.json()
|
||||
if models_data and len(models_data) > 0:
|
||||
MODELS_CACHE["models"] = models_data
|
||||
else:
|
||||
MODELS_CACHE["models"] = DEFAULT_IMAGE_MODELS
|
||||
MODELS_CACHE["last_update"] = current_time
|
||||
else:
|
||||
MODELS_CACHE["models"] = DEFAULT_IMAGE_MODELS
|
||||
except Exception as e:
|
||||
print(f"Error fetching image models: {e}")
|
||||
MODELS_CACHE["models"] = DEFAULT_IMAGE_MODELS
|
||||
|
||||
return MODELS_CACHE["models"]
|
||||
SEARCH_MODELS = [
|
||||
"searchgpt", # OpenAI GPT-4o Mini Search Preview
|
||||
"elixposearch" # Elixpo Search
|
||||
]
|
||||
|
||||
def get_text_models():
|
||||
"""Get available text models from API with caching"""
|
||||
current_time = time.time()
|
||||
|
||||
if current_time - TEXT_MODELS_CACHE["last_update"] > 3600 or not TEXT_MODELS_CACHE["model_info"]:
|
||||
try:
|
||||
response = requests.get("https://text.pollinations.ai/models", timeout=10)
|
||||
if response.status_code == 200:
|
||||
models_data = response.json()
|
||||
if models_data and len(models_data) > 0:
|
||||
# Store model info as {name, description} pairs
|
||||
model_info = []
|
||||
for model in models_data:
|
||||
model_info.append({
|
||||
'name': model['name'],
|
||||
'description': model.get('description', f"Default {model['name']} model")
|
||||
})
|
||||
TEXT_MODELS_CACHE["model_info"] = model_info
|
||||
else:
|
||||
TEXT_MODELS_CACHE["model_info"] = [
|
||||
{'name': name, 'description': f"Default {name} model"}
|
||||
for name in DEFAULT_TEXT_MODELS
|
||||
]
|
||||
TEXT_MODELS_CACHE["last_update"] = current_time
|
||||
else:
|
||||
TEXT_MODELS_CACHE["model_info"] = [
|
||||
{'name': name, 'description': f"Default {name} model"}
|
||||
for name in DEFAULT_TEXT_MODELS
|
||||
]
|
||||
except Exception as e:
|
||||
print(f"Error fetching text models: {e}")
|
||||
TEXT_MODELS_CACHE["model_info"] = [
|
||||
{'name': name, 'description': f"Default {name} model"}
|
||||
for name in DEFAULT_TEXT_MODELS
|
||||
]
|
||||
|
||||
# Return only descriptions for display
|
||||
return [model['description'] for model in TEXT_MODELS_CACHE["model_info"]]
|
||||
TEXT_TO_SPEECH_MODELS = [
|
||||
"openai-audio", # OpenAI GPT-4o Mini Audio Preview
|
||||
"hypnosis-tracy" # Hypnosis Tracy
|
||||
]
|
||||
|
||||
# Updated voice list from API
|
||||
AVAILABLE_VOICES = ["alloy", "echo", "fable", "onyx", "nova", "shimmer", "coral", "verse", "ballad", "ash", "sage", "amuch", "dan"]
|
||||
class PollinationsImageGen:
|
||||
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
models = get_available_models()
|
||||
default_model = "flux" if "flux" in models else models[0] if models else "flux"
|
||||
|
||||
# Use fixed model list
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"prompt": ("STRING", {"multiline": True, "placeholder": "Enter a description of the image you want..."}),
|
||||
"model": (models, {"default": default_model}),
|
||||
"model": (DEFAULT_IMAGE_MODELS, {"default": "flux"}),
|
||||
"width": ("INT", {"default": 1024, "min": 512, "max": 4096, "step": 8}),
|
||||
"height": ("INT", {"default": 1024, "min": 512, "max": 4096, "step": 8}),
|
||||
"batch_size": ("INT", {"default": 1, "min": 1, "max": 4, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
"negative_prompt": ("STRING", {"multiline": True, "default": ""}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"enhance": ("BOOLEAN", {"default": True}),
|
||||
"nologo": ("BOOLEAN", {"default": True}),
|
||||
@@ -113,41 +68,38 @@ class PollinationsImageGen:
|
||||
FUNCTION = "generate"
|
||||
CATEGORY = "🧪AILab/🌸Pollinations"
|
||||
|
||||
def generate(self, prompt, model, width, height, batch_size=1, negative_prompt="", seed=0,
|
||||
def generate(self, prompt, model, width, height, batch_size=1, seed=0,
|
||||
enhance=True, nologo=True, private=True, safe=False):
|
||||
"""Generate multiple images"""
|
||||
# Use fixed model list
|
||||
|
||||
images = []
|
||||
urls = []
|
||||
prompts = []
|
||||
|
||||
|
||||
for i in range(batch_size):
|
||||
current_seed = seed + i if seed != 0 else 0
|
||||
try:
|
||||
image, url, final_prompt = self._generate_single(
|
||||
prompt, model, width, height, negative_prompt,
|
||||
prompt, model, width, height,
|
||||
current_seed, enhance, nologo, private, safe
|
||||
)
|
||||
images.append(image)
|
||||
urls.append(url)
|
||||
prompts.append(final_prompt)
|
||||
except Exception as e:
|
||||
print(f"Error generating image {i+1}: {e}")
|
||||
images.append(torch.zeros(1, 512, 512, 3))
|
||||
urls.append(f"Error: {str(e)}")
|
||||
prompts.append(prompt)
|
||||
|
||||
|
||||
return (images, urls, prompts)
|
||||
|
||||
def _generate_single(self, prompt, model, width, height, negative_prompt="", seed=0,
|
||||
def _generate_single(self, prompt, model, width, height, seed=0,
|
||||
enhance=True, nologo=True, private=True, safe=False):
|
||||
"""Generate a single image"""
|
||||
try:
|
||||
base_url = "https://image.pollinations.ai/prompt/"
|
||||
full_prompt = prompt
|
||||
if negative_prompt:
|
||||
full_prompt = f"{prompt} ### {negative_prompt}"
|
||||
|
||||
encoded_prompt = quote(full_prompt)
|
||||
encoded_prompt = quote(prompt)
|
||||
params = {
|
||||
"model": model,
|
||||
"width": width,
|
||||
@@ -167,23 +119,21 @@ class PollinationsImageGen:
|
||||
|
||||
param_str = "&".join([f"{k}={v}" for k, v in params.items()])
|
||||
url = f"{base_url}{encoded_prompt}?{param_str}"
|
||||
|
||||
print(f"Generating image, URL: {url}")
|
||||
|
||||
response = requests.get(url, stream=True)
|
||||
response.raise_for_status()
|
||||
|
||||
final_prompt = full_prompt
|
||||
|
||||
final_prompt = prompt
|
||||
|
||||
try:
|
||||
image_url = response.url
|
||||
if "/prompt/" in image_url:
|
||||
encoded_part = image_url.split("/prompt/")[1].split("?")[0]
|
||||
extracted_prompt = unquote(encoded_part)
|
||||
if extracted_prompt != full_prompt and enhance:
|
||||
if extracted_prompt != prompt and enhance:
|
||||
final_prompt = extracted_prompt
|
||||
print(f"Enhanced prompt: {final_prompt}")
|
||||
except Exception as ee:
|
||||
print(f"Error extracting enhanced prompt: {ee}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
temp_dir = tempfile.gettempdir()
|
||||
filename = f"pollinations_{int(time.time())}.png"
|
||||
@@ -200,7 +150,6 @@ class PollinationsImageGen:
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Pollinations API error: {str(e)}"
|
||||
print(error_msg)
|
||||
empty_image = torch.zeros(1, 512, 512, 3)
|
||||
return (empty_image, error_msg, prompt)
|
||||
|
||||
@@ -208,16 +157,15 @@ class PollinationsImageGen:
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return time.time()
|
||||
|
||||
|
||||
class PollinationsTextGen:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
text_models = get_text_models()
|
||||
default_description = next((model['description'] for model in TEXT_MODELS_CACHE.get("model_info", [])
|
||||
if model['name'] == "openai"), text_models[0] if text_models else "Default openai model")
|
||||
# Use fixed model list
|
||||
return {
|
||||
"required": {
|
||||
"prompt": ("STRING", {"multiline": True, "placeholder": "Enter your text prompt..."}),
|
||||
"model": (text_models, {"default": default_description}),
|
||||
"model": (TEXT_GENERATION_MODELS, {"default": "openai"}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
},
|
||||
"optional": {
|
||||
@@ -232,15 +180,8 @@ class PollinationsTextGen:
|
||||
|
||||
def generate_text(self, prompt, model, seed, private=True):
|
||||
try:
|
||||
# Find the model name that matches this description
|
||||
model_name = None
|
||||
for model_info in TEXT_MODELS_CACHE["model_info"]:
|
||||
if model_info['description'] == model:
|
||||
model_name = model_info['name']
|
||||
break
|
||||
|
||||
if not model_name:
|
||||
model_name = "openai" # fallback
|
||||
# Use model directly from fixed list
|
||||
model_name = model if model in TEXT_GENERATION_MODELS else "openai"
|
||||
|
||||
params = {
|
||||
"model": model_name,
|
||||
@@ -249,8 +190,7 @@ class PollinationsTextGen:
|
||||
}
|
||||
param_str = "&".join([f"{k}={v}" for k, v in params.items()])
|
||||
url = f"https://text.pollinations.ai/{quote(prompt)}?{param_str}"
|
||||
|
||||
print(f"Generating Text, URL: {url}")
|
||||
|
||||
response = requests.get(url)
|
||||
if response.status_code == 200:
|
||||
return (response.text,)
|
||||
@@ -259,6 +199,48 @@ class PollinationsTextGen:
|
||||
except Exception as e:
|
||||
return (f"Text generation failed: {str(e)}",)
|
||||
|
||||
# Adding Search node
|
||||
class PollinationsSearch:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"query": ("STRING", {"multiline": True, "placeholder": "Enter your search query..."}),
|
||||
"model": (SEARCH_MODELS, {"default": "searchgpt"}),
|
||||
},
|
||||
"optional": {
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"private": ("BOOLEAN", {"default": True, "tooltip": "Keep the search private"})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("search_results",)
|
||||
FUNCTION = "search"
|
||||
CATEGORY = "🧪AILab/🌸Pollinations"
|
||||
|
||||
def search(self, query, model, seed=0, private=True):
|
||||
try:
|
||||
params = {
|
||||
"model": model,
|
||||
"seed": seed,
|
||||
"private": str(private).lower()
|
||||
}
|
||||
param_str = "&".join([f"{k}={v}" for k, v in params.items()])
|
||||
url = f"https://text.pollinations.ai/{quote(query)}?{param_str}"
|
||||
|
||||
response = requests.get(url, timeout=30)
|
||||
if response.status_code == 200:
|
||||
return (response.text,)
|
||||
else:
|
||||
return (f"Search error: {response.status_code}",)
|
||||
except Exception as e:
|
||||
return (f"Search failed: {str(e)}",)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return float("NaN")
|
||||
|
||||
# Adding Text-to-Speech node
|
||||
class PollinationsTextToSpeech:
|
||||
@classmethod
|
||||
@@ -266,11 +248,11 @@ class PollinationsTextToSpeech:
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"multiline": True, "placeholder": "Enter text to convert to speech..."}),
|
||||
"voice": (["nova", "alloy", "echo", "fable", "onyx", "shimmer"], {"default": "nova"}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"model": (TEXT_TO_SPEECH_MODELS, {"default": "openai-audio"}),
|
||||
"voice": (AVAILABLE_VOICES, {"default": "nova"}),
|
||||
},
|
||||
"optional": {
|
||||
"private": ("BOOLEAN", {"default": True, "tooltip": "Keep the generation private"})
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Random seed (requires authentication)"})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,20 +261,25 @@ class PollinationsTextToSpeech:
|
||||
FUNCTION = "generate_speech"
|
||||
CATEGORY = "🧪AILab/🌸Pollinations"
|
||||
|
||||
def generate_speech(self, text, voice, seed, private=True):
|
||||
def generate_speech(self, text, model, voice, seed=None):
|
||||
try:
|
||||
# Use selected model, fallback to openai-audio if not in list
|
||||
model_name = model if model in TEXT_TO_SPEECH_MODELS else "openai-audio"
|
||||
|
||||
params = {
|
||||
"model": "openai-audio",
|
||||
"voice": voice,
|
||||
"seed": seed,
|
||||
"private": str(private).lower()
|
||||
"model": model_name,
|
||||
"voice": voice
|
||||
}
|
||||
|
||||
# Only add seed if provided and not 0 (requires authentication)
|
||||
if seed is not None and seed != 0:
|
||||
params["seed"] = seed
|
||||
|
||||
param_str = "&".join([f"{k}={v}" for k, v in params.items()])
|
||||
url = f"https://text.pollinations.ai/{quote(text)}?{param_str}"
|
||||
|
||||
print(f"Generating Speech, URL: {url}")
|
||||
|
||||
response = requests.get(url, stream=True)
|
||||
|
||||
|
||||
if response.status_code == 200:
|
||||
# Get ComfyUI's temp directory
|
||||
temp_dir = os.path.join(folder_paths.get_output_directory(), "pollinations_temp")
|
||||
@@ -332,12 +319,16 @@ class PollinationsTextToSpeech:
|
||||
}
|
||||
|
||||
return (audio_dict, mp3_path)
|
||||
elif response.status_code == 402:
|
||||
# Payment required - authentication needed (usually when using seed)
|
||||
error_msg = "Text-to-Speech with seed parameter requires authentication. Remove seed or visit https://auth.pollinations.ai to get authentication."
|
||||
print(f"[PollinationsTextToSpeech] {error_msg}")
|
||||
return ({"waveform": torch.zeros(1, 1, 16000), "sample_rate": 16000}, "")
|
||||
else:
|
||||
print(f"Error generating speech: {response.status_code}")
|
||||
print(f"[PollinationsTextToSpeech] Error: HTTP {response.status_code} - {response.text[:200]}")
|
||||
return ({"waveform": torch.zeros(1, 1, 16000), "sample_rate": 16000}, "")
|
||||
except Exception as e:
|
||||
error_msg = f"Speech generation failed: {str(e)}"
|
||||
print(error_msg)
|
||||
print(f"[PollinationsTextToSpeech] Exception: {str(e)}")
|
||||
return ({"waveform": torch.zeros(1, 1, 16000), "sample_rate": 16000}, "")
|
||||
|
||||
@classmethod
|
||||
@@ -347,11 +338,13 @@ class PollinationsTextToSpeech:
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"PollinationsImageGen": PollinationsImageGen,
|
||||
"PollinationsTextGen": PollinationsTextGen,
|
||||
"PollinationsSearch": PollinationsSearch,
|
||||
"PollinationsTextToSpeech": PollinationsTextToSpeech,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"PollinationsImageGen": "Image Gen 🖼️ (Pollinations)",
|
||||
"PollinationsTextGen": "Text Gen 📝 (Pollinations)",
|
||||
"PollinationsSearch": "Search 🔍 (Pollinations)",
|
||||
"PollinationsTextToSpeech": "Text To Speech Chat 🔊 (Pollinations)",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
import torch
|
||||
import requests
|
||||
import tempfile
|
||||
import os
|
||||
import time
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
from urllib.parse import quote, unquote
|
||||
import base64
|
||||
import torchaudio
|
||||
import folder_paths
|
||||
|
||||
# Premium models that require authentication
|
||||
PREMIUM_IMAGE_MODELS = ["gptimage", "kontext"]
|
||||
TEXT_TO_SPEECH_MODELS = ["openai-audio", "hypnosis-tracy"]
|
||||
AVAILABLE_VOICES = ["alloy", "echo", "fable", "onyx", "nova", "shimmer", "coral", "verse", "ballad", "ash", "sage", "amuch", "dan"]
|
||||
|
||||
class PollinationsPremiumImageGen:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"api_token": ("STRING", {"default": ""}),
|
||||
"prompt": ("STRING", {"multiline": True, "default": "a beautiful landscape"}),
|
||||
"model": (PREMIUM_IMAGE_MODELS, {"default": "gptimage"}),
|
||||
"width": ("INT", {"default": 512, "min": 64, "max": 2048, "step": 64}),
|
||||
"height": ("INT", {"default": 512, "min": 64, "max": 2048, "step": 64}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 2147483647}),
|
||||
"count": ("INT", {"default": 1, "min": 1, "max": 4}),
|
||||
},
|
||||
"optional": {
|
||||
"enhance": ("BOOLEAN", {"default": True}),
|
||||
"nologo": ("BOOLEAN", {"default": True}),
|
||||
"private": ("BOOLEAN", {"default": True}),
|
||||
"safe": ("BOOLEAN", {"default": False}),
|
||||
"transparent": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "STRING", "STRING")
|
||||
RETURN_NAMES = ("images", "urls", "prompts")
|
||||
FUNCTION = "generate"
|
||||
CATEGORY = "🧪AILab/🌸Pollinations/💎Premium"
|
||||
|
||||
def generate(self, prompt, model, api_token, width=512, height=512, seed=0, count=1,
|
||||
enhance=True, nologo=True, private=True, safe=False, transparent=False):
|
||||
|
||||
if not api_token.strip():
|
||||
error_msg = f"API token required for {model} model. Get your token at: https://auth.pollinations.ai"
|
||||
empty_image = torch.zeros(1, 512, 512, 3)
|
||||
return ([empty_image], [error_msg], [prompt])
|
||||
|
||||
images = []
|
||||
urls = []
|
||||
prompts = []
|
||||
|
||||
for i in range(count):
|
||||
try:
|
||||
image, url, final_prompt = self._generate_single(
|
||||
prompt, model, api_token, width, height, seed + i,
|
||||
enhance, nologo, private, safe, transparent
|
||||
)
|
||||
images.append(image)
|
||||
urls.append(url)
|
||||
prompts.append(final_prompt)
|
||||
except Exception as e:
|
||||
images.append(torch.zeros(1, 512, 512, 3))
|
||||
urls.append(f"Error: {str(e)}")
|
||||
prompts.append(prompt)
|
||||
|
||||
return (images, urls, prompts)
|
||||
|
||||
def _generate_single(self, prompt, model, api_token, width, height, seed,
|
||||
enhance=True, nologo=True, private=True, safe=False, transparent=False):
|
||||
try:
|
||||
base_url = "https://image.pollinations.ai/prompt/"
|
||||
|
||||
encoded_prompt = quote(prompt)
|
||||
|
||||
params = {
|
||||
"model": model,
|
||||
"width": str(width),
|
||||
"height": str(height),
|
||||
"seed": str(seed),
|
||||
"nologo": str(nologo).lower(),
|
||||
"private": str(private).lower()
|
||||
}
|
||||
|
||||
if enhance and model in ["gptimage"]:
|
||||
params["enhance"] = "true"
|
||||
|
||||
if safe:
|
||||
params["safe"] = "true"
|
||||
|
||||
if transparent and model == "gptimage":
|
||||
params["transparent"] = "true"
|
||||
|
||||
param_str = "&".join([f"{k}={v}" for k, v in params.items()])
|
||||
url = f"{base_url}{encoded_prompt}?{param_str}"
|
||||
|
||||
headers = {"Authorization": f"Bearer {api_token}"}
|
||||
response = requests.get(url, stream=True, headers=headers, timeout=60)
|
||||
response.raise_for_status()
|
||||
|
||||
final_prompt = prompt
|
||||
|
||||
try:
|
||||
image_url = response.url
|
||||
if "/prompt/" in image_url:
|
||||
encoded_part = image_url.split("/prompt/")[1].split("?")[0]
|
||||
extracted_prompt = unquote(encoded_part)
|
||||
if extracted_prompt != prompt and enhance:
|
||||
final_prompt = extracted_prompt
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
temp_dir = tempfile.gettempdir()
|
||||
filename = f"pollinations_premium_{int(time.time())}.png"
|
||||
image_path = os.path.join(temp_dir, filename)
|
||||
|
||||
with open(image_path, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
|
||||
image = Image.open(image_path)
|
||||
image_array = np.array(image).astype(np.float32) / 255.0
|
||||
image_tensor = torch.from_numpy(image_array).unsqueeze(0)
|
||||
|
||||
os.unlink(image_path)
|
||||
|
||||
return (image_tensor, url, final_prompt)
|
||||
|
||||
except Exception as e:
|
||||
if "401" in str(e) or "403" in str(e):
|
||||
error_msg = f"Authentication failed. Check your API token for {model} model."
|
||||
elif "tier" in str(e).lower():
|
||||
error_msg = f"Model {model} requires higher tier access. Upgrade at: https://auth.pollinations.ai"
|
||||
else:
|
||||
error_msg = f"Premium API error: {str(e)}"
|
||||
|
||||
empty_image = torch.zeros(1, 512, 512, 3)
|
||||
return (empty_image, error_msg, prompt)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return float("NaN")
|
||||
|
||||
class PollinationsPremiumImageEdit:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"api_token": ("STRING", {"default": ""}),
|
||||
"prompt": ("STRING", {"multiline": True, "default": "enhance this image"}),
|
||||
"model": (PREMIUM_IMAGE_MODELS, {"default": "kontext"}),
|
||||
"width": ("INT", {"default": 512, "min": 64, "max": 2048, "step": 64}),
|
||||
"height": ("INT", {"default": 512, "min": 64, "max": 2048, "step": 64}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 2147483647}),
|
||||
},
|
||||
"optional": {
|
||||
"enhance": ("BOOLEAN", {"default": True}),
|
||||
"nologo": ("BOOLEAN", {"default": True}),
|
||||
"private": ("BOOLEAN", {"default": True}),
|
||||
"safe": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "STRING", "STRING")
|
||||
RETURN_NAMES = ("image", "url", "prompt")
|
||||
FUNCTION = "edit_image"
|
||||
CATEGORY = "🧪AILab/🌸Pollinations/💎Premium"
|
||||
|
||||
def edit_image(self, image, prompt, model, api_token, width=512, height=512, seed=0,
|
||||
enhance=True, nologo=True, private=True, safe=False):
|
||||
|
||||
if not api_token.strip():
|
||||
error_msg = f"API token required for {model} model. Get your token at: https://auth.pollinations.ai"
|
||||
empty_image = torch.zeros(1, 512, 512, 3)
|
||||
return (empty_image, error_msg, prompt)
|
||||
|
||||
try:
|
||||
image_np = (image.squeeze().cpu().numpy() * 255).astype(np.uint8)
|
||||
pil_image = Image.fromarray(image_np)
|
||||
|
||||
max_size = 512
|
||||
if pil_image.width > max_size or pil_image.height > max_size:
|
||||
pil_image.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
|
||||
|
||||
temp_dir = tempfile.gettempdir()
|
||||
input_filename = f"pollinations_premium_input_{int(time.time())}.jpg"
|
||||
input_path = os.path.join(temp_dir, input_filename)
|
||||
pil_image.save(input_path, "JPEG", quality=85)
|
||||
|
||||
with open(input_path, "rb") as img_file:
|
||||
img_base64 = base64.b64encode(img_file.read()).decode('utf-8')
|
||||
|
||||
os.unlink(input_path)
|
||||
|
||||
image_url = f"data:image/jpeg;base64,{img_base64}"
|
||||
|
||||
if len(image_url) > 8000:
|
||||
pass
|
||||
|
||||
edited_image, result_url, final_prompt = self._edit_single(
|
||||
prompt, model, api_token, width, height, image_url,
|
||||
seed, enhance, nologo, private, safe
|
||||
)
|
||||
|
||||
return (edited_image, result_url, final_prompt)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Premium image editing error: {str(e)}"
|
||||
empty_image = torch.zeros(1, 512, 512, 3)
|
||||
return (empty_image, error_msg, prompt)
|
||||
|
||||
def _edit_single(self, prompt, model, api_token, width, height, image_url, seed=0,
|
||||
enhance=True, nologo=True, private=True, safe=False):
|
||||
try:
|
||||
base_url = "https://image.pollinations.ai/prompt/"
|
||||
|
||||
encoded_prompt = quote(prompt)
|
||||
|
||||
params = {
|
||||
"model": model,
|
||||
"width": str(width),
|
||||
"height": str(height),
|
||||
"seed": str(seed),
|
||||
"image": image_url,
|
||||
"nologo": str(nologo).lower(),
|
||||
"private": str(private).lower()
|
||||
}
|
||||
|
||||
if enhance and model in ["kontext"]:
|
||||
params["enhance"] = "true"
|
||||
|
||||
if safe:
|
||||
params["safe"] = "true"
|
||||
|
||||
param_str = "&".join([f"{k}={quote(str(v))}" for k, v in params.items()])
|
||||
url = f"{base_url}{encoded_prompt}?{param_str}"
|
||||
|
||||
headers = {"Authorization": f"Bearer {api_token}"}
|
||||
response = requests.get(url, stream=True, timeout=60, headers=headers)
|
||||
response.raise_for_status()
|
||||
|
||||
final_prompt = prompt
|
||||
|
||||
temp_dir = tempfile.gettempdir()
|
||||
filename = f"pollinations_premium_edit_{int(time.time())}.png"
|
||||
image_path = os.path.join(temp_dir, filename)
|
||||
|
||||
with open(image_path, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
|
||||
edited_image = Image.open(image_path)
|
||||
edited_array = np.array(edited_image).astype(np.float32) / 255.0
|
||||
edited_tensor = torch.from_numpy(edited_array).unsqueeze(0)
|
||||
|
||||
os.unlink(image_path)
|
||||
|
||||
return (edited_tensor, url, final_prompt)
|
||||
|
||||
except Exception as e:
|
||||
if "401" in str(e) or "403" in str(e):
|
||||
error_msg = f"Authentication failed. Check your API token for {model} model."
|
||||
elif "tier" in str(e).lower():
|
||||
error_msg = f"Model {model} requires higher tier access. Upgrade at: https://auth.pollinations.ai"
|
||||
else:
|
||||
error_msg = f"Premium editing API error: {str(e)}"
|
||||
|
||||
empty_image = torch.zeros(1, 512, 512, 3)
|
||||
return (empty_image, error_msg, prompt)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return float("NaN")
|
||||
|
||||
|
||||
class PollinationsPremiumTextToSpeech:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"api_token": ("STRING", {"default": ""}),
|
||||
"text": ("STRING", {"multiline": True, "placeholder": "Enter text to convert to speech..."}),
|
||||
"model": (TEXT_TO_SPEECH_MODELS, {"default": "openai-audio"}),
|
||||
"voice": (AVAILABLE_VOICES, {"default": "nova"}),
|
||||
},
|
||||
"optional": {
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"private": ("BOOLEAN", {"default": True, "tooltip": "Keep the generation private"})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("AUDIO", "STRING")
|
||||
RETURN_NAMES = ("audio", "filename")
|
||||
FUNCTION = "generate_speech"
|
||||
CATEGORY = "🧪AILab/🌸Pollinations/💎Premium"
|
||||
|
||||
def generate_speech(self, api_token, text, model, voice, seed=0, private=True):
|
||||
if not api_token.strip():
|
||||
error_msg = f"API token required for premium text-to-speech. Get your token at: https://auth.pollinations.ai"
|
||||
print(f"[PollinationsPremiumTextToSpeech] {error_msg}")
|
||||
return ({"waveform": torch.zeros(1, 1, 16000), "sample_rate": 16000}, "")
|
||||
|
||||
try:
|
||||
# Use selected model, fallback to openai-audio if not in list
|
||||
model_name = model if model in TEXT_TO_SPEECH_MODELS else "openai-audio"
|
||||
|
||||
params = {
|
||||
"model": model_name,
|
||||
"voice": voice,
|
||||
"seed": seed
|
||||
}
|
||||
|
||||
# Add private parameter for premium features
|
||||
if private:
|
||||
params["private"] = "true"
|
||||
|
||||
param_str = "&".join([f"{k}={v}" for k, v in params.items()])
|
||||
url = f"https://text.pollinations.ai/{quote(text)}?{param_str}"
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_token}",
|
||||
"User-Agent": "ComfyUI-Pollinations/1.3.0"
|
||||
}
|
||||
|
||||
print(f"[PollinationsPremiumTextToSpeech] Generating speech with model: {model_name}, voice: {voice}")
|
||||
|
||||
response = requests.get(url, headers=headers, stream=True)
|
||||
|
||||
if response.status_code == 200:
|
||||
# Save the audio file
|
||||
output_dir = folder_paths.get_temp_directory()
|
||||
timestamp = int(time.time())
|
||||
filename = f"pollinations_tts_premium_{timestamp}.mp3"
|
||||
filepath = os.path.join(output_dir, filename)
|
||||
|
||||
with open(filepath, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
|
||||
# Load audio using torchaudio
|
||||
waveform, sample_rate = torchaudio.load(filepath)
|
||||
|
||||
# Convert to the format expected by ComfyUI
|
||||
audio_dict = {
|
||||
"waveform": waveform.unsqueeze(0), # Add batch dimension
|
||||
"sample_rate": sample_rate
|
||||
}
|
||||
|
||||
print(f"[PollinationsPremiumTextToSpeech] Audio generated successfully: {filename}")
|
||||
return (audio_dict, filename)
|
||||
|
||||
elif response.status_code == 401:
|
||||
error_msg = "Invalid API token. Please check your token at https://auth.pollinations.ai"
|
||||
print(f"[PollinationsPremiumTextToSpeech] {error_msg}")
|
||||
return ({"waveform": torch.zeros(1, 1, 16000), "sample_rate": 16000}, "")
|
||||
|
||||
elif response.status_code == 402:
|
||||
error_msg = "Insufficient tier access. Premium text-to-speech requires 'seed' tier or higher."
|
||||
print(f"[PollinationsPremiumTextToSpeech] {error_msg}")
|
||||
return ({"waveform": torch.zeros(1, 1, 16000), "sample_rate": 16000}, "")
|
||||
|
||||
else:
|
||||
error_msg = f"API request failed with status {response.status_code}: {response.text}"
|
||||
print(f"[PollinationsPremiumTextToSpeech] {error_msg}")
|
||||
return ({"waveform": torch.zeros(1, 1, 16000), "sample_rate": 16000}, "")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error generating speech: {str(e)}"
|
||||
print(f"[PollinationsPremiumTextToSpeech] {error_msg}")
|
||||
return ({"waveform": torch.zeros(1, 1, 16000), "sample_rate": 16000}, "")
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return float("NaN")
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"PollinationsPremiumImageGen": PollinationsPremiumImageGen,
|
||||
"PollinationsPremiumImageEdit": PollinationsPremiumImageEdit,
|
||||
"PollinationsPremiumTextToSpeech": PollinationsPremiumTextToSpeech,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"PollinationsPremiumImageGen": "Premium Image Gen 🔑 (Pollinations)",
|
||||
"PollinationsPremiumImageEdit": "Premium Image Edit 🔑 (Pollinations)",
|
||||
"PollinationsPremiumTextToSpeech": "Premium Text-to-Speech 🔑 (Pollinations)",
|
||||
}
|
||||
Reference in New Issue
Block a user