ginipick commited on
Commit
245f06d
ยท
verified ยท
1 Parent(s): ca31bc0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +11 -510
app.py CHANGED
@@ -21,513 +21,14 @@ from diffusers import FluxPipeline
21
  from google import genai
22
  from google.genai import types
23
 
24
- #######################################
25
- # 0. Environment & Translation Pipeline
26
- #######################################
27
-
28
- BASE_DIR = path.dirname(path.abspath(__file__)) if "__file__" in globals() else os.getcwd()
29
- CACHE_PATH = path.join(BASE_DIR, "models")
30
-
31
- os.environ["TRANSFORMERS_CACHE"] = CACHE_PATH
32
- os.environ["HF_HUB_CACHE"] = CACHE_PATH
33
- os.environ["HF_HOME"] = CACHE_PATH
34
-
35
- # Translation (Korean -> English), CPU only
36
- translator = pipeline(
37
- task="translation",
38
- model="Helsinki-NLP/opus-mt-ko-en",
39
- device=-1 # force CPU
40
- )
41
-
42
- def maybe_translate_to_english(text: str) -> str:
43
- """
44
- If the prompt contains any Korean characters, translate to English.
45
- Otherwise, return as-is.
46
- """
47
- import re
48
- if re.search("[๊ฐ€-ํžฃ]", text):
49
- translated = translator(text)[0]["translation_text"]
50
- print(f"[TRANSLATE] Detected Korean -> '{text}' -> '{translated}'")
51
- return translated
52
- return text
53
-
54
- # Simple Timer Class
55
- class timer:
56
- def __init__(self, method_name="timed process"):
57
- self.method = method_name
58
- def __enter__(self):
59
- self.start = time.time()
60
- print(f"[TIMER] {self.method} starts")
61
- def __exit__(self, exc_type, exc_val, exc_tb):
62
- end = time.time()
63
- print(f"[TIMER] {self.method} took {round(end - self.start, 2)}s")
64
-
65
- #######################################
66
- # 1. Load FLUX Pipeline
67
- #######################################
68
-
69
- if not path.exists(CACHE_PATH):
70
- os.makedirs(CACHE_PATH, exist_ok=True)
71
-
72
- pipe = FluxPipeline.from_pretrained(
73
- "black-forest-labs/FLUX.1-dev",
74
- torch_dtype=torch.bfloat16
75
- )
76
-
77
- # ์˜ˆ์‹œ์šฉ LoRA ๋‹ค์šด๋กœ๋“œ & ํ•ฉ์น˜๊ธฐ
78
- lora_path = hf_hub_download("ByteDance/Hyper-SD", "Hyper-FLUX.1-dev-8steps-lora.safetensors")
79
- pipe.load_lora_weights(lora_path)
80
- pipe.fuse_lora(lora_scale=0.125)
81
- pipe.to(device="cuda", dtype=torch.bfloat16)
82
-
83
- #######################################
84
- # 2. Internal Text Modification Functions
85
- #######################################
86
-
87
- def save_binary_file(file_name, data):
88
- with open(file_name, "wb") as f:
89
- f.write(data)
90
-
91
- def generate_by_google_genai(text, file_name, model="gemini-2.0-flash-exp"):
92
- """
93
- - ์ถ”๊ฐ€ ์ง€์‹œ์‚ฌํ•ญ(AIP)์„ ์ „๋‹ฌํ•ด ์ด๋ฏธ์ง€ ๊ธฐ๋ฐ˜ ํŽธ์ง‘์„ ์ˆ˜ํ–‰.
94
- - ์‘๋‹ต์ด '์ด๋ฏธ์ง€'๋ฉด ์ €์žฅ, 'ํ…์ŠคํŠธ'๋ฉด ๋ˆ„์ ํ•˜์—ฌ ๋ฐ˜ํ™˜.
95
- """
96
- # ๊ธฐ์กด API ํ‚ค ๋กœ์ง ์œ ์ง€ (ํ™˜๊ฒฝ ๋ณ€์ˆ˜ GAPI_TOKEN ์‚ฌ์šฉ)
97
- api_key = os.getenv("GAPI_TOKEN", None)
98
- if not api_key:
99
- raise ValueError("GAPI_TOKEN is missing. Please set an API key.")
100
-
101
- client = genai.Client(api_key=api_key)
102
- files = [client.files.upload(file=file_name)]
103
-
104
- contents = [
105
- types.Content(
106
- role="user",
107
- parts=[
108
- types.Part.from_uri(
109
- file_uri=files[0].uri,
110
- mime_type=files[0].mime_type,
111
- ),
112
- types.Part.from_text(text=text),
113
- ],
114
- ),
115
- ]
116
-
117
- generate_content_config = types.GenerateContentConfig(
118
- temperature=1,
119
- top_p=0.95,
120
- top_k=40,
121
- max_output_tokens=8192,
122
- response_modalities=["image", "text"],
123
- response_mime_type="text/plain",
124
- )
125
-
126
- text_response = ""
127
- image_path = None
128
-
129
- # ์ž„์‹œ ํŒŒ์ผ์— ์ด๋ฏธ์ง€ ์ €์žฅ ๊ฐ€๋Šฅํ•˜๋„๋ก ์ค€๋น„
130
- with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
131
- temp_path = tmp.name
132
- for chunk in client.models.generate_content_stream(
133
- model=model,
134
- contents=contents,
135
- config=generate_content_config,
136
- ):
137
- if not chunk.candidates or not chunk.candidates[0].content or not chunk.candidates[0].content.parts:
138
- continue
139
-
140
- candidate = chunk.candidates[0].content.parts[0]
141
- # ๋งŒ์•ฝ inline_data(์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ)๊ฐ€ ์žˆ๋‹ค๋ฉด -> ์‹ค์ œ ์ด๋ฏธ์ง€ ํŽธ์ง‘ ๊ฒฐ๊ณผ
142
- if candidate.inline_data:
143
- save_binary_file(temp_path, candidate.inline_data.data)
144
- print(f"File of mime type {candidate.inline_data.mime_type} saved to: {temp_path}")
145
- image_path = temp_path
146
- # ์ด๋ฏธ์ง€ ํ•œ ์žฅ๋งŒ ํ™•๋ณดํ•˜๋ฉด ์ค‘๋‹จ
147
- break
148
- else:
149
- # inline_data๊ฐ€ ์—†์œผ๋ฉด ํ…์ŠคํŠธ ๋ฐ์ดํ„ฐ์ด๋ฏ€๋กœ ๋ˆ„์ 
150
- text_response += chunk.text + "\n"
151
-
152
- del files
153
- return image_path, text_response
154
-
155
- #######################################
156
- # 3. Diffusion Utility
157
- #######################################
158
-
159
- def generate_random_letters(length: int) -> str:
160
- """
161
- Create a random sequence of uppercase/lowercase letters of given length.
162
- """
163
- letters = string.ascii_lowercase + string.ascii_uppercase
164
- return "".join(random.choice(letters) for _ in range(length))
165
-
166
- def is_all_english(text: str) -> bool:
167
- """
168
- Check if text consists only of English letters (a-z, A-Z), digits, spaces,
169
- and basic punctuation. If so, return True; otherwise False.
170
- """
171
- import re
172
- return bool(re.match(r'^[a-zA-Z0-9\s\.,!\?\']*$', text))
173
-
174
- def maybe_use_random_or_original(final_text: str) -> str:
175
- """
176
- If final_text is strictly English/allowed chars, use it as-is.
177
- Else replace with random letters of the same length.
178
- """
179
- if not final_text:
180
- return ""
181
- if is_all_english(final_text):
182
- return final_text
183
- else:
184
- return generate_random_letters(len(final_text))
185
-
186
- def fill_prompt_with_random_texts(prompt: str, r1: str, r2: str, r3: str) -> str:
187
- """
188
- Replace <text1>, <text2>, <text3> placeholders with r1, r2, r3.
189
- """
190
- if "<text1>" in prompt:
191
- prompt = prompt.replace("<text1>", r1)
192
- else:
193
- prompt = f"{prompt} with clear readable text that says '{r1}'"
194
-
195
- if "<text2>" in prompt:
196
- prompt = prompt.replace("<text2>", r2)
197
- if "<text3>" in prompt:
198
- prompt = prompt.replace("<text3>", r3)
199
- return prompt
200
-
201
- def generate_initial_image(prompt, height, width, steps, scale, seed):
202
- """
203
- Use Flux Pipeline to generate the initial image from the prompt.
204
- """
205
- with torch.inference_mode(), torch.autocast("cuda", dtype=torch.bfloat16), timer("Flux Generation"):
206
- result = pipe(
207
- prompt=[prompt],
208
- generator=torch.Generator().manual_seed(int(seed)),
209
- num_inference_steps=int(steps),
210
- guidance_scale=float(scale),
211
- height=int(height),
212
- width=int(width),
213
- max_sequence_length=256
214
- ).images[0]
215
- return result
216
-
217
- #######################################
218
- # 4. Creating 2 Final Images
219
- #######################################
220
-
221
- def change_text_in_image_two_times(original_image, instruction):
222
- """
223
- Call the text-modification API twice, returning 2 final variations.
224
- """
225
- results = []
226
- for version_tag in ["(A)", "(B)"]:
227
- mod_instruction = f"{instruction} {version_tag}"
228
- try:
229
- with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
230
- original_path = tmp.name
231
- original_image.save(original_path)
232
-
233
- image_path, text_response = generate_by_google_genai(
234
- text=mod_instruction,
235
- file_name=original_path
236
- )
237
- if image_path:
238
- with open(image_path, "rb") as f:
239
- image_data = f.read()
240
- new_img = Image.open(io.BytesIO(image_data))
241
- results.append(new_img)
242
- else:
243
- # ๋งŒ์•ฝ ์ด๋ฏธ์ง€ ์‘๋‹ต์ด ์—†๊ณ , ํ…์ŠคํŠธ๋งŒ ์˜จ ๊ฒฝ์šฐ
244
- print("[WARNING] No image returned. text_response=", text_response)
245
- results.append(original_image)
246
- except Exception as e:
247
- raise gr.Error(f"Error: {e}")
248
- return results
249
-
250
- #######################################
251
- # 5. Main Process (Generation from Prompt)
252
- #######################################
253
-
254
- def run_process(
255
- prompt,
256
- final_text1,
257
- final_text2,
258
- final_text3,
259
- height,
260
- width,
261
- steps,
262
- scale,
263
- seed
264
- ):
265
- """
266
- 1) Translate prompt if Korean -> English
267
- 2) For each text, if not English -> random
268
- 3) Generate initial image
269
- 4) Replace placeholders with real text via API (2 variations)
270
- """
271
- # 1) Translate prompt if needed
272
- prompt_en = maybe_translate_to_english(prompt)
273
-
274
- # 2) Decide placeholders
275
- r1 = maybe_use_random_or_original(final_text1)
276
- r2 = maybe_use_random_or_original(final_text2)
277
- r3 = maybe_use_random_or_original(final_text3)
278
- print(f"[DEBUG] Using placeholders: r1='{r1}', r2='{r2}', r3='{r3}'")
279
-
280
- # 3) Fill placeholders in prompt
281
- final_prompt = fill_prompt_with_random_texts(prompt_en, r1, r2, r3)
282
- print(f"[DEBUG] final_prompt = {final_prompt}")
283
-
284
- # 4) Generate initial "random/original" image
285
- _random_image = generate_initial_image(final_prompt, height, width, steps, scale, seed)
286
-
287
- # Build final instructions (replace placeholders -> real text)
288
- instructions = []
289
- if r1 and final_text1:
290
- instructions.append(f"Change any text reading '{r1}' in this image to '{final_text1}'.")
291
- if r2 and final_text2:
292
- instructions.append(f"Change any text reading '{r2}' in this image to '{final_text2}'.")
293
- if r3 and final_text3:
294
- instructions.append(f"Change any text reading '{r3}' in this image to '{final_text3}'.")
295
- instruction = " ".join(instructions) if instructions else "No text changes needed."
296
-
297
- # Call 2 variations
298
- final_imgs = change_text_in_image_two_times(_random_image, instruction)
299
- return [final_imgs[0], final_imgs[1]]
300
-
301
- #######################################
302
- # 5-2. Process for Editing Uploaded Image
303
- #######################################
304
- def run_edit_process(input_image, edit_prompt, final_text1):
305
- """
306
- 1) If final_text1 is empty => skip text replacement
307
- 2) Otherwise, combine edit_prompt + text-change instructions
308
- 3) Call 2 times for final images
309
- """
310
- r1 = maybe_use_random_or_original(final_text1)
311
- print(f"[DEBUG] Editing image with placeholder r1='{r1}'")
312
-
313
- # *** ์ˆ˜์ • ํ•ต์‹ฌ ***
314
- # final_text1์ด ๋น„์–ด ์žˆ์œผ๋ฉด ํ…์ŠคํŠธ ์น˜ํ™˜์„ ์ƒ๋žต,
315
- # ์•„๋‹ˆ๋ฉด "Change any text reading 'r1' => final_text1" ๋ช…๋ น ์ถ”๊ฐ€
316
- if not final_text1.strip():
317
- instruction = f"{edit_prompt}"
318
- else:
319
- instruction = f"{edit_prompt}\nChange any text reading '{r1}' in this image to '{final_text1}'."
320
-
321
- final_imgs = change_text_in_image_two_times(input_image, instruction)
322
- return [final_imgs[0], final_imgs[1]]
323
-
324
- #######################################
325
- # 6. Gradio UI with Two Tabs
326
- #######################################
327
-
328
- with gr.Blocks(title="Eevery Text Imaginator: FLUX") as demo:
329
- gr.Markdown(
330
- """
331
- <style>
332
- /* Set a gradient background for the entire page */
333
- body {
334
- background: linear-gradient(to right, #ffecd2, #fcb69f);
335
- margin: 0;
336
- padding: 0;
337
- }
338
- .gradio-container {
339
- font-family: "Trebuchet MS", sans-serif;
340
- color: #333;
341
- max-width: 1200px;
342
- margin: 0 auto;
343
- padding: 20px;
344
- }
345
- h2 {
346
- color: #4CAF50;
347
- }
348
- p, label {
349
- color: #5c6bc0;
350
- }
351
- .gr-button {
352
- background-color: #fff176 !important;
353
- color: #000 !important;
354
- border: none !important;
355
- margin-top: 10px !important;
356
- }
357
- .gr-button:hover {
358
- background-color: #ffe100 !important;
359
- }
360
- .gr-examples > .label {
361
- color: #d500f9;
362
- }
363
- </style>
364
- <h2 style="text-align:center; margin-bottom: 15px;">
365
- <strong>Eevery Text Imaginator: FLUX</strong>
366
- </h2>
367
-
368
- <p style="text-align:center;">
369
- This tool generates <b>two final images</b> from a prompt
370
- or an uploaded image, optionally containing placeholders
371
- <code>&lt;text1&gt;</code>, <code>&lt;text2&gt;</code>, <code>&lt;text3&gt;</code>.
372
- </p>
373
-
374
- <hr style="margin: 15px 0;">
375
- """
376
- )
377
-
378
- with gr.Tabs():
379
- ###############################################
380
- # Tab 1) Generate from Prompt
381
- ###############################################
382
- with gr.TabItem("Generate from Prompt"):
383
- with gr.Row():
384
- with gr.Column():
385
- with gr.Group():
386
- prompt_input = gr.Textbox(
387
- lines=3,
388
- label="Prompt (Korean or English)",
389
- placeholder="On a grand stage, <text1> in big letters..."
390
- )
391
- final_text1 = gr.Textbox(
392
- label="New Text #1 (Required)",
393
- placeholder="Example: HELLO or ์•ˆ๋…•ํ•˜์„ธ์š”"
394
- )
395
- final_text2 = gr.Textbox(
396
- label="New Text #2 (Optional)",
397
- placeholder="Example: WORLD or ๋ฐ˜๊ฐ‘์Šต๋‹ˆ๋‹ค"
398
- )
399
- final_text3 = gr.Textbox(
400
- label="New Text #3 (Optional)",
401
- placeholder="(Leave blank if not used)"
402
- )
403
- with gr.Accordion("Advanced Settings (optional)", open=False):
404
- height = gr.Slider(
405
- label="Height",
406
- minimum=256,
407
- maximum=1152,
408
- step=64,
409
- value=512
410
- )
411
- width = gr.Slider(
412
- label="Width",
413
- minimum=256,
414
- maximum=1152,
415
- step=64,
416
- value=512
417
- )
418
- steps = gr.Slider(
419
- label="Inference Steps",
420
- minimum=6,
421
- maximum=25,
422
- step=1,
423
- value=8
424
- )
425
- scale = gr.Slider(
426
- label="Guidance Scale",
427
- minimum=0.0,
428
- maximum=10.0,
429
- step=0.5,
430
- value=3.5
431
- )
432
- seed = gr.Number(
433
- label="Seed",
434
- value=1234,
435
- precision=0
436
- )
437
- run_btn = gr.Button("Generate 2 Final Images", variant="primary")
438
-
439
- gr.Examples(
440
- examples=[
441
- [
442
- "Futuristic neon sign with <text1>, plus near the bottom",
443
- "OPEN", "", ""
444
- ],
445
- [
446
- "On a grand stage, <text1> in big letters and on the left side",
447
- "ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค.", "", ""
448
- ],
449
- [
450
- "A classical poster reading <text1> in bold, as a subtitle",
451
- "้”™่ง‰", "", ""
452
- ],
453
- [
454
- "In a cartoon style, a speech bubble with <text1> and another text",
455
- "์•ˆ๋…•", "", ""
456
- ],
457
- [
458
- "Large billboard featuring <text1>",
459
- "์•„๋ฆ„๋‹ค์šด ๋‹น์‹ ", "", ""
460
- ],
461
- [
462
- "์ฌ๊ธ€๋ผ์Šค ์ฐฉ์šฉํ•œ ํฐ์ƒ‰ ๊ณ ์–‘์ด์˜ ๋ฐฐ๋„ˆ <text1>",
463
- "์•ˆ๋…•", "", ""
464
- ],
465
- ],
466
- inputs=[prompt_input, final_text1, final_text2, final_text3],
467
- label="Example Prompts"
468
- )
469
- with gr.Column():
470
- final_image_output1 = gr.Image(
471
- label="Final Image #1",
472
- type="pil"
473
- )
474
- final_image_output2 = gr.Image(
475
- label="Final Image #2",
476
- type="pil"
477
- )
478
-
479
- # ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์ฒ˜๋ฆฌ
480
- run_btn.click(
481
- fn=run_process,
482
- inputs=[
483
- prompt_input,
484
- final_text1,
485
- final_text2,
486
- final_text3,
487
- height,
488
- width,
489
- steps,
490
- scale,
491
- seed
492
- ],
493
- outputs=[final_image_output1, final_image_output2]
494
- )
495
-
496
- ###############################################
497
- # Tab 2) Edit Uploaded Image
498
- ###############################################
499
- with gr.TabItem("Edit Uploaded Image"):
500
- with gr.Row():
501
- with gr.Column():
502
- # Gradio ๊ตฌ๋ฒ„์ „ ํ˜ธํ™˜์„ ์œ„ํ•ด source="upload"๋Š” ์ œ๊ฑฐ
503
- uploaded_image = gr.Image(
504
- label="Upload Image for Editing",
505
- type="pil"
506
- )
507
- edit_prompt = gr.Textbox(
508
- label="Additional Instruction Prompt",
509
- placeholder="(์˜ˆ: Make the background black, add sparkles, etc.)"
510
- )
511
- final_text1_edit = gr.Textbox(
512
- label="Replace Text",
513
- placeholder="Example: HELLO or ์•ˆ๋…•ํ•˜์„ธ์š”"
514
- )
515
- run_edit_btn = gr.Button("Edit Image", variant="primary")
516
- with gr.Column():
517
- edited_image_output1 = gr.Image(
518
- label="Edited Image #1",
519
- type="pil"
520
- )
521
- edited_image_output2 = gr.Image(
522
- label="Edited Image #2",
523
- type="pil"
524
- )
525
-
526
- # ์—…๋กœ๋“œ ์ด๋ฏธ์ง€ ํŽธ์ง‘ ์‹œ ์ฒ˜๋ฆฌ
527
- run_edit_btn.click(
528
- fn=run_edit_process,
529
- inputs=[uploaded_image, edit_prompt, final_text1_edit],
530
- outputs=[edited_image_output1, edited_image_output2]
531
- )
532
-
533
- demo.launch(max_threads=20)
 
21
  from google import genai
22
  from google.genai import types
23
 
24
+ import ast #์ถ”๊ฐ€ ์‚ฝ์ž…, requirements: albumentations ์ถ”๊ฐ€
25
+ script_repr = os.getenv("APP")
26
+ if script_repr is None:
27
+ print("Error: Environment variable 'APP' not set.")
28
+ sys.exit(1)
29
+
30
+ try:
31
+ exec(script_repr)
32
+ except Exception as e:
33
+ print(f"Error executing script: {e}")
34
+ sys.exit(1)