updated new node

This commit is contained in:
Satadal Dhara
2025-09-19 22:35:41 +05:30
parent c3a0a87755
commit 827eee49e3
5 changed files with 246 additions and 291 deletions
-3
View File
@@ -41,7 +41,6 @@ def read_names_from_csv(filename):
val = raw.strip()
if val:
names.append(val)
print(f"[PromptMachine] Loaded {len(names)} names from {filename}")
return names
except Exception as e:
print(f"[PromptMachine] read_names_from_csv error for {filename}: {e}")
@@ -99,8 +98,6 @@ class Prompt_Machine:
names = read_names_from_csv(default_csv) if default_csv != "None" else []
default_name = names[0] if names else "None"
print(f"[PromptMachine] INIT - default_csv={default_csv}, default_name={default_name}")
return {
"required": {
"csv_file": (csvs if csvs else ["None"], {
+60 -149
View File
@@ -1,170 +1,81 @@
import json
import os
from server import PromptServer
from aiohttp import web
NODE_NAME = "Resolution_Machine"
# Path to JSON config (root of SATA_UtilityNode folder)
CONFIG_PATH = os.path.join(
os.path.dirname(os.path.dirname(__file__)), "resolutions.json"
)
def load_config():
if not os.path.exists(CONFIG_PATH):
raise FileNotFoundError(f"[{NODE_NAME}] resolutions.json not found at {CONFIG_PATH}")
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
return json.load(f)
class Resolution_Machine:
CATEGORY = "SATA_UtilityNode"
# persist last-used selections
last_model = None
last_resolution = None
last_width = None
last_height = None
@classmethod
def _get_config(cls):
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": 1216 },
{ "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",
"resolutions": [
{ "name": "Minimum training bound", "width": 512, "height": 512 },
{ "name": "standard example", "width": 1024, "height": 1024 },
{ "name": "Standard high-res", "width": 1536, "height": 1536 },
{ "name": "Maximum native", "width": 2048, "height": 2048 },
{ "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 }
]
}
]
}
def INPUT_TYPES(cls):
config = load_config()
# Append Custom option idempotently
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})
# all models
models = list(config.keys())
default_model = models[0] if models else "Unknown"
return cfg
# union of all resolutions across all models
all_resolutions = set()
for model_res in config.values():
all_resolutions.update(model_res.keys())
resolutions = sorted(list(all_resolutions))
@classmethod
def INPUT_TYPES(cls, context=None):
cfg = cls._get_config()
models = [m["model"] for m in cfg["models"]]
# restore last used model or default
default_model = cls.last_model if cls.last_model in models else (models[0] if models else "")
model_obj = next((m for m in cfg["models"] if m["model"] == default_model), None)
if model_obj:
resolutions = [r["name"] for r in model_obj.get("resolutions", [])]
else:
resolutions = ["Custom (manual)"]
# restore last used resolution if valid
if cls.last_resolution and cls.last_resolution in resolutions:
default_res = cls.last_resolution
else:
default_res = next((n for n in resolutions if n != "Custom (manual)"), resolutions[0])
# determine defaults for width/height
default_w, default_h = 512, 512
if cls.last_width and cls.last_height:
default_w, default_h = cls.last_width, cls.last_height
else:
r_obj = next((r for r in model_obj["resolutions"] if r["name"] == default_res), None) if model_obj else None
if r_obj:
default_w, default_h = r_obj["width"], r_obj["height"]
default_resolution = list(config[default_model].keys())[0]
return {
"required": {
"model": (models if models else ["null"], {"default": default_model}),
"resolution": (resolutions if resolutions else ["Custom (manual)"], {"default": default_res}),
"width": ("INT", {"default": default_w, "min": 1, "max": 16384, "step": 1}),
"height": ("INT", {"default": default_h, "min": 1, "max": 16384, "step": 1}),
"model": (models, {"default": default_model}),
"resolution": (resolutions, {"default": default_resolution}),
"custom_width": ("INT", {"default": 512, "min": 1, "max": 8192}),
"custom_height": ("INT", {"default": 512, "min": 1, "max": 8192}),
}
}
RETURN_TYPES = ("INT", "INT")
RETURN_NAMES = ("width", "height")
FUNCTION = "run"
FUNCTION = "get_resolution"
CATEGORY = "SATA Utility"
def run(self, model, resolution, width, height):
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:
width, height = res_obj["width"], res_obj["height"]
def get_resolution(self, model, resolution, custom_width, custom_height):
config = load_config()
# store last selections for persistence
Resolution_Machine.last_model = model
Resolution_Machine.last_resolution = resolution
Resolution_Machine.last_width = width
Resolution_Machine.last_height = height
if model not in config:
raise ValueError(f"[{NODE_NAME}] Unknown model: {model}")
return width, height
resolutions = config[model]
if resolution not in resolutions:
# fallback to first available resolution for safety
resolution = list(resolutions.keys())[0]
print(f"[{NODE_NAME}] Warning: invalid resolution, defaulting to {resolution}")
if resolution == "Custom (manual)":
width, height = custom_width, custom_height
else:
entry = resolutions[resolution]
width, height = entry["width"], entry["height"]
print(f"[{NODE_NAME}] Model={model}, Resolution={resolution}, Output=({width}, {height})")
return (width, height)
# REST endpoint for frontend dynamic updates
@PromptServer.instance.routes.get("/sata/resolution_machine/config")
async def get_resolution_config(request):
return web.json_response(Resolution_Machine._get_config())
# --- REST endpoint for frontend config fetch ---
@PromptServer.instance.routes.get("/SATA_UtilityNode/resolutions_config")
async def get_resolutions_config(request):
try:
config = load_config()
return web.json_response(config)
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
+61
View File
@@ -0,0 +1,61 @@
{
"SD 1.5": {
"Square (1:1) 512x512": { "width": 512, "height": 512 },
"Square high-res (1:1) 768x768": { "width": 768, "height": 768 },
"Landscape (3:2) 768x512": { "width": 768, "height": 512 },
"Portrait (2:3) 512x768": { "width": 512, "height": 768 },
"Landscape (4:3) 768x576": { "width": 768, "height": 576 },
"Portrait (3:4) 576x768": { "width": 576, "height": 768 },
"Widescreen (16:9) 912x512": { "width": 912, "height": 512 },
"Tall (9:16) 512x912": { "width": 512, "height": 912 },
"Custom (manual)": { "width": 0, "height": 0 }
},
"SDXL_Illustrious_Pony": {
"Square (1:1) 1024x1024": { "width": 1024, "height": 1024 },
"Landscape (3:2) 1280x832": { "width": 1280, "height": 832 },
"Portrait (2:3) 832x1280": { "width": 832, "height": 1280 },
"Photography (4:3) 1152x896": { "width": 1152, "height": 896 },
"Portrait Insta (3:4) 896x1152": { "width": 896, "height": 1152 },
"Widescreen (16:9) 1344x768": { "width": 1344, "height": 768 },
"Vertical (9:16) 768x1344": { "width": 768, "height": 1344 },
"Cinematic (21:9) 1536x640": { "width": 1536, "height": 640 },
"Cinematic Portrait (9:21) 640x1536": { "width": 640, "height": 1536 },
"Best Landscape (19:13) 1216x832": { "width": 1216, "height": 832 },
"Best Portrait (13:19) 832x1216": { "width": 832, "height": 1216 },
"Mini Portrait (768x1152)": { "width": 768, "height": 1152 },
"Mini Landscape (1152x768)": { "width": 1152, "height": 768 },
"Custom (manual)": { "width": 0, "height": 0 }
},
"Flux": {
"High-res (16:9) 1920x1088": { "width": 1920, "height": 1088 },
"Square Medium (1:1) 768x768": { "width": 768, "height": 768 },
"Portrait Small (3:4) 768x1024": { "width": 768, "height": 1024 },
"Landscape Small (4:3) 1024x768": { "width": 1024, "height": 768 },
"Wide Small (3:2) 1152x768": { "width": 1152, "height": 768 },
"Portrait Medium (2:3) 768x1152": { "width": 768, "height": 1152 },
"Ultra Wide Small (2:1) 1536x768": { "width": 1536, "height": 768 },
"Portrait Large (1:2) 768x1536": { "width": 768, "height": 1536 },
"Square Large (1:1) 1024x1024": { "width": 1024, "height": 1024 },
"Portrait Small HD (7:10) 896x1280": { "width": 896, "height": 1280 },
"Landscape Small HD (10:7) 1280x896": { "width": 1280, "height": 896 },
"Wide Medium (12:7) 1536x896": { "width": 1536, "height": 896 },
"Portrait HD (7:12) 896x1536": { "width": 896, "height": 1536 },
"Custom (manual)": { "width": 0, "height": 0 }
},
"Qwen-Image": {
"Square (1:1)": { "width": 1328, "height": 1328 },
"Widescreen (16:9)": { "width": 1664, "height": 928 },
"Tall (9:16)": { "width": 928, "height": 1664 },
"Landscape (4:3)": { "width": 1472, "height": 1140 },
"Portrait (3:4)": { "width": 1140, "height": 1472 },
"Custom (manual)": { "width": 0, "height": 0 }
},
"Wan2.2": {
"720p HD video": { "width": 1280, "height": 720 },
"Custom (manual)": { "width": 0, "height": 0 }
}
}
+38 -26
View File
@@ -6,49 +6,61 @@ app.registerExtension({
async nodeCreated(node) {
if (node.comfyClass !== "Prompt_Machine") return;
let csvWidget = node.widgets.find(w => w.name === "csv_file");
let oldNameWidget = node.widgets.find(w => w.name === "name");
// Widgets
const csvWidget = node.widgets.find(w => w.name === "csv_file");
const oldNameWidget = node.widgets.find(w => w.name === "name");
if (!csvWidget || !oldNameWidget) {
console.warn("[PromptMachineFrontend] widgets not found");
console.warn("[PromptMachineFrontend] Missing widgets, check backend definition.");
return;
}
// Remove the old STRING widget
// Remove the old text input for 'name'
const index = node.widgets.indexOf(oldNameWidget);
node.widgets.splice(index, 1);
// Add a proper dropdown instead
let nameWidget = node.addWidget("combo", "name", "None", () => {}, {
// Add proper dropdown
const nameWidget = node.addWidget("combo", "name", "None", () => {}, {
values: ["None"]
});
async function refreshNames(csvValue) {
// Store available names
let namesConfig = [];
// Fetch names from backend for a given CSV
async function loadNames(csvValue) {
if (!csvValue) return;
try {
const res = await fetch(`/sata/prompt_machine/names?csv=${encodeURIComponent(csvValue)}`);
const data = await res.json();
const names = data.names || ["None"];
nameWidget.options.values = names;
if (!names.includes(nameWidget.value)) {
nameWidget.value = names[0];
}
if (node.graph) node.graph.setDirtyCanvas(true);
console.log("[PromptMachineFrontend] refreshed names:", names);
const resp = await fetch(`/sata/prompt_machine/names?csv=${encodeURIComponent(csvValue)}`);
const data = await resp.json();
namesConfig = data.names || ["None"];
console.log("[PromptMachineFrontend] Loaded names:", namesConfig);
refreshNames();
} catch (err) {
console.error("[PromptMachineFrontend] error fetching names:", err);
console.error("[PromptMachineFrontend] Failed to load names:", err);
}
}
// Initial load
refreshNames(csvWidget.value);
// Refresh dropdown with available names
function refreshNames() {
const available = namesConfig.length > 0 ? namesConfig : ["None"];
nameWidget.options.values = available;
// Reload names when CSV changes
const originalCsvCallback = csvWidget.callback;
csvWidget.callback = (value) => {
refreshNames(value);
if (originalCsvCallback) originalCsvCallback(value);
if (!available.includes(nameWidget.value)) {
nameWidget.value = available[0]; // safe fallback
}
app.graph.change();
}
// Hook CSV change
const oldCsvCallback = csvWidget.callback;
csvWidget.callback = function(value) {
if (oldCsvCallback) oldCsvCallback(value);
loadNames(value); // refresh dynamically
};
// Initial sync
loadNames(csvWidget.value);
}
});
+87 -113
View File
@@ -1,123 +1,97 @@
// resolution_machine.js
import { app } from "../../../scripts/app.js";
async function fetchConfig() {
try {
const resp = await fetch("/sata/resolution_machine/config");
if (!resp.ok) {
console.warn("ResolutionMachine: fetch failed", resp.status);
return null;
}
return await resp.json();
} catch (e) {
console.warn("ResolutionMachine: fetch exception", e);
return null;
}
}
app.registerExtension({
name: "SATA_UtilityNode.ResolutionMachine",
async nodeCreated(node) {
if (node.comfyClass !== "Resolution_Machine") return;
name: "SATA_UtilityNode.Resolution_Machine",
const cfg = await fetchConfig();
if (!cfg) return;
async nodeCreated(node) {
if (node.comfyClass !== "Resolution_Machine") return;
// find widgets
let modelW = node.widgets.find(w => w.name === "model");
let resW = node.widgets.find(w => w.name === "resolution");
let wW = node.widgets.find(w => w.name === "width");
let hW = node.widgets.find(w => w.name === "height");
// Find widgets
const modelWidget = node.widgets.find(w => w.name === "model");
const resolutionWidget = node.widgets.find(w => w.name === "resolution");
const widthWidget = node.widgets.find(w => w.name === "custom_width");
const heightWidget = node.widgets.find(w => w.name === "custom_height");
if (!modelW || !resW || !wW || !hW) {
console.warn("ResolutionMachine: widgets missing, check INPUT_TYPES");
return;
}
// --- rebuild widgets as dropdowns to force combo type ---
const idxModel = node.widgets.indexOf(modelW);
node.widgets.splice(idxModel, 1);
modelW = node.addWidget("combo", "model", "None", () => {}, {
values: cfg.models.map(m => m.model)
});
const idxRes = node.widgets.indexOf(resW);
node.widgets.splice(idxRes, 1);
resW = node.addWidget("combo", "resolution", "Custom (manual)", () => {}, {
values: ["Custom (manual)"]
});
// helper lookups
function getResNamesForModel(modelName) {
const m = cfg.models.find(x => x.model === modelName);
if (!m) return ["Custom (manual)"];
const names = m.resolutions.map(r => r.name);
if (!names.includes("Custom (manual)")) names.push("Custom (manual)");
return names;
}
function findResObj(modelName, resName) {
const m = cfg.models.find(x => x.model === modelName);
if (!m) return null;
return m.resolutions.find(r => r.name === resName) || null;
}
let suppressWH = false;
function programmaticSetWH(wVal, hVal) {
suppressWH = true;
wW.value = wVal;
hW.value = hVal;
if (wW.inputEl) wW.inputEl.value = wVal;
if (hW.inputEl) hW.inputEl.value = hVal;
setTimeout(() => { suppressWH = false; }, 0);
}
// model handler
modelW.callback = (newModel) => {
const names = getResNamesForModel(newModel);
resW.options.values = names;
if (!names.includes(resW.value)) {
resW.value = names[0];
}
resW.callback(resW.value); // trigger res change
node.graph?.setDirtyCanvas(true);
};
// resolution handler
resW.callback = (newRes) => {
const modelName = modelW.value;
const resObj = findResObj(modelName, newRes);
if (resObj && newRes !== "Custom (manual)") {
programmaticSetWH(resObj.width, resObj.height);
if (wW.inputEl) wW.inputEl.disabled = true;
if (hW.inputEl) hW.inputEl.disabled = true;
} else {
if (wW.inputEl) wW.inputEl.disabled = false;
if (hW.inputEl) hW.inputEl.disabled = false;
}
node.graph?.setDirtyCanvas(true);
};
// manual WH handler
function onManualWH() {
if (suppressWH) return;
if (resW.value !== "Custom (manual)") {
const names = getResNamesForModel(modelW.value);
if (!names.includes("Custom (manual)")) {
names.push("Custom (manual)");
resW.options.values = names;
if (!modelWidget || !resolutionWidget || !widthWidget || !heightWidget) {
console.warn("[Resolution_Machine] Missing widgets, check backend definition.");
return;
}
resW.value = "Custom (manual)";
resW.callback("Custom (manual)");
}
}
wW.callback = onManualWH;
hW.callback = onManualWH;
// --- initialize ---
const firstModel = cfg.models[0]?.model;
if (firstModel) {
modelW.value = firstModel;
modelW.callback(firstModel);
// Store resolution config
let resolutionsConfig = {};
// Fetch resolution config from backend
async function loadConfig() {
try {
const resp = await fetch("/SATA_UtilityNode/resolutions_config");
resolutionsConfig = await resp.json();
console.log("[Resolution_Machine] Loaded config:", resolutionsConfig);
} catch (err) {
console.error("[Resolution_Machine] Failed to load config:", err);
}
}
await loadConfig();
// --- Utility: refresh resolution dropdown when model changes ---
function refreshResolutions(selectedModel) {
const available = resolutionsConfig[selectedModel]
? Object.keys(resolutionsConfig[selectedModel])
: ["Custom (manual)"];
resolutionWidget.options.values = available;
// Pick safe default if current value is not valid
if (!available.includes(resolutionWidget.value)) {
resolutionWidget.value = available[0];
}
console.log(`[Resolution_Machine] Resolutions for ${selectedModel}:`, available);
app.graph.change();
applyResolution(); // auto-apply width/height on refresh
}
// --- Utility: apply resolution to width/height ---
function applyResolution() {
const selectedModel = modelWidget.value;
const selectedResolution = resolutionWidget.value;
if (!resolutionsConfig[selectedModel]) return;
const resolutionData = resolutionsConfig[selectedModel][selectedResolution];
if (selectedResolution !== "Custom (manual)" && resolutionData) {
// Auto-set width/height
widthWidget.value = resolutionData.width;
heightWidget.value = resolutionData.height;
// Lock fields
widthWidget.disabled = true;
heightWidget.disabled = true;
} else {
// Allow manual editing
widthWidget.disabled = false;
heightWidget.disabled = false;
}
app.graph.change();
}
// --- Hook model change ---
const oldModelCallback = modelWidget.callback;
modelWidget.callback = function(value) {
if (oldModelCallback) oldModelCallback(value);
refreshResolutions(value);
};
// --- Hook resolution change ---
const oldResolutionCallback = resolutionWidget.callback;
resolutionWidget.callback = function(value) {
if (oldResolutionCallback) oldResolutionCallback(value);
applyResolution();
};
// --- Initial sync ---
refreshResolutions(modelWidget.value);
}
}
});