ssboost commited on
Commit
80316eb
ยท
verified ยท
1 Parent(s): c37afe3

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +313 -0
app.py ADDED
@@ -0,0 +1,313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import replicate
3
+ import os
4
+ import requests
5
+ from PIL import Image, ImageDraw, ImageFilter, ImageEnhance
6
+ import io
7
+ import base64
8
+ import tempfile
9
+ import numpy as np
10
+
11
+ # Replicate API ํ† ํฐ ์„ค์ • (ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ)
12
+ REPLICATE_API_TOKEN = os.getenv("REPLICATE_API_TOKEN")
13
+ if REPLICATE_API_TOKEN:
14
+ os.environ["REPLICATE_API_TOKEN"] = REPLICATE_API_TOKEN
15
+
16
+ def upload_image_to_temp_url(image):
17
+ """์ด๋ฏธ์ง€๋ฅผ ์ž„์‹œ URL๋กœ ์—…๋กœ๋“œํ•˜๋Š” ํ•จ์ˆ˜ (์‹ค์ œ๋กœ๋Š” base64 ์ธ์ฝ”๋”ฉ)"""
18
+ if image is None:
19
+ return None
20
+
21
+ # PIL Image๋ฅผ base64๋กœ ์ธ์ฝ”๋”ฉ
22
+ buffer = io.BytesIO()
23
+ image.save(buffer, format='PNG')
24
+ buffer.seek(0)
25
+
26
+ # ์ž„์‹œ ํŒŒ์ผ๋กœ ์ €์žฅ
27
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmp_file:
28
+ image.save(tmp_file.name, format='PNG')
29
+ return tmp_file.name
30
+
31
+ def create_white_background_shadow(image, shadow_intensity=0.3, shadow_offset=(10, 10)):
32
+ """ํฐ ๋ฐฐ๊ฒฝ์— ๊ทธ๋ฆผ์ž ํšจ๊ณผ ์ถ”๊ฐ€"""
33
+ if image.mode != 'RGBA':
34
+ image = image.convert('RGBA')
35
+
36
+ # ์›๋ณธ ์ด๋ฏธ์ง€ ํฌ๊ธฐ
37
+ width, height = image.size
38
+
39
+ # ๊ทธ๋ฆผ์ž๋ฅผ ์œ„ํ•œ ์—ฌ๋ฐฑ ์ถ”๊ฐ€
40
+ shadow_margin = max(shadow_offset) + 20
41
+ new_width = width + shadow_margin * 2
42
+ new_height = height + shadow_margin * 2
43
+
44
+ # ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ ์ƒ์„ฑ
45
+ result = Image.new('RGBA', (new_width, new_height), (255, 255, 255, 255))
46
+
47
+ # ๊ทธ๋ฆผ์ž ์ƒ์„ฑ
48
+ shadow = Image.new('RGBA', (new_width, new_height), (0, 0, 0, 0))
49
+ shadow_draw = ImageDraw.Draw(shadow)
50
+
51
+ # ์›๋ณธ ์ด๋ฏธ์ง€์˜ ์•ŒํŒŒ ์ฑ„๋„์„ ์ด์šฉํ•ด ๊ทธ๋ฆผ์ž ๋งˆ์Šคํฌ ์ƒ์„ฑ
52
+ alpha = image.split()[-1]
53
+ shadow_pos = (shadow_margin + shadow_offset[0], shadow_margin + shadow_offset[1])
54
+ shadow.paste(Image.new('RGBA', image.size, (0, 0, 0, int(255 * shadow_intensity))), shadow_pos, alpha)
55
+
56
+ # ๊ทธ๋ฆผ์ž ๋ธ”๋Ÿฌ ํšจ๊ณผ
57
+ shadow = shadow.filter(ImageFilter.GaussianBlur(radius=8))
58
+
59
+ # ๊ทธ๋ฆผ์ž๋ฅผ ๋ฐฐ๊ฒฝ์— ํ•ฉ์„ฑ
60
+ result = Image.alpha_composite(result, shadow)
61
+
62
+ # ์›๋ณธ ์ด๋ฏธ์ง€๋ฅผ ์ค‘์•™์— ๋ฐฐ์น˜
63
+ image_pos = (shadow_margin, shadow_margin)
64
+ result.paste(image, image_pos, image)
65
+
66
+ return result.convert('RGB')
67
+
68
+ def enhance_image_quality(image):
69
+ """์ด๋ฏธ์ง€ ํ™”์งˆ ํ–ฅ์ƒ"""
70
+ # ์„ ๋ช…๋„ ํ–ฅ์ƒ
71
+ enhancer = ImageEnhance.Sharpness(image)
72
+ image = enhancer.enhance(1.2)
73
+
74
+ # ๋Œ€๋น„ ํ–ฅ์ƒ
75
+ enhancer = ImageEnhance.Contrast(image)
76
+ image = enhancer.enhance(1.1)
77
+
78
+ # ์ƒ‰์ƒ ์ฑ„๋„ ํ–ฅ์ƒ
79
+ enhancer = ImageEnhance.Color(image)
80
+ image = enhancer.enhance(1.05)
81
+
82
+ return image
83
+
84
+ def rotate_image_slightly(image, angle=5):
85
+ """์ด๋ฏธ์ง€๋ฅผ ์•ฝ๊ฐ„ ํšŒ์ „์‹œ์ผœ ์•„์ดํ…œ ์œ„๋„ˆ ํšŒํ”ผ"""
86
+ return image.rotate(angle, expand=True, fillcolor=(255, 255, 255))
87
+
88
+ def check_white_background_ratio(image, threshold=0.85):
89
+ """ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ ๋น„์œจ ํ™•์ธ (85% ๊ทœ์น™)"""
90
+ if image.mode != 'RGB':
91
+ image = image.convert('RGB')
92
+
93
+ pixels = np.array(image)
94
+ white_pixels = np.sum(np.all(pixels >= [240, 240, 240], axis=2))
95
+ total_pixels = pixels.shape[0] * pixels.shape[1]
96
+ white_ratio = white_pixels / total_pixels
97
+
98
+ return white_ratio >= threshold, white_ratio
99
+
100
+ def process_with_ai_enhancement(image_path, prompt_option):
101
+ """AI๋ฅผ ์ด์šฉํ•œ ์ด๋ฏธ์ง€ ํ’ˆ์งˆ ํ–ฅ์ƒ"""
102
+ if not REPLICATE_API_TOKEN:
103
+ return None, "Replicate API ํ† ํฐ์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
104
+
105
+ try:
106
+ # ํ”„๋กฌํ”„ํŠธ ์˜ต์…˜๋ณ„ ์„ค์ •
107
+ prompts = {
108
+ "๊ธฐ๋ณธ ํ’ˆ์งˆํ–ฅ์ƒ": "Enhance image quality, make it more professional and crisp, white background",
109
+ "์ œํ’ˆ ์‚ฌ์ง„ ์ตœ์ ํ™”": "Transform into professional product photography, clean white background, high quality, studio lighting",
110
+ "์ฟ ํŒก ์Šคํƒ€์ผ": "Make it look like a professional e-commerce product photo for Korean online shopping, clean white background, perfect lighting",
111
+ "ํ”„๋ฆฌ๋ฏธ์—„ ๋А๋‚Œ": "Make it look premium and luxury, professional product shot, perfect white background, studio quality"
112
+ }
113
+
114
+ selected_prompt = prompts.get(prompt_option, prompts["๊ธฐ๋ณธ ํ’ˆ์งˆํ–ฅ์ƒ"])
115
+
116
+ # Replicate API ํ˜ธ์ถœ
117
+ output = replicate.run(
118
+ "black-forest-labs/flux-kontext-pro",
119
+ input={
120
+ "prompt": selected_prompt,
121
+ "input_image": open(image_path, "rb"),
122
+ "output_format": "jpg"
123
+ }
124
+ )
125
+
126
+ # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ
127
+ response = requests.get(output)
128
+ if response.status_code == 200:
129
+ enhanced_image = Image.open(io.BytesIO(response.content))
130
+ return enhanced_image, "AI ํ–ฅ์ƒ ์™„๋ฃŒ"
131
+ else:
132
+ return None, "AI ์ฒ˜๋ฆฌ ์‹คํŒจ"
133
+
134
+ except Exception as e:
135
+ return None, f"AI ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜: {str(e)}"
136
+
137
+ def create_coupang_thumbnail(image, apply_shadow, apply_rotation, rotation_angle, ai_enhancement, prompt_option):
138
+ """์ฟ ํŒก ์ธ๋„ค์ผ ์ƒ์„ฑ ๋ฉ”์ธ ํ•จ์ˆ˜"""
139
+ if image is None:
140
+ return None, "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”."
141
+
142
+ try:
143
+ result_image = image.copy()
144
+ status_messages = []
145
+
146
+ # 1๋‹จ๊ณ„: AI ํ–ฅ์ƒ (์„ ํƒ์‚ฌํ•ญ)
147
+ if ai_enhancement and REPLICATE_API_TOKEN:
148
+ temp_path = upload_image_to_temp_url(result_image)
149
+ if temp_path:
150
+ enhanced_img, ai_msg = process_with_ai_enhancement(temp_path, prompt_option)
151
+ if enhanced_img:
152
+ result_image = enhanced_img
153
+ status_messages.append(f"โœ… AI ํ–ฅ์ƒ: {ai_msg}")
154
+ else:
155
+ status_messages.append(f"โŒ AI ํ–ฅ์ƒ: {ai_msg}")
156
+ os.unlink(temp_path) # ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ
157
+
158
+ # 2๋‹จ๊ณ„: ํ™”์งˆ ํ–ฅ์ƒ
159
+ result_image = enhance_image_quality(result_image)
160
+ status_messages.append("โœ… ํ™”์งˆ ํ–ฅ์ƒ ์™„๋ฃŒ")
161
+
162
+ # 3๋‹จ๊ณ„: ๊ฐ๋„ ๋ณ€๊ฒฝ (์•„์ดํ…œ ์œ„๋„ˆ ํšŒํ”ผ)
163
+ if apply_rotation:
164
+ result_image = rotate_image_slightly(result_image, rotation_angle)
165
+ status_messages.append(f"โœ… ๊ฐ๋„ ๋ณ€๊ฒฝ: {rotation_angle}๋„ ํšŒ์ „")
166
+
167
+ # 4๋‹จ๊ณ„: ํฐ ๋ฐฐ๊ฒฝ + ๊ทธ๋ฆผ์ž ํšจ๊ณผ
168
+ if apply_shadow:
169
+ result_image = create_white_background_shadow(result_image)
170
+ status_messages.append("โœ… ๊ทธ๋ฆผ์ž ํšจ๊ณผ ์ ์šฉ")
171
+ else:
172
+ # ๊ทธ๋ฆผ์ž ์—†์ด ํฐ ๋ฐฐ๊ฒฝ๋งŒ ์ ์šฉ
173
+ if result_image.mode == 'RGBA':
174
+ white_bg = Image.new('RGB', result_image.size, (255, 255, 255))
175
+ white_bg.paste(result_image, mask=result_image.split()[-1] if result_image.mode == 'RGBA' else None)
176
+ result_image = white_bg
177
+
178
+ # 85% ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ ๊ทœ์น™ ํ™•์ธ
179
+ is_compliant, white_ratio = check_white_background_ratio(result_image)
180
+ if is_compliant:
181
+ status_messages.append(f"โœ… 85% ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ ๊ทœ์น™ ์ค€์ˆ˜ ({white_ratio:.1%})")
182
+ else:
183
+ status_messages.append(f"โš ๏ธ ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ ๋น„์œจ ๋ถ€์กฑ ({white_ratio:.1%}) - 85% ๋ฏธ๋งŒ")
184
+
185
+ # ์ตœ์ข… ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์กฐ์ • (์ฟ ํŒก ๊ถŒ์žฅ ์‚ฌ์ด์ฆˆ)
186
+ result_image = result_image.resize((1000, 1000), Image.Resampling.LANCZOS)
187
+ status_messages.append("โœ… 1000x1000 ํฌ๊ธฐ๋กœ ์กฐ์ • ์™„๋ฃŒ")
188
+
189
+ return result_image, "\n".join(status_messages)
190
+
191
+ except Exception as e:
192
+ return None, f"์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
193
+
194
+ # Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ
195
+ def create_interface():
196
+ with gr.Blocks(title="๐Ÿ›’ ์ฟ ํŒก ์ธ๋„ค์ผ ์ƒ์„ฑ๊ธฐ", theme=gr.themes.Soft()) as iface:
197
+ gr.Markdown("""
198
+ # ๐Ÿ›’ ์ฟ ํŒก ์ธ๋„ค์ผ ์ƒ์„ฑ๊ธฐ
199
+
200
+ ๋ฐฐ๊ฒฝ์ด ์ œ๊ฑฐ๋œ ์ƒํ’ˆ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๋ฉด ์ฟ ํŒก ๊ทœ์ •์— ๋งž๋Š” ์ธ๋„ค์ผ๋กœ ์ž๋™ ๋ณ€ํ™˜ํ•ด๋“œ๋ฆฝ๋‹ˆ๋‹ค!
201
+
202
+ ## ๐Ÿ’ก ์ ์šฉ๋˜๋Š” ์ฟ ํŒก ๊ทœ์ • ๋Œ€์‘ ์ „๋žต:
203
+ - โœ… **ํฐ ๋ฐฐ๊ฒฝ + 85% ๊ทœ์น™** ์™„๋ฒฝ ์ค€์ˆ˜
204
+ - โœ… **AI ๋งˆ๋ฒ• ํ”„๋กฌํ”„ํŠธ**๋กœ ํ™”์งˆ ํ–ฅ์ƒ
205
+ - โœ… **๊ฐ๋„ ๋ณ€๊ฒฝ**์œผ๋กœ ์•„์ดํ…œ ์œ„๋„ˆ ํšŒํ”ผ
206
+ - โœ… **๊ทธ๋ฆผ์ž ํšจ๊ณผ**๋กœ ์ž์—ฐ์Šค๋Ÿฌ์šด ์ž…์ฒด๊ฐ ์—ฐ์ถœ
207
+ """)
208
+
209
+ with gr.Row():
210
+ with gr.Column(scale=1):
211
+ gr.Markdown("### ๐Ÿ“ค ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ")
212
+ input_image = gr.Image(
213
+ label="๋ฐฐ๊ฒฝ ์ œ๊ฑฐ๋œ ์ƒํ’ˆ ์ด๋ฏธ์ง€",
214
+ type="pil",
215
+ height=300
216
+ )
217
+
218
+ gr.Markdown("### โš™๏ธ ์˜ต์…˜ ์„ค์ •")
219
+
220
+ with gr.Group():
221
+ gr.Markdown("**๐ŸŽจ ํšจ๊ณผ ์˜ต์…˜**")
222
+ apply_shadow = gr.Checkbox(
223
+ label="๊ทธ๋ฆผ์ž ํšจ๊ณผ ์ ์šฉ",
224
+ value=True,
225
+ info="์ž์—ฐ์Šค๋Ÿฌ์šด ์ž…์ฒด๊ฐ ์—ฐ์ถœ"
226
+ )
227
+ apply_rotation = gr.Checkbox(
228
+ label="๊ฐ๋„ ๋ณ€๊ฒฝ ์ ์šฉ",
229
+ value=True,
230
+ info="์•„์ดํ…œ ์œ„๋„ˆ ํšŒํ”ผ"
231
+ )
232
+ rotation_angle = gr.Slider(
233
+ minimum=-15,
234
+ maximum=15,
235
+ value=5,
236
+ step=1,
237
+ label="ํšŒ์ „ ๊ฐ๋„",
238
+ visible=True
239
+ )
240
+
241
+ with gr.Group():
242
+ gr.Markdown("**๐Ÿค– AI ํ–ฅ์ƒ (Replicate API ํ•„์š”)**")
243
+ ai_enhancement = gr.Checkbox(
244
+ label="AI ํ’ˆ์งˆ ํ–ฅ์ƒ ์‚ฌ์šฉ",
245
+ value=False,
246
+ info="Replicate API ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค"
247
+ )
248
+ prompt_option = gr.Dropdown(
249
+ choices=["๊ธฐ๋ณธ ํ’ˆ์งˆํ–ฅ์ƒ", "์ œํ’ˆ ์‚ฌ์ง„ ์ตœ์ ํ™”", "์ฟ ํŒก ์Šคํƒ€์ผ", "ํ”„๋ฆฌ๋ฏธ์—„ ๋А๋‚Œ"],
250
+ value="์ฟ ํŒก ์Šคํƒ€์ผ",
251
+ label="AI ํ–ฅ์ƒ ์Šคํƒ€์ผ"
252
+ )
253
+
254
+ process_btn = gr.Button("๐Ÿš€ ์ฟ ํŒก ์ธ๋„ค์ผ ์ƒ์„ฑ", variant="primary", size="lg")
255
+
256
+ with gr.Column(scale=1):
257
+ gr.Markdown("### ๐Ÿ“ฅ ๊ฒฐ๊ณผ")
258
+ output_image = gr.Image(
259
+ label="์ฟ ํŒก ๊ทœ์ • ์ธ๋„ค์ผ",
260
+ height=400
261
+ )
262
+ status_output = gr.Textbox(
263
+ label="์ฒ˜๋ฆฌ ์ƒํƒœ",
264
+ lines=8,
265
+ max_lines=15
266
+ )
267
+
268
+ # ์‚ฌ์šฉ ์˜ˆ์‹œ
269
+ gr.Markdown("""
270
+ ---
271
+ ## ๐Ÿ“‹ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
272
+ 1. **๋ฐฐ๊ฒฝ์ด ์ œ๊ฑฐ๋œ ์ƒํ’ˆ ์ด๋ฏธ์ง€**๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”
273
+ 2. ์›ํ•˜๋Š” **ํšจ๊ณผ ์˜ต์…˜**์„ ์„ ํƒํ•˜์„ธ์š”
274
+ 3. **AI ํ–ฅ์ƒ**์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Replicate API ํ† ํฐ์„ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ์„ค์ •ํ•˜์„ธ์š”
275
+ 4. **'์ฟ ํŒก ์ธ๋„ค์ผ ์ƒ์„ฑ'** ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”
276
+
277
+ ## ๐Ÿ”‘ API ์„ค์ • (์„ ํƒ์‚ฌํ•ญ)
278
+ AI ํ–ฅ์ƒ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”:
279
+ ```bash
280
+ export REPLICATE_API_TOKEN=your_token_here
281
+ ```
282
+ """)
283
+
284
+ # ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
285
+ def update_rotation_visibility(apply_rot):
286
+ return gr.update(visible=apply_rot)
287
+
288
+ apply_rotation.change(update_rotation_visibility, apply_rotation, rotation_angle)
289
+
290
+ process_btn.click(
291
+ create_coupang_thumbnail,
292
+ inputs=[
293
+ input_image,
294
+ apply_shadow,
295
+ apply_rotation,
296
+ rotation_angle,
297
+ ai_enhancement,
298
+ prompt_option
299
+ ],
300
+ outputs=[output_image, status_output]
301
+ )
302
+
303
+ return iface
304
+
305
+ # ์•ฑ ์‹คํ–‰
306
+ if __name__ == "__main__":
307
+ app = create_interface()
308
+ app.launch(
309
+ server_name="0.0.0.0",
310
+ server_port=7860,
311
+ share=True,
312
+ show_error=True
313
+ )