Compare commits

...
Author SHA1 Message Date
Yazan Numoor ddbf7d0695 Update pyproject.toml 2025-10-22 20:38:36 +03:00
Ubuntu 3befc0a2ac update automatic nodes to return 7 results 2025-10-16 08:15:20 +00:00
Ubuntu af6ef2a829 fix Gabi Feedback 2025-10-15 10:39:44 +00:00
Ubuntu bc5dacb9ef Merge branch 'main' of https://github.com/Bria-AI/ComfyUI-BRIA-API into WAI-4049 2025-09-29 13:44:10 +00:00
Ubuntu fc8aa8b6a7 WAI-4049 2025-09-29 13:39:47 +00:00
Yazan Numoor 9960a93044 Merge pull request #25 from Bria-AI/WAI-4011
WAI-4011
2025-09-29 15:47:10 +03:00
Ubuntu 8d3eef85ca update version 2025-09-29 12:44:46 +00:00
Ubuntu bb4108b6c4 WAI-4049 2025-09-29 11:48:24 +00:00
mabualrob1997 18a5ffbcca Update Readme.md 2025-09-29 12:45:46 +03:00
Ubuntu 4f3b7fb77a WAI-4011 2025-09-23 06:49:12 +00:00
Yazan Numoor c3b5fea335 Update pyproject.toml 2025-09-18 19:31:00 +03:00
Yazan Numoor b8e40e90bc Merge pull request #24 from Bria-AI/WAI-3976-feedback-fixes
WAI-3976-feedback-fixes
2025-09-18 19:30:33 +03:00
Ubuntu 9b3f15b7bd WAI-3976-feedback-fixes 2025-09-18 12:03:07 +00:00
Yazan Numoor f7c9ed12b0 Merge pull request #23 from Bria-AI/WAI-3976
WAI-3976
2025-09-17 17:55:23 +03:00
Yazan Numoor bdd053a81b update version 2025-09-17 17:55:05 +03:00
Ubuntu 2913a9eb6b update polling status url 2025-09-17 12:54:10 +00:00
Ubuntu 080c17f4a4 WAI-3976 2025-09-16 17:11:06 +00:00
Yazan Numoor 711080ffb0 Update pyproject.toml 2025-09-10 15:06:35 +03:00
Yazan Numoor 69f111bfe9 Merge pull request #22 from Bria-AI/WAI-3922
WAI-3922
2025-09-10 15:04:09 +03:00
Ubuntu 011f728b0d WAI-3922 2025-09-10 09:23:52 +00:00
gabiburtman ac9678cd23 Update pyproject.toml
fixed issue in shot_by_text_node
2025-09-09 20:10:44 +03:00
gabiburtman daede982ac Delete nodes/shot_by_text_auto_placement_node.py 2025-09-09 20:10:11 +03:00
gabiburtman e716e585df Create shot_by_text_auto_placement_node.py 2025-09-09 20:07:26 +03:00
gabiburtman 1296b6a27c Update shot_by_text_node.py 2025-09-09 20:04:48 +03:00
gabiburtman 19f15f4ba4 Update shot_by_text_node.py 2025-09-09 19:54:21 +03:00
gabiburtman 30d0154caf Update pyproject.toml 2025-09-09 13:00:51 +03:00
gabiburtman 283665f654 Update shot_by_text_node.py 2025-09-09 13:00:30 +03:00
gabiburtman 03e3bec952 Update pyproject.toml 2025-09-09 12:39:08 +03:00
gabiburtman f1793ba9ad Merge pull request #21 from ComfyNodePRs/update-publish-yaml
Update Github Action for Publishing to Comfy Registry
2025-09-09 12:22:17 +03:00
gabiburtman eda093ea66 Update shot_by_text_node.py 2025-09-09 11:13:41 +03:00
gabiburtman 165a103f0c Update replace_bg_node.py 2025-09-09 11:11:35 +03:00
gabiburtman ad2f4a5853 Update text_2_image_base_node.py 2025-06-16 16:24:17 +03:00
gabiburtman 43f858dca4 Update text_2_image_base_node.py 2025-06-12 16:06:17 +03:00
gabiburtman 538cd53ac8 Update text_2_image_base_node.py 2025-06-12 15:31:08 +03:00
BriaOr bae0ed3842 Added restyle portrait to readme 2025-03-17 14:43:25 +02:00
BriaOr a164f8ec45 Update Readme.md 2025-03-13 14:03:38 +02:00
BriaOr ebe9e2e6b1 Update Readme.md 2025-03-13 14:03:18 +02:00
BriaOr 429c51ac6d Update pyproject.toml 2025-03-11 14:50:01 +02:00
BriaOr aed4832984 Update __init__.py 2025-03-11 14:29:10 +02:00
BriaOr a78aff0fb2 Update pyproject.toml 2025-03-11 14:02:49 +02:00
xenia-kra 6edfc55109 Comfy tailored portrait (#20) 2025-03-10 17:32:37 +04:00
MishaFein 00dccbb17a Update Readme.md 2025-02-10 18:11:40 +02:00
MishaFein 1cb9ce4394 Update Readme.md 2025-02-10 18:10:57 +02:00
MishaFein b2f8b8d0e3 Update Readme.md 2025-02-10 18:05:56 +02:00
MishaFein 129bc599d0 Update Readme.md 2025-02-10 18:04:28 +02:00
MishaFein 28c2631581 Update Readme.md 2025-02-10 17:57:11 +02:00
ori-liberman baadbc02bc Add content moderation option to background removal and image generation nodes 2025-02-10 12:49:02 +00:00
or a73045f8e6 Added workflow sample to readme 2025-02-09 14:21:38 +02:00
tairBria dd74d03fd0 Merge pull request #17 from Bria-AI/t2i-tailored-content-moderation
content moderation t2i reimagime tailored
2025-02-09 13:12:30 +02:00
or bfc83da41f added text-to-image workflow and updated version 2025-02-04 17:40:08 +02:00
BriaOr 499ec5d104 Update Readme.md 2025-02-04 13:44:48 +02:00
BriaOr fef2e49902 Update Readme.md with new API URL 2025-02-04 13:39:36 +02:00
BriaOr 2eae6dae46 Merge pull request #16 from Bria-AI/feature/content-moderation-expansion-removefg
image expansion & remove fg- content moderation
2025-02-03 16:10:11 +02:00
israelweiss90 28c0cdf8d5 image expansion & remove fg- content moderation 2025-02-02 16:14:02 +00:00
Tair 54f380f1f0 content moderation t2i reimagime tailored 2025-02-02 14:52:18 +00:00
BriaOr 4f7691ff93 Merge pull request #14 from Bria-AI/bugfi/rmbg-temp-file
temp file to buffer to avoid OS dependency
2025-02-02 14:08:39 +02:00
israelweiss90 1781beff09 temp file to buffer to avoid OS dependency 2025-02-02 10:56:04 +00:00
BriaOr 502206f518 Merge pull request #13 from Bria-AI/DvirYBria-gen-fill-seed
Added seed go gen fill
2025-02-02 11:45:15 +02:00
DvirYBria da8adda281 Added seed go gen fill 2025-02-02 11:35:57 +02:00
BriaOr 1c02dea96b Update Readme.md 2025-01-30 17:08:42 +02:00
or 5b77fd90a8 updated genfill workflow 2025-01-30 16:52:21 +02:00
BriaOr 246e05ac3e Update Readme.md 2025-01-30 15:29:41 +02:00
or b6f2f8b297 Updated background workflow 2025-01-30 15:24:53 +02:00
BriaOr 5052a7bf94 Update Readme.md 2025-01-30 15:13:40 +02:00
BriaOr afc9599c63 Update Readme.md 2025-01-30 15:03:42 +02:00
BriaOr 13a3dfb2bb Update Readme.md 2025-01-30 15:02:07 +02:00
BriaOr 6573f2e43d Rename Product shot generation_workflow.json to product shot generation_workflow.json 2025-01-30 14:52:06 +02:00
or ac7e97c348 Updated workflows 2025-01-30 14:51:39 +02:00
BriaOr c6fe701115 Merge pull request #10 from Bria-AI/feature/new-comfyui-nodes
4 new nodes- rmbg, replace bg, expand, remove fg
2025-01-30 14:21:15 +02:00
BriaOr 3b5cca2dc8 Update Readme.md 2025-01-29 16:07:42 +02:00
BriaOr aaf0729f78 Update Readme.md 2025-01-28 17:57:17 +02:00
BriaOr 1a37414fe4 Update Readme.md 2025-01-28 17:51:10 +02:00
BriaOr 2fbbce0d1b Update Readme.md 2025-01-28 17:47:33 +02:00
BriaOr 23f9e46eff Update Readme.md 2025-01-28 17:39:26 +02:00
snomiao a4855c1a1a chore(publish): update workflow for node publishing
- Added permissions for issue writing in the workflow.
- Modified condition to check repository owner instead of fork status.
- Updated action version from `main` to `v1` for `publish-node-action`.
2025-01-25 07:47:26 +00:00
45 changed files with 1624 additions and 1517 deletions
+5 -3
View File
@@ -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 }}
+2 -1
View File
@@ -1 +1,2 @@
*.pyc
*.pyc
.idea
+44 -24
View File
@@ -4,14 +4,22 @@
<img src="./images/Bria Logo.svg" alt="BRIA Logo" width="200"/>
</p>
This repository provides custom nodes for ComfyUI, enabling direct access to **BRIA's API endpoints** for image generation workflows. **API documentation** is available [**here**](https://bria-ai-api-docs.redoc.ly/#operation//generation/bria-v2/text-to-image).
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/).
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.
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>.
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.
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>
<!-- Placeholder image of cool workflows. -->
@@ -19,43 +27,55 @@ To load a workflow, import the compatible workflow.json files from this [folder]
<!-- <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"/> -->
# Coming soon
- [ ] Video Editing
# Available Nodes
## Image Generation Nodes
These nodes create high-quality images from text or image prompts, generating photorealistic or artistic results with support for various aspect ratios.
### **Text2Image Base Node**
This node generates images from text prompts, serving as the foundation for creating visuals based on descriptive input. [[🤗model card](https://huggingface.co/briaai/BRIA-2.3)]
| 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. [[API docs](https://bria-ai-api-docs.redoc.ly/tag/Tailored-Generation)].
These nodes use pre-trained tailored models to generate images that faithfully reproduce specific visual IP elements or guidelines.
### **Tailored Gen**
This node generates images using a trained tailored model, faithfully reproducing specific visual IP elements or guidelines established during model training.
### **Tailored Model Info**
This node retrieves the **default settings** and **prompt prefix** of a **trained tailored model**. It provides the necessary information to configure and run the model in the **Tailored Gen node**, ensuring consistency with the model's intended behavior.
| 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.[[API docs](https://bria-ai-api-docs.redoc.ly/tag/Image-Editing)]
These nodes modify specific parts of images, enabling adjustments while maintaining the integrity of the rest of the image.
### **Eraser**
This node is used to remove specific objects or areas from an image by providing a mask. Powered by BRIA's ControlNet inpainting [[🤗model card](https://huggingface.co/briaai/BRIA-2.3-ControlNet-Inpainting)] [[🤗HF demo](https://huggingface.co/spaces/briaai/BRIA-Eraser-API)].
| 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. |
### **GenFill**
This node is used to generate objects by prompt in a specific region of an image. This functionality is powered by BRIA's ControlNet Generative Fill. [[🤗model card](https://huggingface.co/briaai/BRIA-2.3-ControlNet-Generative-Fill)] [[🤗HF demo](https://huggingface.co/spaces/briaai/BRIA-Generative-Fill-API)]
## 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) |
## Product Shot Generation Nodes
These nodes create high-quality product images for eCommerce workflows. [[API docs](https://bria-ai-api-docs.redoc.ly/tag/Product-Shots-Generation)]
### **ShotByText**
This node is used to modify the background in an image by providing a prompt, This functionality is powered by BRIA's ControlNet Background-Generation.[[🤗ContrlNet model card](https://huggingface.co/briaai/BRIA-2.3-ControlNet-BG-Gen)] [[🤗HF demo](https://huggingface.co/spaces/briaai/Product-Shot-Generation)].
### **ShotByImage**
This node is used to modify the background in an image by providing a reference image. This functionality is powered by BRIA's ControlNet Background-Generation and BRIA's Image-Prompt. [[🤗ContrlNet model card](https://huggingface.co/briaai/BRIA-2.3-ControlNet-Inpainting)] [[🤗IP-Adapter model card](https://huggingface.co/briaai/Image-Prompt)] [[🤗HF demo](https://huggingface.co/spaces/briaai/Product-Shot-Generation)].
# Installation
There are two methods to install the BRIA ComfyUI API nodes:
+57 -7
View File
@@ -1,6 +1,32 @@
from .nodes import (EraserNode, GenFillNode, ImageExpansionNode, ReplaceBgNode, RmbgNode, RemoveForegroundNode, ShotByTextNode, ShotByImageNode, TailoredGenNode,
TailoredModelInfoNode, Text2ImageBaseNode, Text2ImageFastNode, Text2ImageHDNode,
ReimagineNode)
from .nodes import (
EraserNode,
GenFillNode,
ImageExpansionNode,
ReplaceBgNode,
RmbgNode,
RemoveForegroundNode,
ShotByTextOriginalNode,
ShotByImageOriginalNode,
TailoredGenNode,
TailoredModelInfoNode,
Text2ImageBaseNode,
Text2ImageFastNode,
Text2ImageHDNode,
TailoredPortraitNode,
ReimagineNode,
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
@@ -9,14 +35,26 @@ NODE_CLASS_MAPPINGS = {
"ReplaceBgNode": ReplaceBgNode,
"RmbgNode": RmbgNode,
"RemoveForegroundNode": RemoveForegroundNode,
"ShotByTextNode": ShotByTextNode,
"ShotByImageNode": ShotByImageNode,
"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
}
# Map the node display name to the one shown in the ComfyUI node interface
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -26,12 +64,24 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"ReplaceBgNode": "Bria Replace Background",
"RmbgNode": "Bria RMBG",
"RemoveForegroundNode": "Bria Remove Foreground",
"ShotByTextNode": "Bria Shot By Text",
"ShotByImageNode": "Bria Shot By Image",
"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"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 634 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 714 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

+17 -3
View File
@@ -4,11 +4,25 @@ from .image_expansion_node import ImageExpansionNode
from .replace_bg_node import ReplaceBgNode
from .rmbg_node import RmbgNode
from .remove_foreground_node import RemoveForegroundNode
from .shot_by_text_node import ShotByTextNode
from .shot_by_image_node import ShotByImageNode
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 .reimagine_node import ReimagineNode
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
+66
View File
@@ -0,0 +1,66 @@
import requests
import torch
from .common import 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.")
# 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}")
+67 -12
View File
@@ -5,6 +5,7 @@ import torch
import base64
from torchvision.transforms import ToPILImage
import requests
import time
def postprocess_image(image):
result_image = Image.open(io.BytesIO(image))
@@ -44,7 +45,7 @@ def preprocess_mask(mask):
return mask
def process_request(api_url, image, mask, api_key):
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.")
@@ -58,10 +59,12 @@ def process_request(api_url, image, mask, api_key):
image_base64 = image_to_base64(image)
mask_base64 = image_to_base64(mask)
# Prepare the API request payload
# Prepare the API request payload for v2 API
payload = {
"file": f"{image_base64}",
"mask_file": f"{mask_base64}"
"image": image_base64,
"mask": mask_base64,
"visual_input_content_moderation":visual_input_content_moderation,
"visual_output_content_moderation":visual_output_content_moderation
}
headers = {
@@ -70,13 +73,23 @@ def process_request(api_url, image, mask, api_key):
}
try:
response = requests.post(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 = 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()
image_response = requests.get(response_dict['result_url'])
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
@@ -84,9 +97,51 @@ def process_request(api_url, image, mask, api_key):
# 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,)
return (result_image,)
else:
raise Exception(f"Error: API request failed with status code {response.status_code}")
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")
+7 -3
View File
@@ -8,6 +8,10 @@ class EraserNode():
"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}),
}
}
@@ -17,9 +21,9 @@ class EraserNode():
FUNCTION = "execute" # This is the method that will be executed
def __init__(self):
self.api_url = "https://engine.prod.bria-api.com/v1/eraser" # Eraser API URL
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):
return process_request(self.api_url, image, mask, api_key)
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)
+33 -12
View File
@@ -4,7 +4,7 @@ from PIL import Image
import io
import torch
from .common import image_to_base64, preprocess_image, preprocess_mask
from .common import preprocess_image, preprocess_mask, image_to_base64, poll_status_until_completed
class GenFillNode():
@@ -17,6 +17,14 @@ class GenFillNode():
"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",)
@@ -25,10 +33,10 @@ class GenFillNode():
FUNCTION = "execute" # This is the method that will be executed
def __init__(self):
self.api_url = "https://engine.prod.bria-api.com/v1/gen_fill" # Eraser API URL
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):
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.")
@@ -44,11 +52,14 @@ class GenFillNode():
# Prepare the API request payload
payload = {
"file": f"{image_base64}",
"mask_file": f"{mask_base64}",
"image": image_base64,
"mask": mask_base64,
"prompt": prompt,
"negative_prompt": "blurry",
"sync": True
"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 = {
@@ -57,20 +68,30 @@ class GenFillNode():
}
try:
# Send initial request to get status URL
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
if response.status_code == 200 or response.status_code == 202:
print('Initial genfill request successful, polling for completion...')
response_dict = response.json()
image_response = requests.get(response_dict['urls'][0])
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}")
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
except Exception as e:
raise Exception(f"{e}")
+61 -25
View File
@@ -4,7 +4,7 @@ from PIL import Image
import io
import torch
from .common import image_to_base64, preprocess_image
from .common import image_to_base64, preprocess_image, poll_status_until_completed
class ImageExpansionNode():
@@ -13,16 +13,21 @@ class ImageExpansionNode():
return {
"required": {
"image": ("IMAGE",), # Input image from another node
"original_image_size": ("STRING",),
"original_image_location": ("STRING",),
"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}),
}
}
@@ -32,26 +37,28 @@ class ImageExpansionNode():
FUNCTION = "execute" # This is the method that will be executed
def __init__(self):
self.api_url = "https://engine.prod.bria-api.com/v1/image_expansion" # Image Expansion API URL
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.")
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 ()
original_image_size = [int(x.strip()) for x in original_image_size.split(",")]
original_image_location = [int(x.strip()) for x in original_image_location.split(",")]
canvas_size = [int(x.strip()) for x in canvas_size.split(",")]
if prompt == "":
prompt = None
if negative_prompt == "":
negative_prompt = " " # hack to avoid error in triton which expects non-empty string
@@ -61,18 +68,32 @@ class ImageExpansionNode():
# Convert the image directly to Base64 string
image_base64 = image_to_base64(image)
# Prepare the API request payload
payload = {
"file": f"{image_base64}",
"original_image_size": original_image_size,
"original_image_location": original_image_location,
"canvas_size": canvas_size,
if aspect_ratio and aspect_ratio != "None":
payload = {
"image": image_base64,
"aspect_ratio": aspect_ratio,
"prompt": prompt,
"negative_prompt": negative_prompt,
"seed": seed
# "sync": True
"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",
@@ -81,19 +102,34 @@ class ImageExpansionNode():
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
if response.status_code == 200 or response.status_code == 202:
print('Initial image expansion request successful, polling for completion...')
response_dict = response.json()
image_response = requests.get(response_dict['result_url'])
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}")
raise Exception(f"Error: API request failed with status code {response.status_code}: {response.text}")
except Exception as e:
raise Exception(f"{e}")
+4 -2
View File
@@ -20,6 +20,7 @@ class ReimagineNode():
"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}),
}
}
@@ -35,8 +36,8 @@ class ReimagineNode():
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,
):
fast = bool(fast)
content_moderation=0,
):
payload = {
"prompt": tailored_generation_prefix + prompt,
"num_results": 1,
@@ -44,6 +45,7 @@ class ReimagineNode():
"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)
+33 -10
View File
@@ -4,7 +4,7 @@ from PIL import Image
import io
import torch
from .common import preprocess_image, image_to_base64
from .common import preprocess_image, image_to_base64, poll_status_until_completed
class RemoveForegroundNode():
@@ -15,6 +15,11 @@ class RemoveForegroundNode():
"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",)
@@ -23,10 +28,10 @@ class RemoveForegroundNode():
FUNCTION = "execute" # This is the method that will be executed
def __init__(self):
self.api_url = "https://engine.internal.prod.bria-api.com/v1/erase_foreground" # remove foreground API URL
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, api_key):
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.")
@@ -41,7 +46,12 @@ class RemoveForegroundNode():
# files=[('file',('temp_img.jpeg', open(temp_img_path, 'rb'),'image/jpeg'))
# ]
payload = {"file": image_to_base64(image)}
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",
@@ -50,18 +60,31 @@ class RemoveForegroundNode():
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
if response.status_code == 200 or response.status_code == 202:
print('Initial request successful, polling for completion...')
response_dict = response.json()
image_response = requests.get(response_dict['result_url'])
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}")
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
except Exception as e:
raise Exception(f"{e}")
+54 -33
View File
@@ -4,7 +4,7 @@ from PIL import Image
import io
import torch
from .common import image_to_base64, preprocess_image, preprocess_mask
from .common import image_to_base64, preprocess_image, preprocess_mask, poll_status_until_completed
class ReplaceBgNode():
@@ -16,15 +16,17 @@ class ReplaceBgNode():
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
},
"optional": {
"fast": ("BOOLEAN", {"default": True}),
"bg_prompt": ("STRING",),
"ref_image": ("IMAGE",), # Input ref image from another node
"mode": (["base", "fast", "high_control"], {"default": "base"}),
"prompt": ("STRING",),
"ref_images": ("IMAGE",),
"refine_prompt": ("BOOLEAN", {"default": True}),
"enhance_ref_image": ("BOOLEAN", {"default": True}),
"enhance_ref_images": ("BOOLEAN", {"default": True}),
"original_quality": ("BOOLEAN", {"default": False}),
"force_rmbg": ("BOOLEAN", {"default": False}),
"negative_prompt": ("STRING", {"default": None}),
"seed": ("INT", {"default": 681794})
"seed": ("INT", {"default": 681794}),
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
"prompt_content_moderation": ("BOOLEAN", {"default": False}),
"force_background_detection": ("BOOLEAN", {"default": False}),
}
}
@@ -34,19 +36,21 @@ class ReplaceBgNode():
FUNCTION = "execute" # This is the method that will be executed
def __init__(self):
self.api_url = "https://engine.prod.bria-api.com/v1/background/replace" # Replace BG API URL
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, fast,
def execute(self, image, mode,
refine_prompt,
enhance_ref_image,
original_quality,
force_rmbg,
negative_prompt,
seed,
api_key,
bg_prompt=None,
ref_image=None,):
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.")
@@ -54,27 +58,29 @@ class ReplaceBgNode():
if isinstance(image, torch.Tensor):
image = preprocess_image(image)
# Convert the image and mask directly to Base64 strings
# Convert the image to Base64 string
image_base64 = image_to_base64(image)
ref_image_file = None # initialization, will be updated if it is supplied
if ref_image is not None:
ref_image = preprocess_image(ref_image)
ref_image_file = image_to_base64(ref_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
# Prepare the API request payload for v2 API
payload = {
"file": f"{image_base64}",
"fast": fast,
"bg_prompt": bg_prompt,
"ref_image_file": ref_image_file,
"image": image_base64,
"mode": mode,
"prompt": prompt,
"ref_images":ref_images,
"refine_prompt": refine_prompt,
"enhance_ref_image": enhance_ref_image,
"original_quality": original_quality,
"force_rmbg": force_rmbg,
"negative_prompt": negative_prompt,
"seed": seed,
"sync": True,
"num_results": 1
"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 = {
@@ -84,19 +90,34 @@ class ReplaceBgNode():
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
if response.status_code == 200 or response.status_code == 202:
print('Initial replace background request successful, polling for completion...')
response_dict = response.json()
image_response = requests.get(response_dict['result'][0][0]) # first indexing for batched, second for url
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}")
raise Exception(f"Error: API request failed with status code {response.status_code}{response.text}")
except Exception as e:
raise Exception(f"{e}")
+44 -17
View File
@@ -4,8 +4,7 @@ from PIL import Image
import io
import torch
from .common import preprocess_image
from .common import preprocess_image, image_to_base64, poll_status_until_completed
class RmbgNode():
@classmethod
@@ -15,6 +14,12 @@ class RmbgNode():
"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",)
@@ -23,10 +28,10 @@ class RmbgNode():
FUNCTION = "execute" # This is the method that will be executed
def __init__(self):
self.api_url = "https://engine.prod.bria-api.com/v1/background/remove" # RMBG API URL
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, api_key):
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.")
@@ -34,27 +39,49 @@ class RmbgNode():
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'))
]
# 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, data={}, headers={"api_token": api_key}, files=files)
# Check for successful response
if response.status_code == 200:
print('response is 200')
# Process the output image from API response
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()
image_response = requests.get(response_dict['result_url'])
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}")
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)
+51
View File
@@ -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)
+41 -68
View File
@@ -1,68 +1,41 @@
import requests
import torch
from .common import postprocess_image, preprocess_image, image_to_base64
class ShotByImageNode():
@classmethod
def INPUT_TYPES(self):
return {
"required": {
"image": ("IMAGE",), # Input image from another node
"ref_image": ("IMAGE",), # ref image from another node
"enhance_ref_image": ("INT", {"default": 1}),
"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):
self.api_url = "https://engine.prod.bria-api.com/v1/product/lifestyle_shot_by_image" # Eraser API URL
# Define the execute method as expected by ComfyUI
def execute(self, image, ref_image, api_key, enhance_ref_image, ):
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 = preprocess_image(image)
if isinstance(ref_image, torch.Tensor):
ref_image = preprocess_image(ref_image)
# Convert the image and mask directly to Base64 strings
image_base64 = image_to_base64(image)
ref_image_base64 = image_to_base64(ref_image)
enhance_ref_image = bool(enhance_ref_image)
payload = {
"file": image_base64,
"ref_image_file": ref_image_base64,
"enhance_ref_image": enhance_ref_image,
"placement_type": "original",
"original_quality": True,
"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['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}")
except Exception as e:
raise Exception(f"{e}")
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)
+53
View File
@@ -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)
+45
View File
@@ -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)
+42 -64
View File
@@ -1,64 +1,42 @@
import requests
import torch
from .common import postprocess_image, preprocess_image, image_to_base64
class ShotByTextNode():
@classmethod
def INPUT_TYPES(self):
return {
"required": {
"image": ("IMAGE",), # Input image from another node
"scene_description": ("STRING",),
"optimize_description": ("INT", {"default": 1}),
"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):
self.api_url = "https://engine.prod.bria-api.com/v1/product/lifestyle_shot_by_text" # Eraser API URL
# Define the execute method as expected by ComfyUI
def execute(self, image, api_key, scene_description, optimize_description, ):
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 = preprocess_image(image)
optimize_description = bool(optimize_description)
image_base64 = image_to_base64(image)
payload = {
"file": image_base64,
"scene_description": scene_description,
"optimize_description": optimize_description,
"placement_type": "original",
"original_quality": True,
"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['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}")
except Exception as e:
raise Exception(f"{e}")
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)
+3 -1
View File
@@ -26,6 +26,7 @@ class TailoredGenNode():
"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}),
}
}
@@ -42,8 +43,8 @@ class TailoredGenNode():
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,
):
fast = bool(fast)
payload = {
"prompt": generation_prefix + prompt,
"num_results": 1,
@@ -55,6 +56,7 @@ class TailoredGenNode():
"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)
+75
View File
@@ -0,0 +1,75 @@
import numpy as np
import requests
from PIL import Image
import io
import torch
from .common import image_to_base64, preprocess_image
class TailoredPortraitNode():
@classmethod
def INPUT_TYPES(self):
return {
"required": {
"image": ("IMAGE",), # Input image from another node
"tailored_model_id": ("INT",),
"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.")
# 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": 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}")
except Exception as e:
raise Exception(f"{e}")
+4 -2
View File
@@ -28,6 +28,7 @@ class Text2ImageBaseNode():
"image_prompt_mode": (["regular", "style_only"], {"default": "regular"}),
"image_prompt_image": ("IMAGE", ),
"image_prompt_scale": ("FLOAT", {"default": 1.0}),
"content_moderation": ("INT", {"default": 0}),
}
}
@@ -37,7 +38,7 @@ class Text2ImageBaseNode():
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/2.3" #"http://0.0.0.0:5000/v1/text-to-image/base/2.3"
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,
@@ -45,8 +46,8 @@ class Text2ImageBaseNode():
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,
):
prompt_enhancement = bool(prompt_enhancement)
payload = {
"prompt": prompt,
"num_results": 1,
@@ -57,6 +58,7 @@ class Text2ImageBaseNode():
"steps_num": steps_num,
"text_guidance_scale": text_guidance_scale,
"prompt_enhancement": prompt_enhancement,
"content_moderation": content_moderation,
}
if medium != "none":
payload["medium"] = medium
+3 -1
View File
@@ -25,6 +25,7 @@ class Text2ImageFastNode():
"image_prompt_mode": (["regular", "style_only"], {"default": "regular"}),
"image_prompt_image": ("IMAGE", ),
"image_prompt_scale": ("FLOAT", {"default": 1.0}),
"content_moderation": ("INT", {"default": 0}),
}
}
@@ -42,8 +43,8 @@ class Text2ImageFastNode():
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,
):
prompt_enhancement = bool(prompt_enhancement)
payload = {
"prompt": prompt,
"num_results": 1,
@@ -52,6 +53,7 @@ class Text2ImageFastNode():
"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)
+3 -2
View File
@@ -19,6 +19,7 @@ class Text2ImageHDNode():
"prompt_enhancement": ("INT", {"default": 0}),
"text_guidance_scale": ("INT", {"default": 5}),
"medium": (["photography", "art", "none"], {"default": "none"}),
"content_moderation": ("INT", {"default": 0}),
}
}
@@ -32,9 +33,8 @@ class Text2ImageHDNode():
def execute(
self, api_key, prompt, aspect_ratio, seed, negative_prompt,
steps_num, prompt_enhancement, text_guidance_scale, medium,
steps_num, prompt_enhancement, text_guidance_scale, medium, content_moderation=0,
):
prompt_enhancement = bool(prompt_enhancement)
payload = {
"prompt": prompt,
"num_results": 1,
@@ -45,6 +45,7 @@ class Text2ImageHDNode():
"steps_num": steps_num,
"text_guidance_scale": text_guidance_scale,
"prompt_enhancement": prompt_enhancement,
"content_moderation": content_moderation,
}
if medium != "none":
payload["medium"] = medium
+204
View File
@@ -0,0 +1,204 @@
import requests
import torch
from ..common import 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"""
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:
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
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-bria-api"
description = "Custom nodes for ComfyUI using BRIA's API."
version = "2.0.1"
version = "2.1.3"
license = {file = "LICENSE"}
[project.urls]
@@ -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}
+1
View File
@@ -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}
-203
View File
@@ -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
}
-204
View File
@@ -1,204 +0,0 @@
{
"last_node_id": 35,
"last_link_id": 55,
"nodes": [
{
"id": 33,
"type": "PreviewImage",
"pos": {
"0": 1420,
"1": 574
},
"size": {
"0": 433.29193115234375,
"1": 357.1255187988281
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 54
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 30,
"type": "LoadImage",
"pos": {
"0": 479,
"1": 572
},
"size": {
"0": 395.7845153808594,
"1": 352.8512268066406
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
53
],
"slot_index": 0,
"shape": 3
},
{
"name": "MASK",
"type": "MASK",
"links": [
55
],
"slot_index": 1,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"clipspace/clipspace-mask-1438488.400000006.png [input]",
"image"
]
},
{
"id": 14,
"type": "Note",
"pos": {
"0": 478,
"1": 444
},
"size": {
"0": 396.80859375,
"1": 61.8046875
},
"flags": {},
"order": 1,
"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": {
"0": 983,
"1": 440
},
"size": {
"0": 306.28387451171875,
"1": 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": 34,
"type": "BriaGenFill",
"pos": {
"0": 992,
"1": 572
},
"size": {
"0": 315,
"1": 102
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 53
},
{
"name": "mask",
"type": "MASK",
"link": 55
}
],
"outputs": [
{
"name": "output_image",
"type": "IMAGE",
"links": [
54
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "BriaGenFill"
},
"widgets_values": [
"a beautiful paint brush",
"BRIA_API_TOKEN"
]
}
],
"links": [
[
53,
30,
0,
34,
0,
"IMAGE"
],
[
54,
34,
0,
33,
0,
"IMAGE"
],
[
55,
30,
1,
34,
1,
"MASK"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.8264462809917364,
"offset": [
-266.96526103236687,
114.47857424738714
]
}
},
"version": 0.4
}
-203
View File
@@ -1,203 +0,0 @@
{
"last_node_id": 35,
"last_link_id": 55,
"nodes": [
{
"id": 33,
"type": "PreviewImage",
"pos": {
"0": 1420,
"1": 574
},
"size": {
"0": 433.29193115234375,
"1": 357.1255187988281
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 54
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 30,
"type": "LoadImage",
"pos": {
"0": 479,
"1": 572
},
"size": {
"0": 395.7845153808594,
"1": 352.8512268066406
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
53
],
"slot_index": 0,
"shape": 3
},
{
"name": "MASK",
"type": "MASK",
"links": [
55
],
"slot_index": 1,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"clipspace/clipspace-mask-1438488.400000006.png [input]",
"image"
]
},
{
"id": 14,
"type": "Note",
"pos": {
"0": 478,
"1": 444
},
"size": {
"0": 396.80859375,
"1": 61.8046875
},
"flags": {},
"order": 1,
"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": {
"0": 983,
"1": 440
},
"size": {
"0": 306.28387451171875,
"1": 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": 34,
"type": "ImageExpansionNode",
"pos": {
"0": 992,
"1": 572
},
"size": {
"0": 315,
"1": 102
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 53
},
{
"name": "mask",
"type": "MASK",
"link": 55
}
],
"outputs": [
{
"name": "output_image",
"type": "IMAGE",
"links": [
54
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ImageExpansionNode"
},
"widgets_values": [
"BRIA_API_TOKEN"
]
}
],
"links": [
[
53,
30,
0,
34,
0,
"IMAGE"
],
[
54,
34,
0,
33,
0,
"IMAGE"
],
[
55,
30,
1,
34,
1,
"MASK"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.8264462809917364,
"offset": [
-266.96526103236687,
114.47857424738714
]
}
},
"version": 0.4
}
@@ -1,18 +1,47 @@
{
"last_node_id": 42,
"last_link_id": 65,
"id": "1cdd7d4c-58b5-4047-947b-1977ad36d364",
"revision": 0,
"last_node_id": 14,
"last_link_id": 11,
"nodes": [
{
"id": 42,
"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": {
"0": 591,
"1": 593
},
"size": {
"0": 315,
"1": 314
},
"pos": [
383.92852783203125,
38.40964889526367
],
"size": [
397.5969543457031,
314
],
"flags": {},
"order": 0,
"mode": 0,
@@ -22,10 +51,9 @@
"name": "IMAGE",
"type": "IMAGE",
"links": [
64,
65
],
"slot_index": 0
1,
3
]
},
{
"name": "MASK",
@@ -37,21 +65,21 @@
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"A_bottle_of_perfume.png",
"CAR.png",
"image"
]
},
{
"id": 39,
"id": 2,
"type": "LoadImage",
"pos": {
"0": 600,
"1": 988
},
"size": {
"0": 315,
"1": 314
},
"pos": [
369.0213623046875,
415.34893798828125
],
"size": [
450.83685302734375,
314.0000305175781
],
"flags": {},
"order": 1,
"mode": 0,
@@ -61,9 +89,8 @@
"name": "IMAGE",
"type": "IMAGE",
"links": [
59
],
"slot_index": 0
4
]
},
{
"name": "MASK",
@@ -75,21 +102,21 @@
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"A_red_studio_with_a_shelf__close_up.png",
"seed_713360865.png",
"image"
]
},
{
"id": 15,
"id": 5,
"type": "Note",
"pos": {
"0": 995.4524536132812,
"1": 601.5353393554688
},
"size": {
"0": 306.28387451171875,
"1": 58
},
"pos": [
951.4669189453125,
5.085720062255859
],
"size": [
210,
88
],
"flags": {},
"order": 2,
"mode": 0,
@@ -103,40 +130,14 @@
"bgcolor": "#653"
},
{
"id": 40,
"id": 7,
"type": "PreviewImage",
"pos": {
"0": 1408,
"1": 623
},
"size": [
210,
246
"pos": [
1368.5450439453125,
340.4889221191406
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 62
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 41,
"type": "PreviewImage",
"pos": {
"0": 1408,
"1": 951
},
"size": [
210,
387.0335693359375,
246
],
"flags": {},
@@ -146,25 +147,26 @@
{
"name": "images",
"type": "IMAGE",
"link": 63
"link": 6
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
"widgets_values": []
},
{
"id": 36,
"type": "ShotByTextNode",
"pos": {
"0": 996,
"1": 736
},
"size": {
"0": 315,
"1": 106
},
"id": 3,
"type": "ShotByTextOriginal",
"pos": [
941.8839111328125,
149.14889526367188
],
"size": [
273.388671875,
202
],
"flags": {},
"order": 3,
"mode": 0,
@@ -172,7 +174,7 @@
{
"name": "image",
"type": "IMAGE",
"link": 64
"link": 1
}
],
"outputs": [
@@ -180,31 +182,34 @@
"name": "output_image",
"type": "IMAGE",
"links": [
62
],
"slot_index": 0
5
]
}
],
"properties": {
"Node name for S&R": "ShotByTextNode"
"Node name for S&R": "ShotByTextOriginal"
},
"widgets_values": [
"a beautiful sunset",
1,
"BRIA_API_TOKEN",
"sea",
"fast",
false,
false,
true,
""
]
},
{
"id": 37,
"type": "ShotByImageNode",
"pos": {
"0": 999,
"1": 932
},
"size": {
"0": 315,
"1": 102
},
"id": 4,
"type": "ShotByImageOriginal",
"pos": [
945.0781860351562,
441.9688415527344
],
"size": [
272.0703125,
174
],
"flags": {},
"order": 4,
"mode": 0,
@@ -212,12 +217,12 @@
{
"name": "image",
"type": "IMAGE",
"link": 65
"link": 3
},
{
"name": "ref_image",
"type": "IMAGE",
"link": 59
"link": 4
}
],
"outputs": [
@@ -225,58 +230,60 @@
"name": "output_image",
"type": "IMAGE",
"links": [
63
],
"slot_index": 0
6
]
}
],
"properties": {
"Node name for S&R": "ShotByImageNode"
"Node name for S&R": "ShotByImageOriginal"
},
"widgets_values": [
0,
""
"BRIA_API_TOKEN",
false,
false,
true,
1
]
}
],
"links": [
[
59,
39,
1,
1,
0,
37,
3,
0,
"IMAGE"
],
[
3,
1,
0,
4,
0,
"IMAGE"
],
[
4,
2,
0,
4,
1,
"IMAGE"
],
[
62,
36,
5,
3,
0,
40,
6,
0,
"IMAGE"
],
[
63,
37,
6,
4,
0,
41,
0,
"IMAGE"
],
[
64,
42,
0,
36,
0,
"IMAGE"
],
[
65,
42,
0,
37,
7,
0,
"IMAGE"
]
@@ -285,12 +292,13 @@
"config": {},
"extra": {
"ds": {
"scale": 0.9849732675807669,
"scale": 0.7513148009015777,
"offset": [
-339.6686422794803,
-496.4354678014682
11.112206386364164,
66.47311795454547
]
}
},
"frontendVersion": "1.25.11"
},
"version": 0.4
}
-179
View File
@@ -1,179 +0,0 @@
{
"last_node_id": 36,
"last_link_id": 58,
"nodes": [
{
"id": 15,
"type": "Note",
"pos": [
983,
440
],
"size": [
306.28387451171875,
58
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"You can get your BRIA API token at:\nhttps://bria.ai/api/"
],
"color": "#432",
"bgcolor": "#653"
},
{
"id": 33,
"type": "PreviewImage",
"pos": [
1420,
574
],
"size": [
433.29193115234375,
357.1255187988281
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 58
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 30,
"type": "LoadImage",
"pos": [
479,
572
],
"size": [
395.7845153808594,
352.8512268066406
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
57
],
"slot_index": 0,
"shape": 3
},
{
"name": "MASK",
"type": "MASK",
"links": [
55
],
"slot_index": 1,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"9f80551c-dd4f-11ef-a692-e2f8583797ff (1).png",
"image"
]
},
{
"id": 36,
"type": "RemoveForegroundNode",
"pos": [
985.8506469726562,
572.9254150390625
],
"size": [
315,
58
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 57
}
],
"outputs": [
{
"name": "output_image",
"type": "IMAGE",
"links": [
58
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "RemoveForegroundNode"
},
"widgets_values": [
"BRIA_API_TOKEN"
]
}
],
"links": [
[
55,
30,
1,
34,
1,
"MASK"
],
[
57,
30,
0,
36,
0,
"IMAGE"
],
[
58,
36,
0,
33,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.4594972986357223,
"offset": [
910.1666624806405,
289.9288421609929
]
},
"node_versions": {
"comfy-core": "0.3.12",
"ComfyUI-BRIA-API": ""
}
},
"version": 0.4
}
-203
View File
@@ -1,203 +0,0 @@
{
"last_node_id": 35,
"last_link_id": 55,
"nodes": [
{
"id": 33,
"type": "PreviewImage",
"pos": {
"0": 1420,
"1": 574
},
"size": {
"0": 433.29193115234375,
"1": 357.1255187988281
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 54
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 30,
"type": "LoadImage",
"pos": {
"0": 479,
"1": 572
},
"size": {
"0": 395.7845153808594,
"1": 352.8512268066406
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
53
],
"slot_index": 0,
"shape": 3
},
{
"name": "MASK",
"type": "MASK",
"links": [
55
],
"slot_index": 1,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"clipspace/clipspace-mask-1438488.400000006.png [input]",
"image"
]
},
{
"id": 14,
"type": "Note",
"pos": {
"0": 478,
"1": 444
},
"size": {
"0": 396.80859375,
"1": 61.8046875
},
"flags": {},
"order": 1,
"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": {
"0": 983,
"1": 440
},
"size": {
"0": 306.28387451171875,
"1": 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": 34,
"type": "ReplaceBgNode",
"pos": {
"0": 992,
"1": 572
},
"size": {
"0": 315,
"1": 102
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 53
},
{
"name": "mask",
"type": "MASK",
"link": 55
}
],
"outputs": [
{
"name": "output_image",
"type": "IMAGE",
"links": [
54
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ReplaceBgNode"
},
"widgets_values": [
"BRIA_API_TOKEN"
]
}
],
"links": [
[
53,
30,
0,
34,
0,
"IMAGE"
],
[
54,
34,
0,
33,
0,
"IMAGE"
],
[
55,
30,
1,
34,
1,
"MASK"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.8264462809917364,
"offset": [
-266.96526103236687,
114.47857424738714
]
}
},
"version": 0.4
}
@@ -1,119 +1,103 @@
{
"last_node_id": 36,
"last_link_id": 58,
"last_node_id": 13,
"last_link_id": 11,
"nodes": [
{
"id": 15,
"type": "Note",
"id": 12,
"type": "LoadImage",
"pos": [
983,
440
669.7035522460938,
136.97129821777344
],
"size": [
306.28387451171875,
58
315,
314
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"You can get your BRIA API token at:\nhttps://bria.ai/api/"
],
"color": "#432",
"bgcolor": "#653"
},
{
"id": 33,
"type": "PreviewImage",
"pos": [
1420,
574
],
"size": [
433.29193115234375,
357.1255187988281
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 58
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 30,
"type": "LoadImage",
"pos": [
479,
572
],
"size": [
395.7845153808594,
352.8512268066406
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
57
10
],
"slot_index": 0,
"shape": 3
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": [
55
],
"slot_index": 1,
"shape": 3
"links": null
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"9f80551c-dd4f-11ef-a692-e2f8583797ff (1).png",
"pexels-photo-3246665.png",
"image"
]
},
{
"id": 36,
"type": "RmbgNode",
"id": 11,
"type": "PreviewImage",
"pos": [
985.8506469726562,
572.9254150390625
1587.27001953125,
97.39167022705078
],
"size": [
315,
58
210,
246
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "image",
"name": "images",
"type": "IMAGE",
"link": 57
"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": [
@@ -121,41 +105,45 @@
"name": "output_image",
"type": "IMAGE",
"links": [
58
11
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "RmbgNode"
"Node name for S&R": "Text2ImageFastNode"
},
"widgets_values": [
"BRIA_API_TOKEN"
"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": [
[
55,
30,
1,
34,
1,
"MASK"
],
[
57,
30,
0,
36,
10,
12,
0,
10,
2,
"IMAGE"
],
[
58,
36,
11,
10,
0,
33,
11,
0,
"IMAGE"
]
@@ -164,16 +152,18 @@
"config": {},
"extra": {
"ds": {
"scale": 0.4594972986357223,
"scale": 0.7513148009015777,
"offset": [
910.1666624806405,
289.9288421609929
11.112206386364164,
66.47311795454547
]
},
"node_versions": {
"comfy-core": "0.3.12",
"ComfyUI-BRIA-API": ""
}
"comfy-core": "v0.3.10-42-gff83865",
"comfyui-bria-api": "499ec5d104cc5110407eafce468ce1d47ac168b3"
},
"VHS_latentpreview": false,
"VHS_latentpreviewrate": 0
},
"version": 0.4
}