Files
cedarconnor-upsampler/nodes.py
T
Claude Code 34c300aa28 Add Google Drive support to bypass 32MB ImgBB limit
- Add Google Drive API integration for large image uploads
- Add new node parameters: google_drive_creds_path, google_drive_folder_id, upload_method
- Auto-select upload method based on image size (>30MB uses Google Drive)
- Add comprehensive setup documentation (GOOGLE_DRIVE_SETUP.md)
- Update requirements.txt with Google API dependencies
- Add security improvements: redact API keys in logs, add .gitignore
- Fix dropdown field configuration for upload_method parameter

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-23 20:12:26 -07:00

692 lines
30 KiB
Python

import os
import requests
import time
import tempfile
import json
from typing import Tuple, Optional, Dict, Any
from urllib.parse import urlparse
from PIL import Image
import torch
import numpy as np
import folder_paths
from comfy.utils import ProgressBar
try:
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseUpload
from google.oauth2.service_account import Credentials
GOOGLE_DRIVE_AVAILABLE = True
except ImportError:
GOOGLE_DRIVE_AVAILABLE = False
class UpsamplerSmartUpscale:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
"api_key": ("STRING", {"multiline": False}),
"input_image_type": (["realism", "anime", "universal"],),
"upscale_factor": ("FLOAT", {"default": 2.0, "min": 1.0, "max": 4.0, "step": 0.1}),
"global_creativity": ("INT", {"default": 5, "min": 0, "max": 10}),
"detail": ("INT", {"default": 5, "min": 0, "max": 10}),
},
"optional": {
"imgbb_api_key": ("STRING", {"multiline": False, "default": ""}),
"google_drive_creds_path": ("STRING", {"multiline": False, "default": ""}),
"google_drive_folder_id": ("STRING", {"multiline": False, "default": ""}),
"upload_method": (["imgbb", "google_drive", "auto"], {"default": "auto"})
"description": ("STRING", {"multiline": True, "default": ""}),
"should_enhance_faces": ("BOOLEAN", {"default": False}),
"should_preserve_blur": ("BOOLEAN", {"default": False}),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("upscaled_image",)
FUNCTION = "upscale"
CATEGORY = "Upsampler"
def upscale(self, image: torch.Tensor, api_key: str, input_image_type: str,
upscale_factor: float, global_creativity: int, detail: int,
imgbb_api_key: str = "", google_drive_creds_path: str = "", google_drive_folder_id: str = "", upload_method: str = "auto",
description: str = "", should_enhance_faces: bool = False,
should_preserve_blur: bool = False) -> Tuple[torch.Tensor]:
print(f"🚀 [Upsampler] Starting Smart Upscale process...")
print(f"📏 [Upsampler] Input image shape: {image.shape}")
print(f"🎛️ [Upsampler] Parameters - Type: {input_image_type}, Factor: {upscale_factor}, Creativity: {global_creativity}, Detail: {detail}")
# Convert ComfyUI image tensor to PIL Image
pil_image = self._tensor_to_pil(image)
print(f"🖼️ [Upsampler] Converted to PIL image: {pil_image.size} ({pil_image.mode})")
# Get ImgBB API key from parameter or environment variable
hosting_api_key = imgbb_api_key or os.getenv('IMGBB_API_KEY', '')
print(f"🔑 [Upsampler] Image hosting: {'ImgBB (with API key)' if hosting_api_key else 'Free services (no API key)'}")
# Upload image to hosting service
print(f"📤 [Upsampler] Uploading image to hosting service...")
image_url = self._upload_image_temp(pil_image, hosting_api_key, google_drive_creds_path, google_drive_folder_id, upload_method)
print(f"✅ [Upsampler] Image uploaded successfully: {image_url}")
# Prepare API request
payload = {
"input": {
"imageUrl": image_url,
"inputImageType": input_image_type,
"upscaleFactor": upscale_factor,
"globalCreativity": global_creativity,
"detail": detail,
"description": description,
"shouldEnhanceFaces": should_enhance_faces,
"shouldPreserveBlur": should_preserve_blur
}
}
# Submit upscaling job
print(f"🚀 [Upsampler] Submitting job to Upsampler API...")
job_id = self._submit_upscale_job("smart-upscale", payload, api_key)
print(f"✅ [Upsampler] Job submitted successfully! Job ID: {job_id}")
# Poll for completion
print(f"⏳ [Upsampler] Waiting for upscaling to complete...")
result_image = self._wait_for_completion(job_id, api_key)
print(f"🎉 [Upsampler] Upscaling completed! Result image: {result_image.size} ({result_image.mode})")
# Convert back to ComfyUI tensor format
result_tensor = self._pil_to_tensor(result_image)
print(f"📤 [Upsampler] Final tensor shape: {result_tensor.shape}")
# Final validation
self._validate_output(result_tensor, result_image)
print(f"✨ [Upsampler] Smart Upscale process completed successfully!")
return (result_tensor,)
def _validate_output(self, tensor: torch.Tensor, image: Image.Image):
"""Validate the final output before returning"""
print(f"🔍 [Final Validation] Starting output validation...")
# Check tensor properties
print(f"📊 [Validation] Tensor - Shape: {tensor.shape}, Dtype: {tensor.dtype}, Device: {tensor.device}")
print(f"📊 [Validation] Image - Size: {image.size}, Mode: {image.mode}, Format: {image.format}")
# Calculate size increase
if len(tensor.shape) == 4:
height, width = tensor.shape[1], tensor.shape[2]
else:
height, width = tensor.shape[0], tensor.shape[1]
print(f"📏 [Validation] Output dimensions: {width}x{height}")
# Validate tensor is not empty or corrupted
if tensor.numel() == 0:
print(f"❌ [Validation] ERROR: Tensor is empty!")
raise Exception("Output tensor is empty")
if torch.isnan(tensor).any():
print(f"❌ [Validation] ERROR: Tensor contains NaN values!")
raise Exception("Output tensor contains NaN values")
if torch.isinf(tensor).any():
print(f"❌ [Validation] ERROR: Tensor contains infinite values!")
raise Exception("Output tensor contains infinite values")
print(f"✅ [Validation] Output validation passed successfully!")
print(f"🎯 [Validation] Image ready for ComfyUI pipeline!")
def _tensor_to_pil(self, tensor: torch.Tensor) -> Image.Image:
# Convert from ComfyUI format [B, H, W, C] to PIL
if len(tensor.shape) == 4:
tensor = tensor.squeeze(0)
# Convert to numpy and scale to 0-255
numpy_image = (tensor.cpu().numpy() * 255).astype(np.uint8)
return Image.fromarray(numpy_image)
def _pil_to_tensor(self, image: Image.Image) -> torch.Tensor:
# Convert PIL to ComfyUI tensor format [1, H, W, C]
print(f"🔄 [Conversion] Converting PIL image to tensor: {image.size} -> tensor")
# Validate image
if image.mode not in ['RGB', 'RGBA']:
print(f"⚠️ [Conversion] Converting from {image.mode} to RGB")
image = image.convert('RGB')
numpy_image = np.array(image).astype(np.float32) / 255.0
tensor = torch.from_numpy(numpy_image)
if len(tensor.shape) == 3:
tensor = tensor.unsqueeze(0)
print(f"✅ [Conversion] Conversion successful: {tensor.shape}")
# Validate tensor values
if tensor.min() < 0 or tensor.max() > 1:
print(f"⚠️ [Validation] Tensor values outside [0,1]: min={tensor.min():.3f}, max={tensor.max():.3f}")
else:
print(f"✅ [Validation] Tensor values valid: min={tensor.min():.3f}, max={tensor.max():.3f}")
return tensor
def _upload_image_temp(self, image: Image.Image, imgbb_api_key: str = None,
google_drive_creds_path: str = "", google_drive_folder_id: str = "", upload_method: str = "auto") -> str:
import base64
import io
# Convert image to base64 for compatibility
buffer = io.BytesIO()
image.save(buffer, format='PNG')
buffer.seek(0)
image_data = base64.b64encode(buffer.getvalue()).decode()
image_size_mb = len(buffer.getvalue()) / (1024 * 1024)
print(f"📊 [Upload] Image size: {image_size_mb:.2f} MB")
# Determine upload method
if upload_method == "google_drive":
return self._upload_to_google_drive(image, google_drive_creds_path, google_drive_folder_id)
elif upload_method == "imgbb":
if not imgbb_api_key:
raise Exception("ImgBB API key required for ImgBB upload method")
return self._upload_to_imgbb(image_data, imgbb_api_key)
elif upload_method == "auto":
# Auto-select based on image size and available credentials
if image_size_mb > 30: # Close to ImgBB limit
print(f"🔄 [Upload] Large image detected ({image_size_mb:.2f} MB), preferring Google Drive...")
if google_drive_creds_path and GOOGLE_DRIVE_AVAILABLE:
return self._upload_to_google_drive(image, google_drive_creds_path, google_drive_folder_id)
elif imgbb_api_key:
print(f"⚠️ [Upload] Image is close to ImgBB 32MB limit, but no Google Drive credentials available")
return self._upload_to_imgbb(image_data, imgbb_api_key)
else:
raise Exception(f"Image too large ({image_size_mb:.2f} MB) for free services. Please use Google Drive or ImgBB.")
else:
# Try ImgBB first for smaller images
if imgbb_api_key:
return self._upload_to_imgbb(image_data, imgbb_api_key)
elif google_drive_creds_path and GOOGLE_DRIVE_AVAILABLE:
return self._upload_to_google_drive(image, google_drive_creds_path, google_drive_folder_id)
# Fallback to free services
try:
return self._upload_to_free_service(image_data)
except Exception as e:
raise Exception(
f"Image upload failed: {str(e)}\n\n"
"To resolve this, you have several options:\n"
"1. Get a free ImgBB API key from https://api.imgbb.com/\n"
"2. Set up Google Drive API credentials\n"
"3. Add 'imgbb_api_key' or 'google_drive_creds_path' parameter\n"
"4. Set IMGBB_API_KEY environment variable\n\n"
"For large images (>30MB), Google Drive is recommended."
)
def _upload_to_imgbb(self, image_data: str, api_key: str) -> str:
"""Upload image to ImgBB service"""
print(f"🔄 [ImgBB] Uploading to ImgBB (expires in 1 hour)...")
response = requests.post(
"https://api.imgbb.com/1/upload",
data={
"key": api_key,
"image": image_data,
"expiration": 3600 # 1 hour expiration
}
)
print(f"📡 [ImgBB] Response status: {response.status_code}")
if response.status_code == 200:
result = response.json()
if result.get("success"):
url = result["data"]["url"]
print(f"✅ [ImgBB] Upload successful: {url}")
return url
else:
error_msg = result.get('error', {}).get('message', 'Unknown error')
print(f"❌ [ImgBB] Upload failed: {error_msg}")
raise Exception(f"ImgBB upload failed: {error_msg}")
else:
print(f"❌ [ImgBB] API error: {response.status_code} - {response.text}")
raise Exception(f"ImgBB API error: {response.status_code} - {response.text}")
def _upload_to_google_drive(self, image: Image.Image, creds_path: str, folder_id: str = "") -> str:
"""Upload image to Google Drive and return public URL"""
if not GOOGLE_DRIVE_AVAILABLE:
raise Exception(
"Google Drive upload requires google-api-python-client and google-auth libraries.\n"
"Install with: pip install google-api-python-client google-auth"
)
if not creds_path:
creds_path = os.getenv('GOOGLE_DRIVE_CREDS_PATH', '')
if not creds_path or not os.path.exists(creds_path):
raise Exception(
f"Google Drive credentials file not found at: {creds_path}\n"
"Please provide a valid service account JSON file path.\n"
"See: https://developers.google.com/drive/api/v3/quickstart/python"
)
print(f"🔄 [Google Drive] Authenticating with credentials: {creds_path}")
# Use folder ID from parameter first, then environment variable
if not folder_id:
folder_id = os.getenv('GOOGLE_DRIVE_FOLDER_ID', '')
if folder_id:
print(f"📁 [Google Drive] Using folder ID: {folder_id}")
else:
print(f"📁 [Google Drive] No folder specified, uploading to root directory")
try:
# Load service account credentials
creds = Credentials.from_service_account_file(
creds_path,
scopes=['https://www.googleapis.com/auth/drive.file']
)
service = build('drive', 'v3', credentials=creds)
# Save image to temp file
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
image.save(temp_file.name, format='PNG')
temp_file.close()
# Upload file to Google Drive
file_metadata = {
'name': f'upsampler_temp_{int(time.time())}.png'
}
# Add folder as parent if specified
if folder_id:
file_metadata['parents'] = [folder_id]
print(f"🔄 [Google Drive] Uploading image...")
with open(temp_file.name, 'rb') as f:
media = MediaIoBaseUpload(f, mimetype='image/png', resumable=True)
file = service.files().create(
body=file_metadata,
media_body=media,
fields='id'
).execute()
file_id = file.get('id')
print(f"✅ [Google Drive] File uploaded with ID: {file_id}")
# Make file publicly viewable
permission = {
'type': 'anyone',
'role': 'reader'
}
service.permissions().create(fileId=file_id, body=permission).execute()
print(f"✅ [Google Drive] File made publicly accessible")
# Clean up temp file
try:
os.unlink(temp_file.name)
except:
pass
# Return direct download URL
public_url = f"https://drive.google.com/uc?export=download&id={file_id}"
print(f"✅ [Google Drive] Public URL: {public_url}")
return public_url
except Exception as e:
# Clean up temp file on error
try:
os.unlink(temp_file.name)
except:
pass
print(f"❌ [Google Drive] Upload failed: {str(e)}")
raise Exception(f"Google Drive upload failed: {str(e)}")
def _upload_to_free_service(self, image_data: str) -> str:
"""Try uploading to free services that don't require API keys"""
# Try 0x0.st - a simple file sharing service
try:
return self._upload_to_0x0st(image_data)
except Exception as e1:
print(f"0x0.st upload failed: {e1}")
# Try imgbb without API key (if they support it)
try:
return self._upload_to_postimages(image_data)
except Exception as e2:
print(f"PostImages upload failed: {e2}")
raise Exception(
"All free hosting services failed. Please use ImgBB with an API key for reliable hosting."
)
def _upload_to_0x0st(self, image_data: str) -> str:
"""Upload to 0x0.st file sharing service"""
import base64
image_bytes = base64.b64decode(image_data)
response = requests.post(
'https://0x0.st',
files={'file': ('image.png', image_bytes, 'image/png')},
timeout=30
)
if response.status_code == 200:
url = response.text.strip()
if url.startswith('https://0x0.st/'):
return url
else:
raise Exception(f"Unexpected response format: {url}")
else:
raise Exception(f"Upload failed with status {response.status_code}: {response.text}")
def _upload_to_postimages(self, image_data: str) -> str:
"""Upload to PostImages service"""
import base64
image_bytes = base64.b64decode(image_data)
response = requests.post(
'https://postimages.org/json/rr',
files={'upload': ('image.png', image_bytes, 'image/png')},
data={
'token': '',
'upload_session': '',
'numfiles': '1',
'gallery': '',
'ui': 'json'
},
timeout=30
)
if response.status_code == 200:
result = response.json()
if 'url' in result:
return result['url']
else:
raise Exception(f"No URL in response: {result}")
else:
raise Exception(f"Upload failed with status {response.status_code}")
def _create_local_server_url(self, image_data: str) -> str:
"""
Creates a temporary local HTTP server to serve the image.
This is a fallback option but may not work in all environments.
"""
import threading
import http.server
import socketserver
import socket
from urllib.parse import urljoin
# Find an available port
sock = socket.socket()
sock.bind(('', 0))
port = sock.getsockname()[1]
sock.close()
# Save image temporarily
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
image_bytes = base64.b64decode(image_data)
temp_file.write(image_bytes)
temp_file.close()
# This approach has limitations and is not recommended for production
raise Exception(
"Local server hosting not implemented in this version. "
"Please use ImgBB or another cloud hosting service."
)
def _submit_upscale_job(self, endpoint: str, payload: Dict[str, Any], api_key: str) -> str:
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
print(f"📡 [Upsampler API] Sending request to: https://upsampler.com/api/v1/{endpoint}")
print(f"🔑 [Upsampler API] Using API key: {'*' * 8}[REDACTED]")
response = requests.post(
f"https://upsampler.com/api/v1/{endpoint}",
json=payload,
headers=headers
)
print(f"📡 [Upsampler API] Response status: {response.status_code}")
if response.status_code != 200:
print(f"❌ [Upsampler API] Request failed: {response.status_code} - {response.text}")
raise Exception(f"API request failed: {response.status_code} - {response.text}")
result = response.json()
print(f"📋 [Upsampler API] Response: {result}")
if result.get("status") == "FAILED":
error = result.get('error')
print(f"❌ [Upsampler API] Job failed immediately: {error}")
raise Exception(f"Job failed: {error}")
job_id = result["id"]
credit_cost = result.get("creditCost", "unknown")
print(f"✅ [Upsampler API] Job queued successfully! ID: {job_id}, Cost: {credit_cost} credits")
return job_id
def _wait_for_completion(self, job_id: str, api_key: str) -> Image.Image:
headers = {
"Authorization": f"Bearer {api_key}"
}
pbar = ProgressBar(100)
progress = 0
check_count = 0
while True:
check_count += 1
print(f"🔄 [Status Check #{check_count}] Checking job status...")
response = requests.get(
f"https://upsampler.com/api/v1/status/{job_id}",
headers=headers
)
if response.status_code != 200:
print(f"❌ [Status Check] Failed: {response.status_code}")
raise Exception(f"Status check failed: {response.status_code}")
result = response.json()
status = result.get("status")
print(f"📊 [Status Check] Current status: {status}")
if status == "SUCCESS":
# Download the result image
image_url = result["imageUrl"] # Use full quality PNG
compressed_url = result.get("compressedImageUrl")
credit_cost = result.get("creditCost", "unknown")
print(f"🎉 [Success] Upscaling completed!")
print(f"💰 [Success] Credits used: {credit_cost}")
print(f"🔗 [Success] Full quality URL: {image_url}")
if compressed_url:
print(f"🔗 [Success] Compressed URL: {compressed_url}")
print(f"⬇️ [Download] Downloading result image...")
image_response = requests.get(image_url)
if image_response.status_code != 200:
print(f"❌ [Download] Failed to download: {image_response.status_code}")
raise Exception(f"Failed to download result image: {image_response.status_code}")
print(f"✅ [Download] Downloaded {len(image_response.content)} bytes")
pbar.update(100)
# Load image from response content
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
temp_file.write(image_response.content)
temp_file.close()
try:
# Open and load the image into memory
with Image.open(temp_file.name) as img:
result_image = img.copy() # Create a copy to ensure it's loaded into memory
print(f"🖼️ [Result] Final image: {result_image.size} ({result_image.mode})")
# Clean up temp file
try:
os.unlink(temp_file.name)
print(f"🧹 [Cleanup] Temporary file deleted successfully")
except PermissionError as e:
print(f"⚠️ [Cleanup] Could not delete temp file (will be cleaned up by OS): {e}")
# Don't raise - the image is loaded, temp file will be cleaned up by OS
except Exception as e:
# If anything goes wrong, try to clean up
try:
os.unlink(temp_file.name)
except:
pass
raise e
return result_image
elif status == "FAILED":
error = result.get('error')
print(f"❌ [Failed] Upscaling failed: {error}")
raise Exception(f"Upscaling failed: {error}")
elif status in ["IN_PROGRESS", "IN_QUEUE"]:
# Update progress bar
progress = min(progress + 5, 95)
pbar.update(progress)
print(f"⏳ [Waiting] Status: {status}, waiting 60 seconds...")
time.sleep(60) # Wait 60 seconds before next check
else:
print(f"❓ [Unknown] Unexpected status: {status}")
raise Exception(f"Unknown status: {status}")
class UpsamplerDynamicUpscale:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
"api_key": ("STRING", {"multiline": False}),
"input_image_type": (["realism", "anime", "universal"],),
"upscale_factor": ("FLOAT", {"default": 2.0, "min": 1.0, "max": 4.0, "step": 0.1}),
"global_creativity": ("INT", {"default": 5, "min": 0, "max": 10}),
"resemblance": ("INT", {"default": 8, "min": 0, "max": 10}),
"detail": ("INT", {"default": 5, "min": 0, "max": 10}),
},
"optional": {
"imgbb_api_key": ("STRING", {"multiline": False, "default": ""}),
"google_drive_creds_path": ("STRING", {"multiline": False, "default": ""}),
"google_drive_folder_id": ("STRING", {"multiline": False, "default": ""}),
"upload_method": (["imgbb", "google_drive", "auto"], {"default": "auto"})
"description": ("STRING", {"multiline": True, "default": ""}),
"should_enhance_faces": ("BOOLEAN", {"default": False}),
"should_preserve_hands": ("BOOLEAN", {"default": False}),
"should_preserve_blur": ("BOOLEAN", {"default": False}),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("upscaled_image",)
FUNCTION = "upscale"
CATEGORY = "Upsampler"
def upscale(self, image: torch.Tensor, api_key: str, input_image_type: str,
upscale_factor: float, global_creativity: int, resemblance: int,
detail: int, imgbb_api_key: str = "", google_drive_creds_path: str = "", google_drive_folder_id: str = "", upload_method: str = "auto",
description: str = "", should_enhance_faces: bool = False,
should_preserve_hands: bool = False, should_preserve_blur: bool = False) -> Tuple[torch.Tensor]:
# Reuse the same methods from SmartUpscale
upscaler = UpsamplerSmartUpscale()
pil_image = upscaler._tensor_to_pil(image)
# Get ImgBB API key from parameter or environment variable
hosting_api_key = imgbb_api_key or os.getenv('IMGBB_API_KEY', '')
image_url = upscaler._upload_image_temp(pil_image, hosting_api_key, google_drive_creds_path, google_drive_folder_id, upload_method)
payload = {
"input": {
"imageUrl": image_url,
"inputImageType": input_image_type,
"upscaleFactor": upscale_factor,
"globalCreativity": global_creativity,
"resemblance": resemblance,
"detail": detail,
"description": description,
"shouldEnhanceFaces": should_enhance_faces,
"shouldPreserveHands": should_preserve_hands,
"shouldPreserveBlur": should_preserve_blur
}
}
job_id = upscaler._submit_upscale_job("dynamic-upscale", payload, api_key)
result_image = upscaler._wait_for_completion(job_id, api_key)
return (upscaler._pil_to_tensor(result_image),)
class UpsamplerPreciseUpscale:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
"api_key": ("STRING", {"multiline": False}),
"input_image_type": (["realism", "anime", "universal"],),
"upscale_factor": ("FLOAT", {"default": 4.0, "min": 1.0, "max": 16.0, "step": 0.1}),
},
"optional": {
"imgbb_api_key": ("STRING", {"multiline": False, "default": ""}),
"google_drive_creds_path": ("STRING", {"multiline": False, "default": ""}),
"google_drive_folder_id": ("STRING", {"multiline": False, "default": ""}),
"upload_method": (["imgbb", "google_drive", "auto"], {"default": "auto"})
"should_enhance_faces": ("BOOLEAN", {"default": False}),
"should_preserve_blur": ("BOOLEAN", {"default": False}),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("upscaled_image",)
FUNCTION = "upscale"
CATEGORY = "Upsampler"
def upscale(self, image: torch.Tensor, api_key: str, input_image_type: str,
upscale_factor: float, imgbb_api_key: str = "", google_drive_creds_path: str = "", google_drive_folder_id: str = "", upload_method: str = "auto",
should_enhance_faces: bool = False, should_preserve_blur: bool = False) -> Tuple[torch.Tensor]:
# Reuse the same methods from SmartUpscale
upscaler = UpsamplerSmartUpscale()
pil_image = upscaler._tensor_to_pil(image)
# Get ImgBB API key from parameter or environment variable
hosting_api_key = imgbb_api_key or os.getenv('IMGBB_API_KEY', '')
image_url = upscaler._upload_image_temp(pil_image, hosting_api_key, google_drive_creds_path, google_drive_folder_id, upload_method)
payload = {
"input": {
"imageUrl": image_url,
"inputImageType": input_image_type,
"upscaleFactor": upscale_factor,
"shouldEnhanceFaces": should_enhance_faces,
"shouldPreserveBlur": should_preserve_blur
}
}
job_id = upscaler._submit_upscale_job("precise-upscale", payload, api_key)
result_image = upscaler._wait_for_completion(job_id, api_key)
return (upscaler._pil_to_tensor(result_image),)