From 8b2bdf547546c4c95f2deb722d9fe8ff67e31ff0 Mon Sep 17 00:00:00 2001
From: smthem <2102561739@qq.com>
Date: Fri, 27 Sep 2024 10:01:31 +0800
Subject: [PATCH] add new 1
---
PuLID/app_flux.py | 27 +-
PuLID/flux/modules/conditioner.py | 64 +-
PuLID/flux/sampling.py | 12 +-
PuLID/flux/util.py | 86 ++-
README.md | 163 +++--
.../attention_processor.py | 0
.../attention_processor_faceid.py | 0
.../{ip_adapter => ip_adapter_}/ip_adapter.py | 2 +-
.../ip_adapter_faceid.py | 2 +-
.../resampler_.py} | 0
.../{ip_adapter => ip_adapter_}/utils.py | 0
StoryMaker/pipeline_sdxl_storymaker.py | 31 +-
Storydiffusion_node.py | 687 ++++++++++++------
examples/flux.png | Bin 0 -> 319946 bytes
examples/flux_img2img2role.png | Bin 431780 -> 0 bytes
examples/flux_pulid_fp8_12GVR.png | Bin 461856 -> 0 bytes
examples/kolor.png | Bin 0 -> 404772 bytes
examples/kolor_faceid.png | Bin 416863 -> 0 bytes
examples/kolor_ipadapter.png | Bin 413338 -> 0 bytes
examples/kolor_ipadapter_use_chinese.png | Bin 429012 -> 0 bytes
examples/kolor_using_comfyclip.png | Bin 443693 -> 0 bytes
examples/kolor_using_configeclip.png | Bin 404816 -> 0 bytes
examples/maker2role.png | Bin 0 -> 553308 bytes
examples/new_0922.json | 240 ------
examples/newest.png | Bin 525873 -> 0 bytes
examples/photomakev1.png | Bin 348575 -> 0 bytes
examples/photomakev2.png | Bin 357012 -> 0 bytes
examples/phtomakev2.png | Bin 0 -> 437265 bytes
examples/pulid_flux.png | Bin 452509 -> 0 bytes
examples/using_storymake_dual.png | Bin 528946 -> 0 bytes
examples/using_storymake_dual_only.png | Bin 518234 -> 0 bytes
examples/using_storymake_dual_onlyA.png | Bin 414827 -> 0 bytes
pyproject.toml | 4 +-
utils/gradio_utils.py | 13 +-
utils/pipeline.py | 3 +-
utils/style_template.py | 20 +-
36 files changed, 698 insertions(+), 656 deletions(-)
rename StoryMaker/{ip_adapter => ip_adapter_}/attention_processor.py (100%)
rename StoryMaker/{ip_adapter => ip_adapter_}/attention_processor_faceid.py (100%)
rename StoryMaker/{ip_adapter => ip_adapter_}/ip_adapter.py (99%)
rename StoryMaker/{ip_adapter => ip_adapter_}/ip_adapter_faceid.py (99%)
rename StoryMaker/{ip_adapter/resampler.py => ip_adapter_/resampler_.py} (100%)
rename StoryMaker/{ip_adapter => ip_adapter_}/utils.py (100%)
create mode 100644 examples/flux.png
delete mode 100644 examples/flux_img2img2role.png
delete mode 100644 examples/flux_pulid_fp8_12GVR.png
create mode 100644 examples/kolor.png
delete mode 100644 examples/kolor_faceid.png
delete mode 100644 examples/kolor_ipadapter.png
delete mode 100644 examples/kolor_ipadapter_use_chinese.png
delete mode 100644 examples/kolor_using_comfyclip.png
delete mode 100644 examples/kolor_using_configeclip.png
create mode 100644 examples/maker2role.png
delete mode 100644 examples/new_0922.json
delete mode 100644 examples/newest.png
delete mode 100644 examples/photomakev1.png
delete mode 100644 examples/photomakev2.png
create mode 100644 examples/phtomakev2.png
delete mode 100644 examples/pulid_flux.png
delete mode 100644 examples/using_storymake_dual.png
delete mode 100644 examples/using_storymake_dual_only.png
delete mode 100644 examples/using_storymake_dual_onlyA.png
diff --git a/PuLID/app_flux.py b/PuLID/app_flux.py
index 8a9f6f2..294ca0c 100644
--- a/PuLID/app_flux.py
+++ b/PuLID/app_flux.py
@@ -8,29 +8,31 @@ from .flux.util import load_ae, load_clip, load_flow_model, load_t5, load_flow_m
from .pulid.pipeline_flux import PuLIDPipeline
-def get_models(name: str,ckpt_path, device: torch.device, offload: bool,quantized_mode):
- t5 = load_t5(name,quantized_mode,device, max_length=128)
- clip = load_clip(name,quantized_mode,device)
+def get_models(name: str,ckpt_path,vae_cf, clip_cf,device: torch.device, offload: bool,quantized_mode):
+ t5 = load_t5(name,clip_cf,quantized_mode,device, max_length=128)
+ clip = load_clip(name,clip_cf,quantized_mode,device)
if quantized_mode=="fp8" or quantized_mode=="nf4":
model = load_flow_model_quintized(name, ckpt_path,quantized_mode,device="cpu" if offload else device)
else:
model = load_flow_model(name, ckpt_path,device="cpu" if offload else device)
model.eval()
- ae = load_ae(name, device="cpu" if offload else device)
+ ae = load_ae(name,vae_cf, device="cpu" if offload else device)
return model, ae, t5, clip
class FluxGenerator:
- def __init__(self, model_name: str, ckpt_path,device: str, offload: bool,aggressive_offload: bool, pretrained_model,quantized_mode,clip_vision_path):
+ def __init__(self, model_name: str, ckpt_path,device: str, offload: bool,aggressive_offload: bool, pretrained_model,quantized_mode,clip_vision_path,clip_cf,vae_cf):
self.device = torch.device(device)
self.offload = offload
self.model_name = model_name
self.aggressive_offload= aggressive_offload
self.ckpt_path=ckpt_path
self.clip_vision_path=clip_vision_path
+ self.clip_cf=clip_cf
+ self.vae_cf=vae_cf
self.quantized_mode=quantized_mode
self.model, self.ae, self.t5, self.clip = get_models(
- model_name,self.ckpt_path,
+ model_name,self.ckpt_path,self.vae_cf,self.clip_cf,
device=self.device,
offload=self.offload,quantized_mode=self.quantized_mode,
)
@@ -55,7 +57,7 @@ class FluxGenerator:
timestep_to_start_cfg=1,
max_sequence_length=128,
):
- self.t5.max_length = max_sequence_length
+ #self.t5.max_length = max_sequence_length
seed = int(seed)
if seed == -1:
@@ -69,9 +71,9 @@ class FluxGenerator:
raise "prompt must be list or str"
img_output = []
- for i,prompt in enumerate(prompt_list):
+ for i in prompt_list:
opts = SamplingOptions(
- prompt=prompt,
+ text=i,
width=width,
height=height,
num_steps=num_steps,
@@ -81,7 +83,8 @@ class FluxGenerator:
if opts.seed is None:
opts.seed = torch.Generator(device="cpu").seed()
- print(f"Generating '{opts.prompt}' with seed {opts.seed}")
+ opts.text = opts.text if isinstance(opts.text,str) else str(opts.text)
+ print(f"Generating '{opts.text}' with seed {opts.seed}")
t0 = time.perf_counter()
use_true_cfg = abs(true_cfg - 1.0) > 1e-2
@@ -103,7 +106,7 @@ class FluxGenerator:
if self.offload:
self.t5, self.clip = self.t5.to(self.device), self.clip.to(self.device)
- inp = prepare(t5=self.t5, clip=self.clip, img=x, prompt=opts.prompt)
+ inp = prepare(t5=self.t5, clip=self.clip, img=x, prompt=opts.text)
inp_neg = prepare(t5=self.t5, clip=self.clip, img=x, prompt=neg_prompt) if use_true_cfg else None
# offload TEs to CPU, load model to gpu
@@ -156,4 +159,4 @@ class FluxGenerator:
img = Image.fromarray((127.5 * (x + 1.0)).cpu().byte().numpy())
# return img, str(opts.seed), self.pulid_model.debug_img_list
img_output.append(img)
- return img_output
+ return img_output
\ No newline at end of file
diff --git a/PuLID/flux/modules/conditioner.py b/PuLID/flux/modules/conditioner.py
index 234ecec..d56c56a 100644
--- a/PuLID/flux/modules/conditioner.py
+++ b/PuLID/flux/modules/conditioner.py
@@ -3,41 +3,47 @@ from transformers import CLIPTextModel, CLIPTokenizer, T5EncoderModel, T5Tokeniz
class HFEmbedder(nn.Module):
- def __init__(self, version: str, tokenizer_path,is_clip,quantized_mode, max_length: int, **hf_kwargs):
+ def __init__(self, version: str, tokenizer_path,clip_cf,is_clip,quantized_mode, max_length: int, **hf_kwargs):
super().__init__()
#self.is_clip = version.startswith("openai")
self.is_clip=is_clip
self.quantized_mode=quantized_mode
+ self.clip_cf=clip_cf
self.max_length = max_length
- self.output_key = "pooler_output" if self.is_clip else "last_hidden_state"
+ #self.output_key = "pooler_output" if self.is_clip else "last_hidden_state"
+ self.output_key = "pooled_output" if self.is_clip else "cond"
- if self.is_clip:
- self.tokenizer: CLIPTokenizer = CLIPTokenizer.from_pretrained(tokenizer_path, max_length=max_length)
- self.hf_module: CLIPTextModel = CLIPTextModel.from_pretrained(version, **hf_kwargs)
- else:
- self.tokenizer: T5Tokenizer = T5Tokenizer.from_pretrained(tokenizer_path, max_length=max_length)
- self.hf_module: T5EncoderModel = T5EncoderModel.from_pretrained(version, **hf_kwargs)
- if self.quantized_mode=="fp8":
- from optimum.quanto import freeze, qfloat8, quantize
- quantize(self.hf_module, weights=qfloat8)
- freeze(self.hf_module)
-
- self.hf_module = self.hf_module.eval().requires_grad_(False)
+ # if self.is_clip:
+ # self.tokenizer: CLIPTokenizer = CLIPTokenizer.from_pretrained(tokenizer_path, max_length=max_length)
+ # self.hf_module: CLIPTextModel = CLIPTextModel.from_pretrained(version, **hf_kwargs)
+ # else:
+ # self.tokenizer: T5Tokenizer = T5Tokenizer.from_pretrained(tokenizer_path, max_length=max_length)
+ # self.hf_module: T5EncoderModel = T5EncoderModel.from_pretrained(version, **hf_kwargs)
+ # if self.quantized_mode=="fp8":
+ # from optimum.quanto import freeze, qfloat8, quantize
+ # quantize(self.hf_module, weights=qfloat8)
+ # freeze(self.hf_module)
+ #
+ # self.hf_module = self.hf_module.eval().requires_grad_(False)
def forward(self, text: list[str]) -> Tensor:
- batch_encoding = self.tokenizer(
- text,
- truncation=True,
- max_length=self.max_length,
- return_length=False,
- return_overflowing_tokens=False,
- padding="max_length",
- return_tensors="pt",
- )
+ # batch_encoding = self.tokenizer(
+ # text,
+ # truncation=True,
+ # max_length=self.max_length,
+ # return_length=False,
+ # return_overflowing_tokens=False,
+ # padding="max_length",
+ # return_tensors="pt",
+ # )
- outputs = self.hf_module(
- input_ids=batch_encoding["input_ids"].to(self.hf_module.device),
- attention_mask=None,
- output_hidden_states=False,
- )
- return outputs[self.output_key]
+ # outputs = self.hf_module(
+ # input_ids=batch_encoding["input_ids"].to(self.hf_module.device),
+ # attention_mask=None,
+ # output_hidden_states=False,
+ # )
+ tokens = self.clip_cf.tokenize(text)
+ outputs = self.clip_cf.encode_from_tokens(tokens, return_pooled=True, return_dict=True)
+ # cond = outputs.pop("cond")
+ # pooled_output=outputs.pop("pooled_output")
+ return outputs.pop(self.output_key)
diff --git a/PuLID/flux/sampling.py b/PuLID/flux/sampling.py
index a4a6480..e22b81b 100644
--- a/PuLID/flux/sampling.py
+++ b/PuLID/flux/sampling.py
@@ -43,8 +43,8 @@ def prepare(t5: HFEmbedder, clip: HFEmbedder, img: Tensor, prompt: str) -> dict[
img_ids[..., 2] = img_ids[..., 2] + torch.arange(w // 2)[None, :]
img_ids = repeat(img_ids, "h w c -> b (h w) c", b=bs)
- if isinstance(prompt, str):
- prompt = [prompt]
+ # if isinstance(prompt, str):
+ # prompt = [prompt]
txt = t5(prompt)
if txt.shape[0] == 1 and bs > 1:
txt = repeat(txt, "1 ... -> bs ...", bs=bs)
@@ -56,10 +56,10 @@ def prepare(t5: HFEmbedder, clip: HFEmbedder, img: Tensor, prompt: str) -> dict[
return {
"img": img,
- "img_ids": img_ids.to(img.device),
- "txt": txt.to(img.device),
- "txt_ids": txt_ids.to(img.device),
- "vec": vec.to(img.device),
+ "img_ids": img_ids.to(img.device,torch.bfloat16),
+ "txt": txt.to(img.device,torch.bfloat16),
+ "txt_ids": txt_ids.to(img.device,torch.bfloat16),
+ "vec": vec.to(img.device,torch.bfloat16),
}
diff --git a/PuLID/flux/util.py b/PuLID/flux/util.py
index 6942cbe..c5e1e73 100644
--- a/PuLID/flux/util.py
+++ b/PuLID/flux/util.py
@@ -2,7 +2,6 @@ import os
from dataclasses import dataclass
import torch
-#from .transformer_flux import FluxTransformer2DModel
from huggingface_hub import hf_hub_download
from safetensors.torch import load_file as load_sft
import json
@@ -13,7 +12,7 @@ import folder_paths
@dataclass
class SamplingOptions:
- prompt: str
+ text: str
width: int
height: int
num_steps: int
@@ -144,23 +143,60 @@ def print_load_warning(missing: list[str], unexpected: list[str]) -> None:
# from XLabs-AI https://github.com/XLabs-AI/x-flux/blob/1f8ef54972105ad9062be69fe6b7f841bce02a08/src/flux/util.py#L330
def load_flow_model_quintized(name: str, ckpt_path,quantized_mode,device: str = "cuda", hf_download: bool = True):
+ from optimum.quanto import requantize,qint4,quantize,freeze
# Loading Flux
- print("Init model")
- if "dev" in name:
- name = "flux-dev"
- else:
- name = "flux-schnell"
+ print(f"Init model in {quantized_mode}")
model = Flux(configs[name].params).to(torch.bfloat16)
- if quantized_mode == "nf4": #nf4 is now useful
- print("Loading checkpoint in nf4")
- json_path = os.path.join(folder_paths.base_path, "custom_nodes", "ComfyUI_StoryDiffusion", "utils", "config.json")
+ if quantized_mode == "nf4": #nf4 is now useful.
+ json_path = os.path.join(folder_paths.base_path, "custom_nodes", "ComfyUI_StoryDiffusion", "utils",
+ "config.json")
+ test=False
+ if test:
+ from accelerate.utils import set_module_tensor_to_device, compute_module_sizes
+ from accelerate import init_empty_weights
+ from .convert_nf4_flux import _replace_with_bnb_linear, create_quantized_param, \
+ check_quantized_param
+ import safetensors.torch
+ import gc
+ from diffusers import FluxTransformer2DModel
+ dtype = torch.bfloat16
+ is_torch_e4m3fn_available = hasattr(torch, "float8_e4m3fn")
+ original_state_dict = safetensors.torch.load_file(ckpt_path)
+ print("Start a quantization process...")
+ with init_empty_weights():
+ config = FluxTransformer2DModel.load_config(json_path)
+ model = FluxTransformer2DModel.from_config(config).to(dtype)
+ expected_state_dict_keys = list(model.state_dict().keys())
+
+ _replace_with_bnb_linear(model, "nf4")
+
+ for param_name, param in original_state_dict.items():
+ if param_name not in expected_state_dict_keys:
+ continue
+
+ is_param_float8_e4m3fn = is_torch_e4m3fn_available and param.dtype == torch.float8_e4m3fn
+ if torch.is_floating_point(param) and not is_param_float8_e4m3fn:
+ param = param.to(dtype)
+
+ if not check_quantized_param(model, param_name):
+ set_module_tensor_to_device(model, param_name, device=0, value=param)
+ else:
+ create_quantized_param(
+ model, param, param_name, target_device=0, state_dict=original_state_dict,
+ pre_quantized=True
+ )
+
+ del original_state_dict
+ gc.collect()
+
+ else:
sd = load_sft(ckpt_path, device='cpu')
with open(json_path) as f:
quantization_map = json.load(f)
+
print("Start a quantization process...")
- from optimum.quanto import requantize
requantize(model, sd, quantization_map)
- print("Model is quantized!")
+ print("Model is quantized!")
else: #fp8
# ckpt_path = 'models/flux-dev-fp8.safetensors'
if (
@@ -172,27 +208,20 @@ def load_flow_model_quintized(name: str, ckpt_path,quantized_mode,device: str =
else:
json_path = os.path.join(folder_paths.base_path, "custom_nodes", "ComfyUI_StoryDiffusion", "utils",
"config.json")
- print("Loading checkpoint in fp8")
# load_sft doesn't support torch.device
sd = load_sft(ckpt_path, device='cpu')
with open(json_path) as f:
quantization_map = json.load(f)
print("Start a quantization process...")
- from optimum.quanto import requantize
requantize(model, sd, quantization_map)
print("Model is quantized!")
return model
def load_flow_model(name: str,ckpt_path, device: str = "cuda", hf_download: bool = True):
# Loading Flux
- print("Init model")
- if "dev" in name:
- name="flux-dev"
- else:
- name="flux-schnell"
+ print("Init model in fp16")
if ckpt_path is not None:
# load_sft doesn't support torch.device
- print("Loading checkpoint in fp16")
with torch.device(device):
model = Flux(configs[name].params).to(torch.bfloat16)
sd = load_sft(ckpt_path, device=str(device))
@@ -201,24 +230,21 @@ def load_flow_model(name: str,ckpt_path, device: str = "cuda", hf_download: bool
return model
-def load_t5(name,quantized_mode,device = "cuda", max_length: int = 512) -> HFEmbedder:
+def load_t5(name,clip_cf,quantized_mode,device = "cuda", max_length: int = 512) -> HFEmbedder:
# max length 64, 128, 256 and 512 should work (if your sequence is short enough)
#return HFEmbedder("xlabs-ai/xflux_text_encoders", max_length=max_length, torch_dtype=torch.bfloat16).to(device)
- return HFEmbedder(f"{name}/text_encoder_2", f"{name}/tokenizer_2",is_clip=False,quantized_mode=quantized_mode,max_length=max_length, torch_dtype=torch.bfloat16).to(device)
+ return HFEmbedder(f"{name}/text_encoder_2", f"{name}/tokenizer_2",clip_cf,is_clip=False,quantized_mode=quantized_mode,max_length=max_length, torch_dtype=torch.bfloat16).to(device)
-def load_clip(name,quantized_mode,device= "cuda") -> HFEmbedder:
- return HFEmbedder(f"{name}/text_encoder",f"{name}/tokenizer",is_clip=True,quantized_mode=quantized_mode,max_length=77, torch_dtype=torch.bfloat16).to(device)
+def load_clip(name,clip_cf,quantized_mode,device= "cuda") -> HFEmbedder:
+ return HFEmbedder(f"{name}/text_encoder",f"{name}/tokenizer",clip_cf,is_clip=True,quantized_mode=quantized_mode,max_length=77, torch_dtype=torch.bfloat16).to(device)
#return HFEmbedder("openai/clip-vit-large-patch14", max_length=77, torch_dtype=torch.bfloat16).to(device)
-def load_ae(name: str, device: str = "cuda", hf_download: bool = True) -> AutoEncoder:
- ckpt_path = os.path.join(name,"ae.safetensors") # ckpt_path = configs[name].ae_path
+def load_ae(name: str,vae_cf, device: str = "cuda", hf_download: bool = True) -> AutoEncoder:
+ ckpt_path=vae_cf
+ #ckpt_path = os.path.join(name,"ae.safetensors") # ckpt_path = configs[name].ae_path
- if "dev" in name:
- name = "flux-dev"
- else:
- name = "flux-schnell"
if (
not os.path.exists(ckpt_path)
and configs[name].repo_id is not None
diff --git a/README.md b/README.md
index a5fbb0d..4e9c09a 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,38 @@
-# ComfyUI_StoryDiffusion
-You can using StoryDiffusion in ComfyUI.
+
+
ComfyUI_StoryDiffusion:You can using StoryDiffusion in ComfyUI.
* [中文说明](https://github.com/smthemex/ComfyUI_StoryDiffusion/blob/main/README-CN.md)
* StoryDiffusion origin From: [link](https://github.com/HVision-NKU/StoryDiffusion) ---&--- MS-Diffusion origin From: [link](https://github.com/MS-Diffusion/MS-Diffusion)---&---StoryMakerr from From:[StoryMaker](https://github.com/RedAIGC/StoryMaker)
-
## Updates:
-**2024/09/24**
-* Now if using Kolor's "ip-adapter" or "face ID", you can choose the monolithic model of clip_vision (such as :"clip-vit-large-patch14.safetensors") to load the image encoder. The change this brings is that Kolor's local directory can delete all files in the "clip-vit-large-patch14-336" and "Kolors-IP-Adapter-Plus" folders. Of course, because comfyUI defaults to clip image processing of "224" size , while Kolor defaults to 336 size, there will be a loss of accuracy and quality. Please refer to the image comparison in readme for details.
-* Another change is that we now need to port the model of "ip_adapter_plus_general.bin" in "kolor-ipadapter" to the "comfyUI/models/photomaker" directory;
+**2024/09/27**
+* make easy and comfy
-**Previous updates:**
-* For the convenience of use, the layout of the node has been adjusted again,delete "id number"(base character lines now),delete " model_type"(base input image [img2img function] or not [txt2img function] now), 'clip_vision' , 'controlnet' , 'character prompt' ,'image','control_image' now in model loader node now.
-* Now,using story_maker or pulid_flux function ,need choice clip vision model (use 'clip_vision' menu);
-* add StoryMaker from From: [StoryMaker](https://github.com/RedAIGC/StoryMaker) to make dual role..or normal img2img,as detailed in the following text 3.5 ,fill "maker,dual" in easy function to using StoryMaker for dual role;
-* Add " PulID FLUX " function, In my testing, a minimum of 12GB of VRAM can run normally,now you can fill "X:/xxx/xxx/black-forest-labs/FLUX.1-dev",and fill easy function "pilid,fp8,cpu"(if you Vram>30G,can remove cpu,and using Kijai/flux-fp8,if you Vram>45G,can remove fp8,cpu), although it is a bit slow if using cpu! ,Of course, some models need to be prepared, as detailed in the following text;
-* Add kolor FaceId function, now you can fill "xxx:/xxx/xxx/Kwai-Kolors/Kolors",and fill easy function "face",Of course, some models need to be prepared, as detailed in the following text;
-* Add diffusers'img2img codes( Not commit diffusers yet),Now you can using flux img2img function. in flux img2img,"guidance_scale" is usually 3.5 ,you can change ip-adapter_strength's number to Control the noise of the output image, the closer the number is to 1, the less it looks like the original image, and the closer it is to 0, the more it looks like the original image. Correspondingly, your generated step count is a multiple of this value, which means that if you enter 50 steps, the actual number of steps taken is 50 * 0.8 (0.8 is the value of change ip-adapter_strength) #you can see exmaple img
-* AWPortrait-FL-fp8.safetensors is support if using fp8 mode,..
-* using img crop to fix ms_diffusion only using square's error;
-* change W and H global names,it cause some error;
-* fix runway error,when loader single model.
-* The loading speed of the NF4 model is many times faster than FP8, so I recommend using the NF4 model to run Flux. I have included the workflow of NF4 in the example,
-* Add an "easy_function" for debugging new function. This time, I have added support for "auraface" in "Photomake V2". You can enter "auraface" into the "easy_function" to test this method
-* support Flux ,1.only using fp8 repo,fill local "X:/xxx/xxx/black-forest-labs/FLUX.1-dev" and ckpt_name="none";if save .pt,fill easy function "save",2. using unet,fill local repo and choice a fp8 Unet(like Kijai/flux-fp8, AWPortrait-FL-fp8.safetensors. *.pt), 3. using fn4,need download weights at [link](https://huggingface.co/sayakpaul/flux.1-dev-nf4/tree/main),put weight in "comfyui/models/checkpoints/";fill local "X:/xxx/xxx/black-forest-labs/FLUX.1-dev"
-* Now clip checkpoints no need diffusers_repo,you can using "clip_g.safetensors" or other base from "CLIP-ViT-bigG-14-laion2B-39B-b160k";
-* 2 role in 1 img now using [A]...[B]... mode,
-* Support "kolors" text2img and "kolors"ipadapter img2img,using repo like :"xxx:/xxx/xxx/Kwai-Kolors/Kolors" (Please refer to the end of the article for detailed file combinations)
-* support photomaker V2;
-* ControlNet now uses community models.
-* The base model now has only two options: using repo input or selecting the community model...
-* Introducing Controlnet for dual character co image, supporting multi image introduction,
-* Add the function of saving and loading character models
+## Function introduction
+**story-diffusion**
+* Support img2img & txt2img,All you need to do is select an SDXL model in“ckpt_name”menu to get started, support sdxl lora;
+* If using img2img,You can choose between the v1 or v2 version of PhotoMaker checkpoints(Automatic downloads); phtomaker v2 need insightface and the models that go with it;
+
+**ms-diffusion**
+* Enabled when 2 characters are required to appear in the same image, When [roleA] and [roleB] appear in a scene prompt at the same time, it will be automatically enabled;
+* Of course the "ms_adapter.bin" model is also required, and the "clip_vision_g.safetensors"I is selected in the clip_vision;
+* MS supports ControlNet, and a dual-role prompt requires a preprocessed ControlNet picture.
+
+**story-maker**
+* "story-maker“ is similar to "story-diffusion", currently only supports img2img, which features the ability to migrate the character's clothing and supports dual characters with the same picture;
+* To turn on this function, you need to enter 'maker' in easy-function; Then select an sdxl model and select the "clip_vision_H.safetensors" model in the clip-vision,The companion “mask.bin” model and“insightface"model are automatically downloaded;
+* If you enter 'maker,dual', the function of using 'story-diffusion' in the front section and using 'story-maker' in the same picture for both characters will be enabled
+* The method requires a mask, so “RMBG-1.4” is built-in, which can be downloaded automatically;
+
+**kolor**
+* With kolor, you need to enter the local path of kolor in the repo_id, using '/' splitting the directory;You can use "clip-vit-large-patch14.safetensors", or the image encoder in the repo;
+* Kolor supports img2img (IPadapeter and FaceID), txt2img,The matching model will be automatically downloaded, and the details can be found in the README model content;
+* Kolor supports prompt input in all Chinese characters, note that the character name needs to be changed to ['张三'];
+* using kolor FaceId function, need fill easy function "face",
+
+**Flux and PULID-FLUX**
+* Flux supports img2img and txt2img, and supports FP8 and NF4 (recommended) quantization models;To enable it, enter the local path of flux diffuser in 'repo_id' and select the corresponding model in 'ckpt-name';example fill "X:/xxx/xxx/black-forest-labs/FLUX.1-dev";
+* PULID-FLUX needs to connect to the dual clip nodes of comfy in clip, and select 'EVA02_CLIP_L_336_psz14_s6B.ptin ' clip-vision, 'ae.safetensors' in vae, fill in 'pulid, fp8, cpu' in 'easy-function' , if your VRAM is about 24G, you can try to remove the cpu;The accompanying 'insightface' model will be automatically downloaded;
1.Installation
-----
@@ -45,7 +46,7 @@ git clone https://github.com/smthemex/ComfyUI_StoryDiffusion.git
```
pip install -r requirements.txt
```
-if using photomakerV2,need:
+if using photomaker V2,pulid-flux,kolor,story-make:
```
pip install insightface==0.7.3 or new
```
@@ -214,7 +215,7 @@ downlaod nf4 model [link](https://huggingface.co/sayakpaul/flux.1-dev-nf4/tree/
| ├── rename nf4 ckpt
```
-**3.3.3.4 using flux pulid,repo_id+ckpt_name** .
+**3.3.3.4 using flux pulid,clip+ckpt_name** .
torch must > 0.24.0
optimum-quanto must >=0.2.4
```
@@ -225,6 +226,7 @@ DIAMONIK7777/antelopev2 auto downlaod....[https://huggingface.co/DIAMONIK7777/an
"pulid_flux_v0.9.0.safetensors" download from [link](https://huggingface.co/guozinan/PuLID/tree/main)
fp8 using flux1-dev-fp8.safetensors from [link](https://huggingface.co/Kijai/flux-fp8/tree/main)
make sure ae.safetensors in you FLUX.1-dev dir,example:
+
```
├── ComfyUI/models/photomaker/
| ├── pulid_flux_v0.9.0.safetensors
@@ -232,6 +234,9 @@ make sure ae.safetensors in you FLUX.1-dev dir,example:
| ├── EVA02_CLIP_L_336_psz14_s6B.pt
├── ComfyUI/models/checkpoints/
| ├── flux1-dev-fp8.safetensors
+├── ComfyUI/models/clip/
+| ├── t5xxl_fp8_e4m3fn.safetensors
+| ├── clip_l.safetensors
```
**3.5 if using storymake..**
@@ -252,51 +257,36 @@ RMBG-1.4 from [link](https://huggingface.co/briaai/RMBG-1.4/tree/main)#自动
```
4 Example
----
-img2img mode only use storymaker 只用storymaker生成(包括双角色),最新 (Latest version)
-
+**story-make**
+img2img 纯storymaker生成,非最新示例 (outdated version examples)
+
-img2img mode use storymaker for dual role only,只用storymaker生成双角色,单角色用diffusion生成,最新(Latest version)
-
+**flux-pulid**
+img2img mode use flux pulid 12G Vram,cpu Flux使用PULID功能,最新示例(Latest version)
+
-img2img mode use flux pulid 12G Vram,cpu Flux使用PULID功能,最新(Latest version)
-
+**kolor-face**
+img2img kolor face,参数输入没变化,非最新示例 (outdated version examples)
+
-img2img mode use nf4 flux 开启flux nf4模式,速度最快,示例图为旧节点(outdated version examples)
-
-
-
-txt2img mode use NF4 FLUX 开启flux nf4模式,速度最快,示例图为旧节点 (outdated version examples)
+**flux-nf4**
+* txt2img mode use NF4 FLUX 开启flux nf4模式,速度最快,非最新示例 (outdated version examples)

+* flux img2img 图生图,注意用ip的参数来控制噪声(多样性),非最新示例 (outdated version examples)
+
-img2img mode use auraface photomake V2 开启v2模式,示例图为旧节点 (outdated version examples)
-
-
-img2img mode kolors ip-adapter using monolithic model 可图使用单体模型来解码图片 最新 (Latest version)
-
-
-img2img model use kolors ip adapter,and using chinese prompt 开启kolor模式,使用中文提示词,示例图为旧节点 (outdated version examples)
-
-
-
-img2img model use kolors ip adapter face id (outdated version examples)
-
-
-img2img sdxl mode, uses photomakeV1 (outdated version examples)
-
-
-img2img sdxl mode, uses photomakeV2 (outdated version examples)
-
-
-txt2img using lora and comic node (outdated version examples)
-
-
-img2img2role in 1 image (Outdated version examples)
+**ms-diffusion**
+* img2img2role in 1 image,双角色同图,非最新示例 (Outdated version examples)

-
-ControlNet added dual role co frame (Role 1 and Role 2) (Outdated version examples)
+* ControlNet added dual role co frame (Role 1 and Role 2) (Outdated version examples)

-Translate the text into other language examples, and the translation nodes in the diagram can be replaced with any translation node. Outdated version examples
+**story-diffusion**
+* photomake v2 in img2img normal 最基础的story流程,非最新示例 (outdated version examples)
+
+* txt2img using lora and comic node (outdated version examples)
+
+* Translate the text into other language examples, and the translation nodes in the diagram can be replaced with any translation node. Outdated version examples

Function Description of Nodes
@@ -304,6 +294,7 @@ Function Description of Nodes
**
**
* image: optional,The interface that must be linked to the image generation diagram. For dual roles, please follow the example and use the built-in image batch node in comfyUI;
* controlnet:only ms function support;
+* clip:Connect the two clip nodes of Comfyui(using flux-pulid)
* character prompt: The prompt for the character, [character name] must be at the beginning. If using the graphic mode, the keyword "img" must be added, such as a man img;if using chinese prompt, need["角色名"] or ['角色名']
* repo_id: using diffuser models ;
* ckpt_name: using community SDLX model selection;
@@ -323,17 +314,16 @@ Function Description of Nodes
****
* model: The interface that must be linked;
* Scene prompts: The prompt for the scene description, [character name], must start at the beginning. It is best for both characters to appear once in the first two lines. [NC] At the beginning, the character does not appear (suitable for non character scenes). When [character A] and [character B], MS diffusion's dual character mode is enabled, and and the spaces before and after it cannot be ignored# Used for segmented prompt, rendering the entire segment, but only outputting the prompt after #;
-* split prompt: The symbol for splitting the prompt, which does not take effect when it is empty. It is used to normalize paragraphs when the prompt is external. For example, when you pass in 10 lines of text, the hyphen may not be correct, but using a hyphen, such as ";", can effectively distinguish each line.
* negative prompt: only effective when img_style is No_style;
* seed/steps/cfg: suitable for commonly used functions in comfyUI;
* ip-adapter_strength: img2img controls the weight of ip-adapter in graph generation,only using in kolors;
* style_strength'ratio: Style weight control, which controls from which step the style takes effect. When the style consistency is not good, you can try increasing or decreasing this parameter;
* role-scale: only effective when two characters are in the same image, controlling the weight of the characters in the image;
-* Mask_threshold: It is only effective when two roles are in the same picture, and controls the position of the role in the picture (MS system automatically assigns the role position according to prompt, so appropriate role position information description can be added to prompt);
+* Mask_threshold: It is only effective when two roles are in the same picture, and controls the position of the role in the picture (MS system automatically assigns the role position according to prompt, so appropriate role position information description can be added to prompt)(ms-diffusion only);
* Start_step: Only effective when two characters are in the same image, controlling the number of starting steps for the character's position in the image
* Save_character: Whether to save the character weights of the current character, file in/ Under ComfyUI_StoryDiffusion/weights/pt, use time as the file name;
-* Controllet_scale: control net weight;
-* guidance_list: contrlol role's position;
+* Controllet_scale: control net weight,(ms-diffusion only);
+* guidance_list: contrlol role's position(ms-diffusion only);
****
* Fonts list: The puzzle node supports custom fonts (place the font file in the fonts directory. fonts/you_font. ttf);
@@ -350,11 +340,38 @@ Function Description of Nodes
* Playground v2.5 can be effective on txt2img, and there is no Playground v2.5 style Lora available when accelerated Lora can be used;
* Role-scale, mask_threshold, and start_step mainly regulate the randomness and style consistency of two characters in the same frame;
* The consistency of style can be adjusted between ip-adapter_strength and style_strength'ratio in img2img;
-* Preprocess translation text nodes, please refer to the example diagram for usage methods. (Pay attention to changing the font for Chinese or other East Asian characters); * By default, use the ";" at the end of each paragraph to divide the paragraph. After translation into Chinese, there is a chance that it will be translated as ";", so remember to change it to ";", otherwise it will be a sentence.
-* The process of generating images using PhotosMaker requires the IMG keyword in the character prompt column. You can use keywords such as a woman IMG, a man IMG, etc;
+* Preprocess translation text nodes, please refer to the example diagram for usage methods. (Pay attention to changing the font for Chinese or other East Asian characters);
+* By default, use the ";" at the end of each paragraph to divide the paragraph. After translation into Chinese, there is a chance that it will be translated as ";", so remember to change it to ";", otherwise it will be a sentence.
+* The process of generating images using PhotosMaker requires the "img" keyword in the character prompt column. You can use keywords such as a woman "img" , a man img , etc;
* No characters appear in the image, add [NC] in front of the scene prompt;
* Segmented prompt, using #, such as AAAA # BBBB, will generate AAAA content, but the text will only display BBBB
+**Previous updates**
+* Now if using Kolor's "ip-adapter" or "face ID", you can choose the monolithic model of clip_vision (such as :"clip-vit-large-patch14.safetensors") to load the image encoder. The change this brings is that Kolor's local directory can delete all files in the "clip-vit-large-patch14-336" and "Kolors-IP-Adapter-Plus" folders. Of course, because comfyUI defaults to clip image processing of "224" size , while Kolor defaults to 336 size, there will be a loss of accuracy and quality. Please refer to the image comparison in readme for details.
+* Another change is that we now need to port the model of "ip_adapter_plus_general.bin" in "kolor-ipadapter" to the "comfyUI/models/photomaker" directory;
+* For the convenience of use, the layout of the node has been adjusted again,delete "id number"(base character lines now),delete " model_type"(base input image [img2img function] or not [txt2img function] now), 'clip_vision' , 'controlnet' , 'character prompt' ,'image','control_image' now in model loader node now.
+* Now,using story_maker or pulid_flux function ,need choice clip vision model (use 'clip_vision' menu);
+* add StoryMaker from From: [StoryMaker](https://github.com/RedAIGC/StoryMaker) to make dual role..or normal img2img,as detailed in the following text 3.5 ,fill "maker,dual" in easy function to using StoryMaker for dual role;
+* Add " PulID FLUX " function, In my testing, a minimum of 12GB of VRAM can run normally,now you can fill "X:/xxx/xxx/black-forest-labs/FLUX.1-dev",and fill easy function "pilid,fp8,cpu"(if you Vram>30G,can remove cpu,and using Kijai/flux-fp8,if you Vram>45G,can remove fp8,cpu), although it is a bit slow if using cpu! ,Of course, some models need to be prepared, as detailed in the following text;
+* Add kolor FaceId function, now you can fill "xxx:/xxx/xxx/Kwai-Kolors/Kolors",and fill easy function "face",Of course, some models need to be prepared, as detailed in the following text;
+* Add diffusers'img2img codes( Not commit diffusers yet),Now you can using flux img2img function. in flux img2img,"guidance_scale" is usually 3.5 ,you can change ip-adapter_strength's number to Control the noise of the output image, the closer the number is to 1, the less it looks like the original image, and the closer it is to 0, the more it looks like the original image. Correspondingly, your generated step count is a multiple of this value, which means that if you enter 50 steps, the actual number of steps taken is 50 * 0.8 (0.8 is the value of change ip-adapter_strength) #you can see exmaple img
+* AWPortrait-FL-fp8.safetensors is support if using fp8 mode,..
+* using img crop to fix ms_diffusion only using square's error;
+* change W and H global names,it cause some error;
+* fix runway error,when loader single model.
+* The loading speed of the NF4 model is many times faster than FP8, so I recommend using the NF4 model to run Flux. I have included the workflow of NF4 in the example,
+* Add an "easy_function" for debugging new function. This time, I have added support for "auraface" in "Photomake V2". You can enter "auraface" into the "easy_function" to test this method
+* support Flux ,1.only using fp8 repo,fill local "X:/xxx/xxx/black-forest-labs/FLUX.1-dev" and ckpt_name="none";if save .pt,fill easy function "save",2. using unet,fill local repo and choice a fp8 Unet(like Kijai/flux-fp8, AWPortrait-FL-fp8.safetensors. *.pt), 3. using fn4,need download weights at [link](https://huggingface.co/sayakpaul/flux.1-dev-nf4/tree/main),put weight in "comfyui/models/checkpoints/";fill local "X:/xxx/xxx/black-forest-labs/FLUX.1-dev"
+* Now clip checkpoints no need diffusers_repo,you can using "clip_g.safetensors" or other base from "CLIP-ViT-bigG-14-laion2B-39B-b160k";
+* 2 role in 1 img now using [A]...[B]... mode,
+* Support "kolors" text2img and "kolors"ipadapter img2img,using repo like :"xxx:/xxx/xxx/Kwai-Kolors/Kolors" (Please refer to the end of the article for detailed file combinations)
+* support photomaker V2;
+* ControlNet now uses community models.
+* The base model now has only two options: using repo input or selecting the community model...
+* Introducing Controlnet for dual character co image, supporting multi image introduction,
+* Add the function of saving and loading character models
+
+
Citation
------
diff --git a/StoryMaker/ip_adapter/attention_processor.py b/StoryMaker/ip_adapter_/attention_processor.py
similarity index 100%
rename from StoryMaker/ip_adapter/attention_processor.py
rename to StoryMaker/ip_adapter_/attention_processor.py
diff --git a/StoryMaker/ip_adapter/attention_processor_faceid.py b/StoryMaker/ip_adapter_/attention_processor_faceid.py
similarity index 100%
rename from StoryMaker/ip_adapter/attention_processor_faceid.py
rename to StoryMaker/ip_adapter_/attention_processor_faceid.py
diff --git a/StoryMaker/ip_adapter/ip_adapter.py b/StoryMaker/ip_adapter_/ip_adapter.py
similarity index 99%
rename from StoryMaker/ip_adapter/ip_adapter.py
rename to StoryMaker/ip_adapter_/ip_adapter.py
index 0033268..6c08a5c 100644
--- a/StoryMaker/ip_adapter/ip_adapter.py
+++ b/StoryMaker/ip_adapter_/ip_adapter.py
@@ -12,7 +12,7 @@ if is_torch2_available():
from .attention_processor import IPAttnProcessor2_0 as IPAttnProcessor, AttnProcessor2_0 as AttnProcessor, CNAttnProcessor2_0 as CNAttnProcessor
else:
from .attention_processor import IPAttnProcessor, AttnProcessor, CNAttnProcessor
-from .resampler import Resampler
+from .resampler_ import Resampler
class ImageProjModel(torch.nn.Module):
diff --git a/StoryMaker/ip_adapter/ip_adapter_faceid.py b/StoryMaker/ip_adapter_/ip_adapter_faceid.py
similarity index 99%
rename from StoryMaker/ip_adapter/ip_adapter_faceid.py
rename to StoryMaker/ip_adapter_/ip_adapter_faceid.py
index ea1f489..79d57dd 100644
--- a/StoryMaker/ip_adapter/ip_adapter_faceid.py
+++ b/StoryMaker/ip_adapter_/ip_adapter_faceid.py
@@ -21,7 +21,7 @@ if is_torch2_available() and (not USE_DAFAULT_ATTN):
)
else:
from .attention_processor_faceid import LoRAAttnProcessor, LoRAIPAttnProcessor
-from .resampler import PerceiverAttention, FeedForward, Resampler
+from .resampler_ import PerceiverAttention, FeedForward, Resampler
class FacePerceiverResampler(torch.nn.Module):
def __init__(
diff --git a/StoryMaker/ip_adapter/resampler.py b/StoryMaker/ip_adapter_/resampler_.py
similarity index 100%
rename from StoryMaker/ip_adapter/resampler.py
rename to StoryMaker/ip_adapter_/resampler_.py
diff --git a/StoryMaker/ip_adapter/utils.py b/StoryMaker/ip_adapter_/utils.py
similarity index 100%
rename from StoryMaker/ip_adapter/utils.py
rename to StoryMaker/ip_adapter_/utils.py
diff --git a/StoryMaker/pipeline_sdxl_storymaker.py b/StoryMaker/pipeline_sdxl_storymaker.py
index 7f8c9f4..a039ce5 100644
--- a/StoryMaker/pipeline_sdxl_storymaker.py
+++ b/StoryMaker/pipeline_sdxl_storymaker.py
@@ -48,12 +48,12 @@ from diffusers.utils.import_utils import is_xformers_available
from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection
from insightface.utils import face_align
-from .ip_adapter.resampler import Resampler
-from .ip_adapter.utils import is_torch2_available
-from .ip_adapter.ip_adapter_faceid import faceid_plus
+from .ip_adapter_.resampler_ import Resampler
+from .ip_adapter_.utils import is_torch2_available
+from .ip_adapter_.ip_adapter_faceid import faceid_plus
-from .ip_adapter.attention_processor import IPAttnProcessor2_0 as IPAttnProcessor, AttnProcessor2_0 as AttnProcessor
-from .ip_adapter.attention_processor_faceid import LoRAIPAttnProcessor2_0 as LoRAIPAttnProcessor, LoRAAttnProcessor2_0 as LoRAAttnProcessor
+from .ip_adapter_.attention_processor import IPAttnProcessor2_0 as IPAttnProcessor, AttnProcessor2_0 as AttnProcessor
+from .ip_adapter_.attention_processor_faceid import LoRAIPAttnProcessor2_0 as LoRAIPAttnProcessor, LoRAAttnProcessor2_0 as LoRAAttnProcessor
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
@@ -216,8 +216,7 @@ class StableDiffusionXLStoryMakerPipeline(StableDiffusionXLPipeline):
clip_img = self.clip_image_processor(images=ref_img, return_tensors="pt").pixel_values
return clip_img, clip_face, torch.from_numpy(face_info.normed_embedding).unsqueeze(0)
- def _encode_prompt_image_emb(self, image, image_2, mask_image, mask_image_2, face_info, face_info_2, cloth, cloth_2, \
- device, num_images_per_prompt, dtype, do_classifier_free_guidance):
+ def _encode_prompt_image_emb(self, image, image_2, mask_image, mask_image_2, face_info, face_info_2, cloth, cloth_2,device, num_images_per_prompt, dtype, do_classifier_free_guidance):
crop_list = []; face_list = []; id_list = []
if image is not None:
clip_img, clip_face, face_emb = self.crop_image(image, mask_image, face_info)
@@ -252,9 +251,12 @@ class StableDiffusionXLStoryMakerPipeline(StableDiffusionXLPipeline):
if do_classifier_free_guidance:
prompt_image_emb = self.image_proj_model(id_embeds, clip_image_embeds, clip_face_embeds)
B, C, D = prompt_image_emb.shape
- prompt_image_emb = prompt_image_emb.view(1, B*C, D)
+ #prompt_image_emb = prompt_image_emb.view(1, B*C, D)
+ prompt_image_emb = prompt_image_emb.view(1, B * C, D)
+
neg_emb = self.image_proj_model(torch.zeros_like(id_embeds), torch.zeros_like(clip_image_embeds), torch.zeros_like(clip_face_embeds))
- neg_emb = neg_emb.view(1, B*C, D)
+ #neg_emb = neg_emb.view(1, B*C, D)
+ neg_emb = neg_emb.view(1, B * C, D)
prompt_image_emb = torch.cat([neg_emb, prompt_image_emb], dim=0) #torch.Size([2, 40, 2048])
#print(prompt_image_emb.shape)
else:
@@ -264,8 +266,9 @@ class StableDiffusionXLStoryMakerPipeline(StableDiffusionXLPipeline):
#print(f'prompt_image_emb: {prompt_image_emb.shape}')#torch.Size([2, 40, 2048])
bs_embed, seq_len, _ = prompt_image_emb.shape
-
prompt_image_emb = prompt_image_emb.repeat(1, num_images_per_prompt, 1)
+
+ #prompt_image_emb = prompt_image_emb.repeat(1, num_images_per_prompt, 1)
prompt_image_emb = prompt_image_emb.view(bs_embed * num_images_per_prompt, seq_len, -1)
return prompt_image_emb.to(device=device, dtype=dtype)
@@ -498,7 +501,7 @@ class StableDiffusionXLStoryMakerPipeline(StableDiffusionXLPipeline):
batch_size = len(prompt)
else:
batch_size = prompt_embeds.shape[0]
- #print(batch_size)
+ #print(f"batch_size is {batch_size}")
device = self.unet.device
# pdb.set_trace()
# 3.1 Encode input prompt
@@ -598,8 +601,8 @@ class StableDiffusionXLStoryMakerPipeline(StableDiffusionXLPipeline):
prompt_embeds = prompt_embeds.to(device)
add_text_embeds = add_text_embeds.to(device)
add_time_ids = add_time_ids.to(device).repeat(batch_size * num_images_per_prompt, 1)
- # print(prompt_embeds.shape, prompt_image_emb.shape) torch.Size([4, 77, 2048])? torch.Size([2, 40, 2048])
- prompt_image_emb=torch.cat((prompt_image_emb,) * batch_size, dim=0)
+ #print(prompt_embeds.shape,"and", prompt_image_emb.shape) # torch.Size([4, 77, 2048]) ? torch.Size([2, 40, 2048])
+ #prompt_image_emb = torch.cat([prompt_image_emb] * batch_size, dim=0)
#print(prompt_embeds.shape, prompt_image_emb.shape)
encoder_hidden_states = torch.cat([prompt_embeds, prompt_image_emb], dim=1)
@@ -696,4 +699,4 @@ class StableDiffusionXLStoryMakerPipeline(StableDiffusionXLPipeline):
if not return_dict:
return (image,)
- return StableDiffusionXLPipelineOutput(images=image)
+ return StableDiffusionXLPipelineOutput(images=image)
\ No newline at end of file
diff --git a/Storydiffusion_node.py b/Storydiffusion_node.py
index 9ff7435..2cd2d46 100644
--- a/Storydiffusion_node.py
+++ b/Storydiffusion_node.py
@@ -16,6 +16,7 @@ from PIL import ImageFont
from safetensors.torch import load_file
+import ComfyUI.comfy.model_sampling
from .PuLID.pulid.utils import resize_numpy_image_long
from .ip_adapter.attention_processor import IPAttnProcessor2_0
import sys
@@ -57,6 +58,7 @@ import folder_paths
from comfy.utils import common_upscale
from comfy.model_management import cleanup_models
from comfy.clip_vision import load as clip_load
+from comfy.model_management import total_vram
global total_count, attn_count, cur_step, mask1024, mask4096, attn_procs, unet
global sa32, sa64
@@ -691,16 +693,17 @@ def process_generation(
height,
load_chars,
lora,
- trigger_words,photomake_mode,use_kolor,use_flux,make_dual_only,kolor_face,pulid,story_maker,app_face, pipeline_mask,role_scale
+ trigger_words,photomake_mode,use_kolor,use_flux,make_dual_only,kolor_face,pulid,story_maker,
+ input_id_emb_s_dict, input_id_img_s_dict,input_id_emb_un_dict, input_id_cloth_dict,role_scale,control_img,empty_emb_zero,miX_mode
): # Corrected font_choice usage
if len(general_prompt.splitlines()) >= 3:
raise "Support for more than three characters is temporarily unavailable due to VRAM limitations, but this issue will be resolved soon."
# _model_type = "Photomaker" if _model_type == "Using Ref Images" else "original"
- if not use_kolor and not use_flux:
+ if not use_kolor and not use_flux and not story_maker:
if model_type == "img2img" and "img" not in general_prompt:
- raise 'Please choice img2img typle,and add the triger word " img " behind the class word you want to customize, such as: man img or woman img'
+ raise 'if using normal SDXL img2img ,need add the triger word " img " behind the class word you want to customize, such as: man img or woman img'
global total_length, attn_procs, cur_model_type
global write
@@ -709,11 +712,12 @@ def process_generation(
#load_chars = load_character_files_on_running(unet, character_files=char_files)
prompts_origin = prompt_array.splitlines()
+ prompts_origin=[i.strip() for i in prompts_origin]
+ prompts_origin=[i for i in prompts_origin if '[' in i] #删除空行
+ #print(prompts_origin)
prompts = [prompt for prompt in prompts_origin if not len(extract_content_from_brackets(prompt))>=2] # 剔除双角色
- if use_kolor:
- add_trigger_words = "," + trigger_words + " " + "风格" + ";"
- else:
- add_trigger_words = "," + trigger_words + " " + "style" + ";"
+
+ add_trigger_words = " " + trigger_words + " style "
if lora :
if lora in lora_lightning_list:
prompts = remove_punctuation_from_strings(prompts)
@@ -757,7 +761,9 @@ def process_generation(
prompts = [
prompt.rpartition("#")[0] if "#" in prompt else prompt for prompt in prompts
]
-
+ img_mode=False
+ if kolor_face or pulid or (story_maker and not make_dual_only) or model_type=="img2img":
+ img_mode=True
# id_prompts = prompts[:id_length]
(
character_index_dict,
@@ -765,15 +771,19 @@ def process_generation(
replace_prompts,
ref_indexs_dict,
ref_totals,
- ) = process_original_prompt(character_dict, prompts, id_length)
-
+ ) = process_original_prompt(character_dict, prompts, id_length,img_mode)
+ #print(character_index_dict,invert_character_index_dict,replace_prompts,ref_indexs_dict,ref_totals)
+ #character_index_dict:{'[Taylor]': [0, 3], '[sam]': [1, 2]},if 1 role {'[Taylor]': [0, 1, 2]}
+ #invert_character_index_dict:{0: ['[Taylor]'], 1: ['[sam]'], 2: ['[sam]'], 3: ['[Taylor]']},if 1 role {0: ['[Taylor]'], 1: ['[Taylor]'], 2: ['[Taylor]']}
+ #ref_indexs_dict:{'[Taylor]': [0, 3], '[sam]': [1, 2]},if 1 role {'[Taylor]': [0]}
+ #ref_totals: [0, 3, 1, 2] if 1 role [0]
if model_type == "img2img":
# _upload_images = [_upload_images]
input_id_images_dict = {}
if len(upload_images) != len(character_dict.keys()):
raise f"You upload images({len(upload_images)}) is not equal to the number of characters({len(character_dict.keys())})!"
for ind, img in enumerate(upload_images):
- input_id_images_dict[character_list[ind]] = [img] # 已经pil转化了 不用load
+ input_id_images_dict[character_list[ind]] = [img] # 已经pil转化了 不用load {a:[img],b:[img]}
# input_id_images_dict[character_list[ind]] = [load_image(img)]
# real_prompts = prompts[id_length:]
# if device == "cuda":
@@ -781,8 +791,6 @@ def process_generation(
write = True
cur_step = 0
attn_count = 0
- #id_prompts_no_using, negative_prompt = apply_style(style_name, ["id_prompts"], negative_prompt)
- #setup_seed(seed_)
total_results = []
id_images = []
results_dict = {}
@@ -790,17 +798,19 @@ def process_generation(
global cur_character
if not load_chars:
- for character_key in character_dict.keys():# 先生成角色对应第一句场景提示词的图片
+ for character_key in character_dict.keys():# 先生成角色对应第一句场景提示词的图片,图生图是批次生成
+ character_key_str=character_key
cur_character = [character_key]
ref_indexs = ref_indexs_dict[character_key]
current_prompts = [replace_prompts[ref_ind] for ref_ind in ref_indexs]
- setup_seed(seed_)
+ if model_type == "txt2img":
+ setup_seed(seed_)
generator = torch.Generator(device=device).manual_seed(seed_)
cur_step = 0
cur_positive_prompts, cur_negative_prompt = apply_style(
style_name, current_prompts, negative_prompt
)
- print(f"sampler:{cur_positive_prompts}")
+ print(f"Sampler {character_key_str} 's cur_positive_prompts :{cur_positive_prompts}")
if model_type == "txt2img":
if use_flux:
id_images = pipe(
@@ -834,27 +844,38 @@ def process_generation(
cur_negative_prompt = cur_negative_prompt * len(cur_positive_prompts) if len(
cur_negative_prompt) != len(cur_positive_prompts) else cur_negative_prompt
if kolor_face:
- img_=input_id_images_dict[character_key][0]
- face_info = app_face.get_faceinfo_one_img(img_)
- face_bbox_square = face_bbox_to_square(face_info["bbox"])
- crop_image = img_.crop(face_bbox_square)
- crop_image = crop_image.resize((336, 336))
- crop_image = [crop_image]*len(cur_positive_prompts)
- face_embeds = torch.from_numpy(np.array([face_info["embedding"]]))
- face_embeds=torch.cat((face_embeds,)*len(cur_positive_prompts),dim=0)
+ crop_image=input_id_img_s_dict[character_key_str][0]
+ face_embeds=input_id_emb_s_dict[character_key_str][0]
face_embeds = face_embeds.to(device, dtype=torch.float16)
- id_images = pipe(
- prompt=cur_positive_prompts,
- negative_prompt=cur_negative_prompt,
- height=height,
- width=width,
- num_inference_steps=_num_steps,
- guidance_scale=guidance_scale,
- num_images_per_prompt=1,
- generator= generator,
- face_crop_image=crop_image,
- face_insightface_embeds=face_embeds,
- ).images
+ if id_length > 1:
+ id_images=[]
+ for index, i in enumerate(cur_positive_prompts):
+ id_image = pipe(
+ prompt=i,
+ negative_prompt=cur_negative_prompt[index],
+ height=height,
+ width=width,
+ num_inference_steps=_num_steps,
+ guidance_scale=guidance_scale,
+ num_images_per_prompt=1,
+ generator=generator,
+ face_crop_image=crop_image,
+ face_insightface_embeds=face_embeds,
+ ).images
+ id_images.append(id_image)
+ else:
+ id_images = pipe(
+ prompt=cur_positive_prompts,
+ negative_prompt=cur_negative_prompt,
+ height=height,
+ width=width,
+ num_inference_steps=_num_steps,
+ guidance_scale=guidance_scale,
+ num_images_per_prompt=1,
+ generator=generator,
+ face_crop_image=crop_image,
+ face_insightface_embeds=face_embeds,
+ ).images
else:
pipe.set_ip_adapter_scale([_Ip_Adapter_Strength])
id_images = pipe(
@@ -870,24 +891,42 @@ def process_generation(
).images
elif use_flux:
if pulid:
- id_image = resize_numpy_image_long(input_id_images_dict[character_key][0], 1024)
- use_true_cfg = abs(role_scale - 1.0) > 1e-2
- id_embeddings, uncond_id_embeddings = pipe.pulid_model.get_id_embedding(id_image,cal_uncond=use_true_cfg)
+ id_embeddings=input_id_emb_s_dict[character_key_str][0]
+ uncond_id_embeddings=input_id_emb_un_dict[character_key_str][0]
strength = _Ip_Adapter_Strength
- id_images= pipe.generate_image(
- prompt=cur_positive_prompts,
- seed=seed_,
- start_step=2,
- num_steps=_num_steps,
- height=height,
- width=width,
- id_embeddings=id_embeddings,
- uncond_id_embeddings=uncond_id_embeddings,
- id_weight=strength,
- guidance=guidance_scale,
- true_cfg=role_scale,
- max_sequence_length=128,
- )
+ if id_length > 1:
+ id_images = []
+ for index, i in enumerate(cur_positive_prompts):
+ id_image = pipe.generate_image(
+ prompt=i,
+ seed=seed_,
+ start_step=2,
+ num_steps=_num_steps,
+ height=height,
+ width=width,
+ id_embeddings=id_embeddings,
+ uncond_id_embeddings=uncond_id_embeddings,
+ id_weight=strength,
+ guidance=guidance_scale,
+ true_cfg=role_scale,
+ max_sequence_length=128,
+ )
+ id_images.append(id_image)
+ else:
+ id_images = pipe.generate_image(
+ prompt=cur_positive_prompts,
+ seed=seed_,
+ start_step=2,
+ num_steps=_num_steps,
+ height=height,
+ width=width,
+ id_embeddings=id_embeddings,
+ uncond_id_embeddings=uncond_id_embeddings,
+ id_weight=strength,
+ guidance=guidance_scale,
+ true_cfg=role_scale,
+ max_sequence_length=128,
+ )
else:
strength = _Ip_Adapter_Strength if _Ip_Adapter_Strength != 1 else 0.9
id_images = pipe(
@@ -903,42 +942,53 @@ def process_generation(
guidance_scale=guidance_scale,
generator=generator,
).images
+
elif story_maker and not make_dual_only:
img = input_id_images_dict[character_key][0]
- mask_image = pipeline_mask(img, return_mask=True) .convert('RGB')# outputs a pillow mask
- print(type(mask_image))
- mask_image.save("asdad.png")
- face_info = app_face.get(cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR))
- face_info = \
- sorted(face_info, key=lambda x: (x['bbox'][2] - x['bbox'][0]) * (x['bbox'][3] - x['bbox'][1]))[
- -1] # only use the maximum face
+ #print(character_key_str,input_id_images_dict)
+ mask_image = input_id_img_s_dict[character_key_str][0]
+ face_info=input_id_emb_s_dict[character_key_str][0]
+ cloth_info=None
+ if isinstance(control_img,torch.Tensor):
+ cloth_info=input_id_cloth_dict[character_key_str][0]
cur_negative_prompt = [cur_negative_prompt]
cur_negative_prompt = cur_negative_prompt * len(cur_positive_prompts) if len(
cur_negative_prompt) != len(cur_positive_prompts) else cur_negative_prompt
- id_images = pipe(
- image=img,
- mask_image=mask_image,
- face_info=face_info,
- prompt=cur_positive_prompts,
- negative_prompt=cur_negative_prompt,
- ip_adapter_scale=_Ip_Adapter_Strength, lora_scale=0.8,
- num_inference_steps=_num_steps,
- guidance_scale=guidance_scale,
- height=height, width=width,
- generator=generator,
- ).images
+ if id_length>1:
+ id_images = []
+ for index, i in enumerate(cur_positive_prompts):
+ id_image = pipe(
+ image=img,
+ mask_image=mask_image,
+ face_info=face_info,
+ prompt=i,
+ negative_prompt=cur_negative_prompt[index],
+ ip_adapter_scale=_Ip_Adapter_Strength, lora_scale=0.8,
+ num_inference_steps=_num_steps,
+ guidance_scale=guidance_scale,
+ height=height, width=width,
+ generator=generator,
+ cloth=cloth_info,
+ ).images
+ id_images.append(id_image)
+ else:
+ id_images = pipe(
+ image=img,
+ mask_image=mask_image,
+ face_info=face_info,
+ prompt=cur_positive_prompts,
+ negative_prompt=cur_negative_prompt,
+ ip_adapter_scale=_Ip_Adapter_Strength, lora_scale=0.8,
+ num_inference_steps=_num_steps,
+ guidance_scale=guidance_scale,
+ height=height, width=width,
+ generator=generator,
+ cloth=cloth_info,
+ ).images
+
else:
if photomake_mode == "v2":
- # 提取id
- # print("v2 mode load_chars", cur_positive_prompts, negative_prompt, character_key)
- img = input_id_images_dict[character_key][0] # input_id_images_dict {'[Taylor]': [pil], '[Lecun]': [pil]}
- img = np.array(img)
- img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
- from .utils.insightface_package import analyze_faces
- faces = analyze_faces(app_face, img, )
- id_embed_list = [torch.from_numpy((faces[0]['embedding']))]
- id_embeds = torch.stack(id_embed_list)
-
+ id_embeds = input_id_emb_s_dict[character_key_str][0]
id_images = pipe(
cur_positive_prompts,
input_id_images=input_id_images_dict[character_key],
@@ -972,8 +1022,18 @@ def process_generation(
p_num+=1
# total_results = id_images + total_results
# yield total_results
- for ind, img in enumerate(id_images):
- results_dict[ref_indexs[ind]] = img
+ if story_maker and not make_dual_only and id_length>1 and model_type == "img2img":
+ for index,ind in enumerate(character_index_dict[character_key]):
+ results_dict[ref_totals[ind]] = id_images[index]
+ elif pulid and id_length>1 and model_type == "img2img":
+ for index,ind in enumerate(character_index_dict[character_key]):
+ results_dict[ref_totals[ind]] = id_images[index]
+ elif kolor_face and id_length>1 and model_type == "img2img":
+ for index,ind in enumerate(character_index_dict[character_key]):
+ results_dict[ref_totals[ind]] = id_images[index]
+ else:
+ for ind, img in enumerate(id_images):
+ results_dict[ref_indexs[ind]] = img
# real_images = []
yield [results_dict[ind] for ind in results_dict.keys()]
write = False
@@ -984,21 +1044,22 @@ def process_generation(
else:
real_prompts_inds = [ind for ind in range(len(prompts))]
-
real_prompt_no, negative_prompt_style = apply_style_positive(style_name, "real_prompt")
negative_prompt=str(negative_prompt)+str(negative_prompt_style)
-
- for real_prompts_ind in real_prompts_inds: # 非角色流程
+ #print(f"real_prompts_inds is {real_prompts_inds}")
+ for real_prompts_ind in real_prompts_inds: #
real_prompt = replace_prompts[real_prompts_ind]
cur_character = get_ref_character(prompts[real_prompts_ind], character_dict)
- #print(cur_character)
- setup_seed(seed_)
+
+ if model_type=="txt2img":
+ setup_seed(seed_)
+ generator = torch.Generator(device=device).manual_seed(seed_)
+
if len(cur_character) > 1 and model_type == "img2img":
raise "Temporarily Not Support Multiple character in Ref Image Mode!"
- generator = torch.Generator(device=device).manual_seed(seed_)
cur_step = 0
real_prompt ,negative_prompt_style_no= apply_style_positive(style_name, real_prompt)
- print(f"sampler:{real_prompt}")
+ print(f"Sample real_prompt : {real_prompt}")
if model_type == "txt2img":
# print(results_dict,real_prompts_ind)
if use_flux:
@@ -1027,15 +1088,9 @@ def process_generation(
empty_img = Image.new('RGB', (height, width), (255, 255, 255))
if use_kolor:
if kolor_face:
- #print(real_prompt,negative_prompt)
- img_1= input_id_images_dict[cur_character[0]] if real_prompts_ind not in nc_indexs else empty_img
- #print(img_1)
- face_info = app_face.get_faceinfo_one_img(img_1)
- face_bbox_square = face_bbox_to_square(face_info["bbox"])
- crop_image = img_1.crop(face_bbox_square)
- crop_image = crop_image.resize((336, 336))
- crop_image = [crop_image]
- face_embeds = torch.from_numpy(np.array([face_info["embedding"]]))
+ empty_image = Image.new('RGB', (336, 336), (255, 255, 255))
+ crop_image=input_id_img_s_dict[cur_character[0]] if real_prompts_ind not in nc_indexs else empty_image
+ face_embeds=input_id_emb_s_dict[cur_character[0]][0] if real_prompts_ind not in nc_indexs else empty_emb_zero
face_embeds = face_embeds.to(device, dtype=torch.float16)
results_dict[real_prompts_ind] = pipe(
prompt=real_prompt,
@@ -1048,7 +1103,6 @@ def process_generation(
generator= generator,
face_crop_image=crop_image,
face_insightface_embeds=face_embeds,
- nc_flag=True if real_prompts_ind in nc_indexs else False, # nc_flag,用索引标记,主要控制非角色人物的生成,默认false
).images[0]
else:
results_dict[real_prompts_ind] = pipe(
@@ -1067,13 +1121,10 @@ def process_generation(
generator=generator,
nc_flag=True if real_prompts_ind in nc_indexs else False, # nc_flag,用索引标记,主要控制非角色人物的生成,默认false
).images[0]
-
elif use_flux:
if pulid:
- img_id = input_id_images_dict[cur_character[0]] if real_prompts_ind not in nc_indexs else empty_img
- id_image = resize_numpy_image_long(img_id, 1024)
- use_true_cfg = abs(role_scale - 1.0) > 1e-2
- id_embeddings, uncond_id_embeddings = pipe.pulid_model.get_id_embedding(id_image,cal_uncond=use_true_cfg)
+ id_embeddings=input_id_emb_s_dict[cur_character[0]][0] if real_prompts_ind not in nc_indexs else empty_emb_zero
+ uncond_id_embeddings=input_id_emb_un_dict[cur_character[0]][0] if real_prompts_ind not in nc_indexs else empty_emb_zero
strength = _Ip_Adapter_Strength
results_dict[real_prompts_ind] =pipe.generate_image(
prompt=real_prompt,
@@ -1086,7 +1137,7 @@ def process_generation(
uncond_id_embeddings=uncond_id_embeddings,
id_weight=strength,
guidance=guidance_scale,
- true_cfg=role_scale,
+ true_cfg=1.0,
max_sequence_length=128,
)
else:
@@ -1109,15 +1160,13 @@ def process_generation(
generator=generator,
).images[0]
elif story_maker and not make_dual_only:
- img_2= input_id_images_dict[cur_character[0]] if real_prompts_ind not in nc_indexs else input_id_images_dict[character_list[0]]
- img_2=img_2[0]
- mask_image = pipeline_mask(img_2, return_mask=True)
- mask_image.save("A2.png")
- mask_image = mask_image.convert('RGB') # outputs a pillow mask
- face_info = app_face.get(cv2.cvtColor(np.array(img_2), cv2.COLOR_RGB2BGR))
- face_info = \
- sorted(face_info, key=lambda x: (x['bbox'][2] - x['bbox'][0]) * (x['bbox'][3] - x['bbox'][1]))[
- -1] # only use the maximum face
+ cloth_info=None
+ if isinstance(control_img, torch.Tensor):
+ cloth_info = input_id_cloth_dict[cur_character[0]][0]
+ mask_image=input_id_img_s_dict[cur_character[0]][0]
+ img_2= input_id_images_dict[cur_character[0]][0] if real_prompts_ind not in nc_indexs else empty_img
+ face_info=input_id_emb_s_dict[cur_character[0]][0] if real_prompts_ind not in nc_indexs else empty_emb_zero
+
results_dict[real_prompts_ind] = pipe(
image=img_2,
mask_image=mask_image,
@@ -1129,30 +1178,13 @@ def process_generation(
guidance_scale=guidance_scale,
height=height, width=width,
generator=generator,
+ cloth=cloth_info,
).images[0]
else:
if photomake_mode == "v2":
# V2版本必须要有id_embeds,只能用input_id_images作为风格参考
- img = (
- input_id_images_dict[cur_character[0]]
- if real_prompts_ind not in nc_indexs
- else input_id_images_dict[character_list[0]]
- ),
- # print(img,type(img[0][0]))
-
- img = np.array(img[0][0])
- img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
- from .utils.insightface_package import analyze_faces
- faces = analyze_faces(app_face, img, )
- nc_flag = True if real_prompts_ind in nc_indexs else False # 有nc为ture,无则false
- if not nc_flag:
- id_embed_list = [torch.from_numpy((faces[0]['embedding']))]
- else:
- id_embed_list = [torch.zeros_like(torch.from_numpy((faces[0]['embedding'])))]
- id_embeds = torch.stack(id_embed_list)
-
- # print(real_prompts_ind, real_prompt,"v2 mode")
-
+ print(cur_character)
+ id_embeds = input_id_emb_s_dict[cur_character[0]][0] if real_prompts_ind not in nc_indexs else empty_emb_zero
results_dict[real_prompts_ind] = pipe(
real_prompt,
input_id_images=(
@@ -1168,7 +1200,7 @@ def process_generation(
negative_prompt=negative_prompt,
generator=generator,
id_embeds=id_embeds,
- nc_flag=True if real_prompts_ind in nc_indexs else False,
+ nc_flag= True if real_prompts_ind in nc_indexs else False,
).images[0]
else:
#print(real_prompts_ind, real_prompt, "v1 mode", )
@@ -1392,7 +1424,7 @@ def msdiffusion_main(image_1, image_2, prompts_dual, width, height, steps, seed,
)
# 添加Lora trigger
- add_trigger_words = "," + trigger_words + " " + "style" + ";"
+ add_trigger_words = " " + trigger_words + " style "
if lora:
prompts_dual = remove_punctuation_from_strings(prompts_dual)
if lora not in lora_lightning_list: # 加速lora不需要trigger
@@ -1489,7 +1521,8 @@ class Storydiffusion_Model_Loader:
"easy_function":("STRING", {"default": ""}),
},
"optional": {"image": ("IMAGE",),
- "control_image": ("IMAGE",), },
+ "control_image": ("IMAGE",),
+ "clip":("CLIP",),},
}
RETURN_TYPES = ("STORY_DICT", )
@@ -1506,12 +1539,14 @@ class Storydiffusion_Model_Loader:
repo = get_instance_path(model_path)
return repo
def story_model_loader(self,character_prompt, repo_id, ckpt_name, vae_id, character_weights, lora, lora_scale,controlnet_model, clip_vision,trigger_words, scheduler,
- sa32_degree, sa64_degree, width, height, photomake_mode, easy_function,**kwargs
- ):
+ sa32_degree, sa64_degree, width, height, photomake_mode, easy_function,**kwargs):
+
+ clip=kwargs.get("clip")
+
id_number=len(character_prompt.splitlines())
if id_number > 2:
id_number=2
- print(f"run in id number {id_number}")
+ print(f"run in id number : {id_number}")
image = kwargs.get("image")
if isinstance(image,torch.Tensor):
batch_num,_,_,_=image.size()
@@ -1536,7 +1571,24 @@ class Storydiffusion_Model_Loader:
control_image=kwargs.get("control_image")
if not isinstance(control_image, torch.Tensor):
raise "if using controlnet,need input a image in control_image"
-
+
+ photomaker_path = os.path.join(photomaker_dir, f"photomaker-{photomake_mode}.bin")
+ if photomake_mode=="v1":
+ if not os.path.exists(photomaker_path):
+ photomaker_path = hf_hub_download(
+ repo_id="TencentARC/PhotoMaker",
+ filename="photomaker-v1.bin",
+ local_dir=photomaker_dir,
+ )
+ else:
+ if not os.path.exists(photomaker_path):
+ photomaker_path = hf_hub_download(
+ repo_id="TencentARC/PhotoMaker-V2",
+ filename="photomaker-v2.bin",
+ local_dir=photomaker_dir,
+ )
+ photomake_mode_=photomake_mode
+
if clip_vision=="none":
clip_vision_path=None
else:
@@ -1558,6 +1610,7 @@ class Storydiffusion_Model_Loader:
auraface=True
else:
auraface = False
+
if "nf4" in easy_function:
NF4=True
else:
@@ -1577,10 +1630,17 @@ class Storydiffusion_Model_Loader:
filename="ipa-faceid-plus.bin",
local_dir=photomaker_dir,
)
+ photomake_mode=""
else:
kolor_face = False
face_ckpt=""
+ if "schnell" in easy_function:
+ flux_pulid_name="flux-schnell"
+ else:
+ flux_pulid_name = "flux-dev"
+
+
if "pulid" in easy_function:
pulid=True
pulid_ckpt=os.path.join(photomaker_dir, "pulid_flux_v0.9.0.safetensors")
@@ -1590,6 +1650,7 @@ class Storydiffusion_Model_Loader:
filename="pulid_flux_v0.9.0.safetensors",
local_dir=photomaker_dir,
)
+ photomake_mode = ""
else:
pulid = False
pulid_ckpt=""
@@ -1603,9 +1664,11 @@ class Storydiffusion_Model_Loader:
aggressive_offload=True
else:
aggressive_offload = False
+
make_dual_only = False
if "maker" in easy_function:
story_maker = True
+ photomake_mode = ""
if "dual" in easy_function:
make_dual_only = True
if not clip_vision_path:
@@ -1621,23 +1684,6 @@ class Storydiffusion_Model_Loader:
else:
story_maker = False
face_adapter=""
-
-
- photomaker_path = os.path.join(photomaker_dir, f"photomaker-{photomake_mode}.bin")
- if photomake_mode=="v1":
- if not os.path.exists(photomaker_path):
- photomaker_path = hf_hub_download(
- repo_id="TencentARC/PhotoMaker",
- filename="photomaker-v1.bin",
- local_dir=photomaker_dir,
- )
- else:
- if not os.path.exists(photomaker_path):
- photomaker_path = hf_hub_download(
- repo_id="TencentARC/PhotoMaker-V2",
- filename="photomaker-v2.bin",
- local_dir=photomaker_dir,
- )
if lora != "none":
lora_path = folder_paths.get_full_path("loras", lora)
@@ -1675,8 +1721,10 @@ class Storydiffusion_Model_Loader:
repo_id.replace("\\","/")
if repo_id.rsplit("/")[-1].lower() in "kwai-kolors/kolors":
use_kolor=True
+ photomake_mode = ""
if repo_id.rsplit("/")[-1] .lower() in "black-forest-labs/flux.1-dev,black-forest-labs/flux.1-schnell":
use_flux = True
+ photomake_mode = ""
if use_kolor:
if model_type=="img2img" and not kolor_face:
@@ -1687,6 +1735,7 @@ class Storydiffusion_Model_Loader:
filename="ip_adapter_plus_general.bin",
local_dir=photomaker_dir,
)
+ photomake_mode = ""
if not repo_id and not ckpt_path:
raise "you need choice a model or repo_id"
@@ -1712,11 +1761,30 @@ class Storydiffusion_Model_Loader:
pipe.load_storymaker_adapter(image_encoder, face_adapter, scale=0.8, lora_scale=0.8)
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
else:
+ photomake_mode_1 = photomake_mode_
pipe = load_models(ckpt_path, model_type=model_type, single_files=True, use_safetensors=True,
- photomake_mode=photomake_mode, photomaker_path=photomaker_path, lora=lora,
+ photomake_mode=photomake_mode_1, photomaker_path=photomaker_path, lora=lora,
lora_path=lora_path,
trigger_words=trigger_words, lora_scale=lora_scale)
set_attention_processor(pipe.unet, id_length, is_ipadapter=False)
+ elif "flux" in ckpt_path.lower():
+ use_flux=True
+ if pulid:
+ from .PuLID.app_flux import FluxGenerator
+ if not clip_vision_path:
+ raise "need 'EVA02_CLIP_L_336_psz14_s6B.pt' in comfyUI/models/clip_vision"
+ if NF4:
+ quantized_mode = "nf4"
+ if vae_id == "none":
+ raise "Now,using pulid must choice ae from comfyUI vae menu"
+ else:
+ vae_path = folder_paths.get_full_path("vae", vae_id)
+ pipe = FluxGenerator(flux_pulid_name, ckpt_path, "cuda", offload=True,
+ aggressive_offload=aggressive_offload, pretrained_model=pulid_ckpt,
+ quantized_mode=quantized_mode, clip_vision_path=clip_vision_path, clip_cf=clip,
+ vae_cf=vae_path)
+ else:
+ raise "need choice a SDXL checkpoint"
else:
pipe = load_models(ckpt_path, model_type=model_type, single_files=True, use_safetensors=True,
photomake_mode=photomake_mode, photomaker_path=photomaker_path, lora=lora,
@@ -1890,9 +1958,13 @@ class Storydiffusion_Model_Loader:
raise "need 'EVA02_CLIP_L_336_psz14_s6B.pt' in comfyUI/models/clip_vision"
if NF4:
quantized_mode="nf4"
- pipe = FluxGenerator(repo_id, ckpt_path, "cuda", offload=True,
+ if vae_id=="none":
+ raise "Now,using pulid must choice ae from comfyUI vae menu"
+ else:
+ vae_path = folder_paths.get_full_path("vae", vae_id)
+ pipe = FluxGenerator(flux_pulid_name, ckpt_path, "cuda", offload=True,
aggressive_offload=aggressive_offload, pretrained_model=pulid_ckpt,
- quantized_mode=quantized_mode,clip_vision_path=clip_vision_path)
+ quantized_mode=quantized_mode,clip_vision_path=clip_vision_path,clip_cf=clip,vae_cf=vae_path)
else:
if NF4:
# https://github.com/huggingface/diffusers/issues/9165
@@ -2013,16 +2085,37 @@ class Storydiffusion_Model_Loader:
# if device != "mps":
# pipe.enable_model_cpu_offload()
torch.cuda.empty_cache()
- if story_maker:
- from insightface.app import FaceAnalysis
- app_face = FaceAnalysis(name='buffalo_l', root='./',
- providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
- app_face.prepare(ctx_id=0, det_size=(640, 640))
- from transformers import pipeline
- pipeline_mask = pipeline("image-segmentation", model="briaai/RMBG-1.4", trust_remote_code=True)
- # pillow_mask = pipe(image_path, return_mask=True) # outputs a pillow mask
- elif use_kolor:
- if kolor_face:
+ # need get emb
+ character_name_dict_, character_list_ = character_to_dict(character_prompt, lora, trigger_words)
+ #print(character_list_)
+ #global input_id_img_s_dict,input_id_emb_s_dict,input_id_emb_un_dict,input_id_cloth_dict
+ miX_mode = False
+ if model_type=="img2img":
+ d1, _, _, _ = image.size()
+ if d1 == 1:
+ image_load = [nomarl_upscale(image, width, height)]
+ else:
+ img_list = list(torch.chunk(image, chunks=d1))
+ image_load = [nomarl_upscale(img, width, height) for img in img_list]
+
+ if photomake_mode == "v2":
+ from .utils.insightface_package import FaceAnalysis2, analyze_faces
+ if auraface:
+ from huggingface_hub import snapshot_download
+ snapshot_download(
+ "fal/AuraFace-v1",
+ local_dir="models/auraface",
+ )
+ app_face = FaceAnalysis2(name="auraface",
+ providers=["CUDAExecutionProvider", "CPUExecutionProvider"], root=".",
+ allowed_modules=['detection', 'recognition'])
+ else:
+ app_face = FaceAnalysis2(providers=['CUDAExecutionProvider'],
+ allowed_modules=['detection', 'recognition'])
+ app_face.prepare(ctx_id=0, det_size=(640, 640))
+ pipeline_mask = None
+ app_face_ = None
+ elif kolor_face:
from .kolors.models.sample_ipadapter_faceid_plus import FaceInfoGenerator
from huggingface_hub import snapshot_download
snapshot_download(
@@ -2030,33 +2123,155 @@ class Storydiffusion_Model_Loader:
local_dir='models/antelopev2',
)
app_face = FaceInfoGenerator(root_dir=".")
+ pipeline_mask=None
+ app_face_ = None
+ elif story_maker:
+ from insightface.app import FaceAnalysis
+ from transformers import pipeline
+ pipeline_mask = pipeline("image-segmentation", model="briaai/RMBG-1.4",
+ trust_remote_code=True)
+ if make_dual_only: # 前段用story 双人用maker
+ if photomake_mode_ == "v2":
+ from .utils.insightface_package import FaceAnalysis2
+ if auraface:
+ from huggingface_hub import snapshot_download
+ snapshot_download(
+ "fal/AuraFace-v1",
+ local_dir="models/auraface",
+ )
+ app_face = FaceAnalysis2(name="auraface",
+ providers=["CUDAExecutionProvider", "CPUExecutionProvider"],
+ root=".",
+ allowed_modules=['detection', 'recognition'])
+ else:
+ app_face = FaceAnalysis2(providers=['CUDAExecutionProvider'],
+ allowed_modules=['detection', 'recognition'])
+ app_face.prepare(ctx_id=0, det_size=(640, 640))
+ app_face_ = FaceAnalysis(name='buffalo_l', root='./',
+ providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
+ app_face_.prepare(ctx_id=0, det_size=(640, 640))
+ else:
+ app_face = FaceAnalysis(name='buffalo_l', root='./',
+ providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
+ app_face.prepare(ctx_id=0, det_size=(640, 640))
+ app_face_ = None
+ else:
+ app_face = FaceAnalysis(name='buffalo_l', root='./',
+ providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
+ app_face.prepare(ctx_id=0, det_size=(640, 640))
+ app_face_ = None
else:
app_face=None
- pipeline_mask = None
- elif photomake_mode == "v2":
- from .utils.insightface_package import FaceAnalysis2, analyze_faces
- if auraface:
- from huggingface_hub import snapshot_download
- snapshot_download(
- "fal/AuraFace-v1",
- local_dir="models/auraface",
- )
- app_face = FaceAnalysis2(name="auraface",
- providers=["CUDAExecutionProvider", "CPUExecutionProvider"], root=".",
- allowed_modules=['detection', 'recognition'])
+ pipeline_mask=None
+ app_face_=None
+ input_id_emb_s_dict = {}
+ input_id_img_s_dict = {}
+ input_id_emb_un_dict ={}
+ for ind, img in enumerate(image_load):
+ if photomake_mode == "v2":
+ from .utils.insightface_package import analyze_faces
+ img = np.array(img)
+ img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
+ faces = analyze_faces(app_face, img,)
+ id_embed_list = torch.from_numpy((faces[0]['embedding']))
+ crop_image=img
+ uncond_id_embeddings = None
+ elif kolor_face:
+ device = (
+ "cuda"
+ if torch.cuda.is_available()
+ else "mps" if torch.backends.mps.is_available() else "cpu"
+ )
+ face_info = app_face.get_faceinfo_one_img(img)
+ face_bbox_square = face_bbox_to_square(face_info["bbox"])
+ crop_image = img.crop(face_bbox_square)
+ crop_image = crop_image.resize((336, 336))
+ face_embeds = torch.from_numpy(np.array([face_info["embedding"]]))
+ id_embed_list = face_embeds.to(device, dtype=torch.float16)
+ uncond_id_embeddings = None
+ elif story_maker:
+ if make_dual_only: #前段用story 双人用maker
+ if photomake_mode_=="v2":
+ from .utils.insightface_package import analyze_faces
+ img = np.array(img)
+ img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
+ faces = analyze_faces(app_face, img, )
+ id_embed_list = torch.from_numpy((faces[0]['embedding']))
+ crop_image = pipeline_mask(img, return_mask=True).convert('RGB') # outputs a pillow mask
+ face_info = app_face_.get(cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR))
+ uncond_id_embeddings = \
+ sorted(face_info,
+ key=lambda x: (x['bbox'][2] - x['bbox'][0]) * (x['bbox'][3] - x['bbox'][1]))[
+ -1] # only use the maximum face
+ photomake_mode="v2"
+ miX_mode=True
+ # make+v2模式下,emb存v2的向量,corp 和 unemb 存make的向量
+ else: #V1不需要调用emb
+ crop_image = pipeline_mask(img, return_mask=True).convert('RGB') # outputs a pillow mask
+ face_info = app_face.get(cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR))
+ id_embed_list = \
+ sorted(face_info,
+ key=lambda x: (x['bbox'][2] - x['bbox'][0]) * (x['bbox'][3] - x['bbox'][1]))[
+ -1] # only use the maximum face
+ uncond_id_embeddings = None
+ else: #全程用maker
+ crop_image = pipeline_mask(img, return_mask=True).convert('RGB') # outputs a pillow mask
+ #timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
+ #crop_image.copy().save(os.path.join(folder_paths.get_output_directory(),f"{timestamp}_mask.png"))
+ face_info = app_face.get(cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR))
+ id_embed_list = \
+ sorted(face_info,
+ key=lambda x: (x['bbox'][2] - x['bbox'][0]) * (x['bbox'][3] - x['bbox'][1]))[
+ -1] # only use the maximum face
+
+ uncond_id_embeddings = None
+ elif pulid:
+ id_image = resize_numpy_image_long(img, 1024)
+ use_true_cfg = abs(1.0 - 1.0) > 1e-2
+ id_embed_list, uncond_id_embeddings = pipe.pulid_model.get_id_embedding(id_image,cal_uncond=use_true_cfg)
+ crop_image = img
+ else:
+ id_embed_list=None
+ uncond_id_embeddings = None
+ crop_image = None
+ input_id_img_s_dict[character_list_[ind]] = [crop_image]
+ input_id_emb_s_dict[character_list_[ind]] = [id_embed_list]
+ input_id_emb_un_dict[character_list_[ind]] = [uncond_id_embeddings]
+
+ if story_maker or kolor_face or photomake_mode == "v2" :
+ del app_face
+ torch.cuda.empty_cache()
+ if story_maker:
+ del pipeline_mask
+ torch.cuda.empty_cache()
+
+ if isinstance(control_image,torch.Tensor) and story_maker:
+ e1, _, _, _ = control_image.size()
+ if e1 == 1:
+ cn_image_load = [nomarl_upscale(control_image, width, height)]
+ else:
+ img_list = list(torch.chunk(control_image, chunks=e1))
+ cn_image_load = [nomarl_upscale(img, width, height) for img in img_list]
+ input_id_cloth_dict = {}
+ for ind, img in enumerate(cn_image_load):
+ input_id_cloth_dict[character_list_[ind]] = [img]
else:
- app_face = FaceAnalysis2(providers=['CUDAExecutionProvider'],
- allowed_modules=['detection', 'recognition'])
- app_face.prepare(ctx_id=0, det_size=(640, 640))
- pipeline_mask = None
+ input_id_cloth_dict={}
else:
- app_face=None
- pipeline_mask=None
+ input_id_emb_s_dict = {}
+ input_id_img_s_dict = {}
+ input_id_emb_un_dict ={}
+ input_id_cloth_dict = {}
+ #print(input_id_img_s_dict)
+ role_name_list = [i for i in character_name_dict_.keys()]
+ #print( role_name_list)
model={"pipe":pipe,"use_flux":use_flux,"use_kolor":use_kolor,"photomake_mode":photomake_mode,"trigger_words":trigger_words,"lora_scale":lora_scale,
"load_chars":load_chars,"repo_id":repo_id,"lora_path":lora_path,"ckpt_path":ckpt_path,"model_type":model_type, "lora": lora,
- "scheduler":scheduler,"auraface":auraface,"width":width,"height":height,"kolor_face":kolor_face,"pulid":pulid,"story_maker":story_maker,
+ "scheduler":scheduler,"width":width,"height":height,"kolor_face":kolor_face,"pulid":pulid,"story_maker":story_maker,
"make_dual_only":make_dual_only,"face_adapter":face_adapter,"clip_vision_path":clip_vision_path,
- "controlnet_path":controlnet_path,"character_prompt":character_prompt,"image":image,"control_image":control_image,"app_face":app_face,"pipeline_mask":pipeline_mask}
+ "controlnet_path":controlnet_path,"character_prompt":character_prompt,"image":image,"control_image":control_image,
+ "input_id_emb_s_dict":input_id_emb_s_dict,"input_id_img_s_dict":input_id_img_s_dict,
+ "input_id_emb_un_dict":input_id_emb_un_dict,"input_id_cloth_dict":input_id_cloth_dict,"role_name_list":role_name_list,"miX_mode":miX_mode}
return (model,)
@@ -2071,7 +2286,6 @@ class Storydiffusion_Sampler:
"model": ("STORY_DICT",),
"scene_prompts": ("STRING", {"multiline": True,
"default": "[Taylor] wake up in the bed ;\n[Taylor] have breakfast by the window;\n[Lecun] drving a car;\n[Lecun] is working."}),
- "split_prompt": ("STRING", {"default": ""}),
"negative_prompt": ("STRING", {"multiline": True,
"default": "bad anatomy, bad hands, missing fingers, extra fingers, "
"three hands, three legs, bad arms, missing legs, "
@@ -2123,7 +2337,7 @@ class Storydiffusion_Sampler:
bottom = (height + new_height) / 2
return img.crop((left, top, right, bottom))
- def story_sampler(self, model,scene_prompts,split_prompt, negative_prompt, img_style, seed, steps,
+ def story_sampler(self, model,scene_prompts, negative_prompt, img_style, seed, steps,
cfg, ip_adapter_strength, style_strength_ratio,
role_scale, mask_threshold, start_step,save_character,controlnet_scale,guidance_list,):
# get value from dict
@@ -2139,7 +2353,6 @@ class Storydiffusion_Sampler:
ckpt_path=model.get("ckpt_path")
lora=model.get("lora")
lora_scale =model.get("lora_scale")
- auraface=model.get("auraface")
scheduler=model.get("scheduler")
height=model.get("height")
width = model.get("width")
@@ -2154,30 +2367,35 @@ class Storydiffusion_Sampler:
character_prompt=model.get("character_prompt")
control_image=model.get("control_image")
image=model.get("image")
- pipeline_mask=model.get("pipeline_mask")
- app_face=model.get("app_face")
+
+ input_id_emb_s_dict = model.get("input_id_emb_s_dict")
+ input_id_img_s_dict = model.get("input_id_img_s_dict")
+ input_id_emb_un_dict = model.get("input_id_emb_un_dict")
+ input_id_cloth_dict = model.get("input_id_cloth_dict")
+ role_name_list=model.get("role_name_list")
+ miX_mode=model.get("miX_mode")
+
+ #print(input_id_emb_s_dict,input_id_img_s_dict,input_id_emb_un_dict,role_name_list) #'[Taylor]',['[Taylor]']
+
+ empty_emb_zero = None
+ if pulid or kolor_face or photomake_mode=="v2":
+ empty_emb_zero = torch.zeros_like(input_id_emb_s_dict[role_name_list[0]][0]).to(device)
+
# 格式化文字内容
- if split_prompt:
- scene_prompts.replace("\n", "").replace(split_prompt, ";\n").strip()
- character_prompt.replace("\n", "").replace(split_prompt, ";\n").strip()
- else:
- scene_prompts.strip()
- character_prompt.strip()
- if "\n" not in scene_prompts:
- scene_prompts.replace(";", ";\n").strip()
- if "\n" in character_prompt:
- if character_prompt.count("\n") > 1:
- character_prompt.replace("\n", "").replace("[", "\n[").strip()
- if character_prompt.count("\n") > 1:
- character_prompt.replace("\n", "").replace("[", "\n[", 2).strip() # 多行角色在这里强行转为双角色
-
+ scene_prompts.strip()
+ character_prompt.strip()
# 从角色列表获取角色方括号信息
char_origin = character_prompt.splitlines()
+ char_origin=[i for i in char_origin if "[" in i]
+ #print(char_origin)
char_describe = char_origin # [A a men...,B a girl ]
char_origin = [char.split("]")[0] + "]" for char in char_origin]
-
+ #print(char_origin)
# 判断是否有双角色prompt,如果有,获取双角色列表及对应的位置列表,
prompts_origin = scene_prompts.splitlines()
+ prompts_origin=[i.strip() for i in prompts_origin]
+ prompts_origin = [i for i in prompts_origin if "[" in i]
+ #print(prompts_origin)
positions_dual = [index for index, prompt in enumerate(prompts_origin) if len(extract_content_from_brackets(prompt))>=2] #改成单句中双方括号方法,利于MS组句,[A]... [B]...[C]
prompts_dual = [prompt for prompt in prompts_origin if len(extract_content_from_brackets(prompt))>=2]
@@ -2205,7 +2423,8 @@ class Storydiffusion_Sampler:
height,
load_chars,
lora,
- trigger_words,photomake_mode,use_kolor,use_flux,make_dual_only,kolor_face,pulid,story_maker,app_face, pipeline_mask,role_scale)
+ trigger_words,photomake_mode,use_kolor,use_flux,make_dual_only,
+ kolor_face,pulid,story_maker,input_id_emb_s_dict, input_id_img_s_dict,input_id_emb_un_dict, input_id_cloth_dict,role_scale,control_image,empty_emb_zero,miX_mode)
else:
if story_maker:
@@ -2221,7 +2440,8 @@ class Storydiffusion_Sampler:
height,
load_chars,
lora,
- trigger_words,photomake_mode,use_kolor,use_flux,make_dual_only,kolor_face,pulid,story_maker,app_face, pipeline_mask,role_scale)
+ trigger_words,photomake_mode,use_kolor,use_flux,make_dual_only,kolor_face,
+ pulid,story_maker,input_id_emb_s_dict, input_id_img_s_dict,input_id_emb_un_dict, input_id_cloth_dict,role_scale,control_image,empty_emb_zero,miX_mode)
for value in gen:
print(type(value))
@@ -2271,26 +2491,28 @@ class Storydiffusion_Sampler:
image_encoder = clip_load(clip_vision_path)
pipe.load_storymaker_adapter(image_encoder, face_adapter, scale=0.8, lora_scale=0.8)
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
-
- mask_image_1 = pipeline_mask(image_a, return_mask=True).convert('RGB') # applies mask on input and returns a pillow image
- mask_image_2 = pipeline_mask(image_b, return_mask=True).convert('RGB') # applies mask on input and returns a pillow image
- #mask_image_1.save("asdad1.png")
-
- face_info_1 = app_face.get(cv2.cvtColor(np.array(image_a), cv2.COLOR_RGB2BGR))
- face_info_1 = \
- sorted(face_info_1, key=lambda x: (x['bbox'][2] - x['bbox'][0]) * (x['bbox'][3] - x['bbox'][1]))[
- -1] # only use the maximum face
- face_info_2 = app_face.get(cv2.cvtColor(np.array(image_b), cv2.COLOR_RGB2BGR))
- face_info_2 = \
- sorted(face_info_2, key=lambda x: (x['bbox'][2] - x['bbox'][0]) * (x['bbox'][3] - x['bbox'][1]))[
- -1] # only use the maximum face
- # get n p prompt
+ mask_image_1=input_id_img_s_dict[role_name_list[0]][0]
+ mask_image_2=input_id_img_s_dict[role_name_list[1]][0]
+
+ if photomake_mode=="v2":
+ face_info_1 = input_id_emb_un_dict[role_name_list[0]][0]
+ face_info_2 = input_id_emb_un_dict[role_name_list[1]][0]
+ else:
+ face_info_1 = input_id_emb_s_dict[role_name_list[0]][0]
+ face_info_2 = input_id_emb_s_dict[role_name_list[1]][0]
+
+ cloth_info_1=None
+ cloth_info_2=None
+ if isinstance(control_image,torch.Tensor):
+ cloth_info_1 = input_id_cloth_dict[role_name_list[0]][0]
+ cloth_info_2 = input_id_cloth_dict[role_name_list[1]][0]
+
prompts_dual, negative_prompt = apply_style(
img_style, prompts_dual, negative_prompt
)
# 添加Lora trigger
- add_trigger_words = "," + trigger_words + " " + "style" + ";"
+ add_trigger_words = " " + trigger_words+ " style "
if lora:
prompts_dual = remove_punctuation_from_strings(prompts_dual)
if lora not in lora_lightning_list: # 加速lora不需要trigger
@@ -2303,7 +2525,8 @@ class Storydiffusion_Sampler:
prompts_dual = [item.replace("[", " ", ).replace("]", " ", ) for item in prompts_dual]
image_dual = []
- setup_seed(seed)
+ if model_type=="txt2img":
+ setup_seed(seed)
generator = torch.Generator(device='cuda').manual_seed(seed)
for i,prompt in enumerate(prompts_dual):
output = pipe(
@@ -2316,6 +2539,8 @@ class Storydiffusion_Sampler:
guidance_scale=cfg,
height=height, width=width,
generator=generator,
+ cloth=cloth_info_1,
+ cloth_2=cloth_info_2
).images[0]
image_dual.append(output)
else: #using ms diffusion
diff --git a/examples/flux.png b/examples/flux.png
new file mode 100644
index 0000000000000000000000000000000000000000..703da8905e7f58aaf8a883463372630e21e721e5
GIT binary patch
literal 319946
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