asdfewef / app.py
ssboost's picture
Update app.py
42fde2a verified
raw
history blame
16.1 kB
import os
import logging
import json
import time
from PIL import Image
import gradio as gr
from dotenv import load_dotenv
load_dotenv()
# ------------------- ๋กœ๊น… ์„ค์ • -------------------
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# ------------------- ๋ฐฐ๊ฒฝ ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ • -------------------
BACKGROUNDS_DIR = "./background"
if not os.path.exists(BACKGROUNDS_DIR):
os.makedirs(BACKGROUNDS_DIR)
logger.info(f"๋ฐฐ๊ฒฝ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค: {BACKGROUNDS_DIR}")
# ์˜ˆ์‹œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
from db_examples import product_background_examples
# ------------------- ์ „์—ญ ๋ณ€์ˆ˜ ์„ค์ • -------------------
IMAGE_CACHE = {} # ์ด๋ฏธ์ง€ ์บ์‹œ ์ถ”๊ฐ€
# ์ปค์Šคํ…€ CSS ์Šคํƒ€์ผ
custom_css = """
:root {
--primary-color: #FB7F0D;
--secondary-color: #ff9a8b;
--accent-color: #FF6B6B;
--background-color: #FFFFFF; /* ๋ฒ ์ด์ง€์ƒ‰(#FFF3E9)์—์„œ ์™„์ „ํ•œ ํฐ์ƒ‰(#FFFFFF)์œผ๋กœ ๋ณ€๊ฒฝ */
--card-bg: #ffffff;
--text-color: #334155;
--border-radius: 18px;
--shadow: 0 8px 30px rgba(251, 127, 13, 0.08);
}
/* ์˜ˆ์‹œ ๊ฐค๋Ÿฌ๋ฆฌ ์Šคํƒ€์ผ */
.example-gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
padding: 20px;
}
.example-item {
cursor: pointer;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: var(--border-radius);
overflow: hidden;
transition: all 0.3s ease;
background: white;
}
.example-item:hover {
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.example-item img {
width: 100%;
height: 250px;
object-fit: cover;
}
.example-label {
padding: 10px;
text-align: center;
font-weight: bold;
background: rgba(251, 127, 13, 0.1);
}
.example-detail-view {
margin-bottom: 30px;
}
.example-params {
background: white;
padding: 20px;
border-radius: var(--border-radius);
box-shadow: var(--shadow);
}
.example-params p {
margin: 10px 0;
font-size: 16px;
}
.example-params strong {
color: var(--primary-color);
}
/* ์„ ํƒ๋œ ์˜ˆ์‹œ ํ•˜์ด๋ผ์ดํŠธ */
.example-item.selected {
border: 3px solid var(--primary-color);
}
/* โ”€โ”€ ๊ทธ๋ฃน ๋ž˜ํผ ๋ฐฐ๊ฒฝ ์™„์ „ ์ œ๊ฑฐ โ”€โ”€ */
.custom-section-group,
.gr-block.gr-group {
background-color: var(--background-color) !important; /* ๋ณ€๊ฒฝ๋œ ๋ฐฐ๊ฒฝ์ƒ‰ ๋ณ€์ˆ˜ ์‚ฌ์šฉ */
box-shadow: none !important;
}
.custom-section-group::before,
.custom-section-group::after,
.gr-block.gr-group::before,
.gr-block.gr-group::after {
display: none !important;
content: none !important;
}
/* ๊ทธ๋ฃน ์ปจํ…Œ์ด๋„ˆ ๋ฐฐ๊ฒฝ์„ ์•„์ด๋ณด๋ฆฌ๋กœ, ๊ทธ๋ฆผ์ž ์ œ๊ฑฐ */
.custom-section-group {
background-color: var(--background-color) !important; /* ๋ณ€๊ฒฝ๋œ ๋ฐฐ๊ฒฝ์ƒ‰ ๋ณ€์ˆ˜ ์‚ฌ์šฉ */
box-shadow: none !important;
}
/* ์ƒ๋‹จยทํ•˜๋‹จ์— ๊ทธ๋ ค์ง€๋Š” ํšŒ์ƒ‰ ์บก(๋‘ฅ๊ทผ ๋ชจ์„œ๋ฆฌ) ์ œ๊ฑฐ */
.custom-section-group::before,
.custom-section-group::after {
display: none !important;
content: none !important;
}
body {
font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: var(--background-color); /* ๋ณ€๊ฒฝ๋œ ๋ฐฐ๊ฒฝ์ƒ‰ ๋ณ€์ˆ˜ ์‚ฌ์šฉ */
color: var(--text-color);
line-height: 1.6;
margin: 0;
padding: 0;
}
/* ํ‘ธํ„ฐ ์ˆจ๊น€ ์„ค์ • ์ถ”๊ฐ€ */
footer {
visibility: hidden;
}
.gradio-container {
width: 100%; /* ์ „์ฒด ๋„ˆ๋น„ 100% ๊ณ ์ • */
margin: 0 auto;
padding: 20px;
background-color: var(--background-color); /* ๋ณ€๊ฒฝ๋œ ๋ฐฐ๊ฒฝ์ƒ‰ ๋ณ€์ˆ˜ ์‚ฌ์šฉ */
}
/* ์ฝ˜ํ…์ธ  ๋ฐ•์Šค (ํ”„๋ ˆ์ž„) ์Šคํƒ€์ผ */
.custom-frame {
background-color: var(--card-bg);
border: 1px solid rgba(0, 0, 0, 0.04);
border-radius: var(--border-radius);
padding: 20px;
margin: 10px 0;
box-shadow: var(--shadow);
}
/* ์„น์…˜ ๊ทธ๋ฃน ์Šคํƒ€์ผ - ํšŒ์ƒ‰ ๋ฐฐ๊ฒฝ ์™„์ „ ์ œ๊ฑฐ */
.custom-section-group {
margin-top: 20px;
padding: 0;
border: none;
border-radius: 0;
background-color: var(--background-color); /* ํšŒ์ƒ‰ โ†’ ์•„์ด๋ณด๋ฆฌ(์ „์ฒด ๋ฐฐ๊ฒฝ์ƒ‰) */
box-shadow: none !important; /* ํ˜น์‹œ ๋‚จ์•„์žˆ๋Š” ๊ทธ๋ฆผ์ž๋„ ๊ฐ™์ด ์ œ๊ฑฐ */
}
/* ๋ฒ„ํŠผ ์Šคํƒ€์ผ - ๊ธ€์ž ํฌ๊ธฐ 18px */
.custom-button {
border-radius: 30px !important;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;
color: white !important;
font-size: 18px !important;
padding: 10px 20px !important;
border: none;
box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25);
transition: transform 0.3s ease;
}
.custom-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3);
}
/* ์ œ๋ชฉ ์Šคํƒ€์ผ (๋ชจ๋“  ํ•ญ๋ชฉ๋ช…์ด ๋™์ผํ•˜๊ฒŒ custom-title ํด๋ž˜์Šค๋กœ) */
.custom-title {
font-size: 28px;
font-weight: bold;
margin-bottom: 10px;
color: var(--text-color);
border-bottom: 2px solid var(--primary-color);
padding-bottom: 5px;
}
/* ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ */
.image-container {
border-radius: var(--border-radius);
overflow: hidden;
border: 1px solid rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
background-color: white;
aspect-ratio: 1 / 1; /* ์ •์‚ฌ๊ฐํ˜• ๋น„์œจ ๊ฐ•์ œ */
}
.image-container:hover {
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.image-container img {
width: 100%;
height: 100%;
object-fit: contain; /* ์ด๋ฏธ์ง€ ๋น„์œจ ์œ ์ง€ํ•˜๋ฉด์„œ ์ปจํ…Œ์ด๋„ˆ์— ๋งž์ถค */
}
/* ์ž…๋ ฅ ํ•„๋“œ ์Šคํƒ€์ผ */
.gr-input, .gr-text-input, .gr-sample-inputs {
border-radius: var(--border-radius) !important;
border: 1px solid #dddddd !important;
padding: 12px !important;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important;
transition: all 0.3s ease !important;
}
.gr-input:focus, .gr-text-input:focus {
border-color: var(--primary-color) !important;
outline: none !important;
box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
}
/* ๋ฉ”์ธ ์ปจํ…์ธ  ์Šคํฌ๋กค๋ฐ” */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: var(--primary-color);
border-radius: 10px;
}
/* ์• ๋‹ˆ๋ฉ”์ด์…˜ ์Šคํƒ€์ผ */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
/* ๋ฐ˜์‘ํ˜• */
@media (max-width: 768px) {
.button-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* ์„น์…˜ ์ œ๋ชฉ ์Šคํƒ€์ผ - ์ฐธ์กฐ์ฝ”๋“œ ์Šคํƒ€์ผ๊ณผ ๋™์ผํ•˜๊ฒŒ ์ ์šฉ */
.section-title {
display: flex;
align-items: center;
font-size: 20px;
font-weight: 700;
color: #333333;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 2px solid #FB7F0D;
font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
}
.section-title img {
margin-right: 10px;
width: 24px;
height: 24px;
}
"""
# FontAwesome ์•„์ด์ฝ˜ ํฌํ•จ
fontawesome_link = """
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
"""
# ------------------- ์˜ˆ์‹œ ํƒญ์„ ์œ„ํ•œ ํ•จ์ˆ˜ -------------------
def load_image_cached(image_path):
"""์ด๋ฏธ์ง€๋ฅผ ์บ์‹œํ•˜์—ฌ ๋กœ๋“œํ•˜๋Š” ํ•จ์ˆ˜"""
global IMAGE_CACHE
if image_path not in IMAGE_CACHE:
try:
img = Image.open(image_path)
# ํฐ ์ด๋ฏธ์ง€๋Š” ๋ฏธ๋ฆฌ ๋ฆฌ์‚ฌ์ด์ฆˆํ•˜์—ฌ ์บ์‹œ
if max(img.size) > 1000:
ratio = 1000 / max(img.size)
new_size = (int(img.size[0] * ratio), int(img.size[1] * ratio))
img = img.resize(new_size, Image.Resampling.LANCZOS)
IMAGE_CACHE[image_path] = img
except Exception as e:
logger.error(f"์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹คํŒจ: {image_path}, ์—๋Ÿฌ: {e}")
return None
return IMAGE_CACHE[image_path]
def preload_example_images():
"""์˜ˆ์‹œ ์ด๋ฏธ์ง€๋“ค์„ ๋ฏธ๋ฆฌ ๋กœ๋“œํ•˜๋Š” ํ•จ์ˆ˜"""
for example in product_background_examples:
load_image_cached(example[0]) # ์ž…๋ ฅ ์ด๋ฏธ์ง€
load_image_cached(example[5]) # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€
def create_app():
with gr.Blocks(css=custom_css, theme=gr.themes.Default(
primary_hue="orange",
secondary_hue="orange",
font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"]
)) as demo:
gr.HTML(fontawesome_link)
# ์ƒํ’ˆ ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์˜ˆ์‹œ ๊ฐค๋Ÿฌ๋ฆฌ ์„น์…˜
with gr.Column(elem_classes="custom-frame"):
gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/681/681443.png"> ์ƒํ’ˆ ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์˜ˆ์‹œ ๊ฐค๋Ÿฌ๋ฆฌ</div>')
# ์ƒ๋‹จ ๋ฉ”์ธ ๋ทฐ ์˜์—ญ
with gr.Row():
example_input_image = gr.Image(
label="์ž…๋ ฅ ์ด๋ฏธ์ง€",
height=400,
width=400,
elem_classes="image-container",
show_label=True,
show_download_button=True,
container=True,
scale=1
)
with gr.Column(elem_classes="example-params"):
example_bg_type = gr.Textbox(label="๋ฐฐ๊ฒฝ ์œ ํ˜•", interactive=False)
example_bg_option = gr.Textbox(label="๋ฐฐ๊ฒฝ ์„ ํƒ", interactive=False)
example_product_name = gr.Textbox(label="์ƒํ’ˆ๋ช…", interactive=False)
example_additional_info = gr.Textbox(label="์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ", interactive=False)
example_output_image = gr.Image(
label="๊ฒฐ๊ณผ ์ด๋ฏธ์ง€",
height=400,
width=400,
elem_classes="image-container",
show_label=True,
show_download_button=True,
container=True,
scale=1
)
# ๋ฐฐ๊ฒฝ ์œ ํ˜•๋ณ„ ๊ฐค๋Ÿฌ๋ฆฌ ์„น์…˜
with gr.Column(elem_classes="custom-frame"):
gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/3068/3068327.png"> ๋ฐฐ๊ฒฝ ์œ ํ˜•๋ณ„ ์˜ˆ์‹œ</div>')
# ์˜ˆ์‹œ๋“ค์„ ๋ฐฐ๊ฒฝ ์œ ํ˜•๋ณ„๋กœ ๊ทธ๋ฃนํ™”
examples_by_type = {}
for example in product_background_examples:
bg_type = example[1] # ๋ฐฐ๊ฒฝ ์œ ํ˜•
if bg_type not in examples_by_type:
examples_by_type[bg_type] = []
examples_by_type[bg_type].append(example)
# ๋ฐฐ๊ฒฝ ์œ ํ˜•๋ณ„ ๊ฐค๋Ÿฌ๋ฆฌ ์„น์…˜ ์ƒ์„ฑ
for bg_type, examples in examples_by_type.items():
gr.Markdown(f"### {bg_type}")
# 10๊ฐœ์”ฉ ํ•œ ์ค„๋กœ ํ‘œ์‹œ
for i in range(0, len(examples), 10):
with gr.Row():
for j in range(10):
if i + j < len(examples):
example = examples[i + j]
with gr.Column(scale=1, min_width=100):
# ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€๋งŒ ํ‘œ์‹œ
display_img = gr.Image(
value=example[5],
label=None, # ๋ ˆ์ด๋ธ” ์ œ๊ฑฐ
elem_classes="example-item",
height=120, # ํฌ๊ธฐ ์ถ•์†Œ
width=120, # ํฌ๊ธฐ ์ถ•์†Œ
show_label=False, # ๋ ˆ์ด๋ธ” ์ˆจ๊ธฐ๊ธฐ
show_download_button=False, # ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ์ˆจ๊ธฐ๊ธฐ
show_share_button=False, # ๊ณต์œ  ๋ฒ„ํŠผ ์ˆจ๊ธฐ๊ธฐ
container=False # ์ปจํ…Œ์ด๋„ˆ ํ…Œ๋‘๋ฆฌ ์ œ๊ฑฐ
)
def make_example_handler(ex):
def handler():
# ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ๋ฅผ ์œ„ํ•œ ์ค‘๊ฐ„ ์—…๋ฐ์ดํŠธ
yield (
gr.update(value=None), # ์ž„์‹œ๋กœ ์ด๋ฏธ์ง€ ๋น„์šฐ๊ธฐ
gr.update(value="๋กœ๋”ฉ ์ค‘..."),
gr.update(value="๋กœ๋”ฉ ์ค‘..."),
gr.update(value="๋กœ๋”ฉ ์ค‘..."),
gr.update(value="๋กœ๋”ฉ ์ค‘..."),
gr.update(value=None)
)
# ์‹ค์ œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
yield (
ex[0], # ์ž…๋ ฅ ์ด๋ฏธ์ง€
ex[1], # ๋ฐฐ๊ฒฝ ์œ ํ˜•
ex[2], # ๋ฐฐ๊ฒฝ ์„ ํƒ
ex[3], # ์ƒํ’ˆ๋ช…
ex[4] if ex[4] else "(์—†์Œ)", # ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ
ex[5] # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€
)
return handler
display_img.select(
fn=make_example_handler(example),
outputs=[
example_input_image,
example_bg_type,
example_bg_option,
example_product_name,
example_additional_info,
example_output_image
],
queue=True # ์š”์ฒญ์„ ํ์— ๋„ฃ์–ด ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ
)
else:
# ๋นˆ ๊ณต๊ฐ„ ์œ ์ง€
with gr.Column(scale=1, min_width=100):
pass
# ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ์ฒซ ๋ฒˆ์งธ ์˜ˆ์‹œ ์ž๋™ ํ‘œ์‹œ
def load_first_example():
if product_background_examples:
first_example = product_background_examples[0]
return (
first_example[0], # ์ž…๋ ฅ ์ด๋ฏธ์ง€
first_example[1], # ๋ฐฐ๊ฒฝ ์œ ํ˜•
first_example[2], # ๋ฐฐ๊ฒฝ ์„ ํƒ
first_example[3], # ์ƒํ’ˆ๋ช…
first_example[4] if first_example[4] else "(์—†์Œ)", # ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ
first_example[5] # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€
)
return None, "", "", "", "", None
demo.load(
fn=load_first_example,
outputs=[
example_input_image,
example_bg_type,
example_bg_option,
example_product_name,
example_additional_info,
example_output_image
]
)
return demo
if __name__ == "__main__":
preload_example_images() # ์˜ˆ์‹œ ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ ๋กœ๋“œ
app = create_app()
app.queue(max_size=10) # ์š”์ฒญ์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ ์„ค์ •
app.launch(share=False, inbrowser=True, width="100%")