Extensions/ComfyUI-StringKit
ComfyUI Extension

ComfyUI-StringKit

String utility nodes for ComfyUI: attach titles to prompts, loop them through your workflow, assemble save paths, and derive a distinct seed per image.

By luku756·Created 18 days ago·Updated 14 days ago· 0
luku756/ComfyUI-StringKit
Nodes
On cloudLocal install
Stars0
Updated14 days ago
Readme

ComfyUI-StringKit

ComfyUI용 문자열 처리 커스텀 노드 팩입니다. 문자열을 병합하고, 분리하고, 리스트로 묶어 다운스트림 노드를 반복 실행시키며, 저장용 경로를 조립합니다.

노드

모든 노드는 노드 검색창의 StringKit 카테고리 아래에 있습니다.

| 노드 | 역할 | |---|---| | Titled Text | 제목이 달린 텍스트를 만듭니다. 프롬프트 입력의 출발점 | | Unpack Text | 실려 온 titleprompt 를 두 출력으로 꺼냅니다 | | Split Text | 구분자로 문자열을 나눠 여러 출력으로 분기합니다 | | Join Texts | 여러 문자열을 구분자로 이어 붙입니다 | | Build Path | 여러 조각을 / 로 이어 파일 경로를 만듭니다 | | Loop Texts | 여러 텍스트를 묶어 다운스트림을 반복 실행시킵니다 | | Seed List | 반복되는 원소마다 서로 다른 시드를 만듭니다 |

Titled Text

제목이 달린 텍스트를 만듭니다. 프롬프트를 입력하는 출발점 노드입니다. titleprompt한 줄로 함께 실어 보냅니다.

| 구분 | 이름 | 타입 | 설명 | |---|---|---|---| | 위젯 | title | STRING (한 줄) | 짧은 제목. 폴더명 등으로 씁니다 | | 위젯 | prompt | STRING (여러 줄) | 본문 | | 입력 | append_prompt | STRING | prompt 뒤에 이어 붙일 말. 링크로만 받습니다 | | 출력 | titled_text | STRING | 값은 prompt 그 자체. title 이 함께 딸려 갑니다 |

출력은 평범한 STRING 이라 어떤 노드의 STRING 입력에나 그대로 꽂힙니다. 받는 쪽에서는 그냥 프롬프트 문자열로 보이고, Unpack Texttitle 을 꺼낼 수 있습니다.

titleprompt 사이에는 구분자를 쓰지 않습니다. 둘을 문자열로 합쳤다 쪼개지 않기 때문에, 어느 쪽에 %/, 든 넣어도 깨지지 않습니다.

⚠️ 이 값은 StringKit 노드 사이에서만 title 을 유지합니다. 중간에 다른 노드팩의 문자열 노드를 끼우면 문자열 연산 결과가 평범한 문자열이 되어 에러 없이 title 만 조용히 사라집니다. Unpack Texttitle 이 비어서 나오면 이 경우를 의심하세요.

append_prompt

여러 Titled Text 가 공유하는 말을 한 곳에서 관리할 때 씁니다. 연결하지 않으면 아무것도 달라지지 않습니다.

prompt         = "1girl"
append_prompt  = "smile"   → "1girl, smile"
  • Loop Textsgeneral_prompt같은 규칙으로 , (쉼표+공백)으로 잇습니다. 붙는 위치만 반대입니다. general_prompt, append_prompt입니다.
  • 앞뒤 공백·개행을 정리한 뒤 붙입니다. 비었거나 공백뿐이면 아무것도 붙지 않습니다.
  • prompt이미 쉼표로 끝나면 쉼표를 겹치지 않고 공백만 넣습니다. "1girl," + "smile""1girl, smile"
  • prompt 가 비어 있으면 append_prompt 만 남습니다. 앞에 구분자가 붙지 않습니다.
  • 딸려 온 title 은 쓰지 않습니다. 제목은 이 노드의 title 만 남습니다.

Unpack Text

Titled Text 가 실어 보낸 titleprompt 를 두 출력으로 꺼냅니다.

| 구분 | 이름 | 타입 | 설명 | |---|---|---|---| | 입력 | titled_text | STRING | Titled Text 계열에서 온 값 | | 출력 | title | STRING | 실려 있지 않으면 빈 문자열 | | 출력 | prompt | STRING | 본문 |

Split Text

구분자로 문자열을 나눠 여러 출력으로 분기하는 범용 노드입니다.

| 구분 | 이름 | 타입 | 설명 | |---|---|---|---| | 입력 | text | STRING | 나눌 문자열 | | 입력 | delimiter | STRING | 구분자. 기본 %. \n \t \r \\ 이스케이프 표기를 씁니다 | | 입력 | max_parts | INT (1~5) | 최대 몇 조각까지 자를지 | | 입력 | strip_whitespace | BOOLEAN | 각 조각의 앞뒤 공백 제거 (기본 켜짐) | | 출력 | part_1part_5 | STRING | 조각. 모자란 칸은 빈 문자열 |

max_parts 가 핵심입니다. 이 개수를 넘는 구분자는 자르지 않고 마지막 조각에 그대로 남깁니다. 본문에 구분자와 같은 문자가 들어 있어도 유실되지 않습니다.

"sunset%a 50% cloudy sky"  를  delimiter="%", max_parts=2 로 분리

part_1 = "sunset"
part_2 = "a 50% cloudy sky"   ← 본문의 % 가 살아 있음
part_3..5 = ""

max_parts 를 5로 올리면 part_1="sunset", part_2="a 50", part_3="cloudy sky" 로 쪼개집니다. 제목과 본문만 나누려면 2로 두세요.

구분자를 빈 문자열로 두면 나누지 않고 part_1 에 통째로 담습니다.

Join Texts

여러 문자열을 구분자로 이어 붙여 하나로 내보냅니다.

| 구분 | 이름 | 타입 | 설명 | |---|---|---|---| | 입력 | input_size | INT (1~50) | 만들어 둘 text_* 소켓 개수 | | 입력 | delimiter | STRING | 구분자. 기본 \n(개행). 이스케이프 표기를 씁니다 | | 입력 | skip_empty | BOOLEAN | 비었거나 공백뿐인 입력을 건너뜀 (기본 켜짐) | | 입력 | text_1text_N | STRING | 이어 붙일 문자열 | | 출력 | joined_text | STRING | 병합 결과 |

input_size 를 바꾸면 text_* 소켓이 따라서 생기고 사라집니다. 연결하지 않은 슬롯은 건너뛰므로 중간이 비어도 구분자가 겹치지 않습니다.

skip_empty 를 끄면 빈 입력도 자리를 차지해 구분자가 연달아 나옵니다. 자리를 맞춰야 할 때 쓰세요.

구분자를 빈 문자열로 두면 아무것도 끼우지 않고 그대로 붙입니다.

Build Path

여러 조각을 / 로 이어 파일 경로를 만듭니다. 저장 경로를 조립하는 전용 노드입니다.

| 구분 | 이름 | 타입 | 설명 | |---|---|---|---| | 위젯 | segment_size | INT (1~10) | 보여 줄 조각 칸 수 | | 위젯 | fix_invalid_chars | BOOLEAN | 파일 시스템이 거부하는 문자를 정리 (기본 켜짐) | | 위젯 | segment_1segment_N | STRING | 경로 조각. 링크로도 받습니다 | | 출력 | path | STRING | / 로 이어진 경로 |

조각은 노드에서 직접 입력할 수도, 링크를 끌어다 놓을 수도 있습니다. 날짜 토큰이나 FD 같은 고정 폴더명에 별도 텍스트 노드를 두지 않아도 됩니다.

segment_size 를 바꾸면 남는 칸이 접히고 노드가 짧아집니다.

segment_1 = %year%-%month%-%day%
segment_2 = 치어리더
segment_3 = 웃음
segment_4 = %year%-%month%-%day%_

→ 2026-08-02/치어리더/웃음/2026-08-02_

날짜 토큰

%year% %month% %day% %hour% %minute% %second%이 노드가 직접 바꿉니다. 날짜용 노드를 따로 두지 않아도 되고, 미리보기 노드에도 실제 날짜가 보입니다.

한 번 실행에서 모든 조각이 같은 시각을 씁니다. 자정이나 초가 넘어가는 순간에 조각마다 다른 값이 섞이지 않습니다. 날짜가 바뀌면 노드가 다시 실행됩니다 (IS_CHANGED) — 이게 없으면 결과가 캐시되어 자정을 넘겨도 어제 날짜로 저장됩니다.

ComfyUI 의 %date:yyyy-MM-dd% 는 지원하지 않습니다. 그 형식은 프론트엔드가 위젯 값에만 적용해서, 링크로 넘어온 문자열에는 확장되지 않습니다. %year% 계열을 쓰세요.

빈 조각과 금지문자

빈 조각과 연결하지 않은 슬롯은 건너뜁니다. 중간이 비어도 // 가 생기지 않아, 조각을 선택적으로 넣을 수 있습니다.

fix_invalid_chars 가 켜져 있으면 각 조각에서 다음을 정리합니다.

| 대상 | 처리 | |---|---| | \ / : * ? " < > \| 와 제어문자 | _ 로 치환 | | 끝의 점·공백 | 제거 (윈도우가 조용히 잘라내 이름이 어긋납니다) | | CON PRN AUX NUL COM1~LPT9 | 앞에 _ 를 붙여 회피 |

프롬프트 제목에 (smile:1.1) 처럼 콜론이 들어가도 경로가 깨지지 않습니다. fix_invalid_chars 를 끄면 조각 안의 / 가 살아나 한 조각으로 여러 단계를 만들 수 있습니다.

Loop Texts

연결된 여러 텍스트 입력을 하나의 리스트로 묶어, 다운스트림 노드를 원소 개수만큼 반복 실행시킵니다.

| 구분 | 이름 | 타입 | 설명 | |---|---|---|---| | 입력 | input_size | INT (1~50) | 만들어 둘 text_* 소켓 개수 | | 위젯 | general_title | STRING (한 줄) | 모든 원소의 제목 앞에 공통으로 붙일 말 | | 위젯 | general_prompt | STRING (여러 줄) | 모든 원소의 프롬프트 앞에 공통으로 붙일 말 | | 입력 | text_1text_N | STRING | 반복시킬 텍스트. 다른 Loop Texts 의 출력도 받습니다 | | 위젯 | enable_1enable_N | on/off | off 하면 해당 슬롯을 연결되지 않은 것으로 취급 | | 출력 | (STRING 리스트) | STRING | 원소 개수만큼 다운스트림이 반복 실행됨 |

input_size 값을 올리거나 내리면 text_* 소켓과 enable_* 토글이 함께 생기고 사라집니다.

공통 제목·프롬프트

그룹마다 Loop Texts 를 하나씩 쓰고 있다면, 그 그룹에 공통으로 붙일 말을 여기서 한 번에 관리할 수 있습니다. general_title제목 앞, general_prompt프롬프트 앞에 붙습니다. 서로 간섭하지 않습니다.

general_title  = "해변알바"
general_prompt = "masterpiece"
text_1 = [("감정-기본", "p1"), ("감정-웃음", "p2")]

→ ("해변알바 감정-기본", "masterpiece, p1")
  ("해변알바 감정-웃음", "masterpiece, p2")

구분자가 다릅니다.

| | 구분자 | 이유 | |---|---|---| | general_title | `` (띄어쓰기 하나) | 제목은 폴더명으로 쓰입니다 | | general_prompt | , (쉼표+공백) | 프롬프트 관례 |

  • 앞뒤 공백·개행을 정리한 뒤 붙입니다. 비었거나 공백뿐이면 아무것도 붙지 않습니다. 연결하지 않은 경우도 같습니다.
  • general_prompt이미 쉼표로 끝나면 쉼표를 겹치지 않고 공백만 넣습니다. "masterpiece,""masterpiece, p1"
  • 원소에 제목이 없으면 general_title 만 남습니다. 끝에 구분자가 붙지 않습니다.
  • Loop Texts 를 중첩하면 누적됩니다. 바깥쪽이 앞에 붙어 전체 그룹 본문 / 바깥, 안쪽, 본문 순서가 됩니다.

라운드 로빈 동작

연결된 슬롯들을 각각 하나의 열(column) 로 보고 행 우선(round-robin) 으로 순회합니다. 스칼라 하나만 연결하면 길이 1짜리 열이 됩니다.

열 길이가 각각 4 / 6 / 2 일 때 출력 순서는 다음과 같습니다.

1-1, 2-1, 3-1   ← 1행
1-2, 2-2, 3-2   ← 2행
1-3, 2-3        ← 3열이 소진되어 건너뜀
1-4, 2-4
     2-5
     2-6

소진된 열은 건너뛰므로 총 출력 개수는 sum(길이) = 4+6+2 = 12개 입니다. 항 × max(길이) (= 3 × 6 = 18) 가 아닙니다.

행 우선으로 도는 이유는, 스칼라만 연결하던 사용법에서의 출력 순서(text_1, text_2, text_3 …)를 그대로 유지하기 위해서입니다.

on/off 토글

각 입력에는 enable_N 토글이 짝지어 붙습니다. off 하면 링크를 끊지 않은 채로 그 슬롯을 통째로 건너뜁니다. off 된 소켓은 회색으로 바뀌고 라벨이 text_N (off) 가 됩니다. 링크를 유지한 채 일부만 빼고 돌려 보고 싶을 때 쓰면 됩니다.

토글은 기본적으로 접혀 있습니다. 입력이 50개까지 늘어나면 노드가 그만큼 길어져서입니다. 노드의 ▶ on/off 펼치기 버튼으로 폈다 접었다 합니다. 접힘 상태는 워크플로에 저장됩니다. 접어 두어도 enable_* 값은 그대로 유지되고 실행에도 정상 반영됩니다.

접어 둔 상태에서도 어느 슬롯이 off 인지는 입력 소켓을 보면 됩니다 — 회색 + text_N (off) 라벨이 그대로 남습니다.

Seed List

Loop Texts 로 여러 장을 뽑을 때, 원소마다 다른 시드를 만들어 줍니다. 같은 시드로 수십 장을 뽑으면 비슷한 장면끼리 그림이 지나치게 닮는 문제를 해결합니다.

| 구분 | 이름 | 타입 | 설명 | |---|---|---|---| | 입력 | text | STRING | 시드의 재료. Unpack Texttitle 을 꽂습니다 | | 위젯 | mode | COMBO | 시드를 어떻게 만들지 | | 위젯 | seed | INT | 기준 시드. Seed (rgthree) 등을 연결합니다 | | 출력 | seed | INT (리스트) | 원소마다 하나씩. 샘플러의 seed 로 | | 출력 | seed_list | STRING | 목록 한 덩어리. ShowText 로 확인용 |

| mode | 동작 | |---|---| | seed+title | 기준 시드와 제목을 섞습니다 (기본값) | | seed+loop index | 기준 시드와 자리 번호를 섞습니다 | | only seed | 전부 기준 시드를 그대로 씁니다 |

재현

시드는 결정적으로 만들어집니다. 같은 기준 시드에 같은 제목이면 언제나 같은 값입니다. 그래서 기준 시드 하나만 이미지 메타데이터에 남아 있으면 나머지가 전부 계산으로 되살아납니다.

Seed (rgthree) 처럼 실행 시점에 값이 확정되어 메타데이터에 기록되는 노드를 연결하세요. 백엔드에서 만든 값은 메타데이터에 남지 않으므로, 이 노드가 직접 난수를 뽑지 않고 기준 시드를 받아 쓰는 이유입니다.

seed+title 을 기본으로 둔 이유

seed+loop indexenable_* 토글을 껐다 켜거나 슬롯 순서를 바꾸면 자리 번호가 밀려 이전에 뽑은 그림들의 시드가 전부 달라집니다. 제목 기준이면 순서가 바뀌어도 감정-기본 은 늘 같은 시드입니다.

대신 제목을 고치면 그 항목의 시드는 바뀝니다. 오타를 고쳐도 그렇습니다.

같은 제목이 두 번 나오면 두 번째부터 번호를 붙여 갈라 줍니다. 첫 번째는 그대로라 기존 결과가 흔들리지 않습니다.

연결

Seed (rgthree) ─┬─→ (기존 배선 유지 — base 를 메타데이터에 기록)
                └─→ Seed List.seed

Loop Texts ─→ Unpack Text ─title─→ Seed List.text
                           └prompt─→ (프롬프트 쪽으로)

                     Seed List ─(INT 리스트)─→ 샘플러의 seed
                               └─(STRING)───→ ShowText

Loop Texts 의 출력을 Seed List.text바로 꽂아도 됩니다. 값에 실려 온 title 을 꺼내 쓰므로 결과가 같습니다. 다만 중간에 다른 노드팩을 거쳐 title 이 사라지면 프롬프트 본문이 재료가 되어 시드가 달라지므로, Unpack Text 를 거치는 쪽이 확실합니다.

KSampler (Efficient) 💬ED 를 쓰신다면 seed 는 소켓이 아니라 위젯이라 평소엔 연결점이 안 보입니다. INT 출력을 노드 위로 끌고 가면 나타납니다. set_seed_cfg_samplerfrom node to ctx 로 두어야 이 시드가 하류까지 전달됩니다.

저장소 구조

ComfyUI-StringKit/
├── __init__.py          # ComfyUI 진입점 — 노드 ID·표시명 매핑만 담당
├── src/                 # 노드 구현
│   ├── common.py        # TextContext, 구분자 이스케이프 해석 등 공용 헬퍼
│   ├── titled_text.py
│   ├── unpack_text.py
│   ├── split_text.py
│   ├── join_texts.py
│   ├── build_path.py
│   ├── loop_texts.py
│   └── seed_list.py
└── web/js/              # 프론트엔드 확장 (동적 소켓 UI)
    ├── join_texts.js
    ├── build_path.js
    └── loop_texts.js

루트 __init__.py 는 옮길 수 없습니다. ComfyUI 가 custom_nodes/ComfyUI-StringKit 디렉터리 자체를 파이썬 패키지로 import 하기 때문입니다. WEB_DIRECTORY 도 이 파일 기준 경로입니다.

설치

ComfyUI-Manager

Manager → Custom Nodes Manager 에서 ComfyUI-StringKit 을 검색해 설치합니다.

git clone

cd ComfyUI/custom_nodes
git clone https://github.com/luku756/ComfyUI-StringKit

설치 후 ComfyUI를 완전히 재시작하세요. 핫 리로드로는 프론트엔드(JS) 변경이 반영되지 않습니다.

배포 (관리자용)

pyproject.tomlversion 을 올려 master 에 push 하면 GitHub Actions 가 ComfyUI Registry 에 자동으로 게시합니다.

version = "1.0.1"   # 올리지 않으면 실패합니다. Registry 는 같은 버전을 두 번 받지 않습니다.

Actions 탭에서 Publish to Comfy Registry 를 수동 실행할 수도 있습니다.

준비물

저장소 Settings → Secrets and variables → Actions 에 아래 이름으로 등록합니다.

| 이름 | 값 | |---|---| | REGISTRY_ACCESS_TOKEN | registry.comfy.org 에서 발급한 API 키 (pat-...) |

게시됐는지 확인하기

Actions 가 성공해도 바로 설치할 수 있는 게 아닙니다. 단계가 셋입니다.

① Actions 성공        업로드 완료          1~2분
② 버전 심사 통과       Pending -> Active    수 분 ~ 하루
③ Manager 목록 반영    캐시 갱신            추가 시간

① Actions 탭 — 초록색이면 zip 업로드까지 끝난 것입니다.

https://github.com/luku756/ComfyUI-StringKit/actions

② 버전 심사 — 여기가 잘 걸리는 지점입니다. Registry 는 새 버전에 보안 스캔을 돌리고, 그동안 상태가 Pending 입니다. Pending 인 동안에는 Manager 에서 설치할 수 없습니다.

curl https://api.comfy.org/nodes/comfyui-stringkit/versions

| status | 뜻 | |---|---| | NodeVersionStatusPending | 심사 중. 기다리면 됩니다 | | NodeVersionStatusActive | 통과. 설치 가능 |

노드 쪽도 같이 보면 확실합니다.

curl https://api.comfy.org/nodes/comfyui-stringkit

statusNodeStatusActive 인데 latest_versionnull 이면, 노드 등록은 됐지만 쓸 수 있는 버전이 아직 없다는 뜻입니다. 대개 ② 가 안 끝난 경우입니다.

③ Registry 웹페이지와 Manager

https://registry.comfy.org/nodes/comfyui-stringkit

Manager 는 Registry 를 주기적으로 받아 캐시합니다. API 에서 Active 인데 Manager 에 안 보이면 실패가 아니라 아직 안 받아온 것입니다. Manager 에서 캐시를 새로고침해 보세요.

기다리는 동안에도 git clone 설치는 됩니다.

자주 나는 실패

| 증상 | 원인 | |---|---| | Actions 는 성공인데 Manager 에 안 보임 | 위 ② 심사가 안 끝났습니다. versionsstatus 를 보세요 | | latest_versionnull | 같은 원인입니다. 버전이 아직 Pending 입니다 | | No such option: --yes | 액션이나 스크립트가 낡아 최신 comfy-cli 에 없는 옵션을 씁니다 | | 인증 오류 | Secret 이름이 다르거나, 키를 폐기했거나, 값이 비었습니다 | | 버전 충돌 | pyproject.tomlversion 을 안 올렸습니다 |

로컬에서 직접 게시할 수도 있습니다.

pip install comfy-cli
comfy node publish        # 토큰을 물어봅니다

라이선스

MIT