Spaces:
				
			
			
	
			
			
		Runtime error
		
	
	
	
			
			
	
	
	
	
		
		
		Runtime error
		
	| 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 ๋ชจ๋ธ ์ด๋ฆ | |
| """ | |
| # (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 | |
| # ์๋ต ์คํธ๋ฆผ์ ๋ฐ์ผ๋ฉด์ ์ด๋ฏธ์ง/ํ ์คํธ ๊ตฌ๋ถ ์ฒ๋ฆฌ | |
| 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" | |
| # ์ ๋ก๋ํ 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>๋ฅผ text๋ก ์นํ | |
| - <text>๊ฐ ์๋ค๋ฉด "with clear readable text that says '<text>'"๋ฅผ ์๋ ๋ถ์ | |
| """ | |
| if "<text>" in prompt: | |
| combined_prompt = prompt.replace("<text>", text) | |
| else: | |
| combined_prompt = f"{prompt} with clear readable text that says '{text}'" | |
| # ๋๋ฒ๊ทธ์ฉ: ์ต์ข ๋ค์ด๊ฐ๋ ํ๋กฌํํธ๋ฅผ ํ์ธ | |
| print(f"[DEBUG] Final combined_prompt: {combined_prompt}") | |
| 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 ๊ตฌ๋ฒ์ ์๋ 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 ๊ธฐ๋ฐ ์ด๋ฏธ์ง ์์ฑ + Google GenAI๋ฅผ ํตํ ํ ์คํธ ๋ณํ | |
| **Usage**: | |
| - You can include `<text>` in the prompt. For example: | |
| `white cat with speech bubble says <text>` | |
| - Then, type the actual text in "Text to Include in the Image" (ex: "Hello" or "์๋ "). | |
| - If `<text>` is not found in your prompt, the text will be automatically appended as: | |
| `with clear readable text that says '<text>'`. | |
| - Finally, you can optionally change the text again via Gemini. | |
| --- | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("## 1) Step 1: FLUX๋ก ํ ์คํธ ํฌํจ ์ด๋ฏธ์ง ์์ฑ") | |
| prompt_input = gr.Textbox( | |
| lines=3, | |
| label="์ด๋ฏธ์ง ์ฅ๋ฉด/๋ฐฐ๊ฒฝ Prompt (use `<text>` placeholder if you like)", | |
| placeholder="e.g. A white cat with speech bubble says <text>" | |
| ) | |
| text_input = gr.Textbox( | |
| lines=1, | |
| label="์ด๋ฏธ์ง ์์ ๋ค์ด๊ฐ ํ ์คํธ", | |
| placeholder="e.g. Hello or ์๋ " | |
| ) | |
| 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) | |
| 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] | |
| ) | |
| demo.launch(max_threads=20) | |
 
			
