Files
SatadalAI-SATA_UtilityNode/nodes/resolution_machine.py
T
2025-09-12 11:29:01 +05:30

201 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# resolution_machine.py
from server import PromptServer
from aiohttp import web
class Resolution_Machine:
CATEGORY = "SATA_UtilityNode"
@classmethod
def _get_config(cls):
"""Hard-coded model->resolutions JSON. We append a 'Custom (manual)' entry
to every model to avoid validation failures when user edits width/height manually.
"""
cfg = {
"models": [
{
"model": "SD1.5",
"resolutions": [
{ "name": "Square (1:1)", "width": 512, "height": 512 },
{ "name": "Square high-res (1:1)", "width": 768, "height": 768 },
{ "name": "Landscape (3:2)", "width": 768, "height": 512 },
{ "name": "Portrait (2:3)", "width": 512, "height": 768 },
{ "name": "Landscape (4:3)", "width": 768, "height": 576 },
{ "name": "Portrait (3:4)", "width": 576, "height": 768 },
{ "name": "Widescreen (16:9)", "width": 912, "height": 512 },
{ "name": "Tall (9:16)", "width": 512, "height": 912 }
]
},
{
"model": "SDXL",
"resolutions": [
{ "name": "Square (1:1 native)", "width": 1024, "height": 1024 },
{ "name": "Portrait (2:3)", "width": 832, "height": 1248 },
{ "name": "Standard (3:4)", "width": 880, "height": 1176 },
{ "name": "Large Format (4:5)", "width": 912, "height": 1144 },
{ "name": "SD TV (4:3)", "width": 1176, "height": 888 },
{ "name": "IMAX (1.43:1)", "width": 1224, "height": 856 },
{ "name": "Golden Ratio (1.618:1)", "width": 1296, "height": 800 },
{ "name": "European Widescreen (1.66:1)", "width": 1312, "height": 792 },
{ "name": "Widescreen / HD TV (16:9)", "width": 1360, "height": 768 },
{ "name": "Standard Widescreen (1.85:1)", "width": 1392, "height": 752 },
{ "name": "Cinemascope / Panavision (2.35:1)", "width": 1568, "height": 664 },
{ "name": "Anamorphic Widescreen (2.39:1)", "width": 1576, "height": 656 },
{ "name": "Older TV & Documentaries (4:3)", "width": 1176, "height": 880 }
]
},
{
"model": "Illustrious XL v1.0",
"resolutions": [
{ "name": "Minimum training bound", "width": 512, "height": 512 },
{ "name": "Non-standard example", "width": 1248, "height": 1824 },
{ "name": "Native high-res support", "width": 1536, "height": 1536 }
]
},
{
"model": "Illustrious XL v3.0",
"resolutions": [
{ "name": "Minimum usable", "width": 256, "height": 256 },
{ "name": "Standard high-res", "width": 1536, "height": 1536 },
{ "name": "Maximum native", "width": 2048, "height": 2048 }
]
},
{
"model": "Pony XL",
"resolutions": [
{ "name": "Portrait-tall", "width": 832, "height": 1216 },
{ "name": "Landscape-wide", "width": 1216, "height": 832 },
{ "name": "Square (1:1)", "width": 1024, "height": 1024 }
]
},
{
"model": "Flux.1 Dev",
"resolutions": [
{ "name": "512×512 (1 MP quick test)", "width": 512, "height": 512 },
{ "name": "1024×1024 (daily work)", "width": 1024, "height": 1024 },
{ "name": "1600×1600 (detailed work)", "width": 1600, "height": 1600 },
{ "name": "1920×1080 (HD)", "width": 1920, "height": 1080 },
{ "name": "2560×1440 (QHD)", "width": 2560, "height": 1440 },
{ "name": "3840×2160 (4K)", "width": 3840, "height": 2160 }
]
},
{
"model": "Qwen-Image",
"resolutions": [
{ "name": "Square (1:1)", "width": 1328, "height": 1328 },
{ "name": "Widescreen (16:9)", "width": 1664, "height": 928 },
{ "name": "Tall (9:16)", "width": 928, "height": 1664 },
{ "name": "Landscape (4:3)", "width": 1472, "height": 1140 },
{ "name": "Portrait (3:4)", "width": 1140, "height": 1472 }
]
},
{
"model": "Wan2.2-S2V",
"resolutions": [
{ "name": "720p HD video", "width": 1280, "height": 720 }
]
},
{
"model": "SD3.5 Large",
"resolutions": [
{ "name": "768×1152 (best portrait)", "width": 768, "height": 1152 },
{ "name": "1152×768 (best landscape)", "width": 1152, "height": 768 },
{ "name": "1024×1024 (square)", "width": 1024, "height": 1024 },
{ "name": "768×768 (quick test)", "width": 768, "height": 768 },
{ "name": "832×1216 portrait", "width": 832, "height": 1216 },
{ "name": "1216×832 landscape", "width": 1216, "height": 832 }
]
},
{
"model": "Lumina-Image 2.0",
"resolutions": [
{ "name": "Default (1 MP square)", "width": 1024, "height": 1024 }
]
}
]
}
# Append Custom option to every model's resolutions (idempotent)
for m in cfg["models"]:
if not any(r["name"] == "Custom (manual)" for r in m.get("resolutions", [])):
m.setdefault("resolutions", []).append({"name": "Custom (manual)", "width": 0, "height": 0})
return cfg
@classmethod
def INPUT_TYPES(cls, context=None):
"""Return model list and compile a global (deterministic) resolution list.
We return a list[str] for 'resolution' so ComfyUI always creates a COMBO dropdown.
"""
cfg = cls._get_config()
# models (keep order)
models = [m["model"] for m in cfg["models"]]
# Build a deterministic list of unique resolution names (preserve model order)
seen = set()
all_resolutions = []
for m in cfg["models"]:
for r in m.get("resolutions", []):
name = r.get("name")
if name and name not in seen:
seen.add(name)
all_resolutions.append(name)
# Guarantee non-empty list (shouldn't be needed, but defensive)
if not all_resolutions:
all_resolutions = ["Custom (manual)"]
# default model & default resolution (first non-custom of default model preferred)
default_model = models[0] if models else ""
default_res = None
model_obj = next((m for m in cfg["models"] if m["model"] == default_model), None)
if model_obj:
first_non_custom = next((r for r in model_obj["resolutions"] if r["name"] != "Custom (manual)"), None)
if first_non_custom:
default_res = first_non_custom["name"]
else:
default_res = model_obj["resolutions"][0]["name"]
if not default_res:
default_res = all_resolutions[0]
# default width/height derived from selected default resolution / model
default_width = 512
default_height = 512
if model_obj and default_res:
res_obj = next((r for r in model_obj["resolutions"] if r["name"] == default_res), None)
if res_obj:
default_width = res_obj["width"]
default_height = res_obj["height"]
return {
"required": {
"model": (models if models else ["null"], {"default": default_model}),
# Declare as list[str] so ComfyUI builds a COMBO dropdown reliably.
"resolution": (all_resolutions, {"default": default_res, "forceInput": True, "force_input": True}),
"width": ("INT", {"default": default_width, "min": 1, "max": 16384, "step": 1}),
"height": ("INT", {"default": default_height, "min": 1, "max": 16384, "step": 1}),
}
}
RETURN_TYPES = ("INT", "INT")
RETURN_NAMES = ("width", "height")
FUNCTION = "run"
def run(self, model, resolution, width, height):
"""If the chosen resolution exists for the selected model and is not Custom,
return the predefined width & height; otherwise return manual values.
"""
cfg = self._get_config()
model_obj = next((m for m in cfg["models"] if m["model"] == model), None)
if model_obj and resolution and resolution != "Custom (manual)":
res_obj = next((r for r in model_obj["resolutions"] if r["name"] == resolution), None)
if res_obj:
return res_obj["width"], res_obj["height"]
# fallback -> manual
return width, height
# REST endpoint so frontend can fetch the full mapping (optional but useful)
@PromptServer.instance.routes.get("/sata/resolution_machine/config")
async def get_resolution_config(request):
return web.json_response(Resolution_Machine._get_config())