Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| import numpy as np | |
| import random | |
| import torch | |
| import spaces | |
| import os | |
| import base64 | |
| import math | |
| from PIL import Image | |
| from diffusers import QwenImageEditPlusPipeline | |
| from pillow_heif import register_heif_opener | |
| from huggingface_hub import login | |
| # from prompt_augment import PromptAugment | |
| hf_token = os.getenv("hf") | |
| space_id = os.getenv("SPACE_ID") | |
| root_path = f"https://{space_id.replace('/', '-')}.hf.space" if space_id else "" | |
| print(f"root path: {root_path}") | |
| if hf_token: | |
| print("Secret loaded successfully.") | |
| else: | |
| print("Secret not found. Check your Space settings.") | |
| login(token=hf_token) | |
| register_heif_opener() | |
| dtype = torch.bfloat16 | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| pipe = QwenImageEditPlusPipeline.from_pretrained( | |
| "FireRedTeam/FireRed-Image-Edit-1.1", | |
| torch_dtype=dtype | |
| ).to(device) | |
| pipe.vae.enable_tiling() | |
| pipe.vae.enable_slicing() | |
| #load lightning | |
| pipe.load_lora_weights( | |
| "FireRedTeam/FireRed-Image-Edit-LoRA-Zoo", | |
| weight_name="FireRed-Image-Edit-Lightning-8steps-v1.0.safetensors", | |
| adapter_name= "lightning" | |
| ) | |
| pipe.load_lora_weights( | |
| "aiunivers/qwen-image-edit-plus-nsfw-lora", | |
| weight_name="qwen-image-edit-plus-nsfw-lora.safetensors", | |
| adapter_name= "aiuniverse" | |
| ) | |
| # prompt_handler = PromptAugment() | |
| LORA_REPO = "wiikoo/Qwen-lora-nsfw" | |
| LORA_CONFIGS = { | |
| "CockQwen_v3": "loras/CockQwen-v3.safetensors", | |
| "Eva_Qwen_V3": "loras/Eva_Qwen_V3.safetensors", | |
| "Facial_Cumshots_V1": "loras/Facial_Cumshots_For_Qwen_Image_V1.safetensors", | |
| "HearmemanAI_V3_Breasts": "loras/HearmemanAI_V3_Rank64_BreastsLoRA_Epoch60.safetensors", | |
| "HearmemanAI_V4_Breasts": "loras/HearmemanAI_V4_Rank128_BreastsLoRA_Epoch80.safetensors", | |
| "InniePussy": "loras/InniePussy.safetensors", | |
| "JTT2_5": "loras/[QWEN] JTT2_5.safetensors", | |
| "LumiNude01a": "loras/LumiNude01a_CE_QWEN_AIT3k.safetensors", | |
| "MEXX_QWEN_TG300": "loras/MEXX_QWEN_TG300_23.safetensors", | |
| "Meta4": "loras/Meta4.safetensors", | |
| "MysticXXX": "loras/Qwen-MysticXXX-v1.safetensors", | |
| "Nsfw_Body_V10": "loras/Qwen_Nsfw_Body_V10-4K.safetensors", | |
| "Nsfw_Body_V14": "loras/Qwen_Nsfw_Body_V14-10K.safetensors", | |
| "OilySkin_V2": "loras/Oily Skin QWEN V2-GMR.safetensors", | |
| "PillowHump_2509": "loras/PillowHump_2509.safetensors", | |
| "PutItHere_V2": "loras/Put it here_Qwen edit_V2.0.safetensors", | |
| "PutItHere_V01": "loras/put it here_QwenEdit_V0.1.safetensors", | |
| "Qwen4Play_v2": "loras/Qwen4Play_v2.safetensors", | |
| "QwenHentai_v3": "loras/QwenImageHentaiPIV_v3.1.safetensors", | |
| "Qwen_Helm": "loras/Qwen-Image-Helm_v0.1.safetensors", | |
| "Qwen_NSFW_Beta1": "loras/Qwen-NSFW.safetensors", | |
| "Qwen_NSFW_Beta2": "loras/Qwen-NSFW-Beta2.safetensors", | |
| "Qwen_NSFW_Beta4": "loras/Qwen-NSFW-Beta4.safetensors", | |
| "Qwen_NSFW_Beta5": "loras/Qwen-NSFW-Beta5.safetensors", | |
| "Qwen_Real_Nud3s": "loras/Qwen_Real_Nud3s.safetensors", | |
| "Qwen_Real_PS": "loras/Qwen-Real PS_v1_83K.safetensors", | |
| "QwenSnofs_v1": "loras/qwen_snofs.safetensors", | |
| "QwenSnofs_v1_1": "loras/QwenSnofs1_1.safetensors", | |
| "Real_Breast_Nipples": "loras/Real Breast Nipples-QWEN-[rbn]-GMR.safetensors", | |
| "SendDudes": "loras/[QWEN] SendDudes.safetensors", | |
| "SendNudesLite": "loras/SendNudesLite (Qwen).safetensors", | |
| "SendNudesPro_Beta": "loras/[QWEN] Send Nudes Pro - Beta v1.safetensors", | |
| "Ultimate_Breast_Nipples": "loras/Ultimate Realistic Breast NIPPLES-QWEN-[rab]-GMR.safetensors", | |
| "ass_up_QWEN": "loras/ass_up_QWEN.safetensors", | |
| "barbell_nipples_QWEN": "loras/QWEN_jtn_barbell.safetensors", | |
| "bfs_v2_face": "loras-sfw/face_swap_5500_qwen_image_edit_2509_v1.safetensors", | |
| "bfs_v2_focus_face": "loras-sfw/bfs_v2_000005000.safetensors", | |
| "big_nipples_QWEN": "loras/big_nipples_QWEN.safetensors", | |
| "bumpynipples": "loras/bumpynipples1.safetensors", | |
| "cmslt_cum_on_her": "loras/cmslt_2509_2.safetensors", | |
| "consistence_edit_v1": "loras-2/consistence_edit_v1.safetensors", | |
| "consistence_edit_v2": "loras2/consistence_edit_v2.safetensors", | |
| "d33p7hroa7": "loras/d33p7hroa7_qwen.safetensors", | |
| "d1ck_p3n1s_V1_1": "loras/qwen-image_d!ck_P3N1S_LoRA_V1.1.safetensors", | |
| "goblin_anal_v1": "loras/goblin_anal_v1_qwen.safetensors", | |
| "horseshoe_nipple_rings": "loras/horseshoe_nipple_rings_QWEN.safetensors", | |
| "jib_nudity_fixer": "loras/jib_qwen_fix_000002750.safetensors", | |
| "jillin": "loras/jillin1.safetensors", | |
| "male_nude": "loras/lora_nudenan_v1.safetensors", | |
| "milk_juggs": "loras/milk_juggs_QWEN.safetensors", | |
| "n00d_b": "loras/n00d-b-qwen.safetensors", | |
| "nsfw_adv_v1": "loras/qwen-image_nsfw_adv_v1.0.safetensors", | |
| "p0ssy_lora_v1": "loras/p0ssy_lora_v1.safetensors", | |
| "p3nis": "loras/p3nis.safetensors", | |
| "qwen_MCNL": "loras/qwen_MCNL_v1.0.safetensors", | |
| "qwen_PENISLORA": "loras/qwen-PENISLORA.safetensors", | |
| "qwen_hand_grab": "loras/qwen_hand_grab_6000s.safetensors", | |
| "qwen_uncensor": "loras/qwen_uncensor_000014928.safetensors", | |
| "reclining_nude": "loras/reclining_nude_v1_000003500.safetensors", | |
| "remove_clothing": "loras/qwen_image_edit_remove-clothing_v1.0.safetensors", | |
| "royal_treatment_V3": "loras/royal+treatment+V3.safetensors", | |
| "sabi_character": "loras-2/sabi_character_v1.safetensors", | |
| "snapchat_selfie": "loras/qwen_image_snapchat.safetensors", | |
| "uka_qwen": "loras/uka_1_qwen.safetensors", | |
| "ultimate_realistic_breast":"loras/ultimate realistic breast.safetensors", | |
| } | |
| ADAPTER_SPECS = { | |
| "Covercraft": { | |
| "repo": "FireRedTeam/FireRed-Image-Edit-LoRA-Zoo", | |
| "weights": "FireRed-Image-Edit-Covercraft.safetensors", | |
| "adapter_name": "covercraft", | |
| }, | |
| "Lightning": { | |
| "repo": "FireRedTeam/FireRed-Image-Edit-LoRA-Zoo", | |
| "weights": "FireRed-Image-Edit-Lightning-8steps-v1.0.safetensors", | |
| "adapter_name": "lightning", | |
| }, | |
| "Makeup": { | |
| "repo": "FireRedTeam/FireRed-Image-Edit-LoRA-Zoo", | |
| "weights": "FireRed-Image-Edit-Makeup.safetensors", | |
| "adapter_name": "makeup", | |
| } | |
| } | |
| LOADED_ADAPTERS = set() | |
| LORA_OPTIONS = ["None"] + list(LORA_CONFIGS.keys()) | |
| print(f"lora option: {LORA_OPTIONS[0]}") | |
| def load_lora(lora_name): | |
| """加载并激活指定的 LoRA""" | |
| if lora_name == "None" or not lora_name: | |
| if LOADED_ADAPTERS: | |
| pipe.set_adapters([], adapter_weights=[]) | |
| return | |
| # spec = ADAPTER_SPECS.get(lora_name) | |
| # if not spec: | |
| # raise gr.Error(f"LoRA 配置未找到: {lora_name}") | |
| # adapter_name = spec["adapter_name"] | |
| if lora_name not in LOADED_ADAPTERS: | |
| print(f"--- Downloading and Loading Adapter: {lora_name} ---") | |
| if lora_name == "Lightning": | |
| pipe.load_lora_weights( | |
| "FireRedTeam/FireRed-Image-Edit-LoRA-Zoo", | |
| weight_name="FireRed-Image-Edit-Lightning-8steps-v1.0.safetensors", | |
| adapter_name= lora_name | |
| ) | |
| LOADED_ADAPTERS.add(lora_name) | |
| else: | |
| try: | |
| pipe.load_lora_weights( | |
| LORA_REPO, | |
| weight_name=LORA_CONFIGS[lora_name], | |
| adapter_name= lora_name | |
| ) | |
| LOADED_ADAPTERS.add(lora_name) | |
| except Exception as e: | |
| raise gr.Error(f"Failed to load adapter {lora_name}: {e}") | |
| else: | |
| print(f"--- Adapter {lora_name} is already loaded ---") | |
| pipe.set_adapters(["lightning", "aiuniverse", lora_name], adapter_weights=[1.0, 1.0, 0.5]) | |
| MAX_SEED = np.iinfo(np.int32).max | |
| MAX_INPUT_IMAGES = 3 | |
| def limit_images(images): | |
| if images is None: | |
| return None | |
| if len(images) > MAX_INPUT_IMAGES: | |
| gr.Info(f"最多支持 {MAX_INPUT_IMAGES} 张图片,已自动移除多余图片") | |
| return images[:MAX_INPUT_IMAGES] | |
| return images | |
| def calculate_dimensions(target_area, ratio): | |
| width = math.sqrt(target_area * ratio) | |
| height = width / ratio | |
| width = round(width / 32) * 32 | |
| height = round(height / 32) * 32 | |
| return int(width), int(height) | |
| def update_dimensions_on_upload(images, max_area=1024*1024): | |
| if images is None or len(images) == 0: | |
| return 0, 0 | |
| try: | |
| first_item = images[0] | |
| if isinstance(first_item, tuple): | |
| img = first_item[0] | |
| else: | |
| img = first_item | |
| if isinstance(img, Image.Image): | |
| pil_img = img | |
| elif isinstance(img, str): | |
| pil_img = Image.open(img) | |
| else: | |
| return 0, 0 | |
| h, w = pil_img.height, pil_img.width | |
| is_multi_image = len(images) > 1 | |
| if not is_multi_image: | |
| return 0, 0 | |
| ratio = w / h | |
| new_w, new_h = calculate_dimensions(max_area, ratio) | |
| return new_h, new_w | |
| except Exception as e: | |
| print(f"获取图片尺寸失败: {e}") | |
| return 0, 0 | |
| def infer( | |
| input_images, | |
| prompt, | |
| lora_choice, | |
| seed=42, | |
| true_guidance_scale=4.0, | |
| num_inference_steps=8, | |
| height=None, | |
| width=None, | |
| # rewrite_prompt=False, | |
| num_images_per_prompt=1, | |
| progress=gr.Progress(track_tqdm=True), | |
| ): | |
| negative_prompt = " " | |
| seed = random.randint(0, MAX_SEED) | |
| generator = torch.Generator(device=device).manual_seed(seed) | |
| load_lora(lora_choice) | |
| pil_images = [] | |
| if input_images is not None: | |
| for item in input_images[:MAX_INPUT_IMAGES]: | |
| try: | |
| if isinstance(item, tuple): | |
| img = item[0] | |
| else: | |
| img = item | |
| if isinstance(img, Image.Image): | |
| pil_images.append(img.convert("RGB")) | |
| elif isinstance(img, str): | |
| pil_images.append(Image.open(img).convert("RGB")) | |
| except Exception as e: | |
| print(f"处理图片出错: {e}") | |
| continue | |
| if height == 0: | |
| height = None | |
| if width == 0: | |
| width = None | |
| # if rewrite_prompt and len(pil_images) > 0: | |
| # # prompt = prompt_handler.predict(prompt, [pil_images[0]]) | |
| # print(f"Rewritten Prompt: {prompt}") | |
| if pil_images: | |
| for i, img in enumerate(pil_images): | |
| print(f" [{i}] size: {img.width}x{img.height}") | |
| num_inference_steps = max(1, int(num_inference_steps or 1)) | |
| images = pipe( | |
| image=pil_images if len(pil_images) > 0 else None, | |
| prompt=prompt, | |
| height=height, | |
| width=width, | |
| negative_prompt=negative_prompt, | |
| num_inference_steps=num_inference_steps, | |
| generator=generator, | |
| guidance_scale=1.0, | |
| true_cfg_scale=true_guidance_scale, | |
| num_images_per_prompt=num_images_per_prompt, | |
| ).images | |
| return images, seed | |
| css = """ | |
| #col-container { margin: 0 auto; max-width: 1200px; } | |
| #edit-btn { height: 100% !important; min-height: 42px; } | |
| """ | |
| def get_image_base64(image_path): | |
| with open(image_path, "rb") as img_file: | |
| return base64.b64encode(img_file.read()).decode('utf-8') | |
| logo_base64 = None | |
| with gr.Blocks() as demo: | |
| with gr.Column(elem_id="col-container"): | |
| gr.Markdown(f"Supports multi-image input (up to {MAX_INPUT_IMAGES} images.)") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| input_images = gr.Gallery( | |
| label="Upload Images", | |
| type="pil", | |
| interactive=True, | |
| height=300, | |
| columns=3, | |
| object_fit="contain", | |
| ) | |
| with gr.Column(scale=1): | |
| result = gr.Gallery( | |
| label="Output Images", | |
| type="pil", | |
| height=300, | |
| columns=2, | |
| object_fit="contain", | |
| ) | |
| prompt = gr.Textbox( | |
| label="Edit Prompt", | |
| placeholder="e.g., transform into anime..", | |
| ) | |
| with gr.Row(equal_height=True): | |
| with gr.Column(scale=5): | |
| lora_choice = gr.Dropdown( | |
| label="Choose Lora", | |
| choices=LORA_OPTIONS, | |
| value=LORA_OPTIONS[0] if LORA_OPTIONS else "None", | |
| ) | |
| with gr.Column(scale=4): | |
| run_button = gr.Button("Edit Image", variant="primary", elem_id="edit-btn") | |
| with gr.Accordion("Advanced Settings", open=True): | |
| with gr.Row(): | |
| seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=42) | |
| with gr.Row(): | |
| true_guidance_scale = gr.Slider(label="Guidance Scale", minimum=1.0, maximum=10.0, step=0.1, value=4.0) | |
| num_inference_steps = gr.Slider(label="Inference Steps", minimum=1, maximum=30, step=1, value=8) | |
| with gr.Row(): | |
| height = gr.Slider(label="Height (0=auto)", minimum=0, maximum=2048, step=8, value=0) | |
| width = gr.Slider(label="Width (0=auto)", minimum=0, maximum=2048, step=8, value=0) | |
| with gr.Row(): | |
| # rewrite_prompt = gr.Checkbox(label="Rewrite Prompt", value=True) | |
| num_images_per_prompt = gr.Slider(label="Num Images", minimum=1, maximum=4, step=1, value=1) | |
| # 监听 LoRA 选择变化:Lightning 时锁定参数 | |
| def on_lora_change(lora_name): | |
| return ( | |
| gr.update(value=8, interactive=False), # num_inference_steps | |
| gr.update(value=1.0, interactive=False), # true_guidance_scale | |
| gr.update(value=43, interactive=True), # seed | |
| # gr.update(value=False, interactive=False), # randomize_seed | |
| ) | |
| lora_choice.change( | |
| fn=on_lora_change, | |
| inputs=[lora_choice], | |
| outputs=[num_inference_steps, true_guidance_scale, seed], | |
| ) | |
| def on_image_upload(images): | |
| limited = limit_images(images) | |
| h, w = update_dimensions_on_upload(limited) | |
| return limited, h, w | |
| input_images.upload( | |
| fn=on_image_upload, | |
| inputs=[input_images], | |
| outputs=[input_images, height, width], | |
| ) | |
| gr.on( | |
| triggers=[run_button.click, prompt.submit], | |
| fn=infer, | |
| inputs=[ | |
| input_images, | |
| prompt, lora_choice, seed, | |
| true_guidance_scale, num_inference_steps, | |
| height, width, num_images_per_prompt, | |
| ], | |
| outputs=[result, seed], | |
| ) | |
| if __name__ == "__main__": | |
| demo.queue() | |
| demo.launch(css=css, allowed_paths=["./"]) | |
| # demo.launch(allowed_paths=["./"], ssr_mode=False, css=css, root_path=root_path, | |
| # show_error=True, auth=None) | |