Initial commit

This commit is contained in:
unknown
2025-12-06 15:16:30 +01:00
commit 7faf9951ac
9 changed files with 575 additions and 0 deletions
+317
View File
@@ -0,0 +1,317 @@
import re
# Separatorer för intern kodning i pair_data
CLIP_SEP = "\n<<<MULTICLIP_CLIP_SEP>>>\n"
META_SEP = "\n<<<MULTICLIP_META_SEP>>>\n"
class MultiClipTextScriptMain:
"""
Multi-Clip Text Script: Main
Tar text från t.ex. Ollama eller någon annan källa + konstant prefix/suffix:
prefix_text:
Beskrivning som alltid ska vara med (personen, scenen, stil, etc).
multi_clip_script (exempel):
(clip01)
woman waves at the camera
(clip02)
woman gets angry
(clip03)
woman calls someone
suffix_text:
Text som läggs till efter varje klipp (t.ex. kamera-stil, kvalitet, osv).
negative_text:
Negativ prompt som är gemensam för alla klipp.
Scriptet parsas i formatet:
(clip01)
<text>
(clip02)
<text>
...
Och omvandlas till ett internt paket (MULTICLIP_PAIR_DATA) där vi sparar:
- prefix
- suffix
- alla clip-texter i ordning
Sedan kan "Multi-Clip Text Script: Clip Selector" plocka ut
en clip-text i taget och bygga en färdig prompt.
Output:
pair_data (MULTICLIP_PAIR_DATA) – intern sträng med all data
num_clips (INT) – hur många klipp som hittades
multi_clip_negative (MULTICLIP_NEGATIVE) – negativ prompt för alla klipp
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
# 1. Prefix först
"prefix_text": (
"STRING",
{
"multiline": True,
"default": "",
},
),
# 2. Huvudfönstret för (clip01)(clip02)...-scriptet
"multi_clip_script": (
"STRING",
{
"multiline": True,
"default": "(clip01)\nwoman walks forward and waves to the camera\n\n"
"(clip02)\nwoman walks forward and gets angry\n\n"
"(clip03)\nwoman throws the phone",
},
),
# 3. Suffix sist
"suffix_text": (
"STRING",
{
"multiline": True,
"default": "",
},
),
},
"optional": {
"negative_text": (
"STRING",
{
"multiline": True,
"default": "",
},
),
},
}
RETURN_TYPES = ("MULTICLIP_PAIR_DATA", "INT", "MULTICLIP_NEGATIVE")
RETURN_NAMES = ("pair_data", "num_clips", "multi_clip_negative")
FUNCTION = "parse_script"
CATEGORY = "Text / Multi-Clip"
def parse_script(
self,
prefix_text: str,
multi_clip_script: str,
suffix_text: str,
negative_text: str = "",
):
# Hitta alla (clipXX) + text fram till nästa (clipYY) eller slutet
pattern = r"\(clip(\d+)\)\s*(.*?)(?=\n\(clip\d+\)|\Z)"
matches = re.findall(pattern, multi_clip_script, flags=re.DOTALL | re.IGNORECASE)
clips = []
for idx, text in matches:
text = text.strip()
if text:
clips.append((int(idx), text)) # "01" -> 1 osv, för sortering
# Sortera efter clip-nummer
clips.sort(key=lambda x: x[0])
prefix_clean = (prefix_text or "").strip()
suffix_clean = (suffix_text or "").strip()
negative_clean = (negative_text or "").strip()
if not clips:
# Inga klipp hittade – koda ändå prefix/suffix men utan clips
payload = META_SEP.join([prefix_clean, suffix_clean, ""])
return (payload, 0, negative_clean)
# Själva klipp-texterna i ordning
clip_texts = [t for _, t in clips]
clips_blob = CLIP_SEP.join(clip_texts)
# Kodning i pair_data:
# prefix <<META_SEP>> suffix <<META_SEP>> clip1 <<CLIP_SEP>> clip2 <<CLIP_SEP>> ...
payload = META_SEP.join([prefix_clean, suffix_clean, clips_blob])
num_clips = len(clip_texts)
return (payload, num_clips, negative_clean)
class MultiClipTextScriptClipSelector:
"""
Multi-Clip Text Script: Clip Selector
Tar emot pair_data från MultiClipTextScriptMain (typ MULTICLIP_PAIR_DATA)
och väljer EN specifik klipp-text baserat på clip_number (1-baserat).
clip_number (1-baserat i UI):
1 -> första klipp-texten (clip01)
2 -> andra (clip02)
3 -> tredje (clip03)
osv.
auto_format:
- False: prefix + tomrad + clip + tomrad + suffix
- True: "prefix, clip, suffix." (klassisk prompt-stil)
+ rensar bort extra kommatecken i slutet av varje del.
negative:
- Input av typen MULTICLIP_NEGATIVE (ingen egen text-ruta i denna nod).
Typisk kedja:
Multi-Clip Text Script: Main (multi_clip_negative)
→ Anything Everywhere
→ Multi-Clip Text Script: Clip Selector (negative)
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"pair_data": ("MULTICLIP_PAIR_DATA", {}),
"clip_number": (
"INT",
{
"default": 1,
"min": 1,
"max": 999,
"step": 1,
},
),
},
"optional": {
"auto_format": (
"BOOLEAN",
{
"default": False,
},
),
"log_to_console": (
"BOOLEAN",
{
"default": False,
},
),
"negative": (
"MULTICLIP_NEGATIVE",
{},
),
},
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("clip_text", "negative_text")
FUNCTION = "select_pair"
CATEGORY = "Text / Multi-Clip"
def select_pair(
self,
pair_data: str,
clip_number: int,
auto_format: bool = False,
log_to_console: bool = False,
negative: str = "",
):
if not pair_data:
return ("", negative)
# Förväntat format:
# prefix <<META_SEP>> suffix <<META_SEP>> clips_blob
parts = pair_data.split(META_SEP)
if len(parts) == 3:
prefix, suffix, clips_blob = parts
elif len(parts) == 2:
prefix, clips_blob = parts
suffix = ""
else:
prefix = ""
suffix = ""
clips_blob = parts[0] if parts else ""
clips_blob = clips_blob or ""
clips = clips_blob.split(CLIP_SEP) if clips_blob else []
internal_index = clip_number - 1
if not clips:
combined = self._combine(prefix, "", suffix, auto_format)
if log_to_console and combined:
print("[Multi-Clip Selector] (no clips) →", combined)
return (combined, negative)
if internal_index < 0:
internal_index = 0
if internal_index >= len(clips):
internal_index = len(clips) - 1
clip_raw = clips[internal_index]
combined = self._combine(prefix, clip_raw, suffix, auto_format)
if log_to_console and combined:
print(f"[Multi-Clip Selector] clip_number={clip_number} →", combined)
return (combined, negative)
@staticmethod
def _clean_piece(piece: str) -> str:
if not piece:
return ""
p = piece.strip()
while p and p[-1] in [",", ";", " "]:
p = p[:-1]
return p.strip()
@staticmethod
def _combine(prefix: str, clip: str, suffix: str, auto_format: bool) -> str:
prefix = prefix or ""
clip = clip or ""
suffix = suffix or ""
if not auto_format:
parts = []
if prefix.strip():
parts.append(prefix.strip())
if clip.strip():
parts.append(clip.strip())
if suffix.strip():
parts.append(suffix.strip())
return "\n\n".join(parts)
prefix_clean = MultiClipTextScriptClipSelector._clean_piece(prefix)
clip_clean = MultiClipTextScriptClipSelector._clean_piece(clip)
suffix_clean = MultiClipTextScriptClipSelector._clean_piece(suffix)
parts = []
if prefix_clean:
parts.append(prefix_clean)
if clip_clean:
parts.append(clip_clean)
if suffix_clean:
parts.append(suffix_clean)
if not parts:
return ""
text = ", ".join(parts)
text = text.strip()
if text and text[-1] not in [".", "!", "?"]:
text += "."
return text
# Registrering för ComfyUI
NODE_CLASS_MAPPINGS = {
"MultiClipTextScriptMain": MultiClipTextScriptMain,
"MultiClipTextScriptClipSelector": MultiClipTextScriptClipSelector,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"MultiClipTextScriptMain": "Multi-Clip Text Script: Main",
"MultiClipTextScriptClipSelector": "Multi-Clip Text Script: Clip Selector",
}