Compare commits
45
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
339f26b64f | ||
|
|
221f31068c | ||
|
|
d394eda558 | ||
|
|
54cadf8578 | ||
|
|
95e5c74266 | ||
|
|
e61b45a79a | ||
|
|
e8ad98c0a6 | ||
|
|
3e9599c0fb | ||
|
|
a52486bbb1 | ||
|
|
aa768b6cdf | ||
|
|
fda3d917b2 | ||
|
|
a970f19bf2 | ||
|
|
2c97deb804 | ||
|
|
e9b797ae90 | ||
|
|
2c6f9754d3 | ||
|
|
7dd5d3a250 | ||
|
|
ddd1252ecb | ||
|
|
812f7f88fa | ||
|
|
80332ca13c | ||
|
|
2b636d8a57 | ||
|
|
855ed814ed | ||
|
|
8f7edd926d | ||
|
|
c2cf2f044f | ||
|
|
5ccf16a4d3 | ||
|
|
2cab356750 | ||
|
|
c9f880bdd8 | ||
|
|
82f31ddf72 | ||
|
|
00de95a160 | ||
|
|
15b69d2a03 | ||
|
|
1cfe5758f1 | ||
|
|
51e75b8f9e | ||
|
|
98322c3c96 | ||
|
|
862403893f | ||
|
|
0e0e59bbd4 | ||
|
|
a611bddb9f | ||
|
|
6a94e8b91b | ||
|
|
725868a7eb | ||
|
|
0a81405f6a | ||
|
|
ff8489b8d3 | ||
|
|
05406c1cef | ||
|
|
dffed52b27 | ||
|
|
3d7c41a15a | ||
|
|
a5a88e2dbd | ||
|
|
64ab007f27 | ||
|
|
3ac0209193 |
@@ -31,28 +31,53 @@ To load a workflow, import the compatible workflow.json files from this [folder]
|
|||||||
|
|
||||||
## Image Generation Nodes
|
## Image Generation Nodes
|
||||||
|
|
||||||
These nodes allow you to leverage Bria's image generation capabilities within ComfyUI. We offer our latest **V2 nodes** designed for precise control via structured prompts (currently powered by the **FIBO** model), alongside our **V1 nodes** for various established pipelines.
|
These nodes allow you to leverage Bria's image generation capabilities within ComfyUI. We offer our latest **V2 nodes** (powered by the **FIBO** model) for precise control via structured prompts, alongside our legacy **V1 nodes**.
|
||||||
|
|
||||||
### V2 Generation Nodes
|
### V2 Generation Nodes (FIBO)
|
||||||
|
|
||||||
These nodes generate images based on detailed **structured prompts** for enhanced control and consistency. They are currently powered by the state-of-the-art **FIBO** text-to-image model.
|
Our V2 nodes utilize a state-of-the-art **two-step process** for enhanced control and consistency:
|
||||||
|
|
||||||
| **Node** | **Description** |
|
- **Translation**: A VLM Bridge translates your input (prompt/images) into a machine-readable `structured_prompt` (JSON).
|
||||||
| --- | --- |
|
- **Generation**: The FIBO model generates the final image based on that specific JSON.
|
||||||
| **Generate Image** | Creates new images from text or image inputs. Internally translates the input into a structured prompt using a selected VLM bridge before generating with the image model. |
|
|
||||||
| **Refine and Regenerate Image** | Refines a generated image using a provided `structured_prompt` (from a previous generation) and a refinement text prompt. |
|
|
||||||
|
|
||||||
### V1 Generation Nodes
|
**Available Versions:**
|
||||||
|
|
||||||
|
- **Regular**: Uses **Gemini 2.5 Flash** as the bridge for state-of-the-art, detailed prompt creation.
|
||||||
|
- **Lite**: Uses **FIBO-VLM** (Bria's open-source bridge) for faster, flexible, or on-prem deployment.
|
||||||
|
|
||||||
|
**Available V2 Nodes & Input Rules**
|
||||||
|
|
||||||
|
We offer three distinct nodes to give you full control over this pipeline:
|
||||||
|
|
||||||
|
1. **Structured Prompt Bridge**
|
||||||
|
- Outputs a JSON string only (no image).
|
||||||
|
- This node decouples the "intent translation" step from generation. It is ideal for "human-in-the-loop" workflows where you want to inspect, audit, or version-control the JSON instructions before generating.
|
||||||
|
- **Supported Input Combinations:**
|
||||||
|
- `prompt`: Generates a structured prompt from text.
|
||||||
|
- `images`: Generates a structured prompt based on an input image.
|
||||||
|
- `images + prompt`: Generates a structured prompt based on an image, guided by text.
|
||||||
|
- `structured_prompt + prompt`: Updates an existing structured prompt using new text instructions (outputs updated JSON).
|
||||||
|
|
||||||
|
2. **Generate Image**
|
||||||
|
- Outputs an Image.
|
||||||
|
- The primary node for generation. It automatically handles translation and generation in one go, or accepts a pre-made structured prompt for reproducible results.
|
||||||
|
- **Supported Input Combinations:**
|
||||||
|
- `prompt`: Generates a new image from text.
|
||||||
|
- `images`: Generates a new image inspired by a reference image.
|
||||||
|
- `images + prompt`: Generates a new image inspired by an image and guided by text.
|
||||||
|
- `structured_prompt`: Recreates a previous image exactly (when combined with a seed).
|
||||||
|
|
||||||
|
3. **Refine and Regenerate**
|
||||||
|
- Outputs a Refined Image.
|
||||||
|
- This node allows you to take a result you like and tweak it without losing the original composition.
|
||||||
|
- **Supported Input Combination:**
|
||||||
|
- `structured_prompt + prompt`: Refines a previous image using new text instructions (combined with a seed) to adjust details while maintaining consistency.
|
||||||
|
|
||||||
|
### V1 Generation Nodes (Legacy)
|
||||||
|
|
||||||
|
These nodes utilize Bria's previous generation pipeline. While V2 is recommended for the highest control and quality, V1 remains available for backward compatibility with established workflows.
|
||||||
These nodes create high-quality images using Bria's V1 pipelines, supporting various aspect ratios and styles.
|
These nodes create high-quality images using Bria's V1 pipelines, supporting various aspect ratios and styles.
|
||||||
|
|
||||||
| Node | Description |
|
|
||||||
|------------------------|--------------------------------------------------------------------|
|
|
||||||
| **Text2Image Base** | Generates images from text prompts, serving as the foundation for text-based image creation. |
|
|
||||||
| **Text2Image Fast** | Optimized for speed, this node generates images from text prompts with faster results while maintaining quality. |
|
|
||||||
| **Text2Image HD** | Optimized for high-resolution outputs, this node generates detailed and sharp visuals from text prompts. |
|
|
||||||
| **Reimagine** | Guides image generation using both prompts and an input image. Preserve the original structure and depth while introducing new materials, colors, and textures. |
|
|
||||||
|
|
||||||
## Tailored Generation Nodes
|
## Tailored Generation Nodes
|
||||||
These nodes use pre-trained tailored models to generate images that faithfully reproduce specific visual IP elements or guidelines.
|
These nodes use pre-trained tailored models to generate images that faithfully reproduce specific visual IP elements or guidelines.
|
||||||
|
|
||||||
|
|||||||
+58
-3
@@ -2,6 +2,7 @@ from .nodes import (
|
|||||||
EraserNode,
|
EraserNode,
|
||||||
GenFillNode,
|
GenFillNode,
|
||||||
ImageExpansionNode,
|
ImageExpansionNode,
|
||||||
|
ImageEnhanceNode,
|
||||||
ReplaceBgNode,
|
ReplaceBgNode,
|
||||||
RmbgNode,
|
RmbgNode,
|
||||||
RemoveForegroundNode,
|
RemoveForegroundNode,
|
||||||
@@ -15,7 +16,11 @@ from .nodes import (
|
|||||||
TailoredPortraitNode,
|
TailoredPortraitNode,
|
||||||
ReimagineNode,
|
ReimagineNode,
|
||||||
GenerateImageNodeV2,
|
GenerateImageNodeV2,
|
||||||
|
GenerateImageLiteNodeV2,
|
||||||
RefineImageNodeV2,
|
RefineImageNodeV2,
|
||||||
|
RefineImageLiteNodeV2,
|
||||||
|
GenerateStructuredPromptNodeV2,
|
||||||
|
GenerateStructuredPromptLiteNodeV2,
|
||||||
ShotByTextAutomaticNode,
|
ShotByTextAutomaticNode,
|
||||||
ShotByImageManualPaddingNode,
|
ShotByImageManualPaddingNode,
|
||||||
ShotByImageAutomaticAspectRatioNode,
|
ShotByImageAutomaticAspectRatioNode,
|
||||||
@@ -26,7 +31,19 @@ from .nodes import (
|
|||||||
ShotByTextManualPlacementNode,
|
ShotByTextManualPlacementNode,
|
||||||
ShotByTextManualPaddingNode,
|
ShotByTextManualPaddingNode,
|
||||||
ShotByTextCustomCoordinatesNode,
|
ShotByTextCustomCoordinatesNode,
|
||||||
AttributionByImageNode
|
AttributionByImageNode,
|
||||||
|
RemoveVideoBackgroundNode,
|
||||||
|
VideoSolidColorBackgroundNode,
|
||||||
|
VideoMaskByPromptNode,
|
||||||
|
VideoMaskByKeyPointsNode,
|
||||||
|
VideoIncreaseResolutionNode,
|
||||||
|
VideoEraseElementsNode,
|
||||||
|
LoadVideoFramesNode,
|
||||||
|
PreviewVideoURLNode,
|
||||||
|
FIBOEditNode,
|
||||||
|
FIBOEditStructuredInstructionNode,
|
||||||
|
BriaMultiImageSelect,
|
||||||
|
ProductIntegrateNode
|
||||||
)
|
)
|
||||||
|
|
||||||
# Map the node class to a name used internally by ComfyUI
|
# Map the node class to a name used internally by ComfyUI
|
||||||
@@ -34,6 +51,7 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
"BriaEraser": EraserNode, # Return the class, not an instance
|
"BriaEraser": EraserNode, # Return the class, not an instance
|
||||||
"BriaGenFill": GenFillNode,
|
"BriaGenFill": GenFillNode,
|
||||||
"ImageExpansionNode": ImageExpansionNode,
|
"ImageExpansionNode": ImageExpansionNode,
|
||||||
|
"ImageEnhanceNode": ImageEnhanceNode,
|
||||||
"ReplaceBgNode": ReplaceBgNode,
|
"ReplaceBgNode": ReplaceBgNode,
|
||||||
"RmbgNode": RmbgNode,
|
"RmbgNode": RmbgNode,
|
||||||
"RemoveForegroundNode": RemoveForegroundNode,
|
"RemoveForegroundNode": RemoveForegroundNode,
|
||||||
@@ -58,13 +76,31 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
"ReimagineNode": ReimagineNode,
|
"ReimagineNode": ReimagineNode,
|
||||||
"AttributionByImageNode": AttributionByImageNode,
|
"AttributionByImageNode": AttributionByImageNode,
|
||||||
"GenerateImageNodeV2": GenerateImageNodeV2,
|
"GenerateImageNodeV2": GenerateImageNodeV2,
|
||||||
|
"GenerateImageLiteNodeV2": GenerateImageLiteNodeV2,
|
||||||
"RefineImageNodeV2": RefineImageNodeV2,
|
"RefineImageNodeV2": RefineImageNodeV2,
|
||||||
|
"RefineImageLiteNodeV2": RefineImageLiteNodeV2,
|
||||||
|
"GenerateStructuredPromptNodeV2": GenerateStructuredPromptNodeV2,
|
||||||
|
"GenerateStructuredPromptLiteNodeV2": GenerateStructuredPromptLiteNodeV2,
|
||||||
|
"RemoveVideoBackgroundNode":RemoveVideoBackgroundNode,
|
||||||
|
"VideoSolidColorBackgroundNode":VideoSolidColorBackgroundNode,
|
||||||
|
"VideoMaskByPromptNode":VideoMaskByPromptNode,
|
||||||
|
"VideoMaskByKeyPointsNode":VideoMaskByKeyPointsNode,
|
||||||
|
"VideoIncreaseResolutionNode":VideoIncreaseResolutionNode,
|
||||||
|
"VideoEraseElementsNode":VideoEraseElementsNode,
|
||||||
|
"LoadVideoFramesNode":LoadVideoFramesNode,
|
||||||
|
"PreviewVideoURLNode":PreviewVideoURLNode,
|
||||||
|
"FIBOEditNode": FIBOEditNode,
|
||||||
|
"FIBOEditStructuredInstructionNode": FIBOEditStructuredInstructionNode,
|
||||||
|
"BriaMultiImageSelect":BriaMultiImageSelect,
|
||||||
|
"ProductIntegrateNode": ProductIntegrateNode
|
||||||
|
|
||||||
}
|
}
|
||||||
# Map the node display name to the one shown in the ComfyUI node interface
|
# Map the node display name to the one shown in the ComfyUI node interface
|
||||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||||
"BriaEraser": "Bria Eraser",
|
"BriaEraser": "Bria Eraser",
|
||||||
"BriaGenFill": "Bria GenFill",
|
"BriaGenFill": "Bria GenFill",
|
||||||
"ImageExpansionNode": "Bria Image Expansion",
|
"ImageExpansionNode": "Bria Image Expansion",
|
||||||
|
"ImageEnhanceNode": "Bria Image Enhance",
|
||||||
"ReplaceBgNode": "Bria Replace Background",
|
"ReplaceBgNode": "Bria Replace Background",
|
||||||
"RmbgNode": "Bria RMBG",
|
"RmbgNode": "Bria RMBG",
|
||||||
"RemoveForegroundNode": "Bria Remove Foreground",
|
"RemoveForegroundNode": "Bria Remove Foreground",
|
||||||
@@ -88,6 +124,25 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
|||||||
"Text2ImageHDNode": "Bria Text2Image HD",
|
"Text2ImageHDNode": "Bria Text2Image HD",
|
||||||
"ReimagineNode": "Bria Reimagine",
|
"ReimagineNode": "Bria Reimagine",
|
||||||
"AttributionByImageNode": "Attribution By Image Node",
|
"AttributionByImageNode": "Attribution By Image Node",
|
||||||
"GenerateImageNodeV2": "Generate Image",
|
"GenerateImageNodeV2": "FIBO - Generate Image",
|
||||||
"RefineImageNodeV2": "Refine and Regenerate Image",
|
"GenerateImageLiteNodeV2": "FIBO - Generate Image - Lite",
|
||||||
|
"RefineImageNodeV2": "FIBO - Refine and Regenerate Image",
|
||||||
|
"RefineImageLiteNodeV2": "FIBO - Refine Image - Lite",
|
||||||
|
"GenerateStructuredPromptNodeV2": "FIBO - Generate Structured Prompt",
|
||||||
|
"GenerateStructuredPromptLiteNodeV2": "FIBO - Generate Structured Prompt - Lite",
|
||||||
|
"RemoveVideoBackgroundNode": "Bria Remove Video Background",
|
||||||
|
"VideoSolidColorBackgroundNode":"Bria SolidColor Background Video",
|
||||||
|
"VideoMaskByPromptNode":"Bria Video Mask By Prompt",
|
||||||
|
"VideoMaskByKeyPointsNode":"Bria Video Mask By Key Points",
|
||||||
|
"VideoIncreaseResolutionNode":"Bria Video Increase Resolution",
|
||||||
|
"VideoEraseElementsNode":"Bria Video Erase Elements",
|
||||||
|
"LoadVideoFramesNode":"Bria Load Video",
|
||||||
|
"PreviewVideoURLNode":"Bria Preview Video",
|
||||||
|
"FIBOEditNode": "FIBO - Edit",
|
||||||
|
"FIBOEditStructuredInstructionNode": "FIBO - Edit - Structured Instruction",
|
||||||
|
"BriaMultiImageSelect":"Bria Multi Image Select",
|
||||||
|
"ProductIntegrateNode": "Bria Product Integrate"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WEB_DIRECTORY = "./web"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from .eraser_node import EraserNode
|
from .eraser_node import EraserNode
|
||||||
from .generative_fill_node import GenFillNode
|
from .generative_fill_node import GenFillNode
|
||||||
from .image_expansion_node import ImageExpansionNode
|
from .image_expansion_node import ImageExpansionNode
|
||||||
|
from .image_enhance_node import ImageEnhanceNode
|
||||||
from .replace_bg_node import ReplaceBgNode
|
from .replace_bg_node import ReplaceBgNode
|
||||||
from .rmbg_node import RmbgNode
|
from .rmbg_node import RmbgNode
|
||||||
from .remove_foreground_node import RemoveForegroundNode
|
from .remove_foreground_node import RemoveForegroundNode
|
||||||
@@ -12,7 +13,11 @@ from .text_2_image_fast_node import Text2ImageFastNode
|
|||||||
from .text_2_image_hd_node import Text2ImageHDNode
|
from .text_2_image_hd_node import Text2ImageHDNode
|
||||||
from .reimagine_node import ReimagineNode
|
from .reimagine_node import ReimagineNode
|
||||||
from .generate_image_node_v2 import GenerateImageNodeV2
|
from .generate_image_node_v2 import GenerateImageNodeV2
|
||||||
|
from .generate_image_lite_node_v2 import GenerateImageLiteNodeV2
|
||||||
from .refine_image_node_v2 import RefineImageNodeV2
|
from .refine_image_node_v2 import RefineImageNodeV2
|
||||||
|
from .refine_image_lite_node_v2 import RefineImageLiteNodeV2
|
||||||
|
from .generate_structured_prompt_node_v2 import GenerateStructuredPromptNodeV2
|
||||||
|
from .generate_structured_prompt_lite_node_v2 import GenerateStructuredPromptLiteNodeV2
|
||||||
from .shot_by_text_node import ShotByTextOriginalNode
|
from .shot_by_text_node import ShotByTextOriginalNode
|
||||||
from .shot_by_text_automatic_aspect_ratio_node import ShotByTextAutomaticAspectRatioNode
|
from .shot_by_text_automatic_aspect_ratio_node import ShotByTextAutomaticAspectRatioNode
|
||||||
from .shot_by_text_automatic_node import ShotByTextAutomaticNode
|
from .shot_by_text_automatic_node import ShotByTextAutomaticNode
|
||||||
@@ -28,3 +33,15 @@ from .shot_by_image_node import ShotByImageOriginalNode
|
|||||||
from .shot_by_image_manual_placement_node import ShotByImageManualPlacementNode
|
from .shot_by_image_manual_placement_node import ShotByImageManualPlacementNode
|
||||||
from .shot_by_image_manual_padding_node import ShotByImageManualPaddingNode
|
from .shot_by_image_manual_padding_node import ShotByImageManualPaddingNode
|
||||||
from .attribution_by_image_node import AttributionByImageNode
|
from .attribution_by_image_node import AttributionByImageNode
|
||||||
|
from .video_nodes.remove_video_background_node import RemoveVideoBackgroundNode
|
||||||
|
from .video_nodes.video_increase_resolution_node import VideoIncreaseResolutionNode
|
||||||
|
from .video_nodes.video_solid_color_background_node import VideoSolidColorBackgroundNode
|
||||||
|
from .video_nodes.video_erase_elements_node import VideoEraseElementsNode
|
||||||
|
from .video_nodes.video_mask_by_prompt_node import VideoMaskByPromptNode
|
||||||
|
from .video_nodes.video_mask_by_key_points_node import VideoMaskByKeyPointsNode
|
||||||
|
from .video_nodes.load_video import LoadVideoFramesNode
|
||||||
|
from .video_nodes.preview_video_node_from_url import PreviewVideoURLNode
|
||||||
|
from .fibo_edit_node import FIBOEditNode
|
||||||
|
from .fibo_edit_structured_instruction_node import FIBOEditStructuredInstructionNode
|
||||||
|
from .multi_image_select import BriaMultiImageSelect
|
||||||
|
from .product_integrate_node import ProductIntegrateNode
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
import requests
|
import requests
|
||||||
import torch
|
|
||||||
|
|
||||||
from .common import deserialize_and_get_comfy_key, preprocess_image, image_to_base64, poll_status_until_completed
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
normalize_images_input,
|
||||||
|
poll_status_until_completed,
|
||||||
|
to_pil_safe,
|
||||||
|
)
|
||||||
|
|
||||||
class AttributionByImageNode():
|
class AttributionByImageNode():
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(self):
|
def INPUT_TYPES(self):
|
||||||
return {
|
return {
|
||||||
"required": {
|
"required": {
|
||||||
"image": ("IMAGE",),
|
"images": ("IMAGE",),
|
||||||
"model_version": (["2.3", "3.0","3.2"], {"default": "2.3"}),
|
"model_version": (["2.3", "3.0", "3.2"], {"default": "2.3"}),
|
||||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("STRING",)
|
RETURN_TYPES = ("STRING",)
|
||||||
@@ -22,46 +27,43 @@ class AttributionByImageNode():
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/attribution/by_image"
|
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, images, model_version, api_key):
|
||||||
def execute(self, image, model_version, api_key):
|
|
||||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||||
raise Exception("Please insert a valid API key.")
|
raise Exception("Please insert a valid API key.")
|
||||||
api_key = deserialize_and_get_comfy_key(api_key)
|
images = normalize_images_input(images)
|
||||||
|
|
||||||
# Check if image is tensor, if so, convert to NumPy array
|
batch_results = []
|
||||||
if isinstance(image, torch.Tensor):
|
|
||||||
image = preprocess_image(image)
|
|
||||||
|
|
||||||
# Convert image to base64 for the new API format
|
for idx, pil_image in enumerate(images):
|
||||||
image_base64 = image_to_base64(image)
|
try:
|
||||||
payload = {
|
image_base64 = image_to_base64(pil_image)
|
||||||
"image": image_base64,
|
|
||||||
"model_version": model_version,
|
|
||||||
}
|
|
||||||
|
|
||||||
headers = {
|
payload = {
|
||||||
"Content-Type": "application/json",
|
"image": image_base64,
|
||||||
"api_token": f"{api_key}"
|
"model_version": model_version,
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
headers = bria_json_headers(api_key)
|
||||||
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')
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
request_id = response_dict.get('request_id')
|
|
||||||
|
|
||||||
|
if response.status_code not in (200, 202):
|
||||||
|
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||||
|
|
||||||
|
# Poll until completion
|
||||||
|
response_dict = response.json()
|
||||||
|
status_url = response_dict.get('status_url')
|
||||||
if not status_url:
|
if not status_url:
|
||||||
raise Exception("No status_url returned from API")
|
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)
|
final_response = poll_status_until_completed(status_url, api_key)
|
||||||
return (str(final_response.get("result",{}).get("content")),)
|
content = str(final_response.get("result", {}).get("content", ""))
|
||||||
else:
|
batch_results.append(content)
|
||||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"{e}")
|
print(f"[AttributionByImageNode] Skipping image {idx} due to error: {e}")
|
||||||
|
batch_results.append("")
|
||||||
|
|
||||||
|
# Join all responses with a delimiter
|
||||||
|
combined_response = "\n---\n".join(batch_results)
|
||||||
|
return (combined_response,)
|
||||||
|
|||||||
+59
-24
@@ -6,16 +6,17 @@ import base64
|
|||||||
from torchvision.transforms import ToPILImage
|
from torchvision.transforms import ToPILImage
|
||||||
import requests
|
import requests
|
||||||
import time
|
import time
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
COMFY_KEY_ERROR = (
|
BRIA_COMFYUI_USER_AGENT = "bria/ComfyUI"
|
||||||
"Invalid Token Type\n\n"
|
|
||||||
"The API token you’ve entered is not a ComfyUI token.\n"
|
|
||||||
"Please use the valid token from your BRIA Account API Keys page:\n"
|
|
||||||
"https://platform.bria.ai/console/account/api-keys"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
def bria_json_headers(api_token: str) -> dict:
|
||||||
|
"""Headers for JSON POST requests to Bria API."""
|
||||||
|
return {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"api_token": api_token,
|
||||||
|
"User-Agent": BRIA_COMFYUI_USER_AGENT,
|
||||||
|
}
|
||||||
def postprocess_image(image):
|
def postprocess_image(image):
|
||||||
result_image = Image.open(io.BytesIO(image))
|
result_image = Image.open(io.BytesIO(image))
|
||||||
result_image = result_image.convert("RGB")
|
result_image = result_image.convert("RGB")
|
||||||
@@ -41,6 +42,35 @@ def preprocess_image(image):
|
|||||||
print("Unexpected image dimensions. Expected 4D tensor.")
|
print("Unexpected image dimensions. Expected 4D tensor.")
|
||||||
return image
|
return image
|
||||||
|
|
||||||
|
def to_pil_safe(image):
|
||||||
|
"""
|
||||||
|
Converts a single image tensor or numpy array (H,W,C) to PIL Image.
|
||||||
|
Handles float32 in 0-1 and uint8.
|
||||||
|
"""
|
||||||
|
if isinstance(image, torch.Tensor):
|
||||||
|
image = image.detach().cpu().numpy()
|
||||||
|
|
||||||
|
# If image is empty, replace with 1x1 black
|
||||||
|
if image.size == 0:
|
||||||
|
image = np.zeros((1,1,3), dtype=np.uint8)
|
||||||
|
|
||||||
|
# Ensure float images are scaled 0-255
|
||||||
|
if image.dtype in [np.float32, np.float64]:
|
||||||
|
if image.max() <= 1.0:
|
||||||
|
image = (image * 255).astype(np.uint8)
|
||||||
|
else:
|
||||||
|
image = image.astype(np.uint8)
|
||||||
|
|
||||||
|
# Handle grayscale images
|
||||||
|
if image.ndim == 2:
|
||||||
|
return Image.fromarray(image, mode="L")
|
||||||
|
elif image.shape[2] == 3:
|
||||||
|
return Image.fromarray(image, mode="RGB")
|
||||||
|
elif image.shape[2] == 4:
|
||||||
|
return Image.fromarray(image, mode="RGBA")
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Cannot convert image with shape {image.shape} to PIL")
|
||||||
|
|
||||||
|
|
||||||
def preprocess_mask(mask):
|
def preprocess_mask(mask):
|
||||||
if isinstance(mask, torch.Tensor):
|
if isinstance(mask, torch.Tensor):
|
||||||
@@ -57,7 +87,6 @@ def preprocess_mask(mask):
|
|||||||
def process_request(api_url, image, mask, api_key, visual_input_content_moderation, visual_output_content_moderation):
|
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":
|
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||||
raise Exception("Please insert a valid API key.")
|
raise Exception("Please insert a valid API key.")
|
||||||
api_key = deserialize_and_get_comfy_key(api_key)
|
|
||||||
|
|
||||||
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
||||||
if isinstance(image, torch.Tensor):
|
if isinstance(image, torch.Tensor):
|
||||||
@@ -77,10 +106,7 @@ def process_request(api_url, image, mask, api_key, visual_input_content_moderati
|
|||||||
"visual_output_content_moderation":visual_output_content_moderation
|
"visual_output_content_moderation":visual_output_content_moderation
|
||||||
}
|
}
|
||||||
|
|
||||||
headers = {
|
headers = bria_json_headers(api_key)
|
||||||
"Content-Type": "application/json",
|
|
||||||
"api_token": f"{api_key}"
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(api_url, json=payload, headers=headers)
|
response = requests.post(api_url, json=payload, headers=headers)
|
||||||
@@ -132,7 +158,7 @@ def poll_status_until_completed(status_url, api_key, timeout=360, check_interval
|
|||||||
Exception: If timeout is reached or API request fails
|
Exception: If timeout is reached or API request fails
|
||||||
"""
|
"""
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
headers = {"api_token": api_key}
|
headers = bria_json_headers(api_key)
|
||||||
|
|
||||||
while time.time() - start_time < timeout:
|
while time.time() - start_time < timeout:
|
||||||
try:
|
try:
|
||||||
@@ -156,20 +182,29 @@ def poll_status_until_completed(status_url, api_key, timeout=360, check_interval
|
|||||||
|
|
||||||
raise Exception(f"Timeout reached after {timeout} seconds")
|
raise Exception(f"Timeout reached after {timeout} seconds")
|
||||||
|
|
||||||
def deserialize_and_get_comfy_key(encoded: str) -> str:
|
|
||||||
|
def normalize_images_input(images):
|
||||||
"""
|
"""
|
||||||
Decodes a base64-encoded JSON token and returns the ComfyUI API key.
|
Converts various image inputs into a list of PIL images:
|
||||||
|
- PIL.Image → [PIL.Image]
|
||||||
|
- list of PIL.Image → unchanged
|
||||||
|
- torch.Tensor (H,W,C) → [PIL.Image]
|
||||||
|
- torch.Tensor (B,H,W,C) → list of PIL.Images
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
decoded = base64.b64decode(encoded).decode("utf-8")
|
|
||||||
payload = json.loads(decoded)
|
|
||||||
|
|
||||||
if payload.get("type") != "comfy":
|
|
||||||
raise Exception(COMFY_KEY_ERROR)
|
|
||||||
|
|
||||||
return payload.get("apiKey")
|
if isinstance(images, Image.Image):
|
||||||
|
return [images]
|
||||||
except Exception as e:
|
elif isinstance(images, list):
|
||||||
raise Exception(COMFY_KEY_ERROR)
|
return [to_pil_safe(img) if isinstance(img, torch.Tensor) else img for img in images]
|
||||||
|
elif isinstance(images, torch.Tensor):
|
||||||
|
if images.ndim == 3: # (H,W,C)
|
||||||
|
return [to_pil_safe(images)]
|
||||||
|
elif images.ndim == 4: # (B,H,W,C)
|
||||||
|
return [to_pil_safe(img) for img in images]
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported tensor shape: {images.shape}")
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported input type: {type(images)}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import requests
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
poll_status_until_completed,
|
||||||
|
preprocess_image,
|
||||||
|
preprocess_mask,
|
||||||
|
postprocess_image,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FIBOEditNode:
|
||||||
|
"""FIBO Edit Node - Edit images with instructions"""
|
||||||
|
|
||||||
|
api_url = "https://engine.prod.bria-api.com/v2/image/edit"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
|
"images": ("IMAGE",),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"instruction": ("STRING",),
|
||||||
|
"mask": ("MASK",),
|
||||||
|
"structured_instruction": ("STRING",),
|
||||||
|
"negative_prompt": ("STRING",),
|
||||||
|
"steps_num": (
|
||||||
|
"INT",
|
||||||
|
{
|
||||||
|
"default": 50,
|
||||||
|
"min": 1,
|
||||||
|
"max": 100,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
"guidance_scale": (
|
||||||
|
"INT",
|
||||||
|
{
|
||||||
|
"default": 5,
|
||||||
|
"min": 1,
|
||||||
|
"max": 20,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
"seed": ("INT", {"default": 123456}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("IMAGE", "STRING", "INT")
|
||||||
|
RETURN_NAMES = ("IMAGE", "structured_instruction", "seed")
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
FUNCTION = "execute"
|
||||||
|
|
||||||
|
def _validate_token(self, api_token: str):
|
||||||
|
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||||
|
raise Exception("Please insert a valid API token.")
|
||||||
|
|
||||||
|
def _build_payload(
|
||||||
|
self,
|
||||||
|
instruction,
|
||||||
|
images,
|
||||||
|
mask=None,
|
||||||
|
structured_instruction=None,
|
||||||
|
negative_prompt=None,
|
||||||
|
steps_num=50,
|
||||||
|
guidance_scale=5,
|
||||||
|
seed=123456,
|
||||||
|
):
|
||||||
|
# Process images
|
||||||
|
if isinstance(images, torch.Tensor):
|
||||||
|
processed_images = preprocess_image(images)
|
||||||
|
else:
|
||||||
|
processed_images = images
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"images": [image_to_base64(processed_images)],
|
||||||
|
"steps_num": steps_num,
|
||||||
|
"guidance_scale": guidance_scale,
|
||||||
|
"seed": seed,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add optional mask
|
||||||
|
if mask is not None:
|
||||||
|
if isinstance(mask, torch.Tensor):
|
||||||
|
processed_mask = preprocess_mask(mask)
|
||||||
|
else:
|
||||||
|
processed_mask = mask
|
||||||
|
payload["mask"] = image_to_base64(processed_mask)
|
||||||
|
|
||||||
|
# Add optional structured_instruction
|
||||||
|
if structured_instruction:
|
||||||
|
payload["structured_instruction"] = structured_instruction
|
||||||
|
# Add optional structured_instruction
|
||||||
|
if instruction:
|
||||||
|
payload["instruction"] = instruction
|
||||||
|
# Add optional negative_prompt
|
||||||
|
if negative_prompt:
|
||||||
|
payload["negative_prompt"] = negative_prompt
|
||||||
|
|
||||||
|
return payload
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
self,
|
||||||
|
api_token,
|
||||||
|
instruction,
|
||||||
|
images,
|
||||||
|
mask=None,
|
||||||
|
structured_instruction=None,
|
||||||
|
negative_prompt=None,
|
||||||
|
steps_num=50,
|
||||||
|
guidance_scale=5,
|
||||||
|
seed=123456,
|
||||||
|
):
|
||||||
|
self._validate_token(api_token)
|
||||||
|
payload = self._build_payload(
|
||||||
|
instruction,
|
||||||
|
images,
|
||||||
|
mask,
|
||||||
|
structured_instruction,
|
||||||
|
negative_prompt,
|
||||||
|
steps_num,
|
||||||
|
guidance_scale,
|
||||||
|
seed,
|
||||||
|
)
|
||||||
|
headers = bria_json_headers(api_token)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code in (200, 202):
|
||||||
|
print(
|
||||||
|
f"Initial request successful to {self.api_url}, polling for completion..."
|
||||||
|
)
|
||||||
|
response_dict = response.json()
|
||||||
|
status_url = response_dict.get("status_url")
|
||||||
|
request_id = response_dict.get("request_id")
|
||||||
|
|
||||||
|
if not status_url:
|
||||||
|
raise Exception("No status_url returned from API")
|
||||||
|
|
||||||
|
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||||
|
|
||||||
|
final_response = poll_status_until_completed(status_url, api_token)
|
||||||
|
|
||||||
|
result = final_response.get("result", {})
|
||||||
|
result_image_url = result.get("image_url")
|
||||||
|
structured_prompt = result.get("structured_prompt", "")
|
||||||
|
used_seed = result.get("seed")
|
||||||
|
|
||||||
|
image_response = requests.get(result_image_url)
|
||||||
|
result_image = postprocess_image(image_response.content)
|
||||||
|
|
||||||
|
return (result_image, structured_prompt, used_seed)
|
||||||
|
|
||||||
|
raise Exception(
|
||||||
|
f"Error: API request failed with status code {response.status_code} {response.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"{e}")
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import requests
|
||||||
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
normalize_images_input,
|
||||||
|
poll_status_until_completed,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FIBOEditStructuredInstructionNode:
|
||||||
|
"""FIBO Edit Structured Instruction Node - Generate structured instructions for image editing"""
|
||||||
|
|
||||||
|
api_url = "https://engine.prod.bria-api.com/v2/structured_instruction/generate"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
|
"images": ("IMAGE",),
|
||||||
|
"instruction": ("STRING",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("STRING",)
|
||||||
|
RETURN_NAMES = ("structured_instruction",)
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
FUNCTION = "execute"
|
||||||
|
|
||||||
|
def _validate_token(self, api_token: str):
|
||||||
|
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||||
|
raise Exception("Please insert a valid API token.")
|
||||||
|
|
||||||
|
def _build_payload(self, processed_image, instruction):
|
||||||
|
payload = {
|
||||||
|
"instruction": instruction,
|
||||||
|
"images": [image_to_base64(processed_image)],
|
||||||
|
}
|
||||||
|
return payload
|
||||||
|
|
||||||
|
def execute(self, api_token, images, instruction):
|
||||||
|
self._validate_token(api_token)
|
||||||
|
# Normalize input to list of PIL images
|
||||||
|
images = normalize_images_input(images)
|
||||||
|
|
||||||
|
batch_results = []
|
||||||
|
|
||||||
|
for idx, pil_image in enumerate(images):
|
||||||
|
try:
|
||||||
|
payload = self._build_payload(pil_image, instruction)
|
||||||
|
headers = bria_json_headers(api_token)
|
||||||
|
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code not in (200, 202):
|
||||||
|
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||||
|
|
||||||
|
print(f"Initial request successful for image {idx}, polling for completion...")
|
||||||
|
response_dict = response.json()
|
||||||
|
status_url = response_dict.get("status_url")
|
||||||
|
if not status_url:
|
||||||
|
raise Exception("No status_url returned from API")
|
||||||
|
|
||||||
|
final_response = poll_status_until_completed(status_url, api_token)
|
||||||
|
result = final_response.get("result", {})
|
||||||
|
structured_instruction = result.get("structured_instruction", "")
|
||||||
|
batch_results.append(structured_instruction)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[FIBOEditStructuredInstructionNode] Skipping image {idx} due to error: {e}")
|
||||||
|
batch_results.append("")
|
||||||
|
|
||||||
|
combined_instructions = "\n---\n".join(batch_results)
|
||||||
|
return (combined_instructions,)
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import requests
|
||||||
|
import torch
|
||||||
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
normalize_images_input,
|
||||||
|
poll_status_until_completed,
|
||||||
|
postprocess_image,
|
||||||
|
)
|
||||||
|
class GenerateImageLiteNodeV2:
|
||||||
|
"""Lite Image Generation Node (multi-image compatible)"""
|
||||||
|
|
||||||
|
api_url = "https://engine.prod.bria-api.com/v2/image/generate/lite"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
|
"prompt": ("STRING",),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"model_version": (["FIBO"], {"default": "FIBO"}),
|
||||||
|
"structured_prompt": ("STRING", {"default": ""}),
|
||||||
|
"images": ("IMAGE",),
|
||||||
|
"aspect_ratio": (
|
||||||
|
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
|
||||||
|
{"default": "1:1"},
|
||||||
|
),
|
||||||
|
"steps_num": ("INT", {"default": 8, "min": 8, "max": 30}),
|
||||||
|
"guidance_scale": ("INT", {"default": 5, "min": 3, "max": 5}),
|
||||||
|
"seed": ("STRING", {"default": "123456"}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("IMAGE", "STRING", "STRING")
|
||||||
|
RETURN_NAMES = ("image", "structured_prompt", "seed")
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
FUNCTION = "execute"
|
||||||
|
|
||||||
|
def _validate_token(self, api_token: str):
|
||||||
|
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||||
|
raise Exception("Please insert a valid API token.")
|
||||||
|
|
||||||
|
def _build_payload(
|
||||||
|
self,
|
||||||
|
prompt,
|
||||||
|
model_version,
|
||||||
|
structured_prompt,
|
||||||
|
aspect_ratio,
|
||||||
|
steps_num,
|
||||||
|
guidance_scale,
|
||||||
|
seed,
|
||||||
|
processed_image=None,
|
||||||
|
):
|
||||||
|
payload = {
|
||||||
|
"prompt": prompt,
|
||||||
|
"model_version": model_version,
|
||||||
|
"aspect_ratio": aspect_ratio,
|
||||||
|
"steps_num": steps_num,
|
||||||
|
"guidance_scale": guidance_scale,
|
||||||
|
"seed": int(seed),
|
||||||
|
}
|
||||||
|
if structured_prompt:
|
||||||
|
payload["structured_prompt"] = structured_prompt
|
||||||
|
if processed_image is not None:
|
||||||
|
payload["images"] = [image_to_base64(processed_image)]
|
||||||
|
return payload
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
self,
|
||||||
|
api_token,
|
||||||
|
prompt,
|
||||||
|
model_version,
|
||||||
|
structured_prompt,
|
||||||
|
aspect_ratio,
|
||||||
|
steps_num,
|
||||||
|
guidance_scale,
|
||||||
|
seed,
|
||||||
|
images=None,
|
||||||
|
):
|
||||||
|
self._validate_token(api_token)
|
||||||
|
images_list = normalize_images_input(images) if images is not None else [None]
|
||||||
|
|
||||||
|
# Structured prompts per image
|
||||||
|
if isinstance(structured_prompt, str):
|
||||||
|
structured_prompts_list = structured_prompt.split("\n---\n")
|
||||||
|
elif isinstance(structured_prompt, list):
|
||||||
|
structured_prompts_list = structured_prompt
|
||||||
|
else:
|
||||||
|
structured_prompts_list = [""] * len(images_list)
|
||||||
|
if len(structured_prompts_list) < len(images_list):
|
||||||
|
structured_prompts_list += [structured_prompts_list[-1]] * (len(images_list) - len(structured_prompts_list))
|
||||||
|
|
||||||
|
# Seeds per image
|
||||||
|
if isinstance(seed, str):
|
||||||
|
seed_values = [int(s.strip()) for s in seed.split(",")]
|
||||||
|
else:
|
||||||
|
seed_values = [int(seed)]
|
||||||
|
if len(seed_values) < len(images_list):
|
||||||
|
seed_values += [seed_values[-1]] * (len(images_list) - len(seed_values))
|
||||||
|
|
||||||
|
batch_results = []
|
||||||
|
batch_structured_prompts = []
|
||||||
|
batch_seeds = []
|
||||||
|
|
||||||
|
for idx, ref_image in enumerate(images_list):
|
||||||
|
try:
|
||||||
|
|
||||||
|
payload = self._build_payload(
|
||||||
|
prompt,
|
||||||
|
model_version,
|
||||||
|
structured_prompts_list[idx],
|
||||||
|
aspect_ratio,
|
||||||
|
steps_num,
|
||||||
|
guidance_scale,
|
||||||
|
seed_values[idx],
|
||||||
|
ref_image,
|
||||||
|
)
|
||||||
|
|
||||||
|
headers = bria_json_headers(api_token)
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
if response.status_code not in (200, 202):
|
||||||
|
raise Exception(
|
||||||
|
f"API request failed with status code {response.status_code}: {response.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"GenerateImageLiteNodeV2 - Initial request successful for image {idx}, polling for completion...")
|
||||||
|
response_dict = response.json()
|
||||||
|
status_url = response_dict.get("status_url")
|
||||||
|
if not status_url:
|
||||||
|
raise Exception("No status_url returned from API")
|
||||||
|
|
||||||
|
final_response = poll_status_until_completed(status_url, api_token)
|
||||||
|
result = final_response.get("result", {})
|
||||||
|
result_image_url = result.get("image_url")
|
||||||
|
structured_prompt_result = result.get("structured_prompt", "")
|
||||||
|
used_seed = result.get("seed", seed_values[idx])
|
||||||
|
|
||||||
|
image_response = requests.get(result_image_url)
|
||||||
|
result_image = postprocess_image(image_response.content)
|
||||||
|
|
||||||
|
batch_results.append(result_image)
|
||||||
|
batch_structured_prompts.append(structured_prompt_result)
|
||||||
|
batch_seeds.append(str(used_seed))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[GenerateImageLiteNodeV2] Skipping iteration {idx} due to error: {e}")
|
||||||
|
batch_results.append(torch.zeros((1, 512, 512, 3), dtype=torch.float32))
|
||||||
|
batch_structured_prompts.append("")
|
||||||
|
batch_seeds.append(str(seed_values[idx]))
|
||||||
|
|
||||||
|
output_batch = torch.cat(batch_results, dim=0)
|
||||||
|
combined_structured_prompts = "\n---\n".join(batch_structured_prompts)
|
||||||
|
combined_seeds = ",".join(batch_seeds)
|
||||||
|
|
||||||
|
return output_batch, combined_structured_prompts, combined_seeds
|
||||||
@@ -2,18 +2,18 @@ import requests
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
from .common import (
|
from .common import (
|
||||||
deserialize_and_get_comfy_key,
|
bria_json_headers,
|
||||||
postprocess_image,
|
|
||||||
preprocess_image,
|
|
||||||
image_to_base64,
|
image_to_base64,
|
||||||
|
normalize_images_input,
|
||||||
poll_status_until_completed,
|
poll_status_until_completed,
|
||||||
|
postprocess_image,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class _BaseGenerateImageNodeV2:
|
class GenerateImageNodeV2:
|
||||||
"""Base class for image generation nodes (standard & pro)."""
|
"""Standard Image Generation Node (multi-image compatible)"""
|
||||||
|
|
||||||
api_url = None # Each subclass must define its API endpoint
|
api_url = "https://engine.prod.bria-api.com/v2/image/generate"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
def INPUT_TYPES(cls):
|
||||||
@@ -24,19 +24,20 @@ class _BaseGenerateImageNodeV2:
|
|||||||
},
|
},
|
||||||
"optional": {
|
"optional": {
|
||||||
"model_version": (["FIBO"], {"default": "FIBO"}),
|
"model_version": (["FIBO"], {"default": "FIBO"}),
|
||||||
"negative_prompt": ("STRING", {"default": ""}),
|
"structured_prompt": ("STRING", {"default": ""}),
|
||||||
|
"negative_prompt": ("STRING",),
|
||||||
"images": ("IMAGE",),
|
"images": ("IMAGE",),
|
||||||
"aspect_ratio": (
|
"aspect_ratio": (
|
||||||
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
|
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
|
||||||
{"default": "1:1"},
|
{"default": "1:1"},
|
||||||
),
|
),
|
||||||
"steps_num": ("INT", {"default": 50, "min": 20, "max": 50}),
|
"steps_num": ("INT", {"default": 50, "min": 35, "max": 50}),
|
||||||
"guidance_scale": ("INT", {"default": 5, "min": 3, "max": 5}),
|
"guidance_scale": ("INT", {"default": 5, "min": 3, "max": 5}),
|
||||||
"seed": ("INT", {"default": 123456}),
|
"seed": ("STRING", {"default": "123456"}), # Accept string to match previous node
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("IMAGE", "STRING", "INT")
|
RETURN_TYPES = ("IMAGE", "STRING", "STRING") # images, structured_prompts, seeds
|
||||||
RETURN_NAMES = ("image", "structured_prompt", "seed")
|
RETURN_NAMES = ("image", "structured_prompt", "seed")
|
||||||
CATEGORY = "API Nodes"
|
CATEGORY = "API Nodes"
|
||||||
FUNCTION = "execute"
|
FUNCTION = "execute"
|
||||||
@@ -49,29 +50,28 @@ class _BaseGenerateImageNodeV2:
|
|||||||
self,
|
self,
|
||||||
prompt,
|
prompt,
|
||||||
model_version,
|
model_version,
|
||||||
negative_prompt,
|
structured_prompt,
|
||||||
aspect_ratio,
|
aspect_ratio,
|
||||||
steps_num,
|
steps_num,
|
||||||
guidance_scale,
|
guidance_scale,
|
||||||
seed,
|
seed,
|
||||||
images=None,
|
negative_prompt=None,
|
||||||
|
processed_image=None,
|
||||||
):
|
):
|
||||||
payload = {
|
payload = {
|
||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
"model_version": model_version,
|
"model_version": model_version,
|
||||||
"negative_prompt": negative_prompt,
|
|
||||||
"aspect_ratio": aspect_ratio,
|
"aspect_ratio": aspect_ratio,
|
||||||
"steps_num": steps_num,
|
"steps_num": steps_num,
|
||||||
"guidance_scale": guidance_scale,
|
"guidance_scale": guidance_scale,
|
||||||
"seed": seed,
|
"seed": int(seed),
|
||||||
}
|
}
|
||||||
|
if structured_prompt:
|
||||||
if images is not None:
|
payload["structured_prompt"] = structured_prompt
|
||||||
if isinstance(images, torch.Tensor):
|
if negative_prompt:
|
||||||
preprocess_images = preprocess_image(images)
|
payload["negative_prompt"] = negative_prompt
|
||||||
payload["images"] = [image_to_base64(preprocess_images)]
|
if processed_image is not None:
|
||||||
|
payload["images"] = [image_to_base64(processed_image)]
|
||||||
|
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
def execute(
|
def execute(
|
||||||
@@ -79,65 +79,88 @@ class _BaseGenerateImageNodeV2:
|
|||||||
api_token,
|
api_token,
|
||||||
prompt,
|
prompt,
|
||||||
model_version,
|
model_version,
|
||||||
negative_prompt,
|
structured_prompt,
|
||||||
aspect_ratio,
|
aspect_ratio,
|
||||||
steps_num,
|
steps_num,
|
||||||
guidance_scale,
|
guidance_scale,
|
||||||
seed,
|
seed,
|
||||||
|
negative_prompt=None,
|
||||||
images=None,
|
images=None,
|
||||||
):
|
):
|
||||||
self._validate_token(api_token)
|
self._validate_token(api_token)
|
||||||
payload = self._build_payload(
|
images_list = normalize_images_input(images) if images is not None else [None]
|
||||||
prompt,
|
|
||||||
model_version,
|
|
||||||
negative_prompt,
|
|
||||||
aspect_ratio,
|
|
||||||
steps_num,
|
|
||||||
guidance_scale,
|
|
||||||
seed,
|
|
||||||
images,
|
|
||||||
)
|
|
||||||
api_token = deserialize_and_get_comfy_key(api_token)
|
|
||||||
|
|
||||||
headers = {"Content-Type": "application/json", "api_token": api_token}
|
# Structured prompts per image
|
||||||
|
if isinstance(structured_prompt, str):
|
||||||
|
structured_prompts_list = structured_prompt.split("\n---\n")
|
||||||
|
elif isinstance(structured_prompt, list):
|
||||||
|
structured_prompts_list = structured_prompt
|
||||||
|
else:
|
||||||
|
structured_prompts_list = [""] * len(images_list)
|
||||||
|
if len(structured_prompts_list) < len(images_list):
|
||||||
|
structured_prompts_list += [structured_prompts_list[-1]] * (len(images_list) - len(structured_prompts_list))
|
||||||
|
|
||||||
try:
|
# Seeds per image
|
||||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
if isinstance(seed, str):
|
||||||
|
seed_values = [int(s.strip()) for s in seed.split(",")]
|
||||||
|
else:
|
||||||
|
seed_values = [int(seed)]
|
||||||
|
if len(seed_values) < len(images_list):
|
||||||
|
seed_values += [seed_values[-1]] * (len(images_list) - len(seed_values))
|
||||||
|
|
||||||
if response.status_code in (200, 202):
|
batch_results = []
|
||||||
print(
|
batch_structured_prompts = []
|
||||||
f"Initial request successful to {self.api_url}, polling for completion..."
|
batch_seeds = []
|
||||||
|
|
||||||
|
for idx, ref_image in enumerate(images_list):
|
||||||
|
try:
|
||||||
|
payload = self._build_payload(
|
||||||
|
prompt,
|
||||||
|
model_version,
|
||||||
|
structured_prompts_list[idx],
|
||||||
|
aspect_ratio,
|
||||||
|
steps_num,
|
||||||
|
guidance_scale,
|
||||||
|
seed_values[idx],
|
||||||
|
negative_prompt,
|
||||||
|
ref_image,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
headers = bria_json_headers(api_token)
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code not in (200, 202):
|
||||||
|
raise Exception(
|
||||||
|
f"API request failed with status code {response.status_code}: {response.text}"
|
||||||
|
)
|
||||||
|
print(f"GenerateImageNodeV2 - Initial request successful for image {idx}, polling for completion...")
|
||||||
response_dict = response.json()
|
response_dict = response.json()
|
||||||
status_url = response_dict.get("status_url")
|
status_url = response_dict.get("status_url")
|
||||||
request_id = response_dict.get("request_id")
|
|
||||||
|
|
||||||
if not status_url:
|
if not status_url:
|
||||||
raise Exception("No status_url returned from API")
|
raise Exception("No status_url returned from API")
|
||||||
|
|
||||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
|
||||||
|
|
||||||
final_response = poll_status_until_completed(status_url, api_token)
|
final_response = poll_status_until_completed(status_url, api_token)
|
||||||
|
|
||||||
result = final_response.get("result", {})
|
result = final_response.get("result", {})
|
||||||
result_image_url = result.get("image_url")
|
result_image_url = result.get("image_url")
|
||||||
structured_prompt = result.get("structured_prompt", "")
|
structured_prompt_result = result.get("structured_prompt", "")
|
||||||
used_seed = result.get("seed")
|
used_seed = result.get("seed", seed_values[idx])
|
||||||
|
|
||||||
image_response = requests.get(result_image_url)
|
image_response = requests.get(result_image_url)
|
||||||
result_image = postprocess_image(image_response.content)
|
result_image = postprocess_image(image_response.content)
|
||||||
|
|
||||||
return (result_image, structured_prompt, used_seed)
|
batch_results.append(result_image)
|
||||||
|
batch_structured_prompts.append(structured_prompt_result)
|
||||||
|
batch_seeds.append(str(used_seed))
|
||||||
|
|
||||||
raise Exception(
|
except Exception as e:
|
||||||
f"Error: API request failed with status code {response.status_code} {response.text}"
|
print(f"[GenerateImageNodeV2] Skipping iteration {idx} due to error: {e}")
|
||||||
)
|
batch_results.append(torch.zeros((1, 512, 512, 3), dtype=torch.float32))
|
||||||
|
batch_structured_prompts.append("")
|
||||||
|
batch_seeds.append(str(seed_values[idx]))
|
||||||
|
|
||||||
except Exception as e:
|
# Return all as strings for proper chaining
|
||||||
raise Exception(f"{e}")
|
output_batch = torch.cat(batch_results, dim=0)
|
||||||
|
combined_structured_prompts = "\n---\n".join(batch_structured_prompts)
|
||||||
|
combined_seeds = ",".join(batch_seeds)
|
||||||
|
|
||||||
|
return output_batch, combined_structured_prompts, combined_seeds
|
||||||
class GenerateImageNodeV2(_BaseGenerateImageNodeV2):
|
|
||||||
"""Standard Image Generation Node"""
|
|
||||||
def __init__(self):
|
|
||||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/generate"
|
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import requests
|
||||||
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
normalize_images_input,
|
||||||
|
poll_status_until_completed,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GenerateStructuredPromptLiteNodeV2:
|
||||||
|
"""Lite Structured Prompt Generation Node (multi-image compatible)"""
|
||||||
|
|
||||||
|
api_url = "https://engine.prod.bria-api.com/v2/structured_prompt/generate/lite"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
|
"prompt": ("STRING",),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"structured_prompt": ("STRING",),
|
||||||
|
"images": ("IMAGE",),
|
||||||
|
"seed": ("STRING", {"default": "123456"}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("STRING", "STRING") # structured_prompts, seeds as comma-separated string
|
||||||
|
RETURN_NAMES = ("structured_prompt", "seed")
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
FUNCTION = "execute"
|
||||||
|
|
||||||
|
def _validate_token(self, api_token: str):
|
||||||
|
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||||
|
raise Exception("Please insert a valid API token.")
|
||||||
|
|
||||||
|
def _build_payload(self, prompt, seed, structured_prompt, processed_image=None):
|
||||||
|
payload = {"prompt": prompt, "seed": int(seed)}
|
||||||
|
if structured_prompt:
|
||||||
|
payload["structured_prompt"] = structured_prompt
|
||||||
|
if processed_image is not None:
|
||||||
|
payload["images"] = [image_to_base64(processed_image)]
|
||||||
|
return payload
|
||||||
|
|
||||||
|
def execute(self, api_token, prompt, seed, structured_prompt, images=None):
|
||||||
|
self._validate_token(api_token)
|
||||||
|
images_list = normalize_images_input(images) if images is not None else [None]
|
||||||
|
|
||||||
|
# Seeds per image
|
||||||
|
if isinstance(seed, str):
|
||||||
|
seed_values = [int(s.strip()) for s in seed.split(",")]
|
||||||
|
else:
|
||||||
|
seed_values = [seed]
|
||||||
|
if len(seed_values) < len(images_list):
|
||||||
|
seed_values += [seed_values[-1]] * (len(images_list) - len(seed_values))
|
||||||
|
|
||||||
|
# Structured prompts per image
|
||||||
|
if isinstance(structured_prompt, str):
|
||||||
|
structured_prompts_list = structured_prompt.split("\n---\n")
|
||||||
|
elif isinstance(structured_prompt, list):
|
||||||
|
structured_prompts_list = structured_prompt
|
||||||
|
else:
|
||||||
|
structured_prompts_list = [""] * len(images_list)
|
||||||
|
if len(structured_prompts_list) < len(images_list):
|
||||||
|
structured_prompts_list += [structured_prompts_list[-1]] * (len(images_list) - len(structured_prompts_list))
|
||||||
|
|
||||||
|
batch_structured_prompts = []
|
||||||
|
batch_seeds = []
|
||||||
|
|
||||||
|
for idx, image in enumerate(images_list):
|
||||||
|
try:
|
||||||
|
|
||||||
|
payload = self._build_payload(
|
||||||
|
prompt,
|
||||||
|
seed_values[idx],
|
||||||
|
structured_prompts_list[idx],
|
||||||
|
image,
|
||||||
|
)
|
||||||
|
|
||||||
|
headers = bria_json_headers(api_token)
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
if response.status_code not in (200, 202):
|
||||||
|
raise Exception(
|
||||||
|
f"API request failed with status code {response.status_code}: {response.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
response_dict = response.json()
|
||||||
|
print(f"GenerateStructuredPromptLiteNodeV2 - Initial request successful for image {idx}, polling for completion...")
|
||||||
|
|
||||||
|
status_url = response_dict.get("status_url")
|
||||||
|
if not status_url:
|
||||||
|
raise Exception("No status_url returned from API")
|
||||||
|
|
||||||
|
final_response = poll_status_until_completed(status_url, api_token)
|
||||||
|
result = final_response.get("result", {})
|
||||||
|
structured_prompt_result = result.get("structured_prompt", "")
|
||||||
|
used_seed = result.get("seed", seed_values[idx])
|
||||||
|
|
||||||
|
batch_structured_prompts.append(structured_prompt_result)
|
||||||
|
batch_seeds.append(str(used_seed))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[GenerateStructuredPromptLiteNodeV2] Skipping iteration {idx} due to error: {e}")
|
||||||
|
batch_structured_prompts.append("")
|
||||||
|
batch_seeds.append(str(seed_values[idx]))
|
||||||
|
|
||||||
|
combined_prompts = "\n---\n".join(batch_structured_prompts)
|
||||||
|
combined_seeds = ",".join(batch_seeds)
|
||||||
|
return combined_prompts, combined_seeds
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
normalize_images_input,
|
||||||
|
poll_status_until_completed,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GenerateStructuredPromptNodeV2:
|
||||||
|
"""Structured Prompt Generation Node (multi-image compatible)"""
|
||||||
|
|
||||||
|
api_url = "https://engine.prod.bria-api.com/v2/structured_prompt/generate"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
|
"prompt": ("STRING",),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"structured_prompt": ("STRING",),
|
||||||
|
"images": ("IMAGE",),
|
||||||
|
"seed": ("STRING", {"default": "123456"}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("STRING", "STRING") # structured_prompts, seeds as comma-separated string
|
||||||
|
RETURN_NAMES = ("structured_prompt", "seed")
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
FUNCTION = "execute"
|
||||||
|
|
||||||
|
def _validate_token(self, api_token: str):
|
||||||
|
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||||
|
raise Exception("Please insert a valid API token.")
|
||||||
|
|
||||||
|
def _build_payload(self, prompt, seed, structured_prompt, processed_image=None):
|
||||||
|
payload = {"prompt": prompt, "seed": int(seed)}
|
||||||
|
if structured_prompt:
|
||||||
|
payload["structured_prompt"] = structured_prompt
|
||||||
|
if processed_image is not None:
|
||||||
|
payload["images"] = [image_to_base64(processed_image)]
|
||||||
|
return payload
|
||||||
|
|
||||||
|
def execute(self, api_token, prompt, seed, structured_prompt, images=None):
|
||||||
|
self._validate_token(api_token)
|
||||||
|
images_list = normalize_images_input(images) if images is not None else [None]
|
||||||
|
|
||||||
|
# Seeds per image
|
||||||
|
if isinstance(seed, str):
|
||||||
|
seed_values = [int(s.strip()) for s in seed.split(",")]
|
||||||
|
else:
|
||||||
|
seed_values = [seed]
|
||||||
|
if len(seed_values) < len(images_list):
|
||||||
|
seed_values += [seed_values[-1]] * (len(images_list) - len(seed_values))
|
||||||
|
|
||||||
|
# Structured prompts per image
|
||||||
|
if isinstance(structured_prompt, str):
|
||||||
|
structured_prompts_list = structured_prompt.split("\n---\n")
|
||||||
|
elif isinstance(structured_prompt, list):
|
||||||
|
structured_prompts_list = structured_prompt
|
||||||
|
else:
|
||||||
|
structured_prompts_list = [""] * len(images_list)
|
||||||
|
if len(structured_prompts_list) < len(images_list):
|
||||||
|
structured_prompts_list += [structured_prompts_list[-1]] * (len(images_list) - len(structured_prompts_list))
|
||||||
|
|
||||||
|
batch_structured_prompts = []
|
||||||
|
batch_seeds = []
|
||||||
|
|
||||||
|
for idx, image in enumerate(images_list):
|
||||||
|
try:
|
||||||
|
|
||||||
|
payload = self._build_payload(
|
||||||
|
prompt,
|
||||||
|
seed_values[idx],
|
||||||
|
structured_prompts_list[idx],
|
||||||
|
image
|
||||||
|
)
|
||||||
|
|
||||||
|
headers = bria_json_headers(api_token)
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
if response.status_code not in (200, 202):
|
||||||
|
raise Exception(
|
||||||
|
f"API request failed with status code {response.status_code}: {response.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
response_dict = response.json()
|
||||||
|
print(f"GenerateStructuredPromptNodeV2 - Initial request successful for image {idx}, polling for completion...")
|
||||||
|
|
||||||
|
status_url = response_dict.get("status_url")
|
||||||
|
if not status_url:
|
||||||
|
raise Exception("No status_url returned from API")
|
||||||
|
|
||||||
|
final_response = poll_status_until_completed(status_url, api_token)
|
||||||
|
|
||||||
|
result = final_response.get("result", {})
|
||||||
|
structured_prompt_result = result.get("structured_prompt", "")
|
||||||
|
used_seed = result.get("seed", seed_values[idx])
|
||||||
|
|
||||||
|
batch_structured_prompts.append(structured_prompt_result)
|
||||||
|
batch_seeds.append(str(used_seed)) # Keep as string for passing between nodes
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[GenerateStructuredPromptNodeV2] Skipping iteration {idx} due to error: {e}")
|
||||||
|
batch_structured_prompts.append("")
|
||||||
|
batch_seeds.append(str(seed_values[idx]))
|
||||||
|
|
||||||
|
# Return combined structured prompts and seeds as strings
|
||||||
|
combined_prompts = "\n---\n".join(batch_structured_prompts)
|
||||||
|
combined_seeds = ",".join(batch_seeds)
|
||||||
|
return combined_prompts, combined_seeds
|
||||||
@@ -4,7 +4,13 @@ from PIL import Image
|
|||||||
import io
|
import io
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from .common import deserialize_and_get_comfy_key, preprocess_image, preprocess_mask, image_to_base64, poll_status_until_completed
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
poll_status_until_completed,
|
||||||
|
preprocess_image,
|
||||||
|
preprocess_mask,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GenFillNode():
|
class GenFillNode():
|
||||||
@@ -39,8 +45,6 @@ class GenFillNode():
|
|||||||
def execute(self, image, mask, prompt, api_key, seed, prompt_content_moderation, visual_input_content_moderation, visual_output_content_moderation):
|
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":
|
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||||
raise Exception("Please insert a valid API key.")
|
raise Exception("Please insert a valid API key.")
|
||||||
api_key = deserialize_and_get_comfy_key(api_key)
|
|
||||||
|
|
||||||
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
||||||
if isinstance(image, torch.Tensor):
|
if isinstance(image, torch.Tensor):
|
||||||
image = preprocess_image(image)
|
image = preprocess_image(image)
|
||||||
@@ -60,13 +64,11 @@ class GenFillNode():
|
|||||||
"seed": seed,
|
"seed": seed,
|
||||||
"prompt_content_moderation":prompt_content_moderation,
|
"prompt_content_moderation":prompt_content_moderation,
|
||||||
"visual_input_content_moderation":visual_input_content_moderation,
|
"visual_input_content_moderation":visual_input_content_moderation,
|
||||||
"visual_output_content_moderation":visual_output_content_moderation
|
"visual_output_content_moderation":visual_output_content_moderation,
|
||||||
|
"version": 2
|
||||||
}
|
}
|
||||||
|
|
||||||
headers = {
|
headers = bria_json_headers(api_key)
|
||||||
"Content-Type": "application/json",
|
|
||||||
"api_token": f"{api_key}"
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Send initial request to get status URL
|
# Send initial request to get status URL
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import io
|
||||||
|
import requests
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
normalize_images_input,
|
||||||
|
poll_status_until_completed,
|
||||||
|
)
|
||||||
|
|
||||||
|
class ImageEnhanceNode():
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"images": ("IMAGE",),
|
||||||
|
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"steps_num": ("INT", {"default": 20, "min": 10, "max": 50}),
|
||||||
|
"resolution": (["1MP", "2MP", "4MP"], {"default": "1MP"}),
|
||||||
|
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
||||||
|
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||||
|
"seed": ("INT", {"default": 681794}),
|
||||||
|
"preserve_alpha": ("BOOLEAN", {"default": True}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("IMAGE", "STRING")
|
||||||
|
RETURN_NAMES = ("output_images", "seeds",)
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
FUNCTION = "execute"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/enhance"
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
self,
|
||||||
|
images,
|
||||||
|
api_key,
|
||||||
|
visual_input_content_moderation,
|
||||||
|
visual_output_content_moderation,
|
||||||
|
seed,
|
||||||
|
steps_num,
|
||||||
|
resolution,
|
||||||
|
preserve_alpha
|
||||||
|
):
|
||||||
|
# Validate API key
|
||||||
|
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||||
|
raise Exception("Please insert a valid API key.")
|
||||||
|
# Normalize input to list of PIL images
|
||||||
|
images = normalize_images_input(images)
|
||||||
|
|
||||||
|
batch_results = []
|
||||||
|
batch_seeds = []
|
||||||
|
|
||||||
|
for idx, pil_image in enumerate(images):
|
||||||
|
try:
|
||||||
|
image_base64 = image_to_base64(pil_image)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"image": image_base64,
|
||||||
|
"visual_input_content_moderation": visual_input_content_moderation,
|
||||||
|
"visual_output_content_moderation": visual_output_content_moderation,
|
||||||
|
"seed": seed,
|
||||||
|
"steps_num": steps_num,
|
||||||
|
"resolution": resolution,
|
||||||
|
"preserve_alpha": preserve_alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = bria_json_headers(api_key)
|
||||||
|
|
||||||
|
# Send request
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
if response.status_code not in (200, 202):
|
||||||
|
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||||
|
|
||||||
|
# Poll until completion
|
||||||
|
response_dict = response.json()
|
||||||
|
status_url = response_dict.get("status_url")
|
||||||
|
if not status_url:
|
||||||
|
raise Exception("No status_url returned from API")
|
||||||
|
|
||||||
|
print(f"ImageEnhanceNode - Initial request successful for image {idx}, polling for completion...")
|
||||||
|
final_response = poll_status_until_completed(status_url, api_key)
|
||||||
|
result_image_url = final_response["result"]["image_url"]
|
||||||
|
used_seed = final_response["result"].get("seed", seed)
|
||||||
|
|
||||||
|
# Download and process image
|
||||||
|
image_response = requests.get(result_image_url)
|
||||||
|
result_image = Image.open(io.BytesIO(image_response.content)).convert("RGB")
|
||||||
|
result_array = np.array(result_image).astype(np.float32) / 255.0
|
||||||
|
result_tensor = torch.from_numpy(result_array) # shape: (H,W,C)
|
||||||
|
|
||||||
|
batch_results.append(result_tensor)
|
||||||
|
batch_seeds.append(used_seed)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ImageEnhanceNode] Skipping image {idx} due to error: {e}")
|
||||||
|
# Append fallback tensor with same size as input
|
||||||
|
fallback_array = np.array(pil_image).astype(np.float32) / 255.0
|
||||||
|
batch_results.append(torch.from_numpy(fallback_array))
|
||||||
|
batch_seeds.append(seed)
|
||||||
|
|
||||||
|
# Return list of tensors (not concatenated) + comma-separated seeds
|
||||||
|
combined_seeds = ",".join(map(str, batch_seeds))
|
||||||
|
return (batch_results, combined_seeds)
|
||||||
+97
-100
@@ -1,136 +1,133 @@
|
|||||||
import numpy as np
|
|
||||||
import requests
|
|
||||||
from PIL import Image
|
|
||||||
import io
|
import io
|
||||||
|
import requests
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from .common import deserialize_and_get_comfy_key, image_to_base64, preprocess_image, poll_status_until_completed
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
normalize_images_input,
|
||||||
|
poll_status_until_completed,
|
||||||
|
)
|
||||||
class ImageExpansionNode():
|
class ImageExpansionNode():
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(self):
|
def INPUT_TYPES(cls):
|
||||||
return {
|
return {
|
||||||
"required": {
|
"required": {
|
||||||
"image": ("IMAGE",), # Input image from another node
|
"images": ("IMAGE",),
|
||||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
},
|
},
|
||||||
|
|
||||||
"optional": {
|
"optional": {
|
||||||
"original_image_size": ("STRING",),
|
"original_image_size": ("STRING",),
|
||||||
"original_image_location": ("STRING",),
|
"original_image_location": ("STRING",),
|
||||||
"canvas_size": ("STRING", {"default": "1000, 1000"}),
|
"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"}),
|
"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": ""}),
|
"prompt": ("STRING", {"default": ""}),
|
||||||
"seed": ("INT", {"default": 681794}),
|
"seed": ("STRING", {"default": "681794"}), # <-- accepts seeds from Enhance
|
||||||
"negative_prompt": ("STRING", {"default": "Ugly, mutated"}),
|
"negative_prompt": ("STRING", {"default": "Ugly, mutated"}),
|
||||||
"prompt_content_moderation": ("BOOLEAN", {"default": False}),
|
"prompt_content_moderation": ("BOOLEAN", {"default": False}),
|
||||||
"preserve_alpha": ("BOOLEAN", {"default": True}),
|
"preserve_alpha": ("BOOLEAN", {"default": True}),
|
||||||
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
||||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("IMAGE",)
|
RETURN_TYPES = ("IMAGE",)
|
||||||
RETURN_NAMES = ("output_image",)
|
RETURN_NAMES = ("output_images",)
|
||||||
CATEGORY = "API Nodes"
|
CATEGORY = "API Nodes"
|
||||||
FUNCTION = "execute" # This is the method that will be executed
|
FUNCTION = "execute"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/expand" # Image Expansion API URL
|
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/expand"
|
||||||
|
|
||||||
# Define the execute method as expected by ComfyUI
|
def execute(
|
||||||
def execute(self, image,
|
self,
|
||||||
original_image_size,
|
images,
|
||||||
original_image_location,
|
original_image_size,
|
||||||
canvas_size,
|
original_image_location,
|
||||||
aspect_ratio,
|
canvas_size,
|
||||||
prompt,
|
aspect_ratio,
|
||||||
seed,
|
prompt,
|
||||||
negative_prompt,
|
seed,
|
||||||
prompt_content_moderation,
|
negative_prompt,
|
||||||
preserve_alpha,
|
prompt_content_moderation,
|
||||||
visual_input_content_moderation,
|
preserve_alpha,
|
||||||
visual_output_content_moderation,
|
visual_input_content_moderation,
|
||||||
api_key):
|
visual_output_content_moderation,
|
||||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
api_key
|
||||||
|
):
|
||||||
|
if api_key.strip() in ("", "BRIA_API_TOKEN"):
|
||||||
raise Exception("Please insert a valid API key.")
|
raise Exception("Please insert a valid API key.")
|
||||||
api_key = deserialize_and_get_comfy_key(api_key)
|
images = normalize_images_input(images)
|
||||||
|
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(",")] if original_image_size else ()
|
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 ()
|
original_image_location = [int(x.strip()) for x in original_image_location.split(",")] if original_image_location else ()
|
||||||
canvas_size = [int(x.strip()) for x in canvas_size.split(",")] if canvas_size else ()
|
|
||||||
|
|
||||||
if negative_prompt == "":
|
|
||||||
negative_prompt = " " # hack to avoid error in triton which expects non-empty string
|
|
||||||
|
|
||||||
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
# Prepare per-image seeds
|
||||||
if isinstance(image, torch.Tensor):
|
seed_values = [int(s.strip()) for s in seed.split(",")] if isinstance(seed, str) else [seed]
|
||||||
image = preprocess_image(image)
|
if len(seed_values) < len(images):
|
||||||
|
seed_values += [seed_values[-1]] * (len(images) - len(seed_values))
|
||||||
|
|
||||||
# Convert the image directly to Base64 string
|
if not negative_prompt:
|
||||||
image_base64 = image_to_base64(image)
|
negative_prompt = " "
|
||||||
if aspect_ratio and aspect_ratio != "None":
|
|
||||||
payload = {
|
|
||||||
"image": image_base64,
|
|
||||||
"aspect_ratio": aspect_ratio,
|
|
||||||
"prompt": prompt,
|
|
||||||
"negative_prompt": negative_prompt,
|
|
||||||
"seed": seed,
|
|
||||||
"prompt_content_moderation": prompt_content_moderation,
|
|
||||||
"preserve_alpha": preserve_alpha,
|
|
||||||
"visual_input_content_moderation": visual_input_content_moderation,
|
|
||||||
"visual_output_content_moderation": visual_output_content_moderation
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
payload = {
|
|
||||||
"image": image_base64,
|
|
||||||
"original_image_size": original_image_size,
|
|
||||||
"original_image_location": original_image_location,
|
|
||||||
"canvas_size": canvas_size,
|
|
||||||
"prompt": prompt,
|
|
||||||
"negative_prompt": negative_prompt,
|
|
||||||
"seed": seed,
|
|
||||||
"prompt_content_moderation": prompt_content_moderation,
|
|
||||||
"preserve_alpha": preserve_alpha,
|
|
||||||
"visual_input_content_moderation": visual_input_content_moderation,
|
|
||||||
"visual_output_content_moderation": visual_output_content_moderation
|
|
||||||
}
|
|
||||||
|
|
||||||
headers = {
|
batch_results = []
|
||||||
"Content-Type": "application/json",
|
|
||||||
"api_token": f"{api_key}"
|
for idx, pil_image in enumerate(images):
|
||||||
}
|
try:
|
||||||
|
image_base64 = image_to_base64(pil_image)
|
||||||
|
|
||||||
|
if aspect_ratio and aspect_ratio != "None":
|
||||||
|
payload = {
|
||||||
|
"image": image_base64,
|
||||||
|
"aspect_ratio": aspect_ratio,
|
||||||
|
"prompt": prompt,
|
||||||
|
"negative_prompt": negative_prompt,
|
||||||
|
"seed": seed_values[idx],
|
||||||
|
"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_values[idx],
|
||||||
|
"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 = bria_json_headers(api_key)
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
if response.status_code not in (200, 202):
|
||||||
|
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
|
||||||
|
|
||||||
if response.status_code == 200 or response.status_code == 202:
|
|
||||||
print('Initial image expansion request successful, polling for completion...')
|
|
||||||
response_dict = response.json()
|
response_dict = response.json()
|
||||||
status_url = response_dict.get('status_url')
|
status_url = response_dict.get("status_url")
|
||||||
request_id = response_dict.get('request_id')
|
|
||||||
|
|
||||||
if not status_url:
|
if not status_url:
|
||||||
raise Exception("No status_url returned from API")
|
raise Exception("No status_url returned from API")
|
||||||
|
|
||||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
|
||||||
|
|
||||||
# Poll status URL until completion
|
|
||||||
final_response = poll_status_until_completed(status_url, api_key)
|
|
||||||
|
|
||||||
# Get the result image URL
|
|
||||||
result_image_url = final_response['result']['image_url']
|
|
||||||
|
|
||||||
# Download and process the result image
|
|
||||||
image_response = requests.get(result_image_url)
|
|
||||||
result_image = Image.open(io.BytesIO(image_response.content))
|
|
||||||
result_image = result_image.convert("RGB")
|
|
||||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
|
||||||
result_image = torch.from_numpy(result_image)[None,]
|
|
||||||
|
|
||||||
return (result_image,)
|
|
||||||
else:
|
|
||||||
raise Exception(f"Error: API request failed with status code {response.status_code}: {response.text}")
|
|
||||||
|
|
||||||
except Exception as e:
|
print(f"ImageExpansionNode - Initial request successful for image {idx}, polling for completion...")
|
||||||
raise Exception(f"{e}")
|
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)).convert("RGB")
|
||||||
|
result_tensor = torch.from_numpy(np.array(result_image).astype(np.float32) / 255.0)
|
||||||
|
|
||||||
|
batch_results.append(result_tensor)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ImageExpansionNode] Skipping image {idx} due to error: {e}")
|
||||||
|
fallback_array = np.array(pil_image).astype(np.float32) / 255.0
|
||||||
|
batch_results.append(torch.from_numpy(fallback_array))
|
||||||
|
|
||||||
|
return (batch_results,)
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import torch
|
||||||
|
import torch.nn.functional as F
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image, ImageOps
|
||||||
|
|
||||||
|
try:
|
||||||
|
from folder_paths import get_input_directory
|
||||||
|
except Exception:
|
||||||
|
get_input_directory = None
|
||||||
|
|
||||||
|
|
||||||
|
IMG_EXTS = (".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tif", ".tiff")
|
||||||
|
|
||||||
|
|
||||||
|
def input_root() -> str:
|
||||||
|
return os.path.abspath(get_input_directory() if get_input_directory else "input")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_paths(value: str) -> List[str]:
|
||||||
|
if not value:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
data = json.loads(value)
|
||||||
|
if isinstance(data, list):
|
||||||
|
return [str(x) for x in data]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BriaMultiImageSelect:
|
||||||
|
"""
|
||||||
|
Select multiple images and return them as a list of PIL Images.
|
||||||
|
Images keep their original size.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"selected_paths": (
|
||||||
|
"STRING",
|
||||||
|
{
|
||||||
|
"multiline": True,
|
||||||
|
"default": "",
|
||||||
|
"placeholder": "Filled automatically by Select Images button",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("IMAGE", "STRING")
|
||||||
|
RETURN_NAMES = ("images", "filenames")
|
||||||
|
FUNCTION = "load"
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
|
||||||
|
def load(self, selected_paths: str):
|
||||||
|
paths = parse_paths(selected_paths)
|
||||||
|
if not paths:
|
||||||
|
raise RuntimeError("BriaMultiImageSelect: No images selected")
|
||||||
|
|
||||||
|
root = input_root()
|
||||||
|
pil_images: List[Image.Image] = []
|
||||||
|
names: List[str] = []
|
||||||
|
|
||||||
|
for rel in paths:
|
||||||
|
abs_path = os.path.join(root, rel)
|
||||||
|
if not abs_path.lower().endswith(IMG_EXTS):
|
||||||
|
continue
|
||||||
|
if not os.path.isfile(abs_path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
pil_images.append(Image.open(abs_path))
|
||||||
|
names.append(os.path.splitext(os.path.basename(rel))[0])
|
||||||
|
|
||||||
|
if not pil_images:
|
||||||
|
raise RuntimeError("BriaMultiImageSelect: No valid images found")
|
||||||
|
|
||||||
|
filenames = ", ".join(names)
|
||||||
|
return (pil_images, filenames)
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import requests
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
poll_status_until_completed,
|
||||||
|
postprocess_image,
|
||||||
|
preprocess_image,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ProductIntegrateNode:
|
||||||
|
"""Product Integrate Node - Integrate a single product into a background scene"""
|
||||||
|
|
||||||
|
api_url = "https://engine.prod.bria-api.com/v2/image/edit/product/integrate"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
|
"scene": ("IMAGE",),
|
||||||
|
"product_image": ("IMAGE",),
|
||||||
|
"x_coordinate": ("INT", {"default": 0, "min": 0, "max": 10000}),
|
||||||
|
"y_coordinate": ("INT", {"default": 0, "min": 0, "max": 10000}),
|
||||||
|
"width": ("INT", {"default": 512, "min": 1, "max": 10000}),
|
||||||
|
"height": ("INT", {"default": 512, "min": 1, "max": 10000}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"seed": ("STRING", {"default": "123456"}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("IMAGE", "INT")
|
||||||
|
RETURN_NAMES = ("IMAGE", "seed")
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
FUNCTION = "execute"
|
||||||
|
|
||||||
|
def _validate_token(self, api_token: str):
|
||||||
|
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||||
|
raise Exception("Please insert a valid API token.")
|
||||||
|
|
||||||
|
def _build_payload(
|
||||||
|
self,
|
||||||
|
scene_image,
|
||||||
|
product_image,
|
||||||
|
x_coordinate,
|
||||||
|
y_coordinate,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
seed,
|
||||||
|
):
|
||||||
|
payload = {
|
||||||
|
"scene": image_to_base64(scene_image),
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"image": image_to_base64(product_image),
|
||||||
|
"coordinates": {
|
||||||
|
"x": x_coordinate,
|
||||||
|
"y": y_coordinate,
|
||||||
|
"width": width,
|
||||||
|
"height": height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"seed": int(seed),
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
self,
|
||||||
|
api_token,
|
||||||
|
scene,
|
||||||
|
product_image,
|
||||||
|
x_coordinate,
|
||||||
|
y_coordinate,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
seed,
|
||||||
|
):
|
||||||
|
self._validate_token(api_token)
|
||||||
|
# Process single scene image
|
||||||
|
if isinstance(scene, torch.Tensor):
|
||||||
|
processed_scene = preprocess_image(scene)
|
||||||
|
if isinstance(product_image, torch.Tensor):
|
||||||
|
processed_product = preprocess_image(product_image)
|
||||||
|
|
||||||
|
payload = self._build_payload(
|
||||||
|
processed_scene,
|
||||||
|
processed_product,
|
||||||
|
x_coordinate,
|
||||||
|
y_coordinate,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
seed,
|
||||||
|
)
|
||||||
|
|
||||||
|
headers = bria_json_headers(api_token)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code in (200, 202):
|
||||||
|
print(
|
||||||
|
f"Initial product integrate 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_token)
|
||||||
|
|
||||||
|
result = final_response.get("result", {})
|
||||||
|
result_image_url = result.get("image_url")
|
||||||
|
used_seed = result.get("seed", seed)
|
||||||
|
|
||||||
|
image_response = requests.get(result_image_url)
|
||||||
|
result_image = postprocess_image(image_response.content)
|
||||||
|
|
||||||
|
return (result_image, used_seed)
|
||||||
|
else:
|
||||||
|
raise Exception(
|
||||||
|
f"API request failed with status code {response.status_code} {response.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"[ProductIntegrateNode] Error: {e}")
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
from .common import bria_json_headers, poll_status_until_completed, postprocess_image
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class RefineImageLiteNodeV2:
|
||||||
|
"""Lite Refine Image Node"""
|
||||||
|
|
||||||
|
api_url = "https://engine.prod.bria-api.com/v2/structured_prompt/generate/lite"
|
||||||
|
generate_api_url = "https://engine.prod.bria-api.com/v2/image/generate/lite"
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
|
"prompt": ("STRING",),
|
||||||
|
"structured_prompt": ("STRING",),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"model_version": (["FIBO"], {"default": "FIBO"}),
|
||||||
|
"aspect_ratio": (
|
||||||
|
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
|
||||||
|
{"default": "1:1"},
|
||||||
|
),
|
||||||
|
"steps_num": (
|
||||||
|
"INT",
|
||||||
|
{
|
||||||
|
"default": 8,
|
||||||
|
"min": 8,
|
||||||
|
"max": 30,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
"guidance_scale": (
|
||||||
|
"INT",
|
||||||
|
{
|
||||||
|
"default": 5,
|
||||||
|
"min": 3,
|
||||||
|
"max": 5,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
"seed": ("INT", {"default": 123456}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RETURN_TYPES = ("IMAGE", "STRING", "INT")
|
||||||
|
RETURN_NAMES = ("image", "structured_prompt", "seed")
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
FUNCTION = "execute"
|
||||||
|
|
||||||
|
def _validate_token(self, api_token: str):
|
||||||
|
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||||
|
raise Exception("Please insert a valid API token.")
|
||||||
|
|
||||||
|
def _build_payload(
|
||||||
|
self,
|
||||||
|
prompt,
|
||||||
|
structured_prompt,
|
||||||
|
model_version,
|
||||||
|
aspect_ratio,
|
||||||
|
steps_num,
|
||||||
|
guidance_scale,
|
||||||
|
seed,
|
||||||
|
):
|
||||||
|
payload = {
|
||||||
|
"prompt": prompt,
|
||||||
|
"model_version": model_version,
|
||||||
|
"aspect_ratio": aspect_ratio,
|
||||||
|
"steps_num": steps_num,
|
||||||
|
"guidance_scale": guidance_scale,
|
||||||
|
"seed": seed,
|
||||||
|
}
|
||||||
|
if structured_prompt:
|
||||||
|
payload["structured_prompt"] = structured_prompt
|
||||||
|
|
||||||
|
return payload
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
self,
|
||||||
|
api_token,
|
||||||
|
prompt,
|
||||||
|
structured_prompt,
|
||||||
|
model_version,
|
||||||
|
aspect_ratio,
|
||||||
|
steps_num,
|
||||||
|
guidance_scale,
|
||||||
|
seed
|
||||||
|
):
|
||||||
|
self._validate_token(api_token)
|
||||||
|
payload = self._build_payload(
|
||||||
|
prompt,
|
||||||
|
structured_prompt,
|
||||||
|
model_version,
|
||||||
|
aspect_ratio,
|
||||||
|
steps_num,
|
||||||
|
guidance_scale,
|
||||||
|
seed,
|
||||||
|
)
|
||||||
|
headers = bria_json_headers(api_token)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code in (200, 202):
|
||||||
|
print(f"Initial refine request successful to {self.api_url}, polling for completion...")
|
||||||
|
response_dict = response.json()
|
||||||
|
status_url = response_dict.get("status_url")
|
||||||
|
request_id = response_dict.get("request_id")
|
||||||
|
|
||||||
|
if not status_url:
|
||||||
|
raise Exception("No status_url returned from API")
|
||||||
|
|
||||||
|
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||||
|
|
||||||
|
final_response = poll_status_until_completed(status_url, api_token)
|
||||||
|
|
||||||
|
result = final_response.get("result", {})
|
||||||
|
structured_prompt = result.get("structured_prompt", "")
|
||||||
|
used_seed = result.get("seed", seed)
|
||||||
|
|
||||||
|
# Step 2 to call genearte image
|
||||||
|
payloadForImageGenetrate = {
|
||||||
|
"prompt": prompt,
|
||||||
|
"structured_prompt":structured_prompt,
|
||||||
|
"model_version": model_version,
|
||||||
|
"aspect_ratio": aspect_ratio,
|
||||||
|
"steps_num": steps_num,
|
||||||
|
"guidance_scale": guidance_scale,
|
||||||
|
"seed": used_seed,
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = bria_json_headers(api_token)
|
||||||
|
|
||||||
|
response = requests.post(self.generate_api_url, json=payloadForImageGenetrate, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code in (200, 202):
|
||||||
|
print(
|
||||||
|
f"Initial request successful to {self.generate_api_url}, polling for completion..."
|
||||||
|
)
|
||||||
|
response_dict = response.json()
|
||||||
|
status_url = response_dict.get("status_url")
|
||||||
|
request_id = response_dict.get("request_id")
|
||||||
|
|
||||||
|
if not status_url:
|
||||||
|
raise Exception("No status_url returned from API")
|
||||||
|
|
||||||
|
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||||
|
|
||||||
|
final_response = poll_status_until_completed(status_url, api_token)
|
||||||
|
|
||||||
|
result = final_response.get("result", {})
|
||||||
|
result_image_url = result.get("image_url")
|
||||||
|
structured_prompt = result.get("structured_prompt", "")
|
||||||
|
used_seed = result.get("seed")
|
||||||
|
|
||||||
|
image_response = requests.get(result_image_url)
|
||||||
|
result_image = postprocess_image(image_response.content)
|
||||||
|
|
||||||
|
return (result_image, structured_prompt, used_seed)
|
||||||
|
|
||||||
|
raise Exception(
|
||||||
|
f"Error: API request failed with status code {response.status_code} {response.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"{e}")
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import requests
|
import requests
|
||||||
from .common import deserialize_and_get_comfy_key, poll_status_until_completed, postprocess_image
|
|
||||||
|
from .common import bria_json_headers, poll_status_until_completed, postprocess_image
|
||||||
|
|
||||||
|
|
||||||
class _BaseRefineImageNodeV2:
|
class RefineImageNodeV2:
|
||||||
"""Base class for refine image nodes (standard & pro)."""
|
"""Standard Refine Image Node"""
|
||||||
|
|
||||||
api_url = None # Must be overridden by subclasses
|
|
||||||
generate_api_url = None
|
|
||||||
|
|
||||||
|
api_url = "https://engine.prod.bria-api.com/v2/structured_prompt/generate" # Must be overridden by subclasses
|
||||||
|
generate_api_url = "https://engine.prod.bria-api.com/v2/image/generate"
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
def INPUT_TYPES(cls):
|
||||||
return {
|
return {
|
||||||
@@ -18,17 +18,32 @@ class _BaseRefineImageNodeV2:
|
|||||||
},
|
},
|
||||||
"optional": {
|
"optional": {
|
||||||
"model_version": (["FIBO"], {"default": "FIBO"}),
|
"model_version": (["FIBO"], {"default": "FIBO"}),
|
||||||
"negative_prompt": ("STRING", {"default": ""}),
|
|
||||||
"aspect_ratio": (
|
"aspect_ratio": (
|
||||||
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
|
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
|
||||||
{"default": "1:1"},
|
{"default": "1:1"},
|
||||||
),
|
),
|
||||||
"steps_num": ("INT", {"default": 50, "min": 20, "max": 50}),
|
"steps_num": (
|
||||||
"guidance_scale": ("INT", {"default": 5, "min": 3, "max": 5}),
|
"INT",
|
||||||
|
{
|
||||||
|
"default": 50,
|
||||||
|
"min": 35,
|
||||||
|
"max": 50,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
"guidance_scale": (
|
||||||
|
"INT",
|
||||||
|
{
|
||||||
|
"default": 5,
|
||||||
|
"min": 3,
|
||||||
|
"max": 5,
|
||||||
|
},
|
||||||
|
),
|
||||||
"seed": ("INT", {"default": 123456}),
|
"seed": ("INT", {"default": 123456}),
|
||||||
|
"negative_prompt": ("STRING", {"default": ""}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
RETURN_TYPES = ("IMAGE", "STRING", "INT")
|
RETURN_TYPES = ("IMAGE", "STRING", "INT")
|
||||||
RETURN_NAMES = ("image", "structured_prompt", "seed")
|
RETURN_NAMES = ("image", "structured_prompt", "seed")
|
||||||
CATEGORY = "API Nodes"
|
CATEGORY = "API Nodes"
|
||||||
@@ -43,22 +58,23 @@ class _BaseRefineImageNodeV2:
|
|||||||
prompt,
|
prompt,
|
||||||
structured_prompt,
|
structured_prompt,
|
||||||
model_version,
|
model_version,
|
||||||
negative_prompt,
|
|
||||||
aspect_ratio,
|
aspect_ratio,
|
||||||
steps_num,
|
steps_num,
|
||||||
guidance_scale,
|
guidance_scale,
|
||||||
seed,
|
seed,
|
||||||
):
|
):
|
||||||
return {
|
payload = {
|
||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
"model_version": model_version,
|
"model_version": model_version,
|
||||||
"negative_prompt": negative_prompt,
|
|
||||||
"aspect_ratio": aspect_ratio,
|
"aspect_ratio": aspect_ratio,
|
||||||
"steps_num": steps_num,
|
"steps_num": steps_num,
|
||||||
"guidance_scale": guidance_scale,
|
"guidance_scale": guidance_scale,
|
||||||
"seed": seed,
|
"seed": seed,
|
||||||
"structured_prompt": structured_prompt,
|
|
||||||
}
|
}
|
||||||
|
if structured_prompt:
|
||||||
|
payload["structured_prompt"] = structured_prompt
|
||||||
|
|
||||||
|
return payload
|
||||||
|
|
||||||
def execute(
|
def execute(
|
||||||
self,
|
self,
|
||||||
@@ -66,25 +82,23 @@ class _BaseRefineImageNodeV2:
|
|||||||
prompt,
|
prompt,
|
||||||
structured_prompt,
|
structured_prompt,
|
||||||
model_version,
|
model_version,
|
||||||
negative_prompt,
|
|
||||||
aspect_ratio,
|
aspect_ratio,
|
||||||
steps_num,
|
steps_num,
|
||||||
guidance_scale,
|
guidance_scale,
|
||||||
seed,
|
seed,
|
||||||
|
negative_prompt=None,
|
||||||
):
|
):
|
||||||
self._validate_token(api_token)
|
self._validate_token(api_token)
|
||||||
payload = self._build_payload(
|
payload = self._build_payload(
|
||||||
prompt,
|
prompt,
|
||||||
structured_prompt,
|
structured_prompt,
|
||||||
model_version,
|
model_version,
|
||||||
negative_prompt,
|
|
||||||
aspect_ratio,
|
aspect_ratio,
|
||||||
steps_num,
|
steps_num,
|
||||||
guidance_scale,
|
guidance_scale,
|
||||||
seed,
|
seed,
|
||||||
)
|
)
|
||||||
api_token = deserialize_and_get_comfy_key(api_token)
|
headers = bria_json_headers(api_token)
|
||||||
headers = {"Content-Type": "application/json", "api_token": api_token}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
@@ -111,13 +125,14 @@ class _BaseRefineImageNodeV2:
|
|||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
"structured_prompt":structured_prompt,
|
"structured_prompt":structured_prompt,
|
||||||
"model_version": model_version,
|
"model_version": model_version,
|
||||||
"negative_prompt": negative_prompt,
|
|
||||||
"aspect_ratio": aspect_ratio,
|
"aspect_ratio": aspect_ratio,
|
||||||
"steps_num": steps_num,
|
"steps_num": steps_num,
|
||||||
"guidance_scale": guidance_scale,
|
"guidance_scale": guidance_scale,
|
||||||
"seed": used_seed,
|
"seed": used_seed,
|
||||||
|
"negative_prompt":negative_prompt
|
||||||
}
|
}
|
||||||
headers = {"Content-Type": "application/json", "api_token": api_token}
|
|
||||||
|
headers = bria_json_headers(api_token)
|
||||||
|
|
||||||
response = requests.post(self.generate_api_url, json=payloadForImageGenetrate, headers=headers)
|
response = requests.post(self.generate_api_url, json=payloadForImageGenetrate, headers=headers)
|
||||||
|
|
||||||
@@ -152,10 +167,3 @@ class _BaseRefineImageNodeV2:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"{e}")
|
raise Exception(f"{e}")
|
||||||
|
|
||||||
|
|
||||||
class RefineImageNodeV2(_BaseRefineImageNodeV2):
|
|
||||||
"""Standard Refine Image Node"""
|
|
||||||
def __init__(self):
|
|
||||||
self.api_url = "https://engine.prod.bria-api.com/v2/structured_prompt/generate"
|
|
||||||
self.generate_api_url = "https://engine.prod.bria-api.com/v2/image/generate"
|
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .common import deserialize_and_get_comfy_key, postprocess_image, preprocess_image, image_to_base64
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
postprocess_image,
|
||||||
|
preprocess_image,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ReimagineNode():
|
class ReimagineNode():
|
||||||
@@ -38,7 +43,6 @@ class ReimagineNode():
|
|||||||
tailored_model_id=None, tailored_model_influence=None, tailored_generation_prefix=None,
|
tailored_model_id=None, tailored_model_influence=None, tailored_generation_prefix=None,
|
||||||
content_moderation=0,
|
content_moderation=0,
|
||||||
):
|
):
|
||||||
api_key = deserialize_and_get_comfy_key(api_key)
|
|
||||||
payload = {
|
payload = {
|
||||||
"prompt": tailored_generation_prefix + prompt,
|
"prompt": tailored_generation_prefix + prompt,
|
||||||
"num_results": 1,
|
"num_results": 1,
|
||||||
@@ -59,7 +63,7 @@ class ReimagineNode():
|
|||||||
response = requests.post(
|
response = requests.post(
|
||||||
self.api_url,
|
self.api_url,
|
||||||
json=payload,
|
json=payload,
|
||||||
headers={"api_token": api_key}
|
headers=bria_json_headers(api_key),
|
||||||
)
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
response_dict = response.json()
|
response_dict = response.json()
|
||||||
|
|||||||
@@ -1,91 +1,93 @@
|
|||||||
import numpy as np
|
|
||||||
import requests
|
|
||||||
from PIL import Image
|
|
||||||
import io
|
import io
|
||||||
|
import requests
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from .common import deserialize_and_get_comfy_key, preprocess_image, image_to_base64, poll_status_until_completed
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
normalize_images_input,
|
||||||
|
poll_status_until_completed,
|
||||||
|
)
|
||||||
|
|
||||||
class RemoveForegroundNode():
|
class RemoveForegroundNode():
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(self):
|
def INPUT_TYPES(cls):
|
||||||
return {
|
return {
|
||||||
"required": {
|
"required": {
|
||||||
"image": ("IMAGE",), # Input image from another node
|
"images": ("IMAGE",),
|
||||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
},
|
},
|
||||||
"optional": {
|
"optional": {
|
||||||
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
||||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||||
"preserve_alpha": ("BOOLEAN", {"default": True}),
|
"preserve_alpha": ("BOOLEAN", {"default": True}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("IMAGE",)
|
RETURN_TYPES = ("IMAGE",)
|
||||||
RETURN_NAMES = ("output_image",)
|
RETURN_NAMES = ("output_images",)
|
||||||
CATEGORY = "API Nodes"
|
CATEGORY = "API Nodes"
|
||||||
FUNCTION = "execute" # This is the method that will be executed
|
FUNCTION = "execute"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/erase_foreground" # remove foreground API URL
|
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/erase_foreground"
|
||||||
|
|
||||||
# Define the execute method as expected by ComfyUI
|
def execute(
|
||||||
def execute(self, image, visual_input_content_moderation, visual_output_content_moderation, preserve_alpha, api_key):
|
self,
|
||||||
|
images,
|
||||||
|
visual_input_content_moderation,
|
||||||
|
visual_output_content_moderation,
|
||||||
|
preserve_alpha,
|
||||||
|
api_key
|
||||||
|
):
|
||||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||||
raise Exception("Please insert a valid API key.")
|
raise Exception("Please insert a valid API key.")
|
||||||
api_key = deserialize_and_get_comfy_key(api_key)
|
images = normalize_images_input(images)
|
||||||
|
batch_results = []
|
||||||
|
|
||||||
# Check if image is tensor, if so, convert to NumPy array
|
for idx, pil_image in enumerate(images):
|
||||||
if isinstance(image, torch.Tensor):
|
try:
|
||||||
image = preprocess_image(image)
|
image_base64 = image_to_base64(pil_image)
|
||||||
|
|
||||||
# Prepare the API request payload
|
payload = {
|
||||||
# temporary save the image to /tmp
|
"image": image_base64,
|
||||||
# temp_img_path = "/tmp/temp_img.jpeg"
|
"visual_input_content_moderation": visual_input_content_moderation,
|
||||||
# image.save(temp_img_path, format="JPEG")
|
"visual_output_content_moderation": visual_output_content_moderation,
|
||||||
|
"preserve_alpha": preserve_alpha
|
||||||
# files=[('file',('temp_img.jpeg', open(temp_img_path, 'rb'),'image/jpeg'))
|
}
|
||||||
# ]
|
|
||||||
payload = {
|
|
||||||
"image": image_to_base64(image),
|
|
||||||
"visual_input_content_moderation": visual_input_content_moderation,
|
|
||||||
"visual_output_content_moderation":visual_output_content_moderation,
|
|
||||||
"preserve_alpha": preserve_alpha
|
|
||||||
}
|
|
||||||
|
|
||||||
headers = {
|
headers = bria_json_headers(api_key)
|
||||||
"Content-Type": "application/json",
|
|
||||||
"api_token": f"{api_key}"
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
if response.status_code not in (200, 202):
|
||||||
|
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||||
|
|
||||||
if response.status_code == 200 or response.status_code == 202:
|
|
||||||
print('Initial request successful, polling for completion...')
|
|
||||||
response_dict = response.json()
|
response_dict = response.json()
|
||||||
status_url = response_dict.get('status_url')
|
status_url = response_dict.get("status_url")
|
||||||
request_id = response_dict.get('request_id')
|
|
||||||
|
|
||||||
if not status_url:
|
if not status_url:
|
||||||
raise Exception("No status_url returned from API")
|
raise Exception("No status_url returned from API")
|
||||||
|
|
||||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
|
||||||
|
|
||||||
# Poll status URL until completion
|
|
||||||
final_response = poll_status_until_completed(status_url, api_key)
|
|
||||||
|
|
||||||
# Get the result image URL
|
|
||||||
result_image_url = final_response['result']['image_url']
|
|
||||||
|
|
||||||
image_response = requests.get(result_image_url)
|
|
||||||
result_image = Image.open(io.BytesIO(image_response.content))
|
|
||||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
|
||||||
result_image = torch.from_numpy(result_image)[None,]
|
|
||||||
return (result_image,)
|
|
||||||
else:
|
|
||||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
|
||||||
|
|
||||||
except Exception as e:
|
print(f"RemoveForegroundNode - Initial request successful for image {idx}, polling for completion...")
|
||||||
raise Exception(f"{e}")
|
final_response = poll_status_until_completed(status_url, api_key)
|
||||||
|
result_image_url = final_response["result"]["image_url"]
|
||||||
|
|
||||||
|
# Download result
|
||||||
|
image_response = requests.get(result_image_url)
|
||||||
|
result_image = Image.open(io.BytesIO(image_response.content)).convert("RGB")
|
||||||
|
|
||||||
|
# Convert to float32 tensor (H, W, C)
|
||||||
|
result_array = np.array(result_image).astype(np.float32) / 255.0
|
||||||
|
result_tensor = torch.from_numpy(result_array)
|
||||||
|
|
||||||
|
batch_results.append(result_tensor)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[RemoveForegroundNode] Skipping image {idx} due to error: {e}")
|
||||||
|
# fallback: use original image as tensor
|
||||||
|
fallback_array = np.array(pil_image).astype(np.float32) / 255.0
|
||||||
|
batch_results.append(torch.from_numpy(fallback_array))
|
||||||
|
|
||||||
|
# Return list of tensors
|
||||||
|
return (batch_results,)
|
||||||
|
|||||||
+90
-92
@@ -1,124 +1,122 @@
|
|||||||
import numpy as np
|
|
||||||
import requests
|
|
||||||
from PIL import Image
|
|
||||||
import io
|
import io
|
||||||
|
import requests
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from .common import deserialize_and_get_comfy_key, image_to_base64, preprocess_image, preprocess_mask, poll_status_until_completed
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
normalize_images_input,
|
||||||
|
poll_status_until_completed,
|
||||||
|
)
|
||||||
|
|
||||||
class ReplaceBgNode():
|
class ReplaceBgNode():
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(self):
|
def INPUT_TYPES(cls):
|
||||||
return {
|
return {
|
||||||
"required": {
|
"required": {
|
||||||
"image": ("IMAGE",), # Input image from another node
|
"images": ("IMAGE",),
|
||||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
},
|
},
|
||||||
"optional": {
|
"optional": {
|
||||||
"mode": (["base", "fast", "high_control"], {"default": "base"}),
|
"mode": (["base", "fast", "high_control"], {"default": "base"}),
|
||||||
"prompt": ("STRING",),
|
"prompt": ("STRING", {"default": ""}),
|
||||||
"ref_images": ("IMAGE",),
|
"ref_images": ("IMAGE",),
|
||||||
"refine_prompt": ("BOOLEAN", {"default": True}),
|
"refine_prompt": ("BOOLEAN", {"default": True}),
|
||||||
"enhance_ref_images": ("BOOLEAN", {"default": True}),
|
"enhance_ref_images": ("BOOLEAN", {"default": True}),
|
||||||
"original_quality": ("BOOLEAN", {"default": False}),
|
"original_quality": ("BOOLEAN", {"default": False}),
|
||||||
"negative_prompt": ("STRING", {"default": None}),
|
"negative_prompt": ("STRING", {"default": None}),
|
||||||
"seed": ("INT", {"default": 681794}),
|
"seed": ("STRING", {"default": "681794"}), # Accept comma-separated seeds
|
||||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||||
"prompt_content_moderation": ("BOOLEAN", {"default": False}),
|
"prompt_content_moderation": ("BOOLEAN", {"default": False}),
|
||||||
"force_background_detection": ("BOOLEAN", {"default": False}),
|
"force_background_detection": ("BOOLEAN", {"default": False}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("IMAGE",)
|
RETURN_TYPES = ("IMAGE",)
|
||||||
RETURN_NAMES = ("output_image",)
|
RETURN_NAMES = ("output_images",)
|
||||||
CATEGORY = "API Nodes"
|
CATEGORY = "API Nodes"
|
||||||
FUNCTION = "execute" # This is the method that will be executed
|
FUNCTION = "execute"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/replace_background" # Replace BG API URL
|
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/replace_background"
|
||||||
|
|
||||||
# Define the execute method as expected by ComfyUI
|
def execute(
|
||||||
def execute(self, image, mode,
|
self,
|
||||||
refine_prompt,
|
images,
|
||||||
original_quality,
|
mode,
|
||||||
negative_prompt,
|
refine_prompt,
|
||||||
seed,
|
original_quality,
|
||||||
api_key,
|
negative_prompt,
|
||||||
visual_output_content_moderation,
|
seed,
|
||||||
prompt_content_moderation,
|
api_key,
|
||||||
enhance_ref_images,
|
visual_output_content_moderation,
|
||||||
force_background_detection,
|
prompt_content_moderation,
|
||||||
prompt=None,
|
enhance_ref_images,
|
||||||
ref_images=None,):
|
force_background_detection,
|
||||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
prompt=None,
|
||||||
|
ref_images=None
|
||||||
|
):
|
||||||
|
if api_key.strip() in ("", "BRIA_API_TOKEN"):
|
||||||
raise Exception("Please insert a valid API key.")
|
raise Exception("Please insert a valid API key.")
|
||||||
api_key = deserialize_and_get_comfy_key(api_key)
|
images = normalize_images_input(images)
|
||||||
|
|
||||||
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
# Normalize reference images
|
||||||
if isinstance(image, torch.Tensor):
|
ref_images_base64 = []
|
||||||
image = preprocess_image(image)
|
|
||||||
|
|
||||||
# Convert the image to Base64 string
|
|
||||||
image_base64 = image_to_base64(image)
|
|
||||||
|
|
||||||
if ref_images is not None:
|
if ref_images is not None:
|
||||||
ref_images = preprocess_image(ref_images)
|
ref_images_list = normalize_images_input(ref_images)
|
||||||
ref_images = [image_to_base64(ref_images)]
|
ref_images_base64 = [image_to_base64(img) for img in ref_images_list]
|
||||||
else:
|
|
||||||
ref_images=[]
|
|
||||||
|
|
||||||
# Prepare the API request payload for v2 API
|
# Prepare per-image seeds
|
||||||
payload = {
|
seed_values = [int(s.strip()) for s in seed.split(",")] if isinstance(seed, str) else [seed]
|
||||||
"image": image_base64,
|
if len(seed_values) < len(images):
|
||||||
"mode": mode,
|
seed_values += [seed_values[-1]] * (len(images) - len(seed_values))
|
||||||
"prompt": prompt,
|
|
||||||
"ref_images":ref_images,
|
|
||||||
"refine_prompt": refine_prompt,
|
|
||||||
"original_quality": original_quality,
|
|
||||||
"negative_prompt": negative_prompt,
|
|
||||||
"seed": seed,
|
|
||||||
"prompt_content_moderation": prompt_content_moderation,
|
|
||||||
"visual_output_content_moderation":visual_output_content_moderation,
|
|
||||||
"enhance_ref_images":enhance_ref_images,
|
|
||||||
"force_background_detection": force_background_detection
|
|
||||||
}
|
|
||||||
|
|
||||||
headers = {
|
batch_results = []
|
||||||
"Content-Type": "application/json",
|
|
||||||
"api_token": f"{api_key}"
|
for idx, pil_image in enumerate(images):
|
||||||
}
|
try:
|
||||||
|
image_base64 = image_to_base64(pil_image)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"image": image_base64,
|
||||||
|
"mode": mode,
|
||||||
|
"prompt": prompt,
|
||||||
|
"ref_images": ref_images_base64,
|
||||||
|
"refine_prompt": refine_prompt,
|
||||||
|
"original_quality": original_quality,
|
||||||
|
"negative_prompt": negative_prompt,
|
||||||
|
"seed": seed_values[idx],
|
||||||
|
"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 = bria_json_headers(api_key)
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
if response.status_code not in (200, 202):
|
||||||
|
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
|
||||||
|
|
||||||
if response.status_code == 200 or response.status_code == 202:
|
|
||||||
print('Initial replace background request successful, polling for completion...')
|
|
||||||
response_dict = response.json()
|
response_dict = response.json()
|
||||||
status_url = response_dict.get('status_url')
|
status_url = response_dict.get("status_url")
|
||||||
request_id = response_dict.get('request_id')
|
|
||||||
|
|
||||||
if not status_url:
|
if not status_url:
|
||||||
raise Exception("No status_url returned from API")
|
raise Exception("No status_url returned from API")
|
||||||
|
|
||||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
|
||||||
|
|
||||||
# Poll status URL until completion
|
|
||||||
final_response = poll_status_until_completed(status_url, api_key)
|
|
||||||
|
|
||||||
# Get the result image URL
|
|
||||||
result_image_url = final_response['result']['image_url']
|
|
||||||
|
|
||||||
# Download and process the result image
|
|
||||||
image_response = requests.get(result_image_url)
|
|
||||||
result_image = Image.open(io.BytesIO(image_response.content))
|
|
||||||
result_image = result_image.convert("RGB")
|
|
||||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
|
||||||
result_image = torch.from_numpy(result_image)[None,]
|
|
||||||
|
|
||||||
return (result_image,)
|
|
||||||
else:
|
|
||||||
raise Exception(f"Error: API request failed with status code {response.status_code}{response.text}")
|
|
||||||
|
|
||||||
except Exception as e:
|
print(f"ReplaceBgNode - Initial request successful for image {idx}, polling for completion...")
|
||||||
raise Exception(f"{e}")
|
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)).convert("RGB")
|
||||||
|
result_tensor = torch.from_numpy(np.array(result_image).astype(np.float32) / 255.0)
|
||||||
|
|
||||||
|
batch_results.append(result_tensor)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ReplaceBgNode] Skipping image {idx} due to error: {e}")
|
||||||
|
fallback_array = np.array(pil_image).astype(np.float32) / 255.0
|
||||||
|
batch_results.append(torch.from_numpy(fallback_array))
|
||||||
|
|
||||||
|
return (batch_results,)
|
||||||
|
|||||||
+54
-52
@@ -1,87 +1,89 @@
|
|||||||
import numpy as np
|
|
||||||
import requests
|
|
||||||
from PIL import Image
|
|
||||||
import io
|
import io
|
||||||
|
import requests
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from .common import deserialize_and_get_comfy_key, preprocess_image, image_to_base64, poll_status_until_completed
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
normalize_images_input,
|
||||||
|
poll_status_until_completed,
|
||||||
|
to_pil_safe,
|
||||||
|
)
|
||||||
|
|
||||||
class RmbgNode():
|
class RmbgNode():
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(self):
|
def INPUT_TYPES(cls):
|
||||||
return {
|
return {
|
||||||
"required": {
|
"required": {
|
||||||
"image": ("IMAGE",), # Input image from another node
|
"images": ("IMAGE",), # Accepts list of PIL Images or single tensor
|
||||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
},
|
},
|
||||||
"optional": {
|
"optional": {
|
||||||
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
||||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||||
"preserve_alpha": ("BOOLEAN", {"default": True}),
|
"preserve_alpha": ("BOOLEAN", {"default": True}),
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("IMAGE",)
|
RETURN_TYPES = ("IMAGE",)
|
||||||
RETURN_NAMES = ("output_image",)
|
RETURN_NAMES = ("output_images",)
|
||||||
CATEGORY = "API Nodes"
|
CATEGORY = "API Nodes"
|
||||||
FUNCTION = "execute" # This is the method that will be executed
|
FUNCTION = "execute"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/remove_background" # RMBG API URL
|
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/remove_background"
|
||||||
|
|
||||||
# Define the execute method as expected by ComfyUI
|
def execute(self, images, visual_input_content_moderation, visual_output_content_moderation, preserve_alpha, 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":
|
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||||
raise Exception("Please insert a valid API key.")
|
raise Exception("Please insert a valid API key.")
|
||||||
api_key = deserialize_and_get_comfy_key(api_key)
|
# Normalize input to list of PIL images
|
||||||
# Check if image is tensor, if so, convert to NumPy array
|
images = normalize_images_input(images)
|
||||||
if isinstance(image, torch.Tensor):
|
|
||||||
image = preprocess_image(image)
|
|
||||||
|
|
||||||
# Convert image to base64 for the new API format
|
batch_results = []
|
||||||
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 = {
|
for idx, pil_image in enumerate(images):
|
||||||
"Content-Type": "application/json",
|
try:
|
||||||
"api_token": f"{api_key}"
|
image_base64 = image_to_base64(pil_image)
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
payload = {
|
||||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
"image": image_base64,
|
||||||
|
"visual_input_content_moderation": visual_input_content_moderation,
|
||||||
if response.status_code == 200 or response.status_code == 202:
|
"visual_output_content_moderation": visual_output_content_moderation,
|
||||||
print('Initial RMBG request successful, polling for completion...')
|
"preserve_alpha": preserve_alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = bria_json_headers(api_key)
|
||||||
|
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
if response.status_code not in (200, 202):
|
||||||
|
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||||
|
|
||||||
|
# Poll until completion
|
||||||
response_dict = response.json()
|
response_dict = response.json()
|
||||||
|
|
||||||
status_url = response_dict.get('status_url')
|
status_url = response_dict.get('status_url')
|
||||||
request_id = response_dict.get('request_id')
|
|
||||||
|
|
||||||
if not status_url:
|
if not status_url:
|
||||||
raise Exception("No status_url returned from API")
|
raise Exception("No status_url returned from API")
|
||||||
|
|
||||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
print(f"RmbgNode - Initial request successful for image {idx}, polling for completion...")
|
||||||
|
|
||||||
final_response = poll_status_until_completed(status_url, api_key)
|
final_response = poll_status_until_completed(status_url, api_key)
|
||||||
|
|
||||||
# Get the result image URL
|
|
||||||
result_image_url = final_response['result']['image_url']
|
result_image_url = final_response['result']['image_url']
|
||||||
|
|
||||||
# Download and process the result image
|
# Download result
|
||||||
image_response = requests.get(result_image_url)
|
image_response = requests.get(result_image_url)
|
||||||
result_image = Image.open(io.BytesIO(image_response.content))
|
result_image = Image.open(io.BytesIO(image_response.content))
|
||||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
|
||||||
result_image = torch.from_numpy(result_image)[None,]
|
|
||||||
|
|
||||||
return (result_image,)
|
|
||||||
else:
|
|
||||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
|
||||||
|
|
||||||
except Exception as e:
|
# Convert to float32 tensor (H, W, C), 0-1
|
||||||
raise Exception(f"{e}")
|
result_array = np.array(result_image).astype(np.float32) / 255.0
|
||||||
|
result_tensor = torch.from_numpy(result_array) # shape: (H,W,4)
|
||||||
|
batch_results.append(result_tensor)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[RmbgNode] Skipping image {idx} due to error: {e}")
|
||||||
|
# Append empty tensor of the same size as original
|
||||||
|
empty_array = np.zeros((pil_image.height, pil_image.width, 4), dtype=np.float32)
|
||||||
|
batch_results.append(torch.from_numpy(empty_array))
|
||||||
|
|
||||||
|
# Return list of tensors (Comfy preview handles this correctly)
|
||||||
|
return (batch_results,)
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .common import deserialize_and_get_comfy_key, postprocess_image, preprocess_image, image_to_base64
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
postprocess_image,
|
||||||
|
preprocess_image,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TailoredGenNode():
|
class TailoredGenNode():
|
||||||
@@ -45,7 +50,6 @@ class TailoredGenNode():
|
|||||||
guidance_method_2=None, guidance_method_2_scale=None, guidance_method_2_image=None,
|
guidance_method_2=None, guidance_method_2_scale=None, guidance_method_2_image=None,
|
||||||
content_moderation=0,
|
content_moderation=0,
|
||||||
):
|
):
|
||||||
api_key = deserialize_and_get_comfy_key(api_key)
|
|
||||||
payload = {
|
payload = {
|
||||||
"prompt": generation_prefix + prompt,
|
"prompt": generation_prefix + prompt,
|
||||||
"num_results": 1,
|
"num_results": 1,
|
||||||
@@ -74,7 +78,7 @@ class TailoredGenNode():
|
|||||||
response = requests.post(
|
response = requests.post(
|
||||||
self.api_url + model_id,
|
self.api_url + model_id,
|
||||||
json=payload,
|
json=payload,
|
||||||
headers={"api_token": api_key}
|
headers=bria_json_headers(api_key),
|
||||||
)
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
response_dict = response.json()
|
response_dict = response.json()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import requests
|
import requests
|
||||||
from .common import deserialize_and_get_comfy_key
|
from .common import bria_json_headers
|
||||||
|
|
||||||
class TailoredModelInfoNode():
|
class TailoredModelInfoNode():
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -21,10 +21,9 @@ class TailoredModelInfoNode():
|
|||||||
|
|
||||||
# Define the execute method as expected by ComfyUI
|
# Define the execute method as expected by ComfyUI
|
||||||
def execute(self, model_id, api_key):
|
def execute(self, model_id, api_key):
|
||||||
api_key = deserialize_and_get_comfy_key(api_key)
|
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
self.api_url + model_id,
|
self.api_url + model_id,
|
||||||
headers={"api_token": api_key}
|
headers=bria_json_headers(api_key),
|
||||||
)
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
generation_prefix = response.json()["generation_prefix"]
|
generation_prefix = response.json()["generation_prefix"]
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
import numpy as np
|
|
||||||
import requests
|
|
||||||
from PIL import Image
|
|
||||||
import io
|
import io
|
||||||
|
import requests
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from .common import deserialize_and_get_comfy_key, image_to_base64, preprocess_image
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
normalize_images_input,
|
||||||
|
to_pil_safe,
|
||||||
|
)
|
||||||
|
|
||||||
class TailoredPortraitNode():
|
class TailoredPortraitNode():
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(self):
|
def INPUT_TYPES(cls):
|
||||||
return {
|
return {
|
||||||
"required": {
|
"required": {
|
||||||
"image": ("IMAGE",), # Input image from another node
|
"images": ("IMAGE",),
|
||||||
"tailored_model_id": ("STRING",), # API Key input with a default value
|
"tailored_model_id": ("STRING",),
|
||||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
},
|
},
|
||||||
"optional": {
|
"optional": {
|
||||||
"seed": ("INT", {"default": 123456}),
|
"seed": ("INT", {"default": 123456}),
|
||||||
@@ -23,54 +28,61 @@ class TailoredPortraitNode():
|
|||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("IMAGE",)
|
RETURN_TYPES = ("IMAGE",)
|
||||||
RETURN_NAMES = ("output_image",)
|
RETURN_NAMES = ("output_images",)
|
||||||
CATEGORY = "API Nodes"
|
CATEGORY = "API Nodes"
|
||||||
FUNCTION = "execute" # This is the method that will be executed
|
FUNCTION = "execute"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.api_url = "https://engine.prod.bria-api.com/v1/tailored-gen/restyle_portrait" # Eraser API URL
|
self.api_url = "https://engine.prod.bria-api.com/v1/tailored-gen/restyle_portrait"
|
||||||
|
|
||||||
# Define the execute method as expected by ComfyUI
|
def execute(
|
||||||
def execute(self, image, tailored_model_id, api_key, seed, tailored_model_influence, id_strength):
|
self,
|
||||||
|
images,
|
||||||
|
tailored_model_id,
|
||||||
|
api_key,
|
||||||
|
seed,
|
||||||
|
tailored_model_influence,
|
||||||
|
id_strength
|
||||||
|
):
|
||||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||||
raise Exception("Please insert a valid API key.")
|
raise Exception("Please insert a valid API key.")
|
||||||
api_key = deserialize_and_get_comfy_key(api_key)
|
# Normalize images to list of PIL images
|
||||||
|
images = normalize_images_input(images)
|
||||||
|
|
||||||
# Convert the image and mask directly to if isinstance(image, torch.Tensor):
|
batch_results = []
|
||||||
if isinstance(image, torch.Tensor):
|
|
||||||
image = preprocess_image(image)
|
|
||||||
|
|
||||||
image_base64 = image_to_base64(image)
|
|
||||||
|
|
||||||
# Prepare the API request payload
|
for idx, pil_image in enumerate(images):
|
||||||
payload = {
|
try:
|
||||||
"id_image_file": f"{image_base64}",
|
image_base64 = image_to_base64(pil_image)
|
||||||
"tailored_model_id": int(tailored_model_id),
|
|
||||||
"tailored_model_influence": tailored_model_influence,
|
|
||||||
"id_strength": id_strength,
|
|
||||||
"seed": seed
|
|
||||||
}
|
|
||||||
|
|
||||||
headers = {
|
payload = {
|
||||||
"Content-Type": "application/json",
|
"id_image_file": image_base64,
|
||||||
"api_token": f"{api_key}"
|
"tailored_model_id": int(tailored_model_id),
|
||||||
}
|
"tailored_model_influence": tailored_model_influence,
|
||||||
|
"id_strength": id_strength,
|
||||||
|
"seed": seed
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = bria_json_headers(api_key)
|
||||||
|
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||||
|
|
||||||
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()
|
response_dict = response.json()
|
||||||
image_response = requests.get(response_dict['image_res'])
|
image_response = requests.get(response_dict["image_res"])
|
||||||
result_image = Image.open(io.BytesIO(image_response.content))
|
result_image = Image.open(io.BytesIO(image_response.content)).convert("RGB")
|
||||||
result_image = result_image.convert("RGB")
|
|
||||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
|
||||||
result_image = torch.from_numpy(result_image)[None,]
|
|
||||||
return (result_image,)
|
|
||||||
else:
|
|
||||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
|
||||||
|
|
||||||
except Exception as e:
|
# Convert to float32 tensor (H,W,C), 0-1
|
||||||
raise Exception(f"{e}")
|
result_array = np.array(result_image).astype(np.float32) / 255.0
|
||||||
|
result_tensor = torch.from_numpy(result_array)
|
||||||
|
|
||||||
|
batch_results.append(result_tensor)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[TailoredPortraitNode] Skipping image {idx} due to error: {e}")
|
||||||
|
fallback_array = np.array(pil_image).astype(np.float32) / 255.0
|
||||||
|
batch_results.append(torch.from_numpy(fallback_array))
|
||||||
|
|
||||||
|
# Return list of tensors (avoids size/dtype mismatch)
|
||||||
|
return (batch_results,)
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .common import deserialize_and_get_comfy_key, postprocess_image, preprocess_image, image_to_base64
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
postprocess_image,
|
||||||
|
preprocess_image,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Text2ImageBaseNode():
|
class Text2ImageBaseNode():
|
||||||
@@ -48,7 +53,6 @@ class Text2ImageBaseNode():
|
|||||||
image_prompt_mode=None, image_prompt_image=None, image_prompt_scale=None,
|
image_prompt_mode=None, image_prompt_image=None, image_prompt_scale=None,
|
||||||
content_moderation=0,
|
content_moderation=0,
|
||||||
):
|
):
|
||||||
api_key = deserialize_and_get_comfy_key(api_key)
|
|
||||||
payload = {
|
payload = {
|
||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
"num_results": 1,
|
"num_results": 1,
|
||||||
@@ -84,7 +88,7 @@ class Text2ImageBaseNode():
|
|||||||
response = requests.post(
|
response = requests.post(
|
||||||
self.api_url,
|
self.api_url,
|
||||||
json=payload,
|
json=payload,
|
||||||
headers={"api_token": api_key}
|
headers=bria_json_headers(api_key),
|
||||||
)
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
response_dict = response.json()
|
response_dict = response.json()
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .common import deserialize_and_get_comfy_key, postprocess_image, preprocess_image, image_to_base64
|
from .common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
postprocess_image,
|
||||||
|
preprocess_image,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Text2ImageFastNode():
|
class Text2ImageFastNode():
|
||||||
@@ -45,7 +50,6 @@ class Text2ImageFastNode():
|
|||||||
image_prompt_mode=None, image_prompt_image=None, image_prompt_scale=None,
|
image_prompt_mode=None, image_prompt_image=None, image_prompt_scale=None,
|
||||||
content_moderation=0,
|
content_moderation=0,
|
||||||
):
|
):
|
||||||
api_key = deserialize_and_get_comfy_key(api_key)
|
|
||||||
payload = {
|
payload = {
|
||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
"num_results": 1,
|
"num_results": 1,
|
||||||
@@ -77,7 +81,7 @@ class Text2ImageFastNode():
|
|||||||
response = requests.post(
|
response = requests.post(
|
||||||
self.api_url,
|
self.api_url,
|
||||||
json=payload,
|
json=payload,
|
||||||
headers={"api_token": api_key}
|
headers=bria_json_headers(api_key),
|
||||||
)
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
response_dict = response.json()
|
response_dict = response.json()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .common import deserialize_and_get_comfy_key, postprocess_image
|
from .common import bria_json_headers, postprocess_image
|
||||||
|
|
||||||
|
|
||||||
class Text2ImageHDNode():
|
class Text2ImageHDNode():
|
||||||
@@ -35,7 +35,6 @@ class Text2ImageHDNode():
|
|||||||
self, api_key, prompt, aspect_ratio, seed, negative_prompt,
|
self, api_key, prompt, aspect_ratio, seed, negative_prompt,
|
||||||
steps_num, prompt_enhancement, text_guidance_scale, medium, content_moderation=0,
|
steps_num, prompt_enhancement, text_guidance_scale, medium, content_moderation=0,
|
||||||
):
|
):
|
||||||
api_key = deserialize_and_get_comfy_key(api_key)
|
|
||||||
payload = {
|
payload = {
|
||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
"num_results": 1,
|
"num_results": 1,
|
||||||
@@ -53,7 +52,7 @@ class Text2ImageHDNode():
|
|||||||
response = requests.post(
|
response = requests.post(
|
||||||
self.api_url,
|
self.api_url,
|
||||||
json=payload,
|
json=payload,
|
||||||
headers={"api_token": api_key}
|
headers=bria_json_headers(api_key),
|
||||||
)
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
response_dict = response.json()
|
response_dict = response.json()
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import requests
|
import requests
|
||||||
import torch
|
import torch
|
||||||
from ..common import deserialize_and_get_comfy_key, postprocess_image, preprocess_image, image_to_base64
|
from ..common import (
|
||||||
|
bria_json_headers,
|
||||||
|
image_to_base64,
|
||||||
|
postprocess_image,
|
||||||
|
preprocess_image,
|
||||||
|
)
|
||||||
|
|
||||||
shot_by_text_api_url = (
|
shot_by_text_api_url = (
|
||||||
"https://engine.prod.bria-api.com/v1/product/lifestyle_shot_by_text"
|
"https://engine.prod.bria-api.com/v1/product/lifestyle_shot_by_text"
|
||||||
@@ -130,8 +135,7 @@ def make_api_request(api_url, payload, api_key, Placement_type = None):
|
|||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api_key = deserialize_and_get_comfy_key(api_key)
|
headers = bria_json_headers(api_key)
|
||||||
headers = {"Content-Type": "application/json", "api_token": f"{api_key}"}
|
|
||||||
response = requests.post(api_url, json=payload, headers=headers)
|
response = requests.post(api_url, json=payload, headers=headers)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import os
|
||||||
|
import folder_paths
|
||||||
|
|
||||||
|
class LoadVideoFramesNode:
|
||||||
|
"""
|
||||||
|
Load a video file from the input folder or upload.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
video (str): Selected or uploaded video filename.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
video_path (STRING): Absolute path to the video file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
input_dir = folder_paths.get_input_directory()
|
||||||
|
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
|
||||||
|
files = folder_paths.filter_files_content_types(files, ["video"])
|
||||||
|
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"video": (sorted(files), {"video_upload": True}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("STRING",)
|
||||||
|
RETURN_NAMES = ("video_path",)
|
||||||
|
FUNCTION = "load_video"
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
|
||||||
|
def load_video(self, video):
|
||||||
|
video_path = folder_paths.get_annotated_filepath(video)
|
||||||
|
if not os.path.exists(video_path):
|
||||||
|
raise FileNotFoundError(f"Video file not found: {video_path}")
|
||||||
|
|
||||||
|
return (video_path,)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def IS_CHANGED(cls, video, **kwargs):
|
||||||
|
"""Force re-execution when video file changes"""
|
||||||
|
video_path = folder_paths.get_annotated_filepath(video)
|
||||||
|
if os.path.exists(video_path):
|
||||||
|
return os.path.getmtime(video_path)
|
||||||
|
return float("nan")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def VALIDATE_INPUTS(cls, video, **kwargs):
|
||||||
|
"""Validate that the video file exists"""
|
||||||
|
if not folder_paths.exists_annotated_filepath(video):
|
||||||
|
return f"Invalid video file: {video}"
|
||||||
|
return True
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import folder_paths
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class PreviewVideoURLNode:
|
||||||
|
"""
|
||||||
|
Bria Preview Video URL Node
|
||||||
|
|
||||||
|
This node takes a video URL as a string and downloads it to preview
|
||||||
|
directly in the ComfyUI interface.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- video_url: URL of the video to preview (http/https)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.output_dir = folder_paths.get_temp_directory()
|
||||||
|
self.type = "temp"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"video_url": ("STRING", {
|
||||||
|
"default": "",
|
||||||
|
"multiline": False,
|
||||||
|
"tooltip": "URL of the video to preview (http/https)"
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"hidden": {
|
||||||
|
"prompt": "PROMPT",
|
||||||
|
"extra_pnginfo": "EXTRA_PNGINFO"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ()
|
||||||
|
FUNCTION = "preview_video_url"
|
||||||
|
OUTPUT_NODE = True
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
DESCRIPTION = "Previews a video from URL directly in the ComfyUI interface."
|
||||||
|
|
||||||
|
def preview_video_url(self, video_url, prompt=None, extra_pnginfo=None):
|
||||||
|
"""
|
||||||
|
Preview video from URL
|
||||||
|
|
||||||
|
Args:
|
||||||
|
video_url: URL of the video (http/https)
|
||||||
|
prompt: Hidden parameter for ComfyUI workflow
|
||||||
|
extra_pnginfo: Hidden parameter for ComfyUI metadata
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: UI output with video file for preview
|
||||||
|
"""
|
||||||
|
if not video_url or video_url.strip() == "":
|
||||||
|
raise ValueError("video_url cannot be empty")
|
||||||
|
|
||||||
|
if not video_url.startswith("http://") and not video_url.startswith("https://"):
|
||||||
|
raise ValueError("video_url must be a valid HTTP or HTTPS URL")
|
||||||
|
|
||||||
|
print(f"Downloading video from URL: {video_url}")
|
||||||
|
|
||||||
|
# Download video from URL
|
||||||
|
try:
|
||||||
|
response = requests.get(video_url, stream=True, timeout=60)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# Determine file extension from URL or Content-Type
|
||||||
|
content_type = response.headers.get('Content-Type', '')
|
||||||
|
extension = self._get_extension_from_content_type(content_type, video_url)
|
||||||
|
|
||||||
|
filename_prefix = str(uuid.uuid4()) + "_video_url_preview"
|
||||||
|
|
||||||
|
# Get save path
|
||||||
|
full_output_folder = self.output_dir
|
||||||
|
filename = f"{filename_prefix}.{extension}"
|
||||||
|
filepath = os.path.join(full_output_folder, filename)
|
||||||
|
|
||||||
|
|
||||||
|
# Save video to temp directory
|
||||||
|
print(f"Saving video to: {filepath}")
|
||||||
|
with open(filepath, 'wb') as f:
|
||||||
|
for chunk in response.iter_content(chunk_size=8192):
|
||||||
|
if chunk:
|
||||||
|
f.write(chunk)
|
||||||
|
|
||||||
|
file_size = os.path.getsize(filepath)
|
||||||
|
print(f"Video downloaded successfully: {filename} ({file_size / (1024*1024):.2f} MB)")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"ui": {
|
||||||
|
"images": [{
|
||||||
|
"filename": filename,
|
||||||
|
"subfolder": "",
|
||||||
|
"type": self.type,
|
||||||
|
"format": extension
|
||||||
|
}],
|
||||||
|
"animated": (True,),
|
||||||
|
"has_audio": (True,)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
raise Exception(f"Failed to download video from URL: {str(e)}")
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Error previewing video: {str(e)}")
|
||||||
|
|
||||||
|
def _get_extension_from_content_type(self, content_type, url):
|
||||||
|
"""
|
||||||
|
Determine file extension from Content-Type header or URL
|
||||||
|
"""
|
||||||
|
# Map common video MIME types to extensions
|
||||||
|
content_type_map = {
|
||||||
|
'video/mp4': 'mp4',
|
||||||
|
'video/webm': 'webm',
|
||||||
|
'video/quicktime': 'mov',
|
||||||
|
'video/x-matroska': 'mkv',
|
||||||
|
'video/x-msvideo': 'avi',
|
||||||
|
'image/gif': 'gif',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Try to get extension from Content-Type
|
||||||
|
for mime_type, ext in content_type_map.items():
|
||||||
|
if mime_type in content_type.lower():
|
||||||
|
return ext
|
||||||
|
|
||||||
|
# Try to get extension from URL
|
||||||
|
url_path = url.split('?')[0] # Remove query parameters
|
||||||
|
if '.' in url_path:
|
||||||
|
url_ext = url_path.rsplit('.', 1)[-1].lower()
|
||||||
|
if url_ext in ['mp4', 'webm', 'mov', 'mkv', 'avi', 'gif', 'webp']:
|
||||||
|
return url_ext
|
||||||
|
|
||||||
|
# Default to mp4
|
||||||
|
return 'mp4'
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import requests
|
||||||
|
import folder_paths
|
||||||
|
from ..common import bria_json_headers, poll_status_until_completed
|
||||||
|
from .video_utils import upload_video_to_s3
|
||||||
|
|
||||||
|
class RemoveVideoBackgroundNode():
|
||||||
|
"""
|
||||||
|
Removes the background from a video using the Bria API.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
api_key (str): Your Bria API key.
|
||||||
|
video_url (str): Local path or URL of the video to process.
|
||||||
|
preserve_audio (bool, optional): Whether to keep the audio track. Default is True.
|
||||||
|
output_container_and_codec (str, optional): Desired output format and codec. Default is "webm_vp9".
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
result_video_url (STRING): URL of the video with background removed.
|
||||||
|
"""
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(self):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
|
"video_url": ("STRING", {
|
||||||
|
"default": "",
|
||||||
|
"tooltip": "URL of video to process (provide either frames or video_url)"
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"preserve_audio": ("BOOLEAN", {"default": True}),
|
||||||
|
"output_container_and_codec": ([
|
||||||
|
"mp4_h264",
|
||||||
|
"mp4_h265",
|
||||||
|
"webm_vp9",
|
||||||
|
"mov_h265",
|
||||||
|
"mov_proresks",
|
||||||
|
"mkv_h264",
|
||||||
|
"mkv_h265",
|
||||||
|
"mkv_vp9",
|
||||||
|
"gif"
|
||||||
|
], {"default": "webm_vp9"}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("STRING",)
|
||||||
|
RETURN_NAMES = ("result_video_url",)
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
FUNCTION = "execute"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.api_url = "https://engine.prod.bria-api.com/v2/video/edit/remove_background"
|
||||||
|
|
||||||
|
def execute(self, api_key, video_url, preserve_audio=True, output_container_and_codec="webm_vp9",):
|
||||||
|
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||||
|
raise Exception("Please insert a valid API key.")
|
||||||
|
video_path = None
|
||||||
|
|
||||||
|
input_video_url = ""
|
||||||
|
if video_url and video_url.strip() != "":
|
||||||
|
if os.path.exists(video_url):
|
||||||
|
filename = f"{ str(uuid.uuid4())}_{os.path.basename(video_url)}"
|
||||||
|
input_video_url = upload_video_to_s3(video_url, filename, api_key)
|
||||||
|
if video_url.startswith(folder_paths.get_temp_directory()):
|
||||||
|
video_path = None
|
||||||
|
else:
|
||||||
|
input_video_url = video_url
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
print("Step 3: Calling Bria API for background removal...")
|
||||||
|
payload = {
|
||||||
|
"video": input_video_url,
|
||||||
|
"preserve_audio": preserve_audio,
|
||||||
|
"output_container_and_codec": output_container_and_codec
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = bria_json_headers(api_key)
|
||||||
|
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 200 or response.status_code == 202:
|
||||||
|
print('Initial Video RMBG request successful, polling for completion...')
|
||||||
|
response_dict = response.json()
|
||||||
|
|
||||||
|
status_url = response_dict.get('status_url')
|
||||||
|
request_id = response_dict.get('request_id')
|
||||||
|
|
||||||
|
if not status_url:
|
||||||
|
raise Exception("No status_url returned from API")
|
||||||
|
|
||||||
|
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||||
|
|
||||||
|
final_response = poll_status_until_completed(status_url, api_key, timeout=3600, check_interval=5)
|
||||||
|
|
||||||
|
result_video_url = final_response['result']['video_url']
|
||||||
|
|
||||||
|
print(f"Video processing completed. Result URL: {result_video_url}")
|
||||||
|
print(f"Background removal complete! Use Preview Video URL node to view the result.")
|
||||||
|
|
||||||
|
return (result_video_url,)
|
||||||
|
else:
|
||||||
|
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"{e}")
|
||||||
|
finally:
|
||||||
|
if video_path:
|
||||||
|
try:
|
||||||
|
if os.path.exists(video_path):
|
||||||
|
os.unlink(video_path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import requests
|
||||||
|
import folder_paths
|
||||||
|
from ..common import bria_json_headers, poll_status_until_completed
|
||||||
|
from .video_utils import upload_video_to_s3
|
||||||
|
|
||||||
|
class VideoEraseElementsNode():
|
||||||
|
"""
|
||||||
|
Erase elements from a video using the Bria API.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
api_key (str): Your Bria API key.
|
||||||
|
video_url (str): Local path or URL of the video to process.
|
||||||
|
mask_url (str, optional): URL of a mask video for selective erasing.
|
||||||
|
output_container_and_codec (str, optional): Desired output format and codec. Default is "mp4_h264".
|
||||||
|
preserve_audio (bool, optional): Whether to keep the audio track. Default is True.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
result_video_url (STRING): URL of the processed video with elements erased.
|
||||||
|
"""
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(self):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
|
"video_url": ("STRING", {
|
||||||
|
"default": "",
|
||||||
|
"tooltip": "URL of video to process (provide either frames or video_url)"
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"mask_url": ("STRING", {
|
||||||
|
"default": "",
|
||||||
|
"tooltip": "URL of mask video (optional)"
|
||||||
|
}),
|
||||||
|
"output_container_and_codec": ([
|
||||||
|
"mp4_h264",
|
||||||
|
"mp4_h265",
|
||||||
|
"webm_vp9",
|
||||||
|
"mov_h265",
|
||||||
|
"mov_proresks",
|
||||||
|
"mkv_h264",
|
||||||
|
"mkv_h265",
|
||||||
|
"mkv_vp9",
|
||||||
|
"gif"
|
||||||
|
], {"default": "mp4_h264"}),
|
||||||
|
"preserve_audio": ("BOOLEAN", {"default": True}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("STRING",)
|
||||||
|
RETURN_NAMES = ("result_video_url",)
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
FUNCTION = "execute"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.api_url = "https://engine.prod.bria-api.com/v2/video/edit/erase"
|
||||||
|
|
||||||
|
def execute(self, api_key, video_url, mask_url="", output_container_and_codec="mp4_h264", preserve_audio=True):
|
||||||
|
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||||
|
raise Exception("Please insert a valid API key.")
|
||||||
|
video_path = None
|
||||||
|
|
||||||
|
if video_url and video_url.strip() != "":
|
||||||
|
# Check if video_url is a local file path or a URL
|
||||||
|
if os.path.exists(video_url):
|
||||||
|
filename = f"{ str(uuid.uuid4())}_{os.path.basename(video_url)}"
|
||||||
|
input_video_url = upload_video_to_s3(video_url, filename, api_key)
|
||||||
|
|
||||||
|
if not input_video_url or not (input_video_url.startswith('http://') or input_video_url.startswith('https://')):
|
||||||
|
raise Exception(f"Failed to upload video to S3. Got: {input_video_url}")
|
||||||
|
if video_url.startswith(folder_paths.get_temp_directory()):
|
||||||
|
video_path = None
|
||||||
|
else:
|
||||||
|
input_video_url = video_url
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
print("Step 3: Calling Bria API for element erasure...")
|
||||||
|
payload = {
|
||||||
|
"video": input_video_url,
|
||||||
|
"mask": mask_url,
|
||||||
|
"output_container_and_codec": output_container_and_codec,
|
||||||
|
"preserve_audio": preserve_audio
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = bria_json_headers(api_key)
|
||||||
|
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 200 or response.status_code == 202:
|
||||||
|
print('Initial Video Erase Elements 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, timeout=3600, check_interval=5)
|
||||||
|
|
||||||
|
result_video_url = final_response['result']['video_url']
|
||||||
|
|
||||||
|
print(f"Video processing completed. Result URL: {result_video_url}")
|
||||||
|
print(f"Element erasure complete! Use Preview Video URL node to view the result.")
|
||||||
|
|
||||||
|
return (result_video_url,)
|
||||||
|
else:
|
||||||
|
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"{e}")
|
||||||
|
finally:
|
||||||
|
if video_path:
|
||||||
|
try:
|
||||||
|
if os.path.exists(video_path):
|
||||||
|
os.unlink(video_path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import requests
|
||||||
|
import folder_paths
|
||||||
|
from ..common import bria_json_headers, poll_status_until_completed
|
||||||
|
from .video_utils import upload_video_to_s3
|
||||||
|
|
||||||
|
class VideoIncreaseResolutionNode():
|
||||||
|
"""
|
||||||
|
Increase the resolution of a video using the Bria API.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
api_key (str): Your Bria API key.
|
||||||
|
video_url (str): Local path or URL of the video to process.
|
||||||
|
desired_increase (str, optional): Resolution increase factor, '2' or '4'. Default is '2'.
|
||||||
|
output_container_and_codec (str, optional): Desired output format and codec. Default is "mp4_h264".
|
||||||
|
preserve_audio (bool, optional): Whether to keep the audio track. Default is True.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
result_video_url (STRING): URL of the processed video with increased resolution.
|
||||||
|
"""
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(self):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
|
"video_url": ("STRING", {
|
||||||
|
"default": "",
|
||||||
|
"tooltip": "URL of video to process (provide either frames or video_url)"
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"desired_increase": (['2', '4'], {"default": '2'}),
|
||||||
|
"output_container_and_codec": ([
|
||||||
|
"mp4_h264",
|
||||||
|
"mp4_h265",
|
||||||
|
"webm_vp9",
|
||||||
|
"mov_h265",
|
||||||
|
"mov_proresks",
|
||||||
|
"mkv_h264",
|
||||||
|
"mkv_h265",
|
||||||
|
"mkv_vp9",
|
||||||
|
"gif"
|
||||||
|
], {"default": "mp4_h264"}),
|
||||||
|
"preserve_audio": ("BOOLEAN", {"default": True}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("STRING",)
|
||||||
|
RETURN_NAMES = ("result_video_url",)
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
FUNCTION = "execute"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.api_url = "https://engine.prod.bria-api.com/v2/video/edit/increase_resolution"
|
||||||
|
|
||||||
|
def execute(self, api_key, video_url, desired_increase='2', output_container_and_codec="mp4_h264", preserve_audio=True):
|
||||||
|
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||||
|
raise Exception("Please insert a valid API key.")
|
||||||
|
video_path = None
|
||||||
|
|
||||||
|
if video_url and video_url.strip() != "":
|
||||||
|
if os.path.exists(video_url):
|
||||||
|
filename = f"{ str(uuid.uuid4())}_{os.path.basename(video_url)}"
|
||||||
|
input_video_url = upload_video_to_s3(video_url, filename, api_key)
|
||||||
|
|
||||||
|
if not input_video_url or not (input_video_url.startswith('http://') or input_video_url.startswith('https://')):
|
||||||
|
raise Exception(f"Failed to upload video to S3. Got: {input_video_url}")
|
||||||
|
|
||||||
|
if video_url.startswith(folder_paths.get_temp_directory()):
|
||||||
|
video_path = None
|
||||||
|
else:
|
||||||
|
input_video_url = video_url
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
print("Step 3: Calling Bria API for resolution increase...")
|
||||||
|
payload = {
|
||||||
|
"video": input_video_url,
|
||||||
|
"desired_increase": desired_increase,
|
||||||
|
"output_container_and_codec": output_container_and_codec,
|
||||||
|
"preserve_audio": preserve_audio
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = bria_json_headers(api_key)
|
||||||
|
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 200 or response.status_code == 202:
|
||||||
|
print('Initial Video Increase Resolution 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, timeout=3600, check_interval=5)
|
||||||
|
|
||||||
|
result_video_url = final_response['result']['video_url']
|
||||||
|
|
||||||
|
print(f"Video processing completed. Result URL: {result_video_url}")
|
||||||
|
print(f"Resolution increase complete! Use Preview Video URL node to view the result.")
|
||||||
|
|
||||||
|
return (result_video_url,)
|
||||||
|
else:
|
||||||
|
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"{e}")
|
||||||
|
finally:
|
||||||
|
if video_path:
|
||||||
|
try:
|
||||||
|
if os.path.exists(video_path):
|
||||||
|
os.unlink(video_path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import requests
|
||||||
|
import folder_paths
|
||||||
|
from ..common import bria_json_headers, poll_status_until_completed
|
||||||
|
from .video_utils import upload_video_to_s3
|
||||||
|
import json
|
||||||
|
|
||||||
|
class VideoMaskByKeyPointsNode():
|
||||||
|
"""
|
||||||
|
Generate a video mask using key points with the Bria API.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
key_points (str): JSON string of key points for masking.
|
||||||
|
api_key (str): Your Bria API key.
|
||||||
|
video_url (str): Local path or URL of the video to process.
|
||||||
|
output_container_and_codec (str, optional): Desired output format and codec. Default is "mp4_h264".
|
||||||
|
preserve_audio (bool, optional): Whether to keep the audio track. Default is True.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
mask_url (STRING): URL of the generated video mask.
|
||||||
|
"""
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(self):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"key_points": ("STRING", {"default": "[]", "multiline": True}),
|
||||||
|
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
|
"video_url": ("STRING", {
|
||||||
|
"default": "",
|
||||||
|
"tooltip": "URL of video to process (provide either frames or video_url)"
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"output_container_and_codec": ([
|
||||||
|
"mp4_h264",
|
||||||
|
"mp4_h265",
|
||||||
|
"webm_vp9",
|
||||||
|
"mov_h265",
|
||||||
|
"mov_proresks",
|
||||||
|
"mkv_h264",
|
||||||
|
"mkv_h265",
|
||||||
|
"mkv_vp9",
|
||||||
|
"gif"
|
||||||
|
], {"default": "mp4_h264"}),
|
||||||
|
"preserve_audio": ("BOOLEAN", {"default": True}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("STRING",)
|
||||||
|
RETURN_NAMES = ("mask_url",)
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
FUNCTION = "execute"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.api_url = "https://engine.prod.bria-api.com/v2/video/segment/mask_by_key_points"
|
||||||
|
|
||||||
|
def execute(self, key_points, api_key, video_url, output_container_and_codec="mp4_h264", preserve_audio=True):
|
||||||
|
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||||
|
raise Exception("Please insert a valid API key.")
|
||||||
|
try:
|
||||||
|
key_points_array = json.loads(key_points)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
raise Exception(f"Invalid JSON format for key_points: {e}")
|
||||||
|
|
||||||
|
video_path = None
|
||||||
|
|
||||||
|
if video_url and video_url.strip() != "":
|
||||||
|
if os.path.exists(video_url):
|
||||||
|
filename = f"{ str(uuid.uuid4())}_{os.path.basename(video_url)}"
|
||||||
|
input_video_url = upload_video_to_s3(video_url, filename, api_key)
|
||||||
|
|
||||||
|
if not input_video_url or not (input_video_url.startswith('http://') or input_video_url.startswith('https://')):
|
||||||
|
raise Exception(f"Failed to upload video to S3. Got: {input_video_url}")
|
||||||
|
|
||||||
|
|
||||||
|
if video_url.startswith(folder_paths.get_temp_directory()):
|
||||||
|
video_path = None
|
||||||
|
else:
|
||||||
|
input_video_url = video_url
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
print("Step 3: Calling Bria API for video mask generation by key points...")
|
||||||
|
payload = {
|
||||||
|
"video": input_video_url,
|
||||||
|
"key_points": key_points_array,
|
||||||
|
"output_container_and_codec": output_container_and_codec,
|
||||||
|
"preserve_audio": preserve_audio
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = bria_json_headers(api_key)
|
||||||
|
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 200 or response.status_code == 202:
|
||||||
|
print('Initial Video Mask by Key Points 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, timeout=3600, check_interval=5)
|
||||||
|
|
||||||
|
result_mask_url = final_response['result']['mask_url']
|
||||||
|
|
||||||
|
print(f"Video mask processing completed. Result URL: {result_mask_url}")
|
||||||
|
print(f"Video mask generation complete! Use Preview Video URL node to view the result.")
|
||||||
|
|
||||||
|
return (result_mask_url,)
|
||||||
|
else:
|
||||||
|
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"{e}")
|
||||||
|
finally:
|
||||||
|
if video_path:
|
||||||
|
try:
|
||||||
|
if os.path.exists(video_path):
|
||||||
|
os.unlink(video_path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import requests
|
||||||
|
import folder_paths
|
||||||
|
from ..common import bria_json_headers, poll_status_until_completed
|
||||||
|
from .video_utils import upload_video_to_s3
|
||||||
|
|
||||||
|
class VideoMaskByPromptNode():
|
||||||
|
"""
|
||||||
|
Generate a video mask using a text prompt with the Bria API.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
prompt (str): Text prompt describing what to mask in the video.
|
||||||
|
api_key (str): Your Bria API key.
|
||||||
|
video_url (str): Local path or URL of the video to process.
|
||||||
|
output_container_and_codec (str, optional): Desired output format and codec. Default is "mp4_h264".
|
||||||
|
preserve_audio (bool, optional): Whether to keep the audio track. Default is True.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
mask_url (STRING): URL of the generated video mask.
|
||||||
|
"""
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(self):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"prompt": ("STRING", {"default": ""}),
|
||||||
|
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
|
"video_url": ("STRING", {
|
||||||
|
"default": "",
|
||||||
|
"tooltip": "URL of video to process (provide either frames or video_url)"
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"output_container_and_codec": ([
|
||||||
|
"mp4_h264",
|
||||||
|
"mp4_h265",
|
||||||
|
"webm_vp9",
|
||||||
|
"mov_h265",
|
||||||
|
"mov_proresks",
|
||||||
|
"mkv_h264",
|
||||||
|
"mkv_h265",
|
||||||
|
"mkv_vp9",
|
||||||
|
"gif"
|
||||||
|
], {"default": "mp4_h264"}),
|
||||||
|
"preserve_audio": ("BOOLEAN", {"default": True}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("STRING",)
|
||||||
|
RETURN_NAMES = ("mask_url",)
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
FUNCTION = "execute"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.api_url = "https://engine.prod.bria-api.com/v2/video/segment/mask_by_prompt"
|
||||||
|
|
||||||
|
def execute(self, prompt, api_key, video_url, output_container_and_codec="mp4_h264", preserve_audio=True):
|
||||||
|
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||||
|
raise Exception("Please insert a valid API key.")
|
||||||
|
video_path = None
|
||||||
|
|
||||||
|
if video_url and video_url.strip() != "":
|
||||||
|
if os.path.exists(video_url):
|
||||||
|
filename = f"{ str(uuid.uuid4())}_{os.path.basename(video_url)}"
|
||||||
|
input_video_url = upload_video_to_s3(video_url, filename, api_key)
|
||||||
|
|
||||||
|
if not input_video_url or not (input_video_url.startswith('http://') or input_video_url.startswith('https://')):
|
||||||
|
raise Exception(f"Failed to upload video to S3. Got: {input_video_url}")
|
||||||
|
|
||||||
|
|
||||||
|
if video_url.startswith(folder_paths.get_temp_directory()):
|
||||||
|
video_path = None
|
||||||
|
else:
|
||||||
|
input_video_url = video_url
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
print("Step 3: Calling Bria API for video mask generation...")
|
||||||
|
payload = {
|
||||||
|
"video": input_video_url,
|
||||||
|
"prompt": prompt,
|
||||||
|
"output_container_and_codec": output_container_and_codec,
|
||||||
|
"preserve_audio": preserve_audio
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = bria_json_headers(api_key)
|
||||||
|
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 200 or response.status_code == 202:
|
||||||
|
print('Initial Video Mask by Prompt 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, timeout=3600, check_interval=5)
|
||||||
|
|
||||||
|
result_mask_url = final_response['result']['mask_url']
|
||||||
|
|
||||||
|
print(f"Video mask processing completed. Result URL: {result_mask_url}")
|
||||||
|
print(f"Video mask generation complete! Use Preview Video URL node to view the result.")
|
||||||
|
|
||||||
|
return (result_mask_url,)
|
||||||
|
else:
|
||||||
|
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"{e}")
|
||||||
|
finally:
|
||||||
|
if video_path:
|
||||||
|
try:
|
||||||
|
if os.path.exists(video_path):
|
||||||
|
os.unlink(video_path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import requests
|
||||||
|
import folder_paths
|
||||||
|
from ..common import bria_json_headers, poll_status_until_completed
|
||||||
|
from .video_utils import upload_video_to_s3
|
||||||
|
|
||||||
|
class VideoSolidColorBackgroundNode():
|
||||||
|
"""
|
||||||
|
Apply a solid color background to a video using the Bria API.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
api_key (str): Your Bria API key.
|
||||||
|
video_url (str): Local path or URL of the video to process.
|
||||||
|
background_color (str, optional): Color to apply as background. Default is "Transparent".
|
||||||
|
output_container_and_codec (str, optional): Desired output format and codec. Default is "mp4_h264".
|
||||||
|
preserve_audio (bool, optional): Whether to keep the audio track. Default is True.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
result_video_url (STRING): URL of the video with the solid color background applied.
|
||||||
|
"""
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(self):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||||
|
"video_url": ("STRING", {
|
||||||
|
"default": "",
|
||||||
|
"tooltip": "URL of video to process (provide either frames or video_url)"
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"background_color": ([
|
||||||
|
"Transparent",
|
||||||
|
"Black",
|
||||||
|
"White",
|
||||||
|
"Gray",
|
||||||
|
"Red",
|
||||||
|
"Green",
|
||||||
|
"Blue",
|
||||||
|
"Yellow",
|
||||||
|
"Cyan",
|
||||||
|
"Magenta",
|
||||||
|
"Orange"
|
||||||
|
], {"default": "Transparent"}),
|
||||||
|
"output_container_and_codec": ([
|
||||||
|
"mp4_h264",
|
||||||
|
"mp4_h265",
|
||||||
|
"webm_vp9",
|
||||||
|
"mov_h265",
|
||||||
|
"mov_proresks",
|
||||||
|
"mkv_h264",
|
||||||
|
"mkv_h265",
|
||||||
|
"mkv_vp9",
|
||||||
|
"gif"
|
||||||
|
], {"default": "webm_vp9"}),
|
||||||
|
"preserve_audio": ("BOOLEAN", {"default": True}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("STRING",)
|
||||||
|
RETURN_NAMES = ("result_video_url",)
|
||||||
|
CATEGORY = "API Nodes"
|
||||||
|
FUNCTION = "execute"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.api_url = "https://engine.prod.bria-api.com/v2/video/edit/remove_background"
|
||||||
|
|
||||||
|
def execute(self, api_key, video_url, background_color="Transparent", output_container_and_codec="webm_vp9", preserve_audio=True):
|
||||||
|
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||||
|
raise Exception("Please insert a valid API key.")
|
||||||
|
video_path = None
|
||||||
|
|
||||||
|
if video_url and video_url.strip() != "":
|
||||||
|
if os.path.exists(video_url):
|
||||||
|
filename = f"{ str(uuid.uuid4())}_{os.path.basename(video_url)}"
|
||||||
|
input_video_url = upload_video_to_s3(video_url, filename, api_key)
|
||||||
|
|
||||||
|
if not input_video_url or not (input_video_url.startswith('http://') or input_video_url.startswith('https://')):
|
||||||
|
raise Exception(f"Failed to upload video to S3. Got: {input_video_url}")
|
||||||
|
|
||||||
|
|
||||||
|
if video_url.startswith(folder_paths.get_temp_directory()):
|
||||||
|
video_path = None
|
||||||
|
else:
|
||||||
|
input_video_url = video_url
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
print("Step 3: Calling Bria API for solid color background...")
|
||||||
|
payload = {
|
||||||
|
"video": input_video_url,
|
||||||
|
"background_color": background_color,
|
||||||
|
"output_container_and_codec": output_container_and_codec,
|
||||||
|
"preserve_audio": preserve_audio
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = bria_json_headers(api_key)
|
||||||
|
|
||||||
|
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 200 or response.status_code == 202:
|
||||||
|
print('Initial Video Solid Color Background request successful, polling for completion...')
|
||||||
|
response_dict = response.json()
|
||||||
|
|
||||||
|
status_url = response_dict.get('status_url')
|
||||||
|
request_id = response_dict.get('request_id')
|
||||||
|
|
||||||
|
if not status_url:
|
||||||
|
raise Exception("No status_url returned from API")
|
||||||
|
|
||||||
|
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||||
|
|
||||||
|
final_response = poll_status_until_completed(status_url, api_key, timeout=3600, check_interval=5)
|
||||||
|
|
||||||
|
result_video_url = final_response['result']['video_url']
|
||||||
|
|
||||||
|
print(f"Video processing completed. Result URL: {result_video_url}")
|
||||||
|
print(f"Solid color background processing complete! Use Preview Video URL node to view the result.")
|
||||||
|
|
||||||
|
return (result_video_url,)
|
||||||
|
else:
|
||||||
|
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"{e}")
|
||||||
|
finally:
|
||||||
|
if video_path:
|
||||||
|
try:
|
||||||
|
if os.path.exists(video_path):
|
||||||
|
os.unlink(video_path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from ..common import BRIA_COMFYUI_USER_AGENT
|
||||||
|
|
||||||
|
|
||||||
|
def upload_video_to_s3(video_path, filename, api_token):
|
||||||
|
api_url = "https://platform.prod.bria-api.com/upload-video/anonymous/presigned-url"
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": BRIA_COMFYUI_USER_AGENT,
|
||||||
|
}
|
||||||
|
extension = os.path.splitext(filename)[1].lower()
|
||||||
|
content_type_map = {
|
||||||
|
'.mp4': 'video/mp4',
|
||||||
|
'.webm': 'video/webm',
|
||||||
|
'.mov': 'video/quicktime',
|
||||||
|
'.mkv': 'video/x-matroska',
|
||||||
|
'.avi': 'video/x-msvideo',
|
||||||
|
'.gif': 'image/gif',
|
||||||
|
'.webp': 'image/webp'
|
||||||
|
}
|
||||||
|
content_type = content_type_map.get(extension, 'video/mp4')
|
||||||
|
if api_token:
|
||||||
|
headers["api_token"] = api_token
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"file_name": filename,
|
||||||
|
"content_type":content_type
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Requesting presigned URL for: {filename}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(api_url, json=payload, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception(f"Failed to get presigned URL: {response.status_code} {response.text}")
|
||||||
|
|
||||||
|
response_data = response.json()
|
||||||
|
video_url = response_data.get("video_url")
|
||||||
|
upload_url = response_data.get("upload_url")
|
||||||
|
|
||||||
|
if not video_url or not upload_url:
|
||||||
|
raise Exception(f"Invalid response from presigned URL API: {response_data}")
|
||||||
|
|
||||||
|
print(f"Received presigned URL")
|
||||||
|
print(f"Video URL: {video_url}")
|
||||||
|
|
||||||
|
# Step 2: Upload video to presigned URL
|
||||||
|
print(f"Uploading video to S3...")
|
||||||
|
|
||||||
|
with open(video_path, 'rb') as f:
|
||||||
|
video_data = f.read()
|
||||||
|
|
||||||
|
# Determine content type based on file extension
|
||||||
|
upload_headers = {
|
||||||
|
"Content-Type": content_type
|
||||||
|
}
|
||||||
|
|
||||||
|
upload_response = requests.put(upload_url, data=video_data, headers=upload_headers)
|
||||||
|
|
||||||
|
if upload_response.status_code not in [200, 204]:
|
||||||
|
raise Exception(f"Failed to upload video to S3: {upload_response.status_code}")
|
||||||
|
|
||||||
|
print(f"Video uploaded successfully to S3")
|
||||||
|
|
||||||
|
return video_url
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Error uploading video to S3: {str(e)}")
|
||||||
|
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "comfyui-bria-api"
|
name = "comfyui-bria-api"
|
||||||
description = "Custom nodes for ComfyUI using BRIA's API."
|
description = "Custom nodes for ComfyUI using BRIA's API."
|
||||||
version = "2.1.7"
|
version = "2.1.16"
|
||||||
license = {file = "LICENSE"}
|
license = {file = "LICENSE"}
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
import { app } from "/scripts/app.js";
|
||||||
|
import { api } from "/scripts/api.js";
|
||||||
|
|
||||||
|
app.registerExtension({
|
||||||
|
name: "BriaMultiImageSelect",
|
||||||
|
|
||||||
|
async nodeCreated(node) {
|
||||||
|
if (node.comfyClass !== "BriaMultiImageSelect") return;
|
||||||
|
|
||||||
|
const getWidget = (name) =>
|
||||||
|
node.widgets?.find(w => w.name === name);
|
||||||
|
|
||||||
|
const pathsWidget = getWidget("selected_paths");
|
||||||
|
if (!pathsWidget) return;
|
||||||
|
|
||||||
|
pathsWidget.hidden = true;
|
||||||
|
pathsWidget.draw = () => {};
|
||||||
|
pathsWidget.computeSize = () => [0, 0];
|
||||||
|
|
||||||
|
const viewURLFromRel = (rel) => {
|
||||||
|
const parts = (rel || "").split("/");
|
||||||
|
const filename = parts.pop();
|
||||||
|
const subfolder = parts.join("/");
|
||||||
|
const params = new URLSearchParams({ filename, type: "input", subfolder });
|
||||||
|
return api.apiURL(`/view?${params.toString()}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadImg = (url) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.crossOrigin = "anonymous";
|
||||||
|
img.onload = () => resolve(img);
|
||||||
|
img.onerror = () => reject();
|
||||||
|
img.src = url;
|
||||||
|
});
|
||||||
|
|
||||||
|
const refreshPreview = async () => {
|
||||||
|
let raw = node.properties.selected_paths;
|
||||||
|
if (!raw) raw = pathsWidget.value;
|
||||||
|
|
||||||
|
let rels = [];
|
||||||
|
try {
|
||||||
|
rels = raw ? JSON.parse(raw) : [];
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse selected_paths:", e);
|
||||||
|
node.imgs = null;
|
||||||
|
node.imgError = "Invalid image data";
|
||||||
|
node.setDirtyCanvas(true, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rels.length) {
|
||||||
|
node.imgs = null;
|
||||||
|
node.imgError = "No images selected";
|
||||||
|
node.setDirtyCanvas(true, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imgs = [];
|
||||||
|
await Promise.allSettled(
|
||||||
|
rels.map(async (rel) => {
|
||||||
|
try {
|
||||||
|
const img = await loadImg(viewURLFromRel(rel));
|
||||||
|
imgs.push(img);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`Failed to load image: ${rel}`, e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (imgs.length) {
|
||||||
|
node.imgs = imgs;
|
||||||
|
node.imgError = null;
|
||||||
|
} else {
|
||||||
|
node.imgs = null;
|
||||||
|
node.imgError = "Failed to load images";
|
||||||
|
}
|
||||||
|
node.setDirtyCanvas(true, true);
|
||||||
|
};
|
||||||
|
// Update node size to accommodate preview
|
||||||
|
const originalComputeSize = node.computeSize;
|
||||||
|
node.computeSize = function () {
|
||||||
|
const size = originalComputeSize ? originalComputeSize.apply(this, arguments) : [200, 100];
|
||||||
|
size[1] = Math.max(size[1], 200); // Ensure minimum height for preview
|
||||||
|
return size;
|
||||||
|
};
|
||||||
|
|
||||||
|
const btn = node.addWidget("button", "Select Images", null, async () => {
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.type = "file";
|
||||||
|
input.multiple = true;
|
||||||
|
input.accept = "image/*";
|
||||||
|
input.style.display = "none";
|
||||||
|
document.body.appendChild(input);
|
||||||
|
|
||||||
|
input.onchange = async () => {
|
||||||
|
const files = Array.from(input.files || []);
|
||||||
|
document.body.removeChild(input);
|
||||||
|
if (!files.length) return;
|
||||||
|
|
||||||
|
const rels = [];
|
||||||
|
|
||||||
|
for (const f of files) {
|
||||||
|
const form = new FormData();
|
||||||
|
form.append("image", f, f.name);
|
||||||
|
|
||||||
|
const resp = await api.fetchApi("/upload/image", {
|
||||||
|
method: "POST",
|
||||||
|
body: form,
|
||||||
|
});
|
||||||
|
if (!resp.ok) continue;
|
||||||
|
|
||||||
|
const data = await resp.json();
|
||||||
|
const rel = data.subfolder
|
||||||
|
? `${data.subfolder}/${data.name}`
|
||||||
|
: data.name;
|
||||||
|
rels.push(rel);
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = JSON.stringify(rels);
|
||||||
|
pathsWidget.value = json;
|
||||||
|
node.properties.selected_paths = json;
|
||||||
|
|
||||||
|
await refreshPreview();
|
||||||
|
};
|
||||||
|
|
||||||
|
input.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
node.widgets.unshift(
|
||||||
|
node.widgets.splice(node.widgets.indexOf(btn), 1)[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initialize properties if not present
|
||||||
|
if (!node.properties) {
|
||||||
|
node.properties = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
await refreshPreview();
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
await refreshPreview();
|
||||||
|
}, 100);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,331 @@
|
|||||||
|
{
|
||||||
|
"id": "3eb93704-25f0-4511-b147-cf0403c5d060",
|
||||||
|
"revision": 0,
|
||||||
|
"last_node_id": 10,
|
||||||
|
"last_link_id": 11,
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"type": "PreviewImage",
|
||||||
|
"pos": [
|
||||||
|
756.945556640625,
|
||||||
|
406.1631774902344
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
140,
|
||||||
|
246
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 5,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 9
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "PreviewImage"
|
||||||
|
},
|
||||||
|
"widgets_values": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"type": "PreviewImage",
|
||||||
|
"pos": [
|
||||||
|
702.7532348632812,
|
||||||
|
830.3753662109375
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
140,
|
||||||
|
246
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 3,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "PreviewImage"
|
||||||
|
},
|
||||||
|
"widgets_values": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"type": "FIBOEditNode",
|
||||||
|
"pos": [
|
||||||
|
390.8927307128906,
|
||||||
|
339.14031982421875
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
278.720703125,
|
||||||
|
266
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 4,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mask",
|
||||||
|
"shape": 7,
|
||||||
|
"type": "MASK",
|
||||||
|
"link": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "structured_instruction",
|
||||||
|
"shape": 7,
|
||||||
|
"type": "STRING",
|
||||||
|
"widget": {
|
||||||
|
"name": "structured_instruction"
|
||||||
|
},
|
||||||
|
"link": 11
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "IMAGE",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"links": [
|
||||||
|
9
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "structured_instruction",
|
||||||
|
"type": "STRING",
|
||||||
|
"links": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "seed",
|
||||||
|
"type": "INT",
|
||||||
|
"links": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "FIBOEditNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
50,
|
||||||
|
5,
|
||||||
|
1199,
|
||||||
|
"randomize"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"type": "FIBOEditNode",
|
||||||
|
"pos": [
|
||||||
|
301.3495788574219,
|
||||||
|
884.5100708007812
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
278.720703125,
|
||||||
|
266
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 1,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mask",
|
||||||
|
"shape": 7,
|
||||||
|
"type": "MASK",
|
||||||
|
"link": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "IMAGE",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"links": [
|
||||||
|
5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "structured_instruction",
|
||||||
|
"type": "STRING",
|
||||||
|
"links": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "seed",
|
||||||
|
"type": "INT",
|
||||||
|
"links": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "FIBOEditNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"",
|
||||||
|
"change the lamp to a radio",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
50,
|
||||||
|
5,
|
||||||
|
55,
|
||||||
|
"randomize"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"type": "LoadImage",
|
||||||
|
"pos": [
|
||||||
|
-245.09060668945312,
|
||||||
|
882.640869140625
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
274.080078125,
|
||||||
|
314
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 0,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "IMAGE",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"links": [
|
||||||
|
4,
|
||||||
|
8,
|
||||||
|
10
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MASK",
|
||||||
|
"type": "MASK",
|
||||||
|
"links": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "LoadImage"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"0a9d91e579872d653daf3243df4598f0 (2).png",
|
||||||
|
"image"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"type": "FIBOEditStructuredInstructionNode",
|
||||||
|
"pos": [
|
||||||
|
-139.52833557128906,
|
||||||
|
410.5189208984375
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
314.6372985839844,
|
||||||
|
82
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 2,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "structured_instruction",
|
||||||
|
"type": "STRING",
|
||||||
|
"links": [
|
||||||
|
11
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "FIBOEditStructuredInstructionNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
[
|
||||||
|
4,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
4,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5,
|
||||||
|
4,
|
||||||
|
0,
|
||||||
|
6,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
8,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
9,
|
||||||
|
8,
|
||||||
|
0,
|
||||||
|
7,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
10,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
8,
|
||||||
|
2,
|
||||||
|
"STRING"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"groups": [],
|
||||||
|
"config": {},
|
||||||
|
"extra": {
|
||||||
|
"ds": {
|
||||||
|
"scale": 0.7522123482651067,
|
||||||
|
"offset": [
|
||||||
|
684.0553164416729,
|
||||||
|
-258.9658284524182
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"frontendVersion": "1.25.11"
|
||||||
|
},
|
||||||
|
"version": 0.4
|
||||||
|
}
|
||||||
@@ -0,0 +1,495 @@
|
|||||||
|
{
|
||||||
|
"id": "17df2a89-3a7b-4b17-9ed1-fe236151bd3c",
|
||||||
|
"revision": 0,
|
||||||
|
"last_node_id": 30,
|
||||||
|
"last_link_id": 34,
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": 23,
|
||||||
|
"type": "PreviewVideoURLNode",
|
||||||
|
"pos": [
|
||||||
|
1146.3548583984375,
|
||||||
|
426.4760437011719
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
270,
|
||||||
|
177.875
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 9,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "video_url",
|
||||||
|
"type": "STRING",
|
||||||
|
"widget": {
|
||||||
|
"name": "video_url"
|
||||||
|
},
|
||||||
|
"link": 28
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "PreviewVideoURLNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 24,
|
||||||
|
"type": "PreviewVideoURLNode",
|
||||||
|
"pos": [
|
||||||
|
285.0629577636719,
|
||||||
|
889.9742431640625
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
270,
|
||||||
|
177.875
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 5,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "video_url",
|
||||||
|
"type": "STRING",
|
||||||
|
"widget": {
|
||||||
|
"name": "video_url"
|
||||||
|
},
|
||||||
|
"link": 29
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "PreviewVideoURLNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 28,
|
||||||
|
"type": "PreviewVideoURLNode",
|
||||||
|
"pos": [
|
||||||
|
560.3937377929688,
|
||||||
|
-124.61785125732422
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
270,
|
||||||
|
177.875
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 7,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "video_url",
|
||||||
|
"type": "STRING",
|
||||||
|
"widget": {
|
||||||
|
"name": "video_url"
|
||||||
|
},
|
||||||
|
"link": 33
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "PreviewVideoURLNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 29,
|
||||||
|
"type": "PreviewVideoURLNode",
|
||||||
|
"pos": [
|
||||||
|
484.5365295410156,
|
||||||
|
-391.69635009765625
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
270,
|
||||||
|
177.875
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 8,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "video_url",
|
||||||
|
"type": "STRING",
|
||||||
|
"widget": {
|
||||||
|
"name": "video_url"
|
||||||
|
},
|
||||||
|
"link": 34
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "PreviewVideoURLNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"type": "LoadVideoFramesNode",
|
||||||
|
"pos": [
|
||||||
|
-898.2652587890625,
|
||||||
|
20.335519790649414
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
270,
|
||||||
|
554
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 0,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "video_path",
|
||||||
|
"type": "STRING",
|
||||||
|
"links": [
|
||||||
|
24,
|
||||||
|
25,
|
||||||
|
26,
|
||||||
|
31,
|
||||||
|
32
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "LoadVideoFramesNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"plane.mp4",
|
||||||
|
"image"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 27,
|
||||||
|
"type": "VideoIncreaseResolutionNode",
|
||||||
|
"pos": [
|
||||||
|
-83.17711639404297,
|
||||||
|
-394.345458984375
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
318.79296875,
|
||||||
|
154
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 4,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "video_url",
|
||||||
|
"type": "STRING",
|
||||||
|
"widget": {
|
||||||
|
"name": "video_url"
|
||||||
|
},
|
||||||
|
"link": 32
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "result_video_url",
|
||||||
|
"type": "STRING",
|
||||||
|
"links": [
|
||||||
|
34
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "VideoIncreaseResolutionNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
"",
|
||||||
|
"2",
|
||||||
|
"mp4_h264",
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26,
|
||||||
|
"type": "RemoveVideoBackgroundNode",
|
||||||
|
"pos": [
|
||||||
|
22.839176177978516,
|
||||||
|
-98.48242950439453
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
318.79296875,
|
||||||
|
130
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 3,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "video_url",
|
||||||
|
"type": "STRING",
|
||||||
|
"widget": {
|
||||||
|
"name": "video_url"
|
||||||
|
},
|
||||||
|
"link": 31
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "result_video_url",
|
||||||
|
"type": "STRING",
|
||||||
|
"links": [
|
||||||
|
33
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "RemoveVideoBackgroundNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
"webm_vp9"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"type": "VideoMaskByPromptNode",
|
||||||
|
"pos": [
|
||||||
|
53.91518783569336,
|
||||||
|
377.6769714355469
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
318.79296875,
|
||||||
|
154
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 2,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "video_url",
|
||||||
|
"type": "STRING",
|
||||||
|
"widget": {
|
||||||
|
"name": "video_url"
|
||||||
|
},
|
||||||
|
"link": 25
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "mask_url",
|
||||||
|
"type": "STRING",
|
||||||
|
"links": [
|
||||||
|
27
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "VideoMaskByPromptNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"airplane",
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
"",
|
||||||
|
"mp4_h264",
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"type": "VideoEraseElementsNode",
|
||||||
|
"pos": [
|
||||||
|
658.7935180664062,
|
||||||
|
377.9380187988281
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
318.79296875,
|
||||||
|
154
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 6,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "video_url",
|
||||||
|
"type": "STRING",
|
||||||
|
"widget": {
|
||||||
|
"name": "video_url"
|
||||||
|
},
|
||||||
|
"link": 26
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mask_url",
|
||||||
|
"shape": 7,
|
||||||
|
"type": "STRING",
|
||||||
|
"widget": {
|
||||||
|
"name": "mask_url"
|
||||||
|
},
|
||||||
|
"link": 27
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "result_video_url",
|
||||||
|
"type": "STRING",
|
||||||
|
"links": [
|
||||||
|
28
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "VideoEraseElementsNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"mp4_h264",
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"type": "VideoSolidColorBackgroundNode",
|
||||||
|
"pos": [
|
||||||
|
-133.50149536132812,
|
||||||
|
856.9811401367188
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
318.79296875,
|
||||||
|
154
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 1,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "video_url",
|
||||||
|
"type": "STRING",
|
||||||
|
"widget": {
|
||||||
|
"name": "video_url"
|
||||||
|
},
|
||||||
|
"link": 24
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "result_video_url",
|
||||||
|
"type": "STRING",
|
||||||
|
"links": [
|
||||||
|
29
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "VideoSolidColorBackgroundNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
"",
|
||||||
|
"Transparent",
|
||||||
|
"webm_vp9",
|
||||||
|
true
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
[
|
||||||
|
24,
|
||||||
|
19,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
0,
|
||||||
|
"STRING"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
25,
|
||||||
|
19,
|
||||||
|
0,
|
||||||
|
21,
|
||||||
|
0,
|
||||||
|
"STRING"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
26,
|
||||||
|
19,
|
||||||
|
0,
|
||||||
|
22,
|
||||||
|
0,
|
||||||
|
"STRING"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
27,
|
||||||
|
21,
|
||||||
|
0,
|
||||||
|
22,
|
||||||
|
1,
|
||||||
|
"STRING"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
28,
|
||||||
|
22,
|
||||||
|
0,
|
||||||
|
23,
|
||||||
|
0,
|
||||||
|
"STRING"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
29,
|
||||||
|
20,
|
||||||
|
0,
|
||||||
|
24,
|
||||||
|
0,
|
||||||
|
"STRING"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
31,
|
||||||
|
19,
|
||||||
|
0,
|
||||||
|
26,
|
||||||
|
0,
|
||||||
|
"STRING"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
32,
|
||||||
|
19,
|
||||||
|
0,
|
||||||
|
27,
|
||||||
|
0,
|
||||||
|
"STRING"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
33,
|
||||||
|
26,
|
||||||
|
0,
|
||||||
|
28,
|
||||||
|
0,
|
||||||
|
"STRING"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34,
|
||||||
|
27,
|
||||||
|
0,
|
||||||
|
29,
|
||||||
|
0,
|
||||||
|
"STRING"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"groups": [],
|
||||||
|
"config": {},
|
||||||
|
"extra": {
|
||||||
|
"ds": {
|
||||||
|
"scale": 0.5644739300537782,
|
||||||
|
"offset": [
|
||||||
|
1311.5448975965508,
|
||||||
|
244.532748842859
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"frontendVersion": "1.25.11"
|
||||||
|
},
|
||||||
|
"version": 0.4
|
||||||
|
}
|
||||||
@@ -1 +1,491 @@
|
|||||||
{"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}
|
{
|
||||||
|
"id": "f47c8ffe-20e8-473c-a56b-c6241da2c469",
|
||||||
|
"revision": 0,
|
||||||
|
"last_node_id": 43,
|
||||||
|
"last_link_id": 78,
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": 31,
|
||||||
|
"type": "PreviewImage",
|
||||||
|
"pos": [
|
||||||
|
1668.734619140625,
|
||||||
|
796.7755737304688
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
210,
|
||||||
|
246
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 10,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 78
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "PreviewImage"
|
||||||
|
},
|
||||||
|
"widgets_values": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 29,
|
||||||
|
"type": "PreviewImage",
|
||||||
|
"pos": [
|
||||||
|
872.8858032226562,
|
||||||
|
679.9429321289062
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
210,
|
||||||
|
246
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 5,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 66
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "PreviewImage"
|
||||||
|
},
|
||||||
|
"widgets_values": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 38,
|
||||||
|
"type": "Note",
|
||||||
|
"pos": [
|
||||||
|
436.7110900878906,
|
||||||
|
566.2047119140625
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
306.28387451171875,
|
||||||
|
88
|
||||||
|
],
|
||||||
|
"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": 28,
|
||||||
|
"type": "LoadImage",
|
||||||
|
"pos": [
|
||||||
|
29.629886627197266,
|
||||||
|
676.8370361328125
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
315,
|
||||||
|
314
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 1,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "IMAGE",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"slot_index": 0,
|
||||||
|
"links": [
|
||||||
|
68
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MASK",
|
||||||
|
"type": "MASK",
|
||||||
|
"links": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "LoadImage"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"pexels-photo-1808399.jpeg",
|
||||||
|
"image"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 34,
|
||||||
|
"type": "LoadImage",
|
||||||
|
"pos": [
|
||||||
|
19.94045066833496,
|
||||||
|
1075.806640625
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
315,
|
||||||
|
314
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 2,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "IMAGE",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"slot_index": 0,
|
||||||
|
"links": [
|
||||||
|
69
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MASK",
|
||||||
|
"type": "MASK",
|
||||||
|
"links": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "LoadImage"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"quirky-red-brick-brick-wallpaper.jpg",
|
||||||
|
"image"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 36,
|
||||||
|
"type": "PreviewImage",
|
||||||
|
"pos": [
|
||||||
|
455.6301574707031,
|
||||||
|
1240.017578125
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
210,
|
||||||
|
246
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 6,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 71
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "PreviewImage"
|
||||||
|
},
|
||||||
|
"widgets_values": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 40,
|
||||||
|
"type": "RmbgNode",
|
||||||
|
"pos": [
|
||||||
|
414.095703125,
|
||||||
|
749.6687622070312
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
347.462890625,
|
||||||
|
130
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 3,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 68
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "output_images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"links": [
|
||||||
|
66,
|
||||||
|
74
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "RmbgNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 41,
|
||||||
|
"type": "RemoveForegroundNode",
|
||||||
|
"pos": [
|
||||||
|
397.44732666015625,
|
||||||
|
1127.818359375
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
347.462890625,
|
||||||
|
130
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 4,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 69
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "output_images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"links": [
|
||||||
|
71,
|
||||||
|
75
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "RemoveForegroundNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 42,
|
||||||
|
"type": "ReplaceBgNode",
|
||||||
|
"pos": [
|
||||||
|
800.56884765625,
|
||||||
|
1050.5228271484375
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
347.462890625,
|
||||||
|
318
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 7,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 74
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ref_images",
|
||||||
|
"shape": 7,
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 75
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "output_images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"links": [
|
||||||
|
72,
|
||||||
|
77
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "ReplaceBgNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
"base",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"681794",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 43,
|
||||||
|
"type": "ImageExpansionNode",
|
||||||
|
"pos": [
|
||||||
|
1241.743896484375,
|
||||||
|
731.8318481445312
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
347.462890625,
|
||||||
|
322
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 9,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 77
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "output_images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"links": [
|
||||||
|
78
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "ImageExpansionNode"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"1000, 1000",
|
||||||
|
"None",
|
||||||
|
"",
|
||||||
|
"681794",
|
||||||
|
"Ugly, mutated",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 33,
|
||||||
|
"type": "PreviewImage",
|
||||||
|
"pos": [
|
||||||
|
1316.2191162109375,
|
||||||
|
1169.61962890625
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
210,
|
||||||
|
246
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 8,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 72
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "PreviewImage"
|
||||||
|
},
|
||||||
|
"widgets_values": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
[
|
||||||
|
66,
|
||||||
|
40,
|
||||||
|
0,
|
||||||
|
29,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
68,
|
||||||
|
28,
|
||||||
|
0,
|
||||||
|
40,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
69,
|
||||||
|
34,
|
||||||
|
0,
|
||||||
|
41,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
71,
|
||||||
|
41,
|
||||||
|
0,
|
||||||
|
36,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
72,
|
||||||
|
42,
|
||||||
|
0,
|
||||||
|
33,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
74,
|
||||||
|
40,
|
||||||
|
0,
|
||||||
|
42,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
75,
|
||||||
|
41,
|
||||||
|
0,
|
||||||
|
42,
|
||||||
|
1,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
77,
|
||||||
|
42,
|
||||||
|
0,
|
||||||
|
43,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
78,
|
||||||
|
43,
|
||||||
|
0,
|
||||||
|
31,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"groups": [],
|
||||||
|
"config": {},
|
||||||
|
"extra": {
|
||||||
|
"ds": {
|
||||||
|
"scale": 0.61159090448415,
|
||||||
|
"offset": [
|
||||||
|
138.07082928418134,
|
||||||
|
-488.9478800208311
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_versions": {
|
||||||
|
"comfyui-bria-api": "c72754d15b53a13ee0c0419d70401232c56b7fdb",
|
||||||
|
"comfy-core": "v0.3.8-1-gc441048",
|
||||||
|
"ComfyUI-Jjk-Nodes": "b3c99bb78a99551776b5eab1a820e1cd58f84f31"
|
||||||
|
},
|
||||||
|
"frontendVersion": "1.25.11"
|
||||||
|
},
|
||||||
|
"version": 0.4
|
||||||
|
}
|
||||||
@@ -0,0 +1,661 @@
|
|||||||
|
{
|
||||||
|
"id": "1c31a92d-1e48-46b0-b4ad-60d537260922",
|
||||||
|
"revision": 0,
|
||||||
|
"last_node_id": 27,
|
||||||
|
"last_link_id": 46,
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"type": "PreviewImage",
|
||||||
|
"pos": [
|
||||||
|
1235.1895751953125,
|
||||||
|
10.958272933959961
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
140,
|
||||||
|
26
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 10,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 43
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "PreviewImage"
|
||||||
|
},
|
||||||
|
"widgets_values": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"type": "PreviewImage",
|
||||||
|
"pos": [
|
||||||
|
774.4347534179688,
|
||||||
|
-58.6872444152832
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
140,
|
||||||
|
26
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 6,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 29
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "PreviewImage"
|
||||||
|
},
|
||||||
|
"widgets_values": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": "Note",
|
||||||
|
"pos": [
|
||||||
|
-175.84976196289062,
|
||||||
|
102.76270294189453
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
210,
|
||||||
|
88
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 0,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {},
|
||||||
|
"widgets_values": [
|
||||||
|
"If you would like to start with prompt"
|
||||||
|
],
|
||||||
|
"color": "#432",
|
||||||
|
"bgcolor": "#653"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"type": "Note",
|
||||||
|
"pos": [
|
||||||
|
-225.1866455078125,
|
||||||
|
624.7235107421875
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
210,
|
||||||
|
88
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 1,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {},
|
||||||
|
"widgets_values": [
|
||||||
|
"If you would like to start with reference image + prompt"
|
||||||
|
],
|
||||||
|
"color": "#432",
|
||||||
|
"bgcolor": "#653"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"type": "PreviewImage",
|
||||||
|
"pos": [
|
||||||
|
719.5359497070312,
|
||||||
|
443.11285400390625
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
140,
|
||||||
|
26
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 8,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 36
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "PreviewImage"
|
||||||
|
},
|
||||||
|
"widgets_values": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"type": "PreviewImage",
|
||||||
|
"pos": [
|
||||||
|
1132.783935546875,
|
||||||
|
517.3931884765625
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
140,
|
||||||
|
26
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 11,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": 44
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "PreviewImage"
|
||||||
|
},
|
||||||
|
"widgets_values": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"type": "GenerateStructuredPromptLiteNodeV2",
|
||||||
|
"pos": [
|
||||||
|
68.9760971069336,
|
||||||
|
82.11421966552734
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
310.7749938964844,
|
||||||
|
174
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 2,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"shape": 7,
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "structured_prompt",
|
||||||
|
"type": "STRING",
|
||||||
|
"links": [
|
||||||
|
27
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "seed",
|
||||||
|
"type": "INT",
|
||||||
|
"links": [
|
||||||
|
28
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "GenerateStructuredPromptLiteNodeV2"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
123456,
|
||||||
|
"randomize"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 24,
|
||||||
|
"type": "GenerateImageLiteNodeV2",
|
||||||
|
"pos": [
|
||||||
|
460.6510314941406,
|
||||||
|
44.938873291015625
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
270,
|
||||||
|
290
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 4,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"shape": 7,
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "structured_prompt",
|
||||||
|
"shape": 7,
|
||||||
|
"type": "STRING",
|
||||||
|
"widget": {
|
||||||
|
"name": "structured_prompt"
|
||||||
|
},
|
||||||
|
"link": 27
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "seed",
|
||||||
|
"shape": 7,
|
||||||
|
"type": "INT",
|
||||||
|
"widget": {
|
||||||
|
"name": "seed"
|
||||||
|
},
|
||||||
|
"link": 28
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "image",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"links": [
|
||||||
|
29
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "structured_prompt",
|
||||||
|
"type": "STRING",
|
||||||
|
"links": [
|
||||||
|
41
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "seed",
|
||||||
|
"type": "INT",
|
||||||
|
"links": [
|
||||||
|
42
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "GenerateImageLiteNodeV2"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
"",
|
||||||
|
"FIBO",
|
||||||
|
"",
|
||||||
|
"1:1",
|
||||||
|
8,
|
||||||
|
5,
|
||||||
|
123456,
|
||||||
|
"randomize"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26,
|
||||||
|
"type": "RefineImageLiteNodeV2",
|
||||||
|
"pos": [
|
||||||
|
834.9641723632812,
|
||||||
|
46.47432327270508
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
270,
|
||||||
|
290
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 7,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "structured_prompt",
|
||||||
|
"type": "STRING",
|
||||||
|
"widget": {
|
||||||
|
"name": "structured_prompt"
|
||||||
|
},
|
||||||
|
"link": 41
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "seed",
|
||||||
|
"shape": 7,
|
||||||
|
"type": "INT",
|
||||||
|
"widget": {
|
||||||
|
"name": "seed"
|
||||||
|
},
|
||||||
|
"link": 42
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "image",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"links": [
|
||||||
|
43
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "structured_prompt",
|
||||||
|
"type": "STRING",
|
||||||
|
"links": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "seed",
|
||||||
|
"type": "INT",
|
||||||
|
"links": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "RefineImageLiteNodeV2"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"FIBO",
|
||||||
|
"1:1",
|
||||||
|
8,
|
||||||
|
5,
|
||||||
|
123456,
|
||||||
|
"randomize"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 25,
|
||||||
|
"type": "GenerateImageLiteNodeV2",
|
||||||
|
"pos": [
|
||||||
|
404.58843994140625,
|
||||||
|
532.5309448242188
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
270,
|
||||||
|
290
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 5,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"shape": 7,
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "structured_prompt",
|
||||||
|
"shape": 7,
|
||||||
|
"type": "STRING",
|
||||||
|
"widget": {
|
||||||
|
"name": "structured_prompt"
|
||||||
|
},
|
||||||
|
"link": 34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "seed",
|
||||||
|
"shape": 7,
|
||||||
|
"type": "INT",
|
||||||
|
"widget": {
|
||||||
|
"name": "seed"
|
||||||
|
},
|
||||||
|
"link": 35
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "image",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"links": [
|
||||||
|
36
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "structured_prompt",
|
||||||
|
"type": "STRING",
|
||||||
|
"links": [
|
||||||
|
45
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "seed",
|
||||||
|
"type": "INT",
|
||||||
|
"links": [
|
||||||
|
46
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "GenerateImageLiteNodeV2"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
"",
|
||||||
|
"FIBO",
|
||||||
|
"",
|
||||||
|
"1:1",
|
||||||
|
8,
|
||||||
|
5,
|
||||||
|
123456,
|
||||||
|
"randomize"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 27,
|
||||||
|
"type": "RefineImageLiteNodeV2",
|
||||||
|
"pos": [
|
||||||
|
820.26611328125,
|
||||||
|
558.9785766601562
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
270,
|
||||||
|
290
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 9,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "structured_prompt",
|
||||||
|
"type": "STRING",
|
||||||
|
"widget": {
|
||||||
|
"name": "structured_prompt"
|
||||||
|
},
|
||||||
|
"link": 45
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "seed",
|
||||||
|
"shape": 7,
|
||||||
|
"type": "INT",
|
||||||
|
"widget": {
|
||||||
|
"name": "seed"
|
||||||
|
},
|
||||||
|
"link": 46
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "image",
|
||||||
|
"type": "IMAGE",
|
||||||
|
"links": [
|
||||||
|
44
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "structured_prompt",
|
||||||
|
"type": "STRING",
|
||||||
|
"links": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "seed",
|
||||||
|
"type": "INT",
|
||||||
|
"links": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "RefineImageLiteNodeV2"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"FIBO",
|
||||||
|
"1:1",
|
||||||
|
8,
|
||||||
|
5,
|
||||||
|
123456,
|
||||||
|
"randomize"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"type": "GenerateStructuredPromptLiteNodeV2",
|
||||||
|
"pos": [
|
||||||
|
3.162916421890259,
|
||||||
|
587.301025390625
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
310.7749938964844,
|
||||||
|
174
|
||||||
|
],
|
||||||
|
"flags": {},
|
||||||
|
"order": 3,
|
||||||
|
"mode": 0,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"shape": 7,
|
||||||
|
"type": "IMAGE",
|
||||||
|
"link": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "structured_prompt",
|
||||||
|
"type": "STRING",
|
||||||
|
"links": [
|
||||||
|
34
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "seed",
|
||||||
|
"type": "INT",
|
||||||
|
"links": [
|
||||||
|
35
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Node name for S&R": "GenerateStructuredPromptLiteNodeV2"
|
||||||
|
},
|
||||||
|
"widgets_values": [
|
||||||
|
"BRIA_API_TOKEN",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
123456,
|
||||||
|
"randomize"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
[
|
||||||
|
27,
|
||||||
|
20,
|
||||||
|
0,
|
||||||
|
24,
|
||||||
|
1,
|
||||||
|
"STRING"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
28,
|
||||||
|
20,
|
||||||
|
1,
|
||||||
|
24,
|
||||||
|
2,
|
||||||
|
"INT"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
29,
|
||||||
|
24,
|
||||||
|
0,
|
||||||
|
6,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34,
|
||||||
|
21,
|
||||||
|
0,
|
||||||
|
25,
|
||||||
|
1,
|
||||||
|
"STRING"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
35,
|
||||||
|
21,
|
||||||
|
1,
|
||||||
|
25,
|
||||||
|
2,
|
||||||
|
"INT"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
36,
|
||||||
|
25,
|
||||||
|
0,
|
||||||
|
15,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
41,
|
||||||
|
24,
|
||||||
|
1,
|
||||||
|
26,
|
||||||
|
0,
|
||||||
|
"STRING"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42,
|
||||||
|
24,
|
||||||
|
2,
|
||||||
|
26,
|
||||||
|
1,
|
||||||
|
"INT"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
43,
|
||||||
|
26,
|
||||||
|
0,
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44,
|
||||||
|
27,
|
||||||
|
0,
|
||||||
|
17,
|
||||||
|
0,
|
||||||
|
"IMAGE"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
45,
|
||||||
|
25,
|
||||||
|
1,
|
||||||
|
27,
|
||||||
|
0,
|
||||||
|
"STRING"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
46,
|
||||||
|
25,
|
||||||
|
2,
|
||||||
|
27,
|
||||||
|
1,
|
||||||
|
"INT"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"groups": [],
|
||||||
|
"config": {},
|
||||||
|
"extra": {
|
||||||
|
"ds": {
|
||||||
|
"scale": 0.6276708501927047,
|
||||||
|
"offset": [
|
||||||
|
650.5506889681789,
|
||||||
|
132.55696596915172
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"frontendVersion": "1.25.11"
|
||||||
|
},
|
||||||
|
"version": 0.4
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user