Add files via upload

This commit is contained in:
AI Lab
2025-07-06 22:36:53 -07:00
committed by GitHub
parent 41d249ea3d
commit 75258b6024
3 changed files with 591 additions and 120 deletions
+86
View File
@@ -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
View File
@@ -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)",
}
}
+392
View File
@@ -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)",
}