feat: add Seedream 4 API nodes and resolution helper utility

- Add Seedream 4 text-to-image generation node with advanced parameters
- Add Seedream 4 image-to-image transformation node
- Add Seedream 4 multi-image blending node for up to 4 input images
- Add Seedream 4 sequential batch generation node with auto mode
- Add resolution helper utility node with preset resolution and aspect ratio calculations
- Update timeout from 60s to 120s for better reliability
- Add size resolution helper method to SeedImageApiHandler

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
pixelworld AI
2025-09-14 11:16:25 -05:00
co-authored by Claude
parent 090d6b6f04
commit 0c5428a57e
+346 -5
View File
@@ -44,6 +44,11 @@ class SeedResultProcessor:
class SeedImageApiHandler:
"""Utility functions for Seed image API interactions."""
@staticmethod
def resolve_size(width, height):
"""Resolve size parameter from width and height dimensions."""
return f"{width}x{height}"
@staticmethod
def generate_image(model, arguments):
"""Generate image using Seed API."""
@@ -69,7 +74,7 @@ class SeedImageApiHandler:
f"{config.get_base_url()}/images/generations",
headers=headers,
json=payload,
timeout=60
timeout=120
)
if response.status_code == 200:
@@ -92,7 +97,7 @@ class SeedImageApiHandler:
class SeedreamTextToImageNode:
"""Seedream Text-to-Image Generation Node"""
"""Seedream 3 Text-to-Image Generation Node"""
@classmethod
def INPUT_TYPES(cls):
@@ -139,7 +144,7 @@ class SeedreamTextToImageNode:
class SeedEditImageToImageNode:
"""SeedEdit Image-to-Image Generation Node"""
"""SeedEdit 3 Image-to-Image Generation Node"""
@classmethod
def INPUT_TYPES(cls):
@@ -196,14 +201,350 @@ class SeedEditImageToImageNode:
)
class Seedream4TextToImageNode:
"""Seedream 4 Text-to-Image Generation Node"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"width": ("INT", {"default": 2048, "min": 64, "max": 4096, "step": 1}),
"height": ("INT", {"default": 2048, "min": 64, "max": 4096, "step": 1}),
},
"optional": {
"seed": ("INT", {"default": -1, "min": -1, "max": 2147483647}),
},
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "generate_image"
CATEGORY = "Seed/ImageGeneration"
def generate_image(self, prompt, width, height, seed=-1):
try:
size = SeedImageApiHandler.resolve_size(width, height)
arguments = {
"prompt": prompt,
"size": size,
"response_format": "url",
"watermark": False,
}
# Only add seed if it's not -1 (random)
if seed != -1:
arguments["seed"] = seed
result = SeedImageApiHandler.generate_image(
"seedream-4-0-250828",
arguments
)
return SeedResultProcessor.process_image_result(result)
except Exception as e:
return SeedImageApiHandler.handle_image_generation_error(
"Seedream 4 Text-to-Image", str(e)
)
class Seedream4ImageToImageNode:
"""Seedream 4 Image-to-Image Generation Node"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"image": ("IMAGE",),
"width": ("INT", {"default": 2048, "min": 64, "max": 4096, "step": 1}),
"height": ("INT", {"default": 2048, "min": 64, "max": 4096, "step": 1}),
},
"optional": {
"seed": ("INT", {"default": -1, "min": -1, "max": 2147483647}),
},
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "generate_image"
CATEGORY = "Seed/ImageGeneration"
def generate_image(self, prompt, image, width, height, seed=-1):
try:
# Convert image to base64
image_base64 = BytePlusImageUtils.image_to_base64(image)
if not image_base64:
return SeedImageApiHandler.handle_image_generation_error(
"Seedream 4 Image-to-Image", "Failed to convert image to base64"
)
size = SeedImageApiHandler.resolve_size(width, height)
arguments = {
"prompt": prompt,
"image": image_base64,
"size": size,
"response_format": "url",
"watermark": False,
"sequential_image_generation": "disabled", # Single image output
}
# Only add seed if it's not -1 (random)
if seed != -1:
arguments["seed"] = seed
result = SeedImageApiHandler.generate_image(
"seedream-4-0-250828",
arguments
)
return SeedResultProcessor.process_image_result(result)
except Exception as e:
return SeedImageApiHandler.handle_image_generation_error(
"Seedream 4 Image-to-Image", str(e)
)
class Seedream4MultiImageBlendingNode:
"""Seedream 4 Multi-Image Blending Node"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"image1": ("IMAGE",),
"image2": ("IMAGE",),
"width": ("INT", {"default": 2048, "min": 64, "max": 4096, "step": 1}),
"height": ("INT", {"default": 2048, "min": 64, "max": 4096, "step": 1}),
},
"optional": {
"image3": ("IMAGE",),
"image4": ("IMAGE",),
"seed": ("INT", {"default": -1, "min": -1, "max": 2147483647}),
},
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "generate_image"
CATEGORY = "Seed/ImageGeneration"
def generate_image(self, prompt, image1, image2, width, height, image3=None, image4=None, seed=-1):
try:
# Convert images to base64
images_base64 = []
for img in [image1, image2, image3, image4]:
if img is not None:
img_b64 = BytePlusImageUtils.image_to_base64(img)
if img_b64:
images_base64.append(img_b64)
if len(images_base64) < 2:
return SeedImageApiHandler.handle_image_generation_error(
"Seedream 4 Multi-Image Blending", "At least 2 images are required"
)
size = SeedImageApiHandler.resolve_size(width, height)
arguments = {
"prompt": prompt,
"image": images_base64,
"size": size,
"response_format": "url",
"watermark": False,
"sequential_image_generation": "disabled", # Multi-image blending to single output
}
# Only add seed if it's not -1 (random)
if seed != -1:
arguments["seed"] = seed
result = SeedImageApiHandler.generate_image(
"seedream-4-0-250828",
arguments
)
return SeedResultProcessor.process_image_result(result)
except Exception as e:
return SeedImageApiHandler.handle_image_generation_error(
"Seedream 4 Multi-Image Blending", str(e)
)
class Seedream4BatchGenerationNode:
"""Seedream 4 Sequential Batch Image Generation Node"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"max_images": ("INT", {"default": 3, "min": 1, "max": 10, "step": 1}),
"width": ("INT", {"default": 2048, "min": 64, "max": 4096, "step": 1}),
"height": ("INT", {"default": 2048, "min": 64, "max": 4096, "step": 1}),
},
"optional": {
"image1": ("IMAGE",),
"image2": ("IMAGE",),
"seed": ("INT", {"default": -1, "min": -1, "max": 2147483647}),
},
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "generate_images"
CATEGORY = "Seed/ImageGeneration"
def generate_images(self, prompt, max_images, width, height, image1=None, image2=None, seed=-1):
try:
size = SeedImageApiHandler.resolve_size(width, height)
arguments = {
"prompt": prompt,
"size": size,
"response_format": "url",
"watermark": False,
"sequential_image_generation": "auto",
"sequential_image_generation_options": {
"max_images": max_images
},
"stream": False,
}
# Add images if provided
images_base64 = []
for img in [image1, image2]:
if img is not None:
img_b64 = BytePlusImageUtils.image_to_base64(img)
if img_b64:
images_base64.append(img_b64)
if images_base64:
arguments["image"] = images_base64
# Only add seed if it's not -1 (random)
if seed != -1:
arguments["seed"] = seed
result = SeedImageApiHandler.generate_image(
"seedream-4-0-250828",
arguments
)
return SeedResultProcessor.process_image_result(result)
except Exception as e:
return SeedImageApiHandler.handle_image_generation_error(
"Seedream 4 Batch Generation", str(e)
)
class ResolutionHelperNode:
"""Resolution Helper - Calculate width/height from resolution presets and aspect ratios"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"resolution": (["1K", "2K", "3K", "4K"], {"default": "2K"}),
"aspect_ratio": (["1:1 (Square)", "4:3 (Standard)", "3:4 (Portrait)", "16:9 (Widescreen)",
"9:16 (Vertical)", "3:2 (Photo)", "2:3 (Portrait Photo)", "21:9 (Ultrawide)"],
{"default": "1:1 (Square)"}),
},
}
RETURN_TYPES = ("INT", "INT")
RETURN_NAMES = ("width", "height")
FUNCTION = "calculate_resolution"
CATEGORY = "Seed/Utilities"
def calculate_resolution(self, resolution, aspect_ratio):
# Base resolutions (approximate pixel counts)
base_pixels = {
"1K": 1024 * 1024, # ~1M pixels
"2K": 2048 * 2048, # ~4M pixels
"3K": 3072 * 3072, # ~9M pixels
"4K": 4096 * 4096, # ~16M pixels
}
# Aspect ratio calculations
aspect_ratios = {
"1:1 (Square)": (1, 1),
"4:3 (Standard)": (4, 3),
"3:4 (Portrait)": (3, 4),
"16:9 (Widescreen)": (16, 9),
"9:16 (Vertical)": (9, 16),
"3:2 (Photo)": (3, 2),
"2:3 (Portrait Photo)": (2, 3),
"21:9 (Ultrawide)": (21, 9),
}
# Get target pixel count and aspect ratio
target_pixels = base_pixels[resolution]
width_ratio, height_ratio = aspect_ratios[aspect_ratio]
# API maximum: 16,777,216 pixels (4096²)
max_pixels = 16777216
# If target exceeds API limit, use the maximum instead
if target_pixels > max_pixels:
target_pixels = max_pixels
# Calculate dimensions
# For aspect ratio w:h, if total pixels = W*H, then:
# W = width_ratio * k, H = height_ratio * k
# W * H = width_ratio * height_ratio * k^2 = target_pixels
# k = sqrt(target_pixels / (width_ratio * height_ratio))
import math
k = math.sqrt(target_pixels / (width_ratio * height_ratio))
width = int(width_ratio * k)
height = int(height_ratio * k)
# Ensure dimensions are above minimum and total pixels don't exceed API limit
width = max(64, width)
height = max(64, height)
# Final safety check: if calculated pixels exceed limit, scale down proportionally
if width * height > max_pixels:
scale_factor = math.sqrt(max_pixels / (width * height))
width = int(width * scale_factor)
height = int(height * scale_factor)
# Round to even numbers for better codec compatibility, but respect pixel limit
# Check if rounding up would exceed the limit
width_even = width if width % 2 == 0 else width + 1
height_even = height if height % 2 == 0 else height + 1
if width_even * height_even > max_pixels:
# Rounding up would exceed limit, so round down instead
width = width if width % 2 == 0 else width - 1
height = height if height % 2 == 0 else height - 1
else:
# Safe to round up to even numbers
width = width_even
height = height_even
return (width, height)
# Node class mappings
NODE_CLASS_MAPPINGS = {
"SeedreamTextToImage": SeedreamTextToImageNode,
"SeedEditImageToImage": SeedEditImageToImageNode,
"Seedream4TextToImage": Seedream4TextToImageNode,
"Seedream4ImageToImage": Seedream4ImageToImageNode,
"Seedream4MultiImageBlending": Seedream4MultiImageBlendingNode,
"Seedream4BatchGeneration": Seedream4BatchGenerationNode,
"ResolutionHelper": ResolutionHelperNode,
}
# Node display name mappings
NODE_DISPLAY_NAME_MAPPINGS = {
"SeedreamTextToImage": "Seedream Text-to-Image",
"SeedEditImageToImage": "SeedEdit Image-to-Image",
"SeedreamTextToImage": "Seedream 3 Text-to-Image",
"SeedEditImageToImage": "SeedEdit 3 Image-to-Image",
"Seedream4TextToImage": "Seedream 4 Text-to-Image",
"Seedream4ImageToImage": "Seedream 4 Image-to-Image",
"Seedream4MultiImageBlending": "Seedream 4 Multi-Image Blending",
"Seedream4BatchGeneration": "Seedream 4 Batch Generation",
"ResolutionHelper": "Resolution Helper",
}