ginipick commited on
Commit
5132eef
ยท
verified ยท
1 Parent(s): 006829f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +167 -113
app.py CHANGED
@@ -9,109 +9,138 @@ import torch
9
  from diffusers import DiffusionPipeline
10
  from PIL import Image
11
 
12
- # -----------------------------
13
- # Gemini API & Text Rendering ๊ด€๋ จ ์ถ”๊ฐ€ ๋ชจ๋“ˆ
14
- # -----------------------------
15
  import re
16
  import tempfile
17
  import io
18
  import logging
19
- import base64
20
- import string
21
- import requests
22
- from google import genai
23
- from google.genai import types
24
-
25
- import numpy as np
26
-
27
 
 
 
 
 
 
28
 
29
  logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
30
 
 
 
 
 
31
  def maybe_translate_to_english(text: str) -> str:
32
  """
33
- ํ…์ŠคํŠธ์— ํ•œ๊ธ€์ด ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉด ๊ฐ„๋‹จํ•œ ๊ทœ์น™์— ๋”ฐ๋ผ ์˜์–ด๋กœ ๋ณ€ํ™˜.
34
  """
35
- if not text or not re.search("[๊ฐ€-ํžฃ]", text):
36
- return text
37
- try:
38
- translations = {
39
- "์•ˆ๋…•ํ•˜์„ธ์š”": "Hello",
40
- "ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค": "Welcome",
41
- "์•„๋ฆ„๋‹ค์šด ๋‹น์‹ ": "Beautiful You",
42
- "์•ˆ๋…•": "Hello",
43
- "๊ณ ์–‘์ด": "Cat",
44
- "๋ฐฐ๋„ˆ": "Banner",
45
- "์ฌ๊ธ€๋ผ์Šค": "Sunglasses",
46
- "์ฐฉ์šฉํ•œ": "wearing",
47
- "ํฐ์ƒ‰": "white"
48
- }
49
- for kr, en in translations.items():
50
- if kr in text:
51
- text = text.replace(kr, en)
52
- print(f"[TRANSLATE] Translated Korean text: '{text}'")
53
- return text
54
- except Exception as e:
55
- print(f"[WARNING] Translation failed: {e}")
56
- return text
57
 
58
  def save_binary_file(file_name, data):
 
59
  with open(file_name, "wb") as f:
60
  f.write(data)
61
 
62
  def generate_by_google_genai(text, file_name, model="gemini-2.0-flash-exp"):
63
  """
64
- Gemini API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ์ด๋ฏธ์ง€ ํŽธ์ง‘/์ƒ์„ฑ์„ ์ˆ˜ํ–‰.
 
 
65
  """
66
- api_key = os.getenv("GAPI_TOKEN", None)
67
  if not api_key:
68
  raise ValueError("GAPI_TOKEN is missing. Please set an API key.")
69
- client = genai.Client(api_key=api_key)
70
- files = [client.files.upload(file=file_name)]
 
 
 
 
 
 
71
  contents = [
72
- types.Content(
73
  role="user",
74
  parts=[
75
- types.Part.from_uri(
76
- file_uri=files[0].uri,
77
- mime_type=files[0].mime_type,
 
78
  ),
79
- types.Part.from_text(text=text),
 
80
  ],
81
  ),
82
  ]
83
- generate_content_config = types.GenerateContentConfig(
 
 
84
  temperature=1,
85
  top_p=0.95,
86
  top_k=40,
87
- max_output_tokens=8192,
88
- response_modalities=["image", "text"],
89
  response_mime_type="text/plain",
90
  )
91
- text_response = ""
92
- image_path = None
 
 
 
93
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
94
  temp_path = tmp.name
95
- for chunk in client.models.generate_content_stream(
 
 
96
  model=model,
97
  contents=contents,
98
- config=generate_content_config,
99
- ):
100
- if not chunk.candidates or not chunk.candidates[0].content or not chunk.candidates[0].content.parts:
101
- continue
102
- candidate = chunk.candidates[0].content.parts[0]
103
- if candidate.inline_data:
104
- save_binary_file(temp_path, candidate.inline_data.data)
105
- print(f"File of mime type {candidate.inline_data.mime_type} saved to: {temp_path}")
106
- image_path = temp_path
 
 
 
 
 
 
 
 
 
 
 
107
  break
108
- else:
109
- text_response += chunk.text + "\n"
110
- del files
 
111
  return image_path, text_response
112
 
 
 
 
 
113
  def change_text_in_image_two_times(original_image, instruction):
114
- # ๋งŒ์•ฝ ์ด๋ฏธ์ง€๊ฐ€ numpy.ndarray ํƒ€์ž…์ด๋ฉด PIL Image๋กœ ๋ณ€ํ™˜
 
 
 
 
 
115
  if isinstance(original_image, np.ndarray):
116
  original_image = Image.fromarray(original_image)
117
 
@@ -123,38 +152,46 @@ def change_text_in_image_two_times(original_image, instruction):
123
  original_path = tmp.name
124
  if isinstance(original_image, Image.Image):
125
  original_image.save(original_path, format="PNG")
126
- print(f"[DEBUG] Saved image to temporary file: {original_path}")
127
  else:
128
  raise gr.Error(f"์˜ˆ์ƒ๋œ PIL Image๊ฐ€ ์•„๋‹Œ {type(original_image)} ํƒ€์ž…์ด ์ œ๊ณต๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
129
- # ์ดํ›„ Gemini API ํ˜ธ์ถœ ๋กœ์ง ์œ ์ง€
130
  image_path, text_response = generate_by_google_genai(
131
  text=mod_instruction,
132
  file_name=original_path
133
  )
134
  if image_path:
 
135
  try:
136
  with open(image_path, "rb") as f:
137
  image_data = f.read()
138
  new_img = Image.open(io.BytesIO(image_data))
139
  results.append(new_img)
140
  except Exception as img_err:
141
- print(f"[ERROR] Failed to process Gemini image: {img_err}")
142
  results.append(original_image)
143
  else:
144
- print(f"[WARNING] ์ด๋ฏธ์ง€๊ฐ€ ๋ฐ˜ํ™˜๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ…์ŠคํŠธ ์‘๋‹ต: {text_response}")
145
  results.append(original_image)
146
  except Exception as e:
147
  logging.exception(f"Text modification error: {e}")
148
  results.append(original_image)
149
  return results
150
 
 
 
 
151
 
152
  def gemini_text_rendering(image, rendering_text):
153
  """
154
- ์ฃผ์–ด์ง„ ์ด๋ฏธ์ง€์— ๋Œ€ํ•ด Gemini API๋ฅผ ์‚ฌ์šฉํ•ด ํ…์ŠคํŠธ ๋ Œ๋”๋ง์„ ์ ์šฉ.
155
  """
156
  rendering_text_en = maybe_translate_to_english(rendering_text)
157
- instruction = f"Render the following text on the image in a clear, visually appealing manner: {rendering_text_en}."
 
 
 
 
158
  rendered_images = change_text_in_image_two_times(image, instruction)
159
  if rendered_images and len(rendered_images) > 0:
160
  return rendered_images[0]
@@ -162,32 +199,16 @@ def gemini_text_rendering(image, rendering_text):
162
 
163
  def apply_text_rendering(image, rendering_text):
164
  """
165
- ์ž…๋ ฅ๋œ ํ…์ŠคํŠธ๊ฐ€ ์žˆ์œผ๋ฉด Gemini API๋ฅผ ํ†ตํ•ด ์ด๋ฏธ์ง€์— ํ…์ŠคํŠธ ๋ Œ๋”๋ง์„ ์ ์šฉํ•˜๊ณ , ์—†์œผ๋ฉด ์›๋ณธ ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜.
 
166
  """
167
  if rendering_text and rendering_text.strip():
168
  return gemini_text_rendering(image, rendering_text)
169
  return image
170
 
171
- # -----------------------------
172
- # ๊ธฐ์กด Diffusion Pipeline ๊ด€๋ จ ์ฝ”๋“œ
173
- # -----------------------------
174
- import gradio_client.utils
175
- import types
176
-
177
- original_json_schema = gradio_client.utils._json_schema_to_python_type
178
- def patched_json_schema(schema, defs=None):
179
- if isinstance(schema, bool):
180
- return "bool"
181
- try:
182
- if "additionalProperties" in schema and isinstance(schema["additionalProperties"], bool):
183
- schema["additionalProperties"] = {"type": "any"}
184
- except (TypeError, KeyError):
185
- pass
186
- try:
187
- return original_json_schema(schema, defs)
188
- except Exception as e:
189
- return "any"
190
- gradio_client.utils._json_schema_to_python_type = patched_json_schema
191
 
192
  SAVE_DIR = "saved_images"
193
  if not os.path.exists(SAVE_DIR):
@@ -198,24 +219,27 @@ repo_id = "black-forest-labs/FLUX.1-dev"
198
  adapter_id = "openfree/flux-chatgpt-ghibli-lora"
199
 
200
  def load_model_with_retry(max_retries=5):
 
 
 
201
  for attempt in range(max_retries):
202
  try:
203
- print(f"Loading model attempt {attempt+1}/{max_retries}...")
204
  pipeline = DiffusionPipeline.from_pretrained(
205
- repo_id,
206
  torch_dtype=torch.bfloat16,
207
  use_safetensors=True,
208
  resume_download=True
209
  )
210
- print("Model loaded successfully, loading LoRA weights...")
211
  pipeline.load_lora_weights(adapter_id)
212
  pipeline = pipeline.to(device)
213
- print("Pipeline ready!")
214
  return pipeline
215
  except Exception as e:
216
  if attempt < max_retries - 1:
217
  wait_time = 10 * (attempt + 1)
218
- print(f"Error loading model: {e}. Retrying in {wait_time} seconds...")
219
  import time
220
  time.sleep(wait_time)
221
  else:
@@ -227,21 +251,31 @@ MAX_SEED = np.iinfo(np.int32).max
227
  MAX_IMAGE_SIZE = 1024
228
 
229
  def save_generated_image(image, prompt):
 
 
 
230
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
231
  unique_id = str(uuid.uuid4())[:8]
232
  filename = f"{timestamp}_{unique_id}.png"
233
  filepath = os.path.join(SAVE_DIR, filename)
234
  image.save(filepath)
 
235
  metadata_file = os.path.join(SAVE_DIR, "metadata.txt")
236
  with open(metadata_file, "a", encoding="utf-8") as f:
237
  f.write(f"{filename}|{prompt}|{timestamp}\n")
238
  return filepath
239
 
240
  def load_generated_images():
 
 
 
241
  if not os.path.exists(SAVE_DIR):
242
  return []
243
- image_files = [os.path.join(SAVE_DIR, f) for f in os.listdir(SAVE_DIR)
244
- if f.endswith(('.png', '.jpg', '.jpeg', '.webp'))]
 
 
 
245
  image_files.sort(key=lambda x: os.path.getctime(x), reverse=True)
246
  return image_files
247
 
@@ -257,9 +291,13 @@ def inference(
257
  lora_scale: float,
258
  progress: gr.Progress = gr.Progress(track_tqdm=True),
259
  ):
 
 
 
260
  if randomize_seed:
261
  seed = random.randint(0, MAX_SEED)
262
  generator = torch.Generator(device=device).manual_seed(seed)
 
263
  try:
264
  image = pipeline(
265
  prompt=prompt,
@@ -270,16 +308,19 @@ def inference(
270
  generator=generator,
271
  joint_attention_kwargs={"scale": lora_scale},
272
  ).images[0]
 
273
  filepath = save_generated_image(image, prompt)
274
  return image, seed, load_generated_images()
 
275
  except Exception as e:
276
- print(f"Error during inference: {e}")
277
  error_img = Image.new('RGB', (width, height), color='red')
278
  return error_img, seed, load_generated_images()
279
 
280
- # -----------------------------
281
- # Gradio UI (์ž…๋ ฅ ํ”„๋กฌํ”„ํŠธ ์•„๋ž˜์— Text Rendering ์ž…๋ ฅ๋ž€ ์ถ”๊ฐ€)
282
- # -----------------------------
 
283
  examples = [
284
  "Ghibli style futuristic stormtrooper with glossy white armor and a sleek helmet, standing heroically on a lush alien planet, vibrant flowers blooming around, soft sunlight illuminating the scene, a gentle breeze rustling the leaves. The armor reflects the pink and purple hues of the alien sunset, creating an ethereal glow around the figure. [trigger]",
285
  "Ghibli style young mechanic girl in a floating workshop, surrounded by hovering tools and glowing mechanical parts, her blue overalls covered in oil stains, tinkering with a semi-transparent robot companion. Magical sparks fly as she works, while floating islands with waterfalls drift past her open workshop window. [trigger]",
@@ -506,7 +547,7 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
506
  placeholder="Describe your Ghibli-style image here...",
507
  lines=3
508
  )
509
- # โ˜… ์ƒˆ๋กญ๊ฒŒ ์ถ”๊ฐ€๋œ Text Rendering ์ž…๋ ฅ๋ž€
510
  text_rendering = gr.Textbox(
511
  label="Text Rendering (Multilingual: English, Korean...)",
512
  placeholder="Man saying '์•ˆ๋…•' in 'speech bubble'",
@@ -568,15 +609,14 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
568
 
569
  with gr.Group(elem_classes="container"):
570
  gr.Markdown("### โœจ Example Prompts")
571
- examples_html = '\n'.join([f'<div class="example-prompt">{example}</div>' for example in examples])
572
  example_container = gr.HTML(examples_html)
573
 
574
  with gr.Column(scale=4):
575
  with gr.Group(elem_classes="container"):
576
- with gr.Group():
577
- generation_status = gr.HTML('<div class="status-complete">Ready to generate</div>')
578
- result = gr.Image(label="Generated Image", elem_id="result-image")
579
- seed_text = gr.Number(label="Used Seed", value=42)
580
 
581
  with gr.Tabs(elem_classes="tabs") as tabs:
582
  with gr.TabItem("Gallery"):
@@ -592,6 +632,9 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
592
  elem_classes="gallery-item"
593
  )
594
 
 
 
 
595
  def refresh_gallery():
596
  return load_generated_images()
597
 
@@ -601,9 +644,12 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
601
  def before_generate():
602
  return '<div class="status-generating">Generating image...</div>'
603
 
604
- def after_generate(image, seed, gallery):
605
- return image, seed, gallery, '<div class="status-complete">Generation complete!</div>'
606
 
 
 
 
607
  refresh_btn.click(
608
  fn=refresh_gallery,
609
  inputs=None,
@@ -616,7 +662,10 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
616
  outputs=[prompt, result, seed_text, generation_status]
617
  )
618
 
619
- # ์ฒด์ธ์— ๋งˆ์ง€๋ง‰์— ํ…์ŠคํŠธ ๋ Œ๋”๋ง ์ ์šฉ (text_rendering ์ž…๋ ฅ๊ฐ’์ด ์žˆ์œผ๋ฉด)
 
 
 
620
  run_button.click(
621
  fn=before_generate,
622
  inputs=None,
@@ -641,9 +690,10 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
641
  ).then(
642
  fn=apply_text_rendering,
643
  inputs=[result, text_rendering],
644
- outputs=result,
645
  )
646
 
 
647
  prompt.submit(
648
  fn=before_generate,
649
  inputs=None,
@@ -668,9 +718,10 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
668
  ).then(
669
  fn=apply_text_rendering,
670
  inputs=[result, text_rendering],
671
- outputs=result,
672
  )
673
 
 
674
  gr.HTML("""
675
  <script>
676
  document.addEventListener('DOMContentLoaded', function() {
@@ -689,10 +740,13 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
689
  </script>
690
  """)
691
 
 
 
 
692
  try:
693
  demo.queue(concurrency_count=1, max_size=20)
694
  demo.launch(debug=True, show_api=False)
695
  except Exception as e:
696
- print(f"Error during launch: {e}")
697
- print("Trying alternative launch configuration...")
698
  demo.launch(debug=True, show_api=False, share=False)
 
9
  from diffusers import DiffusionPipeline
10
  from PIL import Image
11
 
 
 
 
12
  import re
13
  import tempfile
14
  import io
15
  import logging
 
 
 
 
 
 
 
 
16
 
17
+ # -----------------------------
18
+ # Google Gemini API ๊ด€๋ จ
19
+ # -----------------------------
20
+ import google.generativeai as genai
21
+ import google.generativeai.types as genai_types
22
 
23
  logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
24
 
25
+ ###############################################################################
26
+ # 1. ํ…์ŠคํŠธ(ํ•œ๊ธ€ โ†’ ์˜์–ด) ๋ณ€ํ™˜ ๋ณด์กฐ ํ•จ์ˆ˜
27
+ ###############################################################################
28
+
29
  def maybe_translate_to_english(text: str) -> str:
30
  """
31
+ ํ…์ŠคํŠธ์— ํ•œ๊ตญ์–ด๊ฐ€ ์žˆ์œผ๋ฉด ๊ฐ„๋‹จํ•œ ์น˜ํ™˜ ๊ทœ์น™์— ๋”ฐ๋ผ ์˜์–ด๋กœ ๋ณ€ํ™˜.
32
  """
33
+ translations = {
34
+ "์•ˆ๋…•ํ•˜์„ธ์š”": "Hello",
35
+ "ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค": "Welcome",
36
+ "์•ˆ๋…•": "Hello",
37
+ "๋ฐฐ๋„ˆ": "Banner",
38
+ # ํ•„์š”์— ๋”ฐ๋ผ ์ถ”๊ฐ€
39
+ }
40
+ for kr, en in translations.items():
41
+ if kr in text:
42
+ text = text.replace(kr, en)
43
+ return text
44
+
45
+ ###############################################################################
46
+ # 2. Gemini API ํ˜ธ์ถœ์„ ์œ„ํ•œ ์ค€๋น„
47
+ ###############################################################################
 
 
 
 
 
 
 
48
 
49
  def save_binary_file(file_name, data):
50
+ """ ์ด์ง„ ํŒŒ์ผ์„ ์ €์žฅํ•˜๋Š” ํ—ฌํผ ํ•จ์ˆ˜ """
51
  with open(file_name, "wb") as f:
52
  f.write(data)
53
 
54
  def generate_by_google_genai(text, file_name, model="gemini-2.0-flash-exp"):
55
  """
56
+ Google Gemini API๋ฅผ ํ˜ธ์ถœํ•ด ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ์ด๋ฏธ์ง€ ํŽธ์ง‘/์ƒ์„ฑ์„ ์ˆ˜ํ–‰.
57
+ file_name: ์›๋ณธ ์ด๋ฏธ์ง€๋ฅผ ์ž„์‹œ ์—…๋กœ๋“œํ•˜์—ฌ API๋กœ ์ „๋‹ฌ
58
+ text: ์ ์šฉํ•  ํ…์ŠคํŠธ ์ง€์‹œ์‚ฌํ•ญ
59
  """
60
+ api_key = os.getenv("GAPI_TOKEN")
61
  if not api_key:
62
  raise ValueError("GAPI_TOKEN is missing. Please set an API key.")
63
+
64
+ # Gemini API ์ธ์ฆ ์„ค์ •
65
+ genai.configure(api_key=api_key)
66
+
67
+ # ์ด๋ฏธ์ง€ ํŒŒ์ผ ์—…๋กœ๋“œ
68
+ uploaded_file = genai.upload_file(path=file_name)
69
+
70
+ # API์— ์ „๋‹ฌํ•  content ๊ตฌ์„ฑ
71
  contents = [
72
+ genai_types.Content(
73
  role="user",
74
  parts=[
75
+ # ๋จผ์ € ์—…๋กœ๋“œ๋œ ํŒŒ์ผ URI๋ฅผ ํฌํ•จ
76
+ genai_types.Part.from_uri(
77
+ file_uri=uploaded_file.uri,
78
+ mime_type=uploaded_file.mime_type,
79
  ),
80
+ # ์ด์–ด์„œ text ์ง€์‹œ์‚ฌํ•ญ์„ ํฌํ•จ
81
+ genai_types.Part.from_text(text=text),
82
  ],
83
  ),
84
  ]
85
+
86
+ # ์ƒ์„ฑ(ํŽธ์ง‘) ์„ค์ •
87
+ generation_config = genai_types.GenerationConfig(
88
  temperature=1,
89
  top_p=0.95,
90
  top_k=40,
91
+ max_output_tokens=8192, # ์ถœ๋ ฅ ํ† ํฐ ์ œํ•œ
 
92
  response_mime_type="text/plain",
93
  )
94
+
95
+ text_response = "" # API๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ํ…์ŠคํŠธ ๋ˆ„์ 
96
+ image_path = None # API๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ์ด๋ฏธ์ง€ ํŒŒ์ผ์˜ ๋กœ์ปฌ ๊ฒฝ๋กœ
97
+
98
+ # ์ž„์‹œ ํŒŒ์ผ์— ํŽธ์ง‘๋œ ์ด๋ฏธ์ง€ ์ €์žฅ
99
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
100
  temp_path = tmp.name
101
+
102
+ # ์ŠคํŠธ๋ฆฌ๋ฐ ํ˜•ํƒœ๋กœ ์‘๋‹ต์„ ๋ฐ›์Œ
103
+ response = genai.generate_content(
104
  model=model,
105
  contents=contents,
106
+ generation_config=generation_config,
107
+ stream=True
108
+ )
109
+
110
+ # ์ŠคํŠธ๋ฆฌ๋ฐ๋œ chunk๋“ค์—์„œ ์ด๋ฏธ์ง€์™€ ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœ
111
+ for chunk in response:
112
+ for candidate in chunk.candidates:
113
+ for part in candidate.content.parts:
114
+ # ์ด๋ฏธ์ง€์ธ ๊ฒฝ์šฐ
115
+ if hasattr(part, 'inline_data') and part.inline_data:
116
+ save_binary_file(temp_path, part.inline_data.data)
117
+ image_path = temp_path
118
+ break
119
+ # ํ…์ŠคํŠธ์ธ ๊ฒฝ์šฐ
120
+ elif hasattr(part, 'text'):
121
+ text_response += part.text + "\n"
122
+
123
+ if image_path:
124
+ break
125
+ if image_path:
126
  break
127
+
128
+ # ์—…๋กœ๋“œ๋œ ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ
129
+ genai.delete_file(uploaded_file.name)
130
+
131
  return image_path, text_response
132
 
133
+ ###############################################################################
134
+ # 3. ์ด๋ฏธ์ง€์— ํ…์ŠคํŠธ๋ฅผ ์‚ฝ์ž…/์ˆ˜์ •ํ•˜๋Š” ํ•จ์ˆ˜ (Gemini API 2ํšŒ ํ˜ธ์ถœ)
135
+ ###############################################################################
136
+
137
  def change_text_in_image_two_times(original_image, instruction):
138
+ """
139
+ Gemini API๋ฅผ ๋‘ ๋ฒˆ ํ˜ธ์ถœํ•˜์—ฌ ๋‘ ๊ฐœ์˜ ๋ฒ„์ „์„ ์ƒ์„ฑํ•œ๋‹ค.
140
+ """
141
+ import numpy as np
142
+
143
+ # ๋งŒ์•ฝ ์ด๋ฏธ์ง€๊ฐ€ numpy.ndarray ํƒ€์ž…์ด๋ฉด PIL๋กœ ๋ณ€ํ™˜
144
  if isinstance(original_image, np.ndarray):
145
  original_image = Image.fromarray(original_image)
146
 
 
152
  original_path = tmp.name
153
  if isinstance(original_image, Image.Image):
154
  original_image.save(original_path, format="PNG")
155
+ logging.debug(f"[DEBUG] Saved image to temporary file: {original_path}")
156
  else:
157
  raise gr.Error(f"์˜ˆ์ƒ๋œ PIL Image๊ฐ€ ์•„๋‹Œ {type(original_image)} ํƒ€์ž…์ด ์ œ๊ณต๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
158
+ # Gemini API ํ˜ธ์ถœ
159
  image_path, text_response = generate_by_google_genai(
160
  text=mod_instruction,
161
  file_name=original_path
162
  )
163
  if image_path:
164
+ # ๋ฐ˜ํ™˜๋œ ์ด๋ฏธ์ง€ ๋กœ๋“œ
165
  try:
166
  with open(image_path, "rb") as f:
167
  image_data = f.read()
168
  new_img = Image.open(io.BytesIO(image_data))
169
  results.append(new_img)
170
  except Exception as img_err:
171
+ logging.error(f"[ERROR] Failed to process Gemini image: {img_err}")
172
  results.append(original_image)
173
  else:
174
+ logging.warning(f"[WARNING] ์ด๋ฏธ์ง€๊ฐ€ ๋ฐ˜ํ™˜๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ…์ŠคํŠธ ์‘๋‹ต: {text_response}")
175
  results.append(original_image)
176
  except Exception as e:
177
  logging.exception(f"Text modification error: {e}")
178
  results.append(original_image)
179
  return results
180
 
181
+ ###############################################################################
182
+ # 4. ํ…์ŠคํŠธ ๋ Œ๋”๋ง(๋ฌธ์ž ์‚ฝ์ž…)์šฉ ํ•จ์ˆ˜
183
+ ###############################################################################
184
 
185
  def gemini_text_rendering(image, rendering_text):
186
  """
187
+ ์ฃผ์–ด์ง„ image์— ๋Œ€ํ•ด Gemini API๋กœ text_rendering์„ ์ ์šฉ
188
  """
189
  rendering_text_en = maybe_translate_to_english(rendering_text)
190
+ instruction = (
191
+ f"Render the following text on the image in a clear, visually appealing manner: "
192
+ f"{rendering_text_en}."
193
+ )
194
+ # ์ด๋ฏธ์ง€์— ํ…์ŠคํŠธ ์‚ฝ์ž…(A/B ๋ฒ„์ „ 2ํšŒ ์ƒ์„ฑ) โ†’ ์—ฌ๊ธฐ์„œ๋Š” 2ํšŒ ์ค‘ ์ฒซ ๋ฒˆ์งธ๋งŒ ๋ฐ˜ํ™˜
195
  rendered_images = change_text_in_image_two_times(image, instruction)
196
  if rendered_images and len(rendered_images) > 0:
197
  return rendered_images[0]
 
199
 
200
  def apply_text_rendering(image, rendering_text):
201
  """
202
+ rendering_text๊ฐ€ ์กด์žฌํ•˜๋ฉด Gemini API๋กœ ํ…์ŠคํŠธ ์‚ฝ์ž…์„ ์ ์šฉ.
203
+ ์—†์œผ๋ฉด ์›๋ณธ ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜.
204
  """
205
  if rendering_text and rendering_text.strip():
206
  return gemini_text_rendering(image, rendering_text)
207
  return image
208
 
209
+ ###############################################################################
210
+ # 5. Diffusion Pipeline ๋กœ๋“œ ๋ฐ ๊ธฐ๋ณธ ์„ธํŒ…
211
+ ###############################################################################
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
  SAVE_DIR = "saved_images"
214
  if not os.path.exists(SAVE_DIR):
 
219
  adapter_id = "openfree/flux-chatgpt-ghibli-lora"
220
 
221
  def load_model_with_retry(max_retries=5):
222
+ """
223
+ ๋กœ์ปฌ ๋˜๋Š” Hugging Face๋กœ๋ถ€ํ„ฐ ๋ชจ๋ธ(FLUX.1-dev) + LoRA ์–ด๋Œ‘ํ„ฐ(weights)๋ฅผ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
224
+ """
225
  for attempt in range(max_retries):
226
  try:
227
+ logging.info(f"Loading model attempt {attempt+1}/{max_retries}...")
228
  pipeline = DiffusionPipeline.from_pretrained(
229
+ repo_id,
230
  torch_dtype=torch.bfloat16,
231
  use_safetensors=True,
232
  resume_download=True
233
  )
234
+ logging.info("Model loaded successfully, loading LoRA weights...")
235
  pipeline.load_lora_weights(adapter_id)
236
  pipeline = pipeline.to(device)
237
+ logging.info("Pipeline ready!")
238
  return pipeline
239
  except Exception as e:
240
  if attempt < max_retries - 1:
241
  wait_time = 10 * (attempt + 1)
242
+ logging.error(f"Error loading model: {e}. Retrying in {wait_time} seconds...")
243
  import time
244
  time.sleep(wait_time)
245
  else:
 
251
  MAX_IMAGE_SIZE = 1024
252
 
253
  def save_generated_image(image, prompt):
254
+ """
255
+ ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๋ฉด์„œ ๋ฉ”ํƒ€ ์ •๋ณด๋ฅผ ๊ธฐ๋กํ•œ๋‹ค.
256
+ """
257
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
258
  unique_id = str(uuid.uuid4())[:8]
259
  filename = f"{timestamp}_{unique_id}.png"
260
  filepath = os.path.join(SAVE_DIR, filename)
261
  image.save(filepath)
262
+
263
  metadata_file = os.path.join(SAVE_DIR, "metadata.txt")
264
  with open(metadata_file, "a", encoding="utf-8") as f:
265
  f.write(f"{filename}|{prompt}|{timestamp}\n")
266
  return filepath
267
 
268
  def load_generated_images():
269
+ """
270
+ ์ €์žฅ๋œ ์ด๋ฏธ์ง€๋ฅผ ์ตœ์‹ ์ˆœ์œผ๋กœ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
271
+ """
272
  if not os.path.exists(SAVE_DIR):
273
  return []
274
+ image_files = [
275
+ os.path.join(SAVE_DIR, f)
276
+ for f in os.listdir(SAVE_DIR)
277
+ if f.endswith(('.png', '.jpg', '.jpeg', '.webp'))
278
+ ]
279
  image_files.sort(key=lambda x: os.path.getctime(x), reverse=True)
280
  return image_files
281
 
 
291
  lora_scale: float,
292
  progress: gr.Progress = gr.Progress(track_tqdm=True),
293
  ):
294
+ """
295
+ Diffusion Pipeline์„ ์‚ฌ์šฉํ•ด ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑ. (LoRA ์Šค์ผ€์ผ, Steps ๋“ฑ ์„ค์ • ๊ฐ€๋Šฅ)
296
+ """
297
  if randomize_seed:
298
  seed = random.randint(0, MAX_SEED)
299
  generator = torch.Generator(device=device).manual_seed(seed)
300
+
301
  try:
302
  image = pipeline(
303
  prompt=prompt,
 
308
  generator=generator,
309
  joint_attention_kwargs={"scale": lora_scale},
310
  ).images[0]
311
+
312
  filepath = save_generated_image(image, prompt)
313
  return image, seed, load_generated_images()
314
+
315
  except Exception as e:
316
+ logging.error(f"Error during inference: {e}")
317
  error_img = Image.new('RGB', (width, height), color='red')
318
  return error_img, seed, load_generated_images()
319
 
320
+ ###############################################################################
321
+ # 6. Gradio UI
322
+ ###############################################################################
323
+
324
  examples = [
325
  "Ghibli style futuristic stormtrooper with glossy white armor and a sleek helmet, standing heroically on a lush alien planet, vibrant flowers blooming around, soft sunlight illuminating the scene, a gentle breeze rustling the leaves. The armor reflects the pink and purple hues of the alien sunset, creating an ethereal glow around the figure. [trigger]",
326
  "Ghibli style young mechanic girl in a floating workshop, surrounded by hovering tools and glowing mechanical parts, her blue overalls covered in oil stains, tinkering with a semi-transparent robot companion. Magical sparks fly as she works, while floating islands with waterfalls drift past her open workshop window. [trigger]",
 
547
  placeholder="Describe your Ghibli-style image here...",
548
  lines=3
549
  )
550
+ # Text Rendering ์ž…๋ ฅ๋ž€
551
  text_rendering = gr.Textbox(
552
  label="Text Rendering (Multilingual: English, Korean...)",
553
  placeholder="Man saying '์•ˆ๋…•' in 'speech bubble'",
 
609
 
610
  with gr.Group(elem_classes="container"):
611
  gr.Markdown("### โœจ Example Prompts")
612
+ examples_html = '\n'.join([f'<div class="example-prompt">{ex}</div>' for ex in examples])
613
  example_container = gr.HTML(examples_html)
614
 
615
  with gr.Column(scale=4):
616
  with gr.Group(elem_classes="container"):
617
+ generation_status = gr.HTML('<div class="status-complete">Ready to generate</div>')
618
+ result = gr.Image(label="Generated Image", elem_id="result-image")
619
+ seed_text = gr.Number(label="Used Seed", value=42)
 
620
 
621
  with gr.Tabs(elem_classes="tabs") as tabs:
622
  with gr.TabItem("Gallery"):
 
632
  elem_classes="gallery-item"
633
  )
634
 
635
+ ###########################################################################
636
+ # Gradio Helper Functions
637
+ ###########################################################################
638
  def refresh_gallery():
639
  return load_generated_images()
640
 
 
644
  def before_generate():
645
  return '<div class="status-generating">Generating image...</div>'
646
 
647
+ def after_generate(image, seed_num, gallery):
648
+ return image, seed_num, gallery, '<div class="status-complete">Generation complete!</div>'
649
 
650
+ ###########################################################################
651
+ # Gradio Event Wiring
652
+ ###########################################################################
653
  refresh_btn.click(
654
  fn=refresh_gallery,
655
  inputs=None,
 
662
  outputs=[prompt, result, seed_text, generation_status]
663
  )
664
 
665
+ # 1) ์ƒํƒœ ํ‘œ์‹œ
666
+ # 2) ์ด๋ฏธ์ง€ ์ƒ์„ฑ
667
+ # 3) ์ƒํƒœ ์—…๋ฐ์ดํŠธ
668
+ # 4) ํ…์ŠคํŠธ ๋ Œ๋”๋ง(์žˆ๋‹ค๋ฉด)
669
  run_button.click(
670
  fn=before_generate,
671
  inputs=None,
 
690
  ).then(
691
  fn=apply_text_rendering,
692
  inputs=[result, text_rendering],
693
+ outputs=result
694
  )
695
 
696
+ # prompt submit ์‹œ์—๋„ ๋™์ผํ•œ ์ฒด์ธ ์‹คํ–‰
697
  prompt.submit(
698
  fn=before_generate,
699
  inputs=None,
 
718
  ).then(
719
  fn=apply_text_rendering,
720
  inputs=[result, text_rendering],
721
+ outputs=result
722
  )
723
 
724
+ # JS๋กœ ์˜ˆ์‹œ prompt ํด๋ฆญ ์‹œ ์ž๋™ ์ฑ„์šฐ๊ธฐ
725
  gr.HTML("""
726
  <script>
727
  document.addEventListener('DOMContentLoaded', function() {
 
740
  </script>
741
  """)
742
 
743
+ ###############################################################################
744
+ # 7. ์‹คํ–‰
745
+ ###############################################################################
746
  try:
747
  demo.queue(concurrency_count=1, max_size=20)
748
  demo.launch(debug=True, show_api=False)
749
  except Exception as e:
750
+ logging.error(f"Error during launch: {e}")
751
+ logging.info("Trying alternative launch configuration...")
752
  demo.launch(debug=True, show_api=False, share=False)