Every-Text / app.py
ginipick's picture
Update app.py
819fc44 verified
raw
history blame
10.4 kB
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)