657 lines
28 KiB
Python
657 lines
28 KiB
Python
# Пути к файлам в контейнере
|
||
import os
|
||
|
||
# Базовый путь для данных
|
||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
|
||
# Путь к файлу для хранения resolutions
|
||
CSV_FILE = os.path.join(BASE_DIR, 'resolutions.csv')
|
||
|
||
# Используем OpenSans шрифт
|
||
FONT_PATH = os.path.join(BASE_DIR, 'opensans.ttf')
|
||
|
||
# Создание папки для галереи
|
||
GALLERY_DIR = os.path.join(BASE_DIR, 'gallery')
|
||
os.makedirs(GALLERY_DIR, exist_ok=True)
|
||
|
||
# Пути к шаблонам
|
||
TEMPLATES = {
|
||
"Еж-тян": os.path.join(BASE_DIR, "ejtan.png"),
|
||
"Ежунья": os.path.join(BASE_DIR, "ejunya.png"),
|
||
"Оливьешка": os.path.join(BASE_DIR, "olive.png"),
|
||
"Опер-тян": os.path.join(BASE_DIR, "opertan.png"),
|
||
"Фортран & Co": os.path.join(BASE_DIR, "fortranco.png"),
|
||
"Фунтик": os.path.join(BASE_DIR, "funtik.png")
|
||
}
|
||
|
||
import streamlit as st
|
||
import pandas as pd
|
||
import random
|
||
from datetime import datetime
|
||
from PIL import Image, ImageDraw, ImageFont
|
||
import shutil
|
||
import base64
|
||
|
||
# Функция для инициализации CSV
|
||
def initialize_csv():
|
||
df = pd.DataFrame(columns=['Resolution', 'Category', 'Mascot', 'Timestamp', 'ImagePath'])
|
||
df.to_csv(CSV_FILE, index=False)
|
||
|
||
# Проверка и инициализация CSV
|
||
if not os.path.exists(CSV_FILE) or os.path.getsize(CSV_FILE) == 0:
|
||
initialize_csv()
|
||
else:
|
||
try:
|
||
pd.read_csv(CSV_FILE)
|
||
except (pd.errors.EmptyDataError, pd.errors.ParserError):
|
||
initialize_csv()
|
||
|
||
# Список категорий и маскотов
|
||
CATEGORIES = ["Новогоднее поздравление", "Ебать ты молодец", "Эпик фейл"]
|
||
MASCOTS = ["Еж-тян", "Ежунья", "Оливьешка", "Опер-тян", "Фортран & Co", "Фунтик"]
|
||
|
||
# Проверка существования файлов шаблонов
|
||
def check_templates():
|
||
missing_templates = []
|
||
for name, path in TEMPLATES.items():
|
||
if not os.path.exists(path):
|
||
missing_templates.append(f"{name}: {path}")
|
||
return missing_templates
|
||
|
||
# Проверка шрифта
|
||
def check_font():
|
||
if not os.path.exists(FONT_PATH):
|
||
return False
|
||
return True
|
||
|
||
# Пул заголовков для каждой категории
|
||
title_pools = {
|
||
"Новогоднее поздравление": [
|
||
"Заверяющий официальное пожелание анона",
|
||
"Подтверждает новогоднее настроение",
|
||
"Вручается в честь наступления очередного витка этой вселенной",
|
||
"Официально разрешает радоваться Новому году",
|
||
"Подтверждает наличие оливьешки",
|
||
"На законное распитие всякого под бой курантов",
|
||
"За сохранение новогоднего духа в условиях треда",
|
||
"Выдан без очереди, потому что новый год",
|
||
"Сертифицирует тёплые пожелания от анона к анону",
|
||
"На выдачу новогоднего настроения в треде"
|
||
],
|
||
"Ебать ты молодец": [
|
||
"Выдаётся охуенному молодцу",
|
||
"Выдаётся по причине охуеть ты молодец",
|
||
"Вручается за ультимативную годноту",
|
||
"Вручается за запредельный уровень молодцовости",
|
||
"Подтверждает факт: ты реально ебать какой молодец",
|
||
"Выдан ебать какому молодцу",
|
||
"За действия, от которых аноны охуели",
|
||
"Официальное признание: охуенно справился",
|
||
"За вклад в общее дело и локальный ахуй",
|
||
"За демонстрацию силы постинга и абсолютной годноты"
|
||
],
|
||
"Эпик фейл": [
|
||
"За полнейший пиздец",
|
||
"Вручается тотальному серуну",
|
||
"Вручается за эталонный проёб всего и сразу",
|
||
"О фиксации эпик фейла",
|
||
"За вклад в фонд коллективного серунства",
|
||
"Подтверждает: так обосраться - это надо уметь",
|
||
"За достижение дна и уверенное его пробитие",
|
||
"О признании фейлом года без права апелляции",
|
||
"За демонстрацию того, как делать НЕ надо",
|
||
"Официальное признание постера позором Ежача"
|
||
]
|
||
}
|
||
|
||
# Пул комментариев для каждой категории
|
||
comment_pools = {
|
||
"Новогоднее поздравление": [
|
||
"Всем Ежачом верим, что так и будет!",
|
||
"Зафиксировано и заверенно, теперь сбудется, инфа 146%",
|
||
"Эти пожелания искренны, я гарантирую это!",
|
||
"Заверено Оператором, так что точно сбудется",
|
||
"Пожелания занесены в аналлы Ежача",
|
||
"Ежач видел - значит так тому и быть",
|
||
"Так и будет, сомневаться запрещено",
|
||
"Записано, сохранено, пути назад нет",
|
||
"Теперь это официальная реальность (в разработке)",
|
||
"С этого момента считается пророчеством"
|
||
],
|
||
"Ебать ты молодец": [
|
||
"Так держать!",
|
||
"Ты заслужил уважение всех анонов",
|
||
"Это было эпично!",
|
||
"Такого ещё не видел никто",
|
||
"Твой подвиг войдет в историю",
|
||
"Оче годно, одобряемо всем тредом",
|
||
"Мощно, анон",
|
||
"Это был просто отвал жопы",
|
||
"Восхищаемся всем Ежачом",
|
||
"Это просто вау"
|
||
|
||
],
|
||
"Эпик фейл": [
|
||
"Ты обосрался на глазах у всего треда",
|
||
"Господи, зачем ты это сделал",
|
||
"Аноны запомнят это на долгие годы",
|
||
"Таких фейлов у нас ещё не было",
|
||
"Даже Оператору стыдно",
|
||
"Интернет всё помнит",
|
||
"Аноны в ахуе",
|
||
"Поздравляем, ты вошёл не в ту историю",
|
||
"Ну ты и выдал",
|
||
"Лучше больше не пиши"
|
||
]
|
||
}
|
||
|
||
# Пул нижнего текста для каждой категории
|
||
signature_pools = {
|
||
"Новогоднее поздравление": [
|
||
"С Новым 2026 годом, анон!",
|
||
"Желаем исполнения желаний в 2026!",
|
||
"До встречи в новом году!",
|
||
"Пусть все обещания сбудутся!",
|
||
"С наступающим, анончик!",
|
||
"Новый год уже здесь!",
|
||
"2026 начинается прямо сейчас!",
|
||
"Береги себя в новом году!",
|
||
"С теплом, от анона к анону!",
|
||
"Увидимся в 2026!"
|
||
],
|
||
"Ебать ты молодец": [
|
||
"Так держать и дальше!",
|
||
"Продолжай в том же духе!",
|
||
"Ты - легенда!",
|
||
"Гордимся тобой, анон!",
|
||
"За тобой будущее Ежача!",
|
||
"Это было красиво!",
|
||
"Ежач одобряе!",
|
||
"Достойно!",
|
||
"Не останавливайся!",
|
||
"Уважение заслужено!"
|
||
],
|
||
"Эпик фейл": [
|
||
"Позор зафиксирован",
|
||
"Это не забудут",
|
||
"Удалить уже не получится",
|
||
"С этим тебе жить",
|
||
"Фейл принят без возражений",
|
||
"Не пиши больше",
|
||
"Это просто пиздец",
|
||
"Невероятный кринж",
|
||
"Смеялись всем тредом",
|
||
"Борды - это не твоё"
|
||
]
|
||
}
|
||
|
||
# Определение текстовых областей для каждой позиции
|
||
# Формат: (x, y, max_width, line_height)
|
||
text_areas = {
|
||
"title": (840, 610, 800, 50), # Заголовок
|
||
"content": (840, 740, 800, 40), # Основной текст
|
||
"comment": (840, 1120, 800, 40), # Комментарий
|
||
"signature": (1110, 1260, 600, 35) # Подпись/нижний текст
|
||
}
|
||
|
||
# Фиксированные размеры шрифтов
|
||
font_sizes = {
|
||
"title": 40,
|
||
"content": 34,
|
||
"comment": 34,
|
||
"signature": 28
|
||
}
|
||
|
||
# Цвет текста
|
||
FONT_COLOR = (232, 201, 165)
|
||
|
||
def wrap_text(text, font, max_width):
|
||
"""Разбивает текст на строки, чтобы он помещался в заданную ширину"""
|
||
lines = []
|
||
|
||
# Если текст уже содержит переносы строк, обрабатываем каждую часть отдельно
|
||
text_parts = text.split('\n')
|
||
|
||
for part in text_parts:
|
||
words = part.split()
|
||
current_line = ""
|
||
|
||
for word in words:
|
||
# Проверяем ширину текущей строки с новым словом
|
||
test_line = f"{current_line} {word}" if current_line else word
|
||
test_bbox = font.getbbox(test_line)
|
||
test_width = test_bbox[2] - test_bbox[0]
|
||
|
||
if test_width <= max_width:
|
||
current_line = test_line
|
||
else:
|
||
if current_line:
|
||
lines.append(current_line)
|
||
current_line = word
|
||
|
||
if current_line:
|
||
lines.append(current_line)
|
||
|
||
return lines
|
||
|
||
def draw_wrapped_text(draw, x, y, text, font, max_width, line_height, color):
|
||
"""Рисует текст с автоматическим переносом"""
|
||
lines = wrap_text(text, font, max_width)
|
||
|
||
for i, line in enumerate(lines):
|
||
draw.text((x, y + i * line_height), line, font=font, fill=color)
|
||
|
||
return len(lines)
|
||
|
||
def generate_certificate_image(category, mascot, user_data):
|
||
"""Генерация изображения сертификата"""
|
||
# Проверяем существование шаблона
|
||
template_path = TEMPLATES.get(mascot)
|
||
|
||
if not template_path or not os.path.exists(template_path):
|
||
st.error(f"Шаблон для маскота '{mascot}' не найден!")
|
||
st.info(f"Ожидаемый путь: {template_path}")
|
||
return None
|
||
|
||
# Проверяем существование шрифта
|
||
if not os.path.exists(FONT_PATH):
|
||
st.error(f"Шрифт не найден по пути: {FONT_PATH}")
|
||
return None
|
||
|
||
try:
|
||
# Загружаем шаблон
|
||
img = Image.open(template_path)
|
||
draw = ImageDraw.Draw(img)
|
||
|
||
# Загружаем шрифт OpenSans
|
||
title_font = ImageFont.truetype(FONT_PATH, font_sizes["title"])
|
||
content_font = ImageFont.truetype(FONT_PATH, font_sizes["content"])
|
||
comment_font = ImageFont.truetype(FONT_PATH, font_sizes["comment"])
|
||
signature_font = ImageFont.truetype(FONT_PATH, font_sizes["signature"])
|
||
|
||
except Exception as e:
|
||
st.error(f"Ошибка при загрузке ресурсов: {str(e)}")
|
||
return None
|
||
|
||
# Выбираем случайный текст из пулов
|
||
title = random.choice(title_pools[category])
|
||
comment = random.choice(comment_pools[category])
|
||
signature_text = random.choice(signature_pools[category])
|
||
|
||
# Формируем основной текст в зависимости от категории
|
||
if category == "Новогоднее поздравление":
|
||
main_text = f"Пожелания: {user_data.get('promise', '')}"
|
||
else:
|
||
main_text = f"Для анона: {user_data.get('username', '')}\nПричина вручения: {user_data.get('reason', '')}"
|
||
|
||
# Получаем параметры областей
|
||
title_x, title_y, title_width, title_line_height = text_areas["title"]
|
||
content_x, content_y, content_width, content_line_height = text_areas["content"]
|
||
comment_x, comment_y, comment_width, comment_line_height = text_areas["comment"]
|
||
signature_x, signature_y, signature_width, signature_line_height = text_areas["signature"]
|
||
|
||
# Рисуем текст на изображении с автоматическим переносом
|
||
# Заголовок
|
||
draw_wrapped_text(draw, title_x, title_y, title, title_font, title_width, title_line_height, FONT_COLOR)
|
||
|
||
# Основной текст
|
||
draw_wrapped_text(draw, content_x, content_y, main_text, content_font, content_width, content_line_height, FONT_COLOR)
|
||
|
||
# Комментарий
|
||
draw_wrapped_text(draw, comment_x, comment_y, comment, comment_font, comment_width, comment_line_height, FONT_COLOR)
|
||
|
||
# Подпись/нижний текст
|
||
draw_wrapped_text(draw, signature_x, signature_y, signature_text, signature_font, signature_width, signature_line_height, FONT_COLOR)
|
||
|
||
# Сохранение временного файла
|
||
temp_file = os.path.join(BASE_DIR, 'certificate.png')
|
||
try:
|
||
img.save(temp_file)
|
||
return temp_file
|
||
except Exception as e:
|
||
st.error(f"Ошибка при сохранении сертификата: {str(e)}")
|
||
return None
|
||
|
||
# Функция для загрузки изображения и конвертации в base64
|
||
def get_image_base64(path):
|
||
if os.path.exists(path):
|
||
try:
|
||
with open(path, "rb") as img_file:
|
||
return base64.b64encode(img_file.read()).decode()
|
||
except Exception as e:
|
||
st.error(f"Ошибка при чтении файла: {str(e)}")
|
||
return None
|
||
return None
|
||
|
||
# Функция для обновления категории
|
||
def update_category(new_category):
|
||
"""Обновляет категорию и очищает соответствующие поля"""
|
||
st.session_state.category = new_category
|
||
if new_category == "Новогоднее поздравление":
|
||
st.session_state.username = ''
|
||
st.session_state.reason = ''
|
||
else:
|
||
st.session_state.promise = ''
|
||
|
||
# Настройка страницы
|
||
st.set_page_config(
|
||
page_title="Glintwine for ejchan",
|
||
page_icon="🍷",
|
||
layout="wide"
|
||
)
|
||
|
||
# Кастомный CSS для оформления
|
||
st.markdown("""
|
||
<style>
|
||
.header-container {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
padding: 10px 0;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
.logo-img {
|
||
height: 60px;
|
||
width: 30px;
|
||
margin-right: 20px;
|
||
object-fit: contain;
|
||
}
|
||
.service-title {
|
||
font-size: 34px;
|
||
font-weight: bold;
|
||
color: #2E8B57;
|
||
margin: 0;
|
||
line-height: 1.2;
|
||
}
|
||
.service-subtitle {
|
||
font-size: 16px;
|
||
color: #666;
|
||
font-style: italic;
|
||
margin-top: 5px;
|
||
margin-bottom: 0;
|
||
opacity: 0.7;
|
||
}
|
||
.footer-container {
|
||
margin-top: 50px;
|
||
padding: 20px 0;
|
||
text-align: center;
|
||
width: 100%;
|
||
}
|
||
.footer-text {
|
||
font-size: 16px;
|
||
color: white;
|
||
opacity: 0.7;
|
||
font-style: italic;
|
||
text-align: center;
|
||
padding: 10px;
|
||
background-color: rgba(46, 139, 87, 0.1);
|
||
border-radius: 5px;
|
||
display: inline-block;
|
||
}
|
||
.main-title {
|
||
margin-top: 30px;
|
||
margin-bottom: 15px;
|
||
color: #ffffff;
|
||
}
|
||
.stButton > button {
|
||
transition: all 0.3s ease;
|
||
}
|
||
.stButton > button:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||
}
|
||
.text-info {
|
||
background-color: #f8f9fa;
|
||
padding: 10px;
|
||
border-radius: 5px;
|
||
border-left: 3px solid #2E8B57;
|
||
margin: 10px 0;
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
</style>
|
||
""", unsafe_allow_html=True)
|
||
|
||
# Шапка с логотипом и названием сервиса
|
||
logo_path = None
|
||
for ext in ['.svg', '.png', '.jpg', '.jpeg', '.webp']:
|
||
test_path = os.path.join(BASE_DIR, f'logo{ext}')
|
||
if os.path.exists(test_path):
|
||
logo_path = test_path
|
||
break
|
||
|
||
# Отображаем шапку
|
||
st.markdown("""
|
||
<div class="header-container">
|
||
""", unsafe_allow_html=True)
|
||
|
||
col1, col2, col3 = st.columns([1, 5, 1])
|
||
with col1:
|
||
if logo_path:
|
||
st.image(logo_path, width=60)
|
||
else:
|
||
st.markdown('<div style="font-size: 48px; margin-right: 20px;">🍷</div>', unsafe_allow_html=True)
|
||
|
||
with col2:
|
||
st.markdown('<p class="service-title">Glintwine for ejchan</p>', unsafe_allow_html=True)
|
||
st.markdown('<p class="service-subtitle">Drug for dreams does not exists, but you can drink some glintwine. Cause why not?</p>', unsafe_allow_html=True)
|
||
|
||
with col3:
|
||
pass
|
||
|
||
st.markdown("</div>", unsafe_allow_html=True)
|
||
|
||
# Проверка необходимых файлов
|
||
missing_templates = check_templates()
|
||
if missing_templates:
|
||
st.warning("⚠️ Некоторые шаблоны не найдены:")
|
||
for missing in missing_templates:
|
||
st.write(f" - {missing}")
|
||
|
||
if not check_font():
|
||
st.error(f"⚠️ Шрифт OpenSans не найден по пути: {FONT_PATH}")
|
||
|
||
# Основной заголовок
|
||
st.markdown('<h1 class="main-title">Генератор сертификатов для ежеанонов 🦔</h1>', unsafe_allow_html=True)
|
||
st.write("Создай уникальный сертификат для себя или другого анона!")
|
||
|
||
# Инициализируем session state для хранения данных формы
|
||
if 'category' not in st.session_state:
|
||
st.session_state.category = CATEGORIES[0]
|
||
if 'promise' not in st.session_state:
|
||
st.session_state.promise = ''
|
||
if 'username' not in st.session_state:
|
||
st.session_state.username = ''
|
||
if 'reason' not in st.session_state:
|
||
st.session_state.reason = ''
|
||
if 'mascot' not in st.session_state:
|
||
st.session_state.mascot = MASCOTS[0]
|
||
|
||
# Используем st.radio для выбора категории
|
||
st.write("### Выбери категорию:")
|
||
category = st.radio(
|
||
"Категория",
|
||
CATEGORIES,
|
||
index=CATEGORIES.index(st.session_state.category),
|
||
key="category_radio",
|
||
label_visibility="collapsed",
|
||
horizontal=True
|
||
)
|
||
|
||
# Обновляем session state при изменении категории
|
||
if category != st.session_state.category:
|
||
update_category(category)
|
||
|
||
# Показываем текущую выбранную категорию
|
||
st.write(f"**Выбрана категория:** {st.session_state.category}")
|
||
|
||
# Используем форму с фиксированным ключом
|
||
with st.form(key='certificate_form'):
|
||
st.write("### Заполни данные:")
|
||
|
||
# Динамические поля в зависимости от категории
|
||
if st.session_state.category == "Новогоднее поздравление":
|
||
promise = st.text_area(
|
||
"Твоё поздравление",
|
||
value=st.session_state.promise,
|
||
key='promise_input',
|
||
height=100
|
||
)
|
||
st.session_state.promise = promise
|
||
|
||
else:
|
||
username = st.text_input(
|
||
"Вставь номер поста или имя анона, кому будет выдано",
|
||
value=st.session_state.username,
|
||
key='username_input'
|
||
)
|
||
|
||
reason_label = "Причина молодца" if st.session_state.category == "Ебать ты молодец" else "Причина фейла"
|
||
reason_example = "Охуенный пост, познавательный" if st.session_state.category == "Ебать ты молодец" else "'Обосрался в дискуссии о чае'"
|
||
|
||
reason = st.text_area(
|
||
f"{reason_label} (например: {reason_example})",
|
||
value=st.session_state.reason,
|
||
key='reason_input',
|
||
height=80,
|
||
)
|
||
|
||
st.session_state.username = username
|
||
st.session_state.reason = reason
|
||
|
||
# Выбор маскота
|
||
mascot = st.selectbox(
|
||
"Выберите оформление",
|
||
MASCOTS,
|
||
index=MASCOTS.index(st.session_state.mascot) if st.session_state.mascot in MASCOTS else 0,
|
||
key='mascot_select'
|
||
)
|
||
st.session_state.mascot = mascot
|
||
|
||
# Дополнительные опции
|
||
save_to_gallery = st.checkbox("Сохранить в галерею", key='gallery_checkbox')
|
||
submit_button = st.form_submit_button(label='🎨 Сгенерировать сертификат')
|
||
|
||
if submit_button:
|
||
# Подготавливаем user_data
|
||
user_data = {}
|
||
if st.session_state.category == "Новогоднее поздравление":
|
||
user_data['promise'] = st.session_state.promise
|
||
else:
|
||
user_data['username'] = st.session_state.username
|
||
user_data['reason'] = st.session_state.reason
|
||
|
||
# Проверка заполнения полей
|
||
is_valid = True
|
||
error_message = ""
|
||
|
||
if st.session_state.category == "Новогоднее поздравление":
|
||
if not user_data.get('promise'):
|
||
error_message = "Заполните поле 'Твоё поздравление'!"
|
||
is_valid = False
|
||
else:
|
||
if not user_data.get('username'):
|
||
error_message = "Заполните поле 'Номер поста или имя анона'!"
|
||
is_valid = False
|
||
elif not user_data.get('reason'):
|
||
field_name = "Причина молодца" if st.session_state.category == "Ебать ты молодец" else "Причина фейла"
|
||
error_message = f"Заполните поле '{field_name}'!"
|
||
is_valid = False
|
||
|
||
if not is_valid:
|
||
st.error(error_message)
|
||
else:
|
||
# Генерация изображения с индикатором загрузки
|
||
with st.spinner("Генерируем сертификат..."):
|
||
cert_path = generate_certificate_image(st.session_state.category, st.session_state.mascot, user_data)
|
||
|
||
if cert_path:
|
||
# Отображение сертификата
|
||
st.success("Сертификат готов!")
|
||
st.image(cert_path, caption="Ура!")
|
||
|
||
# Кнопка скачивания
|
||
try:
|
||
with open(cert_path, "rb") as file:
|
||
st.download_button(
|
||
label="💾 Скачать сертификат",
|
||
data=file,
|
||
file_name="certificate.png",
|
||
mime="image/png"
|
||
)
|
||
except Exception as e:
|
||
st.error(f"Ошибка при подготовке файла для скачивания: {str(e)}")
|
||
|
||
# Сохранение в галерею
|
||
if save_to_gallery:
|
||
try:
|
||
now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||
unique_name = f"cert_{now}.png"
|
||
gallery_path = os.path.join(GALLERY_DIR, unique_name)
|
||
shutil.copy(cert_path, gallery_path)
|
||
|
||
# Сохраняем данные в CSV
|
||
new_entry = pd.DataFrame([[str(user_data), st.session_state.category, st.session_state.mascot, now, gallery_path]],
|
||
columns=['Resolution', 'Category', 'Mascot', 'Timestamp', 'ImagePath'])
|
||
try:
|
||
df = pd.read_csv(CSV_FILE)
|
||
except (pd.errors.EmptyDataError, pd.errors.ParserError):
|
||
initialize_csv()
|
||
df = pd.read_csv(CSV_FILE)
|
||
|
||
df = pd.concat([df, new_entry], ignore_index=True)
|
||
df.to_csv(CSV_FILE, index=False)
|
||
|
||
st.success("✅ Сертификат сохранён в галерее!")
|
||
|
||
except Exception as e:
|
||
st.error(f"Ошибка при сохранении в галерею: {str(e)}")
|
||
|
||
# Галерея сертификатов
|
||
st.header("Галерея сертификатов")
|
||
st.caption("Последние сгенерированные сертификаты")
|
||
|
||
try:
|
||
if os.path.exists(CSV_FILE) and os.path.getsize(CSV_FILE) > 0:
|
||
df = pd.read_csv(CSV_FILE)
|
||
if not df.empty:
|
||
# Получаем только существующие файлы
|
||
valid_images = []
|
||
for i, row in df.iterrows():
|
||
if os.path.exists(row['ImagePath']):
|
||
valid_images.append(row)
|
||
|
||
if valid_images:
|
||
# Показываем последние 9 сертификатов (3 ряда по 3)
|
||
recent_images = list(reversed(valid_images))[:9]
|
||
|
||
# Создаем сетку 3x3
|
||
for i in range(0, len(recent_images), 3):
|
||
cols = st.columns(3)
|
||
for j in range(3):
|
||
if i + j < len(recent_images):
|
||
with cols[j]:
|
||
row = recent_images[i + j]
|
||
try:
|
||
st.image(row['ImagePath'], caption=f"{row['Category']}")
|
||
except Exception as e:
|
||
st.error(f"Ошибка при загрузке изображения")
|
||
else:
|
||
st.write("Нет доступных изображений в галерее.")
|
||
else:
|
||
st.write("Пока пусто — будь первым!")
|
||
else:
|
||
st.write("Пока пусто — будь первым!")
|
||
|
||
except Exception as e:
|
||
st.error(f"Ошибка при загрузке галереи: {str(e)}")
|
||
st.write("Попробуйте сгенерировать новый сертификат.")
|
||
|
||
# Футер с надписью
|
||
st.markdown("""
|
||
<div class="footer-container">
|
||
<div class="footer-text">
|
||
Glintwine v1.1a. The d4d project
|
||
</div>
|
||
</div>
|
||
""", unsafe_allow_html=True) |