"""
def add_selectbox(title, label, listitems, **_):
listitems = listitems.split(",")
if not label.isidentifier():
raise RuntimeError(f"{title}: label must contain only valid identifier characters.")
if not all(item == html.escape(item) for item in listitems):
raise RuntimeError(f"{title}: listitems must contain only HTML-acceptable characters.")
param.setdefault(label, "")
text_html = f"""
"""
block[f"{title}_{label}"] = text_html
def add_sizeselect(title, label, listitems, **_):
listitems = listitems.split(",")
if not label.isidentifier():
raise RuntimeError(f"{title}: label must contain only valid identifier characters.")
if not all(item == html.escape(item) for item in listitems):
raise RuntimeError(f"{title}: listitems must contain only HTML-acceptable characters.")
param.setdefault(label, "")
text_html = f"""
"""
block[f"{title}_{label}"] = text_html
def add_fileselect(title, label, folder_name, folder_path, mode, use_sub, use_move, **_):
if not label.isidentifier():
raise RuntimeError(f"{title}: label must contain only valid identifier characters.")
if not (mode == "" or mode.isidentifier()):
raise RuntimeError(f"{title}: mode must contain only valid identifier characters.")
param.setdefault(label, "")
state.setdefault(f"{label}Folder", "")
text_html = ""
if use_sub:
text_html += f"""
"""
text_html += f"""
"""
if use_move:
text_html += f"""
"""
text_html += f"""
"""
if use_move:
text_html += f"""
"""
block[f"{title}_{label}"] = text_html
def add_previewbox(title, label, **_):
if not label.isidentifier():
raise RuntimeError(f"{title}: label must contain only valid identifier characters.")
if (label + "PreviewBox") in state:
block[f"{title}_{label}"] = f"""
"""
def add_pastebox(title, label, **_):
if not label.isidentifier():
raise RuntimeError(f"{title}: label must contain only valid identifier characters.")
block[f"{title}_{label}"] = f"""
"""
def add_logbox(title, label, rows, **_):
if not label.isidentifier():
raise RuntimeError(f"{title}: label must contain only valid identifier characters.")
if (label + "LogBox") in state:
block[f"{title}_{label}"] = f"""
"""
info = server.PromptServer.instance.prompt_queue.get_history(max_items=1, map_function=lambda p: p["prompt"][2])
nodelist = next(iter(info.values())).values() if info else []
if nodelist:
for node in nodelist:
class_type = node["class_type"]
title = node["_meta"]["title"]
inputs = node["inputs"]
if class_type == "FlipStreamSection":
add_section(title, **inputs)
if class_type == "FlipStreamButton":
add_button(title, **inputs)
if class_type == "FlipStreamSlider":
add_slider(title, **inputs)
if class_type == "FlipStreamTextBox":
add_textbox(title, **inputs)
if class_type == "FlipStreamInputBox":
add_inputbox(title, **inputs)
if class_type.startswith("FlipStreamSelectBox"):
add_selectbox(title, **inputs)
if class_type.startswith("FlipStreamSizeSelect"):
add_sizeselect(title, **inputs)
if class_type.startswith("FlipStreamFileSelect"):
add_fileselect(title, **inputs)
if class_type == "FlipStreamPreviewBox":
add_previewbox(title, **inputs)
if class_type == "FlipStreamPasteBox":
add_pastebox(title, **inputs)
if class_type == "FlipStreamLogBox":
add_logbox(title, **inputs)
text_html = f"""{HEAD}
Input
{"".join([x[1] for x in sorted(block.items())])}
Status
{state["darker"]}drk
Preset
Lora
Rate:
X
Y
S
Status
Darker
{state["darker"]}drk
Preset
{"".join([f'''
''' for file in Path("preset", state["presetFolder"]).glob("*.json")])}
"""
return web.Response(text=text_html, content_type="text/html")
@server.PromptServer.instance.routes.get("/flipstreamviewer/refresh_view")
async def get_status(request):
if request.remote not in allowed_ips:
raise HTTPForbidden()
status_elapsed = 0
status_info = []
remain = server.PromptServer.instance.prompt_queue.get_tasks_remaining()
global refresh_updating
if remain == 0:
refresh_updating = 0
elif refresh_updating == 0:
refresh_updating = time.time()
if refresh_updating:
status_elapsed = int(time.time() - refresh_updating)
status_info.append(f"{status_elapsed}s")
if "current" in state:
status_info.append(state["current"])
status_info.append(f"q{remain}")
info = server.PromptServer.instance.prompt_queue.get_history(max_items=1, map_function=lambda p: p["status"])
status = next(iter(info.values())) if info else []
if status:
status_info.append(status["status_str"])
errinfo = status["messages"][2][1]
status_info += [errinfo[key] for key in ["node_id", "node_type", "exception_message", "exception_type"] if key in errinfo]
data = refresh_data.copy()
data["param"] = refresh_param.copy()
param.update(refresh_param)
refresh_param.clear()
data["status_elapsed"] = status_elapsed
data["status_info"] = status_info
data["preview_mtime"] = {key: state[key][0] for key in state if key.endswith("PreviewBox")}
data["mtime"] = {key: state[key] for key in state if key.endswith("_mtime")}
data["log"] = {key: state[key] for key in state if key.endswith("LogBox")}
return web.json_response(data)
@server.PromptServer.instance.routes.post("/flipstreamviewer/update_param")
async def update_param(request):
if request.remote not in allowed_ips:
raise HTTPForbidden()
stt, prm = await request.json()
state.update(stt)
param.update(prm)
time.sleep(UPDATE_DELAY)
return web.Response()
@server.PromptServer.instance.routes.post("/flipstreamviewer/get_lorainfo")
async def get_lorainfo(request):
if request.remote not in allowed_ips:
raise HTTPForbidden()
req = await request.json()
loraPath = Path("ComfyUI/models/loras", state["loraMode"], req["loraFolder"], req["loraFile"])
hash = None
with open(loraPath, "rb") as file:
header_size = int.from_bytes(file.read(8), "little", signed=False)
if header_size <= 0:
raise HTTPError("get_lorainfo: Invalid header size")
header = file.read(header_size)
if header_size <= 0:
raise HTTPError("get_lorainfo: Invalid header")
file.seek(0)
hash = hashlib.sha256(file.read()).hexdigest()
header_json = json.loads(header)
lorainfo = header_json["__metadata__"] if "__metadata__" in header_json else {}
if hash:
res = requests.get("https://civitai.com/api/v1/model-versions/by-hash/" + hash).json()
lorainfo["_lorapreview_href"] = "https://civitai.com/models/" + str(res["modelId"])
lorainfo["_lorapreview_src"] = res["images"][0]["url"]
return web.json_response(lorainfo)
@server.PromptServer.instance.routes.post("/flipstreamviewer/set_frame")
async def set_frame(request):
if request.remote not in allowed_ips:
raise HTTPForbidden()
frame = base64.b64decode((await request.text()).split(',', 1)[1])
global frame_buffer
global frame_mtime
global setframe_mtime
global setframe_buffer
frame_buffer = [frame]
frame_mtime = time.time()
setframe_buffer = [frame]
setframe_mtime = frame_mtime
return web.Response()
@server.PromptServer.instance.routes.post("/flipstreamviewer/move_file")
async def move_file(request):
if request.remote not in allowed_ips:
raise HTTPForbidden()
stt, folder_path, mode, label, moveFrom, moveTo = await request.json()
state.update(stt)
pathFrom = Path(folder_path, mode, state[label + "Folder"], Path(moveFrom).name)
pathTo = Path(folder_path, mode, moveTo, pathFrom.name)
Path(pathFrom).rename(pathTo)
state[label + "Folder"] = moveTo
return web.Response()
@server.PromptServer.instance.routes.post("/flipstreamviewer/move_presetfile")
async def move_presetfile(request):
if request.remote not in allowed_ips:
raise HTTPForbidden()
stt, moveTo = await request.json()
state.update(stt)
pathFrom = Path("preset", state["presetFolder"], state["presetFile"])
pathTo = Path("preset", moveTo, pathFrom.name)
Path(pathFrom).rename(pathTo)
state["presetFolder"] = moveTo
return web.Response()
@server.PromptServer.instance.routes.post("/flipstreamviewer/move_lorafile")
async def move_lorafile(request):
if request.remote not in allowed_ips:
raise HTTPForbidden()
stt, moveTo = await request.json()
state.update(stt)
pathFrom = Path("ComfyUI/models/loras", state["loraMode"], state["loraFolder"], state["loraFile"])
pathTo = Path("ComfyUI/models/loras", state["loraMode"], moveTo, pathFrom.name)
Path(pathFrom).rename(pathTo)
state["loraFolder"] = moveTo
return web.Response()
def xor_crypt(data_str, key_str, crypt=True):
xorall = lambda data, key: bytes([b1 ^ b2 for b1, b2 in zip(data, itertools.cycle(key))])
key_bytes = key_str.encode('utf-8')
input_bytes = data_str.encode('utf-8')
if crypt:
encrypted_bytes = xorall(input_bytes, key_bytes)
return base64.b64encode(encrypted_bytes).decode('utf-8')
else:
decoded_bytes = base64.b64decode(input_bytes)
return xorall(decoded_bytes, key_bytes).decode('utf-8')
@server.PromptServer.instance.routes.post("/flipstreamviewer/load_preset")
async def load_preset(request):
if request.remote not in allowed_ips:
raise HTTPForbidden()
stt, loraPromptOnly, xorkey = await request.json()
filename = stt["presetFile"]
path = Path("preset", stt["presetFolder"], filename)
with open(path, "r") as file:
if xorkey:
buf = json.loads(xor_crypt(file.read(), xorkey + filename, False))
else:
buf = json.load(file)
if loraPromptOnly:
buf = {"lora": buf.get("lora", "")}
state.update(stt)
param.clear()
param.update(default_param)
param.update(buf)
state["presetTitle"] = Path(state["presetFile"]).stem
time.sleep(UPDATE_DELAY)
return web.json_response({"lora": param["lora"], "presetTitle": state["presetTitle"]})
@server.PromptServer.instance.routes.post("/flipstreamviewer/save_preset")
async def save_preset(request):
if request.remote not in allowed_ips:
raise HTTPForbidden()
stt, prm, xorkey = await request.json()
state.update(stt)
param.update(prm)
time.sleep(UPDATE_DELAY)
filename = state["presetTitle"] + ".json"
path = Path("preset", state["presetFolder"], filename)
with open(path, "w") as file:
if xorkey:
file.write(xor_crypt(json.dumps(param), xorkey + filename))
else:
json.dump(param, file)
return web.Response()
class FlipStreamSection:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"section": ("STRING", {"default": "Section"}),
},
"optional": {
"hook": (anytype,),
}
}
RETURN_TYPES = (anytype,)
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, hook=True, **_):
return (hook,)
class FlipStreamButton:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"run": ("BOOLEAN", {"default": True}),
"capture": ("BOOLEAN", {"default": True}),
},
"optional": {
"hook": (anytype,),
}
}
RETURN_TYPES = (anytype,)
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, hook=True, **_):
return (hook,)
class FlipStreamSlider:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"label": ("STRING", {"default": "empty"}),
"default": ("FLOAT", {"default": 0, "step": 0.01, "round": 0.01}),
"min": ("FLOAT", {"default": 0, "step": 0.01, "round": 0.01}),
"max": ("FLOAT", {"default": 0, "step": 0.01, "round": 0.01}),
"step": ("FLOAT", {"default": 0.01, "min": 0.00, "step": 0.01, "round": 0.01}),
}
}
RETURN_TYPES = ("FLOAT", "INT", "BOOLEAN")
RETURN_NAMES = ("float", "int", "enable")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
@classmethod
def IS_CHANGED(cls, label, default, **_):
param.setdefault(label, default)
return hash(param[label])
def run(self, label, default, **_):
param.setdefault(label, default)
return (float(param[label]), int(float(param[label])), bool(float(param[label])))
class FlipStreamTextBox:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"label": ("STRING", {"default": "empty"}),
"default": ("STRING", {"default": "", "multiline": True}),
"rows": ("INT", {"default": 3}),
}
}
RETURN_TYPES = ("STRING", "BOOLEAN")
RETURN_NAMES = ("text", "enable")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
@classmethod
def IS_CHANGED(cls, label, default, **_):
param.setdefault(label, btoa_utf8(default))
return hash(param[label])
def run(self, label, default, **_):
param.setdefault(label, btoa_utf8(default))
text = atob_utf8(param[label])
return (text, bool(text))
class FlipStreamInputBox:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"label": ("STRING", {"default": "empty"}),
"default": ("STRING", {"default": ""}),
"boxtype": (["text", "number", "seed", "r4d"],),
}
}
RETURN_TYPES = ("STRING", "FLOAT", "INT", "BOOLEAN")
RETURN_NAMES = ("text", "float", "int", "enable")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
@classmethod
def IS_CHANGED(cls, label, default, **_):
return hash(param.get(label))
def run(self, label, default, boxtype, **_):
param.setdefault(label, default)
text = param[label]
enable = bool(text)
try:
num = float(text)
enable = bool(num)
except:
num = 0
return (text, num, int(num), enable)
class FlipStreamSelectBox:
LISTITEMS = ["a", "b", "c"]
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"label": ("STRING", {"default": "empty"}),
"default": (s.LISTITEMS,),
"listitems": ("STRING", {"default": ",".join(s.LISTITEMS)})
},
}
RETURN_TYPES = (anytype, "BOOLEAN",)
RETURN_NAMES = ("item", "enable",)
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
@classmethod
def IS_CHANGED(cls, label, **_):
param.setdefault(label, "")
return hash(param[label])
def run(self, label, default, **_):
param.setdefault(label, "")
item = param[label] if param[label] else default
return (item, param[label] != "")
class FlipStreamSelectBox_Samplers(FlipStreamSelectBox):
LISTITEMS = comfy.samplers.KSampler.SAMPLERS
class FlipStreamSelectBox_Scheduler(FlipStreamSelectBox):
LISTITEMS = comfy.samplers.KSampler.SCHEDULERS
class FlipStreamSizeSelect:
SIZEDICT = {
"sdxl": {
"1:1": (1024, 1024),
"16:9": (1344, 768),
"9:16": (768, 1344),
"4:3": (1152, 896),
"3:4": (896, 1152),
"3:2": (1216, 832),
"2:3": (832, 1216),
},
"qwen": {
"1:1": (1328, 1328),
"16:9": (1664, 928),
"9:16": (928, 1664),
"4:3": (1472, 1104),
"3:4": (1104, 1472),
"3:2": (1584, 1056),
"2:3": (1056, 1584),
},
"wan": {
"1:1": (512, 512),
"16:9": (832, 480),
"9:16": (480, 832),
"4:3": (832, 480),
"3:4": (480, 832),
"3:2": (832, 480),
"2:3": (480, 832),
}
}
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"label": ("STRING", {"default": "empty"}),
"default": (list(s.SIZEDICT["sdxl"].keys()),),
"modeltype": (["sdxl", "qwen", "wan"],),
"listitems": ("STRING", {"default": ",".join(s.SIZEDICT["sdxl"].keys())})
},
}
RETURN_TYPES = ("INT", "INT", "BOOLEAN")
RETURN_NAMES = ("width", "height", "enable")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
@classmethod
def IS_CHANGED(cls, label, default, **_):
param.setdefault(label, default)
return hash(param[label])
def run(self, label, default, modeltype, **_):
param.setdefault(label, default)
width, height = self.SIZEDICT[modeltype][param.get(label)]
return (width, height, param[label] != "")
class FlipStreamGetSize(FlipStreamSizeSelect):
pass
class FlipStreamFileSelect:
FOLDER_NAME = ""
FOLDER_PATH = ""
@staticmethod
def get_filelist(folder_name, folder_path, mode):
if folder_name == "checkpoints" and not mode:
return CheckpointLoaderSimple.INPUT_TYPES()["required"]["ckpt_name"][0]
elif folder_name == "vae":
return VAELoader.INPUT_TYPES()["required"]["vae_name"][0]
elif folder_name == "controlnet":
return folder_paths.get_filename_list("controlnet")
elif folder_name == "tensorrt":
if folder_path.startswith("_error_"):
return [folder_path]
if TensorRTLoader is None:
return ["_error_ ComfyUI_TensorRT is not installed"]
return TensorRTLoader.INPUT_TYPES()["required"]["unet_name"][0]
elif folder_name == "animatediff_models":
if folder_path.startswith("_error_"):
return [folder_path]
return folder_paths.get_filename_list(folder_name)
else:
return [str(p.relative_to(folder_path)) for p in Path(folder_path, mode).glob("*.*")]
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"label": ("STRING", {"default": "empty"}),
"default": ([""] + s.get_filelist(s.FOLDER_NAME, s.FOLDER_PATH, ""),),
"folder_name": ([s.FOLDER_NAME],),
"folder_path": ([s.FOLDER_PATH],),
"mode": ("STRING", {"default": ""}),
"use_sub": ("BOOLEAN", {"defalut": False}),
"use_move": ("BOOLEAN", {"defalut": False}),
}
}
RETURN_TYPES = (anytype, anytype, "BOOLEAN",)
RETURN_NAMES = ("file", "path", "enable",)
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
@classmethod
def IS_CHANGED(cls, label, **_):
param.setdefault(label, "")
return hash(param[label])
def run(self, label, default, folder_path, **_):
param.setdefault(label, "")
file = param[label] if param[label] else default
return (file, str(Path(folder_path, file)), param[label] != "")
class FlipStreamFileSelect_Checkpoints(FlipStreamFileSelect):
FOLDER_NAME = "checkpoints"
FOLDER_PATH = Path(folder_paths.get_folder_paths(FOLDER_NAME)[0]).relative_to(Path.cwd()).as_posix()
class FlipStreamFileSelect_Loras(FlipStreamFileSelect):
FOLDER_NAME = "loras"
FOLDER_PATH = Path(folder_paths.get_folder_paths(FOLDER_NAME)[0]).relative_to(Path.cwd()).as_posix()
class FlipStreamFileSelect_VAE(FlipStreamFileSelect):
FOLDER_NAME = "vae"
FOLDER_PATH = Path(folder_paths.get_folder_paths(FOLDER_NAME)[0]).relative_to(Path.cwd()).as_posix()
class FlipStreamFileSelect_LLM(FlipStreamFileSelect):
FOLDER_NAME = "LLM"
FOLDER_PATH = Path(folder_paths.models_dir, FOLDER_NAME).relative_to(Path.cwd()).as_posix()
class FlipStreamFileSelect_ControlNetModel(FlipStreamFileSelect):
FOLDER_NAME = "controlnet"
FOLDER_PATH = Path(folder_paths.get_folder_paths(FOLDER_NAME)[0]).relative_to(Path.cwd()).as_posix()
class FlipStreamFileSelect_TensorRT(FlipStreamFileSelect):
FOLDER_NAME = "tensorrt"
try:
FOLDER_PATH = Path(folder_paths.get_folder_paths(FOLDER_NAME)[0]).relative_to(Path.cwd()).as_posix()
except:
FOLDER_PATH = "_error_ tensorrt folder is not found"
class FlipStreamFileSelect_AnimateDiffModel(FlipStreamFileSelect):
FOLDER_NAME = "animatediff_models"
try:
FOLDER_PATH = Path(folder_paths.get_folder_paths(FOLDER_NAME)[0]).relative_to(Path.cwd()).as_posix()
except:
FOLDER_PATH = "_error_ animatediff_models folder is not found"
class FlipStreamFileSelect_Input(FlipStreamFileSelect):
FOLDER_NAME = "input"
FOLDER_PATH = Path(folder_paths.input_directory).relative_to(Path.cwd()).as_posix()
class FlipStreamFileSelect_Output(FlipStreamFileSelect):
FOLDER_NAME = "output"
FOLDER_PATH = Path(folder_paths.output_directory).relative_to(Path.cwd()).as_posix()
class FlipStreamPreviewBox:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"label": ("STRING", {"default": "empty"}),
"tensor": ("IMAGE",),
}
}
RETURN_TYPES = ()
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, label, tensor, **_):
if tensor is None:
tensor = torch.zeros((1, 8, 8, 3))
buf = np.array(tensor[0].cpu().numpy() * 255, dtype=np.uint8)
image = Image.fromarray(buf)
image.thumbnail((256, 256))
with io.BytesIO() as output:
image.save(output, format="PNG", compress_level=STREAM_COMPRESSION)
state[label + "PreviewBox"] = (time.time(), output.getvalue())
return ()
class FlipStreamPasteBox:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"label": ("STRING", {"default": "empty"}),
}
}
RETURN_TYPES = ("IMAGE", "BOOLEAN")
RETURN_NAMES = ("image", "enable")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
@classmethod
def IS_CHANGED(cls, label):
return hash(state.get(label + "_mtime"))
def run(self, label, **_):
if label not in state:
return (None, False)
return (state[label], True)
class FlipStreamLogBox:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"label": ("STRING", {"default": "empty"}),
"log": ("STRING",),
"rows": ("INT", {"default": 3}),
}
}
RETURN_TYPES = ()
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, label, log, **_):
state[label + "LogBox"] = btoa_utf8(log)
return ()
class FlipStreamRunOnce:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
},
"optional": {
"hook": (anytype,),
}
}
RETURN_TYPES = (anytype,)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, hook=True, **_):
server.PromptServer.instance.send_sync("FlipStreamViewer_run_once", {})
global refresh_updating
refresh_updating = 0
return (hook,)
class FlipStreamSetMessage:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"message": ("STRING", {"default": "", "multiline": True}),
"fontsize": ("FLOAT", {"default": 1.0, "min": 0.5, "max": 2.0, "step": 0.1}),
},
"optional": {
"hook": (anytype,),
}
}
RETURN_TYPES = (anytype,)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, message, fontsize, hook=True, **_):
refresh_data["message"] = btoa_utf8(message)
refresh_data["message_fontsize"] = f"{fontsize}rem"
return (hook,)
class FlipStreamAnd:
@classmethod
def INPUT_TYPES(s):
return {
"required": {},
"optional": {f"v{i}": (anytype,) for i in range(10)}
}
RETURN_TYPES = (anytype,)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, **kwargs):
res = None
for res in kwargs.values():
if not res: break
return (res,)
class FlipStreamOr:
@classmethod
def INPUT_TYPES(s):
return {
"required": {},
"optional": {f"v{i}": (anytype,) for i in range(10)}
}
RETURN_TYPES = (anytype,)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, **kwargs):
res = None
for res in kwargs.values():
if res: break
return (res,)
class FlipStreamSetState:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"label": ("STRING", {"default": "empty"}),
"replace": ("BOOLEAN", {"default": False}),
"thumbnail": ("BOOLEAN", {"default": False}),
},
"optional": {
"hook": (anytype,),
"value": (anytype,),
}
}
RETURN_TYPES = (anytype,)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, label, replace, thumbnail, hook=True, value=None, **_):
if replace or label not in state:
if thumbnail and value is not None:
buf = np.array(value[0].cpu().numpy() * 255, dtype=np.uint8)
image = Image.fromarray(buf)
image.thumbnail((256, 256))
with io.BytesIO() as output:
image.save(output, format="PNG", compress_level=STREAM_COMPRESSION)
state[label + "_thumbnail"] = output.getvalue()
state[label + "_mtime"] = time.time()
else:
if label + "_thumbnail" in state:
del state[label + "_thumbnail"]
del state[label + "_mtime"]
state[label] = value
return (hook,)
class FlipStreamGetState:
CACHED_LASTGET = {}
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"label": ("STRING", {"default": "empty"}),
},
"optional": {
"hook": (anytype,),
},
"hidden": {
"unique_id": "UNIQUE_ID"
}
}
RETURN_TYPES = (anytype, "BOOLEAN", "BOOLEAN")
RETURN_NAMES = ("value", "enable", "changed")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
@classmethod
def IS_CHANGED(cls, label, **_):
value = state.get(label)
return hash(value[:1024] if hasattr(value, '__getitem__') else value)
def run(self, label, unique_id, **_):
enable = label in param
value = state.get(label)
changed = False
h = hash(str(value[:1024]) if hasattr(value, '__getitem__') else value)
cache = FlipStreamGetState.CACHED_LASTGET.setdefault(unique_id, {})
if cache.get(label, h) != h:
changed = True
cache[label] = h
return (value, enable, changed)
class FlipStreamSetParam:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"label": ("STRING", {"default": "empty"}),
"value": ("STRING", {"default": "", "multiline": True}),
"replace": ("BOOLEAN", {"default": False}),
"b64enc": ("BOOLEAN", {"default": False}),
},
"optional": {
"hook": (anytype,),
}
}
RETURN_TYPES = (anytype,)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, label, value, replace, b64enc, hook=True, **_):
empty = ""
if b64enc:
value = btoa_utf8(value)
empty = btoa_utf8(empty)
if replace or label not in param or param[label] == empty:
param[label] = value
refresh_param[label] = value
return (hook,)
class FlipStreamGetParam:
CACHED_LASTGET = {}
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"label": ("STRING", {"default": "empty"}),
"default": ("STRING", {"default": ""}),
"b64dec": ("BOOLEAN", {"default": False}),
},
"optional": {
"hook": (anytype,),
},
"hidden": {
"unique_id": "UNIQUE_ID"
}
}
RETURN_TYPES = (anytype, "FLOAT", "INT", "BOOLEAN", "BOOLEAN")
RETURN_NAMES = ("text", "float", "int", "enable", "changed")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
@classmethod
def IS_CHANGED(cls, label, **_):
return hash(param.get(label))
def __init__(self):
self.lastget = {}
def run(self, label, default, b64dec, unique_id, **_):
enable = label in param
text = param.get(label, default)
if enable and b64dec:
text = atob_utf8(text)
enable = bool(text)
try:
num = float(text)
enable = bool(num)
except:
num = 0
changed = False
h = hash(text)
cache = FlipStreamGetParam.CACHED_LASTGET.setdefault(unique_id, {})
if cache.get(label, h) != h:
changed = True
cache[label] = h
return (text, num, int(num), enable, changed)
class FlipStreamGet:
CACHED_LASTGET = {}
@classmethod
def INPUT_TYPES(s):
return {
"required": {},
"optional": {
"hook": (anytype,),
**{f"label{i}": ("STRING", {"default": ""}) for i in range(20)}
},
"hidden": {
"unique_id": "UNIQUE_ID"
}
}
RETURN_TYPES = (anytype, "BOOLEAN") + (anytype,) * 20
RETURN_NAMES = ("hook", "changed") + tuple(f"value{i}" for i in range(20))
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def get_value(self, label, unique_id):
def auto(v):
try:
return atob_utf8(v)
except:
return v
convert_map = {
"": auto,
"text": str,
"bool": bool,
"int": int,
"float": float,
"b64dec": atob_utf8,
}
convert = ""
source = param
label, _, convert = label.partition("->")
label, sep, default = label.partition("|")
if not sep: default = None
if label.startswith("state:"):
_, _, label = label.partition(":")
source = state
value = None
changed = False
if label:
value = convert_map[convert](source.get(label, default))
cache = FlipStreamGet.CACHED_LASTGET.setdefault(unique_id, {})
h = hash(str(value[:1024]) if hasattr(value, '__getitem__') else value)
if cache.get(label, h) != h:
changed = True
cache[label] = h
return value, changed
def run(self, unique_id, hook=True, **kwargs):
vlist = []
changed = False
for k in kwargs:
if k.startswith("label"):
val, c = self.get_value(kwargs[k], unique_id)
vlist.append(val)
changed |= c
return (hook, changed, *vlist)
class FlipStreamGetPreviewRoi:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"label": ("STRING", {"default": "empty"}),
"width": ("INT", {"default": 512, "min": 256, "max": 2048, "step": 32}),
"height": ("INT", {"default": 512, "min": 256, "max": 2048, "step": 32}),
"default_left": ("INT", {"default": 0}),
"default_top": ("INT", {"default": 0}),
"default_right": ("INT", {"default": 0}),
"default_bottom": ("INT", {"default": 0}),
},
}
RETURN_TYPES = ("INT", "INT", "INT", "INT", "INT", "INT")
RETURN_NAMES = ("left", "top", "right", "bottom", "inner_width", "inner_height")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
@classmethod
def IS_CHANGED(cls, label, **_):
roi_data = frozenset(param.get(label + "PreviewRoi", {}).items())
return hash(roi_data)
def run(self, label, default_left, default_top, default_right, default_bottom, width, height):
roi_data = param.get(label + "PreviewRoi", {})
left = int(roi_data['sx'] * width) if 'sx' in roi_data else default_left
top = int(roi_data['sy'] * height) if 'sy' in roi_data else default_top
right = int(width - roi_data['ex'] * width) if 'ex' in roi_data else default_right
bottom = int(height - roi_data['ey'] * height) if 'ey' in roi_data else default_bottom
inner_width = width - left - right
inner_height = height - top - bottom
return (left, top, right, bottom, inner_width, inner_height)
class FlipStreamImageSize:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"tensor": ("IMAGE",),
}
}
RETURN_TYPES = ("IMAGE","INT","INT","INT")
RETURN_NAMES = ("tensor","width","height","batchsize")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, tensor):
(batchsize, height, width) = tensor.shape[0:3]
return (tensor, width, height, batchsize)
class FlipStreamTextReplace:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"default": "", "multiline": True}),
"find": ("STRING", {"default": ""}),
"replace": ("STRING", {"default": ""}),
},
"optional": {
"value": (anytype,),
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, text, find, replace, value=None):
for word in find.split(","):
word = word.strip()
if not word:
continue
text = text.replace(word, replace.format(value))
return (text,)
class FlipStreamTextConcat:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"joinstr": ("STRING", {"default": "", "multiline": True}),
"enable": ("BOOLEAN", {"default": True})
},
"optional": {
**{f"text{i}": ("STRING", {"default": ""}) for i in range(20)}
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, joinstr, enable, **kwargs):
if not enable:
return ("",)
text = []
for k in kwargs:
if k.startswith("text"):
val = kwargs[k]
text.append(val)
return (str(joinstr).join(map(str, filter(None, text))),)
class FlipStreamGetFrame:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"index": ("INT", {"default": 0, "min": 0}),
"frames": ("INT", {"default": 1, "min": 1}),
"capture_only": ("BOOLEAN", {"default": True}),
"enable": ("BOOLEAN", {"default": True}),
}
}
RETURN_TYPES = ("IMAGE", "BOOLEAN")
RETURN_NAMES = ("image", "enable")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
@classmethod
def IS_CHANGED(cls, capture_only, **_):
if capture_only:
return setframe_mtime
else:
return frame_mtime
def run(self, index, frames, capture_only, enable, **_):
if not enable:
return (None, False)
buf = setframe_buffer if capture_only else frame_buffer
images_list = [
np.array(Image.open(io.BytesIO(buf[i])))
for i in range(index, min(index + frames, len(buf)))
]
if not images_list:
return (None, False)
image = torch.from_numpy(np.stack(images_list)).float() / 255.0
if image is not None and image.shape[3] == 4:
image = image[:,:,:,:3] * image[:,:,:,3:4]
return (image, True)
class FlipStreamScreenGrabber:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"left": ("INT", {"default": 0, "min": 0, "step": 32}),
"top": ("INT", {"default": 0, "min": 0, "step": 32}),
"width": ("INT", {"default": 512, "min": 256, "max": 2048, "step": 32}),
"height": ("INT", {"default": 512, "min": 256, "max": 2048, "step": 32}),
"frames": ("INT", {"default": 8, "min": 1, "max": 100}),
"fps": ("INT", {"default": 8, "min": 1, "max": 30}),
"delay_sec": ("FLOAT", {"default": 3, "min": 0.0, "max": 30.0, "step": 0.1}),
"enable": ("BOOLEAN", {"default": True}),
}
}
RETURN_TYPES = ("IMAGE", "BOOLEAN")
RETURN_NAMES = ("image", "enable")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def __init__(self):
self.grabber_thread = None
self.flag_stop_grabber = False
self.last_args = None
self.grabbed_frames = []
def grabber(self, area, frames, fps):
with mss.mss() as sct:
while True:
self.grabbed_frames.append(sct.grab(area)) # BGRA format
self.grabbed_frames = self.grabbed_frames[-frames:]
time.sleep(1 / fps)
if self.flag_stop_grabber:
break
def start_grabber(self, area, frames, fps):
if self.grabber_thread is None or not self.grabber_thread.is_alive():
self.flag_stop_grabber = False
self.grabber_thread = threading.Thread(target=self.grabber, args=(area, frames, fps), daemon=True)
self.grabber_thread.start()
def stop_grabber(self):
if self.grabber_thread is not None and self.grabber_thread.is_alive():
self.flag_stop_grabber = True
self.grabber_thread.join()
def __del__(self):
self.stop_grabber()
def run(self, top, left, width, height, frames, fps, delay_sec, enable):
image = None
if enable:
enable = False
area = {"left": left, "top": top, "width": width, "height": height}
if self.last_args is None or self.last_args != (area, frames, fps):
self.last_args = (area, frames, fps)
self.stop_grabber()
self.start_grabber(area, frames, fps)
time.sleep(delay_sec)
buf = self.grabbed_frames[-frames:]
if len(buf) == frames:
image = torch.tensor(np.array(buf)[:, :, :, (2, 1, 0)] / 255, dtype=torch.float32)
enable = True
else:
self.stop_grabber()
return (image, enable)
class FlipStreamVideoInput:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": ("STRING", {"default": ""}),
"first": ("INT", {"default": 0, "min": 0}),
"step": ("INT", {"default": 1, "min": 1}),
"frames": ("INT", {"default": 1, "min": 1}),
},
}
RETURN_TYPES = ("IMAGE", "BOOLEAN")
RETURN_NAMES = ("image", "enable")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, path, first, step, frames):
if not path or not Path(path).is_file():
return (torch.zeros((1, 64, 64, 3)), False)
with iio.imopen(path, "r") as file:
buf = [file.read(index=i) for i in range(first, first+(frames-1)*step+1, step)]
buf = np.stack(buf).astype(np.float32) / 255
enable = buf.shape[0] == frames
image = torch.from_numpy(buf) if enable else None
return (image, enable)
class FlipStreamSource:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"width": ("INT", {"default": 0, "min": 0, "max": 2048, "step": 32}),
"height": ("INT", {"default": 0, "min": 0, "max": 2048, "step": 32}),
"frames": ("INT", {"default": 0, "min": 0}),
"strim": ("INT", {"default": 0, "min": 0}),
"etrim": ("INT", {"default": 0, "min": 0}),
},
"optional": {
"image": ("IMAGE",),
"vae": ("VAE",),
}
}
RETURN_TYPES = ("IMAGE", "LATENT", "BOOLEAN")
RETURN_NAMES = ("image", "latent", "enable")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, width, height, frames, strim, etrim, image=None, vae=None):
latent = None
if image is not None and not torch.any(image):
image = None
if image is not None:
if image.shape[3] == 4:
image = image[:,:,:,:3] * image[:,:,:,3:4]
if height and not width:
width = int(image.shape[2] * height / image.shape[1] // 32 * 32)
if width and not height:
height = int(image.shape[1] * width / image.shape[2] // 32 * 32)
if not width and not height:
width = image.shape[2]
height = image.shape[1]
if not frames:
frames = image.shape[0]
if image is not None and image.shape[0] >= frames and frames > strim + etrim:
buf = image[strim:frames-etrim]
buf = buf.movedim(-1,1)
buf = comfy.utils.common_upscale(buf, width, height, "lanczos", "center")
image = buf.movedim(1,-1)
if vae:
latent = {"samples": vae.encode(image)}
else:
latent = {"samples": torch.zeros([frames, 4, height // 8, width // 8], device=comfy.model_management.intermediate_device())}
return (image, latent, True)
else:
if not frames:
frames = 1
if not width:
width = 64
if not height:
height = 64
image = torch.zeros([frames, height, width, 3])
latent = {"samples": torch.zeros([frames, 4, height // 8, width // 8], device=comfy.model_management.intermediate_device())}
return (image, latent, False)
class FlipStreamSwitch:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"value": (anytype,),
},
"optional": {
"value_enable": (anytype,),
"enable": ("BOOLEAN",),
}
}
RETURN_TYPES = (anytype,)
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, value=None, value_enable=None, enable=None):
if enable:
return (value_enable,)
return (value,)
class FlipStreamSwitchImage:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
},
"optional": {
"image_enable": ("IMAGE",),
"enable": ("BOOLEAN",),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, image=None, image_enable=None, enable=None):
if enable:
return (image_enable,)
return (image,)
class FlipStreamSwitchLatent:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"latent": ("LATENT",),
},
"optional": {
"latent_enable": ("LATENT",),
"enable": ("BOOLEAN",),
}
}
RETURN_TYPES = ("LATENT",)
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, latent, latent_enable=None, enable=None):
if enable:
return (latent_enable,)
return (latent,)
class FlipStreamGate:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("MODEL",),
"pos": ("CONDITIONING",),
"neg": ("CONDITIONING",),
"latent": ("LATENT",),
},
"optional": {
"a": (anytype,),
"b": (anytype,),
"c": (anytype,),
"d": (anytype,),
"e": (anytype,),
"f": (anytype,),
"g": (anytype,),
"h": (anytype,)
}
}
RETURN_TYPES = ("MODEL", "CONDITIONING", "CONDITIONING", "LATENT", anytype, anytype, anytype, anytype, anytype, anytype, anytype, anytype)
RETURN_NAMES = ("model", "pos", "neg", "latent", "a", "b", "c", "d", "e", "f", "g", "h")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, model, pos, neg, latent, a=None, b=None, c=None, d=None, e=None, f=None, g=None, h=None):
return (model, pos, neg, latent, a, b, c, d, e, f, g, h)
class FlipStreamRembg:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
},
}
RETURN_TYPES = ("IMAGE", "MASK")
FUNCTION = "run"
CATEGORY = "image"
def __init__(self):
if rembg is None:
raise RuntimeError("FlipStreamRembg: ComfyUI-Inspyrenet-Rembg must be installed to use this function.")
self.rembg = rembg.InspyrenetRembg()
def run(self, image):
img, mask = self.rembg.remove_background(image, "default")
return (img[..., :3] * img[..., 3:4], mask)
class FlipStreamSegMask:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"tensor": ("IMAGE",),
"target": ("STRING", {"default": "", "multiline": True}),
},
}
RETURN_TYPES = ("IMAGE", "MASK",)
RETURN_NAMES =("preview", "mask",)
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
model = None
processor = None
def run(self, tensor, target):
if florence2 is None:
raise RuntimeError("FlipStreamSegMask: ComfyUI-Florence2 must be installed to use this function.")
# Create mask
_, h, w = tensor.shape[0:3]
mask_image = Image.new('RGB', (w, h), 'black')
# Skip if empty target
if target.strip() == "":
mask_tensor = torch.from_numpy(np.array(mask_image).astype(np.float32) / 255.0).unsqueeze(0)
return (mask_tensor, mask_tensor[:,:,:,0])
# Download model if it not found
model_id = 'microsoft/Florence-2-large'
if FlipStreamSegMask.model is None:
model_dir = Path(folder_paths.models_dir, "LLM")
model_dir.mkdir(exist_ok=True)
model_name = model_id.rsplit('/', 1)[-1]
model_path = Path(model_dir, model_name)
if not model_path.exists():
print(f"Downloading Florence2 model to: {model_path}")
from huggingface_hub import snapshot_download
snapshot_download(repo_id=model_id, local_dir=str(model_path), local_dir_use_symlinks=False)
# Load model
if FlipStreamSegMask.model is None:
FlipStreamSegMask.model = transformers.Florence2ForConditionalGeneration.from_pretrained(
model_id,
trust_remote_code=True,
attn_implementation='sdpa',
device_map=comfy.model_management.get_torch_device(),
torch_dtype=torch.bfloat16
)
FlipStreamSegMask.processor = transformers.AutoProcessor.from_pretrained(model_id, trust_remote_code=True)
model = FlipStreamSegMask.model
processor = FlipStreamSegMask.processor
model.to(comfy.model_management.get_torch_device())
# Process prompt for each target
task_prompt = ''
image = Image.fromarray(np.clip(255. * tensor[0,:,:,:].cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
mask_draw = ImageDraw.Draw(mask_image)
for item in target.split(','):
prompt = task_prompt + item
inputs = processor(text=prompt, images=image, return_tensors="pt", do_rescale=False).to('cuda', torch.bfloat16)
generated_ids = model.generate(
input_ids=inputs["input_ids"].cuda(),
pixel_values=inputs["pixel_values"].cuda(),
max_new_tokens=1024,
early_stopping=False,
do_sample=False,
num_beams=1,
)
generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0]
parsed_answer = processor.post_process_generation(generated_text, task=task_prompt, image_size=(w, h))
# Draw mask
predictions = parsed_answer[task_prompt]
for polygons in predictions['polygons']:
for p in polygons:
p = np.array(p).reshape(-1, 2)
p = np.clip(p, [0, 0], [w - 1, h - 1])
if len(p) < 3:
print('Invalid polygon:', p)
continue
p = p.reshape(-1).tolist()
mask_draw.polygon(p, outline="white", fill="white")
# Offload model
model.to(torch.device("cpu"))
mask_tensor = torch.from_numpy(np.array(mask_image).astype(np.float32) / 255.0).unsqueeze(0)
return (mask_tensor, mask_tensor[:,:,:,0])
class FlipStreamChat:
CACHED_OUTPUT = {}
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model_file": ([path.name for path in Path(folder_paths.models_dir, "LLM").glob("*.gguf")],),
"n_ctx": ("INT", {"default": 2048, "min": 0, "max": 8192}),
"n_gpu_layers": ("INT", {"default": -1, "min": -1}),
"unload_other_models": ("BOOLEAN", {"default": False}),
"close_after_use": ("BOOLEAN", {"default": False}),
"system": ("STRING", {"default": "", "multiline": True}),
"user": ("STRING", {"default": "", "multiline": True}),
"instant": ("BOOLEAN", {"default": False}),
"max_history": ("INT", {"default": 10, "min": -1}),
"stop": ("STRING", {"default": "[,<"}),
"temperature": ("FLOAT", {"default": 0.2, "min": 0}),
"top_p": ("FLOAT", {"default": 0.95, "min": 0}),
"seed": ("INT", {"default": -1}),
"max_tokens": ("INT", {"default": 1024, "min": 0, "max": 8192}),
"presence_penalty": ("FLOAT", {"default": 0, "min": 0}),
"frequency_penalty": ("FLOAT", {"default": 0.5, "min": 0}),
"repeat_penalty": ("FLOAT", {"default": 1.0, "min": 0}),
"response_format": ("STRING", {"default": "", "multiline": True}),
"enable": ("BOOLEAN", {"default": True}),
},
"optional": {
"chat_model": ("CHAT_MODEL",),
"messages": ("MESSAGES",)
},
"hidden": {
"unique_id": "UNIQUE_ID"
}
}
RETURN_TYPES = ("CHAT_MODEL", "STRING", "MESSAGES")
RETURN_NAMES = ("chat_model", "response", "messages")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def __init__(self):
self.model = None
self.system = None
self.messages = []
def load_model(self, model_file, n_ctx, n_gpu_layers, unload_other_models):
h = hash((model_file, n_ctx, n_gpu_layers))
if self.model is None or self.model._FlipStreamChat_is_closed or self.model._FlipStreamChat_last_hash != h:
model_path = Path(folder_paths.models_dir, "LLM", model_file)
if not model_path.exists():
raise RuntimeError(f"FlipStreamChat: {model_path} not found.")
if Llama is None:
raise RuntimeError("FlipStreamChat: llama-cpp-python required.")
if unload_other_models:
try:
comfy.model_management.unload_all_models()
comfy.model_management.soft_empty_cache(True)
comfy.gc.collect()
torch.cuda.empty_cache()
torch.cuda.ipc_collect()
except:
pass
self.model = Llama(str(model_path), chat_format="llama-2", n_ctx=n_ctx, n_gpu_layers=n_gpu_layers, verbose=False)
self.model._FlipStreamChat_is_closed = False
self.model._FlipStreamChat_last_hash = h
def close_model(self):
if self.model:
self.model.close()
self.model._FlipStreamChat_is_closed = True
def chat(self, system, user, stop, messages, response_format, **kwargs):
if system != self.system:
self.system = system
messages.clear()
if system and len(messages) == 0:
messages.append(dict(role="system", content=system))
if system and messages[0]["role"] != "system":
messages[0] = dict(role="system", content=system)
if user:
messages.append(dict(role="user", content=user))
if response_format:
response_format = json.loads(response_format)
else:
response_format = None
return self.model.create_chat_completion(messages, stop=list(filter(str.strip, stop.split(","))), response_format=response_format, **kwargs)["choices"][0]["message"]
def run(self, model_file, n_ctx, n_gpu_layers, unload_other_models, close_after_use, system, user, instant, max_history, stop, enable, unique_id, chat_model=None, messages=None, **kwargs):
if chat_model is not None:
self.model = chat_model
if messages is None:
messages = self.messages
if not enable:
if close_after_use:
self.close_model()
return (self.model, None, messages)
if max_history == 0:
messages.clear()
elif max_history >= 1:
messages[:] = messages[:max_history]
self.load_model(model_file, n_ctx, n_gpu_layers, unload_other_models)
cache = FlipStreamChat.CACHED_OUTPUT
if instant:
res = self.chat(system, user, stop, messages.copy(), **kwargs)
cache[unique_id] = res["content"]
else:
res = self.chat(system, user, stop, messages, **kwargs)
cache[unique_id] = res["content"]
if res["role"] == "assistant":
messages.append(dict(role="assistant", content=cache[unique_id]))
self.messages = messages
if close_after_use:
self.close_model()
return (self.model, cache.get(unique_id), messages)
class FlipStreamParseJson:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"json_input": ("STRING", {"default": "", "multiline": True}),
"keys": ("STRING", {"default": "", "multiline": True}),
"joinstr": ("STRING", {"default": ",", "multiline": True}),
"ignore_error": ("BOOLEAN", {"default": True}),
"enable": ("BOOLEAN", {"default": True}),
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, json_input, keys, joinstr, ignore_error, enable):
if not enable:
return ("",)
value = []
try:
for key in keys.split("\n"):
value.append(json.loads(json_input, strict=False)[key.strip()])
except Exception as e:
if not ignore_error:
raise RuntimeError(f"FlipStreamParseJsonItem: Invalid JSON input: {e}: {json_input}")
return (joinstr.join(value),)
class FlipStreamChatJson(FlipStreamChat):
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model_file": ([path.name for path in Path(folder_paths.models_dir, "LLM").glob("*.gguf")],),
"unload_other_models": ("BOOLEAN", {"default": False}),
"close_after_use": ("BOOLEAN", {"default": False}),
"system": ("STRING", {"default": "", "multiline": True}),
"user": ("STRING", {"default": "", "multiline": True}),
"seed": ("INT", {"default": -1}),
"n_ctx": ("INT", {"default": 2048, "min": 0, "max": 8192}),
"enable": ("BOOLEAN", {"default": True})
},
"optional": {
"chat_model": ("CHAT_MODEL",),
**{f"label{i}": ("STRING", {"default": ""}) for i in range(20)}
},
"hidden": {
"unique_id": "UNIQUE_ID"
}
}
RETURN_TYPES = ("CHAT_MODEL", "STRING", "MESSAGES") + (anytype,) * 20
RETURN_NAMES = ("chat_model", "response", "messages") + tuple(f"value{i}" for i in range(20))
FUNCTION = "run2"
CATEGORY = "FlipStreamViewer"
def run2(self, **kwargs):
kwargs.update({
"n_gpu_layers": -1,
"instant": False,
"max_history": 0,
"stop": "",
"temperature":0.2,
"top_p": 0.95,
"max_tokens": kwargs["n_ctx"] - 512,
"presence_penalty": 0,
"frequency_penalty": 0.5,
"repeat_penalty": 1.0
})
label = {}
for k in list(kwargs):
if k.startswith('label'):
if kwargs[k]:
label[k] = kwargs[k]
del kwargs[k]
response_format = {
"type": "json_object",
"schema": {
"type": "object",
"properties": {k: {"type": "string"} for k in label.values()},
"required": list(label.values())
}
}
kwargs["response_format"] = json.dumps(response_format, ensure_ascii=False, indent=4)
chat_model, response, messages = self.run(**kwargs)
value = {f"label{i}": "" for i in range(20)}
if response:
data = json.loads(response, strict=False)
value.update({k: data[v] for k, v in label.items()})
return (chat_model, response, messages, *value.values())
class FlipStreamBatchPrompt:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"clip": ("CLIP",),
"frames": ("INT",),
}
}
RETURN_TYPES = ("CONDITIONING",)
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, prompt, clip, frames):
cond_buf = []
pooled_buf = []
buf = prompt.split("----")
prompt = buf[0].strip()
batchPrompt = buf[1].strip() if len(buf) > 1 else ""
appPrompt = buf[2].strip() if len(buf) > 2 else ""
count = len(batchPrompt.split("\n"))
batchPrompt = ",\n".join([f'"{int(frames * n / count)}":"{item.lstrip("-").strip()}"' for n, item in enumerate(batchPrompt.split("\n"))])
tokens = clip.tokenize(prompt)
cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
s = json.loads("{" + batchPrompt + "}")
for i in range(frames):
if str(i) in s:
tokens = clip.tokenize(" ".join([prompt, s[str(i)], appPrompt]).strip())
cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
cond_buf.append(cond)
pooled_buf.append(pooled)
max_length = max([t.size(1) for t in cond_buf])
cond_buf = [
NNF.pad(t, (0, 0, 0, max_length - t.size(1))) if t.size(1) < max_length else t[:, :max_length, :]
for t in cond_buf
]
return ([[torch.cat(cond_buf, dim=0), {"pooled_output":torch.cat(pooled_buf, dim=0)}]],)
class FlipStreamFilmVfi:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"frames": ("IMAGE",),
"multiplier": ("INT", {"default": 1, "min": 1, "max": 16}),
"clear_cache_after_n_frames": ("INT", {"default": 10, "min": 1, "max": 1000}),
},
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
model = None
def run(self, frames, multiplier, clear_cache_after_n_frames):
if frames.shape[0] < 2 or multiplier < 2:
return (frames,)
if film is None:
raise RuntimeError("FlipStreamFilmVfi: ComfyUI-Frame-Interpolation must be installed to use this function.")
if FlipStreamFilmVfi.model is None:
model_path = film.load_file_from_github_release("film", "film_net_fp32.pt")
model = torch.jit.load(model_path, map_location="cpu")
model.eval()
FlipStreamFilmVfi.model = model
model = FlipStreamFilmVfi.model
model = model.to(comfy.model_management.get_torch_device())
dtype = torch.float32
frames = film.preprocess_frames(frames)
number_of_frames_processed_since_last_cleared_cuda_cache = 0
output_frames = []
for frame_itr in range(len(frames) - 1):
frame_0 = frames[frame_itr:frame_itr+1].to(comfy.model_management.get_torch_device()).float()
frame_1 = frames[frame_itr+1:frame_itr+2].to(comfy.model_management.get_torch_device()).float()
relust = film.inference(model, frame_0, frame_1, multiplier - 1)
output_frames.extend([frame.detach().cpu().to(dtype=dtype) for frame in relust[:-1]])
number_of_frames_processed_since_last_cleared_cuda_cache += 1
if number_of_frames_processed_since_last_cleared_cuda_cache >= clear_cache_after_n_frames:
film.soft_empty_cache()
number_of_frames_processed_since_last_cleared_cuda_cache = 0
output_frames.append(frames[-1:].to(dtype=dtype))
output_frames = [frame.cpu() for frame in output_frames]
out = torch.cat(output_frames, dim=0)
film.soft_empty_cache()
model.to(torch.device("cpu"))
return (film.postprocess_frames(out),)
class FlipStreamViewer:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"tensor": ("IMAGE",),
"allowip": ("STRING", {"default": ""}),
"idle": ("FLOAT", {"default": 1.0, "min": 0.0}),
"fps": ("INT", {"default": 16, "min": 1, "max": 30}),
"loramode": ("STRING", {"default": ""}),
"pingpong": ("BOOLEAN", {"default": True}),
},
}
@classmethod
def IS_CHANGED(cls, allowip, idle, loramode, **_):
global allowed_ips
allowed_ips = ["127.0.0.1"] + list(map(str.strip, allowip.split(",")))
state["loraMode"] = loramode
time.sleep(idle)
return None
RETURN_TYPES = ()
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, allowip, tensor, fps, loramode, pingpong, **_):
global allowed_ips
allowed_ips = ["127.0.0.1"] + list(map(str.strip, allowip.split(",")))
state["loraMode"] = loramode
fb = []
buf = (tensor.detach().cpu().numpy() * 255).astype(np.uint8)
if tensor.shape[0] != 1 and pingpong:
buf = np.concatenate([buf, np.flip(buf, axis=0)])
for image in buf:
with io.BytesIO() as output:
img = Image.fromarray(image)
img.save(output, format="PNG", compress_level=STREAM_COMPRESSION)
fb.append(output.getvalue())
global frame_buffer
global frame_mtime
global frame_fps
frame_buffer = fb
frame_mtime = time.time()
frame_fps = fps
return ()
class FlipStreamViewerSimple:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"tensor": ("IMAGE",),
"idle": ("FLOAT", {"default": 0, "min": 0.0}),
"fps": ("INT", {"default": 16, "min": 1, "max": 30}),
"pingpong": ("BOOLEAN", {"default": True}),
},
}
@classmethod
def IS_CHANGED(cls, idle, **_):
time.sleep(idle)
return None
RETURN_TYPES = ()
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, tensor, fps, pingpong, **_):
fb = []
buf = (tensor.detach().cpu().numpy() * 255).astype(np.uint8)
if tensor.shape[0] != 1 and pingpong:
buf = np.concatenate([buf, np.flip(buf, axis=0)])
for image in buf:
with io.BytesIO() as output:
img = Image.fromarray(image)
img.save(output, format="PNG", compress_level=STREAM_COMPRESSION)
fb.append(output.getvalue())
global frame_buffer
global frame_mtime
global frame_fps
frame_buffer = fb
frame_mtime = time.time()
frame_fps = fps
return ()
class FlipStreamAllowIp:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"allowip": ("STRING", {"default": ""}),
},
"optional": {
"hook": (anytype,),
},
}
RETURN_TYPES = (anytype,)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, allowip, hook=True, **_):
global allowed_ips
allowed_ips = ["127.0.0.1"] + list(map(str.strip, allowip.split(",")))
return (hook,)
class FlipStreamCurrent:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"current": ("STRING", {"default": ""}),
},
"optional": {
"hook": (anytype,),
},
}
RETURN_TYPES = (anytype,)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, current, hook=True, **_):
state["current"] = current
return (hook,)
class FlipStreamLoraMode:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"loraMode": ("STRING", {"default": ""}),
},
"optional": {
"hook": (anytype,),
},
}
RETURN_TYPES = (anytype,)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, loraMode, hook=True, **_):
state["loraMode"] = loraMode
return (hook,)
class FlipStreamLoadLora:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("MODEL",),
"clip": ("CLIP",),
"mode": ("STRING", {"default": ""}),
},
"optional": {
"hook": (anytype,),
},
}
RETURN_TYPES = ("MODEL", "CLIP", "STRING")
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
@classmethod
def IS_CHANGED(cls, mode, **_):
text = re.sub(rf"@(\w+)\{{(.*?)\}}", lambda m: m.group(2) if m.group(1) == mode else "", atob_utf8(param.get("lora", "")), flags=re.S)
return hash(text.strip())
def run(self, model, clip, mode, hook=True):
text = re.sub(rf"@(\w+)\{{(.*?)\}}", lambda m: m.group(2) if m.group(1) == mode else "", atob_utf8(param.get("lora", "")), flags=re.S)
def apply(m):
nonlocal model, clip
name, w = m.group(1), float(m.group(2))
for file in folder_paths.get_filename_list("loras"):
if Path(file).name.startswith(name):
if path := folder_paths.get_full_path("loras", file):
model, clip = comfy.sd.load_lora_for_models(model, clip, comfy.utils.load_torch_file(path), w, w)
print(f"FlipStreamLoadLora: loaded: {name} -> {path}")
break
else:
print(f"FlipStreamLoadLora: LoRA not found: {name}")
return ""
text = re.sub(r"\", apply, text).strip()
return (model, clip, text)
class FlipStreamSaveApiWorkflow:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"savepath": ("STRING", {"default": ""}),
"override": ("BOOLEAN", {"default": False}),
},
"optional": {
"hook": (anytype,),
},
"hidden": {
"prompt": "PROMPT"
}
}
RETURN_TYPES = (anytype,)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, savepath, override, hook=True, prompt=None):
p = Path(savepath)
if prompt and (override or not p.exists()):
p.write_text(json.dumps(prompt))
print(f"FlipStreamSaveApiWorkflow: saved {savepath}")
return (hook,)
class FlipStreamRunApiWorkflow:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"path": ("STRING", {"default": ""}),
"enable": ("BOOLEAN", {"default": True}),
},
"optional": {
"hook": (anytype,),
}
}
RETURN_TYPES = (anytype,)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, enable, path, hook=None):
if enable:
prompt = json.loads(Path(path).read_text())
print(f"FlipStreamRunApiWorkflow: queue {path}")
requests.post(f"http://127.0.0.1:{server.PromptServer.instance.port}/prompt", json={"prompt": prompt})
return (hook,)
class FlipStreamFree:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"unload_models": ("BOOLEAN", {"default": True}),
"free_memory": ("BOOLEAN", {"default": True}),
},
"optional": {
"hook": (anytype,),
}
}
RETURN_TYPES = (anytype,)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, unload_models, free_memory, hook=None):
requests.post(f"http://127.0.0.1:{server.PromptServer.instance.port}/free", json={"unload_models": unload_models, "free_memory": free_memory})
return (hook,)
class FlipStreamShutdown:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"minutes": ("INT", {"default": 30, "min": 1}),
},
"optional": {
"hook": (anytype,),
}
}
RETURN_TYPES = (anytype,)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "FlipStreamViewer"
def run(self, minutes, hook=True, **_):
subprocess.run(["shutdown", "/a"], stderr=subprocess.DEVNULL)
subprocess.run(["shutdown", "/s", "/t", str(minutes * 60)])
return (hook,)
WEB_DIRECTORY = "./web"
NODE_CLASS_MAPPINGS = {
"FlipStreamSection": FlipStreamSection,
"FlipStreamButton": FlipStreamButton,
"FlipStreamSlider": FlipStreamSlider,
"FlipStreamTextBox": FlipStreamTextBox,
"FlipStreamInputBox": FlipStreamInputBox,
"FlipStreamSelectBox_Samplers": FlipStreamSelectBox_Samplers,
"FlipStreamSelectBox_Scheduler": FlipStreamSelectBox_Scheduler,
"FlipStreamSizeSelect": FlipStreamSizeSelect,
"FlipStreamGetSize": FlipStreamGetSize,
"FlipStreamFileSelect_Checkpoints": FlipStreamFileSelect_Checkpoints,
"FlipStreamFileSelect_Loras": FlipStreamFileSelect_Loras,
"FlipStreamFileSelect_VAE": FlipStreamFileSelect_VAE,
"FlipStreamFileSelect_LLM": FlipStreamFileSelect_LLM,
"FlipStreamFileSelect_ControlNetModel": FlipStreamFileSelect_ControlNetModel,
"FlipStreamFileSelect_TensorRT": FlipStreamFileSelect_TensorRT,
"FlipStreamFileSelect_AnimateDiffModel": FlipStreamFileSelect_AnimateDiffModel,
"FlipStreamFileSelect_Input": FlipStreamFileSelect_Input,
"FlipStreamFileSelect_Output": FlipStreamFileSelect_Output,
"FlipStreamPreviewBox": FlipStreamPreviewBox,
"FlipStreamPasteBox": FlipStreamPasteBox,
"FlipStreamLogBox": FlipStreamLogBox,
"FlipStreamRunOnce": FlipStreamRunOnce,
"FlipStreamSetMessage": FlipStreamSetMessage,
"FlipStreamAnd": FlipStreamAnd,
"FlipStreamOr": FlipStreamOr,
"FlipStreamSetState": FlipStreamSetState,
"FlipStreamGetState": FlipStreamGetState,
"FlipStreamSetParam": FlipStreamSetParam,
"FlipStreamGetParam": FlipStreamGetParam,
"FlipStreamGet": FlipStreamGet,
"FlipStreamGetFrame": FlipStreamGetFrame,
"FlipStreamGetPreviewRoi": FlipStreamGetPreviewRoi,
"FlipStreamImageSize": FlipStreamImageSize,
"FlipStreamTextReplace": FlipStreamTextReplace,
"FlipStreamTextConcat": FlipStreamTextConcat,
"FlipStreamScreenGrabber": FlipStreamScreenGrabber,
"FlipStreamVideoInput": FlipStreamVideoInput,
"FlipStreamSource": FlipStreamSource,
"FlipStreamSwitch": FlipStreamSwitch,
"FlipStreamSwitchImage": FlipStreamSwitchImage,
"FlipStreamSwitchLatent": FlipStreamSwitchLatent,
"FlipStreamGate": FlipStreamGate,
"FlipStreamRembg": FlipStreamRembg,
"FlipStreamSegMask": FlipStreamSegMask,
"FlipStreamChat": FlipStreamChat,
"FlipStreamParseJson": FlipStreamParseJson,
"FlipStreamChatJson": FlipStreamChatJson,
"FlipStreamBatchPrompt": FlipStreamBatchPrompt,
"FlipStreamFilmVfi": FlipStreamFilmVfi,
"FlipStreamViewer": FlipStreamViewer,
"FlipStreamViewerSimple": FlipStreamViewerSimple,
"FlipStreamAllowIp": FlipStreamAllowIp,
"FlipStreamCurrent": FlipStreamCurrent,
"FlipStreamLoraMode": FlipStreamLoraMode,
"FlipStreamLoadLora": FlipStreamLoadLora,
"FlipStreamSaveApiWorkflow": FlipStreamSaveApiWorkflow,
"FlipStreamRunApiWorkflow": FlipStreamRunApiWorkflow,
"FlipStreamFree": FlipStreamFree,
"FlipStreamShutdown": FlipStreamShutdown,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"FlipStreamSection": "FlipStreamSection",
"FlipStreamButton": "FlipStreamButton",
"FlipStreamSlider": "FlipStreamSlider",
"FlipStreamTextBox": "FlipStreamTextBox",
"FlipStreamInputBox": "FlipStreamInputBox",
"FlipStreamSelectBox_Samplers": "FlipStreamSelectBox_Samplers",
"FlipStreamSelectBox_Scheduler": "FlipStreamSelectBox_Scheduler",
"FlipStreamSizeSelect": "FlipStreamSizeSelect",
"FlipStreamGetSize": "FlipStreamGetSize",
"FlipStreamFileSelect_Checkpoints": "FlipStreamFileSelect_Checkpoints",
"FlipStreamFileSelect_Loras": "FlipStreamFileSelect_Loras",
"FlipStreamFileSelect_VAE": "FlipStreamFileSelect_VAE",
"FlipStreamFileSelect_LLM": "FlipStreamFileSelect_LLM",
"FlipStreamFileSelect_ControlNetModel": "FlipStreamFileSelect_ControlNetModel",
"FlipStreamFileSelect_TensorRT": "FlipStreamFileSelect_TensorRT",
"FlipStreamFileSelect_AnimateDiffModel": "FlipStreamFileSelect_AnimateDiffModel",
"FlipStreamFileSelect_Input": "FlipStreamFileSelect_Input",
"FlipStreamFileSelect_Output": "FlipStreamFileSelect_Output",
"FlipStreamPreviewBox": "FlipStreamPreviewBox",
"FlipStreamPasteBox": "FlipStreamPasteBox",
"FlipStreamLogBox": "FlipStreamLogBox",
"FlipStreamRunOnce": "FlipStreamRunOnce",
"FlipStreamSetMessage": "FlipStreamSetMessage",
"FlipStreamAnd": "FlipStreamAnd(experimental)",
"FlipStreamOr": "FlipStreamOr(experimental)",
"FlipStreamSetState": "FlipStreamSetState",
"FlipStreamGetState": "FlipStreamGetState",
"FlipStreamSetParam": "FlipStreamSetParam",
"FlipStreamGetParam": "FlipStreamGetParam",
"FlipStreamGet": "FlipStreamGet(experimental)",
"FlipStreamGetFrame": "FlipStreamGetFrame",
"FlipStreamGetPreviewRoi": "FlipStreamGetPreviewRoi",
"FlipStreamImageSize": "FlipStreamImageSize",
"FlipStreamTextReplace": "FlipStreamTextReplace",
"FlipStreamTextConcat": "FlipStreamTextConcat",
"FlipStreamScreenGrabber": "FlipStreamScreenGrabber",
"FlipStreamVideoInput": "FlipStreamVideoInput",
"FlipStreamSource": "FlipStreamSource",
"FlipStreamSwitch": "FlipStreamSwitch",
"FlipStreamSwitchImage": "FlipStreamSwitchImage",
"FlipStreamSwitchLatent": "FlipStreamSwitchLatent",
"FlipStreamGate": "FlipStreamGate(deprecated)",
"FlipStreamRembg": "FlipStreamRembg",
"FlipStreamSegMask": "FlipStreamSegMask(deprecated)",
"FlipStreamChat": "FlipStreamChat",
"FlipStreamParseJson": "FlipStreamParseJson",
"FlipStreamChatJson": "FlipStreamChatJson(experimental)",
"FlipStreamBatchPrompt": "FlipStreamBatchPrompt",
"FlipStreamFilmVfi": "FlipStreamFilmVfi",
"FlipStreamViewer": "FlipStreamViewer",
"FlipStreamViewerSimple": "FlipStreamViewerSimple",
"FlipStreamAllowIp": "FlipStreamAllowIp",
"FlipStreamCurrent": "FlipStreamCurrent",
"FlipStreamLoraMode": "FlipStreamLoraMode",
"FlipStreamLoadLora": "FlipStreamLoadLora",
"FlipStreamSaveApiWorkflow": "FlipStreamSaveApiWorkflow",
"FlipStreamRunApiWorkflow": "FlipStreamRunApiWorkflow",
"FlipStreamFree": "FlipStreamFree",
"FlipStreamShutdown": "FlipStreamShutdown",
}