Files
CraftopiaStudio-ComfyUI-Cra…/__init__.py
T
rubz c6f6914488 fix: batch collapse, memory blowup, resize rounding, and 8 more Should-Fix issues from code review
- Reject use_original_name=False + use_counter=False (all files collapsed to one output name)
- Add preview_limit to cap in-memory preview tensors (was holding entire batch as float32)
- Recover from per-file failures instead of aborting the whole batch
- Fix _calc_new_size overshoot past longest_side and off-by-one truncation
- Add upscale_if_smaller to SmartBatchResize to match SmartResize
- Clip pixel values before uint8 cast (was wrapping on overflow)
- Support CUDA tensors and RGBA/grayscale images in SmartResize
- Handle empty batches; derive width/height from the actual output tensor
- Require POST + loopback/origin check on the browse_folder endpoint (CSRF)
- Add subprocess timeout to the folder picker dialog
- Isolate the browse_folder route registration so import failures don't take down all nodes
- Validate folder_custom against path traversal and self-overwrite
- Replace IS_CHANGED=NaN with a folder-listing hash so unrelated graph runs can still cache
2026-07-27 18:42:54 +02:00

140 lines
5.3 KiB
Python

WEB_DIRECTORY = "./js"
from .smart_prompt_controller import NODE_CLASS_MAPPINGS as A, NODE_DISPLAY_NAME_MAPPINGS as A_NAMES
from .smart_profile_switch import NODE_CLASS_MAPPINGS as B, NODE_DISPLAY_NAME_MAPPINGS as B_NAMES
from .smart_resize import NODE_CLASS_MAPPINGS as C, NODE_DISPLAY_NAME_MAPPINGS as C_NAMES
from .smart_batch_resize import NODE_CLASS_MAPPINGS as D, NODE_DISPLAY_NAME_MAPPINGS as D_NAMES
from .smart_resolution_multiplier import NODE_CLASS_MAPPINGS as E, NODE_DISPLAY_NAME_MAPPINGS as E_NAMES
NODE_CLASS_MAPPINGS = {**A, **B, **C, **D, **E}
NODE_DISPLAY_NAME_MAPPINGS = {**A_NAMES, **B_NAMES, **C_NAMES, **D_NAMES, **E_NAMES}
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
try:
import asyncio
import concurrent.futures
from aiohttp import web
from server import PromptServer
async def browse_folder(request):
import subprocess, sys
if request.remote not in ("127.0.0.1", "::1"):
return web.json_response({"ok": False, "error": "forbidden"}, status=403)
origin = request.headers.get("Origin")
if origin is not None:
host = request.headers.get("Host", "")
origin_host = origin.split("://", 1)[-1]
if origin_host != host:
return web.json_response({"ok": False, "error": "forbidden"}, status=403)
def _open_dialog():
if sys.platform != "win32":
return ""
ps = r"""
Add-Type -AssemblyName System.Windows.Forms
Add-Type @"
using System;
using System.Runtime.InteropServices;
public static class CraftKitFolderPicker
{
[ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")]
private class FileOpenDialogRCW { }
[ComImport, Guid("42f85136-db7e-439c-85f1-e4075d135fc8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IFileDialog
{
[PreserveSig] int Show(IntPtr parent);
void SetFileTypes(uint cFileTypes, IntPtr rgFilterSpec);
void SetFileTypeIndex(uint iFileType);
void GetFileTypeIndex(out uint piFileType);
void Advise(IntPtr pfde, out uint pdwCookie);
void Unadvise(uint dwCookie);
void SetOptions(uint fos);
void GetOptions(out uint fos);
void SetDefaultFolder(IntPtr psi);
void SetFolder(IntPtr psi);
void GetFolder(out IntPtr ppsi);
void GetCurrentSelection(out IntPtr ppsi);
void SetFileName(string pszName);
void GetFileName(out IntPtr pszName);
void SetTitle(string pszTitle);
void SetOkButtonLabel(string pszText);
void SetFileNameLabel(string pszLabel);
void GetResult(out IShellItemLocal ppsi);
void AddPlace(IntPtr psi, uint fdap);
void SetDefaultExtension(string pszDefaultExtension);
void Close(int hr);
void SetClientGuid(ref Guid guid);
void ClearClientData();
void SetFilter(IntPtr pFilter);
}
[ComImport, Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellItemLocal
{
void BindToHandler(IntPtr pbc, ref Guid bhid, ref Guid riid, out IntPtr ppv);
void GetParent(out IShellItemLocal ppsi);
void GetDisplayName(uint sigdnName, out IntPtr ppszName);
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
void Compare(IShellItemLocal psi, uint hint, out int piOrder);
}
public static string ShowDialog(IntPtr owner, string title)
{
var dialog = (IFileDialog)new FileOpenDialogRCW();
dialog.SetOptions(0x20u | 0x40u); // FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM
if (!string.IsNullOrEmpty(title)) dialog.SetTitle(title);
int hr = dialog.Show(owner);
if (hr != 0) return null;
IShellItemLocal item;
dialog.GetResult(out item);
IntPtr pszPath;
item.GetDisplayName(0x80058000u, out pszPath); // SIGDN_FILESYSPATH
string path = Marshal.PtrToStringAuto(pszPath);
Marshal.FreeCoTaskMem(pszPath);
return path;
}
}
"@
$r = ''
$o = New-Object System.Windows.Forms.Form
$o.TopMost = $true
$o.ShowInTaskbar = $false
$o.FormBorderStyle = 'None'
$o.Width = 1; $o.Height = 1; $o.Opacity = 0
$o.StartPosition = 'CenterScreen'
$o.Add_Shown({
$o.Activate()
$path = [CraftKitFolderPicker]::ShowDialog($o.Handle, 'Select input folder')
if ($path) { $script:r = $path }
$o.Close()
})
$o.ShowDialog() | Out-Null
$r
"""
try:
result = subprocess.run(
["powershell", "-NoProfile", "-Command", ps],
capture_output=True, text=True, timeout=300
)
except subprocess.TimeoutExpired:
return ""
return result.stdout.strip()
loop = asyncio.get_running_loop()
with concurrent.futures.ThreadPoolExecutor() as pool:
folder = await loop.run_in_executor(pool, _open_dialog)
if folder and __import__("os").path.isdir(folder):
return web.json_response({"ok": True, "path": folder})
return web.json_response({"ok": False, "cancelled": True})
PromptServer.instance.routes.post("/craftkit/browse_folder")(browse_folder)
except Exception as e:
print(f"[CraftKit] Browse folder endpoint unavailable ({e}); paste input folder paths manually.")