Compare commits
157
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5d4d41bb8 | ||
|
|
0f2b1115c5 | ||
|
|
b408c8781a | ||
|
|
f6e134cfd3 | ||
|
|
7855c4dba2 | ||
|
|
c1f67dd4fb | ||
|
|
c1a77106b4 | ||
|
|
0f170bfc6b | ||
|
|
6b4b9e2dee | ||
|
|
4385a8c429 | ||
|
|
dd8d70000f | ||
|
|
582e16600e | ||
|
|
33f657ccbd | ||
|
|
f8a6650b4b | ||
|
|
493d23bdcb | ||
|
|
ecf52dd764 | ||
|
|
a9f894102d | ||
|
|
e50dd5dd63 | ||
|
|
00ce39dae5 | ||
|
|
270128d32a | ||
|
|
513fec79b5 | ||
|
|
ddbf7d0695 | ||
|
|
43893286cc | ||
|
|
3befc0a2ac | ||
|
|
af6ef2a829 | ||
|
|
96d2924dfc | ||
|
|
327ace887b | ||
|
|
bc5dacb9ef | ||
|
|
fc8aa8b6a7 | ||
|
|
9960a93044 | ||
|
|
8d3eef85ca | ||
|
|
bb4108b6c4 | ||
|
|
18a5ffbcca | ||
|
|
4f3b7fb77a | ||
|
|
c3b5fea335 | ||
|
|
b8e40e90bc | ||
|
|
9b3f15b7bd | ||
|
|
f7c9ed12b0 | ||
|
|
bdd053a81b | ||
|
|
2913a9eb6b | ||
|
|
080c17f4a4 | ||
|
|
711080ffb0 | ||
|
|
69f111bfe9 | ||
|
|
011f728b0d | ||
|
|
ac9678cd23 | ||
|
|
daede982ac | ||
|
|
e716e585df | ||
|
|
1296b6a27c | ||
|
|
19f15f4ba4 | ||
|
|
30d0154caf | ||
|
|
283665f654 | ||
|
|
03e3bec952 | ||
|
|
f1793ba9ad | ||
|
|
eda093ea66 | ||
|
|
165a103f0c | ||
|
|
ad2f4a5853 | ||
|
|
43f858dca4 | ||
|
|
538cd53ac8 | ||
|
|
bae0ed3842 | ||
|
|
a164f8ec45 | ||
|
|
ebe9e2e6b1 | ||
|
|
429c51ac6d | ||
|
|
aed4832984 | ||
|
|
a78aff0fb2 | ||
|
|
6edfc55109 | ||
|
|
00dccbb17a | ||
|
|
1cb9ce4394 | ||
|
|
b2f8b8d0e3 | ||
|
|
129bc599d0 | ||
|
|
28c2631581 | ||
|
|
baadbc02bc | ||
|
|
a73045f8e6 | ||
|
|
dd74d03fd0 | ||
|
|
bfc83da41f | ||
|
|
499ec5d104 | ||
|
|
fef2e49902 | ||
|
|
2eae6dae46 | ||
|
|
28c0cdf8d5 | ||
|
|
54f380f1f0 | ||
|
|
4f7691ff93 | ||
|
|
1781beff09 | ||
|
|
502206f518 | ||
|
|
da8adda281 | ||
|
|
1c02dea96b | ||
|
|
5b77fd90a8 | ||
|
|
246e05ac3e | ||
|
|
b6f2f8b297 | ||
|
|
5052a7bf94 | ||
|
|
afc9599c63 | ||
|
|
13a3dfb2bb | ||
|
|
6573f2e43d | ||
|
|
ac7e97c348 | ||
|
|
c6fe701115 | ||
|
|
ab17ee227d | ||
|
|
2fa966e887 | ||
|
|
3b5cca2dc8 | ||
|
|
aaf0729f78 | ||
|
|
1a37414fe4 | ||
|
|
2fbbce0d1b | ||
|
|
23f9e46eff | ||
|
|
a4855c1a1a | ||
|
|
709d16cb72 | ||
|
|
55133b0910 | ||
|
|
da4c773cb7 | ||
|
|
fc1d1aff05 | ||
|
|
44adccf16d | ||
|
|
d465f55b3a | ||
|
|
fcce3cbfb3 | ||
|
|
fb1eed93ae | ||
|
|
2990e8f024 | ||
|
|
bb9b1d5755 | ||
|
|
4ac24cbd5a | ||
|
|
7a8276a8e4 | ||
|
|
68a83db7a5 | ||
|
|
cdc1e52076 | ||
|
|
449b6ebb84 | ||
|
|
02ead854bf | ||
|
|
eaca630863 | ||
|
|
c72754d15b | ||
|
|
731b03634a | ||
|
|
c5193119cf | ||
|
|
7a97620e78 | ||
|
|
8cf75db231 | ||
|
|
8c86560f23 | ||
|
|
3af0cc4341 | ||
|
|
bba67b3767 | ||
|
|
1d10365fe1 | ||
|
|
00c6822881 | ||
|
|
beadb83b5d | ||
|
|
f50bf3f2ed | ||
|
|
67f237b37c | ||
|
|
93971fc014 | ||
|
|
e4ea36e135 | ||
|
|
f42b100e1b | ||
|
|
9f3bfab023 | ||
|
|
424678c8e8 | ||
|
|
e4cf57f129 | ||
|
|
c45ad0647d | ||
|
|
158838158b | ||
|
|
6c1d953896 | ||
|
|
808721a995 | ||
|
|
7510ccca3a | ||
|
|
7d3edf6174 | ||
|
|
396d56eb2a | ||
|
|
e4a6238935 | ||
|
|
796013d91a | ||
|
|
0b935dcffc | ||
|
|
99155fb898 | ||
|
|
c266970425 | ||
|
|
83a1f760f4 | ||
|
|
7886ec31e2 | ||
|
|
cbd980ac92 | ||
|
|
0ef5288ff6 | ||
|
|
928091f7bb | ||
|
|
0a0e7eebaa | ||
|
|
6357dfccf0 | ||
|
|
9e6f75a7bf |
@@ -7,17 +7,19 @@ on:
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
# if this is a forked repository. Skipping the workflow.
|
||||
if: github.event.repository.fork == false
|
||||
if: ${{ github.repository_owner == 'Bria-AI' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@main
|
||||
uses: Comfy-Org/publish-node-action@v1
|
||||
with:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
*.pyc
|
||||
.idea
|
||||
@@ -1,30 +1,97 @@
|
||||
# BRIA ComfyUI API Nodes
|
||||
|
||||
## Overview
|
||||
This repository contains custom nodes for ComfyUI that allow access to BRIA's API endpoints. You can find our API documentation [here](https://bria-ai-api-docs.redoc.ly/#operation//generation/bria-v2/text-to-image).
|
||||
<p align="center" style="background-color:black; padding:10px;">
|
||||
<img src="./images/Bria Logo.svg" alt="BRIA Logo" width="200"/>
|
||||
</p>
|
||||
|
||||
To use the nodes in the workflow, you need a valid BRIA API token. You can get one [here](https://bria.ai/api/) (with 1000 free calls)
|
||||
This repository provides custom nodes for ComfyUI, enabling direct access to **BRIA's API endpoints** for image generation and editing workflows. **API documentation** is available [**here**](https://docs.bria.ai/).
|
||||
|
||||
You can load the workflow, which includes all available nodes, by importing the [workflow.json](workflow.json) file in this repo.
|
||||
BRIA's APIs and models are built for commercial use and trained on 100% licensed data and does not contain copyrighted materials, such as fictional characters, logos, trademarks, public figures, harmful content, or privacy-infringing content.
|
||||
|
||||
You can also download the following image and import it to comfyui:
|
||||
An API token is required to use the nodes in your workflows. Get started quickly here
|
||||
<a href="https://bria.ai/api/" style="text-decoration:none; vertical-align:middle;">
|
||||
<img src="https://img.shields.io/badge/GET%20YOUR%20TOKEN-1000%20Free%20Calls-blue?style=flat-square" alt="Get Your Token" height="20">
|
||||
</a>.
|
||||
|
||||
<img src="./images/eraser_workflow.png" alt="Original image" width="500"/>
|
||||
for direct API endpoint use, you can find our APIs through partners like [**fal.ai**](https://fal.ai/models?keywords=bria).
|
||||
For source code and weigths access, go to our [**Hugging Face**](https://huggingface.co/briaai) space.
|
||||
|
||||
An illustration of the workflow:
|
||||
To load a workflow, import the compatible workflow.json files from this [folder](workflows).
|
||||
<p align="center">
|
||||
<img src="./images/background_workflow.png" width="1200"/>
|
||||
</p>
|
||||
|
||||
<img src="./images/eraser_workflow_diagram.jpg" alt="Eraser workflow example" width="650"/> <img src="./images/original_image.jpg" alt="Original image" width="150"/>
|
||||
|
||||
## Available Nodes
|
||||
<!-- Placeholder image of cool workflows. -->
|
||||
|
||||
### Eraser
|
||||
The **Eraser** node allows users to remove specific objects or areas from an image by providing a mask.
|
||||
|
||||
This functionality is powered by BRIA's ControlNet inpainting, available on [this model card](https://huggingface.co/briaai/BRIA-2.3-ControlNet-Inpainting) on Hugging Face.
|
||||
<!-- <img src="./images/bria_api_nodes_workflow_diagram.png" alt="all workflows example" width="400"/> <img src="./images/bria_api_nodes_workflow_diagram_2.png" alt="all workflows example" width="400"/> -->
|
||||
|
||||
You can also try out BRIA's Eraser demo by visiting our Hugging Face space [here](https://huggingface.co/spaces/briaai/BRIA-Eraser-API).
|
||||
# Available Nodes
|
||||
|
||||
## Installation
|
||||
## Image Generation Nodes
|
||||
|
||||
These nodes allow you to leverage Bria's image generation capabilities within ComfyUI. We offer our latest **V2 nodes** designed for precise control via structured prompts (currently powered by the **FIBO** model), alongside our **V1 nodes** for various established pipelines.
|
||||
|
||||
### V2 Generation Nodes
|
||||
|
||||
These nodes generate images based on detailed **structured prompts** for enhanced control and consistency. They are currently powered by the state-of-the-art **FIBO** text-to-image model.
|
||||
|
||||
| **Node** | **Description** |
|
||||
| --- | --- |
|
||||
| **Generate Image** | Creates new images from text or image inputs. Internally translates the input into a structured prompt using a selected VLM bridge before generating with the image model. |
|
||||
| **Refine and Regenerate Image** | Refines a generated image using a provided `structured_prompt` (from a previous generation) and a refinement text prompt. |
|
||||
|
||||
### V1 Generation Nodes
|
||||
|
||||
These nodes create high-quality images using Bria's V1 pipelines, supporting various aspect ratios and styles.
|
||||
|
||||
| Node | Description |
|
||||
|------------------------|--------------------------------------------------------------------|
|
||||
| **Text2Image Base** | Generates images from text prompts, serving as the foundation for text-based image creation. |
|
||||
| **Text2Image Fast** | Optimized for speed, this node generates images from text prompts with faster results while maintaining quality. |
|
||||
| **Text2Image HD** | Optimized for high-resolution outputs, this node generates detailed and sharp visuals from text prompts. |
|
||||
| **Reimagine** | Guides image generation using both prompts and an input image. Preserve the original structure and depth while introducing new materials, colors, and textures. |
|
||||
|
||||
## Tailored Generation Nodes
|
||||
These nodes use pre-trained tailored models to generate images that faithfully reproduce specific visual IP elements or guidelines.
|
||||
|
||||
| Node | Description |
|
||||
|------------------------|--------------------------------------------------------------------|
|
||||
| **Tailored Gen** | Generates images using a trained tailored model, reproducing specific visual IP elements or guidelines. Use the Tailored Model Info node to load the model's default settings. |
|
||||
| **Tailored Model Info**| Retrieves the default settings and prompt prefix of a trained tailored model, which can be used to configure the Tailored Gen node. |
|
||||
| **Restyle Portrait** | Transforms the style of a portrait while preserving the person's facial features. |
|
||||
|
||||
## Image Editing Nodes
|
||||
These nodes modify specific parts of images, enabling adjustments while maintaining the integrity of the rest of the image.
|
||||
|
||||
| Node | Description |
|
||||
|------------------------|--------------------------------------------------------------------|
|
||||
| **RMBG 2.0 (Remove Background)** | Removes the background from an image, isolating the foreground subject. |
|
||||
| **Replace Background** | Replaces an image’s background with a new one, guided by either a reference image or a prompt. |
|
||||
| **Expand Image** | Expands the dimensions of an image, generating new content to fill the extended areas. |
|
||||
| **Eraser** | Removes specific objects or areas from an image by providing a mask. |
|
||||
| **GenFill** | Generates objects by prompt in a specific region of an image. |
|
||||
| **Erase Foreground** | Removes the foreground from an image, isolating the background. |
|
||||
|
||||
## Product Shot Editing Nodes
|
||||
These nodes create high-quality product images for eCommerce workflows.
|
||||
|
||||
| Node | Description |
|
||||
|------------------------|--------------------------------------------------------------------|
|
||||
| **ShotByText** | Modifies an image's background by providing a text prompt. Powered by BRIA's ControlNet Background-Generation. |
|
||||
| **ShotByImage** | Modifies an image's background by providing a reference image. Uses BRIA's ControlNet Background-Generation and Image-Prompt. |
|
||||
|
||||
## Attribution Node
|
||||
|
||||
| Node | Description |
|
||||
|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Attribution By Image Node** | This node shares generated images via API for Bria to pay attribution to the data owners who contributed to the generation. Once the images are shared with Bria, Bria calculates the attribution, completes the payment on behalf of the user, and erases the images immediately. This node should be included in any workflow using nodes of Bria’s Models (not necessary for Bria’s API nodes). You can also refer to the [**API documentation**]( https://docs.bria.ai/bria-attribution-service/other/postattributionbyimage) |
|
||||
|
||||
|
||||
|
||||
|
||||
# Installation
|
||||
There are two methods to install the BRIA ComfyUI API nodes:
|
||||
|
||||
### Method 1: Using ComfyUI's Custom Node Manager
|
||||
@@ -43,3 +110,6 @@ There are two methods to install the BRIA ComfyUI API nodes:
|
||||
```
|
||||
|
||||
3. Restart ComfyUI and load the workflows.
|
||||
|
||||
<!-- ### Campaign generation
|
||||
Coming soon -->
|
||||
|
||||
+84
-2
@@ -1,11 +1,93 @@
|
||||
from .bria_api_node import EraserNode
|
||||
from .nodes import (
|
||||
EraserNode,
|
||||
GenFillNode,
|
||||
ImageExpansionNode,
|
||||
ReplaceBgNode,
|
||||
RmbgNode,
|
||||
RemoveForegroundNode,
|
||||
ShotByTextOriginalNode,
|
||||
ShotByImageOriginalNode,
|
||||
TailoredGenNode,
|
||||
TailoredModelInfoNode,
|
||||
Text2ImageBaseNode,
|
||||
Text2ImageFastNode,
|
||||
Text2ImageHDNode,
|
||||
TailoredPortraitNode,
|
||||
ReimagineNode,
|
||||
GenerateImageNodeV2,
|
||||
RefineImageNodeV2,
|
||||
ShotByTextAutomaticNode,
|
||||
ShotByImageManualPaddingNode,
|
||||
ShotByImageAutomaticAspectRatioNode,
|
||||
ShotByImageCustomCoordinatesNode,
|
||||
ShotByImageManualPlacementNode,
|
||||
ShotByImageAutomaticNode,
|
||||
ShotByTextAutomaticAspectRatioNode,
|
||||
ShotByTextManualPlacementNode,
|
||||
ShotByTextManualPaddingNode,
|
||||
ShotByTextCustomCoordinatesNode,
|
||||
AttributionByImageNode
|
||||
)
|
||||
|
||||
# Map the node class to a name used internally by ComfyUI
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"BriaEraser": EraserNode, # Return the class, not an instance
|
||||
"BriaGenFill": GenFillNode,
|
||||
"ImageExpansionNode": ImageExpansionNode,
|
||||
"ReplaceBgNode": ReplaceBgNode,
|
||||
"RmbgNode": RmbgNode,
|
||||
"RemoveForegroundNode": RemoveForegroundNode,
|
||||
"ShotByTextOriginal": ShotByTextOriginalNode,
|
||||
"ShotByImageOriginal": ShotByImageOriginalNode,
|
||||
"ShotByTextAutomatic": ShotByTextAutomaticNode,
|
||||
"ShotByTextManualPlacement": ShotByTextManualPlacementNode,
|
||||
"ShotByTextCustomCoordinates": ShotByTextCustomCoordinatesNode,
|
||||
"ShotByTextManualPadding": ShotByTextManualPaddingNode,
|
||||
"ShotByTextAutomaticAspectRatio": ShotByTextAutomaticAspectRatioNode,
|
||||
"ShotByImageAutomatic": ShotByImageAutomaticNode,
|
||||
"ShotByImageManualPlacement": ShotByImageManualPlacementNode,
|
||||
"ShotByImageCustomCoordinates": ShotByImageCustomCoordinatesNode,
|
||||
"ShotByImageManualPadding": ShotByImageManualPaddingNode,
|
||||
"ShotByImageAutomaticAspectRatio": ShotByImageAutomaticAspectRatioNode,
|
||||
"BriaTailoredGen": TailoredGenNode,
|
||||
"TailoredModelInfoNode": TailoredModelInfoNode,
|
||||
"TailoredPortraitNode": TailoredPortraitNode,
|
||||
"Text2ImageBaseNode": Text2ImageBaseNode,
|
||||
"Text2ImageFastNode": Text2ImageFastNode,
|
||||
"Text2ImageHDNode": Text2ImageHDNode,
|
||||
"ReimagineNode": ReimagineNode,
|
||||
"AttributionByImageNode": AttributionByImageNode,
|
||||
"GenerateImageNodeV2": GenerateImageNodeV2,
|
||||
"RefineImageNodeV2": RefineImageNodeV2,
|
||||
}
|
||||
|
||||
# Map the node display name to the one shown in the ComfyUI node interface
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"BriaEraser": "Bria Eraser",
|
||||
"BriaGenFill": "Bria GenFill",
|
||||
"ImageExpansionNode": "Bria Image Expansion",
|
||||
"ReplaceBgNode": "Bria Replace Background",
|
||||
"RmbgNode": "Bria RMBG",
|
||||
"RemoveForegroundNode": "Bria Remove Foreground",
|
||||
"ShotByTextOriginal": "Shot by Text - Original",
|
||||
"ShotByImageOriginal": "Shot by Image - Original",
|
||||
"ShotByTextAutomatic": "Shot by Text - Automatic",
|
||||
"ShotByTextManualPlacement": "Shot by Text - Manual Placement",
|
||||
"ShotByTextCustomCoordinates": "Shot by Text - Custom Coordinates",
|
||||
"ShotByTextManualPadding": "Shot by Text - Manual Padding",
|
||||
"ShotByTextAutomaticAspectRatio": "Shot by Text - Automatic Aspect Ratio",
|
||||
"ShotByImageAutomatic": "Shot by Image - Automatic",
|
||||
"ShotByImageManualPlacement": "Shot by Image - Manual Placement",
|
||||
"ShotByImageCustomCoordinates": "Shot by Image - Custom Coordinates",
|
||||
"ShotByImageManualPadding": "Shot by Image - Manual Padding",
|
||||
"ShotByImageAutomaticAspectRatio": "Shot by Image - Automatic Aspect Ratio",
|
||||
"BriaTailoredGen": "Bria Tailored Gen",
|
||||
"TailoredModelInfoNode": "Bria Tailored Model Info",
|
||||
"TailoredPortraitNode": "Bria Restyle Portrait",
|
||||
"Text2ImageBaseNode": "Bria Text2Image Base",
|
||||
"Text2ImageFastNode": "Bria Text2Image Fast",
|
||||
"Text2ImageHDNode": "Bria Text2Image HD",
|
||||
"ReimagineNode": "Bria Reimagine",
|
||||
"AttributionByImageNode": "Attribution By Image Node",
|
||||
"GenerateImageNodeV2": "Generate Image",
|
||||
"RefineImageNodeV2": "Refine and Regenerate Image",
|
||||
}
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
import numpy as np
|
||||
import requests
|
||||
from PIL import Image
|
||||
import io
|
||||
import base64
|
||||
from torchvision.transforms import ToPILImage, ToTensor
|
||||
import torch
|
||||
|
||||
# Base class for shared functionality between both nodes
|
||||
class BriaAPINode:
|
||||
def __init__(self, api_url):
|
||||
self.api_url = api_url
|
||||
|
||||
def preprocess_image(self, image):
|
||||
if isinstance(image, torch.Tensor):
|
||||
# Print image shape for debugging
|
||||
if image.dim() == 4: # (batch_size, height, width, channels)
|
||||
image = image.squeeze(0) # Remove the batch dimension (1)
|
||||
# Convert to PIL after permuting to (height, width, channels)
|
||||
image = ToPILImage()(image.permute(2, 0, 1)) # (height, width, channels)
|
||||
else:
|
||||
print("Unexpected image dimensions. Expected 4D tensor.")
|
||||
return image
|
||||
|
||||
def preprocess_mask(self, mask):
|
||||
if isinstance(mask, torch.Tensor):
|
||||
# Print mask shape for debugging
|
||||
if mask.dim() == 3: # (batch_size, height, width)
|
||||
mask = mask.squeeze(0) # Remove the batch dimension (1)
|
||||
# Convert to PIL (grayscale mask)
|
||||
mask = ToPILImage()(mask) # No permute needed for grayscale
|
||||
else:
|
||||
print("Unexpected mask dimensions. Expected 3D tensor.")
|
||||
return mask
|
||||
|
||||
def image_to_base64(self, pil_image):
|
||||
# Convert a PIL image to a base64-encoded string
|
||||
buffered = io.BytesIO()
|
||||
pil_image.save(buffered, format="PNG") # Save the image to the buffer in PNG format
|
||||
buffered.seek(0) # Rewind the buffer to the beginning
|
||||
return base64.b64encode(buffered.getvalue()).decode('utf-8')
|
||||
|
||||
def process_request(self, image, mask, api_key):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
|
||||
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = self.preprocess_image(image)
|
||||
if isinstance(mask, torch.Tensor):
|
||||
mask = self.preprocess_mask(mask)
|
||||
|
||||
# Convert the image and mask directly to Base64 strings
|
||||
image_base64 = self.image_to_base64(image)
|
||||
mask_base64 = self.image_to_base64(mask)
|
||||
|
||||
# Prepare the API request payload
|
||||
payload = {
|
||||
"file": f"{image_base64}",
|
||||
"mask_file": f"{mask_base64}"
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
# Check for successful response
|
||||
if response.status_code == 200:
|
||||
print('response is 200')
|
||||
# Process the output image from API response
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['result_url'])
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = result_image.convert("RGBA")
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
# image_tensor = image_tensor = ToTensor()(output_image)
|
||||
# image_tensor = image_tensor.permute(1, 2, 0) / 255.0 # Shape now becomes [1, 2200, 1548, 3]
|
||||
# print(f"output tensor shape is: {image_tensor.shape}")
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
|
||||
# Eraser Node
|
||||
class EraserNode(BriaAPINode):
|
||||
@staticmethod
|
||||
def INPUT_TYPES():
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"mask": ("MASK",), # Binary mask input
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}) # API Key input with a default value
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("https://engine.prod.bria-api.com/v1/eraser") # Eraser API URL
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, mask, api_key):
|
||||
return self.process_request(image, mask, api_key)
|
||||
|
||||
|
||||
# Generative Fill Node
|
||||
class GenerativeFillNode(BriaAPINode):
|
||||
@staticmethod
|
||||
def INPUT_TYPES():
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"mask": ("MASK",), # Binary mask input
|
||||
"prompt": ("STRING",),
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
||||
},
|
||||
"optional": {
|
||||
"negative_prompt": ("STRING", {"default": None}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("https://engine.prod.bria-api.com/v1/gen_fill") # Eraser API URL
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, mask, prompt, negative_prompt, api_key):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
|
||||
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = self.preprocess_image(image)
|
||||
if isinstance(mask, torch.Tensor):
|
||||
mask = self.preprocess_mask(mask)
|
||||
|
||||
# Convert the image and mask directly to Base64 strings
|
||||
image_base64 = self.image_to_base64(image)
|
||||
mask_base64 = self.image_to_base64(mask)
|
||||
|
||||
# Prepare the API request payload
|
||||
payload = {
|
||||
"file": f"{image_base64}",
|
||||
"mask_file": f"{mask_base64}",
|
||||
"prompt": prompt,
|
||||
"negative_prompt": negative_prompt,
|
||||
"sync": True
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
# Check for successful response
|
||||
if response.status_code == 200:
|
||||
print('response is 200')
|
||||
# Process the output image from API response
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['urls'][0])
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = result_image.convert("RGB")
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
@@ -0,0 +1,21 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="132" height="85.104" viewBox="0 0 132 85.104">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-2{fill:#5300c9}.cls-3{fill:#80f}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Logo" transform="translate(-471.45 -246.19)">
|
||||
<circle id="Ellipse_1" data-name="Ellipse 1" cx="9.553" cy="9.553" r="9.553" transform="translate(560.271 310.103)" style="fill:#d80067"/>
|
||||
<g id="Group_56" data-name="Group 56" transform="translate(471.45 246.19)">
|
||||
<path id="Path_504" data-name="Path 504" class="cls-2" d="M690.915 569.28h-6.548a.077.077 0 0 0-.078.078v6.042a.077.077 0 0 0 .078.078h6.421c2.237 0 3.46-1.224 3.46-3.164a3.008 3.008 0 0 0-3.333-3.034z" transform="translate(-652.51 -521.037)"/>
|
||||
<path id="Path_505" data-name="Path 505" class="cls-2" d="M684.378 502.736h6a3.179 3.179 0 0 0 3.24-2.044 3.127 3.127 0 0 0-3.039-4.032h-6.2a.077.077 0 0 0-.078.078v5.922a.077.077 0 0 0 .077.076z" transform="translate(-652.518 -459.261)"/>
|
||||
<path id="Path_506" data-name="Path 506" class="cls-2" d="M877.962 498.92h-6.5a.077.077 0 0 0-.078.078v6.639a.077.077 0 0 0 .078.078h6.5c2.489 0 4.093-1.35 4.093-3.418 0-2.027-1.604-3.377-4.093-3.377z" transform="translate(-811.664 -461.183)"/>
|
||||
<path id="Path_507" data-name="Path 507" class="cls-2" d="M552.856 278.056a.077.077 0 0 1 .078-.078h2.244a42.566 42.566 0 1 0-2.322 28.112zm-41.9 28.191h-14.609a.077.077 0 0 1-.078-.078v-28.113a.077.077 0 0 1 .078-.078h14.564c5.19 0 8.565 3.08 8.565 7.721a5.646 5.646 0 0 1-4.641 5.949v.169c2.616.211 5.443 2.237 5.443 6.5 0 4.683-3.164 7.929-9.325 7.929zm30.8 0a.074.074 0 0 1-.058-.027l-8.477-9.652a.08.08 0 0 0-.058-.027h-1.913a.077.077 0 0 0-.078.078v9.55a.077.077 0 0 1-.078.078h-6.807a.077.077 0 0 1-.078-.078v-28.113a.077.077 0 0 1 .078-.078h14.227c6.117 0 10.168 3.712 10.168 9.24 0 4.6-2.818 7.914-7.3 8.959a.075.075 0 0 0-.04.124l8.658 9.817a.077.077 0 0 1-.058.128z" transform="translate(-471.45 -246.19)"/>
|
||||
</g>
|
||||
<g id="Group_57" data-name="Group 57" transform="translate(557.15 259.772)">
|
||||
<path id="Path_508" data-name="Path 508" class="cls-3" d="M1147.2 613.979a.078.078 0 0 0-.072-.049h-11.793a.076.076 0 0 0-.072.049l-1.327 3.446a23.417 23.417 0 0 0 15.136 1.414z" transform="translate(-1120.72 -572.602)"/>
|
||||
<path id="Path_509" data-name="Path 509" class="cls-3" d="M1068.251 337.15a23.451 23.451 0 0 0-22.851 18.206h2.591a.077.077 0 0 1 .078.078v17.094a23.542 23.542 0 0 0 4.712 5.68l9.529-22.8a.078.078 0 0 1 .072-.048h7.028a.079.079 0 0 1 .072.048l10.624 25.421a23.445 23.445 0 0 0-11.854-43.675z" transform="translate(-1045.4 -337.15)"/>
|
||||
<path id="Path_510" data-name="Path 510" class="cls-3" d="M1166.073 521.687a.077.077 0 0 0 .072-.106l-3.515-8.992a.078.078 0 0 0-.145 0l-3.475 8.992a.078.078 0 0 0 .072.106z" transform="translate(-1142.042 -486.351)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 634 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 369 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 MiB |
@@ -0,0 +1,30 @@
|
||||
from .eraser_node import EraserNode
|
||||
from .generative_fill_node import GenFillNode
|
||||
from .image_expansion_node import ImageExpansionNode
|
||||
from .replace_bg_node import ReplaceBgNode
|
||||
from .rmbg_node import RmbgNode
|
||||
from .remove_foreground_node import RemoveForegroundNode
|
||||
from .tailored_gen_node import TailoredGenNode
|
||||
from .tailored_model_info_node import TailoredModelInfoNode
|
||||
from .tailored_portrait_node import TailoredPortraitNode
|
||||
from .text_2_image_base_node import Text2ImageBaseNode
|
||||
from .text_2_image_fast_node import Text2ImageFastNode
|
||||
from .text_2_image_hd_node import Text2ImageHDNode
|
||||
from .reimagine_node import ReimagineNode
|
||||
from .generate_image_node_v2 import GenerateImageNodeV2
|
||||
from .refine_image_node_v2 import RefineImageNodeV2
|
||||
from .shot_by_text_node import ShotByTextOriginalNode
|
||||
from .shot_by_text_automatic_aspect_ratio_node import ShotByTextAutomaticAspectRatioNode
|
||||
from .shot_by_text_automatic_node import ShotByTextAutomaticNode
|
||||
from .shot_by_text_custom_coordinates_node import ShotByTextCustomCoordinatesNode
|
||||
from .shot_by_text_manual_placement_node import ShotByTextManualPlacementNode
|
||||
from .shot_by_text_manual_padding_node import ShotByTextManualPaddingNode
|
||||
from .shot_by_image_automatic_aspect_ratio_node import (
|
||||
ShotByImageAutomaticAspectRatioNode,
|
||||
)
|
||||
from .shot_by_image_automatic_node import ShotByImageAutomaticNode
|
||||
from .shot_by_image_custom_coordinates_node import ShotByImageCustomCoordinatesNode
|
||||
from .shot_by_image_node import ShotByImageOriginalNode
|
||||
from .shot_by_image_manual_placement_node import ShotByImageManualPlacementNode
|
||||
from .shot_by_image_manual_padding_node import ShotByImageManualPaddingNode
|
||||
from .attribution_by_image_node import AttributionByImageNode
|
||||
@@ -0,0 +1,67 @@
|
||||
import requests
|
||||
import torch
|
||||
|
||||
from .common import deserialize_and_get_comfy_key, preprocess_image, image_to_base64, poll_status_until_completed
|
||||
|
||||
class AttributionByImageNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"model_version": (["2.3", "3.0","3.2"], {"default": "2.3"}),
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("api_response",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/attribution/by_image"
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, model_version, api_key):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
api_key = deserialize_and_get_comfy_key(api_key)
|
||||
|
||||
# Check if image is tensor, if so, convert to NumPy array
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
|
||||
# Convert image to base64 for the new API format
|
||||
image_base64 = image_to_base64(image)
|
||||
payload = {
|
||||
"image": image_base64,
|
||||
"model_version": model_version,
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
print('Initial Attribution via Images API request successful, polling for completion...')
|
||||
response_dict = response.json()
|
||||
|
||||
status_url = response_dict.get('status_url')
|
||||
request_id = response_dict.get('request_id')
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_key)
|
||||
return (str(final_response.get("result",{}).get("content")),)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import io
|
||||
import torch
|
||||
import base64
|
||||
from torchvision.transforms import ToPILImage
|
||||
import requests
|
||||
import time
|
||||
import json
|
||||
|
||||
|
||||
COMFY_KEY_ERROR = (
|
||||
"Invalid Token Type\n\n"
|
||||
"The API token you’ve entered is not a ComfyUI token.\n"
|
||||
"Please use the valid token from your BRIA Account API Keys page:\n"
|
||||
"https://platform.bria.ai/console/account/api-keys"
|
||||
)
|
||||
|
||||
def postprocess_image(image):
|
||||
result_image = Image.open(io.BytesIO(image))
|
||||
result_image = result_image.convert("RGB")
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
return result_image
|
||||
|
||||
def image_to_base64(pil_image):
|
||||
# Convert a PIL image to a base64-encoded string
|
||||
buffered = io.BytesIO()
|
||||
pil_image.save(buffered, format="PNG") # Save the image to the buffer in PNG format
|
||||
buffered.seek(0) # Rewind the buffer to the beginning
|
||||
return base64.b64encode(buffered.getvalue()).decode('utf-8')
|
||||
|
||||
def preprocess_image(image):
|
||||
if isinstance(image, torch.Tensor):
|
||||
# Print image shape for debugging
|
||||
if image.dim() == 4: # (batch_size, height, width, channels)
|
||||
image = image.squeeze(0) # Remove the batch dimension (1)
|
||||
# Convert to PIL after permuting to (height, width, channels)
|
||||
image = ToPILImage()(image.permute(2, 0, 1)) # (height, width, channels)
|
||||
else:
|
||||
print("Unexpected image dimensions. Expected 4D tensor.")
|
||||
return image
|
||||
|
||||
|
||||
def preprocess_mask(mask):
|
||||
if isinstance(mask, torch.Tensor):
|
||||
# Print mask shape for debugging
|
||||
if mask.dim() == 3: # (batch_size, height, width)
|
||||
mask = mask.squeeze(0) # Remove the batch dimension (1)
|
||||
# Convert to PIL (grayscale mask)
|
||||
mask = ToPILImage()(mask) # No permute needed for grayscale
|
||||
else:
|
||||
print("Unexpected mask dimensions. Expected 3D tensor.")
|
||||
return mask
|
||||
|
||||
|
||||
def process_request(api_url, image, mask, api_key, visual_input_content_moderation, visual_output_content_moderation):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
api_key = deserialize_and_get_comfy_key(api_key)
|
||||
|
||||
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
if isinstance(mask, torch.Tensor):
|
||||
mask = preprocess_mask(mask)
|
||||
|
||||
# Convert the image and mask directly to Base64 strings
|
||||
image_base64 = image_to_base64(image)
|
||||
mask_base64 = image_to_base64(mask)
|
||||
|
||||
# Prepare the API request payload for v2 API
|
||||
payload = {
|
||||
"image": image_base64,
|
||||
"mask": mask_base64,
|
||||
"visual_input_content_moderation":visual_input_content_moderation,
|
||||
"visual_output_content_moderation":visual_output_content_moderation
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(api_url, json=payload, headers=headers)
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
print('Initial request successful, polling for completion...')
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get('status_url')
|
||||
request_id = response_dict.get('request_id')
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_key)
|
||||
result_image_url = final_response['result']['image_url']
|
||||
|
||||
# Download and process the result image
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = result_image.convert("RGBA")
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
# image_tensor = image_tensor = ToTensor()(output_image)
|
||||
# image_tensor = image_tensor.permute(1, 2, 0) / 255.0 # Shape now becomes [1, 2200, 1548, 3]
|
||||
# print(f"output tensor shape is: {image_tensor.shape}")
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
|
||||
|
||||
def poll_status_until_completed(status_url, api_key, timeout=360, check_interval=2):
|
||||
"""
|
||||
Poll a status URL until the status is COMPLETED or timeout is reached.
|
||||
|
||||
Args:
|
||||
status_url (str): The status URL to poll
|
||||
api_key (str): API token for authentication
|
||||
timeout (int): Maximum time to wait in seconds (default: 360)
|
||||
check_interval (int): Time between checks in seconds (default: 2)
|
||||
|
||||
Returns:
|
||||
dict: The final response containing the result
|
||||
|
||||
Raises:
|
||||
Exception: If timeout is reached or API request fails
|
||||
"""
|
||||
start_time = time.time()
|
||||
headers = {"api_token": api_key}
|
||||
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
response = requests.get(status_url, headers=headers)
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
response_dict = response.json()
|
||||
status = response_dict.get("status", "").upper()
|
||||
|
||||
if status == "COMPLETED":
|
||||
return response_dict
|
||||
elif status == "ERROR":
|
||||
raise Exception(f"Request failed: {response_dict}")
|
||||
else:
|
||||
print(f"Status: {status}, waiting...")
|
||||
time.sleep(check_interval)
|
||||
else:
|
||||
raise Exception(f"Status check failed with status code {response.status_code}")
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise Exception(f"Error checking status: {e}")
|
||||
|
||||
raise Exception(f"Timeout reached after {timeout} seconds")
|
||||
|
||||
def deserialize_and_get_comfy_key(encoded: str) -> str:
|
||||
"""
|
||||
Decodes a base64-encoded JSON token and returns the ComfyUI API key.
|
||||
"""
|
||||
try:
|
||||
decoded = base64.b64decode(encoded).decode("utf-8")
|
||||
payload = json.loads(decoded)
|
||||
|
||||
if payload.get("type") != "comfy":
|
||||
raise Exception(COMFY_KEY_ERROR)
|
||||
|
||||
return payload.get("apiKey")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(COMFY_KEY_ERROR)
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
from .common import process_request
|
||||
|
||||
class EraserNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"mask": ("MASK",), # Binary mask input
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}) # API Key input with a default value
|
||||
},
|
||||
"optional": {
|
||||
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/erase" # Eraser API URL
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, mask, api_key, visual_input_content_moderation, visual_output_content_moderation):
|
||||
return process_request(self.api_url, image, mask, api_key, visual_input_content_moderation, visual_output_content_moderation)
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import requests
|
||||
import torch
|
||||
|
||||
from .common import (
|
||||
deserialize_and_get_comfy_key,
|
||||
postprocess_image,
|
||||
preprocess_image,
|
||||
image_to_base64,
|
||||
poll_status_until_completed,
|
||||
)
|
||||
|
||||
|
||||
class _BaseGenerateImageNodeV2:
|
||||
"""Base class for image generation nodes (standard & pro)."""
|
||||
|
||||
api_url = None # Each subclass must define its API endpoint
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
"prompt": ("STRING",),
|
||||
},
|
||||
"optional": {
|
||||
"model_version": (["FIBO"], {"default": "FIBO"}),
|
||||
"negative_prompt": ("STRING", {"default": ""}),
|
||||
"images": ("IMAGE",),
|
||||
"aspect_ratio": (
|
||||
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
|
||||
{"default": "1:1"},
|
||||
),
|
||||
"steps_num": ("INT", {"default": 50, "min": 20, "max": 50}),
|
||||
"guidance_scale": ("INT", {"default": 5, "min": 3, "max": 5}),
|
||||
"seed": ("INT", {"default": 123456}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "STRING", "INT")
|
||||
RETURN_NAMES = ("image", "structured_prompt", "seed")
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def _validate_token(self, api_token: str):
|
||||
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API token.")
|
||||
|
||||
def _build_payload(
|
||||
self,
|
||||
prompt,
|
||||
model_version,
|
||||
negative_prompt,
|
||||
aspect_ratio,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed,
|
||||
images=None,
|
||||
):
|
||||
payload = {
|
||||
"prompt": prompt,
|
||||
"model_version": model_version,
|
||||
"negative_prompt": negative_prompt,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"steps_num": steps_num,
|
||||
"guidance_scale": guidance_scale,
|
||||
"seed": seed,
|
||||
}
|
||||
|
||||
if images is not None:
|
||||
if isinstance(images, torch.Tensor):
|
||||
preprocess_images = preprocess_image(images)
|
||||
payload["images"] = [image_to_base64(preprocess_images)]
|
||||
|
||||
|
||||
return payload
|
||||
|
||||
def execute(
|
||||
self,
|
||||
api_token,
|
||||
prompt,
|
||||
model_version,
|
||||
negative_prompt,
|
||||
aspect_ratio,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed,
|
||||
images=None,
|
||||
):
|
||||
self._validate_token(api_token)
|
||||
payload = self._build_payload(
|
||||
prompt,
|
||||
model_version,
|
||||
negative_prompt,
|
||||
aspect_ratio,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed,
|
||||
images,
|
||||
)
|
||||
api_token = deserialize_and_get_comfy_key(api_token)
|
||||
|
||||
headers = {"Content-Type": "application/json", "api_token": api_token}
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code in (200, 202):
|
||||
print(
|
||||
f"Initial request successful to {self.api_url}, polling for completion..."
|
||||
)
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get("status_url")
|
||||
request_id = response_dict.get("request_id")
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_token)
|
||||
|
||||
result = final_response.get("result", {})
|
||||
result_image_url = result.get("image_url")
|
||||
structured_prompt = result.get("structured_prompt", "")
|
||||
used_seed = result.get("seed")
|
||||
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = postprocess_image(image_response.content)
|
||||
|
||||
return (result_image, structured_prompt, used_seed)
|
||||
|
||||
raise Exception(
|
||||
f"Error: API request failed with status code {response.status_code} {response.text}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
|
||||
|
||||
class GenerateImageNodeV2(_BaseGenerateImageNodeV2):
|
||||
"""Standard Image Generation Node"""
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/generate"
|
||||
@@ -0,0 +1,98 @@
|
||||
import numpy as np
|
||||
import requests
|
||||
from PIL import Image
|
||||
import io
|
||||
import torch
|
||||
|
||||
from .common import deserialize_and_get_comfy_key, preprocess_image, preprocess_mask, image_to_base64, poll_status_until_completed
|
||||
|
||||
|
||||
class GenFillNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"mask": ("MASK",), # Binary mask input
|
||||
"prompt": ("STRING",),
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
||||
},
|
||||
"optional": {
|
||||
"seed": ("INT", {"default": 123456}),
|
||||
"prompt_content_moderation": ("BOOLEAN", {"default": True}),
|
||||
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/gen_fill"
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, mask, prompt, api_key, seed, prompt_content_moderation, visual_input_content_moderation, visual_output_content_moderation):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
api_key = deserialize_and_get_comfy_key(api_key)
|
||||
|
||||
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
if isinstance(mask, torch.Tensor):
|
||||
mask = preprocess_mask(mask)
|
||||
|
||||
# Convert the image and mask directly to Base64 strings
|
||||
image_base64 = image_to_base64(image)
|
||||
mask_base64 = image_to_base64(mask)
|
||||
|
||||
# Prepare the API request payload
|
||||
payload = {
|
||||
"image": image_base64,
|
||||
"mask": mask_base64,
|
||||
"prompt": prompt,
|
||||
"negative_prompt": "blurry",
|
||||
"seed": seed,
|
||||
"prompt_content_moderation":prompt_content_moderation,
|
||||
"visual_input_content_moderation":visual_input_content_moderation,
|
||||
"visual_output_content_moderation":visual_output_content_moderation
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
|
||||
try:
|
||||
# Send initial request to get status URL
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
print('Initial genfill request successful, polling for completion...')
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get('status_url')
|
||||
request_id = response_dict.get('request_id')
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_key)
|
||||
result_image_url = final_response['result']['image_url']
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = result_image.convert("RGB")
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
@@ -0,0 +1,136 @@
|
||||
import numpy as np
|
||||
import requests
|
||||
from PIL import Image
|
||||
import io
|
||||
import torch
|
||||
|
||||
from .common import deserialize_and_get_comfy_key, image_to_base64, preprocess_image, poll_status_until_completed
|
||||
|
||||
|
||||
class ImageExpansionNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
||||
},
|
||||
|
||||
"optional": {
|
||||
"original_image_size": ("STRING",),
|
||||
"original_image_location": ("STRING",),
|
||||
"canvas_size": ("STRING", {"default": "1000, 1000"}),
|
||||
"aspect_ratio": (["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9","None"], {"default": "None"}),
|
||||
"prompt": ("STRING", {"default": ""}),
|
||||
"seed": ("INT", {"default": 681794}),
|
||||
"negative_prompt": ("STRING", {"default": "Ugly, mutated"}),
|
||||
"prompt_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"preserve_alpha": ("BOOLEAN", {"default": True}),
|
||||
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/expand" # Image Expansion API URL
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image,
|
||||
original_image_size,
|
||||
original_image_location,
|
||||
canvas_size,
|
||||
aspect_ratio,
|
||||
prompt,
|
||||
seed,
|
||||
negative_prompt,
|
||||
prompt_content_moderation,
|
||||
preserve_alpha,
|
||||
visual_input_content_moderation,
|
||||
visual_output_content_moderation,
|
||||
api_key):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
api_key = deserialize_and_get_comfy_key(api_key)
|
||||
original_image_size = [int(x.strip()) for x in original_image_size.split(",")] if original_image_size else ()
|
||||
original_image_location = [int(x.strip()) for x in original_image_location.split(",")] if original_image_location else ()
|
||||
canvas_size = [int(x.strip()) for x in canvas_size.split(",")] if canvas_size else ()
|
||||
|
||||
if negative_prompt == "":
|
||||
negative_prompt = " " # hack to avoid error in triton which expects non-empty string
|
||||
|
||||
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
|
||||
# Convert the image directly to Base64 string
|
||||
image_base64 = image_to_base64(image)
|
||||
if aspect_ratio and aspect_ratio != "None":
|
||||
payload = {
|
||||
"image": image_base64,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"prompt": prompt,
|
||||
"negative_prompt": negative_prompt,
|
||||
"seed": seed,
|
||||
"prompt_content_moderation": prompt_content_moderation,
|
||||
"preserve_alpha": preserve_alpha,
|
||||
"visual_input_content_moderation": visual_input_content_moderation,
|
||||
"visual_output_content_moderation": visual_output_content_moderation
|
||||
}
|
||||
else:
|
||||
payload = {
|
||||
"image": image_base64,
|
||||
"original_image_size": original_image_size,
|
||||
"original_image_location": original_image_location,
|
||||
"canvas_size": canvas_size,
|
||||
"prompt": prompt,
|
||||
"negative_prompt": negative_prompt,
|
||||
"seed": seed,
|
||||
"prompt_content_moderation": prompt_content_moderation,
|
||||
"preserve_alpha": preserve_alpha,
|
||||
"visual_input_content_moderation": visual_input_content_moderation,
|
||||
"visual_output_content_moderation": visual_output_content_moderation
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
print('Initial image expansion request successful, polling for completion...')
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get('status_url')
|
||||
request_id = response_dict.get('request_id')
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
# Poll status URL until completion
|
||||
final_response = poll_status_until_completed(status_url, api_key)
|
||||
|
||||
# Get the result image URL
|
||||
result_image_url = final_response['result']['image_url']
|
||||
|
||||
# Download and process the result image
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = result_image.convert("RGB")
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code}: {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
@@ -0,0 +1,161 @@
|
||||
import requests
|
||||
from .common import deserialize_and_get_comfy_key, poll_status_until_completed, postprocess_image
|
||||
|
||||
|
||||
class _BaseRefineImageNodeV2:
|
||||
"""Base class for refine image nodes (standard & pro)."""
|
||||
|
||||
api_url = None # Must be overridden by subclasses
|
||||
generate_api_url = None
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
"prompt": ("STRING",),
|
||||
"structured_prompt": ("STRING",),
|
||||
},
|
||||
"optional": {
|
||||
"model_version": (["FIBO"], {"default": "FIBO"}),
|
||||
"negative_prompt": ("STRING", {"default": ""}),
|
||||
"aspect_ratio": (
|
||||
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
|
||||
{"default": "1:1"},
|
||||
),
|
||||
"steps_num": ("INT", {"default": 50, "min": 20, "max": 50}),
|
||||
"guidance_scale": ("INT", {"default": 5, "min": 3, "max": 5}),
|
||||
"seed": ("INT", {"default": 123456}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "STRING", "INT")
|
||||
RETURN_NAMES = ("image", "structured_prompt", "seed")
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def _validate_token(self, api_token: str):
|
||||
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API token.")
|
||||
|
||||
def _build_payload(
|
||||
self,
|
||||
prompt,
|
||||
structured_prompt,
|
||||
model_version,
|
||||
negative_prompt,
|
||||
aspect_ratio,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed,
|
||||
):
|
||||
return {
|
||||
"prompt": prompt,
|
||||
"model_version": model_version,
|
||||
"negative_prompt": negative_prompt,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"steps_num": steps_num,
|
||||
"guidance_scale": guidance_scale,
|
||||
"seed": seed,
|
||||
"structured_prompt": structured_prompt,
|
||||
}
|
||||
|
||||
def execute(
|
||||
self,
|
||||
api_token,
|
||||
prompt,
|
||||
structured_prompt,
|
||||
model_version,
|
||||
negative_prompt,
|
||||
aspect_ratio,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed,
|
||||
):
|
||||
self._validate_token(api_token)
|
||||
payload = self._build_payload(
|
||||
prompt,
|
||||
structured_prompt,
|
||||
model_version,
|
||||
negative_prompt,
|
||||
aspect_ratio,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed,
|
||||
)
|
||||
api_token = deserialize_and_get_comfy_key(api_token)
|
||||
headers = {"Content-Type": "application/json", "api_token": api_token}
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code in (200, 202):
|
||||
print(f"Initial refine request successful to {self.api_url}, polling for completion...")
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get("status_url")
|
||||
request_id = response_dict.get("request_id")
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_token)
|
||||
|
||||
result = final_response.get("result", {})
|
||||
structured_prompt = result.get("structured_prompt", "")
|
||||
used_seed = result.get("seed", seed)
|
||||
|
||||
# Step 2 to call genearte image
|
||||
payloadForImageGenetrate = {
|
||||
"prompt": prompt,
|
||||
"structured_prompt":structured_prompt,
|
||||
"model_version": model_version,
|
||||
"negative_prompt": negative_prompt,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"steps_num": steps_num,
|
||||
"guidance_scale": guidance_scale,
|
||||
"seed": used_seed,
|
||||
}
|
||||
headers = {"Content-Type": "application/json", "api_token": api_token}
|
||||
|
||||
response = requests.post(self.generate_api_url, json=payloadForImageGenetrate, headers=headers)
|
||||
|
||||
if response.status_code in (200, 202):
|
||||
print(
|
||||
f"Initial request successful to {self.generate_api_url}, polling for completion..."
|
||||
)
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get("status_url")
|
||||
request_id = response_dict.get("request_id")
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_token)
|
||||
|
||||
result = final_response.get("result", {})
|
||||
result_image_url = result.get("image_url")
|
||||
structured_prompt = result.get("structured_prompt", "")
|
||||
used_seed = result.get("seed")
|
||||
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = postprocess_image(image_response.content)
|
||||
|
||||
return (result_image, structured_prompt, used_seed)
|
||||
|
||||
raise Exception(
|
||||
f"Error: API request failed with status code {response.status_code} {response.text}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
|
||||
|
||||
class RefineImageNodeV2(_BaseRefineImageNodeV2):
|
||||
"""Standard Refine Image Node"""
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/structured_prompt/generate"
|
||||
self.generate_api_url = "https://engine.prod.bria-api.com/v2/image/generate"
|
||||
@@ -0,0 +1,70 @@
|
||||
import requests
|
||||
|
||||
from .common import deserialize_and_get_comfy_key, postprocess_image, preprocess_image, image_to_base64
|
||||
|
||||
|
||||
class ReimagineNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"api_key": ("STRING", ),
|
||||
"prompt": ("STRING",),
|
||||
},
|
||||
"optional": {
|
||||
"seed": ("INT", {"default": -1}),
|
||||
"steps_num": ("INT", {"default": 12}), # if used with tailored, possibly get this from the tailored model info node
|
||||
"structure_ref_influence": ("FLOAT", {"default": 0.75}),
|
||||
"fast": ("INT", {"default": 0}), # if used with tailored, possibly get this from the tailored model info node
|
||||
"structure_image": ("IMAGE", ),
|
||||
"tailored_model_id": ("STRING", ),
|
||||
"tailored_model_influence": ("FLOAT", {"default": 0.5}),
|
||||
"tailored_generation_prefix": ("STRING",), # if used with tailored, possibly get this from the tailored model info node
|
||||
"content_moderation": ("INT", {"default": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/reimagine" #"http://0.0.0.0:5000/v1/reimagine"
|
||||
|
||||
def execute(
|
||||
self, api_key, prompt, seed,
|
||||
steps_num, fast, structure_ref_influence, structure_image=None,
|
||||
tailored_model_id=None, tailored_model_influence=None, tailored_generation_prefix=None,
|
||||
content_moderation=0,
|
||||
):
|
||||
api_key = deserialize_and_get_comfy_key(api_key)
|
||||
payload = {
|
||||
"prompt": tailored_generation_prefix + prompt,
|
||||
"num_results": 1,
|
||||
"sync": True,
|
||||
"seed": seed,
|
||||
"steps_num": steps_num,
|
||||
"include_generation_prefix": False,
|
||||
"content_moderation": content_moderation,
|
||||
}
|
||||
if structure_image is not None:
|
||||
structure_image = preprocess_image(structure_image)
|
||||
structure_image = image_to_base64(structure_image)
|
||||
payload["structure_image_file"] = structure_image
|
||||
payload["structure_ref_influence"] = structure_ref_influence
|
||||
if tailored_model_id is not None and tailored_model_id != "":
|
||||
payload["tailored_model_id"] = tailored_model_id
|
||||
payload["tailored_model_influence"] = tailored_model_influence
|
||||
response = requests.post(
|
||||
self.api_url,
|
||||
json=payload,
|
||||
headers={"api_token": api_key}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['result'][0]["urls"][0])
|
||||
result_image = postprocess_image(image_response.content)
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} and text {response.text}")
|
||||
@@ -0,0 +1,91 @@
|
||||
import numpy as np
|
||||
import requests
|
||||
from PIL import Image
|
||||
import io
|
||||
import torch
|
||||
|
||||
from .common import deserialize_and_get_comfy_key, preprocess_image, image_to_base64, poll_status_until_completed
|
||||
|
||||
|
||||
class RemoveForegroundNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
||||
},
|
||||
"optional": {
|
||||
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"preserve_alpha": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/erase_foreground" # remove foreground API URL
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, visual_input_content_moderation, visual_output_content_moderation, preserve_alpha, api_key):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
api_key = deserialize_and_get_comfy_key(api_key)
|
||||
|
||||
# Check if image is tensor, if so, convert to NumPy array
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
|
||||
# Prepare the API request payload
|
||||
# temporary save the image to /tmp
|
||||
# temp_img_path = "/tmp/temp_img.jpeg"
|
||||
# image.save(temp_img_path, format="JPEG")
|
||||
|
||||
# files=[('file',('temp_img.jpeg', open(temp_img_path, 'rb'),'image/jpeg'))
|
||||
# ]
|
||||
payload = {
|
||||
"image": image_to_base64(image),
|
||||
"visual_input_content_moderation": visual_input_content_moderation,
|
||||
"visual_output_content_moderation":visual_output_content_moderation,
|
||||
"preserve_alpha": preserve_alpha
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
print('Initial request successful, polling for completion...')
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get('status_url')
|
||||
request_id = response_dict.get('request_id')
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
# Poll status URL until completion
|
||||
final_response = poll_status_until_completed(status_url, api_key)
|
||||
|
||||
# Get the result image URL
|
||||
result_image_url = final_response['result']['image_url']
|
||||
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
@@ -0,0 +1,124 @@
|
||||
import numpy as np
|
||||
import requests
|
||||
from PIL import Image
|
||||
import io
|
||||
import torch
|
||||
|
||||
from .common import deserialize_and_get_comfy_key, image_to_base64, preprocess_image, preprocess_mask, poll_status_until_completed
|
||||
|
||||
|
||||
class ReplaceBgNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
||||
},
|
||||
"optional": {
|
||||
"mode": (["base", "fast", "high_control"], {"default": "base"}),
|
||||
"prompt": ("STRING",),
|
||||
"ref_images": ("IMAGE",),
|
||||
"refine_prompt": ("BOOLEAN", {"default": True}),
|
||||
"enhance_ref_images": ("BOOLEAN", {"default": True}),
|
||||
"original_quality": ("BOOLEAN", {"default": False}),
|
||||
"negative_prompt": ("STRING", {"default": None}),
|
||||
"seed": ("INT", {"default": 681794}),
|
||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"prompt_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"force_background_detection": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/replace_background" # Replace BG API URL
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, mode,
|
||||
refine_prompt,
|
||||
original_quality,
|
||||
negative_prompt,
|
||||
seed,
|
||||
api_key,
|
||||
visual_output_content_moderation,
|
||||
prompt_content_moderation,
|
||||
enhance_ref_images,
|
||||
force_background_detection,
|
||||
prompt=None,
|
||||
ref_images=None,):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
api_key = deserialize_and_get_comfy_key(api_key)
|
||||
|
||||
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
|
||||
# Convert the image to Base64 string
|
||||
image_base64 = image_to_base64(image)
|
||||
|
||||
if ref_images is not None:
|
||||
ref_images = preprocess_image(ref_images)
|
||||
ref_images = [image_to_base64(ref_images)]
|
||||
else:
|
||||
ref_images=[]
|
||||
|
||||
# Prepare the API request payload for v2 API
|
||||
payload = {
|
||||
"image": image_base64,
|
||||
"mode": mode,
|
||||
"prompt": prompt,
|
||||
"ref_images":ref_images,
|
||||
"refine_prompt": refine_prompt,
|
||||
"original_quality": original_quality,
|
||||
"negative_prompt": negative_prompt,
|
||||
"seed": seed,
|
||||
"prompt_content_moderation": prompt_content_moderation,
|
||||
"visual_output_content_moderation":visual_output_content_moderation,
|
||||
"enhance_ref_images":enhance_ref_images,
|
||||
"force_background_detection": force_background_detection
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
print('Initial replace background request successful, polling for completion...')
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get('status_url')
|
||||
request_id = response_dict.get('request_id')
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
# Poll status URL until completion
|
||||
final_response = poll_status_until_completed(status_url, api_key)
|
||||
|
||||
# Get the result image URL
|
||||
result_image_url = final_response['result']['image_url']
|
||||
|
||||
# Download and process the result image
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = result_image.convert("RGB")
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code}{response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
@@ -0,0 +1,87 @@
|
||||
import numpy as np
|
||||
import requests
|
||||
from PIL import Image
|
||||
import io
|
||||
import torch
|
||||
|
||||
from .common import deserialize_and_get_comfy_key, preprocess_image, image_to_base64, poll_status_until_completed
|
||||
|
||||
class RmbgNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
||||
},
|
||||
"optional": {
|
||||
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"preserve_alpha": ("BOOLEAN", {"default": True}),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/remove_background" # RMBG API URL
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, visual_input_content_moderation, visual_output_content_moderation, preserve_alpha, api_key):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
api_key = deserialize_and_get_comfy_key(api_key)
|
||||
# Check if image is tensor, if so, convert to NumPy array
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
|
||||
# Convert image to base64 for the new API format
|
||||
image_base64 = image_to_base64(image)
|
||||
payload = {
|
||||
"image": image_base64,
|
||||
"visual_input_content_moderation": visual_input_content_moderation,
|
||||
"visual_output_content_moderation":visual_output_content_moderation,
|
||||
"preserve_alpha":preserve_alpha
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
print('Initial RMBG request successful, polling for completion...')
|
||||
response_dict = response.json()
|
||||
|
||||
status_url = response_dict.get('status_url')
|
||||
request_id = response_dict.get('request_id')
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_key)
|
||||
|
||||
# Get the result image URL
|
||||
result_image_url = final_response['result']['image_url']
|
||||
|
||||
# Download and process the result image
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
@@ -0,0 +1,46 @@
|
||||
from .utils.shot_utils import get_image_input_types, create_image_payload, make_api_request, shot_by_image_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByImageAutomaticAspectRatioNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_image_input_types()
|
||||
input_types["required"]["aspect_ratio"] = (
|
||||
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
|
||||
{"default": "1:1"},
|
||||
)
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_image_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
ref_image,
|
||||
aspect_ratio,
|
||||
api_key,
|
||||
sync=False,
|
||||
enhance_ref_image=True,
|
||||
ref_image_influence=1.0,
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_image_payload(
|
||||
image,
|
||||
ref_image,
|
||||
api_key,
|
||||
PlacementType.AUTOMATIC_ASPECT_RATIO.value,
|
||||
aspect_ratio=aspect_ratio,
|
||||
sync=sync,
|
||||
enhance_ref_image=enhance_ref_image,
|
||||
ref_image_influence=ref_image_influence,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,51 @@
|
||||
from .utils.shot_utils import get_image_input_types, create_image_payload, make_api_request, shot_by_image_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByImageAutomaticNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_image_input_types()
|
||||
input_types["required"]["shot_size"] = ("STRING", {"default": "1000, 1000"})
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE")
|
||||
RETURN_NAMES = (
|
||||
"output_image_1",
|
||||
"output_image_2",
|
||||
"output_image_3",
|
||||
"output_image_4",
|
||||
"output_image_5",
|
||||
"output_image_6",
|
||||
"output_image_7",
|
||||
)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_image_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
ref_image,
|
||||
shot_size,
|
||||
api_key,
|
||||
sync=False,
|
||||
enhance_ref_image=True,
|
||||
ref_image_influence=1.0,
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_image_payload(
|
||||
image,
|
||||
ref_image,
|
||||
api_key,
|
||||
PlacementType.AUTOMATIC.value,
|
||||
shot_size=shot_size,
|
||||
sync=sync,
|
||||
enhance_ref_image=enhance_ref_image,
|
||||
ref_image_influence=ref_image_influence,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key, Placement_type = PlacementType.AUTOMATIC.value)
|
||||
@@ -0,0 +1,55 @@
|
||||
from .utils.shot_utils import get_image_input_types, create_image_payload, make_api_request, shot_by_image_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByImageCustomCoordinatesNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_image_input_types()
|
||||
input_types["required"]["shot_size"] = ("STRING", {"default": "1000, 1000"})
|
||||
input_types["required"]["foreground_image_size"] = (
|
||||
"STRING",
|
||||
{"default": "500,500"},
|
||||
)
|
||||
input_types["required"]["foreground_image_location"] = (
|
||||
"STRING",
|
||||
{"default": "0, 0"},
|
||||
)
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_image_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
ref_image,
|
||||
shot_size,
|
||||
foreground_image_size,
|
||||
foreground_image_location,
|
||||
api_key,
|
||||
sync=False,
|
||||
enhance_ref_image=True,
|
||||
ref_image_influence=1.0,
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_image_payload(
|
||||
image,
|
||||
ref_image,
|
||||
api_key,
|
||||
PlacementType.CUSTOM_COORDINATES.value,
|
||||
shot_size=shot_size,
|
||||
foreground_image_size=foreground_image_size,
|
||||
foreground_image_location=foreground_image_location,
|
||||
sync=sync,
|
||||
enhance_ref_image=enhance_ref_image,
|
||||
ref_image_influence=ref_image_influence,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,44 @@
|
||||
from .utils.shot_utils import get_image_input_types, create_image_payload, make_api_request, shot_by_image_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByImageManualPaddingNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_image_input_types()
|
||||
input_types["required"]["padding_values"] = ("STRING", {"default": "0,0,0,0"})
|
||||
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_image_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
ref_image,
|
||||
padding_values,
|
||||
api_key,
|
||||
sync=False,
|
||||
enhance_ref_image=True,
|
||||
ref_image_influence=1.0,
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_image_payload(
|
||||
image,
|
||||
ref_image,
|
||||
api_key,
|
||||
PlacementType.MANUAL_PADDING.value,
|
||||
padding_values=padding_values,
|
||||
sync=sync,
|
||||
enhance_ref_image=enhance_ref_image,
|
||||
ref_image_influence=ref_image_influence,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,59 @@
|
||||
from .utils.shot_utils import get_image_input_types, create_image_payload, make_api_request, shot_by_image_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByImageManualPlacementNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_image_input_types()
|
||||
input_types["required"]["shot_size"] = ("STRING", {"default": "1000, 1000"})
|
||||
input_types["required"]["manual_placement_selection"] = (
|
||||
[
|
||||
"upper_left",
|
||||
"upper_right",
|
||||
"bottom_left",
|
||||
"bottom_right",
|
||||
"right_center",
|
||||
"left_center",
|
||||
"upper_center",
|
||||
"bottom_center",
|
||||
"center_vertical",
|
||||
"center_horizontal",
|
||||
],
|
||||
{"default": "upper_left"},
|
||||
)
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_image_api_url
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
ref_image,
|
||||
shot_size,
|
||||
manual_placement_selection,
|
||||
api_key,
|
||||
sync=False,
|
||||
enhance_ref_image=True,
|
||||
ref_image_influence=1.0,
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_image_payload(
|
||||
image,
|
||||
ref_image,
|
||||
api_key,
|
||||
PlacementType.MANUAL_PLACEMENT.value,
|
||||
shot_size=shot_size,
|
||||
manual_placement_selection=manual_placement_selection,
|
||||
sync=sync,
|
||||
enhance_ref_image=enhance_ref_image,
|
||||
ref_image_influence=ref_image_influence,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,41 @@
|
||||
from .utils.shot_utils import get_image_input_types, create_image_payload, make_api_request, shot_by_image_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByImageOriginalNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_image_input_types()
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_image_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
ref_image,
|
||||
api_key,
|
||||
sync=True,
|
||||
enhance_ref_image=True,
|
||||
ref_image_influence=1.0,
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_image_payload(
|
||||
image,
|
||||
ref_image,
|
||||
api_key,
|
||||
PlacementType.ORIGINAL.value,
|
||||
original_quality=True,
|
||||
sync=sync,
|
||||
enhance_ref_image=enhance_ref_image,
|
||||
ref_image_influence=ref_image_influence,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,48 @@
|
||||
from .utils.shot_utils import get_text_input_types, create_text_payload, make_api_request, shot_by_text_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByTextAutomaticAspectRatioNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_text_input_types()
|
||||
input_types["required"]["aspect_ratio"] = (
|
||||
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
|
||||
{"default": "1:1"},
|
||||
)
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_text_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
scene_description,
|
||||
mode,
|
||||
aspect_ratio,
|
||||
api_key,
|
||||
sync=False,
|
||||
optimize_description=True,
|
||||
exclude_elements="",
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_text_payload(
|
||||
image,
|
||||
api_key,
|
||||
scene_description,
|
||||
mode,
|
||||
PlacementType.AUTOMATIC_ASPECT_RATIO.value,
|
||||
aspect_ratio=aspect_ratio,
|
||||
sync=sync,
|
||||
optimize_description=optimize_description,
|
||||
exclude_elements=exclude_elements,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,53 @@
|
||||
from .utils.shot_utils import get_text_input_types, create_text_payload, make_api_request, shot_by_text_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByTextAutomaticNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_text_input_types()
|
||||
input_types["required"]["shot_size"] = ("STRING", {"default": "1000, 1000"})
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE")
|
||||
RETURN_NAMES = (
|
||||
"output_image_1",
|
||||
"output_image_2",
|
||||
"output_image_3",
|
||||
"output_image_4",
|
||||
"output_image_5",
|
||||
"output_image_6",
|
||||
"output_image_7",
|
||||
)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_text_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
scene_description,
|
||||
mode,
|
||||
shot_size,
|
||||
api_key,
|
||||
sync=False,
|
||||
optimize_description=True,
|
||||
exclude_elements="",
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_text_payload(
|
||||
image,
|
||||
api_key,
|
||||
scene_description,
|
||||
mode,
|
||||
PlacementType.AUTOMATIC.value,
|
||||
shot_size=shot_size,
|
||||
sync=sync,
|
||||
optimize_description=optimize_description,
|
||||
exclude_elements=exclude_elements,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key, Placement_type= PlacementType.AUTOMATIC.value)
|
||||
@@ -0,0 +1,57 @@
|
||||
from .utils.shot_utils import get_text_input_types, create_text_payload, make_api_request, shot_by_text_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByTextCustomCoordinatesNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_text_input_types()
|
||||
input_types["required"]["shot_size"] = ("STRING", {"default": "1000, 1000"})
|
||||
input_types["required"]["foreground_image_size"] = (
|
||||
"STRING",
|
||||
{"default": "500,500"},
|
||||
)
|
||||
input_types["required"]["foreground_image_location"] = (
|
||||
"STRING",
|
||||
{"default": "0, 0"},
|
||||
)
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_text_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
scene_description,
|
||||
mode,
|
||||
shot_size,
|
||||
foreground_image_size,
|
||||
foreground_image_location,
|
||||
api_key,
|
||||
sync=False,
|
||||
optimize_description=True,
|
||||
exclude_elements="",
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_text_payload(
|
||||
image,
|
||||
api_key,
|
||||
scene_description,
|
||||
mode,
|
||||
PlacementType.CUSTOM_COORDINATES.value,
|
||||
shot_size=shot_size,
|
||||
foreground_image_size=foreground_image_size,
|
||||
foreground_image_location=foreground_image_location,
|
||||
sync=sync,
|
||||
optimize_description=optimize_description,
|
||||
exclude_elements=exclude_elements,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,45 @@
|
||||
from .utils.shot_utils import get_text_input_types, create_text_payload, make_api_request, shot_by_text_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByTextManualPaddingNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_text_input_types()
|
||||
input_types["required"]["padding_values"] = ("STRING", {"default": "0,0,0,0"})
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_text_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
scene_description,
|
||||
mode,
|
||||
padding_values,
|
||||
api_key,
|
||||
sync=False,
|
||||
optimize_description=True,
|
||||
exclude_elements="",
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_text_payload(
|
||||
image,
|
||||
api_key,
|
||||
scene_description,
|
||||
mode,
|
||||
PlacementType.MANUAL_PADDING.value,
|
||||
padding_values=padding_values,
|
||||
sync=sync,
|
||||
optimize_description=optimize_description,
|
||||
exclude_elements=exclude_elements,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,62 @@
|
||||
from .utils.shot_utils import get_text_input_types, create_text_payload, make_api_request, shot_by_text_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByTextManualPlacementNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_text_input_types()
|
||||
input_types["required"]["shot_size"] = ("STRING", {"default": "1000, 1000"})
|
||||
input_types["required"]["manual_placement_selection"] = (
|
||||
[
|
||||
"upper_left",
|
||||
"upper_right",
|
||||
"bottom_left",
|
||||
"bottom_right",
|
||||
"right_center",
|
||||
"left_center",
|
||||
"upper_center",
|
||||
"bottom_center",
|
||||
"center_vertical",
|
||||
"center_horizontal",
|
||||
],
|
||||
{"default": "upper_left"},
|
||||
)
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_text_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
scene_description,
|
||||
mode,
|
||||
shot_size,
|
||||
manual_placement_selection,
|
||||
api_key,
|
||||
sync=False,
|
||||
optimize_description=True,
|
||||
exclude_elements="",
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_text_payload(
|
||||
image,
|
||||
api_key,
|
||||
scene_description,
|
||||
mode,
|
||||
PlacementType.MANUAL_PLACEMENT.value,
|
||||
shot_size=shot_size,
|
||||
manual_placement_selection=manual_placement_selection,
|
||||
sync=sync,
|
||||
optimize_description=optimize_description,
|
||||
exclude_elements=exclude_elements,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,42 @@
|
||||
from .utils.shot_utils import get_text_input_types, create_text_payload, make_api_request, shot_by_text_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByTextOriginalNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_text_input_types()
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_text_api_url
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
scene_description,
|
||||
mode,
|
||||
api_key,
|
||||
sync=True,
|
||||
optimize_description=True,
|
||||
exclude_elements="",
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_text_payload(
|
||||
image,
|
||||
api_key,
|
||||
scene_description,
|
||||
mode,
|
||||
PlacementType.ORIGINAL.value,
|
||||
original_quality=True,
|
||||
sync=sync,
|
||||
optimize_description=optimize_description,
|
||||
exclude_elements=exclude_elements,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,85 @@
|
||||
import requests
|
||||
|
||||
from .common import deserialize_and_get_comfy_key, postprocess_image, preprocess_image, image_to_base64
|
||||
|
||||
|
||||
class TailoredGenNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"model_id": ("STRING",),
|
||||
"api_key": ("STRING", ),
|
||||
},
|
||||
"optional": {
|
||||
"prompt": ("STRING",),
|
||||
"generation_prefix": ("STRING",), # possibly get this from the tailored model info node
|
||||
"aspect_ratio": (["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"], {"default": "4:3"}),
|
||||
"seed": ("INT", {"default": -1}),
|
||||
"model_influence": ("FLOAT", {"default": 1.0}),
|
||||
"negative_prompt": ("STRING", {"default": ""}),
|
||||
"fast": ("INT", {"default": 1}), # possibly get this from the tailored model info node
|
||||
"steps_num": ("INT", {"default": 8}), # possibly get this from the tailored model info node
|
||||
"guidance_method_1": (["controlnet_canny", "controlnet_depth", "controlnet_recoloring", "controlnet_color_grid"], {"default": "controlnet_canny"}),
|
||||
"guidance_method_1_scale": ("FLOAT", {"default": 1.0}),
|
||||
"guidance_method_1_image": ("IMAGE", ),
|
||||
"guidance_method_2": (["controlnet_canny", "controlnet_depth", "controlnet_recoloring", "controlnet_color_grid"], {"default": "controlnet_canny"}),
|
||||
"guidance_method_2_scale": ("FLOAT", {"default": 1.0}),
|
||||
"guidance_method_2_image": ("IMAGE", ),
|
||||
"content_moderation": ("INT", {"default": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/text-to-image/tailored/" #"http://0.0.0.0:5000/v1/text-to-image/tailored/"
|
||||
|
||||
def execute(
|
||||
self, model_id, api_key, prompt, generation_prefix, aspect_ratio,
|
||||
seed, model_influence, negative_prompt, fast, steps_num,
|
||||
guidance_method_1=None, guidance_method_1_scale=None, guidance_method_1_image=None,
|
||||
guidance_method_2=None, guidance_method_2_scale=None, guidance_method_2_image=None,
|
||||
content_moderation=0,
|
||||
):
|
||||
api_key = deserialize_and_get_comfy_key(api_key)
|
||||
payload = {
|
||||
"prompt": generation_prefix + prompt,
|
||||
"num_results": 1,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"sync": True,
|
||||
"seed": seed,
|
||||
"model_influence": model_influence,
|
||||
"negative_prompt": negative_prompt,
|
||||
"fast": fast,
|
||||
"steps_num": steps_num,
|
||||
"include_generation_prefix": False,
|
||||
"content_moderation": content_moderation,
|
||||
}
|
||||
if guidance_method_1_image is not None:
|
||||
guidance_method_1_image = preprocess_image(guidance_method_1_image)
|
||||
guidance_method_1_image = image_to_base64(guidance_method_1_image)
|
||||
payload["guidance_method_1"] = guidance_method_1
|
||||
payload["guidance_method_1_scale"] = guidance_method_1_scale
|
||||
payload["guidance_method_1_image_file"] = guidance_method_1_image
|
||||
if guidance_method_2_image is not None:
|
||||
guidance_method_2_image = preprocess_image(guidance_method_2_image)
|
||||
guidance_method_2_image = image_to_base64(guidance_method_2_image)
|
||||
payload["guidance_method_2"] = guidance_method_2
|
||||
payload["guidance_method_2_scale"] = guidance_method_2_scale
|
||||
payload["guidance_method_2_image_file"] = guidance_method_2_image
|
||||
response = requests.post(
|
||||
self.api_url + model_id,
|
||||
json=payload,
|
||||
headers={"api_token": api_key}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['result'][0]["urls"][0])
|
||||
result_image = postprocess_image(image_response.content)
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} and text {response.text}")
|
||||
@@ -0,0 +1,36 @@
|
||||
import requests
|
||||
from .common import deserialize_and_get_comfy_key
|
||||
|
||||
class TailoredModelInfoNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"model_id": ("STRING",),
|
||||
"api_key": ("STRING", )
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING","INT", "INT", )
|
||||
RETURN_NAMES = ("generation_prefix", "model_id", "default_fast", "default_steps_num", )
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/tailored-gen/models/"
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, model_id, api_key):
|
||||
api_key = deserialize_and_get_comfy_key(api_key)
|
||||
response = requests.get(
|
||||
self.api_url + model_id,
|
||||
headers={"api_token": api_key}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
generation_prefix = response.json()["generation_prefix"]
|
||||
training_version = response.json()["training_version"]
|
||||
default_fast = 1 if training_version == "light" else 0
|
||||
default_steps_num = 8 if training_version == "light" else 30
|
||||
return (generation_prefix, model_id, default_fast, default_steps_num,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} and text {response.text}")
|
||||
@@ -0,0 +1,76 @@
|
||||
import numpy as np
|
||||
import requests
|
||||
from PIL import Image
|
||||
import io
|
||||
import torch
|
||||
|
||||
from .common import deserialize_and_get_comfy_key, image_to_base64, preprocess_image
|
||||
|
||||
class TailoredPortraitNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"tailored_model_id": ("STRING",), # API Key input with a default value
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
||||
},
|
||||
"optional": {
|
||||
"seed": ("INT", {"default": 123456}),
|
||||
"tailored_model_influence": ("FLOAT", {"default": 0.9}),
|
||||
"id_strength": ("FLOAT", {"default": 0.7}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/tailored-gen/restyle_portrait" # Eraser API URL
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, tailored_model_id, api_key, seed, tailored_model_influence, id_strength):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
api_key = deserialize_and_get_comfy_key(api_key)
|
||||
|
||||
# Convert the image and mask directly to if isinstance(image, torch.Tensor):
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
|
||||
image_base64 = image_to_base64(image)
|
||||
|
||||
# Prepare the API request payload
|
||||
payload = {
|
||||
"id_image_file": f"{image_base64}",
|
||||
"tailored_model_id": int(tailored_model_id),
|
||||
"tailored_model_influence": tailored_model_influence,
|
||||
"id_strength": id_strength,
|
||||
"seed": seed
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
# Check for successful response
|
||||
if response.status_code == 200:
|
||||
print('response is 200')
|
||||
# Process the output image from API response
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['image_res'])
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = result_image.convert("RGB")
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
@@ -0,0 +1,95 @@
|
||||
import requests
|
||||
|
||||
from .common import deserialize_and_get_comfy_key, postprocess_image, preprocess_image, image_to_base64
|
||||
|
||||
|
||||
class Text2ImageBaseNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"api_key": ("STRING", ),
|
||||
},
|
||||
"optional": {
|
||||
"prompt": ("STRING",),
|
||||
"aspect_ratio": (["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"], {"default": "4:3"}),
|
||||
"seed": ("INT", {"default": -1}),
|
||||
"negative_prompt": ("STRING", {"default": ""}),
|
||||
"steps_num": ("INT", {"default": 30}),
|
||||
"prompt_enhancement": ("INT", {"default": 0}),
|
||||
"text_guidance_scale": ("INT", {"default": 5}),
|
||||
"medium": (["photography", "art", "none"], {"default": "none"}),
|
||||
"guidance_method_1": (["controlnet_canny", "controlnet_depth", "controlnet_recoloring", "controlnet_color_grid"], {"default": "controlnet_canny"}),
|
||||
"guidance_method_1_scale": ("FLOAT", {"default": 1.0}),
|
||||
"guidance_method_1_image": ("IMAGE", ),
|
||||
"guidance_method_2": (["controlnet_canny", "controlnet_depth", "controlnet_recoloring", "controlnet_color_grid"], {"default": "controlnet_canny"}),
|
||||
"guidance_method_2_scale": ("FLOAT", {"default": 1.0}),
|
||||
"guidance_method_2_image": ("IMAGE", ),
|
||||
"image_prompt_mode": (["regular", "style_only"], {"default": "regular"}),
|
||||
"image_prompt_image": ("IMAGE", ),
|
||||
"image_prompt_scale": ("FLOAT", {"default": 1.0}),
|
||||
"content_moderation": ("INT", {"default": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/text-to-image/base/3.2"
|
||||
|
||||
def execute(
|
||||
self, api_key, prompt, aspect_ratio, seed, negative_prompt,
|
||||
steps_num, prompt_enhancement, text_guidance_scale, medium,
|
||||
guidance_method_1=None, guidance_method_1_scale=None, guidance_method_1_image=None,
|
||||
guidance_method_2=None, guidance_method_2_scale=None, guidance_method_2_image=None,
|
||||
image_prompt_mode=None, image_prompt_image=None, image_prompt_scale=None,
|
||||
content_moderation=0,
|
||||
):
|
||||
api_key = deserialize_and_get_comfy_key(api_key)
|
||||
payload = {
|
||||
"prompt": prompt,
|
||||
"num_results": 1,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"sync": True,
|
||||
"seed": seed,
|
||||
"negative_prompt": negative_prompt,
|
||||
"steps_num": steps_num,
|
||||
"text_guidance_scale": text_guidance_scale,
|
||||
"prompt_enhancement": prompt_enhancement,
|
||||
"content_moderation": content_moderation,
|
||||
}
|
||||
if medium != "none":
|
||||
payload["medium"] = medium
|
||||
if guidance_method_1_image is not None:
|
||||
guidance_method_1_image = preprocess_image(guidance_method_1_image)
|
||||
guidance_method_1_image = image_to_base64(guidance_method_1_image)
|
||||
payload["guidance_method_1"] = guidance_method_1
|
||||
payload["guidance_method_1_scale"] = guidance_method_1_scale
|
||||
payload["guidance_method_1_image_file"] = guidance_method_1_image
|
||||
if guidance_method_2_image is not None:
|
||||
guidance_method_2_image = preprocess_image(guidance_method_2_image)
|
||||
guidance_method_2_image = image_to_base64(guidance_method_2_image)
|
||||
payload["guidance_method_2"] = guidance_method_2
|
||||
payload["guidance_method_2_scale"] = guidance_method_2_scale
|
||||
payload["guidance_method_2_image_file"] = guidance_method_2_image
|
||||
if image_prompt_image is not None:
|
||||
image_prompt_image = preprocess_image(image_prompt_image)
|
||||
image_prompt_image = image_to_base64(image_prompt_image)
|
||||
payload["image_prompt_mode"] = image_prompt_mode
|
||||
payload["image_prompt_file"] = image_prompt_image
|
||||
payload["image_prompt_scale"] = image_prompt_scale
|
||||
response = requests.post(
|
||||
self.api_url,
|
||||
json=payload,
|
||||
headers={"api_token": api_key}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['result'][0]["urls"][0])
|
||||
result_image = postprocess_image(image_response.content)
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} and text {response.text}")
|
||||
@@ -0,0 +1,88 @@
|
||||
import requests
|
||||
|
||||
from .common import deserialize_and_get_comfy_key, postprocess_image, preprocess_image, image_to_base64
|
||||
|
||||
|
||||
class Text2ImageFastNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"api_key": ("STRING", ),
|
||||
},
|
||||
"optional": {
|
||||
"prompt": ("STRING",),
|
||||
"aspect_ratio": (["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"], {"default": "4:3"}),
|
||||
"seed": ("INT", {"default": -1}),
|
||||
"steps_num": ("INT", {"default": 8}),
|
||||
"prompt_enhancement": ("INT", {"default": 0}),
|
||||
"guidance_method_1": (["controlnet_canny", "controlnet_depth", "controlnet_recoloring", "controlnet_color_grid"], {"default": "controlnet_canny"}),
|
||||
"guidance_method_1_scale": ("FLOAT", {"default": 1.0}),
|
||||
"guidance_method_1_image": ("IMAGE", ),
|
||||
"guidance_method_2": (["controlnet_canny", "controlnet_depth", "controlnet_recoloring", "controlnet_color_grid"], {"default": "controlnet_canny"}),
|
||||
"guidance_method_2_scale": ("FLOAT", {"default": 1.0}),
|
||||
"guidance_method_2_image": ("IMAGE", ),
|
||||
"image_prompt_mode": (["regular", "style_only"], {"default": "regular"}),
|
||||
"image_prompt_image": ("IMAGE", ),
|
||||
"image_prompt_scale": ("FLOAT", {"default": 1.0}),
|
||||
"content_moderation": ("INT", {"default": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/text-to-image/fast/2.3" #"http://0.0.0.0:5000/v1/text-to-image/fast/2.3"
|
||||
|
||||
def execute(
|
||||
self, api_key, prompt, aspect_ratio, seed,
|
||||
steps_num, prompt_enhancement,
|
||||
guidance_method_1=None, guidance_method_1_scale=None, guidance_method_1_image=None,
|
||||
guidance_method_2=None, guidance_method_2_scale=None, guidance_method_2_image=None,
|
||||
image_prompt_mode=None, image_prompt_image=None, image_prompt_scale=None,
|
||||
content_moderation=0,
|
||||
):
|
||||
api_key = deserialize_and_get_comfy_key(api_key)
|
||||
payload = {
|
||||
"prompt": prompt,
|
||||
"num_results": 1,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"sync": True,
|
||||
"seed": seed,
|
||||
"steps_num": steps_num,
|
||||
"prompt_enhancement": prompt_enhancement,
|
||||
"content_moderation": content_moderation,
|
||||
}
|
||||
if guidance_method_1_image is not None:
|
||||
guidance_method_1_image = preprocess_image(guidance_method_1_image)
|
||||
guidance_method_1_image = image_to_base64(guidance_method_1_image)
|
||||
payload["guidance_method_1"] = guidance_method_1
|
||||
payload["guidance_method_1_scale"] = guidance_method_1_scale
|
||||
payload["guidance_method_1_image_file"] = guidance_method_1_image
|
||||
if guidance_method_2_image is not None:
|
||||
guidance_method_2_image = preprocess_image(guidance_method_2_image)
|
||||
guidance_method_2_image = image_to_base64(guidance_method_2_image)
|
||||
payload["guidance_method_2"] = guidance_method_2
|
||||
payload["guidance_method_2_scale"] = guidance_method_2_scale
|
||||
payload["guidance_method_2_image_file"] = guidance_method_2_image
|
||||
if image_prompt_image is not None:
|
||||
image_prompt_image = preprocess_image(image_prompt_image)
|
||||
image_prompt_image = image_to_base64(image_prompt_image)
|
||||
payload["image_prompt_mode"] = image_prompt_mode
|
||||
payload["image_prompt_file"] = image_prompt_image
|
||||
payload["image_prompt_scale"] = image_prompt_scale
|
||||
response = requests.post(
|
||||
self.api_url,
|
||||
json=payload,
|
||||
headers={"api_token": api_key}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['result'][0]["urls"][0])
|
||||
result_image = postprocess_image(image_response.content)
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} and text {response.text}")
|
||||
@@ -0,0 +1,64 @@
|
||||
import requests
|
||||
|
||||
from .common import deserialize_and_get_comfy_key, postprocess_image
|
||||
|
||||
|
||||
class Text2ImageHDNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"api_key": ("STRING", ),
|
||||
},
|
||||
"optional": {
|
||||
"prompt": ("STRING",),
|
||||
"aspect_ratio": (["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"], {"default": "4:3"}),
|
||||
"seed": ("INT", {"default": -1}),
|
||||
"negative_prompt": ("STRING", {"default": ""}),
|
||||
"steps_num": ("INT", {"default": 30}),
|
||||
"prompt_enhancement": ("INT", {"default": 0}),
|
||||
"text_guidance_scale": ("INT", {"default": 5}),
|
||||
"medium": (["photography", "art", "none"], {"default": "none"}),
|
||||
"content_moderation": ("INT", {"default": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/text-to-image/hd/2.2" #"http://0.0.0.0:5000/v1/text-to-image/hd/2.3"
|
||||
|
||||
def execute(
|
||||
self, api_key, prompt, aspect_ratio, seed, negative_prompt,
|
||||
steps_num, prompt_enhancement, text_guidance_scale, medium, content_moderation=0,
|
||||
):
|
||||
api_key = deserialize_and_get_comfy_key(api_key)
|
||||
payload = {
|
||||
"prompt": prompt,
|
||||
"num_results": 1,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"sync": True,
|
||||
"seed": seed,
|
||||
"negative_prompt": negative_prompt,
|
||||
"steps_num": steps_num,
|
||||
"text_guidance_scale": text_guidance_scale,
|
||||
"prompt_enhancement": prompt_enhancement,
|
||||
"content_moderation": content_moderation,
|
||||
}
|
||||
if medium != "none":
|
||||
payload["medium"] = medium
|
||||
response = requests.post(
|
||||
self.api_url,
|
||||
json=payload,
|
||||
headers={"api_token": api_key}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['result'][0]["urls"][0])
|
||||
result_image = postprocess_image(image_response.content)
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} and text {response.text}")
|
||||
@@ -0,0 +1,207 @@
|
||||
import requests
|
||||
import torch
|
||||
from ..common import deserialize_and_get_comfy_key, postprocess_image, preprocess_image, image_to_base64
|
||||
|
||||
shot_by_text_api_url = (
|
||||
"https://engine.prod.bria-api.com/v1/product/lifestyle_shot_by_text"
|
||||
)
|
||||
shot_by_image_api_url = (
|
||||
"https://engine.prod.bria-api.com/v1/product/lifestyle_shot_by_image"
|
||||
)
|
||||
|
||||
from enum import Enum
|
||||
|
||||
class PlacementType(str, Enum):
|
||||
ORIGINAL = "original"
|
||||
AUTOMATIC = "automatic"
|
||||
MANUAL_PLACEMENT = "manual_placement"
|
||||
MANUAL_PADDING = "manual_padding"
|
||||
CUSTOM_COORDINATES = "custom_coordinates"
|
||||
AUTOMATIC_ASPECT_RATIO = "automatic_aspect_ratio"
|
||||
|
||||
|
||||
|
||||
def validate_api_key(api_key):
|
||||
"""Validate API key input"""
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
|
||||
|
||||
def update_payload_for_placement(placement_type, payload, **kwargs):
|
||||
if placement_type == PlacementType.AUTOMATIC.value:
|
||||
payload["shot_size"] = [
|
||||
int(x.strip()) for x in kwargs.get("shot_size").split(",")
|
||||
]
|
||||
elif placement_type == PlacementType.MANUAL_PLACEMENT.value:
|
||||
payload["shot_size"] = [
|
||||
int(x.strip()) for x in kwargs.get("shot_size").split(",")
|
||||
]
|
||||
payload["manual_placement_selection"] = [
|
||||
kwargs.get("manual_placement_selection", "upper_left")
|
||||
]
|
||||
elif placement_type == PlacementType.CUSTOM_COORDINATES.value:
|
||||
payload["shot_size"] = [
|
||||
int(x.strip()) for x in kwargs.get("shot_size").split(",")
|
||||
]
|
||||
payload["foreground_image_size"] = [
|
||||
int(x.strip()) for x in kwargs.get("foreground_image_size").split(",")
|
||||
]
|
||||
payload["foreground_image_location"] = [
|
||||
int(x.strip()) for x in kwargs.get("foreground_image_location").split(",")
|
||||
]
|
||||
elif placement_type == PlacementType.MANUAL_PADDING.value:
|
||||
payload["padding_values"] = [
|
||||
int(x.strip()) for x in kwargs.get("padding_values").split(",")
|
||||
]
|
||||
|
||||
elif placement_type == PlacementType.AUTOMATIC_ASPECT_RATIO.value:
|
||||
payload["aspect_ratio"] = kwargs.get("aspect_ratio", "1:1")
|
||||
elif placement_type == PlacementType.ORIGINAL.value:
|
||||
payload["original_quality"] = kwargs.get("original_quality", True)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def create_text_payload(
|
||||
image, api_key, scene_description, mode, placement_type, **kwargs
|
||||
):
|
||||
|
||||
validate_api_key(api_key)
|
||||
|
||||
|
||||
# Process image
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
|
||||
image_base64 = image_to_base64(image)
|
||||
|
||||
payload = {
|
||||
"file": image_base64,
|
||||
"placement_type": placement_type,
|
||||
"sync": True,
|
||||
"num_results": 1,
|
||||
"force_rmbg": kwargs.get("force_rmbg", False),
|
||||
"content_moderation": kwargs.get("content_moderation", False),
|
||||
"scene_description": scene_description,
|
||||
"mode": mode,
|
||||
"optimize_description": kwargs.get("optimize_description", True),
|
||||
}
|
||||
|
||||
if kwargs.get("exclude_elements", "").strip():
|
||||
payload["exclude_elements"] = kwargs["exclude_elements"]
|
||||
|
||||
payload = update_payload_for_placement(placement_type, payload, **kwargs)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def create_image_payload(image, ref_image, api_key, placement_type, **kwargs):
|
||||
"""Create payload for image-based shot nodes"""
|
||||
validate_api_key(api_key)
|
||||
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
if isinstance(ref_image, torch.Tensor):
|
||||
ref_image = preprocess_image(ref_image)
|
||||
|
||||
image_base64 = image_to_base64(image)
|
||||
ref_image_base64 = image_to_base64(ref_image)
|
||||
|
||||
# Base payload
|
||||
payload = {
|
||||
"file": image_base64,
|
||||
"ref_image_file": ref_image_base64,
|
||||
"enhance_ref_image": kwargs.get("enhance_ref_image", True),
|
||||
"ref_image_influence": kwargs.get("ref_image_influence", 1.0),
|
||||
"placement_type": placement_type,
|
||||
"sync": True,
|
||||
"num_results": 1,
|
||||
"force_rmbg": kwargs.get("force_rmbg", False),
|
||||
"content_moderation": kwargs.get("content_moderation", False),
|
||||
}
|
||||
|
||||
payload = update_payload_for_placement(placement_type, payload, **kwargs)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def make_api_request(api_url, payload, api_key, Placement_type = None):
|
||||
"""Make API request and return processed image"""
|
||||
|
||||
|
||||
try:
|
||||
api_key = deserialize_and_get_comfy_key(api_key)
|
||||
headers = {"Content-Type": "application/json", "api_token": f"{api_key}"}
|
||||
response = requests.post(api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
print("response is 200")
|
||||
response_dict = response.json()
|
||||
if Placement_type == PlacementType.AUTOMATIC.value:
|
||||
result_images = []
|
||||
for i, result in enumerate(response_dict.get("result", [])[:7]):
|
||||
image_url = result[0]
|
||||
image_response = requests.get(image_url)
|
||||
processed = postprocess_image(image_response.content)
|
||||
result_images.append(processed)
|
||||
|
||||
# If less than 7 images, pad with None to match ComfyUI return structure
|
||||
while len(result_images) < 7:
|
||||
result_images.append(None)
|
||||
print(result_images)
|
||||
|
||||
return tuple(result_images)
|
||||
|
||||
image_response = requests.get(response_dict["result"][0][0])
|
||||
result_image = postprocess_image(image_response.content)
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(
|
||||
f"Error: API request failed with status code {response.status_code}{response.text}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
|
||||
|
||||
def get_common_input_types():
|
||||
"""Get common input types for all nodes"""
|
||||
return {
|
||||
"required": {"api_key": ("STRING", {"default": "BRIA_API_TOKEN"})},
|
||||
"optional": {
|
||||
"force_rmbg": ("BOOLEAN", {"default": False}),
|
||||
"content_moderation": ("BOOLEAN", {"default": False}),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_text_input_types():
|
||||
"""Get text-specific input types"""
|
||||
common = get_common_input_types()
|
||||
common["required"].update(
|
||||
{
|
||||
"image": ("IMAGE",),
|
||||
"scene_description": ("STRING",),
|
||||
"mode": (["base", "fast", "high_control"], {"default": "fast"}),
|
||||
}
|
||||
)
|
||||
common["optional"].update(
|
||||
{
|
||||
"optimize_description": ("BOOLEAN", {"default": True}),
|
||||
"exclude_elements": ("STRING", {"default": ""}),
|
||||
}
|
||||
)
|
||||
return common
|
||||
|
||||
|
||||
def get_image_input_types():
|
||||
"""Get image-specific input types"""
|
||||
common = get_common_input_types()
|
||||
common["required"].update({"image": ("IMAGE",), "ref_image": ("IMAGE",)})
|
||||
common["optional"].update(
|
||||
{
|
||||
"enhance_ref_image": ("BOOLEAN", {"default": True}),
|
||||
"ref_image_influence": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0}),
|
||||
}
|
||||
)
|
||||
return common
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-bria-api"
|
||||
description = "Custom nodes for ComfyUI using BRIA's API."
|
||||
version = "1.0.0"
|
||||
version = "2.1.7"
|
||||
license = {file = "LICENSE"}
|
||||
|
||||
[project.urls]
|
||||
|
||||
-203
@@ -1,203 +0,0 @@
|
||||
{
|
||||
"last_node_id": 28,
|
||||
"last_link_id": 42,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 15,
|
||||
"type": "Note",
|
||||
"pos": {
|
||||
"0": 1021,
|
||||
"1": 280
|
||||
},
|
||||
"size": {
|
||||
"0": 311.8914794921875,
|
||||
"1": 153.69827270507812
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"The default BRIA API key for ComfyUI (BRIA_ComfyUI_Key) offers 10,000 API calls for the entire community. \n\nGet your own token at:\nhttps://bria.ai/api/"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "PreviewImage",
|
||||
"pos": {
|
||||
"0": 1410,
|
||||
"1": 160
|
||||
},
|
||||
"size": {
|
||||
"0": 474.7605895996094,
|
||||
"1": 303.117919921875
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 42
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "LoadImage",
|
||||
"pos": {
|
||||
"0": 477,
|
||||
"1": 156
|
||||
},
|
||||
"size": {
|
||||
"0": 408.4602355957031,
|
||||
"1": 333.19830322265625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
40
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
41
|
||||
],
|
||||
"slot_index": 1,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"clipspace/clipspace-mask-82245.69999998808.png [input]",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"type": "BriaEraser",
|
||||
"pos": {
|
||||
"0": 1022,
|
||||
"1": 159
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 78
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 40
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 41
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "output_image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
42
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "BriaEraser"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_ComfyUI_Key"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "Note",
|
||||
"pos": {
|
||||
"0": 483,
|
||||
"1": 39
|
||||
},
|
||||
"size": [
|
||||
396.80859375,
|
||||
61.8046875
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Right click, and choose \"Open in Mask Editor\" to draw a mask of areas you want animated more. "
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
40,
|
||||
21,
|
||||
0,
|
||||
28,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
41,
|
||||
21,
|
||||
1,
|
||||
28,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
42,
|
||||
28,
|
||||
0,
|
||||
13,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
-293.75,
|
||||
167.65625
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,543 @@
|
||||
{
|
||||
"id": "3875cd62-7a0e-4c8c-8951-22ec8a63dd8d",
|
||||
"revision": 0,
|
||||
"last_node_id": 14,
|
||||
"last_link_id": 8,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 3,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
632.3427124023438,
|
||||
32.13115310668945
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.62",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "RefineImageNodeV2",
|
||||
"pos": [
|
||||
704.8090209960938,
|
||||
218.65850830078125
|
||||
],
|
||||
"size": [
|
||||
287.4712829589844,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "structured_prompt"
|
||||
},
|
||||
"link": 3
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"shape": 7,
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
},
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
4
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfyui-bria-api",
|
||||
"ver": "2.1.4",
|
||||
"Node name for S&R": "RefineImageNodeV2",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {
|
||||
"api_token": true,
|
||||
"prompt": true,
|
||||
"structured_prompt": true,
|
||||
"model_version": true,
|
||||
"negative_prompt": true,
|
||||
"aspect_ratio": true,
|
||||
"steps_num": true,
|
||||
"guidance_scale": true,
|
||||
"seed": true
|
||||
},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"",
|
||||
"FIBO",
|
||||
"",
|
||||
"1:1",
|
||||
50,
|
||||
5,
|
||||
123456,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1153.29345703125,
|
||||
270.2264404296875
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.62",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "GenerateImageNodeV2",
|
||||
"pos": [
|
||||
272.6366882324219,
|
||||
227.9468231201172
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
290
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"shape": 7,
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfyui-bria-api",
|
||||
"ver": "2.1.4",
|
||||
"Node name for S&R": "GenerateImageNodeV2",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {
|
||||
"api_token": true,
|
||||
"prompt": true,
|
||||
"model_version": true,
|
||||
"negative_prompt": true,
|
||||
"aspect_ratio": true,
|
||||
"steps_num": true,
|
||||
"guidance_scale": true,
|
||||
"seed": true
|
||||
},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"FIBO",
|
||||
"",
|
||||
"1:1",
|
||||
50,
|
||||
5,
|
||||
123456,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
-43.613197326660156,
|
||||
324.2381896972656
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
88
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"If you would like to start with prompt"
|
||||
],
|
||||
"color": "#c09430",
|
||||
"bgcolor": "rgba(24,24,27,.9)"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1182.5216064453125,
|
||||
1006.7373657226562
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 7
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.62",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "RefineImageNodeV2",
|
||||
"pos": [
|
||||
703.2684326171875,
|
||||
1034.9910888671875
|
||||
],
|
||||
"size": [
|
||||
287.4712829589844,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "structured_prompt"
|
||||
},
|
||||
"link": 5
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"shape": 7,
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
},
|
||||
"link": 6
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
7
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfyui-bria-api",
|
||||
"ver": "2.1.5",
|
||||
"Node name for S&R": "RefineImageNodeV2"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"",
|
||||
"FIBO",
|
||||
"",
|
||||
"1:1",
|
||||
50,
|
||||
5,
|
||||
123456,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
-47.50065612792969,
|
||||
1042.9178466796875
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
95.76702880859375
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"If you would like to start with reference image + prompt"
|
||||
],
|
||||
"color": "#c09430",
|
||||
"bgcolor": "rgba(24,24,27,.9)"
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
593.5224609375,
|
||||
844.5531616210938
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.62",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "GenerateImageNodeV2",
|
||||
"pos": [
|
||||
262.3690490722656,
|
||||
1041.67431640625
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
290
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"shape": 7,
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
8
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
6
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfyui-bria-api",
|
||||
"ver": "2.1.5",
|
||||
"Node name for S&R": "GenerateImageNodeV2"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"FIBO",
|
||||
"",
|
||||
"1:1",
|
||||
50,
|
||||
5,
|
||||
123456,
|
||||
"randomize"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
2,
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
1,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
3,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
4,
|
||||
2,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
5,
|
||||
7,
|
||||
1,
|
||||
9,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
6,
|
||||
7,
|
||||
2,
|
||||
9,
|
||||
1,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
7,
|
||||
9,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
8,
|
||||
7,
|
||||
0,
|
||||
14,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ue_links": [],
|
||||
"ds": {
|
||||
"scale": 0.3897342147473183,
|
||||
"offset": [
|
||||
658.5745748406673,
|
||||
99.37411299539059
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.28.4",
|
||||
"VHS_latentpreview": false,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"last_node_id":39,"last_link_id":65,"nodes":[{"id":34,"type":"LoadImage","pos":[19.94045066833496,1075.806640625],"size":[315,314],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[61],"slot_index":0,"localized_name":"IMAGE"},{"name":"MASK","type":"MASK","links":null,"localized_name":"MASK"}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["quirky-red-brick-brick-wallpaper.jpg","image"]},{"id":36,"type":"PreviewImage","pos":[468.7108154296875,1210.288818359375],"size":[210,246],"flags":{},"order":5,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":63,"localized_name":"images"}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"},"widgets_values":[]},{"id":33,"type":"PreviewImage","pos":[1317.4083251953125,1118.4864501953125],"size":[210,246],"flags":{},"order":8,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":59,"localized_name":"images"}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"},"widgets_values":[]},{"id":31,"type":"PreviewImage","pos":[1668.734619140625,796.7755737304688],"size":[210,246],"flags":{},"order":10,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":57,"localized_name":"images"}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"},"widgets_values":[]},{"id":29,"type":"PreviewImage","pos":[872.8858032226562,679.9429321289062],"size":[210,246],"flags":{},"order":6,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":55,"localized_name":"images"}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"},"widgets_values":[]},{"id":28,"type":"LoadImage","pos":[29.629886627197266,676.8370361328125],"size":[315,314],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[54],"slot_index":0,"localized_name":"IMAGE"},{"name":"MASK","type":"MASK","links":null,"localized_name":"MASK"}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["pexels-photo-1808399.jpeg","image"]},{"id":35,"type":"RemoveForegroundNode","pos":[414.2638854980469,1079.1705322265625],"size":[315,58],"flags":{},"order":3,"mode":0,"inputs":[{"name":"image","type":"IMAGE","link":61,"localized_name":"image"}],"outputs":[{"name":"output_image","type":"IMAGE","links":[62,63],"slot_index":0,"localized_name":"output_image"}],"properties":{"Node name for S&R":"RemoveForegroundNode"},"widgets_values":["BRIA_API_TOKEN"]},{"id":32,"type":"ReplaceBgNode","pos":[834.6115112304688,1063.0406494140625],"size":[315,294],"flags":{},"order":7,"mode":0,"inputs":[{"name":"image","type":"IMAGE","link":65,"localized_name":"image"},{"name":"ref_image","type":"IMAGE","link":62,"shape":7,"localized_name":"ref_image"}],"outputs":[{"name":"output_image","type":"IMAGE","links":[59,64],"slot_index":0,"localized_name":"output_image"}],"properties":{"Node name for S&R":"ReplaceBgNode"},"widgets_values":["BRIA_API_TOKEN",false,"",true,true,true,false,"",1978,"randomize"]},{"id":30,"type":"ImageExpansionNode","pos":[1265.001220703125,798.8323974609375],"size":[315,226],"flags":{},"order":9,"mode":0,"inputs":[{"name":"image","type":"IMAGE","link":64,"localized_name":"image"}],"outputs":[{"name":"output_image","type":"IMAGE","links":[57],"slot_index":0,"localized_name":"output_image"}],"properties":{"Node name for S&R":"ImageExpansionNode"},"widgets_values":["600,760","200, 0","BRIA_API_TOKEN","1200, 800","",1729,"randomize","Ugly, mutated"]},{"id":38,"type":"Note","pos":[436.7110900878906,566.2047119140625],"size":[306.28387451171875,58],"flags":{},"order":2,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["You can get your BRIA API token at:\nhttps://bria.ai/api/"],"color":"#432","bgcolor":"#653"},{"id":27,"type":"RmbgNode","pos":[430.8815002441406,678.7727661132812],"size":[315,58],"flags":{},"order":4,"mode":0,"inputs":[{"name":"image","type":"IMAGE","link":54,"localized_name":"image"}],"outputs":[{"name":"output_image","type":"IMAGE","links":[55,65],"slot_index":0,"localized_name":"output_image"}],"properties":{"Node name for S&R":"RmbgNode"},"widgets_values":["BRIA_API_TOKEN"]}],"links":[[51,5,0,15,2,"STRING"],[54,28,0,27,0,"IMAGE"],[55,27,0,29,0,"IMAGE"],[57,30,0,31,0,"IMAGE"],[59,32,0,33,0,"IMAGE"],[61,34,0,35,0,"IMAGE"],[62,35,0,32,1,"IMAGE"],[63,35,0,36,0,"IMAGE"],[64,32,0,30,0,"IMAGE"],[65,27,0,32,0,"IMAGE"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.8140274938684037,"offset":[101.53311990208498,-477.0342684311694]},"node_versions":{"comfyui-bria-api":"c72754d15b53a13ee0c0419d70401232c56b7fdb","comfy-core":"v0.3.8-1-gc441048","ComfyUI-Jjk-Nodes":"b3c99bb78a99551776b5eab1a820e1cd58f84f31"}},"version":0.4}
|
||||
@@ -0,0 +1 @@
|
||||
{"last_node_id":41,"last_link_id":62,"nodes":[{"id":14,"type":"Note","pos":[478,444],"size":[396.80859375,61.8046875],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["Right click, and choose \"Open in Mask Editor\" to draw a mask of areas you want to erase."],"color":"#432","bgcolor":"#653"},{"id":15,"type":"Note","pos":[1080.3062744140625,445.9654541015625],"size":[306.28387451171875,58],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["You can get your BRIA API token at:\nhttps://bria.ai/api/"],"color":"#432","bgcolor":"#653"},{"id":30,"type":"LoadImage","pos":[479,572],"size":[395.7845153808594,352.8512268066406],"flags":{},"order":2,"mode":0,"inputs":[],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[56],"slot_index":0,"shape":3,"localized_name":"IMAGE"},{"name":"MASK","type":"MASK","links":[57],"slot_index":1,"shape":3,"localized_name":"MASK"}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["clipspace/clipspace-mask-4068974.800000012.png [input]","image"]},{"id":37,"type":"PreviewImage","pos":[1504.4755859375,568.9967651367188],"size":[438.50262451171875,376.8338317871094],"flags":{},"order":6,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":58,"localized_name":"images"}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"},"widgets_values":[]},{"id":33,"type":"PreviewImage","pos":[1502.785888671875,1078.3564453125],"size":[433.29193115234375,357.1255187988281],"flags":{},"order":7,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":54,"localized_name":"images"}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"},"widgets_values":[]},{"id":40,"type":"LoadImage","pos":[541.1226806640625,1079.39697265625],"size":[315,314],"flags":{},"order":3,"mode":0,"inputs":[],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[61],"slot_index":0,"localized_name":"IMAGE"},{"name":"MASK","type":"MASK","links":[62],"slot_index":1,"localized_name":"MASK"}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["clipspace/clipspace-mask-4411367.100000024.png [input]","image"]},{"id":34,"type":"BriaGenFill","pos":[1032.416748046875,1073.984619140625],"size":[315,102],"flags":{},"order":5,"mode":0,"inputs":[{"name":"image","type":"IMAGE","link":61,"localized_name":"image"},{"name":"mask","type":"MASK","link":62,"localized_name":"mask"}],"outputs":[{"name":"output_image","type":"IMAGE","links":[54],"slot_index":0,"shape":3,"localized_name":"output_image"}],"properties":{"Node name for S&R":"BriaGenFill"},"widgets_values":["a blue coffee mug","BRIA_API_TOKEN"]},{"id":36,"type":"BriaEraser","pos":[1068.6063232421875,574.6080322265625],"size":[315,78],"flags":{},"order":4,"mode":0,"inputs":[{"name":"image","type":"IMAGE","link":56,"localized_name":"image"},{"name":"mask","type":"MASK","link":57,"localized_name":"mask"}],"outputs":[{"name":"output_image","type":"IMAGE","links":[58],"slot_index":0,"localized_name":"output_image"}],"properties":{"Node name for S&R":"BriaEraser"},"widgets_values":["BRIA_API_TOKEN"]}],"links":[[54,34,0,33,0,"IMAGE"],[56,30,0,36,0,"IMAGE"],[57,30,1,36,1,"MASK"],[58,36,0,37,0,"IMAGE"],[61,40,0,34,0,"IMAGE"],[62,40,1,34,1,"MASK"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.6727499949325677,"offset":[131.53042816003972,-419.53430403204317]}},"version":0.4}
|
||||
@@ -0,0 +1,304 @@
|
||||
{
|
||||
"id": "1cdd7d4c-58b5-4047-947b-1977ad36d364",
|
||||
"revision": 0,
|
||||
"last_node_id": 14,
|
||||
"last_link_id": 11,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 6,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1351.83154296875,
|
||||
26.696861267089844
|
||||
],
|
||||
"size": [
|
||||
399.811279296875,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 5
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
383.92852783203125,
|
||||
38.40964889526367
|
||||
],
|
||||
"size": [
|
||||
397.5969543457031,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
1,
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"CAR.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
369.0213623046875,
|
||||
415.34893798828125
|
||||
],
|
||||
"size": [
|
||||
450.83685302734375,
|
||||
314.0000305175781
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
4
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"seed_713360865.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
951.4669189453125,
|
||||
5.085720062255859
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
88
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"You can get your BRIA API token at:\nhttps://bria.ai/api/"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1368.5450439453125,
|
||||
340.4889221191406
|
||||
],
|
||||
"size": [
|
||||
387.0335693359375,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 6
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "ShotByTextOriginal",
|
||||
"pos": [
|
||||
941.8839111328125,
|
||||
149.14889526367188
|
||||
],
|
||||
"size": [
|
||||
273.388671875,
|
||||
202
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "output_image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
5
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShotByTextOriginal"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"sea",
|
||||
"fast",
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "ShotByImageOriginal",
|
||||
"pos": [
|
||||
945.0781860351562,
|
||||
441.9688415527344
|
||||
],
|
||||
"size": [
|
||||
272.0703125,
|
||||
174
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 3
|
||||
},
|
||||
{
|
||||
"name": "ref_image",
|
||||
"type": "IMAGE",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "output_image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
6
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShotByImageOriginal"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
3,
|
||||
1,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
4,
|
||||
2,
|
||||
0,
|
||||
4,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
5,
|
||||
3,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
6,
|
||||
4,
|
||||
0,
|
||||
7,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.7513148009015777,
|
||||
"offset": [
|
||||
11.112206386364164,
|
||||
66.47311795454547
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.25.11"
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"last_node_id":21,"last_link_id":49,"nodes":[{"id":2,"type":"TailoredModelInfoNode","pos":[480.2208557128906,641.4290161132812],"size":[315,122],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"generation_prefix","type":"STRING","links":[2],"slot_index":0,"localized_name":"generation_prefix"},{"name":"default_fast","type":"INT","links":[46],"slot_index":1,"localized_name":"default_fast"},{"name":"default_steps_num","type":"INT","links":[40],"slot_index":2,"localized_name":"default_steps_num"}],"properties":{"Node name for S&R":"TailoredModelInfoNode"},"widgets_values":["",""]},{"id":3,"type":"PreviewImage","pos":[1728.9140625,688.2759399414062],"size":[210,246],"flags":{},"order":6,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":41,"localized_name":"images"}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"},"widgets_values":[]},{"id":11,"type":"LoadImage","pos":[693.87060546875,831.6130981445312],"size":[315,314],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[48],"slot_index":0,"localized_name":"IMAGE"},{"name":"MASK","type":"MASK","links":null,"localized_name":"MASK"}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["example.png","image"]},{"id":5,"type":"JjkShowText","pos":[847.2867431640625,591.890625],"size":[315,76],"flags":{},"order":4,"mode":0,"inputs":[{"name":"text","type":"STRING","link":2,"widget":{"name":"text"}}],"outputs":[{"name":"text","type":"STRING","links":[49],"slot_index":0,"shape":6,"localized_name":"text"}],"properties":{"Node name for S&R":"JjkShowText"},"widgets_values":["A photo of a character named Sami, a siamese cat with blue eyes, "]},{"id":15,"type":"BriaTailoredGen","pos":[1207.595947265625,645.6781005859375],"size":[456,438],"flags":{},"order":5,"mode":0,"inputs":[{"name":"guidance_method_1_image","type":"IMAGE","link":48,"shape":7,"localized_name":"guidance_method_1_image"},{"name":"guidance_method_2_image","type":"IMAGE","link":null,"shape":7,"localized_name":"guidance_method_2_image"},{"name":"generation_prefix","type":"STRING","link":49,"widget":{"name":"generation_prefix"},"shape":7},{"name":"fast","type":"INT","link":46,"widget":{"name":"fast"},"shape":7},{"name":"steps_num","type":"INT","link":40,"widget":{"name":"steps_num"},"shape":7}],"outputs":[{"name":"output_image","type":"IMAGE","links":[41],"slot_index":0,"localized_name":"output_image"}],"properties":{"Node name for S&R":"BriaTailoredGen"},"widgets_values":["","","a cat","","4:3",-1,"randomize",1,"","",1,"controlnet_canny",1,"controlnet_canny",1]},{"id":21,"type":"Note","pos":[1215.2469482421875,522.4407348632812],"size":[449.75360107421875,58],"flags":{},"order":3,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["You can get your BRIA API token at: https://bria.ai/api/"],"color":"#432","bgcolor":"#653"},{"id":19,"type":"Note","pos":[484.5993957519531,486.4328918457031],"size":[306.0655212402344,89.87609100341797],"flags":{},"order":2,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["This node is used to retrieve default settings and prompt prefixes for the chosen tailored model."],"color":"#432","bgcolor":"#653"}],"links":[[2,2,0,5,0,"STRING"],[40,2,2,15,4,"INT"],[41,15,0,3,0,"IMAGE"],[46,2,1,15,3,"INT"],[48,11,0,15,0,"IMAGE"],[49,5,0,15,2,"STRING"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.7627768444385483,"offset":[-122.90473166350671,-300.2018923615813]},"node_versions":{"comfyui-bria-api":"c72754d15b53a13ee0c0419d70401232c56b7fdb","comfy-core":"v0.3.8-1-gc441048","ComfyUI-Jjk-Nodes":"b3c99bb78a99551776b5eab1a820e1cd58f84f31"}},"version":0.4}
|
||||
@@ -0,0 +1,169 @@
|
||||
{
|
||||
"last_node_id": 13,
|
||||
"last_link_id": 11,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 12,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
669.7035522460938,
|
||||
136.97129821777344
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
10
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"pexels-photo-3246665.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1587.27001953125,
|
||||
97.39167022705078
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 11
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "Text2ImageFastNode",
|
||||
"pos": [
|
||||
1058.320556640625,
|
||||
96.64427185058594
|
||||
],
|
||||
"size": [
|
||||
438.71258544921875,
|
||||
394.27716064453125
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "guidance_method_1_image",
|
||||
"type": "IMAGE",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "guidance_method_2_image",
|
||||
"type": "IMAGE",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "image_prompt_image",
|
||||
"type": "IMAGE",
|
||||
"link": 10,
|
||||
"shape": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "output_image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
11
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "Text2ImageFastNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"A drawing of a lion on a table.\t",
|
||||
"4:3",
|
||||
990,
|
||||
"randomize",
|
||||
8,
|
||||
0,
|
||||
"controlnet_canny",
|
||||
1,
|
||||
"controlnet_canny",
|
||||
1,
|
||||
"regular",
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
10,
|
||||
12,
|
||||
0,
|
||||
10,
|
||||
2,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
11,
|
||||
10,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.7513148009015777,
|
||||
"offset": [
|
||||
11.112206386364164,
|
||||
66.47311795454547
|
||||
]
|
||||
},
|
||||
"node_versions": {
|
||||
"comfy-core": "v0.3.10-42-gff83865",
|
||||
"comfyui-bria-api": "499ec5d104cc5110407eafce468ce1d47ac168b3"
|
||||
},
|
||||
"VHS_latentpreview": false,
|
||||
"VHS_latentpreviewrate": 0
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
Reference in New Issue
Block a user