import gradio as gr from gradio_client import Client, handle_file import os import tempfile import shutil HF_TOKEN = os.environ.get("HF_TOKEN", "") REMOTE_SPACE = "signsur4739379373/qwen-image-edit-rapid-aio-nsfw-v23" _client = None def get_client(): global _client if _client is None: _client = Client(REMOTE_SPACE, hf_token=HF_TOKEN) return _client def extract_paths(images): paths = [] if not images: return paths for item in images: if isinstance(item, dict) and "path" in item: paths.append(item["path"]) elif isinstance(item, dict) and "url" in item: paths.append(item["url"]) elif isinstance(item, (list, tuple)) and len(item) > 0: if isinstance(item[0], dict) and "path" in item[0]: paths.append(item[0]["path"]) elif isinstance(item[0], dict) and "url" in item[0]: paths.append(item[0]["url"]) elif isinstance(item[0], str): paths.append(item[0]) elif isinstance(item, str): paths.append(item) return paths def generate(images, prompt, steps, guidance, seed, randomize, rewrite, width, height, chain_in): if not images: raise gr.Error("Upload at least one source image first.") if not prompt or not prompt.strip(): raise gr.Error("Write an edit prompt first.") img_paths = extract_paths(images) if not img_paths: raise gr.Error("Could not extract image file paths from upload.") # Convert dims: 256 means auto/None w = width if width and width > 256 else None h = height if height and height > 256 else None try: client = get_client() # Try named API first, then positional result = None last_err = None try: result = client.predict( images=img_paths, prompt=prompt.strip(), seed=int(seed) if seed else 0, randomize_seed=bool(randomize), num_inference_steps=int(steps), true_guidance_scale=float(guidance), rewrite_prompt=bool(rewrite), width=w, height=h, api_name="/infer", hf_token=HF_TOKEN, ) except Exception as e1: last_err = e1 try: # Positional fallback result = client.predict( img_paths, prompt.strip(), int(seed) if seed else 0, bool(randomize), int(steps), float(guidance), bool(rewrite), w, h, ) except Exception as e2: last_err = e2 if result is None: raise gr.Error(f"Remote Space call failed: {last_err}") # Parse result - expected format: (gallery_images, seed, ui_update) gen_images = None used_seed = seed if isinstance(result, (list, tuple)): if len(result) >= 1: gen_images = result[0] if len(result) >= 2: used_seed = result[1] elif isinstance(result, dict): gen_images = result.get("images") or result.get("gallery") used_seed = result.get("seed", seed) if not gen_images: raise gr.Error("No images returned from remote Space.") # Normalize to list of file paths out_paths = [] if isinstance(gen_images, list): for img in gen_images: if isinstance(img, dict) and "path" in img: out_paths.append(img["path"]) elif isinstance(img, dict) and "url" in img: out_paths.append(img["url"]) elif isinstance(img, (list, tuple)) and len(img) > 0: if isinstance(img[0], dict) and "path" in img[0]: out_paths.append(img[0]["path"]) elif isinstance(img[0], str): out_paths.append(img[0]) elif isinstance(img, str): out_paths.append(img) elif isinstance(gen_images, str): out_paths.append(gen_images) if not out_paths: raise gr.Error("Could not parse generated image paths.") # If chaining, return output as new input gallery format if chain_in: chain_gallery = [] for p in out_paths: chain_gallery.append({"path": p, "url": p}) return out_paths, str(used_seed), chain_gallery else: return out_paths, str(used_seed), None except gr.Error: raise except Exception as e: raise gr.Error(f"Error: {e}") # Template helpers TPL = { "preserve": "Keep the subject's facial features, hair, skin tone, and costume details identical to the source image. ", "lighting": "Match the original scene's warm stage lighting and color grading exactly. ", "realism": "Professional digital photography. ", "group": "Keep all subjects' identities identical. ", "pose": "Same character, same identity. She is now in a new position. ", } def add_template(tpl_key, current_prompt): return current_prompt + TPL.get(tpl_key, "") with gr.Blocks() as demo: gr.HTML("""
Client for Qwen-Image-Edit-Rapid-AIO NSFW v2.3