Spaces:
Running
on
Zero
Running
on
Zero
| import os | |
| os.system( | |
| 'pip install --upgrade --pre --extra-index-url https://download.pytorch.org/whl/nightly/cu126 ' | |
| '"torch==2.9.0" "torchvision==0.24.0" "torchaudio==2.9.0" ' | |
| '"transformers>=4.44" "huggingface-hub>=1.0.0rc6" spaces -q' | |
| ) | |
| import spaces | |
| import gradio as gr | |
| import torch | |
| import math | |
| from PIL import Image | |
| from diffusers import QwenImageEditPlusPipeline, FlowMatchEulerDiscreteScheduler | |
| import requests | |
| import logging | |
| import numpy as np | |
| import random | |
| from fastapi import FastAPI, HTTPException | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| filename="qwen_image_editor.log", | |
| filemode="a", | |
| format="%(asctime)s - %(levelname)s - %(message)s", | |
| ) | |
| logger = logging.getLogger(__name__) | |
| def translate_albanian_to_english(text: str, language: str = "en"): | |
| if not text.strip(): | |
| raise gr.Error("Please enter a description.") | |
| for attempt in range(2): | |
| try: | |
| response = requests.post( | |
| "https://hal1993-mdftranslation1234567890abcdef1234567890-fc073a6.hf.space/v1/translate", | |
| json={"from_language": "sq", "to_language": "en", "input_text": text}, | |
| headers={"accept": "application/json", "Content-Type": "application/json"}, | |
| timeout=5, | |
| ) | |
| response.raise_for_status() | |
| translated = response.json().get("translate", "") | |
| logger.info(f"Translation response: {translated}") | |
| return translated | |
| except Exception as e: | |
| logger.error(f"Translation error (attempt {attempt + 1}): {e}") | |
| if attempt == 1: | |
| raise gr.Error("Translation failed. Please try again.") | |
| raise gr.Error("Translation failed. Please try again.") | |
| scheduler_config = { | |
| "base_image_seq_len": 256, | |
| "base_shift": math.log(3), | |
| "invert_sigmas": False, | |
| "max_image_seq_len": 8192, | |
| "max_shift": math.log(3), | |
| "num_train_timesteps": 1000, | |
| "shift": 1.0, | |
| "shift_terminal": None, | |
| "stochastic_sampling": False, | |
| "time_shift_type": "exponential", | |
| "use_beta_sigmas": False, | |
| "use_dynamic_shifting": True, | |
| "use_exponential_sigmas": False, | |
| "use_karras_sigmas": False, | |
| } | |
| scheduler = FlowMatchEulerDiscreteScheduler.from_config(scheduler_config) | |
| pipeline = QwenImageEditPlusPipeline.from_pretrained( | |
| "Qwen/Qwen-Image-Edit-2509", | |
| scheduler=scheduler, | |
| torch_dtype=torch.bfloat16, | |
| ) | |
| pipeline.to("cuda") | |
| pipeline.set_progress_bar_config(disable=None) | |
| pipeline.load_lora_weights( | |
| "lightx2v/Qwen-Image-Lightning", | |
| weight_name="Qwen-Image-Lightning-8steps-V2.0-bf16.safetensors", | |
| ) | |
| pipeline.fuse_lora() | |
| MAX_SEED = np.iinfo(np.int32).max | |
| QUALITY_PROMPT = ", high quality, detailed, vibrant, professional lighting" | |
| def edit_images(image1, image2, prompt): | |
| if image1 is None or image2 is None: | |
| raise gr.Error("Please upload both images") | |
| prompt_en = translate_albanian_to_english(prompt.strip(), language="en") | |
| prompt_final = prompt_en + QUALITY_PROMPT | |
| if not isinstance(image1, Image.Image): | |
| image1 = Image.fromarray(image1) | |
| if not isinstance(image2, Image.Image): | |
| image2 = Image.fromarray(image2) | |
| seed = random.randint(0, MAX_SEED) | |
| true_cfg_scale = 1.0 | |
| negative_prompt = "" | |
| num_steps = 8 | |
| guidance_scale = 1.0 | |
| inputs = { | |
| "image": [image1, image2], | |
| "prompt": prompt_final, | |
| "generator": torch.manual_seed(seed), | |
| "true_cfg_scale": true_cfg_scale, | |
| "negative_prompt": negative_prompt, | |
| "num_inference_steps": num_steps, | |
| "guidance_scale": guidance_scale, | |
| "num_images_per_prompt": 1, | |
| } | |
| logger.info(f"Calling pipeline – Prompt: {prompt_final}") | |
| logger.info(f"Seed: {seed} | Steps: {num_steps}") | |
| with torch.inference_mode(): | |
| output = pipeline(**inputs) | |
| return output.images[0] | |
| def create_demo(): | |
| with gr.Blocks(css="", title="Qwen Image Editor") as demo: | |
| gr.HTML( | |
| """ | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;700&display=swap'); | |
| @keyframes glow {0%{box-shadow:0 0 14px rgba(0,255,128,0.5);}50%{box-shadow:0 0 14px rgba(0,255,128,0.7);}100%{box-shadow:0 0 14px rgba(0,255,128,0.5);}} | |
| @keyframes glow-hover {0%{box-shadow:0 0 20px rgba(0,255,128,0.7);}50%{box-shadow:0 0 20px rgba(0,255,128,0.9);}100%{box-shadow:0 0 20px rgba(0,255,128,0.7);}} | |
| @keyframes slide {0%{background-position:0% 50%;}50%{background-position:100% 50%;}100%{background-position:0% 50%;}} | |
| body{ | |
| background:#000000 !important; | |
| color:#FFFFFF !important; | |
| font-family:'Orbitron',sans-serif; | |
| min-height:100vh; | |
| margin:0 !important; | |
| padding:0 !important; | |
| width:100% !important; | |
| max-width:100vw !important; | |
| overflow-x:hidden !important; | |
| display:flex !important; | |
| justify-content:center; | |
| align-items:center; | |
| flex-direction:column; | |
| } | |
| body::before{ | |
| content:""; | |
| display:block; | |
| height:600px; | |
| background:#000000 !important; | |
| } | |
| .gr-blocks,.container{ | |
| width:100% !important; | |
| max-width:100vw !important; | |
| margin:0 !important; | |
| padding:0 !important; | |
| box-sizing:border-box !important; | |
| overflow-x:hidden !important; | |
| background:#000000 !important; | |
| color:#FFFFFF !important; | |
| } | |
| /* NEW: force rows, columns and Gradio containers to be full width */ | |
| .gr-row,.gr-column{ | |
| width:100% !important; | |
| max-width:100vw !important; | |
| margin:0 !important; | |
| padding:0 !important; | |
| box-sizing:border-box !important; | |
| } | |
| .gradio-container,.gradio-app,.gradio-interface{ | |
| width:100% !important; | |
| max-width:100vw !important; | |
| margin:0 !important; | |
| padding:0 !important; | |
| box-sizing:border-box !important; | |
| } | |
| #general_items{ | |
| width:100% !important; | |
| max-width:100vw !important; | |
| margin:2rem 0 !important; | |
| display:flex !important; | |
| flex-direction:column; | |
| align-items:center; | |
| justify-content:center; | |
| background:#000000 !important; | |
| color:#FFFFFF !important; | |
| } | |
| #input_column{ | |
| background:#000000 !important; | |
| border:none !important; | |
| border-radius:8px; | |
| padding:1rem !important; | |
| box-shadow:0 0 10px rgba(255,255,255,0.3) !important; | |
| width:100% !important; | |
| max-width:100vw !important; | |
| box-sizing:border-box !important; | |
| color:#FFFFFF !important; | |
| } | |
| h1{ | |
| font-size:5rem; | |
| font-weight:700; | |
| text-align:center; | |
| color:#FFFFFF !important; | |
| text-shadow:0 0 8px rgba(255,255,255,0.3) !important; | |
| margin:0 auto .5rem; | |
| display:block; | |
| max-width:100%; | |
| } | |
| #subtitle{ | |
| font-size:1rem; | |
| text-align:center; | |
| color:#FFFFFF !important; | |
| opacity:0.8; | |
| margin-bottom:1rem; | |
| display:block; | |
| max-width:100%; | |
| } | |
| .gradio-component{ | |
| background:#000000 !important; | |
| border:none; | |
| margin:0.75rem 0; | |
| width:100% !important; | |
| max-width:100vw !important; | |
| color:#FFFFFF !important; | |
| } | |
| .image-container{ | |
| aspect-ratio:1/1; | |
| width:100% !important; | |
| max-width:100vw !important; | |
| min-height:500px; | |
| height:auto; | |
| border:0.5px solid #FFFFFF !important; | |
| border-radius:4px; | |
| box-sizing:border-box !important; | |
| background:#000000 !important; | |
| box-shadow:0 0 10px rgba(255,255,255,0.3) !important; | |
| position:relative; | |
| color:#FFFFFF !important; | |
| } | |
| .image-container img{ | |
| width:100% !important; | |
| height:auto; | |
| box-sizing:border-box !important; | |
| display:block !important; | |
| } | |
| .image-container[aria-label="First Image"] .file-upload, | |
| .image-container[aria-label="First Image"] .file-preview, | |
| .image-container[aria-label="First Image"] .image-actions, | |
| .image-container[aria-label="First Image"] .gr-file-upload, | |
| .image-container[aria-label="First Image"] .gr-file, | |
| .image-container[aria-label="First Image"] .gr-actions, | |
| .image-container[aria-label="First Image"] .gr-upload-button, | |
| .image-container[aria-label="First Image"] .gr-image-toolbar, | |
| .image-container[aria-label="First Image"] .gr-file-actions, | |
| .image-container[aria-label="First Image"] .gr-upload-options, | |
| div[aria-label="First Image"] > div > div:not(.image-container), | |
| div[aria-label="First Image"] .gr-button, | |
| .image-container[aria-label="Second Image"] .file-upload, | |
| .image-container[aria-label="Second Image"] .file-preview, | |
| .image-container[aria-label="Second Image"] .image-actions, | |
| .image-container[aria-label="Second Image"] .gr-file-upload, | |
| .image-container[aria-label="Second Image"] .gr-file, | |
| .image-container[aria-label="Second Image"] .gr-actions, | |
| .image-container[aria-label="Second Image"] .gr-upload-button, | |
| .image-container[aria-label="Second Image"] .gr-image-toolbar, | |
| .image-container[aria-label="Second Image"] .gr-file-actions, | |
| .image-container[aria-label="Second Image"] .gr-upload-options, | |
| div[aria-label="Second Image"] .gr-button{ | |
| display:none !important; | |
| } | |
| input,textarea{ | |
| background:#000000 !important; | |
| color:#FFFFFF !important; | |
| border:1px solid #FFFFFF !important; | |
| border-radius:4px; | |
| padding:0.5rem; | |
| width:100% !important; | |
| max-width:100vw !important; | |
| box-sizing:border-box !important; | |
| } | |
| input:hover,textarea:hover{ | |
| box-shadow:0 0 8px rgba(255,255,255,0.3) !important; | |
| transition:box-shadow 0.3s; | |
| } | |
| .gr-button-primary{ | |
| background:linear-gradient(90deg,rgba(0,255,128,0.3),rgba(0,200,100,0.3),rgba(0,255,128,0.3)) !important; | |
| background-size:200% 100%; | |
| animation:slide 4s ease-in-out infinite,glow 3s ease-in-out infinite; | |
| color:#FFFFFF !important; | |
| border:1px solid #FFFFFF !important; | |
| border-radius:6px; | |
| padding:0.75rem 1.5rem; | |
| font-size:1.1rem; | |
| font-weight:600; | |
| box-shadow:0 0 14px rgba(0,255,128,0.7) !important; | |
| transition:box-shadow 0.3s,transform 0.3s; | |
| width:100% !important; | |
| max-width:100vw !important; | |
| min-height:48px; | |
| cursor:pointer; | |
| } | |
| .gr-button-primary:hover{ | |
| box-shadow:0 0 20px rgba(0,255,128,0.9) !important; | |
| animation:slide 4s ease-in-out infinite,glow-hover 3s ease-in-out infinite; | |
| transform:scale(1.05); | |
| } | |
| button[aria-label="Fullscreen"],button[aria-label="Share"]{display:none !important;} | |
| button[aria-label="Download"]{ | |
| transform:scale(3); | |
| transform-origin:top right; | |
| background:#000000 !important; | |
| color:#FFFFFF !important; | |
| border:1px solid #FFFFFF !important; | |
| border-radius:4px; | |
| padding:0.4rem !important; | |
| margin:0.5rem !important; | |
| box-shadow:0 0 8px rgba(255,255,255,0.3) !important; | |
| transition:box-shadow 0.3s; | |
| } | |
| button[aria-label="Download"]:hover{ | |
| box-shadow:0 0 12px rgba(255,255,255,0.5) !important; | |
| } | |
| .progress-text,.gr-progress,.gr-prose,.gr-log{display:none !important;} | |
| footer,.gr-button-secondary{display:none !important;} | |
| .gr-group{ | |
| background:#000000 !important; | |
| border:none !important; | |
| width:100% !important; | |
| max-width:100vw !important; | |
| } | |
| @media (max-width:768px){ | |
| h1{font-size:4rem;} | |
| #subtitle{font-size:0.9rem;} | |
| .gr-button-primary{ | |
| padding:0.6rem 1rem; | |
| font-size:1rem; | |
| box-shadow:0 0 10px rgba(0,255,128,0.7) !important; | |
| animation:slide 4s ease-in-out infinite,glow 3s ease-in-out infinite; | |
| } | |
| .gr-button-primary:hover{ | |
| box-shadow:0 0 12px rgba(0,255,128,0.9) !important; | |
| animation:slide 4s ease-in-out infinite,glow-hover 3s ease-in-out infinite; | |
| } | |
| .image-container{ | |
| min-height:300px; | |
| box-shadow:0 0 8px rgba(255,255,255,0.3) !important; | |
| border:0.5px solid #FFFFFF !important; | |
| } | |
| } | |
| </style> | |
| <script> | |
| const allowedPath = /^\\/q3w4e5r6t7y8u9i0o1p2l3k4j5h6g7f8d9s0a1q2w3e4r5t6y7u8i9o0p1l2k3j4(\\/.*)?$/; | |
| if (!allowedPath.test(window.location.pathname)) { | |
| document.body.innerHTML = '<h1 style="color:#ef4444;font-family:sans-serif;text-align:center;margin-top:100px;">500 Internal Server Error</h1>'; | |
| throw new Error('500'); | |
| } | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const containers = document.querySelectorAll('#general_items, #input_column, .image-container'); | |
| containers.forEach(container => { | |
| const width = container.offsetWidth; | |
| const style = window.getComputedStyle(container); | |
| console.log(`Container ${container.id || container.className}: width=${width}px, box-shadow=${style.boxShadow}, background=${style.background}, border=${style.border} (Viewport: ${window.innerWidth}px)`); | |
| container.setAttribute('data-width', `${width}px`); | |
| }); | |
| const editButton = document.querySelector('.gr-button-primary'); | |
| if (editButton) { | |
| const style = window.getComputedStyle(editButton); | |
| console.log(`Edit button: box-shadow=${style.boxShadow}, background=${style.background}, border=${style.border}, animation=${style.animation}, background-position=${style.backgroundPosition}`); | |
| } | |
| const toolbars = document.querySelectorAll( | |
| '.image-container[aria-label="First Image"] > div > div:not(.image-container), ' + | |
| '.image-container[aria-label="First Image"] .file-upload, ' + | |
| '.image-container[aria-label="First Image"] .file-preview, ' + | |
| '.image-container[aria-label="First Image"] .image-actions, ' + | |
| '.image-container[aria-label="First Image"] .gr-file-upload, ' + | |
| '.image-container[aria-label="First Image"] .gr-file, ' + | |
| '.image-container[aria-label="First Image"] .gr-actions, ' + | |
| '.image-container[aria-label="First Image"] .gr-upload-button, ' + | |
| '.image-container[aria-label="First Image"] .gr-image-toolbar, ' + | |
| '.image-container[aria-label="First Image"] .gr-file-actions, ' + | |
| '.image-container[aria-label="First Image"] .gr-upload-options, ' + | |
| 'div[aria-label="First Image"] .gr-button, ' + | |
| '.image-container[aria-label="Second Image"] > div > div:not(.image-container), ' + | |
| '.image-container[aria-label="Second Image"] .file-upload, ' + | |
| '.image-container[aria-label="Second Image"] .file-preview, ' + | |
| '.image-container[aria-label="Second Image"] .image-actions, ' + | |
| '.image-container[aria-label="Second Image"] .gr-file-upload, ' + | |
| '.image-container[aria-label="Second Image"] .gr-file, ' + | |
| '.image-container[aria-label="Second Image"] .gr-actions, ' + | |
| '.image-container[aria-label="Second Image"] .gr-upload-button, ' + | |
| '.image-container[aria-label="Second Image"] .gr-image-toolbar, ' + | |
| '.image-container[aria-label="Second Image"] .gr-file-actions, ' + | |
| '.image-container[aria-label="Second Image"] .gr-upload-options, ' + | |
| 'div[aria-label="Second Image"] .gr-button' | |
| ); | |
| toolbars.forEach(tb => { | |
| tb.style.display = 'none'; | |
| console.log(`Forced hide toolbar: ${tb.className}`); | |
| }); | |
| setInterval(() => { | |
| document.querySelectorAll('.progress-text,.gr-progress,[class*="progress"]').forEach(el => el.remove()); | |
| }, 500); | |
| }); | |
| </script> | |
| """ | |
| ) | |
| with gr.Row(elem_id="general_items"): | |
| gr.Markdown("# ") | |
| gr.Markdown("Blend images together guided by a prompt description.", elem_id="subtitle") | |
| with gr.Column(elem_id="input_column"): | |
| image1_input = gr.Image( | |
| label="First Image", | |
| type="pil", | |
| sources=["upload"], | |
| interactive=True, | |
| elem_classes=["gradio-component", "image-container"], | |
| ) | |
| image2_input = gr.Image( | |
| label="Second Image", | |
| type="pil", | |
| sources=["upload"], | |
| interactive=True, | |
| elem_classes=["gradio-component", "image-container"], | |
| ) | |
| prompt_input = gr.Textbox( | |
| label="Prompt", | |
| placeholder="Describe how you want the images combined or edited...", | |
| lines=3, | |
| elem_classes=["gradio-component"], | |
| ) | |
| run_button = gr.Button( | |
| "Edit!", | |
| variant="primary", | |
| elem_classes=["gradio-component", "gr-button-primary"], | |
| ) | |
| output_image = gr.Image( | |
| label="Result Image", | |
| type="pil", | |
| interactive=False, | |
| elem_classes=["gradio-component", "image-container"], | |
| ) | |
| gr.on( | |
| triggers=[run_button.click, prompt_input.submit], | |
| fn=edit_images, | |
| inputs=[image1_input, image2_input, prompt_input], | |
| outputs=[output_image], | |
| show_progress="full", | |
| ) | |
| return demo | |
| app = FastAPI() | |
| demo = create_demo() | |
| app.mount("/q3w4e5r6t7y8u9i0o1p2l3k4j5h6g7f8d9s0a1q2w3e4r5t6y7u8i9o0p1l2k3j4", demo.app) | |
| async def catch_all(path: str): | |
| if not path.startswith("spaceishere"): | |
| raise HTTPException(status_code=500, detail="Internal Server Error") | |
| return demo | |
| if __name__ == "__main__": | |
| logger.info(f"Gradio version: {gr.__version__}") | |
| demo.queue().launch(share=True) |