Initial commit: Ideogram Magic Prompt node for ComfyUI V3
- Implements the V3 (Modern Node Design) schema via io.ComfyNode. - Falls back to V1 (NODE_CLASS_MAPPINGS) for older ComfyUI installs. - Calls Ideogram's hosted magic-prompt API via urllib (no extra deps). - Strips bbox / aspect_ratio echoes so the caption is a clean drop-in for Ideogram4Pipeline.
This commit is contained in:
+42
@@ -0,0 +1,42 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Alexandr Radowskiy (@hardhant)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,119 @@
|
||||
# ComfyUI Ideogram Magic Prompt
|
||||
|
||||
[](LICENSE)
|
||||
[](https://www.python.org)
|
||||
[](https://docs.comfy.org/)
|
||||
|
||||
A ComfyUI custom node that calls Ideogram's hosted **magic-prompt API** to
|
||||
turn a plain-text prompt into the structured JSON caption that Ideogram 4
|
||||
expects.
|
||||
|
||||
It uses Ideogram's free hosted service — no model download, no GPU usage.
|
||||
Typical round-trip: 2-5 seconds.
|
||||
|
||||
## Preview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ prompt: "a ginger cat wearing a │
|
||||
│ tiny wizard hat reading a │
|
||||
│ spellbook" │
|
||||
│ api_key: •••••• │
|
||||
│ aspect_ratio: [1x1 ▾] │
|
||||
└──────────────┬──────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ json_caption: │
|
||||
│ { │
|
||||
│ "high_level_description": "...", │
|
||||
│ "style_description": { ... }, │
|
||||
│ "compositional_deconstruction": { │
|
||||
│ "background": "...", │
|
||||
│ "elements": [ │
|
||||
│ { "type": "obj", "bbox": ... }│
|
||||
│ ] │
|
||||
│ } │
|
||||
│ } │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
1. Drop the entire `comfyui_ideogram_magic_prompt/` folder into
|
||||
`<your-comfyui>/custom_nodes/`.
|
||||
2. Restart ComfyUI.
|
||||
3. The node **"Ideogram Magic Prompt"** will appear under the
|
||||
**Add Node → Ideogram** category.
|
||||
|
||||
No `pip install` needed — the node uses only `urllib` from the Python
|
||||
standard library plus the `comfy_api` module shipped with ComfyUI.
|
||||
|
||||
## Inputs
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `prompt` | multiline string | The plain-text prompt to expand. |
|
||||
| `api_key` | string (password) | Your `IDEOGRAM_API_KEY` from [ideogram.ai/api/learn/](https://ideogram.ai/api/learn/). |
|
||||
| `aspect_ratio` | combo | One of the 18 ratios accepted by Ideogram's magic-prompt API, plus `AUTO`. Pick the closest match to your target image. |
|
||||
|
||||
### Supported aspect ratios
|
||||
|
||||
`AUTO`, `1x4`, `1x3`, `1x2`, `9x16`, `10x16`, `2x3`, `3x4`, `4x5`, `1x1`,
|
||||
`5x4`, `4x3`, `3x2`, `16x10`, `16x9`, `2x1`, `3x1`, `4x1`
|
||||
|
||||
## Output
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `json_caption` | string (JsonPreview) | Stringified JSON caption ready to feed into any Ideogram 4 generation node. |
|
||||
|
||||
The output is automatically parsed and displayed as a JSON tree in the
|
||||
node (ComfyUI's built-in `JsonPreview` tool). You can also connect it as
|
||||
a string to downstream text-handling nodes.
|
||||
|
||||
The output caption is cleaned of `aspect_ratio` echoes and any
|
||||
`bbox` fields, so it's a clean drop-in for
|
||||
`Ideogram4Pipeline.__call__(prompt=...)` or any equivalent.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
plain prompt ─┐
|
||||
├──▶ Ideogram Magic Prompt ──▶ json_caption ──▶ [your Ideogram4 generator]
|
||||
IDEOGRAM_API_KEY ┘ ▲
|
||||
│
|
||||
live JSON preview
|
||||
```
|
||||
|
||||
In a typical workflow you would:
|
||||
1. Add the **Ideogram Magic Prompt** node.
|
||||
2. Plug in a free-form text prompt and your API key.
|
||||
3. Connect the `json_caption` output to your Ideogram 4 generation node
|
||||
(e.g. `Ideogram4Pipeline.__call__` from a custom node, or paste the
|
||||
JSON into the standalone `gradio_app.py` UI as
|
||||
`--prompt (Get-Content .\caption.json)`).
|
||||
|
||||
## Compatibility
|
||||
|
||||
| ComfyUI version | Status |
|
||||
| --- | --- |
|
||||
| **V3 (Modern Node Design)** | ✅ Primary target. Uses `io.ComfyNode`, `io.Schema`, `ComfyExtension`, `comfy_entrypoint()`. |
|
||||
| **V1 / older installs** | ✅ Works. Re-exports `NODE_CLASS_MAPPINGS` and `NODE_DISPLAY_NAME_MAPPINGS` for the legacy loader. |
|
||||
|
||||
The package has no runtime dependencies beyond `comfy_api` (bundled with
|
||||
ComfyUI V3) and the Python standard library.
|
||||
|
||||
## Development
|
||||
|
||||
Run the tests locally:
|
||||
|
||||
```bash
|
||||
python -m pip install -e .[test] # if you add a test extra
|
||||
python -m pytest
|
||||
```
|
||||
|
||||
There's no test suite in this drop — pull requests welcome.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE) — see `LICENSE` for the full text.
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
"""Ideogram Magic Prompt -- ComfyUI custom node package.
|
||||
|
||||
Drop this entire folder into ComfyUI/custom_nodes/ and restart ComfyUI.
|
||||
The node "Ideogram Magic Prompt" will appear under the Ideogram category.
|
||||
"""
|
||||
|
||||
from .nodes import IdeogramExtension, IdeogramMagicPrompt
|
||||
|
||||
# V1-compatible fallback for older ComfyUI installs (V1 schema) so the
|
||||
# node still loads even if comfy_api.latest isn't available. ComfyUI's
|
||||
# loader expects NODE_CLASS_MAPPINGS / NODE_DISPLAY_NAME_MAPPINGS at
|
||||
# module import time.
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"IdeogramMagicPrompt": IdeogramMagicPrompt,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"IdeogramMagicPrompt": "Ideogram Magic Prompt",
|
||||
}
|
||||
|
||||
# Web-exposed metadata
|
||||
__all__ = [
|
||||
"IdeogramMagicPrompt",
|
||||
"IdeogramExtension",
|
||||
"NODE_CLASS_MAPPINGS",
|
||||
"NODE_DISPLAY_NAME_MAPPINGS",
|
||||
]
|
||||
@@ -0,0 +1,152 @@
|
||||
"""Ideogram Magic Prompt -- ComfyUI custom node.
|
||||
|
||||
Generates the structured JSON caption that Ideogram 4 expects from a
|
||||
plain-text prompt, using Ideogram's free hosted magic-prompt API.
|
||||
|
||||
Inputs: prompt (multiline text), api_key (password), aspect_ratio (combo)
|
||||
Output: json_caption (string, displayed via the built-in JsonPreview tool)
|
||||
|
||||
Drop this folder into your ComfyUI/custom_nodes/ directory and restart
|
||||
ComfyUI. The node will appear under the "Ideogram" category.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
try:
|
||||
from comfy_api.latest import ComfyExtension, io, ui
|
||||
except ImportError: # older ComfyUI / V1 fallback
|
||||
from comfy_api.v0_0_2 import ComfyExtension, io, ui # type: ignore[no-redef]
|
||||
|
||||
MAGIC_PROMPT_URL = "https://api.ideogram.ai/v1/ideogram-v4/magic-prompt"
|
||||
|
||||
# 18 aspect ratios accepted by the Ideogram magic-prompt API
|
||||
SUPPORTED_ARS = [
|
||||
"AUTO",
|
||||
"1x4", "1x3", "1x2",
|
||||
"9x16", "10x16",
|
||||
"2x3", "3x4", "4x5",
|
||||
"1x1",
|
||||
"5x4", "4x3", "3x2",
|
||||
"16x10", "16x9",
|
||||
"2x1", "3x1", "4x1",
|
||||
]
|
||||
|
||||
DEFAULT_HEADERS = {
|
||||
"Content-Type": "application/json",
|
||||
"Api-Key": "", # filled at call time
|
||||
}
|
||||
|
||||
DEFAULT_TIMEOUT = 120.0
|
||||
|
||||
|
||||
def _post_magic_prompt(prompt: str, aspect_ratio: str, api_key: str, timeout: float = DEFAULT_TIMEOUT) -> dict:
|
||||
"""Call Ideogram's hosted magic-prompt API. Returns the parsed JSON
|
||||
caption dict (after stripping `aspect_ratio` and bboxes so the
|
||||
caption is a clean drop-in for the Ideogram4Pipeline)."""
|
||||
headers = {**DEFAULT_HEADERS, "Api-Key": api_key}
|
||||
body = json.dumps({"text_prompt": prompt, "aspect_ratio": aspect_ratio}).encode("utf-8")
|
||||
req = urllib.request.Request(MAGIC_PROMPT_URL, data=body, headers=headers, method="POST")
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
payload = json.loads(resp.read().decode("utf-8"))
|
||||
except urllib.error.HTTPError as e:
|
||||
raise RuntimeError(
|
||||
f"Ideogram magic-prompt API HTTP {e.code}: {e.read().decode('utf-8', errors='replace')}"
|
||||
) from e
|
||||
except urllib.error.URLError as e:
|
||||
raise RuntimeError(f"Ideogram magic-prompt API unreachable: {e.reason}") from e
|
||||
|
||||
jp = payload.get("json_prompt")
|
||||
if not jp:
|
||||
raise RuntimeError(f"Ideogram API returned no json_prompt: {payload}")
|
||||
# Clean up the caption: drop the echoed aspect_ratio and any
|
||||
# bboxes (the model sometimes includes them in the upstream JSON).
|
||||
jp.pop("aspect_ratio", None)
|
||||
for el in jp.get("compositional_deconstruction", {}).get("elements", []):
|
||||
if isinstance(el, dict):
|
||||
el.pop("bbox", None)
|
||||
return jp
|
||||
|
||||
|
||||
class IdeogramMagicPrompt(io.ComfyNode):
|
||||
"""Expand a plain prompt into Ideogram 4's native structured JSON caption.
|
||||
|
||||
The output is a stringified JSON object. Feed it directly into an
|
||||
Ideogram4 generation node (or paste into the ideogram4 gradio UI).
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="IdeogramMagicPrompt",
|
||||
display_name="Ideogram Magic Prompt",
|
||||
category="Ideogram",
|
||||
description=(
|
||||
"Rewrites a plain prompt into the structured JSON caption "
|
||||
"format the Ideogram 4 model was trained on. Uses Ideogram's "
|
||||
"free hosted magic-prompt API."
|
||||
),
|
||||
inputs=[
|
||||
io.String.Input(
|
||||
"prompt",
|
||||
multiline=True,
|
||||
tooltip="Plain-text prompt to expand.",
|
||||
),
|
||||
io.String.Input(
|
||||
"api_key",
|
||||
default="",
|
||||
tooltip="IDEOGRAM_API_KEY from https://ideogram.ai/api/learn/",
|
||||
),
|
||||
io.Combo.Input(
|
||||
"aspect_ratio",
|
||||
options=SUPPORTED_ARS,
|
||||
default="1x1",
|
||||
tooltip="Closest aspect ratio for which to lay out the caption.",
|
||||
),
|
||||
],
|
||||
outputs=[
|
||||
io.String.Output(
|
||||
"json_caption",
|
||||
tooltip="Stringified JSON caption -- feed to Ideogram4 generate.",
|
||||
tool="JsonPreview",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, prompt: str, api_key: str, aspect_ratio: str) -> io.NodeOutput:
|
||||
if not prompt or not prompt.strip():
|
||||
raise ValueError("Prompt is empty.")
|
||||
if not api_key or not api_key.strip():
|
||||
raise ValueError(
|
||||
"IDEOGRAM_API_KEY is empty. Get one at "
|
||||
"https://ideogram.ai/api/learn/ and paste it into the node."
|
||||
)
|
||||
|
||||
t0 = time.time()
|
||||
caption_obj = _post_magic_prompt(prompt, aspect_ratio, api_key)
|
||||
elapsed = time.time() - t0
|
||||
|
||||
caption_str = json.dumps(caption_obj, ensure_ascii=False, separators=(",", ":"))
|
||||
# Wrap the timing metadata as a log we can show in the UI.
|
||||
log = f"magic-prompt expansion: {elapsed:.1f}s, aspect={aspect_ratio}"
|
||||
return io.NodeOutput(
|
||||
caption_str,
|
||||
ui=ui.PreviewText(log),
|
||||
)
|
||||
|
||||
|
||||
class IdeogramExtension(ComfyExtension):
|
||||
"""Registers the Ideogram magic-prompt node with ComfyUI."""
|
||||
|
||||
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
||||
return [IdeogramMagicPrompt]
|
||||
|
||||
|
||||
async def comfy_entrypoint() -> IdeogramExtension: # noqa: D401
|
||||
return IdeogramExtension()
|
||||
Reference in New Issue
Block a user