163 lines
6.1 KiB
Python
163 lines
6.1 KiB
Python
import time
|
|
import random
|
|
import os
|
|
import numpy as np
|
|
from mflux.config.model_config import ModelConfig
|
|
from mflux.config.config import Config
|
|
from mflux.flux.flux import Flux1
|
|
from mflux.controlnet.flux_controlnet import Flux1Controlnet
|
|
from pathlib import Path
|
|
from folder_paths import get_filename_list, get_output_directory, models_dir
|
|
from huggingface_hub import snapshot_download
|
|
import torch
|
|
import json
|
|
|
|
flux_cache = {}
|
|
|
|
def download_hg_model(model_id, path, exDir="mflux"):
|
|
model_checkpoint = os.path.join(models_dir, exDir, path)
|
|
if not os.path.exists(model_checkpoint):
|
|
print(f"Downloading model {model_id} to {model_checkpoint}...")
|
|
try:
|
|
snapshot_download(repo_id=model_id, local_dir=model_checkpoint, local_dir_use_symlinks=False)
|
|
except Exception as e:
|
|
print(f"Error downloading model {model_id}: {e}")
|
|
return None
|
|
else:
|
|
print(f"Model {model_id} already exists at {model_checkpoint}. Skipping download.")
|
|
return model_checkpoint
|
|
|
|
def load_or_create_flux(model, path, lora_paths, lora_scales, is_controlnet=False):
|
|
key = (model, path, tuple(lora_paths), tuple(lora_scales), is_controlnet)
|
|
if key not in flux_cache:
|
|
FluxClass = Flux1Controlnet if is_controlnet else Flux1
|
|
flux_cache[key] = FluxClass(
|
|
model_config=ModelConfig.from_alias(model),
|
|
local_path=path,
|
|
lora_paths=lora_paths,
|
|
lora_scales=lora_scales,
|
|
)
|
|
# This code is licensed under the Apache 2.0 License.
|
|
# Portions of this code are derived from the work of CharafChnioune at https://github.com/CharafChnioune/MFLUX-WEBUI
|
|
return flux_cache[key]
|
|
|
|
|
|
def generate_image(prompt, model, seed, height, width, steps, guidance, path, lora_files, lora_scales_str):
|
|
lora_paths = lora_files if lora_files else []
|
|
lora_scales = [float(s.strip()) for s in lora_scales_str.split(",")] if lora_scales_str else []
|
|
|
|
seed = random.randint(0, 0xffffffffffffffff) if seed == -1 else int(seed)
|
|
print(f"Using seed: {seed}")
|
|
|
|
flux = load_or_create_flux(model, path if path else None, lora_paths, lora_scales)
|
|
|
|
image = flux.generate_image(
|
|
seed=seed,
|
|
prompt=prompt,
|
|
config=Config(
|
|
num_inference_steps=steps,
|
|
height=height,
|
|
width=width,
|
|
guidance=guidance,
|
|
),
|
|
)
|
|
|
|
inner_image = image.image
|
|
|
|
inner_image_numpy = np.array(inner_image).astype(np.float32)
|
|
|
|
normalized = inner_image_numpy / 255.0
|
|
|
|
tensor_image = torch.from_numpy(normalized).permute(2, 0, 1)
|
|
tensor_image = tensor_image.unsqueeze(0)
|
|
tensor_image = tensor_image.permute(0, 2, 3, 1)
|
|
|
|
return inner_image, tensor_image
|
|
|
|
class QuickMfluxNode:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"multiline": True, "dynamicPrompts": True, "tooltip": "The text to be encoded.", "default": "Luxury food photograph"}),
|
|
"model": (["dev", "schnell"], {"default": "schnell"}),
|
|
"path": (["flux.1-schnell-mflux-4bit", "flux.1-dev-mflux-4bit"], {"default": "flux.1-schnell-mflux-4bit"}),
|
|
"seed": ("INT", {"default": -1, "min": -1, "max": 0xffffffffffffffff}),
|
|
"height": ("INT", {"default": 1024, "min": 256, "max": 1024}),
|
|
"width": ("INT", {"default": 1024, "min": 256, "max": 1024}),
|
|
"steps": ("INT", {"default": 2, "min": 1}),
|
|
"guidance": ("FLOAT", {"default": 3.5, "min": 0.0}),
|
|
"metadata": (["true", "false"], {"default": "false"}),
|
|
},
|
|
"optional": {
|
|
"lora_files": ("LIST", {"default": []}),
|
|
"lora_scales": ("STRING", {"default": ""}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
CATEGORY = "Mflux/Quick"
|
|
FUNCTION = "generate"
|
|
|
|
def generate(self, prompt, model, path, seed, height, width, steps, guidance, metadata="false", lora_files=[], lora_scales=""):
|
|
model_id = f"madroid/{path}"
|
|
full_model_path = download_hg_model(model_id, path)
|
|
|
|
if model == "dev":
|
|
steps = max(steps, 14)
|
|
elif model == "schnell":
|
|
steps = min(max(steps, 2), 4)
|
|
|
|
inner_image, tensor_image = generate_image(prompt, model, seed, height, width, steps, guidance, full_model_path, lora_files, lora_scales)
|
|
|
|
if metadata == "true":
|
|
self.save_images([inner_image], prompt, model, path, seed, height, width, steps, guidance)
|
|
|
|
print(f"Generated tensor image with shape: {tensor_image.shape}, dtype: {tensor_image.dtype}")
|
|
|
|
return (tensor_image,)
|
|
|
|
def save_images(self, images, prompt, model, path, seed, height, width, steps, guidance, filename_prefix="Mflux"):
|
|
output_dir = get_output_directory()
|
|
mflux_dir = os.path.join(output_dir, "Mflux")
|
|
os.makedirs(mflux_dir, exist_ok=True)
|
|
|
|
results = []
|
|
existing_files = os.listdir(mflux_dir)
|
|
existing_count = len([f for f in existing_files if f.startswith(filename_prefix)])
|
|
|
|
for (batch_number, img) in enumerate(images):
|
|
filename = f"{filename_prefix}_{existing_count + batch_number:05}.png"
|
|
file_path = os.path.join(mflux_dir, filename)
|
|
img.save(file_path)
|
|
|
|
metadata = {
|
|
"prompt": prompt,
|
|
"model": model,
|
|
"path": path,
|
|
"seed": seed,
|
|
"height": height,
|
|
"width": width,
|
|
"steps": steps,
|
|
"guidance": guidance,
|
|
}
|
|
|
|
metadata_filename = os.path.join(mflux_dir, f"{filename_prefix}_{existing_count + batch_number:05}.json")
|
|
with open(metadata_filename, 'w') as metadata_file:
|
|
json.dump(metadata, metadata_file)
|
|
|
|
results.append({
|
|
"filename": filename,
|
|
"path": file_path,
|
|
})
|
|
|
|
return results
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"QuickMfluxNode": QuickMfluxNode,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"QuickMfluxNode": "Quick MFlux Generation"
|
|
}
|