|
|
import gradio as gr |
|
|
import numpy as np |
|
|
import random |
|
|
import torch |
|
|
import spaces |
|
|
|
|
|
from PIL import Image |
|
|
from diffusers import FlowMatchEulerDiscreteScheduler |
|
|
from optimization import optimize_pipeline_ |
|
|
from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline |
|
|
from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel |
|
|
from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3 |
|
|
|
|
|
import math |
|
|
from huggingface_hub import hf_hub_download |
|
|
from safetensors.torch import load_file |
|
|
|
|
|
from PIL import Image |
|
|
import os |
|
|
import gradio as gr |
|
|
from gradio_client import Client, handle_file |
|
|
import tempfile |
|
|
from huggingface_hub import InferenceClient |
|
|
|
|
|
|
|
|
|
|
|
dtype = torch.bfloat16 |
|
|
device = "cuda" if torch.cuda.is_available() else "cpu" |
|
|
|
|
|
pipe = QwenImageEditPlusPipeline.from_pretrained("Qwen/Qwen-Image-Edit-2509", torch_dtype=dtype).to(device) |
|
|
|
|
|
|
|
|
pipe.load_lora_weights( |
|
|
"dx8152/Qwen-Image-Edit-2509-Relight", |
|
|
weight_name="Qwen-Edit-Relight.safetensors", adapter_name="relight" |
|
|
) |
|
|
|
|
|
pipe.load_lora_weights("lightx2v/Qwen-Image-Lightning", |
|
|
weight_name="Qwen-Image-Lightning-4steps-V2.0-bf16.safetensors", adapter_name="lightning") |
|
|
pipe.set_adapters(["relight", "lightning"], adapter_weights=[1., 1.]) |
|
|
pipe.fuse_lora(adapter_names=["relight", "lightning"], lora_scale=1) |
|
|
pipe.unload_lora_weights() |
|
|
|
|
|
pipe.transformer.__class__ = QwenImageTransformer2DModel |
|
|
pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3()) |
|
|
|
|
|
optimize_pipeline_(pipe, image=[Image.new("RGB", (1024, 1024)), Image.new("RGB", (1024, 1024))], prompt="prompt") |
|
|
|
|
|
|
|
|
MAX_SEED = np.iinfo(np.int32).max |
|
|
|
|
|
|
|
|
translation_client = InferenceClient( |
|
|
api_key=os.environ.get("HF_TOKEN"), |
|
|
) |
|
|
|
|
|
def translate_to_chinese(text: str) -> str: |
|
|
"""Translate any language text to Chinese using Qwen API.""" |
|
|
if not text or not text.strip(): |
|
|
return "" |
|
|
|
|
|
|
|
|
chinese_chars = sum(1 for char in text if '\u4e00' <= char <= '\u9fff') |
|
|
if chinese_chars / max(len(text), 1) > 0.5: |
|
|
|
|
|
return text |
|
|
|
|
|
try: |
|
|
completion = translation_client.chat.completions.create( |
|
|
model="Qwen/Qwen3-Next-80B-A3B-Instruct:novita", |
|
|
messages=[ |
|
|
{ |
|
|
"role": "system", |
|
|
"content": "You are a professional translator. Translate the user's text to Chinese. Only output the translated text, nothing else." |
|
|
}, |
|
|
{ |
|
|
"role": "user", |
|
|
"content": f"Translate this to Chinese: {text}" |
|
|
} |
|
|
], |
|
|
max_tokens=500, |
|
|
) |
|
|
|
|
|
translated = completion.choices[0].message.content.strip() |
|
|
print(f"Translated '{text}' to '{translated}'") |
|
|
return translated |
|
|
except Exception as e: |
|
|
print(f"Translation error: {e}") |
|
|
|
|
|
return text |
|
|
|
|
|
def _generate_video_segment(input_image_path: str, output_image_path: str, prompt: str, request: gr.Request) -> str: |
|
|
"""Generates a single video segment using the external service.""" |
|
|
x_ip_token = request.headers['x-ip-token'] |
|
|
video_client = Client("multimodalart/wan-2-2-first-last-frame", headers={"x-ip-token": x_ip_token}) |
|
|
result = video_client.predict( |
|
|
start_image_pil=handle_file(input_image_path), |
|
|
end_image_pil=handle_file(output_image_path), |
|
|
prompt=prompt, api_name="/generate_video", |
|
|
) |
|
|
return result[0]["video"] |
|
|
|
|
|
def build_relight_prompt(light_type, light_direction, light_intensity, illumination_env, prompt): |
|
|
"""Build the relighting prompt based on user selections - Qwen style.""" |
|
|
|
|
|
|
|
|
if prompt and prompt.strip(): |
|
|
translated = translate_to_chinese(prompt) |
|
|
|
|
|
if "重新照明" not in translated: |
|
|
return f"重新照明,{translated}" |
|
|
return translated |
|
|
|
|
|
|
|
|
prompt_parts = ["重新照明"] |
|
|
|
|
|
|
|
|
light_descriptions = { |
|
|
"none": "", |
|
|
"soft_window": "窗帘透光(柔和漫射)", |
|
|
"golden_hour": "金色黄昏的温暖光线", |
|
|
"studio": "专业摄影棚的均匀光线", |
|
|
"dramatic": "戏剧性的高对比度光线", |
|
|
"natural": "自然日光", |
|
|
"neon": "霓虹灯光效果", |
|
|
"candlelight": "烛光的温暖氛围", |
|
|
"moonlight": "月光的冷色调", |
|
|
"sunrise": "日出的柔和光线", |
|
|
"sunset_sea": "海面日落光线", |
|
|
"overcast": "阴天的柔和漫射光", |
|
|
"harsh_sun": "强烈的正午阳光", |
|
|
"twilight": "黄昏时分的神秘光线", |
|
|
"aurora": "极光般的多彩光线", |
|
|
"firelight": "篝火的跳动光线", |
|
|
"lightning": "闪电的瞬间强光", |
|
|
"underwater": "水下的柔和蓝光", |
|
|
"foggy": "雾气中的柔和扩散光", |
|
|
"magic": "魔法般的神秘光芒", |
|
|
"cyberpunk": "赛博朋克风格的RGB霓虹光", |
|
|
"warm_home": "家庭温馨的暖色光", |
|
|
"cold_industrial": "冷酷的工业照明", |
|
|
"spotlight": "聚光灯效果", |
|
|
"rim_light": "边缘光效果", |
|
|
} |
|
|
|
|
|
|
|
|
direction_descriptions = { |
|
|
"none": "", |
|
|
"front": "正面照射", |
|
|
"side": "侧面照射", |
|
|
"left": "左侧照射", |
|
|
"right": "右侧照射", |
|
|
"back": "背后照射(逆光)", |
|
|
"top": "上方照射", |
|
|
"bottom": "下方照射", |
|
|
"diagonal": "对角线照射", |
|
|
} |
|
|
|
|
|
|
|
|
intensity_descriptions = { |
|
|
"none": "", |
|
|
"soft": "柔和强度", |
|
|
"medium": "中等强度", |
|
|
"strong": "强烈强度", |
|
|
} |
|
|
|
|
|
|
|
|
illumination_envs = { |
|
|
"none": "", |
|
|
"sunshine_window": "阳光从窗户透入", |
|
|
"neon_city": "霓虹夜景,城市灯光", |
|
|
"sci_fi_rgb": "科幻RGB发光,赛博朋克风格", |
|
|
"warm_bedroom": "温暖氛围,家中,卧室", |
|
|
"magic_lit": "魔法照明", |
|
|
"gothic_cave": "邪恶哥特风格,洞穴中", |
|
|
"light_shadow": "光影交错", |
|
|
"window_shadow": "窗户投影", |
|
|
"soft_studio": "柔和摄影棚灯光", |
|
|
"cozy_bedroom": "家庭氛围,温馨卧室照明", |
|
|
"wong_kar_wai": "王家卫风格霓虹灯,温暖色调", |
|
|
"moonlight_curtains": "月光透过窗帘", |
|
|
"stormy_sky": "暴风雨天空照明", |
|
|
"underwater_glow": "水下发光,深海", |
|
|
"foggy_forest": "雾中森林黎明", |
|
|
"meadow_golden": "草地上的黄金时刻", |
|
|
"rainbow_neon": "彩虹反射,霓虹", |
|
|
"apocalyptic": "末日烟雾氛围", |
|
|
"emergency_red": "红色紧急灯光", |
|
|
"mystical_forest": "神秘发光,魔法森林", |
|
|
"campfire": "篝火光芒", |
|
|
"industrial_harsh": "严酷工业照明", |
|
|
"mountain_sunrise": "山中日出", |
|
|
"desert_evening": "沙漠黄昏", |
|
|
"dark_alley": "黑暗小巷的月光", |
|
|
"fairground": "游乐场的金色光芒", |
|
|
"forest_midnight": "森林深夜", |
|
|
"twilight_purple": "黄昏的紫粉色调", |
|
|
"foggy_morning": "雾蒙蒙的早晨", |
|
|
"rustic_candle": "乡村风格烛光", |
|
|
"office_fluorescent": "办公室荧光灯", |
|
|
"storm_lightning": "暴风雨中的闪电", |
|
|
"fireplace_night": "夜晚壁炉的温暖光芒", |
|
|
"ethereal_magic": "空灵发光,魔法森林", |
|
|
"beach_dusky": "海滩的黄昏", |
|
|
"trees_afternoon": "树林中的午后光线", |
|
|
"urban_blue_neon": "蓝色霓虹灯,城市街道", |
|
|
"rain_police": "雨中红蓝警灯", |
|
|
"aurora_arctic": "极光,北极景观", |
|
|
"foggy_mountains": "雾中山峦日出", |
|
|
"city_skyline": "城市天际线的黄金时刻", |
|
|
"twilight_mist": "神秘黄昏,浓雾", |
|
|
"forest_rays": "森林空地的清晨光线", |
|
|
"festival_lantern": "节日多彩灯笼光", |
|
|
"stained_glass": "彩色玻璃的柔和光芒", |
|
|
"dark_spotlight": "黑暗房间的强烈聚光", |
|
|
"lake_evening": "湖面柔和的黄昏光", |
|
|
"cave_crystal": "洞穴水晶反射", |
|
|
"autumn_forest": "秋林中的鲜艳光线", |
|
|
"snowfall_dusk": "黄昏轻柔降雪", |
|
|
"winter_hazy": "冬日清晨的朦胧光", |
|
|
"rain_city": "雨中城市灯光倒影", |
|
|
"trees_golden_sun": "金色阳光穿过树林", |
|
|
"fireflies_summer": "萤火虫点亮夏夜", |
|
|
"forge_embers": "锻造炉的发光余烬", |
|
|
"gothic_castle": "哥特城堡的昏暗烛光", |
|
|
"starlight_midnight": "午夜明亮星光", |
|
|
"rural_sunset": "乡村的温暖日落", |
|
|
"haunted_flicker": "闹鬼房屋的闪烁灯光", |
|
|
"desert_mirage": "沙漠日落海市蜃楼般的光", |
|
|
"storm_beams": "风暴云中穿透的金色光束", |
|
|
} |
|
|
|
|
|
|
|
|
if light_type != "none": |
|
|
prompt_parts.append(light_descriptions.get(light_type, "")) |
|
|
|
|
|
if illumination_env != "none": |
|
|
prompt_parts.append(illumination_envs.get(illumination_env, "")) |
|
|
|
|
|
if light_direction != "none": |
|
|
prompt_parts.append(direction_descriptions.get(light_direction, "")) |
|
|
|
|
|
if light_intensity != "none": |
|
|
prompt_parts.append(intensity_descriptions.get(light_intensity, "")) |
|
|
|
|
|
final_prompt = ",".join([p for p in prompt_parts if p]) |
|
|
|
|
|
|
|
|
if len(prompt_parts) > 1: |
|
|
final_prompt += ",对图片进行重新照明" |
|
|
|
|
|
return final_prompt if len(prompt_parts) > 1 else "重新照明,使用自然光线对图片进行重新照明" |
|
|
|
|
|
|
|
|
@spaces.GPU |
|
|
def infer_relight( |
|
|
image, |
|
|
light_type, |
|
|
light_direction, |
|
|
light_intensity, |
|
|
illumination_env, |
|
|
prompt, |
|
|
seed, |
|
|
randomize_seed, |
|
|
true_guidance_scale, |
|
|
num_inference_steps, |
|
|
height, |
|
|
width, |
|
|
prev_output = None, |
|
|
progress=gr.Progress(track_tqdm=True) |
|
|
): |
|
|
final_prompt = build_relight_prompt(light_type, light_direction, light_intensity, illumination_env, prompt) |
|
|
print(f"Generated Prompt: {final_prompt}") |
|
|
|
|
|
if randomize_seed: |
|
|
seed = random.randint(0, MAX_SEED) |
|
|
generator = torch.Generator(device=device).manual_seed(seed) |
|
|
|
|
|
|
|
|
pil_images = [] |
|
|
if image is not None: |
|
|
if isinstance(image, Image.Image): |
|
|
pil_images.append(image.convert("RGB")) |
|
|
elif hasattr(image, "name"): |
|
|
pil_images.append(Image.open(image.name).convert("RGB")) |
|
|
elif prev_output: |
|
|
pil_images.append(prev_output.convert("RGB")) |
|
|
|
|
|
if len(pil_images) == 0: |
|
|
raise gr.Error("Please upload an image first.") |
|
|
|
|
|
result = pipe( |
|
|
image=pil_images, |
|
|
prompt=final_prompt, |
|
|
height=height if height != 0 else None, |
|
|
width=width if width != 0 else None, |
|
|
num_inference_steps=num_inference_steps, |
|
|
generator=generator, |
|
|
true_cfg_scale=true_guidance_scale, |
|
|
num_images_per_prompt=1, |
|
|
).images[0] |
|
|
|
|
|
return result, seed, final_prompt |
|
|
|
|
|
def create_video_between_images(input_image, output_image, prompt: str, request: gr.Request) -> str: |
|
|
"""Create a video between the input and output images.""" |
|
|
if input_image is None or output_image is None: |
|
|
raise gr.Error("Both input and output images are required to create a video.") |
|
|
|
|
|
try: |
|
|
|
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp: |
|
|
input_image.save(tmp.name) |
|
|
input_image_path = tmp.name |
|
|
|
|
|
output_pil = Image.fromarray(output_image.astype('uint8')) |
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp: |
|
|
output_pil.save(tmp.name) |
|
|
output_image_path = tmp.name |
|
|
|
|
|
video_path = _generate_video_segment( |
|
|
input_image_path, |
|
|
output_image_path, |
|
|
prompt if prompt else "Relighting transformation", |
|
|
request |
|
|
) |
|
|
return video_path |
|
|
except Exception as e: |
|
|
raise gr.Error(f"Video generation failed: {e}") |
|
|
|
|
|
|
|
|
|
|
|
css = ''' |
|
|
#col-container { max-width: 1200px; margin: 0 auto; } |
|
|
.dark .progress-text{color: white !important} |
|
|
#examples{max-width: 1200px; margin: 0 auto; } |
|
|
.radio-group {display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 8px;} |
|
|
.radio-group [data-testid="block-info"] { display: none !important } |
|
|
''' |
|
|
|
|
|
def reset_all(): |
|
|
return ["none", "none", "none", "none", "", False, True] |
|
|
|
|
|
def end_reset(): |
|
|
return False |
|
|
|
|
|
def update_dimensions_on_upload(image): |
|
|
if image is None: |
|
|
return 1024, 1024 |
|
|
|
|
|
original_width, original_height = image.size |
|
|
|
|
|
if original_width > original_height: |
|
|
new_width = 1024 |
|
|
aspect_ratio = original_height / original_width |
|
|
new_height = int(new_width * aspect_ratio) |
|
|
else: |
|
|
new_height = 1024 |
|
|
aspect_ratio = original_width / original_height |
|
|
new_width = int(new_height * aspect_ratio) |
|
|
|
|
|
|
|
|
new_width = (new_width // 8) * 8 |
|
|
new_height = (new_height // 8) * 8 |
|
|
|
|
|
return new_width, new_height |
|
|
|
|
|
|
|
|
with gr.Blocks(theme=gr.themes.Citrus(), css=css) as demo: |
|
|
with gr.Column(elem_id="col-container"): |
|
|
gr.Markdown("## 💡 Qwen Image Edit — Relighting Control") |
|
|
gr.Markdown(""" |
|
|
Qwen Image Edit 2509 for Image Relighting ✨ |
|
|
Using [dx8152's Qwen-Image-Edit-2509-Relight LoRA](https://huggingface.co/dx8152/Qwen-Image-Edit-2509-Relight) and [lightx2v/Qwen-Image-Lightning](https://huggingface.co/lightx2v/Qwen-Image-Lightning) for 4-step inference 💨 |
|
|
""" |
|
|
) |
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
image = gr.Image(label="Input Image", type="pil") |
|
|
prev_output = gr.Image(value=None, visible=False) |
|
|
is_reset = gr.Checkbox(value=False, visible=False) |
|
|
|
|
|
with gr.Accordion("💡 Light Type", open=True): |
|
|
light_type = gr.Radio( |
|
|
choices=[ |
|
|
("None", "none"), |
|
|
("Soft Window Light", "soft_window"), |
|
|
("Golden Hour", "golden_hour"), |
|
|
("Studio Lighting", "studio"), |
|
|
("Dramatic", "dramatic"), |
|
|
("Natural Daylight", "natural"), |
|
|
("Neon", "neon"), |
|
|
("Candlelight", "candlelight"), |
|
|
("Moonlight", "moonlight"), |
|
|
("Sunrise", "sunrise"), |
|
|
("Sunset over Sea", "sunset_sea"), |
|
|
("Overcast", "overcast"), |
|
|
("Harsh Sunlight", "harsh_sun"), |
|
|
("Twilight", "twilight"), |
|
|
("Aurora", "aurora"), |
|
|
("Firelight", "firelight"), |
|
|
("Lightning", "lightning"), |
|
|
("Underwater", "underwater"), |
|
|
("Foggy", "foggy"), |
|
|
("Magic Light", "magic"), |
|
|
("Cyberpunk", "cyberpunk"), |
|
|
("Warm Home", "warm_home"), |
|
|
("Cold Industrial", "cold_industrial"), |
|
|
("Spotlight", "spotlight"), |
|
|
("Rim Light", "rim_light"), |
|
|
], |
|
|
value="none", |
|
|
elem_classes="radio-group" |
|
|
) |
|
|
|
|
|
with gr.Accordion("🧭 Light Direction", open=True): |
|
|
light_direction = gr.Radio( |
|
|
choices=[ |
|
|
("None", "none"), |
|
|
("Front", "front"), |
|
|
("Side", "side"), |
|
|
("Left", "left"), |
|
|
("Right", "right"), |
|
|
("Back (Backlight)", "back"), |
|
|
("Top", "top"), |
|
|
("Bottom", "bottom"), |
|
|
("Diagonal", "diagonal"), |
|
|
], |
|
|
value="none", |
|
|
elem_classes="radio-group" |
|
|
) |
|
|
|
|
|
with gr.Accordion("⚡ Light Intensity", open=True): |
|
|
light_intensity = gr.Radio( |
|
|
choices=[ |
|
|
("None", "none"), |
|
|
("Soft", "soft"), |
|
|
("Medium", "medium"), |
|
|
("Strong", "strong"), |
|
|
], |
|
|
value="none", |
|
|
elem_classes="radio-group" |
|
|
) |
|
|
|
|
|
with gr.Accordion("🌍 Illumination Environment", open=False): |
|
|
illumination_env = gr.Radio( |
|
|
choices=[ |
|
|
("None", "none"), |
|
|
("Sunshine from Window", "sunshine_window"), |
|
|
("Neon Night, City", "neon_city"), |
|
|
("Sci-Fi RGB Glowing, Cyberpunk", "sci_fi_rgb"), |
|
|
("Warm Atmosphere, at Home, Bedroom", "warm_bedroom"), |
|
|
("Magic Lit", "magic_lit"), |
|
|
("Evil, Gothic, in a Cave", "gothic_cave"), |
|
|
("Light and Shadow", "light_shadow"), |
|
|
("Shadow from Window", "window_shadow"), |
|
|
("Soft Studio Lighting", "soft_studio"), |
|
|
("Home Atmosphere, Cozy Bedroom", "cozy_bedroom"), |
|
|
("Neon, Wong Kar-wai, Warm", "wong_kar_wai"), |
|
|
("Moonlight through Curtains", "moonlight_curtains"), |
|
|
("Stormy Sky Lighting", "stormy_sky"), |
|
|
("Underwater Glow, Deep Sea", "underwater_glow"), |
|
|
("Foggy Forest at Dawn", "foggy_forest"), |
|
|
("Golden Hour in a Meadow", "meadow_golden"), |
|
|
("Rainbow Reflections, Neon", "rainbow_neon"), |
|
|
("Apocalyptic, Smoky Atmosphere", "apocalyptic"), |
|
|
("Red Glow, Emergency Lights", "emergency_red"), |
|
|
("Mystical Glow, Enchanted Forest", "mystical_forest"), |
|
|
("Campfire Light", "campfire"), |
|
|
("Harsh, Industrial Lighting", "industrial_harsh"), |
|
|
("Sunrise in the Mountains", "mountain_sunrise"), |
|
|
("Evening Glow in the Desert", "desert_evening"), |
|
|
("Moonlight in a Dark Alley", "dark_alley"), |
|
|
("Golden Glow at a Fairground", "fairground"), |
|
|
("Midnight in the Forest", "forest_midnight"), |
|
|
("Purple and Pink Hues at Twilight", "twilight_purple"), |
|
|
("Foggy Morning, Muted Light", "foggy_morning"), |
|
|
("Candle-lit Room, Rustic Vibe", "rustic_candle"), |
|
|
("Fluorescent Office Lighting", "office_fluorescent"), |
|
|
("Lightning Flash in Storm", "storm_lightning"), |
|
|
("Night, Cozy Warm Light from Fireplace", "fireplace_night"), |
|
|
("Ethereal Glow, Magical Forest", "ethereal_magic"), |
|
|
("Dusky Evening on a Beach", "beach_dusky"), |
|
|
("Afternoon Light Filtering through Trees", "trees_afternoon"), |
|
|
("Blue Neon Light, Urban Street", "urban_blue_neon"), |
|
|
("Red and Blue Police Lights in Rain", "rain_police"), |
|
|
("Aurora Borealis Glow, Arctic Landscape", "aurora_arctic"), |
|
|
("Sunrise through Foggy Mountains", "foggy_mountains"), |
|
|
("Golden Hour on a City Skyline", "city_skyline"), |
|
|
("Mysterious Twilight, Heavy Mist", "twilight_mist"), |
|
|
("Early Morning Rays, Forest Clearing", "forest_rays"), |
|
|
("Colorful Lantern Light at Festival", "festival_lantern"), |
|
|
("Soft Glow through Stained Glass", "stained_glass"), |
|
|
("Harsh Spotlight in Dark Room", "dark_spotlight"), |
|
|
("Mellow Evening Glow on a Lake", "lake_evening"), |
|
|
("Crystal Reflections in a Cave", "cave_crystal"), |
|
|
("Vibrant Autumn Lighting in a Forest", "autumn_forest"), |
|
|
("Gentle Snowfall at Dusk", "snowfall_dusk"), |
|
|
("Hazy Light of a Winter Morning", "winter_hazy"), |
|
|
("Rain-soaked Reflections in City Lights", "rain_city"), |
|
|
("Golden Sunlight Streaming through Trees", "trees_golden_sun"), |
|
|
("Fireflies Lighting up a Summer Night", "fireflies_summer"), |
|
|
("Glowing Embers from a Forge", "forge_embers"), |
|
|
("Dim Candlelight in a Gothic Castle", "gothic_castle"), |
|
|
("Midnight Sky with Bright Starlight", "starlight_midnight"), |
|
|
("Warm Sunset in a Rural Village", "rural_sunset"), |
|
|
("Flickering Light in a Haunted House", "haunted_flicker"), |
|
|
("Desert Sunset with Mirage-like Glow", "desert_mirage"), |
|
|
("Golden Beams Piercing through Storm Clouds", "storm_beams"), |
|
|
], |
|
|
value="none", |
|
|
elem_classes="radio-group" |
|
|
) |
|
|
|
|
|
with gr.Accordion("✍️ Custom Prompt", open=False): |
|
|
prompt = gr.Textbox( |
|
|
placeholder="Example: Add warm sunset lighting from the right", |
|
|
lines=3 |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
reset_btn = gr.Button("🔄 Reset") |
|
|
run_btn = gr.Button("✨ Generate", variant="primary") |
|
|
|
|
|
with gr.Accordion("⚙️ Advanced Settings", open=False): |
|
|
seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0) |
|
|
randomize_seed = gr.Checkbox(label="Randomize Seed", value=True) |
|
|
true_guidance_scale = gr.Slider(label="True Guidance Scale", minimum=1.0, maximum=10.0, step=0.1, value=1.0) |
|
|
num_inference_steps = gr.Slider(label="Inference Steps", minimum=1, maximum=40, step=1, value=4) |
|
|
height = gr.Slider(label="Height", minimum=256, maximum=2048, step=8, value=1024) |
|
|
width = gr.Slider(label="Width", minimum=256, maximum=2048, step=8, value=1024) |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
result = gr.Image(label="Output Image", interactive=False) |
|
|
prompt_preview = gr.Textbox(label="Processed Prompt", interactive=False) |
|
|
create_video_button = gr.Button("🎥 Create Video Between Images", variant="secondary", visible=False) |
|
|
with gr.Group(visible=False) as video_group: |
|
|
video_output = gr.Video(label="Generated Video", show_download_button=True, autoplay=True) |
|
|
|
|
|
inputs = [ |
|
|
image, light_type, light_direction, light_intensity, illumination_env, prompt, |
|
|
seed, randomize_seed, true_guidance_scale, num_inference_steps, height, width, prev_output |
|
|
] |
|
|
outputs = [result, seed, prompt_preview] |
|
|
|
|
|
|
|
|
reset_btn.click( |
|
|
fn=reset_all, |
|
|
inputs=None, |
|
|
outputs=[light_type, light_direction, light_intensity, illumination_env, prompt, is_reset], |
|
|
queue=False |
|
|
).then(fn=end_reset, inputs=None, outputs=[is_reset], queue=False) |
|
|
|
|
|
|
|
|
def infer_and_show_video_button(*args): |
|
|
result_img, result_seed, result_prompt = infer_relight(*args) |
|
|
|
|
|
show_button = args[0] is not None and result_img is not None |
|
|
return result_img, result_seed, result_prompt, gr.update(visible=show_button) |
|
|
|
|
|
run_event = run_btn.click( |
|
|
fn=infer_and_show_video_button, |
|
|
inputs=inputs, |
|
|
outputs=outputs + [create_video_button] |
|
|
) |
|
|
|
|
|
|
|
|
create_video_button.click( |
|
|
fn=lambda: gr.update(visible=True), |
|
|
outputs=[video_group], |
|
|
api_name=False |
|
|
).then( |
|
|
fn=create_video_between_images, |
|
|
inputs=[image, result, prompt_preview], |
|
|
outputs=[video_output], |
|
|
api_name=False |
|
|
) |
|
|
|
|
|
|
|
|
gr.Examples( |
|
|
examples=[ |
|
|
["harold.png", "dramatic", "side", "soft", "none", "", 0, True, 1.0, 4, 672, 1024], |
|
|
["distracted.png", "golden_hour", "side", "strong", "none", "", 0, True, 1.0, 4, 640, 1024], |
|
|
["disaster.jpg", "moonlight", "front", "medium", "neon_city", "", 0, True, 1.0, 4, 640, 1024], |
|
|
], |
|
|
inputs=[image, light_type, light_direction, light_intensity, illumination_env, prompt, |
|
|
seed, randomize_seed, true_guidance_scale, num_inference_steps, height, width], |
|
|
outputs=outputs, |
|
|
fn=infer_relight, |
|
|
cache_examples="lazy", |
|
|
elem_id="examples" |
|
|
) |
|
|
|
|
|
|
|
|
image.upload( |
|
|
fn=update_dimensions_on_upload, |
|
|
inputs=[image], |
|
|
outputs=[width, height] |
|
|
).then( |
|
|
fn=reset_all, |
|
|
inputs=None, |
|
|
outputs=[light_type, light_direction, light_intensity, illumination_env, prompt, is_reset], |
|
|
queue=False |
|
|
).then( |
|
|
fn=end_reset, |
|
|
inputs=None, |
|
|
outputs=[is_reset], |
|
|
queue=False |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
def maybe_infer(is_reset, progress=gr.Progress(track_tqdm=True), *args): |
|
|
if is_reset: |
|
|
return gr.update(), gr.update(), gr.update(), gr.update() |
|
|
else: |
|
|
result_img, result_seed, result_prompt = infer_relight(*args) |
|
|
|
|
|
show_button = args[0] is not None and result_img is not None |
|
|
return result_img, result_seed, result_prompt, gr.update(visible=show_button) |
|
|
|
|
|
control_inputs = [ |
|
|
image, light_type, light_direction, light_intensity, illumination_env, prompt, |
|
|
seed, randomize_seed, true_guidance_scale, num_inference_steps, height, width, prev_output |
|
|
] |
|
|
control_inputs_with_flag = [is_reset] + control_inputs |
|
|
|
|
|
for control in [light_type, light_direction, light_intensity, illumination_env]: |
|
|
control.input(fn=maybe_infer, inputs=control_inputs_with_flag, outputs=outputs + [create_video_button]) |
|
|
|
|
|
run_event.then(lambda img, *_: img, inputs=[result], outputs=[prev_output]) |
|
|
|
|
|
demo.launch() |