Spaces:
Running
Running
import os | |
import time | |
from os import path | |
import tempfile | |
import uuid | |
import base64 | |
import mimetypes | |
import json | |
import io | |
import random | |
import string | |
import torch | |
from PIL import Image | |
from safetensors.torch import load_file | |
from huggingface_hub import hf_hub_download | |
# Diffusers ๊ด๋ จ ๋ผ์ด๋ธ๋ฌ๋ฆฌ | |
import gradio as gr | |
from diffusers import FluxPipeline | |
# Google GenAI ๋ผ์ด๋ธ๋ฌ๋ฆฌ | |
from google import genai | |
from google.genai import types | |
####################################### | |
# 0. ํ๊ฒฝ์ค์ | |
####################################### | |
BASE_DIR = path.dirname(path.abspath(__file__)) if "__file__" in globals() else os.getcwd() | |
CACHE_PATH = path.join(BASE_DIR, "models") | |
os.environ["TRANSFORMERS_CACHE"] = CACHE_PATH | |
os.environ["HF_HUB_CACHE"] = CACHE_PATH | |
os.environ["HF_HOME"] = CACHE_PATH | |
# ํ์ด๋จธ ํด๋์ค | |
class timer: | |
def __init__(self, method_name="timed process"): | |
self.method = method_name | |
def __enter__(self): | |
self.start = time.time() | |
print(f"{self.method} starts") | |
def __exit__(self, exc_type, exc_val, exc_tb): | |
end = time.time() | |
print(f"{self.method} took {str(round(end - self.start, 2))}s") | |
####################################### | |
# 1. FLUX ํ์ดํ๋ผ์ธ ๋ก๋ | |
####################################### | |
if not path.exists(CACHE_PATH): | |
os.makedirs(CACHE_PATH, exist_ok=True) | |
pipe = FluxPipeline.from_pretrained( | |
"black-forest-labs/FLUX.1-dev", | |
torch_dtype=torch.bfloat16 | |
) | |
lora_path = hf_hub_download("ByteDance/Hyper-SD", "Hyper-FLUX.1-dev-8steps-lora.safetensors") | |
pipe.load_lora_weights(lora_path) | |
pipe.fuse_lora(lora_scale=0.125) | |
pipe.to(device="cuda", dtype=torch.bfloat16) | |
####################################### | |
# 2. Google GenAI ๋ชจ๋ธ๋ก ํ ์คํธ ๋ณํ ํจ์ | |
####################################### | |
def save_binary_file(file_name, data): | |
"""Google GenAI์์ ์๋ต๋ฐ์ ์ด์ง ๋ฐ์ดํฐ๋ฅผ ์ด๋ฏธ์ง ํ์ผ๋ก ์ ์ฅ""" | |
with open(file_name, "wb") as f: | |
f.write(data) | |
def generate_by_google_genai(text, file_name, model="gemini-2.0-flash-exp"): | |
""" | |
Google GenAI(gemini) ๋ชจ๋ธ์ ํตํด ์ด๋ฏธ์ง/ํ ์คํธ๋ฅผ ์์ฑํ๊ฑฐ๋ ๋ณํ. | |
- text: ๋ณ๊ฒฝํ ํ ์คํธ๋ ๋ช ๋ น์ด ๋ฑ ํ๋กฌํํธ | |
- file_name: ์๋ณธ ์ด๋ฏธ์ง(์: .png) ๊ฒฝ๋ก | |
- model: ์ฌ์ฉํ gemini ๋ชจ๋ธ ์ด๋ฆ | |
""" | |
api_key = os.getenv("GAPI_TOKEN", None) | |
if not api_key: | |
raise ValueError( | |
"GAPI_TOKEN ํ๊ฒฝ ๋ณ์๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค. " | |
"Google GenAI API ์ฌ์ฉ์ ์ํด์๋ GAPI_TOKEN์ด ํ์ํฉ๋๋ค." | |
) | |
client = genai.Client(api_key=api_key) | |
files = [client.files.upload(file=file_name)] | |
contents = [ | |
types.Content( | |
role="user", | |
parts=[ | |
types.Part.from_uri( | |
file_uri=files[0].uri, | |
mime_type=files[0].mime_type, | |
), | |
types.Part.from_text(text=text), | |
], | |
), | |
] | |
generate_content_config = types.GenerateContentConfig( | |
temperature=1, | |
top_p=0.95, | |
top_k=40, | |
max_output_tokens=8192, | |
response_modalities=["image", "text"], | |
response_mime_type="text/plain", | |
) | |
text_response = "" | |
image_path = None | |
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp: | |
temp_path = tmp.name | |
for chunk in client.models.generate_content_stream( | |
model=model, | |
contents=contents, | |
config=generate_content_config, | |
): | |
if not chunk.candidates or not chunk.candidates[0].content or not chunk.candidates[0].content.parts: | |
continue | |
candidate = chunk.candidates[0].content.parts[0] | |
if candidate.inline_data: | |
save_binary_file(temp_path, candidate.inline_data.data) | |
print(f"File of mime type {candidate.inline_data.mime_type} saved to: {temp_path}") | |
image_path = temp_path | |
break | |
else: | |
text_response += chunk.text + "\n" | |
del files | |
return image_path, text_response | |
####################################### | |
# 3. Diffusion + GoogleGenAI๋ฅผ ์ฐ๊ฒฐ | |
####################################### | |
def generate_initial_image(prompt, text, height, width, steps, scale, seed): | |
""" | |
1) FLUX ํ์ดํ๋ผ์ธ์ ์ฌ์ฉํด 'text'๊ฐ ๋ค์ด๊ฐ ์ด๋ฏธ์ง๋ฅผ ์์ฑ | |
- prompt ๋ด <text>๊ฐ ์์ผ๋ฉด ์นํ, ์์ผ๋ฉด ์๋ ์ถ๊ฐ | |
""" | |
if "<text>" in prompt: | |
combined_prompt = prompt.replace("<text>", text) | |
else: | |
combined_prompt = f"{prompt} with clear readable text that says '{text}'" | |
print("[DEBUG] combined_prompt:", combined_prompt) | |
with torch.inference_mode(), torch.autocast("cuda", dtype=torch.bfloat16), timer("GenerateInitialImage"): | |
result = pipe( | |
prompt=[combined_prompt], | |
generator=torch.Generator().manual_seed(int(seed)), | |
num_inference_steps=int(steps), | |
guidance_scale=float(scale), | |
height=int(height), | |
width=int(width), | |
max_sequence_length=256 | |
).images[0] | |
return result | |
def change_text_in_image(original_image, new_text): | |
""" | |
2) Gemini ๋ชจ๋ธ์ ํตํด, | |
original_image ๋ด ํ ์คํธ๋ฅผ `new_text`๋ก ๋ณ๊ฒฝํ ์ด๋ฏธ์ง ๋ฐํ | |
""" | |
try: | |
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp: | |
original_path = tmp.name | |
original_image.save(original_path) | |
image_path, text_response = generate_by_google_genai( | |
text=f"Change the text in this image to: '{new_text}'", | |
file_name=original_path | |
) | |
if image_path: | |
with open(image_path, "rb") as f: | |
image_data = f.read() | |
modified_img = Image.open(io.BytesIO(image_data)) | |
return modified_img | |
else: | |
# ์ด๋ฏธ์ง๊ฐ ์์ด ํ ์คํธ๋ง ์๋ต๋ ๊ฒฝ์ฐ | |
return None | |
except Exception as e: | |
raise gr.Error(f"Error: {e}") | |
####################################### | |
# 4. ์์ ์ํ๋ฒณ ์์ฑ | |
####################################### | |
def generate_random_letters(length: int) -> str: | |
""" | |
length ๊ธธ์ด์ ์์ ์ํ๋ฒณ(๋์๋ฌธ์) ๋ฌธ์์ด ์์ฑ | |
""" | |
letters = string.ascii_lowercase + string.ascii_uppercase | |
return "".join(random.choice(letters) for _ in range(length)) | |
####################################### | |
# 5. ์ต์ข ํจ์: ๋ฒํผ ํ ๋ฒ์ผ๋ก | |
# (1) ๋ฌด์์ ์ํ๋ฒณ์ผ๋ก 1์ฐจ ์ด๋ฏธ์ง ์์ฑ | |
# (2) ์ง์ง "์๋ก ๋ฐ๊ฟ ํ ์คํธ"๋ก 2์ฐจ ์ด๋ฏธ์ง ์์ฑ | |
####################################### | |
def run_full_process(prompt, final_text, height, width, steps, scale, seed): | |
""" | |
- final_text์ ๊ธธ์ด์ ๋ง์ถฐ ๋๋ค ์ํ๋ฒณ์ ์์ฑ -> 1์ฐจ ์ด๋ฏธ์ง | |
- ๊ทธ 1์ฐจ ์ด๋ฏธ์ง๋ฅผ ๋ฐํ์ผ๋ก, final_text๋ก ๊ต์ฒด -> 2์ฐจ ์ต์ข ์ด๋ฏธ์ง | |
""" | |
# (A) ์๋ก ๋ฐ๊ฟ ํ ์คํธ(final_text) ๊ธ์์๋งํผ ์์ ์ํ๋ฒณ ์์ฑ | |
random_len = len(final_text) | |
random_text = generate_random_letters(random_len) | |
print(f"[STEP] final_text='{final_text}' => random_text='{random_text}'") | |
# (B) 1์ฐจ ์ด๋ฏธ์ง: ๋ฌด์์ ์ํ๋ฒณ์ผ๋ก ์์ฑ | |
random_image = generate_initial_image(prompt, random_text, height, width, steps, scale, seed) | |
# (C) 2์ฐจ ์ด๋ฏธ์ง: ์ค์ final_text๋ก ๊ต์ฒด | |
final_image = change_text_in_image(random_image, final_text) | |
return [random_image, final_image] | |
####################################### | |
# 6. Gradio UI | |
####################################### | |
with gr.Blocks(title="Flux + Google GenAI (Random & Then Real Text)") as demo: | |
gr.Markdown( | |
""" | |
# Flux + Google GenAI: ๋ ๋จ๊ณ์ ๊ฑธ์น ํ ์คํธ ๊ต์ฒด | |
**์ฌ์ฉ ํ๋ฆ** | |
1) Prompt์ ์ฅ๋ฉด์ด๋ ์คํ์ผ์ ์์ฑ (ํ์ํ๋ฉด `<text>` ๊ตฌ๋ถ์ ์ฌ์ฉ) | |
2) "์๋ก ๋ฐ๊ฟ ํ ์คํธ" ์ ์ต์ข ์ํ๋ ๋ฌธ์์ด์ ์ ๋ ฅ (์: "์๋ ํ์ธ์") | |
3) "Generate Images" ๋ฒํผ์ ๋๋ฅด๋ฉด, | |
- ๋จผ์ "์๋ก ๋ฐ๊ฟ ํ ์คํธ" ๊ธธ์ด์ ๋ง๋ **๋ฌด์์ ์ํ๋ฒณ**์ ๋ฃ์ด ์ด๋ฏธ์ง ์์ฑ (1์ฐจ ์ด๋ฏธ์ง) | |
- ์ด์ด์ **์ง์ง** "์๋ก ๋ฐ๊ฟ ํ ์คํธ"๋ก ๋ค์ ๊ต์ฒด(2์ฐจ ์ต์ข ์ด๋ฏธ์ง) | |
4) ๊ฒฐ๊ณผ๋ก ๋ ์ฅ์ ์ด๋ฏธ์ง๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. | |
**์ฃผ์ ํฌ์ธํธ** | |
- "์ด๋ฏธ์ง ์์ ๋ค์ด๊ฐ ํ ์คํธ"๋ **UI์ ๋ ธ์ถ๋์ง ์์ผ๋ฉฐ**(์ฌ์ฉ์ ์ ๋ ฅ ๋ถ๊ฐ), ์ค์ง ๋ด๋ถ์์ ์๋ ์ค์ ๋ฉ๋๋ค. | |
- 1์ฐจ ์ด๋ฏธ์ง๋ ์์ ํ ์์์ ์ํ๋ฒณ ํ ์คํธ๋ฅผ ํฌํจํฉ๋๋ค. | |
- 2์ฐจ ์ด๋ฏธ์ง๋ ์ต์ข ์ ์ผ๋ก ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ "์๋ก ๋ฐ๊ฟ ํ ์คํธ"๋ฅผ ํฌํจํฉ๋๋ค. | |
""" | |
) | |
with gr.Row(): | |
with gr.Column(): | |
prompt_input = gr.Textbox( | |
lines=3, | |
label="Prompt (use `<text>` if you want)", | |
placeholder="e.g. A white cat with a speech bubble <text>" | |
) | |
final_text_input = gr.Textbox( | |
lines=1, | |
label="์๋ก ๋ฐ๊ฟ ํ ์คํธ", | |
placeholder="์) ์๋ ํ์ธ์" | |
) | |
with gr.Accordion("๊ณ ๊ธ ์ค์ (ํ์ฅ)", open=False): | |
height = gr.Slider(label="Height", minimum=256, maximum=1152, step=64, value=512) | |
width = gr.Slider(label="Width", minimum=256, maximum=1152, step=64, value=512) | |
steps = gr.Slider(label="Inference Steps", minimum=6, maximum=25, step=1, value=8) | |
scale = gr.Slider(label="Guidance Scale", minimum=0.0, maximum=10.0, step=0.5, value=3.5) | |
seed = gr.Number(label="Seed (reproducibility)", value=1234, precision=0) | |
run_btn = gr.Button("Generate Images", variant="primary") | |
with gr.Column(): | |
random_image_output = gr.Image(label="1) Random Text Image", type="pil") | |
final_image_output = gr.Image(label="2) Final Text Image", type="pil") | |
# ๋ฒํผ ์ก์ : ์ ๋จ๊ณ๋ค์ ๋ชจ๋ ์คํ -> ๊ฒฐ๊ณผ 2์ฅ ์ถ๋ ฅ | |
run_btn.click( | |
fn=run_full_process, | |
inputs=[prompt_input, final_text_input, height, width, steps, scale, seed], | |
outputs=[random_image_output, final_image_output] | |
) | |
demo.launch(max_threads=20) | |