Every-Text / app.py
ginipick's picture
Update app.py
b2f5030 verified
raw
history blame
9.62 kB
import os
import time
from os import path
import tempfile
import uuid
import base64
import mimetypes
import json
import io
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 ๋ชจ๋ธ ์ด๋ฆ„
"""
# GAPI_TOKEN ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ํ‚ค๋ฅผ ๊ฐ€์ ธ์˜ด (ํ•„์ˆ˜)
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)]
# gemini์— ์ „๋‹ฌํ•  Content ์ค€๋น„
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]
# inline_data(์ด๋ฏธ์ง€) ์‘๋‹ต์ธ ๊ฒฝ์šฐ
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. Gradio ํ•จ์ˆ˜
#######################################
def generate_initial_image(prompt, text, height, width, steps, scale, seed):
"""
FLUX๋ฅผ ์ด์šฉํ•ด ํ…์ŠคํŠธ๊ฐ€ ํฌํ•จ๋œ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑ
- prompt ๋‚ด์— <text>๋ผ๋Š” ํŠน์ˆ˜ ๊ตฌ๋ถ„์ž๊ฐ€ ์žˆ์œผ๋ฉด, ๊ฑฐ๊ธฐ์— text๊ฐ€ ์น˜ํ™˜๋จ.
- ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ, ๊ธฐ์กด์ฒ˜๋Ÿผ prompt ๋’ค์— โ€œwith clear readable text that says ...โ€๋ฅผ ์ถ”๊ฐ€.
"""
if "<text>" in prompt:
combined_prompt = prompt.replace("<text>", text)
else:
combined_prompt = f"{prompt} with clear readable text that says '{text}'"
with torch.inference_mode(), torch.autocast("cuda", dtype=torch.bfloat16), timer("inference"):
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):
"""
Gemini ๋ชจ๋ธ์„ ํ†ตํ•ด,
์—…๋กœ๋“œ๋œ ์ด๋ฏธ์ง€ ๋‚ด๋ถ€์˜ ๋ฌธ๊ตฌ๋ฅผ `new_text`๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ๋Š” ํ•จ์ˆ˜.
"""
try:
# ์ž„์‹œ ํŒŒ์ผ์— ๋จผ์ € ์ €์žฅ
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
original_path = tmp.name
original_image.save(original_path)
# Gemini ๋ชจ๋ธ ํ˜ธ์ถœ
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:
# Gradio ๊ตฌ๋ฒ„์ „์—๋Š” decode_base64_to_image๊ฐ€ ์—†์œผ๋ฏ€๋กœ PIL์„ ์ง์ ‘ ์‚ฌ์šฉ
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, text_response
except Exception as e:
raise gr.Error(f"Error: {e}")
#######################################
# 4. Gradio ์ธํ„ฐํŽ˜์ด์Šค
#######################################
with gr.Blocks(title="Flux + Google GenAI Text Replacement") as demo:
gr.Markdown(
"""
# Flux Image Generation + Google GenAI Text Replacement
**Usage Instructions (in English)**
1. Write a prompt that may contain the special placeholder `<text>`.
- Example: `A white cat says <text> in a cartoon style`.
2. Enter the actual text in the "Text to Include in the Image" field.
- Example: `์•ˆ๋…•`
3. Click the "Generate Base Image" button.
- The prompt will be transformed so that `<text>` is replaced with your actual text.
- If `<text>` is **not** found, the text will be appended automatically as `with clear readable text that says ...`.
4. (Optional) If you want to change the text again, use the "Change Text in Image" button.
---
"""
)
with gr.Row():
with gr.Column():
gr.Markdown("## 1) Generate the Base Image (FLUX)")
prompt_input = gr.Textbox(
lines=3,
label="Prompt (with optional `<text>` placeholder)",
placeholder="e.g. A white cat says <text> in a cartoon style"
)
text_input = gr.Textbox(
lines=1,
label="Text to Include in the Image",
placeholder="e.g. ์•ˆ๋…•"
)
with gr.Accordion("Advanced Settings", 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=5.0, step=0.1, value=3.5)
seed = gr.Number(label="Seed (reproducibility)", value=1234, precision=0)
generate_btn = gr.Button("Generate Base Image", variant="primary")
generated_image = gr.Image(label="Generated Image", type="pil")
with gr.Column():
gr.Markdown("## 2) (Optional) Change Text in the Generated Image (Gemini)")
new_text_input = gr.Textbox(
label="New Text to Insert",
placeholder="e.g. Hello"
)
modify_btn = gr.Button("Change Text in Image via Gemini", variant="secondary")
output_img = gr.Image(label="Modified Image", type="pil")
output_txt = gr.Textbox(label="(If only text returned)")
# ๋ฒ„ํŠผ ์•ก์…˜ ์—ฐ๊ฒฐ
generate_btn.click(
fn=generate_initial_image,
inputs=[prompt_input, text_input, height, width, steps, scale, seed],
outputs=[generated_image]
)
modify_btn.click(
fn=change_text_in_image,
inputs=[generated_image, new_text_input],
outputs=[output_img, output_txt]
)
demo.launch(max_threads=20)