Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||
|
||||
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
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}")
|
||||
@@ -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}")
|
||||
@@ -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}")
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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}")
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
@@ -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]
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user