Every-Text / app.py
ginipick's picture
Update app.py
e48aa5a verified
raw
history blame
10.3 kB
import os
import time
from os import path
import tempfile
import uuid
import base64
import mimetypes
import json
import io
import torch
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
# PIL ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ์ถ”๊ฐ€
from PIL import Image
#######################################
# 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
# (์˜ˆ์‹œ) Google GenAI ์‚ฌ์šฉ:
# export GAPI_TOKEN="<YOUR_GOOGLE_GENAI_API_KEY>"
# ๊ฐ„๋‹จํ•œ ํƒ€์ด๋จธ ํด๋ž˜์Šค
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)
# FLUX ํŒŒ์ดํ”„๋ผ์ธ ๋กœ๋“œ
pipe = FluxPipeline.from_pretrained(
"black-forest-labs/FLUX.1-dev",
torch_dtype=torch.bfloat16
)
# LoRA ๊ฐ€์ค‘์น˜ ๋กœ๋“œ
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)
# GPU๋กœ ์˜ฎ๊ธฐ๊ธฐ
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 ๋ชจ๋ธ ์ด๋ฆ„
"""
# (1) ํ™˜๊ฒฝ ๋ณ€์ˆ˜์—์„œ API ํ‚ค ๊ฐ€์ ธ์˜ค๊ธฐ (ํ•„์ˆ˜)
api_key = os.getenv("GAPI_TOKEN", None)
if not api_key:
raise ValueError(
"GAPI_TOKEN ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. "
"Google GenAI API๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” GAPI_TOKEN์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."
)
# (2) Google Client ์ดˆ๊ธฐํ™”
client = genai.Client(api_key=api_key)
# (3) ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ
files = [client.files.upload(file=file_name)]
# (4) 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),
],
),
]
# (5) ์ƒ์„ฑ/๋ณ€ํ™˜ ์„ค์ •
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
# 5) ์ŠคํŠธ๋ฆผ ํ˜•ํƒœ๋กœ ์‘๋‹ต ๋ฐ›์•„์„œ ์ด๋ฏธ์ง€/ํ…์ŠคํŠธ ๊ตฌ๋ถ„ ์ฒ˜๋ฆฌ
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
break
else:
# ์ด๋ฏธ์ง€ ์—†์ด ํ…์ŠคํŠธ๋งŒ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒฝ์šฐ(ํฌ๋ฐ•) ๋ˆ„์ 
text_response += chunk.text + "\n"
# ์—…๋กœ๋“œํ•œ File ๊ฐ์ฒด ์ œ๊ฑฐ
del files
return image_path, text_response
#######################################
# 3. Gradio ํ•จ์ˆ˜:
# (1) FLUX๋กœ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ->
# (2) Google GenAI๋กœ ํ…์ŠคํŠธ ๊ต์ฒด
#######################################
def generate_initial_image(prompt, text, height, width, steps, scale, seed):
"""
FLUX ํŒŒ์ดํ”„๋ผ์ธ์„ ์‚ฌ์šฉํ•ด 'ํ…์ŠคํŠธ๊ฐ€ ํฌํ•จ๋œ ์ด๋ฏธ์ง€๋ฅผ' ๋จผ์ € ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜.
prompt: ์ด๋ฏธ์ง€ ๋ฐฐ๊ฒฝ/์žฅ๋ฉด/์Šคํƒ€์ผ ๋ฌ˜์‚ฌ๋ฅผ ์œ„ํ•œ ํ”„๋กฌํ”„ํŠธ
text: ์‹ค์ œ๋กœ ์ด๋ฏธ์ง€์— ๋“ค์–ด๊ฐ€์•ผ ํ•  ๋ฌธ๊ตฌ(์˜ˆ: "์•ˆ๋…•ํ•˜์„ธ์š”", "Hello world" ๋“ฑ)
"""
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):
"""
Google GenAI์˜ 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 ๊ตฌ๋ฒ„์ „(4.26.0 ์ดํ•˜)์—๋Š” decode_base64_to_image๊ฐ€ ์—†์œผ๋ฏ€๋กœ PIL์„ ์ง์ ‘ ์‚ฌ์šฉ
with open(image_path, "rb") as f:
image_data = f.read()
# base64๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š๊ณ , PIL Image๋กœ ๋””์ฝ”๋”ฉ
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 ๊ธฐ๋ฐ˜ ์ด๋ฏธ์ง€ ์ƒ์„ฑ + Google GenAI๋ฅผ ํ†ตํ•œ ํ…์ŠคํŠธ ๋ณ€ํ™˜
**์ด ๋ฐ๋ชจ๋Š” ์•„๋ž˜ ๋‘ ๋‹จ๊ณ„๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.**
1) **Diffusion ๋ชจ๋ธ(FluxPipeline)์„ ์ด์šฉํ•ด** ์ด๋ฏธ์ง€ ์ƒ์„ฑ.
- ์ด๋•Œ, ์‚ฌ์šฉ์ž๊ฐ€ ์ง€์ •ํ•œ ํ…์ŠคํŠธ๋ฅผ ์ด๋ฏธ์ง€ ์•ˆ์— ํ‘œ์‹œํ•˜๋„๋ก ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.
2) **์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๋ฅผ Google GenAI(gemini) ๋ชจ๋ธ์— ์ „๋‹ฌ**ํ•˜์—ฌ,
- ์ด๋ฏธ์ง€ ๋‚ด ํ…์ŠคํŠธ ๋ถ€๋ถ„๋งŒ ๋‹ค๋ฅธ ๋ฌธ์ž์—ด๋กœ ๋ณ€๊ฒฝ.
---
"""
)
with gr.Row():
with gr.Column():
gr.Markdown("## 1) Step 1: FLUX๋กœ ํ…์ŠคํŠธ ํฌํ•จ ์ด๋ฏธ์ง€ ์ƒ์„ฑ")
prompt_input = gr.Textbox(
lines=3,
label="์ด๋ฏธ์ง€ ์žฅ๋ฉด/๋ฐฐ๊ฒฝ Prompt",
placeholder="์˜ˆ) A poster with futuristic neon style..."
)
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=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 (with text)",
type="pil"
)
with gr.Column():
gr.Markdown("## 2) Step 2: ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€ ๋‚ด ํ…์ŠคํŠธ ์ˆ˜์ •")
new_text_input = gr.Textbox(
label="์ƒˆ๋กœ ๋ฐ”๊ฟ€ ํ…์ŠคํŠธ",
placeholder="์˜ˆ) Hello world"
)
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]
)
# Gradio ์‹คํ–‰
demo.launch(max_threads=20)