From 431aeb08eab443cc24ee3d4618d8e6d518428088 Mon Sep 17 00:00:00 2001 From: type-two Date: Sat, 25 Jul 2026 00:54:25 +1000 Subject: [PATCH] feat(mflux_image_edit): Qwen-Image-Edit LoRA support mflux-generate-qwen-edit already exposes --lora-paths/--lora-scales; wire them to params.loras (['multiple-angles=1.0', ...]). Names resolve by filename stem or fuzzy match across QWEN_LORA_DIRS (default ~/Documents/localmodels/QwenLora) so John can drop new .safetensors in and use them by short name. Misses log loudly instead of failing the job. Co-Authored-By: Claude Fable 5 --- server/operators/mflux_image_edit/run.py | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/server/operators/mflux_image_edit/run.py b/server/operators/mflux_image_edit/run.py index c020cac..ba1011b 100644 --- a/server/operators/mflux_image_edit/run.py +++ b/server/operators/mflux_image_edit/run.py @@ -1,9 +1,30 @@ import argparse import json +import os import subprocess import sys from pathlib import Path +# Qwen-Image-Edit LoRA search path (name → file). Drop .safetensors anywhere under these and they +# become usable as {"loras": ["multiple-angles=1.0", ...]}. Same UX as sd_local's LoRA list. +QWEN_LORA_DIRS = [Path(os.path.expanduser(d)) for d in os.environ.get( + "QWEN_LORA_DIRS", "~/Documents/localmodels/QwenLora").split(":")] + + +def find_lora(stem: str): + """Match by exact filename stem, else the first file whose stem contains it (case-insensitive).""" + stem = stem.strip() + cands = [f for d in QWEN_LORA_DIRS if d.is_dir() for f in d.rglob("*.safetensors")] + for f in cands: + if f.stem == stem: + return f + low = stem.lower() + for f in cands: + if low in f.stem.lower() or low in f.parent.name.lower(): + return f + return None + + ap = argparse.ArgumentParser() ap.add_argument("--input", action="append", default=[]) ap.add_argument("--outdir", required=True) @@ -34,6 +55,20 @@ cmd = [str(cli), "--image-paths", *[str(Path(x).resolve()) for x in a.input], "--guidance", str(p.get("guidance", 4.0)), "--output", str(out.resolve())] +# optional LoRAs: ["multiple-angles=1.0", "gymnastics-pose=0.8"] → --lora-paths/--lora-scales +paths, scales = [], [] +for spec in (p.get("loras") or []): + name, _, w = str(spec).partition("=") + f = find_lora(name) + if not f: + print(f"LORA MISS '{name}' — searched {[str(d) for d in QWEN_LORA_DIRS]}", flush=True) + continue + paths.append(str(f)) + scales.append(str(float(w or 1.0))) +if paths: + cmd += ["--lora-paths", *paths, "--lora-scales", *scales] + print(f"loras: {list(zip(paths, scales))}", flush=True) + print("+", " ".join(cmd), flush=True) print("(first run downloads Qwen-Image-Edit weights)", flush=True) res = subprocess.run(cmd, cwd=str(outdir), stdout=sys.stdout, stderr=subprocess.STDOUT)