first commit

This commit is contained in:
charlyad142
2025-10-21 15:56:23 -04:00
commit a382e5759e
5 changed files with 812 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Entornos virtuales
venv/
env/
ENV/
# IDEs y editores
.idea/
.vscode/
*.swp
*.swo
.DS_Store
# Archivos de configuración local
.env
.env.local
config.json
config.ini
# Archivos de caché y logs
*.log
.cache/
.pytest_cache/
# Archivos temporales
*.tmp
*.temp
.coverage
htmlcov/
# Archivos específicos de ComfyUI
models/
outputs/
temp/
+152
View File
@@ -0,0 +1,152 @@
# ComfyUI Replicate Image Generation & Edit
Este nodo de ComfyUI permite usar varios modelos de Replicate para generar y editar imágenes:
- **Qwen Image Edit Plus (Multi-Image)**: Usa `qwen/qwen-image-edit-plus` para editar múltiples imágenes basándose en poses de referencia
- **Qwen Image Edit (Single Image)**: Usa `qwen/qwen-image-edit` para editar una sola imagen con instrucciones de texto
- **SeedEdit 3.0 (ByteDance)**: Usa `bytedance/seededit-3.0` para edición avanzada de imágenes con control de adherencia al prompt
- **Seedream 4.0 (ByteDance)**: Usa `bytedance/seedream-4` para generación de imágenes de alta calidad con múltiples resoluciones
## Características
### Qwen Image Edit Plus (Multi-Image)
- **Edición de múltiples imágenes**: Acepta 1-5 imágenes, la primera como referencia de pose
- **Edición basada en poses**: Las demás imágenes adoptan la pose de la primera imagen
- **Flexibilidad**: Puedes usar entre 1 y 5 imágenes según tus necesidades
### Qwen Image Edit (Single Image)
- **Edición de imagen única**: Acepta una sola imagen para editar
- **Instrucciones de texto**: Usa prompts de texto para describir cómo editar la imagen
- **Simplicidad**: Ideal para ediciones directas con instrucciones claras
### SeedEdit 3.0 (ByteDance)
- **Edición avanzada**: Modelo especializado de ByteDance para edición de imágenes
- **Control de adherencia**: Parámetro `guidance_scale` para controlar qué tan literal es el resultado
- **Alta calidad**: Optimizado para ediciones precisas y detalladas
### Seedream 4.0 (ByteDance)
- **Generación de imágenes**: Modelo de ByteDance para generar imágenes desde texto
- **Múltiples resoluciones**: Soporte para 1K, 2K, 4K y dimensiones personalizadas
- **Generación secuencial**: Opción para generar múltiples imágenes relacionadas
- **Image-to-image**: Soporte para generación basada en imágenes de entrada
### Características generales
- **Integración con Replicate API**: Utiliza la API oficial de Replicate
- **Configuración flexible**: Soporte para múltiples parámetros de configuración
- **Manejo de errores robusto**: Gestión completa de errores y timeouts
## Instalación
1. Coloca este nodo en la carpeta `custom_nodes` de ComfyUI
2. Instala las dependencias necesarias:
```bash
pip install replicate requests pillow
```
## Configuración
### Opción 1: Variable de entorno (Recomendado)
```bash
export REPLICATE_API_TOKEN="tu_token_aqui"
```
### Opción 2: Archivo de configuración
Edita el archivo `config.ini` y reemplaza `tu_token_aqui` con tu token de API de Replicate.
### Opción 3: Token en el nodo
Puedes ingresar el token directamente en el campo `api_token` del nodo.
## Uso
### Qwen Image Edit Plus (Multi-Image)
1. **images**: Una o más imágenes (mínimo 1, máximo 5). La primera imagen se usa como referencia de pose, las demás se editan.
2. **prompt**: Descripción de cómo quieres que se edite la imagen (ej: "The person in the second image adopts the pose from the first image")
3. **api_token**: Token de API de Replicate (opcional si está configurado en variables de entorno)
### Qwen Image Edit (Single Image)
1. **image**: Una sola imagen para editar
2. **prompt**: Instrucciones de texto sobre cómo editar la imagen (ej: "Make the person wear a red shirt", "Change the background to a beach")
3. **api_token**: Token de API de Replicate (opcional si está configurado en variables de entorno)
### SeedEdit 3.0 (ByteDance)
1. **image**: Una sola imagen para editar
2. **prompt**: Instrucciones de texto sobre cómo editar la imagen
3. **api_token**: Token de API de Replicate (opcional si está configurado en variables de entorno)
### Seedream 4.0 (ByteDance)
1. **prompt**: Descripción de la imagen que quieres generar
2. **api_token**: Token de API de Replicate (opcional si está configurado en variables de entorno)
3. **image_input** (opcional): Imagen de referencia para generación image-to-image
### Parámetros opcionales
#### Para Qwen Image Edit Plus y Qwen Image Edit:
- **seed**: Semilla aleatoria para generación reproducible (-1 para aleatorio)
- **go_fast**: Ejecutar predicciones más rápidas con optimizaciones adicionales (por defecto: True)
- **aspect_ratio**: Proporción de aspecto para la imagen generada (por defecto: "match_input_image")
- **output_format**: Formato de salida (por defecto: "webp")
- **output_quality**: Calidad de salida de 0 a 100 (por defecto: 95)
- **disable_safety_checker**: Deshabilitar el verificador de seguridad (por defecto: False)
#### Para SeedEdit 3.0:
- **seed**: Semilla aleatoria para generación reproducible (-1 para aleatorio)
- **guidance_scale**: Control de adherencia al prompt (1.0-10.0, por defecto: 5.5)
- Valores más altos = más literal al prompt
- Valores más bajos = más creativo/artístico
#### Para Seedream 4.0:
- **size**: Resolución de la imagen (1K, 2K, 4K, custom)
- **width**: Ancho personalizado (1024-4096, solo cuando size='custom')
- **height**: Alto personalizado (1024-4096, solo cuando size='custom')
- **max_images**: Número máximo de imágenes a generar (1-15)
- **aspect_ratio**: Proporción de aspecto de la imagen
- **sequential_image_generation**: Modo de generación (disabled, auto)
## Salidas
- **image**: Imagen editada como tensor de ComfyUI
- **output_url**: URL de la imagen generada en Replicate
## Ejemplos de uso
### Qwen Image Edit Plus (Multi-Image)
1. Carga una o más imágenes en el campo `images` (mínimo 1, máximo 5)
2. La primera imagen se usa como referencia de pose
3. Las demás imágenes se editarán para adoptar la pose de la primera
4. Escribe un prompt descriptivo como "The person in the second image adopts the pose from the first image"
5. Configura tu token de API
6. Ejecuta el nodo
### Qwen Image Edit (Single Image)
1. Carga una imagen en el campo `image`
2. Escribe instrucciones claras como "Make the person wear a red shirt" o "Change the background to a beach"
3. Configura tu token de API
4. Ejecuta el nodo
### SeedEdit 3.0 (ByteDance)
1. Carga una imagen en el campo `image`
2. Escribe instrucciones detalladas sobre cómo editar la imagen
3. Ajusta el `guidance_scale` según necesites más o menos adherencia al prompt
4. Configura tu token de API
5. Ejecuta el nodo
### Seedream 4.0 (ByteDance)
1. Escribe un prompt descriptivo de la imagen que quieres generar
2. Selecciona la resolución (1K, 2K, 4K o custom)
3. Si usas 'custom', configura width y height
4. Opcionalmente conecta una imagen de referencia en `image_input`
5. Configura tu token de API
6. Ejecuta el nodo
## Obtener token de API
1. Ve a [https://replicate.com/account/api-tokens](https://replicate.com/account/api-tokens)
2. Crea una cuenta o inicia sesión
3. Genera un nuevo token de API
4. Copia el token y configúralo según una de las opciones anteriores
## Notas
- El nodo convierte las imágenes de entrada a formato base64 para enviarlas a la API
- La imagen de salida se descarga automáticamente y se convierte al formato de tensor de ComfyUI
- El nodo incluye manejo de errores completo y mensajes informativos
+3
View File
@@ -0,0 +1,3 @@
from .replicate_nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
+597
View File
@@ -0,0 +1,597 @@
import requests
import torch
import replicate
import os
import numpy as np
from PIL import Image
import io
import base64
def create_error_image():
"""Crear una imagen de error roja"""
blank = Image.new('RGB', (512, 512), color='red')
blank.putpixel((256, 256), (255, 0, 0))
tensor = torch.from_numpy(np.array(blank).astype(np.float32) / 255.0)
return tensor[None,]
class ReplicateQwenImageEdit:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"images": ("IMAGE",),
"prompt": ("STRING", {"multiline": True, "default": ""}),
"api_token": ("STRING", {"default": ""}),
},
"optional": {
"seed": ("INT", {"default": -1}),
"go_fast": ("BOOLEAN", {"default": True}),
"aspect_ratio": (["match_input_image", "1:1", "16:9", "9:16", "4:3", "3:4"], {"default": "match_input_image"}),
"output_format": (["webp", "jpg", "png"], {"default": "webp"}),
"output_quality": ("INT", {"default": 95, "min": 0, "max": 100}),
"disable_safety_checker": ("BOOLEAN", {"default": False}),
}
}
RETURN_TYPES = ("IMAGE", "STRING")
RETURN_NAMES = ("image", "output_url")
FUNCTION = "generate"
CATEGORY = "Replicate/Qwen"
def generate(self, **kwargs):
try:
# Obtener el token de API
api_token = self.get_api_token(kwargs['api_token'])
# Configurar el cliente de Replicate
client = replicate.Client(api_token=api_token)
# Procesar las imágenes (puede ser 1-5 imágenes)
images_tensor = kwargs['images']
# Verificar que tenemos al menos 1 imagen
if images_tensor is None or len(images_tensor) == 0:
raise Exception("Se requiere al menos 1 imagen")
# Verificar que no excedemos el máximo de 5 imágenes
if len(images_tensor) > 5:
raise Exception("Máximo 5 imágenes permitidas")
# Convertir todas las imágenes a URLs
image_urls = []
for i in range(len(images_tensor)):
image_url = self.image_to_url(images_tensor[i])
image_urls.append(image_url)
# Preparar los parámetros de entrada
input_params = {
"image": image_urls,
"prompt": kwargs['prompt'],
"go_fast": kwargs.get('go_fast', True),
"aspect_ratio": kwargs.get('aspect_ratio', "match_input_image"),
"output_format": kwargs.get('output_format', "webp"),
"output_quality": kwargs.get('output_quality', 95),
"disable_safety_checker": kwargs.get('disable_safety_checker', False),
}
# Agregar seed solo si no es -1
if kwargs.get('seed', -1) != -1:
input_params["seed"] = kwargs['seed']
# Ejecutar el modelo
output = client.run(
"qwen/qwen-image-edit-plus",
input=input_params
)
# Procesar la salida
if output and len(output) > 0:
# Obtener la URL de la imagen generada
# La salida de Replicate ya es una URL de cadena directamente
output_url = str(output[0])
# Descargar y convertir la imagen
image_tensor = self.download_and_convert_image(output_url)
return (image_tensor, output_url)
else:
raise Exception("No se recibió ninguna imagen de la API")
except Exception as e:
print(f"Error en Replicate Qwen Image Edit: {str(e)}")
return (create_error_image(), f"Error: {str(e)}")
def get_api_token(self, node_token):
"""Obtener el token de API desde el nodo o variables de entorno"""
if node_token and node_token.strip():
return node_token.strip()
# Intentar obtener desde variable de entorno
env_token = os.getenv('REPLICATE_API_TOKEN')
if env_token:
return env_token
raise ValueError("No se encontró un token de API válido. Configure REPLICATE_API_TOKEN en las variables de entorno o en el nodo.")
def image_to_url(self, image_tensor):
"""Convertir tensor de imagen a URL temporal o base64"""
try:
# Convertir tensor a PIL Image
if hasattr(image_tensor, 'numpy'):
image_array = (image_tensor.numpy().squeeze() * 255).astype(np.uint8)
else:
# Si ya es un array de numpy
image_array = (image_tensor.squeeze() * 255).astype(np.uint8)
if len(image_array.shape) == 3:
image = Image.fromarray(image_array)
else:
image = Image.fromarray(image_array, mode='L')
# Convertir a base64 data URL
buffered = io.BytesIO()
image.save(buffered, format='PNG')
img_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
return f"data:image/png;base64,{img_base64}"
except Exception as e:
print(f"Error convirtiendo imagen: {str(e)}")
print(f"Tipo de tensor: {type(image_tensor)}")
print(f"Forma del tensor: {image_tensor.shape if hasattr(image_tensor, 'shape') else 'No shape'}")
raise Exception(f"Error procesando imagen: {str(e)}")
def download_and_convert_image(self, image_url):
"""Descargar imagen desde URL y convertir a tensor"""
try:
# Descargar la imagen
response = requests.get(image_url, timeout=30)
response.raise_for_status()
# Convertir a PIL Image
image = Image.open(io.BytesIO(response.content))
# Convertir a RGB si es necesario
if image.mode != 'RGB':
image = image.convert('RGB')
# Convertir a tensor de ComfyUI
img_array = np.array(image).astype(np.float32) / 255.0
return torch.from_numpy(img_array)[None,]
except Exception as e:
print(f"Error descargando imagen: {str(e)}")
raise Exception(f"Error descargando imagen: {str(e)}")
class ReplicateQwenImageEditSingle:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"prompt": ("STRING", {"multiline": True, "default": ""}),
"api_token": ("STRING", {"default": ""}),
},
"optional": {
"seed": ("INT", {"default": -1}),
"go_fast": ("BOOLEAN", {"default": True}),
"aspect_ratio": (["match_input_image", "1:1", "16:9", "9:16", "4:3", "3:4"], {"default": "match_input_image"}),
"output_format": (["webp", "jpg", "png"], {"default": "webp"}),
"output_quality": ("INT", {"default": 95, "min": 0, "max": 100}),
"disable_safety_checker": ("BOOLEAN", {"default": False}),
}
}
RETURN_TYPES = ("IMAGE", "STRING")
RETURN_NAMES = ("image", "output_url")
FUNCTION = "generate"
CATEGORY = "Replicate/Qwen"
def generate(self, **kwargs):
try:
# Obtener el token de API
api_token = self.get_api_token(kwargs['api_token'])
# Configurar el cliente de Replicate
client = replicate.Client(api_token=api_token)
# Procesar la imagen (solo una imagen)
image_tensor = kwargs['image']
# Verificar que tenemos una imagen
if image_tensor is None or len(image_tensor) == 0:
raise Exception("Se requiere al menos 1 imagen")
# Convertir la imagen a URL
image_url = self.image_to_url(image_tensor[0]) # Tomar la primera imagen
# Preparar los parámetros de entrada
input_params = {
"image": image_url,
"prompt": kwargs['prompt'],
"go_fast": kwargs.get('go_fast', True),
"aspect_ratio": kwargs.get('aspect_ratio', "match_input_image"),
"output_format": kwargs.get('output_format', "webp"),
"output_quality": kwargs.get('output_quality', 95),
"disable_safety_checker": kwargs.get('disable_safety_checker', False),
}
# Agregar seed solo si no es -1
if kwargs.get('seed', -1) != -1:
input_params["seed"] = kwargs['seed']
# Ejecutar el modelo
output = client.run(
"qwen/qwen-image-edit",
input=input_params
)
# Procesar la salida
if output and len(output) > 0:
# Obtener la URL de la imagen generada
output_url = str(output[0])
# Descargar y convertir la imagen
image_tensor = self.download_and_convert_image(output_url)
return (image_tensor, output_url)
else:
raise Exception("No se recibió ninguna imagen de la API")
except Exception as e:
print(f"Error en Replicate Qwen Image Edit Single: {str(e)}")
return (create_error_image(), f"Error: {str(e)}")
def get_api_token(self, node_token):
"""Obtener el token de API desde el nodo o variables de entorno"""
if node_token and node_token.strip():
return node_token.strip()
# Intentar obtener desde variable de entorno
env_token = os.getenv('REPLICATE_API_TOKEN')
if env_token:
return env_token
raise ValueError("No se encontró un token de API válido. Configure REPLICATE_API_TOKEN en las variables de entorno o en el nodo.")
def image_to_url(self, image_tensor):
"""Convertir tensor de imagen a URL temporal o base64"""
try:
# Convertir tensor a PIL Image
if hasattr(image_tensor, 'numpy'):
image_array = (image_tensor.numpy().squeeze() * 255).astype(np.uint8)
else:
# Si ya es un array de numpy
image_array = (image_tensor.squeeze() * 255).astype(np.uint8)
if len(image_array.shape) == 3:
image = Image.fromarray(image_array)
else:
image = Image.fromarray(image_array, mode='L')
# Convertir a base64 data URL
buffered = io.BytesIO()
image.save(buffered, format='PNG')
img_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
return f"data:image/png;base64,{img_base64}"
except Exception as e:
print(f"Error convirtiendo imagen: {str(e)}")
print(f"Tipo de tensor: {type(image_tensor)}")
print(f"Forma del tensor: {image_tensor.shape if hasattr(image_tensor, 'shape') else 'No shape'}")
raise Exception(f"Error procesando imagen: {str(e)}")
def download_and_convert_image(self, image_url):
"""Descargar imagen desde URL y convertir a tensor"""
try:
# Descargar la imagen
response = requests.get(image_url, timeout=30)
response.raise_for_status()
# Convertir a PIL Image
image = Image.open(io.BytesIO(response.content))
# Convertir a RGB si es necesario
if image.mode != 'RGB':
image = image.convert('RGB')
# Convertir a tensor de ComfyUI
img_array = np.array(image).astype(np.float32) / 255.0
return torch.from_numpy(img_array)[None,]
except Exception as e:
print(f"Error descargando imagen: {str(e)}")
raise Exception(f"Error descargando imagen: {str(e)}")
class ReplicateSeedEdit:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"prompt": ("STRING", {"multiline": True, "default": ""}),
"api_token": ("STRING", {"default": ""}),
},
"optional": {
"seed": ("INT", {"default": -1}),
"guidance_scale": ("FLOAT", {"default": 5.5, "min": 1.0, "max": 10.0}),
}
}
RETURN_TYPES = ("IMAGE", "STRING")
RETURN_NAMES = ("image", "output_url")
FUNCTION = "generate"
CATEGORY = "Replicate/SeedEdit"
def generate(self, **kwargs):
try:
# Obtener el token de API
api_token = self.get_api_token(kwargs['api_token'])
# Configurar el cliente de Replicate
client = replicate.Client(api_token=api_token)
# Procesar la imagen (solo una imagen)
image_tensor = kwargs['image']
# Verificar que tenemos una imagen
if image_tensor is None or len(image_tensor) == 0:
raise Exception("Se requiere al menos 1 imagen")
# Convertir la imagen a URL
image_url = self.image_to_url(image_tensor[0]) # Tomar la primera imagen
# Preparar los parámetros de entrada
input_params = {
"image": image_url,
"prompt": kwargs['prompt'],
"guidance_scale": kwargs.get('guidance_scale', 5.5),
}
# Agregar seed solo si no es -1
if kwargs.get('seed', -1) != -1:
input_params["seed"] = kwargs['seed']
# Ejecutar el modelo
output = client.run(
"bytedance/seededit-3.0",
input=input_params
)
# Procesar la salida
if output:
# Obtener la URL de la imagen generada
# Para SeedEdit, la salida es directamente una URL de cadena
output_url = str(output)
# Descargar y convertir la imagen
image_tensor = self.download_and_convert_image(output_url)
return (image_tensor, output_url)
else:
raise Exception("No se recibió ninguna imagen de la API")
except Exception as e:
print(f"Error en Replicate SeedEdit: {str(e)}")
return (create_error_image(), f"Error: {str(e)}")
def get_api_token(self, node_token):
"""Obtener el token de API desde el nodo o variables de entorno"""
if node_token and node_token.strip():
return node_token.strip()
# Intentar obtener desde variable de entorno
env_token = os.getenv('REPLICATE_API_TOKEN')
if env_token:
return env_token
raise ValueError("No se encontró un token de API válido. Configure REPLICATE_API_TOKEN en las variables de entorno o en el nodo.")
def image_to_url(self, image_tensor):
"""Convertir tensor de imagen a URL temporal o base64"""
try:
# Convertir tensor a PIL Image
if hasattr(image_tensor, 'numpy'):
image_array = (image_tensor.numpy().squeeze() * 255).astype(np.uint8)
else:
# Si ya es un array de numpy
image_array = (image_tensor.squeeze() * 255).astype(np.uint8)
if len(image_array.shape) == 3:
image = Image.fromarray(image_array)
else:
image = Image.fromarray(image_array, mode='L')
# Convertir a base64 data URL
buffered = io.BytesIO()
image.save(buffered, format='PNG')
img_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
return f"data:image/png;base64,{img_base64}"
except Exception as e:
print(f"Error convirtiendo imagen: {str(e)}")
print(f"Tipo de tensor: {type(image_tensor)}")
print(f"Forma del tensor: {image_tensor.shape if hasattr(image_tensor, 'shape') else 'No shape'}")
raise Exception(f"Error procesando imagen: {str(e)}")
def download_and_convert_image(self, image_url):
"""Descargar imagen desde URL y convertir a tensor"""
try:
# Descargar la imagen
response = requests.get(image_url, timeout=30)
response.raise_for_status()
# Convertir a PIL Image
image = Image.open(io.BytesIO(response.content))
# Convertir a RGB si es necesario
if image.mode != 'RGB':
image = image.convert('RGB')
# Convertir a tensor de ComfyUI
img_array = np.array(image).astype(np.float32) / 255.0
return torch.from_numpy(img_array)[None,]
except Exception as e:
print(f"Error descargando imagen: {str(e)}")
raise Exception(f"Error descargando imagen: {str(e)}")
class ReplicateSeedream4:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"multiline": True, "default": ""}),
"api_token": ("STRING", {"default": ""}),
},
"optional": {
"image_input": ("IMAGE",),
"size": (["1K", "2K", "4K", "custom"], {"default": "2K"}),
"width": ("INT", {"default": 2048, "min": 1024, "max": 4096}),
"height": ("INT", {"default": 2048, "min": 1024, "max": 4096}),
"max_images": ("INT", {"default": 1, "min": 1, "max": 15}),
"aspect_ratio": (["match_input_image", "1:1", "16:9", "9:16", "4:3", "3:4"], {"default": "match_input_image"}),
"sequential_image_generation": (["disabled", "auto"], {"default": "disabled"}),
}
}
RETURN_TYPES = ("IMAGE", "STRING")
RETURN_NAMES = ("image", "output_url")
FUNCTION = "generate"
CATEGORY = "Replicate/Seedream"
def generate(self, **kwargs):
try:
# Obtener el token de API
api_token = self.get_api_token(kwargs['api_token'])
# Configurar el cliente de Replicate
client = replicate.Client(api_token=api_token)
# Preparar los parámetros de entrada
input_params = {
"prompt": kwargs['prompt'],
"size": kwargs.get('size', "2K"),
"max_images": kwargs.get('max_images', 1),
"aspect_ratio": kwargs.get('aspect_ratio', "match_input_image"),
"sequential_image_generation": kwargs.get('sequential_image_generation', "disabled"),
}
# Agregar dimensiones personalizadas solo si size es 'custom'
if kwargs.get('size') == 'custom':
input_params['width'] = kwargs.get('width', 2048)
input_params['height'] = kwargs.get('height', 2048)
# Procesar imagen de entrada si se proporciona
if 'image_input' in kwargs and kwargs['image_input'] is not None:
image_tensor = kwargs['image_input']
# Verificar que tenemos una imagen
if image_tensor is not None and len(image_tensor) > 0:
# Convertir todas las imágenes a URLs
image_urls = []
for i in range(len(image_tensor)):
image_url = self.image_to_url(image_tensor[i])
image_urls.append(image_url)
input_params['image_input'] = image_urls
# Ejecutar el modelo
output = client.run(
"bytedance/seedream-4",
input=input_params
)
# Procesar la salida
if output and len(output) > 0:
# Obtener la URL de la imagen generada (tomar la primera si hay múltiples)
output_url = str(output[0])
# Descargar y convertir la imagen
image_tensor = self.download_and_convert_image(output_url)
return (image_tensor, output_url)
else:
raise Exception("No se recibió ninguna imagen de la API")
except Exception as e:
print(f"Error en Replicate Seedream4: {str(e)}")
return (create_error_image(), f"Error: {str(e)}")
def get_api_token(self, node_token):
"""Obtener el token de API desde el nodo o variables de entorno"""
if node_token and node_token.strip():
return node_token.strip()
# Intentar obtener desde variable de entorno
env_token = os.getenv('REPLICATE_API_TOKEN')
if env_token:
return env_token
raise ValueError("No se encontró un token de API válido. Configure REPLICATE_API_TOKEN en las variables de entorno o en el nodo.")
def image_to_url(self, image_tensor):
"""Convertir tensor de imagen a URL temporal o base64"""
try:
# Convertir tensor a PIL Image
if hasattr(image_tensor, 'numpy'):
image_array = (image_tensor.numpy().squeeze() * 255).astype(np.uint8)
else:
# Si ya es un array de numpy
image_array = (image_tensor.squeeze() * 255).astype(np.uint8)
if len(image_array.shape) == 3:
image = Image.fromarray(image_array)
else:
image = Image.fromarray(image_array, mode='L')
# Convertir a base64 data URL
buffered = io.BytesIO()
image.save(buffered, format='PNG')
img_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
return f"data:image/png;base64,{img_base64}"
except Exception as e:
print(f"Error convirtiendo imagen: {str(e)}")
print(f"Tipo de tensor: {type(image_tensor)}")
print(f"Forma del tensor: {image_tensor.shape if hasattr(image_tensor, 'shape') else 'No shape'}")
raise Exception(f"Error procesando imagen: {str(e)}")
def download_and_convert_image(self, image_url):
"""Descargar imagen desde URL y convertir a tensor"""
try:
# Descargar la imagen
response = requests.get(image_url, timeout=30)
response.raise_for_status()
# Convertir a PIL Image
image = Image.open(io.BytesIO(response.content))
# Convertir a RGB si es necesario
if image.mode != 'RGB':
image = image.convert('RGB')
# Convertir a tensor de ComfyUI
img_array = np.array(image).astype(np.float32) / 255.0
return torch.from_numpy(img_array)[None,]
except Exception as e:
print(f"Error descargando imagen: {str(e)}")
raise Exception(f"Error descargando imagen: {str(e)}")
NODE_CLASS_MAPPINGS = {
"Replicate Qwen Image Edit Plus": ReplicateQwenImageEdit,
"Replicate Qwen Image Edit": ReplicateQwenImageEditSingle,
"Replicate SeedEdit": ReplicateSeedEdit,
"Replicate Seedream4": ReplicateSeedream4,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Replicate Qwen Image Edit Plus": "Qwen Image Edit Plus (Multi-Image)",
"Replicate Qwen Image Edit": "Qwen Image Edit (Single Image)",
"Replicate SeedEdit": "SeedEdit 3.0 (ByteDance)",
"Replicate Seedream4": "Seedream 4.0 (ByteDance)",
}
+5
View File
@@ -0,0 +1,5 @@
replicate>=0.25.0
requests>=2.25.0
Pillow>=8.0.0
torch>=1.9.0
numpy>=1.21.0