ginipick commited on
Commit
da1235d
ยท
verified ยท
1 Parent(s): 9267408

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +182 -102
app.py CHANGED
@@ -9,41 +9,186 @@ import torch
9
  from diffusers import DiffusionPipeline
10
  from PIL import Image
11
 
12
- # Apply more comprehensive patches to Gradio's utility functions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  import gradio_client.utils
14
  import types
15
 
16
- # Patch 1: Fix the _json_schema_to_python_type function
17
  original_json_schema = gradio_client.utils._json_schema_to_python_type
18
-
19
  def patched_json_schema(schema, defs=None):
20
- # Handle boolean values directly
21
  if isinstance(schema, bool):
22
  return "bool"
23
-
24
- # Handle cases where 'additionalProperties' is a boolean
25
  try:
26
  if "additionalProperties" in schema and isinstance(schema["additionalProperties"], bool):
27
  schema["additionalProperties"] = {"type": "any"}
28
  except (TypeError, KeyError):
29
  pass
30
-
31
- # Call the original function
32
  try:
33
  return original_json_schema(schema, defs)
34
  except Exception as e:
35
- # Fallback to a safe value when the schema can't be parsed
36
  return "any"
37
-
38
- # Replace the original function with our patched version
39
  gradio_client.utils._json_schema_to_python_type = patched_json_schema
40
 
41
- # Create permanent storage directory
42
- SAVE_DIR = "saved_images" # Gradio will handle the persistence
43
  if not os.path.exists(SAVE_DIR):
44
  os.makedirs(SAVE_DIR, exist_ok=True)
45
 
46
- # Safe settings for model loading
47
  device = "cuda" if torch.cuda.is_available() else "cpu"
48
  repo_id = "black-forest-labs/FLUX.1-dev"
49
  adapter_id = "openfree/flux-chatgpt-ghibli-lora"
@@ -72,37 +217,27 @@ def load_model_with_retry(max_retries=5):
72
  else:
73
  raise Exception(f"Failed to load model after {max_retries} attempts: {e}")
74
 
75
- # Load the model
76
  pipeline = load_model_with_retry()
77
 
78
  MAX_SEED = np.iinfo(np.int32).max
79
  MAX_IMAGE_SIZE = 1024
80
 
81
  def save_generated_image(image, prompt):
82
- # Generate unique filename with timestamp
83
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
84
  unique_id = str(uuid.uuid4())[:8]
85
  filename = f"{timestamp}_{unique_id}.png"
86
  filepath = os.path.join(SAVE_DIR, filename)
87
-
88
- # Save the image
89
  image.save(filepath)
90
-
91
- # Save metadata
92
  metadata_file = os.path.join(SAVE_DIR, "metadata.txt")
93
  with open(metadata_file, "a", encoding="utf-8") as f:
94
  f.write(f"{filename}|{prompt}|{timestamp}\n")
95
-
96
  return filepath
97
 
98
  def load_generated_images():
99
  if not os.path.exists(SAVE_DIR):
100
  return []
101
-
102
- # Load all images from the directory
103
  image_files = [os.path.join(SAVE_DIR, f) for f in os.listdir(SAVE_DIR)
104
  if f.endswith(('.png', '.jpg', '.jpeg', '.webp'))]
105
- # Sort by creation time (newest first)
106
  image_files.sort(key=lambda x: os.path.getctime(x), reverse=True)
107
  return image_files
108
 
@@ -121,8 +256,6 @@ def inference(
121
  if randomize_seed:
122
  seed = random.randint(0, MAX_SEED)
123
  generator = torch.Generator(device=device).manual_seed(seed)
124
-
125
- # Error handling for the inference process
126
  try:
127
  image = pipeline(
128
  prompt=prompt,
@@ -133,33 +266,25 @@ def inference(
133
  generator=generator,
134
  joint_attention_kwargs={"scale": lora_scale},
135
  ).images[0]
136
-
137
- # Save the generated image
138
  filepath = save_generated_image(image, prompt)
139
-
140
- # Return the image, seed, and updated gallery
141
  return image, seed, load_generated_images()
142
  except Exception as e:
143
- # Log the error and return a simple error image
144
  print(f"Error during inference: {e}")
145
  error_img = Image.new('RGB', (width, height), color='red')
146
  return error_img, seed, load_generated_images()
147
 
 
 
 
148
  examples = [
149
  "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]",
150
-
151
  "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]",
152
-
153
  "Ghibli style ancient forest guardian robot, covered in moss and flowering vines, sitting peacefully in a crystal-clear lake. Its gentle eyes glow with soft blue light, while bioluminescent dragonflies dance around its weathered metal frame. Ancient tech symbols on its surface pulse with a gentle rhythm. [trigger]",
154
-
155
  "Ghibli style sky whale transport ship, its metallic skin adorned with traditional Japanese patterns, gliding through cotton candy clouds at sunrise. Small floating gardens hang from its sides, where workers in futuristic kimonos tend to glowing plants. Rainbow auroras shimmer in the background. [trigger]",
156
-
157
  "Ghibli style cyber-shrine maiden with flowing holographic robes, performing a ritual dance among floating lanterns and digital cherry blossoms. Her traditional headdress emits soft light patterns, while spirit-like AI constructs swirl around her in elegant patterns. The scene is set in a modern shrine with both ancient wood and sleek chrome elements. [trigger]",
158
-
159
  "Ghibli style robot farmer tending to floating rice paddies in the sky, wearing a traditional straw hat with advanced sensors. Its gentle movements create ripples in the water as it plants glowing rice seedlings. Flying fish leap between the terraced fields, leaving trails of sparkles in their wake, while future Tokyo's spires gleam in the distance. [trigger]"
160
  ]
161
 
162
- # Enhanced CSS for a more visually refined UI
163
  css = """
164
  :root {
165
  --primary-color: #6a92cc;
@@ -172,17 +297,14 @@ css = """
172
  --shadow: 0 4px 12px rgba(0,0,0,0.08);
173
  --font-main: 'Poppins', -apple-system, BlinkMacSystemFont, sans-serif;
174
  }
175
-
176
  body {
177
  background-color: var(--background-color);
178
  font-family: var(--font-main);
179
  }
180
-
181
  .gradio-container {
182
  margin: 0 auto;
183
  max-width: 1200px !important;
184
  }
185
-
186
  .main-header {
187
  text-align: center;
188
  padding: 2rem 1rem 1rem;
@@ -192,32 +314,27 @@ body {
192
  border-radius: var(--border-radius);
193
  box-shadow: var(--shadow);
194
  }
195
-
196
  .main-header h1 {
197
  font-size: 2.5rem;
198
  margin-bottom: 0.5rem;
199
  font-weight: 700;
200
  text-shadow: 0 2px 4px rgba(0,0,0,0.2);
201
  }
202
-
203
  .main-header p {
204
  font-size: 1rem;
205
  margin-bottom: 0.5rem;
206
  opacity: 0.9;
207
  }
208
-
209
  .main-header a {
210
  color: var(--secondary-color);
211
  text-decoration: none;
212
  font-weight: 600;
213
  transition: all 0.2s ease;
214
  }
215
-
216
  .main-header a:hover {
217
  text-decoration: underline;
218
  opacity: 0.9;
219
  }
220
-
221
  .container {
222
  background-color: var(--panel-background);
223
  padding: 1.5rem;
@@ -225,7 +342,6 @@ body {
225
  box-shadow: var(--shadow);
226
  margin-bottom: 1.5rem;
227
  }
228
-
229
  button.primary {
230
  background: var(--primary-color) !important;
231
  border: none !important;
@@ -236,13 +352,11 @@ button.primary {
236
  box-shadow: 0 2px 5px rgba(0,0,0,0.1) !important;
237
  transition: all 0.2s ease !important;
238
  }
239
-
240
  button.primary:hover {
241
  background: var(--primary-hover) !important;
242
  transform: translateY(-2px) !important;
243
  box-shadow: 0 4px 8px rgba(0,0,0,0.15) !important;
244
  }
245
-
246
  button.secondary {
247
  background: white !important;
248
  border: 1px solid #ddd !important;
@@ -253,64 +367,51 @@ button.secondary {
253
  box-shadow: 0 2px 5px rgba(0,0,0,0.05) !important;
254
  transition: all 0.2s ease !important;
255
  }
256
-
257
  button.secondary:hover {
258
  background: #f5f5f5 !important;
259
  transform: translateY(-2px) !important;
260
  }
261
-
262
  .gr-box {
263
  border-radius: var(--border-radius) !important;
264
  border: 1px solid #e0e0e0 !important;
265
  }
266
-
267
  .gr-panel {
268
  border-radius: var(--border-radius) !important;
269
  }
270
-
271
  .gr-input {
272
  border-radius: 8px !important;
273
  border: 1px solid #ddd !important;
274
  padding: 12px !important;
275
  }
276
-
277
  .gr-form {
278
  border-radius: var(--border-radius) !important;
279
  background-color: var(--panel-background) !important;
280
  }
281
-
282
  .gr-accordion {
283
  border-radius: var(--border-radius) !important;
284
  overflow: hidden !important;
285
  }
286
-
287
  .gr-button {
288
  border-radius: 8px !important;
289
  }
290
-
291
  .gallery-item {
292
  border-radius: var(--border-radius) !important;
293
  transition: all 0.3s ease !important;
294
  }
295
-
296
  .gallery-item:hover {
297
  transform: scale(1.02) !important;
298
  box-shadow: 0 6px 15px rgba(0,0,0,0.1) !important;
299
  }
300
-
301
  .tabs {
302
  border-radius: var(--border-radius) !important;
303
  overflow: hidden !important;
304
  }
305
-
306
  footer {
307
  display: none !important;
308
  }
309
-
310
  .settings-accordion legend span {
311
  font-weight: 600 !important;
312
  }
313
-
314
  .example-prompt {
315
  font-size: 0.9rem;
316
  color: #555;
@@ -322,12 +423,9 @@ footer {
322
  cursor: pointer;
323
  transition: all 0.2s;
324
  }
325
-
326
  .example-prompt:hover {
327
  background: #eef2f8;
328
  }
329
-
330
- /* Status indicators */
331
  .status-generating {
332
  color: #ffa200;
333
  font-weight: 500;
@@ -335,7 +433,6 @@ footer {
335
  align-items: center;
336
  gap: 8px;
337
  }
338
-
339
  .status-generating::before {
340
  content: "";
341
  display: inline-block;
@@ -345,7 +442,6 @@ footer {
345
  background-color: #ffa200;
346
  animation: pulse 1.5s infinite;
347
  }
348
-
349
  .status-complete {
350
  color: #00c853;
351
  font-weight: 500;
@@ -353,7 +449,6 @@ footer {
353
  align-items: center;
354
  gap: 8px;
355
  }
356
-
357
  .status-complete::before {
358
  content: "";
359
  display: inline-block;
@@ -362,42 +457,27 @@ footer {
362
  border-radius: 50%;
363
  background-color: #00c853;
364
  }
365
-
366
  @keyframes pulse {
367
- 0% {
368
- opacity: 0.6;
369
- }
370
- 50% {
371
- opacity: 1;
372
- }
373
- 100% {
374
- opacity: 0.6;
375
- }
376
  }
377
-
378
- /* Custom accordions and tabs styling */
379
  .gr-accordion-title {
380
  font-weight: 600 !important;
381
  color: var(--text-color) !important;
382
  }
383
-
384
  .tabs button {
385
  font-weight: 500 !important;
386
  padding: 10px 16px !important;
387
  }
388
-
389
  .tabs button.selected {
390
  font-weight: 600 !important;
391
  color: var(--primary-color) !important;
392
  background: rgba(106, 146, 204, 0.1) !important;
393
  }
394
-
395
- /* Improve slider appearance */
396
  .gr-slider-container {
397
  padding: 10px 0 !important;
398
  }
399
-
400
- /* Enhanced Markdown content */
401
  .gr-prose h3 {
402
  font-weight: 600 !important;
403
  color: var(--primary-color) !important;
@@ -405,10 +485,8 @@ footer {
405
  }
406
  """
407
 
408
- # Use a cleaner, more visual UI configuration
409
  with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
410
  with gr.Column():
411
- # Custom header with improved styling
412
  gr.HTML('''
413
  <div class="main-header">
414
  <h1>โœจ FLUX Ghibli LoRA Generator โœจ</h1>
@@ -424,6 +502,12 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
424
  placeholder="Describe your Ghibli-style image here...",
425
  lines=3
426
  )
 
 
 
 
 
 
427
 
428
  with gr.Row():
429
  run_button = gr.Button("โœจ Generate Image", elem_classes="primary")
@@ -439,7 +523,6 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
439
  value=42,
440
  )
441
  randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
442
-
443
  with gr.Row():
444
  width = gr.Slider(
445
  label="Width",
@@ -455,7 +538,6 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
455
  step=32,
456
  value=768,
457
  )
458
-
459
  with gr.Row():
460
  guidance_scale = gr.Slider(
461
  label="Guidance scale",
@@ -464,7 +546,6 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
464
  step=0.1,
465
  value=3.5,
466
  )
467
-
468
  with gr.Row():
469
  num_inference_steps = gr.Slider(
470
  label="Steps",
@@ -483,13 +564,11 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
483
 
484
  with gr.Group(elem_classes="container"):
485
  gr.Markdown("### โœจ Example Prompts")
486
- # Create HTML for examples manually
487
  examples_html = '\n'.join([f'<div class="example-prompt">{example}</div>' for example in examples])
488
  example_container = gr.HTML(examples_html)
489
 
490
  with gr.Column(scale=4):
491
  with gr.Group(elem_classes="container"):
492
- # Image result container with status indicator
493
  with gr.Group():
494
  generation_status = gr.HTML('<div class="status-complete">Ready to generate</div>')
495
  result = gr.Image(label="Generated Image", elem_id="result-image")
@@ -509,7 +588,6 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
509
  elem_classes="gallery-item"
510
  )
511
 
512
- # Event handlers
513
  def refresh_gallery():
514
  return load_generated_images()
515
 
@@ -534,7 +612,7 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
534
  outputs=[prompt, result, seed_text, generation_status]
535
  )
536
 
537
- # Update with status indicators for generation process
538
  run_button.click(
539
  fn=before_generate,
540
  inputs=None,
@@ -556,6 +634,10 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
556
  fn=after_generate,
557
  inputs=[result, seed_text, generated_gallery],
558
  outputs=[result, seed_text, generated_gallery, generation_status],
 
 
 
 
559
  )
560
 
561
  prompt.submit(
@@ -579,36 +661,34 @@ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
579
  fn=after_generate,
580
  inputs=[result, seed_text, generated_gallery],
581
  outputs=[result, seed_text, generated_gallery, generation_status],
 
 
 
 
582
  )
583
 
584
- # Custom JavaScript for handling example prompts
585
  gr.HTML("""
586
  <script>
587
  document.addEventListener('DOMContentLoaded', function() {
588
- // Add click handlers to example prompts
589
  setTimeout(() => {
590
  const examples = document.querySelectorAll('.example-prompt');
591
  const promptInput = document.querySelector('textarea');
592
-
593
  examples.forEach(example => {
594
  example.addEventListener('click', function() {
595
  promptInput.value = this.textContent.trim();
596
- // Trigger input event to update Gradio's state
597
  const event = new Event('input', { bubbles: true });
598
  promptInput.dispatchEvent(event);
599
  });
600
  });
601
- }, 1000); // Small delay to ensure elements are loaded
602
  });
603
  </script>
604
  """)
605
 
606
- # Launch with fallback options
607
  try:
608
  demo.queue(concurrency_count=1, max_size=20)
609
  demo.launch(debug=True, show_api=False)
610
  except Exception as e:
611
  print(f"Error during launch: {e}")
612
  print("Trying alternative launch configuration...")
613
- # Skip queue and simplify launch parameters
614
- demo.launch(debug=True, show_api=False, share=False)
 
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
+ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
26
+
27
+ def maybe_translate_to_english(text: str) -> str:
28
+ """
29
+ ํ…์ŠคํŠธ์— ํ•œ๊ธ€์ด ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉด ๊ฐ„๋‹จํ•œ ๊ทœ์น™์— ๋”ฐ๋ผ ์˜์–ด๋กœ ๋ณ€ํ™˜.
30
+ """
31
+ if not text or not re.search("[๊ฐ€-ํžฃ]", text):
32
+ return text
33
+ try:
34
+ translations = {
35
+ "์•ˆ๋…•ํ•˜์„ธ์š”": "Hello",
36
+ "ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค": "Welcome",
37
+ "์•„๋ฆ„๋‹ค์šด ๋‹น์‹ ": "Beautiful You",
38
+ "์•ˆ๋…•": "Hello",
39
+ "๊ณ ์–‘์ด": "Cat",
40
+ "๋ฐฐ๋„ˆ": "Banner",
41
+ "์ฌ๊ธ€๋ผ์Šค": "Sunglasses",
42
+ "์ฐฉ์šฉํ•œ": "wearing",
43
+ "ํฐ์ƒ‰": "white"
44
+ }
45
+ for kr, en in translations.items():
46
+ if kr in text:
47
+ text = text.replace(kr, en)
48
+ print(f"[TRANSLATE] Translated Korean text: '{text}'")
49
+ return text
50
+ except Exception as e:
51
+ print(f"[WARNING] Translation failed: {e}")
52
+ return text
53
+
54
+ def save_binary_file(file_name, data):
55
+ with open(file_name, "wb") as f:
56
+ f.write(data)
57
+
58
+ def generate_by_google_genai(text, file_name, model="gemini-2.0-flash-exp"):
59
+ """
60
+ Gemini API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ์ด๋ฏธ์ง€ ํŽธ์ง‘/์ƒ์„ฑ์„ ์ˆ˜ํ–‰.
61
+ """
62
+ api_key = os.getenv("GAPI_TOKEN", None)
63
+ if not api_key:
64
+ raise ValueError("GAPI_TOKEN is missing. Please set an API key.")
65
+ client = genai.Client(api_key=api_key)
66
+ files = [client.files.upload(file=file_name)]
67
+ contents = [
68
+ types.Content(
69
+ role="user",
70
+ parts=[
71
+ types.Part.from_uri(
72
+ file_uri=files[0].uri,
73
+ mime_type=files[0].mime_type,
74
+ ),
75
+ types.Part.from_text(text=text),
76
+ ],
77
+ ),
78
+ ]
79
+ generate_content_config = types.GenerateContentConfig(
80
+ temperature=1,
81
+ top_p=0.95,
82
+ top_k=40,
83
+ max_output_tokens=8192,
84
+ response_modalities=["image", "text"],
85
+ response_mime_type="text/plain",
86
+ )
87
+ text_response = ""
88
+ image_path = None
89
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
90
+ temp_path = tmp.name
91
+ for chunk in client.models.generate_content_stream(
92
+ model=model,
93
+ contents=contents,
94
+ config=generate_content_config,
95
+ ):
96
+ if not chunk.candidates or not chunk.candidates[0].content or not chunk.candidates[0].content.parts:
97
+ continue
98
+ candidate = chunk.candidates[0].content.parts[0]
99
+ if candidate.inline_data:
100
+ save_binary_file(temp_path, candidate.inline_data.data)
101
+ print(f"File of mime type {candidate.inline_data.mime_type} saved to: {temp_path}")
102
+ image_path = temp_path
103
+ break
104
+ else:
105
+ text_response += chunk.text + "\n"
106
+ del files
107
+ return image_path, text_response
108
+
109
+ def change_text_in_image_two_times(original_image, instruction):
110
+ """
111
+ Gemini API๋ฅผ ๋‘ ๋ฒˆ ํ˜ธ์ถœํ•˜์—ฌ ํ…์ŠคํŠธ ์ˆ˜์ •๋œ ์ด๋ฏธ์ง€ 2๊ฐœ(๋ฒ„์ „ A, B)๋ฅผ ๋ฐ˜ํ™˜.
112
+ """
113
+ if original_image is None:
114
+ raise gr.Error("์ฒ˜๋ฆฌํ•  ์ด๋ฏธ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋จผ์ € ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”.")
115
+ results = []
116
+ for version_tag in ["(A)", "(B)"]:
117
+ mod_instruction = f"{instruction} {version_tag}"
118
+ try:
119
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
120
+ original_path = tmp.name
121
+ if isinstance(original_image, Image.Image):
122
+ original_image.save(original_path, format="PNG")
123
+ print(f"[DEBUG] Saved image to temporary file: {original_path}")
124
+ else:
125
+ raise gr.Error(f"์˜ˆ์ƒ๋œ PIL Image๊ฐ€ ์•„๋‹Œ {type(original_image)} ํƒ€์ž…์ด ์ œ๊ณต๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
126
+ print(f"[DEBUG] Google Gemini API์— ๋ณด๋‚ด๋Š” ์ง€์‹œ์‚ฌํ•ญ: {mod_instruction}")
127
+ image_path, text_response = generate_by_google_genai(
128
+ text=mod_instruction,
129
+ file_name=original_path
130
+ )
131
+ if image_path:
132
+ try:
133
+ with open(image_path, "rb") as f:
134
+ image_data = f.read()
135
+ new_img = Image.open(io.BytesIO(image_data))
136
+ results.append(new_img)
137
+ except Exception as img_err:
138
+ print(f"[ERROR] Failed to process Gemini image: {img_err}")
139
+ results.append(original_image)
140
+ else:
141
+ print(f"[WARNING] ์ด๋ฏธ์ง€๊ฐ€ ๋ฐ˜ํ™˜๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ…์ŠคํŠธ ์‘๋‹ต: {text_response}")
142
+ results.append(original_image)
143
+ except Exception as e:
144
+ logging.exception(f"Text modification error: {e}")
145
+ results.append(original_image)
146
+ return results
147
+
148
+ def gemini_text_rendering(image, rendering_text):
149
+ """
150
+ ์ฃผ์–ด์ง„ ์ด๋ฏธ์ง€์— ๋Œ€ํ•ด Gemini API๋ฅผ ์‚ฌ์šฉํ•ด ํ…์ŠคํŠธ ๋ Œ๋”๋ง์„ ์ ์šฉ.
151
+ """
152
+ rendering_text_en = maybe_translate_to_english(rendering_text)
153
+ instruction = f"Render the following text on the image in a clear, visually appealing manner: {rendering_text_en}."
154
+ rendered_images = change_text_in_image_two_times(image, instruction)
155
+ if rendered_images and len(rendered_images) > 0:
156
+ return rendered_images[0]
157
+ return image
158
+
159
+ def apply_text_rendering(image, rendering_text):
160
+ """
161
+ ์ž…๋ ฅ๋œ ํ…์ŠคํŠธ๊ฐ€ ์žˆ์œผ๋ฉด Gemini API๋ฅผ ํ†ตํ•ด ์ด๋ฏธ์ง€์— ํ…์ŠคํŠธ ๋ Œ๋”๋ง์„ ์ ์šฉํ•˜๊ณ , ์—†์œผ๋ฉด ์›๋ณธ ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜.
162
+ """
163
+ if rendering_text and rendering_text.strip():
164
+ return gemini_text_rendering(image, rendering_text)
165
+ return image
166
+
167
+ # -----------------------------
168
+ # ๊ธฐ์กด Diffusion Pipeline ๊ด€๋ จ ์ฝ”๋“œ
169
+ # -----------------------------
170
  import gradio_client.utils
171
  import types
172
 
 
173
  original_json_schema = gradio_client.utils._json_schema_to_python_type
 
174
  def patched_json_schema(schema, defs=None):
 
175
  if isinstance(schema, bool):
176
  return "bool"
 
 
177
  try:
178
  if "additionalProperties" in schema and isinstance(schema["additionalProperties"], bool):
179
  schema["additionalProperties"] = {"type": "any"}
180
  except (TypeError, KeyError):
181
  pass
 
 
182
  try:
183
  return original_json_schema(schema, defs)
184
  except Exception as e:
 
185
  return "any"
 
 
186
  gradio_client.utils._json_schema_to_python_type = patched_json_schema
187
 
188
+ SAVE_DIR = "saved_images"
 
189
  if not os.path.exists(SAVE_DIR):
190
  os.makedirs(SAVE_DIR, exist_ok=True)
191
 
 
192
  device = "cuda" if torch.cuda.is_available() else "cpu"
193
  repo_id = "black-forest-labs/FLUX.1-dev"
194
  adapter_id = "openfree/flux-chatgpt-ghibli-lora"
 
217
  else:
218
  raise Exception(f"Failed to load model after {max_retries} attempts: {e}")
219
 
 
220
  pipeline = load_model_with_retry()
221
 
222
  MAX_SEED = np.iinfo(np.int32).max
223
  MAX_IMAGE_SIZE = 1024
224
 
225
  def save_generated_image(image, prompt):
 
226
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
227
  unique_id = str(uuid.uuid4())[:8]
228
  filename = f"{timestamp}_{unique_id}.png"
229
  filepath = os.path.join(SAVE_DIR, filename)
 
 
230
  image.save(filepath)
 
 
231
  metadata_file = os.path.join(SAVE_DIR, "metadata.txt")
232
  with open(metadata_file, "a", encoding="utf-8") as f:
233
  f.write(f"{filename}|{prompt}|{timestamp}\n")
 
234
  return filepath
235
 
236
  def load_generated_images():
237
  if not os.path.exists(SAVE_DIR):
238
  return []
 
 
239
  image_files = [os.path.join(SAVE_DIR, f) for f in os.listdir(SAVE_DIR)
240
  if f.endswith(('.png', '.jpg', '.jpeg', '.webp'))]
 
241
  image_files.sort(key=lambda x: os.path.getctime(x), reverse=True)
242
  return image_files
243
 
 
256
  if randomize_seed:
257
  seed = random.randint(0, MAX_SEED)
258
  generator = torch.Generator(device=device).manual_seed(seed)
 
 
259
  try:
260
  image = pipeline(
261
  prompt=prompt,
 
266
  generator=generator,
267
  joint_attention_kwargs={"scale": lora_scale},
268
  ).images[0]
 
 
269
  filepath = save_generated_image(image, prompt)
 
 
270
  return image, seed, load_generated_images()
271
  except Exception as e:
 
272
  print(f"Error during inference: {e}")
273
  error_img = Image.new('RGB', (width, height), color='red')
274
  return error_img, seed, load_generated_images()
275
 
276
+ # -----------------------------
277
+ # Gradio UI (์ž…๋ ฅ ํ”„๋กฌํ”„ํŠธ ์•„๋ž˜์— Text Rendering ์ž…๋ ฅ๋ž€ ์ถ”๊ฐ€)
278
+ # -----------------------------
279
  examples = [
280
  "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]",
 
281
  "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]",
 
282
  "Ghibli style ancient forest guardian robot, covered in moss and flowering vines, sitting peacefully in a crystal-clear lake. Its gentle eyes glow with soft blue light, while bioluminescent dragonflies dance around its weathered metal frame. Ancient tech symbols on its surface pulse with a gentle rhythm. [trigger]",
 
283
  "Ghibli style sky whale transport ship, its metallic skin adorned with traditional Japanese patterns, gliding through cotton candy clouds at sunrise. Small floating gardens hang from its sides, where workers in futuristic kimonos tend to glowing plants. Rainbow auroras shimmer in the background. [trigger]",
 
284
  "Ghibli style cyber-shrine maiden with flowing holographic robes, performing a ritual dance among floating lanterns and digital cherry blossoms. Her traditional headdress emits soft light patterns, while spirit-like AI constructs swirl around her in elegant patterns. The scene is set in a modern shrine with both ancient wood and sleek chrome elements. [trigger]",
 
285
  "Ghibli style robot farmer tending to floating rice paddies in the sky, wearing a traditional straw hat with advanced sensors. Its gentle movements create ripples in the water as it plants glowing rice seedlings. Flying fish leap between the terraced fields, leaving trails of sparkles in their wake, while future Tokyo's spires gleam in the distance. [trigger]"
286
  ]
287
 
 
288
  css = """
289
  :root {
290
  --primary-color: #6a92cc;
 
297
  --shadow: 0 4px 12px rgba(0,0,0,0.08);
298
  --font-main: 'Poppins', -apple-system, BlinkMacSystemFont, sans-serif;
299
  }
 
300
  body {
301
  background-color: var(--background-color);
302
  font-family: var(--font-main);
303
  }
 
304
  .gradio-container {
305
  margin: 0 auto;
306
  max-width: 1200px !important;
307
  }
 
308
  .main-header {
309
  text-align: center;
310
  padding: 2rem 1rem 1rem;
 
314
  border-radius: var(--border-radius);
315
  box-shadow: var(--shadow);
316
  }
 
317
  .main-header h1 {
318
  font-size: 2.5rem;
319
  margin-bottom: 0.5rem;
320
  font-weight: 700;
321
  text-shadow: 0 2px 4px rgba(0,0,0,0.2);
322
  }
 
323
  .main-header p {
324
  font-size: 1rem;
325
  margin-bottom: 0.5rem;
326
  opacity: 0.9;
327
  }
 
328
  .main-header a {
329
  color: var(--secondary-color);
330
  text-decoration: none;
331
  font-weight: 600;
332
  transition: all 0.2s ease;
333
  }
 
334
  .main-header a:hover {
335
  text-decoration: underline;
336
  opacity: 0.9;
337
  }
 
338
  .container {
339
  background-color: var(--panel-background);
340
  padding: 1.5rem;
 
342
  box-shadow: var(--shadow);
343
  margin-bottom: 1.5rem;
344
  }
 
345
  button.primary {
346
  background: var(--primary-color) !important;
347
  border: none !important;
 
352
  box-shadow: 0 2px 5px rgba(0,0,0,0.1) !important;
353
  transition: all 0.2s ease !important;
354
  }
 
355
  button.primary:hover {
356
  background: var(--primary-hover) !important;
357
  transform: translateY(-2px) !important;
358
  box-shadow: 0 4px 8px rgba(0,0,0,0.15) !important;
359
  }
 
360
  button.secondary {
361
  background: white !important;
362
  border: 1px solid #ddd !important;
 
367
  box-shadow: 0 2px 5px rgba(0,0,0,0.05) !important;
368
  transition: all 0.2s ease !important;
369
  }
 
370
  button.secondary:hover {
371
  background: #f5f5f5 !important;
372
  transform: translateY(-2px) !important;
373
  }
 
374
  .gr-box {
375
  border-radius: var(--border-radius) !important;
376
  border: 1px solid #e0e0e0 !important;
377
  }
 
378
  .gr-panel {
379
  border-radius: var(--border-radius) !important;
380
  }
 
381
  .gr-input {
382
  border-radius: 8px !important;
383
  border: 1px solid #ddd !important;
384
  padding: 12px !important;
385
  }
 
386
  .gr-form {
387
  border-radius: var(--border-radius) !important;
388
  background-color: var(--panel-background) !important;
389
  }
 
390
  .gr-accordion {
391
  border-radius: var(--border-radius) !important;
392
  overflow: hidden !important;
393
  }
 
394
  .gr-button {
395
  border-radius: 8px !important;
396
  }
 
397
  .gallery-item {
398
  border-radius: var(--border-radius) !important;
399
  transition: all 0.3s ease !important;
400
  }
 
401
  .gallery-item:hover {
402
  transform: scale(1.02) !important;
403
  box-shadow: 0 6px 15px rgba(0,0,0,0.1) !important;
404
  }
 
405
  .tabs {
406
  border-radius: var(--border-radius) !important;
407
  overflow: hidden !important;
408
  }
 
409
  footer {
410
  display: none !important;
411
  }
 
412
  .settings-accordion legend span {
413
  font-weight: 600 !important;
414
  }
 
415
  .example-prompt {
416
  font-size: 0.9rem;
417
  color: #555;
 
423
  cursor: pointer;
424
  transition: all 0.2s;
425
  }
 
426
  .example-prompt:hover {
427
  background: #eef2f8;
428
  }
 
 
429
  .status-generating {
430
  color: #ffa200;
431
  font-weight: 500;
 
433
  align-items: center;
434
  gap: 8px;
435
  }
 
436
  .status-generating::before {
437
  content: "";
438
  display: inline-block;
 
442
  background-color: #ffa200;
443
  animation: pulse 1.5s infinite;
444
  }
 
445
  .status-complete {
446
  color: #00c853;
447
  font-weight: 500;
 
449
  align-items: center;
450
  gap: 8px;
451
  }
 
452
  .status-complete::before {
453
  content: "";
454
  display: inline-block;
 
457
  border-radius: 50%;
458
  background-color: #00c853;
459
  }
 
460
  @keyframes pulse {
461
+ 0% { opacity: 0.6; }
462
+ 50% { opacity: 1; }
463
+ 100% { opacity: 0.6; }
 
 
 
 
 
 
464
  }
 
 
465
  .gr-accordion-title {
466
  font-weight: 600 !important;
467
  color: var(--text-color) !important;
468
  }
 
469
  .tabs button {
470
  font-weight: 500 !important;
471
  padding: 10px 16px !important;
472
  }
 
473
  .tabs button.selected {
474
  font-weight: 600 !important;
475
  color: var(--primary-color) !important;
476
  background: rgba(106, 146, 204, 0.1) !important;
477
  }
 
 
478
  .gr-slider-container {
479
  padding: 10px 0 !important;
480
  }
 
 
481
  .gr-prose h3 {
482
  font-weight: 600 !important;
483
  color: var(--primary-color) !important;
 
485
  }
486
  """
487
 
 
488
  with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo:
489
  with gr.Column():
 
490
  gr.HTML('''
491
  <div class="main-header">
492
  <h1>โœจ FLUX Ghibli LoRA Generator โœจ</h1>
 
502
  placeholder="Describe your Ghibli-style image here...",
503
  lines=3
504
  )
505
+ # โ˜… ์ƒˆ๋กญ๊ฒŒ ์ถ”๊ฐ€๋œ Text Rendering ์ž…๋ ฅ๋ž€
506
+ text_rendering = gr.Textbox(
507
+ label="Text Rendering (Multilingual: English, Korean...)",
508
+ placeholder="Man saying '์•ˆ๋…•' in 'speech bubble'",
509
+ lines=1
510
+ )
511
 
512
  with gr.Row():
513
  run_button = gr.Button("โœจ Generate Image", elem_classes="primary")
 
523
  value=42,
524
  )
525
  randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
 
526
  with gr.Row():
527
  width = gr.Slider(
528
  label="Width",
 
538
  step=32,
539
  value=768,
540
  )
 
541
  with gr.Row():
542
  guidance_scale = gr.Slider(
543
  label="Guidance scale",
 
546
  step=0.1,
547
  value=3.5,
548
  )
 
549
  with gr.Row():
550
  num_inference_steps = gr.Slider(
551
  label="Steps",
 
564
 
565
  with gr.Group(elem_classes="container"):
566
  gr.Markdown("### โœจ Example Prompts")
 
567
  examples_html = '\n'.join([f'<div class="example-prompt">{example}</div>' for example in examples])
568
  example_container = gr.HTML(examples_html)
569
 
570
  with gr.Column(scale=4):
571
  with gr.Group(elem_classes="container"):
 
572
  with gr.Group():
573
  generation_status = gr.HTML('<div class="status-complete">Ready to generate</div>')
574
  result = gr.Image(label="Generated Image", elem_id="result-image")
 
588
  elem_classes="gallery-item"
589
  )
590
 
 
591
  def refresh_gallery():
592
  return load_generated_images()
593
 
 
612
  outputs=[prompt, result, seed_text, generation_status]
613
  )
614
 
615
+ # ์ฒด์ธ์— ๋งˆ์ง€๋ง‰์— ํ…์ŠคํŠธ ๋ Œ๋”๋ง ์ ์šฉ (text_rendering ์ž…๋ ฅ๊ฐ’์ด ์žˆ์œผ๋ฉด)
616
  run_button.click(
617
  fn=before_generate,
618
  inputs=None,
 
634
  fn=after_generate,
635
  inputs=[result, seed_text, generated_gallery],
636
  outputs=[result, seed_text, generated_gallery, generation_status],
637
+ ).then(
638
+ fn=apply_text_rendering,
639
+ inputs=[result, text_rendering],
640
+ outputs=result,
641
  )
642
 
643
  prompt.submit(
 
661
  fn=after_generate,
662
  inputs=[result, seed_text, generated_gallery],
663
  outputs=[result, seed_text, generated_gallery, generation_status],
664
+ ).then(
665
+ fn=apply_text_rendering,
666
+ inputs=[result, text_rendering],
667
+ outputs=result,
668
  )
669
 
 
670
  gr.HTML("""
671
  <script>
672
  document.addEventListener('DOMContentLoaded', function() {
 
673
  setTimeout(() => {
674
  const examples = document.querySelectorAll('.example-prompt');
675
  const promptInput = document.querySelector('textarea');
 
676
  examples.forEach(example => {
677
  example.addEventListener('click', function() {
678
  promptInput.value = this.textContent.trim();
 
679
  const event = new Event('input', { bubbles: true });
680
  promptInput.dispatchEvent(event);
681
  });
682
  });
683
+ }, 1000);
684
  });
685
  </script>
686
  """)
687
 
 
688
  try:
689
  demo.queue(concurrency_count=1, max_size=20)
690
  demo.launch(debug=True, show_api=False)
691
  except Exception as e:
692
  print(f"Error during launch: {e}")
693
  print("Trying alternative launch configuration...")
694
+ demo.launch(debug=True, show_api=False, share=False)