Version: 1.2.0 License: GPL-3.0
A general-purpose calculation node that evaluates a safe Python-like script subset and returns multiple text and numeric outputs based on the inputs.
This custom node accepts numbers (int/float) and text (string) as inputs, runs calculations and logical operations internally, and returns multiple values through separate output ports. It consolidates workflows that previously required combining several basic nodes into a single node.
- Navigate to the
ComfyUI/custom_nodes/directory. - Clone this repository:
git clone https://github.com/kantan-kanto/ComfyUI-ScriptFlow
- Restart ComfyUI.
- Multiple inputs: freely combine numeric and text inputs.
- Calculations and logic: supports arithmetic, comparison, and simple conditional branching.
- Multiple outputs: pass results to downstream nodes via separate output ports.
- Safer execution: AST interpreter with restricted syntax and no file/OS access.
- In Wan2.2 I2V workflows, automatically select a recommended resolution (512×384 or 384×512) based on whether the input image is portrait or landscape.
- Batch-generate file names (e.g., local LLM prompt history, output logs).
- Apply conditional logic to control workflow behavior (e.g., switch parameters or routes based on inputs).
- Parse and transform LLM outputs into structured values for downstream nodes. Dynamically generate numeric parameters (e.g., seeds, thresholds, scaling values) using custom calculations.
Category: utils
Inputs (optional, connectable):
in_text_1(accepts any input; validated asstrat runtime)in_text_2(accepts any input; validated asstrat runtime)in_text_3(accepts any input; validated asstrat runtime)in_value_1(accepts any input; validated asintorfloatat runtime)in_value_2(accepts any input; validated asintorfloatat runtime)in_value_3(accepts any input; validated asintorfloatat runtime)code(multiline string)
Outputs:
out_text_1(STRING)out_text_2(STRING)out_text_3(STRING)out_value_1(INT)out_value_2(INT)out_value_3(INT)
Notes on numeric outputs:
- Outputs are
INT. - If you need
FLOATvalues from integer inputs, use thecentinode. - If a value is a float, the fractional part is truncated on output.
- If you prefer rounding or ceiling, handle it in the script (e.g.,
round(...),math.ceil(...)).
Use the following variables inside code:
- Inputs:
it1,it2,it3,iv1,iv2,iv3 - Outputs:
ot1,ot2,ot3,ov1,ov2,ov3
Unassigned outputs default to None.
# Example: keep landscape at 512x384, portrait at 384x512
# Connect GetImageSize width/height to in_value_1/in_value_2
# iv1: image width, iv2: image height
w, h = iv1, iv2
ov1, ov2 = (512, 384) if w >= h else (384, 512)Category: utils
Inputs (optional, connectable):
int_1(INT)int_2(INT)int_3(INT)
Outputs:
float_1(FLOAT) =int_1 / 100float_2(FLOAT) =int_2 / 100float_3(FLOAT) =int_3 / 100
Notes:
- If
int_nis not connected, the correspondingfloat_noutput isNone. - The node is intentionally minimal and connector-only.
The script is parsed with Python AST and evaluated by ScriptFlow's safe interpreter. It supports common workflow logic such as assignment, arithmetic, branching, loops, user-defined helper functions, dictionaries, lists, strings, and selected methods.
len,min,max,sum,abs,roundint,float,str,boolsorted,reversedenumerate,range,zipany,all,pow,divmodlist,dict,tuple
random:random,randint,uniform,choicedatetime:datetime.now,date.today, date/time fields such asyear,month,day,hour,minute,second, plusisoformatandstrftime(strftimerequires v1.2.0 or later)math:ceil,floor,sqrt,sin,cos,tan,asin,acos,atan,atan2,log,log10,exp,pow,fabs,isfinite,pi,e,tau
str:replace,find,strip,split,splitlines,startswith,endswith,join,lower,upperlist:append
importstatements- file access (
open, etc.) - OS operations (
os,sys) - dynamic Python execution
- blocked syntax in safe mode:
Import,ImportFrom,Global,Nonlocal,ClassDef,Try,With,AsyncWith,Lambda,Delete,Yield,Await - blocked calls in safe mode: dynamic execution, file/input access, runtime namespace inspection, and dynamic attribute mutation
- Using
randomordatetimemakes outputs non-deterministic. datetime.strftime(...)is supported in v1.2.0 or later for concise timestamp formatting:now = datetime.datetime.now() ot1 = now.strftime("%Y%m%d%H%M%S")
- For versions earlier than v1.2.0, format the same timestamp manually:
now = datetime.datetime.now() ot1 = f'{now.year:04d}{now.month:02d}{now.day:02d}{now.hour:02d}{now.minute:02d}{now.second:02d}'
- Type mismatches raise an error and stop execution.
- Safe mode validates AST before evaluation and stops scripts that exceed the step or loop limit.
- Use only trusted scripts/workflows. Do not run untrusted code from unknown sources.
Demonstrates running the sample workflow with a landscape image and a portrait image.
ComfyUI-LLM-Session returns the full result of LLM Dialogue Cycle as transcript_text.
You can connect that text to MultiOutputScript.in_text_1 and paste the script below into
the code field to generate a speaker-tagged JSON text for downstream dialogue TTS nodes.
Recommended workflow:
LLM Dialogue Cycle
-> transcript_text
-> MultiOutputScript.in_text_1
-> MultiOutputScript.out_text_1
-> dialogue_segments_json input of a TTS node
Paste this code into MultiOutputScript.code:
# Convert ComfyUI-LLM-Session "LLM Dialogue Cycle" transcript_text
# into dialogue_segments_json for TTS.
# Only text inside Japanese corner quotes 「...」 is used as spoken dialogue.
#
# Input:
# it1: transcript_text
#
# Output:
# ot1: JSON text with schema "kantan.dialogue.v1"
#
# Expected transcript lines:
# [2026-05-16T12:00:00] USER -> A: initial prompt
# [2026-05-16T12:00:01] A: 彼女は少し考えた。「こんにちは。」
# [2026-05-16T12:00:02] B: 彼は笑った。「やあ。」「調子はどう?」
#
# The parser also accepts the unicode arrow form:
# USER → A:
def json_escape(value):
s = str(value)
s = s.replace("\\", "\\\\")
s = s.replace('"', '\\"')
s = s.replace("\n", "\\n")
s = s.replace("\r", "\\r")
s = s.replace("\t", "\\t")
return s
def extract_spoken_text(value):
source = str(value)
parts = []
position = 0
while position < len(source):
start = source.find("「", position)
if start < 0:
position = len(source)
else:
end = source.find("」", start + 1)
if end < 0:
position = len(source)
else:
spoken = source[start + 1:end].strip()
if spoken:
parts.append(spoken)
position = end + 1
return "\n".join(parts)
transcript = str(it1 or "")
lines = transcript.splitlines()
utterances = []
current = None
for raw_line in lines:
line = str(raw_line)
speaker = None
text = None
timestamp = ""
if line.startswith("[") and "] " in line:
close_index = line.find("] ")
timestamp = line[1:close_index]
rest = line[close_index + 2:]
if rest.startswith("A:"):
speaker = "A"
text = rest[2:].strip()
elif rest.startswith("B:"):
speaker = "B"
text = rest[2:].strip()
elif rest.startswith("USER -> A:"):
speaker = "USER"
text = rest[len("USER -> A:"):].strip()
elif rest.startswith("USER → A:"):
speaker = "USER"
text = rest[len("USER → A:"):].strip()
if speaker == "A" or speaker == "B":
if current is not None:
utterances.append(current)
current = {
"speaker": speaker,
"text": text,
"timestamp": timestamp,
}
else:
if current is not None:
if current["text"]:
current["text"] = current["text"] + "\n" + line
else:
current["text"] = line
if current is not None:
utterances.append(current)
items = []
index = 1
for u in utterances:
spoken_text = extract_spoken_text(u["text"])
if not spoken_text:
continue
item = (
'{"index":'
+ str(index)
+ ',"speaker":"'
+ json_escape(u["speaker"])
+ '","text":"'
+ json_escape(spoken_text)
+ '","timestamp":"'
+ json_escape(u["timestamp"])
+ '"}'
)
items.append(item)
index = index + 1
ot1 = (
'{"schema":"kantan.dialogue.v1",'
+ '"source":"ComfyUI-LLM-Session LLM Dialogue Cycle",'
+ '"utterances":['
+ ",".join(items)
+ "]}"
)The resulting out_text_1 is a JSON string like this:
{
"schema": "kantan.dialogue.v1",
"source": "ComfyUI-LLM-Session LLM Dialogue Cycle",
"utterances": [
{
"index": 1,
"speaker": "A",
"text": "こんにちは。",
"timestamp": "2026-05-16T12:00:01"
},
{
"index": 2,
"speaker": "B",
"text": "やあ。\n調子はどう?",
"timestamp": "2026-05-16T12:00:02"
}
]
}Notes:
USER -> A/USER → Ais intentionally skipped so TTS reads only character dialogue.- Multiline model output is kept inside the previous
AorButterance. - Only text inside
「...」is emitted toutterances[].text; narration and inner thoughts outside quotes are skipped. - If one utterance contains multiple quoted lines, they are joined with newlines.
- Utterances without
「...」are omitted from the TTS script. out_text_2,out_text_3, and numeric outputs are unused by this recipe.
examples/
├─ example_workflow.json
This project is licensed under the GNU General Public License v3.0.
Copyright (C) 2026 kantan-kanto
GitHub: https://github.com/kantan-kanto
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
- Issues: Report bugs or request features via GitHub Issues
- Examples: Check examples/ for workflow templates
- Replaced direct Python execution with a safe AST interpreter for ComfyUI Registry compatibility.
- Added Python-subset support for helper functions, loops, dictionaries, lists, string methods, f-strings,
random,datetime, andmath. - Replaced trace-based timeout handling with step and loop limits.
- Kept existing
MultiOutputScriptinputs and outputs unchanged.
- Added safe-mode AST validation before script execution.
- Blocked unsafe syntax (
Import,ImportFrom,Global,Nonlocal,ClassDef,Try,With,AsyncWith). - Blocked unsafe dynamic execution, file/input access, namespace inspection, and dynamic attribute mutation calls.
- Added execution timeout guard (
1.5sdefault) to stop runaway scripts. - Updated security notes for trusted-workflow usage.
Added centi node (utils).
- Minimal connector-only utility node.
- Three optional integer inputs:
int_1,int_2,int_3. - Three float outputs:
float_1,float_2,float_3. - Each connected input is converted by
int_n / 100; unconnected inputs returnNone.
- Improved documentation and project metadata.
- Initial release
