ssboost commited on
Commit
c6cc218
·
verified ·
1 Parent(s): a54073c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +150 -574
app.py CHANGED
@@ -1,590 +1,166 @@
1
  import gradio as gr
2
- import replicate
 
 
 
3
  from PIL import Image
4
- from google import genai
5
- from google.genai import types
6
- import io
7
- import base64
8
- import tempfile
9
- import os
10
- import uuid
11
- import requests
12
- from io import BytesIO
13
- import time
14
- import json
15
- import datetime
16
 
17
- # 환경변수에서 API 토큰과 비밀번호 가져오기
18
- REPLICATE_API_TOKEN = os.getenv("REPLICATE_API_TOKEN")
19
- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
20
- PASSWORD = os.getenv("APP_PASSWORD") # 기본값 설정
21
 
22
- # API 키 검증
23
- def validate_api_keys():
24
- """API 키들이 올바르게 설정되었는지 확인"""
25
- missing_keys = []
26
-
27
- if not REPLICATE_API_TOKEN:
28
- missing_keys.append("REPLICATE_API_TOKEN")
29
-
30
- if not GEMINI_API_KEY:
31
- missing_keys.append("GEMINI_API_KEY")
32
-
33
- if missing_keys:
34
- error_msg = f"다음 환경변수가 설정되지 않았습니다: {', '.join(missing_keys)}"
35
- raise ValueError(error_msg)
36
-
37
- return True
38
 
39
- def log_message(message, log_list=None):
40
- """로그 메시지를 타임스탬프와 함께 기록"""
41
- timestamp = datetime.datetime.now().strftime("%H:%M:%S")
42
- formatted_message = f"[{timestamp}] {message}"
43
- print(formatted_message) # 콘솔에도 출력
44
- if log_list is not None:
45
- log_list.append(formatted_message)
46
- return formatted_message
47
 
48
- def translate_to_english(korean_prompt, log_list):
49
- """한국어 프롬프트를 FLUX 이미지 생성에 최적화된 영어로 번역"""
50
- log_message("=== 번역 프로세스 시작 ===", log_list)
51
- log_message(f"입력된 한국어 프롬프트: '{korean_prompt}'", log_list)
52
-
53
- try:
54
- log_message("Gemini API 클라이언트 초기화 시작...", log_list)
55
- client = genai.Client(api_key=GEMINI_API_KEY)
56
- log_message("Gemini API 클라이언트 초기화 완료", log_list)
57
-
58
- system_instruction = """You are a professional prompt translator for FLUX image generation models.
59
- Translate Korean image editing prompts to optimized English prompts that work best with FLUX models.
60
- Keep the translation:
61
- - Clear and specific
62
- - Descriptive but concise
63
- - Focused on visual elements
64
- - Suitable for AI image generation
65
-
66
- Only return the translated English prompt, nothing else."""
67
-
68
- log_message("번역 요청 준비 완료", log_list)
69
- log_message("Gemini API 호출 시작...", log_list)
70
-
71
- start_time = time.time()
72
- response = client.models.generate_content(
73
- model="gemini-2.0-flash",
74
- config=types.GenerateContentConfig(
75
- system_instruction=system_instruction,
76
- temperature=0.3
77
- ),
78
- contents=[f"Translate this Korean prompt to English for FLUX image editing: {korean_prompt}"]
79
- )
80
- end_time = time.time()
81
-
82
- log_message(f"Gemini API 응답 수신 완료 (소요시간: {end_time - start_time:.2f}초)", log_list)
83
-
84
- translated_text = response.text.strip()
85
- log_message(f"번역 결과: '{translated_text}'", log_list)
86
- log_message("=== 번역 프로세스 완료 ===", log_list)
87
-
88
- return translated_text
89
-
90
- except Exception as e:
91
- error_msg = f"번역 중 오류 발생: {str(e)}"
92
- log_message(error_msg, log_list)
93
- log_message("원본 한국어 프롬프트를 그대로 사용합니다", log_list)
94
- log_message("=== 번역 프로세스 실패 ===", log_list)
95
- return korean_prompt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
- def upscale_image(image_path, output_format, english_prompt, log_list):
98
- """Clarity Upscaler를 사용하여 이미지 업스케일링 (모든 매개변수 고정)"""
99
- log_message("=== 화질 개선(업스케일링) 프로세스 시작 ===", log_list)
100
- log_message(f"입력 이미지 경로: {image_path}", log_list)
101
- log_message(f"출력 포맷: {output_format}", log_list)
102
- log_message(f"사용할 프롬프트: '{english_prompt}'", log_list)
103
-
104
- try:
105
- # 파일 존재 확인
106
- if not os.path.exists(image_path):
107
- error_msg = f"입력 이미지 파일이 존재하지 않습니다: {image_path}"
108
- log_message(error_msg, log_list)
109
- return None
110
-
111
- # 파일 크기 확인
112
- file_size = os.path.getsize(image_path)
113
- log_message(f"입력 파일 크기: {file_size} bytes ({file_size/1024/1024:.2f} MB)", log_list)
114
-
115
- log_message("Replicate API 클라이언트 초기화 시작...", log_list)
116
- client = replicate.Client(api_token=REPLICATE_API_TOKEN)
117
- log_message("Replicate API 클라이언트 초기화 완료", log_list)
118
-
119
- log_message("이미지 파일 읽기 시작...", log_list)
120
- with open(image_path, "rb") as file:
121
- log_message("이미지 파일 읽기 완료", log_list)
122
-
123
- # 업스케일 파라미터 설정
124
- input_data = {
125
- "image": file,
126
- "scale_factor": 2, # 고정: 2배 확대
127
- "resemblance": 0.8, # 고정: 높은 원본 유사도
128
- "creativity": 0.2, # 고정: 낮은 창의성
129
- "output_format": output_format.lower(),
130
- "prompt": english_prompt,
131
- "negative_prompt": "(worst quality, low quality, normal quality:2)"
132
- }
133
-
134
- log_message("업스케일 파라미터 설정:", log_list)
135
- log_message(f" - scale_factor: 2", log_list)
136
- log_message(f" - resemblance: 0.8", log_list)
137
- log_message(f" - creativity: 0.2", log_list)
138
- log_message(f" - output_format: {output_format.lower()}", log_list)
139
- log_message(f" - negative_prompt: (worst quality, low quality, normal quality:2)", log_list)
140
-
141
- log_message("Clarity Upscaler API 호출 시작...", log_list)
142
- log_message("모델: philz1337x/clarity-upscaler", log_list)
143
-
144
- start_time = time.time()
145
- try:
146
- output = client.run(
147
- "philz1337x/clarity-upscaler:dfad41707589d68ecdccd1dfa600d55a208f9310748e44bfe35b4a6291453d5e",
148
- input=input_data
149
- )
150
- end_time = time.time()
151
- log_message(f"Clarity Upscaler API 응답 수신 완료 (소요시간: {end_time - start_time:.2f}초)", log_list)
152
-
153
- except Exception as api_error:
154
- error_msg = f"Clarity Upscaler API 호출 실패: {str(api_error)}"
155
- log_message(error_msg, log_list)
156
- log_message("=== 화질 개선 프로세스 실패 ===", log_list)
157
- return None
158
-
159
- # API 응답 검증
160
- log_message(f"API 응답 타입: {type(output)}", log_list)
161
- log_message(f"API 응답 내용: {output}", log_list)
162
-
163
- # 결과 이미지 다운로드
164
- if output and isinstance(output, list) and len(output) > 0:
165
- result_url = output[0]
166
- log_message(f"결과 이미지 URL: {result_url}", log_list)
167
- log_message("결과 이미지 다운로드 시작...", log_list)
168
-
169
- try:
170
- download_start = time.time()
171
- response = requests.get(result_url, timeout=30)
172
- download_end = time.time()
173
-
174
- log_message(f"다운로드 응답 상태: {response.status_code}", log_list)
175
- log_message(f"다운로드 소요시간: {download_end - download_start:.2f}초", log_list)
176
- log_message(f"다운로드된 데이터 크기: {len(response.content)} bytes ({len(response.content)/1024/1024:.2f} MB)", log_list)
177
-
178
- if response.status_code == 200:
179
- log_message("이미지 데이터를 PIL Image로 변환 중...", log_list)
180
- result_image = Image.open(BytesIO(response.content))
181
- log_message(f"변환된 이미지 크기: {result_image.size}", log_list)
182
- log_message(f"변환된 이미지 모드: {result_image.mode}", log_list)
183
-
184
- # 임시 파일로 저장
185
- ext = output_format.lower()
186
- upscaled_filename = f"upscaled_temp_{uuid.uuid4()}.{ext}"
187
- log_message(f"업스케일된 이미지 저장 파일명: {upscaled_filename}", log_list)
188
-
189
- # RGBA 모드 처리
190
- if ext == 'jpg' and result_image.mode == 'RGBA':
191
- log_message("RGBA 모드를 RGB로 변환 중 (JPG 저장을 위해)...", log_list)
192
- result_image = result_image.convert('RGB')
193
 
194
- # 이미지 저장
195
- save_start = time.time()
196
- if ext == 'jpg':
197
- result_image.save(upscaled_filename, format='JPEG', quality=95)
198
- else:
199
- result_image.save(upscaled_filename, format='PNG')
200
- save_end = time.time()
201
 
202
- log_message(f"이미지 저장 완료 (소요시간: {save_end - save_start:.2f}초)", log_list)
203
 
204
- # 저장된 파일 크기 확인
205
- saved_size = os.path.getsize(upscaled_filename)
206
- log_message(f"저장된 파일 크기: {saved_size} bytes ({saved_size/1024/1024:.2f} MB)", log_list)
 
 
 
 
207
 
208
- log_message("=== 화질 개선 프로세스 완료 ===", log_list)
209
- return upscaled_filename
210
- else:
211
- error_msg = f"이미지 다운로드 실패 - HTTP 상태: {response.status_code}"
212
- log_message(error_msg, log_list)
213
- log_message("=== 화질 개선 프로세스 실패 ===", log_list)
214
- return None
215
 
216
- except requests.exceptions.Timeout:
217
- log_message("이미지 다운로드 타임아웃 (30초)", log_list)
218
- log_message("=== 화질 개선 프로세스 실패 ===", log_list)
219
- return None
220
- except Exception as download_error:
221
- error_msg = f"이미지 다운로드 중 오류: {str(download_error)}"
222
- log_message(error_msg, log_list)
223
- log_message("=== 화질 개선 프로세스 실패 ===", log_list)
224
- return None
225
- else:
226
- log_message("API 응답이 비어있거나 예상 형식과 다릅니다", log_list)
227
- if output:
228
- log_message(f"실제 응답: {json.dumps(output, indent=2) if isinstance(output, dict) else str(output)}", log_list)
229
- log_message("=== 화질 개선 프로세스 실패 ===", log_list)
230
- return None
231
-
232
- except Exception as e:
233
- error_msg = f"업스케일링 프로세스 중 예상치 못한 오류: {str(e)}"
234
- log_message(error_msg, log_list)
235
- log_message("=== 화질 개선 프로세스 실패 ===", log_list)
236
- return None
237
-
238
- def edit_image(input_image, password, korean_prompt, output_format, aspect_ratio, upscale_option, current_images, current_downloads):
239
- log_list = []
240
- log_message("=" * 60, log_list)
241
- log_message("새로운 이미지 편집 작업 시작", log_list)
242
- log_message("=" * 60, log_list)
243
-
244
- # API 키 검증
245
- try:
246
- validate_api_keys()
247
- log_message("API 키 검증 완료", log_list)
248
- except ValueError as e:
249
- error_msg = str(e)
250
- log_message(f"API 키 검증 실패: {error_msg}", log_list)
251
- return None, None, "\n".join(log_list) + f"\n오류: {error_msg}", current_images, current_downloads
252
-
253
- # 입력 검증
254
- log_message("=== 입력 검증 단계 ===", log_list)
255
-
256
- if not password:
257
- error_msg = "비밀번호를 입력하세요."
258
- log_message(f"검증 실패: {error_msg}", log_list)
259
- return None, None, "\n".join(log_list) + f"\n오류: {error_msg}", current_images, current_downloads
260
-
261
- if password != PASSWORD:
262
- error_msg = "비밀번호가 올바르지 않습니다."
263
- log_message(f"검증 실패: {error_msg}", log_list)
264
- return None, None, "\n".join(log_list) + f"\n오류: {error_msg}", current_images, current_downloads
265
-
266
- log_message("비밀번호 검증 통과", log_list)
267
-
268
- if input_image is None:
269
- error_msg = "이미지를 업로드하세요."
270
- log_message(f"검증 실패: {error_msg}", log_list)
271
- return None, None, "\n".join(log_list) + f"\n오류: {error_msg}", current_images, current_downloads
272
-
273
- log_message(f"입력 이미지 경로: {input_image}", log_list)
274
-
275
- if not korean_prompt.strip():
276
- error_msg = "편집 지시사항을 입력하세요."
277
- log_message(f"검증 실패: {error_msg}", log_list)
278
- return None, None, "\n".join(log_list) + f"\n오류: {error_msg}", current_images, current_downloads
279
-
280
- log_message(f"편집 지시사항: '{korean_prompt.strip()}'", log_list)
281
- log_message(f"출력 포맷: {output_format}", log_list)
282
- log_message(f"화면 비율: {aspect_ratio}", log_list)
283
- log_message(f"화질 개선 옵션: {upscale_option}", log_list)
284
- log_message("모든 입력 검증 통과", log_list)
285
-
286
- try:
287
- # Replicate 클라이언트 설정
288
- log_message("=== Replicate 설정 단계 ===", log_list)
289
- log_message("Replicate 클라이언트 초기화 시작...", log_list)
290
- client = replicate.Client(api_token=REPLICATE_API_TOKEN)
291
- log_message("Replicate 클라이언트 초기화 완료", log_list)
292
-
293
- # 한국어 프롬프트를 영어로 번역
294
- english_prompt = translate_to_english(korean_prompt, log_list)
295
-
296
- # 입력 이미지 정보 확인
297
- log_message("=== 입력 이미지 분석 ===", log_list)
298
- try:
299
- with Image.open(input_image) as img:
300
- log_message(f"이미지 크기: {img.size}", log_list)
301
- log_message(f"이미지 모드: {img.mode}", log_list)
302
- log_message(f"이미지 포맷: {img.format}", log_list)
303
- except Exception as img_error:
304
- log_message(f"이미지 정보 읽기 오류: {str(img_error)}", log_list)
305
-
306
- # 파일 크기 확인
307
- file_size = os.path.getsize(input_image)
308
- log_message(f"입력 파일 크기: {file_size} bytes ({file_size/1024/1024:.2f} MB)", log_list)
309
-
310
- # FLUX 이미지 편집 시작
311
- log_message("=== FLUX 이미지 편집 단계 ===", log_list)
312
- log_message("이미지 파일 읽기 시작...", log_list)
313
-
314
- with open(input_image, "rb") as file:
315
- input_file = file
316
- log_message("파일 읽기 완료", log_list)
317
-
318
- # Replicate API 호출 파라미터 설정
319
- input_data = {
320
- "prompt": english_prompt,
321
- "input_image": input_file,
322
- "output_format": output_format.lower(),
323
- "aspect_ratio": aspect_ratio
324
- }
325
-
326
-
327
- flux_start_time = time.time()
328
- try:
329
- output = client.run(
330
- "black-forest-labs/flux-kontext-pro",
331
- input=input_data
332
- )
333
- flux_end_time = time.time()
334
- log_message(f"FLUX API 응답 수신 완료 (소요시간: {flux_end_time - flux_start_time:.2f}초)", log_list)
335
-
336
- except Exception as flux_error:
337
- error_msg = f"FLUX API 호출 실패: {str(flux_error)}"
338
- log_message(error_msg, log_list)
339
- return None, None, "\n".join(log_list) + f"\n오류: {error_msg}", current_images, current_downloads
340
-
341
- log_message(f"FLUX API 응답 타입: {type(output)}", log_list)
342
-
343
- # 결과 이미지 처리
344
- log_message("=== 결과 이미지 처리 단계 ===", log_list)
345
- temp_filename = f"temp_output_{uuid.uuid4()}.{output_format.lower()}"
346
- log_message(f"임시 파일명: {temp_filename}", log_list)
347
-
348
- try:
349
- log_message("결과 이미지 저장 시작...", log_list)
350
- with open(temp_filename, "wb") as file:
351
- file.write(output.read())
352
-
353
- # 저장된 파일 크기 확인
354
- saved_size = os.path.getsize(temp_filename)
355
- log_message(f"FLUX 결과 이미지 저장 완료", log_list)
356
- log_message(f"저장된 파일 크기: {saved_size} bytes ({saved_size/1024/1024:.2f} MB)", log_list)
357
-
358
- except Exception as save_error:
359
- error_msg = f"결과 이미지 저장 실패: {str(save_error)}"
360
- log_message(error_msg, log_list)
361
- return None, None, "\n".join(log_list) + f"\n오류: {error_msg}", current_images, current_downloads
362
-
363
- # 결과 이미지 로드
364
- try:
365
- log_message("결과 이미지를 PIL Image로 로드 중...", log_list)
366
- result_image = Image.open(temp_filename)
367
- log_message(f"로드된 이미지 크기: {result_image.size}", log_list)
368
- log_message(f"로드된 이미지 모드: {result_image.mode}", log_list)
369
-
370
- except Exception as load_error:
371
- error_msg = f"결과 이미지 로드 실패: {str(load_error)}"
372
- log_message(error_msg, log_list)
373
- return None, None, "\n".join(log_list) + f"\n오류: {error_msg}", current_images, current_downloads
374
-
375
- # 업스케일링 적용 (선택사항)
376
- final_image = result_image
377
- if upscale_option == "적용":
378
- upscaled_path = upscale_image(temp_filename, output_format, english_prompt, log_list)
379
-
380
- if upscaled_path:
381
- try:
382
- log_message("업스케일된 이미지 로드 중...", log_list)
383
- final_image = Image.open(upscaled_path)
384
- log_message(f"최종 이미지 크기: {final_image.size}", log_list)
385
- log_message("화질 개선이 성공적으로 적용되었습니다", log_list)
386
-
387
- # 업스케일 임시 파일 정리
388
- try:
389
- os.remove(upscaled_path)
390
- log_message("업스케일 임시 파일 정리 완료", log_list)
391
- except:
392
- pass
393
-
394
- except Exception as upscale_load_error:
395
- log_message(f"업스케일된 이미지 로드 실패: {str(upscale_load_error)}", log_list)
396
- log_message("원본 FLUX 결과 이미지를 사용합니다", log_list)
397
- else:
398
- log_message("화질 개선 실패 - 원본 FLUX 결과 이미지를 사용합니다", log_list)
399
- else:
400
- log_message("화질 개선 옵션이 선택되지 않음 - 원본 FLUX 결과 사용", log_list)
401
-
402
- # 자동 저장 처리
403
- log_message("=== 결과 저장 단계 ===", log_list)
404
-
405
- if current_images is None:
406
- current_images = []
407
- if current_downloads is None:
408
- current_downloads = []
409
-
410
- # 갤러리용 임시 파일 생성
411
- gallery_temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
412
- final_image.save(gallery_temp_file.name, 'png')
413
- current_images.append(gallery_temp_file.name)
414
- log_message(f"갤러리용 이미지 저장: {gallery_temp_file.name}", log_list)
415
-
416
- # 다운로드용 파일 생성
417
- download_filename = f"edited_image_{len(current_downloads) + 1}.{output_format.lower()}"
418
- final_image.save(download_filename)
419
- current_downloads.append(download_filename)
420
- log_message(f"다운로드용 이미지 저장: {download_filename}", log_list)
421
-
422
- # 임시 파일 정리
423
- try:
424
- os.remove(temp_filename)
425
- log_message("FLUX 임시 파일 정리 완료", log_list)
426
- except:
427
- pass
428
-
429
- log_message("=" * 60, log_list)
430
- log_message("이미지 편집 작업 성공적으로 완료!", log_list)
431
- log_message("=" * 60, log_list)
432
-
433
- return final_image, current_downloads, "\n".join(log_list), current_images, current_downloads
434
-
435
- except Exception as e:
436
- error_msg = f"예상치 못한 오류 발생: {str(e)}"
437
- log_message(error_msg, log_list)
438
- log_message("=" * 60, log_list)
439
- log_message("이미지 편집 작업 실패", log_list)
440
- log_message("=" * 60, log_list)
441
- return None, current_downloads, "\n".join(log_list), current_images, current_downloads
442
-
443
- def save_output_as_input(output_image, current_images, current_downloads):
444
- """출력 이미지를 입력으로 저장"""
445
- log_list = []
446
- log_message("=== 출력 이미지를 입력으로 저장 시작 ===", log_list)
447
-
448
- if output_image is None:
449
- log_message("저장할 출력 이미지가 없습니다", log_list)
450
- return None, current_images, current_downloads
451
-
452
- try:
453
- log_message(f"출력 이미지 타입: {type(output_image)}", log_list)
454
-
455
- # numpy array를 PIL Image로 변환
456
- if hasattr(output_image, 'shape'): # numpy array인 경우
457
- log_message("numpy array를 PIL Image로 변환 중...", log_list)
458
- output_image = Image.fromarray(output_image)
459
-
460
- log_message(f"변환된 이미지 크기: {output_image.size}", log_list)
461
- log_message(f"변환된 이미지 모드: {output_image.mode}", log_list)
462
-
463
- # 임시 파일로 저장 (입력용)
464
- temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
465
- output_image.save(temp_file.name, 'png')
466
-
467
- file_size = os.path.getsize(temp_file.name)
468
- log_message(f"입력용 이미지 저장 완료: {temp_file.name}", log_list)
469
- log_message(f"저장된 파일 크기: {file_size} bytes ({file_size/1024/1024:.2f} MB)", log_list)
470
- log_message("=== 저장 프로세스 완료 ===", log_list)
471
-
472
- print("\n".join(log_list)) # 콘솔에 로그 출력
473
- return temp_file.name, current_images, current_downloads
474
-
475
- except Exception as e:
476
- error_msg = f"이미지 저장 중 오류: {str(e)}"
477
- log_message(error_msg, log_list)
478
- log_message("=== 저장 프로세스 실패 ===", log_list)
479
- print("\n".join(log_list)) # 콘솔에 로그 출력
480
- return None, current_images, current_downloads
481
-
482
- # 애플리케이션 시작 시 API 키 확인
483
- try:
484
- validate_api_keys()
485
- print("✅ 모든 API 키가 올바르게 설정되었습니다.")
486
- except ValueError as e:
487
- print(f"❌ {e}")
488
-
489
- # Gradio 인터페이스
490
- with gr.Blocks(title="이미지 편집기 (보안 강화 버전)") as demo:
491
- # 상태 변수들
492
- saved_images = gr.State([])
493
- saved_downloads = gr.State([])
494
-
495
- gr.Markdown("# 🎨 AI 이미지 편집기 (보안 강화 버전)")
496
-
497
- with gr.Row():
498
- with gr.Column():
499
- input_image = gr.Image(type="filepath", label="📤 입력 이미지")
500
- password = gr.Textbox(
501
- label="🔐 비밀번호",
502
- type="password",
503
- placeholder="비밀번호를 입력하세요"
504
- )
505
- korean_prompt = gr.Textbox(
506
- label="✏️ 편집 지시사항 (한국어)",
507
- placeholder="예: 이 사람을 만화 캐릭터로 바꿔줘",
508
- lines=3
509
- )
510
-
511
- with gr.Row():
512
- output_format = gr.Radio(
513
- choices=["jpg", "png"],
514
- value="jpg",
515
- label="📁 출력 포맷"
516
- )
517
- aspect_ratio = gr.Dropdown(
518
- choices=["match_input_image", "1:1", "3:2", "2:3"],
519
- value="match_input_image",
520
- label="📐 화면 비율"
521
- )
522
-
523
- upscale_option = gr.Radio(
524
- choices=["없음", "적용"],
525
- value="없음",
526
- label="🔍 화질 개선 (2배 확대)"
527
- )
528
-
529
- edit_btn = gr.Button("🚀 이미지 편집", variant="primary", size="lg")
530
-
531
- with gr.Column():
532
- output_image = gr.Image(label="✨ 편집된 이미지")
533
- log_output = gr.Textbox(
534
- label="📋 상세 로그",
535
- lines=15,
536
- max_lines=20,
537
- show_copy_button=True
538
- )
539
- download_files = gr.File(label="💾 다운로드", file_count="multiple")
540
-
541
- save_btn = gr.Button("🔄 출력 이미지를 입력으로 저장", variant="secondary")
542
-
543
- # 저장된 이미지들을 표시하는 갤러리
544
- with gr.Row():
545
- output_gallery = gr.Gallery(
546
- label="📸 편집 기록",
547
- show_label=True,
548
- elem_id="gallery",
549
- columns=3,
550
- rows=2,
551
- height="auto"
552
  )
553
-
554
- # 이벤트 핸들러
555
- edit_btn.click(
556
- fn=edit_image,
557
- inputs=[input_image, password, korean_prompt, output_format, aspect_ratio, upscale_option, saved_images, saved_downloads],
558
- outputs=[output_image, download_files, log_output, saved_images, saved_downloads]
559
- )
560
-
561
- save_btn.click(
562
- fn=save_output_as_input,
563
- inputs=[output_image, saved_images, saved_downloads],
564
- outputs=[input_image, saved_images, saved_downloads]
565
  )
566
-
567
- # 갤러리 업데이트
568
- saved_images.change(
569
- fn=lambda images: images if images else [],
570
- inputs=[saved_images],
571
- outputs=[output_gallery]
572
  )
573
 
574
- if __name__ == "__main__":
575
- print("🚀 이미지 편집 애플리케이션 시작 중...")
576
-
577
- # API 키 상태 확인
578
- try:
579
- validate_api_keys()
580
- print("✅ 모든 API 키가 설정되어 애플리케이션을 시작합니다.")
581
- except ValueError as e:
582
- print(f"⚠️ 경고: {e}")
583
- print("일부 기능이 제한될 수 있습니다.")
584
-
585
- demo.launch(
586
- server_name="0.0.0.0",
587
- server_port=7860,
588
- share=False,
589
- debug=True
590
- )
 
1
  import gradio as gr
2
+ import numpy as np
3
+ import spaces
4
+ import torch
5
+ import random
6
  from PIL import Image
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
+ from diffusers import FluxKontextPipeline
9
+ from diffusers.utils import load_image
 
 
10
 
11
+ MAX_SEED = np.iinfo(np.int32).max
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ pipe = FluxKontextPipeline.from_pretrained("black-forest-labs/FLUX.1-Kontext-dev", torch_dtype=torch.bfloat16).to("cuda")
 
 
 
 
 
 
 
14
 
15
+ @spaces.GPU
16
+ def infer(input_image, prompt, seed=42, randomize_seed=False, guidance_scale=2.5, steps=28, progress=gr.Progress(track_tqdm=True)):
17
+ """
18
+ Perform image editing using the FLUX.1 Kontext pipeline.
19
+
20
+ This function takes an input image and a text prompt to generate a modified version
21
+ of the image based on the provided instructions. It uses the FLUX.1 Kontext model
22
+ for contextual image editing tasks.
23
+
24
+ Args:
25
+ input_image (PIL.Image.Image): The input image to be edited. Will be converted
26
+ to RGB format if not already in that format.
27
+ prompt (str): Text description of the desired edit to apply to the image.
28
+ Examples: "Remove glasses", "Add a hat", "Change background to beach".
29
+ seed (int, optional): Random seed for reproducible generation. Defaults to 42.
30
+ Must be between 0 and MAX_SEED (2^31 - 1).
31
+ randomize_seed (bool, optional): If True, generates a random seed instead of
32
+ using the provided seed value. Defaults to False.
33
+ guidance_scale (float, optional): Controls how closely the model follows the
34
+ prompt. Higher values mean stronger adherence to the prompt but may reduce
35
+ image quality. Range: 1.0-10.0. Defaults to 2.5.
36
+ steps (int, optional): Controls how many steps to run the diffusion model for.
37
+ Range: 1-30. Defaults to 28.
38
+ progress (gr.Progress, optional): Gradio progress tracker for monitoring
39
+ generation progress. Defaults to gr.Progress(track_tqdm=True).
40
+
41
+ Returns:
42
+ tuple: A 3-tuple containing:
43
+ - PIL.Image.Image: The generated/edited image
44
+ - int: The seed value used for generation (useful when randomize_seed=True)
45
+ - gr.update: Gradio update object to make the reuse button visible
46
+
47
+ Example:
48
+ >>> edited_image, used_seed, button_update = infer(
49
+ ... input_image=my_image,
50
+ ... prompt="Add sunglasses",
51
+ ... seed=123,
52
+ ... randomize_seed=False,
53
+ ... guidance_scale=2.5
54
+ ... )
55
+ """
56
+ if randomize_seed:
57
+ seed = random.randint(0, MAX_SEED)
58
+
59
+ if input_image:
60
+ input_image = input_image.convert("RGB")
61
+ image = pipe(
62
+ image=input_image,
63
+ prompt=prompt,
64
+ guidance_scale=guidance_scale,
65
+ width = input_image.size[0],
66
+ height = input_image.size[1],
67
+ num_inference_steps=steps,
68
+ generator=torch.Generator().manual_seed(seed),
69
+ ).images[0]
70
+ else:
71
+ image = pipe(
72
+ prompt=prompt,
73
+ guidance_scale=guidance_scale,
74
+ num_inference_steps=steps,
75
+ generator=torch.Generator().manual_seed(seed),
76
+ ).images[0]
77
+ return image, seed, gr.Button(visible=True)
78
 
79
+ @spaces.GPU
80
+ def infer_example(input_image, prompt):
81
+ image, seed, _ = infer(input_image, prompt)
82
+ return image, seed
83
+
84
+ css="""
85
+ #col-container {
86
+ margin: 0 auto;
87
+ max-width: 960px;
88
+ }
89
+ """
90
+
91
+ with gr.Blocks(css=css) as demo:
92
+
93
+ with gr.Column(elem_id="col-container"):
94
+ gr.Markdown(f"""# FLUX.1 Kontext [dev]
95
+ Image editing and manipulation model guidance-distilled from FLUX.1 Kontext [pro], [[blog]](https://bfl.ai/announcements/flux-1-kontext-dev) [[model]](https://huggingface.co/black-forest-labs/FLUX.1-Kontext-dev)
96
+ """)
97
+ with gr.Row():
98
+ with gr.Column():
99
+ input_image = gr.Image(label="Upload the image for editing", type="pil")
100
+ with gr.Row():
101
+ prompt = gr.Text(
102
+ label="Prompt",
103
+ show_label=False,
104
+ max_lines=1,
105
+ placeholder="Enter your prompt for editing (e.g., 'Remove glasses', 'Add a hat')",
106
+ container=False,
107
+ )
108
+ run_button = gr.Button("Run", scale=0)
109
+ with gr.Accordion("Advanced Settings", open=False):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
+ seed = gr.Slider(
112
+ label="Seed",
113
+ minimum=0,
114
+ maximum=MAX_SEED,
115
+ step=1,
116
+ value=0,
117
+ )
118
 
119
+ randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
120
 
121
+ guidance_scale = gr.Slider(
122
+ label="Guidance Scale",
123
+ minimum=1,
124
+ maximum=10,
125
+ step=0.1,
126
+ value=2.5,
127
+ )
128
 
129
+ steps = gr.Slider(
130
+ label="Steps",
131
+ minimum=1,
132
+ maximum=30,
133
+ value=28,
134
+ step=1
135
+ )
136
 
137
+ with gr.Column():
138
+ result = gr.Image(label="Result", show_label=False, interactive=False)
139
+ reuse_button = gr.Button("Reuse this image", visible=False)
140
+
141
+
142
+ examples = gr.Examples(
143
+ examples=[
144
+ ["flowers.png", "turn the flowers into sunflowers"],
145
+ ["monster.png", "make this monster ride a skateboard on the beach"],
146
+ ["cat.png", "make this cat happy"]
147
+ ],
148
+ inputs=[input_image, prompt],
149
+ outputs=[result, seed],
150
+ fn=infer_example,
151
+ cache_examples="lazy"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  )
153
+
154
+ gr.on(
155
+ triggers=[run_button.click, prompt.submit],
156
+ fn = infer,
157
+ inputs = [input_image, prompt, seed, randomize_seed, guidance_scale, steps],
158
+ outputs = [result, seed, reuse_button]
 
 
 
 
 
 
159
  )
160
+ reuse_button.click(
161
+ fn = lambda image: image,
162
+ inputs = [result],
163
+ outputs = [input_image]
 
 
164
  )
165
 
166
+ demo.launch(mcp_server=True)