|
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 = {} |
|
|
|
|
|
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_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}") |
|
|
|
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%") |