Compare commits
111
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
339f26b64f | ||
|
|
221f31068c | ||
|
|
d394eda558 | ||
|
|
54cadf8578 | ||
|
|
95e5c74266 | ||
|
|
e61b45a79a | ||
|
|
e8ad98c0a6 | ||
|
|
3e9599c0fb | ||
|
|
a52486bbb1 | ||
|
|
aa768b6cdf | ||
|
|
fda3d917b2 | ||
|
|
a970f19bf2 | ||
|
|
2c97deb804 | ||
|
|
e9b797ae90 | ||
|
|
2c6f9754d3 | ||
|
|
7dd5d3a250 | ||
|
|
ddd1252ecb | ||
|
|
812f7f88fa | ||
|
|
80332ca13c | ||
|
|
2b636d8a57 | ||
|
|
855ed814ed | ||
|
|
8f7edd926d | ||
|
|
c2cf2f044f | ||
|
|
5ccf16a4d3 | ||
|
|
2cab356750 | ||
|
|
c9f880bdd8 | ||
|
|
82f31ddf72 | ||
|
|
00de95a160 | ||
|
|
15b69d2a03 | ||
|
|
1cfe5758f1 | ||
|
|
51e75b8f9e | ||
|
|
98322c3c96 | ||
|
|
862403893f | ||
|
|
0e0e59bbd4 | ||
|
|
a611bddb9f | ||
|
|
6a94e8b91b | ||
|
|
725868a7eb | ||
|
|
0a81405f6a | ||
|
|
ff8489b8d3 | ||
|
|
05406c1cef | ||
|
|
dffed52b27 | ||
|
|
3d7c41a15a | ||
|
|
a5a88e2dbd | ||
|
|
64ab007f27 | ||
|
|
3ac0209193 | ||
|
|
b5d4d41bb8 | ||
|
|
0f2b1115c5 | ||
|
|
b408c8781a | ||
|
|
f6e134cfd3 | ||
|
|
7855c4dba2 | ||
|
|
c1f67dd4fb | ||
|
|
c1a77106b4 | ||
|
|
0f170bfc6b | ||
|
|
6b4b9e2dee | ||
|
|
4385a8c429 | ||
|
|
dd8d70000f | ||
|
|
582e16600e | ||
|
|
33f657ccbd | ||
|
|
f8a6650b4b | ||
|
|
493d23bdcb | ||
|
|
ecf52dd764 | ||
|
|
a9f894102d | ||
|
|
e50dd5dd63 | ||
|
|
00ce39dae5 | ||
|
|
270128d32a | ||
|
|
513fec79b5 | ||
|
|
ddbf7d0695 | ||
|
|
43893286cc | ||
|
|
3befc0a2ac | ||
|
|
af6ef2a829 | ||
|
|
96d2924dfc | ||
|
|
327ace887b | ||
|
|
bc5dacb9ef | ||
|
|
fc8aa8b6a7 | ||
|
|
9960a93044 | ||
|
|
8d3eef85ca | ||
|
|
bb4108b6c4 | ||
|
|
18a5ffbcca | ||
|
|
4f3b7fb77a | ||
|
|
c3b5fea335 | ||
|
|
b8e40e90bc | ||
|
|
9b3f15b7bd | ||
|
|
f7c9ed12b0 | ||
|
|
bdd053a81b | ||
|
|
2913a9eb6b | ||
|
|
080c17f4a4 | ||
|
|
711080ffb0 | ||
|
|
69f111bfe9 | ||
|
|
011f728b0d | ||
|
|
ac9678cd23 | ||
|
|
daede982ac | ||
|
|
e716e585df | ||
|
|
1296b6a27c | ||
|
|
19f15f4ba4 | ||
|
|
30d0154caf | ||
|
|
283665f654 | ||
|
|
03e3bec952 | ||
|
|
f1793ba9ad | ||
|
|
eda093ea66 | ||
|
|
165a103f0c | ||
|
|
ad2f4a5853 | ||
|
|
43f858dca4 | ||
|
|
538cd53ac8 | ||
|
|
bae0ed3842 | ||
|
|
a164f8ec45 | ||
|
|
ebe9e2e6b1 | ||
|
|
429c51ac6d | ||
|
|
aed4832984 | ||
|
|
a78aff0fb2 | ||
|
|
6edfc55109 | ||
|
|
a4855c1a1a |
@@ -7,17 +7,19 @@ on:
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
# if this is a forked repository. Skipping the workflow.
|
||||
if: github.event.repository.fork == false
|
||||
if: ${{ github.repository_owner == 'Bria-AI' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@main
|
||||
uses: Comfy-Org/publish-node-action@v1
|
||||
with:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
|
||||
+2
-1
@@ -1 +1,2 @@
|
||||
*.pyc
|
||||
*.pyc
|
||||
.idea
|
||||
@@ -13,7 +13,7 @@ An API token is required to use the nodes in your workflows. Get started quickly
|
||||
<img src="https://img.shields.io/badge/GET%20YOUR%20TOKEN-1000%20Free%20Calls-blue?style=flat-square" alt="Get Your Token" height="20">
|
||||
</a>.
|
||||
|
||||
For direct API Endpoint use, look for the endpoint in our of our API partners like: [**fal.ai**](https://fal.ai/models?keywords=bria).
|
||||
for direct API endpoint use, you can find our APIs through partners like [**fal.ai**](https://fal.ai/models?keywords=bria).
|
||||
For source code and weigths access, go to our [**Hugging Face**](https://huggingface.co/briaai) space.
|
||||
|
||||
To load a workflow, import the compatible workflow.json files from this [folder](workflows).
|
||||
@@ -30,14 +30,53 @@ To load a workflow, import the compatible workflow.json files from this [folder]
|
||||
# Available Nodes
|
||||
|
||||
## Image Generation Nodes
|
||||
These nodes create high-quality images from text or image prompts, generating photorealistic or artistic results with support for various aspect ratios.
|
||||
|
||||
| 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. |
|
||||
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 (FIBO)
|
||||
|
||||
Our V2 nodes utilize a state-of-the-art **two-step process** for enhanced control and consistency:
|
||||
|
||||
- **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.
|
||||
|
||||
**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.
|
||||
|
||||
## Tailored Generation Nodes
|
||||
These nodes use pre-trained tailored models to generate images that faithfully reproduce specific visual IP elements or guidelines.
|
||||
@@ -45,7 +84,8 @@ These nodes use pre-trained tailored models to generate images that faithfully r
|
||||
| Node | Description |
|
||||
|------------------------|--------------------------------------------------------------------|
|
||||
| **Tailored Gen** | Generates images using a trained tailored model, reproducing specific visual IP elements or guidelines. Use the Tailored Model Info node to load the model's default settings. |
|
||||
| **Tailored Model Info** | Retrieves the default settings and prompt prefix of a trained tailored model, which can be used to configure the Tailored Gen node. |
|
||||
| **Tailored Model Info**| Retrieves the default settings and prompt prefix of a trained tailored model, which can be used to configure the Tailored Gen node. |
|
||||
| **Restyle Portrait** | Transforms the style of a portrait while preserving the person's facial features. |
|
||||
|
||||
## Image Editing Nodes
|
||||
These nodes modify specific parts of images, enabling adjustments while maintaining the integrity of the rest of the image.
|
||||
@@ -67,6 +107,15 @@ These nodes create high-quality product images for eCommerce workflows.
|
||||
| **ShotByText** | Modifies an image's background by providing a text prompt. Powered by BRIA's ControlNet Background-Generation. |
|
||||
| **ShotByImage** | Modifies an image's background by providing a reference image. Uses BRIA's ControlNet Background-Generation and Image-Prompt. |
|
||||
|
||||
## Attribution Node
|
||||
|
||||
| Node | Description |
|
||||
|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Attribution By Image Node** | This node shares generated images via API for Bria to pay attribution to the data owners who contributed to the generation. Once the images are shared with Bria, Bria calculates the attribution, completes the payment on behalf of the user, and erases the images immediately. This node should be included in any workflow using nodes of Bria’s Models (not necessary for Bria’s API nodes). You can also refer to the [**API documentation**]( https://docs.bria.ai/bria-attribution-service/other/postattributionbyimage) |
|
||||
|
||||
|
||||
|
||||
|
||||
# Installation
|
||||
There are two methods to install the BRIA ComfyUI API nodes:
|
||||
|
||||
|
||||
+117
-7
@@ -1,16 +1,72 @@
|
||||
from .nodes import (EraserNode, GenFillNode, ImageExpansionNode, ReplaceBgNode, RmbgNode, RemoveForegroundNode, ShotByTextNode, ShotByImageNode, TailoredGenNode,
|
||||
TailoredModelInfoNode, Text2ImageBaseNode, Text2ImageFastNode, Text2ImageHDNode, TailoredPortraitNode,
|
||||
ReimagineNode)
|
||||
from .nodes import (
|
||||
EraserNode,
|
||||
GenFillNode,
|
||||
ImageExpansionNode,
|
||||
ImageEnhanceNode,
|
||||
ReplaceBgNode,
|
||||
RmbgNode,
|
||||
RemoveForegroundNode,
|
||||
ShotByTextOriginalNode,
|
||||
ShotByImageOriginalNode,
|
||||
TailoredGenNode,
|
||||
TailoredModelInfoNode,
|
||||
Text2ImageBaseNode,
|
||||
Text2ImageFastNode,
|
||||
Text2ImageHDNode,
|
||||
TailoredPortraitNode,
|
||||
ReimagineNode,
|
||||
GenerateImageNodeV2,
|
||||
GenerateImageLiteNodeV2,
|
||||
RefineImageNodeV2,
|
||||
RefineImageLiteNodeV2,
|
||||
GenerateStructuredPromptNodeV2,
|
||||
GenerateStructuredPromptLiteNodeV2,
|
||||
ShotByTextAutomaticNode,
|
||||
ShotByImageManualPaddingNode,
|
||||
ShotByImageAutomaticAspectRatioNode,
|
||||
ShotByImageCustomCoordinatesNode,
|
||||
ShotByImageManualPlacementNode,
|
||||
ShotByImageAutomaticNode,
|
||||
ShotByTextAutomaticAspectRatioNode,
|
||||
ShotByTextManualPlacementNode,
|
||||
ShotByTextManualPaddingNode,
|
||||
ShotByTextCustomCoordinatesNode,
|
||||
AttributionByImageNode,
|
||||
RemoveVideoBackgroundNode,
|
||||
VideoSolidColorBackgroundNode,
|
||||
VideoMaskByPromptNode,
|
||||
VideoMaskByKeyPointsNode,
|
||||
VideoIncreaseResolutionNode,
|
||||
VideoEraseElementsNode,
|
||||
LoadVideoFramesNode,
|
||||
PreviewVideoURLNode,
|
||||
FIBOEditNode,
|
||||
FIBOEditStructuredInstructionNode,
|
||||
BriaMultiImageSelect,
|
||||
ProductIntegrateNode
|
||||
)
|
||||
|
||||
# Map the node class to a name used internally by ComfyUI
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"BriaEraser": EraserNode, # Return the class, not an instance
|
||||
"BriaGenFill": GenFillNode,
|
||||
"ImageExpansionNode": ImageExpansionNode,
|
||||
"ImageEnhanceNode": ImageEnhanceNode,
|
||||
"ReplaceBgNode": ReplaceBgNode,
|
||||
"RmbgNode": RmbgNode,
|
||||
"RemoveForegroundNode": RemoveForegroundNode,
|
||||
"ShotByTextNode": ShotByTextNode,
|
||||
"ShotByImageNode": ShotByImageNode,
|
||||
"ShotByTextOriginal": ShotByTextOriginalNode,
|
||||
"ShotByImageOriginal": ShotByImageOriginalNode,
|
||||
"ShotByTextAutomatic": ShotByTextAutomaticNode,
|
||||
"ShotByTextManualPlacement": ShotByTextManualPlacementNode,
|
||||
"ShotByTextCustomCoordinates": ShotByTextCustomCoordinatesNode,
|
||||
"ShotByTextManualPadding": ShotByTextManualPaddingNode,
|
||||
"ShotByTextAutomaticAspectRatio": ShotByTextAutomaticAspectRatioNode,
|
||||
"ShotByImageAutomatic": ShotByImageAutomaticNode,
|
||||
"ShotByImageManualPlacement": ShotByImageManualPlacementNode,
|
||||
"ShotByImageCustomCoordinates": ShotByImageCustomCoordinatesNode,
|
||||
"ShotByImageManualPadding": ShotByImageManualPaddingNode,
|
||||
"ShotByImageAutomaticAspectRatio": ShotByImageAutomaticAspectRatioNode,
|
||||
"BriaTailoredGen": TailoredGenNode,
|
||||
"TailoredModelInfoNode": TailoredModelInfoNode,
|
||||
"TailoredPortraitNode": TailoredPortraitNode,
|
||||
@@ -18,21 +74,75 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Text2ImageFastNode": Text2ImageFastNode,
|
||||
"Text2ImageHDNode": Text2ImageHDNode,
|
||||
"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,
|
||||
"FIBOEditNode": FIBOEditNode,
|
||||
"FIBOEditStructuredInstructionNode": FIBOEditStructuredInstructionNode,
|
||||
"BriaMultiImageSelect":BriaMultiImageSelect,
|
||||
"ProductIntegrateNode": ProductIntegrateNode
|
||||
|
||||
}
|
||||
# Map the node display name to the one shown in the ComfyUI node interface
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"BriaEraser": "Bria Eraser",
|
||||
"BriaGenFill": "Bria GenFill",
|
||||
"ImageExpansionNode": "Bria Image Expansion",
|
||||
"ImageEnhanceNode": "Bria Image Enhance",
|
||||
"ReplaceBgNode": "Bria Replace Background",
|
||||
"RmbgNode": "Bria RMBG",
|
||||
"RemoveForegroundNode": "Bria Remove Foreground",
|
||||
"ShotByTextNode": "Bria Shot By Text",
|
||||
"ShotByImageNode": "Bria Shot By Image",
|
||||
"ShotByTextOriginal": "Shot by Text - Original",
|
||||
"ShotByImageOriginal": "Shot by Image - Original",
|
||||
"ShotByTextAutomatic": "Shot by Text - Automatic",
|
||||
"ShotByTextManualPlacement": "Shot by Text - Manual Placement",
|
||||
"ShotByTextCustomCoordinates": "Shot by Text - Custom Coordinates",
|
||||
"ShotByTextManualPadding": "Shot by Text - Manual Padding",
|
||||
"ShotByTextAutomaticAspectRatio": "Shot by Text - Automatic Aspect Ratio",
|
||||
"ShotByImageAutomatic": "Shot by Image - Automatic",
|
||||
"ShotByImageManualPlacement": "Shot by Image - Manual Placement",
|
||||
"ShotByImageCustomCoordinates": "Shot by Image - Custom Coordinates",
|
||||
"ShotByImageManualPadding": "Shot by Image - Manual Padding",
|
||||
"ShotByImageAutomaticAspectRatio": "Shot by Image - Automatic Aspect Ratio",
|
||||
"BriaTailoredGen": "Bria Tailored Gen",
|
||||
"TailoredModelInfoNode": "Bria Tailored Model Info",
|
||||
"TailoredPortraitNode": "Bria Restyle Portrait",
|
||||
"Text2ImageBaseNode": "Bria Text2Image Base",
|
||||
"Text2ImageFastNode": "Bria Text2Image Fast",
|
||||
"Text2ImageHDNode": "Bria Text2Image HD",
|
||||
"ReimagineNode": "Bria Reimagine",
|
||||
"AttributionByImageNode": "Attribution By Image Node",
|
||||
"GenerateImageNodeV2": "FIBO - Generate Image",
|
||||
"GenerateImageLiteNodeV2": "FIBO - Generate Image - Lite",
|
||||
"RefineImageNodeV2": "FIBO - Refine and Regenerate Image",
|
||||
"RefineImageLiteNodeV2": "FIBO - Refine Image - Lite",
|
||||
"GenerateStructuredPromptNodeV2": "FIBO - Generate Structured Prompt",
|
||||
"GenerateStructuredPromptLiteNodeV2": "FIBO - Generate Structured Prompt - Lite",
|
||||
"RemoveVideoBackgroundNode": "Bria Remove Video Background",
|
||||
"VideoSolidColorBackgroundNode":"Bria SolidColor Background Video",
|
||||
"VideoMaskByPromptNode":"Bria Video Mask By Prompt",
|
||||
"VideoMaskByKeyPointsNode":"Bria Video Mask By Key Points",
|
||||
"VideoIncreaseResolutionNode":"Bria Video Increase Resolution",
|
||||
"VideoEraseElementsNode":"Bria Video Erase Elements",
|
||||
"LoadVideoFramesNode":"Bria Load Video",
|
||||
"PreviewVideoURLNode":"Bria Preview Video",
|
||||
"FIBOEditNode": "FIBO - Edit",
|
||||
"FIBOEditStructuredInstructionNode": "FIBO - Edit - Structured Instruction",
|
||||
"BriaMultiImageSelect":"Bria Multi Image Select",
|
||||
"ProductIntegrateNode": "Bria Product Integrate"
|
||||
|
||||
}
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
+35
-3
@@ -1,15 +1,47 @@
|
||||
from .eraser_node import EraserNode
|
||||
from .generative_fill_node import GenFillNode
|
||||
from .image_expansion_node import ImageExpansionNode
|
||||
from .image_enhance_node import ImageEnhanceNode
|
||||
from .replace_bg_node import ReplaceBgNode
|
||||
from .rmbg_node import RmbgNode
|
||||
from .remove_foreground_node import RemoveForegroundNode
|
||||
from .shot_by_text_node import ShotByTextNode
|
||||
from .shot_by_image_node import ShotByImageNode
|
||||
from .tailored_gen_node import TailoredGenNode
|
||||
from .tailored_model_info_node import TailoredModelInfoNode
|
||||
from .tailored_portrait_node import TailoredPortraitNode
|
||||
from .text_2_image_base_node import Text2ImageBaseNode
|
||||
from .text_2_image_fast_node import Text2ImageFastNode
|
||||
from .text_2_image_hd_node import Text2ImageHDNode
|
||||
from .reimagine_node import ReimagineNode
|
||||
from .reimagine_node import ReimagineNode
|
||||
from .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
|
||||
from .shot_by_text_custom_coordinates_node import ShotByTextCustomCoordinatesNode
|
||||
from .shot_by_text_manual_placement_node import ShotByTextManualPlacementNode
|
||||
from .shot_by_text_manual_padding_node import ShotByTextManualPaddingNode
|
||||
from .shot_by_image_automatic_aspect_ratio_node import (
|
||||
ShotByImageAutomaticAspectRatioNode,
|
||||
)
|
||||
from .shot_by_image_automatic_node import ShotByImageAutomaticNode
|
||||
from .shot_by_image_custom_coordinates_node import ShotByImageCustomCoordinatesNode
|
||||
from .shot_by_image_node import ShotByImageOriginalNode
|
||||
from .shot_by_image_manual_placement_node import ShotByImageManualPlacementNode
|
||||
from .shot_by_image_manual_padding_node import ShotByImageManualPaddingNode
|
||||
from .attribution_by_image_node import AttributionByImageNode
|
||||
from .video_nodes.remove_video_background_node import RemoveVideoBackgroundNode
|
||||
from .video_nodes.video_increase_resolution_node import VideoIncreaseResolutionNode
|
||||
from .video_nodes.video_solid_color_background_node import VideoSolidColorBackgroundNode
|
||||
from .video_nodes.video_erase_elements_node import VideoEraseElementsNode
|
||||
from .video_nodes.video_mask_by_prompt_node import VideoMaskByPromptNode
|
||||
from .video_nodes.video_mask_by_key_points_node import VideoMaskByKeyPointsNode
|
||||
from .video_nodes.load_video import LoadVideoFramesNode
|
||||
from .video_nodes.preview_video_node_from_url import PreviewVideoURLNode
|
||||
from .fibo_edit_node import FIBOEditNode
|
||||
from .fibo_edit_structured_instruction_node import FIBOEditStructuredInstructionNode
|
||||
from .multi_image_select import BriaMultiImageSelect
|
||||
from .product_integrate_node import ProductIntegrateNode
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import requests
|
||||
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
normalize_images_input,
|
||||
poll_status_until_completed,
|
||||
to_pil_safe,
|
||||
)
|
||||
|
||||
class AttributionByImageNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"model_version": (["2.3", "3.0", "3.2"], {"default": "2.3"}),
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("api_response",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/attribution/by_image"
|
||||
|
||||
def execute(self, images, model_version, api_key):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
images = normalize_images_input(images)
|
||||
|
||||
batch_results = []
|
||||
|
||||
for idx, pil_image in enumerate(images):
|
||||
try:
|
||||
image_base64 = image_to_base64(pil_image)
|
||||
|
||||
payload = {
|
||||
"image": image_base64,
|
||||
"model_version": model_version,
|
||||
}
|
||||
|
||||
headers = bria_json_headers(api_key)
|
||||
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code not in (200, 202):
|
||||
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||
|
||||
# Poll until completion
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get('status_url')
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_key)
|
||||
content = str(final_response.get("result", {}).get("content", ""))
|
||||
batch_results.append(content)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[AttributionByImageNode] Skipping image {idx} due to error: {e}")
|
||||
batch_results.append("")
|
||||
|
||||
# Join all responses with a delimiter
|
||||
combined_response = "\n---\n".join(batch_results)
|
||||
return (combined_response,)
|
||||
+134
-16
@@ -5,7 +5,18 @@ import torch
|
||||
import base64
|
||||
from torchvision.transforms import ToPILImage
|
||||
import requests
|
||||
import time
|
||||
|
||||
|
||||
BRIA_COMFYUI_USER_AGENT = "bria/ComfyUI"
|
||||
|
||||
def bria_json_headers(api_token: str) -> dict:
|
||||
"""Headers for JSON POST requests to Bria API."""
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": api_token,
|
||||
"User-Agent": BRIA_COMFYUI_USER_AGENT,
|
||||
}
|
||||
def postprocess_image(image):
|
||||
result_image = Image.open(io.BytesIO(image))
|
||||
result_image = result_image.convert("RGB")
|
||||
@@ -31,6 +42,35 @@ def preprocess_image(image):
|
||||
print("Unexpected image dimensions. Expected 4D tensor.")
|
||||
return image
|
||||
|
||||
def to_pil_safe(image):
|
||||
"""
|
||||
Converts a single image tensor or numpy array (H,W,C) to PIL Image.
|
||||
Handles float32 in 0-1 and uint8.
|
||||
"""
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = image.detach().cpu().numpy()
|
||||
|
||||
# If image is empty, replace with 1x1 black
|
||||
if image.size == 0:
|
||||
image = np.zeros((1,1,3), dtype=np.uint8)
|
||||
|
||||
# Ensure float images are scaled 0-255
|
||||
if image.dtype in [np.float32, np.float64]:
|
||||
if image.max() <= 1.0:
|
||||
image = (image * 255).astype(np.uint8)
|
||||
else:
|
||||
image = image.astype(np.uint8)
|
||||
|
||||
# Handle grayscale images
|
||||
if image.ndim == 2:
|
||||
return Image.fromarray(image, mode="L")
|
||||
elif image.shape[2] == 3:
|
||||
return Image.fromarray(image, mode="RGB")
|
||||
elif image.shape[2] == 4:
|
||||
return Image.fromarray(image, mode="RGBA")
|
||||
else:
|
||||
raise ValueError(f"Cannot convert image with shape {image.shape} to PIL")
|
||||
|
||||
|
||||
def preprocess_mask(mask):
|
||||
if isinstance(mask, torch.Tensor):
|
||||
@@ -44,7 +84,7 @@ def preprocess_mask(mask):
|
||||
return mask
|
||||
|
||||
|
||||
def process_request(api_url, image, mask, api_key):
|
||||
def process_request(api_url, image, mask, api_key, visual_input_content_moderation, visual_output_content_moderation):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
|
||||
@@ -58,25 +98,34 @@ def process_request(api_url, image, mask, api_key):
|
||||
image_base64 = image_to_base64(image)
|
||||
mask_base64 = image_to_base64(mask)
|
||||
|
||||
# Prepare the API request payload
|
||||
# Prepare the API request payload for v2 API
|
||||
payload = {
|
||||
"file": f"{image_base64}",
|
||||
"mask_file": f"{mask_base64}"
|
||||
"image": image_base64,
|
||||
"mask": mask_base64,
|
||||
"visual_input_content_moderation":visual_input_content_moderation,
|
||||
"visual_output_content_moderation":visual_output_content_moderation
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
headers = bria_json_headers(api_key)
|
||||
|
||||
try:
|
||||
response = requests.post(api_url, json=payload, headers=headers)
|
||||
# Check for successful response
|
||||
if response.status_code == 200:
|
||||
print('response is 200')
|
||||
# Process the output image from API response
|
||||
response = requests.post(api_url, json=payload, headers=headers)
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
print('Initial request successful, polling for completion...')
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['result_url'])
|
||||
status_url = response_dict.get('status_url')
|
||||
request_id = response_dict.get('request_id')
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_key)
|
||||
result_image_url = final_response['result']['image_url']
|
||||
|
||||
# Download and process the result image
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = result_image.convert("RGBA")
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
@@ -84,9 +133,78 @@ def process_request(api_url, image, mask, api_key):
|
||||
# image_tensor = image_tensor = ToTensor()(output_image)
|
||||
# image_tensor = image_tensor.permute(1, 2, 0) / 255.0 # Shape now becomes [1, 2200, 1548, 3]
|
||||
# print(f"output tensor shape is: {image_tensor.shape}")
|
||||
return (result_image,)
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code}")
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
|
||||
|
||||
def poll_status_until_completed(status_url, api_key, timeout=360, check_interval=2):
|
||||
"""
|
||||
Poll a status URL until the status is COMPLETED or timeout is reached.
|
||||
|
||||
Args:
|
||||
status_url (str): The status URL to poll
|
||||
api_key (str): API token for authentication
|
||||
timeout (int): Maximum time to wait in seconds (default: 360)
|
||||
check_interval (int): Time between checks in seconds (default: 2)
|
||||
|
||||
Returns:
|
||||
dict: The final response containing the result
|
||||
|
||||
Raises:
|
||||
Exception: If timeout is reached or API request fails
|
||||
"""
|
||||
start_time = time.time()
|
||||
headers = bria_json_headers(api_key)
|
||||
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
response = requests.get(status_url, headers=headers)
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
response_dict = response.json()
|
||||
status = response_dict.get("status", "").upper()
|
||||
|
||||
if status == "COMPLETED":
|
||||
return response_dict
|
||||
elif status == "ERROR":
|
||||
raise Exception(f"Request failed: {response_dict}")
|
||||
else:
|
||||
print(f"Status: {status}, waiting...")
|
||||
time.sleep(check_interval)
|
||||
else:
|
||||
raise Exception(f"Status check failed with status code {response.status_code}")
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise Exception(f"Error checking status: {e}")
|
||||
|
||||
raise Exception(f"Timeout reached after {timeout} seconds")
|
||||
|
||||
|
||||
def normalize_images_input(images):
|
||||
"""
|
||||
Converts various image inputs into a list of PIL images:
|
||||
- PIL.Image → [PIL.Image]
|
||||
- list of PIL.Image → unchanged
|
||||
- torch.Tensor (H,W,C) → [PIL.Image]
|
||||
- torch.Tensor (B,H,W,C) → list of PIL.Images
|
||||
"""
|
||||
|
||||
|
||||
if isinstance(images, Image.Image):
|
||||
return [images]
|
||||
elif isinstance(images, list):
|
||||
return [to_pil_safe(img) if isinstance(img, torch.Tensor) else img for img in images]
|
||||
elif isinstance(images, torch.Tensor):
|
||||
if images.ndim == 3: # (H,W,C)
|
||||
return [to_pil_safe(images)]
|
||||
elif images.ndim == 4: # (B,H,W,C)
|
||||
return [to_pil_safe(img) for img in images]
|
||||
else:
|
||||
raise ValueError(f"Unsupported tensor shape: {images.shape}")
|
||||
else:
|
||||
raise ValueError(f"Unsupported input type: {type(images)}")
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@ class EraserNode():
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"mask": ("MASK",), # Binary mask input
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}) # API Key input with a default value
|
||||
},
|
||||
"optional": {
|
||||
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +21,9 @@ class EraserNode():
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/eraser" # Eraser API URL
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/erase" # Eraser API URL
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, mask, api_key):
|
||||
return process_request(self.api_url, image, mask, api_key)
|
||||
def execute(self, image, mask, api_key, visual_input_content_moderation, visual_output_content_moderation):
|
||||
return process_request(self.api_url, image, mask, api_key, visual_input_content_moderation, visual_output_content_moderation)
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import requests
|
||||
import torch
|
||||
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
poll_status_until_completed,
|
||||
preprocess_image,
|
||||
preprocess_mask,
|
||||
postprocess_image,
|
||||
)
|
||||
|
||||
|
||||
class FIBOEditNode:
|
||||
"""FIBO Edit Node - Edit images with instructions"""
|
||||
|
||||
api_url = "https://engine.prod.bria-api.com/v2/image/edit"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
"images": ("IMAGE",),
|
||||
},
|
||||
"optional": {
|
||||
"instruction": ("STRING",),
|
||||
"mask": ("MASK",),
|
||||
"structured_instruction": ("STRING",),
|
||||
"negative_prompt": ("STRING",),
|
||||
"steps_num": (
|
||||
"INT",
|
||||
{
|
||||
"default": 50,
|
||||
"min": 1,
|
||||
"max": 100,
|
||||
},
|
||||
),
|
||||
"guidance_scale": (
|
||||
"INT",
|
||||
{
|
||||
"default": 5,
|
||||
"min": 1,
|
||||
"max": 20,
|
||||
},
|
||||
),
|
||||
"seed": ("INT", {"default": 123456}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "STRING", "INT")
|
||||
RETURN_NAMES = ("IMAGE", "structured_instruction", "seed")
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def _validate_token(self, api_token: str):
|
||||
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API token.")
|
||||
|
||||
def _build_payload(
|
||||
self,
|
||||
instruction,
|
||||
images,
|
||||
mask=None,
|
||||
structured_instruction=None,
|
||||
negative_prompt=None,
|
||||
steps_num=50,
|
||||
guidance_scale=5,
|
||||
seed=123456,
|
||||
):
|
||||
# Process images
|
||||
if isinstance(images, torch.Tensor):
|
||||
processed_images = preprocess_image(images)
|
||||
else:
|
||||
processed_images = images
|
||||
|
||||
payload = {
|
||||
"images": [image_to_base64(processed_images)],
|
||||
"steps_num": steps_num,
|
||||
"guidance_scale": guidance_scale,
|
||||
"seed": seed,
|
||||
}
|
||||
|
||||
# Add optional mask
|
||||
if mask is not None:
|
||||
if isinstance(mask, torch.Tensor):
|
||||
processed_mask = preprocess_mask(mask)
|
||||
else:
|
||||
processed_mask = mask
|
||||
payload["mask"] = image_to_base64(processed_mask)
|
||||
|
||||
# Add optional structured_instruction
|
||||
if structured_instruction:
|
||||
payload["structured_instruction"] = structured_instruction
|
||||
# Add optional structured_instruction
|
||||
if instruction:
|
||||
payload["instruction"] = instruction
|
||||
# Add optional negative_prompt
|
||||
if negative_prompt:
|
||||
payload["negative_prompt"] = negative_prompt
|
||||
|
||||
return payload
|
||||
|
||||
def execute(
|
||||
self,
|
||||
api_token,
|
||||
instruction,
|
||||
images,
|
||||
mask=None,
|
||||
structured_instruction=None,
|
||||
negative_prompt=None,
|
||||
steps_num=50,
|
||||
guidance_scale=5,
|
||||
seed=123456,
|
||||
):
|
||||
self._validate_token(api_token)
|
||||
payload = self._build_payload(
|
||||
instruction,
|
||||
images,
|
||||
mask,
|
||||
structured_instruction,
|
||||
negative_prompt,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed,
|
||||
)
|
||||
headers = bria_json_headers(api_token)
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code in (200, 202):
|
||||
print(
|
||||
f"Initial request successful to {self.api_url}, polling for completion..."
|
||||
)
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get("status_url")
|
||||
request_id = response_dict.get("request_id")
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_token)
|
||||
|
||||
result = final_response.get("result", {})
|
||||
result_image_url = result.get("image_url")
|
||||
structured_prompt = result.get("structured_prompt", "")
|
||||
used_seed = result.get("seed")
|
||||
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = postprocess_image(image_response.content)
|
||||
|
||||
return (result_image, structured_prompt, used_seed)
|
||||
|
||||
raise Exception(
|
||||
f"Error: API request failed with status code {response.status_code} {response.text}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
@@ -0,0 +1,74 @@
|
||||
import requests
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
normalize_images_input,
|
||||
poll_status_until_completed,
|
||||
)
|
||||
|
||||
|
||||
class FIBOEditStructuredInstructionNode:
|
||||
"""FIBO Edit Structured Instruction Node - Generate structured instructions for image editing"""
|
||||
|
||||
api_url = "https://engine.prod.bria-api.com/v2/structured_instruction/generate"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
"images": ("IMAGE",),
|
||||
"instruction": ("STRING",),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("structured_instruction",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def _validate_token(self, api_token: str):
|
||||
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API token.")
|
||||
|
||||
def _build_payload(self, processed_image, instruction):
|
||||
payload = {
|
||||
"instruction": instruction,
|
||||
"images": [image_to_base64(processed_image)],
|
||||
}
|
||||
return payload
|
||||
|
||||
def execute(self, api_token, images, instruction):
|
||||
self._validate_token(api_token)
|
||||
# Normalize input to list of PIL images
|
||||
images = normalize_images_input(images)
|
||||
|
||||
batch_results = []
|
||||
|
||||
for idx, pil_image in enumerate(images):
|
||||
try:
|
||||
payload = self._build_payload(pil_image, instruction)
|
||||
headers = bria_json_headers(api_token)
|
||||
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code not in (200, 202):
|
||||
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||
|
||||
print(f"Initial request successful for image {idx}, polling for completion...")
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get("status_url")
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_token)
|
||||
result = final_response.get("result", {})
|
||||
structured_instruction = result.get("structured_instruction", "")
|
||||
batch_results.append(structured_instruction)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[FIBOEditStructuredInstructionNode] Skipping image {idx} due to error: {e}")
|
||||
batch_results.append("")
|
||||
|
||||
combined_instructions = "\n---\n".join(batch_results)
|
||||
return (combined_instructions,)
|
||||
@@ -0,0 +1,157 @@
|
||||
import requests
|
||||
import torch
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
normalize_images_input,
|
||||
poll_status_until_completed,
|
||||
postprocess_image,
|
||||
)
|
||||
class GenerateImageLiteNodeV2:
|
||||
"""Lite Image Generation Node (multi-image compatible)"""
|
||||
|
||||
api_url = "https://engine.prod.bria-api.com/v2/image/generate/lite"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
"prompt": ("STRING",),
|
||||
},
|
||||
"optional": {
|
||||
"model_version": (["FIBO"], {"default": "FIBO"}),
|
||||
"structured_prompt": ("STRING", {"default": ""}),
|
||||
"images": ("IMAGE",),
|
||||
"aspect_ratio": (
|
||||
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
|
||||
{"default": "1:1"},
|
||||
),
|
||||
"steps_num": ("INT", {"default": 8, "min": 8, "max": 30}),
|
||||
"guidance_scale": ("INT", {"default": 5, "min": 3, "max": 5}),
|
||||
"seed": ("STRING", {"default": "123456"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "STRING", "STRING")
|
||||
RETURN_NAMES = ("image", "structured_prompt", "seed")
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def _validate_token(self, api_token: str):
|
||||
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API token.")
|
||||
|
||||
def _build_payload(
|
||||
self,
|
||||
prompt,
|
||||
model_version,
|
||||
structured_prompt,
|
||||
aspect_ratio,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed,
|
||||
processed_image=None,
|
||||
):
|
||||
payload = {
|
||||
"prompt": prompt,
|
||||
"model_version": model_version,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"steps_num": steps_num,
|
||||
"guidance_scale": guidance_scale,
|
||||
"seed": int(seed),
|
||||
}
|
||||
if structured_prompt:
|
||||
payload["structured_prompt"] = structured_prompt
|
||||
if processed_image is not None:
|
||||
payload["images"] = [image_to_base64(processed_image)]
|
||||
return payload
|
||||
|
||||
def execute(
|
||||
self,
|
||||
api_token,
|
||||
prompt,
|
||||
model_version,
|
||||
structured_prompt,
|
||||
aspect_ratio,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed,
|
||||
images=None,
|
||||
):
|
||||
self._validate_token(api_token)
|
||||
images_list = normalize_images_input(images) if images is not None else [None]
|
||||
|
||||
# Structured prompts per image
|
||||
if isinstance(structured_prompt, str):
|
||||
structured_prompts_list = structured_prompt.split("\n---\n")
|
||||
elif isinstance(structured_prompt, list):
|
||||
structured_prompts_list = structured_prompt
|
||||
else:
|
||||
structured_prompts_list = [""] * len(images_list)
|
||||
if len(structured_prompts_list) < len(images_list):
|
||||
structured_prompts_list += [structured_prompts_list[-1]] * (len(images_list) - len(structured_prompts_list))
|
||||
|
||||
# Seeds per image
|
||||
if isinstance(seed, str):
|
||||
seed_values = [int(s.strip()) for s in seed.split(",")]
|
||||
else:
|
||||
seed_values = [int(seed)]
|
||||
if len(seed_values) < len(images_list):
|
||||
seed_values += [seed_values[-1]] * (len(images_list) - len(seed_values))
|
||||
|
||||
batch_results = []
|
||||
batch_structured_prompts = []
|
||||
batch_seeds = []
|
||||
|
||||
for idx, ref_image in enumerate(images_list):
|
||||
try:
|
||||
|
||||
payload = self._build_payload(
|
||||
prompt,
|
||||
model_version,
|
||||
structured_prompts_list[idx],
|
||||
aspect_ratio,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed_values[idx],
|
||||
ref_image,
|
||||
)
|
||||
|
||||
headers = bria_json_headers(api_token)
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
if response.status_code not in (200, 202):
|
||||
raise Exception(
|
||||
f"API request failed with status code {response.status_code}: {response.text}"
|
||||
)
|
||||
|
||||
print(f"GenerateImageLiteNodeV2 - Initial request successful for image {idx}, polling for completion...")
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get("status_url")
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_token)
|
||||
result = final_response.get("result", {})
|
||||
result_image_url = result.get("image_url")
|
||||
structured_prompt_result = result.get("structured_prompt", "")
|
||||
used_seed = result.get("seed", seed_values[idx])
|
||||
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = postprocess_image(image_response.content)
|
||||
|
||||
batch_results.append(result_image)
|
||||
batch_structured_prompts.append(structured_prompt_result)
|
||||
batch_seeds.append(str(used_seed))
|
||||
|
||||
except Exception as e:
|
||||
print(f"[GenerateImageLiteNodeV2] Skipping iteration {idx} due to error: {e}")
|
||||
batch_results.append(torch.zeros((1, 512, 512, 3), dtype=torch.float32))
|
||||
batch_structured_prompts.append("")
|
||||
batch_seeds.append(str(seed_values[idx]))
|
||||
|
||||
output_batch = torch.cat(batch_results, dim=0)
|
||||
combined_structured_prompts = "\n---\n".join(batch_structured_prompts)
|
||||
combined_seeds = ",".join(batch_seeds)
|
||||
|
||||
return output_batch, combined_structured_prompts, combined_seeds
|
||||
@@ -0,0 +1,166 @@
|
||||
import requests
|
||||
import torch
|
||||
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
normalize_images_input,
|
||||
poll_status_until_completed,
|
||||
postprocess_image,
|
||||
)
|
||||
|
||||
|
||||
class GenerateImageNodeV2:
|
||||
"""Standard Image Generation Node (multi-image compatible)"""
|
||||
|
||||
api_url = "https://engine.prod.bria-api.com/v2/image/generate"
|
||||
|
||||
@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": ""}),
|
||||
"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": 35, "max": 50}),
|
||||
"guidance_scale": ("INT", {"default": 5, "min": 3, "max": 5}),
|
||||
"seed": ("STRING", {"default": "123456"}), # Accept string to match previous node
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "STRING", "STRING") # images, structured_prompts, seeds
|
||||
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,
|
||||
negative_prompt=None,
|
||||
processed_image=None,
|
||||
):
|
||||
payload = {
|
||||
"prompt": prompt,
|
||||
"model_version": model_version,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"steps_num": steps_num,
|
||||
"guidance_scale": guidance_scale,
|
||||
"seed": int(seed),
|
||||
}
|
||||
if structured_prompt:
|
||||
payload["structured_prompt"] = structured_prompt
|
||||
if negative_prompt:
|
||||
payload["negative_prompt"] = negative_prompt
|
||||
if processed_image is not None:
|
||||
payload["images"] = [image_to_base64(processed_image)]
|
||||
return payload
|
||||
|
||||
def execute(
|
||||
self,
|
||||
api_token,
|
||||
prompt,
|
||||
model_version,
|
||||
structured_prompt,
|
||||
aspect_ratio,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed,
|
||||
negative_prompt=None,
|
||||
images=None,
|
||||
):
|
||||
self._validate_token(api_token)
|
||||
images_list = normalize_images_input(images) if images is not None else [None]
|
||||
|
||||
# Structured prompts per image
|
||||
if isinstance(structured_prompt, str):
|
||||
structured_prompts_list = structured_prompt.split("\n---\n")
|
||||
elif isinstance(structured_prompt, list):
|
||||
structured_prompts_list = structured_prompt
|
||||
else:
|
||||
structured_prompts_list = [""] * len(images_list)
|
||||
if len(structured_prompts_list) < len(images_list):
|
||||
structured_prompts_list += [structured_prompts_list[-1]] * (len(images_list) - len(structured_prompts_list))
|
||||
|
||||
# Seeds per image
|
||||
if isinstance(seed, str):
|
||||
seed_values = [int(s.strip()) for s in seed.split(",")]
|
||||
else:
|
||||
seed_values = [int(seed)]
|
||||
if len(seed_values) < len(images_list):
|
||||
seed_values += [seed_values[-1]] * (len(images_list) - len(seed_values))
|
||||
|
||||
batch_results = []
|
||||
batch_structured_prompts = []
|
||||
batch_seeds = []
|
||||
|
||||
for idx, ref_image in enumerate(images_list):
|
||||
try:
|
||||
payload = self._build_payload(
|
||||
prompt,
|
||||
model_version,
|
||||
structured_prompts_list[idx],
|
||||
aspect_ratio,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed_values[idx],
|
||||
negative_prompt,
|
||||
ref_image,
|
||||
)
|
||||
|
||||
headers = bria_json_headers(api_token)
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code not in (200, 202):
|
||||
raise Exception(
|
||||
f"API request failed with status code {response.status_code}: {response.text}"
|
||||
)
|
||||
print(f"GenerateImageNodeV2 - Initial request successful for image {idx}, polling for completion...")
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get("status_url")
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_token)
|
||||
result = final_response.get("result", {})
|
||||
result_image_url = result.get("image_url")
|
||||
structured_prompt_result = result.get("structured_prompt", "")
|
||||
used_seed = result.get("seed", seed_values[idx])
|
||||
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = postprocess_image(image_response.content)
|
||||
|
||||
batch_results.append(result_image)
|
||||
batch_structured_prompts.append(structured_prompt_result)
|
||||
batch_seeds.append(str(used_seed))
|
||||
|
||||
except Exception as e:
|
||||
print(f"[GenerateImageNodeV2] Skipping iteration {idx} due to error: {e}")
|
||||
batch_results.append(torch.zeros((1, 512, 512, 3), dtype=torch.float32))
|
||||
batch_structured_prompts.append("")
|
||||
batch_seeds.append(str(seed_values[idx]))
|
||||
|
||||
# Return all as strings for proper chaining
|
||||
output_batch = torch.cat(batch_results, dim=0)
|
||||
combined_structured_prompts = "\n---\n".join(batch_structured_prompts)
|
||||
combined_seeds = ",".join(batch_seeds)
|
||||
|
||||
return output_batch, combined_structured_prompts, combined_seeds
|
||||
@@ -0,0 +1,110 @@
|
||||
import requests
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
normalize_images_input,
|
||||
poll_status_until_completed,
|
||||
)
|
||||
|
||||
|
||||
class GenerateStructuredPromptLiteNodeV2:
|
||||
"""Lite Structured Prompt Generation Node (multi-image compatible)"""
|
||||
|
||||
api_url = "https://engine.prod.bria-api.com/v2/structured_prompt/generate/lite"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
"prompt": ("STRING",),
|
||||
},
|
||||
"optional": {
|
||||
"structured_prompt": ("STRING",),
|
||||
"images": ("IMAGE",),
|
||||
"seed": ("STRING", {"default": "123456"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING") # structured_prompts, seeds as comma-separated string
|
||||
RETURN_NAMES = ("structured_prompt", "seed")
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def _validate_token(self, api_token: str):
|
||||
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API token.")
|
||||
|
||||
def _build_payload(self, prompt, seed, structured_prompt, processed_image=None):
|
||||
payload = {"prompt": prompt, "seed": int(seed)}
|
||||
if structured_prompt:
|
||||
payload["structured_prompt"] = structured_prompt
|
||||
if processed_image is not None:
|
||||
payload["images"] = [image_to_base64(processed_image)]
|
||||
return payload
|
||||
|
||||
def execute(self, api_token, prompt, seed, structured_prompt, images=None):
|
||||
self._validate_token(api_token)
|
||||
images_list = normalize_images_input(images) if images is not None else [None]
|
||||
|
||||
# Seeds per image
|
||||
if isinstance(seed, str):
|
||||
seed_values = [int(s.strip()) for s in seed.split(",")]
|
||||
else:
|
||||
seed_values = [seed]
|
||||
if len(seed_values) < len(images_list):
|
||||
seed_values += [seed_values[-1]] * (len(images_list) - len(seed_values))
|
||||
|
||||
# Structured prompts per image
|
||||
if isinstance(structured_prompt, str):
|
||||
structured_prompts_list = structured_prompt.split("\n---\n")
|
||||
elif isinstance(structured_prompt, list):
|
||||
structured_prompts_list = structured_prompt
|
||||
else:
|
||||
structured_prompts_list = [""] * len(images_list)
|
||||
if len(structured_prompts_list) < len(images_list):
|
||||
structured_prompts_list += [structured_prompts_list[-1]] * (len(images_list) - len(structured_prompts_list))
|
||||
|
||||
batch_structured_prompts = []
|
||||
batch_seeds = []
|
||||
|
||||
for idx, image in enumerate(images_list):
|
||||
try:
|
||||
|
||||
payload = self._build_payload(
|
||||
prompt,
|
||||
seed_values[idx],
|
||||
structured_prompts_list[idx],
|
||||
image,
|
||||
)
|
||||
|
||||
headers = bria_json_headers(api_token)
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
if response.status_code not in (200, 202):
|
||||
raise Exception(
|
||||
f"API request failed with status code {response.status_code}: {response.text}"
|
||||
)
|
||||
|
||||
response_dict = response.json()
|
||||
print(f"GenerateStructuredPromptLiteNodeV2 - Initial request successful for image {idx}, polling for completion...")
|
||||
|
||||
status_url = response_dict.get("status_url")
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_token)
|
||||
result = final_response.get("result", {})
|
||||
structured_prompt_result = result.get("structured_prompt", "")
|
||||
used_seed = result.get("seed", seed_values[idx])
|
||||
|
||||
batch_structured_prompts.append(structured_prompt_result)
|
||||
batch_seeds.append(str(used_seed))
|
||||
|
||||
except Exception as e:
|
||||
print(f"[GenerateStructuredPromptLiteNodeV2] Skipping iteration {idx} due to error: {e}")
|
||||
batch_structured_prompts.append("")
|
||||
batch_seeds.append(str(seed_values[idx]))
|
||||
|
||||
combined_prompts = "\n---\n".join(batch_structured_prompts)
|
||||
combined_seeds = ",".join(batch_seeds)
|
||||
return combined_prompts, combined_seeds
|
||||
@@ -0,0 +1,113 @@
|
||||
import requests
|
||||
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
normalize_images_input,
|
||||
poll_status_until_completed,
|
||||
)
|
||||
|
||||
|
||||
class GenerateStructuredPromptNodeV2:
|
||||
"""Structured Prompt Generation Node (multi-image compatible)"""
|
||||
|
||||
api_url = "https://engine.prod.bria-api.com/v2/structured_prompt/generate"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
"prompt": ("STRING",),
|
||||
},
|
||||
"optional": {
|
||||
"structured_prompt": ("STRING",),
|
||||
"images": ("IMAGE",),
|
||||
"seed": ("STRING", {"default": "123456"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING") # structured_prompts, seeds as comma-separated string
|
||||
RETURN_NAMES = ("structured_prompt", "seed")
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def _validate_token(self, api_token: str):
|
||||
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API token.")
|
||||
|
||||
def _build_payload(self, prompt, seed, structured_prompt, processed_image=None):
|
||||
payload = {"prompt": prompt, "seed": int(seed)}
|
||||
if structured_prompt:
|
||||
payload["structured_prompt"] = structured_prompt
|
||||
if processed_image is not None:
|
||||
payload["images"] = [image_to_base64(processed_image)]
|
||||
return payload
|
||||
|
||||
def execute(self, api_token, prompt, seed, structured_prompt, images=None):
|
||||
self._validate_token(api_token)
|
||||
images_list = normalize_images_input(images) if images is not None else [None]
|
||||
|
||||
# Seeds per image
|
||||
if isinstance(seed, str):
|
||||
seed_values = [int(s.strip()) for s in seed.split(",")]
|
||||
else:
|
||||
seed_values = [seed]
|
||||
if len(seed_values) < len(images_list):
|
||||
seed_values += [seed_values[-1]] * (len(images_list) - len(seed_values))
|
||||
|
||||
# Structured prompts per image
|
||||
if isinstance(structured_prompt, str):
|
||||
structured_prompts_list = structured_prompt.split("\n---\n")
|
||||
elif isinstance(structured_prompt, list):
|
||||
structured_prompts_list = structured_prompt
|
||||
else:
|
||||
structured_prompts_list = [""] * len(images_list)
|
||||
if len(structured_prompts_list) < len(images_list):
|
||||
structured_prompts_list += [structured_prompts_list[-1]] * (len(images_list) - len(structured_prompts_list))
|
||||
|
||||
batch_structured_prompts = []
|
||||
batch_seeds = []
|
||||
|
||||
for idx, image in enumerate(images_list):
|
||||
try:
|
||||
|
||||
payload = self._build_payload(
|
||||
prompt,
|
||||
seed_values[idx],
|
||||
structured_prompts_list[idx],
|
||||
image
|
||||
)
|
||||
|
||||
headers = bria_json_headers(api_token)
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
if response.status_code not in (200, 202):
|
||||
raise Exception(
|
||||
f"API request failed with status code {response.status_code}: {response.text}"
|
||||
)
|
||||
|
||||
response_dict = response.json()
|
||||
print(f"GenerateStructuredPromptNodeV2 - Initial request successful for image {idx}, polling for completion...")
|
||||
|
||||
status_url = response_dict.get("status_url")
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_token)
|
||||
|
||||
result = final_response.get("result", {})
|
||||
structured_prompt_result = result.get("structured_prompt", "")
|
||||
used_seed = result.get("seed", seed_values[idx])
|
||||
|
||||
batch_structured_prompts.append(structured_prompt_result)
|
||||
batch_seeds.append(str(used_seed)) # Keep as string for passing between nodes
|
||||
|
||||
except Exception as e:
|
||||
print(f"[GenerateStructuredPromptNodeV2] Skipping iteration {idx} due to error: {e}")
|
||||
batch_structured_prompts.append("")
|
||||
batch_seeds.append(str(seed_values[idx]))
|
||||
|
||||
# Return combined structured prompts and seeds as strings
|
||||
combined_prompts = "\n---\n".join(batch_structured_prompts)
|
||||
combined_seeds = ",".join(batch_seeds)
|
||||
return combined_prompts, combined_seeds
|
||||
@@ -4,7 +4,13 @@ from PIL import Image
|
||||
import io
|
||||
import torch
|
||||
|
||||
from .common import image_to_base64, preprocess_image, preprocess_mask
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
poll_status_until_completed,
|
||||
preprocess_image,
|
||||
preprocess_mask,
|
||||
)
|
||||
|
||||
|
||||
class GenFillNode():
|
||||
@@ -18,7 +24,12 @@ class GenFillNode():
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
||||
},
|
||||
"optional": {
|
||||
"seed": ("INT", {"default": 123456})
|
||||
"seed": ("INT", {"default": 123456}),
|
||||
"prompt_content_moderation": ("BOOLEAN", {"default": True}),
|
||||
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +39,12 @@ class GenFillNode():
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/gen_fill" # Eraser API URL
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/gen_fill"
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, mask, prompt, api_key, seed):
|
||||
def execute(self, image, mask, prompt, api_key, seed, prompt_content_moderation, visual_input_content_moderation, visual_output_content_moderation):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
|
||||
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
@@ -47,34 +57,44 @@ class GenFillNode():
|
||||
|
||||
# Prepare the API request payload
|
||||
payload = {
|
||||
"file": f"{image_base64}",
|
||||
"mask_file": f"{mask_base64}",
|
||||
"image": image_base64,
|
||||
"mask": mask_base64,
|
||||
"prompt": prompt,
|
||||
"negative_prompt": "blurry",
|
||||
"sync": True,
|
||||
"seed": seed,
|
||||
"prompt_content_moderation":prompt_content_moderation,
|
||||
"visual_input_content_moderation":visual_input_content_moderation,
|
||||
"visual_output_content_moderation":visual_output_content_moderation,
|
||||
"version": 2
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
headers = bria_json_headers(api_key)
|
||||
|
||||
try:
|
||||
# Send initial request to get status URL
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
# Check for successful response
|
||||
if response.status_code == 200:
|
||||
print('response is 200')
|
||||
# Process the output image from API response
|
||||
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
print('Initial genfill request successful, polling for completion...')
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['urls'][0])
|
||||
status_url = response_dict.get('status_url')
|
||||
request_id = response_dict.get('request_id')
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_key)
|
||||
result_image_url = final_response['result']['image_url']
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = result_image.convert("RGB")
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code}")
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import io
|
||||
import requests
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import torch
|
||||
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
normalize_images_input,
|
||||
poll_status_until_completed,
|
||||
)
|
||||
|
||||
class ImageEnhanceNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
},
|
||||
"optional": {
|
||||
"steps_num": ("INT", {"default": 20, "min": 10, "max": 50}),
|
||||
"resolution": (["1MP", "2MP", "4MP"], {"default": "1MP"}),
|
||||
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"seed": ("INT", {"default": 681794}),
|
||||
"preserve_alpha": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "STRING")
|
||||
RETURN_NAMES = ("output_images", "seeds",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/enhance"
|
||||
|
||||
def execute(
|
||||
self,
|
||||
images,
|
||||
api_key,
|
||||
visual_input_content_moderation,
|
||||
visual_output_content_moderation,
|
||||
seed,
|
||||
steps_num,
|
||||
resolution,
|
||||
preserve_alpha
|
||||
):
|
||||
# Validate API key
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
# Normalize input to list of PIL images
|
||||
images = normalize_images_input(images)
|
||||
|
||||
batch_results = []
|
||||
batch_seeds = []
|
||||
|
||||
for idx, pil_image in enumerate(images):
|
||||
try:
|
||||
image_base64 = image_to_base64(pil_image)
|
||||
|
||||
payload = {
|
||||
"image": image_base64,
|
||||
"visual_input_content_moderation": visual_input_content_moderation,
|
||||
"visual_output_content_moderation": visual_output_content_moderation,
|
||||
"seed": seed,
|
||||
"steps_num": steps_num,
|
||||
"resolution": resolution,
|
||||
"preserve_alpha": preserve_alpha
|
||||
}
|
||||
|
||||
headers = bria_json_headers(api_key)
|
||||
|
||||
# Send request
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
if response.status_code not in (200, 202):
|
||||
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||
|
||||
# Poll until completion
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get("status_url")
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"ImageEnhanceNode - Initial request successful for image {idx}, polling for completion...")
|
||||
final_response = poll_status_until_completed(status_url, api_key)
|
||||
result_image_url = final_response["result"]["image_url"]
|
||||
used_seed = final_response["result"].get("seed", seed)
|
||||
|
||||
# Download and process image
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = Image.open(io.BytesIO(image_response.content)).convert("RGB")
|
||||
result_array = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_tensor = torch.from_numpy(result_array) # shape: (H,W,C)
|
||||
|
||||
batch_results.append(result_tensor)
|
||||
batch_seeds.append(used_seed)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ImageEnhanceNode] Skipping image {idx} due to error: {e}")
|
||||
# Append fallback tensor with same size as input
|
||||
fallback_array = np.array(pil_image).astype(np.float32) / 255.0
|
||||
batch_results.append(torch.from_numpy(fallback_array))
|
||||
batch_seeds.append(seed)
|
||||
|
||||
# Return list of tensors (not concatenated) + comma-separated seeds
|
||||
combined_seeds = ",".join(map(str, batch_seeds))
|
||||
return (batch_results, combined_seeds)
|
||||
+106
-74
@@ -1,101 +1,133 @@
|
||||
import numpy as np
|
||||
import requests
|
||||
from PIL import Image
|
||||
import io
|
||||
import requests
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import torch
|
||||
|
||||
from .common import image_to_base64, preprocess_image
|
||||
|
||||
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
normalize_images_input,
|
||||
poll_status_until_completed,
|
||||
)
|
||||
class ImageExpansionNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"images": ("IMAGE",),
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
},
|
||||
"optional": {
|
||||
"original_image_size": ("STRING",),
|
||||
"original_image_location": ("STRING",),
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
||||
},
|
||||
|
||||
"optional": {
|
||||
"canvas_size": ("STRING", {"default": "1000, 1000"}),
|
||||
"aspect_ratio": (["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "None"], {"default": "None"}),
|
||||
"prompt": ("STRING", {"default": ""}),
|
||||
"seed": ("INT", {"default": 681794}),
|
||||
"seed": ("STRING", {"default": "681794"}), # <-- accepts seeds from Enhance
|
||||
"negative_prompt": ("STRING", {"default": "Ugly, mutated"}),
|
||||
"content_moderation": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
"prompt_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"preserve_alpha": ("BOOLEAN", {"default": True}),
|
||||
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
RETURN_NAMES = ("output_images",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/image_expansion" # Image Expansion API URL
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/expand"
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image,
|
||||
original_image_size,
|
||||
original_image_location,
|
||||
canvas_size,
|
||||
prompt,
|
||||
seed,
|
||||
negative_prompt,
|
||||
content_moderation,
|
||||
api_key):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
def execute(
|
||||
self,
|
||||
images,
|
||||
original_image_size,
|
||||
original_image_location,
|
||||
canvas_size,
|
||||
aspect_ratio,
|
||||
prompt,
|
||||
seed,
|
||||
negative_prompt,
|
||||
prompt_content_moderation,
|
||||
preserve_alpha,
|
||||
visual_input_content_moderation,
|
||||
visual_output_content_moderation,
|
||||
api_key
|
||||
):
|
||||
if api_key.strip() in ("", "BRIA_API_TOKEN"):
|
||||
raise Exception("Please insert a valid API key.")
|
||||
|
||||
original_image_size = [int(x.strip()) for x in original_image_size.split(",")]
|
||||
original_image_location = [int(x.strip()) for x in original_image_location.split(",")]
|
||||
canvas_size = [int(x.strip()) for x in canvas_size.split(",")]
|
||||
|
||||
if prompt == "":
|
||||
prompt = None
|
||||
if negative_prompt == "":
|
||||
negative_prompt = " " # hack to avoid error in triton which expects non-empty string
|
||||
images = normalize_images_input(images)
|
||||
canvas_size = [int(x.strip()) for x in canvas_size.split(",")] if canvas_size else ()
|
||||
original_image_size = [int(x.strip()) for x in original_image_size.split(",")] if original_image_size else ()
|
||||
original_image_location = [int(x.strip()) for x in original_image_location.split(",")] if original_image_location else ()
|
||||
|
||||
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
# Prepare per-image seeds
|
||||
seed_values = [int(s.strip()) for s in seed.split(",")] if isinstance(seed, str) else [seed]
|
||||
if len(seed_values) < len(images):
|
||||
seed_values += [seed_values[-1]] * (len(images) - len(seed_values))
|
||||
|
||||
# Convert the image directly to Base64 string
|
||||
image_base64 = image_to_base64(image)
|
||||
if not negative_prompt:
|
||||
negative_prompt = " "
|
||||
|
||||
# Prepare the API request payload
|
||||
payload = {
|
||||
"file": f"{image_base64}",
|
||||
"original_image_size": original_image_size,
|
||||
"original_image_location": original_image_location,
|
||||
"canvas_size": canvas_size,
|
||||
"prompt": prompt,
|
||||
"negative_prompt": negative_prompt,
|
||||
"seed": seed,
|
||||
"content_moderation": content_moderation
|
||||
}
|
||||
batch_results = []
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
for idx, pil_image in enumerate(images):
|
||||
try:
|
||||
image_base64 = image_to_base64(pil_image)
|
||||
|
||||
if aspect_ratio and aspect_ratio != "None":
|
||||
payload = {
|
||||
"image": image_base64,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"prompt": prompt,
|
||||
"negative_prompt": negative_prompt,
|
||||
"seed": seed_values[idx],
|
||||
"prompt_content_moderation": prompt_content_moderation,
|
||||
"preserve_alpha": preserve_alpha,
|
||||
"visual_input_content_moderation": visual_input_content_moderation,
|
||||
"visual_output_content_moderation": visual_output_content_moderation
|
||||
}
|
||||
else:
|
||||
payload = {
|
||||
"image": image_base64,
|
||||
"original_image_size": original_image_size,
|
||||
"original_image_location": original_image_location,
|
||||
"canvas_size": canvas_size,
|
||||
"prompt": prompt,
|
||||
"negative_prompt": negative_prompt,
|
||||
"seed": seed_values[idx],
|
||||
"prompt_content_moderation": prompt_content_moderation,
|
||||
"preserve_alpha": preserve_alpha,
|
||||
"visual_input_content_moderation": visual_input_content_moderation,
|
||||
"visual_output_content_moderation": visual_output_content_moderation
|
||||
}
|
||||
|
||||
headers = bria_json_headers(api_key)
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
if response.status_code not in (200, 202):
|
||||
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
# Check for successful response
|
||||
if response.status_code == 200:
|
||||
print('response is 200')
|
||||
# Process the output image from API response
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['result_url'])
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = result_image.convert("RGB")
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code}")
|
||||
status_url = response_dict.get("status_url")
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
print(f"ImageExpansionNode - Initial request successful for image {idx}, polling for completion...")
|
||||
final_response = poll_status_until_completed(status_url, api_key)
|
||||
result_image_url = final_response["result"]["image_url"]
|
||||
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = Image.open(io.BytesIO(image_response.content)).convert("RGB")
|
||||
result_tensor = torch.from_numpy(np.array(result_image).astype(np.float32) / 255.0)
|
||||
|
||||
batch_results.append(result_tensor)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ImageExpansionNode] Skipping image {idx} due to error: {e}")
|
||||
fallback_array = np.array(pil_image).astype(np.float32) / 255.0
|
||||
batch_results.append(torch.from_numpy(fallback_array))
|
||||
|
||||
return (batch_results,)
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import os
|
||||
import json
|
||||
from typing import List
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
import numpy as np
|
||||
from PIL import Image, ImageOps
|
||||
|
||||
try:
|
||||
from folder_paths import get_input_directory
|
||||
except Exception:
|
||||
get_input_directory = None
|
||||
|
||||
|
||||
IMG_EXTS = (".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tif", ".tiff")
|
||||
|
||||
|
||||
def input_root() -> str:
|
||||
return os.path.abspath(get_input_directory() if get_input_directory else "input")
|
||||
|
||||
|
||||
def parse_paths(value: str) -> List[str]:
|
||||
if not value:
|
||||
return []
|
||||
try:
|
||||
data = json.loads(value)
|
||||
if isinstance(data, list):
|
||||
return [str(x) for x in data]
|
||||
except Exception:
|
||||
pass
|
||||
return []
|
||||
|
||||
|
||||
|
||||
class BriaMultiImageSelect:
|
||||
"""
|
||||
Select multiple images and return them as a list of PIL Images.
|
||||
Images keep their original size.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"selected_paths": (
|
||||
"STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
"placeholder": "Filled automatically by Select Images button",
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "STRING")
|
||||
RETURN_NAMES = ("images", "filenames")
|
||||
FUNCTION = "load"
|
||||
CATEGORY = "API Nodes"
|
||||
|
||||
def load(self, selected_paths: str):
|
||||
paths = parse_paths(selected_paths)
|
||||
if not paths:
|
||||
raise RuntimeError("BriaMultiImageSelect: No images selected")
|
||||
|
||||
root = input_root()
|
||||
pil_images: List[Image.Image] = []
|
||||
names: List[str] = []
|
||||
|
||||
for rel in paths:
|
||||
abs_path = os.path.join(root, rel)
|
||||
if not abs_path.lower().endswith(IMG_EXTS):
|
||||
continue
|
||||
if not os.path.isfile(abs_path):
|
||||
continue
|
||||
|
||||
pil_images.append(Image.open(abs_path))
|
||||
names.append(os.path.splitext(os.path.basename(rel))[0])
|
||||
|
||||
if not pil_images:
|
||||
raise RuntimeError("BriaMultiImageSelect: No valid images found")
|
||||
|
||||
filenames = ", ".join(names)
|
||||
return (pil_images, filenames)
|
||||
@@ -0,0 +1,134 @@
|
||||
import requests
|
||||
import torch
|
||||
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
poll_status_until_completed,
|
||||
postprocess_image,
|
||||
preprocess_image,
|
||||
)
|
||||
|
||||
|
||||
class ProductIntegrateNode:
|
||||
"""Product Integrate Node - Integrate a single product into a background scene"""
|
||||
|
||||
api_url = "https://engine.prod.bria-api.com/v2/image/edit/product/integrate"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
"scene": ("IMAGE",),
|
||||
"product_image": ("IMAGE",),
|
||||
"x_coordinate": ("INT", {"default": 0, "min": 0, "max": 10000}),
|
||||
"y_coordinate": ("INT", {"default": 0, "min": 0, "max": 10000}),
|
||||
"width": ("INT", {"default": 512, "min": 1, "max": 10000}),
|
||||
"height": ("INT", {"default": 512, "min": 1, "max": 10000}),
|
||||
},
|
||||
"optional": {
|
||||
"seed": ("STRING", {"default": "123456"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "INT")
|
||||
RETURN_NAMES = ("IMAGE", "seed")
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def _validate_token(self, api_token: str):
|
||||
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API token.")
|
||||
|
||||
def _build_payload(
|
||||
self,
|
||||
scene_image,
|
||||
product_image,
|
||||
x_coordinate,
|
||||
y_coordinate,
|
||||
width,
|
||||
height,
|
||||
seed,
|
||||
):
|
||||
payload = {
|
||||
"scene": image_to_base64(scene_image),
|
||||
"products": [
|
||||
{
|
||||
"image": image_to_base64(product_image),
|
||||
"coordinates": {
|
||||
"x": x_coordinate,
|
||||
"y": y_coordinate,
|
||||
"width": width,
|
||||
"height": height,
|
||||
}
|
||||
}
|
||||
],
|
||||
"seed": int(seed),
|
||||
}
|
||||
|
||||
return payload
|
||||
|
||||
def execute(
|
||||
self,
|
||||
api_token,
|
||||
scene,
|
||||
product_image,
|
||||
x_coordinate,
|
||||
y_coordinate,
|
||||
width,
|
||||
height,
|
||||
seed,
|
||||
):
|
||||
self._validate_token(api_token)
|
||||
# Process single scene image
|
||||
if isinstance(scene, torch.Tensor):
|
||||
processed_scene = preprocess_image(scene)
|
||||
if isinstance(product_image, torch.Tensor):
|
||||
processed_product = preprocess_image(product_image)
|
||||
|
||||
payload = self._build_payload(
|
||||
processed_scene,
|
||||
processed_product,
|
||||
x_coordinate,
|
||||
y_coordinate,
|
||||
width,
|
||||
height,
|
||||
seed,
|
||||
)
|
||||
|
||||
headers = bria_json_headers(api_token)
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code in (200, 202):
|
||||
print(
|
||||
f"Initial product integrate request successful, polling for completion..."
|
||||
)
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get("status_url")
|
||||
request_id = response_dict.get("request_id")
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_token)
|
||||
|
||||
result = final_response.get("result", {})
|
||||
result_image_url = result.get("image_url")
|
||||
used_seed = result.get("seed", seed)
|
||||
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = postprocess_image(image_response.content)
|
||||
|
||||
return (result_image, used_seed)
|
||||
else:
|
||||
raise Exception(
|
||||
f"API request failed with status code {response.status_code} {response.text}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"[ProductIntegrateNode] Error: {e}")
|
||||
@@ -0,0 +1,167 @@
|
||||
import requests
|
||||
|
||||
from .common import bria_json_headers, poll_status_until_completed, postprocess_image
|
||||
|
||||
|
||||
|
||||
class RefineImageLiteNodeV2:
|
||||
"""Lite Refine Image Node"""
|
||||
|
||||
api_url = "https://engine.prod.bria-api.com/v2/structured_prompt/generate/lite"
|
||||
generate_api_url = "https://engine.prod.bria-api.com/v2/image/generate/lite"
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"api_token": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
"prompt": ("STRING",),
|
||||
"structured_prompt": ("STRING",),
|
||||
},
|
||||
"optional": {
|
||||
"model_version": (["FIBO"], {"default": "FIBO"}),
|
||||
"aspect_ratio": (
|
||||
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
|
||||
{"default": "1:1"},
|
||||
),
|
||||
"steps_num": (
|
||||
"INT",
|
||||
{
|
||||
"default": 8,
|
||||
"min": 8,
|
||||
"max": 30,
|
||||
},
|
||||
),
|
||||
"guidance_scale": (
|
||||
"INT",
|
||||
{
|
||||
"default": 5,
|
||||
"min": 3,
|
||||
"max": 5,
|
||||
},
|
||||
),
|
||||
"seed": ("INT", {"default": 123456}),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "STRING", "INT")
|
||||
RETURN_NAMES = ("image", "structured_prompt", "seed")
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def _validate_token(self, api_token: str):
|
||||
if api_token.strip() == "" or api_token.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API token.")
|
||||
|
||||
def _build_payload(
|
||||
self,
|
||||
prompt,
|
||||
structured_prompt,
|
||||
model_version,
|
||||
aspect_ratio,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed,
|
||||
):
|
||||
payload = {
|
||||
"prompt": prompt,
|
||||
"model_version": model_version,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"steps_num": steps_num,
|
||||
"guidance_scale": guidance_scale,
|
||||
"seed": seed,
|
||||
}
|
||||
if structured_prompt:
|
||||
payload["structured_prompt"] = structured_prompt
|
||||
|
||||
return payload
|
||||
|
||||
def execute(
|
||||
self,
|
||||
api_token,
|
||||
prompt,
|
||||
structured_prompt,
|
||||
model_version,
|
||||
aspect_ratio,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed
|
||||
):
|
||||
self._validate_token(api_token)
|
||||
payload = self._build_payload(
|
||||
prompt,
|
||||
structured_prompt,
|
||||
model_version,
|
||||
aspect_ratio,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed,
|
||||
)
|
||||
headers = bria_json_headers(api_token)
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code in (200, 202):
|
||||
print(f"Initial refine request successful to {self.api_url}, polling for completion...")
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get("status_url")
|
||||
request_id = response_dict.get("request_id")
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_token)
|
||||
|
||||
result = final_response.get("result", {})
|
||||
structured_prompt = result.get("structured_prompt", "")
|
||||
used_seed = result.get("seed", seed)
|
||||
|
||||
# Step 2 to call genearte image
|
||||
payloadForImageGenetrate = {
|
||||
"prompt": prompt,
|
||||
"structured_prompt":structured_prompt,
|
||||
"model_version": model_version,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"steps_num": steps_num,
|
||||
"guidance_scale": guidance_scale,
|
||||
"seed": used_seed,
|
||||
}
|
||||
|
||||
headers = bria_json_headers(api_token)
|
||||
|
||||
response = requests.post(self.generate_api_url, json=payloadForImageGenetrate, headers=headers)
|
||||
|
||||
if response.status_code in (200, 202):
|
||||
print(
|
||||
f"Initial request successful to {self.generate_api_url}, polling for completion..."
|
||||
)
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get("status_url")
|
||||
request_id = response_dict.get("request_id")
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_token)
|
||||
|
||||
result = final_response.get("result", {})
|
||||
result_image_url = result.get("image_url")
|
||||
structured_prompt = result.get("structured_prompt", "")
|
||||
used_seed = result.get("seed")
|
||||
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = postprocess_image(image_response.content)
|
||||
|
||||
return (result_image, structured_prompt, used_seed)
|
||||
|
||||
raise Exception(
|
||||
f"Error: API request failed with status code {response.status_code} {response.text}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
@@ -0,0 +1,169 @@
|
||||
import requests
|
||||
|
||||
from .common import bria_json_headers, poll_status_until_completed, postprocess_image
|
||||
|
||||
|
||||
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 {
|
||||
"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": 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"
|
||||
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,
|
||||
negative_prompt=None,
|
||||
):
|
||||
self._validate_token(api_token)
|
||||
payload = self._build_payload(
|
||||
prompt,
|
||||
structured_prompt,
|
||||
model_version,
|
||||
aspect_ratio,
|
||||
steps_num,
|
||||
guidance_scale,
|
||||
seed,
|
||||
)
|
||||
headers = bria_json_headers(api_token)
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code in (200, 202):
|
||||
print(f"Initial refine request successful to {self.api_url}, polling for completion...")
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get("status_url")
|
||||
request_id = response_dict.get("request_id")
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_token)
|
||||
|
||||
result = final_response.get("result", {})
|
||||
structured_prompt = result.get("structured_prompt", "")
|
||||
used_seed = result.get("seed", seed)
|
||||
|
||||
# Step 2 to call genearte image
|
||||
payloadForImageGenetrate = {
|
||||
"prompt": prompt,
|
||||
"structured_prompt":structured_prompt,
|
||||
"model_version": model_version,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"steps_num": steps_num,
|
||||
"guidance_scale": guidance_scale,
|
||||
"seed": used_seed,
|
||||
"negative_prompt":negative_prompt
|
||||
}
|
||||
|
||||
headers = bria_json_headers(api_token)
|
||||
|
||||
response = requests.post(self.generate_api_url, json=payloadForImageGenetrate, headers=headers)
|
||||
|
||||
if response.status_code in (200, 202):
|
||||
print(
|
||||
f"Initial request successful to {self.generate_api_url}, polling for completion..."
|
||||
)
|
||||
response_dict = response.json()
|
||||
status_url = response_dict.get("status_url")
|
||||
request_id = response_dict.get("request_id")
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_token)
|
||||
|
||||
result = final_response.get("result", {})
|
||||
result_image_url = result.get("image_url")
|
||||
structured_prompt = result.get("structured_prompt", "")
|
||||
used_seed = result.get("seed")
|
||||
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = postprocess_image(image_response.content)
|
||||
|
||||
return (result_image, structured_prompt, used_seed)
|
||||
|
||||
raise Exception(
|
||||
f"Error: API request failed with status code {response.status_code} {response.text}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
@@ -1,6 +1,11 @@
|
||||
import requests
|
||||
|
||||
from .common import postprocess_image, preprocess_image, image_to_base64
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
postprocess_image,
|
||||
preprocess_image,
|
||||
)
|
||||
|
||||
|
||||
class ReimagineNode():
|
||||
@@ -37,7 +42,7 @@ class ReimagineNode():
|
||||
steps_num, fast, structure_ref_influence, structure_image=None,
|
||||
tailored_model_id=None, tailored_model_influence=None, tailored_generation_prefix=None,
|
||||
content_moderation=0,
|
||||
):
|
||||
):
|
||||
payload = {
|
||||
"prompt": tailored_generation_prefix + prompt,
|
||||
"num_results": 1,
|
||||
@@ -58,7 +63,7 @@ class ReimagineNode():
|
||||
response = requests.post(
|
||||
self.api_url,
|
||||
json=payload,
|
||||
headers={"api_token": api_key}
|
||||
headers=bria_json_headers(api_key),
|
||||
)
|
||||
if response.status_code == 200:
|
||||
response_dict = response.json()
|
||||
|
||||
@@ -1,70 +1,93 @@
|
||||
import numpy as np
|
||||
import requests
|
||||
from PIL import Image
|
||||
import io
|
||||
import requests
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import torch
|
||||
|
||||
from .common import preprocess_image, image_to_base64
|
||||
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
normalize_images_input,
|
||||
poll_status_until_completed,
|
||||
)
|
||||
|
||||
class RemoveForegroundNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
||||
"images": ("IMAGE",),
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
},
|
||||
"optional": {
|
||||
"content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"preserve_alpha": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
RETURN_NAMES = ("output_images",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.internal.prod.bria-api.com/v1/erase_foreground" # remove foreground API URL
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/erase_foreground"
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, content_moderation, api_key):
|
||||
def execute(
|
||||
self,
|
||||
images,
|
||||
visual_input_content_moderation,
|
||||
visual_output_content_moderation,
|
||||
preserve_alpha,
|
||||
api_key
|
||||
):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
images = normalize_images_input(images)
|
||||
batch_results = []
|
||||
|
||||
# Check if image is tensor, if so, convert to NumPy array
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
for idx, pil_image in enumerate(images):
|
||||
try:
|
||||
image_base64 = image_to_base64(pil_image)
|
||||
|
||||
# Prepare the API request payload
|
||||
# temporary save the image to /tmp
|
||||
# temp_img_path = "/tmp/temp_img.jpeg"
|
||||
# image.save(temp_img_path, format="JPEG")
|
||||
|
||||
# files=[('file',('temp_img.jpeg', open(temp_img_path, 'rb'),'image/jpeg'))
|
||||
# ]
|
||||
payload = {"file": image_to_base64(image), "content_moderation": content_moderation}
|
||||
payload = {
|
||||
"image": image_base64,
|
||||
"visual_input_content_moderation": visual_input_content_moderation,
|
||||
"visual_output_content_moderation": visual_output_content_moderation,
|
||||
"preserve_alpha": preserve_alpha
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
headers = bria_json_headers(api_key)
|
||||
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
if response.status_code not in (200, 202):
|
||||
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
# Check for successful response
|
||||
if response.status_code == 200:
|
||||
print('response is 200')
|
||||
# Process the output image from API response
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['result_url'])
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code}")
|
||||
status_url = response_dict.get("status_url")
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
print(f"RemoveForegroundNode - Initial request successful for image {idx}, polling for completion...")
|
||||
final_response = poll_status_until_completed(status_url, api_key)
|
||||
result_image_url = final_response["result"]["image_url"]
|
||||
|
||||
# Download result
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = Image.open(io.BytesIO(image_response.content)).convert("RGB")
|
||||
|
||||
# Convert to float32 tensor (H, W, C)
|
||||
result_array = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_tensor = torch.from_numpy(result_array)
|
||||
|
||||
batch_results.append(result_tensor)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[RemoveForegroundNode] Skipping image {idx} due to error: {e}")
|
||||
# fallback: use original image as tensor
|
||||
fallback_array = np.array(pil_image).astype(np.float32) / 255.0
|
||||
batch_results.append(torch.from_numpy(fallback_array))
|
||||
|
||||
# Return list of tensors
|
||||
return (batch_results,)
|
||||
|
||||
+96
-79
@@ -1,105 +1,122 @@
|
||||
import numpy as np
|
||||
import requests
|
||||
from PIL import Image
|
||||
import io
|
||||
import requests
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import torch
|
||||
|
||||
from .common import image_to_base64, preprocess_image, preprocess_mask
|
||||
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
normalize_images_input,
|
||||
poll_status_until_completed,
|
||||
)
|
||||
|
||||
class ReplaceBgNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
||||
"images": ("IMAGE",),
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
},
|
||||
"optional": {
|
||||
"fast": ("BOOLEAN", {"default": True}),
|
||||
"bg_prompt": ("STRING",),
|
||||
"ref_image": ("IMAGE",), # Input ref image from another node
|
||||
"refine_prompt": ("BOOLEAN", {"default": True}),
|
||||
"enhance_ref_image": ("BOOLEAN", {"default": True}),
|
||||
"original_quality": ("BOOLEAN", {"default": False}),
|
||||
"force_rmbg": ("BOOLEAN", {"default": False}),
|
||||
"negative_prompt": ("STRING", {"default": None}),
|
||||
"seed": ("INT", {"default": 681794}),
|
||||
"content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"mode": (["base", "fast", "high_control"], {"default": "base"}),
|
||||
"prompt": ("STRING", {"default": ""}),
|
||||
"ref_images": ("IMAGE",),
|
||||
"refine_prompt": ("BOOLEAN", {"default": True}),
|
||||
"enhance_ref_images": ("BOOLEAN", {"default": True}),
|
||||
"original_quality": ("BOOLEAN", {"default": False}),
|
||||
"negative_prompt": ("STRING", {"default": None}),
|
||||
"seed": ("STRING", {"default": "681794"}), # Accept comma-separated seeds
|
||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"prompt_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"force_background_detection": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
RETURN_NAMES = ("output_images",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/background/replace" # Replace BG API URL
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/replace_background"
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, fast,
|
||||
refine_prompt,
|
||||
enhance_ref_image,
|
||||
original_quality,
|
||||
force_rmbg,
|
||||
negative_prompt,
|
||||
seed,
|
||||
api_key,
|
||||
content_moderation,
|
||||
bg_prompt=None,
|
||||
ref_image=None,):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
def execute(
|
||||
self,
|
||||
images,
|
||||
mode,
|
||||
refine_prompt,
|
||||
original_quality,
|
||||
negative_prompt,
|
||||
seed,
|
||||
api_key,
|
||||
visual_output_content_moderation,
|
||||
prompt_content_moderation,
|
||||
enhance_ref_images,
|
||||
force_background_detection,
|
||||
prompt=None,
|
||||
ref_images=None
|
||||
):
|
||||
if api_key.strip() in ("", "BRIA_API_TOKEN"):
|
||||
raise Exception("Please insert a valid API key.")
|
||||
images = normalize_images_input(images)
|
||||
|
||||
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
# Normalize reference images
|
||||
ref_images_base64 = []
|
||||
if ref_images is not None:
|
||||
ref_images_list = normalize_images_input(ref_images)
|
||||
ref_images_base64 = [image_to_base64(img) for img in ref_images_list]
|
||||
|
||||
# Convert the image and mask directly to Base64 strings
|
||||
image_base64 = image_to_base64(image)
|
||||
ref_image_file = None # initialization, will be updated if it is supplied
|
||||
if ref_image is not None:
|
||||
ref_image = preprocess_image(ref_image)
|
||||
ref_image_file = image_to_base64(ref_image)
|
||||
# Prepare per-image seeds
|
||||
seed_values = [int(s.strip()) for s in seed.split(",")] if isinstance(seed, str) else [seed]
|
||||
if len(seed_values) < len(images):
|
||||
seed_values += [seed_values[-1]] * (len(images) - len(seed_values))
|
||||
|
||||
# Prepare the API request payload
|
||||
payload = {
|
||||
"file": f"{image_base64}",
|
||||
"fast": fast,
|
||||
"bg_prompt": bg_prompt,
|
||||
"ref_image_file": ref_image_file,
|
||||
"refine_prompt": refine_prompt,
|
||||
"enhance_ref_image": enhance_ref_image,
|
||||
"original_quality": original_quality,
|
||||
"force_rmbg": force_rmbg,
|
||||
"negative_prompt": negative_prompt,
|
||||
"seed": seed,
|
||||
"sync": True,
|
||||
"num_results": 1,
|
||||
"content_moderation": content_moderation
|
||||
}
|
||||
batch_results = []
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
for idx, pil_image in enumerate(images):
|
||||
try:
|
||||
image_base64 = image_to_base64(pil_image)
|
||||
|
||||
payload = {
|
||||
"image": image_base64,
|
||||
"mode": mode,
|
||||
"prompt": prompt,
|
||||
"ref_images": ref_images_base64,
|
||||
"refine_prompt": refine_prompt,
|
||||
"original_quality": original_quality,
|
||||
"negative_prompt": negative_prompt,
|
||||
"seed": seed_values[idx],
|
||||
"prompt_content_moderation": prompt_content_moderation,
|
||||
"visual_output_content_moderation": visual_output_content_moderation,
|
||||
"enhance_ref_images": enhance_ref_images,
|
||||
"force_background_detection": force_background_detection
|
||||
}
|
||||
|
||||
headers = bria_json_headers(api_key)
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
if response.status_code not in (200, 202):
|
||||
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
# Check for successful response
|
||||
if response.status_code == 200:
|
||||
print('response is 200')
|
||||
# Process the output image from API response
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['result'][0][0]) # first indexing for batched, second for url
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = result_image.convert("RGB")
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code}")
|
||||
status_url = response_dict.get("status_url")
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
print(f"ReplaceBgNode - Initial request successful for image {idx}, polling for completion...")
|
||||
final_response = poll_status_until_completed(status_url, api_key)
|
||||
result_image_url = final_response["result"]["image_url"]
|
||||
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = Image.open(io.BytesIO(image_response.content)).convert("RGB")
|
||||
result_tensor = torch.from_numpy(np.array(result_image).astype(np.float32) / 255.0)
|
||||
|
||||
batch_results.append(result_tensor)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ReplaceBgNode] Skipping image {idx} due to error: {e}")
|
||||
fallback_array = np.array(pil_image).astype(np.float32) / 255.0
|
||||
batch_results.append(torch.from_numpy(fallback_array))
|
||||
|
||||
return (batch_results,)
|
||||
|
||||
+62
-40
@@ -1,67 +1,89 @@
|
||||
import numpy as np
|
||||
import requests
|
||||
from PIL import Image
|
||||
import io
|
||||
import requests
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import torch
|
||||
|
||||
from .common import preprocess_image
|
||||
from io import BytesIO
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
normalize_images_input,
|
||||
poll_status_until_completed,
|
||||
to_pil_safe,
|
||||
)
|
||||
|
||||
class RmbgNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
||||
"images": ("IMAGE",), # Accepts list of PIL Images or single tensor
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
},
|
||||
"optional": {
|
||||
"content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"visual_input_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"visual_output_content_moderation": ("BOOLEAN", {"default": False}),
|
||||
"preserve_alpha": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
RETURN_NAMES = ("output_images",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/background/remove" # RMBG API URL
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/image/edit/remove_background"
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, content_moderation, api_key):
|
||||
def execute(self, images, visual_input_content_moderation, visual_output_content_moderation, preserve_alpha, api_key):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
# Normalize input to list of PIL images
|
||||
images = normalize_images_input(images)
|
||||
|
||||
# Check if image is tensor, if so, convert to NumPy array
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
batch_results = []
|
||||
|
||||
# Prepare the API request payload
|
||||
image_buffer = BytesIO()
|
||||
image.save(image_buffer, format="JPEG")
|
||||
for idx, pil_image in enumerate(images):
|
||||
try:
|
||||
image_base64 = image_to_base64(pil_image)
|
||||
|
||||
# Get binary data from buffer
|
||||
image_buffer.seek(0) # Move cursor to the start of the buffer
|
||||
binary_data = image_buffer.read()
|
||||
payload = {
|
||||
"image": image_base64,
|
||||
"visual_input_content_moderation": visual_input_content_moderation,
|
||||
"visual_output_content_moderation": visual_output_content_moderation,
|
||||
"preserve_alpha": preserve_alpha
|
||||
}
|
||||
|
||||
files=[('file',('temp_img.jpeg', BytesIO(binary_data),'image/jpeg'))]
|
||||
payload = {"content_moderation": content_moderation}
|
||||
headers = bria_json_headers(api_key)
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, data=payload, headers={"api_token": api_key}, files=files)
|
||||
# Check for successful response
|
||||
if response.status_code == 200:
|
||||
print('response is 200')
|
||||
# Process the output image from API response
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
if response.status_code not in (200, 202):
|
||||
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||
|
||||
# Poll until completion
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['result_url'])
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code}")
|
||||
status_url = response_dict.get('status_url')
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
print(f"RmbgNode - Initial request successful for image {idx}, polling for completion...")
|
||||
final_response = poll_status_until_completed(status_url, api_key)
|
||||
result_image_url = final_response['result']['image_url']
|
||||
|
||||
# Download result
|
||||
image_response = requests.get(result_image_url)
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
|
||||
# Convert to float32 tensor (H, W, C), 0-1
|
||||
result_array = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_tensor = torch.from_numpy(result_array) # shape: (H,W,4)
|
||||
batch_results.append(result_tensor)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[RmbgNode] Skipping image {idx} due to error: {e}")
|
||||
# Append empty tensor of the same size as original
|
||||
empty_array = np.zeros((pil_image.height, pil_image.width, 4), dtype=np.float32)
|
||||
batch_results.append(torch.from_numpy(empty_array))
|
||||
|
||||
# Return list of tensors (Comfy preview handles this correctly)
|
||||
return (batch_results,)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
from .utils.shot_utils import get_image_input_types, create_image_payload, make_api_request, shot_by_image_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByImageAutomaticAspectRatioNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_image_input_types()
|
||||
input_types["required"]["aspect_ratio"] = (
|
||||
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
|
||||
{"default": "1:1"},
|
||||
)
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_image_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
ref_image,
|
||||
aspect_ratio,
|
||||
api_key,
|
||||
sync=False,
|
||||
enhance_ref_image=True,
|
||||
ref_image_influence=1.0,
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_image_payload(
|
||||
image,
|
||||
ref_image,
|
||||
api_key,
|
||||
PlacementType.AUTOMATIC_ASPECT_RATIO.value,
|
||||
aspect_ratio=aspect_ratio,
|
||||
sync=sync,
|
||||
enhance_ref_image=enhance_ref_image,
|
||||
ref_image_influence=ref_image_influence,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,51 @@
|
||||
from .utils.shot_utils import get_image_input_types, create_image_payload, make_api_request, shot_by_image_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByImageAutomaticNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_image_input_types()
|
||||
input_types["required"]["shot_size"] = ("STRING", {"default": "1000, 1000"})
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE")
|
||||
RETURN_NAMES = (
|
||||
"output_image_1",
|
||||
"output_image_2",
|
||||
"output_image_3",
|
||||
"output_image_4",
|
||||
"output_image_5",
|
||||
"output_image_6",
|
||||
"output_image_7",
|
||||
)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_image_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
ref_image,
|
||||
shot_size,
|
||||
api_key,
|
||||
sync=False,
|
||||
enhance_ref_image=True,
|
||||
ref_image_influence=1.0,
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_image_payload(
|
||||
image,
|
||||
ref_image,
|
||||
api_key,
|
||||
PlacementType.AUTOMATIC.value,
|
||||
shot_size=shot_size,
|
||||
sync=sync,
|
||||
enhance_ref_image=enhance_ref_image,
|
||||
ref_image_influence=ref_image_influence,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key, Placement_type = PlacementType.AUTOMATIC.value)
|
||||
@@ -0,0 +1,55 @@
|
||||
from .utils.shot_utils import get_image_input_types, create_image_payload, make_api_request, shot_by_image_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByImageCustomCoordinatesNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_image_input_types()
|
||||
input_types["required"]["shot_size"] = ("STRING", {"default": "1000, 1000"})
|
||||
input_types["required"]["foreground_image_size"] = (
|
||||
"STRING",
|
||||
{"default": "500,500"},
|
||||
)
|
||||
input_types["required"]["foreground_image_location"] = (
|
||||
"STRING",
|
||||
{"default": "0, 0"},
|
||||
)
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_image_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
ref_image,
|
||||
shot_size,
|
||||
foreground_image_size,
|
||||
foreground_image_location,
|
||||
api_key,
|
||||
sync=False,
|
||||
enhance_ref_image=True,
|
||||
ref_image_influence=1.0,
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_image_payload(
|
||||
image,
|
||||
ref_image,
|
||||
api_key,
|
||||
PlacementType.CUSTOM_COORDINATES.value,
|
||||
shot_size=shot_size,
|
||||
foreground_image_size=foreground_image_size,
|
||||
foreground_image_location=foreground_image_location,
|
||||
sync=sync,
|
||||
enhance_ref_image=enhance_ref_image,
|
||||
ref_image_influence=ref_image_influence,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,44 @@
|
||||
from .utils.shot_utils import get_image_input_types, create_image_payload, make_api_request, shot_by_image_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByImageManualPaddingNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_image_input_types()
|
||||
input_types["required"]["padding_values"] = ("STRING", {"default": "0,0,0,0"})
|
||||
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_image_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
ref_image,
|
||||
padding_values,
|
||||
api_key,
|
||||
sync=False,
|
||||
enhance_ref_image=True,
|
||||
ref_image_influence=1.0,
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_image_payload(
|
||||
image,
|
||||
ref_image,
|
||||
api_key,
|
||||
PlacementType.MANUAL_PADDING.value,
|
||||
padding_values=padding_values,
|
||||
sync=sync,
|
||||
enhance_ref_image=enhance_ref_image,
|
||||
ref_image_influence=ref_image_influence,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,59 @@
|
||||
from .utils.shot_utils import get_image_input_types, create_image_payload, make_api_request, shot_by_image_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByImageManualPlacementNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_image_input_types()
|
||||
input_types["required"]["shot_size"] = ("STRING", {"default": "1000, 1000"})
|
||||
input_types["required"]["manual_placement_selection"] = (
|
||||
[
|
||||
"upper_left",
|
||||
"upper_right",
|
||||
"bottom_left",
|
||||
"bottom_right",
|
||||
"right_center",
|
||||
"left_center",
|
||||
"upper_center",
|
||||
"bottom_center",
|
||||
"center_vertical",
|
||||
"center_horizontal",
|
||||
],
|
||||
{"default": "upper_left"},
|
||||
)
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_image_api_url
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
ref_image,
|
||||
shot_size,
|
||||
manual_placement_selection,
|
||||
api_key,
|
||||
sync=False,
|
||||
enhance_ref_image=True,
|
||||
ref_image_influence=1.0,
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_image_payload(
|
||||
image,
|
||||
ref_image,
|
||||
api_key,
|
||||
PlacementType.MANUAL_PLACEMENT.value,
|
||||
shot_size=shot_size,
|
||||
manual_placement_selection=manual_placement_selection,
|
||||
sync=sync,
|
||||
enhance_ref_image=enhance_ref_image,
|
||||
ref_image_influence=ref_image_influence,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
+41
-72
@@ -1,72 +1,41 @@
|
||||
import requests
|
||||
import torch
|
||||
|
||||
from .common import postprocess_image, preprocess_image, image_to_base64
|
||||
|
||||
class ShotByImageNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"ref_image": ("IMAGE",), # ref image from another node
|
||||
"enhance_ref_image": ("INT", {"default": 1}),
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}) # API Key input with a default value
|
||||
},
|
||||
"optional": {
|
||||
"content_moderation": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/product/lifestyle_shot_by_image" # Eraser API URL
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, ref_image, api_key, enhance_ref_image, content_moderation):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
|
||||
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
if isinstance(ref_image, torch.Tensor):
|
||||
ref_image = preprocess_image(ref_image)
|
||||
|
||||
# Convert the image and mask directly to Base64 strings
|
||||
image_base64 = image_to_base64(image)
|
||||
ref_image_base64 = image_to_base64(ref_image)
|
||||
enhance_ref_image = bool(enhance_ref_image)
|
||||
|
||||
payload = {
|
||||
"file": image_base64,
|
||||
"ref_image_file": ref_image_base64,
|
||||
"enhance_ref_image": enhance_ref_image,
|
||||
"placement_type": "original",
|
||||
"original_quality": True,
|
||||
"sync": True,
|
||||
"content_moderation": content_moderation
|
||||
}
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
# Check for successful response
|
||||
if response.status_code == 200:
|
||||
print('response is 200')
|
||||
# Process the output image from API response
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['result'][0][0])
|
||||
result_image = postprocess_image(image_response.content)
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
from .utils.shot_utils import get_image_input_types, create_image_payload, make_api_request, shot_by_image_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByImageOriginalNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_image_input_types()
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_image_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
ref_image,
|
||||
api_key,
|
||||
sync=True,
|
||||
enhance_ref_image=True,
|
||||
ref_image_influence=1.0,
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_image_payload(
|
||||
image,
|
||||
ref_image,
|
||||
api_key,
|
||||
PlacementType.ORIGINAL.value,
|
||||
original_quality=True,
|
||||
sync=sync,
|
||||
enhance_ref_image=enhance_ref_image,
|
||||
ref_image_influence=ref_image_influence,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
from .utils.shot_utils import get_text_input_types, create_text_payload, make_api_request, shot_by_text_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByTextAutomaticAspectRatioNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_text_input_types()
|
||||
input_types["required"]["aspect_ratio"] = (
|
||||
["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9"],
|
||||
{"default": "1:1"},
|
||||
)
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_text_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
scene_description,
|
||||
mode,
|
||||
aspect_ratio,
|
||||
api_key,
|
||||
sync=False,
|
||||
optimize_description=True,
|
||||
exclude_elements="",
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_text_payload(
|
||||
image,
|
||||
api_key,
|
||||
scene_description,
|
||||
mode,
|
||||
PlacementType.AUTOMATIC_ASPECT_RATIO.value,
|
||||
aspect_ratio=aspect_ratio,
|
||||
sync=sync,
|
||||
optimize_description=optimize_description,
|
||||
exclude_elements=exclude_elements,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,53 @@
|
||||
from .utils.shot_utils import get_text_input_types, create_text_payload, make_api_request, shot_by_text_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByTextAutomaticNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_text_input_types()
|
||||
input_types["required"]["shot_size"] = ("STRING", {"default": "1000, 1000"})
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE")
|
||||
RETURN_NAMES = (
|
||||
"output_image_1",
|
||||
"output_image_2",
|
||||
"output_image_3",
|
||||
"output_image_4",
|
||||
"output_image_5",
|
||||
"output_image_6",
|
||||
"output_image_7",
|
||||
)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_text_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
scene_description,
|
||||
mode,
|
||||
shot_size,
|
||||
api_key,
|
||||
sync=False,
|
||||
optimize_description=True,
|
||||
exclude_elements="",
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_text_payload(
|
||||
image,
|
||||
api_key,
|
||||
scene_description,
|
||||
mode,
|
||||
PlacementType.AUTOMATIC.value,
|
||||
shot_size=shot_size,
|
||||
sync=sync,
|
||||
optimize_description=optimize_description,
|
||||
exclude_elements=exclude_elements,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key, Placement_type= PlacementType.AUTOMATIC.value)
|
||||
@@ -0,0 +1,57 @@
|
||||
from .utils.shot_utils import get_text_input_types, create_text_payload, make_api_request, shot_by_text_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByTextCustomCoordinatesNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_text_input_types()
|
||||
input_types["required"]["shot_size"] = ("STRING", {"default": "1000, 1000"})
|
||||
input_types["required"]["foreground_image_size"] = (
|
||||
"STRING",
|
||||
{"default": "500,500"},
|
||||
)
|
||||
input_types["required"]["foreground_image_location"] = (
|
||||
"STRING",
|
||||
{"default": "0, 0"},
|
||||
)
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_text_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
scene_description,
|
||||
mode,
|
||||
shot_size,
|
||||
foreground_image_size,
|
||||
foreground_image_location,
|
||||
api_key,
|
||||
sync=False,
|
||||
optimize_description=True,
|
||||
exclude_elements="",
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_text_payload(
|
||||
image,
|
||||
api_key,
|
||||
scene_description,
|
||||
mode,
|
||||
PlacementType.CUSTOM_COORDINATES.value,
|
||||
shot_size=shot_size,
|
||||
foreground_image_size=foreground_image_size,
|
||||
foreground_image_location=foreground_image_location,
|
||||
sync=sync,
|
||||
optimize_description=optimize_description,
|
||||
exclude_elements=exclude_elements,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,45 @@
|
||||
from .utils.shot_utils import get_text_input_types, create_text_payload, make_api_request, shot_by_text_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByTextManualPaddingNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_text_input_types()
|
||||
input_types["required"]["padding_values"] = ("STRING", {"default": "0,0,0,0"})
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_text_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
scene_description,
|
||||
mode,
|
||||
padding_values,
|
||||
api_key,
|
||||
sync=False,
|
||||
optimize_description=True,
|
||||
exclude_elements="",
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_text_payload(
|
||||
image,
|
||||
api_key,
|
||||
scene_description,
|
||||
mode,
|
||||
PlacementType.MANUAL_PADDING.value,
|
||||
padding_values=padding_values,
|
||||
sync=sync,
|
||||
optimize_description=optimize_description,
|
||||
exclude_elements=exclude_elements,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
@@ -0,0 +1,62 @@
|
||||
from .utils.shot_utils import get_text_input_types, create_text_payload, make_api_request, shot_by_text_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByTextManualPlacementNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_text_input_types()
|
||||
input_types["required"]["shot_size"] = ("STRING", {"default": "1000, 1000"})
|
||||
input_types["required"]["manual_placement_selection"] = (
|
||||
[
|
||||
"upper_left",
|
||||
"upper_right",
|
||||
"bottom_left",
|
||||
"bottom_right",
|
||||
"right_center",
|
||||
"left_center",
|
||||
"upper_center",
|
||||
"bottom_center",
|
||||
"center_vertical",
|
||||
"center_horizontal",
|
||||
],
|
||||
{"default": "upper_left"},
|
||||
)
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_text_api_url
|
||||
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
scene_description,
|
||||
mode,
|
||||
shot_size,
|
||||
manual_placement_selection,
|
||||
api_key,
|
||||
sync=False,
|
||||
optimize_description=True,
|
||||
exclude_elements="",
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_text_payload(
|
||||
image,
|
||||
api_key,
|
||||
scene_description,
|
||||
mode,
|
||||
PlacementType.MANUAL_PLACEMENT.value,
|
||||
shot_size=shot_size,
|
||||
manual_placement_selection=manual_placement_selection,
|
||||
sync=sync,
|
||||
optimize_description=optimize_description,
|
||||
exclude_elements=exclude_elements,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
+42
-69
@@ -1,69 +1,42 @@
|
||||
import requests
|
||||
import torch
|
||||
|
||||
from .common import postprocess_image, preprocess_image, image_to_base64
|
||||
|
||||
class ShotByTextNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"scene_description": ("STRING",),
|
||||
"optimize_description": ("INT", {"default": 1}),
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}) # API Key input with a default value
|
||||
},
|
||||
"optional": {
|
||||
"content_moderation": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/product/lifestyle_shot_by_text" # Eraser API URL
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, api_key, scene_description, optimize_description, content_moderation):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
|
||||
# Check if image and mask are tensors, if so, convert to NumPy arrays
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
|
||||
optimize_description = bool(optimize_description)
|
||||
image_base64 = image_to_base64(image)
|
||||
payload = {
|
||||
"file": image_base64,
|
||||
"scene_description": scene_description,
|
||||
"optimize_description": optimize_description,
|
||||
"placement_type": "original",
|
||||
"original_quality": True,
|
||||
"sync": True,
|
||||
"content_moderation": content_moderation
|
||||
|
||||
}
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
# Check for successful response
|
||||
if response.status_code == 200:
|
||||
print('response is 200')
|
||||
# Process the output image from API response
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['result'][0][0])
|
||||
result_image = postprocess_image(image_response.content)
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
|
||||
from .utils.shot_utils import get_text_input_types, create_text_payload, make_api_request, shot_by_text_api_url, PlacementType
|
||||
|
||||
|
||||
class ShotByTextOriginalNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
input_types = get_text_input_types()
|
||||
return input_types
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = shot_by_text_api_url
|
||||
def execute(
|
||||
self,
|
||||
image,
|
||||
scene_description,
|
||||
mode,
|
||||
api_key,
|
||||
sync=True,
|
||||
optimize_description=True,
|
||||
exclude_elements="",
|
||||
force_rmbg=False,
|
||||
content_moderation=False,
|
||||
):
|
||||
payload = create_text_payload(
|
||||
image,
|
||||
api_key,
|
||||
scene_description,
|
||||
mode,
|
||||
PlacementType.ORIGINAL.value,
|
||||
original_quality=True,
|
||||
sync=sync,
|
||||
optimize_description=optimize_description,
|
||||
exclude_elements=exclude_elements,
|
||||
force_rmbg=force_rmbg,
|
||||
content_moderation=content_moderation,
|
||||
)
|
||||
return make_api_request(self.api_url, payload, api_key)
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import requests
|
||||
|
||||
from .common import postprocess_image, preprocess_image, image_to_base64
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
postprocess_image,
|
||||
preprocess_image,
|
||||
)
|
||||
|
||||
|
||||
class TailoredGenNode():
|
||||
@@ -73,7 +78,7 @@ class TailoredGenNode():
|
||||
response = requests.post(
|
||||
self.api_url + model_id,
|
||||
json=payload,
|
||||
headers={"api_token": api_key}
|
||||
headers=bria_json_headers(api_key),
|
||||
)
|
||||
if response.status_code == 200:
|
||||
response_dict = response.json()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import requests
|
||||
|
||||
from .common import bria_json_headers
|
||||
|
||||
class TailoredModelInfoNode():
|
||||
@classmethod
|
||||
@@ -23,7 +23,7 @@ class TailoredModelInfoNode():
|
||||
def execute(self, model_id, api_key):
|
||||
response = requests.get(
|
||||
self.api_url + model_id,
|
||||
headers={"api_token": api_key}
|
||||
headers=bria_json_headers(api_key),
|
||||
)
|
||||
if response.status_code == 200:
|
||||
generation_prefix = response.json()["generation_prefix"]
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import numpy as np
|
||||
import requests
|
||||
from PIL import Image
|
||||
import io
|
||||
import requests
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import torch
|
||||
|
||||
from .common import image_to_base64, preprocess_image
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
normalize_images_input,
|
||||
to_pil_safe,
|
||||
)
|
||||
|
||||
class TailoredPortraitNode():
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # Input image from another node
|
||||
"tailored_model_id": ("INT",),
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), # API Key input with a default value
|
||||
"images": ("IMAGE",),
|
||||
"tailored_model_id": ("STRING",),
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
},
|
||||
"optional": {
|
||||
"seed": ("INT", {"default": 123456}),
|
||||
@@ -23,53 +28,61 @@ class TailoredPortraitNode():
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("output_image",)
|
||||
RETURN_NAMES = ("output_images",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/tailored-gen/restyle_portrait" # Eraser API URL
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/tailored-gen/restyle_portrait"
|
||||
|
||||
# Define the execute method as expected by ComfyUI
|
||||
def execute(self, image, tailored_model_id, api_key, seed, tailored_model_influence, id_strength):
|
||||
def execute(
|
||||
self,
|
||||
images,
|
||||
tailored_model_id,
|
||||
api_key,
|
||||
seed,
|
||||
tailored_model_influence,
|
||||
id_strength
|
||||
):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
# Normalize images to list of PIL images
|
||||
images = normalize_images_input(images)
|
||||
|
||||
# Convert the image and mask directly to if isinstance(image, torch.Tensor):
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
|
||||
image_base64 = image_to_base64(image)
|
||||
batch_results = []
|
||||
|
||||
# Prepare the API request payload
|
||||
payload = {
|
||||
"id_image_file": f"{image_base64}",
|
||||
"tailored_model_id": tailored_model_id,
|
||||
"tailored_model_influence": tailored_model_influence,
|
||||
"id_strength": id_strength,
|
||||
"seed": seed
|
||||
}
|
||||
for idx, pil_image in enumerate(images):
|
||||
try:
|
||||
image_base64 = image_to_base64(pil_image)
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"api_token": f"{api_key}"
|
||||
}
|
||||
payload = {
|
||||
"id_image_file": image_base64,
|
||||
"tailored_model_id": int(tailored_model_id),
|
||||
"tailored_model_influence": tailored_model_influence,
|
||||
"id_strength": id_strength,
|
||||
"seed": seed
|
||||
}
|
||||
|
||||
headers = bria_json_headers(api_key)
|
||||
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
# Check for successful response
|
||||
if response.status_code == 200:
|
||||
print('response is 200')
|
||||
# Process the output image from API response
|
||||
response_dict = response.json()
|
||||
image_response = requests.get(response_dict['image_res'])
|
||||
result_image = Image.open(io.BytesIO(image_response.content))
|
||||
result_image = result_image.convert("RGB")
|
||||
result_image = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_image = torch.from_numpy(result_image)[None,]
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code}")
|
||||
image_response = requests.get(response_dict["image_res"])
|
||||
result_image = Image.open(io.BytesIO(image_response.content)).convert("RGB")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
# Convert to float32 tensor (H,W,C), 0-1
|
||||
result_array = np.array(result_image).astype(np.float32) / 255.0
|
||||
result_tensor = torch.from_numpy(result_array)
|
||||
|
||||
batch_results.append(result_tensor)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[TailoredPortraitNode] Skipping image {idx} due to error: {e}")
|
||||
fallback_array = np.array(pil_image).astype(np.float32) / 255.0
|
||||
batch_results.append(torch.from_numpy(fallback_array))
|
||||
|
||||
# Return list of tensors (avoids size/dtype mismatch)
|
||||
return (batch_results,)
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import requests
|
||||
|
||||
from .common import postprocess_image, preprocess_image, image_to_base64
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
postprocess_image,
|
||||
preprocess_image,
|
||||
)
|
||||
|
||||
|
||||
class Text2ImageBaseNode():
|
||||
@@ -38,7 +43,7 @@ class Text2ImageBaseNode():
|
||||
FUNCTION = "execute" # This is the method that will be executed
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/text-to-image/base/2.3" #"http://0.0.0.0:5000/v1/text-to-image/base/2.3"
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/text-to-image/base/3.2"
|
||||
|
||||
def execute(
|
||||
self, api_key, prompt, aspect_ratio, seed, negative_prompt,
|
||||
@@ -83,7 +88,7 @@ class Text2ImageBaseNode():
|
||||
response = requests.post(
|
||||
self.api_url,
|
||||
json=payload,
|
||||
headers={"api_token": api_key}
|
||||
headers=bria_json_headers(api_key),
|
||||
)
|
||||
if response.status_code == 200:
|
||||
response_dict = response.json()
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import requests
|
||||
|
||||
from .common import postprocess_image, preprocess_image, image_to_base64
|
||||
from .common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
postprocess_image,
|
||||
preprocess_image,
|
||||
)
|
||||
|
||||
|
||||
class Text2ImageFastNode():
|
||||
@@ -76,7 +81,7 @@ class Text2ImageFastNode():
|
||||
response = requests.post(
|
||||
self.api_url,
|
||||
json=payload,
|
||||
headers={"api_token": api_key}
|
||||
headers=bria_json_headers(api_key),
|
||||
)
|
||||
if response.status_code == 200:
|
||||
response_dict = response.json()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import requests
|
||||
|
||||
from .common import postprocess_image
|
||||
from .common import bria_json_headers, postprocess_image
|
||||
|
||||
|
||||
class Text2ImageHDNode():
|
||||
@@ -29,7 +29,7 @@ class Text2ImageHDNode():
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/text-to-image/hd/2.3" #"http://0.0.0.0:5000/v1/text-to-image/hd/2.3"
|
||||
self.api_url = "https://engine.prod.bria-api.com/v1/text-to-image/hd/2.2" #"http://0.0.0.0:5000/v1/text-to-image/hd/2.3"
|
||||
|
||||
def execute(
|
||||
self, api_key, prompt, aspect_ratio, seed, negative_prompt,
|
||||
@@ -52,7 +52,7 @@ class Text2ImageHDNode():
|
||||
response = requests.post(
|
||||
self.api_url,
|
||||
json=payload,
|
||||
headers={"api_token": api_key}
|
||||
headers=bria_json_headers(api_key),
|
||||
)
|
||||
if response.status_code == 200:
|
||||
response_dict = response.json()
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
import requests
|
||||
import torch
|
||||
from ..common import (
|
||||
bria_json_headers,
|
||||
image_to_base64,
|
||||
postprocess_image,
|
||||
preprocess_image,
|
||||
)
|
||||
|
||||
shot_by_text_api_url = (
|
||||
"https://engine.prod.bria-api.com/v1/product/lifestyle_shot_by_text"
|
||||
)
|
||||
shot_by_image_api_url = (
|
||||
"https://engine.prod.bria-api.com/v1/product/lifestyle_shot_by_image"
|
||||
)
|
||||
|
||||
from enum import Enum
|
||||
|
||||
class PlacementType(str, Enum):
|
||||
ORIGINAL = "original"
|
||||
AUTOMATIC = "automatic"
|
||||
MANUAL_PLACEMENT = "manual_placement"
|
||||
MANUAL_PADDING = "manual_padding"
|
||||
CUSTOM_COORDINATES = "custom_coordinates"
|
||||
AUTOMATIC_ASPECT_RATIO = "automatic_aspect_ratio"
|
||||
|
||||
|
||||
|
||||
def validate_api_key(api_key):
|
||||
"""Validate API key input"""
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
|
||||
|
||||
def update_payload_for_placement(placement_type, payload, **kwargs):
|
||||
if placement_type == PlacementType.AUTOMATIC.value:
|
||||
payload["shot_size"] = [
|
||||
int(x.strip()) for x in kwargs.get("shot_size").split(",")
|
||||
]
|
||||
elif placement_type == PlacementType.MANUAL_PLACEMENT.value:
|
||||
payload["shot_size"] = [
|
||||
int(x.strip()) for x in kwargs.get("shot_size").split(",")
|
||||
]
|
||||
payload["manual_placement_selection"] = [
|
||||
kwargs.get("manual_placement_selection", "upper_left")
|
||||
]
|
||||
elif placement_type == PlacementType.CUSTOM_COORDINATES.value:
|
||||
payload["shot_size"] = [
|
||||
int(x.strip()) for x in kwargs.get("shot_size").split(",")
|
||||
]
|
||||
payload["foreground_image_size"] = [
|
||||
int(x.strip()) for x in kwargs.get("foreground_image_size").split(",")
|
||||
]
|
||||
payload["foreground_image_location"] = [
|
||||
int(x.strip()) for x in kwargs.get("foreground_image_location").split(",")
|
||||
]
|
||||
elif placement_type == PlacementType.MANUAL_PADDING.value:
|
||||
payload["padding_values"] = [
|
||||
int(x.strip()) for x in kwargs.get("padding_values").split(",")
|
||||
]
|
||||
|
||||
elif placement_type == PlacementType.AUTOMATIC_ASPECT_RATIO.value:
|
||||
payload["aspect_ratio"] = kwargs.get("aspect_ratio", "1:1")
|
||||
elif placement_type == PlacementType.ORIGINAL.value:
|
||||
payload["original_quality"] = kwargs.get("original_quality", True)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def create_text_payload(
|
||||
image, api_key, scene_description, mode, placement_type, **kwargs
|
||||
):
|
||||
|
||||
validate_api_key(api_key)
|
||||
|
||||
|
||||
# Process image
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
|
||||
image_base64 = image_to_base64(image)
|
||||
|
||||
payload = {
|
||||
"file": image_base64,
|
||||
"placement_type": placement_type,
|
||||
"sync": True,
|
||||
"num_results": 1,
|
||||
"force_rmbg": kwargs.get("force_rmbg", False),
|
||||
"content_moderation": kwargs.get("content_moderation", False),
|
||||
"scene_description": scene_description,
|
||||
"mode": mode,
|
||||
"optimize_description": kwargs.get("optimize_description", True),
|
||||
}
|
||||
|
||||
if kwargs.get("exclude_elements", "").strip():
|
||||
payload["exclude_elements"] = kwargs["exclude_elements"]
|
||||
|
||||
payload = update_payload_for_placement(placement_type, payload, **kwargs)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def create_image_payload(image, ref_image, api_key, placement_type, **kwargs):
|
||||
"""Create payload for image-based shot nodes"""
|
||||
validate_api_key(api_key)
|
||||
|
||||
if isinstance(image, torch.Tensor):
|
||||
image = preprocess_image(image)
|
||||
if isinstance(ref_image, torch.Tensor):
|
||||
ref_image = preprocess_image(ref_image)
|
||||
|
||||
image_base64 = image_to_base64(image)
|
||||
ref_image_base64 = image_to_base64(ref_image)
|
||||
|
||||
# Base payload
|
||||
payload = {
|
||||
"file": image_base64,
|
||||
"ref_image_file": ref_image_base64,
|
||||
"enhance_ref_image": kwargs.get("enhance_ref_image", True),
|
||||
"ref_image_influence": kwargs.get("ref_image_influence", 1.0),
|
||||
"placement_type": placement_type,
|
||||
"sync": True,
|
||||
"num_results": 1,
|
||||
"force_rmbg": kwargs.get("force_rmbg", False),
|
||||
"content_moderation": kwargs.get("content_moderation", False),
|
||||
}
|
||||
|
||||
payload = update_payload_for_placement(placement_type, payload, **kwargs)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def make_api_request(api_url, payload, api_key, Placement_type = None):
|
||||
"""Make API request and return processed image"""
|
||||
|
||||
|
||||
try:
|
||||
headers = bria_json_headers(api_key)
|
||||
response = requests.post(api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
print("response is 200")
|
||||
response_dict = response.json()
|
||||
if Placement_type == PlacementType.AUTOMATIC.value:
|
||||
result_images = []
|
||||
for i, result in enumerate(response_dict.get("result", [])[:7]):
|
||||
image_url = result[0]
|
||||
image_response = requests.get(image_url)
|
||||
processed = postprocess_image(image_response.content)
|
||||
result_images.append(processed)
|
||||
|
||||
# If less than 7 images, pad with None to match ComfyUI return structure
|
||||
while len(result_images) < 7:
|
||||
result_images.append(None)
|
||||
print(result_images)
|
||||
|
||||
return tuple(result_images)
|
||||
|
||||
image_response = requests.get(response_dict["result"][0][0])
|
||||
result_image = postprocess_image(image_response.content)
|
||||
return (result_image,)
|
||||
else:
|
||||
raise Exception(
|
||||
f"Error: API request failed with status code {response.status_code}{response.text}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
|
||||
|
||||
def get_common_input_types():
|
||||
"""Get common input types for all nodes"""
|
||||
return {
|
||||
"required": {"api_key": ("STRING", {"default": "BRIA_API_TOKEN"})},
|
||||
"optional": {
|
||||
"force_rmbg": ("BOOLEAN", {"default": False}),
|
||||
"content_moderation": ("BOOLEAN", {"default": False}),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_text_input_types():
|
||||
"""Get text-specific input types"""
|
||||
common = get_common_input_types()
|
||||
common["required"].update(
|
||||
{
|
||||
"image": ("IMAGE",),
|
||||
"scene_description": ("STRING",),
|
||||
"mode": (["base", "fast", "high_control"], {"default": "fast"}),
|
||||
}
|
||||
)
|
||||
common["optional"].update(
|
||||
{
|
||||
"optimize_description": ("BOOLEAN", {"default": True}),
|
||||
"exclude_elements": ("STRING", {"default": ""}),
|
||||
}
|
||||
)
|
||||
return common
|
||||
|
||||
|
||||
def get_image_input_types():
|
||||
"""Get image-specific input types"""
|
||||
common = get_common_input_types()
|
||||
common["required"].update({"image": ("IMAGE",), "ref_image": ("IMAGE",)})
|
||||
common["optional"].update(
|
||||
{
|
||||
"enhance_ref_image": ("BOOLEAN", {"default": True}),
|
||||
"ref_image_influence": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0}),
|
||||
}
|
||||
)
|
||||
return common
|
||||
@@ -0,0 +1,52 @@
|
||||
import os
|
||||
import folder_paths
|
||||
|
||||
class LoadVideoFramesNode:
|
||||
"""
|
||||
Load a video file from the input folder or upload.
|
||||
|
||||
Parameters:
|
||||
video (str): Selected or uploaded video filename.
|
||||
|
||||
Returns:
|
||||
video_path (STRING): Absolute path to the video file.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
input_dir = folder_paths.get_input_directory()
|
||||
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
|
||||
files = folder_paths.filter_files_content_types(files, ["video"])
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"video": (sorted(files), {"video_upload": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("video_path",)
|
||||
FUNCTION = "load_video"
|
||||
CATEGORY = "API Nodes"
|
||||
|
||||
def load_video(self, video):
|
||||
video_path = folder_paths.get_annotated_filepath(video)
|
||||
if not os.path.exists(video_path):
|
||||
raise FileNotFoundError(f"Video file not found: {video_path}")
|
||||
|
||||
return (video_path,)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, video, **kwargs):
|
||||
"""Force re-execution when video file changes"""
|
||||
video_path = folder_paths.get_annotated_filepath(video)
|
||||
if os.path.exists(video_path):
|
||||
return os.path.getmtime(video_path)
|
||||
return float("nan")
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, video, **kwargs):
|
||||
"""Validate that the video file exists"""
|
||||
if not folder_paths.exists_annotated_filepath(video):
|
||||
return f"Invalid video file: {video}"
|
||||
return True
|
||||
@@ -0,0 +1,135 @@
|
||||
import os
|
||||
import uuid
|
||||
import folder_paths
|
||||
import requests
|
||||
|
||||
class PreviewVideoURLNode:
|
||||
"""
|
||||
Bria Preview Video URL Node
|
||||
|
||||
This node takes a video URL as a string and downloads it to preview
|
||||
directly in the ComfyUI interface.
|
||||
|
||||
Parameters:
|
||||
- video_url: URL of the video to preview (http/https)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.output_dir = folder_paths.get_temp_directory()
|
||||
self.type = "temp"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"video_url": ("STRING", {
|
||||
"default": "",
|
||||
"multiline": False,
|
||||
"tooltip": "URL of the video to preview (http/https)"
|
||||
}),
|
||||
},
|
||||
"hidden": {
|
||||
"prompt": "PROMPT",
|
||||
"extra_pnginfo": "EXTRA_PNGINFO"
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "preview_video_url"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "API Nodes"
|
||||
DESCRIPTION = "Previews a video from URL directly in the ComfyUI interface."
|
||||
|
||||
def preview_video_url(self, video_url, prompt=None, extra_pnginfo=None):
|
||||
"""
|
||||
Preview video from URL
|
||||
|
||||
Args:
|
||||
video_url: URL of the video (http/https)
|
||||
prompt: Hidden parameter for ComfyUI workflow
|
||||
extra_pnginfo: Hidden parameter for ComfyUI metadata
|
||||
|
||||
Returns:
|
||||
dict: UI output with video file for preview
|
||||
"""
|
||||
if not video_url or video_url.strip() == "":
|
||||
raise ValueError("video_url cannot be empty")
|
||||
|
||||
if not video_url.startswith("http://") and not video_url.startswith("https://"):
|
||||
raise ValueError("video_url must be a valid HTTP or HTTPS URL")
|
||||
|
||||
print(f"Downloading video from URL: {video_url}")
|
||||
|
||||
# Download video from URL
|
||||
try:
|
||||
response = requests.get(video_url, stream=True, timeout=60)
|
||||
response.raise_for_status()
|
||||
|
||||
# Determine file extension from URL or Content-Type
|
||||
content_type = response.headers.get('Content-Type', '')
|
||||
extension = self._get_extension_from_content_type(content_type, video_url)
|
||||
|
||||
filename_prefix = str(uuid.uuid4()) + "_video_url_preview"
|
||||
|
||||
# Get save path
|
||||
full_output_folder = self.output_dir
|
||||
filename = f"{filename_prefix}.{extension}"
|
||||
filepath = os.path.join(full_output_folder, filename)
|
||||
|
||||
|
||||
# Save video to temp directory
|
||||
print(f"Saving video to: {filepath}")
|
||||
with open(filepath, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
|
||||
file_size = os.path.getsize(filepath)
|
||||
print(f"Video downloaded successfully: {filename} ({file_size / (1024*1024):.2f} MB)")
|
||||
|
||||
return {
|
||||
"ui": {
|
||||
"images": [{
|
||||
"filename": filename,
|
||||
"subfolder": "",
|
||||
"type": self.type,
|
||||
"format": extension
|
||||
}],
|
||||
"animated": (True,),
|
||||
"has_audio": (True,)
|
||||
}
|
||||
}
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise Exception(f"Failed to download video from URL: {str(e)}")
|
||||
except Exception as e:
|
||||
raise Exception(f"Error previewing video: {str(e)}")
|
||||
|
||||
def _get_extension_from_content_type(self, content_type, url):
|
||||
"""
|
||||
Determine file extension from Content-Type header or URL
|
||||
"""
|
||||
# Map common video MIME types to extensions
|
||||
content_type_map = {
|
||||
'video/mp4': 'mp4',
|
||||
'video/webm': 'webm',
|
||||
'video/quicktime': 'mov',
|
||||
'video/x-matroska': 'mkv',
|
||||
'video/x-msvideo': 'avi',
|
||||
'image/gif': 'gif',
|
||||
}
|
||||
|
||||
# Try to get extension from Content-Type
|
||||
for mime_type, ext in content_type_map.items():
|
||||
if mime_type in content_type.lower():
|
||||
return ext
|
||||
|
||||
# Try to get extension from URL
|
||||
url_path = url.split('?')[0] # Remove query parameters
|
||||
if '.' in url_path:
|
||||
url_ext = url_path.rsplit('.', 1)[-1].lower()
|
||||
if url_ext in ['mp4', 'webm', 'mov', 'mkv', 'avi', 'gif', 'webp']:
|
||||
return url_ext
|
||||
|
||||
# Default to mp4
|
||||
return 'mp4'
|
||||
@@ -0,0 +1,115 @@
|
||||
import os
|
||||
import uuid
|
||||
import requests
|
||||
import folder_paths
|
||||
from ..common import bria_json_headers, poll_status_until_completed
|
||||
from .video_utils import upload_video_to_s3
|
||||
|
||||
class RemoveVideoBackgroundNode():
|
||||
"""
|
||||
Removes the background from a video using the Bria API.
|
||||
|
||||
Parameters:
|
||||
api_key (str): Your Bria API key.
|
||||
video_url (str): Local path or URL of the video to process.
|
||||
preserve_audio (bool, optional): Whether to keep the audio track. Default is True.
|
||||
output_container_and_codec (str, optional): Desired output format and codec. Default is "webm_vp9".
|
||||
|
||||
Returns:
|
||||
result_video_url (STRING): URL of the video with background removed.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
"video_url": ("STRING", {
|
||||
"default": "",
|
||||
"tooltip": "URL of video to process (provide either frames or video_url)"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"preserve_audio": ("BOOLEAN", {"default": True}),
|
||||
"output_container_and_codec": ([
|
||||
"mp4_h264",
|
||||
"mp4_h265",
|
||||
"webm_vp9",
|
||||
"mov_h265",
|
||||
"mov_proresks",
|
||||
"mkv_h264",
|
||||
"mkv_h265",
|
||||
"mkv_vp9",
|
||||
"gif"
|
||||
], {"default": "webm_vp9"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("result_video_url",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/video/edit/remove_background"
|
||||
|
||||
def execute(self, api_key, video_url, preserve_audio=True, output_container_and_codec="webm_vp9",):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
video_path = None
|
||||
|
||||
input_video_url = ""
|
||||
if video_url and video_url.strip() != "":
|
||||
if os.path.exists(video_url):
|
||||
filename = f"{ str(uuid.uuid4())}_{os.path.basename(video_url)}"
|
||||
input_video_url = upload_video_to_s3(video_url, filename, api_key)
|
||||
if video_url.startswith(folder_paths.get_temp_directory()):
|
||||
video_path = None
|
||||
else:
|
||||
input_video_url = video_url
|
||||
|
||||
try:
|
||||
|
||||
print("Step 3: Calling Bria API for background removal...")
|
||||
payload = {
|
||||
"video": input_video_url,
|
||||
"preserve_audio": preserve_audio,
|
||||
"output_container_and_codec": output_container_and_codec
|
||||
}
|
||||
|
||||
headers = bria_json_headers(api_key)
|
||||
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
print('Initial Video RMBG request successful, polling for completion...')
|
||||
response_dict = response.json()
|
||||
|
||||
status_url = response_dict.get('status_url')
|
||||
request_id = response_dict.get('request_id')
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_key, timeout=3600, check_interval=5)
|
||||
|
||||
result_video_url = final_response['result']['video_url']
|
||||
|
||||
print(f"Video processing completed. Result URL: {result_video_url}")
|
||||
print(f"Background removal complete! Use Preview Video URL node to view the result.")
|
||||
|
||||
return (result_video_url,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
finally:
|
||||
if video_path:
|
||||
try:
|
||||
if os.path.exists(video_path):
|
||||
os.unlink(video_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import os
|
||||
import uuid
|
||||
import requests
|
||||
import folder_paths
|
||||
from ..common import bria_json_headers, poll_status_until_completed
|
||||
from .video_utils import upload_video_to_s3
|
||||
|
||||
class VideoEraseElementsNode():
|
||||
"""
|
||||
Erase elements from a video using the Bria API.
|
||||
|
||||
Parameters:
|
||||
api_key (str): Your Bria API key.
|
||||
video_url (str): Local path or URL of the video to process.
|
||||
mask_url (str, optional): URL of a mask video for selective erasing.
|
||||
output_container_and_codec (str, optional): Desired output format and codec. Default is "mp4_h264".
|
||||
preserve_audio (bool, optional): Whether to keep the audio track. Default is True.
|
||||
|
||||
Returns:
|
||||
result_video_url (STRING): URL of the processed video with elements erased.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
"video_url": ("STRING", {
|
||||
"default": "",
|
||||
"tooltip": "URL of video to process (provide either frames or video_url)"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"mask_url": ("STRING", {
|
||||
"default": "",
|
||||
"tooltip": "URL of mask video (optional)"
|
||||
}),
|
||||
"output_container_and_codec": ([
|
||||
"mp4_h264",
|
||||
"mp4_h265",
|
||||
"webm_vp9",
|
||||
"mov_h265",
|
||||
"mov_proresks",
|
||||
"mkv_h264",
|
||||
"mkv_h265",
|
||||
"mkv_vp9",
|
||||
"gif"
|
||||
], {"default": "mp4_h264"}),
|
||||
"preserve_audio": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("result_video_url",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/video/edit/erase"
|
||||
|
||||
def execute(self, api_key, video_url, mask_url="", output_container_and_codec="mp4_h264", preserve_audio=True):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
video_path = None
|
||||
|
||||
if video_url and video_url.strip() != "":
|
||||
# Check if video_url is a local file path or a URL
|
||||
if os.path.exists(video_url):
|
||||
filename = f"{ str(uuid.uuid4())}_{os.path.basename(video_url)}"
|
||||
input_video_url = upload_video_to_s3(video_url, filename, api_key)
|
||||
|
||||
if not input_video_url or not (input_video_url.startswith('http://') or input_video_url.startswith('https://')):
|
||||
raise Exception(f"Failed to upload video to S3. Got: {input_video_url}")
|
||||
if video_url.startswith(folder_paths.get_temp_directory()):
|
||||
video_path = None
|
||||
else:
|
||||
input_video_url = video_url
|
||||
|
||||
try:
|
||||
|
||||
print("Step 3: Calling Bria API for element erasure...")
|
||||
payload = {
|
||||
"video": input_video_url,
|
||||
"mask": mask_url,
|
||||
"output_container_and_codec": output_container_and_codec,
|
||||
"preserve_audio": preserve_audio
|
||||
}
|
||||
|
||||
headers = bria_json_headers(api_key)
|
||||
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
print('Initial Video Erase Elements request successful, polling for completion...')
|
||||
response_dict = response.json()
|
||||
|
||||
status_url = response_dict.get('status_url')
|
||||
request_id = response_dict.get('request_id')
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_key, timeout=3600, check_interval=5)
|
||||
|
||||
result_video_url = final_response['result']['video_url']
|
||||
|
||||
print(f"Video processing completed. Result URL: {result_video_url}")
|
||||
print(f"Element erasure complete! Use Preview Video URL node to view the result.")
|
||||
|
||||
return (result_video_url,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
finally:
|
||||
if video_path:
|
||||
try:
|
||||
if os.path.exists(video_path):
|
||||
os.unlink(video_path)
|
||||
except:
|
||||
pass
|
||||
@@ -0,0 +1,120 @@
|
||||
import os
|
||||
import uuid
|
||||
import requests
|
||||
import folder_paths
|
||||
from ..common import bria_json_headers, poll_status_until_completed
|
||||
from .video_utils import upload_video_to_s3
|
||||
|
||||
class VideoIncreaseResolutionNode():
|
||||
"""
|
||||
Increase the resolution of a video using the Bria API.
|
||||
|
||||
Parameters:
|
||||
api_key (str): Your Bria API key.
|
||||
video_url (str): Local path or URL of the video to process.
|
||||
desired_increase (str, optional): Resolution increase factor, '2' or '4'. Default is '2'.
|
||||
output_container_and_codec (str, optional): Desired output format and codec. Default is "mp4_h264".
|
||||
preserve_audio (bool, optional): Whether to keep the audio track. Default is True.
|
||||
|
||||
Returns:
|
||||
result_video_url (STRING): URL of the processed video with increased resolution.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
"video_url": ("STRING", {
|
||||
"default": "",
|
||||
"tooltip": "URL of video to process (provide either frames or video_url)"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"desired_increase": (['2', '4'], {"default": '2'}),
|
||||
"output_container_and_codec": ([
|
||||
"mp4_h264",
|
||||
"mp4_h265",
|
||||
"webm_vp9",
|
||||
"mov_h265",
|
||||
"mov_proresks",
|
||||
"mkv_h264",
|
||||
"mkv_h265",
|
||||
"mkv_vp9",
|
||||
"gif"
|
||||
], {"default": "mp4_h264"}),
|
||||
"preserve_audio": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("result_video_url",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/video/edit/increase_resolution"
|
||||
|
||||
def execute(self, api_key, video_url, desired_increase='2', output_container_and_codec="mp4_h264", preserve_audio=True):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
video_path = None
|
||||
|
||||
if video_url and video_url.strip() != "":
|
||||
if os.path.exists(video_url):
|
||||
filename = f"{ str(uuid.uuid4())}_{os.path.basename(video_url)}"
|
||||
input_video_url = upload_video_to_s3(video_url, filename, api_key)
|
||||
|
||||
if not input_video_url or not (input_video_url.startswith('http://') or input_video_url.startswith('https://')):
|
||||
raise Exception(f"Failed to upload video to S3. Got: {input_video_url}")
|
||||
|
||||
if video_url.startswith(folder_paths.get_temp_directory()):
|
||||
video_path = None
|
||||
else:
|
||||
input_video_url = video_url
|
||||
|
||||
try:
|
||||
|
||||
print("Step 3: Calling Bria API for resolution increase...")
|
||||
payload = {
|
||||
"video": input_video_url,
|
||||
"desired_increase": desired_increase,
|
||||
"output_container_and_codec": output_container_and_codec,
|
||||
"preserve_audio": preserve_audio
|
||||
}
|
||||
|
||||
headers = bria_json_headers(api_key)
|
||||
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
print('Initial Video Increase Resolution request successful, polling for completion...')
|
||||
response_dict = response.json()
|
||||
|
||||
status_url = response_dict.get('status_url')
|
||||
request_id = response_dict.get('request_id')
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_key, timeout=3600, check_interval=5)
|
||||
|
||||
result_video_url = final_response['result']['video_url']
|
||||
|
||||
print(f"Video processing completed. Result URL: {result_video_url}")
|
||||
print(f"Resolution increase complete! Use Preview Video URL node to view the result.")
|
||||
|
||||
return (result_video_url,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
finally:
|
||||
if video_path:
|
||||
try:
|
||||
if os.path.exists(video_path):
|
||||
os.unlink(video_path)
|
||||
except:
|
||||
pass
|
||||
@@ -0,0 +1,127 @@
|
||||
import os
|
||||
import uuid
|
||||
import requests
|
||||
import folder_paths
|
||||
from ..common import bria_json_headers, poll_status_until_completed
|
||||
from .video_utils import upload_video_to_s3
|
||||
import json
|
||||
|
||||
class VideoMaskByKeyPointsNode():
|
||||
"""
|
||||
Generate a video mask using key points with the Bria API.
|
||||
|
||||
Parameters:
|
||||
key_points (str): JSON string of key points for masking.
|
||||
api_key (str): Your Bria API key.
|
||||
video_url (str): Local path or URL of the video to process.
|
||||
output_container_and_codec (str, optional): Desired output format and codec. Default is "mp4_h264".
|
||||
preserve_audio (bool, optional): Whether to keep the audio track. Default is True.
|
||||
|
||||
Returns:
|
||||
mask_url (STRING): URL of the generated video mask.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"key_points": ("STRING", {"default": "[]", "multiline": True}),
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
"video_url": ("STRING", {
|
||||
"default": "",
|
||||
"tooltip": "URL of video to process (provide either frames or video_url)"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"output_container_and_codec": ([
|
||||
"mp4_h264",
|
||||
"mp4_h265",
|
||||
"webm_vp9",
|
||||
"mov_h265",
|
||||
"mov_proresks",
|
||||
"mkv_h264",
|
||||
"mkv_h265",
|
||||
"mkv_vp9",
|
||||
"gif"
|
||||
], {"default": "mp4_h264"}),
|
||||
"preserve_audio": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("mask_url",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/video/segment/mask_by_key_points"
|
||||
|
||||
def execute(self, key_points, api_key, video_url, output_container_and_codec="mp4_h264", preserve_audio=True):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
try:
|
||||
key_points_array = json.loads(key_points)
|
||||
except json.JSONDecodeError as e:
|
||||
raise Exception(f"Invalid JSON format for key_points: {e}")
|
||||
|
||||
video_path = None
|
||||
|
||||
if video_url and video_url.strip() != "":
|
||||
if os.path.exists(video_url):
|
||||
filename = f"{ str(uuid.uuid4())}_{os.path.basename(video_url)}"
|
||||
input_video_url = upload_video_to_s3(video_url, filename, api_key)
|
||||
|
||||
if not input_video_url or not (input_video_url.startswith('http://') or input_video_url.startswith('https://')):
|
||||
raise Exception(f"Failed to upload video to S3. Got: {input_video_url}")
|
||||
|
||||
|
||||
if video_url.startswith(folder_paths.get_temp_directory()):
|
||||
video_path = None
|
||||
else:
|
||||
input_video_url = video_url
|
||||
|
||||
try:
|
||||
|
||||
print("Step 3: Calling Bria API for video mask generation by key points...")
|
||||
payload = {
|
||||
"video": input_video_url,
|
||||
"key_points": key_points_array,
|
||||
"output_container_and_codec": output_container_and_codec,
|
||||
"preserve_audio": preserve_audio
|
||||
}
|
||||
|
||||
headers = bria_json_headers(api_key)
|
||||
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
print('Initial Video Mask by Key Points request successful, polling for completion...')
|
||||
response_dict = response.json()
|
||||
|
||||
status_url = response_dict.get('status_url')
|
||||
request_id = response_dict.get('request_id')
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_key, timeout=3600, check_interval=5)
|
||||
|
||||
result_mask_url = final_response['result']['mask_url']
|
||||
|
||||
print(f"Video mask processing completed. Result URL: {result_mask_url}")
|
||||
print(f"Video mask generation complete! Use Preview Video URL node to view the result.")
|
||||
|
||||
return (result_mask_url,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
finally:
|
||||
if video_path:
|
||||
try:
|
||||
if os.path.exists(video_path):
|
||||
os.unlink(video_path)
|
||||
except:
|
||||
pass
|
||||
@@ -0,0 +1,121 @@
|
||||
import os
|
||||
import uuid
|
||||
import requests
|
||||
import folder_paths
|
||||
from ..common import bria_json_headers, poll_status_until_completed
|
||||
from .video_utils import upload_video_to_s3
|
||||
|
||||
class VideoMaskByPromptNode():
|
||||
"""
|
||||
Generate a video mask using a text prompt with the Bria API.
|
||||
|
||||
Parameters:
|
||||
prompt (str): Text prompt describing what to mask in the video.
|
||||
api_key (str): Your Bria API key.
|
||||
video_url (str): Local path or URL of the video to process.
|
||||
output_container_and_codec (str, optional): Desired output format and codec. Default is "mp4_h264".
|
||||
preserve_audio (bool, optional): Whether to keep the audio track. Default is True.
|
||||
|
||||
Returns:
|
||||
mask_url (STRING): URL of the generated video mask.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"prompt": ("STRING", {"default": ""}),
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
"video_url": ("STRING", {
|
||||
"default": "",
|
||||
"tooltip": "URL of video to process (provide either frames or video_url)"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"output_container_and_codec": ([
|
||||
"mp4_h264",
|
||||
"mp4_h265",
|
||||
"webm_vp9",
|
||||
"mov_h265",
|
||||
"mov_proresks",
|
||||
"mkv_h264",
|
||||
"mkv_h265",
|
||||
"mkv_vp9",
|
||||
"gif"
|
||||
], {"default": "mp4_h264"}),
|
||||
"preserve_audio": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("mask_url",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/video/segment/mask_by_prompt"
|
||||
|
||||
def execute(self, prompt, api_key, video_url, output_container_and_codec="mp4_h264", preserve_audio=True):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
video_path = None
|
||||
|
||||
if video_url and video_url.strip() != "":
|
||||
if os.path.exists(video_url):
|
||||
filename = f"{ str(uuid.uuid4())}_{os.path.basename(video_url)}"
|
||||
input_video_url = upload_video_to_s3(video_url, filename, api_key)
|
||||
|
||||
if not input_video_url or not (input_video_url.startswith('http://') or input_video_url.startswith('https://')):
|
||||
raise Exception(f"Failed to upload video to S3. Got: {input_video_url}")
|
||||
|
||||
|
||||
if video_url.startswith(folder_paths.get_temp_directory()):
|
||||
video_path = None
|
||||
else:
|
||||
input_video_url = video_url
|
||||
|
||||
try:
|
||||
|
||||
print("Step 3: Calling Bria API for video mask generation...")
|
||||
payload = {
|
||||
"video": input_video_url,
|
||||
"prompt": prompt,
|
||||
"output_container_and_codec": output_container_and_codec,
|
||||
"preserve_audio": preserve_audio
|
||||
}
|
||||
|
||||
headers = bria_json_headers(api_key)
|
||||
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
print('Initial Video Mask by Prompt request successful, polling for completion...')
|
||||
response_dict = response.json()
|
||||
|
||||
status_url = response_dict.get('status_url')
|
||||
request_id = response_dict.get('request_id')
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_key, timeout=3600, check_interval=5)
|
||||
|
||||
result_mask_url = final_response['result']['mask_url']
|
||||
|
||||
print(f"Video mask processing completed. Result URL: {result_mask_url}")
|
||||
print(f"Video mask generation complete! Use Preview Video URL node to view the result.")
|
||||
|
||||
return (result_mask_url,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
finally:
|
||||
if video_path:
|
||||
try:
|
||||
if os.path.exists(video_path):
|
||||
os.unlink(video_path)
|
||||
except:
|
||||
pass
|
||||
@@ -0,0 +1,133 @@
|
||||
import os
|
||||
import uuid
|
||||
import requests
|
||||
import folder_paths
|
||||
from ..common import bria_json_headers, poll_status_until_completed
|
||||
from .video_utils import upload_video_to_s3
|
||||
|
||||
class VideoSolidColorBackgroundNode():
|
||||
"""
|
||||
Apply a solid color background to a video using the Bria API.
|
||||
|
||||
Parameters:
|
||||
api_key (str): Your Bria API key.
|
||||
video_url (str): Local path or URL of the video to process.
|
||||
background_color (str, optional): Color to apply as background. Default is "Transparent".
|
||||
output_container_and_codec (str, optional): Desired output format and codec. Default is "mp4_h264".
|
||||
preserve_audio (bool, optional): Whether to keep the audio track. Default is True.
|
||||
|
||||
Returns:
|
||||
result_video_url (STRING): URL of the video with the solid color background applied.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required": {
|
||||
"api_key": ("STRING", {"default": "BRIA_API_TOKEN"}),
|
||||
"video_url": ("STRING", {
|
||||
"default": "",
|
||||
"tooltip": "URL of video to process (provide either frames or video_url)"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"background_color": ([
|
||||
"Transparent",
|
||||
"Black",
|
||||
"White",
|
||||
"Gray",
|
||||
"Red",
|
||||
"Green",
|
||||
"Blue",
|
||||
"Yellow",
|
||||
"Cyan",
|
||||
"Magenta",
|
||||
"Orange"
|
||||
], {"default": "Transparent"}),
|
||||
"output_container_and_codec": ([
|
||||
"mp4_h264",
|
||||
"mp4_h265",
|
||||
"webm_vp9",
|
||||
"mov_h265",
|
||||
"mov_proresks",
|
||||
"mkv_h264",
|
||||
"mkv_h265",
|
||||
"mkv_vp9",
|
||||
"gif"
|
||||
], {"default": "webm_vp9"}),
|
||||
"preserve_audio": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("result_video_url",)
|
||||
CATEGORY = "API Nodes"
|
||||
FUNCTION = "execute"
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = "https://engine.prod.bria-api.com/v2/video/edit/remove_background"
|
||||
|
||||
def execute(self, api_key, video_url, background_color="Transparent", output_container_and_codec="webm_vp9", preserve_audio=True):
|
||||
if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN":
|
||||
raise Exception("Please insert a valid API key.")
|
||||
video_path = None
|
||||
|
||||
if video_url and video_url.strip() != "":
|
||||
if os.path.exists(video_url):
|
||||
filename = f"{ str(uuid.uuid4())}_{os.path.basename(video_url)}"
|
||||
input_video_url = upload_video_to_s3(video_url, filename, api_key)
|
||||
|
||||
if not input_video_url or not (input_video_url.startswith('http://') or input_video_url.startswith('https://')):
|
||||
raise Exception(f"Failed to upload video to S3. Got: {input_video_url}")
|
||||
|
||||
|
||||
if video_url.startswith(folder_paths.get_temp_directory()):
|
||||
video_path = None
|
||||
else:
|
||||
input_video_url = video_url
|
||||
|
||||
try:
|
||||
|
||||
print("Step 3: Calling Bria API for solid color background...")
|
||||
payload = {
|
||||
"video": input_video_url,
|
||||
"background_color": background_color,
|
||||
"output_container_and_codec": output_container_and_codec,
|
||||
"preserve_audio": preserve_audio
|
||||
}
|
||||
|
||||
headers = bria_json_headers(api_key)
|
||||
|
||||
response = requests.post(self.api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code == 200 or response.status_code == 202:
|
||||
print('Initial Video Solid Color Background request successful, polling for completion...')
|
||||
response_dict = response.json()
|
||||
|
||||
status_url = response_dict.get('status_url')
|
||||
request_id = response_dict.get('request_id')
|
||||
|
||||
if not status_url:
|
||||
raise Exception("No status_url returned from API")
|
||||
|
||||
print(f"Request ID: {request_id}, Status URL: {status_url}")
|
||||
|
||||
final_response = poll_status_until_completed(status_url, api_key, timeout=3600, check_interval=5)
|
||||
|
||||
result_video_url = final_response['result']['video_url']
|
||||
|
||||
print(f"Video processing completed. Result URL: {result_video_url}")
|
||||
print(f"Solid color background processing complete! Use Preview Video URL node to view the result.")
|
||||
|
||||
return (result_video_url,)
|
||||
else:
|
||||
raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"{e}")
|
||||
finally:
|
||||
if video_path:
|
||||
try:
|
||||
if os.path.exists(video_path):
|
||||
os.unlink(video_path)
|
||||
except:
|
||||
pass
|
||||
@@ -0,0 +1,72 @@
|
||||
import os
|
||||
import requests
|
||||
|
||||
from ..common import BRIA_COMFYUI_USER_AGENT
|
||||
|
||||
|
||||
def upload_video_to_s3(video_path, filename, api_token):
|
||||
api_url = "https://platform.prod.bria-api.com/upload-video/anonymous/presigned-url"
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": BRIA_COMFYUI_USER_AGENT,
|
||||
}
|
||||
extension = os.path.splitext(filename)[1].lower()
|
||||
content_type_map = {
|
||||
'.mp4': 'video/mp4',
|
||||
'.webm': 'video/webm',
|
||||
'.mov': 'video/quicktime',
|
||||
'.mkv': 'video/x-matroska',
|
||||
'.avi': 'video/x-msvideo',
|
||||
'.gif': 'image/gif',
|
||||
'.webp': 'image/webp'
|
||||
}
|
||||
content_type = content_type_map.get(extension, 'video/mp4')
|
||||
if api_token:
|
||||
headers["api_token"] = api_token
|
||||
|
||||
payload = {
|
||||
"file_name": filename,
|
||||
"content_type":content_type
|
||||
}
|
||||
|
||||
print(f"Requesting presigned URL for: {filename}")
|
||||
|
||||
try:
|
||||
response = requests.post(api_url, json=payload, headers=headers)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Failed to get presigned URL: {response.status_code} {response.text}")
|
||||
|
||||
response_data = response.json()
|
||||
video_url = response_data.get("video_url")
|
||||
upload_url = response_data.get("upload_url")
|
||||
|
||||
if not video_url or not upload_url:
|
||||
raise Exception(f"Invalid response from presigned URL API: {response_data}")
|
||||
|
||||
print(f"Received presigned URL")
|
||||
print(f"Video URL: {video_url}")
|
||||
|
||||
# Step 2: Upload video to presigned URL
|
||||
print(f"Uploading video to S3...")
|
||||
|
||||
with open(video_path, 'rb') as f:
|
||||
video_data = f.read()
|
||||
|
||||
# Determine content type based on file extension
|
||||
upload_headers = {
|
||||
"Content-Type": content_type
|
||||
}
|
||||
|
||||
upload_response = requests.put(upload_url, data=video_data, headers=upload_headers)
|
||||
|
||||
if upload_response.status_code not in [200, 204]:
|
||||
raise Exception(f"Failed to upload video to S3: {upload_response.status_code}")
|
||||
|
||||
print(f"Video uploaded successfully to S3")
|
||||
|
||||
return video_url
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"Error uploading video to S3: {str(e)}")
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-bria-api"
|
||||
description = "Custom nodes for ComfyUI using BRIA's API."
|
||||
version = "2.0.3"
|
||||
version = "2.1.16"
|
||||
license = {file = "LICENSE"}
|
||||
|
||||
[project.urls]
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
import { api } from "/scripts/api.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "BriaMultiImageSelect",
|
||||
|
||||
async nodeCreated(node) {
|
||||
if (node.comfyClass !== "BriaMultiImageSelect") return;
|
||||
|
||||
const getWidget = (name) =>
|
||||
node.widgets?.find(w => w.name === name);
|
||||
|
||||
const pathsWidget = getWidget("selected_paths");
|
||||
if (!pathsWidget) return;
|
||||
|
||||
pathsWidget.hidden = true;
|
||||
pathsWidget.draw = () => {};
|
||||
pathsWidget.computeSize = () => [0, 0];
|
||||
|
||||
const viewURLFromRel = (rel) => {
|
||||
const parts = (rel || "").split("/");
|
||||
const filename = parts.pop();
|
||||
const subfolder = parts.join("/");
|
||||
const params = new URLSearchParams({ filename, type: "input", subfolder });
|
||||
return api.apiURL(`/view?${params.toString()}`);
|
||||
};
|
||||
|
||||
const loadImg = (url) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.crossOrigin = "anonymous";
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = () => reject();
|
||||
img.src = url;
|
||||
});
|
||||
|
||||
const refreshPreview = async () => {
|
||||
let raw = node.properties.selected_paths;
|
||||
if (!raw) raw = pathsWidget.value;
|
||||
|
||||
let rels = [];
|
||||
try {
|
||||
rels = raw ? JSON.parse(raw) : [];
|
||||
} catch (e) {
|
||||
console.error("Failed to parse selected_paths:", e);
|
||||
node.imgs = null;
|
||||
node.imgError = "Invalid image data";
|
||||
node.setDirtyCanvas(true, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rels.length) {
|
||||
node.imgs = null;
|
||||
node.imgError = "No images selected";
|
||||
node.setDirtyCanvas(true, true);
|
||||
return;
|
||||
}
|
||||
|
||||
const imgs = [];
|
||||
await Promise.allSettled(
|
||||
rels.map(async (rel) => {
|
||||
try {
|
||||
const img = await loadImg(viewURLFromRel(rel));
|
||||
imgs.push(img);
|
||||
} catch (e) {
|
||||
console.warn(`Failed to load image: ${rel}`, e);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (imgs.length) {
|
||||
node.imgs = imgs;
|
||||
node.imgError = null;
|
||||
} else {
|
||||
node.imgs = null;
|
||||
node.imgError = "Failed to load images";
|
||||
}
|
||||
node.setDirtyCanvas(true, true);
|
||||
};
|
||||
// Update node size to accommodate preview
|
||||
const originalComputeSize = node.computeSize;
|
||||
node.computeSize = function () {
|
||||
const size = originalComputeSize ? originalComputeSize.apply(this, arguments) : [200, 100];
|
||||
size[1] = Math.max(size[1], 200); // Ensure minimum height for preview
|
||||
return size;
|
||||
};
|
||||
|
||||
const btn = node.addWidget("button", "Select Images", null, async () => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.multiple = true;
|
||||
input.accept = "image/*";
|
||||
input.style.display = "none";
|
||||
document.body.appendChild(input);
|
||||
|
||||
input.onchange = async () => {
|
||||
const files = Array.from(input.files || []);
|
||||
document.body.removeChild(input);
|
||||
if (!files.length) return;
|
||||
|
||||
const rels = [];
|
||||
|
||||
for (const f of files) {
|
||||
const form = new FormData();
|
||||
form.append("image", f, f.name);
|
||||
|
||||
const resp = await api.fetchApi("/upload/image", {
|
||||
method: "POST",
|
||||
body: form,
|
||||
});
|
||||
if (!resp.ok) continue;
|
||||
|
||||
const data = await resp.json();
|
||||
const rel = data.subfolder
|
||||
? `${data.subfolder}/${data.name}`
|
||||
: data.name;
|
||||
rels.push(rel);
|
||||
}
|
||||
|
||||
const json = JSON.stringify(rels);
|
||||
pathsWidget.value = json;
|
||||
node.properties.selected_paths = json;
|
||||
|
||||
await refreshPreview();
|
||||
};
|
||||
|
||||
input.click();
|
||||
});
|
||||
|
||||
node.widgets.unshift(
|
||||
node.widgets.splice(node.widgets.indexOf(btn), 1)[0]
|
||||
);
|
||||
|
||||
// Initialize properties if not present
|
||||
if (!node.properties) {
|
||||
node.properties = {};
|
||||
}
|
||||
|
||||
await refreshPreview();
|
||||
|
||||
setTimeout(async () => {
|
||||
await refreshPreview();
|
||||
}, 100);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,331 @@
|
||||
{
|
||||
"id": "3eb93704-25f0-4511-b147-cf0403c5d060",
|
||||
"revision": 0,
|
||||
"last_node_id": 10,
|
||||
"last_link_id": 11,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
756.945556640625,
|
||||
406.1631774902344
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 9
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
702.7532348632812,
|
||||
830.3753662109375
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 5
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "FIBOEditNode",
|
||||
"pos": [
|
||||
390.8927307128906,
|
||||
339.14031982421875
|
||||
],
|
||||
"size": [
|
||||
278.720703125,
|
||||
266
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 8
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"shape": 7,
|
||||
"type": "MASK",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "structured_instruction",
|
||||
"shape": 7,
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "structured_instruction"
|
||||
},
|
||||
"link": 11
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
9
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "structured_instruction",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FIBOEditNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
50,
|
||||
5,
|
||||
1199,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "FIBOEditNode",
|
||||
"pos": [
|
||||
301.3495788574219,
|
||||
884.5100708007812
|
||||
],
|
||||
"size": [
|
||||
278.720703125,
|
||||
266
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 4
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"shape": 7,
|
||||
"type": "MASK",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "structured_instruction",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FIBOEditNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
"change the lamp to a radio",
|
||||
"",
|
||||
"",
|
||||
50,
|
||||
5,
|
||||
55,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
-245.09060668945312,
|
||||
882.640869140625
|
||||
],
|
||||
"size": [
|
||||
274.080078125,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
4,
|
||||
8,
|
||||
10
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"0a9d91e579872d653daf3243df4598f0 (2).png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "FIBOEditStructuredInstructionNode",
|
||||
"pos": [
|
||||
-139.52833557128906,
|
||||
410.5189208984375
|
||||
],
|
||||
"size": [
|
||||
314.6372985839844,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 10
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "structured_instruction",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
11
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FIBOEditStructuredInstructionNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
4,
|
||||
2,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
5,
|
||||
4,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
8,
|
||||
2,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
9,
|
||||
8,
|
||||
0,
|
||||
7,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
10,
|
||||
2,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
11,
|
||||
10,
|
||||
0,
|
||||
8,
|
||||
2,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.7522123482651067,
|
||||
"offset": [
|
||||
684.0553164416729,
|
||||
-258.9658284524182
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.25.11"
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
{
|
||||
"id": "17df2a89-3a7b-4b17-9ed1-fe236151bd3c",
|
||||
"revision": 0,
|
||||
"last_node_id": 30,
|
||||
"last_link_id": 34,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 23,
|
||||
"type": "PreviewVideoURLNode",
|
||||
"pos": [
|
||||
1146.3548583984375,
|
||||
426.4760437011719
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
177.875
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "video_url",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "video_url"
|
||||
},
|
||||
"link": 28
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewVideoURLNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"type": "PreviewVideoURLNode",
|
||||
"pos": [
|
||||
285.0629577636719,
|
||||
889.9742431640625
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
177.875
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "video_url",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "video_url"
|
||||
},
|
||||
"link": 29
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewVideoURLNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"type": "PreviewVideoURLNode",
|
||||
"pos": [
|
||||
560.3937377929688,
|
||||
-124.61785125732422
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
177.875
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "video_url",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "video_url"
|
||||
},
|
||||
"link": 33
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewVideoURLNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"type": "PreviewVideoURLNode",
|
||||
"pos": [
|
||||
484.5365295410156,
|
||||
-391.69635009765625
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
177.875
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "video_url",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "video_url"
|
||||
},
|
||||
"link": 34
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewVideoURLNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "LoadVideoFramesNode",
|
||||
"pos": [
|
||||
-898.2652587890625,
|
||||
20.335519790649414
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
554
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "video_path",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
31,
|
||||
32
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadVideoFramesNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"plane.mp4",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"type": "VideoIncreaseResolutionNode",
|
||||
"pos": [
|
||||
-83.17711639404297,
|
||||
-394.345458984375
|
||||
],
|
||||
"size": [
|
||||
318.79296875,
|
||||
154
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "video_url",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "video_url"
|
||||
},
|
||||
"link": 32
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "result_video_url",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
34
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VideoIncreaseResolutionNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"2",
|
||||
"mp4_h264",
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"type": "RemoveVideoBackgroundNode",
|
||||
"pos": [
|
||||
22.839176177978516,
|
||||
-98.48242950439453
|
||||
],
|
||||
"size": [
|
||||
318.79296875,
|
||||
130
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "video_url",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "video_url"
|
||||
},
|
||||
"link": 31
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "result_video_url",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
33
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "RemoveVideoBackgroundNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
true,
|
||||
"webm_vp9"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "VideoMaskByPromptNode",
|
||||
"pos": [
|
||||
53.91518783569336,
|
||||
377.6769714355469
|
||||
],
|
||||
"size": [
|
||||
318.79296875,
|
||||
154
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "video_url",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "video_url"
|
||||
},
|
||||
"link": 25
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "mask_url",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
27
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VideoMaskByPromptNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"airplane",
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"mp4_h264",
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "VideoEraseElementsNode",
|
||||
"pos": [
|
||||
658.7935180664062,
|
||||
377.9380187988281
|
||||
],
|
||||
"size": [
|
||||
318.79296875,
|
||||
154
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "video_url",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "video_url"
|
||||
},
|
||||
"link": 26
|
||||
},
|
||||
{
|
||||
"name": "mask_url",
|
||||
"shape": 7,
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "mask_url"
|
||||
},
|
||||
"link": 27
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "result_video_url",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
28
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VideoEraseElementsNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"",
|
||||
"mp4_h264",
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "VideoSolidColorBackgroundNode",
|
||||
"pos": [
|
||||
-133.50149536132812,
|
||||
856.9811401367188
|
||||
],
|
||||
"size": [
|
||||
318.79296875,
|
||||
154
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "video_url",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "video_url"
|
||||
},
|
||||
"link": 24
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "result_video_url",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
29
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VideoSolidColorBackgroundNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"Transparent",
|
||||
"webm_vp9",
|
||||
true
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
24,
|
||||
19,
|
||||
0,
|
||||
20,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
25,
|
||||
19,
|
||||
0,
|
||||
21,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
26,
|
||||
19,
|
||||
0,
|
||||
22,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
27,
|
||||
21,
|
||||
0,
|
||||
22,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
28,
|
||||
22,
|
||||
0,
|
||||
23,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
29,
|
||||
20,
|
||||
0,
|
||||
24,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
31,
|
||||
19,
|
||||
0,
|
||||
26,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
32,
|
||||
19,
|
||||
0,
|
||||
27,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
33,
|
||||
26,
|
||||
0,
|
||||
28,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
34,
|
||||
27,
|
||||
0,
|
||||
29,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.5644739300537782,
|
||||
"offset": [
|
||||
1311.5448975965508,
|
||||
244.532748842859
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.25.11"
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -1 +1,491 @@
|
||||
{"last_node_id":39,"last_link_id":65,"nodes":[{"id":34,"type":"LoadImage","pos":[19.94045066833496,1075.806640625],"size":[315,314],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[61],"slot_index":0,"localized_name":"IMAGE"},{"name":"MASK","type":"MASK","links":null,"localized_name":"MASK"}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["quirky-red-brick-brick-wallpaper.jpg","image"]},{"id":36,"type":"PreviewImage","pos":[468.7108154296875,1210.288818359375],"size":[210,246],"flags":{},"order":5,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":63,"localized_name":"images"}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"},"widgets_values":[]},{"id":33,"type":"PreviewImage","pos":[1317.4083251953125,1118.4864501953125],"size":[210,246],"flags":{},"order":8,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":59,"localized_name":"images"}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"},"widgets_values":[]},{"id":31,"type":"PreviewImage","pos":[1668.734619140625,796.7755737304688],"size":[210,246],"flags":{},"order":10,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":57,"localized_name":"images"}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"},"widgets_values":[]},{"id":29,"type":"PreviewImage","pos":[872.8858032226562,679.9429321289062],"size":[210,246],"flags":{},"order":6,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":55,"localized_name":"images"}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"},"widgets_values":[]},{"id":28,"type":"LoadImage","pos":[29.629886627197266,676.8370361328125],"size":[315,314],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[54],"slot_index":0,"localized_name":"IMAGE"},{"name":"MASK","type":"MASK","links":null,"localized_name":"MASK"}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["pexels-photo-1808399.jpeg","image"]},{"id":35,"type":"RemoveForegroundNode","pos":[414.2638854980469,1079.1705322265625],"size":[315,58],"flags":{},"order":3,"mode":0,"inputs":[{"name":"image","type":"IMAGE","link":61,"localized_name":"image"}],"outputs":[{"name":"output_image","type":"IMAGE","links":[62,63],"slot_index":0,"localized_name":"output_image"}],"properties":{"Node name for S&R":"RemoveForegroundNode"},"widgets_values":["BRIA_API_TOKEN"]},{"id":32,"type":"ReplaceBgNode","pos":[834.6115112304688,1063.0406494140625],"size":[315,294],"flags":{},"order":7,"mode":0,"inputs":[{"name":"image","type":"IMAGE","link":65,"localized_name":"image"},{"name":"ref_image","type":"IMAGE","link":62,"shape":7,"localized_name":"ref_image"}],"outputs":[{"name":"output_image","type":"IMAGE","links":[59,64],"slot_index":0,"localized_name":"output_image"}],"properties":{"Node name for S&R":"ReplaceBgNode"},"widgets_values":["BRIA_API_TOKEN",false,"",true,true,true,false,"",1978,"randomize"]},{"id":30,"type":"ImageExpansionNode","pos":[1265.001220703125,798.8323974609375],"size":[315,226],"flags":{},"order":9,"mode":0,"inputs":[{"name":"image","type":"IMAGE","link":64,"localized_name":"image"}],"outputs":[{"name":"output_image","type":"IMAGE","links":[57],"slot_index":0,"localized_name":"output_image"}],"properties":{"Node name for S&R":"ImageExpansionNode"},"widgets_values":["600,760","200, 0","BRIA_API_TOKEN","1200, 800","",1729,"randomize","Ugly, mutated"]},{"id":38,"type":"Note","pos":[436.7110900878906,566.2047119140625],"size":[306.28387451171875,58],"flags":{},"order":2,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["You can get your BRIA API token at:\nhttps://bria.ai/api/"],"color":"#432","bgcolor":"#653"},{"id":27,"type":"RmbgNode","pos":[430.8815002441406,678.7727661132812],"size":[315,58],"flags":{},"order":4,"mode":0,"inputs":[{"name":"image","type":"IMAGE","link":54,"localized_name":"image"}],"outputs":[{"name":"output_image","type":"IMAGE","links":[55,65],"slot_index":0,"localized_name":"output_image"}],"properties":{"Node name for S&R":"RmbgNode"},"widgets_values":["BRIA_API_TOKEN"]}],"links":[[51,5,0,15,2,"STRING"],[54,28,0,27,0,"IMAGE"],[55,27,0,29,0,"IMAGE"],[57,30,0,31,0,"IMAGE"],[59,32,0,33,0,"IMAGE"],[61,34,0,35,0,"IMAGE"],[62,35,0,32,1,"IMAGE"],[63,35,0,36,0,"IMAGE"],[64,32,0,30,0,"IMAGE"],[65,27,0,32,0,"IMAGE"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.8140274938684037,"offset":[101.53311990208498,-477.0342684311694]},"node_versions":{"comfyui-bria-api":"c72754d15b53a13ee0c0419d70401232c56b7fdb","comfy-core":"v0.3.8-1-gc441048","ComfyUI-Jjk-Nodes":"b3c99bb78a99551776b5eab1a820e1cd58f84f31"}},"version":0.4}
|
||||
{
|
||||
"id": "f47c8ffe-20e8-473c-a56b-c6241da2c469",
|
||||
"revision": 0,
|
||||
"last_node_id": 43,
|
||||
"last_link_id": 78,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 31,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1668.734619140625,
|
||||
796.7755737304688
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 78
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
872.8858032226562,
|
||||
679.9429321289062
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 66
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
436.7110900878906,
|
||||
566.2047119140625
|
||||
],
|
||||
"size": [
|
||||
306.28387451171875,
|
||||
88
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"You can get your BRIA API token at:\nhttps://bria.ai/api/"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
29.629886627197266,
|
||||
676.8370361328125
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
68
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"pexels-photo-1808399.jpeg",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
19.94045066833496,
|
||||
1075.806640625
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
69
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"quirky-red-brick-brick-wallpaper.jpg",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
455.6301574707031,
|
||||
1240.017578125
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 71
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"type": "RmbgNode",
|
||||
"pos": [
|
||||
414.095703125,
|
||||
749.6687622070312
|
||||
],
|
||||
"size": [
|
||||
347.462890625,
|
||||
130
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 68
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "output_images",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
66,
|
||||
74
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "RmbgNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 41,
|
||||
"type": "RemoveForegroundNode",
|
||||
"pos": [
|
||||
397.44732666015625,
|
||||
1127.818359375
|
||||
],
|
||||
"size": [
|
||||
347.462890625,
|
||||
130
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 69
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "output_images",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
71,
|
||||
75
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "RemoveForegroundNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 42,
|
||||
"type": "ReplaceBgNode",
|
||||
"pos": [
|
||||
800.56884765625,
|
||||
1050.5228271484375
|
||||
],
|
||||
"size": [
|
||||
347.462890625,
|
||||
318
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 74
|
||||
},
|
||||
{
|
||||
"name": "ref_images",
|
||||
"shape": 7,
|
||||
"type": "IMAGE",
|
||||
"link": 75
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "output_images",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
72,
|
||||
77
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ReplaceBgNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"base",
|
||||
"",
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
"",
|
||||
"681794",
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"type": "ImageExpansionNode",
|
||||
"pos": [
|
||||
1241.743896484375,
|
||||
731.8318481445312
|
||||
],
|
||||
"size": [
|
||||
347.462890625,
|
||||
322
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 77
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "output_images",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
78
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ImageExpansionNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"",
|
||||
"1000, 1000",
|
||||
"None",
|
||||
"",
|
||||
"681794",
|
||||
"Ugly, mutated",
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1316.2191162109375,
|
||||
1169.61962890625
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 72
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
66,
|
||||
40,
|
||||
0,
|
||||
29,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
68,
|
||||
28,
|
||||
0,
|
||||
40,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
69,
|
||||
34,
|
||||
0,
|
||||
41,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
71,
|
||||
41,
|
||||
0,
|
||||
36,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
72,
|
||||
42,
|
||||
0,
|
||||
33,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
74,
|
||||
40,
|
||||
0,
|
||||
42,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
75,
|
||||
41,
|
||||
0,
|
||||
42,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
77,
|
||||
42,
|
||||
0,
|
||||
43,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
78,
|
||||
43,
|
||||
0,
|
||||
31,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.61159090448415,
|
||||
"offset": [
|
||||
138.07082928418134,
|
||||
-488.9478800208311
|
||||
]
|
||||
},
|
||||
"node_versions": {
|
||||
"comfyui-bria-api": "c72754d15b53a13ee0c0419d70401232c56b7fdb",
|
||||
"comfy-core": "v0.3.8-1-gc441048",
|
||||
"ComfyUI-Jjk-Nodes": "b3c99bb78a99551776b5eab1a820e1cd58f84f31"
|
||||
},
|
||||
"frontendVersion": "1.25.11"
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,661 @@
|
||||
{
|
||||
"id": "1c31a92d-1e48-46b0-b4ad-60d537260922",
|
||||
"revision": 0,
|
||||
"last_node_id": 27,
|
||||
"last_link_id": 46,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 5,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1235.1895751953125,
|
||||
10.958272933959961
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 43
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
774.4347534179688,
|
||||
-58.6872444152832
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 29
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
-175.84976196289062,
|
||||
102.76270294189453
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
88
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"If you would like to start with prompt"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
-225.1866455078125,
|
||||
624.7235107421875
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
88
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"If you would like to start with reference image + prompt"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
719.5359497070312,
|
||||
443.11285400390625
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 36
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1132.783935546875,
|
||||
517.3931884765625
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 44
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "GenerateStructuredPromptLiteNodeV2",
|
||||
"pos": [
|
||||
68.9760971069336,
|
||||
82.11421966552734
|
||||
],
|
||||
"size": [
|
||||
310.7749938964844,
|
||||
174
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"shape": 7,
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
27
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
28
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "GenerateStructuredPromptLiteNodeV2"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"",
|
||||
123456,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"type": "GenerateImageLiteNodeV2",
|
||||
"pos": [
|
||||
460.6510314941406,
|
||||
44.938873291015625
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
290
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"shape": 7,
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"shape": 7,
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "structured_prompt"
|
||||
},
|
||||
"link": 27
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"shape": 7,
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
},
|
||||
"link": 28
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
29
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
41
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
42
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "GenerateImageLiteNodeV2"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"FIBO",
|
||||
"",
|
||||
"1:1",
|
||||
8,
|
||||
5,
|
||||
123456,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"type": "RefineImageLiteNodeV2",
|
||||
"pos": [
|
||||
834.9641723632812,
|
||||
46.47432327270508
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
290
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "structured_prompt"
|
||||
},
|
||||
"link": 41
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"shape": 7,
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
},
|
||||
"link": 42
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
43
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "RefineImageLiteNodeV2"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"",
|
||||
"FIBO",
|
||||
"1:1",
|
||||
8,
|
||||
5,
|
||||
123456,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"type": "GenerateImageLiteNodeV2",
|
||||
"pos": [
|
||||
404.58843994140625,
|
||||
532.5309448242188
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
290
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"shape": 7,
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"shape": 7,
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "structured_prompt"
|
||||
},
|
||||
"link": 34
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"shape": 7,
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
},
|
||||
"link": 35
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
36
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
45
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
46
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "GenerateImageLiteNodeV2"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"FIBO",
|
||||
"",
|
||||
"1:1",
|
||||
8,
|
||||
5,
|
||||
123456,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"type": "RefineImageLiteNodeV2",
|
||||
"pos": [
|
||||
820.26611328125,
|
||||
558.9785766601562
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
290
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "structured_prompt"
|
||||
},
|
||||
"link": 45
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"shape": 7,
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
},
|
||||
"link": 46
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
44
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "RefineImageLiteNodeV2"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"",
|
||||
"FIBO",
|
||||
"1:1",
|
||||
8,
|
||||
5,
|
||||
123456,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "GenerateStructuredPromptLiteNodeV2",
|
||||
"pos": [
|
||||
3.162916421890259,
|
||||
587.301025390625
|
||||
],
|
||||
"size": [
|
||||
310.7749938964844,
|
||||
174
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"shape": 7,
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
34
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
35
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "GenerateStructuredPromptLiteNodeV2"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"",
|
||||
123456,
|
||||
"randomize"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
27,
|
||||
20,
|
||||
0,
|
||||
24,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
28,
|
||||
20,
|
||||
1,
|
||||
24,
|
||||
2,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
29,
|
||||
24,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
34,
|
||||
21,
|
||||
0,
|
||||
25,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
35,
|
||||
21,
|
||||
1,
|
||||
25,
|
||||
2,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
36,
|
||||
25,
|
||||
0,
|
||||
15,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
41,
|
||||
24,
|
||||
1,
|
||||
26,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
42,
|
||||
24,
|
||||
2,
|
||||
26,
|
||||
1,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
43,
|
||||
26,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
44,
|
||||
27,
|
||||
0,
|
||||
17,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
45,
|
||||
25,
|
||||
1,
|
||||
27,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
46,
|
||||
25,
|
||||
2,
|
||||
27,
|
||||
1,
|
||||
"INT"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.6276708501927047,
|
||||
"offset": [
|
||||
650.5506889681789,
|
||||
132.55696596915172
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.25.11"
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,696 @@
|
||||
{
|
||||
"id": "3875cd62-7a0e-4c8c-8951-22ec8a63dd8d",
|
||||
"revision": 0,
|
||||
"last_node_id": 18,
|
||||
"last_link_id": 19,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 4,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1284.572509765625,
|
||||
229.44073486328125
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 16
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.62"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
809.5054321289062,
|
||||
38.50392150878906
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.62"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
-143.02835083007812,
|
||||
277.0796203613281
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
88
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"If you would like to start with prompt"
|
||||
],
|
||||
"color": "#c09430",
|
||||
"bgcolor": "rgba(24,24,27,.9)"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "GenerateStructuredPromptNodeV2",
|
||||
"pos": [
|
||||
110.83424377441406,
|
||||
260.3714294433594
|
||||
],
|
||||
"size": [
|
||||
275.7613220214844,
|
||||
174
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"shape": 7,
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
9
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
10
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "GenerateStructuredPromptNodeV2"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"",
|
||||
123456,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1261.5439453125,
|
||||
996.5408935546875
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 19
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.62"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
751.5670166015625,
|
||||
845.8276977539062
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.62"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
-135.44485473632812,
|
||||
1056.937744140625
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
95.76702880859375
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"If you would like to start with reference image + prompt"
|
||||
],
|
||||
"color": "#c09430",
|
||||
"bgcolor": "rgba(24,24,27,.9)"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "GenerateStructuredPromptNodeV2",
|
||||
"pos": [
|
||||
117.20681762695312,
|
||||
1049.3199462890625
|
||||
],
|
||||
"size": [
|
||||
275.7613220214844,
|
||||
174
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"shape": 7,
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
11
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
12
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "GenerateStructuredPromptNodeV2"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"",
|
||||
123456,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "GenerateImageNodeV2",
|
||||
"pos": [
|
||||
482.9381103515625,
|
||||
222.8486785888672
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
314
|
||||
],
|
||||
"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": 9
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"shape": 7,
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
},
|
||||
"link": 10
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
13
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
15
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "GenerateImageNodeV2",
|
||||
"cnr_id": "comfyui-bria-api",
|
||||
"ver": "2.1.4",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {
|
||||
"api_token": true,
|
||||
"prompt": true,
|
||||
"model_version": true,
|
||||
"negative_prompt": true,
|
||||
"aspect_ratio": true,
|
||||
"steps_num": true,
|
||||
"guidance_scale": true,
|
||||
"seed": true
|
||||
},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"FIBO",
|
||||
"",
|
||||
"1:1",
|
||||
50,
|
||||
5,
|
||||
123456,
|
||||
"randomize",
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"type": "RefineImageNodeV2",
|
||||
"pos": [
|
||||
876.5194091796875,
|
||||
234.5377655029297
|
||||
],
|
||||
"size": [
|
||||
287.4712829589844,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "structured_prompt"
|
||||
},
|
||||
"link": 13
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"shape": 7,
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
},
|
||||
"link": 15
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
16
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "RefineImageNodeV2"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"",
|
||||
"FIBO",
|
||||
"1:1",
|
||||
50,
|
||||
5,
|
||||
123456,
|
||||
"randomize",
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "GenerateImageNodeV2",
|
||||
"pos": [
|
||||
482.86688232421875,
|
||||
1034.027099609375
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
314
|
||||
],
|
||||
"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": 11
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"shape": 7,
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
},
|
||||
"link": 12
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
8
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
17
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
18
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "GenerateImageNodeV2",
|
||||
"cnr_id": "comfyui-bria-api",
|
||||
"ver": "2.1.5"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"FIBO",
|
||||
"",
|
||||
"1:1",
|
||||
50,
|
||||
5,
|
||||
123456,
|
||||
"randomize",
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"type": "RefineImageNodeV2",
|
||||
"pos": [
|
||||
915.1214599609375,
|
||||
1000.7938842773438
|
||||
],
|
||||
"size": [
|
||||
287.4712829589844,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "structured_prompt"
|
||||
},
|
||||
"link": 17
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"shape": 7,
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
},
|
||||
"link": 18
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
19
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "structured_prompt",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "RefineImageNodeV2"
|
||||
},
|
||||
"widgets_values": [
|
||||
"BRIA_API_TOKEN",
|
||||
"",
|
||||
"",
|
||||
"FIBO",
|
||||
"1:1",
|
||||
50,
|
||||
5,
|
||||
123456,
|
||||
"randomize",
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
8,
|
||||
7,
|
||||
0,
|
||||
14,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
9,
|
||||
16,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
10,
|
||||
16,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
11,
|
||||
15,
|
||||
0,
|
||||
7,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
12,
|
||||
15,
|
||||
1,
|
||||
7,
|
||||
2,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
13,
|
||||
1,
|
||||
1,
|
||||
17,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
15,
|
||||
1,
|
||||
2,
|
||||
17,
|
||||
1,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
16,
|
||||
17,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
17,
|
||||
7,
|
||||
1,
|
||||
18,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
18,
|
||||
7,
|
||||
2,
|
||||
18,
|
||||
1,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
19,
|
||||
18,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.6904379352119744,
|
||||
"offset": [
|
||||
213.80311902834225,
|
||||
-667.3066536880174
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.25.11",
|
||||
"ue_links": [],
|
||||
"VHS_latentpreview": false,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
+144
-136
@@ -1,18 +1,47 @@
|
||||
{
|
||||
"last_node_id": 42,
|
||||
"last_link_id": 65,
|
||||
"id": "1cdd7d4c-58b5-4047-947b-1977ad36d364",
|
||||
"revision": 0,
|
||||
"last_node_id": 14,
|
||||
"last_link_id": 11,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 42,
|
||||
"id": 6,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1351.83154296875,
|
||||
26.696861267089844
|
||||
],
|
||||
"size": [
|
||||
399.811279296875,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 5
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "LoadImage",
|
||||
"pos": {
|
||||
"0": 591,
|
||||
"1": 593
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 314
|
||||
},
|
||||
"pos": [
|
||||
383.92852783203125,
|
||||
38.40964889526367
|
||||
],
|
||||
"size": [
|
||||
397.5969543457031,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
@@ -22,10 +51,9 @@
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
64,
|
||||
65
|
||||
],
|
||||
"slot_index": 0
|
||||
1,
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
@@ -37,21 +65,21 @@
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"A_bottle_of_perfume.png",
|
||||
"CAR.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"id": 2,
|
||||
"type": "LoadImage",
|
||||
"pos": {
|
||||
"0": 600,
|
||||
"1": 988
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 314
|
||||
},
|
||||
"pos": [
|
||||
369.0213623046875,
|
||||
415.34893798828125
|
||||
],
|
||||
"size": [
|
||||
450.83685302734375,
|
||||
314.0000305175781
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
@@ -61,9 +89,8 @@
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
59
|
||||
],
|
||||
"slot_index": 0
|
||||
4
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
@@ -75,21 +102,21 @@
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"A_red_studio_with_a_shelf__close_up.png",
|
||||
"seed_713360865.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"id": 5,
|
||||
"type": "Note",
|
||||
"pos": {
|
||||
"0": 995.4524536132812,
|
||||
"1": 601.5353393554688
|
||||
},
|
||||
"size": {
|
||||
"0": 306.28387451171875,
|
||||
"1": 58
|
||||
},
|
||||
"pos": [
|
||||
951.4669189453125,
|
||||
5.085720062255859
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
88
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
@@ -103,40 +130,14 @@
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"id": 7,
|
||||
"type": "PreviewImage",
|
||||
"pos": {
|
||||
"0": 1408,
|
||||
"1": 623
|
||||
},
|
||||
"size": [
|
||||
210,
|
||||
246
|
||||
"pos": [
|
||||
1368.5450439453125,
|
||||
340.4889221191406
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 62
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 41,
|
||||
"type": "PreviewImage",
|
||||
"pos": {
|
||||
"0": 1408,
|
||||
"1": 951
|
||||
},
|
||||
"size": [
|
||||
210,
|
||||
387.0335693359375,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
@@ -146,25 +147,26 @@
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 63
|
||||
"link": 6
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"type": "ShotByTextNode",
|
||||
"pos": {
|
||||
"0": 996,
|
||||
"1": 736
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"id": 3,
|
||||
"type": "ShotByTextOriginal",
|
||||
"pos": [
|
||||
941.8839111328125,
|
||||
149.14889526367188
|
||||
],
|
||||
"size": [
|
||||
273.388671875,
|
||||
202
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
@@ -172,7 +174,7 @@
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 64
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
@@ -180,31 +182,34 @@
|
||||
"name": "output_image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
62
|
||||
],
|
||||
"slot_index": 0
|
||||
5
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShotByTextNode"
|
||||
"Node name for S&R": "ShotByTextOriginal"
|
||||
},
|
||||
"widgets_values": [
|
||||
"a beautiful sunset",
|
||||
1,
|
||||
"BRIA_API_TOKEN",
|
||||
"sea",
|
||||
"fast",
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 37,
|
||||
"type": "ShotByImageNode",
|
||||
"pos": {
|
||||
"0": 999,
|
||||
"1": 932
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 102
|
||||
},
|
||||
"id": 4,
|
||||
"type": "ShotByImageOriginal",
|
||||
"pos": [
|
||||
945.0781860351562,
|
||||
441.9688415527344
|
||||
],
|
||||
"size": [
|
||||
272.0703125,
|
||||
174
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
@@ -212,12 +217,12 @@
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 65
|
||||
"link": 3
|
||||
},
|
||||
{
|
||||
"name": "ref_image",
|
||||
"type": "IMAGE",
|
||||
"link": 59
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
@@ -225,58 +230,60 @@
|
||||
"name": "output_image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
63
|
||||
],
|
||||
"slot_index": 0
|
||||
6
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShotByImageNode"
|
||||
"Node name for S&R": "ShotByImageOriginal"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
""
|
||||
"BRIA_API_TOKEN",
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
59,
|
||||
39,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
37,
|
||||
3,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
3,
|
||||
1,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
4,
|
||||
2,
|
||||
0,
|
||||
4,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
62,
|
||||
36,
|
||||
5,
|
||||
3,
|
||||
0,
|
||||
40,
|
||||
6,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
63,
|
||||
37,
|
||||
6,
|
||||
4,
|
||||
0,
|
||||
41,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
64,
|
||||
42,
|
||||
0,
|
||||
36,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
65,
|
||||
42,
|
||||
0,
|
||||
37,
|
||||
7,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
@@ -285,12 +292,13 @@
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.9849732675807669,
|
||||
"scale": 0.7513148009015777,
|
||||
"offset": [
|
||||
-339.6686422794803,
|
||||
-496.4354678014682
|
||||
11.112206386364164,
|
||||
66.47311795454547
|
||||
]
|
||||
}
|
||||
},
|
||||
"frontendVersion": "1.25.11"
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user