Skip to content
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,15 @@ That's it — you're ready to use the tools.
| Erase | Remove objects with inpainting |
| Expand | Extend image canvas with AI generation |
| FIBO Edit | Prompt-driven image editing |
| FIBO Edit Recipes | FIBO Edit with 99 curated preset prompts across 11 categories |
| FIBO Generate | Text-to-image generation |
| FIBO Edit Recipes | FIBO Edit with 100+ curated preset prompts across 11 categories — including Create Mask, Create Soft Mask, and Remove Greenscreen Spill compositing presets |
| FIBO Generate | Text-to-image generation with multi-variant batch (1–4 in parallel) |
| Generate VGL | Structured-prompt JSON generator (chains into FIBO nodes) |
| Sequence Output | Batch render a chain of upstream Bria nodes across a frame range |

All tools are accessible from the **Bria** menu or by pressing Tab and typing "bria".

Every Bria node also includes **View Generation Data** and **Copy Generation Data** buttons (Settings tab) for inspecting or copying the most recent run's prompt, seed, response, and full sidecar JSON.

## Shared configuration

Bria tools store configuration in `~/.bria/bria.json`. This file is shared across
Expand All @@ -76,10 +78,10 @@ Dev launcher scripts are also provided in `tools/dev/`.

## Documentation

- Detailed setup and node reference: [docs/bria_nuke/README.md](docs/bria_nuke/README.md)
- Configuration reference: [docs/CONFIG.md](docs/CONFIG.md)
- Repo structure: [docs/contributing/STRUCTURE.md](docs/contributing/STRUCTURE.md)
- Troubleshooting: [docs/bria_nuke/TROUBLESHOOTING.md](docs/bria_nuke/TROUBLESHOOTING.md)
- Detailed setup and node reference: [documentation/bria_nuke/README.md](documentation/bria_nuke/README.md)
- Configuration reference: [documentation/CONFIG.md](documentation/CONFIG.md)
- Repo structure: [STRUCTURE.md](STRUCTURE.md)
- Troubleshooting: [documentation/bria_nuke/TROUBLESHOOTING.md](documentation/bria_nuke/TROUBLESHOOTING.md)

## License

Expand Down
71 changes: 52 additions & 19 deletions bria_core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,28 +116,61 @@ def resolve_proxies(


def extract_image_url(data: dict) -> str | None:
urls = extract_image_urls(data)
return urls[0] if urls else None


def extract_image_urls(data: dict) -> list[str]:
"""Return every image URL the API response contains, in order.

Handles a few shapes Bria's endpoints have used:
- {"result": {"image_url": "..."}} (single, FIBO Generate sync)
- {"result": {"images": ["url1", "url2"]}} (multi as list of strings)
- {"result": {"images": [{"image_url": "..."}, ...]}} (multi as list of dicts)
- {"result": [{"image_url": "..."}, ...]} (result as list of dicts)
- {"image_url": "..."} (top-level)
- {"images": [...]} (top-level array)
"""
if not isinstance(data, dict):
return None
return []

seen: set[str] = set()
out: list[str] = []

def _add(value: object) -> None:
if isinstance(value, str):
v = value.strip()
if v and v not in seen:
seen.add(v)
out.append(v)

def _walk_image_dict(obj: object) -> None:
if isinstance(obj, dict):
for key in ("image_url", "url", "imageUrl"):
_add(obj.get(key))

def _walk_image_list(items: object) -> None:
if isinstance(items, list):
for item in items:
if isinstance(item, str):
_add(item)
elif isinstance(item, dict):
_walk_image_dict(item)

result = data.get("result")
if isinstance(result, dict):
url = result.get("image_url")
if isinstance(url, str) and url.strip():
return url.strip()

images = result.get("images")
if isinstance(images, list) and images:
first = images[0]
if isinstance(first, str) and first.strip():
return first.strip()
if isinstance(first, dict):
first_url = first.get("image_url")
if isinstance(first_url, str) and first_url.strip():
return first_url.strip()

url_top = data.get("image_url")
if isinstance(url_top, str) and url_top.strip():
return url_top.strip()
return None
_walk_image_dict(result)
_walk_image_list(result.get("images"))
_walk_image_list(result.get("results"))
elif isinstance(result, list):
_walk_image_list(result)

# Top-level fallbacks
_walk_image_dict(data)
_walk_image_list(data.get("images"))
_walk_image_list(data.get("results"))

return out


def png_ihdr_info(path: str) -> dict | None:
Expand Down
23 changes: 19 additions & 4 deletions docs/bria_nuke/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ Current node set (11):
Enhance defaults and controls:

- `preserve_alpha` defaults to **on**
- `sync` is hidden on current new nodes and forced to `true` at runtime because async polling is not implemented for Enhance yet
- `sync` is hidden on current new nodes and forced to `false` at runtime — Bria's `/v2/image/edit/enhance` endpoint stopped accepting `sync=true` (returns 422) in May 2026, and `BriaClient._poll_status` handles the async response transparently
- `seed` is optional
- `Use Mask` toggles region-limited internal composite (`Keymix`) between original and enhanced result
- mask inputs without alpha are normalized internally (mask red -> alpha) before compositing
Expand Down Expand Up @@ -153,6 +153,13 @@ FIBO Generate UX highlights:
- includes `status` field (`idle` / `processing` / `done` / `error`)
- includes `Open Bria Dashboard` button
- `Before` / `After` compares previous generated result against latest generated result
- **Multi-variant batch (Generate Multiple Images)**:
- `Generate Multiple Images` toggle reveals a `Num Images` dropdown (2, 3, or 4), an `Image Index` slider, and a `Use This Image` button
- toggle ON → on Run, the node fires N parallel async calls to `/v2/image/generate` (the endpoint returns one image per call, so true batching is parallel-call style; per-variant seed offset is applied when a base seed is set so variants are actually different)
- saved files are suffixed `_v01.png` … `_vNN.png` next to the canonical `result_path`; the canonical path points at variant 1 by default
- after a multi-variant run, drag the `Image Index` slider to switch the internal Read between variants without re-running
- `Use This Image` promotes the currently-displayed variant to be the canonical `result_path` so downstream nodes (Sequence Output, etc.) consume the locked-in variant
- sidecar JSON records every variant in `result_paths`, with `result_path` / `image_path` still pointing at the first for backward compatibility

FIBO Edit UX highlights:

Expand Down Expand Up @@ -197,10 +204,18 @@ Generate VGL node history:

FIBO Edit Recipes UX highlights:

- categorized preset launcher built on FIBO Edit's execution path; ships with 99 prompts across 11 categories (Style, Weather, Seasons, Time of Day, Camera, Lighting, Compositing, Cleanup, AI Corrections, Object Edits, plus Custom for user prompts)
- categorized preset launcher built on FIBO Edit's execution path; ships with 100+ prompts across 11 categories (Style, Weather, Seasons, Time of Day, Camera, Lighting, Compositing, Cleanup, AI Corrections, Object Edits, plus Custom for user prompts)
- pick a category → the matching Preset dropdown appears with that category's options; selecting a preset auto-fills the prompt field
- **Object-targeted presets** (5 of them: delete, replace, isolate, change color, change material) reveal 5 fixed Object slots — fill in the names of objects in your image and the prompt is rebuilt with constraint-injection ("STRICT LOCKS") wording so the model only edits what you specify
- **Compositing-targeted presets** (7 of them: harmonize lighting, match shadows, blend edges, color harmonize, fix reflections, integrate elements, match ambient) reveal 5 fixed Element slots — describe which composited elements need attention
- **Object-targeted presets** (delete, replace, isolate, change color, change material) reveal 5 fixed Object slots — fill in the names of objects in your image and the prompt is rebuilt with constraint-injection ("STRICT LOCKS") wording so the model only edits what you specify. Isolate Object and Delete Object work with object name only (no modifier required); other presets fall back to the generic preset when the modifier is missing.
- **Compositing-targeted presets** (harmonize lighting, match shadows, blend edges, color harmonize, fix reflections, integrate elements, match ambient, **Create Mask**, **Create Soft Mask**, **Remove Greenscreen Spill**) reveal 5 fixed Element slots — describe which composited elements need attention
- **Create Mask** outputs a clean binary alpha matte (white over named element(s), black elsewhere) suitable as a luminance key
- **Create Soft Mask** outputs a grayscale density matte for atmospherics — smoke, fire, dust, fog, mist — where solid objects in the same frame remain pure black
- **Remove Greenscreen Spill** removes green/blue-screen color contamination, fringing, and edge spill from named element(s) while preserving naturally-occurring greens (foliage, signage, wardrobe). Designed to run after the keyed subject is composited over its new background.
- **Premultiply with Mask toggle + Mask Edge Softness slider** appear automatically when Create Mask or Create Soft Mask is the active preset
- toggle ON → routes the API-returned mask through an internal `Blur → FilterErode → Copy(rgb=input, a=mask.red) → Premult` chain, producing a clean cutout element with anti-aliased edges
- toggle OFF → output is the raw API mask (the existing behavior)
- Mask Edge Softness drives the Blur size and a half-size FilterErode that compensates for blur expansion, so the alpha stays tight to the named element
- both controls are hidden for any other preset and auto-clear when the user switches preset/category, so subsequent runs do not unexpectedly produce cutouts
- auto-vs-manual prompt tracking: if you edit the prompt manually after a preset is loaded, switching presets won't clobber your edits
- delegates the actual API call to the existing FIBO Edit pipeline — same history, presets, metadata sidecar, and viewer behavior

Expand Down
6 changes: 5 additions & 1 deletion nuke/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,9 +468,13 @@ def upscale_from_files(
session=session,
)

# As of 2026-05, /v2/image/edit/enhance returns 422 for sync=true ("sync request is not
# supported with this endpoint"). BriaClient._poll_status handles async responses, so
# default Enhance to sync=False. Other modes (increase_resolution) retain the prior default.
default_sync = False if endpoint_name == "enhance" else True
payload: Dict[str, object] = {
"image": file_to_base64(image_path),
"sync": True if sync is None else bool(sync),
"sync": default_sync if sync is None else bool(sync),
}

if seed is not None:
Expand Down
Loading
Loading