Compare commits

...
Author SHA1 Message Date
Yazan Numoor 855ed814ed Merge branch 'main' into WAI-4253 2025-12-16 17:11:53 +02:00
Yazan Numoor 8f7edd926d Update pyproject.toml 2025-12-16 12:17:30 +02:00
Yazan Numoor c2cf2f044f Merge pull request #36 from Bria-AI/split_fibo_nodes
split_fibo_nodes
2025-12-16 12:16:37 +02:00
Ubuntu 5ccf16a4d3 split_fibo_nodes 2025-12-16 09:42:20 +00:00
Yazan Numoor 2cab356750 Merge pull request #34 from Bria-AI/update_Readme_file_for_Fibo
Update Readme.md
2025-12-16 08:28:20 +02:00
Yazan Numoor c9f880bdd8 Merge pull request #33 from Bria-AI/WAI-4234
WAI-4234
2025-12-16 08:27:11 +02:00
Ubuntu 82f31ddf72 refactor video flow 2025-12-15 11:23:41 +00:00
Ubuntu 00de95a160 update min steps for fibo 2025-12-11 20:35:51 +00:00
Ubuntu 15b69d2a03 fix_fibo_lite_steps 2025-12-11 14:01:19 +00:00
Ubuntu 1cfe5758f1 output the resukt url 2025-12-10 13:34:36 +00:00
Ubuntu 51e75b8f9e update fibo lite flow 2025-12-10 07:27:23 +00:00
Ubuntu 98322c3c96 update default steps for fibo lite 2025-12-10 07:20:19 +00:00
Ubuntu 862403893f fix default value 2025-12-09 08:31:08 +00:00
Ubuntu 0e0e59bbd4 fix default value 2025-12-08 11:25:00 +00:00
Ubuntu a611bddb9f support load, preview videos nodes 2025-12-08 11:08:19 +00:00
Ubuntu 6a94e8b91b WAI-4253 2025-12-03 10:23:07 +00:00
mabualrob1997 725868a7eb Update Readme.md 2025-12-01 16:46:53 +02:00
Ubuntu 0a81405f6a fix Gabi Feedback 2025-11-30 20:29:27 +00:00
Ubuntu ff8489b8d3 update version 2025-11-26 20:23:32 +00:00
Ubuntu 05406c1cef fix Structured prompt generate parameters 2025-11-26 20:17:22 +00:00
Ubuntu dffed52b27 WAI-4234 2025-11-23 20:22:26 +00:00
Ubuntu 3d7c41a15a WAI-4234 2025-11-23 20:11:25 +00:00
Yazan Numoor a5a88e2dbd Merge pull request #32 from Bria-AI/WAI-4174
WAI-4174
2025-11-20 11:43:06 +02:00
Ubuntu 64ab007f27 WAI-4174 2025-11-20 07:43:01 +00:00
Yazan Numoor 3ac0209193 Merge pull request #31 from Bria-AI/fix-invalid-token-error-message
fix invalid token error message
2025-11-12 14:31:14 +02:00
23 changed files with 3381 additions and 423 deletions
+40 -15
View File
@@ -31,28 +31,53 @@ To load a workflow, import the compatible workflow.json files from this [folder]
## 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** |
| --- | --- |
| **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. |
- **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.
### 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.
| Node | Description |
|------------------------|--------------------------------------------------------------------|
| **Text2Image Base** | Generates images from text prompts, serving as the foundation for text-based image creation. |
| **Text2Image Fast** | Optimized for speed, this node generates images from text prompts with faster results while maintaining quality. |
| **Text2Image HD** | Optimized for high-resolution outputs, this node generates detailed and sharp visuals from text prompts. |
| **Reimagine** | Guides image generation using both prompts and an input image. Preserve the original structure and depth while introducing new materials, colors, and textures. |
## Tailored Generation Nodes
These nodes use pre-trained tailored models to generate images that faithfully reproduce specific visual IP elements or guidelines.
+39 -1
View File
@@ -15,7 +15,11 @@ from .nodes import (
TailoredPortraitNode,
ReimagineNode,
GenerateImageNodeV2,
GenerateImageLiteNodeV2,
RefineImageNodeV2,
RefineImageLiteNodeV2,
GenerateStructuredPromptNodeV2,
GenerateStructuredPromptLiteNodeV2,
ShotByTextAutomaticNode,
ShotByImageManualPaddingNode,
ShotByImageAutomaticAspectRatioNode,
@@ -26,7 +30,15 @@ from .nodes import (
ShotByTextManualPlacementNode,
ShotByTextManualPaddingNode,
ShotByTextCustomCoordinatesNode,
AttributionByImageNode
AttributionByImageNode,
RemoveVideoBackgroundNode,
VideoSolidColorBackgroundNode,
VideoMaskByPromptNode,
VideoMaskByKeyPointsNode,
VideoIncreaseResolutionNode,
VideoEraseElementsNode,
LoadVideoFramesNode,
PreviewVideoURLNode
)
# Map the node class to a name used internally by ComfyUI
@@ -58,7 +70,19 @@ NODE_CLASS_MAPPINGS = {
"ReimagineNode": ReimagineNode,
"AttributionByImageNode": AttributionByImageNode,
"GenerateImageNodeV2": GenerateImageNodeV2,
"GenerateImageLiteNodeV2": GenerateImageLiteNodeV2,
"RefineImageNodeV2": RefineImageNodeV2,
"RefineImageLiteNodeV2": RefineImageLiteNodeV2,
"GenerateStructuredPromptNodeV2": GenerateStructuredPromptNodeV2,
"GenerateStructuredPromptLiteNodeV2": GenerateStructuredPromptLiteNodeV2,
"RemoveVideoBackgroundNode":RemoveVideoBackgroundNode,
"VideoSolidColorBackgroundNode":VideoSolidColorBackgroundNode,
"VideoMaskByPromptNode":VideoMaskByPromptNode,
"VideoMaskByKeyPointsNode":VideoMaskByKeyPointsNode,
"VideoIncreaseResolutionNode":VideoIncreaseResolutionNode,
"VideoEraseElementsNode":VideoEraseElementsNode,
"LoadVideoFramesNode":LoadVideoFramesNode,
"PreviewVideoURLNode":PreviewVideoURLNode
}
# Map the node display name to the one shown in the ComfyUI node interface
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -89,5 +113,19 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"ReimagineNode": "Bria Reimagine",
"AttributionByImageNode": "Attribution By Image Node",
"GenerateImageNodeV2": "Generate Image",
"GenerateImageLiteNodeV2": "Generate Image - Lite",
"RefineImageNodeV2": "Refine and Regenerate Image",
"RefineImageLiteNodeV2": "Refine Image - Lite",
"GenerateStructuredPromptNodeV2": "Generate Structured Prompt",
"GenerateStructuredPromptLiteNodeV2": "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"
}
+13
View File
@@ -12,7 +12,11 @@ from .text_2_image_fast_node import Text2ImageFastNode
from .text_2_image_hd_node import Text2ImageHDNode
from .reimagine_node import ReimagineNode
from .generate_image_node_v2 import GenerateImageNodeV2
from .generate_image_lite_node_v2 import GenerateImageLiteNodeV2
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_automatic_aspect_ratio_node import ShotByTextAutomaticAspectRatioNode
from .shot_by_text_automatic_node import ShotByTextAutomaticNode
@@ -28,3 +32,12 @@ from .shot_by_image_node import ShotByImageOriginalNode
from .shot_by_image_manual_placement_node import ShotByImageManualPlacementNode
from .shot_by_image_manual_padding_node import ShotByImageManualPaddingNode
from .attribution_by_image_node import AttributionByImageNode
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
+151
View File
@@ -0,0 +1,151 @@
import requests
import torch
from .common import (
deserialize_and_get_comfy_key,
postprocess_image,
preprocess_image,
image_to_base64,
poll_status_until_completed,
)
class GenerateImageLiteNodeV2:
"""Lite Image Generation Node"""
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": ("INT", {"default": 123456}),
},
}
RETURN_TYPES = ("IMAGE", "STRING", "INT")
RETURN_NAMES = ("image", "structured_prompt", "seed")
CATEGORY = "API Nodes"
FUNCTION = "execute"
def _validate_token(self, api_token: str):
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
raise Exception("Please insert a valid API token.")
def _build_payload(
self,
prompt,
model_version,
structured_prompt,
aspect_ratio,
steps_num,
guidance_scale,
seed,
images=None,
):
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
if images is not None:
if isinstance(images, torch.Tensor):
preprocess_images = preprocess_image(images)
payload["images"] = [image_to_base64(preprocess_images)]
return payload
def execute(
self,
api_token,
prompt,
model_version,
structured_prompt,
aspect_ratio,
steps_num,
guidance_scale,
seed,
images=None,
):
self._validate_token(api_token)
payload = self._build_payload(
prompt,
model_version,
structured_prompt,
aspect_ratio,
steps_num,
guidance_scale,
seed,
images,
)
api_token = deserialize_and_get_comfy_key(api_token)
headers = {"Content-Type": "application/json", "api_token": api_token}
try:
response = requests.post(self.api_url, json=payload, headers=headers)
if response.status_code in (200, 202):
print(
f"Initial request successful to {self.api_url}, polling for completion..."
)
response_dict = response.json()
status_url = response_dict.get("status_url")
request_id = response_dict.get("request_id")
if not status_url:
raise Exception("No status_url returned from API")
print(f"Request ID: {request_id}, Status URL: {status_url}")
final_response = poll_status_until_completed(status_url, api_token)
result = final_response.get("result", {})
result_image_url = result.get("image_url")
structured_prompt = result.get("structured_prompt", "")
used_seed = result.get("seed")
image_response = requests.get(result_image_url)
result_image = postprocess_image(image_response.content)
return (result_image, structured_prompt, used_seed)
raise Exception(
f"Error: API request failed with status code {response.status_code} {response.text}"
)
except Exception as e:
raise Exception(f"{e}")
+32 -18
View File
@@ -10,10 +10,10 @@ from .common import (
)
class _BaseGenerateImageNodeV2:
"""Base class for image generation nodes (standard & pro)."""
class GenerateImageNodeV2:
"""Standard Image Generation Node"""
api_url = None # Each subclass must define its API endpoint
api_url = "https://engine.prod.bria-api.com/v2/image/generate"
@classmethod
def INPUT_TYPES(cls):
@@ -24,14 +24,29 @@ class _BaseGenerateImageNodeV2:
},
"optional": {
"model_version": (["FIBO"], {"default": "FIBO"}),
"negative_prompt": ("STRING", {"default": ""}),
"structured_prompt": ("STRING", {"default": ""}),
"negative_prompt": ("STRING",),
"images": ("IMAGE",),
"aspect_ratio": (
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
{"default": "1:1"},
),
"steps_num": ("INT", {"default": 50, "min": 20, "max": 50}),
"guidance_scale": ("INT", {"default": 5, "min": 3, "max": 5}),
"steps_num": (
"INT",
{
"default": 50,
"min": 35,
"max": 50,
},
),
"guidance_scale": (
"INT",
{
"default": 5,
"min": 3,
"max": 5,
},
),
"seed": ("INT", {"default": 123456}),
},
}
@@ -41,6 +56,7 @@ class _BaseGenerateImageNodeV2:
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.")
@@ -49,29 +65,31 @@ class _BaseGenerateImageNodeV2:
self,
prompt,
model_version,
negative_prompt,
structured_prompt,
aspect_ratio,
steps_num,
guidance_scale,
seed,
negative_prompt=None,
images=None,
):
payload = {
"prompt": prompt,
"model_version": model_version,
"negative_prompt": negative_prompt,
"aspect_ratio": aspect_ratio,
"steps_num": steps_num,
"guidance_scale": guidance_scale,
"seed": seed,
"negative_prompt":negative_prompt
}
if structured_prompt:
payload["structured_prompt"] = structured_prompt
if images is not None:
if isinstance(images, torch.Tensor):
preprocess_images = preprocess_image(images)
payload["images"] = [image_to_base64(preprocess_images)]
return payload
def execute(
@@ -79,22 +97,24 @@ class _BaseGenerateImageNodeV2:
api_token,
prompt,
model_version,
negative_prompt,
structured_prompt,
aspect_ratio,
steps_num,
guidance_scale,
seed,
negative_prompt=None,
images=None,
):
self._validate_token(api_token)
payload = self._build_payload(
prompt,
model_version,
negative_prompt,
structured_prompt,
aspect_ratio,
steps_num,
guidance_scale,
seed,
negative_prompt,
images,
)
api_token = deserialize_and_get_comfy_key(api_token)
@@ -134,10 +154,4 @@ class _BaseGenerateImageNodeV2:
)
except Exception as e:
raise Exception(f"{e}")
class GenerateImageNodeV2(_BaseGenerateImageNodeV2):
"""Standard Image Generation Node"""
def __init__(self):
self.api_url = "https://engine.prod.bria-api.com/v2/image/generate"
raise Exception(f"{e}")
@@ -0,0 +1,106 @@
import requests
from .common import (
deserialize_and_get_comfy_key,
image_to_base64,
poll_status_until_completed,
preprocess_image,
)
import torch
class GenerateStructuredPromptLiteNodeV2:
"""Lite Structured Prompt Generation Node"""
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": ("INT", {"default": 123456}),
},
}
RETURN_TYPES = ("STRING", "INT")
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,
images=None
):
payload = {
"prompt": prompt,
"seed": seed,
}
if structured_prompt:
payload["structured_prompt"] = structured_prompt
if images is not None:
if isinstance(images, torch.Tensor):
preprocess_images = preprocess_image(images)
payload["images"] = [image_to_base64(preprocess_images)]
return payload
def execute(
self,
api_token,
prompt,
seed,
structured_prompt,
images=None,
):
self._validate_token(api_token)
payload = self._build_payload(
prompt,
seed,
structured_prompt,
images
)
api_token = deserialize_and_get_comfy_key(api_token)
headers = {"Content-Type": "application/json", "api_token": api_token}
try:
response = requests.post(self.api_url, json=payload, headers=headers)
if response.status_code in (200, 202):
print(
f"Initial request successful to {self.api_url}, polling for completion..."
)
response_dict = response.json()
status_url = response_dict.get("status_url")
request_id = response_dict.get("request_id")
if not status_url:
raise Exception("No status_url returned from API")
print(f"Request ID: {request_id}, Status URL: {status_url}")
final_response = poll_status_until_completed(status_url, api_token)
result = final_response.get("result", {})
structured_prompt = result.get("structured_prompt", "")
used_seed = result.get("seed", seed)
return (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}")
+105
View File
@@ -0,0 +1,105 @@
import requests
from .common import (
deserialize_and_get_comfy_key,
image_to_base64,
poll_status_until_completed,
preprocess_image,
)
import torch
class GenerateStructuredPromptNodeV2:
"""Standard Structured Prompt Generation Node"""
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": ("INT", {"default": 123456}),
},
}
RETURN_TYPES = ("STRING", "INT")
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,
images=None
):
payload = {
"prompt": prompt,
"seed": seed,
}
if structured_prompt:
payload["structured_prompt"] = structured_prompt
if images is not None:
if isinstance(images, torch.Tensor):
preprocess_images = preprocess_image(images)
payload["images"] = [image_to_base64(preprocess_images)]
return payload
def execute(
self,
api_token,
prompt,
seed,
structured_prompt,
images=None,
):
self._validate_token(api_token)
payload = self._build_payload(
prompt,
seed,
structured_prompt,
images
)
api_token = deserialize_and_get_comfy_key(api_token)
headers = {"Content-Type": "application/json", "api_token": api_token}
try:
response = requests.post(self.api_url, json=payload, headers=headers)
if response.status_code in (200, 202):
print(
f"Initial request successful to {self.api_url}, polling for completion..."
)
response_dict = response.json()
status_url = response_dict.get("status_url")
request_id = response_dict.get("request_id")
if not status_url:
raise Exception("No status_url returned from API")
print(f"Request ID: {request_id}, Status URL: {status_url}")
final_response = poll_status_until_completed(status_url, api_token)
result = final_response.get("result", {})
structured_prompt = result.get("structured_prompt", "")
used_seed = result.get("seed", seed)
return (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}")
+2 -1
View File
@@ -60,7 +60,8 @@ class GenFillNode():
"seed": seed,
"prompt_content_moderation":prompt_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 = {
+167
View File
@@ -0,0 +1,167 @@
import requests
from .common import deserialize_and_get_comfy_key, 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,
)
api_token = deserialize_and_get_comfy_key(api_token)
headers = {"Content-Type": "application/json", "api_token": api_token}
try:
response = requests.post(self.api_url, json=payload, headers=headers)
if response.status_code in (200, 202):
print(f"Initial refine request successful to {self.api_url}, polling for completion...")
response_dict = response.json()
status_url = response_dict.get("status_url")
request_id = response_dict.get("request_id")
if not status_url:
raise Exception("No status_url returned from API")
print(f"Request ID: {request_id}, Status URL: {status_url}")
final_response = poll_status_until_completed(status_url, api_token)
result = final_response.get("result", {})
structured_prompt = result.get("structured_prompt", "")
used_seed = result.get("seed", seed)
# Step 2 to call genearte image
payloadForImageGenetrate = {
"prompt": prompt,
"structured_prompt":structured_prompt,
"model_version": model_version,
"aspect_ratio": aspect_ratio,
"steps_num": steps_num,
"guidance_scale": guidance_scale,
"seed": used_seed,
}
headers = {"Content-Type": "application/json", "api_token": api_token}
response = requests.post(self.generate_api_url, json=payloadForImageGenetrate, headers=headers)
if response.status_code in (200, 202):
print(
f"Initial request successful to {self.generate_api_url}, polling for completion..."
)
response_dict = response.json()
status_url = response_dict.get("status_url")
request_id = response_dict.get("request_id")
if not status_url:
raise Exception("No status_url returned from API")
print(f"Request ID: {request_id}, Status URL: {status_url}")
final_response = poll_status_until_completed(status_url, api_token)
result = final_response.get("result", {})
result_image_url = result.get("image_url")
structured_prompt = result.get("structured_prompt", "")
used_seed = result.get("seed")
image_response = requests.get(result_image_url)
result_image = postprocess_image(image_response.content)
return (result_image, structured_prompt, used_seed)
raise Exception(
f"Error: API request failed with status code {response.status_code} {response.text}"
)
except Exception as e:
raise Exception(f"{e}")
+30 -22
View File
@@ -2,12 +2,11 @@ import requests
from .common import deserialize_and_get_comfy_key, poll_status_until_completed, postprocess_image
class _BaseRefineImageNodeV2:
"""Base class for refine image nodes (standard & pro)."""
api_url = None # Must be overridden by subclasses
generate_api_url = None
class RefineImageNodeV2:
"""Standard Refine Image Node"""
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
def INPUT_TYPES(cls):
return {
@@ -18,17 +17,32 @@ class _BaseRefineImageNodeV2:
},
"optional": {
"model_version": (["FIBO"], {"default": "FIBO"}),
"negative_prompt": ("STRING", {"default": ""}),
"aspect_ratio": (
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
{"default": "1:1"},
),
"steps_num": ("INT", {"default": 50, "min": 20, "max": 50}),
"guidance_scale": ("INT", {"default": 5, "min": 3, "max": 5}),
"steps_num": (
"INT",
{
"default": 50,
"min": 35,
"max": 50,
},
),
"guidance_scale": (
"INT",
{
"default": 5,
"min": 3,
"max": 5,
},
),
"seed": ("INT", {"default": 123456}),
"negative_prompt": ("STRING", {"default": ""}),
},
}
RETURN_TYPES = ("IMAGE", "STRING", "INT")
RETURN_NAMES = ("image", "structured_prompt", "seed")
CATEGORY = "API Nodes"
@@ -43,22 +57,23 @@ class _BaseRefineImageNodeV2:
prompt,
structured_prompt,
model_version,
negative_prompt,
aspect_ratio,
steps_num,
guidance_scale,
seed,
):
return {
payload = {
"prompt": prompt,
"model_version": model_version,
"negative_prompt": negative_prompt,
"aspect_ratio": aspect_ratio,
"steps_num": steps_num,
"guidance_scale": guidance_scale,
"seed": seed,
"structured_prompt": structured_prompt,
}
if structured_prompt:
payload["structured_prompt"] = structured_prompt
return payload
def execute(
self,
@@ -66,18 +81,17 @@ class _BaseRefineImageNodeV2:
prompt,
structured_prompt,
model_version,
negative_prompt,
aspect_ratio,
steps_num,
guidance_scale,
seed,
negative_prompt=None,
):
self._validate_token(api_token)
payload = self._build_payload(
prompt,
structured_prompt,
model_version,
negative_prompt,
aspect_ratio,
steps_num,
guidance_scale,
@@ -111,12 +125,13 @@ class _BaseRefineImageNodeV2:
"prompt": prompt,
"structured_prompt":structured_prompt,
"model_version": model_version,
"negative_prompt": negative_prompt,
"aspect_ratio": aspect_ratio,
"steps_num": steps_num,
"guidance_scale": guidance_scale,
"seed": used_seed,
"negative_prompt":negative_prompt
}
headers = {"Content-Type": "application/json", "api_token": api_token}
response = requests.post(self.generate_api_url, json=payloadForImageGenetrate, headers=headers)
@@ -152,10 +167,3 @@ class _BaseRefineImageNodeV2:
except Exception as e:
raise Exception(f"{e}")
class RefineImageNodeV2(_BaseRefineImageNodeV2):
"""Standard Refine Image Node"""
def __init__(self):
self.api_url = "https://engine.prod.bria-api.com/v2/structured_prompt/generate"
self.generate_api_url = "https://engine.prod.bria-api.com/v2/image/generate"
+52
View File
@@ -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,119 @@
import os
import uuid
import requests
import folder_paths
from ..common import deserialize_and_get_comfy_key, 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.")
api_key = deserialize_and_get_comfy_key(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 = {
"Content-Type": "application/json",
"api_token": f"{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,127 @@
import os
import uuid
import requests
import folder_paths
from ..common import deserialize_and_get_comfy_key, 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.")
api_key = deserialize_and_get_comfy_key(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 = {
"Content-Type": "application/json",
"api_token": f"{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,124 @@
import os
import uuid
import requests
import folder_paths
from ..common import deserialize_and_get_comfy_key, 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.")
api_key = deserialize_and_get_comfy_key(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 = {
"Content-Type": "application/json",
"api_token": f"{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,132 @@
import os
import uuid
import requests
import folder_paths
from ..common import deserialize_and_get_comfy_key, 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.")
api_key = deserialize_and_get_comfy_key(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 = {
"Content-Type": "application/json",
"api_token": f"{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,126 @@
import os
import uuid
import requests
import folder_paths
from ..common import deserialize_and_get_comfy_key, 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.")
api_key = deserialize_and_get_comfy_key(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 = {
"Content-Type": "application/json",
"api_token": f"{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,137 @@
import os
import uuid
import requests
import folder_paths
from ..common import deserialize_and_get_comfy_key, 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.")
api_key = deserialize_and_get_comfy_key(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 = {
"Content-Type": "application/json",
"api_token": f"{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
+69
View File
@@ -0,0 +1,69 @@
import os
import requests
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"
}
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
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-bria-api"
description = "Custom nodes for ComfyUI using BRIA's API."
version = "2.1.7"
version = "2.1.10"
license = {file = "LICENSE"}
[project.urls]
+495
View File
@@ -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
}
@@ -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