Files
razv23andClaude 728d83611f Fix directory structure: move files from subfolder to root
Moved all files from comfyui-razv-wavespeed-custom/ subfolder directly to razv-wavespeed/ root directory to fix ComfyUI custom node loading. ComfyUI expects node files to be in the main directory, not in a subfolder.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-10 16:07:23 +02:00

321 lines
12 KiB
Python

import time
import requests
from .utils import BaseRequest
import PIL.Image
import io
import base64
class WaveSpeedClient:
"""
WaveSpeed AI API Client
This class handles the core communication with the WaveSpeed AI API.
"""
BASE_URL = "https://api.wavespeed.ai"
def __init__(self, api_key):
"""
Initialize WaveSpeed AI API client
Args:
api_key (str): WaveSpeed AI API key
"""
self.api_key = api_key
self.once_timeout = 1800 # Default timeout is 1800 seconds (30 minutes)
self.headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
def post(self, endpoint, payload, timeout=30):
"""
Send POST request to WaveSpeed AI API
Args:
endpoint (str): API endpoint
payload (dict): Request payload
timeout (float, optional): Request timeout in seconds
Returns:
dict: API response
"""
url = f"{self.BASE_URL}{endpoint}"
response = requests.post(url, headers=self.headers, json=payload, timeout=timeout)
if response.status_code == 401:
raise Exception("Unauthorized: Invalid API key")
if response.status_code != 200:
error_message = f"Error: {response.status_code}"
try:
error_data = response.json()
if "message" in error_data:
error_message = f"Error: {error_data['message']}"
except:
pass
raise Exception(error_message)
response_data = response.json()
if isinstance(response_data, dict) and 'code' in response_data:
if response_data['code'] == 401:
raise Exception("Unauthorized: Invalid API key")
if response_data['code'] != 200:
raise Exception(f"API Error: {response_data.get('message', 'Unknown error')}")
return response_data.get('data', {})
return response_data
def get(self, endpoint, params=None, timeout=30):
"""
Send GET request to WaveSpeed AI API
Args:
endpoint (str): API endpoint
params (dict, optional): Query parameters
timeout (float, optional): Request timeout in seconds
Returns:
dict: API response
"""
url = f"{self.BASE_URL}{endpoint}"
response = requests.get(url, headers=self.headers, params=params, timeout=timeout)
if response.status_code != 200:
error_message = f"Error: {response.status_code}"
try:
error_data = response.json()
if "error" in error_data:
error_message = f"Error: {error_data['error']}"
except:
pass
raise Exception(error_message)
response_data = response.json()
if isinstance(response_data, dict) and 'code' in response_data:
if response_data['code'] != 200:
raise Exception(f"API Error: {response_data.get('message', 'Unknown error')}")
return response_data.get('data', {})
return response_data
def check_task_status(self, request_id):
"""
Check the status of a task
Args:
request_id (str): Task ID
Returns:
dict: Task status information, including status, progress, output, etc.
"""
if not request_id:
raise Exception("No valid task ID provided")
return self.get(f"/api/v2/predictions/{request_id}/result")
def wait_for_task(self, request_id, polling_interval=5, timeout=None):
"""
Wait for task completion and return the result
Args:
request_id (str, optional): Task ID.
polling_interval (int): Polling interval in seconds.
timeout (int): Maximum time to wait for task completion in seconds.
Returns:
dict: Task result.
Raises:
Exception: If the task fails or times out.
"""
if not timeout:
timeout = self.once_timeout
if not request_id:
raise Exception("No valid task ID provided")
start_time = time.time()
while time.time() - start_time < timeout:
task_status = self.check_task_status(request_id)
status = task_status.get("status")
if status == "completed":
return task_status
elif status == "failed":
error_message = task_status.get("error", "Task failed")
raise Exception(f"Task failed: {error_message}")
time.sleep(polling_interval)
raise Exception("Task timed out")
def send_request(self, request: BaseRequest, wait_for_completion=True, polling_interval=5, timeout=None):
"""
Sends an API request using a request object.
Args:
request (BaseRequest): The request object containing payload and endpoint logic.
wait_for_completion (bool, optional): Whether to wait for task completion.
polling_interval (int): Polling interval in seconds.
timeout (int): Maximum time to wait for task completion in seconds.
Returns:
dict: API response or task result.
"""
payload = request.build_payload()
payload["enable_base64_output"] = False
if "seed" in payload:
payload["seed"] = payload["seed"] % 9999999999 if payload["seed"] != -1 else -1
response = self.post(request.get_api_path(), payload)
request_id = response.get("id")
if not request_id:
raise Exception("No request ID in response")
if not wait_for_completion:
return {"request_id": request_id, "status": "processing"}
task_result = self.wait_for_task(request_id, polling_interval=polling_interval, timeout=timeout)
return task_result
def upload_file(self, image: PIL.Image.Image, max_retries=3):
"""
Upload a file to WaveSpeed AI API with retry logic for SSL issues
Args:
image (PIL.Image.Image): Image to be uploaded
max_retries (int): Maximum number of retry attempts
Returns:
str: Download URL of the uploaded file
"""
url = f"{self.BASE_URL}/api/v2/media/upload/binary"
buffered = io.BytesIO()
image.save(buffered, format="PNG")
buffered.seek(0)
headers = {'Authorization': f'Bearer {self.api_key}'}
files = {'file': ('image.png', buffered, 'image/png')}
last_exception = None
for attempt in range(max_retries):
try:
# Reset buffer position for retry attempts
buffered.seek(0)
# Configure session with SSL settings
session = requests.Session()
# Add SSL adapter configuration
session.verify = True
response = session.post(
url,
headers=headers,
files={'file': ('image.png', buffered, 'image/png')},
timeout=30
)
if response.status_code != 200:
raise Exception(f"Upload failed with status {response.status_code}: {response.text}")
response_data = response.json()
if isinstance(response_data, dict) and 'code' in response_data:
if response_data['code'] != 200:
raise Exception(f"API Error: {response_data.get('message', 'Unknown error')}")
return response_data.get('data', {})["download_url"]
# If we get here, no proper response structure
raise Exception("No download URL in response")
except (requests.exceptions.SSLError, requests.exceptions.ConnectionError) as e:
last_exception = e
print(f"Upload attempt {attempt + 1} failed with SSL/Connection error: {str(e)}")
if attempt < max_retries - 1:
wait_time = (attempt + 1) * 2 # Progressive backoff: 2, 4, 6 seconds
print(f"Retrying in {wait_time} seconds...")
time.sleep(wait_time)
continue
else:
break
except Exception as e:
# For non-SSL errors, don't retry
raise e
finally:
if 'session' in locals():
session.close()
# If we get here, all retries failed
raise Exception(f"Upload failed after {max_retries} attempts. Last error: {str(last_exception)}")
def upload_file_with_type(self, file_path: str, file_type: str, max_retries=3):
"""
Upload a file to WaveSpeed AI API with retry logic for SSL issues
Args:
file_path (str): Path to the file to be uploaded
file_type (str): MIME type of the file
max_retries (int): Maximum number of retry attempts
Returns:
str: Download URL of the uploaded file
"""
url = f"{self.BASE_URL}/api/v2/media/upload/binary"
# Determine file name based on type
file_name = ""
if "video" in file_type:
file_name = "video.mp4"
elif "image" in file_type:
file_name = "image.png"
elif "audio" in file_type:
file_name = "audio.mp3"
else:
raise Exception("Invalid file type")
headers = {'Authorization': f'Bearer {self.api_key}'}
last_exception = None
for attempt in range(max_retries):
try:
# Configure session with SSL settings
session = requests.Session()
session.verify = True
with open(file_path, "rb") as file:
files = {'file': (file_name, file, file_type)}
response = session.post(
url,
headers=headers,
files=files,
timeout=60 # Longer timeout for file uploads
)
if response.status_code != 200:
raise Exception(f"Upload failed with status {response.status_code}: {response.text}")
response_data = response.json()
if isinstance(response_data, dict) and 'code' in response_data:
if response_data['code'] != 200:
raise Exception(f"API Error: {response_data.get('message', 'Unknown error')}")
return response_data.get('data', {})["download_url"]
# If we get here, no proper response structure
raise Exception("No download URL in response")
except (requests.exceptions.SSLError, requests.exceptions.ConnectionError) as e:
last_exception = e
print(f"Upload attempt {attempt + 1} failed with SSL/Connection error: {str(e)}")
if attempt < max_retries - 1:
wait_time = (attempt + 1) * 2 # Progressive backoff: 2, 4, 6 seconds
print(f"Retrying in {wait_time} seconds...")
time.sleep(wait_time)
continue
else:
break
except Exception as e:
# For non-SSL errors, don't retry
raise e
finally:
if 'session' in locals():
session.close()
# If we get here, all retries failed
raise Exception(f"Upload failed after {max_retries} attempts. Last error: {str(last_exception)}")